为什么在提交Git之前要登台?


102

我是版本控制的新手,我了解“提交”实际上是在更新您正在使用的新“当前”版本时创建备份。

从实际的角度来看,我不了解分期进行。分期是仅以名称存在还是有目的?当您提交时,它仍然会提交所有内容,对吗?

编辑:我想我可能会混淆术语。“暂存”文件与“跟踪”文件是否相同?


6
不会。被跟踪的文件是存储库已知的文件(通常是先前提交的文件)。暂存文件是已添加到索引的文件,以后将用于提交。
马克·彼得斯

Answers:


82

提交时,只会提交索引(“暂存”文件)中的更改。这样做有很多用途,但是最明显的是将您的工作更改分解成较小的,独立的部分。也许您在实现功能时已修复了一个错误。您可以git add只提交该文件(或git add -p仅添加一部分文件!),然后在提交其他所有内容之前提交该错误修正。如果您正在使用,则只在提交之前git commit -a强制执行add所有操作。-a如果要利用暂存文件,请不要使用。

您还可以使用--cached许多命令将暂存文件视为中间工作副本。例如,git diff --cached将向您展示该阶段与当前阶段有何不同,HEAD以便您可以看到要提交的内容,而无需混合其他工作更改。


25
另一个真正常见的用途是用于某些更改永远不应该提交的情况。例如,您可以暂存好东西,提交,然后使用吹走坏东西git reset --hard
卡斯卡贝尔2011年

3
在您的示例中,@ BenJackson,stage + commit和选择性提交之间有什么区别?我没什么区别。
Eugenio

9
我个人无法对“分期的目的是什么?”做出令人满意的答复。题。坦白说,这是不必要的。我已经在使用本地分支机构,因此没有中断构建的风险。在我完全满意所做的更改之前,我不会发布并发出请求请求。我已经可以按逻辑划分我的提交了。只是不需要中间步骤。因此,我从不使用它。
mrplainswalker

3
我认为您没有真正回答这个问题。“但是最明显的是将您的工作变更分解成较小的,独立的部分。” 为什么有人想将他们的变更分成较小的变更?如果您打算在修复最初打算更改的代码之前添加并提交单个错误修复程序,为什么不提交该错误修复程序而不是先添加然后提交呢?
kiwicomb123

1
@ kiwicomb123通常是因为您在执行其他操作时发现了该错误,并且希望通过自己的日志消息在自己的提交中获得该修复程序,并希望灵活地合并/在其他地方修复该问题。
本杰克逊

26
  • 暂存区域使控件可以减小提交。只需对代码进行逻辑更改,将更改的文件添加到暂存区,最后如果更改不正确,则签出到上一个提交或以其他方式提交更改,从而可以灵活地将任务拆分为较小的任务并提交较小的任务变化。使用暂存区域,可以更轻松地专注于小型任务。
  • 它还为您提供了休息的机会,而忘记了休息之前您已经做了多少工作。假设您需要更改三个文件以进行一个逻辑更改,并且您已经更改了第一个文件,并且需要很长时间才能开始进行其他更改。目前,您无法提交,并且您想跟踪完成了哪些文件,因此回来后无需记住已完成的工作量。因此,将文件添加到暂存区域中,它将保存您的工作。当您回来时,只需git diff --staged检查一下更改的文件以及在何处开始进行其他更改即可。

13

暂存的一个实际目的是文件提交的逻辑分离。

由于暂存使您可以继续对文件/工作目录进行编辑,并在认为一切准备就绪时进行部分提交,因此可以将单独的阶段用于逻辑上不相关的编辑。

假设你有4个文件fileA.htmlfileB.htmlfileC.htmlfileD.html。您对所有4个文件进行了更改,可以提交,但更改fileA.htmlfileB.html在逻辑上相关(例如,两个文件中都具有相同的新功能实现),而更改fileC.htmlfileD.html中的更改则是独立的,并且与上一个文件在逻辑上无关。您可以第一阶段的文件fileA.htmlfileB.html并提交这些。

git add fileA.html
git add fileB.html
git commit -m "Implemented new feature XYZ"

然后,在下一步中,暂存并提交对其余两个文件的更改。

git add fileC.html
git add fileD.html
git commit -m "Implemented another feature EFG"

6
在此示例中,我不确定是否确实需要分段。编辑完所有4个文件之后,如果我只想提交fileA.html和fileB.html,我仍然可以提交而不进行分段。命令: git commit -m "Implemented new feature XYZ" fileA.html fileB.html 不需要git add命令就可以正常工作。我来自颠覆世界,在那里暂存不是一个概念,因此,我对git暂存的有用性不敢相信
Pavan

5

