[PATCH v7 13/45] builtin-am: implement --skip

2015-08-04 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
supported resuming from a failed patch application by skipping the
current patch. Re-implement this feature by introducing am_skip().

Helped-by: Stefan Beller 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 123 ++-
 1 file changed, 121 insertions(+), 2 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index ec579a6..765844b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -16,6 +16,8 @@
 #include "commit.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "unpack-trees.h"
+#include "branch.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -868,6 +870,116 @@ static void am_resolve(struct am_state *state)
 }
 
 /**
+ * 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;
+}
+
+/**
+ * Resume the current am session by skipping the current patch.
+ */
+static void am_skip(struct am_state *state)
+{
+   unsigned char head[GIT_SHA1_RAWSZ];
+
+   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_run(state, 0);
+}
+
+/**
  * parse_options() callback that validates and sets opt->value to the
  * PATCH_FORMAT_* enum value corresponding to `arg`.
  */
@@ -885,7 +997,8 @@ static int parse_opt_patchformat(const struct option *opt, 
const char *arg, int
 enum resume_mode {
RESUME_FALSE = 0,
RESUME_APPLY,
-   RESUME_RESOLVED
+   RESUME_RESOLVED,
+   RESUME_SKIP
 };
 
 int cmd_am(int argc, const char **argv, const char *prefix)
@@ -896,7 +1009,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
const char * const usage[] = {
N_("git am [options] [(|)...]"),
-   N_("git am [options] --continue"),
+   N_("git am [options] (--continue | -

[PATCH v7 14/45] builtin-am: implement --abort

2015-08-04 Thread Paul Tan
Since 3e5057a (git am --abort, 2008-07-16), git-am supported the --abort
option that will rewind HEAD back to the original commit. Re-implement
this through am_abort().

Since 7b3b7e3 (am --abort: keep unrelated commits since the last failure
and warn, 2010-12-21), to prevent commits made since the last failure
from being lost, git-am will not rewind HEAD back to the original
commit if HEAD moved since the last failure. Re-implement this through
safe_to_abort().

Helped-by: Stefan Beller 
Signed-off-by: Paul Tan 
---

Notes:
v7

* Add a free(curr_branch) so we don't leak memory.

 builtin/am.c | 103 +--
 1 file changed, 100 insertions(+), 3 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 765844b..6c24d07 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -490,6 +490,8 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
 static void am_setup(struct am_state *state, enum patch_format patch_format,
const char **paths)
 {
+   unsigned char curr_head[GIT_SHA1_RAWSZ];
+
if (!patch_format)
patch_format = detect_patch_format(paths);
 
@@ -506,6 +508,14 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
die(_("Failed to split patches."));
}
 
+   if (!get_sha1("HEAD", curr_head)) {
+   write_file(am_path(state, "abort-safety"), 1, "%s", 
sha1_to_hex(curr_head));
+   update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, 
UPDATE_REFS_DIE_ON_ERR);
+   } else {
+   write_file(am_path(state, "abort-safety"), 1, "%s", "");
+   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.
@@ -522,6 +532,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
  */
 static void am_next(struct am_state *state)
 {
+   unsigned char head[GIT_SHA1_RAWSZ];
+
free(state->author_name);
state->author_name = NULL;
 
@@ -538,6 +550,11 @@ static void am_next(struct am_state *state)
unlink(am_path(state, "author-script"));
unlink(am_path(state, "final-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);
 }
@@ -788,10 +805,14 @@ 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))
+   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);
 
@@ -980,6 +1001,75 @@ static void am_skip(struct am_state *state)
 }
 
 /**
+ * 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)) 

[PATCH v7 02/45] wrapper: implement xfopen()

2015-08-04 Thread Paul Tan
A common usage pattern of fopen() is to check if it succeeded, and die()
if it failed:

FILE *fp = fopen(path, "w");
if (!fp)
die_errno(_("could not open '%s' for writing"), path);

Implement a wrapper function xfopen() for the above, so that we can save
a few lines of code and make the die() messages consistent.

Helped-by: Jeff King 
Helped-by: Johannes Schindelin 
Helped-by: Junio C Hamano 
Signed-off-by: Paul Tan 
---
 git-compat-util.h |  1 +
 wrapper.c | 21 +
 2 files changed, 22 insertions(+)

diff --git a/git-compat-util.h b/git-compat-util.h
index e168dfd..392da79 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -722,6 +722,7 @@ extern ssize_t xread(int fd, void *buf, size_t len);
 extern ssize_t xwrite(int fd, const void *buf, size_t len);
 extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
 extern int xdup(int fd);
+extern FILE *xfopen(const char *path, const char *mode);
 extern FILE *xfdopen(int fd, const char *mode);
 extern int xmkstemp(char *template);
 extern int xmkstemp_mode(char *template, int mode);
diff --git a/wrapper.c b/wrapper.c
index 0a4502d..e451463 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -346,6 +346,27 @@ int xdup(int fd)
return ret;
 }
 
+/**
+ * xfopen() is the same as fopen(), but it die()s if the fopen() fails.
+ */
+FILE *xfopen(const char *path, const char *mode)
+{
+   for (;;) {
+   FILE *fp = fopen(path, mode);
+   if (fp)
+   return fp;
+   if (errno == EINTR)
+   continue;
+
+   if (*mode && mode[1] == '+')
+   die_errno(_("could not open '%s' for reading and 
writing"), path);
+   else if (*mode == 'w' || *mode == 'a')
+   die_errno(_("could not open '%s' for writing"), path);
+   else
+   die_errno(_("could not open '%s' for reading"), path);
+   }
+}
+
 FILE *xfdopen(int fd, const char *mode)
 {
FILE *stream = fdopen(fd, mode);
-- 
2.5.0.280.gd88bd6e

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 08/45] builtin-am: apply patch with git-apply

2015-08-04 Thread Paul Tan
Implement applying the patch to the index using git-apply.

If a file is unchanged but stat-dirty, git-apply may erroneously fail to
apply patches, thinking that they conflict with a dirty working tree.

As such, since 2a6f08a (am: refresh the index at start and --resolved,
2011-08-15), git-am will refresh the index before applying patches.
Re-implement this behavior.

Helped-by: Junio C Hamano 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 72 +++-
 1 file changed, 71 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 63f0fa4..1f198e4 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -10,6 +10,7 @@
 #include "dir.h"
 #include "run-command.h"
 #include "quote.h"
+#include "lockfile.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -42,6 +43,14 @@ static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
return 0;
 }
 
+/**
+ * Returns the length of the first line of msg.
+ */
+static int linelen(const char *msg)
+{
+   return strchrnul(msg, '\n') - msg;
+}
+
 enum patch_format {
PATCH_FORMAT_UNKNOWN = 0,
PATCH_FORMAT_MBOX
@@ -540,6 +549,19 @@ static const char *msgnum(const struct am_state *state)
 }
 
 /**
+ * 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"));
+}
+
+/**
  * 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
@@ -629,10 +651,35 @@ finish:
 }
 
 /**
+ * Applies current patch with git-apply. Returns 0 on success, -1 otherwise.
+ */
+static int run_apply(const struct am_state *state)
+{
+   struct child_process cp = CHILD_PROCESS_INIT;
+
+   cp.git_cmd = 1;
+
+   argv_array_push(&cp.args, "apply");
+   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();
+
+   return 0;
+}
+
+/**
  * Applies all queued mail.
  */
 static void am_run(struct am_state *state)
 {
+   refresh_and_write_cache();
+
while (state->cur <= state->last) {
const char *mail = am_path(state, msgnum(state));
 
@@ -645,7 +692,27 @@ static void am_run(struct am_state *state)
write_author_script(state);
write_commit_msg(state);
 
-   /* NEEDSWORK: Patch application not implemented yet */
+   printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg);
+
+   if (run_apply(state) < 0) {
+   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"));
+
+   exit(128);
+   }
+
+   /*
+* NEEDSWORK: After the patch has been applied to the index
+* with git-apply, we need to make commit as well.
+*/
 
 next:
am_next(state);
@@ -707,6 +774,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
argc = parse_options(argc, argv, prefix, options, usage, 0);
 
+   if (read_index_preload(&the_index, NULL) < 0)
+   die(_("failed to read the index"));
+
if (am_in_progress(&state))
am_load(&state);
else {
-- 
2.5.0.280.gd88bd6e

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 06/45] builtin-am: auto-detect mbox patches

2015-08-04 Thread Paul Tan
Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh is able to autodetect mbox, stgit and
mercurial patches through heuristics.

Re-implement support for autodetecting mbox/maildir files in
builtin/am.c.

RFC 2822 requires that lines are terminated by "\r\n". To support this,
implement strbuf_getline_crlf(), which will remove both '\n' and "\r\n"
from the end of the line.

Helped-by: Junio C Hamano 
Helped-by: Eric Sunshine 
Helped-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 109 +++
 1 file changed, 109 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 5f3c131..c12566a 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -10,6 +10,21 @@
 #include "dir.h"
 #include "run-command.h"
 
+/**
+ * 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;
+}
+
 enum patch_format {
PATCH_FORMAT_UNKNOWN = 0,
PATCH_FORMAT_MBOX
@@ -128,6 +143,92 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * 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(®ex, 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(®ex, sb.buf, 0, NULL, 0)) {
+   ret = 0;
+   goto done;
+   }
+   }
+
+done:
+   regfree(®ex);
+   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;
+   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 (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.
  */
@@ -185,6 +286,14 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
 static void am_setup(struct am_state *state, enum patch_format patch_format,
const char **paths)
 {
+   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);
 
-- 
2.5.0.280.gd88bd6e

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 05/45] builtin-am: split out mbox/maildir patches with git-mailsplit

2015-08-04 Thread Paul Tan
git-am.sh supports mbox, stgit and mercurial patches. Re-implement
support for splitting out mbox/maildirs using git-mailsplit, while also
implementing the framework required to support other patch formats in
the future.

Re-implement support for the --patch-format option (since a5a6755
(git-am foreign patch support: introduce patch_format, 2009-05-27)) to
allow the user to choose between the different patch formats.

Helped-by: Junio C Hamano 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 107 +--
 1 file changed, 104 insertions(+), 3 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index ac172c4..5f3c131 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -8,6 +8,12 @@
 #include "exec_cmd.h"
 #include "parse-options.h"
 #include "dir.h"
+#include "run-command.h"
+
+enum patch_format {
+   PATCH_FORMAT_UNKNOWN = 0,
+   PATCH_FORMAT_MBOX
+};
 
 struct am_state {
/* state directory path */
@@ -16,6 +22,9 @@ struct am_state {
/* current and last patch numbers, 1-indexed */
int cur;
int last;
+
+   /* number of digits in patch filename */
+   int prec;
 };
 
 /**
@@ -28,6 +37,8 @@ static void am_state_init(struct am_state *state, const char 
*dir)
 
assert(dir);
state->dir = xstrdup(dir);
+
+   state->prec = 4;
 }
 
 /**
@@ -117,13 +128,71 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * 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)
+{
+   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");
+   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;
+}
+
+/**
+ * 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.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail(struct am_state *state, enum patch_format patch_format,
+   const char **paths)
+{
+   switch (patch_format) {
+   case PATCH_FORMAT_MBOX:
+   return split_mail_mbox(state, paths);
+   default:
+   die("BUG: invalid patch_format");
+   }
+   return -1;
+}
+
+/**
  * Setup a new am session for applying patches
  */
-static void am_setup(struct am_state *state)
+static void am_setup(struct am_state *state, enum patch_format patch_format,
+   const char **paths)
 {
if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
die_errno(_("failed to create directory '%s'"), state->dir);
 
+   if (split_mail(state, patch_format, paths) < 0) {
+   am_destroy(state);
+   die(_("Failed to split patches."));
+   }
+
/*
 * NOTE: Since the "next" and "last" files determine if an am_state
 * session is in progress, they should be written last.
@@ -159,9 +228,25 @@ static void am_run(struct am_state *state)
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
+   return error(_("Invalid value for --patch-format: %s"), arg);
+   return 0;
+}
+
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
struct am_state state;
+   int patch_format = PATCH_FORMAT_UNKNOWN;
 
const char * const usage[] = {
N_("git am [options] [(|)...]"),
@@ -169,6 +254,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
};
 
struct option options[] = {

[PATCH v7 10/45] builtin-am: refuse to apply patches if index is dirty

2015-08-04 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
will refuse to apply patches if the index is dirty. Re-implement this
behavior in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 45 +
 1 file changed, 45 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index a2811b6..537ad62 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -14,6 +14,8 @@
 #include "cache-tree.h"
 #include "refs.h"
 #include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -565,6 +567,43 @@ static void refresh_and_write_cache(void)
 }
 
 /**
+ * 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;
+   }
+}
+
+/**
  * 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
@@ -726,9 +765,15 @@ static void do_commit(const struct am_state *state)
 static void am_run(struct am_state *state)
 {
const char *argv_gc_auto[] = {"gc", "--auto", NULL};
+   struct strbuf sb = STRBUF_INIT;
 
refresh_and_write_cache();
 
+   if (index_has_changes(&sb))
+   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));
 
-- 
2.5.0.280.gd88bd6e

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 01/45] wrapper: implement xopen()

2015-08-04 Thread Paul Tan
A common usage pattern of open() is to check if it was successful, and
die() if it was not:

int fd = open(path, O_WRONLY | O_CREAT, 0777);
if (fd < 0)
die_errno(_("Could not open '%s' for writing."), path);

Implement a wrapper function xopen() that does the above so that we can
save a few lines of code, and make the die() messages consistent.

Helped-by: Torsten Bögershausen 
Helped-by: Jeff King 
Helped-by: Johannes Schindelin 
Helped-by: Junio C Hamano 
Signed-off-by: Paul Tan 
---
 git-compat-util.h |  1 +
 wrapper.c | 35 +++
 2 files changed, 36 insertions(+)

diff --git a/git-compat-util.h b/git-compat-util.h
index c6d391f..e168dfd 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -717,6 +717,7 @@ extern void *xrealloc(void *ptr, size_t size);
 extern void *xcalloc(size_t nmemb, size_t size);
 extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, 
off_t offset);
 extern void *xmmap_gently(void *start, size_t length, int prot, int flags, int 
fd, off_t offset);
+extern int xopen(const char *path, int flags, ...);
 extern ssize_t xread(int fd, void *buf, size_t len);
 extern ssize_t xwrite(int fd, const void *buf, size_t len);
 extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
diff --git a/wrapper.c b/wrapper.c
index ff49807..0a4502d 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -189,6 +189,41 @@ void *xcalloc(size_t nmemb, size_t size)
 # endif
 #endif
 
+/**
+ * xopen() is the same as open(), but it die()s if the open() fails.
+ */
+int xopen(const char *path, int oflag, ...)
+{
+   mode_t mode = 0;
+   va_list ap;
+
+   /*
+* va_arg() will have undefined behavior if the specified type is not
+* compatible with the argument type. Since integers are promoted to
+* ints, we fetch the next argument as an int, and then cast it to a
+* mode_t to avoid undefined behavior.
+*/
+   va_start(ap, oflag);
+   if (oflag & O_CREAT)
+   mode = va_arg(ap, int);
+   va_end(ap);
+
+   for (;;) {
+   int fd = open(path, oflag, mode);
+   if (fd >= 0)
+   return fd;
+   if (errno == EINTR)
+   continue;
+
+   if ((oflag & O_RDWR) == O_RDWR)
+   die_errno(_("could not open '%s' for reading and 
writing"), path);
+   else if ((oflag & O_WRONLY) == O_WRONLY)
+   die_errno(_("could not open '%s' for writing"), path);
+   else
+   die_errno(_("could not open '%s' for reading"), path);
+   }
+}
+
 /*
  * xread() is the same a read(), but it automatically restarts read()
  * operations with a recoverable error (EAGAIN and EINTR). xread()
-- 
2.5.0.280.gd88bd6e

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: What's cooking in git.git (Aug 2015, #01; Mon, 3)

2015-08-03 Thread Paul Tan
On Tue, Aug 4, 2015 at 6:18 AM, Junio C Hamano  wrote:
>  - I think pt/am-builtin is more or less ready, but the fix to the
>issue that options given to 'git am' when restarting were
>rejected must be queued on that topic before we can start
>thinking of merging it to 'master' for the next release.

Yeah, sorry for the lack of updates, but I'm working on it[1]. I've
added a patch that adds stdin pty support to test_terminal, and made
--signoff work when resuming.

However, it turns out that --keep, --message-id and --scissors do not
work as well, as they apply to the mail-parsing stage (git-mailinfo),
which comes before the patch application stage. It's tricky, as their
functionality is locked inside builtin/mailinfo.c. :-/

I'm thinking about leaving them broken for now to push this patch
series forward, until the future where the git-mailinfo functionality
gets moved into libgit.a or something so we can access the individual
functions directly and work something out.

Will send a re-roll once I look over the patches with a fresh set of eyes.

[1] 
https://github.com/pyokagan/git/compare/pt/builtin-am...pt/am-resume-override-opts

Thanks,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH] am: let command-line options override saved options

2015-07-31 Thread Paul Tan
On Sat, Aug 1, 2015 at 12:04 AM, Junio C Hamano  wrote:
> Paul Tan  writes:
>
>> I think I will introduce a format_patch() function that takes a single
>> commit-ish so that we can use tag names to name the patches:
>>
>> # Given a single commit $commit, formats the following patches with
>> # git-format-patch:
>> #
>> # 1. $commit.eml: an email patch with a Message-Id header.
>> # 2. $commit.scissors: like $commit.eml but contains a scissors line at the
>> #start of the commit message body.
>> format_patch () {
>> {
>> echo "Message-Id: <$1...@example.com>" &&
>> git format-patch --stdout -1 "$1" | sed -e '1d'
>> } >"$1".eml &&
>
> I only said I can "understand" what is going on, though.
>
> It feels a bit unnatural for a test to feed a message that lack the
> "From " header line.  Perhaps
>
> git format-patch --add-header="Message-Id: ..." --stdout -1
>
> or something?

Ah, okay. I wasn't aware of the --add-header option, but this is
definitely better.

>> These functions are called before we attempt to apply the patch, so we
>> should probably call append_signoff before then. However, this still
>> means that --no-signoff will have no effect should the patch
>> application fail and we resume, as the signoff would still have
>> already been appended...
>
> Ah, I see.  Let's not worry about this; we cannot change the
> expectation existing hook scripts depends on.

Okay, although this means that with the below change, --[no-]signoff
will be the oddball option that does not work when resuming.

>> 2. Re-reading Peff's message, I see that he expects the command-line
>> options to affect just the current patch, which makes sense. This
>> patch would need to be extended to call am_load() after we finish
>> processing the current patch when resuming.
>
> Yeah, so the idea is:
>
>  - upon the very first invocation, we parse the command line options
>and write the states out;
>
>  - subsequent invocation, we read from the states and then override
>with the command line options, but we do not write the states out
>to update, so that subsequent invocations will keep reading from
>the very first one.

... and we also load back the saved options after processing the patch
that we resume from, so the command-line options only affect the
conflicting patch, which fits in with Peff's idea on "wiggling that
_one_ patch".

>>>> +test_expect_success '--3way, --no-3way' '
>>>> + rm -fr .git/rebase-apply &&
>>>> + git reset --hard &&
>>>> + git checkout first &&
>>>> + test_must_fail git am --3way side-first.patch side-second.patch &&
>>>> + test -n "$(git ls-files -u)" &&
>>>> + echo will-conflict >file &&
>>>> + git add file &&
>>>> + test_must_fail git am --no-3way --continue &&
>>>> + test -z "$(git ls-files -u)"
>>>> +'
>>>> +
>>
>> ... Although if I implement the above change, I can't implement the
>> test for --3way, as I think the only way to check if --3way/--no-3way
>> successfully overrides the saved options for the current patch only is
>> to run "git am --3way", but that does not work in the test runner as
>> it expects stdin to be a TTY :-/ So I may have to remove this test.
>> This shouldn't be a problem though, as all the tests in this test
>> suite all test the same mechanism.
>
> Sorry, you lost me.  Where does the TTY come into the picture only
> for --3way (but not for other things like --quiet)?

Ah, sorry, I should have provided more context. This is due to the
following block of code:

/*
 * 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);

And it will activate when git-am is run without
--continue/--abort/--skip (e.g. "git am --3way") because the test
framework sets stdin to /dev/null.

Thanks,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH] pull.sh: quote $upload_pack when passing it to git-fetch

2015-07-31 Thread Paul Tan
On Fri, Jul 31, 2015 at 4:40 AM, Matthieu Moy  wrote:
> The previous code broke for example
>
>   git pull --upload-pack 'echo --foo'
>
> Reported-by: Joey Hess 
> Fix-suggested-by: Junio C Hamano 
> Signed-off-by: Matthieu Moy 

Thanks for cleaning up my mess! ><

Regards,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH] am: let command-line options override saved options

2015-07-31 Thread Paul Tan
On Wed, Jul 29, 2015 at 1:09 AM, Junio C Hamano  wrote:
> Paul Tan  writes:
>
>> diff --git a/t/t4153-am-resume-override-opts.sh 
>> b/t/t4153-am-resume-override-opts.sh
>> new file mode 100755
>> index 000..c49457c
>> --- /dev/null
>> +++ b/t/t4153-am-resume-override-opts.sh
>> @@ -0,0 +1,144 @@
>> +#!/bin/sh
>> +
>> +test_description='git-am command-line options override saved options'
>> +
>> +. ./test-lib.sh
>> +
>> +test_expect_success 'setup' '
>> + test_commit initial file &&
>> + test_commit first file &&
>> +
>> + git checkout -b side initial &&
>> + test_commit side-first file &&
>> + test_commit side-second file &&
>> +
>> + {
>> + echo "Message-Id: " &&
>> + git format-patch --stdout -1 side-first | sed -e "1d"
>> + } >side-first.patch &&
>> + {
>> + sed -ne "1,/^\$/p" side-first.patch &&
>
> sed -e "/^\$/q" would work just as well here

OK.

>> + echo "-- >8 --" &&
>> + sed -e "1,/^\$/d" side-first.patch
>> + } >side-first.scissors &&
>> + {
>> + echo "Message-Id: " &&
>> + git format-patch --stdout -1 side-second | sed -e "1d"
>> + } >side-second.patch &&
>> + {
>> + sed -ne "1,/^\$/p" side-second.patch &&
>> + echo "-- >8 --" &&
>> + sed -e "1,/^\$/d" side-second.patch
>> + } >side-second.scissors
>> +'
>
> A helper function that takes the branch name may be a good idea,
> not just to consolidate the implementation but as a place to
> document how these pairs of files are constructed and why.

I think I will introduce a format_patch() function that takes a single
commit-ish so that we can use tag names to name the patches:

# Given a single commit $commit, formats the following patches with
# git-format-patch:
#
# 1. $commit.eml: an email patch with a Message-Id header.
# 2. $commit.scissors: like $commit.eml but contains a scissors line at the
#start of the commit message body.
format_patch () {
{
echo "Message-Id: <$1...@example.com>" &&
git format-patch --stdout -1 "$1" | sed -e '1d'
} >"$1".eml &&
{
sed -e '/^$/q' "$1".eml &&
echo '-- >8 --' &&
sed -e '1,/^$/d' "$1".eml
} >"$1".scissors
}

>> +'
>> +
>> +test_expect_success '--signoff, --no-signoff' '
>> + rm -fr .git/rebase-apply &&
>> + git reset --hard &&
>> + git checkout first &&
>> + test_must_fail git am --signoff side-first.patch side-second.patch &&
>> + echo side-first >file &&
>> + git add file &&
>> + git am --no-signoff --continue &&
>> +
>> + # applied side-first will be signed off
>> + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" 
>> >expected &&
>> + git cat-file commit HEAD^ | grep "Signed-off-by:" >actual &&
>> + test_cmp expected actual &&
>> +
>> + # applied side-second will not be signed off
>> + test $(git cat-file commit HEAD | grep -c "Signed-off-by:") -eq 0
>> +'
>
> Hmm, the command was run with --signoff at the start, first gets
> applied with "am --no-signoff --resolved" so I would expect it does
> not get signed off, but the second one will apply cleanly on top, so
> shouldn't it get signed off?  Or perhaps somehow I misread Peff's
> idea to make these override one-shot in $gmane/274635?

Ah, I was just following the structure of the code, but stepping back
to think about it, I think there are 2 bugs:

1. The signoff is appended during the email-parsing stage. As such,
when we are resuming, --no-signoff will have no effect, because the
signoff has already been appended at that stage.

A solution for this is tricky though, as there are functions of git-am
that probably depend on the present behavior of the appended signoff
being present in the commit message:

* The applypatch-msg hook

* The --interactive prompt, where the user can edit the commit message
(to remove or edit the signoff maybe?)

These functions are called before we attem

[PATCH] am: let command-line options override saved options

2015-07-28 Thread Paul Tan
When resuming, git-am ignores command-line options. For instance, when a
patch fails to apply with "git am patch", subsequently running "git am
--3way patch" would not cause git-am to fall back on attempting a
threeway merge. This occurs because by default the --3way option is
saved as "false", and the saved am options are loaded after the
command-line options are parsed, thus overwriting the command-line
options when resuming.

Fix this by moving the am_load() function call before parse_options(),
so that command-line options will override the saved am options.

Reported-by: Junio C Hamano 
Helped-by: Jeff King 
Signed-off-by: Paul Tan 
---
 builtin/am.c   |   9 ++-
 t/t4153-am-resume-override-opts.sh | 144 +
 2 files changed, 150 insertions(+), 3 deletions(-)
 create mode 100755 t/t4153-am-resume-override-opts.sh

diff --git a/builtin/am.c b/builtin/am.c
index 1116304..8a0b0e4 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2131,6 +2131,7 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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] [(|)...]"),
@@ -2226,6 +2227,10 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
 
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)
@@ -2238,7 +2243,7 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
if (read_index_preload(&the_index, NULL) < 0)
die(_("failed to read the index"));
 
-   if (am_in_progress(&state)) {
+   if (in_progress) {
/*
 * Catch user error to feed us patches when there is a session
 * in progress:
@@ -2256,8 +2261,6 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
 
if (resume == RESUME_FALSE)
resume = RESUME_APPLY;
-
-   am_load(&state);
} else {
struct argv_array paths = ARGV_ARRAY_INIT;
int i;
diff --git a/t/t4153-am-resume-override-opts.sh 
b/t/t4153-am-resume-override-opts.sh
new file mode 100755
index 000..c49457c
--- /dev/null
+++ b/t/t4153-am-resume-override-opts.sh
@@ -0,0 +1,144 @@
+#!/bin/sh
+
+test_description='git-am command-line options override saved options'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+   test_commit initial file &&
+   test_commit first file &&
+
+   git checkout -b side initial &&
+   test_commit side-first file &&
+   test_commit side-second file &&
+
+   {
+   echo "Message-Id: " &&
+   git format-patch --stdout -1 side-first | sed -e "1d"
+   } >side-first.patch &&
+   {
+   sed -ne "1,/^\$/p" side-first.patch &&
+   echo "-- >8 --" &&
+   sed -e "1,/^\$/d" side-first.patch
+   } >side-first.scissors &&
+
+   {
+   echo "Message-Id: " &&
+   git format-patch --stdout -1 side-second | sed -e "1d"
+   } >side-second.patch &&
+   {
+   sed -ne "1,/^\$/p" side-second.patch &&
+   echo "-- >8 --" &&
+   sed -e "1,/^\$/d" side-second.patch
+   } >side-second.scissors
+'
+
+test_expect_success '--3way, --no-3way' '
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout first &&
+   test_must_fail git am --3way side-first.patch side-second.patch &&
+   test -n "$(git ls-files -u)" &&
+   echo will-conflict >file &&
+   git add file &&
+   test_must_fail git am --no-3way --continue &&
+   test -z "$(git ls-files -u)"
+'
+
+test_expect_success '--no-quiet, --quiet' '
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout first &&
+   test_must_fail git am --no-quiet side-first.patch side-second.patch &&
+   test_must_be_empty out &&
+   echo side-first >file &&
+   git add file &&
+   git am --quiet --continue >out &&
+   test_must_be_empty out
+'
+
+test_expect_success '--signoff, --no-signoff' '
+   rm -fr .git/reba

Re: "git am" and then "git am -3" regression?

2015-07-25 Thread Paul Tan
On Sat, Jul 25, 2015 at 2:09 AM, Jeff King  wrote:
> Yeah, I think this hunk is to blame (though I just read the code and did not
> test):
>
> @@ -658,6 +665,8 @@ fi
>  if test "$(cat "$dotest/threeway")" = t
>  then
> threeway=t
> +else
> +   threeway=f
>  fi
>
> It comes after the command-line option parsing, so it overrides our option (I
> think that running "git am -3" followed by "git am --no-3way" would have the
> same problem). It cannot just check whether $threeway is unset, though, as it
> may have come from the config.

Thanks for the detailed analysis, I completely agree. Note that the
code that handles the --message-id option somewhat handles the case
where $messageid is unset:

case "$(cat "$dotest/messageid")" in
t)
messageid=-m ;;
f)
messageid= ;;
esac

However, it still does not handle "git am --no-message-id" followed by
"git am --message-id", or "git -c am.messageid=true am" followed by
"git am --no-message-id". I think the same thing occurs for
--scissors/--no-scissors, as well as the git-apply options as well.

The real problem is that the state directory loading code comes after
the config loading and option parsing code, and thus overrides any
variables set.

> We'd need a separate variable, the way the code
> is ordered now.

If we are just fixing --3way, adding one extra variable won't be that
bad. However, I think that if we are using this approach to fix all of
the options, then it would introduce too much code complexity.

> Ideally the code would just be ordered as:
>
>   - load config from git-config
>
>   - override that with defaults inherited from a previous run
>
>   - override that with command-line parsing

So I'm more in favor of this solution. It's feels much more natural to
me, rather than attempting to workaround the existing code structure.

> but I don't know if there are other ordering gotchas that would break.

For the C code, there won't be any problem, but yeah, fixing it in
git-am.sh might need a bit more effort.

> It does look like that is how Paul's builtin/am.c does it, which makes
> me think it might not be broken. It's also possibly I've horribly
> misdiagnosed the bug. ;)

Nah, it follows the same structure as git-am.sh and so will exhibit
the same behavior. It currently does something like this:

1. am_state_init() (config settings are loaded)
2. parse_options()
3. if (am_in_progress()) am_load(); else am_setup();

So it would be quite trivial to change the control flow such that it is:

1. am_state_init()
2. if (am_in_progress()) am_load()
3. parse_options();
4 if (!am_in_progress()) am_setup()

The next question is, should any options set on the command-line
affect subsequent invocations? If yes, then the control flow will be
like:

1. am_state_init();
2. if (am_in_progress()) am_load();
3. parse_options();
4. if (am_in_progress()) am_save_opts(); else am_setup();

where am_save_opts() will write the updated variables back to the
state directory. What do you think?

Since the builtin-am series is in 'next' already, and the fix in C is
straightforward, to save time and effort I'm wondering if we could
just do "am.threeWay patch -> builtin-am series -> bugfix patch in C".
My university term is starting soon so I may not have so much time,
but I'll see what I can do :-/

Junio, how do you want to proceed?

Thanks,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 26/45] builtin-am: implement --[no-]message-id, am.messageid

2015-07-19 Thread Paul Tan
Since a078f73 (git-am: add --message-id/--no-message-id, 2014-11-25),
git-am.sh supported the --[no-]message-id options, and the
"am.messageid" setting which specifies the default option.

--[no-]message-id tells git-am whether or not the -m option should be
passed to git-mailinfo.

Re-implement this option in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 13 +
 1 file changed, 13 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index af08a22..e41a104 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -98,6 +98,7 @@ struct am_state {
int append_signoff;
int utf8;
int keep; /* enum keep_type */
+   int message_id;
const char *resolvemsg;
int rebasing;
 };
@@ -118,6 +119,8 @@ static void am_state_init(struct am_state *state, const 
char *dir)
git_config_get_bool("am.threeway", &state->threeway);
 
state->utf8 = 1;
+
+   git_config_get_bool("am.messageid", &state->message_id);
 }
 
 /**
@@ -390,6 +393,9 @@ static void am_load(struct am_state *state)
else
state->keep = KEEP_FALSE;
 
+   read_state_file(&sb, state, "messageid", 1);
+   state->message_id = !strcmp(sb.buf, "t");
+
state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
strbuf_release(&sb);
@@ -598,6 +604,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
 
write_file(am_path(state, "keep"), 1, "%s", str);
 
+   write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : 
"f");
+
if (state->rebasing)
write_file(am_path(state, "rebasing"), 1, "%s", "");
else
@@ -779,6 +787,9 @@ static int parse_mail(struct am_state *state, const char 
*mail)
die("BUG: invalid value for state->keep");
}
 
+   if (state->message_id)
+   argv_array_push(&cp.args, "-m");
+
argv_array_push(&cp.args, am_path(state, "msg"));
argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1522,6 +1533,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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")),
OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 39/45] builtin-am: support and auto-detect StGit patches

2015-07-19 Thread Paul Tan
Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27),
git-am.sh supported converting StGit patches into RFC2822 mail patches
that can be parsed with git-mailinfo.

Implement this by introducing two functions in builtin/am.c:
stgit_patch_to_mail() and split_mail_conv().

stgit_patch_to_mail() is a callback function for split_mail_conv(), and
contains the logic for converting an StGit patch into an RFC2822 mail
patch.

split_mail_conv() implements the logic to go through each file in the
`paths` list, reading from stdin where specified, and calls the callback
function to write the converted patch to the corresponding output file
in the state directory. This interface should be generic enough to
support other foreign patch formats in the future.

Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh is able to auto-detect StGit patches.
Re-implement this in builtin/am.c.

Helped-by: Eric Sunshine 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 132 ++-
 1 file changed, 131 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 89613e0..101bca3 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -65,9 +65,22 @@ 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_MBOX,
+   PATCH_FORMAT_STGIT
 };
 
 enum keep_type {
@@ -612,6 +625,8 @@ 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;
 
/*
@@ -637,6 +652,23 @@ static int detect_patch_format(const char **paths)
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;
@@ -677,6 +709,100 @@ static int split_mail_mbox(struct am_state *state, const 
char **paths, int keep_
 }
 
 /**
+ * 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 

[PATCH v6 32/45] builtin-am: implement -S/--gpg-sign, commit.gpgsign

2015-07-19 Thread Paul Tan
Since 3b4e395 (am: add the --gpg-sign option, 2014-02-01), git-am.sh
supported the --gpg-sign option, and would pass it to git-commit-tree,
thus GPG-signing the commit object.

Re-implement this option in builtin/am.c.

git-commit-tree would also sign the commit by default if the
commit.gpgsign setting is true. Since we do not run commit-tree, we
re-implement this behavior by handling the commit.gpgsign setting
ourselves.

Helped-by: Stefan Beller 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 11 ++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 1080df6..3612bb3 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -110,6 +110,7 @@ struct am_state {
const char *resolvemsg;
int committer_date_is_author_date;
int ignore_date;
+   const char *sign_commit;
int rebasing;
 };
 
@@ -119,6 +120,8 @@ struct am_state {
  */
 static void am_state_init(struct am_state *state, const char *dir)
 {
+   int gpgsign;
+
memset(state, 0, sizeof(*state));
 
assert(dir);
@@ -135,6 +138,9 @@ static void am_state_init(struct am_state *state, const 
char *dir)
state->scissors = SCISSORS_UNSET;
 
argv_array_init(&state->git_apply_opts);
+
+   if (!git_config_get_bool("commit.gpgsign", &gpgsign))
+   state->sign_commit = gpgsign ? "" : NULL;
 }
 
 /**
@@ -1229,7 +1235,7 @@ static void do_commit(const struct am_state *state)
state->ignore_date ? "" : state->author_date, 1);
 
if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
-   author, NULL))
+   author, state->sign_commit))
die(_("failed to write commit object"));
 
reflog_msg = getenv("GIT_REFLOG_ACTION");
@@ -1674,6 +1680,9 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
N_("lie about committer date")),
OPT_BOOL(0, "ignore-date", &state.ignore_date,
N_("use current timestamp for author date")),
+   { 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()
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 33/45] builtin-am: invoke post-rewrite hook

2015-07-19 Thread Paul Tan
Since 96e1948 (rebase: invoke post-rewrite hook, 2010-03-12), git-am.sh
will invoke the post-rewrite hook after it successfully finishes
applying all the queued patches.

To do this, when parsing a mail to extract its patch and metadata, in
--rebasing mode git-am.sh will also store the original commit ID in the
$state_dir/original-commit file. Once it applies and commits the patch,
the original commit ID, and the new commit ID, will be appended to the
$state_dir/rewritten file.

Once all of the queued mail have been processed, git-am.sh will then
invoke the post-rewrite hook with the contents of the
$state_dir/rewritten file.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 55 +++
 1 file changed, 55 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 3612bb3..e99e1ec 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -95,6 +95,9 @@ struct am_state {
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;
 
@@ -394,6 +397,11 @@ static void am_load(struct am_state *state)
 
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");
 
@@ -449,6 +457,30 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * 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;
+}
+
+/**
  * 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.
@@ -722,6 +754,9 @@ static void am_next(struct am_state *state)
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
@@ -1040,6 +1075,8 @@ static void write_commit_patch(const struct am_state 
*state, struct commit *comm
  * 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)
@@ -1056,6 +1093,10 @@ static int parse_mail_rebase(struct am_state *state, 
const char *mail)
 
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;
 }
 
@@ -1247,6 +1288,15 @@ static void do_commit(const struct am_state *state)
 
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);
+   }
+
strbuf_release(&sb);
 }
 
@@ -1355,6 +1405,11 @@ next:
am_next(state);
}
 
+   if (!is_empty_file(am_path(state, "rewritten"))) {
+   assert(state->rebasing);
+   run_post_rewrite_hook(state);
+   }
+
/*
 * In rebasing mode, it's up to the caller to take care of
 * housekeeping.
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 40/45] builtin-am: support and auto-detect StGit series files

2015-07-19 Thread Paul Tan
Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27),
git-am.sh is able to read a single StGit series file and, for each StGit
patch listed in the file, convert the StGit patch into a RFC2822 mail
patch suitable for parsing with git-mailinfo, and queue them in the
state directory for applying.

Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh is able to auto-detect StGit series
files by checking to see if the file starts with the string:

# This series applies on GIT commit

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 59 ++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 101bca3..01f8a53 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -80,7 +80,8 @@ static int str_isspace(const char *str)
 enum patch_format {
PATCH_FORMAT_UNKNOWN = 0,
PATCH_FORMAT_MBOX,
-   PATCH_FORMAT_STGIT
+   PATCH_FORMAT_STGIT,
+   PATCH_FORMAT_STGIT_SERIES
 };
 
 enum keep_type {
@@ -652,6 +653,11 @@ static int detect_patch_format(const char **paths)
goto done;
}
 
+   if (starts_with(l1.buf, "# This series applies on GIT commit")) {
+   ret = PATCH_FORMAT_STGIT_SERIES;
+   goto done;
+   }
+
strbuf_reset(&l2);
strbuf_getline_crlf(&l2, fp);
strbuf_reset(&l3);
@@ -803,6 +809,53 @@ static int stgit_patch_to_mail(FILE *out, FILE *in, int 
keep_cr)
 }
 
 /**
+ * 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;
+}
+
+/**
  * 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`.
@@ -832,6 +885,8 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
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);
default:
die("BUG: invalid patch_format");
}
@@ -1881,6 +1936,8 @@ static int parse_opt_patchformat(const struct option 
*opt, const char *arg, int
*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
return error(_("Invalid value for --patch-format: %s"), arg);
return 0;
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 09/45] builtin-am: implement committing applied patch

2015-07-19 Thread Paul Tan
Implement do_commit(), which commits the index which contains the
results of applying the patch, along with the extracted commit message
and authorship information.

Since 29b6754 (am: remove rebase-apply directory before gc, 2010-02-22),
git gc --auto is also invoked to pack the loose objects that are created
from making the commits.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 55 +++
 1 file changed, 51 insertions(+), 4 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 1f198e4..a2811b6 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -11,6 +11,9 @@
 #include "run-command.h"
 #include "quote.h"
 #include "lockfile.h"
+#include "cache-tree.h"
+#include "refs.h"
+#include "commit.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -674,10 +677,56 @@ static int run_apply(const struct am_state *state)
 }
 
 /**
+ * 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 (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;
+   fprintf_ln(stderr, _("applying to an empty history"));
+   }
+
+   author = fmt_ident(state->author_name, state->author_email,
+   state->author_date, IDENT_STRICT);
+
+   if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
+   author, NULL))
+   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);
+
+   strbuf_release(&sb);
+}
+
+/**
  * Applies all queued mail.
  */
 static void am_run(struct am_state *state)
 {
+   const char *argv_gc_auto[] = {"gc", "--auto", NULL};
+
refresh_and_write_cache();
 
while (state->cur <= state->last) {
@@ -709,16 +758,14 @@ static void am_run(struct am_state *state)
exit(128);
}
 
-   /*
-* NEEDSWORK: After the patch has been applied to the index
-* with git-apply, we need to make commit as well.
-*/
+   do_commit(state);
 
 next:
am_next(state);
}
 
am_destroy(state);
+   run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
 }
 
 /**
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 19/45] cache-tree: introduce write_index_as_tree()

2015-07-19 Thread Paul Tan
A caller may wish to write a temporary index as a tree. However,
write_cache_as_tree() assumes that the index was read from, and will
write to, the default index file path. Introduce write_index_as_tree()
which removes this limitation by allowing the caller to specify its own
index_state and index file path.

Signed-off-by: Paul Tan 
---
 cache-tree.c | 29 +
 cache-tree.h |  1 +
 2 files changed, 18 insertions(+), 12 deletions(-)

diff --git a/cache-tree.c b/cache-tree.c
index 32772b9..feace8b 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -592,7 +592,7 @@ static struct cache_tree *cache_tree_find(struct cache_tree 
*it, const char *pat
return it;
 }
 
-int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
+int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, 
const char *index_path, int flags, const char *prefix)
 {
int entries, was_valid, newfd;
struct lock_file *lock_file;
@@ -603,23 +603,23 @@ int write_cache_as_tree(unsigned char *sha1, int flags, 
const char *prefix)
 */
lock_file = xcalloc(1, sizeof(struct lock_file));
 
-   newfd = hold_locked_index(lock_file, 1);
+   newfd = hold_lock_file_for_update(lock_file, index_path, 
LOCK_DIE_ON_ERROR);
 
-   entries = read_cache();
+   entries = read_index_from(index_state, index_path);
if (entries < 0)
return WRITE_TREE_UNREADABLE_INDEX;
if (flags & WRITE_TREE_IGNORE_CACHE_TREE)
-   cache_tree_free(&(active_cache_tree));
+   cache_tree_free(&index_state->cache_tree);
 
-   if (!active_cache_tree)
-   active_cache_tree = cache_tree();
+   if (!index_state->cache_tree)
+   index_state->cache_tree = cache_tree();
 
-   was_valid = cache_tree_fully_valid(active_cache_tree);
+   was_valid = cache_tree_fully_valid(index_state->cache_tree);
if (!was_valid) {
-   if (cache_tree_update(&the_index, flags) < 0)
+   if (cache_tree_update(index_state, flags) < 0)
return WRITE_TREE_UNMERGED_INDEX;
if (0 <= newfd) {
-   if (!write_locked_index(&the_index, lock_file, 
COMMIT_LOCK))
+   if (!write_locked_index(index_state, lock_file, 
COMMIT_LOCK))
newfd = -1;
}
/* Not being able to write is fine -- we are only interested
@@ -631,14 +631,14 @@ int write_cache_as_tree(unsigned char *sha1, int flags, 
const char *prefix)
}
 
if (prefix) {
-   struct cache_tree *subtree =
-   cache_tree_find(active_cache_tree, prefix);
+   struct cache_tree *subtree;
+   subtree = cache_tree_find(index_state->cache_tree, prefix);
if (!subtree)
return WRITE_TREE_PREFIX_ERROR;
hashcpy(sha1, subtree->sha1);
}
else
-   hashcpy(sha1, active_cache_tree->sha1);
+   hashcpy(sha1, index_state->cache_tree->sha1);
 
if (0 <= newfd)
rollback_lock_file(lock_file);
@@ -646,6 +646,11 @@ int write_cache_as_tree(unsigned char *sha1, int flags, 
const char *prefix)
return 0;
 }
 
+int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
+{
+   return write_index_as_tree(sha1, &the_index, get_index_file(), flags, 
prefix);
+}
+
 static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
 {
struct tree_desc desc;
diff --git a/cache-tree.h b/cache-tree.h
index aa7b3e4..41c5746 100644
--- a/cache-tree.h
+++ b/cache-tree.h
@@ -46,6 +46,7 @@ int update_main_cache_tree(int);
 #define WRITE_TREE_UNMERGED_INDEX (-2)
 #define WRITE_TREE_PREFIX_ERROR (-3)
 
+int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, 
const char *index_path, int flags, const char *prefix);
 int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix);
 void prime_cache_tree(struct index_state *, struct tree *);
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 36/45] builtin-am: invoke pre-applypatch hook

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sg will invoke the pre-applypatch hook after applying the patch
to the index, but before a commit is made. Should the hook exit with a
non-zero status, git am will exit.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 764e955..fdba3b0 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1336,6 +1336,9 @@ static void do_commit(const struct am_state *state)
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"));
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 42/45] builtin-am: implement -i/--interactive

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the --interactive mode. After parsing the patch mail
and extracting the patch, commit message and authorship info, an
interactive session will begin that allows the user to choose between:

* applying the patch

* applying the patch and all subsequent patches (by disabling
  interactive mode in subsequent patches)

* skipping the patch

* editing the commit message

Since f89ad67 (Add [v]iew patch in git-am interactive., 2005-10-25),
git-am.sh --interactive also supported viewing the patch to be applied.

When --resolved-ing in --interactive mode, we need to take care to
update the patch with the contents of the index, such that the correct
patch will be displayed when the patch is viewed in interactive mode.

Re-implement the above in builtin/am.c

Signed-off-by: Paul Tan 
---

Notes:
Can't be tested because even with test_terminal isatty(0) still returns
false.

 builtin/am.c | 105 ++-
 1 file changed, 104 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 8fe616e..39ea3c7 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -25,6 +25,7 @@
 #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.
@@ -119,6 +120,7 @@ struct am_state {
int prec;
 
/* various operating modes and command line options */
+   int interactive;
int threeway;
int quiet;
int append_signoff;
@@ -1173,7 +1175,7 @@ static void NORETURN die_user_resolve(const struct 
am_state *state)
if (state->resolvemsg) {
printf_ln("%s", state->resolvemsg);
} else {
-   const char *cmdline = "git am";
+   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);
@@ -1406,6 +1408,36 @@ static void write_commit_patch(const struct am_state 
*state, struct commit *comm
 }
 
 /**
+ * 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.
@@ -1657,6 +1689,65 @@ static void validate_resume_state(const struct am_state 
*state)
 }
 
 /**
+ * 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' 

[PATCH v6 37/45] builtin-am: invoke post-applypatch hook

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh will invoke the post-applypatch hook after the patch is
applied and a commit is made. The exit code of the hook is ignored.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index fdba3b0..638b6d3 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1380,6 +1380,8 @@ static void do_commit(const struct am_state *state)
fclose(fp);
}
 
+   run_hook_le(NULL, "post-applypatch", NULL);
+
strbuf_release(&sb);
 }
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 31/45] builtin-am: implement --committer-date-is-author-date

2015-07-19 Thread Paul Tan
Since 3f01ad6 (am: Add --committer-date-is-author-date option,
2009-01-22), git-am.sh implemented the --committer-date-is-author-date
option, which tells git-am to use the timestamp recorded in the email
message as both author and committer date.

Re-implement this option in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 8 
 1 file changed, 8 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 7f068e2..1080df6 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -108,6 +108,7 @@ struct am_state {
int scissors; /* enum scissors_type */
struct argv_array git_apply_opts;
const char *resolvemsg;
+   int committer_date_is_author_date;
int ignore_date;
int rebasing;
 };
@@ -1223,6 +1224,10 @@ static void do_commit(const struct am_state *state)
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, NULL))
die(_("failed to write commit object"));
@@ -1664,6 +1669,9 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 35/45] builtin-am: invoke applypatch-msg hook

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh will invoke the applypatch-msg hooks just after extracting the
patch message. If the applypatch-msg hook exits with a non-zero status,
git-am.sh abort before even applying the patch to the index.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 24 
 1 file changed, 24 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 64b467f..764e955 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -458,6 +458,27 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * 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)
