Loading

Git常用命令与底层原理学习

Git基础与思想

Git将数据看做小型文件系统的一组快照, 以快照流的形式进行版本控制,记录版本变动的信息。

Git有三种状态:已提交、已修改、已暂存。修改了文件,没有做任何操作,该文件的状态就是已修改;使用add命令,将文件添加到暂存区,该文件的当前版本被做了标记,状态就变成了已暂存;使用commit命令将数据安全地保存在本地数据库中,状态就变成了已提交

Git常用命令

一、初始化仓库

$ git init # 初始化仓库

$ git init --bare # 初始化裸仓库

$ git clone <git -repo> # 克隆仓库

$ git clone -b branch_name repo_address # 克隆仓库的某分支

二、配置

$ git config -l # 列出配置信息

$ git config --global user.email <mail> # 配置邮箱

$ git config --global user.name <name> # 配置用户名

$ git config --global alias.co checkout # 配置别名

三、添加,删除与移动

$ git add . # 添加所有文件到暂存区

$ git rm <file> # 同时删除工作区与暂存区的文件

$ git rm --cached <file> # 仅删除暂存区文件

$ git mv <file1> <file2> # 重命名暂存区文件

四、查看工作区状态

$ git status # 查看工作区状态

$ git status -sb # 查看工作区状态并显示分支及追踪状态

五、查看更改信息

$ git diff # 显示工作区与暂存区的不同,即没有add前查看修改

$ git diff --cached # 显示暂存区与本地仓库的不同,即add后查看修改

$ git diff HEAD # 显示工作区,暂存区与本地仓库的不同

$ git diff <commit1> <commit2> # 显示两次提交的不同

$ git show <commit> # 显示某次提交的更改信息

六、储藏及恢复

$ git stash # 储藏工作区的某些文件

$ git stash save <message> # 储藏文件并添加描述信息

$ git stash apply # 恢复最后一次储藏的文件

$ git stash apply stash@{x} #恢复一次旧的储藏,编号为x

$ git stash pop # 恢复最后一次储藏的文件,并删除此次储存记录

$ git stash list # 查看储藏列表

七、回退

如果执行了git add,想回退

$ git restore --staged <file_name>

执行了git commit或push,想回退

$ git log 

找到想要回退的版本号

$ git reset --soft <commit_hash>

刚执行了git pull,想回退
1.先用用git reflog查看历史记录如下

$ git reflog
fdb70fe HEAD@*{0}*: pull origin newpbft: Fast-forward
40a9a83 HEAD@*{1}*: checkout: moving from guan to master
......

2.然后使用reset命令,比如想要回到倒数第二个状态

$ git reset --hard 40a9a83 
  • 辨析reset --soft --hard --mixed区别
$ git reset --soft <commit_hash> # 恢复本分支到某次提交,由commit-hash指定

$ git reset --mixed <commit_hash> # reset默认操作,恢复本分支到某次提交,由commit-hash指定,重置暂存区,所有文件回到了add和commit之前

$ git reset --hard <commit_hash> # 恢复本分支到某次提交,由commit-hash指定,重置工作区与暂存区,危险操作,工作区的文件也回到了修改前

若想对工作区进行重置

$ git checkout -- <file_name> # 重置工作区某文件

$ git checkout . # 重置工作区

八、分支操作

$ git branch # 显示分支列表

$ git branch -a # 显示全部分支

$ git branch -r # 显示远程分支

$ git branch -vv # 显示本地分支与追踪关系

$ git branch <branch_name> # 建立一个分支,但不切换工作区

$ git branch -d <branch_name> # 删除一个分支

$ git branch -D <branch_name> # 强制删除一个分支

$ git branch -m <new_branch_name> # 更改分支名称

# 切换分支

$ git checkout <branch_name> # 切换分支

$ git checkout -b <branch_name> # 新建分支并切换工作区

$ git checkout - # 切换到最近一次的分支

# 合并分支

$ git merge <branch_name> # 合并branch_name到本分支

$ git merge - # 合并最近切换的分支

$ git rebase <branch_name> # 变基合并分支 
# !!!警示:已经推送到公共仓库的更新,不可以使用rebase

九、推送与拉取

$ git push origin master # 推送到远程仓库并建立追踪关系

$ git pull origin master # 从远程仓库拉取文件

