Git进阶教程

2013.08.25/2015.06.05发布于笔记暂无评论/目录

Git的常用命令和场景可参考Git快速使用指南,在这里介绍进一步的使用和部分生僻的命令。

Git概念和技巧

Author Date vs Committer Date

  • Author Date: 原作者第一次创建commit的时间,唯一。
  • Committer Date: commit发生修改的时间,通常记录的是执行如下命令的时间:
    • git-rebase
    • git-cherry-pick
    • git-commit --amend
    • git-am

Index vs Working tree

使用git reset命令的时候不容易搞懂Index和Working tree这两个概念,下面简要总结一下。

(图片取自stackoverflow上的一个回答,来源未知)

Git index

有时候也被称作Staging area和Cache,git add等命令会将文件改动暂存在index中(.git/index),运行git status后,列在Changes to be committed后面的改动都位于index上。

Git index中的改动可以用git commit提交到本地仓库中。

Git working tree

也被称作Working directory,git add之前的文件改动都是在working tree中进行的,运行git status后,列在Changes not staged for commit和Untracked files后面的改动都位于working tree上。

提交的相对ref

假设有以下提交历史:

master:    A - B - C - D - M
                \         /
branch-1         E - F - G

其中,branch-1在M处merge到master,则:

  • D^ = D~1 = C
  • D^^ = D~1~1 = D~2 = B
  • M^ = M^1 = M~1 = D
  • M^2 = G
  • M^2^1 = G^1 = F
  • M^2^^ = M^2~2 = E

需要注意的是merge处的commit,M^默认是第一个父commit(git show --full M),M^2是第二个父commit。

提交范围

详细内容可参考Commit Ranges

通常用于git log和git diff,主要有两种格式,以下面的提交历史为例:

A - B - C - D - E - F
  • double dot C..E: E能追溯到且C不能追溯到的提交

      D - E
    

    git log C..E等同于git log ^C Egit log E --not C

  • triple dot C...E: E或C能追溯到且不能同时被E和C追溯到的提交,即C xor E

      D - E
    

以下面的提交历史为例:

A - B - C - D - E - F
         \
          G - H - I
  • F..I: G H I
  • F...I: D E F G H I

另外,需要说明的是:

  1. git log的选项--not^符号可以构造比..更复杂的筛选条件。--not^的区别在于--not对之后(下一个--not之前)的所有提交(包括^前缀的提交)都有效,而^仅对其之后的一个提交有效,例如

    • --not F H --not I: I
    • --not F H ^I: I
    • ^F H ^I: 无
    • ^F F: 无
    • D H --not C: D G H
  2. git log中使用...时,使用--left-right选项可以直观的输出结果

     $ git log --left-right F...I
     > I
     > H
     > G
     < F
     < E
     < D
    

Git进阶使用场景

并不常见的使用场景。

git reset

git reset --soft

既没修改index也没修改working tree,只是将当前HEAD指针放到ORIG_HEAD里,然后将HEAD指向目标Commit(默认为HEAD)。

如果要撤销git reset --soft,只需要使用git reflog找到最近的一次提交,然后对其再做一次reset即可,比如git reset --soft HEAD@{0}

git reset --mixed

重置index,不修改working tree。通常用于将git add的改动移出index,不会修改文件内容。

git reset --hard

重置index和working tree,完全恢复到目标comit(默认是HEAD)的状态。所有未commit或stash的改动都会丢失,使用此命令需多加小心。

detached HEAD state

有时需要返回之前的提交状态,此时会处于“detached head“状态。

mkdir git-test && cd $_ && git init  # 初始化一个新的git仓库
touch a.txt && git add $_ && git commit -m "first commit"   # 添加文件a.txt并提交
touch b.txt && git add $_ && git commit -m "second commit"  # 添加文件b.txt并提交
git log --oneline  # 查看提交日志
# 输出如下(##开头)
## 8083b9e second commit
## 15b7a79 first commit
git branch         # 查看分支情况
# 输出如下(##开头)
## * master
# 现在签出first commit
git checkout 15b7a79
# 输出如下(##开头)
## Note: checking out '15b7a79'.
## You are in 'detached HEAD' state.
## ...

此时即处于detached head状态,当前的文件回到first commit后的样子。

git branch
# 输出如下所示(##开头)
## * (no branch)
## master

最后,可以用如下命令回到second commit状态。 git checkout master

Git生僻命令

git fsck

检查git对象是否有效和能否被链接到。

使用git fsck的一个例子是,当不小心drop或clear了stash列表时,可以这样找回(代码取自stackoverflow上的一个回答):

for ref in `git fsck --unreachable | grep commit | cut -d' ' -f3`; do git show --summary $ref; done | less

git fsck的速度会比较慢,上面的命令会列出比较多的commit,因为git stash的默认提交信息都是WIP on ...,所以根据提交信息即可找到丢失的stash提交。

git reflog

记录了HEAD指针的完整改动历史,通常用于于查找“丢失”的commit和跨分支查看提交历史。当因为某些操作导致某些commit“丢失”时,可以使用git reflog配合git reset --hard来恢复到之前的提交状态。

git rev-list

按时间逆序列出提交对象,常用于查找涉及到某些文件的提交的hash。例如,查找所有关系到文件readme的提交:

git rev-list HEAD -- readme

更多功能参考man git-rev-list

git rebase

rebase操作可以将两个分支上的差异合并到另一个分支上,和merge操作不同的是,rebase最后形成线性的历史。

git rebase --onto O U B

上面的命令将U和B之间的差异U..B在分支O上重新做一遍。通常我们用的是这个命令的简化版本,例如

A - B - C - D - E   (master)
        \
         F - G - H  (hotfix)

在分支hotfix上运行命令git rebase master,实际上就是运行git rebase --onto master master hotfix,最后生成如下的线性提交历史

A - B - C - D - E - F' - G' - H' (hotfix)
                \
                 (master)

实际应用中,当需要提交pull request时,最好在自己的分支上rebase下主分支并解决发生的conflicts,这样仓库管理员就可以直接用git merge hotfix来接受你的pr,而不会遇到任何冲突。

注意事项

使用git rebase需要注意的一点就是不要rebase已经提交到远程仓库的代码

撤销rebase

需要撤销rebase引入的变更时,比较简便的方法是使用git reflog。首先通过git refloggit log HEAD@{XX}找到恢复点,然后使用git reset --hard。如果rebase之后还没有做过其他会变更提交历史的git操作,直接执行git reset --hard ORIG_HEAD即可。

举例说明,假设master分支的提交记录如下(#后为注释内容):

* 2014-02-19 0101db8 update a3 # A2
* 2014-02-19 6a741e8 update a2 # A1
* 2014-02-19 d2e4ed4 update a  # A
* 2014-02-19 f4d5abd init

test分支的提交记录如下:

* 2014-02-19 82137d8 update b3 # B2
* 2014-02-19 74bfa49 update b2 # B1
* 2014-02-19 d2e4ed4 update a  # A
* 2014-02-19 f4d5abd init

直观的分支图如下:

A--A1--A2 (分支master)
 \
  \B1--B2 (分支test)

现在执行命令git rebase test^1 master[1],则分支图变为:

A--B1--A1'--A2' (分支master)
 \
  \B1--B2       (分支test)

如果想撤销rebase操作,执行get reflog输出如下(节选):

691342e HEAD@{3}: rebase finished: returning to refs/heads/master
691342e HEAD@{4}: rebase: update a3
1ce3d6a HEAD@{5}: rebase: update a2
74bfa49 HEAD@{6}: checkout: moving from test to 74bfa49cadc0bf53bd471e330e3c769509018c74^0
0101db8 HEAD@{7}: reset: moving to HEAD^

上面的日志中,HEAD@{3} ~ HEAD@{6}属于rebase操作,我们要做的就是找到rebase操作前的那个提交,然后执行

git log HEAD@{7}           # 查看日志以确认是正确的恢复点
git reset --hard HEAD@{7}  # 撤销rebase操作

图形化界面(GUI)

git有许多很好的图形化界面,比如gitk, qgit和tig等。

big-picture

git-big-picture可以将git仓库的分支和标签之间的衍生关系以图片的形式展示出来,对于直观上了解代码的历史很有帮助。

git big-picture -o pic.png

下面是pelican的big-picture(部分):

默认显示所有的分支,标签和首次提交,所以上图看上去很乱,可以通过如下选项只显示标签之间的关系(部分)

git big-picture --no-branches --no-roots -o pic.png

阅读资料


  1. test^1表示仅rebase到提交B1

#git#教程

评论