很久很久以前,有人在一个遥远的办公室里复制了一个github存储库,并将其上传到Visual Studio Team Services(VSTS)。我们开发人员愉快地进行了编码,开发了功能并修复了VSTS中的错误。现在是时候将我们的代码重新发布到开源社区的热爱者了……
不幸的是,我们的VSTS存储库与github存储库没有共享历史记录,因为它是副本,而不是克隆。虽然我们可以将github存储库添加为远程存储库,但是将我们的代码合并回主要分支中却是令人讨厌的冲突。整个文件夹结构已被移动或重命名,并且开源开发人员已对github存储库中的这些文件进行了更改。
有没有办法将我们的分支挂回到它们的来源?像将整个分支树重新放置到复制存储库时github上的最后一次提交上一样?
我想出的最好的办法是将VSTS中的每个CL都挑选到github上,这听起来像是一些认真的侦探工作,弄清楚了在哪里插入重命名。
答案 0 :(得分:2)
通常很难将非克隆与实际克隆相结合。
让我们写一个理论示例,使用git://github.com/repo
作为原始示例。假设ssh://example.com/copy.git
将代表您使用以下命令序列设置的存储库:
<download tarball or zip file from github.com/repo>
<extract tarball or zip file into directory D>
$ cd D
$ git init
$ git add .
$ git commit -m initial -m "" -m "imported from github.com/repo.git"
之后,您从该独立存储库中创建了一个位于--bare
的{{1}}存储库。
现在已经过了一段时间,您已经意识到您想使用ssh://example.com/repo.git
的实际克隆。 las,您的github.com/repo.git
与ssh://example.com/repo.git
没有共享历史,也没有共同的提交。正在运行:
git://github.com/repo.git
让您获得所有公共提交,但是尝试将$ git clone ssh://example.com/repo.git combine
$ cd combine
$ git remote add public git://github.com/repo.git
$ git fetch public
与您自己的私有public/master
合并是很麻烦的。
在某些非常特殊的情况下,解决这个问题实际上并不难。诀窍在于比较master
可以访问的combine
存储库中的 root commit 与master
信息库中所有可以访问的所有提交combine
远程跟踪名称。如果幸运的话,一个提交的public/*
与您自己的根提交的tree
完全匹配,因为您得到的tarball或zip文件生成了一棵相同的树。
如果您不很幸运,则不会进行此类提交。在这种情况下,您也许可以找到“足够接近”的提交。但是,假设您 did 找到了一个tree
可以到达的,与您自己的根提交完全匹配的提交:
public/master
在这里,大写字母A--B--...--o--o <-- master (HEAD), origin/master
\
... (there may be other branches)
C--...--R--...--o <-- public/master
代表您自己的根提交(从下载的tarball或zip文件中创建的根提交)的实际哈希ID,而A
是紧随其后的提交一。 B
代表可以从C
到达的(或某些)根落实,并且主要在图中仅用于说明:我们可以肯定的是,至少还有一个这样的根(无父母)落实。 字母public/master
代表与您的提交R
完全匹配的提交,这是当前最有趣的提交。
我们现在想做的是假装 第二最有趣的提交A
的父级是提交B
而不是提交R
。我们做得到! Git有一个名为A
的工具。 git replace
的作用是在进行一些更改的同时复制一个对象。在我们的例子中,我们想要的是将提交git replace
复制到看起来与B
几乎完全一样但有一个改变的新提交B'
:其父项。我们希望B
列出提交A
的哈希ID,而不是将提交B'
的哈希ID作为B'
的父项。
换句话说,我们将拥有:
R
现在我们要做的就是说服Git,当它查找提交A---------B--...--o--o <-- master (HEAD), origin/master
B'
/
C--...--R--...--o <-- public/master
时,它应该注意到有一个 replacement 提交B
,并迅速避免了它的提交。从B'
注视着B
。这就是B'
的其余工作。因此,找到提交git replace
和R
后,我们运行:
B
现在Git 假装该图显示为:
git replace --graft <hash-of-B> <hash-of-R>
(好吧,除非我们运行 B'-...--o--o <-- master (HEAD), origin/master
/
C--...--R--...--o <-- public/master
来观察现实,否则Git会假装这个。)
除了查找提交git --no-replace-objects
相当艰巨的工作之外,查找R
和A
非常容易,它们是B
列出的最后两个哈希ID。 git rev-list --topo-order master
技巧有缺陷。替换提交git replace
现在存在于我们的存储库中,但通过特殊名称B'
在中定位,其中 refs/replace/hash
是原始提交hash
的哈希ID。默认情况下,此替换对象(及其名称)不会发送到新的克隆。
您可以制作具有替换对象及其名称的克隆,并对其进行处理,一切正常。但这意味着每次有人克隆您的B
存储库时,他们都必须运行:
combine
或类似的命令(此特定规则只是将克隆的git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*'
命名空间从属refs/replace/
的命名空间,这很粗糙,但有效)。
或者,您可以声明flag day并运行origin
或类似方法以将替换固定在适当的位置。我已经在其他地方对此进行了描述,尽管目前我能找到的最好的答案是我对How can I attach an orphan branch to master "as-is"?的回答,实际上,您创建了一个 new 存储库,其中包含git filter-branch
而不是{{1 }},没有B'
,并且具有作为B
的后代的每个提交的新副本(除了父哈希ID以外,其他内容相同)。然后,所有用户都从旧的A
切换到新的用户。这很痛苦,但是只有一次。
如果您不打算长时间使用组合存储库,则可能没有关系。
除上述内容外,您还可以使用嫁接的历史记录进行合并-Git命令通常会跟随替换项-之后,您可能不需要替换嫁接项。在这种情况下,缺点是短暂的:它只会持续到合并代码为止。
答案 1 :(得分:0)
假设VSTS仓库是Git仓库,您可以: