Merge branch 'jk/has-uncommitted-changes-fix'
authorJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:37 +0000 (15:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:38 +0000 (15:30 -0700)
"git pull --rebase" on a corrupt HEAD caused a segfault. In
general we substitute an empty tree object when running the in-core
equivalent of the diff-index command, and the codepath has been
corrected to do so as well to fix this issue.

* jk/has-uncommitted-changes-fix:
has_uncommitted_changes(): fall back to empty tree

1  2 
diff-lib.c
wt-status.c
diff --combined diff-lib.c
index a9f38eb5a3e0e17d111301b581d06876ce5fd510,7eea70e8139672749733c812db3e9848055f53c5..732f684a49c54cdbc3b9708e91c064b7845d7716
@@@ -92,7 -92,6 +92,7 @@@ int run_diff_files(struct rev_info *rev
        int diff_unmerged_stage = revs->max_count;
        unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED)
                              ? CE_MATCH_RACY_IS_DIRTY : 0);
 +      uint64_t start = getnanotime();
  
        diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/");
  
        }
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
 +      trace_performance_since(start, "diff-files");
        return 0;
  }
  
@@@ -304,7 -302,7 +304,7 @@@ static int get_stat_data(const struct c
  }
  
  static void show_new_file(struct rev_info *revs,
 -                        const struct cache_entry *new,
 +                        const struct cache_entry *new_file,
                          int cached, int match_missing)
  {
        const struct object_id *oid;
         * New file in the index: it might actually be different in
         * the working tree.
         */
 -      if (get_stat_data(new, &oid, &mode, cached, match_missing,
 +      if (get_stat_data(new_file, &oid, &mode, cached, match_missing,
            &dirty_submodule, &revs->diffopt) < 0)
                return;
  
 -      diff_index_show_file(revs, "+", new, oid, !is_null_oid(oid), mode, dirty_submodule);
 +      diff_index_show_file(revs, "+", new_file, oid, !is_null_oid(oid), mode, dirty_submodule);
  }
  
  static int show_modified(struct rev_info *revs,
 -                       const struct cache_entry *old,
 -                       const struct cache_entry *new,
 +                       const struct cache_entry *old_entry,
 +                       const struct cache_entry *new_entry,
                         int report_missing,
                         int cached, int match_missing)
  {
        const struct object_id *oid;
        unsigned dirty_submodule = 0;
  
 -      if (get_stat_data(new, &oid, &mode, cached, match_missing,
 +      if (get_stat_data(new_entry, &oid, &mode, cached, match_missing,
                          &dirty_submodule, &revs->diffopt) < 0) {
                if (report_missing)
 -                      diff_index_show_file(revs, "-", old,
 -                                           &old->oid, 1, old->ce_mode,
 +                      diff_index_show_file(revs, "-", old_entry,
 +                                           &old_entry->oid, 1, old_entry->ce_mode,
                                             0);
                return -1;
        }
  
        if (revs->combine_merges && !cached &&
 -          (oidcmp(oid, &old->oid) || oidcmp(&old->oid, &new->oid))) {
 +          (oidcmp(oid, &old_entry->oid) || oidcmp(&old_entry->oid, &new_entry->oid))) {
                struct combine_diff_path *p;
 -              int pathlen = ce_namelen(new);
 +              int pathlen = ce_namelen(new_entry);
  
                p = xmalloc(combine_diff_path_size(2, pathlen));
                p->path = (char *) &p->parent[2];
                p->next = NULL;
 -              memcpy(p->path, new->name, pathlen);
 +              memcpy(p->path, new_entry->name, pathlen);
                p->path[pathlen] = 0;
                p->mode = mode;
                oidclr(&p->oid);
                memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent));
                p->parent[0].status = DIFF_STATUS_MODIFIED;
 -              p->parent[0].mode = new->ce_mode;
 -              oidcpy(&p->parent[0].oid, &new->oid);
 +              p->parent[0].mode = new_entry->ce_mode;
 +              oidcpy(&p->parent[0].oid, &new_entry->oid);
                p->parent[1].status = DIFF_STATUS_MODIFIED;
 -              p->parent[1].mode = old->ce_mode;
 -              oidcpy(&p->parent[1].oid, &old->oid);
 +              p->parent[1].mode = old_entry->ce_mode;
 +              oidcpy(&p->parent[1].oid, &old_entry->oid);
                show_combined_diff(p, 2, revs->dense_combined_merges, revs);
                free(p);
                return 0;
        }
  
 -      oldmode = old->ce_mode;
 -      if (mode == oldmode && !oidcmp(oid, &old->oid) && !dirty_submodule &&
 +      oldmode = old_entry->ce_mode;
 +      if (mode == oldmode && !oidcmp(oid, &old_entry->oid) && !dirty_submodule &&
            !revs->diffopt.flags.find_copies_harder)
                return 0;
  
        diff_change(&revs->diffopt, oldmode, mode,
 -                  &old->oid, oid, 1, !is_null_oid(oid),
 -                  old->name, 0, dirty_submodule);
 +                  &old_entry->oid, oid, 1, !is_null_oid(oid),
 +                  old_entry->name, 0, dirty_submodule);
        return 0;
  }
  
@@@ -389,12 -387,8 +389,12 @@@ static void do_oneway_diff(struct unpac
        struct rev_info *revs = o->unpack_data;
        int match_missing, cached;
  
 -      /* i-t-a entries do not actually exist in the index */
 -      if (revs->diffopt.ita_invisible_in_index &&
 +      /*
 +       * i-t-a entries do not actually exist in the index (if we're
 +       * looking at its content)
 +       */
 +      if (o->index_only &&
 +          revs->diffopt.ita_invisible_in_index &&
            idx && ce_intent_to_add(idx)) {
                idx = NULL;
                if (!tree)
@@@ -518,8 -512,10 +518,11 @@@ static int diff_cache(struct rev_info *
  int run_diff_index(struct rev_info *revs, int cached)
  {
        struct object_array_entry *ent;
 +      uint64_t start = getnanotime();
  
+       if (revs->pending.nr != 1)
+               BUG("run_diff_index must be passed exactly one tree");
        ent = revs->pending.objects;
        if (diff_cache(revs, &ent->item->oid, ent->name, cached))
                exit(128);
        diffcore_fix_diff_index(&revs->diffopt);
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
 +      trace_performance_since(start, "diff-index");
        return 0;
  }
  
diff --combined wt-status.c
index 8827a256d32925ea24d51ccf00f3108154d01757,e1965672360acf174f62bda603196025a354daf4..fadbed0de9710643ac595f551f2897133b05f8c9
@@@ -136,11 -136,7 +136,11 @@@ void wt_status_prepare(struct wt_statu
        s->ignored.strdup_strings = 1;
        s->show_branch = -1;  /* unspecified */
        s->show_stash = 0;
 +      s->ahead_behind_flags = AHEAD_BEHIND_UNSPECIFIED;
        s->display_comment_prefix = 0;
 +      s->detect_rename = -1;
 +      s->rename_score = -1;
 +      s->rename_limit = -1;
  }
  
  static void wt_longstatus_print_unmerged_header(struct wt_status *s)
@@@ -267,7 -263,7 +267,7 @@@ static const char *wt_status_unmerged_s
        case 7:
                return _("both modified:");
        default:
 -              die("BUG: unhandled unmerged status %x", stagemask);
 +              BUG("unhandled unmerged status %x", stagemask);
        }
  }
  
@@@ -380,7 -376,7 +380,7 @@@ static void wt_longstatus_print_change_
                status = d->worktree_status;
                break;
        default:
 -              die("BUG: unhandled change_type %d in wt_longstatus_print_change_data",
 +              BUG("unhandled change_type %d in wt_longstatus_print_change_data",
                    change_type);
        }
  
        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        what = wt_status_diff_status_string(status);
        if (!what)
 -              die("BUG: unhandled diff status %c", status);
 +              BUG("unhandled diff status %c", status);
        len = label_width - utf8_strwidth(what);
        assert(len >= 0);
        if (one_name != two_name)
@@@ -473,7 -469,7 +473,7 @@@ static void wt_status_collect_changed_c
                case DIFF_STATUS_COPIED:
                case DIFF_STATUS_RENAMED:
                        if (d->rename_status)
 -                              die("BUG: multiple renames on the same target? how?");
 +                              BUG("multiple renames on the same target? how?");
                        d->rename_source = xstrdup(p->one->path);
                        d->rename_score = p->score * 100 / MAX_SCORE;
                        d->rename_status = p->status;
                        break;
  
                default:
 -                      die("BUG: unhandled diff-files status '%c'", p->status);
 +                      BUG("unhandled diff-files status '%c'", p->status);
                        break;
                }
  
@@@ -550,7 -546,7 +550,7 @@@ static void wt_status_collect_updated_c
                case DIFF_STATUS_COPIED:
                case DIFF_STATUS_RENAMED:
                        if (d->rename_status)
 -                              die("BUG: multiple renames on the same target? how?");
 +                              BUG("multiple renames on the same target? how?");
                        d->rename_source = xstrdup(p->one->path);
                        d->rename_score = p->score * 100 / MAX_SCORE;
                        d->rename_status = p->status;
                        break;
  
                default:
 -                      die("BUG: unhandled diff-index status '%c'", p->status);
 +                      BUG("unhandled diff-index status '%c'", p->status);
                        break;
                }
        }
@@@ -595,9 -591,6 +595,9 @@@ static void wt_status_collect_changes_w
        }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
 +      rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
 +      rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
 +      rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
        copy_pathspec(&rev.prune_data, &s->pathspec);
        run_diff_files(&rev, 0);
  }
