为啥我不能推送这个最新的 Git 子树?

Posted

技术标签:

【中文标题】为啥我不能推送这个最新的 Git 子树?【英文标题】:Why can't I push this up-to-date Git subtree?为什么我不能推送这个最新的 Git 子树? 【发布时间】:2012-11-25 05:11:28 【问题描述】:

我将 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 (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

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

【问题讨论】:

你为什么不做 git 告诉你的事情: git pull ? Git pull 与我的问题无关。但是,是的,git pull 给了我一条最新消息,因为原始仓库中没有任何新内容。 听起来你所在的分支在主 HEAD 所在的后面——例如,远程主 HEAD 所在的 ... -> A -> B -> C 的历史记录C,但您当前的分支位于 A(或未连接到 C 的 A 的某些后代)。我会看看我的 git 历史来确定。 我遇到了同样的问题。我一直在使用--rejoin 来加速子树推送。当我手动逐步完成子树推送时,运行subtree split --rejoin,拆分子树分支只有返回到上次重新加入的历史记录,而它通常包含整个子树历史记录。对我来说,这是非快进错误的直接原因。我仍然不确定为什么子树拆分会生成截断的历史记录。 【参考方案1】:

我在这个博客评论https://coderwall.com/p/ssxp5q找到了答案

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

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

【讨论】:

如何在 Windows 上运行这个命令?我收到error: unknown option 'prefix' 太棒了,这救了我的命 :) 尽管它确实提供了一种解决方法,但它并不能解释这种行为。 是的,但可能大多数以这种方式使用子树是向 heroku 推送的一种方式。一般来说,heroku 不被认为是供多个用户推送和拉取的事实上的 git 存储库。 我很难确定herokupythonapp 在答案中的关系。将其翻译成 Heroku 特定的语言:git push <your subtree's origin> `git subtree split --prefix=Path/to/subtree master`:master --force【参考方案2】:

在 Windows 上,嵌套命令不起作用:

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

你可以先运行嵌套位:

git subtree split --prefix pythonapp master

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

157a66d050d7a6188f243243264c765f18bc85fb956

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

git push heroku 157a66d050d7a6188f243243264c765f18bc85fb956:master --force

【讨论】:

这是一个更清洁的解决方案。为我工作! 当我运行 git subtree split --prefix subtreedirectoryname master 时,我得到致命的模棱两可的参数 'master': unknown revision or path not in the working tree 这是一个很好的解决方案,我之前一直收到git subtree is not a command。使用您的解决方案后,我能够将文件夹部署到 heroku 而不是我的所有 repo 很好的解决方案和节省时间 由于某些非常奇怪的原因,此命令会在第二次推送时删除我的子树分支【参考方案3】:

使用--onto 标志:

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

