如果远程分支被删除,git push应该会失败 - 如何?

时间:2016-06-04 14:10:28

标签: git github

无法找到自己,所以请求帮助专家!

我正在分支机构,做了我的更改并想要提交。有人在github上删除了这个分支。现在如果我推,一个新的分支出现在github上,我们不喜欢。如果远程分支(它已经跟踪远程分支)不再存在,我希望推送失败。这是因为我可能不知道远程分支删除,我不想每次使用git branch -avv和git remote prune和其他命令进行检查。

请澄清,谢谢。

6 个答案:

答案 0 :(得分:2)

如果您不定期使用git pull --prune清理本地环境,则您的本地环境仍会知道旧的远程跟踪分支,即使它们已在远程存储库中删除。

如果它对您的工作流程非常重要,那么确保不再发生这种情况的最佳方法是:

  1. 默认设置环境以执行此操作:git config --global fetch.prune true
  2. 作为练习,请务必在每次推动前拉动。
  3. 然后,如果您在分支上本地工作并且其远程跟踪分支已被删除,您应该收到以下消息:

    There is no tracking information for the current branch.
    Please specify which branch you want to merge with.
    See git-pull(1) for details.
    
    git pull <remote> <branch>
    
    If you wish to set tracking information for this branch you can do so with:
    
    git branch --set-upstream-to=origin/<branch> <branch>
    

答案 1 :(得分:1)

简短回答:您可能希望同时使用git push --force-with-lease git fetch --prune--force-with-lease选项要求本地和远程的Git不要太老,至少是版本1.8.5。任何现代发行版都有这个,但某些未命名的Linux发行版仍然使用Git 1.7版(!)。 (Git 2.7.2及更高版本也会更好地报告推送结果。)

请注意,这是 不是 git pull --prune--prune未记录为git pull的标志),它是<强> git fetch --prune 即可。 (在某些版本的Git中,使用--prune 可能git pull一起使用。它没有被记录为受支持。)

讨论(操作理论)

首先,让我们注意push的反面实际上是fetch(不是pull),而git pull首先运行git fetch }。在Git中,运行git pull基本上为您运行git fetch,然后为您运行git mergegit rebase。在你对Git非常熟悉之前,我相信你最好分开运行每一步。首先,任何一个步骤都可能失败,并且根据哪个步骤失败,从这些失败中恢复的动作是完全不同的。另一方面,您可能希望在进行合并或重组之前检查传入的更改(或者甚至可能选择完全执行其他操作)。如果您使用git pull,则必须在之前决定merge-vs-rebase ,以获得做出该决定所需的信息。

在任何情况下,都是git fetch操作将您的Git连接到遥控器上的Git。一旦你的Git和他们的Git谈话(通常通过互联网连接),你的Git就会从他们的Git中获取所有分支和相应提交哈希的列表。 --prune中的git fetch选项告诉您的Git从您的远程跟踪分支中移除您的Git可以告诉他们的Git不再拥有的任何分支。

更具体地说,考虑origin的情况。你的Git有远程跟踪分支,名为origin/masterorigin/develop,依此类推,但原因它有这些是你的Git通过互联网电话调用他们的Git他们的Git告诉你的Git&#34;我有一个名为master的分支和一个名为develop&#34;的分支,所以你的 Git使你的< / em> origin/master跟踪他们的master,并让您origin/develop跟踪他们的develop,依此类推。现在你的Git在网络电话上打电话给他们的Git,而这次他们的Git说'#34;我有masterfeature&#34;但对develop一无所知。由于他们的Git告诉你关于所有分支的所有信息,很明显他们不再拥有develop

你的Git现在必须决定:我们会丢弃origin/develop因为他们不再拥有develop吗?或者,我们是否保留origin/develop以防您仍在使用它?默认情况下,你的Git会保留它。使用--prune告诉你的Git,你希望你的Git丢弃它。如Briana Swift's answer中所述,您可能希望使用git config在全局(特定于用户)和/或本地(特定于存储库)设置中将fetch.prune设置为true ,以便这些git fetch操作自动放弃此类分支。

