S_lion's Studio

git原理及常用命令

字数统计: 4.1k阅读时长: 17 min
2021/09/10 Share

关于git的使用介绍网上也比较多了,尤其是廖雪峰大佬的git教程与苏玲大佬的git网课都是git入门的经典。写这篇文章主要是想通过图示化配合文字的方式,加深记忆,也在日常工作中起到速查的作用。

git简介

Git 是一个免费的开源 分布式版本控制系统,旨在快速高效地处理从小到大的所有项目。这是git官网对其的描述。

在没有出现版本控制系统前,常见的作法是在一台服务器上把文件共享出来,每个成员各自创个文件夹,以目录拷贝的方式来区别不同的成员开发的东西以及不同的版本,缺点显而易见,公共文件容易被覆盖,代码集成效率低下。慢慢的产生了cvs和svn之类的集中式版本管理系统,其通过一个版本管理服务器来记录每个文件和文件夹的版本演变历史,也有分支管理的功能,集成效率有了明显的提高,但是版本库都集中存放在了中央服务器,干活时需要先从中央服务器拉取最新的版本,然后在本地开发,最后推送到服务器,没网是寸步难行,性能和效率是最大的问题。后来linux创始人linus写出了分布式的版本控制系统——git,其去中心化的思想,让每个客户端都相关于是一个完整的版本库,加上增强性的分支管理能力,使性能和效率有了很大提升,目前已经成为了市场上主流的版本控制系统,伴随着git的日趋成熟,也出现了很多基于git的web端产品,比如github和gitlab。

git安装

不同操作系统的git安装方式可以参考官方文档
git安装

git核心原理

版本管理实现.git

git相比svn等工具具备最优的存储能力,也可以实现在没有远端中心服务器的情况下,在本地具备版本管理能力。

当我们使用git init后会在当前目录生成一个.git的隐藏目录,此目录下的各种文件就是git核心的一些东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@slions_pc1 first_git]# git init
初始化空的 Git 版本库于 /home/first_git/.git/
[root@slions_pc1 first_git]# tree .git/
.git/
├── branches
├── config
├── description
├── HEAD
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── prepare-commit-msg.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   └── update.sample
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
├── heads
└── tags

.git/config文件

顾名思义,config文件存储的是当前仓库的配置信息,如果什么也没有配置时,它长这样:

1
2
3
4
5
6
[root@slions_pc1 first_git]# cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true

修改此文件的配置会立即生效,当然推荐做法是使用命令git config --local来配置。

在多人协助时,一定要配置user.nameuser.email,这样才能区别用户。

如下示例,我配置当前仓库的用户与邮箱,可以看到配置完.git/config也更新了信息。

1
2
3
4
5
6
7
8
9
10
11
[root@slions_pc1 first_git]# git config --global user.name slions
[root@slions_pc1 first_git]# git config --local user.email slions@163.com
[root@slions_pc1 first_git]# cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[user]
email = slions@163.com
name = slions

后续我们在此仓库中提交的用户就都是slions了。

使用git config --global可以配置全局的git配置,也就是说如果本地仓库没有设置专属配置时,就会以全局配置作为默认配置,相应的全局也有config文件,linux的在/root/.gitconfig,windows在用户宿主目录下的~/.gitconfig

.git/objects目录

存储是版本控制系统中非常重要的技术点,特别是当各种文档频繁的进行变更与回退,如果没有一个好的存储系统作为支撑,那版本库会随着日积月累越来越大,性能和效率都会变差。

git使用SHA-1的Hash算法来记录与存储文件,不管输入数据的数据量有多大,使用同一个哈希算法,得到的加密结果长度固定;哈希算法确定,输入数据确定,输出结果保证不变。

git是一个内容寻址文件系统,通过hash算法得到文件的“指纹”(40位16进制数字),通过文件指纹存取数据,存取的数据都在objects目录。

object目录下有3种类型的数据:

  • Commit
  • Tree
  • Blob

在使用git的过程中,创建的提交会被存储为Commit类型的文件,文件夹会被存储为Tree类型的文件,文件会被存储为Blob类型的文件。其中pack目录会存储的是一些打包文件(Git 往磁盘保存对象时默认使用的格式叫松散对象 (loose object) 格式,Git 时不时地将这些对象打包至一个叫 packfile 的二进制文件以节省空间并提高效率)。

示例说明:

