【问题标题】:Pushing from feature branch pushed a different branch?从功能分支推送推送不同的分支?
【发布时间】:2019-02-02 09:22:55
【问题描述】:

从我们的develop 分支,我用git checkout -b jsm/logging 创建了一个新分支。使用git push -u origin HEAD 进行更改、提交并推送到原点。做了一个 PR 并合并并删除了 remote 分支。进行了另一次调整,并用git commit --amend --no-edit -a 修改了我的最后一次提交。然后我检查了我的状态并使用git push -f 强制推送。令我惊讶的是,错误的分支被(力)推了!查看我的控制台日志(请注意,我将 g 别名为 gitststatus 的别名,cocheckout 的别名)。

旁注:我还注意到,例如,当我尝试推送 develop 时,Git 经常抱怨 master 不同步(需要先拉取)——但为什么它会与当我不在那个分支上时主人?好像有关系,不知道是什么问题。

控制台日志(“$”前的分支名称):

josh:~/Projects/my-project jsm/logging $ git commit --amend --no-edit  -a
[jsm/logging 4cdb3dc] add logging
 Date: Mon Aug 27 15:18:41 2018 -0400
 1 file changed, 12 insertions(+), 6 deletions(-)

josh:~/Projects/my-project jsm/logging $ git st
## jsm/logging...origin/jsm/logging [ahead 1, behind 1]

josh:~/Projects/my-project jsm/logging $ git push -f 
Counting objects: 1, done.
Writing objects: 100% (1/1), 685 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:my-org/my-project
 + 5a649bc...8d320d2 develop -> develop (forced update)
                     ^^^^^^^ why is it pushing a different branch than I'm on?!

josh:~/Projects/my-project jsm/logging $ g co develop
Switched to branch 'develop'
Your branch is up-to-date with 'origin/develop'.

josh:~/Projects/my-project develop $ g co jsm/logging
Switched to branch 'jsm/logging'
Your branch and 'origin/jsm/logging' have diverged,
and have 1 and 1 different commit each, respectively.
  (use "git pull" to merge the remote branch into yours)

josh:~/Projects/my-project jsm/logging $ git st
## jsm/logging...origin/jsm/logging [ahead 1, behind 1]

josh:~/Projects/my-project jsm/logging $ git push -fu origin jsm/logging
Counting objects: 11, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (11/11), 1.04 KiB | 0 bytes/s, done.
Total 11 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), completed with 5 local objects.
To git@github.com:my-org/my-project
 * [new branch]      jsm/logging -> jsm/logging
Branch jsm/logging set up to track remote branch jsm/logging from origin.

git 配置

alias.st=status -sb
alias.co=checkout
alias.cob=checkout -b
pull.rebase=true
push.default=matching
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
remote.origin.url=git@github.com:my-org/my-project
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.develop.remote=origin
branch.develop.merge=refs/heads/develop
branch.jsm/logging.remote=origin
branch.jsm/logging.merge=refs/heads/jsm/logging

【问题讨论】:

  • 你的 git 配置是 push.default=matching。这意味着您总是在推送所有名称与原点分支匹配的分支。尝试使用push.default=current 仅推送当前分支。不知道为什么没有推送jsm/logging,您确实正确设置了上游跟踪分支(git push -u origin HEAD)。
  • 在这种情况下,我通常使用git reflog 来帮助我了解发生了什么。它向您显示您本地 HEAD 所在的所有状态,包括切换分支。我希望你也觉得这很有帮助。
  • 而不是使用git push -f。您可能需要指定您将哪个分支强制推送到原点。例如,git push -f origin <branch name>
  • 谢谢@knugie。我确实使用了 reflog,但在这种情况下它没有照亮任何东西,所以我将它从 OP 的控制台输出中排除。
  • @Nisarg -- 谢谢,我已经为分支设置了跟踪 (push -u origin HEAD)。

标签: git github feature-branch


【解决方案1】:

knugie's comment 包含正确的原因:您将push.default 设置为matching。您可能一直期望使用current,甚至upstream。这就是 TL;DR 就在那里。

