Merge branch 'jc/pull-signed-tag'
authorJunio C Hamano <gitster@pobox.com>
Wed, 1 Feb 2012 06:30:42 +0000 (22:30 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 1 Feb 2012 06:30:42 +0000 (22:30 -0800)
* jc/pull-signed-tag:
merge: use editor by default in interactive sessions

Conflicts:
Documentation/merge-options.txt

1  2 
builtin/merge.c
t/test-lib.sh
diff --combined builtin/merge.c
index 3a451727d0e637ae197c9a1193435e57e84e6c58,0006175d15a341793392296a8097cef905f2fa63..bfb75476aab154e430d6247073e0bbcd83c992f6
@@@ -26,8 -26,6 +26,8 @@@
  #include "merge-recursive.h"
  #include "resolve-undo.h"
  #include "remote.h"
 +#include "fmt-merge-msg.h"
 +#include "gpg-interface.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -46,12 -44,11 +46,12 @@@ static const char * const builtin_merge
        NULL
  };
  
 -static int show_diffstat = 1, shortlog_len, squash;
 +static int show_diffstat = 1, shortlog_len = -1, squash;
  static int option_commit = 1, allow_fast_forward = 1;
- static int fast_forward_only, option_edit;
+ static int fast_forward_only, option_edit = -1;
  static int allow_trivial = 1, have_message;
 -static struct strbuf merge_msg;
 +static int overwrite_ignore = 1;
 +static struct strbuf merge_msg = STRBUF_INIT;
  static struct commit_list *remoteheads;
  static struct strategy **use_strategies;
  static size_t use_strategies_nr, use_strategies_alloc;
