git 使用总结
![git 使用总结](https://static001.geekbang.org/infoq/45/4575199b78236f53b98347c7923ad31b.png)
前言
文章结合工作场景总结了常用的 git 命令的在本地使用以及使用过程中的注意事项,特别是一些误操作如何撤销,以及同样能达到目的或者感觉效果一样的命令的区别,同时也推荐了一些使用方式,比如提交模板、log 定制、借助 git 得到一些统计信息。写这篇总结的时候拓展了自己对 git 的认知,同时也希望对你有帮助。写完这篇总结后,发现自己还有很多不知道的,也在继续了解实践中,所以如果你有相关的问题,欢迎评论区留言,在一段时间内,我也在继续关注着这些问题。
说下内容排版:
先讲 git log,因为在介绍其他命令操作的时候,经常会使用 git log 查看提交历史,所以会先把 git log 的使用介绍一遍,这样在继续看的同时,大家就已经把 git log 实践了一遍了。
然后就是按照正常的顺序一次讲解工作中用到的几个命令
git log
当我们在项目中使用 git log 命令时,可以看到下面的输出:
但一般不会这样单纯的使用 git log 命令,因为在 idea 中,使用快捷键 <cmd + 9> 有更好的视觉效果:
![](https://static001.geekbang.org/infoq/03/038943bb1faa3db17a98ec2521ef7a18.png)
如果只看上一次的 log 日志,我们可以使用 git log -1 HEAD
。
当我想详细的查看最近的几次提交的时候,我会使用 idea 的这个 git log 面板工具,它可以在左侧窗口灵活的切换不同的分支来查看分支上最近的提交历史,同时针对不同的历史提交,可以在右侧窗口查看详细的更新文件和提交信息。
但是,当我浏览当前项目近期的提交历史记录的时候时候,idea 的这个工具面板又显得不是那么方便了,因为他没有 git log 命令本身提供的一些参数控制的那么全面,下面我们会一起来看几个例子,为了目录方便,这里我们分下模块,按照选项过滤和输出样式两部分来说明。
过滤选项
过滤选项的目的是为了过滤出那些我们感兴趣的提交历史
1.限制输出提交历史记录数量,比如只想看最近 10 次提交历史
2.限制某个日期往后的提交历史,比如只想看 2022-1-1 日之后的提交历史。当然也可以看某个时间之前的提交历史,使用--before='<日期>'
,两者结合使用也 OK,甚至可以使用相对时间,当我们一般不会用
3.限制查看某个作者的提交历史,比如只想看 maike 的提交历史
4.根据提交信息进行过滤
5.根据修改的文件进行过滤,比如只查看更新了 README.md 文件的提交历史
6.根据修改内容进行过滤,比如想知道那行神奇代码 Hello world!
是哪个提交
7.查看从主分支 main 分离到分支 branch-a 后的所有提交历史
8.不想看合并分支的提交记录,在执行分支合并的时候,会生成一个 commit,这个 commit 如果我们不关心,可以过滤掉
输出格式
其实刚刚已经用到了输出格式相关的选项 --oneline
,它可以在我们查看简要提交信息的时候使用,包含 commit id, commit message,references,并展示在一行,reference 是指提交历史所关联的 branch 或者 tag 等。
我们接着来看其他几个跟输出格式相关的选项
1.graph,该选项使用 ASCII 字符串来形象地展示你的分支、合并历史
2.stat,该选项会显示每次提交的文件修改统计信息
3.abbrev-commit,仅显示 SHA-1 校验和所有 40 个字符中的前几个字符。
4.date,日期格式化
5.pretty,使用其他格式显示历史提交信息。可用的选项包括 oneline、short、full、fuller 和 format,我们还可以加上颜色区分
这里只能截图看效果了:
![](https://static001.geekbang.org/infoq/34/347acf81cd8d1ca14b36f1ed319ba963.png)
好了,到这里定制输出样式和过滤筛选都说完了,接下来就靠大家的实践了。
这里给出我最常用的两条 git log 配置(网上找的好的),大家可以参考:
好了,到这里为止,git log 已经说完了,接下来会按照我们工作中 git 使用顺序来安排文章的顺序,首先是 git add 命令
git add
将工作区改动内容或新增内容加入暂存区。
说明:喜欢思考的同学可能会问为什么需要暂存区?为什么不直接提交呢?
我们写代码的时候,并不会主观的按照逻辑分好改动范围后进行,而是连贯高效专注的完成一系列工作,比如在编写新需求的同时顺手修改一下别人的 bug,如果没有暂存区,我们就需要把手头的工作全部作为一次提交,而我们倡导的原子提交是需要按逻辑分开进行的,这样查看提交历史的时候也更清晰,一目了然,更重要的是方便跟踪 bug 和恢复变更。暂存区可以让我们按逻辑选择需要提交的内容放入暂存区,然后进行提交,将工作内容多次进行暂存提交操作,形成一个好的提交记录。这就解决了既能连贯高效工作又能保证原子性提交了。
常用命令
不仅可以针对文件夹和文件进行暂存,git 还可以针对某个文件中的某几行代码进行暂存,使用 git add -i
命令。很少用,这里不详细说了,知道有这么回事可以了
撤销操作
撤销工作区的改动内容
撤销暂存区的改动内容
![](https://static001.geekbang.org/infoq/15/15ff995c391bf4268a5d319df42892ab.png)
git stash
这个命令叫贮藏,我之前没用过,在写这篇 git 总结的时候我发现了它,仿佛打开了新世界的大门,因为我一直有个痛点让我很苦恼,这个命令就是解决方案。
比如我本来在 feature 分支上写着产品丢过来的需求,然后代码写的正酣,来了个线上 bug,我脑子里千头万绪还没缕好,代码已经写了很多了,这个时候分开提交肯定很费神费时间,但又不能直接执行一下 git add ; git commit
,(之前我就是这么干的),然后切换到 bugfix 分支去改 bug。
这个时候我们可以使用 git stash
命令将当前工作内容临时存起来,等我们改完 bug 后再取出来 git stash apply
,
very perfect!
这个命令,目前简单的存取就够我用一段时间了,暂不深入研究。有高手的评论区告诉我一声
git commit
将暂存区的内容提交到本地仓库。
git commit 会将当前项目作为一个快照存起来并生成一个提交对象。
而分支则是一个指向 commit 的指针。
如何控制多个分支的前进走向呢?通过 HEAD
常用命令
直接使用 git commit
会打开一个文本编辑器,默认是一个 vi 编辑器,让你输入提交信息,默认是这样的
![](https://static001.geekbang.org/infoq/9d/9d356bacb107855b16370ca37509c338.png)
提交模板
我们也可以配置提交模板,我在网上找了一份 git commit 模板 git-commit-template.txt
设置一下:
再次执行 git commit
,效果如图
![](https://static001.geekbang.org/infoq/f5/f59e1396a73a240d91f100bf897081ec.png)
如果使用 idea 的话,idea 上的提交模板插件 Git Commit Template,效果很好,建议使用,篇幅关系就不深入介绍了。
好的提交可以产生好的提交记录,使用 git log 查看的时候更赏心悦目,最关键的是,好的提交历史更显专业。
排除文件
.gitignore 文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。 在这种情况下,我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件的模式。
.gitignore 的格式规范如下:
所有空行或者以 # 开头的行都会被 Git 忽略。
可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
匹配模式可以以(/)开头防止递归。
匹配模式可以以(/)结尾指定目录。
要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。
我们看个 .gitignore 文件的例子:
git config core.excludesfile
前面说的 .gitignore 文件用于排除项目中无需跟踪的文件,当我们需要在所有的版本库中忽略某一类文件时,就可以通过该配置项设置全局生效的 .gitignore 文件 。
例如 MacOS 系统的 .DS_Store,
在用户主目录创建一个 .gitignore_global 文件:~/.gitignore_global
设置全局生效
撤销操作
修改提交信息
执行这条命令,将会弹出一个编辑器,编辑器中带入了上个提交的提交信息,更新完关闭后会生成一个新的提交,在 git log 中已经看不到之前的那个提交记录了。
修改提交内容
如果只是修改提交内容,不需要更新提交信息,则只需要将补充的提交内容添加到暂存区,然后执行以下命令
git diff
git diff
命令用于比较工作区、暂存区、本地仓库、远程仓库以及不同分支之间的差异
比较工作区与暂存区
直接使用 git diff
比较的是工作区与暂存区的差异
我们更改一下 a.txt 文件,注意 a.txt 已经被 track 了,更新之后使用 git diff
可以查看到我们新增了 "aaaaaaa"。
c.txt 为什么没有?
![](https://static001.geekbang.org/infoq/b6/b673bab346c737a2fe7eded5197a434d.png)
比较暂存区与版本库
使用 git diff --cached <commit_id>
,用于比较暂存区与版本库的差异,commit_id 可以省略,默认比较的是暂存区与当前版本库的差异
还是上一个例子,我们把 c.txt 加入暂存区,然后执行 git diff --cached
命令
![](https://static001.geekbang.org/infoq/cb/cb9718b4b395d0c7768ce328c1ccdda0.png)
比较工作区与版本库
使用 git diff <commit-id>
,查看工作区与版本库的差异,一般常用 git diff HEAD
查看当前工作区与最新提交做比较,还是上一个例子,我们执行一下 git diff HEAD
看下结果:
![](https://static001.geekbang.org/infoq/3c/3c1b4b26105ad2c9711763d5ebd78a2a.png)
比较两个分支
使用 git diff branch-a branch-b
查看 branch-a 分支与 branch-b 分支的差异
还是上一个例子,我们先查看下 main 分支与 branch-b 分支的差异
![](https://static001.geekbang.org/infoq/4b/4b5186b5949a45f65d2b785165be09eb.png)
main 分支包含 a.txt、c.txt、file_1k、LICENSE、README.md 文件,其中 c.txt 是新增的,a.txt 有改动。
使用 git stash
命令将这两个改动临时存起来,切换到 branch-b 分支。可以看到 branch_b 比 main 分支多了一个 b.txt 文件。
我们执行一些 git diff main branch_b
,查看结果:
![](https://static001.geekbang.org/infoq/e6/e653392a0150c4a30bf1204d5a81a3ef.png)
右边窗口把贮藏区的内容取出来再做一次 diff,为什么这里要做个对比呢?其实 branch 是一个指向某个提交的指针,随着提交进行移动,虽然我们有新增 c.txt 文件,也改了 a.txt 文件,但我们并没有提交,所以 main 分支对应的 commit 与 branch_b 对应的 commit 进行 diff 时不会受这两个文件的影响。
merge vs rebase
整合不同分支的修改有两种方法,最常用的是 merge,就是常说的合并代码,还有一种是 rebase,叫变基。不知道 rebase 也没什么关系,用 merge 是没错的,而用 rebase 可能会让同事认为你在搞事情。
merge
快速合并和三路合并
fast forward
当我们从 master 分支检出 branch_b 分支后,master 分支没有更新,一致停留在 C2 这个提交点,branch_b 前进至 C4 这个提交点,这个时候我们在 master 分支上执行 git merge branch_b
命令,master 分支指针直接前进到 C4 这个提交点(如图一所示)。整个提交历史看起来是线性的(如图二所示)
![](https://static001.geekbang.org/infoq/3d/3d27e1dc296aab2f96c07254b7820c93.png)
图一
![](https://static001.geekbang.org/infoq/65/653aa1672e877486e9a36bb14b475fd8.png)
图二
three way
在上述场景中,如果 master 在合并 branch_b 之前进行了一次提交到 C5 这个提交点,这个时候将位于 C4 提交点的 branch_b 合并到 master,将不会进行快速合并,而是新生成一个提交 C6,C6 将有两个父提交(如图三)。同时形成一个合并交叉的提交历史(如图四)
![](https://static001.geekbang.org/infoq/0a/0a863ac645900cee8b721c3bfc356898.png)
图三
为了找回刚刚那种情况,我们需要先将 main 分支的合并操作撤销,这里使用
git reset
命令$ git reset --hard 9d4bbdc
![](https://static001.geekbang.org/infoq/85/85c5f9b654443f80957ccd03258c0a99.png)
图四
rebase
在 merge 的三路合并中,会将 main 分支的 C5 快照与 branch_b 的 C4 快照以及两个分支的共同祖先 C2 进行三方合并,合并生成一个新的快照并提交形成 C6。
rebase 也可以达到同样的效果,但 rebase 的处理过程不一样,比如我们执行以下命令:
![](https://static001.geekbang.org/infoq/b2/b22dac4a2403b2bffb340c09b6ac3e7c.png)
![](https://static001.geekbang.org/infoq/cb/cbdade2882e93c3912efc4fe17dc96f4.png)
我们用 git log --oneline --graph
看下提交历史:
![](https://static001.geekbang.org/infoq/99/9944bb4f02f6c003e437516826712b9c.png)
对比下之前 merge 的三路合并,rebase 形成的提交历史是线性的,且没有形成新的提交。在 branch_b rebase main 的过程中,只是将 branch_b 分支的 C3 和 C4 在 C5 上进行重放。
问题 1:那我们用 merge 还是 rebase 呢?
如果没有要求使用 rebase,那就使用 merge
问题 2:什么情况下使用 rebase
当你被要求需要确保在向远程分支推送时能保持提交历史的整洁的时候
reset vs revert
在演示 merge 和 rebase 的时候,为了重复使用 main 分支合 branch_b 分支形成对比,我使用了 reset 重置 main 分支。
如果用的不多,还有一个命令 revert 经常会与 reset 搞混,现在我们用例子来操作一下,让印象更深刻
经过上面的操作,现在 main 分支已经形成的如下的提交历史
现在我们分别使用 reset 和 revert 进行一下操作
RESET
使用 reset 命令,git 会把要回退版本之后提交的修改都删除掉。
执行git reset --hard 3a52650
可以看到 master 当前指向 9d4bbdc 这个版本,我们回到了 main 分支最初的提交。
如果 reset 的时候误操作了咋办?如何撤销 reset 呢?
比如我们刚刚把准备演示的三个提交重置到初次提交了,现在要恢复回来。
可以再使用 git reset --hard 3a9e49f
,重置到之前的状态,当然因为我在上面记录了 reset 之前的提交历史,知道之前的提交点是 3a9e49f,一般是不会记得,但是没关系,有一个 git reflog
命令可以查看本地的 HEAD 历史。
我们现在执行一下这个命令,看下结果:
![](https://static001.geekbang.org/infoq/eb/eb7dfde7258de7ff2bfdb195fd39a9d7.png)
REVERT
经过恢复,我们现在的提交历史还是之前的样子:
在这基础上,我们继续提交
现在的提交历史是这样的:
看下 c.txt 文件内容
现在假设发现第五次修改有错误,想要恢复第五次修改,但是要保留第六次修改
这个时候就要使用 git revert 命令
![](https://static001.geekbang.org/infoq/d6/d65c64e00b2ddb9aa3926d15d3a2b100.png)
我们再提交一下:git commit -m "移除第 5 次提交内容"
git 提交历史就会变成下面这样:
![](https://static001.geekbang.org/infoq/24/246b13b683bc58fe671bbb1b1d3c715f.png)
最佳实践
在确认要回滚的版本之后,如果别人没有最新提交,那么就可以直接用 reset 命令进行版本回退,否则,就可以考虑使用 revert 命令进行还原修改,不能影响到别人的提交。
配置 git
配置范围
系统级配置
/etc/gitconfig
文件,很少用
全局设置
~/.gitconfig
文件
即设置本地所有仓库的代码提交作者
项目设置
项目目录下的 .git/config
文件
本地既有公司仓库又有个人仓库时,公司仓库使用公司账号,个人仓库使用个人账号
进入个人仓库设置个人账号信息
进入公司仓库设置公司账号信息
常用配置
user.name
user.email
alias
commit.template
core.excludesfile
color.ui
可以把经常使用的几个命令设置下别名
统计信息
查看 git 上的个人代码量
--numstat
与 --stat
类似,会输出每个文件作者添加和删除的行数。但对二进制文件无效。</br>比如 c.txt 文件,maike 添加了三行代码,删除了一行代码,执行以下命令观察输出结果:
awk
我们可以借助 awk 命令来分割上个命令的输出内容并累加每个文件的增删数量,并计算出总添加行数、总删除行数、累计代码总数:
统计每个人增删行数
上面那个统计是指定了 author,如果统计每个人的代码增删行数,只需要外层加个循环即可
查看仓库提交者排名
git log --pretty='%aN'
输出项目所有的提交记录的提交作者信息</br>sort
按作者名称排序</br>uniq -c
去重并计算重复出现的次数</br>sort -k1 -nr
按第一列即名称重复的次数的数值大小从大到小排序</br>head -n 10
只显示 10 行</br>
这里我的示例项目不好弄,我在开源框架 Hystrix 项目中执行一下
谢谢
看到这里,很感谢您能花时间阅读本文,如果本文对您有帮助,请不要吝啬您的鼓励和支持,有空评论点赞收藏三连,没空点赞也好,让作者保持继续创作的欲望。谢谢!
参考资料
版权声明: 本文为 InfoQ 作者【攻城狮MK】的原创文章。
原文链接:【http://xie.infoq.cn/article/dd9f3a407bc9081e16296600a】。文章转载请联系作者。
评论