免费模板网站哪个好,做一个网站平台的流程是什么,网页制作人员培训课程,中国纳溪门户网站建设项目环境影响一、分支机制简述 要想真正理解Git的分支机制#xff0c;我们要首先回过头来看一下Git是如何存储数据的。 Git并没有采用多个变更集( changeset )或是差异的方式存储数据#xff0c;而是采用一系列快照的方式。当你发起提交时#xff0c;Git存储的是提交对象( commi…一、分支机制简述 要想真正理解Git的分支机制我们要首先回过头来看一下Git是如何存储数据的。 Git并没有采用多个变更集( changeset )或是差异的方式存储数据而是采用一系列快照的方式。当你发起提交时Git存储的是提交对象( commit object),其中包含了指向暂存区快照的指针。提交对象也包括作者姓名和邮箱地址、已输入的提交信息以及指向其父提交的指针。初始提交没有父提交而一般的提交会有一个父提交;对于两个或更多分支的合并提交来说,存在着多个父提交。 为了把上述内容形象化让我们假设有一个包含 了三个文件的目录而你把这些文件都加入到了暂存区并进行了提交。暂存操作会为每个文件计算校验和(SHA-1散列值)并把文件的当前版本保存到Git仓库中( Git把这些数据叫作blob对象),然后把校验和添加到暂存区:
$ git add README test.rb LICENSE
$ git commit -m The initial commit of my project 当执行git commit进行提交时Git会先为每个子目录计算校验和(在本例中只有项目的根目录)然后再把这些树对象( tree object )保存到Git仓库中。Git随后会创建提交对象其中包括元数据以及指向项目根目录的树对象的指针以便有需要的时候重新创建这次快照。 Git的分支只不过是一个指向某次提交的轻量级的可移动指针( movable pointer )。Git默认的分支名称是master。当你发起提交时就有了一个指向最后一次提交的master分支。每次提交时它都会自动向前移动。 注意在Git中 master分支其 实并不是一个特殊的分支它与其他分支没什么区别。几乎每个Git仓库都拥有该分支这只是因为git init命令会默认创建该分支而大多数人都懒得去更改它。 1.1、创建新分支 当你创建新分支时会发生什么? 实际上Git会创建一个可移动的新指针供你使用。现在假设你要创建一个名为testing的新分支。 这可以通过git branch命令实现:
$ git branch testing 这会创建一个指向当前提交的新指针。 Git如何知道你当前处在哪一分支上呢?实际上Git维护着一个名为HEAD的特殊指针。请注意,这里HEAD的概念与你可能了解的其他版本控制系统(例如Subversion或CVS )中的HEAD有着很大的不同。在Git中, HEAD是一个指向当前所在的本地分支的指针。在上述例子中,你仍然处在master分支上。这是因为git branch命令只会创建新分支而不会切换到新的分支上去。 可以简单地通过git log命令查看各个分支当前所指向的对象。这需要用到选项--decorate
$ git log --oneline --decorate
f30ab (HEAD, master, testing) add feature #32 - ability to add new
34ac2 fixed bug #1328 . stack overflow under cer tain conditions
98ca9 initial conmit of my project 可以看到master 和testing分支就显示在f30ab提交旁边。
1.2、切换分支 要切换到已有的分支,可以执行git checkout命令。现在让我们切换到新建的testing分支上去如下所示。
$ git checkout testing 这条命令会改变HEAD指针使其指向testing分支。 这么做意义何在? 好现在让我们再提交一次:
$ vim test.rb
$ git commit -a -m
made a change 现在就有意思了 testing分支已经向前移动然而inaster分支仍然指向你之前执行git checkout切换分支时所在的提交。让我们再切换到master分支
$ git checkout master 以上命令一共做了两件事。它会把HEAD指针移回到master分支,还会把工作目录的文件恢复到master分支指向的快照的状态。这也就意味着从这时起你所做出的修改将基于项目的较老 版本。总而言之上述操作回滚了你在testing分支上所做的工作使你能向另一个方向进行开发工作。 分支切换会更改工作目录文件 请注意当你在Git中切换分支时工作目录的文件会被改变。如果你切换到较旧的分支工作目录会被恢复到该分支上最后一次提交的状态。如果Git在当前状态下无法干净地完成恢复操作就不会允许你切换分支。 让我们做出一些改动再提交一次 如下所示。
$ vim test.rb
$ git commit -a -m made other changes 现在项目历史已经产生了分叉。你创建并切换到了新的分支在新分支上做了一次修改然后又切换回你的主分支并做了另一次修改。这两次修改是在不同的分支上做出的,彼此互相分离。你可以在分支间自由切换当你准备好了之后就可以合并这些修改。只需使用简单的branch、checkout和commit命令就实现了上述操作。 Git中的分支实际上就是一个简单的文件 其中只包含了该分支所指向提交的长度为40个字符的SHA-1校验和。正因为如此, Git分支创建和删除的成本很低。创建新分支就如同向文件写入41个字节( 40个字符外加一个换行符)一样又简单又快速。 这样的效率与其他大多数较老的版本控制系统处理分支的方式形成了鲜明对比。其他系统大多会把整个项目的所有文件复制到一个新的目录中。根据项目的大小这样的操作会花费几秒钟甚至几分钟的时间。与之相反在Git中分支操作几乎都是即刻完成的。而且,由于提交时Git保存了父对象的指针当进行合并操作时Git会自动寻找适当的合并基础操作起来非常简单。有了上述特性作为保障Git鼓励开发人员经常创建和使用分支。
二、基本的分支与合并操作 现在我们要展示一个简单的分支和合并案例其中的工作流可供真实项目借鉴。要遵循的步骤如下
(1)在网站展开工作;(2)为新需求创建分支;(3)在新分支上展开工作;
这时你接到一个电话说项目有一个严重问题需要紧急修复。你随后会这样做:
(1)切换到你的生产环境分支;(2)创建新的分支来进行此次问题的热修补工作;(3)通过测试后合并热修补分支并推送到生产环境中;(4)切换回之前的需求分支上继续工作。
2.1、基本的分支操作 首先假设你在所工作的项目上已经完成了一些提交。 这时你决定要修复公司所用的问题跟踪系统中的#53问题。可以使用带有-b选项的git checkout命令来创建并切换到新分支上
$ git checkout -b iss53
switched to a new branch iss53
上面这条命令相当于
$ git branch iss53
$ git checkout iss53 接下来继续工作并又进行了几次提交。这么做会让iss53分支指针向前移动这是因为你当前检出的就是iss53分支(换句话说HEAD指针当前指向该分支)
$ vim index.html
$ git commit -a -m added a new footer [issue 53] 现在你接到一个电话说网站有个问题需要立即修复。如果没有Git的帮助你要么把你的修复补丁和iss53的变更一起部署, 要么就花费大量精力去恢复之前针对iss53所做的工作好让你制作的修复补丁单独上线。如今你要做的就是切换回master分支即可。 但先别急在你切换分支之前要注意的是如果你的工作目录或者暂存区存在着未提交的更改并且这些更改与你要切换到的分支冲突Git就不允许你切换分支。在切换分支时最好是保持一个干净的工作区域。稍后我们会介绍几种绕过这个问题的办法:储藏和修订提交。就现在而言,让我们假定你已经提交了所有修改这样你就可以切换回master分支了:
$ git checkout master
Switched to branch master 此时项目的工作目录就与你开始处理#53问题之前的状态一模一样了你就可以集中精力制作热补丁了。这里有一点需要强调当你切换分支时Git会把工作目录恢复到你切换到的分支上最后一次提交时的状态。
接下来需要制作热补丁。让我们创建hotfix分支并在这个分支上展开修复工作
$ git checkout -b hotfix
Switched to a new branch hotfix
$ vim index. html
$ git commit -a -m fixed the broken email address
[hotfix 1fb7853] fixed the broken email address
1 file changed, 2 insertions() 你可以运行测试来确保热补丁的效果无误然后将其合并到master分支,以便部署到生产环境。使用git merge命令来完成上述操作
$ git checkout master
$ git merge hotfix
Updating f42c576. 。3a0874c
Fast- forward
index.html | 2
1 file changed, 2 insertions() 你会注意到合并时出现了“fast-forward的提示。由于当前所在的master分支所指向的提交是要并入的hotfix分支的直接上游因而Git会将master分支指针向前移动。换句话说当你试图去合并两个不同的提交而顺着其中一个提交的历史可以直接到达另一个提交时Git就会简化合并操作直接把分支指针向前移动因为这种单线历史不存在有分歧的工作。这就叫作“fast-forward。 现在你的变更已经进入了master分支所指向的提交快照可以部署补丁了。 在部署了这次极其重要的热修复补丁之后,你准备要切换回之前被打断的工作上去。不过先别急首先你要把已经用不着的hotfix分支删除该分支和master分支指向的位置相同。使用git branch的 -d 选项来删除这个分支:
$ git branch -d hotfix
Deleted br anch hotfix (3a0874c).
现在你可以切换回之前未完成的#53问题分支并且继续进行工作
$ git checkout iss53
Switched to branch iss53
$ vim index.html
$ git commit -a -m finished the new footer [issue 53]
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion() 值得注意的是iss53分支并不包含你在hotfix分支上做过的工作。如果需要把上述修补工作并入iss53就需要执行git merge master使得master分支合并到iss53中或者可以等到要把iss53合并回master分支时再把热修补的工作整合进来。
2.2、基本的合并操作 假设现在#53的工作已经完工可以合并回master分支了。这次的合并操作实现起来与之前合并hotfix分支的操作差不多。只需要切换到master分支上,并执行git merge命令即可
$ git checkout master
Switched to branch master
$ git merge iss53
Merge made by the recursive strategy.
index. html| 1
1 file changed, 1 insertion() 这次合并看起来与之前hotfix的合并有点不一样。在这次合并中,开发历史从某个早先的时间点开始有了分叉。由于当前master分支指向的提交“并不是iss53分支的直接祖先,因而Git必须要做一些额外的工作。本例中Git执行的操作是简单的三方合并。三方合并操作会使用两个待合并分支上最新提交的快照以及这两个分支的共同祖先的提交快照(如图3-16所示)。 与之前简单地向前移动分支指针的做法不同这一次Git会基于三方合并的结果创建新的快照然后再创建一个提交指向新建的快照。这个提交叫作“合并提交”。合并提交的特殊性在于它拥有不止一个父提交。 值得注意的是Git会自己判断最优的共同祖先并将其作为合并基础。这种做法与诸如CVS或Subversion ( 1.5以前的版本)等较老的工具不同。在这些较老的工具中开发者必须自己找出最优的合并基础来执行合并操作。以上区别使得Git在合并操作方面比其他工具要简单得多。现在你的工作成果已经合并进来了你就不再需要ss53分支了。你可以在问题追踪系统里面关闭这个问题并删除分支。
$ git branch -d iss53
2.3、基本 的合并冲突处理 有时候上述合并过程并不会那么顺利。如果你在要合并的两个分支上都改了同一个文件的同一部分内容Git就没办法干净地合并这两个分支。假设你在#53问题上的工作和在hotfix分支上的工作都修改了同一文件的同一部分那么就会引起合并冲突你会看到类似下面的输出
$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.htnl
Automatic merge failed; fix conflicts and then commit the result. . Git并没有自动创建新的合并提交。它会暂停整个合并过程等待你来解决冲突。在发生了合并冲突后要查看哪些文件没有被合并可以执行git status 任何存在着未解决的合并冲突的文件都会显示成未合并状态。Git会给这些有冲突的文件添加标准的待解决冲突标记以便你手动打开这些文件来解决冲突。可以看到冲突文件包含一个类似下面这样的区域: HEAD:index.html
div idfootercontact : email. suppor tgi thub . com/divdiv id footer
please contact us at suppor tgithub . Com
/diviss53: index. html 上面这段代码中HEAD版本的内容显示在上半部分( 以上的部分), iss53分支的内容则在下半部分。其中HEAD指向的是master分支因为你在执行merge 命令之前已经切换到该分支。可以选择使用任一版本的内容或是自已整合两者的内容来解决冲突。例如你可以把整段内容替换成以下代码:
div id footer
please contact us at email. suppor tgithub . com
/div 这种解决方法实际上是把两个版本的内容各取一部分整合在一起,并去掉了 和这三行内容。在解决了每个冲突文件的所有冲突部分后就可以执行git add来把每个文件标记为冲突已解决状态。在Git中这可以通过把文件添加到暂存区来实现。
若要使用图形化工具解决冲突可以执行git mergetool该命令会启动相应的图形化合并工具并引导你一步步解决冲突:
$ git mergetoolThis message is displayed because merge. tool is not configured .
See git mergetool --tool-help or git help config for more details .
git mergetool will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisenerge gvindiff diffuse diffmerge ecnerge p4merge
Merging:
index .htmlNormal merge conflict for index.html :{local}: modified file{remote}: modified file
Hit return to start merge resolution tool (opendiff): 如果你想选择除默认工具之外的其他合并工具(在本例中Git使用的是opendiff工具因为所处的运行环境是Mac),则可以在上方one of the following tools的提示下找到所有可用的合并工具列表。键入要使用的工具名就可以了。 当退出合并工具时Git会询问合并是否已经成功完成。如果合并成功它就会将合并后的文件添加到暂存区并将其标记为冲突已解决的状态。可以再次执行git status来确认所有的冲突都已解决
$ git status
On branch naster
All conflicts fixed but you are still merging.
(use git commit to conclude merge)
Changes to be committed:
nodified: index. html 如果觉得满意了并确认了所有冲突都已解决相应的文件也进入了暂存区就可以通过git commit命令来完成此次合并提交。默认的提交信息如下所示
Merge branch iss53
Conflicts:
index.html
#
# It looks like you may be comnitting a merge。
# If this is not correct, please remove the file
# .git/MERGE HEAD
# and try again.# Please enter the commit message for your changes. Lines starting
# with # will be ignored, and an enpty message aborts the comit.
# On branch master
# All conflicts fixed but you are still merging .
#
# Changes to be comnitted: .
# modified: index.html
#如果想给将来审阅此次合并的人一点帮助那么可以修改上述合并信息提供更多关于你如何进行此次合并的细节比如你做了什么以及为什么这么做。
三、分支管理 到现在为止你已经尝试过创建、合并以及删除分支。现在让我们试试一些分支管理工具。这些工具在经常使用分支时会很有用。 git branch命令并不只是可以用来创建和删除分支。如果你执行不带参数的git branch命令就会得到当前所有分支的简短列表如下所示
$ git branchiss53
* mastertesting 请留意master分支前面的 * 字符它表明了你当前所在的分支(即HEAD指向的分支)。这意味着如果你现在进行一次提交, mnaster分支指针会随着你的新提交向前移动。要看到每个分支上的最新提交可以执行git branch -v
$ git branch -viss5393b412c fix javascript issue
* master7a98805 Merge branch iss53testing 782fd34 add scott to the author list in the readmes 另外两个很有用的选项是--merged和--no-nerged。这两个选项分别是筛选已并入当前分支的所有分支和筛选尚未并人的所有分支。要查看有哪些分支已经并入当前分支,可以执行git branch --merged
$ git branch --mergediss53
* master 由于之前iss53已被合并因此它出现在了上述列表中。一般来说对于前面没有 * 的分支,可以使用git branch -d把它们全部删除。你已经把这些分支上的工作纳入到了其他分支中所以不会因此丢失任何东西。 要查看包含尚未合并的工作的所有分支可以使用git branch --no-merged
$ git branch --no-merged
testing 上述命令会显示出另一个分支。因为该分支包含了尚未合并到主线的工作所以git branch -d并不能成功删除它
$ git branch -d testing
error: The branch testing is not fully merged .
If you are sure you want to delete it, run git branch -D testing. 如果你确实想要删除该分支并丢弃其上的所有工作可以按照上述输出的提示信息使用-D选项强制删除。
四、与分支有关的工作流 既然你已经学会了基本的分支和合并操作,应该用它们来做点什么呢?在本节中,我们会讲解一些常见的工作流。这些工作流之所以能够存在要得益于Git的轻量级分支机制。你可以根据自己项目的实际情况自由选用它们。
4.1、长期分支 由于Git简洁的三方合并机制在较长的一段时间内多次把一个分支合并到另一分支是很容易的操作。这意味着你可以拥有多个开放的分支以用于开发周期的不同阶段;你也可以经常性地把其中某些分支合并到其他的分支去。 很多使用Git的开发者都喜欢用这种方式构建他们自己的工作流。例如其中一种流程就是在master分支只存放稳定版的代码即已经发布版本或即将发布版本的代码。他们还会使用另一个叫作develop或next的平行分支用于开发或是用于测试代码的稳定性。这个分支不会一直保持稳定版本不过一旦它达到稳定版本的状态就可以把它合并到master分支去。这样的分支也被用来接受主题分支(短期分支例如之前的s53分支)的合并来确保这些新开发的特性能够通过所有测试而不会引发新的错误。 实际上,我们刚才谈论的是随着你的提交操作而不断移动的分支指针。稳定的分支会在提交历史中较为靠后而前沿的开发分支会较为靠前。 可以把这些分支认为是不同的工作筒仓,几组提交经过完整的测试后就会从一个筒仓移动到另一个更稳定的筒仓中去。 可以按照上述方式构建几个不同稳定性级别的分支。有些大型项目有名为proposed (提议)或pu( proposed updates,提议的更新)的分支。这个分支会整合那些还没有准备好并入next或master的分支。这么做背后的缘由是不同的分支拥有不同程度的稳定性。当分支达到更高的稳定程度时它就被合并到更高级别的分支中去。所以虽然拥有多个长期分支并非必须但这样很实用特别是当你开发大型项目或复杂项目时更是如此。
4.2、主题分支 与上述长期分支有所不同在任何规模的项目上主题分支( topic branch)都非常有用。主题分支是指短期的、用于实现某一特定功能及其相关工作的分支。你在之前的版本控制系统里可能没有使用过主题分支因为一般而言创建和合并分支的操作成本太高了。但是在Git中一天里多次进行分支的创建、使用、合并和删除操作是很常见的。 在示例中创建ss53和hotfix分支时已经见识到上述主题分支了。当时你在这两个分支上进行过几次提交然后把它们合并到主干分支,最后把它们删除。这种技术使你能够快速进行完整的上下文切换。同时由于你的工作分散在不同的筒仓中,并且每个分支上的更改都与它的目标特性相关使得在代码审查等活动中能够更容易读懂所做的更改。你可以把这些更改保留在主题分支中几分钟、几天甚至几个月等它们准备就绪时再合并到主干你也不需要去管这些分支的创建或是开发的先后顺序。 现在请看一个例子先是在master分支上进行了工作之后为了实现某个需求创建并切换到主题分支iss91并在其上做了一些开发。在此之后你又为了尝试另一种实现上面需求的方式创建并切换到了新的分支iss91v2。接着你又切换回master分支并继续工作了一阵子最后你创建了新的分支dumbidea来实现你的一个不确定好不好的想法。你的整个提交历史看起来就类似图3-20。 现在假设你喜欢实现需求的第二种方案( iss91v2),并决定使用该方案。同时你向同事展示了你在dumbidea分支上所做的工作他们认为这是天才之作。这时你可以舍弃一开始的iss91分支(C5和C6提交也会一同丢失) 并把另两个主题分支并入主干。这时的提交历史如图3-21所示。 要注意上述所有操作中涉及的分支全部都是本地分支。你进行的分支和合并操作也全都是只在本地Git仓库上进行的没有涉及任何与服务器端的通信。
五、远程分支 远程分支是指向远程仓库的分支的指针,这些指针存在于本地且无法被移动。当你与服务器进行任何网络通信时它们会自动更新。远程分支有点像书签,它们会提示你上一次连接服务器时远程仓库中每个分支的位置。 远程分支的表示形式是(remote)/(branch)。例如如果你想查看上次与服务器通信时远程origin仓库中的naster分支的内容就需要查看origin/master分支。假设你与合作伙伴协同开发某个需求而他们将数据推送到了ss53分支。这时你也可能有一个自已本地的iss53分支,但是服务器端的分支其实指向的是origin/iss53。 上述内容可能有点令人困惑所以让我们再来看一个例子。假设你有一台网络上的Git服务器地址是git.ourcompany.com。如果你将内容从这台服务器上克隆到本地Git的clone命令会自动把这台服务器命名为origin,并拉取它的全部数据然后会在本地创建指向服务器上master分支的指针并命名为origin/master。Git接着也会帮你创建你自己的本地master分支。这个分支一开始会与origin上的master分支指向一样的位置这样你就可以在它上面开始工作了。 origin并非特殊名称 与master分支名称一样origin在Git中也没有什么特殊的含义。master被广泛使用只是因为它是执行git init时创建的初始分支的默认名称。origin也一样是执行git clone时远程仓库的默认名称。如果你执行的不是上述命令而是git clone -o booyah,那么你的默认远程分支就会是booyah/master。 假设你在本地的master分支上进行了一些工作与此同时,别人向git.ourcompany.com推送了数据更新了服务器上的master分支,这时你的提交历史就与服务器上的历史产生了偏离。而且只要你不与服务器通信你的origin/master指针就不会移动。 要与服务器同步需要执行git fetch origin命令。这条命令会查询“origin” 对应的服务器地址(本例中是git.ourcompany.com),并从服务器取得所有本地尚未包含的数据然后更新本地数据库最后把origin/master指针移动到最新的位置上去。 为了演示使用多个远程服务器的项目以及远程分支在这样的项目上是什么样子,让我们假设你还有另一个仅供敏捷开发小组使用的内部Git服务器。这台服务器的地址是giteamnl.ourcompany.com。如第2章所述可以用git renote add命令把它作为新的远程服务器添加到正在开发的项目中。然后把它命名为teamone作为该服务器URL的简短名称。 现在可以执行git fetch teamone获取到远程的teamone服务器上的所有本地不存在的数据。由于到目前为止上述teamone服 务器上的数据在origin服务器上全部都有, Git并不会真正拉取到数据只会创建名为teamone/master的远程分支指向teamone服 务器上的master分支的最新提交。 5.1、推送 当需要同别人共享某个分支上的工作成果时就要把它推送到一个具有写权限的远程仓库。你的本地分支并不会自动同步到远程仓库,必须要显式地推送那些你想要与别人共享的分支。这样一来你可以使用私有分支做一些不想与别人共享的工作而仅仅推送那些需要与别人协作的主题分支。 假设你有一个叫作serverfix的分支需要与其他人协作开发你可以按照之前推送第一个分支的方法推送它。只需执行git push (remote) (branch)命令 即可:
$ git push origin serverfix
Counting objects: 24 done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.
Total 24 (delta 2) reused 日(delta 0)
To https: //github. com/ schacon/simplegit
* [new branch]
serverfix - serverfix 上述命令实际上是一个简化的写法。Git会 自动把分支名称serverfix扩展成refs/heads/serverfix:refs/heads/serverfix。上述操作的含 义是:“ 把本地的serverfix分支推送到远程的serverfix分支上,以更新远程数据。”refs/heads/这部分一般情况下你都可以省略不写这部分。也就是说你可以执行git push origin serverfix: serverfix,这条命令可以达到与之前的命令一样的效果。类似这样的命令格式可以用来将本地分支推送到不同名称的远程分支。比如如果你不想把远程分支命名为serverfix,就可以执行git push origin serverfix: awesomebranch,把你的本地serverfix分支推送到远程的awesomebranch分支上去。 不用每次都键入密码 如果你使用HTTPS的远程服务器地址进行数据推送那么Git服务器会要求你提供用户名和密码以进行身份验证。默认情况下需要在终端上键入上述身份信息,服务器会据此信息判断你是否有权限推送数据。 如果不想每次推送时都键入密码可以设置一个“凭据缓存”( credential cache)。最简单的设置方法是把凭据信息暂时保存在内存中几分钟这只需要执行git config--global credential.helper cache命令即可。 下一次与你协作的同事从服务器上拉取数据时他就会获取到一个指向服务器上serverfix分支的指针这个指针就叫作origin/serverfix:
$ git fetch origin
remote: Counting objects: 7, done.
renote: Compressing objects: 100% (2/2), done.
renote: Total 3 (delta 0)reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://gi thub. com/schacon/simplegit
* [new branch]
serverfix
- origin/serverfix 要注意的一点是,当获取服务器上的数据时,如果获取到了本地还没有的新的远程跟踪分支,这时Git并不会自动提供给你该分支的本地可编辑副本。换句话说在上述例子中在本地就不会自动创建新的serverfix分支,而只是拥有了指向origin/serverfix的指针,不能直接作出修改。 要把该分支上的工作合并到你的当前工作分支可以执行git merge origin/serverfix。 如果你想要创建自己的本地serverfix分支以便在其上工作可以执行以下命令。
$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track renote branch serverfix from origin.
Switched to a new branch serverfix 这样做会基于origin/serverfix创建本地分支使你可以在其上工作。
5.2、跟踪分支 基于远程分支创建的本地分支会自动成为跟踪分支( tracking branch),或者有时候也叫作上游分支( upstream branch )。 跟踪分支是与远程分支直接关联的本地分支。如果你正处在一个跟踪分支上并键入git push,Git会知道要将数据推送到哪个远程服务器上的哪个分支。同样地执行git pull时Git也能够知道从哪个服务器上拉取数据并与本地分支进行合并。 当你克隆一个远程仓库时, Git默认情况下会自动创建跟踪着远程origin/master分支的本地master分支。除此之外,你也可以选择自己设置其他的跟踪分支比如跟踪其他远程服务器上的分支,或是设置成不跟踪master分支。之前看到的例子是一种最简单的情况即执行git checkout-b [branch] [renotename]/[branch]。 这种操作很常见所以Git提供了-- track的简略表达方式:
$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch serverfix 实际上该操作是如此常见以至于Git做了进一步 的简化。当你试图执行分支切换操作时如果该分支尚未被创建,并且该分支名称和某个远程分支名称一致, 那么Git会帮你创建跟踪分支。
$ git checkout serverfix
Branch serverfix set up to track renote branch serverfix from origin.
Switched to a new branch serverfix 要想让创建的本地分支的名称与对应的远程分支名称不一样,可以用我们一开始提供的命令形式来指定不同的本地分支名称:
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch sf 执行完上述命令后你的本地分支sf就会从origin/serverfix上获取数据。如果想给本地已存在的分支设置跟踪分支,或者要更改本地分支对应的远程分支可以使用git branch命令的-u或是- -set-upstream- to选项设置任意远程分支。
$ git branch -u origin/serverfix
Branch serverfix set up to track renote branch serverfix from origin. 上游分支的简单写法 如果你已经设置好上游分支,就可以通过{upstream}或{u}的简略写法来使用它。例如假设你在master分支上并且该分支跟踪着origin/master,你就可以使用git merge {u}来代替git merge origin/master。 可以使用git branch的-vv选项来查看已经设置了哪些跟踪分支。该命令将会输出所有本地分支的列表还会列出每个分支跟踪的远程分支信息以及本地分支是否领先于或落后于远程分支的信息。
$ git branch -vviss537e424c3 [origin/iss53: ahead 2] forgot the bracketsmaster1ae2a45 [origin/master] deploying index fix
* serverfix f8674d9 [ teanone/server-fix-good: ahead 3, behind 1] this should do ittesting5ea463a trying something new 从上述输出信息中可以看出iss53分 支跟踪着远程的origin/iss53分支并且“领先”两次提交。“领先”两次提交的意思是本地分支上有两次提交还没有被推送到服务器端。我们还可以看出本地的master分支跟踪着origin/master并且处于与远程分支同步的状态。接下来我们看见的是serverfix分支它跟踪着teamone服务器上的server-fix-good分支并且领先三次提交同时也落后一次提交。上面的意思是服务器上有一次提交的更改还没有合并到本地并且有三次本地的提交还没有推送到服务器。最后看到的是testing分支它并没有跟踪远程的任何分支。 要注意的是上述这些信息是从上次你从各个远程服务器读取数据后开始计算的。也就是说上面执行的这条命令并不会与服务器通信以获取最新信息而只是提供给你本地缓存中的信息。如果你需要最新的“ 领先和落后多少次提交”的信息就需要在执行命令前先从所有远程服务器中读取数据。这可以通过执行$ git fetch - all; git branch -vv命令来完成。
5.3、 拉取 git fetch命令会拉取本地没有的远程所有最新更改数据但这条命令完全不会更改你的工作目录。它只会从服务器上读取数据然后让你自己进行合并。除此之外还有一个git pull命令,这条命令在大多数情况下基本等同于执行git fetch之后紧跟着执行了git merge。 如果你拥有5.2节中演示过的跟踪分支(可以手动设置,或是通过clone或checkout命令而得到)执行git pull时Git就会读取上游服务器和分支上的数据并尝试着将远程分支上的修改合并到本地。 一般来说显式地直接使用fetch和merge命令比使用git pull要更好因为git pull的机制会常常使人迷惑。
5.4、删除远程分支 当你和你的同事已经完成一个功能并且把工作合并到了远程的master分支(或其他稳定版本代码分支)之后你已经不再需要包含这个功能的远程分支了。可以通过git push的--delete选项来删除远程分支。例如如果需要删除远程服务器上的serverfix分支需要执行以下命令。
$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
-[deleted] serverfix 基本上可以说以上命令只是删除了远程服务器上的分支指针。Git会保留数据一段时间直到下一次触发垃圾回收。所以即使误删了分支一般来说也很容易进行恢复。
六、变基 在Git中,要把更改从一个分支整合到另一个分支,有两种主要方式合并merge )和变基( rebase )。在本节中你将学到什么是变基、如何使用变基、变基的强大之处以及变基操作不适用的场景。
6.1、基本的变 基操作 回顾一下2.2 节中的例子,在这个例子中你的提交历史产生了偏离在两个不同的分支上分别都进行了提交。 之前我们讲过要整合不同的分支最简单的办法就是使用merge命令。该命令会对两个分支上的最新提交快照( C3和C4)以及这两个提交快照最近的共同祖先(C2),进行一次三方合并并创建一个新的合并提交。 实际上除了上述方式之外还有一种方式你可以把C4提交的更改以补丁形式应用到C3提交上。在Git中这就叫作变基操作。该操作使用的是rebase命令会把某个分支上所有提交的更改在另一个分支上重现一遍。
在本例中你要执行以下命令:
$ git checkout experinent
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged conmand 变基的工作原理是首先找到两个要整合的分支(你当前所在的分支和要整合到的分支)的共同祖先然后取得当前所在分支的每次提交引入的更改( diff),并把这些更改保存为临时文件这之后将当前分支重置为要整合到的分支最后在该分支上依次引人之前保存的每个更改。 现在你可以回到master分支进行快进合并( fast-forward merge ):
$ git checkout master
$ git merge experinent 到此为止图3-30中C4所指向的提交快照的内容与之前采用merge方法得到的C5快照是完全一样的。也就是说两种方法最终得到的整合结果是没有区别的,但使用变基的方式可以获得更简洁的提交历史。变基后得到的分支的提交历史看起来是一条线 就好像所有的工作都是顺序进行的即使-开始的情况其实是存在两条平行的开发历史线。 在需要确保你提交的更改能够干净地应用在远程分支上时经常会用到变基。例如你想要为某个项目贡献更改但该项目并不受你控制和维护。在这种情况下你会在本地分支进行开发工作然后在准备好把补丁提交到项目主干时就要把你的工作变基到origin/master上。这么做可以让项目的维护者不用去做任何的整合工作而只进行简单利落的快进合并。要注意不管是变基操作后最新的提交,还是合并操作后最终的合并提交,这两个提交的快照内容是完全- -样的 这两种操作的结果区别只是得到的提交历史不-样。 总结下 变基操作是把某条开发分支线上的工作在另一个分支线上按顺序重现。而合并操作则是找出两个分支的末端并把它们合并到一起。
6.2、更有趣的变基操作 在变基时还可以把分支上的工作在变基目标分支之外的分支上重现。举例来说假如你有类似图3-31中所示的提交历史。你为了给项目的服务器端增加某个功能创建了主题分支server ,并进行了提交。接下来你为了进行一些客户端功能的改变,在server分支基础上又创建了新的分支client,并进行了几次提交。最后你切换回了server分支,并又进行了几次提交。 现在你决定要把客户端主题分支上的更改合并到主线开发分支并准备发布但又不想合并服务器端的未经测试的更改。可以用git rebase的--onto选项让客户端主题分支上独有的工作( C8和C9)在master分支上重现。
$ git rebase --onto master server client 上面这条命令的意思大致是:“将当前分支切换到client分支,并找出client分 支和server分支的共同祖先提交然后把自从共同祖先以来client分支上独有的工作在master分支上重现。”说起来有点复杂但命令的执行效果还是很酷的。 现在你可以对你的master分支进行快进操作了(见图3-33),如下所示:
$ git checkout master
$ git merge client 假设我们需要把server分支的工作也整合进来。你可以通过git rebase [basebr anch] [topicbranch]命令直接对该分支执行变基操作而不需要先切换到该分支。该命令会读取主题分支( server)上的更改并在基础分支( master)上重现:
$ git rebase master server
这会把server分支的工作在master分支上重现如图3-34所示。 在这之后就可以快进基础分支(master) 了:
$ git checkout master
$ git merge server 由client分支和Iserver分支上的所有工作都已经被整合到主干分支现在就可以把这两个用不着的分支删除了。
$ git branch -d client
$ git branch -d server6.3、变基操作的潜在危害 变基操作可以带来种种好处。但它并非完美无缺其缺点可以总结成一句话: 不要对已经存在于本地仓库之外的提交执行变基操作。 如果你听取上述忠告那就万事大吉。否则同事会埋怨你朋友和家人会鄙视你。这是因为在执行变基操作时实际上是抛弃了已有的某些提交随后创建了新的对应提交。新提交和原有的提交虽然内容上相似,但实际上它们是不同的提交。假设你已经把你的提交推送到远端然后其他人拉取了这些提交内容,并以此为基础开始进行工作。随后你使用git rebase命令进行了变基操作,改写了你之前的提交并重新向远端推送数据。这时你的同事就不得不重新整合他们的工作如果你随后试图去拉取他们的工作并整合事情会变得更糟糕。 下面是一个对已经公开发布的工作进行变基操作的例子,看看这会造成什么样的问题。假设你从远程服务器上克隆下来一个仓库并在其上做了一些工作。 现在,某位同事也进行了-些开发工作,其中包括- -次合并操作。他接着把这些工作推送到了中央服务器。你从服务器上拉取了数据并把新的远程分支与你的工作进行合并最后得到的提交历史如图3-37所示。 接下来之前推送提交的这位同事决定改用变基操作替代之前使用的合并操作,于是他使用了git push --force来覆盖服务器上已有的提交历史。接着你从服务器上拉取了这些新提交如图3-38所示。 现在你们就碰上麻烦了。如果你执行git pull,就会创建一个包括了两条提交历史记录的合并提交这会使Git仓库看起来如图3-39所示。 这时候如果你执行git log,就会看到其中有两个提交拥有着同样的作者、日期和提交信息让人摸不着头脑。而且,如果你把现在的提交历史推送到服务器就会再-次将这些变基后的提交引入中央服务器,增加他人的困惑。我们可以基本假定其他的开发人员不需要提交历史中的C4和C6提交这也是为什么这两个提交一开始会被变基。
6.4、只在需要的时候执行变基操作 如果你确实遇到了这种情况Git有一些高级的办法可以帮助你。如果某个同事强行推送了某些更改从而覆盖了你自己的提交这时你遇到的挑战就是判断出哪些是你自己的提交,哪些是被别人覆盖了的提交。 实际上Git除了会计算提交的SHA-1校验和还会计算提交引入的“补丁”( patch)的校验和这叫作patch-id。 如果你拉取了一些被重写了的提交 并基于同事的一些新的提交执行变基操作, Git常常可以成功判断出哪些是你独有的提交并把它们应用到新的分支上去。
例如在之前描述的场景(图3-38)中我们并不执行合并操作而是执行git rebase teanone/masterGit将 会执行以下操作。
判断出分支上哪些工作是本地独有的(C2、C3、C4、C6、C7)。判断出哪些提交不是合并提交(C2、C3. C4)。判断出哪些提交并没有被重写到新的分支上(只有C2和C3符合条件,因为C4和C4 是相同的补丁)。把最后符合条件的提交应用到teamone/master上去。
与我们在图3-39中看到的结果不同我们将会看到类似图3-40所示的结果。 这种解决方法能够使用的前提是你的同事提交的C4和1C4是基本相同的补丁。否则Git的变基操作也无法判断出这两个提交是重复的就会再增加一个类似C4的补丁提交(该提交多半会引入合并冲突因为提交引人的更改或多或少已存在于仓库中)。 要使用上述解决方法可以执行git pull -rebase, 而不是通常的git pull。 你也可以手动来进行这些步骤:先执行git fetch, 之后再执行git rebase teamone/master. 如果你使用git pull时希望将--rebase设置为默认选项可以通过类似git config --globalpull.rebase true这样的命令来设置pull.rebase选项的值。 总之如果你将变基操作看作在推送数据前整理和处理提交的一个手段,并且你只对那些仅存在于本地还没有公开的提交进行变基操作那么一切都不会有问题。反之如果你对那些已经推送了的提交执行变基操作,而这时其他人可能已经基于这些提交进行了自己的开发工作,那么你就可能遇到很大的麻烦也会遭到同事的批评。 如果你或是同事确实遇到了这种麻烦请让所有人了解并执行git pull --rebase以此来减轻痛苦。
6.5、变基操作与 合并操作的对比 现在你已经了解了如何使用变基操作和合并操作你可能不清楚到底哪个更好。在回答这个问题之前让我们先看看“提交历史”到底意味着什么。 有一种观点认为Git仓库的提交历史就是实际发生过的事件的记录。它是一个记载着历史的“史书”自有其价值而且不能随意篡改。从这个角度来说不应该允许更改提交历史因为这样做就是在谎报实际发生的事情。而如果提交历史中有一大堆复杂错乱的合并提交,那么该怎么办呢?这种观点认为既然这些提交已经实际发生那么它们就应该被完整保存记录以供后来者查阅。 另一种相反的观点则认为提交历史是关于项目如何被构建的故事。正如你并不会直接发布你写的书的初稿如何构建并维护软件项目的过程手册也应该被细心地编辑和校对。这也就是为什么要使用类似rebase和filter -branch这种命令操作来改变整个项目的历史叙事使得后来者能够更好地理解项目的构建。 现在让我们回到之前的问题合并操作和变基操作哪个更好?问题的答案并没有那么简单。Git是一个很强大的工具它允许你针对提交历史做很多操作然而每个具体团队和每个具体项目的情况都是不同的。既然你已经了解两种操作分别是如何发挥作用的那么针对你的具体项目情况要选用哪种操作也由你自已决定。 通常来说结合两种操作的优点的操作方式是对本地尚未推送的更改进行变基操作从而简化提交历史但决不能对任何已经推送到服务器的更改进行变基操作。