从功能分支推送推送到不同的分支?

Pushing from feature branch pushed a different branch?

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

旁注:我还注意到,例如,当我尝试推送 develop 时,Git 经常抱怨 master 不同步(需要先拉) -- 但是当我不在那个分支上时,为什么它对 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

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

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

详细说明(可选)

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

然后,您的 Git 在那个 URL (remote.origin.url) 呼叫另一个 Git。其他 Git 有 自己的 b运行 目录、标签和其他引用。您的 Git 有他们的 Git 列出了他们所有的 b运行 目录、标签和其他参考资料。您可以通过 运行ning git ls-remote origin 看到您的 Git 看到的内容,它执行这两个步骤,打印出结果,然后停止。

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

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

  • 如果您没有列出 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 是一个 b运行ch-name 而 v1.2 是一个标签名称(通过查看b运行ch 和标签名),但如果它猜错了,或者你想确定,你可以拼出全名。

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

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

fetch和push主要是关于commits

获取和推送必须完成两件事。第一个,也是迄今为止最重要的,是 t运行sfer commits。没有提交,Git 就一无所有:提交是 Git 存在的原因。他们(间接)持有文件。

因此,如果您 运行 git push,您可以 Git 向他们 Git 提供您拥有的任何提交,他们没有,他们是需要。如果你 运行 git fetch,你的 Git 从他们的 Git 获得他们拥有但你不需要的任何提交。

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

时,您将需要他们的 b运行ch 上的提交

在运行将正确的一组提交提交到右侧 Git 之后,您的两个 Git 现在必须在最后一步中合作:设置一些姓名。如果你正在 git pushing,你有你的 Git 要求他们的 Git 设置 他们的 名字:你要求他们更新他们的 master,或者更新或创建他们的 jsm/logging,例如。例如,如果您正在 git fetch,您的 Git 会根据他们的 master 设置您的 origin/master,以及重命名他们的 master 的特殊技巧您的 origin/master,通过 remote.origin.fetch.

中设置的 refspecs 发生

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

因此,如果您 在命令行上命名一些 refspecs,您的 Git 将根据您列出的源名称获取或推送提交。接收者 Git 将 记住 通过设置一些名称获取或推送的提交——在你的 Git if fetching 中,在他们的 if pushing—基于您列出的目的地名称。

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

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

如果你只是 运行 git push origingit push——有或没有强制标志——你的 Git 使用一些默认设置。如果您没有遥控器的特定 refspec,您的 Git 使用 push.default。这就是它的五个设置的用武之地:

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

  • current:这告诉您的 Git 使用您的 当前 b运行ch。这可能是你所期待的。这相当于做 git push <em>remote</em> refs/heads/<em>b运行ch</em>: refs/heads/<em>b运行ch</em>.

  • upstream(又名 tracking):这告诉您的 Git 使用当前的 b运行ch 作为源,但使用它的上游名称作为目的地。也就是说,如果你当前的b运行ch是B,但是B的上游是origin/not-B,这相当于git push origin B:not-B.1

  • simple:类似于upstream但要求上游名称与当前b运行ch名称匹配。也就是说,如果 master 的上游有 origin/master,而你在 master 上,git push 会像你期望的那样推送到 origin/master——但是如果 B 推送到 origin/not-B 而你在 Bgit push 只是失败。

  • matching:你的 Git 遍历了他们 Git 的 b运行ch 名称列表(所有 git ls-remoterefs/heads/ 开头的名称)。对于他们拥有的每个 b运行ch 名称,您的 Git 推送 您的 b运行ch 同名。

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

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

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

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

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

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


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