Olius
Olius

Reputation: 282

Is there a way to `git commit --patch --only`?

Say I’ve been staging changes using git add, but then realize that there is some small change—consisting of a single hunk, let’s say—I should commit separately before committing the staged changes. If this change is in an unstaged file path, all I need to do is git commit path. If however it is in a file which contains changes which are already staged, I intuitively thought something like git commit -po would allow me to interactively select changes to commit, while also ignoring staged changes, but instead I get the following:

$ git commit -po
fatal: Only one of --include/--only/--all/--interactive/--patch can be used.

Is there a relatively simple shortcut which allows me to achieve what I want, without having for example to commit the staged changes to a temporary branch and then merge?

Upvotes: 3

Views: 95

Answers (4)

jthill
jthill

Reputation: 60487

There's only three usual places to store content for a path: the work tree, the index entry, and the committed tree. git commit -o applies the work tree content to a new commit.

You need a fourth set of content for the path: the committed content but with selected changes from your work tree, and Git's --patch options are set up to produce their partial staging in the index entry, but Git's --only option is set up for whole-file slices from the work tree, so easiest is to save and reset the index, do git commit -p, then restore the index.

Brute-force method using git's content-tracker core commands git write-tree and git read-tree to save and restore the index:

index=`git write-tree`      # save the current index
git reset -q                # reset it to clean checkout
git commit -p $thatpath     # commit -p with just that path
git read-tree $index        # restore the index

edit after getting a night's sleep: use a sideband index file. This will preserve inflight merge conflicts or git add -ns in the current index that can't be committed as-is.

temp=$(git rev-parse --git-dir)/scratchindex
GIT_INDEX_FILE=$temp git reset -q
GIT_INDEX_FILE=$temp git commit -p $thatpath

Upvotes: 2

LeGEC
LeGEC

Reputation: 52081

You can do the following sequence of actions:

  1. git commit (create the "complete" commit)
  2. git reset HEAD~ (move back one step)
  3. git add -p ...; git commit (create the small commit)
  4. git restore -S -s <sha> -- . where <sha> is the commit you created at step 1 (you may find it in git reflog)

Also worth mentioning: you can use the standard git gui tool (comes distributed with git) to do step 2 and 3 with a GUI (even step 1 actually).

You would tick the "amend commit" checkbox which appears to the right, below the file view and over the "commit message" text area, and you can then:

  • stage or un stage complete files from the left panes,
  • select and stage or un stage lines or block of lines from the right pane

The GUI is stock Tcl/Tk and looks a bit clunky, but I came to find it really useful for reviewing and editing the changes I want to commit.

Upvotes: 1

Olius
Olius

Reputation: 282

Not ideal but does exactly what is wanted:

  1. git commit -p small change (along with already staged changes)
  2. git stash
  3. git reset HEAD^
  4. git commit -p small change
  5. git add -u
  6. git stash pop

We end up where we started, with the same material staged and unstaged, except the small change is committed.

Upvotes: 0

dani-vta
dani-vta

Reputation: 7130

You could use git restore combined with the --staged and --patch options to selectively unstage the hunks of changes that have been added to the index by mistake.

git restore --staged --patch <file>

After that, you could stage the remaining changes and record them with a separate commit as you intended.

Upvotes: 1

Related Questions