Git指南:0表示重新设置基准

2020-08-30 16:47:53

本指南的目的是让您在不必阅读整本书的情况下更好地使用GIT,我将通过让您更深入地了解GIT的内部工作来做到这一点。

如果您有好奇心,并且想要更好地使用git,本指南的目的是满足您的好奇心,而不是“传统的”git指南。

我希望您能在这里学到足够多的知识来更自信地使用GIT,并且更好地知道当您陷入困境时应该往哪里看。

Git是一个计算机程序,用于创建计算机上文件夹的快照(实质上是备份)。在Git中,这些快照称为“提交”。提交包含该文件夹中的所有内容:

Git的优势来自它组织提交的方式,以及它提供的用于操作、标记和比较提交的所有命令,处理提交是使Git成为“版本控制系统”的原因。

Git试图使备份变得非常容易,以便您在需要时总是可以找到一个有用的位置来返回。此外,拥有详细的提交历史记录可以帮助回答有关过去不同时间点的情况以及为什么进行更改的问题。每个提交都有关于它何时创建、由谁创建的信息,以及由提交者编写的“提交消息”,理想情况下,该消息包含有关它为什么存在的重要信息。

Git也有与其他人协作的命令(例如Push/Pull/Fetch)。协作本质上意味着交换提交和“分支”(在Git中,它们只是提交的标签)。协作通常意味着将它们发送到服务器,其他人可以在那里检索它们,但也可以直接与其他协作者共享。1个。

Git通常从命令行使用,但也有其他方式使用它(Git GUI、SourceTree、TortoiseGit等等)。这里有一个很大的git图形用户界面列表)。

Git init#创建repositorygit add。#准备提交#the folder.git Commit-m";Initial Commit";#用消息#";Initial Commit";存储提交。

当您使用git init在文件夹中创建repo时,git会在其中添加一个名为“.git”的隐藏文件夹。.git文件夹是它放置所有提交的位置。

文件夹的其余部分现在称为“工作目录”。

创建提交时,Git将所有文件和文件夹作为“对象”存储在.git文件夹中。此外,存储的对象还包含重新创建工作目录内容所需的所有信息。

当您使用git克隆检索repo时,git实质上检索.git文件夹并从对象重新创建-“签出”-工作目录。

有几种不同类型的GIT对象,让我们仔细看看。

在Git中,一切都是文件。所有对象都作为文件存储在.git存储库中。对象根据它们包含的数据有不同的类型。对象的文件名通常是一串乱码,用作对该对象的唯一引用。

当您调用git Commit时,git会为每个文件夹创建一个“Commit”对象,为每个文件夹创建一个“tree”对象,为快照中的每个文件创建一个“blob”对象。blob是最简单的,它只是文件的副本,除了名称,它是一个乱七八糟的引用。

如果您查看.git/对象,您会发现repo中的所有提交、树和BLOB对象,它们的名称都是乱七八糟的。

通过不将对象与文件或文件夹名称相关联,即使文件或文件夹的名称或位置发生更改,只要内容保持不变,也可以重用对象。

每个直接子文件夹的名称和引用。该引用是对描述子文件夹内容的树对象的引用。

文件夹中每个文件的名称和引用。该引用指向具有文件内容的BLOB对象。

提交者和作者(即您,如果您进行了提交-您的姓名和电子邮件地址通过GIT配置进行配置)。

每个提交都指向一个父级,这意味着可以将提交理解为建立在它之前的内容之上。

尽管COMMIT被认为是一种更改或差异(也称为“增量”),但GIT将其存储为独立的快照。当需要时,通过比较COMMITS来计算有关它所代表的更改的任何信息。

因为对象是通过引用来引用的,所以只要对象已经存在,git就只引用现有的对象。因此,当提交只更改一个文件时,只需要创建一个新的BLOB对象(以及至少一个树对象)。

通过引用检索任何对象只意味着查找具有特定名称的文件。

在免费的专业Git图书的Git内部章节中对对象进行了深入的描述。如果您好奇,我推荐它,我就是在这里学到这一点的。

