1。简介 谈及版本控制系统,或许大多数程序员最开始接触的都是SVN(Subversion),它是一个集中式的版本控制系统,使用的时候需要提供一台的服务器来进行部署,所有的更新与同步操作都需要与这台服务器进行交互,一旦这台服务器宕机,便无法对代码库进行任何操作。 随着时间的推移,Git横空出现,那么Git又是怎样的一个版本控制系统呢? Git是一种分布式版本的版本控制系统(VersionControlSystem),与SVN最大的区别在于,使用Git,我们可以在本地进行提交并在本地进行完整的版本控制,当需要与远程仓库进行同步时,再进行同步与更新,对版本的控制不受中央服务器的影响。 2。设置 在我们安装完Git之后,就可以对Git环境进行一些设置了。2。1用户设置2。1。1查看当前配置 查看当前配置gitconfiglist2。1。1自定义配置全局设置gitconfigglobaluser。nameStoNEgitconfigglobaluser。emailstoneqq。com项目设置,在项目目录下设置gitconfiglocaluser。nameStoNEgitconfiglocaluser。emailstoneqq。com3。Git基本使用3。1git仓库初始化初始化这个目录,让Git对这个目录进行版本控制,会在该目录下创建一个。git目录gitinitInitializedemptyGitrepositoryinC:UsersStoNEDesktoptest。git 如果想取消git对该目录的版本控制,只需要将。git目录删除即可3。2把文件交给git管理 在test目录下创建test。txt,使用gitstatus查看git状态3。2。1查看git状态 gitstatusgitstatusOnbranchmasterNocommitsyetUntrackedfiles:(usegitaddfile。。。toincludeinwhatwillbecommitted)test。txtnothingaddedtocommitbutuntrackedfilespresent(usegitaddtotrack)3。2。2添加文件到暂存区 如果在Git仓库根目录创建了空文件空目录,即文件目录中没有任何内容,是无法被Git追踪的,Git对内容敏感。 gitadd将该文件添加到暂存区gitaddtest。txtgitstatusOnbranchmasterNocommitsyetChangestobecommitted:(usegitrmcachedfile。。。tounstage)newfile:test。txt 这时,文件状态已经由Untracked变成newfile。表示该文件已经被安置到暂存区(StagingArea),此时该文件就交给Git进行版本控制了。 如果我们修改了多个文件,如果使用像gitaddtest。txt一次只能添加一个文件的操作,就会增加很多,Git为我们提供了一次性添加多个文件的操作:gitaddall和gitadd。。那么它们之间有什么区别呢?gitaddall:该操作会将仓库中所有的改动都会被加入暂存区。gitadd。:该操作只会将当前目录下(包括其子目录)的所有改动加入暂存区。3。2。3修改文件 我们修改test。txt的文件内容,再来查看文件状态gitstatusOnbranchmasterNocommitsyetChangestobecommitted:(usegitrmcachedfile。。。tounstage)newfile:test。txtChangesnotstagedforcommit:(usegitaddfile。。。toupdatewhatwillbecommitted)(usegitrestorefile。。。todiscardchangesinworkingdirectory)modified:test。txt 可以发现,我们在test。txt文件进行修改后,修改后的内容并没有被添加到暂存区。如果我们已经确定要对该文件进行修改,可以再次执行gitadd将其添加到暂存区。3。2。4把暂存区的内容提交到版本库 经过上面的gitadd操作,只是将文件的改动添加到暂存区,此时还并没有对这些文件进行存档(也就是所谓的版本),如果要为这些内容生成它的第一个版本,需要使用gitcommit操作来完成。 gitcommitminitcommit表示这个commit修改了什么gitcommitminitcommit commit命令只会处理在暂存区中的内容,没有被加到暂存区的内容不会被commit到版本库中。 在commit时如果没有指定mcomment,git默认是不会完成commit的,其目的主要就是体现这次提交做了什么改动。 当我们没有新增或修改文件时,想要测试commit命令也是可以的。使用gitcommitallowemptymtest3。2。5工作区、暂存区与版本库工作区:指的是我们执行gitinit命令的目录,由操作系统进行维护。暂存区:执行gitadd命令后,文件存放的位置,由Git维护。版本库:执行gitcommit命令后,信息存放的位置,由Git维护。在暂存区的文件,如果进行了修改,此时gitcommit不会将修改也提交到存储库中。因此在对暂存区的文件进行修改后,需要执行gitadd将修改添加到暂存区。 如果想要在gitcommit时,将对暂存区的文件的修改一起提交,可以使用a参数,gitcommitaminitcommit。这样即使没有执行gitadd也可以完成gitcommit。该参数只对已经存在于暂存区的文件有效 3。3查看记录3。3。1查看整个仓库的记录 那么,我们在使用commit命令之后,要怎么来查询commit记录呢?使用gitlog命令。 gitloggitlogcommitbf62d29800ce3e4d22fab67be8da7c7c1d8d7910(HEADmaster)Author:StoNE767864968qq。comDate:FriDec922:10:5220220800test 如果想要输出的结果更为精简,可以添加参数oneline来解决。3。3。2使用搜索条件 当然还有一些条件搜索的参数,例如:author:例如:gitlogonelineauthorstone,可以查询该作者提交的commit。grep:例如:gitlogonelinegrepJava,查询commit信息中包含该关键字的内容。S:例如:gitlogonelineSJava,查询commit文件中,符号条件的内容。since、until:例如:gitlogonelinesince20230101until20230211,查询某个时间段内的commit。3。3。3查看特定文件的commit记录查看该文件的commit记录gitlogtest1。txt查看该文件的commit记录,以及每次commit做了什么改动gitlogptest1。txt3。3。4查看某一行是谁写的 想要知道某个文件的某一行的作者是谁,我们可以使用gitblame命令来查看。gitblameL1test1。txtf3e6839b(StoNE2022121015:30:2108001)HelloWorldf3e6839b(StoNE2022121015:30:2108002)f3e6839b(StoNE2022121015:30:2108003)success2 如果文件太大,可以加上L参数,只显示指定行数而内容。只显示第1行的内容gitblameL1,1test1。txtf3e6839b(StoNE2022121015:30:2108001)HelloWorld3。4删除文件3。4。1直接删除 使用操作系统的命令,rm。会将文件从工作区删除。 rmtest。txtrmtest。txtgitstatusOnbranchmasterChangesnotstagedforcommit:(usegitaddrmfile。。。toupdatewhatwillbecommitted)(usegitrestorefile。。。todiscardchangesinworkingdirectory)deleted:test。txtnochangesaddedtocommit(usegitaddandorgitcommita) 此时对文件的删除操作并没有添加到暂存区,如果我们想要删除该操作,可以执行gitaddtest。txt将修改添加到暂存区。 无论是执行rm命令,还是执行gitrm命令,都会将该文件从工作目录中删除。如果只是想将该文件不受git版本控制,可以使用cached3。4。2git删除 我们也可以使用gitrmtest。txt,直接就将修改添加到了暂存区,不需要再执行一次add命令。 无论是执行rm命令,还是执行gitrm命令,都会将该文件从工作区删除,如果只是想让该文件不再由Git进行版本控制,可以加上cached参数。 cachedgitrmtest。txtcachedrmtest。txtgitstatusOnbranchmasterChangestobecommitted:(usegitrestorestagedfile。。。tounstage)deleted:test。txtUntrackedfiles:(usegitaddfile。。。toincludeinwhatwillbecommitted)test。txt3。4。3恢复被删除的文件文件夹 有些时候,可能误删除了某些文件,只有。git目录没被删除,被误删的文件是可以找回来的。rmtest1。txtgitstatusOnbranchmasterChangesnotstagedforcommit:(usegitaddrmfile。。。toupdatewhatwillbecommitted)(usegitrestorefile。。。todiscardchangesinworkingdirectory)deleted:test1。txt 可以看见,test1。txt文件处于deleted状态。可以使用gitcheckout恢复恢复单个文件(如果文件被删除或修改后想恢复到未修改之前)gitcheckouttest1。txt恢复当前目录的所有文件gitcheckout。 如果gitcheckout后面跟的是分支名,则会切换分支,如果后面跟的是文件名或路径,则不会切换分支,而是从。git文件夹中将文件复制一份到当前工作目录。更精确地说是:将暂存区的文件拿来覆盖当前工作目录的文件。3。5修改文件名3。5。1直接修改文件名 mvtest。txttest1。txtmvtest。txttest1。txtgitstatusOnbranchmasterChangesnotstagedforcommit:(usegitaddrmfile。。。toupdatewhatwillbecommitted)(usegitrestorefile。。。todiscardchangesinworkingdirectory)deleted:test。txtUntrackedfiles:(usegitaddfile。。。toincludeinwhatwillbecommitted)test1。txt 虽然只是更改文件名,但是对git来说却是两个动作,一个是删除test。txt文件,另一个是创建test1。txt文件。将这些改变添加到暂存区gitaddallgitaddallgitstatusOnbranchmasterChangestobecommitted:(usegitrestorestagedfile。。。tounstage)renamed:test。txttest1。txt3。5。2git修改文件名 gitmvtest。txttest1。txtgitmvtest。txttest1。txtgitstatusOnbranchmasterChangestobecommitted:(usegitrestorestagedfile。。。tounstage)renamed:test。txttest1。txt 可以看到,文件状态已经变成renamed了,同时省去了gitaddall3。6修改commit记录3。6。1修改最后一次commit记录 amendgitcommitamendmeditcomment如果没有修改任何文件,执行该命令,就可以修改comment信息如果修改了文件,并将文件添加到暂存区,就可以追加文件到最后一次commit中。3。7新增文件夹 git是根据文件的内容来进行处理的,如果只是新增一个文件夹,git是无法处理的,因此空的文件夹是无法提交的。 只要这个文件夹下有文件,我们就可以正常进行add和commit命令。3。8忽略文件3。8。1忽略某个文件 如果不想把文件放在git中,需要在项目根目录中创建一个。gitignore文件,并且设置想要忽略的规则。即使这个文件没有被commit或push到Git服务器,也会有效果。 虽然通过。gitignore文件,我们设置了一些忽略的规则,同样,我们也可以忽略这些设置的规则,使用gitaddf文件名称,还是可以将文件加入暂存区。 如果我们在添加。gitignore文件时,一些要被忽略的文件已经加入到Git版本控制了,那么这些忽略规则对这些文件就是无效的了。我们需要使用gitrmcached文件名称将这些文件移除版本控制,它们就会被忽略了。3。8。2清除忽略的文件 如果想清除那些已经被忽略的文件,可以使用gitcleanfX。3。9Reset命令3。9。1resetcommit commit之后,发现了一些错误,想要回滚到上一次提交。先来看看commit记录gitlogoneline799f231(HEADmaster)Helloworld4161dc1heihei944e412renamefile02852daupdatefilename81dd455updatefilenamef5c310adeletetest1。txt9a08888addtest1。txt0ce400edeletetest1。txt262e104addtest1。txt10d9664deletetest。txt9e0e265helloworlda6cfb82initcommit相对做法因为当前HEAD和master都指向799f231这个commit,所以以下3个命令的效果是同等的。gitreset799f231gitresetmastergitresetHEAD 表示前一次,所以799f231代表的是799f231这个commit的前一次commit绝对做法 如果知道要回退到哪个commit,可以直接指明gitreset4161dc13。9。2reset模式mixed模式mixed:gitreset命令如果没有指定参数,将使用mixed模式。该模式会将commit回退的文件留在工作区,但不会留在暂存区soft模式soft:该模式会将commit回退的文件直接存放在暂存区hard模式hard:该模式会将commit回退的文件从工作目录和暂存区一并删除 如果一开始回退时,使用hard参数,我们会发现commit的工作目录和暂存区都被删除了,并且忘记commit的SHA1值,那么如何恢复。可以使用Reflog查看记录gitreflog799f231(HEADmaster)HEAD{0}:reset:movingto799f231799f231(HEADmaster)HEAD{1}:reset:movingto799f2314161dc1HEAD{2}:reset:movingto799f231799f231(HEADmaster)HEAD{3}:reset:movingto799f2314161dc1HEAD{4}:reset:movingto4161dc1799f231(HEADmaster)HEAD{5}:reset:movingto799f2314161dc1HEAD{6}:reset:movingto799f231799f231(HEADmaster)HEAD{7}:reset:movingto799f2314161dc1HEAD{8}:reset:movingto799f231799f231(HEADmaster)HEAD{9}:commit:Helloworld4161dc1HEAD{10}:commit(amend):heihei40940efHEAD{11}:commit(amend):wtf403d573HEAD{12}:commit(amend):renamedf4aab38HEAD{13}:commit(amend):wtf5b48069HEAD{14}:commit(amend):renamed01c8884HEAD{15}:commit:wtf944e412HEAD{16}:commit:renamefile02852daHEAD{17}:commit:updatefilename81dd455HEAD{18}:commit:updatefilenamef5c310aHEAD{19}:commit:deletetest1。txt9a08888HEAD{20}:commit:addtest1。txt0ce400eHEAD{21}:commit:deletetest1。txt262e104HEAD{22}:commit:addtest1。txt10d9664HEAD{23}:commit:deletetest。txt9e0e265HEAD{24}:commit:helloworlda6cfb82HEAD{25}:commit(initial):initcommit 然后在恢复的时候,找到目标commit的值,使用gitresethardcommit值即可。3。10commit部分内容 有的时候,我们已经在文件中做了多处修改,但是在提交的时候,只想提交部分修改,可以通过一下方式来处理。gitaddptest1。txt输出内容diffgitatest1。txtbtest1。txtindex9801343。。5791be4100644atest1。txtbtest1。txt1,21,3HelloWorldsuccess2Nonewlineatendoffile(11)Stagethishunk〔y,n,q,a,d,e,?〕? 使用gitaddptest1。txt命令,会询问是否要把这个区域(hunk)加到暂存区,如果选择y时将整个文件添加到暂存区,选择e可以选择要添加的内容Manualhunkeditmodeseebottomforaquickguide。1,21,3HelloWorldsuccess2NonewlineatendoffileToremovelines,makethemlines(context)。Toremovelines,deletethem。Linesstartingwithwillberemoved。Ifthepatchappliescleanly,theeditedhunkwillimmediatelybemarkedforstaging。Ifitdoesnotapplycleanly,youwillbegivenanopportunitytoeditagain。Ifalllinesofthehunkareremoved,thentheeditisabortedandthehunkisleftunchanged。 这个时候,如果我们不需要将success2那一行添加到暂存区,只需要将这一行删掉即可。3。11HEAD是什么 HEAD就像是一个标签,指向某一个分支,通常可以把他理解为:当前所在分支。 在。git目录中有一个名为HEAD的文件,我们可以来看看其中的内容:cat。gitHEADref:refsheadsmaster 可以看出,HEAD目前指向master分支。当我们切换的分支的时候,HEAD文件的内容也会随着改变。 除了HEAD文件之外,还有一个ORIGHEAD文件,在做一些比较危险的操作(merge,reset、rebase)时,Git会把HEAD的状态存放在该文件,让你随时回退到危险动作之前的状态。3。12Git运行原理 对于Git来说,。git目录是至关重要了,由Git进行版本控制的信息都存放在其中了,因此,探究。git目录的结构,才能更好地理解Git的运行原理。3。12。1四大对象 在Git中,有四大对象:blob对象、tree对象、commit对象和tag对象。 首先,我们这里重新初始化一个Git仓库,新建一个index。html文件,并将其加入暂存区。初始化Git仓库gitinit新建index。html文件,并加入暂存区。echoHelloWorldindex。htmlgitaddindex。html查看状态gitstatus输出内容OnbranchmasterNocommitsyetChangestobecommitted:(usegitrmcachedfile。。。tounstage)newfile:index。html 可以看到,index。html已经加入到暂存区,当文件加入暂存区后,Git便会根据文件的内容,在。gitobjects目录下生成对应的blob对象。该文件的内容是经过压缩的,可以使用gitcatfile命令来查看。 gitcatfilet557db03de997c86a4a028e1ebd3a1ceb225be238输出内容,其确实是一个blob对象blob 那么,我们来看一下该文件的内容是什么样子的?gitcatfilep557db03de997c86a4a028e1ebd3a1ceb225be238输出内容,就是我们创建时输入的内容。HelloWorld 此时,我们对上面的步骤进行一个总结:当使用gitadd命令将文件加入暂存区时,Git会根据这个对象的内容计算出SHA1值。Git使用SHA1值的前两个字节作为目录名称,后38个字节作为文件名,创建目录以及文件并存放在。gitobjects目录下,文件的内容时GIt使用压缩算法把原本内容压缩之后的结果。 blob对象在进行gitadd操作之后出现。 通过上面的操作,文件已经加入到暂存区,可以进行commit操作。gitcommitminticommit输出内容〔master(rootcommit)aeb827f〕inticommit1filechanged,1insertion()createmode100644index。html 可以看到,多出了几个目录,我们来看看这些目录中的文件。gitcatfiletefbd263bc3503e5ebc193a8051ee264b461f89bf输出内容tree 该对象是一个tree对象,我们来看看其内容,其中存放了一个blob对象。 目录或文件的名称存放在tree对象中。tree对象在commit之后生成,根据目录的内容生成其SHA1值。gitcatfilepefbd263bc3503e5ebc193a8051ee264b461f89bf输出内容100644blob557db03de997c86a4a028e1ebd3a1ceb225be238index。html 再看剩下的一个文件,查看其类型:gitcatfiletaeb827f8087c395d0c38362328af8d41f1cee0c3输出内容,表明其是一个commit对象commit commit对象在commit之后生成。 查看其内容:gitcatfilepaeb827f8087c395d0c38362328af8d41f1cee0c3输出内容treeefbd263bc3503e5ebc193a8051ee264b461f89bfauthorStoNE767864968qq。com16760967720800committerStoNE767864968qq。com16760967720800inticommit 可以得知,commit对象包含的信息:commit对象中记录了tree对象。本次commit的作者。本次commit的提交者,一般情况下,作者和提交者是同一个人。本次commit的信息。通过以上的分析,得出以下结论: commit对象指向某个tree对象,该对象指向根目录。 除了第一个commit,其他的commit对象,会包含一个parent信息,指向它的前一个commit tree对象指向某个或某些blob对象,或其他tree对象。 最后,我们再来看看tag对象。 tag对象不会在commit过程中出现,必须手动将tag标记在某个commit上。我们可以用来标记程序的发布点。在当前最新的commit上,标记一个tag,末尾的v1。0即tag的名称gittagamv1。0v1。0当然,我们也可以在指定的commit上,标记一个tag,aeb827f为commit值gittagamv1。0v1。0aeb827f3。12。2小总结把文件放入Git的暂存区后,文件的内容会被Git压缩之后,存放在blob对象中。目录即文件名会存放在tree对象中,tree对象会指向blob对象或其他的tree对象。在将暂存区的文件提交到版本库后,会生成commit对象。commit对象会指向某个tree对象,同时,除了第一个commit,其他的commit,会包含一个parent信息,其指向前一个commit对象。tag对象需要手动创建,并指向某个commit对象。4。分支什么是分支。创建分支,并不会将文件复制到另外的目录。分支就像一张标签一样,贴在一个commit上。 当进行了一个新的commit之后,这个新的commit会指向它的前一个commit。而当前分支,也就是HEAD所指的这个分支,此时master会指向该commit,HEAD会指向该分支4。1查看分支 gitbranch 如果后面没有跟任何参数,会输出当前项目的所有分支,前面的表示当前分支获取分支列表gitbranchmaster4。2新增分支4。2。1直接创建 在gitbranch后面加上分支名称,即可创建新的分支创建新分支gitbranchdev获取分支列表gitbranchdevmaster4。2。2从过去的某个commit创建从777cde5上创建一个分支gitbranchtest3777cde5从777cde5上创建一个分支,并切换过去gitcheckoutbtest3777cde54。3修改分支名称 使用m参数修改分支名称gitbranchmdevtest获取分支列表gitbranchmastertest4。4删除分支 使用d参数删除分支gitbranchdtestDeletedbranchtest(was799f231)。获取分支列表gitbranchmaster4。5切换分支获取分支列表gitbranchdevmaster切换分支到devgitcheckoutdevSwitchedtobranchdev获取分支列表,可以看到号在dev前面,表示当前分支已切换到devgitbranchdevmaster 如果要切换的分支不存在,可以添加b参数,git会创建该分支,然后切换过去。4。5。1切换分支时发生了什么 首先,我在dev分支创建一个index。html并提交,查看gitloggitlogoneline6fb55ca(HEADdev)index。htmlf3e6839devcommit799f231(master)Helloworld4161dc1heihei944e412renamefile02852daupdatefilename81dd455updatefilenamef5c310adeletetest1。txt9a08888addtest1。txt0ce400edeletetest1。txt262e104addtest1。txt10d9664deletetest。txt9e0e265helloworlda6cfb82initcommit 切换到master分支gitcheckoutmasterSwitchedtobranchmastergitlogoneline799f231(HEADmaster)Helloworld4161dc1heihei944e412renamefile02852daupdatefilename81dd455updatefilenamef5c310adeletetest1。txt9a08888addtest1。txt0ce400edeletetest1。txt262e104addtest1。txt10d9664deletetest。txt9e0e265helloworlda6cfb82initcommit 在切换之后,发现index。html不见了。其实git在切换分支时主要做了两件事:更新暂存区和工作目录git切换分支时,会用该分支指向的那个commit的内容来更新暂存区以及工作目录。但在切换分支前所做的改动会留在工作目录,不受影响变更HEAD位置将HEAD指向切换后的分支4。5。2切换分支时,当前分支有未提交的改动 如果切换分支时,当前分支还有未提交的改动,Git会禁止切换分支,会提示让我们提交改动,或者stash。gitcheckoutmaster输出内容error:Yourlocalchangestothefollowingfileswouldbeoverwrittenbycheckout:test2。htmlPleasecommityourchangesorstashthembeforeyouswitchbranches。Aborting 1。先提交当前进度gitcommitmnotfinish 然后切换到master分支,在master上进行完善后,再切换到test2分支,执行reset命令,恢复到之前的工作状态gitresetHEAD 2。使用stash 将修改先stash起来:gitstashpushmteststash输出内容SavedworkingdirectoryandindexstateOntest2:teststash查看stash列表gitstashlist输出内容stash{0}:Ontest2:teststash 将修改从stash中恢复回来将修改恢复,并将其从stash列表中删除gitstashpopstash{0}将修改恢复,不会其从stash列表中删除gitstashapplystash{0}4。6合并分支4。6。1合并分支 如果想要用master分支合并dev分支,需要先切换到master分支,然后使用gitmerge命令合并gitmergedev输出内容Updating799f231。。6fb55caFastforwardinex。html0test1。txt42fileschanged,3insertions(),1deletion()createmode100644inex。htmlgitlogoneline输出内容6fb55ca(HEADmaster,dev)index。htmlf3e6839devcommit799f231Helloworld4161dc1heihei944e412renamefile02852daupdatefilename81dd455updatefilenamef5c310adeletetest1。txt9a08888addtest1。txt0ce400edeletetest1。txt262e104addtest1。txt10d9664deletetest。txt9e0e265helloworlda6cfb82initcommit 在合并后,我们可以看到master和dev分支的内容已经一致。 A合并B,与B合并A有什么不同? 首先test1分支与test2分支都来自master分支,所以不管master要合并test1分支还是test2分支,Git都会使用快转模式(FastForward)进行合并。 但是test1与test2这两个分支要进行互相合并就不一样了。假设用test1分支合并test2,这种情况下,Git会额外生成一个commit,这个commit分别指向两个分支的最新commit,HEAD随着test1分支往前,而test2分支停留在原地。同样的,如果是使用test2合并test1,HEAD随着test2分支往前,而test1分支停留在原地。4。6。2快转模式(FastForward) 快转模式,该模式不会产生新的commit,只是将master分支这个标签,往前贴到test1分支所指的commit上。 noff参数是指不要使用快转模式合并,这样就会额外做出一个Commit对象。4。6。2删除未合并分支 如果删除了未合并的分支怎么恢复。 当前处于dev分支,新建index。txt文件并commitgitlogonelineaccd781(HEADdev)devindex。txt6fb55ca(master)index。htmlf3e6839devcommit799f231Helloworld4161dc1heihei944e412renamefile02852daupdatefilename81dd455updatefilenamef5c310adeletetest1。txt9a08888addtest1。txt0ce400edeletetest1。txt262e104addtest1。txt10d9664deletetest。txt9e0e265helloworlda6cfb82initcommit 删除分支时,是不能删除当前分支的。先切换到mastergitcheckoutmasterSwitchedtobranchmaster此时删除dev分支,会提示dev分支还没有完全合并到master,我们仍然将其删除gitbranchddeverror:Thebranchdevisnotfullymerged。Ifyouaresureyouwanttodeleteit,rungitbranchDdev强行删除dev分支gitbranchDdevDeletedbranchdev(wasaccd781) 虽然dev分支被删除了,其实commit记录还是存在的,只是我们看不到了。分支就像一张标签一样指向某个commit,因此删除分支只是将这张标签从这个commit上撕了下来。只是我们可能没有记录下这个commit的SHA1,所以不容易再拿来使用。 所以,即使分支没有合并就被删除了,还是有机会可以恢复回来的创建dev分支,并让其指向accd781这个commitgitbranchdevaccd781切换分支,发现index。txt又回来了gitcheckoutdev4。6。3Rebase rebase,有重新定义分支的参考基准的含义。 我们在master分支基础上,创建一个新的test分支,在test分支上创建test。html文件并提交。gitcheckoutdevSwitchedtobranchdevgitlogonelineaccd781(HEADdev)devindex。txt6fb55ca(master)index。htmlf3e6839devcommit799f231Helloworld4161dc1heihei944e412renamefile02852daupdatefilename81dd455updatefilenamef5c310adeletetest1。txt9a08888addtest1。txt0ce400edeletetest1。txt262e104addtest1。txt10d9664deletetest。txt9e0e265helloworlda6cfb82initcommitgitcheckouttestSwitchedtobranchtestgitlogoneline11757d4(HEADtest)branchtestcommit6fb55ca(master)index。htmlf3e6839devcommit799f231Helloworld4161dc1heihei944e412renamefile02852daupdatefilename81dd455updatefilenamef5c310adeletetest1。txt9a08888addtest1。txt0ce400edeletetest1。txt262e104addtest1。txt10d9664deletetest。txt9e0e265helloworlda6cfb82initcommit 可以看到dev和test分支都是基于master分支的。gitrebasedevSuccessfullyrebasedandupdatedrefsheadstest。 上述命令的意思就是,test分支现在要重新定义其参考基准(其当前参考基准为master),并以dev分支作为新的参考基准gitlogonelineb871de9(HEADtest)branchcommittest2。html60a9a4cbranchtestcommitaccd781(dev)devindex。txt6fb55ca(master)index。htmlf3e6839devcommit799f231Helloworld4161dc1heihei944e412renamefile02852daupdatefilename81dd455updatefilenamef5c310adeletetest1。txt9a08888addtest1。txt0ce400edeletetest1。txt262e104addtest1。txt10d9664deletetest。txt9e0e265helloworlda6cfb82initcommit 可以看到test分支的参考基准已经变成了dev分支,并且之前的commit11757d4变成了commit60a9a4c。说明rebase会修改commit的SHA1值4。6。4取消rebase 如果知道rebase之前该分支的所指向的commit,可以直接gitresetcommit值hard4。6。5合并发生冲突 1。此时我们在test分支上修改inex。html,在首行添加一行这是一个html文件,并commit。 2。切换到dev分支,修改inex。html,在首行添加一行这不是一个html,并commit。 3。在dev分支上合并test分支gitmergetestAutomerginginex。htmlCONFLICT(content):Mergeconflictininex。htmlAutomaticmergefailed;fixconflictsandthencommittheresult 4。查看git状态gitstatusOnbranchdevYouhaveunmergedpaths。(fixconflictsandrungitcommit)(usegitmergeaborttoabortthemerge)Changestobecommitted:newfile:test。htmlnewfile:test2。htmlUnmergedpaths:(usegitaddfile。。。tomarkresolution)bothmodified:inex。html 因为inex。html文件在两个分支上都修改了同一行,因此git将其标记为bothmodified状态 5。解决冲突HEADh1这不是一个htmlh1h1这是一个html文件h1test git将有冲突的地方标记了出来,上面是HEAD,表示是当前分支dev,中间是分隔线,分隔线下面test分支的内容。最后我们决定使用test分支的内容,顺便把标记清除。处理之后的文本h1这是一个html文件h1 如果可以确定要使用谁的版本,也可以使用以下命令来解决冲突:使用我们自己的版本来解决冲突gitcheckoutoursinex。html使用被合并方的版本来解决冲突gitcheckouttheirsinex。html 6。将修改完的文本添加到暂存区gitaddinex。htmlgitstatusOnbranchdevAllconflictsfixedbutyouarestillmerging。(usegitcommittoconcludemerge)Changestobecommitted:modified:inex。htmlnewfile:test。htmlnewfile:test2。html 7。提交5。修改历史记录5。1修改历史commit的信息 要修改历史记录信息,之前我们使用了amend参数,来修改最后一次commit的信息,但是,要改动更早的commit信息,就得使用其他的方法了。 我们这里还是使用gitrebase命令,然后配置i参数,开启互动模式,而后面的aeb827f指的是应用范围为从当前commit到aeb827f这个commitgitrebaseiaeb827f 会弹出vim编辑器,这里的commit顺序与gitlog指令结果的顺序是相反的。pickde98713test2pick777cde5teststashRebaseaeb827f。。777cde5ontoaeb827f(2commands)Commands:p,pickcommitusecommitr,rewordcommitusecommit,buteditthecommitmessagee,editcommitusecommit,butstopforamendings,squashcommitusecommit,butmeldintopreviouscommitf,fixup〔Cc〕commitlikesquashbutkeeponlythepreviouscommitslogmessage,unlessCisused,inwhichcasekeeponlythiscommitsmessage;cissameasCbutopenstheeditorx,execcommandruncommand(therestoftheline)usingshellb,breakstophere(continuerebaselaterwithgitrebasecontinue)d,dropcommitremovecommitl,labellabellabelcurrentHEADwithanamet,resetlabelresetHEADtoalabelm,merge〔Ccommitccommit〕label〔oneline〕createamergecommitusingtheoriginalmergecommitsmessage(ortheoneline,ifnooriginalmergecommitwas 我们将前两行的pick改成reword,表示要改动这两次commit的信息,存档并离开后,会继续弹出vim窗口,以便我们修改commit信息,在修改完之后,Git会继续完成后面的操作。 由于修改了commit的信息,两次commit的SHA1值都变了,变成了两个全新的commit对象。5。2把多个commit合并为一个commit 有时候,commit太过琐碎,可能多个commit也就修改了一个文件,如果把这几个commit合并为一个commit,会让commit看起来更简洁。我们同样可以使用gitrebase来处理。gitrebaseiaeb827f 会弹出如下vim编辑窗口:pickde98713test2pick777cde5teststashRebaseaeb827f。。777cde5ontoaeb827f(2commands)Commands:p,pickcommitusecommitr,rewordcommitusecommit,buteditthecommitmessagee,editcommitusecommit,butstopforamendings,squashcommitusecommit,butmeldintopreviouscommitf,fixup〔Cc〕commitlikesquashbutkeeponlythepreviouscommitslogmessage,unlessCisused,inwhichcasekeeponlythiscommitsmessage;cissameasCbutopenstheeditorx,execcommandruncommand(therestoftheline)usingshellb,breakstophere(continuerebaselaterwithgitrebasecontinue)d,dropcommitremovecommitl,labellabellabelcurrentHEADwithanamet,resetlabelresetHEADtoalabelm,merge〔Ccommitccommit〕label〔oneline〕createamergecommitusingtheoriginalmergecommitsmessage(ortheoneline,ifnooriginalmergecommitwas 这里我们将第二行的pick修改为squash,表明就第二个commit与第一个commit进行合并。编辑并保存编辑器后,会继续弹出vim编辑窗口:Thisisacombinationof2commits。Thisisthe1stcommitmessage:test2Thisisthecommitmessage2:teststashPleaseenterthecommitmessageforyourchanges。Linesstartingwithwillbeignored,andanemptymessageabortsthecommit。Date:SatFeb1116:31:5120230800interactiverebaseinprogress;ontoaeb827fLastcommandsdone(2commandsdone):pickde98713test2squash777cde5teststashNocommandsremaining。Youarecurrentlyrebasingbranchtest2onaeb827f。Changestobecommitted: 这里我们将commit信息,修改为一个就好。编辑并保存后,Git会继续完成后续的rebase操作。5。3将一个commit拆解为多个commit 有的时候,我们在一个commit中提交了过多的文件,我们同样可以使用gitrebasei来进行拆解。gitrebaseiaeb827f 会弹出vim编辑窗口:pickde98713test2pick777cde5teststashpick3ac54d7committwofilesRebaseaeb827f。。3ac54d7ontoaeb827f(3commands)Commands:p,pickcommitusecommitr,rewordcommitusecommit,buteditthecommitmessagee,editcommitusecommit,butstopforamendings,squashcommitusecommit,butmeldintopreviouscommitf,fixup〔Cc〕commitlikesquashbutkeeponlythepreviouscommitslogmessage,unlessCisused,inwhichcasekeeponlythiscommitsmessage;cissameasCbutopenstheeditorx,execcommandruncommand(therestoftheline)usingshellb,breakstophere(continuerebaselaterwithgitrebasecontinue)d,dropcommitremovecommitl,labellabellabelcurrentHEADwithanamet,resetlabelresetHEADtoalabelm,merge〔Ccommitccommit〕label〔oneline〕createamergecommitusingtheoriginalmergecommits 因为3ac54d7提交了两个文件,我们将这个commit拆解为两个commit。将其前面的pick修改为edit,这个时候rebase就停止下来了。gitrebaseiaeb827fStoppedat3ac54d7。。。committwofilesYoucanamendthecommitnow,withgitcommitamendOnceyouaresatisfiedwithyourchanges,rungitrebasecontinue 先查看一下gitloggitlogoneline3ac54d7(HEAD,test2)committwofiles777cde5teststashde98713test2aeb827f(tag:v1。0)inticommit 此时HEAD指向的3ac54d7这个commit,我们可以使用gitresetHEAD,再查看statusgitstatusinteractiverebaseinprogress;ontoaeb827fLastcommandsdone(3commandsdone):pick777cde5teststashedit3ac54d7committwofiles(seemoreinfile。gitrebasemergedone)Nocommandsremaining。Youarecurrentlyeditingacommitwhilerebasingbranchtest2onaeb827f。(usegitcommitamendtoamendthecurrentcommit)(usegitrebasecontinueonceyouaresatisfiedwithyourchanges)Untrackedfiles:(usegitaddfile。。。toincludeinwhatwillbecommitted)one。htmltwo。htmlnothingaddedtocommitbutuntrackedfilespresent(usegitaddtotrack) 达到这个状态,我们就可以使用熟悉gitadd配合gitcommit命令了。gitaddone。htmlgitcommitmaddone。htmlgitaddtwo。htmlgitcommitmaddtwo。html 最后,我们使用gitrebasecontinue,就可以完成此次rebase操作了。再次查看gitlog:gitlogoneline926265c(HEADtest2)addtwo。htmlca245ecaddone。html777cde5teststashde98713test2aeb827f(tag:v1。0)inticommit5。4在某些commit之间添加新的commit 这个操作的原理和我们上面讲到的将一个commit拆解为多个commit的原理是一样的,都是需要先将rebase操作暂停在某个commit之上,然后进行后续的操作。5。5调整commit的顺序删除commit1。使用gitrebasei命令,在弹出的vim编辑器窗口中,调整commit的顺序即可。 2。使用gitrebasei命令,在弹出的vim编辑器窗口中,调整commit前面的pick改成drop即可。 但是,不管是调整commit的顺序,还是删除某个commit,都要注意相互关联性的问题,否则会出现很大的问题,因此在使用rebase指令时要特别注意。5。6reset、rebase和revert的区别5。6。1使用revert 先来看看revert指令的用法。如果要取消最后的commit,看看使用revert指令来处理,noedit:表示不编辑commit信息gitrevertHEADnoedit输出内容〔test28a27f7b〕RevertcommittwofilesDate:SatFeb1122:01:32202308002fileschanged,2deletions()deletemode100644one。htmldeletemode100644two。html 这个时候,最后一次commit的文件删掉了,但是会新增一个commit。表明revert指令就是创建一个新的commit,来取消我们不需要的commit。gitlogoneline输出内容8a27f7b(HEADtest2)Revertcommittwofiles3ac54d7committwofiles777cde5teststashde98713test2aeb827f(tag:v1。0)inticommit5。6。2取消revert 1。我们可以再开一个revert来取消上一个revert,但这又会增加一个commit,有点套娃的意思。 2。使用gitresetHEAD5。6。33个指令的区别 指令 是否修改历史记录 说明 reset 是 把当前状态设置成某个指定的commit的状态,通常适用于尚未push的commit rebase 是 用来对commit进行新增、修改、删除等操作,通常适用于尚未push的commit revert 否 新增一个commit来取消另一个commit的内容,原来的commit会保留,通常适用于已经push的commit6。标签(tag) 通常我们开发的软件在完成特定的版本并上线后,就需要使用tag来做标记。6。1添加tag 标签可以分为轻量标签(lightweighttag)和有附注的标签(annotatedtag),都存放与。gitrefstags目录下。轻量标签gittagtag13ac54d7有附注的标签,适用于程序发版时。a:声明为有附注的标签,m:添加说明gittagtagv151d54ffamv16。2删除taggittagdtag17。远程仓库 在这之前的操作,我们都是本地的Git仓库进行的,接下来我们需要把本地Git仓库推送到远程仓库中。7。1推送到Gitee 如果本地还不存在Git仓库,需要新创建一个目录,执行gitinit初始化,然后添加一些文件,执行gitadd和gitcommit,这个时候我们的本地仓库就配置好了。7。1。1为本地仓库添加远程节点 接下来,我们需要为本地仓库配置远程仓库,这样我们才能将本地的内容推送上去。gitremoteaddoriginhttps:gitee。comxxtest2。gitgitremote:是指与远程仓库相关的操作add指令:是指为本地仓库添加一个远程的节点origin:是一个节点代名词,默认使用这个名词,指的就是远程仓库的地址(https:gitee。comxxtest2。git) 执行以上命令,就为本地仓库添加了远程仓库节点,接下来就可以将我们的本地仓库的内容推送到远程仓库了。7。1。2推送到远程仓库意思是将本地仓库的master分支推送到origin远程仓库节点的master分支上,如果远程仓库中master分支不存在,就创建一个。gitpushuoriginmaster上面的指令与这个指令是一样的效果gitpushuoriginmaster:master意思是将本地仓库的master分支推送到origin远程仓库节点的test1分支上,如果远程仓库中test1分支不存在,就创建一个。gitpushuoriginmaster:test1 u:用来设置upstream,指代远程仓库的某个分支,设置之后,下次执行gitpush命令并且不加任何参数时,Git就会知道我们要把本地分支master分支推送到origin远程仓库节点的master分支上,使用起来更加简便。7。2更新远程仓库到本地7。2。1将远程仓库内容拉取到本地gitfetch 执行以上命令,会把远程仓库上更新的内容拉取下来,并让originmaster这个分支指向最新的commit,此时本地仓库的master分支的内容保持不变,所以该命令实际上并没有让本地仓库的内容与远程仓库的内容进行真正的同步。7。2。2同步内容到本地仓库 既然gitfetch拉取下来的内容在originmaster,那么我们将其合并过来,就实现了本地仓库内容与远程仓库内容的同步。gitmergeoriginmaster7。2。3pull命令 gitpull实际上是等于先执行gitfetch操作,然后再执行gitmerge操作。7。2。4删除远程仓库分支gitpushorigin:test17。2。5推送tag到远程仓库 前面我们在本地创建了tag,那么要怎样才能推送到远程仓库呢?gitpushtags 在push的时候加上tags参数,就可以将本地的tag推送到远程仓库了。