Git这是它应该如何工作的吗?

时间:2017-01-08 21:23:31

标签: git version-control

我有两个本地分支,大多数文件相同。不同之处在于主分支不跟踪另一个分支跟踪的2个文件。

在我创建另一个分支之前,我有两个被master分支忽略的文件。 文件A和文件B.

我创建了另一个分支并检查了它。我在另一个分支上更改了gitignore以跟踪文件A和文件B.

然后我继续在主分支上工作。过了一会儿,我检查了另一个分支,并从master中提取/合并了最新的提交。当我在执行此操作后切换到主分支时,文件A和文件B不再存在。他们仍然在另一个分支中,因此我猜测其他分支每次提交时都会将文件保留给自己。

这是预期的行为吗?

1 个答案:

答案 0 :(得分:2)

出于各种原因,人们发现"跟踪/未跟踪文件"和分支的想法,非常神秘。但事实上,他们并非如此。

放弃的第一个概念是分支。他们真的不是什么意思!嗯,也就是说,它们意味着的意思。他们有一些非常具体的定义,实际上,这个词是" branch"在Git中有两个不同的含义。有关这方面的更多信息,请参阅What exactly do we mean by "branch"?但是,现在,考虑一下Git在从提交到提交的过程中所做的事情 - 因为这是问题的来源。

提交,以及它们如何形成分支

