diff --git a/builtin/branch.c b/builtin/branch.c index 7a1d1eeb07..81b5c111cb 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -77,12 +77,11 @@ define_list_config_array(color_branch_slots); static int git_branch_config(const char *var, const char *value, void *cb) { const char *slot_name; - struct ref_sorting **sorting_tail = (struct ref_sorting **)cb; if (!strcmp(var, "branch.sort")) { if (!value) return config_error_nonbool(var); - parse_ref_sorting(sorting_tail, value); + string_list_append(cb, value); return 0; } @@ -625,7 +624,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) enum branch_track track; struct ref_filter filter; int icase = 0; - static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + static struct ref_sorting *sorting; + struct string_list sorting_options = STRING_LIST_INIT_DUP; struct ref_format format = REF_FORMAT_INIT; struct option options[] = { @@ -666,7 +666,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_MERGED(&filter, N_("print only branches that are merged")), OPT_NO_MERGED(&filter, N_("print only branches that are not merged")), OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), - OPT_REF_SORT(sorting_tail), + OPT_REF_SORT(&sorting_options), OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"), N_("print only branches of the object"), parse_opt_object_name), OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), @@ -683,7 +683,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_branch_usage, options); - git_config(git_branch_config, sorting_tail); + git_config(git_branch_config, &sorting_options); track = git_branch_track; @@ -749,8 +749,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) * local branches 'refs/heads/...' and finally remote-tracking * branches 'refs/remotes/...'. */ - if (!sorting) - sorting = ref_default_sorting(); + sorting = ref_sorting_options(&sorting_options); ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); ref_sorting_set_sort_flags_all( sorting, REF_SORTING_DETACHED_HEAD_FIRST, 1); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 16a2c7d57c..6f62f40d12 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -17,7 +17,8 @@ static char const * const for_each_ref_usage[] = { int cmd_for_each_ref(int argc, const char **argv, const char *prefix) { int i; - struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + struct ref_sorting *sorting; + struct string_list sorting_options = STRING_LIST_INIT_DUP; int maxcount = 0, icase = 0; struct ref_array array; struct ref_filter filter; @@ -39,7 +40,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_INTEGER( 0 , "count", &maxcount, N_("show only matched refs")), OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT__COLOR(&format.use_color, N_("respect format colors")), - OPT_REF_SORT(sorting_tail), + OPT_REF_SORT(&sorting_options), OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"), N_("print only refs which points at the given object"), parse_opt_object_name), @@ -70,8 +71,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (verify_ref_format(&format)) usage_with_options(for_each_ref_usage, opts); - if (!sorting) - sorting = ref_default_sorting(); + sorting = ref_sorting_options(&sorting_options); ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 318949c3d7..44448fa61d 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -54,7 +54,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) struct transport *transport; const struct ref *ref; struct ref_array ref_array; - static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + struct string_list sorting_options = STRING_LIST_INIT_DUP; struct option options[] = { OPT__QUIET(&quiet, N_("do not print remote URL")), @@ -68,7 +68,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) OPT_BIT(0, "refs", &flags, N_("do not show peeled tags"), REF_NORMAL), OPT_BOOL(0, "get-url", &get_url, N_("take url..insteadOf into account")), - OPT_REF_SORT(sorting_tail), + OPT_REF_SORT(&sorting_options), OPT_SET_INT_F(0, "exit-code", &status, N_("exit with exit code 2 if no matching refs are found"), 2, PARSE_OPT_NOCOMPLETE), @@ -86,8 +86,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) packet_trace_identity("ls-remote"); - UNLEAK(sorting); - if (argc > 1) { int i; CALLOC_ARRAY(pattern, argc); @@ -139,8 +137,13 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) item->symref = xstrdup_or_null(ref->symref); } - if (sorting) + if (sorting_options.nr) { + struct ref_sorting *sorting; + + sorting = ref_sorting_options(&sorting_options); ref_array_sort(sorting, &ref_array); + ref_sorting_release(sorting); + } for (i = 0; i < ref_array.nr; i++) { const struct ref_array_item *ref = ref_array.items[i]; diff --git a/builtin/tag.c b/builtin/tag.c index 6fe646710d..41863c5ab7 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -178,7 +178,6 @@ static const char tag_template_nocleanup[] = static int git_tag_config(const char *var, const char *value, void *cb) { int status; - struct ref_sorting **sorting_tail = (struct ref_sorting **)cb; if (!strcmp(var, "tag.gpgsign")) { config_sign_tag = git_config_bool(var, value); @@ -188,7 +187,7 @@ static int git_tag_config(const char *var, const char *value, void *cb) if (!strcmp(var, "tag.sort")) { if (!value) return config_error_nonbool(var); - parse_ref_sorting(sorting_tail, value); + string_list_append(cb, value); return 0; } @@ -436,7 +435,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; struct ref_filter filter; - static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + struct ref_sorting *sorting; + struct string_list sorting_options = STRING_LIST_INIT_DUP; struct ref_format format = REF_FORMAT_INIT; int icase = 0; int edit_flag = 0; @@ -470,7 +470,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")), OPT_MERGED(&filter, N_("print only tags that are merged")), OPT_NO_MERGED(&filter, N_("print only tags that are not merged")), - OPT_REF_SORT(sorting_tail), + OPT_REF_SORT(&sorting_options), { OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT, @@ -486,7 +486,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) setup_ref_filter_porcelain_msg(); - git_config(git_tag_config, sorting_tail); + git_config(git_tag_config, &sorting_options); memset(&opt, 0, sizeof(opt)); memset(&filter, 0, sizeof(filter)); @@ -525,8 +525,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die(_("--column and -n are incompatible")); colopts = 0; } - if (!sorting) - sorting = ref_default_sorting(); + sorting = ref_sorting_options(&sorting_options); ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; if (cmdmode == 'l') { diff --git a/ref-filter.c b/ref-filter.c index 282cdad103..afed03472f 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2470,6 +2470,12 @@ static int memcasecmp(const void *vs1, const void *vs2, size_t n) return 0; } +struct ref_sorting { + struct ref_sorting *next; + int atom; /* index into used_atom array (internal) */ + enum ref_sorting_order sort_flags; +}; + static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b) { struct atom_value *va, *vb; @@ -2663,7 +2669,7 @@ static int parse_sorting_atom(const char *atom) } /* If no sorting option is given, use refname to sort as default */ -struct ref_sorting *ref_default_sorting(void) +static struct ref_sorting *ref_default_sorting(void) { static const char cstr_name[] = "refname"; @@ -2674,7 +2680,7 @@ struct ref_sorting *ref_default_sorting(void) return sorting; } -void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *arg) +static void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *arg) { struct ref_sorting *s; @@ -2692,17 +2698,25 @@ void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *arg) s->atom = parse_sorting_atom(arg); } -int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) +struct ref_sorting *ref_sorting_options(struct string_list *options) { - /* - * NEEDSWORK: We should probably clear the list in this case, but we've - * already munged the global used_atoms list, which would need to be - * undone. - */ - BUG_ON_OPT_NEG(unset); + struct string_list_item *item; + struct ref_sorting *sorting = NULL, **tail = &sorting; - parse_ref_sorting(opt->value, arg); - return 0; + if (!options->nr) { + sorting = ref_default_sorting(); + } else { + for_each_string_list_item(item, options) + parse_ref_sorting(tail, item->string); + } + + /* + * From here on, the ref_sorting list should be used to talk + * about the sort order used for the output. The caller + * should not touch the string form anymore. + */ + string_list_clear(options, 0); + return sorting; } void ref_sorting_release(struct ref_sorting *sorting) diff --git a/ref-filter.h b/ref-filter.h index 6228458d30..aa0eea4ecf 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -23,16 +23,13 @@ #define FILTER_REFS_KIND_MASK (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD) struct atom_value; +struct ref_sorting; -struct ref_sorting { - struct ref_sorting *next; - int atom; /* index into used_atom array (internal) */ - enum { - REF_SORTING_REVERSE = 1<<0, - REF_SORTING_ICASE = 1<<1, - REF_SORTING_VERSION = 1<<2, - REF_SORTING_DETACHED_HEAD_FIRST = 1<<3, - } sort_flags; +enum ref_sorting_order { + REF_SORTING_REVERSE = 1<<0, + REF_SORTING_ICASE = 1<<1, + REF_SORTING_VERSION = 1<<2, + REF_SORTING_DETACHED_HEAD_FIRST = 1<<3, }; struct ref_array_item { @@ -97,9 +94,8 @@ struct ref_format { #define OPT_NO_MERGED(f, h) _OPT_MERGED_NO_MERGED("no-merged", f, h) #define OPT_REF_SORT(var) \ - OPT_CALLBACK_F(0, "sort", (var), \ - N_("key"), N_("field name to sort on"), \ - PARSE_OPT_NONEG, parse_opt_ref_sorting) + OPT_STRING_LIST(0, "sort", (var), \ + N_("key"), N_("field name to sort on")) /* * API for filtering a set of refs. Based on the type of refs the user @@ -121,14 +117,10 @@ int format_ref_array_item(struct ref_array_item *info, struct ref_format *format, struct strbuf *final_buf, struct strbuf *error_buf); -/* Parse a single sort specifier and add it to the list */ -void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *atom); -/* Callback function for parsing the sort option */ -int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset); -/* Default sort option based on refname */ -struct ref_sorting *ref_default_sorting(void); /* Release a "struct ref_sorting" */ void ref_sorting_release(struct ref_sorting *); +/* Convert list of sort options into ref_sorting */ +struct ref_sorting *ref_sorting_options(struct string_list *); /* Function to parse --merged and --no-merged options */ int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset); /* Get the current HEAD's description */ diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index e575ffb4ff..0e23d18807 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -1418,7 +1418,17 @@ test_expect_success 'invalid sort parameter in configuration' ' ( cd sort && git config branch.sort "v:notvalid" && - test_must_fail git branch + + # this works in the "listing" mode, so bad sort key + # is a dying offence. + test_must_fail git branch && + + # these do not need to use sorting, and should all + # succeed + git branch newone main && + git branch -c newone newerone && + git branch -m newone newestone && + git branch -d newerone newestone ) ' diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 80679d5e12..9f2c706c12 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -419,6 +419,11 @@ test_expect_success 'Verify descending sort' ' test_cmp expected actual ' +test_expect_success 'Give help even with invalid sort atoms' ' + test_expect_code 129 git for-each-ref --sort=bogus -h >actual 2>&1 && + grep "^usage: git for-each-ref" actual +' + cat >expected <<\EOF refs/tags/testtag refs/tags/testtag-2 @@ -1019,6 +1024,27 @@ test_expect_success 'equivalent sorts fall back on refname' ' test_cmp expected actual ' +test_expect_success '--no-sort cancels the previous sort keys' ' + cat >expected <<-\EOF && + 100000 refs/tags/multi-ref1-100000-user1 + 100000 refs/tags/multi-ref1-100000-user2 + 100000 refs/tags/multi-ref2-100000-user1 + 100000 refs/tags/multi-ref2-100000-user2 + 200000 refs/tags/multi-ref1-200000-user1 + 200000 refs/tags/multi-ref1-200000-user2 + 200000 refs/tags/multi-ref2-200000-user1 + 200000 refs/tags/multi-ref2-200000-user2 + EOF + git for-each-ref \ + --format="%(taggerdate:unix) %(taggeremail) %(refname)" \ + --sort=-refname \ + --sort=taggeremail \ + --no-sort \ + --sort=taggerdate \ + "refs/tags/multi-*" >actual && + test_cmp expected actual +' + test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' ' test_when_finished "git checkout main" && git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&