如何进行Subversion的rebase操作?

50
我发现这种方法更容易合并分支且冲突较少:

将主干复制到一个新的分支,将其与功能分支合并。完成后,将新的分支合并回主干。这个技巧很像mercurial和git的rebasing。

我曾经将主干的任何更改合并到功能分支中。但是稍后当我将功能分支合并回主干时,一些来自主干的东西会再次合并回主干,这导致了很多冲突。有一种重新整合合并的选择,但对我似乎不起作用。 有人使用类似于svn的rebasing吗? 我最近刚开始尝试这样做,还没有看到任何副作用。 这会导致任何未预见的问题吗?

你在问题中提出的建议是正确的解决方案;-) 它应该按预期工作,我看不到任何副作用。 - ymajoros
我是一个版本控制系统新手。我很好奇:这些会是什么样的冲突?如果你将trunk@r1到trunk@r2合并到分支中,然后将结果合并回trunk,那么由于trunk不应该有任何更改,所以不应该有任何变化。你能举个例子吗? - Panayiotis Karabassis
6个回答

44

一般而言,rebase(变基)是将上游更改合并到功能分支中,然后再将功能分支合并回上游分支的操作。

在git中,此过程更加复杂,因为首先需要将自分支创建以来所做出的更改取出并缓存,再应用上游更改,最后再应用缓存的更改。这里要注意的是,在git术语中,将主干分支合并到功能分支不是rebase,还有其他操作需要执行。Git的方法具有许多优点,但无法在svn中实现得非常干净,因为所有提交都必须存储在服务器上(svn不是分布式版本控制系统),但它在svn中也可以实现。

“svn rebase”(git的方式)可能看起来像这样:

  1. svn cp trunk feature
  2. feature和trunk提交了更改
  3. svn cp trunk feature-rebase
  4. svn co feature-rebase
  5. cd feature-rebase
  6. svn merge feature
  7. svn commit
  8. svn rm feature
  9. svn mv feature-rebase feature
  10. (返回feature-rebase工作副本)svn switch feature

然后最终在主干的工作副本上,svn merge --reintegrate feature

您看到与仅将主干合并到功能分支的差异了吗?您要从上游开始,以这个例子为例就是trunk,然后将功能分支的更改合并到其中。

想象一下,主干的某些提交可能来自将另一个功能分支合并到主干中,因此我绝不建议直接提交到主干。


点对点的解释对于初学者来说有点过于简略了,而 quickshiftin 创建的文章现在已经无法获取了。QQ - ashrasmun
答案不是针对初学者的,而是旨在成为权威指南。关于您提到的问题,我已经删除了文章链接。 - quickshiftin

9
我希望我能告诉你在SVN中如何实现rebasing的巧妙技巧,但是我一直避免手动刷新SVN中分支与主干的更改,主要是因为需要手动挑选的复杂性,正如jdehaan所提到的。相反,我通常的做法是将分支的更改合并到主干中,删除分支,然后从主干重新创建分支。这样可以使我刷新/重置我的特性分支,但有时会产生不良影响,即该分支的任何先前更改现在都成为主干的一部分。因此,仅当特性分支处于稳定可用状态,但我仍希望继续在该特性上工作以完成某些更大目标时,才会采用这种做法。
我更喜欢的方法是,通过将主干的更改合并回分支来刷新分支,而不会导致随后的重整合并从该分支拉取这些已经重置的修订版本。基于合并信息属性,应该可以做到这一点,但根据jdehaan所述和我担心的是,仍然需要进行挑选。
请注意,适当的重置实现还应考虑阶梯式示例,其中一个分支是由另一个分支创建的。

更新:根据Subversion文档,当使用--reintegrate选项时,Subversion应该能够以一种注意任何可能的刷新合并的方式正确地重新整合在分支中完成的工作。当然,这从技术上讲与变基略有不同,但我认为在用法上足够相似,可以称之为变基。


1
在Subversion中向上合并时,必须使用*--reintegrate。这一切都随着svn 1.5合并跟踪而来,相比于旧的追踪哪些变更集已经合并到分支中的麻烦,使合并成为可能*。与hg/git提供的功能相比,它仍然是一个相当原始的实现,并不真正属于变基过程的一部分。它更多的是用于完成一个特性或发布分支。 - quickshiftin

5
在我的公司中,我们采用以下方法:
  1. 对于问题跟踪器中的每个任务NK-$X,我们都有一个单独的分支branches/NK-$X。
  2. 我们通过svn cp trunk branches/NK-$X开始处理任务。
  3. 我们从不直接向主干提交更改。对于每个计划更新UPDNK-$X,我们都有一个单独的分支branches/UPDNK-$X。我们在更新之前使用svn cp trunk branches/UPDNK-$X创建它。
  4. 当任务NK-$X计划进行更新UPDNK-$Y时,我们将branches/NK-$X合并到UPDNK-$Y中。即cd UPDNK-$Y; svn merge -r start:HEAD branches/NK-$X。
  5. 在UPDNK-$Y准备好后,我们将其合并到主干中。即cd trunk;svn merge -r start:HEAD branches/UPDNK-$Y。