$ git pull --rebase origin master # 设置 rebase 模式拉取代码

git pull = git fetch + git merge

  • 辨析git fetch + git merge 和 git fetch + git rebase 的差异

十、提交

$ git commit -m <message> # 提交

$ git commit --amend -m <message> # 重写提交信息并追加到同一提交

$ git commit --amend # 修改后,追加到同一提交

十一、日志

$ git log # 查看日志

$ git log -p # 显示每次提交的差异

$ git log -2 # 显示近两次提交

$ git log --pretty=format:"%h(简短哈希) %an(作者) %s(信息)" # 按指定格式输出日志

$ git log --graph # 按图形式输出日志

$ git log --author=<author_name> # 仅显示某人的提交日志

详细格式符可见Git-book

十二、搜索

$ git grep -n <some_words> # 查看以追踪文件的some_words,并显示文件与行号

$ git log -S<variable_name> --oneline # 按行显示日志,找到关于variable_name这个变量的增加,修改与删除

$ git log -L :<method_name>:<file_name> # 行日志搜索,查看file_name这个文件中,method_name函数的更改信息

十三、其他技巧

统计各成员commit情况

git shortlog -sn

快速切换合并分支

git checkout -  // 表示切换到最近的一次分支
git merge - // 表示将最近的一次分支合并过来

(此处不介绍git blame命令,结合VScode的git lens插件,完全可以定位提交每一行代码的作者)

Git底层原理

为了使用户使用方便,现在的Git对底层的实现进行了封装,使得使用起来十分方便快捷,但是对理解底层实现并不是十分容易。

早期的Git侧重于作为一个文件系统(git实质上是一个内容寻址文件系统),有一套面向版本控制系统的工具集,包含了一部分用于完成底层工作的命令。这些命令叫做底层命令。从这些命令入手可以接触到Git的底层。

首先git init创建空的Git仓库,进入git init初始化的.git目录下,查看

$ ls -F1
HEAD
config*
description
hooks/
info/
objects/
refs/

其中有四个项目很重要:

HEAD 文件、(尚待创建的)index 文件,和 objects 目录、refs 目录。 这些条目是 Git 的核心组成部分。 objects 目录存储所有数据内容;refs 目录存储指向数据(分支)的提交对象的指针;HEAD 文件指当前分支引用的指针,总是指向该分支上的最后一次提交,并且将作为下一次提交的父结点;index 文件保存暂存区信息。

Git有四种对象:blob(数据对象) tree(树对象) commit(提交对象) tag(标签对象,可以理解为commit对象的别名,相互对应),主要用到的是blob, tree和commit

几条关键底层命令:

$ git hash-object # 向Git数据库写入数据
$ git cat-file # 从Git数据库读数据
$ git update-index # 创建暂存区
$ git ls-files --stage # 查看暂存区内容
$ git write-tree # 将暂存区内容写入一个树对象
$ git commit-tree # 创建提交对象,要指定提交的树对象的哈希值
$ git update-ref refs/heads/master <commit-hash> # 创建一个引用,将“master”与<commit-hash>连接
$ echo "hello world" | git hash-object -w --stdin # 写入hello world进数据库中
3b18e512dba79e4c8300dd08aeb37f8e728b8dad
$ find .git/objects/ -type f # 查看object目录内容
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad

可见显示了一串hash值,这是Git使用SHA-1算法对数据内容+头部信息进行运算得到的40位十六进制校验和。在Git数据库中,这个值就是"key",可以通过这个"key"检索到内容。

查看object目录下,有一个新的数据,以校验和前两位做文件夹名称,后38位做内容。

$ git cat-file -t 3b18e5 # -t 查看3b18e5数据的类型
blob # 之前提到的blob对象

$ git cat-file -p 3b18e5 # -p 查看数据的内容
hello world

$ echo "hello world" > file1.txt
$ git hash-object -w file1.txt
3b18e512dba79e4c8300dd08aeb37f8e728b8dad # 尽管是新的文件,但是内容一致,所以校验和也是一样的
$ find .git/objects/ -type f
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad # objects目录下也仍然只有一条数据,说明Git只关注内容