此时我们位于默认的master分支,工作区与暂存区都是干净的,创建一个hello.file文件,查看.git/objects无任何变化,因为hello.file还没被git纳管。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@slions_pc1 first_git]# git status
# 位于分支 master
#
# 初始提交
#
无文件要提交(创建/拷贝文件并使用 "git add" 建立跟踪)
[root@slions_pc1 first_git]# ls
[root@slions_pc1 first_git]# echo hello world! >> hello.file
[root@slions_pc1 first_git]# cat hello.file
hello world!
[root@slions_pc1 first_git]# tree .git
.git
├── config
...
├── objects
│   ├── info
│   └── pack
└── refs
├── heads
└── tags

9 directories, 14 files

添加hello.file到暂存区,可以看到git/objects中多了一个目录与文件,拼起来正是hello.file的hash值,使用git cat-file -p查看文件的内容就是hello.file的内容,使用git cat-file -t查看其类型为blob。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@slions_pc1 first_git]# git add .
[root@slions_pc1 first_git]# tree .git
.git
├── config
...
├── objects
│   ├── a0
│   │   └── 423896973644771497bdc03eb99d5281615b51
│   ├── info
│   └── pack
└── refs
├── heads
└── tags

10 directories, 15 files
[root@slions_pc1 first_git]# git hash-object hello.file
a0423896973644771497bdc03eb99d5281615b51
[root@slions_pc1 first_git]# git cat-file -p a0423896973644771497bdc03eb99d5281615b51
hello world!
[root@slions_pc1 first_git]# git cat-file -t a0423896973644771497bdc03eb99d5281615b51
blob

创建一个文件夹试试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[root@slions_pc1 first_git]# mkdir mydir
[root@slions_pc1 first_git]# echo "这个文件处于mydir目录中" > mydir/test.file
[root@slions_pc1 first_git]# git status
# 位于分支 master
#
# 初始提交
#
# 要提交的变更:
# (使用 "git rm --cached <file>..." 撤出暂存区)
#
# 新文件: hello.file
#
# 未跟踪的文件:
# (使用 "git add <file>..." 以包含要提交的内容)
#
# mydir/
[root@slions_pc1 first_git]# git add .
[root@slions_pc1 first_git]# tree .git/
.git/
├── config
...
├── objects
│   ├── a0
│   │   └── 423896973644771497bdc03eb99d5281615b51
│   ├── b4
│   │   └── 742fdeca5b697ffdf5b0e0d1c7b74085f36bca
│   ├── info
│   └── pack
└── refs
├── heads
└── tags

11 directories, 16 files
[root@slions_pc1 first_git]# git cat-file -p b4742fdeca5b697ffdf5b0e0d1c7b74085f36bca
这个文件处于mydir目录中
[root@slions_pc1 first_git]# git cat-file -t b4742fdeca5b697ffdf5b0e0d1c7b74085f36bca
blob

提交看看效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@slions_pc1 first_git]# tree .git/
.git/
├── branches
├── COMMIT_EDITMSG
├── config
...
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│   └── heads
│   └── master
├── objects
│   ├── a0
│   │   └── 423896973644771497bdc03eb99d5281615b51
│   ├── af
│   │   └── 5f8664975db42eb0780392064254516a5386ae
│   ├── b4
│   │   └── 742fdeca5b697ffdf5b0e0d1c7b74085f36bca
│   ├── c6
│   │   └── 98f655405d32c8d5130f3cd5393dfa5897275d
│   ├── e9
│   │   └── a3da963bf7298300b2130a64315799b6baef31
│   ├── info
│   └── pack
└── refs
├── heads
│   └── master
└── tags

将hello.file与mydir/test.file提交后,发现在objects下多了3个文件,分别是af/5f8664975db42eb0780392064254516a5386aec6/98f655405d32c8d5130f3cd5393dfa5897275d,e9/a3da963bf7298300b2130a64315799b6baef31

查看其内容与类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@slions_pc1 first_git]# git cat-file -p af5f8664975db42eb0780392064254516a5386ae
100644 blob b4742fdeca5b697ffdf5b0e0d1c7b74085f36bca test.file
[root@slions_pc1 first_git]# git cat-file -t af5f8664975db42eb0780392064254516a5386ae
tree
[root@slions_pc1 first_git]# git cat-file -p c698f655405d32c8d5130f3cd5393dfa5897275d
100644 blob a0423896973644771497bdc03eb99d5281615b51 hello.file
040000 tree af5f8664975db42eb0780392064254516a5386ae mydir
[root@slions_pc1 first_git]# git cat-file -t c698f655405d32c8d5130f3cd5393dfa5897275d
tree
[root@slions_pc1 first_git]# git cat-file -p e9a3da963bf7298300b2130a64315799b6baef31
tree c698f655405d32c8d5130f3cd5393dfa5897275d
author slions <slions@163.com> 1631430170 +0800
committer slions <slions@163.com> 1631430170 +0800

