git所有的操作, 其实都是在跟commit打交道:

stage你的commit, 创建一个commit, 查看之前的commit, 把commit在不同的branch之间挪动, push你的commit等等.

那么, 怎么指定commit就很重要.

1. 通过commit hash来指定.

一个commit跟一个commit hash是一一对应的, 这是最直接的指定commit的方式.

2. 通过refs

refs是一种间接指定commit的方式, 相当于commit hash的别名, 它更加对人类友好, 不过, 不是所有的commit hash都有别名(也就是refs)的. 通常你见到的branch name, 比如master, 其实就是一种ref.

1. 一般的ref

要知道你的repo有多少refs, 可以在.git/refs这个目录里看到类似下面的目录树.

如果你的git repo是一个大型repo, 很可能你的refs被压缩成一个叫.git/packed-refs的文件, 而不是在.git/refs目录下.

heads/ # 这个目录里存放了你所有的local branch
	master
	some-feature
remotes/ # 这个目录下存放的是你所有fetch到本地的remote branch
	origin/
		master
tags/ # 这个目录下是你所有的tag
	v0.9

使用一个ref来指定一个commit很简单, 比如

git show master
# 这里的master, 只是refs/heads/master的简写, 正常情况下, 我们简写refs就可以, 除非tags目录下也有一个master的tag存在, 这时候就不能简写master,那样就无法区分是refs/heads/master, 还是refs/tags/master.
2. 特殊的ref

除了.git/refs(或者.git/packed-refs)下的refs, 还有一些特殊的refs, 定义在.git这个目录下, 说几个常见的:

  • HEAD: 当然checkout的commit/branch
  • FETCH_HEAD: 最近一次从远端 fetch的branch
  • MERGE_HEAD: 你正在merge到当前brach的那个commit.
3. 相对refs

你可以指定通过一个commit来指定另一个commit:

  • ~这个字符, 可以帮助你指定你当前commit的parent commit.

    git show HEAD~1: HEAD的上一个commit.

  • 如果发生过merge, 那么一个commit, 就有可能有两个parent, 这个时候, 如何指定另一个parent呢? ^字符, 可以帮你:

    git show HEAD^2

~^的区别, 下面这张图你可以看到.

Accessing commits using relative refs

4. Reflog

你在local repo上做的所有关于commit hash的历史操作, git其实都为你保留在reflog里. 可以通过git reflog来查看:

5a9f9aa25 (HEAD -> xtao, origin/xtao, origin/HEAD) HEAD@{0}: reset: moving to origin/xtao
53ca49f77 HEAD@{1}: cherry-pick: change dht log level
c83c124b2 HEAD@{2}: cherry-pick: delete unecessary code to fix make error.

如果你要恢复某一个历史commit hash, 你可以通过HEAD@{<n>}这样的语法, 来指定在reflog里的一个commit hash, 比如:

git checkout HEAD@{1}

总结

通过了解git如何引用一个commit, 我们其实了解到了很多git内部的机制, 比如它是如何存储branch信息和tag信息的, 这会帮助我们更好的理解平时使用的git 命令.

有的时候, 我们还需要指定一定范围内的commit, 比如当你想把一个branch上最近提交的几个commit, 提交到另一个branch上, 该怎么做呢? 敬请期待下一篇啦.

参考: https://www.atlassian.com/git/tutorials/refs-and-the-reflog