如果 file.txt 没有暂存,`git commit file.txt` 会做啥?

Posted

技术标签:

【中文标题】如果 file.txt 没有暂存,`git commit file.txt` 会做啥?【英文标题】:What does `git commit file.txt` do if file.txt is not staged?如果 file.txt 没有暂存,`git commit file.txt` 会做什么? 【发布时间】:2020-12-23 06:58:47 【问题描述】:

我有一个文件夹,其中除了一个文件 (file.txt) 之外的所有文件都添加到了暂存区。 此时,我运行命令git commit file.txt会发生什么?

git 会在提交前自动添加到暂存区吗? 究竟是什么承诺?只是文件还是所有文件?

【问题讨论】:

【参考方案1】:

正如https://git-scm.com/docs/git-commit 解释的那样,提交是对file.txt 的更改(无论它们是否已暂存),并且任何其他文件的状态(暂存或未暂存)都不会更改。

由于分阶段的更改将包含在提交中,因此提交的文件将不再有分阶段的更改。

在对许多文件进行暂存更改后,您可以通过为 git commit 提供路径名来更改记录更改的顺序。当给出路径名时,该命令会进行一次提交,只记录对命名路径所做的更改:

$ edit hello.c hello.h
$ git add hello.c hello.h
$ edit Makefile
$ git commit Makefile

这会进行一次提交,记录对 Makefile 的修改。为 hello.c 和 hello.h 暂存的更改不包含在生成的提交中。但是,它们的变化并没有丢失 —— 它们仍然在上演,只是被阻止了。在上述顺序之后,如果你这样做:

$ git commit

第二次提交将按预期记录对 hello.c 和 hello.h 的更改。

提交将是对file.txt 所做的更改。在我的测试中,即使我已将更改暂存到 c1.txt,但暂存和未暂存的更改都作为我提交的一部分提交。这是有道理的,因为在不同阶段所做的更改都是一次性提交的,并且提交后没有区别。

$ git init ./
Initialized empty Git ...
$ echo a > a1.txt
$ echo b > b1.txt
$ echo c > c1.txt
$ git add --all
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   a1.txt
    new file:   b1.txt
    new file:   c1.txt

$ echo 'more c' >> c1.txt
$ git commit c1.txt

(editor comes up)

[master (root-commit) 2849446] c1.txt
 1 file changed, 2 insertions(+)
 create mode 100644 c1.txt
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   a1.txt
    new file:   b1.txt

【讨论】:

【参考方案2】:

Git 的文档在这里不是很好。缺少的是对 Git 首先对 git commit 做了什么的准确解释,这是在 git commit --only file.txtgit commit --include file.txt 之前需要的。

您可能已经知道 Git 提交是编号的,具有随机外观的 散列 ID 而不是简单的序列号。每个提交的哈希 ID 实际上是该提交的全部内容的加密校验和,这就是为什么提交中的任何内容都无法真正更改的关键原因,即使是 Git 本身也无法更改。

您可能不知道——但应该知道——每次提交都会保存 Git 知道的每个文件的完整快照。也就是说,当您运行 git commit 时,Git 会生成这些快照之一。该快照成为提交的主要数据。提交还包含元数据,包括您的姓名和电子邮件地址,以及上一次提交的提交编号(哈希 ID)。

我们在这里讨论的有趣部分是短语Git 知道的文件。那些是什么文件?我们稍后会谈到这一点,但请考虑一下上述两个事实的含义:

任何提交都不能更改; 提交保存快照。

这意味着提交内部的文件是只读的。它们以特殊的仅 Git 格式存储,经过压缩和重复数据删除。重复数据删除处理了许多提交只是一遍又一遍地重复使用相同文件的事实。仅限 Git 的格式被永久冻结,因此重新使用文件是安全的,这些文件实际上无法更改。但这也意味着这些文件不能用于做新的工作

要实际工作,您需要每个文件的常规、普通、读/写副本。 Git 将从提交中提取提交的文件,并将这些提取的文件放入这些常规的读/写副本中。这些普通文件副本存在于 Git 所称的 working treework-tree 中。

您可能认为这些是 Git 知道的文件,但事实并非如此。其他系统会这样做,但 Git 不会。取而代之的是,Git 保留每个文件的 第三个​​副本,在 Git 所称的 index暂存区 或 - 很少有这些天——缓存。当你签出一些提交时,Git 会填充它的索引——它保存冻结、重复数据删除和仅 Git 格式的文件——和你的工作树。由于已经删除了重复数据,因此索引中与当前提交中的文件匹配的所有“副本”都不使用空间。1

