Skip to content

主要

基础篇-1. git commit

命令描述
  • Git仓库中的提交记录保存的是你的目录下所有文件的快照。
  • Git提交记录尽轻量,因此在每次进行提交时,它并不会复制整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。
  • Git保存了提交的历史记录。这也是为什么大多数提交记录的上面都有 parent 节点的原因。

练习题答案:

bash
git commit -m 'feat: one'
git commit -m 'feat: two'

基础篇-2. git branch

命令详解
  • git分支也非常轻量,它们只是简单地指向某个提交纪录。
  • 这是因为即使创建再多的分支也不会造成储存或内存上的开销,并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。 在将分支和提交记录结合起来后,我们会看到两者如何协作。

注意

Git 2.23 版本中,引入了一个名为 git switch 的新命令,最终会取代 git checkout。 因为 checkout 作为单个命令有点超载(它承载了很多独立的功能)。 由于现在很多人还无法使用 switch,本次课程仍然使用 checkout 而不是 switch, 但是如果你想尝试一下新命令,我们的应用也是支持的!并且你可以从这里学到更多关于新命令的内容。

练习题答案:

bash
git checkout -b bugFix

基础篇-3. git merge

命令详解

在 Git 中合并两个分支时会产生一个特殊的提交记录,它有两个 parent 节点。翻译成自然语言相当于:“我要把这两个 parent 节点本身及它们所有的祖先都包含进来。”

步骤提示
  1. 创建新分支 bugFix
  2. 用 git checkout bugFix 命令切换到该分支
  3. 提交一次
  4. 用 git checkout main 切换回 main
  5. 再提交一次
  6. 用 git merge 把 bugFix 合并到 main

练习题答案:

bash
git checkout -b bugFix
git commit -m 'feat: bugFix'
git checkout main
git commit -m 'feat: main'
git merge bugFix

基础篇-4. git rebase

命令详解

除了 git merge,第二种合并分支的方法是 git rebase

  • Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
  • Rebase 的优势就是可以创造更线性的提交历史。
  • 如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。
步骤提示
  1. 新建并切换到 bugFix 分支
  2. 提交一次
  3. 切换回 main 分支再提交一次
  4. 再次切换到 bugFix 分支,rebase 到 main 上

练习题答案:

bash
git checkout -b bugFix
git commit -m 'feat: bugFix'
git checkout main
git commit -m 'feat: main'
git checkout bugFix
git rebase main

高级篇-1. 分离HEAD

HEAD

  • HEAD 是一个对当前所在分支的符号引用,也就是指向你正在其基础上进行工作的提交记录。

  • HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。

  • HEAD 通常情况下是指向分支名的(如 bugFix)。在你提交时,改变了 bugFix 的状态,这一变化通过 HEAD 变得可见。

  • 如果想看 HEAD 指向,可以通过 cat .git/HEAD 查看。

  • 如果 HEAD 指向的是一个引用,还可以用 git symbolic-ref HEAD 查看它的指向。

分离的HEAD

分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名。

在命令执行之前的状态如下所示:

  • HEAD -> main -> C1
  • HEAD 指向 main, main 指向 C1

练习题答案:

bash
git checkout C4

高级篇-2. 相对引用(^)

^操作符
  • 在实际应用时,如果要用哈希值的方式移动,就不得不用 git log 来查查看提交记录的哈希值。所以 Git 引入了相对引用。
  • 使用相对引用的话,你就可以从一个易于记忆的地方(比如 bugFix 分支或 HEAD)开始计算。

两个简单的用法:

  • 使用 ^ 向上移动 1 个提交记录
    • 操作符 ^ 表示让 Git 寻找指定提交记录的 parent 提交。
    • 例如: main^ 相当于 main 的 parent 节点。main^^ 是 main 的第二个 parent 节点。
  • 使用 ~<num> 向上移动多个提交记录,如 ~3

练习题答案:

bash
git checkout bugFix^

高级篇-3. 相对引用2(~)

~操作符
  • 操作符~也是相对引用,一般形式是 ~3,相当于 ^^^,比^写起来要简便一点。
  • 数字是可选,不跟数字时与 ^ 相同,向上移动一次。

