Merge branch 'ja/do-not-ask-needless-questions'
authorJunio C Hamano <gitster@pobox.com>
Mon, 29 May 2017 03:34:48 +0000 (12:34 +0900)
committerJunio C Hamano <gitster@pobox.com>
Mon, 29 May 2017 03:34:48 +0000 (12:34 +0900)
Git sometimes gives an advice in a rhetorical question that does
not require an answer, which can confuse new users and non native
speakers. Attempt to rephrase them.

* ja/do-not-ask-needless-questions:
git-filter-branch: be more direct in an error message
read-tree -m: make error message for merging 0 trees less smart aleck
usability: don't ask questions if no reply is required

1  2 
Documentation/git-read-tree.txt
builtin/am.c
builtin/checkout.c
builtin/read-tree.c
index ed9d63ef4a35fa5ba70b1b7f1ffd393f517be030,6bb725bffb3be59b65d9513e720efc9ee7f866d5..02576d8c0ae6008fc22d7c7c706909efde5856da
@@@ -115,12 -115,6 +115,12 @@@ OPTION
        directories the index file and index output file are
        located in.
  
 +--[no-]recurse-submodules::
 +      Using --recurse-submodules will update the content of all initialized
 +      submodules according to the commit recorded in the superproject by
 +      calling read-tree recursively, also setting the submodules HEAD to be
 +      detached at that commit.
 +
  --no-sparse-checkout::
        Disable sparse checkout support even if `core.sparseCheckout`
        is true.
@@@ -137,7 -131,7 +137,7 @@@ Mergin
  -------
  If `-m` is specified, 'git read-tree' can perform 3 kinds of
  merge, a single tree merge if only 1 tree is given, a
- fast-forward merge with 2 trees, or a 3-way merge if 3 trees are
+ fast-forward merge with 2 trees, or a 3-way merge if 3 or more trees are
  provided.
  
  
