Merge branch 'jc/rerere'
authorJunio C Hamano <gitster@pobox.com>
Tue, 19 Jan 2010 02:13:01 +0000 (18:13 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 19 Jan 2010 02:13:01 +0000 (18:13 -0800)
* jc/rerere:
Teach --[no-]rerere-autoupdate option to merge, revert and friends

1  2 
builtin-commit.c
builtin-merge.c
builtin-rerere.c
git-rebase.sh
diff --combined builtin-commit.c
index e64487121059b1b4a618828095375c1a42e4e93e,72e0f0b563ee08993f0b73fa868ad525edc5f8ab..69241f8ed6245b4f80f8758540bdd1aba926c4ce
@@@ -24,7 -24,6 +24,7 @@@
  #include "string-list.h"
  #include "rerere.h"
  #include "unpack-trees.h"
 +#include "quote.h"
  
  static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@@ -36,7 -35,7 +36,7 @@@ static const char * const builtin_statu
        NULL
  };
  
 -static unsigned char head_sha1[20], merge_head_sha1[20];
 +static unsigned char head_sha1[20];
  static char *use_message_buffer;
  static const char commit_editmsg[] = "COMMIT_EDITMSG";
  static struct lock_file index_lock; /* real index */
@@@ -53,7 -52,7 +53,7 @@@ static char *edit_message, *use_message
  static char *author_name, *author_email, *author_date;
  static int all, edit_flag, also, interactive, only, amend, signoff;
  static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 -static char *untracked_files_arg;
 +static char *untracked_files_arg, *force_date;
  /*
   * The default commit message cleanup mode will remove the lines
   * beginning with # (shell comments) and leading and trailing
@@@ -68,17 -67,10 +68,17 @@@ static enum 
  } cleanup_mode;
  static char *cleanup_arg;
  
 -static int use_editor = 1, initial_commit, in_merge;
 +static int use_editor = 1, initial_commit, in_merge, include_status = 1;
  static const char *only_include_assumed;
  static struct strbuf message;
  
 +static int null_termination;
 +static enum {
 +      STATUS_FORMAT_LONG,
 +      STATUS_FORMAT_SHORT,
 +      STATUS_FORMAT_PORCELAIN,
 +} status_format = STATUS_FORMAT_LONG;
 +
  static int opt_parse_m(const struct option *opt, const char *arg, int unset)
  {
        struct strbuf *buf = opt->value;
  static struct option builtin_commit_options[] = {
        OPT__QUIET(&quiet),
        OPT__VERBOSE(&verbose),
 -      OPT_GROUP("Commit message options"),
  
 +      OPT_GROUP("Commit message options"),
        OPT_FILENAME('F', "file", &logfile, "read log from file"),
        OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
 +      OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
        OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
        OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
        OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
        OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
 +      OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
 +      OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
 +      /* end commit message options */
  
        OPT_GROUP("Commit contents options"),
        OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
        OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
        OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
        OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
 +      OPT_SET_INT(0, "short", &status_format, "show status concisely",
 +                  STATUS_FORMAT_SHORT),
 +      OPT_SET_INT(0, "porcelain", &status_format,
 +                  "show porcelain output format", STATUS_FORMAT_PORCELAIN),
 +      OPT_BOOLEAN('z', "null", &null_termination,
 +                  "terminate entries with NUL"),
        OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
        { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
 -      OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
 +      /* end commit contents options */
  
        OPT_END()
  };
@@@ -184,15 -166,11 +184,15 @@@ static int list_paths(struct string_lis
  
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
 +              struct string_list_item *item;
 +
                if (ce->ce_flags & CE_UPDATE)
                        continue;
                if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
                        continue;
 -              string_list_insert(ce->name, list);
 +              item = string_list_insert(ce->name, list);
 +              if (ce_skip_worktree(ce))
 +                      item->util = item; /* better a valid pointer than a fake one */
        }
  
        return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