强制修改分支位置

相对引用最多的就是移动分支。

可以直接使用 -f 选项让分支指向另一个提交。例如:

git branch -f main HEAD~3

上面的命令会将 main 分支强制指向 HEAD 的第 3 级 parent 提交。

练习题答案:

bash
# 要完成此关,移动 HEAD,main 和 bugFix 到目标所示的位置。
git checkout main
git branch -f main HEAD~2
git branch -f main C6
git checkout bugFix
git branch -f bugFix HEAD~3
git checkout C1

git branch -f bugFix C0
git branch -f main C6
git checkout HEAD^

高级篇-4. 撤销变更

撤销变更 git reset & git revert

和提交一样,撤销变更由底层部分(暂存区的独立文件或者片段)和上层部分(变更到底是通过哪种方式被撤销的)组成。我们这个应用主要关注的是后者。

主要有两种方法用来撤销变更:

  • git reset
    • git reset 通过把分支记录回退几个提交记录来实现撤销改动。
    • git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样:git reset HEAD~1
  • git revert
    • git reset 虽然很方便,但是这种方法对大家一起使用的远程分支是无效的,为了撤销更改并分享给别人,我们需要使用 git revertgit revert HEAD
    • revert 之后就可以把你的更改推送到远程仓库与别人分享啦

练习题答案:

bash
git reset HEAD^
git checkout pushed
git revert HEAD

移动提交记录-1. git cherry-pick

命令详解

命令形式:git cherry-pick <提交号>...

如果你想将一些提交复制到当前所在的位置(HEAD)下面的话, cherry-pick 是最直接的方式了。

练习题答案:

bash
# 将bugFix、C4和C7分支合并到当前HEAD分支下
git cherry-pick bugFix C4 C7

移动提交记录-2. 交互式 rebase

交互式rebase 命令详解

使用背景

  • 当你知道你所需要的提交记录(并且还知道这些提交记录的哈希值)时, 用 cherry-pick 再好不过了 —— 没有比这更简单的方式了。
  • 但是如果你不清楚你想要的提交记录的哈希值呢? 我们可以利用交互式的 rebase

交互式 rebase

指的是使用带参数 --interactive 的 rebase 命令, 简写为 -i

如果你在命令后增加了这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。

当 rebase UI界面打开时, 你能做3件事

  • 调整提交记录的顺序(通过鼠标拖放来完成)
  • 删除你不想要的提交(通过切换 pick 的状态来完成,关闭就意味着你不想要这个提交记录)
  • 合并提交。

练习题答案:

bash
git rebase -i HEAD~4

# C3 omit
# C5 omit
# C4 omit
# C2 pick

杂项-1. 只取一个提交记录

本地栈式提交

本地栈式提交案例

来看一个在开发中经常会遇到的情况:我正在解决某个特别棘手的 Bug,为了便于调试而在代码中添加了一些调试命令并向控制台打印了一些信息。这些调试和打印语句都在它们各自的提交记录里。最后我终于找到了造成这个 Bug 的根本原因,解决掉以后觉得沾沾自喜! 最后就差把 bugFix 分支里的工作合并回 main 分支了。你可以选择通过 fast-forward 快速合并到 main 分支上,但这样的话 main 分支就会包含我这些调试语句了。你肯定不想这样,应该还有更好的方式……

实际我们只要让 Git 复制解决问题的那一个提交记录就可以了:

  • git rebase -i
  • git cherry-pick

练习题答案:

bash
git rebase -I HEAD~3
git branch -f main bugFix

杂项-2. 提交的技巧 #1

内容详解

接下来这种情况也是很常见的:你之前在 newImage 分支上进行了一次提交,然后又基于它创建了 caption 分支,然后又提交了一次。 此时你想对某个以前的提交记录进行一些小小的调整。比如设计师想修改一下 newImage 中图片的分辨率,尽管那个提交记录并不是最新的了。

我们可以通过下面的方法来克服困难:

  1. 先用 git rebase -i 将提交重新排序,然后把我们想要修改的提交记录挪到最前
  2. 然后用 git commit --amend 来进行一些小修改
  3. 接着再用 git rebase -i 来将他们调回原来的顺序
  4. 最后我们把 main 移到修改的最前端就大功告成啦!

