{"thread":{"id":"27905","subject":"feature request: git add--interactive --patch on regex-matched hunks only","startedAt":"2011-07-24T05:11:29Z","lastAt":"2011-07-27T17:24:09Z","messageCount":19,"participants":["Nguyen Thai Ngoc Duy","Jeff King","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"171912","messageId":"CACsJy8B1B25DZ1yrzHq69vwgzQyM2ouTXCHb8oPRpb_cAX+JZQ@mail.gmail.com","threadId":"27905","inReplyTo":null,"subject":"feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-07-24T05:11:29Z","receivedAt":"2011-07-24T05:11:29Z","isPatch":false,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Hello Perl heroes,\n\nCan we have \"git add--interactive --patch --match=regex\" where only\n(splitted if possible) hunks that match regex are shown?\n\nI've been reviewing a .po file and adding translations that I approve,\nleaving the rest to be determined later. The problem with .po files is\nthat a lot of comment lines are changes because some translations are\nmoved in source code. I don't care about those comment changes and\nwould like to completely ignore them (even destroy them with git\ncheckout -p).\n-- \nDuy\n"},{"id":"172017","messageId":"20110725215553.GA23145@sigill.intra.peff.net","threadId":"27905","inReplyTo":"CACsJy8B1B25DZ1yrzHq69vwgzQyM2ouTXCHb8oPRpb_cAX+JZQ@mail.gmail.com","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-25T21:55:55Z","receivedAt":"2011-07-25T21:55:55Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Jul 24, 2011 at 12:11:29PM +0700, Nguyen Thai Ngoc Duy wrote:\n\n> Can we have \"git add--interactive --patch --match=regex\" where only\n> (splitted if possible) hunks that match regex are shown?\n\nThe patch below does this, but there are a lot of unanswered questions.\nSuch as:\n\n  1. What does it mean to be \"shown\"? My patch auto-marks non-matching\n     hunks as \"do not stage\". That means you can still switch back to\n     them in the hunk list and enable them if you want. Another option\n     would be to omit them entirely, and pretend that those hunks don't\n     exist.\n\n  2. What should we do with non-text changes, like mode changes are\n     full-file deletion?\n\n  3. What should be shown for a file with no matching hunks? Probably\n     nothing (i.e., as if it had been limited away by pathname)? My\n     patch shows the header, but that is easy to fix.\n\nI think those depend on the intended use case. For me, it seems useful\nto do something like:\n\n  $ hack hack hack\n  [oops, I need to refactor foo() and its callers first]\n  $ refactor refactor refactor\n  $ git add -p --match=foo\n  $ git commit -m 'refactor foo'\n  [resume initial hacking]\n\nHowever, I'm not sure I would trust my regex to actually get all of the\nchanges needed for the refactoring (e.g., there might be nearby hunks\nrelated to the refactoring that are not specifically in the same hunk as\nthe word \"foo\"). So I tend to just \"git add -p\" and flip through the\nchanges manually.\n\n> I've been reviewing a .po file and adding translations that I approve,\n> leaving the rest to be determined later. The problem with .po files is\n> that a lot of comment lines are changes because some translations are\n> moved in source code. I don't care about those comment changes and\n> would like to completely ignore them (even destroy them with git\n> checkout -p).\n\nYou can already skip around in the hunk list using \"/regex\". Might that\nbe enough for you? I think you're stuck typing \"/yoursearch\" over and\nover, though. It would be nice if doing just \"/\" would search again for\nthe previous regex.\n\nAnyway, here's the patch. It's just the perl bits. You'd need to write\nthe scaffolding to add an option to \"git add\", \"git checkout\", etc.\n\n---\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 2ee0908..07896d4 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -86,6 +86,7 @@ sub colored {\n # command line options\n my $patch_mode;\n my $patch_mode_revision;\n+my $patch_match;\n \n sub apply_patch;\n sub apply_patch_for_checkout_commit;\n@@ -1277,6 +1278,17 @@ sub display_hunks {\n \treturn $i;\n }\n \n+sub want_hunk {\n+\tmy ($re, $hunk) = @_;\n+\n+\treturn 1 if $hunk->{TYPE} ne 'hunk';\n+\n+\tforeach my $line (@{$hunk->{TEXT}}) {\n+\t\treturn 1 if $line =~ $re;\n+\t}\n+\treturn 0;\n+}\n+\n sub patch_update_file {\n \tmy $quit = 0;\n \tmy ($ix, $num);\n@@ -1301,6 +1313,20 @@ sub patch_update_file {\n \t$num = scalar @hunk;\n \t$ix = 0;\n \n+\tif ($patch_match) {\n+\t\t# mark non-matching text hunks as \"do not want\"\n+\t\tforeach my $hunk (@hunk) {\n+\t\t\tif (!want_hunk($patch_match, $hunk)) {\n+\t\t\t\t$hunk->{USE} = 0;\n+\t\t\t}\n+\t\t}\n+\t\t# and then advance us to the first undecided hunk\n+\t\twhile ($ix < $num) {\n+\t\t\tlast unless defined $hunk[$ix]{USE};\n+\t\t\t$ix++;\n+\t\t}\n+\t}\n+\n \twhile (1) {\n \t\tmy ($prev, $next, $other, $undecided, $i);\n \t\t$other = '';\n@@ -1606,6 +1632,10 @@ sub process_args {\n \t\t} else {\n \t\t\t$patch_mode = 'stage';\n \t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\tif ($arg =~ /--match=(.*)/) {\n+\t\t\t\t$patch_match = qr/$1/;\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t}\n \t\t}\n \t\tdie \"invalid argument $arg, expecting --\"\n \t\t    unless $arg eq \"--\";\n"},{"id":"172029","messageId":"7vbowiq62m.fsf@alter.siamese.dyndns.org","threadId":"27905","inReplyTo":"20110725215553.GA23145@sigill.intra.peff.net","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-25T23:44:01Z","receivedAt":"2011-07-25T23:44:01Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> However, I'm not sure I would trust my regex to actually get all of the\n> changes needed for the refactoring (e.g., there might be nearby hunks\n> related to the refactoring that are not specifically in the same hunk as\n> the word \"foo\"). So I tend to just \"git add -p\" and flip through the\n> changes manually.\n\nYes, that is a valid concern.\n\n> You can already skip around in the hunk list using \"/regex\". Might that\n> be enough for you? I think you're stuck typing \"/yoursearch\" over and\n> over, though. It would be nice if doing just \"/\" would search again for\n> the previous regex.\n\nYes, I think that makes a lot more sense than the current \"Ah, empty? ask\nfrom the terminal\" behaviour.\n\n git-add--interactive.perl |   14 ++++++++------\n 1 files changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 8f0839d..0b6f8a6 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -1257,7 +1257,7 @@ sub display_hunks {\n \n sub patch_update_file {\n \tmy $quit = 0;\n-\tmy ($ix, $num);\n+\tmy ($ix, $num, $last_search_string);\n \tmy $path = shift;\n \tmy ($head, @hunk) = parse_diff($path);\n \t($head, my $mode, my $deletion) = parse_diff_header($head);\n@@ -1395,11 +1395,12 @@ sub patch_update_file {\n \t\t\t}\n \t\t\telsif ($line =~ m|^/(.*)|) {\n \t\t\t\tmy $regex = $1;\n-\t\t\t\tif ($1 eq \"\") {\n-\t\t\t\t\tprint colored $prompt_color, \"search for regex? \";\n-\t\t\t\t\t$regex = <STDIN>;\n-\t\t\t\t\tif (defined $regex) {\n-\t\t\t\t\t\tchomp $regex;\n+\t\t\t\tif ($regex eq \"\") {\n+\t\t\t\t\tif ($last_search_string) {\n+\t\t\t\t\t\t$regex = $last_search_string;\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\terror_msg \"Need a regexp to search\\n\";\n+\t\t\t\t\t\tnext;\n \t\t\t\t\t}\n \t\t\t\t}\n \t\t\t\tmy $search_string;\n@@ -1412,6 +1413,7 @@ sub patch_update_file {\n \t\t\t\t\terror_msg \"Malformed search regexp $exp: $err\\n\";\n \t\t\t\t\tnext;\n \t\t\t\t}\n+\t\t\t\t$last_search_string = $regex;\n \t\t\t\tmy $iy = $ix;\n \t\t\t\twhile (1) {\n \t\t\t\t\tmy $text = join (\"\", @{$hunk[$iy]{TEXT}});\n"},{"id":"172031","messageId":"CACsJy8Db_sYFsQ2GcbcumJJYrXZDkKmuuULSM0_Z=HWvbYh8Bg@mail.gmail.com","threadId":"27905","inReplyTo":"20110725215553.GA23145@sigill.intra.peff.net","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-07-26T03:03:31Z","receivedAt":"2011-07-26T03:03:31Z","isPatch":false,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Jul 26, 2011 at 4:55 AM, Jeff King <peff@peff.net> wrote:\n> On Sun, Jul 24, 2011 at 12:11:29PM +0700, Nguyen Thai Ngoc Duy wrote:\n>\n>> Can we have \"git add--interactive --patch --match=regex\" where only\n>> (splitted if possible) hunks that match regex are shown?\n>\n> The patch below does this,\n\nThanks!\n\n> but there are a lot of unanswered questions.\n> Such as:\n>\n>  1. What does it mean to be \"shown\"? My patch auto-marks non-matching\n>     hunks as \"do not stage\". That means you can still switch back to\n>     them in the hunk list and enable them if you want. Another option\n>     would be to omit them entirely, and pretend that those hunks don't\n>     exist.\n\nEither way is ok. Maybe the option in this case could be changed to\n--nostage=regex to reflect the behavior.\n\n>  2. What should we do with non-text changes, like mode changes are\n>     full-file deletion?\n\nProbably invalid use case for --match.\n\n>  3. What should be shown for a file with no matching hunks? Probably\n>     nothing (i.e., as if it had been limited away by pathname)? My\n>     patch shows the header, but that is easy to fix.\n\nPrinting \"Nothing to add\" would be nice.\n\n> However, I'm not sure I would trust my regex to actually get all of the\n> changes needed for the refactoring (e.g., there might be nearby hunks\n> related to the refactoring that are not specifically in the same hunk as\n> the word \"foo\"). So I tend to just \"git add -p\" and flip through the\n> changes manually.\n\nWell, I do \"git diff --cached\" and \"git diff\"  (the search in the\ndiff) before committing just to make sure I don't miss any changes.\n\n> You can already skip around in the hunk list using \"/regex\". Might that\n> be enough for you? I think you're stuck typing \"/yoursearch\" over and\n> over, though. It would be nice if doing just \"/\" would search again for\n> the previous regex.\n\nYes I'm stuck typing /mysearch. Yes, \"/\" alone would be nice, but in\nthis case I really want mass ignore certain changes so I can focus on\nimportant strings first.\n-- \nDuy\n"},{"id":"172034","messageId":"20110726050304.GA25046@sigill.intra.peff.net","threadId":"27905","inReplyTo":"7vbowiq62m.fsf@alter.siamese.dyndns.org","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-26T05:03:04Z","receivedAt":"2011-07-26T05:03:04Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 25, 2011 at 04:44:01PM -0700, Junio C Hamano wrote:\n\n> diff --git a/git-add--interactive.perl b/git-add--interactive.perl\n> index 8f0839d..0b6f8a6 100755\n> --- a/git-add--interactive.perl\n> +++ b/git-add--interactive.perl\n> @@ -1257,7 +1257,7 @@ sub display_hunks {\n>  \n>  sub patch_update_file {\n>  \tmy $quit = 0;\n> -\tmy ($ix, $num);\n> +\tmy ($ix, $num, $last_search_string);\n>  \tmy $path = shift;\n>  \tmy ($head, @hunk) = parse_diff($path);\n>  \t($head, my $mode, my $deletion) = parse_diff_header($head);\n> @@ -1395,11 +1395,12 @@ sub patch_update_file {\n>  \t\t\t}\n>  \t\t\telsif ($line =~ m|^/(.*)|) {\n>  \t\t\t\tmy $regex = $1;\n> -\t\t\t\tif ($1 eq \"\") {\n> -\t\t\t\t\tprint colored $prompt_color, \"search for regex? \";\n> -\t\t\t\t\t$regex = <STDIN>;\n> -\t\t\t\t\tif (defined $regex) {\n> -\t\t\t\t\t\tchomp $regex;\n> +\t\t\t\tif ($regex eq \"\") {\n> +\t\t\t\t\tif ($last_search_string) {\n> +\t\t\t\t\t\t$regex = $last_search_string;\n> +\t\t\t\t\t} else {\n> +\t\t\t\t\t\terror_msg \"Need a regexp to search\\n\";\n> +\t\t\t\t\t\tnext;\n\nHow does this interact with single-key mode? I imagine we just get the\n\"/\" at that point and have to read the rest of the regex manually. Which\nis probably why this code was here in the first place.\n\nSo I think we might have to do something like:\n\n  my $regex = $1;\n  if ($use_readkey) {\n    print colored $prompt_color, \"search for regex?\";\n    $regex = <STDIN>;\n    chomp $regex;\n  }\n  if ($regex eq \"\") {\n    ...\n  }\n\nAnd then that would give single-key people an opportunity to input a new\nregex, or to hit enter to just use the last one.\n\n-Peff\n"},{"id":"172035","messageId":"20110726051411.GB25046@sigill.intra.peff.net","threadId":"27905","inReplyTo":"CACsJy8Db_sYFsQ2GcbcumJJYrXZDkKmuuULSM0_Z=HWvbYh8Bg@mail.gmail.com","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-26T05:14:12Z","receivedAt":"2011-07-26T05:14:12Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 26, 2011 at 10:03:31AM +0700, Nguyen Thai Ngoc Duy wrote:\n\n> On Tue, Jul 26, 2011 at 4:55 AM, Jeff King <peff@peff.net> wrote:\n> > On Sun, Jul 24, 2011 at 12:11:29PM +0700, Nguyen Thai Ngoc Duy wrote:\n> >\n> >> Can we have \"git add--interactive --patch --match=regex\" where only\n> >> (splitted if possible) hunks that match regex are shown?\n> >\n> > The patch below does this,\n> \n> Thanks!\n\nSo I did this as a quick \"that should be really easy\" proof of concept.\nI'm not too interested personally in taking it all the way to an\nacceptable patch.  I'm happy to help out with the perl parts, though, if\nyou want to do the rest (C scaffolding for calling add--interactive,\ntests, and docs).\n\n> >  1. What does it mean to be \"shown\"? My patch auto-marks non-matching\n> >     hunks as \"do not stage\". That means you can still switch back to\n> >     them in the hunk list and enable them if you want. Another option\n> >     would be to omit them entirely, and pretend that those hunks don't\n> >     exist.\n> \n> Either way is ok. Maybe the option in this case could be changed to\n> --nostage=regex to reflect the behavior.\n\nI can't help but think there is some way to relate it to the\npath-limiting that we do elsewhere. It's sort of like diffcore's \"-G\",\nexcept that is about picking whole files, not individual hunks. I guess\nmost of git doesn't operate at the hunk level in quite the same way, so\nthere is no analagous part.\n\nThe more I think about it, it is probably simpler both conceptually and\nin implementation to filter out those hunks entirely instead of marking\nthem used. The latter gives the user a chance to manually find them and\ngo back to them via 'J' and 'K'. But I find the chance that that is\nuseful to be much less than the chance that somebody gets confused or\nannoyed by them being there (because they were iterating, or because\nthey did a \"/\" search).\n\nAnd it's not like everything needs to be in a single \"add\" call. The\npoint of \"add\" is that you could call it multiple times, or even call\nit, then check what \"git diff\" says, then decide you want to stage some\nmore.\n\n> >  2. What should we do with non-text changes, like mode changes are\n> >     full-file deletion?\n> \n> Probably invalid use case for --match.\n\nInvalid, like we should have an error? I think that would be annoying,\nbecause you want to be able to do \"git add -p --match=foo\" even when\nthere is an unrelated mode change. I think in the mindset of \"this is a\nfilter for things you want to see\", it probably makes sense to just\npretend they aren't there (and the user can always follow up with\nanother \"add\", as I mentioned above).\n\n> >  3. What should be shown for a file with no matching hunks? Probably\n> >     nothing (i.e., as if it had been limited away by pathname)? My\n> >     patch shows the header, but that is easy to fix.\n> \n> Printing \"Nothing to add\" would be nice.\n\nIf we treat it like path limiting, I think we could just print nothing\nfor that file. That is, doing:\n\n  git add -p foo\n\nwould not even look at \"bar\" or print anything about it. Similarly:\n\n  git add -p --match=foo\n\nshould probably say nothing whatsoever about files that didn't mention\nfoo at all.\n\nFor reference, here's the C scaffolding I used to test the patch I sent\nearlier. I didn't include it earlier because it made the perl bits hard\nto find amidst all of the changes.\n\nProbably the other interactive modes (checkout, reset, etc) should get a\nsimilar option; it would just be a matter of passing the regex through\nto the perl script.\n\n---\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 822ee3a..096dbc7 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -220,20 +220,25 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p\n }\n \n int run_add_interactive(const char *revision, const char *patch_mode,\n-\t\t\tconst char **pathspec)\n+\t\t\tconst char *match, const char **pathspec)\n {\n \tint status, ac, pc = 0;\n \tconst char **args;\n+\tstruct strbuf match_arg = STRBUF_INIT;\n \n \tif (pathspec)\n \t\twhile (pathspec[pc])\n \t\t\tpc++;\n \n-\targs = xcalloc(sizeof(const char *), (pc + 5));\n+\targs = xcalloc(sizeof(const char *), (pc + 6));\n \tac = 0;\n \targs[ac++] = \"add--interactive\";\n \tif (patch_mode)\n \t\targs[ac++] = patch_mode;\n+\tif (match) {\n+\t\tstrbuf_addf(&match_arg, \"--match=%s\", match);\n+\t\targs[ac++] = match_arg.buf;\n+\t}\n \tif (revision)\n \t\targs[ac++] = revision;\n \targs[ac++] = \"--\";\n@@ -245,10 +250,12 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n \n \tstatus = run_command_v_opt(args, RUN_GIT_CMD);\n \tfree(args);\n+\tstrbuf_release(&match_arg);\n \treturn status;\n }\n \n-int interactive_add(int argc, const char **argv, const char *prefix, int patch)\n+int interactive_add(int argc, const char **argv, const char *prefix, int patch,\n+\t\t    const char *match)\n {\n \tconst char **pathspec = NULL;\n \n@@ -260,7 +267,7 @@ int interactive_add(int argc, const char **argv, const char *prefix, int patch)\n \n \treturn run_add_interactive(NULL,\n \t\t\t\t   patch ? \"--patch\" : NULL,\n-\t\t\t\t   pathspec);\n+\t\t\t\t   match, pathspec);\n }\n \n static int edit_patch(int argc, const char **argv, const char *prefix)\n@@ -317,6 +324,7 @@ N_(\"The following paths are ignored by one of your .gitignore files:\\n\");\n \n static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;\n static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0;\n+static const char *patch_match;\n \n static struct option builtin_add_options[] = {\n \tOPT__DRY_RUN(&show_only, \"dry run\"),\n@@ -324,6 +332,8 @@ static struct option builtin_add_options[] = {\n \tOPT_GROUP(\"\"),\n \tOPT_BOOLEAN('i', \"interactive\", &add_interactive, \"interactive picking\"),\n \tOPT_BOOLEAN('p', \"patch\", &patch_interactive, \"select hunks interactively\"),\n+\tOPT_STRING(0, \"match\", &patch_match, \"regex\",\n+\t\t   \"find pattern within --patch hunks\"),\n \tOPT_BOOLEAN('e', \"edit\", &edit_interactive, \"edit current diff and apply\"),\n \tOPT__FORCE(&ignored_too, \"allow adding otherwise ignored files\"),\n \tOPT_BOOLEAN('u', \"update\", &take_worktree_changes, \"update tracked files\"),\n@@ -384,7 +394,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \tif (patch_interactive)\n \t\tadd_interactive = 1;\n \tif (add_interactive)\n-\t\texit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));\n+\t\texit(interactive_add(argc - 1, argv + 1, prefix,\n+\t\t\t\t     patch_interactive, patch_match));\n \n \tif (edit_interactive)\n \t\treturn(edit_patch(argc, argv, prefix));\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex d647a31..8319bb2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -765,7 +765,8 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\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+\treturn run_add_interactive(revision, \"--patch=checkout\", NULL,\n+\t\t\t\t   pathspec);\n }\n \n struct tracking_name_data {\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex e50694e..24d90a4 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -366,7 +366,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int\n \t\told_index_env = getenv(INDEX_ENVIRONMENT);\n \t\tsetenv(INDEX_ENVIRONMENT, index_lock.filename, 1);\n \n-\t\tif (interactive_add(argc, argv, prefix, patch_interactive) != 0)\n+\t\tif (interactive_add(argc, argv, prefix, patch_interactive,\n+\t\t\t\t    NULL) != 0)\n \t\t\tdie(_(\"interactive add failed\"));\n \n \t\tif (old_index_env && *old_index_env)\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 811e8e2..924c104 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -165,7 +165,7 @@ static int interactive_reset(const char *revision, const char **argv,\n \tif (*argv)\n \t\tpathspec = get_pathspec(prefix, argv);\n \n-\treturn run_add_interactive(revision, \"--patch=reset\", pathspec);\n+\treturn run_add_interactive(revision, \"--patch=reset\", NULL, pathspec);\n }\n \n static int read_from_tree(const char *prefix, const char **argv,\ndiff --git a/commit.h b/commit.h\nindex b23f33b..5ab0072 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -167,9 +167,10 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,\n int is_descendant_of(struct commit *, struct commit_list *);\n int in_merge_bases(struct commit *, struct commit **, int);\n \n-extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);\n+extern int interactive_add(int argc, const char **argv, const char *prefix,\n+\t\t\t   int patch, const char *match);\n extern int run_add_interactive(const char *revision, const char *patch_mode,\n-\t\t\t       const char **pathspec);\n+\t\t\t       const char *match, const char **pathspec);\n \n static inline int single_parent(struct commit *commit)\n {\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 2ee0908..07896d4 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -86,6 +86,7 @@ sub colored {\n # command line options\n my $patch_mode;\n my $patch_mode_revision;\n+my $patch_match;\n \n sub apply_patch;\n sub apply_patch_for_checkout_commit;\n@@ -1277,6 +1278,17 @@ sub display_hunks {\n \treturn $i;\n }\n \n+sub want_hunk {\n+\tmy ($re, $hunk) = @_;\n+\n+\treturn 1 if $hunk->{TYPE} ne 'hunk';\n+\n+\tforeach my $line (@{$hunk->{TEXT}}) {\n+\t\treturn 1 if $line =~ $re;\n+\t}\n+\treturn 0;\n+}\n+\n sub patch_update_file {\n \tmy $quit = 0;\n \tmy ($ix, $num);\n@@ -1301,6 +1313,20 @@ sub patch_update_file {\n \t$num = scalar @hunk;\n \t$ix = 0;\n \n+\tif ($patch_match) {\n+\t\t# mark non-matching text hunks as \"do not want\"\n+\t\tforeach my $hunk (@hunk) {\n+\t\t\tif (!want_hunk($patch_match, $hunk)) {\n+\t\t\t\t$hunk->{USE} = 0;\n+\t\t\t}\n+\t\t}\n+\t\t# and then advance us to the first undecided hunk\n+\t\twhile ($ix < $num) {\n+\t\t\tlast unless defined $hunk[$ix]{USE};\n+\t\t\t$ix++;\n+\t\t}\n+\t}\n+\n \twhile (1) {\n \t\tmy ($prev, $next, $other, $undecided, $i);\n \t\t$other = '';\n@@ -1606,6 +1632,10 @@ sub process_args {\n \t\t} else {\n \t\t\t$patch_mode = 'stage';\n \t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\tif ($arg =~ /--match=(.*)/) {\n+\t\t\t\t$patch_match = qr/$1/;\n+\t\t\t\t$arg = shift @ARGV or die \"missing --\";\n+\t\t\t}\n \t\t}\n \t\tdie \"invalid argument $arg, expecting --\"\n \t\t    unless $arg eq \"--\";\n"},{"id":"172037","messageId":"CACsJy8Ay1wPXAx61_rGymHDJ=YGywAy=9epiXRfJ9e68np8x6g@mail.gmail.com","threadId":"27905","inReplyTo":"20110726051411.GB25046@sigill.intra.peff.net","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-07-26T05:57:46Z","receivedAt":"2011-07-26T05:57:46Z","isPatch":false,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Jul 26, 2011 at 12:14 PM, Jeff King <peff@peff.net> wrote:\n> On Tue, Jul 26, 2011 at 10:03:31AM +0700, Nguyen Thai Ngoc Duy wrote:\n>\n>> On Tue, Jul 26, 2011 at 4:55 AM, Jeff King <peff@peff.net> wrote:\n>> > On Sun, Jul 24, 2011 at 12:11:29PM +0700, Nguyen Thai Ngoc Duy wrote:\n>> >\n>> >> Can we have \"git add--interactive --patch --match=regex\" where only\n>> >> (splitted if possible) hunks that match regex are shown?\n>> >\n>> > The patch below does this,\n>>\n>> Thanks!\n>\n> So I did this as a quick \"that should be really easy\" proof of concept.\n> I'm not too interested personally in taking it all the way to an\n> acceptable patch.  I'm happy to help out with the perl parts, though, if\n> you want to do the rest (C scaffolding for calling add--interactive,\n> tests, and docs).\n\nIt's my itch. I'm more than happy if you take care of the Perl part,\nI'll do the rest. While you're doing the Perl part, should we have\nboth --match and --no-match? One filters in. The other filters out. I\nguess people may find filter-in case more helpful than the other.\n\n> The more I think about it, it is probably simpler both conceptually and\n> in implementation to filter out those hunks entirely instead of marking\n> them used. The latter gives the user a chance to manually find them and\n> go back to them via 'J' and 'K'. But I find the chance that that is\n> useful to be much less than the chance that somebody gets confused or\n> annoyed by them being there (because they were iterating, or because\n> they did a \"/\" search).\n\nI thought of this way initially, but I needed to split hunks in\nparse_diff and was stuck because I was not familiar with data\nstructure used in git-add--interactive.\n\n> And it's not like everything needs to be in a single \"add\" call. The\n> point of \"add\" is that you could call it multiple times, or even call\n> it, then check what \"git diff\" says, then decide you want to stage some\n> more.\n\nYes, the point of this new feature, is to get me focus on a selected\nparts I care.\n\n>> >  2. What should we do with non-text changes, like mode changes are\n>> >     full-file deletion?\n>>\n>> Probably invalid use case for --match.\n>\n> Invalid, like we should have an error? I think that would be annoying,\n> because you want to be able to do \"git add -p --match=foo\" even when\n> there is an unrelated mode change. I think in the mindset of \"this is a\n> filter for things you want to see\", it probably makes sense to just\n> pretend they aren't there (and the user can always follow up with\n> another \"add\", as I mentioned above).\n\nAgreed.\n\n>> >  3. What should be shown for a file with no matching hunks? Probably\n>> >     nothing (i.e., as if it had been limited away by pathname)? My\n>> >     patch shows the header, but that is easy to fix.\n>>\n>> Printing \"Nothing to add\" would be nice.\n>\n> If we treat it like path limiting, I think we could just print nothing\n> for that file. That is, doing:\n>\n>  git add -p foo\n>\n> would not even look at \"bar\" or print anything about it. Similarly:\n>\n>  git add -p --match=foo\n>\n> should probably say nothing whatsoever about files that didn't mention\n> foo at all.\n\nHmm.. I did not thought of doing multiple files. Printing nothing\nmakes sense this case.\n-- \nDuy\n"},{"id":"172038","messageId":"20110726060903.GA29486@sigill.intra.peff.net","threadId":"27905","inReplyTo":"CACsJy8Ay1wPXAx61_rGymHDJ=YGywAy=9epiXRfJ9e68np8x6g@mail.gmail.com","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-26T06:09:04Z","receivedAt":"2011-07-26T06:09:04Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 26, 2011 at 12:57:46PM +0700, Nguyen Thai Ngoc Duy wrote:\n\n> It's my itch. I'm more than happy if you take care of the Perl part,\n> I'll do the rest. While you're doing the Perl part, should we have\n> both --match and --no-match? One filters in. The other filters out. I\n> guess people may find filter-in case more helpful than the other.\n\nYeah. Also, what about grep options like case-insensitivity? I don't\nwant to go too overboard with adding options that are unlikely to be\nused. It is perl, so technically you can do \"(?i:foo)\" to match \"foo\"\ncase-insensitively. I don't think there's a way to do --no-match style\nnegation in the regex itself, though.\n\nSpeaking of which: the name \"--match\" is way too generic for \"git add\".\nSomething like \"--hunk-match\" would be better. It also is obviously only\nuseful with \"--patch\". Should it imply \"--patch\"? Should it actually be\n\"--patch=...\"?\n\n> I thought of this way initially, but I needed to split hunks in\n> parse_diff and was stuck because I was not familiar with data\n> structure used in git-add--interactive.\n\nOh yeah, you mentioned splitting before. I'm not sure how that would\ninteract with this feature. In some cases, you'd want to split as much\nas possible, and in other cases, you'd want a bigger hunk. So I actually\nthink it is an orthogonal feature to \"autosplit\" hunks before we show\nthem to the user. I.e., something like:\n\n  git add -p --autosplit-hunks --hunk-match=...\n\n(where maybe the names could be better, but hopefully you get the\npoint).\n\n-Peff\n"},{"id":"172058","messageId":"CACsJy8Birqg2Ldp1Mt4NWOq1aT0oigTcFA8S=RWcK5y+zstwDA@mail.gmail.com","threadId":"27905","inReplyTo":"20110726060903.GA29486@sigill.intra.peff.net","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-07-26T12:44:06Z","receivedAt":"2011-07-26T12:44:06Z","isPatch":false,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Jul 26, 2011 at 1:09 PM, Jeff King <peff@peff.net> wrote:\n> On Tue, Jul 26, 2011 at 12:57:46PM +0700, Nguyen Thai Ngoc Duy wrote:\n>\n>> It's my itch. I'm more than happy if you take care of the Perl part,\n>> I'll do the rest. While you're doing the Perl part, should we have\n>> both --match and --no-match? One filters in. The other filters out. I\n>> guess people may find filter-in case more helpful than the other.\n>\n> Yeah. Also, what about grep options like case-insensitivity? I don't\n> want to go too overboard with adding options that are unlikely to be\n> used. It is perl, so technically you can do \"(?i:foo)\" to match \"foo\"\n> case-insensitively.\n\nOr even standard regex with [Ff][Oo][Oo]. We can wait until users\ncomplain about the lack of feature. I won't complain because I may\nneed to match non-ascii letters (unless pcre does support unicode,\nthen yes pleasssse)\n\n> I don't think there's a way to do --no-match style negation in the\n> regex itself, though.\n\nYour coding skills are needed :)\n\n>> I thought of this way initially, but I needed to split hunks in\n>> parse_diff and was stuck because I was not familiar with data\n>> structure used in git-add--interactive.\n>\n> Oh yeah, you mentioned splitting before. I'm not sure how that would\n> interact with this feature. In some cases, you'd want to split as much\n> as possible, and in other cases, you'd want a bigger hunk. So I actually\n> think it is an orthogonal feature to \"autosplit\" hunks before we show\n> them to the user. I.e., something like:\n>\n>  git add -p --autosplit-hunks --hunk-match=...\n\nYep. Sometimes I do want --autosplit-hunks alone.\n\n> (where maybe the names could be better, but hopefully you get the\n> point).\n\nSpeaking of names, I'm usually bad at naming, but here goes. How about\n--hunks=regex, --no-hunks=regex and --split-hunks? We may have\n--[no-]case-hunks later on but that does sound bad.\n\n<over-engineering>maybe we should support multiple --hunks (or\n--no-hunks, but not a mix of them), all must be matched, because there\nare many lines in a hunk and people may want set patterns across\nlines</over-engineering>\n-- \nDuy\n"},{"id":"172059","messageId":"CACsJy8C822Fvwav4Wpw4e-12ZY20XM1s2v4KymZkaDYLxkMHvw@mail.gmail.com","threadId":"27905","inReplyTo":"CACsJy8Birqg2Ldp1Mt4NWOq1aT0oigTcFA8S=RWcK5y+zstwDA@mail.gmail.com","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-07-26T13:03:01Z","receivedAt":"2011-07-26T13:03:01Z","isPatch":false,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"It would be even more cool if --hunks (or whatever the name will be)\ncould work without -p. I mean, if \"git diff\" supports it, then I can\nfine tune my regex to meet a selection of hunks I want, and verify it\nreally is what I want. Then \"git add --hunks=magic\" and voila! (The\n\"git add --hunks\" without -p surely can be workaround by adding \"-p\",\nthen accept all hunks).\n\nAnd if diff machinery learns this, we would have \"git log --hunks\" too.\n\nOK I'm asking too much..\n-- \nDuy\n"},{"id":"172131","messageId":"20110727080303.GA8105@sigill.intra.peff.net","threadId":"27905","inReplyTo":"CACsJy8Birqg2Ldp1Mt4NWOq1aT0oigTcFA8S=RWcK5y+zstwDA@mail.gmail.com","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-27T08:03:04Z","receivedAt":"2011-07-27T08:03:04Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 26, 2011 at 07:44:06PM +0700, Nguyen Thai Ngoc Duy wrote:\n\n> > I don't think there's a way to do --no-match style negation in the\n> > regex itself, though.\n> \n> Your coding skills are needed :)\n\nOK. Patches to follow. :)\n\n> Speaking of names, I'm usually bad at naming, but here goes. How about\n> --hunks=regex, --no-hunks=regex and --split-hunks? We may have\n> --[no-]case-hunks later on but that does sound bad.\n> \n> <over-engineering>maybe we should support multiple --hunks (or\n> --no-hunks, but not a mix of them), all must be matched, because there\n> are many lines in a hunk and people may want set patterns across\n> lines</over-engineering>\n\nMy series lets you do multiple --hunks, and a hunk just needs to match\nany of them.\n\nHaving \"--no-hunks\" implies that you can negate just some of the\nfilters. Like:\n\n  git add -p --hunks=foo --no-hunks=bar\n\nMy series doesn't support that, though it would not be that big a deal\nto do so (the tricky part is defining the OR-ing and AND-ing sensibly).\nSomething like:\n\n  git add -p --negate-hunks --hunks=foo --hunks=bar\n\nmakes it clear that you are either selecting or de-selecting with your\nfilter.\n\nTechnically somebody could also want:\n\n  git add -p --hunk=foo --and --hunk=bar\n\nbut I didn't want to get into parsing arbitrary boolean expressions.\n\nAn easy flexible thing would be to just eval perl code like:\n\n  git add -p --hunk-filter='/foo/ && !/bar/'\n\nbut I don't think we want to tie ourselves to the implementation being\nin perl forever.\n\nAnyway, the series is:\n\n  [1/5]: add--interactive: refactor patch mode argument processing\n  [2/5]: add--interactive: factor out regex error handling\n  [3/5]: add--interactive: allow hunk filtering on command line\n  [4/5]: add--interactive: allow negatation of hunk filters\n  [5/5]: add--interactive: add option to autosplit hunks\n\nLightly tested by me. You can either build on top, or just squash them\ninto your commits as appropriate.\n\n-Peff\n"},{"id":"172132","messageId":"20110727080506.GA14002@sigill.intra.peff.net","threadId":"27905","inReplyTo":"20110727080303.GA8105@sigill.intra.peff.net","subject":"[PATCH 1/5] add--interactive: refactor patch mode argument processing","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-27T08:05:07Z","receivedAt":"2011-07-27T08:05:07Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"The existing parser was very inflexible and tree-like;\nseeing \"--patch=$mode\" would put us into a conditional for\nparsing the options for that particular $mode. That made it\nvery difficult to add additional options that would work for\nall patch modes.\n\nThis cleanup turns the argument processing into a proper\nloop ready to handle multiple arguments. To replace the\nmode-specific parsing, we restructure the mode definitions\nto better reflect the various cases (i.e., no revision given\nversus \"HEAD\" versus an arbitrary revision).\n\nAs a bonus, our parsing is now a little more robust. For\nexample, we catch the bogus \"add--interactive --patch HEAD\n--\", which is meaningless, but was silently accepted before.\nIn practice this probably doesn't matter, since we are\nalways called by other parts of git, but it might catch a\nbug in another part of git.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n git-add--interactive.perl |  212 ++++++++++++++++++++++++---------------------\n 1 files changed, 112 insertions(+), 100 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 8f0839d..c5cd300 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -93,74 +93,86 @@ sub apply_patch_for_stash;\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\tIS_REVERSE => 0,\n+\t\tdefault => 'index',\n+\t\tindex => {\n+\t\t\tDIFF => 'diff-files -p',\n+\t\t\tAPPLY => sub { apply_patch 'apply --cached', @_; },\n+\t\t\tAPPLY_CHECK => 'apply --cached',\n+\t\t\tVERB => 'Stage',\n+\t\t\tTARGET => '',\n+\t\t\tPARTICIPLE => 'staging',\n+\t\t\tFILTER => 'file-only',\n+\t\t\tIS_REVERSE => 0,\n+\t\t},\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\tIS_REVERSE => 0,\n+\t\tdefault => 'head',\n+\t\thead => {\n+\t\t\tDIFF => 'diff-index -p',\n+\t\t\tAPPLY => sub { apply_patch 'apply --cached', @_; },\n+\t\t\tAPPLY_CHECK => 'apply --cached',\n+\t\t\tVERB => 'Stash',\n+\t\t\tTARGET => '',\n+\t\t\tPARTICIPLE => 'stashing',\n+\t\t\tFILTER => undef,\n+\t\t\tIS_REVERSE => 0,\n+\t\t},\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\tIS_REVERSE => 1,\n+\t'reset' => {\n+\t\tdefault => 'head',\n+\t\thead => {\n+\t\t\tDIFF => 'diff-index -p --cached',\n+\t\t\tAPPLY => sub { apply_patch 'apply -R --cached', @_; },\n+\t\t\tAPPLY_CHECK => 'apply -R --cached',\n+\t\t\tVERB => 'Unstage',\n+\t\t\tTARGET => '',\n+\t\t\tPARTICIPLE => 'unstaging',\n+\t\t\tFILTER => 'index-only',\n+\t\t\tIS_REVERSE => 1,\n+\t\t},\n+\t\tnothead => {\n+\t\t\tDIFF => 'diff-index -R -p --cached',\n+\t\t\tAPPLY => sub { apply_patch 'apply --cached', @_; },\n+\t\t\tAPPLY_CHECK => 'apply --cached',\n+\t\t\tVERB => 'Apply',\n+\t\t\tTARGET => ' to index',\n+\t\t\tPARTICIPLE => 'applying',\n+\t\t\tFILTER => 'index-only',\n+\t\t\tIS_REVERSE => 0,\n+\t\t},\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\tIS_REVERSE => 0,\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\tIS_REVERSE => 1,\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\tIS_REVERSE => 1,\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\tIS_REVERSE => 0,\n+\t'checkout' => {\n+\t\tdefault => 'index',\n+\t\tindex => {\n+\t\t\tDIFF => 'diff-files -p',\n+\t\t\tAPPLY => sub { apply_patch 'apply -R', @_; },\n+\t\t\tAPPLY_CHECK => 'apply -R',\n+\t\t\tVERB => 'Discard',\n+\t\t\tTARGET => ' from worktree',\n+\t\t\tPARTICIPLE => 'discarding',\n+\t\t\tFILTER => 'file-only',\n+\t\t\tIS_REVERSE => 1,\n+\t\t},\n+\t\thead => {\n+\t\t\tDIFF => 'diff-index -p',\n+\t\t\tAPPLY => sub { apply_patch_for_checkout_commit '-R', @_ },\n+\t\t\tAPPLY_CHECK => 'apply -R',\n+\t\t\tVERB => 'Discard',\n+\t\t\tTARGET => ' from index and worktree',\n+\t\t\tPARTICIPLE => 'discarding',\n+\t\t\tFILTER => undef,\n+\t\t\tIS_REVERSE => 1,\n+\t\t},\n+\t\tnothead => {\n+\t\t\tDIFF => 'diff-index -R -p',\n+\t\t\tAPPLY => sub { apply_patch_for_checkout_commit '', @_ },\n+\t\t\tAPPLY_CHECK => 'apply',\n+\t\t\tVERB => 'Apply',\n+\t\t\tTARGET => ' to index and worktree',\n+\t\t\tPARTICIPLE => 'applying',\n+\t\t\tFILTER => undef,\n+\t\t\tIS_REVERSE => 0,\n+\t\t},\n \t},\n );\n \n@@ -1546,45 +1558,45 @@ EOF\n \n sub process_args {\n \treturn unless @ARGV;\n-\tmy $arg = shift @ARGV;\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+\n+\twhile (@ARGV) {\n+\t\tif ($ARGV[0] =~ /--patch(?:=(.*))?/) {\n+\t\t\t$patch_mode = defined $1 ? $1 : 'stage';\n+\t\t}\n+\t\telse {\n+\t\t\tlast;\n+\t\t}\n+\t\tshift @ARGV;\n+\t}\n+\n+\tif (@ARGV && $ARGV[0] ne '--') {\n+\t\t$patch_mode_revision = shift @ARGV;\n+\t}\n+\t@ARGV or die \"missing --\";\n+\tshift @ARGV;\n+\n+\tif (defined $patch_mode) {\n+\t\tmy $mode = $patch_modes{$patch_mode}\n+\t\t\tor die \"unknown --patch mode: $patch_mode\";\n+\n+\t\tmy $submode;\n+\t\tif (!defined $patch_mode_revision) {\n+\t\t\t$submode = $mode->{default};\n+\t\t\tif ($submode eq '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 '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' 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\";\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+\t\telsif ($patch_mode_revision eq 'HEAD') {\n+\t\t\t$submode = 'head';\n+\t\t}\n+\t\telse {\n+\t\t\t$submode = 'nothead';\n+\t\t}\n+\n+\t\tif (!exists $mode->{$submode}) {\n+\t\t\tdie \"mode '$patch_mode' cannot handle '$submode'\";\n+\t\t}\n+\t\t%patch_mode_flavour = %{$mode->{$submode}};\n \t}\n }\n \n-- \n1.7.5.4.31.ge4d5e\n"},{"id":"172133","messageId":"20110727080534.GB14002@sigill.intra.peff.net","threadId":"27905","inReplyTo":"20110727080303.GA8105@sigill.intra.peff.net","subject":"[PATCH 2/5] add--interactive: factor out regex error handling","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-27T08:05:34Z","receivedAt":"2011-07-27T08:05:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"When perl complains about compiling a regex the user has\ngiven us, we trim the error to get a nicer message. It's\nonly one line of code, but it's ugly and non-obvious, so\nlet's factor it into a reusable function.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n git-add--interactive.perl |   11 ++++++++---\n 1 files changed, 8 insertions(+), 3 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex c5cd300..3e4c8a4 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -1267,6 +1267,12 @@ sub display_hunks {\n \treturn $i;\n }\n \n+sub trim_error {\n+\tlocal $_ = shift;\n+\ts/ at .*git-add--interactive line \\d+, <STDIN> line \\d+.*$//;\n+\treturn $_;\n+}\n+\n sub patch_update_file {\n \tmy $quit = 0;\n \tmy ($ix, $num);\n@@ -1419,9 +1425,8 @@ sub patch_update_file {\n \t\t\t\t\t$search_string = qr{$regex}m;\n \t\t\t\t};\n \t\t\t\tif ($@) {\n-\t\t\t\t\tmy ($err,$exp) = ($@, $1);\n-\t\t\t\t\t$err =~ s/ at .*git-add--interactive line \\d+, <STDIN> line \\d+.*$//;\n-\t\t\t\t\terror_msg \"Malformed search regexp $exp: $err\\n\";\n+\t\t\t\t\tmy $exp = $1;\n+\t\t\t\t\terror_msg \"Malformed search regexp $exp: \" . trim_error($@) . \"\\n\";\n \t\t\t\t\tnext;\n \t\t\t\t}\n \t\t\t\tmy $iy = $ix;\n-- \n1.7.5.4.31.ge4d5e\n"},{"id":"172134","messageId":"20110727080548.GC14002@sigill.intra.peff.net","threadId":"27905","inReplyTo":"20110727080303.GA8105@sigill.intra.peff.net","subject":"[PATCH 3/5] add--interactive: allow hunk filtering on command line","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-27T08:05:49Z","receivedAt":"2011-07-27T08:05:49Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"If you have a lot of uninteresting hunks (e.g., because you\nhave a file with boilerplate that changed, but you want to\nadd only the interesting bits and discard the rest), it can\nbe cumbersome to sift through the boring hunks.\n\nThis patch provides an option to match hunks by regular\nexpression, pretending as if the non-matching changes do not\nexist at all (similar to how path-limiting ignores\nnon-matching paths entirely).\n\nThere are two special case pseudo-hunks to consider in the\ncode: mode hunks and deletion hunks.\n\n  - If a filter is given, this patch will never include mode\n    hunks, since the user has asked for a specific subset of\n    hunks, and the mode has no text to match.\n\n  - Deletion hunks actually require no special care. In a\n    deletion there will be only a single giant diff hunk of\n    deleted content. If it matches the filter, then the\n    deletion can be presented as normal. If it doesn't\n    match, then we exit early from patch_update_file and\n    never consider showing the deletion at all (since there\n    is nothing to delete).\n\nThis patch provides only the perl portion; it is up to\nindividual callers of add--interactive to pass the option\nthrough to the perl code.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n git-add--interactive.perl |   24 +++++++++++++++++++++++-\n 1 files changed, 23 insertions(+), 1 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 3e4c8a4..bb16f71 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -86,6 +86,7 @@ sub colored {\n # command line options\n my $patch_mode;\n my $patch_mode_revision;\n+my @patch_mode_hunk_filter;\n \n sub apply_patch;\n sub apply_patch_for_checkout_commit;\n@@ -1273,17 +1274,33 @@ sub trim_error {\n \treturn $_;\n }\n \n+sub want_hunk {\n+\tmy $hunk = shift;\n+\tmy $text = join('', @{$hunk->{TEXT}});\n+\n+\tforeach my $re (@patch_mode_hunk_filter) {\n+\t\treturn 1 if $text =~ $re;\n+\t}\n+\treturn 0;\n+}\n+\n sub patch_update_file {\n \tmy $quit = 0;\n \tmy ($ix, $num);\n \tmy $path = shift;\n \tmy ($head, @hunk) = parse_diff($path);\n+\n+\tif (@patch_mode_hunk_filter) {\n+\t\t@hunk = grep { want_hunk($_) } @hunk;\n+\t\treturn unless @hunk;\n+\t}\n+\n \t($head, my $mode, my $deletion) = parse_diff_header($head);\n \tfor (@{$head->{DISPLAY}}) {\n \t\tprint;\n \t}\n \n-\tif (@{$mode->{TEXT}}) {\n+\tif (@{$mode->{TEXT}} && !@patch_mode_hunk_filter) {\n \t\tunshift @hunk, $mode;\n \t}\n \tif (@{$deletion->{TEXT}}) {\n@@ -1568,6 +1585,11 @@ sub process_args {\n \t\tif ($ARGV[0] =~ /--patch(?:=(.*))?/) {\n \t\t\t$patch_mode = defined $1 ? $1 : 'stage';\n \t\t}\n+\t\telsif ($ARGV[0] =~ /--hunk-filter=(.*)/) {\n+\t\t\tmy $re = eval { qr{$1}m }\n+\t\t\t\tor die \"malformed hunk filter $1: \" . trim_error($@);\n+\t\t\tpush @patch_mode_hunk_filter, $re;\n+\t\t}\n \t\telse {\n \t\t\tlast;\n \t\t}\n-- \n1.7.5.4.31.ge4d5e\n"},{"id":"172135","messageId":"20110727080556.GD14002@sigill.intra.peff.net","threadId":"27905","inReplyTo":"20110727080303.GA8105@sigill.intra.peff.net","subject":"[PATCH 4/5] add--interactive: allow negatation of hunk filters","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-27T08:05:56Z","receivedAt":"2011-07-27T08:05:56Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This patch lets you filter out certain boring hunks, rather\nthan trying to match particular interesting ones (which may\nbe much easier, depending on your file contents).\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n git-add--interactive.perl |    8 ++++++--\n 1 files changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex bb16f71..917b2a9 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -87,6 +87,7 @@ sub colored {\n my $patch_mode;\n my $patch_mode_revision;\n my @patch_mode_hunk_filter;\n+my $patch_mode_negate_filter;\n \n sub apply_patch;\n sub apply_patch_for_checkout_commit;\n@@ -1279,9 +1280,9 @@ sub want_hunk {\n \tmy $text = join('', @{$hunk->{TEXT}});\n \n \tforeach my $re (@patch_mode_hunk_filter) {\n-\t\treturn 1 if $text =~ $re;\n+\t\treturn !$patch_mode_negate_filter if $text =~ $re;\n \t}\n-\treturn 0;\n+\treturn $patch_mode_negate_filter;\n }\n \n sub patch_update_file {\n@@ -1590,6 +1591,9 @@ sub process_args {\n \t\t\t\tor die \"malformed hunk filter $1: \" . trim_error($@);\n \t\t\tpush @patch_mode_hunk_filter, $re;\n \t\t}\n+\t\telsif ($ARGV[0] eq '--negate-hunk-filter') {\n+\t\t\t$patch_mode_negate_filter = 1;\n+\t\t}\n \t\telse {\n \t\t\tlast;\n \t\t}\n-- \n1.7.5.4.31.ge4d5e\n"},{"id":"172136","messageId":"20110727080601.GE14002@sigill.intra.peff.net","threadId":"27905","inReplyTo":"20110727080303.GA8105@sigill.intra.peff.net","subject":"[PATCH 5/5] add--interactive: add option to autosplit hunks","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-27T08:06:01Z","receivedAt":"2011-07-27T08:06:01Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"It is sometimes useful to see changes in their smallest\npossible form, rather than complete hunks with overlapping\ncontext. You can do this manually, of course, with the\n's'plit hunk command. But when filtering hunks, you may want\nto pre-split to give your filtering a finer granularity.\n\nThis patch adds only the perl infrastructure; to expose this\nto the user, the various callers of add--interactive need to\npass the new option through to the perl script.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n git-add--interactive.perl |    8 ++++++++\n 1 files changed, 8 insertions(+), 0 deletions(-)\n\ndiff --git a/git-add--interactive.perl b/git-add--interactive.perl\nindex 917b2a9..41c7c6f 100755\n--- a/git-add--interactive.perl\n+++ b/git-add--interactive.perl\n@@ -88,6 +88,7 @@ my $patch_mode;\n my $patch_mode_revision;\n my @patch_mode_hunk_filter;\n my $patch_mode_negate_filter;\n+my $patch_mode_autosplit;\n \n sub apply_patch;\n sub apply_patch_for_checkout_commit;\n@@ -1291,6 +1292,10 @@ sub patch_update_file {\n \tmy $path = shift;\n \tmy ($head, @hunk) = parse_diff($path);\n \n+\tif ($patch_mode_autosplit) {\n+\t\t@hunk = map { split_hunk($_->{TEXT}, $_->{DISPLAY}) } @hunk;\n+\t}\n+\n \tif (@patch_mode_hunk_filter) {\n \t\t@hunk = grep { want_hunk($_) } @hunk;\n \t\treturn unless @hunk;\n@@ -1594,6 +1599,9 @@ sub process_args {\n \t\telsif ($ARGV[0] eq '--negate-hunk-filter') {\n \t\t\t$patch_mode_negate_filter = 1;\n \t\t}\n+\t\telsif ($ARGV[0] eq '--split-hunks') {\n+\t\t\t$patch_mode_autosplit = 1;\n+\t\t}\n \t\telse {\n \t\t\tlast;\n \t\t}\n-- \n1.7.5.4.31.ge4d5e\n"},{"id":"172137","messageId":"20110727081034.GB8105@sigill.intra.peff.net","threadId":"27905","inReplyTo":"CACsJy8C822Fvwav4Wpw4e-12ZY20XM1s2v4KymZkaDYLxkMHvw@mail.gmail.com","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-27T08:10:34Z","receivedAt":"2011-07-27T08:10:34Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 26, 2011 at 08:03:01PM +0700, Nguyen Thai Ngoc Duy wrote:\n\n> It would be even more cool if --hunks (or whatever the name will be)\n> could work without -p. I mean, if \"git diff\" supports it, then I can\n> fine tune my regex to meet a selection of hunks I want, and verify it\n> really is what I want. Then \"git add --hunks=magic\" and voila! (The\n> \"git add --hunks\" without -p surely can be workaround by adding \"-p\",\n> then accept all hunks).\n\nYeah, doing \"yes | git add -p --hunks=foo\" would probably do what you\nwant. But you don't really have a good way of verifying what it will add\n(you could check after the fact what's left in \"git diff\", or what's now\nin \"git diff --cached\", of course).\n\n> And if diff machinery learns this, we would have \"git log --hunks\" too.\n> \n> OK I'm asking too much..\n\nProbably. :)\n\nI'm not sure how useful \"log --hunks\" would be. The changes you commit\ndon't tend to be that big (well, not if you're doing it right). It seems\nmuch more likely to have the case you brought up, which is that some\nfile has a bunch of boring boilerplate that doesn't need to be\nchanged, and you need to pick out the interesting changes from the\nboilerplate changes.\n\nI suppose if somebody committed all of the boilerplate changes (like .po\ncomment changes), then you would want to be able to pick them apart. But\nthat just seems like the wrong thing (i.e., if those comments really are\nuninteresting, they should not be committed). But I don't work with .po\nfiles at all, so maybe there is a good reason to commit them.\n\n-Peff\n"},{"id":"172144","messageId":"CACsJy8ByDUByZiX43_+4_49uB+h0AMCk9J+35kZJSKgBn2KZbA@mail.gmail.com","threadId":"27905","inReplyTo":"20110727081034.GB8105@sigill.intra.peff.net","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-07-27T09:02:48Z","receivedAt":"2011-07-27T09:02:48Z","isPatch":false,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Jul 27, 2011 at 3:10 PM, Jeff King <peff@peff.net> wrote:\n> I'm not sure how useful \"log --hunks\" would be. The changes you commit\n> don't tend to be that big (well, not if you're doing it right). It seems\n> much more likely to have the case you brought up, which is that some\n> file has a bunch of boring boilerplate that doesn't need to be\n> changed, and you need to pick out the interesting changes from the\n> boilerplate changes.\n>\n> I suppose if somebody committed all of the boilerplate changes (like .po\n> comment changes), then you would want to be able to pick them apart. But\n> that just seems like the wrong thing (i.e., if those comments really are\n> uninteresting, they should not be committed). But I don't work with .po\n> files at all, so maybe there is a good reason to commit them.\n\nI was thinking of it as an extension of \"git log -Sregex\", where as -S\nshows full diff of matched files, the new option only shows hunks that\nactually match. Not sure if that is really useful though. On the other\nhand, \"git diff --hunk\" is useful for me, I'll see if I can add that\noption.\n-- \nDuy\n"},{"id":"172176","messageId":"20110727172408.GB16031@sigill.intra.peff.net","threadId":"27905","inReplyTo":"CACsJy8ByDUByZiX43_+4_49uB+h0AMCk9J+35kZJSKgBn2KZbA@mail.gmail.com","subject":"Re: feature request: git add--interactive --patch on regex-matched hunks only","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-27T17:24:09Z","receivedAt":"2011-07-27T17:24:09Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jul 27, 2011 at 04:02:48PM +0700, Nguyen Thai Ngoc Duy wrote:\n\n> I was thinking of it as an extension of \"git log -Sregex\", where as -S\n> shows full diff of matched files, the new option only shows hunks that\n> actually match. Not sure if that is really useful though. On the other\n> hand, \"git diff --hunk\" is useful for me, I'll see if I can add that\n> option.\n\nI think it is more like \"log -Gregex\", which actually greps within the\ndiff. It would not be too hard to adapt the \"-G\" code to return the set\nof hunks, rather than a simple \"yes we have it\". If you did that, it\nwould make the perl bits way simpler: they would simply call \"diff\n-Gregex\" when creating the patch.\n\nI don't think \"git diff --hunk\" would be that hard, either. The\ntrickiest part is that the diff code tries to output lines as soon as we\nget them. You'd have to buffer whole chunks.\n\n-Peff\n"}]}