我的第一次提交,创建hello.file与mydir/test.file文件
[root@slions_pc1 first_git]# git cat-file -t e9a3da963bf7298300b2130a64315799b6baef31
commit

可以发现,objects中commit类型的文件内容与git log的内容如出一辙,且commit ID一致。

1
2
3
4
5
6
[root@slions_pc1 first_git]# git log
commit e9a3da963bf7298300b2130a64315799b6baef31
Author: slions <slions@163.com>
Date: Sun Sep 12 15:02:50 2021 +0800

我的第一次提交,创建hello.file与mydir/test.file文件

再创建个提交来观察下变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@slions_pc1 first_git]# echo test2 >   mydir/test2.file
[root@slions_pc1 first_git]# git add .
[root@slions_pc1 first_git]# git commit -m '第二次提交,创建mydir/test2.file文件'
[master 48acad0] 第二次提交,创建mydir/test2.file文件
1 file changed, 1 insertion(+)
create mode 100644 mydir/test2.file
[root@slions_pc1 first_git]# tree .git/objects/
.git/objects/
├── 18
│   └── 0cf8328022becee9aaa2577a8f84ea2b9f3827
├── 48
│   └── acad07a1446a569f8fe2ea06ef15973291561b
├── 71
│   └── bac679881c174b509333379601f4096220809b
├── 77
│   └── 8da9f040bedfbf92188332f2d0097cfd1b4fb2
├── a0
│   └── 423896973644771497bdc03eb99d5281615b51
├── af
│   └── 5f8664975db42eb0780392064254516a5386ae
├── b4
│   └── 742fdeca5b697ffdf5b0e0d1c7b74085f36bca
├── c6
│   └── 98f655405d32c8d5130f3cd5393dfa5897275d
├── e9
│   └── a3da963bf7298300b2130a64315799b6baef31
├── info
└── pack

查看多出来的这4个文件的内容与类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@slions_pc1 first_git]# git cat-file -p 180cf8328022becee9aaa2577a8f84ea2b9f3827
test2
[root@slions_pc1 first_git]# git cat-file -t 180cf8328022becee9aaa2577a8f84ea2b9f3827
blob
[root@slions_pc1 first_git]# git cat-file -p 48acad07a1446a569f8fe2ea06ef15973291561b
tree 71bac679881c174b509333379601f4096220809b
parent e9a3da963bf7298300b2130a64315799b6baef31
author slions <slions@163.com> 1631432193 +0800
committer slions <slions@163.com> 1631432193 +0800

第二次提交,创建mydir/test2.file文件
[root@slions_pc1 first_git]# git cat-file -t 48acad07a1446a569f8fe2ea06ef15973291561b
commit
[root@slions_pc1 first_git]# git cat-file -p 71bac679881c174b509333379601f4096220809b
100644 blob a0423896973644771497bdc03eb99d5281615b51 hello.file
040000 tree 778da9f040bedfbf92188332f2d0097cfd1b4fb2 mydir
[root@slions_pc1 first_git]# git cat-file -t 71bac679881c174b509333379601f4096220809b
tree
[root@slions_pc1 first_git]# git cat-file -p 778da9f040bedfbf92188332f2d0097cfd1b4fb2
100644 blob b4742fdeca5b697ffdf5b0e0d1c7b74085f36bca test.file
100644 blob 180cf8328022becee9aaa2577a8f84ea2b9f3827 test2.file
[root@slions_pc1 first_git]# git cat-file -t 778da9f040bedfbf92188332f2d0097cfd1b4fb2
tree

这次可以看到48acad07a1446a569f8fe2ea06ef15973291561b的内容多了一个parent行,其“指纹”和第一次提交时的一致。

通过一张图来说明当前我本地环境中commit、tree、blob之间的关系。

如果我们创建相同的100个文件,并且分别进行提交操作,因为文件内容都一样,所以object中只会存一份blob,会出现100个tree,大大的提高了存储空间的利用率于寻址效率。

