控制系统
这是由两部分组成的系列文章中的第二篇。 如果您还没有阅读过第1部分,则应该阅读,因为我将使用相同的Git和Subversion(SVN)设置,它会让您习惯于我的幽默感。
SVN中的分支和合并
版本控制系统(VCS)管理人员最容易头痛的最大原因就是分支和合并 。 绝大多数开发人员喜欢将所有更改都提交到主干中。 一旦出现分支和合并,开发人员便开始抱怨,VCS经理开始处理它。
为了公平起见,分支和合并是可怕的操作。 结果并不总是很明显,并且合并可能会使其他人的工作变得麻烦。
SVN很好地管理了主干,许多开发人员不再为分支而烦恼。 1.5之前的SVN客户端是关于跟踪合并的原始语言,因此,如果您习惯于较早的SVN客户端,则可能不了解SVN的svn:mergeinfo
属性。
还有一个名为svnmerge.py(参见工具相关信息中的链接)。 svnmerge.py可以在没有svn:mergeinfo
支持的情况下跟踪合并,因此适用于较早的SVN客户端。
由于SVN合并支持的复杂性和多样性,我将不提供具体示例。 相反,我们只谈论Git的分支合并。 你可以阅读在参考SVN手册中的相关主题部分,如果你有兴趣。
在Git中分支和合并
如果说并发版本系统(CVS)是分支和合并方面的白痴,那么SVN是牧师,而Git是市长。 Git实际上是为了支持简单的分支和合并而设计的。 此Git功能不仅在演示中印象深刻,而且每天都非常方便。
举个例子,Git有多种合并策略,包括一种叫做章鱼策略的策略,它可以让您一次合并多个分支。 章鱼策略! 只要考虑一下尝试在CVS或SVN中进行这种合并的疯狂。 Git还支持另一种称为rebasing的合并。 我不会在这里检查重新编制基准,但是它对于简化存储库历史记录非常有帮助,因此您可能需要查找它。
在继续下面的合并示例之前,您应该熟悉第1部分中的分支设置。 您有HEAD
(当前分支,在本例中为master
)和empty-gdbinit
分支。 首先,让我们将empty-gdbinit
合并到HEAD
,然后在HEAD
进行更改,然后以另一种方式将其合并到empty-gdbinit
:
清单1.使用Git合并从分支到HEAD的更改
# start clean
% git clone git@github.com:tzz/datatest.git
# ...clone output...
# what branches are available?
% git branch -a
#* master
# origin/HEAD
# origin/empty-gdbinit
# origin/master
# do the merge
% git merge origin/empty-gdbinit
#Updating 6750342..5512d0a
#Fast forward
# gdbinit | 1005 ---------------------------------------------------------------
# 1 files changed, 0 insertions(+), 1005 deletions(-)
# now push the merge to the server
% git push
#Total 0 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
# 6750342..5512d0a master -> master
只要知道master
具有HEAD
,这就不难,并且在与empty-gdbinit
分支合并之后,将master
分支推送到远程服务器以与origin/master
同步。 换句话说,您是从远程分支本地合并的,然后将结果推送到另一个远程分支。
在这里重要的是要了解Git如何不在乎哪个分支是权威的。 您可以从本地分支合并到另一个本地分支或远程分支。 Git服务器仅参与远程操作。 相反,SVN始终需要SVN服务器,因为使用SVN,服务器上的存储库是唯一的权威版本。
当然,Git是分布式VCS,因此这并不奇怪。 它旨在在没有中央权威的情况下工作。 尽管如此,对于习惯于CVS和SVN的开发人员来说,自由可能会有点破坏。
现在,准备好所有这些隆重的演讲,让我们建立另一个本地分支:
清单2.在机器A上创建并切换到发行分支
# create and switch to the stable branch
% git checkout -b release-stable
#Switched to a new branch "release-stable"
% git branch
# master
#* release-stable
# push the new branch to the origin
% git push --all
#Total 0 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
# * [new branch] release-stable -> release-stable
现在,在另一台计算机上,我们将从master分支中删除gdbinit
文件。 当然,它不必是其他机器,它可以简单地位于其他目录中,但是我在第1部分中针对机器B在Ubuntu上重用“ Other Ted”身份。
清单3.从计算机B的master分支中删除gdbinit
# start clean
% git clone git@github.com:tzz/datatest.git
# ...clone output...
% git rm gdbinit
# rm 'gdbinit'
# hey, what branch am I in?
% git branch
#* master
# all right, commit my changes
% git commit -m "removed gdbinit"
#Created commit 259e0fd: removed gdbinit
# 1 files changed, 0 insertions(+), 1 deletions(-)
# delete mode 100644 gdbinit
# and now push the change to the remote branch
% git push
#updating 'refs/heads/master'
# from 5512d0a4327416c499dcb5f72c3f4f6a257d209f
# to 259e0fda9a8e9f3b0a4b3019781b99a914891150
#Generating pack...
#Done counting 3 objects.
#Result has 2 objects.
#Deltifying 2 objects...
# 100% (2/2) done
#Writing 2 objects...
# 100% (2/2) done
#Total 2 (delta 1), reused 0 (delta 0)
这里没有什么疯狂的(“删除”除外,这听起来像是您在体育馆里要做的事情,或者是在大片水域附近河流可能做的事情)。 但是在机器A的release-stable
分支中会发生什么?
清单4.将gdbinit从master分支的删除合并到机器A上的release-stable分支
# remember, we're in the release-stable branch
% git branch
# master
#* release-stable
# what's different vs. the master?
% git diff origin/master
#diff --git a/gdbinit b/gdbinit
#new file mode 100644
#index 0000000..8b13789
#--- /dev/null
#+++ b/gdbinit
#@@ -0,0 +1 @@
#+
# pull in the changes (removal of gdbinit)
% git pull origin master
#From git@github.com:tzz/datatest
# * branch master -> FETCH_HEAD
#Updating 5512d0a..259e0fd
#Fast forward
# gdbinit | 1 -
# 1 files changed, 0 insertions(+), 1 deletions(-)
# delete mode 100644 gdbinit
# push the changes to the remote server (updating the remote release-stable branch)
% git push
#Total 0 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
# 5512d0a..259e0fd release-stable -> release-stable
我在第1部分中提到的mentat接口在diff中再次出现。 您应该知道/dev/null
是一个不包含任何内容的特殊文件,因此,远程master分支没有任何内容,而本地release-stable
分支具有gdbinit
文件。 对于大多数用户而言,这并不总是显而易见的。
经过所有这些有趣的事情, pull
将本地分支与origin/master
合并,然后push
使用更改更新origin/release-stable
。 像往常一样,“ delta”是Git开发人员最喜欢的词-永远不会错过使用它的机会。
平分变化
我不会在这里详细介绍git bisect
命令,因为它非常复杂,但是我想提一下它,因为它是一个了不起的工具。 平分更改实际上是对提交日志的二进制搜索。 “二进制”是指搜索将搜索间隔向下划分中间部分,并每次都测试中间部分,以确定所需段是在中间部分之上还是之下。
它的工作方式很简单。 您告诉Git版本A不错,而版本Z不好。 然后,Git询问您(或询问自动脚本)在A和Z之间的中间版本(例如Q)是否不好。 如果Q不好,那么错误的提交在A和Q之间; 否则,错误的提交将在Q和Z之间。重复此过程,直到找到错误的提交为止。
可以使用测试脚本自动执行二等分功能,这是特别好。 这样就可以为Z版本编写测试,并向后使用它来查找功能何时中断,大多数开发人员将其称为自动回归测试 。 这些可以节省您的时间。
解决冲突
合并冲突在任何VCS中都是不可避免的,尤其是在分布式VCS(例如Git)中。 如果两个人在同一分支中以冲突的方式更改文件会怎样? 以下两个示例都在到目前为止我们一直在使用的datatest存储库的master分支中。
首先,我们在机器B上对encode.pl进行更改:
清单5.计算机B上的“不起作用”
# we're at time T1
# change the contents
% echo "# this script doesn't work" > encode.pl
% git commit -a -m 'does not work'
#Created commit e61713b: does not work
# 1 files changed, 1 insertions(+), 1 deletions(-)
# we're at time T2 now, what's our status?
% git status
# On branch master
#nothing to commit (working directory clean)
现在,我们在不知道机器B上的更改的情况下,对机器A上的encoding.pl进行了更改,然后push
其push
:
清单6.在机器A上“起作用”
# we're at time T2
# change the contents
% echo "this script does work" > encode.pl
% git commit -a -m 'does not work'
#Created commit e61713b: does not work
# 1 files changed, 1 insertions(+), 1 deletions(-)
# we're at time T3 now, what's our status?
% git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
#nothing to commit (working directory clean)
% git push
#Counting objects: 5, done.
#Delta compression using 2 threads.
#Compressing objects: 100% (2/2), done.
#Writing objects: 100% (3/3), 298 bytes, done.
#Total 3 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
# 259e0fd..f949703 master -> master
现在,在机器B上,我们进行了一次git pull
并意识到事情并非如此美妙:
清单7.机器B上的哦
% git pull
#remote: Counting objects: 5, done.
#Compressing objects: 100% (2/2), done.)
#remote: Total 3 (delta 0), reused 0 (delta 0)
#Unpacking 3 objects...
# 100% (3/3) done
#* refs/remotes/origin/master: fast forward to branch 'master'
# of git@github.com:tzz/datatest
# old..new: 259e0fd..f949703
#Auto-merged encode.pl
#CONFLICT (content): Merge conflict in encode.pl
#Automatic merge failed; fix conflicts and then commit the result.
# the next command is optional
% echo uh-oh
#uh-oh
# you can also use "git diff" to see the conflicts
% cat encode.pl
#<<<<<<< HEAD:encode.pl
## this script doesn't work
#=======
#this script works
#>>>>>>> f9497037ce14f87ff984c1391b6811507a4dd86c:encode.pl
这种情况在SVN中也很常见。 其他人的更改与您的文件版本不同。 只需编辑文件并提交:
清单8.在机器B上修复和提交
# fix encode.pl before this to contain only "# this script doesn't work"...
% echo "# this script doesn't work" > encode.pl
# commit, conflict resolved
% git commit -a -m ''
#Created commit 05ecdf1: Merge branch 'master' of git@github.com:tzz/datatest
% git push
#updating 'refs/heads/master'
# from f9497037ce14f87ff984c1391b6811507a4dd86c
# to 05ecdf164f17cd416f356385ce8f5c491b40bf01
#updating 'refs/remotes/origin/HEAD'
# from 5512d0a4327416c499dcb5f72c3f4f6a257d209f
# to f9497037ce14f87ff984c1391b6811507a4dd86c
#updating 'refs/remotes/origin/master'
# from 5512d0a4327416c499dcb5f72c3f4f6a257d209f
# to f9497037ce14f87ff984c1391b6811507a4dd86c
#Generating pack...
#Done counting 8 objects.
#Result has 4 objects.
#Deltifying 4 objects...
# 100% (4/4) done
#Writing 4 objects...
# 100% (4/4) done
#Total 4 (delta 0), reused 0 (delta 0)
那很容易,不是吗? 让我们看看机器A下次更新时会发生什么。
清单9.在机器B上修复和提交
% git pull
#remote: Counting objects: 8, done.
#remote: Compressing objects: 100% (3/3), done.
#remote: Total 4 (delta 0), reused 0 (delta 0)
#Unpacking objects: 100% (4/4), done.
#From git@github.com:tzz/datatest
# f949703..05ecdf1 master -> origin/master
#Updating f949703..05ecdf1
#Fast forward
# encode.pl | 2 +-
# 1 files changed, 1 insertions(+), 1 deletions(-)
% cat encode.pl
## this script doesn't work
Fast forward
意味着本地分支会自动追上远程分支,因为它对远程分支不包含任何新内容。 换句话说,快进意味着不需要合并。 所有本地文件都不比远程分支机构的最新推送更新。
最后,我应该提到git revert
和git reset
,它们对于撤消对Git树的提交或其他更改非常有用。 这里没有空间解释它们,但请确保您知道如何使用它们。
结论
本文打开了合并的概念,展示了将本地分支和远程分支保留在两台计算机上并解决它们之间的冲突的感觉。 我还提请注意复杂的甚至是不可思议的Git消息,因为与SVN相比,Git更加冗长且难以理解。 当您将此事实与Git命令的复杂语法结合在一起时,它会使大多数初学者对Git感到恐惧。 但是,一旦解释了一些基本概念,Git就会变得更加轻松-甚至令人愉快!
翻译自: https://www.ibm.com/developerworks/opensource/library/l-git-subversion-2/index.html
控制系统