Git fork

revision: add wrapper to setup_revisions() from a strvec

The setup_revisions() function was designed to take the argc/argv pair
from the operating system. But we sometimes construct our own argv using
a strvec and pass that in. There are a few gotchas that callers need to
deal with here:

1. You should always pass the free_removed_argv_elements option via
setup_revision_opt. Otherwise, entries may be leaked if
setup_revisions() re-shuffles options.

2. After setup_revisions() returns, the strvec state is odd. We get a
reduced argc from setup_revisions() telling us how many unknown
options were left in place. Entries after that in argv may be
retained, or may be NULL (depending on how the reshuffling
happened). But the strvec's "nr" field still represents the
original value, and some of the entries it thinks it is still
storing may be NULL. Callers must be careful with how they access
it.

Some callers deal with (1), but not all. In practice they are OK because
they do not pass any options that would cause setup_revisions() to
re-shuffle (namely unknown options which may be relayed from the user,
and the use of the "--" separator). But it's probably a good idea to
consistently pass this option anyway to future-proof ourselves against
the details of setup_revisions() changing.

No callers address (2), though I don't think there any visible bugs.
Most of them simply call strvec_clear() and never otherwise look at the
result. And in fact, if they naively set foo.nr to the argc returned by
setup_revisions(), that would cause leaks! Because setup_revisions()
does not free consumed options[1], we have to leave the "nr" field of
the strvec at its original value to find and free them during
strvec_clear().

So I don't think there are any bugs to fix here, but we can make things
safer and simpler for callers. Let's introduce a helper function that
sets the free_removed_argv_elements automatically and shrinks the strvec
to represent the retained options afterwards (taking care to free the
now-obsolete entries).

We'll start by converting all of the call-sites which use the
free_removed_argv_elements option. There should be no behavior change
for them, except that their "shrunken" entries are cleaned up
immediately, rather than waiting for a strvec_clear() call.

[1] Arguably setup_revisions() should be doing this step for us if we
told it to free removed options, but there are many existing callers
which will be broken if it did. Introducing this helper is a
possible first step towards that.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Jeff King and committed by
Junio C Hamano
f93c1d86 cd439487