(请注意,当git pull调用git fetch时,pull会告知fetch仅将一个分支转移到其远程跟踪等效分支。 1 这也禁止修剪其他远程跟踪分支 - 避免git pull的另一个原因。不可否认,如果你的互联网连接速度很慢,它可能会提高{{1}的速度步骤只带来一个分支所需的对象。)

fetchfetch

的不对称

除非你以一种非常奇怪的方式设置Git, 2 它始终可以安全地运行push并从遥控器中获取所有更新。原因是你在本地分支机构工作。您可以查看git fetch和/或master和/或develop,最多将这些本地分支机构设置为feature远程跟踪分支,例如{{1} }}。这种跟踪只是告诉--track报告您前方和/或后方的距离,并使运行origin/master和/或git status更加方便。

当您运行git rebasegit merge时,您的Git会通过互联网电话调出他们的(来源)Git,获取所有分支的列表,然后收到< / em>(下载到您的存储库)跟踪其分支和更新远程跟踪分支所需的所有对象。然后你的Git会更新你的远程跟踪分支。如果选择修剪,您的Git也会删除任何过时的远程跟踪分支。由于您所依赖的内容位于本地分支中,并且这仅影响远程跟踪分支,因此非常安全。

然而,当你问你的Git git fetch到遥控器时,情况就不同了。这次你的Git 发送对象(从提交和/或标签开始,并将这些提交和/或标签所需的任何树和文件添加到遥控器)。然后,在发送所有这些对象后,您的Git会发送一系列&#34;更新分支或标记&#34;请求。

通过常规(非git fetch origin)推送,您的Git会发送一个&#34;如果您愿意,请将此分支设置为此新提交ID&#34;请求,每个分支一个更新。通过git push推送,您的Git会发送更强大的#34;将此分支设置为此新提交ID&#34;命令。如果您要发送标签,您的Git会发送相同的请求或命令,但会发送标签。 (这些都有一般形式,因为Git获得了笔记,你的Git也可以发送笔记更新请求或命令。命令被简化为单个&#34;强制标记&#34;,实际上,你可以设置或使用--force语法在每个引用的基础上清除它:--force有一个强制更新,用于远程引用 + ,以及两个礼貌请求,远程引用 git push remote l1:r1 +l2:r2 l3:r3 r2 。)

他们的Git现在有机会使用内置规则检查这些变更请求/命令,以及他们(远程Git的所有者)选择在r1和{{1钩子。默认情况下,如果分支不是快进,则拒绝更新分支的礼貌请求,并拒绝任何礼貌请求更新标记。 3 命令请求设置了强制标志 - 仍然允许,如果操作创建或删除分支或标记,则允许它是否具有强制标志。在任何情况下,钩子都可以拒绝更新(尽管他们不能允许默认规则拒绝的更新)。

r2

这些规则在大多数情况下都能很好地工作,但在某些情况下它们还不够。强制推卸rebase操作就是这样一种情况,而且当你且仅当该分支实际存在时,你特别希望更新远程Git上的分支是另一种情况。这是pre-receive的用武之地。

请注意,每当我们将操作拆分为&#34;从远程获取,然后推送到远程&#34;时,我们就会遇到一个问题:无法保证我们的推送,我们在做之后就做了fetch,是获取后发生的 only push 4 操作。例如,假设我们在我们的机器上运行update,Bob在Bob的机器上运行--force-with-lease,而Bob正在使用我们相同的遥控器,Bob的机器偷偷摸摸他的&#34;删除分支foo&#34;在我们的获取和推动之间?我们完成提取并开始检查以确保--force-with-lease仍然有效。然后Bob运行他的推送,删除分支git fetch --prune origin && git rev-parse origin/foo && git push origin foo。然后,在我们的机器上,我们确定分支git push origin :foo确实存在于遥控器上 - 直到Bob刚刚删除它! - 我们继续推origin/foo,我们成功了创造它。

(同样的事情可能发生在rebase-and-force-push上:我们认为我们拥有来自foo的所有内容;我们将其全部重新定义,而Bob推动他的变化;然后我们强制推动我们的变革和失去鲍勃的改变。当然,一般来说,我们不应该这样做,除非包括鲍勃在内的所有人都同意我们允许我们改变那个分支。鲍勃可以从此恢复,因为鲍勃还有鲍勃&# 39; foo的版本。但它充其量只是令人讨厌。)

foo选项专为这些情况而设计。一般的想法是,我们不是简单地命令其他Git设置引用,而是告诉其他Git 两个事物:&#34;设置引用&#34;和&#34;我们认为它目前设置为&lt; value&gt;&#34;。在我们的案例和rebase案例中,我们应该做的是告诉远程Git我们在远程跟踪分支中存储的值:我们认为origin/foo目前是foo,而如果这是正确的,我们会命令其他Git将其设置为--force-with-lease

如果Bob偷偷改变或删除分支refs/heads/foo,我们的信念 - 它当前a123456... - 变得不正确。远程Git用&#34来回答我们的命令;好吧,我可能已经服从了,但你对参考的价值你错了。&#34;这结束了&#34;获取以获得旧值&#34;之间的竞争。和&#34;更新以设置新值&#34;。这确实意味着失去这场比赛的人必须重新开始,如果合适的话,但这总是会发生。

请注意,在您指定b987654...参数的Git方面,有很多方法可以指定您期望的值。但最简单的方法是让你的Git从远程跟踪分支中获取值,这是你默认获得的。因此,您可以忽略the documentation中列出的大多数可选值,但如果您想要特别偏执,则可以注意到&#34; experimental&#34;请注意并明确说明:

foo

(我相信经过这么长时间后,实验应该宣布成功,a123456...不应该改变。:-))

请注意,--force-with-lease非常安全,甚至可能是默认值。没有它的唯一推送,被它拒绝,是那些你的推送或者以快进方式恢复从远程分支的尖端明确删除的提交,或者创建新分支的推送与旧分支曾经拥有的名称相同(最后一个只是你的情况,复活已删除的分支)。

1 这要求你的Git至少是版本1.8.4。在旧版本中,Git甚至没有更新远程跟踪分支。这可能会完全抑制所有修剪;我没有调查过。

2 特别是,如果您依赖于远程跟踪分支而不是本地分支,或者您配置了一个获取镜像(以便没有远程跟踪)只需从远程强制复制分支和所有本地分支,然后获取可能会丢失您所依赖的对象。

3 在1.8.2之前的Git版本中,标签更新使用了分支更新规则,因此默认情况下,遥控器允许对标签进行快进操作。

4 或者实际上,如果其远程计算机上的远程Git实际上对登录的某些其他用户本地更新到那个远程机器,他们在当地为当地的Git做本地的事情。

答案 2 :(得分:0)

最后,选项 - force-with-lease 工作,但我想要的只是附加 origin b1:b1 。以下是我最近的踪迹的完整描述。所以这意味着,是否建议在使用Git时始终使用完整的语法形式?即从不说只是 git push 但是说 git push remote-name local-branch-name:remote-branch-name ?学习git不仅有趣,而且可怕。

$ git pull
From https://github.com/<user-name>/<repo-name>
 x [deleted]         (none)     -> origin/b1
Your configuration specifies to merge with the ref 'refs/heads/b1'
from the remote, but no such ref was fetched.

$ git fetch --prune

$ vi README.md

$ git push --force-with-lease
Everything up-to-date

$ git push
Everything up-to-date

$ git status
On branch b1
Your branch is based on 'origin/b1', but the upstream is gone.
  (use "git branch --unset-upstream" to fixup)
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

$ git commit -am "dd"
[b1 9026fd5] dd
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git config --global fetch.prune
true

$ git push --force-with-lease
Everything up-to-date

$ git status
On branch b1
Your branch is based on 'origin/b1', but the upstream is gone.
  (use "git branch --unset-upstream" to fixup)
nothing to commit, working directory clean

$ git push
Everything up-to-date

$ git push --force-with-lease
Everything up-to-date

$ git push --force
Everything up-to-date

$ git push --force-with-lease origin b1:b1
To https://github.com/<user-name>/<repo-name>.git
 ! [rejected]        b1 -> b1 (stale info)
error: failed to push some refs to 'https://github.com/<user-name>/<repo-name>.git'

$ git push origin b1:b1
Counting objects: 14, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (14/14), 1.18 KiB | 0 bytes/s, done.
Total 14 (delta 4), reused 0 (delta 0)
To https://github.com/<user-name>/<repo-name>.git
 * [new branch]      b1 -> b1

答案 3 :(得分:0)

请在下方再次查看此工作流程。有趣的是, git config --global fetch.prune 似乎无关紧要,除非它默认为true,即使它为null。我把它清空了,但似乎有同样的反应。

$ git config --global fetch.prune ""

$ git config --global fetch.prune

$ git remote show origin
* remote origin
  Fetch URL: https://github.com/userid/repo.git
  Push  URL: https://github.com/userid/repo.git
  HEAD branch: master
  Remote branches:
    master                 tracked
    refs/remotes/origin/b1 stale (use 'git remote prune' to remove)
  Local branches configured for 'git pull':
    b1     merges with remote b1
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

$ git remote prune origin
Pruning origin
URL: https://github.com/userid/repo.git
 * [pruned] origin/b1

$ vi README.md

$ git commit -am "ddd"
[b1 3c20f89] ddd
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git push
Everything up-to-date

$ git push  --force-with-lease origin
Everything up-to-date

$ git push --force-with-lease origin b1:b1
To https://github.com/userid/repo.git
 ! [rejected]        b1 -> b1 (stale info)
error: failed to push some refs to 'https://github.com/userid/repo.git'

$ git push origin b1:b1
Counting objects: 17, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (17/17), 1.40 KiB | 0 bytes/s, done.
Total 17 (delta 4), reused 0 (delta 0)
To https://github.com/userid/repo.git
 * [new branch]      b1 -> b1

答案 4 :(得分:0)

您可以尝试pre-push挂钩。

#!/bin/bash

while read local_ref local_sha remote_ref remote_sha
do
    git ls-remote | if grep -q $remote_ref$
        then
            exit 0
        else
            echo $remote_ref does not exist, not pushing
            exit 1
        fi
done

我还没有对这个钩子进行全面测试。已知问题是,在git push启用此挂钩的情况下,您无法在远程仓库中创建新分支。

答案 5 :(得分:0)

注意:Git 2.10 + documents a more precise use of git push --force-with-lease

commit 9eed4f3Johannes Schindelin (dscho)(2016年8月4日) 请commit 64ac39a查看commit eee98e7commit d132b32(2016年7月26日)和John Keeping (johnkeeping)(2016年7月25日)。
(由Junio C Hamano -- gitster --合并于commit e674762,2016年8月10日)

  

--force-with-lease=<refname>:<expect>将保护指定的ref(单独),   如果要更新,则要求其当前值   与指定值<expect>相同(允许为   与远程跟踪分支不同,我们有refname,   或者我们甚至不必拥有这样的远程跟踪分支   使用此表格。)   如果<expect>是空字符串,则指定的ref必须不存在。

     

push:允许使用--force-with-lease

推送新分支      

如果分支没有上游信息,很可能就是这样   是新创建的,可以安全地在正常快进下推送   规则。
  放松--force-with-lease检查,以便我们不会立即拒绝这些分支,而是尝试将它们作为新分支推送,使用空SHA-1作为预期值。

     

事实上,已经可以使用显式推送新的分支   --force-with-lease=<branch>:<expect>语法,所以我们在这里做的就是make   如果未指定明确的“expect”值,则此行为为默认值。

请记住,如Git 2.13(2017年第2季度)所述,在某些情况下可以忽略此推送选项:请参阅“push --force-with-lease by default”。