25

In git, is there any (simple) way to modify the index so that only changes to files which are already in it are added? It sounds kind of complicated, but what I want to achieve is simple.

Lets say my index looks like this (slightly stripped git status output):

# Changes to be committed:
#       modified:   A
#       modified:   B
#
# Changed but not updated:
#       modified:   B
#       modified:   C
#
# Untracked files:
#       D

Some changes to B are in the index, some aren't. C is not staged at all.

How can I update B in the index (stage its unstaged changes) without adding C?

I.e. I would like for the index to look like this:

# Changes to be committed:
#       modified:   A
#       modified:   B
#
# Changed but not updated:
#       modified:   C
#
# Untracked files:
#       D

In this simple case it can of course be achieved with a simple git add B, but I would like to know if there's a simple answer for the general case. I tried git add --refresh, but if I understand correctly, that only updates stat info.

axelarge
  • 947
  • 1
  • 9
  • 11
  • Can you show a real use-case where you'd want to do that? I can't imagine any (when I add a file and than modify it more, it does not automatically mean I'll want to add those changes too, especially when I won't want to add changes in some other files). – Jan Hudec Apr 04 '12 at 08:34
  • @JanHudec I probably wouldn't need this if I was stricter and touched only those files that should go in the commit (`B`). However sometimes I get carried away and start making changes that should be in a new commit (`C`), so I add `B` to the index. Before commiting I always review diff --cached and sometimes clean something up in `B`. and that's when I need this. I guess it could probably be solved in another way, say with stash – axelarge Apr 04 '12 at 09:09
  • My point is, that when you are doing something in file `X` that should go in commit `B` and get carried away and do changes that should go in commit `C`, some of those changes are often again to file `X`. In which case you want to add -i individual hunks manually and not just re-add file `X`. Obviously if you get carried away, you probably noticed something around the point you were changing, which is why it's unlikely the extra changes don't touch the files the fist set does. – Jan Hudec Apr 04 '12 at 09:39

2 Answers2

36

The following command will update the index to contain the other changes in B that has not been staged yet:

git update-index --again
rtn
  • 127,556
  • 20
  • 111
  • 121
1

I don't know of a completely trivial way to do this, but:

git status --porcelain

will show file B (and only B) as state "MM", so:

git status --porcelain | grep ^MM | cut -d' ' -f 2

will produce a list of such files.

There's no harm in "re-adding" A, though.

You can also use git diff-index --cached --name-status HEAD. (Might need this if your git is too old to have git status --porcelain.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks, I didn't know about --porcelain for status. – axelarge Apr 04 '12 at 09:14
  • It's currently the same as -s / --short, but promises to continue to be machine-parse-able, which is important if you stick it in some script. :-) – torek Apr 04 '12 at 09:28
  • I tried `git status --porcelain | grep ^[AM]M | cut -d' ' -f 2- | xargs git add`, but it fails with filenames that contain spaces. I guess this could be seen as another question, but maybe you can help? With xargs -0 it doesn't work at all – axelarge Apr 04 '12 at 09:54
  • @axelarge: add `tr '\n' '\0'` in the pipe and then use `xargs -0` – hroptatyr Apr 04 '12 at 10:06
  • Ugh, spaces. I deliberately ignored them above. :-) Without using `-z` here, git encodes spaces with quoting. This works for at least one case: `git status --porcelain | grep ^MM | sed 's/...//' | while read var; do eval git add $var; done` but I don't know if it will work for all (using `eval` like this does *not* give me warm fuzzies...). – torek Apr 04 '12 at 10:07
  • @hroptatyr: that won't quite work because git prints `"E F"` (with the double quotes) for the file named e-space-f. `git status --porcelain -z` can be used, but then the simple grep fails. – torek Apr 04 '12 at 10:10
  • Oh, also, watch out when using square brackets as in `^[AM]M` since the shell will then attempt to expand them to match a file named `^AM` for instance. (Just put quotes around the grep expression.) – torek Apr 04 '12 at 10:12
  • @torek Thanks for all the help. This seems to work, do you see any problems? (I'm not exactly an expert on all the unix tools) `git status --porcelain | grep '^[AM]M' | cut -d' ' -f 2- | sed 's/ /\\ /' | xargs git add` – axelarge Apr 04 '12 at 10:16
  • I was surprised at first that what amounts to `git add E\\\ F` works to add the file whose shell-level name is just `E\ F`, but it's presumably because of the `fnmatch` call git uses. You can also `git add \\A` even if there is only a file named `A` (not `\A`). But, I'd leave out the last `sed`, it's not needed (try using `xargs ls -l` instead of `git add` for instance). But, this does not work with a file named `"`. (Using `| while read -r var; do eval git add $var; done` works with the file named `"`. Not that I like using `eval`.) – torek Apr 04 '12 at 10:39