git:在上游仓库中保留 files/folder,但不包含在源代码中

git: Keep files/folder in upstream repo, but don't include in source

我想在我的 git 存储库中包含一个名为 screenshots/ 的文件夹(主要是为了支持自述文件)以显示...我创建的应用程序的屏幕截图,所以我做到了。然而,下次我尝试将新提交推送到回购协议时,我收到一个错误,敦促我拉下上游回购协议中所做的更改。

显然,我想要在我的项目源代码中提供大量屏幕截图。有没有办法“忽略”上游的某些目录?

我知道有 .gitignore,但那是为了将某些文件夹保留在本地项目的源代码中,但不想对其进行版本控制。我想要的是 .gitignore.

的对立面

思考 Git 存储的内容 会有所帮助。 Git 首先不存储文件夹;相反,Git 存储 commits,然后提交存储(部分)files,作为一种存档,及时冻结.这些文件的名称很长,可能包含斜线,例如 path/to/file.ext。您的 OS 可能——几乎肯定“确实”——要求将此文件 提取为 一个名为 file.ext 的文件位于名为 to 的文件夹中,该文件夹位于名为 path 的文件夹中。您的 OS 可能会称其为 path\to\file.ext 而不是 path/to/file.ext。但是对于 Git,它只是一个名为 path/to/file.ext 文件,文件名中包含(正向)斜杠。1

无论如何,上面的真正要点是要注意Git存储库中的存储单元是提交。每次提交:

  • 编号,具有独特的、非常大的 random-looking 数字,通常用 hexadecimal 表示。
  • 完全是read-only。这是编号技术工作所必需的,这意味着一旦提交,任何提交的任何部分都不能更改。 (例如,git commit --amend 命令标志有点像谎言:它只是做了一个 new-and-improved 替换提交,将旧提交推到一边。)
  • 存储两个东西:
    • 每个提交都包含每个 文件的完整快照,就像您(或任何人)进行提交时的形式一样。每个文件的这种存储形式可以占用非常小的磁盘space,有时字面上根本没有space对于这个特定的提交,因为文件是de-duplicated 在提交内和提交之间。但是只有 Git 可以读取这些文件,实际上没有任何东西,甚至 Git 本身也不能覆盖它们。
    • 每个提交存储一些元数据,或关于提交本身的信息。这包括提交人的姓名和电子邮件地址等内容。

在中存储的快照在您运行[=19]任何给定提交时提取到工作区 =] 或 git switch。此命令的意思是:从任何先前的 checked-out 提交中删除所有提取的文件,并将新选择的提交中的所有文件放置到位。 有很多挑剔的细节在这里我故意掩饰,但这是 git checkoutgit switch(您喜欢哪个命令)的基本工作。

签出一些提交后,您现在可以处理/使用这些文件。您处理/使用的文件 不在 Git 中。它们在您的 工作树 work-tree 中,作为普通文件存储在您的 OS 所需的文件夹中。您可以对这些文件做任何您想做的事情,因为它们 普通文件,不受 Git 任何方式的控制。当你用 这些文件做完事情后,你必须 运行 git add:2 这告诉 Git 到 扫描 这些文件并查找更新,如果需要,Git 将复制到其索引中,为下一个 git commit 做好准备。然后你 运行 git commit 和 Git 将其索引中的所有内容——所有最初签出的文件,除了你的 git added 文件更新的文件——变成一个新的快照对于新提交。

