Git是一个强大的版本控制系统,可以记录、对比、云同步、回退、撤销、还原文件的改动。
原文链接: https://jswyll.com/note/git/
为什么要使用Git?
“项目需求有变,回到半个月前的样子!”
“今天现场测试的版本不稳定,前天不是好好的吗,这两天改了什么?能不能用之前的版本测试?测完再研究现在这个版本的问题?”
“之前xx版本的功能不要了,能不能只去掉那个功能而保留后来的功能?”
“删掉的功能又需要了,能不能恢复回来?”
“发现紧急BUG,请暂停当前的新改动,先修复BUG。”
“改一下论文,改完同步给老师,让老师知道你改了哪里。”
“电脑坏了,上个月的版本发给小伙伴有备份,但是忘了这个月改了什么了!”
每次改了文件把整个工程的文件复制出来作为备份?嗯,这是一个粗暴且有效的办法,但不便于管理。
一些教程
菜鸟教程 - 《学习Git》[1]
廖雪峰 - 《浅显易懂的Git教程》[2]
Git官方中文教程[3]
码云 - Git 知识大全[4]
工作区(workspace): 电脑当前的目录、文件
暂存区(stage/index): git提交到版本库之前暂存的地方
本地仓库(repository): 本地版本库,记录着每个版本之间的文件差异
远程仓库(remote): 远程版本库
分支(branch): 使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作
git init
初始化。创建版本库(默认分支为master
),生成一个隐藏的.git
目录,版本库的记录存在里面,初始化后才可以跟踪当前目录(版本库根目录)和子路径下的文件。
git add 文件路径
添加文件到暂存区。可以使用git add .
将除忽略跟踪以外的文件都添加到暂存区。
git commit -m "备注"
将暂存区的文件变化提交到版本库。每提交一次,就生成了一个版本,记录在.git
目录下的文件里。添加-a
参数可以将全部已跟踪的文件的提交,省去git add 文件路径
的步骤,但不会提交未跟踪过的文件。
git push 远程主机名 本地分支名:远程分支名
将本地版本库推送到远程版本库。远程和本地的分支一样时可以忽略:远程分支名
。
git pull
拉取远程版本库到本地版本库,并合并文件变化到工作区。
Git工作流程[5]
图1 Git工作流程
Note
可以“无视”暂存区的功能,将
git add
和git commit
看作一个操作,只管工作区 - 本地仓库 - 远程仓库
的关系。
Note
在命令行里,复制和粘贴的快捷键分别是
Ctrl+Insert
和Shift+Insert
,按TAB键进行命令补全,按↑、↓查看历史命令。 输入git help Git指令
可以查看该指令的完整说明手册。
下载安装Git[6] 。
进入命令行模式。打开工作区文件夹,右键选择Git Bash Here
。
图2 进入命令行模式
|
git config --global user.name "姓名" |
|
git config --global user.email 邮箱 |
代码段1
git init
初始化为Git仓库。
编写.gitignore
文件来忽略跟踪当前目录下不需要跟踪的文件。
在仓库根目录下新建名为.gitignore
(没有其它后缀名)的文件,编写规则可以忽略跟踪指定的文件,这个功能可以用来指示Git忽略编译生成的中间或输出文件。以文本方式编辑,多条规则分行写,以#
开头的行是注释。
忽略文件
|
# 忽略所有.o文件 |
|
*.o |
|
|
|
# 忽略JLinkLog.txt |
|
JLinkLog.txt |
|
|
|
# 忽略所有名称中含.uvguix的文件 |
|
*.uvguix* |
代码段2
忽略文件夹
|
# 忽略settings文件夹下的所有文件 |
|
settings/ |
|
|
|
# 忽略build文件夹下的所有文件(可以不加最后的斜杠/) |
|
build |
代码段3
在已忽略文件中不忽略指定文件
以感叹号开头[7]
|
# 忽略node_modules文件夹下除了layer/layer.js以外的文件 |
|
/node_modules/* |
|
!/node_modules/layer/layer.js |
代码段4
Note
忽略规则中,以/开头表示只忽略与仓库根目录相关的文件夹或文件,否则表示忽略路径中包含该规则的文件。
示例: 假设仓库下的文件结构为
1
├─┬ path
2
│ │ └── file.c (1)
3
│ │ └── file2.c (2)
4
├─┬ path2
5
│ ├── path
6
│ │ └── file.c (3)
7
│ ├── file.c (4)
8
│ ├── file2.c (5)
9
├── file.c (6)
代码段5
则不同忽略规则对应如下
1
# 忽略(1)(3)(4)(6)
2
file.c
3
4
# 忽略(6)
5
/file.c
6
7
# 忽略(4)
8
path2/file.c
9
10
# 忽略(1)(2)(3)
11
path
12
13
# 忽略(1)(2)
14
/path
代码段6
忽略文件的模板
GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表, 你可以在[8] 找到它。
git add .
提交除忽略文件以外的全部文件到暂存区。
git commit -m "备注"
提交更改,产生版本。
远程仓库可以用于云备份和协作,比较知名的远程仓库有GitHub和码云Gitee,以下使用码云。
创建远程仓库[9],得到一个远程仓库链接https://gitee.com/用户名/xxx.git
。
在本地仓库git remote add origin 远程仓库链接
,第一次会弹出提示框输入码云的账号(用户名)和密码,可以在控制面板 -> 用户账户 -> 凭据管理器 -> 管理Windows凭据
查看或修改远程仓库使用的账号信息。
git branch --set-upstream-to=origin/master master
把本地分支和远程分支关联(关联后拉取和推送可以省去远程名称和分支名称)。
git pull
将远程仓库的内容合并到本地仓库和工作区。
git push
将本地仓库的内容推送到远程仓库,可以添加-f
参数表示强制以本地仓库覆盖远程仓库的当前内容。
每次改动了文件后,重复步骤2、6、7提交一个新的版本;如果需要把本地的提交备份到远程仓库,重复步骤11~12。
Note
Git以行 为单位进行比较差异,请养成在文件末尾添加一个空行的习惯。
对于Word文档之类的二进制文件,只能跟踪整个文件大小的变化。(但对于Latex语法编辑的论文,可以按行跟踪。)
对于Windows平台,可以在仓库根目录编写.bat
脚本文件进行批处理:
|
@echo off |
|
set DATE=%date:~0,4%.%date:~5,2%.%date:~8,2% |
|
git add . |
|
git commit -am %DATE% |
|
git pull origin master --tags || pause |
|
git push origin master --tags || pause |
代码段7
其中,第一行代码表示关闭回显,第二行表示取系统日期作为提交备注(形如2022.01.12
),参数--tags
表示拉取或推送远程仓库的标签,|| pause
表示遇到错误则暂停执行(正常运行完脚本自动关闭)。
每次双击该文件就可以将工作区的文件改动一键提交到本地和远程仓库。
把该bat脚本添加到Windows任务[10]可以让电脑定时自动运行该脚本,比如设置每天中午12:00自动备份一次。
多仓库自动提交
将上面的一键提交脚本命名为push.bat
,新建一个PushAll.bat
脚本放在任意地方,编写如下代码
|
set PATHS=仓库1根目录 仓库2根目录 仓库3根目录 |
|
(for %%p in (%PATHS%) do ( |
|
cd %%p |
|
call push.bat |
|
)) |
代码段8
然后把PushAll.bat
添加到Windows任务即可定时批量备份仓库
使用过一次git add
添加过某个文件后,文件就处于Git跟踪状态了,这时才在.gitignore
写忽略规则是不生效的。使用git rm --cached 文件路径
可以取消跟踪该文件。
|
# 移除单个文件的跟踪 |
|
git rm --cached test.txt |
|
|
|
# 移除文件夹的跟踪 |
|
git rm --cached -r MCUAssistant/bin/ |
代码段9
假设有三个文本文件ming.txt
、hong.txt
和hua.txt
分别代表小明、小红和小华(素材: 驻颜术[11]),前后共提交了5次,每次的提交信息备注和改动为:
初始化
小明18岁
(ming.txt)小红20岁
(hong.txt)小明23岁
工作了5年,23岁了
(ming.txt)结婚
23岁的小明娶了小红
(ming.txt)2014年小红嫁给了小明
(hong.txt)小华出生
2016年小明当爹了
(ming.txt)2016年生了小华
(hong.txt)小华出生
(hua.txt)2022年
2022年小明31岁了,已经是大明了
(ming.txt)2022年小红忘了自己的年龄,但是脸上有了岁月的痕迹
(hong.txt)2022年小华8岁了
(hua.txt)第5次提交后,各文件的内容为:
ming.txt:
|
18岁的小明 |
|
工作了5年,23岁了 |
|
23岁的小明娶了小红 |
|
2016年小明当爹了 |
|
2022年小明31岁了,已经是大明了 |
代码段10
hong.txt:
|
20岁的小红 |
|
2014年小红嫁给了小明 |
|
2016年生了小华 |
|
2022年小红忘了自己的年龄,但是脸上有了岁月的痕迹 |
代码段11
hua.txt:
|
小华出生 |
|
2022年小华8岁了 |
代码段12
每提交一次,就产生了一个版本。使用git log
查看提交日志,加--oneline
可以使每条记录显示为一行。
图3 Git提交日志
可以看到每次提交的commit号、操作人的名字和邮箱、提交时间、提交备注。
每个版本都会有一个对应的commit号,可以用它的前几位表示该版本。也可以用下面的方式代表版本:
|
HEAD 当前版本 |
|
|
|
HEAD~1 上一个版本 |
|
|
|
HEAD~2 上上一个版本 |
|
|
|
HEAD~3 上上上一个版本 |
|
|
|
以此类推... |
代码段13
还可以使用版本标签标记,语义化版本号,打标签后可以用标签名代替commit号。
git tag 标签 版本
对目标版本添加标签。不提供版本参数时,默认对当前版本打标签。标签名需要唯一,一个版本可以有多个标签。
|
# 对当前版本添加标签v5 |
|
git tag v5 |
|
|
|
# 对commit号为dbc202开头的版本打标签birth |
|
git tag birth dbc202 |
|
|
|
# 对之前的版本打标签 |
|
git tag v4 HEAD~1 |
|
git tag v3 HEAD~2 |
|
git tag v2 HEAD~3 |
|
git tag v1 HEAD~4 |
代码段14
查看结果:
图4 打标签后的结果
git status
查看文件的状态,加-s
参数简介显示,加-u
参数显示工作区新增的目录和文件。??
表示文件是工作区新增的(未被跟踪,Untracked); M
(右边,红色)表示在工作区文件改动了且未暂存(Modified);A
(左边,绿色)表示暂存了新增的但未提交(Added);M
(左边,绿色)表示暂存了改动的文件但未提交; D
表示之前已跟踪的文件在工作区被删除(Deleted);MM
表示文件暂存了还没提交又在工作区改动了。
示例: HEAD处于结婚
时,工作区改成了小华出生
且未暂存时、暂存后、提交后的文件状态:
图5 Git文件状态
git show 版本
显示某个版本相对于其前一个版本的差异
示例: 查看小华出生
改变了什么:
图6 详细改动记录
可见,Git的每次提交记录的是与前一次提交的差异。
git diff
比较工作区和暂存区差异。改动了工作区已跟踪的文件后,可以用这个指令查看改动了哪些文件和内容,因为继上次提交后暂存区与当前版本(HEAD)一致。
git diff 版本1 版本2
比较两个版本间的差异,待比较的放在后面。
git diff HEAD
比较工作区和版本库差异。
git diff --cached
比较暂存区和版本库差异。
示例: 查看全家人从结婚
(不含)后至2022年
发生了什么:
图7 全家人结婚之后的改动
Note
使用
git diff
(不加其它参数)比较的是已处于跟踪状态的文件,因此不能比较出工作区刚新增的(Untracked)文件的内容。要比较工作区与HEAD的差异,可以先用git status
列出新增的和改动(已跟踪)的文件列表,再用git diff
比较已跟踪文件的变化;或先用git add
将新增的文件加入跟踪状态,再用git diff --cached
查看全部改动。
git reset 模式 目标版本
将版本回退到某个版本,该版本之后的版本的改动将被“移除”。模式是以下值之一,默认为--mixed
--mixed
: 将HEAD和暂存区恢复为目标版本
--soft
: 将HEAD移到目标版本
--hard
: 将工作区、暂存区和HEAD恢复为目标版本。工作区内未提交到仓库的改动将被丢弃!
要回退到的版本为D,则不同参数的操作结果如下表
参数 | 工作区(working) | 暂存区(index) | 当前版本(HEAD) |
---|---|---|---|
原版本 | A | B | C |
--mixed | A | D | D |
--soft | A | B | D |
--hard | D | D | D |
假设当前状态如下图(暂存区与HEAD一致):
图8 当前状态
工作区相对于v5
版本改动了一行,这就相当于删去了原来的一行再新增了一行。
回退示例1
git reset --hard v3
全家人回退到结婚
版本:
图9 --hard回退
可见工作区被目标版本覆盖了,包括已改动但未提交的文件;hua.txt
文件被删除。
回退示例2
目标: 全家人回退到结婚时,并保留未提交的改动2014年小红25岁,嫁给了小明
。
git reset v3
全家人回退到结婚
图10 回退示意图
git status
+git diff HEAD
查看回退的版本之后的各个版本+工作区总共改动了什么图11 回退后工作区与HEAD的差异
相对于HEAD,当前工作区存在一个新增的文件hua.txt
和两个有改动的文件。
决定文件内容的去留。
新增的文件(相对于HEAD为未跟踪的文件)如果不要了,直接删除(hua.txt);改动了的文件,如果需要使用HEAD的版本,使用git checkout 文件路径
(ming.txt);如果需要使用工作区的,直接暂存/提交;或根据工作区和HEAD版本的差异各留一部分。然后,使用git add
和git commit
发起提交。
总之,这时候就是在相对于回退的版本(v3
)作修改,产生一个新版本。
图12 决定文件内容去留
Note
如果不需要保留回退版本之后的所有内容,使用
git reset --hard 目标版本
;否则使用git reset 目标版本
。
git revert 目标版本
以一次新的提交撤销掉某个历史提交的版本。
撤销示例: 当前版本为v5
,需要撤销v2
(小明23岁)的改动。
图13 撤销版本
撤销时出现了冲突,解决冲突的办法是把冲突的文件修改为需要的内容,然后再发起提交。
Note
使用
git log
、git diff
、git reset
、git show
、git checkout
指令时可以添加文件路径参数,限定操作只影响指定的文件。限定示例1
当前版本为
v5
,查看hong.txt
在哪些提交被改动过:图14 查看指定文件的提交
限定示例2
当前版本为
v5
,小红要去除2022年
产生的“岁月痕迹”、小明无所谓、小华不能丢:图15 回退限定
使用git reset
后,回退版本之后的版本会被“移除”,使用git log
不显示,这时要想恢复到回退版本之后的某个版本: 如果记得目标版本的commit号或标签,可以使用git reset
可以穿梭回去;如果不记得了,使用git reflog
可以查看引用日志,引用日志记录了每次的改动后HEAD指向的commit号。
图16 任意穿梭
Note
再次提醒: 任意穿梭的是已提交改动产生的版本,使用
git reset --hard
应先使用git status
确认有没有未提交的改动,或先提交全部改动(产生一个版本)再回退,或不使用--hard
参数然后决定是否保留工作区可能存在的未提交的改动。
命令行模式适用于Linux、Windows、Mac等平台,但需要记忆一些指令。掌握了Git的原理后,可以使用GUI完成大多数操作。除了Git GUI
以外,大多数IDE平台内置了Git,例如Visual Studio
、Visual Studio Code
(VS Code
)、eclipse
和基于它开发的IDE(RT-Thread Studio
、STM32 Studio
、AURIX Development Studio
等)、IntelliJ IDEA
/PyCharm
。以下以轻量级代码编辑工具VS Code
为例。
下载安装VS Code[12];安装后打开,在左侧扩展
栏分别搜索Chinese
(中文简体)和GitLens
插件安装。
菜单 -> 文件 -> 打开文件夹...
打开工作区文件夹。
打开左侧分支图标源代码管理
栏,初始化当前工作区目录为Git仓库。
图17 初始化Git
图18 添加远程仓库
然后输入远程仓库名称(一般用origin
),按回车确认。
源代码管理 -> 更改
列出了所有改动的文件和Git状态,U
、M
、D
分别表示对应工作区上的文件未跟踪(新增的)、已改动、已删除。单击文件可以浏览文件的改动。图19 查看已改动的文件
如果需要撤销工作区的改动,可以点文件名右方的撤销图标
将文件恢复到上个版本的状态,否则进行下一步。
提交全部改动到仓库。
图20 提交全部改动到仓库
把本地仓库推送到远程仓库。
如果远程仓库是第一次创建并且是空的,显示的是$(云上传)发布分支
图21 发布分支
之后是$(同步)同步更改
图22 同步更改
每次改动文件后,重复步骤5~7。
假设该工作区的文件提交了5次,改动内容和备注和前面的一样。
文动历史
打开文件时,在左侧侧边栏时间线
可以看到文件在哪些提交中改动了这个文件,单击某个提交可以查看该提交的改动。
图23 文件时间线
其它操作
源代码管理 -> COMMITS
可以查看仓库提交历史。展开某个版本(commit号或标签)可以看到改动了的文件,点击改动的文件可以查看该提交的改动内容。
图24 仓库提交历史、标签等
在版本上右键,可以进行版本对比、撤销、回退、打标签等等。例如点击版本回退,选择一个回退模式
图25 回退模式
然后根据情况重复步骤5~7。
这些功能由GitLens
插件提供,更多使用说明见: 《GitLens - Git supercharged》[13]
仓库管理者创建仓库,得到远程仓库链接
管理者添加开发者[14]
开发者使用git clone 远程仓库地址
克隆远程仓库到本地
各成员每次工作时,git pull
拉取云端最新的改动并合并到本地 -> 修改 -> 提交 -> 同步(拉取+推送)。
两个不同的地方(本地的不同分支、本地与云端、两个人通过远程仓库协作)基于同一各版本,改动了文件后各自产生了向后的版本,Git尝试进行合并,如果两个新版本改动的地方相同(同一个文件的同一处地方),则产生冲突。
示例: 基于前面的v5(commit号为372b817...
)版本,开发者A修改了hong.txt
为
|
20岁的小红 |
|
2014年小红25岁,嫁给了小明 |
|
2016年生了小华 |
|
2022年小红忘了自己的年龄,但是脸上有了岁月的痕迹 |
代码段15
提交改动后产生了版本4ff4c9e...
;
开发者B也基于v5版本修改了hong.txt
|
20岁的小红 |
|
2014年小红27岁,嫁给了小明 |
|
2016年生了小华 |
|
2022年小红忘了自己的年龄,但是脸上有了岁月的痕迹 |
代码段16
然后B发起提交,产生5888e4a...
版本,这时B同步(拉取+推送)时产生了冲突,冲突的地方被Git修改为:
|
<<<<<<< 当前版本 |
|
当前内容 |
|
======= |
|
对方内容 |
|
>>>>>>> 对方版本 |
代码段17
打开该文件可以看到
图26 出现冲突
VS Code还在上方提供了保留当前改动、保留对方改动、保留双方改动、对比文件、开启动态会话按钮,可以点击前三个按钮的其中一个快速解决冲突,或根据需要手动修改冲突的文件。改完后发起暂存+提交就算解决了冲突,使用git log --oneline --graph
可以查看版本的走向。
图27 解决冲突后的分支结构
其中,各行的commit号和备注对应的是带*
号的版本。
Note
如果开发者B先拉取A修改后的文件,然后在A的基础上修改,提交推送时就不会有冲突。这时版本的走向为
图28 先拉取再修改
前面的操作都是单分支结构(默认主分支master
),理解了冲突之后,就可以使用多分支了[15],每个分支可以有自己独立的版本走向,也可以与另一个分支合并,HEAD其实是指向当前分支的顶端版本。云端可以用master
分支作为正式版、用develop
作为开发版;本地新增某个功能时用feature
开发新特性、用bug
分支修复问题,完成后再合并到主分支。
本文使用markdown编写,管理员阿伟将说明文档和示例文件在码云[16]开源。
访客阿冬在学习时发现一个BUG: 小华出生时应该是2014年!
提交Issue
发现开源仓库的问题时,可以提交一个Issue/任务,提交后仓库管理员会收到通知。
Pull Request
如果有更好的方案,可以向开源仓库Pull Request(PR)。所谓PR,是指发送通知给目标仓库管理员,请求审核、测试后决定是否当内容合并自己的改动到目标仓库。
Note
访客(非仓库成员)直接
克隆仓库 -> 修改提交 -> 推送
会报错403 拒绝访问
图29 非仓库成员push
正确的方式如下。
图30 Fork仓库
在本地克隆副本仓库 -> 修改 -> 提交推送
到副本仓库。
在副本仓库发起PR
图31 发起PR
图32 填写PR信息
图33 审查、测试
图34 手动合并(或由系统自动合并)
图35 PR成功
Note
PR的流程略微麻烦,码云提供了轻量级PR的办法——在网页版打开目标仓库直接编辑提交,即可自动发起PR[17]。
Git本身每个人的权限是平等的,前面同级开发开发时各个成员都有权合并自己的改动,管理员可以将仓库的某些分支设置为保护分支,开启评审模式。
图36 Gitee设置保护分支
标准模式。只有设定的可推送代码的成员和可合并的成员才能推送或合并该分支,其它人推送时返回拒绝访问
。
示例: 假设云端设置了master
保护分支和dev_wei
和dev_dong
分支,阿伟是管理员,阿冬是普通开发者。阿冬修改了文件
图37 阿冬的修改内容
如果他直接向master
分支推送:
图38 无权限人员向标准的保护分支推送
正确的流程是: 修改提交到本地仓库后,git push origin master:dev_dong(推送到自己的分支) -> 在网页版登录切换到dev_dong分支发起PR -> 填写PR信息 -> ...
图39 仓库成员向保护分支发起PR
评审模式。标准模式比较繁琐,Gitee支持设置保护分支为评审模式。
示例: 阿冬可以直接向master
分支推送,仍然是无权限合并,但云端自动创建了PR。
图40 向评审分支推送结果
[1] 菜鸟教程 - 学习Git. Git 教程 | 菜鸟教程
[2] 廖雪峰 - 浅显易懂的Git教程. Git教程 - 廖雪峰的官方网站
[3] Git官方中文教程. Git - Book
[4] Git 知识大全. Git 知识大全 - Gitee.com
[5] 踏月而来 - git版本控制. git版本控制 - 踏月而来 - 博客园
[6] Git - downloads. Git - Downloads
[7] git提交忽略文件或文件. git提交忽略文件或文件夹 - 深夜、程序、与烟 - 博客园
[8] .gitignore模板. GitHub - github/gitignore: A collection of useful .gitignore templates
[9] 码云 - 创建你的第一个仓库. 创建你的第一个仓库 - Gitee.com
[10] 添加Windows任务. 每日定时提交git ,批处理命令_未兆的博客-CSDN博客_git定时提交
[11] thewindkee - git reset --soft驻颜术(恢复文件之前的状态). git reset --soft驻颜术(恢复文件之前的状态)_thewindkee的博客-CSDN博客_git reset soft
[12] Download Visual Studio Code. Download Visual Studio Code - Mac, Linux, Windows
[13] GitLens - Git supercharged. GitLens — Git supercharged - Visual Studio Marketplace
[14] Gitee - 仓库成员管理. 仓库成员管理 - Gitee.com
[15] Git分支. Git - 分支简介
[16] 海南大学刘伟 - Git学习笔记. 海南大学-刘伟/git_demo
[17] Gitee - 轻量级 PR. Gitee 轻量级 PR - Gitee.com