{"thread":{"id":"15787","subject":"What about renaming ga -i [r]evert to [r]eset ?","startedAt":"2008-10-05T15:26:54Z","lastAt":"2008-10-06T15:55:35Z","messageCount":10,"participants":["Marc Weber","Jeff King","Shawn O. Pearce"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"92334","messageId":"20081005152654.GA13618@gmx.de","threadId":"15787","inReplyTo":null,"subject":"What about renaming ga -i [r]evert to [r]eset ?","fromName":"Marc Weber","fromEmail":"marco-oweber@gmx.de","sentAt":"2008-10-05T15:26:54Z","receivedAt":"2008-10-05T15:26:54Z","isPatch":false,"sender":{"key":"marco-oweber@gmx.de","avatar":null},"body":"Hi,\n\nSome time ago I told a friend to use ga -i  [r](evert).. and I thought\nit would do the same as svn revert (set the working copy back to the\nstate recorded in repo (which git checkout does)..\n\nThe ga -i help display reveals that reverting does reset the index\n(cache to be comitted) instead.\nI think it's better to rename the command [r]evert to [r]eset ?\n\nI've also added the command [c]heckout last.\n\nYou can get the patch from git://mawercer.de/git_my_patches branch\nrevert_to_reset_renaming\n\nMaybe someone of you beeing more familiar with perl,\ngit-add--interactive and git can review it maybe telling me what should\nbe done so that it can be committed upstream?\n\nThanks\nMarc\n"},{"id":"92344","messageId":"20081005164124.GA31903@coredump.intra.peff.net","threadId":"15787","inReplyTo":"20081005152654.GA13618@gmx.de","subject":"Re: What about renaming ga -i [r]evert to [r]eset ?","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2008-10-05T16:41:25Z","receivedAt":"2008-10-05T16:41:25Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Oct 05, 2008 at 05:26:54PM +0200, Marc Weber wrote:\n\n> You can get the patch from git://mawercer.de/git_my_patches branch\n> revert_to_reset_renaming\n> \n> Maybe someone of you beeing more familiar with perl,\n> git-add--interactive and git can review it maybe telling me what should\n> be done so that it can be committed upstream?\n\nPlease take a look at Documentation/SubmittingPatches. In particular:\n\n  - this change should be split into two different patches (one for\n    s/revert/reset, and one for adding \"checkout\").\n\n  - patches should be sent to the list for review, as described in\n    SubmittingPatches\n\n  - commit messages should be formatted in the usual git way. I.e,\n\n      one-line description of the change\n\n      more elaborate description of the change, including\n      details of the change, but most importantly discussing\n      the rationale for the change. In order for your patch to\n      be accepted, you must explain _why_ you think it's a good\n      idea.\n\n-Peff\n"},{"id":"92366","messageId":"20081005214644.GB32727@gmx.de","threadId":"15787","inReplyTo":"20081005164124.GA31903@coredump.intra.peff.net","subject":"[PATCH 2] renaming git add -i [r]evert -> reset and adding gitt add -i [c]heckout","fromName":"Marc Weber","fromEmail":"marco-oweber@gmx.de","sentAt":"2008-10-05T21:46:44Z","receivedAt":"2008-10-05T21:46:44Z","isPatch":true,"sender":{"key":"marco-oweber@gmx.de","avatar":null},"body":"Hi Jeff, thanks!\nI'll try to do it better now (not sure who is the mantainer though, I've\nseen that you've commited some lines to this file)..\n\nBoth patches apply cleanly against c427559 (master).\nYou can push enhancements directly to \n\ngit://mawercer.de/git_my_patches \ntopic branches: \n        renaming_revert_to_reset\n        revert_to_reset_renaming\n\nSincerly\nMarc Weber\n\n============= attachement git show output ============================\n\ncommit 852b21bf88b1de784244a6e99de9a53a5c61dd8c\nAuthor: Marc Weber <marco-oweber@gmx.de>\nDate:   Sun Oct 5 23:09:04 2008 +0200\n\n    rename the git add -i [r]evert command to [r]eset to use the same term as in git reset\n    purpose: decrease possibility of confusion for users with svn background (svn revert = git checkout)\n    \n    Signed-off-by: Marc Weber <marco-oweber@gmx.de>\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex da768ee..5352d16 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -498,8 +498,8 @@ sub update_cmd {\n        print \"\\n\";\n }\n \n-sub revert_cmd {\n-       my @update = list_and_choose({ PROMPT => 'Revert',\n+sub reset_cmd {\n+       my @update = list_and_choose({ PROMPT => 'Reset',\n                                       HEADER => $status_head, },\n                                     list_modified());\n        if (@update) {\n@@ -527,7 +527,7 @@ sub revert_cmd {\n                        }\n                }\n                refresh();\n-               say_n_paths('reverted', @update);\n+               say_n_paths('reset', @update);\n        }\n        print \"\\n\";\n }\n@@ -1046,7 +1046,7 @@ sub help_cmd {\n        print colored $help_color, <<\\EOF ;\n status        - show paths with changes\n update        - add working tree state to the staged set of changes\n-revert        - revert staged set of changes back to the HEAD version\n+reset         - reset staged set of changes back to the HEAD version\n patch         - pick hunks and update selectively\n diff         - view diff between HEAD and index\n add untracked - add contents of untracked files to the staged set of changes\n@@ -1070,7 +1070,7 @@ sub process_args {\n sub main_loop {\n        my @cmd = ([ 'status', \\&status_cmd, ],\n                   [ 'update', \\&update_cmd, ],\n-                  [ 'revert', \\&revert_cmd, ],\n+                  [ 'reset', \\&reset_cmd, ],\n                   [ 'add untracked', \\&add_untracked_cmd, ],\n                   [ 'patch', \\&patch_update_cmd, ],\n                   [ 'diff', \\&diff_cmd, ],\n\n\n\n\n\n\n\n\ncommit b3d438764a7429e1dfacef8f499a0126076ed2bc\nAuthor: Marc Weber <marco-oweber@gmx.de>\nDate:   Sun Oct 5 15:15:38 2008 +0000\n\n    renamed revert in ga -i to reset, added [c]heckout\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex da768ee..32c300f 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -498,8 +498,8 @@ sub update_cmd {\n        print \"\\n\";\n }\n \n-sub revert_cmd {\n-       my @update = list_and_choose({ PROMPT => 'Revert',\n+sub reset_cmd {\n+       my @update = list_and_choose({ PROMPT => 'Reset',\n                                       HEADER => $status_head, },\n                                     list_modified());\n        if (@update) {\n@@ -527,7 +527,31 @@ sub revert_cmd {\n                        }\n                }\n                refresh();\n-               say_n_paths('reverted', @update);\n+               say_n_paths('reset', @update);\n+       }\n+       print \"\\n\";\n+}\n+\n+sub checkout_cmd {\n+       my @update = list_and_choose({ PROMPT => 'Checkout',\n+                                      HEADER => $status_head, },\n+                                    list_modified());\n+       if (@update) {\n+               if (is_initial_commit()) {\n+                        # should never be executed because there can't\nbe modified files\n+                        print \"error: no revision in repo yet\\n\";\n+               }\n+               else {\n+                       for (@update) {\n+                               if ($_->{INDEX_ADDDEL} &&\n+                                   $_->{INDEX_ADDDEL} eq 'create') {\n+                                       system(qw(git checkout --),\n+                                              $_->{VALUE});\n+                               }\n+                       }\n+               }\n+               refresh();\n+               say_n_paths('checked out', @update);\n        }\n        print \"\\n\";\n }\n@@ -1046,7 +1070,8 @@ sub help_cmd {\n        print colored $help_color, <<\\EOF ;\n status        - show paths with changes\n update        - add working tree state to the staged set of changes\n-revert        - revert staged set of changes back to the HEAD version\n+reset         - reset staged set of changes back to the HEAD version\n+checkout      - reset working copy file back to the HEAD version\n patch         - pick hunks and update selectively\n diff         - view diff between HEAD and index\n add untracked - add contents of untracked files to the staged set of\nchanges\n@@ -1070,7 +1095,8 @@ sub process_args {\n sub main_loop {\n        my @cmd = ([ 'status', \\&status_cmd, ],\n                   [ 'update', \\&update_cmd, ],\n-                  [ 'revert', \\&revert_cmd, ],\n+                  [ 'reset', \\&reset_cmd, ],\n+                  [ 'checkout', \\&checkout_cmd, ],\n                   [ 'add untracked', \\&add_untracked_cmd, ],\n                   [ 'patch', \\&patch_update_cmd, ],\n                   [ 'diff', \\&diff_cmd, ],\n"},{"id":"92368","messageId":"20081005221150.GA22007@coredump.intra.peff.net","threadId":"15787","inReplyTo":"20081005214644.GB32727@gmx.de","subject":"Re: [PATCH 2] renaming git add -i [r]evert -> reset and adding gitt add -i [c]heckout","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2008-10-05T22:11:50Z","receivedAt":"2008-10-05T22:11:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Oct 05, 2008 at 11:46:44PM +0200, Marc Weber wrote:\n\n> I'll try to do it better now (not sure who is the mantainer though,\n> I've seen that you've commited some lines to this file)..\n\nThe usual git maintainer is Junio Hamano <gitster@pobox.com>.\n\nBut he is out of touch for a few weeks, so the interim maintainer is\nShawn Pearce <spearce@spearce.org>.\n\n> ============= attachement git show output ============================\n\nThe usual way of mailing patches is a bit different:\n\n - send one patch per email; if multiple patches are meant to be applied\n   in series, number them [PATCH i/n]. In this case, I think you would\n   want your patches applied in series (since the second one will\n   textually depend on the first).\n\n - use \"git format-patch\" to generate the patches. This generates an\n   email format that can be applied on the other end by \"git am\".\n\n - use \"git send-email\" to send the patches generated by format-patch.\n   You can also send the patches directly from your mail reader, but you\n   will want to merge your regular mail headers with those generated by\n   format-patch (e.g., the Subject line generated by format-patch should\n   become the subject line of your message).\n\n> commit 852b21bf88b1de784244a6e99de9a53a5c61dd8c\n> Author: Marc Weber <marco-oweber@gmx.de>\n> Date:   Sun Oct 5 23:09:04 2008 +0200\n> \n>     rename the git add -i [r]evert command to [r]eset to use the same term as in git reset\n>     purpose: decrease possibility of confusion for users with svn background (svn revert = git checkout)\n\nPlease follow the usual \"subject\\n\\nbody\" convention for commit\nmessages. And if the patch touches just one subsystem, we usually say\n\"subsystem: what the patch does.\" So something like:\n\n  add--interactive: rename [r]evert command to [r]eset\n\nfollowed by a newline, then your explanation.\n\nI think you may want to note on this patch, too, that \"revert\" is also\nconfusing for git users. Maybe like:\n\n  The term \"revert\" is used very confusingly here. In git, to \"revert\"\n  is to take the changes made by a previous commit and un-apply them,\n  creating a new commit. The act of changing what is in the index to\n  match HEAD is called \"reset\".\n\n  Furthermore, incoming svn users will also find this confusing, since\n  to them, \"revert\" means to reset the working tree, which corresponds\n  to \"checkout\" in git.\n\n> commit b3d438764a7429e1dfacef8f499a0126076ed2bc\n> Author: Marc Weber <marco-oweber@gmx.de>\n> Date:   Sun Oct 5 15:15:38 2008 +0000\n> \n>     renamed revert in ga -i to reset, added [c]heckout\n\nThe first patch has been split out, and then this one contains the\nchanges for both. So I think what you want to do is to rebase this\nchange on top of the previous one, so that it should then have just the\n\"checkout\" changes, and then write an appropriate commit message for it.\n\n-Peff\n"},{"id":"92413","messageId":"b41e1729a8817f0d3cba2be1edc37513a72901dd.1223304141.git.marco-oweber@gmx.de","threadId":"15787","inReplyTo":"20081005221150.GA22007@coredump.intra.peff.net","subject":"[PATCH 1/2] add--interactive: rename [r]evert command to [r]eset","fromName":"Marc Weber","fromEmail":"marco-oweber@gmx.de","sentAt":"2008-10-06T14:46:55Z","receivedAt":"2008-10-06T14:46:55Z","isPatch":true,"sender":{"key":"marco-oweber@gmx.de","avatar":null},"body":"The term \"revert\" is used very confusingly here. In git, to \"revert\"\nis to take the changes made by a previous commit and un-apply them,\ncreating a new commit. The act of changing what is in the index to\nmatch HEAD is called \"reset\".\n\nFurthermore, incoming svn users will also find this confusing, since\nto them, \"revert\" means to reset the working tree, which corresponds\nto \"checkout\" in git.\n\nSigned-off-by: Marc Weber <marco-oweber@gmx.de>\ncommit message rewritten by: Jeff King <peff@peff.net>\n---\n git-add--interactive.perl |   10 +++++-----\n 1 files changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex da768ee..5352d16 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -498,8 +498,8 @@ sub update_cmd {\n        print \"\\n\";\n }\n \n-sub revert_cmd {\n-       my @update = list_and_choose({ PROMPT => 'Revert',\n+sub reset_cmd {\n+       my @update = list_and_choose({ PROMPT => 'Reset',\n                                       HEADER => $status_head, },\n                                     list_modified());\n        if (@update) {\n@@ -527,7 +527,7 @@ sub revert_cmd {\n                        }\n                }\n                refresh();\n-               say_n_paths('reverted', @update);\n+               say_n_paths('reset', @update);\n        }\n        print \"\\n\";\n }\n@@ -1046,7 +1046,7 @@ sub help_cmd {\n        print colored $help_color, <<\\EOF ;\n status        - show paths with changes\n update        - add working tree state to the staged set of changes\n-revert        - revert staged set of changes back to the HEAD version\n+reset         - reset staged set of changes back to the HEAD version\n patch         - pick hunks and update selectively\n diff         - view diff between HEAD and index\n add untracked - add contents of untracked files to the staged set of changes\n@@ -1070,7 +1070,7 @@ sub process_args {\n sub main_loop {\n        my @cmd = ([ 'status', \\&status_cmd, ],\n                   [ 'update', \\&update_cmd, ],\n-                  [ 'revert', \\&revert_cmd, ],\n+                  [ 'reset', \\&reset_cmd, ],\n                   [ 'add untracked', \\&add_untracked_cmd, ],\n                   [ 'patch', \\&patch_update_cmd, ],\n                   [ 'diff', \\&diff_cmd, ],\n-- \n1.6.0.2.309.g14f93\n"},{"id":"92412","messageId":"353e718308129444268e61f251dab769761f095f.1223304141.git.marco-oweber@gmx.de","threadId":"15787","inReplyTo":"b41e1729a8817f0d3cba2be1edc37513a72901dd.1223304141.git.marco-oweber@gmx.de","subject":"[PATCH 2/2] add--interactive: added [c]heckout","fromName":"Marc Weber","fromEmail":"marco-oweber@gmx.de","sentAt":"2008-10-06T14:49:05Z","receivedAt":"2008-10-06T14:49:05Z","isPatch":true,"sender":{"key":"marco-oweber@gmx.de","avatar":null},"body":"this is convinient for cygwin users not using rxvt.\n\nadded last (before quit and help) to not break menu layout and keep exisiting\nnumbering\n\nSigned-off-by: Marc Weber <marco-oweber@gmx.de>\n---\n git-add--interactive | 1129 ++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 1129 insertions(+), 0 deletions(-)\n create mode 100755 git-add--interactive\n\ndiff --git a/git-add--interactive b/git-add--interactive\nnew file mode 100755\nindex 0000000..2e7fa55\n--- /dev/null\n+++ b/git-add--interactive\n@@ -0,0 +1,1129 @@\n+#!/nix/store/y0s7qlxqr35cqscfg9xy7pvvjmx1hgmn-perl-5.10.0/bin/perl -w\n+use lib (split(/:/, $ENV{GITPERLLIB} || \"/usr/local/lib/site_perl/5.10.0\"));\n+\n+use strict;\n+use Git;\n+\n+my $repo = Git->repository();\n+\n+my $menu_use_color = $repo->get_colorbool('color.interactive');\n+my ($prompt_color, $header_color, $help_color) =\n+\t$menu_use_color ? (\n+\t\t$repo->get_color('color.interactive.prompt', 'bold blue'),\n+\t\t$repo->get_color('color.interactive.header', 'bold'),\n+\t\t$repo->get_color('color.interactive.help', 'red bold'),\n+\t) : ();\n+\n+my $diff_use_color = $repo->get_colorbool('color.diff');\n+my ($fraginfo_color) =\n+\t$diff_use_color ? (\n+\t\t$repo->get_color('color.diff.frag', 'cyan'),\n+\t) : ();\n+my ($diff_plain_color) =\n+\t$diff_use_color ? (\n+\t\t$repo->get_color('color.diff.plain', ''),\n+\t) : ();\n+my ($diff_old_color) =\n+\t$diff_use_color ? (\n+\t\t$repo->get_color('color.diff.old', 'red'),\n+\t) : ();\n+my ($diff_new_color) =\n+\t$diff_use_color ? (\n+\t\t$repo->get_color('color.diff.new', 'green'),\n+\t) : ();\n+\n+my $normal_color = $repo->get_color(\"\", \"reset\");\n+\n+sub colored {\n+\tmy $color = shift;\n+\tmy $string = join(\"\", @_);\n+\n+\tif (defined $color) {\n+\t\t# Put a color code at the beginning of each line, a reset at the end\n+\t\t# color after newlines that are not at the end of the string\n+\t\t$string =~ s/(\\n+)(.)/$1$color$2/g;\n+\t\t# reset before newlines\n+\t\t$string =~ s/(\\n+)/$normal_color$1/g;\n+\t\t# codes at beginning and end (if necessary):\n+\t\t$string =~ s/^/$color/;\n+\t\t$string =~ s/$/$normal_color/ unless $string =~ /\\n$/;\n+\t}\n+\treturn $string;\n+}\n+\n+# command line options\n+my $patch_mode;\n+\n+sub run_cmd_pipe {\n+\tif ($^O eq 'MSWin32' || $^O eq 'msys') {\n+\t\tmy @invalid = grep {m/[\":*]/} @_;\n+\t\tdie \"$^O does not support: @invalid\\n\" if @invalid;\n+\t\tmy @args = map { m/ /o ? \"\\\"$_\\\"\": $_ } @_;\n+\t\treturn qx{@args};\n+\t} else {\n+\t\tmy $fh = undef;\n+\t\topen($fh, '-|', @_) or die;\n+\t\treturn <$fh>;\n+\t}\n+}\n+\n+my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));\n+\n+if (!defined $GIT_DIR) {\n+\texit(1); # rev-parse would have already said \"not a git repo\"\n+}\n+chomp($GIT_DIR);\n+\n+sub refresh {\n+\tmy $fh;\n+\topen $fh, 'git update-index --refresh |'\n+\t    or die;\n+\twhile (<$fh>) {\n+\t\t;# ignore 'needs update'\n+\t}\n+\tclose $fh;\n+}\n+\n+sub list_untracked {\n+\tmap {\n+\t\tchomp $_;\n+\t\t$_;\n+\t}\n+\trun_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);\n+}\n+\n+my $status_fmt = '%12s %12s %s';\n+my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');\n+\n+{\n+\tmy $initial;\n+\tsub is_initial_commit {\n+\t\t$initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0\n+\t\t\tunless defined $initial;\n+\t\treturn $initial;\n+\t}\n+}\n+\n+sub get_empty_tree {\n+\treturn '4b825dc642cb6eb9a060e54bf8d69288fbee4904';\n+}\n+\n+# Returns list of hashes, contents of each of which are:\n+# VALUE:\tpathname\n+# BINARY:\tis a binary path\n+# INDEX:\tis index different from HEAD?\n+# FILE:\t\tis file different from index?\n+# INDEX_ADDDEL:\tis it add/delete between HEAD and index?\n+# FILE_ADDDEL:\tis it add/delete between index and file?\n+\n+sub list_modified {\n+\tmy ($only) = @_;\n+\tmy (%data, @return);\n+\tmy ($add, $del, $adddel, $file);\n+\tmy @tracked = ();\n+\n+\tif (@ARGV) {\n+\t\t@tracked = map {\n+\t\t\tchomp $_; $_;\n+\t\t} run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV);\n+\t\treturn if (!@tracked);\n+\t}\n+\n+\tmy $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';\n+\tfor (run_cmd_pipe(qw(git diff-index --cached\n+\t\t\t     --numstat --summary), $reference,\n+\t\t\t     '--', @tracked)) {\n+\t\tif (($add, $del, $file) =\n+\t\t    /^([-\\d]+)\t([-\\d]+)\t(.*)/) {\n+\t\t\tmy ($change, $bin);\n+\t\t\tif ($add eq '-' && $del eq '-') {\n+\t\t\t\t$change = 'binary';\n+\t\t\t\t$bin = 1;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\t$change = \"+$add/-$del\";\n+\t\t\t}\n+\t\t\t$data{$file} = {\n+\t\t\t\tINDEX => $change,\n+\t\t\t\tBINARY => $bin,\n+\t\t\t\tFILE => 'nothing',\n+\t\t\t}\n+\t\t}\n+\t\telsif (($adddel, $file) =\n+\t\t       /^ (create|delete) mode [0-7]+ (.*)$/) {\n+\t\t\t$data{$file}{INDEX_ADDDEL} = $adddel;\n+\t\t}\n+\t}\n+\n+\tfor (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) {\n+\t\tif (($add, $del, $file) =\n+\t\t    /^([-\\d]+)\t([-\\d]+)\t(.*)/) {\n+\t\t\tif (!exists $data{$file}) {\n+\t\t\t\t$data{$file} = +{\n+\t\t\t\t\tINDEX => 'unchanged',\n+\t\t\t\t\tBINARY => 0,\n+\t\t\t\t};\n+\t\t\t}\n+\t\t\tmy ($change, $bin);\n+\t\t\tif ($add eq '-' && $del eq '-') {\n+\t\t\t\t$change = 'binary';\n+\t\t\t\t$bin = 1;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\t$change = \"+$add/-$del\";\n+\t\t\t}\n+\t\t\t$data{$file}{FILE} = $change;\n+\t\t\tif ($bin) {\n+\t\t\t\t$data{$file}{BINARY} = 1;\n+\t\t\t}\n+\t\t}\n+\t\telsif (($adddel, $file) =\n+\t\t       /^ (create|delete) mode [0-7]+ (.*)$/) {\n+\t\t\t$data{$file}{FILE_ADDDEL} = $adddel;\n+\t\t}\n+\t}\n+\n+\tfor (sort keys %data) {\n+\t\tmy $it = $data{$_};\n+\n+\t\tif ($only) {\n+\t\t\tif ($only eq 'index-only') {\n+\t\t\t\tnext if ($it->{INDEX} eq 'unchanged');\n+\t\t\t}\n+\t\t\tif ($only eq 'file-only') {\n+\t\t\t\tnext if ($it->{FILE} eq 'nothing');\n+\t\t\t}\n+\t\t}\n+\t\tpush @return, +{\n+\t\t\tVALUE => $_,\n+\t\t\t%$it,\n+\t\t};\n+\t}\n+\treturn @return;\n+}\n+\n+sub find_unique {\n+\tmy ($string, @stuff) = @_;\n+\tmy $found = undef;\n+\tfor (my $i = 0; $i < @stuff; $i++) {\n+\t\tmy $it = $stuff[$i];\n+\t\tmy $hit = undef;\n+\t\tif (ref $it) {\n+\t\t\tif ((ref $it) eq 'ARRAY') {\n+\t\t\t\t$it = $it->[0];\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\t$it = $it->{VALUE};\n+\t\t\t}\n+\t\t}\n+\t\teval {\n+\t\t\tif ($it =~ /^$string/) {\n+\t\t\t\t$hit = 1;\n+\t\t\t};\n+\t\t};\n+\t\tif (defined $hit && defined $found) {\n+\t\t\treturn undef;\n+\t\t}\n+\t\tif ($hit) {\n+\t\t\t$found = $i + 1;\n+\t\t}\n+\t}\n+\treturn $found;\n+}\n+\n+# inserts string into trie and updates count for each character\n+sub update_trie {\n+\tmy ($trie, $string) = @_;\n+\tforeach (split //, $string) {\n+\t\t$trie = $trie->{$_} ||= {COUNT => 0};\n+\t\t$trie->{COUNT}++;\n+\t}\n+}\n+\n+# returns an array of tuples (prefix, remainder)\n+sub find_unique_prefixes {\n+\tmy @stuff = @_;\n+\tmy @return = ();\n+\n+\t# any single prefix exceeding the soft limit is omitted\n+\t# if any prefix exceeds the hard limit all are omitted\n+\t# 0 indicates no limit\n+\tmy $soft_limit = 0;\n+\tmy $hard_limit = 3;\n+\n+\t# build a trie modelling all possible options\n+\tmy %trie;\n+\tforeach my $print (@stuff) {\n+\t\tif ((ref $print) eq 'ARRAY') {\n+\t\t\t$print = $print->[0];\n+\t\t}\n+\t\telsif ((ref $print) eq 'HASH') {\n+\t\t\t$print = $print->{VALUE};\n+\t\t}\n+\t\tupdate_trie(\\%trie, $print);\n+\t\tpush @return, $print;\n+\t}\n+\n+\t# use the trie to find the unique prefixes\n+\tfor (my $i = 0; $i < @return; $i++) {\n+\t\tmy $ret = $return[$i];\n+\t\tmy @letters = split //, $ret;\n+\t\tmy %search = %trie;\n+\t\tmy ($prefix, $remainder);\n+\t\tmy $j;\n+\t\tfor ($j = 0; $j < @letters; $j++) {\n+\t\t\tmy $letter = $letters[$j];\n+\t\t\tif ($search{$letter}{COUNT} == 1) {\n+\t\t\t\t$prefix = substr $ret, 0, $j + 1;\n+\t\t\t\t$remainder = substr $ret, $j + 1;\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\tmy $prefix = substr $ret, 0, $j;\n+\t\t\t\treturn ()\n+\t\t\t\t    if ($hard_limit && $j + 1 > $hard_limit);\n+\t\t\t}\n+\t\t\t%search = %{$search{$letter}};\n+\t\t}\n+\t\tif ($soft_limit && $j + 1 > $soft_limit) {\n+\t\t\t$prefix = undef;\n+\t\t\t$remainder = $ret;\n+\t\t}\n+\t\t$return[$i] = [$prefix, $remainder];\n+\t}\n+\treturn @return;\n+}\n+\n+# filters out prefixes which have special meaning to list_and_choose()\n+sub is_valid_prefix {\n+\tmy $prefix = shift;\n+\treturn (defined $prefix) &&\n+\t    !($prefix =~ /[\\s,]/) && # separators\n+\t    !($prefix =~ /^-/) &&    # deselection\n+\t    !($prefix =~ /^\\d+/) &&  # selection\n+\t    ($prefix ne '*') &&      # \"all\" wildcard\n+\t    ($prefix ne '?');        # prompt help\n+}\n+\n+# given a prefix/remainder tuple return a string with the prefix highlighted\n+# for now use square brackets; later might use ANSI colors (underline, bold)\n+sub highlight_prefix {\n+\tmy $prefix = shift;\n+\tmy $remainder = shift;\n+\n+\tif (!defined $prefix) {\n+\t\treturn $remainder;\n+\t}\n+\n+\tif (!is_valid_prefix($prefix)) {\n+\t\treturn \"$prefix$remainder\";\n+\t}\n+\n+\tif (!$menu_use_color) {\n+\t\treturn \"[$prefix]$remainder\";\n+\t}\n+\n+\treturn \"$prompt_color$prefix$normal_color$remainder\";\n+}\n+\n+sub list_and_choose {\n+\tmy ($opts, @stuff) = @_;\n+\tmy (@chosen, @return);\n+\tmy $i;\n+\tmy @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};\n+\n+      TOPLOOP:\n+\twhile (1) {\n+\t\tmy $last_lf = 0;\n+\n+\t\tif ($opts->{HEADER}) {\n+\t\t\tif (!$opts->{LIST_FLAT}) {\n+\t\t\t\tprint \"     \";\n+\t\t\t}\n+\t\t\tprint colored $header_color, \"$opts->{HEADER}\\n\";\n+\t\t}\n+\t\tfor ($i = 0; $i < @stuff; $i++) {\n+\t\t\tmy $chosen = $chosen[$i] ? '*' : ' ';\n+\t\t\tmy $print = $stuff[$i];\n+\t\t\tmy $ref = ref $print;\n+\t\t\tmy $highlighted = highlight_prefix(@{$prefixes[$i]})\n+\t\t\t    if @prefixes;\n+\t\t\tif ($ref eq 'ARRAY') {\n+\t\t\t\t$print = $highlighted || $print->[0];\n+\t\t\t}\n+\t\t\telsif ($ref eq 'HASH') {\n+\t\t\t\tmy $value = $highlighted || $print->{VALUE};\n+\t\t\t\t$print = sprintf($status_fmt,\n+\t\t\t\t    $print->{INDEX},\n+\t\t\t\t    $print->{FILE},\n+\t\t\t\t    $value);\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\t$print = $highlighted || $print;\n+\t\t\t}\n+\t\t\tprintf(\"%s%2d: %s\", $chosen, $i+1, $print);\n+\t\t\tif (($opts->{LIST_FLAT}) &&\n+\t\t\t    (($i + 1) % ($opts->{LIST_FLAT}))) {\n+\t\t\t\tprint \"\\t\";\n+\t\t\t\t$last_lf = 0;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\tprint \"\\n\";\n+\t\t\t\t$last_lf = 1;\n+\t\t\t}\n+\t\t}\n+\t\tif (!$last_lf) {\n+\t\t\tprint \"\\n\";\n+\t\t}\n+\n+\t\treturn if ($opts->{LIST_ONLY});\n+\n+\t\tprint colored $prompt_color, $opts->{PROMPT};\n+\t\tif ($opts->{SINGLETON}) {\n+\t\t\tprint \"> \";\n+\t\t}\n+\t\telse {\n+\t\t\tprint \">> \";\n+\t\t}\n+\t\tmy $line = <STDIN>;\n+\t\tif (!$line) {\n+\t\t\tprint \"\\n\";\n+\t\t\t$opts->{ON_EOF}->() if $opts->{ON_EOF};\n+\t\t\tlast;\n+\t\t}\n+\t\tchomp $line;\n+\t\tlast if $line eq '';\n+\t\tif ($line eq '?') {\n+\t\t\t$opts->{SINGLETON} ?\n+\t\t\t    singleton_prompt_help_cmd() :\n+\t\t\t    prompt_help_cmd();\n+\t\t\tnext TOPLOOP;\n+\t\t}\n+\t\tfor my $choice (split(/[\\s,]+/, $line)) {\n+\t\t\tmy $choose = 1;\n+\t\t\tmy ($bottom, $top);\n+\n+\t\t\t# Input that begins with '-'; unchoose\n+\t\t\tif ($choice =~ s/^-//) {\n+\t\t\t\t$choose = 0;\n+\t\t\t}\n+\t\t\t# A range can be specified like 5-7 or 5-.\n+\t\t\tif ($choice =~ /^(\\d+)-(\\d*)$/) {\n+\t\t\t\t($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);\n+\t\t\t}\n+\t\t\telsif ($choice =~ /^\\d+$/) {\n+\t\t\t\t$bottom = $top = $choice;\n+\t\t\t}\n+\t\t\telsif ($choice eq '*') {\n+\t\t\t\t$bottom = 1;\n+\t\t\t\t$top = 1 + @stuff;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\t$bottom = $top = find_unique($choice, @stuff);\n+\t\t\t\tif (!defined $bottom) {\n+\t\t\t\t\tprint \"Huh ($choice)?\\n\";\n+\t\t\t\t\tnext TOPLOOP;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif ($opts->{SINGLETON} && $bottom != $top) {\n+\t\t\t\tprint \"Huh ($choice)?\\n\";\n+\t\t\t\tnext TOPLOOP;\n+\t\t\t}\n+\t\t\tfor ($i = $bottom-1; $i <= $top-1; $i++) {\n+\t\t\t\tnext if (@stuff <= $i || $i < 0);\n+\t\t\t\t$chosen[$i] = $choose;\n+\t\t\t}\n+\t\t}\n+\t\tlast if ($opts->{IMMEDIATE} || $line eq '*');\n+\t}\n+\tfor ($i = 0; $i < @stuff; $i++) {\n+\t\tif ($chosen[$i]) {\n+\t\t\tpush @return, $stuff[$i];\n+\t\t}\n+\t}\n+\treturn @return;\n+}\n+\n+sub singleton_prompt_help_cmd {\n+\tprint colored $help_color, <<\\EOF ;\n+Prompt help:\n+1          - select a numbered item\n+foo        - select item based on unique prefix\n+           - (empty) select nothing\n+EOF\n+}\n+\n+sub prompt_help_cmd {\n+\tprint colored $help_color, <<\\EOF ;\n+Prompt help:\n+1          - select a single item\n+3-5        - select a range of items\n+2-3,6-9    - select multiple ranges\n+foo        - select item based on unique prefix\n+-...       - unselect specified items\n+*          - choose all items\n+           - (empty) finish selecting\n+EOF\n+}\n+\n+sub status_cmd {\n+\tlist_and_choose({ LIST_ONLY => 1, HEADER => $status_head },\n+\t\t\tlist_modified());\n+\tprint \"\\n\";\n+}\n+\n+sub say_n_paths {\n+\tmy $did = shift @_;\n+\tmy $cnt = scalar @_;\n+\tprint \"$did \";\n+\tif (1 < $cnt) {\n+\t\tprint \"$cnt paths\\n\";\n+\t}\n+\telse {\n+\t\tprint \"one path\\n\";\n+\t}\n+}\n+\n+sub update_cmd {\n+\tmy @mods = list_modified('file-only');\n+\treturn if (!@mods);\n+\n+\tmy @update = list_and_choose({ PROMPT => 'Update',\n+\t\t\t\t       HEADER => $status_head, },\n+\t\t\t\t     @mods);\n+\tif (@update) {\n+\t\tsystem(qw(git update-index --add --remove --),\n+\t\t       map { $_->{VALUE} } @update);\n+\t\tsay_n_paths('updated', @update);\n+\t}\n+\tprint \"\\n\";\n+}\n+\n+sub reset_cmd {\n+\tmy @update = list_and_choose({ PROMPT => 'Reset',\n+\t\t\t\t       HEADER => $status_head, },\n+\t\t\t\t     list_modified());\n+\tif (@update) {\n+\t\tif (is_initial_commit()) {\n+\t\t\tsystem(qw(git rm --cached),\n+\t\t\t\tmap { $_->{VALUE} } @update);\n+\t\t}\n+\t\telse {\n+\t\t\tmy @lines = run_cmd_pipe(qw(git ls-tree HEAD --),\n+\t\t\t\t\t\t map { $_->{VALUE} } @update);\n+\t\t\tmy $fh;\n+\t\t\topen $fh, '| git update-index --index-info'\n+\t\t\t    or die;\n+\t\t\tfor (@lines) {\n+\t\t\t\tprint $fh $_;\n+\t\t\t}\n+\t\t\tclose($fh);\n+\t\t\tfor (@update) {\n+\t\t\t\tif ($_->{INDEX_ADDDEL} &&\n+\t\t\t\t    $_->{INDEX_ADDDEL} eq 'create') {\n+\t\t\t\t\tsystem(qw(git update-index --force-remove --),\n+\t\t\t\t\t       $_->{VALUE});\n+\t\t\t\t\tprint \"note: $_->{VALUE} is untracked now.\\n\";\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\trefresh();\n+\t\tsay_n_paths('reset', @update);\n+\t}\n+\tprint \"\\n\";\n+}\n+\n+sub checkout_cmd {\n+\tmy @update = list_and_choose({ PROMPT => 'Checkout',\n+\t\t\t\t       HEADER => $status_head, },\n+\t\t\t\t     list_modified());\n+\tif (@update) {\n+\t\tif (is_initial_commit()) {\n+                        # should never be executed because there can't be modified files\n+                        print \"error: no revision in repo yet\\n\";\n+\t\t}\n+\t\telse {\n+\t\t\tfor (@update) {\n+                                system(qw(git checkout --),\n+                                       $_->{VALUE});\n+\t\t\t}\n+\t\t}\n+\t\trefresh();\n+\t\tsay_n_paths('checked out', @update);\n+\t}\n+\tprint \"\\n\";\n+}\n+\n+sub add_untracked_cmd {\n+\tmy @add = list_and_choose({ PROMPT => 'Add untracked' },\n+\t\t\t\t  list_untracked());\n+\tif (@add) {\n+\t\tsystem(qw(git update-index --add --), @add);\n+\t\tsay_n_paths('added', @add);\n+\t}\n+\tprint \"\\n\";\n+}\n+\n+sub parse_diff {\n+\tmy ($path) = @_;\n+\tmy @diff = run_cmd_pipe(qw(git diff-files -p --), $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}\n+\tmy (@hunk) = { TEXT => [], DISPLAY => [] };\n+\n+\tfor (my $i = 0; $i < @diff; $i++) {\n+\t\tif ($diff[$i] =~ /^@@ /) {\n+\t\t\tpush @hunk, { TEXT => [], DISPLAY => [] };\n+\t\t}\n+\t\tpush @{$hunk[-1]{TEXT}}, $diff[$i];\n+\t\tpush @{$hunk[-1]{DISPLAY}},\n+\t\t\t($diff_use_color ? $colored[$i] : $diff[$i]);\n+\t}\n+\treturn @hunk;\n+}\n+\n+sub parse_diff_header {\n+\tmy $src = shift;\n+\n+\tmy $head = { TEXT => [], DISPLAY => [] };\n+\tmy $mode = { TEXT => [], DISPLAY => [] };\n+\n+\tfor (my $i = 0; $i < @{$src->{TEXT}}; $i++) {\n+\t\tmy $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\\d+)$/ ?\n+\t\t\t$mode : $head;\n+\t\tpush @{$dest->{TEXT}}, $src->{TEXT}->[$i];\n+\t\tpush @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];\n+\t}\n+\treturn ($head, $mode);\n+}\n+\n+sub hunk_splittable {\n+\tmy ($text) = @_;\n+\n+\tmy @s = split_hunk($text);\n+\treturn (1 < @s);\n+}\n+\n+sub parse_hunk_header {\n+\tmy ($line) = @_;\n+\tmy ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =\n+\t    $line =~ /^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@/;\n+\t$o_cnt = 1 unless defined $o_cnt;\n+\t$n_cnt = 1 unless defined $n_cnt;\n+\treturn ($o_ofs, $o_cnt, $n_ofs, $n_cnt);\n+}\n+\n+sub split_hunk {\n+\tmy ($text, $display) = @_;\n+\tmy @split = ();\n+\tif (!defined $display) {\n+\t\t$display = $text;\n+\t}\n+\t# If there are context lines in the middle of a hunk,\n+\t# it can be split, but we would need to take care of\n+\t# overlaps later.\n+\n+\tmy ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);\n+\tmy $hunk_start = 1;\n+\n+      OUTER:\n+\twhile (1) {\n+\t\tmy $next_hunk_start = undef;\n+\t\tmy $i = $hunk_start - 1;\n+\t\tmy $this = +{\n+\t\t\tTEXT => [],\n+\t\t\tDISPLAY => [],\n+\t\t\tOLD => $o_ofs,\n+\t\t\tNEW => $n_ofs,\n+\t\t\tOCNT => 0,\n+\t\t\tNCNT => 0,\n+\t\t\tADDDEL => 0,\n+\t\t\tPOSTCTX => 0,\n+\t\t\tUSE => undef,\n+\t\t};\n+\n+\t\twhile (++$i < @$text) {\n+\t\t\tmy $line = $text->[$i];\n+\t\t\tmy $display = $display->[$i];\n+\t\t\tif ($line =~ /^ /) {\n+\t\t\t\tif ($this->{ADDDEL} &&\n+\t\t\t\t    !defined $next_hunk_start) {\n+\t\t\t\t\t# We have seen leading context and\n+\t\t\t\t\t# adds/dels and then here is another\n+\t\t\t\t\t# context, which is trailing for this\n+\t\t\t\t\t# split hunk and leading for the next\n+\t\t\t\t\t# one.\n+\t\t\t\t\t$next_hunk_start = $i;\n+\t\t\t\t}\n+\t\t\t\tpush @{$this->{TEXT}}, $line;\n+\t\t\t\tpush @{$this->{DISPLAY}}, $display;\n+\t\t\t\t$this->{OCNT}++;\n+\t\t\t\t$this->{NCNT}++;\n+\t\t\t\tif (defined $next_hunk_start) {\n+\t\t\t\t\t$this->{POSTCTX}++;\n+\t\t\t\t}\n+\t\t\t\tnext;\n+\t\t\t}\n+\n+\t\t\t# add/del\n+\t\t\tif (defined $next_hunk_start) {\n+\t\t\t\t# We are done with the current hunk and\n+\t\t\t\t# this is the first real change for the\n+\t\t\t\t# next split one.\n+\t\t\t\t$hunk_start = $next_hunk_start;\n+\t\t\t\t$o_ofs = $this->{OLD} + $this->{OCNT};\n+\t\t\t\t$n_ofs = $this->{NEW} + $this->{NCNT};\n+\t\t\t\t$o_ofs -= $this->{POSTCTX};\n+\t\t\t\t$n_ofs -= $this->{POSTCTX};\n+\t\t\t\tpush @split, $this;\n+\t\t\t\tredo OUTER;\n+\t\t\t}\n+\t\t\tpush @{$this->{TEXT}}, $line;\n+\t\t\tpush @{$this->{DISPLAY}}, $display;\n+\t\t\t$this->{ADDDEL}++;\n+\t\t\tif ($line =~ /^-/) {\n+\t\t\t\t$this->{OCNT}++;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\t$this->{NCNT}++;\n+\t\t\t}\n+\t\t}\n+\n+\t\tpush @split, $this;\n+\t\tlast;\n+\t}\n+\n+\tfor my $hunk (@split) {\n+\t\t$o_ofs = $hunk->{OLD};\n+\t\t$n_ofs = $hunk->{NEW};\n+\t\tmy $o_cnt = $hunk->{OCNT};\n+\t\tmy $n_cnt = $hunk->{NCNT};\n+\n+\t\tmy $head = (\"@@ -$o_ofs\" .\n+\t\t\t    (($o_cnt != 1) ? \",$o_cnt\" : '') .\n+\t\t\t    \" +$n_ofs\" .\n+\t\t\t    (($n_cnt != 1) ? \",$n_cnt\" : '') .\n+\t\t\t    \" @@\\n\");\n+\t\tmy $display_head = $head;\n+\t\tunshift @{$hunk->{TEXT}}, $head;\n+\t\tif ($diff_use_color) {\n+\t\t\t$display_head = colored($fraginfo_color, $head);\n+\t\t}\n+\t\tunshift @{$hunk->{DISPLAY}}, $display_head;\n+\t}\n+\treturn @split;\n+}\n+\n+\n+sub color_diff {\n+\treturn map {\n+\t\tcolored((/^@/  ? $fraginfo_color :\n+\t\t\t /^\\+/ ? $diff_new_color :\n+\t\t\t /^-/  ? $diff_old_color :\n+\t\t\t $diff_plain_color),\n+\t\t\t$_);\n+\t} @_;\n+}\n+\n+sub edit_hunk_manually {\n+\tmy ($oldtext) = @_;\n+\n+\tmy $hunkfile = $repo->repo_path . \"/addp-hunk-edit.diff\";\n+\tmy $fh;\n+\topen $fh, '>', $hunkfile\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+\tprint $fh <<EOF;\n+# ---\n+# To remove '-' lines, make them ' ' lines (context).\n+# To remove '+' lines, delete them.\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+# 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+\tclose $fh;\n+\n+\tmy $editor = $ENV{GIT_EDITOR} || $repo->config(\"core.editor\")\n+\t\t|| $ENV{VISUAL} || $ENV{EDITOR} || \"vi\";\n+\tsystem('sh', '-c', $editor.' \"$@\"', $editor, $hunkfile);\n+\n+\topen $fh, '<', $hunkfile\n+\t\tor die \"failed to open hunk edit file for reading: \" . $!;\n+\tmy @newtext = grep { !/^#/ } <$fh>;\n+\tclose $fh;\n+\tunlink $hunkfile;\n+\n+\t# Abort if nothing remains\n+\tif (!grep { /\\S/ } @newtext) {\n+\t\treturn undef;\n+\t}\n+\n+\t# Reinsert the first hunk header if the user accidentally deleted it\n+\tif ($newtext[0] !~ /^@/) {\n+\t\tunshift @newtext, $oldtext->[0];\n+\t}\n+\treturn \\@newtext;\n+}\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+}\n+\n+sub prompt_yesno {\n+\tmy ($prompt) = @_;\n+\twhile (1) {\n+\t\tprint colored $prompt_color, $prompt;\n+\t\tmy $line = <STDIN>;\n+\t\treturn 0 if $line =~ /^n/i;\n+\t\treturn 1 if $line =~ /^y/i;\n+\t}\n+}\n+\n+sub edit_hunk_loop {\n+\tmy ($head, $hunk, $ix) = @_;\n+\tmy $text = $hunk->[$ix]->{TEXT};\n+\n+\twhile (1) {\n+\t\t$text = edit_hunk_manually($text);\n+\t\tif (!defined $text) {\n+\t\t\treturn undef;\n+\t\t}\n+\t\tmy $newhunk = { TEXT => $text, USE => 1 };\n+\t\tif (diff_applies($head,\n+\t\t\t\t @{$hunk}[0..$ix-1],\n+\t\t\t\t $newhunk,\n+\t\t\t\t @{$hunk}[$ix+1..$#{$hunk}])) {\n+\t\t\t$newhunk->{DISPLAY} = [color_diff(@{$text})];\n+\t\t\treturn $newhunk;\n+\t\t}\n+\t\telse {\n+\t\t\tprompt_yesno(\n+\t\t\t\t'Your edited hunk does not apply. Edit again '\n+\t\t\t\t. '(saying \"no\" discards!) [y/n]? '\n+\t\t\t\t) or return undef;\n+\t\t}\n+\t}\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+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+j - leave this hunk undecided, see next undecided hunk\n+J - leave this hunk undecided, see next hunk\n+k - leave this hunk undecided, see previous undecided hunk\n+K - leave this hunk undecided, see previous hunk\n+s - split the current hunk into smaller hunks\n+e - manually edit the current hunk\n+? - print help\n+EOF\n+}\n+\n+sub patch_update_cmd {\n+\tmy @mods = grep { !($_->{BINARY}) } list_modified('file-only');\n+\tmy @them;\n+\n+\tif (!@mods) {\n+\t\tprint STDERR \"No changes.\\n\";\n+\t\treturn 0;\n+\t}\n+\tif ($patch_mode) {\n+\t\t@them = @mods;\n+\t}\n+\telse {\n+\t\t@them = list_and_choose({ PROMPT => 'Patch update',\n+\t\t\t\t\t  HEADER => $status_head, },\n+\t\t\t\t\t@mods);\n+\t}\n+\tfor (@them) {\n+\t\tpatch_update_file($_->{VALUE});\n+\t}\n+}\n+\n+sub patch_update_file {\n+\tmy ($ix, $num);\n+\tmy $path = shift;\n+\tmy ($head, @hunk) = parse_diff($path);\n+\t($head, my $mode) = parse_diff_header($head);\n+\tfor (@{$head->{DISPLAY}}) {\n+\t\tprint;\n+\t}\n+\n+\tif (@{$mode->{TEXT}}) {\n+\t\twhile (1) {\n+\t\t\tprint @{$mode->{DISPLAY}};\n+\t\t\tprint colored $prompt_color,\n+\t\t\t\t\"Stage mode change [y/n/a/d/?]? \";\n+\t\t\tmy $line = <STDIN>;\n+\t\t\tif ($line =~ /^y/i) {\n+\t\t\t\t$mode->{USE} = 1;\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t\telsif ($line =~ /^n/i) {\n+\t\t\t\t$mode->{USE} = 0;\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t\telsif ($line =~ /^a/i) {\n+\t\t\t\t$_->{USE} = 1 foreach ($mode, @hunk);\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t\telsif ($line =~ /^d/i) {\n+\t\t\t\t$_->{USE} = 0 foreach ($mode, @hunk);\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\thelp_patch_cmd('');\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t$num = scalar @hunk;\n+\t$ix = 0;\n+\n+\twhile (1) {\n+\t\tmy ($prev, $next, $other, $undecided, $i);\n+\t\t$other = '';\n+\n+\t\tif ($num <= $ix) {\n+\t\t\t$ix = 0;\n+\t\t}\n+\t\tfor ($i = 0; $i < $ix; $i++) {\n+\t\t\tif (!defined $hunk[$i]{USE}) {\n+\t\t\t\t$prev = 1;\n+\t\t\t\t$other .= '/k';\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t}\n+\t\tif ($ix) {\n+\t\t\t$other .= '/K';\n+\t\t}\n+\t\tfor ($i = $ix + 1; $i < $num; $i++) {\n+\t\t\tif (!defined $hunk[$i]{USE}) {\n+\t\t\t\t$next = 1;\n+\t\t\t\t$other .= '/j';\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t}\n+\t\tif ($ix < $num - 1) {\n+\t\t\t$other .= '/J';\n+\t\t}\n+\t\tfor ($i = 0; $i < $num; $i++) {\n+\t\t\tif (!defined $hunk[$i]{USE}) {\n+\t\t\t\t$undecided = 1;\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t}\n+\t\tlast if (!$undecided);\n+\n+\t\tif (hunk_splittable($hunk[$ix]{TEXT})) {\n+\t\t\t$other .= '/s';\n+\t\t}\n+\t\t$other .= '/e';\n+\t\tfor (@{$hunk[$ix]{DISPLAY}}) {\n+\t\t\tprint;\n+\t\t}\n+\t\tprint colored $prompt_color, \"Stage this hunk [y/n/a/d$other/?]? \";\n+\t\tmy $line = <STDIN>;\n+\t\tif ($line) {\n+\t\t\tif ($line =~ /^y/i) {\n+\t\t\t\t$hunk[$ix]{USE} = 1;\n+\t\t\t}\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 =~ /^a/i) {\n+\t\t\t\twhile ($ix < $num) {\n+\t\t\t\t\tif (!defined $hunk[$ix]{USE}) {\n+\t\t\t\t\t\t$hunk[$ix]{USE} = 1;\n+\t\t\t\t\t}\n+\t\t\t\t\t$ix++;\n+\t\t\t\t}\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t\telsif ($line =~ /^d/i) {\n+\t\t\t\twhile ($ix < $num) {\n+\t\t\t\t\tif (!defined $hunk[$ix]{USE}) {\n+\t\t\t\t\t\t$hunk[$ix]{USE} = 0;\n+\t\t\t\t\t}\n+\t\t\t\t\t$ix++;\n+\t\t\t\t}\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t\telsif ($other =~ /K/ && $line =~ /^K/) {\n+\t\t\t\t$ix--;\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t\telsif ($other =~ /J/ && $line =~ /^J/) {\n+\t\t\t\t$ix++;\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t\telsif ($other =~ /k/ && $line =~ /^k/) {\n+\t\t\t\twhile (1) {\n+\t\t\t\t\t$ix--;\n+\t\t\t\t\tlast if (!$ix ||\n+\t\t\t\t\t\t !defined $hunk[$ix]{USE});\n+\t\t\t\t}\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t\telsif ($other =~ /j/ && $line =~ /^j/) {\n+\t\t\t\twhile (1) {\n+\t\t\t\t\t$ix++;\n+\t\t\t\t\tlast if ($ix >= $num ||\n+\t\t\t\t\t\t !defined $hunk[$ix]{USE});\n+\t\t\t\t}\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t\telsif ($other =~ /s/ && $line =~ /^s/) {\n+\t\t\t\tmy @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});\n+\t\t\t\tif (1 < @split) {\n+\t\t\t\t\tprint colored $header_color, \"Split into \",\n+\t\t\t\t\tscalar(@split), \" hunks.\\n\";\n+\t\t\t\t}\n+\t\t\t\tsplice (@hunk, $ix, 1, @split);\n+\t\t\t\t$num = scalar @hunk;\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t\telsif ($line =~ /^e/) {\n+\t\t\t\tmy $newhunk = edit_hunk_loop($head, \\@hunk, $ix);\n+\t\t\t\tif (defined $newhunk) {\n+\t\t\t\t\tsplice @hunk, $ix, 1, $newhunk;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\thelp_patch_cmd($other);\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t\t# soft increment\n+\t\t\twhile (1) {\n+\t\t\t\t$ix++;\n+\t\t\t\tlast if ($ix >= $num ||\n+\t\t\t\t\t !defined $hunk[$ix]{USE});\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tmy $n_lofs = 0;\n+\tmy @result = ();\n+\tif ($mode->{USE}) {\n+\t\tpush @result, @{$mode->{TEXT}};\n+\t}\n+\tfor (@hunk) {\n+\t\tif ($_->{USE}) {\n+\t\t\tpush @result, @{$_->{TEXT}};\n+\t\t}\n+\t}\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\trefresh();\n+\t}\n+\n+\tprint \"\\n\";\n+}\n+\n+sub diff_cmd {\n+\tmy @mods = list_modified('index-only');\n+\t@mods = grep { !($_->{BINARY}) } @mods;\n+\treturn if (!@mods);\n+\tmy (@them) = list_and_choose({ PROMPT => 'Review diff',\n+\t\t\t\t     IMMEDIATE => 1,\n+\t\t\t\t     HEADER => $status_head, },\n+\t\t\t\t   @mods);\n+\treturn if (!@them);\n+\tmy $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';\n+\tsystem(qw(git diff -p --cached), $reference, '--',\n+\t\tmap { $_->{VALUE} } @them);\n+}\n+\n+sub quit_cmd {\n+\tprint \"Bye.\\n\";\n+\texit(0);\n+}\n+\n+sub help_cmd {\n+\tprint colored $help_color, <<\\EOF ;\n+status        - show paths with changes\n+update        - add working tree state to the staged set of changes\n+reset         - reset staged set of changes back to the HEAD version\n+patch         - pick hunks and update selectively\n+diff\t      - view diff between HEAD and index\n+add untracked - add contents of untracked files to the staged set of changes\n+EOF\n+}\n+\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+\t\tdie \"invalid argument $arg, expecting --\"\n+\t\t    unless $arg eq \"--\";\n+\t}\n+\telsif ($arg ne \"--\") {\n+\t\tdie \"invalid argument $arg, expecting --\";\n+\t}\n+}\n+\n+sub main_loop {\n+\tmy @cmd = ([ 'status', \\&status_cmd, ],\n+\t\t   [ 'update', \\&update_cmd, ],\n+\t\t   [ 'reset', \\&reset_cmd, ],\n+\t\t   [ 'add untracked', \\&add_untracked_cmd, ],\n+\t\t   [ 'patch', \\&patch_update_cmd, ],\n+\t\t   [ 'diff', \\&diff_cmd, ],\n+\t\t   [ 'checkout', \\&checkout_cmd, ],\n+\t\t   [ 'quit', \\&quit_cmd, ],\n+\t\t   [ 'help', \\&help_cmd, ],\n+\t);\n+\twhile (1) {\n+\t\tmy ($it) = list_and_choose({ PROMPT => 'What now',\n+\t\t\t\t\t     SINGLETON => 1,\n+\t\t\t\t\t     LIST_FLAT => 4,\n+\t\t\t\t\t     HEADER => '*** Commands ***',\n+\t\t\t\t\t     ON_EOF => \\&quit_cmd,\n+\t\t\t\t\t     IMMEDIATE => 1 }, @cmd);\n+\t\tif ($it) {\n+\t\t\teval {\n+\t\t\t\t$it->[1]->();\n+\t\t\t};\n+\t\t\tif ($@) {\n+\t\t\t\tprint \"$@\";\n+\t\t\t}\n+\t\t}\n+\t}\n+}\n+\n+process_args();\n+refresh();\n+if ($patch_mode) {\n+\tpatch_update_cmd();\n+}\n+else {\n+\tstatus_cmd();\n+\tmain_loop();\n+}\n-- \n1.6.0.2.309.g14f93\n"},{"id":"92414","messageId":"20081006145137.GC8203@spearce.org","threadId":"15787","inReplyTo":"353e718308129444268e61f251dab769761f095f.1223304141.git.marco-oweber@gmx.de","subject":"Re: [PATCH 2/2] add--interactive: added [c]heckout","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-10-06T14:51:37Z","receivedAt":"2008-10-06T14:51:37Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Marc Weber <marco-oweber@gmx.de> wrote:\n> this is convinient for cygwin users not using rxvt.\n> \n> added last (before quit and help) to not break menu layout and keep exisiting\n> numbering\n> \n> Signed-off-by: Marc Weber <marco-oweber@gmx.de>\n> ---\n>  git-add--interactive | 1129 ++++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 files changed, 1129 insertions(+), 0 deletions(-)\n>  create mode 100755 git-add--interactive\n> \n> diff --git a/git-add--interactive b/git-add--interactive\n> new file mode 100755\n> index 0000000..2e7fa55\n> --- /dev/null\n> +++ b/git-add--interactive\n\nUhm, what is this patch against?  git.git?  If so why are we creating\na new 1129 line script named something erily close to a script we\nalready have?\n\n-- \nShawn.\n"},{"id":"92415","messageId":"20081006145509.GD8203@spearce.org","threadId":"15787","inReplyTo":"b41e1729a8817f0d3cba2be1edc37513a72901dd.1223304141.git.marco-oweber@gmx.de","subject":"Re: [PATCH 1/2] add--interactive: rename [r]evert command to [r]eset","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-10-06T14:55:09Z","receivedAt":"2008-10-06T14:55:09Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Marc Weber <marco-oweber@gmx.de> wrote:\n> diff --git a/git-add--interactive.perl b/git-add--interactive.perl\n> index da768ee..5352d16 100755\n> --- a/git-add--interactive.perl\n> +++ b/git-add--interactive.perl\n> @@ -498,8 +498,8 @@ sub update_cmd {\n>         print \"\\n\";\n>  }\n>  \n> -sub revert_cmd {\n> -       my @update = list_and_choose({ PROMPT => 'Revert',\n> +sub reset_cmd {\n> +       my @update = list_and_choose({ PROMPT => 'Reset',\n>                                        HEADER => $status_head, },\n>                                      list_modified());\n>         if (@update) {\n\nThis patch is mangled.  The context line immediately above is\nindented with tabs in the source file, but is 8 spaces in the patch.\nThe patch doesn't apply.\n\n-- \nShawn.\n"},{"id":"92420","messageId":"20081006155141.GA28582@gmx.de","threadId":"15787","inReplyTo":"20081006145509.GD8203@spearce.org","subject":"Re: [PATCH 1/2] add--interactive: rename [r]evert command to [r]eset","fromName":"Marc Weber","fromEmail":"marco-oweber@gmx.de","sentAt":"2008-10-06T15:51:41Z","receivedAt":"2008-10-06T15:51:41Z","isPatch":true,"sender":{"key":"marco-oweber@gmx.de","avatar":null},"body":"> This patch is mangled.  The context line immediately above is\nSorry, I thought set paste sets noexpandtab as well, I was wrong.\n\n\n============= patch starting at commit message =======================\nThe term \"revert\" is used very confusingly here. In git, to \"revert\"\nis to take the changes made by a previous commit and un-apply them,\ncreating a new commit. The act of changing what is in the index to\nmatch HEAD is called \"reset\".\n\nFurthermore, incoming svn users will also find this confusing, since\nto them, \"revert\" means to reset the working tree, which corresponds\nto \"checkout\" in git.\n\nSigned-off-by: Marc Weber <marco-oweber@gmx.de>\ncommit message rewritten by: Jeff King <peff@peff.net>\n---\n git-add--interactive.perl |   10 +++++-----\n 1 files changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex da768ee..5352d16 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -498,8 +498,8 @@ sub update_cmd {\n \tprint \"\\n\";\n }\n \n-sub revert_cmd {\n-\tmy @update = list_and_choose({ PROMPT => 'Revert',\n+sub reset_cmd {\n+\tmy @update = list_and_choose({ PROMPT => 'Reset',\n \t\t\t\t       HEADER => $status_head, },\n \t\t\t\t     list_modified());\n \tif (@update) {\n@@ -527,7 +527,7 @@ sub revert_cmd {\n \t\t\t}\n \t\t}\n \t\trefresh();\n-\t\tsay_n_paths('reverted', @update);\n+\t\tsay_n_paths('reset', @update);\n \t}\n \tprint \"\\n\";\n }\n@@ -1046,7 +1046,7 @@ sub help_cmd {\n \tprint colored $help_color, <<\\EOF ;\n status        - show paths with changes\n update        - add working tree state to the staged set of changes\n-revert        - revert staged set of changes back to the HEAD version\n+reset         - reset staged set of changes back to the HEAD version\n patch         - pick hunks and update selectively\n diff\t      - view diff between HEAD and index\n add untracked - add contents of untracked files to the staged set of changes\n@@ -1070,7 +1070,7 @@ sub process_args {\n sub main_loop {\n \tmy @cmd = ([ 'status', \\&status_cmd, ],\n \t\t   [ 'update', \\&update_cmd, ],\n-\t\t   [ 'revert', \\&revert_cmd, ],\n+\t\t   [ 'reset', \\&reset_cmd, ],\n \t\t   [ 'add untracked', \\&add_untracked_cmd, ],\n \t\t   [ 'patch', \\&patch_update_cmd, ],\n \t\t   [ 'diff', \\&diff_cmd, ],\n-- \n1.6.0.2.GIT\n"},{"id":"92421","messageId":"20081006155535.GB28582@gmx.de","threadId":"15787","inReplyTo":"20081006145137.GC8203@spearce.org","subject":"Re: [PATCH 2/2] add--interactive: added [c]heckout","fromName":"Marc Weber","fromEmail":"marco-oweber@gmx.de","sentAt":"2008-10-06T15:55:35Z","receivedAt":"2008-10-06T15:55:35Z","isPatch":true,"sender":{"key":"marco-oweber@gmx.de","avatar":null},"body":"> Uhm, what is this patch against?  git.git?  If so why are we creating\n> a new 1129 line script named something erily close to a script we\n> already have?\nI accidently added the dist file git-add--interactive instead of\nthe .perl file.. I'll take more care the next time.\nBoth patches should apply cleanly against git.git master branch c4275591fb\n\n============= 2/2 starting at commit message =========================\nFrom 741d1e87669ced27a41f8be1b7b65836bd969d5a Mon Sep 17 00:00:00 2001\nMessage-Id: <741d1e87669ced27a41f8be1b7b65836bd969d5a.1223307236.git.marco-oweber@gmx.de>\nIn-Reply-To: <b41e1729a8817f0d3cba2be1edc37513a72901dd.1223307236.git.marco-oweber@gmx.de>\nReferences: <b41e1729a8817f0d3cba2be1edc37513a72901dd.1223307236.git.marco-oweber@gmx.de>\nFrom: Marc Weber <marco-oweber@gmx.de>\nDate: Mon, 6 Oct 2008 14:32:13 +0000\nSubject: [PATCH 2/2] add--interactive: added [c]heckout\n\nthis is convinient for cygwin users not using rxvt.\n\ncommand added last (before quit and help) to not break menu layout and keep exisiting\nnumbering\n\nSigned-off-by: Marc Weber <marco-oweber@gmx.de>\n---\n git-add--interactive.perl |   22 ++++++++++++++++++++++\n 1 files changed, 22 insertions(+), 0 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 5352d16..34629b5 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -532,6 +532,27 @@ sub reset_cmd {\n \tprint \"\\n\";\n }\n \n+sub checkout_cmd {\n+\tmy @update = list_and_choose({ PROMPT => 'Checkout',\n+\t\t\t\t       HEADER => $status_head, },\n+\t\t\t\t     list_modified());\n+\tif (@update) {\n+\t\tif (is_initial_commit()) {\n+\t\t\t# should never be executed because there can't be modified files\n+\t\t\tprint \"error: no revision in repo yet\\n\";\n+\t\t}\n+\t\telse {\n+\t\t\tfor (@update) {\n+\t\t\t\tsystem(qw(git checkout --),\n+\t\t\t\t$_->{VALUE});\n+\t\t\t}\n+\t\t}\n+\t\trefresh();\n+\t\tsay_n_paths('checked out', @update);\n+\t}\n+\tprint \"\\n\";\n+}\n+\n sub add_untracked_cmd {\n \tmy @add = list_and_choose({ PROMPT => 'Add untracked' },\n \t\t\t\t  list_untracked());\n@@ -1074,6 +1095,7 @@ sub main_loop {\n \t\t   [ 'add untracked', \\&add_untracked_cmd, ],\n \t\t   [ 'patch', \\&patch_update_cmd, ],\n \t\t   [ 'diff', \\&diff_cmd, ],\n+\t\t   [ 'checkout', \\&checkout_cmd, ],\n \t\t   [ 'quit', \\&quit_cmd, ],\n \t\t   [ 'help', \\&help_cmd, ],\n \t);\n-- \n1.6.0.2.GIT\n"}]}