Git提交相关内容
在Git提交时,会保存一个提交对象,该对象包括一个指向暂存区内容快照的指针,包括本次提交作者等相关附属信息,包括零个或多个指向该提交对象的父对象指针:首次提交时是没有祖先,普通提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先。在使用git commit新建一个提交对象前。Git会先计算每一个子文件夹的校验和。然后在Git仓库中将这些文件夹保存为树(tree)对象。之后Git创建的提交对象,除了包括相关提交信息。还包括指向这个树对象(项目根文件夹)的指针,如此。它就能够在将来须要的时候,重现此次快照的内容。作些改动后再次提交。那么这次的提交对象会包括一个指向上次提交对象的指针,即本次提交的对象会包括一个指向上次提交的对象的指针。
Git分支简单介绍
Git分支本质不过个指向commit对象的可变指针,所以创建一个分支,实际上便是创建了一个新的分支指针。Git会使用master作为分支的默认名字。
在若干次提交后。就有一个指向最后一次提交对象的master分支,该分支在每次提交时都会自己主动向前移动。同一时候,Git仓库中会保存一个名为HEAD的文件,该文件实质上是一个特别的指针,该特别的指针指向当前正在工作中的本地分支的指针。而Git也正是通过该HEAD指针来知道当前处于哪个分支上工作。而由于Git中的分支实际上仅是一个包括所指对象校验(40个字符长度SHA-1字串)和的文件,所以创建和销毁一个分支很便宜,由于新建一个分支就是向一个文件里写入41个字符(外加一个换行符)。
Git切换分支时注意事项
切换分支的时候应该保持一个清洁的工作区域,即暂存区域和工作文件夹里的改动都被提交到git仓库中了。假设这两个区域还有没有提交的改动。它会和即将检出的分支产生冲突从而阻止Git切换分支。
Git命令选项—merged和—no-merged
在当前分支中使用命令git branch --merged可以刷选出与当前分支已经合并的分支。使用命令git branch –no-merged可以刷选出没有与当前分支合并的分支。
好的Git开发方式
好的Git开发方式应该仅在master分支中保留全然稳定的代码(即已经公布或即将公布的代码)。与此同一时候。创建一个名为develop/next的平行分支专门用于兴许的开发,或仅用于稳定性測试,当代码进入某种稳定状态,便能够把它合并到master分支。
这样。在确保这些已完毕的特性分支能够通过全部測试,而且不会引入很多其它的错误后,就能够并到主干分支中。等待下一次的公布。
特性分支
特性分支是指短期的,用来实现某单一特性或与其相关共组的分支。不论什么规模的项目中都可以使用特性分支。并且可以非常好地帮助开发。
远程分支
远程分支(remote branch)是对远程仓库中的分支的索引。
它们是一些无法移动的本地分支;仅仅有在 Git 进行网络交互时才会更新。远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。远程分支的表示方法: (远程仓库名)/(分支名)。当我们从远程仓库中克隆一个git仓库时,Git会自己主动为你将此远程仓库命名为origin,并下载当中全部的数据,建立一个指向它的master分支的指针,在本地相应的名称为origin/master。但你无法在本地更改数据。接着,Git建立一个属于你自己的本地master分支,始于origin上master分支同样的位置,如此,便能開始工作。
跟踪远程分支
从远程分支checkout
出来的本地分支,称为_跟踪分支(tracking branch)_。
跟踪分支是一种和远程分支有直接联系的本地分支。在跟踪分支里输入git push
,Git 会自行判断应该向哪个server的哪个分支推送数据。反过来。在这些分支里执行git pull
会获取全部远程索引,并把它们的数据都合并到本地分支中来。克隆仓库时,Git会自己主动创建一个名为master的分支来跟踪origin/master。这也正是git
push和git pull開始就能正常工作的原因。当然,也能够设定为其它跟踪分支,命令格式为:git checkout –b <分支名> <远程名>/<分支名>。比如,$gitcheckout –b sf origin/serverfix。在1.6.2版本号以上,能够用—track选项简化。比如,$git checkout --trackorigin/serverfix。即在本地创建一个名为serverfix的分支跟踪远程分支origin/serverfix。
删除远程分支
命令格式: git push <远程仓库名> :<本地分支名>。
比如,$git pushorigin :serverfix,该命令即是删除本地的serverfix分支。
分支的衍合
把一个分支整合到还有一个分支的办法有两种:merge 和 rebase(译注:rebase 的翻译暂定为“衍合”,大家知道就能够了。)。在本章我们会学习什么是衍合。怎样使用衍合,为什么衍合操作如此富有魅力,以及我们应该在什么情况下使用衍合。
主要的衍合操作
请回想之前有关合并的一节(见图 3-27)。你会看到开发进程分叉到两个不同分支,又各自提交了更新。
图 3-27. 最初分叉的提交历史。
之前介绍过。最easy的整合分支的方法是 merge 命令。它会把两个分支最新的快照(C3 和 C4)以及二者最新的共同祖先(C2)进行三方合并,合并的结果是产生一个新的提交对象(C5)。如图 3-28 所看到的:
图 3-28. 通过合并一个分支来整合分叉了的历史。
事实上,还有另外一个选择:你能够把在 C3 里产生的变化补丁在 C4 的基础上又一次打一遍。
在 Git 里,这样的操作叫做_衍合(rebase)_。
有了 rebase 命令。就能够把在一个分支里提交的改变移到还有一个分支里重放一遍。
在上面这个样例中,执行:
$ git checkoutexperiment
$ git rebasemaster
First,rewinding head to replay your work on top of it...
Applying:added staged command
它的原理是回到两个分支近期的共同祖先,依据当前分支(也就是要进行衍合的分支 experiment)兴许的历次提交对象(这里仅仅有一个 C3)。生成一系列文件补丁,然后以基底分支(也就是主干分支master)最后一个提交对象(C4)为新的出发点。逐个应用之前准备好的补丁文件。最后会生成一个新的合并提交对象(C3’),从而改写experiment 的提交历史。使它成为 master 分支的直接下游,如图 3-29 所看到的:
图 3-29. 把 C3里产生的改变到 C4 上重演一遍。
如今回到 master 分支。进行一次快进合并(见图 3-30):
图 3-30. master 分支的快进。
如今的 C3’ 相应的快照。事实上和普通的三方合并。即上个样例中的 C5 相应的快照内容一模一样了。虽然最后整合得到的结果没有不论什么差别,但衍合能产生一个更为整洁的提交历史。假设视察一个衍合过的分支的历史记录,看起来会更 清楚:仿佛全部改动都是在一根线上先后进行的。虽然实际上它们原本是同一时候并行发生的。
一般我们使用衍合的目的,是想要得到一个能在远程分支上干净应用的补丁 — 比方某些项目你不是维护者,但想帮点忙的话。最好用衍合:先在自己的一个分支里进行开发,当准备向主项目提交补丁的时候。依据最新的origin/master 进行一次衍合操作然后再提交,这样维护者就不须要做不论什么整合工作(译注:实际上是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决。),仅仅需依据你提供的仓库地址作一次快进合并,或者直接採纳你提交的补丁。
请注意。合并结果中最后一次提交所指向的快照,不管是通过衍合。还是三方合并。都会得到同样的快照内容,仅仅只是提交历史不同罢了。
衍合是依照每行的改动次序重演一遍改动。而合并是把终于结果合在一起。
有趣的衍合
衍合也能够放到其它分支进行。并不一定非得依据分化之前的分支。以图 3-31 的历史为例。我们为了给服务器端代码加入一些功能而创建了特性分支 server,然后提交 C3 和 C4。然后又从 C3 的地方再添加一个client 分支来对客户端代码进行一些对应改动,所以提交了 C8 和 C9。最后,又回到server 分支提交了 C10。
图 3-31. 从一个特性分支里再分出一个特性分支的历史。
如果在接下来的一次软件公布中,我们决定先把client的改动并到主线中,而暂缓并入服务端软件的改动(由于还须要进一步測试)。这个时候。我们就能够把基于 server 分支而非 master 分支的改变(即 C8 和 C9),跳过 server 直接放到master 分支中重演一遍。但这须要用git rebase 的 --onto 选项指定新的基底分支master:
$ git rebase--onto master server client
这好比在说:“取出 client 分支,找出 client 分支和 server 分支的共同祖先之后的变化,然后把它们在master上重演一遍”。是不是有点复杂?只是它的结果如图 3-32 所看到的,非常酷(译注:尽管client 里的 C8, C9 在 C3之后,但这仅表明时间上的先后,而非在 C3 改动的基础上进一步改动,由于server 和client 这两个分支相应的代码应该是两套文件,尽管这么说不是非常严格,但应理解为在 C3 时间点之后,对另外的文件所做的 C8,C9 改动。放到主干重演。
):
图 3-32. 将特性分支上的还有一个特性分支衍合到其它分支。
如今能够快进 master 分支了(见图 3-33):
$ git checkoutmaster
$ git mergeclient
图 3-33. 快进master 分支,使之包括 client 分支的变化。
如今我们决定把 server 分支的变化也包括进来。我们能够直接把 server 分支衍合到 master。而不用手工切换到 server 分支后再运行衍合操作 — gitrebase [主分支] [特性分支]命令会先取出特性分支server。然后在主分支master 上重演:
$ git rebase master server
于是。server 的进度应用到 master 的基础上。如图 3-34 所看到的:
图 3-34. 在master 分支上衍合 server 分支。
然后就能够快进主干分支 master 了:
$ git checkoutmaster
$ git mergeserver
如今 client 和 server 分支的变化都已经集成到主干分支来了,能够删掉它们了。终于我们的提交历史会变成图 3-35 的样子:
$ git branch-d client
$ git branch-d server
图 3-35. 终于的提交历史
衍合的风险
呃,奇异的衍合也并不是完美无缺,要用它得遵守一条准则:
一旦分支中的提交对象公布到公共仓库,就千万不要对该分支进行衍合操作。
假设你遵循这条金科玉律,就不会出差错。否则。人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
在进行衍合的时候,实际上抛弃了一些现存的提交对象而创造了一些类似但不同的新的提交对象。
假设你把原来分支中的提交对象公布出去。而且其它人更新下载后在其基础上开展工作。而稍后你又用git rebase 抛弃这些提交对象,把新的重演后的提交对象公布出去的话。你的合作者就不得不又一次合并他们的工作,这样当你再次从他们那里获取内容时,提交历史就会变得一团糟。
以下我们用一个实际样例来说明为什么公开的衍合会带来问题。
如果你从一个中央server克隆然后在它的基础上搞了一些开发,提交历史类似图 3-36 所看到的:
图 3-36. 克隆一个仓库,在其基础上工作一番。
如今,某人在 C1 的基础上做了些改变,并合并他自己的分支得到结果 C6,推送到中央server。当你抓取并合并这些数据到你本地的开发分支中后,会得到合并结果 C7,历史提交会变成图 3-37 这样:
图 3-37. 抓取他人提交,并入自己主干。
接下来,那个推送 C6 上来的人决定用衍合代替之前的合并操作;继而又用 git push --force 覆盖了server上的历史,得到 C4’。而之后当你再从server上下载最新提交后,会得到:
图 3-38. 有人推送了衍合后得到的 C4’,丢弃了你作为开发基础的 C4 和 C6。
下载更新后须要合并,但此时衍合产生的提交对象 C4’ 的SHA-1 校验值和之前 C4 全然不同,所以 Git 会把它们当作新的提交对象处理。而实际上此刻你的提交历史 C7 中早已经包括了 C4 的改动内容,于是合并操作会把 C7 和C4’ 合并为 C8(见图3-39):
图 3-39. 你把同样的内容又合并了一遍。生成一个新的提交 C8。
C8 这一步的合并是迟早会发生的,由于仅仅有这样你才干和其它协作者提交的内容保持同步。
而在 C8 之后,你的提交历史里就会同一时候包括 C4 和 C4’,两者有着不同的 SHA-1 校验值,假设用git log 查看历史,会看到两个提交拥有同样的作者日期与说明,令人费解。
而更糟的是,当你把这种历史推送到server后,会再次把这些衍合后的提交引入到中央服务 器,进一步困扰其它人(译注:这个样例中。出问题的责任方是那个公布了 C6 后又用衍合公布 C4’ 的人。其它人会因此反馈双重历史到共享主干,从而混淆大家的视听。)。
假设把衍合当成一种在推送之前清理提交历史的手段,并且只衍合那些尚未公开的提交对象,就没问题。假设衍合那些已经公开的提交对象,并且已经有人基于这些提交对象开展了兴许开发工作的话,就会出现叫人沮丧的麻烦。