@@ -1422,6 +1443,9 @@ static void am_run(struct am_state *state, int resume)
write_commit_msg(state);
}
 
+   if (run_applypatch_msg_hook(state))
+   exit(1);
+
say(state, stdout, _("Applying: %.*s"), linelen(state->msg), 
state->msg);
 
apply_status = run_apply(state, NULL);
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 43/45] builtin-am: implement legacy -b/--binary option

2015-07-19 Thread Paul Tan
The -b/--binary option was initially implemented in 087b674 (git-am:
--binary; document --resume and --binary., 2005-11-16). The option will
pass the --binary flag to git-apply to allow it to apply binary patches.

However, in 2b6eef9 (Make apply --binary a no-op., 2006-09-06), --binary
was been made a no-op in git-apply. Following that, since cb3a160
(git-am: ignore --binary option, 2008-08-09), the --binary option in
git-am is ignored as well.

In 6c15a1c (am: officially deprecate -b/--binary option, 2012-03-13),
the --binary option was tweaked to its present behavior: when set, the
message:

The -b/--binary option has been a no-op for long time, and it
will be removed. Please do not use it anymore.

will be printed.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 7 +++
 1 file changed, 7 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 39ea3c7..6a2b887 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2127,6 +2127,7 @@ enum resume_mode {
 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;
@@ -2140,6 +2141,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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")),
@@ -2240,6 +2243,10 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
 
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."));
+
if (read_index_preload(&the_index, NULL) < 0)
die(_("failed to read the index"));
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 44/45] builtin-am: check for valid committer ident

