{"thread":{"id":"39596","subject":"[PATCH/WIP v2 00/19] Make git-am a builtin","startedAt":"2015-06-11T10:21:46Z","lastAt":"2015-06-18T08:44:32Z","messageCount":31,"participants":["Paul Tan","Stefan Beller","Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":19},"messages":[{"id":"263558","messageId":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":null,"subject":"[PATCH/WIP v2 00/19] Make git-am a builtin","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:46Z","receivedAt":"2015-06-11T10:21:46Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"This is a re-roll of [v1]. Thanks Junio, Torsten, Jeff, Eric for the reviews\nlast round.\n\nPrevious versions:\n\n[v1] http://thread.gmane.org/gmane.comp.version-control.git/270048\n\ngit-am is a commonly used command for applying a series of patches from a\nmailbox to the current branch. Currently, it is implemented by the shell script\ngit-am.sh. However, compared to C, shell scripts have certain deficiencies:\nthey need to spawn a lot of processes, introduce a lot of dependencies and\ncannot take advantage of git's internal caches.\n\nThis WIP patch series rewrites git-am.sh into optimized C builtin/am.c, and is\npart of my GSoC project to rewrite git-pull and git-am into C builtins[1].\n\n[1] https://gist.github.com/pyokagan/1b7b0d1f4dab6ba3cef1\n\nPaul Tan (19):\n  wrapper: implement xopen()\n  wrapper: implement xfopen()\n  am: implement skeletal builtin am\n  am: implement patch queue mechanism\n  am: split out mbox/maildir patches with git-mailsplit\n  am: detect mbox patches\n  am: extract patch, message and authorship with git-mailinfo\n  am: apply patch with git-apply\n  am: commit applied patch\n  am: refresh the index at start\n  am: refuse to apply patches if index is dirty\n  am: implement --resolved/--continue\n  am: implement --skip\n  am: implement --abort\n  am: implement quiet option\n  am: exit with user friendly message on patch failure\n  am: implement am --signoff\n  cache-tree: introduce write_index_as_tree()\n  am: implement 3-way merge\n\n Makefile          |    1 +\n builtin.h         |    1 +\n builtin/am.c      | 1214 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n cache-tree.c      |   29 +-\n cache-tree.h      |    1 +\n git-compat-util.h |    2 +\n git.c             |    1 +\n wrapper.c         |   43 ++\n 8 files changed, 1280 insertions(+), 12 deletions(-)\n create mode 100644 builtin/am.c\n\n-- \n2.1.4\n"},{"id":"263559","messageId":"1434018125-31804-2-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 01/19] wrapper: implement xopen()","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:47Z","receivedAt":"2015-06-11T10:21:47Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"A common usage pattern of open() is to check if it was successful, and\ndie() if it was not:\n\n\tint fd = open(path, O_WRONLY | O_CREAT, 0777);\n\tif (fd < 0)\n\t\tdie_errno(_(\"Could not open '%s' for writing.\"), path);\n\nImplement a wrapper function xopen() that does the above so that we can\nsave a few lines of code, and make the die() messages consistent.\n\nHelped-by: Torsten Bögershausen <tboegi@web.de>\nHelped-by: Jeff King <peff@peff.net>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v2\n    \n    * retry on EINTR\n    \n    * mode is now an optional argument in xopen(). We use the mode argument\n      only if O_CREAT is specified in oflag.\n\n git-compat-util.h |  1 +\n wrapper.c         | 25 +++++++++++++++++++++++++\n 2 files changed, 26 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 17584ad..95cc278 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -718,6 +718,7 @@ extern char *xstrndup(const char *str, size_t len);\n extern void *xrealloc(void *ptr, size_t size);\n extern void *xcalloc(size_t nmemb, size_t size);\n extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);\n+extern int xopen(const char *path, int flags, ...);\n extern ssize_t xread(int fd, void *buf, size_t len);\n extern ssize_t xwrite(int fd, const void *buf, size_t len);\n extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);\ndiff --git a/wrapper.c b/wrapper.c\nindex c1a663f..82658b3 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -189,6 +189,31 @@ void *xcalloc(size_t nmemb, size_t size)\n # endif\n #endif\n \n+/**\n+ * xopen() is the same as open(), but it die()s if the open() fails.\n+ */\n+int xopen(const char *path, int oflag, ...)\n+{\n+\tmode_t mode = 0;\n+\tva_list ap;\n+\n+\tva_start(ap, oflag);\n+\tif (oflag & O_CREAT)\n+\t\tmode = va_arg(ap, mode_t);\n+\tva_end(ap);\n+\n+\tassert(path);\n+\n+\tfor (;;) {\n+\t\tint fd = open(path, oflag, mode);\n+\t\tif (fd >= 0)\n+\t\t\treturn fd;\n+\t\tif (errno == EINTR)\n+\t\t\tcontinue;\n+\t\tdie_errno(_(\"could not open '%s'\"), path);\n+\t}\n+}\n+\n /*\n  * xread() is the same a read(), but it automatically restarts read()\n  * operations with a recoverable error (EAGAIN and EINTR). xread()\n-- \n2.1.4\n"},{"id":"263560","messageId":"1434018125-31804-3-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 02/19] wrapper: implement xfopen()","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:48Z","receivedAt":"2015-06-11T10:21:48Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"A common usage pattern of fopen() is to check if it succeeded, and die()\nif it failed:\n\n\tFILE *fp = fopen(path, \"w\");\n\tif (!fp)\n\t\tdie_errno(_(\"could not open '%s' for writing\"), path);\n\nImplement a wrapper function xfopen() for the above, so that we can save\na few lines of code and make the die() messages consistent.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v2\n    \n    * Removed the error message distinction between reading and writing.\n    \n    * Handle EINTR.\n\n git-compat-util.h |  1 +\n wrapper.c         | 18 ++++++++++++++++++\n 2 files changed, 19 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 95cc278..03ea3a2 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -723,6 +723,7 @@ extern ssize_t xread(int fd, void *buf, size_t len);\n extern ssize_t xwrite(int fd, const void *buf, size_t len);\n extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);\n extern int xdup(int fd);\n+extern FILE *xfopen(const char *path, const char *mode);\n extern FILE *xfdopen(int fd, const char *mode);\n extern int xmkstemp(char *template);\n extern int xmkstemp_mode(char *template, int mode);\ndiff --git a/wrapper.c b/wrapper.c\nindex 82658b3..9692460 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -336,6 +336,24 @@ int xdup(int fd)\n \treturn ret;\n }\n \n+/**\n+ * xfopen() is the same as fopen(), but it die()s if the fopen() fails.\n+ */\n+FILE *xfopen(const char *path, const char *mode)\n+{\n+\tassert(path);\n+\tassert(mode);\n+\n+\tfor (;;) {\n+\t\tFILE *fp = fopen(path, mode);\n+\t\tif (fp)\n+\t\t\treturn fp;\n+\t\tif (errno == EINTR)\n+\t\t\tcontinue;\n+\t\tdie_errno(_(\"could not open '%s'\"), path);\n+\t}\n+}\n+\n FILE *xfdopen(int fd, const char *mode)\n {\n \tFILE *stream = fdopen(fd, mode);\n-- \n2.1.4\n"},{"id":"263561","messageId":"1434018125-31804-4-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 03/19] am: implement skeletal builtin am","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:49Z","receivedAt":"2015-06-11T10:21:49Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"For the purpose of rewriting git-am.sh into a C builtin, implement a\nskeletal builtin/am.c that redirects to $GIT_EXEC_PATH/git-am if the\nenvironment variable _GIT_USE_BUILTIN_AM is not defined. Since in the\nMakefile git-am.sh takes precedence over builtin/am.c,\n$GIT_EXEC_PATH/git-am will contain the shell script git-am.sh, and thus\nthis allows us to fall back on the functional git-am.sh when running the\ntest suite for tests that depend on a working git-am implementation.\n\nThis redirection will be removed when all the features of git-am.sh have\nbeen re-implemented in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v2\n    \n    * Declare struct am_state state as static.\n\n Makefile     |  1 +\n builtin.h    |  1 +\n builtin/am.c | 20 ++++++++++++++++++++\n git.c        |  1 +\n 4 files changed, 23 insertions(+)\n create mode 100644 builtin/am.c\n\ndiff --git a/Makefile b/Makefile\nindex 54ec511..b82ba0e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -812,6 +812,7 @@ LIB_OBJS += xdiff-interface.o\n LIB_OBJS += zlib.o\n \n BUILTIN_OBJS += builtin/add.o\n+BUILTIN_OBJS += builtin/am.o\n BUILTIN_OBJS += builtin/annotate.o\n BUILTIN_OBJS += builtin/apply.o\n BUILTIN_OBJS += builtin/archive.o\ndiff --git a/builtin.h b/builtin.h\nindex b87df70..d50c9d1 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -30,6 +30,7 @@ extern int textconv_object(const char *path, unsigned mode, const unsigned char\n extern int is_builtin(const char *s);\n \n extern int cmd_add(int argc, const char **argv, const char *prefix);\n+extern int cmd_am(int argc, const char **argv, const char *prefix);\n extern int cmd_annotate(int argc, const char **argv, const char *prefix);\n extern int cmd_apply(int argc, const char **argv, const char *prefix);\n extern int cmd_archive(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/am.c b/builtin/am.c\nnew file mode 100644\nindex 0000000..0ccbe33\n--- /dev/null\n+++ b/builtin/am.c\n@@ -0,0 +1,20 @@\n+/*\n+ * Builtin \"git am\"\n+ *\n+ * Based on git-am.sh by Junio C Hamano.\n+ */\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"exec_cmd.h\"\n+\n+int cmd_am(int argc, const char **argv, const char *prefix)\n+{\n+\tif (!getenv(\"_GIT_USE_BUILTIN_AM\")) {\n+\t\tconst char *path = mkpath(\"%s/git-am\", git_exec_path());\n+\n+\t\tif (sane_execvp(path, (char**) argv) < 0)\n+\t\t\tdie_errno(\"could not exec %s\", path);\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/git.c b/git.c\nindex 44374b1..42328ed 100644\n--- a/git.c\n+++ b/git.c\n@@ -370,6 +370,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \n static struct cmd_struct commands[] = {\n \t{ \"add\", cmd_add, RUN_SETUP | NEED_WORK_TREE },\n+\t{ \"am\", cmd_am, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"annotate\", cmd_annotate, RUN_SETUP },\n \t{ \"apply\", cmd_apply, RUN_SETUP_GENTLY },\n \t{ \"archive\", cmd_archive },\n-- \n2.1.4\n"},{"id":"263562","messageId":"1434018125-31804-5-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 04/19] am: implement patch queue mechanism","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:50Z","receivedAt":"2015-06-11T10:21:50Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"git-am applies a series of patches. If the process terminates\nabnormally, we want to be able to resume applying the series of patches.\nThis requires the session state to be saved in a persistent location.\n\nImplement the mechanism of a \"patch queue\", represented by 2 integers --\nthe index of the current patch we are applying and the index of the last\npatch, as well as its lifecycle through the following functions:\n\n* am_setup(), which will set up the state directory\n  $GIT_DIR/rebase-apply. As such, even if the process exits abnormally,\n  the last-known state will still persist.\n\n* am_load(), which is called if there is an am session in\n  progress, to load the last known state from the state directory so we\n  can resume applying patches.\n\n* am_run(), which will do the actual patch application. After applying a\n  patch, it calls am_next() to increment the current patch index. The\n  logic for applying and committing a patch is not implemented yet.\n\n* am_destroy(), which is finally called when we successfully applied all\n  the patches in the queue, to clean up by removing the state directory\n  and its contents.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v2\n    \n    * Declare struct am_state as static\n\n builtin/am.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 164 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 0ccbe33..f061d21 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -6,6 +6,154 @@\n #include \"cache.h\"\n #include \"builtin.h\"\n #include \"exec_cmd.h\"\n+#include \"parse-options.h\"\n+#include \"dir.h\"\n+\n+struct am_state {\n+\t/* state directory path */\n+\tstruct strbuf dir;\n+\n+\t/* current and last patch numbers, 1-indexed */\n+\tint cur;\n+\tint last;\n+};\n+\n+/**\n+ * Initializes am_state with the default values.\n+ */\n+static void am_state_init(struct am_state *state)\n+{\n+\tmemset(state, 0, sizeof(*state));\n+\n+\tstrbuf_init(&state->dir, 0);\n+}\n+\n+/**\n+ * Release memory allocated by an am_state.\n+ */\n+static void am_state_release(struct am_state *state)\n+{\n+\tstrbuf_release(&state->dir);\n+}\n+\n+/**\n+ * Returns path relative to the am_state directory.\n+ */\n+static inline const char *am_path(const struct am_state *state, const char *path)\n+{\n+\treturn mkpath(\"%s/%s\", state->dir.buf, path);\n+}\n+\n+/**\n+ * Returns 1 if there is an am session in progress, 0 otherwise.\n+ */\n+static int am_in_progress(const struct am_state *state)\n+{\n+\tstruct stat st;\n+\n+\tif (lstat(state->dir.buf, &st) < 0 || !S_ISDIR(st.st_mode))\n+\t\treturn 0;\n+\tif (lstat(am_path(state, \"last\"), &st) || !S_ISREG(st.st_mode))\n+\t\treturn 0;\n+\tif (lstat(am_path(state, \"next\"), &st) || !S_ISREG(st.st_mode))\n+\t\treturn 0;\n+\treturn 1;\n+}\n+\n+/**\n+ * Reads the contents of `file`. The third argument can be used to give a hint\n+ * about the file size, to avoid reallocs. Returns number of bytes read on\n+ * success, -1 if the file does not exist. If trim is set, trailing whitespace\n+ * will be removed from the file contents.\n+ */\n+static int read_state_file(struct strbuf *sb, const char *file, size_t hint, int trim)\n+{\n+\tstrbuf_reset(sb);\n+\tif (strbuf_read_file(sb, file, hint) >= 0) {\n+\t\tif (trim)\n+\t\t\tstrbuf_rtrim(sb);\n+\n+\t\treturn sb->len;\n+\t}\n+\n+\tif (errno == ENOENT)\n+\t\treturn -1;\n+\n+\tdie_errno(_(\"could not read '%s'\"), file);\n+}\n+\n+/**\n+ * Loads state from disk.\n+ */\n+static void am_load(struct am_state *state)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tread_state_file(&sb, am_path(state, \"next\"), 8, 1);\n+\tstate->cur = strtol(sb.buf, NULL, 10);\n+\n+\tread_state_file(&sb, am_path(state, \"last\"), 8, 1);\n+\tstate->last = strtol(sb.buf, NULL, 10);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n+/**\n+ * Remove the am_state directory.\n+ */\n+static void am_destroy(const struct am_state *state)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&sb, state->dir.buf);\n+\tremove_dir_recursively(&sb, 0);\n+\tstrbuf_release(&sb);\n+}\n+\n+/**\n+ * Setup a new am session for applying patches\n+ */\n+static void am_setup(struct am_state *state)\n+{\n+\tif (mkdir(state->dir.buf, 0777) < 0 && errno != EEXIST)\n+\t\tdie_errno(_(\"failed to create directory '%s'\"), state->dir.buf);\n+\n+\twrite_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n+\n+\twrite_file(am_path(state, \"last\"), 1, \"%d\", state->last);\n+}\n+\n+/**\n+ * Increments the patch pointer, and cleans am_state for the application of the\n+ * next patch.\n+ */\n+static void am_next(struct am_state *state)\n+{\n+\tstate->cur++;\n+\twrite_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n+}\n+\n+/**\n+ * Applies all queued patches.\n+ */\n+static void am_run(struct am_state *state)\n+{\n+\twhile (state->cur <= state->last)\n+\t\tam_next(state);\n+\n+\tam_destroy(state);\n+}\n+\n+static struct am_state state;\n+\n+static const char * const am_usage[] = {\n+\tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n+\tNULL\n+};\n+\n+static struct option am_options[] = {\n+\tOPT_END()\n+};\n \n int cmd_am(int argc, const char **argv, const char *prefix)\n {\n@@ -16,5 +164,21 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\tdie_errno(\"could not exec %s\", path);\n \t}\n \n+\tgit_config(git_default_config, NULL);\n+\n+\tam_state_init(&state);\n+\tstrbuf_addstr(&state.dir, git_path(\"rebase-apply\"));\n+\n+\targc = parse_options(argc, argv, prefix, am_options, am_usage, 0);\n+\n+\tif (am_in_progress(&state))\n+\t\tam_load(&state);\n+\telse\n+\t\tam_setup(&state);\n+\n+\tam_run(&state);\n+\n+\tam_state_release(&state);\n+\n \treturn 0;\n }\n-- \n2.1.4\n"},{"id":"263564","messageId":"1434018125-31804-6-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 05/19] am: split out mbox/maildir patches with git-mailsplit","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:51Z","receivedAt":"2015-06-11T10:21:51Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"git-am.sh supports mbox, stgit and mercurial patches. Re-implement\nsupport for splitting out mbox/maildirs using git-mailsplit, while also\nimplementing the framework required to support other patch formats in\nthe future.\n\nRe-implement support for the --patch-format option (since a5a6755\n(git-am foreign patch support: introduce patch_format, 2009-05-27)) to\nallow the user to choose between the different patch formats.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v2\n    \n    * Declare int opt_patchformat as static.\n    \n    * Fix up indentation style for the switch()\n\n builtin/am.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 103 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex f061d21..5198a8e 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -8,6 +8,12 @@\n #include \"exec_cmd.h\"\n #include \"parse-options.h\"\n #include \"dir.h\"\n+#include \"run-command.h\"\n+\n+enum patch_format {\n+\tPATCH_FORMAT_UNKNOWN = 0,\n+\tPATCH_FORMAT_MBOX\n+};\n \n struct am_state {\n \t/* state directory path */\n@@ -16,6 +22,9 @@ struct am_state {\n \t/* current and last patch numbers, 1-indexed */\n \tint cur;\n \tint last;\n+\n+\t/* number of digits in patch filename */\n+\tint prec;\n };\n \n /**\n@@ -26,6 +35,7 @@ static void am_state_init(struct am_state *state)\n \tmemset(state, 0, sizeof(*state));\n \n \tstrbuf_init(&state->dir, 0);\n+\tstate->prec = 4;\n }\n \n /**\n@@ -111,13 +121,67 @@ static void am_destroy(const struct am_state *state)\n }\n \n /**\n+ * Splits out individual patches from `paths`, where each path is either a mbox\n+ * file or a Maildir. Return 0 on success, -1 on failure.\n+ */\n+static int split_patches_mbox(struct am_state *state, struct string_list *paths)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct string_list_item *item;\n+\tstruct strbuf last = STRBUF_INIT;\n+\n+\tcp.git_cmd = 1;\n+\targv_array_push(&cp.args, \"mailsplit\");\n+\targv_array_pushf(&cp.args, \"-d%d\", state->prec);\n+\targv_array_pushf(&cp.args, \"-o%s\", state->dir.buf);\n+\targv_array_push(&cp.args, \"-b\");\n+\targv_array_push(&cp.args, \"--\");\n+\n+\tfor_each_string_list_item(item, paths)\n+\t\targv_array_push(&cp.args, item->string);\n+\n+\tif (capture_command(&cp, &last, 8))\n+\t\treturn -1;\n+\n+\tstate->cur = 1;\n+\tstate->last = strtol(last.buf, NULL, 10);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * Splits out individual patches, of patch_format, contained within paths.\n+ * These patches will be stored in the state directory, with each patch's\n+ * filename being its index, padded to state->prec digits. state->cur will be\n+ * set to the index of the first patch, and state->last will be set to the\n+ * index of the last patch. Returns 0 on success, -1 on failure.\n+ */\n+static int split_patches(struct am_state *state, enum patch_format patch_format,\n+\t\tstruct string_list *paths)\n+{\n+\tswitch (patch_format) {\n+\tcase PATCH_FORMAT_MBOX:\n+\t\treturn split_patches_mbox(state, paths);\n+\tdefault:\n+\t\tdie(\"BUG: invalid patch_format\");\n+\t}\n+\treturn -1;\n+}\n+\n+/**\n  * Setup a new am session for applying patches\n  */\n-static void am_setup(struct am_state *state)\n+static void am_setup(struct am_state *state, enum patch_format patch_format,\n+\t\tstruct string_list *paths)\n {\n \tif (mkdir(state->dir.buf, 0777) < 0 && errno != EEXIST)\n \t\tdie_errno(_(\"failed to create directory '%s'\"), state->dir.buf);\n \n+\tif (split_patches(state, patch_format, paths) < 0) {\n+\t\tam_destroy(state);\n+\t\tdie(_(\"Failed to split patches.\"));\n+\t}\n+\n \twrite_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n \n \twrite_file(am_path(state, \"last\"), 1, \"%d\", state->last);\n@@ -138,13 +202,33 @@ static void am_next(struct am_state *state)\n  */\n static void am_run(struct am_state *state)\n {\n-\twhile (state->cur <= state->last)\n+\twhile (state->cur <= state->last) {\n+\n+\t\t/* TODO: Patch application not implemented yet */\n+\n \t\tam_next(state);\n+\t}\n \n \tam_destroy(state);\n }\n \n+/**\n+ * parse_options() callback that validates and sets opt->value to the\n+ * PATCH_FORMAT_* enum value corresponding to `arg`.\n+ */\n+static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tif (!strcmp(arg, \"mbox\"))\n+\t\t*opt_value = PATCH_FORMAT_MBOX;\n+\telse\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n static struct am_state state;\n+static int opt_patch_format;\n \n static const char * const am_usage[] = {\n \tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n@@ -152,6 +236,8 @@ static const char * const am_usage[] = {\n };\n \n static struct option am_options[] = {\n+\tOPT_CALLBACK(0, \"patch-format\", &opt_patch_format, N_(\"format\"),\n+\t\tN_(\"format the patch(es) are in\"), parse_opt_patchformat),\n \tOPT_END()\n };\n \n@@ -173,8 +259,21 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \n \tif (am_in_progress(&state))\n \t\tam_load(&state);\n-\telse\n-\t\tam_setup(&state);\n+\telse {\n+\t\tstruct string_list paths = STRING_LIST_INIT_DUP;\n+\t\tint i;\n+\n+\t\tfor (i = 0; i < argc; i++) {\n+\t\t\tif (is_absolute_path(argv[i]) || !prefix)\n+\t\t\t\tstring_list_append(&paths, argv[i]);\n+\t\t\telse\n+\t\t\t\tstring_list_append(&paths, mkpath(\"%s/%s\", prefix, argv[i]));\n+\t\t}\n+\n+\t\tam_setup(&state, opt_patch_format, &paths);\n+\n+\t\tstring_list_clear(&paths, 0);\n+\t}\n \n \tam_run(&state);\n \n-- \n2.1.4\n"},{"id":"263563","messageId":"1434018125-31804-7-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 06/19] am: detect mbox patches","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:52Z","receivedAt":"2015-06-11T10:21:52Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 15ced75 (git-am foreign patch support: autodetect some patch\nformats, 2009-05-27), git-am.sh is able to autodetect mbox, stgit and\nmercurial patches through heuristics.\n\nRe-implement support for autodetecting mbox/maildir files.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v2\n    \n    * Various small code tweaks suggested by Eric.\n\n builtin/am.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 98 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 5198a8e..7379b97 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -120,6 +120,96 @@ static void am_destroy(const struct am_state *state)\n \tstrbuf_release(&sb);\n }\n \n+/*\n+ * Returns 1 if the file looks like a piece of email a-la RFC2822, 0 otherwise.\n+ * We check this by grabbing all the non-indented lines and seeing if they look\n+ * like they begin with valid header field names.\n+ */\n+static int is_email(const char *filename)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tFILE *fp = xfopen(filename, \"r\");\n+\tint ret = 1;\n+\n+\twhile (!strbuf_getline(&sb, fp, '\\n')) {\n+\t\tconst char *x;\n+\n+\t\tstrbuf_rtrim(&sb);\n+\n+\t\tif (!sb.len)\n+\t\t\tbreak; /* End of header */\n+\n+\t\t/* Ignore indented folded lines */\n+\t\tif (*sb.buf == '\\t' || *sb.buf == ' ')\n+\t\t\tcontinue;\n+\n+\t\t/* It's a header if it matches the regexp \"^[!-9;-~]+:\" */\n+\t\tfor (x = sb.buf; *x; x++) {\n+\t\t\tif (('!' <= *x && *x <= '9') || (';' <= *x && *x <= '~'))\n+\t\t\t\tcontinue;\n+\t\t\tif (*x == ':' && x != sb.buf)\n+\t\t\t\tbreak;\n+\t\t\tret = 0;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+done:\n+\tfclose(fp);\n+\tstrbuf_release(&sb);\n+\treturn ret;\n+}\n+\n+/**\n+ * Attempts to detect the patch_format of the patches contained in `paths`,\n+ * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if\n+ * detection fails.\n+ */\n+static int detect_patch_format(struct string_list *paths)\n+{\n+\tenum patch_format ret = PATCH_FORMAT_UNKNOWN;\n+\tstruct strbuf l1 = STRBUF_INIT;\n+\tstruct strbuf l2 = STRBUF_INIT;\n+\tstruct strbuf l3 = STRBUF_INIT;\n+\tFILE *fp;\n+\n+\t/*\n+\t * We default to mbox format if input is from stdin and for directories\n+\t */\n+\tif (!paths->nr || !strcmp(paths->items->string, \"-\") ||\n+\t    is_directory(paths->items->string)) {\n+\t\tret = PATCH_FORMAT_MBOX;\n+\t\tgoto done;\n+\t}\n+\n+\t/*\n+\t * Otherwise, check the first 3 lines of the first patch, starting\n+\t * from the first non-blank line, to try to detect its format.\n+\t */\n+\tfp = xfopen(paths->items->string, \"r\");\n+\twhile (!strbuf_getline(&l1, fp, '\\n')) {\n+\t\tstrbuf_trim(&l1);\n+\t\tif (l1.len)\n+\t\t\tbreak;\n+\t}\n+\tstrbuf_getline(&l2, fp, '\\n');\n+\tstrbuf_trim(&l2);\n+\tstrbuf_getline(&l3, fp, '\\n');\n+\tstrbuf_trim(&l3);\n+\tfclose(fp);\n+\n+\tif (starts_with(l1.buf, \"From \") || starts_with(l1.buf, \"From: \"))\n+\t\tret = PATCH_FORMAT_MBOX;\n+\telse if (l1.len && l2.len && l3.len && is_email(paths->items->string))\n+\t\tret = PATCH_FORMAT_MBOX;\n+\n+done:\n+\tstrbuf_release(&l1);\n+\tstrbuf_release(&l2);\n+\tstrbuf_release(&l3);\n+\treturn ret;\n+}\n+\n /**\n  * Splits out individual patches from `paths`, where each path is either a mbox\n  * file or a Maildir. Return 0 on success, -1 on failure.\n@@ -174,6 +264,14 @@ static int split_patches(struct am_state *state, enum patch_format patch_format,\n static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t\tstruct string_list *paths)\n {\n+\tif (!patch_format)\n+\t\tpatch_format = detect_patch_format(paths);\n+\n+\tif (!patch_format) {\n+\t\tfprintf_ln(stderr, _(\"Patch format detection failed.\"));\n+\t\texit(128);\n+\t}\n+\n \tif (mkdir(state->dir.buf, 0777) < 0 && errno != EEXIST)\n \t\tdie_errno(_(\"failed to create directory '%s'\"), state->dir.buf);\n \n-- \n2.1.4\n"},{"id":"263567","messageId":"1434018125-31804-8-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 07/19] am: extract patch, message and authorship with git-mailinfo","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:53Z","receivedAt":"2015-06-11T10:21:53Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"For the purpose of applying the patch and committing the results,\nimplement extracting the patch data, commit message and authorship from\nan e-mail message using git-mailinfo.\n\ngit-mailinfo is run as a separate process, but ideally in the future,\nwe should be be able to access its functionality directly without\nspawning a new process.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Jeff King <peff@peff.net>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v2\n    \n    * use die_errno()\n    \n    * use '%*d' as the format specifier for msgnum()\n\n builtin/am.c | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 228 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 7379b97..a1db474 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -9,6 +9,23 @@\n #include \"parse-options.h\"\n #include \"dir.h\"\n #include \"run-command.h\"\n+#include \"quote.h\"\n+\n+/**\n+ * Returns 1 if the file is empty or does not exist, 0 otherwise.\n+ */\n+static int is_empty_file(const char *filename)\n+{\n+\tstruct stat st;\n+\n+\tif (stat(filename, &st) < 0) {\n+\t\tif (errno == ENOENT)\n+\t\t\treturn 1;\n+\t\tdie_errno(_(\"could not stat %s\"), filename);\n+\t}\n+\n+\treturn !st.st_size;\n+}\n \n enum patch_format {\n \tPATCH_FORMAT_UNKNOWN = 0,\n@@ -23,6 +40,12 @@ struct am_state {\n \tint cur;\n \tint last;\n \n+\t/* commit message and metadata */\n+\tstruct strbuf author_name;\n+\tstruct strbuf author_email;\n+\tstruct strbuf author_date;\n+\tstruct strbuf msg;\n+\n \t/* number of digits in patch filename */\n \tint prec;\n };\n@@ -35,6 +58,10 @@ static void am_state_init(struct am_state *state)\n \tmemset(state, 0, sizeof(*state));\n \n \tstrbuf_init(&state->dir, 0);\n+\tstrbuf_init(&state->author_name, 0);\n+\tstrbuf_init(&state->author_email, 0);\n+\tstrbuf_init(&state->author_date, 0);\n+\tstrbuf_init(&state->msg, 0);\n \tstate->prec = 4;\n }\n \n@@ -44,6 +71,10 @@ static void am_state_init(struct am_state *state)\n static void am_state_release(struct am_state *state)\n {\n \tstrbuf_release(&state->dir);\n+\tstrbuf_release(&state->author_name);\n+\tstrbuf_release(&state->author_email);\n+\tstrbuf_release(&state->author_date);\n+\tstrbuf_release(&state->msg);\n }\n \n /**\n@@ -93,6 +124,95 @@ static int read_state_file(struct strbuf *sb, const char *file, size_t hint, int\n }\n \n /**\n+ * Parses the \"author script\" `filename`, and sets state->author_name,\n+ * state->author_email and state->author_date accordingly. We are strict with\n+ * our parsing, as the author script is supposed to be eval'd, and loosely\n+ * parsing it may not give the results the user expects.\n+ *\n+ * The author script is of the format:\n+ *\n+ * \tGIT_AUTHOR_NAME='$author_name'\n+ * \tGIT_AUTHOR_EMAIL='$author_email'\n+ * \tGIT_AUTHOR_DATE='$author_date'\n+ *\n+ * where $author_name, $author_email and $author_date are quoted.\n+ */\n+static int read_author_script(struct am_state *state)\n+{\n+\tchar *value;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *filename = am_path(state, \"author-script\");\n+\tFILE *fp = fopen(filename, \"r\");\n+\tif (!fp) {\n+\t\tif (errno == ENOENT)\n+\t\t\treturn 0;\n+\t\tdie_errno(_(\"could not open '%s' for reading\"), filename);\n+\t}\n+\n+\tif (strbuf_getline(&sb, fp, '\\n'))\n+\t\treturn -1;\n+\tif (!skip_prefix(sb.buf, \"GIT_AUTHOR_NAME=\", (const char**) &value))\n+\t\treturn -1;\n+\tvalue = sq_dequote(value);\n+\tif (!value)\n+\t\treturn -1;\n+\tstrbuf_reset(&state->author_name);\n+\tstrbuf_addstr(&state->author_name, value);\n+\n+\tif (strbuf_getline(&sb, fp, '\\n'))\n+\t\treturn -1;\n+\tif (!skip_prefix(sb.buf, \"GIT_AUTHOR_EMAIL=\", (const char**) &value))\n+\t\treturn -1;\n+\tvalue = sq_dequote(value);\n+\tif (!value)\n+\t\treturn -1;\n+\tstrbuf_reset(&state->author_email);\n+\tstrbuf_addstr(&state->author_email, value);\n+\n+\tif (strbuf_getline(&sb, fp, '\\n'))\n+\t\treturn -1;\n+\tif (!skip_prefix(sb.buf, \"GIT_AUTHOR_DATE=\", (const char**) &value))\n+\t\treturn -1;\n+\tvalue = sq_dequote(value);\n+\tif (!value)\n+\t\treturn -1;\n+\tstrbuf_reset(&state->author_date);\n+\tstrbuf_addstr(&state->author_date, value);\n+\n+\tif (fgetc(fp) != EOF)\n+\t\treturn -1;\n+\n+\tfclose(fp);\n+\tstrbuf_release(&sb);\n+\treturn 0;\n+}\n+\n+/**\n+ * Saves state->author_name, state->author_email and state->author_date in\n+ * `filename` as an \"author script\", which is the format used by git-am.sh.\n+ */\n+static void write_author_script(const struct am_state *state)\n+{\n+\tstatic const char fmt[] = \"GIT_AUTHOR_NAME=%s\\n\"\n+\t\t\"GIT_AUTHOR_EMAIL=%s\\n\"\n+\t\t\"GIT_AUTHOR_DATE=%s\\n\";\n+\tstruct strbuf author_name = STRBUF_INIT;\n+\tstruct strbuf author_email = STRBUF_INIT;\n+\tstruct strbuf author_date = STRBUF_INIT;\n+\n+\tsq_quote_buf(&author_name, state->author_name.buf);\n+\tsq_quote_buf(&author_email, state->author_email.buf);\n+\tsq_quote_buf(&author_date, state->author_date.buf);\n+\n+\twrite_file(am_path(state, \"author-script\"), 1, fmt,\n+\t\t\tauthor_name.buf, author_email.buf, author_date.buf);\n+\n+\tstrbuf_release(&author_name);\n+\tstrbuf_release(&author_email);\n+\tstrbuf_release(&author_date);\n+}\n+\n+/**\n  * Loads state from disk.\n  */\n static void am_load(struct am_state *state)\n@@ -105,6 +225,11 @@ static void am_load(struct am_state *state)\n \tread_state_file(&sb, am_path(state, \"last\"), 8, 1);\n \tstate->last = strtol(sb.buf, NULL, 10);\n \n+\tif (read_author_script(state) < 0)\n+\t\tdie(_(\"could not parse author script\"));\n+\n+\tread_state_file(&state->msg, am_path(state, \"final-commit\"), 0, 0);\n+\n \tstrbuf_release(&sb);\n }\n \n@@ -293,6 +418,98 @@ static void am_next(struct am_state *state)\n {\n \tstate->cur++;\n \twrite_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n+\n+\tstrbuf_reset(&state->author_name);\n+\tstrbuf_reset(&state->author_email);\n+\tstrbuf_reset(&state->author_date);\n+\tunlink(am_path(state, \"author-script\"));\n+\n+\tstrbuf_reset(&state->msg);\n+\tunlink(am_path(state, \"final-commit\"));\n+}\n+\n+/**\n+ * Returns the filename of the current patch.\n+ */\n+static const char *msgnum(const struct am_state *state)\n+{\n+\tstatic struct strbuf sb = STRBUF_INIT;\n+\n+\tstrbuf_reset(&sb);\n+\tstrbuf_addf(&sb, \"%0*d\", state->prec, state->cur);\n+\n+\treturn sb.buf;\n+}\n+\n+/**\n+ * Parses `patch` using git-mailinfo. state->msg will be set to the patch\n+ * message. state->author_name, state->author_email, state->author_date will be\n+ * set to the patch author's name, email and date respectively. The patch's\n+ * body will be written to \"$state_dir/patch\", where $state_dir is the state\n+ * directory.\n+ *\n+ * Returns 1 if the patch should be skipped, 0 otherwise.\n+ */\n+static int parse_patch(struct am_state *state, const char *patch)\n+{\n+\tFILE *fp;\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tcp.git_cmd = 1;\n+\tcp.in = xopen(patch, O_RDONLY, 0);\n+\tcp.out = xopen(am_path(state, \"info\"), O_WRONLY | O_CREAT, 0777);\n+\n+\targv_array_push(&cp.args, \"mailinfo\");\n+\targv_array_push(&cp.args, am_path(state, \"msg\"));\n+\targv_array_push(&cp.args, am_path(state, \"patch\"));\n+\n+\tif (run_command(&cp) < 0)\n+\t\tdie(\"could not parse patch\");\n+\n+\tclose(cp.in);\n+\tclose(cp.out);\n+\n+\t/* Extract message and author information */\n+\tfp = xfopen(am_path(state, \"info\"), \"r\");\n+\twhile (!strbuf_getline(&sb, fp, '\\n')) {\n+\t\tconst char *x;\n+\n+\t\tif (skip_prefix(sb.buf, \"Subject: \", &x)) {\n+\t\t\tif (state->msg.len)\n+\t\t\t\tstrbuf_addch(&state->msg, '\\n');\n+\t\t\tstrbuf_addstr(&state->msg, x);\n+\t\t} else if (skip_prefix(sb.buf, \"Author: \", &x)) {\n+\t\t\tif (state->author_name.len)\n+\t\t\t\tstrbuf_addch(&state->author_name, '\\n');\n+\t\t\tstrbuf_addstr(&state->author_name, x);\n+\t\t} else if (skip_prefix(sb.buf, \"Email: \", &x)) {\n+\t\t\tif (state->author_email.len)\n+\t\t\t\tstrbuf_addch(&state->author_email, '\\n');\n+\t\t\tstrbuf_addstr(&state->author_email, x);\n+\t\t} else if (skip_prefix(sb.buf, \"Date: \", &x)) {\n+\t\t\tif (state->author_date.len)\n+\t\t\t\tstrbuf_addch(&state->author_date, '\\n');\n+\t\t\tstrbuf_addstr(&state->author_date, x);\n+\t\t}\n+\t}\n+\tfclose(fp);\n+\n+\t/* Skip pine's internal folder data */\n+\tif (!strcmp(state->author_name.buf, \"Mail System Internal Data\"))\n+\t\treturn 1;\n+\n+\tif (is_empty_file(am_path(state, \"patch\")))\n+\t\tdie(_(\"Patch is empty. Was it split wrong?\\n\"\n+\t\t\"If you would prefer to skip this patch, instead run \\\"git am --skip\\\".\\n\"\n+\t\t\"To restore the original branch and stop patching run \\\"git am --abort\\\".\"));\n+\n+\tstrbuf_addstr(&state->msg, \"\\n\\n\");\n+\tif (strbuf_read_file(&state->msg, am_path(state, \"msg\"), 0) < 0)\n+\t\tdie_errno(_(\"could not read '%s'\"), am_path(state, \"msg\"));\n+\tstripspace(&state->msg, 0);\n+\n+\treturn 0;\n }\n \n /**\n@@ -301,9 +518,20 @@ static void am_next(struct am_state *state)\n static void am_run(struct am_state *state)\n {\n \twhile (state->cur <= state->last) {\n+\t\tconst char *patch = am_path(state, msgnum(state));\n+\n+\t\tif (!file_exists(patch))\n+\t\t\tgoto next;\n+\n+\t\tif (parse_patch(state, patch))\n+\t\t\tgoto next; /* patch should be skipped */\n+\n+\t\twrite_author_script(state);\n+\t\twrite_file(am_path(state, \"final-commit\"), 1, \"%s\", state->msg.buf);\n \n \t\t/* TODO: Patch application not implemented yet */\n \n+next:\n \t\tam_next(state);\n \t}\n \n-- \n2.1.4\n"},{"id":"263565","messageId":"1434018125-31804-9-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 08/19] am: apply patch with git-apply","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:54Z","receivedAt":"2015-06-11T10:21:54Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Implement applying the patch to the index using git-apply.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 54 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex a1db474..b725a74 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -27,6 +27,18 @@ static int is_empty_file(const char *filename)\n \treturn !st.st_size;\n }\n \n+/**\n+ * Returns the first line of msg\n+ */\n+static const char *firstline(const char *msg)\n+{\n+\tstatic struct strbuf sb = STRBUF_INIT;\n+\n+\tstrbuf_reset(&sb);\n+\tstrbuf_add(&sb, msg, strchrnul(msg, '\\n') - msg);\n+\treturn sb.buf;\n+}\n+\n enum patch_format {\n \tPATCH_FORMAT_UNKNOWN = 0,\n \tPATCH_FORMAT_MBOX\n@@ -512,6 +524,29 @@ static int parse_patch(struct am_state *state, const char *patch)\n \treturn 0;\n }\n \n+/*\n+ * Applies current patch with git-apply. Returns 0 on success, -1 otherwise.\n+ */\n+static int run_apply(const struct am_state *state)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\tcp.git_cmd = 1;\n+\n+\targv_array_push(&cp.args, \"apply\");\n+\targv_array_push(&cp.args, \"--index\");\n+\targv_array_push(&cp.args, am_path(state, \"patch\"));\n+\n+\tif (run_command(&cp))\n+\t\treturn -1;\n+\n+\t/* Reload index as git-apply will have modified it. */\n+\tdiscard_cache();\n+\tread_cache();\n+\n+\treturn 0;\n+}\n+\n /**\n  * Applies all queued patches.\n  */\n@@ -529,7 +564,25 @@ static void am_run(struct am_state *state)\n \t\twrite_author_script(state);\n \t\twrite_file(am_path(state, \"final-commit\"), 1, \"%s\", state->msg.buf);\n \n-\t\t/* TODO: Patch application not implemented yet */\n+\t\tprintf_ln(_(\"Applying: %s\"), firstline(state->msg.buf));\n+\n+\t\tif (run_apply(state) < 0) {\n+\t\t\tint value;\n+\n+\t\t\tprintf_ln(_(\"Patch failed at %s %s\"), msgnum(state),\n+\t\t\t\t\tfirstline(state->msg.buf));\n+\n+\t\t\tif (!git_config_get_bool(\"advice.amworkdir\", &value) && !value)\n+\t\t\t\tprintf_ln(_(\"The copy of the patch that failed is found in: %s\"),\n+\t\t\t\t\t\tam_path(state, \"patch\"));\n+\n+\t\t\texit(128);\n+\t\t}\n+\n+\t\t/*\n+\t\t * TODO: After the patch has been applied to the index with\n+\t\t * git-apply, we need to make commit as well.\n+\t\t */\n \n next:\n \t\tam_next(state);\n-- \n2.1.4\n"},{"id":"263568","messageId":"1434018125-31804-10-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 09/19] am: commit applied patch","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:55Z","receivedAt":"2015-06-11T10:21:55Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Implement do_commit(), which commits the index which contains the\nresults of applying the patch, along with the extracted commit message\nand authorship information.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++----\n 1 file changed, 46 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex b725a74..ecc6d29 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -10,6 +10,9 @@\n #include \"dir.h\"\n #include \"run-command.h\"\n #include \"quote.h\"\n+#include \"cache-tree.h\"\n+#include \"refs.h\"\n+#include \"commit.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -548,6 +551,48 @@ static int run_apply(const struct am_state *state)\n }\n \n /**\n+ * Commits the current index with state->msg as the commit message and\n+ * state->author_name, state->author_email and state->author_date as the author\n+ * information.\n+ */\n+static void do_commit(const struct am_state *state)\n+{\n+\tunsigned char tree[20], parent[20], commit[20];\n+\tunsigned char *ptr;\n+\tstruct commit_list *parents = NULL;\n+\tconst char *reflog_msg, *author;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (write_cache_as_tree(tree, 0, NULL))\n+\t\tdie(_(\"git write-tree failed to write a tree\"));\n+\n+\tif (!get_sha1_commit(\"HEAD\", parent)) {\n+\t\tptr = parent;\n+\t\tcommit_list_insert(lookup_commit(parent), &parents);\n+\t} else {\n+\t\tptr = NULL;\n+\t\tfprintf_ln(stderr, _(\"applying to an empty history\"));\n+\t}\n+\n+\tauthor = fmt_ident(state->author_name.buf, state->author_email.buf,\n+\t\t\tstate->author_date.buf, IDENT_STRICT);\n+\n+\tif (commit_tree(state->msg.buf, state->msg.len, tree, parents, commit,\n+\t\t\t\tauthor, NULL))\n+\t\tdie(_(\"failed to write commit object\"));\n+\n+\treflog_msg = getenv(\"GIT_REFLOG_ACTION\");\n+\tif (!reflog_msg)\n+\t\treflog_msg = \"am\";\n+\n+\tstrbuf_addf(&sb, \"%s: %s\", reflog_msg, firstline(state->msg.buf));\n+\n+\tupdate_ref(sb.buf, \"HEAD\", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n+/**\n  * Applies all queued patches.\n  */\n static void am_run(struct am_state *state)\n@@ -579,10 +624,7 @@ static void am_run(struct am_state *state)\n \t\t\texit(128);\n \t\t}\n \n-\t\t/*\n-\t\t * TODO: After the patch has been applied to the index with\n-\t\t * git-apply, we need to make commit as well.\n-\t\t */\n+\t\tdo_commit(state);\n \n next:\n \t\tam_next(state);\n-- \n2.1.4\n"},{"id":"263566","messageId":"1434018125-31804-11-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 10/19] am: refresh the index at start","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:56Z","receivedAt":"2015-06-11T10:21:56Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"If a file is unchanged but stat-dirty, git-apply may erroneously fail to\napply patches, thinking that they conflict with a dirty working tree.\n\nAs such, since 2a6f08a (am: refresh the index at start and --resolved,\n2011-08-15), git-am will refresh the index before applying patches.\nRe-implement this behavior.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex ecc6d29..417bfde 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -13,6 +13,7 @@\n #include \"cache-tree.h\"\n #include \"refs.h\"\n #include \"commit.h\"\n+#include \"lockfile.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -457,6 +458,20 @@ static const char *msgnum(const struct am_state *state)\n }\n \n /**\n+ * Refresh and write index.\n+ */\n+static void refresh_and_write_cache(void)\n+{\n+\tstatic struct lock_file lock_file;\n+\n+\thold_locked_index(&lock_file, 1);\n+\trefresh_cache(REFRESH_QUIET);\n+\tif (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n+\t\tdie(_(\"unable to write index file\"));\n+\trollback_lock_file(&lock_file);\n+}\n+\n+/**\n  * Parses `patch` using git-mailinfo. state->msg will be set to the patch\n  * message. state->author_name, state->author_email, state->author_date will be\n  * set to the patch author's name, email and date respectively. The patch's\n@@ -597,6 +612,8 @@ static void do_commit(const struct am_state *state)\n  */\n static void am_run(struct am_state *state)\n {\n+\trefresh_and_write_cache();\n+\n \twhile (state->cur <= state->last) {\n \t\tconst char *patch = am_path(state, msgnum(state));\n \n@@ -678,6 +695,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, am_options, am_usage, 0);\n \n+\tif (read_index_preload(&the_index, NULL) < 0)\n+\t\tdie(_(\"failed to read the index\"));\n+\n \tif (am_in_progress(&state))\n \t\tam_load(&state);\n \telse {\n-- \n2.1.4\n"},{"id":"263569","messageId":"1434018125-31804-12-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 11/19] am: refuse to apply patches if index is dirty","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:57Z","receivedAt":"2015-06-11T10:21:57Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am\nwill refuse to apply patches if the index is dirty. Re-implement this\nbehavior.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 46 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 417bfde..234762c 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -14,6 +14,8 @@\n #include \"refs.h\"\n #include \"commit.h\"\n #include \"lockfile.h\"\n+#include \"diff.h\"\n+#include \"diffcore.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -472,6 +474,43 @@ static void refresh_and_write_cache(void)\n }\n \n /**\n+ * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn\n+ * branch, returns 1 if there are entries in the index, 0 otherwise. If an\n+ * strbuf is provided, the space-separated list of files that differ will be\n+ * appended to it.\n+ */\n+static int index_has_changes(struct strbuf *sb)\n+{\n+\tunsigned char head[GIT_SHA1_RAWSZ];\n+\tint i;\n+\n+\tif (!get_sha1_tree(\"HEAD\", head)) {\n+\t\tstruct diff_options opt;\n+\n+\t\tdiff_setup(&opt);\n+\t\tDIFF_OPT_SET(&opt, EXIT_WITH_STATUS);\n+\t\tif (!sb)\n+\t\t\tDIFF_OPT_SET(&opt, QUICK);\n+\t\tdo_diff_cache(head, &opt);\n+\t\tdiffcore_std(&opt);\n+\t\tfor (i = 0; sb && i < diff_queued_diff.nr; i++) {\n+\t\t\tif (i)\n+\t\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tstrbuf_addstr(sb, diff_queued_diff.queue[i]->two->path);\n+\t\t}\n+\t\tdiff_flush(&opt);\n+\t\treturn DIFF_OPT_TST(&opt, HAS_CHANGES) != 0;\n+\t} else {\n+\t\tfor (i = 0; sb && i < active_nr; i++) {\n+\t\t\tif (i)\n+\t\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tstrbuf_addstr(sb, active_cache[i]->name);\n+\t\t}\n+\t\treturn !!active_nr;\n+\t}\n+}\n+\n+/**\n  * Parses `patch` using git-mailinfo. state->msg will be set to the patch\n  * message. state->author_name, state->author_email, state->author_date will be\n  * set to the patch author's name, email and date respectively. The patch's\n@@ -612,8 +651,15 @@ static void do_commit(const struct am_state *state)\n  */\n static void am_run(struct am_state *state)\n {\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n \trefresh_and_write_cache();\n \n+\tif (index_has_changes(&sb))\n+\t\tdie(_(\"Dirty index: cannot apply patches (dirty: %s)\"), sb.buf);\n+\n+\tstrbuf_release(&sb);\n+\n \twhile (state->cur <= state->last) {\n \t\tconst char *patch = am_path(state, msgnum(state));\n \n-- \n2.1.4\n"},{"id":"263570","messageId":"1434018125-31804-13-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 12/19] am: implement --resolved/--continue","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:58Z","receivedAt":"2015-06-11T10:21:58Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 0c15cc9 (git-am: --resolved., 2005-11-16), git-am supported\nresuming from a failed patch application. The user will manually apply\nthe patch, and the run git am --resolved which will then commit the\nresulting index. Re-implement this feature by introducing am_resolve().\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 51 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 234762c..935ffcb 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -697,6 +697,34 @@ next:\n }\n \n /**\n+ * Resume the current am session after patch application failure. The user did\n+ * all the hard work, and we do not have to do any patch application. Just\n+ * trust and commit what the user has in the index and working tree.\n+ */\n+static void am_resolve(struct am_state *state)\n+{\n+\tprintf_ln(_(\"Applying: %s\"), firstline(state->msg.buf));\n+\n+\tif (!index_has_changes(NULL)) {\n+\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n+\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n+\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n+\t\texit(128);\n+\t}\n+\n+\tif (unmerged_cache()) {\n+\t\tprintf_ln(_(\"You still have unmerged paths in your index.\\n\"\n+\t\t\t\"Did you forget to use 'git add'?\"));\n+\t\texit(128);\n+\t}\n+\n+\tdo_commit(state);\n+\n+\tam_next(state);\n+\tam_run(state);\n+}\n+\n+/**\n  * parse_options() callback that validates and sets opt->value to the\n  * PATCH_FORMAT_* enum value corresponding to `arg`.\n  */\n@@ -711,17 +739,30 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int\n \treturn 0;\n }\n \n+enum resume_mode {\n+\tRESUME_FALSE = 0,\n+\tRESUME_RESOLVED\n+};\n+\n static struct am_state state;\n static int opt_patch_format;\n+static enum resume_mode opt_resume;\n \n static const char * const am_usage[] = {\n \tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n+\tN_(\"git am [options] --continue\"),\n \tNULL\n };\n \n static struct option am_options[] = {\n \tOPT_CALLBACK(0, \"patch-format\", &opt_patch_format, N_(\"format\"),\n \t\tN_(\"format the patch(es) are in\"), parse_opt_patchformat),\n+\tOPT_CMDMODE(0, \"continue\", &opt_resume,\n+\t\tN_(\"continue applying patches after resolving a conflict\"),\n+\t\tRESUME_RESOLVED),\n+\tOPT_CMDMODE('r', \"resolved\", &opt_resume,\n+\t\tN_(\"synonyms for --continue\"),\n+\t\tRESUME_RESOLVED),\n \tOPT_END()\n };\n \n@@ -762,7 +803,16 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tstring_list_clear(&paths, 0);\n \t}\n \n-\tam_run(&state);\n+\tswitch (opt_resume) {\n+\tcase RESUME_FALSE:\n+\t\tam_run(&state);\n+\t\tbreak;\n+\tcase RESUME_RESOLVED:\n+\t\tam_resolve(&state);\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"BUG: invalid resume value\");\n+\t}\n \n \tam_state_release(&state);\n \n-- \n2.1.4\n"},{"id":"263571","messageId":"1434018125-31804-14-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 13/19] am: implement --skip","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:21:59Z","receivedAt":"2015-06-11T10:21:59Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am\nsupported resuming from a failed patch application by skipping the\ncurrent patch. Re-implement this feature by introducing am_skip().\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 119 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 935ffcb..1434e68 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -16,6 +16,8 @@\n #include \"lockfile.h\"\n #include \"diff.h\"\n #include \"diffcore.h\"\n+#include \"unpack-trees.h\"\n+#include \"branch.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -725,6 +727,114 @@ static void am_resolve(struct am_state *state)\n }\n \n /**\n+ * Performs a checkout fast-forward from `head` to `remote`. If `reset` is\n+ * true, any unmerged entries will be discarded. Returns 0 on success, -1 on\n+ * failure.\n+ */\n+static int fast_forward_to(struct tree *head, struct tree *remote, int reset)\n+{\n+\tstruct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));\n+\tstruct unpack_trees_options opts;\n+\tstruct tree_desc t[2];\n+\n+\tif (parse_tree(head) || parse_tree(remote))\n+\t\treturn -1;\n+\n+\thold_locked_index(lock_file, 1);\n+\n+\trefresh_cache(REFRESH_QUIET);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = &the_index;\n+\topts.dst_index = &the_index;\n+\topts.update = 1;\n+\topts.merge = 1;\n+\topts.reset = reset;\n+\topts.fn = twoway_merge;\n+\tinit_tree_desc(&t[0], head->buffer, head->size);\n+\tinit_tree_desc(&t[1], remote->buffer, remote->size);\n+\n+\tif (unpack_trees(2, t, &opts)) {\n+\t\trollback_lock_file(lock_file);\n+\t\treturn -1;\n+\t}\n+\n+\tif (write_locked_index(&the_index, lock_file, COMMIT_LOCK))\n+\t\tdie(_(\"unable to write new index file\"));\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * Clean the index without touching entries that are not modified between\n+ * `head` and `remote`.\n+ */\n+static int clean_index(const unsigned char *head, const unsigned char *remote)\n+{\n+\tstruct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));\n+\tstruct tree *head_tree, *remote_tree, *index_tree;\n+\tunsigned char index[GIT_SHA1_RAWSZ];\n+\tstruct pathspec pathspec;\n+\n+\thead_tree = parse_tree_indirect(head);\n+\tif (!head_tree)\n+\t\treturn error(_(\"Could not parse object '%s'.\"), sha1_to_hex(head));\n+\n+\tremote_tree = parse_tree_indirect(remote);\n+\tif (!remote_tree)\n+\t\treturn error(_(\"Could not parse object '%s'.\"), sha1_to_hex(remote));\n+\n+\tread_cache_unmerged();\n+\n+\tif (fast_forward_to(head_tree, head_tree, 1))\n+\t\treturn -1;\n+\n+\tif (write_cache_as_tree(index, 0, NULL))\n+\t\treturn -1;\n+\n+\tindex_tree = parse_tree_indirect(index);\n+\tif (!index_tree)\n+\t\treturn error(_(\"Could not parse object '%s'.\"), sha1_to_hex(index));\n+\n+\tif (fast_forward_to(index_tree, remote_tree, 0))\n+\t\treturn -1;\n+\n+\tmemset(&pathspec, 0, sizeof(pathspec));\n+\n+\thold_locked_index(lock_file, 1);\n+\n+\tif (read_tree(remote_tree, 0, &pathspec)) {\n+\t\trollback_lock_file(lock_file);\n+\t\treturn -1;\n+\t}\n+\n+\tif (write_locked_index(&the_index, lock_file, COMMIT_LOCK))\n+\t\tdie(_(\"unable to write new index file\"));\n+\n+\tremove_branch_state();\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * Resume the current am session by skipping the current patch.\n+ */\n+static void am_skip(struct am_state *state)\n+{\n+\tunsigned char head[GIT_SHA1_RAWSZ];\n+\n+\tif (get_sha1(\"HEAD\", head))\n+\t\thashcpy(head, EMPTY_TREE_SHA1_BIN);\n+\n+\tif (clean_index(head, head))\n+\t\tdie(_(\"failed to clean index\"));\n+\n+\tam_next(state);\n+\tam_run(state);\n+}\n+\n+/**\n  * parse_options() callback that validates and sets opt->value to the\n  * PATCH_FORMAT_* enum value corresponding to `arg`.\n  */\n@@ -741,7 +851,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int\n \n enum resume_mode {\n \tRESUME_FALSE = 0,\n-\tRESUME_RESOLVED\n+\tRESUME_RESOLVED,\n+\tRESUME_SKIP\n };\n \n static struct am_state state;\n@@ -750,7 +861,7 @@ static enum resume_mode opt_resume;\n \n static const char * const am_usage[] = {\n \tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n-\tN_(\"git am [options] --continue\"),\n+\tN_(\"git am [options] (--continue | --skip)\"),\n \tNULL\n };\n \n@@ -763,6 +874,9 @@ static struct option am_options[] = {\n \tOPT_CMDMODE('r', \"resolved\", &opt_resume,\n \t\tN_(\"synonyms for --continue\"),\n \t\tRESUME_RESOLVED),\n+\tOPT_CMDMODE(0, \"skip\", &opt_resume,\n+\t\tN_(\"skip the current patch\"),\n+\t\tRESUME_SKIP),\n \tOPT_END()\n };\n \n@@ -810,6 +924,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \tcase RESUME_RESOLVED:\n \t\tam_resolve(&state);\n \t\tbreak;\n+\tcase RESUME_SKIP:\n+\t\tam_skip(&state);\n+\t\tbreak;\n \tdefault:\n \t\tdie(\"BUG: invalid resume value\");\n \t}\n-- \n2.1.4\n"},{"id":"263572","messageId":"1434018125-31804-15-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 14/19] am: implement --abort","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:22:00Z","receivedAt":"2015-06-11T10:22:00Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 3e5057a (git am --abort, 2008-07-16), git-am supported the --abort\noption that will rewind HEAD back to the original commit. Re-implement\nthis feature through am_abort().\n\nSince 7b3b7e3 (am --abort: keep unrelated commits since the last failure\nand warn, 2010-12-21), to prevent commits made since the last failure\nfrom being lost, git-am will not rewind HEAD back to the original\ncommit if HEAD moved since the last failure. Re-implement this through\nsafe_to_abort().\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 92 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1434e68..cdc07ab 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -409,6 +409,8 @@ static int split_patches(struct am_state *state, enum patch_format patch_format,\n static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t\tstruct string_list *paths)\n {\n+\tunsigned char curr_head[20];\n+\n \tif (!patch_format)\n \t\tpatch_format = detect_patch_format(paths);\n \n@@ -428,6 +430,14 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \twrite_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n \n \twrite_file(am_path(state, \"last\"), 1, \"%d\", state->last);\n+\n+\tif (!get_sha1(\"HEAD\", curr_head)) {\n+\t\twrite_file(am_path(state, \"abort-safety\"), 1, \"%s\", sha1_to_hex(curr_head));\n+\t\tupdate_ref(\"am\", \"ORIG_HEAD\", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n+\t} else {\n+\t\twrite_file(am_path(state, \"abort-safety\"), 1, \"%s\", \"\");\n+\t\tdelete_ref(\"ORIG_HEAD\", NULL, 0);\n+\t}\n }\n \n /**\n@@ -436,6 +446,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n  */\n static void am_next(struct am_state *state)\n {\n+\tunsigned char head[GIT_SHA1_RAWSZ];\n+\n \tstate->cur++;\n \twrite_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n \n@@ -446,6 +458,11 @@ static void am_next(struct am_state *state)\n \n \tstrbuf_reset(&state->msg);\n \tunlink(am_path(state, \"final-commit\"));\n+\n+\tif (!get_sha1(\"HEAD\", head))\n+\t\twrite_file(am_path(state, \"abort-safety\"), 1, \"%s\", sha1_to_hex(head));\n+\telse\n+\t\twrite_file(am_path(state, \"abort-safety\"), 1, \"%s\", \"\");\n }\n \n /**\n@@ -655,10 +672,14 @@ static void am_run(struct am_state *state)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \n+\tunlink(am_path(state, \"dirtyindex\"));\n+\n \trefresh_and_write_cache();\n \n-\tif (index_has_changes(&sb))\n+\tif (index_has_changes(&sb)) {\n+\t\twrite_file(am_path(state, \"dirtyindex\"), 1, \"t\");\n \t\tdie(_(\"Dirty index: cannot apply patches (dirty: %s)\"), sb.buf);\n+\t}\n \n \tstrbuf_release(&sb);\n \n@@ -834,6 +855,67 @@ static void am_skip(struct am_state *state)\n \tam_run(state);\n }\n \n+static int safe_to_abort(const struct am_state *state)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tunsigned char abort_safety[20], head[20];\n+\n+\tif (file_exists(am_path(state, \"dirtyindex\")))\n+\t\treturn 0;\n+\n+\tif (read_state_file(&sb, am_path(state, \"abort-safety\"), 40, 1) > 0) {\n+\t\tif (get_sha1_hex(sb.buf, abort_safety))\n+\t\t\tdie(_(\"could not parse %s\"), am_path(state, \"abort_safety\"));\n+\t} else\n+\t\thashclr(abort_safety);\n+\n+\tif (get_sha1(\"HEAD\", head))\n+\t\thashclr(head);\n+\n+\tif (!hashcmp(head, abort_safety))\n+\t\treturn 1;\n+\n+\terror(_(\"You seem to have moved HEAD since the last 'am' failure.\\n\"\n+\t\t\"Not rewinding to ORIG_HEAD\"));\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * Aborts the current am session if it is safe to do so.\n+ */\n+static void am_abort(struct am_state *state)\n+{\n+\tunsigned char curr_head[20], orig_head[20];\n+\tint has_curr_head, has_orig_head;\n+\tconst char *curr_branch;\n+\n+\tif (!safe_to_abort(state)) {\n+\t\tam_destroy(state);\n+\t\treturn;\n+\t}\n+\n+\tcurr_branch = resolve_refdup(\"HEAD\", 0, curr_head, NULL);\n+\thas_curr_head = !is_null_sha1(curr_head);\n+\tif (!has_curr_head)\n+\t\thashcpy(curr_head, EMPTY_TREE_SHA1_BIN);\n+\n+\thas_orig_head = !get_sha1(\"ORIG_HEAD\", orig_head);\n+\tif (!has_orig_head)\n+\t\thashcpy(orig_head, EMPTY_TREE_SHA1_BIN);\n+\n+\tclean_index(curr_head, orig_head);\n+\n+\tif (has_orig_head)\n+\t\tupdate_ref(\"am --abort\", \"HEAD\", orig_head,\n+\t\t\t\thas_curr_head ? curr_head : NULL, 0,\n+\t\t\t\tUPDATE_REFS_DIE_ON_ERR);\n+\telse if (curr_branch)\n+\t\tdelete_ref(curr_branch, NULL, REF_NODEREF);\n+\n+\tam_destroy(state);\n+}\n+\n /**\n  * parse_options() callback that validates and sets opt->value to the\n  * PATCH_FORMAT_* enum value corresponding to `arg`.\n@@ -852,7 +934,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int\n enum resume_mode {\n \tRESUME_FALSE = 0,\n \tRESUME_RESOLVED,\n-\tRESUME_SKIP\n+\tRESUME_SKIP,\n+\tRESUME_ABORT\n };\n \n static struct am_state state;\n@@ -861,7 +944,7 @@ static enum resume_mode opt_resume;\n \n static const char * const am_usage[] = {\n \tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n-\tN_(\"git am [options] (--continue | --skip)\"),\n+\tN_(\"git am [options] (--continue | --skip | --abort)\"),\n \tNULL\n };\n \n@@ -877,6 +960,9 @@ static struct option am_options[] = {\n \tOPT_CMDMODE(0, \"skip\", &opt_resume,\n \t\tN_(\"skip the current patch\"),\n \t\tRESUME_SKIP),\n+\tOPT_CMDMODE(0, \"abort\", &opt_resume,\n+\t\tN_(\"restore the original branch and abort the patching operation.\"),\n+\t\tRESUME_ABORT),\n \tOPT_END()\n };\n \n@@ -927,6 +1013,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \tcase RESUME_SKIP:\n \t\tam_skip(&state);\n \t\tbreak;\n+\tcase RESUME_ABORT:\n+\t\tam_abort(&state);\n+\t\tbreak;\n \tdefault:\n \t\tdie(\"BUG: invalid resume value\");\n \t}\n-- \n2.1.4\n"},{"id":"263573","messageId":"1434018125-31804-16-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 15/19] am: implement quiet option","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:22:01Z","receivedAt":"2015-06-11T10:22:01Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 0e987a1 (am, rebase: teach quiet option, 2009-06-16), git-am\nsupported the --quiet option and GIT_QUIET environment variable, and\nwhen told to be quiet, would only speak on failure. Re-implement this by\nintroducing the say() function, which works like fprintf_ln(), but would\nonly write to the stream when state->quiet is false.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 36 +++++++++++++++++++++++++++++++++---\n 1 file changed, 33 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex cdc07ab..795b672 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -68,6 +68,8 @@ struct am_state {\n \n \t/* number of digits in patch filename */\n \tint prec;\n+\n+\tint quiet;\n };\n \n /**\n@@ -75,6 +77,8 @@ struct am_state {\n  */\n static void am_state_init(struct am_state *state)\n {\n+\tconst char *quiet;\n+\n \tmemset(state, 0, sizeof(*state));\n \n \tstrbuf_init(&state->dir, 0);\n@@ -83,6 +87,10 @@ static void am_state_init(struct am_state *state)\n \tstrbuf_init(&state->author_date, 0);\n \tstrbuf_init(&state->msg, 0);\n \tstate->prec = 4;\n+\n+\tquiet = getenv(\"GIT_QUIET\");\n+\tif (quiet && *quiet)\n+\t\tstate->quiet = 1;\n }\n \n /**\n@@ -106,6 +114,22 @@ static inline const char *am_path(const struct am_state *state, const char *path\n }\n \n /**\n+ * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline\n+ * at the end.\n+ */\n+static void say(const struct am_state *state, FILE *fp, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\tif (!state->quiet) {\n+\t\tvfprintf(fp, fmt, ap);\n+\t\tputc('\\n', fp);\n+\t}\n+\tva_end(ap);\n+}\n+\n+/**\n  * Returns 1 if there is an am session in progress, 0 otherwise.\n  */\n static int am_in_progress(const struct am_state *state)\n@@ -250,6 +274,9 @@ static void am_load(struct am_state *state)\n \n \tread_state_file(&state->msg, am_path(state, \"final-commit\"), 0, 0);\n \n+\tread_state_file(&sb, am_path(state, \"quiet\"), 2, 1);\n+\tstate->quiet = !strcmp(sb.buf, \"t\");\n+\n \tstrbuf_release(&sb);\n }\n \n@@ -431,6 +458,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \n \twrite_file(am_path(state, \"last\"), 1, \"%d\", state->last);\n \n+\twrite_file(am_path(state, \"quiet\"), 1, state->quiet ? \"t\" : \"f\");\n+\n \tif (!get_sha1(\"HEAD\", curr_head)) {\n \t\twrite_file(am_path(state, \"abort-safety\"), 1, \"%s\", sha1_to_hex(curr_head));\n \t\tupdate_ref(\"am\", \"ORIG_HEAD\", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n@@ -644,7 +673,7 @@ static void do_commit(const struct am_state *state)\n \t\tcommit_list_insert(lookup_commit(parent), &parents);\n \t} else {\n \t\tptr = NULL;\n-\t\tfprintf_ln(stderr, _(\"applying to an empty history\"));\n+\t\tsay(state, stderr, _(\"applying to an empty history\"));\n \t}\n \n \tauthor = fmt_ident(state->author_name.buf, state->author_email.buf,\n@@ -695,7 +724,7 @@ static void am_run(struct am_state *state)\n \t\twrite_author_script(state);\n \t\twrite_file(am_path(state, \"final-commit\"), 1, \"%s\", state->msg.buf);\n \n-\t\tprintf_ln(_(\"Applying: %s\"), firstline(state->msg.buf));\n+\t\tsay(state, stdout, _(\"Applying: %s\"), firstline(state->msg.buf));\n \n \t\tif (run_apply(state) < 0) {\n \t\t\tint value;\n@@ -726,7 +755,7 @@ next:\n  */\n static void am_resolve(struct am_state *state)\n {\n-\tprintf_ln(_(\"Applying: %s\"), firstline(state->msg.buf));\n+\tsay(state, stdout, _(\"Applying: %s\"), firstline(state->msg.buf));\n \n \tif (!index_has_changes(NULL)) {\n \t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n@@ -949,6 +978,7 @@ static const char * const am_usage[] = {\n };\n \n static struct option am_options[] = {\n+\tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n \tOPT_CALLBACK(0, \"patch-format\", &opt_patch_format, N_(\"format\"),\n \t\tN_(\"format the patch(es) are in\"), parse_opt_patchformat),\n \tOPT_CMDMODE(0, \"continue\", &opt_resume,\n-- \n2.1.4\n"},{"id":"263574","messageId":"1434018125-31804-17-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 16/19] am: exit with user friendly message on patch failure","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:22:02Z","receivedAt":"2015-06-11T10:22:02Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since ced9456 (Give the user a hint for how to continue in the case that\ngit-am fails because it requires user intervention, 2006-05-02), git-am\nprints additional information on how the user can re-invoke git-am to\nresume patch application after resolving the failure. Re-implement this\nthrough the die_user_resolve() function.\n\nSince cc12005 (Make git rebase interactive help match documentation.,\n2006-05-13), git-am supports the --resolvemsg option which is used by\ngit-rebase to override the message printed out when git-am fails.\nRe-implement this option.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 26 +++++++++++++++++++++++---\n 1 file changed, 23 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 795b672..4cd21b8 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -70,6 +70,9 @@ struct am_state {\n \tint prec;\n \n \tint quiet;\n+\n+\t/* override error message when patch failure occurs */\n+\tconst char *resolvemsg;\n };\n \n /**\n@@ -629,6 +632,21 @@ static int parse_patch(struct am_state *state, const char *patch)\n \treturn 0;\n }\n \n+/**\n+ * Dies with a user-friendly message on how to proceed after resolving the\n+ * problem. This message can be overridden with state->resolvemsg.\n+ */\n+static void NORETURN die_user_resolve(const struct am_state *state)\n+{\n+\tif (state->resolvemsg)\n+\t\tprintf_ln(\"%s\", state->resolvemsg);\n+\telse\n+\t\tprintf_ln(_(\"When you have resolved this problem, run \\\"git am --continue\\\".\\n\"\n+\t\t\t\"If you prefer to skip this patch, run \\\"git am --skip\\\" instead.\\n\"\n+\t\t\t\"To restore the original branch and stop patching, run \\\"git am --abort\\\".\"));\n+\texit(128);\n+}\n+\n /*\n  * Applies current patch with git-apply. Returns 0 on success, -1 otherwise.\n  */\n@@ -736,7 +754,7 @@ static void am_run(struct am_state *state)\n \t\t\t\tprintf_ln(_(\"The copy of the patch that failed is found in: %s\"),\n \t\t\t\t\t\tam_path(state, \"patch\"));\n \n-\t\t\texit(128);\n+\t\t\tdie_user_resolve(state);\n \t\t}\n \n \t\tdo_commit(state);\n@@ -761,13 +779,13 @@ static void am_resolve(struct am_state *state)\n \t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n \t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n \t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n-\t\texit(128);\n+\t\tdie_user_resolve(state);\n \t}\n \n \tif (unmerged_cache()) {\n \t\tprintf_ln(_(\"You still have unmerged paths in your index.\\n\"\n \t\t\t\"Did you forget to use 'git add'?\"));\n-\t\texit(128);\n+\t\tdie_user_resolve(state);\n \t}\n \n \tdo_commit(state);\n@@ -981,6 +999,8 @@ static struct option am_options[] = {\n \tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n \tOPT_CALLBACK(0, \"patch-format\", &opt_patch_format, N_(\"format\"),\n \t\tN_(\"format the patch(es) are in\"), parse_opt_patchformat),\n+\tOPT_STRING(0, \"resolvemsg\", &state.resolvemsg, NULL,\n+\t\tN_(\"override error message when patch failure occurs\")),\n \tOPT_CMDMODE(0, \"continue\", &opt_resume,\n \t\tN_(\"continue applying patches after resolving a conflict\"),\n \t\tRESUME_RESOLVED),\n-- \n2.1.4\n"},{"id":"263575","messageId":"1434018125-31804-18-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 17/19] am: implement am --signoff","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:22:03Z","receivedAt":"2015-06-11T10:22:03Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am\nsupported the --signoff option which will append a signoff at the end of\nthe commit messsage. Re-implement this feature by calling\nappend_signoff() if the option is set.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 13 +++++++++++++\n 1 file changed, 13 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 4cd21b8..71fda16 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -18,6 +18,7 @@\n #include \"diffcore.h\"\n #include \"unpack-trees.h\"\n #include \"branch.h\"\n+#include \"sequencer.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -73,6 +74,8 @@ struct am_state {\n \n \t/* override error message when patch failure occurs */\n \tconst char *resolvemsg;\n+\n+\tint sign;\n };\n \n /**\n@@ -280,6 +283,9 @@ static void am_load(struct am_state *state)\n \tread_state_file(&sb, am_path(state, \"quiet\"), 2, 1);\n \tstate->quiet = !strcmp(sb.buf, \"t\");\n \n+\tread_state_file(&sb, am_path(state, \"sign\"), 2, 1);\n+\tstate->sign = !strcmp(sb.buf, \"t\");\n+\n \tstrbuf_release(&sb);\n }\n \n@@ -463,6 +469,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \n \twrite_file(am_path(state, \"quiet\"), 1, state->quiet ? \"t\" : \"f\");\n \n+\twrite_file(am_path(state, \"sign\"), 1, state->sign ? \"t\" : \"f\");\n+\n \tif (!get_sha1(\"HEAD\", curr_head)) {\n \t\twrite_file(am_path(state, \"abort-safety\"), 1, \"%s\", sha1_to_hex(curr_head));\n \t\tupdate_ref(\"am\", \"ORIG_HEAD\", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR);\n@@ -629,6 +637,9 @@ static int parse_patch(struct am_state *state, const char *patch)\n \t\tdie_errno(_(\"could not read '%s'\"), am_path(state, \"msg\"));\n \tstripspace(&state->msg, 0);\n \n+\tif (state->sign)\n+\t\tappend_signoff(&state->msg, 0, 0);\n+\n \treturn 0;\n }\n \n@@ -997,6 +1008,8 @@ static const char * const am_usage[] = {\n \n static struct option am_options[] = {\n \tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n+\tOPT_BOOL('s', \"signoff\", &state.sign,\n+\t\tN_(\"add a Signed-off-by line to the commit message\")),\n \tOPT_CALLBACK(0, \"patch-format\", &opt_patch_format, N_(\"format\"),\n \t\tN_(\"format the patch(es) are in\"), parse_opt_patchformat),\n \tOPT_STRING(0, \"resolvemsg\", &state.resolvemsg, NULL,\n-- \n2.1.4\n"},{"id":"263576","messageId":"1434018125-31804-19-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 18/19] cache-tree: introduce write_index_as_tree()","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:22:04Z","receivedAt":"2015-06-11T10:22:04Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"A caller may wish to write a temporary index as a tree. However,\nwrite_cache_as_tree() assumes that the index was read from, and will\nwrite to, the default index file path. Introduce write_index_as_tree()\nwhich removes this limitation by allowing the caller to specify its own\nindex_state and index file path.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n cache-tree.c | 29 +++++++++++++++++------------\n cache-tree.h |  1 +\n 2 files changed, 18 insertions(+), 12 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 32772b9..feace8b 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -592,7 +592,7 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat\n \treturn it;\n }\n \n-int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)\n+int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, const char *index_path, int flags, const char *prefix)\n {\n \tint entries, was_valid, newfd;\n \tstruct lock_file *lock_file;\n@@ -603,23 +603,23 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)\n \t */\n \tlock_file = xcalloc(1, sizeof(struct lock_file));\n \n-\tnewfd = hold_locked_index(lock_file, 1);\n+\tnewfd = hold_lock_file_for_update(lock_file, index_path, LOCK_DIE_ON_ERROR);\n \n-\tentries = read_cache();\n+\tentries = read_index_from(index_state, index_path);\n \tif (entries < 0)\n \t\treturn WRITE_TREE_UNREADABLE_INDEX;\n \tif (flags & WRITE_TREE_IGNORE_CACHE_TREE)\n-\t\tcache_tree_free(&(active_cache_tree));\n+\t\tcache_tree_free(&index_state->cache_tree);\n \n-\tif (!active_cache_tree)\n-\t\tactive_cache_tree = cache_tree();\n+\tif (!index_state->cache_tree)\n+\t\tindex_state->cache_tree = cache_tree();\n \n-\twas_valid = cache_tree_fully_valid(active_cache_tree);\n+\twas_valid = cache_tree_fully_valid(index_state->cache_tree);\n \tif (!was_valid) {\n-\t\tif (cache_tree_update(&the_index, flags) < 0)\n+\t\tif (cache_tree_update(index_state, flags) < 0)\n \t\t\treturn WRITE_TREE_UNMERGED_INDEX;\n \t\tif (0 <= newfd) {\n-\t\t\tif (!write_locked_index(&the_index, lock_file, COMMIT_LOCK))\n+\t\t\tif (!write_locked_index(index_state, lock_file, COMMIT_LOCK))\n \t\t\t\tnewfd = -1;\n \t\t}\n \t\t/* Not being able to write is fine -- we are only interested\n@@ -631,14 +631,14 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)\n \t}\n \n \tif (prefix) {\n-\t\tstruct cache_tree *subtree =\n-\t\t\tcache_tree_find(active_cache_tree, prefix);\n+\t\tstruct cache_tree *subtree;\n+\t\tsubtree = cache_tree_find(index_state->cache_tree, prefix);\n \t\tif (!subtree)\n \t\t\treturn WRITE_TREE_PREFIX_ERROR;\n \t\thashcpy(sha1, subtree->sha1);\n \t}\n \telse\n-\t\thashcpy(sha1, active_cache_tree->sha1);\n+\t\thashcpy(sha1, index_state->cache_tree->sha1);\n \n \tif (0 <= newfd)\n \t\trollback_lock_file(lock_file);\n@@ -646,6 +646,11 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)\n \treturn 0;\n }\n \n+int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)\n+{\n+\treturn write_index_as_tree(sha1, &the_index, get_index_file(), flags, prefix);\n+}\n+\n static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)\n {\n \tstruct tree_desc desc;\ndiff --git a/cache-tree.h b/cache-tree.h\nindex aa7b3e4..41c5746 100644\n--- a/cache-tree.h\n+++ b/cache-tree.h\n@@ -46,6 +46,7 @@ int update_main_cache_tree(int);\n #define WRITE_TREE_UNMERGED_INDEX (-2)\n #define WRITE_TREE_PREFIX_ERROR (-3)\n \n+int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, const char *index_path, int flags, const char *prefix);\n int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix);\n void prime_cache_tree(struct index_state *, struct tree *);\n \n-- \n2.1.4\n"},{"id":"263577","messageId":"1434018125-31804-20-git-send-email-pyokagan@gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH/WIP v2 19/19] am: implement 3-way merge","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-11T10:22:05Z","receivedAt":"2015-06-11T10:22:05Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am\nsupported the --3way option, and if set, would attempt to do a 3-way\nmerge if the initial patch application fails. Re-implement this feature\nthrough the fall_back_threeway() function.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 129 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 71fda16..8566d22 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -19,6 +19,7 @@\n #include \"unpack-trees.h\"\n #include \"branch.h\"\n #include \"sequencer.h\"\n+#include \"merge-recursive.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -76,6 +77,8 @@ struct am_state {\n \tconst char *resolvemsg;\n \n \tint sign;\n+\n+\tint threeway;\n };\n \n /**\n@@ -659,16 +662,32 @@ static void NORETURN die_user_resolve(const struct am_state *state)\n }\n \n /*\n- * Applies current patch with git-apply. Returns 0 on success, -1 otherwise.\n+ * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If\n+ * index_file is not NULL, the patch will be applied to that index.\n  */\n-static int run_apply(const struct am_state *state)\n+static int run_apply(const struct am_state *state, const char *index_file)\n {\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \n \tcp.git_cmd = 1;\n \n+\tif (index_file)\n+\t\targv_array_pushf(&cp.env_array, \"GIT_INDEX_FILE=%s\", index_file);\n+\n+\t/*\n+\t * If we are allowed to fall back on 3-way merge, don't give false\n+\t * errors during the initial attempt.\n+\t */\n+\tif (state->threeway && !index_file) {\n+\t\tcp.no_stdout = 1;\n+\t\tcp.no_stderr = 1;\n+\t}\n+\n \targv_array_push(&cp.args, \"apply\");\n-\targv_array_push(&cp.args, \"--index\");\n+\tif (index_file)\n+\t\targv_array_push(&cp.args, \"--cached\");\n+\telse\n+\t\targv_array_push(&cp.args, \"--index\");\n \targv_array_push(&cp.args, am_path(state, \"patch\"));\n \n \tif (run_command(&cp))\n@@ -676,8 +695,92 @@ static int run_apply(const struct am_state *state)\n \n \t/* Reload index as git-apply will have modified it. */\n \tdiscard_cache();\n+\tread_cache_from(index_file ? index_file : get_index_file());\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Builds a index that contains just the blobs needed for a 3way merge.\n+ */\n+static int build_fake_ancestor(const struct am_state *state, const char *index_file)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\tcp.git_cmd = 1;\n+\targv_array_push(&cp.args, \"apply\");\n+\targv_array_pushf(&cp.args, \"--build-fake-ancestor=%s\", index_file);\n+\targv_array_push(&cp.args, am_path(state, \"patch\"));\n+\n+\tif (run_command(&cp))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * Attempt a threeway merge, using index_path as the temporary index.\n+ */\n+static int fall_back_threeway(const struct am_state *state, const char *index_path)\n+{\n+\tunsigned char orig_tree[20], his_tree[20], our_tree[20];\n+\tconst unsigned char *bases[1] = {orig_tree};\n+\tstruct merge_options o;\n+\tstruct commit *result;\n+\n+\tif (get_sha1(\"HEAD\", our_tree) < 0)\n+\t\thashcpy(our_tree, EMPTY_TREE_SHA1_BIN);\n+\n+\tif (build_fake_ancestor(state, index_path))\n+\t\treturn error(\"could not build fake ancestor\");\n+\n+\tdiscard_cache();\n+\tread_cache_from(index_path);\n+\n+\tif (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))\n+\t\treturn error(_(\"Repository lacks necessary blobs to fall back on 3-way merge.\"));\n+\n+\tsay(state, stdout, _(\"Using index info to reconstruct a base tree...\"));\n+\n+\tif (!state->quiet) {\n+\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\t\tcp.git_cmd = 1;\n+\t\targv_array_pushf(&cp.env_array, \"GIT_INDEX_FILE=%s\", index_path);\n+\t\targv_array_pushl(&cp.args, \"diff-index\", \"--cached\",\n+\t\t\t\t\"--diff-filter=AM\", \"--name-status\", \"HEAD\", NULL);\n+\t\trun_command(&cp);\n+\t}\n+\n+\tif (run_apply(state, index_path))\n+\t\treturn error(_(\"Did you hand edit your patch?\\n\"\n+\t\t\t\t\"It does not apply to blobs recorded in its index.\"));\n+\n+\tif (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL))\n+\t\treturn error(\"could not write tree\");\n+\n+\tsay(state, stdout, _(\"Falling back to patching base and 3-way merge...\"));\n+\n+\tdiscard_cache();\n \tread_cache();\n \n+\t/*\n+\t * This is not so wrong. Depending on which base we picked, orig_tree\n+\t * may be wildly different from ours, but his_tree has the same set of\n+\t * wildly different changes in parts the patch did not touch, so\n+\t * recursive ends up canceling them, saying that we reverted all those\n+\t * changes.\n+\t */\n+\n+\tinit_merge_options(&o);\n+\n+\to.branch1 = \"HEAD\";\n+\to.branch2 = firstline(state->msg.buf);\n+\tif (state->quiet)\n+\t\to.verbosity = 0;\n+\n+\tif (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result))\n+\t\treturn error(_(\"Failed to merge in the changes.\"));\n+\n \treturn 0;\n }\n \n@@ -743,6 +846,7 @@ static void am_run(struct am_state *state)\n \n \twhile (state->cur <= state->last) {\n \t\tconst char *patch = am_path(state, msgnum(state));\n+\t\tint apply_status;\n \n \t\tif (!file_exists(patch))\n \t\t\tgoto next;\n@@ -755,7 +859,26 @@ static void am_run(struct am_state *state)\n \n \t\tsay(state, stdout, _(\"Applying: %s\"), firstline(state->msg.buf));\n \n-\t\tif (run_apply(state) < 0) {\n+\t\tapply_status = run_apply(state, NULL);\n+\n+\t\tif (apply_status && state->threeway) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\t\tstrbuf_addstr(&sb, am_path(state, \"patch-merge-index\"));\n+\t\t\tapply_status = fall_back_threeway(state, sb.buf);\n+\t\t\tstrbuf_release(&sb);\n+\n+\t\t\t/*\n+\t\t\t * Applying the patch to an earlier tree and merging\n+\t\t\t * the result may have produced the same tree as ours.\n+\t\t\t */\n+\t\t\tif (!apply_status && !index_has_changes(NULL)) {\n+\t\t\t\tsay(state, stdout, _(\"No changes -- Patch already applied.\"));\n+\t\t\t\tgoto next;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (apply_status) {\n \t\t\tint value;\n \n \t\t\tprintf_ln(_(\"Patch failed at %s %s\"), msgnum(state),\n@@ -1007,6 +1130,8 @@ static const char * const am_usage[] = {\n };\n \n static struct option am_options[] = {\n+\tOPT_BOOL('3', \"3way\", &state.threeway,\n+\t\tN_(\"allow fall back on 3way merging if needed\")),\n \tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n \tOPT_BOOL('s', \"signoff\", &state.sign,\n \t\tN_(\"add a Signed-off-by line to the commit message\")),\n-- \n2.1.4\n"},{"id":"263619","messageId":"CAGZ79kbLneLygPiDtL+nqwsxdvZEhT95vj0z9MbN0QrqvAmebA@mail.gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-5-git-send-email-pyokagan@gmail.com","subject":"Re: [PATCH/WIP v2 04/19] am: implement patch queue mechanism","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2015-06-11T17:39:50Z","receivedAt":"2015-06-11T17:39:50Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Thu, Jun 11, 2015 at 3:21 AM, Paul Tan <pyokagan@gmail.com> wrote:\n> git-am applies a series of patches. If the process terminates\n> abnormally, we want to be able to resume applying the series of patches.\n> This requires the session state to be saved in a persistent location.\n>\n> Implement the mechanism of a \"patch queue\", represented by 2 integers --\n> the index of the current patch we are applying and the index of the last\n> patch, as well as its lifecycle through the following functions:\n>\n> * am_setup(), which will set up the state directory\n>   $GIT_DIR/rebase-apply. As such, even if the process exits abnormally,\n>   the last-known state will still persist.\n>\n> * am_load(), which is called if there is an am session in\n>   progress, to load the last known state from the state directory so we\n>   can resume applying patches.\n>\n> * am_run(), which will do the actual patch application. After applying a\n>   patch, it calls am_next() to increment the current patch index. The\n>   logic for applying and committing a patch is not implemented yet.\n>\n> * am_destroy(), which is finally called when we successfully applied all\n>   the patches in the queue, to clean up by removing the state directory\n>   and its contents.\n>\n> Signed-off-by: Paul Tan <pyokagan@gmail.com>\n> ---\n>\n> Notes:\n>     v2\n>\n>     * Declare struct am_state as static\n>\n>  builtin/am.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 164 insertions(+)\n>\n> diff --git a/builtin/am.c b/builtin/am.c\n> index 0ccbe33..f061d21 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -6,6 +6,154 @@\n>  #include \"cache.h\"\n>  #include \"builtin.h\"\n>  #include \"exec_cmd.h\"\n> +#include \"parse-options.h\"\n> +#include \"dir.h\"\n> +\n> +struct am_state {\n\nDid you mean to declare all the functions below to be static or the\nstruct as well?\nReading further, you declared it static below. I thought maybe it'd be\nuseful to have definition\nand declaration up here, but having all declarations further below may\nbe even better.\n\n\n\n> +       /* state directory path */\n> +       struct strbuf dir;\n> +\n> +       /* current and last patch numbers, 1-indexed */\n> +       int cur;\n> +       int last;\n> +};\n> +\n> +/**\n> + * Initializes am_state with the default values.\n> + */\n> +static void am_state_init(struct am_state *state)\n> +{\n> +       memset(state, 0, sizeof(*state));\n> +\n> +       strbuf_init(&state->dir, 0);\n> +}\n> +\n> +/**\n> + * Release memory allocated by an am_state.\n> + */\n> +static void am_state_release(struct am_state *state)\n> +{\n> +       strbuf_release(&state->dir);\n> +}\n> +\n> +/**\n> + * Returns path relative to the am_state directory.\n> + */\n> +static inline const char *am_path(const struct am_state *state, const char *path)\n> +{\n> +       return mkpath(\"%s/%s\", state->dir.buf, path);\n> +}\n> +\n> +/**\n> + * Returns 1 if there is an am session in progress, 0 otherwise.\n> + */\n> +static int am_in_progress(const struct am_state *state)\n> +{\n> +       struct stat st;\n> +\n> +       if (lstat(state->dir.buf, &st) < 0 || !S_ISDIR(st.st_mode))\n> +               return 0;\n> +       if (lstat(am_path(state, \"last\"), &st) || !S_ISREG(st.st_mode))\n> +               return 0;\n> +       if (lstat(am_path(state, \"next\"), &st) || !S_ISREG(st.st_mode))\n> +               return 0;\n> +       return 1;\n> +}\n> +\n> +/**\n> + * Reads the contents of `file`. The third argument can be used to give a hint\n> + * about the file size, to avoid reallocs. Returns number of bytes read on\n> + * success, -1 if the file does not exist. If trim is set, trailing whitespace\n> + * will be removed from the file contents.\n> + */\n> +static int read_state_file(struct strbuf *sb, const char *file, size_t hint, int trim)\n> +{\n> +       strbuf_reset(sb);\n> +       if (strbuf_read_file(sb, file, hint) >= 0) {\n> +               if (trim)\n> +                       strbuf_rtrim(sb);\n> +\n> +               return sb->len;\n> +       }\n> +\n> +       if (errno == ENOENT)\n> +               return -1;\n> +\n> +       die_errno(_(\"could not read '%s'\"), file);\n> +}\n> +\n> +/**\n> + * Loads state from disk.\n> + */\n> +static void am_load(struct am_state *state)\n> +{\n> +       struct strbuf sb = STRBUF_INIT;\n> +\n> +       read_state_file(&sb, am_path(state, \"next\"), 8, 1);\n> +       state->cur = strtol(sb.buf, NULL, 10);\n> +\n> +       read_state_file(&sb, am_path(state, \"last\"), 8, 1);\n> +       state->last = strtol(sb.buf, NULL, 10);\n> +\n> +       strbuf_release(&sb);\n> +}\n> +\n> +/**\n> + * Remove the am_state directory.\n> + */\n> +static void am_destroy(const struct am_state *state)\n> +{\n> +       struct strbuf sb = STRBUF_INIT;\n> +\n> +       strbuf_addstr(&sb, state->dir.buf);\n> +       remove_dir_recursively(&sb, 0);\n> +       strbuf_release(&sb);\n> +}\n> +\n> +/**\n> + * Setup a new am session for applying patches\n> + */\n> +static void am_setup(struct am_state *state)\n> +{\n> +       if (mkdir(state->dir.buf, 0777) < 0 && errno != EEXIST)\n> +               die_errno(_(\"failed to create directory '%s'\"), state->dir.buf);\n> +\n> +       write_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n> +\n> +       write_file(am_path(state, \"last\"), 1, \"%d\", state->last);\n> +}\n> +\n> +/**\n> + * Increments the patch pointer, and cleans am_state for the application of the\n> + * next patch.\n> + */\n> +static void am_next(struct am_state *state)\n> +{\n> +       state->cur++;\n> +       write_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n> +}\n> +\n> +/**\n> + * Applies all queued patches.\n> + */\n> +static void am_run(struct am_state *state)\n> +{\n> +       while (state->cur <= state->last)\n> +               am_next(state);\n> +\n> +       am_destroy(state);\n> +}\n> +\n> +static struct am_state state;\n> +\n> +static const char * const am_usage[] = {\n> +       N_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n> +       NULL\n> +};\n> +\n> +static struct option am_options[] = {\n> +       OPT_END()\n> +};\n>\n>  int cmd_am(int argc, const char **argv, const char *prefix)\n>  {\n> @@ -16,5 +164,21 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n>                         die_errno(\"could not exec %s\", path);\n>         }\n>\n> +       git_config(git_default_config, NULL);\n> +\n> +       am_state_init(&state);\n> +       strbuf_addstr(&state.dir, git_path(\"rebase-apply\"));\n> +\n> +       argc = parse_options(argc, argv, prefix, am_options, am_usage, 0);\n> +\n> +       if (am_in_progress(&state))\n> +               am_load(&state);\n> +       else\n> +               am_setup(&state);\n> +\n> +       am_run(&state);\n> +\n> +       am_state_release(&state);\n> +\n>         return 0;\n>  }\n> --\n> 2.1.4\n>\n"},{"id":"263622","messageId":"CAGZ79kYUK=UhfR1CQREH9GLNuo_7=AmcGwMw15_f_zYFZLarsw@mail.gmail.com","threadId":"39596","inReplyTo":"1434018125-31804-6-git-send-email-pyokagan@gmail.com","subject":"Re: [PATCH/WIP v2 05/19] am: split out mbox/maildir patches with git-mailsplit","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2015-06-11T17:45:27Z","receivedAt":"2015-06-11T17:45:27Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Thu, Jun 11, 2015 at 3:21 AM, Paul Tan <pyokagan@gmail.com> wrote:\n> git-am.sh supports mbox, stgit and mercurial patches. Re-implement\n> support for splitting out mbox/maildirs using git-mailsplit, while also\n> implementing the framework required to support other patch formats in\n> the future.\n>\n> Re-implement support for the --patch-format option (since a5a6755\n> (git-am foreign patch support: introduce patch_format, 2009-05-27)) to\n> allow the user to choose between the different patch formats.\n>\n> Signed-off-by: Paul Tan <pyokagan@gmail.com>\n> ---\n>\n> Notes:\n>     v2\n>\n>     * Declare int opt_patchformat as static.\n>\n>     * Fix up indentation style for the switch()\n>\n>  builtin/am.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---\n>  1 file changed, 103 insertions(+), 4 deletions(-)\n>\n> diff --git a/builtin/am.c b/builtin/am.c\n> index f061d21..5198a8e 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -8,6 +8,12 @@\n>  #include \"exec_cmd.h\"\n>  #include \"parse-options.h\"\n>  #include \"dir.h\"\n> +#include \"run-command.h\"\n> +\n> +enum patch_format {\n> +       PATCH_FORMAT_UNKNOWN = 0,\n> +       PATCH_FORMAT_MBOX\n> +};\n>\n>  struct am_state {\n>         /* state directory path */\n> @@ -16,6 +22,9 @@ struct am_state {\n>         /* current and last patch numbers, 1-indexed */\n>         int cur;\n>         int last;\n> +\n> +       /* number of digits in patch filename */\n> +       int prec;\n>  };\n>\n>  /**\n> @@ -26,6 +35,7 @@ static void am_state_init(struct am_state *state)\n>         memset(state, 0, sizeof(*state));\n>\n>         strbuf_init(&state->dir, 0);\n> +       state->prec = 4;\n>  }\n>\n>  /**\n> @@ -111,13 +121,67 @@ static void am_destroy(const struct am_state *state)\n>  }\n>\n>  /**\n> + * Splits out individual patches from `paths`, where each path is either a mbox\n> + * file or a Maildir. Return 0 on success, -1 on failure.\n> + */\n> +static int split_patches_mbox(struct am_state *state, struct string_list *paths)\n> +{\n> +       struct child_process cp = CHILD_PROCESS_INIT;\n> +       struct string_list_item *item;\n> +       struct strbuf last = STRBUF_INIT;\n> +\n> +       cp.git_cmd = 1;\n> +       argv_array_push(&cp.args, \"mailsplit\");\n> +       argv_array_pushf(&cp.args, \"-d%d\", state->prec);\n> +       argv_array_pushf(&cp.args, \"-o%s\", state->dir.buf);\n> +       argv_array_push(&cp.args, \"-b\");\n> +       argv_array_push(&cp.args, \"--\");\n> +\n> +       for_each_string_list_item(item, paths)\n> +               argv_array_push(&cp.args, item->string);\n> +\n> +       if (capture_command(&cp, &last, 8))\n> +               return -1;\n> +\n> +       state->cur = 1;\n> +       state->last = strtol(last.buf, NULL, 10);\n> +\n> +       return 0;\n> +}\n> +\n> +/**\n> + * Splits out individual patches, of patch_format, contained within paths.\n> + * These patches will be stored in the state directory, with each patch's\n> + * filename being its index, padded to state->prec digits. state->cur will be\n> + * set to the index of the first patch, and state->last will be set to the\n> + * index of the last patch. Returns 0 on success, -1 on failure.\n> + */\n> +static int split_patches(struct am_state *state, enum patch_format patch_format,\n> +               struct string_list *paths)\n> +{\n> +       switch (patch_format) {\n> +       case PATCH_FORMAT_MBOX:\n> +               return split_patches_mbox(state, paths);\n> +       default:\n> +               die(\"BUG: invalid patch_format\");\n> +       }\n> +       return -1;\n> +}\n> +\n> +/**\n>   * Setup a new am session for applying patches\n>   */\n> -static void am_setup(struct am_state *state)\n> +static void am_setup(struct am_state *state, enum patch_format patch_format,\n> +               struct string_list *paths)\n>  {\n>         if (mkdir(state->dir.buf, 0777) < 0 && errno != EEXIST)\n>                 die_errno(_(\"failed to create directory '%s'\"), state->dir.buf);\n>\n> +       if (split_patches(state, patch_format, paths) < 0) {\n> +               am_destroy(state);\n> +               die(_(\"Failed to split patches.\"));\n> +       }\n> +\n>         write_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n>\n>         write_file(am_path(state, \"last\"), 1, \"%d\", state->last);\n> @@ -138,13 +202,33 @@ static void am_next(struct am_state *state)\n>   */\n>  static void am_run(struct am_state *state)\n>  {\n> -       while (state->cur <= state->last)\n> +       while (state->cur <= state->last) {\n> +\n> +               /* TODO: Patch application not implemented yet */\n> +\n>                 am_next(state);\n> +       }\n\nWhen reviewing the previous patch I did look at this loop for awhile confused,\nif you want to apply patches in am_next(state) and thought there might be\na better approach.\n\nMaybe you want to move this chunk with the TODO into the previous patch,\nso it's clear after reading the documentation of am_run, that the actual am is\nmissing there.\n\n\n>\n>         am_destroy(state);\n>  }\n>\n> +/**\n> + * parse_options() callback that validates and sets opt->value to the\n> + * PATCH_FORMAT_* enum value corresponding to `arg`.\n> + */\n> +static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)\n> +{\n> +       int *opt_value = opt->value;\n> +\n> +       if (!strcmp(arg, \"mbox\"))\n> +               *opt_value = PATCH_FORMAT_MBOX;\n> +       else\n> +               return -1;\n> +       return 0;\n> +}\n> +\n>  static struct am_state state;\n> +static int opt_patch_format;\n>\n>  static const char * const am_usage[] = {\n>         N_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n> @@ -152,6 +236,8 @@ static const char * const am_usage[] = {\n>  };\n>\n>  static struct option am_options[] = {\n> +       OPT_CALLBACK(0, \"patch-format\", &opt_patch_format, N_(\"format\"),\n> +               N_(\"format the patch(es) are in\"), parse_opt_patchformat),\n>         OPT_END()\n>  };\n>\n> @@ -173,8 +259,21 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n>\n>         if (am_in_progress(&state))\n>                 am_load(&state);\n> -       else\n> -               am_setup(&state);\n> +       else {\n> +               struct string_list paths = STRING_LIST_INIT_DUP;\n> +               int i;\n> +\n> +               for (i = 0; i < argc; i++) {\n> +                       if (is_absolute_path(argv[i]) || !prefix)\n> +                               string_list_append(&paths, argv[i]);\n> +                       else\n> +                               string_list_append(&paths, mkpath(\"%s/%s\", prefix, argv[i]));\n> +               }\n> +\n> +               am_setup(&state, opt_patch_format, &paths);\n> +\n> +               string_list_clear(&paths, 0);\n> +       }\n>\n>         am_run(&state);\n>\n> --\n> 2.1.4\n>\n"},{"id":"263829","messageId":"xmqqzj42kkol.fsf@gitster.dls.corp.google.com","threadId":"39596","inReplyTo":"1434018125-31804-4-git-send-email-pyokagan@gmail.com","subject":"Re: [PATCH/WIP v2 03/19] am: implement skeletal builtin am","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-06-14T22:08:10Z","receivedAt":"2015-06-14T22:08:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Paul Tan <pyokagan@gmail.com> writes:\n\n> +int cmd_am(int argc, const char **argv, const char *prefix)\n> +{\n> +\tif (!getenv(\"_GIT_USE_BUILTIN_AM\")) {\n> +\t\tconst char *path = mkpath(\"%s/git-am\", git_exec_path());\n> +\n> +\t\tif (sane_execvp(path, (char**) argv) < 0)\n\nStyle: \n\n\t\tif (sane_execvp(path, (char**)argv) < 0)\n\n> +\t\t\tdie_errno(\"could not exec %s\", path);\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> diff --git a/git.c b/git.c\n> index 44374b1..42328ed 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -370,6 +370,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n>  \n>  static struct cmd_struct commands[] = {\n>  \t{ \"add\", cmd_add, RUN_SETUP | NEED_WORK_TREE },\n> +\t{ \"am\", cmd_am, RUN_SETUP | NEED_WORK_TREE },\n\nWould this, especially having RUN_SETUP, keep the same behaviour\nwhen the command is run from a subdirectory of a working tree?\ne.g.\n\n\tsave messages to ./inbox\n        $ cd sub/dir\n        $ git am ../../inbox\n\n\n\n>  \t{ \"annotate\", cmd_annotate, RUN_SETUP },\n>  \t{ \"apply\", cmd_apply, RUN_SETUP_GENTLY },\n>  \t{ \"archive\", cmd_archive },\n"},{"id":"263830","messageId":"xmqqvbeqkkkv.fsf@gitster.dls.corp.google.com","threadId":"39596","inReplyTo":"1434018125-31804-8-git-send-email-pyokagan@gmail.com","subject":"Re: [PATCH/WIP v2 07/19] am: extract patch, message and authorship with git-mailinfo","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-06-14T22:10:24Z","receivedAt":"2015-06-14T22:10:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Paul Tan <pyokagan@gmail.com> writes:\n\n> For the purpose of applying the patch and committing the results,\n> implement extracting the patch data, commit message and authorship from\n> an e-mail message using git-mailinfo.\n>\n> git-mailinfo is run as a separate process, but ideally in the future,\n> we should be be able to access its functionality directly without\n> spawning a new process.\n>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\n> Helped-by: Jeff King <peff@peff.net>\n> Signed-off-by: Paul Tan <pyokagan@gmail.com>\n> ---\n>\n> Notes:\n>     v2\n>     \n>     * use die_errno()\n>     \n>     * use '%*d' as the format specifier for msgnum()\n>\n>  builtin/am.c | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 228 insertions(+)\n>\n> diff --git a/builtin/am.c b/builtin/am.c\n> index 7379b97..a1db474 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -9,6 +9,23 @@\n>  #include \"parse-options.h\"\n>  #include \"dir.h\"\n>  #include \"run-command.h\"\n> +#include \"quote.h\"\n> +\n> +/**\n> + * Returns 1 if the file is empty or does not exist, 0 otherwise.\n> + */\n> +static int is_empty_file(const char *filename)\n> +{\n> +\tstruct stat st;\n> +\n> +\tif (stat(filename, &st) < 0) {\n> +\t\tif (errno == ENOENT)\n> +\t\t\treturn 1;\n> +\t\tdie_errno(_(\"could not stat %s\"), filename);\n> +\t}\n> +\n> +\treturn !st.st_size;\n> +}\n>  \n>  enum patch_format {\n>  \tPATCH_FORMAT_UNKNOWN = 0,\n> @@ -23,6 +40,12 @@ struct am_state {\n>  \tint cur;\n>  \tint last;\n>  \n> +\t/* commit message and metadata */\n> +\tstruct strbuf author_name;\n> +\tstruct strbuf author_email;\n> +\tstruct strbuf author_date;\n> +\tstruct strbuf msg;\n> +\n>  \t/* number of digits in patch filename */\n>  \tint prec;\n>  };\n> @@ -35,6 +58,10 @@ static void am_state_init(struct am_state *state)\n>  \tmemset(state, 0, sizeof(*state));\n>  \n>  \tstrbuf_init(&state->dir, 0);\n> +\tstrbuf_init(&state->author_name, 0);\n> +\tstrbuf_init(&state->author_email, 0);\n> +\tstrbuf_init(&state->author_date, 0);\n> +\tstrbuf_init(&state->msg, 0);\n>  \tstate->prec = 4;\n>  }\n>  \n> @@ -44,6 +71,10 @@ static void am_state_init(struct am_state *state)\n>  static void am_state_release(struct am_state *state)\n>  {\n>  \tstrbuf_release(&state->dir);\n> +\tstrbuf_release(&state->author_name);\n> +\tstrbuf_release(&state->author_email);\n> +\tstrbuf_release(&state->author_date);\n> +\tstrbuf_release(&state->msg);\n>  }\n>  \n>  /**\n> @@ -93,6 +124,95 @@ static int read_state_file(struct strbuf *sb, const char *file, size_t hint, int\n>  }\n>  \n>  /**\n> + * Parses the \"author script\" `filename`, and sets state->author_name,\n> + * state->author_email and state->author_date accordingly. We are strict with\n> + * our parsing, as the author script is supposed to be eval'd, and loosely\n> + * parsing it may not give the results the user expects.\n> + *\n> + * The author script is of the format:\n> + *\n> + * \tGIT_AUTHOR_NAME='$author_name'\n\nIt seems that you have SP * SP TAB GIT_AUTHOR_... here; lose SP\nbefore the TAB?\n\n> +\tif (!skip_prefix(sb.buf, \"GIT_AUTHOR_NAME=\", (const char**) &value))\n\nStyle:\n\n\tif (!skip_prefix(sb.buf, \"GIT_AUTHOR_NAME=\", (const char **)&value))\n"},{"id":"263844","messageId":"CACRoPnR28kNvxk6sdYaNkPYx7wi=j4wCbaXYrx62AGhjpQiPVA@mail.gmail.com","threadId":"39596","inReplyTo":"xmqqzj42kkol.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/WIP v2 03/19] am: implement skeletal builtin am","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-15T09:49:57Z","receivedAt":"2015-06-15T09:49:57Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"On Mon, Jun 15, 2015 at 6:08 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Paul Tan <pyokagan@gmail.com> writes:\n>> diff --git a/git.c b/git.c\n>> index 44374b1..42328ed 100644\n>> --- a/git.c\n>> +++ b/git.c\n>> @@ -370,6 +370,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n>>\n>>  static struct cmd_struct commands[] = {\n>>       { \"add\", cmd_add, RUN_SETUP | NEED_WORK_TREE },\n>> +     { \"am\", cmd_am, RUN_SETUP | NEED_WORK_TREE },\n>\n> Would this, especially having RUN_SETUP, keep the same behaviour\n> when the command is run from a subdirectory of a working tree?\n> e.g.\n>\n>         save messages to ./inbox\n>         $ cd sub/dir\n>         $ git am ../../inbox\n>\n\nYes, in 05/19, where the splitting of patches is implemented, we\nprefix the mbox paths with the path to the working tree.\n\nThere are also tests in t4150 to catch this, introduced in bb034f8\n(am: read from the right mailbox when started from a subdirectory,\n2008-03-04).\n\nThanks,\nPaul\n"},{"id":"263845","messageId":"CACRoPnQ5=wYv8S8ZoijNK9-XwQQTwC_fVKZNYFVMpPFMCAnWjw@mail.gmail.com","threadId":"39596","inReplyTo":"CAGZ79kYUK=UhfR1CQREH9GLNuo_7=AmcGwMw15_f_zYFZLarsw@mail.gmail.com","subject":"Re: [PATCH/WIP v2 05/19] am: split out mbox/maildir patches with git-mailsplit","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-15T10:08:22Z","receivedAt":"2015-06-15T10:08:22Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"On Fri, Jun 12, 2015 at 1:45 AM, Stefan Beller <sbeller@google.com> wrote:\n> On Thu, Jun 11, 2015 at 3:21 AM, Paul Tan <pyokagan@gmail.com> wrote:\n>> @@ -138,13 +202,33 @@ static void am_next(struct am_state *state)\n>>   */\n>>  static void am_run(struct am_state *state)\n>>  {\n>> -       while (state->cur <= state->last)\n>> +       while (state->cur <= state->last) {\n>> +\n>> +               /* TODO: Patch application not implemented yet */\n>> +\n>>                 am_next(state);\n>> +       }\n>\n> When reviewing the previous patch I did look at this loop for awhile confused,\n> if you want to apply patches in am_next(state) and thought there might be\n> a better approach.\n>\n> Maybe you want to move this chunk with the TODO into the previous patch,\n> so it's clear after reading the documentation of am_run, that the actual am is\n> missing there.\n\nAh right, this is a mistake. This comment should be in the previous patch.\n\nThanks,\nPaul\n"},{"id":"263847","messageId":"CACRoPnS3QwoWoebDpz_aBeoGx91pdP68f63Qp5KxLk9BCvc7DQ@mail.gmail.com","threadId":"39596","inReplyTo":"CAGZ79kbLneLygPiDtL+nqwsxdvZEhT95vj0z9MbN0QrqvAmebA@mail.gmail.com","subject":"Re: [PATCH/WIP v2 04/19] am: implement patch queue mechanism","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-15T10:46:27Z","receivedAt":"2015-06-15T10:46:27Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"On Fri, Jun 12, 2015 at 1:39 AM, Stefan Beller <sbeller@google.com> wrote:\n> On Thu, Jun 11, 2015 at 3:21 AM, Paul Tan <pyokagan@gmail.com> wrote:\n>> Notes:\n>>     v2\n>>\n>>     * Declare struct am_state as static\n>>\n>>  builtin/am.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>>  1 file changed, 164 insertions(+)\n>>\n>> diff --git a/builtin/am.c b/builtin/am.c\n>> index 0ccbe33..f061d21 100644\n>> --- a/builtin/am.c\n>> +++ b/builtin/am.c\n>> @@ -6,6 +6,154 @@\n>>  #include \"cache.h\"\n>>  #include \"builtin.h\"\n>>  #include \"exec_cmd.h\"\n>> +#include \"parse-options.h\"\n>> +#include \"dir.h\"\n>> +\n>> +struct am_state {\n>\n> Did you mean to declare all the functions below to be static or the\n> struct as well?\n\nWell, everything in this file, with the exception of the cmd_am()\nentry point, should have static linkage.\n\nThe changelog comment was referring to [1], but I should have made it\nclearer. Sorry if it was confusing.\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/270048/focus=270205\n\n> Reading further, you declared it static below. I thought maybe it'd be\n> useful to have definition\n> and declaration up here, but having all declarations further below may\n> be even better.\n\nRight, I aimed to have a strict separation between \"git-am: the\nfunctionality\" and \"git-am: the command-line interface\", where the\nlatter depends on the former, and not the other way round (or have\ncircular dependencies). The former perhaps could even be moved into\nlibgit.a in the future.\n\nThanks,\nPaul\n"},{"id":"263866","messageId":"xmqqwpz4ao6l.fsf@gitster.dls.corp.google.com","threadId":"39596","inReplyTo":"CACRoPnR28kNvxk6sdYaNkPYx7wi=j4wCbaXYrx62AGhjpQiPVA@mail.gmail.com","subject":"Re: [PATCH/WIP v2 03/19] am: implement skeletal builtin am","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-06-15T17:14:58Z","receivedAt":"2015-06-15T17:14:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Paul Tan <pyokagan@gmail.com> writes:\n\n> On Mon, Jun 15, 2015 at 6:08 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Paul Tan <pyokagan@gmail.com> writes:\n>>> diff --git a/git.c b/git.c\n>>> index 44374b1..42328ed 100644\n>>> --- a/git.c\n>>> +++ b/git.c\n>>> @@ -370,6 +370,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n>>>\n>>>  static struct cmd_struct commands[] = {\n>>>       { \"add\", cmd_add, RUN_SETUP | NEED_WORK_TREE },\n>>> +     { \"am\", cmd_am, RUN_SETUP | NEED_WORK_TREE },\n>>\n>> Would this, especially having RUN_SETUP, keep the same behaviour\n>> when the command is run from a subdirectory of a working tree?\n>> e.g.\n>>\n>>         save messages to ./inbox\n>>         $ cd sub/dir\n>>         $ git am ../../inbox\n>>\n>\n> Yes, in 05/19, where the splitting of patches is implemented, we\n> prefix the mbox paths with the path to the working tree.\n\nI wasn't wondering about your new code.\n\nThe scripted Porcelain is spawned after applying patches 1-3 from\nhere, when you do not have _GIT_USE_BUILTIN_AM exported.  Haven't\nRUN_SETUP code did its thing by that time?\n"},{"id":"263867","messageId":"CACRoPnS9fkNQTB8bEKdOBqFgV1E1Mff7oE8rBFDUK+c-woxqZg@mail.gmail.com","threadId":"39596","inReplyTo":"xmqqwpz4ao6l.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/WIP v2 03/19] am: implement skeletal builtin am","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-15T17:20:17Z","receivedAt":"2015-06-15T17:20:17Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"On Tue, Jun 16, 2015 at 1:14 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Paul Tan <pyokagan@gmail.com> writes:\n>\n>> On Mon, Jun 15, 2015 at 6:08 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> Paul Tan <pyokagan@gmail.com> writes:\n>>>> diff --git a/git.c b/git.c\n>>>> index 44374b1..42328ed 100644\n>>>> --- a/git.c\n>>>> +++ b/git.c\n>>>> @@ -370,6 +370,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n>>>>\n>>>>  static struct cmd_struct commands[] = {\n>>>>       { \"add\", cmd_add, RUN_SETUP | NEED_WORK_TREE },\n>>>> +     { \"am\", cmd_am, RUN_SETUP | NEED_WORK_TREE },\n>>>\n>>> Would this, especially having RUN_SETUP, keep the same behaviour\n>>> when the command is run from a subdirectory of a working tree?\n>>> e.g.\n>>>\n>>>         save messages to ./inbox\n>>>         $ cd sub/dir\n>>>         $ git am ../../inbox\n>>>\n>>\n>> Yes, in 05/19, where the splitting of patches is implemented, we\n>> prefix the mbox paths with the path to the working tree.\n>\n> I wasn't wondering about your new code.\n>\n> The scripted Porcelain is spawned after applying patches 1-3 from\n> here, when you do not have _GIT_USE_BUILTIN_AM exported.  Haven't\n> RUN_SETUP code did its thing by that time?\n\nAh right, the RUN_SETUP code would have chdir()-ed to the working\ndirectory root, so git-am.sh will be unable to find the original\nworking directory. To aid it, we would have to chdir() back to the\noriginal working directory, and unset GIT_DIR.\n\nThanks,\nPaul\n"},{"id":"263870","messageId":"xmqqoakgamcj.fsf@gitster.dls.corp.google.com","threadId":"39596","inReplyTo":"CACRoPnS9fkNQTB8bEKdOBqFgV1E1Mff7oE8rBFDUK+c-woxqZg@mail.gmail.com","subject":"Re: [PATCH/WIP v2 03/19] am: implement skeletal builtin am","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-06-15T17:54:36Z","receivedAt":"2015-06-15T17:54:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Paul Tan <pyokagan@gmail.com> writes:\n\n>> The scripted Porcelain is spawned after applying patches 1-3 from\n>> here, when you do not have _GIT_USE_BUILTIN_AM exported.  Haven't\n>> RUN_SETUP code did its thing by that time?\n>\n> Ah right, the RUN_SETUP code would have chdir()-ed to the working\n> directory root, so git-am.sh will be unable to find the original\n> working directory. To aid it, we would have to chdir() back to the\n> original working directory, and unset GIT_DIR.\n\nI do not think that is a correct workaround, though.  GIT_DIR may\nhave come from the end user, i.e.\n\n\t$ GIT_WORK_TREE=somewhere GIT_DIR=somewhere.else git am ...\n\nAs the RUN_SETUP|REQUIRE_WORK_TREE bit is merely a convenence in\ngit.c, one workable way to keep these dual implementations is to do\nwhat built-in commands used to do before these were invented.\nPerhaps studying how cmd_diff(), which is run from git.c without\neither RUN_SETUP or NEED_WORK_TREE, and taking good bits from it\nwould help.  I think the implementation roughly would look like\nthis:\n\n\tint cmd_am(int ac, const char **av, const char *prefix)\n\t{\n                /*\n                 * NEEDSWORK: once we retire the dual-mode\n                 * implementation, this preamble can be removed...                \n                 */\n\t\tif (... want to do scripted ...) {\n                \t... spawn the scripted thing ...\n\t\t}\n\t\tprefix = setup_git_directory();\n                setup_work_tree();\n                /* ... up to this point */\n\n\t\t... your real \"git am in C\" comes here ...\n\t}\n"},{"id":"264111","messageId":"CACRoPnRfTCyTj=WnygzwSNu7s_2iGrtygPEh7u2d1Hqchd1ZxQ@mail.gmail.com","threadId":"39596","inReplyTo":"xmqqoakgamcj.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/WIP v2 03/19] am: implement skeletal builtin am","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-06-18T08:44:32Z","receivedAt":"2015-06-18T08:44:32Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"On Tue, Jun 16, 2015 at 1:54 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Paul Tan <pyokagan@gmail.com> writes:\n>\n>>> The scripted Porcelain is spawned after applying patches 1-3 from\n>>> here, when you do not have _GIT_USE_BUILTIN_AM exported.  Haven't\n>>> RUN_SETUP code did its thing by that time?\n>>\n>> Ah right, the RUN_SETUP code would have chdir()-ed to the working\n>> directory root, so git-am.sh will be unable to find the original\n>> working directory. To aid it, we would have to chdir() back to the\n>> original working directory, and unset GIT_DIR.\n>\n> I do not think that is a correct workaround, though.  GIT_DIR may\n> have come from the end user, i.e.\n>\n>         $ GIT_WORK_TREE=somewhere GIT_DIR=somewhere.else git am ...\n\nAh, forgot about that ><\n\n> As the RUN_SETUP|REQUIRE_WORK_TREE bit is merely a convenence in\n> git.c, one workable way to keep these dual implementations is to do\n> what built-in commands used to do before these were invented.\n> Perhaps studying how cmd_diff(), which is run from git.c without\n> either RUN_SETUP or NEED_WORK_TREE, and taking good bits from it\n> would help.  I think the implementation roughly would look like\n> this:\n>\n>         int cmd_am(int ac, const char **av, const char *prefix)\n>         {\n>                 /*\n>                  * NEEDSWORK: once we retire the dual-mode\n>                  * implementation, this preamble can be removed...\n>                  */\n>                 if (... want to do scripted ...) {\n>                         ... spawn the scripted thing ...\n>                 }\n>                 prefix = setup_git_directory();\n>                 setup_work_tree();\n>                 /* ... up to this point */\n>\n>                 ... your real \"git am in C\" comes here ...\n>         }\n>\n\nAh OK. Just to be sure, I took a look at run_builtin() in git.c, and\nindeed it only just does:\n\nprefix = setup_git_directory();\ntrace_repo_setup(prefix);\nsetup_work_tree();\n\nwhich is not bad at all. Thanks.\n\nRegards,\nPaul\n"}]}