{"thread":{"id":"20208","subject":"[PATCH] git-add -p: be able to undo a given hunk","startedAt":"2009-07-23T07:41:04Z","lastAt":"2009-08-19T10:11:34Z","messageCount":76,"participants":["Pierre Habouzit","Thomas Rast","Junio C Hamano","Nanako Shiraishi","Matthias Kestenholz","Jeff King","Bert Wesarg","Elijah Newren","Sverre Rabbelier","Nicolas Sebrecht"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"118564","messageId":"20090723074104.GI4750@laphroaig.corp","threadId":"20208","inReplyTo":null,"subject":"[PATCH] git-add -p: be able to undo a given hunk","fromName":"Pierre Habouzit","fromEmail":"madcoder@debian.org","sentAt":"2009-07-23T07:41:04Z","receivedAt":"2009-07-23T07:41:04Z","isPatch":true,"sender":{"key":"madcoder@debian.org","avatar":"https://avatars.githubusercontent.com/u/44708?v=4"},"body":"One of my most frequent use case for git-add -p is when I had an intense\ndebug session with quite a lot of debug() traces added. I then want only\nto select the hunks corresponding to the bugfixes and throw away the debug\nones.\n\nWith this new operation, instead of not staging hunks I don't want and\nwill eventually undo, I can just undo them.\n\nSigned-off-by: Pierre Habouzit <madcoder@debian.org>\n---\n\n    I reckon this is a tad late given we're already at -rc2, but that's\n    an itch that has scratched me for quite some time already, and I had\n    to scratch it today...\n\n    the change looks pretty safe to me though.\n\n    The only think that looks odd in the patch is the removal of the\n    if ($_->{USE}) clause from the TEXT copying loops, but\n    coalesce_overlapping_hunks already ensures that only ->{USE}d hunks\n    remain. I just have modified it to deal with ->{UNDO}ed hunks the\n    same way.\n\n Documentation/git-add.txt |    1 +\n git-add--interactive.perl |   38 ++++++++++++++++++++++++++++++--------\n 2 files changed, 31 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-add.txt b/Documentation/git-add.txt\nindex ab1943c..7b173dc 100644\n--- a/Documentation/git-add.txt\n+++ b/Documentation/git-add.txt\n@@ -254,6 +254,7 @@ patch::\n \n        y - stage this hunk\n        n - do not stage this hunk\n+       u - do not stage this hunk and revert it\n        q - quit, do not stage this hunk nor any of the remaining ones\n        a - stage this and all the remaining hunks in the file\n        d - do not stage this hunk nor any of the remaining hunks in the file\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex df9f231..945de9d 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -693,6 +693,7 @@ sub split_hunk {\n \t\t\tADDDEL => 0,\n \t\t\tPOSTCTX => 0,\n \t\t\tUSE => undef,\n+\t\t\tUNDO => undef,\n \t\t};\n \n \t\twhile (++$i < @$text) {\n@@ -835,12 +836,13 @@ sub merge_hunk {\n }\n \n sub coalesce_overlapping_hunks {\n+\tmy $field = shift;\n \tmy (@in) = @_;\n \tmy @out = ();\n \n \tmy ($last_o_ctx, $last_was_dirty);\n \n-\tfor (grep { $_->{USE} } @in) {\n+\tfor (grep { $_->{$field} } @in) {\n \t\tmy $text = $_->{TEXT};\n \t\tmy ($o_ofs) = parse_hunk_header($text->[0]);\n \t\tif (defined $last_o_ctx &&\n@@ -991,6 +993,7 @@ sub help_patch_cmd {\n \tprint colored $help_color, <<\\EOF ;\n y - stage this hunk\n n - do not stage this hunk\n+u - do not stage this hunk and revert it\n q - quit, do not stage this hunk nor any of the remaining ones\n a - stage this and all the remaining hunks in the file\n d - do not stage this hunk nor any of the remaining hunks in the file\n@@ -1140,7 +1143,7 @@ sub patch_update_file {\n \t\t}\n \t\tprint colored $prompt_color, 'Stage ',\n \t\t  ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),\n-\t\t  \" [y,n,q,a,d,/$other,?]? \";\n+\t\t  \" [y,n,u,q,a,d,/$other,?]? \";\n \t\tmy $line = prompt_single_character;\n \t\tif ($line) {\n \t\t\tif ($line =~ /^y/i) {\n@@ -1149,6 +1152,10 @@ sub patch_update_file {\n \t\t\telsif ($line =~ /^n/i) {\n \t\t\t\t$hunk[$ix]{USE} = 0;\n \t\t\t}\n+\t\t\telsif ($line =~ /^u/) {\n+\t\t\t\t$hunk[$ix]{USE} = 0;\n+\t\t\t\t$hunk[$ix]{UNDO} = 1;\n+\t\t\t}\n \t\t\telsif ($line =~ /^a/i) {\n \t\t\t\twhile ($ix < $num) {\n \t\t\t\t\tif (!defined $hunk[$ix]{USE}) {\n@@ -1301,14 +1308,14 @@ sub patch_update_file {\n \t\t}\n \t}\n \n-\t@hunk = coalesce_overlapping_hunks(@hunk);\n-\n \tmy $n_lofs = 0;\n \tmy @result = ();\n-\tfor (@hunk) {\n-\t\tif ($_->{USE}) {\n-\t\t\tpush @result, @{$_->{TEXT}};\n-\t\t}\n+\tmy @undo = ();\n+\tfor (coalesce_overlapping_hunks(\"USE\", @hunk)) {\n+\t\tpush @result, @{$_->{TEXT}};\n+\t}\n+\tfor (coalesce_overlapping_hunks(\"UNDO\", @hunk)) {\n+\t\tpush @undo, @{$_->{TEXT}};\n \t}\n \n \tif (@result) {\n@@ -1326,6 +1333,21 @@ sub patch_update_file {\n \t\trefresh();\n \t}\n \n+\tif (@undo) {\n+\t\tmy $fh;\n+\n+\t\topen $fh, '| git apply -R';\n+\t\tfor (@{$head->{TEXT}}, @undo) {\n+\t\t\tprint $fh $_;\n+\t\t}\n+\t\tif (!close $fh) {\n+\t\t\tfor (@{$head->{TEXT}}, @undo) {\n+\t\t\t\tprint STDERR $_;\n+\t\t\t}\n+\t\t}\n+\t\trefresh();\n+\t}\n+\n \tprint \"\\n\";\n \treturn $quit;\n }\n-- \n1.6.4.rc1.189.g9f628.dirty\n"},{"id":"118569","messageId":"200907231041.32567.trast@student.ethz.ch","threadId":"20208","inReplyTo":"20090723074104.GI4750@laphroaig.corp","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-23T08:41:31Z","receivedAt":"2009-07-23T08:41:31Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Pierre Habouzit wrote:\n> One of my most frequent use case for git-add -p is when I had an intense\n> debug session with quite a lot of debug() traces added. I then want only\n> to select the hunks corresponding to the bugfixes and throw away the debug\n> ones.\n> \n> With this new operation, instead of not staging hunks I don't want and\n> will eventually undo, I can just undo them.\n\nThis is what 'git checkout --interactive -- $file' or 'git reset\n--interactive --hard' would accomplish, if they existed.  I wonder if\nthere would be a way to avoid yet more command letters, and instead\nhave \"modes\" that affect what happens with hunks you said 'y' to.  For\nexample:\n\n  add -p\t\tapply --cached\n  undo -p\t\tapply -R\n  unstage -p\t\tapply -R --cached\n    [with hunks coming from diff --cached obviously]\n\n(I picked 'undo' and 'unstage' semi-randomly, but it's not, after all,\nan 'add' operation any more and the user doesn't need to know that the\nprogram doing this is in fact git-add--interactive.)\n\n> +       u - do not stage this hunk and revert it\n\nyou're overloading terminology a bit too much for my taste.  It has\nnothing to do with what git-revert does, and we shouldn't confuse\npeople more about that.\n\n>  Documentation/git-add.txt |    1 +\n>  git-add--interactive.perl |   38 ++++++++++++++++++++++++++++++--------\n\nTests?\n\n> @@ -693,6 +693,7 @@ sub split_hunk {\n>  \t\t\tADDDEL => 0,\n>  \t\t\tPOSTCTX => 0,\n>  \t\t\tUSE => undef,\n> +\t\t\tUNDO => undef,\n>  \t\t};\n\nWhy not fold this into a single field?  It could, say, take values 0,\n1, or '-R'.  It could probably be renamed to ACTION, but USE would be\nfine if you want to avoid the code churn.  Then you shouldn't need\n_completely_ separate handling during application.\n\n> @@ -1149,6 +1152,10 @@ sub patch_update_file {\n>  \t\t\telsif ($line =~ /^n/i) {\n>  \t\t\t\t$hunk[$ix]{USE} = 0;\n\n+\t\t\t\t$hunk[$ix]{UNDO} = 0;\n\nand similarly for [yad] too, on the grounds that the user can go back\nand change his choices with [KJ].  Of course that is not necessary if\nyou go the ACTION way outlined above.\n\n> +\tif (@undo) {\n> +\t\tmy $fh;\n> +\n> +\t\topen $fh, '| git apply -R';\n\nThis probably needs a --recount to cope with the case where the hunk\nheaders became stale/invalid through user [e]diting.\n\n> +\t\tfor (@{$head->{TEXT}}, @undo) {\n> +\t\t\tprint $fh $_;\n> +\t\t}\n> +\t\tif (!close $fh) {\n> +\t\t\tfor (@{$head->{TEXT}}, @undo) {\n> +\t\t\t\tprint STDERR $_;\n> +\t\t\t}\n> +\t\t}\n> +\t\trefresh();\n> +\t}\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"118570","messageId":"20090723085046.GK4750@laphroaig.corp","threadId":"20208","inReplyTo":"200907231041.32567.trast@student.ethz.ch","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Pierre Habouzit","fromEmail":"madcoder@madism.org","sentAt":"2009-07-23T08:50:47Z","receivedAt":"2009-07-23T08:50:47Z","isPatch":true,"sender":{"key":"madcoder@madism.org","avatar":null},"body":"On Thu, Jul 23, 2009 at 10:41:31AM +0200, Thomas Rast wrote:\n> Pierre Habouzit wrote:\n> > One of my most frequent use case for git-add -p is when I had an intense\n> > debug session with quite a lot of debug() traces added. I then want only\n> > to select the hunks corresponding to the bugfixes and throw away the debug\n> > ones.\n> > \n> > With this new operation, instead of not staging hunks I don't want and\n> > will eventually undo, I can just undo them.\n> \n> This is what 'git checkout --interactive -- $file' or 'git reset\n> --interactive --hard' would accomplish, if they existed.  I wonder if\n> there would be a way to avoid yet more command letters, and instead\n> have \"modes\" that affect what happens with hunks you said 'y' to.  For\n> example:\n> \n>   add -p\t\tapply --cached\n>   undo -p\t\tapply -R\n>   unstage -p\t\tapply -R --cached\n>     [with hunks coming from diff --cached obviously]\n> \n> (I picked 'undo' and 'unstage' semi-randomly, but it's not, after all,\n> an 'add' operation any more and the user doesn't need to know that the\n> program doing this is in fact git-add--interactive.)\n\nThe point is I want to do them at once, see my use case: I want to stage\nthe hunks from my bugfix, and remove the debugging bits of it at the\nsame time, I don't want to run two commands.\n\nI see git add -p as a triaging command, giving me the choice to do the\nthree things you can do with a hunk:\n  - stage it now (y)\n  - stage it later (n)\n  - never stage it, it was intermediate code, debug, whatever (u).\n\n> > +       u - do not stage this hunk and revert it\n> \n> you're overloading terminology a bit too much for my taste.  It has\n> nothing to do with what git-revert does, and we shouldn't confuse\n> people more about that.\n\nRight, revert is probably too overloaded, let it be \"forget\" or \"drop\"\ninstead if you want, I don't care much.\n\n> >  Documentation/git-add.txt |    1 +\n> >  git-add--interactive.perl |   38 ++++++++++++++++++++++++++++++--------\n> \n> Tests?\n\nRiight, will do :)\n\n> > @@ -693,6 +693,7 @@ sub split_hunk {\n> >  \t\t\tADDDEL => 0,\n> >  \t\t\tPOSTCTX => 0,\n> >  \t\t\tUSE => undef,\n> > +\t\t\tUNDO => undef,\n> >  \t\t};\n> \n> Why not fold this into a single field?  It could, say, take values 0,\n> 1, or '-R'.  It could probably be renamed to ACTION, but USE would be\n> fine if you want to avoid the code churn.  Then you shouldn't need\n> _completely_ separate handling during application.\n> \n> > @@ -1149,6 +1152,10 @@ sub patch_update_file {\n> >  \t\t\telsif ($line =~ /^n/i) {\n> >  \t\t\t\t$hunk[$ix]{USE} = 0;\n> \n> +\t\t\t\t$hunk[$ix]{UNDO} = 0;\n> \n> and similarly for [yad] too, on the grounds that the user can go back\n> and change his choices with [KJ].  Of course that is not necessary if\n> you go the ACTION way outlined above.\n\nI hesitated to do so, but yes it makes perfect sense.\n\n> > +\tif (@undo) {\n> > +\t\tmy $fh;\n> > +\n> > +\t\topen $fh, '| git apply -R';\n> \n> This probably needs a --recount to cope with the case where the hunk\n> headers became stale/invalid through user [e]diting.\n\nIndeed.\n\n-- \nIntersec <http://www.intersec.com>\nPierre Habouzit <pierre.habouzit@intersec.com>\nTél : +33 (0)1 5570 3346\nMob : +33 (0)6 1636 8131\nFax : +33 (0)1 5570 3332\n37 Rue Pierre Lhomme\n92400 Courbevoie\n"},{"id":"118604","messageId":"7veis7yxwx.fsf@alter.siamese.dyndns.org","threadId":"20208","inReplyTo":"20090723074104.GI4750@laphroaig.corp","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-23T19:58:06Z","receivedAt":"2009-07-23T19:58:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Pierre Habouzit <madcoder@debian.org> writes:\n\n> One of my most frequent use case for git-add -p is when I had an intense\n> debug session with quite a lot of debug() traces added. I then want only\n> to select the hunks corresponding to the bugfixes and throw away the debug\n> ones.\n\nI do not particularly like this change.  \"add -i\", \"add -p\" and \"add\" in\ngeneral are about manipulating the index.  They are never meant to touch\nthe work tree contents.  Which means that even if you make a mistake in\nsaying y/n, you won't damange the state you have in your work tree, and\nalso means that you can recover safely by simply restarting \"add -p\"\nsession if you really botched splitting of the patch.\n\nI fear tempting a new user who sees \"undo\" to say \"yeah, I added the\nchange in this hunk to the index by mistake, please undo\", which would\nlose the work.  The confusion is easier to avoid if \"add\" only manipulates\nthe index without harming the work tree, and the user used a different\ncommand, namely \"checkout from the index\", to get rid of the remaining\ndebug cruft, once s/he added all the necessary bits to the index perhaps\nafter a multi-stage commit session.\n"},{"id":"118619","messageId":"2d756f643fc7d1b50cf590c95467fa029b49ff46.1248426652.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"200907231041.32567.trast@student.ethz.ch","subject":"[RFC PATCH] Implement unstage and reset modes for git-add--interactive","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-24T09:15:51Z","receivedAt":"2009-07-24T09:15:51Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This is just the required change to git-add--interactive.  Since the\nuse of this script is an implementation detail, the next step would be\nto wrap this option in other commands such as 'git reset --patch' and\n'git unstage --patch'.\n\nSince there is no frontend support whatsoever, you have to run\n\n  git add--interactive --patch=unstage --\n\nmanually (including the --), and similarly for --patch=reset.\n---\n\nI wrote:\n> This is what 'git checkout --interactive -- $file' or 'git reset\n> --interactive --hard' would accomplish, if they existed.  I wonder if\n> there would be a way to avoid yet more command letters, and instead\n> have \"modes\" that affect what happens with hunks you said 'y' to.  For\n> example:\n> \n>   add -p                apply --cached\n>   undo -p               apply -R\n>   unstage -p            apply -R --cached\n>     [with hunks coming from diff --cached obviously]\n> \n> (I picked 'undo' and 'unstage' semi-randomly, but it's not, after all,\n> an 'add' operation any more and the user doesn't need to know that the\n> program doing this is in fact git-add--interactive.)\n\nLike so, maybe.  I realise that it does _not_ fill Pierre's need for a\ncommand that can do it all in one go, but if people like this I'll\nmake a real patch series.\n\nI'm not completely happy with the idea of reset --interactive by the\nway, as it's not a reset in the 'reset $commit' sense.\n\n\n\n git-add--interactive.perl |   60 +++++++++++++++++++++++++++++++++-----------\n 1 files changed, 45 insertions(+), 15 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex df9f231..ad322df 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -73,6 +73,27 @@\n # command line options\n my $patch_mode;\n \n+my %patch_modes = (\n+\t'stage' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => 'apply --cached',\n+\t\tVERB => 'Stage',\n+\t\tPARTICIPLE => 'Staging',\n+\t},\n+\t'unstage' => {\n+\t\tDIFF => 'diff-index -p --cached HEAD',\n+\t\tAPPLY => 'apply -R --cached',\n+\t\tVERB => 'UNstage',\n+\t\tPARTICIPLE => 'UNstaging',\n+\t},\n+\t'reset' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => 'apply -R',\n+\t\tVERB => 'RESET',\n+\t\tPARTICIPLE => 'RESETTING',\n+\t},\n+);\n+\n sub run_cmd_pipe {\n \tif ($^O eq 'MSWin32' || $^O eq 'msys') {\n \t\tmy @invalid = grep {m/[\":*]/} @_;\n@@ -615,10 +636,11 @@\n \n sub parse_diff {\n \tmy ($path) = @_;\n-\tmy @diff = run_cmd_pipe(qw(git diff-files -p --), $path);\n+\tmy @diff_cmd = split(\" \", $patch_modes{$patch_mode}{DIFF});\n+\tmy @diff = run_cmd_pipe(\"git\", @diff_cmd, \"--\", $path);\n \tmy @colored = ();\n \tif ($diff_use_color) {\n-\t\t@colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);\n+\t\t@colored = run_cmd_pipe(\"git\", @diff_cmd, qw(--color --), $path);\n \t}\n \tmy (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };\n \n@@ -877,6 +899,7 @@\n \t\tor die \"failed to open hunk edit file for writing: \" . $!;\n \tprint $fh \"# Manual hunk edit mode -- see bottom for a quick guide\\n\";\n \tprint $fh @$oldtext;\n+\tmy $participle = $patch_modes{$patch_mode}{PARTICIPLE};\n \tprint $fh <<EOF;\n # ---\n # To remove '-' lines, make them ' ' lines (context).\n@@ -884,7 +907,7 @@\n # Lines starting with # will be removed.\n #\n # If the patch applies cleanly, the edited hunk will immediately be\n-# marked for staging. If it does not apply cleanly, you will be given\n+# marked for $participle. If it does not apply cleanly, you will be given\n # an opportunity to edit again. If all lines of the hunk are removed,\n # then the edit is aborted and the hunk is left unchanged.\n EOF\n@@ -918,7 +941,7 @@\n \n sub diff_applies {\n \tmy $fh;\n-\topen $fh, '| git apply --recount --cached --check';\n+\topen $fh, '| git '.$patch_modes{$patch_mode}{APPLY}.' --recount --check';\n \tfor my $h (@_) {\n \t\tprint $fh @{$h->{TEXT}};\n \t}\n@@ -988,12 +1011,13 @@\n }\n \n sub help_patch_cmd {\n-\tprint colored $help_color, <<\\EOF ;\n-y - stage this hunk\n-n - do not stage this hunk\n-q - quit, do not stage this hunk nor any of the remaining ones\n-a - stage this and all the remaining hunks in the file\n-d - do not stage this hunk nor any of the remaining hunks in the file\n+\tmy $verb = lc $patch_modes{$patch_mode}{VERB};\n+\tprint colored $help_color, <<EOF ;\n+y - $verb this hunk\n+n - do not $verb this hunk\n+q - quit, do not $verb this hunk nor any of the remaining ones\n+a - $verb this and all the remaining hunks in the file\n+d - do not $verb this hunk nor any of the remaining hunks in the file\n g - select a hunk to go to\n / - search for a hunk matching the given regex\n j - leave this hunk undecided, see next undecided hunk\n@@ -1138,8 +1162,8 @@\n \t\tfor (@{$hunk[$ix]{DISPLAY}}) {\n \t\t\tprint;\n \t\t}\n-\t\tprint colored $prompt_color, 'Stage ',\n-\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),\n+\t\tprint colored $prompt_color, $patch_modes{$patch_mode}{VERB},\n+\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'),\n \t\t  \" [y,n,q,a,d,/$other,?]? \";\n \t\tmy $line = prompt_single_character;\n \t\tif ($line) {\n@@ -1314,7 +1338,7 @@\n \tif (@result) {\n \t\tmy $fh;\n \n-\t\topen $fh, '| git apply --cached --recount';\n+\t\topen $fh, '| git '.$patch_modes{$patch_mode}{APPLY}.' --recount';\n \t\tfor (@{$head->{TEXT}}, @result) {\n \t\t\tprint $fh $_;\n \t\t}\n@@ -1363,8 +1387,14 @@\n sub process_args {\n \treturn unless @ARGV;\n \tmy $arg = shift @ARGV;\n-\tif ($arg eq \"--patch\") {\n-\t\t$patch_mode = 1;\n+\tif ($arg =~ /--patch(?:=(.*))?/) {\n+\t\tif (defined $1 && defined $patch_modes{$1}) {\n+\t\t\t$patch_mode = $1;\n+\t\t} elsif (defined $1) {\n+\t\t\tdie \"unknown --patch mode: $1\";\n+\t\t} else {\n+\t\t\t$patch_mode = 'stage';\n+\t\t}\n \t\t$arg = shift @ARGV or die \"missing --\";\n \t\tdie \"invalid argument $arg, expecting --\"\n \t\t    unless $arg eq \"--\";\n-- \n1.6.4.rc2.215.g4f661\n"},{"id":"118625","messageId":"20090724193207.6117@nanako3.lavabit.com","threadId":"20208","inReplyTo":"7veis7yxwx.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Nanako Shiraishi","fromEmail":"nanako3@lavabit.com","sentAt":"2009-07-24T10:32:07Z","receivedAt":"2009-07-24T10:32:07Z","isPatch":true,"sender":{"key":"nanako3@lavabit.com","avatar":"https://gravatar.com/avatar/3777b9e201c5883a62b1a6fdf7c53f2d712d1d80989146063ea861e33aad72a8?d=mp&s=160"},"body":"Quoting Junio C Hamano <gitster@pobox.com>\n\n> I fear tempting a new user who sees \"undo\" to say \"yeah, I added the\n> change in this hunk to the index by mistake, please undo\", which would\n> lose the work.  The confusion is easier to avoid if \"add\" only manipulates\n> the index without harming the work tree, and the user used a different\n> command, namely \"checkout from the index\", to get rid of the remaining\n> debug cruft, once s/he added all the necessary bits to the index perhaps\n> after a multi-stage commit session.\n\nI can see your argument that this might introduce more danger for newbies. As you said yourself number of times, nobody will stay being a newbie forever, and I don't think it is wise to reject a feature that is very handy for experts based solely on such a fear.\n\n-- \nNanako Shiraishi\nhttp://ivory.ap.teacup.com/nanako3/\n"},{"id":"118628","messageId":"20090724145851.GH4903@laphroaig.corp","threadId":"20208","inReplyTo":"7veis7yxwx.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Pierre Habouzit","fromEmail":"madcoder@madism.org","sentAt":"2009-07-24T14:58:51Z","receivedAt":"2009-07-24T14:58:51Z","isPatch":true,"sender":{"key":"madcoder@madism.org","avatar":null},"body":"On Thu, Jul 23, 2009 at 12:58:06PM -0700, Junio C Hamano wrote:\n> Pierre Habouzit <madcoder@debian.org> writes:\n> \n> > One of my most frequent use case for git-add -p is when I had an intense\n> > debug session with quite a lot of debug() traces added. I then want only\n> > to select the hunks corresponding to the bugfixes and throw away the debug\n> > ones.\n> \n> I do not particularly like this change.  \"add -i\", \"add -p\" and \"add\" in\n> general are about manipulating the index.  They are never meant to touch\n> the work tree contents.  Which means that even if you make a mistake in\n> saying y/n, you won't damange the state you have in your work tree, and\n> also means that you can recover safely by simply restarting \"add -p\"\n> session if you really botched splitting of the patch.\n\nOkay, fair enough, this is kind of mixing stuff together, I reckon. OTOH\nthis fills out a real need: sorting out the debug code from the non\ndebug one, it's particularily handy, and many people at work here have\napplied my patch and it saves them lots of time.\n\nSo maybe we should see how to have a new command like git hunk-sort or\ngit hunk-triage or whatever, I don't really mind.\n\nAnother way, that is non destructive is to _not_ apply the reverting\npatch, but only to generate it, and let the user apply it himself. E.g.\nsomething along the lines of:\n\n$ git add -p\n... select your patches ...\ninfo: you have selected hunks for removal from you tree\ninfo: run the following command to make them go away:\n  git apply -R --recount git-add-undo-12asWED.patch\n$\n\nAnd let the user cut & paste the command.\n\n_I_ could live with that, and you lose the dangerous factor.\n\n-- \nIntersec <http://www.intersec.com>\nPierre Habouzit <pierre.habouzit@intersec.com>\nTél : +33 (0)1 5570 3346\nMob : +33 (0)6 1636 8131\nFax : +33 (0)1 5570 3332\n37 Rue Pierre Lhomme\n92400 Courbevoie\n"},{"id":"118634","messageId":"7v8wienk07.fsf@alter.siamese.dyndns.org","threadId":"20208","inReplyTo":"20090724193207.6117@nanako3.lavabit.com","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-24T16:06:16Z","receivedAt":"2009-07-24T16:06:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nanako Shiraishi <nanako3@lavabit.com> writes:\n\n> Quoting Junio C Hamano <gitster@pobox.com>\n>\n>> I fear tempting a new user who sees \"undo\" to say \"yeah, I added the\n>> change in this hunk to the index by mistake, please undo\", which would\n>> lose the work.  The confusion is easier to avoid if \"add\" only manipulates\n>> the index without harming the work tree, and the user used a different\n>> command, namely \"checkout from the index\", to get rid of the remaining\n>> debug cruft, once s/he added all the necessary bits to the index perhaps\n>> after a multi-stage commit session.\n>\n> I can see your argument that this might introduce more danger for\n> newbies. As you said yourself number of times, nobody will stay being a\n> newbie forever, and I don't think it is wise to reject a feature that is\n> very handy for experts based solely on such a fear.\n\nIt is true that as new people learn they gain proficiency, and you are\nalso right to point out that I'd usually choose to optimize the interface\nfor making the life of experts easier rather than welding training wheels\nto the system.\n\nBUT\n\nAs new people gain proficiency in git, three things happen.\n\n (1) It becomes a lot less likely for them to make mistakes in choosing\n     commands.  I mentioned that they may misunderstand what Pierre's\n     \"undo\" would do, and that fear may be alleviated because of this.\n\n (2) It does _not_ become less likely for them to make typos when giving\n     the command they chose.  The chance of saying 'u' when you mean 'y'\n     does not decrease that much as you become more used to using git.\n     Especially with interactive.singlekey, the consequence of such a typo\n     is devastating.\n\n (3) They form a better mental model of how the world works.\n\n     The high level view of the git workflow is for you to:\n\n     (a) prepare good changes, together with some changes that are not\n         quite ready, in your work tree; and\n\n     (b) use \"git add\" to add only good changes suitable for the next\n         commit to the index; and finally\n\n     (c) make the next commit out of the index.  Repeat (b) and (c) as\n         necessary to create multiple commits.\n\n     If you botch the \"git add\" step during this process, because \"git\n     add\" promises not to touch the work tree, you can safely reset the\n     index entry to its previous state and redo the \"git add\" step,\n     without having to fear that you may lose your work.\n\nIn your arsenal, you have \"git add -p\" to help you sift good pieces from\nother parts in finer grained manner, instead of having to make an all or\nnothing decision per file basis (i.e. \"git add file\").  But \"git add -p\"\n(and \"git add -i\") is still about the \"git add\" step in the above high\nlevel view.  You have a mixture of good and not so good changes in your\nwork tree, and you pick only good pieces to add to the index, _knowing_\nthat you can go back and redo this step safely exactly because your work\ntree will stay the same even if you did make mistakes.\n\nThe proposed change breaks this expectation you would have naturally\ngained during the course of becoming more and more proficient in using\ngit.\n\nIn other words, I do not think you can say that the change will not harm\nthe experts due to both the points 2 (experts can easily make typo) and 3\nabove (the change breaks the mental model of the world experts would have\nformed).\n\nHaving said all that, it indeed would be useful to selectively revert\nchanges from the work tree files.\n\nEven though you could add good bits interactively, making multiple\ncommits, and remove the remaining debugging cruft at the very end with\n\"git checkout $files\" or \"git reset --hard\", if there are debugging crufts\nfor two or more phases of development, this alternative procedure would\nnot work well, compared to the workflow using Pierre's 'u', which allows\nyou to add necessary bits to commit one phase, remove the debugging bits\nfor that phase (but keeping other debugging bits for the remaining good\nparts), then continue working and repeat committing and cleaning second\nand subsequent phases.\n\nIt might be enough to change the command key to uppercase \"U\" to avoid\nunintended mistakes, and document the fact prominently that this action is\nan oddball exception to the principle of \"git add\", while describing why\nit is an oddball, along the lines of the above discussion, if necessary.\n"},{"id":"118635","messageId":"683ed99f389d643d21bda6750bd2ee9caf287625.1248452530.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"2d756f643fc7d1b50cf590c95467fa029b49ff46.1248426652.git.trast@student.ethz.ch","subject":"[RFC PATCH v2 1/3] Introduce git-unstage","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-24T16:24:34Z","receivedAt":"2009-07-24T16:24:34Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"The new command 'git unstage' is the precise opposite of 'git stage'\n(i.e., git-add).  As such, it is the same as 'git reset --' unless the\ncurrent branch is unborn.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nSo I decided I had wished for these frequently enough to actually\nimplement them.  I ran out of time before getting to the tests, but\nthe rest should be there.\n\nv1 had a bug where it would not correctly detect the changed files,\nthis is fixed in the new patch (now 3/3).\n\n\n Documentation/git-unstage.txt |   26 ++++++++++++++++++++++++++\n Makefile                      |    1 +\n git-unstage.sh                |   26 ++++++++++++++++++++++++++\n wt-status.c                   |    6 +-----\n 4 files changed, 54 insertions(+), 5 deletions(-)\n create mode 100644 Documentation/git-unstage.txt\n create mode 100644 git-unstage.sh\n\ndiff --git a/Documentation/git-unstage.txt b/Documentation/git-unstage.txt\nnew file mode 100644\nindex 0000000..49d09fb\n--- /dev/null\n+++ b/Documentation/git-unstage.txt\n@@ -0,0 +1,26 @@\n+git-unstage(1)\n+==============\n+\n+NAME\n+----\n+git-unstage - Remove changes to a file from the staging area\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git unstage' <paths> ...\n+\n+\n+DESCRIPTION\n+-----------\n+\n+Overwrites the staged changes to the 'paths' with the values from\n+HEAD, so that they are not included in the next commit.  The worktree\n+is not affected.  (This is the same as `git reset \\-- <paths>` unless\n+you are on an unborn branch.)\n+\n+\n+SEE ALSO\n+--------\n+linkgit:git-reset[1]\ndiff --git a/Makefile b/Makefile\nindex 75b9dcb..9e48fdc 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -329,6 +329,7 @@ SCRIPT_SH += git-request-pull.sh\n SCRIPT_SH += git-sh-setup.sh\n SCRIPT_SH += git-stash.sh\n SCRIPT_SH += git-submodule.sh\n+SCRIPT_SH += git-unstage.sh\n SCRIPT_SH += git-web--browse.sh\n \n SCRIPT_PERL += git-add--interactive.perl\ndiff --git a/git-unstage.sh b/git-unstage.sh\nnew file mode 100644\nindex 0000000..7f99adf\n--- /dev/null\n+++ b/git-unstage.sh\n@@ -0,0 +1,26 @@\n+#!/bin/sh\n+\n+SUBDIRECTORY_OK=Yes\n+OPTIONS_KEEPDASHDASH=\n+OPTIONS_SPEC=\"\\\n+git unstage file ...\n+--\"\n+\n+. git-sh-setup\n+\n+\n+case \"$1\" in\n+    --)\n+\tif [ $# -eq 1 ]; then\n+\t    die \"You must specify at least one file to unstage\"\n+\tfi\n+\tif git rev-parse -q --verify HEAD >/dev/null; then\n+\t    exec git reset \"$@\"\n+\telse\n+\t    exec git rm --cached \"$@\"\n+\tfi\n+\t;;\n+    *)\n+\tusage\n+\t;;\n+esac\ndiff --git a/wt-status.c b/wt-status.c\nindex 47735d8..f1a74a4 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -62,11 +62,7 @@ static void wt_status_print_cached_header(struct wt_status *s)\n {\n \tconst char *c = color(WT_STATUS_HEADER);\n \tcolor_fprintf_ln(s->fp, c, \"# Changes to be committed:\");\n-\tif (!s->is_initial) {\n-\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git reset %s <file>...\\\" to unstage)\", s->reference);\n-\t} else {\n-\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git rm --cached <file>...\\\" to unstage)\");\n-\t}\n+\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git unstage %s <file>...\\\" to unstage)\", s->reference);\n \tcolor_fprintf_ln(s->fp, c, \"#\");\n }\n \n-- \n1.6.4.rc2.217.g74c0b.dirty\n"},{"id":"118636","messageId":"cf51e6aa92ea98fc9409826cc0468a01fdf7b01e.1248452530.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"2d756f643fc7d1b50cf590c95467fa029b49ff46.1248426652.git.trast@student.ethz.ch","subject":"[RFC PATCH v2 2/3] Introduce git-discard","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-24T16:24:35Z","receivedAt":"2009-07-24T16:24:35Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"The new command 'git discard' is precisely the same as 'git checkout --'.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-discard.txt |   29 +++++++++++++++++++++++++++++\n Makefile                      |    1 +\n git-discard.sh                |   22 ++++++++++++++++++++++\n wt-status.c                   |    2 +-\n 4 files changed, 53 insertions(+), 1 deletions(-)\n create mode 100644 Documentation/git-discard.txt\n create mode 100644 git-discard.sh\n\ndiff --git a/Documentation/git-discard.txt b/Documentation/git-discard.txt\nnew file mode 100644\nindex 0000000..4db14f0\n--- /dev/null\n+++ b/Documentation/git-discard.txt\n@@ -0,0 +1,29 @@\n+git-discard(1)\n+==============\n+\n+NAME\n+----\n+git-discard - Remove changes to a file from the worktree\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git discard' <paths> ...\n+\n+\n+DESCRIPTION\n+-----------\n+\n+Overwrites your edits to the 'paths' with the values from the staging\n+area, effectively throwing them away entirely.\n+\n+*WARNING:* All unstaged changes to the 'paths' are *irreversibly*\n+lost.\n+\n+(This is the same as `git checkout \\-- <paths>`.)\n+\n+\n+SEE ALSO\n+--------\n+linkgit:git-checkout[1]\ndiff --git a/Makefile b/Makefile\nindex 9e48fdc..814d4b6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -312,6 +312,7 @@ TEST_PROGRAMS =\n SCRIPT_SH += git-am.sh\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n+SCRIPT_SH += git-discard.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-lost-found.sh\n SCRIPT_SH += git-merge-octopus.sh\ndiff --git a/git-discard.sh b/git-discard.sh\nnew file mode 100644\nindex 0000000..595df98\n--- /dev/null\n+++ b/git-discard.sh\n@@ -0,0 +1,22 @@\n+#!/bin/sh\n+\n+SUBDIRECTORY_OK=Yes\n+OPTIONS_KEEPDASHDASH=\n+OPTIONS_SPEC=\"\\\n+git discard file ...\n+--\"\n+\n+. git-sh-setup\n+\n+\n+case \"$1\" in\n+    --)\n+\tif [ $# -eq 1 ]; then\n+\t    die \"You must specify at least one file to discard changes from\"\n+\tfi\n+\texec git checkout \"$@\"\n+\t;;\n+    *)\n+\tusage\n+\t;;\n+esac\ndiff --git a/wt-status.c b/wt-status.c\nindex f1a74a4..1dd4bed 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -75,7 +75,7 @@ static void wt_status_print_dirty_header(struct wt_status *s,\n \t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add <file>...\\\" to update what will be committed)\");\n \telse\n \t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add/rm <file>...\\\" to update what will be committed)\");\n-\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git checkout -- <file>...\\\" to discard changes in working directory)\");\n+\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git discard <file>...\\\" to discard changes in working directory)\");\n \tcolor_fprintf_ln(s->fp, c, \"#\");\n }\n \n-- \n1.6.4.rc2.217.g74c0b.dirty\n"},{"id":"118637","messageId":"9cad6176fe941bd56bad7fed49cc55a96a7040ae.1248452530.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"2d756f643fc7d1b50cf590c95467fa029b49ff46.1248426652.git.trast@student.ethz.ch","subject":"[RFC PATCH v2 3/3] Implement unstage --patch and discard --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-24T16:24:36Z","receivedAt":"2009-07-24T16:24:36Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This implements a new optional argument --patch=<mode> in the\ngit-add--interactive helper.  The modes are:\n\n'stage' (default, as before): offer hunks from 'git diff' and stage\n  the ones selected by the user.\n\n'unstage': offer hunks from 'git diff --cached', and unstage the ones\n  selected.\n\n'discard': offer hunks from 'git diff', and discard (i.e., undo the\n  edit in the working tree) the ones selected.\n\nWith this in hand, we can then easily add a --patch option to the\ngit-unstage and git-discard scripts that run git-add--interactive in\nthe corresponding modes.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-discard.txt |    7 ++++\n Documentation/git-unstage.txt |    7 ++++\n git-add--interactive.perl     |   69 +++++++++++++++++++++++++++++++---------\n git-discard.sh                |    9 ++++-\n git-unstage.sh                |    9 ++++-\n 5 files changed, 81 insertions(+), 20 deletions(-)\n\ndiff --git a/Documentation/git-discard.txt b/Documentation/git-discard.txt\nindex 4db14f0..2c063d1 100644\n--- a/Documentation/git-discard.txt\n+++ b/Documentation/git-discard.txt\n@@ -24,6 +24,13 @@ lost.\n (This is the same as `git checkout \\-- <paths>`.)\n \n \n+OPTIONS\n+-------\n+-p::\n+--patch::\n+\tInteractively select hunks to discard.\n+\n+\n SEE ALSO\n --------\n linkgit:git-checkout[1]\ndiff --git a/Documentation/git-unstage.txt b/Documentation/git-unstage.txt\nindex 49d09fb..d3da3e1 100644\n--- a/Documentation/git-unstage.txt\n+++ b/Documentation/git-unstage.txt\n@@ -21,6 +21,13 @@ is not affected.  (This is the same as `git reset \\-- <paths>` unless\n you are on an unborn branch.)\n \n \n+OPTIONS\n+-------\n+-p::\n+--patch::\n+\tInteractively select hunks to unstage.\n+\n+\n SEE ALSO\n --------\n linkgit:git-reset[1]\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex df9f231..502a0e4 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -73,6 +73,30 @@\n # command line options\n my $patch_mode;\n \n+my %patch_modes = (\n+\t'stage' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => 'apply --cached',\n+\t\tVERB => 'Stage',\n+\t\tPARTICIPLE => 'Staging',\n+\t\tFILTER => 'file-only',\n+\t},\n+\t'unstage' => {\n+\t\tDIFF => 'diff-index -p --cached HEAD',\n+\t\tAPPLY => 'apply -R --cached',\n+\t\tVERB => 'UNstage',\n+\t\tPARTICIPLE => 'UNstaging',\n+\t\tFILTER => 'index-only',\n+\t},\n+\t'discard' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => 'apply -R',\n+\t\tVERB => 'RESET',\n+\t\tPARTICIPLE => 'RESETTING',\n+\t\tFILTER => 'file-only',\n+\t},\n+);\n+\n sub run_cmd_pipe {\n \tif ($^O eq 'MSWin32' || $^O eq 'msys') {\n \t\tmy @invalid = grep {m/[\":*]/} @_;\n@@ -615,10 +639,11 @@\n \n sub parse_diff {\n \tmy ($path) = @_;\n-\tmy @diff = run_cmd_pipe(qw(git diff-files -p --), $path);\n+\tmy @diff_cmd = split(\" \", $patch_modes{$patch_mode}{DIFF});\n+\tmy @diff = run_cmd_pipe(\"git\", @diff_cmd, \"--\", $path);\n \tmy @colored = ();\n \tif ($diff_use_color) {\n-\t\t@colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);\n+\t\t@colored = run_cmd_pipe(\"git\", @diff_cmd, qw(--color --), $path);\n \t}\n \tmy (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };\n \n@@ -877,6 +902,7 @@\n \t\tor die \"failed to open hunk edit file for writing: \" . $!;\n \tprint $fh \"# Manual hunk edit mode -- see bottom for a quick guide\\n\";\n \tprint $fh @$oldtext;\n+\tmy $participle = $patch_modes{$patch_mode}{PARTICIPLE};\n \tprint $fh <<EOF;\n # ---\n # To remove '-' lines, make them ' ' lines (context).\n@@ -884,7 +910,7 @@\n # Lines starting with # will be removed.\n #\n # If the patch applies cleanly, the edited hunk will immediately be\n-# marked for staging. If it does not apply cleanly, you will be given\n+# marked for $participle. If it does not apply cleanly, you will be given\n # an opportunity to edit again. If all lines of the hunk are removed,\n # then the edit is aborted and the hunk is left unchanged.\n EOF\n@@ -918,7 +944,7 @@\n \n sub diff_applies {\n \tmy $fh;\n-\topen $fh, '| git apply --recount --cached --check';\n+\topen $fh, '| git '.$patch_modes{$patch_mode}{APPLY}.' --recount --check';\n \tfor my $h (@_) {\n \t\tprint $fh @{$h->{TEXT}};\n \t}\n@@ -988,12 +1014,13 @@\n }\n \n sub help_patch_cmd {\n-\tprint colored $help_color, <<\\EOF ;\n-y - stage this hunk\n-n - do not stage this hunk\n-q - quit, do not stage this hunk nor any of the remaining ones\n-a - stage this and all the remaining hunks in the file\n-d - do not stage this hunk nor any of the remaining hunks in the file\n+\tmy $verb = lc $patch_modes{$patch_mode}{VERB};\n+\tprint colored $help_color, <<EOF ;\n+y - $verb this hunk\n+n - do not $verb this hunk\n+q - quit, do not $verb this hunk nor any of the remaining ones\n+a - $verb this and all the remaining hunks in the file\n+d - do not $verb this hunk nor any of the remaining hunks in the file\n g - select a hunk to go to\n / - search for a hunk matching the given regex\n j - leave this hunk undecided, see next undecided hunk\n@@ -1007,7 +1034,11 @@\n }\n \n sub patch_update_cmd {\n-\tmy @all_mods = list_modified('file-only');\n+\tmy $filter = 'file-only';\n+\tif ($patch_mode) {\n+\t\t$filter = $patch_modes{$patch_mode}{FILTER};\n+\t}\n+\tmy @all_mods = list_modified($filter);\n \tmy @mods = grep { !($_->{BINARY}) } @all_mods;\n \tmy @them;\n \n@@ -1138,8 +1169,8 @@\n \t\tfor (@{$hunk[$ix]{DISPLAY}}) {\n \t\t\tprint;\n \t\t}\n-\t\tprint colored $prompt_color, 'Stage ',\n-\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),\n+\t\tprint colored $prompt_color, $patch_modes{$patch_mode}{VERB},\n+\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'),\n \t\t  \" [y,n,q,a,d,/$other,?]? \";\n \t\tmy $line = prompt_single_character;\n \t\tif ($line) {\n@@ -1314,7 +1345,7 @@\n \tif (@result) {\n \t\tmy $fh;\n \n-\t\topen $fh, '| git apply --cached --recount';\n+\t\topen $fh, '| git '.$patch_modes{$patch_mode}{APPLY}.' --recount';\n \t\tfor (@{$head->{TEXT}}, @result) {\n \t\t\tprint $fh $_;\n \t\t}\n@@ -1363,8 +1394,14 @@\n sub process_args {\n \treturn unless @ARGV;\n \tmy $arg = shift @ARGV;\n-\tif ($arg eq \"--patch\") {\n-\t\t$patch_mode = 1;\n+\tif ($arg =~ /--patch(?:=(.*))?/) {\n+\t\tif (defined $1 && defined $patch_modes{$1}) {\n+\t\t\t$patch_mode = $1;\n+\t\t} elsif (defined $1) {\n+\t\t\tdie \"unknown --patch mode: $1\";\n+\t\t} else {\n+\t\t\t$patch_mode = 'stage';\n+\t\t}\n \t\t$arg = shift @ARGV or die \"missing --\";\n \t\tdie \"invalid argument $arg, expecting --\"\n \t\t    unless $arg eq \"--\";\ndiff --git a/git-discard.sh b/git-discard.sh\nindex 595df98..628346d 100644\n--- a/git-discard.sh\n+++ b/git-discard.sh\n@@ -3,13 +3,18 @@\n SUBDIRECTORY_OK=Yes\n OPTIONS_KEEPDASHDASH=\n OPTIONS_SPEC=\"\\\n-git discard file ...\n---\"\n+git discard [-p] file ...\n+--\n+p,patch         interactively select hunks to discard\"\n \n . git-sh-setup\n \n \n case \"$1\" in\n+    -p|--patch)\n+\tshift\n+\texec git add--interactive --patch=discard \"$@\"\n+\t;;\n     --)\n \tif [ $# -eq 1 ]; then\n \t    die \"You must specify at least one file to discard changes from\"\ndiff --git a/git-unstage.sh b/git-unstage.sh\nindex 7f99adf..921e12b 100644\n--- a/git-unstage.sh\n+++ b/git-unstage.sh\n@@ -3,13 +3,18 @@\n SUBDIRECTORY_OK=Yes\n OPTIONS_KEEPDASHDASH=\n OPTIONS_SPEC=\"\\\n-git unstage file ...\n---\"\n+git unstage [-p] file ...\n+--\n+p,patch         interactively select hunks to unstage\"\n \n . git-sh-setup\n \n \n case \"$1\" in\n+    -p|--patch)\n+\tshift\n+\texec git add--interactive --patch=unstage \"$@\"\n+\t;;\n     --)\n \tif [ $# -eq 1 ]; then\n \t    die \"You must specify at least one file to unstage\"\n-- \n1.6.4.rc2.217.g74c0b.dirty\n"},{"id":"118639","messageId":"7vocrakpbo.fsf@alter.siamese.dyndns.org","threadId":"20208","inReplyTo":"2d756f643fc7d1b50cf590c95467fa029b49ff46.1248426652.git.trast@student.ethz.ch","subject":"Re: [RFC PATCH] Implement unstage and reset modes for git-add--interactive","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-24T16:39:39Z","receivedAt":"2009-07-24T16:39:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> I'm not completely happy with the idea of reset --interactive by the\n> way, as it's not a reset in the 'reset $commit' sense.\n\nWhy not?\n\n\"resetting index entries selectively\" makes perfect sense.  So do\n\"checking out index entries selectively\" and \"adding to index\nselectively\".\n"},{"id":"118640","messageId":"1f6632e50907240940j6174a689i4390c245f0771fcb@mail.gmail.com","threadId":"20208","inReplyTo":"9cad6176fe941bd56bad7fed49cc55a96a7040ae.1248452530.git.trast@student.ethz.ch","subject":"Re: [RFC PATCH v2 3/3] Implement unstage --patch and discard --patch","fromName":"Matthias Kestenholz","fromEmail":"mk@feinheit.ch","sentAt":"2009-07-24T16:40:40Z","receivedAt":"2009-07-24T16:40:40Z","isPatch":true,"sender":{"key":"mk@feinheit.ch","avatar":"https://gravatar.com/avatar/f4f02a5336cf0e3d40b05498959e997f023cb5d8c83ab41545a3272268c67949?d=mp&s=160"},"body":"On Fri, Jul 24, 2009 at 6:24 PM, Thomas Rast<trast@student.ethz.ch> wrote:\n> This implements a new optional argument --patch=<mode> in the\n> git-add--interactive helper.  The modes are:\n>\n> 'stage' (default, as before): offer hunks from 'git diff' and stage\n>  the ones selected by the user.\n>\n> 'unstage': offer hunks from 'git diff --cached', and unstage the ones\n>  selected.\n>\n> 'discard': offer hunks from 'git diff', and discard (i.e., undo the\n>  edit in the working tree) the ones selected.\n>\n> With this in hand, we can then easily add a --patch option to the\n> git-unstage and git-discard scripts that run git-add--interactive in\n> the corresponding modes.\n>\n\nWhile I do not really think even more git commands are so helpful, I\nlike the proposed command names, and I really like that it would be\npossible to remove single hunks from the index. I have come to rely\nvery much on git add -p to make self-contained and clear commits and\nthink it would be a great feature if I could use this the other way\nround too.\n\n\nThanks,\nMatthias\n"},{"id":"118641","messageId":"20090724170622.GA20247@sigill.intra.peff.net","threadId":"20208","inReplyTo":"7v8wienk07.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2009-07-24T17:06:22Z","receivedAt":"2009-07-24T17:06:22Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Jul 24, 2009 at 09:06:16AM -0700, Junio C Hamano wrote:\n\n> In your arsenal, you have \"git add -p\" to help you sift good pieces from\n> other parts in finer grained manner, instead of having to make an all or\n> nothing decision per file basis (i.e. \"git add file\").  But \"git add -p\"\n> (and \"git add -i\") is still about the \"git add\" step in the above high\n> level view.  You have a mixture of good and not so good changes in your\n> work tree, and you pick only good pieces to add to the index, _knowing_\n> that you can go back and redo this step safely exactly because your work\n> tree will stay the same even if you did make mistakes.\n> \n> The proposed change breaks this expectation you would have naturally\n> gained during the course of becoming more and more proficient in using\n> git.\n> \n> In other words, I do not think you can say that the change will not harm\n> the experts due to both the points 2 (experts can easily make typo) and 3\n> above (the change breaks the mental model of the world experts would have\n> formed).\n> \n> Having said all that, it indeed would be useful to selectively revert\n> changes from the work tree files.\n\nPerhaps it makes sense to have an interactive stash rather than an\ninteractive revert? Then the reverts that you make are still being saved\nsomewhere, and you can recover from an error by applying the stash. Not\nto mention that interactive stash is useful in its own right.\n\nThe downside is that if you are the sort of person who keeps a clean\nstash list (and I am not such a person), then you have this\n\"to-be-deleted\" cruft on the top of your stash (whereas with a true\nrevert, it just goes away).\n\n-Peff\n"},{"id":"118644","messageId":"36ca99e90907241059l1d06681v1490a89fbbcaaf8@mail.gmail.com","threadId":"20208","inReplyTo":"683ed99f389d643d21bda6750bd2ee9caf287625.1248452530.git.trast@student.ethz.ch","subject":"Re: [RFC PATCH v2 1/3] Introduce git-unstage","fromName":"Bert Wesarg","fromEmail":"bert.wesarg@googlemail.com","sentAt":"2009-07-24T17:59:34Z","receivedAt":"2009-07-24T17:59:34Z","isPatch":true,"sender":{"key":"bert.wesarg@googlemail.com","avatar":"https://avatars.githubusercontent.com/u/111934?v=4"},"body":"On Fri, Jul 24, 2009 at 18:24, Thomas Rast<trast@student.ethz.ch> wrote:\n> As such, it is the same as 'git reset --' ...\nNope!\n\n> +           exec git reset \"$@\"\nSee!\n\nBert\n"},{"id":"118647","messageId":"36ca99e90907241102v1b261e43yf85ba52754edf869@mail.gmail.com","threadId":"20208","inReplyTo":"36ca99e90907241059l1d06681v1490a89fbbcaaf8@mail.gmail.com","subject":"Re: [RFC PATCH v2 1/3] Introduce git-unstage","fromName":"Bert Wesarg","fromEmail":"bert.wesarg@googlemail.com","sentAt":"2009-07-24T18:02:21Z","receivedAt":"2009-07-24T18:02:21Z","isPatch":true,"sender":{"key":"bert.wesarg@googlemail.com","avatar":"https://avatars.githubusercontent.com/u/111934?v=4"},"body":"On Fri, Jul 24, 2009 at 19:59, Bert Wesarg<bert.wesarg@googlemail.com> wrote:\n> On Fri, Jul 24, 2009 at 18:24, Thomas Rast<trast@student.ethz.ch> wrote:\n>> As such, it is the same as 'git reset --' ...\n> Nope!\n>\n>> +           exec git reset \"$@\"\n> See!\nSorry, it was not obvious clear to me, that you don't shift away '--'.\nDoes the option parsing add '--' if it is missing?\n\nBert\n>\n> Bert\n>\n"},{"id":"118645","messageId":"51419b2c0907241102s31c2fbaeof81e2fb053465c89@mail.gmail.com","threadId":"20208","inReplyTo":"cf51e6aa92ea98fc9409826cc0468a01fdf7b01e.1248452530.git.trast@student.ethz.ch","subject":"Re: [RFC PATCH v2 2/3] Introduce git-discard","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2009-07-24T18:02:49Z","receivedAt":"2009-07-24T18:02:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 24, 2009 at 10:24 AM, Thomas Rast<trast@student.ethz.ch> wrote:\n> The new command 'git discard' is precisely the same as 'git checkout --'.\n> +(This is the same as `git checkout \\-- <paths>`.)\n\nActually, there's an important difference:\n\n> +       exec git checkout \"$@\"\n\n$ git branch foo HEAD~20\n$ touch foo && git add foo\n$ echo hi >> foo\n$ git discard foo\nA     foo\nSwitched to branch \"foo\"\n\nYou really do need that \"--\".\n"},{"id":"118646","messageId":"36ca99e90907241108h78881239o403f50773764bd43@mail.gmail.com","threadId":"20208","inReplyTo":"9cad6176fe941bd56bad7fed49cc55a96a7040ae.1248452530.git.trast@student.ethz.ch","subject":"Re: [RFC PATCH v2 3/3] Implement unstage --patch and discard --patch","fromName":"Bert Wesarg","fromEmail":"bert.wesarg@googlemail.com","sentAt":"2009-07-24T18:08:54Z","receivedAt":"2009-07-24T18:08:54Z","isPatch":true,"sender":{"key":"bert.wesarg@googlemail.com","avatar":"https://avatars.githubusercontent.com/u/111934?v=4"},"body":"On Fri, Jul 24, 2009 at 18:24, Thomas Rast<trast@student.ethz.ch> wrote:\n> 'discard': offer hunks from 'git diff', and discard (i.e., undo the\n>  edit in the working tree) the ones selected.\nGreat, I really miss this feature in git-gui, maybe its easy to add\nthis there too. I may look into this by my self, but not this weekend.\n\nBert\n"},{"id":"118648","messageId":"36ca99e90907241112p6cc2c875q5fa4becd1169bfd3@mail.gmail.com","threadId":"20208","inReplyTo":"51419b2c0907241102s31c2fbaeof81e2fb053465c89@mail.gmail.com","subject":"Re: [RFC PATCH v2 2/3] Introduce git-discard","fromName":"Bert Wesarg","fromEmail":"bert.wesarg@googlemail.com","sentAt":"2009-07-24T18:12:56Z","receivedAt":"2009-07-24T18:12:56Z","isPatch":true,"sender":{"key":"bert.wesarg@googlemail.com","avatar":"https://avatars.githubusercontent.com/u/111934?v=4"},"body":"On Fri, Jul 24, 2009 at 20:02, Elijah Newren<newren@gmail.com> wrote:\n> On Fri, Jul 24, 2009 at 10:24 AM, Thomas Rast<trast@student.ethz.ch> wrote:\n>> The new command 'git discard' is precisely the same as 'git checkout --'.\n>> +(This is the same as `git checkout \\-- <paths>`.)\n>\n> Actually, there's an important difference:\n>\n>> +       exec git checkout \"$@\"\n>\n> $ git branch foo HEAD~20\n> $ touch foo && git add foo\n> $ echo hi >> foo\n> $ git discard foo\n> A     foo\n> Switched to branch \"foo\"\n>\n> You really do need that \"--\".\nYou fall into the same trap as me for patch 1/3. He does not shift\naway the '--', so it is still in \"$@\".\n\nBert.\n"},{"id":"118649","messageId":"51419b2c0907241123s4ce39ef0q99a25132429cad61@mail.gmail.com","threadId":"20208","inReplyTo":"683ed99f389d643d21bda6750bd2ee9caf287625.1248452530.git.trast@student.ethz.ch","subject":"Re: [RFC PATCH v2 1/3] Introduce git-unstage","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2009-07-24T18:23:03Z","receivedAt":"2009-07-24T18:23:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 24, 2009 at 10:24 AM, Thomas Rast<trast@student.ethz.ch> wrote:\n> The new command 'git unstage' is the precise opposite of 'git stage'\n> (i.e., git-add).  As such, it is the same as 'git reset --' unless the\n> current branch is unborn.\n\nI really, really like the idea of being able to do something like\nunstage -p.  That's sweet, thanks for working on that.\n\nSome potential issues, though:\n\ngit unstage may cause issues for some people if they try to use it\nwhen in the middle of a merge (why reset back to HEAD rather than one\nof the other branches being merged?).  With git reset, one can specify\nwhich commit to reset the index back to.  With your 'unstage' command,\nyou not only assume 'HEAD' but don't allow specifying any other value.\n You could add a commit argument here, but that may be problematic\nwording-wise, in that you're allowing to 'unstage' back to a specified\ncommit, with the possibly perplexing result that you have 'staged'\nsomething else.\n\nAlso, you have added two new commands, unstage and discard, which are\ntwo pieces of an hg-like revert (revert edits).  We still don't have a\ncommand for a full hg-like revert, though (git checkout does not cover\nthe bases), and to cover that we'd have to add a third command.\nPerhaps the three could be combined?\n\n\nElijah\n"},{"id":"118651","messageId":"51419b2c0907241124q18232290u860c9d7ca7bb374c@mail.gmail.com","threadId":"20208","inReplyTo":"36ca99e90907241112p6cc2c875q5fa4becd1169bfd3@mail.gmail.com","subject":"Re: [RFC PATCH v2 2/3] Introduce git-discard","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2009-07-24T18:24:51Z","receivedAt":"2009-07-24T18:24:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 24, 2009 at 12:12 PM, Bert Wesarg<bert.wesarg@googlemail.com> wrote:\n> You fall into the same trap as me for patch 1/3. He does not shift\n> away the '--', so it is still in \"$@\".\n\nIndeed; I somehow missed that '--' was silently added by git.  I guess\nthat's what I get for shooting off my mouth without actually trying\nit.  Sorry for the noise.\n"},{"id":"118662","messageId":"20090725065822.6117@nanako3.lavabit.com","threadId":"20208","inReplyTo":"7vocrakpbo.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC PATCH] Implement unstage and reset modes for git-add--interactive","fromName":"Nanako Shiraishi","fromEmail":"nanako3@lavabit.com","sentAt":"2009-07-24T21:58:22Z","receivedAt":"2009-07-24T21:58:22Z","isPatch":true,"sender":{"key":"nanako3@lavabit.com","avatar":"https://gravatar.com/avatar/3777b9e201c5883a62b1a6fdf7c53f2d712d1d80989146063ea861e33aad72a8?d=mp&s=160"},"body":"Quoting Junio C Hamano <gitster@pobox.com>\n\n> \"resetting index entries selectively\" makes perfect sense.  So do\n> \"checking out index entries selectively\" and \"adding to index\n> selectively\".\n\nAre you saying that you are fine with the concept of Thomas'es patch series but you don't want to see different words used to name these operations?\n\nIn other words, do you mean the following would be a pair of better companions to \"git add -p $file\" than Thomas'es discard and unstage?\n\n - \"git checkout -p $file\" and \"git checkout -p $commit $file\" that let you view the patch to bring the file in the working tree to the version in the index (or the commit) and selectively apply that to the working tree and the index, to implement \"discarding changes selectively\".\n\n - \"git reset -p $file\" and \"git reset -p $commit $file\" that let you view the patch to bring the version of the file in the index to the version in the HEAD (or the commit) and selectively apply that to the index, to implement \"undoing changes made to the index selectively\".\n\nI think it preserves the UI consistency better to enhance checkout and reset than adding new commands to do conceptually the same thing. Unfortunately I don't know how hard the necessary change will be, because these two commands are now implemented in C...\n\n-- \nNanako Shiraishi\nhttp://ivory.ap.teacup.com/nanako3/\n"},{"id":"118684","messageId":"200907250117.31847.trast@student.ethz.ch","threadId":"20208","inReplyTo":"20090725065822.6117@nanako3.lavabit.com","subject":"Re: [RFC PATCH] Implement unstage and reset modes for git-add--interactive","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-24T23:17:28Z","receivedAt":"2009-07-24T23:17:28Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Nanako Shiraishi wrote:\n> Quoting Junio C Hamano <gitster@pobox.com>\n> \n> > \"resetting index entries selectively\" makes perfect sense.  So do\n> > \"checking out index entries selectively\" and \"adding to index\n> > selectively\".\n> \n> Are you saying that you are fine with the concept of Thomas'es patch\n> series but you don't want to see different words used to name these\n> operations?\n> \n> In other words, do you mean the following would be a pair of better\n> companions to \"git add -p $file\" than Thomas'es discard and unstage?\n> \n>  - \"git checkout -p $file\" and \"git checkout -p $commit $file\" [...]\n>  - \"git reset -p $file\" and \"git reset -p $commit $file\" [...]\n\nI'd be quite interested to hear some opinions on this.  I wasn't sure\nwhat to do and eventually opted for unstage/discard because I felt\n'git checkout -p $file' was not an accurate description of what it\ndoes, but maybe I just have the mental blocks in the wrong places.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"118685","messageId":"7vzlat64u1.fsf@alter.siamese.dyndns.org","threadId":"20208","inReplyTo":"20090725065822.6117@nanako3.lavabit.com","subject":"Re: [RFC PATCH] Implement unstage and reset modes for git-add--interactive","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-24T23:25:58Z","receivedAt":"2009-07-24T23:25:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nanako Shiraishi <nanako3@lavabit.com> writes:\n\n> Quoting Junio C Hamano <gitster@pobox.com>\n>\n>> \"resetting index entries selectively\" makes perfect sense.  So do\n>> \"checking out index entries selectively\" and \"adding to index\n>> selectively\".\n>\n> Are you saying that you are fine with the concept of Thomas'es patch\n> series but you don't want to see different words used to name these\n> operations?\n\nEssentially, yes.\n\nCould you please wrap your lines to a reasonable length, by the way?\n\n> In other words, do you mean the following would be a pair of better\n> companions to \"git add -p $file\" than Thomas'es discard and unstage?\n>\n>  - \"git checkout -p $file\" and \"git checkout -p $commit $file\" that let\n>  you view the patch to bring the file in the working tree to the version\n>  in the index (or the commit) and selectively apply that to the working\n>  tree and the index, to implement \"discarding changes selectively\".\n>\n>  - \"git reset -p $file\" and \"git reset -p $commit $file\" that let you\n>  view the patch to bring the version of the file in the index to the\n>  version in the HEAD (or the commit) and selectively apply that to the\n>  index, to implement \"undoing changes made to the index selectively\".\n>\n> I think it preserves the UI consistency better to enhance checkout and\n> reset than adding new commands to do conceptually the same\n> thing...\n\nI didn't read Thomas's series beyond the cover letter, but I'd say the\nabove three (counting \"add -p\" in the mix) extends the existing concepts\nin a natural way:\n\n - \"add\" goes from work tree to the index;\n\n - \"checkout\" goes from commit or index to the work tree; and\n\n - \"reset\" goes from commit to index.\n\nThe \"-p\" variant (which exists to \"add\" but new to \"checkout\" and \"reset\")\nallows you to do these movements in a finer grained manner than per\nwhole-file.\n\n> ... Unfortunately I don't know how hard the necessary change will be,\n> because these two commands are now implemented in C...\n\nThat's OK.  There are others on the list who groks C, like Thomas ;-)\n"},{"id":"118698","messageId":"7v1vo537m0.fsf@alter.siamese.dyndns.org","threadId":"20208","inReplyTo":"20090724170622.GA20247@sigill.intra.peff.net","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-25T00:54:15Z","receivedAt":"2009-07-25T00:54:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Perhaps it makes sense to have an interactive stash rather than an\n> interactive revert? Then the reverts that you make are still being saved\n> somewhere, and you can recover from an error by applying the stash. Not\n> to mention that interactive stash is useful in its own right.\n>\n> The downside is that if you are the sort of person who keeps a clean\n> stash list (and I am not such a person), then you have this\n> \"to-be-deleted\" cruft on the top of your stash (whereas with a true\n> revert, it just goes away).\n\nYeah, such a stash entry would be more like \"trash can\".  It is not\n\"to-be-deleted\" but \"have been deleted, but you _could_ resurrect\".\n\nIt may not be a bad idea to do it that way, or perhaps \"git checkout -p\"\ncan automatically create such a trash can while undoing the local changes\nin the work tree.\n"},{"id":"118708","messageId":"200907251135.33164.trast@student.ethz.ch","threadId":"20208","inReplyTo":"7v1vo537m0.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-25T09:35:31Z","receivedAt":"2009-07-25T09:35:31Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano wrote:\n> \n> Yeah, such a stash entry would be more like \"trash can\".  It is not\n> \"to-be-deleted\" but \"have been deleted, but you _could_ resurrect\".\n> \n> It may not be a bad idea to do it that way, or perhaps \"git checkout -p\"\n> can automatically create such a trash can while undoing the local changes\n> in the work tree.\n\nI'd rather implement this as part of the generic \"worktree/index log\"\nthat was proposed a while back, where any index- or\nworktree-overwriting operation that actually discards data would save\nthe lost state in a special reflog.  That way people won't complain\nbecause 'git checkout -p -- $file' <press a> saved their state, but\n'git checkout -- $file' didn't, even though they should be equivalent.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"118726","messageId":"20090725144803.GA18545@artemis.corp","threadId":"20208","inReplyTo":"20090724170622.GA20247@sigill.intra.peff.net","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Pierre Habouzit","fromEmail":"madcoder@madism.org","sentAt":"2009-07-25T14:48:03Z","receivedAt":"2009-07-25T14:48:03Z","isPatch":true,"sender":{"key":"madcoder@madism.org","avatar":null},"body":"On Fri, Jul 24, 2009 at 01:06:22PM -0400, Jeff King wrote:\n> On Fri, Jul 24, 2009 at 09:06:16AM -0700, Junio C Hamano wrote:\n> \n> > In your arsenal, you have \"git add -p\" to help you sift good pieces from\n> > other parts in finer grained manner, instead of having to make an all or\n> > nothing decision per file basis (i.e. \"git add file\").  But \"git add -p\"\n> > (and \"git add -i\") is still about the \"git add\" step in the above high\n> > level view.  You have a mixture of good and not so good changes in your\n> > work tree, and you pick only good pieces to add to the index, _knowing_\n> > that you can go back and redo this step safely exactly because your work\n> > tree will stay the same even if you did make mistakes.\n> > \n> > The proposed change breaks this expectation you would have naturally\n> > gained during the course of becoming more and more proficient in using\n> > git.\n> > \n> > In other words, I do not think you can say that the change will not harm\n> > the experts due to both the points 2 (experts can easily make typo) and 3\n> > above (the change breaks the mental model of the world experts would have\n> > formed).\n> > \n> > Having said all that, it indeed would be useful to selectively revert\n> > changes from the work tree files.\n> \n> Perhaps it makes sense to have an interactive stash rather than an\n> interactive revert?\n\nVery cool idea, it's even better than creating a revert patch.\n\nNote that to undermine the \"dirty stash list\" effect we could have\nquite easily different stash queues if it's badly needed, so it sounds\nlike a moot point to me.\n-- \nIntersec <http://www.intersec.com>\nPierre Habouzit <pierre.habouzit@intersec.com>\nTél : +33 (0)1 5570 3346\nMob : +33 (0)6 1636 8131\nFax : +33 (0)1 5570 3332\n37 Rue Pierre Lhomme\n92400 Courbevoie\n"},{"id":"118727","messageId":"20090725145237.GB18545@artemis.corp","threadId":"20208","inReplyTo":"7v8wienk07.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Pierre Habouzit","fromEmail":"madcoder@madism.org","sentAt":"2009-07-25T14:52:37Z","receivedAt":"2009-07-25T14:52:37Z","isPatch":true,"sender":{"key":"madcoder@madism.org","avatar":null},"body":"On Fri, Jul 24, 2009 at 09:06:16AM -0700, Junio C Hamano wrote:\n> Even though you could add good bits interactively, making multiple\n> commits, and remove the remaining debugging cruft at the very end with\n> \"git checkout $files\" or \"git reset --hard\", if there are debugging crufts\n> for two or more phases of development,\n\nFWIW it's what I was doing so far, and it's not very efficient for many\npatterns, you talked about the bit where you want to keep some of the\ndebug, for this one I used to do that:\n\nwhile I have meaning full commits to do:\n    git add -p; commit;\ngit add -p the things I want to trash and commit\ngit stash\ngit reset --hard HEAD~1\ngit stash apply\n\nThat sucks.\n\nAnother thing is that: if you have _many_ commits to do, you have to\nrefuse the same hunks (or worse edit) all over the place. That's awful.\nWith an \"undo\", you look at them once only. That's a lot of thinking\nsaved, and when you have a lot of hunks, less chances to mess an answer\n(who here hasn't typed 'y' where he meant 'n' at least once ...)\n\n\nFor all those reasons I believe it's a good thing to be able to have\nsomething to remove hunks from the working-directory. Jeff's suggestions\nto move them to some stash is the best suggestion so far, and is safe.\n\n-- \nIntersec <http://www.intersec.com>\nPierre Habouzit <pierre.habouzit@intersec.com>\nTél : +33 (0)1 5570 3346\nMob : +33 (0)6 1636 8131\nFax : +33 (0)1 5570 3332\n37 Rue Pierre Lhomme\n92400 Courbevoie\n"},{"id":"118728","messageId":"20090725145819.GC18545@artemis.corp","threadId":"20208","inReplyTo":"cf51e6aa92ea98fc9409826cc0468a01fdf7b01e.1248452530.git.trast@student.ethz.ch","subject":"Re: [RFC PATCH v2 2/3] Introduce git-discard","fromName":"Pierre Habouzit","fromEmail":"madcoder@madism.org","sentAt":"2009-07-25T14:58:20Z","receivedAt":"2009-07-25T14:58:20Z","isPatch":true,"sender":{"key":"madcoder@madism.org","avatar":null},"body":"On Fri, Jul 24, 2009 at 06:24:35PM +0200, Thomas Rast wrote:\n> +*WARNING:* All unstaged changes to the 'paths' are *irreversibly*\n> +lost.\n\nFWIW, as git checkout -- <path> is already doing that, and that\ngit-discard is clearly something meant as some UI sugar, I would make\nthat operation a \"safe\" porcelain by puting the discarded hunks in a\nstash.\n\nI'm shamelessly stealing this idea from Jeff, but it's probably an\nexcellent idea to have a strash¹. It makes git even safer to use,\nespecially to beginners, to whom we would talk about git-discard instead\nof git checkout.\n\n¹: stash-trash\n-- \nIntersec <http://www.intersec.com>\nPierre Habouzit <pierre.habouzit@intersec.com>\nTél : +33 (0)1 5570 3346\nMob : +33 (0)6 1636 8131\nFax : +33 (0)1 5570 3332\n37 Rue Pierre Lhomme\n92400 Courbevoie\n"},{"id":"118788","messageId":"cover.1248557241.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"7vzlat64u1.fsf@alter.siamese.dyndns.org","subject":"[RFC PATCH v3 0/5] {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-25T21:29:29Z","receivedAt":"2009-07-25T21:29:29Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano wrote:\n> > ... Unfortunately I don't know how hard the necessary change will be,\n> > because these two commands are now implemented in C...\n> \n> That's OK.  There are others on the list who groks C, like Thomas ;-)\n\nWell, then let's see if your trust is justified ;-)\n\n>  - \"add\" goes from work tree to the index;\n> \n>  - \"reset\" goes from commit to index.\n\nLuckily \"reset\" is exactly symmetrical to \"add\", so that's not too\nhard.\n\n>  - \"checkout\" goes from commit or index to the work tree; and\n\nUnfortunately there are some subtleties here.  In the 'git checkout\nHEAD -- $file' case, we overwrite both worktree and index.  So\nmorally, if you select a hunk for checking out it should be reverse\napplied to both, too.  However, that may not always be possible.\n\n[One possible change to checkout -p would be to reverse the direction\nof hunks as shown.  Currently it's more in the \"discard\" spirit: you\nmade the change shown in this hunk, saying 'y' will throw this change\naway.]\n\nSimilarly, \"stash\" has some problems: we want to encode the changes\nHEAD..index into one commit, and index..worktree into another.\nHowever, these patches may not apply on top of each other depending on\nwhat hunks were selected.  I see three options:\n\n* Make more a priori restrictions, such as, --patch is strictly about\n  the worktree and simply refuses to stash anything if you have staged\n  changes; or, we only deal with the worktree and always stash the\n  index whole.  I think at least the first option would make it\n  significantly less useful though.\n\n* Hope that it works out, and catch failure later.  This is what it\n  currently does.\n\n* Expand the stash format to four commits so that, e.g.,\n  stash^1..stash^2 is HEAD..index and stash^3..stash is\n  index..worktree.  (Currently stash^1 is HEAD, stash^2 is index and\n  stash is worktree.)  This would require more changes, and make these\n  stashes backward incompatible w.r.t. application, so I'm not sure it\n  is worth the trouble.\n\nYeah, there still aren't any tests, sorry.  I'm not sure I will get\naround to it this weekend, but then it's -rc anyway and we're in no\nhurry.\n\n\nThomas Rast (5):\n  git-apply--interactive: Refactor patch mode code\n  builtin-add: refactor the meat of interactive_add()\n  Implement 'git reset --patch'\n  Implement 'git checkout --patch'\n  Implement 'git stash save --patch'\n\n Documentation/git-checkout.txt |   13 +++-\n Documentation/git-reset.txt    |   15 +++-\n Documentation/git-stash.txt    |   10 ++-\n builtin-add.c                  |   43 ++++++---\n builtin-checkout.c             |   19 ++++\n builtin-reset.c                |   15 +++\n commit.h                       |    2 +\n git-add--interactive.perl      |  189 +++++++++++++++++++++++++++++++++-------\n git-stash.sh                   |  120 +++++++++++++++++++++-----\n 9 files changed, 355 insertions(+), 71 deletions(-)\n"},{"id":"118791","messageId":"4392f03ec6461f51133a10c4c74d2fda55e84cff.1248557241.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248557241.git.trast@student.ethz.ch","subject":"[RFC PATCH v3 1/5] git-apply--interactive: Refactor patch mode code","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-25T21:29:30Z","receivedAt":"2009-07-25T21:29:30Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This makes some aspects of the 'git add -p' loop configurable (within\nthe code), so that we can later reuse git-add--interactive for other\nsimilar tools.\n\nMost fields are fairly straightforward, but APPLY gets a subroutine\n(instead of just a string a la 'apply --cached') so that we can handle\n'checkout -p', which will need to atomically apply the patch twice\n(index and worktree).\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n git-add--interactive.perl |   79 +++++++++++++++++++++++++++++---------------\n 1 files changed, 52 insertions(+), 27 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex df9f231..58c3332 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -73,6 +73,21 @@\n # command line options\n my $patch_mode;\n \n+sub apply_patch;\n+\n+my %patch_modes = (\n+\t'stage' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => sub { apply_patch 'apply --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Stage',\n+\t\tPARTICIPLE => 'staging',\n+\t\tFILTER => 'file-only',\n+\t},\n+);\n+\n+my %patch_mode_flavour = %{$patch_modes{stage}};\n+\n sub run_cmd_pipe {\n \tif ($^O eq 'MSWin32' || $^O eq 'msys') {\n \t\tmy @invalid = grep {m/[\":*]/} @_;\n@@ -613,12 +628,21 @@\n \tprint \"\\n\";\n }\n \n+sub run_git_apply {\n+\tmy $cmd = shift;\n+\tmy $fh;\n+\topen $fh, '| git ' . $cmd;\n+\tprint $fh @_;\n+\treturn close $fh;\n+}\n+\n sub parse_diff {\n \tmy ($path) = @_;\n-\tmy @diff = run_cmd_pipe(qw(git diff-files -p --), $path);\n+\tmy @diff_cmd = split(\" \", $patch_mode_flavour{DIFF});\n+\tmy @diff = run_cmd_pipe(\"git\", @diff_cmd, \"--\", $path);\n \tmy @colored = ();\n \tif ($diff_use_color) {\n-\t\t@colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);\n+\t\t@colored = run_cmd_pipe(\"git\", @diff_cmd, qw(--color --), $path);\n \t}\n \tmy (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };\n \n@@ -877,6 +901,7 @@\n \t\tor die \"failed to open hunk edit file for writing: \" . $!;\n \tprint $fh \"# Manual hunk edit mode -- see bottom for a quick guide\\n\";\n \tprint $fh @$oldtext;\n+\tmy $participle = $patch_mode_flavour{PARTICIPLE};\n \tprint $fh <<EOF;\n # ---\n # To remove '-' lines, make them ' ' lines (context).\n@@ -884,7 +909,7 @@\n # Lines starting with # will be removed.\n #\n # If the patch applies cleanly, the edited hunk will immediately be\n-# marked for staging. If it does not apply cleanly, you will be given\n+# marked for $participle. If it does not apply cleanly, you will be given\n # an opportunity to edit again. If all lines of the hunk are removed,\n # then the edit is aborted and the hunk is left unchanged.\n EOF\n@@ -918,11 +943,8 @@\n \n sub diff_applies {\n \tmy $fh;\n-\topen $fh, '| git apply --recount --cached --check';\n-\tfor my $h (@_) {\n-\t\tprint $fh @{$h->{TEXT}};\n-\t}\n-\treturn close $fh;\n+\treturn run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check',\n+\t\t\t     map { @{$_->{TEXT}} } @_);\n }\n \n sub _restore_terminal_and_die {\n@@ -988,12 +1010,13 @@\n }\n \n sub help_patch_cmd {\n-\tprint colored $help_color, <<\\EOF ;\n-y - stage this hunk\n-n - do not stage this hunk\n-q - quit, do not stage this hunk nor any of the remaining ones\n-a - stage this and all the remaining hunks in the file\n-d - do not stage this hunk nor any of the remaining hunks in the file\n+\tmy $verb = lc $patch_mode_flavour{VERB};\n+\tprint colored $help_color, <<EOF ;\n+y - $verb this hunk\n+n - do not $verb this hunk\n+q - quit, do not $verb this hunk nor any of the remaining ones\n+a - $verb this and all the remaining hunks in the file\n+d - do not $verb this hunk nor any of the remaining hunks in the file\n g - select a hunk to go to\n / - search for a hunk matching the given regex\n j - leave this hunk undecided, see next undecided hunk\n@@ -1006,8 +1029,17 @@\n EOF\n }\n \n+sub apply_patch {\n+\tmy $cmd = shift;\n+\tmy $ret = run_git_apply $cmd . ' --recount', @_;\n+\tif (!$ret) {\n+\t\tprint STDERR @_;\n+\t}\n+\treturn $ret;\n+}\n+\n sub patch_update_cmd {\n-\tmy @all_mods = list_modified('file-only');\n+\tmy @all_mods = list_modified($patch_mode_flavour{FILTER});\n \tmy @mods = grep { !($_->{BINARY}) } @all_mods;\n \tmy @them;\n \n@@ -1138,8 +1170,8 @@\n \t\tfor (@{$hunk[$ix]{DISPLAY}}) {\n \t\t\tprint;\n \t\t}\n-\t\tprint colored $prompt_color, 'Stage ',\n-\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),\n+\t\tprint colored $prompt_color, $patch_mode_flavour{VERB},\n+\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'),\n \t\t  \" [y,n,q,a,d,/$other,?]? \";\n \t\tmy $line = prompt_single_character;\n \t\tif ($line) {\n@@ -1313,16 +1345,9 @@\n \n \tif (@result) {\n \t\tmy $fh;\n-\n-\t\topen $fh, '| git apply --cached --recount';\n-\t\tfor (@{$head->{TEXT}}, @result) {\n-\t\t\tprint $fh $_;\n-\t\t}\n-\t\tif (!close $fh) {\n-\t\t\tfor (@{$head->{TEXT}}, @result) {\n-\t\t\t\tprint STDERR $_;\n-\t\t\t}\n-\t\t}\n+\t\tmy @patch = (@{$head->{TEXT}}, @result);\n+\t\tmy $apply_routine = $patch_mode_flavour{APPLY};\n+\t\t&$apply_routine(@patch);\n \t\trefresh();\n \t}\n \n-- \n1.6.4.rc2.227.gf5e17\n"},{"id":"118786","messageId":"35bee46b2361c0af841bbc844ce2f76c3ebbeb3e.1248557241.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248557241.git.trast@student.ethz.ch","subject":"[RFC PATCH v3 2/5] builtin-add: refactor the meat of interactive_add()","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-25T21:29:31Z","receivedAt":"2009-07-25T21:29:31Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This moves the call setup for 'git add--interactive' to a separate\nfunction, as other users will call it without running\nvalidate_pathspec() first.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n builtin-add.c |   43 +++++++++++++++++++++++++++++--------------\n commit.h      |    2 ++\n 2 files changed, 31 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin-add.c b/builtin-add.c\nindex 581a2a1..c422a62 100644\n--- a/builtin-add.c\n+++ b/builtin-add.c\n@@ -131,27 +131,27 @@ static void refresh(int verbose, const char **pathspec)\n \treturn pathspec;\n }\n \n-int interactive_add(int argc, const char **argv, const char *prefix)\n+int run_add_interactive(const char *revision, const char *patch_mode,\n+\t\t\tconst char **pathspec)\n {\n-\tint status, ac;\n+\tint status, ac, pc = 0;\n \tconst char **args;\n-\tconst char **pathspec = NULL;\n \n-\tif (argc) {\n-\t\tpathspec = validate_pathspec(argc, argv, prefix);\n-\t\tif (!pathspec)\n-\t\t\treturn -1;\n-\t}\n+\tif (pathspec)\n+\t\twhile (pathspec[pc])\n+\t\t\tpc++;\n \n-\targs = xcalloc(sizeof(const char *), (argc + 4));\n+\targs = xcalloc(sizeof(const char *), (pc + 5));\n \tac = 0;\n \targs[ac++] = \"add--interactive\";\n-\tif (patch_interactive)\n-\t\targs[ac++] = \"--patch\";\n+\tif (patch_mode)\n+\t\targs[ac++] = patch_mode;\n+\tif (revision)\n+\t\targs[ac++] = revision;\n \targs[ac++] = \"--\";\n-\tif (argc) {\n-\t\tmemcpy(&(args[ac]), pathspec, sizeof(const char *) * argc);\n-\t\tac += argc;\n+\tif (pc) {\n+\t\tmemcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);\n+\t\tac += pc;\n \t}\n \targs[ac] = NULL;\n \n@@ -160,6 +160,21 @@ int interactive_add(int argc, const char **argv, const char *prefix)\n \treturn status;\n }\n \n+int interactive_add(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char **pathspec = NULL;\n+\n+\tif (argc) {\n+\t\tpathspec = validate_pathspec(argc, argv, prefix);\n+\t\tif (!pathspec)\n+\t\t\treturn -1;\n+\t}\n+\n+\treturn run_add_interactive(NULL,\n+\t\t\t\t   patch_interactive ? \"--patch\" : NULL,\n+\t\t\t\t   pathspec);\n+}\n+\n static int edit_patch(int argc, const char **argv, const char *prefix)\n {\n \tchar *file = xstrdup(git_path(\"ADD_EDIT.patch\"));\ndiff --git a/commit.h b/commit.h\nindex 8bfdf0e..0555e80 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -139,6 +139,8 @@ struct commit_graft {\n int in_merge_bases(struct commit *, struct commit **, int);\n \n extern int interactive_add(int argc, const char **argv, const char *prefix);\n+extern int run_add_interactive(const char *revision, const char *patch_mode,\n+\t\t\t       const char **pathspec);\n \n static inline int single_parent(struct commit *commit)\n {\n-- \n1.6.4.rc2.227.gf5e17\n"},{"id":"118787","messageId":"9a85f03f0205fd97ee9dd11d1f11c94be063aa40.1248557241.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248557241.git.trast@student.ethz.ch","subject":"[RFC PATCH v3 3/5] Implement 'git reset --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-25T21:29:32Z","receivedAt":"2009-07-25T21:29:32Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This introduces a --patch mode for git-reset.  The basic case is\n\n  git reset --patch -- [files...]\n\nwhich acts as the opposite of 'git add --patch -- [files...]': it\noffers hunks for *un*staging.  Advanced usage is\n\n  git reset --patch <revision> -- [files...]\n\nwhich offers hunks from the diff between <revision> and the index for\nreverse application to the index.  (That is, the basic case is just\n<revision> = HEAD.)  This means it can be used to \"undo\" changes since\n<revision> in the index, but may be slightly confusing when <revision>\nis logically \"newer\" than the index state.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-reset.txt |   15 +++++++++++++--\n builtin-reset.c             |   15 +++++++++++++++\n git-add--interactive.perl   |   36 +++++++++++++++++++++++++++++++++---\n 3 files changed, 61 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt\nindex abb25d1..469cf6d 100644\n--- a/Documentation/git-reset.txt\n+++ b/Documentation/git-reset.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n [verse]\n 'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]\n 'git reset' [-q] [<commit>] [--] <paths>...\n+'git reset' --patch [<commit>] [--] [<paths>...]\n \n DESCRIPTION\n -----------\n@@ -23,8 +24,9 @@ the undo in the history.\n If you want to undo a commit other than the latest on a branch,\n linkgit:git-revert[1] is your friend.\n \n-The second form with 'paths' is used to revert selected paths in\n-the index from a given commit, without moving HEAD.\n+The second and third forms with 'paths' and/or --patch are used to\n+revert selected paths in the index from a given commit, without moving\n+HEAD.\n \n \n OPTIONS\n@@ -50,6 +52,15 @@ OPTIONS\n \tand updates the files that are different between the named commit\n \tand the current commit in the working tree.\n \n+-p::\n+--patch::\n+\tInteractively select hunks in the difference between the index\n+\tand <commit> (defaults to HEAD).  The chosen hunks are applied\n+\tin reverse to the index.\n++\n+This means that `git reset -p` is the opposite of `git add -p` (see\n+linkgit:git-add[1]).\n+\n -q::\n \tBe quiet, only report errors.\n \ndiff --git a/builtin-reset.c b/builtin-reset.c\nindex 5fa1789..e4fef5d 100644\n--- a/builtin-reset.c\n+++ b/builtin-reset.c\n@@ -142,6 +142,13 @@ static void update_index_from_diff(struct diff_queue_struct *q,\n \t}\n }\n \n+static int interactive_reset(const char *revision, const char **argv,\n+\t\t\t     const char *prefix)\n+{\n+\treturn run_add_interactive(revision, \"--patch=reset\",\n+\t\t\t\t   get_pathspec(prefix, argv));\n+}\n+\n static int read_from_tree(const char *prefix, const char **argv,\n \t\tunsigned char *tree_sha1, int refresh_flags)\n {\n@@ -183,6 +190,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)\n int cmd_reset(int argc, const char **argv, const char *prefix)\n {\n \tint i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;\n+\tint patch_mode = 0;\n \tconst char *rev = \"HEAD\";\n \tunsigned char sha1[20], *orig = NULL, sha1_orig[20],\n \t\t\t\t*old_orig = NULL, sha1_old_orig[20];\n@@ -198,6 +206,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\t\t\t\"reset HEAD, index and working tree\", MERGE),\n \t\tOPT_BOOLEAN('q', NULL, &quiet,\n \t\t\t\t\"disable showing new HEAD in hard reset and progress message\"),\n+\t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\tOPT_END()\n \t};\n \n@@ -251,6 +260,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\tdie(\"Could not parse object '%s'.\", rev);\n \thashcpy(sha1, commit->object.sha1);\n \n+\tif (patch_mode) {\n+\t\tif (reset_type != NONE)\n+\t\t\tdie(\"--patch is incompatible with --{hard,mixed,soft}\");\n+\t\treturn interactive_reset(rev, argv, prefix);\n+\t}\n+\n \t/* git reset tree [--] paths... can be used to\n \t * load chosen paths from the tree into the index without\n \t * affecting the working tree nor HEAD. */\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 58c3332..b1aa846 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -72,6 +72,7 @@\n \n # command line options\n my $patch_mode;\n+my $patch_mode_revision;\n \n sub apply_patch;\n \n@@ -84,6 +85,14 @@\n \t\tPARTICIPLE => 'staging',\n \t\tFILTER => 'file-only',\n \t},\n+\t'reset' => {\n+\t\tDIFF => 'diff-index -p --cached',\n+\t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply -R --cached',\n+\t\tVERB => 'Reset',\n+\t\tPARTICIPLE => 'resetting',\n+\t\tFILTER => 'index-only',\n+\t},\n );\n \n my %patch_mode_flavour = %{$patch_modes{stage}};\n@@ -639,6 +648,9 @@\n sub parse_diff {\n \tmy ($path) = @_;\n \tmy @diff_cmd = split(\" \", $patch_mode_flavour{DIFF});\n+\tif (defined $patch_mode_revision) {\n+\t\tpush @diff_cmd, $patch_mode_revision;\n+\t}\n \tmy @diff = run_cmd_pipe(\"git\", @diff_cmd, \"--\", $path);\n \tmy @colored = ();\n \tif ($diff_use_color) {\n@@ -1388,11 +1400,29 @@\n sub process_args {\n \treturn unless @ARGV;\n \tmy $arg = shift @ARGV;\n-\tif ($arg eq \"--patch\") {\n-\t\t$patch_mode = 1;\n-\t\t$arg = shift @ARGV or die \"missing --\";\n+\tif ($arg =~ /--patch(?:=(.*))?/) {\n+\t\tif (defined $1) {\n+\t\t\tif ($1 eq 'reset') {\n+\t\t\t\t$patch_mode = 'reset';\n+\t\t\t\t$patch_mode_revision = 'HEAD';\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\tif ($arg ne '--') {\n+\t\t\t\t\t$patch_mode_revision = $arg;\n+\t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\t}\n+\t\t\t} elsif ($1 eq 'stage') {\n+\t\t\t\t$patch_mode = 'stage';\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t} else {\n+\t\t\t\tdie \"unknown --patch mode: $1\";\n+\t\t\t}\n+\t\t} else {\n+\t\t\t$patch_mode = 'stage';\n+\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t}\n \t\tdie \"invalid argument $arg, expecting --\"\n \t\t    unless $arg eq \"--\";\n+\t\t%patch_mode_flavour = %{$patch_modes{$patch_mode}};\n \t}\n \telsif ($arg ne \"--\") {\n \t\tdie \"invalid argument $arg, expecting --\";\n-- \n1.6.4.rc2.227.gf5e17\n"},{"id":"118790","messageId":"93da49abd5fc514906bac73553d6802eb7fb69fd.1248557241.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248557241.git.trast@student.ethz.ch","subject":"[RFC PATCH v3 4/5] Implement 'git checkout --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-25T21:29:33Z","receivedAt":"2009-07-25T21:29:33Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This introduces a --patch mode for git-checkout.  In the index usage\n\n  git checkout --patch -- [files...]\n\nit lets the user discard edits from the <files> at the granularity of\nhunks (by selecting hunks from 'git diff' and then reverse applying\nthem to the worktree).\n\nWe also accept a revision argument\n\n  git checkout --patch <revision> -- [files...]\n\nwhich offers hunks from the difference between the <revision> and the\nworktree.  The chosen hunks are then reverse applied to both index and\nworktree, discarding them completely.  This application is done\n\"atomically\" in the sense that we first check if the patch applies to\nthe index (it should always apply to the worktree).  If it does not,\nwe give the user a choice to either abort or apply to the worktree\nanyway.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-checkout.txt |   13 ++++++++++-\n builtin-checkout.c             |   19 +++++++++++++++\n git-add--interactive.perl      |   48 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 79 insertions(+), 1 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex ad4b31e..26a5447 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [<branch>]\n 'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n+'git checkout' --patch [<tree-ish>] [--] [<paths>...]\n \n DESCRIPTION\n -----------\n@@ -25,7 +26,7 @@ use the --track or --no-track options, which will be passed to `git\n branch`.  As a convenience, --track without `-b` implies branch\n creation; see the description of --track below.\n \n-When <paths> are given, this command does *not* switch\n+When <paths> or --patch are given, this command does *not* switch\n branches.  It updates the named paths in the working tree from\n the index file, or from a named <tree-ish> (most often a commit).  In\n this case, the `-b` and `--track` options are meaningless and giving\n@@ -113,6 +114,16 @@ the conflicted merge in the specified paths.\n \t\"merge\" (default) and \"diff3\" (in addition to what is shown by\n \t\"merge\" style, shows the original contents).\n \n+-p::\n+--patch::\n+\tInteractively select hunks in the difference between the\n+\t<tree-ish> (or the index, if unspecified) and the working\n+\ttree.  The chosen hunks are then applied in reverse to the\n+\tworking tree (and if a <tree-ish> was specified, the index).\n++\n+This means that you can use `git checkout -p` to selectively discard\n+edits from your current working tree.\n+\n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\n \twhen prepended with \"refs/heads/\", is a valid ref), then that\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex 446cac7..7d57741 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -572,6 +572,13 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\n \treturn git_xmerge_config(var, value, cb);\n }\n \n+static int interactive_checkout(const char *revision, const char **pathspec,\n+\t\t\t\tstruct checkout_opts *opts)\n+{\n+\treturn run_add_interactive(revision, \"--patch=checkout\", pathspec);\n+}\n+\n+\n int cmd_checkout(int argc, const char **argv, const char *prefix)\n {\n \tstruct checkout_opts opts;\n@@ -580,6 +587,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \tstruct branch_info new;\n \tstruct tree *source_tree = NULL;\n \tchar *conflict_style = NULL;\n+\tint patch_mode = 0;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&opts.quiet),\n \t\tOPT_STRING('b', NULL, &opts.new_branch, \"new branch\", \"branch\"),\n@@ -594,6 +602,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOLEAN('m', \"merge\", &opts.merge, \"merge\"),\n \t\tOPT_STRING(0, \"conflict\", &conflict_style, \"style\",\n \t\t\t   \"conflict style (merge or diff3)\"),\n+\t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\tOPT_END(),\n \t};\n \tint has_dash_dash;\n@@ -608,6 +617,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options, checkout_usage,\n \t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n+\tif (patch_mode && (opts.track > 0 || opts.new_branch\n+\t\t\t   || opts.new_branch_log || opts.merge || opts.force))\n+\t\tdie (\"--patch is incompatible with all other options\");\n+\n \t/* --track without -b should DWIM */\n \tif (0 < opts.track && !opts.new_branch) {\n \t\tconst char *argv0 = argv[0];\n@@ -714,6 +727,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tif (!pathspec)\n \t\t\tdie(\"invalid path specification\");\n \n+\t\tif (patch_mode)\n+\t\t\treturn interactive_checkout(NULL, pathspec, &opts);\n+\n \t\t/* Checkout paths */\n \t\tif (opts.new_branch) {\n \t\t\tif (argc == 1) {\n@@ -729,6 +745,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\treturn checkout_paths(source_tree, pathspec, &opts);\n \t}\n \n+\tif (patch_mode)\n+\t\treturn interactive_checkout(new.name, NULL, &opts);\n+\n \tif (opts.new_branch) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (strbuf_check_branch_ref(&buf, opts.new_branch))\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex b1aa846..5005a8d 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -75,6 +75,7 @@\n my $patch_mode_revision;\n \n sub apply_patch;\n+sub apply_patch_for_checkout_commit;\n \n my %patch_modes = (\n \t'stage' => {\n@@ -93,6 +94,22 @@\n \t\tPARTICIPLE => 'resetting',\n \t\tFILTER => 'index-only',\n \t},\n+\t'checkout_index' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => sub { apply_patch 'apply -R', @_; },\n+\t\tAPPLY_CHECK => 'apply -R',\n+\t\tVERB => 'Check out',\n+\t\tPARTICIPLE => 'checking out',\n+\t\tFILTER => 'file-only',\n+\t},\n+\t'checkout_commit' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => \\&apply_patch_for_checkout_commit,\n+\t\tAPPLY_CHECK => 'apply -R',\n+\t\tVERB => 'Check out',\n+\t\tPARTICIPLE => 'checking out',\n+\t\tFILTER => undef,\n+\t},\n );\n \n my %patch_mode_flavour = %{$patch_modes{stage}};\n@@ -1050,6 +1067,28 @@\n \treturn $ret;\n }\n \n+sub apply_patch_for_checkout_commit {\n+\tmy $applies_index = run_git_apply 'apply -R --cached --recount --check', @_;\n+\tmy $applies_worktree = run_git_apply 'apply -R --recount --check', @_;\n+\n+\tif ($applies_worktree && $applies_index) {\n+\t\trun_git_apply 'apply -R --cached --recount', @_;\n+\t\trun_git_apply 'apply -R --recount', @_;\n+\t\treturn 1;\n+\t} elsif (!$applies_index) {\n+\t\tprint colored $error_color, \"The selected hunks do not apply to the index tree!\\n\";\n+\t\tif (prompt_yesno \"Apply them to the worktree anyway? \") {\n+\t\t\treturn run_git_apply 'apply -R --recount', @_;\n+\t\t} else {\n+\t\t\tprint colored $error_color, \"Nothing was applied.\\n\";\n+\t\t\treturn 0;\n+\t\t}\n+\t} else {\n+\t\tprint STDERR @_;\n+\t\treturn 0;\n+\t}\n+}\n+\n sub patch_update_cmd {\n \tmy @all_mods = list_modified($patch_mode_flavour{FILTER});\n \tmy @mods = grep { !($_->{BINARY}) } @all_mods;\n@@ -1410,6 +1449,15 @@\n \t\t\t\t\t$patch_mode_revision = $arg;\n \t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t\t}\n+\t\t\t} elsif ($1 eq 'checkout') {\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\tif ($arg eq '--') {\n+\t\t\t\t\t$patch_mode = 'checkout_index';\n+\t\t\t\t} else {\n+\t\t\t\t\t$patch_mode = 'checkout_commit';\n+\t\t\t\t\t$patch_mode_revision = $arg;\n+\t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\t}\n \t\t\t} elsif ($1 eq 'stage') {\n \t\t\t\t$patch_mode = 'stage';\n \t\t\t\t$arg = shift @ARGV or die \"missing --\";\n-- \n1.6.4.rc2.227.gf5e17\n"},{"id":"118789","messageId":"e5ef2e602347914a7a14d769ca2297223cde8042.1248557241.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248557241.git.trast@student.ethz.ch","subject":"[RFC PATCH v3 5/5] Implement 'git stash save --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-25T21:29:34Z","receivedAt":"2009-07-25T21:29:34Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This adds a hunk-based mode to git-stash.  You can select hunks from\nthe index and the worktree, and git-stash will attempt to build a\nstash that reflects these changes.\n\nInternally, we have the problem that we're trying to offer hunks from\none index (the one the user sees) for inclusion in another index (used\nto build the stash).  We solve this by letting git-add--interactive\nwrite out the hunks to a patch file, and then using git-apply with a\ndifferent GIT_INDEX_FILE.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-stash.txt |   10 +++-\n git-add--interactive.perl   |   30 ++++++++++-\n git-stash.sh                |  120 +++++++++++++++++++++++++++++++++++--------\n 3 files changed, 134 insertions(+), 26 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 1c64a02..e6f310a 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n-'git stash' [save [--keep-index] [-q|--quiet] [<message>]]\n+'git stash' [save [--patch] [--keep-index] [-q|--quiet] [<message>]]\n 'git stash' clear\n 'git stash' create\n \n@@ -42,7 +42,7 @@ is also possible).\n OPTIONS\n -------\n \n-save [--keep-index] [-q|--quiet] [<message>]::\n+save [--patch] [--keep-index] [-q|--quiet] [<message>]::\n \n \tSave your local modifications to a new 'stash', and run `git reset\n \t--hard` to revert them.  This is the default action when no\n@@ -51,6 +51,12 @@ save [--keep-index] [-q|--quiet] [<message>]::\n +\n If the `--keep-index` option is used, all changes already added to the\n index are left intact.\n++\n+With --patch, interactively select hunks of changes to be stashed.\n+This first asks for the hunks to be taken from the HEAD..index\n+difference, and afterwards for hunks from the index..worktree\n+difference.  Then, a stash is constructed that contains these changes,\n+and the changes are removed from the index and worktree, respectively.\n \n list [<options>]::\n \ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 5005a8d..17100d3 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -76,6 +76,7 @@\n \n sub apply_patch;\n sub apply_patch_for_checkout_commit;\n+sub apply_patch_for_stash;\n \n my %patch_modes = (\n \t'stage' => {\n@@ -86,6 +87,22 @@\n \t\tPARTICIPLE => 'staging',\n \t\tFILTER => 'file-only',\n \t},\n+\t'stash_index' => {\n+\t\tDIFF => 'diff-index -p --cached HEAD',\n+\t\tAPPLY => \\&apply_patch_for_stash,\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Stash',\n+\t\tPARTICIPLE => 'stashing',\n+\t\tFILTER => 'index-only',\n+\t},\n+\t'stash_worktree' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => \\&apply_patch_for_stash,\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Stash',\n+\t\tPARTICIPLE => 'stashing',\n+\t\tFILTER => 'file-only',\n+\t},\n \t'reset' => {\n \t\tDIFF => 'diff-index -p --cached',\n \t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n@@ -1067,6 +1084,14 @@\n \treturn $ret;\n }\n \n+sub apply_patch_for_stash {\n+\tmy $fh;\n+\topen $fh, '>>', $ENV{GIT_STASH_TEMP_PATCH}\n+\t\tor die \"cannot open temporary patch file: $!\";\n+\tprint $fh @_;\n+\treturn close $fh;\n+}\n+\n sub apply_patch_for_checkout_commit {\n \tmy $applies_index = run_git_apply 'apply -R --cached --recount --check', @_;\n \tmy $applies_worktree = run_git_apply 'apply -R --recount --check', @_;\n@@ -1458,8 +1483,9 @@\n \t\t\t\t\t$patch_mode_revision = $arg;\n \t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t\t}\n-\t\t\t} elsif ($1 eq 'stage') {\n-\t\t\t\t$patch_mode = 'stage';\n+\t\t\t} elsif ($1 eq 'stage' or $1 eq 'stash_index'\n+\t\t\t\t or $1 eq 'stash_worktree') {\n+\t\t\t\t$patch_mode = $1;\n \t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t} else {\n \t\t\t\tdie \"unknown --patch mode: $1\";\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 03e589f..04c49e8 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -21,6 +21,14 @@ trap 'rm -f \"$TMP-*\"' 0\n \n ref_stash=refs/stash\n \n+if git config --get-colorbool color.interactive; then\n+       help_color=\"$(git config --get-color color.interactive.help 'red bold')\"\n+       reset_color=\"$(git config --get-color '' reset)\"\n+else\n+       help_color=\n+       reset_color=\n+fi\n+\n no_changes () {\n \tgit diff-index --quiet --cached HEAD --ignore-submodules -- &&\n \tgit diff-files --quiet --ignore-submodules\n@@ -62,24 +70,73 @@ create_stash () {\n \tfi\n \tmsg=$(printf '%s: %s' \"$branch\" \"$head\")\n \n-\t# state of the index\n-\ti_tree=$(git write-tree) &&\n-\ti_commit=$(printf 'index on %s\\n' \"$msg\" |\n-\t\tgit commit-tree $i_tree -p $b_commit) ||\n-\t\tdie \"Cannot save the current index state\"\n-\n-\t# state of the working tree\n-\tw_tree=$( (\n-\t\trm -f \"$TMP-index\" &&\n-\t\tcp -p ${GIT_INDEX_FILE-\"$GIT_DIR/index\"} \"$TMP-index\" &&\n-\t\tGIT_INDEX_FILE=\"$TMP-index\" &&\n-\t\texport GIT_INDEX_FILE &&\n-\t\tgit read-tree -m $i_tree &&\n-\t\tgit add -u &&\n-\t\tgit write-tree &&\n-\t\trm -f \"$TMP-index\"\n-\t) ) ||\n-\t\tdie \"Cannot save the current worktree state\"\n+\tif test -z \"$patch_mode\"\n+\tthen\n+\n+\t\t# state of the index\n+\t\ti_tree=$(git write-tree) &&\n+\t\ti_commit=$(printf 'index on %s\\n' \"$msg\" |\n+\t\t\tgit commit-tree $i_tree -p $b_commit) ||\n+\t\t\tdie \"Cannot save the current index state\"\n+\n+\t\t# state of the working tree\n+\t\tw_tree=$( (\n+\t\t\trm -f \"$TMP-index\" &&\n+\t\t\tcp -p ${GIT_INDEX_FILE-\"$GIT_DIR/index\"} \"$TMP-index\" &&\n+\t\t\tGIT_INDEX_FILE=\"$TMP-index\" &&\n+\t\t\texport GIT_INDEX_FILE &&\n+\t\t\tgit read-tree -m $i_tree &&\n+\t\t\tgit add -u &&\n+\t\t\tgit write-tree &&\n+\t\t\trm -f \"$TMP-index\"\n+\t\t) ) ||\n+\t\t\tdie \"Cannot save the current worktree state\"\n+\n+\telse\n+\n+\t\t# find out what the user wants\n+\t\techo\n+\t\techo \"${help_color}stash --patch: index changes${reset_color}\"\n+\t\techo\n+\t\t: > \"$TMP-patch-i\"\n+\t\tGIT_STASH_TEMP_PATCH=\"$TMP-patch-i\" \\\n+\t\t\tgit add--interactive --patch=stash_index --\n+\t\techo \"${help_color}stash --patch: worktree changes${reset_color}\"\n+\t\techo\n+\t\t: > \"$TMP-patch-w\"\n+\t\tGIT_STASH_TEMP_PATCH=\"$TMP-patch-w\" \\\n+\t\t\tgit add--interactive --patch=stash_worktree --\n+\n+\t\ttest -s \"$TMP-patch-i\" -o -s \"$TMP-patch-w\" ||\n+\t\tdie \"Neither index nor worktree changes selected.\"\n+\n+\t\t# state of the index\n+\t\ti_tree=$( (\n+\t\t\trm -f \"$TMP-index\" &&\n+\t\t\tGIT_INDEX_FILE=\"$TMP-index\" &&\n+\t\t\texport GIT_INDEX_FILE &&\n+\t\t\tgit read-tree --reset HEAD &&\n+\t\t\t( test ! -s \"$TMP-patch-i\" || \\\n+\t\t\t\tgit apply --cached < \"$TMP-patch-i\" ) &&\n+\t\t\tgit write-tree\n+\t\t\t# keep $TMP-index for $w_tree construction\n+\t\t) ) &&\n+\t\ti_commit=$(printf 'index on %s\\n' \"$msg\" |\n+\t\t\tgit commit-tree $i_tree -p $b_commit) ||\n+\t\t\t( cat \"$TMP-patch-i\"; die \"Cannot save the current index state\" )\n+\n+\t\t# state of the working tree\n+\t\tw_tree=$( (\n+\t\t\tGIT_INDEX_FILE=\"$TMP-index\" &&\n+\t\t\texport GIT_INDEX_FILE &&\n+\t\t\t( test ! -s \"$TMP-patch-w\" || \\\n+\t\t\t\tgit apply --cached < \"$TMP-patch-w\" ) &&\n+\t\t\tgit write-tree &&\n+\t\t\trm -f \"$TMP-index\"\n+\t\t) ) ||\n+\t\t\tdie \"Cannot save the current worktree state\"\n+\n+\tfi\n \n \t# create the stash\n \tif test -z \"$stash_msg\"\n@@ -95,12 +152,16 @@ create_stash () {\n \n save_stash () {\n \tkeep_index=\n+\tpatch_mode=\n \twhile test $# != 0\n \tdo\n \t\tcase \"$1\" in\n \t\t--keep-index)\n \t\t\tkeep_index=t\n \t\t\t;;\n+\t\t-p|--patch)\n+\t\t\tpatch_mode=t\n+\t\t\t;;\n \t\t-q|--quiet)\n \t\t\tGIT_QUIET=t\n \t\t\t;;\n@@ -131,11 +192,26 @@ save_stash () {\n \t\tdie \"Cannot save the current status\"\n \tsay Saved working directory and index state \"$stash_msg\"\n \n-\tgit reset --hard ${GIT_QUIET:+-q}\n-\n-\tif test -n \"$keep_index\" && test -n $i_tree\n+\tif test -z \"$patch_mode\"\n \tthen\n-\t\tgit read-tree --reset -u $i_tree\n+\t\tgit reset --hard ${GIT_QUIET:+-q}\n+\n+\t\tif test -n \"$keep_index\" && test -n $i_tree\n+\t\tthen\n+\t\t\tgit read-tree --reset -u $i_tree\n+\t\tfi\n+\telse\n+\t\tif test -s \"$TMP-patch-w\"\n+\t\tthen\n+\t\t\tgit apply -R < \"$TMP-patch-w\" ||\n+\t\t\tdie \"Cannot remove worktree changes\"\n+\t\tfi\n+\n+\t\tif test -z \"$keep_index\" -a -s \"$TMP-patch-i\"\n+\t\tthen\n+\t\t\tgit apply -R --cached < \"$TMP-patch-i\" ||\n+\t\t\tdie \"Cannot remove index changes\"\n+\t\tfi\n \tfi\n }\n \n-- \n1.6.4.rc2.227.gf5e17\n"},{"id":"118814","messageId":"fabb9a1e0907252303y2606d585l41819a74084b58f1@mail.gmail.com","threadId":"20208","inReplyTo":"e5ef2e602347914a7a14d769ca2297223cde8042.1248557241.git.trast@student.ethz.ch","subject":"Re: [RFC PATCH v3 5/5] Implement 'git stash save --patch'","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2009-07-26T06:03:12Z","receivedAt":"2009-07-26T06:03:12Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Sat, Jul 25, 2009 at 14:29, Thomas Rast<trast@student.ethz.ch> wrote:\n> This adds a hunk-based mode to git-stash.  You can select hunks from\n> the index and the worktree, and git-stash will attempt to build a\n> stash that reflects these changes.\n\nAwesome! Does it also remove the stashed hunks from the worktree? From\nwhat I gather from the patch it looks like it, but the commit message\ndoesn't say.\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"118820","messageId":"200907261045.55551.trast@student.ethz.ch","threadId":"20208","inReplyTo":"fabb9a1e0907252303y2606d585l41819a74084b58f1@mail.gmail.com","subject":"Re: [RFC PATCH v3 5/5] Implement 'git stash save --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-26T08:45:54Z","receivedAt":"2009-07-26T08:45:54Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Sverre Rabbelier wrote:\n> \n> On Sat, Jul 25, 2009 at 14:29, Thomas Rast<trast@student.ethz.ch> wrote:\n> > This adds a hunk-based mode to git-stash.  You can select hunks from\n> > the index and the worktree, and git-stash will attempt to build a\n> > stash that reflects these changes.\n> \n> Awesome! Does it also remove the stashed hunks from the worktree? From\n> what I gather from the patch it looks like it, but the commit message\n> doesn't say.\n\nYes, and unless you used --keep-index, also the stashed index-hunks\nfrom the index.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"118833","messageId":"20090726153950.GA16780@sigill.intra.peff.net","threadId":"20208","inReplyTo":"20090725145237.GB18545@artemis.corp","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2009-07-26T15:39:50Z","receivedAt":"2009-07-26T15:39:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Jul 25, 2009 at 04:52:37PM +0200, Pierre Habouzit wrote:\n\n> FWIW it's what I was doing so far, and it's not very efficient for many\n> patterns, you talked about the bit where you want to keep some of the\n> debug, for this one I used to do that:\n> \n> while I have meaning full commits to do:\n>     git add -p; commit;\n> git add -p the things I want to trash and commit\n> git stash\n> git reset --hard HEAD~1\n> git stash apply\n> \n> That sucks.\n> [...]\n> For all those reasons I believe it's a good thing to be able to have\n> something to remove hunks from the working-directory. Jeff's suggestions\n> to move them to some stash is the best suggestion so far, and is safe.\n\nHere's kind of a weird idea I've been considering. Feel free to write it\noff as insane ranting.\n\nMy two complaints about using stash for separating changes are:\n\n  - it lacks the tool support for splitting changes that we have for\n    making commits (like \"add -p\"), and it lacks the ability to build up\n    a set of changes over multiple commands (like we can do for commits)\n\n  - it works as a single destination. You stash and delete a change from\n    the working tree, or you leave it. It's hard to say \"there are 3\n    different types of change here\" and sort them all at once.\n\nMy idea is to instead have a general set of \"registers\" that contain\nstates, each of which is basically an index. You can copy state from\nregister to register, from working tree to register, or from register to\nregister. You can also do any of those moves by looking at differences\nbetween two states and saying \"move this change\" (i.e., like what \"add\n-p\" does for the regular index).\n\nSo one way of splitting changes would be to say:\n\n  1. Set registers 'a' and 'b' to the same state as HEAD\n\n  2. Pick changes from the working tree to go to 'a'\n\n  3. Pick changes from the working tree to go to 'b'\n\n  4. Commit 'a' on top of HEAD\n\n  5. Commit 'b' on top of new HEAD (and this would probably actually\n     mean the changes from 'b' to the old HEAD, not setting the new HEAD\n     state to what's in 'b').\n\nSo it's sort of a generalized form of the index, where you have N \"index\nregisters\" and you sort your changes into them. And during steps 2 and\n3, you could also make more changes, pick them out, etc.\n\nThe workflow you want maps into that pretty simply: you would sort your\nchanges into \"stuff you want to commit\" and \"stuff that is debugging\ncruft\". And then you would just throw away the latter register (or use a\nspecial \"trash\" register).\n\nAnd the workflow I described is \"pick the changes for 'a', then for\n'a'\". But there's no reason you couldn't go through the changes, sorting\neach into \"put this one into 'a', and this one into 'b'\". Which is what\nyou asked for.\n\nIs this really that different from what you proposed? No, I don't really\nthink so in terms of implementation, but it is really about a different\nmental model:\n\n  1. You never delete things. You only copy or move them into registers.\n\n  2. The interface should be the same whether you are moving between\n     registers, or to/from the working tree.\n\n  3. It extends naturally to multiple registers.\n\nAnyway, just some stray thoughts. No code, so feel free to ignore. ;)\n\n-Peff\n"},{"id":"118866","messageId":"20090727082623.GB18268@laphroaig.corp","threadId":"20208","inReplyTo":"20090726153950.GA16780@sigill.intra.peff.net","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Pierre Habouzit","fromEmail":"madcoder@madism.org","sentAt":"2009-07-27T08:26:24Z","receivedAt":"2009-07-27T08:26:24Z","isPatch":true,"sender":{"key":"madcoder@madism.org","avatar":null},"body":"On Sun, Jul 26, 2009 at 11:39:50AM -0400, Jeff King wrote:\n> Is this really that different from what you proposed?\n\nNo it's not, in the sense that what I propose is a subset of your\nproposal, functionaly speaking ;)\n\n> No, I don't really think so in terms of implementation, but it is\n> really about a different mental model:\n> \n>   1. You never delete things. You only copy or move them into registers.\n> \n>   2. The interface should be the same whether you are moving between\n>      registers, or to/from the working tree.\n> \n>   3. It extends naturally to multiple registers.\n\nI like the general idea, I'm unsure what the UI for such tools would\nlook like though.\n-- \nIntersec <http://www.intersec.com>\nPierre Habouzit <pierre.habouzit@intersec.com>\nTél : +33 (0)1 5570 3346\nMob : +33 (0)6 1636 8131\nFax : +33 (0)1 5570 3332\n37 Rue Pierre Lhomme\n92400 Courbevoie\n"},{"id":"118869","messageId":"200907271206.11431.trast@student.ethz.ch","threadId":"20208","inReplyTo":"20090726153950.GA16780@sigill.intra.peff.net","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-27T10:06:10Z","receivedAt":"2009-07-27T10:06:10Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Jeff King wrote:\n> \n>   5. Commit 'b' on top of new HEAD (and this would probably actually\n>      mean the changes from 'b' to the old HEAD, not setting the new HEAD\n>      state to what's in 'b').\n> \n> So it's sort of a generalized form of the index, where you have N \"index\n> registers\" and you sort your changes into them. And during steps 2 and\n> 3, you could also make more changes, pick them out, etc.\n\nI think the parenthetical remark actually contradicts the notion that\nit's an index.  It's more like a place to hold a patch.  Which then\nmakes it rather similar to a temporary branch and cherry-pick, or\ninteractive rebase, or whatever.\n\nGranted, the register idea does not directly map to interactive rebase\nbecause that cannot (automatically) add changes to an older commit.\nSo I frequently wind up making a series of commits along the lines of\n\n  WIP implement foo\n  WIP implement bar\n  WIP fix foo some\n  WIP docs for bar\n  WIP docs for foo\n  WIP tests for foo\n\nand then have to sort and squash them with rebase -i.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"118870","messageId":"200907271210.40001.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248557241.git.trast@student.ethz.ch","subject":"Re: [RFC PATCH v3 0/5] {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-27T10:10:36Z","receivedAt":"2009-07-27T10:10:36Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"I wrote:\n> \n> Similarly, \"stash\" has some problems: we want to encode the changes\n> HEAD..index into one commit, and index..worktree into another.\n> However, these patches may not apply on top of each other depending on\n> what hunks were selected.  I see three options:\n> \n> * Make more a priori restrictions, such as, --patch is strictly about\n>   the worktree and simply refuses to stash anything if you have staged\n>   changes; or, we only deal with the worktree and always stash the\n>   index whole.  I think at least the first option would make it\n>   significantly less useful though.\n\nI'm still not happy with this interface.  How about the following to\nmake it less confusing:\n\n1b) 'stash save -p' defaults to --keep-index (which can be disabled\n    with a new option --no-keep-index).  In --keep-index mode, it only\n    offers hunks from the worktree.\n\nThat way, it's almost analogous to 'git add -p', but for \"adding to\nthe stash\".\n\n> * Hope that it works out, and catch failure later.  This is what it\n>   currently does.\n> \n> * Expand the stash format to four commits so that, e.g.,\n>   stash^1..stash^2 is HEAD..index and stash^3..stash is\n>   index..worktree.  (Currently stash^1 is HEAD, stash^2 is index and\n>   stash is worktree.)  This would require more changes, and make these\n>   stashes backward incompatible w.r.t. application, so I'm not sure it\n>   is worth the trouble.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"118871","messageId":"20090727103031.GA3131@coredump.intra.peff.net","threadId":"20208","inReplyTo":"20090727082623.GB18268@laphroaig.corp","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2009-07-27T10:30:32Z","receivedAt":"2009-07-27T10:30:32Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 27, 2009 at 10:26:24AM +0200, Pierre Habouzit wrote:\n\n> > No, I don't really think so in terms of implementation, but it is\n> > really about a different mental model:\n> > \n> >   1. You never delete things. You only copy or move them into registers.\n> > \n> >   2. The interface should be the same whether you are moving between\n> >      registers, or to/from the working tree.\n> > \n> >   3. It extends naturally to multiple registers.\n> \n> I like the general idea, I'm unsure what the UI for such tools would\n> look like though.\n\nI was thinking of unifying the multiple interfaces that we use to move\ncontent and changes around into a single \"git sort\" and \"git sort -i\"\n(and yes, I just made those names up, so feel free to call them crappy),\nwhich would take a source and a destination (which would probably\ndefault to the working tree and index respectively).\n\nSo you could do the equivalent of:\n\n  - git add foo => git sort foo\n  - git add -p => git sort -i foo\n  - git checkout -- foo => git sort --from=index --to=tree foo\n  - git checkout HEAD -- foo => git sort --from=HEAD --to=tree foo\n  - git reset --mixed => git sort --from=HEAD --to=index\n  - git reset --mixed foo => git sort --from=HEAD --to=index foo\n    (note that this reset doesn't actually exist now, but is something\n    that people try to do)\n  - git stash save => git sort --to=%mystash\n    (and note that I just made up some \"this is a register\" syntax;\n     we maybe would really just want these as refs like refs/registers,\n     so you would specify registers/mystash)\n  - git stash -i => git sort --to=%mystash -i\n    (interactive stash doesn't exist yet, of course)\n  - git stash foo => git sort --to=%mystash foo\n    (partial stash doesn't exist yet)\n  - git diff HEAD stash -- foo | git apply => git sort --from=%mystash foo\n\nSo really, it could be a new way of interacting with the _regular_\nindex, as well, though perhaps it is overboard to completely redesign\nthe git interface. :) I just think it introduces a consistency to the\ninterface around the single concept of \"moving your content around\". So\nin a sense it would be a good candidate for an alternative porcelain.\nBut do note that many common operations are more typing (like the\ncheckout replacements); that would be something to fix.\n\nAgain, just thinking out loud. Feel free to ignore, but if you think\nthere is anything interesting to pick out, let me know.\n\n-Peff\n"},{"id":"118872","messageId":"20090727103609.GB3131@coredump.intra.peff.net","threadId":"20208","inReplyTo":"200907271206.11431.trast@student.ethz.ch","subject":"Re: [PATCH] git-add -p: be able to undo a given hunk","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2009-07-27T10:36:09Z","receivedAt":"2009-07-27T10:36:09Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 27, 2009 at 12:06:10PM +0200, Thomas Rast wrote:\n\n> >   5. Commit 'b' on top of new HEAD (and this would probably actually\n> >      mean the changes from 'b' to the old HEAD, not setting the new HEAD\n> >      state to what's in 'b').\n> > \n> > So it's sort of a generalized form of the index, where you have N \"index\n> > registers\" and you sort your changes into them. And during steps 2 and\n> > 3, you could also make more changes, pick them out, etc.\n> \n> I think the parenthetical remark actually contradicts the notion that\n> it's an index.  It's more like a place to hold a patch.  Which then\n> makes it rather similar to a temporary branch and cherry-pick, or\n> interactive rebase, or whatever.\n\nSort of. It's not an index in the sense that you might make a tree and\ncommit directly from it. But I think of it as an index in that it holds\na particular state, and you can diff that state against other things. I\nwould probably implement it as an index via GIT_INDEX_FILE (though I\nguess performance would not be as nice as if it shared the cache parts\nof the main index).\n\nImplicit in my thinking was that you could actually get rid of the\nconcept of \"the index\" and simply replace it with such a register (which\nmaybe would be the \"default register\" or something). So whether you\ncommitted directly from it, or whether you applied its diff would be\ndecided not at creation time, but at the time you wanted to commit. And\nthat would depend on what operations you were doing (simply making a\ncommit, sorting changes for multiple commits, etc).\n\n> Granted, the register idea does not directly map to interactive rebase\n> because that cannot (automatically) add changes to an older commit.\n> So I frequently wind up making a series of commits along the lines of\n> \n>   WIP implement foo\n>   WIP implement bar\n>   WIP fix foo some\n>   WIP docs for bar\n>   WIP docs for foo\n>   WIP tests for foo\n> \n> and then have to sort and squash them with rebase -i.\n\nYes, I do that a lot. And maybe this whole thing is a stupid idea; git\nalready has lots of tools for working with _commits_, so maybe registers\nshould really just be commits. So in that sense what I am asking for is\njust a multi-headed stash, and tools for doing interactive, incremental\nstashing to those heads.\n\n-Peff\n"},{"id":"118986","messageId":"cover.1248815580.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"200907271210.40001.trast@student.ethz.ch","subject":"[PATCH v4 0/5] {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-28T21:20:06Z","receivedAt":"2009-07-28T21:20:06Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"I wrote:\n> 1b) 'stash save -p' defaults to --keep-index (which can be disabled\n>     with a new option --no-keep-index).  In --keep-index mode, it only\n>     offers hunks from the worktree.\n\nI ended up implementing the even more restricted form, it always\nstashes the index as-is and only offers hunks from the worktree.  This\nis because it finally dawned on me that 'git stash apply' does not\nmerge the changesets base..index and index..worktree, but in fact (in\none go) base..worktree, into the new HEAD.\n\nThat way the rules are much simpler as to what goes where.  The\ndownside of course is that the state of the worktree (with all stashed\nhunks discarded) compared to the index (still as before) can be a bit\nconfusing if you stash areas of files that already have staged\nchanges.\n\nI also finally found some round tuits, and wrote tests.  Which then of\ncourse immediately showed that neither 'git reset -p HEAD^' nor 'git\ncheckout -p HEAD^' ever worked as advertised, so I had to fix that.\nPatches 1/5 and 2/5 are unaffected and still the same.\n\nLast but not least, I rather like Dscho's patch\n\n  http://article.gmane.org/gmane.comp.version-control.git/124182\n\nso I added a small patch to do the same DWIM logic for -p/--patch that\ngoes on top of a merge of 1-5 and his patch.  I suspect it's more work\nto do the merge than to just edit either 5/5 or Dscho's patch,\nhowever.\n\n\nThomas Rast (6):\n  git-apply--interactive: Refactor patch mode code\n  builtin-add: refactor the meat of interactive_add()\n  Implement 'git reset --patch'\n  Implement 'git checkout --patch'\n  Implement 'git stash save --patch'\n  DWIM 'git stash save -p' for 'git stash -p'\n"},{"id":"118991","messageId":"c4714f52f098912e84aa5b478f32d44ed38b0366.1248815580.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248815580.git.trast@student.ethz.ch","subject":"[PATCH v4 1/5] git-apply--interactive: Refactor patch mode code","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-28T21:20:07Z","receivedAt":"2009-07-28T21:20:07Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This makes some aspects of the 'git add -p' loop configurable (within\nthe code), so that we can later reuse git-add--interactive for other\nsimilar tools.\n\nMost fields are fairly straightforward, but APPLY gets a subroutine\n(instead of just a string a la 'apply --cached') so that we can handle\n'checkout -p', which will need to atomically apply the patch twice\n(index and worktree).\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n git-add--interactive.perl |   79 +++++++++++++++++++++++++++++---------------\n 1 files changed, 52 insertions(+), 27 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex df9f231..58c3332 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -73,6 +73,21 @@\n # command line options\n my $patch_mode;\n \n+sub apply_patch;\n+\n+my %patch_modes = (\n+\t'stage' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => sub { apply_patch 'apply --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Stage',\n+\t\tPARTICIPLE => 'staging',\n+\t\tFILTER => 'file-only',\n+\t},\n+);\n+\n+my %patch_mode_flavour = %{$patch_modes{stage}};\n+\n sub run_cmd_pipe {\n \tif ($^O eq 'MSWin32' || $^O eq 'msys') {\n \t\tmy @invalid = grep {m/[\":*]/} @_;\n@@ -613,12 +628,21 @@\n \tprint \"\\n\";\n }\n \n+sub run_git_apply {\n+\tmy $cmd = shift;\n+\tmy $fh;\n+\topen $fh, '| git ' . $cmd;\n+\tprint $fh @_;\n+\treturn close $fh;\n+}\n+\n sub parse_diff {\n \tmy ($path) = @_;\n-\tmy @diff = run_cmd_pipe(qw(git diff-files -p --), $path);\n+\tmy @diff_cmd = split(\" \", $patch_mode_flavour{DIFF});\n+\tmy @diff = run_cmd_pipe(\"git\", @diff_cmd, \"--\", $path);\n \tmy @colored = ();\n \tif ($diff_use_color) {\n-\t\t@colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);\n+\t\t@colored = run_cmd_pipe(\"git\", @diff_cmd, qw(--color --), $path);\n \t}\n \tmy (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };\n \n@@ -877,6 +901,7 @@\n \t\tor die \"failed to open hunk edit file for writing: \" . $!;\n \tprint $fh \"# Manual hunk edit mode -- see bottom for a quick guide\\n\";\n \tprint $fh @$oldtext;\n+\tmy $participle = $patch_mode_flavour{PARTICIPLE};\n \tprint $fh <<EOF;\n # ---\n # To remove '-' lines, make them ' ' lines (context).\n@@ -884,7 +909,7 @@\n # Lines starting with # will be removed.\n #\n # If the patch applies cleanly, the edited hunk will immediately be\n-# marked for staging. If it does not apply cleanly, you will be given\n+# marked for $participle. If it does not apply cleanly, you will be given\n # an opportunity to edit again. If all lines of the hunk are removed,\n # then the edit is aborted and the hunk is left unchanged.\n EOF\n@@ -918,11 +943,8 @@\n \n sub diff_applies {\n \tmy $fh;\n-\topen $fh, '| git apply --recount --cached --check';\n-\tfor my $h (@_) {\n-\t\tprint $fh @{$h->{TEXT}};\n-\t}\n-\treturn close $fh;\n+\treturn run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check',\n+\t\t\t     map { @{$_->{TEXT}} } @_);\n }\n \n sub _restore_terminal_and_die {\n@@ -988,12 +1010,13 @@\n }\n \n sub help_patch_cmd {\n-\tprint colored $help_color, <<\\EOF ;\n-y - stage this hunk\n-n - do not stage this hunk\n-q - quit, do not stage this hunk nor any of the remaining ones\n-a - stage this and all the remaining hunks in the file\n-d - do not stage this hunk nor any of the remaining hunks in the file\n+\tmy $verb = lc $patch_mode_flavour{VERB};\n+\tprint colored $help_color, <<EOF ;\n+y - $verb this hunk\n+n - do not $verb this hunk\n+q - quit, do not $verb this hunk nor any of the remaining ones\n+a - $verb this and all the remaining hunks in the file\n+d - do not $verb this hunk nor any of the remaining hunks in the file\n g - select a hunk to go to\n / - search for a hunk matching the given regex\n j - leave this hunk undecided, see next undecided hunk\n@@ -1006,8 +1029,17 @@\n EOF\n }\n \n+sub apply_patch {\n+\tmy $cmd = shift;\n+\tmy $ret = run_git_apply $cmd . ' --recount', @_;\n+\tif (!$ret) {\n+\t\tprint STDERR @_;\n+\t}\n+\treturn $ret;\n+}\n+\n sub patch_update_cmd {\n-\tmy @all_mods = list_modified('file-only');\n+\tmy @all_mods = list_modified($patch_mode_flavour{FILTER});\n \tmy @mods = grep { !($_->{BINARY}) } @all_mods;\n \tmy @them;\n \n@@ -1138,8 +1170,8 @@\n \t\tfor (@{$hunk[$ix]{DISPLAY}}) {\n \t\t\tprint;\n \t\t}\n-\t\tprint colored $prompt_color, 'Stage ',\n-\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),\n+\t\tprint colored $prompt_color, $patch_mode_flavour{VERB},\n+\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'),\n \t\t  \" [y,n,q,a,d,/$other,?]? \";\n \t\tmy $line = prompt_single_character;\n \t\tif ($line) {\n@@ -1313,16 +1345,9 @@\n \n \tif (@result) {\n \t\tmy $fh;\n-\n-\t\topen $fh, '| git apply --cached --recount';\n-\t\tfor (@{$head->{TEXT}}, @result) {\n-\t\t\tprint $fh $_;\n-\t\t}\n-\t\tif (!close $fh) {\n-\t\t\tfor (@{$head->{TEXT}}, @result) {\n-\t\t\t\tprint STDERR $_;\n-\t\t\t}\n-\t\t}\n+\t\tmy @patch = (@{$head->{TEXT}}, @result);\n+\t\tmy $apply_routine = $patch_mode_flavour{APPLY};\n+\t\t&$apply_routine(@patch);\n \t\trefresh();\n \t}\n \n-- \n1.6.4.rc3.215.g18405\n"},{"id":"118990","messageId":"dc47610d9634317e77b8db58eb2cf91813360d30.1248815580.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248815580.git.trast@student.ethz.ch","subject":"[PATCH v4 2/5] builtin-add: refactor the meat of interactive_add()","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-28T21:20:08Z","receivedAt":"2009-07-28T21:20:08Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This moves the call setup for 'git add--interactive' to a separate\nfunction, as other users will call it without running\nvalidate_pathspec() first.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n builtin-add.c |   43 +++++++++++++++++++++++++++++--------------\n commit.h      |    2 ++\n 2 files changed, 31 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin-add.c b/builtin-add.c\nindex 581a2a1..c422a62 100644\n--- a/builtin-add.c\n+++ b/builtin-add.c\n@@ -131,27 +131,27 @@ static void refresh(int verbose, const char **pathspec)\n \treturn pathspec;\n }\n \n-int interactive_add(int argc, const char **argv, const char *prefix)\n+int run_add_interactive(const char *revision, const char *patch_mode,\n+\t\t\tconst char **pathspec)\n {\n-\tint status, ac;\n+\tint status, ac, pc = 0;\n \tconst char **args;\n-\tconst char **pathspec = NULL;\n \n-\tif (argc) {\n-\t\tpathspec = validate_pathspec(argc, argv, prefix);\n-\t\tif (!pathspec)\n-\t\t\treturn -1;\n-\t}\n+\tif (pathspec)\n+\t\twhile (pathspec[pc])\n+\t\t\tpc++;\n \n-\targs = xcalloc(sizeof(const char *), (argc + 4));\n+\targs = xcalloc(sizeof(const char *), (pc + 5));\n \tac = 0;\n \targs[ac++] = \"add--interactive\";\n-\tif (patch_interactive)\n-\t\targs[ac++] = \"--patch\";\n+\tif (patch_mode)\n+\t\targs[ac++] = patch_mode;\n+\tif (revision)\n+\t\targs[ac++] = revision;\n \targs[ac++] = \"--\";\n-\tif (argc) {\n-\t\tmemcpy(&(args[ac]), pathspec, sizeof(const char *) * argc);\n-\t\tac += argc;\n+\tif (pc) {\n+\t\tmemcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);\n+\t\tac += pc;\n \t}\n \targs[ac] = NULL;\n \n@@ -160,6 +160,21 @@ int interactive_add(int argc, const char **argv, const char *prefix)\n \treturn status;\n }\n \n+int interactive_add(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char **pathspec = NULL;\n+\n+\tif (argc) {\n+\t\tpathspec = validate_pathspec(argc, argv, prefix);\n+\t\tif (!pathspec)\n+\t\t\treturn -1;\n+\t}\n+\n+\treturn run_add_interactive(NULL,\n+\t\t\t\t   patch_interactive ? \"--patch\" : NULL,\n+\t\t\t\t   pathspec);\n+}\n+\n static int edit_patch(int argc, const char **argv, const char *prefix)\n {\n \tchar *file = xstrdup(git_path(\"ADD_EDIT.patch\"));\ndiff --git a/commit.h b/commit.h\nindex 8bfdf0e..0555e80 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -139,6 +139,8 @@ struct commit_graft {\n int in_merge_bases(struct commit *, struct commit **, int);\n \n extern int interactive_add(int argc, const char **argv, const char *prefix);\n+extern int run_add_interactive(const char *revision, const char *patch_mode,\n+\t\t\t       const char **pathspec);\n \n static inline int single_parent(struct commit *commit)\n {\n-- \n1.6.4.rc3.215.g18405\n"},{"id":"118988","messageId":"663b24a0458440c02d74c546ce9012cfda8129f4.1248815580.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248815580.git.trast@student.ethz.ch","subject":"[PATCH v4 3/5] Implement 'git reset --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-28T21:20:09Z","receivedAt":"2009-07-28T21:20:09Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This introduces a --patch mode for git-reset.  The basic case is\n\n  git reset --patch -- [files...]\n\nwhich acts as the opposite of 'git add --patch -- [files...]': it\noffers hunks for *un*staging.  Advanced usage is\n\n  git reset --patch <revision> -- [files...]\n\nwhich offers hunks from the diff between <revision> and the index for\nreverse application to the index.  (That is, the basic case is just\n<revision> = HEAD.)  This means it can be used to \"undo\" changes since\n<revision> in the index, but may be slightly confusing when <revision>\nis logically \"newer\" than the index state.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-reset.txt |   15 +++++++++++-\n builtin-reset.c             |   19 +++++++++++++++++\n git-add--interactive.perl   |   45 ++++++++++++++++++++++++++++++++++++---\n t/t7105-reset-patch.sh      |   48 +++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 121 insertions(+), 6 deletions(-)\n create mode 100755 t/t7105-reset-patch.sh\n\ndiff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt\nindex abb25d1..469cf6d 100644\n--- a/Documentation/git-reset.txt\n+++ b/Documentation/git-reset.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n [verse]\n 'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]\n 'git reset' [-q] [<commit>] [--] <paths>...\n+'git reset' --patch [<commit>] [--] [<paths>...]\n \n DESCRIPTION\n -----------\n@@ -23,8 +24,9 @@ the undo in the history.\n If you want to undo a commit other than the latest on a branch,\n linkgit:git-revert[1] is your friend.\n \n-The second form with 'paths' is used to revert selected paths in\n-the index from a given commit, without moving HEAD.\n+The second and third forms with 'paths' and/or --patch are used to\n+revert selected paths in the index from a given commit, without moving\n+HEAD.\n \n \n OPTIONS\n@@ -50,6 +52,15 @@ OPTIONS\n \tand updates the files that are different between the named commit\n \tand the current commit in the working tree.\n \n+-p::\n+--patch::\n+\tInteractively select hunks in the difference between the index\n+\tand <commit> (defaults to HEAD).  The chosen hunks are applied\n+\tin reverse to the index.\n++\n+This means that `git reset -p` is the opposite of `git add -p` (see\n+linkgit:git-add[1]).\n+\n -q::\n \tBe quiet, only report errors.\n \ndiff --git a/builtin-reset.c b/builtin-reset.c\nindex 5fa1789..246a127 100644\n--- a/builtin-reset.c\n+++ b/builtin-reset.c\n@@ -142,6 +142,17 @@ static void update_index_from_diff(struct diff_queue_struct *q,\n \t}\n }\n \n+static int interactive_reset(const char *revision, const char **argv,\n+\t\t\t     const char *prefix)\n+{\n+\tconst char **pathspec = NULL;\n+\n+\tif (*argv)\n+\t\tpathspec = get_pathspec(prefix, argv);\n+\n+\treturn run_add_interactive(revision, \"--patch=reset\", pathspec);\n+}\n+\n static int read_from_tree(const char *prefix, const char **argv,\n \t\tunsigned char *tree_sha1, int refresh_flags)\n {\n@@ -183,6 +194,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)\n int cmd_reset(int argc, const char **argv, const char *prefix)\n {\n \tint i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;\n+\tint patch_mode = 0;\n \tconst char *rev = \"HEAD\";\n \tunsigned char sha1[20], *orig = NULL, sha1_orig[20],\n \t\t\t\t*old_orig = NULL, sha1_old_orig[20];\n@@ -198,6 +210,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\t\t\t\"reset HEAD, index and working tree\", MERGE),\n \t\tOPT_BOOLEAN('q', NULL, &quiet,\n \t\t\t\t\"disable showing new HEAD in hard reset and progress message\"),\n+\t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\tOPT_END()\n \t};\n \n@@ -251,6 +264,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\tdie(\"Could not parse object '%s'.\", rev);\n \thashcpy(sha1, commit->object.sha1);\n \n+\tif (patch_mode) {\n+\t\tif (reset_type != NONE)\n+\t\t\tdie(\"--patch is incompatible with --{hard,mixed,soft}\");\n+\t\treturn interactive_reset(rev, argv + i, prefix);\n+\t}\n+\n \t/* git reset tree [--] paths... can be used to\n \t * load chosen paths from the tree into the index without\n \t * affecting the working tree nor HEAD. */\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 58c3332..333e80d 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -72,6 +72,7 @@\n \n # command line options\n my $patch_mode;\n+my $patch_mode_revision;\n \n sub apply_patch;\n \n@@ -84,6 +85,14 @@\n \t\tPARTICIPLE => 'staging',\n \t\tFILTER => 'file-only',\n \t},\n+\t'reset' => {\n+\t\tDIFF => 'diff-index -p --cached',\n+\t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply -R --cached',\n+\t\tVERB => 'Reset',\n+\t\tPARTICIPLE => 'resetting',\n+\t\tFILTER => 'index-only',\n+\t},\n );\n \n my %patch_mode_flavour = %{$patch_modes{stage}};\n@@ -205,7 +214,14 @@\n \t\treturn if (!@tracked);\n \t}\n \n-\tmy $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';\n+\tmy $reference;\n+\tif (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') {\n+\t\t$reference = $patch_mode_revision;\n+\t} elsif (is_initial_commit()) {\n+\t\t$reference = get_empty_tree();\n+\t} else {\n+\t\t$reference = 'HEAD';\n+\t}\n \tfor (run_cmd_pipe(qw(git diff-index --cached\n \t\t\t     --numstat --summary), $reference,\n \t\t\t     '--', @tracked)) {\n@@ -639,6 +655,9 @@\n sub parse_diff {\n \tmy ($path) = @_;\n \tmy @diff_cmd = split(\" \", $patch_mode_flavour{DIFF});\n+\tif (defined $patch_mode_revision) {\n+\t\tpush @diff_cmd, $patch_mode_revision;\n+\t}\n \tmy @diff = run_cmd_pipe(\"git\", @diff_cmd, \"--\", $path);\n \tmy @colored = ();\n \tif ($diff_use_color) {\n@@ -1388,11 +1407,29 @@\n sub process_args {\n \treturn unless @ARGV;\n \tmy $arg = shift @ARGV;\n-\tif ($arg eq \"--patch\") {\n-\t\t$patch_mode = 1;\n-\t\t$arg = shift @ARGV or die \"missing --\";\n+\tif ($arg =~ /--patch(?:=(.*))?/) {\n+\t\tif (defined $1) {\n+\t\t\tif ($1 eq 'reset') {\n+\t\t\t\t$patch_mode = 'reset';\n+\t\t\t\t$patch_mode_revision = 'HEAD';\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\tif ($arg ne '--') {\n+\t\t\t\t\t$patch_mode_revision = $arg;\n+\t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\t}\n+\t\t\t} elsif ($1 eq 'stage') {\n+\t\t\t\t$patch_mode = 'stage';\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t} else {\n+\t\t\t\tdie \"unknown --patch mode: $1\";\n+\t\t\t}\n+\t\t} else {\n+\t\t\t$patch_mode = 'stage';\n+\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t}\n \t\tdie \"invalid argument $arg, expecting --\"\n \t\t    unless $arg eq \"--\";\n+\t\t%patch_mode_flavour = %{$patch_modes{$patch_mode}};\n \t}\n \telsif ($arg ne \"--\") {\n \t\tdie \"invalid argument $arg, expecting --\";\ndiff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh\nnew file mode 100755\nindex 0000000..57bfeea\n--- /dev/null\n+++ b/t/t7105-reset-patch.sh\n@@ -0,0 +1,48 @@\n+#!/bin/sh\n+\n+test_description='git reset --patch'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\techo parent > dir/foo &&\n+\techo dummy > dir/bar &&\n+\tgit add dir &&\n+\tgit commit -m initial &&\n+\ttest_tick &&\n+\ttest_commit second dir/foo head &&\n+\techo work > dir/foo &&\n+\techo bar_index > dir/bar &&\n+\tgit add dir/bar &&\n+\techo bar_work > dir/bar\n+'\n+\n+# note: bar sorts before foo, so the first 'n' is always to skip 'bar'\n+\n+test_expect_success 'saying \"n\" does nothing' '\n+\tgit add dir/foo &&\n+\t(echo n; echo n) | git reset -p &&\n+\ttest \"$(git show :dir/foo)\" = work &&\n+\ttest \"$(cat dir/foo)\" = work &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_expect_success 'git reset -p' '\n+\tgit add dir/foo &&\n+\t(echo n; echo y) | git reset -p &&\n+\ttest \"$(git show :dir/foo)\" = head &&\n+\ttest \"$(cat dir/foo)\" = work &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_expect_success 'git reset -p HEAD^' '\n+\t(echo n; echo y) | git reset -p HEAD^ &&\n+\ttest \"$(git show :dir/foo)\" = parent &&\n+\ttest \"$(cat dir/foo)\" = work &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_done\n-- \n1.6.4.rc3.215.g18405\n"},{"id":"118989","messageId":"659c0ee8f40855e2c73995212bd86c0b7bbd0ba1.1248815580.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248815580.git.trast@student.ethz.ch","subject":"[PATCH v4 4/5] Implement 'git checkout --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-28T21:20:10Z","receivedAt":"2009-07-28T21:20:10Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This introduces a --patch mode for git-checkout.  In the index usage\n\n  git checkout --patch -- [files...]\n\nit lets the user discard edits from the <files> at the granularity of\nhunks (by selecting hunks from 'git diff' and then reverse applying\nthem to the worktree).\n\nWe also accept a revision argument\n\n  git checkout --patch <revision> -- [files...]\n\nwhich offers hunks from the difference between the <revision> and the\nworktree.  The chosen hunks are then reverse applied to both index and\nworktree, discarding them completely.  This application is done\n\"atomically\" in the sense that we first check if the patch applies to\nthe index (it should always apply to the worktree).  If it does not,\nwe give the user a choice to either abort or apply to the worktree\nanyway.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-checkout.txt |   13 ++++++-\n builtin-checkout.c             |   19 +++++++++\n git-add--interactive.perl      |   48 +++++++++++++++++++++++\n t/t2015-checkout-patch.sh      |   84 ++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 163 insertions(+), 1 deletions(-)\n create mode 100755 t/t2015-checkout-patch.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex ad4b31e..26a5447 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [<branch>]\n 'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n+'git checkout' --patch [<tree-ish>] [--] [<paths>...]\n \n DESCRIPTION\n -----------\n@@ -25,7 +26,7 @@ use the --track or --no-track options, which will be passed to `git\n branch`.  As a convenience, --track without `-b` implies branch\n creation; see the description of --track below.\n \n-When <paths> are given, this command does *not* switch\n+When <paths> or --patch are given, this command does *not* switch\n branches.  It updates the named paths in the working tree from\n the index file, or from a named <tree-ish> (most often a commit).  In\n this case, the `-b` and `--track` options are meaningless and giving\n@@ -113,6 +114,16 @@ the conflicted merge in the specified paths.\n \t\"merge\" (default) and \"diff3\" (in addition to what is shown by\n \t\"merge\" style, shows the original contents).\n \n+-p::\n+--patch::\n+\tInteractively select hunks in the difference between the\n+\t<tree-ish> (or the index, if unspecified) and the working\n+\ttree.  The chosen hunks are then applied in reverse to the\n+\tworking tree (and if a <tree-ish> was specified, the index).\n++\n+This means that you can use `git checkout -p` to selectively discard\n+edits from your current working tree.\n+\n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\n \twhen prepended with \"refs/heads/\", is a valid ref), then that\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex 446cac7..7d57741 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -572,6 +572,13 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\n \treturn git_xmerge_config(var, value, cb);\n }\n \n+static int interactive_checkout(const char *revision, const char **pathspec,\n+\t\t\t\tstruct checkout_opts *opts)\n+{\n+\treturn run_add_interactive(revision, \"--patch=checkout\", pathspec);\n+}\n+\n+\n int cmd_checkout(int argc, const char **argv, const char *prefix)\n {\n \tstruct checkout_opts opts;\n@@ -580,6 +587,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \tstruct branch_info new;\n \tstruct tree *source_tree = NULL;\n \tchar *conflict_style = NULL;\n+\tint patch_mode = 0;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&opts.quiet),\n \t\tOPT_STRING('b', NULL, &opts.new_branch, \"new branch\", \"branch\"),\n@@ -594,6 +602,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOLEAN('m', \"merge\", &opts.merge, \"merge\"),\n \t\tOPT_STRING(0, \"conflict\", &conflict_style, \"style\",\n \t\t\t   \"conflict style (merge or diff3)\"),\n+\t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\tOPT_END(),\n \t};\n \tint has_dash_dash;\n@@ -608,6 +617,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options, checkout_usage,\n \t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n+\tif (patch_mode && (opts.track > 0 || opts.new_branch\n+\t\t\t   || opts.new_branch_log || opts.merge || opts.force))\n+\t\tdie (\"--patch is incompatible with all other options\");\n+\n \t/* --track without -b should DWIM */\n \tif (0 < opts.track && !opts.new_branch) {\n \t\tconst char *argv0 = argv[0];\n@@ -714,6 +727,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tif (!pathspec)\n \t\t\tdie(\"invalid path specification\");\n \n+\t\tif (patch_mode)\n+\t\t\treturn interactive_checkout(NULL, pathspec, &opts);\n+\n \t\t/* Checkout paths */\n \t\tif (opts.new_branch) {\n \t\t\tif (argc == 1) {\n@@ -729,6 +745,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\treturn checkout_paths(source_tree, pathspec, &opts);\n \t}\n \n+\tif (patch_mode)\n+\t\treturn interactive_checkout(new.name, NULL, &opts);\n+\n \tif (opts.new_branch) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (strbuf_check_branch_ref(&buf, opts.new_branch))\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 333e80d..bb76c37 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -75,6 +75,7 @@\n my $patch_mode_revision;\n \n sub apply_patch;\n+sub apply_patch_for_checkout_commit;\n \n my %patch_modes = (\n \t'stage' => {\n@@ -93,6 +94,22 @@\n \t\tPARTICIPLE => 'resetting',\n \t\tFILTER => 'index-only',\n \t},\n+\t'checkout_index' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => sub { apply_patch 'apply -R', @_; },\n+\t\tAPPLY_CHECK => 'apply -R',\n+\t\tVERB => 'Check out',\n+\t\tPARTICIPLE => 'checking out',\n+\t\tFILTER => 'file-only',\n+\t},\n+\t'checkout_commit' => {\n+\t\tDIFF => 'diff-index -p',\n+\t\tAPPLY => \\&apply_patch_for_checkout_commit,\n+\t\tAPPLY_CHECK => 'apply -R',\n+\t\tVERB => 'Check out',\n+\t\tPARTICIPLE => 'checking out',\n+\t\tFILTER => undef,\n+\t},\n );\n \n my %patch_mode_flavour = %{$patch_modes{stage}};\n@@ -1057,6 +1074,28 @@\n \treturn $ret;\n }\n \n+sub apply_patch_for_checkout_commit {\n+\tmy $applies_index = run_git_apply 'apply -R --cached --recount --check', @_;\n+\tmy $applies_worktree = run_git_apply 'apply -R --recount --check', @_;\n+\n+\tif ($applies_worktree && $applies_index) {\n+\t\trun_git_apply 'apply -R --cached --recount', @_;\n+\t\trun_git_apply 'apply -R --recount', @_;\n+\t\treturn 1;\n+\t} elsif (!$applies_index) {\n+\t\tprint colored $error_color, \"The selected hunks do not apply to the index!\\n\";\n+\t\tif (prompt_yesno \"Apply them to the worktree anyway? \") {\n+\t\t\treturn run_git_apply 'apply -R --recount', @_;\n+\t\t} else {\n+\t\t\tprint colored $error_color, \"Nothing was applied.\\n\";\n+\t\t\treturn 0;\n+\t\t}\n+\t} else {\n+\t\tprint STDERR @_;\n+\t\treturn 0;\n+\t}\n+}\n+\n sub patch_update_cmd {\n \tmy @all_mods = list_modified($patch_mode_flavour{FILTER});\n \tmy @mods = grep { !($_->{BINARY}) } @all_mods;\n@@ -1417,6 +1456,15 @@\n \t\t\t\t\t$patch_mode_revision = $arg;\n \t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t\t}\n+\t\t\t} elsif ($1 eq 'checkout') {\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\tif ($arg eq '--') {\n+\t\t\t\t\t$patch_mode = 'checkout_index';\n+\t\t\t\t} else {\n+\t\t\t\t\t$patch_mode = 'checkout_commit';\n+\t\t\t\t\t$patch_mode_revision = $arg;\n+\t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\t}\n \t\t\t} elsif ($1 eq 'stage') {\n \t\t\t\t$patch_mode = 'stage';\n \t\t\t\t$arg = shift @ARGV or die \"missing --\";\ndiff --git a/t/t2015-checkout-patch.sh b/t/t2015-checkout-patch.sh\nnew file mode 100755\nindex 0000000..bb96652\n--- /dev/null\n+++ b/t/t2015-checkout-patch.sh\n@@ -0,0 +1,84 @@\n+#!/bin/sh\n+\n+test_description='git checkout --patch'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\techo parent > dir/foo &&\n+\techo dummy > dir/bar &&\n+\tgit add dir &&\n+\tgit commit -m initial &&\n+\ttest_tick &&\n+\ttest_commit second dir/foo head &&\n+\techo work > dir/foo &&\n+\techo bar_index > dir/bar &&\n+\tgit add dir/bar &&\n+\techo bar_work > dir/bar\n+'\n+\n+# note: bar sorts before foo, so the first 'n' is always to skip 'bar'\n+\n+test_expect_success 'saying \"n\" does nothing' '\n+\t(echo n; echo n) | git checkout -p &&\n+\ttest \"$(cat dir/foo)\" = work\n+'\n+\n+test_expect_success 'git checkout -p' '\n+\t\n+\t(echo n; echo y) | git checkout -p &&\n+\ttest \"$(cat dir/foo)\" = head &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_expect_success 'git checkout -p with staged changes' '\n+\techo index > dir/foo &&\n+\tgit add dir/foo &&\n+\techo work > dir/foo &&\n+\t(echo n; echo y) | git checkout -p &&\n+\ttest \"$(git show :dir/foo)\" = index &&\n+\ttest \"$(cat dir/foo)\" = index &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_expect_success 'git checkout -p HEAD with NO staged changes: abort' '\n+\tgit reset -q -- dir/foo &&\n+\techo work > dir/foo &&\n+\t(echo n; echo y; echo n) | git checkout -p HEAD &&\n+\ttest \"$(git show :dir/foo)\" = head &&\n+\ttest \"$(cat dir/foo)\" = work &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_expect_success 'git checkout -p HEAD with NO staged changes: apply' '\n+\t(echo n; echo y; echo y) | git checkout -p HEAD &&\n+\ttest \"$(git show :dir/foo)\" = head &&\n+\ttest \"$(cat dir/foo)\" = head &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_expect_success 'git checkout -p HEAD with change already staged' '\n+\techo index > dir/foo &&\n+\tgit add dir/foo &&\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo n; echo y; echo n) | git checkout -p HEAD &&\n+\ttest \"$(git show :dir/foo)\" = head &&\n+\ttest \"$(cat dir/foo)\" = head &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_expect_success 'git checkout -p HEAD^' '\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo n; echo y; echo n) | git checkout -p HEAD^ &&\n+\ttest \"$(git show :dir/foo)\" = parent &&\n+\ttest \"$(cat dir/foo)\" = parent &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_done\n-- \n1.6.4.rc3.215.g18405\n"},{"id":"118987","messageId":"fab366a3117f00e8987dc0bdb2be80729f13cd8f.1248815580.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248815580.git.trast@student.ethz.ch","subject":"[PATCH v4 5/5] Implement 'git stash save --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-28T21:20:11Z","receivedAt":"2009-07-28T21:20:11Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This adds a hunk-based mode to git-stash.  You can select hunks from\nthe difference between HEAD and worktree, and git-stash will build a\nstash that reflects these changes.  The index state of the stash is\nthe same as your current index, and we also let --patch imply\n--keep-index.\n\nNote that because the selected hunks are rolled back from the worktree\nbut not the index, the resulting state may appear somewhat confusing\nif you had also staged these changes.  This is not entirely\nsatisfactory, but due to the way stashes are applied, other solutions\nwould require a change to the stash format.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-stash.txt |   14 ++++++-\n git-add--interactive.perl   |   13 ++++++-\n git-stash.sh                |   80 +++++++++++++++++++++++++++++++++++-------\n t/t3904-stash-patch.sh      |   66 +++++++++++++++++++++++++++++++++++\n 4 files changed, 155 insertions(+), 18 deletions(-)\n create mode 100755 t/t3904-stash-patch.sh\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 1c64a02..4b15459 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n-'git stash' [save [--keep-index] [-q|--quiet] [<message>]]\n+'git stash' [save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]]\n 'git stash' clear\n 'git stash' create\n \n@@ -42,7 +42,7 @@ is also possible).\n OPTIONS\n -------\n \n-save [--keep-index] [-q|--quiet] [<message>]::\n+save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::\n \n \tSave your local modifications to a new 'stash', and run `git reset\n \t--hard` to revert them.  This is the default action when no\n@@ -51,6 +51,16 @@ save [--keep-index] [-q|--quiet] [<message>]::\n +\n If the `--keep-index` option is used, all changes already added to the\n index are left intact.\n++\n+With `--patch`, you can interactively select hunks from in the diff\n+between HEAD and the working tree to be stashed.  The stash entry is\n+constructed such that its index state is the same as the index state\n+of your repository, and its worktree contains only the changes you\n+selected interactively.  The selected changes are then rolled back\n+from your worktree.\n++\n+The `--patch` option implies `--keep-index`.  You can use\n+`--no-keep-index` to override this.\n \n list [<options>]::\n \ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex bb76c37..91f1657 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -76,6 +76,7 @@\n \n sub apply_patch;\n sub apply_patch_for_checkout_commit;\n+sub apply_patch_for_stash;\n \n my %patch_modes = (\n \t'stage' => {\n@@ -86,6 +87,14 @@\n \t\tPARTICIPLE => 'staging',\n \t\tFILTER => 'file-only',\n \t},\n+\t'stash' => {\n+\t\tDIFF => 'diff-index -p HEAD',\n+\t\tAPPLY => sub { apply_patch 'apply --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Stash',\n+\t\tPARTICIPLE => 'stashing',\n+\t\tFILTER => undef,\n+\t},\n \t'reset' => {\n \t\tDIFF => 'diff-index -p --cached',\n \t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n@@ -1465,8 +1474,8 @@\n \t\t\t\t\t$patch_mode_revision = $arg;\n \t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t\t}\n-\t\t\t} elsif ($1 eq 'stage') {\n-\t\t\t\t$patch_mode = 'stage';\n+\t\t\t} elsif ($1 eq 'stage' or $1 eq 'stash') {\n+\t\t\t\t$patch_mode = $1;\n \t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t} else {\n \t\t\t\tdie \"unknown --patch mode: $1\";\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 03e589f..567aa5d 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -21,6 +21,14 @@ trap 'rm -f \"$TMP-*\"' 0\n \n ref_stash=refs/stash\n \n+if git config --get-colorbool color.interactive; then\n+       help_color=\"$(git config --get-color color.interactive.help 'red bold')\"\n+       reset_color=\"$(git config --get-color '' reset)\"\n+else\n+       help_color=\n+       reset_color=\n+fi\n+\n no_changes () {\n \tgit diff-index --quiet --cached HEAD --ignore-submodules -- &&\n \tgit diff-files --quiet --ignore-submodules\n@@ -68,19 +76,44 @@ create_stash () {\n \t\tgit commit-tree $i_tree -p $b_commit) ||\n \t\tdie \"Cannot save the current index state\"\n \n-\t# state of the working tree\n-\tw_tree=$( (\n+\tif test -z \"$patch_mode\"\n+\tthen\n+\n+\t\t# state of the working tree\n+\t\tw_tree=$( (\n+\t\t\trm -f \"$TMP-index\" &&\n+\t\t\tcp -p ${GIT_INDEX_FILE-\"$GIT_DIR/index\"} \"$TMP-index\" &&\n+\t\t\tGIT_INDEX_FILE=\"$TMP-index\" &&\n+\t\t\texport GIT_INDEX_FILE &&\n+\t\t\tgit read-tree -m $i_tree &&\n+\t\t\tgit add -u &&\n+\t\t\tgit write-tree &&\n+\t\t\trm -f \"$TMP-index\"\n+\t\t) ) ||\n+\t\t\tdie \"Cannot save the current worktree state\"\n+\n+\telse\n+\n \t\trm -f \"$TMP-index\" &&\n-\t\tcp -p ${GIT_INDEX_FILE-\"$GIT_DIR/index\"} \"$TMP-index\" &&\n-\t\tGIT_INDEX_FILE=\"$TMP-index\" &&\n-\t\texport GIT_INDEX_FILE &&\n-\t\tgit read-tree -m $i_tree &&\n-\t\tgit add -u &&\n-\t\tgit write-tree &&\n-\t\trm -f \"$TMP-index\"\n-\t) ) ||\n+\t\tGIT_INDEX_FILE=\"$TMP-index\" git read-tree HEAD &&\n+\n+\t\t# find out what the user wants\n+\t\tGIT_INDEX_FILE=\"$TMP-index\" \\\n+\t\t\tgit add--interactive --patch=stash -- &&\n+\n+\t\t# state of the working tree\n+\t\tw_tree=$(GIT_INDEX_FILE=\"$TMP-index\" git write-tree) ||\n \t\tdie \"Cannot save the current worktree state\"\n \n+\t\tgit diff-tree -p HEAD $w_tree > \"$TMP-patch\" &&\n+\t\ttest -s \"$TMP-patch\" ||\n+\t\tdie \"No changes selected\"\n+\n+\t\trm -f \"$TMP-index\" ||\n+\t\tdie \"Cannot remove temporary index (can't happen)\"\n+\n+\tfi\n+\n \t# create the stash\n \tif test -z \"$stash_msg\"\n \tthen\n@@ -95,12 +128,20 @@ create_stash () {\n \n save_stash () {\n \tkeep_index=\n+\tpatch_mode=\n \twhile test $# != 0\n \tdo\n \t\tcase \"$1\" in\n \t\t--keep-index)\n \t\t\tkeep_index=t\n \t\t\t;;\n+\t\t--no-keep-index)\n+\t\t\tkeep_index=\n+\t\t\t;;\n+\t\t-p|--patch)\n+\t\t\tpatch_mode=t\n+\t\t\tkeep_index=t\n+\t\t\t;;\n \t\t-q|--quiet)\n \t\t\tGIT_QUIET=t\n \t\t\t;;\n@@ -131,11 +172,22 @@ save_stash () {\n \t\tdie \"Cannot save the current status\"\n \tsay Saved working directory and index state \"$stash_msg\"\n \n-\tgit reset --hard ${GIT_QUIET:+-q}\n-\n-\tif test -n \"$keep_index\" && test -n $i_tree\n+\tif test -z \"$patch_mode\"\n \tthen\n-\t\tgit read-tree --reset -u $i_tree\n+\t\tgit reset --hard ${GIT_QUIET:+-q}\n+\n+\t\tif test -n \"$keep_index\" && test -n $i_tree\n+\t\tthen\n+\t\t\tgit read-tree --reset -u $i_tree\n+\t\tfi\n+\telse\n+\t\tgit apply -R < \"$TMP-patch\" ||\n+\t\tdie \"Cannot remove worktree changes\"\n+\n+\t\tif test -z \"$keep_index\"\n+\t\tthen\n+\t\t\tgit reset\n+\t\tfi\n \tfi\n }\n \ndiff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh\nnew file mode 100755\nindex 0000000..c7b8e5d\n--- /dev/null\n+++ b/t/t3904-stash-patch.sh\n@@ -0,0 +1,66 @@\n+#!/bin/sh\n+\n+test_description='git checkout --patch'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\techo parent > dir/foo &&\n+\techo dummy > dir/bar &&\n+\tgit add dir &&\n+\tgit commit -m initial &&\n+\ttest_tick &&\n+\ttest_commit second dir/foo head &&\n+\techo index > dir/foo &&\n+\tgit add dir/foo &&\n+\techo work > dir/foo &&\n+\techo bar_index > dir/bar &&\n+\tgit add dir/bar &&\n+\techo bar_work > dir/bar\n+'\n+\n+# note: bar sorts before foo, so the first 'n' is always to skip 'bar'\n+\n+test_expect_success 'saying \"n\" does nothing' '\n+\t(echo n; echo n) | test_must_fail git stash save -p &&\n+\ttest \"$(git show :dir/foo)\" = index &&\n+\ttest \"$(cat dir/foo)\" = work &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work\n+'\n+\n+test_expect_success 'git stash -p' '\n+\t(echo n; echo y) | git stash save -p &&\n+\ttest \"$(git show :dir/foo)\" = index &&\n+\ttest \"$(cat dir/foo)\" = head &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = bar_work &&\n+\tgit reset --hard &&\n+\tgit stash apply &&\n+\ttest \"$(git show :dir/foo)\" = head &&\n+\ttest \"$(cat dir/foo)\" = work &&\n+\ttest \"$(git show :dir/bar)\" = dummy &&\n+\ttest \"$(cat dir/bar)\" = dummy\n+'\n+\n+test_expect_success 'git stash -p --no-keep-index' '\n+\techo index > dir/foo &&\n+\tgit add dir/foo &&\n+\techo work > dir/foo &&\n+\techo bar_index > dir/bar &&\n+\tgit add dir/bar &&\n+\techo bar_work > dir/bar &&\n+\t(echo n; echo y) | git stash save -p --no-keep-index &&\n+\ttest \"$(git show :dir/foo)\" = head &&\n+\ttest \"$(cat dir/foo)\" = head &&\n+\ttest \"$(git show :dir/bar)\" = dummy &&\n+\ttest \"$(cat dir/bar)\" = bar_work &&\n+\tgit reset --hard &&\n+\tgit stash apply --index &&\n+\ttest \"$(git show :dir/foo)\" = index &&\n+\ttest \"$(cat dir/foo)\" = work &&\n+\ttest \"$(git show :dir/bar)\" = bar_index &&\n+\ttest \"$(cat dir/bar)\" = dummy\n+'\n+\n+test_done\n-- \n1.6.4.rc3.215.g18405\n"},{"id":"118992","messageId":"73e749442329242681f24ce4786708b982c0c243.1248815580.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1248815580.git.trast@student.ethz.ch","subject":"[PATCH v4 6/5] DWIM 'git stash save -p' for 'git stash -p'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-07-28T21:20:12Z","receivedAt":"2009-07-28T21:20:12Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Signed-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-stash.txt |    2 +-\n git-stash.sh                |    4 ++--\n 2 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 3dc16a1..1c4ed41 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n 'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]\n-'git stash' [-k|--keep-index]\n+'git stash' [-p|--patch|-k|--keep-index]\n 'git stash' clear\n 'git stash' create\n \ndiff --git a/git-stash.sh b/git-stash.sh\nindex 81a72f6..9fd7289 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -406,8 +406,8 @@ branch)\n \tapply_to_branch \"$@\"\n \t;;\n *)\n-\tcase $#,\"$1\" in\n-\t0,|1,-k|1,--keep-index)\n+\tcase $#,\"$1\",\"$2\" in\n+\t0,,|1,-k,|1,--keep-index,|1,-p,|1,--patch,|2,-p,--no-keep-index|2,--patch,--no-keep-index)\n \t\tsave_stash \"$@\" &&\n \t\tsay '(To restore them type \"git stash apply\")'\n \t\t;;\n-- \n1.6.4.rc3.215.g18405\n"},{"id":"120030","messageId":"20090809065207.GA14093@coredump.intra.peff.net","threadId":"20208","inReplyTo":"cover.1248815580.git.trast@student.ethz.ch","subject":"Re: [PATCH v4 0/5] {checkout,reset,stash} --patch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2009-08-09T06:52:07Z","receivedAt":"2009-08-09T06:52:07Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 28, 2009 at 11:20:06PM +0200, Thomas Rast wrote:\n\n> Thomas Rast (6):\n>   git-apply--interactive: Refactor patch mode code\n>   builtin-add: refactor the meat of interactive_add()\n>   Implement 'git reset --patch'\n>   Implement 'git checkout --patch'\n>   Implement 'git stash save --patch'\n>   DWIM 'git stash save -p' for 'git stash -p'\n\nI finally got a few minutes to look at this. I tried \"checkout --patch\"\nfirst, which was very confusing:\n\n  $ echo old content >file && git add file && git commit -m old\n  $ echo new content >>file\n  $ git checkout --patch file\n  diff --git a/file b/file\n  index 33194a0..805c3b0 100644\n  --- a/file\n  +++ b/file\n  @@ -1 +1,2 @@\n   old content\n   +new content\n  Check out this hunk [y,n,q,a,d,/,e,?]?\n\nShouldn't the diff be reversed? That is, I think what users would like\nto see is \"bring this hunk over from the index to the working tree\". But\nwe have the opposite (a hunk that is in the working tree that we would\nlike to undo).\n\nMaybe it is just the use of the verb \"check out\". I think of it as\nbringing _this_ hunk out. But it is really about erasing this hunk. \"git\nreset --patch\" has the same behavior, but I don't find it as bothersome,\nsince it says \"Reset this hunk\".\n\nAnd \"stash --patch\" doesn't have the issue because it goes from the\nworking tree to the stash already.\n\n-Peff\n"},{"id":"120043","messageId":"200908091117.19167.trast@student.ethz.ch","threadId":"20208","inReplyTo":"20090809065207.GA14093@coredump.intra.peff.net","subject":"Re: [PATCH v4 0/5] {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-09T09:17:16Z","receivedAt":"2009-08-09T09:17:16Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Jeff King wrote:\n> I finally got a few minutes to look at this. I tried \"checkout --patch\"\n> first, which was very confusing:\n> \n>   $ echo old content >file && git add file && git commit -m old\n>   $ echo new content >>file\n>   $ git checkout --patch file\n>   diff --git a/file b/file\n>   index 33194a0..805c3b0 100644\n>   --- a/file\n>   +++ b/file\n>   @@ -1 +1,2 @@\n>    old content\n>    +new content\n>   Check out this hunk [y,n,q,a,d,/,e,?]?\n> \n> Shouldn't the diff be reversed? That is, I think what users would like\n> to see is \"bring this hunk over from the index to the working tree\". But\n> we have the opposite (a hunk that is in the working tree that we would\n> like to undo).\n\nWell, my thinking for the initial (restricted; you couldn't say 'git\ncheckout -p HEAD~14') version went something like this: 'reset -p'\nshould be the opposite of 'add -p', so it offers the same hunks with\nthe question \"Reset?\".  Then 'checkout -p' should somehow follow suit,\nbut asked \"Discard?\" (IIRC I even had it in all caps).\n\nI'm not 100% happy with your suggested forward patches strategy\nbecause I think (particularly for people with colors enabled, and I\nsuspect we all have), it's less confusing \"my\" way if they go through\n'add -p' and suddenly think \"oops, mistake, I need to reset that\": it\nis much easier for the (at least for my) eye to find the same hunk\nagain if it is really 100% the same.  Probably we would have to change\nthe verb to \"discard\" again, though.\n\nOTOH this does become rather weird once you specify anything other\nthan HEAD.  And while we could of course switch the approach if the\nuser does that, and hope that he'll understand on the grounds that\nit's an advanced usage, I'm not sure switching is a good idea in\ngeneral.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"120060","messageId":"20090809163233.GA12911@vidovic","threadId":"20208","inReplyTo":"200908091117.19167.trast@student.ethz.ch","subject":"[PATCH v4 0/5] Re: {checkout,reset,stash} --patch","fromName":"Nicolas Sebrecht","fromEmail":"nicolas.s.dev@gmx.fr","sentAt":"2009-08-09T16:32:33Z","receivedAt":"2009-08-09T16:32:33Z","isPatch":true,"sender":{"key":"nicolas.s.dev@gmx.fr","avatar":null},"body":"The 09/08/09, Thomas Rast wrote:\n> Jeff King wrote:\n> > \n> > Shouldn't the diff be reversed? That is, I think what users would like\n> > to see is \"bring this hunk over from the index to the working tree\". But\n> > we have the opposite (a hunk that is in the working tree that we would\n> > like to undo).\n> \n> Well, my thinking for the initial (restricted; you couldn't say 'git\n> checkout -p HEAD~14') version went something like this: 'reset -p'\n> should be the opposite of 'add -p', so it offers the same hunks with\n> the question \"Reset?\".  Then 'checkout -p' should somehow follow suit,\n> but asked \"Discard?\" (IIRC I even had it in all caps).\n\nI agree this approach is fine. That said, I admit I've been confused at\nthe beginning. FMPOV, asking \"Discard this hunk?\" is a real improvement.\n\nIn the same way I'd change \"Reset?\" of 'git reset -p' to \"Unstage?\".\nOtherwise, the end-user don't know if the command will discard the hunk\nfrom the WT too.\n\nAlso, I'd expect to have 'git reset --hard -p' discarding hunks from\nboth the index and the WT (which is not possible for now unless I missed\nsomething).\n\n-- \nNicolas Sebrecht\n"},{"id":"120061","messageId":"200908091844.43107.trast@student.ethz.ch","threadId":"20208","inReplyTo":"20090809163233.GA12911@vidovic","subject":"Re: [PATCH v4 0/5] Re: {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-09T16:44:41Z","receivedAt":"2009-08-09T16:44:41Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Nicolas Sebrecht wrote:\n> \n> Also, I'd expect to have 'git reset --hard -p' discarding hunks from\n> both the index and the WT (which is not possible for now unless I missed\n> something).\n\nWell, the unfortunate overlap between 'git reset --hard' and 'git\ncheckout HEAD -- .' strikes again :-)\n\nSince you can't say 'git reset --hard -- file', you have to do 'git\ncheckout HEAD -- file' to achieve this effect.  So this usage is\ncovered by 'git checkout -p HEAD'.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"120093","messageId":"20090809212831.GA12932@vidovic","threadId":"20208","inReplyTo":"200908091844.43107.trast@student.ethz.ch","subject":"[PATCH v4 0/5] Re: {checkout,reset,stash} --patch","fromName":"Nicolas Sebrecht","fromEmail":"nicolas.s.dev@gmx.fr","sentAt":"2009-08-09T21:28:31Z","receivedAt":"2009-08-09T21:28:31Z","isPatch":true,"sender":{"key":"nicolas.s.dev@gmx.fr","avatar":null},"body":"The 09/08/09, Thomas Rast wrote:\n> Nicolas Sebrecht wrote:\n> > \n> > Also, I'd expect to have 'git reset --hard -p' discarding hunks from\n> > both the index and the WT (which is not possible for now unless I missed\n> > something).\n> \n> Well, the unfortunate overlap between 'git reset --hard' and 'git\n> checkout HEAD -- .' strikes again :-)\n> \n> Since you can't say 'git reset --hard -- file', you have to do 'git\n> checkout HEAD -- file' to achieve this effect.  So this usage is\n> covered by 'git checkout -p HEAD'.\n\nNot exactly:\n\n  Current branch: my_branch (clean)\n  nicolas@vidovic git % echo 'hi' > file\n  Current branch: my_branch (dirty: untracked:1)\n  nicolas@vidovic git % git add file\n  Current branch: my_branch (dirty: index)\n  nicolas@vidovic git % git checkout -p\n  No changes.\n\n\nSame example with an already tracked file:\n\n  Current branch: my_branch (clean)\n  nicolas@vidovic git % echo 'hi Thomas' >> Documentation/SubmittingPatches\n  Current branch: my_branch (dirty: working_tree)\n  nicolas@vidovic git % git add Documentation/SubmittingPatches\n  Current branch: my_branch (dirty: index)\n  nicolas@vidovic git % git checkout -p\n  No changes.\n\n\nSo, we can discard a hunk from the index with 'git reset -p' without\ntouching the WT. And we can discard a hunk from the WT with 'git\ncheckout -p' without touching the index. But we can't discard a hunk\nfrom both the index and the WT.\n\n\nIOW, we have (from the user POV):\n\n  .git --- (reset -p) ---> index\n                           index --- (checkout -p) ---> WT\n\nWhat's missing:\n\n  .git ------------------ ( ??? ) --------------------> WT\n\n-- \nNicolas Sebrecht\n"},{"id":"120095","messageId":"200908092342.31733.trast@student.ethz.ch","threadId":"20208","inReplyTo":"20090809212831.GA12932@vidovic","subject":"Re: [PATCH v4 0/5] Re: {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-09T21:42:28Z","receivedAt":"2009-08-09T21:42:28Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Nicolas Sebrecht wrote:\n> The 09/08/09, Thomas Rast wrote:\n> > \n> > Since you can't say 'git reset --hard -- file', you have to do 'git\n> > checkout HEAD -- file' to achieve this effect.  So this usage is\n> > covered by 'git checkout -p HEAD'.\n> \n> So, we can discard a hunk from the index with 'git reset -p' without\n> touching the WT. And we can discard a hunk from the WT with 'git\n> checkout -p' without touching the index. But we can't discard a hunk\n> from both the index and the WT.\n\nYes, you can, precisely as I wrote:\n\n> > covered by 'git checkout -p HEAD'.\n                                ^^^^\n\nI figured this makes sense:\n\n  git checkout -- file         # copy file from index to worktree\n  git checkout -p [file]       # copy hunks from index to worktree\n  git checkout HEAD -- file    # copy file from HEAD to index&worktree\n  git checkout -p HEAD -- file # copy hunks from HEAD to index&worktree\n\nNote that the patch application stage has no guarantees that what you\npicked will also apply to the index; it tries first, and if it\ndoesn't, it asks if it should apply to the worktree anyway (and leave\nthe index unchanged).\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"120097","messageId":"20090809222658.GB12932@vidovic","threadId":"20208","inReplyTo":"200908092342.31733.trast@student.ethz.ch","subject":"[PATCH v4 0/5] Re: {checkout,reset,stash} --patch","fromName":"Nicolas Sebrecht","fromEmail":"nicolas.s.dev@gmx.fr","sentAt":"2009-08-09T22:26:58Z","receivedAt":"2009-08-09T22:26:58Z","isPatch":true,"sender":{"key":"nicolas.s.dev@gmx.fr","avatar":null},"body":"The 09/08/09, Thomas Rast wrote:\n> Nicolas Sebrecht wrote:\n>\n> Yes, you can, precisely as I wrote:\n> \n> > > covered by 'git checkout -p HEAD'.\n\nI did my tests including \"HEAD\" but only with \"-- file\" too...\n\n> I figured this makes sense:\n> \n>   git checkout -- file         # copy file from index to worktree\n>   git checkout -p [file]       # copy hunks from index to worktree\n>   git checkout HEAD -- file    # copy file from HEAD to index&worktree\n>   git checkout -p HEAD -- file # copy hunks from HEAD to index&worktree\n\n...and I don't have what's expected\n\n  Current branch: my_branch (clean)\n  nicolas@vidovic git % echo 'Hi Thomas' >> Documentation/SubmittingPatches\n  Current branch: my_branch (dirty: working_tree)\n  nicolas@vidovic git % git add Documentation/SubmittingPatches\n  Current branch: my_branch (dirty: index)\n  nicolas@vidovic git % git checkout -p HEAD -- Documentation/SubmittingPatches\n  No changes.\n\nwhere \"checkout -p HEAD\" works fine.\n\n> Note that the patch application stage has no guarantees that what you\n> picked will also apply to the index; it tries first, and if it\n> doesn't, it asks if it should apply to the worktree anyway (and leave\n> the index unchanged).\n\nOk. All of what you say above makes sense and I'm actually fine with\nyour whole answer. That said,\n\n  % git checkout -p HEAD\n\nand\n\n  % git checkout -p HEAD -- file\n\nbehave differently here in my test above.\n\n-- \nNicolas Sebrecht\n"},{"id":"120133","messageId":"200908101136.34660.trast@student.ethz.ch","threadId":"20208","inReplyTo":"20090809222658.GB12932@vidovic","subject":"Re: [PATCH v4 0/5] Re: {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-10T09:36:33Z","receivedAt":"2009-08-10T09:36:33Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Nicolas Sebrecht wrote:\n> \n> Ok. All of what you say above makes sense and I'm actually fine with\n> your whole answer. That said,\n> \n>   % git checkout -p HEAD\n> \n> and\n> \n>   % git checkout -p HEAD -- file\n> \n> behave differently here in my test above.\n\nOh.  Sorry, that's indeed a bug, I'll fix this.  (The -- file form\nloses the HEAD when invoking add--interactive.)\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"120516","messageId":"cover.1250164190.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"200908101136.34660.trast@student.ethz.ch","subject":"[PATCH v5 0/6] {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-13T12:29:38Z","receivedAt":"2009-08-13T12:29:38Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano wrote:\n> * tr/reset-checkout-patch (Tue Jul 28 23:20:12 2009 +0200) 8 commits\n[...]\n> Progress?\n\nSlow, as always.  There are three groups of changes:\n\n1. This iteration goes the \"complicated\" way to mitigate Jeff's concern:\n\nJeff King wrote:\n> Shouldn't the diff [in checkout -p] be reversed? That is, I think\n> what users would like to see is \"bring this hunk over from the index\n> to the working tree\". But we have the opposite (a hunk that is in\n> the working tree that we would like to undo).\n\nThat is, the rules are now as follows:\n\nadd -p\t\t\tforward application\nreset -p [HEAD]\t\texact opposite of add -p: reverse application\nreset -p other\t\tforward application to index (**)\ncheckout -p\t\t\"opposite of editing\": reverse application\ncheckout -p HEAD\t\"opposite of editing and staging\": reverse application\ncheckout -p other\tforward application to WT and index (**)\nstash -p\t\t\"stash these edits\": reverse application to WT, \"forward to stash\"\n\nThose marked (**) are the only ones that changed semantics compared to\nv4.  However, I adjusted the messages to look different:\n\nadd -p\t\t\tStage this hunk?\nreset -p [HEAD]\t\tReset this hunk? (**)\nreset -p other\t\tApply this hunk to index? (**)\ncheckout -p\t\tDiscard this hunk from worktree? (**)\ncheckout -p HEAD\tDiscard this hunk from index and worktree? (**)\ncheckout -p other\tApply this hunk to index and worktree? (**)\nstash -p\t\tStash this hunk?\n\nAgain, (**) are the changed ones from v4.  The help message also shows\nthe \"to/from ...\" extra in the help for y/n.\n\nI think this should now make 'reset -p' and 'checkout -p' fairly\nintuitive, while at the same time making the '... other' forms easier\nto wrap one's head around.  Of course, as stated earlier in the\nthread, the downside with this approach is that the direction suddenly\nchanges when you give it an 'other'.\n\nThese changes affect all 'Implement foo --patch' patches, and the\ngit-apply--interactive refactoring.\n\n\n2. git checkout -p HEAD fixed\n\nNicolas Sebrecht wrote:\n> \n>   % git checkout -p HEAD\n> \n> and\n> \n>   % git checkout -p HEAD -- file\n> \n> behave differently here in my test above.\n\nThis sadly was a rather trivial thinko on my part in the C glue for\n'checkout -p', which I fixed.  I also changed the tests to cover\nvarious ways of limiting paths.\n\n\n3. Tests rewritten\n\nI added a new 2/6 refactors the many occurences of\n\n\ttest \"$(cat file)\" = expected_worktree &&\n\ttest \"$(git show :file)\" = expected_index\n\nto a few library functions, and rewritten all three tests to use them.\nDue to the bug discussed in (2.) above, the tests also cover pathspecs\nfor all new commands.  Due to my own concern because this was broken\nat some point during development, all commands also check if relative\npaths inside a directory work.\n\n\n3/6 (which was 2/5) and 7/6 (was 6/5) are unchanged, and apart from\nthe fix for (2.) which was a one-liner, so is all the C code.  7/6 is,\nas before, based on a merge with js/stash-dwim.\n\n\nThomas Rast (7):\n  git-apply--interactive: Refactor patch mode code\n  Add a small patch-mode testing library\n  builtin-add: refactor the meat of interactive_add()\n  Implement 'git reset --patch'\n  Implement 'git checkout --patch'\n  Implement 'git stash save --patch'\n  DWIM 'git stash save -p' for 'git stash -p'\n"},{"id":"120520","messageId":"d7c4f69f13bf6939e37f2cd43752943108a7071d.1250164190.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5 1/6] git-apply--interactive: Refactor patch mode code","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-13T12:29:39Z","receivedAt":"2009-08-13T12:29:39Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This makes some aspects of the 'git add -p' loop configurable (within\nthe code), so that we can later reuse git-add--interactive for other\nsimilar tools.\n\nMost fields are fairly straightforward, but APPLY gets a subroutine\n(instead of just a string a la 'apply --cached') so that we can handle\n'checkout -p', which will need to atomically apply the patch twice\n(index and worktree).\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n git-add--interactive.perl |   82 ++++++++++++++++++++++++++++++---------------\n 1 files changed, 55 insertions(+), 27 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex df9f231..3606103 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -73,6 +73,22 @@\n # command line options\n my $patch_mode;\n \n+sub apply_patch;\n+\n+my %patch_modes = (\n+\t'stage' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => sub { apply_patch 'apply --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Stage',\n+\t\tTARGET => '',\n+\t\tPARTICIPLE => 'staging',\n+\t\tFILTER => 'file-only',\n+\t},\n+);\n+\n+my %patch_mode_flavour = %{$patch_modes{stage}};\n+\n sub run_cmd_pipe {\n \tif ($^O eq 'MSWin32' || $^O eq 'msys') {\n \t\tmy @invalid = grep {m/[\":*]/} @_;\n@@ -613,12 +629,21 @@\n \tprint \"\\n\";\n }\n \n+sub run_git_apply {\n+\tmy $cmd = shift;\n+\tmy $fh;\n+\topen $fh, '| git ' . $cmd;\n+\tprint $fh @_;\n+\treturn close $fh;\n+}\n+\n sub parse_diff {\n \tmy ($path) = @_;\n-\tmy @diff = run_cmd_pipe(qw(git diff-files -p --), $path);\n+\tmy @diff_cmd = split(\" \", $patch_mode_flavour{DIFF});\n+\tmy @diff = run_cmd_pipe(\"git\", @diff_cmd, \"--\", $path);\n \tmy @colored = ();\n \tif ($diff_use_color) {\n-\t\t@colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);\n+\t\t@colored = run_cmd_pipe(\"git\", @diff_cmd, qw(--color --), $path);\n \t}\n \tmy (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };\n \n@@ -877,6 +902,7 @@\n \t\tor die \"failed to open hunk edit file for writing: \" . $!;\n \tprint $fh \"# Manual hunk edit mode -- see bottom for a quick guide\\n\";\n \tprint $fh @$oldtext;\n+\tmy $participle = $patch_mode_flavour{PARTICIPLE};\n \tprint $fh <<EOF;\n # ---\n # To remove '-' lines, make them ' ' lines (context).\n@@ -884,7 +910,7 @@\n # Lines starting with # will be removed.\n #\n # If the patch applies cleanly, the edited hunk will immediately be\n-# marked for staging. If it does not apply cleanly, you will be given\n+# marked for $participle. If it does not apply cleanly, you will be given\n # an opportunity to edit again. If all lines of the hunk are removed,\n # then the edit is aborted and the hunk is left unchanged.\n EOF\n@@ -918,11 +944,8 @@\n \n sub diff_applies {\n \tmy $fh;\n-\topen $fh, '| git apply --recount --cached --check';\n-\tfor my $h (@_) {\n-\t\tprint $fh @{$h->{TEXT}};\n-\t}\n-\treturn close $fh;\n+\treturn run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check',\n+\t\t\t     map { @{$_->{TEXT}} } @_);\n }\n \n sub _restore_terminal_and_die {\n@@ -988,12 +1011,14 @@\n }\n \n sub help_patch_cmd {\n-\tprint colored $help_color, <<\\EOF ;\n-y - stage this hunk\n-n - do not stage this hunk\n-q - quit, do not stage this hunk nor any of the remaining ones\n-a - stage this and all the remaining hunks in the file\n-d - do not stage this hunk nor any of the remaining hunks in the file\n+\tmy $verb = lc $patch_mode_flavour{VERB};\n+\tmy $target = $patch_mode_flavour{TARGET};\n+\tprint colored $help_color, <<EOF ;\n+y - $verb this hunk$target\n+n - do not $verb this hunk$target\n+q - quit, do not $verb this hunk nor any of the remaining ones\n+a - $verb this and all the remaining hunks in the file\n+d - do not $verb this hunk nor any of the remaining hunks in the file\n g - select a hunk to go to\n / - search for a hunk matching the given regex\n j - leave this hunk undecided, see next undecided hunk\n@@ -1006,8 +1031,17 @@\n EOF\n }\n \n+sub apply_patch {\n+\tmy $cmd = shift;\n+\tmy $ret = run_git_apply $cmd . ' --recount', @_;\n+\tif (!$ret) {\n+\t\tprint STDERR @_;\n+\t}\n+\treturn $ret;\n+}\n+\n sub patch_update_cmd {\n-\tmy @all_mods = list_modified('file-only');\n+\tmy @all_mods = list_modified($patch_mode_flavour{FILTER});\n \tmy @mods = grep { !($_->{BINARY}) } @all_mods;\n \tmy @them;\n \n@@ -1138,8 +1172,9 @@\n \t\tfor (@{$hunk[$ix]{DISPLAY}}) {\n \t\t\tprint;\n \t\t}\n-\t\tprint colored $prompt_color, 'Stage ',\n-\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),\n+\t\tprint colored $prompt_color, $patch_mode_flavour{VERB},\n+\t\t  ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'),\n+\t\t  $patch_mode_flavour{TARGET},\n \t\t  \" [y,n,q,a,d,/$other,?]? \";\n \t\tmy $line = prompt_single_character;\n \t\tif ($line) {\n@@ -1313,16 +1348,9 @@\n \n \tif (@result) {\n \t\tmy $fh;\n-\n-\t\topen $fh, '| git apply --cached --recount';\n-\t\tfor (@{$head->{TEXT}}, @result) {\n-\t\t\tprint $fh $_;\n-\t\t}\n-\t\tif (!close $fh) {\n-\t\t\tfor (@{$head->{TEXT}}, @result) {\n-\t\t\t\tprint STDERR $_;\n-\t\t\t}\n-\t\t}\n+\t\tmy @patch = (@{$head->{TEXT}}, @result);\n+\t\tmy $apply_routine = $patch_mode_flavour{APPLY};\n+\t\t&$apply_routine(@patch);\n \t\trefresh();\n \t}\n \n-- \n1.6.4.262.gbda8\n"},{"id":"120518","messageId":"5bbd70c769ada6bf75a899e77a65d12ac5730bdc.1250164190.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5 2/6] Add a small patch-mode testing library","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-13T12:29:40Z","receivedAt":"2009-08-13T12:29:40Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"The tests for {reset,commit,stash} -p will frequently have to set both\nworktree and index states to known values, and verify that the outcome\n(again both worktree and index) are what was expected.\n\nAdd a small helper library that lets us do these tasks more easily.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n t/lib-patch-mode.sh |   36 ++++++++++++++++++++++++++++++++++++\n 1 files changed, 36 insertions(+), 0 deletions(-)\n create mode 100755 t/lib-patch-mode.sh\n\ndiff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh\nnew file mode 100755\nindex 0000000..afb4b66\n--- /dev/null\n+++ b/t/lib-patch-mode.sh\n@@ -0,0 +1,36 @@\n+. ./test-lib.sh\n+\n+set_state () {\n+\techo \"$3\" > \"$1\" &&\n+\tgit add \"$1\" &&\n+\techo \"$2\" > \"$1\"\n+}\n+\n+save_state () {\n+\tnoslash=\"$(echo \"$1\" | tr / _)\" &&\n+\tcat \"$1\" > _worktree_\"$noslash\" &&\n+\tgit show :\"$1\" > _index_\"$noslash\"\n+}\n+\n+set_and_save_state () {\n+\tset_state \"$@\" &&\n+\tsave_state \"$1\"\n+}\n+\n+verify_state () {\n+\ttest \"$(cat \"$1\")\" = \"$2\" &&\n+\ttest \"$(git show :\"$1\")\" = \"$3\"\n+}\n+\n+verify_saved_state () {\n+\tnoslash=\"$(echo \"$1\" | tr / _)\" &&\n+\tverify_state \"$1\" \"$(cat _worktree_\"$noslash\")\" \"$(cat _index_\"$noslash\")\"\n+}\n+\n+save_head () {\n+\tgit rev-parse HEAD > _head\n+}\n+\n+verify_saved_head () {\n+\ttest \"$(cat _head)\" = \"$(git rev-parse HEAD)\"\n+}\n-- \n1.6.4.262.gbda8\n"},{"id":"120519","messageId":"fc79a0af2e3687373d4a7fdd9bf35b9a79b37cf9.1250164190.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5 3/6] builtin-add: refactor the meat of interactive_add()","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-13T12:29:41Z","receivedAt":"2009-08-13T12:29:41Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This moves the call setup for 'git add--interactive' to a separate\nfunction, as other users will call it without running\nvalidate_pathspec() first.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n builtin-add.c |   43 +++++++++++++++++++++++++++++--------------\n commit.h      |    2 ++\n 2 files changed, 31 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin-add.c b/builtin-add.c\nindex 581a2a1..c422a62 100644\n--- a/builtin-add.c\n+++ b/builtin-add.c\n@@ -131,27 +131,27 @@ static void refresh(int verbose, const char **pathspec)\n \treturn pathspec;\n }\n \n-int interactive_add(int argc, const char **argv, const char *prefix)\n+int run_add_interactive(const char *revision, const char *patch_mode,\n+\t\t\tconst char **pathspec)\n {\n-\tint status, ac;\n+\tint status, ac, pc = 0;\n \tconst char **args;\n-\tconst char **pathspec = NULL;\n \n-\tif (argc) {\n-\t\tpathspec = validate_pathspec(argc, argv, prefix);\n-\t\tif (!pathspec)\n-\t\t\treturn -1;\n-\t}\n+\tif (pathspec)\n+\t\twhile (pathspec[pc])\n+\t\t\tpc++;\n \n-\targs = xcalloc(sizeof(const char *), (argc + 4));\n+\targs = xcalloc(sizeof(const char *), (pc + 5));\n \tac = 0;\n \targs[ac++] = \"add--interactive\";\n-\tif (patch_interactive)\n-\t\targs[ac++] = \"--patch\";\n+\tif (patch_mode)\n+\t\targs[ac++] = patch_mode;\n+\tif (revision)\n+\t\targs[ac++] = revision;\n \targs[ac++] = \"--\";\n-\tif (argc) {\n-\t\tmemcpy(&(args[ac]), pathspec, sizeof(const char *) * argc);\n-\t\tac += argc;\n+\tif (pc) {\n+\t\tmemcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);\n+\t\tac += pc;\n \t}\n \targs[ac] = NULL;\n \n@@ -160,6 +160,21 @@ int interactive_add(int argc, const char **argv, const char *prefix)\n \treturn status;\n }\n \n+int interactive_add(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char **pathspec = NULL;\n+\n+\tif (argc) {\n+\t\tpathspec = validate_pathspec(argc, argv, prefix);\n+\t\tif (!pathspec)\n+\t\t\treturn -1;\n+\t}\n+\n+\treturn run_add_interactive(NULL,\n+\t\t\t\t   patch_interactive ? \"--patch\" : NULL,\n+\t\t\t\t   pathspec);\n+}\n+\n static int edit_patch(int argc, const char **argv, const char *prefix)\n {\n \tchar *file = xstrdup(git_path(\"ADD_EDIT.patch\"));\ndiff --git a/commit.h b/commit.h\nindex ba9f638..339f1f6 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -137,6 +137,8 @@ struct commit_graft {\n int in_merge_bases(struct commit *, struct commit **, int);\n \n extern int interactive_add(int argc, const char **argv, const char *prefix);\n+extern int run_add_interactive(const char *revision, const char *patch_mode,\n+\t\t\t       const char **pathspec);\n \n static inline int single_parent(struct commit *commit)\n {\n-- \n1.6.4.262.gbda8\n"},{"id":"120517","messageId":"f3b8fdbcf451fe28786ed221d02717e28423e6dd.1250164190.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5 4/6] Implement 'git reset --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-13T12:29:42Z","receivedAt":"2009-08-13T12:29:42Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This introduces a --patch mode for git-reset.  The basic case is\n\n  git reset --patch -- [files...]\n\nwhich acts as the opposite of 'git add --patch -- [files...]': it\noffers hunks for *un*staging.  Advanced usage is\n\n  git reset --patch <revision> -- [files...]\n\nwhich offers hunks from the diff between the index and <revision> for\nforward application to the index.  (That is, the basic case is just\n<revision> = HEAD.)\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-reset.txt |   15 ++++++++-\n builtin-reset.c             |   19 ++++++++++++\n git-add--interactive.perl   |   57 +++++++++++++++++++++++++++++++++--\n t/t7105-reset-patch.sh      |   69 +++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 154 insertions(+), 6 deletions(-)\n create mode 100755 t/t7105-reset-patch.sh\n\ndiff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt\nindex abb25d1..469cf6d 100644\n--- a/Documentation/git-reset.txt\n+++ b/Documentation/git-reset.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n [verse]\n 'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]\n 'git reset' [-q] [<commit>] [--] <paths>...\n+'git reset' --patch [<commit>] [--] [<paths>...]\n \n DESCRIPTION\n -----------\n@@ -23,8 +24,9 @@ the undo in the history.\n If you want to undo a commit other than the latest on a branch,\n linkgit:git-revert[1] is your friend.\n \n-The second form with 'paths' is used to revert selected paths in\n-the index from a given commit, without moving HEAD.\n+The second and third forms with 'paths' and/or --patch are used to\n+revert selected paths in the index from a given commit, without moving\n+HEAD.\n \n \n OPTIONS\n@@ -50,6 +52,15 @@ OPTIONS\n \tand updates the files that are different between the named commit\n \tand the current commit in the working tree.\n \n+-p::\n+--patch::\n+\tInteractively select hunks in the difference between the index\n+\tand <commit> (defaults to HEAD).  The chosen hunks are applied\n+\tin reverse to the index.\n++\n+This means that `git reset -p` is the opposite of `git add -p` (see\n+linkgit:git-add[1]).\n+\n -q::\n \tBe quiet, only report errors.\n \ndiff --git a/builtin-reset.c b/builtin-reset.c\nindex 5fa1789..246a127 100644\n--- a/builtin-reset.c\n+++ b/builtin-reset.c\n@@ -142,6 +142,17 @@ static void update_index_from_diff(struct diff_queue_struct *q,\n \t}\n }\n \n+static int interactive_reset(const char *revision, const char **argv,\n+\t\t\t     const char *prefix)\n+{\n+\tconst char **pathspec = NULL;\n+\n+\tif (*argv)\n+\t\tpathspec = get_pathspec(prefix, argv);\n+\n+\treturn run_add_interactive(revision, \"--patch=reset\", pathspec);\n+}\n+\n static int read_from_tree(const char *prefix, const char **argv,\n \t\tunsigned char *tree_sha1, int refresh_flags)\n {\n@@ -183,6 +194,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)\n int cmd_reset(int argc, const char **argv, const char *prefix)\n {\n \tint i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;\n+\tint patch_mode = 0;\n \tconst char *rev = \"HEAD\";\n \tunsigned char sha1[20], *orig = NULL, sha1_orig[20],\n \t\t\t\t*old_orig = NULL, sha1_old_orig[20];\n@@ -198,6 +210,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\t\t\t\"reset HEAD, index and working tree\", MERGE),\n \t\tOPT_BOOLEAN('q', NULL, &quiet,\n \t\t\t\t\"disable showing new HEAD in hard reset and progress message\"),\n+\t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\tOPT_END()\n \t};\n \n@@ -251,6 +264,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\tdie(\"Could not parse object '%s'.\", rev);\n \thashcpy(sha1, commit->object.sha1);\n \n+\tif (patch_mode) {\n+\t\tif (reset_type != NONE)\n+\t\t\tdie(\"--patch is incompatible with --{hard,mixed,soft}\");\n+\t\treturn interactive_reset(rev, argv + i, prefix);\n+\t}\n+\n \t/* git reset tree [--] paths... can be used to\n \t * load chosen paths from the tree into the index without\n \t * affecting the working tree nor HEAD. */\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 3606103..f040249 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -72,6 +72,7 @@\n \n # command line options\n my $patch_mode;\n+my $patch_mode_revision;\n \n sub apply_patch;\n \n@@ -85,6 +86,24 @@\n \t\tPARTICIPLE => 'staging',\n \t\tFILTER => 'file-only',\n \t},\n+\t'reset_head' => {\n+\t\tDIFF => 'diff-index -p --cached',\n+\t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply -R --cached',\n+\t\tVERB => 'Unstage',\n+\t\tTARGET => '',\n+\t\tPARTICIPLE => 'unstaging',\n+\t\tFILTER => 'index-only',\n+\t},\n+\t'reset_nothead' => {\n+\t\tDIFF => 'diff-index -p --cached',\n+\t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply -R --cached',\n+\t\tVERB => 'Apply',\n+\t\tTARGET => ' to index',\n+\t\tPARTICIPLE => 'applying',\n+\t\tFILTER => 'index-only',\n+\t},\n );\n \n my %patch_mode_flavour = %{$patch_modes{stage}};\n@@ -206,7 +225,14 @@\n \t\treturn if (!@tracked);\n \t}\n \n-\tmy $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';\n+\tmy $reference;\n+\tif (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') {\n+\t\t$reference = $patch_mode_revision;\n+\t} elsif (is_initial_commit()) {\n+\t\t$reference = get_empty_tree();\n+\t} else {\n+\t\t$reference = 'HEAD';\n+\t}\n \tfor (run_cmd_pipe(qw(git diff-index --cached\n \t\t\t     --numstat --summary), $reference,\n \t\t\t     '--', @tracked)) {\n@@ -640,6 +666,9 @@\n sub parse_diff {\n \tmy ($path) = @_;\n \tmy @diff_cmd = split(\" \", $patch_mode_flavour{DIFF});\n+\tif (defined $patch_mode_revision) {\n+\t\tpush @diff_cmd, $patch_mode_revision;\n+\t}\n \tmy @diff = run_cmd_pipe(\"git\", @diff_cmd, \"--\", $path);\n \tmy @colored = ();\n \tif ($diff_use_color) {\n@@ -1391,11 +1420,31 @@\n sub process_args {\n \treturn unless @ARGV;\n \tmy $arg = shift @ARGV;\n-\tif ($arg eq \"--patch\") {\n-\t\t$patch_mode = 1;\n-\t\t$arg = shift @ARGV or die \"missing --\";\n+\tif ($arg =~ /--patch(?:=(.*))?/) {\n+\t\tif (defined $1) {\n+\t\t\tif ($1 eq 'reset') {\n+\t\t\t\t$patch_mode = 'reset_head';\n+\t\t\t\t$patch_mode_revision = 'HEAD';\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\tif ($arg ne '--') {\n+\t\t\t\t\t$patch_mode_revision = $arg;\n+\t\t\t\t\t$patch_mode = ($arg eq 'HEAD' ?\n+\t\t\t\t\t\t       'reset_head' : 'reset_nothead');\n+\t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\t}\n+\t\t\t} elsif ($1 eq 'stage') {\n+\t\t\t\t$patch_mode = 'stage';\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t} else {\n+\t\t\t\tdie \"unknown --patch mode: $1\";\n+\t\t\t}\n+\t\t} else {\n+\t\t\t$patch_mode = 'stage';\n+\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t}\n \t\tdie \"invalid argument $arg, expecting --\"\n \t\t    unless $arg eq \"--\";\n+\t\t%patch_mode_flavour = %{$patch_modes{$patch_mode}};\n \t}\n \telsif ($arg ne \"--\") {\n \t\tdie \"invalid argument $arg, expecting --\";\ndiff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh\nnew file mode 100755\nindex 0000000..c1f4fc3\n--- /dev/null\n+++ b/t/t7105-reset-patch.sh\n@@ -0,0 +1,69 @@\n+#!/bin/sh\n+\n+test_description='git reset --patch'\n+. ./lib-patch-mode.sh\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\techo parent > dir/foo &&\n+\techo dummy > bar &&\n+\tgit add dir &&\n+\tgit commit -m initial &&\n+\ttest_tick &&\n+\ttest_commit second dir/foo head &&\n+\tset_and_save_state bar bar_work bar_index &&\n+\tsave_head\n+'\n+\n+# note: bar sorts before foo, so the first 'n' is always to skip 'bar'\n+\n+test_expect_success 'saying \"n\" does nothing' '\n+\tset_and_save_state dir/foo work work\n+\t(echo n; echo n) | git reset -p &&\n+\tverify_saved_state dir/foo &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'git reset -p' '\n+\t(echo n; echo y) | git reset -p &&\n+\tverify_state dir/foo work head &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'git reset -p HEAD^' '\n+\t(echo n; echo y) | git reset -p HEAD^ &&\n+\tverify_state dir/foo work parent &&\n+\tverify_saved_state bar\n+'\n+\n+# The idea in the rest is that bar sorts first, so we always say 'y'\n+# first and if the path limiter fails it'll apply to bar instead of\n+# dir/foo.  There's always an extra 'n' to reject edits to dir/foo in\n+# the failure case (and thus get out of the loop).\n+\n+test_expect_success 'git reset -p dir' '\n+\tset_state dir/foo work work\n+\t(echo y; echo n) | git reset -p dir &&\n+\tverify_state dir/foo work head &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'git reset -p -- foo (inside dir)' '\n+\tset_state dir/foo work work\n+\t(echo y; echo n) | (cd dir && git reset -p -- foo) &&\n+\tverify_state dir/foo work head &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'git reset -p HEAD^ -- dir' '\n+\t(echo y; echo n) | git reset -p HEAD^ -- dir &&\n+\tverify_state dir/foo work parent &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'none of this moved HEAD' '\n+\tverify_saved_head\n+'\n+\n+\n+test_done\n-- \n1.6.4.262.gbda8\n"},{"id":"120522","messageId":"b75f4adebf7f417b8c46746202933e8f7ff80331.1250164190.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5 5/6] Implement 'git checkout --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-13T12:29:43Z","receivedAt":"2009-08-13T12:29:43Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This introduces a --patch mode for git-checkout.  In the index usage\n\n  git checkout --patch -- [files...]\n\nit lets the user discard edits from the <files> at the granularity of\nhunks (by selecting hunks from 'git diff' and then reverse applying\nthem to the worktree).\n\nWe also accept a revision argument.  In the case\n\n  git checkout --patch HEAD -- [files...]\n\nwe offer hunks from the difference between HEAD and the worktree, and\nreverse applies them to both index and worktree, allowing you to\ndiscard staged changes completely.  In the non-HEAD usage\n\n  git checkout --patch <revision> -- [files...]\n\nit offers hunks from the difference between the worktree and\n<revision>.  The chosen hunks are then applied to both index and\nworktree.\n\nThe application to worktree and index is done \"atomically\" in the\nsense that we first check if the patch applies to the index (it should\nalways apply to the worktree).  If it does not, we give the user a\nchoice to either abort or apply to the worktree anyway.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-checkout.txt |   13 +++++-\n builtin-checkout.c             |   19 +++++++\n git-add--interactive.perl      |   61 +++++++++++++++++++++++\n t/t2015-checkout-patch.sh      |  107 ++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 199 insertions(+), 1 deletions(-)\n create mode 100755 t/t2015-checkout-patch.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex ad4b31e..26a5447 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [<branch>]\n 'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n+'git checkout' --patch [<tree-ish>] [--] [<paths>...]\n \n DESCRIPTION\n -----------\n@@ -25,7 +26,7 @@ use the --track or --no-track options, which will be passed to `git\n branch`.  As a convenience, --track without `-b` implies branch\n creation; see the description of --track below.\n \n-When <paths> are given, this command does *not* switch\n+When <paths> or --patch are given, this command does *not* switch\n branches.  It updates the named paths in the working tree from\n the index file, or from a named <tree-ish> (most often a commit).  In\n this case, the `-b` and `--track` options are meaningless and giving\n@@ -113,6 +114,16 @@ the conflicted merge in the specified paths.\n \t\"merge\" (default) and \"diff3\" (in addition to what is shown by\n \t\"merge\" style, shows the original contents).\n \n+-p::\n+--patch::\n+\tInteractively select hunks in the difference between the\n+\t<tree-ish> (or the index, if unspecified) and the working\n+\ttree.  The chosen hunks are then applied in reverse to the\n+\tworking tree (and if a <tree-ish> was specified, the index).\n++\n+This means that you can use `git checkout -p` to selectively discard\n+edits from your current working tree.\n+\n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\n \twhen prepended with \"refs/heads/\", is a valid ref), then that\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex 8a9a474..8b942ba 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -572,6 +572,13 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\n \treturn git_xmerge_config(var, value, cb);\n }\n \n+static int interactive_checkout(const char *revision, const char **pathspec,\n+\t\t\t\tstruct checkout_opts *opts)\n+{\n+\treturn run_add_interactive(revision, \"--patch=checkout\", pathspec);\n+}\n+\n+\n int cmd_checkout(int argc, const char **argv, const char *prefix)\n {\n \tstruct checkout_opts opts;\n@@ -580,6 +587,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \tstruct branch_info new;\n \tstruct tree *source_tree = NULL;\n \tchar *conflict_style = NULL;\n+\tint patch_mode = 0;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&opts.quiet),\n \t\tOPT_STRING('b', NULL, &opts.new_branch, \"new branch\", \"branch\"),\n@@ -594,6 +602,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOLEAN('m', \"merge\", &opts.merge, \"merge\"),\n \t\tOPT_STRING(0, \"conflict\", &conflict_style, \"style\",\n \t\t\t   \"conflict style (merge or diff3)\"),\n+\t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\tOPT_END(),\n \t};\n \tint has_dash_dash;\n@@ -608,6 +617,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options, checkout_usage,\n \t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n+\tif (patch_mode && (opts.track > 0 || opts.new_branch\n+\t\t\t   || opts.new_branch_log || opts.merge || opts.force))\n+\t\tdie (\"--patch is incompatible with all other options\");\n+\n \t/* --track without -b should DWIM */\n \tif (0 < opts.track && !opts.new_branch) {\n \t\tconst char *argv0 = argv[0];\n@@ -714,6 +727,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tif (!pathspec)\n \t\t\tdie(\"invalid path specification\");\n \n+\t\tif (patch_mode)\n+\t\t\treturn interactive_checkout(new.name, pathspec, &opts);\n+\n \t\t/* Checkout paths */\n \t\tif (opts.new_branch) {\n \t\t\tif (argc == 1) {\n@@ -729,6 +745,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\treturn checkout_paths(source_tree, pathspec, &opts);\n \t}\n \n+\tif (patch_mode)\n+\t\treturn interactive_checkout(new.name, NULL, &opts);\n+\n \tif (opts.new_branch) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (strbuf_check_branch_ref(&buf, opts.new_branch))\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex f040249..9c202fc 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -75,6 +75,7 @@\n my $patch_mode_revision;\n \n sub apply_patch;\n+sub apply_patch_for_checkout_commit;\n \n my %patch_modes = (\n \t'stage' => {\n@@ -104,6 +105,33 @@\n \t\tPARTICIPLE => 'applying',\n \t\tFILTER => 'index-only',\n \t},\n+\t'checkout_index' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => sub { apply_patch 'apply -R', @_; },\n+\t\tAPPLY_CHECK => 'apply -R',\n+\t\tVERB => 'Discard',\n+\t\tTARGET => ' from worktree',\n+\t\tPARTICIPLE => 'discarding',\n+\t\tFILTER => 'file-only',\n+\t},\n+\t'checkout_head' => {\n+\t\tDIFF => 'diff-index -p',\n+\t\tAPPLY => sub { apply_patch_for_checkout_commit '-R', @_ },\n+\t\tAPPLY_CHECK => 'apply',\n+\t\tVERB => 'Discard',\n+\t\tTARGET => ' from index and worktree',\n+\t\tPARTICIPLE => 'discarding',\n+\t\tFILTER => undef,\n+\t},\n+\t'checkout_nothead' => {\n+\t\tDIFF => 'diff-index -R -p',\n+\t\tAPPLY => sub { apply_patch_for_checkout_commit '', @_ },\n+\t\tAPPLY_CHECK => 'apply',\n+\t\tVERB => 'Apply',\n+\t\tTARGET => ' to index and worktree',\n+\t\tPARTICIPLE => 'applying',\n+\t\tFILTER => undef,\n+\t},\n );\n \n my %patch_mode_flavour = %{$patch_modes{stage}};\n@@ -1069,6 +1097,29 @@\n \treturn $ret;\n }\n \n+sub apply_patch_for_checkout_commit {\n+\tmy $reverse = shift;\n+\tmy $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_;\n+\tmy $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_;\n+\n+\tif ($applies_worktree && $applies_index) {\n+\t\trun_git_apply 'apply '.$reverse.' --cached --recount', @_;\n+\t\trun_git_apply 'apply '.$reverse.' --recount', @_;\n+\t\treturn 1;\n+\t} elsif (!$applies_index) {\n+\t\tprint colored $error_color, \"The selected hunks do not apply to the index!\\n\";\n+\t\tif (prompt_yesno \"Apply them to the worktree anyway? \") {\n+\t\t\treturn run_git_apply 'apply '.$reverse.' --recount', @_;\n+\t\t} else {\n+\t\t\tprint colored $error_color, \"Nothing was applied.\\n\";\n+\t\t\treturn 0;\n+\t\t}\n+\t} else {\n+\t\tprint STDERR @_;\n+\t\treturn 0;\n+\t}\n+}\n+\n sub patch_update_cmd {\n \tmy @all_mods = list_modified($patch_mode_flavour{FILTER});\n \tmy @mods = grep { !($_->{BINARY}) } @all_mods;\n@@ -1432,6 +1483,16 @@\n \t\t\t\t\t\t       'reset_head' : 'reset_nothead');\n \t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t\t}\n+\t\t\t} elsif ($1 eq 'checkout') {\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\tif ($arg eq '--') {\n+\t\t\t\t\t$patch_mode = 'checkout_index';\n+\t\t\t\t} else {\n+\t\t\t\t\t$patch_mode_revision = $arg;\n+\t\t\t\t\t$patch_mode = ($arg eq 'HEAD' ?\n+\t\t\t\t\t\t       'checkout_head' : 'checkout_nothead');\n+\t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\t}\n \t\t\t} elsif ($1 eq 'stage') {\n \t\t\t\t$patch_mode = 'stage';\n \t\t\t\t$arg = shift @ARGV or die \"missing --\";\ndiff --git a/t/t2015-checkout-patch.sh b/t/t2015-checkout-patch.sh\nnew file mode 100755\nindex 0000000..4d1c2e9\n--- /dev/null\n+++ b/t/t2015-checkout-patch.sh\n@@ -0,0 +1,107 @@\n+#!/bin/sh\n+\n+test_description='git checkout --patch'\n+\n+. ./lib-patch-mode.sh\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\techo parent > dir/foo &&\n+\techo dummy > bar &&\n+\tgit add bar dir/foo &&\n+\tgit commit -m initial &&\n+\ttest_tick &&\n+\ttest_commit second dir/foo head &&\n+\tset_and_save_state bar bar_work bar_index &&\n+\tsave_head\n+'\n+\n+# note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar'\n+\n+test_expect_success 'saying \"n\" does nothing' '\n+\tset_and_save_state dir/foo work head &&\n+\t(echo n; echo n) | git checkout -p &&\n+\tverify_saved_state bar &&\n+\tverify_saved_state dir/foo\n+'\n+\n+test_expect_success 'git checkout -p' '\n+\t(echo n; echo y) | git checkout -p &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'git checkout -p with staged changes' '\n+\tset_state dir/foo work index\n+\t(echo n; echo y) | git checkout -p &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo index index\n+'\n+\n+test_expect_success 'git checkout -p HEAD with NO staged changes: abort' '\n+\tset_and_save_state dir/foo work head &&\n+\t(echo n; echo y; echo n) | git checkout -p HEAD &&\n+\tverify_saved_state bar &&\n+\tverify_saved_state dir/foo\n+'\n+\n+test_expect_success 'git checkout -p HEAD with NO staged changes: apply' '\n+\t(echo n; echo y; echo y) | git checkout -p HEAD &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'git checkout -p HEAD with change already staged' '\n+\tset_state dir/foo index index\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo n; echo y; echo n) | git checkout -p HEAD &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'git checkout -p HEAD^' '\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo n; echo y; echo n) | git checkout -p HEAD^ &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo parent parent\n+'\n+\n+# The idea in the rest is that bar sorts first, so we always say 'y'\n+# first and if the path limiter fails it'll apply to bar instead of\n+# dir/foo.  There's always an extra 'n' to reject edits to dir/foo in\n+# the failure case (and thus get out of the loop).\n+\n+test_expect_success 'path limiting works: dir' '\n+\tset_state dir/foo work head &&\n+\t(echo y; echo n) | git checkout -p dir &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'path limiting works: -- dir' '\n+\tset_state dir/foo work head &&\n+\t(echo y; echo n) | git checkout -p -- dir &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'path limiting works: HEAD^ -- dir' '\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo y; echo n; echo n) | git checkout -p HEAD^ -- dir &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo parent parent\n+'\n+\n+test_expect_success 'path limiting works: foo inside dir' '\n+\tset_state dir/foo work head &&\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo y; echo n; echo n) | (cd dir && git checkout -p foo) &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'none of this moved HEAD' '\n+\tverify_saved_head\n+'\n+\n+test_done\n-- \n1.6.4.262.gbda8\n"},{"id":"120523","messageId":"249f68309e4cc6a5a9e458cb9d429423e64f17c9.1250164190.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5 6/6] Implement 'git stash save --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-13T12:29:44Z","receivedAt":"2009-08-13T12:29:44Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This adds a hunk-based mode to git-stash.  You can select hunks from\nthe difference between HEAD and worktree, and git-stash will build a\nstash that reflects these changes.  The index state of the stash is\nthe same as your current index, and we also let --patch imply\n--keep-index.\n\nNote that because the selected hunks are rolled back from the worktree\nbut not the index, the resulting state may appear somewhat confusing\nif you had also staged these changes.  This is not entirely\nsatisfactory, but due to the way stashes are applied, other solutions\nwould require a change to the stash format.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-stash.txt |   14 ++++++-\n git-add--interactive.perl   |   14 ++++++-\n git-stash.sh                |   80 +++++++++++++++++++++++++++++++++++-------\n t/t3904-stash-patch.sh      |   55 +++++++++++++++++++++++++++++\n 4 files changed, 145 insertions(+), 18 deletions(-)\n create mode 100755 t/t3904-stash-patch.sh\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 2f5ca7b..d206297 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n-'git stash' [save [--keep-index] [-q|--quiet] [<message>]]\n+'git stash' [save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]]\n 'git stash' clear\n 'git stash' create\n \n@@ -42,7 +42,7 @@ is also possible).\n OPTIONS\n -------\n \n-save [--keep-index] [-q|--quiet] [<message>]::\n+save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::\n \n \tSave your local modifications to a new 'stash', and run `git reset\n \t--hard` to revert them.  This is the default action when no\n@@ -51,6 +51,16 @@ save [--keep-index] [-q|--quiet] [<message>]::\n +\n If the `--keep-index` option is used, all changes already added to the\n index are left intact.\n++\n+With `--patch`, you can interactively select hunks from in the diff\n+between HEAD and the working tree to be stashed.  The stash entry is\n+constructed such that its index state is the same as the index state\n+of your repository, and its worktree contains only the changes you\n+selected interactively.  The selected changes are then rolled back\n+from your worktree.\n++\n+The `--patch` option implies `--keep-index`.  You can use\n+`--no-keep-index` to override this.\n \n list [<options>]::\n \ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 9c202fc..c5e0586 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -76,6 +76,7 @@\n \n sub apply_patch;\n sub apply_patch_for_checkout_commit;\n+sub apply_patch_for_stash;\n \n my %patch_modes = (\n \t'stage' => {\n@@ -87,6 +88,15 @@\n \t\tPARTICIPLE => 'staging',\n \t\tFILTER => 'file-only',\n \t},\n+\t'stash' => {\n+\t\tDIFF => 'diff-index -p HEAD',\n+\t\tAPPLY => sub { apply_patch 'apply --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Stash',\n+\t\tTARGET => '',\n+\t\tPARTICIPLE => 'stashing',\n+\t\tFILTER => undef,\n+\t},\n \t'reset_head' => {\n \t\tDIFF => 'diff-index -p --cached',\n \t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n@@ -1493,8 +1503,8 @@\n \t\t\t\t\t\t       'checkout_head' : 'checkout_nothead');\n \t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t\t}\n-\t\t\t} elsif ($1 eq 'stage') {\n-\t\t\t\t$patch_mode = 'stage';\n+\t\t\t} elsif ($1 eq 'stage' or $1 eq 'stash') {\n+\t\t\t\t$patch_mode = $1;\n \t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t} else {\n \t\t\t\tdie \"unknown --patch mode: $1\";\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 03e589f..567aa5d 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -21,6 +21,14 @@ trap 'rm -f \"$TMP-*\"' 0\n \n ref_stash=refs/stash\n \n+if git config --get-colorbool color.interactive; then\n+       help_color=\"$(git config --get-color color.interactive.help 'red bold')\"\n+       reset_color=\"$(git config --get-color '' reset)\"\n+else\n+       help_color=\n+       reset_color=\n+fi\n+\n no_changes () {\n \tgit diff-index --quiet --cached HEAD --ignore-submodules -- &&\n \tgit diff-files --quiet --ignore-submodules\n@@ -68,19 +76,44 @@ create_stash () {\n \t\tgit commit-tree $i_tree -p $b_commit) ||\n \t\tdie \"Cannot save the current index state\"\n \n-\t# state of the working tree\n-\tw_tree=$( (\n+\tif test -z \"$patch_mode\"\n+\tthen\n+\n+\t\t# state of the working tree\n+\t\tw_tree=$( (\n+\t\t\trm -f \"$TMP-index\" &&\n+\t\t\tcp -p ${GIT_INDEX_FILE-\"$GIT_DIR/index\"} \"$TMP-index\" &&\n+\t\t\tGIT_INDEX_FILE=\"$TMP-index\" &&\n+\t\t\texport GIT_INDEX_FILE &&\n+\t\t\tgit read-tree -m $i_tree &&\n+\t\t\tgit add -u &&\n+\t\t\tgit write-tree &&\n+\t\t\trm -f \"$TMP-index\"\n+\t\t) ) ||\n+\t\t\tdie \"Cannot save the current worktree state\"\n+\n+\telse\n+\n \t\trm -f \"$TMP-index\" &&\n-\t\tcp -p ${GIT_INDEX_FILE-\"$GIT_DIR/index\"} \"$TMP-index\" &&\n-\t\tGIT_INDEX_FILE=\"$TMP-index\" &&\n-\t\texport GIT_INDEX_FILE &&\n-\t\tgit read-tree -m $i_tree &&\n-\t\tgit add -u &&\n-\t\tgit write-tree &&\n-\t\trm -f \"$TMP-index\"\n-\t) ) ||\n+\t\tGIT_INDEX_FILE=\"$TMP-index\" git read-tree HEAD &&\n+\n+\t\t# find out what the user wants\n+\t\tGIT_INDEX_FILE=\"$TMP-index\" \\\n+\t\t\tgit add--interactive --patch=stash -- &&\n+\n+\t\t# state of the working tree\n+\t\tw_tree=$(GIT_INDEX_FILE=\"$TMP-index\" git write-tree) ||\n \t\tdie \"Cannot save the current worktree state\"\n \n+\t\tgit diff-tree -p HEAD $w_tree > \"$TMP-patch\" &&\n+\t\ttest -s \"$TMP-patch\" ||\n+\t\tdie \"No changes selected\"\n+\n+\t\trm -f \"$TMP-index\" ||\n+\t\tdie \"Cannot remove temporary index (can't happen)\"\n+\n+\tfi\n+\n \t# create the stash\n \tif test -z \"$stash_msg\"\n \tthen\n@@ -95,12 +128,20 @@ create_stash () {\n \n save_stash () {\n \tkeep_index=\n+\tpatch_mode=\n \twhile test $# != 0\n \tdo\n \t\tcase \"$1\" in\n \t\t--keep-index)\n \t\t\tkeep_index=t\n \t\t\t;;\n+\t\t--no-keep-index)\n+\t\t\tkeep_index=\n+\t\t\t;;\n+\t\t-p|--patch)\n+\t\t\tpatch_mode=t\n+\t\t\tkeep_index=t\n+\t\t\t;;\n \t\t-q|--quiet)\n \t\t\tGIT_QUIET=t\n \t\t\t;;\n@@ -131,11 +172,22 @@ save_stash () {\n \t\tdie \"Cannot save the current status\"\n \tsay Saved working directory and index state \"$stash_msg\"\n \n-\tgit reset --hard ${GIT_QUIET:+-q}\n-\n-\tif test -n \"$keep_index\" && test -n $i_tree\n+\tif test -z \"$patch_mode\"\n \tthen\n-\t\tgit read-tree --reset -u $i_tree\n+\t\tgit reset --hard ${GIT_QUIET:+-q}\n+\n+\t\tif test -n \"$keep_index\" && test -n $i_tree\n+\t\tthen\n+\t\t\tgit read-tree --reset -u $i_tree\n+\t\tfi\n+\telse\n+\t\tgit apply -R < \"$TMP-patch\" ||\n+\t\tdie \"Cannot remove worktree changes\"\n+\n+\t\tif test -z \"$keep_index\"\n+\t\tthen\n+\t\t\tgit reset\n+\t\tfi\n \tfi\n }\n \ndiff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh\nnew file mode 100755\nindex 0000000..f37e3bc\n--- /dev/null\n+++ b/t/t3904-stash-patch.sh\n@@ -0,0 +1,55 @@\n+#!/bin/sh\n+\n+test_description='git checkout --patch'\n+. ./lib-patch-mode.sh\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\techo parent > dir/foo &&\n+\techo dummy > bar &&\n+\tgit add bar dir/foo &&\n+\tgit commit -m initial &&\n+\ttest_tick &&\n+\ttest_commit second dir/foo head &&\n+\techo index > dir/foo &&\n+\tgit add dir/foo &&\n+\tset_and_save_state bar bar_work bar_index &&\n+\tsave_head\n+'\n+\n+# note: bar sorts before dir, so the first 'n' is always to skip 'bar'\n+\n+test_expect_success 'saying \"n\" does nothing' '\n+\tset_state dir/foo work index\n+\t(echo n; echo n) | test_must_fail git stash save -p &&\n+\tverify_state dir/foo work index &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'git stash -p' '\n+\t(echo n; echo y) | git stash save -p &&\n+\tverify_state dir/foo head index &&\n+\tverify_saved_state bar &&\n+\tgit reset --hard &&\n+\tgit stash apply &&\n+\tverify_state dir/foo work head &&\n+\tverify_state bar dummy dummy\n+'\n+\n+test_expect_success 'git stash -p --no-keep-index' '\n+\tset_state dir/foo work index &&\n+\tset_state bar bar_work bar_index &&\n+\t(echo n; echo y) | git stash save -p --no-keep-index &&\n+\tverify_state dir/foo head head &&\n+\tverify_state bar bar_work dummy &&\n+\tgit reset --hard &&\n+\tgit stash apply --index &&\n+\tverify_state dir/foo work index &&\n+\tverify_state bar dummy bar_index\n+'\n+\n+test_expect_success 'none of this moved HEAD' '\n+\tverify_saved_head\n+'\n+\n+test_done\n-- \n1.6.4.262.gbda8\n"},{"id":"120521","messageId":"b82a94299ffc996da68a678419371dac74919b48.1250164190.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"cover.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5 7/6] DWIM 'git stash save -p' for 'git stash -p'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-13T12:29:45Z","receivedAt":"2009-08-13T12:29:45Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Signed-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-stash.txt |    2 +-\n git-stash.sh                |    4 ++--\n 2 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 7af1840..ada16a0 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n 'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]\n-'git stash' [-k|--keep-index]\n+'git stash' [-p|--patch|-k|--keep-index]\n 'git stash' clear\n 'git stash' create\n \ndiff --git a/git-stash.sh b/git-stash.sh\nindex 81a72f6..9fd7289 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -406,8 +406,8 @@ branch)\n \tapply_to_branch \"$@\"\n \t;;\n *)\n-\tcase $#,\"$1\" in\n-\t0,|1,-k|1,--keep-index)\n+\tcase $#,\"$1\",\"$2\" in\n+\t0,,|1,-k,|1,--keep-index,|1,-p,|1,--patch,|2,-p,--no-keep-index|2,--patch,--no-keep-index)\n \t\tsave_stash \"$@\" &&\n \t\tsay '(To restore them type \"git stash apply\")'\n \t\t;;\n-- \n1.6.4.262.gbda8\n"},{"id":"120662","messageId":"20090814205753.GD12950@vidovic","threadId":"20208","inReplyTo":"cover.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5 0/6] Re: {checkout,reset,stash} --patch","fromName":"Nicolas Sebrecht","fromEmail":"nicolas.s.dev@gmx.fr","sentAt":"2009-08-14T20:57:53Z","receivedAt":"2009-08-14T20:57:53Z","isPatch":true,"sender":{"key":"nicolas.s.dev@gmx.fr","avatar":null},"body":"The 13/08/09, Thomas Rast wrote:\n> Junio C Hamano wrote:\n\n> > * tr/reset-checkout-patch (Tue Jul 28 23:20:12 2009 +0200) 8 commits\n> [...]\n> > Progress?\n> \n> Slow, as always.  There are three groups of changes:\n\n<...>\n\n> Thomas Rast (7):\n>   git-apply--interactive: Refactor patch mode code\n>   Add a small patch-mode testing library\n>   builtin-add: refactor the meat of interactive_add()\n>   Implement 'git reset --patch'\n>   Implement 'git checkout --patch'\n>   Implement 'git stash save --patch'\n>   DWIM 'git stash save -p' for 'git stash -p'\n\nTested-by: Nicolas Sebrecht <nicolas.s.dev@gmx.fr>\n\n-- \nNicolas Sebrecht\n"},{"id":"120688","messageId":"20090815065125.GA23068@coredump.intra.peff.net","threadId":"20208","inReplyTo":"cover.1250164190.git.trast@student.ethz.ch","subject":"Re: [PATCH v5 0/6] {checkout,reset,stash} --patch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2009-08-15T06:51:26Z","receivedAt":"2009-08-15T06:51:26Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Aug 13, 2009 at 02:29:38PM +0200, Thomas Rast wrote:\n\n> Those marked (**) are the only ones that changed semantics compared to\n> v4.  However, I adjusted the messages to look different:\n\nThanks for following up on this. This is something I've wanted for a\nwhile, and now my procrastination is paying off. ;)\n\n> add -p\t\t\tStage this hunk?\n\nMakes sense.\n\n> reset -p [HEAD]\t\tReset this hunk? (**)\n\nActually, it says \"Unstage this hunk\", but I like that even better.\n\n> reset -p other\t\tApply this hunk to index? (**)\n\nThis doesn't make sense to me. For example:\n\n  $ git show HEAD^:file\n  content\n  $ git show :file\n  content\n  with some changes\n  $ git reset -p HEAD^\n  diff --git a/file b/file\n  index d95f3ad..60a1a4e 100644\n  --- a/file\n  +++ b/file\n  @@ -1 +1,2 @@\n   content\n  +with some changes\n  Apply this hunk to index [y,n,q,a,d,/,e,?]?\n\nThe hunk is _already_ in the index. You are really asking to remove it\nfrom the index. So shouldn't it say something like \"Unstage this hunk\"\nor \"Remove this hunk from the index\"?\n\nOr did you intend to reverse the diff, as with \"checkout -p\" below?\n\n> checkout -p\t\tDiscard this hunk from worktree? (**)\n\nGood, that addresses my earlier confusion.\n\n> checkout -p HEAD\tDiscard this hunk from index and worktree? (**)\n\nGood. I like how it clarifies what is being touched.\n\n> checkout -p other\tApply this hunk to index and worktree? (**)\n\nI really expected this to just be the same as the \"HEAD\" case. That is,\nwith \"git checkout -p HEAD\", you are saying \"I'm not interested in these\nbits, discard to return back to HEAD\". So if I do \"git checkout -p\nHEAD^\", that is conceptually the same thing, except going back further\nin time.\n\nBut I guess you are thinking of it as \"pull these changes out of\n'other'\", in which case showing the reverse diff makes sense.\n\nI think this may be a situation where the user has one of two mental\nmodels in issuing the command, and we don't necessarily know which. So I\nguess what you have is fine, but I wanted to register my surprise.\n\n> stash -p\t\tStash this hunk?\n\nGetting greedy, is there a reason not to have \"stash apply -p\" as well?\n\n-Peff\n"},{"id":"120690","messageId":"7v4os9v7al.fsf@alter.siamese.dyndns.org","threadId":"20208","inReplyTo":"20090815065125.GA23068@coredump.intra.peff.net","subject":"Re: [PATCH v5 0/6] {checkout,reset,stash} --patch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-08-15T07:57:06Z","receivedAt":"2009-08-15T07:57:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n>> reset -p [HEAD]\t\tReset this hunk? (**)\n>> reset -p other\t\tApply this hunk to index? (**)\n>\n> This doesn't make sense to me.\n\nNot to me, either.\n\nLet's say you have modified $path and ran \"git add $path\" earlier.\n\n\"reset -p -- $path\" and \"reset -p HEAD -- $path\" both show what your index\nhas relative to the commit you are resetting your index to and offer to\n\"Unstage\" [*1*].  This is consistent and feels natural.\n\n\"reset -p HEAD^ -- $path\" however shows the same forward diff (i.e. how\nyour index is different compared to the commit HEAD^ you are resetting\nto), but offers to \"Apply\".\n\nWhen a user is resetting to the current HEAD (with or without an explicit\nHEAD parameter), it is likely that the user did \"git add\" earlier and is\ntrying to reverse the effect of that.  And showing a forward diff makes\nsense.  But when a user is resetting to a different commit, it may make\nsense to show a reverse diff, saying \"Here is a hunk you _could_ choose to\nuse to bring the current index to a different state, do you want to apply\nit to do so?\"  Perhaps you meant to show a reverse diff and use the word\n\"Apply\".\n\nHowever, that would break down rather badly when HEAD did not change $path\nsince HEAD^.  Logically what the \"reset -p\" would do to $path is the same,\nbut the patch shown and the operation offered to the user are opposite.\n\nYou could compare HEAD and the commit you are resetting the index to and\nsee if the path in question is different between the two commits, and\nswitch the direction---if there is no change, you show forward diff and\noffer to \"Remove this change out of the index\", if there is a change, you\nshow reverse diff and offer to \"Apply this change to the index\".  But if\nthe difference between HEAD and the commit you are resetting to does not\noverlap with the change you staged to the index earlier from your work\ntree, it is unclear such heuristics would yield a natural feel.\n\nSo I actually think you may be better off if you consistently showed a\nforward diff (i.e. what patch would have been applied to the commit in\nquestion to bring the index into its current shape), and always offer\n\"Remove this change out of the index?\"\n\nThe same comment applies to \"checkout -p HEAD\" vs \"checkout -p HEAD^\".\nI think the latter shouldn't show a reverse diff and offer \"Apply?\";\ninstead both should consitently show a forward diff (i.e. what patch would\nhave been applied to the commit to bring your work tree into its current\nshape), and offer \"Remove this change out of the index and the work tree?\".\n\n[Footnote]\n\n*1* I actually have a slight problem with the use of word \"Unstage\" in\nthis context; \"to stage\", at least to me, means \"adding _from the work\ntree_ to the index\", not just \"modifying the index\" from a random source.\nThe command is resetting the index in this case from a tree-ish and there\nis no work tree involved, and the word \"stage/unstage\" feels out of place.\n"},{"id":"120695","messageId":"200908151204.36709.trast@student.ethz.ch","threadId":"20208","inReplyTo":"20090815065125.GA23068@coredump.intra.peff.net","subject":"Re: [PATCH v5 0/6] {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-15T10:04:35Z","receivedAt":"2009-08-15T10:04:35Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Jeff King wrote:\n> > reset -p other\t\tApply this hunk to index? (**)\n> \n> This doesn't make sense to me. For example:\n[...]\n> The hunk is _already_ in the index. You are really asking to remove it\n> from the index. So shouldn't it say something like \"Unstage this hunk\"\n> or \"Remove this hunk from the index\"?\n> \n> Or did you intend to reverse the diff, as with \"checkout -p\" below?\n\nYes, sorry :-(  I apparently managed to forget the reversing here and\nnever noticed.  I'll make a fixed patch 4/6 today.\n\n> > checkout -p HEAD\tDiscard this hunk from index and worktree? (**)\n> \n> Good. I like how it clarifies what is being touched.\n> \n> > checkout -p other\tApply this hunk to index and worktree? (**)\n> \n> I really expected this to just be the same as the \"HEAD\" case. That is,\n> with \"git checkout -p HEAD\", you are saying \"I'm not interested in these\n> bits, discard to return back to HEAD\". So if I do \"git checkout -p\n> HEAD^\", that is conceptually the same thing, except going back further\n> in time.\n> \n> But I guess you are thinking of it as \"pull these changes out of\n> 'other'\", in which case showing the reverse diff makes sense.\n> \n> I think this may be a situation where the user has one of two mental\n> models in issuing the command, and we don't necessarily know which. So I\n> guess what you have is fine, but I wanted to register my surprise.\n\nWell, as I said earlier in the thread I'd hate to reverse the\ndirection of plain \"{reset,checkout,stash} -p\" because I want them to\nshow hunks that match visually.\n\nBut then my mental model of \"checkout other -- file\" is already of the\nsort \"fetch me the contents of 'file' from 'other'\", and I think I use\nit about as frequently in a forward as in a backward application.  And\nI just felt it would be easier to mentally go over the consequences if\nit shoed the diff the other way.\n\n(I'm sufficiently confused about which way is back that I think I'll\njust stop saying \"backward\"...)\n\n> > stash -p\t\tStash this hunk?\n> \n> Getting greedy, is there a reason not to have \"stash apply -p\" as well?\n\nShould be doable, I'll look at it.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"120703","messageId":"200908151215.00713.trast@student.ethz.ch","threadId":"20208","inReplyTo":"7v4os9v7al.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v5 0/6] {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-15T10:14:58Z","receivedAt":"2009-08-15T10:14:58Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano wrote:\n> Jeff King <peff@peff.net> writes:\n> \n> >> reset -p [HEAD]\t\tReset this hunk? (**)\n> >> reset -p other\t\tApply this hunk to index? (**)\n> >\n> > This doesn't make sense to me.\n> \n> Not to me, either.\n> \n> Let's say you have modified $path and ran \"git add $path\" earlier.\n> \n> \"reset -p -- $path\" and \"reset -p HEAD -- $path\" both show what your index\n> has relative to the commit you are resetting your index to and offer to\n> \"Unstage\" [*1*].  This is consistent and feels natural.\n> \n> \"reset -p HEAD^ -- $path\" however shows the same forward diff (i.e. how\n> your index is different compared to the commit HEAD^ you are resetting\n> to), but offers to \"Apply\".\n[...]\n> Perhaps you meant to show a reverse diff and use the word\n> \"Apply\".\n\nIndeed.\n\n> However, that would break down rather badly when HEAD did not change $path\n> since HEAD^.  Logically what the \"reset -p\" would do to $path is the same,\n> but the patch shown and the operation offered to the user are opposite.\n> \n> You could compare HEAD and the commit you are resetting the index to and\n> see if the path in question is different between the two commits, and\n> switch the direction---if there is no change, you show forward diff and\n> offer to \"Remove this change out of the index\", if there is a change, you\n> show reverse diff and offer to \"Apply this change to the index\".  But if\n> the difference between HEAD and the commit you are resetting to does not\n> overlap with the change you staged to the index earlier from your work\n> tree, it is unclear such heuristics would yield a natural feel.\n> \n> So I actually think you may be better off if you consistently showed a\n> forward diff (i.e. what patch would have been applied to the commit in\n> question to bring the index into its current shape), and always offer\n> \"Remove this change out of the index?\"\n\nHEAD^ is not special.  What do we do if the user resets to something\nthat is logically further progressed in time, perhaps another branch?\nI just have a much better mental model of it as \"apply <something> to\nindex\" than if it said: here's some diff between whatever commit you\ngave me, and your index; but I'm going to apply it reverse!\n\n(v4 actually did it this way and I found it a bit confusing...)\n\n> The same comment applies to \"checkout -p HEAD\" vs \"checkout -p HEAD^\".\n> I think the latter shouldn't show a reverse diff and offer \"Apply?\";\n> instead both should consitently show a forward diff (i.e. what patch would\n> have been applied to the commit to bring your work tree into its current\n> shape), and offer \"Remove this change out of the index and the work tree?\".\n\nAgain (and unlike in the reset case, I can actually see myself doing\nthis at times) the user could pass in a commit that is logically\nnewer than HEAD.\n\n> *1* I actually have a slight problem with the use of word \"Unstage\" in\n> this context; \"to stage\", at least to me, means \"adding _from the work\n> tree_ to the index\", not just \"modifying the index\" from a random source.\n> The command is resetting the index in this case from a tree-ish and there\n> is no work tree involved, and the word \"stage/unstage\" feels out of place.\n\nIt's not using \"unstage\" any more if the commit is not HEAD.  If it\nis, then we're doing the opposite of 'add -p', so doesn't the term\napply then?\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"120706","messageId":"fca8ddb4f49a85a9e524712adef069fd7d9ea32f.1250336810.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"b75f4adebf7f417b8c46746202933e8f7ff80331.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5.1 5/6] Implement 'git checkout --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-15T11:48:30Z","receivedAt":"2009-08-15T11:48:30Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This introduces a --patch mode for git-checkout.  In the index usage\n\n  git checkout --patch -- [files...]\n\nit lets the user discard edits from the <files> at the granularity of\nhunks (by selecting hunks from 'git diff' and then reverse applying\nthem to the worktree).\n\nWe also accept a revision argument.  In the case\n\n  git checkout --patch HEAD -- [files...]\n\nwe offer hunks from the difference between HEAD and the worktree, and\nreverse applies them to both index and worktree, allowing you to\ndiscard staged changes completely.  In the non-HEAD usage\n\n  git checkout --patch <revision> -- [files...]\n\nit offers hunks from the difference between the worktree and\n<revision>.  The chosen hunks are then applied to both index and\nworktree.\n\nThe application to worktree and index is done \"atomically\" in the\nsense that we first check if the patch applies to the index (it should\nalways apply to the worktree).  If it does not, we give the user a\nchoice to either abort or apply to the worktree anyway.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nI found another mismatch of the -R flags during double checking, as\nfollows:\n\n  diff --git c/git-add--interactive.perl i/git-add--interactive.perl\n  index 2363a77..21746d5 100755\n  --- c/git-add--interactive.perl\n  +++ i/git-add--interactive.perl\n  @@ -117,7 +117,7 @@\n   \t'checkout_head' => {\n   \t\tDIFF => 'diff-index -p',\n   \t\tAPPLY => sub { apply_patch_for_checkout_commit '-R', @_ },\n  -\t\tAPPLY_CHECK => 'apply',\n  +\t\tAPPLY_CHECK => 'apply -R',\n   \t\tVERB => 'Discard',\n   \t\tTARGET => ' from index and worktree',\n   \t\tPARTICIPLE => 'discarding',\n\n\n Documentation/git-checkout.txt |   13 +++++-\n builtin-checkout.c             |   19 +++++++\n git-add--interactive.perl      |   61 +++++++++++++++++++++++\n t/t2015-checkout-patch.sh      |  107 ++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 199 insertions(+), 1 deletions(-)\n create mode 100755 t/t2015-checkout-patch.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex ad4b31e..26a5447 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [<branch>]\n 'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n+'git checkout' --patch [<tree-ish>] [--] [<paths>...]\n \n DESCRIPTION\n -----------\n@@ -25,7 +26,7 @@ use the --track or --no-track options, which will be passed to `git\n branch`.  As a convenience, --track without `-b` implies branch\n creation; see the description of --track below.\n \n-When <paths> are given, this command does *not* switch\n+When <paths> or --patch are given, this command does *not* switch\n branches.  It updates the named paths in the working tree from\n the index file, or from a named <tree-ish> (most often a commit).  In\n this case, the `-b` and `--track` options are meaningless and giving\n@@ -113,6 +114,16 @@ the conflicted merge in the specified paths.\n \t\"merge\" (default) and \"diff3\" (in addition to what is shown by\n \t\"merge\" style, shows the original contents).\n \n+-p::\n+--patch::\n+\tInteractively select hunks in the difference between the\n+\t<tree-ish> (or the index, if unspecified) and the working\n+\ttree.  The chosen hunks are then applied in reverse to the\n+\tworking tree (and if a <tree-ish> was specified, the index).\n++\n+This means that you can use `git checkout -p` to selectively discard\n+edits from your current working tree.\n+\n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\n \twhen prepended with \"refs/heads/\", is a valid ref), then that\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex 8a9a474..8b942ba 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -572,6 +572,13 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\n \treturn git_xmerge_config(var, value, cb);\n }\n \n+static int interactive_checkout(const char *revision, const char **pathspec,\n+\t\t\t\tstruct checkout_opts *opts)\n+{\n+\treturn run_add_interactive(revision, \"--patch=checkout\", pathspec);\n+}\n+\n+\n int cmd_checkout(int argc, const char **argv, const char *prefix)\n {\n \tstruct checkout_opts opts;\n@@ -580,6 +587,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \tstruct branch_info new;\n \tstruct tree *source_tree = NULL;\n \tchar *conflict_style = NULL;\n+\tint patch_mode = 0;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&opts.quiet),\n \t\tOPT_STRING('b', NULL, &opts.new_branch, \"new branch\", \"branch\"),\n@@ -594,6 +602,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOLEAN('m', \"merge\", &opts.merge, \"merge\"),\n \t\tOPT_STRING(0, \"conflict\", &conflict_style, \"style\",\n \t\t\t   \"conflict style (merge or diff3)\"),\n+\t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\tOPT_END(),\n \t};\n \tint has_dash_dash;\n@@ -608,6 +617,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options, checkout_usage,\n \t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n+\tif (patch_mode && (opts.track > 0 || opts.new_branch\n+\t\t\t   || opts.new_branch_log || opts.merge || opts.force))\n+\t\tdie (\"--patch is incompatible with all other options\");\n+\n \t/* --track without -b should DWIM */\n \tif (0 < opts.track && !opts.new_branch) {\n \t\tconst char *argv0 = argv[0];\n@@ -714,6 +727,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tif (!pathspec)\n \t\t\tdie(\"invalid path specification\");\n \n+\t\tif (patch_mode)\n+\t\t\treturn interactive_checkout(new.name, pathspec, &opts);\n+\n \t\t/* Checkout paths */\n \t\tif (opts.new_branch) {\n \t\t\tif (argc == 1) {\n@@ -729,6 +745,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\treturn checkout_paths(source_tree, pathspec, &opts);\n \t}\n \n+\tif (patch_mode)\n+\t\treturn interactive_checkout(new.name, NULL, &opts);\n+\n \tif (opts.new_branch) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (strbuf_check_branch_ref(&buf, opts.new_branch))\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex d14f48c..21746d5 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -75,6 +75,7 @@\n my $patch_mode_revision;\n \n sub apply_patch;\n+sub apply_patch_for_checkout_commit;\n \n my %patch_modes = (\n \t'stage' => {\n@@ -104,6 +105,33 @@\n \t\tPARTICIPLE => 'applying',\n \t\tFILTER => 'index-only',\n \t},\n+\t'checkout_index' => {\n+\t\tDIFF => 'diff-files -p',\n+\t\tAPPLY => sub { apply_patch 'apply -R', @_; },\n+\t\tAPPLY_CHECK => 'apply -R',\n+\t\tVERB => 'Discard',\n+\t\tTARGET => ' from worktree',\n+\t\tPARTICIPLE => 'discarding',\n+\t\tFILTER => 'file-only',\n+\t},\n+\t'checkout_head' => {\n+\t\tDIFF => 'diff-index -p',\n+\t\tAPPLY => sub { apply_patch_for_checkout_commit '-R', @_ },\n+\t\tAPPLY_CHECK => 'apply -R',\n+\t\tVERB => 'Discard',\n+\t\tTARGET => ' from index and worktree',\n+\t\tPARTICIPLE => 'discarding',\n+\t\tFILTER => undef,\n+\t},\n+\t'checkout_nothead' => {\n+\t\tDIFF => 'diff-index -R -p',\n+\t\tAPPLY => sub { apply_patch_for_checkout_commit '', @_ },\n+\t\tAPPLY_CHECK => 'apply',\n+\t\tVERB => 'Apply',\n+\t\tTARGET => ' to index and worktree',\n+\t\tPARTICIPLE => 'applying',\n+\t\tFILTER => undef,\n+\t},\n );\n \n my %patch_mode_flavour = %{$patch_modes{stage}};\n@@ -1069,6 +1097,29 @@\n \treturn $ret;\n }\n \n+sub apply_patch_for_checkout_commit {\n+\tmy $reverse = shift;\n+\tmy $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_;\n+\tmy $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_;\n+\n+\tif ($applies_worktree && $applies_index) {\n+\t\trun_git_apply 'apply '.$reverse.' --cached --recount', @_;\n+\t\trun_git_apply 'apply '.$reverse.' --recount', @_;\n+\t\treturn 1;\n+\t} elsif (!$applies_index) {\n+\t\tprint colored $error_color, \"The selected hunks do not apply to the index!\\n\";\n+\t\tif (prompt_yesno \"Apply them to the worktree anyway? \") {\n+\t\t\treturn run_git_apply 'apply '.$reverse.' --recount', @_;\n+\t\t} else {\n+\t\t\tprint colored $error_color, \"Nothing was applied.\\n\";\n+\t\t\treturn 0;\n+\t\t}\n+\t} else {\n+\t\tprint STDERR @_;\n+\t\treturn 0;\n+\t}\n+}\n+\n sub patch_update_cmd {\n \tmy @all_mods = list_modified($patch_mode_flavour{FILTER});\n \tmy @mods = grep { !($_->{BINARY}) } @all_mods;\n@@ -1432,6 +1483,16 @@\n \t\t\t\t\t\t       'reset_head' : 'reset_nothead');\n \t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t\t}\n+\t\t\t} elsif ($1 eq 'checkout') {\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\tif ($arg eq '--') {\n+\t\t\t\t\t$patch_mode = 'checkout_index';\n+\t\t\t\t} else {\n+\t\t\t\t\t$patch_mode_revision = $arg;\n+\t\t\t\t\t$patch_mode = ($arg eq 'HEAD' ?\n+\t\t\t\t\t\t       'checkout_head' : 'checkout_nothead');\n+\t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\t}\n \t\t\t} elsif ($1 eq 'stage') {\n \t\t\t\t$patch_mode = 'stage';\n \t\t\t\t$arg = shift @ARGV or die \"missing --\";\ndiff --git a/t/t2015-checkout-patch.sh b/t/t2015-checkout-patch.sh\nnew file mode 100755\nindex 0000000..4d1c2e9\n--- /dev/null\n+++ b/t/t2015-checkout-patch.sh\n@@ -0,0 +1,107 @@\n+#!/bin/sh\n+\n+test_description='git checkout --patch'\n+\n+. ./lib-patch-mode.sh\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\techo parent > dir/foo &&\n+\techo dummy > bar &&\n+\tgit add bar dir/foo &&\n+\tgit commit -m initial &&\n+\ttest_tick &&\n+\ttest_commit second dir/foo head &&\n+\tset_and_save_state bar bar_work bar_index &&\n+\tsave_head\n+'\n+\n+# note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar'\n+\n+test_expect_success 'saying \"n\" does nothing' '\n+\tset_and_save_state dir/foo work head &&\n+\t(echo n; echo n) | git checkout -p &&\n+\tverify_saved_state bar &&\n+\tverify_saved_state dir/foo\n+'\n+\n+test_expect_success 'git checkout -p' '\n+\t(echo n; echo y) | git checkout -p &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'git checkout -p with staged changes' '\n+\tset_state dir/foo work index\n+\t(echo n; echo y) | git checkout -p &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo index index\n+'\n+\n+test_expect_success 'git checkout -p HEAD with NO staged changes: abort' '\n+\tset_and_save_state dir/foo work head &&\n+\t(echo n; echo y; echo n) | git checkout -p HEAD &&\n+\tverify_saved_state bar &&\n+\tverify_saved_state dir/foo\n+'\n+\n+test_expect_success 'git checkout -p HEAD with NO staged changes: apply' '\n+\t(echo n; echo y; echo y) | git checkout -p HEAD &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'git checkout -p HEAD with change already staged' '\n+\tset_state dir/foo index index\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo n; echo y; echo n) | git checkout -p HEAD &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'git checkout -p HEAD^' '\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo n; echo y; echo n) | git checkout -p HEAD^ &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo parent parent\n+'\n+\n+# The idea in the rest is that bar sorts first, so we always say 'y'\n+# first and if the path limiter fails it'll apply to bar instead of\n+# dir/foo.  There's always an extra 'n' to reject edits to dir/foo in\n+# the failure case (and thus get out of the loop).\n+\n+test_expect_success 'path limiting works: dir' '\n+\tset_state dir/foo work head &&\n+\t(echo y; echo n) | git checkout -p dir &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'path limiting works: -- dir' '\n+\tset_state dir/foo work head &&\n+\t(echo y; echo n) | git checkout -p -- dir &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'path limiting works: HEAD^ -- dir' '\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo y; echo n; echo n) | git checkout -p HEAD^ -- dir &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo parent parent\n+'\n+\n+test_expect_success 'path limiting works: foo inside dir' '\n+\tset_state dir/foo work head &&\n+\t# the third n is to get out in case it mistakenly does not apply\n+\t(echo y; echo n; echo n) | (cd dir && git checkout -p foo) &&\n+\tverify_saved_state bar &&\n+\tverify_state dir/foo head head\n+'\n+\n+test_expect_success 'none of this moved HEAD' '\n+\tverify_saved_head\n+'\n+\n+test_done\n-- \n1.6.4.287.g3e02d\n"},{"id":"120707","messageId":"00b886234218b93a324166058144e21bacce202d.1250335949.git.trast@student.ethz.ch","threadId":"20208","inReplyTo":"f3b8fdbcf451fe28786ed221d02717e28423e6dd.1250164190.git.trast@student.ethz.ch","subject":"[PATCH v5.1 4/6] Implement 'git reset --patch'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-15T11:48:31Z","receivedAt":"2009-08-15T11:48:31Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This introduces a --patch mode for git-reset.  The basic case is\n\n  git reset --patch -- [files...]\n\nwhich acts as the opposite of 'git add --patch -- [files...]': it\noffers hunks for *un*staging.  Advanced usage is\n\n  git reset --patch <revision> -- [files...]\n\nwhich offers hunks from the diff between the index and <revision> for\nforward application to the index.  (That is, the basic case is just\n<revision> = HEAD.)\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nJeff noticed the prompt and patch direction weren't matching:\n\n  http://article.gmane.org/gmane.comp.version-control.git/125981\n\nThis flips the direction of the patch in the 'git reset -p other'\ncase, as I really wanted it that way around.\n\n\n Documentation/git-reset.txt |   15 ++++++++-\n builtin-reset.c             |   19 ++++++++++++\n git-add--interactive.perl   |   57 +++++++++++++++++++++++++++++++++--\n t/t7105-reset-patch.sh      |   69 +++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 154 insertions(+), 6 deletions(-)\n create mode 100755 t/t7105-reset-patch.sh\n\ndiff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt\nindex abb25d1..469cf6d 100644\n--- a/Documentation/git-reset.txt\n+++ b/Documentation/git-reset.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n [verse]\n 'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]\n 'git reset' [-q] [<commit>] [--] <paths>...\n+'git reset' --patch [<commit>] [--] [<paths>...]\n \n DESCRIPTION\n -----------\n@@ -23,8 +24,9 @@ the undo in the history.\n If you want to undo a commit other than the latest on a branch,\n linkgit:git-revert[1] is your friend.\n \n-The second form with 'paths' is used to revert selected paths in\n-the index from a given commit, without moving HEAD.\n+The second and third forms with 'paths' and/or --patch are used to\n+revert selected paths in the index from a given commit, without moving\n+HEAD.\n \n \n OPTIONS\n@@ -50,6 +52,15 @@ OPTIONS\n \tand updates the files that are different between the named commit\n \tand the current commit in the working tree.\n \n+-p::\n+--patch::\n+\tInteractively select hunks in the difference between the index\n+\tand <commit> (defaults to HEAD).  The chosen hunks are applied\n+\tin reverse to the index.\n++\n+This means that `git reset -p` is the opposite of `git add -p` (see\n+linkgit:git-add[1]).\n+\n -q::\n \tBe quiet, only report errors.\n \ndiff --git a/builtin-reset.c b/builtin-reset.c\nindex 5fa1789..246a127 100644\n--- a/builtin-reset.c\n+++ b/builtin-reset.c\n@@ -142,6 +142,17 @@ static void update_index_from_diff(struct diff_queue_struct *q,\n \t}\n }\n \n+static int interactive_reset(const char *revision, const char **argv,\n+\t\t\t     const char *prefix)\n+{\n+\tconst char **pathspec = NULL;\n+\n+\tif (*argv)\n+\t\tpathspec = get_pathspec(prefix, argv);\n+\n+\treturn run_add_interactive(revision, \"--patch=reset\", pathspec);\n+}\n+\n static int read_from_tree(const char *prefix, const char **argv,\n \t\tunsigned char *tree_sha1, int refresh_flags)\n {\n@@ -183,6 +194,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)\n int cmd_reset(int argc, const char **argv, const char *prefix)\n {\n \tint i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;\n+\tint patch_mode = 0;\n \tconst char *rev = \"HEAD\";\n \tunsigned char sha1[20], *orig = NULL, sha1_orig[20],\n \t\t\t\t*old_orig = NULL, sha1_old_orig[20];\n@@ -198,6 +210,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\t\t\t\"reset HEAD, index and working tree\", MERGE),\n \t\tOPT_BOOLEAN('q', NULL, &quiet,\n \t\t\t\t\"disable showing new HEAD in hard reset and progress message\"),\n+\t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\tOPT_END()\n \t};\n \n@@ -251,6 +264,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\tdie(\"Could not parse object '%s'.\", rev);\n \thashcpy(sha1, commit->object.sha1);\n \n+\tif (patch_mode) {\n+\t\tif (reset_type != NONE)\n+\t\t\tdie(\"--patch is incompatible with --{hard,mixed,soft}\");\n+\t\treturn interactive_reset(rev, argv + i, prefix);\n+\t}\n+\n \t/* git reset tree [--] paths... can be used to\n \t * load chosen paths from the tree into the index without\n \t * affecting the working tree nor HEAD. */\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 3606103..d14f48c 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -72,6 +72,7 @@\n \n # command line options\n my $patch_mode;\n+my $patch_mode_revision;\n \n sub apply_patch;\n \n@@ -85,6 +86,24 @@\n \t\tPARTICIPLE => 'staging',\n \t\tFILTER => 'file-only',\n \t},\n+\t'reset_head' => {\n+\t\tDIFF => 'diff-index -p --cached',\n+\t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply -R --cached',\n+\t\tVERB => 'Unstage',\n+\t\tTARGET => '',\n+\t\tPARTICIPLE => 'unstaging',\n+\t\tFILTER => 'index-only',\n+\t},\n+\t'reset_nothead' => {\n+\t\tDIFF => 'diff-index -R -p --cached',\n+\t\tAPPLY => sub { apply_patch 'apply --cached', @_; },\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Apply',\n+\t\tTARGET => ' to index',\n+\t\tPARTICIPLE => 'applying',\n+\t\tFILTER => 'index-only',\n+\t},\n );\n \n my %patch_mode_flavour = %{$patch_modes{stage}};\n@@ -206,7 +225,14 @@\n \t\treturn if (!@tracked);\n \t}\n \n-\tmy $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';\n+\tmy $reference;\n+\tif (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') {\n+\t\t$reference = $patch_mode_revision;\n+\t} elsif (is_initial_commit()) {\n+\t\t$reference = get_empty_tree();\n+\t} else {\n+\t\t$reference = 'HEAD';\n+\t}\n \tfor (run_cmd_pipe(qw(git diff-index --cached\n \t\t\t     --numstat --summary), $reference,\n \t\t\t     '--', @tracked)) {\n@@ -640,6 +666,9 @@\n sub parse_diff {\n \tmy ($path) = @_;\n \tmy @diff_cmd = split(\" \", $patch_mode_flavour{DIFF});\n+\tif (defined $patch_mode_revision) {\n+\t\tpush @diff_cmd, $patch_mode_revision;\n+\t}\n \tmy @diff = run_cmd_pipe(\"git\", @diff_cmd, \"--\", $path);\n \tmy @colored = ();\n \tif ($diff_use_color) {\n@@ -1391,11 +1420,31 @@\n sub process_args {\n \treturn unless @ARGV;\n \tmy $arg = shift @ARGV;\n-\tif ($arg eq \"--patch\") {\n-\t\t$patch_mode = 1;\n-\t\t$arg = shift @ARGV or die \"missing --\";\n+\tif ($arg =~ /--patch(?:=(.*))?/) {\n+\t\tif (defined $1) {\n+\t\t\tif ($1 eq 'reset') {\n+\t\t\t\t$patch_mode = 'reset_head';\n+\t\t\t\t$patch_mode_revision = 'HEAD';\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\tif ($arg ne '--') {\n+\t\t\t\t\t$patch_mode_revision = $arg;\n+\t\t\t\t\t$patch_mode = ($arg eq 'HEAD' ?\n+\t\t\t\t\t\t       'reset_head' : 'reset_nothead');\n+\t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\t}\n+\t\t\t} elsif ($1 eq 'stage') {\n+\t\t\t\t$patch_mode = 'stage';\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t} else {\n+\t\t\t\tdie \"unknown --patch mode: $1\";\n+\t\t\t}\n+\t\t} else {\n+\t\t\t$patch_mode = 'stage';\n+\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t}\n \t\tdie \"invalid argument $arg, expecting --\"\n \t\t    unless $arg eq \"--\";\n+\t\t%patch_mode_flavour = %{$patch_modes{$patch_mode}};\n \t}\n \telsif ($arg ne \"--\") {\n \t\tdie \"invalid argument $arg, expecting --\";\ndiff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh\nnew file mode 100755\nindex 0000000..c1f4fc3\n--- /dev/null\n+++ b/t/t7105-reset-patch.sh\n@@ -0,0 +1,69 @@\n+#!/bin/sh\n+\n+test_description='git reset --patch'\n+. ./lib-patch-mode.sh\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\techo parent > dir/foo &&\n+\techo dummy > bar &&\n+\tgit add dir &&\n+\tgit commit -m initial &&\n+\ttest_tick &&\n+\ttest_commit second dir/foo head &&\n+\tset_and_save_state bar bar_work bar_index &&\n+\tsave_head\n+'\n+\n+# note: bar sorts before foo, so the first 'n' is always to skip 'bar'\n+\n+test_expect_success 'saying \"n\" does nothing' '\n+\tset_and_save_state dir/foo work work\n+\t(echo n; echo n) | git reset -p &&\n+\tverify_saved_state dir/foo &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'git reset -p' '\n+\t(echo n; echo y) | git reset -p &&\n+\tverify_state dir/foo work head &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'git reset -p HEAD^' '\n+\t(echo n; echo y) | git reset -p HEAD^ &&\n+\tverify_state dir/foo work parent &&\n+\tverify_saved_state bar\n+'\n+\n+# The idea in the rest is that bar sorts first, so we always say 'y'\n+# first and if the path limiter fails it'll apply to bar instead of\n+# dir/foo.  There's always an extra 'n' to reject edits to dir/foo in\n+# the failure case (and thus get out of the loop).\n+\n+test_expect_success 'git reset -p dir' '\n+\tset_state dir/foo work work\n+\t(echo y; echo n) | git reset -p dir &&\n+\tverify_state dir/foo work head &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'git reset -p -- foo (inside dir)' '\n+\tset_state dir/foo work work\n+\t(echo y; echo n) | (cd dir && git reset -p -- foo) &&\n+\tverify_state dir/foo work head &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'git reset -p HEAD^ -- dir' '\n+\t(echo y; echo n) | git reset -p HEAD^ -- dir &&\n+\tverify_state dir/foo work parent &&\n+\tverify_saved_state bar\n+'\n+\n+test_expect_success 'none of this moved HEAD' '\n+\tverify_saved_head\n+'\n+\n+\n+test_done\n-- \n1.6.4.287.g3e02d\n"},{"id":"121121","messageId":"20090818164826.GA27377@coredump.intra.peff.net","threadId":"20208","inReplyTo":"200908151204.36709.trast@student.ethz.ch","subject":"Re: [PATCH v5 0/6] {checkout,reset,stash} --patch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2009-08-18T16:48:26Z","receivedAt":"2009-08-18T16:48:26Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Aug 15, 2009 at 12:04:35PM +0200, Thomas Rast wrote:\n\n> > Getting greedy, is there a reason not to have \"stash apply -p\" as well?\n> \n> Should be doable, I'll look at it.\n\nI took a look at this today. It is actually a bit harder than I had\nhoped. The problem is that a stash is not a _state_, but rather two\nstates whose difference is of interest to us.\n\nSo it's not right to just pick hunks out of the stash as compared with\nthe current tree. You will get \"false\" hunks for the changes between\nthe current tree and the base upon which the stash was created. In fact,\nwe actually do a full 3-way merge with the current state and the new\nstate with the stash base as the ancestor.\n\nThe strategy I employ in the patch below is to actually just prune the\nstash tree against the base, and then use that result to do the merge.\nFor example, consider the following situation:\n\n  $ echo base >file && git add file && git commit -m base\n  $ echo stash >file && git stash\n  $ echo tree >file && git add file\n\nNow you have a stash whose base has 'base' and whose content has\n'stash', and your current tree has 'tree'. Applying the stash should\ntherefore cause a conflict (but I am using such a simple example because\nwe can still illustrate what's happening).\n\nYou can see that ignoring the base creates an incorrect result. You\nwould see that the stash has 'stash' and we have 'tree', and ask about\napplying the hunk\n\n  -tree\n  +stash\n\nbut that's not right. We should have a conflict.\n\nSo what I do instead is to load the base tree into a temporary index,\nthen git-add--interactive the changes from the stashed tree, then put\nwrite the result into a new tree, which becomes our new merge point. So\nyou will be asked if you want to apply the hunk\n\n  -base\n  +stash\n\nIf not, then the resulting tree will have 'base'. Since the base also\nhas 'base', the stash has done nothing, and the 3-way merge will keep\nyour tree contents. If you do, then the resulting tree will have\n'stash', and you will have a 3-way merge with a conflict.\n\nNow there is a slight problem with that. What if you had already applied\nor recreated part of the stash? In other words, instead of 'tree' in\nyour current content, you had 'stash'? We would _still_ ask if you\nwanted to replace 'base' with 'stash', even though when we get to the\nthree-way merge, it will be a no-op.\n\nHmm. I was about to write \"so we need some clever way of integrating the\ninteractive hunk selection with a 3-way merge\". But I just had a\nthought: we should do it in the reverse order. We do the three-way merge\ninto a temporary index, and then ask the user to apply the result of\n_that_ tree into the working tree. Or maybe I am missing something else\nobvious and you can enlighten me.\n\nI'll try to experiment with merging first, though I doubt I will have\nany more time for the next day or two. In the meantime, you can peek at\nthe current patch I have below. It has two additional problems:\n\n  1. For --index mode, it actually invokes add--interactive twice. It\n     would be nice to do both passes at the same time, but I don't think\n     it is possible with the current add--interactive infrastructure.\n\n  2. You will notice a hack-ish 'sleep' in the test; because of the\n     double add--interactive, when piping from stdin, the first one eats\n     the responses in its input buffer and the second one never sees\n     them.\n\n-Peff\n\n---\n git-add--interactive.perl    |   28 ++++++++++++++++++++++++++++\n git-stash.sh                 |   35 +++++++++++++++++++++++++++++------\n t/t3905-stash-apply-patch.sh |   42 ++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 99 insertions(+), 6 deletions(-)\n create mode 100755 t/t3905-stash-apply-patch.sh\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 392efb9..cbbf0a7 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -97,6 +97,28 @@ my %patch_modes = (\n \t\tPARTICIPLE => 'stashing',\n \t\tFILTER => undef,\n \t},\n+\t'stash-apply-tree' => {\n+\t\tDIFF => 'diff-index -R -p --cached',\n+\t\tAPPLY => sub {\n+\t\t\tapply_patch 'apply --cached', $patch_mode_revision, @_;\n+\t\t},\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Apply',\n+\t\tTARGET => ' to working tree',\n+\t\tPARTICIPLE => 'applying',\n+\t\tFILTER => undef,\n+\t},\n+\t'stash-apply-index' => {\n+\t\tDIFF => 'diff-index -R -p --cached',\n+\t\tAPPLY => sub {\n+\t\t\tapply_patch 'apply --cached', $patch_mode_revision, @_;\n+\t\t},\n+\t\tAPPLY_CHECK => 'apply --cached',\n+\t\tVERB => 'Apply',\n+\t\tTARGET => ' to index',\n+\t\tPARTICIPLE => 'applying',\n+\t\tFILTER => undef,\n+\t},\n \t'reset_head' => {\n \t\tDIFF => 'diff-index -p --cached',\n \t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n@@ -1507,6 +1529,12 @@ sub process_args {\n \t\t\t\t\t\t       'checkout_head' : 'checkout_nothead');\n \t\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t\t}\n+\t\t\t} elsif ($1 eq 'stash-apply-tree'\n+\t\t\t\t\t|| $1 eq 'stash-apply-index') {\n+\t\t\t\t$patch_mode = $1;\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t\t$patch_mode_revision = $arg;\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n \t\t\t} elsif ($1 eq 'stage' or $1 eq 'stash') {\n \t\t\t\t$patch_mode = $1;\n \t\t\t\t$arg = shift @ARGV or die \"missing --\";\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 9efc46d..8090bbf 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -214,8 +214,19 @@ show_stash () {\n \tgit diff $flags $b_commit $w_commit\n }\n \n+munge_tree_interactive () {\n+\t(\n+\t GIT_INDEX_FILE=\"$TMP-index\"\n+\t export GIT_INDEX_FILE\n+\t git read-tree --reset \"$2\" &&\n+\t git add--interactive --patch=stash-apply-$1 \"$3\" -- >&3 &&\n+\t git write-tree\n+\t)\n+}\n+\n apply_stash () {\n \tunstash_index=\n+\tpatch_mode=\n \n \twhile test $# != 0\n \tdo\n@@ -226,6 +237,9 @@ apply_stash () {\n \t\t-q|--quiet)\n \t\t\tGIT_QUIET=t\n \t\t\t;;\n+\t\t-p|--patch)\n+\t\t\tpatch_mode=t\n+\t\t\t;;\n \t\t*)\n \t\t\tbreak\n \t\t\t;;\n@@ -258,12 +272,21 @@ apply_stash () {\n \tif test -n \"$unstash_index\" && test \"$b_tree\" != \"$i_tree\" &&\n \t\t\ttest \"$c_tree\" != \"$i_tree\"\n \tthen\n-\t\tgit diff-tree --binary $s^2^..$s^2 | git apply --cached\n-\t\ttest $? -ne 0 &&\n-\t\t\tdie 'Conflicts in index. Try without --index.'\n-\t\tunstashed_index_tree=$(git write-tree) ||\n-\t\t\tdie 'Could not save index tree'\n-\t\tgit reset\n+\t\tif test -n \"$patch_mode\"; then\n+\t\t\ti_tree=`munge_tree_interactive index $b_tree $i_tree` 3>&1\n+\t\tfi\n+\t\tif test \"$b_tree\" != \"$i_tree\"; then\n+\t\t\tgit diff-tree --binary $b_tree $i_tree | git apply --cached\n+\t\t\ttest $? -ne 0 &&\n+\t\t\t\tdie 'Conflicts in index. Try without --index.'\n+\t\t\tunstashed_index_tree=$(git write-tree) ||\n+\t\t\t\tdie 'Could not save index tree'\n+\t\t\tgit reset\n+\t\tfi\n+\tfi\n+\n+\tif test -n \"$patch_mode\"; then\n+\t\tw_tree=`munge_tree_interactive tree $b_tree $w_tree` 3>&1\n \tfi\n \n \teval \"\ndiff --git a/t/t3905-stash-apply-patch.sh b/t/t3905-stash-apply-patch.sh\nnew file mode 100755\nindex 0000000..07cd69b\n--- /dev/null\n+++ b/t/t3905-stash-apply-patch.sh\n@@ -0,0 +1,42 @@\n+#!/bin/sh\n+\n+test_description='git stash apply --patch'\n+. ./lib-patch-mode.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit one bar bar_head &&\n+\ttest_commit two foo foo_head &&\n+\tset_state bar bar_work bar_index &&\n+\tset_state foo foo_work foo_index &&\n+\tsave_head &&\n+\tgit stash\n+'\n+\n+test_expect_success 'saying \"n\" does nothing' '\n+\tgit reset --hard &&\n+\t(echo n; echo n) | git stash apply -p &&\n+\tverify_state bar bar_head bar_head &&\n+\tverify_state foo foo_head foo_head\n+'\n+\n+# n/y will apply foo but not bar\n+test_expect_success 'git stash apply -p' '\n+\tgit reset --hard &&\n+\t(echo n; echo y) | git stash apply -p &&\n+\tverify_state bar bar_head bar_head &&\n+\tverify_state foo foo_work foo_head\n+'\n+\n+# we need two per file, for index and working tree\n+test_expect_success 'git stash apply -p --index' '\n+\tgit reset --hard &&\n+\t(echo n; echo y; sleep 2; echo n; echo y) | git stash apply -p --index &&\n+\tverify_state bar bar_head bar_head &&\n+\tverify_state foo foo_work foo_index\n+'\n+\n+test_expect_success 'none of this moved HEAD' '\n+\tverify_saved_head\n+'\n+\n+test_done\n-- \n1.6.4.304.ge0c3be.dirty\n"},{"id":"121231","messageId":"200908191140.22235.trast@student.ethz.ch","threadId":"20208","inReplyTo":"20090818164826.GA27377@coredump.intra.peff.net","subject":"Re: [PATCH v5 0/6] {checkout,reset,stash} --patch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-08-19T09:40:20Z","receivedAt":"2009-08-19T09:40:20Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Jeff King wrote:\n> \n> I took a look at this today.\n\nHeh, now *my* procrastination is paying off :-)\n\n> Hmm. I was about to write \"so we need some clever way of integrating the\n> interactive hunk selection with a 3-way merge\". But I just had a\n> thought: we should do it in the reverse order. We do the three-way merge\n> into a temporary index, and then ask the user to apply the result of\n> _that_ tree into the working tree. Or maybe I am missing something else\n> obvious and you can enlighten me.\n\nI think that is the correct way to go about it from the user's POV.\nHe would be confused if the patch applied to WT/index were different\n(because of a later merge) from the hunks he chose in the -p loop.\n\nHowever, there's the issue of merge conflicts.  Some options I can\nthink of are\n\n1) refuse to work in the face of merge problems\n\n2) stash requires a clean WT, so we can move the user's index out of\n   the way and use temporary index + WT to let the user resolve the\n   conflicts\n\n3) require both clean WT and index so we can simply use the repo to\n   resolve\n\n(The first one isn't quite as restrictive as it sounds; the user can\nalways apply on top of a clean HEAD, fix conflicts and re-stash, thus\ndoing a \"stash rebase\".)\n\n\n>   1. For --index mode, it actually invokes add--interactive twice. It\n>      would be nice to do both passes at the same time, but I don't think\n>      it is possible with the current add--interactive infrastructure.\n\nNote that the 'git stash -p' in next always stashes the index whole,\nso the \"easy\" way might simply be to also unstash the index whole (if\nrequested).\n\nThe changes will usually still be available in the worktree\napplication, because the 3-way merge is between base and HEAD on one\nside and base and worktree-stash on the other side.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"121236","messageId":"20090819101134.GB28920@sigill.intra.peff.net","threadId":"20208","inReplyTo":"200908191140.22235.trast@student.ethz.ch","subject":"Re: [PATCH v5 0/6] {checkout,reset,stash} --patch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2009-08-19T10:11:34Z","receivedAt":"2009-08-19T10:11:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Aug 19, 2009 at 11:40:20AM +0200, Thomas Rast wrote:\n\n> > I took a look at this today.\n> Heh, now *my* procrastination is paying off :-)\n\nCurses, I fell into your trap!\n\n> I think that is the correct way to go about it from the user's POV.\n> He would be confused if the patch applied to WT/index were different\n> (because of a later merge) from the hunks he chose in the -p loop.\n> \n> However, there's the issue of merge conflicts.  Some options I can\n> think of are\n> \n> 1) refuse to work in the face of merge problems\n\nI had assumed we would do (1), just to keep things simple. Otherwise\nstash becomes a multi-invocation command (with a --continue feature),\nwhich really has a lot of complexity and corner cases.\n\n> 2) stash requires a clean WT, so we can move the user's index out of\n>    the way and use temporary index + WT to let the user resolve the\n>    conflicts\n> \n> 3) require both clean WT and index so we can simply use the repo to\n>    resolve\n\nActually, we currently require that the index and WT match, so\nthese two are equivalent. But I think they add a lot of complexity\nbecause of the continuation.\n\n> (The first one isn't quite as restrictive as it sounds; the user can\n> always apply on top of a clean HEAD, fix conflicts and re-stash, thus\n> doing a \"stash rebase\".)\n\nWhich is really a nice way of dodging the continuation bullet, since the\nresults after each step are well-defined in terms of currently existing\nsteps. That is, the user could \"git stash apply\" and never invoke \"git\nstash apply -p\" if munging the conflicts led to the result they wanted.\n\nOTOH, it may have been simpler for them to edit the stash beforehand to\navoid the conflicts.\n\nHmm. Maybe we are really talking about two different commands:\n\n  1. edit the hunks that will be applied to the working tree from the\n     stash (apply -p)\n\n  2. edit the _stash itself_, taking or leaving hunks to create a new\n     stash\n\nWhat was in my patch was basically (2), but then always followed by\napplying. By splitting them, you can use whichever makes sense for your\nsituation.\n\n> >   1. For --index mode, it actually invokes add--interactive twice. It\n> >      would be nice to do both passes at the same time, but I don't think\n> >      it is possible with the current add--interactive infrastructure.\n> \n> Note that the 'git stash -p' in next always stashes the index whole,\n> so the \"easy\" way might simply be to also unstash the index whole (if\n> requested).\n\nThat might be worthwhile. It is less flexible, but I really wonder if\npeople actually keep stash randomly different changes in their index and\nworktree and want to pick through them individually. I guess we could\nalso add a --patch-index for people who really wanted it.\n\n-Peff\n"}]}