2015-07-19 Thread Paul Tan
When commit_tree() is called, if the user does not have an explicit
committer ident configured, it will attempt to construct a default
committer ident based on the user's and system's info (e.g. gecos field,
hostname etc.) However, if a default committer ident is unable to be
constructed, commit_tree() will die(), but at this point of git-am's
execution, there will already be changes made to the index and work
tree.

This can be confusing to new users, and as such since d64e6b0 (Keep
Porcelainish from failing by broken ident after making changes.,
2006-02-18) git-am.sh will check to see if the committer ident has been
configured, or a default one can be constructed, before even starting to
apply patches.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 6a2b887..1fe2c65 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2247,6 +2247,9 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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"));
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 45/45] builtin-am: remove redirection to git-am.sh

2015-07-19 Thread Paul Tan
At the beginning of the rewrite of git-am.sh to C, in order to not break
existing test scripts that depended on a functional git-am, a
redirection to git-am.sh was introduced that would activate if the
environment variable _GIT_USE_BUILTIN_AM was not defined.

Now that all of git-am.sh's functionality has been re-implemented in
builtin/am.c, remove this redirection, and retire git-am.sh into
contrib/examples/.

Signed-off-by: Paul Tan 
---
 Makefile|  1 -
 builtin/am.c| 15 ---
 git-am.sh => contrib/examples/git-am.sh |  0
 git.c   |  7 +--
 4 files changed, 1 insertion(+), 22 deletions(-)
 rename git-am.sh => contrib/examples/git-am.sh (100%)

diff --git a/Makefile b/Makefile
index f88642a..ec51f03 100644
--- a/Makefile
+++ b/Makefile
@@ -467,7 +467,6 @@ TEST_PROGRAMS_NEED_X =
 # interactive shell sessions without exporting it.
 unexport CDPATH
 
-SCRIPT_SH += git-am.sh
 SCRIPT_SH += git-bisect.sh
 SCRIPT_SH += git-difftool--helper.sh
 SCRIPT_SH += git-filter-branch.sh
diff --git a/builtin/am.c b/builtin/am.c
index 1fe2c65..1116304 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -,21 +,6 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
OPT_END()
};
 
-   /*
-* NEEDSWORK: Once all the features of git-am.sh have been
-* re-implemented in builtin/am.c, this preamble can be removed.
-*/
-   if (!getenv("_GIT_USE_BUILTIN_AM")) {
-   const char *path = mkpath("%s/git-am", git_exec_path());
-
-   if (sane_execvp(path, (char **)argv) < 0)
-   die_errno("could not exec %s", path);
-   } else {
-   prefix = setup_git_directory();
-   trace_repo_setup(prefix);
-   setup_work_tree();
-   }
-
git_config(git_default_config, NULL);
 
am_state_init(&state, git_path("rebase-apply"));
diff --git a/git-am.sh b/contrib/examples/git-am.sh
similarity index 100%
rename from git-am.sh
rename to contrib/examples/git-am.sh
diff --git a/git.c b/git.c
index 38d9ad5..5feba41 100644
--- a/git.c
+++ b/git.c
@@ -370,12 +370,7 @@ static int run_builtin(struct cmd_struct *p, int argc, 
const char **argv)
 
 static struct cmd_struct commands[] = {
{ "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
-   /*
-* NEEDSWORK: Once the redirection to git-am.sh in builtin/am.c has
-* been removed, this entry should be changed to
-* RUN_SETUP | NEED_WORK_TREE
-*/
-   { "am", cmd_am },
+   { "am", cmd_am, RUN_SETUP | NEED_WORK_TREE },
{ "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive },
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 25/45] builtin-am: implement -k/--keep, --keep-non-patch

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the -k/--keep option to pass the -k option to
git-mailsplit.

Since f7e5ea1 (am: learn passing -b to mailinfo, 2012-01-16), git-am.sh
supported the --keep-non-patch option to pass the -b option to
git-mailsplit.

Re-implement these two options in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 50 ++
 1 file changed, 50 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index a64a859..af08a22 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -68,6 +68,12 @@ enum patch_format {
PATCH_FORMAT_MBOX
 };
 
+enum keep_type {
+   KEEP_FALSE = 0,
+   KEEP_TRUE,  /* pass -k flag to git-mailinfo */
+   KEEP_NON_PATCH  /* pass -b flag to git-mailinfo */
+};
+
 struct am_state {
/* state directory path */
char *dir;
@@ -91,6 +97,7 @@ struct am_state {
int quiet;
int append_signoff;
int utf8;
+   int keep; /* enum keep_type */
const char *resolvemsg;
int rebasing;
 };
@@ -375,6 +382,14 @@ static void am_load(struct am_state *state)
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;
+
state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
strbuf_release(&sb);
@@ -538,6 +553,7 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
const char **paths)
 {
unsigned char curr_head[GIT_SHA1_RAWSZ];
+   const char *str;
 
if (!patch_format)
patch_format = detect_patch_format(paths);
@@ -566,6 +582,22 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
 
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);
+
if (state->rebasing)
write_file(am_path(state, "rebasing"), 1, "%s", "");
else
@@ -733,6 +765,20 @@ static int parse_mail(struct am_state *state, const char 
*mail)
 
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");
+   }
+
argv_array_push(&cp.args, am_path(state, "msg"));
argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1472,6 +1518,10 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
N_("add a Signed-off-by line to the commit message")),
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_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 41/45] builtin-am: support and auto-detect mercurial patches

2015-07-19 Thread Paul Tan
Since 0cfd112 (am: preliminary support for hg patches, 2011-08-29),
git-am.sh could convert mercurial patches to an RFC2822 mail patch
suitable for parsing with git-mailinfo, and queue them in the state
directory for application.

Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh was able to auto-detect mercurial
patches by checking if the file begins with the line:

# HG changeset patch

Re-implement the above in builtin/am.c.

Helped-by: Stefan Beller 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 74 +++-
 1 file changed, 73 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 01f8a53..8fe616e 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -81,7 +81,8 @@ enum patch_format {
PATCH_FORMAT_UNKNOWN = 0,
PATCH_FORMAT_MBOX,
PATCH_FORMAT_STGIT,
-   PATCH_FORMAT_STGIT_SERIES
+   PATCH_FORMAT_STGIT_SERIES,
+   PATCH_FORMAT_HG
 };
 
 enum keep_type {
@@ -658,6 +659,11 @@ static int detect_patch_format(const char **paths)
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);
@@ -856,6 +862,68 @@ static int split_mail_stgit_series(struct am_state *state, 
const char **paths,
 }
 
 /**
+ * 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_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`.
@@ -887,6 +955,8 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
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");
}
@@ -1938,6 +2008,8 @@ static int parse_opt_patchformat(const struct option 
*opt, const char *arg, int
*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

[PATCH v6 38/45] builtin-am: rerere support

2015-07-19 Thread Paul Tan
git-am.sh will call git-rerere at the following events:

* "git rerere" when a three-way merge fails to record the conflicted
  automerge results. Since 8389b52 (git-rerere: reuse recorded resolve.,
  2006-01-28)

  * Since cb6020b (Teach --[no-]rerere-autoupdate option to merge,
revert and friends, 2009-12-04), git-am.sh supports the
--[no-]rerere-autoupdate option as well, and would pass it to
git-rerere.

* "git rerere" when --resolved, to record the hand resolution. Since
  f131dd4 (rerere: record (or avoid misrecording) resolved, skipped or
  aborted rebase/am, 2006-12-08)

* "git rerere clear" when --skip-ing. Since f131dd4 (rerere: record (or
  avoid misrecording) resolved, skipped or aborted rebase/am,
  2006-12-08)

* "git rerere clear" when --abort-ing. Since 3e5057a (git am --abort,
  2008-07-16)

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 25 +
 1 file changed, 25 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 638b6d3..89613e0 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -24,6 +24,7 @@
 #include "revision.h"
 #include "log-tree.h"
 #include "notes-utils.h"
+#include "rerere.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -114,6 +115,7 @@ struct am_state {
const char *resolvemsg;
int committer_date_is_author_date;
int ignore_date;
+   int allow_rerere_autoupdate;
const char *sign_commit;
int rebasing;
 };
@@ -1314,6 +1316,7 @@ static int fall_back_threeway(const struct am_state 
*state, const char *index_pa
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."));
}
@@ -1533,6 +1536,8 @@ static void am_resolve(struct am_state *state)
die_user_resolve(state);
}
 
+   rerere(0);
+
do_commit(state);
 
am_next(state);
@@ -1633,12 +1638,29 @@ static int clean_index(const unsigned char *head, const 
unsigned char *remote)
 }
 
 /**
+ * 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);
 
@@ -1696,6 +1718,8 @@ static void am_abort(struct am_state *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)
@@ -1824,6 +1848,7 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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) "" },
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 34/45] builtin-am: support automatic notes copying

2015-07-19 Thread Paul Tan
Since eb2151b (rebase: support automatic notes copying, 2010-03-12),
git-am.sh supported automatic notes copying in --rebasing mode by
invoking "git notes copy" once it has finished applying all the patches.

Re-implement this feature in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 60 
 1 file changed, 60 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index e99e1ec..64b467f 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -23,6 +23,7 @@
 #include "merge-recursive.h"
 #include "revision.h"
 #include "log-tree.h"
+#include "notes-utils.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -481,6 +482,64 @@ static int run_post_rewrite_hook(const struct am_state 
*state)
 }
 
 /**
+ * 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.
@@ -1407,6 +1466,7 @@ next:
 
if (!is_empty_file(am_path(state, "rewritten"))) {
assert(state->rebasing);
+   copy_notes_for_rebase(state);
run_post_rewrite_hook(state);
}
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 23/45] builtin-am: handle stray state directory

2015-07-19 Thread Paul Tan
Should git-am terminate unexpectedly between the point where the state
directory is created, but the "next" and "last" files are not written
yet, a stray state directory will be left behind.

As such, since b141f3c (am: handle stray $dotest directory, 2013-06-15),
git-am.sh explicitly recognizes such a stray directory, and allows the
user to remove it with am --abort.

Re-implement this feature in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 17 +
 1 file changed, 17 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 01f01bf..398debf 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1531,6 +1531,23 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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."));
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 27/45] builtin-am: support --keep-cr, am.keepcr

2015-07-19 Thread Paul Tan
Since ad2c928 (git-am: Add command line parameter `--keep-cr` passing it
to git-mailsplit, 2010-02-27), git-am.sh supported the --keep-cr option
and would pass it to git-mailsplit.

Since e80d4cb (git-am: Add am.keepcr and --no-keep-cr to override it,
2010-02-27), git-am.sh supported the am.keepcr config setting, which
controls whether --keep-cr is on by default.

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 29 +++--
 1 file changed, 23 insertions(+), 6 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index e41a104..80f091e 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -504,7 +504,7 @@ done:
  * 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)
+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;
@@ -514,6 +514,8 @@ static int split_mail_mbox(struct am_state *state, const 
char **paths)
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);
 
@@ -538,14 +540,22 @@ static int split_mail_mbox(struct am_state *state, const 
char **paths)
  * 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)
+   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);
+   return split_mail_mbox(state, paths, keep_cr);
default:
die("BUG: invalid patch_format");
}
@@ -556,7 +566,7 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
  * Setup a new am session for applying patches
  */
 static void am_setup(struct am_state *state, enum patch_format patch_format,
-   const char **paths)
+   const char **paths, int keep_cr)
 {
unsigned char curr_head[GIT_SHA1_RAWSZ];
const char *str;
@@ -572,7 +582,7 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
die_errno(_("failed to create directory '%s'"), state->dir);
 
-   if (split_mail(state, patch_format, paths) < 0) {
+   if (split_mail(state, patch_format, paths, keep_cr) < 0) {
am_destroy(state);
die(_("Failed to split patches."));
}
@@ -1512,6 +1522,7 @@ enum resume_mode {
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
struct am_state state;
+   int keep_cr = -1;
int patch_format = PATCH_FORMAT_UNKNOWN;
enum resume_mode resume = RESUME_FALSE;
 
@@ -1535,6 +1546,12 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
@@ -1632,7 +1649,7 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
argv_array_push(&paths, mkpath("%s/%s", prefix, 
argv[i]));
   

[PATCH v6 28/45] builtin-am: implement --[no-]scissors

2015-07-19 Thread Paul Tan
Since 017678b (am/mailinfo: Disable scissors processing by default,
2009-08-26), git-am supported the --[no-]scissors option, passing it to
git-mailinfo.

Re-implement support for this option in builtin/am.c.

Since the default setting of --scissors in git-mailinfo can be
configured with mailinfo.scissors (and perhaps through other settings in
the future), to be safe we make an explicit distinction between
SCISSORS_UNSET, SCISSORS_TRUE and SCISSORS_FALSE.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 48 
 1 file changed, 48 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 80f091e..1f79750 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -74,6 +74,12 @@ enum keep_type {
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 */
+};
+
 struct am_state {
/* state directory path */
char *dir;
@@ -99,6 +105,7 @@ struct am_state {
int utf8;
int keep; /* enum keep_type */
int message_id;
+   int scissors; /* enum scissors_type */
const char *resolvemsg;
int rebasing;
 };
@@ -121,6 +128,8 @@ static void am_state_init(struct am_state *state, const 
char *dir)
state->utf8 = 1;
 
git_config_get_bool("am.messageid", &state->message_id);
+
+   state->scissors = SCISSORS_UNSET;
 }
 
 /**
@@ -396,6 +405,14 @@ static void am_load(struct am_state *state)
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;
+
state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
strbuf_release(&sb);
@@ -616,6 +633,22 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
 
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);
+
if (state->rebasing)
write_file(am_path(state, "rebasing"), 1, "%s", "");
else
@@ -800,6 +833,19 @@ static int parse_mail(struct am_state *state, const char 
*mail)
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"));
 
@@ -1552,6 +1598,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
{ 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_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 29/45] builtin-am: pass git-apply's options to git-apply

2015-07-19 Thread Paul Tan
git-am.sh recognizes some of git-apply's options, and would pass them to
git-apply:

* --whitespace, since 8c31cb8 (git-am: --whitespace=x option.,
  2006-02-28)

* -C, since 67dad68 (add -C[NUM] to git-am, 2007-02-08)

* -p, since 2092a1f (Teach git-am to pass -p option down to git-apply,
  2007-02-11)

* --directory, since b47dfe9 (git-am: add --directory= option,
  2009-01-11)

* --reject, since b80da42 (git-am: implement --reject option passed to
  git-apply, 2009-01-23)

* --ignore-space-change, --ignore-whitespace, since 86c91f9 (git apply:
  option to ignore whitespace differences, 2009-08-04)

* --exclude, since 77e9e49 (am: pass exclude down to apply, 2011-08-03)

* --include, since 58725ef (am: support --include option, 2012-03-28)

* --reject, since b80da42 (git-am: implement --reject option passed to
  git-apply, 2009-01-23)

Re-implement support for these options in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 45 +
 1 file changed, 45 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 1f79750..9ff0736 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -106,6 +106,7 @@ struct am_state {
int keep; /* enum keep_type */
int message_id;
int scissors; /* enum scissors_type */
+   struct argv_array git_apply_opts;
const char *resolvemsg;
int rebasing;
 };
@@ -130,6 +131,8 @@ static void am_state_init(struct am_state *state, const 
char *dir)
git_config_get_bool("am.messageid", &state->message_id);
 
state->scissors = SCISSORS_UNSET;
+
+   argv_array_init(&state->git_apply_opts);
 }
 
 /**
@@ -142,6 +145,7 @@ static void am_state_release(struct am_state *state)
free(state->author_email);
free(state->author_date);
free(state->msg);
+   argv_array_clear(&state->git_apply_opts);
 }
 
 /**
@@ -413,6 +417,11 @@ static void am_load(struct am_state *state)
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);
@@ -587,6 +596,7 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
 {
unsigned char curr_head[GIT_SHA1_RAWSZ];
const char *str;
+   struct strbuf sb = STRBUF_INIT;
 
if (!patch_format)
patch_format = detect_patch_format(paths);
@@ -649,6 +659,9 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
 
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
@@ -673,6 +686,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
write_file(am_path(state, "next"), 1, "%d", state->cur);
 
write_file(am_path(state, "last"), 1, "%d", state->last);
+
+   strbuf_release(&sb);
 }
 
 /**
@@ -1060,6 +1075,8 @@ static int run_apply(const struct am_state *state, const 
char *index_file)
 
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
@@ -1086,6 +1103,7 @@ static int build_fake_ancestor(const struct am_state 
*state, const char *index_f
 
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"));
 
@@ -1600,9 +1618,36 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
  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,
+ 

[PATCH v6 30/45] builtin-am: implement --ignore-date

2015-07-19 Thread Paul Tan
Since a79ec62 (git-am: Add --ignore-date option, 2009-01-24), git-am.sh
supported the --ignore-date option, and would use the current timestamp
instead of the one provided in the patch if the option was set.

Re-implement this option in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 6 +-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 9ff0736..7f068e2 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -108,6 +108,7 @@ struct am_state {
int scissors; /* enum scissors_type */
struct argv_array git_apply_opts;
const char *resolvemsg;
+   int ignore_date;
int rebasing;
 };
 
@@ -1219,7 +1220,8 @@ static void do_commit(const struct am_state *state)
}
 
author = fmt_ident(state->author_name, state->author_email,
-   state->author_date, IDENT_STRICT);
+   state->ignore_date ? NULL : state->author_date,
+   IDENT_STRICT);
 
if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
author, NULL))
@@ -1662,6 +1664,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
OPT_CMDMODE(0, "abort", &resume,
N_("restore the original branch and abort the patching 
operation."),
RESUME_ABORT),
+   OPT_BOOL(0, "ignore-date", &state.ignore_date,
+   N_("use current timestamp for author date")),
OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
N_("(internal use for git-rebase)")),
OPT_END()
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 12/45] builtin-am: don't parse mail when resuming

2015-07-19 Thread Paul Tan
Since 271440e (git-am: make it easier after fixing up an unapplicable
patch., 2005-10-25), when "git am" is run again after being paused, the
current mail message will not be re-parsed, but instead the contents of
the state directory's patch, msg and author-script files will be used
as-is instead.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---

Notes:
v6

New patch. A test for this in t4150 would be something like this:

test_expect_success 'do not parse mail when resuming' '
rm -fr .git/rebase-apply &&
git reset --hard &&
git checkout lorem2^^ &&
test_must_fail git am lorem-move.patch &&
test_path_is_dir .git/rebase-apply &&
test_cmp_rev lorem2^^ HEAD &&
echo resolved >file &&
git diff >.git/rebase-apply/patch &&
git reset --hard &&
git am <&6 &&
test_path_is_missing .git/rebase-apply &&
test resolved = "$(cat file)"
'

