一、git命令
Git Commit
Git 仓库中的提交记录保存的是你的目录下所有文件的快照,就像是把整个目录复制,然后再粘贴一样,它并不会盲目地复制整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录!
- git Commit xxx: 提交代码
- git checkout -b:新建一个
Git Checkout
- git checkout xxx:切换到 xxx分支
- git checkout -b xxx:新建一个xxx分支并切换到 xxx分支
Git Branch
- git branch xxx: 新建一个分支
- git branch :查看本地分支
- git branch -a:查看本地和远程仓库的所有分支
- git branch -r:查看远程仓库的分支
Git Merge
在 Git 中合并两个分支时会产生一个特殊的提交记录,它有两个父节点。翻译成自然语言相当于:“我要把这两个父节点本身及它们所有的祖先都包含进来。”
- git checkout xxx; git merge master, 把master的代码合并到xxx上
Git Rebase
也是合并两个分支代码的另一种方法, 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。Rebase的优势就是可以创造更线性的提交历史,这听上去有些难以理解。如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。
git checkout bugFix
git rebase main :
意思是把xxx分支的提交记录复制出来放到main分支的最顶端
但是main 还没有更新 使用以下命令让它更新
git checkout main
git rebase bugFix
Git Log
查看历史提交记录的哈希值
二、HEAD
HEAD 是一个对当前检出记录的符号引用,总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的
想看 HEAD 指向,可以通过 cat .git/HEAD
查看, 如果 HEAD 指向的是一个引用,还可以用 git symbolic-ref HEAD
- git checkout C1:在分支上的C1提交上分离HEAD
三、相对引用
~<num>
向上移动多个提交记录,如 ~3,
使用 ^
向上移动 1 个提交记录
- git checkout main
^
:main^
相当于“main
的父节点 - git checkout main^^:main^^ 相当于“
main
的第二个父节点 - git checkout HEAD^:也可以将
HEAD
作为相对引用的参照
“~”操作符
如果你想在提交树中向上移动很多步的话,敲那么多 ^
貌似也挺烦人的,Git 当然也考虑到了这一点,于是又引入了操作符 ~
。
该操作符后面可以跟一个数字(可选,不跟数字时与 ^
相同,向上移动一次),指定向上移动多少次
- git checkout HEAD~4:
~<num>
一次后退四步
强制修改分支位置
使用相对引用最多的就是移动分支。可以直接使用 -f
选项让分支指向另一个提交
- git branch -f main HEAD~3:将 main 分支强制指向 HEAD 的第 3 级父提交。
五、撤销变更(删除提交记录)
主要有两种方法用来撤销变更 —— 一是 git reset
,还有就是 git revert
- git reset HEAD~1: Git 把 main 分支移回到上一个提交
虽然在你的本地分支中使用 git reset
很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦!
为了撤销更改并分享给别人,我们需要使用 git revert
。
- git revert HEAD:
在我们要撤销的提交记录后面居然多了一个新提交!这是因为新提交记录 C2' 引入了更改 —— 这些更改刚好是用来撤销 C2 这个提交的。也就是说 C2' 的状态与 C1 是相同的。
revert 之后就可以把你的更改推送到远程仓库与别人分享啦
六、整理提交记录
本系列的第一个命令是 git cherry-pick
, 命令形式为:
git cherry-pick <提交号>...
如果你想将一些提交复制到当前所在的位置(HEAD
)下面的话, Cherry-pick 是最直接的方式了
- git cherry-pick C2 C4
我们只需要提交记录 C2 和 C4,所以 Git 就将被它们抓过来放到当前分支下了
交互式 rebase(对提交重新排序)
交互式 rebase 指的是使用带参数 --interactive
的 rebase 命令, 简写为 -i
如果你在命令后增加了这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。所谓的 UI 窗口一般会在文本编辑器
当 rebase UI界面打开时, 你能做3件事:
- 调整提交记录的顺序(通过鼠标拖放来完成)
- 删除你不想要的提交(通过切换
pick
的状态来完成,关闭就意味着你不想要这个提交记录) - 合并提交。 遗憾的是由于某种逻辑的原因,我们的课程不支持此功能,因此我不会详细介绍这个操作。简而言之,它允许你把多个提交记录合并成一个
git rebase -i HEAD~4
本地栈式提交
来看一个在开发中经常会遇到的情况:我正在解决某个特别棘手的 Bug,为了便于调试而在代码中添加了一些调试命令并向控制台打印了一些信息。
这些调试和打印语句都在它们各自的提交记录里。最后我终于找到了造成这个 Bug 的根本原因,解决掉以后觉得沾沾自喜!
最后就差把 bugFix
分支里的工作合并回 main
分支了。你可以选择通过 fast-forward 快速合并到 main
分支上,但这样的话 main
分支就会包含我这些调试语句了。你肯定不想这样,应该还有更好的方式……
实际我们只要让 Git 复制解决问题的那一个提交记录就可以了。跟之前我们在“整理提交记录”中学到的一样,我们可以使用
git rebase -i
git cherry-pick
来达到目的。
提交的技巧 #1
接下来这种情况也是很常见的:你之前在 newImage
分支上进行了一次提交,然后又基于它创建了 caption
分支,然后又提交了一次。
此时你想对某个以前的提交记录进行一些小小的调整。比如设计师想修改一下 newImage
中图片的分辨率,尽管那个提交记录并不是最新的了。
我们可以通过下面的方法来克服困难:
- 先用
git rebase -i
将提交重新排序,然后把我们想要修改的提交记录挪到最前 - 然后用
git commit --amend
来进行一些小修改 - 接着再用
git rebase -i
来将他们调回原来的顺序 - 最后我们把 main 移到修改的最前端(用你自己喜欢的方法),就大功告成啦!
方法二就是用 git cherry-pick
Git Tags
相信通过前面课程的学习你已经发现了:分支很容易被人为移动,并且当有新的提交时,它也会移动。分支很容易被改变,大部分分支还只是临时的,并且还一直在变。
你可能会问了:有没有什么可以永远指向某个提交记录的标识呢,比如软件发布新的大版本,或者是修正一些重要的 Bug 或是增加了某些新特性,有没有比分支更好的可以永远指向这些提交的方法呢?
Git 的 tag 就是干这个用的啊,它们可以(在某种程度上 —— 因为标签可以被删除后重新在另外一个位置创建同名的标签)永久地将某个特定的提交命名为里程碑,然后就可以像分支一样引用了。更难得的是,它们并不会随着新的提交而移动。你也不能检出到某个标签上面进行修改提交,它就像是提交树上的一个锚点,标识了某个特定的位置。
咱们先建立一个标签,指向提交记录 C1
,表示这是我们 1.0 版本。
git tag v0 C1:我们将这个标签命名为 v0,并且明确地让它指向提交记录 C1
Git Describe
Git Describe是Git 专门设计了一个命令用来描述离你最近的锚点,能帮你在提交历史中移动了多次以后找到方向,当你用 git bisect
(一个查找产生 Bug 的提交记录的指令)找到某个提交记录时,或者是当你坐在你那刚刚度假回来的同事的电脑前时, 可能会用到这个命令
git describe
的语法是:
git describe xxx
<ref>
可以是任何能被 Git 识别成提交记录的引用,如果你没有指定的话,Git 会以你目前所检出的位置(HEAD
)。
它输出的结果是这样的:
<tag>_<numCommits>_g<hash>
tag
表示的是离 xxx分支 最近的标签, numCommits
是表示这个 xxx分支 与 tag
相差有多少个提交记录, hash
表示的是你所给定的 ref
所表示的提交记录哈希值的前几位。
当 ref
提交记录上有某个标签时,则只输出标签名称
示范
git describe main
会输出:
v1_2_gC2
git describe side
会输出:
v2_1_gC4
选择父提交记录
操作符 ^
与 ~
符一样,后面也可以跟一个数字。 但是该操作符后面的数字与 ~
后面的不同,并不是用来指定向上返回几代,而是指定合并提交记录的某个父提交,Git 默认选择合并提交的“第一个”父提交,在操作符 ^
后跟一个数字可以改变这一默认行为。使用 ^ 和 ~ 可以自由地在提交树中移动
git checkout main^ git checkout main^2
操作符还支持链式操作
git checkout HEAD~
git checkout HEAD^2
git checkout HEAD~2
等于
git checkout HEAD~^2~2