1

git push 是否完全替换了远程仓库中的目标分支?我见过的所有撤消 git push 的解决方案都涉及在本地更改您的分支,然后重做您的 git push。你不能像撤消合并一样撤消 git push 吗?

4

2 回答 2

2

子问题“git push 是否完全替换了远程仓库中的目标分支?” 是有问题的。用“是”或“否”来回答充其量是误导。(与老律师的笑话“你停止殴打你的妻子吗”相比:如果你说“是”,则意味着你曾经殴打过她,如果你说“不”,则意味着你仍然会这样做![作为计算机程序员/逻辑学家,我想说正确的答案可能是“不” ,但没有那种暗示,但人们并不总是这样想。])

粗略地说,git push所做的事情与所做的事情相同git fetch,但方向相反。通过智能协议(如 ssh),有一个完整的协商序列,其中“你的一端”和“他们的一端”交换关于他们拥有哪些对象以及他们没有但想要哪些对象的信息。在 a 的情况下push,您告诉另一端:“我有一个分支标签,B,那指的是最终提交 C9,我可以把它推给你吗?”他可能会说“等等 C9 的父母是什么”,而你说“C8”,他会说“我也没有那个,C8 的父母是什么”并且你说“C6 和 C7 的合并”,他说“哦,我得到了这两个 OK 给我 C8 和 C9”。所以你的结果然后制作了一个“包装”(特别是一个“薄包装” - 细节并不那么重要,但这就是您看到的消息,“计数对象”和“压缩对象”等等,是关于)保存这些提交以及它们引用的树和文件。

一旦您的发送将对象发送到他们的一端,他们​​的一端会将推送的分支标签移动到新的分支提示。如果新的分支提示标记了一个与远程分支完全没有共同历史的分支[见下面的注释],那么 - 实际上 - 答案将是“是的,它完全取代了目标分支”。也就是说,它发送一包全新的提交对象,分支标签B进入新的(不同的)提交树。

不过,更常见的情况是,您的一端在上面描述的提交 C9 上具有类似的分支提示标签,因此您只需交出几个新提交(C8 和 C9),最后,他们将分支标签从 C7 移出到 C9。在这种情况下,答案是“不,它不会替换目标分支,它只是扩充它并将标签向前移动。”

不过,还有一个更复杂但实际上非常典型的场景。也许“他们的结局”在提交 C7 之后有一个你没有的提交 C10 。同时,您有要发送的提交 C8 和 C9。这是一个“非快进”操作。通常--bare会设置一个存储库来拒绝此类推送:他们会看到您要求添加 C8 和 C9,然后将分支标签从 C10 移动到 C9。这将留下一个“孤立”提交(C10),即没有标签的提交。如果你--force推动(并且被允许),git 无论如何都会继续这样做。[这是之前的注释:推送一个不相关的分支也需要--force,出于几乎相同的原因:git 检测到某些提交将变得未被引用,并让您说“我真的打算这样做”。]

与 All Things Git 一样,之前的提交“仍然存在”,只是不再被引用。当然,推送几乎总是去--bare回购。--bare由于没有充满 reflog 的目录,repo的事情变得有点棘手.git/logs,所以现在要找到像 C10 这样丢失的提交或整个丢失的提交链要困难得多。(您,或本次讨论中的“他们”,需要git fsck --unreachable找到无法访问的提交对象来恢复它们。)

现在,如果你做了一个你不应该做的推送——假设你打算推送提交 C8,而不是 C9——那么就有可能说服遥控器更改他的标签版本。假设您已更新master,并且remote.origin.url类似于ssh://barehost/dir/with/repo.git. 假设您可以使用repossh barehost登录主机:--bare

edithost$ ssh barehost
barehost$ cd /dir/with/repo.git
barehost$ git log --oneline --decorate -2 master
5e01371 (HEAD, master) commit-C9's one line description
309b36c commit-C8's one line desc.
barehost$ 

现在您可以重新标记master以指向提交 C8:

barehost$ git branch -f master master^

噗,--bare repo.git副本现在有一个以提交 C8 结束的分支。

但是,如果您只是在其上创建自己的分支edithost并强制推送它,则会获得相同的效果:

edithost$ git branch rewinder master^
edithost$ git push -f origin rewinder:master

这第二个git push将连接到barehost并且交换如下所示:

edithost (to barehost): branch label for you, master should go to 309b36c
barehost (to edithost): I got 309b36c but are you sure? force push?
edithost: yep, he's sure...
barehost: OK, I set master to 309b36c!

(实际上在最初的交换中强制标志是正确的,但我认为这样想象它更有趣/更难忘)。这真的很快;不需要“薄包”对象。

执行这些操作中的任何一个(<code>ssh 到barehost并手动重置git branch,或强制推送rewinder:master标签对)的缺陷是相同的:如果其他人(我们称他为 Pat)进来并barehost在这期间抓住了提交 C9你不小心推了它,而你“撤销”它的时间,那么帕特可能会感到困惑。他现在有提交 C9,据他所知,它应该在 C8 之后。根据接下来发生的情况,他可能会尝试使用它,或者他可能会将其推回barehost并使其重新出现,或者其他什么。

如果你做了一个错误的提交出现在某个共享的地方,那么“摆脱它”的“安全”(为了某种安全的价值)要做的事情就是在那个提交之后进行一个新的提交,这会消除坏的影响一。每个人都希望出现更多新的提交。但是,除非您提前安排,否则没有人会期望提交(或整个提交树)会消失。


您可以完全忽略其余部分

这是一个完整的示例,根据要求带有一些 ASCII 艺术。我不确定它会好得多。实际上可能更糟。:-)

barehost让我们从, 在 a 中的一些提交开始--bare repo.gitA0是初始提交,如下所示。(这个 git 树只包含一个文件,somefile,带有无意义的文本。)

barehost
A0 -- A1 -- A2                     <-- master

现在我将它克隆到edithost:

$ git clone ssh://[redacted]/repo.git
$ cd repo

我决定我不喜欢其中的任何内容,所以我将旧的 master 分支移开并创建一个新分支。(另外,我不想old_master跟踪origin/masteredithost

$ git branch -m master old_master
$ git config --remove-section branch.old_master

(现在追踪消失了)

$ git checkout --orphan master
Switched to a new branch 'master'
$ git rm somefile                 # or "git rm -rf ."
rm 'somefile'
$ echo '*.o' > .gitignore
$ git add .gitignore && git commit -m C0
[master (root-commit) f09f66f] C0
 1 file changed, 1 insertion(+)
 create mode 100644 .gitignore

这是生成的分支图。(我HEAD在所有情况下都省略了以避免混乱。)

A0 -- A1 -- A2                     <-- origin/master, old_master
C0                                 <-- master

确实希望这个新master的来跟踪远程主机(最终),所以我也会在这里设置它:

$ git config branch.master.remote origin
$ git config branch.master.merge refs/heads/master

现在让我们继续向下并通过 提交C1C7作为C7 一个新分支。内容并不重要,所以我将它们排除在外。然而,现在的画面是这样的:

A0 -- A1 -- A2                     <-- origin/master, old_master
C0 -- ... -- C5 -- C6              <-- master
                     \
                       C7          <-- feep

现在:

$ git push origin feep:feep
Counting objects: 25, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (21/21), done.
Writing objects: 100% (25/25), 2.27 KiB | 0 bytes/s, done.
Total 25 (delta 6), reused 0 (delta 0)
To ssh://[redacted]/repo.git
 * [new branch]      feep -> feep

如果我尝试“git push origin master:master”,当然会出现错误,但无论如何我都可以使用 -f 推送:

$ git push -f origin master:master
Total 0 (delta 0), reused 0 (delta 0)
To ssh://[redacted]/repo.git
 + dce5ffc...ae235f9 master -> master (forced update)

注意这次推送说的是“强制更新”(和“+”);那是-f旗帜。它也跳过了Counting objects,只是说Total 0。这是因为我们已经在推送分支“ feep ”的步骤中推送了C0提交。C7这次唯一发生的事情是从提交中删除标签“master”A2并将其粘贴在提交上C6(不是C7,那只是在“feep”分支上)。但是现在图片看起来像这样 edithost

A0 -- A1 -- A2                     <-- old_master
C0 -- ... -- C5 -- C6              <-- origin/master, master
                     \
                       C7          <-- origin/feep, feep

像这样barehost

A0 -- A1 -- A2                     [no label]
C0 -- ... -- C5 -- C6              <-- master
                     \
                       C7          <-- feep

这里有一个关键点:此时,repository 是一样的;只有标签名称不同。我可以使用 label old_masteronedithost并将其转换为提交 ID:

$ git rev-parse old_master
dce5ffcfcee7c5c66275189c75ea68219e8f26f5

并转到barehost并查看提交A0A1A2。在一些临时仓库上自己尝试一下(您的提交 ID 会有所不同):git log --oneline dce5ffcfcee7c5c66275189c75ea68219e8f26f5

无论如何,回到edithost. 让我们做一个新的克隆:

$ cd ..
$ git clone ssh://[redacted]/repo.git altrepo
Cloning into 'altrepo'...
remote: Counting objects: 25, done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 25 (delta 6), reused 0 (delta 0)
Receiving objects: 100% (25/25), done.
Resolving deltas: 100% (6/6), done.
Checking connectivity... done
$ cd altrepo

在此处运行git log --all显示C0通过C7. 让我们在分支C10上添加一个提交,然后推送它:C6master

$ git branch
* master
$ git log --oneline -1
ae235f9 C6
$ echo 'Some important documentation blah blah blah' > doc.txt
$ git add doc.txt && git commit -m C10
[master a22a608] C10
 1 file changed, 1 insertion(+)
 create mode 100644 doc.txt
$ git push origin master:master
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 368 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ssh://[redacted]/repo.git
   ae235f9..a22a608  master -> master
$

现在barehost有这个:

A0 -- A1 -- A2                     [no label]
C0 -- ... -- C5 -- C6 -- C10       <-- master
                     \
                       C7          <-- feep

同时,在repo(not altrepo) 我们还C10没有。如果我们再次强制推送,我们将在这里故意视而不见来说明“丢失”提交。

让我们回到那里并合并feepmaster一个非快进,在 commit 下C8,我们想要推回;并添加另一个实验性提交,我们将在下面“错误地”推送:

$ cd ../repo
$ git checkout master
$ git merge --no-ff feep -m C8
Merge made by the 'recursive' strategy.
 silly.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)