当前提交两个commit后git仓库可简化为下图:

.git/refs目录

refs目录存储的都是引用文件。如本地分支,远端分支,标签等。

  • refs/heads/xxx 本地分支
  • refs/remotes/origin/xxx 远端分支
  • refs/tags/xxx 本地tag

引用文件的内容都是40位长度的commit

1
2
3
4
5
6
7
[root@slions_pc1 first_git]# tree .git/refs/
.git/refs/
├── heads
│   └── master
└── tags
[root@slions_pc1 first_git]# cat .git/refs/heads/master
48acad07a1446a569f8fe2ea06ef15973291561b

当前git仓库逻辑拓扑为下图:

.git/HEAD文件

HEAD目录存储的是当前所在的位置,内容是分支的名称:

1
2
[root@slions_pc1 first_git]# cat .git/HEAD
ref: refs/heads/master

当前git仓库逻辑拓扑为下图:

.git/branches文件

1
2
3
4
5
6
7
[root@slions_pc1 first_git]# git branch -v
* master 48acad0 第二次提交,创建mydir/test2.file文件
[root@slions_pc1 first_git]# git checkout -b test e9a3da9
A hello
D hello.file
切换到一个新分支 'test'

git常用命令速查

新建代码库

把已有的项目代码纳入git管理

1
2
$ cd 项目代码所在的文件夹
$ git init

新建的项目直接用git管理

1
2
3
$ cd 某个文件夹
$ git init [project-name]
$ cd project-name

下载一个项目和它的整个代码历史

1
$ git clone [url]

配置

显示当前的Git配置

1
$ git config --list

编辑Git配置文件

1
$ git config -e [--global]

设置提交代码时的用户信息

1
2
$ git config [--global/local] user.name "[name]"
$ git config [--global/local] user.email "[email address]"

增加删除文件

添加指定文件到暂存区

1
$ git add [file1] [file2] ...

添加指定目录到暂存区,包括子目录

1
$ git add [dir]

添加当前目录的所有文件到暂存区

1
$ git add .

删除工作区文件,并且将这次删除放入暂存区

1
$ git rm [file1] [file2] ...

停止追踪指定文件,但该文件会保留在工作区

1
$ git rm --cached [file]

文件重命名

改名文件,并且将这个改名放入暂存区

1
$ git mv [file-original] [file-renamed]

代码提交

提交暂存区到仓库区

1
$ git commit -m [message]

提交暂存区的指定文件到仓库区

1
$ git commit [file1] [file2] ... -m [message]

提交工作区自上次commit之后的变化,直接到仓库区

1
$ git commit -a

提交时显示所有diff信息

1
$ git commit -v

使用一次新的commit,替代上一次提交,如果代码没有任何新变化,则用来改写上一次commit的提交信息

1
$ git commit --amend -m [message]

重做上一次commit,并包括指定文件的新变化

1
$ git commit --amend [file1] [file2] ...

分支

列出所有本地分支

1
$ git branch

列出所有远程分支

1
$ git branch -r

列出所有本地分支和远程分支

1
$ git branch -a

新建一个分支,但依然停留在当前分支

1
$ git branch [branch-name]

新建一个分支,并切换到该分支

1
$ git checkout -b [branch]

新建一个分支,指向指定commit

1
$ git branch [branch] [commit]

新建一个分支,与指定的远程分支建立追踪关系

1
$ git branch --track [branch] [remote-branch]

切换到指定分支,并更新工作区

1
$ git checkout [branch-name]

切换到上一个分支

1
$ git checkout -

建立追踪关系,在现有分支与指定的远程分支之间

1
$ git branch --set-upstream [branch] [remote-branch]

合并指定分支到当前分支

1
$ git merge [branch]

选择一个commit,合并进当前分支

1
$ git cherry-pick [commit]

删除分支

1
$ git branch -d [branch-name]

删除远程分支

1
2
3
$ git push origin --delete [branch-name]

$ git branch -dr [remote/branch]

标签

列出所有tag

1
$ git tag

新建一个tag在当前commit

1
$ git tag [tag]

新建一个tag在指定commit

1
$ git tag [tag] [commit]

删除本地tag

1
$ git tag -d [tag]

删除远程tag

1
$ git push origin :refs/tags/[tagName]

查看tag信息

1
$ git show [tag]

提交指定tag

1
$ git push [remote] [tag]

提交所有tag

1
$ git push [remote] --tags

新建一个分支,指向某个tag