However, it relies on stdin being a TTY so I can't add it to the
more-am-tests patch series :-/

 builtin/am.c | 34 +-
 1 file changed, 25 insertions(+), 9 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index fd26721..ec579a6 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -776,8 +776,12 @@ static void validate_resume_state(const struct am_state 
*state)
 
 /**
  * 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)
+static void am_run(struct am_state *state, int resume)
 {
const char *argv_gc_auto[] = {"gc", "--auto", NULL};
struct strbuf sb = STRBUF_INIT;
@@ -795,11 +799,16 @@ static void am_run(struct am_state *state)
if (!file_exists(mail))
goto next;
 
-   if (parse_mail(state, mail))
-   goto next; /* mail should be skipped */
+   if (resume) {
+   validate_resume_state(state);
+   resume = 0;
+   } else {
+   if (parse_mail(state, mail))
+   goto next; /* mail should be skipped */
 
-   write_author_script(state);
-   write_commit_msg(state);
+   write_author_script(state);
+   write_commit_msg(state);
+   }
 
printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -855,7 +864,7 @@ static void am_resolve(struct am_state *state)
do_commit(state);
 
am_next(state);
-   am_run(state);
+   am_run(state, 0);
 }
 
 /**
@@ -875,6 +884,7 @@ static int parse_opt_patchformat(const struct option *opt, 
const char *arg, int
 
 enum resume_mode {
RESUME_FALSE = 0,
+   RESUME_APPLY,
RESUME_RESOLVED
 };
 
@@ -927,9 +937,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)
if (read_index_preload(&the_index, NULL) < 0)
die(_("failed to read the index"));
 
-   if (am_in_progress(&state))
+   if (am_in_progress(&state)) {
+   if (resume == RESUME_FALSE)
+   resume = RESUME_APPLY;
+
am_load(&state);
-   else {
+   } else {
struct argv_array paths = ARGV_ARRAY_INIT;
int i;
 
@@ -950,7 +963,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
switch (resume) {
case RESUME_FALSE:
-   am_run(&state);
+   am_run(&state, 0);
+   break;
+   case RESUME_APPLY:
+   am_run(&state, 1);
break;
case RESUME_RESOLVED:
am_resolve(&state);
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 10/45] builtin-am: refuse to apply patches if index is dirty

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
will refuse to apply patches if the index is dirty. Re-implement this
behavior in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 45 +
 1 file changed, 45 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index a2811b6..537ad62 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -14,6 +14,8 @@
 #include "cache-tree.h"
 #include "refs.h"
 #include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -565,6 +567,43 @@ static void refresh_and_write_cache(void)
 }
 
 /**
+ * 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;
+   }
+}
+
+/**
  * 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
@@ -726,9 +765,15 @@ static void do_commit(const struct am_state *state)
 static void am_run(struct am_state *state)
 {
const char *argv_gc_auto[] = {"gc", "--auto", NULL};
+   struct strbuf sb = STRBUF_INIT;
 
refresh_and_write_cache();
 
+   if (index_has_changes(&sb))
+   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));
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 20/45] builtin-am: implement --3way, am.threeWay

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the --3way option, and if set, would attempt to do a
3-way merge if the initial patch application fails.

Since d96a275 (git-am: add am.threeWay config variable, 2015-06-04), the
setting am.threeWay configures if the --3way option is set by default.

Since 5d86861 (am -3: list the paths that needed 3-way fallback,
2012-03-28), in a 3-way merge git-am.sh would list the paths that needed
3-way fallback, so that the user can review them more carefully to spot
mismerges.

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 156 +--
 1 file changed, 152 insertions(+), 4 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 7c0f604..7237fff 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -19,6 +19,8 @@
 #include "unpack-trees.h"
 #include "branch.h"
 #include "sequencer.h"
+#include "revision.h"
+#include "merge-recursive.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -83,6 +85,7 @@ struct am_state {
int prec;
 
/* various operating modes and command line options */
+   int threeway;
int quiet;
int append_signoff;
const char *resolvemsg;
@@ -100,6 +103,8 @@ static void am_state_init(struct am_state *state, const 
char *dir)
state->dir = xstrdup(dir);
 
state->prec = 4;
+
+   git_config_get_bool("am.threeway", &state->threeway);
 }
 
 /**
@@ -352,6 +357,9 @@ static void am_load(struct am_state *state)
 
read_commit_msg(state);
 
+   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");
 
@@ -536,6 +544,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
die(_("Failed to split patches."));
}
 
+   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->append_signoff ? "t" : 
"f");
@@ -766,16 +776,34 @@ finish:
 }
 
 /**
- * Applies current patch with git-apply. Returns 0 on success, -1 otherwise.
+ * 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)
+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_push(&cp.args, "--index");
+
+   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))
@@ -783,8 +811,106 @@ static int run_apply(const struct am_state *state)
 
/* 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_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 *resu

[PATCH v6 24/45] builtin-am: implement -u/--utf8

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the -u,--utf8 option. If set, the -u option will be
passed to git-mailinfo to re-code the commit log message and authorship
in the charset specified by i18n.commitencoding. If unset, the -n option
will be passed to git-mailinfo, which disables the re-encoding.

Since d84029b (--utf8 is now default for 'git-am', 2007-01-08), --utf8
is specified by default in git-am.sh.

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 11 +++
 1 file changed, 11 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 398debf..a64a859 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -90,6 +90,7 @@ struct am_state {
int threeway;
int quiet;
int append_signoff;
+   int utf8;
const char *resolvemsg;
int rebasing;
 };
@@ -108,6 +109,8 @@ static void am_state_init(struct am_state *state, const 
char *dir)
state->prec = 4;
 
git_config_get_bool("am.threeway", &state->threeway);
+
+   state->utf8 = 1;
 }
 
 /**
@@ -369,6 +372,9 @@ static void am_load(struct am_state *state)
read_state_file(&sb, state, "sign", 1);
state->append_signoff = !strcmp(sb.buf, "t");
 
+   read_state_file(&sb, state, "utf8", 1);
+   state->utf8 = !strcmp(sb.buf, "t");
+
state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
strbuf_release(&sb);
@@ -558,6 +564,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
 
write_file(am_path(state, "sign"), 1, state->append_signoff ? "t" : 
"f");
 
+   write_file(am_path(state, "utf8"), 1, state->utf8 ? "t" : "f");
+
if (state->rebasing)
write_file(am_path(state, "rebasing"), 1, "%s", "");
else
@@ -724,6 +732,7 @@ static int parse_mail(struct am_state *state, const char 
*mail)
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");
argv_array_push(&cp.args, am_path(state, "msg"));
argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1461,6 +1470,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
OPT__QUIET(&state.quiet, N_("be quiet")),
OPT_BOOL('s', "signoff", &state.append_signoff,
N_("add a Signed-off-by line to the commit message")),
+   OPT_BOOL('u', "utf8", &state.utf8,
+   N_("recode into utf8 (default)")),
OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 14/45] builtin-am: implement --abort

2015-07-19 Thread Paul Tan
Since 3e5057a (git am --abort, 2008-07-16), git-am supported the --abort
option that will rewind HEAD back to the original commit. Re-implement
this through am_abort().

Since 7b3b7e3 (am --abort: keep unrelated commits since the last failure
and warn, 2010-12-21), to prevent commits made since the last failure
from being lost, git-am will not rewind HEAD back to the original
commit if HEAD moved since the last failure. Re-implement this through
safe_to_abort().

Signed-off-by: Paul Tan 
---
 builtin/am.c | 102 +--
 1 file changed, 99 insertions(+), 3 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 765844b..8a054ee 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -490,6 +490,8 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
 static void am_setup(struct am_state *state, enum patch_format patch_format,
const char **paths)
 {
+   unsigned char curr_head[GIT_SHA1_RAWSZ];
+
if (!patch_format)
patch_format = detect_patch_format(paths);
 
@@ -506,6 +508,14 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
die(_("Failed to split patches."));
}
 
+   if (!get_sha1("HEAD", curr_head)) {
+   write_file(am_path(state, "abort-safety"), 1, "%s", 
sha1_to_hex(curr_head));
+   update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, 
UPDATE_REFS_DIE_ON_ERR);
+   } else {
+   write_file(am_path(state, "abort-safety"), 1, "%s", "");
+   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.
@@ -522,6 +532,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
  */
 static void am_next(struct am_state *state)
 {
+   unsigned char head[GIT_SHA1_RAWSZ];
+
free(state->author_name);
state->author_name = NULL;
 
@@ -538,6 +550,11 @@ static void am_next(struct am_state *state)
unlink(am_path(state, "author-script"));
unlink(am_path(state, "final-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);
 }
@@ -788,10 +805,14 @@ 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))
+   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);
 
@@ -980,6 +1001,74 @@ static void am_skip(struct am_state *state)
 }
 
 /**
+ * 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;
+   const char *curr_branch;
+
+   if (!safe_to_abort(state)) {
+   am_destroy(state);
+   return;
+   }
+
+   curr_branch = resolv

[PATCH v6 16/45] builtin-am: implement -q/--quiet

2015-07-19 Thread Paul Tan
Since 0e987a1 (am, rebase: teach quiet option, 2009-06-16), git-am
supported the --quiet option, and when told to be quiet, would only
speak on failure. Re-implement this by introducing the say() function,
which works like fprintf_ln(), but would only write to the stream when
state->quiet is false.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 31 ---
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index fa522f5..e0b86d1 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -80,6 +80,9 @@ struct am_state {
 
/* number of digits in patch filename */
int prec;
+
+   /* various operating modes and command line options */
+   int quiet;
 };
 
 /**
@@ -117,6 +120,22 @@ static inline const char *am_path(const struct am_state 
*state, const char *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)
@@ -330,6 +349,9 @@ static void am_load(struct am_state *state)
 
read_commit_msg(state);
 
+   read_state_file(&sb, state, "quiet", 1);
+   state->quiet = !strcmp(sb.buf, "t");
+
strbuf_release(&sb);
 }
 
@@ -508,6 +530,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
die(_("Failed to split patches."));
}
 
+   write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
+
if (!get_sha1("HEAD", curr_head)) {
write_file(am_path(state, "abort-safety"), 1, "%s", 
sha1_to_hex(curr_head));
update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, 
UPDATE_REFS_DIE_ON_ERR);
@@ -756,7 +780,7 @@ static void do_commit(const struct am_state *state)
commit_list_insert(lookup_commit(parent), &parents);
} else {
ptr = NULL;
-   fprintf_ln(stderr, _("applying to an empty history"));
+   say(state, stderr, _("applying to an empty history"));
}
 
author = fmt_ident(state->author_name, state->author_email,
@@ -833,7 +857,7 @@ static void am_run(struct am_state *state, int resume)
write_commit_msg(state);
}
 
-   printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg);
+   say(state, stdout, _("Applying: %.*s"), linelen(state->msg), 
state->msg);
 
if (run_apply(state) < 0) {
int advice_amworkdir = 1;
@@ -869,7 +893,7 @@ static void am_resolve(struct am_state *state)
 {
validate_resume_state(state);
 
-   printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg);
+   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"
@@ -1104,6 +1128,7 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
};
 
struct option options[] = {
+   OPT__QUIET(&state.quiet, N_("be quiet")),
OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 15/45] builtin-am: reject patches when there's a session in progress

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
would error out if the user gave it mbox(s) on the command-line, but
there was a session in progress.

Since c95b138 (Fix git-am safety checks, 2006-09-15), git-am would
detect if the user attempted to feed it a mbox via stdin, by checking if
stdin is not a tty and there is no resume command given.

Re-implement the above two safety checks.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 15 +++
 1 file changed, 15 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 8a054ee..fa522f5 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1147,6 +1147,21 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
die(_("failed to read the index"));
 
if (am_in_progress(&state)) {
+   /*
+* 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;
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 18/45] builtin-am: implement -s/--signoff

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
supported the --signoff option which will append a signoff at the end of
the commit messsage. Re-implement this feature in parse_mail() by
calling append_signoff() if the option is set.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 12 
 1 file changed, 12 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index fb428f9..7c0f604 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -18,6 +18,7 @@
 #include "diffcore.h"
 #include "unpack-trees.h"
 #include "branch.h"
+#include "sequencer.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -83,6 +84,7 @@ struct am_state {
 
/* various operating modes and command line options */
int quiet;
+   int append_signoff;
const char *resolvemsg;
 };
 
@@ -353,6 +355,9 @@ static void am_load(struct am_state *state)
read_state_file(&sb, state, "quiet", 1);
state->quiet = !strcmp(sb.buf, "t");
 
+   read_state_file(&sb, state, "sign", 1);
+   state->append_signoff = !strcmp(sb.buf, "t");
+
strbuf_release(&sb);
 }
 
@@ -533,6 +538,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
 
write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
 
+   write_file(am_path(state, "sign"), 1, state->append_signoff ? "t" : 
"f");
+
if (!get_sha1("HEAD", curr_head)) {
write_file(am_path(state, "abort-safety"), 1, "%s", 
sha1_to_hex(curr_head));
update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, 
UPDATE_REFS_DIE_ON_ERR);
@@ -734,6 +741,9 @@ static int parse_mail(struct am_state *state, const char 
*mail)
die_errno(_("could not read '%s'"), am_path(state, "msg"));
stripspace(&msg, 0);
 
+   if (state->append_signoff)
+   append_signoff(&msg, 0, 0);
+
assert(!state->author_name);
state->author_name = strbuf_detach(&author_name, NULL);
 
@@ -1149,6 +1159,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
 
struct option options[] = {
OPT__QUIET(&state.quiet, N_("be quiet")),
+   OPT_BOOL('s', "signoff", &state.append_signoff,
+   N_("add a Signed-off-by line to the commit message")),
OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 21/45] builtin-am: implement --rebasing mode

2015-07-19 Thread Paul Tan
Since 3041c32 (am: --rebasing, 2008-03-04), git-am.sh supported the
--rebasing option, which is used internally by git-rebase to tell git-am
that it is being used for its purpose. It would create the empty file
$state_dir/rebasing to help "completion" scripts tell if the ongoing
operation is am or rebase.

As of 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26),
--rebasing also implies --3way as well.

Since a1549e1 (am: return control to caller, for housekeeping,
2013-05-12), git-am.sh would only clean up the state directory when it
is not --rebasing, instead deferring cleanup to git-rebase.sh.

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 30 ++
 1 file changed, 26 insertions(+), 4 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 7237fff..3107aba 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -89,6 +89,7 @@ struct am_state {
int quiet;
int append_signoff;
const char *resolvemsg;
+   int rebasing;
 };
 
 /**
@@ -366,6 +367,8 @@ static void am_load(struct am_state *state)
read_state_file(&sb, state, "sign", 1);
state->append_signoff = !strcmp(sb.buf, "t");
 
+   state->rebasing = !!file_exists(am_path(state, "rebasing"));
+
strbuf_release(&sb);
 }
 
@@ -544,18 +547,29 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
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->append_signoff ? "t" : 
"f");
 
+   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));
-   update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, 
UPDATE_REFS_DIE_ON_ERR);
+   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", "");
-   delete_ref("ORIG_HEAD", NULL, 0);
+   if (!state->rebasing)
+   delete_ref("ORIG_HEAD", NULL, 0);
}
 
/*
@@ -1056,8 +1070,14 @@ next:
am_next(state);
}
 
-   am_destroy(state);
-   run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+   /*
+* 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);
+   }
 }
 
 /**
@@ -1326,6 +1346,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
OPT_CMDMODE(0, "abort", &resume,
N_("restore the original branch and abort the patching 
operation."),
RESUME_ABORT),
+   OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
+   N_("(internal use for git-rebase)")),
OPT_END()
};
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 13/45] builtin-am: implement --skip

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
supported resuming from a failed patch application by skipping the
current patch. Re-implement this feature by introducing am_skip().

Helped-by: Stefan Beller 
Signed-off-by: Paul Tan 
---

Notes:
v6

* Moved lock_file memory allocation to just before the
  hold_locked_index() to prevent memory leaks.

 builtin/am.c | 123 ++-
 1 file changed, 121 insertions(+), 2 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index ec579a6..765844b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -16,6 +16,8 @@
 #include "commit.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "unpack-trees.h"
+#include "branch.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -868,6 +870,116 @@ static void am_resolve(struct am_state *state)
 }
 
 /**
+ * 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;
+}
+
+/**
+ * Resume the current am session by skipping the current patch.
+ */
+static void am_skip(struct am_state *state)
+{
+   unsigned char head[GIT_SHA1_RAWSZ];
+
+   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_run(state, 0);
+}
+
+/**
  * parse_options() callback that validates and sets opt->value to the
  * PATCH_FORMAT_* enum value corresponding to `arg`.
  */
@@ -885,7 +997,8 @@ static int parse_opt_patchformat(const struct option *opt, 
const char *arg, int
 enum resume_mode {
RESUME_FALSE = 0,
RESUME_APPLY,
-   RESUME_RESOLVED
+   RESUME_RESOLVED,
+   RESUME_SKIP
 };
 
 int cmd_am(int argc, const char **argv, const char *prefix)
@@ -896,7 +1009,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
const char * const usage[] = {
N_("git am [options] [(|)...]"),
-  

[PATCH v6 17/45] builtin-am: exit with user friendly message on failure

2015-07-19 Thread Paul Tan
Since ced9456 (Give the user a hint for how to continue in the case that
git-am fails because it requires user intervention, 2006-05-02), git-am
prints additional information on how the user can re-invoke git-am to
resume patch application after resolving the failure. Re-implement this
through the die_user_resolve() function.

Since cc12005 (Make git rebase interactive help match documentation.,
2006-05-13), git-am supports the --resolvemsg option which is used by
git-rebase to override the message printed out when git-am fails.
Re-implement this option.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 30 ++
 1 file changed, 26 insertions(+), 4 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index e0b86d1..fb428f9 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -83,6 +83,7 @@ struct am_state {
 
/* various operating modes and command line options */
int quiet;
+   const char *resolvemsg;
 };
 
 /**
@@ -647,6 +648,25 @@ static int index_has_changes(struct strbuf *sb)
 }
 
 /**
+ * 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 = "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);
+}
+
+/**
  * 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
@@ -706,7 +726,7 @@ static int parse_mail(struct am_state *state, const char 
*mail)
 
if (is_empty_file(am_path(state, "patch"))) {
printf_ln(_("Patch is empty. Was it split wrong?"));
-   exit(128);
+   die_user_resolve(state);
}
 
strbuf_addstr(&msg, "\n\n");
@@ -871,7 +891,7 @@ static void am_run(struct am_state *state, int resume)
printf_ln(_("The copy of the patch that failed 
is found in: %s"),
am_path(state, "patch"));
 
-   exit(128);
+   die_user_resolve(state);
}
 
do_commit(state);
@@ -899,13 +919,13 @@ static void am_resolve(struct am_state *state)
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."));
-   exit(128);
+   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'?"));
-   exit(128);
+   die_user_resolve(state);
}
 
do_commit(state);
@@ -1132,6 +1152,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
+   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),
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 11/45] builtin-am: implement --resolved/--continue

2015-07-19 Thread Paul Tan
Since 0c15cc9 (git-am: --resolved., 2005-11-16), git-am supported
resuming from a failed patch application. The user will manually apply
the patch, and the run git am --resolved which will then commit the
resulting index. Re-implement this feature by introducing am_resolve().

Since it makes no sense for the user to run am --resolved when there is
no session in progress, we error out in this case.

Signed-off-by: Paul Tan 
---

Notes:
v6

* Extracted out the validation of the state directory for resuming into
  a separate validate_resume_state() function, so that it can be shared
  with the next patch.

 builtin/am.c | 72 +++-
 1 file changed, 71 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 537ad62..fd26721 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -760,6 +760,21 @@ static void do_commit(const struct am_state *state)
 }
 
 /**
+ * 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"));
+}
+
+/**
  * Applies all queued mail.
  */
 static void am_run(struct am_state *state)
@@ -814,6 +829,36 @@ next:
 }
 
 /**
+ * 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);
+
+   printf_ln(_("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."));
+   exit(128);
+   }
+
+   if (unmerged_cache()) {
+   printf_ln(_("You still have unmerged paths in your index.\n"
+   "Did you forget to use 'git add'?"));
+   exit(128);
+   }
+
+   do_commit(state);
+
+   am_next(state);
+   am_run(state);
+}
+
+/**
  * parse_options() callback that validates and sets opt->value to the
  * PATCH_FORMAT_* enum value corresponding to `arg`.
  */
@@ -828,13 +873,20 @@ static int parse_opt_patchformat(const struct option 
*opt, const char *arg, int
return 0;
 }
 
+enum resume_mode {
+   RESUME_FALSE = 0,
+   RESUME_RESOLVED
+};
+
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
struct am_state state;
int patch_format = PATCH_FORMAT_UNKNOWN;
+   enum resume_mode resume = RESUME_FALSE;
 
const char * const usage[] = {
N_("git am [options] [(|)...]"),
+   N_("git am [options] --continue"),
NULL
};
 
@@ -842,6 +894,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)
OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
+   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_END()
};
 
@@ -875,6 +933,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
struct argv_array paths = ARGV_ARRAY_INIT;
int i;
 
+   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]);
@@ -887,7 +948,16 @@ int cmd_am(int argc, const char **argv, const char *prefix)
argv_array_clear(&paths);
}
 
-   am_run(&state);
+   switch (resume) {
+   case RESUME_FALSE:
+   am_run(&state);
+   break;
+   

[PATCH v6 22/45] builtin-am: bypass git-mailinfo when --rebasing

2015-07-19 Thread Paul Tan
Since 5e835ca (rebase: do not munge commit log message, 2008-04-16),
git am --rebasing no longer gets the commit log message from the patch,
but reads it directly from the commit identified by the "From " header
line.

Since 43c2325 (am: use get_author_ident_from_commit instead of mailinfo
when rebasing, 2010-06-16), git am --rebasing also gets the author name,
email and date directly from the commit.

Since 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26), git am
--rebasing does not use git-mailinfo to get the patch body, but rather
generates it directly from the commit itself.

The above 3 commits introduced a separate parse_mail() code path in
git-am.sh's --rebasing mode that bypasses git-mailinfo. Re-implement
this code path in builtin/am.c as parse_mail_rebase().

Signed-off-by: Paul Tan 
---
 builtin/am.c | 134 ++-
 1 file changed, 133 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 3107aba..01f01bf 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -21,6 +21,8 @@
 #include "sequencer.h"
 #include "revision.h"
 #include "merge-recursive.h"
