【摘】Git- 从零单排 04 期
前言
今天,笔者针对几个平时可用可不用的指令来聊聊自己的理解。可不用,因为只要会了add commit pull push,就满足了平常的工作需要;可用,在合适的时候,这些指令能有奇效,能提高工作效率。<br />_行文时,系统环境为 macOS Mojave v10.14.5,Git版本为 2.20.1 ,开发工具为 vscode 1.45.1 。
stash
当你遇到要切换到其它分支,而又不想把手上做了一半的工作创建一次提交时。你就可以使用 git stash 来把手上的修改 贮藏 起来。比如,你一个功能正写了一半,老板专门过来跟你讲,不得了,线上有一个大 bug 需要你现在马上修复,这个时候你就可以把你那写了一半的功能 stash 一下,切一个 hotfix 分支改老板的 bug 。bug 改完,然后再 pop 一下,接着写那一半的功能。指令如下:
基本的操作也就是上面那些。接下来,笔者就讲一下自己对 stash 的理解。主要就讲一下关键的 怎么存 和 怎么应用
git stash
的时候, Git 会以当前 暂存区 为内容,生成一个 commit(先称为C1),这个提交的 父对象 指向 HEAD ;再以 工作区 为内容生成一个另外一个 commit(先称为C2),这个提交的父对象分别是 HEAD (父1对象)和 C1(父2对象), 然后把 stash@{0} 指向 C2 ,并且把 C2 的校验和写入到 .git/refs/stash 文件里(这里是覆盖写入),把 C2 的相关信息(parent等)写入到 .git/logs/refs/stash 文件里(这里写入到尾部)。 这个工作区的然后这两个对象会跟其它对象一样,被存放在 objects 下。然后,还会把存好之后,你的工作区和暂存区就都是干净的。这样,贮藏就完成了。git stash apply stash@{0}
的时候, Git 就去取出 stash@{0} 对象,然后把它与当前内容合并(类似 merge 效果)。
稍微提一下,如果想查看 stash@{0} 的校验和,你可以用 git rev-parse stash@{0}
;如果只是想看一下贮藏了哪些修改,你可以 git diff master stash@{0}
。
rebase
git rebase
在另一个基础技巧上重新应用提交。也就是大家说的变基。这里说的 基 是什么呢?就是一个 commit 的 parent 对象,commitA 是基于 commitB 提交的,那这个 commitB 就是 commitA 基 。变基呢,就是改变这个 parent 。也可以是把一个提交片段的 基 给改了。
git rebase -i [startpoint] [endpoint]
,其中-i
的意思是--interactive
,即弹出交互式的界面让用户编辑完成合并操作,[startpoint]
[endpoint]
则指定了一个编辑区间,如果不指定[endpoint]
,则该区间的终点默认是当前分支HEAD
所指向的commit
(注:该区间指定的是一个前开后闭的区间,就是不会操作[startpoint])。输入指令后,界面会把你选中的区间给列出来。
类似这样,我们只要修改操作的选项就能达到把pick改成s就是合并,改成d就删除。
git rebase --onto commitA commitStart commitEnd
,这里的大概意思就是,把 commitStart 到 commitEnd(不包含 commitStart )这一段提交记录,应用到 commitA 上。或者说,把 commitStart的下一个提交的 parent 对象指向 commitA。当然,commitStart 到 commitEnd 这中间的提交都会重新生成,会有新的 commitId ,之前的提交还是在它本来的位置。使用 --onto
的好处就是,我可以随意的选择提交片段的起点和终点,可以控制这个片段的长度。注意,终点最好写branch,如果写HEAD(或其它指针),则会把HEAD从branch脱离。
git rebase commitA
,这个其实跟 --onto
是一样的,只是不能随意的选择你想操作的提交片段。我们假设 commitA 的 log 是: init -> a -> b -> c -> d。而当前 HEAD 的 log 是: init -> 1 -> 2 -> 3-> 4。那么 git rebase commitA
就等于 git rebase --onto commitA init HEAD 。大白话就是,把当前分支提交记录里,不在 commitA 提交的记录的那一部分,重新应用到 commitA 上。<br />当然,
git rebase -i` 也是操作提交片段。
我们可以思考一下,适用场景有哪些,比如:
1. 把多个提交合成一个提交,跟下文讲 resert 时说的整理小技巧一样。
2. 删除提交记录里的某一个。
3. 修改提交记录里的某一个。如果说,你只是想改最后一个提交,你可以使用git commit --amend
来修改,但是你想修改很早以前的就需要用 rebase 了。
4. 把其它分支的修改‘应用’到当前分支来。
revert
git revert
用于记录一些新提交,以逆转一些较早提交的效果。大概的效果就是,把指定的提交的修改补丁移除,然后生成一个新的提交。比如你提交一个在 file 文件里新增一行 '123' 文本的 commit ,你使用 git revert commitId
就相当于你又提交一个把 file 文件里那一行 '123' 文体删除的 commit。常用如下:
值得注意的事,你对某一个 commitObject 进行 revert 之后,你会发现,你不管怎么 merge 或查看 changes ,这个 commitObject 就是死活找不到,怎么合并也合并不过来,这就会导致你认为代码丢了。其实,你只需要对 revert 的提交,再进行一次 revert 就会把 commitObject 找回来了。(还原之前的‘还原’)
merge
git merge branch
将两个或多个开发历史结合在一起。这个没有什么常用的技巧,记住两点,一是合并时,是拿 branch 、HEAD、两者的共同祖先节点,三个 commit 进行合并。二是合并之后会生成一个新的合并提交,这个提交会有两个parent,我们会用 commit^1 和 commit^2 来区分。还有一个就是,如果是快合并,也就是合并过来的 branch 的提交记录完全包含了当前 HEAD 的提交,那就不是产生新的合并提交,仅仅是移到 HEAD 指向 branch。
reset
git reset
是唯一的两个可指定目标位置移动头指针的的指令之一,另一个 git checkout
。 reset 就是移动 HEAD 到指定的对象上,如果 HEAD 有挂载在 branch 上,那就会带着 branch 一起移动。这个指令有三种常用模式和两个不常用的。
只要理解了指针,这个指令就可以肆无忌惮的玩~。
笔者有一个使用小技巧,就是用 reset 来整理提交记录。比如,我写一个功能,提交了20次,我怕领导看到我一个小功能都要分成20次提交,我就找到第1次提交的 commitId 。然后 git reset --mixed commitId@{0}
,回退到第一次提交。这样就只有一个提交了。当然,这个也能用 git rebase
来实现的。
cherry-pick
git cherry-pick commit
应用一些现有提交到当前分支。除了 merge 和 rebase , cherry-pick 也能把其它地方的提交转移(或叫拷贝)到当前分支。使用方法有如下三种:
看得出来,分别是拷贝单个,多个,一个片段。这个呢,笔者也有一个小技巧,如果你想拷贝其它仓库的提交,你可以先把那个仓库 git remote add originA git://url
加到本地,然后可以通过 git log originA/branch
查看得到那个提交的 commitId ,然后 git cherry-pick commitId
就可以了。当然了,这个并不是 cherry-pick 能这么干,这个操作的原理就是,你把其它仓库 fetch 到本地后,你的本地 .git 里就会有对应的 objects ,这些都在你自己的电脑里了,那不就任你玩咯。
后语
终于把计划的 Git 系列完成了。这个系列正文到这里就结束了。后续发现了好玩的,实用的,就可能会有 git 加餐 系列。同时,如果有错误的地方,欢迎留言。感谢!
———— 活在当下
版权声明: 本文为 InfoQ 作者【卡尔】的原创文章。
原文链接:【http://xie.infoq.cn/article/6994fe3c3390d570900132e35】。文章转载请联系作者。
评论