@@@ -609,7 -602,7 +609,7 @@@ static void wt_status_collect_changes_i
  
        init_revisions(&rev, NULL);
        memset(&opt, 0, sizeof(opt));
 -      opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
 +      opt.def = s->is_initial ? empty_tree_oid_hex() : s->reference;
        setup_revisions(0, NULL, &rev, &opt);
  
        rev.diffopt.flags.override_submodule_config = 1;
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = wt_status_collect_updated_cb;
        rev.diffopt.format_callback_data = s;
 -      rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
 -      rev.diffopt.rename_limit = 200;
 -      rev.diffopt.break_opt = 0;
 +      rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
 +      rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
 +      rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
        copy_pathspec(&rev.prune_data, &s->pathspec);
        run_diff_index(&rev, 1);
  }
@@@ -987,13 -980,11 +987,13 @@@ static void wt_longstatus_print_verbose
        rev.diffopt.ita_invisible_in_index = 1;
  
        memset(&opt, 0, sizeof(opt));
 -      opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
 +      opt.def = s->is_initial ? empty_tree_oid_hex() : s->reference;
        setup_revisions(0, NULL, &rev, &opt);
  
        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
 -      rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
 +      rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
 +      rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
 +      rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
        rev.diffopt.file = s->fp;
        rev.diffopt.close_file = 0;
        /*
@@@ -1041,7 -1032,7 +1041,7 @@@ static void wt_longstatus_print_trackin
        if (!skip_prefix(s->branch, "refs/heads/", &branch_name))
                return;
        branch = branch_get(branch_name);
 -      if (!format_tracking_info(branch, &sb))
 +      if (!format_tracking_info(branch, &sb, s->ahead_behind_flags))
                return;
  
        i = 0;
@@@ -1196,7 -1187,7 +1196,7 @@@ static void abbrev_sha1_in_line(struct 
                strbuf_trim(split[1]);
                if (!get_oid(split[1]->buf, &oid)) {
                        strbuf_reset(split[1]);
 -                      strbuf_add_unique_abbrev(split[1], oid.hash,
 +                      strbuf_add_unique_abbrev(split[1], &oid,
                                                 DEFAULT_ABBREV);
                        strbuf_addch(split[1], ' ');
                        strbuf_reset(line);
@@@ -1317,7 -1308,7 +1317,7 @@@ static void show_rebase_in_progress(str
                        status_printf_ln(s, color,
                                _("  (use \"git rebase --abort\" to check out the original branch)"));
                }
 -      } else if (state->rebase_in_progress || !stat(git_path_merge_msg(), &st)) {
 +      } else if (state->rebase_in_progress || !stat(git_path_merge_msg(the_repository), &st)) {
                print_rebase_state(s, state, color);
                if (s->hints)
                        status_printf_ln(s, color,
@@@ -1358,7 -1349,7 +1358,7 @@@ static void show_cherry_pick_in_progres
                                        const char *color)
  {
        status_printf_ln(s, color, _("You are currently cherry-picking commit %s."),
 -                      find_unique_abbrev(state->cherry_pick_head_sha1, DEFAULT_ABBREV));
 +                      find_unique_abbrev(&state->cherry_pick_head_oid, DEFAULT_ABBREV));
        if (s->hints) {
                if (has_unmerged(s))
                        status_printf_ln(s, color,
@@@ -1377,7 -1368,7 +1377,7 @@@ static void show_revert_in_progress(str
                                        const char *color)
  {
        status_printf_ln(s, color, _("You are currently reverting commit %s."),
 -                       find_unique_abbrev(state->revert_head_sha1, DEFAULT_ABBREV));
 +                       find_unique_abbrev(&state->revert_head_oid, DEFAULT_ABBREV));
        if (s->hints) {
                if (has_unmerged(s))
                        status_printf_ln(s, color,
@@@ -1430,7 -1421,7 +1430,7 @@@ static char *get_branch(const struct wo
                ;
        else if (!get_oid_hex(sb.buf, &oid)) {
                strbuf_reset(&sb);
 -              strbuf_add_unique_abbrev(&sb, oid.hash, DEFAULT_ABBREV);
 +              strbuf_add_unique_abbrev(&sb, &oid, DEFAULT_ABBREV);
        } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */
                goto got_nothing;
        else                    /* bisect */
