使用子树合并策略,历史不会合并

  • 本文关键字:合并 历史 策略 git subtree
  • 更新时间 :
  • 英文 :


我正在尝试通过"子树合并"将外部SVN存储库用作存储库中的子树。 我相信这应该保持库中文件的历史记录完好无损,但它不起作用 - 合并到我的主分支中的子树中的库中的文件没有历史记录,但在我添加它们时提交 - 这是一个历史记录来显示我的意思,确切地说我将达到这种状态。

lappy8086:YACYAML jamie$ git log --graph 
* commit 0cc6c4e5061741e67d009f3375ce1d2bcd3ab540
| Author: James Montgomerie
| Date:   Thu May 17 12:04:43 2012 +0100
| 
|     Subtree-merge in libYAML (from a git-svn checkout).
|  
* commit b5af5af109d77f6adafebc3dcf5a4796a5035a2e
Author: James Montgomerie
Date:   Thu May 17 11:47:32 2012 +0100
First commit, add .gitignore.

以下是我正在尝试使其工作的方法:

# check out SVN repo
git svn clone http://svn.pyyaml.org/libyaml/branches/stable libYAML
# create my repo
mkdir YACYAML
cd YACYAML
git init
touch .gitignore
git add .gitignore
git commit -m "First commit, add .gitignore"
# Fetch from git-svn repo I got earlier
git remote add libyaml-svn ../libYAML/
git fetch libyaml-svn
git checkout -b libyaml-svn libyaml-svn/master
# Switch back to master, and try to merge in subtree
git checkout master
git read-tree --prefix=libYAML/ -u libyaml-svn/master
git commit -m "Merge in libYAML as subtree (from git-svn checkout of SVN repo)"

这"有效",但是,正如我所说,当我查看我的历史时,我希望看到 libYAML 存储库的完整历史记录,但我没有 - 如上所述。

当合并包括子目录中树的重父级时,git log [--follow] <filename>的行为方式似乎不一致,就像git subtree一样。

我进行了一些实验,如果您在第一个子树合并之前在源代码行上引入合成的重新父级提交,那么历史记录将开始通过git log --follow <filename>报告。

我可以看到的选项:

  1. 修复git log以遵循合并期间发生的重命名
  2. 更改git subtree为每个添加创建两个提交,首先在一次提交中重新设置树的父级,然后合并重新设置父级的提交
  3. 通过使用.git/info/graftsgit filter-branch完成 #2 来手动解决问题

解决方法:

$ git log --grep git-subtree-mainline
commit 8789f3c80122d1fc52ff43ab776a7b186f51c3c6
Merge: 0c11300 4757376
Author: John Sumsion <email>
Date:   Wed Apr 17 09:43:21 2013
    Add 'some-subdir/' from commit 'f54875a391499f910eeb8d6ff3e6b00f9778a8ab'
    git-subtree-dir: some-subdir
    git-subtree-mainline: 0c113003278e58d32116c8bd5a60f2c848b61bbb
    git-subtree-split: f54875a391499f910eeb8d6ff3e6b00f9778a8ab
$ git checkout -b fix 
Switched to a new branch 'fix'
$ mkdir -p some-subdir
$ git mv <files> some-subdir
$ git commit -m "Re-parenting files before subtree merge to preserve 'git log --follow' history"
$ echo <orig_merge> <orig_parent> <fixed_merge_parent> >> .git/info/grafts
$ git filter-branch --index-filter true --tag-name-filter cat master

以下提交在哪里:

  • orig_merge : 8789f3c80122d1fc52ff43ab776a7b186f51c3c6
  • orig_parent : 0c113003278e58d32116c8bd5a60f2c848b61bbb
  • fixed_merge_parent : 来自git commit的沙

不幸的是,在第一个子树合并后通过git subtree合并的后续更改似乎不会通过git log --follow <filename>报告,即使第一个子树合并是合成的重新父级。

出于某种原因,我似乎记得这在 Git 1.7.x 时间范围内工作正常,但那是遥远过去的模糊记忆,我没有时间研究。 在 Git 1.8.3.2 中观察到了上述情况。

嗯,一个答案是安装 git-subtree 并使用它来:

git subtree add --prefix=libYAML/ ../libYAML master

