git add --intent-to-add或-N是什么,何时应该使用?

42

git add -h 命令中,我可以看到以下选项:

-N, --intent-to-add   record only the fact that the path will be added later

但我不明白应该在什么情况下使用这个选项。这个选项到底是做什么的,应该如何使用它?


仅记录路径稍后将被添加的事实。按照典型的Git风格,帮助文本并不是很清晰,尽管官方文档解释得有点更好(但也不是非常好)。 - user456814
4个回答

43

启用未跟踪文件的差异比较

Blue112的回答部分正确。 git add --intent-to-add确实会为工作副本中每个指定的未跟踪文件添加一个空文件到暂存区/索引中,但其中一个主要目的是使您能够使用git diff来比较尚未添加到Git存储库中的文件,通过将未跟踪的文件与其在暂存区中的空版本进行比较:

$ echo foo > foo.txt
$ git diff foo.txt

$ git add --intent-to-add foo.txt
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   foo.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   foo.txt

$ git diff --staged foo.txt
diff --git a/foo.txt b/foo.txt
new file mode 100644
index 0000000..e69de29

$ git diff foo.txt
diff --git a/foo.txt b/foo.txt
index e69de29..257cc56 100644
--- a/foo.txt
+++ b/foo.txt
@@ -0,0 +1 @@
+foo

一旦您已经对文件进行了差异比较,您可以通过简单地执行普通的git add命令将非空版本添加到暂存区/索引中:

$ git add foo.txt

启用git commit -a提交未跟踪的文件

同样地,由于--intent-to-add可以通过向暂存区/索引添加空文件来使未跟踪的文件“已知”于Git,因此它还允许您使用git commit --allgit commit -a提交这些文件以及已知修改过的文件,否则您将无法执行此操作。

正如官方Linux内核文档中关于git commit所述

使用commit命令的-a [或--all]选项会自动从所有已知文件(即已在索引中列出的所有文件)中“添加”更改......然后执行实际提交

文档

来自官方Linux内核git add文档

-N
--intent-to-add

Record only the fact that the path will be added later. An entry for the path is placed in the index with no content. This is useful for, among other things, showing the unstaged content of such files with git diff and committing them with git commit -a.


2
你的答案是正确的,也很有道理,所以加一。然而,--intent-to-add 对我来说似乎没有用处。比如我有一个新文件 foo.txt,里面有 100 行代码。当我运行 git add -N foo.txt,再运行 git diff foo.txt,我会看到添加了 100 行代码。(这好像相当于剪切文件的所有内容,添加文件,然后将其粘贴回文件中。)现在当我进行更改——比如添加一行——并再次运行 git diff,我会看到添加了 101 行代码。因此,在舞台上添加一个空文件路径对于查看差异并没有帮助。 - chharvey
1
@chharvey 这取决于你的工作流程。你可以通过暂存文件并忽略这个新选项来获得你想要的行为。当我输入 git diff 时,我喜欢看到我写过的 所有 代码,这样我就可以在提交和打开 PR 之前在一个地方自我审查所有内容。 - Philip
9
我经常在交互模式下打补丁,如果没有使用-N选项,则无法对新添加的文件打补丁。 - WloHu
我有三个文件test1 test2 test3,其中test1已经被提交并且被修改过,test2是通过git add添加的,而test3则是通过git add -N添加的。当我运行git commit --all时,这三个文件都会被包含在提交中。因此,我不明白你所说的“启用git commit --all”的意思。 - Olsgaard
1
@Olsgaard 作者的意思是 git add -N 命令可以让你使用 git commit -a 命令提交一个未被追踪的文件,即使在执行了 git add -N 命令之后,该文件仍然是未被追踪的。否则,你将无法通过 git commit -a 命令提交未被追踪的文件。 - Fab1n

12

user456814's answer很好地解释了git add -N的作用。我只是想更详细地解释一下背后发生了什么。

您可以将git视为维护文件更改历史记录,例如创建、修改和删除(我们称之为增量)。这些增量在最近的提交之前都很容易理解,但是当您在项目中工作以准备新的提交时,情况会变得更加复杂。在此情况下,有三种不同类型的增量。

(顺便说一句:大多数人初次接触git时,被教导的是“在git中提交需要两个步骤;首先执行git add,然后才能执行git commit”。虽然这是正确的,但它只关注了要提交的更改类型。git add -N需要理解其他类型的增量。)

