Yifans_Z's Blog.

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

字数统计: 1.8k阅读时长: 25 min
2017/12/25 Share

主要内容:【历史穿梭】、【改变历史】、【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:

CATALOG
  1. 1. 历史穿梭
    1. 1.1. 版本表示法:git rev-parse
    2. 1.2. 版本范围表示法:git rev-list
    3. 1.3. 浏览日志:git
    4. 1.4. 差异比较:git diff
    5. 1.5. 文件追溯:git blame
    6. 1.6. 二分查找:git bisect
  2. 2. 改变历史
    1. 2.1. 悔棋
    2. 2.2. 多步悔棋
    3. 2.3. 回到未来
    4. 2.4. 丢弃历史
    5. 2.5. 反转提交
  3. 3. Git 克隆
    1. 3.1. 鸡蛋不装在一个篮子里
    2. 3.2. 克隆生成裸版本库
    3. 3.3. 创建生成裸版本库