1

The problem is that I cannot apparently make a partial commit of four lines' worth of changes in a file, out of about all 200 or so changed lines in that file. It might be due to the command I mistakenly did and then supposedly corrected. I am an experienced git user and I have done this sort of task before, but I always did it without making a mistake previously.

Here is what I did.

I have a file with 3 or 4 changes to it, including one that I think might be desirable even if the other changes are too experimental.

I also have some renamed files, so the renames are staged.

I did the usual git add -p file step and verified that git diff --cached file showed exactly the change I wanted to check in.

I accidentally did git commit -m "Message" (omitting the file name I should have included) and it committed the (staged) renames, which was not desired, as well as file.

Then I backed that commit out with git reset HEAD~1 --soft. This canceled the commit, but left file in a staged state, with ALL the changes, not just the add -p changes, as shown by git diff --cached file.

Because I didn't want all the changes staged for commit yet, I reset the file with git reset HEAD file. At this point git status and git diff seem to show that I am back to the state I was in before the git add -p file command.

The problem is that, at this point, if I do the following with only the desired change accepted during the add,

git add -p file
git commit file -m "Message"

The status shows all the changes to file getting committed. The line counts show way too many lines after the commit is completed.

I how do I only commit what I chose during git add -p file now that it seems like I am blocked from doing so?

Commentary: Ordinarily I don't have staged renames, so ordinarily I don't have to try to specify the file I want to commit. I think that the meaning of git commit file is not the meaning I need for my purpose. From reading the man page, I think what it's trying to say is that it doesn't matter what is staged for file when you do git commit file, it will commit that file's complete set of changes. Also git commit -p file will do the right thing for file after letting me pick the change interactively, but it will also commit the unrelated renamings that are staged.

Romain Valeri
  • 19,645
  • 3
  • 36
  • 61
cardiff space man
  • 1,442
  • 14
  • 31
  • 2
    Commits store *entire files*, not changes. Thinking of commits as storing changes is what has led you down this garden path. Each commit stores the *entire set of all files*, intact. To let you see changes, Git extracts *two* commits and compares them. – torek Mar 07 '19 at 19:50
  • @torek If you do some research and simple experiments you'll learn that GIT should not be described as "storing entire files" because it's a misleading oversimplification. In any case, if a thing stores my changed file, it stores my changes. – cardiff space man Mar 07 '19 at 21:53
  • 1
    @cardiffspaceman I know he's too humble to say that himself but please do check torek's activity, namely on git tag. I do *not* appeal to any "authority" of his, I'm just pointing out that suggesting to him to "do some research and simple experiments" is sarcatic at best, but probably insulting in light of the situation. And by the way git *does* store files as blobs. Each version has a blob with all content in this specific version. But again, I'm open to correct my misconceptions. – Romain Valeri Mar 07 '19 at 22:34
  • It was a very small act of patience on my part to respond to torek firmly briefly and factually. I disagree that I was down a garden path. If git was storing the actual output of diff(1) or if it was storing entire copies of every version of every file, it still was the case that `git add -p file` is unexpectedly overridden by `git commit file` and that is the essential information. – cardiff space man Mar 07 '19 at 23:10

2 Answers2

4

Edit: for a practical approach, as opposed to the background, see RomainValeri's answer, which I've upvoted. Note that git reset defaults to mixed; it will restore the old file names to your index, so that comparing index and work-tree, Git will think that some files are removed and created under new names. If the contents of the removed-but-created-under-new-names files are similar enough, Git will call that "renamed".

(You can also use git stash, though I generally recommend avoiding git stash. What git stash does is to make two new commits, holding the current index state and the current work-tree state, that are not on any branch. It then resets the index and work-tree a la git reset --hard. You can later restore these two commits to your index and work-tree, but be sure to use git stash apply --index or git stash pop --index when doing so. Without --index, git stash ignores the saved index!)


The mistake is to mention file in git commit file -m "Message".

Putting a file name in git commit is equivalent to asking for git commit --only, as described in the git commit documentation, though in my opinion, not all that well.

The precise details are complicated, but, to (over?)simplify it a bit and put it in a more explicable way, git commit --only file(s) is roughly equivalent to:

mv .git/index .git/index-save   # shelve your proposed commit away
git reset --mixed               # go back to the current commit
git add file(s)                 # set up new proposed commit
git commit                      # now MAKE that commit
mv .git/index-save .git/index   # restore the earlier proposed commit
git add file(s)                 # overwrite the same files in it as before

This is somewhat similar to, but clearly more complicated than, git commit --include file(s), which is roughly equivalent to:

git add file(s)                 # update the proposed commit
git commit                      # make the commit

Note that either one destroys the third version of file that you had carefully set up using git add -p. Remember, there are, at all times, three active copies of each file:

  • the frozen one in HEAD, which you can see with git show HEAD:file,
  • the malleable but Git-ified one in the index, which you can see with git show :file, and
  • the ordinary one in your work-tree, which you can see and work with in the ordinary way, without using Git at all.

What git add does is to copy the work-tree file into the index, overwriting whatever was there before. What git add -p does is selectively update the one in the index, by comparing it to the one in the work-tree, showing you each difference, and allowing you to patch the index copy, one piece at a time. So git add -p builds an index copy that is different from both the HEAD copy and the work-tree copy.

Note that the index already contains a copy of every other file as well. That's what makes it the new proposed commit: at all times, it has all files ready to go.

torek
  • 448,244
  • 59
  • 642
  • 775
2

Unless something else in your context makes it impractical, why not just

git reset HEAD
git add -p file
git commit -m "Message"

Of course, afterwards you'd have to re-add the renamed files and other changes to the index for the next commit, but it seems the most straightforward course of action here.

Romain Valeri
  • 19,645
  • 3
  • 36
  • 61
  • What does the reset do to the pending (staged) renames? If nothing, then the commit will commit those. If it reverses the renames, then what happens to the edits that those files have since being renamed? – cardiff space man Mar 07 '19 at 20:10
  • @cardiffspaceman I'm not familiar enough yet with the renaming logic in git, I'll trust you on this and go read a bit more on the subject :-) (edit ...and I just read torek's answer. Glad that renames are adressed. I feel like a broken record thanking him...) .... thank you? – Romain Valeri Mar 07 '19 at 20:14
  • @cardiffspaceman: Git doesn't store the fact of the renames. It just stores the new content under the new name. If the new content matches the old content, but the two names differ, Git can, later, discover that easily and *report* it as a rename. But it didn't *store* it as a "rename this file" operation. It just stored the new content of the new file. – torek Mar 07 '19 at 22:03
  • The current status is that there's a display from `git status`, and an effect of `git commit`, that the next `git commit` will change the names of the files and not do anything else, even though the working versions of the renamed files are changed since the renaming. So if `git reset HEAD` will leave me with no sign of the renaming in the status then afterwards, I can only stage the renamed files along with their changes, and the renaming can't be committed as a separate commit after the `git reset HEAD`. – cardiff space man Mar 07 '19 at 22:28
  • If / when you use `git reset`, you'll grab the original (old file name + old contents) copy of the file from the `HEAD` commit and copy that into the index. Now that `HEAD` and proposed-next-commit both have a file named, say, `foo.py`, Git won't look for some *other, new* file that's *similar to* `foo.py` but under another name, so it won't say `foo.py renamed to bar.py`. If and when you *re-remove* `foo.py` from the index (but it's in `HEAD` after a new commit), and there's a `bar.py` in the index that's sufficiently similar, Git will once again say "renamed". – torek Mar 07 '19 at 23:14
  • Again, all this flows from the fact that Git doesn't store *changes*. It only stores *files* —whole files, wholly intact. At various times, Git compares the sets of files (in two commits, or in one commit and one proposed commit, for instance) and finds which files have the same names, which ones don't have any matching names but there's a *new* file that looks suspiciously similar, and so on. That's how Git detects renames. – torek Mar 07 '19 at 23:16
  • I think I see what you're saying but there's a detail I need to have visible. git reset HEAD will change the index and the working copy to the HEAD commit, so the full effect of `git mv file2a file2b` will be reversed: The index change and the working copy change. If I understand the man page properly. – cardiff space man Mar 07 '19 at 23:49
  • The thing that I meant to emphasize in the last comment is the "and the working copy" part. But it's too late to edit the comment for this emphasis. – cardiff space man Mar 08 '19 at 00:03