Git 在团队中的最佳实践(一):Git 备忘清单

前言

Git 是一个免费并且开源的分布式版本控制系统,旨在快速高效地处理从小到大所有项目的版本管理。Git 是目前最流行的版本管理工具,目前绝大部分公司都是使用 Git 作为项目的版本管理工具。目前最火的开源社区 Github,就是基于 Git 版本控制系统,所以掌握 Git 技能很重要。由于 Git 开发效率高、团队协作方便,现在很多 IDE 都集成了 Git,并且提供一些相关的图形化操作。也有很多很优秀,专门用来简化 Git 操作的 Git GUI 工具,例如 Sourcetree,Tortoise 等。我刚接触 Git 的时候,就是从 GUI 入手的,使用 Sourcetree 可视化版本控制工具进行操作。Sourcetree 底层也是对常用的 Git 命令进行封装实现的,傻瓜式操作,使用非常方便,但是我用完什么都不懂,Git 的内部原理也不易理解,决定在回头仔细学习一下 Git 命令行。学习完 Git 命令行后发现,命令行很好学,非常灵活,而且使用起来非常帅气。因此,直接 Git 命令,才是最灵活的操作。

本篇是我学习 Git 系列的开篇,主要讲述 Git 的基本概念和工作原理,然后介绍一下 Git 安装以及环境配置,最后探讨一下 Git 常用命令以及使用场景。

Git 基本概念以及工作原理

你所不了解的 Git 诞生史

同生活中的许多伟大事件一样,Git 诞生于一个极富纷争大举创新的年代。众所周知,Linux 内核开源项目有着为数众广的参与者,但是绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002 年间),即 Linus 本人通过手工方式合并世界各地志愿者通过 diff 的方式传过来的代码。

到 2002 年后,由于代码库太大,Linus 很难继续通过手工方式进行管理,于是 Linux 整个项目组开始启用分布式版本控制系统 BitKeeper 来管理和维护代码,BitKeeper 的东家 BitMover 公司出于人道主义精神,授权 Linux 社区免费使用这个版本控制系统。

到了 2005 年,一位 Linux 开发成员 Andrew(Samba 协议之父)写了一个可以连接 BitKeeper 仓库的外挂,因此 BitMover 公司(BitKeeper 持有者)认为他反编译了 BitKeeper;于是 BitMover 决定中止 Linux 免费使用 BitKeeper 的授权。最终 Linux 团队与 BitMover 磋商无果,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了免费使用 BitKeeper 的权力。这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)不得不吸取教训,只有开发一套属于自己的版本控制系统才不至于重蹈覆辙。

于是,Linus 花了两周时间用 C 语言写了一个分布式版本控制系统,这就是 Git!一个月之内,Linux 系统的源码已经由 Git 进行管理了!

Git 工作原理

Git 是一套内容寻址文件系统,Git 从核心上来看不过是简单地存储键值对(key-value)。它允许插入任意类型的内容,并会返回一个键值,通过该键值可以在任何时候再取出该内容。可以通过底层命令 hash-object 来示范这点,传一些数据给该命令,它会将数据保存在 .git 目录并返回表示这些数据的键值。

文件目录

Git 文件目录

Git 工作区有个隐藏目录. git,核心文件包括:config 文件、objects 文件夹、HEAD 文件、index 文件以及 refs 文件夹。下面依次对其进行说明:

  • config 文件:该文件主要记录针对该项目的一些配置信息,例如是否以 bare 方式初始化、remote 的信息等,通过 git remote add 命令增加的远程分支的信息就保存在这里;
  • objects 文件夹:该文件夹主要包含 git 对象。Git 中的文件和一些操作都会以 git 对象来保存,git 对象分为 BLOB、tree 和 commit 三种类型,例如 git commit 便是 git 中的 commit 对象,而各个版本之间是通过版本树来组织的,比如当前的 HEAD 会指向某个 commit 对象,而该 commit 对象又会指向几个 BLOB 对象或者 tree 对象。objects 文件夹中会包含很多的子文件夹,其中 Git 对象保存在以其 sha-1 值的前两位为子文件夹、后 38 位位文件名的文件中;除此以外,Git 为了节省存储对象所占用的磁盘空间,会定期对 Git 对象进行压缩和打包,其中 pack 文件夹用于存储打包压缩的对象,而 info 文件夹用于从打包的文件中查找 git 对象;
  • HEAD 文件:该文件指明了 git branch(即当前分支)的结果,比如当前分支是 master,则该文件就会指向 master,但是并不是存储一个 master 字符串,而是分支在 refs 中的表示,例如 ref: refs/heads/master。
  • index 文件:该文件保存了暂存区域的信息。该文件某种程度就是缓冲区(staging area),内容包括它指向的文件的时间戳、文件名、sha1 值等;
  • Refs 文件夹:该文件夹存储指向数据(分支)的提交对象的指针。其中 heads 文件夹存储本地每一个分支最近一次 commit 的 sha-1 值(也就是 commit 对象的 sha-1 值),每个分支一个文件;remotes 文件夹则记录你最后一次和每一个远程仓库的通信,Git 会把你最后一次推送到这个 remote 的每个分支的值都记录在这个文件夹中;tag 文件夹则是分支的别名,这里不需要对其有过多的了解;