diff --combined builtin/am.c
index 8b218f9978628534ab47db80ab66f880553f4065,38944f79022f9dbc0805a529b83b5c6b2307cbcd..0f63dcab1610f111f19b513eea47089132c1c702
@@@ -134,15 -134,17 +134,15 @@@ struct am_state 
  };
  
  /**
 - * Initializes am_state with the default values. The state directory is set to
 - * dir.
 + * Initializes am_state with the default values.
   */
 -static void am_state_init(struct am_state *state, const char *dir)
 +static void am_state_init(struct am_state *state)
  {
        int gpgsign;
  
        memset(state, 0, sizeof(*state));
  
 -      assert(dir);
 -      state->dir = xstrdup(dir);
 +      state->dir = git_pathdup("rebase-apply");
  
        state->prec = 4;
  
@@@ -760,18 -762,14 +760,18 @@@ static int split_mail_conv(mail_conv_f
                mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
  
                out = fopen(mail, "w");
 -              if (!out)
 +              if (!out) {
 +                      if (in != stdin)
 +                              fclose(in);
                        return error_errno(_("could not open '%s' for writing"),
                                           mail);
 +              }
  
                ret = fn(out, in, keep_cr);
  
                fclose(out);
 -              fclose(in);
 +              if (in != stdin)
 +                      fclose(in);
  
                if (ret)
                        return error(_("could not parse patch '%s'"), *paths);
@@@ -879,12 -877,12 +879,12 @@@ static int hg_patch_to_mail(FILE *out, 
                if (skip_prefix(sb.buf, "# User ", &str))
                        fprintf(out, "From: %s\n", str);
                else if (skip_prefix(sb.buf, "# Date ", &str)) {
 -                      unsigned long timestamp;
 +                      timestamp_t timestamp;
                        long tz, tz2;
                        char *end;
  
                        errno = 0;
 -                      timestamp = strtoul(str, &end, 10);
 +                      timestamp = parse_timestamp(str, &end, 10);
                        if (errno)
                                return error(_("invalid timestamp"));
  
@@@ -1051,7 -1049,7 +1051,7 @@@ static void am_setup(struct am_state *s
        } else {
                write_state_text(state, "abort-safety", "");
                if (!state->rebasing)
 -                      delete_ref("ORIG_HEAD", NULL, 0);
 +                      delete_ref(NULL, "ORIG_HEAD", NULL, 0);
        }
  
        /*
@@@ -1145,7 -1143,7 +1145,7 @@@ static int index_has_changes(struct str
                DIFF_OPT_SET(&opt, EXIT_WITH_STATUS);
                if (!sb)
                        DIFF_OPT_SET(&opt, QUICK);
 -              do_diff_cache(head.hash, &opt);
 +              do_diff_cache(&head, &opt);
                diffcore_std(&opt);
                for (i = 0; sb && i < diff_queued_diff.nr; i++) {
                        if (i)
@@@ -1183,39 -1181,42 +1183,39 @@@ static void NORETURN die_user_resolve(c
        exit(128);
  }
  
 -static void am_signoff(struct strbuf *sb)
 +/**
 + * Appends signoff to the "msg" field of the am_state.
 + */
 +static void am_append_signoff(struct am_state *state)
  {
        char *cp;
        struct strbuf mine = STRBUF_INIT;
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
  
 -      /* Does it end with our own sign-off? */
 +      /* our sign-off */
        strbuf_addf(&mine, "\n%s%s\n",
                    sign_off_header,
                    fmt_name(getenv("GIT_COMMITTER_NAME"),
                             getenv("GIT_COMMITTER_EMAIL")));
 -      if (mine.len < sb->len &&
 -          !strcmp(mine.buf, sb->buf + sb->len - mine.len))
 +
 +      /* Does sb end with it already? */
 +      if (mine.len < sb.len &&
 +          !strcmp(mine.buf, sb.buf + sb.len - mine.len))
                goto exit; /* no need to duplicate */
  
        /* Does it have any Signed-off-by: in the text */
 -      for (cp = sb->buf;
 +      for (cp = sb.buf;
             cp && *cp && (cp = strstr(cp, sign_off_header)) != NULL;
             cp = strchr(cp, '\n')) {
 -              if (sb->buf == cp || cp[-1] == '\n')
 +              if (sb.buf == cp || cp[-1] == '\n')
                        break;
        }
  
 -      strbuf_addstr(sb, mine.buf + !!cp);
 +      strbuf_addstr(&sb, mine.buf + !!cp);
  exit:
        strbuf_release(&mine);
 -}
 -
 -/**
 - * Appends signoff to the "msg" field of the am_state.
 - */
 -static void am_append_signoff(struct am_state *state)
 -{
 -      struct strbuf sb = STRBUF_INIT;
 -
 -      strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
 -      am_signoff(&sb);
        state->msg = strbuf_detach(&sb, &state->msg_len);
  }
  
@@@ -1312,7 -1313,7 +1312,7 @@@ static int parse_mail(struct am_state *
        }
  
        if (is_empty_file(am_path(state, "patch"))) {
-               printf_ln(_("Patch is empty. Was it split wrong?"));
+               printf_ln(_("Patch is empty."));
                die_user_resolve(state);
        }
  
        strbuf_addbuf(&msg, &mi.log_message);
        strbuf_stripspace(&msg, 0);
  
 -      if (state->signoff)
 -              am_signoff(&msg);
 -
        assert(!state->author_name);
        state->author_name = strbuf_detach(&author_name, NULL);
  
@@@ -1351,16 -1355,19 +1351,16 @@@ static int get_mail_commit_oid(struct o
        struct strbuf sb = STRBUF_INIT;
        FILE *fp = xfopen(mail, "r");
        const char *x;
 +      int ret = 0;
  
 -      if (strbuf_getline_lf(&sb, fp))
 -              return -1;
 -
 -      if (!skip_prefix(sb.buf, "From ", &x))
 -              return -1;
 -
 -      if (get_oid_hex(x, commit_id) < 0)
 -              return -1;
 +      if (strbuf_getline_lf(&sb, fp) ||
 +          !skip_prefix(sb.buf, "From ", &x) ||
 +          get_oid_hex(x, commit_id) < 0)
 +              ret = -1;
  
        strbuf_release(&sb);
        fclose(fp);
 -      return 0;
 +      return ret;
  }
  
  /**
   */
  static void get_commit_info(struct am_state *state, struct commit *commit)
  {
 -      const char *buffer, *ident_line, *author_date, *msg;
 +      const char *buffer, *ident_line, *msg;
        size_t ident_len;
 -      struct ident_split ident_split;
 -      struct strbuf sb = STRBUF_INIT;
 +      struct ident_split id;
  
        buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());
  
        ident_line = find_commit_header(buffer, "author", &ident_len);
  
 -      if (split_ident_line(&ident_split, ident_line, ident_len) < 0) {
 -              strbuf_add(&sb, ident_line, ident_len);
 -              die(_("invalid ident line: %s"), sb.buf);
 -      }
 +      if (split_ident_line(&id, ident_line, ident_len) < 0)
 +              die(_("invalid ident line: %.*s"), (int)ident_len, ident_line);
  
        assert(!state->author_name);
 -      if (ident_split.name_begin) {
 -              strbuf_add(&sb, ident_split.name_begin,
 -                      ident_split.name_end - ident_split.name_begin);
 -              state->author_name = strbuf_detach(&sb, NULL);
 -      } else
 +      if (id.name_begin)
 +              state->author_name =
 +                      xmemdupz(id.name_begin, id.name_end - id.name_begin);
 +      else
                state->author_name = xstrdup("");
  
        assert(!state->author_email);
 -      if (ident_split.mail_begin) {
 -              strbuf_add(&sb, ident_split.mail_begin,
 -                      ident_split.mail_end - ident_split.mail_begin);
 -              state->author_email = strbuf_detach(&sb, NULL);
 -      } else
 +      if (id.mail_begin)
 +              state->author_email =
 +                      xmemdupz(id.mail_begin, id.mail_end - id.mail_begin);
 +      else
                state->author_email = xstrdup("");
  
 -      author_date = show_ident_date(&ident_split, DATE_MODE(NORMAL));
 -      strbuf_addstr(&sb, author_date);
        assert(!state->author_date);
 -      state->author_date = strbuf_detach(&sb, NULL);
 +      state->author_date = xstrdup(show_ident_date(&id, DATE_MODE(NORMAL)));
  
        assert(!state->msg);
        msg = strstr(buffer, "\n\n");
                die(_("unable to parse commit %s"), oid_to_hex(&commit->object.oid));
        state->msg = xstrdup(msg + 2);
        state->msg_len = strlen(state->msg);
 +      unuse_commit_buffer(commit, buffer);
  }
  
  /**
@@@ -1444,9 -1457,9 +1444,9 @@@ static void write_index_patch(const str
        FILE *fp;
  
        if (!get_sha1_tree("HEAD", head.hash))
 -              tree = lookup_tree(head.hash);
 +              tree = lookup_tree(&head);
        else
 -              tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
 +              tree = lookup_tree(&empty_tree_oid);
  
        fp = xfopen(am_path(state, "patch"), "w");
        init_revisions(&rev_info, NULL);
@@@ -1479,7 -1492,7 +1479,7 @@@ static int parse_mail_rebase(struct am_
        if (get_mail_commit_oid(&commit_oid, mail) < 0)
                die(_("could not parse %s"), mail);
  
 -      commit = lookup_commit_or_die(commit_oid.hash, mail);
 +      commit = lookup_commit_or_die(&commit_oid, mail);
  
        get_commit_info(state, commit);
  
@@@ -1609,7 -1622,7 +1609,7 @@@ static int fall_back_threeway(const str
                init_revisions(&rev_info, NULL);
                rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
                diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);
 -              add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0);
 +              add_pending_oid(&rev_info, "HEAD", &our_tree, 0);
                diff_setup_done(&rev_info.diffopt);
                run_diff_index(&rev_info, 1);
        }
@@@ -1674,7 -1687,7 +1674,7 @@@ static void do_commit(const struct am_s
  
        if (!get_sha1_commit("HEAD", parent.hash)) {
                old_oid = &parent;
 -              commit_list_insert(lookup_commit(parent.hash), &parents);
 +              commit_list_insert(lookup_commit(&parent), &parents);
        } else {
                old_oid = NULL;
                say(state, stderr, _("applying to an empty history"));
@@@ -1835,9 -1848,6 +1835,9 @@@ static void am_run(struct am_state *sta
                        if (skip)
                                goto next; /* mail should be skipped */
  
 +                      if (state->signoff)
 +                              am_append_signoff(state);
 +
                        write_author_script(state);
                        write_commit_msg(state);
                }
@@@ -1931,7 -1941,8 +1931,8 @@@ static void am_resolve(struct am_state 
  
        if (unmerged_cache()) {
                printf_ln(_("You still have unmerged paths in your index.\n"
-                       "Did you forget to use 'git add'?"));
+                       "You should 'git add' each file with resolved conflicts to mark them as such.\n"
+                       "You might run `git rm` on a file to accept \"deleted by them\" for it."));
                die_user_resolve(state);
        }
  
@@@ -2036,11 -2047,11 +2037,11 @@@ static int clean_index(const struct obj
        struct tree *head_tree, *remote_tree, *index_tree;
        struct object_id index;
  
 -      head_tree = parse_tree_indirect(head->hash);
 +      head_tree = parse_tree_indirect(head);
        if (!head_tree)
                return error(_("Could not parse object '%s'."), oid_to_hex(head));
  
 -      remote_tree = parse_tree_indirect(remote->hash);
 +      remote_tree = parse_tree_indirect(remote);
        if (!remote_tree)
                return error(_("Could not parse object '%s'."), oid_to_hex(remote));
  
        if (write_cache_as_tree(index.hash, 0, NULL))
                return -1;
  
 -      index_tree = parse_tree_indirect(index.hash);
 +      index_tree = parse_tree_indirect(&index);
        if (!index_tree)
                return error(_("Could not parse object '%s'."), oid_to_hex(&index));
  
@@@ -2147,7 -2158,7 +2148,7 @@@ static void am_abort(struct am_state *s
        am_rerere_clear();
  
        curr_branch = resolve_refdup("HEAD", 0, curr_head.hash, NULL);
 -      has_curr_head = !is_null_oid(&curr_head);
 +      has_curr_head = curr_branch && !is_null_oid(&curr_head);
        if (!has_curr_head)
                hashcpy(curr_head.hash, EMPTY_TREE_SHA1_BIN);
  
                                has_curr_head ? &curr_head : NULL, 0,
                                UPDATE_REFS_DIE_ON_ERR);
        else if (curr_branch)
 -              delete_ref(curr_branch, NULL, REF_NODEREF);
 +              delete_ref(NULL, curr_branch, NULL, REF_NODEREF);
  
        free(curr_branch);
        am_destroy(state);
@@@ -2312,7 -2323,7 +2313,7 @@@ int cmd_am(int argc, const char **argv
  
        git_config(git_am_config, NULL);
  
 -      am_state_init(&state, git_path("rebase-apply"));
 +      am_state_init(&state);
  
        in_progress = am_in_progress(&state);
        if (in_progress)
diff --combined builtin/checkout.c
index b6249a3a39eea52587c1515a1c54b4263e630a83,8d313ceb523ea8c4b1126f47631b48dfb4e3d747..a6b2af39d3e881480193a38ff1ca3b65cdc55d94
  #include "submodule-config.h"
  #include "submodule.h"
  
 +static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 +
  static const char * const checkout_usage[] = {
        N_("git checkout [<options>] <branch>"),
        N_("git checkout [<options>] [<branch>] -- <file>..."),
        NULL,
  };
  
 +static int option_parse_recurse_submodules(const struct option *opt,
 +                                         const char *arg, int unset)
 +{
 +      if (unset) {
 +              recurse_submodules = RECURSE_SUBMODULES_OFF;
 +              return 0;
 +      }
 +      if (arg)
 +              recurse_submodules =
 +                      parse_update_recurse_submodules_arg(opt->long_name,
 +                                                          arg);
 +      else
 +              recurse_submodules = RECURSE_SUBMODULES_ON;
 +
 +      return 0;
 +}
 +
  struct checkout_opts {
        int patch_mode;
        int quiet;
@@@ -235,24 -216,22 +235,24 @@@ static int checkout_merged(int pos, con
        /*
         * NEEDSWORK:
         * There is absolutely no reason to write this as a blob object
 -       * and create a phony cache entry just to leak.  This hack is
 -       * primarily to get to the write_entry() machinery that massages
 -       * the contents to work-tree format and writes out which only
 -       * allows it for a cache entry.  The code in write_entry() needs
 -       * to be refactored to allow us to feed a <buffer, size, mode>
 -       * instead of a cache entry.  Such a refactoring would help
 -       * merge_recursive as well (it also writes the merge result to the
 -       * object database even when it may contain conflicts).
 +       * and create a phony cache entry.  This hack is primarily to get
 +       * to the write_entry() machinery that massages the contents to
 +       * work-tree format and writes out which only allows it for a
 +       * cache entry.  The code in write_entry() needs to be refactored
 +       * to allow us to feed a <buffer, size, mode> instead of a cache
 +       * entry.  Such a refactoring would help merge_recursive as well
 +       * (it also writes the merge result to the object database even
 +       * when it may contain conflicts).
         */
        if (write_sha1_file(result_buf.ptr, result_buf.size,
                            blob_type, oid.hash))
                die(_("Unable to add merge result for '%s'"), path);
 +      free(result_buf.ptr);
        ce = make_cache_entry(mode, oid.hash, path, 2, 0);
        if (!ce)
                die(_("make_cache_entry failed for path '%s'"), path);
        status = checkout_entry(ce, state, NULL);
 +      free(ce);
        return status;
  }
  
@@@ -395,7 -374,7 +395,7 @@@ static int checkout_paths(const struct 
                die(_("unable to write new index file"));
  
        read_ref_full("HEAD", 0, rev.hash, NULL);
 -      head = lookup_commit_reference_gently(rev.hash, 1);
 +      head = lookup_commit_reference_gently(&rev, 1);
  
        errs |= post_checkout_hook(head, head, 0);
        return errs;
@@@ -529,10 -508,10 +529,10 @@@ static int merge_working_tree(const str
                        setup_standard_excludes(topts.dir);
                }
                tree = parse_tree_indirect(old->commit ?
 -                                         old->commit->object.oid.hash :
 -                                         EMPTY_TREE_SHA1_BIN);
 +                                         &old->commit->object.oid :
 +                                         &empty_tree_oid);
                init_tree_desc(&trees[0], tree->buffer, tree->size);
 -              tree = parse_tree_indirect(new->commit->object.oid.hash);
 +              tree = parse_tree_indirect(&new->commit->object.oid);
                init_tree_desc(&trees[1], tree->buffer, tree->size);
  
                ret = unpack_trees(2, trees, &topts);
@@@ -723,7 -702,7 +723,7 @@@ static int add_pending_uninteresting_re
                                         const struct object_id *oid,
                                         int flags, void *cb_data)
  {
 -      add_pending_sha1(cb_data, refname, oid->hash, UNINTERESTING);
 +      add_pending_oid(cb_data, refname, oid, UNINTERESTING);
        return 0;
  }
  
@@@ -809,7 -788,7 +809,7 @@@ static void orphaned_commit_warning(str
        add_pending_object(&revs, object, oid_to_hex(&object->oid));
  
        for_each_ref(add_pending_uninteresting_ref, &revs);
 -      add_pending_sha1(&revs, "HEAD", new->object.oid.hash, UNINTERESTING);
 +      add_pending_oid(&revs, "HEAD", &new->object.oid, UNINTERESTING);
  
        refs = revs.pending;
        revs.leak_pending = 1;
@@@ -835,8 -814,7 +835,8 @@@ static int switch_branches(const struc
        int flag, writeout_error = 0;
        memset(&old, 0, sizeof(old));
        old.path = path_to_free = resolve_refdup("HEAD", 0, rev.hash, &flag);
 -      old.commit = lookup_commit_reference_gently(rev.hash, 1);
 +      if (old.path)
 +              old.commit = lookup_commit_reference_gently(&rev, 1);
        if (!(flag & REF_ISSYMREF))
                old.path = NULL;
  
@@@ -911,10 -889,11 +911,10 @@@ static int check_tracking_name(struct r
  static const char *unique_tracking_name(const char *name, struct object_id *oid)
  {
        struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
 -      char src_ref[PATH_MAX];
 -      snprintf(src_ref, PATH_MAX, "refs/heads/%s", name);
 -      cb_data.src_ref = src_ref;
 +      cb_data.src_ref = xstrfmt("refs/heads/%s", name);
        cb_data.dst_oid = oid;
        for_each_remote(check_tracking_name, &cb_data);
 +      free(cb_data.src_ref);
        if (cb_data.unique)
                return cb_data.dst_ref;
        free(cb_data.dst_ref);
@@@ -1050,10 -1029,10 +1050,10 @@@ static int parse_branchname_arg(int arg
        else
                new->path = NULL; /* not an existing branch */
  
 -      new->commit = lookup_commit_reference_gently(rev->hash, 1);
 +      new->commit = lookup_commit_reference_gently(rev, 1);
        if (!new->commit) {
                /* not a commit */
 -              *source_tree = parse_tree_indirect(rev->hash);
 +              *source_tree = parse_tree_indirect(rev);
        } else {
                parse_commit_or_die(new->commit);
                *source_tree = new->commit->tree;
@@@ -1184,9 -1163,6 +1184,9 @@@ int cmd_checkout(int argc, const char *
                                N_("second guess 'git checkout <no-such-branch>'")),
                OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
                         N_("do not check if another worktree is holding the given ref")),
 +              { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
 +                          "checkout", "control recursive updating of submodules",
 +                          PARSE_OPT_OPTARG, option_parse_recurse_submodules },
                OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
                OPT_END(),
        };
                git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
        }
  
 +      if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
 +              git_config(submodule_config, NULL);
 +              if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT)
 +                      set_config_update_recurse_submodules(recurse_submodules);
 +      }
 +
        if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
                die(_("-b, -B and --orphan are mutually exclusive"));
  
                 * new_branch && argc > 1 will be caught later.
                 */
                if (opts.new_branch && argc == 1)
-                       die(_("Cannot update paths and switch to branch '%s' at the same time.\n"
-                             "Did you intend to checkout '%s' which can not be resolved as commit?"),
-                           opts.new_branch, argv[0]);
+                       die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
+                               argv[0], opts.new_branch);
  
                if (opts.force_detach)
                        die(_("git checkout: --detach does not take a path argument '%s'"),
diff --combined builtin/read-tree.c
index daf8d7795d5c7e120abacb9a1324869ba81eeb19,8eae1e88e86895fd8578a6e0cb39b4e8eba9a131..78d3193659e06b4969324153689f219f1cd1c1b3
  #include "builtin.h"
  #include "parse-options.h"
  #include "resolve-undo.h"
 +#include "submodule.h"
 +#include "submodule-config.h"
  
  static int nr_trees;
  static int read_empty;
  static struct tree *trees[MAX_UNPACK_TREES];
 +static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
  
 -static int list_tree(unsigned char *sha1)
 +static int list_tree(struct object_id *oid)
  {
        struct tree *tree;
  
        if (nr_trees >= MAX_UNPACK_TREES)
                die("I cannot read more than %d trees", MAX_UNPACK_TREES);
 -      tree = parse_tree_indirect(sha1);
 +      tree = parse_tree_indirect(oid);
        if (!tree)
                return -1;
        trees[nr_trees++] = tree;
@@@ -99,29 -96,12 +99,29 @@@ static int debug_merge(const struct cac
        return 0;
  }
  
 +static int option_parse_recurse_submodules(const struct option *opt,
 +                                         const char *arg, int unset)
 +{
 +      if (unset) {
 +              recurse_submodules = RECURSE_SUBMODULES_OFF;
 +              return 0;
 +      }
 +      if (arg)
 +              recurse_submodules =
 +                      parse_update_recurse_submodules_arg(opt->long_name,
 +                                                          arg);
 +      else
 +              recurse_submodules = RECURSE_SUBMODULES_ON;
 +
 +      return 0;
 +}
 +
  static struct lock_file lock_file;
  
  int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
  {
        int i, stage = 0;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct tree_desc t[MAX_UNPACK_TREES];
        struct unpack_trees_options opts;
        int prefix_set = 0;
                         N_("skip applying sparse checkout filter")),
                OPT_BOOL(0, "debug-unpack", &opts.debug_unpack,
                         N_("debug unpack-trees")),
 +              { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
 +                          "checkout", "control recursive updating of submodules",
 +                          PARSE_OPT_OPTARG, option_parse_recurse_submodules },
                OPT_END()
        };
  
  
        hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
  
 +      if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) {
 +              gitmodules_config();
 +              git_config(submodule_config, NULL);
 +              set_config_update_recurse_submodules(RECURSE_SUBMODULES_ON);
 +      }
 +
        prefix_set = opts.prefix ? 1 : 0;
        if (1 < opts.merge + opts.reset + prefix_set)
                die("Which one? -m, --reset, or --prefix?");
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
  
 -              if (get_sha1(arg, sha1))
 +              if (get_oid(arg, &oid))
                        die("Not a valid object name %s", arg);
 -              if (list_tree(sha1) < 0)
 +              if (list_tree(&oid) < 0)
                        die("failed to unpack tree object %s", arg);
                stage++;
        }
 -      if (nr_trees == 0 && !read_empty)
 +      if (!nr_trees && !read_empty && !opts.merge)
                warning("read-tree: emptying the index with no arguments is deprecated; use --empty");
        else if (nr_trees > 0 && read_empty)
                die("passing trees as arguments contradicts --empty");
                setup_work_tree();
  
        if (opts.merge) {
-               if (stage < 2)
-                       die("just how do you expect me to merge %d trees?", stage-1);
                switch (stage - 1) {
+               case 0:
+                       die("you must specify at least one tree to merge");
+                       break;
                case 1:
                        opts.fn = opts.prefix ? bind_merge : oneway_merge;
                        break;