我们公司购买了第三方库开发人员的源代码。我们每次发布新版本时都会获得其源代码的副本。但是,我们还对其源代码进行了一些修改,这些修改不一定是上游到他们的代码库。
我在想我们应该能够创建一个git存储库,它只处理它们的源代码。然后,我们可以使用我们修改过的源代码然后执行一种rebase以使其保持同步。
我看到的一个问题是,我认为变基是一种在分支上发生的操作,将分支点从它所在的位置移动到它所在的分支上。然后会指出任何合并冲突。但是,我不确定如何做到这一点,这将使我们的存储库成为他们的主要分支。
我的想法是为他们的源代码创建一个单独的存储库(让我们称之为3rd-party
),我们将通过修改分支出来,并将其源代码作为主干。然后,我们可以将我们的分支机构从该主干上移除。然后,在我们的主存储库中,我们将链接到我们的3rd-party
分支。这是最后一点,就像我说阿布拉卡达布拉一样,我的双手都是波浪状的!
我建议的解决方案是什么?或者还有其他方法可以做我要求的事情吗?
如果我没有正确使用这些条款,请原谅我。我们在上个月才转向git,我还在学习。
为了清楚起见,我们的源代码已经包含了我们的更改。所以,我甚至不确定引入旧源代码的最佳方法是什么,我们已经成功合并,以便我们有一个共同的基础。我们的代码是主干,主干是我们的开发分支。我们已经以这种方式设置了我们的系统,并且已经设置了一些假定此设置的工具。我们还拥有不止一个图书馆。
我们目前的“流程”是从TFS迁移到的
当然,第3步是最成问题的,我希望这样做会简化事情,因为会有一个共同的祖先。
我编写了一些脚本,这些脚本将自动暂存那些没有我们更改的文件,以简化此过程。
由于变基似乎使事情变得复杂,或许我可以创建一个只包含3rd-party
库的存储库。我会根据@Mark Adelsberger的回答使用该存储库进行合并。然后我可以将该存储库的HEAD复制到我们的主存储库中。
这意味着它们将是两个独立且不同的存储库,它们之间没有链接,但可能是最好的方法。
根据评论中的讨论更新。不过,在我进入项目结构之前,还有一些关于项目结构的新注释。
如果您在第一个供应商源代码丢弃之前提交了代码,那么除了供应商代码的修改版本之外,这至少表明您拥有自己的重要代码文件的可能性。这不是我从原始问题中提到的。
如果是这种情况,您可能希望将供应商代码放在特定的子目录(./vendor/
)中,这不会反映在我最初提供的脚本中。 (如果没有,您将如何避免他们在您用于其中一个文件的路径/文件名中添加新文件的可能性?)
所以无论如何,无论我在哪里使用命令
cp -R /path/to/latest/source/drop/* .
我的假设是代码在/path/to/latest/source/drop
“正确”布局。
原始答案
每次你修改(或以任何方式重写)一个分支时,每个已经拥有该分支副本的仓库都需要进行清理。由于我希望您的所有开发团队都拥有包含对此源代码进行本地修改的分支的克隆,因此我不建议将rebase作为工作流程的常规部分。
相反,只是合并。
rebase的销售推销是它产生一个干净,线性的历史。有时这很有用,虽然它通常有成本。但在这种情况下,它根本不合理。您无法合理地生成包含您的更改和其他组织的更改的单个线性历史记录,其中它们不会持续进行您正在进行的某些更改,并且您确实可以持续获得所做的更改。 (哎呀,你甚至无法控制他们的变化是作为一个线性历史开始的。)
更重要的是,你会花费大量不必要的努力来做到这一点,即使条件恰到好处并且你以某种方式实现它。
我要做的是:使用适用于您团队的任何分支策略来维护您的代码。添加到“供应商”分支。您的修改永远不会影响供应商分支,但您将从供应商分支合并到您的开发分支(详细信息可能会根据您的确切分支策略而有所不同)。
例如,假设您有一个dev
分支,可以为即将发布的版本积累您的工作。您首先导入供应商代码,创建提交V1
。然后你从那里开始创建你的dev
分支并开始进行更改。
V1 <--(vendor)
\
A -- B -- C <--(dev)
现在有一段时间了,你从供应商处获得了另一个来源。查看vendor
分支,然后用新的源代码替换工作版本。
(下面假设vendor
分支工作树没有理由包含供应商提供的文件以外的任何文件。在实践中,您可能会发现一些内容,如.gitignore或.gitattributes文件,是有用的;并且在任何你需要确保.git目录没有被删除。所以你可能想要一个更“小心”的脚本代替我在这里展示的rm
命令...)
git checkout vendor
rm -rf *
cp -R /path/to/latest/source/drop/* .
git add .
git commit -m "20xx-xx-xx source drop from $vendor"
生产
V1 -- V2 <--(vendor)
\
A -- B -- C <--(dev)
现在你可以从vendor
合并到dev
了。
git checkout dev
git merge vendor
为了计算合并,git会发现V1
是合并基础,并将确定自vendor
以来在V1
上发生了什么变化。所以你可能会遇到冲突,事实上这可能会变得很难看,因为你可能无法阻止供应商随心所欲地进行疯狂的重构,但它会像任何其他方式一样容易。
V1 ----------- V2 <--(vendor)
\ \
A -- B -- C -- M<--(dev)
然后这个循环继续;你继续开发,每当你导入Vn
git将实现由于以前的合并,V(n-1)
是合并基础。
UPDATE
在评论中你提到你已经有一个repo设置,里面有你的一些代码。这并不妨碍使用上述方法,但它引入了一些问题 - 因为如果您的项目基于供应商代码的修改,那么第一个源代码之前的版本是什么样的呢?
我看到了一些可能意味着的事情,所以请随意跳到最能描述您情况的部分(或者,如果这些部分都没有,请澄清):
这是否意味着您只是没有提交原始供应商代码丢弃?
(更新2:基于评论,听起来就是这种情况。我建议的第一种方法涉及历史重写;你已经声明你不想重写历史。这取决于你,所以这是另一种选择这创造了一个稍微“怪异”的历史,但是执行起来很简单,并且可以很好地进行。)
与我发布的所有选项一样,您首先要为供应商分支创建“干净”的历史记录。
git checkout --orphan vendor
rm -rf *
cp -R /path/to/latest/source/drop/* .
git add .
git commit -m "initial source drop from $vendor"
现在你将“伪合并”到你现有的dev
分支中,这样当你将未来的版本添加到vendor
分支时,git会把它理解为合并基础。
git checkout dev
git merge --allow-unrelated-histories -s ours vendor
-s ours
指定了“我们的”合并策略,该策略说“不要改变我在HEAD
提交中已有的内容”。这会产生
V1 <--(vendor)
\
A -- B -- C -- M <--(dev)
其中M
与TREE
具有相同的“内容”(C
),但承认V1
为父母提交。您可以从这里继续使用最初描述的方法,因为当您引入V1
时,V2
将作为合并基础。
值得注意的是,当默认合并策略会产生合并冲突时,你应该只使用-s
- 它会在这里,所以没问题。如果默认合并策略能够解析合并,那么使用另一种合并策略会创建一个“邪恶合并”,其中的变化(相对于父母双方)被“隐藏”的方式可能会混淆用户和一些git命令。为此,它应该没问题。
也就是说,重写是“冒险”的说法有两个原因值得怀疑。 (1)在验证结果之前,您不必更换原点,因此组织风险为零;在最坏的情况下需要时间(任何替代方案也是如此)。 (2)你在评论中描述的合并方法的类型有更多的动作部分,因此更难以推理 - 这就是为什么我说某些事情可能出错(但不能真正预测究竟是什么)。重写不太熟悉,但这与“风险较高”不同。
考虑到这一点,我在这里保留了原始的重写方法:
在您的仓库中创建第二个“root”提交。关于rm
命令的相同注意事项如上所述。
git checkout --orphan vendor
rm -rf *
cp -R /path/to/latest/source/drop/* .
git add .
git commit -m "initial source drop from $vendor"
现在你有了
V1 <--(vendor)
A -- B -- C <--(dev)
由于我现在假设A
已经包含供应商代码的修改版本,所以你只想重新加入A
。
这将是历史重写,但您只需要执行一次。我推荐的这种重写方法是让所有人将所有代码都推送到共享远程,然后丢弃他们的克隆,然后进行重写,然后每个人都创建新的克隆。
重写将使用git filter-branch
完成。您需要找到V1
的提交ID。你可以从像这样的命令中得到这个
git log -n1 --format=%H vendor
它将是一个40个字符的十六进制数字字符串。然后重写命令看起来像
git filter-brnach --parent-filter 'sed "s/^\$/-p <commit-ID-from-above-command>/"' -- dev
如果你有多个分支,那么我说dev
你想要命名你的所有分支(除了vendor
)。如果你有很多分支,我想你可以使用--all
而不是全部输入,但是你需要一个稍微复杂的--parent-filter
来区分你的root和供应商的根目录;在这种情况下,请参阅git filter-branch
文档。
如果你的历史记录包含标签,你会希望它们移动,所以添加一个--tag-name-filter
git filter-brnach --parent-filter 'sed "s/^\$/-p <commit-ID-from-above-command>/"' --tag-name-filter cat -- dev
结果就是这样
V1 <--(vendor)
\
A' -- B' -- C' <--(dev)
其中A'
取代了A
等。重要的一点是,您拥有A'
所基于的“纯”供应商代码,可用作下一个供应商源代码的合并基础。从这里开始,一切都按照我最初的描述。
或者这是否意味着您拥有自己的代码并向其添加供应商代码?
在这种情况下,您现有的提交将不包含供应商代码;所以修复是类似的,但可能更简单。可能没有必要重写。
(您可以进行重写,特别是如果您希望供应商代码出现在历史记录中已存在的提交中。但这必须通过rebase而不是重新显示来完成,这会打开一整套新的蠕虫。如果您可以在初始历史记录之后添加供应商代码,那就更简单;如果您需要有关如何将供应商代码重写到现有历史记录中的说明,请告知我们,我们可以添加其他信息。)
您仍然希望为供应商分支创建“干净”的历史记录。
git checkout --orphan vendor
rm -rf *
cp -R /path/to/latest/source/drop/* .
git add .
git commit -m "initial source drop from $vendor"
但现在你只需将它合并到现有的dev
分支中,这样你就可以从那时起获得供应商文件。
git checkout dev
git merge --allow-unrelated-histories vendor
只要您的文件没有任何供应商文件具有相同的路径/文件名,此合并将顺利进行
V1 <--(vendor)
\
A -- B -- C -- M <--(dev)
然后你可以从这里继续使用最初描述的方法,因为当你引入V1
时,V2
将作为合并基础。