/

【Git 权威指南】读书笔记 - 独奏 - Part 4

主要内容:【历史穿梭】、【改变历史】、【Git 克隆】

历史穿梭

查看条件个数:

$ git rev-list HEAD | wc -l

版本表示法:git rev-parse

git rev-parse pick out and massage parameters for other git commands.

  • --git-dir 可以显示 Git 版本库的位置
  • --show-cdup 当前工作区目录的深度
  • --parseopt 可以用于被 Git 无关应用用于解析命令行参数
# 显示分支,tag
$ git rev-parse --symbolic --branches

$ git rev-parse --symbolic --branches

# 显示定义的所有引用
$ git rev-parse --symbolic --glob=refs/*
# 显示多个表达式的 SHA1 哈希值:

$ git rev-parse master refs/heads/master
6652a0dce6a5067732c00ef0a220810a7230655e
6652a0dce6a5067732c00ef0a220810a7230655e

^后面的数字代表该提交的第几个父提交,~<n>就相当于连续<n>个符号^

$ git rev-parse A~3 A^^^
e80aa7481beda65ae00e35afc4bc4b171f9b0ebf
e80aa7481beda65ae00e35afc4bc4b171f9b0ebf

# 暂存区里的文件和HEAD中的文件相同

$ git rev-parse :gitg.png HEAD:gitg.png
fc58966ccc1e5af24c2c9746196550241bc01c50
fc58966ccc1e5af24c2c9746196550241bc01c50

# 在提交日志中查找字串的方式显示提交
$ git rev-parse :/"Commit A"
81993234fc12a325d303eccea20f6fd629412712

版本范围表示法:git rev-list

git rev-list 可以帮助研究 Git 的各种版本范围语法。

# 从开始到 tag:A 的所有历史提交
$ git rev-list --oneline A

# 每个 tag 历史提交的并集
$ git rev-list --oneline D F

# ^ 排除这个版本及其历史版本
$ git rev-list --oneline ^G D

# ^G D 等价于 G..D
$ git rev-list --oneline G..D

# 含 ^ 的参数顺序不重要,..
# ^B C 相当于 B..C
# C ^B 相当于 B..C
# C..B 相当于 ^C B

浏览日志:git

--graph 参数调用 git log 可以显示字符界面的提交关系图。

$ git config --global alias.glog "log --graph"
  • --oneline 单行显示
  • -<n> 显示最近的 条日志
  • -p 显示变动
  • --stat 显示变动摘要

查看、分析某一个提交:

$ git show D --stat
tag D
...

$ git cat-file -p D^0

差异比较:git diff

  • 比较里程碑 B 和里程碑 A,用命令:git diff B A
  • 比较工作区和里程碑 A,用命令:git diff A
  • 比较暂存区和里程碑 A,用命令:git diff -cached A
  • 比较工作区和暂存区,用命令:git diff
  • 比较暂存区和 HEAD,用命令:git diff -cached
  • 比较工作区和 HEAD,用命令:git diff HEAD

显示不同版本下的文件差异:

$ git diff <commit1> <commit2> -- <paths>

非 Git 目录/文件的差异比较,可在版本库之外使用:

$ git diff <path1> <path2>
  • --word-diff 差异逐 _词_ 比较,而非缺省的逐 _行_ 比较

文件追溯:git blame

逐行显示文件,在每一行的行首显示此行最早是在什么版本引入的,由谁引入。

只想查看某几行,使用 -L n,m 参数:

$ git blame -L 6,+5 README

二分查找:git bisect

定位问题代码。

改变历史

作为分布式版本控制系统,一旦版本库被多人共享,改变历史就可能是无法完成的任务。

悔棋

git commit –amend 单步悔棋,修补式提交。

检出文件到前一版:

$ git checkout HEAD^ -- src/hello.h

多步悔棋

$ git reset --soft HEAD^^

回到未来

拣选指令 git cherry-pick 从众多的提交中挑选出一个提交应用在当前的工作分支中。

该命令需要提供一个提交 ID 作为参数,操作过程相当于将该提交导出为补丁文件,然后在当前 HEAD 上重放形成无论内容还是提交说明都一致的提交。

操作例子:A B C D E F 6 次提交。

例子 1.1:出掉 D

# 将 HEAD 指针切换到 C
$ git checkout C

# 拣选将 E 提交在当前 HEAD 上
$ git cherry-pick E

# 拣选操作将 F 提交在当前 HEAD 上
$ git cherry-pick master

# 将 master 分支指向新的提交 ID(f677821)上
$ git checkout master
$ git reset --hard HEAD@{1}

例子 1.2:D 融入 C:

$ git checkout D

# 将 C 和 D 融合
$ git reset --soft HEAD^^

# 提交说明重用C提交的提交说明
$ git commit -C C

$ git cherry-pick E
$ git cherry-pick F

git rebase 对提交执行变基操作,即可以实现将指定范围的提交“嫁接”到另外一个提交之上。

git rebase --onto <newbase> <since> <till>

变基操作的过程:

  1. 首先执行 git checkout ,如果 till 不是一个分支,则变基操作是在 detached HEAD 分离头指针 状态的,当变基结束后,对 master 分支执行重置以实现把变基结果记录在分支中。
  2. ..所标识的提交范围写到一个临时文件中。(..是指包括的所有历史提交排除以及的历史提交后形成的版本范围)
  3. 当前分支强制重置(git reset –hard)到
  4. 从保存在临时文件中的提交列表中,一个一个将提交按照顺序重新提交到重置之后的分支上。
  5. 如果遇到提交已经在分支中包含,跳过该提交。
  6. 如果在提交过程遇到冲突,变基过程暂停。用户解决冲突后,执行 git rebase –continue 继续变基操作。或者执行 git rebase –skip 跳过此提交。或者执行 git rebase –abort 就此终止变基操作切换到变基前的分支上。

例子 2.1:出掉 D

$ git rebase --onto C D F
# or
$ git rebase --onto C E^ F

例子 2.2:D 融入 C:

$ git checkout D

# C和D融合
$ git reset --soft HEAD^^

$ git add .

# 复用 C 的提交信息
$ git commit -C C

# 记住这个提交 ID,可以用 tag 的方法
$ git tag newbase

$ git rebase --onto newbase E^ master

-i 交互式变基方法。

例子 3.1:出掉 D

$ git rebase -i D^

# d, drop = remove commit
# 提交 D 标示修改为 d

例子 3.2:D 融入 C:

$ git rebase -i C^

# 提交 D 标示修改为 s

丢弃历史

重点内容第一次 Get

历史有的时候会成为负担。只保留最近的 100 次提交,抛弃之前的历史提交。那么应该如何操作呢?

例:清除 tag A 之前的提交历史:

# 查看里程碑A指向的目录树
$ git cat-file -p A^{tree}

# 使用git commit-tree命令直接从该目录树创建提交
$ echo "Commit from tree of tag A." | git commit-tree A^{tree}
8f7f94ba6a9d94ecc1c223aa4b311670599e1f86

# 命令git commit-tree的输出是一个提交的SHA1哈希值。
# 会发现这个提交没有历史提交,可以称之为孤儿提交。
$ git log 8f7f94ba6a9d94ecc1c223aa4b311670599e1f86

# 将master分支从里程碑到最新的提交全部迁移到刚刚生成的孤儿提交上。
$ git rebase --onto 8f7f94ba6a9d94ecc1c223aa4b311670599e1f86 A master

反转提交

$ git revert HEAD

Git 克隆

鸡蛋不装在一个篮子里

1. git clone <repository> <directory>
2. git clone --bare <repository> <directory.git>
3. git clone --mirror <repository> <directory.git>

一般约定俗成裸版本库的目录名以 .git 为后缀。

用法 3 区别于用法 2 之处在于用法 3 克隆出来的裸版本对上游版本库进行了注册,这样可以在裸版本库中使用 git fetch 命令和上游版本库进行持续同步。

克隆生成裸版本库

$ git clone --bare /path/to/my/workspace/demo /path/to/repos/demo.git

demo.git 目录就是版本库目录,不含工作区。

$ git --git-dir=/path/to/repos/demo.git config core.bare
true

# 向其 push
$ git push /path/to/repos/demo.git

创建生成裸版本库

$ git init --bare /path/to/repos/demo-init.git

Reference: