更新
对该问题的评论提出了另一种方法:您可以加密存储库中的机密文件,而不与承包商共享解密密钥。如果加密很好,这是一个可行的解决方案,并且比以下任何方法都更容易。如果我真的很偏执,这仍然让我有点不安,因为人们认为“足够安全”的加密方案会受到损害。不过,这是一种选择。
它还提出了另一种方法:
假设你没有在这个 repo 中使用 git lfs。然后你可以使用 git lfs 来跟踪秘密文件。 (这不是 lfs 的真正用途,但请耐心等待。)然后您将不与承包商共享您的 lfs 商店,因此承包商不应该 将他们的 repo 克隆配置为使用 lfs(或者,如果他们这样做,他们只会在尝试解析秘密文件时出错,这仍然可以)。
这个想法可行,因为 lfs 将 repo 中的文件替换为包含实际文件的 SHA-256 哈希的“指针文件”。如果无法访问您的 lfs 存储,则没有远程实用的方法可以从该哈希中推断出实际的文件内容。
这样做的问题是,如果您想将 lfs 用于其预期目的(管理大型二进制文件),那么您需要共享 lfs 存储,因此 lfs 存储不能再隐藏秘密。 (好吧,也许有办法解决这个问题,但它又开始变得怪异了。)即使那样,也可以选择设置自己的清洁/涂抹过滤器, lfs (替换为“无意义" 指向原始文件等的指针),尽管这更像是一种需要整理的高级配置。
任何这些解决方案的缺点是 git 的更改跟踪功能受到阻碍。例如,如果存储的版本被加密或被指针文件替换,则不能直接将此版本的机密文件与该版本的机密文件进行比较;你必须检查这两个版本到工作树并在文件系统上区分它们。
不管怎样,继续原来的答案...
这根本不是一个容易解决的问题。
MaSiMan 所说的或多或少是正确的,但并未解决在分叉和原始文件之间持续共享更改的潜在复杂性(不会将秘密文件泄露到分叉中)。当然,您可能已经拥有包含秘密文件和一堆历史记录的存储库,而且没有时间机器。所以在那种情况下......仍然可行,但可能涉及更多。
我将介绍如何使这种方法发挥作用,主要是希望它能让您相信它不实用。因此,如果您想相信我的话,请跳到下一个水平规则,之后我会建议您可能不喜欢的替代方案(但这仍然胜过这种方法)。
因此,您确实需要至少两个存储库。我不知道任何允许用户级别控制读取特定分支的托管软件,因此您需要一个承包商可以完全阅读的存储库。
首先创建一个您可以安全共享的分支。听起来您对此已有计划,但请注意:如果计划是创建分支,请删除机密文件,然后提交到分支...
x -- x -- x -- A -- x -- x <--(master)
\
\ # rm secret-file
\
B <--(contractor)
...contractor 分支并不像您想象的那样“干净”,因为A 与“contractor 分支”一样多,因为它“在master”上。
要完成这项工作,您的第二个 repo 必须是浅层克隆。
git clone --depth=1 -b contractor url/of/origin
您的托管软件可能无法创建“浅叉”,但只要它可以托管浅存储库,您至少可以手动创建克隆并将其托管为承包商的 origin 存储库。然后你只需要协调回购之间的推/拉。同样,这可能取决于托管软件,但在最坏的情况下,您可以设置浅仓库的本地克隆,并将原始仓库作为第二个远程仓库。
如果您希望承包商拥有更完整的历史记录(除机密文件之外的所有内容),您可以使用 git filter-branch 生成经过清理的历史记录。
git checkout master
git checkout -b clean-master
git filter-branch index-filter='git rm --cached --ignore-unmatch -- path/to/secret-file' -- clean-master
现在你有(在一个 repo 中)
A -- B -- C <--(master)
A' -- B' -- C' <--(clean-master)
秘密文件在clean-master 的历史上没有任何位置。因此,您创建了一个包含 仅 clean-master 分支的克隆
git clone --single-branch -b clean-master url/of/origin
现在是极度偏执,我应该指出,这个应该创建一个“干净”的克隆,但可以想象一个 git 实现可以在处理包文件时采取捷径,这样信息就会“泄漏” "从原来的分支到新的仓库。如果这让您担心,您可以检查新存储库中是否存在秘密文件(即在原始存储库中找到其 BLOB ID,并查看克隆是否通过这些 ID 知道任何对象),或者只是强制垃圾收集器在克隆中运行
git gc --aggressive --prune=now
应该这样做,因为新克隆还没有任何 reflog 历史记录。
无论如何,您可以在您的托管软件中设置这个新的 repo(它甚至不必再忍受浅层克隆),并且您必须再次弄清楚如何在两者之间进行推/拉。
对于任何一种解决方案,仍然存在一个问题,即如何在“干净”分支和常规分支之间共享更改而不会意外泄露机密文件。任何在两者之间合并的尝试都会有陷阱,并且无论如何“干净”分支的历史都不能包含这样的合并。为此使用合并可能是不切实际的。
当您看到我的建议时,您可能想知道这怎么可能是“更实用”的替代方案……就像我说的,这不是一个容易解决的问题。
因此,您可能必须通过变基来回共享更改——这不是我通常推荐的做法,因为这意味着您不断地创建并行提交,在两个不同的分支中“进行相同的更改”。这也意味着您必须使用标签(或一些类似的机制)来跟踪每一侧的哪些更改已共享给另一侧。
因此,无论您使用哪种方法创建“干净”分支,最初所有更改都存在于两个分支中。所以标记两个头。
git checkout master
git tag shared-to-clean-master
git checkout clean-master
git tag shared-to-master
现在在你的脏分支中进行了一些工作,并且承包商做了一些被推送到你的干净分支的工作,最终你有
... A -- B -- C -- D <--(master)
^shared-to-clean-master
... A' -- X -- Y -- Z <--(clean-master)
^shared-to-master
不,您在两者之间进行了 rebase 更改。
git checkout clean-master
git checkout -b clean-to-dirty-temp
git rebase --onto master shared-to-master clean_to_dirty_temp
git checkout master
git checkout -b dirty_to_clean_temp
git rebase --onto clean_master shared-to-clean-master dirty_to_clean_temp
虽然您可能认为“我只是编写脚本”,但请记住,rebase 操作可能会产生冲突。特别是,如果“脏”端包含对秘密文件的更改,那么您希望那些发生冲突,以便您可以通过确保文件不会在“干净”的分支。
所以这会产生
X' -- Y' -- Z' <--(clean-to-dirty-=temp)
/
... A -- B -- C -- D <--(master)
^shared-to-clean-master
B' -- C' -- D' <--(dirty-to-clean-temp)
/
... A' -- X -- Y -- Z <--(clean-master)
^shared-to-master
现在您想要推进 master 和 clean-master 引用,可能使用快进或挤压合并(取决于您是否要保留原始提交粒度)。
git checkout master
# then either...
git merge clean-to-dirty-temp
# ... which should fast-forward, or
git merge --squash clean-to-dirty-temp
# and finally
git branch -d clean-to-dirty-temp
git tag -f shared-to-clean-master
当然还有另一边
git checkout clean-master
# then either...
git merge dirty-to-clean-temp
# ... which should fast-forward, or
git merge --squash dirty-to-clean-temp
# and finally
git branch -d dirty-to-clean-temp
git tag -f shared-to-master
假设您快进了脏分支并挤压了干净分支。这会让你留下
... A -- B -- C -- D -- X' -- Y' -- Z' <--(master)
^shared-to-clean-master
... A' -- X -- Y -- Z -- BCD <--(clean-master)
^shared-to-master
你应该能够验证
git diff master clean-master
仅显示clean-master 中缺少秘密文件。冲洗并重复。
哦,除非任一侧分支和合并,否则变基会变得更加复杂。事实上,如果任何一方进行了“邪恶合并”,你就会遇到一些真正的麻烦。这可能意味着,如果任何一方确实包含分支和合并,您需要在来回重新调整更改之前简化历史记录。有很多方法可以做到这一点,但这是另一层复杂性。举个例子
git checkout shared-to-master
git checkout -b clean-to-dirty-temp
git merge --squash clean-master
# and proceed with the rebase from here
当然,为了防止分支分散到足以使这比已经保证的情况更大的混乱,您必须经常这样做。
好的,让我们面对现实吧:上面的解决方案很糟糕。怎么办?
好吧,您基本上需要从存储库中删除秘密文件,以便您和承包商可以共享相同的持续更改历史记录。
但是你需要那个文件,而且你可能需要它是版本协调的。
所以给秘密文件它自己的 repo,只供你看。然后创建第三个 repo,它不包含自己的文件,但将共享 repo 和“秘密文件” repo 作为子模块引用。