如何在git中仅对新文件的部分进行暂存?

259

我喜欢使用git add --interactive,它是我日常工作流程的一部分。

问题似乎在于它不能处理未跟踪的文件。我想要的是跟踪一个新文件,但只添加其中的一部分,也就是说,这个新文件的某些部分还没有准备好被提交。

例如,使用git add -i,我可以选择打补丁(patch)选项,甚至可以编辑单个块(hunks)以便提交代码的部分内容,同时保留未提交的调试代码注释。我喜欢这种方式工作,因为它明确了我目前正在处理哪些需要继续修改的地方。

不幸的是,我似乎无法对未跟踪的文件执行相同的操作。要么我提交整个文件,要么什么都不提交。我一直在使用的解决方法是在文件为空时提交或者甚至提交一个新文件,然后按照通常的方式逐个提交更改。但是这个解决方案感觉像是一种肮脏的黑客技巧,当我忘记或改变主意时,它会带来比应该有的更多的麻烦。

因此,问题是:如何只提交一个新文件的一部分,以便跟踪这个新文件,同时保留未提交的全部或部分内容?

5个回答

505

哇,所有的update-indexhash-object操作似乎过于复杂了。那就用这个代替吧:

git add -N new_file
git add -i  # or 'git add -p' if you prefer

来自git help 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.

11
++1!应该给予应有的赞扬。我太过自信,认为我已经熟知这些手册页了....</惭愧> - sehe
2
找到了。我应该再往下滚一下手册页。也许我应该退回 Git Pro 徽章。 - dave1010
4
为什么git-gui不内部使用这个功能来使得可以将新文件的行加入暂存区。 - Deiwin
9
很棒的 -N 发现,谢谢!尽管在这种情况下我更喜欢将其与 -p 结合起来。 gid add -N the_filegit add -p。应该补充说明 -N 也适用于 -A - Cyril CHAPON
-N 对我不起作用,但是 --intent-to-add 行得通。 - Zarepheth
显示剩余2条评论

9
git update-index --add --cacheinfo 100644 $(git hash-object -w /dev/null) newfile
git add --interactive newfile

简单演示:

mkdir /tmp/demo
cd /tmp/demo
git init .

echo hello > newfile
git update-index --add --cacheinfo 100644 $(git hash-object -w /dev/null) newfile
  • 提示 如果你确定"空"blob已经存在于你的git对象数据库中,你可以硬编码哈希e69de29bb2d1d6434b8b29ae775ad8c2e48c5391。但我不建议这样做。
  • 提示 如果你在Windows上,你可能只需要使用NUL:而不是/dev/null。否则,使用类似echo -n '' | git hash-object --stdin -w的命令。

现在索引将包含newfile作为空blob,并且如果它之前不存在,则空blob已被输入到对象数据库中:

$ find .git/objects/ -type f 
.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391

$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#   new file:   newfile
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   newfile
#

$ git diff
diff --git a/newfile b/newfile
index e69de29..ce01362 100644
--- a/newfile
+++ b/newfile
@@ -0,0 +1 @@
+hello

这应该正是你想要的。我还可以推荐使用 vim fugitive 插件进行非常智能的索引管理(见 Better git add -p? )。


1
这似乎运行良好。我将其别名为“stage-empty-file”,因为它有点复杂难记。 - dave1010
@dave1010:很高兴能帮到你!_是的,我考虑过添加别名,但我不知道你如何处理例如通配符或一般的多个文件名参数。所以我选择不这样做_。 - sehe

7

最简单的做法(以及交互式暂存的一般做法)是使用 git gui。它附带在 git 中,应该可以在几乎所有受 git 支持的平台上工作。

只需运行 git gui,一个图形用户界面将打开,允许对已跟踪和未跟踪文件的 hunk 甚至单个行进行临时暂存。

Git Gui screenshot


这是一个有创意的答案,有时非常非常有用。 - Pablo Olmos de Aguilera C.
3
请注意,并非所有的 Git 发行版都包含 git gui。例如,在 Debian 中,它位于名为 'git-gui' 的单独软件包中。 - cristoper
3
git gui中,我从来没有成功过单独暂存未跟踪文件的行。实际上,这使得这些菜单选项无效。 - Deiwin
3
我会尽力进行翻译,以下是需要翻译的内容:@Deiwin,实际上我在问题中可能漏掉了这一点 - 我的屏幕截图显示已经被跟踪的文件。如果你想对一个新文件使用 git gui,你必须首先运行 git add -N new_file - 参见Richard Hansen的答案。 - Chronial
@Chronial,我知道--intend-to-add技巧,但它也有其限制。您可以在git gui中暂存单个行,但无法取消暂存单个行。 - Deiwin
显示剩余2条评论

3

编辑:现在似乎不起作用了。我确定之前可以(在git 1.7.1上)。如果它不起作用,我建议像sehe上面建议的那样将/dev/null暂存:

git update-index --add --cacheinfo 100644 $(git hash-object -w /dev/null) newfile

如果你使用的是 Windows 操作系统(没有 /dev/null),那么你可以将其替换为指向一个空文件的路径。 原始回答: 你需要
git add -p # (or --patch)

这会为我添加未跟踪的文件。来自手册页面:

在索引和工作树之间交互地选择补丁块,并将它们添加到索引中。这使用户有机会在将修改后的内容添加到索引之前查看差异。

这实际上运行了add --interactive命令,但绕过了初始命令菜单,直接跳转到patch子命令。有关详细信息,请参见“交互模式”。


2
然而,这对我并没有用(git 1.7.4); git add -p newfile 根本不起作用(无效),而 git add --interactive '[a]dd untracked' 的效果与 git add newfile 相同; 编辑 还使用 git 1.7.5.3 进行了检查; 没有结果。 - sehe
Windows的好提示,在我的答案中添加了相关提示。谢谢。 - sehe

2
仅供参考,还有另一种可能性: 我使用。
git add -N file
git add -p file

然后您可以分阶段提交代码块,或直接在原地进行编辑。


1
这个回答与被接受的回答相同,而那个回答可以追溯到2011年。请点踩。 - Stanislav Pankevich
4
被接受的回答将 add -Nadd -i 结合使用。我只是补充了使用 add -p 而不是 add -i 也可以的信息。 - Pedro Adame Vergara
1
Richard在回答发布3年后添加了“-p”注释。这是公平竞争。+1 - Błażej Michalik

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