1. 问题背景

本地建立了一个git工程开发完成后才在远程新建一个工程,需要将本地的项目强制push到远程,保留本地的git的提交记录:

2. 问题解决

本地提交后Push,会出现没有远程分支,此时添加Define remote
image.png
Name为origin,URL为git复制项目地址去除.git
image.png
IDEA有个console会输出当前git命令的操作日志,发现fast-forwards错误,因为本地的代码远程没有,而远程为空项目,所以直接复制命令加上-f,git push -f 强制push
image.png

3. fast-forward原理

今天完成一个功能开发,提交代码的时候,突然提示如下错误:

  1. To C:/Users/Alpha/AppData/Local/Temp/d20170730-15308-3dbr6w/.git
  2. ! [rejected] master -> master (non-fast-forward)
  3. error: failed to push some refs to 'C:/Users/Alpha/AppData/Local/Temp/d20170730-15308-3dbr6w/.git'
  4. hint: Updates were rejected because the tip of your current branch is behind
  5. hint: its remote counterpart. Integrate the remote changes (e.g.
  6. hint: 'git pull ...') before pushing again.
  7. hint: See the 'Note about fast-forwards' in 'git push --help' for details.

意思就是本次提交被远程仓库拒绝了,因为当前分支无法与远程仓库对应起来。远程仓库对应分支默认有个指针指向最新提交到仓库的 commit ,而所有的本地仓库的分支都可以看做是从这个 commit 分散开来的。也就是本地分支的最后一次 push 到仓库的 commit 一定与仓库对应分支的最新一次 commit 是相同的,否则就无法对接。也就是会出现上面的错误提示。如果是正常 push 到仓库,正确的完成 commit 更新,那么这次更新就是一个 fast-forward 更新,而如果不理会错误警告用本地更新强制覆盖仓库,就是一次 no-fast-forward 更新,很明显,**no-fast-forward** 更新会导致记录丢失

那么这种问题是如何发生的呢?比如有两个人都是从仓库的 master 分支克隆到本地,然后分别开发。master 本身有一个指针 HEAD 指向最后一次 commit 记录 commit-0 。A 先完成一个功能,并 push 到仓库,这次 commit 记为 commit-A,这也就是一次 fast-forward 更新,此时仓库的 master 分支的 HEAD 指针就指向了 commit-A。接下来 B 也完成了一个功能,要向仓库 push commit-B,如果没有做额外操作,肯定会出现上面的错误。

知道错误是如何发生的,就可以避免了。既然仓库有了更新,那么就要先把仓库的更新拉取到本地。这里有两种方式可以拉取:一是直接使用 git pull 命令,该命令会在拉取的同时会直接与本地对应分支进行合并,如果确信仓库的更新与本地不会发生冲突,那么可以直接使用。但是很可能 A 与 B 都对同一些文件做出了修改,那么必然导致冲突。不过既然知道会冲突也只能老老实实解决冲突了,不管是 fetch 先解决冲突在合并还是 pull 先合并再解决冲突,这个过程少不了的,除非你确定仓库的更新是没用的可以直接抛弃,就可以执行 git push -f 强制覆盖到仓库,这会导致仓库中某些记录丢失。

我们借助于 githug 28 关来模拟看看:

  1. $ git push -u origin master
  2. To C:/Users/Alpha/AppData/Local/Temp/d20170731-15124-1ywoym1/.git
  3. ! [rejected] master -> master (non-fast-forward)
  4. error: failed to push some refs to 'C:/Users/Alpha/AppData/Local/Temp/d20170731-15124-1ywoym1/.git'
  5. hint: Updates were rejected because the tip of your current branch is behind
  6. hint: its remote counterpart. Integrate the remote changes (e.g.
  7. hint: 'git pull ...') before pushing again.
  8. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
  9. $ git log origin/master --oneline
  10. 68ad000 Fourth commit
  11. $ git log --oneline
  12. b977ec3 Third commit
  13. 6d15890 Second commit
  14. 5266aa2 First commit
  15. $ git push -f -u origin master
  16. Counting objects: 7, done.
  17. Delta compression using up to 4 threads.
  18. Compressing objects: 100% (5/5), done.
  19. Writing objects: 100% (7/7), 546 bytes | 0 bytes/s, done.
  20. Total 7 (delta 2), reused 0 (delta 0)
  21. To C:/Users/Alpha/AppData/Local/Temp/d20170731-15124-1ywoym1/.git
  22. + 68ad000...b977ec3 master -> master (forced update)
  23. Branch master set up to track remote branch master from origin.
  24. $ git log origin/master --oneline
  25. b977ec3 Third commit
  26. 6d15890 Second commit
  27. 5266aa2 First commit

