前言:本文主要介绍git版本控制系统的一些基础使用,适合小白入门,因为内容较多,会分为两部分进行分享,查看上部请点传送门。
删除文件
git删除文件一般有三种情况,第一种是在工作区修改了文件,但是还没有添加到暂存区;第二种是已经添加到暂存区但是还没有提交到版本库中;第三种就是已经提交到版本库中。
撤销工作区的修改
在工作区修改了文件,但还未添加到暂存区,即撤销在工作区对文件的修改动作,在前面查看版本状态的章节,我们已经介绍了一个命令,就是git restore
,新版本都会提示使用这个命令,旧版本则是提示使用git checkout --
这个命令,它与新版命令的用法其实没什么区别,一样可以撤销工作区的修改,但这个命令有两种使用情况:
1.如果我们在文件修改后,文件还未添加到暂存区,想要把修改的全部撤回,那么使用该命令可以撤销修改的部分,回到这个文件的上一个版本。
2.如果文件已经添加到暂存区后,再一次修改了文件,使用该命令就可以撤销修改,回到添加到暂存区时的那一个版本。
需要注意的是,git checkout --
命令中有两个--
,如果没有了--
,它就不是撤销命令了,变成了切换分支命令git checkout
,后面的章节中会提到这个命令。
从暂存区移除
我们提交文件到版本库,一般需要两步,先添加到暂存区再提交到版本库,文件添加到暂存区时,我们可能不想提交了,那么就需要把他从暂存区中移除。
前面查看版本状态的章节,我们已经介绍了一个命令git restore --staged
,同样的,这是新版的命令,我们安装了新版就使用新版命令就好了,仍使用旧版的同学,就可以使用git reset HEAD
这个命令,用法是一样的。
删除版本库的文件
1.git rm 删除文件
一般我们在操作系统上删除文件,就是右键删除,但在git中,你在工作区删除了文件,实际上只是删除了你本地,版本库中该文件仍然是存在的,比如tets_login.py文件,在操作系统右键删除后我们查看一下git状态。
git知道你删除了哪些文件,并且工作区和版本库已经不一致了。这时你有两个选择,一是执行git rm
,并且执行git commit
命令去删除版本库对应的文件,保持工作区和版本库一致。二是使用git restore
撤销对工作区的修改,把删除的文件还原,这样也可以保持工作区和版本库一致。
我们也可以不在工作区操作(即右键删除),直接从版本库中删除文件,使用git rm
把删除动作添加到暂存区,然后git commit
真正地从版本库中删除。我们想要真正地影响到版本库,都需要执行commit,否则它就只是对工作区产生影响。
这里要注意的是,如果我们直接执行了git rm
,但还未提交到版本库,想要还原到工作区时,就需要先执行git restore --staged
移除暂存区,再执行git restore
撤销对工作区的修改。
2.git rm --cached 只删除版本库的文件
使用git rm
且git commit
之后文件会从本地中删除,且会从版本库中删除。如果我们只想删除版本库的文件保留工作区的,我们可以在删除命令加上一个--cached
参数,同样的执行时需要commit一下,如果想撤销,方法同上。我们不需要死记硬背这些命令,查看版本状态时它会提示你可以使用哪些命令。
3.git rm *.txt 删除符合规则的文件
我们在忽略特殊文件的章节有提到过glob模式匹配,这里删除文件也支持模式匹配,比如git rm *.txt
意思就是说删除所有的txt文件。
移动文件
git可以使用git mv
来移动文件,需要输入旧文件名,新文件位置,新文件位置还可以重命名,如把test.txt移动到doc目录下,并且重命名为readme.txt:
我们第一步执行的时候,提示No such file or directory
意思是说没有text.txt文件或者目录不存在,因为我们目录没有创建,因此需要手动创建一个目录或者直接使用linux命令创建一个目录,就可以移动成功了,移动后记得要执行commit才会对版本库生效。
git这一个移动文件的命令实现原理很简单,其实就是执行了下面三个动作:
mv
:linux移动命令git rm
:删除旧文件git add
:添加新文件到暂存区
版本补录
什么是版本补录,就是我们在修改文件提交版本时遗漏了某些文件的提交,或者有了新的修改但又不想重新提交一个版本,希望补录在上一个版本中。
我们只需要在执行commit时增加一个--amend
参数,即git commit --amend
,如提交了一个版本后,再次修改了readme.txt,然后执行该命令进行补录。
上面补录时加了-a
参数直接一步到为提交到了版本库,提交到版本库后会进入版本信息的vim(vi)界面,vim是unix系统文本编辑器,学过linux的同学应该都有了解。这里它进入vim界面我们可以修改增加版本的一些备注信息,如果不需要修改,可以通过按下Esc退出编辑状态,然后连按两次大写字母Z,就可以退出vim了。
退出vim模式后,我们再查看一下版本信息就会看到最近的一个版本有两次提交记录,其中一次是补录的。
版本标签
git标签(tag)的作用主要是快速地定位版本以及版本间的区分,毎次发布一个版本时,我们通常都会在版本库中打上一个标签,标签可以跟踪到版本。之后不管在什么时候,我们都可以通过一个标签,把对应的历史版本取出来。因此,标签实际上就是版本库的一个快照。
但你可能回想,我们commit的时候不是生成了一个版本唯一id吗?版本id是一连串的字符,不容易记住且无法辨别是哪一个版本。总而言之,标签就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
附注标签
git使用的标签有两种类型:轻量标签(lightweight)和附注标签(annotated)。附注标签实际就是存储在版本库中的一个独立对象,它有自身的一些校验信息,包含标签名、作者的信息、标签日期以及标签说明。一般我们都会使用附注标签,方便查看更多的标签信息。
关键命令:git tag -a
这里的-a指的是附注(annotated)
打上标签之后,我们查看版本信息会发现最近的一个版本多了一个tag,因为git默认标签是打在最新提交的一个版本上的。
轻量标签
如果我们只是临时加的标签,不需要额外的一些标签信息,我们就可以使用轻量版标签。
关键命令:git tag
,默认是给最近的一个版本打上标签
查看指定标签
使用git show
查看指定标签的信息
从上图我们可以看出查询的标签是一个附注标签,它有很多一些附带的信息,如标签名、作者的信息、标签日期以及标签说明;那轻量标签又是如何呢?下面这个就是一个轻量标签了,它只是简单的打了一个标签,没有更多的信息了。
查看所有标签
使用git tag
可以查看版本库中有哪些标签,标签名按字母排序。
查看匹配标签
这里查看标签同样适用glob模式匹配,关键命令:git tag -l 匹配规则
,-l表示以列表形式查看标签,如查看v开头的标签:
因为只打了一个v开头的标签,因此只显示一个。
补录标签
默认标签是打在最新提交的commit上的。那么如果commit时忘了打标签怎么办呢?答案就是找到历史提交的commit的id,在标签命令的然后打上标签即可。关键命令:git tag
当然,补录的时候我们是可以选择补录附注标签还是轻量标签的,根据相关命令补充参数即可。
删除标签
如果标签打错了,可以删除标签。关键命令:git tag -d
,-d表示delete
因为我们创建的标签默认都只存储在本地,不会自动推送到远程,因此打错的标签可以在本地直接删除。
这是为什么呢,因为git pull推送时并不会把标签也推送到远程服务器上,只有通过显示命令才能把标签推送出去,关键命令:git push origin
,或者一次性推送所有标签git push origin --tags
,关于推送,会在后面的章节介绍。
如果标签已经推送出去,要删除标签就需要先删除本地,再从远程删除,远程删除命令:git push origin :refs/tags/
。
分支管理
分支的工作方式
几乎每个版本控制系统都支持分支管理,使用分支意味着你可以从开发主线上分离出来,在不影响主线的同时继续工作,在前面多次commit和查看版本,可以知道每次commit,git都会把他们串成一条时间线,这条时间线就是一个分支,目前为止,我们的git里只有一条时间线,即主分支(master分支)。
另外,我们查看版本信息的时候,会发现最新的commit上总会有一个HEAD
的标识,如下图:
HEAD
是什么意思呢,它可以理解为“头指针”,指向当前工作区的分支,当分支很多时,git如何知道你在哪条分支上工作呢,就是通过HEAD
来标识。因为当前只有master主干,没有其他分支,因此HEAD
是一直指向master的。
这里我们要明白,HEAD
是指向分支的,不是指向commit,master(分支)才是指向commit,开始的时候,master分支是一条时间线,git用master指向最新的commit,再用HEAD
指向master,这样就能明确知道当前的分支,以及当前分支的提交点,如下图:
之后每一次commit,master分支就会向前移动一步,随着不断的提交,master分支的时间线就会越来越长(在没有其他分支的情况下)。
当我们创建分支的时候,比如创建一个dev分支,git就会新建一个dev指针,指向跟master一样的commit,再把HEAD
指向dev,表示当前是在dev分支上,如下图:
git创建一个分支是很快的,因为它只是增加了一个dev指针,改变一下HEAD
的指向,而工作区的文件是没有任何变化的,但从现在开始,之后对工作区的修改和commit都是针对dev分支了,每次commit,dev都会往前移一步,而master指针不变,如下图:
假如我们在dev分支上的工作已经完成了,就可以把dev合并到master上。那git如何分支呢?显然,直接把master指向dev最新的commit就可以完成合并了,因此,git的合并分支也很快,改一下指针即可,工作区内容不变。
合并完分支之后,甚至可以把dev分支删掉,这里删除分支其实就是把dev指针删掉,因为我们已经合并完成了,删掉后,这条时间线就只剩下一个master分支。
分支操作
1.创建分支
关键命令:git branch
2.查看分支
关键命令:git branch
,*号代表当前所属分支
3.切换分支
关键命令:git checkout
也可以直接在创建分支时加上一个-b
参数(branch-分支的缩写),表示创建并切换,完整命令:git checkout -b
,相当于执行了创建和切换两个命令。
知识点:
在删除文件的章节曾介绍过一个git checkout --
命令,是用于撤销工作区的修改,同样是checkout命令,作用却相差很大,因此,新版的git提供了git switch
用于切换分支,git restore
用于撤销工作区的修改,用于区分两者的功能,虽然提供了新的命令,但旧命令仍然是可用的。
switch切换分支命令如下:
git switch
:切换分支git switch -c
:创建并切换分支
4.合并分支
现在我们已经切换到dev分支了,我们在dev分支修改一下readme.txt,追加一行:this is a new branch!
dev的工作已经完成,切换到master分支,查看一下readme.txt,发现刚刚在readme.txt追加的一行不见了,因为那是在dev上commit的,不会影响到master。
现在我们来合并分支,关键命令:git merge
,这个命令用于把当前分支合并到指定的分支。合并后,我们再查看readme.txt就会再次看到追加的那一行。
如果你仔细看,会发现合并成功时,有一行这样的提示:Fast-forward
,这是告诉我们,这个合并是“快进模式”,也就是直接把master的指针直接指向dev的最新commit,所以合并速度很快。当然,并不是每次合并能如此快速,后面还会讲其他方式的合并。
5.删除分支
合并完成后,dev分支就没有用了,我们可以选择把它删除,只保留master一个主分支,删除命令:git branch -d
,d就是delete的意思。
合并冲突
1.解决冲突
并不是每一次合并都是顺利完成的,我们先看一下下面一个例子。
第一步,我们先重新创建一个dev分支,并在readme.txt上追加一行:this is dev!然后commit。
第二步,切回master分支,此时readme.txt是没有上面追加的那一行的,但我们给它追加一行:this is master!然后commit。
现在master分支和dev分支各自都有一个新的commit,时间线都往前移了一步,如下图:
前面合并分支时我们也提到过,并不是每一次合并都是快进模式,像上面的情况,合并的时候就会引起冲突,因为两个commit工作区的文件内容不是一样的,我们先试着合并:
果然,git告诉我们readme.txt文件存在冲突,使用git status查看工作区情况时也会告诉我们有文件冲突。
这个时候我们查看readme.txt,会发现里面多了一些内容,git用<<<<<<<
,=======
,>>>>>>>
标记了不同分支的内容。当文件存在冲突时,我们必须手动解决冲突后再提交,即手动修改文件。
2.查看合并情况
手动解决冲突后,工作树会再次回到干净状态。此时master和dev分支就变成了下图的样子:
查看合并情况:git log --graph
,我们可以再加上--pretty==oneline
(一个版本显示一行)和--abbrev-commit
(仅显示版本号前几个字符)两个参数,更简洁的查看合并情况:
分支管理策略
1.禁用快进模式
前面合并分支,我们就提到过Fast forward
快进模式,但这种模式在删除分之后,就会丢掉分支信息,无法去查看合并的记录。
因此我们可以强制禁用Fast forward
模式,git会在合并时生成一个新的commit,这样在查看合并记录时就可以查看分支信息了。
合并时禁用快进模式的关键命令: git merge --no-ff -m '版本信息'
我们重新建一个test分支来使用一下这个禁用命令:
因为本次合并禁用了快进模式,git在合并时会生成一个新的commit,所以要加上-m
参数,把commit描述写进去,现在已经合并完成,我们来查看一下合并情况:
之前我们使用快进模式时,合并后,dev和master是指向同一个commit的,合并只是把master指针向前移了一步,而禁用快进之后,合并时创建了一个新的commit,master会指向新的commit,test仍然指向所在分支的最近的一次commit,如下对比图:
2.分支策略
在实际的工作中,我们应按照以下几个基本原则进行分支管理:
- ✔️ master要保持一个稳定的版本,即仅用来发布新版本,不在此分支进行日常的开发;
- ✔️ 日常开发在dev分支上,也就是说,dev是不稳定的一个分支,在到达某个时间点后,需要发布版本时,再把dev合并到master上;
- ✔️ 每个人分别在自己的分支上进行开发,根据需要再时不时地合并到dev分支上。
这时候,团队的分支情况就像下图:
检出版本
检出版本
通过分支管理,我们已经了解到分支的概念,也知道HEAD
是指向分支的,比如下图中HEAD
是指向master分支的,master分支又默认指向最新的commit,但是有时候我们可能并不想要分支指向最新的commit,由于某些原因最新的commit不可用了,想要在分支中的某一个旧版本进行继续工作时怎么办呢?
我们可以使用checkout来检出版本,什么是检出版本,说白了其实就是修改HEAD
的指向。
检出版本关键代码:git checkout 版本号
首先查看一下历史版本,记住当前master最新的commit是ef69bbf,想要检出版本的版本号:66b725a
现在我们来检出这个版本:
检出版本后,会出现一堆提示,它告诉我们已经检出到66b725a版本,并且当前处于detached HEAD
(分离头指针)状态,这个状态意思是说,HEAD
指针指向了一个具体的commit,而不是分支,我们说过HEAD
是指向分支的,分支才是指向commit的。这里它处于分离状态是因为我们主动把HEAD
指针修改了,使它指向了一个commit。
检出版本后,再次查看版本记录,对比发现最近提交的两个版本已经看不到了,指针已经指向了回退的版本。
如果要查看比回退高的版本,可以执行git reflog
。
除了可以通过版本号检出版本,还可以通过标签名去检出,关键代码:git checkout
,这里不再做演示。
现在我们已经检出一个版本了,我们在这个版本上对readme.txt追加一行:this is checkout!添加到暂存区后,查看一下文件的状态:
它提示了HEAD detached at 66b725a
,意思是说当前不属于任何分支,因为HEAD
处于“分离头指针”模式,接下来我们提交这个版本,查看一下版本信息:
此时HEAD指针指向新的commit,并且是在检出版本的前面,我们先记下这个最新的commit:99dc7db。然后切换到master分支:
切换到master分之后,它发出一个警告:99dc7db这个commit将被遗留掉,因为这个commit不在任何一个分支上。此时,我们再次查看一下版本信息:
发现已经看不到99dc7db这个commit的信息了,HEAD
重新指向了master分支的最新commit:ef69bbf,而我们检出版本时修改的readme.txt文件也回到了之前的版本。由于99dc7db这个commit当时是处于分离头指针状态,不属于任何分支,因此也没有任何分支可以追踪到,但它并不是被删除了,它仍保留在版本库中,我们可以通过指定版本号去查看这个commit:
挽留分离头指针
在“分离头指针”模式下提交的commit,在没有归到任何一个分支时,也就只能通过版本号(99dc7db)指定去访问了,如果这个commit是master分支所需要的,可以使用合并命令,把这个commit合并到master分支上,实现两者的兼容:
别名
设置别名
git并不会像一些编译软件,在你输入部分命令时就自动联想到你想要的命令,必须要手动敲出一个完整且正确的命令。还好git支持别名设置,可以通过 git config
文件来轻松地给一些常用、或者复杂的命令设置一个别名,比如git status
命令我们经常使用,我们可以告诉git以后st就表示status
。
关键命令:git config --global alias.别名 命令
,--global
是全局参数,也就是说设置的命令在这台电脑的所有git仓库下都可用。
当然,我们还可以设置其他各种各样的命令,比如:ck表示checkout
,ci表示commit
,br表示branch
等,具体可以参考https://git-scm.com/book/zh/v2/Git-基础-Git-别名
git config --global alias.ck checkout
git config --global alias.br branch
git config --global alias.cf config
git config --global alias.fc fetch
配置的命令还可以是多个,但需要加上引号来表示,比如我们可以配置一个git last
,让其显示最后一次提交信息,即last是log -1
的别名:
配置文件
还有一些很长的命令我们都可以这样修改成简单易记的别名,而这些别名设置都保存在~/.gitconfig
文件中:
别名就在[alias]
后面,要删除别名,直接把对应的行删掉即可;如果要修改别名,直接重新设置就行,git会在已有的别名进行覆盖,除了可查看配置文件,也可以通过git config --list | grep alias
命令来查看这些别名:
GitHub的使用
前面我们所讲的操作都是基于本地的,git作为一个分布式的版本控制系统,和svn最显著的区别就是可以离线使用,不需要创建一个中心的git服务,就可以在本地使用版本控制。
同一个git仓库,可以分布到不同的机器上。在实际工作中,我们在多人协作时就必须要有一个集中管理的地方,因此就有了各种各样的代码托管服务,GitHub就是其中的一种,这里就不对其进行过多的介绍。
GitHub官网地址:https://github.com/,自行注册一个账号。
新建项目
登录后,在首页点击Start a project。
然后根据页面提示,输入对应的信息:
填好信息之后点击创建,就会跳转到该仓库,可以看到仓库的远程地址:
git远程地址
查看本地关联的远程仓库地址,相关命令如下:
git remote
:不带参数,列出已经存在的远程分支git remote -v
:列出详细信息,在每一个名字后面列出其远程url, -v 参数为 –verbose 的简写,表示显示对应的克隆地址git remote add 仓库名 仓库远程url
:添加一个远程仓库
我们先查看一下本地是否有远程的仓库:
没有内容返回说明当前没有远程的仓库,接下来我们把刚刚创建好的github仓库添加到本地:
添加成功后再次查看远程地址就有对应的数据返回了。
git远程操作
1.查看远程项目信息
关键代码:git remote show 远程仓库名
2.推送内容到远程仓库
关键代码:git push 远程仓库名
首次执行,会弹出github登录界面,授权登录即可,推送完毕查看github的文件,会发现分支的所有文件都已经传到github了
3.从远程仓库拉取代码
关键代码:
git fetch 远程仓库名
:从远程获取最新版本到本地,但不会自动mergegit pull 远程仓库名
:从远程获取最新版本并merge到本地
我们先在github模拟其他人修改分支内容,点击github分支中的Create new file新建一个txt文件,输入内容后点击左下角Commit new file提交;
然后在本地的git进行拉取文件,我们先看一下使用git fetch来拉取的效果:
拉取完成后,查看一下本地的工作目录,发现在github创建的test_fetch.txt并没有出现在本地,这是为什么呢?原来git fetch并没有真正地去影响到本地工作目录,只是把数据拉取下来放到了本地的远程仓库,我们可以通过git merge 远程仓库名/
来合并分支。
这样的话,每次拉取都需要执行一遍合并实在太麻烦了,git还有另外一个拉取命令,就是git pull 远程仓库名
,它会在拉取代码时自动合并到分支,拉取后再看一下工作目录,数据已经与远程仓库一致了:
4.远程修改
关键代码:
git remote rename
:重命名远程仓库名称,只是修改本地git remote rm name
:删除远程仓库地址(github地址如果更新了就可以删除重新添加)
5.克隆远程项目
关键代码:git clone url
当我们本地没有git项目且已知一个远程项目可用时,我们就可以通过克隆,把远程仓库的整个项目克隆下来进行工作。首先,需要手动创建一个文件夹,在文件里右键点击Git Bash Here进入git命令窗口(也可以通过cd命令切换到该目录),输入克隆命令回车即可,这里克隆的url就是github远程项目的地址,可以在github上查看。
克隆完成后,就可以进入项目开始你的工作了,默认是进入到master分支,需要在其他分支工作时使用分支切换命令切换即可,也可以在克隆时加上-b参数切换分支,克隆完成后会自动切换到指定的分支,完整命令:git clone -b