@@@ -1467,7 -1458,7 +1467,7 @@@ static int grab_1st_switch(struct objec
        if (!strcmp(cb->buf.buf, "HEAD")) {
                /* HEAD is relative. Resolve it to the right reflog entry. */
                strbuf_reset(&cb->buf);
 -              strbuf_add_unique_abbrev(&cb->buf, noid->hash, DEFAULT_ABBREV);
 +              strbuf_add_unique_abbrev(&cb->buf, noid, DEFAULT_ABBREV);
        }
        return 1;
  }
@@@ -1497,10 -1488,10 +1497,10 @@@ static void wt_status_get_detached_from
                state->detached_from = xstrdup(from);
        } else
                state->detached_from =
 -                      xstrdup(find_unique_abbrev(cb.noid.hash, DEFAULT_ABBREV));
 -      hashcpy(state->detached_sha1, cb.noid.hash);
 +                      xstrdup(find_unique_abbrev(&cb.noid, DEFAULT_ABBREV));
 +      oidcpy(&state->detached_oid, &cb.noid);
        state->detached_at = !get_oid("HEAD", &oid) &&
 -                           !hashcmp(oid.hash, state->detached_sha1);
 +                           !oidcmp(&oid, &state->detached_oid);
  
        free(ref);
        strbuf_release(&cb.buf);