练习题答案:

bash
git rebase -i HEAD~2
# C3 omit
# C2 omit
git commit —amend
git rebase -i HEAD~2
# C2 omit
# C3 omit
git branch -f main caption

杂项-3. 提交的技巧 #2

内容详解

上面的提交唯一的问题就是要进行两次排序,而这有可能造成由 rebase 而导致的冲突。

下面还是看看 git cherry-pick 是怎么做的吧。

git cherry-pick C2

练习题答案:

bash
git checkout main
git cherry-pick  C2
git commit —amend
git cherry-pick C3

杂项-4. git tag

命令详解

分支很容易被人为移动,并且当有新的提交时,它也会移动。分支很容易被改变,大部分分支还只是临时的,并且还一直在变。git tag 可以永久地将某个特定的提交命名为里程碑,然后就可以像分支一样引用了。

git tag v1 C1

先建立一个标签,指向提交记录 C1,表示这是我们 1.0 版本。

练习题答案:

bash
git tag v0 C1
git checkout C2
git tag v1

杂项-5. git describe

命令详解

git describe

  • 由于标签在代码库中起着“锚点”的作用,Git 还为此专门设计了一个命令用来描述离你最近的锚点(也就是标签),它就是 git describe
  • git describe 能帮你在提交历史中移动了多次以后找到方向;当你用 git bisect(一个查找产生 Bug 的提交记录的指令)找到某个提交记录时,或者是当你坐在你那刚刚度假回来的同事的电脑前时, 可能会用到这个命令。
  • git describe 的 语法是:git describe <ref>
  • <ref> 可以是任何能被 Git 识别成提交记录的引用,如果你没有指定的话,Git 会使用你目前所在的位置(HEAD)。 它输出的结果是这样的:<tag>_<numCommits>_g<hash>
    • tag 表示的是离 ref 最近的标签
    • numCommits 是表示这个 ref 与 tag 相差有多少个提交记录
    • hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位

git describe案例

当 ref 提交记录上有某个标签时,则只输出标签名称

比如:git tag v2 C3

  • git describe main 会输出:v1_2_gC2
  • git describe side 会输出:v2_1_gC4

练习题答案:

bash
git describe main
# v0_2_gC2
git describe side
# v1_1_gC4
git describe
# v1_2_gC6

高级话题-1. 多次rebase

练习题答案:

bash
git rebase main bugFix
git rebase bugFix side
git rebase side another
git rebase another main

高级话题-2. 两个parent节点

内容详解

操作符 ^ 与 ~ 符一样,后面也可以跟一个数字。

  • ~ 后面数字是用来指定向上返回几代
  • ^ 后面数字是指定合并提交记录的某个 parent 提交

还记得前面提到过的一个合并提交有两个 parent 提交吧,所以遇到这样的节点时该选择哪条路径就不是很清晰了。Git 默认选择合并提交的“第一个” parent 提交,在操作符 ^ 后跟一个数字可以改变这一默认行为。

合并案例

这里有一个合并提交记录。如果不加数字修改符直接切换到 main^,会回到第一个 parent 提交记录。

  • git checkout main^
  • git checkout main^2

使用 ^ 和 ~ 可以自由地在提交树中移动,非常给力:

  • git checkout HEAD~
  • git checkout HEAD^2
  • git checkout HEAD~2

合并之后 git checkout HEAD~^2~2

练习题答案:

bash
git branch bugWork main~^2^

高级话题-3. 纠缠不清的分支

内容详解

现在我们的 main 分支是比 one、two 和 three 要多几个提交。出于某种原因,我们需要把 main 分支上最近的几次提交做不同的调整后,分别添加到各个的分支上。

one 需要重新排序并删除 C5,two 仅需要重排排序,而 three 只需要提交一次。

练习题答案:

bash
git branch -f three C2
git checkout one
git cherry-pick C4 C3 C2
git checkout two
git cherry-pick C5 C4' C3' C2'

MIT Licensed