Git子树 – 子树最新,但不能推

我正在使用Git子树与我正在处理的几个项目,以便在它们之间共享一些基本代码。 基本代码经常更新,升级可能发生在任何项目中,最终所有项目都会得到更新。

我遇到了一个问题,git报告我的子树是最新的,但推送被拒绝。 例如:

#! git subtree pull --prefix=public/shared project-shared master From github.com:**** * branch master -> FETCH_HEAD Already up-to-date. 

如果我推,我应该得到一个信息,没有什么可推…对吗? 对? 🙁

 #! git subtree push --prefix=public/shared project-shared master git push using: project-shared master To git@github.com:*** ! [rejected] 72a6157733c4e0bf22f72b443e4ad3be0bc555ce -> master (non-fast-forward) error: failed to push some refs to 'git@github.com:***' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Merge the remote changes (eg 'git pull') hint: before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. 

这可能是什么原因? 为什么推动失败?

我在这个博客评论https://coderwall.com/p/ssxp5q上find了答案;

如果你遇到“更新被拒绝,因为你的当前分支的提示是后面的,当你推动时,合并远程更改(例如'git pull')”问题(由于任何原因,尤其是使用git历史)那么你需要嵌套git命令,以便你可以强制推送到heroku。 例如,给出上面的例子:

 git push heroku `git subtree split --prefix pythonapp master`:master --force 

在窗口上嵌套命令不起作用:

 git push heroku `git subtree split --prefix pythonapp master`:master --force 

你可以先运行嵌套位:

 git subtree split --prefix pythonapp master 

这将(在很多数字之后)返回一个令牌,例如

 157a66d050d7a6188f243243264c765f18bc85fb956 

在包含的命令中使用它,例如:

 git push heroku 157a66d050d7a6188f243243264c765f18bc85fb956:master --force 

使用--onto标志:

 # DOESN'T WORK: git subtree push --prefix=public/shared project-shared master --onto=project-shared/master 

[编辑:不幸的是, subtree push不会转发 – 基本split ,所以操作必须在两个命令! 这样做后,我看到我的命令与其他答案中的命令是相同的,但是解释是不同的,所以我将把它留在这里。

 git push project-shared $(git subtree split --prefix=public/shared --onto=project-shared/master):master 

或者如果你不使用bash:

 git subtree split --prefix=public/shared --onto=project-shared/master # This will print an ID, say 0123456789abcdef0123456789abcdef, # which you then use as follows: git push project-shared 01234567:master 

我花了好几个小时仔细研究git-subtree的源代码,所以我希望你能够欣赏它);

通过运行subtree split开始subtree pushsubtree split将您的提交历史重写为应该准备推送的格式。 这样做的方式是,它剥夺任何path的public/shared/closures它,并删除任何关于没有它的文件的信息。 这意味着,即使你拉不压缩,所有的上游子仓库提交都被忽略,因为他们通过裸path命名文件。 (提交不触及public/shared/下的任何文件,或合并与父代相同的提交也将折叠。[编辑:此外,我已经find了一些壁球检测,所以现在我想这是只有当你拉不被压扁,然后在另一个答案中描述的简单的合并提交崩溃设法select非压扁的path,并丢弃压扁的path])结果是,它试图推动的东西最终包含任何工作人员致力于你推动的当前主机存储库,但不工作人员直接提交到子存储库或通过另一个主机存储库。

但是,如果使用 – --onto ,那么所有上游提交都被logging为OK,以便逐字使用,所以当重写过程遇到它们想要重写的合并父项之一时,它将保留它们而不是尝试以通常的方式重写它们。

对于“GitHub页面”types的应用程序,您将“dist”子树部署到gh-pages分支,解决scheme可能看起来像这样

 git push origin `git subtree split --prefix dist master`:gh-pages --force 

我提到这个,因为它与上面给出的heroku示例略有不同。 你可以看到我的“dist”文件夹存在于我的repo的master分支上,然后把它作为一个子树推送到原来的gh-pages分支上。