+27 -19
+1 -4
bisect.c
··· 674 const char *bad_format, const char *good_format, 675 int read_paths) 676 { 677 - struct setup_revision_opt opt = { 678 - .free_removed_argv_elements = 1, 679 - }; 680 int i; 681 682 repo_init_revisions(r, revs, prefix); ··· 693 if (read_paths) 694 read_bisect_paths(rev_argv); 695 696 - setup_revisions(rev_argv->nr, rev_argv->v, revs, &opt); 697 } 698 699 static void bisect_common(struct rev_info *revs)
··· 674 const char *bad_format, const char *good_format, 675 int read_paths) 676 { 677 int i; 678 679 repo_init_revisions(r, revs, prefix); ··· 690 if (read_paths) 691 read_bisect_paths(rev_argv); 692 693 + setup_revisions_from_strvec(rev_argv, revs, NULL); 694 } 695 696 static void bisect_common(struct rev_info *revs)
+2 -3
builtin/stash.c
··· 956 static int show_stash(int argc, const char **argv, const char *prefix, 957 struct repository *repo UNUSED) 958 { 959 - struct setup_revision_opt opt = { .free_removed_argv_elements = 1 }; 960 int i; 961 int ret = -1; 962 struct stash_info info = STASH_INFO_INIT; ··· 1015 } 1016 } 1017 1018 - argc = setup_revisions(revision_args.nr, revision_args.v, &rev, &opt); 1019 - if (argc > 1) 1020 goto usage; 1021 if (!rev.diffopt.output_format) { 1022 rev.diffopt.output_format = DIFF_FORMAT_PATCH;
··· 956 static int show_stash(int argc, const char **argv, const char *prefix, 957 struct repository *repo UNUSED) 958 { 959 int i; 960 int ret = -1; 961 struct stash_info info = STASH_INFO_INIT; ··· 1014 } 1015 } 1016 1017 + setup_revisions_from_strvec(&revision_args, &rev, NULL); 1018 + if (revision_args.nr > 1) 1019 goto usage; 1020 if (!rev.diffopt.output_format) { 1021 rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+2 -8
builtin/submodule--helper.c
··· 616 struct rev_info rev = REV_INFO_INIT; 617 struct strbuf buf = STRBUF_INIT; 618 const char *git_dir; 619 - struct setup_revision_opt opt = { 620 - .free_removed_argv_elements = 1, 621 - }; 622 623 if (validate_submodule_path(path) < 0) 624 die(NULL); ··· 655 656 repo_init_revisions(the_repository, &rev, NULL); 657 rev.abbrev = 0; 658 - setup_revisions(diff_files_args.nr, diff_files_args.v, &rev, &opt); 659 run_diff_files(&rev, 0); 660 661 if (!diff_result_code(&rev)) { ··· 1094 { 1095 struct strvec diff_args = STRVEC_INIT; 1096 struct rev_info rev; 1097 - struct setup_revision_opt opt = { 1098 - .free_removed_argv_elements = 1, 1099 - }; 1100 struct module_cb_list list = MODULE_CB_LIST_INIT; 1101 int ret = 0; 1102 ··· 1114 repo_init_revisions(the_repository, &rev, info->prefix); 1115 rev.abbrev = 0; 1116 precompose_argv_prefix(diff_args.nr, diff_args.v, NULL); 1117 - setup_revisions(diff_args.nr, diff_args.v, &rev, &opt); 1118 rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK; 1119 rev.diffopt.format_callback = submodule_summary_callback; 1120 rev.diffopt.format_callback_data = &list;
··· 616 struct rev_info rev = REV_INFO_INIT; 617 struct strbuf buf = STRBUF_INIT; 618 const char *git_dir; 619 620 if (validate_submodule_path(path) < 0) 621 die(NULL); ··· 652 653 repo_init_revisions(the_repository, &rev, NULL); 654 rev.abbrev = 0; 655 + setup_revisions_from_strvec(&diff_files_args, &rev, NULL); 656 run_diff_files(&rev, 0); 657 658 if (!diff_result_code(&rev)) { ··· 1091 { 1092 struct strvec diff_args = STRVEC_INIT; 1093 struct rev_info rev; 1094 struct module_cb_list list = MODULE_CB_LIST_INIT; 1095 int ret = 0; 1096 ··· 1108 repo_init_revisions(the_repository, &rev, info->prefix); 1109 rev.abbrev = 0; 1110 precompose_argv_prefix(diff_args.nr, diff_args.v, NULL); 1111 + setup_revisions_from_strvec(&diff_args, &rev, NULL); 1112 rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK; 1113 rev.diffopt.format_callback = submodule_summary_callback; 1114 rev.diffopt.format_callback_data = &list;
+1 -4
remote.c
··· 2137 struct object_id oid; 2138 struct commit *ours, *theirs; 2139 struct rev_info revs; 2140 - struct setup_revision_opt opt = { 2141 - .free_removed_argv_elements = 1, 2142 - }; 2143 struct strvec argv = STRVEC_INIT; 2144 2145 /* Cannot stat if what we used to build on no longer exists */ ··· 2174 strvec_push(&argv, "--"); 2175 2176 repo_init_revisions(the_repository, &revs, NULL); 2177 - setup_revisions(argv.nr, argv.v, &revs, &opt); 2178 if (prepare_revision_walk(&revs)) 2179 die(_("revision walk setup failed")); 2180
··· 2137 struct object_id oid; 2138 struct commit *ours, *theirs; 2139 struct rev_info revs; 2140 struct strvec argv = STRVEC_INIT; 2141 2142 /* Cannot stat if what we used to build on no longer exists */ ··· 2171 strvec_push(&argv, "--"); 2172 2173 repo_init_revisions(the_repository, &revs, NULL); 2174 + setup_revisions_from_strvec(&argv, &revs, NULL); 2175 if (prepare_revision_walk(&revs)) 2176 die(_("revision walk setup failed")); 2177
+19
revision.c
··· 3178 return left; 3179 } 3180 3181 static void release_revisions_cmdline(struct rev_cmdline_info *cmdline) 3182 { 3183 unsigned int i;
··· 3178 return left; 3179 } 3180 3181 + void setup_revisions_from_strvec(struct strvec *argv, struct rev_info *revs, 3182 + struct setup_revision_opt *opt) 3183 + { 3184 + struct setup_revision_opt fallback_opt; 3185 + int ret; 3186 + 3187 + if (!opt) { 3188 + memset(&fallback_opt, 0, sizeof(fallback_opt)); 3189 + opt = &fallback_opt; 3190 + } 3191 + opt->free_removed_argv_elements = 1; 3192 + 3193 + ret = setup_revisions(argv->nr, argv->v, revs, opt); 3194 + 3195 + for (size_t i = ret; i < argv->nr; i++) 3196 + free((char *)argv->v[i]); 3197 + argv->nr = ret; 3198 + } 3199 + 3200 static void release_revisions_cmdline(struct rev_cmdline_info *cmdline) 3201 { 3202 unsigned int i;
+2
revision.h
··· 441 }; 442 int setup_revisions(int argc, const char **argv, struct rev_info *revs, 443 struct setup_revision_opt *); 444 445 /** 446 * Free data allocated in a "struct rev_info" after it's been
··· 441 }; 442 int setup_revisions(int argc, const char **argv, struct rev_info *revs, 443 struct setup_revision_opt *); 444 + void setup_revisions_from_strvec(struct strvec *argv, struct rev_info *revs, 445 + struct setup_revision_opt *); 446 447 /** 448 * Free data allocated in a "struct rev_info" after it's been