@@@ -205,10 -183,6 +205,10 @@@ static void add_remove_files(struct str
                struct stat st;
                struct string_list_item *p = &(list->items[i]);
  
 +              /* p->util is skip-worktree */
 +              if (p->util)
 +                      continue;
 +
                if (!lstat(p->string, &st)) {
                        if (add_to_cache(p->string, &st, 0))
                                die("updating files failed");
@@@ -332,7 -306,7 +332,7 @@@ static char *prepare_index(int argc, co
         */
        commit_style = COMMIT_PARTIAL;
  
 -      if (file_exists(git_path("MERGE_HEAD")))
 +      if (in_merge)
                die("cannot do a partial commit during a merge.");
  
        memset(&partial, 0, sizeof(partial));
  static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
                      struct wt_status *s)
  {
 +      unsigned char sha1[20];
 +
        if (s->relative_paths)
                s->prefix = prefix;
  
        s->index_file = index_file;
        s->fp = fp;
        s->nowarn = nowarn;
 +      s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
  
 -      wt_status_print(s);
 +      wt_status_collect(s);
 +
 +      switch (status_format) {
 +      case STATUS_FORMAT_SHORT:
 +              wt_shortstatus_print(s, null_termination);
 +              break;
 +      case STATUS_FORMAT_PORCELAIN:
 +              wt_porcelain_print(s, null_termination);
 +              break;
 +      case STATUS_FORMAT_LONG:
 +              wt_status_print(s);
 +              break;
 +      }
  
        return s->commitable;
  }
@@@ -451,9 -410,6 +451,9 @@@ static void determine_author_info(void
                email = xstrndup(lb + 2, rb - (lb + 2));
        }
  
 +      if (force_date)
 +              date = force_date;
 +
        author_name = name;
        author_email = email;
        author_date = date;
@@@ -591,7 -547,7 +591,7 @@@ static int prepare_to_commit(const cha
  
        /* This checks if committer ident is explicitly given */
        git_committer_info(0);
 -      if (use_editor) {
 +      if (use_editor && include_status) {
                char *author_ident;
                const char *committer_ident;
  
@@@ -779,21 -735,6 +779,21 @@@ static const char *find_author_by_nickn
        die("No existing author found with '%s'", name);
  }
  
 +
 +static void handle_untracked_files_arg(struct wt_status *s)
 +{
 +      if (!untracked_files_arg)
 +              ; /* default already initialized */
 +      else if (!strcmp(untracked_files_arg, "no"))
 +              s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
 +      else if (!strcmp(untracked_files_arg, "normal"))
 +              s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 +      else if (!strcmp(untracked_files_arg, "all"))
 +              s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
 +      else
 +              die("Invalid untracked files mode '%s'", untracked_files_arg);
 +}
 +
  static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix,
        if (get_sha1("HEAD", head_sha1))
                initial_commit = 1;
  
 -      if (!get_sha1("MERGE_HEAD", merge_head_sha1))
 -              in_merge = 1;
 -
        /* Sanity check options */
        if (amend && initial_commit)
                die("You have nothing to amend.");
        else
                die("Invalid cleanup mode %s", cleanup_arg);
  
 -      if (!untracked_files_arg)
 -              ; /* default already initialized */
 -      else if (!strcmp(untracked_files_arg, "no"))
 -              s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
 -      else if (!strcmp(untracked_files_arg, "normal"))
 -              s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 -      else if (!strcmp(untracked_files_arg, "all"))
 -              s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
 -      else
 -              die("Invalid untracked files mode '%s'", untracked_files_arg);
 +      handle_untracked_files_arg(s);
  
        if (all && argc > 0)
                die("Paths with -a does not make sense.");
        else if (interactive && argc > 0)
                die("Paths with --interactive does not make sense.");
  
 +      if (null_termination && status_format == STATUS_FORMAT_LONG)
 +              status_format = STATUS_FORMAT_PORCELAIN;
 +      if (status_format != STATUS_FORMAT_LONG)
 +              dry_run = 1;
 +
        return argc;
  }
  
@@@ -942,7 -890,7 +942,7 @@@ static int parse_status_slot(const cha
                return WT_STATUS_NOBRANCH;
        if (!strcasecmp(var+offset, "unmerged"))
                return WT_STATUS_UNMERGED;
 -      die("bad config variable '%s'", var);
 +      return -1;
  }
  
  static int git_status_config(const char *k, const char *v, void *cb)
        }
        if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
                int slot = parse_status_slot(k, 13);
 +              if (slot < 0)
 +                      return 0;
                if (!v)
                        return config_error_nonbool(k);
                color_parse(v, k, s->color_palette[slot]);
  int cmd_status(int argc, const char **argv, const char *prefix)
  {
        struct wt_status s;
 +      unsigned char sha1[20];
 +      static struct option builtin_status_options[] = {
 +              OPT__VERBOSE(&verbose),
 +              OPT_SET_INT('s', "short", &status_format,
 +                          "show status concisely", STATUS_FORMAT_SHORT),
 +              OPT_SET_INT(0, "porcelain", &status_format,
 +                          "show porcelain output format",
 +                          STATUS_FORMAT_PORCELAIN),
 +              OPT_BOOLEAN('z', "null", &null_termination,
 +                          "terminate entries with NUL"),
 +              { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
 +                "mode",
 +                "show untracked files, optional modes: all, normal, no. (Default: all)",
 +                PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 +              OPT_END(),
 +      };
 +
 +      if (null_termination && status_format == STATUS_FORMAT_LONG)
 +              status_format = STATUS_FORMAT_PORCELAIN;
  
        wt_status_prepare(&s);
        git_config(git_status_config, &s);
 +      in_merge = file_exists(git_path("MERGE_HEAD"));
 +      argc = parse_options(argc, argv, prefix,
 +                           builtin_status_options,
 +                           builtin_status_usage, 0);
 +      handle_untracked_files_arg(&s);
 +
 +      if (*argv)
 +              s.pathspec = get_pathspec(prefix, argv);
 +
 +      read_cache();
 +      refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
 +      s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
 +      s.in_merge = in_merge;
 +      wt_status_collect(&s);
 +
 +      if (s.relative_paths)
 +              s.prefix = prefix;
        if (s.use_color == -1)
                s.use_color = git_use_color_default;
        if (diff_use_color_default == -1)
                diff_use_color_default = git_use_color_default;
  
 -      argc = parse_and_validate_options(argc, argv, builtin_status_usage,
 -                                        prefix, &s);
 -      return dry_run_commit(argc, argv, prefix, &s);
 +      switch (status_format) {
 +      case STATUS_FORMAT_SHORT:
 +              wt_shortstatus_print(&s, null_termination);
 +              break;
 +      case STATUS_FORMAT_PORCELAIN:
 +              wt_porcelain_print(&s, null_termination);
 +              break;
 +      case STATUS_FORMAT_LONG:
 +              s.verbose = verbose;
 +              wt_status_print(&s);
 +              break;
 +      }
 +      return 0;
  }
  
  static void print_summary(const char *prefix, const unsigned char *sha1)
@@@ -1106,10 -1006,6 +1106,10 @@@ static int git_commit_config(const cha
  
        if (!strcmp(k, "commit.template"))
                return git_config_pathname(&template_file, k, v);
 +      if (!strcmp(k, "commit.status")) {
 +              include_status = git_config_bool(k, v);
 +              return 0;
 +      }
  
        return git_status_config(k, v, s);
  }
@@@ -1128,11 -1024,10 +1128,11 @@@ int cmd_commit(int argc, const char **a
  
        wt_status_prepare(&s);
        git_config(git_commit_config, &s);
 +      in_merge = file_exists(git_path("MERGE_HEAD"));
 +      s.in_merge = in_merge;
  
        if (s.use_color == -1)
                s.use_color = git_use_color_default;
 -
        argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
                                          prefix, &s);
        if (dry_run) {
                     "new_index file. Check that disk is not full or quota is\n"
                     "not exceeded, and then \"git reset HEAD\" to recover.");
  
-       rerere();
+       rerere(0);
        run_hook(get_index_file(), "post-commit", NULL);
        if (!quiet)
                print_summary(prefix, commit_sha1);
diff --combined builtin-merge.c
index f1c84d759dd44f661fb76741d528e43702dfc901,c3faa6b9c3978267b979e74ddbb3a3d893c81dba..82e2a0491a0f3f148ff65851d44481d2809f64de
@@@ -52,6 -52,7 +52,7 @@@ static struct strategy **use_strategies
  static size_t use_strategies_nr, use_strategies_alloc;
  static const char *branch;
  static int verbosity;
+ static int allow_rerere_auto;
  
  static struct strategy all_strategy[] = {
        { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@@ -170,6 -171,7 +171,7 @@@ static struct option builtin_merge_opti
                "allow fast-forward (default)"),
        OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
                "abort if fast-forward is not possible"),
+       OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
        OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
                "merge strategy to use", option_parse_strategy),
        OPT_CALLBACK('m', "message", &merge_msg, "message",
@@@ -790,12 -792,17 +792,12 @@@ static int suggest_conflicts(void
                }
        }
        fclose(fp);
-       rerere();
+       rerere(allow_rerere_auto);
        printf("Automatic merge failed; "
                        "fix conflicts and then commit the result.\n");
        return 1;
  }
  
 -static const char deprecation_warning[] =
 -      "'git merge <msg> HEAD <commit>' is deprecated. Please update\n"
 -      "your script to use 'git merge -m <msg> <commit>' instead.\n"
 -      "In future versions of git, this syntax will be removed.";
 -
  static struct commit *is_old_style_invocation(int argc, const char **argv)
  {
        struct commit *second_token = NULL;
                        die("'%s' is not a commit", argv[1]);
                if (hashcmp(second_token->object.sha1, head))
                        return NULL;
 -              warning(deprecation_warning);
        }
        return second_token;
  }
