Merge branch 'md/sort-detached-head-first'
authorJunio C Hamano <gitster@pobox.com>
Tue, 9 Jul 2019 22:25:42 +0000 (15:25 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 9 Jul 2019 22:25:42 +0000 (15:25 -0700)
"git branch --list" learned to always output the detached HEAD as
the first item (when the HEAD is detached, of course), regardless
of the locale.

* md/sort-detached-head-first:
ref-filter: sort detached HEAD lines firstly

1  2 
ref-filter.c
wt-status.c
wt-status.h
diff --combined ref-filter.c
index 8fb25c1412b1324c10e2b0a518350488383aa669,1d76690c237e19393a35fba2f580380906e402b1..791f0648a6edc4ecc4629865393703d9b1e4a227
@@@ -20,8 -20,6 +20,8 @@@
  #include "commit-slab.h"
  #include "commit-graph.h"
  #include "commit-reach.h"
 +#include "worktree.h"
 +#include "hashmap.h"
  
  static struct ref_msg {
        const char *gone;
@@@ -77,27 -75,6 +77,27 @@@ static struct expand_data 
        struct object_info info;
  } oi, oi_deref;
  
 +struct ref_to_worktree_entry {
 +      struct hashmap_entry ent; /* must be the first member! */
 +      struct worktree *wt; /* key is wt->head_ref */
 +};
 +
 +static int ref_to_worktree_map_cmpfnc(const void *unused_lookupdata,
 +                                    const void *existing_hashmap_entry_to_test,
 +                                    const void *key,
 +                                    const void *keydata_aka_refname)
 +{
 +      const struct ref_to_worktree_entry *e = existing_hashmap_entry_to_test;
 +      const struct ref_to_worktree_entry *k = key;
 +      return strcmp(e->wt->head_ref,
 +              keydata_aka_refname ? keydata_aka_refname : k->wt->head_ref);
 +}
 +
 +static struct ref_to_worktree_map {
 +      struct hashmap map;
 +      struct worktree **worktrees;
 +} ref_to_worktree_map;
 +
  /*
   * An atom is a valid field atom listed below, possibly prefixed with
   * a "*" to denote deref_tag().
@@@ -503,16 -480,11 +503,16 @@@ static struct 
        { "flag", SOURCE_NONE },
        { "HEAD", SOURCE_NONE, FIELD_STR, head_atom_parser },
        { "color", SOURCE_NONE, FIELD_STR, color_atom_parser },
 +      { "worktreepath", SOURCE_NONE },
        { "align", SOURCE_NONE, FIELD_STR, align_atom_parser },
        { "end", SOURCE_NONE },
        { "if", SOURCE_NONE, FIELD_STR, if_atom_parser },
        { "then", SOURCE_NONE },
        { "else", SOURCE_NONE },
 +      /*
 +       * Please update $__git_ref_fieldlist in git-completion.bash
 +       * when you add new atoms
 +       */
  };
  
  #define REF_FORMATTING_STATE_INIT  { 0, NULL }
