版本控制系统(VCS)
版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统
- 本地版本控制系统(Local VCS)

- 通过在本地计算机上创建一个数据库来保存文件的所有版本
- 需要手动备份和恢复
- 集中式版本控制系统(Centralized VCS)

- 通过在服务器上创建一个数据库来保存文件的所有版本
- 需要手动备份和恢复
- 需要网络连接才能访问版本库
- 需要服务器维护和管理
- 分布式版本控制系统(Distributed VCS)

- 每个开发者的计算机上都有一个完整的版本库
- 可以在本地进行版本控制
Git 是一个分布式版本控制系统,最初由 Linus Torvalds 开发,用于管理 Linux 内核的开发Git 的设计目标是速度快、数据完整性高和支持分布式非线性工作流程
Git 更像是把数据看作是对小型文件系统的一系列快照,而不是像其他版本控制系统那样记录文件的差异,这使得 Git 在处理大文件时更加高效
在 Git 中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引
你执行的 Git 操作,几乎只往 Git 数据库中 添加 数据 你很难使用 Git 从数据库中删除数据,也就是说 Git 几乎不会执行任何可能导致文件不可恢复的操作 因此可以尽情做各种尝试,而没有把事情弄糟的危险
主要特点:
- 几乎所有操作都是本地执行
- 保证完整性(SHA-1 哈希算法)
- 操作可追溯且可恢复
- 文件的三种状态:已修改、已暂存、已提交
初始化设置(git config)
在使用 Git 之前,需要先进行一些基本的设置:
# 配置用户信息
git config --global user.name "Your Name"
git config --global user.email "email@example.com"
# 查看配置
git config --list
# 获取帮助
git help <命令>
git <命令> --help
可选参数
--global
表示全局配置,省略参数表示只对当前仓库生效
配置姓名和邮箱是为了方便识别是谁的提交而备注的信息,不起到登录注册的作用
配置文件位置:
/etc/gitconfig
:系统配置~/.gitconfig
:用户配置.git/config
:仓库配置
优先级:仓库配置 > 用户配置 > 系统配置
获取 Git 仓库(Repository)
获取 Git 仓库有两种方式:
- 克隆一个现有的 Git 仓库
git clone <url> #克隆远程仓库
可在
<url>
后跟上新的文件名,表示重命名
- 在本地创建一个新的 Git 仓库
git init #当前目录下创建
该命令将创建一个名为
.git
的隐藏目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的骨干
核心概念
工作区域
Git 的工作区域分为三个部分:
-
工作目录(Working Directory)
-
项目的实际文件所在的目录
-
可以直接修改的区域
-
-
暂存区(Staging Area)
- 位于 .git 目录中的 index 文件
- 临时保存你的改动
-
Git 仓库(Repository)
- .git 目录中的数据库
- 保存项目元数据和对象数据库
暂存区也叫缓冲区,存在意义是实现更灵活的提交
文件状态
你工作目录下的每一个文件都不外乎这两种状态:已跟踪 或 未跟踪
已跟踪的文件又可以分为三种状态:已修改、已暂存、已提交
未跟踪 ───────► 已暂存 ───────► 已提交
(Untracked) (Staged) (Committed)
▲ ▲
│ │
└──── 已修改 ──┘
(Modified)
- 未跟踪(Untracked):新创建但还没被 git 管理的文件
- 已修改(Modified):修改了但是没有保存到暂存区的文件
- 已暂存(Staged):修改后已经保存到暂存区的问件
- 已提交(Committed):把暂存区的文件提交到本地仓库后的状态
可以通过 status
命令查看文件的状态
git status #查看状态
status
可跟上-s
或--short
参数,你将得到一种格式更为紧凑的输出
基本的 Git 流程如下:
- 在工作区中修改文件
- 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区
- 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录
常用操作
跟踪 / 暂存文件(add)
git add #从工作区送往暂存区
运行之后可通过 git status
查看状态

git add
命令可以跟上文件名、目录名、通配符等参数
git add . #添加当前目录下的所有文件
git add <file> #添加单个文件
git add *.png #添加当前目录下所有 png 文件
git add -u #添加已跟踪的文件
git add -A #添加所有文件,包括未跟踪的文件
一些特殊文件(.gitignore)
如果你不希望某些文件被 Git 跟踪,可以在项目根目录下创建一个名为 .gitignore
的文件,并在其中列出要忽略的文件或目录
常见的特殊文件还有:
文件名 | 作用 |
---|---|
.git | git 仓库的元数据和对象数据库 |
.gitignore | 忽略文件,里面的列举的文件不需要提交到仓库中 |
.gitattributes | 指向当前分支的指针 |
.gitkeep | 使空目录被提交到仓库 |
.gitmodules | 记录子模块的信息 |
.gitconfig | 记录仓库的配置信息 |
Github 官方给的常见参考模板 https://github.com/github/gitignore
查看提交差异(diff)
git diff #不加参数默认比较工作区和暂存区内容
比较差异更常用的是图形化界面:
git difftool
或者 VScode 等 GUI 软件

提交更新(commit)
每次准备提交前,先用 git status
看下,你所需要的修改的文件是不是都已暂存起来了, 然后再运行提交命令
修改不仅仅表示文件中内容的改动,而是包括增加、删除、改动等一系列操作
git commit -m "提交信息"
如果不加
-m
参数,Git 会打开一个文本编辑器让你输入提交信息
可选参数
-a
会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过git add
步骤
删除文件(rm)
本地删除、暂存、提交一条龙
rm xxx
git add .
git commit
或者
git rm #将 xxx 从工作区和暂存区同时删除
git commit
--cached
参数可以仅删除暂存区
重命名文件(mv)
要在 Git 中对文件改名,可以这么做:
git mv file_from file_to
其实,运行 git mv
就相当于运行了下面三条命令:
mv README.md README
git rm README.md
git add README
但是一般也是在 vscode 中直接对文件重命名就行
查看历史(log)
Git 记录了每次提交的历史信息,无论是添加、修改还是删除或者是提交的时间、作者、提交信息等都可以通过 git log
命令查看

git log # 查看提交历史
git log --oneline # 单行显示历史
git reflog # 查看引用日志
版本回退 / 撤销(reset/amend)
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了 此时,可以运行带有 --amend
选项的提交命令来重新提交:
git commit --amend -m "新的提交信息" #修改上次提交信息
如果想重置为之前的某个提交,并且删除所有之后的提交 可以运行 reset
git reset <commit-id> #重置到某个提交
参数 | 作用解释 | 使用场景 |
---|---|---|
--hard | 参数表示重置工作区和暂存区,并且清空两者改动 | 完全放弃这次的改动 |
--soft | 参数表示重置暂存区,但不清空改动 | 需要合并多个提交为一个版本 |
--mixed | 参数表示重置工作区(默认),且清除暂存区的改动 | 和 --soft 异曲同工 |
如果不小心使用了
--hard
可以用git reflog
和git reset --hard
来重置到之前的一次操作
除了 <commit-id>
还可以使用 HEAD
来表示当前版本
git reset --soft HEAD^ #软重置到上一个版本
HEAD
(指针)表示指向的当前版本,HEAD^
表示上一个,HEAD~4
表示上上上上一个版本
远程仓库(remote repo)
git remote add <remote-name> <remote-url> #添加远程仓库
远程仓库可以不止有一个,可以是多个人或者多台 git 服务器的仓库地址
git remote -v #查看远程仓库

git remote rm <remote-name> #删除远程仓库
git remote rename <old-name> <new-name> #重命名远程仓库
git remote set-url <remote-name> <new-url> #修改远程仓库地址
git push -u <remote-name> <branch-name> #将本地仓库推送至远程
git fetch <remote> #将远程同步至本地仓库
git fetch
和 git pull
的区别:
git fetch
只是将远程仓库的更新下载到本地,但不会自动合并到当前分支git pull
会自动下载远程仓库的更新,并将其合并到当前分支
打标签(tag)
Git 可以给仓库历史中的某一个提交打上标签,以示重要
git tag #列出标签
可能有的项目标签数量很多,如果只对 1.8.5 系列感兴趣,可以运行
git tag -l "v1.8.5*" #列出所有以 v1.8.5 开头的标签

git tag -a v1.0.0 -m "这是v1.0.0" #给当前提交打上 v1.0.0 的标签
git tag -a v1.0.0 <commit-id> -m "这是v1.0.0" #给具体提交打上 v1.0.0 的标签
默认的 git push 不会将 tag 推送出去,需要使用
git push origin <tagname> #将某个标签共享出去
或者:
git push origin --tags #把所有不在远程仓库服务器上的标签全部传送到那里
如果需要删除标签:
git tag -d v1.4 #删除本地的标签
但是上述命令不会删除远程仓库的标签,必须更新一次远程仓库
git push origin --delete <tagname>
切换至某个标签
git checkout 2.0.0
但是如果你需要在 2.0.0 上进行更改然后提交,标签号不会发生变化。因此,如果你需要进行更改,比如你要修复旧版本中的错误,那么通常需要创建一个新的分支:
git checkout -b version2 v2.0.0
别名(alias)
Git 允许你为常用的命令创建别名,以便更方便地使用
git config --global alias.<alias-name> <command>
例如:
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
这样你就可以使用 git co
来代替 git checkout
,使用 git br
来代替 git branch
,以此类推
分支
上文提到 Git 保存的不是文件的变化或者差异,而是一系列不同时刻的快照。
在提交操作时,Git 会保存一个提交对象,该提交对象会包含一个指向暂存内容快照的指针,还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针
比如我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件,然后我们进行首次提交:
git add README test.rb LICENSE
git commit -m 'The initial commit of my project'
现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象 (记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)