diff --combined builtin-rerere.c
index 2be9ffb77b38ae687537f3599c9d11e3fbc026a9,7ec602cf55fd39ee5ae5b84831ccd5b583795631..5028138898bf0d95d969ea19b85d23af129912d0
@@@ -48,8 -48,6 +48,8 @@@ static void garbage_collect(struct stri
  
        git_config(git_rerere_gc_config, NULL);
        dir = opendir(git_path("rr-cache"));
 +      if (!dir)
 +              die_errno("unable to open rr-cache directory");
        while ((e = readdir(dir))) {
                if (is_dot_or_dotdot(e->d_name))
                        continue;
@@@ -103,15 -101,24 +103,24 @@@ static int diff_two(const char *file1, 
  int cmd_rerere(int argc, const char **argv, const char *prefix)
  {
        struct string_list merge_rr = { NULL, 0, 0, 1 };
-       int i, fd;
+       int i, fd, flags = 0;
+       if (2 < argc) {
+               if (!strcmp(argv[1], "-h"))
+                       usage(git_rerere_usage);
+               if (!strcmp(argv[1], "--rerere-autoupdate"))
+                       flags = RERERE_AUTOUPDATE;
+               else if (!strcmp(argv[1], "--no-rerere-autoupdate"))
+                       flags = RERERE_NOAUTOUPDATE;
+               if (flags) {
+                       argc--;
+                       argv++;
+               }
+       }
        if (argc < 2)
-               return rerere();
-       if (!strcmp(argv[1], "-h"))
-               usage(git_rerere_usage);
+               return rerere(flags);
  
-       fd = setup_rerere(&merge_rr);
+       fd = setup_rerere(&merge_rr, flags);
        if (fd < 0)
                return 0;
  
diff --combined git-rebase.sh
index 3de0942c0fa604fd0aed8561e3f2727c0a7887e5,398ea737160dbc60617e25d52bd253b6f6bf709f..eddc02875f3802844a4c284aaedef48417f4580d
@@@ -34,8 -34,6 +34,8 @@@ set_reflog_action rebas
  require_work_tree
  cd_to_toplevel
  
 +LF='
 +'
  OK_TO_SKIP_PRE_REBASE=
  RESOLVEMSG="
  When you have resolved this problem run \"git rebase --continue\".
@@@ -52,6 -50,7 +52,7 @@@ diffstat=$(git config --bool rebase.sta
  git_am_opt=
  rebase_root=
  force_rebase=
+ allow_rerere_autoupdate=
  
  continue_merge () {
        test -n "$prev_head" || die "prev_head must be defined"
@@@ -120,7 -119,7 +121,7 @@@ call_merge () 
                return
                ;;
        1)
-               git rerere
+               git rerere $allow_rerere_autoupdate
                die "$RESOLVEMSG"
                ;;
        2)
@@@ -351,6 -350,9 +352,9 @@@ d
        -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase)
                force_rebase=t
                ;;
+       --rerere-autoupdate|--no-rerere-autoupdate)
+               allow_rerere_autoupdate="$1"
+               ;;
        -*)
                usage
                ;;
  
  # Make sure the branch to rebase onto is valid.
  onto_name=${newbase-"$upstream_name"}
 -onto=$(git rev-parse --verify "${onto_name}^0") || exit
 +case "$onto_name" in
 +*...*)
 +      if      left=${onto_name%...*} right=${onto_name#*...} &&
 +              onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
 +      then
 +              case "$onto" in
 +              ?*"$LF"?*)
 +                      die "$onto_name: there are more than one merge bases"
 +                      ;;
 +              '')
 +                      die "$onto_name: there is no merge base"
 +                      ;;
 +              esac
 +      else
 +              die "$onto_name: there is no merge base"
 +      fi
 +      ;;
 +*)
 +      onto=$(git rev-parse --verify "${onto_name}^0") || exit
 +      ;;
 +esac
  
  # If a hook exists, give it a chance to interrupt
  run_pre_rebase_hook "$upstream_arg" "$@"