无论是数据科学家、算法工程师还是普通开发人员,在每个团队协作开发任务中,Git 都是必不可少的版本控制工具,因此掌握它的基本操作十分有必要。但即便是教程满天飞的今天,开发人员在使用 Git 时也还是会犯一些不应该犯的错误。本文总结了其中的几种常见错误,希望能对新手有所帮助。
force push
有时,我们会需要用 force push 把 commit 推送到远端仓库。
-
假设有 2 名开发人员正在合作开发一个分支
-
之前开发人员1已经完成更改,把代码 push 到了远程仓库
-
现在,开发人员 2 也完成了更改,正当他准备提交时,他却发现自己无法将代码推送到远程仓库
-
由于开发人员 2 是个初学者,他 Google 了一下,发现了一个神奇的命令 git push -f,于是进行了强制 push
-
之后开发人员 1 在检查远程仓库时,发现自己编写的代码全消失了
出现这个问题的原因是 force push 会覆盖远程仓库中的代码,使现有代码全部丢失。
如果开发人员 2 想避免这个问题,一种理想方法是他先把开发人员 1 的更新从远程仓库 pull 到本地,然后把自己的代码 rebase 一下,再进行 push。这里我们讨论的是在同一分支中从远程到本地仓库的 rebase。
git push -f 这个命令非常不安全,除非有绝对的必要,大家最好还是不要用它。它会把本地分支的提交覆盖远程推送分支的提交,给协作的同伴带去不少麻烦,即便是上面的解决方案,它也可能存在一个时间差的问题,因为你不可能时刻掌握同伴的工作进展。
所以如果大家都用正确的 git 工作流,让每个开发人员都拥有自己的功能分支,这种情况根本不会发生。
Rebase
如果你想把一个分支的修改合并到当前分支,你可以用 git rebase。它和 git merge 的区别是 merge 有一个合并 commit 的步骤,而 rebase 是把所有 commit 都串联在一起,让你本地的分支历史看起来像没有经过任何合并一样。
-
假设有 2 名开发人员正在合作开发一个功能分支
-
开发人员 1 率先完成了一系列 commit,并将其推送到远程功能分支
-
之后,开发人员 2 把远程功能分支的最新更改 pull 到本地
-
开发人员 2 向本地功能分支添加了一堆 commit
-
这时,他想把本地仓库的更新重新 rebase 到远程仓库中,于是他把整个预发分支(release branch)在本地功能分支上 rebase 了一下。这里我们讨论的是在不同分支中从远程到本地仓库的 rebase
-
现在,开发人员 2 试着把代码 push 到远程功能分支上,由于提交历史记录已更改,这个操作不被允许,他只能又开始用 git push -f
-
最后,当开发人员 1 想从远程仓库提取最新代码时,由于提交记录已更改,他被迫需要处理大量代码冲突问题
如上图所示,rebase远程仓库会改变提交历史记录,并在其他开发人员尝试从远程仓库中提取最新代码时产生问题。处理这种情况的理想方法是始终只rebase本地仓库,本地仓库中的任何commit都不应该被push到远程仓库。
如果别人事先已经把commit推送到远程功能分支,那么你最好先用pull命令把更新拉到本地,用merge和你的修改合并,因为merge不会改变提交历史,而rebase会。
此外,和上个问题一样,如果使用正确的git工作流,每个开发人员都会有自己的功能分支,这时,开发者在自己的功能分支上进行更新并且在远程功能分支上做rebase是不会报错的,因为没有其他开发人员从同一个远程功能分支中提取代码。无论如何,尽量避免重新定义远程仓库。
Rebase是一个非常强大的功能,使用时也需谨慎。
amend
git amend 命令的作用是修复最近一次 commit,让你合并你缓存区的修改和上一次 commit,而不是提交一个新的快照。这里需要注意一点,它不是修改最近一次 commit,而是整个替换掉原 commit,所以对 Git 来说这是一个新的 commit。此外,它还可以用来编辑上一次的 commit 描述。
-
假设有 2 名开发人员正在合作开发一个功能分支
-
开发人员 1 率先完成了 commit,并将其推送到远程功能分支,我们把它称为“old commit”
-
之后,开发人员 2 把最新代码从远程功能分支 pull 到本地功能分支
-
然后他开始处理本地仓库中的代码,在这个过程中,他没有向远程仓库 push 任何 commit
-
这时开发人员 1 突然发现之前的 commit 中存在 bug,他用 amend 命令修复了本地仓库里的最近一次 commit,我们把它称为“new commit”
-
开发人员 1 尝试把这个新 commit 重新 push 到远程功能分支,由于提交历史记录发生了变化,这个操作报错了,于是他调用了 git push -f
-
现在,当开发人员 2 想从远程功能分支中提取最新代码时,git 会注意到提交历史记录的变化并创建合并的 commit。因此当他 pull 到本地后,他会在本地仓库里发现“commit old”和“commit new”,这就破坏了 amend 这个操作的意义。
-
最后,即便开发人员 2 从远程分支到本地分支执行 rebase,这个“commit old”还是会出现在本地仓库中,它仍然会作为历史提交的一部分。
amend commit 会更改提交历史记录,所以当其他开发人员尝试从远程仓库提取最新代码时,修改远程仓库中的 commit 会产生混淆。
为了避免这个错误,最好的方法是只在本地仓库里修改 commit,不要对远程库里的 commit 做任何修改。当然,一人一个分支也不会出现这个问题。
Hard reset
git reset 命令是用来将当前 branch 重置到另外一个 commit 的。它不会产生 commit,而是只更新一个 branch(branch 本身就是一个指向一个 commit 的指针)指向另外一个 commit。
-
Hard reset 的命令是 git reset --hard
-
此外,git reset 还有 --soft 和 --mixed,只不过它们都没有 Hard reset 那么不安全
-
假设开发人员 1 正在开发一个功能分支,并在本地仓库中完成了 5 次 commit
-
与此同时,他还正在处理尚未提交的两个文件
-
这时,如果他运行了 git reset --hard <commit4hash>
-
那么功能分支中的最新 commit 会变成是 commit4,commit5 丢失
-
同时他正在处理的那两个未提交文件也会丢失
这时 commit5 还在 git 内部,只不过对它的引用丢失了,我们可以用 git reflog 把它恢复,但总体来说,hard reset 还是很不安全。在 git 中使用 reset 命令时要非常小心,如果必须得用,确保你已经完全评估所有情况。
小结
综上所述,为了避免使用 git 时出错,我们可以牢记这几条教训:
-
避免多人在同一分支上协作。上述四个例子中有三个都是在说明这个问题,在日常工作中,遵守正确的工作流非常重要,要确保只有一个人在一个功能分支上工作,这是技术主管、高级开发人员尤其需要注意的。
-
不要到处实用 force push。
-
如果万不得已必须使用 force push,先评估其他方案,把它作为最后的手段。
-
不要试图修改远程仓库里的 commit,要只在本地仓库中更改 commit 历史记录。但即便是在本地仓库里,用 rebase 还是要谨慎。
原文地址:adityasridhar.com/posts/how-you-can-go-wrong-with-git