引言
在上一篇文章中我们简单介绍了如何使用Git进行日常的版本管理,但是Git的功能远不止如此,最重要的功能之一便是分支管理,本文将重点介绍Git中分支的使用,并会涉及到其他一些杂项的命令,最后在文末谈一下自己在Git使用中遇到的一些比较迷的问题以及是如何解决的。
Git中的分支管理
首先我们要理解什么是分支。Git常常把我们的Commit隐喻为工作流(Work Flow),分支亦同,我们每一次的提交都会使流前进一点,当我们在同一个commit的基础上产生了两个不同的新commit的时候,就如同河流在同一点流向不同的方向,从而产生了分支(Branch)。分支有许多的作用,例如不同版本的并行开发,同一版本不同人员的并行开发,做测试分支等等。
在上一篇文章中我们提到的出现冲突时,其实系统就已经形成了一个临时的分支,只是这个分支没有名称,而是以commit的标识号作为标识的临时分支。
当我们新建一个仓库后,默认是在master分支。可以用如下命令来创建一个分支
git checkout -b dev //创建并跳转到dev分支
相当于如下两条命令
git branch dev // 创建分支dev
git checkout dev // 跳转到dev分支
跳转分支时如果工作区(Working directories)还有未staged或commit的文件,会报错,需先处理
其他相关命令:
git branch -d branchName // 删除某条分支
现在我们在dev分支上随意的做些更改,
git commit -a -m "dev changes"
git checkout master // 跳转到主分支
git merge dev // merge dev分支
会看到如下输出
Updating 476259c..3934ab8 //从commit 476259c 到 3934ab8
Fast-forward //merge使用的是fast-forward模式, ff模式表示无冲突的情况下,直接将master分支指向了dev分支的commit,因此合并较快,可以用--no-ff禁用ff模式。另一种模式称为recursive模式,即产生一个新的commit来表示这个merge的过程,这样这个merge过程就会保留,否则如果采取ff模式,删除另一分支后,合并的过程就不复存在了
现在我们来做一个小实验,即两个人同时修改一个文件。
我们从dev 分支建立两个子分支 feature1, feature2
然后同时修改一个文件,然后merge,这时候git会尝试自己merge(一般是两个人修改了同一文件的不同地方),如果成功了
Auto-merging No1_TwoSum.cpp
Merge made by the 'recursive' strategy
否则(一般是两个人修改了文件的相同地方)
Auto-merging No1_TwoSum.cpp
CONFLICT (content): Merge conflict in No1_TwoSum.cpp
Automatic merge failed; fix conflicts and then commit the result.
如果自动merge失败,那么merge失败的文件会在工作区中,我们修改提交后再做commit即完成了merge的过程
<<<<<<< HEAD // 表示当前分支
Creating a new branch is quick & simple. // 表示当前分支该文件的内容
======= // 两个分支的分割线
Creating a new branch is quick AND simple. // 要合并分支的内容
>>>>>>> feature1
当一个分支被merge之后,可以如下命令删除之
git merge -d <branchName>
git merge -D <branchName> // 删除分支,即使未被merge
分支小结
到这儿,我们整理了Git分支的使用,跳转,合并,删除。但是更重要的是要理解分支的使用场景。一般分支可以有如下的使用场景:
- 使用dev分支作为开发分支:始终保持master分支上是稳定的可发布的版本,只有在经过充分的测试之后才将dev分支merge到master分支
- 使用bug分支来解决某个bug
- 使用feature分支来增加某个新功能
- 建立程序员个人分支来保证多人并行开发
原则是多建分支,多做合并
Git commit的指代
其实任何一次commit都可以视为一个临时分支,只是这个分支没有名称,除了git branch命令之外,其他命令中的分支都可以用临时分支代替。临时分支指代有几种方式:
- commit的ID指代:不需要完整的id,通常我们只需要列出前5-6个字符即可
- HEAD或其他的分支名称的相对值
- HEAD^ 代表HEAD的前一个commit (bash中可能HEAD会有歧义,需要用双引号包裹起来)
- master^^ 代表master的前两个commit
- HEAD~3 代表HEAD的前三个commit
- 使用标签(tag)的相对值,方法同分支名称,标签不做介绍,可自行Google
例如我们想把test分支前一次commit所在的分支合并到当前分支中,我们可以:
branch test C0 C1 C2 C3
branch master C0
git merge test~1 // 会把C1和C2merge过来
如果我们只是想要那一次commit的更改,不想要那次commit之前的其他更改呢?
git cherry-pick test~1 // 此时只会把C2这次Commit加到C0后面,没有merge的过程
注:如果想要重新安排commit的顺序,可以用rebase -i (--interactive)
Git 的“后悔药” --- Git revert 与 Git reset
加入我们修改了一些文件,但是我们对我们的修改很不满意,甚至不希望它出现在历史提交记录中以免被同时笑话,那有没有后悔药可以吃呢?答案是有的。
- 如果你只是想清除你工作区的修改,不包括已经staged的文件
git checkout <filePath>
- 如果你想只想清除staged区的修改,不包括工作区的文件:
git reset (--mixed) HEAD <filePath> // HEAD可以替换为其他分支
- 如果你想要跳回到上一次commit,并保留当前工作区和staged区的修改:
git reset --soft HEAD^
- 如果你想要跳回到某一次commit,并清除工作区和staged区的所有修改:
git reset --hard master~5
- 如果你的commit已经提交到远端了,那么reset没有意义,因为你的队友是在你的commit的基础上开发的,于是为了撤销你的更改,可以使用revert命令
git revert HEAD^1 HEAD^2 // 重置HEAD前两次commit所涉及到的所有修改,形成一个新的commit(可以增加-n, --no-commit 来使得不会自动形成commit)
Git 杂项命令
git log // 查看git历史提交记录
git log --graph // 以图表方式查看记录
git reflog // 查看最近的git历史命令,包括其他分支的,包括在当前commit之后的命令
git tag // 建立tag
git rm // 删除文件,从工作区和staged区
Git常见问题
- 有一些必须存在文件系统,但是又不需要git同步的文件
- 使用.gitignore,在项目根目录下增加一个这样的文件,然后加入相关的忽略文件,不知道忽略哪些文件?戳这儿
- 项目进行到一半才增加gitignore导致已经被跟踪的文件无法忽略?
git rm --cached -r <filename/directoryName>//删除staged区的文件/文件夹
- 文件名称大小写写错了,git没检测到区别?
- git 本身是忽略大小写的,可以更改设置使得它不忽略大小写(git config core.ignorecase false),但是可能在不区分大小写的系统上引发bug,一个更好的选择是
git mv -f OldFileNameCase newfilenamecase
- (想到了再接着补充)
结语
首先,git还有许多高级的功能还没有介绍(其实笔者也不会 : ) 但是git作为一个工具,想必本文介绍的以足够应付日常生活的使用。其次git的使用其实可以很简单,多掌握一些命令只是有些时候可以更方便用更少的命令的去达到某些目的。至于使用Git命令行还是GUI,我觉得影响不大,只要你确实理解了Git的使用即可。
如果觉得本文对你有帮助的话,请点个喜欢。你的喜欢是对笔者最大的鞭策😊
如果觉得有任何疑问,可以在评论区里提出。