Git内部原理:修改`git-merge-one-file`使其不使用工作树

9
是否有可能修改默认的git-merge-one-file程序,使其在不触及工作树的情况下仅使用索引完成所有操作,完全不修改它?
更新和细节:现在我明白了,在文件级别合并(其中合并是针对文件中的行而不是整个文件进行操作)时,不能在不使用工作树的情况下进行。(与针对整个文件进行操作的合并不同。)所以我得使用工作树。
另一个细节:如果合并可以自动完成而无需手动解决,则我可以接受该解决方案仅在这些情况下起作用。如果合并不是自动的,则只需显示错误消息即可。(当然,始终保持清洁。)
另一个细节:我不直接使用git-merge-one-file,而是在此脚本中使用它:https://gist.github.com/cool-RR/6575042 我尝试遵循@torek的建议并使用临时工作树(正如您在脚本中看到的那样),因为这似乎是目前最好的方向。问题是,我会得到这些错误:
git checkout-index: my_file is not in the cache
error: my_file: cannot add to the index - missing --add option?

我在谷歌上搜索了这些错误信息,但没有找到有用的内容。
有什么主意可以解决吗?

目标是什么?性能?预览合并以便绑定到另一个脚本中?保留内容? - Will Palmer
@WillPalmer 目标是使其在我的脚本中使用,将分支 a 合并到分支 b,无需检出任何一个分支,并使用临时索引文件,以便既不触碰索引文件也不触碰工作目录。 - Ram Rachum
正如我在下面的答案中提到的那样,据我所知,只有git notes可以这样做。它会在一个内部、专用和临时的工作树中进行合并。 - VonC
我已经将GIT_WORK_TREE设置为指向我的临时文件夹,并在git仓库中运行该命令。 - Ram Rachum
有人能帮我解决更新时出现的错误吗? - Ram Rachum
显示剩余2条评论
5个回答

5

虽然 Git 需要一个工作区,但是您可以将其指向不同的工作树位置,以进行“合并单个文件”操作。

我不知道这是否适用于“merge-one-file”,但要设置的环境变量是 GIT_WORK_TREE

env GIT_WORK_TREE=/some/where/else git ...

对于大多数shell而言,您可以省略env,但并非所有shell都适用。

一个更或多或少等效的方法是在另一个目录中工作,并使用GIT_DIR指定存储库的位置,这可能会“感觉更安全” :-) 或者对某些目的更方便:

cd /some/where/else
env GIT_DIR=/place/with/repo/.git git ...

你甚至可以将它们组合在一起,同时设置 GIT_DIRGIT_WORK_TREE


好的,这是一个不错的解决方法。除非有更简洁的方式(可能涉及修改git-merge-one-file以避免尝试写入磁盘),否则这将是最佳答案。 - Ram Rachum
我现在尝试了这个,但是出现了错误。我得到了 git checkout-index: my_file is not in the cache 然后是 error: my_file: cannot add to the index - missing --add option? - Ram Rachum
如果我有时间,我会稍后尝试实验一下。git-merge-one-file是一个shell脚本,当我简要查看它时,我无法确定它是否可以“开箱即用”,正如我所指出的那样。 - torek
我并没有直接使用 git-merge-one-file,而是使用了这个脚本:https://gist.github.com/cool-RR/6575042 - Ram Rachum

2
不,但最简单的方法是将更改暂存(stash),合并(merge),暂存(stage),然后取消暂存(unstash):
git stash save
git merge-file foo.txt base-foo.txt their-foo.txt
git add foo.txt
git stash pop

如果您不想使用stash,那么您只能选择差异和补丁选项:将工作树更改保存到补丁中,删除工作树更改,进行必要的更改,然后重新应用补丁。
git diff -p --raw foo.txt > foo.txt.diff
git checkout -- foo.txt
git merge-file foo.txt base-foo.txt their-foo.txt
patch -p1 < foo.txt.diff    

对我来说这不是一个有效的解决方案,因为隐藏有它自己的复杂性。我正在寻找一个解决方案,只需更改git-merge-one-file以完全不使用工作树,并在索引上执行所有操作。 - Ram Rachum
索引不是一个真正的文件,它已经在内部成为对象。因此,您无法在不触及工作树的情况下使用索引。那么唯一的可能性就是将工作树更改保存到补丁中,git checkout foo.txt 以将文件重置为索引状态,进行必要的更改,然后重新应用补丁。 - CharlesB
我知道我可以使用read-tree在索引上完成简单的合并,甚至是非快进式的合并,而不需要触及工作树。那么为什么使用git-merge-one-file进行合并时需要工作树呢? - Ram Rachum
我没有问关于 merge-file,我问的是类似但不同的 git-merge-one-file,它与 merge-index 一起使用。 - Ram Rachum
抱歉,我以为你打错了,我不知道这个命令。对我来说太过于“git-hacky”了。 - CharlesB
显示剩余2条评论