现在让我们仔细看看唯一的胡言乱语引用(“hash”)。

这一节可能很重,但继续往前走,它触及了git的许多怪癖的核心。

在git中,到处都是散列,显示为由数字和字母组成的长字符串。所有对象都有散列,但是您最常看到的是提交散列,例如当您运行git log时。

散列是从给定对象的内容计算出来的,对于该对象来说是完全唯一的,通常被称为提交的“sha”,因为散列的类型是SHA1。

散列(或“摘要”)是一个大数字,它总是有一定的长度。对于sha1,长度是160位,或20字节3。在git中,这些字节总是显示为40个十六进制字符(0-9,a-f)的文本字符串4。

哈希是通过获取数据(输入)并对其进行一些复杂的数学运算来创建的。输入可以是任何长度的任何数据。在git中,对象充当输入。

无论输入的长度如何,散列的长度都是相同的。

即使更改、添加或删除了单个位,即使输入稍有不同,散列也总是完全不同。

夏尔(#34;a&34;):86f7e437faa5a7fce15d1ddcb9eaeaea377667b8sha1(";To死亡,去睡觉--去睡觉,也许去做梦--啊,这就是问题所在,因为在这死亡的睡眠中,什么梦可能会出现。):0855319deffa58b0458c29edeaa8d6a120d44f35sha1(";to死亡,去睡觉--去睡觉,也许去做梦--是的,这就是问题所在,因为在这死亡的睡眠中,可能会出现什么梦?):kab29b74b98c3ad638fe9c1abed72be40d69:kap29b74b98c3ad638fe9c1abed72be40d69;(#34;a#34;):kap29b74b98c3ad638fe9c1abed72be40d69;

根据这些规则,我们可以认识到,如果对象中的任何数据发生更改,它就不再是同一个对象,因为引用将完全不同,这包括提交时时间是否只更改了一秒,文件是否只更改了一个字节,或者文件夹名称是否稍有更改。

这还意味着更改必须传播。如果文件更改,其引用也会更改,因此包含引用的树也会更改,而包含树的提交也会更改。

从提交派生的任何提交也必须更改。事实上,回顾过去,提交散列不仅唯一地标识提交,而且通过其父代和祖辈等标识整个GIT历史-直到该提交之前的每个树、BLOB和提交。如果历史中任何位置的任何详细信息不同,则当前提交将不会是相同的提交,因为其散列将不同。

在创建提交时,您可能希望限制与前一个提交的差异,希望以逻辑的方式对更改进行分组,使提交更易于描述,并在以后对其进行推理。

不过,自上次提交以来,您可能已经做了很多更改。Git允许您在提交更改之前“试运行”更改,从而在这种情况下提供帮助。Git add将文件添加到“索引”,即暂存它们,即将它们标记为“待提交”。

创建提交时,只有暂存文件和文件夹会接收新对象,其余的将以未更改的方式存储。

Git在您准备文件时从文件创建树和BLOB,因此Git Commit只需要创建一个引用为索引创建的树的提交对象。

索引经常让人感到困惑,但是在本指南的这一点上,了解索引和提交之间的相似之处可能会很有趣。

我曾经错误地删除了一个我一直在处理的新文件,因为我没有提交它,所以损失了几个小时的工作(经常提交,各位!)。但是我记得我曾经临时设置过它,所以我知道创建了一个blob对象。我设法在我的.git文件夹中的文件中搜索丢失的文件中存在的短语,然后找到了该文件。blob没有指向它的引用,所以它最终会被垃圾收集,但由于我速度很快,我设法恢复了它,并立即进行了验证。

现在,让我们回到我们的“旅程”。我们已经彻底研究了什么是提交,以及提交之间是如何关联的。现在,让我们来看一下分支,看看为什么它们既非常简单又非常复杂。

假设您刚刚做出了一个承诺。您现在想要进行一些新的更改,但是您对如何更改文件有两种不同的想法。要充分探索这一点,您需要备用的时间表。

回想一下,每个提交都有一个父提交。没有什么可以阻止您使用同一个父提交创建多个提交。相反,提交对象实际上可以包含多个父提交。这意味着总的历史记录并不总是看起来像一行,而是可以分支出来,然后聚集在一起。

如果您运行git Branch myBranch,您将从当前提交创建一个名为“myBranch”的分支。git中的分支非常类似于书签,它跟踪您在特定时间线内进行了多远。如果您运行git switch myBranch,您将使myBranch成为您的活动分支(保留以前活动的分支,希望能附加一个书签)。

您创建的提交将始终添加到当前活动的分支中。这意味着当您调用git Branch myBranch时所执行的提交最终将有多个子项,每个分支都有一个指向它的子项。

分支的概念在版本控制中广泛存在,而不仅仅是在git中。它通常代表一个单独的开发流,它可以拥有自己的生命,直到它准备成为更大的共享流的一部分,或者直到它被丢弃,转而使用另一个流。在git中,分支创建的拓扑是提交历史记录中不可避免的一部分,并且有一些工具可以将其可视化(例如GITK)。

然而,分支的名称比它们产生的拓扑要短暂得多,让我们来看看。

Git的分支通过另一种对象类型“ref”对象来命名。ref是git中最简单的对象,它只是对提交的引用。引用的文件名是给提交的标签,例如分支名称(分支不是唯一的引用类型)。

引用通常是进入git历史的入口点:(人类可读的)引用名称指向一个提交,该提交指向它的树和导致该提交的历史。

对于分支,引用指向分支上的提示(最新提交),此引用在将提交添加到分支时自动更新。

“当前分支”是名为“Head”(.git/head)的引用。使用git开关或git签出切换分支会更改此引用。

裁判的工作方式解释了git分支的一个怪癖,如果你正在查看提交,就没有办法确切地说出当你提交时你在哪个分支上,它不会被记录在任何地方。即使分支引用指向提交,引用也可能是在提交之后创建的。

有时,拥有这些信息会很好,但大多数情况下,提交本身就说明了问题。标签通常是永久性的,因此它们也可以用来传达上下文。

大多数分支机构都是临时性的。通常只有一个永久分支机构,其他分支机构最终会合并到其中,之后它们的引用通常会被删除。

现在,以前在myBranch上所做的所有提交也是dev历史的一部分。

合并是一个复杂的主题,但归根结底,GIT合并只是一个包含多个父项的提交,将两个或多个时间线捆绑在一起。复杂性在于确定合并委员会的树对象的过程。它必须忠实于所有涉及的时间线自它们偏离以来发生的更改。如果多个时间线对同一文件中的同一行进行了不同的更改,则很难做到这一点。Git非常擅长合并,但有时它会放弃,您必须手动告诉Git某些部分应该如何更改。Git很擅长合并,但有时它会放弃,您必须手动告诉Git某些部分应该如何更改。Git很擅长合并,但有时它会放弃,您必须手动告诉Git某些部分应该如何更改。

如果您提交了一个分支,然后意识到它应该在另一个分支上,但是您不想将这些分支合并在一起,该怎么办呢?

Git cherry-Pick 1234abcd#使用hash";1234abcd&#将提交复制到当前分支。

命令git cherry-ick将单个提交复制到当前分支的顶端。记住:更改任何内容(包括父级)都会使提交成为不同的提交,因此精心挑选的提交将始终被视为对其源提交的不同提交。如果是在后来合并的分支之间精心挑选的提交,则git日志可能看起来有多个提交副本。

但是Cherry-Pick命令更改的不仅仅是新提交的父级。您已经了解到,Git为每次提交存储了文件夹内容的独立快照,但是Git的用户通常最关心的是每次提交之间发生了什么变化。这就是Cherry-Pick假设您想要的。

不是为新提交赋予与源提交相同的树,而是精挑细选地查看提交代表什么更改,并尝试将该更改应用于新提交的父级。这意味着新提交通常具有与其源不同的树。

如果您认为精挑细选听起来很像合并,那么您就完全正确了。事实上,它们在引擎盖下共享功能。

您还可以精挑细选多个提交。Git Cherry-Pick可以接受一系列提交,并会一个接一个地挑选,这给我们带来了很好的重新定位。

Git Rebase基本上是精挑细选的,有很多花哨(很多花哨),尽管它的目的是解决一个略有不同的问题。

Cherry-Pick允许您解决“我想要将那些提交移动到当前分支中”的问题,而Rebase解决了“我想要移动当前分支,就好像它的提交来自不同的基提交一样”。5个。

与Cherry-Pick类似,Rebase将所有移动的提交放入不同的提交中:由于第一个提交获得不同的父提交,因此它变成了不同的提交,这意味着下一次提交的父提交会发生变化,依此类推。

REBASE可以在很多方面用于很多事情。最后,这里有一些例子,展示了一些最重要的花哨:

如果要从历史中删除提交(及其更改),请将它之后的提交重新定位到它之前的提交。

如果您忘记在几个提交之前的提交中包含某些内容,您可以重新提交,并将其合并到原始提交中。为此,您需要使用“交互式重定基址(Interactive Rebase)”。Git rebase--交互式IS rebase是神模式,允许您自由更改提交的顺序,并在rebase过程中将它们拆分并挤压在一起。

Git Commit--fixup head~2#创建一个要压缩到倒数第三个提交中的提交。git rebase--交互式head~4#交互式地重新设置最后4个提交的基础#。

当调用交互式rebase时,git将打开一个列出所有提交的文本文件,希望您对其进行编辑、保存和关闭。

如果您已提交,但其他人已提交到同一分支,则git推送将失败,告诉您执行git提取。默认情况下,git提取会将其他更改合并到您的分支中。这些合并提交特别容易混淆,因为它们是无意的和任意的。Git拉动--REBASE将把您的更改重新建立在新的提交之上,就好像您是在它们之后提交的一样。

如果一个分支已经将许多其他分支合并到其中,那么该分支的提交历史可能看起来非常混乱。默认情况下,git将丢弃它在重新基址期间遇到的所有合并提交,而是逐个精挑细选所有常规提交,因此生成的历史记录是线性的。

另一方面,如果您还想完全控制合并提交,git rebase--interactive--rebase-merges会让您成为git历史的终极统治者。您可以创建或销毁整个分支,随意合并它们,并根据需要自由移动提交,所有这些都是通过重新排列和编辑文本文件来实现的。

Git非常棒,尽管它的命令可能令人困惑,它的基础和核心思想是坚如磐石的,并且工作得非常好。

我希望你喜欢这个,并学到了一些东西。我刚开始编写这样的指南,将来我很可能会修改它,所以非常感谢您的反馈。

在Git中,创建和编辑提交完全独立于与他人共享提交,这就是为什么Git被称为“分布式版本控制系统”。相反,像SVN这样的非分布式版本控制系统通常在创建提交时将提交发送到服务器。这意味着您不会感觉到可以进行提交,因为您必须处理任何协作问题(如果有人更改了与您相同的文件)才能进行提交,并且任何人都可以看到您所做的提交。Git允许您单独执行这些活动。在Git中,创建和编辑提交是完全独立于与他人共享提交的,这就是为什么git被称为“分布式版本控制系统”。相反,像SVN这样的非分布式版本控制系统通常会在创建提交时将提交发送到服务器。这是有道理的,因为原则上没有理由将它们结合起来。↩。

可能的sha1散列数量有限(2?⁶⁰),因此在理论上可以有产生相同散列的输入,但实际上2?⁶⁰是一个非常大的数字,以至于这种情况永远不会发生(最有可能的是,每个GIT回收库中的每个唯一对象都有不同的散列)。。↩。

为了方便起见,git允许您在调用git命令时缩短散列,只要它仍然可以唯一地标识来自给定十六进制数字的提交。例如,。

Git Reset--hard<;newbase>;#修改分支引用,使其指向#<;newbase>;,允许我们将提交添加到itgit cherry-Pick..@{1}#cherry-Pick All Commits Not Present#on the new Branch。@{1}表示我们#在GIT重置之前的位置(请参阅";reflog";)。