前言
原文来自我的个人博客
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 "提交信息"`**
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2b4894e100e04418af023271bdd3576e~tplv-k3u1fbpfcp-zoom-1.image)
- **如果我们修改文件的
add
操作,加上commit
的操作有点繁琐,那么可以将两个命令结合来使用:git commit -a -m "修改了bbb文件"**
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ba0803d34d3d4d379eb82c7ff58e234a~tplv-k3u1fbpfcp-zoom-1.image)
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
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/077d6406164245e88549db0ad5cd3621~tplv-k3u1fbpfcp-zoom-1.image)
3.9 版本回退 – git reset
如果想要进行版本回退,我们需要先知道目前处于哪一个版本:
Git
通过HEAD指针
记录当前版本。HEAD
是当前分支引用的指针,它总是指向该分支上的最后一次提交;- 理解
HEAD
的最简方式,就是将它看做 该分支上的最后一次提交 的快照;
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e632341dc7f54fd6befc8066ee5bb748~tplv-k3u1fbpfcp-zoom-1.image)
我们可以通过
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
中,对于提交历史来说会有很大的变化;