git 分区
- 工作区 -> 暂存区
git add {file}
- 暂存区 -> 仓库
git commit
- 仓库 -> 暂存区
git reset --soft {commit id}
- 暂存区 -> 工作区
git reset HEAD -- {file}
git 底层存储格式
- git 底层存储放在 .git/objects 文件夹下,分为 commit、tree、blob 三类 object
- 每个 object 以key-value 形式存在:key(文件名) 是基于 value 生成的哈希字符串
- commit object: 对应于每一次的提交行为,内容包含指向前一个 commit object 的 key,当前 commit 下 tree 的 key;此外还记录了提交人、时间、摘要等信息
- tree object: 对应于每个文件夹,记录了文件夹下子 object 的信息. tree 之间可以进行嵌套
- blob object: 对应为每个文件. value 是文件内容,key 是以文件内容生成的 hash 字符串
- branch head: 对应为每个分支的头指针,指向该分支下一个 commit object 的 key
• git 底层存储介质是一个 kv 数据库,key 是基于 value 通过 SHA-1 算法生成的摘要字符串
• git kv 数据库中包含 3 类 object,commit、tree、blob:
blob object:与仓库下的一个文件一一对应. value 是文件内容,key 是哈希摘要 tree
object:与仓库的一个文件夹一一对应. value 是文件夹下的子 object 信息,key 是哈希摘要 commit
object:与一次提交行为一一对应. value 包含了前一次提交的 key(parent);整个仓库文件夹的
key(tree);以及提交行为信息(committer、note 等)
所谓【git版本控制】,概念拆解后得到:
其中的【版本】,对应为一次提交行为及其生成的 commit object 其中的【版本控制】,是在基于 commit
版本链的基础上进行延伸、修改和移动操作
【版本控制】过程中的复用策略是,能复用所有 value 值未发生变化的 object
常用命令解析
git log --graph
如前文所介绍, 分支与分支之间是可能存在分叉和合并的拓扑关系的,比如在执行 checkout -b {new branch} 或者 merge {new branch} 操作时,版本链可能演化成分叉并交汇的模型. 此时我们可以在 git log 基础上添加上 ——graph 和 ——oneline 的子参数,以树状单行的形式更清晰直观地展示 commit 链的拓扑结构.
git log --graph --oneline
以下为例,对应输出对应的版本链拓扑结构内容为:
git log --graph --oneline
* 8e4b529 (HEAD -> master) Merge branch 'test'
|\
| * a24e6d8 (test) second commit
* | dfa7be2 second commit
|/
* 3a3ad28 first commit
可以看到其很好地还原了 master 分支与 test 分支之间分叉和聚合的拓扑结构:
git merge 与 git rebase
- 背景是:我们有 branch1 和 branch2 两个分支;两个分支公共 commit 祖先为 commit1、commit2、commit3;branch1 独有的 commit 为 commit4、commit5;branch2 独有的 commit 为 commit6、commit7.
- 接下来切换到 branch1,branch HEAD 指向 commit5,然后执行 git merge branch2 指令
- 最后得到的结果是在 branch1 commit5 的基础上新生成了一个 commit8,其汇总了本次合并操作所涉及到的 branch2 的变更内容,并且有两个 parent 指针同时指向 branch1 commit5 和 branch2 commit7
git checkout branch 1
git merge branch2
执行完 merge 操作后,在 branch1 下执行 git log 指令后得到的信息如下所示,可以看到与上面的示意图是一一对应的:
git log --oneline --graph
* b79e36c (HEAD -> master) commit 8
|\
| * 9a75b57 (branch2) commit 7
| * fad3156 commit 6
* | c440186 commit 5
* | 3d1198d commit 4
|/
* 5845278 commit 3
* 10f4b01 commit 2
* 0d3a41d commit 1
- 背景是:我们有 branch1 和 branch2 两个分支;两个分支公共 commit 祖先为 commit1、commit2、commit3;branch1 独有的 commit 为 commit4、commit5;branch2 独有的 commit 为 commit6、commit7.
- 接下来切换到 branch1,branch HEAD 指向 commit5,然后执行 git rebase branch2 指令
- 最后得到的结果是在 branch1 会复用 branch2 的 commit6 和 commit7 作为基点, 然后将对应于 commit4 和 commit5 的两次提交挂载在 commit7 之后. 需要强调的是,执行完 rebase 操作后,新追加的两个 commit 应该称为 commit4' 和 commit5' 更为合适一些,虽然其提交的内容和 commit4、commit5 相同,但此时需要和 commit6、commit7 进行合并并解决可能存在的冲突,且其指向的 parent 也由 commit3 变为了 commit7,因此本质上是两批独立的 commit.
git rebase branch2
执行完 rebase 操作后,下面在 branch1 下执行 git log 指令,展示结果和上图一致:
git log --oneline --graph
* 19b585d (HEAD -> master) commit 5'
* 64c93c4 commit 4'
* 9a75b57 (branch2) commit 7
* fad3156 6th commit 6
* 5845278 3rd commmit 3
* 10f4b01 2nd commit 2
* 0d3a41d 1st commit 1
在 rebase 过程中,倘若变基操作发生冲突,可以在手动修复冲突后执行 git add/rm 操作后,进一步执行 rebase continue 指令推进 rebase 流程:
git rebase --continue
倘若 rebase 过程中发生错误,需要回滚本次操作,可以执行 abort 操作进行回滚:
git rebase --abort
至此我们做一轮小结:
git merge 和 git rebase 都能实现分支之间合并交互的效果,但是团队协作时大家往往比较推崇使用 rebase 指令胜过 merge 指令,主要原因在于:
- 相比于 merge 操作后形成环状交汇的版本链,rebase 操作后的版本链仍保持为单向链表,更加清爽直观
- rebase 还支持 git rebase -i 的交互式变基操作,可以提供更加灵活的变基功能,这部分我们在 4.3 小节中展开
git cherry-pick
cherry-pick 直译为 【摘樱桃】 ,指的是在像摘取樱桃一样轻松地获取到一个个 commit 对象并将其延伸到当前分支的尾部:
下面我们演示一下 cherry-pick 具体效果.
在上小节的基础上,我们首先切换到 branch2 分支:
git checkout branch2
接下来执行 cherry-pick 指令,一次性摘取 branch1 当中的 commit4’‘、commit9、commit10 三个 commit,延伸追加到 branch2 的尾部:
git cherry-pick 5685f7e .. 0c3d861
执行上述 cherry-pick 操作前后,branch2 分支拓扑结构的变化示意如下图所示:
需要注意的是,摘取后延伸到 branch2 尾部的 commit4'''、commit9'、comit10' 相较于 branch1 中的 commit4''、commit9、commit10 也分别是独立的 commit 对象.
git log --oneline --graph
* 4bf9f47 (HEAD -> branch2) 10th
* 445e4ab 9th
* 4164373 4th
* 9a75b57 7th
* fad3156 6th
* 5845278 3rd
* 10f4b01 2nd
* 0d3a41d 1st
在 git cherry-pick 的过程很可能会发生内容冲突,此时需要手动修复冲突,通过 git add/rm 指令将修复后的内容进行添加或者移除,并在此之后执行 continue 指令继续推进 cherry-pick 进程:
git cherry-pick --continue
倘若本次 cherry-pick 操作需要回滚,执行 abort 指令即可:
git cherry-pick --abort