我之前也遇到过这个问题,下面是我如何解决这个问题。

我发现的是我有一个没有隶属于当地主分支的分支。 这个分支存在,它只是挂在虚空中。 在你的情况下,它可能被称为project-shared 。 假设是这种情况,当你做一个git branch你可以看到一个本地的project-shared分支,那么你可以通过做一个“ 附加 ”新的提交到你现有的project-shared分支:

git subtree split --prefix=public/shared --onto public-shared --branch public-shared

我理解的方式是git subtree将开始创build新的分支 – – 在这种情况下,其本地public-shared分支。 然后分支意味着创build一个分支,它只是replace旧的public-shared分支。

这将保留public-shared分支的所有以前的SHA。 最后,你可以做一个

git push project-shared project-shared:master

假设你有一个project-shared远程, 这将推动本地挂在无效的 project-shared分支到远程project-sharedmaster分支。

这是因为原有algorithm的局限性。 在处理合并提交时,原始algorithm使用简化的标准来切断不相关的父母。 特别是,它检查是否存在具有相同树的父代。 如果find这样一个父节点,它会折叠合并提交,并使用父提交,假设其他父节点的变化与子树无关。 在某些情况下,这会导致部分历史logging丢失,对子树进行实际更改。 特别是,它会丢弃提交的序列,这将触摸一个子树,但会导致相同的子树值。

让我们看一个例子(你可以很容易地重现),以更好地理解这是如何工作的。 考虑下面的历史(行格式是:commit [tree] subject):

 % git log --graph --decorate --pretty=oneline --pretty="%h [%t] %s" * E [z] Merge branch 'master' into side-branch |\ | * D [z] add dir/file2.txt * | C [y] Revert "change dir/file1.txt" * | B [x] change dir/file1.txt |/ * A [w] add dir/file1.txt 

在这个例子中,我们正在分裂dir 。 提交DE具有相同的树z ,因为我们已经提交了C ,撤消了提交B ,所以BC序列对dir没有任何作用,尽pipe它已经改变了。

现在让我们分裂。 首先我们分裂提交C

 % git log `git subtree split -P dir C` ... * C' [y'] Revert "change dir/file1.txt" * B' [x'] change dir/file1.txt * A' [w'] add dir/file1.txt 

接下来我们分裂提交E

 % git log `git subtree split -P dir E` ... * D' [z'] add dir/file2.txt * A' [w'] add dir/file1.txt 

是的,我们失去了两次提交。 这会导致在尝试推送第二个分割时出错,因为它没有这两个已经进入原点的提交。

通常你可以通过使用push --force来容忍这个错误,因为丢失的commit通常不会在其中有重要的信息。 从长远来看,这个错误需要修正,所以分割历史实际上会有所有的提交,如预期的那样触及dir 。 我希望修补程序包括隐式依赖关系的父提交的更深入的分析。

作为参考,这里是原始代码的一部分,负责行为。

 copy_or_skip() ... for parent in $newparents; do ptree=$(toptree_for_commit $parent) || exit $? [ -z "$ptree" ] && continue if [ "$ptree" = "$tree" ]; then # an identical parent could be used in place of this rev. identical="$parent" else nonidentical="$parent" fi ... if [ -n "$identical" ]; then echo $identical else copy_commit $rev $tree "$p" || exit $? fi 

埃里克·伍德拉夫的回答没有帮助我,但是下面这样做了:

我通常用'–squash'选项'git subtree pull'。 看起来这样做让事情变得更加难以调和,所以我需要做一个子树而不是压缩这个时间,解决一些冲突然后推动。

我必须补充说,被压扁的拉并没有发现任何冲突,告诉我一切都是好的。

您可以强制将本地更改推送到远程子树回购

 git push subtree_remote_address.git `git subtree split --prefix=subtree_folder`:refs/heads/branch --force 

另一个[更简单的]解决scheme是通过进行另一个提交,如果可以的话,推进远程的头部。 将这个高级头部拉到当地的子树后,您将可以再次从中推出。