+#include "revision.h"
+#include "log-tree.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -790,6 +792,129 @@ finish:
 }
 
 /**
+ * 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_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

[PATCH v6 08/45] builtin-am: apply patch with git-apply

2015-07-19 Thread Paul Tan
Implement applying the patch to the index using git-apply.

If a file is unchanged but stat-dirty, git-apply may erroneously fail to
apply patches, thinking that they conflict with a dirty working tree.

As such, since 2a6f08a (am: refresh the index at start and --resolved,
2011-08-15), git-am will refresh the index before applying patches.
Re-implement this behavior.

Helped-by: Junio C Hamano 
Signed-off-by: Paul Tan 
---

Notes:
v6

* refresh_and_write_cache(): use a heap-allocated lock_file instead of a
  static-allocated one to allow the function to be reentrant.

* refresh_and_write_cache(): remove the rollback_lock_file() -- since we
  ensure that the write_locked_index() succeeds there is no need to call
  it.

 builtin/am.c | 72 +++-
 1 file changed, 71 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 63f0fa4..1f198e4 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -10,6 +10,7 @@
 #include "dir.h"
 #include "run-command.h"
 #include "quote.h"
+#include "lockfile.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -42,6 +43,14 @@ static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
return 0;
 }
 
+/**
+ * Returns the length of the first line of msg.
+ */
+static int linelen(const char *msg)
+{
+   return strchrnul(msg, '\n') - msg;
+}
+
 enum patch_format {
PATCH_FORMAT_UNKNOWN = 0,
PATCH_FORMAT_MBOX
@@ -540,6 +549,19 @@ static const char *msgnum(const struct am_state *state)
 }
 
 /**
+ * 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"));
+}
+
+/**
  * 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
@@ -629,10 +651,35 @@ finish:
 }
 
 /**
+ * Applies current patch with git-apply. Returns 0 on success, -1 otherwise.
+ */
+static int run_apply(const struct am_state *state)
+{
+   struct child_process cp = CHILD_PROCESS_INIT;
+
+   cp.git_cmd = 1;
+
+   argv_array_push(&cp.args, "apply");
+   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();
+
+   return 0;
+}
+
+/**
  * Applies all queued mail.
  */
 static void am_run(struct am_state *state)
 {
+   refresh_and_write_cache();
+
while (state->cur <= state->last) {
const char *mail = am_path(state, msgnum(state));
 
@@ -645,7 +692,27 @@ static void am_run(struct am_state *state)
write_author_script(state);
write_commit_msg(state);
 
-   /* NEEDSWORK: Patch application not implemented yet */
+   printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg);
+
+   if (run_apply(state) < 0) {
+   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"));
+
+   exit(128);
+   }
+
+   /*
+* NEEDSWORK: After the patch has been applied to the index
+* with git-apply, we need to make commit as well.
+*/
 
 next:
am_next(state);
@@ -707,6 +774,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
argc = parse_options(argc, argv, prefix, options, usage, 0);
 
+   if (read_index_preload(&the_index, NULL) < 0)
+   die(_("failed to read the index"));
+
if (am_in_progress(&state))
am_load(&state);
else {
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 03/45] builtin-am: implement skeletal builtin am

2015-07-19 Thread Paul Tan
For the purpose of rewriting git-am.sh into a C builtin, implement a
skeletal builtin/am.c that redirects to $GIT_EXEC_PATH/git-am if the
environment variable _GIT_USE_BUILTIN_AM is not defined. Since in the
Makefile git-am.sh takes precedence over builtin/am.c,
$GIT_EXEC_PATH/git-am will contain the shell script git-am.sh, and thus
this allows us to fall back on the functional git-am.sh when running the
test suite for tests that depend on a working git-am implementation.

Since git-am.sh cannot handle any environment modifications by
setup_git_directory(), "am" is declared with no setup flags in git.c. On
the other hand, to re-implement git-am.sh in builtin/am.c, we need to
run all the git dir and work tree setup logic that git.c typically does
for us. As such, we work around this temporarily by copying the logic in
git.c's run_builtin(), which is roughly:

prefix = setup_git_directory();
trace_repo_setup(prefix);
setup_work_tree();

This redirection should be removed when all the features of git-am.sh
have been re-implemented in builtin/am.c.

Helped-by: Junio C Hamano 
Signed-off-by: Paul Tan 
---
 Makefile |  1 +
 builtin.h|  1 +
 builtin/am.c | 29 +
 git.c|  6 ++
 4 files changed, 37 insertions(+)
 create mode 100644 builtin/am.c

diff --git a/Makefile b/Makefile
index c6c28bc..f88642a 100644
--- a/Makefile
+++ b/Makefile
@@ -812,6 +812,7 @@ LIB_OBJS += xdiff-interface.o
 LIB_OBJS += zlib.o
 
 BUILTIN_OBJS += builtin/add.o
+BUILTIN_OBJS += builtin/am.o
 BUILTIN_OBJS += builtin/annotate.o
 BUILTIN_OBJS += builtin/apply.o
 BUILTIN_OBJS += builtin/archive.o
diff --git a/builtin.h b/builtin.h
index 839483d..79aaf0a 100644
--- a/builtin.h
+++ b/builtin.h
@@ -30,6 +30,7 @@ extern int textconv_object(const char *path, unsigned mode, 
const unsigned char
 extern int is_builtin(const char *s);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
+extern int cmd_am(int argc, const char **argv, const char *prefix);
 extern int cmd_annotate(int argc, const char **argv, const char *prefix);
 extern int cmd_apply(int argc, const char **argv, const char *prefix);
 extern int cmd_archive(int argc, const char **argv, const char *prefix);
diff --git a/builtin/am.c b/builtin/am.c
new file mode 100644
index 000..fd32caf
--- /dev/null
+++ b/builtin/am.c
@@ -0,0 +1,29 @@
+/*
+ * Builtin "git am"
+ *
+ * Based on git-am.sh by Junio C Hamano.
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+
+int cmd_am(int argc, const char **argv, const char *prefix)
+{
+
+   /*
+* NEEDSWORK: Once all the features of git-am.sh have been
+* re-implemented in builtin/am.c, this preamble can be removed.
+*/
+   if (!getenv("_GIT_USE_BUILTIN_AM")) {
+   const char *path = mkpath("%s/git-am", git_exec_path());
+
+   if (sane_execvp(path, (char **)argv) < 0)
+   die_errno("could not exec %s", path);
+   } else {
+   prefix = setup_git_directory();
+   trace_repo_setup(prefix);
+   setup_work_tree();
+   }
+
+   return 0;
+}
diff --git a/git.c b/git.c
index 55c327c..38d9ad5 100644
--- a/git.c
+++ b/git.c
@@ -370,6 +370,12 @@ static int run_builtin(struct cmd_struct *p, int argc, 
const char **argv)
 
 static struct cmd_struct commands[] = {
{ "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
+   /*
+* NEEDSWORK: Once the redirection to git-am.sh in builtin/am.c has
+* been removed, this entry should be changed to
+* RUN_SETUP | NEED_WORK_TREE
+*/
+   { "am", cmd_am },
{ "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive },
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 05/45] builtin-am: split out mbox/maildir patches with git-mailsplit

2015-07-19 Thread Paul Tan
git-am.sh supports mbox, stgit and mercurial patches. Re-implement
support for splitting out mbox/maildirs using git-mailsplit, while also
implementing the framework required to support other patch formats in
the future.

Re-implement support for the --patch-format option (since a5a6755
(git-am foreign patch support: introduce patch_format, 2009-05-27)) to
allow the user to choose between the different patch formats.

Helped-by: Junio C Hamano 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 107 +--
 1 file changed, 104 insertions(+), 3 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index ac172c4..5f3c131 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -8,6 +8,12 @@
 #include "exec_cmd.h"
 #include "parse-options.h"
 #include "dir.h"
+#include "run-command.h"
+
+enum patch_format {
+   PATCH_FORMAT_UNKNOWN = 0,
+   PATCH_FORMAT_MBOX
+};
 
 struct am_state {
/* state directory path */
@@ -16,6 +22,9 @@ struct am_state {
/* current and last patch numbers, 1-indexed */
int cur;
int last;
+
+   /* number of digits in patch filename */
+   int prec;
 };
 
 /**
@@ -28,6 +37,8 @@ static void am_state_init(struct am_state *state, const char 
*dir)
 
assert(dir);
state->dir = xstrdup(dir);
+
+   state->prec = 4;
 }
 
 /**
@@ -117,13 +128,71 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * 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)
+{
+   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");
+   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;
+}
+
+/**
+ * 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.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail(struct am_state *state, enum patch_format patch_format,
+   const char **paths)
+{
+   switch (patch_format) {
+   case PATCH_FORMAT_MBOX:
+   return split_mail_mbox(state, paths);
+   default:
+   die("BUG: invalid patch_format");
+   }
+   return -1;
+}
+
+/**
  * Setup a new am session for applying patches
  */
-static void am_setup(struct am_state *state)
+static void am_setup(struct am_state *state, enum patch_format patch_format,
+   const char **paths)
 {
if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
die_errno(_("failed to create directory '%s'"), state->dir);
 
+   if (split_mail(state, patch_format, paths) < 0) {
+   am_destroy(state);
+   die(_("Failed to split patches."));
+   }
+
/*
 * NOTE: Since the "next" and "last" files determine if an am_state
 * session is in progress, they should be written last.
@@ -159,9 +228,25 @@ static void am_run(struct am_state *state)
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
+   return error(_("Invalid value for --patch-format: %s"), arg);
+   return 0;
+}
+
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
struct am_state state;
+   int patch_format = PATCH_FORMAT_UNKNOWN;
 
const char * const usage[] = {
N_("git am [options] [(|)...]"),
@@ -169,6 +254,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
};
 
struct option options[] = {

[PATCH v6 06/45] builtin-am: auto-detect mbox patches

2015-07-19 Thread Paul Tan
Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh is able to autodetect mbox, stgit and
mercurial patches through heuristics.

Re-implement support for autodetecting mbox/maildir files in
builtin/am.c.

RFC 2822 requires that lines are terminated by "\r\n". To support this,
implement strbuf_getline_crlf(), which will remove both '\n' and "\r\n"
from the end of the line.

Helped-by: Junio C Hamano 
Helped-by: Eric Sunshine 
Helped-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 109 +++
 1 file changed, 109 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 5f3c131..c12566a 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -10,6 +10,21 @@
 #include "dir.h"
 #include "run-command.h"
 
+/**
+ * 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;
+}
+
 enum patch_format {
PATCH_FORMAT_UNKNOWN = 0,
PATCH_FORMAT_MBOX
@@ -128,6 +143,92 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * 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(®ex, 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(®ex, sb.buf, 0, NULL, 0)) {
+   ret = 0;
+   goto done;
+   }
+   }
+
+done:
+   regfree(®ex);
+   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;
+   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 (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.
  */
@@ -185,6 +286,14 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
 static void am_setup(struct am_state *state, enum patch_format patch_format,
const char **paths)
 {
+   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);
 
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 07/45] builtin-am: extract patch and commit info with git-mailinfo

2015-07-19 Thread Paul Tan
For the purpose of applying the patch and committing the results,
implement extracting the patch data, commit message and authorship from
an e-mail message using git-mailinfo.

git-mailinfo is run as a separate process, but ideally in the future,
we should be be able to access its functionality directly without
spawning a new process.

Helped-by: Junio C Hamano 
Helped-by: Jeff King 
Helped-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 317 +++
 1 file changed, 317 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index c12566a..63f0fa4 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -9,6 +9,23 @@
 #include "parse-options.h"
 #include "dir.h"
 #include "run-command.h"
+#include "quote.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.
@@ -38,6 +55,13 @@ struct am_state {
int cur;
int last;
 
+   /* commit metadata and message */
+   char *author_name;
+   char *author_email;
+   char *author_date;
+   char *msg;
+   size_t msg_len;
+
/* number of digits in patch filename */
int prec;
 };
@@ -62,6 +86,10 @@ static void am_state_init(struct am_state *state, const char 
*dir)
 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);
 }
 
 /**
@@ -112,6 +140,161 @@ static int read_state_file(struct strbuf *sb, const 
struct am_state *state,
 }
 
 /**
+ * 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

[PATCH v6 02/45] wrapper: implement xfopen()

2015-07-19 Thread Paul Tan
A common usage pattern of fopen() is to check if it succeeded, and die()
if it failed:

FILE *fp = fopen(path, "w");
if (!fp)
die_errno(_("could not open '%s' for writing"), path);

Implement a wrapper function xfopen() for the above, so that we can save
a few lines of code and make the die() messages consistent.

Helped-by: Jeff King 
Helped-by: Johannes Schindelin 
Helped-by: Junio C Hamano 
Signed-off-by: Paul Tan 
---
 git-compat-util.h |  1 +
 wrapper.c | 21 +
 2 files changed, 22 insertions(+)

diff --git a/git-compat-util.h b/git-compat-util.h
index e168dfd..392da79 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -722,6 +722,7 @@ extern ssize_t xread(int fd, void *buf, size_t len);
 extern ssize_t xwrite(int fd, const void *buf, size_t len);
 extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
 extern int xdup(int fd);
+extern FILE *xfopen(const char *path, const char *mode);
 extern FILE *xfdopen(int fd, const char *mode);
 extern int xmkstemp(char *template);
 extern int xmkstemp_mode(char *template, int mode);
diff --git a/wrapper.c b/wrapper.c
index 0a4502d..e451463 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -346,6 +346,27 @@ int xdup(int fd)
return ret;
 }
 
+/**
+ * xfopen() is the same as fopen(), but it die()s if the fopen() fails.
+ */
+FILE *xfopen(const char *path, const char *mode)
+{
+   for (;;) {
+   FILE *fp = fopen(path, mode);
+   if (fp)
+   return fp;
+   if (errno == EINTR)
+   continue;
+
+   if (*mode && mode[1] == '+')
+   die_errno(_("could not open '%s' for reading and 
writing"), path);
+   else if (*mode == 'w' || *mode == 'a')
+   die_errno(_("could not open '%s' for writing"), path);
+   else
+   die_errno(_("could not open '%s' for reading"), path);
+   }
+}
+
 FILE *xfdopen(int fd, const char *mode)
 {
FILE *stream = fdopen(fd, mode);
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 01/45] wrapper: implement xopen()

2015-07-19 Thread Paul Tan
A common usage pattern of open() is to check if it was successful, and
die() if it was not:

int fd = open(path, O_WRONLY | O_CREAT, 0777);
if (fd < 0)
die_errno(_("Could not open '%s' for writing."), path);

Implement a wrapper function xopen() that does the above so that we can
save a few lines of code, and make the die() messages consistent.

Helped-by: Torsten Bögershausen 
Helped-by: Jeff King 
Helped-by: Johannes Schindelin 
Helped-by: Junio C Hamano 
Signed-off-by: Paul Tan 
---
 git-compat-util.h |  1 +
 wrapper.c | 35 +++
 2 files changed, 36 insertions(+)

diff --git a/git-compat-util.h b/git-compat-util.h
index c6d391f..e168dfd 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -717,6 +717,7 @@ extern void *xrealloc(void *ptr, size_t size);
 extern void *xcalloc(size_t nmemb, size_t size);
 extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, 
off_t offset);
 extern void *xmmap_gently(void *start, size_t length, int prot, int flags, int 
fd, off_t offset);
+extern int xopen(const char *path, int flags, ...);
 extern ssize_t xread(int fd, void *buf, size_t len);
 extern ssize_t xwrite(int fd, const void *buf, size_t len);
 extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
diff --git a/wrapper.c b/wrapper.c
index ff49807..0a4502d 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -189,6 +189,41 @@ void *xcalloc(size_t nmemb, size_t size)
 # endif
 #endif
 
+/**
+ * xopen() is the same as open(), but it die()s if the open() fails.
+ */
+int xopen(const char *path, int oflag, ...)
+{
+   mode_t mode = 0;
+   va_list ap;
+
+   /*
+* va_arg() will have undefined behavior if the specified type is not
+* compatible with the argument type. Since integers are promoted to
+* ints, we fetch the next argument as an int, and then cast it to a
+* mode_t to avoid undefined behavior.
+*/
+   va_start(ap, oflag);
+   if (oflag & O_CREAT)
+   mode = va_arg(ap, int);
+   va_end(ap);
+
+   for (;;) {
+   int fd = open(path, oflag, mode);
+   if (fd >= 0)
+   return fd;
+   if (errno == EINTR)
+   continue;
+
+   if ((oflag & O_RDWR) == O_RDWR)
+   die_errno(_("could not open '%s' for reading and 
writing"), path);
+   else if ((oflag & O_WRONLY) == O_WRONLY)
+   die_errno(_("could not open '%s' for writing"), path);
+   else
+   die_errno(_("could not open '%s' for reading"), path);
+   }
+}
+
 /*
  * xread() is the same a read(), but it automatically restarts read()
  * operations with a recoverable error (EAGAIN and EINTR). xread()
-- 
2.5.0.rc2.110.gb39b692

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 04/45] builtin-am: implement patch queue mechanism

2015-07-19 Thread Paul Tan
git-am applies a series of patches. If the process terminates
abnormally, we want to be able to resume applying the series of patches.
This requires the session state to be saved in a persistent location.

Implement the mechanism of a "patch queue", represented by 2 integers --
the index of the current patch we are applying and the index of the last
patch, as well as its lifecycle through the following functions:

* am_setup(), which will set up the state directory
  $GIT_DIR/rebase-apply. As such, even if the process exits abnormally,
  the last-known state will still persist.

* am_load(), which is called if there is an am session in
  progress, to load the last known state from the state directory so we
  can resume applying patches.

* am_run(), which will do the actual patch application. After applying a
  patch, it calls am_next() to increment the current patch index. The
  logic for applying and committing a patch is not implemented yet.

* am_destroy(), which is finally called when we successfully applied all
  the patches in the queue, to clean up by removing the state directory
  and its contents.

Helped-by: Junio C Hamano 
Helped-by: Stefan Beller 
Helped-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 builtin/am.c | 177 +++
 1 file changed, 177 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index fd32caf..ac172c4 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -6,9 +6,171 @@
 #include "cache.h"
 #include "builtin.h"
 #include "exec_cmd.h"
+#include "parse-options.h"
+#include "dir.h"
+
+struct am_state {
+   /* state directory path */
+   char *dir;
+
+   /* current and last patch numbers, 1-indexed */
+   int cur;
+   int last;
+};
+
+/**
+ * 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)
+{
+   memset(state, 0, sizeof(*state));
+
+   assert(dir);
+   state->dir = xstrdup(dir);
+}
+
+/**
+ * Releases memory allocated by an am_state.
+ */
+static void am_state_release(struct am_state *state)
+{
+   free(state->dir);
+}
+
+/**
+ * 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);
+}
+
+/**
+ * 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));
+}
+
+/**
+ * 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);
+
+   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);
+}
+
+/**
+ * Setup a new am session for applying patches
+ */
+static void am_setup(struct am_state *state)
+{
+   if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
+   die_errno(_("failed to create directory '%s'"), state->dir);
+
+   /*
+* NOTE: Since the "next" and "last" files determine if an am_state
+ 

[PATCH v6 00/45] Make git-am a builtin

2015-07-19 Thread Paul Tan
This patch series depends on pt/pull-builtin.

This is a re-roll of [v5]. The changes are as follows:

* Compacted the struct am_state declaration, so there are no ugly newlines
  between every field.

* Removed unnecessary assert()s.

* Simplified "if (x) free(x)" to just free(x)

* Only alloc memory for lock_file just before calling hold_locked_index() to
  prevent memory leaks.

* Added a patch to re-implement 271440e (git-am: make it easier after fixing up
  an unapplicable patch., 2005-10-25).

Thanks Junio and Stefan for the reviews last round. Interdiff below.

Previous versions:

[WIP v1] http://thread.gmane.org/gmane.comp.version-control.git/270048
[WIP v2] http://thread.gmane.org/gmane.comp.version-control.git/271381
[WIP v3] http://thread.gmane.org/gmane.comp.version-control.git/271967
[v4] http://thread.gmane.org/gmane.comp.version-control.git/272876
[v5] http://thread.gmane.org/gmane.comp.version-control.git/273520

git-am is a commonly used command for applying a series of patches from a
mailbox to the current branch. Currently, it is implemented by the shell script
git-am.sh. However, compared to C, shell scripts have certain deficiencies:
they need to spawn a lot of processes, introduce a lot of dependencies and
cannot take advantage of git's internal caches.

This patch series rewrites git-am.sh into C builtin/am.c, and is part of my
GSoC project to rewrite git-pull and git-am into C builtins[1].

[1] https://gist.github.com/pyokagan/1b7b0d1f4dab6ba3cef1


Paul Tan (45):
  wrapper: implement xopen()
  wrapper: implement xfopen()
  builtin-am: implement skeletal builtin am
  builtin-am: implement patch queue mechanism
  builtin-am: split out mbox/maildir patches with git-mailsplit
  builtin-am: auto-detect mbox patches
  builtin-am: extract patch and commit info with git-mailinfo
  builtin-am: apply patch with git-apply
  builtin-am: implement committing applied patch
  builtin-am: refuse to apply patches if index is dirty
  builtin-am: implement --resolved/--continue
  builtin-am: don't parse mail when resuming
  builtin-am: implement --skip
  builtin-am: implement --abort
  builtin-am: reject patches when there's a session in progress
  builtin-am: implement -q/--quiet
  builtin-am: exit with user friendly message on failure
  builtin-am: implement -s/--signoff
  cache-tree: introduce write_index_as_tree()
  builtin-am: implement --3way, am.threeWay
  builtin-am: implement --rebasing mode
  builtin-am: bypass git-mailinfo when --rebasing
  builtin-am: handle stray state directory
  builtin-am: implement -u/--utf8
  builtin-am: implement -k/--keep, --keep-non-patch
  builtin-am: implement --[no-]message-id, am.messageid
  builtin-am: support --keep-cr, am.keepcr
  builtin-am: implement --[no-]scissors
  builtin-am: pass git-apply's options to git-apply
  builtin-am: implement --ignore-date
  builtin-am: implement --committer-date-is-author-date
  builtin-am: implement -S/--gpg-sign, commit.gpgsign
  builtin-am: invoke post-rewrite hook
  builtin-am: support automatic notes copying
  builtin-am: invoke applypatch-msg hook
  builtin-am: invoke pre-applypatch hook
  builtin-am: invoke post-applypatch hook
  builtin-am: rerere support
  builtin-am: support and auto-detect StGit patches
  builtin-am: support and auto-detect StGit series files
  builtin-am: support and auto-detect mercurial patches
  builtin-am: implement -i/--interactive
  builtin-am: implement legacy -b/--binary option
  builtin-am: check for valid committer ident
  builtin-am: remove redirection to git-am.sh

 Makefile|2 +-
 builtin.h   |1 +
 builtin/am.c| 2320 +++
 cache-tree.c|   29 +-
 cache-tree.h|1 +
 git-am.sh => contrib/examples/git-am.sh |0
 git-compat-util.h   |2 +
 git.c   |1 +
 wrapper.c   |   56 +
 9 files changed, 2399 insertions(+), 13 deletions(-)
 create mode 100644 builtin/am.c
 rename git-am.sh => contrib/examples/git-am.sh (100%)

diff --git a/builtin/am.c b/builtin/am.c
index c548129..1116304 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -119,38 +119,21 @@ struct am_state {
/* number of digits in patch filename */
int prec;
 
+   /* various operating modes and command line options */
int interactive;
-
int threeway;
-
int quiet;
-
int append_signoff;
-
int utf8;
-
-   /* one of the enum keep_type values */
-   int keep;
-
-   /* pass -m flag to git-mailinfo */
+   int keep; /* enum keep_type */
int message_id;
-
-   /* one of the enum scissors_type values */
-   int scissors;
-
+   int scissors; /* enum scissors_type */
struct argv_array git_apply_opts;
-
-   /* override error message when p

[PATCH v3 09/12] t4150: am with post-applypatch hook

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh will invoke the post-applypatch hook after the patch is
applied and a commit is made. The exit code of the hook is ignored.

Add tests for this hook.

Helped-by: Junio C Hamano 
Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t4150-am.sh | 38 ++
 1 file changed, 38 insertions(+)

diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 957c63c..7494240 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -267,6 +267,44 @@ test_expect_success 'am with failing pre-applypatch hook' '
test_cmp_rev first HEAD
 '
 
+test_expect_success 'am with post-applypatch hook' '
+   test_when_finished "rm -f .git/hooks/post-applypatch" &&
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout first &&
+   mkdir -p .git/hooks &&
+   write_script .git/hooks/post-applypatch <<-\EOF &&
+   git rev-parse HEAD >head.actual
+   git diff second >diff.actual
+   exit 0
+   EOF
+   git am patch1 &&
+   test_path_is_missing .git/rebase-apply &&
+   test_cmp_rev second HEAD &&
+   git rev-parse second >head.expected &&
+   test_cmp head.expected head.actual &&
+   git diff second >diff.expected &&
+   test_cmp diff.expected diff.actual
+'
+
+test_expect_success 'am with failing post-applypatch hook' '
+   test_when_finished "rm -f .git/hooks/post-applypatch" &&
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout first &&
+   mkdir -p .git/hooks &&
+   write_script .git/hooks/post-applypatch <<-\EOF &&
+   git rev-parse HEAD >head.actual
+   exit 1
+   EOF
+   git am patch1 &&
+   test_path_is_missing .git/rebase-apply &&
+   git diff --exit-code second &&
+   test_cmp_rev second HEAD &&
+   git rev-parse second >head.expected &&
+   test_cmp head.expected head.actual
+'
+
 test_expect_success 'setup: new author and committer' '
GIT_AUTHOR_NAME="Another Thor" &&
GIT_AUTHOR_EMAIL="a.t...@example.com" &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 11/12] t3418: non-interactive rebase --continue with rerere enabled

2015-07-19 Thread Paul Tan
Since 8389b52 (git-rerere: reuse recorded resolve., 2006-01-28), git-am
will call git-rerere to re-use recorded merge conflict resolutions if
any occur in a threeway merge.

Add a test to ensure that git-rerere is called by git-am (which handles
the non-interactive rebase).

Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t3418-rebase-continue.sh | 19 +++
 1 file changed, 19 insertions(+)

diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index 2680375..4428b90 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -40,6 +40,25 @@ test_expect_success 'non-interactive rebase --continue works 
with touched file'
git rebase --continue
 '
 
+test_expect_success 'non-interactive rebase --continue with rerere enabled' '
+   test_config rerere.enabled true &&
+   test_when_finished "test_might_fail git rebase --abort" &&
+   git reset --hard commit-new-file-F2-on-topic-branch &&
+   git checkout master &&
+   rm -fr .git/rebase-* &&
+
+   test_must_fail git rebase --onto master master topic &&
+   echo "Resolved" >F2 &&
+   git add F2 &&
+   cp F2 F2.expected &&
+   git rebase --continue &&
+
+   git reset --hard commit-new-file-F2-on-topic-branch &&
+   git checkout master &&
+   test_must_fail git rebase --onto master master topic &&
+   test_cmp F2.expected F2
+'
+
 test_expect_success 'rebase --continue can not be used with other options' '
test_must_fail git rebase -v --continue &&
test_must_fail git rebase --continue -v
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 10/12] t4150: tests for am --[no-]scissors

2015-07-19 Thread Paul Tan
Since 017678b (am/mailinfo: Disable scissors processing by default,
2009-08-26), git-am supported the --[no-]scissors option, passing it to
git-mailinfo.

Add tests to ensure that git-am will pass the --scissors option to
git-mailinfo, and that --no-scissors will override the configuration
setting of mailinfo.scissors.

Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t4150-am.sh | 48 
 1 file changed, 48 insertions(+)

diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 7494240..67fbf0e 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -67,6 +67,19 @@ test_expect_success 'setup: messages' '
 
EOF
 
+   cat >scissors-msg <<-\EOF &&
+   Test git-am with scissors line
+
+   This line should be included in the commit message.
+   EOF
+
+   cat - scissors-msg >no-scissors-msg <<-\EOF &&
+   This line should not be included in the commit message with --scissors 
enabled.
+
+- - >8 - - remove everything above this line - - >8 - -
+
+   EOF
+
signoff="Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
 '
 
@@ -105,6 +118,20 @@ test_expect_success setup '
git format-patch --stdout first | sed -e "1d"
} > patch1-ws.eml &&
 
+   echo scissors-file >scissors-file &&
+   git add scissors-file &&
+   git commit -F scissors-msg &&
+   git tag scissors &&
+   git format-patch --stdout scissors^ >scissors-patch.eml &&
+   git reset --hard HEAD^ &&
+
+   echo no-scissors-file >no-scissors-file &&
+   git add no-scissors-file &&
+   git commit -F no-scissors-msg &&
+   git tag no-scissors &&
+   git format-patch --stdout no-scissors^ >no-scissors-patch.eml &&
+   git reset --hard HEAD^ &&
+
sed -n -e "3,\$p" msg >file &&
git add file &&
test_tick &&
@@ -305,6 +332,27 @@ test_expect_success 'am with failing post-applypatch hook' 
'
test_cmp head.expected head.actual
 '
 
+test_expect_success 'am --scissors cuts the message at the scissors line' '
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout second &&
+   git am --scissors scissors-patch.eml &&
+   test_path_is_missing .git/rebase-apply &&
+   git diff --exit-code scissors &&
+   test_cmp_rev scissors HEAD
+'
+
+test_expect_success 'am --no-scissors overrides mailinfo.scissors' '
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout second &&
+   test_config mailinfo.scissors true &&
+   git am --no-scissors no-scissors-patch.eml &&
+   test_path_is_missing .git/rebase-apply &&
+   git diff --exit-code no-scissors &&
+   test_cmp_rev no-scissors HEAD
+'
+
 test_expect_success 'setup: new author and committer' '
GIT_AUTHOR_NAME="Another Thor" &&
GIT_AUTHOR_EMAIL="a.t...@example.com" &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 12/12] t3901: test git-am encoding conversion

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
supported the --utf8 and --no-utf8 options, and if set, would pass the
-u flag and the -k flag respectively.

git mailinfo -u will re-code the commit log message and authorship info
in the charset specified by i18n.commitencoding setting, while
git mailinfo -n will disable the re-coding.

Since d84029b (--utf8 is now default for 'git-am', 2007-01-08), --utf8
is set by default in git-am.

Add various encoding conversion tests to t3901 to test git-mailinfo's
encoding conversion. In addition, add a test for --no-utf8 to check that
no encoding conversion will occur if that option is set.

Helped-by: Johannes Sixt 
Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---

Notes:
v3

* Added !MINGW prereq for the other test case that uses ISO-8859-1
  encoded environment vars

 t/t3901-i18n-patch.sh | 62 +++
 1 file changed, 62 insertions(+)

diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index 75cf3ff..509084e 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -251,4 +251,66 @@ test_expect_success 'rebase --merge (L/U)' '
check_encoding 2 8859
 '
 
+test_expect_success 'am (U/U)' '
+   # Apply UTF-8 patches with UTF-8 commitencoding
+   git config i18n.commitencoding UTF-8 &&
+   . "$TEST_DIRECTORY"/t3901-utf8.txt &&
+
+   git reset --hard master &&
+   git am out-u1 out-u2 &&
+
+   check_encoding 2
+'
+
+test_expect_success !MINGW 'am (L/L)' '
+   # Apply ISO-8859-1 patches with ISO-8859-1 commitencoding
+   git config i18n.commitencoding ISO8859-1 &&
+   . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+
+   git reset --hard master &&
+   git am out-l1 out-l2 &&
+
+   check_encoding 2 8859
+'
+
+test_expect_success 'am (U/L)' '
+   # Apply ISO-8859-1 patches with UTF-8 commitencoding
+   git config i18n.commitencoding UTF-8 &&
+   . "$TEST_DIRECTORY"/t3901-utf8.txt &&
+   git reset --hard master &&
+
+   # am specifies --utf8 by default.
+   git am out-l1 out-l2 &&
+
+   check_encoding 2
+'
+
+test_expect_success 'am --no-utf8 (U/L)' '
+   # Apply ISO-8859-1 patches with UTF-8 commitencoding
+   git config i18n.commitencoding UTF-8 &&
+   . "$TEST_DIRECTORY"/t3901-utf8.txt &&
+
+   git reset --hard master &&
+   git am --no-utf8 out-l1 out-l2 2>err &&
+
+   # commit-tree will warn that the commit message does not contain valid 
UTF-8
+   # as mailinfo did not convert it
+   grep "did not conform" err &&
+
+   check_encoding 2
+'
+
+test_expect_success !MINGW 'am (L/U)' '
+   # Apply UTF-8 patches with ISO-8859-1 commitencoding
+   git config i18n.commitencoding ISO8859-1 &&
+   . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+
+   git reset --hard master &&
+   # mailinfo will re-code the commit message to the charset specified by
+   # i18n.commitencoding
+   git am out-u1 out-u2 &&
+
+   check_encoding 2 8859
+'
+
 test_done
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 02/12] t4150: am fails if index is dirty

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
will ensure that the index is clean before applying the patch. This is
to prevent changes unrelated to the patch from being committed.

Add a test for this check.

Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t4150-am.sh | 12 
 1 file changed, 12 insertions(+)

diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 264f53f..a85e06a 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -154,6 +154,18 @@ test_expect_success 'am applies patch correctly' '
test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
 '
 
+test_expect_success 'am fails if index is dirty' '
+   test_when_finished "rm -f dirtyfile" &&
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout first &&
+   echo dirtyfile >dirtyfile &&
+   git add dirtyfile &&
+   test_must_fail git am patch1 &&
+   test_path_is_dir .git/rebase-apply &&
+   test_cmp_rev first HEAD
+'
+
 test_expect_success 'am applies patch e-mail not in a mbox' '
rm -fr .git/rebase-apply &&
git reset --hard &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 07/12] t4150: am with applypatch-msg hook

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
will invoke the applypatch-msg hooks just after extracting the patch
message. If the applypatch-msg hook exits with a non-zero status, git-am
abort before even applying the patch to the index.

Add tests for this hook.

Helped-by: Junio C Hamano 
Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t4150-am.sh | 35 +++
 1 file changed, 35 insertions(+)

diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index c1ec4d5..7878e32 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -199,6 +199,41 @@ test_expect_success 'am applies patch e-mail with 
preceding whitespace' '
test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
 '
 
+test_expect_success 'am with applypatch-msg hook' '
+   test_when_finished "rm -f .git/hooks/applypatch-msg" &&
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout first &&
+   mkdir -p .git/hooks &&
+   write_script .git/hooks/applypatch-msg <<-\EOF &&
+   cat "$1" >actual-msg &&
+   echo hook-message >"$1"
+   EOF
+   git am patch1 &&
+   test_path_is_missing .git/rebase-apply &&
+   git diff --exit-code second &&
+   echo hook-message >expected &&
+   git log -1 --format=format:%B >actual &&
+   test_cmp expected actual &&
+   git log -1 --format=format:%B second >expected &&
+   test_cmp expected actual-msg
+'
+
+test_expect_success 'am with failing applypatch-msg hook' '
+   test_when_finished "rm -f .git/hooks/applypatch-msg" &&
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout first &&
+   mkdir -p .git/hooks &&
+   write_script .git/hooks/applypatch-msg <<-\EOF &&
+   exit 1
+   EOF
+   test_must_fail git am patch1 &&
+   test_path_is_dir .git/rebase-apply &&
+   git diff --exit-code first &&
+   test_cmp_rev first HEAD
+'
+
 test_expect_success 'setup: new author and committer' '
GIT_AUTHOR_NAME="Another Thor" &&
GIT_AUTHOR_EMAIL="a.t...@example.com" &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 01/12] t4150: am.messageid really adds the message id

2015-07-19 Thread Paul Tan
Since a078f73 (git-am: add --message-id/--no-message-id, 2014-11-25),
the am.messageid setting determines whether the --message-id option is
set by default.

Add a test for this.

Reviewed-by: Paolo Bonzini 
Signed-off-by: Paul Tan 
---
 t/t4150-am.sh | 12 
 1 file changed, 12 insertions(+)

diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index b822a39..264f53f 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -563,6 +563,18 @@ test_expect_success 'am --message-id really adds the 
message id' '
test_cmp expected actual
 '
 
+test_expect_success 'am.messageid really adds the message id' '
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout HEAD^ &&
+   test_config am.messageid true &&
+   git am patch1.eml &&
+   test_path_is_missing .git/rebase-apply &&
+   git cat-file commit HEAD | tail -n1 >actual &&
+   grep Message-Id patch1.eml >expected &&
+   test_cmp expected actual
+'
+
 test_expect_success 'am --message-id -s signs off after the message id' '
rm -fr .git/rebase-apply &&
git reset --hard &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 05/12] t4150: am --resolved fails if index has no changes

2015-07-19 Thread Paul Tan
Since 6d28644 (git-am: do not allow empty commits by mistake.,
2006-02-23), git-am --resolved will check to see if the index has any
changes to prevent the user from creating an empty commit by mistake.

Add a test for this.

Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t4150-am.sh | 12 
 1 file changed, 12 insertions(+)

diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index c350967..430ae71 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -425,6 +425,18 @@ test_expect_success 'am --resolved works' '
test_cmp expected another
 '
 
+test_expect_success 'am --resolved fails if index has no changes' '
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout lorem2^^ &&
+   test_must_fail git am lorem-move.patch &&
+   test_path_is_dir .git/rebase-apply &&
+   test_cmp_rev lorem2^^ HEAD &&
+   test_must_fail git am --resolved &&
+   test_path_is_dir .git/rebase-apply &&
+   test_cmp_rev lorem2^^ HEAD
+'
+
 test_expect_success 'am takes patches from a Pine mailbox' '
rm -fr .git/rebase-apply &&
git reset --hard &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 08/12] t4150: am with pre-applypatch hook

2015-07-19 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sg will invoke the pre-applypatch hook after applying the patch
to the index, but before a commit is made. Should the hook exit with a
non-zero status, git am will exit.

Add tests for this hook.

Helped-by: Junio C Hamano 
Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t4150-am.sh | 33 +
 1 file changed, 33 insertions(+)

diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 7878e32..957c63c 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -234,6 +234,39 @@ test_expect_success 'am with failing applypatch-msg hook' '
test_cmp_rev first HEAD
 '
 
+test_expect_success 'am with pre-applypatch hook' '
+   test_when_finished "rm -f .git/hooks/pre-applypatch" &&
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout first &&
+   mkdir -p .git/hooks &&
+   write_script .git/hooks/pre-applypatch <<-\EOF &&
+   git diff first >diff.actual
+   exit 0
+   EOF
+   git am patch1 &&
+   test_path_is_missing .git/rebase-apply &&
+   git diff --exit-code second &&
+   test_cmp_rev second HEAD &&
+   git diff first..second >diff.expected &&
+   test_cmp diff.expected diff.actual
+'
+
+test_expect_success 'am with failing pre-applypatch hook' '
+   test_when_finished "rm -f .git/hooks/pre-applypatch" &&
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout first &&
+   mkdir -p .git/hooks &&
+   write_script .git/hooks/pre-applypatch <<-\EOF &&
+   exit 1
+   EOF
+   test_must_fail git am patch1 &&
+   test_path_is_dir .git/rebase-apply &&
+   git diff --exit-code second &&
+   test_cmp_rev first HEAD
+'
+
 test_expect_success 'setup: new author and committer' '
GIT_AUTHOR_NAME="Another Thor" &&
GIT_AUTHOR_EMAIL="a.t...@example.com" &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 03/12] t4151: am --abort will keep dirty index intact

2015-07-19 Thread Paul Tan
Since 7b3b7e3 (am --abort: keep unrelated commits since the last failure
and warn, 2010-12-21), git-am --abort will not touch the index if on the
previous invocation, git-am failed because the index is dirty. This is
to ensure that the user's modifications to the index are not discarded.

Add a test for this.

Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t4151-am-abort.sh | 15 +++
 1 file changed, 15 insertions(+)

diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh
index 833e7b2..05bdc3e 100755
--- a/t/t4151-am-abort.sh
+++ b/t/t4151-am-abort.sh
@@ -95,6 +95,21 @@ test_expect_success 'am --abort will keep the local commits 
intact' '
test_cmp expect actual
 '
 
+test_expect_success 'am --abort will keep dirty index intact' '
+   git reset --hard initial &&
+   echo dirtyfile >dirtyfile &&
+   cp dirtyfile dirtyfile.expected &&
+   git add dirtyfile &&
+   test_must_fail git am 0001-*.patch &&
+   test_cmp_rev initial HEAD &&
+   test_path_is_file dirtyfile &&
+   test_cmp dirtyfile.expected dirtyfile &&
+   git am --abort &&
+   test_cmp_rev initial HEAD &&
+   test_path_is_file dirtyfile &&
+   test_cmp dirtyfile.expected dirtyfile
+'
+
 test_expect_success 'am -3 stops on conflict on unborn branch' '
git checkout -f --orphan orphan &&
git reset &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 04/12] t4150: am refuses patches when paused

2015-07-19 Thread Paul Tan
Since c95b138 (Fix git-am safety checks, 2006-09-15), when there is a
session in progress, git-am will check the command-line arguments and
standard input to ensure that the user does not pass it any patches.

Add a test for this.

Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t4150-am.sh | 14 ++
 1 file changed, 14 insertions(+)

diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index a85e06a..c350967 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -397,6 +397,20 @@ test_expect_success 'am --abort removes a stray directory' 
'
test_path_is_missing .git/rebase-apply
 '
 
+test_expect_success 'am refuses patches when paused' '
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout lorem2^^ &&
+
+   test_must_fail git am lorem-move.patch &&
+   test_path_is_dir .git/rebase-apply &&
+   test_cmp_rev lorem2^^ HEAD &&
+
+   test_must_fail git am expected &&
rm -fr .git/rebase-apply &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 06/12] t4150: am --resolved fails if index has unmerged entries

2015-07-19 Thread Paul Tan
Since c1d1128 (git-am --resolved: more usable error message.,
2006-04-28), git-am --resolved will check to see if there are any
unmerged entries, and will error out with a user-friendly error message
if there are.

Add a test for this.

Reviewed-by: Stefan Beller 
Reviewed-by: Johannes Schindelin 
Signed-off-by: Paul Tan 
---
 t/t4150-am.sh | 13 +
 1 file changed, 13 insertions(+)

diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 430ae71..c1ec4d5 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -437,6 +437,19 @@ test_expect_success 'am --resolved fails if index has no 
changes' '
test_cmp_rev lorem2^^ HEAD
 '
 
+test_expect_success 'am --resolved fails if index has unmerged entries' '
+   rm -fr .git/rebase-apply &&
+   git reset --hard &&
+   git checkout second &&
+   test_must_fail git am -3 lorem-move.patch &&
+   test_path_is_dir .git/rebase-apply &&
+   test_cmp_rev second HEAD &&
+   test_must_fail git am --resolved >err &&
+   test_path_is_dir .git/rebase-apply &&
+   test_cmp_rev second HEAD &&
+   test_i18ngrep "still have unmerged paths" err
+'
+
 test_expect_success 'am takes patches from a Pine mailbox' '
rm -fr .git/rebase-apply &&
git reset --hard &&
-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v3 00/12] Improve git-am test coverage

2015-07-19 Thread Paul Tan
This is a very minor re-roll of [v2] that adds in the missing !MINGW prereq in
t3901.  Thanks Johannes Schindelin and Johannes Sixt for the reviews last
round. Interdiff below.

Previous versions:

[v1] http://thread.gmane.org/gmane.comp.version-control.git/273254
[v2] http://thread.gmane.org/gmane.comp.version-control.git/273507

Increase test coverage of git-am.sh to help prevent regressions that could
arise from the rewrite of git-am.sh to C. This patch series, along with
pt/am-foreign, improved test coverage as measured by kcov from 56.5%[1] to
67.3%[2].

No tests for git-am's interactive mode, though, as test_terminal does not seem
to attach a pseudo-tty to stdin(?), thus making git-am's "test -t 0" check
fail.

This is part of my GSoC project to rewrite git-am.sh to a C builtin[3].

[1] 
http://pyokagan.github.io/git/20150430132408-a75942b//kcov-merged/git-am.eb79278e.html
[2] 
http://pyokagan.github.io/git/20150702173751-2fdae08//kcov-merged/git-am.eb79278e.html
[3] https://gist.github.com/pyokagan/1b7b0d1f4dab6ba3cef1


Paul Tan (12):
  t4150: am.messageid really adds the message id
  t4150: am fails if index is dirty
  t4151: am --abort will keep dirty index intact
  t4150: am refuses patches when paused
  t4150: am --resolved fails if index has no changes
  t4150: am --resolved fails if index has unmerged entries
  t4150: am with applypatch-msg hook
  t4150: am with pre-applypatch hook
  t4150: am with post-applypatch hook
  t4150: tests for am --[no-]scissors
  t3418: non-interactive rebase --continue with rerere enabled
  t3901: test git-am encoding conversion

 t/t3418-rebase-continue.sh |  19 
 t/t3901-i18n-patch.sh  |  62 +
 t/t4150-am.sh  | 217 +
 t/t4151-am-abort.sh|  15 
 4 files changed, 313 insertions(+)

diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index b49bdb7..509084e 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -262,7 +262,7 @@ test_expect_success 'am (U/U)' '
check_encoding 2
 '
 
-test_expect_success 'am (L/L)' '
+test_expect_success !MINGW 'am (L/L)' '
# Apply ISO-8859-1 patches with ISO-8859-1 commitencoding
git config i18n.commitencoding ISO8859-1 &&
. "$TEST_DIRECTORY"/t3901-8859-1.txt &&

-- 
2.5.0.rc2.110.gf2a5fc6

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 12/44] builtin-am: implement --skip

2015-07-18 Thread Paul Tan
On Tue, Jul 14, 2015 at 09:54:10AM -0700, Stefan Beller wrote:
> So what I meant to suggest, was to only allocate the memory if we really need 
> it
> by moving the allocation further down.
> 
> static int clean_index(const unsigned char *head, const unsigned char *remote)
> {
>struct lock_file *lock_file;
> ...
> ... // includes return -1, which would not leak the memory already allocated
> ...
>lock_file = xalloc (...);
>hold_locked_index(lock_file, 1);

Ah, sorry, I misinterpreted your message ><. Thanks for catching this.

I've moved all the lock file memory allocation to just before the
hold_locked_index() calls on my end.

diff --git a/builtin/am.c b/builtin/am.c
index c597325..dc8e862 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1138,13 +1138,12 @@ static const char *msgnum(const struct am_state *state)
  */
 static void refresh_and_write_cache(void)
 {
-   static struct lock_file lock_file;
+   struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
 
-   hold_locked_index(&lock_file, 1);
+   hold_locked_index(lock_file, 1);
refresh_cache(REFRESH_QUIET);
-   if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+   if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
die(_("unable to write index file"));
-   rollback_lock_file(&lock_file);
 }
 
 /**
@@ -1923,13 +1922,14 @@ next:
  */
 static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
 {
-   struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+   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);
@@ -1962,7 +1962,7 @@ static int fast_forward_to(struct tree *head, struct tree 
*remote, int reset)
  */
 static int clean_index(const unsigned char *head, const unsigned char *remote)
 {
-   struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+   struct lock_file *lock_file;
struct tree *head_tree, *remote_tree, *index_tree;
unsigned char index[GIT_SHA1_RAWSZ];
struct pathspec pathspec;
@@ -1992,6 +1992,7 @@ static int clean_index(const unsigned char *head, const 
unsigned char *remote)
 
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)) {

Thanks,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v4 42/44] builtin-am: implement legacy -b/--binary option

2015-07-14 Thread Paul Tan
On Thu, Jul 9, 2015 at 5:17 PM, Erik Faye-Lund  wrote:
> On Jun 29, 2015 10:06 PM, "Stefan Beller"  wrote:
>>
>> On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan  wrote:
>> > The -b/--binary option was initially implemented in 087b674 (git-am:
>> > --binary; document --resume and --binary., 2005-11-16). The option will
>> > pass the --binary flag to git-apply to allow it to apply binary patches.
>> >
>> > However, in 2b6eef9 (Make apply --binary a no-op., 2006-09-06), --binary
>> > was been made a no-op in git-apply. Following that, since cb3a160
>> > (git-am: ignore --binary option, 2008-08-09), the --binary option in
>> > git-am is ignored as well.
>> >
>> > In 6c15a1c (am: officially deprecate -b/--binary option, 2012-03-13),
>> > the --binary option was tweaked to its present behavior: when set, the
>> > message:
>> >
>> > The -b/--binary option has been a no-op for long time, and it
>> > will be removed. Please do not use it anymore.
>> >
>> > will be printed.
>>
>> I wonder if now would be the right time? The rewrite aim's at full
>> feature compatibility, but we may want to revert this commit on
>> top of patch 44 later.
>>
>
> I would just remove the deprecated hack from the old implementation in the
> beginning of the series.

Junio, what do you think?

Regards,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 00/44] Make git-am a builtin

2015-07-14 Thread Paul Tan
On Mon, Jul 13, 2015 at 03:31:37PM -0700, Junio C Hamano wrote:
> A fix is to edit the patch to replace that "flags);" line with full
> "return delete_ref()" line and save it.  Then running
> 
>   $ git am
> 
> (no argument) is supposed to read from the corrected patch file and
> continue the application.
> 
> This no longer works with the version with this series, it seems.

Ah, this is actually the same bug as the previous one, and it actually
all stems from the fact that I completely overlooked the code path
change introduced by 271440e (git-am: make it easier after fixing up an
unapplicable patch., 2005-10-25). When "git am" is run again while
paused, the mail message should not be re-parsed at all.

So, I'm thinking of something like the following as a separate patch in
this series to re-implement the feature:

diff a/builtin/am.c b/builtin/am.c
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1714,6 +1692,21 @@ static void do_commit(const struct am_state *state)
 }
 
 /**
+ * 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.
  *
@@ -1774,8 +1767,12 @@ static int do_interactive(struct am_state *state)
 
 /**
  * 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)
+static void am_run(struct am_state *state, int resume)
 {
const char *argv_gc_auto[] = {"gc", "--auto", NULL};
struct strbuf sb = STRBUF_INIT;
@@ -1793,21 +1790,28 @@ static void am_run(struct am_state *state)
 
while (state->cur <= state->last) {
const char *mail = am_path(state, msgnum(state));
-   int apply_status, skip;
+   int apply_status;
 
if (!file_exists(mail))
goto next;
 
-   if (state->rebasing)
-   skip = parse_mail_rebase(state, mail);
-   else
-   skip = parse_mail(state, mail);
+   if (resume) {
+   validate_resume_state(state);
+   resume = 0;
+   } 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 */
+   if (skip)
+   goto next; /* mail should be skipped */
 
-   write_author_script(state);
-   write_commit_msg(state);
+   write_author_script(state);
+   write_commit_msg(state);
+   }
 
if (state->interactive && do_interactive(state))
goto next;
@@ -1880,13 +1884,7 @@ next:
  */
 static void am_resolve(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"));
+   validate_resume_state(state);
 
say(state, stdout, _("Applying: %.*s"), linelen(state->msg), 
state->msg);
 
@@ -1915,7 +1913,7 @@ static void am_resolve(struct am_state *state)
 
 next:
am_next(state);
-   am_run(state);
+   am_run(state, 0);
 }
 
 /**
@@ -2040,7 +2038,7 @@ static void am_skip(struct am_state *state)
die(_("failed to clean index"));
 
am_next(state);
-   am_run(state);
+   am_run(state, 0);
 }
 
 /**
@@ -2136,6 +2134,7 @@ static int parse_opt_patchformat(const struct option 
*opt, const char *arg, int
 
 enum resume_mode {
RESUME_FALSE = 0,
+   RESUME_APPLY,
RESUME_RESOLVED,
RESUME_SKIP,
RESUME_ABORT
@@ -2271,6 +2270,9 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
die(_("previous rebase directory %s still exists but 
mbox given."),
state.dir);
 
+   if (resume == RESUME_FALSE)
+   resume = RESUME_APPLY;
+
am

Re: [PATCH 12/12] t3901: test git-am encoding conversion

2015-07-14 Thread Paul Tan
On Thu, Jul 9, 2015 at 4:44 AM, Johannes Sixt  wrote:
> Am 02.07.2015 um 20:16 schrieb Paul Tan:
>> diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
>> index 75cf3ff..b49bdb7 100755
>> --- a/t/t3901-i18n-patch.sh
>> +++ b/t/t3901-i18n-patch.sh
>> @@ -251,4 +251,66 @@ test_expect_success 'rebase --merge (L/U)' '
>> check_encoding 2 8859
>>   '
>>
>> +test_expect_success 'am (U/U)' '
>> +   # Apply UTF-8 patches with UTF-8 commitencoding
>> +   git config i18n.commitencoding UTF-8 &&
>> +   . "$TEST_DIRECTORY"/t3901-utf8.txt &&
>> +
>> +   git reset --hard master &&
>> +   git am out-u1 out-u2 &&
>> +
>> +   check_encoding 2
>> +'
>> +
>> +test_expect_success 'am (L/L)' '
>> +   # Apply ISO-8859-1 patches with ISO-8859-1 commitencoding
>> +   git config i18n.commitencoding ISO8859-1 &&
>> +   . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
>> +
>> +   git reset --hard master &&
>> +   git am out-l1 out-l2 &&
>> +
>> +   check_encoding 2 8859
>> +'
>
>
> This test case must be protected by !MINGW, just like the last case below
> and other cases that are already in the file. See 32f4cb6cee for details.

Ah, OK.

Thanks,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 12/44] builtin-am: implement --skip

2015-07-14 Thread Paul Tan
On Tue, Jul 14, 2015 at 3:05 AM, Stefan Beller  wrote:
> All returns before this point leak the memory of `lock_file`.

Yeah, it's intentional. From Documentation/technical/api-lockfile.txt:

* Allocates a `struct lock_file` either as a static variable or on the
  heap, initialized to zeros. Once you use the structure to call the
  `hold_lock_file_*` family of functions, it belongs to the lockfile
  subsystem and its storage must remain valid throughout the life of
  the program (i.e. you cannot use an on-stack variable to hold this
  structure).

Thanks,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 19/44] builtin-am: implement --3way, am.threeWay

2015-07-14 Thread Paul Tan
On Wed, Jul 8, 2015 at 4:14 AM, Junio C Hamano  wrote:
> Paul Tan  writes:
>
>> @@ -82,6 +84,8 @@ struct am_state {
>>   /* number of digits in patch filename */
>>   int prec;
>>
>> + int threeway;
>> +
>>   int quiet;
>>
>>   int append_signoff;
>
> These "one line surrounded by blank on both sides" starts to get
> irritating, and the structure looksq horrifying after you apply all
> these patches.  Perhaps have a clean-up patch at the end to make
> them look more like this?
>
> 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 append_signoff;
> int utf8;
> int committer_date_is_author_date;
> int ignore_date;
> int allow_rerere_autoupdate;
> const char *sign_commit;
> int rebasing;
>
> /* one of the enum keep_type values */
> int keep;
>
> /* pass -m flag to git-mailinfo */
> int message_id;
>
> /* one of the enum scissors_type values */
> int scissors;
>
> /* used when spawning "git apply" via run_command() */
> struct argv_array git_apply_opts;
>
> /* override error message when patch failure occurs */
> const char *resolvemsg;

Okay. I would prefer to keep the above fields all in the same block
though, as they are all "various operating modes and command line
options". And in retrospect, the comments don't really add much value,
with the exception of the "enum" comments to make it explicit that we
expect an enum value in that field. So, something like this, perhaps?

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 append_signoff;
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;
};

