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