这导致了我从手动执行此操作中寻找(和期望(的内容:

lappy8086:YACYAML jamie$ git log --graph
*   commit 453d464cfc140c798d0dea85ab667fe16250181d
|  Merge: 9fb083d 0ca365a
| | Author: James Montgomerie 
| | Date:   Thu May 17 14:32:36 2012 +0100
| | 
| |     Add 'libYAML/' from commit '0ca365adeb5711bf918d4401e98fce00bab8b3ec'
| |     
| |     git-subtree-dir: libYAML
| |     git-subtree-mainline: 9fb083d923011dd990222da2a58eda42e5220cde
| |     git-subtree-split: 0ca365adeb5711bf918d4401e98fce00bab8b3ec
| |   
| * commit 0ca365adeb5711bf918d4401e98fce00bab8b3ec
| | Author: xi
| | Date:   Sun May 29 05:52:36 2011 +0000
| | 
| |     Bumped the version number and updated the announcement.
| |     
| |     git-svn-id: http://svn.pyyaml.org/libyaml/branches/stable@374 18f92427-320e-0410-9341-c67f048884a3
| |   
| * commit 210b313e5ab158f32d8f09db6a8df8cb9bd6a982
| | Author: xi
| | Date:   Sun May 29 05:29:39 2011 +0000
| | 
| |     Added support for pkg-config.
| |     
| |     git-svn-id: http://svn.pyyaml.org/libyaml/branches/stable@373 18f92427-320e-0410-9341-c67f048884a3
...etc...

不过,我仍然想知道在不依赖 git 子树的情况下执行此操作的正确方法。

加上jdsumsion所说的,子树合并(或git subtree,在一个步骤中做同样的事情(将不起作用,因为它所做的只是给你一个合并提交,将所有文件从根目录移动到你的子目录。为了维护文件历史记录,文件需要始终位于其最终位置,这需要重写所有以前的提交。

所以你这样做的方式是你不使用git filter-branch,因为这是一个非常不希望你使用它的小 bash 脚本。你应该改用 git-filter-repo。

该过程仅涉及将外部项目作为其自己的远程获取,就像子树合并一样,然后创建一个本地跟踪分支并重写该分支上的所有提交,以追溯始终使用您想要的路径。然后,您可以使用 unrelated-histories 标志将该分支合并到主项目中。

bash变量的使用主要是为了便于重用和可读性。如果您希望子目录包含空格等,我不希望这起作用,但在这种情况下手动调整应该相当容易。

export SUBTREE_PREFIX="MySubproject"
git remote add -f "${SUBTREE_PREFIX:?}-remote" https://my-git-repo.invalid/Subproject.git
git checkout "${SUBTREE_PREFIX:?}-remote"/master -b "${SUBTREE_PREFIX:?}-master"
# --force is to skip the "freshly cloned repo" check.
# All the refs we'll be operating on are fresh, even if the repo isn't
# Remove --dry-run once you've checked .git/filter-repo/fast-export.filtered
# to be sure that everything is correct.
git filter-repo --refs "${SUBTREE_PREFIX:?}-master" --to-subdirectory-filter "${SUBTREE_PREFIX:?}" --force --dry-run
git checkout master
git merge "${SUBTREE_PREFIX:?}-master" --allow-unrelated-histories
# Repeat for however many repos you need to add

就我自己而言,鉴于操作的全部意义在于将多个存储库的提交历史记录分组为一个,我还想在提交消息前面加上这些子项目来自哪个子项目,以便我可以在之后进行跟踪。

git filter-repo --refs "${SUBTREE_PREFIX:?}-master" --to-subdirectory-filter "${SUBTREE_PREFIX:?}" --message-callback="return message if message.startswith(b'${SUBTREE_PREFIX:?}:') else b'${SUBTREE_PREFIX:?}: ' + message" --force --dry-run

此外,如果您尝试推送不是由您提交的提交,某些 git 服务器将拒绝您的分支。 git rebase通常会将提交者设置为您,同时保持提交作者不变,但在这里您需要手动执行此操作。

git filter-repo --refs "${SUBTREE_PREFIX:?}-master" --to-subdirectory-filter "${SUBTREE_PREFIX:?}" --commit-callback '
        commit.committer_name = "You"
        commit.committer_email = "your@email.example"
' --message-callback="return message if message.startswith(b'${SUBTREE_PREFIX:?}:') else b'${SUBTREE_PREFIX:?}: ' + message" --force --dry-run

请记住,与git subtree或子模块不同,您将无法单独维护项目的独立副本和更改副本,因为它们将不再是任何历史记录。如果这是一个第三方库,您试图在树中保留供应商的最新副本,您会发现合并上游更改实际上是不可能的。

最新更新