现代 Git 中默认的push.defaultsimple,大多数人认为它更安全——matching 是 2.0 之前的默认值,这让很多人很头疼。但要了解为什么会出现这种情况,让我们从高层次上看一下git push 做了什么。这也是git fetch 所做的,有点,所以在某种程度上两者都值得涵盖。不过,我将省略特殊的 fetch-specific 规则。

详细说明(可选)

首先,您的 Git 挑选出一个(单个)其他 Git 进行联系。 (如果您从多个远程获取或推送到多个远程,Git 一次执行一个。)另一个 Git 可以在某个 URL 找到。通常,您使用origin 之类的名称来提供 URL,但如果您愿意,也可以直接拼出一个。 (这很少是一个好主意——它主要是史前 Git 的遗留物。)或者你可以让 Git 弄清楚。如果只有一个遥控器,名为origin,Git 每次都会正确处理。 :-)

然后,您的 Git 在该 URL (remote.origin.url) 调用另一个 Git。其他 Git 有自己的 分支、标签和其他引用。你的 Git 有他们的 Git 列表所有他们的分支、标签和其他引用。您可以通过运行 git ls-remote origin 来查看您的 Git 看到的内容,它会执行这两个步骤,打印出结果,然后停止。

不过,对于git push,下一步取决于几件事:

  • 您是否在命令行中列出了参考规范? (如果是这样,Git 将使用您列出的 refspecs。)例如,refspecs 位于远程名称之后:git push origin <refspec1> <refspec2> ...。如果您在没有额外参数的情况下运行 git push,则您没有列出任何参考规范。

  • 如果您没有列出 refspecs,此遥控器是否有特殊的默认值? (如果是这样,这是默认设置。请注意,这是一个 refspec,而不是像 push.default! 这样的字符串字面量)

  • 否则,push.default 是默认值。它有五个设置,我们将在下面介绍。

对于git fetch,有类似的模式,但 Git 几乎总是使用remote.<em>remote</em>.fetch 设置结束,例如remote.origin.fetch,因为总是有这样的设置,而您作为用户倾向于只运行git fetch origin,甚至只是git fetch

这留下了另一个明显的问题:refspec 到底是什么?

参考规范

第二简单的 refspec 形式类似于 master:masterjsm/logging:jsm/logging——或者,对于 git fetch,类似于 master:origin/master。也就是说,有一个左侧名称、一个冒号: 字符和一个右侧名称。

左边的名字是来源,右边的名字是目的地。每个名称都是一个referenceref 名称,这意味着您可以拼出一个全名,例如refs/heads/master。如果您不拼出名称,Git 通常会正确猜测 master 是一个分支名称,v1.2 是一个标签名称(通过查看您拥有的分支和标签名称),但如果它猜到了错了,或者你想确定一下,你可以拼出全名。

但我说这是第二-最简单的形式。最简单的方法是完全省略冒号和目标:masterv1.2jsm/logging。在这里,fetch 和 push 的处理方式不同:它们仍然是操作的 ,但对于 git pushdestination 是资源。对于git fetch,目标是不保存——丢弃——名称。由于我们正在查看git push,因此我们可以跳过获取特殊性,而专注于git push 喜欢在两边使用相同 名称的方式。

值得注意的是:您可以在 refspec 中添加前导 +。这将设置强制标志仅用于该 refspec。下面我们来看一个简单的例子。

fetch和push主要是关于commits

fetch 和 push 必须完成两件事。第一个,也是迄今为止最重要的,是传输commits。没有提交,Git 什么都没有:提交是 Git 存在的原因。他们持有(间接)文件。

因此,如果您运行 git push,您的 Git 会将您拥有的、他们没有的、他们需要的任何提交给他们的 Git。如果你运行git fetch,你的 Git 会从他们的 Git 中获取他们拥有的任何你不需要但你不需要的提交。

您或他们需要的提交集由可达性 决定,这是一个相当大的话题。有关这方面的非常好的介绍,请参阅Think Like (a) Git。但是,过度总结的一句话是,当您推送您的分支时,他们将需要您的分支上的提交;当您获取他们的分支时,您将需要他们分支上的提交。

在将正确的提交集转移到正确的 Git 之后,您的两个 Git 现在必须在最后一步合作:设置一些名称。如果你是git pushing,你让你的 Git 让他们的 Git 设置他们的名字:你让他们更新他们的master,或者更新或创建他们的jsm/logging,例如。例如,如果您是 git fetching,那么您的 Git 会根据他们的 master 设置您的 origin/master,例如,将他们的 master 重命名为您的 origin/master 的特殊技巧是通过 refspecs 设置在remote.origin.fetch