可以看到,强制覆盖 push 后,仓库的 Fourth commit 已经不见了。

但如果不想丢掉 commit-A 的同时又不想与 commit-A 合并,B 想继续接着本地仓库工作,可以使用 git rebase origin/master ,表示将本地所有 commit 排在仓库 的 commit 记录之后。然后向仓库的 push 就会被接受。同样借助于 githug 28 关,而且,这才是 28 关正确的过关方式:

详细过关过程如下:

  1. $ git push -u origin master
  2. To C:/Users/Alpha/AppData/Local/Temp/d20170731-14980-yb0fll/.git
  3. ! [rejected] master -> master (non-fast-forward)
  4. error: failed to push some refs to 'C:/Users/Alpha/AppData/Local/Temp/d20170731-14980-yb0fll/.git'
  5. hint: Updates were rejected because the tip of your current branch is behind
  6. hint: its remote counterpart. Integrate the remote changes (e.g.
  7. hint: 'git pull ...') before pushing again.
  8. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
  9. $ git log origin/master --oneline
  10. 015383a Fourth commit
  11. $ git log --oneline
  12. 38aa398 Third commit
  13. 1c03f48 Second commit
  14. ad32a6d First commit
  15. $ git rebase origin/master
  16. First, rewinding head to replay your work on top of it...
  17. Applying: First commit
  18. Applying: Second commit
  19. Applying: Third commit
  20. $ git log --oneline
  21. fbf0528 Third commit
  22. 03d1240 Second commit
  23. 9828360 First commit
  24. 015383a Fourth commit
  25. $ git push -u origin master
  26. Counting objects: 6, done.
  27. Delta compression using up to 4 threads.
  28. Compressing objects: 100% (6/6), done.
  29. Writing objects: 100% (6/6), 607 bytes | 0 bytes/s, done.
  30. Total 6 (delta 2), reused 0 (delta 0)
  31. To C:/Users/Alpha/AppData/Local/Temp/d20170731-14980-yb0fll/.git
  32. 015383a..fbf0528 master -> master
  33. Branch master set up to track remote branch master from origin.

但其实还有一种比较常见的出现 no-fast-forward 这种错误的情境,是在你向一个只有你自己可访问的仓库 push 的时候发生的。当你已经将一次 commit-A push 到仓库后,然后因为某些原因又使用了 git commit —amend 修改了 commit-A ,这个时候 commit-A 就变成了 commit-B,而此时本地仓库就没有关系 commit-A 的记录了,这个时候再次向仓库 push ,很明显,commit-B 无法与仓库的 commit-A 进行对接,所以出现了 no-fast-forward 错误。这种情况下其实也很好解决,如果你确定 commit-A 已经完全无用并且没有人将 commit-A 拉取到本地进行进一步开发之后,你就使用 git push -f 来覆盖仓库记录。之后,你就会永远丢失 commit-A 记录了。

而对比发现,我之所以会遇到本文开头的错误,就是因为之前使用了 git commit --amend 命令修改了已经 push 到仓库的 commit 的注释导致的。因此,一旦已经 push 到仓库,想要做出修改,就只能通过一次新的 commit 来完成对某次已经 push 到仓库的 commit 记录的修改了,可以参考 githug 52 关 revert。

参考:
[1]https://git-scm.com/docs/git-push/2.10.0#Note about fast-forwards
[2]极客学院 githug 通关攻略