@@@ -1552,20 -1543,20 +1552,20 @@@ void wt_status_get_state(struct wt_stat
        struct stat st;
        struct object_id oid;
  
 -      if (!stat(git_path_merge_head(), &st)) {
 +      if (!stat(git_path_merge_head(the_repository), &st)) {
                state->merge_in_progress = 1;
        } else if (wt_status_check_rebase(NULL, state)) {
                ;               /* all set */
 -      } else if (!stat(git_path_cherry_pick_head(), &st) &&
 +      } else if (!stat(git_path_cherry_pick_head(the_repository), &st) &&
                        !get_oid("CHERRY_PICK_HEAD", &oid)) {
                state->cherry_pick_in_progress = 1;
 -              hashcpy(state->cherry_pick_head_sha1, oid.hash);
 +              oidcpy(&state->cherry_pick_head_oid, &oid);
        }
        wt_status_check_bisect(NULL, state);
 -      if (!stat(git_path_revert_head(), &st) &&
 +      if (!stat(git_path_revert_head(the_repository), &st) &&
            !get_oid("REVERT_HEAD", &oid)) {
                state->revert_in_progress = 1;
 -              hashcpy(state->revert_head_sha1, oid.hash);
 +              oidcpy(&state->revert_head_oid, &oid);
        }
  
        if (get_detached_from)
@@@ -1802,7 -1793,7 +1802,7 @@@ static void wt_shortstatus_print_tracki
        const char *base;
        char *short_base;
        const char *branch_name;
 -      int num_ours, num_theirs;
 +      int num_ours, num_theirs, sti;
        int upstream_is_gone = 0;
  
        color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
  
        color_fprintf(s->fp, branch_color_local, "%s", branch_name);
  
 -      if (stat_tracking_info(branch, &num_ours, &num_theirs, &base) < 0) {
 +      sti = stat_tracking_info(branch, &num_ours, &num_theirs, &base,
 +                               s->ahead_behind_flags);
 +      if (sti < 0) {
                if (!base)
                        goto conclude;
  
        color_fprintf(s->fp, branch_color_remote, "%s", short_base);
        free(short_base);
  
 -      if (!upstream_is_gone && !num_ours && !num_theirs)
 +      if (!upstream_is_gone && !sti)
                goto conclude;
  
        color_fprintf(s->fp, header_color, " [");
        if (upstream_is_gone) {
                color_fprintf(s->fp, header_color, LABEL(N_("gone")));
 +      } else if (s->ahead_behind_flags == AHEAD_BEHIND_QUICK) {
 +              color_fprintf(s->fp, header_color, LABEL(N_("different")));
        } else if (!num_ours) {
                color_fprintf(s->fp, header_color, LABEL(N_("behind ")));
                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
@@@ -1918,19 -1905,18 +1918,19 @@@ static void wt_porcelain_print(struct w
   *
   *    <upstream> ::= the upstream branch name, when set.
   *
 - *       <ahead> ::= integer ahead value, when upstream set
 - *                   and the commit is present (not gone).
 - *
 - *      <behind> ::= integer behind value, when upstream set
 - *                   and commit is present.
 + *       <ahead> ::= integer ahead value or '?'.
   *
 + *      <behind> ::= integer behind value or '?'.
   *
   * The end-of-line is defined by the -z flag.
   *
   *                 <eol> ::= NUL when -z,
   *                           LF when NOT -z.
   *
 + * When an upstream is set and present, the 'branch.ab' line will
 + * be printed with the ahead/behind counts for the branch and the
 + * upstream.  When AHEAD_BEHIND_QUICK is requested and the branches
 + * are different, '?' will be substituted for the actual count.
   */
  static void wt_porcelain_v2_print_tracking(struct wt_status *s)
  {
                /* Lookup stats on the upstream tracking branch, if set. */
                branch = branch_get(branch_name);
                base = NULL;
 -              ab_info = (stat_tracking_info(branch, &nr_ahead, &nr_behind, &base) == 0);
 +              ab_info = stat_tracking_info(branch, &nr_ahead, &nr_behind,
 +                                           &base, s->ahead_behind_flags);
                if (base) {
                        base = shorten_unambiguous_ref(base, 0);
                        fprintf(s->fp, "# branch.upstream %s%c", base, eol);
                        free((char *)base);
  
 -                      if (ab_info)
 -                              fprintf(s->fp, "# branch.ab +%d -%d%c", nr_ahead, nr_behind, eol);
 +                      if (ab_info > 0) {
 +                              /* different */
 +                              if (nr_ahead || nr_behind)
 +                                      fprintf(s->fp, "# branch.ab +%d -%d%c",
 +                                              nr_ahead, nr_behind, eol);
 +                              else
 +                                      fprintf(s->fp, "# branch.ab +? -?%c",
 +                                              eol);
 +                      } else if (!ab_info) {
 +                              /* same */
 +                              fprintf(s->fp, "# branch.ab +0 -0%c", eol);
 +                      }
                }
        }
  
@@@ -2166,7 -2141,7 +2166,7 @@@ static void wt_porcelain_v2_print_unmer
        case 6: key = "AA"; break; /* both added */
        case 7: key = "UU"; break; /* both modified */
        default:
 -              die("BUG: unhandled unmerged status %x", d->stagemask);
 +              BUG("unhandled unmerged status %x", d->stagemask);
        }
  
        /*
                sum |= (1 << (stage - 1));
        }
        if (sum != d->stagemask)
 -              die("BUG: observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask);
 +              BUG("observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask);
  
        if (s->null_termination)
                path_index = it->string;
@@@ -2297,7 -2272,7 +2297,7 @@@ void wt_status_print(struct wt_status *
                wt_porcelain_v2_print(s);
                break;
        case STATUS_FORMAT_UNSPECIFIED:
 -              die("BUG: finalize_deferred_config() should have been called");
 +              BUG("finalize_deferred_config() should have been called");
                break;
        case STATUS_FORMAT_NONE:
        case STATUS_FORMAT_LONG:
@@@ -2340,7 -2315,17 +2340,17 @@@ int has_uncommitted_changes(int ignore_
        if (ignore_submodules)
                rev_info.diffopt.flags.ignore_submodules = 1;
        rev_info.diffopt.flags.quick = 1;
        add_head_to_pending(&rev_info);
+       if (!rev_info.pending.nr) {
+               /*
+                * We have no head (or it's corrupt); use the empty tree,
+                * which will complain if the index is non-empty.
+                */
+               struct tree *tree = lookup_tree(the_hash_algo->empty_tree);
+               add_pending_object(&rev_info, &tree->object, "");
+       }
        diff_setup_done(&rev_info.diffopt);
        result = run_diff_index(&rev_info, 1);
        return diff_result_code(&rev_info.diffopt, result);