1
$ git checkout -b [branch] [tag]

查看信息

显示有变更的文件

1
$ git status

显示当前分支的版本历史

1
$ git log

显示commit历史,以及每次commit发生变更的文件

1
$ git log --stat

搜索提交历史,根据关键词

1
$ git log -S [keyword]

显示某个commit之后的所有变动,每个commit占据一行

1
$ git log [tag] HEAD --pretty=format:%s

显示某个commit之后的所有变动,其”提交说明”必须符合搜索条件

1
$ git log [tag] HEAD --grep feature

显示某个文件的版本历史,包括文件改名

1
2
3
$ git log --follow [file]

$ git whatchanged [file]

显示指定文件相关的每一次diff

1
$ git log -p [file]

显示过去5次提交

1
$ git log -5 --pretty --oneline

显示所有提交过的用户,按提交次数排序

1
$ git shortlog -sn

显示指定文件是什么人在什么时间修改过

1
$ git blame [file]

显示今天你写了多少行代码

1
$ git diff --shortstat "@{0 day ago}"

显示某次提交的元数据和内容变化

1
$ git show [commit]

显示某次提交发生变化的文件

1
$ git show --name-only [commit]

显示某次提交时,某个文件的内容

1
$ git show [commit]:[filename]

显示当前分支的最近几次提交

1
$ git reflog

从本地master拉取代码更新当前分支:branch 一般为master

1
$ git rebase [branch]

对比

显示暂存区和工作区的代码差异

1
$ git diff

显示暂存区和上一个commit的差异

1
$ git diff --cached [file]

显示工作区与当前分支最新commit之间的差异

1
$ git diff HEAD

显示两次提交之间的差异

1
$ git diff [first-branch]...[second-branch]

远程同步

更新远程仓储

1
$ git remote update

下载远程仓库的所有变动

1
$ git fetch [remote]

显示所有远程仓库

1
$ git remote -v

显示某个远程仓库的信息

1
$ git remote show [remote]

增加一个新的远程仓库,并命名

1
$ git remote add [shortname] [url]

取回远程仓库的变化,并与本地分支合并

1
$ git pull [remote] [branch]

上传本地指定分支到远程仓库

1
$ git push [remote] [branch]

强行推送当前分支到远程仓库,即使有冲突

1
$ git push [remote] --force

推送所有分支到远程仓库

1
$ git push [remote] --all

撤销

恢复暂存区的指定文件到工作区

1
$ git checkout [file]

恢复某个commit的指定文件到暂存区和工作区

1
$ git checkout [commit] [file]

恢复暂存区的所有文件到工作区

1
$ git checkout .

重置暂存区的指定文件,与上一次commit保持一致,但工作区不变

1
$ git reset [file]

重置暂存区与工作区,与上一次commit保持一致

1
$ git reset --hard

重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变

1
$ git reset [commit]

重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致

1
$ git reset --hard [commit]

重置当前HEAD为指定commit,但保持暂存区和工作区不变

1
$ git reset --keep [commit]

新建一个commit,用来撤销指定commit

后者的所有变化都将被前者抵消,并且应用到当前分支

1
$ git revert [commit]

暂时将未提交的变化移除,稍后再移入

1
2
3
$ git stash

$ git stash pop

其他

生成一个可供发布的压缩包

1
$ git archive

多彩log输出

1
2
$ git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
$ git lg

推荐文章

git动图展示工作流: https://zhuanlan.zhihu.com/p/132573100

使用原理视角看git: https://www.cnblogs.com/Coding-net/p/5577485.html

CATALOG
  1. 1. git简介
  2. 2. git安装
  3. 3. git核心原理
    1. 3.1. 版本管理实现.git
      1. 3.1.1. .git/config文件
      2. 3.1.2. .git/objects目录
      3. 3.1.3. .git/refs目录
      4. 3.1.4. .git/HEAD文件
      5. 3.1.5. .git/branches文件
  4. 4. git常用命令速查
    1. 4.1. 新建代码库
    2. 4.2. 配置
    3. 4.3. 增加删除文件
    4. 4.4. 文件重命名
    5. 4.5. 代码提交
    6. 4.6. 分支
    7. 4.7. 标签
    8. 4.8. 查看信息
    9. 4.9. 对比
    10. 4.10. 远程同步
    11. 4.11. 撤销
    12. 4.12. 其他
    13. 4.13. 多彩log输出
  5. 5. 推荐文章