3

背景

我们有一个包含多个项目的 SVN 存储库:

ROOT
 - Project1
   - trunk
 - Project2
   - trunk

..ETC

然后我们开始迁移到 git。我们设置subgit以保持 SVN 和 GIT 存储库同步(双向)。几个月以来,我们有一些开发人员使用 svn 和一些使用 git,显然没有任何问题。

上面的每个项目都变成了一个 git 存储库,其中 subgit 配置为:

trunk = trunk:refs/heads/master
branches = branches/*:refs/heads/*
tags = tags/*:refs/tags/*
shelves = shelves/*:refs/shelves/*

到目前为止,一切都很好。从现在开始,让我们关注Project1 。

问题

我注意到最近在 svn 中完成的一些提交没有移植到 git。查看 svn 端的日志,我发现这些提交在 svn 中也处于“不一致”状态(日志部分匿名):

$ svn log -v Project1

------------------------------------------------------------------------
r6109 | user1 | 2015-01-13 12:47:43 +0100 (di, 13 jan 2015) | 1 line
Changed paths:
   R /Project1/trunk (from /Project1/trunk:5477)

Branch '/trunk' replaced from /trunk:5477
------------------------------------------------------------------------
r6089 | user2 | 2015-01-08 13:46:27 +0100 (do, 08 jan 2015) | 1 line
Changed paths:
   M /Project1/trunk/src/com/....

-- fix xyz
------------------------------------------------------------------------
r5978 | user2 | 2014-12-26 21:30:28 +0100 (vr, 26 dec 2014) | 4 lines
Changed paths:
   M /Project1/trunk/src/com/...
-- fix abc
------------------------------------------------------------------------
[ ... more commits ... ]
------------------------------------------------------------------------
r5477 | user2 | 2014-10-16 17:07:25 +0200 (do, 16 okt 2014) | 1 line
Changed paths:
   M /Project1/trunk/src/com/...
-- fix bug23

现在,最后一次提交 r6109 看起来很吓人。好像说当前主干已经被r5477覆盖了。

实际上,主干文件夹的日志完全错过了两者之间的修订:

$ svn log -v Project1/trunk

------------------------------------------------------------------------
r6109 | user1 | 2015-01-13 12:47:43 +0100 (di, 13 jan 2015) | 1 line
Changed paths:
   R /Project1/trunk (from /Project1/trunk:5477)

Branch '/trunk' replaced from /trunk:5477
------------------------------------------------------------------------
r5477 | user2 | 2014-10-16 17:07:25 +0200 (do, 16 okt 2014) | 1 line
Changed paths:
   M /Project1/trunk/src/com/...
-- fix bug23

问题

我需要一些帮助来确定当前状态和可能的解决方案。

认为已经发生的事情(请在这里帮助我!):

  • 在某个时候,在同一源上使用 git 的开发人员使用 agit reset --hard将工作副本重置到本地 master 分支,该分支位于与 svn r5477 对应的提交处,并将其推送到远程 master。
  • 这由 subgit 翻译,导致 svn 修订版 6109,上面写着“分支 '/trunk' 从 /trunk:5477 替换”
  • 因为 git 存储库映射到Project1/trunk,所以已经用 r5477 重写了
  • 但是,Project1subgit 甚至没有看到该文件夹​​,这解释了为什么历史记录Project1仍然包含现在丢失的提交的日志(主干中的代码实际上回到了 r5477 修订版)。

谁能确认这很可能是当前状态?我还在忽略什么吗?我不知道 svn 可以让你(在这种情况下为 subgit)最终得到不一致的历史记录。

如果这是真的,任何人都可以建议最好的解决方案是什么?请注意,git 存储库没有那部分历史记录,因为它只同步Project1/trunk而不是Project1. 我觉得这应该在 svn 存储库中修复。但我不知道怎么做。

以下是可能的解决方案吗?

$ cd Project1
$ svn merge -rHEAD:6089 .
$ svn ci

警告和/或更好的建议有人吗?

4

2 回答 2

3

我会解释发生了什么非快进更新翻译

Git 存储库中有一个“master”的更新,它绑定到 SVN 中的主干。此外,更新是非快进的,默认情况下 Git 不允许在没有命令-f选项的情况下进行此类推送git push。非快进更新被转换为分支替换,因为这是 SVN 存储库中最接近的模拟。例如,您可以在此git log master之后进行比较并使用相应的输出进行更新,svn log -v Project1/trunk并看到两者都不包含 r6089,并且列出的第一个提交将是 r5477(如果我们不考虑 r6109 没有在 Git 中没有模拟的更改)。要查看输出中的修订号,您可以按照SubGit bookgit log的“4.6. 推荐的客户端 Git 配置”部分中描述的方式配置 Git 客户端并运行git fetch. 所以我不认为 SVN 和 Git 存储库是不一致的。

如果您想防止将来更换分支,则不应使用git push -f命令。如果您想严格禁止此类更新,您可以在服务器上的 Git 配置中设置receive.denyNonFastForwards选项。true

修复怎么样:有两种方法。如果您可以在您的 Subversion 历史记录中有这样的提交,您可以trunk再次替换您的,但来自 trunk@6089。您可以从 SVN 端执行此操作,例如分两步:

$ svn delete Project1/trunk
$ svn commit -m "Trunk deleted"
$ svn update
$ svn cp <URL of trunk>@6089 Project1/trunk
$ svn commit -m "Trunk recreated from r6089"

或者你可以从 Git 端更新它(在这样做之前确保你的工作树是干净的):

$ git update-ref refs/heads/master <SHA-1 of r6089 commit>
$ git push origin master -f

如果您当前的分支是 master,那么您可能需要立即运行git reset --hard HEAD

请注意,与 r6089 对应的 Git 提交不会丢失,对于没有引用的此类提交,SubGit 创建一个“阁楼”引用(以防止通过“git gc”收集它们),对于您的情况refs/svn/attic/trunk/6089,我猜是,所以您可以获取和

$ git fetch origin refs/svn/attic/trunk/6089:refs/heads/trunk6089

在客户端上有这个提交。请注意,在那之后您将几乎不会注意到 r6109 的任何证据,svn log(以及git log)将直接跳过它到最后一个替换源,即 r6089。

另一种(更糟糕的)方法是从 SVN 历史记录中删除 r6109(我假设您对主干没有其他提交)。这可以通过“svnadmin dump/load”过程来完成。在服务器上运行以下

$ svnadmin dump path/to/your/current/svn/repository -r1:6108 > repo.dump
$ svnadmin create path/for/repaiered/svn/repository
$ svnadmin load path/for/repaiered/svn/repository < repo.dump

然后配置对这个新存储库而不是您的 SVN 存储库的访问,并为其重新安装 SubGit。某些 SHA-1 哈希值可能会有所不同。有一种方法可以保留大部分哈希,但这取决于您的 SubGit 模式:本地或远程。

你的合并方法怎么样,对我来说这会使历史变得复杂。特别是如果您合并整个项目而不是某些分支,那么我不会执行该合并。

如果您有任何问题,请随时联系 support@subgit.com

于 2015-01-15T17:53:38.440 回答
0

Dmitry Pavlenko 提供的答案是正确的。一个重要的信息是 Git 提交和 SVN 修订之间存在 1:1 的关系,如SubGit bugreport中所述。

为了防止发生这些分支替换,我们不同步包含斜杠(默认 SubGit 配置)的分支,并使用服务器端 git hook ( .git/hooks/user-pre-receive):

#!/bin/bash

check_ref() {

    OLD=$(git rev-parse $1)
    NEW=$(git rev-parse $2)
    REFNAME=$3

    ZERO="0000000000000000000000000000000000000000"

    if ! [[ $REFNAME == refs/heads/* ]]; then
            # Not a branch action, probably a tag
            return
    fi

    BRANCH_NAME=$(expr "$REFNAME" : "refs/heads/\(.*\)")
    FEATURE_BRANCH=$(expr "$BRANCH_NAME" : "\(.*/.*\)")

    if [ "$OLD" = "$ZERO" ]; then
            if [ ${FEATURE_BRANCH} ]; then
                    # Permit feature branch creation
                    return
            else
                    echo "*** Rejected creation of non-feature branch"
                    exit 1
            fi
    fi

    if [ "$NEW" = "$ZERO" ]; then
            if [ ${FEATURE_BRANCH} ]; then
                    # Permit feature branch deletion
                    return
            else
                    echo "*** Rejected deletion of non-feature branch"
                    exit 1
            fi
    fi

    if [ ${FEATURE_BRANCH} ]; then
            # Pushes to feature branches are always allowed
            return
    fi

    for COMMIT in `git rev-list $OLD ^$NEW`; do
            # $COMMIT is reachable from $OLD, but not $NEW -> Force push
            echo "*** Force push is not allowed on branch $REFNAME"
            exit 1
    done

    # Check for non-fast-forward merge
    ALL_REVS=`git rev-list $OLD..$NEW | wc -l`
    REVS_WITH_SINGLE_PARENT=`git rev-list $OLD..$NEW --max-parents=1 | wc -l`

    if [ ${ALL_REVS} -ne ${REVS_WITH_SINGLE_PARENT} ]; then
            echo "*** Non-fast-forward merge detected on branch $REFNAME"
            echo "*** Please rebase your work before pushing!"
            exit 1
    fi

    for COMMIT in `git rev-list $OLD..$NEW`; do
            # Checking commit $COMMIT
            git branch --contains $COMMIT | grep -q -v / && echo "*** Commit $COMMIT rejected because it is contained in other branches" && exit 1
    done

}

while read old new refname;
do
    check_ref $old $new $refname
done

exit 0
于 2016-02-19T16:01:58.760 回答