【摘】Git- 从零单排 04 期

用户头像
卡尔
关注
发布于: 2020 年 06 月 18 日
【摘】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 一下,接着写那一半的功能。指令如下:

# 贮藏
git stash
# 查看贮藏列表
git stash list
# 应用贮藏列表的第一个 stash@{0} 并且删除
git stash pop
# 应用贮藏列表的第 n 个。如果不指定应用哪一个,刚系统默认为 stash@{0}
git stash apply stash@{n}
# 移除贮藏
git stash drop stash@{n}
# 清空贮藏列表
git stash clear

基本的操作也就是上面那些。接下来,笔者就讲一下自己对 stash 的理解。主要就讲一下关键的 怎么存 和 怎么应用



  1. 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 下。然后,还会把存好之后,你的工作区和暂存区就都是干净的。这样,贮藏就完成了。

  2. 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 是操作,7d189ab 是commitId,dev1 是提交说明。注意,这个与log是反序的。越近的提交越在下面。
pick 7d189ab dev1
pick 8a3055c dev2
pick 4cdb50c dev3

# pick:保留该commit(缩写:p)
# reword:保留该commit,修改该commit的注释(缩写:r)
# edit:保留该commit, 修改该提交(不仅仅修改注释)(缩写:e)
# squash:将该commit和前一个commit合并。向上合并(缩写:s)
# fixup:将该commit和前一个commit合并,不保留该提交的注释信息(缩写:f)
# exec:执行shell命令(缩写:x)
# drop:我要丢弃该commit(缩写:d)


类似这样,我们只要修改操作的选项就能达到把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。常用如下:

# 还原单个普通提交
git revert commitId
# 还原merge提交, 当你要还原的是一个合并时,需要带上 -m {n} 参数。n的值一般只有 1 或 2
# 如果n = 1,那意思是留下合并里第1父提交对象的链路上的内容,那这样相当于还原了之前的 merge。
# 如果n = 2,那意思就是把 merge 过来的内容留下了,把本来分支上的内容丢弃了。
git revert -m 1 commitId
# 还原多个提交,-n 的意思是不自动提交
git revert -n commitId..commitId

值得注意的事,你对某一个 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 一起移动。这个指令有三种常用模式和两个不常用的。

# 最‘强硬’的一种模式,就是不在乎你当前的状态,
# 把暂存区和工作区的修改都扔掉,把 commitId 里的内容签出来。
git reset --hard commitId
# 最‘温柔’的一种模式,保留你的工作区内容,
# 保留你的暂存区内容,把 commitId 与 当前的差异放入到暂存区。
git reset --soft commitId
# 这个是默认模式,混合。
# 它会把当前工作区、暂存区、commitId 与 当前的差异这三方的修改混合到工作区。
git reset --mixed commitId
# 不常用
# 和--hard类似,只不过如果在执行reset命令之前你有改动一些文件并且未提交,
# merge会保留你的这些修改,hard则不会。
git reset --merge commitId
# 和--hard类似,执行reset之前改动文件如果是a分支修改了的,会提示你修改了相同的文件,不能合并。
# 如果不是a分支修改的文件,会移除缓存区。git status还是可以看到保持了这些修改。
git reset --keep commitId

只要理解了指针,这个指令就可以肆无忌惮的玩~。

笔者有一个使用小技巧,就是用 reset 来整理提交记录。比如,我写一个功能,提交了20次,我怕领导看到我一个小功能都要分成20次提交,我就找到第1次提交的 commitId 。然后 git reset --mixed commitId@{0} ,回退到第一次提交。这样就只有一个提交了。当然,这个也能用 git rebase 来实现的。

cherry-pick

git cherry-pick commit 应用一些现有提交到当前分支。除了 merge 和 rebase , cherry-pick 也能把其它地方的提交转移(或叫拷贝)到当前分支。使用方法有如下三种:

git cherry-pick commitA
git cherry-pick commitA commitB
git cherry-pick commitA..commitB

看得出来,分别是拷贝单个,多个,一个片段。这个呢,笔者也有一个小技巧,如果你想拷贝其它仓库的提交,你可以先把那个仓库 git remote add originA git://url 加到本地,然后可以通过 git log originA/branch 查看得到那个提交的 commitId ,然后 git cherry-pick commitId 就可以了。当然了,这个并不是 cherry-pick 能这么干,这个操作的原理就是,你把其它仓库 fetch 到本地后,你的本地 .git 里就会有对应的 objects ,这些都在你自己的电脑里了,那不就任你玩咯。

后语

终于把计划的 Git 系列完成了。这个系列正文到这里就结束了。后续发现了好玩的,实用的,就可能会有 git 加餐 系列。同时,如果有错误的地方,欢迎留言。感谢!

———— 活在当下



发布于: 2020 年 06 月 18 日 阅读数: 32
用户头像

卡尔

关注

还未添加个人签名 2019.03.01 加入

还未添加个人简介

评论

发布
暂无评论
【摘】Git-从零单排 04期