Merge branch 'dt/cache-tree-repair'
authorJunio C Hamano <gitster@pobox.com>
Thu, 11 Sep 2014 17:33:32 +0000 (10:33 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 11 Sep 2014 17:33:32 +0000 (10:33 -0700)
Add a few more places in "commit" and "checkout" that make sure
that the cache-tree is fully populated in the index.

* dt/cache-tree-repair:
cache-tree: do not try to use an invalidated subtree info to build a tree
cache-tree: Write updated cache-tree after commit
cache-tree: subdirectory tests
test-dump-cache-tree: invalid trees are not errors
cache-tree: create/update cache-tree on checkout

1  2 
builtin/checkout.c
builtin/commit.c
cache-tree.c
cache-tree.h
test-dump-cache-tree.c
diff --combined builtin/checkout.c
index f71e74531d2a7d195ff2987f6dca3c69c636aa7c,054214fe43c8f8e3b815d4afb747408f2524c378..8afdf2b5c4bfbd53e7315afe9d8ae3d88f93dd57
@@@ -225,6 -225,7 +225,6 @@@ static int checkout_paths(const struct 
        int flag;
        struct commit *head;
        int errs = 0;
 -      int newfd;
        struct lock_file *lock_file;
  
        if (opts->track != BRANCH_TRACK_UNSPECIFIED)
  
        lock_file = xcalloc(1, sizeof(struct lock_file));
  
 -      newfd = hold_locked_index(lock_file, 1);
 +      hold_locked_index(lock_file, 1);
        if (read_cache_preload(&opts->pathspec) < 0)
                return error(_("corrupt index file"));
  
        memset(&state, 0, sizeof(state));
        state.force = 1;
        state.refresh_cache = 1;
 +      state.istate = &the_index;
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
                if (ce->ce_flags & CE_MATCHED) {
                }
        }
  
 -      if (write_cache(newfd, active_cache, active_nr) ||
 -          commit_locked_index(lock_file))
 +      if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
  
        read_ref_full("HEAD", rev, 0, &flag);
@@@ -443,8 -444,8 +443,8 @@@ static int merge_working_tree(const str
  {
        int ret;
        struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
 -      int newfd = hold_locked_index(lock_file, 1);
  
 +      hold_locked_index(lock_file, 1);
        if (read_cache_preload(NULL) < 0)
                return error(_("corrupt index file"));
  
                }
        }
  
 -              cache_tree_update(active_cache_tree,
 -                                (const struct cache_entry * const *)active_cache,
 -                                active_nr, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+       if (!cache_tree_fully_valid(active_cache_tree))
 -      if (write_cache(newfd, active_cache, active_nr) ||
 -          commit_locked_index(lock_file))
++              cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
 +      if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
  
        if (!opts->force && !opts->quiet)
@@@ -622,7 -632,7 +628,7 @@@ static void update_refs_for_switch(cons
                /* Nothing to do. */
        } else if (opts->force_detach || !new->path) {  /* No longer on any branch. */
                update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
 -                         REF_NODEREF, DIE_ON_ERR);
 +                         REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old->path && advice_detached_head)
                                detach_advice(new->name);
                        }
                }
                if (old->path && old->name) {
 -                      char log_file[PATH_MAX], ref_file[PATH_MAX];
 -
 -                      git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
 -                      git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
 -                      if (!file_exists(ref_file) && file_exists(log_file))
 -                              remove_path(log_file);
 +                      if (!ref_exists(old->path) && reflog_exists(old->path))
 +                              delete_reflog(old->path);
                }
        }
        remove_branch_state();
@@@ -774,8 -788,8 +780,8 @@@ static int switch_branches(const struc
        if (!(flag & REF_ISSYMREF))
                old.path = NULL;
  
 -      if (old.path && starts_with(old.path, "refs/heads/"))
 -              old.name = old.path + strlen("refs/heads/");
 +      if (old.path)
 +              skip_prefix(old.path, "refs/heads/", &old.name);
  
        if (!new->name) {
                new->name = "HEAD";
diff --combined builtin/commit.c
index 5911447486f5b2325dff4747872a25dd4325c1e2,77570c48ba02adf7aa78654c644b13478246a4e0..41f481bd030ba96f8883e64517eda95931344545
@@@ -42,20 -42,7 +42,20 @@@ static const char * const builtin_statu
        NULL
  };
  
 -static const char implicit_ident_advice[] =
 +static const char implicit_ident_advice_noconfig[] =
 +N_("Your name and email address were configured automatically based\n"
 +"on your username and hostname. Please check that they are accurate.\n"
 +"You can suppress this message by setting them explicitly. Run the\n"
 +"following command and follow the instructions in your editor to edit\n"
 +"your configuration file:\n"
 +"\n"
 +"    git config --global --edit\n"
 +"\n"
 +"After doing this, you may fix the identity used for this commit with:\n"
 +"\n"
 +"    git commit --amend --reset-author\n");
 +
 +static const char implicit_ident_advice_config[] =
  N_("Your name and email address were configured automatically based\n"
  "on your username and hostname. Please check that they are accurate.\n"
  "You can suppress this message by setting them explicitly:\n"
@@@ -318,6 -305,7 +318,6 @@@ static void refresh_cache_or_die(int re
  static char *prepare_index(int argc, const char **argv, const char *prefix,
                           const struct commit *current_head, int is_status)
  {
 -      int fd;
        struct string_list partial;
        struct pathspec pathspec;
        int refresh_flags = REFRESH_QUIET;
  
        if (interactive) {
                char *old_index_env = NULL;
 -              fd = hold_locked_index(&index_lock, 1);
 +              hold_locked_index(&index_lock, 1);
  
                refresh_cache_or_die(refresh_flags);
  
 -              if (write_cache(fd, active_cache, active_nr) ||
 -                  close_lock_file(&index_lock))
 +              if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
                        die(_("unable to create temporary index"));
  
                old_index_env = getenv(INDEX_ENVIRONMENT);
  
                discard_cache();
                read_cache_from(index_lock.filename);
 -                      fd = open(index_lock.filename, O_WRONLY);
 -                      if (fd >= 0)
 -                              if (write_cache(fd, active_cache, active_nr) < 0)
 -                                      die(_("unable to write index file"));
 -                              else
 -                                      close_lock_file(&index_lock);
 -                      else
+               if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
++                      if (reopen_lock_file(&index_lock) < 0)
+                               die(_("unable to write index file"));
++                      if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
++                              die(_("unable to update temporary index"));
+               } else
+                       warning(_("Failed to update main cache tree"));
  
                commit_style = COMMIT_NORMAL;
                return index_lock.filename;
         * (B) on failure, rollback the real index.
         */
        if (all || (also && pathspec.nr)) {
 -              fd = hold_locked_index(&index_lock, 1);
 +              hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
                refresh_cache_or_die(refresh_flags);
                update_main_cache_tree(WRITE_TREE_SILENT);
 -              if (write_cache(fd, active_cache, active_nr) ||
 -                  close_lock_file(&index_lock))
 +              if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
                        die(_("unable to write new_index file"));
                commit_style = COMMIT_NORMAL;
                return index_lock.filename;
         * We still need to refresh the index here.
         */
        if (!only && !pathspec.nr) {
 -              fd = hold_locked_index(&index_lock, 1);
 +              hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
-               if (active_cache_changed) {
+               if (active_cache_changed
+                   || !cache_tree_fully_valid(active_cache_tree)) {
                        update_main_cache_tree(WRITE_TREE_SILENT);
 -                      if (write_cache(fd, active_cache, active_nr) ||
 -                          commit_locked_index(&index_lock))
+                       active_cache_changed = 1;
+               }
+               if (active_cache_changed) {
 +                      if (write_locked_index(&the_index, &index_lock,
 +                                             COMMIT_LOCK))
                                die(_("unable to write new_index file"));
                } else {
                        rollback_lock_file(&index_lock);
                        die(_("cannot do a partial commit during a cherry-pick."));
        }
  
 -      memset(&partial, 0, sizeof(partial));
 -      partial.strdup_strings = 1;
 +      string_list_init(&partial, 1);
        if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
                exit(1);
  
        if (read_cache() < 0)
                die(_("cannot read the index"));
  
 -      fd = hold_locked_index(&index_lock, 1);
 +      hold_locked_index(&index_lock, 1);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
 -      if (write_cache(fd, active_cache, active_nr) ||
 -          close_lock_file(&index_lock))
+       update_main_cache_tree(WRITE_TREE_SILENT);
 +      if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
                die(_("unable to write new_index file"));
  
 -      fd = hold_lock_file_for_update(&false_lock,
 -                                     git_path("next-index-%"PRIuMAX,
 -                                              (uintmax_t) getpid()),
 -                                     LOCK_DIE_ON_ERROR);
 +      hold_lock_file_for_update(&false_lock,
 +                                git_path("next-index-%"PRIuMAX,
 +                                         (uintmax_t) getpid()),
 +                                LOCK_DIE_ON_ERROR);
  
        create_base_index(current_head);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
  
 -      if (write_cache(fd, active_cache, active_nr) ||
 -          close_lock_file(&false_lock))
 +      if (write_locked_index(&the_index, &false_lock, CLOSE_LOCK))
                die(_("unable to write temporary index file"));
  
        discard_cache();
@@@ -533,29 -542,10 +545,29 @@@ static int sane_ident_split(struct iden
        return 1;
  }
  
 +static int parse_force_date(const char *in, char *out, int len)
 +{
 +      if (len < 1)
 +              return -1;
 +      *out++ = '@';
 +      len--;
 +
 +      if (parse_date(in, out, len) < 0) {
 +              int errors = 0;
 +              unsigned long t = approxidate_careful(in, &errors);
 +              if (errors)
 +                      return -1;
 +              snprintf(out, len, "%lu", t);
 +      }
 +
 +      return 0;
 +}
 +
  static void determine_author_info(struct strbuf *author_ident)
  {
        char *name, *email, *date;
        struct ident_split author;
 +      char date_buf[64];
  
        name = getenv("GIT_AUTHOR_NAME");
        email = getenv("GIT_AUTHOR_EMAIL");
                email = xstrndup(lb + 2, rb - (lb + 2));
        }
  
 -      if (force_date)
 -              date = force_date;
 +      if (force_date) {
 +              if (parse_force_date(force_date, date_buf, sizeof(date_buf)))
 +                      die(_("invalid date format: %s"), force_date);
 +              date = date_buf;
 +      }
 +
        strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT));
        if (!split_ident_line(&author, author_ident->buf, author_ident->len) &&
            sane_ident_split(&author)) {
        }
  }
  
 -static char *cut_ident_timestamp_part(char *string)
 +static void split_ident_or_die(struct ident_split *id, const struct strbuf *buf)
 +{
 +      if (split_ident_line(id, buf->buf, buf->len) ||
 +          !sane_ident_split(id))
 +              die(_("Malformed ident string: '%s'"), buf->buf);
 +}
 +
 +static int author_date_is_interesting(void)
 +{
 +      return author_message || force_date;
 +}
 +
 +static void adjust_comment_line_char(const struct strbuf *sb)
  {
 -      char *ket = strrchr(string, '>');
 -      if (!ket || ket[1] != ' ')
 -              die(_("Malformed ident string: '%s'"), string);
 -      *++ket = '\0';
 -      return ket;
 +      char candidates[] = "#;@!$%^&|:";
 +      char *candidate;
 +      const char *p;
 +
 +      comment_line_char = candidates[0];
 +      if (!memchr(sb->buf, comment_line_char, sb->len))
 +              return;
 +
 +      p = sb->buf;
 +      candidate = strchr(candidates, *p);
 +      if (candidate)
 +              *candidate = ' ';
 +      for (p = sb->buf; *p; p++) {
 +              if ((p[0] == '\n' || p[0] == '\r') && p[1]) {
 +                      candidate = strchr(candidates, p[1]);
 +                      if (candidate)
 +                              *candidate = ' ';
 +              }
 +      }
 +
 +      for (p = candidates; *p == ' '; p++)
 +              ;
 +      if (!*p)
 +              die(_("unable to select a comment character that is not used\n"
 +                    "in the current commit message"));
 +      comment_line_char = *p;
  }
  
  static int prepare_to_commit(const char *index_file, const char *prefix,
                char *buffer;
                buffer = strstr(use_message_buffer, "\n\n");
                if (buffer)
 -                      strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
 +                      strbuf_addstr(&sb, buffer + 2);
                hook_arg1 = "commit";
                hook_arg2 = use_message;
        } else if (fixup_message) {
        if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
                die_errno(_("could not write commit template"));
  
 +      if (auto_comment_line_char)
 +              adjust_comment_line_char(&sb);
        strbuf_release(&sb);
  
        /* This checks if committer ident is explicitly given */
        if (use_editor && include_status) {
                int ident_shown = 0;
                int saved_color_setting;
 -              char *ai_tmp, *ci_tmp;
 +              struct ident_split ci, ai;
 +
                if (whence != FROM_COMMIT) {
                        if (cleanup_mode == CLEANUP_SCISSORS)
                                wt_status_add_cut_line(s->fp);
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                        "%s", only_include_assumed);
  
 -              ai_tmp = cut_ident_timestamp_part(author_ident->buf);
 -              ci_tmp = cut_ident_timestamp_part(committer_ident.buf);
 -              if (strcmp(author_ident->buf, committer_ident.buf))
 +              split_ident_or_die(&ai, author_ident);
 +              split_ident_or_die(&ci, &committer_ident);
 +
 +              if (ident_cmp(&ai, &ci))
 +                      status_printf_ln(s, GIT_COLOR_NORMAL,
 +                              _("%s"
 +                              "Author:    %.*s <%.*s>"),
 +                              ident_shown++ ? "" : "\n",
 +                              (int)(ai.name_end - ai.name_begin), ai.name_begin,
 +                              (int)(ai.mail_end - ai.mail_begin), ai.mail_begin);
 +
 +              if (author_date_is_interesting())
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                _("%s"
 -                              "Author:    %s"),
 +                              "Date:      %s"),
                                ident_shown++ ? "" : "\n",
 -                              author_ident->buf);
 +                              show_ident_date(&ai, DATE_NORMAL));
  
                if (!committer_ident_sufficiently_given())
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                _("%s"
 -                              "Committer: %s"),
 +                              "Committer: %.*s <%.*s>"),
                                ident_shown++ ? "" : "\n",
 -                              committer_ident.buf);
 +                              (int)(ci.name_end - ci.name_begin), ci.name_begin,
 +                              (int)(ci.mail_end - ci.mail_begin), ci.mail_begin);
  
                if (ident_shown)
 -                      status_printf_ln(s, GIT_COLOR_NORMAL, "");
 +                      status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
  
                saved_color_setting = s->use_color;
                s->use_color = 0;
                commitable = run_status(s->fp, index_file, prefix, 1, s);
                s->use_color = saved_color_setting;
 -
 -              *ai_tmp = ' ';
 -              *ci_tmp = ' ';
        } else {
                unsigned char sha1[20];
                const char *parent = "HEAD";
@@@ -1027,7 -970,7 +1039,7 @@@ static int message_is_empty(struct strb
  static int template_untouched(struct strbuf *sb)
  {
        struct strbuf tmpl = STRBUF_INIT;
 -      char *start;
 +      const char *start;
  
        if (cleanup_mode == CLEANUP_NONE && sb->len)
                return 0;
                return 0;
  
        stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
 -      start = (char *)skip_prefix(sb->buf, tmpl.buf);
 -      if (!start)
 +      if (!skip_prefix(sb->buf, tmpl.buf, &start))
                start = sb->buf;
        strbuf_release(&tmpl);
        return rest_is_empty(sb, start - sb->buf);
@@@ -1061,8 -1005,7 +1073,8 @@@ static const char *find_author_by_nickn
        revs.mailmap = &mailmap;
        read_mailmap(revs.mailmap, NULL);
  
 -      prepare_revision_walk(&revs);
 +      if (prepare_revision_walk(&revs))
 +              die(_("revision walk setup failed"));
        commit = get_revision(&revs);
        if (commit) {
                struct pretty_print_context ctx = {0};
@@@ -1416,24 -1359,6 +1428,24 @@@ int cmd_status(int argc, const char **a
        return 0;
  }
  
 +static const char *implicit_ident_advice(void)
 +{
 +      char *user_config = NULL;
 +      char *xdg_config = NULL;
 +      int config_exists;
 +
 +      home_config_paths(&user_config, &xdg_config, "config");
 +      config_exists = file_exists(user_config) || file_exists(xdg_config);
 +      free(user_config);
 +      free(xdg_config);
 +
 +      if (config_exists)
 +              return _(implicit_ident_advice_config);
 +      else
 +              return _(implicit_ident_advice_noconfig);
 +
 +}
 +
  static void print_summary(const char *prefix, const unsigned char *sha1,
                          int initial_commit)
  {
                strbuf_addstr(&format, "\n Author: ");
                strbuf_addbuf_percentquote(&format, &author_ident);
        }
 +      if (author_date_is_interesting()) {
 +              struct strbuf date = STRBUF_INIT;
 +              format_commit_message(commit, "%ad", &date, &pctx);
 +              strbuf_addstr(&format, "\n Date: ");
 +              strbuf_addbuf_percentquote(&format, &date);
 +              strbuf_release(&date);
 +      }
        if (!committer_ident_sufficiently_given()) {
                strbuf_addstr(&format, "\n Committer: ");
                strbuf_addbuf_percentquote(&format, &committer_ident);
                if (advice_implicit_identity) {
                        strbuf_addch(&format, '\n');
 -                      strbuf_addstr(&format, _(implicit_ident_advice));
 +                      strbuf_addstr(&format, implicit_ident_advice());
                }
        }
        strbuf_release(&author_ident);
@@@ -1540,7 -1458,7 +1552,7 @@@ static int run_rewrite_hook(const unsig
  {
        /* oldsha1 SP newsha1 LF NUL */
        static char buf[2*40 + 3];
 -      struct child_process proc;
 +      struct child_process proc = CHILD_PROCESS_INIT;
        const char *argv[3];
        int code;
        size_t n;
        argv[1] = "amend";
        argv[2] = NULL;
  
 -      memset(&proc, 0, sizeof(proc));
        proc.argv = argv;
        proc.in = -1;
        proc.stdout_to_stderr = 1;
@@@ -1651,12 -1570,11 +1663,12 @@@ int cmd_commit(int argc, const char **a
        const char *index_file, *reflog_msg;
        char *nl;
        unsigned char sha1[20];
 -      struct ref_lock *ref_lock;
        struct commit_list *parents = NULL, **pptr = &parents;
        struct stat statbuf;
        struct commit *current_head = NULL;
        struct commit_extra_header *extra = NULL;
 +      struct ref_transaction *transaction;
 +      struct strbuf err = STRBUF_INIT;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
                append_merge_tag_headers(parents, &tail);
        }
  
 -      if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
 -                               author_ident.buf, sign_commit, extra)) {
 +      if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1,
 +                       parents, sha1, author_ident.buf, sign_commit, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
        strbuf_release(&author_ident);
        free_commit_extra_headers(extra);
  
 -      ref_lock = lock_any_ref_for_update("HEAD",
 -                                         !current_head
 -                                         ? NULL
 -                                         : current_head->object.sha1,
 -                                         0, NULL);
 -
        nl = strchr(sb.buf, '\n');
        if (nl)
                strbuf_setlen(&sb, nl + 1 - sb.buf);
        strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
        strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
  
 -      if (!ref_lock) {
 -              rollback_index_files();
 -              die(_("cannot lock HEAD ref"));
 -      }
 -      if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
 +      transaction = ref_transaction_begin(&err);
 +      if (!transaction ||
 +          ref_transaction_update(transaction, "HEAD", sha1,
 +                                 current_head
 +                                 ? current_head->object.sha1 : NULL,
 +                                 0, !!current_head, &err) ||
 +          ref_transaction_commit(transaction, sb.buf, &err)) {
                rollback_index_files();
 -              die(_("cannot update HEAD ref"));
 +              die("%s", err.buf);
        }
 +      ref_transaction_free(transaction);
  
        unlink(git_path("CHERRY_PICK_HEAD"));
        unlink(git_path("REVERT_HEAD"));
        if (!quiet)
                print_summary(prefix, sha1, !current_head);
  
 +      strbuf_release(&err);
        return 0;
  }
diff --combined cache-tree.c
index c53f7de2b13acfc1b97c60b04552e57791377442,57597ac8b13e6d681913b2b40098c429a08fd71f..75a54fdc7232e5693b042aa06540d51a99f01551
@@@ -98,7 -98,7 +98,7 @@@ struct cache_tree_sub *cache_tree_sub(s
        return find_subtree(it, path, pathlen, 1);
  }
  
 -void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
 +static int do_invalidate_path(struct cache_tree *it, const char *path)
  {
        /* a/b/c
         * ==> invalidate self
  #endif
  
        if (!it)
 -              return;
 +              return 0;
        slash = strchrnul(path, '/');
        namelen = slash - path;
        it->entry_count = -1;
                                (it->subtree_nr - pos - 1));
                        it->subtree_nr--;
                }
 -              return;
 +              return 1;
        }
        down = find_subtree(it, path, namelen, 0);
        if (down)
 -              cache_tree_invalidate_path(down->cache_tree, slash + 1);
 +              do_invalidate_path(down->cache_tree, slash + 1);
 +      return 1;
  }
  
 -static int verify_cache(const struct cache_entry * const *cache,
 +void cache_tree_invalidate_path(struct index_state *istate, const char *path)
 +{
 +      if (do_invalidate_path(istate->cache_tree, path))
 +              istate->cache_changed |= CACHE_TREE_CHANGED;
 +}
 +
 +static int verify_cache(struct cache_entry **cache,
                        int entries, int flags)
  {
        int i, funny;
@@@ -236,7 -229,7 +236,7 @@@ int cache_tree_fully_valid(struct cache
  }
  
  static int update_one(struct cache_tree *it,
 -                    const struct cache_entry * const *cache,
 +                    struct cache_entry **cache,
                      int entries,
                      const char *base,
                      int baselen,
        struct strbuf buffer;
        int missing_ok = flags & WRITE_TREE_MISSING_OK;
        int dryrun = flags & WRITE_TREE_DRY_RUN;
+       int repair = flags & WRITE_TREE_REPAIR;
        int to_invalidate = 0;
        int i;
  
+       assert(!(dryrun && repair));
        *skip_count = 0;
  
        if (0 <= it->entry_count && has_sha1_file(it->sha1))
                int pathlen, entlen;
                const unsigned char *sha1;
                unsigned mode;
+               int expected_missing = 0;
  
                path = ce->name;
                pathlen = ce_namelen(ce);
                        i += sub->count;
                        sha1 = sub->cache_tree->sha1;
                        mode = S_IFDIR;
-                       if (sub->cache_tree->entry_count < 0)
+                       if (sub->cache_tree->entry_count < 0) {
                                to_invalidate = 1;
+                               expected_missing = 1;
+                       }
                }
                else {
                        sha1 = ce->sha1;
                }
                if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) {
                        strbuf_release(&buffer);
+                       if (expected_missing)
+                               return -1;
                        return error("invalid object %06o %s for '%.*s'",
                                mode, sha1_to_hex(sha1), entlen+baselen, path);
                }
  #endif
        }
  
-       if (dryrun)
+       if (repair) {
+               unsigned char sha1[20];
+               hash_sha1_file(buffer.buf, buffer.len, tree_type, sha1);
+               if (has_sha1_file(sha1))
+                       hashcpy(it->sha1, sha1);
+               else
+                       to_invalidate = 1;
+       } else if (dryrun)
                hash_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1);
        else if (write_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1)) {
                strbuf_release(&buffer);
        return i;
  }
  
 -int cache_tree_update(struct cache_tree *it,
 -                    const struct cache_entry * const *cache,
 -                    int entries,
 -                    int flags)
 +int cache_tree_update(struct index_state *istate, int flags)
  {
 -      int i, skip;
 -      i = verify_cache(cache, entries, flags);
 +      struct cache_tree *it = istate->cache_tree;
 +      struct cache_entry **cache = istate->cache;
 +      int entries = istate->cache_nr;
 +      int skip, i = verify_cache(cache, entries, flags);
 +
        if (i)
                return i;
        i = update_one(it, cache, entries, "", 0, &skip, flags);
        if (i < 0)
                return i;
 +      istate->cache_changed |= CACHE_TREE_CHANGED;
        return 0;
  }
  
@@@ -598,10 -605,13 +613,10 @@@ int write_cache_as_tree(unsigned char *
  
        was_valid = cache_tree_fully_valid(active_cache_tree);
        if (!was_valid) {
 -              if (cache_tree_update(active_cache_tree,
 -                                    (const struct cache_entry * const *)active_cache,
 -                                    active_nr, flags) < 0)
 +              if (cache_tree_update(&the_index, flags) < 0)
                        return WRITE_TREE_UNMERGED_INDEX;
                if (0 <= newfd) {
 -                      if (!write_cache(newfd, active_cache, active_nr) &&
 -                          !commit_lock_file(lock_file))
 +                      if (!write_locked_index(&the_index, lock_file, COMMIT_LOCK))
                                newfd = -1;
                }
                /* Not being able to write is fine -- we are only interested
@@@ -654,12 -664,11 +669,12 @@@ static void prime_cache_tree_rec(struc
        it->entry_count = cnt;
  }
  
 -void prime_cache_tree(struct cache_tree **it, struct tree *tree)
 +void prime_cache_tree(struct index_state *istate, struct tree *tree)
  {
 -      cache_tree_free(it);
 -      *it = cache_tree();
 -      prime_cache_tree_rec(*it, tree);
 +      cache_tree_free(&istate->cache_tree);
 +      istate->cache_tree = cache_tree();
 +      prime_cache_tree_rec(istate->cache_tree, tree);
 +      istate->cache_changed |= CACHE_TREE_CHANGED;
  }
  
  /*
@@@ -698,5 -707,7 +713,5 @@@ int update_main_cache_tree(int flags
  {
        if (!the_index.cache_tree)
                the_index.cache_tree = cache_tree();
 -      return cache_tree_update(the_index.cache_tree,
 -                               (const struct cache_entry * const *)the_index.cache,
 -                               the_index.cache_nr, flags);
 +      return cache_tree_update(&the_index, flags);
  }
diff --combined cache-tree.h
index b47ccec7f626d7cda34ed17661c1dc3e4f035d6a,666d18f8348ba4a6d25e8d1a301b2e1fac0ebaaf..aa7b3e4a0a9d4bbb29574d71e23c05349be4c6fc
@@@ -23,14 -23,14 +23,14 @@@ struct cache_tree 
  
  struct cache_tree *cache_tree(void);
  void cache_tree_free(struct cache_tree **);
 -void cache_tree_invalidate_path(struct cache_tree *, const char *);
 +void cache_tree_invalidate_path(struct index_state *, const char *);
  struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *);
  
  void cache_tree_write(struct strbuf *, struct cache_tree *root);
  struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
  
  int cache_tree_fully_valid(struct cache_tree *);
 -int cache_tree_update(struct cache_tree *, const struct cache_entry * const *, int, int);
 +int cache_tree_update(struct index_state *, int);
  
  int update_main_cache_tree(int);
  
@@@ -39,6 -39,7 +39,7 @@@
  #define WRITE_TREE_IGNORE_CACHE_TREE 2
  #define WRITE_TREE_DRY_RUN 4
  #define WRITE_TREE_SILENT 8
+ #define WRITE_TREE_REPAIR 16
  
  /* error return codes */
  #define WRITE_TREE_UNREADABLE_INDEX (-1)
@@@ -46,7 -47,7 +47,7 @@@
  #define WRITE_TREE_PREFIX_ERROR (-3)
  
  int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix);
 -void prime_cache_tree(struct cache_tree **, struct tree *);
 +void prime_cache_tree(struct index_state *, struct tree *);
  
  extern int cache_tree_matches_traversal(struct cache_tree *, struct name_entry *ent, struct traverse_info *info);
  
diff --combined test-dump-cache-tree.c
index 330ba4f4dd23e2cf21acbe8a7023eaca4ec09afe,cbbbd8e41222da715ae272008c480426dead367f..54c0872fcb18edb4ca28a63fdfd4be3a17df388e
@@@ -26,16 -26,16 +26,16 @@@ static int dump_cache_tree(struct cache
                return 0;
  
        if (it->entry_count < 0) {
+               /* invalid */
                dump_one(it, pfx, "");
                dump_one(ref, pfx, "#(ref) ");
-               if (it->subtree_nr != ref->subtree_nr)
-                       errs = 1;
        }
        else {
                dump_one(it, pfx, "");
                if (hashcmp(it->sha1, ref->sha1) ||
                    ref->entry_count != it->entry_count ||
                    ref->subtree_nr != it->subtree_nr) {
+                       /* claims to be valid but is lying */
                        dump_one(ref, pfx, "#(ref) ");
                        errs = 1;
                }
  
  int main(int ac, char **av)
  {
 +      struct index_state istate;
        struct cache_tree *another = cache_tree();
        if (read_cache() < 0)
                die("unable to read index file");
 -      cache_tree_update(another,
 -                        (const struct cache_entry * const *)active_cache,
 -                        active_nr, WRITE_TREE_DRY_RUN);
 +      istate = the_index;
 +      istate.cache_tree = another;
 +      cache_tree_update(&istate, WRITE_TREE_DRY_RUN);
        return dump_cache_tree(active_cache_tree, another, "");
  }