试图理解Git(通过VS 2017)

时间:2017-07-26 20:11:44

标签: git visual-studio tfs visual-studio-2017

我一直在阅读和玩Git但是我仍然无法理解如何正确地处理分支。

例如,我有一个LocalMaster,它可以同步回网络上的最新版本。

在拉完所有东西之后:

git checkout -b BugFix

这创建了一个BugFix分支,并将我设置为该分支到目前为止一切顺利。

现在我开始在这个分支中进行更改。

  1. 如果我不提交,为什么在切换回LocalMaster时会看到这些更改?

  2. 假设我在BugFix分支中进行更改,我只将它们提交到该分支。如何查看此分支中存在哪些更改?

  3. 假设我在第2步中进行了更改。现在出现了其他内容,我只想在LocalMaster分支中快速修复。我可以做到这一点并将其推回网络。如何将我的BugFix分支与同一更改同步?

  4. 我说我在第2步中做了一个我不想合并到LocalMaster的更改。我该如何退出这一变化呢?

1 个答案:

答案 0 :(得分:1)

许多GUI会隐藏您的许多细节,通常是为了让Git看起来更简单。我认为这是一个错误,因为底层的Git细节无论如何都会突然冒出来。我不知道VS2017在多大程度上试图隐藏东西 - 一般来说,我只是避免使用GUI,除了特殊情况。

无论如何,这里发生的事情是你正在处理的事实是,在Git中,每个文件都有三个(!)副本。一个是只读,永久提交的版本,它是当前提交的一部分。第二个是Git调用的那个,不同的是,索引临时区域缓存,具体取决于谁编写了文档何时。

每个文件的这两个版本都以内部的Git-ty压缩格式保存。 (压缩得非常好,以至于大部分时间都只有一个副本。)如果你的GUI很好,它会让你查看这两个,尽管它们的格式只有Git本身可以直接处理。

文件的第三个副本是工作树中的副本。这是普通文件系统中的普通文件,以普通程序以普通方式处理它的方式存储。这是该文件唯一真正占用空间的版本(除了一些Git不能很好地压缩的二进制文件)。所以这三份副本大多只是管理上的头痛。

显然,文件的工作树版本是您可以更改的。毕竟,它只是一个普通的文件。您可以编辑它,重写它,甚至删除它。您还可以创建新文件,这些文件尚未包含在索引中,也可能不在任何提交中。

每个文件的索引版本也是可写的。您使用git add编写它:它将当前工作树中的任何内容复制到索引中的同名文件中。如果索引中已有一个,则替换索引副本。如果索引中还没有一个,则将一个放入索引中。

当您运行git add时,除了使用git commit将内容复制到其中之外,处理索引的最重要方式就是这样。此时,Git 会查看您的索引,而不是您的工作树,以进行新的提交。无论索引中的文件是什么,这些文件都会进入新的提交。如果从索引中删除文件(使用git rm),则该文件不在新提交中。每个文件的内容都是您复制到索引中的内容:不多也不少。

一旦新提交安全地保存在存储库中,Git就会更改当前分支(如HEAD中所记录的那样),以便分支命名新提交。新提交的父级是之前提交 当前提交的任何提交。由于Git刚刚使用索引进行了新的提交,因此提交和索引现在匹配。

这为您的四个问题设定了场景:

  

如果我没有提交,为什么在切换回LocalMaster时我会看到这些更改?

工作树和/或索引中所做的更改只在工作树和/或索引中。

当你要求Git检查一些其他提交时,Git会将当前(HEAD)提交与其他提交进行比较。如果某些文件不同,Git必须替换索引和工作树版本。如果没有,Git可以不管它。

让我们来看一个简短的例子,你可以让Git(通过git checkout)从提交badbeef...转移到提交ac0ffee...。每个文件分为三个文件:READMEa.txtb.txt。两个提交中的README版本是相同的。 a.txtb.txt的版本不是。

要执行此操作,Git必须在索引和工作树中交换现有的a.txtb.txt。这样安全吗?好吧,如果您对a.txt没有做任何更改,那么 部分是安全的。如果您对b.txt进行了更改,则表示没有,并且您收到错误。

README在两次提交中都是相同的。 Git没有必要换掉它。因此,您在索引和/或工作树中对README所做的任何更改都可以保留。只要a.txtb.txt不受影响,Git就可以取代它们;由于README没有需要替换,Git可以保持不变。

这使您可以在这两次提交中进行未提交的README更改,但不会提及任何未提交的a.txtb.txt。除非它们与切换前提交中的内容匹配,否则这两个都不能安全地保存起来。

  

说我在BugFix分支中进行了更改,我只将它们提交到该分支。如何查看此分支中存在哪些更改?

进行提交时,这是一个完整的快照(索引中的任何内容)。提交本身会导致当前分支名称发生更改,以便名称解析为新提交。实际上并没有任何改变"在这一点上:它是一个快照。要找出"改变了什么"你必须选择一些其他快照并让Git对它们进行比较。

很容易让Git将任何提交与其直接父提交进行比较:git log -pgit show将显示什么,"更改&#34 34 ;.但是,要将这些显示为更改,Git必须每次从两个快照重新计算差异。

如果您希望查看自某些特定之前的提交以来的所有更改,则需要git diff。这需要两个快照并进行比较。它就像git log -p一样,只有git log你总是比较父母和孩子,而git diff你比较两个提交选择。

Git没有跟踪"分支何时开始"因此,要在分支上找到"更改,您必须定义自己的起点。

  

假设我在第2步中进行了更改。现在出现了其他内容,我只想在LocalMaster分支中快速修复。我可以做到这一点并将其推回网络。如何使用相同的更改同步我的BugFix分支?

唉,现在我们进入提交图。这实际上是一些GUI闪耀的地方(有些真的很糟糕......)。

我们在上面提到过,当您进行新提交时,会使当前分支名称前进到指向新提交。让我们绘制一个包含三个提交的存储库:

A <-B <-C   <--master

这些提交具有简单的一个大写字母名称,而不是大丑陋的哈希ID。提交C是最新的,因此名称master会记住其哈希ID。提交C会记住其父B的哈希ID,而B会记住第一次提交的哈希ID A

由于A是第一次提交,因此它根本没有父级。这是一个 root commit (这只是一种说法&#34;一个没有父级&#34的提交;)。

提交中的内部箭头一直是固定的,就像提交的其他内容一样。 (这有很好的技术原因:基本上,哈希ID本身就是提交内容的加密校验和,所以如果你改变任何东西,你会得到一个新的,不同的提交。)所以内部箭头不是很有趣 - 我们只需要记住他们总是倒退。所以:

A--B--C   <-- master

分支名称会随着时间的推移而变化。这个当前存储C的哈希ID。如果我们进行 new 提交,我们会得到:

A--B--C--D   <-- master

现在master存储D的哈希ID。 (要查找C,Git从D开始。D存储C的ID - &#34;指向&#34; C - 和这会让我们到C,然后到B,等等。)

当您创建一个新分支时,Git会将一些ID复制到新分支名称中。默认情况下,我们从当前提交(现在是D)开始:

A--B--C--D   <-- master, newbranch (HEAD)

现在有两个名称,我们需要知道哪一个是当前。 Git将其存储在特殊名称HEAD中(实际上是文件.git/HEAD):HEAD字面上包含分支的名称

现在,如果我们进行新的提交,Git会像往常一样进行新提交,并更新当前分支,即newbranch

A--B--C--D   <-- master
          \
           E   <-- newbranch (HEAD)

我们在一个新的分支上有一个新的提交,在视觉上看起来像分支。除了它只是一条带有扭结的直线,真的。为了使它正确分支,我们必须检查master(以便HEAD说master)并进行另一次新的提交:

A--B--C--D--F   <-- master (HEAD)
          \
           E   <-- newbranch

现在我们确实有一个分支。

这里的关键是要注意实际的分支是属性,而不是名称master vs newbranch),但是提交及其嵌入的图形链接。名称只是让我们开始进入图表,之后我们将所有这些向后 - 链接跟随。

因此,为了正确回答问题3,我们必须看看图表是什么样的。它可能看起来像这样:

...--G--H--I   <-- LocalMaster (HEAD)
            \
             J   <-- BugFix

您现在可以在LocalMaster上进行新的提交:

...--G--H--I--K   <-- LocalMaster (HEAD)
            \
             J   <-- BugFix

现在,您希望BugFix从提交K开始。您根本无法更改 J,但您可以将其复制到新的临时分支。复制到新的类似提交后,您会得到:

                J'   <-- tmp (HEAD)
               /
...--G--H--I--K   <-- LocalMaster
            \
             J   <-- BugFix

(使用名称J'表示它是J的副本。您现在可以通过强制Git重新找到名称J以指向BugFix来开始忽略原始J'

                J'   <-- BugFix (HEAD), tmp
               /
...--G--H--I--K   <-- LocalMaster
            \
             J   [abandoned]

现在你不需要临时名称,所以你可以删除它。

为您执行所有这一切的命令git rebase

但是, ,但是,有些情况下它是不明智的。特别是,假设您将提交J提供给其他人,将其推回到某个地方。那些其他人现在拥有他们的提交J的副本。您建议将其替换为新的J'。你必须得到其他人来做同样的替换!否则,您的旧J可以回来:他们可能认为原始的J是重要的,而不是J'的重复,并且稍后会重新介绍它。

如果不能选择基础,则可以使用git merge。合并的作用很复杂,但最终,它会生成一个新的合并提交,这是一个不仅指向一个父级而是指向两个父级的提交。您可以像以前一样开始使用:

...--G--H--I--K   <-- LocalMaster (HEAD)
            \
             J   <-- BugFix

然后,您可以查看BugFix并运行git merge LocalMaster。这将弄清楚如何将合并基础(提交I,其中分支聚集在一起)的更改与两个分支提示的提交相结合(提交{ {1}}和J,按照某种顺序 - 顺序对于组合并不重要,但后来确实很重要。如果组合成功,Git将进行新的合并提交。其第一个父级将为K,因为J期间的提交为HEAD。结果看起来像图形一样:

git merge
  

我说我在第2步中做了一个我不想合并到LocalMaster的更改。我该如何退出这一变化?

您在此建议您在...--G--H--I--K <-- LocalMaster \ \ J--M <-- BugFix (HEAD) 上进行了更改并提交了更改。没有什么可以退出的:

BugFix

在这里,哪个分支是最新的并不重要。名称...--G--H--I <-- LocalMaster \ J <-- BugFix 会记录提交LocalMaster,因此I上提交的内容包括I。名称LocalMaster记录了提交BugFix,因此J的提交位于G--H--I--J。请注意,许多(在这种情况下只有一个)提交都在两个分支上 - 这是Git特有的另一种方式。