Git 推拉很头疼

Git push pull is a headache

我是 Git 爱好者。十多年来,我们公司一直在使用 CVS 进行版本控制。我试图说服我的同事切换到 Git 以克服 CVS 的限制。

我的一位前辈指出了 CVS 优于 Git 的情况(真的!)。说说情况吧。

假设 John、Bill、Harry 和 Tom 在同一个存储库上工作,但都在不同的功能中,并且在某一时刻,John 和 Bill 修改了同一个实用程序文件。

在这种情况下,John 试图推动他的更改。与此同时,Bill 已经推动了他的改变。因此,John 必须先拉取更改,合并它们,然后再次尝试推送。

现在,Harry 推送他的更改。因此,John 必须再次重做拉-合并-推 activity。

Tom 再次推送他的更改。约翰未能推送他的更改 3 次,但从未成功,因为其他人在他之前推送了他们的更改。

这种情况不会出现在 CVS 中,因为我们可以在不更新整个存储库的情况下提交对一组文件的更改。

所以我的问题是,拥有大量开发人员的公司如何使用Git?这个 starvation 问题的解决方法是什么?

首先,我不确定我是否理解 CVS 如何比 GIT 更好地处理两个用户更新同一个实用程序文件的情况。

在任何代码存储库中,标准做法是更新您的代码,然后推出您的更改,这样您就不会不小心踩到别人的工作或无意中覆盖别人的工作..

在标准 git 工作流程中,会发生以下情况。 假设我在基于 develop 分支的功能分支 branch_a 上工作。

我会做以下事情,

i.On develop 分支执行:git checkout -b branch_a

ii.Make 我在 branch_a 上的更改 在这一点上,我确定我需要做出的改变。但是,我正在处理的 develop 分支可能已被其他用户更新。

iii.So 我做了一个:git pull --rebase origin develop 这会根据当前 develop 分支更新我的本地分支。 如果确实有人推出了与您的冲突的更改,那么您自己也会在这里发生冲突。您可以在此时解决它们。

iv.After 这一步您的分支已准备好合并开发。

v.You 现在可以通过签出开发分支并执行 git merge branch_a.

将您的分支合并到开发中

Git 与大多数旧系统的不同之处在于您在本地分支上进行本地提交的方式,而不是一次性向回购提交的旧方式。对我来说,Git 的方法在维护大量用户的回购时要干净和安全得多。

补充一下 Som Bhattacharyya(我没有足够的代表发表评论):我希望 John、Bill、Harry 和 Tom 密切合作。当 John 注意到因为 Harry 推了什么东西他不能推时,他会呼唤这些家伙,他们会互相调谐。

另一种选择是 4 名开发人员,每个人都在自己的王国中努力推动他们的改变。

为了帮助我的同事过渡到 Git(主要来自 SVN),我说了以下两点:

  1. 记住,在Git,树枝很便宜。便宜的。分行.

  2. 请阅读the Pro Git book

在此之后,我们能够想出一个非常适合我们的工作流程,完全可以防止您遇到的那种问题。

更新:写下这个答案和今天工作中发生的一些事情,让我更加意识到,对我们来说,最重要的还是沟通,而不是严格遵守工作流程。我敢说这对每个团队都适用吗?

实际上,这种情况可能并且 确实 发生在 CVS 中,只是(显着)少见,因为此类冲突发生在文件级别。更具体地说,CVS 具有 文件原子性 .

实际上,这种冲突在 Git 中很少见,因为(正如其他答案已经指出的那样)我们使用分支来实现更好的并行开发。此外,在所有 CVS 不会发生冲突的情况下(因为更改在单独的文件中),Git(或等效的 Mercurial)将很容易地自行进行合并或变基,因此在您的在这种情况下,John 可能不得不重试 git fetch && git rebase && git push 一次或两次,1 但由于他可以非常简单地完成此操作(例如,使用 CTRL-PENTER in the shell), 结果一点负担都没有。最后,在 CVS 中 do 发生冲突的情况下,通常必须立即解决该冲突。使用像 Git 这样的 DVCS,您可以通过将其扔到一个分支中来延迟解决(只要您愿意)(Mercurial 使这个决定变得更加困难,因为分支是持久的和全局的)。

