git subtree命令是在git之上,用shell脚本编写的一个子命令。 与git submodule相比,它提供了更方便的子仓库管理; 与filter-branch相比,它确保了每次检出节点的一致性。
原则上,git subtree应该是一个很流畅透明的使用流程, 但是因为我们有过squash/no-squash混用,加之push --force的行为, 在没有理解git subtree实现原理之前,一度给我们带来很大的困惑。
接下来我先将subtree命令做简单剖析,再分析我们项目中的错误使用,望读者对git subtree能有更深入的理解。
pull本质上也是fetch + merge。 在squash下,merge会查找最近一次squash节点,并在此基础上做squash。
push的本质是split新历史并推送到指定远端的分支。 注意:split接受的参数不能直接传递给push,所以遇到特殊需求,先手动split -b吧。
Squashed 'prefix/dir' changes from 69bd276..da8167e da8167e msg... dfafdae msg2... ... 69bd276 msgN... git-subtree-dir: prefix/dir git-subtree-split: da8167ea0ad433f5abb102d12b4bb78e9f823fe3
建议不要混用squash/no-squash,从一而终。 这个commit的message也是十分重要的,应为merge的时候会查找检验。
将子仓库的历史切分出来后,使用git merge -s ours将新的节点更新至当前分支信息。 这样,merge命令可以很快的定位上一次节点,做增量的更新。
Split 'prefix/dir' into commit '511145b80ecbdf471cbe0371a1619909a1e6a3a2' git-subtree-dir: prefix/dir git-subtree-mainline: 0b28ce2dbd843caf1b5cd5623b427ed6a17cca33 git-subtree-split: 511145b80ecbdf471cbe0371a1619909a1e6a3a2
ignore-joins (no-ignore-joins)
忽略上面squash/rejoin提交的节点信息,使用当前主仓库所有的历史信息,重做subtree的历史。
在某一次squash后,我们强推了我们的子仓库代码,导致历史不一致,无法push.
git subtree push --prefix prefix/dir sub-remote maseter
经典的错误提示: fatal: bad object da8167ea0ad433f5abb102d12b4bb78e9f823fe3
git log --grep da8167ea0ad433f5abb102d12b4bb78e9f823fe3查找到出错的commit:
commit 8c8bfb0ffb6475c0e23a9b4aac803ccaf0f3e235 Squashed 'prefix/dir' changes from 69bd276..da8167e ... git-subtree-split: da8167ea0ad433f5abb102d12b4bb78e9f823fe3
git subtree split --prefix prefix/dir 8c8bfb0ffb6475c0e23a9b4aac803ccaf0f3e235.. -b new-sub-tree git push sub-remote new-sub-tree:master
现在的解决方案(之后可正常subtree push):
git subtree split --prefix prefix/dir -b new-sub-tree --ignore-joins --rejoin git push sub-remote new-sub-tree:master