git HEAD理解和使用
理解HEAD是迈入git高级的门槛,具有非常重要的意义。本文介绍了git中HEAD相关概念,然后从HEAD的角度出发,对一些常见的git指令进行了解读。
分支和HEAD和指针
- HEAD的本质 是指向某个commit对象的指针;
- 分支指针的本质 是指向某个commit对象的指针;
- 补充分支指针和分支的概念。分支是git库中众多commit对象的组织方式 ,所以分支又可以看成是commit对象链表 。而分支指针则指向这个链表的尾部。借用C语言中的知识,可以用分支指针完全表示这个commit链表,所以分支指针就是分支(正如
int a[] = {1,2,3};
中的a的含义一样)。 - 所以HEAD的本质和分支指针的本质是相同的。
- 那么它们之间有什么关系呢?分支指针指向的是所在分支最新提交 产生的commit对象;而HEAD指向的是某一个分支最新提交 产生的commit对象。也就是说HEAD肯定是 分支指针,而只有被激活的分支(或者称为"当前分支")的分支指针才可以被称为是HEAD。
- 用程序的方式来理解: // 创建三个分支a,b,c
int a[] = {1,2,3};
int b[] = {4,5,6};
int c[] = {7,8,9};
// HEAD,也就是p,在a分支上,表示当前分支为aint *p = a;
// 切换到了b分支上, HEAD指向bp = b;
// HEAD切换到了c分支上, HEAD指向cp = c;
// 分支指针是不可以移动的,但是HEAD可以 // c++ Errorp++ // 表示HEAD指向了c分支上不同的commit对象
c++
#include <stdio.h>
#include <stdlib.h>
int main() {
int a[] = {1,2,3};
int b[] = {4,5,6};
int c[] = {7,8,9};
int* p = a; // 指向a分支的起始位置
printf("%d\n", *p); // 输出a分支的第一个元素
p = b; // 切换到b分支,将指针p指向b分支的起始位置
printf("%d\n", *p); // 输出b分支的第一个元素
p = c; // 切换到c分支,将指针p指向c分支的起始位置
printf("%d\n", *p); // 输出c分支的第一个元素
// 分支指针是不可以移动的,但是HEAD可以,这里涉及到指针运算
// c++;
p++; // HEAD指向了c分支上其他commit对象
printf("%d\n", *p);
getchar();
return 0;
}
HEAD表达式
- HEAD 指向当前分支最新的提交。
- HEAD~ 等同于 HEAD^,表示当前提交的父提交。
- HEAD^ 表示当前提交的第一个父提交。
- HEAD~n 表示当前提交的第 n 代祖先提交。
HEAD相关操作
下面举一些常见的git操作,也许你已经对这些操作了如指掌,但是这次我们从HEAD的角度来理解一下这些操作的含义:
-
查看当前 HEAD 的位置:使用
git rev-parse HEAD
命令可以查看 HEAD 所指向的提交的 SHA 值。 -
切换到另一个分支:使用
git checkout branch-name
命令可以切换到另一个分支,并将 HEAD 指针移动到该分支。 -
创建新分支并切换到该分支:通过
git checkout -b new-branch-name
命令可以在当前 HEAD 所在的提交上创建一个新分支,并立即切换到该分支,对应到上述代码,相当于是int* p = a;
。 -
撤销最新的提交:使用
git reset --hard HEAD^
命令可以撤销最新的提交,并回退到上一个提交状态,之所以有这种效果,在于HEAD~或者HEAD^是HEAD的上一次提交,也就是倒数第二次提交。 -
查看特定文件在 HEAD 版本的内容:
使用 git show HEAD:path/to/file
命令可以查看 HEAD 版本中某个文件的具体内容,当然git show HEAD~n:path/to/file
也是可以的。 -
检出特定提交:使用
git checkout commit-hash
命令可以将 HEAD 指针移动到特定的提交,以查看该提交的内容或创建一个分离的 HEAD 状态(说人话就是打上tag),这种情况实际上就是上面代码中的p++,虽然分支指针不能移动(正如不能对数组变量进行运算),但是指向分支指针的HEAD可以移动。 -
创建临时分支:通过
git checkout -b temporary-branch-name
命令可以在当前 HEAD 所在的提交上创建一个临时分支,一般来说修改bug的时候会创建临时分支,这种一般称为hotfixes分支。 -
移动 HEAD 到指定分支的最新提交:使用
git checkout branch-name
和git pull
命令可以将 HEAD 指针移动到某个特定分支的最新提交,就是上面代码中p从指向a数组变成了指向b或者c数组。 -
查看 HEAD 所在分支的状态:使用
git status
命令可以查看当前 HEAD 所在分支的状态,包括已修改、已暂存和未跟踪的文件。 -
执行交互式的 rebase 操作:使用
git rebase -i HEAD~n
命令可以执行交互式的变基操作,编辑、合并或重新排序提交;这个代码简单理解成对最近n次提交进行操作,不要和"变基"这么猥琐的词联系起来。 -
创建轻量级标签:通过
git tag tag-name
命令可以创建一个轻量级标签,它只是指向当前 HEAD 指针所在的提交;简单来说就是给当前HEAD指向的commit对象起个别名,并将别名记录下来以便日后使用,一般对于有参考意义的重大commit进行此操作。 -
查看 HEAD 所在分支的提交图形:使用
git log --graph
命令可以以图形化方式展示当前 HEAD 所在分支的提交历史。 -
检查 HEAD 是否指向某个分支:使用
git symbolic-ref --short HEAD
命令可以确定 HEAD 是否指向一个分支,即返回当前所在的分支的分支名。
总结一下:
上述的命令可以大致分成下面几类:
- 查看状态类:
- git rev-parse HEAD
- git show HEAD:path/to/file
- git status
- git log --graph
- git symbolic-ref --short HEAD
- 切换分支类:
- git checkout branch-name
- git checkout -b new-branch-name
- git checkout -b temporary-branch-name
- 同分支内移动类:
- git reset --hard HEAD^
- git pull
- git rebase -i HEAD~n
- 直接操作commit类:
- git checkout commit-hash
- git tag tag-name