gitGit原理与完全使⽤指南
⽬录:
⼀、从了解版本控制系统开始
所谓版本控制,就是在⽂件修改的历程中保留修改历史,可以⽅便的撤销(如同⽂本编辑的撤销操作⼀般,只是版本控制会复杂的多)之前
对⽂件的修改。⼀个版本控制系统的三个核⼼内容:版本控制(最基本的功能),主动提交(commit历史)和远程仓库(协同开发)。
1.中央式版本控制系统(VCS)
⼯作模型:
主⼯程师搭好项⽬框架
在公司服务器创建⼀个远程仓库,并提交代码
其他⼈拉取代码,并⾏开发
每个⼈独⽴负责⼀个功能,开发完成提交代码
其他⼈随时拉取代码,保持同步
2.分布式版本控制系统(DVCS)
分布式与中央式的区别主要在于,分布式除了远程仓库之外团队中每⼀个成员的机器上都有⼀份本地仓库,每个⼈在⾃⼰的机器上就可以进
⾏提交代码,查看版本,切换分⽀等操作⽽不需要完全依赖⽹络环境。
⼯作模型:
主⼯程师搭好项⽬框架,并提交代码到本地仓库
在公司服务器创建⼀个远程仓库,并将1的提交推送到远程仓库
其他⼈把远程仓库所有内容克隆到本地,拥有了各⾃的本地仓库,开始并⾏开发
每个⼈独⽴负责⼀个功能,可以把每⼀个⼩改动提交到本地(由于本地提交⽆需⽴即上传到远程仓库,所以每⼀步提交不必是⼀个完整功
能,⽽可以是功能中的⼀个步骤或块)
功能开发完毕,将和这个功能相关的所有提交从本地推送到远程仓库
每次当有⼈把新的提交推送到远程仓库的时候,其他⼈就可以选择把这些提交同步到⾃⼰的机器上,并把它们和⾃⼰的本地代码合并
分布式版本管理系统的优缺点:
优点:
⼤多数操作本地进⾏,数度更快,不受⽹络与物理位置限制,不联⽹也可以提交代码、查看历史、切换分⽀等等
分布提交代码,提交更细利于review
缺点:
初次clone时间较长
本地占⽤存储⾼于中央式系统
⼆、继续深⼊git原理
假设你已经安装好了git并将代码clone到了本地,新⼿移步git安装与代码拷贝指南。
最基本的⼯作模型
⾸先理解三个基本概念:
⼯作区:就是你在电脑⾥能看到的⽬录
版本库:⼯作区有⼀个隐藏⽬录.git,这个不算⼯作区,⽽是Git的本地版本库,你的所有版本信息都会存在这⾥
暂存区:英⽂叫stage,或index。⼀般存放在“.git⽬录下”下的index⽂件(.git/index)中,所以我们把暂存区有时也叫作索引
(index)
⼯作模型
1.⾸先新建⼀个⽂件并对其进⾏修改,通过status可以查看⼯作⽬录当前状态,此时对git来说是不存在的(Untracked)
2.然后通过add命令将修改放⼊暂存区(git开始追踪它)
可以看到,的⽂字变成了绿⾊,它的前⾯多了「newfile:」的标记,⽽它的描述也从“Untrackedfiles”变成了“Changesto
becommited”。这些都说明⼀点:这个⽂件的状态从“untracked”(未跟踪)变成了“staged”(已暂存),意思是这个⽂
件中被改动的部分(也就是这整个⽂件)被记录进了stagingarea(暂存区)。
stage这个词在Git⾥,是「集中收集改动以待提交」的意思;⽽stagingarea,就是⼀个「汇集待提交的⽂件改动的地⽅」。简
称「暂存」和「暂存区」。⾄于staged表⽰「已暂存」。
3.现在⽂件已经放⼊暂存区,可以⽤commit命令提交:
在这⾥你也可以直接commit提交会进⼊commit信息编辑页⾯,⽽加上-m参数可以快捷输⼊简短的提交备注信息,这样你就完成了⼀次提
交(可以通过gitlog查看提交历史)
接着对该⽂件再次进⾏修改,输⼊gitstatus可以看到,该⽂件⼜变红了,不过这次它左边的⽂字不是“Newfile:”⽽是“modified:”,
⽽且上⽅显⽰它的状态也不是“Untracked”⽽是“notstagedforcommit”,意思很明确:Git已经认识这个⽂件了,它不是个新⽂
件,但它有了⼀些改动。所以虽然状态的显⽰有点不同,但处理⽅式还是⼀样的:
接下来再次将该⽂件add、commit,查看log可以看到已经存在两条提交记录
4.最后通过push把本地的所有commit上传到远程仓库:
2.团队⼯作基本模型
⼯作模型
1.在上⾯基本操作的基础上,同事commit代码到他的本地,并push到远程仓库
2.你把远程仓库新的提交通过pull指令拉取到你的本地
通过这个流程,你和同事就可以简单地合作了:你写了代码,commit,push到远程仓库,然后他pull到他的本地;他再写代
码,commit,push到远程仓库,然后你再pull到你的本地。你来我往,配合得不亦乐乎。(但是有时候push会失败)
为什么会失败?
因为Git的push其实是⽤本地仓库的commit记录去覆盖远程仓库的commit记录(注:这是简化概念后的说法,push的实质和这个
说法略有不同),⽽如果在远程仓库含有本地没有的commit的时候,push(如果成功)将会导致远端的commit被擦掉。这种结果
当然是不可⾏的,因此Git会在push的时候进⾏检查,如果出现这样的情况,push就会失败
这时只需要先通过gitpull(实为fetch和merge的组合操作)将本地仓库的提交和远程仓库的提交进⾏合并,然后再push就可以了
eBranching:最流⾏的⼯作流
核⼼:
(1)任何新的功能(feature)或bug修复全都新建⼀个branch来写;
(2)branch写完后,合并到master,然后删掉这个branch(可使⽤gitorigin-d分⽀名删除远程仓库的分⽀)。
优势:
(1)代码分享:写完之后可以在开发分⽀review之后再merge到master分⽀
(2)⼀⼈多任务:当正在开发接到更重要的新任务时,你只要稍微把⽬前未提交的代码简单收尾⼀下,然后做⼀个带有「未完成」标记的
提交(例如,在提交信息⾥标上「TODO」),然后回到master去创建⼀个新的branch进⾏开发就好了。
三、HEAD、branch、引⽤的本质以及push的本质
:当前commit的引⽤
当前commit在哪⾥,HEAD就在哪⾥,这是⼀个永远⾃动指向当前commit的引⽤,所以你永远可以⽤HEAD来操作当前commit,
:
HEAD是Git中⼀个独特的引⽤,它是唯⼀的。⽽除了HEAD之外,Git还有⼀种引⽤,叫做branch(分⽀)。HEAD除了可以指向
commit,还可以指向⼀个branch,当指向⼀个branch时,HEAD会通过branch间接指向当前commit,HEAD移动会带着branch⼀起移
动:
branch包含了从初始commit到它的所有路径,⽽不是⼀条路径。并且,这些路径之间也是彼此平等的。
像上图这样,master在合并了branch1之后,从初始commit到master有了两条路径。这时,master的串就包含了12347和1
2567这两条路径。⽽且,这两条路径是平等的,12347这条路径并不会因为它是「原⽣路径」⽽拥有任何的特别之处
创建branch:gitbranch名称
切换branch:gitcheckout名称(将HEAD指向该branch)
创建+切换:gitcheckout-b名称
在切换到新的branch后,再次commit时HEAD就会带着新的branch移动了:
⽽这个时候,如果你再切换到master去commit,就会真正地出现分叉了:
删除branch:gitbranch-d名称
注意:
(1)HEAD指向的branch不能删除。如果要删除HEAD指向的branch,需要先⽤checkout把HEAD指向其他地⽅。
(2)由于Git中的branch只是⼀个引⽤,所以删除branch的操作也只会删掉这个引⽤,并不会删除任何的commit。(不过如果⼀个
commit不在任何⼀个branch的「路径」上,或者换句话说,如果没有任何⼀个branch可以回溯到这条commit(也许可以称为野⽣
commit?),那么在⼀定时间后,它会被Git的回收机制删除掉)
(3)出于安全考虑,没有被合并到master过的branch在删除时会失败(怕误删未完成branch)把-d换成-D可以强制删除
3.引⽤的本质
所谓引⽤,其实就是⼀个个的字符串。这个字符串可以是⼀个commit的SHA-1码(例:
c08de9a4d8771144cd23986f9f76c4ed729e69b0),也可以是⼀个branch(例:ref:refs/heads/feature3)。
Git中的HEAD和每⼀个branch以及其他的引⽤,都是以⽂本⽂件的形式存储在本地仓库.git⽬录中,⽽Git在⼯作的时候,就是通过
这些⽂本⽂件的内容来判断这些所谓的「引⽤」是指向谁的。
的本质:把branch上传到远程仓库
(1)把当前branch位置上传到远程仓库,并把它路径上的commits⼀并上传
(2)git中(2.0及以后版本),gitpush不加参数只能上传到从远程仓库clone或者pull下来的分⽀,如需push在本地创建的分⽀则需使
⽤gitpushorigin分⽀名的命令
(3)远端仓库的HEAD并不随push与本地⼀致,远端仓库HEAD永远指向默认分⽀(master),并随之移动(可以使⽤gitbr-r查看远程
分⽀的HEAD指向)。
四、开启git操作之旅
:合并
含义:从⽬标commit和当前commit(即HEAD所指向的commit)分叉的位置起,把⽬标commit的路径上的所有commit的内容
⼀并应⽤到当前commit,然后⾃动⽣成⼀个新的commit。
当执⾏gitmergebranch1操作,Git会把5和6这两个commit的内容⼀并应⽤到4上,然后⽣成⼀个新的提交7。
merge的特殊情况:
(1)merge冲突:你的两个分⽀改了相同的内容,Git不知道应该以哪个为准。如果在merge的时候发⽣了这种情况,Git就会把问题交
给你来决定。具体地,它会告诉你merge失败,以及失败的原因;这时候你只需要⼿动解决掉冲突并重新add、commit(改动不同⽂件或
同⼀⽂件的不同⾏都不会产⽣冲突);或者使⽤gitmerge--abort放弃解决冲突,取消merge
(2)HEAD领先于⽬标commit:merge是⼀个空操作:
此时merge不会有任何反应。
(3)HEAD落后于⽬标commit且不存在分⽀(fast-forward):
git会直接把HEAD与其指向的branch(如果有的话)⼀起移动到⽬标commit。
:给commit序列重新设置基础点
有些⼈不喜欢merge,因为在merge之后,commit历史就会出现分叉,这种分叉再汇合的结构会让有些⼈觉得混乱⽽难以管理。如果你
不希望commit历史出现分叉,可以⽤reba来代替merge。
可以看出,通过reba,5和6两条commits把基础点从2换成了4。通过这样的⽅式,就让本来分叉了的提交历史重新回到了⼀条
线。这种「重新设置基础点」的操作,就是reba的含义。另外,在reba之后,记得切回master再merge⼀下,把master移到
最新的commit。
为什么要从branch1来reba,然后再切回master再merge⼀下这么⿇烦,⽽不是直接在master上执⾏reba?
从图中可以看出,reba后的每个commit虽然内容和reba之前相同,但它们已经是不同的commit了(每个commit有唯⼀标
志)。如果直接从master执⾏reba的话,就会是下⾯这样:
这就导致master上之前的两个最新commit(3和4)被剔除了。如果这两个commit之前已经在远程仓库存在,这就会导致没法
push:
所以,为了避免和远程仓库发⽣冲突,⼀般不要从master向其他branch执⾏reba操作。⽽如果是master以外的
branch之间的reba(⽐如branch1和branch2之间),就不必这么多费⼀步,直接reba就好。
需要说明的是,reba是站在需要被reba的commit上进⾏操作,这点和merge是不同的。
:临时存放⼯作⽬录的改动
stash指令可以帮你把⼯作⽬录的内容全部放在你本地的⼀个独⽴的地⽅,它不会被提交,也不会被删除,你把东西放起来之后就可以去做
你的临时⼯作了,做完以后再来取⾛,就可以继续之前⼿头的事了。
操作步骤:
(1)gitstash可以加上save参数后⾯带备注信息(gitstashsave'备注信息')
(2)此时⼯作⽬录已经清空,可以切换到其他分⽀⼲其他事情了
(3)gitstashpop弹出第⼀个stash(该stash从历史stash中移除);或者使⽤gitstashapply达到相同的效果(该stash仍存在stashlist
中),同时可以使⽤gitstashlist查看stash历史记录并在apply后⾯加上指定的stash返回到该stash。
注意:没有被track的⽂件会被git忽略⽽不被stash,如果想⼀起stash,加上-u参数。
:引⽤记录的log
可以查看git的引⽤记录,不指定参数,默认显⽰HEAD的引⽤记录;如果不⼩⼼把分⽀删掉了,可以使⽤该命令查看引⽤记录,然后使⽤
checkout切到该记录处重建分⽀即可。
注意:不再被引⽤直接或间接指向的commits会在⼀定时间后被Git回收,所以使⽤reflog来找回被删除的branch
的操作⼀定要及时,不然有可能会由于commit被回收⽽再也找不回来。
5.看看⾃⼰都改了什么
(1)log:查看已提交内容
gitlog-p可以查看每个commit的改动细节(到改动⽂件的每⼀⾏)
gitlog--stat查看简要统计(哪⼏个⽂件改动了)
gitshow指定commit指定⽂件名查看指定commit的指定⽂件改动细节
(2)diff:查看未提交内容
gitdiff--staged可以显⽰暂存区和上⼀条提交之间的不同。换句话说,这条指令可以让你看到「如果你⽴即输⼊gitcommit,你将会提交什
么」
gitdiff可以显⽰⼯作⽬录和暂存区之间的不同。换句话说,这条指令可以让你看到「如果你现在把所有⽂件都add,你会向暂存区中增加哪
些内容」
gitdiffHEAD可以显⽰⼯作⽬录和上⼀条提交之间的不同,它是上⾯这⼆者的内容相加。换句话说,这条指令可以让你看到「如果你现在把
所有⽂件都add然后gitcommit,你将会提交什么」(不过需要注意,没有被Git记录在案的⽂件(即从来没有被add过的⽂
件,untrackedfiles并不会显⽰出来。因为对Git来说它并不存在)实质上,如果你把HEAD换成别的commit,也可以显⽰当前⼯作⽬
录和这条commit的区别。
6.刚刚提交的代码发现写错了怎么办?
优雅和简单的解决⽅法:commit--amend。
具体做法:
(1)修改好问题
(2)将修改add到暂存区
(3)使⽤gitcommit--amend提交修改,结果如下图:
7.错误不是最新的提交⽽是倒数第⼆个?
使⽤reba-i(交互式reba):
所谓「交互式reba」,就是在reba的操作执⾏之前,你可以指定要reba的commit链中的每⼀个commit是否需要进⼀步修
改,那么你就可以利⽤这个特点,进⾏⼀次「原地reba」。
操作过程:
(1)gitreba-iHEAD^^
说明:在Git中,有两个「偏移符号」:^和~。
^的⽤法:在commit的后⾯加⼀个或多个^号,可以把commit往回偏移,偏移的数量是^的数量。例如:master^表⽰
master指向的commit之前的那个commit;HEAD^^表⽰HEAD所指向的commit往前数两个commit。
~的⽤法:在commit的后⾯加上~号和⼀个数,可以把commit往回偏移,偏移的数量是~号后⾯的数。例如:HEAD~5表⽰
HEAD指向的commit往前数5个commit。
上⾯这⾏代码表⽰,把当前commit(HEAD所指向的commit)reba到HEAD之前2个的commit上:
(2)进⼊编辑页⾯,选择commit对应的操作,commit为正序排列,旧的在上,新的在下,前⾯黄⾊的为如何操作该commit,默认
pick(直接应⽤该commit不做任何改变),修改第⼀个commit为edit(应⽤这个commit,然后停下来等待继续修正)然后:wq退出编辑
页⾯,此时reba停在第⼆个commit的位置,此时可以对内容进⾏修改:
(3)修改完后使⽤add,commit--amend将修改提交
(4)gitreba--continue继续reba过程,把后⾯的commit直接应⽤上去,这次交互式reba的过程就完美结束了,你的那个倒数
第⼆个写错的commit就也被修正了:
8.想直接丢弃某次提交?
(1)ret--hard丢弃最新的提交
gitret--hardHEAD^
HEAD^表⽰HEAD往回数⼀个位置的commit
(2)⽤交互式reba撤销历史提交
操作步骤与修改历史提交类似,第⼆步把需要撤销的commit修改为drop,其他步骤不再赘述。
(3)⽤reba--onto撤销提交
gitreba--ontoHEAD^^HEAD^branch1
上⾯这⾏代码的意思是:以倒数第⼆个commit为起点(起点不包含在reba序列⾥),branch1为终点,reba到倒数第三个
commit上。
9.错误代码已经push?
有的时候,代码push到了远程仓库,才发现有个commit写错了。这种问题的处理分两种情况:
(1)出错内容在⾃⼰的分⽀
假如是某个你⾃⼰独⽴开发的branch出错了,不会影响到其他⼈,那没关系⽤前⾯⼏节讲的⽅法把写错的commit修改或者删除掉,然
后再push上去就好了。但是此时会push报错,因为远程仓库包含本地没有的commits(在本地已经被替换或被删除了),此时直接使
⽤gitpushorigin分⽀名-f强制push。
(2)问题内容已合并到master
(1)增加新提交覆盖之前内容
(2)使⽤gitrevert指定commit
它的⽤法很简单,你希望撤销哪个commit,就把它填在后⾯。如:gitrevertHEAD^
上⾯这⾏代码就会增加⼀条新的commit,它的内容和倒数第⼆个commit是相反的,从⽽和倒数第⼆个commit相互抵消,达到撤销的
效果。在revert完成之后,把新的commit再push上去,这个commit的内容就被撤销了。它和前⾯所介绍的撤销⽅式相⽐,最主要的
区别是,这次改动只是被「反转」了,并没有在历史中消失掉,你的历史中会存在两条commit:⼀个原始commit,⼀个对它的反转
commit。
:不⽌可以撤销提交
gitret--hard指定commit你的⼯作⽬录⾥的内容会被完全重置为和指定commit位置相同的内容。换句话说,就是你的未提交的修改会被全
部擦掉。
gitret--soft指定commit会在重置HEAD和branch时,保留⼯作⽬录和暂存区中的内容,并把重置HEAD所带来的新的差异放进暂存
区。
什么是「重置HEAD所带来的新的差异」?:
gitret--mixed(或者不加参数)指定commit保留⼯作⽬录,并且清空暂存区。也就是说,⼯作⽬录的修改、暂存区的内容以及由ret所导
致的新的⽂件差异,都会被放进⼯作区。简⽽⾔之,就是「把所有差异都混合(mixed)放在⼯作区中」。
ut:签出指定commit
checkout的本质是签出指定的commit,不⽌可以切换branch还可以指定commit作为参数,把HEAD移动到指定的commit上;与ret
的区别在于只移动HEAD不改变绑定的branch;gitcheckout--detach可以把HEAD和branch脱离,直接指向当前commit。
本文发布于:2022-12-27 03:58:28,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/37958.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |