工具(4): Git自助手册
目录:
- 0x01 基础教程
- 0x02 使用.gitignore忽略不应该提交到git仓库的文件
- 0x03 分支流程
- 0x04 提交日志
- 0x05 变化比较
- 0x06 团队协作
- 0x07 高级用法
- 0x08 常见问题(FAQ)
- 0x09 掌握Git的秘诀
0x01 基础教程
首先,这是一组根据链接难度和相关性组织的懒人图解学习包:
其次,需要推荐Git Data Transport Command里的这张图,一图读懂Git的数据传输流程,对于通过理解来记忆命令非常有好处。为了便于修订和添加,我重新制作了这张图:
最后,@SoftwareTeacher提到了上图中的workspace/index/local repository
三者都是用户在自己本机上的代码,比较让初学者困惑。我们可以对照下中英文术语解释下:
- workspace,中文术语:工作区。就是本地代码目录下除去
.git
文件夹外的文件,这部分代码的任何变动通过git status
可以看到是红色标示的。 - index,也叫stage area,中文术语:暂存区。暂存区域是一个文件,一般在.git目录中,保存了下次将提交的文件列表信息。在暂存区,还未提交到版本库的文件,通过
git status
可以看到是绿色标示的。 - local repository,中文术语:本地版本库。就是代码目录下的.git目录。本地版本库和暂存区的元数据都存储在.git目录下。本地版本库里含有本地创建的各个分支索引,以及远程版本库分支的索引信息。在local repository上的操作默认是针对当前分支。
- remote repository,中文术语:远程版本库。就是Github/Gitlab或者自己搭建的代码托管服务器上的代码仓库。
把本地代码分为三个区,是git的一个特色,是一种解决本地复杂代码修改变动管理的方案,Git的基本操作在这三个区之间:
- 在工作目录中修改文件。
- 暂存文件,将文件的快照放入暂存区域。
- 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。
0x02 使用.gitignore忽略不应该提交到git仓库的文件
git源码目录下的每个文件夹下都可以添加名为.gitignore
的文本文件,在这个名字的文件里使用文件路径通配符写上需要忽略的文件,这些忽略的文件不会提交到git。常见的不应该提交到git的文件有这些:
- 某个平台特定的系统索引文件,例如MacOS下的
.DS_Store
文件 - 缓存文件,例如
.*.swp
- IDE/编辑器的配置文件,例如VisualStudioCode的
.vscode
文件 - 编译器生成的bin目录,obj目录等
- 某种语言的第三方依赖库,例如nodejs的
node_modules
目录 - 某种前端框架的编译输出目录,例如
dist
目录 - 某种格式文件的缓存文件,例如Word文件的
~$*.docx
除了自己编写,你也可以使用通用的.gitignore
文件,例如,这个网站gitignore.io上,你可以输入平台、语言的名字生成默认的忽略文件,操作如图所示:
点击Create
,生成包含Windows
,C++
,Java
三种环境的基本忽略文件:
一个常见的问题是:如果不小心添加了文件并提交到git仓库了怎么办?,操作步骤如下:
- 首先添加路径到.gitignore文件
- 其次将其从git里删除,删除有两种,一种是删除目录,一种是删除文件,分别添加-r或者-f选型,其中--cached表示从暂存区删除,如果不加--cached,则本地工作区里文件也被删除,参考上一节的分区图片。
# 删除目录用-r选项
git rm --cached path_to_dir -r
# 删除文件用-f选项
git rm --cached path_to_file -f
# 添加日志并生成一个git commit
git commit -m 'delete remote somefile'
# 提交到远程仓库
git push
0x03 分支流程
- 分支操作的基本命令:
git checkout -b <name>
可以创建出一个名为name
的分支git checkout <name>
可以切换到名为name
的分支git branch
,用来显示本地有哪些分支,用*
号标示的就是当前分支。当你在分支之间做git merge
或者git rebase
,以及在远程版本库和本地版本库之间做git pull
或者git pull --rebase
时,遇到冲突的时候,查看状态最重要的两个命令就是git status
和git branch
。git branch -a
,用来显示本地的分支以及已经同步的远程分支列表。通过git fetch
可以把还没有同步到本地的远程分支索引同步下来,再通过git branch -a
就可以看到。
- 图解Github的PullRequest开发方式:Understanding the GitHub Flow
- 一种成功的git分支策略:A successful Git branching model
- 在线交互式的方式学习Git的分支,只需要鼠标点击即可学习:learn git branching
- 一种成功的git分支策略:A successful Git branching model
0x04 提交日志
- 基本命令:
- git commit -m 'message'
- 如何写好git的提交日志:How to Write a Git Commit Message
0x05 比较|可视化
- Visual Studio Code编辑器对Git Diff十分友好:配置
- SublimeMerge用来管理查看多分支状态,以及合并/rebase也十分直观方便
- 通过
git bisect
二分排除法来定位哪次提交出现问题: git bisect
- 通过
- SublimeMerge用来管理查看多分支状态,以及合并/rebase也十分直观方便
- 使用GIT自带的GITK可视化,命令:
gitk
0x06 团队协作
0x07 高级用法
- 最佳实践、本文略晦涩、慎入:Commit Often, Perfect Later, Publish Once: Git Best Practices
- git内部如何工作,十分简洁地把git内部的Objects和Refs的关系讲解清楚
0x08 常见问题(FAQ)
状态查看:
Q:为什么git status
的使用频率很高?
A:git status是最高频的命令,用来查看当前状态以确认发生了什么,主要是下面三种常见情况:
- 红色的是workspace里新发生的变动。
- 绿色的是已经执行过git add,从workspace添加到index的变动。
- 解决冲突中当前冲突解决的状态
日志:
Q: 如何diff文件的最后一次变动?
A: git log -1 -p -- filename
Q: 如何查看两个分支之间的提交差异,例如branckA分支有的,而branchB分支没有的commit?
A: git log branchB..branchA --oneline
Q: 如何查看最新1条日志,-ni
表示最新i条日志,默认全部展示太多了:
A: git log -n1
Q: 如何查看某个成员的所有日志:
A: git log --oneline --author=feilong
Q: 如何过滤日志:
A: git log --grep keyword
Q: 如何查看某个文件被那条.gitignore忽略规则所忽略?
A: git check-ignore -v filename
Q: 如何方便的查看变动日志?
A: 如果机子上装了lighttpd或者webrick则git instaweb
或者git instaweb --httpd=webrick
Q: 如何同时查看每个分支的最顶端的commit?
A: git branch -vv
Q: 如何查找哪些文件里含有一个字符串,例如函数名?
A: git grep -l monitor
分支:
Q:如何查看所有分支
A:git branch -a
Q:如何切换到别的分支
A: git checkout other_branch_name
Q: 如何从别到分支合并到当前分支
A: git merge other_branch_name
Q: 如何以别的分支为基准在当前分支上重做
A:git rebase other_branch_name
提交:
Q:代码修改一半,需要临时更新代码修改一个BUG怎么办?
A:把当前代码临时放到stash stack里:
- 入栈:
git stash
- 更新,修改并提交:
git pull --rebase
- do something
git add .
git commit -m 'fix bug'
git push origin master
- 出栈:
git stash pop
冲突:
Q: 遇到冲突的时候,如何只使用自己的版本?
A: 取决于是merge还是rebase,在merge中--ours
表示自己的版本,--theirs
表示对方的版本,而rebase时相反,rebase顾名思义指以对方的版本为基准版本。
- merge:
git merge other_branch_name
- 在merge中,使用自己的版本是:
git checkout --ours
,使用对方的版本是git checkout --theirs
,两者都需要则要通过diff工具比对合并。 git merge --continue
- rebase:
git rebase other_branch_name
- 在merge中,使用自己的版本是:
git checkout --theirs
,使用对方的版本是git checkout --ours
,两者都需要则要通过diff工具比对合并。 git rebase --continue
- merge和rebase哪个好?
- 观点:rebase是有害的一种流行的说法是
git rebase
可以让历史记录显的更干净,但是这篇文章揭示了rebase本质上是merge后把被merge节点的来源忽略了,文章认为这实际上会使得commit节点的历史记录丢失了,让历史记录显的更干净的做法应该是提供一个更好的显示策略和工具,而不是直接在索引里把元数据丢掉。 - merge和rebase的节点变动对比:
- 观点:rebase是有害的一种流行的说法是
Q: git pull
和git pull --rebase
的区别是什么?
A:记住下面的几条
git pull=git fetch+git merge
git pull --rebase=git fetch+git rebase
- 所以在两种情况下的冲突的解决分别和
merge
和rebase
相同。 - 如果想配置默认使用git pull --rebase,执行命令:
git config --global pull.rebase true
Q:git cherry_pick
怎么用?
A:如果你想把分支A上的一个commit提取出来添加到分支B上,基本步骤是:
- 通过
git checkout A
切换到A分支- 通过
git log --oneline -n10
查看A分支上最近N条记录(这里是查看了10条),复制你需要提取的那个commit的哈希例如是7f6eb072b7e。
- 通过
- 通过
git checkout B
切换到B分支- 通过
git cherry_pick 7f6eb072b7e
将哈希值7f6eb072b7e对应的那个commit作用到当前分支,也就是分支B。 - 如果有冲突,你需要解决下冲突。
- 通过
- 应该尽量少用cherry-pick,主要是 git cherry-pick 设计上会导致后续合并的问题:https://devblogs.microsoft.com/oldnewthing/20180312-00/?p=98215
Q: 中文文件名显示成了8进制?
A: git config --global core.quotepath false
Q: 如果远程仓库里有大文件,clone不下来怎么办?
A: 此时会报错:fatal: protocol error: bad pack header
,解决办法就是浅克隆一层原始仓库,然后设置远程仓库为目标分支,再浅层fetch下目标分支,然后checkout切过去。
git clone --depth 1 https://xxx.git
git remote set-branches origin 'remote_branch_name'
git fetch --depth 1 origin remote_branch_name
git checkout remote_branch_name
0x09 掌握Git的秘诀
首先,我学习 git/svn/latex 之类的,基本做法是理解背后的原理,然后使用的过程中随时查手册,久了就通过肌肉记忆自然熟悉。像这里是latex的索引,但是我现在编辑latex比较少,有时候排版书籍的时候用的时候会去更新下。
其次,坚持使用git命令行来处理日常工作,学习git的原子指令。如果使用GUI,会习惯GUI的操作,但是对于git本身的常用操作会生疏,甚至不理解一个GUI复合了哪些原子的操作,出现问题的时候到底是哪个环节出问题。
最后,工欲善其事,必先利其器,即使是使用git命令本身,也有利器辅助。我用了2年多Mac系统,有一个历史命令自动完成的工具,叫fishshell,通过fish,我只要敲前面几个字母,后面就会灰色自动完成,上下键可以切换同类的敲过的高频命令,按向右的方向键自动完成。通过这种方式,我又比别人减少了大量输入错误导致的命令学习障碍,极大增加成功率。