Git 版本控制工具详解
前言
原文来自我的个人博客
1. 认识版本控制工具
1.1 什么是版本控制?
版本控制的英文是 Version Control
是维护工程蓝图的标准作法,能追踪工程蓝图从诞生一直到定案的过程
版本控制也是一种软件工程技巧,借此能在软件开发的过程中,确保由不同人所编辑的同一程序文件都得到同步;
简单来说,版本控制在软件开发中,可以帮助程序员进行代码的追踪、维护、控制等等一系列的操作。

1.2 版本控制有什么用?
对于我们日常开发,我们常常面临如下一些问题,通过版本控制可以很好的解决:
1️⃣ 能够存储管理不同版本:
-
一个项目会不断进行版本的迭代,来修复之前的一些问题、增加新的功能、需求,甚至包括项目的重构;
-
如果我们通过手动来维护一系列的项目备份,简直是一场噩梦;
2️⃣ 能够备份维护重大版本:
- 对于很多重大的版本,我们会进行备份管理;
3️⃣ 能够恢复之前的项目版本:
- 当我们开发过程中发生一些严重的问题时,想要恢复之前的操作或者回到之前某个版本;
4️⃣ 能够记录项目的点点滴滴:
- 如果我们每一个功能的修改、bug的修复、新的需求更改都需要记录下来,版本控制可以很好的解决;
5️⃣ 能够合并多人开发的代码:
- 项目中通常都是多人开发,将多人代码进行合并,并且在出现冲突时更好的进行处理;
1.3 版本控制的历史
1️⃣ 版本控制的史前时代(没有版本控制):
- 人们通常通过文件备份的方式来进行管理,再通过
diff命令来对比两个文件的差异;
2️⃣ CVS(Concurrent Versions System)
第一个被大规模使用的版本控制工具,诞生于 1985 年;
- 由荷兰阿姆斯特丹
VU大学的Dick Grune教授实现的,也算是SVN的前身(SVN的出现就是为了取代CVS的)。
3️⃣ SVN(Subversion)
-
因其命令行工具名为
SVN因此通常被简称为SVN; -
SVN由CollabNet公司于2000年资助并发起开发,目的是取代CVS,对CVS进行了很多的优化; -
SVN和CVS一样,也属于集中式版本控制工具; -
SVN在早期公司开发中使用率非常高,但是目前已经被Git取代;
4️⃣ Git(Linus的作品)
- 早期的时候,
Linux社区使用的是BitKeeper来进行版本控制; 但是因为一些原因,BitKeeper想要收回对Linux社区的免费授权; - 于是
Linus用了大概一周的时间,开发了Git用来取代BitKeeper; Linus完成了Git的核心设计,在之后Linus功成身退,将Git交由另外一个Git的主要贡献者Junio C Hamano来维护;
1.4 集中式版本控制
CVS 和 SVN 都是属于集中式版本控制系统(Centralized Version Control Systems,简称 CVCS)
- 它们的主要特点是单一的集中管理的服务器,保存所有文件的修订版本;
- 协同开发人员通过客户端连接到这台服务器,取出最新的文件或者提交更新;

- 这种做法带来了许多好处,特别是相较于老式的本地管理来说,每个人都可以在一定程度上看到项目中的 其他人正在做些什么
- 但是集中式版本控制也有一个核心的问题: 中央服务器不能出现故障:
- 如果宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作;
- 如果中心数据库所在的磁盘发生损坏,又没有做恰当备份,毫无疑问你将丢失所有数据;
1.5 分布式版本控制
Git是属于分布式版本控制系统(Distributed Version Control System,简称DVCS)-
客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录;
-
这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何 一个镜像出来的本地仓库恢复;
-
因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份;
-

2. Git 的配置
2.1 Git 的安装
电脑上要想使用 Git,需要先对 Git 进行安装,直接去Git的官网根据自己的操作系统下载 Git 即可;

