版本控制的概述
版本控制可以完成不同版本的存储管理、重大版本的备份维护、恢复之前的项目版本、记录项目的每处修改、多人开发的代码合并。
分布式版本控制
早期的CVN与SVN采用的都是集中式版本控制,使用单台服务器管理整个项目的开发进程,开发人员通过服务器提交或下载最新版本的文件。这样的作法会造成开发过程容错率低,服务器一旦出错则无法正常开发。
Git采用分布式版本控制,客户端在下载项目时不只是下载最新的文件,同时也将整个项目的代码仓库完整下载,包括历史记录。此时如果服务器发生故障仍可通过客户端进行项目的恢复。
Git的安装
安装过程中会安装Git Bash、Git CMD、Git GUI。bash是windows下的命令行工具,支持linux命令,比CMD更强大。Git CMD的功能与Windows的CMD无异。Git GUI则是图形化操作界面。
配置邮箱和用户名
git安装完毕后需进行邮箱和用户名的配置才可以使用,如下。
shell
git config --global user.name="lin" //--global代表所有项目均使用该配置
git config --global user.email="1419@qq.com"
获取Git仓库
获取Git仓库有两种方式,如下。
-
本地新建仓库
bashgit init
-
从Git远程仓库获取
bashgit clone http://123.1.1/a.git //注意,该命令只会clone远程仓库中的master/main分支,其他分支无法clone
Git文件状态
文件状态分为未跟踪和已跟踪两个状态。未跟踪的文件需要使用git add .
进行文件的跟踪。已跟踪的文件分为staged暂存区状态(使用add命令后的状态)、unmodified未修改状态(使用commit命令后的状态)、modified修改状态(修改文件但未add的状态)。
每次新建文件或者修改文件后想要记录时,都需要使用如下命令。
bash
git add . //将所有文件添加至暂存区
git commit -m "新增修改" //提交文件
add与commit命令可以简写,如下。
shell
git commit -a -m "新增修改"
可以使用git status
命令查看当前文件的状态,工作区为空状态,即所有文件都已被提交至Git,属于一种安全状态。如下图。
未跟踪untracked状态文件如下图。
将文件add至暂存区后,文件变为staged暂存区状态,如下图(本图中的unstaged改为staged)。
修改a.js并add后,文件变为staged暂存区状态,如下图。
修改a.js后,文件变为modified被修改状态,如下图。
.gitignore配置文件
使用git add .
命令可以将文件均添加至git仓库,但是总有那么些文件不需要被添加,比如node_modules文件夹中的内容,此时便可使用.gitignore配置文件来忽略指定文件的添加。实际开发中脚手架会配好。
日志
使用git log
可以查看每次提交的历史,效果如下图。
由上图知无用信息过多,且记录之间空行太多,因此可以使用git log --pretty=oneline
查看更加简洁的信息并且每条记录都在一行显示。如下图。
另外,可以使用图结构进行展示,即使用git log --pretty=oneline --graph
,在使用分支时才有效果(后面学习)。
版本回退
git使用HEAD指针指向当前分支,可以通过移动HEAD指针实现回退至某个版本,命令如下。
shell
git reset --hard HEAD^ //回退到上个版本
git reset --hard HEAD^^ //回退到前个版本
git reset --hard HEAD~8 //回退到前8个版本
git reset --hard 8efabd //回退到commit id为8efabd对应的状态,一般使用commit id的前七八位即可(前七八位不和其他id前七八位重复的情况下)
使用git reset
命令回退后再使用git log
命令查看会发现该版本之后的所有版本都消失不见,如下图。
此时可以使用git reflog
命令展示所有操作记录,回退到c版本之后的版本。效果如下。
常见的远程仓库
常见的远程仓库有Github、Gitee、自己搭建Gitlab。
如果想要对远程仓库进行访问或操作,需要有相应的权限。git验证手段有http连接和SSH连接。
-
http连接:输入远程仓库中自己的账号密码,但是由于http默认短连接,因此用户名和密码需要通过其他方式存储起来(缓存、凭证等)以便操作远程仓库时自动携带验证信息。使用http进行关联的步骤如下。
-
ssh连接:ssh连接采用非对称加密的形式,客户端保存私钥,服务端保存公钥,客户端使用私钥加密数据,服务端采用公钥解密;服务端使用公钥加密数据,客户端采用私钥解密;
首先使用如下命令生成一对公钥和私钥:
shellssh-keygen -t ed25519 -C "your email" //ed25519是一种加密方式 //-C代表注释,一般用email作注释
输入命令后会有对应的选项,包括保存的路径以及访问密码,一般使用默认即可。
之后即可生成对应的公钥和私钥,git会显示其保存的路径,如下图。
然后在服务端中保存这个公钥,gitee中点击设置->SSH公钥,如下图。
在连接后,可以使用git remote -v|--verbose
进行远程仓库的连接情况查询。
与远程仓库的同步
与远程仓库的同步分为pull和push操作。
从远程仓库同步至本地
git pull
命令用于项目从远程仓库同步至本地当前分支,该命令实际上是git fetch
加上git merge
的简写,即先下载至仓库再进行合并。
bash
git pull origin master //origin为远程仓库名,master为远程分支名
每次都需要指定远程仓库名和远程分支名比较麻烦,因此,可以为本地分支设置上游分支,来进行pull命令的简写。
bash
git branch --set-upstream-to=origin/master //设置当前分支的上游分支为远程仓库origin下的master分支
git pull //设置完后再使用pull命令时即可不用传递参数
注意,使用clone创建的本地仓库是不用指定上游分支的,会自动将本地进行远程关联。因此,在clone后执行git pull
和git push
是不用带参数的。
另外,在git2.9版本之后使用merge操作时是无法合并没有相同祖先分支(base)的两个分支的。如下图。
如果想要在没有共同base的情况下强行合并,可以使用如下命令。
bash
git merge origin/master --allow-unrelated-histories
//--allow-unrelated-histories选项只需全局执行一次
//一般origin/master代表已经fetch至本地仓库的origin仓库中的master分支
//origin master代表远程仓库中的master分支
从本地同步至远程仓库
git push
命令用于项目从本地同步至远程仓库,命令如下。
bash
git push origin master //push至origin远程仓库的同名分支master分支
//若指定了上游分支则不需要带参
git push
命令有自己的默认配置,push的默认配置为simple,详解如下(以下情况均假设本地有master分支,远程有main分支)。
- 如果单用不带参的
git push
命令,默认将本地当前分支推送至远程服务器的同名分支,会报错。 - 如果写
git push origin master
默认将本地master分支推送至远程服务器的master分支,会报错。 - 如果写全称
git push origin master:main
则是将本地master分支推送至到远程服务器的main分支,将成功推送。
默认配置可以更改,为master分支设置了上游分支为origin/main后,可将默认配置更改为upstream,配置以后则可以直接写不带参的git push
命令将本地master分支直接推送至远程仓库下的main分支。更改push默认行为为upstream的命令如下。
bash
git config push.default upstream
使用git进行管理的三种情况总结
项目已搭建好
当项目已搭建好并存在于远程仓库时,命令如下。
bash
git clone [location]
---编写代码中,这个期间别人可能上传了最新代码---
git commit -a -m "c1" //提交自己代码
git pull //获取别人在这期间上传的最新代码并进行合并
git push //上传至远程仓库
项目须由自己搭建
当项目项目须由自己从零开始搭建时,有两个方案。
-
在服务器先创建远程仓库,然后本地clone,命令如下。
bash---创建远程仓库中--- git clone [location] ---编写代码中,这个期间别人可能上传了最新代码--- git commit -a -m "c1" git pull //获取别人在这期间上传的最新代码并进行合并 git push
-
本地仓库进行初始化,同时服务器有创建一个远程仓库,即此时本地和远程各有一个仓库,没有相同的base。(假设此时本地为master分支,远程仓库为main分支,所有推送均需推送至远程main分支)
bashgit init //创建本地仓库 git remote add [location] //关联至远程仓库 git fetch origin master //获取别人在这期间上传的最新代码 git branch --set-upstream-to=origin/main //设置当前分支的上游 git merge --allow-unrelated-histories //无相同base情况下强行合并代码 //注意,直接执行git pull命令代替fetch+merge会出错,因为无相同的base // 疑惑:为啥不是origin master ?? ---编写代码中,这个期间别人可能上传了最新代码--- git commit -a -m "c1" git pull //下载远程仓库最新代码并合并 git config push.default upstream //配置push的默认行为 git push
以上命令总是将本地master分支与远程main分支联系显得较别扭,因此常常在本地新建main分支并指定该main分支关联至远程main分支即可避免繁琐的操作。命令如下。
bashgit remote add [location] git fetch git checkout --track origin/main //该命令先检查远程仓库中是否有对应main分支,有则创建对应本地分支并跟踪,且会自动切换至该分支 ---编写代码中,这个期间别人可能上传了最新代码--- git commit -a -m "c1" git pull git push
开源协议
常见的开源协议及其内容如下图。
tag标签
如果有比较大的更新提交,往往会给该版本打上标签,即tag。否则过多的commit操作中往往难以区分重大版本的更新。本地操作生成的tag是无法直接推送至远程仓库的,需要写上对应参数。
本地tag操作
本地打上标签的命令如下。
bash
git tag v1.0.0
git tag -a v1.0.0 -m "重大版本更新1"
删除标签的命令如下。
bash
git tag -d v1.0.0
将本地tag与远程仓库tag同步
tag想要推送至远程仓库时,需要在push操作时显式指定,命令如下。
bash
git push origin v1.0.0
git push origin --tags //所有tag均推送至远程仓库
但是从远程仓库拉取至本地时,使用不带参的git pull
命令是可以直接下载远程仓库中的tag的。
删除远程仓库的tag的命令如下。
bash
git push origin -d v1.0.0 //-d是--delete简写
基于某个tag进行开发
想要查看对应某个标签指向的文件版本,可以使用git checkout
命令,命令如下。
bash
git checkout v1.0.0
---新建分支并开发中---
git分支
使用git分支的流程
假设有如下场景:
项目经过依一定次数的commit后开发出了v1.0.0版本并上线。如下图。
然后,基于当前的master分支继续进行开发新功能,如下图。
此时,v1.0.0版本的项目经过用户反馈后存在bug,那么由于master分支正在开发新功能,去改动不符计划内的代码是不合适的,因此会在v1.0.0标签对应的提交下,新建一个用于改bug的分支(重点),并进行bug的修复。新建分支如下图。
然后基于该分支进行bug的修复,生成了v1.0.1版本,如下图。
修复BUG的相关命令如下。
js
git checkout v1.0.0 //切换至tag v1.0.0所指向版本
git branch branch1 //新建分支1
git checkout branch1
---修复BUG中---
git commit -a -m "修复BUG" //提交修复
git tag v1.0.1 //修复后上线1.0.1版本,master分支继续完成新功能的开发
最后,再将该分支对应的修复结果合并至主分支master上即可。合并命令如下。
bash
git merge branch1
合并后,可能会产生冲突,对于产生冲突的文件,如下图。
有如下几种解决方案。
- 手动删除无效字段。将文件中<<<<head和=====等。
- ide的自动合并。
合并后再一次提交commit(合并branch1)的图示如下。
使用git log --pretty=oneline --graph
以图结构展示提交记录的结果如下图(本质上同上图)。
与分支相关常用命令
bash
git branch //查看分支
git branch -v //同时查看最后一次提交
git brach --merged //查看所有合并到当前分支的分支
git brach -d brach1 //删除branch1分支
常见分支解释
-
master分支
master分支作为主分支,通常接收来自development分支的合并,使得master分支下的日志记录清晰明了。
-
develop分支
develop分支用于开发分支,常常伴随着许多次提交,在有稳定的版本时会合并至master分支。
-
topic分支
topic分支作为某个主题分支,开发需求中的不定性等都会使用该分支,完成开发后会合并至develop分支,但是否完成也具有不定性。
git工作流
由于git分支的便捷性产生了许多git工作流。
小公司常用工作流

大公司常用工作流

使用git clone
操作克隆的只是远程仓库中的main/master分支,无法clone其他分支如develop,因此想要在develop分支进行开发,可以使用git checkout --track origin/develop
,该命令先检查远程仓库中是否有对应develop分支,有则创建对应本地分支并跟踪,且会自动切换至该分支。使用该命令进行正式开发有如下两种情况。(假设本地无对应仓库,远程仓库中已有develop和main分支)
-
加入项目开发时远程仓库中已有对应代码,本地仓库无代码,命令如下。
bashgit clone [location] git checkout --track origin/develop //创建+跟踪+切换至develop进行开发 ---开发中,别人可能上传了最新代码--- git commit -a -m "c1" git pull git push //默认模式simple,此时由develop push至远程仓库下的develop
-
加入项目开发时远程仓库和本地仓库中均已有代码,命令如下。
bashgit init git remote add [locaiton] git fetch git checkout --track origin/develop ---开发中,别人可能上传了最新代码--- git commit -a -m "c1" git pull git push //默认模式simple,此时由develop push至远程仓库下的develop
git rebase变基(了解)
常用的合并分支方法有git merge
和git rebase
,下面论述下两者的区别。
假设现有feature和develop分支,各自提交情况如下。
提交记录如下图。
此时想要合并feature分支至develop分支上,可以在develop分支下使用git merge feature
合并feature分支。
合并后的图示如下。
提交记录如下图,可见,在记录中呈现树形结构。
这里有个问题,有些开发场景中并不想要如上的树形结构,而是更喜欢线性结构,因此可以在feature分支中使用git rebase
命令代替git merge
命令。变基后的图示如下。
在feature中查看提交记录如下图,可见,在记录中呈现线性结构。
此时的develop分支仍然指向dev3,为使develop分支指向f2版本,可以在develop分支中使用git merge
。merge后的图示如下。
HEAD指针同时指向feature和master,如下图。
总的变基命令如下。
bash
git checkout develop
git commit -a -m "dev1"
git commit -a -m "dev2"
git branch feature //新建feature分支
git checkout feature //切换至feature分支
git commit -a -m "f1"
git commit -a -m "f2"
git checkout develop
git commit -a -m "dev3"
git checkout feature
git rebase develop //变基
git checkout develop
git merge feature //合并feature分支
实际可以看出来rebase不太好用的,实际开发较少使用。
注意,永远不要在master分支里面使用rebase命令,会造成混乱。