使用 refspecs,Git 只会推送或获取您指定的内容

因此,如果您在命令行上命名了一组 refspec,您的 Git 将根据您列出的源名称获取或推送提交。接收 Git 将记住根据您列出的目标名称设置一些名称(如果 fetching 在您的 Git 中,如果 pushing 在他们的 Git 中)获取或推送的提交。

请注意,git push 可以将其最终名称设置操作作为礼貌请求发送 - *请将您的 master 设置为 a123456.... - 或者作为相当有力的命令:将您的 master 设置为 @987654379 @ 或者不吃晚饭直接上床睡觉! 他们的 Git 可以 仍然拒绝命令,但通常的默认设置是检查礼貌的请求,看看他们是否只是添加新的提交,并服从强制命令。

没有 refspecs,git push 回退,可能一直到push.default

如果您只是运行 git push origingit push(带或不带 force 标志),您的 Git 会使用一些默认设置。如果您没有特定的远程参考规范,您的 Git 使用 push.default。这就是它的五个设置的用武之地:

  • nothing:这会使git push 失败,迫使您列出一些参考规范。 (我自己尝试了一段时间,但发现它太痛苦了。)

  • current:这告诉你的 Git 使用你的当前分支。这可能是你所期待的。相当于做git push <em>remote</em> refs/heads/<em>branch</em>:refs/heads/<em>branch</em>

  • upstream(又名tracking):这告诉你的 Git 使用你当前的分支作为源,但使用它的上游名称作为目标。也就是说,如果你当前的分支是B,但是B的上游是origin/not-B,这相当于git push origin B:not-B1

  • simple:类似于upstream,但要求上游名称与当前分支名称匹配。也就是说,如果masterorigin/master 作为其上游,并且您在master 上,git push 正如您所期望的那样推送到origin/master - 但是如果B 推送到origin/not-B 并且您在Bgit push 根本就失败了。

  • matching:您的 Git 会遍历其 Git 的分支名称列表(所有以 refs/heads/ 开头的 git ls-remote 名称)。对于他们拥有的每个分支名称,您的 Git 都会推送 您的同名分支。

请注意,如果您使用--force 标志,则这适用于所有推送的分支。如果您的模式是matching,它适用于匹配的分支。这就是为什么您的输出显示为:

+ 5a649bc...8d320d2 develop -> develop (forced update)

你的 Git 发现你和他们都有refs/heads/develop。他们的5a649bc,你的是8d320d2,而5a649bc不是8d320d2的祖先。一个礼貌的请求——*请将你的develop 设置为8d320d2——会被拒绝,但是在强制标志生效的情况下,你的 Git 发送了一个命令,他们的 Git 服从了。这从他们的develop 中丢失了一些提交,所以他们说“强制更新”并且你的 Git 打印了那个和三个点(正常的非强制 push 只显示 两个 个点) .

如果您在自己的存储库中仍有提交 5a649bc,您可以轻松地从中恢复。如果没有,那就更棘手了。要恢复,如果有,请考虑运行:

git push origin +5a649bc:refs/heads/develop

这使用了一个 refspec,其中设置了 +(强制标志),source 是原始提交哈希 5a649bcdestination 是分支名称develop。请注意,在此处拼写 refs/heads/develop 是明智的(甚至可能是必要的),因为源“名称”是原始哈希 ID,因此您的 Git 不知道这应该是一个分支。


1这可能会或可能不会召唤莎士比亚的幽灵。 (或者是哈姆雷特国王?)

【讨论】:

  • 感谢您的详细解释 - 不完全确定我如何/为什么设置 push.default,但我会修复它!
  • 您可能将其设置为 matching 以使 Git 2.x 的行为类似于 Git 1.x。我怀疑网络上仍有一些地方建议这样做,因为有些人真的很喜欢旧的行为。 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-22
  • 2015-04-02
  • 1970-01-01
  • 1970-01-01
  • 2014-11-08
  • 1970-01-01
  • 2016-12-24
相关资源
最近更新 更多