Merge branch 'cc/apply-am'
authorJunio C Hamano <gitster@pobox.com>
Mon, 19 Sep 2016 20:47:17 +0000 (13:47 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 19 Sep 2016 20:47:18 +0000 (13:47 -0700)
"git am" has been taught to make an internal call to "git apply"'s
innards without spawning the latter as a separate process.

* cc/apply-am: (41 commits)
builtin/am: use apply API in run_apply()
apply: learn to use a different index file
apply: pass apply state to build_fake_ancestor()
apply: refactor `git apply` option parsing
apply: change error_routine when silent
usage: add get_error_routine() and get_warn_routine()
usage: add set_warn_routine()
apply: don't print on stdout in verbosity_silent mode
apply: make it possible to silently apply
apply: use error_errno() where possible
apply: make some parsing functions static again
apply: move libified code from builtin/apply.c to apply.{c,h}
apply: rename and move opt constants to apply.h
builtin/apply: rename option parsing functions
builtin/apply: make create_one_file() return -1 on error
builtin/apply: make try_create_file() return -1 on error
builtin/apply: make write_out_results() return -1 on error
builtin/apply: make write_out_one_result() return -1 on error
builtin/apply: make create_file() return -1 on error
builtin/apply: make add_index_file() return -1 on error
...

1  2 
Makefile
builtin/am.c
git-compat-util.h
usage.c
diff --combined Makefile
index 7f184923ddc6fa5089b64ced3cdca405758468e1,3230fd0f1a1f427b2b0af3e2048cc4cee64f8d82..df4f86bb3039a11eefe3921a969f98fe92b28e4d
+++ b/Makefile
@@@ -296,11 -296,6 +296,11 @@@ all:
  # Define USE_NED_ALLOCATOR if you want to replace the platforms default
  # memory allocators with the nedmalloc allocator written by Niall Douglas.
  #
 +# Define OVERRIDE_STRDUP to override the libc version of strdup(3).
 +# This is necessary when using a custom allocator in order to avoid
 +# crashes due to allocation and free working on different 'heaps'.
 +# It's defined automatically if USE_NED_ALLOCATOR is set.
 +#
  # Define NO_REGEX if you have no or inferior regex support in your C library.
  #
  # Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
  # Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function.
  #
  # Define HAVE_GETDELIM if your system has the getdelim() function.
 +#
 +# Define PAGER_ENV to a SP separated VAR=VAL pairs to define
 +# default environment variables to be passed when a pager is spawned, e.g.
 +#
 +#    PAGER_ENV = LESS=FRX LV=-c
 +#
 +# to say "export LESS=FRX (and LV=-c) if the environment variable
 +# LESS (and LV) is not set, respectively".
  
  GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@@ -696,6 -683,7 +696,7 @@@ LIB_OBJS += abspath.
  LIB_OBJS += advice.o
  LIB_OBJS += alias.o
  LIB_OBJS += alloc.o
+ LIB_OBJS += apply.o
  LIB_OBJS += archive.o
  LIB_OBJS += archive-tar.o
  LIB_OBJS += archive-zip.o
@@@ -768,7 -756,6 +769,7 @@@ LIB_OBJS += merge.
  LIB_OBJS += merge-blobs.o
  LIB_OBJS += merge-recursive.o
  LIB_OBJS += mergesort.o
 +LIB_OBJS += mru.o
  LIB_OBJS += name-hash.o
  LIB_OBJS += notes.o
  LIB_OBJS += notes-cache.o
@@@ -1461,14 -1448,8 +1462,14 @@@ ifdef NATIVE_CRL
  endif
  
  ifdef USE_NED_ALLOCATOR
 -       COMPAT_CFLAGS += -Icompat/nedmalloc
 -       COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
 +      COMPAT_CFLAGS += -Icompat/nedmalloc
 +      COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
 +      OVERRIDE_STRDUP = YesPlease
 +endif
 +
 +ifdef OVERRIDE_STRDUP
 +      COMPAT_CFLAGS += -DOVERRIDE_STRDUP
 +      COMPAT_OBJS += compat/strdup.o
  endif
  
  ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
@@@ -1520,10 -1501,6 +1521,10 @@@ ifeq ($(PYTHON_PATH),
  NO_PYTHON = NoThanks
  endif
  
 +ifndef PAGER_ENV
 +PAGER_ENV = LESS=FRX LV=-c
 +endif
 +
  QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
  QUIET_SUBDIR1  =
  
@@@ -1653,11 -1630,6 +1654,11 @@@ ifdef DEFAULT_HELP_FORMA
  BASIC_CFLAGS += -DDEFAULT_HELP_FORMAT='"$(DEFAULT_HELP_FORMAT)"'
  endif
  
 +PAGER_ENV_SQ = $(subst ','\'',$(PAGER_ENV))
 +PAGER_ENV_CQ = "$(subst ",\",$(subst \,\\,$(PAGER_ENV)))"
 +PAGER_ENV_CQ_SQ = $(subst ','\'',$(PAGER_ENV_CQ))
 +BASIC_CFLAGS += -DPAGER_ENV='$(PAGER_ENV_CQ_SQ)'
 +
  ALL_CFLAGS += $(BASIC_CFLAGS)
  ALL_LDFLAGS += $(BASIC_LDFLAGS)
  
@@@ -1782,7 -1754,7 +1783,7 @@@ common-cmds.h: $(wildcard Documentation
  
  SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
        $(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\
 -      $(gitwebdir_SQ):$(PERL_PATH_SQ):$(SANE_TEXT_GREP)
 +      $(gitwebdir_SQ):$(PERL_PATH_SQ):$(SANE_TEXT_GREP):$(PAGER_ENV)
  define cmd_munge_script
  $(RM) $@ $@+ && \
  sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
      -e 's|@@GITWEBDIR@@|$(gitwebdir_SQ)|g' \
      -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
      -e 's|@@SANE_TEXT_GREP@@|$(SANE_TEXT_GREP)|g' \
 +    -e 's|@@PAGER_ENV@@|$(PAGER_ENV_SQ)|g' \
      $@.sh >$@+
  endef
  
@@@ -2040,7 -2011,7 +2041,7 @@@ endi
  
  ifdef USE_NED_ALLOCATOR
  compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
 -      -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR
 +      -DNDEBUG -DREPLACE_SYSTEM_ALLOCATOR
  compat/nedmalloc/nedmalloc.sp: SPARSE_FLAGS += -Wno-non-pointer-null
  endif
  
@@@ -2203,7 -2174,6 +2204,7 @@@ GIT-BUILD-OPTIONS: FORC
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@+
        @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+
        @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+
 +      @echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
  ifdef TEST_OUTPUT_DIRECTORY
        @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+
  endif
diff --combined builtin/am.c
index 9daeb27225c04c28e33adc7b8e9071d825a490de,1819f4d7bff43ea8dd4d141985a77c8ad0378b71..ddd23badae2eea631db10e754c0fcd2ed2d449b4
@@@ -28,7 -28,7 +28,8 @@@
  #include "rerere.h"
  #include "prompt.h"
  #include "mailinfo.h"
+ #include "apply.h"
 +#include "string-list.h"
  
  /**
   * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@@ -259,29 -259,38 +260,29 @@@ static int read_state_file(struct strbu
  }
  
  /**
 - * Reads a KEY=VALUE shell variable assignment from `fp`, returning the VALUE
 - * as a newly-allocated string. VALUE must be a quoted string, and the KEY must
 - * match `key`. Returns NULL on failure.
 - *
 - * This is used by read_author_script() to read the GIT_AUTHOR_* variables from
 - * the author-script.
 + * Take a series of KEY='VALUE' lines where VALUE part is
 + * sq-quoted, and append <KEY, VALUE> at the end of the string list
   */
 -static char *read_shell_var(FILE *fp, const char *key)
 +static int parse_key_value_squoted(char *buf, struct string_list *list)
  {
 -      struct strbuf sb = STRBUF_INIT;
 -      const char *str;
 -
 -      if (strbuf_getline_lf(&sb, fp))
 -              goto fail;
 -
 -      if (!skip_prefix(sb.buf, key, &str))
 -              goto fail;
 -
 -      if (!skip_prefix(str, "=", &str))
 -              goto fail;
 -
 -      strbuf_remove(&sb, 0, str - sb.buf);
 -
 -      str = sq_dequote(sb.buf);
 -      if (!str)
 -              goto fail;
 -
 -      return strbuf_detach(&sb, NULL);
 -
 -fail:
 -      strbuf_release(&sb);
 -      return NULL;
 +      while (*buf) {
 +              struct string_list_item *item;
 +              char *np;
 +              char *cp = strchr(buf, '=');
 +              if (!cp)
 +                      return -1;
 +              np = strchrnul(cp, '\n');
 +              *cp++ = '\0';
 +              item = string_list_append(list, buf);
 +
 +              buf = np + (*np == '\n');
 +              *np = '\0';
 +              cp = sq_dequote(cp);
 +              if (!cp)
 +                      return -1;
 +              item->util = xstrdup(cp);
 +      }
 +      return 0;
  }
  
  /**
  static int read_author_script(struct am_state *state)
  {
        const char *filename = am_path(state, "author-script");
 -      FILE *fp;
 +      struct strbuf buf = STRBUF_INIT;
 +      struct string_list kv = STRING_LIST_INIT_DUP;
 +      int retval = -1; /* assume failure */
 +      int fd;
  
        assert(!state->author_name);
        assert(!state->author_email);
        assert(!state->author_date);
  
 -      fp = fopen(filename, "r");
 -      if (!fp) {
 +      fd = open(filename, O_RDONLY);
 +      if (fd < 0) {
                if (errno == ENOENT)
                        return 0;
                die_errno(_("could not open '%s' for reading"), filename);
        }
 +      strbuf_read(&buf, fd, 0);
 +      close(fd);
 +      if (parse_key_value_squoted(buf.buf, &kv))
 +              goto finish;
  
 -      state->author_name = read_shell_var(fp, "GIT_AUTHOR_NAME");
 -      if (!state->author_name) {
 -              fclose(fp);
 -              return -1;
 -      }
 -
 -      state->author_email = read_shell_var(fp, "GIT_AUTHOR_EMAIL");
 -      if (!state->author_email) {
 -              fclose(fp);
 -              return -1;
 -      }
 -
 -      state->author_date = read_shell_var(fp, "GIT_AUTHOR_DATE");
 -      if (!state->author_date) {
 -              fclose(fp);
 -              return -1;
 -      }
 -
 -      if (fgetc(fp) != EOF) {
 -              fclose(fp);
 -              return -1;
 -      }
 -
 -      fclose(fp);
 -      return 0;
 +      if (kv.nr != 3 ||
 +          strcmp(kv.items[0].string, "GIT_AUTHOR_NAME") ||
 +          strcmp(kv.items[1].string, "GIT_AUTHOR_EMAIL") ||
 +          strcmp(kv.items[2].string, "GIT_AUTHOR_DATE"))
 +              goto finish;
 +      state->author_name = kv.items[0].util;
 +      state->author_email = kv.items[1].util;
 +      state->author_date = kv.items[2].util;
 +      retval = 0;
 +finish:
 +      string_list_clear(&kv, !!retval);
 +      strbuf_release(&buf);
 +      return retval;
  }
  
  /**
@@@ -1509,39 -1523,59 +1510,59 @@@ static int parse_mail_rebase(struct am_
   */
  static int run_apply(const struct am_state *state, const char *index_file)
  {
-       struct child_process cp = CHILD_PROCESS_INIT;
-       cp.git_cmd = 1;
-       if (index_file)
-               argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file);
+       struct argv_array apply_paths = ARGV_ARRAY_INIT;
+       struct argv_array apply_opts = ARGV_ARRAY_INIT;
+       struct apply_state apply_state;
+       int res, opts_left;
+       static struct lock_file lock_file;
+       int force_apply = 0;
+       int options = 0;
+       if (init_apply_state(&apply_state, NULL, &lock_file))
+               die("BUG: init_apply_state() failed");
+       argv_array_push(&apply_opts, "apply");
+       argv_array_pushv(&apply_opts, state->git_apply_opts.argv);
+       opts_left = apply_parse_options(apply_opts.argc, apply_opts.argv,
+                                       &apply_state, &force_apply, &options,
+                                       NULL);
+       if (opts_left != 0)
+               die("unknown option passed through to git apply");
+       if (index_file) {
+               apply_state.index_file = index_file;
+               apply_state.cached = 1;
+       } else
+               apply_state.check_index = 1;
  
        /*
         * If we are allowed to fall back on 3-way merge, don't give false
         * errors during the initial attempt.
         */
-       if (state->threeway && !index_file) {
-               cp.no_stdout = 1;
-               cp.no_stderr = 1;
-       }
+       if (state->threeway && !index_file)
+               apply_state.apply_verbosity = verbosity_silent;
  
-       argv_array_push(&cp.args, "apply");
+       if (check_apply_state(&apply_state, force_apply))
+               die("BUG: check_apply_state() failed");
  
-       argv_array_pushv(&cp.args, state->git_apply_opts.argv);
+       argv_array_push(&apply_paths, am_path(state, "patch"));
  
-       if (index_file)
-               argv_array_push(&cp.args, "--cached");
-       else
-               argv_array_push(&cp.args, "--index");
+       res = apply_all_patches(&apply_state, apply_paths.argc, apply_paths.argv, options);
  
-       argv_array_push(&cp.args, am_path(state, "patch"));
+       argv_array_clear(&apply_paths);
+       argv_array_clear(&apply_opts);
+       clear_apply_state(&apply_state);
  
-       if (run_command(&cp))
-               return -1;
+       if (res)
+               return res;
  
-       /* Reload index as git-apply will have modified it. */
-       discard_cache();
-       read_cache_from(index_file ? index_file : get_index_file());
+       if (index_file) {
+               /* Reload index as apply_all_patches() will have modified it. */
+               discard_cache();
+               read_cache_from(index_file);
+       }
  
        return 0;
  }
@@@ -1565,19 -1599,48 +1586,19 @@@ static int build_fake_ancestor(const st
        return 0;
  }
  
 -/**
 - * Do the three-way merge using fake ancestor, their tree constructed
 - * from the fake ancestor and the postimage of the patch, and our
 - * state.
 - */
 -static int run_fallback_merge_recursive(const struct am_state *state,
 -                                      unsigned char *orig_tree,
 -                                      unsigned char *our_tree,
 -                                      unsigned char *their_tree)
 -{
 -      struct child_process cp = CHILD_PROCESS_INIT;
 -      int status;
 -
 -      cp.git_cmd = 1;
 -
 -      argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
 -                       sha1_to_hex(their_tree), linelen(state->msg), state->msg);
 -      if (state->quiet)
 -              argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
 -
 -      argv_array_push(&cp.args, "merge-recursive");
 -      argv_array_push(&cp.args, sha1_to_hex(orig_tree));
 -      argv_array_push(&cp.args, "--");
 -      argv_array_push(&cp.args, sha1_to_hex(our_tree));
 -      argv_array_push(&cp.args, sha1_to_hex(their_tree));
 -
 -      status = run_command(&cp) ? (-1) : 0;
 -      discard_cache();
 -      read_cache();
 -      return status;
 -}
 -
  /**
   * Attempt a threeway merge, using index_path as the temporary index.
   */
  static int fall_back_threeway(const struct am_state *state, const char *index_path)
  {
 -      unsigned char orig_tree[GIT_SHA1_RAWSZ], their_tree[GIT_SHA1_RAWSZ],
 -                    our_tree[GIT_SHA1_RAWSZ];
 +      struct object_id orig_tree, their_tree, our_tree;
 +      const struct object_id *bases[1] = { &orig_tree };
 +      struct merge_options o;
 +      struct commit *result;
 +      char *their_tree_name;
  
 -      if (get_sha1("HEAD", our_tree) < 0)
 -              hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
 +      if (get_oid("HEAD", &our_tree) < 0)
 +              hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN);
  
        if (build_fake_ancestor(state, index_path))
                return error("could not build fake ancestor");
        discard_cache();
        read_cache_from(index_path);
  
 -      if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
 +      if (write_index_as_tree(orig_tree.hash, &the_index, index_path, 0, NULL))
                return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
  
        say(state, stdout, _("Using index info to reconstruct a base tree..."));
                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, 0);
 +              add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0);
                diff_setup_done(&rev_info.diffopt);
                run_diff_index(&rev_info, 1);
        }
                return error(_("Did you hand edit your patch?\n"
                                "It does not apply to blobs recorded in its index."));
  
 -      if (write_index_as_tree(their_tree, &the_index, index_path, 0, NULL))
 +      if (write_index_as_tree(their_tree.hash, &the_index, index_path, 0, NULL))
                return error("could not write tree");
  
        say(state, stdout, _("Falling back to patching base and 3-way merge..."));
         * changes.
         */
  
 -      if (run_fallback_merge_recursive(state, orig_tree, our_tree, their_tree)) {
 +      init_merge_options(&o);
 +
 +      o.branch1 = "HEAD";
 +      their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
 +      o.branch2 = their_tree_name;
 +
 +      if (state->quiet)
 +              o.verbosity = 0;
 +
 +      if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
                rerere(state->allow_rerere_autoupdate);
 +              free(their_tree_name);
                return error(_("Failed to merge in the changes."));
        }
  
 +      free(their_tree_name);
        return 0;
  }
  