@@@ -941,7 -913,7 +941,7 @@@ static void grab_common_values(struct a
  }
  
  /* See grab_values */
 -static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 +static void grab_tag_values(struct atom_value *val, int deref, struct object *obj)
  {
        int i;
        struct tag *tag = (struct tag *) obj;
  }
  
  /* See grab_values */
 -static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 +static void grab_commit_values(struct atom_value *val, int deref, struct object *obj)
  {
        int i;
        struct commit *commit = (struct commit *) obj;
        }
  }
  
 -static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
 +static const char *find_wholine(const char *who, int wholen, const char *buf)
  {
        const char *eol;
        while (*buf) {
@@@ -1092,7 -1064,7 +1092,7 @@@ static void grab_date(const char *buf, 
  }
  
  /* See grab_values */
 -static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 +static void grab_person(const char *who, struct atom_value *val, int deref, void *buf)
  {
        int i;
        int wholen = strlen(who);
                    !starts_with(name + wholen, "date"))
                        continue;
                if (!wholine)
 -                      wholine = find_wholine(who, wholen, buf, sz);
 +                      wholine = find_wholine(who, wholen, buf);
                if (!wholine)
                        return; /* no point looking for it */
                if (name[wholen] == 0)
        if (strcmp(who, "tagger") && strcmp(who, "committer"))
                return; /* "author" for commit object is not wanted */
        if (!wholine)
 -              wholine = find_wholine(who, wholen, buf, sz);
 +              wholine = find_wholine(who, wholen, buf);
        if (!wholine)
                return;
        for (i = 0; i < used_atom_cnt; i++) {
        }
  }
  
 -static void find_subpos(const char *buf, unsigned long sz,
 +static void find_subpos(const char *buf,
                        const char **sub, unsigned long *sublen,
                        const char **body, unsigned long *bodylen,
                        unsigned long *nonsiglen,
@@@ -1220,7 -1192,7 +1220,7 @@@ static void append_lines(struct strbuf 
  }
  
  /* See grab_values */
 -static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 +static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
  {
        int i;
        const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
                    !starts_with(name, "contents"))
                        continue;
                if (!subpos)
 -                      find_subpos(buf, sz,
 +                      find_subpos(buf,
                                    &subpos, &sublen,
                                    &bodypos, &bodylen, &nonsiglen,
                                    &sigpos, &siglen);
@@@ -1293,19 -1265,19 +1293,19 @@@ static void fill_missing_values(struct 
   * pointed at by the ref itself; otherwise it is the object the
   * ref (which is a tag) refers to.
   */
 -static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 +static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)
  {
        switch (obj->type) {
        case OBJ_TAG:
 -              grab_tag_values(val, deref, obj, buf, sz);
 -              grab_sub_body_contents(val, deref, obj, buf, sz);
 -              grab_person("tagger", val, deref, obj, buf, sz);
 +              grab_tag_values(val, deref, obj);
 +              grab_sub_body_contents(val, deref, buf);
 +              grab_person("tagger", val, deref, buf);
                break;
        case OBJ_COMMIT:
 -              grab_commit_values(val, deref, obj, buf, sz);
 -              grab_sub_body_contents(val, deref, obj, buf, sz);
 -              grab_person("author", val, deref, obj, buf, sz);
 -              grab_person("committer", val, deref, obj, buf, sz);
 +              grab_commit_values(val, deref, obj);
 +              grab_sub_body_contents(val, deref, buf);
 +              grab_person("author", val, deref, buf);
 +              grab_person("committer", val, deref, buf);
                break;
        case OBJ_TREE:
                /* grab_tree_values(val, deref, obj, buf, sz); */
@@@ -1416,8 -1388,7 +1416,8 @@@ static void fill_remote_ref_details(str
                *s = show_ref(&atom->u.remote_ref.refname, refname);
        else if (atom->u.remote_ref.option == RR_TRACK) {
                if (stat_tracking_info(branch, &num_ours, &num_theirs,
 -                                     NULL, AHEAD_BEHIND_FULL) < 0) {
 +                                     NULL, atom->u.remote_ref.push,
 +                                     AHEAD_BEHIND_FULL) < 0) {
                        *s = xstrdup(msgs.gone);
                } else if (!num_ours && !num_theirs)
                        *s = xstrdup("");
                }
        } else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
                if (stat_tracking_info(branch, &num_ours, &num_theirs,
 -                                     NULL, AHEAD_BEHIND_FULL) < 0) {
 +                                     NULL, atom->u.remote_ref.push,
 +                                     AHEAD_BEHIND_FULL) < 0) {
                        *s = xstrdup("");
                        return;
                }
@@@ -1471,35 -1441,35 +1471,35 @@@ char *get_head_description(void
        struct wt_status_state state;
        memset(&state, 0, sizeof(state));
        wt_status_get_state(the_repository, &state, 1);
+       /*
+        * The ( character must be hard-coded and not part of a localizable
+        * string, since the description is used as a sort key and compared
+        * with ref names.
+        */
+       strbuf_addch(&desc, '(');
        if (state.rebase_in_progress ||
            state.rebase_interactive_in_progress) {
                if (state.branch)
-                       strbuf_addf(&desc, _("(no branch, rebasing %s)"),
+                       strbuf_addf(&desc, _("no branch, rebasing %s"),
                                    state.branch);
                else
-                       strbuf_addf(&desc, _("(no branch, rebasing detached HEAD %s)"),
+                       strbuf_addf(&desc, _("no branch, rebasing detached HEAD %s"),
                                    state.detached_from);
        } else if (state.bisect_in_progress)
-               strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
+               strbuf_addf(&desc, _("no branch, bisect started on %s"),
                            state.branch);
        else if (state.detached_from) {
                if (state.detached_at)
-                       /*
-                        * TRANSLATORS: make sure this matches "HEAD
-                        * detached at " in wt-status.c
-                        */
-                       strbuf_addf(&desc, _("(HEAD detached at %s)"),
-                               state.detached_from);
+                       strbuf_addstr(&desc, HEAD_DETACHED_AT);
                else
-                       /*
-                        * TRANSLATORS: make sure this matches "HEAD
-                        * detached from " in wt-status.c
-                        */
-                       strbuf_addf(&desc, _("(HEAD detached from %s)"),
-                               state.detached_from);
+                       strbuf_addstr(&desc, HEAD_DETACHED_FROM);
+               strbuf_addstr(&desc, state.detached_from);
        }
        else
-               strbuf_addstr(&desc, _("(no branch)"));
+               strbuf_addstr(&desc, _("no branch"));
+       strbuf_addch(&desc, ')');
        free(state.branch);
        free(state.onto);
        free(state.detached_from);
@@@ -1546,7 -1516,7 +1546,7 @@@ static int get_object(struct ref_array_
                        return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
                                               oid_to_hex(&oi->oid), ref->refname);
                }
 -              grab_values(ref->value, deref, *obj, oi->content, oi->size);
 +              grab_values(ref->value, deref, *obj, oi->content);
        }
  
        grab_common_values(ref->value, deref, oi);
        return 0;
  }
  
 +static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees)
 +{
 +      int i;
 +
 +      for (i = 0; worktrees[i]; i++) {
 +              if (worktrees[i]->head_ref) {
 +                      struct ref_to_worktree_entry *entry;
 +                      entry = xmalloc(sizeof(*entry));
 +                      entry->wt = worktrees[i];
 +                      hashmap_entry_init(entry, strhash(worktrees[i]->head_ref));
 +
 +                      hashmap_add(map, entry);
 +              }
 +      }
 +}
 +
 +static void lazy_init_worktree_map(void)
 +{
 +      if (ref_to_worktree_map.worktrees)
 +              return;
 +
 +      ref_to_worktree_map.worktrees = get_worktrees(0);
 +      hashmap_init(&(ref_to_worktree_map.map), ref_to_worktree_map_cmpfnc, NULL, 0);
 +      populate_worktree_map(&(ref_to_worktree_map.map), ref_to_worktree_map.worktrees);
 +}
 +
 +static char *get_worktree_path(const struct used_atom *atom, const struct ref_array_item *ref)
 +{
 +      struct hashmap_entry entry;
 +      struct ref_to_worktree_entry *lookup_result;
 +
 +      lazy_init_worktree_map();
 +
 +      hashmap_entry_init(&entry, strhash(ref->refname));
 +      lookup_result = hashmap_get(&(ref_to_worktree_map.map), &entry, ref->refname);
 +
 +      if (lookup_result)
 +              return xstrdup(lookup_result->wt->path);
 +      else
 +              return xstrdup("");
 +}
 +
  /*
   * Parse the object referred by ref, and grab needed value.
   */
@@@ -1634,13 -1562,6 +1634,13 @@@ static int populate_value(struct ref_ar
  
                if (starts_with(name, "refname"))
                        refname = get_refname(atom, ref);
 +              else if (!strcmp(name, "worktreepath")) {
 +                      if (ref->kind == FILTER_REFS_BRANCHES)
 +                              v->s = get_worktree_path(atom, ref);
 +                      else
 +                              v->s = xstrdup("");
 +                      continue;
 +              }
                else if (starts_with(name, "symref"))
                        refname = get_symref(atom, ref);
                else if (starts_with(name, "upstream")) {
@@@ -2124,11 -2045,6 +2124,11 @@@ void ref_array_clear(struct ref_array *
                free_array_item(array->items[i]);
        FREE_AND_NULL(array->items);
        array->nr = array->alloc = 0;
 +      if (ref_to_worktree_map.worktrees) {
 +              hashmap_free(&(ref_to_worktree_map.map), 1);
 +              free_worktrees(ref_to_worktree_map.worktrees);
 +              ref_to_worktree_map.worktrees = NULL;
 +      }
  }
  
  static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
@@@ -2417,13 -2333,8 +2417,13 @@@ void parse_ref_sorting(struct ref_sorti
  
  int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
  {
 -      if (!arg) /* should --no-sort void the list ? */
 -              return -1;
 +      /*
 +       * 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);
 +
        parse_ref_sorting(opt->value, arg);
        return 0;
  }
diff --combined wt-status.c
index 0bccef542fccee5deaa6ee3c740f60879aa97fb3,9e24855b82f1a919b79daf34ed4273d0dc7d8777..c29e4bf091ad7744124f2a47abed058198422fa2
@@@ -17,7 -17,6 +17,7 @@@
  #include "utf8.h"
  #include "worktree.h"
  #include "lockfile.h"
 +#include "sequencer.h"
  
  static const char cut_line[] =
  "------------------------ >8 ------------------------\n";
@@@ -749,23 -748,12 +749,23 @@@ static int has_unmerged(struct wt_statu
  
  void wt_status_collect(struct wt_status *s)
  {
 +      trace2_region_enter("status", "worktrees", s->repo);
        wt_status_collect_changes_worktree(s);
 -      if (s->is_initial)
 +      trace2_region_leave("status", "worktrees", s->repo);
 +
 +      if (s->is_initial) {
 +              trace2_region_enter("status", "initial", s->repo);
                wt_status_collect_changes_initial(s);
 -      else
 +              trace2_region_leave("status", "initial", s->repo);
 +      } else {
 +              trace2_region_enter("status", "index", s->repo);
                wt_status_collect_changes_index(s);
 +              trace2_region_leave("status", "index", s->repo);
 +      }
 +
 +      trace2_region_enter("status", "untracked", s->repo);
        wt_status_collect_untracked(s);
 +      trace2_region_leave("status", "untracked", s->repo);
  
        wt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, "HEAD"));
        if (s->state.merge_in_progress && !has_unmerged(s))
@@@ -1007,19 -995,13 +1007,19 @@@ size_t wt_status_locate_end(const char 
        return len;
  }
  
 -void wt_status_add_cut_line(FILE *fp)
 +void wt_status_append_cut_line(struct strbuf *buf)
  {
        const char *explanation = _("Do not modify or remove the line above.\nEverything below it will be ignored.");
 +
 +      strbuf_commented_addf(buf, "%s", cut_line);
 +      strbuf_add_commented_lines(buf, explanation, strlen(explanation));
 +}
 +
 +void wt_status_add_cut_line(FILE *fp)
 +{
        struct strbuf buf = STRBUF_INIT;
  
 -      fprintf(fp, "%c %s", comment_line_char, cut_line);
 -      strbuf_add_commented_lines(&buf, explanation, strlen(explanation));
 +      wt_status_append_cut_line(&buf);
        fputs(buf.buf, fp);
        strbuf_release(&buf);
  }
@@@ -1215,9 -1197,7 +1215,9 @@@ static void abbrev_sha1_in_line(struct 
        int i;
  
        if (starts_with(line->buf, "exec ") ||
 -          starts_with(line->buf, "x "))
 +          starts_with(line->buf, "x ") ||
 +          starts_with(line->buf, "label ") ||
 +          starts_with(line->buf, "l "))
                return;
  
        split = strbuf_split_max(line, ' ', 3);
@@@ -1389,22 -1369,12 +1389,22 @@@ static void show_rebase_in_progress(str
  static void show_cherry_pick_in_progress(struct wt_status *s,
                                         const char *color)
  {
 -      status_printf_ln(s, color, _("You are currently cherry-picking commit %s."),
 -                      find_unique_abbrev(&s->state.cherry_pick_head_oid, DEFAULT_ABBREV));
 +      if (is_null_oid(&s->state.cherry_pick_head_oid))
 +              status_printf_ln(s, color,
 +                      _("Cherry-pick currently in progress."));
 +      else
 +              status_printf_ln(s, color,
 +                      _("You are currently cherry-picking commit %s."),
 +                      find_unique_abbrev(&s->state.cherry_pick_head_oid,
 +                                         DEFAULT_ABBREV));
 +
        if (s->hints) {
                if (has_unmerged(s))
                        status_printf_ln(s, color,
                                _("  (fix conflicts and run \"git cherry-pick --continue\")"));
 +              else if (is_null_oid(&s->state.cherry_pick_head_oid))
 +                      status_printf_ln(s, color,
 +                              _("  (run \"git cherry-pick --continue\" to continue)"));
                else
                        status_printf_ln(s, color,
                                _("  (all conflicts fixed: run \"git cherry-pick --continue\")"));
  static void show_revert_in_progress(struct wt_status *s,
                                    const char *color)
  {
 -      status_printf_ln(s, color, _("You are currently reverting commit %s."),
 -                       find_unique_abbrev(&s->state.revert_head_oid, DEFAULT_ABBREV));
 +      if (is_null_oid(&s->state.revert_head_oid))
 +              status_printf_ln(s, color,
 +                      _("Revert currently in progress."));
 +      else
 +              status_printf_ln(s, color,
 +                      _("You are currently reverting commit %s."),
 +                      find_unique_abbrev(&s->state.revert_head_oid,
 +                                         DEFAULT_ABBREV));
        if (s->hints) {
                if (has_unmerged(s))
                        status_printf_ln(s, color,
                                _("  (fix conflicts and run \"git revert --continue\")"));
 +              else if (is_null_oid(&s->state.revert_head_oid))
 +                      status_printf_ln(s, color,
 +                              _("  (run \"git revert --continue\" to continue)"));
                else
                        status_printf_ln(s, color,
                                _("  (all conflicts fixed: run \"git revert --continue\")"));
@@@ -1602,7 -1563,6 +1602,7 @@@ void wt_status_get_state(struct reposit
  {
        struct stat st;
        struct object_id oid;
 +      enum replay_action action;
  
        if (!stat(git_path_merge_head(r), &st)) {
                wt_status_check_rebase(NULL, state);
                state->revert_in_progress = 1;
                oidcpy(&state->revert_head_oid, &oid);
        }
 -
 +      if (!sequencer_get_last_command(r, &action)) {
 +              if (action == REPLAY_PICK) {
 +                      state->cherry_pick_in_progress = 1;
 +                      oidcpy(&state->cherry_pick_head_oid, &null_oid);
 +              } else {
 +                      state->revert_in_progress = 1;
 +                      oidcpy(&state->revert_head_oid, &null_oid);
 +              }
 +      }
        if (get_detached_from)
                wt_status_get_detached_from(r, state);
  }
@@@ -1676,9 -1628,9 +1676,9 @@@ static void wt_longstatus_print(struct 
                        } else if (s->state.detached_from) {
                                branch_name = s->state.detached_from;
                                if (s->state.detached_at)
-                                       on_what = _("HEAD detached at ");
+                                       on_what = HEAD_DETACHED_AT;
                                else
-                                       on_what = _("HEAD detached from ");
+                                       on_what = HEAD_DETACHED_FROM;
                        } else {
                                branch_name = "";
                                on_what = _("Not currently on any branch.");
@@@ -1888,7 -1840,7 +1888,7 @@@ static void wt_shortstatus_print_tracki
        color_fprintf(s->fp, branch_color_local, "%s", branch_name);
  
        sti = stat_tracking_info(branch, &num_ours, &num_theirs, &base,
 -                               s->ahead_behind_flags);
 +                               0, s->ahead_behind_flags);
        if (sti < 0) {
                if (!base)
                        goto conclude;
@@@ -2027,7 -1979,7 +2027,7 @@@ static void wt_porcelain_v2_print_track
                branch = branch_get(branch_name);
                base = NULL;
                ab_info = stat_tracking_info(branch, &nr_ahead, &nr_behind,
 -                                           &base, s->ahead_behind_flags);
 +                                           &base, 0, s->ahead_behind_flags);
                if (base) {
                        base = shorten_unambiguous_ref(base, 0);
                        fprintf(s->fp, "# branch.upstream %s%c", base, eol);
@@@ -2076,7 -2028,9 +2076,7 @@@ static void wt_porcelain_v2_submodule_s
  /*
   * Fix-up changed entries before we print them.
   */
 -static void wt_porcelain_v2_fix_up_changed(
 -      struct string_list_item *it,
 -      struct wt_status *s)
 +static void wt_porcelain_v2_fix_up_changed(struct string_list_item *it)
  {
        struct wt_status_change_data *d = it->util;
  
@@@ -2136,7 -2090,7 +2136,7 @@@ static void wt_porcelain_v2_print_chang
        char submodule_token[5];
        char sep_char, eol_char;
  
 -      wt_porcelain_v2_fix_up_changed(it, s);
 +      wt_porcelain_v2_fix_up_changed(it);
        wt_porcelain_v2_submodule_state(d, submodule_token);
  
        key[0] = d->index_status ? d->index_status : '.';
@@@ -2337,13 -2291,6 +2337,13 @@@ static void wt_porcelain_v2_print(struc
  
  void wt_status_print(struct wt_status *s)
  {
 +      trace2_data_intmax("status", s->repo, "count/changed", s->change.nr);
 +      trace2_data_intmax("status", s->repo, "count/untracked",
 +                         s->untracked.nr);
 +      trace2_data_intmax("status", s->repo, "count/ignored", s->ignored.nr);
 +
 +      trace2_region_enter("status", "print", s->repo);
 +
        switch (s->status_format) {
        case STATUS_FORMAT_SHORT:
                wt_shortstatus_print(s);
                wt_longstatus_print(s);
                break;
        }
 +
 +      trace2_region_leave("status", "print", s->repo);
  }
  
  /**
diff --combined wt-status.h
index 64f1ddc9fd9950f7dbe3b0889bf2085111814cec,ecdeb828a1a85ae83551b38a5dc6e042086f1790..b0cfdc8011c3cf06db89f1fa19c9c2586fcfe4df
@@@ -65,6 -65,9 +65,9 @@@ enum wt_status_format 
        STATUS_FORMAT_UNSPECIFIED
  };
  
+ #define HEAD_DETACHED_AT _("HEAD detached at ")
+ #define HEAD_DETACHED_FROM _("HEAD detached from ")
  struct wt_status_state {
        int merge_in_progress;
        int am_in_progress;
@@@ -129,7 -132,6 +132,7 @@@ struct wt_status 
  };
  
  size_t wt_status_locate_end(const char *s, size_t len);
 +void wt_status_append_cut_line(struct strbuf *buf);
  void wt_status_add_cut_line(FILE *fp);
  void wt_status_prepare(struct repository *r, struct wt_status *s);
  void wt_status_print(struct wt_status *s);