如果任务NK-$X持续时间超过一个迭代周期,并且因此需要刷新,我们永远不会将主干合并到NK-$X中。我们有一个规则,只有你自己编写的内容才能提交到你的分支,这使得一切变得更加容易。相反,我们会执行以下操作:

cd NK-$X
svn log
//let L = the number of the last changeset to this branch changeset
//let F = the number of the first changeset to this branch
svn rm branches/NK-$X 
svn cp trunk branches/NK-$X 
svn up
svn merge -r F:L branches/NK-$X@L 
svn ci -m 'refereshed'

这样,每当您查看branches/NK-$X的变更日志时,您只会看到开发人员实际执行的更改。
更新: 由于上述工作流程可以自动化,我在github上启动了一个项目:svn rebase

2

使用 git svn

git svn clone -s <SVN主干/分支/标签父节点链接>

然后随意使用 git rebase 命令。

Original Answer 翻译成“最初的回答”。


0

我使用一个脚本,类似于 git 的 rebase 功能,但是用于 svn:

#!/bin/bash

set_safe()
{
    set -eo pipefail
}

validate_number()
(
    if (! [ "$1" -eq "$1" ] 2>/dev/null ) then
    {
        echo "$1 is not a number"
        return 1
    }
    fi
)

get_line()
(
    set_safe
    #head -n "$1" | tail -n 1
    sed -n "${1}p;$(($1+1))q"
)

split_trim()
(
    set_safe
    tr "$1" '\n' | sed -e 's/^\s*//;' -e 's/\s*$//'
)

run_svn()
(
    set +x
    #echo "svn $*" 1>&2
    svn --non-interactive --password "$svn_password" "$@"
)

rebase()
(
    set_safe

    url="$1"
    cur="$2"
    end="$3"

    validate_number "$cur"
    if ([ -z "$end" ] || [ "$end" = "HEAD" ]) then
    {
        end="$(run_svn info "$url" | grep "Last Changed Rev" | cut -d' ' -f4)"
        echo "end: $end"
    }
    else
    {
        validate_number "$end";
    }
    fi

    while (true) do
    {
        log="$(run_svn log "$url" -l1 -r "$cur:HEAD")"
        meta="$(echo -n "$log" | get_line 2 | split_trim '|')"
        next="$(echo -n "$meta" | get_line 1 | tail -c +2)"
        author="$(echo -n "$meta" | get_line 2)"
        date="$(echo -n "$meta" | get_line 3 | awk '{print $1, $2, $3}')"
        msg="$(echo -n "$log" | tail -n +4 | head -n -1)"
        cur=$next

        if ([ -z $cur ] || [ $cur -gt $end ]) then { break; } fi

        echo "$msg" > .msg

        echo "Merging revision $cur:"
        echo "========"
        cat .msg
        echo "========"

        run_svn merge "$url" -c $cur
        run_svn commit -F .msg
        rm -f .msg
        run_svn update

        echo "Success"
        echo

        cur=$(($cur + 1))
    }
    done
)

if ([ -z "$1" ]) then
{
    echo "Usage:"
    echo "    svn-rebase.sh <url> <begin revision> [end revision]"
    exit
}
fi

echo -n "svn password: "
read -s svn_password
echo

rebase "$1" "$2" "$3"
err=$?
if ([ $err -ne 0 ]) then { echo "rebase failed: $err"; } fi
exit $err

它逐一合并来自其他分支的修订。


0
我采用的方法是:
通过变基,您必须注意在再次合并时不要接受已经被变基的修订版。在合并时,进行挑选:仅选择实现新功能的特性分支上的修订版,而不是变基的变更集。然后它应该工作得很好。评论:我从来没有记得使用过重新整合分支做什么。我认为它仅适用于非常简单的用例。
在您的新方法中,从描述中不清楚您如何处理从主干到特性分支的变基(如果需要)。你想完全禁止变基吗?由于在SVN中分支操作很便宜,这也可以是一个选项。

1
以下是我的程序:
  1. 主干->f1,f2(特性分支)
  2. 当f1,f2完成后,主干->版本分支(用于集成)
  3. 将f1,f2合并到版本分支(VB)
  4. 测试VB;从f1,f2合并修复到VB;保持此步骤简短,否则重复2到3
  5. 当VB完成后,将其合并回主干,发布。
- bo bo

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