branch: sort detached HEAD based on a flag

Change the ref-filter sorting of detached HEAD to check the FILTER_REFS_DETACHED_HEAD flag, instead of relying on the ref description filled-in by get_head_description() to start with "(", which in turn we expect to ASCII-sort before any other reference. For context, we'd like the detached line to appear first at the start of "git branch -l", e.g.: $ git branch -l * (HEAD detached at <hash>) master This doesn't change that, but improves on a fix made in 28438e84e04 (ref-filter: sort detached HEAD lines firstly, 2019-06-18) and gives the Chinese translation the ability to use its preferred punctuation marks again. In Chinese the fullwidth versions of punctuation like "()" are typically written as (U+FF08 fullwidth left parenthesis), (U+FF09 fullwidth right parenthesis) instead[1]. This form is used in both po/zh_{CN,TW}.po in most cases where "()" is translated in a string. Aside from that improvement to the Chinese translation, it also just makes for cleaner code that we mark any special cases in the ref_array we're sorting with flags and make the sort function aware of them, instead of piggy-backing on the general-case of strcmp() doing the right thing. As seen in the amended tests this made reverse sorting a bit more consistent. Before this we'd sometimes sort this message in the middle, now it's consistently at the beginning or end, depending on whether we're doing a normal or reverse sort. Having it at the end doesn't make much sense either, but at least it behaves consistently now. A follow-up commit will make this behavior under reverse sorting even better. I'm removing the "TRANSLATORS" comments that were in the old code while I'm at it. Those were added in d4919bb288e (ref-filter: move get_head_description() from branch.c, 2017-01-10). I think it's obvious from context, string and translation memory in typical translation tools that these are the same or similar string. 1. https://en.wikipedia.org/wiki/Chinese_punctuation#Marks_similar_to_European_punctuation Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed Jan 7, 2021 at 10:51 UTC 2708ce62d2105c4470399a2c839b6d451c9c148f
6 files changed +32 -25
builtin/branch.c
+2
@@ -740,6 +740,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
740 if (!sorting)
741 sorting = ref_default_sorting();
742 ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
743 + ref_sorting_set_sort_flags_all(
744 + sorting, REF_SORTING_DETACHED_HEAD_FIRST, 1);
745 print_ref_list(&filter, sorting, &format);
746 print_columns(&output, colopts, NULL);
747 string_list_clear(&output, 0);
ref-filter.c
+25 -19
@@ -1536,36 +1536,27 @@ char *get_head_description(void)
1536 struct wt_status_state state;
1537 memset(&state, 0, sizeof(state));
1538 wt_status_get_state(the_repository, &state, 1);
1539 -
1540 - /*
1541 - * The ( character must be hard-coded and not part of a localizable
1542 - * string, since the description is used as a sort key and compared
1543 - * with ref names.
1544 - */
1545 - strbuf_addch(&desc, '(');
1539 if (state.rebase_in_progress ||
1540 state.rebase_interactive_in_progress) {
1541 if (state.branch)
1549 - strbuf_addf(&desc, _("no branch, rebasing %s"),
1542 + strbuf_addf(&desc, _("(no branch, rebasing %s)"),
1543 state.branch);
1544 else
1552 - strbuf_addf(&desc, _("no branch, rebasing detached HEAD %s"),
1545 + strbuf_addf(&desc, _("(no branch, rebasing detached HEAD %s)"),
1546 state.detached_from);
1547 } else if (state.bisect_in_progress)
1555 - strbuf_addf(&desc, _("no branch, bisect started on %s"),
1548 + strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
1549 state.branch);
1550 else if (state.detached_from) {
1551 if (state.detached_at)
1559 - strbuf_addstr(&desc, HEAD_DETACHED_AT);
1552 + strbuf_addf(&desc, _("(HEAD detached at %s)"),
1553 + state.detached_from);
1554 else
1561 - strbuf_addstr(&desc, HEAD_DETACHED_FROM);
1562 - strbuf_addstr(&desc, state.detached_from);
1563 - }
1564 - else
1565 - strbuf_addstr(&desc, _("no branch"));
1566 - strbuf_addch(&desc, ')');
1555 + strbuf_addf(&desc, _("(HEAD detached from %s)"),
1556 + state.detached_from);
1557 + } else
1558 + strbuf_addstr(&desc, _("(no branch)"));
1559
1568 - wt_status_state_free_buffers(&state);
1560 return strbuf_detach(&desc, NULL);
1561 }
1562
@@ -2350,6 +2341,18 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
2341 return ret;
2342 }
2343
2344 +static int compare_detached_head(struct ref_array_item *a, struct ref_array_item *b)
2345 +{
2346 + if (!(a->kind ^ b->kind))
2347 + BUG("ref_kind_from_refname() should only mark one ref as HEAD");
2348 + if (a->kind & FILTER_REFS_DETACHED_HEAD)
2349 + return -1;
2350 + else if (b->kind & FILTER_REFS_DETACHED_HEAD)
2351 + return 1;
2352 + BUG("should have died in the xor check above");
2353 + return 0;
2354 +}
2355 +
2356 static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
2357 {
2358 struct atom_value *va, *vb;
@@ -2362,7 +2365,10 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
2365 if (get_ref_atom_value(b, s->atom, &vb, &err))
2366 die("%s", err.buf);
2367 strbuf_release(&err);
2365 - if (s->sort_flags & REF_SORTING_VERSION) {
2368 + if (s->sort_flags & REF_SORTING_DETACHED_HEAD_FIRST &&
2369 + ((a->kind | b->kind) & FILTER_REFS_DETACHED_HEAD)) {
2370 + cmp = compare_detached_head(a, b);
2371 + } else if (s->sort_flags & REF_SORTING_VERSION) {
2372 cmp = versioncmp(va->s, vb->s);
2373 } else if (cmp_type == FIELD_STR) {
2374 int (*cmp_fn)(const char *, const char *);
ref-filter.h
+1
@@ -32,6 +32,7 @@ struct ref_sorting {
32 REF_SORTING_REVERSE = 1<<0,
33 REF_SORTING_ICASE = 1<<1,
34 REF_SORTING_VERSION = 1<<2,
35 + REF_SORTING_DETACHED_HEAD_FIRST = 1<<3,
36 } sort_flags;
37 };
38
t/t3203-branch-output.sh
+2 -2
@@ -223,8 +223,8 @@ test_expect_success 'git branch `--sort=[-]objectsize` option' '
223 cat >expect <<-\EOF &&
224 branch-one
225 main
226 - * (HEAD detached from fromtag)
226 branch-two
227 + * (HEAD detached from fromtag)
228 EOF
229 git branch --sort=-objectsize >actual &&
230 test_i18ncmp expect actual
@@ -241,10 +241,10 @@ test_expect_success 'git branch `--sort=[-]type` option' '
241 test_i18ncmp expect actual &&
242
243 cat >expect <<-\EOF &&
244 - * (HEAD detached from fromtag)
244 branch-one
245 branch-two
246 main
247 + * (HEAD detached from fromtag)
248 EOF
249 git branch --sort=-type >actual &&
250 test_i18ncmp expect actual
wt-status.c
+2 -2
@@ -1742,9 +1742,9 @@ static void wt_longstatus_print(struct wt_status *s)
1742 } else if (s->state.detached_from) {
1743 branch_name = s->state.detached_from;
1744 if (s->state.detached_at)
1745 - on_what = HEAD_DETACHED_AT;
1745 + on_what = _("HEAD detached at ");
1746 else
1747 - on_what = HEAD_DETACHED_FROM;
1747 + on_what = _("HEAD detached from ");
1748 } else {
1749 branch_name = "";
1750 on_what = _("Not currently on any branch.");
wt-status.h
-2
@@ -77,8 +77,6 @@ enum wt_status_format {
77 STATUS_FORMAT_UNSPECIFIED
78 };
79
80 -#define HEAD_DETACHED_AT _("HEAD detached at ")
81 -#define HEAD_DETACHED_FROM _("HEAD detached from ")
80 #define SPARSE_CHECKOUT_DISABLED -1
81
82 struct wt_status_state {