这里介绍Git的常用命令、用法
基本模型
工作区域
Git有三个工作区域,分别是:
- Working Directory 工作目录:其是本地文件系统中的一个目录,包含了项目文件的当前状态。从Git仓库中检出时,项目文件即会位于工作目录中. 可以在这里对项目文件进行修改、查看、删除等操作
- Staging Area 暂存区:其是Repository仓库中名为index的文件,保存了下次将要提交的文件列表信息
- Repository 仓库:其是工作目录下一个名为.git的隐藏目录。仓库是git的核心部分,存储了项目的版本信息、历史记录等数据
文件状态
Git中文件的状态:
- Untracked 未跟踪:在工作目录下新建文件后,该文件即会处于未跟踪的状态。只有将其添加到git的暂存区后,git才会对其进行版本管理
- Unmodified 未修改:文件已经被git跟踪,且内容与仓库中的版本完全一致
- Modified 已修改:文件已经被git跟踪。其内容虽然被修改,但这些修改尚未添加到暂存区
- Staged 已暂存:文件已经被git跟踪。其内容虽然被修改,但这些修改已经被添加到暂存区。这样下次提交时这些被暂存的修改即被记录到仓库中
合并 VS 变基
合并命令如下
1 | # 将指定分支的更改合并到当前分支中 |
Fast-forward Merge 快进合并:如果当前分支是
Non-fast-forward Merge 非快进合并:如果当前分支不是
变基命令如下,base branch又被称为目标基底分支。该命令会将当前分支上的所有提交都移至目标基底分支上。特别地,当base branch分支未指定时,Git会使用当前分支的上游分支
1 | # 将当前分支上的提交重新应用到 <base branch> 分支上 |
变基的基本原理:
- 首先,找到这两个分支的最近公共祖先(即C1),然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件
- 然后,将 当前分支 指向 目标基底分支的最新提交(即C2)
- 最后,以此将之前另存为临时文件的修改依序应用
从上图不难看出,在我们完成变基后只需切到master分支,进行一次快进合并就可以将feature分支的修改整合到master分支中了。不难看出,变基与合并这两种整合方式在最终结果上没有区别。而且相比较于直接合并,先变基再合并(快进合并)可以让(这里的master分支的)提交历史更加整洁、清晰。因为没有产生新的合并提交
But……
从上图我们会发现一个问题,当我们在feature分支上执行变基命令后,该分支的提交历史发生了改变(上图置为灰色的提交历史)。因为,变基的本质是丢弃一些现有的提交(例如:C3、C4),然后相应地创建一些内容一样但实质上不同的新提交(例如:C3’、C4’)。所以,如果你此前已经将这些提交(例如:C3、C4)push到远程仓库,而其他人又从该仓库拉取这些提交并进行了后续工作……接下来迎接你们的就是Boom……
所以,正确使用变基的命令原则是:永远只在私有分支上执行变基命令。即,只有在当前分支是私有分支的前提下,才允许执行变基命令。换言之,一旦某个分支已经push到远程仓库成为协作分支了。不论别人是否真的会拉取、修改、提交该分支。都永远不要在这个分支下执行变基命令,而是老老实实的使用常规的合并命令来进行整合。而对于 base branch目标基底分支 就没有这个限制了,无论是私有分支还是协作分支都可以。因为变基命令不会影响该分支,更不会修改该分支的提交历史了
分支比较
两点语法:差集
下述命令会展示存在于分支B而不存在于分支A中的提交,即:B-A的差集。如果未指定分支B时,则默认为HEAD,即当前分支
1 | # 展示存在于分支B而不存在于分支A中的提交 |
常用场景如下:
查看feature分支中未合并到master分支的提交
1 | git log master..feature |
假设HEAD指向feature分支。如果想知道push时,哪些提交会被push到远程仓库时,可以通过下述方式查看
1 | git log origin/feature..HEAD |
三点语法:对称差集
下述命令会展示 只存在于分支A 和 只存在于分支B 的提交,而不会展示同时存在于A、B分支的提交。即:A和B的对称差集。如果未指定分支B时,则默认为HEAD,即当前分支。可以添加 —left-right 选项,这样输出结果中带<标记的表示是分支A的提交、带>标记的表示是分支B的提交
1 | # 展示 只存在于分支A 和 只存在于分支B 的提交 |
fetch VS pull
git fetch抓取命令
仅仅会从指定的远程仓库获取最新数据到本地,并在本地创建对应的远程分支引用(e.g., origin/master、origin/feature等)。该命令并不会把远程分支的提交合并、修改到本地分支当中。故你可以使用该命令来查看远程分支的最新状态,而不影响你的本地仓库、分支
1 | # 从远程仓库抓取全部分支的最新数据 |
git pull拉取命令
该命令首先会从远程仓库获取某个远程分支(指定分支 或 当前本地分支关联的远程分支)最新的数据到本地,然后将获取到该远程分支的数据自动合并到当前的本地分支。如果在自动合并过程中发生冲突,Git会停止合并并让你解决这些冲突
1 | # 拉取远程仓库指定分支的数据,并合并到当前的本地分支上 |
所以git pull命令实际上是git fetch、git merge命令的组合
1 | # 从远程仓库抓取指定分支的最新数据 |
撤销修改
撤销 工作区的修改
下述两个命令均可以撤销工作目录中未暂存的修改。换言之,如果某个文件在工作区被修改后,未进行暂存。则使用该命令会将该文件恢复到上一次提交的版本;如果某个文件经历了第一次修改、暂存、第二次修改过程,则此时执行该命令后,该文件只会丢弃第二次的修改。第一次的修改依然保留在暂存区了
1 | git checkout -- <file> |
撤销 暂存区的修改
如果误使用git add命令,将文件修改添加到暂存区。则下述两个命令都可以实现撤销对文件修改的暂存。下图可看到撤销暂存后,文件修改保留在工作目录中,并没有丢失。等待我们下一次添加暂存
1 | git reset HEAD <file> |
git diff命令
git diff命令
该命令比较的是 修改后但还未暂存起来(git add)的差异。所以如果你暂存了(git add)所有修改过的文件,此时执行git diff后会发现差异结果为空
1 | # 比较 工作目录下文件 与 暂存区域快照 之间的差异 |
—staged/—cached 选项
下述两个命令功能上是一样的。都是比较 暂存区(git add)和上一次提交(HEAD)之间的差异
1 | # 下述两个命令功能上是一样的 |
git commit命令
-a选项
使用该选项后,git commit命令会把已跟踪文件的所有修改直接进行提交。相当于跳过了手动执行git add命令的过程。但对于未跟踪的新文件,仍需先使用git add命令手动将它们添加到暂存区
1 | git commit -am <commit message> |
—amend选项
该选项用于修改最近一次提交。注意,如果最近一次的提交已经push到远程仓库了,强烈不推荐使用该选项。原因详见变基rebase相关部分的内容
修改提交信息
进行提交后发现message信息写错了。可以直接执行下述命令,用于修改上一次提交的message
1 | git commit --amend -m <new commit message> |
提交遗漏修改
进行git commit操作提交后,如果发现某些文件遗漏了。我们就需要再次修改、提交。如果不希望出现两次提交记录,就可以提交时添加该选项。这样最终两次git commit操作也只会有一个提交记录,因为第二次git commit操作会包含第一次git commit的修改
1 | # 与上次提交进行合并,并使用新的提交信息 |
参考文献
- 精通Git·第2版 Scott Chacon、Ben Straub著