我在下面引用我自己正在写的书...

1事实上,git fetch && git rebase有一个方便的快捷方式,git pull --rebase。不会短很多,是吗? :-) 您可以将 git pull 配置为 自动 rebase,这样它就变成了 git pull && git push,但在您非常熟悉 fetch 之前,您不应该这样做-和-rebase.


集中式与分布式

许多较旧的 VCS 是 集中式 或 CVCS。 Git 和 Mercurial 是 DVCSes:分布式 版本控制系统。

这两种系统的主要区别在于集中式 VCS 具有指定的主存储库。可能有 master 的多个副本,甚至可能有多个具有某种同步协议(例如 ClearCase MultiSite)的 master,但只有一个 master。他们的设计假定了这种单主船,因此可以依赖它。

对于分布式 VCS,没有指定的主存储库。用户通常拥有每个存储库的完整、私有副本。至少在原则上,这些私有副本之间的通信是对等操作:两个存储库都不再精通,并且冲突(Alice 和 Bob 都对同一文件的相同区域进行了更改的情况)可以并且可以做到发生并需要某种解决方案。

始终可以以集中方式使用分布式 VCS:您只需将一个特定的存储库指定为主版本,并协调对其进行更新。但是,集中式系统 通常提供锁定源文件或目录、限制访问(读取 and/or 写入、特定文件、目录、and/or 分支)等功能。使用典型的 DVCS 更难(尽管在技术上并非不可能)提供这些,而 Git 和 Mercurial 根本不会,至少在没有附加组件的情况下不会。使用提供锁定的 CVCS,用户可以锁定文件(通常只是一个特定的版本 ID)以防止其他用户进行冲突更改。这在概念上更简单,但当然可以禁止并行工作。

存储库和工作树

VCS 区分存储库(其中文件受到良好控制和版本控制)和工作树(其中文件通常没有版本控制)。工作树通常是您编辑文件、编译它们以及使用它们的地方。 [删去段落的其余部分,充实了一些较早的定义]

使用集中式 VCS,主存储库可以留在中央服务器上。然后我们可以在用户机器(例如笔记本电脑)上检出工作树,而无需首先复制整个存储库, 因此笔记本电脑的存储空间可以小于服务器的存储空间。通常我们也可以只提取一小部分:如果存储库包含数百个包、库或其他子系统,我们可以只检出一个子系统,甚至只检出一个文件。这个时候方便 一个只是进行快速简单的更改。另一方面,它要求工作树在结帐和checkin/commit操作期间连接(联网)到中心服务器,如果本地工作space断开连接,则其他修订可能无法使用.

由于分布式 VCS 通常会复制整个存储库,5 整个历史通常随时可用。这里的主要权衡是初始副本(clone 操作)的设置时间更长,以及克隆需要额外的非易失性存储。这些 DVCS 努力提高同步操作的效率,因此一旦有了初始克隆,获取新版本的速度就相对较快。 (例如,我看到初始克隆在慢速网络上需要四个或更多小时,但它们的重新同步通常只需要几秒钟。)


5Git 和 Mercurial 现在都支持浅克隆单分支克隆,可以省略一些repository。 [剪下脚注的其余部分,这基本上是稍后即将发布的信息的预告片,或者可能是 link,最终...]

原子性:修改的最小单位是什么?

较旧的 VCS 一次只处理一个文件,使用签出/签入模型。它们的原子性单位是文件。即使您一次签出(或签入)多个文件,VCS 也只是对 每个文件的基础上,就好像你一次完成一个。考虑 Table 1.1 中显示的四个可构建迭代。让我们假设在每次迭代中,一组新的可编译文件被一起检入——但我们的 VCS 只适用于文件,一次一个文件。每个文件都以版本 1 开始,但在第 3 次迭代时,文件 kanga.c 有两个版本,而文件 roo.c 有三个版本。6 最后一个可构建迭代引入了新文件 wallaby.c,现在是版本 1。为了构建任何给定的迭代,您需要哪些文件的哪些版本?您需要跳过哪些文件版本组合?答案当然在我们的 table 中,但 VCS 不会自行跟踪。