如果您想象在Github上的存储库中维护着一个日志文件addcommit那么更容易理解git命令的用法。对我而言,典型项目的日志文件可能如下所示:

---------------- Day 1 --------------------
Message: Complete Task A
Index of files changed: File1, File2

Message: Complete Task B
Index of files changed: File2, File3
-------------------------------------------

---------------- Day 2 --------------------
Message: Correct typos
Index of files changed: File3, File1
-------------------------------------------
...
...
...and so on

我通常以一个git pull请求开始新的一天,以一个请求结束git push。因此,一天记录中的所有内容都与它们之间发生的情况相对应。每天都有一个或多个逻辑任务,我要完成,这些需要更改一些文件。在该任务期间编辑的文件在索引中列出。

这些子任务(此处为任务A和任务B)中的每一个都是单独的提交。该git add命令将文件添加到“已更改文件的索引”列表中。此过程也称为暂存。该git commit命令记录/确定更改以及相应的索引列表以及自定义消息。

请记住,您仍然只更改存储库的本地副本,而不更改Github上的副本。之后,只有当您执行“ git push”操作时,所有这些记录的更改以及每次提交的索引文件才被记录在主存储库(位于Github上)上。

例如,要获得该假想日志文件中的第二个条目,我将完成以下操作:

git pull
# Make changes to these files
git add File3 File4
# Verify changes, run tests etc..
git commit -m 'Correct typos'
git push

简而言之,git addgit commit使您可以将对主存储库的更改分解为系统的逻辑子更改。正如其他答案和评论所指出的那样,它们当然还有许多其他用途。但是,这是最常见的用法之一,也是Git背后的驱动原理,它是一个多阶段修订控制系统,与其他流行的Svn等不同。


2

暂存区有助于我们更加灵活地制定提交。通过精心制作,我的意思是将提交分成逻辑单元。如果您想要一个可维护的软件,这是非常关键的。实现此目的的最明显方法是:

您可以在一个工作目录中处理多个功能/错误,仍然可以进行有意义的提交。具有包含我们所有活动工作的单个工作目录也非常方便。(只要更改不与文件重叠,就可以在没有暂存区域的情况下完成此操作。此外,您还具有手动跟踪它们是否重叠的额外责任)

您可以在此处找到更多示例: 索引的使用

最好的部分是,优势并不仅仅局限于这些工作流程。如果出现了独特的工作流程,您几乎可以确定过渡区域将为您提供帮助。


2

为了扩展本杰克逊的答案,这很好,让我们仔细看一下原始问题。(请参阅他的答案,以了解为什么要打扰类型问题;这更多地是关于正在发生的事情。)

我是版本控制的新手,我了解“提交”实际上是在更新您正在使用的新“当前”版本时创建备份。

不太正确。备份和版本控制当然是相关的-确切程度取决于某些程度上在某些方面需要考虑的问题-但如果只是出于意图,则肯定存在一些差异:备份通常是为灾难恢复而设计的(机器故障,火灾摧毁)整个建筑物,包括所有存储介质等)。版本控制通常是为更细粒度的交互而设计的,并提供了备份所没有的功能。备份通常会存储一段时间,然后被抛弃为“过旧”:最重要的是备份。版本控制通常会永久保存每个提交的版本。

从实际的角度来看,我不了解分期进行。分期是仅以名称存在还是有目的?当您提交时,它仍然会提交所有内容,对吗?

是的,没有。Git在这里的设计有些奇怪。存在一些版本控制系统,它们不需要单独的暂存步骤。举例来说,水银,这是否则会很喜欢的Git在使用方面,并不需要单独的hg add一步,超越了最初的一个介绍一个全新的文件。使用Mercurial,您可以使用hg命令来选择一些提交,然后执行工作,然后运行hg commit,然后完成。使用Git,您可以使用git checkout1完成您的工作,然后运行git add,然后选择git commit。为什么要采取额外的git add步骤?

这里的秘诀就是Git所谓的索引暂存区,有时(有时是最近)缓存。这些都是同一事物的名称。

编辑:我想我可能会混淆术语。“暂存”文件与“跟踪”文件是否相同?

不,但是这些是相关的。一个跟踪文件是一个存在于Git的指数。为了正确地理解索引,最好先了解提交。


从Git 2.23版本开始,您可以使用git switch代替git checkout。对于这种特殊情况,这两个命令执行的功能完全相同。之所以存在新命令,是因为git checkout过多地塞满了东西。他们分成了两个单独的命令git switchgit restore,以使其更容易,更安全地使用Git。


提交

