OSDN Git Service

Merge branch 'mh/tempfile'
authorJunio C Hamano <gitster@pobox.com>
Tue, 25 Aug 2015 21:57:09 +0000 (14:57 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 25 Aug 2015 21:57:09 +0000 (14:57 -0700)
The "lockfile" API has been rebuilt on top of a new "tempfile" API.

* mh/tempfile:
  credential-cache--daemon: use tempfile module
  credential-cache--daemon: delete socket from main()
  gc: use tempfile module to handle gc.pid file
  lock_repo_for_gc(): compute the path to "gc.pid" only once
  diff: use tempfile module
  setup_temporary_shallow(): use tempfile module
  write_shared_index(): use tempfile module
  register_tempfile(): new function to handle an existing temporary file
  tempfile: add several functions for creating temporary files
  prepare_tempfile_object(): new function, extracted from create_tempfile()
  tempfile: a new module for handling temporary files
  commit_lock_file(): use get_locked_file_path()
  lockfile: add accessor get_lock_file_path()
  lockfile: add accessors get_lock_file_fd() and get_lock_file_fp()
  create_bundle(): duplicate file descriptor to avoid closing it twice
  lockfile: move documentation to lockfile.h and lockfile.c

1  2 
Makefile
builtin/am.c
builtin/commit.c
builtin/gc.c
builtin/pull.c
config.c
diff.c
lockfile.c
read-cache.c
refs.c
shallow.c

diff --cc Makefile
Simple merge
diff --cc builtin/am.c
index 634f7a7,0000000..b9c62e3
mode 100644,000000..100644
--- /dev/null
@@@ -1,2351 -1,0 +1,2352 @@@
 +/*
 + * Builtin "git am"
 + *
 + * Based on git-am.sh by Junio C Hamano.
 + */
 +#include "cache.h"
 +#include "builtin.h"
 +#include "exec_cmd.h"
 +#include "parse-options.h"
 +#include "dir.h"
 +#include "run-command.h"
 +#include "quote.h"
++#include "tempfile.h"
 +#include "lockfile.h"
 +#include "cache-tree.h"
 +#include "refs.h"
 +#include "commit.h"
 +#include "diff.h"
 +#include "diffcore.h"
 +#include "unpack-trees.h"
 +#include "branch.h"
 +#include "sequencer.h"
 +#include "revision.h"
 +#include "merge-recursive.h"
 +#include "revision.h"
 +#include "log-tree.h"
 +#include "notes-utils.h"
 +#include "rerere.h"
 +#include "prompt.h"
 +
 +/**
 + * Returns 1 if the file is empty or does not exist, 0 otherwise.
 + */
 +static int is_empty_file(const char *filename)
 +{
 +      struct stat st;
 +
 +      if (stat(filename, &st) < 0) {
 +              if (errno == ENOENT)
 +                      return 1;
 +              die_errno(_("could not stat %s"), filename);
 +      }
 +
 +      return !st.st_size;
 +}
 +
 +/**
 + * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators.
 + */
 +static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
 +{
 +      if (strbuf_getwholeline(sb, fp, '\n'))
 +              return EOF;
 +      if (sb->buf[sb->len - 1] == '\n') {
 +              strbuf_setlen(sb, sb->len - 1);
 +              if (sb->len > 0 && sb->buf[sb->len - 1] == '\r')
 +                      strbuf_setlen(sb, sb->len - 1);
 +      }
 +      return 0;
 +}
 +
 +/**
 + * Returns the length of the first line of msg.
 + */
 +static int linelen(const char *msg)
 +{
 +      return strchrnul(msg, '\n') - msg;
 +}
 +
 +/**
 + * Returns true if `str` consists of only whitespace, false otherwise.
 + */
 +static int str_isspace(const char *str)
 +{
 +      for (; *str; str++)
 +              if (!isspace(*str))
 +                      return 0;
 +
 +      return 1;
 +}
 +
 +enum patch_format {
 +      PATCH_FORMAT_UNKNOWN = 0,
 +      PATCH_FORMAT_MBOX,
 +      PATCH_FORMAT_STGIT,
 +      PATCH_FORMAT_STGIT_SERIES,
 +      PATCH_FORMAT_HG
 +};
 +
 +enum keep_type {
 +      KEEP_FALSE = 0,
 +      KEEP_TRUE,      /* pass -k flag to git-mailinfo */
 +      KEEP_NON_PATCH  /* pass -b flag to git-mailinfo */
 +};
 +
 +enum scissors_type {
 +      SCISSORS_UNSET = -1,
 +      SCISSORS_FALSE = 0,  /* pass --no-scissors to git-mailinfo */
 +      SCISSORS_TRUE        /* pass --scissors to git-mailinfo */
 +};
 +
 +enum signoff_type {
 +      SIGNOFF_FALSE = 0,
 +      SIGNOFF_TRUE = 1,
 +      SIGNOFF_EXPLICIT /* --signoff was set on the command-line */
 +};
 +
 +struct am_state {
 +      /* state directory path */
 +      char *dir;
 +
 +      /* current and last patch numbers, 1-indexed */
 +      int cur;
 +      int last;
 +
 +      /* commit metadata and message */
 +      char *author_name;
 +      char *author_email;
 +      char *author_date;
 +      char *msg;
 +      size_t msg_len;
 +
 +      /* when --rebasing, records the original commit the patch came from */
 +      unsigned char orig_commit[GIT_SHA1_RAWSZ];
 +
 +      /* number of digits in patch filename */
 +      int prec;
 +
 +      /* various operating modes and command line options */
 +      int interactive;
 +      int threeway;
 +      int quiet;
 +      int signoff; /* enum signoff_type */
 +      int utf8;
 +      int keep; /* enum keep_type */
 +      int message_id;
 +      int scissors; /* enum scissors_type */
 +      struct argv_array git_apply_opts;
 +      const char *resolvemsg;
 +      int committer_date_is_author_date;
 +      int ignore_date;
 +      int allow_rerere_autoupdate;
 +      const char *sign_commit;
 +      int rebasing;
 +};
 +
 +/**
 + * Initializes am_state with the default values. The state directory is set to
 + * dir.
 + */
 +static void am_state_init(struct am_state *state, const char *dir)
 +{
 +      int gpgsign;
 +
 +      memset(state, 0, sizeof(*state));
 +
 +      assert(dir);
 +      state->dir = xstrdup(dir);
 +
 +      state->prec = 4;
 +
 +      git_config_get_bool("am.threeway", &state->threeway);
 +
 +      state->utf8 = 1;
 +
 +      git_config_get_bool("am.messageid", &state->message_id);
 +
 +      state->scissors = SCISSORS_UNSET;
 +
 +      argv_array_init(&state->git_apply_opts);
 +
 +      if (!git_config_get_bool("commit.gpgsign", &gpgsign))
 +              state->sign_commit = gpgsign ? "" : NULL;
 +}
 +
 +/**
 + * Releases memory allocated by an am_state.
 + */
 +static void am_state_release(struct am_state *state)
 +{
 +      free(state->dir);
 +      free(state->author_name);
 +      free(state->author_email);
 +      free(state->author_date);
 +      free(state->msg);
 +      argv_array_clear(&state->git_apply_opts);
 +}
 +
 +/**
 + * Returns path relative to the am_state directory.
 + */
 +static inline const char *am_path(const struct am_state *state, const char *path)
 +{
 +      return mkpath("%s/%s", state->dir, path);
 +}
 +
 +/**
 + * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline
 + * at the end.
 + */
 +static void say(const struct am_state *state, FILE *fp, const char *fmt, ...)
 +{
 +      va_list ap;
 +
 +      va_start(ap, fmt);
 +      if (!state->quiet) {
 +              vfprintf(fp, fmt, ap);
 +              putc('\n', fp);
 +      }
 +      va_end(ap);
 +}
 +
 +/**
 + * Returns 1 if there is an am session in progress, 0 otherwise.
 + */
 +static int am_in_progress(const struct am_state *state)
 +{
 +      struct stat st;
 +
 +      if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode))
 +              return 0;
 +      if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode))
 +              return 0;
 +      if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode))
 +              return 0;
 +      return 1;
 +}
 +
 +/**
 + * Reads the contents of `file` in the `state` directory into `sb`. Returns the
 + * number of bytes read on success, -1 if the file does not exist. If `trim` is
 + * set, trailing whitespace will be removed.
 + */
 +static int read_state_file(struct strbuf *sb, const struct am_state *state,
 +                      const char *file, int trim)
 +{
 +      strbuf_reset(sb);
 +
 +      if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {
 +              if (trim)
 +                      strbuf_trim(sb);
 +
 +              return sb->len;
 +      }
 +
 +      if (errno == ENOENT)
 +              return -1;
 +
 +      die_errno(_("could not read '%s'"), am_path(state, file));
 +}
 +
 +/**
 + * 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.
 + */
 +static char *read_shell_var(FILE *fp, const char *key)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      const char *str;
 +
 +      if (strbuf_getline(&sb, fp, '\n'))
 +              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;
 +}
 +
 +/**
 + * Reads and parses the state directory's "author-script" file, and sets
 + * state->author_name, state->author_email and state->author_date accordingly.
 + * Returns 0 on success, -1 if the file could not be parsed.
 + *
 + * The author script is of the format:
 + *
 + *    GIT_AUTHOR_NAME='$author_name'
 + *    GIT_AUTHOR_EMAIL='$author_email'
 + *    GIT_AUTHOR_DATE='$author_date'
 + *
 + * where $author_name, $author_email and $author_date are quoted. We are strict
 + * with our parsing, as the file was meant to be eval'd in the old git-am.sh
 + * script, and thus if the file differs from what this function expects, it is
 + * better to bail out than to do something that the user does not expect.
 + */
 +static int read_author_script(struct am_state *state)
 +{
 +      const char *filename = am_path(state, "author-script");
 +      FILE *fp;
 +
 +      assert(!state->author_name);
 +      assert(!state->author_email);
 +      assert(!state->author_date);
 +
 +      fp = fopen(filename, "r");
 +      if (!fp) {
 +              if (errno == ENOENT)
 +                      return 0;
 +              die_errno(_("could not open '%s' for reading"), filename);
 +      }
 +
 +      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;
 +}
 +
 +/**
 + * Saves state->author_name, state->author_email and state->author_date in the
 + * state directory's "author-script" file.
 + */
 +static void write_author_script(const struct am_state *state)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      strbuf_addstr(&sb, "GIT_AUTHOR_NAME=");
 +      sq_quote_buf(&sb, state->author_name);
 +      strbuf_addch(&sb, '\n');
 +
 +      strbuf_addstr(&sb, "GIT_AUTHOR_EMAIL=");
 +      sq_quote_buf(&sb, state->author_email);
 +      strbuf_addch(&sb, '\n');
 +
 +      strbuf_addstr(&sb, "GIT_AUTHOR_DATE=");
 +      sq_quote_buf(&sb, state->author_date);
 +      strbuf_addch(&sb, '\n');
 +
 +      write_file(am_path(state, "author-script"), 1, "%s", sb.buf);
 +
 +      strbuf_release(&sb);
 +}
 +
 +/**
 + * Reads the commit message from the state directory's "final-commit" file,
 + * setting state->msg to its contents and state->msg_len to the length of its
 + * contents in bytes.
 + *
 + * Returns 0 on success, -1 if the file does not exist.
 + */
 +static int read_commit_msg(struct am_state *state)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      assert(!state->msg);
 +
 +      if (read_state_file(&sb, state, "final-commit", 0) < 0) {
 +              strbuf_release(&sb);
 +              return -1;
 +      }
 +
 +      state->msg = strbuf_detach(&sb, &state->msg_len);
 +      return 0;
 +}
 +
 +/**
 + * Saves state->msg in the state directory's "final-commit" file.
 + */
 +static void write_commit_msg(const struct am_state *state)
 +{
 +      int fd;
 +      const char *filename = am_path(state, "final-commit");
 +
 +      fd = xopen(filename, O_WRONLY | O_CREAT, 0666);
 +      if (write_in_full(fd, state->msg, state->msg_len) < 0)
 +              die_errno(_("could not write to %s"), filename);
 +      close(fd);
 +}
 +
 +/**
 + * Loads state from disk.
 + */
 +static void am_load(struct am_state *state)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      if (read_state_file(&sb, state, "next", 1) < 0)
 +              die("BUG: state file 'next' does not exist");
 +      state->cur = strtol(sb.buf, NULL, 10);
 +
 +      if (read_state_file(&sb, state, "last", 1) < 0)
 +              die("BUG: state file 'last' does not exist");
 +      state->last = strtol(sb.buf, NULL, 10);
 +
 +      if (read_author_script(state) < 0)
 +              die(_("could not parse author script"));
 +
 +      read_commit_msg(state);
 +
 +      if (read_state_file(&sb, state, "original-commit", 1) < 0)
 +              hashclr(state->orig_commit);
 +      else if (get_sha1_hex(sb.buf, state->orig_commit) < 0)
 +              die(_("could not parse %s"), am_path(state, "original-commit"));
 +
 +      read_state_file(&sb, state, "threeway", 1);
 +      state->threeway = !strcmp(sb.buf, "t");
 +
 +      read_state_file(&sb, state, "quiet", 1);
 +      state->quiet = !strcmp(sb.buf, "t");
 +
 +      read_state_file(&sb, state, "sign", 1);
 +      state->signoff = !strcmp(sb.buf, "t");
 +
 +      read_state_file(&sb, state, "utf8", 1);
 +      state->utf8 = !strcmp(sb.buf, "t");
 +
 +      read_state_file(&sb, state, "keep", 1);
 +      if (!strcmp(sb.buf, "t"))
 +              state->keep = KEEP_TRUE;
 +      else if (!strcmp(sb.buf, "b"))
 +              state->keep = KEEP_NON_PATCH;
 +      else
 +              state->keep = KEEP_FALSE;
 +
 +      read_state_file(&sb, state, "messageid", 1);
 +      state->message_id = !strcmp(sb.buf, "t");
 +
 +      read_state_file(&sb, state, "scissors", 1);
 +      if (!strcmp(sb.buf, "t"))
 +              state->scissors = SCISSORS_TRUE;
 +      else if (!strcmp(sb.buf, "f"))
 +              state->scissors = SCISSORS_FALSE;
 +      else
 +              state->scissors = SCISSORS_UNSET;
 +
 +      read_state_file(&sb, state, "apply-opt", 1);
 +      argv_array_clear(&state->git_apply_opts);
 +      if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0)
 +              die(_("could not parse %s"), am_path(state, "apply-opt"));
 +
 +      state->rebasing = !!file_exists(am_path(state, "rebasing"));
 +
 +      strbuf_release(&sb);
 +}
 +
 +/**
 + * Removes the am_state directory, forcefully terminating the current am
 + * session.
 + */
 +static void am_destroy(const struct am_state *state)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      strbuf_addstr(&sb, state->dir);
 +      remove_dir_recursively(&sb, 0);
 +      strbuf_release(&sb);
 +}
 +
 +/**
 + * Runs applypatch-msg hook. Returns its exit code.
 + */
 +static int run_applypatch_msg_hook(struct am_state *state)
 +{
 +      int ret;
 +
 +      assert(state->msg);
 +      ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
 +
 +      if (!ret) {
 +              free(state->msg);
 +              state->msg = NULL;
 +              if (read_commit_msg(state) < 0)
 +                      die(_("'%s' was deleted by the applypatch-msg hook"),
 +                              am_path(state, "final-commit"));
 +      }
 +
 +      return ret;
 +}
 +
 +/**
 + * Runs post-rewrite hook. Returns it exit code.
 + */
 +static int run_post_rewrite_hook(const struct am_state *state)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      const char *hook = find_hook("post-rewrite");
 +      int ret;
 +
 +      if (!hook)
 +              return 0;
 +
 +      argv_array_push(&cp.args, hook);
 +      argv_array_push(&cp.args, "rebase");
 +
 +      cp.in = xopen(am_path(state, "rewritten"), O_RDONLY);
 +      cp.stdout_to_stderr = 1;
 +
 +      ret = run_command(&cp);
 +
 +      close(cp.in);
 +      return ret;
 +}
 +
 +/**
 + * Reads the state directory's "rewritten" file, and copies notes from the old
 + * commits listed in the file to their rewritten commits.
 + *
 + * Returns 0 on success, -1 on failure.
 + */
 +static int copy_notes_for_rebase(const struct am_state *state)
 +{
 +      struct notes_rewrite_cfg *c;
 +      struct strbuf sb = STRBUF_INIT;
 +      const char *invalid_line = _("Malformed input line: '%s'.");
 +      const char *msg = "Notes added by 'git rebase'";
 +      FILE *fp;
 +      int ret = 0;
 +
 +      assert(state->rebasing);
 +
 +      c = init_copy_notes_for_rewrite("rebase");
 +      if (!c)
 +              return 0;
 +
 +      fp = xfopen(am_path(state, "rewritten"), "r");
 +
 +      while (!strbuf_getline(&sb, fp, '\n')) {
 +              unsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ];
 +
 +              if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) {
 +                      ret = error(invalid_line, sb.buf);
 +                      goto finish;
 +              }
 +
 +              if (get_sha1_hex(sb.buf, from_obj)) {
 +                      ret = error(invalid_line, sb.buf);
 +                      goto finish;
 +              }
 +
 +              if (sb.buf[GIT_SHA1_HEXSZ] != ' ') {
 +                      ret = error(invalid_line, sb.buf);
 +                      goto finish;
 +              }
 +
 +              if (get_sha1_hex(sb.buf + GIT_SHA1_HEXSZ + 1, to_obj)) {
 +                      ret = error(invalid_line, sb.buf);
 +                      goto finish;
 +              }
 +
 +              if (copy_note_for_rewrite(c, from_obj, to_obj))
 +                      ret = error(_("Failed to copy notes from '%s' to '%s'"),
 +                                      sha1_to_hex(from_obj), sha1_to_hex(to_obj));
 +      }
 +
 +finish:
 +      finish_copy_notes_for_rewrite(c, msg);
 +      fclose(fp);
 +      strbuf_release(&sb);
 +      return ret;
 +}
 +
 +/**
 + * Determines if the file looks like a piece of RFC2822 mail by grabbing all
 + * non-indented lines and checking if they look like they begin with valid
 + * header field names.
 + *
 + * Returns 1 if the file looks like a piece of mail, 0 otherwise.
 + */
 +static int is_mail(FILE *fp)
 +{
 +      const char *header_regex = "^[!-9;-~]+:";
 +      struct strbuf sb = STRBUF_INIT;
 +      regex_t regex;
 +      int ret = 1;
 +
 +      if (fseek(fp, 0L, SEEK_SET))
 +              die_errno(_("fseek failed"));
 +
 +      if (regcomp(&regex, header_regex, REG_NOSUB | REG_EXTENDED))
 +              die("invalid pattern: %s", header_regex);
 +
 +      while (!strbuf_getline_crlf(&sb, fp)) {
 +              if (!sb.len)
 +                      break; /* End of header */
 +
 +              /* Ignore indented folded lines */
 +              if (*sb.buf == '\t' || *sb.buf == ' ')
 +                      continue;
 +
 +              /* It's a header if it matches header_regex */
 +              if (regexec(&regex, sb.buf, 0, NULL, 0)) {
 +                      ret = 0;
 +                      goto done;
 +              }
 +      }
 +
 +done:
 +      regfree(&regex);
 +      strbuf_release(&sb);
 +      return ret;
 +}
 +
 +/**
 + * Attempts to detect the patch_format of the patches contained in `paths`,
 + * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if
 + * detection fails.
 + */
 +static int detect_patch_format(const char **paths)
 +{
 +      enum patch_format ret = PATCH_FORMAT_UNKNOWN;
 +      struct strbuf l1 = STRBUF_INIT;
 +      struct strbuf l2 = STRBUF_INIT;
 +      struct strbuf l3 = STRBUF_INIT;
 +      FILE *fp;
 +
 +      /*
 +       * We default to mbox format if input is from stdin and for directories
 +       */
 +      if (!*paths || !strcmp(*paths, "-") || is_directory(*paths))
 +              return PATCH_FORMAT_MBOX;
 +
 +      /*
 +       * Otherwise, check the first few lines of the first patch, starting
 +       * from the first non-blank line, to try to detect its format.
 +       */
 +
 +      fp = xfopen(*paths, "r");
 +
 +      while (!strbuf_getline_crlf(&l1, fp)) {
 +              if (l1.len)
 +                      break;
 +      }
 +
 +      if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) {
 +              ret = PATCH_FORMAT_MBOX;
 +              goto done;
 +      }
 +
 +      if (starts_with(l1.buf, "# This series applies on GIT commit")) {
 +              ret = PATCH_FORMAT_STGIT_SERIES;
 +              goto done;
 +      }
 +
 +      if (!strcmp(l1.buf, "# HG changeset patch")) {
 +              ret = PATCH_FORMAT_HG;
 +              goto done;
 +      }
 +
 +      strbuf_reset(&l2);
 +      strbuf_getline_crlf(&l2, fp);
 +      strbuf_reset(&l3);
 +      strbuf_getline_crlf(&l3, fp);
 +
 +      /*
 +       * If the second line is empty and the third is a From, Author or Date
 +       * entry, this is likely an StGit patch.
 +       */
 +      if (l1.len && !l2.len &&
 +              (starts_with(l3.buf, "From:") ||
 +               starts_with(l3.buf, "Author:") ||
 +               starts_with(l3.buf, "Date:"))) {
 +              ret = PATCH_FORMAT_STGIT;
 +              goto done;
 +      }
 +
 +      if (l1.len && is_mail(fp)) {
 +              ret = PATCH_FORMAT_MBOX;
 +              goto done;
 +      }
 +
 +done:
 +      fclose(fp);
 +      strbuf_release(&l1);
 +      return ret;
 +}
 +
 +/**
 + * Splits out individual email patches from `paths`, where each path is either
 + * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
 + */
 +static int split_mail_mbox(struct am_state *state, const char **paths, int keep_cr)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      struct strbuf last = STRBUF_INIT;
 +
 +      cp.git_cmd = 1;
 +      argv_array_push(&cp.args, "mailsplit");
 +      argv_array_pushf(&cp.args, "-d%d", state->prec);
 +      argv_array_pushf(&cp.args, "-o%s", state->dir);
 +      argv_array_push(&cp.args, "-b");
 +      if (keep_cr)
 +              argv_array_push(&cp.args, "--keep-cr");
 +      argv_array_push(&cp.args, "--");
 +      argv_array_pushv(&cp.args, paths);
 +
 +      if (capture_command(&cp, &last, 8))
 +              return -1;
 +
 +      state->cur = 1;
 +      state->last = strtol(last.buf, NULL, 10);
 +
 +      return 0;
 +}
 +
 +/**
 + * Callback signature for split_mail_conv(). The foreign patch should be
 + * read from `in`, and the converted patch (in RFC2822 mail format) should be
 + * written to `out`. Return 0 on success, or -1 on failure.
 + */
 +typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr);
 +
 +/**
 + * Calls `fn` for each file in `paths` to convert the foreign patch to the
 + * RFC2822 mail format suitable for parsing with git-mailinfo.
 + *
 + * Returns 0 on success, -1 on failure.
 + */
 +static int split_mail_conv(mail_conv_fn fn, struct am_state *state,
 +                      const char **paths, int keep_cr)
 +{
 +      static const char *stdin_only[] = {"-", NULL};
 +      int i;
 +
 +      if (!*paths)
 +              paths = stdin_only;
 +
 +      for (i = 0; *paths; paths++, i++) {
 +              FILE *in, *out;
 +              const char *mail;
 +              int ret;
 +
 +              if (!strcmp(*paths, "-"))
 +                      in = stdin;
 +              else
 +                      in = fopen(*paths, "r");
 +
 +              if (!in)
 +                      return error(_("could not open '%s' for reading: %s"),
 +                                      *paths, strerror(errno));
 +
 +              mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
 +
 +              out = fopen(mail, "w");
 +              if (!out)
 +                      return error(_("could not open '%s' for writing: %s"),
 +                                      mail, strerror(errno));
 +
 +              ret = fn(out, in, keep_cr);
 +
 +              fclose(out);
 +              fclose(in);
 +
 +              if (ret)
 +                      return error(_("could not parse patch '%s'"), *paths);
 +      }
 +
 +      state->cur = 1;
 +      state->last = i;
 +      return 0;
 +}
 +
 +/**
 + * A split_mail_conv() callback that converts an StGit patch to an RFC2822
 + * message suitable for parsing with git-mailinfo.
 + */
 +static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      int subject_printed = 0;
 +
 +      while (!strbuf_getline(&sb, in, '\n')) {
 +              const char *str;
 +
 +              if (str_isspace(sb.buf))
 +                      continue;
 +              else if (skip_prefix(sb.buf, "Author:", &str))
 +                      fprintf(out, "From:%s\n", str);
 +              else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date"))
 +                      fprintf(out, "%s\n", sb.buf);
 +              else if (!subject_printed) {
 +                      fprintf(out, "Subject: %s\n", sb.buf);
 +                      subject_printed = 1;
 +              } else {
 +                      fprintf(out, "\n%s\n", sb.buf);
 +                      break;
 +              }
 +      }
 +
 +      strbuf_reset(&sb);
 +      while (strbuf_fread(&sb, 8192, in) > 0) {
 +              fwrite(sb.buf, 1, sb.len, out);
 +              strbuf_reset(&sb);
 +      }
 +
 +      strbuf_release(&sb);
 +      return 0;
 +}
 +
 +/**
 + * This function only supports a single StGit series file in `paths`.
 + *
 + * Given an StGit series file, converts the StGit patches in the series into
 + * RFC2822 messages suitable for parsing with git-mailinfo, and queues them in
 + * the state directory.
 + *
 + * Returns 0 on success, -1 on failure.
 + */
 +static int split_mail_stgit_series(struct am_state *state, const char **paths,
 +                                      int keep_cr)
 +{
 +      const char *series_dir;
 +      char *series_dir_buf;
 +      FILE *fp;
 +      struct argv_array patches = ARGV_ARRAY_INIT;
 +      struct strbuf sb = STRBUF_INIT;
 +      int ret;
 +
 +      if (!paths[0] || paths[1])
 +              return error(_("Only one StGIT patch series can be applied at once"));
 +
 +      series_dir_buf = xstrdup(*paths);
 +      series_dir = dirname(series_dir_buf);
 +
 +      fp = fopen(*paths, "r");
 +      if (!fp)
 +              return error(_("could not open '%s' for reading: %s"), *paths,
 +                              strerror(errno));
 +
 +      while (!strbuf_getline(&sb, fp, '\n')) {
 +              if (*sb.buf == '#')
 +                      continue; /* skip comment lines */
 +
 +              argv_array_push(&patches, mkpath("%s/%s", series_dir, sb.buf));
 +      }
 +
 +      fclose(fp);
 +      strbuf_release(&sb);
 +      free(series_dir_buf);
 +
 +      ret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr);
 +
 +      argv_array_clear(&patches);
 +      return ret;
 +}
 +
 +/**
 + * A split_patches_conv() callback that converts a mercurial patch to a RFC2822
 + * message suitable for parsing with git-mailinfo.
 + */
 +static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      while (!strbuf_getline(&sb, in, '\n')) {
 +              const char *str;
 +
 +              if (skip_prefix(sb.buf, "# User ", &str))
 +                      fprintf(out, "From: %s\n", str);
 +              else if (skip_prefix(sb.buf, "# Date ", &str)) {
 +                      unsigned long timestamp;
 +                      long tz, tz2;
 +                      char *end;
 +
 +                      errno = 0;
 +                      timestamp = strtoul(str, &end, 10);
 +                      if (errno)
 +                              return error(_("invalid timestamp"));
 +
 +                      if (!skip_prefix(end, " ", &str))
 +                              return error(_("invalid Date line"));
 +
 +                      errno = 0;
 +                      tz = strtol(str, &end, 10);
 +                      if (errno)
 +                              return error(_("invalid timezone offset"));
 +
 +                      if (*end)
 +                              return error(_("invalid Date line"));
 +
 +                      /*
 +                       * mercurial's timezone is in seconds west of UTC,
 +                       * however git's timezone is in hours + minutes east of
 +                       * UTC. Convert it.
 +                       */
 +                      tz2 = labs(tz) / 3600 * 100 + labs(tz) % 3600 / 60;
 +                      if (tz > 0)
 +                              tz2 = -tz2;
 +
 +                      fprintf(out, "Date: %s\n", show_date(timestamp, tz2, DATE_MODE(RFC2822)));
 +              } else if (starts_with(sb.buf, "# ")) {
 +                      continue;
 +              } else {
 +                      fprintf(out, "\n%s\n", sb.buf);
 +                      break;
 +              }
 +      }
 +
 +      strbuf_reset(&sb);
 +      while (strbuf_fread(&sb, 8192, in) > 0) {
 +              fwrite(sb.buf, 1, sb.len, out);
 +              strbuf_reset(&sb);
 +      }
 +
 +      strbuf_release(&sb);
 +      return 0;
 +}
 +
 +/**
 + * Splits a list of files/directories into individual email patches. Each path
 + * in `paths` must be a file/directory that is formatted according to
 + * `patch_format`.
 + *
 + * Once split out, the individual email patches will be stored in the state
 + * directory, with each patch's filename being its index, padded to state->prec
 + * digits.
 + *
 + * state->cur will be set to the index of the first mail, and state->last will
 + * be set to the index of the last mail.
 + *
 + * Set keep_cr to 0 to convert all lines ending with \r\n to end with \n, 1
 + * to disable this behavior, -1 to use the default configured setting.
 + *
 + * Returns 0 on success, -1 on failure.
 + */
 +static int split_mail(struct am_state *state, enum patch_format patch_format,
 +                      const char **paths, int keep_cr)
 +{
 +      if (keep_cr < 0) {
 +              keep_cr = 0;
 +              git_config_get_bool("am.keepcr", &keep_cr);
 +      }
 +
 +      switch (patch_format) {
 +      case PATCH_FORMAT_MBOX:
 +              return split_mail_mbox(state, paths, keep_cr);
 +      case PATCH_FORMAT_STGIT:
 +              return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
 +      case PATCH_FORMAT_STGIT_SERIES:
 +              return split_mail_stgit_series(state, paths, keep_cr);
 +      case PATCH_FORMAT_HG:
 +              return split_mail_conv(hg_patch_to_mail, state, paths, keep_cr);
 +      default:
 +              die("BUG: invalid patch_format");
 +      }
 +      return -1;
 +}
 +
 +/**
 + * Setup a new am session for applying patches
 + */
 +static void am_setup(struct am_state *state, enum patch_format patch_format,
 +                      const char **paths, int keep_cr)
 +{
 +      unsigned char curr_head[GIT_SHA1_RAWSZ];
 +      const char *str;
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      if (!patch_format)
 +              patch_format = detect_patch_format(paths);
 +
 +      if (!patch_format) {
 +              fprintf_ln(stderr, _("Patch format detection failed."));
 +              exit(128);
 +      }
 +
 +      if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
 +              die_errno(_("failed to create directory '%s'"), state->dir);
 +
 +      if (split_mail(state, patch_format, paths, keep_cr) < 0) {
 +              am_destroy(state);
 +              die(_("Failed to split patches."));
 +      }
 +
 +      if (state->rebasing)
 +              state->threeway = 1;
 +
 +      write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f");
 +
 +      write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
 +
 +      write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f");
 +
 +      write_file(am_path(state, "utf8"), 1, state->utf8 ? "t" : "f");
 +
 +      switch (state->keep) {
 +      case KEEP_FALSE:
 +              str = "f";
 +              break;
 +      case KEEP_TRUE:
 +              str = "t";
 +              break;
 +      case KEEP_NON_PATCH:
 +              str = "b";
 +              break;
 +      default:
 +              die("BUG: invalid value for state->keep");
 +      }
 +
 +      write_file(am_path(state, "keep"), 1, "%s", str);
 +
 +      write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : "f");
 +
 +      switch (state->scissors) {
 +      case SCISSORS_UNSET:
 +              str = "";
 +              break;
 +      case SCISSORS_FALSE:
 +              str = "f";
 +              break;
 +      case SCISSORS_TRUE:
 +              str = "t";
 +              break;
 +      default:
 +              die("BUG: invalid value for state->scissors");
 +      }
 +
 +      write_file(am_path(state, "scissors"), 1, "%s", str);
 +
 +      sq_quote_argv(&sb, state->git_apply_opts.argv, 0);
 +      write_file(am_path(state, "apply-opt"), 1, "%s", sb.buf);
 +
 +      if (state->rebasing)
 +              write_file(am_path(state, "rebasing"), 1, "%s", "");
 +      else
 +              write_file(am_path(state, "applying"), 1, "%s", "");
 +
 +      if (!get_sha1("HEAD", curr_head)) {
 +              write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head));
 +              if (!state->rebasing)
 +                      update_ref("am", "ORIG_HEAD", curr_head, NULL, 0,
 +                                      UPDATE_REFS_DIE_ON_ERR);
 +      } else {
 +              write_file(am_path(state, "abort-safety"), 1, "%s", "");
 +              if (!state->rebasing)
 +                      delete_ref("ORIG_HEAD", NULL, 0);
 +      }
 +
 +      /*
 +       * NOTE: Since the "next" and "last" files determine if an am_state
 +       * session is in progress, they should be written last.
 +       */
 +
 +      write_file(am_path(state, "next"), 1, "%d", state->cur);
 +
 +      write_file(am_path(state, "last"), 1, "%d", state->last);
 +
 +      strbuf_release(&sb);
 +}
 +
 +/**
 + * Increments the patch pointer, and cleans am_state for the application of the
 + * next patch.
 + */
 +static void am_next(struct am_state *state)
 +{
 +      unsigned char head[GIT_SHA1_RAWSZ];
 +
 +      free(state->author_name);
 +      state->author_name = NULL;
 +
 +      free(state->author_email);
 +      state->author_email = NULL;
 +
 +      free(state->author_date);
 +      state->author_date = NULL;
 +
 +      free(state->msg);
 +      state->msg = NULL;
 +      state->msg_len = 0;
 +
 +      unlink(am_path(state, "author-script"));
 +      unlink(am_path(state, "final-commit"));
 +
 +      hashclr(state->orig_commit);
 +      unlink(am_path(state, "original-commit"));
 +
 +      if (!get_sha1("HEAD", head))
 +              write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head));
 +      else
 +              write_file(am_path(state, "abort-safety"), 1, "%s", "");
 +
 +      state->cur++;
 +      write_file(am_path(state, "next"), 1, "%d", state->cur);
 +}
 +
 +/**
 + * Returns the filename of the current patch email.
 + */
 +static const char *msgnum(const struct am_state *state)
 +{
 +      static struct strbuf sb = STRBUF_INIT;
 +
 +      strbuf_reset(&sb);
 +      strbuf_addf(&sb, "%0*d", state->prec, state->cur);
 +
 +      return sb.buf;
 +}
 +
 +/**
 + * Refresh and write index.
 + */
 +static void refresh_and_write_cache(void)
 +{
 +      struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
 +
 +      hold_locked_index(lock_file, 1);
 +      refresh_cache(REFRESH_QUIET);
 +      if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
 +              die(_("unable to write index file"));
 +}
 +
 +/**
 + * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn
 + * branch, returns 1 if there are entries in the index, 0 otherwise. If an
 + * strbuf is provided, the space-separated list of files that differ will be
 + * appended to it.
 + */
 +static int index_has_changes(struct strbuf *sb)
 +{
 +      unsigned char head[GIT_SHA1_RAWSZ];
 +      int i;
 +
 +      if (!get_sha1_tree("HEAD", head)) {
 +              struct diff_options opt;
 +
 +              diff_setup(&opt);
 +              DIFF_OPT_SET(&opt, EXIT_WITH_STATUS);
 +              if (!sb)
 +                      DIFF_OPT_SET(&opt, QUICK);
 +              do_diff_cache(head, &opt);
 +              diffcore_std(&opt);
 +              for (i = 0; sb && i < diff_queued_diff.nr; i++) {
 +                      if (i)
 +                              strbuf_addch(sb, ' ');
 +                      strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path);
 +              }
 +              diff_flush(&opt);
 +              return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0;
 +      } else {
 +              for (i = 0; sb && i < active_nr; i++) {
 +                      if (i)
 +                              strbuf_addch(sb, ' ');
 +                      strbuf_addstr(sb, active_cache[i]->name);
 +              }
 +              return !!active_nr;
 +      }
 +}
 +
 +/**
 + * Dies with a user-friendly message on how to proceed after resolving the
 + * problem. This message can be overridden with state->resolvemsg.
 + */
 +static void NORETURN die_user_resolve(const struct am_state *state)
 +{
 +      if (state->resolvemsg) {
 +              printf_ln("%s", state->resolvemsg);
 +      } else {
 +              const char *cmdline = state->interactive ? "git am -i" : "git am";
 +
 +              printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
 +              printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
 +              printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline);
 +      }
 +
 +      exit(128);
 +}
 +
 +/**
 + * 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);
 +      append_signoff(&sb, 0, 0);
 +      state->msg = strbuf_detach(&sb, &state->msg_len);
 +}
 +
 +/**
 + * Parses `mail` using git-mailinfo, extracting its patch and authorship info.
 + * state->msg will be set to the patch message. state->author_name,
 + * state->author_email and state->author_date will be set to the patch author's
 + * name, email and date respectively. The patch body will be written to the
 + * state directory's "patch" file.
 + *
 + * Returns 1 if the patch should be skipped, 0 otherwise.
 + */
 +static int parse_mail(struct am_state *state, const char *mail)
 +{
 +      FILE *fp;
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      struct strbuf sb = STRBUF_INIT;
 +      struct strbuf msg = STRBUF_INIT;
 +      struct strbuf author_name = STRBUF_INIT;
 +      struct strbuf author_date = STRBUF_INIT;
 +      struct strbuf author_email = STRBUF_INIT;
 +      int ret = 0;
 +
 +      cp.git_cmd = 1;
 +      cp.in = xopen(mail, O_RDONLY, 0);
 +      cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777);
 +
 +      argv_array_push(&cp.args, "mailinfo");
 +      argv_array_push(&cp.args, state->utf8 ? "-u" : "-n");
 +
 +      switch (state->keep) {
 +      case KEEP_FALSE:
 +              break;
 +      case KEEP_TRUE:
 +              argv_array_push(&cp.args, "-k");
 +              break;
 +      case KEEP_NON_PATCH:
 +              argv_array_push(&cp.args, "-b");
 +              break;
 +      default:
 +              die("BUG: invalid value for state->keep");
 +      }
 +
 +      if (state->message_id)
 +              argv_array_push(&cp.args, "-m");
 +
 +      switch (state->scissors) {
 +      case SCISSORS_UNSET:
 +              break;
 +      case SCISSORS_FALSE:
 +              argv_array_push(&cp.args, "--no-scissors");
 +              break;
 +      case SCISSORS_TRUE:
 +              argv_array_push(&cp.args, "--scissors");
 +              break;
 +      default:
 +              die("BUG: invalid value for state->scissors");
 +      }
 +
 +      argv_array_push(&cp.args, am_path(state, "msg"));
 +      argv_array_push(&cp.args, am_path(state, "patch"));
 +
 +      if (run_command(&cp) < 0)
 +              die("could not parse patch");
 +
 +      close(cp.in);
 +      close(cp.out);
 +
 +      /* Extract message and author information */
 +      fp = xfopen(am_path(state, "info"), "r");
 +      while (!strbuf_getline(&sb, fp, '\n')) {
 +              const char *x;
 +
 +              if (skip_prefix(sb.buf, "Subject: ", &x)) {
 +                      if (msg.len)
 +                              strbuf_addch(&msg, '\n');
 +                      strbuf_addstr(&msg, x);
 +              } else if (skip_prefix(sb.buf, "Author: ", &x))
 +                      strbuf_addstr(&author_name, x);
 +              else if (skip_prefix(sb.buf, "Email: ", &x))
 +                      strbuf_addstr(&author_email, x);
 +              else if (skip_prefix(sb.buf, "Date: ", &x))
 +                      strbuf_addstr(&author_date, x);
 +      }
 +      fclose(fp);
 +
 +      /* Skip pine's internal folder data */
 +      if (!strcmp(author_name.buf, "Mail System Internal Data")) {
 +              ret = 1;
 +              goto finish;
 +      }
 +
 +      if (is_empty_file(am_path(state, "patch"))) {
 +              printf_ln(_("Patch is empty. Was it split wrong?"));
 +              die_user_resolve(state);
 +      }
 +
 +      strbuf_addstr(&msg, "\n\n");
 +      if (strbuf_read_file(&msg, am_path(state, "msg"), 0) < 0)
 +              die_errno(_("could not read '%s'"), am_path(state, "msg"));
 +      stripspace(&msg, 0);
 +
 +      if (state->signoff)
 +              append_signoff(&msg, 0, 0);
 +
 +      assert(!state->author_name);
 +      state->author_name = strbuf_detach(&author_name, NULL);
 +
 +      assert(!state->author_email);
 +      state->author_email = strbuf_detach(&author_email, NULL);
 +
 +      assert(!state->author_date);
 +      state->author_date = strbuf_detach(&author_date, NULL);
 +
 +      assert(!state->msg);
 +      state->msg = strbuf_detach(&msg, &state->msg_len);
 +
 +finish:
 +      strbuf_release(&msg);
 +      strbuf_release(&author_date);
 +      strbuf_release(&author_email);
 +      strbuf_release(&author_name);
 +      strbuf_release(&sb);
 +      return ret;
 +}
 +
 +/**
 + * Sets commit_id to the commit hash where the mail was generated from.
 + * Returns 0 on success, -1 on failure.
 + */
 +static int get_mail_commit_sha1(unsigned char *commit_id, const char *mail)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      FILE *fp = xfopen(mail, "r");
 +      const char *x;
 +
 +      if (strbuf_getline(&sb, fp, '\n'))
 +              return -1;
 +
 +      if (!skip_prefix(sb.buf, "From ", &x))
 +              return -1;
 +
 +      if (get_sha1_hex(x, commit_id) < 0)
 +              return -1;
 +
 +      strbuf_release(&sb);
 +      fclose(fp);
 +      return 0;
 +}
 +
 +/**
 + * Sets state->msg, state->author_name, state->author_email, state->author_date
 + * to the commit's respective info.
 + */
 +static void get_commit_info(struct am_state *state, struct commit *commit)
 +{
 +      const char *buffer, *ident_line, *author_date, *msg;
 +      size_t ident_len;
 +      struct ident_split ident_split;
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      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);
 +      }
 +
 +      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
 +              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
 +              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);
 +
 +      assert(!state->msg);
 +      msg = strstr(buffer, "\n\n");
 +      if (!msg)
 +              die(_("unable to parse commit %s"), sha1_to_hex(commit->object.sha1));
 +      state->msg = xstrdup(msg + 2);
 +      state->msg_len = strlen(state->msg);
 +}
 +
 +/**
 + * Writes `commit` as a patch to the state directory's "patch" file.
 + */
 +static void write_commit_patch(const struct am_state *state, struct commit *commit)
 +{
 +      struct rev_info rev_info;
 +      FILE *fp;
 +
 +      fp = xfopen(am_path(state, "patch"), "w");
 +      init_revisions(&rev_info, NULL);
 +      rev_info.diff = 1;
 +      rev_info.abbrev = 0;
 +      rev_info.disable_stdin = 1;
 +      rev_info.show_root_diff = 1;
 +      rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
 +      rev_info.no_commit_id = 1;
 +      DIFF_OPT_SET(&rev_info.diffopt, BINARY);
 +      DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX);
 +      rev_info.diffopt.use_color = 0;
 +      rev_info.diffopt.file = fp;
 +      rev_info.diffopt.close_file = 1;
 +      add_pending_object(&rev_info, &commit->object, "");
 +      diff_setup_done(&rev_info.diffopt);
 +      log_tree_commit(&rev_info, commit);
 +}
 +
 +/**
 + * Writes the diff of the index against HEAD as a patch to the state
 + * directory's "patch" file.
 + */
 +static void write_index_patch(const struct am_state *state)
 +{
 +      struct tree *tree;
 +      unsigned char head[GIT_SHA1_RAWSZ];
 +      struct rev_info rev_info;
 +      FILE *fp;
 +
 +      if (!get_sha1_tree("HEAD", head))
 +              tree = lookup_tree(head);
 +      else
 +              tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
 +
 +      fp = xfopen(am_path(state, "patch"), "w");
 +      init_revisions(&rev_info, NULL);
 +      rev_info.diff = 1;
 +      rev_info.disable_stdin = 1;
 +      rev_info.no_commit_id = 1;
 +      rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
 +      rev_info.diffopt.use_color = 0;
 +      rev_info.diffopt.file = fp;
 +      rev_info.diffopt.close_file = 1;
 +      add_pending_object(&rev_info, &tree->object, "");
 +      diff_setup_done(&rev_info.diffopt);
 +      run_diff_index(&rev_info, 1);
 +}
 +
 +/**
 + * Like parse_mail(), but parses the mail by looking up its commit ID
 + * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
 + * of patches.
 + *
 + * state->orig_commit will be set to the original commit ID.
 + *
 + * Will always return 0 as the patch should never be skipped.
 + */
 +static int parse_mail_rebase(struct am_state *state, const char *mail)
 +{
 +      struct commit *commit;
 +      unsigned char commit_sha1[GIT_SHA1_RAWSZ];
 +
 +      if (get_mail_commit_sha1(commit_sha1, mail) < 0)
 +              die(_("could not parse %s"), mail);
 +
 +      commit = lookup_commit_or_die(commit_sha1, mail);
 +
 +      get_commit_info(state, commit);
 +
 +      write_commit_patch(state, commit);
 +
 +      hashcpy(state->orig_commit, commit_sha1);
 +      write_file(am_path(state, "original-commit"), 1, "%s",
 +                      sha1_to_hex(commit_sha1));
 +
 +      return 0;
 +}
 +
 +/**
 + * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If
 + * `index_file` is not NULL, the patch will be applied to that index.
 + */
 +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);
 +
 +      /*
 +       * 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;
 +      }
 +
 +      argv_array_push(&cp.args, "apply");
 +
 +      argv_array_pushv(&cp.args, state->git_apply_opts.argv);
 +
 +      if (index_file)
 +              argv_array_push(&cp.args, "--cached");
 +      else
 +              argv_array_push(&cp.args, "--index");
 +
 +      argv_array_push(&cp.args, am_path(state, "patch"));
 +
 +      if (run_command(&cp))
 +              return -1;
 +
 +      /* Reload index as git-apply will have modified it. */
 +      discard_cache();
 +      read_cache_from(index_file ? index_file : get_index_file());
 +
 +      return 0;
 +}
 +
 +/**
 + * Builds an index that contains just the blobs needed for a 3way merge.
 + */
 +static int build_fake_ancestor(const struct am_state *state, const char *index_file)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +
 +      cp.git_cmd = 1;
 +      argv_array_push(&cp.args, "apply");
 +      argv_array_pushv(&cp.args, state->git_apply_opts.argv);
 +      argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
 +      argv_array_push(&cp.args, am_path(state, "patch"));
 +
 +      if (run_command(&cp))
 +              return -1;
 +
 +      return 0;
 +}
 +
 +/**
 + * 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], his_tree[GIT_SHA1_RAWSZ],
 +                    our_tree[GIT_SHA1_RAWSZ];
 +      const unsigned char *bases[1] = {orig_tree};
 +      struct merge_options o;
 +      struct commit *result;
 +      char *his_tree_name;
 +
 +      if (get_sha1("HEAD", our_tree) < 0)
 +              hashcpy(our_tree, 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))
 +              return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
 +
 +      say(state, stdout, _("Using index info to reconstruct a base tree..."));
 +
 +      if (!state->quiet) {
 +              /*
 +               * List paths that needed 3-way fallback, so that the user can
 +               * review them with extra care to spot mismerges.
 +               */
 +              struct rev_info rev_info;
 +              const char *diff_filter_str = "--diff-filter=AM";
 +
 +              init_revisions(&rev_info, NULL);
 +              rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
 +              diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1);
 +              add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
 +              diff_setup_done(&rev_info.diffopt);
 +              run_diff_index(&rev_info, 1);
 +      }
 +
 +      if (run_apply(state, index_path))
 +              return error(_("Did you hand edit your patch?\n"
 +                              "It does not apply to blobs recorded in its index."));
 +
 +      if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL))
 +              return error("could not write tree");
 +
 +      say(state, stdout, _("Falling back to patching base and 3-way merge..."));
 +
 +      discard_cache();
 +      read_cache();
 +
 +      /*
 +       * This is not so wrong. Depending on which base we picked, orig_tree
 +       * may be wildly different from ours, but his_tree has the same set of
 +       * wildly different changes in parts the patch did not touch, so
 +       * recursive ends up canceling them, saying that we reverted all those
 +       * changes.
 +       */
 +
 +      init_merge_options(&o);
 +
 +      o.branch1 = "HEAD";
 +      his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
 +      o.branch2 = his_tree_name;
 +
 +      if (state->quiet)
 +              o.verbosity = 0;
 +
 +      if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) {
 +              rerere(state->allow_rerere_autoupdate);
 +              free(his_tree_name);
 +              return error(_("Failed to merge in the changes."));
 +      }
 +
 +      free(his_tree_name);
 +      return 0;
 +}
 +
 +/**
 + * Commits the current index with state->msg as the commit message and
 + * state->author_name, state->author_email and state->author_date as the author
 + * information.
 + */
 +static void do_commit(const struct am_state *state)
 +{
 +      unsigned char tree[GIT_SHA1_RAWSZ], parent[GIT_SHA1_RAWSZ],
 +                    commit[GIT_SHA1_RAWSZ];
 +      unsigned char *ptr;
 +      struct commit_list *parents = NULL;
 +      const char *reflog_msg, *author;
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      if (run_hook_le(NULL, "pre-applypatch", NULL))
 +              exit(1);
 +
 +      if (write_cache_as_tree(tree, 0, NULL))
 +              die(_("git write-tree failed to write a tree"));
 +
 +      if (!get_sha1_commit("HEAD", parent)) {
 +              ptr = parent;
 +              commit_list_insert(lookup_commit(parent), &parents);
 +      } else {
 +              ptr = NULL;
 +              say(state, stderr, _("applying to an empty history"));
 +      }
 +
 +      author = fmt_ident(state->author_name, state->author_email,
 +                      state->ignore_date ? NULL : state->author_date,
 +                      IDENT_STRICT);
 +
 +      if (state->committer_date_is_author_date)
 +              setenv("GIT_COMMITTER_DATE",
 +                      state->ignore_date ? "" : state->author_date, 1);
 +
 +      if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
 +                              author, state->sign_commit))
 +              die(_("failed to write commit object"));
 +
 +      reflog_msg = getenv("GIT_REFLOG_ACTION");
 +      if (!reflog_msg)
 +              reflog_msg = "am";
 +
 +      strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg),
 +                      state->msg);
 +
 +      update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR);
 +
 +      if (state->rebasing) {
 +              FILE *fp = xfopen(am_path(state, "rewritten"), "a");
 +
 +              assert(!is_null_sha1(state->orig_commit));
 +              fprintf(fp, "%s ", sha1_to_hex(state->orig_commit));
 +              fprintf(fp, "%s\n", sha1_to_hex(commit));
 +              fclose(fp);
 +      }
 +
 +      run_hook_le(NULL, "post-applypatch", NULL);
 +
 +      strbuf_release(&sb);
 +}
 +
 +/**
 + * Validates the am_state for resuming -- the "msg" and authorship fields must
 + * be filled up.
 + */
 +static void validate_resume_state(const struct am_state *state)
 +{
 +      if (!state->msg)
 +              die(_("cannot resume: %s does not exist."),
 +                      am_path(state, "final-commit"));
 +
 +      if (!state->author_name || !state->author_email || !state->author_date)
 +              die(_("cannot resume: %s does not exist."),
 +                      am_path(state, "author-script"));
 +}
 +
 +/**
 + * Interactively prompt the user on whether the current patch should be
 + * applied.
 + *
 + * Returns 0 if the user chooses to apply the patch, 1 if the user chooses to
 + * skip it.
 + */
 +static int do_interactive(struct am_state *state)
 +{
 +      assert(state->msg);
 +
 +      if (!isatty(0))
 +              die(_("cannot be interactive without stdin connected to a terminal."));
 +
 +      for (;;) {
 +              const char *reply;
 +
 +              puts(_("Commit Body is:"));
 +              puts("--------------------------");
 +              printf("%s", state->msg);
 +              puts("--------------------------");
 +
 +              /*
 +               * TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
 +               * in your translation. The program will only accept English
 +               * input at this point.
 +               */
 +              reply = git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "), PROMPT_ECHO);
 +
 +              if (!reply) {
 +                      continue;
 +              } else if (*reply == 'y' || *reply == 'Y') {
 +                      return 0;
 +              } else if (*reply == 'a' || *reply == 'A') {
 +                      state->interactive = 0;
 +                      return 0;
 +              } else if (*reply == 'n' || *reply == 'N') {
 +                      return 1;
 +              } else if (*reply == 'e' || *reply == 'E') {
 +                      struct strbuf msg = STRBUF_INIT;
 +
 +                      if (!launch_editor(am_path(state, "final-commit"), &msg, NULL)) {
 +                              free(state->msg);
 +                              state->msg = strbuf_detach(&msg, &state->msg_len);
 +                      }
 +                      strbuf_release(&msg);
 +              } else if (*reply == 'v' || *reply == 'V') {
 +                      const char *pager = git_pager(1);
 +                      struct child_process cp = CHILD_PROCESS_INIT;
 +
 +                      if (!pager)
 +                              pager = "cat";
 +                      argv_array_push(&cp.args, pager);
 +                      argv_array_push(&cp.args, am_path(state, "patch"));
 +                      run_command(&cp);
 +              }
 +      }
 +}
 +
 +/**
 + * Applies all queued mail.
 + *
 + * If `resume` is true, we are "resuming". The "msg" and authorship fields, as
 + * well as the state directory's "patch" file is used as-is for applying the
 + * patch and committing it.
 + */
 +static void am_run(struct am_state *state, int resume)
 +{
 +      const char *argv_gc_auto[] = {"gc", "--auto", NULL};
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      unlink(am_path(state, "dirtyindex"));
 +
 +      refresh_and_write_cache();
 +
 +      if (index_has_changes(&sb)) {
 +              write_file(am_path(state, "dirtyindex"), 1, "t");
 +              die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf);
 +      }
 +
 +      strbuf_release(&sb);
 +
 +      while (state->cur <= state->last) {
 +              const char *mail = am_path(state, msgnum(state));
 +              int apply_status;
 +
 +              if (!file_exists(mail))
 +                      goto next;
 +
 +              if (resume) {
 +                      validate_resume_state(state);
 +              } else {
 +                      int skip;
 +
 +                      if (state->rebasing)
 +                              skip = parse_mail_rebase(state, mail);
 +                      else
 +                              skip = parse_mail(state, mail);
 +
 +                      if (skip)
 +                              goto next; /* mail should be skipped */
 +
 +                      write_author_script(state);
 +                      write_commit_msg(state);
 +              }
 +
 +              if (state->interactive && do_interactive(state))
 +                      goto next;
 +
 +              if (run_applypatch_msg_hook(state))
 +                      exit(1);
 +
 +              say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 +
 +              apply_status = run_apply(state, NULL);
 +
 +              if (apply_status && state->threeway) {
 +                      struct strbuf sb = STRBUF_INIT;
 +
 +                      strbuf_addstr(&sb, am_path(state, "patch-merge-index"));
 +                      apply_status = fall_back_threeway(state, sb.buf);
 +                      strbuf_release(&sb);
 +
 +                      /*
 +                       * Applying the patch to an earlier tree and merging
 +                       * the result may have produced the same tree as ours.
 +                       */
 +                      if (!apply_status && !index_has_changes(NULL)) {
 +                              say(state, stdout, _("No changes -- Patch already applied."));
 +                              goto next;
 +                      }
 +              }
 +
 +              if (apply_status) {
 +                      int advice_amworkdir = 1;
 +
 +                      printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
 +                              linelen(state->msg), state->msg);
 +
 +                      git_config_get_bool("advice.amworkdir", &advice_amworkdir);
 +
 +                      if (advice_amworkdir)
 +                              printf_ln(_("The copy of the patch that failed is found in: %s"),
 +                                              am_path(state, "patch"));
 +
 +                      die_user_resolve(state);
 +              }
 +
 +              do_commit(state);
 +
 +next:
 +              am_next(state);
 +
 +              if (resume)
 +                      am_load(state);
 +              resume = 0;
 +      }
 +
 +      if (!is_empty_file(am_path(state, "rewritten"))) {
 +              assert(state->rebasing);
 +              copy_notes_for_rebase(state);
 +              run_post_rewrite_hook(state);
 +      }
 +
 +      /*
 +       * In rebasing mode, it's up to the caller to take care of
 +       * housekeeping.
 +       */
 +      if (!state->rebasing) {
 +              am_destroy(state);
 +              run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
 +      }
 +}
 +
 +/**
 + * Resume the current am session after patch application failure. The user did
 + * all the hard work, and we do not have to do any patch application. Just
 + * trust and commit what the user has in the index and working tree.
 + */
 +static void am_resolve(struct am_state *state)
 +{
 +      validate_resume_state(state);
 +
 +      say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 +
 +      if (!index_has_changes(NULL)) {
 +              printf_ln(_("No changes - did you forget to use 'git add'?\n"
 +                      "If there is nothing left to stage, chances are that something else\n"
 +                      "already introduced the same changes; you might want to skip this patch."));
 +              die_user_resolve(state);
 +      }
 +
 +      if (unmerged_cache()) {
 +              printf_ln(_("You still have unmerged paths in your index.\n"
 +                      "Did you forget to use 'git add'?"));
 +              die_user_resolve(state);
 +      }
 +
 +      if (state->interactive) {
 +              write_index_patch(state);
 +              if (do_interactive(state))
 +                      goto next;
 +      }
 +
 +      rerere(0);
 +
 +      do_commit(state);
 +
 +next:
 +      am_next(state);
 +      am_load(state);
 +      am_run(state, 0);
 +}
 +
 +/**
 + * Performs a checkout fast-forward from `head` to `remote`. If `reset` is
 + * true, any unmerged entries will be discarded. Returns 0 on success, -1 on
 + * failure.
 + */
 +static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
 +{
 +      struct lock_file *lock_file;
 +      struct unpack_trees_options opts;
 +      struct tree_desc t[2];
 +
 +      if (parse_tree(head) || parse_tree(remote))
 +              return -1;
 +
 +      lock_file = xcalloc(1, sizeof(struct lock_file));
 +      hold_locked_index(lock_file, 1);
 +
 +      refresh_cache(REFRESH_QUIET);
 +
 +      memset(&opts, 0, sizeof(opts));
 +      opts.head_idx = 1;
 +      opts.src_index = &the_index;
 +      opts.dst_index = &the_index;
 +      opts.update = 1;
 +      opts.merge = 1;
 +      opts.reset = reset;
 +      opts.fn = twoway_merge;
 +      init_tree_desc(&t[0], head->buffer, head->size);
 +      init_tree_desc(&t[1], remote->buffer, remote->size);
 +
 +      if (unpack_trees(2, t, &opts)) {
 +              rollback_lock_file(lock_file);
 +              return -1;
 +      }
 +
 +      if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
 +              die(_("unable to write new index file"));
 +
 +      return 0;
 +}
 +
 +/**
 + * Clean the index without touching entries that are not modified between
 + * `head` and `remote`.
 + */
 +static int clean_index(const unsigned char *head, const unsigned char *remote)
 +{
 +      struct lock_file *lock_file;
 +      struct tree *head_tree, *remote_tree, *index_tree;
 +      unsigned char index[GIT_SHA1_RAWSZ];
 +      struct pathspec pathspec;
 +
 +      head_tree = parse_tree_indirect(head);
 +      if (!head_tree)
 +              return error(_("Could not parse object '%s'."), sha1_to_hex(head));
 +
 +      remote_tree = parse_tree_indirect(remote);
 +      if (!remote_tree)
 +              return error(_("Could not parse object '%s'."), sha1_to_hex(remote));
 +
 +      read_cache_unmerged();
 +
 +      if (fast_forward_to(head_tree, head_tree, 1))
 +              return -1;
 +
 +      if (write_cache_as_tree(index, 0, NULL))
 +              return -1;
 +
 +      index_tree = parse_tree_indirect(index);
 +      if (!index_tree)
 +              return error(_("Could not parse object '%s'."), sha1_to_hex(index));
 +
 +      if (fast_forward_to(index_tree, remote_tree, 0))
 +              return -1;
 +
 +      memset(&pathspec, 0, sizeof(pathspec));
 +
 +      lock_file = xcalloc(1, sizeof(struct lock_file));
 +      hold_locked_index(lock_file, 1);
 +
 +      if (read_tree(remote_tree, 0, &pathspec)) {
 +              rollback_lock_file(lock_file);
 +              return -1;
 +      }
 +
 +      if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
 +              die(_("unable to write new index file"));
 +
 +      remove_branch_state();
 +
 +      return 0;
 +}
 +
 +/**
 + * Resets rerere's merge resolution metadata.
 + */
 +static void am_rerere_clear(void)
 +{
 +      struct string_list merge_rr = STRING_LIST_INIT_DUP;
 +      int fd = setup_rerere(&merge_rr, 0);
 +
 +      if (fd < 0)
 +              return;
 +
 +      rerere_clear(&merge_rr);
 +      string_list_clear(&merge_rr, 1);
 +}
 +
 +/**
 + * Resume the current am session by skipping the current patch.
 + */
 +static void am_skip(struct am_state *state)
 +{
 +      unsigned char head[GIT_SHA1_RAWSZ];
 +
 +      am_rerere_clear();
 +
 +      if (get_sha1("HEAD", head))
 +              hashcpy(head, EMPTY_TREE_SHA1_BIN);
 +
 +      if (clean_index(head, head))
 +              die(_("failed to clean index"));
 +
 +      am_next(state);
 +      am_load(state);
 +      am_run(state, 0);
 +}
 +
 +/**
 + * Returns true if it is safe to reset HEAD to the ORIG_HEAD, false otherwise.
 + *
 + * It is not safe to reset HEAD when:
 + * 1. git-am previously failed because the index was dirty.
 + * 2. HEAD has moved since git-am previously failed.
 + */
 +static int safe_to_abort(const struct am_state *state)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      unsigned char abort_safety[GIT_SHA1_RAWSZ], head[GIT_SHA1_RAWSZ];
 +
 +      if (file_exists(am_path(state, "dirtyindex")))
 +              return 0;
 +
 +      if (read_state_file(&sb, state, "abort-safety", 1) > 0) {
 +              if (get_sha1_hex(sb.buf, abort_safety))
 +                      die(_("could not parse %s"), am_path(state, "abort_safety"));
 +      } else
 +              hashclr(abort_safety);
 +
 +      if (get_sha1("HEAD", head))
 +              hashclr(head);
 +
 +      if (!hashcmp(head, abort_safety))
 +              return 1;
 +
 +      error(_("You seem to have moved HEAD since the last 'am' failure.\n"
 +              "Not rewinding to ORIG_HEAD"));
 +
 +      return 0;
 +}
 +
 +/**
 + * Aborts the current am session if it is safe to do so.
 + */
 +static void am_abort(struct am_state *state)
 +{
 +      unsigned char curr_head[GIT_SHA1_RAWSZ], orig_head[GIT_SHA1_RAWSZ];
 +      int has_curr_head, has_orig_head;
 +      char *curr_branch;
 +
 +      if (!safe_to_abort(state)) {
 +              am_destroy(state);
 +              return;
 +      }
 +
 +      am_rerere_clear();
 +
 +      curr_branch = resolve_refdup("HEAD", 0, curr_head, NULL);
 +      has_curr_head = !is_null_sha1(curr_head);
 +      if (!has_curr_head)
 +              hashcpy(curr_head, EMPTY_TREE_SHA1_BIN);
 +
 +      has_orig_head = !get_sha1("ORIG_HEAD", orig_head);
 +      if (!has_orig_head)
 +              hashcpy(orig_head, EMPTY_TREE_SHA1_BIN);
 +
 +      clean_index(curr_head, orig_head);
 +
 +      if (has_orig_head)
 +              update_ref("am --abort", "HEAD", orig_head,
 +                              has_curr_head ? curr_head : NULL, 0,
 +                              UPDATE_REFS_DIE_ON_ERR);
 +      else if (curr_branch)
 +              delete_ref(curr_branch, NULL, REF_NODEREF);
 +
 +      free(curr_branch);
 +      am_destroy(state);
 +}
 +
 +/**
 + * parse_options() callback that validates and sets opt->value to the
 + * PATCH_FORMAT_* enum value corresponding to `arg`.
 + */
 +static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)
 +{
 +      int *opt_value = opt->value;
 +
 +      if (!strcmp(arg, "mbox"))
 +              *opt_value = PATCH_FORMAT_MBOX;
 +      else if (!strcmp(arg, "stgit"))
 +              *opt_value = PATCH_FORMAT_STGIT;
 +      else if (!strcmp(arg, "stgit-series"))
 +              *opt_value = PATCH_FORMAT_STGIT_SERIES;
 +      else if (!strcmp(arg, "hg"))
 +              *opt_value = PATCH_FORMAT_HG;
 +      else
 +              return error(_("Invalid value for --patch-format: %s"), arg);
 +      return 0;
 +}
 +
 +enum resume_mode {
 +      RESUME_FALSE = 0,
 +      RESUME_APPLY,
 +      RESUME_RESOLVED,
 +      RESUME_SKIP,
 +      RESUME_ABORT
 +};
 +
 +int cmd_am(int argc, const char **argv, const char *prefix)
 +{
 +      struct am_state state;
 +      int binary = -1;
 +      int keep_cr = -1;
 +      int patch_format = PATCH_FORMAT_UNKNOWN;
 +      enum resume_mode resume = RESUME_FALSE;
 +      int in_progress;
 +
 +      const char * const usage[] = {
 +              N_("git am [options] [(<mbox>|<Maildir>)...]"),
 +              N_("git am [options] (--continue | --skip | --abort)"),
 +              NULL
 +      };
 +
 +      struct option options[] = {
 +              OPT_BOOL('i', "interactive", &state.interactive,
 +                      N_("run interactively")),
 +              OPT_HIDDEN_BOOL('b', "binary", &binary,
 +                      N_("(historical option -- no-op")),
 +              OPT_BOOL('3', "3way", &state.threeway,
 +                      N_("allow fall back on 3way merging if needed")),
 +              OPT__QUIET(&state.quiet, N_("be quiet")),
 +              OPT_SET_INT('s', "signoff", &state.signoff,
 +                      N_("add a Signed-off-by line to the commit message"),
 +                      SIGNOFF_EXPLICIT),
 +              OPT_BOOL('u', "utf8", &state.utf8,
 +                      N_("recode into utf8 (default)")),
 +              OPT_SET_INT('k', "keep", &state.keep,
 +                      N_("pass -k flag to git-mailinfo"), KEEP_TRUE),
 +              OPT_SET_INT(0, "keep-non-patch", &state.keep,
 +                      N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH),
 +              OPT_BOOL('m', "message-id", &state.message_id,
 +                      N_("pass -m flag to git-mailinfo")),
 +              { OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL,
 +                N_("pass --keep-cr flag to git-mailsplit for mbox format"),
 +                PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
 +              { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL,
 +                N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
 +                PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
 +              OPT_BOOL('c', "scissors", &state.scissors,
 +                      N_("strip everything before a scissors line")),
 +              OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
 +                      N_("pass it through git-apply"),
 +                      0),
 +              OPT_PASSTHRU_ARGV(0, "ignore-space-change", &state.git_apply_opts, NULL,
 +                      N_("pass it through git-apply"),
 +                      PARSE_OPT_NOARG),
 +              OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &state.git_apply_opts, NULL,
 +                      N_("pass it through git-apply"),
 +                      PARSE_OPT_NOARG),
 +              OPT_PASSTHRU_ARGV(0, "directory", &state.git_apply_opts, N_("root"),
 +                      N_("pass it through git-apply"),
 +                      0),
 +              OPT_PASSTHRU_ARGV(0, "exclude", &state.git_apply_opts, N_("path"),
 +                      N_("pass it through git-apply"),
 +                      0),
 +              OPT_PASSTHRU_ARGV(0, "include", &state.git_apply_opts, N_("path"),
 +                      N_("pass it through git-apply"),
 +                      0),
 +              OPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_("n"),
 +                      N_("pass it through git-apply"),
 +                      0),
 +              OPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_("num"),
 +                      N_("pass it through git-apply"),
 +                      0),
 +              OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 +                      N_("format the patch(es) are in"),
 +                      parse_opt_patchformat),
 +              OPT_PASSTHRU_ARGV(0, "reject", &state.git_apply_opts, NULL,
 +                      N_("pass it through git-apply"),
 +                      PARSE_OPT_NOARG),
 +              OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
 +                      N_("override error message when patch failure occurs")),
 +              OPT_CMDMODE(0, "continue", &resume,
 +                      N_("continue applying patches after resolving a conflict"),
 +                      RESUME_RESOLVED),
 +              OPT_CMDMODE('r', "resolved", &resume,
 +                      N_("synonyms for --continue"),
 +                      RESUME_RESOLVED),
 +              OPT_CMDMODE(0, "skip", &resume,
 +                      N_("skip the current patch"),
 +                      RESUME_SKIP),
 +              OPT_CMDMODE(0, "abort", &resume,
 +                      N_("restore the original branch and abort the patching operation."),
 +                      RESUME_ABORT),
 +              OPT_BOOL(0, "committer-date-is-author-date",
 +                      &state.committer_date_is_author_date,
 +                      N_("lie about committer date")),
 +              OPT_BOOL(0, "ignore-date", &state.ignore_date,
 +                      N_("use current timestamp for author date")),
 +              OPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate),
 +              { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 +                N_("GPG-sign commits"),
 +                PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 +              OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 +                      N_("(internal use for git-rebase)")),
 +              OPT_END()
 +      };
 +
 +      git_config(git_default_config, NULL);
 +
 +      am_state_init(&state, git_path("rebase-apply"));
 +
 +      in_progress = am_in_progress(&state);
 +      if (in_progress)
 +              am_load(&state);
 +
 +      argc = parse_options(argc, argv, prefix, options, usage, 0);
 +
 +      if (binary >= 0)
 +              fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n"
 +                              "it will be removed. Please do not use it anymore."));
 +
 +      /* Ensure a valid committer ident can be constructed */
 +      git_committer_info(IDENT_STRICT);
 +
 +      if (read_index_preload(&the_index, NULL) < 0)
 +              die(_("failed to read the index"));
 +
 +      if (in_progress) {
 +              /*
 +               * Catch user error to feed us patches when there is a session
 +               * in progress:
 +               *
 +               * 1. mbox path(s) are provided on the command-line.
 +               * 2. stdin is not a tty: the user is trying to feed us a patch
 +               *    from standard input. This is somewhat unreliable -- stdin
 +               *    could be /dev/null for example and the caller did not
 +               *    intend to feed us a patch but wanted to continue
 +               *    unattended.
 +               */
 +              if (argc || (resume == RESUME_FALSE && !isatty(0)))
 +                      die(_("previous rebase directory %s still exists but mbox given."),
 +                              state.dir);
 +
 +              if (resume == RESUME_FALSE)
 +                      resume = RESUME_APPLY;
 +
 +              if (state.signoff == SIGNOFF_EXPLICIT)
 +                      am_append_signoff(&state);
 +      } else {
 +              struct argv_array paths = ARGV_ARRAY_INIT;
 +              int i;
 +
 +              /*
 +               * Handle stray state directory in the independent-run case. In
 +               * the --rebasing case, it is up to the caller to take care of
 +               * stray directories.
 +               */
 +              if (file_exists(state.dir) && !state.rebasing) {
 +                      if (resume == RESUME_ABORT) {
 +                              am_destroy(&state);
 +                              am_state_release(&state);
 +                              return 0;
 +                      }
 +
 +                      die(_("Stray %s directory found.\n"
 +                              "Use \"git am --abort\" to remove it."),
 +                              state.dir);
 +              }
 +
 +              if (resume)
 +                      die(_("Resolve operation not in progress, we are not resuming."));
 +
 +              for (i = 0; i < argc; i++) {
 +                      if (is_absolute_path(argv[i]) || !prefix)
 +                              argv_array_push(&paths, argv[i]);
 +                      else
 +                              argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
 +              }
 +
 +              am_setup(&state, patch_format, paths.argv, keep_cr);
 +
 +              argv_array_clear(&paths);
 +      }
 +
 +      switch (resume) {
 +      case RESUME_FALSE:
 +              am_run(&state, 0);
 +              break;
 +      case RESUME_APPLY:
 +              am_run(&state, 1);
 +              break;
 +      case RESUME_RESOLVED:
 +              am_resolve(&state);
 +              break;
 +      case RESUME_SKIP:
 +              am_skip(&state);
 +              break;
 +      case RESUME_ABORT:
 +              am_abort(&state);
 +              break;
 +      default:
 +              die("BUG: invalid resume value");
 +      }
 +
 +      am_state_release(&state);
 +
 +      return 0;
 +}
