接触 Git 很久了,期间也看过各种博客,或浅显或深奥,但是看完依然不懂 Git,倒是最近用 Git 比较频繁,一点点也了解了,算是入了个门。

Git 非常强大,强大的同时伴随的时学习成本的提升。现在感触比较深的教训就是初学 Git 时不要想太多,把各个功能分离开来。我最初学习的时候总是在想如果我修改了一些工作区文件,然后切换了分支,那么工作区的文件会怎样?如果我撤销了修改,本地的暂存区会怎样?现在回想起来,其实有这种想法本身就是一个错误。

根据我自己经常用的一些操作,我把 Git 的操作分为三类:

  • 一些基础的操作(包括对工作区、暂存区的操作及一些查询操作)
  • 与撤销相关的操作
  • 与分支相关的操作

下面来依次进行说明。

基础操作

提交修改:

# 将文件提交到暂存区
$ git stage|add <file>
#提交
$ git commit
# 把所有跟踪的文件暂存起来一起提交,相当于合写
$ git commit -a

查看状态:

$ git status

建议在 stage 之前以及 commit 之前都进行 git status,这样有问题能够及时发现,比如有文件忘了提交,如果是在 commit 之前发现,只需要 stage 一下就好,如果是在 commit 之后发现,可以用新的提交覆盖掉:

# 覆盖之前的提交
$ git commit --amend
# 如果跟上次提交时的信息一样,又懒得再输一遍
$ git commit --amend --no-edit

当然,不排除可能某一天心血来潮查看 commit 历史发现两个 commit 应该合起来,那就需要 rebase -i 了,这里不多说。

通常我在提交前还会进行 diff 操作,diff 比较简单,像这样:

# 比较工作区与暂存区
$ git diff <file>
# 比较暂存区与当前 HEAD 所指向的提交
$ git diff --stage|--cached <file>

当然,不排除有想比较两个版本中文件的冲动,这当然是可行的:

$ git diff <commit 1>:<file> <commit 2>:<file>

查看项目历史也是非常需要的,众所周知需要 git log 命令,但是默认的 git log 往往不尽如人意,尽如人意的 git log 命令往往又长到让人吐血,这时候就需要 alias 啊,通常一两个关于 log 的 alias 就够用了:

$ git config --global alias.history "log --pretty=format:'%Cred%h%Creset %C(bold blue)<%an>%Creset%C(yellow)%d%Creset %Cgreen(%cr)%Creset%n%w(80,8,8)%s' --graph"

与标签相关的操作虽然不常用但还是有必要了解的:

# 列出标签
$ git tag
# 默认是按字母排序,在高版本 Git 中可以按时间排序
$ git tag --sort -l --sort=v:refname # In git >= 2.0
# 打标签
$ git tag -a <tagname> -m 'message'
# push 到远端
$ git push origin <tagname>

与撤销相关的操作

在讲 Git 的撤销操作之前,特别提醒:Git 中版本库中的文件同步到工作区中间一定会途经暂存区,同理,工作区的文件提交到版本库也一定会经过暂存区,暂存区作为一个缓冲带,我没有发现哪条命令可以跨越。

平时误操作基本都是 Ctrl+Z 或者 u 解决,但是这并不适用于所有场景。

把 Git 的撤销操作分为对文件的撤销操作与对 commit 的撤销操作,先说文件的,最常见的莫过于 git status 的提醒:

(use “git checkout –…” to discard changes in working directory)

不用多说,这条命令就是将暂存区的文件同步到工作区。当然,实际需求可能不止如此,比如,把某个 commit 中的文件同步到工作区,这样操作:

# 将 commit 中的对应文件同步到暂存区
$ git reset <commit> <file>
# 此时是一个执行 git diff 的好时机哦,做好准备后就可以同步到工作区了
$ git checkout -- <file>

对于 commit 的撤销,分几种情况:
1.在公共分支上,撤销后依然先于远端或者跟远端相同

# --soft, --mixed, --hard 根据情况进行选择
$ git reset <commit>
$ git push

2.在公共分支上,撤销后当前 commit 会滞后于远端,这时不要使用 reset,改用 revert(如果选择 reset 会影响到别人):

$ git revert <commit>
$ git push

3.在个人分支上,这种情况是怎么折腾都无所谓的,为了有一个干净的历史,建议这样操作:

$ git reset <commit>
$ git push
# 如果 push 失败,强制一下
$ git push -f

忘了还有一种常见的场景,如果想要撤销全部修改,恢复到上次同步的状态,只需两条命令:

# 将版本库同步到暂存区合工作区
$ git reset HEAD --hard
# 清空未跟踪的文件和文件夹
$ git clean -df

这样本地的项目就又崭新如初了。

最后,如果找不到想要恢复的 commit,比如 reset 之后后悔,想要撤销 reset 操作,可以通过 git reflog 命令查找相应的 ID,这个 git reflog 会记录每一次对 HEAD 的操作。

与分支相关的操作

特别提醒:在进行切换分支操作之前,一定要保持工作区和暂存区干净,如果当前的状态是可以提交的,就 git commit,如果还未开发完成,就 git stash,切回来之后 git stash pop

先说远程分支同步的事情,如果比较懒,通常会直接 git pullgit pull 相当于:

$ git fetch <remote>
$ git merge

实际项目中很难预期 Git 是否会进行快速合并,所以我更推荐这样操作:

$ git fetch <remote>
$ git rebase

当然,如果希望能一条命令完成,这是可行的:

git pull --rebase <remote>

但是你可能连 --rebase 都懒的打,依然可行:

git config --global branch.autosetuprebase always # In git < 1.7.9
git config --global pull.rebase true # In git >= 1.7.9

接下来说一下分支合并时对于 merge 和 rebase 的选择吧,其实这是一个个人的喜好问题,如果用不明白 rebase 全都改用 merge 也无所谓,但是如果这样的话,切记不要在两个长期分支上来回 merge,那样项目历史会非常的混乱。

我对 merge 和 rebase 的选择依据非常简单,在个人分支上 rebase 公共分支,在公共分支上 merge 个人分支。

偶尔,对合并要求可能比较奇葩,要求只与指定的 commit 进行合并,这时候 cherry-pick 就派上用场了

git cherry-pick <commit 1> <commit 2> ...

Git 的命令还有很多,但是于我而言,上面的命令已经能满足绝大部分的场景了。

参考:

文档信息

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
本文链接:www.snovey.com/2017/09/use-git.html