您通常需要使用的 git add 命令告诉 Git:使该文件的索引副本与工作树副本匹配。这意味着此时,Git 将工作树副本压缩为 Git 化的、准备提交的形式,如果是重复的,则对其进行重复删除。无论哪种方式,该文件现在都可以提交了,并且在索引中:如果它之前在索引中,那么现在该文件的不同 version 在索引中,如果它不是t 之前在索引中,现在是。

所以,一个典型的git commit,没有额外的选项或参数,只是打包当时索引中的任何内容以用作新快照。它还收集元数据所需的所有内容,例如您的姓名、当前时间和日志消息。然后,commit 命令将所有这些打包成一个新的提交。

Git 知道的文件正是那些在 Git 索引中的文件。实际上,索引充当您提议的下一次提交。这就是 Git 将其称为 暂存区的原因: 下一次提交将对 Git 索引中的任何内容进行快照。


1文件的索引“副本”确实会占用一些空间:一些字节用于保存文件名、文件模式、缓存数据和内部 blob 哈希 ID。长度是可变的,取决于文件名。


git commit 的额外选项

短语索引意味着只有一个Git索引。那是……几乎是真的。 ? 实际上,每个工作树都有一个主索引,或者更准确地说,每个工作树都有一个主索引(因为您可以使用 git worktree add 添加更多工作树)。但是当你运行git commit 时,Git 可以创建一个临时索引,甚至两个。

Git 创建这个或两个临时索引的方式取决于您提供的选项。命令:

git commit --only file.txt

或:

git commit file.txt

(意思相同)会:

创建一个保存当前提交的临时索引,就好像您刚刚签出该提交一样;2file.txt 的工作树副本添加到该临时索引; 创建第二个临时索引来保存当前索引,并将file.txt 的工作树副本添加到临时索引中; 使用first临时索引来创建一个新的提交;和 如果可行,请将普通索引(“the”索引/暂存区)替换为 second 临时索引。

最终结果是新提交包含与之前提交相同的文件,除了替换或添加了file.txt。如果这样可行,Git 会像运行 git add file.txt 一样继续运行,因为第二个临时索引等于运行 git add file.txt 的结果。如果你告诉 Git not 毕竟要进行提交——有几种方法可以做到这一点,包括使用预提交挂钩——Git 会丢弃两个临时索引文件,看起来好像你从来没有Git 运行 git add file.txt

使用git commit --include 时,Git 只创建一个临时索引,而不是两个。临时索引作为主索引的副本开始,然后 Git 使用临时索引执行 git add,并尝试使用临时索引提交。如果一切顺利,临时索引成为主索引。如果没有,Git 会删除临时索引,并且设置看起来 Git 从未运行过 git add

请注意,git commit -a 相当于运行 git commit --include 并列出 Git 知道的每个文件。也就是说,Git 创建这个临时索引,然后用它做一个git add -u 并尝试提交。


2如果您没有当前提交(例如在新的空存储库中的情况),Git 会创建一个空的临时索引。


这看起来非常复杂

不幸的是,它有点复杂。但这正是 Git 真正做的事情,我们需要了解这里的所有点点滴滴,以解释为什么结果是这样的,包括在你最终中止提交的情况下。

不过,如果它有助于记住这一切,请记住 git commit 通常使用 索引,但是当使用 --include--only-a 时,它会产生一些额外的并使用它们,然后,如果一切顺利,让它看起来它没有做任何事情。然后咨询the documentation 以更详细地记住每个临时索引文件中的内容。

【讨论】:

是的,您的答案非常复杂,而这正是我要寻找的。阅读后,我的理解是,file.txt 被暂存在不同的索引上,然后作为独立提交添加。结果是它超越了提交层次结构中的其他文件,而其他文件却没有注意到刚刚发生的事情。到目前为止我是正确的吗? 不确定你所说的“独立”提交是什么意思,但是是的:假设你使用了--only(或者让它默认为--only),新的提交是只添加了命令行上命名的文件到不同的(临时)索引。如果一切顺利,后面的常规索引也更新了。 “独立”是指它自己的提交。 但所有提交都是“自己的”:每个提交都包含一个快照(其数据)和一组元数据(作者、提交者、日志消息、父提交哈希 ID 列表等)。

以上是关于如果 file.txt 没有暂存,`git commit file.txt` 会做啥?的主要内容,如果未能解决你的问题,请参考以下文章

GitGit 基础命令 ( 添加暂存文件 git add | 提交文件至版本库 git commit | 查看版本库状态 git status | 查询文件修改 git diff )

有没有办法让 Git 暂存/取消暂存文件而不是更改?

Git中的文件夹列为没有扩展名的文件

在 git 中暂存文件?

TortoiseGIT

Git 暂存文件列表