在Git中,提交会保存Git知道的每个文件的完整快照。(Git知道哪些文件?我们将在下一节中看到。)这些快照以特殊,只读,仅Git,压缩和重复数据删除的形式存储,通常只有Git本身可以读取。(每个提交中不仅包含此快照,还包含更多内容,但这就是我们将在此处介绍的全部内容。)

重复数据删除有助于节省空间:我们通常只更改几个文件,然后进行新的提交。所以,大多数的文件提交大多相同,在以前的文件提交。通过直接直接直接重复使用这些文件,Git节省了大量空间:如果我们仅触摸一个文件,则新提交仅占用一个新副本的空间。即使这样,它也会被压缩(有时会非常压缩,尽管这实际上是在以后发生的),因此一旦将.git目录扩展为普通的日常文件,该目录实际上可以小于其包含的文件。重复数据删除是安全的,因为已提交的文件会一直冻结。没有人可以更改一个,因此提交依赖于彼此的副本是安全的。

由于存储的文件采用这种特殊的,永久冻结的,仅限Git的格式,因此Git必须将每个文件扩展为普通的日常副本。普通副本不是Git的副本:它是您的副本,可以随心所欲地做。当您告诉Git这样做时,Git只会对其进行写操作,以便您可以使用副本。这些可用的副本位于您的工作树工作树中

这意味着当您检出某些特定的提交时,每个文件自动有两个副本:

  • Git在当前commit中有一个永久冻结的,Git认证的副本。您不能更改此副本(尽管您当然可以选择其他提交或进行新的提交)。

  • 您的工作树中具有普通格式的副本。您可以使用计算机上的任何命令来执行此操作。

其他版本控制系统(包括上述的Mercurial)在此处停止,并带有这两个副本。您只需要修改工作树副本,然后提交即可。Git ...没有。

指标

在这两个副本之间,Git存储每个文件的第三个副本2。第三份副本为冻结格式,但是与提交中的冻结副本不同,您可以更改它。要更改它,请使用git add

git add命令意味着使文件的索引副本与工作树副本匹配。也就是说,您正在告诉Git:通过压缩更新后的工作树副本,对其进行重复数据删除,并准备将其冻结为新提交,从而替换索引中现在的冻结格式,重复数据删除副本。 如果您使用git add,则索引仍会保留当前提交的冻结格式副本。

运行时git commit,Git会打包索引中的所有内容,然后将其用作新快照。由于它已经处于冻结格式,并且已经过重复数据删除,因此Git不必做很多额外的工作。

这也说明了所有未跟踪的文件。未跟踪的文件是位于您的工作树中但不在 Git索引中的文件现在。在这种状态下文件如何结束都无关紧要。也许您将其从计算机上的其他位置复制到了工作树中。也许您是在这里新鲜创建的。也许有在Git的指数的副本,但你删除的副本git rm --cached。一种或另一种方式是,您的工作树中有一个副本,但是Git的索引中没有副本。如果您现在进行新的提交,则该文件将不在新的提交中。

请注意,git checkout最初从您签出的提交中填写 Git的索引。因此索引开始与提交匹配。Git也从同一来源填充您的工作树。因此,最初,所有三个匹配。现在,当您在工作树及其文件中更改文件时git add,索引和工作树便已匹配。然后运行git commit,Git从索引进行新的提交,现在所有三个再次匹配。

因为Git从索引进行新的提交,所以我们可以这样写: Git的索引保存了您计划进行的下一次提交。 这忽略了Git索引在冲突合并期间承担的扩展角色,但是无论如何我们现在还是想忽略它。:-)

这就是所有内容,但仍然相当复杂!这特别棘手,因为没有简单的方法可以准确查看Git索引中的内容。3 但是有,告诉你这是怎么回事,在某种程度上这是非常有用的,并且该命令是一个Git命令git status


2从技术上讲,这实际上根本不是副本。而是对经过Git验证的文件(已进行重复数据删除)和所有内容引用。这里还有更多的东西,例如模式,文件名,暂存号以及一些可使Git快速运行的缓存数据。但是,除非您开始使用Git的某些低级命令(尤其是其中的命令)git ls-files --stage,否则您git update-index只能将其视为副本。

3git ls-files --stage命令将向您显示Git索引中每个文件的名称和暂存号,但是通常这并不是很有用。


git status

git status命令实际上是通过git diff为您运行两个单独的命令来工作的(并且还做一些其他有用的事情,例如告诉您所处的分支)。

第一个git diff将当前提交(记住,一直冻结)与Git索引中的内容进行比较。对于相同的文件,Git将什么也不说。对于不同的文件,Git会告诉您该文件已准备提交。这包括全新的文件-如果提交中没有提交sub.py,但是索引中确实包含sub.py该文件,则将添加该文件-以及所有已提交但未提交的已删除文件。不再索引(git rm,也许)。

