前言:本文主要介绍git的原理、使用和一些技巧,目的在于使读者对git的了解不仅仅局限于简单的使用push、pull命令,而要做到知其然且知其所以然。当然,本文并不会深入去探讨诸如git的实现原理之类的深层次东西,毕竟它只是一个代码管理工具罢了,作为使用者,我们只要达到真正熟练使用的地步就够了,至于更深层次的东西,诸位有兴趣的可以自行学习研究。
另外,本文分支相关图片取自learngitbranching,这是个用游戏的方式,图文结合学习git分支的网站,相当nice,推荐大家去完整过一遍,相信对于git的理解会更上一层楼。
Git起源
git是由Linux的作者Linus花两周时间写出的分布式版本控制软件。在这之前,Linux社区使用BitKeeper作为版本控制系统,但是由于社区中有人试图破解BitKeeper的协议,这惹恼了BitKeeper的东家BitMover公司,于是BitMover决定收回linux社区的免费使用权。
在这样的背景下,Linus花了两周时间写出了git,在一个月内替换了BitKeeper,作为Linux的版本控制工具,并在后面不断完善,最终成为了现在代码版本控制的首选
基本概念
三种工作域:
- git目录(git direcdtory):即仓库(Repository),保存项目中所有版本和相关信息,是git存放数据和信息的地方
- 工作目录(work directory): 是对应项目的某个版本的文件集合,对应从 git 目录中解压出来的供用户进行操作和修改的数据和信息
- 暂存目录(staging area):用于记录下次commit时需要保存的文件列表
三种文件状态:
- committed:已提交状态,表示数据文件已经被保存至本地数据仓库中。
- modified:修改状态,表示文件已被修改,但是尚未被提交(保存)。
- staged:暂存状态,表示是被标记了的被修改文件,在下次提交时会将所有标记过的修改保存。
另外新增的文件为untracked file,未在git管理范围内,需要先通过git add添加到暂存目录,然后其状态会变为staged
如图所示,git分为远端和本地。远程远程服务器存储了仓库信息,而本地则是三种工作域都有。
Branch、HEAD和Commit tree
本地提交代码到远端的一般流程:
- git add,将修改保存到暂存区(stage area)
- git commit,将暂存区中的文件推送到本地分支,本地仓库更新
- git push,将本地仓库的更改推送到远程仓库,远程仓库更新
可以看到,想要更新代码,commit是必不可少的。每次commit都会生成一个工作目录的快照(前提是有修改),在git中,这些commit的快照数据使用树(tree)结构来管理,称为提交树(Commit Tree)或者工作树(Work Tree)。
Git 的分支(Branch),其实本质上仅仅是指向提交对象的可变指针。分支是git的核心所在,因为分支的存在,工作树才是工作树而不是工作"线"。可以将每个分支看作工作树的分叉,项目可以在不同分支上并行开发,然后在合适的时机又可以合并在一起,这都是分支的作用。
HEAD表示当前所处提交位置。通常来说,HEAD是指向某个分支的,当然也可以手动切换将HEAD指向工作树中的任意commit(这种情况称为HEAD分离)。
图中一共有c0-c4四个提交,main、bugFix和feature三个分支,三个分支分别指向C1、C3、C4三个提交,HEAD处于分离状态,指向C2
了解了以上的基础概念以后,让我们来探讨一下git分支相关内容。
Git分支
之前说了,分支的存在是为了并行开发,每一个分支都会指向一个具体的提交。需要多人协作的项目离不开对分支的操作。
通常来说,新建一个项目时默认分支为master,可以根据需要新建develop、release等分支。
下面介绍一些常见的git分支相关命令
- git commit。以当前提交为父节点生成新的子提交节点,并且当前HEAD/Branch会指向新生成的节点
- git branch。单独使用时查看所有分支,后面加一个branchName表示在当前节点创建名为branchName的新分支,git checkout -b branchName也能达到相同的效果,区别在于后者会将HEAD指向新创建的分支
- git checkout BRANCH/COMMIT。切换到对应的分支或者提交节点,之前有提过,直接切换到提交节点就是HEAD分离状态
- git merge BRANCH/COMMIT。将当前提交节点和指定提交节点合并并生成新的提交节点,新生成的节点有两个父节点。
- git rebase BRANCH/COMMIT。将当前分支所在节点及之前的节点中有且在另一分支/节点中所没有的节点全部按序复制一份到目标分支,然后将当前BRANCH/HEAD移到目标位置。
- git reset COMMIT.撤销commit到指定提交,与git branch -f CURRENT_COMMIT COMMIT效果是一样的。但是这种更改只能在本地体现,是不能同步到远程的
- git revert COMMIT。要想撤销能同步到远程,就得用到git revert COMMIT。此命令会通过新生成提交节点的方式撤销指定的提交节点
- git cherry-pick COMMIT_1 COMMIT_2 ... 。选取指定的一些节点按序rebase到当前分支
git merge 和 git rebase 的区别和抉择
git rebase:
- 优点:提交树呈线性,干净简单
- 缺点:修改了提交树的历史
git merge:
- 优点:提交记录顺序正确,不会造成迷惑
- 缺点:多分支时显得看起来很复杂
两者如何抉择,该使用哪条命令来合并,这取决于用户的习惯,如果对提交历史顺序的正确性有要求,就用git merge,否则使用git rebase
与远程仓库的交互
通常来说,开发的一般流程是在远程建立一个仓库,然后开发人员各自在本地clone仓库,新建自己的分支进行开发,开发完成后再推送到远程分支,再合并到主分支。
当我们clone远程仓库到本地时,会在本地复制一份仓库信息和工作目录
注意,本地多了一个origin/main的分支,这个分支叫做远程分支,它反映了你最近一次操作远程仓库的状态。远程分支比较特殊,不能像普通分支一样通过checkout、branch等命令直接操控,必须通过pull、push、fetch等命令与远端同步。
下面介绍一些常见的与远程仓库交互的命令:
- git clone Repository。将远程仓库克隆到本地,可通过git clone -b branchName Repository命令指定克隆的分支。
- git fetch,下载远程分支的最新状态到本地,只会更新本地的远程分支,不会更改HEAD和本地分支远程
有更新
使用 git fetch后
- git pull。可以视作 git fetch + git merge 的缩写。存在下列常见情况,远程分支有人提交了更新,自己本地也提交了更新,这时候就需要先拉取最新的代码
使用git pull,第一步是下载最新分支更改,即git fetch
![git pull1](https://img2022.cnblogs.com/blog/1491971/202204/1491971-20220427100530923-1663855047.png
第二步合并当前分支与远程分支,即git merge o/main
- git push。将当前分支的本地更改推送到远程分支。git push会更新本地的远程分支,并且将修改同步到远端对应的分支
使用git push推送
关于一些实际开发场景的问题和解决方式
前提:团队目前使用develop分支作为本地测试环境,Release分支作为代码备份,通过手动部署的方式部署到真实环境,也就是说,每次开发完成以后会将develop分支合到release分支。
问题:在某次开发后develop最新提交为C2,但是C2忘了合并到release分支,然后又另外创建了个分支nas开发nas相关功能C3,现在nas已经合并到develop分支。这时候nas相关功能由于存在一些问题,需要回滚,请问这时候应该怎么做?
解决方案:使用git revert C3回滚
在回滚以后另外新建分支开发新功能C4,之后又想将nas分支的功能重新合并到develop,应该怎么做?
解决方案: git revert C3'