Thanks,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 00/44] Make git-am a builtin

2015-07-12 Thread Paul Tan
Hi,

(Sorry for the late reply. Caught a nasty stomach bug that kept me in
bed for a while ><)

On Thu, Jul 9, 2015 at 2:00 PM, Junio C Hamano  wrote:
> What I pushed out tonight should have SQUASH??? (or fixup!) that
> splits this into appropriate steps in your series.  Please check.

Yeah they look good.

> Note that you do not have to say "if the variable h2as something,
> then free it".  free(NULL) is perfectly fine and we can read
>
> free(var);
> var = compute_new_value();
>
> just fine.

OK. There are other places where I used "if (x) free(x)" though (such
as in am_state_release()), so I will fix them in a re-roll as well.

> However, I am reluctant to blindly replace assert(!state->field)
> with free(state->field).  Are there cases where we _must_ call a
> function that sets these fields at most once?

I wouldn't say we are "blindly" replacing them. The purpose of the
assert(!state->field) is to ensure that we are not overwriting any
commit metadata already stored in the state directory. And in almost
all cases, this holds true. However, as you have just shown, running
"git am" again while there is a session in progress will override the
commit metadata stored in the state directory, so I believe the
replacement with free() is reasonable.

> On the other hand, assert() like this is more or less useless.
>
> assert(state->field);
> ...
> printf("%s", state->field); /* or other uses */
>
> "The caller must have filled the field" can be seen by unconditional
> use of "state->field" without such an assert().

OK. Will remove them as well.

Thanks,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 00/44] Make git-am a builtin

2015-07-08 Thread Paul Tan
On Wed, Jul 08, 2015 at 12:48:06AM -0700, Junio C Hamano wrote:
> Junio C Hamano  writes:
> $ git am -s ./+dt
> ...
> error: patch failed: builtin/update-ref.c:421
> error: builtin/update-ref.c: patch does not apply
> Patch failed at 0007 update-ref and tag: add --create-reflog arg
> The copy of the patch that failed is found in:.git/rebase-apply/patch
> When you have resolved this problem, run "git am --continue".
> If you prefer to skip this patch, run "git am --skip" instead.
> To restore the original branch and stop patching, run "git am --abort".
> $ git am -3
> git: builtin/am.c:1332: parse_mail: Assertion `!state->author_name'
> failed.
> Aborted (core dumped)

Ah, it's because parse_mail() does not expect to be called while the
authorship and commit msg fields have been filled up. This is a wrong
assumption, of course.

So the fix would be to remove the assert()s, as follows:

diff --git a/builtin/am.c b/builtin/am.c
index c548129..ab560ab 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1327,16 +1327,20 @@ static int parse_mail(struct am_state *state, const 
char *mail)
if (state->append_signoff)
append_signoff(&msg, 0, 0);
 
-   assert(!state->author_name);
+   if (state->author_name)
+   free(state->author_name);
state->author_name = strbuf_detach(&author_name, NULL);
 
-   assert(!state->author_email);
+   if (state->author_email)
+   free(state->author_email);
state->author_email = strbuf_detach(&author_email, NULL);
 
-   assert(!state->author_date);
+   if (state->author_date)
+   free(state->author_date);
state->author_date = strbuf_detach(&author_date, NULL);
 
-   assert(!state->msg);
+   if (state->msg)
+   free(state->msg);
state->msg = strbuf_detach(&msg, &state->msg_len);
 
 finish:
@@ -1392,7 +1396,9 @@ static void get_commit_info(struct am_state *state, 
struct commit *commit)
die(_("invalid ident line: %s"), sb.buf);
}
 
-   assert(!state->author_name);
+   if (state->author_name)
+   free(state->author_name);
+
if (ident_split.name_begin) {
strbuf_add(&sb, ident_split.name_begin,
ident_split.name_end - ident_split.name_begin);
@@ -1400,7 +1406,9 @@ static void get_commit_info(struct am_state *state, 
struct commit *commit)
} else
state->author_name = xstrdup("");
 
-   assert(!state->author_email);
+   if (state->author_email)
+   free(state->author_email);
+
if (ident_split.mail_begin) {
strbuf_add(&sb, ident_split.mail_begin,
ident_split.mail_end - ident_split.mail_begin);
@@ -1410,13 +1418,17 @@ static void get_commit_info(struct am_state *state, 
struct commit *commit)
 
author_date = show_ident_date(&ident_split, DATE_NORMAL);
strbuf_addstr(&sb, author_date);
-   assert(!state->author_date);
+
+   if (state->author_date)
+   free(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));
+
+   if (state->msg)
+   free(state->msg);
state->msg = xstrdup(msg + 2);
state->msg_len = strlen(state->msg);
 }
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 00/44] Make git-am a builtin

2015-07-08 Thread Paul Tan
On Wed, Jul 8, 2015 at 3:31 PM, Junio C Hamano  wrote:
> Paul Tan  writes:
>
>> This patch series rewrites git-am.sh into optimized C builtin/am.c, and is
>> part of my GSoC project to rewrite git-pull and git-am into C builtins[1].
>
> I merged this to 'jch' (that is somewhere in between 'next' and
> 'pu', which is what I use for everyday work) and tried it to apply
> the 8-patch series that starts at $gmane/273622 on top of 912bd49.
>
> It appears that your builtin version makes "am -3s" barf at the
> [PATCH 7/8] in that series, complaining about lack of author
> identity.

Hmm? I compiled jch, and tried to reproduce the problem, but could not
do so. Here's what I did:

git checkout 912bd49
git am -3s mbox
Applying: refs.c: add err arguments to reflog functions
Applying: cherry-pick: treat CHERRY_PICK_HEAD and REVERT_HEAD as refs
Applying: bisect: treat BISECT_HEAD as a ref
Applying: refs: Break out check for reflog autocreation
Applying: refs: new public ref function: safe_create_reflog
Applying: git-reflog: add exists command
Applying: update-ref and tag: add --create-reflog arg
Using index info to reconstruct a base tree...
Mbuiltin/update-ref.c
Falling back to patching base and 3-way merge...
Auto-merging builtin/update-ref.c
CONFLICT (content): Merge conflict in builtin/update-ref.c
error: Failed to merge in the changes.
Patch failed at 0007 update-ref and tag: add --create-reflog arg
The copy of the patch that failed is found in: .git/rebase-apply/patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

... after resolving the conflict ...

git add builtin/update-ref.c
git am --continue
Applying: update-ref and tag: add --create-reflog arg
Applying: git-stash: use update-ref --create-reflog instead of creating files

And it completes successfully.

Thanks,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 00/44] Make git-am a builtin

2015-07-07 Thread Paul Tan
On Wed, Jul 8, 2015 at 2:52 AM, Junio C Hamano  wrote:
> Paul Tan  writes:
>> This patch series rewrites git-am.sh into optimized C builtin/am.c, and is
>> part of my GSoC project to rewrite git-pull and git-am into C builtins[1].
>>
>> [1] https://gist.github.com/pyokagan/1b7b0d1f4dab6ba3cef1
>
> Is it really a rewrite into "optimized C", or just "C"?  I suspect
> it is the latter.

Well, "optimized" in comparison to the shell script ;-) We don't
replicate the shell script in its entirety, but optimize the code
where it is obvious and sensible. It's not the "most optimized" (and I
will gladly accept any suggestions where there are obvious
optimizations to be made), but I do consider it an improvement in
terms of efficiency, while keeping the overall structure of the code
close to the shell script for easy review.

I'll remove the word though, because it's true that the main purpose
of this patch series is to "make it work" first.

> This seems to apply cleanly to 'master' but fails to compile, as it
> depends on some new stuff that are not even in 'next' yet, e.g.
> argv_array_pushv() that is from pt/pull-builtin, and it does not
> apply cleanly on top of that branch, either.

I tried applying the series, and yeah it conflicts with 1570856
(config.c: avoid xmmap error messages, 2015-05-28) because the
pt/pull-builtin branch in pu is based on an old version of master.
That's the only conflict though, it applies cleanly otherwise.

> I'll see what's the cleanest way to queue this would be.  Perhaps
> merge pt/builtin-pull on a copy of 'master' and then apply these, or
> something like that.

Thanks.

Regards,
Paul
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 34/44] builtin-am: invoke applypatch-msg hook

2015-07-07 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh will invoke the applypatch-msg hooks just after extracting the
patch message. If the applypatch-msg hook exits with a non-zero status,
git-am.sh abort before even applying the patch to the index.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 24 
 1 file changed, 24 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 2255a1e..f856d3b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -491,6 +491,27 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * 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)
@@ -1434,6 +1455,9 @@ static void am_run(struct am_state *state)
write_author_script(state);
write_commit_msg(state);
 
+   if (run_applypatch_msg_hook(state))
+   exit(1);
+
say(state, stdout, _("Applying: %.*s"), linelen(state->msg), 
state->msg);
 
apply_status = run_apply(state, NULL);
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 19/44] builtin-am: implement --3way, am.threeWay

2015-07-07 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the --3way option, and if set, would attempt to do a
3-way merge if the initial patch application fails.

Since d96a275 (git-am: add am.threeWay config variable, 2015-06-04), the
setting am.threeWay configures if the --3way option is set by default.

Since 5d86861 (am -3: list the paths that needed 3-way fallback,
2012-03-28), in a 3-way merge git-am.sh would list the paths that needed
3-way fallback, so that the user can review them more carefully to spot
mismerges.

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---

Notes:
v5

* s/a index/an index/

 builtin/am.c | 157 +--
 1 file changed, 153 insertions(+), 4 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 511e4dd..af22c35 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -19,6 +19,8 @@
 #include "unpack-trees.h"
 #include "branch.h"
 #include "sequencer.h"
+#include "revision.h"
+#include "merge-recursive.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -82,6 +84,8 @@ struct am_state {
/* number of digits in patch filename */
int prec;
 
+   int threeway;
+
int quiet;
 
int append_signoff;
@@ -102,6 +106,8 @@ static void am_state_init(struct am_state *state, const 
char *dir)
state->dir = xstrdup(dir);
 
state->prec = 4;
+
+   git_config_get_bool("am.threeway", &state->threeway);
 }
 
 /**
@@ -371,6 +377,9 @@ static void am_load(struct am_state *state)
 
read_commit_msg(state);
 
+   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");
 
@@ -555,6 +564,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
die(_("Failed to split patches."));
}
 
+   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->append_signoff ? "t" : 
"f");
@@ -790,16 +801,34 @@ finish:
 }
 
 /**
- * Applies current patch with git-apply. Returns 0 on success, -1 otherwise.
+ * 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)
+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_push(&cp.args, "--index");
+
+   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))
@@ -807,8 +836,106 @@ static int run_apply(const struct am_state *state)
 
/* 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_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 *r

[PATCH v5 44/44] builtin-am: remove redirection to git-am.sh

2015-07-07 Thread Paul Tan
At the beginning of the rewrite of git-am.sh to C, in order to not break
existing test scripts that depended on a functional git-am, a
redirection to git-am.sh was introduced that would activate if the
environment variable _GIT_USE_BUILTIN_AM was not defined.

Now that all of git-am.sh's functionality has been re-implemented in
builtin/am.c, remove this redirection, and retire git-am.sh into
contrib/examples/.

Signed-off-by: Paul Tan 
---
 Makefile|  1 -
 builtin/am.c| 15 ---
 git-am.sh => contrib/examples/git-am.sh |  0
 git.c   |  7 +--
 4 files changed, 1 insertion(+), 22 deletions(-)
 rename git-am.sh => contrib/examples/git-am.sh (100%)

diff --git a/Makefile b/Makefile
index ff9bdc0..005d771 100644
--- a/Makefile
+++ b/Makefile
@@ -466,7 +466,6 @@ TEST_PROGRAMS_NEED_X =
 # interactive shell sessions without exporting it.
 unexport CDPATH
 
-SCRIPT_SH += git-am.sh
 SCRIPT_SH += git-bisect.sh
 SCRIPT_SH += git-difftool--helper.sh
 SCRIPT_SH += git-filter-branch.sh
diff --git a/builtin/am.c b/builtin/am.c
index a0982d9..c548129 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2239,21 +2239,6 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
OPT_END()
};
 
-   /*
-* NEEDSWORK: Once all the features of git-am.sh have been
-* re-implemented in builtin/am.c, this preamble can be removed.
-*/
-   if (!getenv("_GIT_USE_BUILTIN_AM")) {
-   const char *path = mkpath("%s/git-am", git_exec_path());
-
-   if (sane_execvp(path, (char **)argv) < 0)
-   die_errno("could not exec %s", path);
-   } else {
-   prefix = setup_git_directory();
-   trace_repo_setup(prefix);
-   setup_work_tree();
-   }
-
git_config(git_default_config, NULL);
 
am_state_init(&state, git_path("rebase-apply"));
diff --git a/git-am.sh b/contrib/examples/git-am.sh
similarity index 100%
rename from git-am.sh
rename to contrib/examples/git-am.sh
diff --git a/git.c b/git.c
index e84e1a1..8420e43 100644
--- a/git.c
+++ b/git.c
@@ -370,12 +370,7 @@ static int run_builtin(struct cmd_struct *p, int argc, 
const char **argv)
 
 static struct cmd_struct commands[] = {
{ "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
-   /*
-* NEEDSWORK: Once the redirection to git-am.sh in builtin/am.c has
-* been removed, this entry should be changed to
-* RUN_SETUP | NEED_WORK_TREE
-*/
-   { "am", cmd_am },
+   { "am", cmd_am, RUN_SETUP | NEED_WORK_TREE },
{ "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive },
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 13/44] builtin-am: implement --abort

2015-07-07 Thread Paul Tan
Since 3e5057a (git am --abort, 2008-07-16), git-am supported the --abort
option that will rewind HEAD back to the original commit. Re-implement
this through am_abort().

Since 7b3b7e3 (am --abort: keep unrelated commits since the last failure
and warn, 2010-12-21), to prevent commits made since the last failure
from being lost, git-am will not rewind HEAD back to the original
commit if HEAD moved since the last failure. Re-implement this through
safe_to_abort().

Signed-off-by: Paul Tan 
---
 builtin/am.c | 102 +--
 1 file changed, 99 insertions(+), 3 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 5087094..1db95f2 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -507,6 +507,8 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
 static void am_setup(struct am_state *state, enum patch_format patch_format,
const char **paths)
 {
+   unsigned char curr_head[GIT_SHA1_RAWSZ];
+
if (!patch_format)
patch_format = detect_patch_format(paths);
 
@@ -523,6 +525,14 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
die(_("Failed to split patches."));
}
 
+   if (!get_sha1("HEAD", curr_head)) {
+   write_file(am_path(state, "abort-safety"), 1, "%s", 
sha1_to_hex(curr_head));
+   update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, 
UPDATE_REFS_DIE_ON_ERR);
+   } else {
+   write_file(am_path(state, "abort-safety"), 1, "%s", "");
+   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.
@@ -539,6 +549,8 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
  */
 static void am_next(struct am_state *state)
 {
+   unsigned char head[GIT_SHA1_RAWSZ];
+
if (state->author_name)
free(state->author_name);
state->author_name = NULL;
@@ -559,6 +571,11 @@ static void am_next(struct am_state *state)
unlink(am_path(state, "author-script"));
unlink(am_path(state, "final-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);
 }
@@ -791,10 +808,14 @@ static void am_run(struct am_state *state)
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))
+   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);
 
@@ -982,6 +1003,74 @@ static void am_skip(struct am_state *state)
 }
 
 /**
+ * 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;
+   const char *curr_branch;
+
+   if (!safe_to_abort(state)) {
+   am_destroy(state);
+   return;
+  

[PATCH v5 24/44] builtin-am: implement -k/--keep, --keep-non-patch

2015-07-07 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the -k/--keep option to pass the -k option to
git-mailsplit.

Since f7e5ea1 (am: learn passing -b to mailinfo, 2012-01-16), git-am.sh
supported the --keep-non-patch option to pass the -b option to
git-mailsplit.

Re-implement these two options in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 52 
 1 file changed, 52 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 0b441fb..4e1ee7f 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -68,6 +68,12 @@ enum patch_format {
PATCH_FORMAT_MBOX
 };
 
+enum keep_type {
+   KEEP_FALSE = 0,
+   KEEP_TRUE,  /* pass -k flag to git-mailinfo */
+   KEEP_NON_PATCH  /* pass -b flag to git-mailinfo */
+};
+
 struct am_state {
/* state directory path */
char *dir;
@@ -94,6 +100,9 @@ struct am_state {
 
int utf8;
 
+   /* one of the enum keep_type values */
+   int keep;
+
/* override error message when patch failure occurs */
const char *resolvemsg;
 
@@ -397,6 +406,14 @@ static void am_load(struct am_state *state)
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;
+
state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
strbuf_release(&sb);
@@ -560,6 +577,7 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
const char **paths)
 {
unsigned char curr_head[GIT_SHA1_RAWSZ];
+   const char *str;
 
if (!patch_format)
patch_format = detect_patch_format(paths);
@@ -588,6 +606,22 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
 
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);
+
if (state->rebasing)
write_file(am_path(state, "rebasing"), 1, "%s", "");
else
@@ -760,6 +794,20 @@ static int parse_mail(struct am_state *state, const char 
*mail)
 
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");
+   }
+
argv_array_push(&cp.args, am_path(state, "msg"));
argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1476,6 +1524,10 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
N_("add a Signed-off-by line to the commit message")),
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_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 31/44] builtin-am: implement -S/--gpg-sign, commit.gpgsign

2015-07-07 Thread Paul Tan
Since 3b4e395 (am: add the --gpg-sign option, 2014-02-01), git-am.sh
supported the --gpg-sign option, and would pass it to git-commit-tree,
thus GPG-signing the commit object.

Re-implement this option in builtin/am.c.

git-commit-tree would also sign the commit by default if the
commit.gpgsign setting is true. Since we do not run commit-tree, we
re-implement this behavior by handling the commit.gpgsign setting
ourselves.

Helped-by: Stefan Beller 
Signed-off-by: Paul Tan 
---

Notes:
v5

* Renamed local "sign_commit" variable in am_state_init() to "gpgsign"
  to aid code review.

 builtin/am.c | 12 +++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index d7bb159..81b3e31 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -124,6 +124,8 @@ struct am_state {
 
int ignore_date;
 
+   const char *sign_commit;
+
int rebasing;
 };
 
@@ -133,6 +135,8 @@ struct am_state {
  */
 static void am_state_init(struct am_state *state, const char *dir)
 {
+   int gpgsign;
+
memset(state, 0, sizeof(*state));
 
assert(dir);
@@ -149,6 +153,9 @@ static void am_state_init(struct am_state *state, const 
char *dir)
state->scissors = SCISSORS_UNSET;
 
argv_array_init(&state->git_apply_opts);
+
+   if (!git_config_get_bool("commit.gpgsign", &gpgsign))
+   state->sign_commit = gpgsign ? "" : NULL;
 }
 
 /**
@@ -1266,7 +1273,7 @@ static void do_commit(const struct am_state *state)
state->ignore_date ? "" : state->author_date, 1);
 
if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
-   author, NULL))
+   author, state->sign_commit))
die(_("failed to write commit object"));
 
reflog_msg = getenv("GIT_REFLOG_ACTION");
@@ -1688,6 +1695,9 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
N_("lie about committer date")),
OPT_BOOL(0, "ignore-date", &state.ignore_date,
N_("use current timestamp for author date")),
+   { 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()
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 41/44] builtin-am: implement -i/--interactive

2015-07-07 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07),
git-am.sh supported the --interactive mode. After parsing the patch mail
and extracting the patch, commit message and authorship info, an
interactive session will begin that allows the user to choose between:

* applying the patch

* applying the patch and all subsequent patches (by disabling
  interactive mode in subsequent patches)

* skipping the patch

* editing the commit message

Since f89ad67 (Add [v]iew patch in git-am interactive., 2005-10-25),
git-am.sh --interactive also supported viewing the patch to be applied.

When --resolved-ing in --interactive mode, we need to take care to
update the patch with the contents of the index, such that the correct
patch will be displayed when the patch is viewed in interactive mode.

Re-implement the above in builtin/am.c

Signed-off-by: Paul Tan 
---

Notes:
Can't be tested because even with test_terminal isatty(0) still returns
false.

 builtin/am.c | 106 ++-
 1 file changed, 105 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 55962c6..2866328 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -25,6 +25,7 @@
 #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.
@@ -118,6 +119,8 @@ struct am_state {
/* number of digits in patch filename */
int prec;
 
+   int interactive;
+
int threeway;
 
int quiet;
@@ -1212,7 +1215,7 @@ static void NORETURN die_user_resolve(const struct 
am_state *state)
if (state->resolvemsg) {
printf_ln("%s", state->resolvemsg);
} else {
-   const char *cmdline = "git am";
+   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);
@@ -1445,6 +1448,36 @@ static void write_commit_patch(const struct am_state 
*state, struct commit *comm
 }
 
 /**
+ * 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.
@@ -1681,6 +1714,65 @@ static void do_commit(const struct am_state *state)
 }
 
 /**
+ * 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->inte

[PATCH v5 14/44] builtin-am: reject patches when there's a session in progress

2015-07-07 Thread Paul Tan
Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
would error out if the user gave it mbox(s) on the command-line, but
there was a session in progress.

Since c95b138 (Fix git-am safety checks, 2006-09-15), git-am would
detect if the user attempted to feed it a mbox via stdin, by checking if
stdin is not a tty and there is no resume command given.

Re-implement the above two safety checks.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 19 +--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 1db95f2..e066a97 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1147,9 +1147,24 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
if (read_index_preload(&the_index, NULL) < 0)
die(_("failed to read the index"));
 
-   if (am_in_progress(&state))
+   if (am_in_progress(&state)) {
+   /*
+* 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);
+
am_load(&state);
-   else {
+   } else {
struct argv_array paths = ARGV_ARRAY_INIT;
int i;
 
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 40/44] builtin-am: support and auto-detect mercurial patches

2015-07-07 Thread Paul Tan
Since 0cfd112 (am: preliminary support for hg patches, 2011-08-29),
git-am.sh could convert mercurial patches to an RFC2822 mail patch
suitable for parsing with git-mailinfo, and queue them in the state
directory for application.

Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh was able to auto-detect mercurial
patches by checking if the file begins with the line:

# HG changeset patch

Re-implement the above in builtin/am.c.

Helped-by: Stefan Beller 
Signed-off-by: Paul Tan 
---

Notes:
v5

* v4 had a math fail in the timestamp conversion. Fixed the math.

* In C89, it is implementation defined whether integer division rounds
  towards 0 or towards negative infinity. To be safe, we do the
  timestamp conversion with positive integers only, and then negate the
  result appropriately.

 builtin/am.c | 74 +++-
 1 file changed, 73 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 7a56005..55962c6 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -81,7 +81,8 @@ enum patch_format {
PATCH_FORMAT_UNKNOWN = 0,
PATCH_FORMAT_MBOX,
PATCH_FORMAT_STGIT,
-   PATCH_FORMAT_STGIT_SERIES
+   PATCH_FORMAT_STGIT_SERIES,
+   PATCH_FORMAT_HG
 };
 
 enum keep_type {
@@ -692,6 +693,11 @@ static int detect_patch_format(const char **paths)
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);
@@ -890,6 +896,68 @@ static int split_mail_stgit_series(struct am_state *state, 
const char **paths,
 }
 
 /**
+ * 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_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`.
@@ -921,6 +989,8 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
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");
}
@@ -1955,6 +2025,8 @@ static int parse_opt_patchformat(const struct option 

[PATCH v5 26/44] builtin-am: support --keep-cr, am.keepcr

2015-07-07 Thread Paul Tan
Since ad2c928 (git-am: Add command line parameter `--keep-cr` passing it
to git-mailsplit, 2010-02-27), git-am.sh supported the --keep-cr option
and would pass it to git-mailsplit.

Since e80d4cb (git-am: Add am.keepcr and --no-keep-cr to override it,
2010-02-27), git-am.sh supported the am.keepcr config setting, which
controls whether --keep-cr is on by default.

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 29 +++--
 1 file changed, 23 insertions(+), 6 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 099c6ed..59a7a2a 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -530,7 +530,7 @@ done:
  * 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)
