GIT 保留分支特定文件 - 始终忽略特定文件

gal*_*lsi 2 git

我正在尝试保留包含分支特定信息(版本)的文件。

添加文件后,我.gitgnore使用文件路径更新。

问题:当我合并分支时,文件正在合并到目标分支中。

我尝试过以下操作: 如何让 Git “忘记”已跟踪但现在位于 .gitignore 中的文件?

使用git rm --cached <file>删除文件

添加文件使用--force会导致文件被跟踪,即使它在.gitignore

有没有办法将文件保存在特定分支上?

谢谢

tor*_*rek 8

不存在特定于分支的文件之类的东西

\n

Git 有:

\n
    \n
  • 提交
  • \n
  • 其中包含文件
  • \n
  • 并通过分支名称找到
  • \n
\n

但:

\n
    \n
  • 两个或多个分支名称可以指定单个提交,并且
  • \n
  • 您可以随时添加和删除分支名称
  • \n
\n

因此,由于分支实际上并不作为一个东西存在\xe2\x80\x94,所以它们在任何时候都是完全可以改变的\xe2\x80\x94,所以实际上不可能有任何特定于分支的文件。只有特定于提交的文件。

\n

您不能忽略跟踪的文件

\n

在评论中,您说:

\n
\n

我想在我将文件添加到当前状态的 .gitgnore 后,从此时起它将被忽略

\n
\n

不是这种情况。

\n

Git 永远不会真正忽略文件,并且.gitignore该文件中的内容的名称是错误的。然而,正确的名称应该是这样的.git-do-not-complain-if-these-files-are-untracked-and-if-they-are-untracked-and-I-use-an-en-masse-add-command-do-not-make-them-tracked。虽然这就是这里列出文件的作用但它是一个荒谬的文件名称,因此 Git 人员选择了.gitignore:一个不准确的名称,但名称很短,人们实际上可以输入。

\n

如何理解这一切

\n

让这一切变得有意义的秘诀是改变你的观点。那些刚接触 Git 的人认为 Git 存储文件,并使用分支来做到这一点。这两种想法都是错误的!Git 存储的东西是提交

\n

现在,提交确实包含文件。但这是一个全有或全无的交易:你要么有一个提交\xe2\x80\x94,因此它的所有文件\xe2\x80\x94,要么你没有,因此你没有它的任何文件。存储在每个提交\xe2\x80\x94内的文件每个提交都保存每个文件的完整快照;我们稍后会回到这个想法\xe2\x80\x94 以一种特殊的、只读的、仅限 Git 的、压缩的和去重复的形式存储。只有 Git 才能读取这些文件,并且 \xe2\x80\x94 甚至 Git 本身\xe2\x80\x94 都无法写入这些文件。这意味着提交的文件对于完成任何工作完全没有用处。

\n

因为提交的文件是无用的(即对于完成工作而言),Git 必须将提交的文件提取到工作区。在此工作区域中,提取的文件将扩展为其正常的日常形式。 这些是您可以查看和使用的文件。它们是普通文件。它们唯一特别的地方是 Git在某个时刻从某个提交中提取了它们。

\n

因为它们实际上是从提交中复制出来的副本,所以事实上这些文件根本不在 Git 中! 它们只是供您随心所欲地使用。换句话说,它们是您的文件。Git 的文件是为 Git 准备的,而你的文件是为你准备的:有时,当你告诉它时,Git 只会复制一些文件。

\n

除了存储的文件\xe2\x80\x94存储在这个特殊的Git化形式\xe2\x80\x94中,每个提交都存储一些元数据,或者有关提交本身的信息。这包括作者姓名和电子邮件地址等内容。我们不会在这里讨论任何细节,但此元数据对于 Git 至关重要:没有它,Git 就无法工作。

\n

所有这一切的目的是了解您看到和使用的文件为何不是 Git 的文件。它们不在 Git 中,因此您可以用它们做任何您想做的事情。这基本上是一件好事\xe2\x80\x94,但由于它们不在 Git 中,你还可以向你的工作区添加更多文件,这些文件甚至不是从 Git 中 出来的。这些额外添加的文件是未跟踪的文件。 这里有一些机制,了解它很重要。

\n

Git 的索引是 Git 了解文件的方式

\n