做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针

Git 的分支,其实本质上仅仅是指向提交对象的可变指针, Git 的默认分支名字是 master。
在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。master 分支会在每次提交时自动向前移动

分支存在的意义是为了让你可以在不同的分支上进行不同的工作,比如说你正在开发一个新功能,你可以在一个新的分支上进行开发,而不会影响到主分支上的代码
当你完成了新功能的开发,并且测试通过后,你可以将这个分支合并到主分支上,这样主分支就会包含这个新功能的代码
分支管理(branch)
git branch #查看所有本地分支,当前分支前有一个 *
参数 -a 查看所有分支,-r 查看远程分支

git branch testing #创建一个名叫 testing 的分支
这会在当前所在的提交对象上创建一个新的指针

HEAD
指向的是当前分支的特殊指针,可以将 HEAD
想象为当前分支的别名
git branch
这是创建分支的命令,但并不会自动切换到这个分支上,因此当前分支还是 master
,而 HEAD
仍然指向 master
分支

可以使用 switch
或 checkout
命令来切换分支:
git switch testing #切换当前分支

这样 HEAD
就指向 testing
分支了
此时你进行提交操作时,HEAD
分支(testing)随着提交操作自动向前移动,而 main
分支却没有

那现在我们再切换回 main
分支
git switch main #切换回 main 分支
会发现之前在 testing
分支上所做的修改并没有出现在 main
分支上
这条命令做了两件事 一是使 HEAD
指回 main
分支,二是将工作目录恢复成 main
分支所指向的快照内容
分支作用一般的场景是
- 开发某个网站
- 为实现某个新的用户需求,创建一个分支
- 在这个分支上开展工作
正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补 你将按照如下方式来处理:
- 切换到你的线上分支 main(生产分支)
- 为这个紧急任务新建一个分支,并在其中修复它
- 在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支
- 切换回你最初工作的分支上,继续工作
分支整合(merge & rebase)
当在新的分支上完成了某个功能的开发后,就可以将这个分支合并到主分支上,让主分支也拥有这个功能
一般实现这个功能会有两种方式:merge
和 rebase
git merge <branch-name> #将分支合并到当前分支中

git rebase <branch-name> #从分叉点开始直线型合并

- merge 不会破坏原分支的提交历史,但会产生分支线,并且多出一条提交记录
- rebase 不会增加额外的提交记录,分支图为线性,但会改变提交历史,不应该在共享的主分支上使用
变基的风险:
如果这次提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。
如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你
既然你的修改已经合并进来了,就不再需要那个新的分支了。现在你可以在任务追踪系统(issue tracker)中关闭此项任务,并删除这个分支
git branch -d <branch-name> #删除已经合并的分支
git branch -D <branch-name> #强制删除分支
可见分支的好处是可以在不同的分支上进行不同的工作,而不会互相影响,但是当分支过多合并的时候可能会出现冲突(两个分支上都修改了同一行代码)
分支冲突(conflict)
当有人在不同分支同时更改了同一个代码 git 不知道保留哪一个的情况下被叫作冲突,此时需要手动介入处理
当运行
git merge <branch-name>
后 git 会等待手动修改冲突文件,修改之后直接暂存和提交后会自动 merge
如果需要中止等待合并可以运行
git merge --abort
Git 交互练习
免费
https://learngitbranching.js.org/
https://www.w3schools.com/git/exercise.asp
付费
https://www.codecademy.com/learn/learn-git
工作流(Flow)
Git 工作流是指在使用 Git 进行版本控制时,团队成员之间如何协作、如何管理代码的流程和规范
常见的工作流有:
- Git Flow
- GitHub Flow
以下是 Github Flow 的工作流图(趋于稳定分支的流水线(“silo”)视图):

具体可以看我的这篇文章:标准化项目的 GitHub Flow 工作流标准化项目的 GitHub Flow 工作流