$ echo experiment > experimental.file
$ git add experimental.file && git commit -m C9

repo这个相当杂乱的图表显示了on中的提交树的状态edithost。请注意,就 git 而言, origin/master 还没有移动。(在../altrepogit 中知道 commit C10,但在 git 中./repo,它不知道。)

A0 -- A1 -- A2                     <-- old_master
C0 -- ... -- C5 -- C6 _            <-- origin/master
                    \  \_ C8 -- C9 <-- master
                     \   /
                       C7          <-- origin/feep, feep

让我们尝试在没有 -f 的情况下推动它:

$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 3 commits.
#   (use "git push" to publish your local commits)
#
nothing to commit, working directory clean
$ git push origin master:master
To ssh://[redacted]/repo.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'ssh://[redacted]/repo.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first merge the remote changes (e.g.,
hint: 'git pull') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

而不是git pull,让我们使用git fetch,因为fetch是正确的反义词push

$ git fetch
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ssh://[redacted]/repo
   ae235f9..a22a608  master     -> origin/master
$ git status
# On branch master
# Your branch and 'origin/master' have diverged,
# and have 3 and 1 different commit each, respectively.
#   (use "git pull" to merge the remote branch into yours)
#
nothing to commit, working directory clean

现在我们可以看到 commit C10。这是git log格式:

$ git log --oneline --decorate --graph --all
* 525b6fa (HEAD, master) C9
*   1933a04 C8
|\
| * c4c3302 (origin/feep, feep) C7
|/
| * a22a608 (origin/master, origin/HEAD) C10
|/
* ae235f9 C6
* e026711 C5
* f16f7cc C4
* 00d0a69 C3
* 45acc03 C2
* 07a1145 C1
* f09f66f C0
* dce5ffc (old_master) A2
* ad84d73 A1
* cee030f initial commit

这是一个 ASCII-art 版本,可能更清晰,也可能不清晰,但至少是我一直使用的风格:

A0 -- A1 -- A2                     <-- old_master

                      C10          <-- origin/master
                     /
C0 -- ... -- C5 -- C6 _
                    \  \_ C8 -- C9 <-- master
                     \   /
                       C7          <-- origin/feep, feep

我实际上不必C10在此处的任何地方保存提交的 ID,因为我在../altrepo. 让我们push -f

$ git push -f origin master:master
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 529 bytes | 0 bytes/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To ssh://[redacted]/repo.git
 + a22a608...525b6fa master -> master (forced update)

提交被复制到--barerepo,它现在有:

A0 -- A1 -- A2                     [no label]

                      C10          [no label]
                     /
C0 -- ... -- C5 -- C6 _
                    \  \_ C8 -- C9 <-- master
                     \   /
                       C7          <-- feep

(如果您在裸主机上,很难看到提交A0A1A2C10,但是如果您获取它们的 SHA-1 值并将它们交给git log,您可以看到它们实际上在那里。)

现在让我们意识到:哎呀,错误,并不是要推C9送到 barehost. barehost从以下位置倒带很容易edithost

$ git branch rewinder master^
$ git push -f origin rewinder:master
Total 0 (delta 0), reused 0 (delta 0)
To ssh://[redacted]/repo.git
 + 525b6fa...1933a04 rewinder -> master (forced update)

并且在 上barehostgit log --oneline表明它似乎已经消失了:

barehost$ git log --oneline --graph
*   1933a04 C8
|\
| * c4c3302 C7
|/
* ae235f9 C6
* e026711 C5
* f16f7cc C4
* 00d0a69 C3
* 45acc03 C2
* 07a1145 C1
* f09f66f C0

但实际上,我们现在拥有的是这样的:

A0 -- A1 -- A2                     [no label]

                      C10          [no label]
                     /
C0 -- ... -- C5 -- C6 _
                    \  \_ C8       <-- master
                     \   /  \_ C9  [no label]
                       C7          <-- feep

让我们使用我们对所有 SHA-1 的知识来“复活”(标记)所有丢失的提交和分支,在barehost

barehost$ barehost$ git branch resurrected_master dce5ffc
barehost$ git branch recovered_c10 a22a608
barehost$ git branch recovered_c9 525b6fa
barehost$ git log --oneline --decorate --graph --all
* 525b6fa (recovered_c9) C9
*   1933a04 (HEAD, master) C8
|\
| * c4c3302 (feep) C7
|/
| * a22a608 (recovered_c10) C10
|/
* ae235f9 C6
* e026711 C5
* f16f7cc C4
* 00d0a69 C3
* 45acc03 C2
* 07a1145 C1
* f09f66f C0
* dce5ffc (resurrected_master) A2
* ad84d73 A1
* cee030f initial commit

现在回到edithost,在repo

$ git fetch
From ssh://[redacted]/repo
 * [new branch]      recovered_c10 -> origin/recovered_c10
 * [new branch]      recovered_c9 -> origin/recovered_c9
 * [new branch]      resurrected_master -> origin/resurrected_master
$ git log --oneline --decorate --graph --all
* 525b6fa (HEAD, origin/recovered_c9, master) C9
*   1933a04 (origin/master, origin/HEAD, rewinder) C8
|\
| * c4c3302 (origin/feep, feep) C7
|/
| * a22a608 (origin/recovered_c10) C10
|/
* ae235f9 C6
* e026711 C5
* f16f7cc C4
* 00d0a69 C3
* 45acc03 C2
* 07a1145 C1
* f09f66f C0
* dce5ffc (origin/resurrected_master, old_master) A2
* ad84d73 A1
* cee030f initial commit

它们都还在那里,现在它们也被标记(作为来源/名称)edithost。(我们可能应该删除 rewinderedithost因为我们可能已经完成了。)

于 2013-08-05T02:05:45.677 回答
0

要撤消它,您有两个选项提交“撤消”代码或替换撤消代码。

要替换它,您将变基并在 force 标志打开的情况下推动。

提交选项会向其他人显示你改变了主意,它更安全,因为如果人们已经拉取和分叉,那么这样他们至少可以参考表单的来源

于 2013-08-04T20:45:14.640 回答