{"thread":{"id":"7036","subject":"[PATCH] add commit --interactive","startedAt":"2007-03-01T10:56:21Z","lastAt":"2007-03-02T12:47:17Z","messageCount":3,"participants":["Paolo Bonzini","Junio C Hamano","David Kågedal"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"35962","messageId":"es6bgl$t3f$1@sea.gmane.org","threadId":"7036","inReplyTo":null,"subject":"[PATCH] add commit --interactive","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2007-03-01T10:56:21Z","receivedAt":"2007-03-01T10:56:21Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"The --interactive option behaves like \"git commit\", except that\n\"git add --interactive\" is executed before committing.  It is\nincompatible with -a and -i.\n\nPaolo\n\n\n* git-commit: add a --interactive option.\n    \nThe --interactive option behaves like \"git commit\", except that\n\"git add --interactive\" is executed before committing.  It is\nincompatible with -a and -i.\n\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 2187eee..3a4d456 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -8,8 +8,9 @@ git-commit - Record changes to the repository\n SYNOPSIS\n --------\n [verse]\n-'git-commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg> |\n-\t    --amend] [--no-verify] [-e] [--author <author>]\n+'git-commit' [-a | --interactive] [-s] [-v]\n+\t   [(-c | -C) <commit> | -F <file> | -m <msg> | --amend]\n+\t   [--no-verify] [-e] [--author <author>]\n \t   [--] [[-i | -o ]<file>...]\n \n DESCRIPTION\n@@ -35,6 +36,10 @@ methods:\n    before, and to automatically \"rm\" files that have been\n    removed from the working tree, and perform the actual commit.\n \n+5. by using the --interactive switch with the 'commit' command to invoke \"add\"\n+   in interactive mode, which lets the user decide one by one which files\n+   to add or remove.\n+\n The gitlink:git-status[1] command can be used to obtain a\n summary of what is included by any of the above for the next\n commit by giving the same set of parameters you would give to\ndiff --git a/git-commit.sh b/git-commit.sh\nindex be3677c..e8ec3b9 100755\n--- a/git-commit.sh\n+++ b/git-commit.sh\n@@ -3,7 +3,7 @@\n # Copyright (c) 2005 Linus Torvalds\n # Copyright (c) 2006 Junio C Hamano\n \n-USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [[-i | -o] <path>...]'\n+USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [[-i | -o] <path>...]'\n SUBDIRECTORY_OK=Yes\n . git-sh-setup\n require_work_tree\n@@ -71,6 +71,7 @@ trap '\n \n all=\n also=\n+interactive=\n only=\n logfile=\n use_commit=\n@@ -131,6 +132,11 @@ do\n \t\talso=t\n \t\tshift\n \t\t;;\n+\t--int|--inte|--inter|--intera|--interac|--interact|--interacti|\\\n+\t--interactiv|--interactive)\n+\t\tinteractive=t\n+\t\tshift\n+\t\t;;\n \t-o|--o|--on|--onl|--only)\n \t\tonly=t\n \t\tshift\n@@ -304,12 +310,14 @@ case \"$#,$also,$only,$amend\" in\n \t;;\n esac\n unset only\n-case \"$all,$also,$#\" in\n-t,t,*)\n-\tdie \"Cannot use -a and -i at the same time.\" ;;\n+case \"$all,$interactive,$also,$#\" in\n+*t,*t,*)\n+\tdie \"Cannot use -a, --interactive or -i at the same time.\" ;;\n t,,[1-9]*)\n \tdie \"Paths with -a does not make sense.\" ;;\n-,t,0)\n+,t,[1-9]*)\n+\tdie \"Paths with --interactive does not make sense.\" ;;\n+,,t,0)\n \tdie \"No paths with -i does not make sense.\" ;;\n esac\n \n@@ -344,6 +352,9 @@ t,)\n \t) || exit\n \t;;\n ,)\n+\tif test \"$interactive\" = t; then\n+\t\tgit add --interactive || exit\n+\tfi\n \tcase \"$#\" in\n \t0)\n \t\t;; # commit as-is\n"},{"id":"36040","messageId":"7v649ki3h1.fsf@assigned-by-dhcp.cox.net","threadId":"7036","inReplyTo":"es6bgl$t3f$1@sea.gmane.org","subject":"Re: [PATCH] add commit --interactive","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-02T06:06:34Z","receivedAt":"2007-03-02T06:06:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I am guilty of introducing \"add --interactive\" and am somewhat\nregretting it.\n\nWhy?\n\nBecause it encourages a wrong workflow, but the word \"wrong\"\nneeds to be clarified.\n\nThe workflow assumed by \"add --interactive\" (hence by its\nwrapper, \"commit --interactive\") goes like this:\n\n 1. You have some large task.  You start hacking and keep hacking\n    and hacking.\n\n 2. Your working tree might still be a messy state, but there are\n    some good bits in the mess that are worth committing on their\n    own, excluding the other changes in the working tree.  This\n    could happen for a number of reasons:\n\n    - You might be actually done with the change and your working\n      tree is perfect, but the change since the HEAD commit is\n      too big to be a single logical change.  You want to split\n      the changes into smaller steps.\n\n    - You might be still in the middle of what you initially\n      wanted to achieve, but at the same time you found\n      something worth changing, which is independently useful\n      regardless of what you are doing.  It may be a typo.  It\n      may be a bug that might or might not affect what you are\n      doing.  But as long as you found it and fixed it, you\n      would want to make a separate commit.\n\n 3. So you would fire up \"add --interactive\", and isolate the\n    \"good bits\" by updating the index selectively to prepare for\n    commit.  You would run \"diff --cached\" to review the change\n    you are going to commit makes sense, and then finally make a\n    commit.\n\nI am not saying the first two steps are wrong.  Everybody does\nthat all the time.\n\nWhat's wrong is the approach \"add --interactive\" takes in the\nlast step.  The isolation of \"good bits\" happens in the index,\nnot in the working tree --- which is largely because git is\ndesigned to commit the state recorded in the index --- but that\nencourages the user to commit something that has never been in\nthe working tree, so by definition what is committed in step 3\nnever could have been even tested.  And that is what I feel is\nvery wrong.  People in the past claimed that they later would go\nback to each individual commit and test them, but that is simply\nhard to believe, because it is tedious in practice.\n\nWe should be able to do better than that.  Instead of the step 3\nin the above, we could have a way to quickly stash the working\ntree state, reset the working tree to that of the last commit\n(or whatever commit we want to build a new commit on top of),\nand selectively apply parts of the the changes we stashed to the\nworking tree.  Then we can test the state in the working tree,\nand run \"commit -a\" as usual.  What's still left in the stashed\nstate can incrementally be trickled in to make further commits.\n\nProbably the operation would go like this.  I'll describe them\nin terms of lower level operations, and leave the scripting to\nothers.\n\n* Stashing the current state\n\n        git commit -a -m 'stashed'\n\tgit tag -f stash\n        git reset --hard HEAD^\n\n  After this point, \"diff stash^ stash\" is the changes yet\n  to be applied.\n\n* Switch to the commit you want to apply part of what was\n  stashed to.  This part is optional if you are only building on\n  top of the current HEAD, but if you have a small but urgent\n  bugfix you might want to switch to another branch (say,\n  'maint').  There is no magic command needed -- just run\n\n\tgit checkout maint\n\n  as usual, for example.\n\n* Trickle some of the stashed changes in, perhaps interactively:\n\n\tgit pick stash\n\n  would probably give an interactive UI similar to \"add -i\" to\n  let you pick and choose from \"git show stash\" (either per-hunk\n  or per-path), but unlike \"add -i\", which applies the chosen\n  change only to the index, it applies the change to the working\n  tree and to the index.  As the last step of its operation,\n  \"git pick\" would save the resulting index state as a tree:\n\n\tgit tag -f pre-fixup `git-write-tree`\n\n* Review and test the change in the working tree.  This does not\n  need any special command.  You may also make fix-up in the\n  working tree as usual.  Then you would make a commit, perhaps\n  using the usual \"git commit\".\n\n* Then this is the tricky and interesting part.  We need to\n  subtract the change we already used to advance HEAD from\n  \"stashed changes\".  I do not think we currently have a single\n  command to do this step, but it would probably go like this.\n\n  1. First, we build a tree that is stash^ plus the changes\n     we committed right now.\n\n\tgit read-tree -m -u stash^\n\tgit merge-recursive HEAD^ -- stash^ HEAD\n\tNEW_TREE=`git write-tree`\n        NEW_BASE=`echo stash | git commit-tree $NEW_TREE`\n\n     This would make our working tree and the index match the\n     state you would have had, had you started from the original\n     state and made only the changes you have committed just\n     now.  Note that the resulting tree contains the manual\n     fix-ups you made since pre-fixup.\n\n  2. The tree state you would eventually want to have used to be\n     in stash but that lacks the manual fix-up you made, which\n     is the difference between pre-fixup and HEAD.  So let's\n     update the final state with this difference:\n\n\tgit read-tree -m -u stash\n        git merge-recursive pre-fixup -- stash HEAD\n\tNEW_TREE=`git write-tree`\n\n  3. The strategy is to keep stash pointed at a commit that has\n     the tree state of what you eventually would want to have\n     (which is now in $NEW_TREE), and represent the changes yet\n     to be applied as \"diff stash^ stash\".  So let's update the\n     stash tag:\n\n\tNEW_STASH=`echo stash | git commit-tree $NEW_TREE -p $NEW_BASE`\n\tgit tag -f stash $NEW_STASH\n\n* You just finished one step, and still have remainder that can\n  be seen from \"git show stash\".  Go back to \"Switch to the\n  commit\" step (or \"Trickle some of the stashed changes\" step)\n  above and repeat.  The output from \"git show stash\" will keep\n  shrinking, and when it becomes empty you are done.\n"},{"id":"36083","messageId":"87hct3zuay.fsf@morpheus.local","threadId":"7036","inReplyTo":"7v649ki3h1.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] add commit --interactive","fromName":"David Kågedal","fromEmail":"davidk@lysator.liu.se","sentAt":"2007-03-02T12:47:17Z","receivedAt":"2007-03-02T12:47:17Z","isPatch":true,"sender":{"key":"davidk@lysator.liu.se","avatar":"https://avatars.githubusercontent.com/u/60530?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> Probably the operation would go like this.  I'll describe them\n> in terms of lower level operations, and leave the scripting to\n> others.\n>\n> * Stashing the current state\n\n[...]\n\n> * Switch to the commit you want to apply part of what was\n\n[...]\n\n> * Trickle some of the stashed changes in, perhaps interactively:\n\n[...]\n\n> * Review and test the change in the working tree.  This does not\n\n[...]\n\n> * Then this is the tricky and interesting part.  We need to\n>   subtract the change we already used to advance HEAD from\n>   \"stashed changes\".  I do not think we currently have a single\n>   command to do this step, but it would probably go like this.\n\nWhat I would want to do here is to simply restore the original \"stash\"\ntree, so something like \"git checkout stash -- .\" would probably work.\n\nThe reason is that what I would consider a common scenario is that I\nrealize that some of the changes I made should be commited first, and\nI extract them the way you describe, and maybe update them to erase\ntraces of things I don't want to commit, perhaps on the same source\nlines.\n\nBut then I want to continue where I was, and reintroduce the things I\ncleaned away in the previous step.\n\nThis is also much simpler to do.  So let's make two scripts\n\"git-stash\" and \"git-unstash\"\n\n    # git-stash:\n    git-commit -m \"temporary stash\"\n    git-tag stash\n    git-reset --hard HEAD^\n\n    # git-unstash:\n    git-checkout stash -- .\n    # or possibly:\n    #current=$(git-rev-parse HEAD)\n    #git-checkout stash\n    #git-reset --mixed $current\n    git-tag -d stash\n\nThen I would do this:\n\n  [ ... hack hack ... ]\n  $ git stash\n  $ git pick stash\n  [ ... cleanup ... ]\n  $ git commit -a -m \"first change\"\n  $ git unstash\n\n-- \nDavid Kågedal\n"}]}