@@@ -1809,8 -1861,6 +1830,8 @@@ static void am_run(struct am_state *sta
                const char *mail = am_path(state, msgnum(state));
                int apply_status;
  
 +              reset_ident_date();
 +
                if (!file_exists(mail))
                        goto next;
  
@@@ -2209,7 -2259,7 +2230,7 @@@ int cmd_am(int argc, const char **argv
        int in_progress;
  
        const char * const usage[] = {
 -              N_("git am [<options>] [(<mbox>|<Maildir>)...]"),
 +              N_("git am [<options>] [(<mbox> | <Maildir>)...]"),
                N_("git am [<options>] (--continue | --skip | --abort)"),
                NULL
        };
diff --combined git-compat-util.h
index 68615b1a6a4db0f4ba7b4ce543e00e31e3b1705e,de04df19a835fa66ada521098db2e9b2d80a904e..37cce0746f460ae6c839e2556c526e0fd41cdf65
@@@ -436,11 -436,13 +436,14 @@@ static inline int const_error(void
        return -1;
  }
  #define error(...) (error(__VA_ARGS__), const_error())
 +#define error_errno(...) (error_errno(__VA_ARGS__), const_error())
  #endif
  
  extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
  extern void set_error_routine(void (*routine)(const char *err, va_list params));
+ extern void (*get_error_routine(void))(const char *err, va_list params);
+ extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
+ extern void (*get_warn_routine(void))(const char *warn, va_list params);
  extern void set_die_is_recursing_routine(int (*routine)(void));
  extern void set_error_handle(FILE *);
  
@@@ -664,22 -666,10 +667,22 @@@ void *gitmemmem(const void *haystack, s
                  const void *needle, size_t needlelen);
  #endif
  
 +#ifdef OVERRIDE_STRDUP
 +#ifdef strdup
 +#undef strdup
 +#endif
 +#define strdup gitstrdup
 +char *gitstrdup(const char *s);
 +#endif
 +
  #ifdef NO_GETPAGESIZE
  #define getpagesize() sysconf(_SC_PAGESIZE)
  #endif
  
 +#ifndef O_CLOEXEC
 +#define O_CLOEXEC 0
 +#endif
 +
  #ifdef FREAD_READS_DIRECTORIES
  #ifdef fopen
  #undef fopen
@@@ -828,7 -818,7 +831,7 @@@ extern FILE *fopen_for_writing(const ch
   * you can do:
   *
   *   struct foo *f;
 - *   FLEX_ALLOC_STR(f, name, src);
 + *   FLEXPTR_ALLOC_STR(f, name, src);
   *
   * and "name" will point to a block of memory after the struct, which will be
   * freed along with the struct (but the pointer can be repointed anywhere).
diff --combined usage.c
index 0efa3faf601231db35239161a1a7253b3975b3f8,2fd3045535164e9f4b8604fca7d219a30b1ac6f3..17f52c1b5ce631eed1cdf3447d80e7223a2e6f01
+++ b/usage.c
@@@ -70,6 -70,21 +70,21 @@@ void set_error_routine(void (*routine)(
        error_routine = routine;
  }
  
+ void (*get_error_routine(void))(const char *err, va_list params)
+ {
+       return error_routine;
+ }
+ void set_warn_routine(void (*routine)(const char *warn, va_list params))
+ {
+       warn_routine = routine;
+ }
+ void (*get_warn_routine(void))(const char *warn, va_list params)
+ {
+       return warn_routine;
+ }
  void set_die_is_recursing_routine(int (*routine)(void))
  {
        die_is_recursing = routine;
@@@ -148,7 -163,6 +163,7 @@@ void NORETURN die_errno(const char *fmt
        va_end(params);
  }
  
 +#undef error_errno
  int error_errno(const char *fmt, ...)
  {
        char buf[1024];