第二个git diff将Git索引中的所有文件与工作树中的文件进行比较。对于相同的文件,Git什么也没说。对于不同的文件,Git会告诉您该文件未准备提交。与第一个差异不同,此特定列表包括全新的文件:如果文件untracked存在于您的工作树中,但不存在于Git的索引中,则Git会将其添加到未跟踪的文件列表中。4

最后,在列表中累积了这些未跟踪的文件后,它们git status也会宣布这些文件的名称,但是有一个特殊的例外:如果文件中列出了文件的名称,则会.gitignore取消最后一个列表的显示。 请注意,用a列出跟踪文件(在Git索引中.gitignore的文件)在这里无效:文件在索引中,因此即使它在中列出,它也会被比较并提交.gitignore。忽略文件仅禁止“未跟踪的文件”投诉。5


4使用简短版本git status—时git status -s,未跟踪的文件不是分开的,但原理是相同的。像这样累积文件git status有时通过仅打印目录名称来汇总未跟踪文件的名称。要获取完整列表,请使用git status -uallgit status -u

5清单文件也使得EN-集体增加了许多文件操作,如git add .git add *跳过未跟踪文件。这部分会变得有些复杂,因为您可以git add --force用来添加通常会被跳过的文件。还有其他一些通常较小的特殊情况,所有这些加起来就是:文件.gitignore可能被更正确地调用,.git-do-not-complain-about-these-untracked-files-and-do-not-auto-add-them或者同样难以处理。但是,这太荒谬了.gitignore


git add -ugit commit -a

这里有一些方便的快捷方式:

  • git add .所有更新文件添加到当前目录和任何子目录中。尊重您.gitignore,因此,如果当前未跟踪的文件没有被投诉git status,它将不会自动添加。

  • git add -u在工作树中的任何位置自动添加所有更新的文件。6 这仅影响跟踪的文件。请注意,如果您已经删除了工作树副本,这也将删除索引副本(作为其一部分使索引与工作树匹配)。git add

  • git add -A就像git add .从工作树的顶层运行一样(但请参见脚注6)。

除此之外,您还可以运行git commit -a,大约等于7git add -u然后再运行git commit。也就是说,这使您获得与Mercurial相同的行为。

我通常建议不要使用这种git commit -a模式:我发现最好git status经常使用,仔细查看输出,如果状态不是您期望的那样,请弄清楚为什么会这样。使用git commit -a,很容易意外修改文件并提交您不打算提交的更改。但这主要取决于品味/意见。


6如果您的Git版本早于Git 2.0,请注意此处:git add -u仅适用于当前目录和子目录,因此您必须首先爬到工作树的顶层。该git add -A选项有类似的问题。

7我说大致相等,因为git commit -a实际上是通过创建一个额外的索引并使用该其他索引来进行提交而起作用。如果提交有效,您将获得与执行相同的效果git add -u && git commit。如果提交不起作用(如果使Git以多种方式中的任何一种跳过提交),则此后将不对文件进行git add-ed操作,因为Git会丢弃临时的额外索引并返回使用主索引。

如果git commit --only在这里使用,还会带来其他并发症。在这种情况下,Git创建了第三个索引,事情变得非常棘手,特别是如果您使用预提交挂钩。这是使用单独git add操作的另一个原因。


1

我看到@Ben Jackson和@Tapashee Tabassum Urmi提到的使用舞台来缩小提交的要点,有时我将其用于此目的,但是我主要使用它来放大我的提交!这是我的观点:

假设我想添加一个小的功能,它需要几个较小的步骤。我认为单独提交较小的步骤并充斥我的时间表没有任何意义。但是,我想保存每个步骤,并在必要时返回,

我只是将较小的步骤相互叠加,当我觉得值得提交时,我就提交了。这样,我从时间轴中删除了不必要的提交,但能够撤消(签出)最后一步。

我看到了执行此操作的其他方法(简化git历史记录),您可以根据自己的喜好使用:

  1. git amend(更改您的最后一次提交),这不是您想要用于此特定目的的东西(我认为它主要是在执行错误的提交,然后修复它)
  2. git rebase,这是事后才想到的,可能会给您和使用存储库的其他人带来严重的问题。
  3. 创建一个临时分支,合并然后再删除它(这也是一个不错的选择,需要更多步骤,但可以提供更多控制权)

0

就像一个复选框,它提供了选择要提交的文件的功能。

例如,如果我已经编辑fileA.txtfileB.txt。但是我只想提交更改fileA.txt。因为我还没有完成fileB.txt

我可以简单地使用git add fileA.txt和提交,git commit -m "changed fileA.txt"然后继续使用fileB.txt,完成后我可以fileB.txt轻松地提交

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.