如果您已经使用计算机和文件一段时间了,那么您的工作树(存放文件的位置)就非常简单。它就像您可以使用的任何其他文件集一样。您可以在这里创建和销毁文件。Git 只是通过检查一些提交来最初为您设置一些文件。Git 使用分支名称查找该提交,我们稍后会回到这个想法,但现在,我们只是注意到您的初始文件集可能来自刚刚的提交。(如果没有,他们不久前就完成了一次提交。)

\n

这意味着每个文件都有两个副本:

\n
    \n
  • 一种是只读副本,以安全形式保存在当前提交中,可以永远访问(或者只要提交继续存在)。
  • \n
  • 另一个是常规文件,可以让您做任何您想做的事情。
  • \n
\n

但是,在提交\xe2\x80\x94(以内部 Git 专用格式\xe2\x80\x94 存储只读、压缩和重复数据删除的文件)和工作树(其中包含这些普通文件)之间,Git 添加了一个第三个副本\xe2\x80\x94 好吧,每个文件的副本 \xe2\x80\x94。第三个“副本”存储在 Git 所称的索引暂存区缓存中。这三个术语都指同一件事。这里真正包含的是文件的名称和一些其他信息,包括有关预去重副本的信息。当文件刚刚从某个提交中出来时,去重后的副本就是存储库中永久冻结的副本。

\n

由于该副本已冻结,因此使用该文件相同版本的所有提交都可以共享它。Git 的索引也是如此。所以它实际上并不需要任何空间来谈论。这就是重复数据删除的实际应用。

\n

如果更改工作树副本,则需要让 Git 更新其索引副本。 这就是该git add命令的含义。 当您运行git add已更改的文件时,Git:

\n
    \n
  • 将文件压缩为特殊格式;
  • \n
  • 检查它是否已经在任何地方拥有该文件,如果是,则使用冻结的文件,更新 Git 的索引;
  • \n
  • 否则,冻结的准备好进入下一次提交,从而更新其索引
  • \n
\n

\xe2\x80\x94 这意味着在 后git add,索引已准备就绪:您可以进行包含更新文件的新提交。所有现有文件仍然存在于 Git 的索引中。因此,正在发生的事情是索引始终准备好下一次提交。下一次提交最初与当前提交相同。

\n

如果您的git add文件尚未在索引中,Git 会将该文件压缩为冻结格式,并在 Git 的索引中为新文件创建一个新条目。如果您是git rm一个文件,Git会从其索引和工作树中删除该文件,现在下一次提交将完全缺少该文件。这样,索引仍然是建议的下一次提交。

\n

如果您从工作树中删除一个文件,然后运行git add该文件,Git 只会删除索引条目。此过程不会影响提交中的冻结文件。情况也是如此git rm:它只删除索引条目,而不删除底层文件数据。底层文件数据根本无法更改,并且只要某些提交正在使用它,它也无法被删除。因此,索引再次保存建议的下一次提交。

\n

这意味着git commit只需要对索引进行快照。索引中的所有内容都已处于冻结格式,随时可以使用:只需将其打包到新的提交中即可。这次新提交中的文件集正是当时 Git 索引中的文件。

\n

您在工作树中的git checkout和之间所做的事情在这里并不真正相关。但是你的工作树复制到 Git 的索引,这确实很重要。因此,只要您也进行这些更改,更改文件的工作树副本就很有用。git commitgit addgit add

\n

如果您不进行 git add这些更改,则来自当前提交的工作树文件(因此现在在索引中已“过时”)(与工作树中的内容相比)仍然存在如果你做了一个新的提交,那么它就会“过时” 。没关系:有时这就是您想要的!git status告诉您这些未针对 commit 进行的更改有点烦人,但这没有任何问题。但请注意,如果您运行git add .,该文件已在索引中,并将在索引中更新。git add .像orgit add *或 这样的整体操作git add -u将看到工作树副本与索引副本相比已更新,并将像往常一样从工作树更新索引副本。

\n

这意味着了解索引中的内容非常重要。没有面向用户的 Git 命令来列出索引中的文件,但有一个不是面向用户的:您可以运行git ls-files(用于git ls-files --stage获取更多详细信息;git ls-files它本身只是列出索引中的文件的名称)在 Git 的索引中;有关详细信息,请参阅文档)。

\n

未跟踪的文件是那些不在索引中的文件

\n

你的工作树是你的。因此,您可以在其中创建尚未出现在 Git 索引中的文件。该git status命令将这些文件称为untracked。因此,如果您签出的提交没有名为 的文件F,并且您创建了一个名为 的新文件F,那么您现在就有了一个名为 的未跟踪文件F

