0

在我们的develop分支中,我使用git checkout -b jsm/logging. 使用 .进行更改、提交并推送到原点git push -u origin HEAD。做了一个 PR 并合并并删除了远程分支。进行了另一次调整,并用git commit --amend --no-edit -a. 然后我检查了我的状态并用git push -f. 令我惊讶的是,错误的分支被(力)推了!查看我的控制台日志(请注意,我已别名ggitst是 的别名status,并且co是 的别名checkout)。

旁注:我还注意到,develop例如,当我尝试 push 时,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.

混帐配置

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
4

1 回答 1

4

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

push.default现代 Git 中的默认值是simple,大多数人认为它更安全——<code>匹配是 2.0 之前的默认值,这让很多人非常头疼。但要理解为什么会这样,让我们git push​​从高层次上看一下是什么。这也是git fetch某种原因,因此在某种程度上值得涵盖两者。不过,我将省略特殊的 fetch-specific 规则。

详细说明(可选)

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

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

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

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

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

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

对于git fetch,有一个类似的模式,但 Git 几乎总是使用设置结束,例如,因为总是有这样的设置,而你作为用户倾向于只运行,甚至只是。remote.remote.fetchremote.origin.fetchgit fetch origingit fetch

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

参考规格

refspec 的第二简单形式看起来像master:masteror jsm/logging:jsm/logging—or, for git fetch, like master:origin/master。也就是说,有一个左侧名称、一个冒号:字符和一个右侧名称。

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

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

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

fetch和push主要是关于commits

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

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

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

将正确的提交集转移到正确的 Git 之后,您的两个 Git 现在必须在最后一步合作:设置一些名称。如果你在git pushing,你让你的 Git 让他们的 Git 设置他们的名字:你让他们更新他们的master,或者更新或创建他们的jsm/logging,例如。例如,如果您正在使用,git fetch您的Gitorigin/master根据master他们的.masterorigin/masterremote.origin.fetch

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

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

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

没有 refspecs,git push回退,也许一直到push.default

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

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

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

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

  • simple:类似于upstream但要求上游名称与当前分支名称匹配。也就是说,如果masterhas origin/masteras its upstream, and you are on mastergit pushorigin/master预期推送到 - 但如果B推送到origin/not-B并且你在B,则git push完全失败。

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

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

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

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

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

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

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


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

于 2018-08-27T23:06:31.623 回答