What does git add --intent-to-add or -N do and whe

2020-03-01 03:00发布

On git add -h I can see the following option:

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

But I don't understand when should this option be used. What does this option really do, and how it should be used?

标签: git
4条回答
不美不萌又怎样
2楼-- · 2020-03-01 03:24

Enable diffing of untracked files

Blue112's answer is partially correct. git add --intent-to-add does indeed add an empty file to the staging area/index for each specified untracked file in your working copy, but one of the main purposes of this is to enable you to use git diff with files that haven't been added to the Git repository yet by diffing your untracked file against its empty version in the staging area:

$ 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

Once you've diffed the file, you can add the non-empty version to the staging area/index by simply doing a normal git add:

$ git add foo.txt

Enable git commit -a of untracked files

Likewise, since --intent-to-add makes untracked files "known" to Git by adding empty versions of those files to the staging area/index, it also allows you to use git commit --all or git commit -a to commit those files along with your known modified files, which is something that you wouldn't be able to do otherwise.

As explained in the official Linux Kernel documentation for git commit:

using the -a [or --all] switch with the commit command [will] automatically "add" changes from all known files (i.e. all files that are already listed in the index)...and then perform the actual commit

Documentation

From the official Linux Kernel git add documentation:

-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.

查看更多
我想做一个坏孩纸
3楼-- · 2020-03-01 03:31

It is mainly used to add an empty file for your commits.

More informations at Git with intent to add!.

查看更多
4楼-- · 2020-03-01 03:38

user456814's answer explains very well what git add -N is useful for. I just want to give a more detailed explanation of what's going on in the background.

You can think of git as maintaining a history of file changes like creations, modifications, and deletions (let's call them deltas). The deltas are pretty self-explanatory up until the most recent commit, but things get more complicated when you're working in your project to prepare a new commit to go on top. There are three different types of deltas when you're in this situation.

(Side note: most people, when first introduced to git, are taught something like "committing in git takes two steps; first you do git add and then you can do git commit". While this is true, it only brings attention to the Changes to be committed type of delta. Understanding git add -N requires understanding the other types of deltas as well.)

#1: Changes to be committed

Commonly called "staged changes", these deltas appear at the top when you run git status, if there are any. If your shell supports color, they will be green.

When you git add a file, it gets promoted into this category. These are the changes that will actually be included if you run git commit without any flags.

#2: Changes not staged for commit

These deltas appear second when you run git status, if there are any. If your shell supports color, they will be red.

These are changes that you have made to files in the git repository that have not yet been committed AND have not been moved to #1. When you edit a file and then save, it appears in this category by default.

For a file to show up in this category, it has to EITHER already exist in the most recent commit, OR be added if the changes in #1 were to be committed. Otherwise, it will show up in category #3.

(Note: because you choose when to "promote" a file into category #1, it's possible to have the same file show up in both #1 and #2. For example, I could see

modified:   abc.txt

in green in #1, and

modified:   abc.txt

in red in #2 at the same time. This can happen if I promote a file to #1, then later make some more changes to it. The entry in #1 references the delta that I made before promoting the file, maybe adding a new line of code, and the entry in #2 references the delta that I made afterwards, maybe adding a comment at the top. If I were more organized, I would have made all the changes before promoting the file to #1, in order to avoid this situation altogether.)

#3: Untracked files

These deltas appear last when you run git status, if there are any. If your shell supports color, they will be red.

These are all the files that are not in the most recent commit AND not in #1. While technically a delta in the sense that adding it would change the commit, it's possible that the file has just always been there and people just don't want git to record any information about it. (In this case, you should add the file to .gitignore, and it will stop showing up in git status.)

When you create a brand-new file, it shows up in this category.

So what does all this have to do with git add -N?

git add -N is all about making it easier to work with #3 deltas. As referenced in the accepted answer above, git diff lets you see what deltas you have actually prepared. Here is a good set of commands that work with git diff.

git diff only shows the differences between #1 and #2 (i.e. the deltas in #2).

git diff --staged only shows the deltas in #1.

git diff HEAD only shows the deltas in #1 and #2, put together.

Notice that none of these commands even look at #3. However, by running git add -N, you basically do the following:

  • Split a new file into two deltas: just file creation, and filling the file with text/content
  • git add the "file creation" delta into #1

This has the effect of making the second delta appear in #2. Now the new file is completely out of #3, and you can use git diff commands with it.

As for git commit -a, essentially what it does is:

  • git add everything in #2 so that it is also staged with everything in #1
  • git commit (which takes everything in #1, including the stuff that was just added, and creates an actual commit from it)

Without git add -N, this command misses your new file in #3; however, you can see that after running git add -N, your new file is spread across #1 and #2, and will get included in the commit.


Well, I've explained everything I want to explain. If you want to check your understanding, you can follow along with the example below:

  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
    
查看更多
在下西门庆
5楼-- · 2020-03-01 03:45

Note that before git 2.10 (Q3 2016), git add -N can sometime skips some entries.

See commit 6d6a782, commit c041d54, commit 378932d, commit f9e7d9f (16 Jul 2016) by Nguyễn Thái Ngọc Duy (pclouds).
(Merged by Junio C Hamano -- gitster -- in commit 3cc75c1, 25 Jul 2016)

If you have:

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

And you add -N everything... then subdir files are not recorded as i-t-a ("intended to add") entries.

cache-tree.c: fix i-t-a entry skipping directory updates sometimes

Commit 3cf773e (cache-tree: fix writing cache-tree when CE_REMOVE is present - 2012-12-16 - Git v1.8.1.1) skips i-t-a entries when building trees objects from the index. Unfortunately it may skip too much.

If subdir/file1 is an i-t-a, because of the broken condition in this code, we still think "subdir" is an i-t-a file and not writing "subdir" down and jump to the-last-file.
The result tree now only has two items: a-file and the-last-file.
subdir should be there too (even though it only records two sub-entries, file2 and file3).


git status has improved, with Git 2.17 (Q2 2018, four years laters): after moving a path in the working tree (hence making it appear "removed") and then adding with the -N option (hence making that appear "added"), git status detected it as a rename, but did not report the old and new pathnames correctly.

See commit 176ea74, commit 5134ccd, commit ea56f97, commit 98bc94e, commit 06dba2b, commit 6de5aaf (27 Dec 2017) by Nguyễn Thái Ngọc Duy (pclouds).
Helped-by: Igor Djordjevic (boogisha).
(Merged by Junio C Hamano -- gitster -- in commit 12accdc, 27 Feb 2018).

Reminder: ita or i-t-a stands for "intended-to-add", what -N does.

wt-status.c: handle worktree renames

Before 425a28e (diff-lib: allow ita entries treated as "not yet exist in index" - 2016-10-24, Git 2.11.0-rc0) there are never "new files" in the index, which essentially disables rename detection because we only detect renames when a new file appears in a diff pair.

After that commit, an i-t-a entry can appear as a new file in "git diff-files". But the diff callback function in wt-status.c does not handle this case and produces incorrect status output.

查看更多
登录 后发表回答