\n

您还可以用于git rm --cached从Git 索引中删除文件副本,而不将其从工作树中删除。假设 Git 提取了一些提交,并且该提交附带了一个名为F. 这意味着当前有 3 个副本F:当前提交中的只读副本、Git 索引中准备进入下一次提交的副本以及工作树中的副本。如果您现在运行git rm --cached F,Git会删除的索引副本F。只读副本仍在当前提交中,但您建议的下一个提交\xe2\x80\x94索引\xe2\x80\x94中的内容缺少文件F。您现在所做的新提交将没有文件F

\n

不管怎样,文件F现在位于您的工作树中,但不在 Git 的索引中。 这就是使文件未被跟踪的原因。 因为它不在 Git 的索引中,所以它不会出现在下一次提交中。如果当前提交F中有一个,则当前提交和下一个提交之间的差异将包括指令:“删除文件”。如果当前提交中没有,则 file 不会有差异,因为它不会出现在当前或下一个提交中。FFF

\n

因此,未跟踪的文件非常简单:它是您工作树中的文件,但不在 Git 的索引中。但文件变得未跟踪的方式就不那么简单了:也许它未跟踪是因为它没有提交,或者它未跟踪是因为您将其从 Git 的索引中删除了。毕竟,你对此有一定的控制权。

\n

相比之下,Git 索引中的文件会跟踪。也许它在那里是因为它在您签出的提交中。或者,也许它在那里是因为你跑去git add将它复制到 Git 的索引中。您也可以对此进行一些控制。

\n

但您确实需要记住,当您切换其他某个提交时,Git 将从该提交中清空这些文件的索引,并用您要切换到的其他提交中的这些文件填充索引。因此,如果您现在有一些跟踪或未跟踪的文件,这种情况可能会改变,因为索引的内容可能会随着新的.git checkout

\n

git reset(和 new-since-2.23命令的一些变体git restore也会影响 Git 的索引。它是一个大而重要的数据结构!它是新提交的来源。所以它总是一个好的时不时地想一想。)

\n

这让我们回到.gitignore

\n

列出文件.gitignore主要完成我之前列出的两件事:

\n
    \n
  • 当文件未跟踪时,它不会git status文件未跟踪。

    \n

    请注意,git status当它被跟踪时,可能也没有提及任何相关内容。因此,您无法真正判断文件是否被跟踪,或者git status至少不是在所有情况下都被跟踪:

    \n
      \n
    • 如果git status说某个文件已暂存以进行提交或未暂存以进行提交,则该文件肯定会被跟踪。
    • \n
    • 如果git status说某个文件未跟踪,则该文件肯定未跟踪。
    • \n
    • 但是,如果git status没有提及文件,我们就不知道其已跟踪/未跟踪状态。
    • \n
    \n
  • \n
  • 并且,如果当前未跟踪该文件,它会阻止git add .或其他类似的“添加许多文件”命令添加该文件。

    \n
  • \n
  • 但如果某个文件已被跟踪,则 中的条目.gitignore无效。

    \n
  • \n
\n

特殊技巧(尽量不要用)

\n

您可以使用一个特殊的技巧,但它不是为此设计的。F假设您有一些被跟踪并提交的文件。您对 的工作树副本进行了一些更改F,但您希望确保不会意外进行git add此更改并将其复制回 Git 的索引:您希望 Git 的索引保留旧副本

\n

你可以在这里做的是运行:

\n
git update-index --skip-worktree F\n
Run Code Online (Sandbox Code Playgroud)\n

在 file 的索引条目上设置F跳过工作树标志。这告诉 Git假装索引副本始终与工作树副本同步。Agit add .不会复制F回 Git 的索引,因此旧副本将保留在 Git 的索引中。

\n

该标志用于Git 称为稀疏结帐的功能。如果您在此处开始设置,以后将无法正确使用稀疏结帐功能。它还具有许多奇怪的副作用,因为 Git 有时需要更改文件的索引副本。我不会在这里讨论所有细节:它们变得很复杂。

\n

分支名称如何运作

\n

无需深入了解所有细节,与每次提交关联的元数据可以让 Git 字符串一起提交到链中。这些链都指向向后(出于我们不会详细讨论的原因),因此如果我们绘制它们,我们会得到如下所示的图片:

