在Git中合并后使用git-svn dcommit是否危险?

135

我尝试使用git-svn的动机是轻松的合并和分支。然后我注意到man git-svn(1)上说:

不建议在您计划从中dcommit的分支上运行git-merge或git-pull。 Subversion不以任何合理或有用的方式表示合并;因此,使用Subversion的用户无法看到您进行的任何合并。此外,如果您从是SVN分支的镜像的git分支合并或拉取,则dcommit可能会提交到错误的分支。

这是否意味着我不能从svn / trunk(或分支)创建本地分支,进行修改,合并回svn / trunk,然后进行dcommit?我了解到svn用户将看到与svn pre 1.5.x中的合并一样的混乱情况,但还有其他缺点吗?最后一句话也让我感到担忧。人们通常会做这些事情吗?


14
这个说法是错误的:Subversion不能以任何合理或有用的方式表示合并,因此使用Subversion的用户无法看到您所做的任何合并。此外,如果您从一个与SVN分支镜像相关的git分支合并或拉取,dcommit可能会提交到错误的分支。Subversion不仅可以表示git合并跟踪信息,还可以表示更细粒度的信息。问题出在git-svn未能正确组合svn:mergeinfo或将dcommit提交到正确的分支上。 - Alexander Kitaev
2
@AlexanderKitaev:自从git-svn文档更改后,另外还有一个--mergeinfo=<mergeinfo>选项,可以将合并信息传递给SVN。不过我不确定应该如何使用它。 - Mr_and_Mrs_D
6个回答

176

实际上,我发现使用git合并时的--no-ff选项甚至更好。以前我使用的所有压缩技巧都不再需要。

我的新工作流程如下:

  • 我有一个“主”分支,这是我从中进行dcommit的唯一分支,并且克隆SVN存储库(-s假定您在存储库的trunk/branches/tags/中具有标准的SVN布局):

