我如何 gitignore 不同回购协议的不同文件,包括拉取请求?
How can I gitignore different files for different repos including pull requests?
remote
______|______
| |
localA localB
-file1 -file3
-file2 -file4
-file3 -file5
我想让我的远程仓库存储文件 1-5。我希望 localA 回购只有 push/pull 个文件 1-3。我希望 localB 回购只有 push/pull 个文件 3-5。所以目标是让一些文件在本地仓库和一些不同步的文件之间同步,但远程仓库应该存储所有这些文件。我的 .gitignore 文件可以很好地提交,但最终会拉下所有我不想要的文件。我也尝试过使用 .git/info/exclude 文件,但也没有成功。如果需要,我可以从头开始,但我更愿意配置现有设置。
编辑上下文:
这是一个奇怪的测试环境,用户在其中使用个人帐户在一个区域进行更改,但必须在另一个区域的共享帐户上进行测试。我希望在他们的个人帐户下跟踪他们的更改,而不是共享帐户。我希望在共享帐户下跟踪测试结果,而不是他们的个人帐户。
你可能会得到你想要的,但首先你必须改变你想要的。
Git 不存储 文件; Git 存储 提交 。一次提交就像一个文件存档——事实上,每个提交都包含 每个 文件的完整存档——还有一些额外的 add-on 特性。推送和获取操作(pull
表示首先 运行 fetch
,然后 运行 第二个 Git 命令 并且它是fetch 步骤,而不是第二个命令,与此处的 push 相当)通过 t运行sferring 整个 commits 工作。因此,假设您的每个用户都在为他们拥有的 每个 文件创建一个 read-only 存档,并且每次都发送这些文件——因为 是 他们在做什么。
名称.gitignore
(或.git/info/exclude
,其工作方式相同)非常具有误导性:它不会导致文件被忽略。 read-only 存档充满了它包含的任何文件,并且 none 这些文件可以 忽略 因为它们已经在 read-only 存档中。当您提取该存档(使用 git checkout
)时,您将获得它的所有文件。这些文件现在位于您的工作区(您可以在其中查看和使用它们,甚至可以更新它们)和 Git 的存储区(Git 称为 暂存区) 或索引,准备好进行 下一次 提交(一个新的 read-only 存档)。除非你明确地删除其中一些文件,否则它们都将在下一个提交中。
B运行ches,在 Git 中,意思也比您想象的要少。 Git 与 b运行ches 无关,而是 commits。 B运行ch 名称只是查找 某些特定提交的一种方式。我们需要这个,因为单个提交的实际名称非常难看 random-looking 哈希 ID,用 hexadecimal 表示,没有人能记住或正确输入。 b运行ch 名称只是我们让计算机保存 latest 提交的哈希 ID 的方式。最新的提交保存了其前一个提交的哈希 ID,因此从最近的提交,Git 可以回顾一跳。前一次提交依次包含 其 前一次提交的哈希 ID,以便 Git 可以再回头一跳,依此类推。
Ok so let's say I have 2 branches, one for localA and one for localB.
同样,b运行ches 不适用于 files。 B运行ches 用于查找提交。每个提交都包含 每个 文件的完整快照(存档)——或者更准确地说,它包含的每个文件。你可以有一个提交——一个档案——只包含一个文件。但这不会很有帮助,至少对于普通的日常 Git 工具来说是这样。 (您可以编写自己的工具来处理这些问题,但这需要大量工作。)
Am I able to merge both branches in the remote repo without overwriting file3?
git merge
操作是关于合并工作。考虑像这样的正常日常合并:
我们采用三个(而不是两个)提交,即每个文件的三个 read-only 存档。
这三个档案之一是 merge base: 提交是 shared 因此 两个b运行ches.
剩下的两个提交是两个 b运行ches 中每一个的最新提交。其中之一是“我们的”b运行ch——我们在开始之前用 git checkout
或 git switch
选出的那个。另一个是“他们的”b运行ch,我们将其命名为 git merge
命令。
我们现在Git将合并库中的所有存储文件与所有进行比较将文件存储在我们的 b运行ch 提示提交中。无论 changes Git 在这里观察到什么——添加新文件、删除旧文件、更改现有文件——都是我们的 changes.
接下来,我们Git将合并库中的所有存储文件与所有进行比较他们 b运行ch 提示提交中存储的文件。 改变 Git 这里观察到的是他们的改变。
最后,我们 Git 将这两组更改结合起来:如果我们添加了一个 totally-new 文件,Git 将获取我们的文件;如果他们添加了一个,Git 会拿走他们的文件;如果我们修改了 fileA
,Git 会保留对该文件的更改;如果他们还修改了 fileA
,Git 也会添加该更改。 Git 然后从 merge base 提交中提取所有文件,并将 combined 更改应用于这些文件。
如果合并更改时出现问题,Git 将在此过程中停止。请注意,如果我们只更改 fileA
而他们只更改 fileB
,那么将这些更改组合起来永远不会有任何问题。当我们和他们接触一个文件中的相同行时,或者如果我们修改fileA
,我们运行就会合并问题和他们例如,完全删除 fileA
。
如果 Git 能够独自完成所有 change-combining,Git 将合并的更改应用于合并基础——这会保留我们更改的内容并添加他们更改的内容— 并创建一个新的 merge commit,这在一个方面是特殊的(我稍后会谈到这一点)。如果没有,Git 会在这个过程中停止,这就变成了你的工作——“你”是 运行 git merge
的人——通过提供 正确的合并结果。无论您在这里提供什么,Git 都会 相信 它是正确的,因此最好做到这一点。然后,您告诉 Git 您的合并解决方案,然后 Git 继续进行相同的合并提交,如果它能够自己进行合并的话,它会进行相同的提交。
因此,您的问题的答案是临时的“是”:
假设组 X 和 Y 以相同的提交开始,因此它们都有相同的存档文件。在此存档中,存在名为 fileA
、fileB
和 file3
的文件。
Group X 创建了一个新的 b运行ch,修改了 fileA
,并且——使用他们的 b运行ch(见下文)——制作了一个新的存档其中除 fileA
之外的所有文件都相同。
分别(在他们的拥有新的b运行ch),Y组修改fileB
并制作一个新的存档,其中除了 fileB
之外的所有文件都是相同的。
你现在通过选择他们的一个提交作为新的“最佳组合结果”来推动 main-line b运行ch,然后你 运行 git merge
在 other commit / b运行ch 上获得新的“最佳组合结果”。由于这三个档案中的 file3
与 相同 ,因此无需对 file3
进行任何更改。 Git 从合并基础存档复制 file3
以放入新存档。
这是 临时 是的原因是 每个小组都必须注意保留而不是修改他们不应该更改的任何文件。如果他们弄乱了其中一个文件,您的合并将不会那么顺利。但是请注意,每个存档都是 read-only 并且永远存在 所以很容易注意到——嘿!——他们更改了 不应该拥有的文件! 并让他们将这些文件放回 new 存档中,然后添加到他们的 b运行ch 中。请参阅下面的示例。
一个例子
让我们从一个完全空的 Git 存储库开始,它有 no 提交和 no b运行 全部:
mkdir repo && cd repo && git init
[Git prints messages about initializing a new repository]
现在您创建初始的 working-tree 文件集:可能是空的 A
、空的 B
和三个编号的文件。然后你 git add
所有这些:这告诉 Git 将它们复制到 Git 的 index 又名 暂存区 ,Git 从中进行新的提交。请注意,这些文件中的 none 将被“忽略”(同样,稍后会详细介绍)因为我们希望 every文件在每个提交中。
您现在在您的存储库中有一个提交。这一次提交是初始提交。一个正常的新提交会 link 回到上一个提交,但由于没有上一个提交,这个确实不能。它有一个丑陋的大哈希 ID:一个唯一的数字,在整个宇宙中的每个 Git 存储库中都是唯一的。1 而不是猜测它的数字,甚至缩写它像 a123456
之类的,让我们把这个提交称为 A
:
A
现在假设您忘记了一些东西,或者发现了一些您没有想到的东西,需要输入 file3
。没问题!您在工作树中调整 file3
的副本——这是一个普通的日常文件; archives 在 commits 中,你不直接处理它们——然后是 运行 git add file3
。 git add
将更新的 file3 复制到 Git 的暂存区/索引中,准备提交。你现在又运行git commit
了。
这会进行第二次提交——第二次每个文件的完整存档。因为 Git 很聪明,所以这个存档实际上与第一个存档共享存储空间。即使 file2
有 100 兆字节大,您的存储库也只会增长几个字节来保存对 file3
的更新。档案彼此独立,但又共享;这就是 Git 实现的神奇之处(它知道如何做到这一点 因为 每个存档都是完整的 read-only,因此很容易共享)。
这个新的提交,我们称之为 B
而不是试图猜测其唯一的哈希 ID,它向后指向旧的提交:
A <-B
Git 知道 B
是 最新的 主 b运行ch 提交——我们称之为 main
;你可以给它取任何你喜欢的名字; Git 仍然默认为 master
尽管 GitHub 现在默认为 main
——是 name main
指向提交B
,像这样:
A <-B <--main
如果您现在发现某个文件中有拼写错误,您可以通过再次提交来解决这个问题。我们将这个新提交称为 C
。提交 C
将指向现有提交 B
,并且 Git 会将 C
的哈希 ID 存储到 b运行ch 名称中:
A--B--C <-- main
(注意:我在这里故意偷懒,不再将提交之间的箭头绘制为箭头,但它们仍然是 one-way 箭头。Git 工作 向后,从最近的提交(通过使用 b运行ch 名称找到)到较旧的提交。)
假设我们现在已准备好让您的测试人员进行测试。我将继续称它们为 X 组和 Y 组(以使单个字母与我的单个提交字母“远离”)。我们现在 两个新的 b运行ch names,都指向提交 C
,所以有 三个 提交的名称C
现在:
A--B--C <-- main, groupX, groupY
1这个唯一性要求就是为什么哈希ID这么大这么丑。从技术上讲,ID 只需要在每个其他 Git 存储库中是唯一的,您的 Git 存储库将永远 与 交谈,但最好确保此 ID 是 完全独一无二。
测试人员开始测试
您的测试人员现在使用 git clone
复制充满提交的 整个存储库 。当他们这样做时,他们得到了 b运行ches 的 所有提交 和 none。相反,他们的克隆有我们所说的 remote-tracking 名称。让我们从X组的角度来看一下。
A--B--C <-- origin/main, origin/groupX, origin/groupY
因为他们想要添加 new 提交,所以他们立即需要一个自己的 b运行ch 名称。如果他们足够聪明,他们会在 git clone
时间告诉他们的 Git:请根据我的新 remote-tracking 名字为我取名 groupX
origin/groupX
。 (他们使用 git clone
的 -b
选项来做到这一点。)这给了他们:
A--B--C <-- groupX (HEAD), origin/main, origin/groupX, origin/groupY
如果他们不那么聪明,他们的 Git 可能会为他们创建错误的名称。假设他们从 GitHub:
获得默认值 main
A--B--C <-- main (HEAD), origin/main, origin/groupX, origin/groupY
注意这里添加的 HEAD
,在 parentheses 中:这表示它们的 当前 b运行ch 名称 。你也有一个,当你唯一的 b运行ch 名字是 main
时,我们就懒得画了。由于 main
是错误的名称,他们现在需要 Git 创建 他们的 groupX
名称,使用 git checkout groupX
或92=]。这使用他们的 origin/groupX
来创建他们的 groupX
。他们的 groupX
将指向与他们的 origin/groupX
相同的提交,就像这样:
A--B--C <-- groupX (HEAD), main, origin/main, origin/groupX, origin/groupY
请注意他们现在有两个 b运行ch 名称:main
和 groupX
。特殊名称 HEAD
附加到 名称 groupX
。 remote-tracking 名称都指向现有提交 C
。两个 b运行ch 名称也指向现有提交 C
。因此,提交 C
是 最新 提交 both of their b运行ches,在这一点上。
所有提交都是 read-only. 它们实际上不能更改现有的三个提交中的任何一个。 他们所能做的就是添加新的提交——但这是他们应该做的。他们现在在他们的工作树中有从提交 C
.
中的存档中提取的文件
他们可以 运行 他们的测试,并更新 fileA
或 localA
或任何你给这个文件起的名字。此更新发生在他们的 工作树 中,其中包含 所有 从提交 C
中提取的文件。然后他们 运行 git add
在他们的一个更新文件上。 (他们可以 运行 在所有文件上使用 git add -A
或 git add .
:Git 会注意到其他文件未更改,实际上不会 更改 这里有任何东西。)这准备 他们的 index/staging-area 通过更新 fileA
或 localA
的暂存副本来进行新的提交或者不管你怎么称呼它。 (其他暂存副本保持不变,即使 git add
写入它们,因为它们没有 更改 这些文件。)
他们现在 运行 git commit
。这使得 每个文件 的新存档作为新的提交,具有新的唯一哈希 ID。我们称它为D
,并将其画在:
D <-- groupX (HEAD)
/
A--B--C <-- main, origin/main, origin/groupX, origin/groupY
请注意 他们的 名称 groupX
是如何更新为指向 他们的 新提交 D
。他们的新提交指向现有提交 C
.
同时,Y组也开始测试
Y 组执行相同的一系列操作,除了 name 他们将用来跟踪 他们的 提交( s) 是 groupY
:
A--B--C <-- groupY (HEAD), origin/main, origin/groupX, origin/groupY
(我这次省略了 main
,假设他们记得 git clone
的 -b
选项。)最终他们以
A--B--C <-- origin/main, origin/groupX, origin/groupY
\
E <-- groupY (HEAD)
合并工作
请注意,此时有 三个存储库,每个存储库都有一些具有相同哈希 ID 的 共享 提交,并且每个都有一些独特的提交,具有 unique-to-it 哈希 ID。现在是 X 组和 Y 组使用 git push
.
的时候了
这里有很多选项,但假设您让它们 git push
直接对一些共享的、可写的存储库进行操作。 X 组将发送他们的新提交 D
并要求共享可写存储库设置 its b运行ch name groupX
指向提交 D
。 Y 组将对他们的提交 E
执行相同的操作,但要求共享可写存储库设置 its b运行ch 名称 groupY
。并且,让我们暂时假设您可以直接访问这个共享的可写存储库,以便您现在可以登录并查看它。它现在有这个:
D <-- groupX
/
A--B--C <-- main (HEAD)
\
E <-- groupY
现在你的工作是合并工作。你:
移动你的 HEAD
/ main
指向 D
或 E
(哪个并不重要),通过使用 git merge
,执行 so-called 快进 合并:
D <-- main (HEAD), groupX
/
A--B--C
\
E <-- groupY
运行 git merge groupY
执行合并;和
Git 自动合并,因为每个人都正确地完成了他们的工作,所以你得到:
D <-- groupX
/ \
A--B--C F <-- main (HEAD)
\ /
E <-- groupY
注意没有提交是如何改变的:我们添加了新的提交来存储新的档案。 b运行ch names 随着我们添加新的提交而向前推进。 Git 继续向后工作,从像 groupX
这样的 b运行ch 名称开始并向后工作。
这里比较特别的是,从名字main
,我们发现commit F
是一个merge commit,有两种方式倒退。这两种方式分别导致提交 D
和 E
。这意味着如果您的测试人员需要更新他们的文件,您所做的 next 合并将有不同的 merge base 提交。
现在由您的测试人员决定他们是否 (a) 选择新的提交 F
和 (b) 更新 their b运行ch合并提交的名称 F
。这不是 必需的 只要他们做的一切都是正确的,但如果他们破坏了某些东西,它会让你的事情变得更容易。
当事情出错时
这种模式会一遍又一遍地重复:有人会更新 他们的 b运行ch,然后发送 他们的向您提交新的提交,您将选择是否合并这些提交。如果他们破坏了某些东西,您可以简单地拒绝合并该提交。例如,假设两个组都更新为 F
,然后组 X 进行了错误提交 G
:
D G <-- groupX
/ \ /
A--B--C F <-- main (HEAD), groupY
\ /
E
你可以不带 G
。您可以要求他们下一次提交 H
parent F
,放弃 他们的 G
,或者他们修复他们的 G
的提交 H
已更正 archive/snapshot。两种方法都可以。这是 abandoned-commit 的样子:
G [abandoned]
/
D /__--H <-- groupX
/ \ /
A--B--C F <-- main (HEAD), groupY
\ /
E
因为不再有 b运行ch name 找到提交 G
,它从视图中消失了。最终,它完全脱离了存储库(un-find-able 提交在一段时间后有资格进行“垃圾收集”)。
好的,那么,.gitignore
/ .git/info/exclude
到底是什么?
阅读完所有内容后,您应该会问自己上述问题。答案是 工作树 往往会充满 永远不应存档 的“垃圾”或临时文件或输出文件。这有两个烦人的副作用:
git status
告诉我们工作树中的内容 可以 放入下一个存档,但目前没有安排。这些是 Git 所谓的 未跟踪文件 。 git status
列出一千个未跟踪的文件,但 不应 添加这些文件,这很烦人。
git add .
或其他en-masse添加操作方便,但会添加未跟踪的文件。
如果我们可以告诉 Git 会怎么样:如果这个文件未被跟踪,(1) 不要抱怨它,(2) 不要 auto-add 它使用 en-masse add-all-changes 操作?这就是 .gitignore
的用途:抑制投诉并避免自动添加这些 never-to-be-archived 文件。
如果一个文件在 Git 的索引中,也就是暂存区——如果它来自某个存档的 out,它将在 out 中列出该文件 .gitignore
或 exclude
文件无效。现在 已跟踪 。只有不在索引/staging-area和在你的工作树中的文件,untracked ,只有这些文件可以被“忽略d"这样。忽略是错误的术语,但正确的术语太长而不能用作文件名。
remote
______|______
| |
localA localB
-file1 -file3
-file2 -file4
-file3 -file5
我想让我的远程仓库存储文件 1-5。我希望 localA 回购只有 push/pull 个文件 1-3。我希望 localB 回购只有 push/pull 个文件 3-5。所以目标是让一些文件在本地仓库和一些不同步的文件之间同步,但远程仓库应该存储所有这些文件。我的 .gitignore 文件可以很好地提交,但最终会拉下所有我不想要的文件。我也尝试过使用 .git/info/exclude 文件,但也没有成功。如果需要,我可以从头开始,但我更愿意配置现有设置。
编辑上下文: 这是一个奇怪的测试环境,用户在其中使用个人帐户在一个区域进行更改,但必须在另一个区域的共享帐户上进行测试。我希望在他们的个人帐户下跟踪他们的更改,而不是共享帐户。我希望在共享帐户下跟踪测试结果,而不是他们的个人帐户。
你可能会得到你想要的,但首先你必须改变你想要的。
Git 不存储 文件; Git 存储 提交 。一次提交就像一个文件存档——事实上,每个提交都包含 每个 文件的完整存档——还有一些额外的 add-on 特性。推送和获取操作(pull
表示首先 运行 fetch
,然后 运行 第二个 Git 命令 并且它是fetch 步骤,而不是第二个命令,与此处的 push 相当)通过 t运行sferring 整个 commits 工作。因此,假设您的每个用户都在为他们拥有的 每个 文件创建一个 read-only 存档,并且每次都发送这些文件——因为 是 他们在做什么。
名称.gitignore
(或.git/info/exclude
,其工作方式相同)非常具有误导性:它不会导致文件被忽略。 read-only 存档充满了它包含的任何文件,并且 none 这些文件可以 忽略 因为它们已经在 read-only 存档中。当您提取该存档(使用 git checkout
)时,您将获得它的所有文件。这些文件现在位于您的工作区(您可以在其中查看和使用它们,甚至可以更新它们)和 Git 的存储区(Git 称为 暂存区) 或索引,准备好进行 下一次 提交(一个新的 read-only 存档)。除非你明确地删除其中一些文件,否则它们都将在下一个提交中。
B运行ches,在 Git 中,意思也比您想象的要少。 Git 与 b运行ches 无关,而是 commits。 B运行ch 名称只是查找 某些特定提交的一种方式。我们需要这个,因为单个提交的实际名称非常难看 random-looking 哈希 ID,用 hexadecimal 表示,没有人能记住或正确输入。 b运行ch 名称只是我们让计算机保存 latest 提交的哈希 ID 的方式。最新的提交保存了其前一个提交的哈希 ID,因此从最近的提交,Git 可以回顾一跳。前一次提交依次包含 其 前一次提交的哈希 ID,以便 Git 可以再回头一跳,依此类推。
Ok so let's say I have 2 branches, one for localA and one for localB.
同样,b运行ches 不适用于 files。 B运行ches 用于查找提交。每个提交都包含 每个 文件的完整快照(存档)——或者更准确地说,它包含的每个文件。你可以有一个提交——一个档案——只包含一个文件。但这不会很有帮助,至少对于普通的日常 Git 工具来说是这样。 (您可以编写自己的工具来处理这些问题,但这需要大量工作。)
Am I able to merge both branches in the remote repo without overwriting file3?
git merge
操作是关于合并工作。考虑像这样的正常日常合并:
我们采用三个(而不是两个)提交,即每个文件的三个 read-only 存档。
这三个档案之一是 merge base: 提交是 shared 因此 两个b运行ches.
剩下的两个提交是两个 b运行ches 中每一个的最新提交。其中之一是“我们的”b运行ch——我们在开始之前用
git checkout
或git switch
选出的那个。另一个是“他们的”b运行ch,我们将其命名为git merge
命令。我们现在Git将合并库中的所有存储文件与所有进行比较将文件存储在我们的 b运行ch 提示提交中。无论 changes Git 在这里观察到什么——添加新文件、删除旧文件、更改现有文件——都是我们的 changes.
接下来,我们Git将合并库中的所有存储文件与所有进行比较他们 b运行ch 提示提交中存储的文件。 改变 Git 这里观察到的是他们的改变。
最后,我们 Git 将这两组更改结合起来:如果我们添加了一个 totally-new 文件,Git 将获取我们的文件;如果他们添加了一个,Git 会拿走他们的文件;如果我们修改了
fileA
,Git 会保留对该文件的更改;如果他们还修改了fileA
,Git 也会添加该更改。 Git 然后从 merge base 提交中提取所有文件,并将 combined 更改应用于这些文件。如果合并更改时出现问题,Git 将在此过程中停止。请注意,如果我们只更改
fileA
而他们只更改fileB
,那么将这些更改组合起来永远不会有任何问题。当我们和他们接触一个文件中的相同行时,或者如果我们修改fileA
,我们运行就会合并问题和他们例如,完全删除fileA
。如果 Git 能够独自完成所有 change-combining,Git 将合并的更改应用于合并基础——这会保留我们更改的内容并添加他们更改的内容— 并创建一个新的 merge commit,这在一个方面是特殊的(我稍后会谈到这一点)。如果没有,Git 会在这个过程中停止,这就变成了你的工作——“你”是 运行
git merge
的人——通过提供 正确的合并结果。无论您在这里提供什么,Git 都会 相信 它是正确的,因此最好做到这一点。然后,您告诉 Git 您的合并解决方案,然后 Git 继续进行相同的合并提交,如果它能够自己进行合并的话,它会进行相同的提交。
因此,您的问题的答案是临时的“是”:
假设组 X 和 Y 以相同的提交开始,因此它们都有相同的存档文件。在此存档中,存在名为
fileA
、fileB
和file3
的文件。Group X 创建了一个新的 b运行ch,修改了
fileA
,并且——使用他们的 b运行ch(见下文)——制作了一个新的存档其中除fileA
之外的所有文件都相同。分别(在他们的拥有新的b运行ch),Y组修改
fileB
并制作一个新的存档,其中除了fileB
之外的所有文件都是相同的。你现在通过选择他们的一个提交作为新的“最佳组合结果”来推动 main-line b运行ch,然后你 运行
git merge
在 other commit / b运行ch 上获得新的“最佳组合结果”。由于这三个档案中的file3
与 相同 ,因此无需对file3
进行任何更改。 Git 从合并基础存档复制file3
以放入新存档。
这是 临时 是的原因是 每个小组都必须注意保留而不是修改他们不应该更改的任何文件。如果他们弄乱了其中一个文件,您的合并将不会那么顺利。但是请注意,每个存档都是 read-only 并且永远存在 所以很容易注意到——嘿!——他们更改了 不应该拥有的文件! 并让他们将这些文件放回 new 存档中,然后添加到他们的 b运行ch 中。请参阅下面的示例。
一个例子
让我们从一个完全空的 Git 存储库开始,它有 no 提交和 no b运行 全部:
mkdir repo && cd repo && git init
[Git prints messages about initializing a new repository]
现在您创建初始的 working-tree 文件集:可能是空的 A
、空的 B
和三个编号的文件。然后你 git add
所有这些:这告诉 Git 将它们复制到 Git 的 index 又名 暂存区 ,Git 从中进行新的提交。请注意,这些文件中的 none 将被“忽略”(同样,稍后会详细介绍)因为我们希望 every文件在每个提交中。
您现在在您的存储库中有一个提交。这一次提交是初始提交。一个正常的新提交会 link 回到上一个提交,但由于没有上一个提交,这个确实不能。它有一个丑陋的大哈希 ID:一个唯一的数字,在整个宇宙中的每个 Git 存储库中都是唯一的。1 而不是猜测它的数字,甚至缩写它像 a123456
之类的,让我们把这个提交称为 A
:
A
现在假设您忘记了一些东西,或者发现了一些您没有想到的东西,需要输入 file3
。没问题!您在工作树中调整 file3
的副本——这是一个普通的日常文件; archives 在 commits 中,你不直接处理它们——然后是 运行 git add file3
。 git add
将更新的 file3 复制到 Git 的暂存区/索引中,准备提交。你现在又运行git commit
了。
这会进行第二次提交——第二次每个文件的完整存档。因为 Git 很聪明,所以这个存档实际上与第一个存档共享存储空间。即使 file2
有 100 兆字节大,您的存储库也只会增长几个字节来保存对 file3
的更新。档案彼此独立,但又共享;这就是 Git 实现的神奇之处(它知道如何做到这一点 因为 每个存档都是完整的 read-only,因此很容易共享)。
这个新的提交,我们称之为 B
而不是试图猜测其唯一的哈希 ID,它向后指向旧的提交:
A <-B
Git 知道 B
是 最新的 主 b运行ch 提交——我们称之为 main
;你可以给它取任何你喜欢的名字; Git 仍然默认为 master
尽管 GitHub 现在默认为 main
——是 name main
指向提交B
,像这样:
A <-B <--main
如果您现在发现某个文件中有拼写错误,您可以通过再次提交来解决这个问题。我们将这个新提交称为 C
。提交 C
将指向现有提交 B
,并且 Git 会将 C
的哈希 ID 存储到 b运行ch 名称中:
A--B--C <-- main
(注意:我在这里故意偷懒,不再将提交之间的箭头绘制为箭头,但它们仍然是 one-way 箭头。Git 工作 向后,从最近的提交(通过使用 b运行ch 名称找到)到较旧的提交。)
假设我们现在已准备好让您的测试人员进行测试。我将继续称它们为 X 组和 Y 组(以使单个字母与我的单个提交字母“远离”)。我们现在 两个新的 b运行ch names,都指向提交 C
,所以有 三个 提交的名称C
现在:
A--B--C <-- main, groupX, groupY
1这个唯一性要求就是为什么哈希ID这么大这么丑。从技术上讲,ID 只需要在每个其他 Git 存储库中是唯一的,您的 Git 存储库将永远 与 交谈,但最好确保此 ID 是 完全独一无二。
测试人员开始测试
您的测试人员现在使用 git clone
复制充满提交的 整个存储库 。当他们这样做时,他们得到了 b运行ches 的 所有提交 和 none。相反,他们的克隆有我们所说的 remote-tracking 名称。让我们从X组的角度来看一下。
A--B--C <-- origin/main, origin/groupX, origin/groupY
因为他们想要添加 new 提交,所以他们立即需要一个自己的 b运行ch 名称。如果他们足够聪明,他们会在 git clone
时间告诉他们的 Git:请根据我的新 remote-tracking 名字为我取名 groupX
origin/groupX
。 (他们使用 git clone
的 -b
选项来做到这一点。)这给了他们:
A--B--C <-- groupX (HEAD), origin/main, origin/groupX, origin/groupY
如果他们不那么聪明,他们的 Git 可能会为他们创建错误的名称。假设他们从 GitHub:
获得默认值main
A--B--C <-- main (HEAD), origin/main, origin/groupX, origin/groupY
注意这里添加的 HEAD
,在 parentheses 中:这表示它们的 当前 b运行ch 名称 。你也有一个,当你唯一的 b运行ch 名字是 main
时,我们就懒得画了。由于 main
是错误的名称,他们现在需要 Git 创建 他们的 groupX
名称,使用 git checkout groupX
或92=]。这使用他们的 origin/groupX
来创建他们的 groupX
。他们的 groupX
将指向与他们的 origin/groupX
相同的提交,就像这样:
A--B--C <-- groupX (HEAD), main, origin/main, origin/groupX, origin/groupY
请注意他们现在有两个 b运行ch 名称:main
和 groupX
。特殊名称 HEAD
附加到 名称 groupX
。 remote-tracking 名称都指向现有提交 C
。两个 b运行ch 名称也指向现有提交 C
。因此,提交 C
是 最新 提交 both of their b运行ches,在这一点上。
所有提交都是 read-only. 它们实际上不能更改现有的三个提交中的任何一个。 他们所能做的就是添加新的提交——但这是他们应该做的。他们现在在他们的工作树中有从提交 C
.
他们可以 运行 他们的测试,并更新 fileA
或 localA
或任何你给这个文件起的名字。此更新发生在他们的 工作树 中,其中包含 所有 从提交 C
中提取的文件。然后他们 运行 git add
在他们的一个更新文件上。 (他们可以 运行 在所有文件上使用 git add -A
或 git add .
:Git 会注意到其他文件未更改,实际上不会 更改 这里有任何东西。)这准备 他们的 index/staging-area 通过更新 fileA
或 localA
的暂存副本来进行新的提交或者不管你怎么称呼它。 (其他暂存副本保持不变,即使 git add
写入它们,因为它们没有 更改 这些文件。)
他们现在 运行 git commit
。这使得 每个文件 的新存档作为新的提交,具有新的唯一哈希 ID。我们称它为D
,并将其画在:
D <-- groupX (HEAD)
/
A--B--C <-- main, origin/main, origin/groupX, origin/groupY
请注意 他们的 名称 groupX
是如何更新为指向 他们的 新提交 D
。他们的新提交指向现有提交 C
.
同时,Y组也开始测试
Y 组执行相同的一系列操作,除了 name 他们将用来跟踪 他们的 提交( s) 是 groupY
:
A--B--C <-- groupY (HEAD), origin/main, origin/groupX, origin/groupY
(我这次省略了 main
,假设他们记得 git clone
的 -b
选项。)最终他们以
A--B--C <-- origin/main, origin/groupX, origin/groupY
\
E <-- groupY (HEAD)
合并工作
请注意,此时有 三个存储库,每个存储库都有一些具有相同哈希 ID 的 共享 提交,并且每个都有一些独特的提交,具有 unique-to-it 哈希 ID。现在是 X 组和 Y 组使用 git push
.
这里有很多选项,但假设您让它们 git push
直接对一些共享的、可写的存储库进行操作。 X 组将发送他们的新提交 D
并要求共享可写存储库设置 its b运行ch name groupX
指向提交 D
。 Y 组将对他们的提交 E
执行相同的操作,但要求共享可写存储库设置 its b运行ch 名称 groupY
。并且,让我们暂时假设您可以直接访问这个共享的可写存储库,以便您现在可以登录并查看它。它现在有这个:
D <-- groupX
/
A--B--C <-- main (HEAD)
\
E <-- groupY
现在你的工作是合并工作。你:
移动你的
HEAD
/main
指向D
或E
(哪个并不重要),通过使用git merge
,执行 so-called 快进 合并:D <-- main (HEAD), groupX / A--B--C \ E <-- groupY
运行
git merge groupY
执行合并;和Git 自动合并,因为每个人都正确地完成了他们的工作,所以你得到:
D <-- groupX / \ A--B--C F <-- main (HEAD) \ / E <-- groupY
注意没有提交是如何改变的:我们添加了新的提交来存储新的档案。 b运行ch names 随着我们添加新的提交而向前推进。 Git 继续向后工作,从像 groupX
这样的 b运行ch 名称开始并向后工作。
这里比较特别的是,从名字main
,我们发现commit F
是一个merge commit,有两种方式倒退。这两种方式分别导致提交 D
和 E
。这意味着如果您的测试人员需要更新他们的文件,您所做的 next 合并将有不同的 merge base 提交。
现在由您的测试人员决定他们是否 (a) 选择新的提交 F
和 (b) 更新 their b运行ch合并提交的名称 F
。这不是 必需的 只要他们做的一切都是正确的,但如果他们破坏了某些东西,它会让你的事情变得更容易。
当事情出错时
这种模式会一遍又一遍地重复:有人会更新 他们的 b运行ch,然后发送 他们的向您提交新的提交,您将选择是否合并这些提交。如果他们破坏了某些东西,您可以简单地拒绝合并该提交。例如,假设两个组都更新为 F
,然后组 X 进行了错误提交 G
:
D G <-- groupX
/ \ /
A--B--C F <-- main (HEAD), groupY
\ /
E
你可以不带 G
。您可以要求他们下一次提交 H
parent F
,放弃 他们的 G
,或者他们修复他们的 G
的提交 H
已更正 archive/snapshot。两种方法都可以。这是 abandoned-commit 的样子:
G [abandoned]
/
D /__--H <-- groupX
/ \ /
A--B--C F <-- main (HEAD), groupY
\ /
E
因为不再有 b运行ch name 找到提交 G
,它从视图中消失了。最终,它完全脱离了存储库(un-find-able 提交在一段时间后有资格进行“垃圾收集”)。
好的,那么,.gitignore
/ .git/info/exclude
到底是什么?
阅读完所有内容后,您应该会问自己上述问题。答案是 工作树 往往会充满 永远不应存档 的“垃圾”或临时文件或输出文件。这有两个烦人的副作用:
git status
告诉我们工作树中的内容 可以 放入下一个存档,但目前没有安排。这些是 Git 所谓的 未跟踪文件 。git status
列出一千个未跟踪的文件,但 不应 添加这些文件,这很烦人。git add .
或其他en-masse添加操作方便,但会添加未跟踪的文件。
如果我们可以告诉 Git 会怎么样:如果这个文件未被跟踪,(1) 不要抱怨它,(2) 不要 auto-add 它使用 en-masse add-all-changes 操作?这就是 .gitignore
的用途:抑制投诉并避免自动添加这些 never-to-be-archived 文件。
如果一个文件在 Git 的索引中,也就是暂存区——如果它来自某个存档的 out,它将在 out 中列出该文件 .gitignore
或 exclude
文件无效。现在 已跟踪 。只有不在索引/staging-area和在你的工作树中的文件,untracked ,只有这些文件可以被“忽略d"这样。忽略是错误的术语,但正确的术语太长而不能用作文件名。