内外翻滚

在版本控制系统中工作的能力是每个程序员都需要的一项技能。 通常,挖掘Git并了解其内部结构似乎是在浪费时间,并且可以通过一组基本命令来解决基本任务。

当然,AppsCast团队希望了解更多信息,并就Git的所有功能的实际应用提供建议,他们转向Square的Yegor Andreyevich



丹尼尔·波波夫(Daniil Popov) :大家好。 今天,来自Square的Yegor Andreyevich加入了我们。

Egor Andreyevich :大家好。 我住在加拿大,在Square(一家金融行业的软件和硬件公司)工作。 我们从接收信用卡付款的终端开始,到现在为企业主提供服务。 我正在开发Cash App产品。 这是一家移动银行,可让您与朋友进行兑换,在商店中订购借记卡进行付款。 该公司在世界各地设有许多办事处,而加拿大办事处则有大约60名程序员。

Daniil Popov :在android开发人员的环境中, Square以其开源项目而闻名,这些项目已成为行业标准:OkHttp,Picasso,Retrofit。 顺理成章的是,开发这种工具对所有人开放,您在Git上工作很多。 我们想谈谈这个。

什么是git


Egor Andreevich :我使用Git已有很长时间了,在某个时候,对它进行更多的了解变得很有趣。

Git是一个简化的文件系统,其上是一组用于版本控制的操作。

Git允许您以特定格式保存文件。 每次编写文件时,Git都会将密钥返回给您的对象hash

丹尼尔·波波夫(Daniil Popov) :许多人注意到该存储库具有一个神奇的隐藏目录.git 。 为什么需要它? 我可以删除或重命名吗?

Egor Andreevich :可以通过git init命令创建存储库。 它创建Git用于控制文件的.git目录。 .Git仅以压缩格式存储您在项目中执行的所有操作。 因此,您可以从此目录还原存储库。

Alexei Kudryavtsev :事实证明您的项目文件夹是扩展的Git文件夹的版本之一?

Egor Andreevich :根据您所在的分支,git会还原您可以使用的项目。

Alexei Kudryavtsev :文件夹中有什么?

Egor Andreevich :Git创建特定的文件夹和文件。 最重要的文件夹是.git / objects,用于存储所有对象。 最简单的对象是blob,本质上与文件相同,但采用git可以理解的格式。 当您想在存储库中保存文本文件时,Git会对其进行压缩,存档,添加数据并创建一个Blob。

有目录-这些是带有子文件夹的文件夹,即 Git有一个对象类型,其中包含对Blob和其他树的引用。

基本上,一棵树是快照,它描述了某个时刻目录的状态。

创建提交时,指向工作目录的链接是固定的-树。

提交是到树的链接,其中包含有关创建者的信息:电子邮件,名称,创建时间,到父(父分支)的链接和消息。 Git还压缩提交并将其写入对象目录。

为了了解所有这些工作原理,您需要从命令行列出子目录。

使用Git的好处


Daniil Popov :Git如何工作? 为什么动作算法如此复杂?

Egor Andreevich :如果将Git与Subversion(SVN)进行比较,那么在第一个系统中,您需要了解许多功能。 我将从暂存区域开始,不应将其视为Git的限制,而应将其视为功能。

众所周知,在使用代码时,并不是一切都会立即进行:需要更改布局的地方,修复错误的地方。 结果,在工作会话之后,会出现许多未互连的受影响文件。 如果您在一次提交中进行所有更改,那么这将很不方便,因为更改本质上是不同的。 然后,将一系列提交提交到输出中,只需通过暂存区域即可创建该提交。 例如,对布局文件的所有更改都发送到一个系列,例如,将单元测试修复到另一个系列。 我们获取几个文件,将它们移到暂存区,并仅在他们参与的情况下创建提交。 工作目录中的其他文件不属于该目录。 因此,您将在工作目录中执行的所有工作分解为几个提交,每个提交代表一个特定的工作。