2
要合并两个不同文件的更改,您需要检查它们的内容:合并更改是在文件内容上进行操作的。内容的处理是在工作树中完成的。在其他地方进行工作并假装它不是工作树只是文字游戏。
如果您想在进行合并时保留当前的工作树不受影响,则可以使用另一个工作树。git clone 很便宜,它就是为这种情况而建立的。
# merge into branch m2 from branch m1 but leave your (non-m2) worktree untouched:
git clone --no-checkout -b m2 . ../m2-work
cd ../m2-work
git reset    # this will warn about the empty worktree, you could instead do
#              git read-tree HEAD to get the same effect without the chatter
git merge origin/m1
git push origin m2

请注意在克隆时加上--no-checkout参数。合并操作需要有工作区才能执行,但它并不关心除了需要比较的文件内容以外的任何实际文件内容。


请问为什么 git-merge-one-file 不能简单地在索引中完成所有工作而不是在工作树中完成?如果可能的话,我更愿意避免创建临时文件。 - Ram Rachum
1
索引不包含内容,它是纯粹的元数据,显示当前工作树与下一个提交之间的关系。此外,合并可能会产生冲突,在这种情况下,您需要修复冲突并将更正后的数据添加到生成的提交中--这是在工作树中完成的,其中已检出合并文件的内容。 - jthill

2
Git中的合并是三方合并,包括:
  • 源(“remote”或“theirs”,您要合并的内容)
  • 目标(“local”或“ours”,始终是工作树,其中HEAD已检出)
  • 共同祖先(或“base”)
请参见 "git rebase,跟踪“local”和“remote”" 中所示的“local”、“base”、“remote”、“merged”。您可以在 "git revert不按预期工作的示例" 中看到一个示例。

git read-tree在 "子树合并" 和 "Git对象" 中提到(以及您在gist中使用的)是关于合并(用于子树合并),而不是文件内容(blob)。
git write-tree可用于创建树对象,但它的文档确实提到“索引必须处于完全合并状态。”(当您想要使用索引合并文件时有点困难)。

Git索引(在此有文档)记录您从工作树中暂存的内容(合并结果为“merged”,作为合并决策的一部分)。它没有有关文件内容的所有信息,仅包含对所述内容的“指针”(“索引条目”)。这不是进行合并的正确结构。
即使是 git-merge-one-file.sh 脚本本身也提到了:
require_work_tree

该函数来自于git-sh-setup.sh脚本(请参见其文档):
test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ||
die "fatal: $0 cannot be used without a working tree."

该要求来自commit 6aaeca90peff Jeff King):

合并单个文件工具是在 GIT_WORK_TREE 发明之前就存在了。

大多数情况下,merge-one-file 可以与 GIT_WORK_TREE 配合正常工作;它的大部分重活都由正确处理 GIT_WORK_TREE 的管道命令完成。


如果您真的不想使用工作树,可以尝试选择“合并笔记”所选的路线:
notes-merge.c会创建自己的工作树来合并git notes

1

Jeff King 帮助我解决了这个问题,我更新了脚本使其正常工作:

https://gist.github.com/cool-RR/6575042

#!bash
if [ -n "$2" ]; then
  export SOURCE=$1 ;
  export DESTINATION=$2 ;
else
  export SOURCE=HEAD ;
  export DESTINATION=$1 ;
fi

export GIT_INDEX_FILE=`git rev-parse --show-toplevel`/.git/aux-merge-index ;
export GIT_WORK_TREE=`create_temporary_folder gm_`;
echo $GIT_INDEX_FILE
trap 'rm -f '"'$GIT_INDEX_FILE'"'; rm -rf '"'$GIT_WORK_TREE'" 0 1 2 3 15 ;
mkdir $GIT_WORK_TREE/.git
set -e ;
git read-tree -im `git merge-base $DESTINATION $SOURCE` $DESTINATION $SOURCE ;
#echo Finished read-tree
#sleep 1000
git merge-index git-merge-one-file -a
#echo Finished merge-index
git write-tree \
| xargs -i@ git commit-tree @ -p $DESTINATION -p $SOURCE -m "Merge $SOURCE into $DESTINATION" \
| xargs git update-ref -m"Merge $SOURCE into $DESTINATION" refs/heads/$DESTINATION ;
exit 0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接