Simple merge
diff --cc builtin/gc.c
Simple merge
diff --cc builtin/pull.c
index b7bc1ff,0000000..7e3c11e
mode 100644,000000..100644
--- /dev/null
@@@ -1,886 -1,0 +1,887 @@@
 +/*
 + * Builtin "git pull"
 + *
 + * Based on git-pull.sh by Junio C Hamano
 + *
 + * Fetch one or more remote refs and merge it/them into the current HEAD.
 + */
 +#include "cache.h"
 +#include "builtin.h"
 +#include "parse-options.h"
 +#include "exec_cmd.h"
 +#include "run-command.h"
 +#include "sha1-array.h"
 +#include "remote.h"
 +#include "dir.h"
 +#include "refs.h"
 +#include "revision.h"
++#include "tempfile.h"
 +#include "lockfile.h"
 +
 +enum rebase_type {
 +      REBASE_INVALID = -1,
 +      REBASE_FALSE = 0,
 +      REBASE_TRUE,
 +      REBASE_PRESERVE
 +};
 +
 +/**
 + * Parses the value of --rebase. If value is a false value, returns
 + * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
 + * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with
 + * a fatal error if fatal is true, otherwise returns REBASE_INVALID.
 + */
 +static enum rebase_type parse_config_rebase(const char *key, const char *value,
 +              int fatal)
 +{
 +      int v = git_config_maybe_bool("pull.rebase", value);
 +
 +      if (!v)
 +              return REBASE_FALSE;
 +      else if (v > 0)
 +              return REBASE_TRUE;
 +      else if (!strcmp(value, "preserve"))
 +              return REBASE_PRESERVE;
 +
 +      if (fatal)
 +              die(_("Invalid value for %s: %s"), key, value);
 +      else
 +              error(_("Invalid value for %s: %s"), key, value);
 +
 +      return REBASE_INVALID;
 +}
 +
 +/**
 + * Callback for --rebase, which parses arg with parse_config_rebase().
 + */
 +static int parse_opt_rebase(const struct option *opt, const char *arg, int unset)
 +{
 +      enum rebase_type *value = opt->value;
 +
 +      if (arg)
 +              *value = parse_config_rebase("--rebase", arg, 0);
 +      else
 +              *value = unset ? REBASE_FALSE : REBASE_TRUE;
 +      return *value == REBASE_INVALID ? -1 : 0;
 +}
 +
 +static const char * const pull_usage[] = {
 +      N_("git pull [options] [<repository> [<refspec>...]]"),
 +      NULL
 +};
 +
 +/* Shared options */
 +static int opt_verbosity;
 +static char *opt_progress;
 +
 +/* Options passed to git-merge or git-rebase */
 +static enum rebase_type opt_rebase = -1;
 +static char *opt_diffstat;
 +static char *opt_log;
 +static char *opt_squash;
 +static char *opt_commit;
 +static char *opt_edit;
 +static char *opt_ff;
 +static char *opt_verify_signatures;
 +static struct argv_array opt_strategies = ARGV_ARRAY_INIT;
 +static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT;
 +static char *opt_gpg_sign;
 +
 +/* Options passed to git-fetch */
 +static char *opt_all;
 +static char *opt_append;
 +static char *opt_upload_pack;
 +static int opt_force;
 +static char *opt_tags;
 +static char *opt_prune;
 +static char *opt_recurse_submodules;
 +static int opt_dry_run;
 +static char *opt_keep;
 +static char *opt_depth;
 +static char *opt_unshallow;
 +static char *opt_update_shallow;
 +static char *opt_refmap;
 +
 +static struct option pull_options[] = {
 +      /* Shared options */
 +      OPT__VERBOSITY(&opt_verbosity),
 +      OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
 +              N_("force progress reporting"),
 +              PARSE_OPT_NOARG),
 +
 +      /* Options passed to git-merge or git-rebase */
 +      OPT_GROUP(N_("Options related to merging")),
 +      { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
 +        N_("false|true|preserve"),
 +        N_("incorporate changes by rebasing rather than merging"),
 +        PARSE_OPT_OPTARG, parse_opt_rebase },
 +      OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
 +              N_("do not show a diffstat at the end of the merge"),
 +              PARSE_OPT_NOARG | PARSE_OPT_NONEG),
 +      OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL,
 +              N_("show a diffstat at the end of the merge"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL,
 +              N_("(synonym to --stat)"),
 +              PARSE_OPT_NOARG | PARSE_OPT_HIDDEN),
 +      OPT_PASSTHRU(0, "log", &opt_log, N_("n"),
 +              N_("add (at most <n>) entries from shortlog to merge commit message"),
 +              PARSE_OPT_OPTARG),
 +      OPT_PASSTHRU(0, "squash", &opt_squash, NULL,
 +              N_("create a single commit instead of doing a merge"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "commit", &opt_commit, NULL,
 +              N_("perform a commit if the merge succeeds (default)"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "edit", &opt_edit, NULL,
 +              N_("edit message before committing"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "ff", &opt_ff, NULL,
 +              N_("allow fast-forward"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL,
 +              N_("abort if fast-forward is not possible"),
 +              PARSE_OPT_NOARG | PARSE_OPT_NONEG),
 +      OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL,
 +              N_("verify that the named commit has a valid GPG signature"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"),
 +              N_("merge strategy to use"),
 +              0),
 +      OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts,
 +              N_("option=value"),
 +              N_("option for selected merge strategy"),
 +              0),
 +      OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"),
 +              N_("GPG sign commit"),
 +              PARSE_OPT_OPTARG),
 +
 +      /* Options passed to git-fetch */
 +      OPT_GROUP(N_("Options related to fetching")),
 +      OPT_PASSTHRU(0, "all", &opt_all, NULL,
 +              N_("fetch from all remotes"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU('a', "append", &opt_append, NULL,
 +              N_("append to .git/FETCH_HEAD instead of overwriting"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"),
 +              N_("path to upload pack on remote end"),
 +              0),
 +      OPT__FORCE(&opt_force, N_("force overwrite of local branch")),
 +      OPT_PASSTHRU('t', "tags", &opt_tags, NULL,
 +              N_("fetch all tags and associated objects"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU('p', "prune", &opt_prune, NULL,
 +              N_("prune remote-tracking branches no longer on remote"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "recurse-submodules", &opt_recurse_submodules,
 +              N_("on-demand"),
 +              N_("control recursive fetching of submodules"),
 +              PARSE_OPT_OPTARG),
 +      OPT_BOOL(0, "dry-run", &opt_dry_run,
 +              N_("dry run")),
 +      OPT_PASSTHRU('k', "keep", &opt_keep, NULL,
 +              N_("keep downloaded pack"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
 +              N_("deepen history of shallow clone"),
 +              0),
 +      OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL,
 +              N_("convert to a complete repository"),
 +              PARSE_OPT_NONEG | PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL,
 +              N_("accept refs that update .git/shallow"),
 +              PARSE_OPT_NOARG),
 +      OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
 +              N_("specify fetch refmap"),
 +              PARSE_OPT_NONEG),
 +
 +      OPT_END()
 +};
 +
 +/**
 + * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level.
 + */
 +static void argv_push_verbosity(struct argv_array *arr)
 +{
 +      int verbosity;
 +
 +      for (verbosity = opt_verbosity; verbosity > 0; verbosity--)
 +              argv_array_push(arr, "-v");
 +
 +      for (verbosity = opt_verbosity; verbosity < 0; verbosity++)
 +              argv_array_push(arr, "-q");
 +}
 +
 +/**
 + * Pushes "-f" switches into arr to match the opt_force level.
 + */
 +static void argv_push_force(struct argv_array *arr)
 +{
 +      int force = opt_force;
 +      while (force-- > 0)
 +              argv_array_push(arr, "-f");
 +}
 +
 +/**
 + * Sets the GIT_REFLOG_ACTION environment variable to the concatenation of argv
 + */
 +static void set_reflog_message(int argc, const char **argv)
 +{
 +      int i;
 +      struct strbuf msg = STRBUF_INIT;
 +
 +      for (i = 0; i < argc; i++) {
 +              if (i)
 +                      strbuf_addch(&msg, ' ');
 +              strbuf_addstr(&msg, argv[i]);
 +      }
 +
 +      setenv("GIT_REFLOG_ACTION", msg.buf, 0);
 +
 +      strbuf_release(&msg);
 +}
 +
 +/**
 + * If pull.ff is unset, returns NULL. If pull.ff is "true", returns "--ff". If
 + * pull.ff is "false", returns "--no-ff". If pull.ff is "only", returns
 + * "--ff-only". Otherwise, if pull.ff is set to an invalid value, die with an
 + * error.
 + */
 +static const char *config_get_ff(void)
 +{
 +      const char *value;
 +
 +      if (git_config_get_value("pull.ff", &value))
 +              return NULL;
 +
 +      switch (git_config_maybe_bool("pull.ff", value)) {
 +      case 0:
 +              return "--no-ff";
 +      case 1:
 +              return "--ff";
 +      }
 +
 +      if (!strcmp(value, "only"))
 +              return "--ff-only";
 +
 +      die(_("Invalid value for pull.ff: %s"), value);
 +}
 +
 +/**
 + * Returns the default configured value for --rebase. It first looks for the
 + * value of "branch.$curr_branch.rebase", where $curr_branch is the current
 + * branch, and if HEAD is detached or the configuration key does not exist,
 + * looks for the value of "pull.rebase". If both configuration keys do not
 + * exist, returns REBASE_FALSE.
 + */
 +static enum rebase_type config_get_rebase(void)
 +{
 +      struct branch *curr_branch = branch_get("HEAD");
 +      const char *value;
 +
 +      if (curr_branch) {
 +              char *key = xstrfmt("branch.%s.rebase", curr_branch->name);
 +
 +              if (!git_config_get_value(key, &value)) {
 +                      enum rebase_type ret = parse_config_rebase(key, value, 1);
 +                      free(key);
 +                      return ret;
 +              }
 +
 +              free(key);
 +      }
 +
 +      if (!git_config_get_value("pull.rebase", &value))
 +              return parse_config_rebase("pull.rebase", value, 1);
 +
 +      return REBASE_FALSE;
 +}
 +
 +/**
 + * Returns 1 if there are unstaged changes, 0 otherwise.
 + */
 +static int has_unstaged_changes(const char *prefix)
 +{
 +      struct rev_info rev_info;
 +      int result;
 +
 +      init_revisions(&rev_info, prefix);
 +      DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
 +      DIFF_OPT_SET(&rev_info.diffopt, QUICK);
 +      diff_setup_done(&rev_info.diffopt);
 +      result = run_diff_files(&rev_info, 0);
 +      return diff_result_code(&rev_info.diffopt, result);
 +}
 +
 +/**
 + * Returns 1 if there are uncommitted changes, 0 otherwise.
 + */
 +static int has_uncommitted_changes(const char *prefix)
 +{
 +      struct rev_info rev_info;
 +      int result;
 +
 +      if (is_cache_unborn())
 +              return 0;
 +
 +      init_revisions(&rev_info, prefix);
 +      DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
 +      DIFF_OPT_SET(&rev_info.diffopt, QUICK);
 +      add_head_to_pending(&rev_info);
 +      diff_setup_done(&rev_info.diffopt);
 +      result = run_diff_index(&rev_info, 1);
 +      return diff_result_code(&rev_info.diffopt, result);
 +}
 +
 +/**
 + * If the work tree has unstaged or uncommitted changes, dies with the
 + * appropriate message.
 + */
 +static void die_on_unclean_work_tree(const char *prefix)
 +{
 +      struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
 +      int do_die = 0;
 +
 +      hold_locked_index(lock_file, 0);
 +      refresh_cache(REFRESH_QUIET);
 +      update_index_if_able(&the_index, lock_file);
 +      rollback_lock_file(lock_file);
 +
 +      if (has_unstaged_changes(prefix)) {
 +              error(_("Cannot pull with rebase: You have unstaged changes."));
 +              do_die = 1;
 +      }
 +
 +      if (has_uncommitted_changes(prefix)) {
 +              if (do_die)
 +                      error(_("Additionally, your index contains uncommitted changes."));
 +              else
 +                      error(_("Cannot pull with rebase: Your index contains uncommitted changes."));
 +              do_die = 1;
 +      }
 +
 +      if (do_die)
 +              exit(1);
 +}
 +
 +/**
 + * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge
 + * into merge_heads.
 + */
 +static void get_merge_heads(struct sha1_array *merge_heads)
 +{
 +      const char *filename = git_path("FETCH_HEAD");
 +      FILE *fp;
 +      struct strbuf sb = STRBUF_INIT;
 +      unsigned char sha1[GIT_SHA1_RAWSZ];
 +
 +      if (!(fp = fopen(filename, "r")))
 +              die_errno(_("could not open '%s' for reading"), filename);
 +      while (strbuf_getline(&sb, fp, '\n') != EOF) {
 +              if (get_sha1_hex(sb.buf, sha1))
 +                      continue;  /* invalid line: does not start with SHA1 */
 +              if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t"))
 +                      continue;  /* ref is not-for-merge */
 +              sha1_array_append(merge_heads, sha1);
 +      }
 +      fclose(fp);
 +      strbuf_release(&sb);
 +}
 +
 +/**
 + * Used by die_no_merge_candidates() as a for_each_remote() callback to
 + * retrieve the name of the remote if the repository only has one remote.
 + */
 +static int get_only_remote(struct remote *remote, void *cb_data)
 +{
 +      const char **remote_name = cb_data;
 +
 +      if (*remote_name)
 +              return -1;
 +
 +      *remote_name = remote->name;
 +      return 0;
 +}
 +
 +/**
 + * Dies with the appropriate reason for why there are no merge candidates:
 + *
 + * 1. We fetched from a specific remote, and a refspec was given, but it ended
 + *    up not fetching anything. This is usually because the user provided a
 + *    wildcard refspec which had no matches on the remote end.
 + *
 + * 2. We fetched from a non-default remote, but didn't specify a branch to
 + *    merge. We can't use the configured one because it applies to the default
 + *    remote, thus the user must specify the branches to merge.
 + *
 + * 3. We fetched from the branch's or repo's default remote, but:
 + *
 + *    a. We are not on a branch, so there will never be a configured branch to
 + *       merge with.
 + *
 + *    b. We are on a branch, but there is no configured branch to merge with.
 + *
 + * 4. We fetched from the branch's or repo's default remote, but the configured
 + *    branch to merge didn't get fetched. (Either it doesn't exist, or wasn't
 + *    part of the configured fetch refspec.)
 + */
 +static void NORETURN die_no_merge_candidates(const char *repo, const char **refspecs)
 +{
 +      struct branch *curr_branch = branch_get("HEAD");
 +      const char *remote = curr_branch ? curr_branch->remote_name : NULL;
 +
 +      if (*refspecs) {
 +              if (opt_rebase)
 +                      fprintf_ln(stderr, _("There is no candidate for rebasing against among the refs that you just fetched."));
 +              else
 +                      fprintf_ln(stderr, _("There are no candidates for merging among the refs that you just fetched."));
 +              fprintf_ln(stderr, _("Generally this means that you provided a wildcard refspec which had no\n"
 +                                      "matches on the remote end."));
 +      } else if (repo && curr_branch && (!remote || strcmp(repo, remote))) {
 +              fprintf_ln(stderr, _("You asked to pull from the remote '%s', but did not specify\n"
 +                      "a branch. Because this is not the default configured remote\n"
 +                      "for your current branch, you must specify a branch on the command line."),
 +                      repo);
 +      } else if (!curr_branch) {
 +              fprintf_ln(stderr, _("You are not currently on a branch."));
 +              if (opt_rebase)
 +                      fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
 +              else
 +                      fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
 +              fprintf_ln(stderr, _("See git-pull(1) for details."));
 +              fprintf(stderr, "\n");
 +              fprintf_ln(stderr, "    git pull <remote> <branch>");
 +              fprintf(stderr, "\n");
 +      } else if (!curr_branch->merge_nr) {
 +              const char *remote_name = NULL;
 +
 +              if (for_each_remote(get_only_remote, &remote_name) || !remote_name)
 +                      remote_name = "<remote>";
 +
 +              fprintf_ln(stderr, _("There is no tracking information for the current branch."));
 +              if (opt_rebase)
 +                      fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
 +              else
 +                      fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
 +              fprintf_ln(stderr, _("See git-pull(1) for details."));
 +              fprintf(stderr, "\n");
 +              fprintf_ln(stderr, "    git pull <remote> <branch>");
 +              fprintf(stderr, "\n");
 +              fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:\n"
 +                              "\n"
 +                              "    git branch --set-upstream-to=%s/<branch> %s\n"),
 +                              remote_name, curr_branch->name);
 +      } else
 +              fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n"
 +                      "from the remote, but no such ref was fetched."),
 +                      *curr_branch->merge_name);
 +      exit(1);
 +}
 +
 +/**
 + * Parses argv into [<repo> [<refspecs>...]], returning their values in `repo`
 + * as a string and `refspecs` as a null-terminated array of strings. If `repo`
 + * is not provided in argv, it is set to NULL.
 + */
 +static void parse_repo_refspecs(int argc, const char **argv, const char **repo,
 +              const char ***refspecs)
 +{
 +      if (argc > 0) {
 +              *repo = *argv++;
 +              argc--;
 +      } else
 +              *repo = NULL;
 +      *refspecs = argv;
 +}
 +
 +/**
 + * Runs git-fetch, returning its exit status. `repo` and `refspecs` are the
 + * repository and refspecs to fetch, or NULL if they are not provided.
 + */
 +static int run_fetch(const char *repo, const char **refspecs)
 +{
 +      struct argv_array args = ARGV_ARRAY_INIT;
 +      int ret;
 +
 +      argv_array_pushl(&args, "fetch", "--update-head-ok", NULL);
 +
 +      /* Shared options */
 +      argv_push_verbosity(&args);
 +      if (opt_progress)
 +              argv_array_push(&args, opt_progress);
 +
 +      /* Options passed to git-fetch */
 +      if (opt_all)
 +              argv_array_push(&args, opt_all);
 +      if (opt_append)
 +              argv_array_push(&args, opt_append);
 +      if (opt_upload_pack)
 +              argv_array_push(&args, opt_upload_pack);
 +      argv_push_force(&args);
 +      if (opt_tags)
 +              argv_array_push(&args, opt_tags);
 +      if (opt_prune)
 +              argv_array_push(&args, opt_prune);
 +      if (opt_recurse_submodules)
 +              argv_array_push(&args, opt_recurse_submodules);
 +      if (opt_dry_run)
 +              argv_array_push(&args, "--dry-run");
 +      if (opt_keep)
 +              argv_array_push(&args, opt_keep);
 +      if (opt_depth)
 +              argv_array_push(&args, opt_depth);
 +      if (opt_unshallow)
 +              argv_array_push(&args, opt_unshallow);
 +      if (opt_update_shallow)
 +              argv_array_push(&args, opt_update_shallow);
 +      if (opt_refmap)
 +              argv_array_push(&args, opt_refmap);
 +
 +      if (repo) {
 +              argv_array_push(&args, repo);
 +              argv_array_pushv(&args, refspecs);
 +      } else if (*refspecs)
 +              die("BUG: refspecs without repo?");
 +      ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
 +      argv_array_clear(&args);
 +      return ret;
 +}
 +
 +/**
 + * "Pulls into void" by branching off merge_head.
 + */
 +static int pull_into_void(const unsigned char *merge_head,
 +              const unsigned char *curr_head)
 +{
 +      /*
 +       * Two-way merge: we treat the index as based on an empty tree,
 +       * and try to fast-forward to HEAD. This ensures we will not lose
 +       * index/worktree changes that the user already made on the unborn
 +       * branch.
 +       */
 +      if (checkout_fast_forward(EMPTY_TREE_SHA1_BIN, merge_head, 0))
 +              return 1;
 +
 +      if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
 +              return 1;
 +
 +      return 0;
 +}
 +
 +/**
 + * Runs git-merge, returning its exit status.
 + */
 +static int run_merge(void)
 +{
 +      int ret;
 +      struct argv_array args = ARGV_ARRAY_INIT;
 +
 +      argv_array_pushl(&args, "merge", NULL);
 +
 +      /* Shared options */
 +      argv_push_verbosity(&args);
 +      if (opt_progress)
 +              argv_array_push(&args, opt_progress);
 +
 +      /* Options passed to git-merge */
 +      if (opt_diffstat)
 +              argv_array_push(&args, opt_diffstat);
 +      if (opt_log)
 +              argv_array_push(&args, opt_log);
 +      if (opt_squash)
 +              argv_array_push(&args, opt_squash);
 +      if (opt_commit)
 +              argv_array_push(&args, opt_commit);
 +      if (opt_edit)
 +              argv_array_push(&args, opt_edit);
 +      if (opt_ff)
 +              argv_array_push(&args, opt_ff);
 +      if (opt_verify_signatures)
 +              argv_array_push(&args, opt_verify_signatures);
 +      argv_array_pushv(&args, opt_strategies.argv);
 +      argv_array_pushv(&args, opt_strategy_opts.argv);
 +      if (opt_gpg_sign)
 +              argv_array_push(&args, opt_gpg_sign);
 +
 +      argv_array_push(&args, "FETCH_HEAD");
 +      ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
 +      argv_array_clear(&args);
 +      return ret;
 +}
 +
 +/**
 + * Returns remote's upstream branch for the current branch. If remote is NULL,
 + * the current branch's configured default remote is used. Returns NULL if
 + * `remote` does not name a valid remote, HEAD does not point to a branch,
 + * remote is not the branch's configured remote or the branch does not have any
 + * configured upstream branch.
 + */
 +static const char *get_upstream_branch(const char *remote)
 +{
 +      struct remote *rm;
 +      struct branch *curr_branch;
 +      const char *curr_branch_remote;
 +
 +      rm = remote_get(remote);
 +      if (!rm)
 +              return NULL;
 +
 +      curr_branch = branch_get("HEAD");
 +      if (!curr_branch)
 +              return NULL;
 +
 +      curr_branch_remote = remote_for_branch(curr_branch, NULL);
 +      assert(curr_branch_remote);
 +
 +      if (strcmp(curr_branch_remote, rm->name))
 +              return NULL;
 +
 +      return branch_get_upstream(curr_branch, NULL);
 +}
 +
 +/**
 + * Derives the remote tracking branch from the remote and refspec.
 + *
 + * FIXME: The current implementation assumes the default mapping of
 + * refs/heads/<branch_name> to refs/remotes/<remote_name>/<branch_name>.
 + */
 +static const char *get_tracking_branch(const char *remote, const char *refspec)
 +{
 +      struct refspec *spec;
 +      const char *spec_src;
 +      const char *merge_branch;
 +
 +      spec = parse_fetch_refspec(1, &refspec);
 +      spec_src = spec->src;
 +      if (!*spec_src || !strcmp(spec_src, "HEAD"))
 +              spec_src = "HEAD";
 +      else if (skip_prefix(spec_src, "heads/", &spec_src))
 +              ;
 +      else if (skip_prefix(spec_src, "refs/heads/", &spec_src))
 +              ;
 +      else if (starts_with(spec_src, "refs/") ||
 +              starts_with(spec_src, "tags/") ||
 +              starts_with(spec_src, "remotes/"))
 +              spec_src = "";
 +
 +      if (*spec_src) {
 +              if (!strcmp(remote, "."))
 +                      merge_branch = mkpath("refs/heads/%s", spec_src);
 +              else
 +                      merge_branch = mkpath("refs/remotes/%s/%s", remote, spec_src);
 +      } else
 +              merge_branch = NULL;
 +
 +      free_refspec(1, spec);
 +      return merge_branch;
 +}
 +
 +/**
 + * Given the repo and refspecs, sets fork_point to the point at which the
 + * current branch forked from its remote tracking branch. Returns 0 on success,
 + * -1 on failure.
 + */
 +static int get_rebase_fork_point(unsigned char *fork_point, const char *repo,
 +              const char *refspec)
 +{
 +      int ret;
 +      struct branch *curr_branch;
 +      const char *remote_branch;
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      curr_branch = branch_get("HEAD");
 +      if (!curr_branch)
 +              return -1;
 +
 +      if (refspec)
 +              remote_branch = get_tracking_branch(repo, refspec);
 +      else
 +              remote_branch = get_upstream_branch(repo);
 +
 +      if (!remote_branch)
 +              return -1;
 +
 +      argv_array_pushl(&cp.args, "merge-base", "--fork-point",
 +                      remote_branch, curr_branch->name, NULL);
 +      cp.no_stdin = 1;
 +      cp.no_stderr = 1;
 +      cp.git_cmd = 1;
 +
 +      ret = capture_command(&cp, &sb, GIT_SHA1_HEXSZ);
 +      if (ret)
 +              goto cleanup;
 +
 +      ret = get_sha1_hex(sb.buf, fork_point);
 +      if (ret)
 +              goto cleanup;
 +
 +cleanup:
 +      strbuf_release(&sb);
 +      return ret ? -1 : 0;
 +}
 +
 +/**
 + * Sets merge_base to the octopus merge base of curr_head, merge_head and
 + * fork_point. Returns 0 if a merge base is found, 1 otherwise.
 + */
 +static int get_octopus_merge_base(unsigned char *merge_base,
 +              const unsigned char *curr_head,
 +              const unsigned char *merge_head,
 +              const unsigned char *fork_point)
 +{
 +      struct commit_list *revs = NULL, *result;
 +
 +      commit_list_insert(lookup_commit_reference(curr_head), &revs);
 +      commit_list_insert(lookup_commit_reference(merge_head), &revs);
 +      if (!is_null_sha1(fork_point))
 +              commit_list_insert(lookup_commit_reference(fork_point), &revs);
 +
 +      result = reduce_heads(get_octopus_merge_bases(revs));
 +      free_commit_list(revs);
 +      if (!result)
 +              return 1;
 +
 +      hashcpy(merge_base, result->item->object.sha1);
 +      return 0;
 +}
 +
 +/**
 + * Given the current HEAD SHA1, the merge head returned from git-fetch and the
 + * fork point calculated by get_rebase_fork_point(), runs git-rebase with the
 + * appropriate arguments and returns its exit status.
 + */
 +static int run_rebase(const unsigned char *curr_head,
 +              const unsigned char *merge_head,
 +              const unsigned char *fork_point)
 +{
 +      int ret;
 +      unsigned char oct_merge_base[GIT_SHA1_RAWSZ];
 +      struct argv_array args = ARGV_ARRAY_INIT;
 +
 +      if (!get_octopus_merge_base(oct_merge_base, curr_head, merge_head, fork_point))
 +              if (!is_null_sha1(fork_point) && !hashcmp(oct_merge_base, fork_point))
 +                      fork_point = NULL;
 +
 +      argv_array_push(&args, "rebase");
 +
 +      /* Shared options */
 +      argv_push_verbosity(&args);
 +
 +      /* Options passed to git-rebase */
 +      if (opt_rebase == REBASE_PRESERVE)
 +              argv_array_push(&args, "--preserve-merges");
 +      if (opt_diffstat)
 +              argv_array_push(&args, opt_diffstat);
 +      argv_array_pushv(&args, opt_strategies.argv);
 +      argv_array_pushv(&args, opt_strategy_opts.argv);
 +      if (opt_gpg_sign)
 +              argv_array_push(&args, opt_gpg_sign);
 +
 +      argv_array_push(&args, "--onto");
 +      argv_array_push(&args, sha1_to_hex(merge_head));
 +
 +      if (fork_point && !is_null_sha1(fork_point))
 +              argv_array_push(&args, sha1_to_hex(fork_point));
 +      else
 +              argv_array_push(&args, sha1_to_hex(merge_head));
 +
 +      ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
 +      argv_array_clear(&args);
 +      return ret;
 +}
 +
 +int cmd_pull(int argc, const char **argv, const char *prefix)
 +{
 +      const char *repo, **refspecs;
 +      struct sha1_array merge_heads = SHA1_ARRAY_INIT;
 +      unsigned char orig_head[GIT_SHA1_RAWSZ], curr_head[GIT_SHA1_RAWSZ];
 +      unsigned char rebase_fork_point[GIT_SHA1_RAWSZ];
 +
 +      if (!getenv("GIT_REFLOG_ACTION"))
 +              set_reflog_message(argc, argv);
 +
 +      argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
 +
 +      parse_repo_refspecs(argc, argv, &repo, &refspecs);
 +
 +      if (!opt_ff)
 +              opt_ff = xstrdup_or_null(config_get_ff());
 +
 +      if (opt_rebase < 0)
 +              opt_rebase = config_get_rebase();
 +
 +      git_config(git_default_config, NULL);
 +
 +      if (read_cache_unmerged())
 +              die_resolve_conflict("Pull");
 +
 +      if (file_exists(git_path("MERGE_HEAD")))
 +              die_conclude_merge();
 +
 +      if (get_sha1("HEAD", orig_head))
 +              hashclr(orig_head);
 +
 +      if (opt_rebase) {
 +              int autostash = 0;
 +
 +              if (is_null_sha1(orig_head) && !is_cache_unborn())
 +                      die(_("Updating an unborn branch with changes added to the index."));
 +
 +              git_config_get_bool("rebase.autostash", &autostash);
 +              if (!autostash)
 +                      die_on_unclean_work_tree(prefix);
 +
 +              if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs))
 +                      hashclr(rebase_fork_point);
 +      }
 +
 +      if (run_fetch(repo, refspecs))
 +              return 1;
 +
 +      if (opt_dry_run)
 +              return 0;
 +
 +      if (get_sha1("HEAD", curr_head))
 +              hashclr(curr_head);
 +
 +      if (!is_null_sha1(orig_head) && !is_null_sha1(curr_head) &&
 +                      hashcmp(orig_head, curr_head)) {
 +              /*
 +               * The fetch involved updating the current branch.
 +               *
 +               * The working tree and the index file are still based on
 +               * orig_head commit, but we are merging into curr_head.
 +               * Update the working tree to match curr_head.
 +               */
 +
 +              warning(_("fetch updated the current branch head.\n"
 +                      "fast-forwarding your working tree from\n"
 +                      "commit %s."), sha1_to_hex(orig_head));
 +
 +              if (checkout_fast_forward(orig_head, curr_head, 0))
 +                      die(_("Cannot fast-forward your working tree.\n"
 +                              "After making sure that you saved anything precious from\n"
 +                              "$ git diff %s\n"
 +                              "output, run\n"
 +                              "$ git reset --hard\n"
 +                              "to recover."), sha1_to_hex(orig_head));
 +      }
 +
 +      get_merge_heads(&merge_heads);
 +
 +      if (!merge_heads.nr)
 +              die_no_merge_candidates(repo, refspecs);
 +
 +      if (is_null_sha1(orig_head)) {
 +              if (merge_heads.nr > 1)
 +                      die(_("Cannot merge multiple branches into empty head."));
 +              return pull_into_void(*merge_heads.sha1, curr_head);
 +      } else if (opt_rebase) {
 +              if (merge_heads.nr > 1)
 +                      die(_("Cannot rebase onto multiple branches."));
 +              return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point);
 +      } else
 +              return run_merge();
 +}