工作区域

Git 本地有三个工作区域:工作目录(Workspace)、暂存区 (Stage/Index)、资源库(Repository)。如果在加上远程的 git 仓库(Remote Directory) 就可以分为四个工作区域。文件在这四个区域之间的转换关系如下:

git 工作区域

  • Workspace:工作区,就是你平时存放项目代码的地方。
  • Index / Stage:暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息。
  • Repository:仓库区(或本地仓库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中 HEAD 指向最新放入仓库的版本。
  • Remote:远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换。

工作流程

Git 的工作流程一般是这样的:

1、在工作目录中添加、修改文件;

2、将需要进行版本管理的文件放入暂存区域;

3、将暂存区域的文件提交到 Git 仓库。

git 工作流程

Git 文件 4 种状态

  • Untracked: 未跟踪, 此文件在文件夹中, 但并没有加入到 git 库, 不参与版本控制. 通过 git add 状态变为 Staged.
  • Unmodify: 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为 Modified. 如果使用 git rm 移出版本库, 则成为 Untracked 文件
  • Modified: 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过 git add 可进入暂存 staged 状态, 使用 git checkout 则丢弃修改过, 返回到 unmodify 状态, 这个 git checkout 即从库中取出文件, 覆盖当前修改
  • Staged: 暂存状态. 执行 git commit 则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为 Unmodify 状态. 执行 git reset HEAD filename 取消暂存, 文件状态为 Modified

GIT 文件 4 种状态

Git 安装以及环境配置

本文统一使用软件包管理器的方式安装 Git,减少环境变量的配置,更加方便快捷。

Linux 安装 Git

CentOS7 中使用 yum 安装 Git 的方法

1
2
3
4
5
6
7
8
9
10
11
# 1. 查看 git 是否安装
$ git --version

# 2. 显示所有已经安装和可以安装的 git 程序包
$ sudo yum list git

# 3. 使用 yum 安装
$ sudo yum install git -y

# 4. 查看安装是否成功
$ git --version

Mac 安装 Git

Mac 中使用 brew 安装 Git 的方法

1
2
3
4
5
6
7
8
# 1. 查看 git 是否安装
$ git --version

# 2. 使用 brew 安装
$ brew install git

# 3. 查看安装是否成功
$ git --version

Windows 安装 Git

Windows 中使用 choco 安装 Git 的方法

1
2
3
4
5
6
7
8
9
10
11
# 1. 查看 git 是否安装
$ git --version

# 2. 搜索 git 安装包
$ choco search git

# 3. 使用 choco 安装
$ choco install git

# 4. 查看安装是否成功
$ git --version

全局配置

Git 用户的配置文件位于 ~/.gitconfig
Git 单个仓库的配置文件位于 ~/$PROJECT_PATH/.git/config

1
2
3
4
5
6
7
8
9
# 1. 显示当前的 Git 配置
$ git config --list

# 2. 编辑 Git 配置文件 p.s.[--global: 表示全局配置, 如果不加, 则表示当前 git 仓库的配置]
$ git config -e [--global]

# 3. 设置提交代码时的用户信息
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"

如果用了 –global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 –global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。

服务器上的 Git - 生成 SSH 公钥

大多数 Git 服务器都会选择使用 SSH 公钥来进行授权。系统中的每个用户都必须提供一个公钥用于授权,没有的话就要生成一个。SSH 公钥默认储存在账户的主目录下的 ~/.ssh 目录。

1
2
3
4
5
6
7
8
9
10
11
# 1. 进入主目录下的~/.ssh 目录
$ cd ~/.ssh

# 2. 创建一个 SSH key p.s.[-t 指定密钥类型,默认是 rsa,可以省略;-C 设置注释文字,比如邮箱;-f 指定密钥文件存储文件名]
$ ssh-keygen -t rsa -C "your_email@example.com"

# 3. 添加你的 SSH key 到 github 上面去
$ cat ./id_rsa.pub

# 4. 测试一下该 SSH key
$ ssh -T git@github.com

关于在多个操作系统上设立相同 SSH 公钥的教程,可以查阅 GitHub 上有关 SSH 公钥的向导。

Git 常用命令以及使用场景

Git 三大分区

仓库

1
2
3
4
5
6
7
8
# 1. 在当前目录新建一个 Git 代码库
$ git init

# 2. 新建一个目录, 将其初始化为 Git 代码库
$ git init [project-name]

# [3]. 下载一个项目和它的整个代码历史
$ git clone [url]

增加 / 删除文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 添加指定文件到暂存区
$ git add [file1] [file2] ...

# 2. 添加指定目录到暂存区, 包括子目录
$ git add [dir]

# [3]. 添加当前目录的所有文件到暂存区
$ git add .

# 4. 添加每个变化前, 都会要求确认[对于同一个文件的多处变化, 可以实现分次提交]
$ git add -p

# 5. 删除工作区文件, 并且将这次删除放入暂存区 p.s.[git rm -f [file1]强制删除暂存区某个文件]
$ git rm [file1] [file2] ...

# [6]. 停止追踪指定文件, 但该文件会保留在工作区
$ git rm --cached [file]

# 7. 改名文件, 并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]

代码提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 提交暂存区到仓库区
$ git commit -m [message]

# 2. 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]

# [3]. 提交工作区自上次 commit 之后的变化, 直接到仓库区
$ git commit -a

# [4]. 提交时显示所有 diff 信息
$ git commit -v

# [5]. 使用一次新的 commit, 替代上一次提交[如果代码没有任何新变化, 则用来改写上一次 commit 的提交信息]
$ git commit --amend -m [message]

# 6. 重做上一次 commit, 并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...

分支

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
38
39
40
41
42
43
# 1. 列出所有本地分支
$ git branch

# 2. 列出所有远程分支
$ git branch -r

# [3]. 列出所有本地分支和远程分支
$ git branch -a

# 4. 新建一个分支, 但依然停留在当前分支
$ git branch [branch-name]

# [5]. 新建一个分支, 并切换到该分支 p.s.[git checkout -b [branch] [remote/branch]根据远程分支,创建本地分支]
$ git checkout -b [branch]
$ git checkout -b [branch] [remote/branch]

# 6. 新建一个分支, 指向指定 commit
$ git branch [branch] [commit]

# 7. 新建一个分支, 与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]

# 8. 切换到指定分支, 并更新工作区
$ git checkout [branch-name]

# [9]. 切换到上一个分支
$ git checkout -

# 10. 建立追踪关系, 在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]

# [11]. 合并指定分支到当前分支 p.s.[git merge --no-ff -m "message" [branch]: 合并某分支到当前分支, 可以保存你之前的分支历史]
$ git merge [branch]

# [12]. 选择一个 commit,合并进当前分支 p.s.[选择某一个分支中的一个或几个 commit(s)来进行操作(操作的对象是 commit)]
$ git cherry-pick [commit1] [commit2] ...

# [13]. 删除分支
$ git branch -d [branch-name]

# [14]. 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]

标签

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
# [1]. 列出所有 tag
$ git tag

# 2. 新建一个 tag 在当前 commit p.s.[git tag -a [tag] -m [message]]
$ git tag [tag]

# 3. 新建一个 tag 在指定 commit
$ git tag [tag] [commit]

# 4. 删除本地 tag
$ git tag -d [tag]

# [5]. 删除远程 tag
$ git push origin :refs/tags/[tagName]

# [6]. 查看 tag 信息
$ git show [tag]

# [7]. 提交指定 tag
$ git push [remote] [tag]

# 8. 提交所有 tag
$ git push [remote] --tags

# [9]. 新建一个分支, 指向某个 tag
$ git checkout -b [branch] [tag]

查看信息

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# [1]. 显示有变更的文件
$ git status

# 2. 显示当前分支的版本历史
$ git log

# [3]. 显示 commit 历史, 以及每次 commit 发生变更的文件
$ git log --stat

# 4. 搜索提交历史, 根据关键词
$ git log -S [keyword]

# 5. 显示某个 commit 之后的所有变动, 每个 commit 占据一行
$ git log [tag] HEAD --pretty=format:%s

# 6. 显示某个 commit 之后的所有变动, 其 "提交说明" 必须符合搜索条件
$ git log [tag] HEAD --grep feature

# [7]. 显示某个文件的版本历史, 包括文件改名
$ git log --follow [file]
$ git whatchanged [file]

# [8]. 显示指定文件相关的每一次 diff
$ git log -p [file]

# [9]. 显示过去 5 次提交
$ git log -5 --pretty --oneline

# [10]. 显示所有提交过的用户, 按提交次数排序
$ git shortlog -sn

# [11]. 显示指定文件是什么人在什么时间修改过
$ git blame [file]

# [12]. 显示暂存区和工作区的差异
$ git diff

# [13]. 显示暂存区和上一个 commit 的差异
$ git diff --cached [file]

# 14. 显示工作区与当前分支最新 commit 之间的差异
$ git diff HEAD

# [15]. 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]

# 16. 显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"

# [17]. 显示某次提交的元数据和内容变化
$ git show [commit]

# 18. 显示某次提交发生变化的文件
$ git show --name-only [commit]

# [19]. 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]

# [20]. 显示当前分支的最近几次提交
$ git reflog

远程同步

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
# [1]. 下载远程仓库的所有变动 p.s.[从远程 refs/heads / 命名空间复制所有分支, 并将它们存储到本地的 refs/remotes/origin / 命名空间]
$ git fetch [remote]
# 1.1.1 将远程仓库的 master 分支下载到本地当前 branch 中
$ git fetch orgin master
# 1.1.2 比较本地的 master 分支和 origin/master 分支的差别
$ git log -p master ..origin/master
# 1.1.3 最后进行合并
$ git merge origin/master

# 1.2.1 从远程仓库 master 分支获取最新, 在本地建立 tmp 分支
$ git fetch origin master:tmp
# 1.2.2 将当前分支和 tmp 进行对比
$ git diff tmp
# 1.2.3 合并 tmp 分支到当前分支
$ git merge tmp

# 2. 显示所有远程仓库
$ git remote -v

# 3. 显示某个远程仓库的信息
$ git remote show [remote]

# 4. 增加一个新的远程仓库, 并命名
$ git remote add [shortname] [url]

# [5]. 取回远程仓库的变化,并与本地分支合并
$ git pull [remote] [branch]

# [6]. 上传本地指定分支到远程仓库
$ git push [remote] [branch]

# [7]. 强行推送当前分支到远程仓库, 即使有冲突 p.s.[git push -f [remote] [branch]: 强行用本地仓库覆盖远端仓库, 强制推送是非常不好的行为, 建议禁止使用这个方式]
$ git push [remote] --force

# 8. 推送所有分支到远程仓库
$ git push [remote] --all

撤销

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
38
# 1. 恢复暂存区的指定文件到工作区 p.s.[撤销对工作区修改: 这个命令是以最新的存储时间节点 (add 和 commit) 为参照, 覆盖工作区对应文件 file; 这个命令改变的是工作区]
$ git checkout [file]

# 2. 恢复某个 commit 的指定文件到暂存区和工作区
$ git checkout [commit] [file]

# [3]. 恢复暂存区的所有文件到工作区, 即放弃工作区所有改动
$ git checkout .

# [4]. 重置暂存区的指定文件, 与上一次 commit 保持一致, 但工作区不变 p.s.[git reset HEAD <filename>:取消暂存某个文件]
$ git reset [file]

# 5. 重置暂存区与工作区, 与上一次 commit 保持一致
$ git reset --hard

# [6]. 重置当前分支的指针为指定 commit, 同时重置暂存区, 但工作区不变 p.s.[git reset HEAD:重置暂存区]
$ git reset [commit]

# [7]. 重置当前分支的 HEAD 为指定 commit, 同时重置暂存区和工作区, 与指定 commit 一致 p.s.[git reset --hard HEAD:强制恢复 git 管理的文件夹的內容及状态]
$ git reset --hard [commit]

# 8. 重置当前 HEAD 为指定 commit, 但保持暂存区和工作区不变
$ git reset --keep [commit]

# [9]. 抵消式撤销, 新建一个 commit, 用来撤销指定 commit, 后者的所有变化都将被前者抵消, 并且应用到当前分支 p.s.[git revert [commit]..HEAD: 撤销指定 commit 到当前 HEAD 之间所有的变化]
$ git revert [commit]

# [10]. 暂时将未提交的变化移除, 稍后再移入
$ git stash
# 10.1 将当前 stash 中的内容弹出, 并应用到当前分支对应的工作目录上
$ git stash pop
# 10.2 查看当前 stash 中的内容
$ git stash list
# 10.3 查看堆栈中最新保存的 stash 和当前目录的差异
$ git stash slow

# [11]. 重置当前 HEAD 为指定 commit[hard: 强行合并 - 重置 stage 区和工作目录; soft: 软合并 - 保留工作目录, 并把重置 HEAD 所带来的新的差异放进暂存区; mixed: 混合合并 - 保留工作目录, 并清空暂存区]
$ git reset [--hard|soft|mixed|merge|keep] [commit|HEAD]

Git 三大分区


参考博文

[1]. 深入浅出 Git 教程
[2]. 常用 Git 命令清单
[3]. 图解 Git


Git 在团队中的最佳实践系列


谢谢你长得那么好看,还打赏我!😘
0%