git svn clone [-s] <svn-url>
  • 我正在一个本地分支 "work" 上工作 (-b 选项用于创建名为 "work" 的分支)

  • git checkout -b work
    
  • 本地提交到"work"分支(-s用于签署您的提交信息)。接下来,我假设您已经进行了3次本地提交。

  • ...
    (work)$> git commit -s -m "msg 1"
    ...
    (work)$> git commit -s -m "msg 2"
    ...
    (work)$> git commit -s -m "msg 3"
    

    现在你想提交到SVN服务器

    • [最终] 储存那些你不希望提交到SVN服务器的修改(通常你会在主文件中注释一些代码,只是因为你想加速编译并专注于某个特定功能)

    (work)$> git stash
    
  • 将主分支与SVN仓库进行rebase(用于从SVN服务器更新)

  • (work)$> git checkout master
    (master)$> git svn rebase
    
  • 切换回工作分支并将其与主分支进行变基

  • (master)$> git checkout work
    (work)$> git rebase master
    
  • 确保一切正常,例如:

    (work)$> git log --graph --oneline --decorate
    
    现在是时候使用这个神奇的--no-ff选项将“work”分支的所有三个提交合并到“master”分支中了。
  • (work)$> git checkout master
    (master)$> git merge --no-ff work
    
    你可以注意到日志的状态:
    (master)$> git log --graph --oneline --decorate
    * 56a779b (work, master) Merge branch 'work'
    |\  
    | * af6f7ae msg 3
    | * 8750643 msg 2
    | * 08464ae msg 1
    |/  
    * 21e20fa (git-svn) last svn commit
    
  • 你现在可能想要编辑(amend)最后一次提交以便给你的 SVN 同事们查看(否则他们只会看到一个带有信息“Merge branch 'work'”的单独提交)

  • (master)$> git commit --amend
    
  • 最后在SVN服务器上提交

  • (master)$> git svn dcommit
    
  • 回到工作目录,最终找回你已经存储的文件:

  • (master)$> git checkout work
    (work)$> git stash pop
    

    4
    这正是这位 Git 新手想要的工作流程。创建本地分支以修复 bug,做任何更改,然后使用一个提交将其上传到 SVN。使用这里评价最高的答案会破坏我的工作 - 无法在 Git SVN 上执行补丁操作而不出错。 - João Bragança
    20
    这不是 git-svn 文档中在问题描述部分警告的吗?运行合并时加上 --no-ff 选项,明确地创建一个合并提交(一个有两个父节点的提交),而不是快进合并。为了确保 svn 跟踪分支中的所有提交都是单父提交,所有合并必须要么是快进合并(--ff-only 可以帮助实现此目的),要么,在你背后出现 trunk 更改时,使用 --squash - jemmons
    4
    这适用于您立即删除的一次性分支,但git svn dcommit会重写您给出的git提交。这意味着您会失去其它上级分支,现在您的git repo没有记录您将该分支合并到“主分支”。这也可能导致您的工作目录处于不一致的状态。 - rescdsk
    9
    使用 git merge --no-ff work -m "commit message" 代替额外的 git commit --amend 步骤。 - tekumara
    3
    我更喜欢使用命令"git merge --ff-only work",因为我想保留所有的提交而不仅仅是最后一个。 - Moataz Elmasry
    显示剩余4条评论

    49

    使用git-svn可以创建本地分支。只要您只是为自己使用本地分支,而不尝试使用git在上游svn分支之间进行合并,那么就应该没有问题。

    我有一个“master”分支,用于跟踪svn服务器。这是我从中提交更改的唯一分支。如果我要做一些工作,我会创建一个专题分支并在其上工作。当我想提交时,我会执行以下操作:

    1. 将所有更改都提交到专题分支
    2. git svn rebase (解决您的工作与svn之间的任何冲突)
    3. git checkout master
    4. git svn rebase (这使得下一步是快进式合并,请参见下面的Aaron评论)
    5. git merge topic_branch
    6. 解决任何合并冲突 (此时不应存在任何冲突)
    7. git svn dcommit

    我还有另一种情况,需要维护一些本地更改(用于调试),这些更改不应推送到svn上。为此,我除了上述的“master”分支外,还有一个名为“work”的分支,在其中通常进行工作。专题分支是从“work”分支中分支出来的。当我想要提交更改时,我会切换到“master”分支,使用cherry-pick从“work”分支中选择要提交到svn的更改。这是因为我想要避免提交三个本地更改提交。然后,我从“master”分支上执行dcommit,并进行rebase操作。

    值得注意的是,先运行git svn dcommit -n确保您将要提交的是确切的内容。与git不同,在svn中重写历史记录很困难!

    我感觉在不使用cherry-pick的情况下合并专题分支上的更改,同时跳过那些本地更改提交的方法肯定存在,如果有任何想法,欢迎分享。


    如果我理解正确的话,在git中,将git与svn合并是可以的,但不支持svn与svn之间的合并?那么我不能在git中将我的svn/分支与svn/trunk合并吗? - Knut Eldhuset
    1
    如果你想做一些微不足道的事情,那么在SVN中重写历史是很困难的。在非微不足道的情况下,这几乎是不可能的。 - Aristotle Pagaltzis
    19
    Greg Hewgill的回答得到了许多投票,但我认为它是错误的。如果只是快进式合并,则从topic_branch合并到master是安全的。如果需要合并提交,则合并提交将在您进行dcommit时丢失。下次尝试将topic_branch合并到master时,git认为第一次合并从未发生过,所有东西都变得混乱不堪。请参见问题为什么git svn dcommit会丢失本地分支的合并提交历史记录? - Aaron
    1
    @Greg Hewgill,编辑不够清晰。你写道rebase“使下一个提交成为快进合并”。我不知道这是什么意思,因为快进合并不涉及提交,但也许你的意思是“使下一个合并成为快进合并”。但如果你的意思是这样,那就不正确了。从topic_branch合并到master的合并是否为快进合并取决于自分支点以来在master上是否进行了任何提交。当你对master进行rebase时,这会在master上创建新的提交,因此随后的合并必然不是快进合并。 - Aaron
    1
    @Aaron:是的,我不知道当时在想什么。我已经再次修复了它,希望现在讲得更清楚了。Git 中有很多方法可以做事情,这就是其中一个麻烦之处,需要花费相当长的时间来描述一系列特定的操作步骤。 - Greg Hewgill
    显示剩余4条评论

    33

    简单解决方案:在合并后删除“work”分支

    简短回答: 你可以使用git任何你喜欢的方式(参见下面的简单工作流),包括合并。只需确保在每个'git merge work'后跟 'git branch -d work'以删除临时工作分支。

    背景说明:合并/提交的问题在于,每当你'git svn dcommit'一个分支时,该分支的合并历史会被'扁平化':git会忘记所有合并操作,只保留文件内容,但该内容(部分)来自特定的其他分支的事实将丢失。详情请见:为什么git svn dcommit会丢失本地分支合并提交历史?

    (注意:git-svn 并没有太多可做的:svn根本不理解更强大的 git 合并。因此,在svn存储库中,不能以任何方式表示这种合并信息。)

    但这就是整个问题所在。如果你在将“work”分支合并到“master分支”之后将其删除,则你的git存储库就是100%干净的,看起来完全像你的svn存储库。

    我的工作流程: 当然,我首先将远程svn存储库克隆到本地git存储库中(这可能需要一些时间):

    $> git svn clone <svn-repository-url> <local-directory>
    

    所有工作都在“本地目录”中进行。每当我需要从服务器获取更新(例如“svn update”)时,我执行以下操作:

    $> git checkout master
    $> git svn rebase
    

    我所有的开发工作都在一个名为“work”的单独分支中完成,创建方式如下:

    $> git checkout -b work
    

    当然,您可以根据自己的需要创建任意数量的分支,并在它们之间自由合并和变基(在完成后只需删除它们--如下所述)。在我日常工作中,我经常进行提交:

    $> git commit -am '-- finished a little piece of work'
    
    下一步骤(git rebase -i)是可选的 --- 它只是在将历史存档到svn之前清理历史记录:一旦我达到了一个稳定的里程碑,我想与他人分享,我会重写这个“工作”分支的历史记录并清理提交消息(其他开发人员不需要看到我在路上犯的所有小错误和步骤---只需要看结果)。为此,我执行以下操作:
    $> git log
    

    复制svn仓库中最后提交的sha-1哈希值(由git-svn-id指示),然后调用该值。

    $> git rebase -i 74e4068360e34b2ccf0c5869703af458cde0cdcb
    

    只需将最后一个svn提交的sha-1哈希粘贴而不是我的。您可能想阅读使用 "git help rebase" 命令查看详细信息。简而言之:此命令首先打开一个编辑器,呈现您的提交——只需将“选择”更改为“压缩”,以便所有这些提交都与以前的提交一起压缩。当然,第一行应保持为“选择”。通过这种方式,您可以将许多小的提交压缩成一个或多个有意义的单元。

    保存并退出编辑器。然后您将得到另一个编辑器,要求您重新编写提交日志消息。

    简而言之:在完成代码修改后,我对“工作”分支进行调整,直到它看起来符合我想向其他程序员展示它的方式(或者当我浏览历史记录时想要看到工作几周后的外观)。

    为了将更改推送到svn存储库中,我执行:

    $> git checkout master
    $> git svn rebase
    

    现在我们回到旧的“master”分支,其中包含svn存储库中发生的所有更改(您的新更改隐藏在“work”分支中)。

    如果有可能与您的新“work”更改冲突的更改,则必须在推送新工作之前在本地解决这些更改(详见下文)。 然后,我们可以将更改推送到svn:

    $> git checkout master
    $> git merge work        # (1) merge your 'work' into 'master'
    $> git branch -d work    # (2) remove the work branch immediately after merging
    $> git svn dcommit       # (3) push your changes to the svn repository
    
    注意1:命令“git branch -d work”非常安全:它只允许你删除不再需要的分支(因为它们已经合并到当前分支中)。如果在将你的工作与'master'分支合并之前误执行此命令,你将收到错误消息。
    注意2:确保在合并和dcommit之间使用“git branch -d work”命令删除分支。如果您尝试在dcommit之后删除分支,将会出现错误消息:当您执行“git svn dcommit”时,git会忘记您的分支已经与'master'合并。你必须用“git branch -D work”删除它,这不会执行安全检查。
    现在,我立即创建一个新的“work”分支,以避免意外地操作'master'分支:
    $> git checkout -b work
    $> git branch            # show my branches:
      master
    * work
    

    将您的“工作”与svn上的更改集成: 当我使用“git svn rebase”时,它显示其他人在我正在处理的“工作”分支上进行了svn仓库的更改,这是我所做的:

    $> git checkout master
    $> git svn rebase              # 'svn pull' changes
    $> git checkout work           # go to my work
    $> git checkout -b integration # make a copy of the branch
    $> git merge master            # integrate my changes with theirs
    $> ... check/fix/debug ...
    $> ... rewrite history with rebase -i if needed
    
    $> git checkout master         # try again to push my changes
    $> git svn rebase              # hopefully no further changes to merge
    $> git merge integration       # (1) merge your work with theirs
    $> git branch -d work          # (2) remove branches that are merged
    $> git branch -d integration   # (2) remove branches that are merged
    $> git svn dcommit             # (3) push your changes to the svn repository
    

    还有更加强大的解决方案: 所提供的工作流程过于简单: 它仅在每轮“update/hack/dcommit”中利用 git 的威力,但是将长期项目历史记录保持线性,就像 svn 存储库一样。如果您只想在传统 svn 项目中小步使用 git 合并,则可以这样做。

    当您熟悉 git 合并后,可以随意尝试其他工作流程: 如果您知道自己在做什么,可以将 git 合并与 svn 合并混合使用(Using git-svn (or similar) just to help out with svn merge?)


    这似乎过于复杂了。我听说有人使用 git merge --squash work,为什么不这样做呢?如果您要创建多个“pick”(比如您有8个提交,您要将每4个提交合并成1个,并将2个提交合并到主分支),那么在合并之前在分支中进行压缩提交是可以理解的。此外,在更新我的“work”分支时,我会使用 rebase,这比为我的分支创建另一个分支并进行合并更简单... - void.pointer

    8

    Greg Hewgill在顶部的答案并不安全!如果在两个“git svn rebase”之间有任何新的提交,合并将无法快进。

    可以通过在git-merge中使用“--ff-only”标志来确保它,但我通常不在分支中运行“git svn rebase”,只在其上运行“git rebase master”(假设它只是一个本地分支)。 然后之后,“git merge thebranch”将保证是快进的。


    6
    使用git merge --squash是合并svn分支到git的一种安全方式。这将创建一个单独的提交,并停止让您添加消息。
    假设您有一个名为svn-branch的主题svn分支。
    git svn fetch
    git checkout remotes/trunk -b big-merge
    git merge --squash svn-branch
    

    此时,您已将来自svn-branch的所有更改压缩到一个提交中,并等待在索引中

    git commit
    

    正如其他人指出的那样,这确实失去了提交记录的细节。 - Kzqai
    有时候你只想要那个。我经常在一个功能分支上提交,比如“修复了之前提交的混乱”,而这些死胡同我喜欢隐藏起来,因为我想保持良好的声誉:-) - schoetbi
    1
    是的,虽然我认为这是一种走钢丝的方式,因为每当你压缩时,你也失去了以后分离单个提交的能力。这只是不同提交标准的选择问题。 - Kzqai
    1
    @schoetbi 是的,有时在发布之前需要清理混乱的历史记录。这就是 'git rebase -i <trunk>' 的好处所在:您可以加入/重新排序/删除甚至拆分(!)提交,有选择地修复消息。 - inger

    5

    将本地git分支变基到主git分支上,然后进行dcommit,这样看起来就像你按顺序做了所有的提交,因此svn用户可以线性地看到它们,就像他们习惯的那样。所以假设你有一个名为topic的本地分支,你可以这样做:

    git rebase master topic
    

    然后,它将在主分支上播放您的提交,以便您可以进行dcommit操作。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接