SVN 主干被旧版本覆盖。项目和主干文件夹现在有不同的历史
SVN trunk overwritten by old release. Project and trunk folder have now different histories
背景
我们有一个包含多个项目的 SVN 存储库:
ROOT
- Project1
- trunk
- Project2
- trunk
..等等
然后我们开始迁移到 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 中也处于 "inconsistent" 状态(日志部分匿名):
$ 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
现在,最后的 commit r6109 看起来很可怕。好像说当前trunk已经被r5477覆盖了
确实,trunk文件夹的日志完全漏掉了中间的修改:
$ 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 的开发人员使用
git reset --hard
将工作副本重置为本地主分支,该分支位于与 svn r5477 对应的提交处,并将其推送到远程主机。
- 这个,由 subgit 翻译,导致 svn 修订版 6109,它说 "Branch '/trunk' replaced from /trunk:5477"
- 因为 git 存储库映射到
Project1/trunk
,这已经用 r5477 重写了
- 然而,文件夹
Project1
甚至没有被 subgit 看到,这解释了为什么 Project1
的历史仍然包含现在丢失的提交的日志(代码在trunk其实是回到了r5477版本。
谁能确认这很可能是当前状态?我还在忽略什么吗?
我不知道 svn 可以让你(subgit 在这种情况下)以不一致的历史结束。
如果这是真的,谁能建议最好的解决方案是什么?
请注意 git 存储库没有那部分历史记录,因为它只同步 Project1/trunk
而不是 Project1
。
我觉得这应该在 svn 存储库中修复。但是我不知道怎么办。
以下是可行的解决方案吗?
$ cd Project1
$ svn merge -rHEAD:6089 .
$ svn ci
警告 and/or 有更好的建议吗?
我会解释发生了什么。
Git 存储库中有 'master' 的更新,它绑定到 SVN 中的 t运行k。此外,更新是 non-fast-forward,默认情况下 Git 不允许在没有 git push
命令的 -f
选项的情况下进行此类推送。
Non-fast-forward 更新被转换为分支替换,因为这是 SVN 存储库中最接近的模拟。例如,您可以在这样之后比较 git log master
并使用相应的 svn log -v Project1/trunk
输出进行更新,看到两者都不包含 r6089,并且列出的第一个提交将是 r5477(如果我们不考虑 r6109 没有Git 中没有模拟的更改)。要在 git log
输出中查看修订号,您可以按照 SubGit book 的“4.6. 推荐 client-side Git 配置”部分中描述的方式配置 Git 客户端和运行 git fetch
。所以我不认为 SVN 和 Git 存储库不一致。
如果您希望防止 分支在未来被替换,您不应该使用git push -f
命令。如果你想严格禁止这样的更新,你可以在服务器上将 Git 配置的 receive.denyNonFastForwards
选项设置为 true
。
修复怎么样:有两种方法。如果您可以在您的 Subversion 历史记录中进行这样的提交,您可以再次替换您的 trunk
但来自 t运行k@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 创建一个 "attic" 引用(以防止 [=61= 收集它们]),我猜你的情况是 refs/svn/attic/trunk/6089
,所以你可以用
获取
$ git fetch origin refs/svn/attic/trunk/6089:refs/heads/trunk6089
在客户端进行此提交。请注意,之后您几乎不会注意到 r6109 的任何证据,svn log
(以及 git log
)将直接跳到最后一个替换源,即 r6089。
另一种(更糟糕的)方法是从 SVN 历史记录中删除 r6109(我假设您没有其他使用 t运行k 的提交)。这可以通过 "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 模式:本地或远程。
你的合并方法怎么样,对我来说这会使历史变得复杂。特别是如果你合并整个项目而不是一些分支,所以我不会执行那个合并。
如果您有任何问题,请随时联系支持@subgit.com
Dmitry Pavlenko 提供的答案是正确的。一个重要的信息是 Git 提交和 SVN 修订之间存在 1:1 关系,如 SubGit bugreport.
中所述
为了防止这些分支替换发生,我们不同步包含斜杠的分支(默认 SubGit 配置)并使用服务器端 git 挂钩(.git/hooks/user-pre-receive
) :
#!/bin/bash
check_ref() {
OLD=$(git rev-parse )
NEW=$(git rev-parse )
REFNAME=
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
背景
我们有一个包含多个项目的 SVN 存储库:
ROOT
- Project1
- trunk
- Project2
- trunk
..等等
然后我们开始迁移到 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 中也处于 "inconsistent" 状态(日志部分匿名):
$ 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
现在,最后的 commit r6109 看起来很可怕。好像说当前trunk已经被r5477覆盖了
确实,trunk文件夹的日志完全漏掉了中间的修改:
$ 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 的开发人员使用
git reset --hard
将工作副本重置为本地主分支,该分支位于与 svn r5477 对应的提交处,并将其推送到远程主机。 - 这个,由 subgit 翻译,导致 svn 修订版 6109,它说 "Branch '/trunk' replaced from /trunk:5477"
- 因为 git 存储库映射到
Project1/trunk
,这已经用 r5477 重写了
- 然而,文件夹
Project1
甚至没有被 subgit 看到,这解释了为什么Project1
的历史仍然包含现在丢失的提交的日志(代码在trunk其实是回到了r5477版本。
谁能确认这很可能是当前状态?我还在忽略什么吗? 我不知道 svn 可以让你(subgit 在这种情况下)以不一致的历史结束。
如果这是真的,谁能建议最好的解决方案是什么?
请注意 git 存储库没有那部分历史记录,因为它只同步 Project1/trunk
而不是 Project1
。
我觉得这应该在 svn 存储库中修复。但是我不知道怎么办。
以下是可行的解决方案吗?
$ cd Project1
$ svn merge -rHEAD:6089 .
$ svn ci
警告 and/or 有更好的建议吗?
我会解释发生了什么。
Git 存储库中有 'master' 的更新,它绑定到 SVN 中的 t运行k。此外,更新是 non-fast-forward,默认情况下 Git 不允许在没有 git push
命令的 -f
选项的情况下进行此类推送。
Non-fast-forward 更新被转换为分支替换,因为这是 SVN 存储库中最接近的模拟。例如,您可以在这样之后比较 git log master
并使用相应的 svn log -v Project1/trunk
输出进行更新,看到两者都不包含 r6089,并且列出的第一个提交将是 r5477(如果我们不考虑 r6109 没有Git 中没有模拟的更改)。要在 git log
输出中查看修订号,您可以按照 SubGit book 的“4.6. 推荐 client-side Git 配置”部分中描述的方式配置 Git 客户端和运行 git fetch
。所以我不认为 SVN 和 Git 存储库不一致。
如果您希望防止 分支在未来被替换,您不应该使用git push -f
命令。如果你想严格禁止这样的更新,你可以在服务器上将 Git 配置的 receive.denyNonFastForwards
选项设置为 true
。
修复怎么样:有两种方法。如果您可以在您的 Subversion 历史记录中进行这样的提交,您可以再次替换您的 trunk
但来自 t运行k@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 创建一个 "attic" 引用(以防止 [=61= 收集它们]),我猜你的情况是 refs/svn/attic/trunk/6089
,所以你可以用
$ git fetch origin refs/svn/attic/trunk/6089:refs/heads/trunk6089
在客户端进行此提交。请注意,之后您几乎不会注意到 r6109 的任何证据,svn log
(以及 git log
)将直接跳到最后一个替换源,即 r6089。
另一种(更糟糕的)方法是从 SVN 历史记录中删除 r6109(我假设您没有其他使用 t运行k 的提交)。这可以通过 "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 模式:本地或远程。
你的合并方法怎么样,对我来说这会使历史变得复杂。特别是如果你合并整个项目而不是一些分支,所以我不会执行那个合并。
如果您有任何问题,请随时联系支持@subgit.com
Dmitry Pavlenko 提供的答案是正确的。一个重要的信息是 Git 提交和 SVN 修订之间存在 1:1 关系,如 SubGit bugreport.
中所述为了防止这些分支替换发生,我们不同步包含斜杠的分支(默认 SubGit 配置)并使用服务器端 git 挂钩(.git/hooks/user-pre-receive
) :
#!/bin/bash
check_ref() {
OLD=$(git rev-parse )
NEW=$(git rev-parse )
REFNAME=
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