@@@ -65,7 -62,6 +65,7 @@@ static int allow_rerere_auto
  static int abort_current_merge;
  static int show_progress = -1;
  static int default_to_upstream;
 +static const char *sign_commit;
  
  static struct strategy all_strategy[] = {
        { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@@ -193,7 -189,7 +193,7 @@@ static struct option builtin_merge_opti
                "create a single commit instead of doing a merge"),
        OPT_BOOLEAN(0, "commit", &option_commit,
                "perform a commit if the merge succeeds (default)"),
-       OPT_BOOLEAN('e', "edit", &option_edit,
+       OPT_BOOL('e', "edit", &option_edit,
                "edit message before committing"),
        OPT_BOOLEAN(0, "ff", &allow_fast_forward,
                "allow fast-forward (default)"),
        OPT_BOOLEAN(0, "abort", &abort_current_merge,
                "abort the current in-progress merge"),
        OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
 +      { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
 +        "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 +      OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"),
        OPT_END()
  };
  
@@@ -323,15 -316,13 +323,15 @@@ static void squash_message(struct commi
        struct rev_info rev;
        struct strbuf out = STRBUF_INIT;
        struct commit_list *j;
 +      const char *filename;
        int fd;
        struct pretty_print_context ctx = {0};
  
        printf(_("Squash commit -- not updating HEAD\n"));
 -      fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
 +      filename = git_path("SQUASH_MSG");
 +      fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
 -              die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG"));
 +              die_errno(_("Could not write to '%s'"), filename);
  
        init_revisions(&rev, NULL);
        rev.ignore_merges = 1;
@@@ -419,7 -410,7 +419,7 @@@ static void finish(struct commit *head_
  static void merge_name(const char *remote, struct strbuf *msg)
  {
        struct commit *remote_head;
 -      unsigned char branch_head[20], buf_sha[20];
 +      unsigned char branch_head[20];
        struct strbuf buf = STRBUF_INIT;
        struct strbuf bname = STRBUF_INIT;
        const char *ptr;
                strbuf_addstr(&truname, "refs/heads/");
                strbuf_addstr(&truname, remote);
                strbuf_setlen(&truname, truname.len - len);
 -              if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
 +              if (ref_exists(truname.buf)) {
                        strbuf_addf(msg,
                                    "%s\t\tbranch '%s'%s of .\n",
                                    sha1_to_hex(remote_head->object.sha1),
  
        if (!strcmp(remote, "FETCH_HEAD") &&
                        !access(git_path("FETCH_HEAD"), R_OK)) {
 +              const char *filename;
                FILE *fp;
                struct strbuf line = STRBUF_INIT;
                char *ptr;
  
 -              fp = fopen(git_path("FETCH_HEAD"), "r");
 +              filename = git_path("FETCH_HEAD");
 +              fp = fopen(filename, "r");
                if (!fp)
                        die_errno(_("could not open '%s' for reading"),
 -                                git_path("FETCH_HEAD"));
 +                                filename);
                strbuf_getline(&line, fp, '\n');
                fclose(fp);
                ptr = strstr(line.buf, "\tnot-for-merge\t");
@@@ -544,8 -533,6 +544,8 @@@ static void parse_branch_merge_options(
  
  static int git_merge_config(const char *k, const char *v, void *cb)
  {
 +      int status;
 +
        if (branch && !prefixcmp(k, "branch.") &&
                !prefixcmp(k + 7, branch) &&
                !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
                return git_config_string(&pull_octopus, k, v);
        else if (!strcmp(k, "merge.renormalize"))
                option_renormalize = git_config_bool(k, v);
 -      else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
 -              int is_bool;
 -              shortlog_len = git_config_bool_or_int(k, v, &is_bool);
 -              if (!is_bool && shortlog_len < 0)
 -                      return error(_("%s: negative length %s"), k, v);
 -              if (is_bool && shortlog_len)
 -                      shortlog_len = DEFAULT_MERGE_LOG_LEN;
 -              return 0;
 -      } else if (!strcmp(k, "merge.ff")) {
 +      else if (!strcmp(k, "merge.ff")) {
                int boolval = git_config_maybe_bool(k, v);
                if (0 <= boolval) {
                        allow_fast_forward = boolval;
                default_to_upstream = git_config_bool(k, v);
                return 0;
        }
 +
 +      status = fmt_merge_msg_config(k, v, cb);
 +      if (status)
 +              return status;
 +      status = git_gpg_config(k, v, NULL);
 +      if (status)
 +              return status;
        return git_diff_ui_config(k, v, cb);
  }
  
@@@ -776,12 -764,10 +776,12 @@@ int checkout_fast_forward(const unsigne
        memset(&trees, 0, sizeof(trees));
        memset(&opts, 0, sizeof(opts));
        memset(&t, 0, sizeof(t));
 -      memset(&dir, 0, sizeof(dir));
 -      dir.flags |= DIR_SHOW_IGNORED;
 -      dir.exclude_per_dir = ".gitignore";
 -      opts.dir = &dir;
 +      if (overwrite_ignore) {
 +              memset(&dir, 0, sizeof(dir));
 +              dir.flags |= DIR_SHOW_IGNORED;
 +              setup_standard_excludes(&dir);
 +              opts.dir = &dir;
 +      }
  
        opts.head_idx = 1;
        opts.src_index = &the_index;
@@@ -856,22 -842,20 +856,22 @@@ static void add_strategies(const char *
  
  static void write_merge_msg(struct strbuf *msg)
  {
 -      int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
 +      const char *filename = git_path("MERGE_MSG");
 +      int fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
                die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_MSG"));
 +                        filename);
        if (write_in_full(fd, msg->buf, msg->len) != msg->len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
  }
  
  static void read_merge_msg(struct strbuf *msg)
  {
 +      const char *filename = git_path("MERGE_MSG");
        strbuf_reset(msg);
 -      if (strbuf_read_file(msg, git_path("MERGE_MSG"), 0) < 0)
 -              die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
 +      if (strbuf_read_file(msg, filename, 0) < 0)
 +              die_errno(_("Could not read from '%s'"), filename);
  }
  
  static void write_merge_state(void);
@@@ -893,12 -877,12 +893,12 @@@ static void prepare_to_commit(void
        write_merge_msg(&msg);
        run_hook(get_index_file(), "prepare-commit-msg",
                 git_path("MERGE_MSG"), "merge", NULL, NULL);
-       if (option_edit) {
+       if (0 < option_edit) {
                if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
                        abort_commit(NULL);
        }
        read_merge_msg(&msg);
-       stripspace(&msg, option_edit);
+       stripspace(&msg, 0 < option_edit);
        if (!msg.len)
                abort_commit(_("Empty commit message."));
        strbuf_release(&merge_msg);
@@@ -918,9 -902,7 +918,9 @@@ static int merge_trivial(struct commit 
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
        prepare_to_commit();
 -      commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
 +      if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,
 +                      sign_commit))
 +              die(_("failed to write commit object"));
        finish(head, result_commit, "In-index merge");
        drop_save();
        return 0;
@@@ -951,9 -933,7 +951,9 @@@ static int finish_automerge(struct comm
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit();
        free_commit_list(remoteheads);
 -      commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
 +      if (commit_tree(&merge_msg, result_tree, parents, result_commit,
 +                      NULL, sign_commit))
 +              die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(head, result_commit, buf.buf);
        strbuf_release(&buf);
  
  static int suggest_conflicts(int renormalizing)
  {
 +      const char *filename;
        FILE *fp;
        int pos;
  
 -      fp = fopen(git_path("MERGE_MSG"), "a");
 +      filename = git_path("MERGE_MSG");
 +      fp = fopen(filename, "a");
        if (!fp)
 -              die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_MSG"));
 +              die_errno(_("Could not open '%s' for writing"), filename);
        fprintf(fp, "\nConflicts:\n");
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
@@@ -1062,7 -1041,6 +1062,7 @@@ static int setup_with_upstream(const ch
  
  static void write_merge_state(void)
  {
 +      const char *filename;
        int fd;
        struct commit_list *j;
        struct strbuf buf = STRBUF_INIT;
                }
                strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
        }
 -      fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
 +      filename = git_path("MERGE_HEAD");
 +      fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
 -              die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_HEAD"));
 +              die_errno(_("Could not open '%s' for writing"), filename);
        if (write_in_full(fd, buf.buf, buf.len) != buf.len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
        strbuf_addch(&merge_msg, '\n');
        write_merge_msg(&merge_msg);
 -      fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
 +
 +      filename = git_path("MERGE_MODE");
 +      fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd < 0)
 -              die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_MODE"));
 +              die_errno(_("Could not open '%s' for writing"), filename);
        strbuf_reset(&buf);
        if (!allow_fast_forward)
                strbuf_addf(&buf, "no-ff");
        if (write_in_full(fd, buf.buf, buf.len) != buf.len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
  }
  
+ static int default_edit_option(void)
+ {
+       static const char name[] = "GIT_MERGE_AUTOEDIT";
+       const char *e = getenv(name);
+       struct stat st_stdin, st_stdout;
+       if (have_message)
+               /* an explicit -m msg without --[no-]edit */
+               return 0;
+       if (e) {
+               int v = git_config_maybe_bool(name, e);
+               if (v < 0)
+                       die("Bad value '%s' in environment '%s'", e, name);
+               return v;
+       }
+       /* Use editor if stdin and stdout are the same and is a tty */
+       return (!fstat(0, &st_stdin) &&
+               !fstat(1, &st_stdout) &&
+               isatty(0) &&
+               st_stdin.st_dev == st_stdout.st_dev &&
+               st_stdin.st_ino == st_stdout.st_ino &&
+               st_stdin.st_mode == st_stdout.st_mode);
+ }
  int cmd_merge(int argc, const char **argv, const char *prefix)
  {
        unsigned char result_tree[20];
        struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
 -      int flag, i;
 +      int flag, i, ret = 0;
        int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
        struct commit_list **remotes = &remoteheads;
 +      void *branch_to_free;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_merge_usage, builtin_merge_options);
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
 -      branch = resolve_ref("HEAD", head_sha1, 0, &flag);
 +      branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag);
        if (branch && !prefixcmp(branch, "refs/heads/"))
                branch += 11;
        if (!branch || is_null_sha1(head_sha1))
                parse_branch_merge_options(branch_mergeoptions);
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
                        builtin_merge_usage, 0);
 +      if (shortlog_len < 0)
 +              shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
  
        if (verbosity < 0 && show_progress == -1)
                show_progress = 0;
                        die(_("There is no merge to abort (MERGE_HEAD missing)."));
  
                /* Invoke 'git reset --merge' */
 -              return cmd_reset(nargc, nargv, prefix);
 +              ret = cmd_reset(nargc, nargv, prefix);
 +              goto done;
        }
  
        if (read_cache_unmerged())
                die(_("You cannot combine --no-ff with --ff-only."));
  
        if (!abort_current_merge) {
 -              if (!argc && default_to_upstream)
 -                      argc = setup_with_upstream(&argv);
 -              else if (argc == 1 && !strcmp(argv[0], "-"))
 +              if (!argc) {
 +                      if (default_to_upstream)
 +                              argc = setup_with_upstream(&argv);
 +                      else
 +                              die(_("No commit specified and merge.defaultToUpstream not set."));
 +              } else if (argc == 1 && !strcmp(argv[0], "-"))
                        argv[0] = "@{-1}";
        }
        if (!argc)
                read_empty(remote_head->object.sha1, 0);
                update_ref("initial pull", "HEAD", remote_head->object.sha1,
                           NULL, 0, DIE_ON_ERR);
 -              return 0;
 +              goto done;
        } else {
                struct strbuf merge_names = STRBUF_INIT;
  
                }
        }
  
+       if (option_edit < 0)
+               option_edit = default_edit_option();
        if (!use_strategies) {
                if (!remoteheads->next)
                        add_strategies(pull_twohead, DEFAULT_TWOHEAD);
                 * but first the most common case of merging one remote.
                 */
                finish_up_to_date("Already up-to-date.");
 -              return 0;
 +              goto done;
        } else if (allow_fast_forward && !remoteheads->next &&
                        !common->next &&
                        !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
                        strbuf_addstr(&msg,
                                " (no commit created; -m option ignored)");
                commit = remoteheads->item;
 -              if (!commit)
 -                      return 1;
 +              if (!commit) {
 +                      ret = 1;
 +                      goto done;
 +              }
  
                if (checkout_fast_forward(head_commit->object.sha1,
 -                                        commit->object.sha1))
 -                      return 1;
 +                                        commit->object.sha1)) {
 +                      ret = 1;
 +                      goto done;
 +              }
  
                finish(head_commit, commit->object.sha1, msg.buf);
                drop_save();
 -              return 0;
 +              goto done;
        } else if (!remoteheads->next && common->next)
                ;
                /*
                        git_committer_info(IDENT_ERROR_ON_NO_NAME);
                        printf(_("Trying really trivial in-index merge...\n"));
                        if (!read_tree_trivial(common->item->object.sha1,
 -                                      head_commit->object.sha1, remoteheads->item->object.sha1))
 -                              return merge_trivial(head_commit);
 +                                             head_commit->object.sha1,
 +                                             remoteheads->item->object.sha1)) {
 +                              ret = merge_trivial(head_commit);
 +                              goto done;
 +                      }
                        printf(_("Nope.\n"));
                }
        } else {
                }
                if (up_to_date) {
                        finish_up_to_date("Already up-to-date. Yeeah!");
 -                      return 0;
 +                      goto done;
                }
        }
  
         * If we have a resulting tree, that means the strategy module
         * auto resolved the merge cleanly.
         */
 -      if (automerge_was_ok)
 -              return finish_automerge(head_commit, common, result_tree,
 -                                      wt_strategy);
 +      if (automerge_was_ok) {
 +              ret = finish_automerge(head_commit, common, result_tree,
 +                                     wt_strategy);
 +              goto done;
 +      }
  
        /*
         * Pick the result from the best strategy and have the user fix
                else
                        fprintf(stderr, _("Merge with strategy %s failed.\n"),
                                use_strategies[0]->name);
 -              return 2;
 +              ret = 2;
 +              goto done;
        } else if (best_strategy == wt_strategy)
                ; /* We already have its result in the working tree. */
        else {
        else
                write_merge_state();
  
 -      if (merge_was_ok) {
 +      if (merge_was_ok)
                fprintf(stderr, _("Automatic merge went well; "
                        "stopped before committing as requested\n"));
 -              return 0;
 -      } else
 -              return suggest_conflicts(option_renormalize);
 +      else
 +              ret = suggest_conflicts(option_renormalize);
 +
 +done:
 +      free(branch_to_free);
 +      return ret;
  }
diff --combined t/test-lib.sh
index 709a30067e5486526095cad98c4ade7334613fc8,ed32c2ab7be2f3ae4045b21649ed8f558c240c30..b22bee7c8448542cdadac36e7ac91b71a63aa7c2
@@@ -44,7 -44,6 +44,7 @@@ export LANG LC_ALL PAGER TERM T
  EDITOR=:
  unset VISUAL
  unset EMAIL
 +unset LANGUAGE
  unset $(perl -e '
        my @env = keys %ENV;
        my $ok = join("|", qw(
@@@ -64,7 -63,8 +64,8 @@@ GIT_AUTHOR_NAME='A U Thor
  GIT_COMMITTER_EMAIL=committer@example.com
  GIT_COMMITTER_NAME='C O Mitter'
  GIT_MERGE_VERBOSITY=5
- export GIT_MERGE_VERBOSITY
+ GIT_MERGE_AUTOEDIT=no
+ export GIT_MERGE_VERBOSITY GIT_MERGE_AUTOEDIT
  export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
  export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
  export EDITOR
@@@ -192,7 -192,6 +193,7 @@@ the
  fi
  
  exec 5>&1
 +exec 6<&0
  if test "$verbose" = "t"
  then
        exec 4>&2 3>&1
@@@ -329,19 -328,6 +330,19 @@@ test_tick () 
        export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
  }
  
 +# Stop execution and start a shell. This is useful for debugging tests and
 +# only makes sense together with "-v".
 +#
 +# Be sure to remove all invocations of this command before submitting.
 +
 +test_pause () {
 +      if test "$verbose" = t; then
 +              "$SHELL_PATH" <&6 >&3 2>&4
 +      else
 +              error >&5 "test_pause requires --verbose"
 +      fi
 +}
 +
  # Call test_commit with the arguments "<message> [<file> [<contents>]]"
  #
  # This will commit a file with the given contents and the given commit
@@@ -394,11 -380,6 +395,11 @@@ test_config () 
        git config "$@"
  }
  
 +test_config_global () {
 +      test_when_finished "test_unconfig --global '$1'" &&
 +      git config --global "$@"
 +}
 +
  # Use test_set_prereq to tell that a particular prerequisite is available.
  # The prerequisite can later be checked for in two ways:
  #
@@@ -489,7 -470,7 +490,7 @@@ test_debug () 
  test_eval_ () {
        # This is a separate function because some tests use
        # "return" to end a test_expect_success block early.
 -      eval >&3 2>&4 "$*"
 +      eval </dev/null >&3 2>&4 "$*"
  }
  
  test_run_ () {
  test -z "$NO_PERL" && test_set_prereq PERL
  test -z "$NO_PYTHON" && test_set_prereq PYTHON
  test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
 +test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
  
  # Can we rely on git's output in the C locale?
  if test -n "$GETTEXT_POISON"
  then
        GIT_GETTEXT_POISON=YesPlease
        export GIT_GETTEXT_POISON
 +      test_set_prereq GETTEXT_POISON
  else
        test_set_prereq C_LOCALE_OUTPUT
  fi