Alexei Kudryavtsev :Git与其他版本控制系统有何不同?

Egor Andreevich :就我个人而言,我从SVN开始,然后立即切换到Git。 重要的是,Git是一个分散的版本控制系统。 Git存储库的所有副本都是完全相同的。 每个公司都有一台主版本所在的服务器,但这与开发人员在计算机上安装的服务器没有什么不同。

SVN具有中央存储库和本地副本。 这意味着任何开发人员都可以单独破坏中央存储库。

在Git中,这不会发生。 如果中央服务器丢失了存储库数据,则可以从任何本地副本还原它。 Git的设计有所不同,它提供了速度优势。

Daniil Popov :Git以其分支而闻名,它的运行速度明显快于SVN。 他是怎么做到的?

Egor Andreevich :在SVN中,分支是上一个分支的完整副本。 Git中没有分支物理表示。 这是到特定开发线中最后提交的链接。 当Git保存对象时,在创建提交时,它将创建一个包含有关提交的特定信息的文件。 Git创建一个符号文件-带有另一个文件链接的符号链接。 当您有许多分支时,您将在存储库中引用不同的提交。 要跟踪分支的历史记录,您需要使用链接从每次提交返回到父提交。

Merjim分支机构


Daniil Popov :有两种方法可以将两个分支合并为一个-这是merge和rebase。 您如何使用它们?

Egor Andreevich :每种方法都有其优点和缺点。 合并是最简单的选择。 例如,有两个分支:master和从中选择的功能。

对于合并,您可以使用快进。 如果从功能分支开始工作的那一刻起,就没有在master中进行新的提交,那么这是可能的。 即,功能中的第一个提交是主服务器中的最后一个提交。

在这种情况下,指针固定在master分支上,并移动到feature分支中的最新提交。 这样,通过将功能分支连接到主主线程并删除不必要的分支,可以消除分支。 事实证明,所有提交都相互遵循。 实际上,该选项很少发生,因为不断有人将提交合并到主控中。

可以是其他方式。 Git创建一个新的提交-合并提交,它有两个指向父提交的链接:一个在主提交中,另一个在功能上。 使用新的提交,两个分支已连接,可以再次删除功能。

合并提交后,您可以查看该故事并看到它是分叉的。 如果您使用图形化地呈现提交的工具,那么在视觉上它将看起来像一棵圣诞树。 这不会破坏Git,但是对于开发人员而言,观看这样的故事非常困难。

另一个工具是变基 。 从概念上讲,您可以从功能分支中获取所有更改,然后将其翻转到主分支上。 第一个功能提交将成为最新的主提交之上的新提交。

有一个陷阱-Git无法更改提交。 该功能具有提交功能,我们不能仅将其包装在master上,因为每个提交都有时间戳。

在使用rebase的情况下,Git读取功能中的所有提交,将其暂时保存,然后在master中以相同顺序重新创建它。 在重新设置基准之后,初始提交会消失,并且具有相同内容的新提交会显示在主服务器上。 有问题。 当您尝试为其他人使用的分支建立基础时,可以破坏存储库。 例如,如果某人从功能中的提交开始分支,然后您销毁并重新创建了该提交。 Rebase更适合本地分支机构。

丹尼尔·波波夫(Daniil Popov) :如果您引入了严格限制一个人在一个功能分支上工作的限制,并且同意您不能与另一个功能分支搭档,那么就不会出现此问题。 但是您练习什么方法呢?

Egor Andreevich :在这个术语的直接意义上,我们不使用要素分支。 每次我们进行更改时,创建一个新分支,在其中工作,然后立即将其倒入master。 没有长期活动的分支机构。

这解决了合并和变基的大量问题,尤其是冲突。 分支存在一个小时,由于没有人向主节点添加任何内容,因此极有可能使用快进。 当我们在合并请求中合并时,仅与创建合并提交合并