[编辑:不幸的是subtree push 不会将--onto 转发到底层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 push 首先运行subtree split,它将您的提交历史重写为应该准备好推送的格式。它这样做的方式是,它将public/shared/ 从任何包含它的路径的前面剥离,并删除有关没有它的文件的任何信息。 这意味着即使你拉出非压缩,所有上游子存储库提交都将被忽略,因为它们以裸路径命名文件。(不涉及public/shared/ 下的任何文件的提交,或合并与父级相同的提交也被折叠。[编辑:另外,我已经发现了一些壁球检测,所以现在我认为只有当你拉出非挤压,然后简单的合并提交折叠在另一个答案中描述的设法选择非压缩路径并丢弃压缩路径。])结果是,它尝试推送的内容最终包含有人提交给您正在推送的当前主机存储库的任何工作,但是不工作的人直接提交到子存储库或通过另一个主机存储库。

但是,如果您使用--onto,则所有上游提交都被记录为可以逐字使用,因此当重写过程遇到它们作为要重写的合并的父级之一时,它将保留它们而不是试图以通常的方式重写它们。

【讨论】:

我推荐这个答案,它已经帮助了我两次。描述是我需要了解拆分和推送到正确位置的工作原理。谢谢。 什么是“项目共享”? @ColinD "project-shared" 是遥控器的名称。这是您在配置要从中获取和推送到的位置时选择的名称。您可以使用git remote -v 获取遥控器列表。 @entheh 你花在这上面的时间让我很开心。谢谢! 我发现当您的子存储库有一个名称与前缀相同的目录时,总是会发生此错误,例如您将子存储库作为“libxxx”添加到主存储库中,并且您的子存储库本身也有一个名为“libxxx”的子目录。在这种情况下,git 会错误地将子存储库中的提交视为主存储库中的提交并尝试拆分它们。 --onto 选项帮助 git 识别子存储库中已经存在的提交。【参考方案4】:

对于“GitHub pages”类型的应用程序,您将“dist”子树部署到 gh-pages 分支,解决方案可能如下所示

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

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

【讨论】:

为什么我必须在这里使用--force 您当然可以尝试不强制,如果您遇到错误,您可能会发现它很有帮助。我不太记得我的情况是什么【参考方案5】:

也有这个问题。以下是我根据最佳答案所做的:

给定:

#! 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 (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

输入这个:

git push <origin> 72a6157733c4e0bf22f72b443e4ad3be0bc555ce:<branch> --force

注意'git subtree push'输出的token在'git push'中使用。

【讨论】:

【参考方案6】:

我以前也遇到过这个问题,下面是我的解决方法。

我发现我有一个未附加到本地主分支的分支。这个分支存在,它只是悬在虚空中。在您的情况下,它可能称为project-shared。假设是这种情况,当您执行 git branch 时,您可以看到本地 project-shared 分支,然后您可以通过执行以下操作将新提交“追加”到现有的 project-shared 分支:

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

我理解的方式是git subtree 将从--onto 开始创建新分支,在这种情况下它是本地public-shared 分支。那么分支就是创建一个分支,只是替换旧的public-shared分支。

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

git push project-shared project-shared:master

假设你也有一个project-shared 遥控器;这会将本地挂在虚空中project-shared 分支推送到远程project-sharedmaster 分支。

【讨论】:

【参考方案7】:

这是由于原始算法的限制。在处理合并提交时,原始算法使用简化的标准来切断不相关的父母。特别是,它检查是否存在具有相同树的父级。如果找到这样的父级,它将折叠合并提交并改用父级提交,假设其他父级具有与子树无关的更改。在某些情况下,这将导致删除部分历史记录,这些历史记录对子树有实际更改。特别是它会丢弃提交序列,这会触及子树,但会产生相同的子树值。

让我们看一个示例(您可以轻松复制)以更好地了解其工作原理。考虑以下历史(行格式为: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,所以B-C 序列对dir 没有任何作用,即使它有更改。

现在让我们进行拆分。首先,我们在提交 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 来容忍这个错误,因为丢弃的提交通常不会包含关键信息。从长远来看,该错误需要修复,因此拆分历史实际上将包含所有提交,如预期的那样触及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

【讨论】:

2020 年 5 月 9 日(星期六)修复的错误? @klimkin 该错误已修复。请参阅:contrib/subtree:修复“子树拆分”跳过合并错误。 github.com/git/git/commit/…【参考方案8】:

Eric Woodruff 的回答对我没有帮助,但以下内容对我有帮助:

我通常使用“--squash”选项来“git subtree pull”。看起来这确实让事情变得更难协调,所以这次我需要在不压缩的情况下进行子树拉取,解决一些冲突然后推送。

我必须补充一点,压扁的拉动没有显示任何冲突,告诉我一切正常。

【讨论】:

【参考方案9】:

另一个[更简单]的解决方案是如果可以的话,通过再次提交来提升遥控器的头部。将这个高级头拉入本地子树后,您将能够再次从中推送。

【讨论】:

这正是我解决远程存储库与本地子树不同步的相同问题的方法。【参考方案10】:

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

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

【讨论】:

【参考方案11】:

基于 Chris Jordan 解决方案的 Windows 用户快速 powershell

$id = git subtree split --prefix pythonapp master
Write-Host "Id is: $id"
Invoke-Expression "git push heroku $id`:master --force"

【讨论】:

【参考方案12】:

这就是我根据@entheh 所说的话写的。

for /f "delims=" %%b in ('git subtree split --prefix [name-of-your-directory-on-the-file-system-you-setup] -b [name-of-your-subtree-branch]') do @set token=%%b
git push [alias-for-your-subtree] %token%:[name-of-your-subtree-branch] --force

pause

【讨论】:

以上是关于为啥我不能推送这个最新的 Git 子树?的主要内容,如果未能解决你的问题,请参考以下文章

Git混淆-如何将本地更改恢复为最新的远程推送?

为啥 Git 说我的主分支“已经是最新的”,即使它不是?

为啥合并后GIT会说“已经是最新的”,但分支之间的差异仍然存在?

为啥我不能在 github 中推送还原的提交?

Git挂在状态添加提交

Git-SVN拉取最新进展