+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;
@@ -540,6 +540,8 @@ static int split_mail_mbox(struct am_state *state, const 
char **paths)
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);
 
@@ -564,14 +566,22 @@ static int split_mail_mbox(struct am_state *state, const 
char **paths)
  * 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)
+   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);
+   return split_mail_mbox(state, paths, keep_cr);
default:
die("BUG: invalid patch_format");
}
@@ -582,7 +592,7 @@ static int split_mail(struct am_state *state, enum 
patch_format patch_format,
  * Setup a new am session for applying patches
  */
 static void am_setup(struct am_state *state, enum patch_format patch_format,
-   const char **paths)
+   const char **paths, int keep_cr)
 {
unsigned char curr_head[GIT_SHA1_RAWSZ];
const char *str;
@@ -598,7 +608,7 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
die_errno(_("failed to create directory '%s'"), state->dir);
 
-   if (split_mail(state, patch_format, paths) < 0) {
+   if (split_mail(state, patch_format, paths, keep_cr) < 0) {
am_destroy(state);
die(_("Failed to split patches."));
}
@@ -1520,6 +1530,7 @@ enum resume_mode {
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
struct am_state state;
+   int keep_cr = -1;
int patch_format = PATCH_FORMAT_UNKNOWN;
enum resume_mode resume = RESUME_FALSE;
 
@@ -1543,6 +1554,12 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
@@ -1637,7 +1654,7 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
argv_array_push(&paths, mkpath("%s/%s", prefix, 
argv[i]));
  

[PATCH v5 20/44] builtin-am: implement --rebasing mode

2015-07-07 Thread Paul Tan
Since 3041c32 (am: --rebasing, 2008-03-04), git-am.sh supported the
--rebasing option, which is used internally by git-rebase to tell git-am
that it is being used for its purpose. It would create the empty file
$state_dir/rebasing to help "completion" scripts tell if the ongoing
operation is am or rebase.

As of 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26),
--rebasing also implies --3way as well.

Since a1549e1 (am: return control to caller, for housekeeping,
2013-05-12), git-am.sh would only clean up the state directory when it
is not --rebasing, instead deferring cleanup to git-rebase.sh.

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 31 +++
 1 file changed, 27 insertions(+), 4 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index af22c35..b68dcc5 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -92,6 +92,8 @@ struct am_state {
 
/* override error message when patch failure occurs */
const char *resolvemsg;
+
+   int rebasing;
 };
 
 /**
@@ -386,6 +388,8 @@ static void am_load(struct am_state *state)
read_state_file(&sb, state, "sign", 1);
state->append_signoff = !strcmp(sb.buf, "t");
 
+   state->rebasing = !!file_exists(am_path(state, "rebasing"));
+
strbuf_release(&sb);
 }
 
@@ -564,18 +568,29 @@ static void am_setup(struct am_state *state, enum 
patch_format patch_format,
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->append_signoff ? "t" : 
"f");
 
+   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));
-   update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, 
UPDATE_REFS_DIE_ON_ERR);
+   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", "");
-   delete_ref("ORIG_HEAD", NULL, 0);
+   if (!state->rebasing)
+   delete_ref("ORIG_HEAD", NULL, 0);
}
 
/*
@@ -1057,8 +1072,14 @@ next:
am_next(state);
}
 
-   am_destroy(state);
-   run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+   /*
+* 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);
+   }
 }
 
 /**
@@ -1330,6 +1351,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
OPT_CMDMODE(0, "abort", &resume,
N_("restore the original branch and abort the patching 
operation."),
RESUME_ABORT),
+   OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
+   N_("(internal use for git-rebase)")),
OPT_END()
};
 
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 38/44] builtin-am: support and auto-detect StGit patches

2015-07-07 Thread Paul Tan
Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27),
git-am.sh supported converting StGit patches into RFC2822 mail patches
that can be parsed with git-mailinfo.

Implement this by introducing two functions in builtin/am.c:
stgit_patch_to_mail() and split_mail_conv().

stgit_patch_to_mail() is a callback function for split_mail_conv(), and
contains the logic for converting an StGit patch into an RFC2822 mail
patch.

split_mail_conv() implements the logic to go through each file in the
`paths` list, reading from stdin where specified, and calls the callback
function to write the converted patch to the corresponding output file
in the state directory. This interface should be generic enough to
support other foreign patch formats in the future.

Since 15ced75 (git-am foreign patch support: autodetect some patch
formats, 2009-05-27), git-am.sh is able to auto-detect StGit patches.
Re-implement this in builtin/am.c.

Helped-by: Eric Sunshine 
Signed-off-by: Paul Tan 
---

Notes:
v5

* Rewrite of the loop in str_isspace() to be clearer.

 builtin/am.c | 132 ++-
 1 file changed, 131 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 6b388de..0bb1875 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -65,9 +65,22 @@ 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_MBOX,
+   PATCH_FORMAT_STGIT
 };
 
 enum keep_type {
@@ -646,6 +659,8 @@ 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;
 
/*
@@ -671,6 +686,23 @@ static int detect_patch_format(const char **paths)
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;
@@ -711,6 +743,100 @@ static int split_mail_mbox(struct am_state *state, const 
char **paths, int keep_
 }
 
 /**
+ * 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() 

[PATCH v5 16/44] builtin-am: exit with user friendly message on failure

2015-07-07 Thread Paul Tan
Since ced9456 (Give the user a hint for how to continue in the case that
git-am fails because it requires user intervention, 2006-05-02), git-am
prints additional information on how the user can re-invoke git-am to
resume patch application after resolving the failure. Re-implement this
through the die_user_resolve() function.

Since cc12005 (Make git rebase interactive help match documentation.,
2006-05-13), git-am supports the --resolvemsg option which is used by
git-rebase to override the message printed out when git-am fails.
Re-implement this option.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 32 
 1 file changed, 28 insertions(+), 4 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 61c2ad3..7eb23b9 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -82,6 +82,9 @@ struct am_state {
int prec;
 
int quiet;
+
+   /* override error message when patch failure occurs */
+   const char *resolvemsg;
 };
 
 /**
@@ -668,6 +671,25 @@ static int index_has_changes(struct strbuf *sb)
 }
 
 /**
+ * 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 = "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);
+}
+
+/**
  * 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
@@ -727,7 +749,7 @@ static int parse_mail(struct am_state *state, const char 
*mail)
 
if (is_empty_file(am_path(state, "patch"))) {
printf_ln(_("Patch is empty. Was it split wrong?"));
-   exit(128);
+   die_user_resolve(state);
}
 
strbuf_addstr(&msg, "\n\n");
@@ -868,7 +890,7 @@ static void am_run(struct am_state *state)
printf_ln(_("The copy of the patch that failed 
is found in: %s"),
am_path(state, "patch"));
 
-   exit(128);
+   die_user_resolve(state);
}
 
do_commit(state);
@@ -902,13 +924,13 @@ static void am_resolve(struct am_state *state)
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."));
-   exit(128);
+   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'?"));
-   exit(128);
+   die_user_resolve(state);
}
 
do_commit(state);
@@ -1132,6 +1154,8 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
N_("format the patch(es) are in"),
parse_opt_patchformat),
+   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),
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 43/44] builtin-am: check for valid committer ident

2015-07-07 Thread Paul Tan
When commit_tree() is called, if the user does not have an explicit
committer ident configured, it will attempt to construct a default
committer ident based on the user's and system's info (e.g. gecos field,
hostname etc.) However, if a default committer ident is unable to be
constructed, commit_tree() will die(), but at this point of git-am's
execution, there will already be changes made to the index and work
tree.

This can be confusing to new users, and as such since d64e6b0 (Keep
Porcelainish from failing by broken ident after making changes.,
2006-02-18) git-am.sh will check to see if the committer ident has been
configured, or a default one can be constructed, before even starting to
apply patches.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---

Notes:
v5

* modified commit message

 builtin/am.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index ad07ba4..a0982d9 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2264,6 +2264,9 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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"));
 
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 37/44] builtin-am: rerere support

2015-07-07 Thread Paul Tan
git-am.sh will call git-rerere at the following events:

* "git rerere" when a three-way merge fails to record the conflicted
  automerge results. Since 8389b52 (git-rerere: reuse recorded resolve.,
  2006-01-28)

  * Since cb6020b (Teach --[no-]rerere-autoupdate option to merge,
revert and friends, 2009-12-04), git-am.sh supports the
--[no-]rerere-autoupdate option as well, and would pass it to
git-rerere.

* "git rerere" when --resolved, to record the hand resolution. Since
  f131dd4 (rerere: record (or avoid misrecording) resolved, skipped or
  aborted rebase/am, 2006-12-08)

* "git rerere clear" when --skip-ing. Since f131dd4 (rerere: record (or
  avoid misrecording) resolved, skipped or aborted rebase/am,
  2006-12-08)

* "git rerere clear" when --abort-ing. Since 3e5057a (git am --abort,
  2008-07-16)

Re-implement the above in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 26 ++
 1 file changed, 26 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index fdaf0a7..6b388de 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -24,6 +24,7 @@
 #include "revision.h"
 #include "log-tree.h"
 #include "notes-utils.h"
+#include "rerere.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -128,6 +129,8 @@ struct am_state {
 
int ignore_date;
 
+   int allow_rerere_autoupdate;
+
const char *sign_commit;
 
int rebasing;
@@ -1352,6 +1355,7 @@ static int fall_back_threeway(const struct am_state 
*state, const char *index_pa
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."));
}
@@ -1551,6 +1555,8 @@ static void am_resolve(struct am_state *state)
die_user_resolve(state);
}
 
+   rerere(0);
+
do_commit(state);
 
am_next(state);
@@ -1649,12 +1655,29 @@ static int clean_index(const unsigned char *head, const 
unsigned char *remote)
 }
 
 /**
+ * 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);
 
@@ -1712,6 +1735,8 @@ static void am_abort(struct am_state *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)
@@ -1839,6 +1864,7 @@ int cmd_am(int argc, const char **argv, const char 
*prefix)
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) "" },
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 32/44] builtin-am: invoke post-rewrite hook

2015-07-07 Thread Paul Tan
Since 96e1948 (rebase: invoke post-rewrite hook, 2010-03-12), git-am.sh
will invoke the post-rewrite hook after it successfully finishes
applying all the queued patches.

To do this, when parsing a mail to extract its patch and metadata, in
--rebasing mode git-am.sh will also store the original commit ID in the
$state_dir/original-commit file. Once it applies and commits the patch,
the original commit ID, and the new commit ID, will be appended to the
$state_dir/rewritten file.

Once all of the queued mail have been processed, git-am.sh will then
invoke the post-rewrite hook with the contents of the
$state_dir/rewritten file.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan 
---
 builtin/am.c | 55 +++
 1 file changed, 55 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 81b3e31..c234ee9 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -95,6 +95,9 @@ struct am_state {
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;
 
@@ -427,6 +430,11 @@ static void am_load(struct am_state *state)
 
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");
 
@@ -482,6 +490,30 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * 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;
+}
+
+/**
  * 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.
@@ -759,6 +791,9 @@ static void am_next(struct am_state *state)
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
@@ -1078,6 +1113,8 @@ static void write_commit_patch(const struct am_state 
*state, struct commit *comm
  * 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)
@@ -1094,6 +1131,10 @@ static int parse_mail_rebase(struct am_state *state, 
const char *mail)
 
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;
 }
 
@@ -1285,6 +1326,15 @@ static void do_commit(const struct am_state *state)
 
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);
+   }
+
strbuf_release(&sb);
 }
 
@@ -1367,6 +1417,11 @@ next:
am_next(state);
}
 
+   if (!is_empty_file(am_path(state, "rewritten"))) {
+   assert(state->rebasing);
+   run_post_rewrite_hook(state);
+   }
+
/*
 * In rebasing mode, it's up to the caller to take care of
 * housekeeping.
-- 
2.5.0.rc1.76.gf60a929

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 21/44] builtin-am: bypass git-mailinfo when --rebasing

2015-07-07 Thread Paul Tan
Since 5e835ca (rebase: do not munge commit log message, 2008-04-16),
git am --rebasing no longer gets the commit log message from the patch,
but reads it directly from the commit identified by the "From " header
line.

Since 43c2325 (am: use get_author_ident_from_commit instead of mailinfo
when rebasing, 2010-06-16), git am --rebasing also gets the author name,
email and date directly from the commit.

Since 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26), git am
--rebasing does not use git-mailinfo to get the patch body, but rather
generates it directly from the commit itself.

The above 3 commits introduced a separate parse_mail() code path in
git-am.sh's --rebasing mode that bypasses git-mailinfo. Re-implement
this code path in builtin/am.c as parse_mail_rebase().

Signed-off-by: Paul Tan 
---
 builtin/am.c | 134 ++-
 1 file changed, 132 insertions(+), 2 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index b68dcc5..06ded5d 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -21,6 +21,8 @@
 #include "sequencer.h"
 #include "revision.h"
 #include "merge-recursive.h"
+#include "revision.h"
+#include "log-tree.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -816,6 +818,129 @@ finish:
 }
 
 /**
+ * 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_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

<    1   2   3   4   5   6   7   >