这意味着什么很简单:

  • screenshot/* 文件是否在提交中;
  • 如果他们在您签出的提交中,他们将在您进行的下一次提交中,如果不在,他们将不会—除非您使用git addgit rm 来改变这种情况。

反过来,这意味着您问题的初始答案是:不,您不能那样做。 但是....


1从技术上讲,这仅适用于出现在 Git 的 index 中的文件。一旦索引的 set-of-files 被冻结到新的提交中,Git 将每个单独的 path-name 组件存储在 tree 对象中,并且它需要多个树对象到 assemble 完整路径名。但是 Git 不会让您单独使用树对象:相反,它将树复制到索引中,让您使用索引然后提交索引中的内容。由于索引中没有文件夹,因此提交中不能有文件夹:即使内部树结构理论上允许它,Git cannot store an empty folder. There is however a submodule trick, which requires using two Git repositories,也可以伪造它。


等等,还有更多!或更少!

Git 确实 提供所谓的稀疏结帐。使用稀疏签出,您告诉 Git:看,我知道我要签出的这个提交中有一百万个文件。但是,例如,我只想查看和处理 src/ 文件。根本不需要提取任何 screenshot/doc/ 文件。您不需要创建名为 screenshotdoc 的 OS-level 个文件夹。你可以 re-use 这些文件我在下一次提交中没有看到,即使我不会也不会在我工作时看到它们。现在,继续检查提交 .

您必须仔细设置稀疏结帐(直到最近,主要是手动设置;新的 Git 版本有一些东西更多地针对只 使用 Git,而不是为 Git 本身编写新代码 的人,以使这更容易)。 设置稀疏签出的任何人都将看到所有 screenshot/ 文件。

或者,如果没有人需要在看到源文件的同时看到screenshot/文件,你可以做两个独立的提交流:

  • 一个提交流包含屏幕截图。您 git checkout screenshotsgit switch screenshots 获取最新的。 Git 从您的工作树中删除所有当前签出的文件(可能是所有源文件),并放置已提交的屏幕截图文件。现在您可以更新屏幕截图并添加和提交并设置新的以后的屏幕截图。
  • 另一个提交流包含源文件。你 git checkout maingit switch dev 或其他什么来获得最新的此类提交。 Git 删除所有当前签出的文件(无论是源文件还是屏幕截图)并放置最新提交的文件。

在此设置中,您可以使用 屏幕截图 来源,但不能同时使用两者。这很好用;这只是一个不同的 mind-set 习惯。新的(自 Git 2.5 起)git worktree 特性——最好不要使用得太广泛,除非你的 Git 版本至少是 2.15——使得在screenshots 分支和 maindev 或任何分支上的不同的完全独立的工作树。请记住,不可能有来自两者的文件的组合工作树。3

要设置 separate-stream-of-commits 分支,请使用:

git switch --orphan screenshots

例如。 (旧的 git checkout 也有一个 --orphan 但如果你使用它你还必须 git rm -r .,这有点可怕。)这个 switch 命令清空 Git 的索引,同时也清除你的工作树(尽管 untracked 文件将保留在其中)。您现在可以创建 screenshots/ 目录,填充它,然后 运行 git add screenshots 添加 screenshots/* 中的所有文件。下一个 git commit 您 运行 将 创建分支 并将此第一次提交作为其第一次也是唯一一次提交。这个分支现在与其他分支分开,不能轻易混在一起,当你 git checkout maingit switch main 时,Git 将删除所有提交的 screenshot/* 文件和re-populate 包含源文件的工作树。

请注意,这个 separate-stream-of-commits 技巧很像拥有一个完全独立的 存储库 ,其中 仅包含屏幕截图 。它带给您的只是 git clone 为您提供了“两个存储库”的所有提交,因为这些提交位于单个存储库而不是两个存储库中。如果屏幕截图实际上不是项目的一部分,那么首先使用单独的存储库可能更明智。


3可能获得两组文件,但不是普通的git checkoutgit switch.您需要告诉 Git 从源代码中获取最新提交, 从屏幕截图分支中获取最新提交,然后 you 需要结合这两者。有一些相对简单的方法可以做到这一点,例如,git archive,将它们放在一个没有人会对它们进行任何工作但可以访问的区域同时在一个 OS-level 文件夹中。

在 Git 版本 2.15 之前要小心 git worktree add 的原因是在 git gc 期间没有正确检查添加的 work-tree 有一个讨厌的错误。如果您有一个额外的 work-tree 闲置 2 周或更长时间,您可能会开始丢失其中完成的工作。 保证不能打破,但这样诱惑命运是不明智的。