$ echo "hello js" > file2.txt # 同样的步骤,创建file2,并写入数据库
$ git hash-object -w file2.txt
a59cb991c571c161bb5fef062e8a1b939ad0f6d0
$ find .git/objects/ -type f # 查看object文件下有了2条数据
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/a5/9cb991c571c161bb5fef062e8a1b939ad0f6d0

模拟版本回退机制

$ echo "hello web" > file2.txt # 修改file2.txt
$ git hash-object -w file2.txt # 将修改后的file2.txt写入数据库
de9c3f56d29d04fe6f7e4d8acc7def8a56795bcc

$ find .git/objects/type -f # 查看数据
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/a5/9cb991c571c161bb5fef062e8a1b939ad0f6d0 # 修改前的file2.txt
.git/objects/de/9c3f56d29d04fe6f7e4d8acc7def8a56795bcc # 修改后的file2.txt
$ git cat-file -p a59cb9 > file2.txt #取出修改前的内容,写入file2.txt
$ cat file2.txt # 看到file2.txt的内容恢复到原先的"hello js"
hello js

可以看到,Git的blob对象代表单一的数据,对于多个文件和文件夹,Git使用tree树对象来组织。

$ git update-index --add file1.txt # 将file1.txt放入了暂存区,就是git add操作

$ git ls-files --stage # 查看暂存区内容,file1.txt的校验和与文件名
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0       file1.txt

$ git write-tree # 将暂存区内容写入一个tree对象,返回哈希值
82424451ac502bd69712561a524e2d97fd932c69
$ git cat-file -t 824244
tree

$ find .git/objects/ -type f # 再次查看object目录,可以看到多出了tree对象对应的数据
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/82/424451ac502bd69712561a524e2d97fd932c69
.git/objects/a5/9cb991c571c161bb5fef062e8a1b939ad0f6d0

$ mkdir newDir
$ echo "hello dir" > newDir/file3.txt
$ git update-index --add newDir/file3.txt # 将新的文件夹下的文件添加到暂存区
$ git ls-files --stage # 可以看到暂存器有两条内容,且之前的file1.txt信息还在
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0       file1.txt
100644 9e52bd0e46171173a69bedbb2d6c30bd4e231d40 0       newDir/file3.txt
$ git write-tree # 再次写入一个树对象
3c6ddda6790ef5daa10254a7f8e6aa8fc57633da
$ git cat-file -p 3c6ddd # 查看该次的树对象内容,可以看到有file1.txt和newDir文件夹
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file1.txt
040000 tree 8f3fc2eeb3c76881cd31751b78ecc23b9cebfa05    newDir

Git还有commit提交对象,用来记录版本时序关系与提交信息

$ git commit-tree 3c6ddd -m "commit message" # 将上面的那个树对象用来创建commit对象
53444655b57059e1b9879798489cca7b576ad6df
$ git cat-file -p 534446 # 查看内容,有树对象的hash,用户名与邮箱
tree 3c6ddda6790ef5daa10254a7f8e6aa8fc57633da
author J*****Wong <75****0@qq.com> 1578831085 +0800
committer J*****Wong <75****0@qq.com> 1578831085 +0800

commit message
$ git cat-file -t 534446 # 对象的类型为commit类型

# 如果不是第一次提交,需要给commit-tree命令后加上-p xxxxxx,表示父结点的哈希值

由于用户使用哈希值来存取文件太过复杂,不太现实,所以Git提供了“引入”操作。之前提到的.git下重要的项目之一,有一个叫refs的目录。该目录下存储指向数据(分支)的提交对象的指针,即指向一个哈希值。

# 将上一次的commit对象哈希值写入一个新的master文件,该文件位于.git/refs/heads/目录下
$ echo "53444655b57059e1b9879798489cca7b576ad6df" > .git/refs/heads/master
# 相比直接编辑,Git更提倡使用git update-ref 命令
# git update-ref refs/heads/master 534446

# 这样,一个引用就创建成功了,之后可以用"master"这个名称来替代之前的哈希值
$ git log master
commit 53444655b57059e1b9879798489cca7b576ad6df (HEAD -> master)
Author: J*****Wong <75****0@qq.com>
Date:   Sun Jan 12 20:11:25 2020 +0800

    commit message

参考

GitBook

深入理解Git实现原理

cheat-sheets

posted @ 2020-01-12 21:43  JavicxhloWong  阅读(325)  评论(0编辑  收藏  举报