Bash – CMD – GUI 区别?
在 Windows 中Git 的安装完成后,可以在开始菜单中看到 Git 的三个启动图标 Git Bash、Git CMD(Deprecated)、Git GUI,那个这三个的区别分别是什么呢?
-
Bash:Unix shell的一种,Linux与Mac OS X都将它作为默认shell。Git Bash就是一个shell,是Windows下的命令行工具,可以执行Linux命令;Git Bash是基于CMD的,在CMD的基础上增添一些新的命令与功能;- 建议在使用的时候,用
Bash更加方便;
-
Git CMD- 命令行提示符(
CMD)是Windows操作系统上的命令行解释程序; - 当你在
Windows上安装Git并且习惯使用命令行时,可以使用cmd来运行git命令;
- 命令行提示符(
-
Git GUI- 基本上针对那些不喜欢黑屏(即命令行)编码的人;
- 它提供了一个图形用户界面来运行
git命令;
2.2 Git 的配置分类
Git 自带一个 git config 的工具来帮助设置控制 Git 外观和行为的配置变量。 这些变量存储在三个不同的位置:
-
/etc/gitconfig文件: 包含系统上每一个用户及他们仓库的通用配置。 如果在执行git config时带上--system选项,那么它就会读写该文件中的配置变量。 (由于它是系统配置文件,因此你需要管理员或超级用户权限来修改它。) -
~/.gitconfig或~/.config/git/config文件:只针对当前用户。 你可以传递--global选项让Git读写此文件,这会对你系统上 所有 的仓库生效。 -
当前使用仓库的
Git目录中的config文件(即.git/config):针对该仓库。 你可以传递--local选项让Git强制读写此文件,虽然默认情况下用的就是它。 (当然,你需要进入某个Git仓库中才能让该选项生效。)
每一个级别会覆盖上一级别的配置,所以 .git/config 的配置变量会覆盖 /etc/gitconfig 中的配置变量。
2.3 Git 的配置选项
安装完 Git 之后,要做的第一件事就是设置你的用户名和邮件地址。 这一点很重要,因为每一个 Git 提交都会使用这些信息,它们会写入到你的每一次提交中,不可更改:
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
强调:如果使用了
--global选项,那么该命令只需要运行一次,因为之后无论你在该系统上做任何事情,Git都会使用那些信息;
检测当前的配置信息: git config --list
$ git config --list
user.name=John Doe
user.email=johndoe@example.com
color.status=auto
color.branch=auto
color.interactive=auto
color.diff=auto
...
2.4 Git 的别名(Alias)
Git 并不会在你输入部分命令时自动推断出你想要的命令
如果不想每次都输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名。
coder@coder ~ % git config --global alias.co checkout
coder@coder ~ % git config --global alias.br branch
coder@coder ~ % git config --global alias.cm commit
coder@coder ~ % git config --global alias.st status
例如,我在上面配置了 st 别名,那么我下次想检查 git 状态就可以直接执行 git st 了
coder@coder demo % git st
On branch dev
Your branch is ahead of 'origin/dev' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
3. Git 基础
3.1 获取Git仓库 – git init/git clone
如果我们要用 Git 来管理源代码,那么我们就需要有一个 Git 仓库。
通常有两种获取 Git 项目仓库的方式:
-
方式一:初始化一个Git仓库,并且可以将当前项目的文件都添加到Git仓库中(目前很多的脚手架在创建项目时都会默认创建一个Git仓库);
git init
该命令将创建一个名为
.git的子目录,这个子目录含有你初始化的Git仓库中所有的必须文件,这些文件是Git仓库的核心。但是,在这个时候,我们仅仅是做了一个初始化的操作,你的项目里的文件还没有被跟踪; -
方式二:从其它服务器 克隆
(clone)一个已存在的 Git 仓库(第一天到公司通常我们需要做这个操作);从
Git远程仓库
git clone https://github.com/coderyjw/jw-ui.git
3.2 文件的状态划分
Git 仓库 将文件状态划分为 未跟踪 和 已跟踪
- 未跟踪:未跟踪的文件就是没有添加到
Git仓库管理的文件,默认情况下都是未跟踪文件,我们需要通过 add命令 来操作; - 已跟踪:添加到
Git仓库管理的文件处于已跟踪状态,Git可以对其进行各种跟踪管理;
已跟踪的文件又可以进行细分状态划分:
- staged:暂缓区中的文件状态;
- Unmodified:
commit命令,可以将staged中文件提交到Git仓库 - Modified:修改了某个文件后,会处于
Modified状态;

在工作时,你可以选择性地将这些修改过的文件放入暂存区,然后提交所有已暂存的修改,如此反复;
3.3 检测文件的状态 - git status
我们在有 Git 仓库的目录下新建一个文件,查看文件的状态:
git status

Untracked files:未跟踪的文件- 未跟踪的文件意味着
Git在之前的提交中没有这些文件; Git不会自动将之纳入跟踪范围,除非你明明白白地告诉它“我需要跟踪该文件”;
- 未跟踪的文件意味着
我们也可以查看更加简洁的状态信息:
git status --short git status --s
左栏指明了暂存区的状态,右栏指明了工作区的状态;
3.4 文件添加到暂存区 – git add
-
跟踪新文件命令:
git add aaa.js-
使用命令
git add开始跟踪一个文件。
-
-
跟踪修改的文件命令:
-
如果我们已经跟踪了某一个文件,这个时候修改了文件也需要重新添加到暂存区中;

-
-
此外,可以通过
git add .将所有的文件添加到暂存区中:
3.5 git 忽略文件
- 一般我们总会有些文件无需纳入
Git的管理,也不希望它们总出现在未跟踪文件列表。- 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等;
- 我们可以创建一个名为
.gitignore的文件 ,列出要忽略的文件的模式;
- 在实际开发中,这个文件通常不需要手动创建,在必须的时候添加自己的忽略内容即可;
- 比如下面是创建的
Vue项目自动创建的忽略文件:- 包括一些不需要提交的文件、文件夹;
- 包括本地环境变量文件;
- 包括一些日志文件;
- 包括一些编辑器自动生成的文件;

3.6 文件更新提交 – git commit
现在的暂存区已经准备就绪,可以提交了。
-
每次准备提交前,先用
git status看下,你所需要的文件是不是都已暂存起来了; -
再运行提交命令
git commit; -
可以在
commit命令后添加-m选项,将提交信息与命令放在同一行;git commit –m "提交信息"
-
**如果我们修改文件的
add操作,加上commit的操作有点繁琐,那么可以将两个命令结合来使用:git commit -a -m "修改了bbb文件"**
3.7 Git 校验和
Git 中所有的数据在存储前都计算校验和,然后以 校验和 来引用。
Git用以计算校验和的机制叫做SHA-1散列(hash,哈希);- 这是一个由 40 个十六进制字符(0-9 和 a-f)组成的字符串,基于 Git 中文件的内容或目录结构计算出来;

3.8 查看提交的历史 – git log
-
提交了若干更新,又或者克隆了某个项目之后,有时候我们想要查看一下所有的历史提交记录。
-
这个时候我们可以使用
git log命令:- 不传入任何参数的默认情况下,
git log会按时间先后顺序列出所有的提交,最近的更新排在最上面; - 这个命令会列出每个提交的
SHA-1校验和、作者的名字和电子邮件地址、提交时间以及提交说明;
- 不传入任何参数的默认情况下,
-
git log
-
git log --pretty=oneline
-
git log --pretty=oneline --graph
3.9 版本回退 – git reset
- 如果想要进行版本回退,我们需要先知道目前处于哪一个版本:
Git通过HEAD指针记录当前版本。-
HEAD是当前分支引用的指针,它总是指向该分支上的最后一次提交; -
理解
HEAD的最简方式,就是将它看做 该分支上的最后一次提交 的快照;
-
- 我们可以通过
HEAD来改变Git目前的版本指向:- 上一个版本就是
HEAD^,上上一个版本就是HEAD^^; - 如果是上
1000个版本,我们可以使用HEAD~1000; - 我们可以指定某一个
commit id;
- 上一个版本就是
git reset --hard HEAD^
git reset --hard HEAD~1000
git reset --hard 2d44982
3.10 Git 标签
像其他版本控制系统(VCS)一样,Git 可以给仓库历史中的某一个提交打上标签,以示重要。 比较有代表性的是人们会使用这个功能来标记发布结点( v1.0 、 v2.0 等等)。
列出标签 git tag 可带上可选的 -l 选项 --list
$ git tag
v1.0
v2.0
创建标签
Git 支持两种标签:轻量标签 (lightweight) 与附注标签(annotated);
附注标签:通过 -a 选项,并且通过 -m 添加额外信息;
git tag v1.0
git tag -a v.1.1 -m "附注标签"
默认情况下,git push 命令并不会传送标签到远程仓库服务器上。 在创建完标签后必须显式地推送标签到共享服务器上,当其他人从仓库中克隆或拉取,他们也能得到那些标签;
git push origin v1.0
git pish origin --tags
删除标签
要删除本地仓库上的标签,可以使用命令 git tag -d <tagname>
git tag -d v1.1
Deleted Tag 'v1.1' (was 9d76105)
要删除远程的 tag 可以通过git push <remote> –delete <tagname>
git push origin --delete v1.1
检出标签
如果想查看某个标签所指向的文件版本,可以使用 git checkout 命令;
通常在检出 tag 的时候还会创建一个对应的分支(分支后续了解);
git checkout v1.0
Note: switching to 'v1.0'
4. 远程仓库
4.1 什么是远程仓库
- 什么是远程仓库(
Remote Repository)呢?- 目前我们的代码是保存在一个本地仓库中,也就意味着我们只是在进行本地操作;
- 在真实开发中,我们通常是多人开发的,所以我们会将管理的代码共享到远程仓库中;
- 那么如何创建一个远程仓库呢?
- 远程仓库通常是搭建在某一个服务器上的(当然本地也可以,但是本地很难共享);
- 所以我们需要在
Git服务器上搭建一个远程仓库;
- 目前我们有如下方式可以使用
Git服务器:- 使用第三方的
Git服务器:比如GitHub、Gitee、Gitlab等等; - 在自己服务器搭建一个
Git服务;
- 使用第三方的

4.2 远程仓库的验证
目前比较流行的远程仓库:GitHub、Gitee、自己搭建Gitlab
对于私有的仓库我们想要进行操作,远程仓库会对我们的身份进行验证。如果没有验证,任何人都可以随意操作仓库是一件非常危险的事情;
目前 Git 服务器验证手段主要有 HTTP 和 SSH 两种
- 基于
HTTP的凭证存储(Credential Storage)
因为本身 HTTP 协议是无状态的连接,所以每一个连接都需要用户名和密码。如果每次都这样操作,那么会非常麻烦。幸运的是,Git 拥有一个凭证系统来处理这个事情。
下面有一些 Git Crediential 的选项:
- 选项一:默认所有都不缓存。 每一次连接都会询问你的用户名和密码;
- 选项二:
cache模式会将凭证存放在内存中一段时间。 密码永远不会被存储在磁盘中,并且在15分钟后从内存中清除; - 选项三:
store模式会将凭证用明文的形式存放在磁盘中,并且永不过期; - 选项四:如果你使用的是
Mac,Git还有一种osxkeychain模式,它会将凭证缓存到你系统用户的钥匙串中(加密的); - 选项五:如果你使用的是
Windows,你可以安装一个叫做Git Credential Manager for Windows” 的辅助工具;
如果你在
2.1中按照默认的步骤安装 Git,那么这个工具是会默认有的。
- 基于
SSH的密钥
Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。
SSH以非对称加密实现身份验证。
-
其中一种方法是使用自动生成的公钥-私钥对来简单地加密网络连接,随后使用密码认证进行登录。
-
而更常见的方法是人工生成一对公钥和私钥,通过生成的密钥进行认证,公钥需要放在待访问的电脑之中,而对应的私钥需要由用户自行保管;这样就可以在不输入密码的情况下登录;
选择下面一条命令生成公钥和私钥
ssh-keygen -t rsa -b 2048 -C “your email"
ssh-keygen -t ed25519 -C "your email"
将生成的公钥复制到远程服务器的 SSH公钥 中
4.3 远程仓库的交互
- 从远程仓库clone代码:将存储库克隆到新创建的目录中;
git clone https://github.com/coderyjw/jw-ui.git
-
将代码
push到远程仓库:将本地仓库的代码推送到远程仓库中;默认情况下是将当前分支(比如master)
push到origin远程仓库的;
git push
git push origin master
-
从远程仓库
fetch代码:从远程仓库获取最新的代码- 默认情况下是从
origin中获取代码;
- 默认情况下是从
git fetch
git fetch origin
- 获取到代码后默认并没有合并到本地仓库,我们需要通过
merge来合并;
git merge
- 从远程仓库
pull代码:上面的两次操作有点繁琐,我们可以通过一个命令来操作 远程仓库的交互
git pull
git fetch + git merge(rebase)
4.4. Git 操作流程图
Git 操作的流程图如下:
- 下图有一份远程仓库
Remote Repo,Alice和Bob通过git clone命令将远程仓库克隆到他们各自本地的电脑仓库上。 (Alice‘s Local Repo 和 Bob‘s Local Repo) Alice在本地文件夹Working Directory新建一份文件,此时文件处于未跟踪状态Alice通过git add命令添加到Index索引中跟踪文件,此时文件处于已跟踪staged状态。Alice修改文件,进入Modified状态, 再执行git add命令进入staged状态Alice多次修改并且git add后,执行git commit命令,将staged中文件提交到.git Directory,此时进入Unmodified状态Alice执行git push命令 将本地仓库代码提交到远程仓库Bob执行git pull命令,拉取远程仓库最新的代码到本地- 接下来
Bob也可以按照 1-6 的步骤修改文件提交代码这样持续开发下去
5. 分支
5.1 创建分支
Git 创建一个新分支就只是创建了一个可以移动的新的指针
比如创建一个 testing 分支, 需要使用 git branch 命令
git branch testing

5.2 切换分支
Git 通过一个名为 HEAD 的特殊指针知道当前在哪一个分支上,可以通过 git checkout 命令切换分支。

通常我们会想创建一个新分支后立即切换过去。这可以用 git checkout -b <newbranchname> 一条命令搞定;
5.3 合并分支
实际场景:
- 开发某个项目,在默认分支
master上进行开发; - 实现项目的功能需求,不断提交;
- 并且在一个大的版本完成时,发布版本,打上一个
tag v1.0.0; - 继续开发后续的新功能,正在此时,突然接到一个电话说有个很严重的问题需要紧急修补,切换到
tag v1.0.0的版本,并且创建一个分支hotfix; - 在分支上开发、修复
bug - 当完成要做的工作后,重新打上一个新的
tag v1.0.1; - 切换回
master分支,但是这个时候master分支也需要修复刚刚的bug - 最后将
master分支和hotfix分支进行合并;

5.4 查看和删除分支
如果我们希望查看当前所有的分支,可以通过以下命令:
git branch # 查看当前所有的分支
git branch –v # 同时查看最后一次提交
git branch --merged # 查看所有合并到当前分支的分支
git branch --no-merged # 查看所有没有合并到当前分支的分支
如果某些已经合并的分支我们不再需要了,那么可以将其移除掉:
git branch –d hotfix # 删除当前分支
git branch –D hotfix # 强制删除某一个分支
5.5 远程分支的管理
推送分支到远程
git push <remote> <branch>
跟踪远程分支
克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支;
如果检出的分支(a)不存在且(b)刚好只有一个名字与之匹配的远程分支,那么 Git 就会创建一个跟踪分支;
git checkout --track <remote>/<branch> # 设置跟踪分支
git checkout <branch>
删除远程分支
如果某一个远程分支不再使用,我们想要删除掉,可以运行带有 --delete 选项的 git push 命令来删除一个远程分支。
git push origin --delete <branch>
5.6 Git rebase
在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase。

什么是 rebase ?
在上面的图例中,你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次;
- 在
Git中,这种操作就叫做 变基(rebase); - 你可以使用
rebase命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样; rebase这个单词如何理解呢?- 我们可以将其理解成改变当前分支的
base; - 比如在分支
experiment上执行rebase master,那么可以改变experiment的base为master
- 我们可以将其理解成改变当前分支的
git checkout experiment
git rebase master
rebase 的原理
rebase的原理是首先找到这两个分支(即当前分支experiment、变基操作的目标基底分支master) 的最近共同祖先C2;- 然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件;
- 然后将当前分支指向目标基底
C3; - 最后以此将之前另存为临时文件的修改依序应用;
再次执行 master 上的合并操作
git checkout master
git merge experiment

rebase 和 merge 的选择
merge 用于记录 git 的所有历史,那么分支的历史错综复杂,也全部记录下来
rebase 用于简化历史记录,将两个分支的历史简化,整个历史更加简洁
了解了rebase的底层原理,就可以根据自己的特定场景选择merge或者rebase。
注意:
rebase有一条黄金法则:永远不要在主分支上使用rebase
如果在
main上面使用rebase,会造成大量的提交历史在main分支中不同;而多人开发时,其他人依然在原来的
main中,对于提交历史来说会有很大的变化;
6. Git常见命令速查表

转载自:https://juejin.cn/post/7198775822697119802