6目前,我们将只对每个文件修订进行编号,而不用担心从修订中制作树。

[Tables 1.1 和 1.2 需要在 Whosebug 中不可用的标记;这里我使用纯文本]


Table 1.1:四次可构建迭代,以文件原子性记录,导致七次签入。每行实际签入的文件都标有星号。

check-in iteration                   files
--------------------------------------------------------
    1                 kanga.c:1*
    2       1         kanga.c:1   roo.c:1*
    3                 kanga.c:2*  roo.c:1
    4       2         kanga.c:2   roo.c:2*
    5       3         kanga.c:2   roo.c:3*
    6                 kanga.c:3*  roo.c:3
    7       4         kanga.c:3   roo.c:3   wallaby.c:1*

较新的系统,包括 Git 和 Mercurial,可以处理更大的文件集。它们的原子性单位是 commit。提交更改会同时输入所有文件。如果出现任何问题,no 文件将得到新的修订;如果整个提交成功,所有 文件都会得到一个新的修订版,如 Table 1.2 所示。提取最新的提交(第 4 行)可以获得所有三个文件的最新版本。备份一个版本可以获得之前的 kanga.croo.c——这会更改 kanga.c 的内容,同时保持 roo.c 的内容不变——并删除 wallaby.c完全自动。


Table 1.2:相同的四个可构建迭代,但具有提交原子性。

commit                    files
------------------------------------------
   1     1:kanga.c   1:roo.c
   2     2:kanga.c   2:roo.c
   3     3:kanga.c   3:roo.c
   4     4:kanga.c   4:roo.c   4:wallaby.c

通常,在文件原子性系统中,您可以命名或标记 一组文件修订,并按标记提取。标签往往有 一个明显的成本——即使他们没有使用很多 space 或时间,7 他们呈现出一种修订混乱,并且在实践中他们仅用于更主要的检查站。基于提交的系统消除了对这些标签的需求(尽管我们将看到,标签仍然有用)。

7例如,CVS 中的标记是在每个文件级别维护的,因此标记整个树是一项非常缓慢的操作。

[更多的片段,删除了关于压缩、文件标识、分支和版本编号的整个部分,关于分支和修订的可分离性的一些哲学问题,等等]

并发模型

无论是集中式还是分布式,任何允许多个用户彼此独立工作的 VCS 都必须提供一些方法来处理潜在的冲突。如前所述,一种方法是 locking:在更改文件之前,用户必须获得一个锁,然后在提交更改时释放该锁。这种简单的方法存在前面提到的禁止并行工作的明显问题。可以使锁定更细粒度——Alice 可能锁定文件的上半部分,而 Bob 可以锁定和更改文件的下半部分——但这存在扩展问题。此外,用户 and/or 管理员必须有破解锁的方法,因为用户将锁定文件但失败或忘记解锁它们(例如,在决定不提交之后)。

如果 VCS 提供 merge 模型,两个或更多人可以处理相同的文件,并在定义的集合点 — 在 CVCS 中,在签入/提交时例如,时间——他们有机会协调他们的变化。合并分支时也需要合并,在现代 DVCS 中,这两种方法通常使用相同的方法。 请注意,在 DVCS 中,会合点(以及任何合并)可以在签入之后发生。 Bob 可以在自己签到之前先去接 Alice 的工作,但由于系统是分布式的,Bob 不必等待 Alice(反之亦然)。

[下一节是对所选 VCS 的回顾。我不会在这里复制它,但会注意到我在 table 中为 CVS 提供了一个条目,表明它比 SCCS 和 RCS 更进一步:它具有合并并发性,但仍然使用文件原子性并且不是分布式的。 Subversion 在下一组,提供提交原子性和合并并发性,但仍然使用集中式存储库。同时 Git 和 Mercurial 都使用提交原子性、合并并发并使用完全分布式模型。

坚持使用集中式 VCS 是有原因的,但今天仍在使用 CVS 的任何人应该多年前就应该转向 SVN。提交原子性是一个巨大的飞跃,至少,如果我能避免的话,我永远不会回头。 SVN 有一个奇怪的分支模型,但它确实有效,并且这里有 cvs2 转换器可以帮助您。]