diff --cc config.c
+++ b/config.c
@@@ -2053,22 -2052,13 +2053,22 @@@ int git_config_set_multivar_in_file(con
  
                fstat(in_fd, &st);
                contents_sz = xsize_t(st.st_size);
 -              contents = xmmap(NULL, contents_sz, PROT_READ,
 -                      MAP_PRIVATE, in_fd, 0);
 +              contents = xmmap_gently(NULL, contents_sz, PROT_READ,
 +                                      MAP_PRIVATE, in_fd, 0);
 +              if (contents == MAP_FAILED) {
 +                      if (errno == ENODEV && S_ISDIR(st.st_mode))
 +                              errno = EISDIR;
 +                      error("unable to mmap '%s': %s",
 +                            config_filename, strerror(errno));
 +                      ret = CONFIG_INVALID_FILE;
 +                      contents = NULL;
 +                      goto out_free;
 +              }
                close(in_fd);
  
-               if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
+               if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
                        error("chmod on %s failed: %s",
-                               lock->filename.buf, strerror(errno));
+                             get_lock_file_path(lock), strerror(errno));
                        ret = CONFIG_NO_WRITE;
                        goto out_free;
                }
diff --cc diff.c
Simple merge
diff --cc lockfile.c
@@@ -101,62 -72,27 +72,19 @@@ static void resolve_symlink(struct strb
  /* Make sure errno contains a meaningful value on error */
  static int lock_file(struct lock_file *lk, const char *path, int flags)
  {
-       size_t pathlen = strlen(path);
-       if (!lock_file_list) {
-               /* One-time initialization */
-               sigchain_push_common(remove_lock_files_on_signal);
-               atexit(remove_lock_files_on_exit);
-       }
-       if (lk->active)
-               die("BUG: cannot lock_file(\"%s\") using active struct lock_file",
-                   path);
-       if (!lk->on_list) {
-               /* Initialize *lk and add it to lock_file_list: */
-               lk->fd = -1;
-               lk->fp = NULL;
-               lk->active = 0;
-               lk->owner = 0;
-               strbuf_init(&lk->filename, pathlen + LOCK_SUFFIX_LEN);
-               lk->next = lock_file_list;
-               lock_file_list = lk;
-               lk->on_list = 1;
-       } else if (lk->filename.len) {
-               /* This shouldn't happen, but better safe than sorry. */
-               die("BUG: lock_file(\"%s\") called with improperly-reset lock_file object",
-                   path);
-       }
+       int fd;
+       struct strbuf filename = STRBUF_INIT;
  
-       if (flags & LOCK_NO_DEREF) {
-               strbuf_add_absolute_path(&lk->filename, path);
-       } else {
-               struct strbuf resolved_path = STRBUF_INIT;
-               strbuf_add(&resolved_path, path, pathlen);
-               resolve_symlink(&resolved_path);
-               strbuf_add_absolute_path(&lk->filename, resolved_path.buf);
-               strbuf_release(&resolved_path);
-       }
+       strbuf_addstr(&filename, path);
+       if (!(flags & LOCK_NO_DEREF))
+               resolve_symlink(&filename);
  
-       strbuf_addstr(&lk->filename, LOCK_SUFFIX);
-       lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666);
-       if (lk->fd < 0) {
-               strbuf_reset(&lk->filename);
-               return -1;
-       }
-       lk->owner = getpid();
-       lk->active = 1;
-       if (adjust_shared_perm(lk->filename.buf)) {
-               int save_errno = errno;
-               error("cannot fix permission bits on %s", lk->filename.buf);
-               rollback_lock_file(lk);
-               errno = save_errno;
-               return -1;
-       }
-       return lk->fd;
+       strbuf_addstr(&filename, LOCK_SUFFIX);
+       fd = create_tempfile(&lk->tempfile, filename.buf);
+       strbuf_release(&filename);
+       return fd;
  }
  
 -static int sleep_microseconds(long us)
 -{
 -      struct timeval tv;
 -      tv.tv_sec = 0;
 -      tv.tv_usec = us;
 -      return select(0, NULL, NULL, NULL, &tv);
 -}
 -
  /*
   * Constants defining the gaps between attempts to lock a file. The
   * first backoff period is approximately INITIAL_BACKOFF_MS
diff --cc read-cache.c
Simple merge
diff --cc refs.c
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -3411,18 -3172,21 +3412,19 @@@ static int write_ref_to_lockfile(struc
                return -1;
        }
        if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
 -              error("Trying to write non-commit object %s to branch %s",
 -                      sha1_to_hex(sha1), lock->ref_name);
 +              strbuf_addf(err,
 +                          "Trying to write non-commit object %s to branch %s",
 +                          sha1_to_hex(sha1), lock->ref_name);
                unlock_ref(lock);
 -              errno = EINVAL;
                return -1;
        }
-       if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 ||
-           write_in_full(lock->lk->fd, &term, 1) != 1 ||
+       fd = get_lock_file_fd(lock->lk);
+       if (write_in_full(fd, sha1_to_hex(sha1), 40) != 40 ||
+           write_in_full(fd, &term, 1) != 1 ||
            close_ref(lock) < 0) {
 -              int save_errno = errno;
 -              error("Couldn't write %s", get_lock_file_path(lock->lk));
 +              strbuf_addf(err,
-                           "Couldn't write %s", lock->lk->filename.buf);
++                          "Couldn't write %s", get_lock_file_path(lock->lk));
                unlock_ref(lock);
 -              errno = save_errno;
                return -1;
        }
        return 0;
diff --cc shallow.c
+++ b/shallow.c
@@@ -314,10 -293,10 +293,10 @@@ void prune_shallow(int show_only
        if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) {
                if (write_in_full(fd, sb.buf, sb.len) != sb.len)
                        die_errno("failed to write to %s",
-                                 shallow_lock.filename.buf);
+                                 get_lock_file_path(&shallow_lock));
                commit_lock_file(&shallow_lock);
        } else {
 -              unlink(git_path("shallow"));
 +              unlink(git_path_shallow());
                rollback_lock_file(&shallow_lock);
        }
        strbuf_release(&sb);