git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH] add commit --interactive

From
Junio C Hamano <junkio@cox.net>
Date
Mar 2, 2007, 06:06 UTC
Message-ID
<7v649ki3h1.fsf@assigned-by-dhcp.cox.net>
In-Reply-To
<es6bgl$t3f$1@sea.gmane.org>

I am guilty of introducing "add --interactive" and am somewhat regretting it.

Why?

Because it encourages a wrong workflow, but the word "wrong" needs to be clarified.

The workflow assumed by "add --interactive" (hence by its wrapper, "commit --interactive") goes like this:

 1. You have some large task.  You start hacking and keep hacking
    and hacking.
 2. Your working tree might still be a messy state, but there are
    some good bits in the mess that are worth committing on their
    own, excluding the other changes in the working tree.  This
    could happen for a number of reasons:
    - You might be actually done with the change and your working
      tree is perfect, but the change since the HEAD commit is
      too big to be a single logical change.  You want to split
      the changes into smaller steps.
    - You might be still in the middle of what you initially
      wanted to achieve, but at the same time you found
      something worth changing, which is independently useful
      regardless of what you are doing.  It may be a typo.  It
      may be a bug that might or might not affect what you are
      doing.  But as long as you found it and fixed it, you
      would want to make a separate commit.
 3. So you would fire up "add --interactive", and isolate the
    "good bits" by updating the index selectively to prepare for
    commit.  You would run "diff --cached" to review the change
    you are going to commit makes sense, and then finally make a
    commit.

I am not saying the first two steps are wrong. Everybody does that all the time.

What's wrong is the approach "add --interactive" takes in the last step. The isolation of "good bits" happens in the index, not in the working tree --- which is largely because git is designed to commit the state recorded in the index --- but that encourages the user to commit something that has never been in the working tree, so by definition what is committed in step 3 never could have been even tested. And that is what I feel is very wrong. People in the past claimed that they later would go back to each individual commit and test them, but that is simply hard to believe, because it is tedious in practice.

We should be able to do better than that. Instead of the step 3 in the above, we could have a way to quickly stash the working tree state, reset the working tree to that of the last commit (or whatever commit we want to build a new commit on top of), and selectively apply parts of the the changes we stashed to the working tree. Then we can test the state in the working tree, and run "commit -a" as usual. What's still left in the stashed state can incrementally be trickled in to make further commits.

Probably the operation would go like this. I'll describe them in terms of lower level operations, and leave the scripting to others.

* Stashing the current state
        git commit -a -m 'stashed'
	git tag -f stash
        git reset --hard HEAD^
  After this point, "diff stash^ stash" is the changes yet
  to be applied.
* Switch to the commit you want to apply part of what was
  stashed to.  This part is optional if you are only building on
  top of the current HEAD, but if you have a small but urgent
  bugfix you might want to switch to another branch (say,
  'maint').  There is no magic command needed -- just run
	git checkout maint
  as usual, for example.
* Trickle some of the stashed changes in, perhaps interactively:
	git pick stash
  would probably give an interactive UI similar to "add -i" to
  let you pick and choose from "git show stash" (either per-hunk
  or per-path), but unlike "add -i", which applies the chosen
  change only to the index, it applies the change to the working
  tree and to the index.  As the last step of its operation,
  "git pick" would save the resulting index state as a tree:
	git tag -f pre-fixup `git-write-tree`
* Review and test the change in the working tree.  This does not
  need any special command.  You may also make fix-up in the
  working tree as usual.  Then you would make a commit, perhaps
  using the usual "git commit".
* Then this is the tricky and interesting part.  We need to
  subtract the change we already used to advance HEAD from
  "stashed changes".  I do not think we currently have a single
  command to do this step, but it would probably go like this.
  1. First, we build a tree that is stash^ plus the changes
     we committed right now.
	git read-tree -m -u stash^
	git merge-recursive HEAD^ -- stash^ HEAD
	NEW_TREE=`git write-tree`
        NEW_BASE=`echo stash | git commit-tree $NEW_TREE`
     This would make our working tree and the index match the
     state you would have had, had you started from the original
     state and made only the changes you have committed just
     now.  Note that the resulting tree contains the manual
     fix-ups you made since pre-fixup.
  2. The tree state you would eventually want to have used to be
     in stash but that lacks the manual fix-up you made, which
     is the difference between pre-fixup and HEAD.  So let's
     update the final state with this difference:
	git read-tree -m -u stash
        git merge-recursive pre-fixup -- stash HEAD
	NEW_TREE=`git write-tree`
  3. The strategy is to keep stash pointed at a commit that has
     the tree state of what you eventually would want to have
     (which is now in $NEW_TREE), and represent the changes yet
     to be applied as "diff stash^ stash".  So let's update the
     stash tag:
	NEW_STASH=`echo stash | git commit-tree $NEW_TREE -p $NEW_BASE`
	git tag -f stash $NEW_STASH
* You just finished one step, and still have remainder that can
  be seen from "git show stash".  Go back to "Switch to the
  commit" step (or "Trickle some of the stashed changes" step)
  above and repeat.  The output from "git show stash" will keep
  shrinking, and when it becomes empty you are done.
Previous: Paolo BonziniNext: David Kågedal
Message 2 of 3 in “add commit --interactive”
  1. add commit --interactivePaolo Bonzini, Mar 1, 2007
  2. Junio C HamanoMar 2, 2007
  3. David KågedalMar 2, 2007

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.