丹尼尔·波波夫(Daniil Popov) :您不怕合并主空闲功能,因为将任务分解为每小时间隔通常是不现实的吗?

Egor Andreevich :我们使用功能标记方法。 这些是动态标志,具有不同状态的特定功能。 例如,打开或关闭互相发送付款的功能。 我们提供的服务可将这种状态动态地传递给客户。 您是否从服务器获得了价值减免功能。 您可以在代码中使用此值-关闭转到屏幕的按钮。 该代码本身在应用程序中,可以被释放,但是无法访问此功能,因为它位于功能标志的后面。

丹尼尔·波波夫(Daniil Popov) :通常,Git的新手会被告知,在改组后,您需要大力推动。 它是哪里来的?

Egor Andreevich :当您推送时,另一个存储库可能会引发错误:您试图运行一个分支,但是在该分支上的提交完全不同。 Git检查所有信息,以便您不会意外破坏存储库。 当您说git push force时 ,请关闭此检查,并认为您比他更了解,并要求重写分支。

重新设置基准后,为什么需要这样做? Rebase重新创建提交。 事实证明,该分支也被称为,但与其他哈希一起提交在其中,Git向您发誓。 在这种情况下,强制推动绝对是正常的,因为您可以控制情况。

丹尼尔·波波夫(Daniil Popov) :仍然存在交互式变基的概念,许多人对此感到恐惧。

Egor Andreevich :没什么可怕的。 由于Git在重新定型期间会重新创建故事,因此它会在临时存储它之前将其丢弃。 当具有临时存储时,它可以执行任何操作。

交互模式下的Rebase假定Git在Rebase之前抛出了一个文本编辑器窗口,您可以在其中指定每个单独提交需要执行的操作。 它看起来像几行文本,其中每一行是分支中的提交之一。 在每次提交之前,都有一个值得执行的操作指示。 最简单的默认操作是pick ,即 采取并纳入变基。 最常见的是squash ,然后Git将从此次提交中获取更改并将其与上一次提交中的更改合并。

通常会出现这种情况。 您在分支机构本地工作,并为保存创建了提交。 结果是很长的提交历史,这对您来说很有趣,但是以同样的方式,不应将其纳入主要历史记录中。 然后,您将壁球重命名为常规提交。

球队名单很长。 您可以抛出提交- 丢弃并消失,可以更改提交消息,等等。

Alexei Kudryavtsev :当您在交互式资源库中有冲突时,您会遇到各种麻烦。

Egor Andreevich :我还不了解交互式基础的所有智慧,但是它是一个强大而复杂的工具。

实际的Git应用


丹尼尔·波波夫(Daniil Popov) :让我们继续练习。 在面试时,我经常问:“您有一千次提交。 首先一切都很好,第千次测试就失败了。 在gita的帮助下,如何找到导致这一变化的变化?”

Egor Andreevich :在这种情况下,您需要使用bisect ,尽管更容易受到指责。

让我们从有趣的开始。 Git bisect适用于您进行回归的情况-它是有效的,可以工作,但是突然停止了。 要查找功能何时失效,理论上您可以随机回滚到应用程序的先前版本,查看代码,但是有一个工具可以让您结构化地解决问题。

Git bisect是一个交互式工具。 有一个git bisect bad命令,您可以通过该命令报告是否存在损坏的提交,而git bisect良好-对于有效的提交。 每次发布应用程序时,我们都会记住发布版本的提交的哈希值。 该哈希也可以用于指示错误和良好的提交。 Bisect接收有关其中一个提交破坏功能的时间间隔的信息,并启动二进制搜索会话,然后在二进制搜索会话中逐步发出提交以检查它们是否起作用。

您开始了会话,Git切换到间隔中的提交之一,并报告此情况。 在一千次提交的情况下,不会有很多迭代。

验证必须手动完成:通过单元测试或运行应用程序,然后手动单击。 Git bisect可以方便地编写脚本。 每次他提交提交时,您都给他一个脚本,以验证代码是否有效。

