Git 概念与常用操作速攻

265 阅读5分钟

基础操作

# 提交 commit
git commit

# 提交带标签的 commit
git commit -m "注释内容"
# 创建分支
git branch <分支名称>

# 切换分支
git checkout <分支名称>

# 切换分支并自动创建
git checkout -b <分支名称>

指针与分支

  • Working Directory:使用 git 正在管理的项目工作目录
  • Index:暂存区,预期的下次提交快照的内容
  • HEAD:上次提交的快照,同时也是下次提交快照的父结点

GIT 工作流程

  • HEAD 是对当前检出的引用,也就是指向当前正在工作状态的提交记录
  • HEAD 通常是指向分支名的(如 bugFix),实际对应的是当前分支上最近一次提交记录
  • 提交会改变 bugFix 的状态,这一变化将体现在 HEAD
  • 大多数修改提交树的 Git 命令都会改变 HEAD 的指向
  • 执行 git checkout C1 时指向会从 HEAD -> main -> C1 变为 HEAD -> C1
# 合并分支到当前版本
git merge <分支名称>

# 合并分支并强制生成 merge commit
git merge <分支名称> --no-ff
# 变基当前版本到目标
git rebase <分支名称>

变基 Rebase 是另一种合并分支的方法,Rebase 的优势就是可以创造更线性的提交历史。实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去;或者说在一个分支的最新版本上重现另一个分支的修改过程,版本树就像两个链条串在一起变成一个链条。

# 查看提交记录
git log

# 通过哈希检出记录
git checkout fed2da64c0efc5293610bdd892f82a58e8cbc5d8

# 通过哈希简写检出记录
git checkout fed2

实际上 checkout 也是在做移动指针的操作,同时由于提交记录是一个有向无环图的链式结构,我们还可以使用相对操作:

# 通过 ^ 操作符检出 HEAD 往前一个记录
git checkout HEAD^

# 通过 ~ 操作符检出 HEAD 往前三个记录
git checkout HEAD~3

# 强制将 main 分支指向 HEAD 往前三个记录
git branch -f main HEAD~3

回退和还原

如果刚刚完成提交 fa64 三个区域的结构分别为:

  • WDfa64 的文件
  • Indexfa64 的文件
  • HEAD:指向 fa64 快照
    • 第三次提交 fa64*
    • 第二次提交 a12c
    • 第一次提交 yhb8

这时使用 git reset --soft HEAD^ 将会导致 HEAD 指向 a12c 快照,相当于进行了一次时间回溯操作回到提交前的状态:

  • WDfa64 的文件
  • Indexfa64 的文件
  • HEAD:指向 a12c 快照
    • 第三次提交 fa64
    • 第二次提交 a12c*
    • 第一次提交 yhb8

如果使用的是 git reset --mixed HEAD^ 命令,那么在上面动作的基础上还会将暂存区的文件置为当前快照也就是 a12c 的文件内容,于是结构变为:

  • WDfa64 的文件
  • Indexa12c 的文件
  • HEAD:指向 a12c 快照
    • 第三次提交 fa64
    • 第二次提交 a12c*
    • 第一次提交 yhb8

而使用的若是 git reset --hard HEAD^ 命令,那么 git 将会在此基础上把工作区的文件也变成 a12c 快照的文件内容。不过这些操作都不会删除已经生成的 fa64 快照,如果这时进行 git checkout fa64 将导致 HEAD 指向 fa64,同时三个区域的文件又变为一致,就能神奇地又恢复到 reset 前刚刚提交 fa64 的状态。但由于在使用 --hard 选项时会重置工作目录,此时尚未提交的文件修改将会被丢弃,所以应当谨慎使用。

每次快照 git 都会储存每个变化的文件,而不是直觉上更容易想到的“精确到行的文件差异”,这是出于性能上的考虑设计的。但是在进行网络传输或本地仓库过大时,Git 会进行垃圾清除和打包压缩。

但是由于 Git 的分布式储存特性,如果使用 reset 会导致版本管理的同步出现问题,这时我们可以转而使用 git revert 指令,它的作用是创建能抵消从当前记录到目标记录为止所有操作的提交。

在合并后的分支上执行 reset 会导致 main 和 dev 分离,但这时的 uncommitted changes 加上 main 的内容刚好和 dev 一致,注意不要纠结于分支颜色和位置的不同。

进阶操作

# 将快照 ca78, g89e 依次复制到当前位置
git cherry-pick ca78 g89e
# 交互式变基,允许重新排序和剔除记录
git rebase -i <快照名称>

直接使用 Node.js 就可以解析 Git 快照文件的内容,以下是快照 7040a9951bdf7bcd10d0bfc6d5d240356095f46c 对应的记录:

> file = fs.readFileSync('.git/objects/70/40a9951bdf7bcd10d0bfc6d5d240356095f46c')
<Buffer 78 01 95 8f 4d 4a 04 31 14 84 5d e7 14 d9 0b f2 5e fe 23 32 8c 47 c9 cf 8b 8a f4 b4 d3 66 6e 30 38 b3 11 04 c5 a5 57 d0 95 20 7d 9e ee c6 5b 18 17 1e ... 177 more bytes>
> zlib.inflateSync(file).toString()
  'commit 293\x00tree ebbe0160233b75f02a4f2ebb1967b3ad1967e98d\n' +
  'parent afd6be5721408c007dfb50a69b02d32e59b18d31\n' +
  'parent 3f1acf67a17d43e583b4edcd14052ee458df805b\n' +
  'author Asuka109 <1097198892@qq.com> 1613405004 +0800\n' +
  'committer Asuka109 <1097198892@qq.com> 1613405004 +0800\n' +
  '\n' +
  'merge:合并分支并重新组织结构\n'

更多玩法请自行发掘或者浏览以下链接,本文也是参考这些内容总结理解得出的,如果希望深入探明 Git 可以参阅 Git pro 这本书。