#1:要提交的更改

通常称为“暂存更改”,如果有任何更改,则会在运行git status时出现在顶部。如果您的shell支持颜色,则它们将呈绿色。

当您git add一个文件时,它会被提升到该类别。这些是在不带任何标志的情况下运行git commit时实际包含的更改。

#2:未暂存的更改

如果有的话,这些变化会在你运行git status时出现在第二个位置。如果你的shell支持颜色,它们将会是红色的。

这些是你对git仓库中的文件所做的更改,尚未提交且未移动到#1。当你编辑一个文件并保存后,默认情况下它会出现在此类别中。

为了使文件出现在这个类别中,它必须已经存在于最近的提交中,或者如果在#1中的更改要被提交,则需要添加。否则,它将出现在第三类别中。

(注意:因为你可以选择何时将文件“提升”到第一类别,所以同一个文件可能会同时出现在#1和#2中。例如,我可以看到

modified:   abc.txt

在#1中以绿色显示,并且

modified:   abc.txt

当文件同时在 #1 和 #2 中出现时,会以红色显示。这种情况可能发生在我将文件提升到 #1 后,稍后又对其进行了一些更改。#1 中的条目引用了我提升文件之前所做的更改差异,例如添加了一行代码,而 #2 中的条目引用了我随后所做的更改差异,例如在顶部添加了注释。如果我更有组织性,我会在将文件提升到 #1 之前先进行所有更改,以完全避免这种情况的出现。

#3:未跟踪的文件

如果存在任何未跟踪的文件,这些差异将在运行 git status 最后显示出来,并且如果您的shell支持颜色,则会以红色显示。

这些都是不在最新提交中且不在 #1 中的所有文件。虽然从技术上讲,它们是差异,因为添加它们会更改提交,但有可能文件一直存在,只是人们不希望 git 记录有关它的任何信息。(在这种情况下,您应该将该文件添加到.gitignore中,它将停止在 git status 中显示。)

当您创建全新的文件时,它将显示在此类别中。

那么所有这些与 git add -N 有什么关系呢?

git add -N的目的是使处理 #3 差异变得更加容易。正如上面接受的答案所引用的那样,git diff让您查看实际准备好的差异。这里有一组与 git diff 一起使用的良好命令。

git diff只显示#1和#2之间的差异(即#2中的增量)。

git diff --staged仅显示#1中的增量。

git diff HEAD仅显示将#1和#2合并后的增量。

请注意,这些命令都没有查看#3。但是,通过运行git add -N,您基本上执行以下操作:

  • 将新文件分成两个增量:仅为文件创建和填充文件的文本/内容
  • git add“文件创建”增量到#1中

这会使第二个增量出现在#2中。现在,新文件完全不在#3中,您可以使用git diff命令进行操作。

至于git commit -a,它实际上做的是:

  • git add #2中的所有内容,以便与#1中的所有内容一起进行暂存
  • git commit(从#1中获取所有内容,包括刚刚添加的内容,并创建一个实际的提交)

如果没有git add -N,则此命令会忽略#3中的新文件;但是,您可以看到在运行git add -N后,您的新文件已分布在#1和#2中,并将包含在提交中。


好的,我已经解释了所有我想要解释的内容。如果你想检查自己的理解,可以跟着下面的例子走:

  1. I make a new git repo.

    sh-4.1$ cd ~/Desktop
    sh-4.1$ mkdir git-demo
    sh-4.1$ cd git-demo
    sh-4.1$ git init
    Initialized empty Git repository in /local/home/Michael/Desktop/git-demo/.git/
    
  2. git status shows me this repo is empty.

    sh-4.1$ git status
    On branch master
    
    Initial commit
    
    nothing to commit (create/copy files and use "git add" to track)
    
  3. I make some new files.

    sh-4.1$ echo "this is the abc file" > abc.txt
    sh-4.1$ echo "this is the def file" > def.txt
    sh-4.1$ echo "this is the ghi file" > ghi.txt
    
  4. git status shows me all the new files are currently in category #3.

    sh-4.1$ git status
    On branch master
    
    Initial commit
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    
        abc.txt
        def.txt
        ghi.txt
    
    nothing added to commit but untracked files present (use "git add" to track)
    
  5. git diff does nothing, since it doesn't operate on #3.

    sh-4.1$ git diff
    
  6. I commit one file, add another one, and add -N the third one.

    sh-4.1$ git add abc.txt && git commit -m "some commit message"
    [master (root-commit) 442c173] some commit message
     1 file changed, 1 insertion(+)
     create mode 100644 abc.txt
    sh-4.1$ git add def.txt
    sh-4.1$ git add -N ghi.txt
    
  7. In git status, abc.txt no longer shows up, since it has already been committed. def.txt only shows up in category #1 since the whole file has been added. ghi.txt shows up in categories #1 and #2.

    sh-4.1$ git status
    On branch master
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
    
        new file:   def.txt
        new file:   ghi.txt
    
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
        modified:   ghi.txt
    
  8. By running git diff, I can show all the deltas listed in #2. The only delta that shows up is that I added a line to ghi.txt.

    sh-4.1$ git diff
    diff --git a/ghi.txt b/ghi.txt
    index e69de29..8a8dee2 100644
    --- a/ghi.txt
    +++ b/ghi.txt
    @@ -0,0 +1 @@
    +this is the ghi file
    
  9. By running git diff --staged, I can show all the deltas listed in #1. Three of them show up: creating a new file def.txt, adding a line in def.txt, and creating a new file ghi.txt. Even though there are 2 deltas for def.txt, the file name itself is only output one time in example 7 above, to avoid clutter.

    sh-4.1$ git diff --staged
    diff --git a/def.txt b/def.txt
    new file mode 100644
    index 0000000..48baf27
    --- /dev/null
    +++ b/def.txt
    @@ -0,0 +1 @@
    +this is the def file
    diff --git a/ghi.txt b/ghi.txt
    new file mode 100644
    index 0000000..e69de29
    

1
很好的例子和解释。+1 - VonC
当你可以简单地使用git add file && git diff --staged时,使用git add -N file && git diff有何意义?我仍然不太明白使用-N的意义。 - tmillr

3
请注意,在 git 2.10(2016年Q3)之前,git add -N 有时会跳过某些条目。

请查看由Nguyễn Thái Ngọc Duy (pclouds)于2016年7月16日提交的提交6d6a782, 提交c041d54, 提交378932d, 提交f9e7d9f
(于2016年7月25日由Junio C Hamano -- gitster --合并至提交3cc75c1)

如果您有:

a-file
subdir/file1
subdir/file2
subdir/file3
the-last-file

你需要添加-N参数来添加所有文件...然后subdir中的文件不会被记录为i-t-a(“打算添加”)条目。

cache-tree.c:修复有时跳过目录更新的i-t-a条目问题

提交 3cf773ecache-tree:修复当存在CE_REMOVE时写入缓存树的问题-2012年12月16日-Git v1.8.1.1)在从索引构建树对象时跳过i-t-a条目。不幸的是,它可能会跳过太多。

如果subdir/file1是i-t-a,由于此代码中的错误条件,我们仍然认为“subdir”是i-t-a文件,而没有将“subdir”写下并跳到最后一个文件。
结果树现在只有两个项目:a-filethe-last-file
subdir也应该在那里(即使它只记录了两个子条目file2file3)。


git status在Git 2.17(2018年第二季度,四年后)得到了改进:在工作树中移动路径(因此使其看起来像是“已删除”),然后使用-N选项添加它(因此使其看起来像是“已添加”),git status将其检测为重命名,但没有正确报告旧和新的路径名。

请查看 提交 176ea74, 提交 5134ccd, 提交 ea56f97, 提交 98bc94e, 提交 06dba2b, 提交 6de5aaf (2017年12月27日) 由 Nguyễn Thái Ngọc Duy (pclouds) 提交。
协助者: Igor Djordjevic (boogisha)
(由 Junio C Hamano -- gitster --提交 12accdc 中合并,于2018年2月27日)

提醒: itai-t-a 表示 "intended-to-add",即 -N 的作用。

wt-status.c: 处理工作树重命名

425a28e (diff-lib: 允许 ita 条目被视为 "尚未存在于索引中" - 2016-10-24, Git 2.11.0-rc0) 之前,索引中从来没有 "新文件",这基本上禁用了重命名检测,因为我们只有在 diff 对中出现新文件时才会检测到重命名。

在那个提交之后,一个 i-t-a 条目可以出现在 "git diff-files" 中作为新文件。但是 wt-status.c 中的 diff 回调函数不处理这种情况,并生成不正确的状态输出。


3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接