19

我的组织正准备使用 github 发布我们软件的开源版本,但是我不确定解决此问题的最佳方法:

我们有两个分支masterreleasemaster包含一些我们决定不发布的专有组件,release包含我们想要分发的清理版本。问题是,如果我们只是将发布分支推送到 github,则可以通过查看修订历史来检索专有组件。

我正在考虑创建一个单独的存储库,将relase的 HEAD 复制到其中,执行git init. 并将该存储库推送到 github。但是,我们希望保留从master中挑选某些补丁到将来发布的能力,并将这些更改推送到 github。

有没有办法在不维护两个单独的存储库的情况下做到这一点?

谢谢!

更新:

更具体地说,这是我们目前提交历史的样子:

--- o - o - o - o - f - o - o - f - master
             \
              c - c - c - c - c - c - c - REL - f - f

其中 'o' 是master,专有分支中的提交,'c' 是删除不应发布的内容的提交(通常不是删除整个文件,而是修改现有文件以不依赖专有组件),而 'f' 是master中的修复也适用于发布,因此已被精心挑选。REL 是我们认为可以安全发布的代码的标记版本,没有任何历史记录(即使是以前版本的发布分支,因为在 REL 标记之前并非所有专有材料都已删除)。

4

2 回答 2

17

Ben Jackson 的回答已经涵盖了总体思路,但我想在这里添加一些关于最终目标的注释(不仅仅是评论的价值)。

您可以很容易地拥有两个分支,一个具有完全干净的(无私有文件)历史记录,一个具有完整的(具有私有文件),并适当地共享内容。关键是要小心你如何合并。过于简单的历史可能看起来像这样:

o - o - o - o - o - o - o (public)
 \       \           \   \
  x ----- x ----x---- x - x (private)

o提交是“干净”的提交,x是包含一些私人信息的提交。只要您从公共合并到私有,它们都可以拥有所有所需的共享内容,而不会泄露任何内容。正如本所说,你确实需要小心这一点 - 你永远不能以其他方式合并。尽管如此,还是有可能避免的——而且你不必限制自己去采摘樱桃。您可以使用正常所需的合并工作流程。

当然,实际上,您的工作流程最终可能会稍微复杂一些。您可以在自己的分支上开发一个主题(功能/错误修复),然后将其合并到公共和私人版本中。你甚至可以时不时地挑选樱桃。真的,任何事情都会发生,除了将私人合并到公共之外。

过滤器分支

所以,你现在的问题只是让你的存储库进入这个状态。不幸的是,这可能非常棘手。假设存在一些涉及私有和公共文件的提交,我相信最简单的方法是使用filter-branch创建公共(干净)版本:

git branch public master   # create the public branch from current master
git filter-branch --tree-filter ... -- public    # filter it (remove private files with a tree filter)

然后创建一个仅包含私有内容的临时私有分支:

git branch private-temp master
git filter-branch --tree-filter ... -- private-temp    # remove public files

最后,创建私有分支。如果您可以只拥有一个完整版本,您可以简单地合并一次:

git branch private private-temp
git merge public

这将为您提供只有一次合并的历史记录:

o - o - o - o - o - o - o - o - o - o (public)
                                     \
  x -- x -- x -- x -- x -- x -- x --- x (private)

注意:这里有两个单独的根提交。这有点奇怪;如果你想避免它,你可以使用git rebase --root --onto <SHA1>将整个私有临时分支移植到公共分支的某个祖先上。

如果你想要一些中间的完整版本,你可以做同样的事情,只是在这里和那里停下来合并和变基:

git checkout -b private <private-SHA1>  # use the SHA1 of the first ancestor of private-temp
                                        # you want to merge something from public into
git merge <public-SHA1>           # merge a corresponding commit of the public branch
git rebase private private-temp   # rebase private-temp to include the merge
git checkout private
git merge <private-SHA1>          # use the next SHA1 on private-temp you want to merge into
                                  # this is a fast-forward merge
git merge <public-SHA1>           # merge something from public
git rebase private private-temp   # and so on and so on...

这会给你一个类似这样的历史:

o - o - o - o - o - o - o - o - o - o (public)
      \              \               \
  x -- x -- x -- x -- x -- x -- x --- x (private)

同样,如果您希望他们有一个共同的祖先,您可以做一个首字母git rebase --root --onto ...开始。

注意:如果您的历史记录中已经有合并,您需要-p在任何变基上使用该选项来保留合并。

假装

编辑:如果修改历史真的很难处理,你总是可以完全捏造它:将整个历史压缩到一个提交,在你已经拥有的同一个根提交之上。像这样的东西:

git checkout public
git reset --soft <root SHA1>
git commit

所以你最终会得到这个:

o - A' (public)
 \
  o - x - o - x - X - A (public@{1}, the previous position of public)
               \
                x - x (private)

whereAA'包含完全相同的内容,并且X是您从公共分支中删除所有私有内容的提交。

此时,您可以将公共合并为私有,然后按照我在答案顶部描述的工作流程进行操作:

git checkout private
git merge -s ours public

-s ours告诉 git 使用“我们的”合并策略。这意味着它将所有内容完全保留在私有分支中,并简单地记录一个合并提交,显示您将公共分支合并到其中。这可以防止 git 将那些“删除私有”更改从提交应用X到私有分支。

如果根提交中包含私有信息,那么您可能想要创建一个新的根提交,而不是在当前提交之上提交一次。

于 2010-10-26T05:00:18.253 回答
5

提交的 SHA 基于提交 blob,其中包括父 SHA、提交文本和文件树的 SHA。树包含树中每个 blob 的 SHA。因此,任何给定的提交都取决于该修订中的所有内容,并且每个父修订都返回到空存储库。如果您有一个从包含您不想发布的文件的版本(无论多么间接)派生​​的提交,那么您就不想发布该分支。

第一个git filter-branch关于从存储库中删除机密文件的示例。它通过创建一个替代历史(重写所有的树和提交)来做到这一点。如果您理解我回答的第一部分,您就会明白为什么这一定是真的。

您应该能够运行 filter-branch 命令从“干净”提交中创建新提交。历史会有些奇怪(旧版本可能无法构建,因为它们现在不完整或以其他方式损坏)。这不会破坏存储库中的任何现有分支或 blob。它将创建所有共享文件 blob 但不共享树或提交的新(并行)文件。您应该能够安全地推送该分支,而不会暴露它未引用的任何对象(当您推送分支时,仅推送该分支命名的 SHA 及其依赖项)。然而,这会有些风险,因为一个git merge进入“干净”分支,您最终可能会拖入“私有”分支和对象。您可能需要使用挂钩(提交或推送触发器)来仔细检查私人文件是否没有转义。

于 2010-10-26T04:20:28.753 回答