如何在强制推送后进行拉取(Git)?

71
假设我从一个Git仓库拉取更改,然后该仓库的作者通过强制推送到中央仓库。现在由于历史已重写,我无法进行拉取。
如果作者已经正确地使用了强制推送,那么如何拉取新提交(并放弃旧提交)?
我知道这是糟糕的Git工作流程,但有时您无法避免这种情况。

10
可能是强制更新后进行git pull的重复问题。 - Bergi
4个回答

130

放弃本地修改

如果你想要放弃你的修改,可以使用 fetchreset 命令。举个例子,如果你有一个名为 origin 的远程仓库和一个名为 master 的分支:

$ git fetch origin
$ git reset --hard origin/master # Destroys your work

保留您的本地更改

如果您不想放弃您的工作,您将需要进行git rebase --onto操作。假设旧的origin看起来像这样:

A ---> B ---> C
              ^
              origin/master

而你拥有这个:

A ---> B ---> C ---> X ---> Y ---> Z
              ^                    ^
              |                    master
              origin/master

现在,上游的更改改变了一些事情:

A ---> B ---> C ---> X ---> Y ---> Z
 \                                 ^
  ---> B'---> C'                   master
              ^          
              origin/master

您需要运行git rebase --onto origin/master <C> master,其中<C>是上游更改之前旧的origin/master分支的SHA-1。这会给您提供以下内容:

A ---> B ---> C ---> X ---> Y ---> Z
 \
  ---> B'---> C'---> X'---> Y'---> Z'
              ^                    ^
              |                    master
              origin/master

注意现在 B、C、X、Y 和 Z 是“无法访问的”。它们最终将被 Git 从您的存储库中删除。与此同时(90 天内),Git 将在 reflog 中保留一份副本,以防您犯了错误。

修复错误

如果您使用 git resetgit rebase 命令时出错,并意外丢失了一些本地更改,可以在 reflog 中找到这些更改。

在评论中,有用户建议使用 git reflog expire 命令并带上 --expire=now 参数,但不要运行此命令,因为这将摧毁您的安全网。拥有 reflog 的整个目的是当您运行错误命令时,Git 有时会挽救您。

基本上,这个命令会立即销毁上述示例中的 B、C、X、Y 和 Z 提交,因此您无法重新获取它们。运行此命令没有真正的好处,除了可能节省一点磁盘空间,但 Git 将在 90 天后自动清除数据,因此这个好处是短暂的。


2
@user:我强烈建议使用大写字母,除非人们想要销毁Git作为安全网保留的数据,否则不要运行该命令。 - Dietrich Epp
1
@user:那个命令是不必要的。在推送时,Git会自动处理正确的事情。问题在于,在执行git reset --hard之后,您可能会意识到自己犯了某种可怕的错误,需要恢复旧数据。这就是引用日志的作用,它是一个安全网,当您进行错误的git reset时,可以使用它来恢复数据,因此这是永久性破坏引用日志中的数据的最糟糕的时机。 - Dietrich Epp
3
@user:任何阅读此评论的人,请不要运行该命令。我甚至不确定该命令尝试实现什么,除了破坏安全网以保存您的数据以防您输入错误的命令,比如答案中的 git reset --hard。再次强调,**除非您非常确定想要这样做(又为什么要这样做?),否则请勿销毁reflog。 - Dietrich Epp
2
问题在于,在这种情况下,Git 不够智能。如果有人进行了强制推送,我认为一定是有原因的。而且,如果我在检出分支后没有更改任何内容,那么用 origin 的更改覆盖我的分支就没问题了。因此,与其尝试合并导致冲突的更改,当发生强制推送时,应该中止简单的 git pull,并让用户有一个选项来进行强制拉取。 - Sebastian Zartner
1
就Git而言,C和C'是不相关的提交。这就是为什么在“git rebase”命令的命令行参数中必须指定C和C'的原因(请注意,“origin/master”在此处只是C'的另一个名称)。 - Dietrich Epp
显示剩余9条评论

2
如果没有本地提交,这将从强制推送中恢复您的检出。您将与远程分支保持最新状态,并稍后提交本地工作。
git fetch
git stash
git reset --hard origin/master # destroys your work
git stash pop # restores your work as local changes
git mergetool # fix any conflicts

此时,您的本地更改与之前相同。您的检出已经更新到主分支或您正在工作的任何分支上的所有更改。


1
与其隐藏起来,我更愿意将我的本地更改提交,然后在origin/branch上进行交互式变基。 - Marc L.

1

我遇到了这种情况的稍微修改过的版本。这是我所做的:

初始条件

A--->B--->C--->D--->E
                    |
             local-1/master

A--->B--->C--->D--->E
                    |
              origin/master

A--->B--->C--->D--->E
                    |
             local-2/master

压缩和强制推送

A--->B--->CDE
           |
    local-1/master

A--->B--->CDE
           |
     origin/master

A--->B--->C--->D--->E
                    |
             local-2/master

同步本地-2/master的更改

$ git reset --soft B


    A--->B---> (local uncommitted changes)
         |
  local-2/master


$ git stash save "backup"


    A--->B
         |
  local-2/master

$ git pull origin master


    A--->B--->CDE
               |
        local-2/master

1
如果您尚未对本地分支进行任何更改,可以尝试以下命令序列。请记住,这是一种粗略的方法来实现您所要求的功能。
git checkout master
git pull
git remote update origin -p
git branch -D myBranch
git checkout myBranch

git remote update origin -p 是可选的。

如果您进行了更改,而且不关心本地分支的内容,则可以尝试此操作:

git stash
git stash drop
git checkout master
git pull
git remote update origin -p
git branch -D myBranch
git checkout myBranch

这两种技术都非常冗长而繁琐,但都能完成工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接