0x000 导读
讲git的文章很多,但是大部分都是一个套路,讲概念,讲命令,太多的概念和命令总是让人有一种稀里糊涂的感觉,讲的很对,但似乎没能讲透,没有醍醐灌顶的感觉,大概是我的悟性差吧。所以这几天一直在做各种git的实验,并且阅读了一些博客、文档、资料,综合下来整理出了这么一篇文章。注意:
- 本篇文章旨在说明我对
git的理解,只是一家之言,聊以分享。 - 本片文章不是一篇命令教程,或者概念说明,需要一定的
git使用经验和踩坑经验。
0x001 总结[提前]
这是一篇比较乱七八糟的文章,不从传统出发,尝试用自己的思想去理解git这一神奇的工具。以前我觉得git是命运石之门,我们在不同的时间线(分支)上跳跃,所有的事件都必须且只能发生在时间线上。但是现在我觉得git是无限的可能性的集合,一个事件可以引申出无限的可能性。而我们要做的是用工具(branch、tag、reset、rebase、merge....)来组织这些可能性,从而构成一个有序的、向前发展的历史,引导整个历史的发展,构建可以走向未来的工程。
0x002 存档和读档
-
存档
其实吧,版本就是存档,就是游戏中的存档,我们不断的推进项目,完成一个又一个任务,然后中途不断的存档,就形成了版本迭代。而版本多了,自然就需要管理了,自然就有了版本管理系统。
在游戏中,存档可以手动存档,也可以到指定点存档,也可以自动定场景存档。在游戏中,存档之后形成的东西叫做档案,而在
git中,叫做commit。我们可以使用git add+git commit完成一个档案的创建,或者说版本的创建。一个
commit拥有许多的属性,比如ID、Message、Date、Author:等等,这些信息都有助于我们了解这个版本,就像游戏中的存档会以关卡名/图片之类的信息来提示你这个存档所代表的进度,比如使用git log可以获取以下信息:commit 4963bab37862245f85c0754f7759858346ddfcbb (HEAD -> master) Author: ********** Date: Thu Jan 10 15:22:12 2019 +0800 版本H commit 1a4268dc09c467cbae08dcdaadad808d393509df Author: ********** Date: Thu Jan 10 15:25:01 2019 +0800 版本G commit 931b3c9e056adf2178b16910d30769503d60d8c3 Author: ********** Date: Thu Jan 10 15:24:50 2019 +0800 版本F .... 复制代码 -
读档
既然有存档,那就有读档。游戏中直接选择一个档案就行了,那
git中呢?如果有可视化操作工具,那我们直接点击也是可以的,但现在不使用工具,而使用命令行,该如何呢。读取一个存档说白了在git中就是读取一个commit而已,所以我们可以使用git checkout和git reset两个命令来做到,那如何指定版本呢?前面提到的commit属性中的ID可以帮我们实现这个目标。- 环境说明:我在仓库中
git commit了8个,每个commit都添加了一个从a-h的文件,并在commit信息中添加版本标记 - 使用
git checkout切到版本A,可以发现,此时只有文件a$ git checkout 401e1b65a1c8f20ad7295985b2f9296d7f237c5b Note: checking out '401e1b65a1c8f20ad7295985b2f9296d7f237c5b'. ... HEAD is now at 401e1b6 版本A $ ls README.md a.txt 复制代码 - 使用
git reset切换到版本G,可以发现,此时有了a-g几个文件了$ git reset 1a4268dc09c467cbae08dcdaadad808d393509df Unstaged changes after reset: D b.txt D c.txt D d.txt D e.txt D f.txt D g.txt $ git stash Saved working directory and index state WIP on (no branch): 1a4268d 版本G l$ ls README.md a.txt b.txt c.txt d.txt e.txt f.txt g.txt 复制代码
- 环境说明:我在仓库中
-
总结: 我们通过
commit的ID属性配合其他命令来达到了任意读档的目的,可以在各种版本中随意穿梭,快乐的很啊。而读档的姿势其实还有很多,但不外乎是对commit操作方式的不同,在git中,我觉得commit 才是构成整个版本管理的基本栗子。每一个commit都是独立的个体,虽然和其他commit存在着关联,但是依旧是独立的,而我们在commit构成节点序列中来回移动和操作,就可以达到所有对版本管理的目的。
0x003 别名系统
在上一个章节中,我们已经可以依靠一些命令和commit ID做到在版本中自由的穿梭,但是却带来一个问题,那就是commit ID的记忆难度。commit ID是hash值,尽管git支持只提供前几位就能匹配到hash,并且也提供了commit message来说明commit,但是依旧存在commit的辨识和记忆问题。而这个问题,可以通过别名系统来解决。
所谓的别名系统,其实是我自己归纳的概念,指的其实就是HEAD、branch、tag这三个东西。在我看来,这三个东西都是一样的东西,都是别名,也就是标记一个commit的东西而已,只是在行为表现上有一些区别。
-
HEAD一个仓库只有一个
HEAD,指向你当前所在的commit。如果你创建了一个commit,HEAD将会指向这个新的commit。也可以通过命令,强制HEAD指向某个commit,比如reset、checkout。也就是不论你在哪个commit之上,那么HEAD就在哪儿,或者说,其实你能在哪个commit,是通过修改HEAD指向的commit实现的。-
通过修改
HEAD在各个版本之间旋转跳跃
-
-
branch一开始我觉得这个东西才是
git的核心,因为创建项目的时候,我们就处于master分支之上,并且我们在工作中,往往也是基于分支工作的。但是后来发现,分支在本质上毫无意义,并不需要真的基于branch去工作,基于commit就行了。而branch只是提供了一个方式来管理这些commit。branch和HEAD相同点是随着新的commit的创建,branch指向的commit会不断更新,当然前提是你需要在这个branch所在的commit上创建新的commit。而branch和HEAD的不同点在于HEAD只能有一个,branch可以有多个。实验一:用
branch来实现切换版本- 目前的库情况
$ git log --pretty=oneline 1a4268dc09c467cbae08dcdaadad808d393509df (HEAD) 版本G 931b3c9e056adf2178b16910d30769503d60d8c3 版本F 071d00a07c5f04bee1cf9a9548c4953914dfcc6b 版本E 0caa2b71d1b03d2dacc7c2c92699466b632da185 版本D 7855905df12bccfd38aad8f5b3b95904dc44233a 版本C 129526058361af7b59b22dd059f15c7e288b9862 版本B 401e1b65a1c8f20ad7295985b2f9296d7f237c5b 版本A 复制代码 - 为版本
A-G分别创建一个分支$ git checkout 1a4268dc09c467cbae08dcdaadad808d393509df -b G Switched to a new branch 'G' $ git checkout 931b3c9e056adf2178b16910d30769503d60d8c3 -b F Switched to a new branch 'F' $ git checkout 071d00a07c5f04bee1cf9a9548c4953914dfcc6b -b E Switched to a new branch 'E' $ git checkout 0caa2b71d1b03d2dacc7c2c92699466b632da185 -b D Switched to a new branch 'D' $ git checkout 7855905df12bccfd38aad8f5b3b95904dc44233a -b C Switched to a new branch 'C' $ git checkout 129526058361af7b59b22dd059f15c7e288b9862 -b B Switched to a new branch 'B' $ git checkout 401e1b65a1c8f20ad7295985b2f9296d7f237c5b -b A Switched to a new branch 'A' $ git log --pretty=oneline 1a4268dc09c467cbae08dcdaadad808d393509df (HEAD -> G) 版本G 931b3c9e056adf2178b16910d30769503d60d8c3 (F) 版本F 071d00a07c5f04bee1cf9a9548c4953914dfcc6b (E) 版本E 0caa2b71d1b03d2dacc7c2c92699466b632da185 (D) 版本D 7855905df12bccfd38aad8f5b3b95904dc44233a (C) 版本C 129526058361af7b59b22dd059f15c7e288b9862 (B) 版本B 401e1b65a1c8f20ad7295985b2f9296d7f237c5b (A) 版本A 复制代码 - 接下来就可以换一种方式在版本之间跳跃了,并且不需要记住或者查询冗长的
commit ID$ git checkout A Switched to branch 'A' $ git checkout B Switched to branch 'B' $ git checkout C Switched to branch 'C' $ git checkout E Switched to branch 'E' $ git checkout F Switched to branch 'F' $ git checkout G Switched to branch 'G' 复制代码
实验二:分支跟随新的
commit-
当前库的情况,注意:这里的
HEAD -> G表示HEAD指向了branch G,而branch G指向了版本G$ git log --pretty=oneline 1a4268dc09c467cbae08dcdaadad808d393509df (HEAD -> G) 版本G 931b3c9e056adf2178b16910d30769503d60d8c3 (F) 版本F 071d00a07c5f04bee1cf9a9548c4953914dfcc6b (E) 版本E 0caa2b71d1b03d2dacc7c2c92699466b632da185 (D) 版本D 7855905df12bccfd38aad8f5b3b95904dc44233a (C) 版本C 129526058361af7b59b22dd059f15c7e288b9862 (B) 版本B 401e1b65a1c8f20ad7295985b2f9296d7f237c5b (A) 版本A 复制代码 - 添加一个文件,创建一个
commit$ echo 'h'> h.txt $ git add h.txt $ git commit -m '版本H' [G d346d27] 版本H 1 file changed, 1 insertion(+) create mode 100644 h.txt 复制代码 - 此时查看
log,可以看到HEAD和G都指向了版本H,就是所谓的branch跟着commit动,但是它真的是跟着commit动吗?$ git log --pretty=oneline d346d279a5073b57ef86f5e7865f52f8286e34cd (HEAD -> G) 版本H 1a4268dc09c467cbae08dcdaadad808d393509df 版本G 931b3c9e056adf2178b16910d30769503d60d8c3 (F) 版本F 071d00a07c5f04bee1cf9a9548c4953914dfcc6b (E) 版本E 0caa2b71d1b03d2dacc7c2c92699466b632da185 (D) 版本D 7855905df12bccfd38aad8f5b3b95904dc44233a (C) 版本C 129526058361af7b59b22dd059f15c7e288b9862 (B) 版本B 401e1b65a1c8f20ad7295985b2f9296d7f237c5b (A) 版本A 复制代码
实验三:分支跟着啥动
- 将
HEAD指向版本G的commit,而不是分支G,也就是使用git checkout commitID,而不是使用git checkout branchName,可以看到,此时HEAD不指向G,而是HEAD和G同时指向了版本H的commit。$ git checkout d346d279a5073b57ef86f5e7865f52f8286e34cd # 版本 H 的 commitID $ git log --pretty=oneline d346d279a5073b57ef86f5e7865f52f8286e34cd (HEAD, G) 版本H 1a4268dc09c467cbae08dcdaadad808d393509df 版本G 931b3c9e056adf2178b16910d30769503d60d8c3 (F) 版本F 071d00a07c5f04bee1cf9a9548c4953914dfcc6b (E) 版本E 0caa2b71d1b03d2dacc7c2c92699466b632da185 (D) 版本D 7855905df12bccfd38aad8f5b3b95904dc44233a (C) 版本C 129526058361af7b59b22dd059f15c7e288b9862 (B) 版本B 401e1b65a1c8f20ad7295985b2f9296d7f237c5b (A) 版本A 复制代码 - 继续创建一个
commit,可以看到,这个时候分支G不再跟着commit移动了,所以,只有在HEAD指向branch的时候,branch才会向前移动,也就是只要HEAD来到branch身边,branch就会跟着HEAD跑。$ echo 'i'> i.txt $ git add i.txt $ git commit -m "版本I" [detached HEAD 2e836eb] 版本I 1 file changed, 1 insertion(+) create mode 100644 i.txt $ git log --pretty=oneline 2e836ebb00b722a29a99101b1c6f7b276aeed033 (HEAD) 版本I d346d279a5073b57ef86f5e7865f52f8286e34cd (G) 版本H 1a4268dc09c467cbae08dcdaadad808d393509df 版本G 931b3c9e056adf2178b16910d30769503d60d8c3 (F) 版本F 071d00a07c5f04bee1cf9a9548c4953914dfcc6b (E) 版本E 0caa2b71d1b03d2dacc7c2c92699466b632da185 (D) 版本D 7855905df12bccfd38aad8f5b3b95904dc44233a (C) 版本C 129526058361af7b59b22dd059f15c7e288b9862 (B) 版本B 401e1b65a1c8f20ad7295985b2f9296d7f237c5b (A) 版本A 复制代码
- 目前的库情况
-
tagtag是比较特殊的一个别名类型,他无法移动,或者说不推荐移动。一旦一个tag和指向某个coimmit,就不希望它移动,因为tag就是用来标记这个commit的,他是一个孤独而忠诚的守望者,而不像branch,花间游龙似的浪子。- 现在库的情况
$ git log --pretty=oneline 1a4268dc09c467cbae08dcdaadad808d393509df (HEAD, G) 版本G 931b3c9e056adf2178b16910d30769503d60d8c3 (F) 版本F 071d00a07c5f04bee1cf9a9548c4953914dfcc6b (E) 版本E 0caa2b71d1b03d2dacc7c2c92699466b632da185 (D) 版本D 7855905df12bccfd38aad8f5b3b95904dc44233a (C) 版本C 129526058361af7b59b22dd059f15c7e288b9862 (B) 版本B 401e1b65a1c8f20ad7295985b2f9296d7f237c5b (A) 版本A 复制代码 - 为每个版本添加一个
tag,为了区别分支名,统统加了个T$ git tag TA A $ git tag TB B $ git tag TC C $ git tag TD D $ git tag TE E $ git tag TF F $ git tag TG G $ git log --pretty=oneline 1a4268dc09c467cbae08dcdaadad808d393509df (HEAD, tag: G, G) 版本G 931b3c9e056adf2178b16910d30769503d60d8c3 (tag: TF, F) 版本F 071d00a07c5f04bee1cf9a9548c4953914dfcc6b (tag: TE, E) 版本E 0caa2b71d1b03d2dacc7c2c92699466b632da185 (tag: TD, D) 版本D 7855905df12bccfd38aad8f5b3b95904dc44233a (tag: TC, C) 版本C 129526058361af7b59b22dd059f15c7e288b9862 (tag: TB, B) 版本B 401e1b65a1c8f20ad7295985b2f9296d7f237c5b (tag: TA, A) 版本A 复制代码 - 现在又多了一种旋转跳跃的方式了
$ git checkout TA Previous HEAD position was 1a4268d 版本G HEAD is now at 401e1b6 版本A $ git checkout TB Previous HEAD position was 401e1b6 版本A HEAD is now at 1295260 版本B $ git checkout TC Previous HEAD position was 1295260 版本B HEAD is now at 7855905 版本C $ git checkout TD Previous HEAD position was 7855905 版本C HEAD is now at 0caa2b7 版本D $ git checkout TE Previous HEAD position was 0caa2b7 版本D HEAD is now at 071d00a 版本E $ git checkout TF Previous HEAD position was 071d00a 版本E HEAD is now at 931b3c9 版本F $ git checkout TG Previous HEAD position was 931b3c9 版本F HEAD is now at 1a4268d 版本G 复制代码 - 总结
所以,不管是
HEAD、tag、branch,都是一种别名,除了行为表现上的差别,没有太大的不同,特别是branch和tag,不过都只是提供了一种管理commit的方式。
- 现在库的情况
0x004 分叉
在上一章节中,我们揭开了别名系统的红盖头,这一章,我们就开始探索一下分叉的神秘。
和游戏中的存档一样,有时候一个游戏有许多的选择,这些选择指向了不同的结果。而作为游戏玩家,我们希望能够走完所有的选择,以探索更多的游戏乐趣。所以我们会在做选择的时候存档,而当我们走完一个选择,就会读取这个存档,继续往另一个选择探索。这个时候,就产生了两个不同的剧情走向,这就是分叉。
在git中,其实我们可以有无数的选择,每一个commit可以创建无数的commit,就会引申出无数的可能。
- 我们遇到了一个抉择,所以需要创建版本,暂时称为
版本X吧$ git log --pretty=oneline 2caeda3e3555f1d134212c2bc6b262026c295743 (HEAD) 版本X .... 复制代码 - 然后我们选择了走
Y,并且沿着Y1一直走到Y3,这是尽头$ git log --pretty=oneline d2e0375ba096c36fcd96e75d142128d3e07d767e (HEAD) 版本Y3 4ca86274b4f7473ac6295100f08d9f3424cf6b8d 版本Y2 fcff93ee95aa2be3418fc73db4c9ce9ed3201790 版本Y1 2caeda3e3555f1d134212c2bc6b262026c295743 版本X ... 复制代码 - 接着我们返回
X,并选择另一个选择Z,从Z1走到Z3$ git checkout 2caeda3e3555f1d134212c2bc6b262026c295743 # 切到`版本X` $ git log --pretty=oneline 16ffb118853798475d38f75bb9c157cc4d0c39cd (HEAD) 版本Z3 0ca53ec8f01cc417d20ce8f5f9399501b696a077 版本Z2 b4a71198961d3e86af6324b4cdeee6e3a2a08ac2 版本Z1 2caeda3e3555f1d134212c2bc6b262026c295743 版本X ... 复制代码 - 总结
可以看到,我们顺着两个选择一直往下发展,在这发展的过程中,我们完全没有用到tag和branch,也就是为了印证commit 是构成 git 世界的基本栗子这一说明。
从git log中,我们看不到了Y走向,那Y真的消失了吗?不是的,我们依旧可以通过Y的commit ID来寻回Y的记录。当然为了方便在YZ中切换,我们可以使用branch来标记一下YZ两个走向,这样就形成了YZ两个branch了,也就是分叉!
那那些没有被branch或者tag标记的commit呢?他们会消失吗?会,也不会。不会是因为不被标记的commit将变成dangling commit,我称之为游离的commit,是git中最孤独的存在,只要我们知道commitID,就会可唤回它。但是很大的可能是我们永远不会记得这么一个不被引用的commit,所以我呼吁,善待每一个commit。会是因为还是可能会被回收的,看这里,git 也有 gc。
0x003 合并
和游戏的存档不同的是,git中的版本可以合并,也就是说我可以在分支Y中做完任务Y1、Y2、Y3,然后分支Z中完成任务Z1、Z2、Z3,然后合并这两个分支,结果回到了X,但是却完成了Y1-y3、Z1-Z3,并拿到了神器Y和Z,这让boss怎么活?
-
实验一:使用
merge合并commit- 创建
版本O$ echo O >> o.txt $ git add o.txt $ git commit -m '版本O' [detached HEAD 478fa6d] 版本O 1 file changed, 1 insertion(+) create mode 100644 o.txt 复制代码 - 基于
版本O创建版本P1$ echo P >>p1.txt $ git add p1.txt $ git commit -m '版本P1' [detached HEAD a3ab178] 版本P1 1 file changed, 1 insertion(+) create mode 100644 p1.txt $ git log --pretty=oneline a3ab178f950163f9eb5b7e857226bb616517d0d7 (HEAD) 版本P1 478fa6d05021bbf7380ab69689c5683a84a394a4 版本O 复制代码 - 基于
版本O创建版本P2$ git checkout 478fa6d05021bbf7380ab69689c5683a84a394a4 # 版本O 的 commitID $ echo p2 >> p2.txt $ git add p2.txt $ git commit -m '版本P2' [detached HEAD cbccf52] 版本P2 1 file changed, 1 insertion(+) create mode 100644 p2.txt $ git log --pretty=oneline cbccf5206d3225fd3fab7c944573042d199bc82b (HEAD) 版本P2 478fa6d05021bbf7380ab69689c5683a84a394a4 版本O 复制代码 - 合并
版本P1到版本P2$ git merge a3ab178f950163f9eb5b7e857226bb616517d0d7 # 版本P1 的 commitID $ git log --pretty=oneline 656a5426db088a1cbeba1cadc2c6cddd3ae9df1f (HEAD) Merge commit 'a3ab178f950163f9eb5b7e857226bb616517d0d7' into HEAD cbccf5206d3225fd3fab7c944573042d199bc82b 版本P2 a3ab178f950163f9eb5b7e857226bb616517d0d7 版本P1 478fa6d05021bbf7380ab69689c5683a84a394a4 版本O 复制代码
- 创建
-
实验三:使用
rebase合并切换到
版本P2,在版本P2中使用rebase$ git checkout cbccf5206d3225fd3fab7c944573042d199bc82b # 版本P2 的 commitID .... HEAD is now at cbccf52 版本P2 $ git rebase a3ab178f950163f9eb5b7e857226bb616517d0d7 # 版本P1 的 commitID First, rewinding head to replay your work on top of it... Applying: 版本P2 $ git log --pretty=oneline 3bd7820a805e70ecd2d4791f7158b3eab69d5456 (HEAD) 版本P2 a3ab178f950163f9eb5b7e857226bb616517d0d7 版本P1 478fa6d05021bbf7380ab69689c5683a84a394a4 版本O 复制代码 -
实验四:使用
cherry-pick合并- 切换到
版本O,新建版本P3$ echo 'p3'>> p3.txt $ git add p3.txt $ git commig -m '版本P3' git: 'commig' is not a git command. See 'git --help'. The most similar command is commit $ git commit -m '版本P3' [detached HEAD ae09e94] 版本P3 1 file changed, 1 insertion(+) create mode 100644 p3.txt $ git log --pretty=oneline ae09e94fdab55dd69f4cf7ca2f3658c20026c441 (HEAD) 版本P3 478fa6d05021bbf7380ab69689c5683a84a394a4 版本O 复制代码 - 切换到
版本P2中使用cherry-pick合并版本P3的东西$ git checkout 3bd7820a805e70ecd2d4791f7158b3eab69d5456 # 版本P2 的commitID ... HEAD is now at 3bd7820 版本P2 $ git cherry-pick ae09e94fdab55dd69f4cf7ca2f3658c20026c441 # 版本P3 的 commitID [detached HEAD f9dfba2] 版本P3 Date: Sat Jan 12 11:35:27 2019 +0800 1 file changed, 1 insertion(+) create mode 100644 p3.txt $ git log --pretty=oneline f9dfba2fcc22cd35c7e654b2ab1c17501124fb02 (HEAD) 版本P3 3bd7820a805e70ecd2d4791f7158b3eab69d5456 版本P2 a3ab178f950163f9eb5b7e857226bb616517d0d7 版本P1 478fa6d05021bbf7380ab69689c5683a84a394a4 版本O 复制代码
- 切换到
-
注意:合并中的冲突解决
合并的过程中可能会出现冲突,比如同时拿到神器
P1、P2,但是在P1中卖掉了O之前拿到的装备S,而在P1中则为S镶上了宝石,那么合并之后要怎么处理?是卖掉S?还是保留镶宝石的S?还是镶了宝石再卖掉?深井冰啊!我不要面子的啊...所以这里就涉及到了合并的冲突解决,这里不再赘述,不是我要讲的内容。
0x005 变基
这个名词让人想入菲菲啊,每次项目新成员加入,总是会提醒他们注意要变基....
这里不去说merge和rebase的爱恨情仇,只说一些rebase的操作,用rebase来整理commit。
上面说到commit 是构成 git 世界的基本栗子,所以,我们需要掌握一些栗子的操作方式
-
查看
commit,可以使用git log,如果需要寻回忘记的commit,可以使用reflog来尝试看看是否能够找到$ git log --pretty=oneline 68de3f74c6a4799a54047efe084336d5452f6bb0 (HEAD -> X) Merge branches 'Y' and 'Z' into X 16ffb118853798475d38f75bb9c157cc4d0c39cd (Z) 版本Z3 ... $ git reflog 23e799e (HEAD) HEAD@{0}: rebase -i (pick): 版本Z 01a10d6 HEAD@{1}: rebase -i (squash): 版本Y a15dd72 HEAD@{2}: rebase -i (squash): # This is a combination of 2 commits. b6f2ea3 HEAD@{3}: rebase -i (start): checkout 100468330c7819173760938d9e6d4b02f37ba001 f4c4ccc HEAD@{4}: rebase -i (abort): updating HEAD ... 复制代码 -
创建
创建使用
git add+git commit就行了$ echo error > error.txt $ git add error.txt $ git commit -m '一个错误的版本' [X bc90774] 一个错误的版本 1 file changed, 1 insertion(+) create mode 100644 error.txt $ git log --pretty=oneline bc907749174ee0db222de16d60126c39fa3096fa (HEAD -> X) 一个错误的版本 68de3f74c6a4799a54047efe084336d5452f6bb0 Merge branches 'Y' and 'Z' into X ... 复制代码 -
更新上一个
commit,直接使用git commit --amend$ echo error2 >> error.txt $ git add error.txt $ git commit --amend // 这里将打开一个vim窗口 一个错误的版本 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Fri Jan 11 17:21:18 2019 +0800 # # On branch X # Changes to be committed: # new file: error.txt # // 保存退出之后输出 [X d5c4487] 一个错误的版本 Date: Fri Jan 11 17:21:18 2019 +0800 1 file changed, 2 insertions(+) create mode 100644 error.txt 复制代码 -
要更新历史中的
commit也是可以做到的,例如需要在版本X中加入文件x1.txt,要使用交互式模式git rebase -i 2e836ebb00b722a29a99101b1c6f7b276aeed033 # 指向 版本X 的前一个 commit 复制代码此时将打开一个交互式窗口
pick 913b571 版本X pick 0eca5e3 版本Y1 pick 33a9ca3 版本Y2 pick b95b3ca 版本Y3 pick 839c481 版本Z1 pick 6fb6cb3 版本Z2 pick c28d3e0 版本Z3 ... 复制代码将
版本X前面的pick改为e或者edit,保存,然后退出,这个时候,仓库将会回到版本X的状态,并输出Stopped at 913b571... 版本X You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue 复制代码添加文件
x1$ echo x1 > x1.txt $ git add x* $ git commit --amend // 打开交互式窗口可以修改 commit message $ git rebase --comtinue Successfully rebased and updated detached HEAD. 复制代码此时又回会到原本的版本并且多出了文件
x1,就像是在版本X中就已经加入一样 -
插入一个新的
commit,上面的栗子中不使用--amend,就会在X和Y1之间插入一个新的commit$ git rebase -i 2e836ebb00b722a29a99101b1c6f7b276aeed033 // 交互式窗口,吧`pick`改为`e` $ echo x2 > x2.txt $ git add x2.txt $ git commit -m '插入一个版本X2' [detached HEAD 1b4821f] 插入一个版本X2 1 file changed, 1 insertion(+) create mode 100644 x2.txt $ git rebase --continue Successfully rebased and updated detached HEAD. $ git log --pretty=oneline 30a57a7ed0bd2b04ee61ba14b59d60b416b4c22f (HEAD) 版本Z3 4b0069b1b87f98aa90b792b97011ef08845ffd7b 版本Z2 cc1d571328d765019b2e21a33a926de973152263 版本Z1 595e928fed812c7de0e6b9732beeff6489dd8a3d 版本Y3 4456b102fca02d12b51ae1a6397d84d5f298233f 版本Y2 b6f2ea31b3ea5ec78b384e9b1669ac2c7063c339 版本Y1 1b4821fcb5e296e282ffe9a87791ec1a94281a2f 插入一个版本X2 100468330c7819173760938d9e6d4b02f37ba001 版本X 复制代码 -
删除
删除一个分支可以使用交互式
rebase,命令:git rebase -i commitID,这里的commitID必须是你要删除的commit的前一个commit。$ git rebase -i 68de3f74c6a4799a54047efe084336d5452f6bb0 复制代码此时将会打开一个
vim窗口pick bf2c542 版本Y1 pick 588feec 版本Y2 pick 1b2ae37 版本Y3 pick 38f7cf3 版本Z1 pick 080e442 版本Z2 pick 206a7ae 版本Z3 pick 6b01f70 一个错误的版本 # Rebase 2caeda3..6b01f70 onto 2caeda3 (7 commands) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # d, drop = remove commit ... 复制代码要删除
一个错误的版本,将前面的pick改为d或者dropd 6b01f70 一个错误的版本 复制代码保存退出,输出
Successfully rebased and updated refs/heads/X. 复制代码 -
合并多个
commit,比如合并Z1-Z3,打开交互式窗口之后,将Z2、Z3的pick改为s$ git rebase -i 100468330c7819173760938d9e6d4b02f37ba001 // 打开了交互式窗口 pick bf2c542 版本Y1 pick 588feec 版本Y2 pick 1b2ae37 版本Y3 pick 38f7cf3 版本Z1 s 080e442 版本Z2 s 206a7ae 版本Z3 复制代码保存退出以后,又打开交互式窗口,显示要合并的
commit的message,这里可以修改commit。# This is a combination of 3 commits. # This is the 1st commit message: 版本Z1 # This is the commit message #2: 版本Z2 # This is the commit message #3: 版本Z3 复制代码这里修改为
Z,保存,退出,输出,可以看到,Z1-Z3消失了,取而代之的是Z,对Y1-Y3做操作detached HEAD f4c4ccc] 版本Z Date: Fri Jan 11 16:27:00 2019 +0800 1 file changed, 3 insertions(+) create mode 100644 z.txt Successfully rebased and updated detached HEAD. $ git log --pretty=oneline f4c4ccc548052b884013c9b133db85e3e730a829 (HEAD) 版本Z 595e928fed812c7de0e6b9732beeff6489dd8a3d 版本Y3 4456b102fca02d12b51ae1a6397d84d5f298233f 版本Y2 b6f2ea31b3ea5ec78b384e9b1669ac2c7063c339 版本Y1 $ git rebase -i 100468330c7819173760938d9e6d4b02f37ba001 [detached HEAD 01a10d6] 版本Y Date: Fri Jan 11 16:24:37 2019 +0800 1 file changed, 3 insertions(+) create mode 100644 y.txt Successfully rebased and updated detached HEAD. $ git rebase --continue 复制代码 -
重新排序
commit顺序,比如重排版本Y和版本Z,交换一下顺序就好了$ git log --pretty=oneline 23e799e9d6445f6f2fea50cf9599f7c407b521ee (HEAD) 版本Z 01a10d6b62bea2f8391e25d193020a456c5b301f 版本Y $ git rebase -i 1b4821fcb5e296e282ffe9a87791ec1a94281a2f 复制代码这时候打开交互式窗口,显示
pick a1942a3 版本Y pick eeabc6c 版本Z 复制代码将它交换顺序,保存,退出
pick eeabc6c 版本Z pick a1942a3 版本Y 复制代码查看结果
Successfully rebased and updated detached HEAD. $ git log --pretty=oneline a1942a31048eabf70210034b0f2d9e2540e88064 (HEAD) 版本Y eeabc6cf5a54ea84ba1ac675a8bb80eae7237ffa 版本Z 复制代码