Blame是一个更简单的工具,根据其名称,它使您可以找到功能故障的“罪魁祸首”。 由于这个否定性定义,在非理性社区中很多人都不喜欢它。

他在做什么? 如果给git blame一个特定的文件,则线性地出现在该文件中将显示哪个提交更改了该行或该行。 我从未在命令行中使用过git blame。 通常,这是在IDEA或Android Studio中完成的-单击并查看谁更改了文件的哪一行以及进行了哪些提交。

Daniil Popov :顺便说一句,在Android Studio中,它被称为Annotate。 责备的消极含义已被消除。

Alexei Kudryavtsev :确实,他们在xCode中将其重命名为Authors。

Egor Andreevich :我还读到有一个实用程序git赞美-找到写这个出色代码的人。

丹尼尔·波波夫(Daniil Popov) :应当指出,审稿人应将建议工作归咎于建议。 他着眼于哪些人触摸了特定文件,并建议该人能够很好地检查您的代码。
在该示例中,大约有1000次提交,在99%的情况下归咎于错误。 Bisect已经是不得已的方法。

Egor Andreevich :是的,我极少采用二等分法,但是我经常使用注释。 尽管有时无法从代码行中理解为什么将其检查为空,但是在整个提交过程中,作者想要做什么很清楚。

如何使用堆叠式PR?


丹尼尔·波波夫(Daniil Popov) :我听说Square使用了堆叠式拉取请求(PR)。

Egor Andreevich :至少在我们的Android团队中,我们经常使用它们。
我们花费大量时间使每个请求请求易于审核。 有时,有一种诱惑力,即快速进食,并让审阅者理解。 我们尝试创建小的拉取请求和简短说明-代码应该说明一切。 当拉取请求较小时,可以轻松快速地求助。

这就是问题所在。 您正在使用的功能需要在代码库中进行大量更改。 你能做什么? 您可以将其放入一个请求请求中,但是它将非常庞大。 您可以通过逐步创建一个拉取请求来工作,但是问题是您创建了一个分支,添加了一些更改并提交了一个拉取请求,返回给master,而您在拉取请求中拥有的代码直到master才可用。合并不会发生。 如果您依赖这些文件中的更改,则由于没有这样的代码,因此很难继续工作。

我们如何解决这个问题? 创建第一个拉取请求后,我们将继续工作,从现有分支创建一个新分支,该分支将在拉取请求之前使用。 每个分支不是来自主分支,而是来自上一个分支。 当我们完成此功能的工作时,我们将提交另一个拉取请求,并再次表明使用merge时,它不会在master中合并,而在上一个分支中。 事实证明,这样的一连串拉取请求-堆积的PRS。 当一个人进行修改时,他看到的仅是此功能所做的更改,而不是前一个所做的更改。

任务是使每个拉取请求尽可能小且清晰,从而无需更改。 因为如果您必须更改堆栈中间的分支中的代码,则最上面的所有内容都会中断,因为您必须重新设置基础。 如果拉取请求很小,我们将尝试尽快冻结它们,然后所有堆叠的合并将逐步合并到主请求中。

丹尼尔·波波夫(Daniil Popov) :我正确地理解,最后会有一些包含所有小请求请求的最后一个请求请求。 您不看该线就倒了吗?

Egor Andreevich :合并来自堆叠的源:首先,第一个拉取请求被合并到master中,接着,基础从分支变为master,因此,Git计算出master中已经有一些更改,快照更少了。

: , , , master?

: , . pull request, , , , bitbucket, merge.

: CI, ?

: . CI , pull request base. , .

: master develop? , ?

: develop, master. . , - - master, . tags — - . - — -, , . , Git , .

: Git, ?

: Git . , , . , . StackOverflow . .

Saint AppsConf Git . Introductory, . Avito : , 2019 .

AppsCast , , SoundCloud , .

Source: https://habr.com/ru/post/zh-CN468177/


All Articles