在Git中,提交几乎就是一切。这是最重要的目标;它是存储库中的粘合剂,以及Git存在的原因。总是 1 当前提交,称为HEAD。但是,究竟是提交了什么?答案是它由两部分或三部分组成,具体取决于你的计算方式:

  • 提交存储工作树的快照

    工作树或工作树(或此拼写的某些变体)是您查看文件,编辑文件以及以其他方式使用文件的地方。它们存储在存储库中的形式对此没有好处,因此Git为您提供了一个可以工作的工作树。

    提交中的快照允许您访问(如git checkout任何您提交的早期版本。也就是说,如果您昨天提交了两次提交,而周五提交了三次提交,则可以查看整个工作树,因为它是昨天的方式,或者是星期五的三种方式。为此,您只需git checkout提交,通过其丑陋的SHA-1哈希ID,c0ffeeface或其他任何内容来命名。 (无论何时运行git log,您都会看到这些ID。)

  • 此外,提交会存储一些元数据。特别是,每次提交都包含提交者的姓名和电子邮件地址以及时间戳。 (事实上​​,有两个名称/电子邮件/时间戳三元组,一个用于"作者"一个用于"提交者",因为Git的历史通过电子邮件发送的补丁:这允许某人通过电子邮件发送补丁并成为作者,而其他人实际上是在提交。)

  • 使用相同的元数据 - 虽然您可能想要单独考虑它--Git保留 ID。每次提交的父级是在您进行 new 提交之前就已提交的提交。然后,Git可以使用这些父链接来浏览仅提交的历史记录,它是向后,完全来自"更近期"到了#34;年龄大了#34; (它是 - 并且必须向后的原因是每个内部Git对象都是只读:一旦它进入,它永远不会改变。它会使人们更愿意记住他们的孩子,而不是让他们记住他们的父母;但是这样做是为了只读,孩子们必须先出生,或同时出生作为父母。所以Git让孩子们记录他们的父母,而不是相反,因为孩子们不可避免地会在以后出生。)

    通过使用这些父链接,Git不仅可以在历史记录中向后工作,还可以向您显示更改的内容。如果父提交有一个工作树,其中README文件表明苹果是紫色的,并且子提交有一个工作树,其中README文件表示苹果为绿色,Git可以比较这两个提交并说:"从父母到孩子,你把苹果从紫色改为绿色。"

事实上,这就是分支 - 概念本身和master等名称 - 来自于分支。有时,你想做一个分支"以便更改与较旧的或至少不同的父级相关:

A--B--C--E--G   <-- master
       \
        D--F    <-- branch

此处的名称master指的是提交G,即第7次提交。但是,提交G的父级不是F,而是E; E的父级为C,其父级为B,其父级为A(然后我们点击了所谓的 root提交没有父母:显然,第一次提交必须是其中之一)。同时,名称branch指的是提交F,其父级为D,其父级为C。因此,提交C实际上有两个子项,DE

这里的关键是名称masterbranch,对Git来说真的没什么意义。他们只是找到丑陋的SHA-1哈希的方法。 Git记得master表示beadc0debranch表示feedbeef,所以如果你说'#34;我想要master工作现在&#34; Git知道要提交beadc0de。然后,当您进行 new 提交时,Git会自动更新当前分支,以便其中包含新提交的ID,并将旧ID存储为新提交的父级(这就是分支增长的方式。)

所以(如What exactly do we mean by "branch"?中所述),当人们说出 branch 这个词时,它们可能意味着分支名称 - 单词master例如,它只是找到分支的提示提交。或者,它们可以表示&#34;通过从分支提示开始并在历史记录中向后工作可以找到的部分或全部提交,以便master表示所有提交回{{1}除了AD之外,F表示除了branchA之外的所有提交都返回E。请注意,在这种情况下,提交G实际上是在两个分支上。

1 &#34;总是&#34;在一个新的,空的,空的存储库中:没有提交,所以没有提交成为当前的A-B-C提交。 Git用一些特殊情况处理这个问题,我们可以在这里忽略它。

索引,以及跟踪&#34;

的含义

我们发现Git快照与工作树的第一个问题是,由于各种原因,我们需要将额外的文件放入实际的工作树中。特别是,如果我们编译代码,或者有临时文件或本地配置,或者出于任何其他正当理由,我们需要提交提交的文件,但是生活在工作树无论如何。因此,所有版本控制系统都提供了一些方法来实现非版本化的#34;文件也是如此。然而,Git的方法很不寻常,甚至可能是独一无二的。 Git做的是揭露大多数版本控制系统隐藏的东西。

在Git中,您可以在各种称为索引暂存区域的内容中构建 next 提交,或者有时(如{ {1}})缓存。这些都是同一个词。索引的简短版本就是它只是&#34;你在那里构建 next 提交&#34;。

要进行提交,首先要使用工作树,该工作树包含版本化(跟踪)文件和其他(未跟踪)文件。您以某种方式编辑某些文件,然后运行HEADgit diff --cached所做的只是文件复制到索引中。然后,一旦你按照自己喜欢的方式进行了所有操作,就运行git add,此时Git会从索引中进行新的提交。但是:之后指数会发生什么变化?

答案非常简单:没有。索引继续保持您刚刚提交的提交!

因此,这对于跟踪的文件意味着什么:它在索引中。

那就是它 - 它就是它的全部。当且仅当文件位于索引中时,跟踪文件。如果它被跟踪,它将在下一次提交中。如果未跟踪,则不会在下一次提交中。

git add怎么样?

名称git commit具有误导性:它不是要忽略的文件。拥有未跟踪文件的缺点是Git经常抱怨它们。 (Git:&#34; 抱怨!文件foo未被跟踪!你确定你想要吗?抱怨,发牢骚&#34;)放一个文件名,或者匹配模式,进入.gitignore主要是关闭Git up about untracked-ness。它并没有实际使文件无法跟踪:当且仅当文件不在索引中时,文件才会被跟踪。当你说&#34;添加所有内容时,它会让Git自动跳过文件,但这通常是我们想要的。

将文件放入.gitignore有一个不好的副作用:它告诉Git,如果有必要,Git也可以随意销毁文件。这里也有一个有趣的方面,因为.gitignore文件本身通常被跟踪。现在是时候考虑.gitigore的工作原理了。

.gitignore如何真正起作用

我在上面提到Git主要关心从提交到提交。对于git checkout也是如此:Git将分支名称转换为原始提交哈希,以便获取与该提交一起使用的文件。但是,当您按名称签出分支时 - 我们通常会这样做 - Git也将该名称保存为当前分支,以便它知道哪个分支名称应该 next 提交。如果您通过其原始ID签出提交,您将获得Git所称的&#34;分离的HEAD&#34;。

所有这一切&#34;脱离了HEAD&#34;意味着Git有一个由其原始ID签出的提交。 (如果您进行 new 提交,这会产生影响,因此通常您希望通过签出名称而不是哈希ID来获得&#34;支持#34;同时,尽管如此,Git仍然存在从一个提交转移到另一个提交的问题,无论它是否要为下一个提交存储分支名称。

Git在这里做的是再次使用索引。同样,索引始终保持下一次提交 - 但是当你刚刚创建一个时,索引和工作树是&#34; clean&#34;并且git checkout表示&#34;无需提交&#34;,索引和工作树已经与当前(git checkout branchname)提交匹配。

让我们说你目前在git status HEAD上,而你说masterbeadc0de。索引(和工作树)匹配git checkout branch,因此Git会比较feedbeefbeadc0de以查看哪些文件不同。然后它在索引和工作树中替换这些文件。这包括文件beadc0de,如果它不同的话!

同时 - 这是您删除的文件的来源 - 如果feedbeef中的文件不在.gitignore中,或者反之亦然? Git在这里做的事情就像以前一样简单:删除我们未移动到的提交中的文件,创建该提交中的文件< / em>的。这涉及从工作树中删除文件,或将新文件写入工作树。

从工作树中删除现有文件会破坏它们。 Git通常会努力而不是来破坏文件,但是 - 哦 - 如果它们在beadc0de中列出,Git可以随意破坏它们!

因此,如果feedbeef(即.gitignore)的branch忽略了某些文件,feedbeef.gitignore)会跟踪这些文件, Git可以安全地删除文件。它们已存储在master中,因此您在切换回来时会将其取回,并且beadc0de会忽略它们,因此可以安全地删除它们。 (事实上​​,我认为存储在beadc0de就足够了,尽管规则有点松散地使用feedbeefbeadc0de之类的文件检出。)

顺便说一下,这个索引与工作树比较的事情也是(以及为什么,何时以及为什么 赢得 )Git允许您使用未提交的文件从一个分支切换到另一个分支。 Git非常努力地做尽可能少的工作,所以如果可以从一个提交切换到另一个提交而不触及索引和工作树中的文件,它就会这样做。