\n
... <-F <-G <-H\n
Run Code Online (Sandbox Code Playgroud)\n

链中的最后一次H提交在哪里。提交有一些哈希 ID,这就是 Git 实际找到提交的方式;例如,您将在输出中看到这些哈希 ID 。但哈希 ID 对人类来说毫无用处,因为它们又大又丑,而且不可能正确处理。因此,虽然Git使用哈希 ID,但我们不使用。我们使用分支名称git log

\n

分支名称仅保存我们想说的“在分支上”的最后一次(最近/最新)提交的哈希 ID。当我们有一个简单的提交链时,我们会得到这样的结果:

\n
...--F--G--H   <-- branch\n
Run Code Online (Sandbox Code Playgroud)\n

然而,一旦我们开始创建大量分支并这些分支上进行不同的提交,我们就会出现分歧:

\n
          I--J   <-- br1\n         /\n...--G--H\n         \\\n          K--L   <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n

这有很多有趣的事情。其中两个是:

\n
    \n
  • 直到并包含的提交H都在两个分支上。
  • \n
  • 名称br1选择 commit J;名称br2选择 commit L
  • \n
\n

这意味着git checkout br1将从 commit 中的文件填充 Git 的索引和工作树J。使用git checkout br2,您将用 commit 中的文件替换这些文件L。只要和中的文件集保持不变,工作树中任何未跟踪的文件(不在或 中)在提交和之间切换时都不会受到干扰。JLJLJL

\n

但关于分支名称的事情是:

\n
    \n
  • 他们移动;和
  • \n
  • 我们可以随时创建和销毁新的。
  • \n
\n

现在让我们看一下为提交创建一个新名称。J我们从git checkout br1,选择commit开始J,并稍微更新我们的绘图:

\n
          I--J   <-- br1 (HEAD)\n         /\n...--G--H\n         \\\n          K--L   <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n

特殊名称HEAD是 Git 如何知道我们正在使用哪个分支名称的方式。

\n

现在让我们br3使用 来创建一个新名称git branch br3。图片有一点变化:

\n
          I--J   <-- br1 (HEAD), br3\n         /\n...--G--H\n         \\\n          K--L   <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n

现在commit有两个J名称。如果我们现在创建一个新的提交,我们所做的新提交将获得一个新的、唯一的哈希 ID,但我们将仅将其称为New (我喜欢在这里为ergeN保留):MM

\n
               N   <-- br1 (HEAD)\n              /\n          I--J   <-- br3\n         /\n...--G--H\n         \\\n          K--L   <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n

Git 所做的就是对索引中的文件进行快照并添加适当的元数据,从而创建提交N,然后N实际的哈希 ID 写入当前分支名称

\n

提交J现在是上的最后一次提交br3,但同时是在br1和 上br3。向上的提交H都在所有三个分支上。

\n

这就是为什么不存在特定于分支的文件之类的原因

\n

当我们创建新的提交时,提交中的文件J不会也不能更改。然而 commitJ 曾经是branch的尖端提交br1。现在是分支的提示提交br3,分支br1已移至选择提交N

\n

中的文件J 可以特定于 commit J。您只需将正确的内容放入 Git 的索引中,然后在进行commitgit commit运行即可。但它们不能特定于某个分支名称:名称会四处移动,并且有一天提交可以在多个分支上进行。例如,我们现在可以运行然后运行。该步骤执行以下操作:JJgit checkout br2git merge br3checkout br2

\n
               N   <-- br1\n              /\n          I--J   <-- br3\n         /\n...--G--H\n         \\\n          K--L   <-- br2 (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

merge命令找到合并基础(提交H),然后将在H-to- leg 上完成的工作与在-to- legL上完成的工作结合起来,以生成新的合并提交HJ M

\n
               N   <-- br1\n              /\n          I--J   <-- br3\n         /    \\\n...--G--H      M   <-- br2 (HEAD)\n         \\    /\n          K--L\n
Run Code Online (Sandbox Code Playgroud)\n

分支名称现在选择新的br2合并提交,并且此合并提交会返回到提交L J,因此该提交J现在位于所有三个分支上。(commit 也是如此I。)

\n

通过从尖端开始并向后工作\xe2\x80\x94 “包含”或到达提交\xe2\x80\x94的分支集在Git存储库中动态更改。一项提交可以(而且很多情况下)是在多个分支上进行的。

\n