【问题标题】:Git internals: Modifying `git-merge-one-file` to not use working treeGit 内部:修改 `git-merge-one-file` 以不使用工作树
【发布时间】:2013-09-13 12:52:58
【问题描述】:

是否可以修改默认的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?

我用谷歌搜索了这些错误消息,但找不到任何有用的信息。

知道该怎么做吗?

【问题讨论】:

  • 目标是什么?表现?预览另一个脚本的合并?保留内容?
  • @WillPalmer 目标是在我的脚本中使用它,该脚本将分支 a 合并到分支 b 而不需要签出其中任何一个,并且使用临时索引文件所以索引文件和工作目录都没有被触及。
  • @RamRachum 正如我提到的in my answer below,据我所知,只有git notes 这样做。它在内部、专用和临时工作树中进行合并
  • @VonC 我想尝试使用内部的、专用的和临时的工作树,我试过了,但我得到了上面显示的错误。 (C 代码对我没有帮助,我不会读 C。)
  • @RamRachum 也许您也需要指定git-dir(指向您的repo/.git 文件夹)。如果不是,那么我引用的实现是我知道的唯一方法......

标签: git


【解决方案1】:

虽然 git 需要一个地方来完成它的工作,但您可以在“合并一个文件”操作期间将其指向不同的工作树位置。

我不知道这对于merge-one-file 是否/如何“开箱即用”,但要设置的环境变量是GIT_WORK_TREE

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

(您可以将env 与大多数但不是全部的shell 一起省略)。

可能“感觉更安全”:-) 或更方便某些用途的或多或少等效的方法是在另一个目录中工作,并使用 GIT_DIR 到 repo 的位置:

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

您甚至可以组合它们,同时设置GIT_DIRGIT_WORK_TREE

【讨论】:

  • 好的,这是一个很好的解决方法。除非有更简洁的方法(可能涉及修改 git-merge-one-file 甚至不尝试写入磁盘),否则这将是最好的答案。
  • 我现在试过了,但我得到了错误。我得到git checkout-index: my_file is not in the cache,然后是error: my_file: cannot add to the index - missing --add option?
  • 如果我有时间,我稍后会尝试这个。 git-merge-one-file 是一个 shell 脚本,当我非常简短地查看它时,我无法判断这是否可以“开箱即用”,正如我所指出的那样。
  • 我没有直接使用git-merge-one-file,我使用的是这个脚本:gist.github.com/cool-RR/6575042
【解决方案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

如果您不想隐藏,则剩下 diff 和补丁选项:将工作树更改保存到补丁,删除工作树更改,进行必要的更改,然后重新应用补丁

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 更改为根本不使用工作树,只需在索引上执行所有操作。
  • 索引不是真正的文件,它在内部已经是对象。所以你不能在不接触工作树的情况下使用索引。那么唯一的可能是将工作树更改保存到补丁中,git checkout foo.txt 将文件重置为索引,进行必要的更改,然后重新应用补丁
  • 我知道我可以使用read-tree 完全在索引上进行简单的合并,甚至是非快进合并,而无需触及工作树。那么为什么使用git-merge-one-file 进行合并需要工作树呢?
  • mmh 不知道用read-tree会发生什么,自己不要用。我认为merge-file 只适用于文件,而read-tree 则适用于索引。
  • in git-scm.com/docs/git-merge-file "git merge-file 被设计为 RCS 合并的最小克隆;"所以它真的不关心你的索引。基本上,与单个文件相比,Git 更适合在树和目录上工作
【解决方案3】:

要合并两个不同文件中的更改,您需要检查它们的内容:合并更改正在处理文件内容。内容工作在工作树中完成。在其他地方做工作并假装它不是工作树只是文字游戏。

如果您想在进行合并时保持当前工作树不变,请使用另一个工作树。 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。 Merge 确实必须有一个工作树来完成它的工作,但它不关心除了需要比较的内容之外的任何实际文件内容。

【讨论】:

  • 您能否解释一下为什么不能简单地将git-merge-one-file 固定为在索引而不是工作树中完成所有工作?如果可能的话,我宁愿避免创建临时文件。
  • 索引不携带内容,它是纯粹的元数据,显示当前工作树和你的下一次提交之间的关系。此外,合并可能会产生冲突,在这种情况下,您将不得不修复冲突并将更正的数据添加到生成的提交中 - 您在工作树中执行此操作,合并文件的内容被签出。
【解决方案4】:

git 中的合并是以下三路合并:

  • 来源('remote' 或 'theirs',你要合并的内容)
  • 目的地('local' 或 'ours',始终是工作树,HEAD 已在其中签出)
  • 共同祖先(或“base”)

请参阅“git rebase, keeping track of 'local' and 'remote'”中所示的“local”、“base”、“remote”、“merged”。
您可以在“git revert does not work as expected”中查看示例。

Subtree Merging”和“Git Objects”(以及you are using in your gist)中提到的git read-tree 是关于合并(用于子树合并),而不是文件内容(blob)。
git write-tree 可用于创建树对象,但其文档确实提到“索引必须处于完全合并状态”。 (当你想使用索引来合并文件时有点困难)。

git index (documented here) 用于记录您在工作树中暂存的内容(“merged”结果),作为合并解决方案的一部分。
它没有关于文件内容的所有信息,只有 pointers(“索引条目”)指向所述内容。它根本不是进行合并的正确结构。


甚至git-merge-one-file.sh 脚本本身也提到:

require_work_tree

函数来自git-sh-setup.sh脚本(见its documentation):

test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ||
die "fatal: $0 cannot be used without a working tree."

该要求来自commit 6aaeca90 (peff Jeff King):

合并一个文件工具早于GIT_WORK_TREE 的发明。

在大多数情况下,merge-one-file 只适用于GIT_WORK_TREE;它的大部分繁重工作都是通过正确尊重GIT_WORK_TREE 的管道命令完成的。


如果您真的不需要使用工作树,您可以尝试走为合并笔记选择的路线:
notes-merge.c 确实会创建自己的工作树合并git notes

【讨论】:

    【解决方案5】:

    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
    

    【讨论】:

      猜你喜欢
      • 2020-12-16
      • 1970-01-01
      • 2011-12-16
      • 1970-01-01
      • 2011-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多