{"thread":{"id":"40011","subject":"[PATCH v7 01/45] wrapper: implement xopen()","startedAt":"2015-08-04T13:51:21Z","lastAt":"2015-08-04T13:52:06Z","messageCount":46,"participants":["Paul Tan"],"isPatch":true,"patchVersion":7,"patchTotal":45},"messages":[{"id":"267464","messageId":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":null,"subject":"[PATCH v7 00/45] Make git-am a builtin","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:21Z","receivedAt":"2015-08-04T13:51:21Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"This is a re-roll of [v6]. The changes are as follows:\n\n* removed am.threeWay config to match master\n\n* renamed am_state's \"append_signoff\" field to the shorter \"signoff\" to\n  preserve horizontal space.\n\n* Fix memory leak in am_abort() (Noticed by Stefan, thanks!)\n\n* Rebase onto master, and adjust to the changes introduced by\n  a5481a6 (convert \"enum date_mode\" into a struct, 2015-06-25).\n\nInterdiff below.\n\nPrevious versions:\n\n[WIP v1] http://thread.gmane.org/gmane.comp.version-control.git/270048\n[WIP v2] http://thread.gmane.org/gmane.comp.version-control.git/271381\n[WIP v3] http://thread.gmane.org/gmane.comp.version-control.git/271967\n[v4] http://thread.gmane.org/gmane.comp.version-control.git/272876\n[v5] http://thread.gmane.org/gmane.comp.version-control.git/273520\n[v6] http://thread.gmane.org/gmane.comp.version-control.git/274225\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 patch series rewrites git-am.sh into C builtin/am.c, and is part of my\nGSoC project to rewrite git-pull and git-am into C builtins[1].\n\n[1] https://gist.github.com/pyokagan/1b7b0d1f4dab6ba3cef1\n\n\nPaul Tan (45):\n  wrapper: implement xopen()\n  wrapper: implement xfopen()\n  builtin-am: implement skeletal builtin am\n  builtin-am: implement patch queue mechanism\n  builtin-am: split out mbox/maildir patches with git-mailsplit\n  builtin-am: auto-detect mbox patches\n  builtin-am: extract patch and commit info with git-mailinfo\n  builtin-am: apply patch with git-apply\n  builtin-am: implement committing applied patch\n  builtin-am: refuse to apply patches if index is dirty\n  builtin-am: implement --resolved/--continue\n  builtin-am: don't parse mail when resuming\n  builtin-am: implement --skip\n  builtin-am: implement --abort\n  builtin-am: reject patches when there's a session in progress\n  builtin-am: implement -q/--quiet\n  builtin-am: exit with user friendly message on failure\n  builtin-am: implement -s/--signoff\n  cache-tree: introduce write_index_as_tree()\n  builtin-am: implement --3way\n  builtin-am: implement --rebasing mode\n  builtin-am: bypass git-mailinfo when --rebasing\n  builtin-am: handle stray state directory\n  builtin-am: implement -u/--utf8\n  builtin-am: implement -k/--keep, --keep-non-patch\n  builtin-am: implement --[no-]message-id, am.messageid\n  builtin-am: support --keep-cr, am.keepcr\n  builtin-am: implement --[no-]scissors\n  builtin-am: pass git-apply's options to git-apply\n  builtin-am: implement --ignore-date\n  builtin-am: implement --committer-date-is-author-date\n  builtin-am: implement -S/--gpg-sign, commit.gpgsign\n  builtin-am: invoke post-rewrite hook\n  builtin-am: support automatic notes copying\n  builtin-am: invoke applypatch-msg hook\n  builtin-am: invoke pre-applypatch hook\n  builtin-am: invoke post-applypatch hook\n  builtin-am: rerere support\n  builtin-am: support and auto-detect StGit patches\n  builtin-am: support and auto-detect StGit series files\n  builtin-am: support and auto-detect mercurial patches\n  builtin-am: implement -i/--interactive\n  builtin-am: implement legacy -b/--binary option\n  builtin-am: check for valid committer ident\n  builtin-am: remove redirection to git-am.sh\n\n Makefile                                |    2 +-\n builtin.h                               |    1 +\n builtin/am.c                            | 2319 +++++++++++++++++++++++++++++++\n cache-tree.c                            |   29 +-\n cache-tree.h                            |    1 +\n git-am.sh => contrib/examples/git-am.sh |    0\n git-compat-util.h                       |    2 +\n git.c                                   |    1 +\n wrapper.c                               |   56 +\n 9 files changed, 2398 insertions(+), 13 deletions(-)\n create mode 100644 builtin/am.c\n rename git-am.sh => contrib/examples/git-am.sh (100%)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1116304..84d57d4 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -123,7 +123,7 @@ struct am_state {\n \tint interactive;\n \tint threeway;\n \tint quiet;\n-\tint append_signoff;\n+\tint signoff;\n \tint utf8;\n \tint keep; /* enum keep_type */\n \tint message_id;\n@@ -152,8 +152,6 @@ static void am_state_init(struct am_state *state, const char *dir)\n \n \tstate->prec = 4;\n \n-\tgit_config_get_bool(\"am.threeway\", &state->threeway);\n-\n \tstate->utf8 = 1;\n \n \tgit_config_get_bool(\"am.messageid\", &state->message_id);\n@@ -429,7 +427,7 @@ static void am_load(struct am_state *state)\n \tstate->quiet = !strcmp(sb.buf, \"t\");\n \n \tread_state_file(&sb, state, \"sign\", 1);\n-\tstate->append_signoff = !strcmp(sb.buf, \"t\");\n+\tstate->signoff = !strcmp(sb.buf, \"t\");\n \n \tread_state_file(&sb, state, \"utf8\", 1);\n \tstate->utf8 = !strcmp(sb.buf, \"t\");\n@@ -906,7 +904,7 @@ static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)\n \t\t\tif (tz > 0)\n \t\t\t\ttz2 = -tz2;\n \n-\t\t\tfprintf(out, \"Date: %s\\n\", show_date(timestamp, tz2, DATE_RFC2822));\n+\t\t\tfprintf(out, \"Date: %s\\n\", show_date(timestamp, tz2, DATE_MODE(RFC2822)));\n \t\t} else if (starts_with(sb.buf, \"# \")) {\n \t\t\tcontinue;\n \t\t} else {\n@@ -998,7 +996,7 @@ 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->append_signoff ? \"t\" : \"f\");\n+\twrite_file(am_path(state, \"sign\"), 1, state->signoff ? \"t\" : \"f\");\n \n \twrite_file(am_path(state, \"utf8\"), 1, state->utf8 ? \"t\" : \"f\");\n \n@@ -1284,7 +1282,7 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tdie_errno(_(\"could not read '%s'\"), am_path(state, \"msg\"));\n \tstripspace(&msg, 0);\n \n-\tif (state->append_signoff)\n+\tif (state->signoff)\n \t\tappend_signoff(&msg, 0, 0);\n \n \tassert(!state->author_name);\n@@ -1368,7 +1366,7 @@ static void get_commit_info(struct am_state *state, struct commit *commit)\n \t} else\n \t\tstate->author_email = xstrdup(\"\");\n \n-\tauthor_date = show_ident_date(&ident_split, DATE_NORMAL);\n+\tauthor_date = show_ident_date(&ident_split, DATE_MODE(NORMAL));\n \tstrbuf_addstr(&sb, author_date);\n \tassert(!state->author_date);\n \tstate->author_date = strbuf_detach(&sb, NULL);\n@@ -2065,7 +2063,7 @@ static void am_abort(struct am_state *state)\n {\n \tunsigned char curr_head[GIT_SHA1_RAWSZ], orig_head[GIT_SHA1_RAWSZ];\n \tint has_curr_head, has_orig_head;\n-\tconst char *curr_branch;\n+\tchar *curr_branch;\n \n \tif (!safe_to_abort(state)) {\n \t\tam_destroy(state);\n@@ -2092,6 +2090,7 @@ static void am_abort(struct am_state *state)\n \telse if (curr_branch)\n \t\tdelete_ref(curr_branch, NULL, REF_NODEREF);\n \n+\tfree(curr_branch);\n \tam_destroy(state);\n }\n \n@@ -2146,7 +2145,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL('3', \"3way\", &state.threeway,\n \t\t\tN_(\"allow fall back on 3way merging if needed\")),\n \t\tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n-\t\tOPT_BOOL('s', \"signoff\", &state.append_signoff,\n+\t\tOPT_BOOL('s', \"signoff\", &state.signoff,\n \t\t\tN_(\"add a Signed-off-by line to the commit message\")),\n \t\tOPT_BOOL('u', \"utf8\", &state.utf8,\n \t\t\tN_(\"recode into utf8 (default)\")),\n\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267419","messageId":"1438696326-19590-2-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 01/45] wrapper: implement xopen()","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:22Z","receivedAt":"2015-08-04T13:51:22Z","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>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n git-compat-util.h |  1 +\n wrapper.c         | 35 +++++++++++++++++++++++++++++++++++\n 2 files changed, 36 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex c6d391f..e168dfd 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -717,6 +717,7 @@ 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 void *xmmap_gently(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 ff49807..0a4502d 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -189,6 +189,41 @@ 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+\t/*\n+\t * va_arg() will have undefined behavior if the specified type is not\n+\t * compatible with the argument type. Since integers are promoted to\n+\t * ints, we fetch the next argument as an int, and then cast it to a\n+\t * mode_t to avoid undefined behavior.\n+\t */\n+\tva_start(ap, oflag);\n+\tif (oflag & O_CREAT)\n+\t\tmode = va_arg(ap, int);\n+\tva_end(ap);\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+\n+\t\tif ((oflag & O_RDWR) == O_RDWR)\n+\t\t\tdie_errno(_(\"could not open '%s' for reading and writing\"), path);\n+\t\telse if ((oflag & O_WRONLY) == O_WRONLY)\n+\t\t\tdie_errno(_(\"could not open '%s' for writing\"), path);\n+\t\telse\n+\t\t\tdie_errno(_(\"could not open '%s' for reading\"), 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.5.0.280.gd88bd6e\n"},{"id":"267420","messageId":"1438696326-19590-3-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 02/45] wrapper: implement xfopen()","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:23Z","receivedAt":"2015-08-04T13:51:23Z","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\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n git-compat-util.h |  1 +\n wrapper.c         | 21 +++++++++++++++++++++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex e168dfd..392da79 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -722,6 +722,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 0a4502d..e451463 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -346,6 +346,27 @@ 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+\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+\n+\t\tif (*mode && mode[1] == '+')\n+\t\t\tdie_errno(_(\"could not open '%s' for reading and writing\"), path);\n+\t\telse if (*mode == 'w' || *mode == 'a')\n+\t\t\tdie_errno(_(\"could not open '%s' for writing\"), path);\n+\t\telse\n+\t\t\tdie_errno(_(\"could not open '%s' for reading\"), path);\n+\t}\n+}\n+\n FILE *xfdopen(int fd, const char *mode)\n {\n \tFILE *stream = fdopen(fd, mode);\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267463","messageId":"1438696326-19590-4-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 03/45] builtin-am: implement skeletal builtin am","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:24Z","receivedAt":"2015-08-04T13:51:24Z","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\nSince git-am.sh cannot handle any environment modifications by\nsetup_git_directory(), \"am\" is declared with no setup flags in git.c. On\nthe other hand, to re-implement git-am.sh in builtin/am.c, we need to\nrun all the git dir and work tree setup logic that git.c typically does\nfor us. As such, we work around this temporarily by copying the logic in\ngit.c's run_builtin(), which is roughly:\n\n\tprefix = setup_git_directory();\n\ttrace_repo_setup(prefix);\n\tsetup_work_tree();\n\nThis redirection should be removed when all the features of git-am.sh\nhave been re-implemented in builtin/am.c.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n Makefile     |  1 +\n builtin.h    |  1 +\n builtin/am.c | 29 +++++++++++++++++++++++++++++\n git.c        |  6 ++++++\n 4 files changed, 37 insertions(+)\n create mode 100644 builtin/am.c\n\ndiff --git a/Makefile b/Makefile\nindex 7efedbe..da451f8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -813,6 +813,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 839483d..79aaf0a 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..fd32caf\n--- /dev/null\n+++ b/builtin/am.c\n@@ -0,0 +1,29 @@\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+\n+\t/*\n+\t * NEEDSWORK: Once all the features of git-am.sh have been\n+\t * re-implemented in builtin/am.c, this preamble can be removed.\n+\t */\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} else {\n+\t\tprefix = setup_git_directory();\n+\t\ttrace_repo_setup(prefix);\n+\t\tsetup_work_tree();\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/git.c b/git.c\nindex 55c327c..38d9ad5 100644\n--- a/git.c\n+++ b/git.c\n@@ -370,6 +370,12 @@ 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/*\n+\t * NEEDSWORK: Once the redirection to git-am.sh in builtin/am.c has\n+\t * been removed, this entry should be changed to\n+\t * RUN_SETUP | NEED_WORK_TREE\n+\t */\n+\t{ \"am\", cmd_am },\n \t{ \"annotate\", cmd_annotate, RUN_SETUP },\n \t{ \"apply\", cmd_apply, RUN_SETUP_GENTLY },\n \t{ \"archive\", cmd_archive },\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267462","messageId":"1438696326-19590-5-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 04/45] builtin-am: implement patch queue mechanism","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:25Z","receivedAt":"2015-08-04T13:51:25Z","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\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Stefan Beller <sbeller@google.com>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 177 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex fd32caf..ac172c4 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -6,9 +6,171 @@\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+\tchar *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. The state directory is set to\n+ * dir.\n+ */\n+static void am_state_init(struct am_state *state, const char *dir)\n+{\n+\tmemset(state, 0, sizeof(*state));\n+\n+\tassert(dir);\n+\tstate->dir = xstrdup(dir);\n+}\n+\n+/**\n+ * Releases memory allocated by an am_state.\n+ */\n+static void am_state_release(struct am_state *state)\n+{\n+\tfree(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, 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, &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` in the `state` directory into `sb`. Returns the\n+ * number of bytes read on success, -1 if the file does not exist. If `trim` is\n+ * set, trailing whitespace will be removed.\n+ */\n+static int read_state_file(struct strbuf *sb, const struct am_state *state,\n+\t\t\tconst char *file, int trim)\n+{\n+\tstrbuf_reset(sb);\n+\n+\tif (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {\n+\t\tif (trim)\n+\t\t\tstrbuf_trim(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'\"), am_path(state, 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+\tif (read_state_file(&sb, state, \"next\", 1) < 0)\n+\t\tdie(\"BUG: state file 'next' does not exist\");\n+\tstate->cur = strtol(sb.buf, NULL, 10);\n+\n+\tif (read_state_file(&sb, state, \"last\", 1) < 0)\n+\t\tdie(\"BUG: state file 'last' does not exist\");\n+\tstate->last = strtol(sb.buf, NULL, 10);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n+/**\n+ * Removes the am_state directory, forcefully terminating the current am\n+ * session.\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);\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, 0777) < 0 && errno != EEXIST)\n+\t\tdie_errno(_(\"failed to create directory '%s'\"), state->dir);\n+\n+\t/*\n+\t * NOTE: Since the \"next\" and \"last\" files determine if an am_state\n+\t * session is in progress, they should be written last.\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+}\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 mail.\n+ */\n+static void am_run(struct am_state *state)\n+{\n+\twhile (state->cur <= state->last) {\n+\n+\t\t/* NEEDSWORK: Patch application not implemented yet */\n+\n+\t\tam_next(state);\n+\t}\n+\n+\tam_destroy(state);\n+}\n \n int cmd_am(int argc, const char **argv, const char *prefix)\n {\n+\tstruct am_state state;\n+\n+\tconst char * const usage[] = {\n+\t\tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n+\t\tNULL\n+\t};\n+\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n \n \t/*\n \t * NEEDSWORK: Once all the features of git-am.sh have been\n@@ -25,5 +187,20 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tsetup_work_tree();\n \t}\n \n+\tgit_config(git_default_config, NULL);\n+\n+\tam_state_init(&state, git_path(\"rebase-apply\"));\n+\n+\targc = parse_options(argc, argv, prefix, options, 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.5.0.280.gd88bd6e\n"},{"id":"267421","messageId":"1438696326-19590-6-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 05/45] builtin-am: split out mbox/maildir patches with git-mailsplit","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:26Z","receivedAt":"2015-08-04T13:51:26Z","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\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 104 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex ac172c4..5f3c131 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@@ -28,6 +37,8 @@ static void am_state_init(struct am_state *state, const char *dir)\n \n \tassert(dir);\n \tstate->dir = xstrdup(dir);\n+\n+\tstate->prec = 4;\n }\n \n /**\n@@ -117,13 +128,71 @@ static void am_destroy(const struct am_state *state)\n }\n \n /**\n+ * Splits out individual email patches from `paths`, where each path is either\n+ * a mbox file or a Maildir. Returns 0 on success, -1 on failure.\n+ */\n+static int split_mail_mbox(struct am_state *state, const char **paths)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\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);\n+\targv_array_push(&cp.args, \"-b\");\n+\targv_array_push(&cp.args, \"--\");\n+\targv_array_pushv(&cp.args, paths);\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 a list of files/directories into individual email patches. Each path\n+ * in `paths` must be a file/directory that is formatted according to\n+ * `patch_format`.\n+ *\n+ * Once split out, the individual email patches will be stored in the state\n+ * directory, with each patch's filename being its index, padded to state->prec\n+ * digits.\n+ *\n+ * state->cur will be set to the index of the first mail, and state->last will\n+ * be set to the index of the last mail.\n+ *\n+ * Returns 0 on success, -1 on failure.\n+ */\n+static int split_mail(struct am_state *state, enum patch_format patch_format,\n+\t\t\tconst char **paths)\n+{\n+\tswitch (patch_format) {\n+\tcase PATCH_FORMAT_MBOX:\n+\t\treturn split_mail_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\t\tconst char **paths)\n {\n \tif (mkdir(state->dir, 0777) < 0 && errno != EEXIST)\n \t\tdie_errno(_(\"failed to create directory '%s'\"), state->dir);\n \n+\tif (split_mail(state, patch_format, paths) < 0) {\n+\t\tam_destroy(state);\n+\t\tdie(_(\"Failed to split patches.\"));\n+\t}\n+\n \t/*\n \t * NOTE: Since the \"next\" and \"last\" files determine if an am_state\n \t * session is in progress, they should be written last.\n@@ -159,9 +228,25 @@ static void am_run(struct am_state *state)\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 error(_(\"Invalid value for --patch-format: %s\"), arg);\n+\treturn 0;\n+}\n+\n int cmd_am(int argc, const char **argv, const char *prefix)\n {\n \tstruct am_state state;\n+\tint patch_format = PATCH_FORMAT_UNKNOWN;\n \n \tconst char * const usage[] = {\n \t\tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n@@ -169,6 +254,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t};\n \n \tstruct option options[] = {\n+\t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n+\t\t\tN_(\"format the patch(es) are in\"),\n+\t\t\tparse_opt_patchformat),\n \t\tOPT_END()\n \t};\n \n@@ -195,8 +283,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 argv_array paths = ARGV_ARRAY_INIT;\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\targv_array_push(&paths, argv[i]);\n+\t\t\telse\n+\t\t\t\targv_array_push(&paths, mkpath(\"%s/%s\", prefix, argv[i]));\n+\t\t}\n+\n+\t\tam_setup(&state, patch_format, paths.argv);\n+\n+\t\targv_array_clear(&paths);\n+\t}\n \n \tam_run(&state);\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267423","messageId":"1438696326-19590-7-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 06/45] builtin-am: auto-detect mbox patches","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:27Z","receivedAt":"2015-08-04T13:51:27Z","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 in\nbuiltin/am.c.\n\nRFC 2822 requires that lines are terminated by \"\\r\\n\". To support this,\nimplement strbuf_getline_crlf(), which will remove both '\\n' and \"\\r\\n\"\nfrom the end of the line.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 109 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 5f3c131..c12566a 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -10,6 +10,21 @@\n #include \"dir.h\"\n #include \"run-command.h\"\n \n+/**\n+ * Like strbuf_getline(), but treats both '\\n' and \"\\r\\n\" as line terminators.\n+ */\n+static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)\n+{\n+\tif (strbuf_getwholeline(sb, fp, '\\n'))\n+\t\treturn EOF;\n+\tif (sb->buf[sb->len - 1] == '\\n') {\n+\t\tstrbuf_setlen(sb, sb->len - 1);\n+\t\tif (sb->len > 0 && sb->buf[sb->len - 1] == '\\r')\n+\t\t\tstrbuf_setlen(sb, sb->len - 1);\n+\t}\n+\treturn 0;\n+}\n+\n enum patch_format {\n \tPATCH_FORMAT_UNKNOWN = 0,\n \tPATCH_FORMAT_MBOX\n@@ -128,6 +143,92 @@ static void am_destroy(const struct am_state *state)\n }\n \n /**\n+ * Determines if the file looks like a piece of RFC2822 mail by grabbing all\n+ * non-indented lines and checking if they look like they begin with valid\n+ * header field names.\n+ *\n+ * Returns 1 if the file looks like a piece of mail, 0 otherwise.\n+ */\n+static int is_mail(FILE *fp)\n+{\n+\tconst char *header_regex = \"^[!-9;-~]+:\";\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tregex_t regex;\n+\tint ret = 1;\n+\n+\tif (fseek(fp, 0L, SEEK_SET))\n+\t\tdie_errno(_(\"fseek failed\"));\n+\n+\tif (regcomp(&regex, header_regex, REG_NOSUB | REG_EXTENDED))\n+\t\tdie(\"invalid pattern: %s\", header_regex);\n+\n+\twhile (!strbuf_getline_crlf(&sb, fp)) {\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 header_regex */\n+\t\tif (regexec(&regex, sb.buf, 0, NULL, 0)) {\n+\t\t\tret = 0;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+done:\n+\tregfree(&regex);\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(const char **paths)\n+{\n+\tenum patch_format ret = PATCH_FORMAT_UNKNOWN;\n+\tstruct strbuf l1 = 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 || !strcmp(*paths, \"-\") || is_directory(*paths))\n+\t\treturn PATCH_FORMAT_MBOX;\n+\n+\t/*\n+\t * Otherwise, check the first few lines of the first patch, starting\n+\t * from the first non-blank line, to try to detect its format.\n+\t */\n+\n+\tfp = xfopen(*paths, \"r\");\n+\n+\twhile (!strbuf_getline_crlf(&l1, fp)) {\n+\t\tif (l1.len)\n+\t\t\tbreak;\n+\t}\n+\n+\tif (starts_with(l1.buf, \"From \") || starts_with(l1.buf, \"From: \")) {\n+\t\tret = PATCH_FORMAT_MBOX;\n+\t\tgoto done;\n+\t}\n+\n+\tif (l1.len && is_mail(fp)) {\n+\t\tret = PATCH_FORMAT_MBOX;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tfclose(fp);\n+\tstrbuf_release(&l1);\n+\treturn ret;\n+}\n+\n+/**\n  * Splits out individual email patches from `paths`, where each path is either\n  * a mbox file or a Maildir. Returns 0 on success, -1 on failure.\n  */\n@@ -185,6 +286,14 @@ static int split_mail(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\t\tconst char **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, 0777) < 0 && errno != EEXIST)\n \t\tdie_errno(_(\"failed to create directory '%s'\"), state->dir);\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267426","messageId":"1438696326-19590-8-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 07/45] builtin-am: extract patch and commit info with git-mailinfo","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:28Z","receivedAt":"2015-08-04T13:51:28Z","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>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 317 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 317 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex c12566a..63f0fa4 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 /**\n  * Like strbuf_getline(), but treats both '\\n' and \"\\r\\n\" as line terminators.\n@@ -38,6 +55,13 @@ struct am_state {\n \tint cur;\n \tint last;\n \n+\t/* commit metadata and message */\n+\tchar *author_name;\n+\tchar *author_email;\n+\tchar *author_date;\n+\tchar *msg;\n+\tsize_t msg_len;\n+\n \t/* number of digits in patch filename */\n \tint prec;\n };\n@@ -62,6 +86,10 @@ static void am_state_init(struct am_state *state, const char *dir)\n static void am_state_release(struct am_state *state)\n {\n \tfree(state->dir);\n+\tfree(state->author_name);\n+\tfree(state->author_email);\n+\tfree(state->author_date);\n+\tfree(state->msg);\n }\n \n /**\n@@ -112,6 +140,161 @@ static int read_state_file(struct strbuf *sb, const struct am_state *state,\n }\n \n /**\n+ * Reads a KEY=VALUE shell variable assignment from `fp`, returning the VALUE\n+ * as a newly-allocated string. VALUE must be a quoted string, and the KEY must\n+ * match `key`. Returns NULL on failure.\n+ *\n+ * This is used by read_author_script() to read the GIT_AUTHOR_* variables from\n+ * the author-script.\n+ */\n+static char *read_shell_var(FILE *fp, const char *key)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *str;\n+\n+\tif (strbuf_getline(&sb, fp, '\\n'))\n+\t\tgoto fail;\n+\n+\tif (!skip_prefix(sb.buf, key, &str))\n+\t\tgoto fail;\n+\n+\tif (!skip_prefix(str, \"=\", &str))\n+\t\tgoto fail;\n+\n+\tstrbuf_remove(&sb, 0, str - sb.buf);\n+\n+\tstr = sq_dequote(sb.buf);\n+\tif (!str)\n+\t\tgoto fail;\n+\n+\treturn strbuf_detach(&sb, NULL);\n+\n+fail:\n+\tstrbuf_release(&sb);\n+\treturn NULL;\n+}\n+\n+/**\n+ * Reads and parses the state directory's \"author-script\" file, and sets\n+ * state->author_name, state->author_email and state->author_date accordingly.\n+ * Returns 0 on success, -1 if the file could not be parsed.\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. We are strict\n+ * with our parsing, as the file was meant to be eval'd in the old git-am.sh\n+ * script, and thus if the file differs from what this function expects, it is\n+ * better to bail out than to do something that the user does not expect.\n+ */\n+static int read_author_script(struct am_state *state)\n+{\n+\tconst char *filename = am_path(state, \"author-script\");\n+\tFILE *fp;\n+\n+\tassert(!state->author_name);\n+\tassert(!state->author_email);\n+\tassert(!state->author_date);\n+\n+\tfp = 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+\tstate->author_name = read_shell_var(fp, \"GIT_AUTHOR_NAME\");\n+\tif (!state->author_name) {\n+\t\tfclose(fp);\n+\t\treturn -1;\n+\t}\n+\n+\tstate->author_email = read_shell_var(fp, \"GIT_AUTHOR_EMAIL\");\n+\tif (!state->author_email) {\n+\t\tfclose(fp);\n+\t\treturn -1;\n+\t}\n+\n+\tstate->author_date = read_shell_var(fp, \"GIT_AUTHOR_DATE\");\n+\tif (!state->author_date) {\n+\t\tfclose(fp);\n+\t\treturn -1;\n+\t}\n+\n+\tif (fgetc(fp) != EOF) {\n+\t\tfclose(fp);\n+\t\treturn -1;\n+\t}\n+\n+\tfclose(fp);\n+\treturn 0;\n+}\n+\n+/**\n+ * Saves state->author_name, state->author_email and state->author_date in the\n+ * state directory's \"author-script\" file.\n+ */\n+static void write_author_script(const struct am_state *state)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&sb, \"GIT_AUTHOR_NAME=\");\n+\tsq_quote_buf(&sb, state->author_name);\n+\tstrbuf_addch(&sb, '\\n');\n+\n+\tstrbuf_addstr(&sb, \"GIT_AUTHOR_EMAIL=\");\n+\tsq_quote_buf(&sb, state->author_email);\n+\tstrbuf_addch(&sb, '\\n');\n+\n+\tstrbuf_addstr(&sb, \"GIT_AUTHOR_DATE=\");\n+\tsq_quote_buf(&sb, state->author_date);\n+\tstrbuf_addch(&sb, '\\n');\n+\n+\twrite_file(am_path(state, \"author-script\"), 1, \"%s\", sb.buf);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n+/**\n+ * Reads the commit message from the state directory's \"final-commit\" file,\n+ * setting state->msg to its contents and state->msg_len to the length of its\n+ * contents in bytes.\n+ *\n+ * Returns 0 on success, -1 if the file does not exist.\n+ */\n+static int read_commit_msg(struct am_state *state)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tassert(!state->msg);\n+\n+\tif (read_state_file(&sb, state, \"final-commit\", 0) < 0) {\n+\t\tstrbuf_release(&sb);\n+\t\treturn -1;\n+\t}\n+\n+\tstate->msg = strbuf_detach(&sb, &state->msg_len);\n+\treturn 0;\n+}\n+\n+/**\n+ * Saves state->msg in the state directory's \"final-commit\" file.\n+ */\n+static void write_commit_msg(const struct am_state *state)\n+{\n+\tint fd;\n+\tconst char *filename = am_path(state, \"final-commit\");\n+\n+\tfd = xopen(filename, O_WRONLY | O_CREAT, 0666);\n+\tif (write_in_full(fd, state->msg, state->msg_len) < 0)\n+\t\tdie_errno(_(\"could not write to %s\"), filename);\n+\tclose(fd);\n+}\n+\n+/**\n  * Loads state from disk.\n  */\n static void am_load(struct am_state *state)\n@@ -126,6 +309,11 @@ static void am_load(struct am_state *state)\n \t\tdie(\"BUG: state file 'last' does not exist\");\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_commit_msg(state);\n+\n \tstrbuf_release(&sb);\n }\n \n@@ -318,19 +506,148 @@ 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+\tfree(state->author_name);\n+\tstate->author_name = NULL;\n+\n+\tfree(state->author_email);\n+\tstate->author_email = NULL;\n+\n+\tfree(state->author_date);\n+\tstate->author_date = NULL;\n+\n+\tfree(state->msg);\n+\tstate->msg = NULL;\n+\tstate->msg_len = 0;\n+\n+\tunlink(am_path(state, \"author-script\"));\n+\tunlink(am_path(state, \"final-commit\"));\n+\n \tstate->cur++;\n \twrite_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n }\n \n /**\n+ * Returns the filename of the current patch email.\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 `mail` using git-mailinfo, extracting its patch and authorship info.\n+ * state->msg will be set to the patch message. state->author_name,\n+ * state->author_email and state->author_date will be set to the patch author's\n+ * name, email and date respectively. The patch body will be written to the\n+ * state directory's \"patch\" file.\n+ *\n+ * Returns 1 if the patch should be skipped, 0 otherwise.\n+ */\n+static int parse_mail(struct am_state *state, const char *mail)\n+{\n+\tFILE *fp;\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tstruct strbuf author_name = STRBUF_INIT;\n+\tstruct strbuf author_date = STRBUF_INIT;\n+\tstruct strbuf author_email = STRBUF_INIT;\n+\tint ret = 0;\n+\n+\tcp.git_cmd = 1;\n+\tcp.in = xopen(mail, 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 (msg.len)\n+\t\t\t\tstrbuf_addch(&msg, '\\n');\n+\t\t\tstrbuf_addstr(&msg, x);\n+\t\t} else if (skip_prefix(sb.buf, \"Author: \", &x))\n+\t\t\tstrbuf_addstr(&author_name, x);\n+\t\telse if (skip_prefix(sb.buf, \"Email: \", &x))\n+\t\t\tstrbuf_addstr(&author_email, x);\n+\t\telse if (skip_prefix(sb.buf, \"Date: \", &x))\n+\t\t\tstrbuf_addstr(&author_date, x);\n+\t}\n+\tfclose(fp);\n+\n+\t/* Skip pine's internal folder data */\n+\tif (!strcmp(author_name.buf, \"Mail System Internal Data\")) {\n+\t\tret = 1;\n+\t\tgoto finish;\n+\t}\n+\n+\tif (is_empty_file(am_path(state, \"patch\"))) {\n+\t\tprintf_ln(_(\"Patch is empty. Was it split wrong?\"));\n+\t\texit(128);\n+\t}\n+\n+\tstrbuf_addstr(&msg, \"\\n\\n\");\n+\tif (strbuf_read_file(&msg, am_path(state, \"msg\"), 0) < 0)\n+\t\tdie_errno(_(\"could not read '%s'\"), am_path(state, \"msg\"));\n+\tstripspace(&msg, 0);\n+\n+\tassert(!state->author_name);\n+\tstate->author_name = strbuf_detach(&author_name, NULL);\n+\n+\tassert(!state->author_email);\n+\tstate->author_email = strbuf_detach(&author_email, NULL);\n+\n+\tassert(!state->author_date);\n+\tstate->author_date = strbuf_detach(&author_date, NULL);\n+\n+\tassert(!state->msg);\n+\tstate->msg = strbuf_detach(&msg, &state->msg_len);\n+\n+finish:\n+\tstrbuf_release(&msg);\n+\tstrbuf_release(&author_date);\n+\tstrbuf_release(&author_email);\n+\tstrbuf_release(&author_name);\n+\tstrbuf_release(&sb);\n+\treturn ret;\n+}\n+\n+/**\n  * Applies all queued mail.\n  */\n static void am_run(struct am_state *state)\n {\n \twhile (state->cur <= state->last) {\n+\t\tconst char *mail = am_path(state, msgnum(state));\n+\n+\t\tif (!file_exists(mail))\n+\t\t\tgoto next;\n+\n+\t\tif (parse_mail(state, mail))\n+\t\t\tgoto next; /* mail should be skipped */\n+\n+\t\twrite_author_script(state);\n+\t\twrite_commit_msg(state);\n \n \t\t/* NEEDSWORK: Patch application not implemented yet */\n \n+next:\n \t\tam_next(state);\n \t}\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267425","messageId":"1438696326-19590-9-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 08/45] builtin-am: apply patch with git-apply","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:29Z","receivedAt":"2015-08-04T13:51:29Z","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\nIf 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\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 71 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 63f0fa4..1f198e4 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -10,6 +10,7 @@\n #include \"dir.h\"\n #include \"run-command.h\"\n #include \"quote.h\"\n+#include \"lockfile.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -42,6 +43,14 @@ static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)\n \treturn 0;\n }\n \n+/**\n+ * Returns the length of the first line of msg.\n+ */\n+static int linelen(const char *msg)\n+{\n+\treturn strchrnul(msg, '\\n') - msg;\n+}\n+\n enum patch_format {\n \tPATCH_FORMAT_UNKNOWN = 0,\n \tPATCH_FORMAT_MBOX\n@@ -540,6 +549,19 @@ 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+\tstruct lock_file *lock_file = xcalloc(1, sizeof(struct 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+}\n+\n+/**\n  * Parses `mail` using git-mailinfo, extracting its patch and authorship info.\n  * state->msg will be set to the patch message. state->author_name,\n  * state->author_email and state->author_date will be set to the patch author's\n@@ -629,10 +651,35 @@ finish:\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 mail.\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 *mail = am_path(state, msgnum(state));\n \n@@ -645,7 +692,27 @@ static void am_run(struct am_state *state)\n \t\twrite_author_script(state);\n \t\twrite_commit_msg(state);\n \n-\t\t/* NEEDSWORK: Patch application not implemented yet */\n+\t\tprintf_ln(_(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n+\n+\t\tif (run_apply(state) < 0) {\n+\t\t\tint advice_amworkdir = 1;\n+\n+\t\t\tprintf_ln(_(\"Patch failed at %s %.*s\"), msgnum(state),\n+\t\t\t\tlinelen(state->msg), state->msg);\n+\n+\t\t\tgit_config_get_bool(\"advice.amworkdir\", &advice_amworkdir);\n+\n+\t\t\tif (advice_amworkdir)\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 * NEEDSWORK: After the patch has been applied to the index\n+\t\t * with git-apply, we need to make commit as well.\n+\t\t */\n \n next:\n \t\tam_next(state);\n@@ -707,6 +774,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, 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.5.0.280.gd88bd6e\n"},{"id":"267461","messageId":"1438696326-19590-10-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 09/45] builtin-am: implement committing applied patch","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:30Z","receivedAt":"2015-08-04T13:51:30Z","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\nSince 29b6754 (am: remove rebase-apply directory before gc, 2010-02-22),\ngit gc --auto is also invoked to pack the loose objects that are created\nfrom making the commits.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++----\n 1 file changed, 51 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1f198e4..a2811b6 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -11,6 +11,9 @@\n #include \"run-command.h\"\n #include \"quote.h\"\n #include \"lockfile.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@@ -674,10 +677,56 @@ 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[GIT_SHA1_RAWSZ], parent[GIT_SHA1_RAWSZ],\n+\t\t      commit[GIT_SHA1_RAWSZ];\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, state->author_email,\n+\t\t\tstate->author_date, IDENT_STRICT);\n+\n+\tif (commit_tree(state->msg, 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, linelen(state->msg),\n+\t\t\tstate->msg);\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 mail.\n  */\n static void am_run(struct am_state *state)\n {\n+\tconst char *argv_gc_auto[] = {\"gc\", \"--auto\", NULL};\n+\n \trefresh_and_write_cache();\n \n \twhile (state->cur <= state->last) {\n@@ -709,16 +758,14 @@ static void am_run(struct am_state *state)\n \t\t\texit(128);\n \t\t}\n \n-\t\t/*\n-\t\t * NEEDSWORK: After the patch has been applied to the index\n-\t\t * with 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 \t}\n \n \tam_destroy(state);\n+\trun_command_v_opt(argv_gc_auto, RUN_GIT_CMD);\n }\n \n /**\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267427","messageId":"1438696326-19590-11-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 10/45] builtin-am: refuse to apply patches if index is dirty","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:31Z","receivedAt":"2015-08-04T13:51:31Z","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 in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 45 +++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 45 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex a2811b6..537ad62 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -14,6 +14,8 @@\n #include \"cache-tree.h\"\n #include \"refs.h\"\n #include \"commit.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@@ -565,6 +567,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 `mail` using git-mailinfo, extracting its patch and authorship info.\n  * state->msg will be set to the patch message. state->author_name,\n  * state->author_email and state->author_date will be set to the patch author's\n@@ -726,9 +765,15 @@ static void do_commit(const struct am_state *state)\n static void am_run(struct am_state *state)\n {\n \tconst char *argv_gc_auto[] = {\"gc\", \"--auto\", NULL};\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 *mail = am_path(state, msgnum(state));\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267460","messageId":"1438696326-19590-12-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 11/45] builtin-am: implement --resolved/--continue","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:32Z","receivedAt":"2015-08-04T13:51:32Z","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\nSince it makes no sense for the user to run am --resolved when there is\nno session in progress, we error out in this case.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 71 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 537ad62..fd26721 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -760,6 +760,21 @@ static void do_commit(const struct am_state *state)\n }\n \n /**\n+ * Validates the am_state for resuming -- the \"msg\" and authorship fields must\n+ * be filled up.\n+ */\n+static void validate_resume_state(const struct am_state *state)\n+{\n+\tif (!state->msg)\n+\t\tdie(_(\"cannot resume: %s does not exist.\"),\n+\t\t\tam_path(state, \"final-commit\"));\n+\n+\tif (!state->author_name || !state->author_email || !state->author_date)\n+\t\tdie(_(\"cannot resume: %s does not exist.\"),\n+\t\t\tam_path(state, \"author-script\"));\n+}\n+\n+/**\n  * Applies all queued mail.\n  */\n static void am_run(struct am_state *state)\n@@ -814,6 +829,36 @@ 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+\tvalidate_resume_state(state);\n+\n+\tprintf_ln(_(\"Applying: %.*s\"), linelen(state->msg), state->msg);\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@@ -828,13 +873,20 @@ 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 int cmd_am(int argc, const char **argv, const char *prefix)\n {\n \tstruct am_state state;\n \tint patch_format = PATCH_FORMAT_UNKNOWN;\n+\tenum resume_mode resume = RESUME_FALSE;\n \n \tconst char * const usage[] = {\n \t\tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n+\t\tN_(\"git am [options] --continue\"),\n \t\tNULL\n \t};\n \n@@ -842,6 +894,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n+\t\tOPT_CMDMODE(0, \"continue\", &resume,\n+\t\t\tN_(\"continue applying patches after resolving a conflict\"),\n+\t\t\tRESUME_RESOLVED),\n+\t\tOPT_CMDMODE('r', \"resolved\", &resume,\n+\t\t\tN_(\"synonyms for --continue\"),\n+\t\t\tRESUME_RESOLVED),\n \t\tOPT_END()\n \t};\n \n@@ -875,6 +933,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tstruct argv_array paths = ARGV_ARRAY_INIT;\n \t\tint i;\n \n+\t\tif (resume)\n+\t\t\tdie(_(\"Resolve operation not in progress, we are not resuming.\"));\n+\n \t\tfor (i = 0; i < argc; i++) {\n \t\t\tif (is_absolute_path(argv[i]) || !prefix)\n \t\t\t\targv_array_push(&paths, argv[i]);\n@@ -887,7 +948,16 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\targv_array_clear(&paths);\n \t}\n \n-\tam_run(&state);\n+\tswitch (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.5.0.280.gd88bd6e\n"},{"id":"267422","messageId":"1438696326-19590-13-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 12/45] builtin-am: don't parse mail when resuming","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:33Z","receivedAt":"2015-08-04T13:51:33Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 271440e (git-am: make it easier after fixing up an unapplicable\npatch., 2005-10-25), when \"git am\" is run again after being paused, the\ncurrent mail message will not be re-parsed, but instead the contents of\nthe state directory's patch, msg and author-script files will be used\nas-is instead.\n\nRe-implement this in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 34 +++++++++++++++++++++++++---------\n 1 file changed, 25 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex fd26721..ec579a6 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -776,8 +776,12 @@ static void validate_resume_state(const struct am_state *state)\n \n /**\n  * Applies all queued mail.\n+ *\n+ * If `resume` is true, we are \"resuming\". The \"msg\" and authorship fields, as\n+ * well as the state directory's \"patch\" file is used as-is for applying the\n+ * patch and committing it.\n  */\n-static void am_run(struct am_state *state)\n+static void am_run(struct am_state *state, int resume)\n {\n \tconst char *argv_gc_auto[] = {\"gc\", \"--auto\", NULL};\n \tstruct strbuf sb = STRBUF_INIT;\n@@ -795,11 +799,16 @@ static void am_run(struct am_state *state)\n \t\tif (!file_exists(mail))\n \t\t\tgoto next;\n \n-\t\tif (parse_mail(state, mail))\n-\t\t\tgoto next; /* mail should be skipped */\n+\t\tif (resume) {\n+\t\t\tvalidate_resume_state(state);\n+\t\t\tresume = 0;\n+\t\t} else {\n+\t\t\tif (parse_mail(state, mail))\n+\t\t\t\tgoto next; /* mail should be skipped */\n \n-\t\twrite_author_script(state);\n-\t\twrite_commit_msg(state);\n+\t\t\twrite_author_script(state);\n+\t\t\twrite_commit_msg(state);\n+\t\t}\n \n \t\tprintf_ln(_(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -855,7 +864,7 @@ static void am_resolve(struct am_state *state)\n \tdo_commit(state);\n \n \tam_next(state);\n-\tam_run(state);\n+\tam_run(state, 0);\n }\n \n /**\n@@ -875,6 +884,7 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int\n \n enum resume_mode {\n \tRESUME_FALSE = 0,\n+\tRESUME_APPLY,\n \tRESUME_RESOLVED\n };\n \n@@ -927,9 +937,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)\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+\tif (am_in_progress(&state)) {\n+\t\tif (resume == RESUME_FALSE)\n+\t\t\tresume = RESUME_APPLY;\n+\n \t\tam_load(&state);\n-\telse {\n+\t} else {\n \t\tstruct argv_array paths = ARGV_ARRAY_INIT;\n \t\tint i;\n \n@@ -950,7 +963,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \n \tswitch (resume) {\n \tcase RESUME_FALSE:\n-\t\tam_run(&state);\n+\t\tam_run(&state, 0);\n+\t\tbreak;\n+\tcase RESUME_APPLY:\n+\t\tam_run(&state, 1);\n \t\tbreak;\n \tcase RESUME_RESOLVED:\n \t\tam_resolve(&state);\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267424","messageId":"1438696326-19590-14-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 13/45] builtin-am: implement --skip","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:34Z","receivedAt":"2015-08-04T13:51:34Z","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\nHelped-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 121 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex ec579a6..765844b 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -16,6 +16,8 @@\n #include \"commit.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@@ -868,6 +870,116 @@ 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;\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+\tlock_file = xcalloc(1, sizeof(struct lock_file));\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;\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+\tlock_file = xcalloc(1, sizeof(struct lock_file));\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, 0);\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@@ -885,7 +997,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int\n enum resume_mode {\n \tRESUME_FALSE = 0,\n \tRESUME_APPLY,\n-\tRESUME_RESOLVED\n+\tRESUME_RESOLVED,\n+\tRESUME_SKIP\n };\n \n int cmd_am(int argc, const char **argv, const char *prefix)\n@@ -896,7 +1009,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \n \tconst char * const usage[] = {\n \t\tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n-\t\tN_(\"git am [options] --continue\"),\n+\t\tN_(\"git am [options] (--continue | --skip)\"),\n \t\tNULL\n \t};\n \n@@ -910,6 +1023,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE('r', \"resolved\", &resume,\n \t\t\tN_(\"synonyms for --continue\"),\n \t\t\tRESUME_RESOLVED),\n+\t\tOPT_CMDMODE(0, \"skip\", &resume,\n+\t\t\tN_(\"skip the current patch\"),\n+\t\t\tRESUME_SKIP),\n \t\tOPT_END()\n \t};\n \n@@ -971,6 +1087,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.5.0.280.gd88bd6e\n"},{"id":"267429","messageId":"1438696326-19590-15-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 14/45] builtin-am: implement --abort","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:35Z","receivedAt":"2015-08-04T13:51:35Z","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 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\nHelped-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v7\n    \n    * Add a free(curr_branch) so we don't leak memory.\n\n builtin/am.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 100 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 765844b..6c24d07 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -490,6 +490,8 @@ static int split_mail(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\t\tconst char **paths)\n {\n+\tunsigned char curr_head[GIT_SHA1_RAWSZ];\n+\n \tif (!patch_format)\n \t\tpatch_format = detect_patch_format(paths);\n \n@@ -506,6 +508,14 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t\tdie(_(\"Failed to split patches.\"));\n \t}\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 \t/*\n \t * NOTE: Since the \"next\" and \"last\" files determine if an am_state\n \t * session is in progress, they should be written last.\n@@ -522,6 +532,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 \tfree(state->author_name);\n \tstate->author_name = NULL;\n \n@@ -538,6 +550,11 @@ static void am_next(struct am_state *state)\n \tunlink(am_path(state, \"author-script\"));\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 \tstate->cur++;\n \twrite_file(am_path(state, \"next\"), 1, \"%d\", state->cur);\n }\n@@ -788,10 +805,14 @@ static void am_run(struct am_state *state, int resume)\n \tconst char *argv_gc_auto[] = {\"gc\", \"--auto\", NULL};\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@@ -980,6 +1001,75 @@ static void am_skip(struct am_state *state)\n }\n \n /**\n+ * Returns true if it is safe to reset HEAD to the ORIG_HEAD, false otherwise.\n+ *\n+ * It is not safe to reset HEAD when:\n+ * 1. git-am previously failed because the index was dirty.\n+ * 2. HEAD has moved since git-am previously failed.\n+ */\n+static int safe_to_abort(const struct am_state *state)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tunsigned char abort_safety[GIT_SHA1_RAWSZ], head[GIT_SHA1_RAWSZ];\n+\n+\tif (file_exists(am_path(state, \"dirtyindex\")))\n+\t\treturn 0;\n+\n+\tif (read_state_file(&sb, state, \"abort-safety\", 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[GIT_SHA1_RAWSZ], orig_head[GIT_SHA1_RAWSZ];\n+\tint has_curr_head, has_orig_head;\n+\tchar *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+\tfree(curr_branch);\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@@ -998,7 +1088,8 @@ enum resume_mode {\n \tRESUME_FALSE = 0,\n \tRESUME_APPLY,\n \tRESUME_RESOLVED,\n-\tRESUME_SKIP\n+\tRESUME_SKIP,\n+\tRESUME_ABORT\n };\n \n int cmd_am(int argc, const char **argv, const char *prefix)\n@@ -1009,7 +1100,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \n \tconst char * const usage[] = {\n \t\tN_(\"git am [options] [(<mbox>|<Maildir>)...]\"),\n-\t\tN_(\"git am [options] (--continue | --skip)\"),\n+\t\tN_(\"git am [options] (--continue | --skip | --abort)\"),\n \t\tNULL\n \t};\n \n@@ -1026,6 +1117,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"skip\", &resume,\n \t\t\tN_(\"skip the current patch\"),\n \t\t\tRESUME_SKIP),\n+\t\tOPT_CMDMODE(0, \"abort\", &resume,\n+\t\t\tN_(\"restore the original branch and abort the patching operation.\"),\n+\t\t\tRESUME_ABORT),\n \t\tOPT_END()\n \t};\n \n@@ -1090,6 +1184,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.5.0.280.gd88bd6e\n"},{"id":"267434","messageId":"1438696326-19590-16-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 15/45] builtin-am: reject patches when there's a session in progress","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:36Z","receivedAt":"2015-08-04T13:51:36Z","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\nwould error out if the user gave it mbox(s) on the command-line, but\nthere was a session in progress.\n\nSince c95b138 (Fix git-am safety checks, 2006-09-15), git-am would\ndetect if the user attempted to feed it a mbox via stdin, by checking if\nstdin is not a tty and there is no resume command given.\n\nRe-implement the above two safety checks.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 6c24d07..d4b4b86 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1148,6 +1148,21 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"failed to read the index\"));\n \n \tif (am_in_progress(&state)) {\n+\t\t/*\n+\t\t * Catch user error to feed us patches when there is a session\n+\t\t * in progress:\n+\t\t *\n+\t\t * 1. mbox path(s) are provided on the command-line.\n+\t\t * 2. stdin is not a tty: the user is trying to feed us a patch\n+\t\t *    from standard input. This is somewhat unreliable -- stdin\n+\t\t *    could be /dev/null for example and the caller did not\n+\t\t *    intend to feed us a patch but wanted to continue\n+\t\t *    unattended.\n+\t\t */\n+\t\tif (argc || (resume == RESUME_FALSE && !isatty(0)))\n+\t\t\tdie(_(\"previous rebase directory %s still exists but mbox given.\"),\n+\t\t\t\tstate.dir);\n+\n \t\tif (resume == RESUME_FALSE)\n \t\t\tresume = RESUME_APPLY;\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267436","messageId":"1438696326-19590-17-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 16/45] builtin-am: implement -q/--quiet","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:37Z","receivedAt":"2015-08-04T13:51:37Z","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 when told to be quiet, would only\nspeak on failure. Re-implement this by introducing the say() function,\nwhich works like fprintf_ln(), but would only write to the stream when\nstate->quiet is false.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 31 ++++++++++++++++++++++++++++---\n 1 file changed, 28 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex d4b4b86..0875e69 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -80,6 +80,9 @@ struct am_state {\n \n \t/* number of digits in patch filename */\n \tint prec;\n+\n+\t/* various operating modes and command line options */\n+\tint quiet;\n };\n \n /**\n@@ -117,6 +120,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@@ -330,6 +349,9 @@ static void am_load(struct am_state *state)\n \n \tread_commit_msg(state);\n \n+\tread_state_file(&sb, state, \"quiet\", 1);\n+\tstate->quiet = !strcmp(sb.buf, \"t\");\n+\n \tstrbuf_release(&sb);\n }\n \n@@ -508,6 +530,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t\tdie(_(\"Failed to split patches.\"));\n \t}\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@@ -756,7 +780,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, state->author_email,\n@@ -833,7 +857,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\twrite_commit_msg(state);\n \t\t}\n \n-\t\tprintf_ln(_(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n+\t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n \t\tif (run_apply(state) < 0) {\n \t\t\tint advice_amworkdir = 1;\n@@ -869,7 +893,7 @@ static void am_resolve(struct am_state *state)\n {\n \tvalidate_resume_state(state);\n \n-\tprintf_ln(_(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n+\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n \tif (!index_has_changes(NULL)) {\n \t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n@@ -1105,6 +1129,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t};\n \n \tstruct option options[] = {\n+\t\tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267430","messageId":"1438696326-19590-18-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 17/45] builtin-am: exit with user friendly message on failure","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:38Z","receivedAt":"2015-08-04T13:51:38Z","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 | 30 ++++++++++++++++++++++++++----\n 1 file changed, 26 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 0875e69..8b8f2da 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -83,6 +83,7 @@ struct am_state {\n \n \t/* various operating modes and command line options */\n \tint quiet;\n+\tconst char *resolvemsg;\n };\n \n /**\n@@ -647,6 +648,25 @@ static int index_has_changes(struct strbuf *sb)\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+\t} else {\n+\t\tconst char *cmdline = \"git am\";\n+\n+\t\tprintf_ln(_(\"When you have resolved this problem, run \\\"%s --continue\\\".\"), cmdline);\n+\t\tprintf_ln(_(\"If you prefer to skip this patch, run \\\"%s --skip\\\" instead.\"), cmdline);\n+\t\tprintf_ln(_(\"To restore the original branch and stop patching, run \\\"%s --abort\\\".\"), cmdline);\n+\t}\n+\n+\texit(128);\n+}\n+\n+/**\n  * Parses `mail` using git-mailinfo, extracting its patch and authorship info.\n  * state->msg will be set to the patch message. state->author_name,\n  * state->author_email and state->author_date will be set to the patch author's\n@@ -706,7 +726,7 @@ static int parse_mail(struct am_state *state, const char *mail)\n \n \tif (is_empty_file(am_path(state, \"patch\"))) {\n \t\tprintf_ln(_(\"Patch is empty. Was it split wrong?\"));\n-\t\texit(128);\n+\t\tdie_user_resolve(state);\n \t}\n \n \tstrbuf_addstr(&msg, \"\\n\\n\");\n@@ -871,7 +891,7 @@ static void am_run(struct am_state *state, int resume)\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@@ -899,13 +919,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@@ -1133,6 +1153,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n+\t\tOPT_STRING(0, \"resolvemsg\", &state.resolvemsg, NULL,\n+\t\t\tN_(\"override error message when patch failure occurs\")),\n \t\tOPT_CMDMODE(0, \"continue\", &resume,\n \t\t\tN_(\"continue applying patches after resolving a conflict\"),\n \t\t\tRESUME_RESOLVED),\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267428","messageId":"1438696326-19590-19-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 18/45] builtin-am: implement -s/--signoff","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:39Z","receivedAt":"2015-08-04T13:51:39Z","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 in parse_mail() by\ncalling append_signoff() if the option is set.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v7\n    \n    * Having a field named \"append_signoff\" takes up a lot of horizontal\n      space when referring to it. Shorten the name to just \"signoff\".\n\n builtin/am.c | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8b8f2da..12952cf 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@@ -83,6 +84,7 @@ struct am_state {\n \n \t/* various operating modes and command line options */\n \tint quiet;\n+\tint signoff;\n \tconst char *resolvemsg;\n };\n \n@@ -353,6 +355,9 @@ static void am_load(struct am_state *state)\n \tread_state_file(&sb, state, \"quiet\", 1);\n \tstate->quiet = !strcmp(sb.buf, \"t\");\n \n+\tread_state_file(&sb, state, \"sign\", 1);\n+\tstate->signoff = !strcmp(sb.buf, \"t\");\n+\n \tstrbuf_release(&sb);\n }\n \n@@ -533,6 +538,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->signoff ? \"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@@ -734,6 +741,9 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tdie_errno(_(\"could not read '%s'\"), am_path(state, \"msg\"));\n \tstripspace(&msg, 0);\n \n+\tif (state->signoff)\n+\t\tappend_signoff(&msg, 0, 0);\n+\n \tassert(!state->author_name);\n \tstate->author_name = strbuf_detach(&author_name, NULL);\n \n@@ -1150,6 +1160,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \n \tstruct option options[] = {\n \t\tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n+\t\tOPT_BOOL('s', \"signoff\", &state.signoff,\n+\t\t\tN_(\"add a Signed-off-by line to the commit message\")),\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267432","messageId":"1438696326-19590-20-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 19/45] cache-tree: introduce write_index_as_tree()","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:40Z","receivedAt":"2015-08-04T13:51:40Z","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.5.0.280.gd88bd6e\n"},{"id":"267433","messageId":"1438696326-19590-21-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 20/45] builtin-am: implement --3way","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:41Z","receivedAt":"2015-08-04T13:51:41Z","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),\ngit-am.sh supported the --3way option, and if set, would attempt to do a\n3-way merge if the initial patch application fails.\n\nSince 5d86861 (am -3: list the paths that needed 3-way fallback,\n2012-03-28), in a 3-way merge git-am.sh would list the paths that needed\n3-way fallback, so that the user can review them more carefully to spot\nmismerges.\n\nRe-implement the above in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v7\n    \n    * As support for am.threeWay was removed in master, this patch now\n      does not implement am.threeWay as well.\n\n builtin/am.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 150 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 12952cf..a5d5e8c 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -19,6 +19,8 @@\n #include \"unpack-trees.h\"\n #include \"branch.h\"\n #include \"sequencer.h\"\n+#include \"revision.h\"\n+#include \"merge-recursive.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -83,6 +85,7 @@ struct am_state {\n \tint prec;\n \n \t/* various operating modes and command line options */\n+\tint threeway;\n \tint quiet;\n \tint signoff;\n \tconst char *resolvemsg;\n@@ -352,6 +355,9 @@ static void am_load(struct am_state *state)\n \n \tread_commit_msg(state);\n \n+\tread_state_file(&sb, state, \"threeway\", 1);\n+\tstate->threeway = !strcmp(sb.buf, \"t\");\n+\n \tread_state_file(&sb, state, \"quiet\", 1);\n \tstate->quiet = !strcmp(sb.buf, \"t\");\n \n@@ -536,6 +542,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t\tdie(_(\"Failed to split patches.\"));\n \t}\n \n+\twrite_file(am_path(state, \"threeway\"), 1, state->threeway ? \"t\" : \"f\");\n+\n \twrite_file(am_path(state, \"quiet\"), 1, state->quiet ? \"t\" : \"f\");\n \n \twrite_file(am_path(state, \"sign\"), 1, state->signoff ? \"t\" : \"f\");\n@@ -766,16 +774,34 @@ finish:\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+\n+\tif (index_file)\n+\t\targv_array_push(&cp.args, \"--cached\");\n+\telse\n+\t\targv_array_push(&cp.args, \"--index\");\n+\n \targv_array_push(&cp.args, am_path(state, \"patch\"));\n \n \tif (run_command(&cp))\n@@ -783,8 +809,106 @@ 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 an 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[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ],\n+\t\t      our_tree[GIT_SHA1_RAWSZ];\n+\tconst unsigned char *bases[1] = {orig_tree};\n+\tstruct merge_options o;\n+\tstruct commit *result;\n+\tchar *his_tree_name;\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\t/*\n+\t\t * List paths that needed 3-way fallback, so that the user can\n+\t\t * review them with extra care to spot mismerges.\n+\t\t */\n+\t\tstruct rev_info rev_info;\n+\t\tconst char *diff_filter_str = \"--diff-filter=AM\";\n+\n+\t\tinit_revisions(&rev_info, NULL);\n+\t\trev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;\n+\t\tdiff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1);\n+\t\tadd_pending_sha1(&rev_info, \"HEAD\", our_tree, 0);\n+\t\tdiff_setup_done(&rev_info.diffopt);\n+\t\trun_diff_index(&rev_info, 1);\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+\this_tree_name = xstrfmt(\"%.*s\", linelen(state->msg), state->msg);\n+\to.branch2 = his_tree_name;\n+\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\tfree(his_tree_name);\n+\t\treturn error(_(\"Failed to merge in the changes.\"));\n+\t}\n+\n+\tfree(his_tree_name);\n \treturn 0;\n }\n \n@@ -872,6 +996,7 @@ static void am_run(struct am_state *state, int resume)\n \n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n+\t\tint apply_status;\n \n \t\tif (!file_exists(mail))\n \t\t\tgoto next;\n@@ -889,7 +1014,26 @@ static void am_run(struct am_state *state, int resume)\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\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 advice_amworkdir = 1;\n \n \t\t\tprintf_ln(_(\"Patch failed at %s %.*s\"), msgnum(state),\n@@ -1159,6 +1303,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t};\n \n \tstruct option options[] = {\n+\t\tOPT_BOOL('3', \"3way\", &state.threeway,\n+\t\t\tN_(\"allow fall back on 3way merging if needed\")),\n \t\tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n \t\tOPT_BOOL('s', \"signoff\", &state.signoff,\n \t\t\tN_(\"add a Signed-off-by line to the commit message\")),\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267435","messageId":"1438696326-19590-22-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 21/45] builtin-am: implement --rebasing mode","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:42Z","receivedAt":"2015-08-04T13:51:42Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 3041c32 (am: --rebasing, 2008-03-04), git-am.sh supported the\n--rebasing option, which is used internally by git-rebase to tell git-am\nthat it is being used for its purpose. It would create the empty file\n$state_dir/rebasing to help \"completion\" scripts tell if the ongoing\noperation is am or rebase.\n\nAs of 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26),\n--rebasing also implies --3way as well.\n\nSince a1549e1 (am: return control to caller, for housekeeping,\n2013-05-12), git-am.sh would only clean up the state directory when it\nis not --rebasing, instead deferring cleanup to git-rebase.sh.\n\nRe-implement the above in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 30 ++++++++++++++++++++++++++----\n 1 file changed, 26 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex a5d5e8c..440a653 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -89,6 +89,7 @@ struct am_state {\n \tint quiet;\n \tint signoff;\n \tconst char *resolvemsg;\n+\tint rebasing;\n };\n \n /**\n@@ -364,6 +365,8 @@ static void am_load(struct am_state *state)\n \tread_state_file(&sb, state, \"sign\", 1);\n \tstate->signoff = !strcmp(sb.buf, \"t\");\n \n+\tstate->rebasing = !!file_exists(am_path(state, \"rebasing\"));\n+\n \tstrbuf_release(&sb);\n }\n \n@@ -542,18 +545,29 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t\tdie(_(\"Failed to split patches.\"));\n \t}\n \n+\tif (state->rebasing)\n+\t\tstate->threeway = 1;\n+\n \twrite_file(am_path(state, \"threeway\"), 1, state->threeway ? \"t\" : \"f\");\n \n \twrite_file(am_path(state, \"quiet\"), 1, state->quiet ? \"t\" : \"f\");\n \n \twrite_file(am_path(state, \"sign\"), 1, state->signoff ? \"t\" : \"f\");\n \n+\tif (state->rebasing)\n+\t\twrite_file(am_path(state, \"rebasing\"), 1, \"%s\", \"\");\n+\telse\n+\t\twrite_file(am_path(state, \"applying\"), 1, \"%s\", \"\");\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\tif (!state->rebasing)\n+\t\t\tupdate_ref(\"am\", \"ORIG_HEAD\", curr_head, NULL, 0,\n+\t\t\t\t\tUPDATE_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\tif (!state->rebasing)\n+\t\t\tdelete_ref(\"ORIG_HEAD\", NULL, 0);\n \t}\n \n \t/*\n@@ -1054,8 +1068,14 @@ next:\n \t\tam_next(state);\n \t}\n \n-\tam_destroy(state);\n-\trun_command_v_opt(argv_gc_auto, RUN_GIT_CMD);\n+\t/*\n+\t * In rebasing mode, it's up to the caller to take care of\n+\t * housekeeping.\n+\t */\n+\tif (!state->rebasing) {\n+\t\tam_destroy(state);\n+\t\trun_command_v_opt(argv_gc_auto, RUN_GIT_CMD);\n+\t}\n }\n \n /**\n@@ -1325,6 +1345,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"abort\", &resume,\n \t\t\tN_(\"restore the original branch and abort the patching operation.\"),\n \t\t\tRESUME_ABORT),\n+\t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n+\t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\n \t};\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267431","messageId":"1438696326-19590-23-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 22/45] builtin-am: bypass git-mailinfo when --rebasing","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:43Z","receivedAt":"2015-08-04T13:51:43Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 5e835ca (rebase: do not munge commit log message, 2008-04-16),\ngit am --rebasing no longer gets the commit log message from the patch,\nbut reads it directly from the commit identified by the \"From \" header\nline.\n\nSince 43c2325 (am: use get_author_ident_from_commit instead of mailinfo\nwhen rebasing, 2010-06-16), git am --rebasing also gets the author name,\nemail and date directly from the commit.\n\nSince 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26), git am\n--rebasing does not use git-mailinfo to get the patch body, but rather\ngenerates it directly from the commit itself.\n\nThe above 3 commits introduced a separate parse_mail() code path in\ngit-am.sh's --rebasing mode that bypasses git-mailinfo. Re-implement\nthis code path in builtin/am.c as parse_mail_rebase().\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v7\n    \n    * Since a5481a6 (convert \"enum date_mode\" into a struct, 2015-06-25),\n      show_ident_date() now takes a date_mode struct. Use the DATE_MODE()\n      macro to pass the equivalent date_mode struct to show_ident_date().\n\n builtin/am.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 133 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 440a653..a02c84e 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -21,6 +21,8 @@\n #include \"sequencer.h\"\n #include \"revision.h\"\n #include \"merge-recursive.h\"\n+#include \"revision.h\"\n+#include \"log-tree.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -788,6 +790,129 @@ finish:\n }\n \n /**\n+ * Sets commit_id to the commit hash where the mail was generated from.\n+ * Returns 0 on success, -1 on failure.\n+ */\n+static int get_mail_commit_sha1(unsigned char *commit_id, const char *mail)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tFILE *fp = xfopen(mail, \"r\");\n+\tconst char *x;\n+\n+\tif (strbuf_getline(&sb, fp, '\\n'))\n+\t\treturn -1;\n+\n+\tif (!skip_prefix(sb.buf, \"From \", &x))\n+\t\treturn -1;\n+\n+\tif (get_sha1_hex(x, commit_id) < 0)\n+\t\treturn -1;\n+\n+\tstrbuf_release(&sb);\n+\tfclose(fp);\n+\treturn 0;\n+}\n+\n+/**\n+ * Sets state->msg, state->author_name, state->author_email, state->author_date\n+ * to the commit's respective info.\n+ */\n+static void get_commit_info(struct am_state *state, struct commit *commit)\n+{\n+\tconst char *buffer, *ident_line, *author_date, *msg;\n+\tsize_t ident_len;\n+\tstruct ident_split ident_split;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tbuffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());\n+\n+\tident_line = find_commit_header(buffer, \"author\", &ident_len);\n+\n+\tif (split_ident_line(&ident_split, ident_line, ident_len) < 0) {\n+\t\tstrbuf_add(&sb, ident_line, ident_len);\n+\t\tdie(_(\"invalid ident line: %s\"), sb.buf);\n+\t}\n+\n+\tassert(!state->author_name);\n+\tif (ident_split.name_begin) {\n+\t\tstrbuf_add(&sb, ident_split.name_begin,\n+\t\t\tident_split.name_end - ident_split.name_begin);\n+\t\tstate->author_name = strbuf_detach(&sb, NULL);\n+\t} else\n+\t\tstate->author_name = xstrdup(\"\");\n+\n+\tassert(!state->author_email);\n+\tif (ident_split.mail_begin) {\n+\t\tstrbuf_add(&sb, ident_split.mail_begin,\n+\t\t\tident_split.mail_end - ident_split.mail_begin);\n+\t\tstate->author_email = strbuf_detach(&sb, NULL);\n+\t} else\n+\t\tstate->author_email = xstrdup(\"\");\n+\n+\tauthor_date = show_ident_date(&ident_split, DATE_MODE(NORMAL));\n+\tstrbuf_addstr(&sb, author_date);\n+\tassert(!state->author_date);\n+\tstate->author_date = strbuf_detach(&sb, NULL);\n+\n+\tassert(!state->msg);\n+\tmsg = strstr(buffer, \"\\n\\n\");\n+\tif (!msg)\n+\t\tdie(_(\"unable to parse commit %s\"), sha1_to_hex(commit->object.sha1));\n+\tstate->msg = xstrdup(msg + 2);\n+\tstate->msg_len = strlen(state->msg);\n+}\n+\n+/**\n+ * Writes `commit` as a patch to the state directory's \"patch\" file.\n+ */\n+static void write_commit_patch(const struct am_state *state, struct commit *commit)\n+{\n+\tstruct rev_info rev_info;\n+\tFILE *fp;\n+\n+\tfp = xfopen(am_path(state, \"patch\"), \"w\");\n+\tinit_revisions(&rev_info, NULL);\n+\trev_info.diff = 1;\n+\trev_info.abbrev = 0;\n+\trev_info.disable_stdin = 1;\n+\trev_info.show_root_diff = 1;\n+\trev_info.diffopt.output_format = DIFF_FORMAT_PATCH;\n+\trev_info.no_commit_id = 1;\n+\tDIFF_OPT_SET(&rev_info.diffopt, BINARY);\n+\tDIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX);\n+\trev_info.diffopt.use_color = 0;\n+\trev_info.diffopt.file = fp;\n+\trev_info.diffopt.close_file = 1;\n+\tadd_pending_object(&rev_info, &commit->object, \"\");\n+\tdiff_setup_done(&rev_info.diffopt);\n+\tlog_tree_commit(&rev_info, commit);\n+}\n+\n+/**\n+ * Like parse_mail(), but parses the mail by looking up its commit ID\n+ * directly. This is used in --rebasing mode to bypass git-mailinfo's munging\n+ * of patches.\n+ *\n+ * Will always return 0 as the patch should never be skipped.\n+ */\n+static int parse_mail_rebase(struct am_state *state, const char *mail)\n+{\n+\tstruct commit *commit;\n+\tunsigned char commit_sha1[GIT_SHA1_RAWSZ];\n+\n+\tif (get_mail_commit_sha1(commit_sha1, mail) < 0)\n+\t\tdie(_(\"could not parse %s\"), mail);\n+\n+\tcommit = lookup_commit_or_die(commit_sha1, mail);\n+\n+\tget_commit_info(state, commit);\n+\n+\twrite_commit_patch(state, commit);\n+\n+\treturn 0;\n+}\n+\n+/**\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@@ -1019,7 +1144,14 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tvalidate_resume_state(state);\n \t\t\tresume = 0;\n \t\t} else {\n-\t\t\tif (parse_mail(state, mail))\n+\t\t\tint skip;\n+\n+\t\t\tif (state->rebasing)\n+\t\t\t\tskip = parse_mail_rebase(state, mail);\n+\t\t\telse\n+\t\t\t\tskip = parse_mail(state, mail);\n+\n+\t\t\tif (skip)\n \t\t\t\tgoto next; /* mail should be skipped */\n \n \t\t\twrite_author_script(state);\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267443","messageId":"1438696326-19590-24-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 23/45] builtin-am: handle stray state directory","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:44Z","receivedAt":"2015-08-04T13:51:44Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Should git-am terminate unexpectedly between the point where the state\ndirectory is created, but the \"next\" and \"last\" files are not written\nyet, a stray state directory will be left behind.\n\nAs such, since b141f3c (am: handle stray $dotest directory, 2013-06-15),\ngit-am.sh explicitly recognizes such a stray directory, and allows the\nuser to remove it with am --abort.\n\nRe-implement this feature in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 17 +++++++++++++++++\n 1 file changed, 17 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex a02c84e..47dd4c7 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1530,6 +1530,23 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tstruct argv_array paths = ARGV_ARRAY_INIT;\n \t\tint i;\n \n+\t\t/*\n+\t\t * Handle stray state directory in the independent-run case. In\n+\t\t * the --rebasing case, it is up to the caller to take care of\n+\t\t * stray directories.\n+\t\t */\n+\t\tif (file_exists(state.dir) && !state.rebasing) {\n+\t\t\tif (resume == RESUME_ABORT) {\n+\t\t\t\tam_destroy(&state);\n+\t\t\t\tam_state_release(&state);\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\n+\t\t\tdie(_(\"Stray %s directory found.\\n\"\n+\t\t\t\t\"Use \\\"git am --abort\\\" to remove it.\"),\n+\t\t\t\tstate.dir);\n+\t\t}\n+\n \t\tif (resume)\n \t\t\tdie(_(\"Resolve operation not in progress, we are not resuming.\"));\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267441","messageId":"1438696326-19590-25-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 24/45] builtin-am: implement -u/--utf8","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:45Z","receivedAt":"2015-08-04T13:51:45Z","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),\ngit-am.sh supported the -u,--utf8 option. If set, the -u option will be\npassed to git-mailinfo to re-code the commit log message and authorship\nin the charset specified by i18n.commitencoding. If unset, the -n option\nwill be passed to git-mailinfo, which disables the re-encoding.\n\nSince d84029b (--utf8 is now default for 'git-am', 2007-01-08), --utf8\nis specified by default in git-am.sh.\n\nRe-implement the above in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 11 +++++++++++\n 1 file changed, 11 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 47dd4c7..528b2c9 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -90,6 +90,7 @@ struct am_state {\n \tint threeway;\n \tint quiet;\n \tint signoff;\n+\tint utf8;\n \tconst char *resolvemsg;\n \tint rebasing;\n };\n@@ -106,6 +107,8 @@ static void am_state_init(struct am_state *state, const char *dir)\n \tstate->dir = xstrdup(dir);\n \n \tstate->prec = 4;\n+\n+\tstate->utf8 = 1;\n }\n \n /**\n@@ -367,6 +370,9 @@ static void am_load(struct am_state *state)\n \tread_state_file(&sb, state, \"sign\", 1);\n \tstate->signoff = !strcmp(sb.buf, \"t\");\n \n+\tread_state_file(&sb, state, \"utf8\", 1);\n+\tstate->utf8 = !strcmp(sb.buf, \"t\");\n+\n \tstate->rebasing = !!file_exists(am_path(state, \"rebasing\"));\n \n \tstrbuf_release(&sb);\n@@ -556,6 +562,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \n \twrite_file(am_path(state, \"sign\"), 1, state->signoff ? \"t\" : \"f\");\n \n+\twrite_file(am_path(state, \"utf8\"), 1, state->utf8 ? \"t\" : \"f\");\n+\n \tif (state->rebasing)\n \t\twrite_file(am_path(state, \"rebasing\"), 1, \"%s\", \"\");\n \telse\n@@ -722,6 +730,7 @@ static int parse_mail(struct am_state *state, const char *mail)\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, state->utf8 ? \"-u\" : \"-n\");\n \targv_array_push(&cp.args, am_path(state, \"msg\"));\n \targv_array_push(&cp.args, am_path(state, \"patch\"));\n \n@@ -1460,6 +1469,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n \t\tOPT_BOOL('s', \"signoff\", &state.signoff,\n \t\t\tN_(\"add a Signed-off-by line to the commit message\")),\n+\t\tOPT_BOOL('u', \"utf8\", &state.utf8,\n+\t\t\tN_(\"recode into utf8 (default)\")),\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267459","messageId":"1438696326-19590-26-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 25/45] builtin-am: implement -k/--keep, --keep-non-patch","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:46Z","receivedAt":"2015-08-04T13:51:46Z","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),\ngit-am.sh supported the -k/--keep option to pass the -k option to\ngit-mailsplit.\n\nSince f7e5ea1 (am: learn passing -b to mailinfo, 2012-01-16), git-am.sh\nsupported the --keep-non-patch option to pass the -b option to\ngit-mailsplit.\n\nRe-implement these two options in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 50 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 528b2c9..68dca2e 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -68,6 +68,12 @@ enum patch_format {\n \tPATCH_FORMAT_MBOX\n };\n \n+enum keep_type {\n+\tKEEP_FALSE = 0,\n+\tKEEP_TRUE,      /* pass -k flag to git-mailinfo */\n+\tKEEP_NON_PATCH  /* pass -b flag to git-mailinfo */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -91,6 +97,7 @@ struct am_state {\n \tint quiet;\n \tint signoff;\n \tint utf8;\n+\tint keep; /* enum keep_type */\n \tconst char *resolvemsg;\n \tint rebasing;\n };\n@@ -373,6 +380,14 @@ static void am_load(struct am_state *state)\n \tread_state_file(&sb, state, \"utf8\", 1);\n \tstate->utf8 = !strcmp(sb.buf, \"t\");\n \n+\tread_state_file(&sb, state, \"keep\", 1);\n+\tif (!strcmp(sb.buf, \"t\"))\n+\t\tstate->keep = KEEP_TRUE;\n+\telse if (!strcmp(sb.buf, \"b\"))\n+\t\tstate->keep = KEEP_NON_PATCH;\n+\telse\n+\t\tstate->keep = KEEP_FALSE;\n+\n \tstate->rebasing = !!file_exists(am_path(state, \"rebasing\"));\n \n \tstrbuf_release(&sb);\n@@ -536,6 +551,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \t\t\tconst char **paths)\n {\n \tunsigned char curr_head[GIT_SHA1_RAWSZ];\n+\tconst char *str;\n \n \tif (!patch_format)\n \t\tpatch_format = detect_patch_format(paths);\n@@ -564,6 +580,22 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \n \twrite_file(am_path(state, \"utf8\"), 1, state->utf8 ? \"t\" : \"f\");\n \n+\tswitch (state->keep) {\n+\tcase KEEP_FALSE:\n+\t\tstr = \"f\";\n+\t\tbreak;\n+\tcase KEEP_TRUE:\n+\t\tstr = \"t\";\n+\t\tbreak;\n+\tcase KEEP_NON_PATCH:\n+\t\tstr = \"b\";\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"BUG: invalid value for state->keep\");\n+\t}\n+\n+\twrite_file(am_path(state, \"keep\"), 1, \"%s\", str);\n+\n \tif (state->rebasing)\n \t\twrite_file(am_path(state, \"rebasing\"), 1, \"%s\", \"\");\n \telse\n@@ -731,6 +763,20 @@ static int parse_mail(struct am_state *state, const char *mail)\n \n \targv_array_push(&cp.args, \"mailinfo\");\n \targv_array_push(&cp.args, state->utf8 ? \"-u\" : \"-n\");\n+\n+\tswitch (state->keep) {\n+\tcase KEEP_FALSE:\n+\t\tbreak;\n+\tcase KEEP_TRUE:\n+\t\targv_array_push(&cp.args, \"-k\");\n+\t\tbreak;\n+\tcase KEEP_NON_PATCH:\n+\t\targv_array_push(&cp.args, \"-b\");\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"BUG: invalid value for state->keep\");\n+\t}\n+\n \targv_array_push(&cp.args, am_path(state, \"msg\"));\n \targv_array_push(&cp.args, am_path(state, \"patch\"));\n \n@@ -1471,6 +1517,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"add a Signed-off-by line to the commit message\")),\n \t\tOPT_BOOL('u', \"utf8\", &state.utf8,\n \t\t\tN_(\"recode into utf8 (default)\")),\n+\t\tOPT_SET_INT('k', \"keep\", &state.keep,\n+\t\t\tN_(\"pass -k flag to git-mailinfo\"), KEEP_TRUE),\n+\t\tOPT_SET_INT(0, \"keep-non-patch\", &state.keep,\n+\t\t\tN_(\"pass -b flag to git-mailinfo\"), KEEP_NON_PATCH),\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267440","messageId":"1438696326-19590-27-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 26/45] builtin-am: implement --[no-]message-id, am.messageid","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:47Z","receivedAt":"2015-08-04T13:51:47Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since a078f73 (git-am: add --message-id/--no-message-id, 2014-11-25),\ngit-am.sh supported the --[no-]message-id options, and the\n\"am.messageid\" setting which specifies the default option.\n\n--[no-]message-id tells git-am whether or not the -m option should be\npassed to git-mailinfo.\n\nRe-implement this option in builtin/am.c.\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 68dca2e..8e97839 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -98,6 +98,7 @@ struct am_state {\n \tint signoff;\n \tint utf8;\n \tint keep; /* enum keep_type */\n+\tint message_id;\n \tconst char *resolvemsg;\n \tint rebasing;\n };\n@@ -116,6 +117,8 @@ static void am_state_init(struct am_state *state, const char *dir)\n \tstate->prec = 4;\n \n \tstate->utf8 = 1;\n+\n+\tgit_config_get_bool(\"am.messageid\", &state->message_id);\n }\n \n /**\n@@ -388,6 +391,9 @@ static void am_load(struct am_state *state)\n \telse\n \t\tstate->keep = KEEP_FALSE;\n \n+\tread_state_file(&sb, state, \"messageid\", 1);\n+\tstate->message_id = !strcmp(sb.buf, \"t\");\n+\n \tstate->rebasing = !!file_exists(am_path(state, \"rebasing\"));\n \n \tstrbuf_release(&sb);\n@@ -596,6 +602,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \n \twrite_file(am_path(state, \"keep\"), 1, \"%s\", str);\n \n+\twrite_file(am_path(state, \"messageid\"), 1, state->message_id ? \"t\" : \"f\");\n+\n \tif (state->rebasing)\n \t\twrite_file(am_path(state, \"rebasing\"), 1, \"%s\", \"\");\n \telse\n@@ -777,6 +785,9 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tdie(\"BUG: invalid value for state->keep\");\n \t}\n \n+\tif (state->message_id)\n+\t\targv_array_push(&cp.args, \"-m\");\n+\n \targv_array_push(&cp.args, am_path(state, \"msg\"));\n \targv_array_push(&cp.args, am_path(state, \"patch\"));\n \n@@ -1521,6 +1532,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"pass -k flag to git-mailinfo\"), KEEP_TRUE),\n \t\tOPT_SET_INT(0, \"keep-non-patch\", &state.keep,\n \t\t\tN_(\"pass -b flag to git-mailinfo\"), KEEP_NON_PATCH),\n+\t\tOPT_BOOL('m', \"message-id\", &state.message_id,\n+\t\t\tN_(\"pass -m flag to git-mailinfo\")),\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267437","messageId":"1438696326-19590-28-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 27/45] builtin-am: support --keep-cr, am.keepcr","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:48Z","receivedAt":"2015-08-04T13:51:48Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since ad2c928 (git-am: Add command line parameter `--keep-cr` passing it\nto git-mailsplit, 2010-02-27), git-am.sh supported the --keep-cr option\nand would pass it to git-mailsplit.\n\nSince e80d4cb (git-am: Add am.keepcr and --no-keep-cr to override it,\n2010-02-27), git-am.sh supported the am.keepcr config setting, which\ncontrols whether --keep-cr is on by default.\n\nRe-implement the above in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 29 +++++++++++++++++++++++------\n 1 file changed, 23 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8e97839..e34bc51 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -502,7 +502,7 @@ done:\n  * Splits out individual email patches from `paths`, where each path is either\n  * a mbox file or a Maildir. Returns 0 on success, -1 on failure.\n  */\n-static int split_mail_mbox(struct am_state *state, const char **paths)\n+static int split_mail_mbox(struct am_state *state, const char **paths, int keep_cr)\n {\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tstruct strbuf last = STRBUF_INIT;\n@@ -512,6 +512,8 @@ static int split_mail_mbox(struct am_state *state, const char **paths)\n \targv_array_pushf(&cp.args, \"-d%d\", state->prec);\n \targv_array_pushf(&cp.args, \"-o%s\", state->dir);\n \targv_array_push(&cp.args, \"-b\");\n+\tif (keep_cr)\n+\t\targv_array_push(&cp.args, \"--keep-cr\");\n \targv_array_push(&cp.args, \"--\");\n \targv_array_pushv(&cp.args, paths);\n \n@@ -536,14 +538,22 @@ static int split_mail_mbox(struct am_state *state, const char **paths)\n  * state->cur will be set to the index of the first mail, and state->last will\n  * be set to the index of the last mail.\n  *\n+ * Set keep_cr to 0 to convert all lines ending with \\r\\n to end with \\n, 1\n+ * to disable this behavior, -1 to use the default configured setting.\n+ *\n  * Returns 0 on success, -1 on failure.\n  */\n static int split_mail(struct am_state *state, enum patch_format patch_format,\n-\t\t\tconst char **paths)\n+\t\t\tconst char **paths, int keep_cr)\n {\n+\tif (keep_cr < 0) {\n+\t\tkeep_cr = 0;\n+\t\tgit_config_get_bool(\"am.keepcr\", &keep_cr);\n+\t}\n+\n \tswitch (patch_format) {\n \tcase PATCH_FORMAT_MBOX:\n-\t\treturn split_mail_mbox(state, paths);\n+\t\treturn split_mail_mbox(state, paths, keep_cr);\n \tdefault:\n \t\tdie(\"BUG: invalid patch_format\");\n \t}\n@@ -554,7 +564,7 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,\n  * Setup a new am session for applying patches\n  */\n static void am_setup(struct am_state *state, enum patch_format patch_format,\n-\t\t\tconst char **paths)\n+\t\t\tconst char **paths, int keep_cr)\n {\n \tunsigned char curr_head[GIT_SHA1_RAWSZ];\n \tconst char *str;\n@@ -570,7 +580,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \tif (mkdir(state->dir, 0777) < 0 && errno != EEXIST)\n \t\tdie_errno(_(\"failed to create directory '%s'\"), state->dir);\n \n-\tif (split_mail(state, patch_format, paths) < 0) {\n+\tif (split_mail(state, patch_format, paths, keep_cr) < 0) {\n \t\tam_destroy(state);\n \t\tdie(_(\"Failed to split patches.\"));\n \t}\n@@ -1511,6 +1521,7 @@ enum resume_mode {\n int cmd_am(int argc, const char **argv, const char *prefix)\n {\n \tstruct am_state state;\n+\tint keep_cr = -1;\n \tint patch_format = PATCH_FORMAT_UNKNOWN;\n \tenum resume_mode resume = RESUME_FALSE;\n \n@@ -1534,6 +1545,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"pass -b flag to git-mailinfo\"), KEEP_NON_PATCH),\n \t\tOPT_BOOL('m', \"message-id\", &state.message_id,\n \t\t\tN_(\"pass -m flag to git-mailinfo\")),\n+\t\t{ OPTION_SET_INT, 0, \"keep-cr\", &keep_cr, NULL,\n+\t\t  N_(\"pass --keep-cr flag to git-mailsplit for mbox format\"),\n+\t\t  PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},\n+\t\t{ OPTION_SET_INT, 0, \"no-keep-cr\", &keep_cr, NULL,\n+\t\t  N_(\"do not pass --keep-cr flag to git-mailsplit independent of am.keepcr\"),\n+\t\t  PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n@@ -1631,7 +1648,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\t\targv_array_push(&paths, mkpath(\"%s/%s\", prefix, argv[i]));\n \t\t}\n \n-\t\tam_setup(&state, patch_format, paths.argv);\n+\t\tam_setup(&state, patch_format, paths.argv, keep_cr);\n \n \t\targv_array_clear(&paths);\n \t}\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267438","messageId":"1438696326-19590-29-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 28/45] builtin-am: implement --[no-]scissors","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:49Z","receivedAt":"2015-08-04T13:51:49Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 017678b (am/mailinfo: Disable scissors processing by default,\n2009-08-26), git-am supported the --[no-]scissors option, passing it to\ngit-mailinfo.\n\nRe-implement support for this option in builtin/am.c.\n\nSince the default setting of --scissors in git-mailinfo can be\nconfigured with mailinfo.scissors (and perhaps through other settings in\nthe future), to be safe we make an explicit distinction between\nSCISSORS_UNSET, SCISSORS_TRUE and SCISSORS_FALSE.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 48 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex e34bc51..727cfb8 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -74,6 +74,12 @@ enum keep_type {\n \tKEEP_NON_PATCH  /* pass -b flag to git-mailinfo */\n };\n \n+enum scissors_type {\n+\tSCISSORS_UNSET = -1,\n+\tSCISSORS_FALSE = 0,  /* pass --no-scissors to git-mailinfo */\n+\tSCISSORS_TRUE        /* pass --scissors to git-mailinfo */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -99,6 +105,7 @@ struct am_state {\n \tint utf8;\n \tint keep; /* enum keep_type */\n \tint message_id;\n+\tint scissors; /* enum scissors_type */\n \tconst char *resolvemsg;\n \tint rebasing;\n };\n@@ -119,6 +126,8 @@ static void am_state_init(struct am_state *state, const char *dir)\n \tstate->utf8 = 1;\n \n \tgit_config_get_bool(\"am.messageid\", &state->message_id);\n+\n+\tstate->scissors = SCISSORS_UNSET;\n }\n \n /**\n@@ -394,6 +403,14 @@ static void am_load(struct am_state *state)\n \tread_state_file(&sb, state, \"messageid\", 1);\n \tstate->message_id = !strcmp(sb.buf, \"t\");\n \n+\tread_state_file(&sb, state, \"scissors\", 1);\n+\tif (!strcmp(sb.buf, \"t\"))\n+\t\tstate->scissors = SCISSORS_TRUE;\n+\telse if (!strcmp(sb.buf, \"f\"))\n+\t\tstate->scissors = SCISSORS_FALSE;\n+\telse\n+\t\tstate->scissors = SCISSORS_UNSET;\n+\n \tstate->rebasing = !!file_exists(am_path(state, \"rebasing\"));\n \n \tstrbuf_release(&sb);\n@@ -614,6 +631,22 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \n \twrite_file(am_path(state, \"messageid\"), 1, state->message_id ? \"t\" : \"f\");\n \n+\tswitch (state->scissors) {\n+\tcase SCISSORS_UNSET:\n+\t\tstr = \"\";\n+\t\tbreak;\n+\tcase SCISSORS_FALSE:\n+\t\tstr = \"f\";\n+\t\tbreak;\n+\tcase SCISSORS_TRUE:\n+\t\tstr = \"t\";\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"BUG: invalid value for state->scissors\");\n+\t}\n+\n+\twrite_file(am_path(state, \"scissors\"), 1, \"%s\", str);\n+\n \tif (state->rebasing)\n \t\twrite_file(am_path(state, \"rebasing\"), 1, \"%s\", \"\");\n \telse\n@@ -798,6 +831,19 @@ static int parse_mail(struct am_state *state, const char *mail)\n \tif (state->message_id)\n \t\targv_array_push(&cp.args, \"-m\");\n \n+\tswitch (state->scissors) {\n+\tcase SCISSORS_UNSET:\n+\t\tbreak;\n+\tcase SCISSORS_FALSE:\n+\t\targv_array_push(&cp.args, \"--no-scissors\");\n+\t\tbreak;\n+\tcase SCISSORS_TRUE:\n+\t\targv_array_push(&cp.args, \"--scissors\");\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"BUG: invalid value for state->scissors\");\n+\t}\n+\n \targv_array_push(&cp.args, am_path(state, \"msg\"));\n \targv_array_push(&cp.args, am_path(state, \"patch\"));\n \n@@ -1551,6 +1597,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_SET_INT, 0, \"no-keep-cr\", &keep_cr, NULL,\n \t\t  N_(\"do not pass --keep-cr flag to git-mailsplit independent of am.keepcr\"),\n \t\t  PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n+\t\tOPT_BOOL('c', \"scissors\", &state.scissors,\n+\t\t\tN_(\"strip everything before a scissors line\")),\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267458","messageId":"1438696326-19590-30-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 29/45] builtin-am: pass git-apply's options to git-apply","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:50Z","receivedAt":"2015-08-04T13:51:50Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"git-am.sh recognizes some of git-apply's options, and would pass them to\ngit-apply:\n\n* --whitespace, since 8c31cb8 (git-am: --whitespace=x option.,\n  2006-02-28)\n\n* -C, since 67dad68 (add -C[NUM] to git-am, 2007-02-08)\n\n* -p, since 2092a1f (Teach git-am to pass -p option down to git-apply,\n  2007-02-11)\n\n* --directory, since b47dfe9 (git-am: add --directory=<dir> option,\n  2009-01-11)\n\n* --reject, since b80da42 (git-am: implement --reject option passed to\n  git-apply, 2009-01-23)\n\n* --ignore-space-change, --ignore-whitespace, since 86c91f9 (git apply:\n  option to ignore whitespace differences, 2009-08-04)\n\n* --exclude, since 77e9e49 (am: pass exclude down to apply, 2011-08-03)\n\n* --include, since 58725ef (am: support --include option, 2012-03-28)\n\n* --reject, since b80da42 (git-am: implement --reject option passed to\n  git-apply, 2009-01-23)\n\nRe-implement support for these options in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 45 +++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 45 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 727cfb8..f842f69 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -106,6 +106,7 @@ struct am_state {\n \tint keep; /* enum keep_type */\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n+\tstruct argv_array git_apply_opts;\n \tconst char *resolvemsg;\n \tint rebasing;\n };\n@@ -128,6 +129,8 @@ static void am_state_init(struct am_state *state, const char *dir)\n \tgit_config_get_bool(\"am.messageid\", &state->message_id);\n \n \tstate->scissors = SCISSORS_UNSET;\n+\n+\targv_array_init(&state->git_apply_opts);\n }\n \n /**\n@@ -140,6 +143,7 @@ static void am_state_release(struct am_state *state)\n \tfree(state->author_email);\n \tfree(state->author_date);\n \tfree(state->msg);\n+\targv_array_clear(&state->git_apply_opts);\n }\n \n /**\n@@ -411,6 +415,11 @@ static void am_load(struct am_state *state)\n \telse\n \t\tstate->scissors = SCISSORS_UNSET;\n \n+\tread_state_file(&sb, state, \"apply-opt\", 1);\n+\targv_array_clear(&state->git_apply_opts);\n+\tif (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0)\n+\t\tdie(_(\"could not parse %s\"), am_path(state, \"apply-opt\"));\n+\n \tstate->rebasing = !!file_exists(am_path(state, \"rebasing\"));\n \n \tstrbuf_release(&sb);\n@@ -585,6 +594,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n {\n \tunsigned char curr_head[GIT_SHA1_RAWSZ];\n \tconst char *str;\n+\tstruct strbuf sb = STRBUF_INIT;\n \n \tif (!patch_format)\n \t\tpatch_format = detect_patch_format(paths);\n@@ -647,6 +657,9 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,\n \n \twrite_file(am_path(state, \"scissors\"), 1, \"%s\", str);\n \n+\tsq_quote_argv(&sb, state->git_apply_opts.argv, 0);\n+\twrite_file(am_path(state, \"apply-opt\"), 1, \"%s\", sb.buf);\n+\n \tif (state->rebasing)\n \t\twrite_file(am_path(state, \"rebasing\"), 1, \"%s\", \"\");\n \telse\n@@ -671,6 +684,8 @@ 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+\tstrbuf_release(&sb);\n }\n \n /**\n@@ -1058,6 +1073,8 @@ static int run_apply(const struct am_state *state, const char *index_file)\n \n \targv_array_push(&cp.args, \"apply\");\n \n+\targv_array_pushv(&cp.args, state->git_apply_opts.argv);\n+\n \tif (index_file)\n \t\targv_array_push(&cp.args, \"--cached\");\n \telse\n@@ -1084,6 +1101,7 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f\n \n \tcp.git_cmd = 1;\n \targv_array_push(&cp.args, \"apply\");\n+\targv_array_pushv(&cp.args, state->git_apply_opts.argv);\n \targv_array_pushf(&cp.args, \"--build-fake-ancestor=%s\", index_file);\n \targv_array_push(&cp.args, am_path(state, \"patch\"));\n \n@@ -1599,9 +1617,36 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t  PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},\n \t\tOPT_BOOL('c', \"scissors\", &state.scissors,\n \t\t\tN_(\"strip everything before a scissors line\")),\n+\t\tOPT_PASSTHRU_ARGV(0, \"whitespace\", &state.git_apply_opts, N_(\"action\"),\n+\t\t\tN_(\"pass it through git-apply\"),\n+\t\t\t0),\n+\t\tOPT_PASSTHRU_ARGV(0, \"ignore-space-change\", &state.git_apply_opts, NULL,\n+\t\t\tN_(\"pass it through git-apply\"),\n+\t\t\tPARSE_OPT_NOARG),\n+\t\tOPT_PASSTHRU_ARGV(0, \"ignore-whitespace\", &state.git_apply_opts, NULL,\n+\t\t\tN_(\"pass it through git-apply\"),\n+\t\t\tPARSE_OPT_NOARG),\n+\t\tOPT_PASSTHRU_ARGV(0, \"directory\", &state.git_apply_opts, N_(\"root\"),\n+\t\t\tN_(\"pass it through git-apply\"),\n+\t\t\t0),\n+\t\tOPT_PASSTHRU_ARGV(0, \"exclude\", &state.git_apply_opts, N_(\"path\"),\n+\t\t\tN_(\"pass it through git-apply\"),\n+\t\t\t0),\n+\t\tOPT_PASSTHRU_ARGV(0, \"include\", &state.git_apply_opts, N_(\"path\"),\n+\t\t\tN_(\"pass it through git-apply\"),\n+\t\t\t0),\n+\t\tOPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_(\"n\"),\n+\t\t\tN_(\"pass it through git-apply\"),\n+\t\t\t0),\n+\t\tOPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_(\"num\"),\n+\t\t\tN_(\"pass it through git-apply\"),\n+\t\t\t0),\n \t\tOPT_CALLBACK(0, \"patch-format\", &patch_format, N_(\"format\"),\n \t\t\tN_(\"format the patch(es) are in\"),\n \t\t\tparse_opt_patchformat),\n+\t\tOPT_PASSTHRU_ARGV(0, \"reject\", &state.git_apply_opts, NULL,\n+\t\t\tN_(\"pass it through git-apply\"),\n+\t\t\tPARSE_OPT_NOARG),\n \t\tOPT_STRING(0, \"resolvemsg\", &state.resolvemsg, NULL,\n \t\t\tN_(\"override error message when patch failure occurs\")),\n \t\tOPT_CMDMODE(0, \"continue\", &resume,\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267444","messageId":"1438696326-19590-31-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 30/45] builtin-am: implement --ignore-date","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:51Z","receivedAt":"2015-08-04T13:51:51Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since a79ec62 (git-am: Add --ignore-date option, 2009-01-24), git-am.sh\nsupported the --ignore-date option, and would use the current timestamp\ninstead of the one provided in the patch if the option was set.\n\nRe-implement this option in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex f842f69..84d3e05 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -108,6 +108,7 @@ struct am_state {\n \tint scissors; /* enum scissors_type */\n \tstruct argv_array git_apply_opts;\n \tconst char *resolvemsg;\n+\tint ignore_date;\n \tint rebasing;\n };\n \n@@ -1217,7 +1218,8 @@ static void do_commit(const struct am_state *state)\n \t}\n \n \tauthor = fmt_ident(state->author_name, state->author_email,\n-\t\t\tstate->author_date, IDENT_STRICT);\n+\t\t\tstate->ignore_date ? NULL : state->author_date,\n+\t\t\tIDENT_STRICT);\n \n \tif (commit_tree(state->msg, state->msg_len, tree, parents, commit,\n \t\t\t\tauthor, NULL))\n@@ -1661,6 +1663,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"abort\", &resume,\n \t\t\tN_(\"restore the original branch and abort the patching operation.\"),\n \t\t\tRESUME_ABORT),\n+\t\tOPT_BOOL(0, \"ignore-date\", &state.ignore_date,\n+\t\t\tN_(\"use current timestamp for author date\")),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267439","messageId":"1438696326-19590-32-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 31/45] builtin-am: implement --committer-date-is-author-date","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:52Z","receivedAt":"2015-08-04T13:51:52Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 3f01ad6 (am: Add --committer-date-is-author-date option,\n2009-01-22), git-am.sh implemented the --committer-date-is-author-date\noption, which tells git-am to use the timestamp recorded in the email\nmessage as both author and committer date.\n\nRe-implement this option in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 84d3e05..1561580 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -108,6 +108,7 @@ struct am_state {\n \tint scissors; /* enum scissors_type */\n \tstruct argv_array git_apply_opts;\n \tconst char *resolvemsg;\n+\tint committer_date_is_author_date;\n \tint ignore_date;\n \tint rebasing;\n };\n@@ -1221,6 +1222,10 @@ static void do_commit(const struct am_state *state)\n \t\t\tstate->ignore_date ? NULL : state->author_date,\n \t\t\tIDENT_STRICT);\n \n+\tif (state->committer_date_is_author_date)\n+\t\tsetenv(\"GIT_COMMITTER_DATE\",\n+\t\t\tstate->ignore_date ? \"\" : state->author_date, 1);\n+\n \tif (commit_tree(state->msg, state->msg_len, tree, parents, commit,\n \t\t\t\tauthor, NULL))\n \t\tdie(_(\"failed to write commit object\"));\n@@ -1663,6 +1668,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"abort\", &resume,\n \t\t\tN_(\"restore the original branch and abort the patching operation.\"),\n \t\t\tRESUME_ABORT),\n+\t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n+\t\t\t&state.committer_date_is_author_date,\n+\t\t\tN_(\"lie about committer date\")),\n \t\tOPT_BOOL(0, \"ignore-date\", &state.ignore_date,\n \t\t\tN_(\"use current timestamp for author date\")),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267442","messageId":"1438696326-19590-33-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 32/45] builtin-am: implement -S/--gpg-sign, commit.gpgsign","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:53Z","receivedAt":"2015-08-04T13:51:53Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 3b4e395 (am: add the --gpg-sign option, 2014-02-01), git-am.sh\nsupported the --gpg-sign option, and would pass it to git-commit-tree,\nthus GPG-signing the commit object.\n\nRe-implement this option in builtin/am.c.\n\ngit-commit-tree would also sign the commit by default if the\ncommit.gpgsign setting is true. Since we do not run commit-tree, we\nre-implement this behavior by handling the commit.gpgsign setting\nourselves.\n\nHelped-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 11 ++++++++++-\n 1 file changed, 10 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1561580..18611fa 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -110,6 +110,7 @@ struct am_state {\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n \tint ignore_date;\n+\tconst char *sign_commit;\n \tint rebasing;\n };\n \n@@ -119,6 +120,8 @@ struct am_state {\n  */\n static void am_state_init(struct am_state *state, const char *dir)\n {\n+\tint gpgsign;\n+\n \tmemset(state, 0, sizeof(*state));\n \n \tassert(dir);\n@@ -133,6 +136,9 @@ static void am_state_init(struct am_state *state, const char *dir)\n \tstate->scissors = SCISSORS_UNSET;\n \n \targv_array_init(&state->git_apply_opts);\n+\n+\tif (!git_config_get_bool(\"commit.gpgsign\", &gpgsign))\n+\t\tstate->sign_commit = gpgsign ? \"\" : NULL;\n }\n \n /**\n@@ -1227,7 +1233,7 @@ static void do_commit(const struct am_state *state)\n \t\t\tstate->ignore_date ? \"\" : state->author_date, 1);\n \n \tif (commit_tree(state->msg, state->msg_len, tree, parents, commit,\n-\t\t\t\tauthor, NULL))\n+\t\t\t\tauthor, state->sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n \n \treflog_msg = getenv(\"GIT_REFLOG_ACTION\");\n@@ -1673,6 +1679,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"lie about committer date\")),\n \t\tOPT_BOOL(0, \"ignore-date\", &state.ignore_date,\n \t\t\tN_(\"use current timestamp for author date\")),\n+\t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n+\t\t  N_(\"GPG-sign commits\"),\n+\t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267446","messageId":"1438696326-19590-34-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 33/45] builtin-am: invoke post-rewrite hook","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:54Z","receivedAt":"2015-08-04T13:51:54Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 96e1948 (rebase: invoke post-rewrite hook, 2010-03-12), git-am.sh\nwill invoke the post-rewrite hook after it successfully finishes\napplying all the queued patches.\n\nTo do this, when parsing a mail to extract its patch and metadata, in\n--rebasing mode git-am.sh will also store the original commit ID in the\n$state_dir/original-commit file. Once it applies and commits the patch,\nthe original commit ID, and the new commit ID, will be appended to the\n$state_dir/rewritten file.\n\nOnce all of the queued mail have been processed, git-am.sh will then\ninvoke the post-rewrite hook with the contents of the\n$state_dir/rewritten file.\n\nRe-implement this in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 55 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 18611fa..dbec9fc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -95,6 +95,9 @@ struct am_state {\n \tchar *msg;\n \tsize_t msg_len;\n \n+\t/* when --rebasing, records the original commit the patch came from */\n+\tunsigned char orig_commit[GIT_SHA1_RAWSZ];\n+\n \t/* number of digits in patch filename */\n \tint prec;\n \n@@ -392,6 +395,11 @@ static void am_load(struct am_state *state)\n \n \tread_commit_msg(state);\n \n+\tif (read_state_file(&sb, state, \"original-commit\", 1) < 0)\n+\t\thashclr(state->orig_commit);\n+\telse if (get_sha1_hex(sb.buf, state->orig_commit) < 0)\n+\t\tdie(_(\"could not parse %s\"), am_path(state, \"original-commit\"));\n+\n \tread_state_file(&sb, state, \"threeway\", 1);\n \tstate->threeway = !strcmp(sb.buf, \"t\");\n \n@@ -447,6 +455,30 @@ static void am_destroy(const struct am_state *state)\n }\n \n /**\n+ * Runs post-rewrite hook. Returns it exit code.\n+ */\n+static int run_post_rewrite_hook(const struct am_state *state)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tconst char *hook = find_hook(\"post-rewrite\");\n+\tint ret;\n+\n+\tif (!hook)\n+\t\treturn 0;\n+\n+\targv_array_push(&cp.args, hook);\n+\targv_array_push(&cp.args, \"rebase\");\n+\n+\tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n+\tcp.stdout_to_stderr = 1;\n+\n+\tret = run_command(&cp);\n+\n+\tclose(cp.in);\n+\treturn ret;\n+}\n+\n+/**\n  * Determines if the file looks like a piece of RFC2822 mail by grabbing all\n  * non-indented lines and checking if they look like they begin with valid\n  * header field names.\n@@ -720,6 +752,9 @@ static void am_next(struct am_state *state)\n \tunlink(am_path(state, \"author-script\"));\n \tunlink(am_path(state, \"final-commit\"));\n \n+\thashclr(state->orig_commit);\n+\tunlink(am_path(state, \"original-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@@ -1038,6 +1073,8 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm\n  * directly. This is used in --rebasing mode to bypass git-mailinfo's munging\n  * of patches.\n  *\n+ * state->orig_commit will be set to the original commit ID.\n+ *\n  * Will always return 0 as the patch should never be skipped.\n  */\n static int parse_mail_rebase(struct am_state *state, const char *mail)\n@@ -1054,6 +1091,10 @@ static int parse_mail_rebase(struct am_state *state, const char *mail)\n \n \twrite_commit_patch(state, commit);\n \n+\thashcpy(state->orig_commit, commit_sha1);\n+\twrite_file(am_path(state, \"original-commit\"), 1, \"%s\",\n+\t\t\tsha1_to_hex(commit_sha1));\n+\n \treturn 0;\n }\n \n@@ -1245,6 +1286,15 @@ static void do_commit(const struct am_state *state)\n \n \tupdate_ref(sb.buf, \"HEAD\", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR);\n \n+\tif (state->rebasing) {\n+\t\tFILE *fp = xfopen(am_path(state, \"rewritten\"), \"a\");\n+\n+\t\tassert(!is_null_sha1(state->orig_commit));\n+\t\tfprintf(fp, \"%s \", sha1_to_hex(state->orig_commit));\n+\t\tfprintf(fp, \"%s\\n\", sha1_to_hex(commit));\n+\t\tfclose(fp);\n+\t}\n+\n \tstrbuf_release(&sb);\n }\n \n@@ -1353,6 +1403,11 @@ next:\n \t\tam_next(state);\n \t}\n \n+\tif (!is_empty_file(am_path(state, \"rewritten\"))) {\n+\t\tassert(state->rebasing);\n+\t\trun_post_rewrite_hook(state);\n+\t}\n+\n \t/*\n \t * In rebasing mode, it's up to the caller to take care of\n \t * housekeeping.\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267450","messageId":"1438696326-19590-35-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 34/45] builtin-am: support automatic notes copying","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:55Z","receivedAt":"2015-08-04T13:51:55Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since eb2151b (rebase: support automatic notes copying, 2010-03-12),\ngit-am.sh supported automatic notes copying in --rebasing mode by\ninvoking \"git notes copy\" once it has finished applying all the patches.\n\nRe-implement this feature in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 60 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex dbec9fc..7d7f91d 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -23,6 +23,7 @@\n #include \"merge-recursive.h\"\n #include \"revision.h\"\n #include \"log-tree.h\"\n+#include \"notes-utils.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -479,6 +480,64 @@ static int run_post_rewrite_hook(const struct am_state *state)\n }\n \n /**\n+ * Reads the state directory's \"rewritten\" file, and copies notes from the old\n+ * commits listed in the file to their rewritten commits.\n+ *\n+ * Returns 0 on success, -1 on failure.\n+ */\n+static int copy_notes_for_rebase(const struct am_state *state)\n+{\n+\tstruct notes_rewrite_cfg *c;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *invalid_line = _(\"Malformed input line: '%s'.\");\n+\tconst char *msg = \"Notes added by 'git rebase'\";\n+\tFILE *fp;\n+\tint ret = 0;\n+\n+\tassert(state->rebasing);\n+\n+\tc = init_copy_notes_for_rewrite(\"rebase\");\n+\tif (!c)\n+\t\treturn 0;\n+\n+\tfp = xfopen(am_path(state, \"rewritten\"), \"r\");\n+\n+\twhile (!strbuf_getline(&sb, fp, '\\n')) {\n+\t\tunsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ];\n+\n+\t\tif (sb.len != GIT_SHA1_HEXSZ * 2 + 1) {\n+\t\t\tret = error(invalid_line, sb.buf);\n+\t\t\tgoto finish;\n+\t\t}\n+\n+\t\tif (get_sha1_hex(sb.buf, from_obj)) {\n+\t\t\tret = error(invalid_line, sb.buf);\n+\t\t\tgoto finish;\n+\t\t}\n+\n+\t\tif (sb.buf[GIT_SHA1_HEXSZ] != ' ') {\n+\t\t\tret = error(invalid_line, sb.buf);\n+\t\t\tgoto finish;\n+\t\t}\n+\n+\t\tif (get_sha1_hex(sb.buf + GIT_SHA1_HEXSZ + 1, to_obj)) {\n+\t\t\tret = error(invalid_line, sb.buf);\n+\t\t\tgoto finish;\n+\t\t}\n+\n+\t\tif (copy_note_for_rewrite(c, from_obj, to_obj))\n+\t\t\tret = error(_(\"Failed to copy notes from '%s' to '%s'\"),\n+\t\t\t\t\tsha1_to_hex(from_obj), sha1_to_hex(to_obj));\n+\t}\n+\n+finish:\n+\tfinish_copy_notes_for_rewrite(c, msg);\n+\tfclose(fp);\n+\tstrbuf_release(&sb);\n+\treturn ret;\n+}\n+\n+/**\n  * Determines if the file looks like a piece of RFC2822 mail by grabbing all\n  * non-indented lines and checking if they look like they begin with valid\n  * header field names.\n@@ -1405,6 +1464,7 @@ next:\n \n \tif (!is_empty_file(am_path(state, \"rewritten\"))) {\n \t\tassert(state->rebasing);\n+\t\tcopy_notes_for_rebase(state);\n \t\trun_post_rewrite_hook(state);\n \t}\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267449","messageId":"1438696326-19590-36-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 35/45] builtin-am: invoke applypatch-msg hook","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:56Z","receivedAt":"2015-08-04T13:51:56Z","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),\ngit-am.sh will invoke the applypatch-msg hooks just after extracting the\npatch message. If the applypatch-msg hook exits with a non-zero status,\ngit-am.sh abort before even applying the patch to the index.\n\nRe-implement this in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 24 ++++++++++++++++++++++++\n 1 file changed, 24 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 7d7f91d..f0e3aab 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -456,6 +456,27 @@ static void am_destroy(const struct am_state *state)\n }\n \n /**\n+ * Runs applypatch-msg hook. Returns its exit code.\n+ */\n+static int run_applypatch_msg_hook(struct am_state *state)\n+{\n+\tint ret;\n+\n+\tassert(state->msg);\n+\tret = run_hook_le(NULL, \"applypatch-msg\", am_path(state, \"final-commit\"), NULL);\n+\n+\tif (!ret) {\n+\t\tfree(state->msg);\n+\t\tstate->msg = NULL;\n+\t\tif (read_commit_msg(state) < 0)\n+\t\t\tdie(_(\"'%s' was deleted by the applypatch-msg hook\"),\n+\t\t\t\tam_path(state, \"final-commit\"));\n+\t}\n+\n+\treturn ret;\n+}\n+\n+/**\n  * Runs post-rewrite hook. Returns it exit code.\n  */\n static int run_post_rewrite_hook(const struct am_state *state)\n@@ -1420,6 +1441,9 @@ static void am_run(struct am_state *state, int resume)\n \t\t\twrite_commit_msg(state);\n \t\t}\n \n+\t\tif (run_applypatch_msg_hook(state))\n+\t\t\texit(1);\n+\n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n \t\tapply_status = run_apply(state, NULL);\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267447","messageId":"1438696326-19590-37-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 36/45] builtin-am: invoke pre-applypatch hook","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:57Z","receivedAt":"2015-08-04T13:51: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),\ngit-am.sg will invoke the pre-applypatch hook after applying the patch\nto the index, but before a commit is made. Should the hook exit with a\nnon-zero status, git am will exit.\n\nRe-implement this in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex f0e3aab..7a7da94 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1334,6 +1334,9 @@ static void do_commit(const struct am_state *state)\n \tconst char *reflog_msg, *author;\n \tstruct strbuf sb = STRBUF_INIT;\n \n+\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n+\t\texit(1);\n+\n \tif (write_cache_as_tree(tree, 0, NULL))\n \t\tdie(_(\"git write-tree failed to write a tree\"));\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267445","messageId":"1438696326-19590-38-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 37/45] builtin-am: invoke post-applypatch hook","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:58Z","receivedAt":"2015-08-04T13:51:58Z","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),\ngit-am.sh will invoke the post-applypatch hook after the patch is\napplied and a commit is made. The exit code of the hook is ignored.\n\nRe-implement this in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 7a7da94..c313e58 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1378,6 +1378,8 @@ static void do_commit(const struct am_state *state)\n \t\tfclose(fp);\n \t}\n \n+\trun_hook_le(NULL, \"post-applypatch\", NULL);\n+\n \tstrbuf_release(&sb);\n }\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267452","messageId":"1438696326-19590-39-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 38/45] builtin-am: rerere support","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:51:59Z","receivedAt":"2015-08-04T13:51:59Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"git-am.sh will call git-rerere at the following events:\n\n* \"git rerere\" when a three-way merge fails to record the conflicted\n  automerge results. Since 8389b52 (git-rerere: reuse recorded resolve.,\n  2006-01-28)\n\n  * Since cb6020b (Teach --[no-]rerere-autoupdate option to merge,\n    revert and friends, 2009-12-04), git-am.sh supports the\n    --[no-]rerere-autoupdate option as well, and would pass it to\n    git-rerere.\n\n* \"git rerere\" when --resolved, to record the hand resolution. Since\n  f131dd4 (rerere: record (or avoid misrecording) resolved, skipped or\n  aborted rebase/am, 2006-12-08)\n\n* \"git rerere clear\" when --skip-ing. Since f131dd4 (rerere: record (or\n  avoid misrecording) resolved, skipped or aborted rebase/am,\n  2006-12-08)\n\n* \"git rerere clear\" when --abort-ing. Since 3e5057a (git am --abort,\n  2008-07-16)\n\nRe-implement the above in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 25 +++++++++++++++++++++++++\n 1 file changed, 25 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex c313e58..33d1f24 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -24,6 +24,7 @@\n #include \"revision.h\"\n #include \"log-tree.h\"\n #include \"notes-utils.h\"\n+#include \"rerere.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -114,6 +115,7 @@ struct am_state {\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n \tint ignore_date;\n+\tint allow_rerere_autoupdate;\n \tconst char *sign_commit;\n \tint rebasing;\n };\n@@ -1312,6 +1314,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa\n \t\to.verbosity = 0;\n \n \tif (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) {\n+\t\trerere(state->allow_rerere_autoupdate);\n \t\tfree(his_tree_name);\n \t\treturn error(_(\"Failed to merge in the changes.\"));\n \t}\n@@ -1531,6 +1534,8 @@ static void am_resolve(struct am_state *state)\n \t\tdie_user_resolve(state);\n \t}\n \n+\trerere(0);\n+\n \tdo_commit(state);\n \n \tam_next(state);\n@@ -1631,12 +1636,29 @@ static int clean_index(const unsigned char *head, const unsigned char *remote)\n }\n \n /**\n+ * Resets rerere's merge resolution metadata.\n+ */\n+static void am_rerere_clear(void)\n+{\n+\tstruct string_list merge_rr = STRING_LIST_INIT_DUP;\n+\tint fd = setup_rerere(&merge_rr, 0);\n+\n+\tif (fd < 0)\n+\t\treturn;\n+\n+\trerere_clear(&merge_rr);\n+\tstring_list_clear(&merge_rr, 1);\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+\tam_rerere_clear();\n+\n \tif (get_sha1(\"HEAD\", head))\n \t\thashcpy(head, EMPTY_TREE_SHA1_BIN);\n \n@@ -1694,6 +1716,8 @@ static void am_abort(struct am_state *state)\n \t\treturn;\n \t}\n \n+\tam_rerere_clear();\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@@ -1823,6 +1847,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"lie about committer date\")),\n \t\tOPT_BOOL(0, \"ignore-date\", &state.ignore_date,\n \t\t\tN_(\"use current timestamp for author date\")),\n+\t\tOPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate),\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267451","messageId":"1438696326-19590-40-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 39/45] builtin-am: support and auto-detect StGit patches","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:52:00Z","receivedAt":"2015-08-04T13:52:00Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27),\ngit-am.sh supported converting StGit patches into RFC2822 mail patches\nthat can be parsed with git-mailinfo.\n\nImplement this by introducing two functions in builtin/am.c:\nstgit_patch_to_mail() and split_mail_conv().\n\nstgit_patch_to_mail() is a callback function for split_mail_conv(), and\ncontains the logic for converting an StGit patch into an RFC2822 mail\npatch.\n\nsplit_mail_conv() implements the logic to go through each file in the\n`paths` list, reading from stdin where specified, and calls the callback\nfunction to write the converted patch to the corresponding output file\nin the state directory. This interface should be generic enough to\nsupport other foreign patch formats in the future.\n\nSince 15ced75 (git-am foreign patch support: autodetect some patch\nformats, 2009-05-27), git-am.sh is able to auto-detect StGit patches.\nRe-implement this in builtin/am.c.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 131 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 33d1f24..d82d07e 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -65,9 +65,22 @@ static int linelen(const char *msg)\n \treturn strchrnul(msg, '\\n') - msg;\n }\n \n+/**\n+ * Returns true if `str` consists of only whitespace, false otherwise.\n+ */\n+static int str_isspace(const char *str)\n+{\n+\tfor (; *str; str++)\n+\t\tif (!isspace(*str))\n+\t\t\treturn 0;\n+\n+\treturn 1;\n+}\n+\n enum patch_format {\n \tPATCH_FORMAT_UNKNOWN = 0,\n-\tPATCH_FORMAT_MBOX\n+\tPATCH_FORMAT_MBOX,\n+\tPATCH_FORMAT_STGIT\n };\n \n enum keep_type {\n@@ -610,6 +623,8 @@ static int detect_patch_format(const char **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@@ -635,6 +650,23 @@ static int detect_patch_format(const char **paths)\n \t\tgoto done;\n \t}\n \n+\tstrbuf_reset(&l2);\n+\tstrbuf_getline_crlf(&l2, fp);\n+\tstrbuf_reset(&l3);\n+\tstrbuf_getline_crlf(&l3, fp);\n+\n+\t/*\n+\t * If the second line is empty and the third is a From, Author or Date\n+\t * entry, this is likely an StGit patch.\n+\t */\n+\tif (l1.len && !l2.len &&\n+\t\t(starts_with(l3.buf, \"From:\") ||\n+\t\t starts_with(l3.buf, \"Author:\") ||\n+\t\t starts_with(l3.buf, \"Date:\"))) {\n+\t\tret = PATCH_FORMAT_STGIT;\n+\t\tgoto done;\n+\t}\n+\n \tif (l1.len && is_mail(fp)) {\n \t\tret = PATCH_FORMAT_MBOX;\n \t\tgoto done;\n@@ -675,6 +707,100 @@ static int split_mail_mbox(struct am_state *state, const char **paths, int keep_\n }\n \n /**\n+ * Callback signature for split_mail_conv(). The foreign patch should be\n+ * read from `in`, and the converted patch (in RFC2822 mail format) should be\n+ * written to `out`. Return 0 on success, or -1 on failure.\n+ */\n+typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr);\n+\n+/**\n+ * Calls `fn` for each file in `paths` to convert the foreign patch to the\n+ * RFC2822 mail format suitable for parsing with git-mailinfo.\n+ *\n+ * Returns 0 on success, -1 on failure.\n+ */\n+static int split_mail_conv(mail_conv_fn fn, struct am_state *state,\n+\t\t\tconst char **paths, int keep_cr)\n+{\n+\tstatic const char *stdin_only[] = {\"-\", NULL};\n+\tint i;\n+\n+\tif (!*paths)\n+\t\tpaths = stdin_only;\n+\n+\tfor (i = 0; *paths; paths++, i++) {\n+\t\tFILE *in, *out;\n+\t\tconst char *mail;\n+\t\tint ret;\n+\n+\t\tif (!strcmp(*paths, \"-\"))\n+\t\t\tin = stdin;\n+\t\telse\n+\t\t\tin = fopen(*paths, \"r\");\n+\n+\t\tif (!in)\n+\t\t\treturn error(_(\"could not open '%s' for reading: %s\"),\n+\t\t\t\t\t*paths, strerror(errno));\n+\n+\t\tmail = mkpath(\"%s/%0*d\", state->dir, state->prec, i + 1);\n+\n+\t\tout = fopen(mail, \"w\");\n+\t\tif (!out)\n+\t\t\treturn error(_(\"could not open '%s' for writing: %s\"),\n+\t\t\t\t\tmail, strerror(errno));\n+\n+\t\tret = fn(out, in, keep_cr);\n+\n+\t\tfclose(out);\n+\t\tfclose(in);\n+\n+\t\tif (ret)\n+\t\t\treturn error(_(\"could not parse patch '%s'\"), *paths);\n+\t}\n+\n+\tstate->cur = 1;\n+\tstate->last = i;\n+\treturn 0;\n+}\n+\n+/**\n+ * A split_mail_conv() callback that converts an StGit patch to an RFC2822\n+ * message suitable for parsing with git-mailinfo.\n+ */\n+static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint subject_printed = 0;\n+\n+\twhile (!strbuf_getline(&sb, in, '\\n')) {\n+\t\tconst char *str;\n+\n+\t\tif (str_isspace(sb.buf))\n+\t\t\tcontinue;\n+\t\telse if (skip_prefix(sb.buf, \"Author:\", &str))\n+\t\t\tfprintf(out, \"From:%s\\n\", str);\n+\t\telse if (starts_with(sb.buf, \"From\") || starts_with(sb.buf, \"Date\"))\n+\t\t\tfprintf(out, \"%s\\n\", sb.buf);\n+\t\telse if (!subject_printed) {\n+\t\t\tfprintf(out, \"Subject: %s\\n\", sb.buf);\n+\t\t\tsubject_printed = 1;\n+\t\t} else {\n+\t\t\tfprintf(out, \"\\n%s\\n\", sb.buf);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tstrbuf_reset(&sb);\n+\twhile (strbuf_fread(&sb, 8192, in) > 0) {\n+\t\tfwrite(sb.buf, 1, sb.len, out);\n+\t\tstrbuf_reset(&sb);\n+\t}\n+\n+\tstrbuf_release(&sb);\n+\treturn 0;\n+}\n+\n+/**\n  * Splits a list of files/directories into individual email patches. Each path\n  * in `paths` must be a file/directory that is formatted according to\n  * `patch_format`.\n@@ -702,6 +828,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,\n \tswitch (patch_format) {\n \tcase PATCH_FORMAT_MBOX:\n \t\treturn split_mail_mbox(state, paths, keep_cr);\n+\tcase PATCH_FORMAT_STGIT:\n+\t\treturn split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);\n \tdefault:\n \t\tdie(\"BUG: invalid patch_format\");\n \t}\n@@ -1750,6 +1878,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int\n \n \tif (!strcmp(arg, \"mbox\"))\n \t\t*opt_value = PATCH_FORMAT_MBOX;\n+\telse if (!strcmp(arg, \"stgit\"))\n+\t\t*opt_value = PATCH_FORMAT_STGIT;\n \telse\n \t\treturn error(_(\"Invalid value for --patch-format: %s\"), arg);\n \treturn 0;\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267448","messageId":"1438696326-19590-41-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 40/45] builtin-am: support and auto-detect StGit series files","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:52:01Z","receivedAt":"2015-08-04T13:52:01Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27),\ngit-am.sh is able to read a single StGit series file and, for each StGit\npatch listed in the file, convert the StGit patch into a RFC2822 mail\npatch suitable for parsing with git-mailinfo, and queue them in the\nstate directory for applying.\n\nSince 15ced75 (git-am foreign patch support: autodetect some patch\nformats, 2009-05-27), git-am.sh is able to auto-detect StGit series\nfiles by checking to see if the file starts with the string:\n\n\t# This series applies on GIT commit\n\nRe-implement the above in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 58 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex d82d07e..3c2ec15 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -80,7 +80,8 @@ static int str_isspace(const char *str)\n enum patch_format {\n \tPATCH_FORMAT_UNKNOWN = 0,\n \tPATCH_FORMAT_MBOX,\n-\tPATCH_FORMAT_STGIT\n+\tPATCH_FORMAT_STGIT,\n+\tPATCH_FORMAT_STGIT_SERIES\n };\n \n enum keep_type {\n@@ -650,6 +651,11 @@ static int detect_patch_format(const char **paths)\n \t\tgoto done;\n \t}\n \n+\tif (starts_with(l1.buf, \"# This series applies on GIT commit\")) {\n+\t\tret = PATCH_FORMAT_STGIT_SERIES;\n+\t\tgoto done;\n+\t}\n+\n \tstrbuf_reset(&l2);\n \tstrbuf_getline_crlf(&l2, fp);\n \tstrbuf_reset(&l3);\n@@ -801,6 +807,53 @@ static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)\n }\n \n /**\n+ * This function only supports a single StGit series file in `paths`.\n+ *\n+ * Given an StGit series file, converts the StGit patches in the series into\n+ * RFC2822 messages suitable for parsing with git-mailinfo, and queues them in\n+ * the state directory.\n+ *\n+ * Returns 0 on success, -1 on failure.\n+ */\n+static int split_mail_stgit_series(struct am_state *state, const char **paths,\n+\t\t\t\t\tint keep_cr)\n+{\n+\tconst char *series_dir;\n+\tchar *series_dir_buf;\n+\tFILE *fp;\n+\tstruct argv_array patches = ARGV_ARRAY_INIT;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint ret;\n+\n+\tif (!paths[0] || paths[1])\n+\t\treturn error(_(\"Only one StGIT patch series can be applied at once\"));\n+\n+\tseries_dir_buf = xstrdup(*paths);\n+\tseries_dir = dirname(series_dir_buf);\n+\n+\tfp = fopen(*paths, \"r\");\n+\tif (!fp)\n+\t\treturn error(_(\"could not open '%s' for reading: %s\"), *paths,\n+\t\t\t\tstrerror(errno));\n+\n+\twhile (!strbuf_getline(&sb, fp, '\\n')) {\n+\t\tif (*sb.buf == '#')\n+\t\t\tcontinue; /* skip comment lines */\n+\n+\t\targv_array_push(&patches, mkpath(\"%s/%s\", series_dir, sb.buf));\n+\t}\n+\n+\tfclose(fp);\n+\tstrbuf_release(&sb);\n+\tfree(series_dir_buf);\n+\n+\tret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr);\n+\n+\targv_array_clear(&patches);\n+\treturn ret;\n+}\n+\n+/**\n  * Splits a list of files/directories into individual email patches. Each path\n  * in `paths` must be a file/directory that is formatted according to\n  * `patch_format`.\n@@ -830,6 +883,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,\n \t\treturn split_mail_mbox(state, paths, keep_cr);\n \tcase PATCH_FORMAT_STGIT:\n \t\treturn split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);\n+\tcase PATCH_FORMAT_STGIT_SERIES:\n+\t\treturn split_mail_stgit_series(state, paths, keep_cr);\n \tdefault:\n \t\tdie(\"BUG: invalid patch_format\");\n \t}\n@@ -1880,6 +1935,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int\n \t\t*opt_value = PATCH_FORMAT_MBOX;\n \telse if (!strcmp(arg, \"stgit\"))\n \t\t*opt_value = PATCH_FORMAT_STGIT;\n+\telse if (!strcmp(arg, \"stgit-series\"))\n+\t\t*opt_value = PATCH_FORMAT_STGIT_SERIES;\n \telse\n \t\treturn error(_(\"Invalid value for --patch-format: %s\"), arg);\n \treturn 0;\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267456","messageId":"1438696326-19590-42-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 41/45] builtin-am: support and auto-detect mercurial patches","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:52:02Z","receivedAt":"2015-08-04T13:52:02Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"Since 0cfd112 (am: preliminary support for hg patches, 2011-08-29),\ngit-am.sh could convert mercurial patches to an RFC2822 mail patch\nsuitable for parsing with git-mailinfo, and queue them in the state\ndirectory for application.\n\nSince 15ced75 (git-am foreign patch support: autodetect some patch\nformats, 2009-05-27), git-am.sh was able to auto-detect mercurial\npatches by checking if the file begins with the line:\n\n\t# HG changeset patch\n\nRe-implement the above in builtin/am.c.\n\nHelped-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n\nNotes:\n    v7\n    \n    * Since a5481a6 (convert \"enum date_mode\" into a struct, 2015-06-25),\n      show_date() now takes a date_mode struct. Use the DATE_MODE() macro to\n      pass the equivalent date_mode struct to show_date().\n\n builtin/am.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 73 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 3c2ec15..98c10a0 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -81,7 +81,8 @@ enum patch_format {\n \tPATCH_FORMAT_UNKNOWN = 0,\n \tPATCH_FORMAT_MBOX,\n \tPATCH_FORMAT_STGIT,\n-\tPATCH_FORMAT_STGIT_SERIES\n+\tPATCH_FORMAT_STGIT_SERIES,\n+\tPATCH_FORMAT_HG\n };\n \n enum keep_type {\n@@ -656,6 +657,11 @@ static int detect_patch_format(const char **paths)\n \t\tgoto done;\n \t}\n \n+\tif (!strcmp(l1.buf, \"# HG changeset patch\")) {\n+\t\tret = PATCH_FORMAT_HG;\n+\t\tgoto done;\n+\t}\n+\n \tstrbuf_reset(&l2);\n \tstrbuf_getline_crlf(&l2, fp);\n \tstrbuf_reset(&l3);\n@@ -854,6 +860,68 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths,\n }\n \n /**\n+ * A split_patches_conv() callback that converts a mercurial patch to a RFC2822\n+ * message suitable for parsing with git-mailinfo.\n+ */\n+static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\twhile (!strbuf_getline(&sb, in, '\\n')) {\n+\t\tconst char *str;\n+\n+\t\tif (skip_prefix(sb.buf, \"# User \", &str))\n+\t\t\tfprintf(out, \"From: %s\\n\", str);\n+\t\telse if (skip_prefix(sb.buf, \"# Date \", &str)) {\n+\t\t\tunsigned long timestamp;\n+\t\t\tlong tz, tz2;\n+\t\t\tchar *end;\n+\n+\t\t\terrno = 0;\n+\t\t\ttimestamp = strtoul(str, &end, 10);\n+\t\t\tif (errno)\n+\t\t\t\treturn error(_(\"invalid timestamp\"));\n+\n+\t\t\tif (!skip_prefix(end, \" \", &str))\n+\t\t\t\treturn error(_(\"invalid Date line\"));\n+\n+\t\t\terrno = 0;\n+\t\t\ttz = strtol(str, &end, 10);\n+\t\t\tif (errno)\n+\t\t\t\treturn error(_(\"invalid timezone offset\"));\n+\n+\t\t\tif (*end)\n+\t\t\t\treturn error(_(\"invalid Date line\"));\n+\n+\t\t\t/*\n+\t\t\t * mercurial's timezone is in seconds west of UTC,\n+\t\t\t * however git's timezone is in hours + minutes east of\n+\t\t\t * UTC. Convert it.\n+\t\t\t */\n+\t\t\ttz2 = labs(tz) / 3600 * 100 + labs(tz) % 3600 / 60;\n+\t\t\tif (tz > 0)\n+\t\t\t\ttz2 = -tz2;\n+\n+\t\t\tfprintf(out, \"Date: %s\\n\", show_date(timestamp, tz2, DATE_MODE(RFC2822)));\n+\t\t} else if (starts_with(sb.buf, \"# \")) {\n+\t\t\tcontinue;\n+\t\t} else {\n+\t\t\tfprintf(out, \"\\n%s\\n\", sb.buf);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tstrbuf_reset(&sb);\n+\twhile (strbuf_fread(&sb, 8192, in) > 0) {\n+\t\tfwrite(sb.buf, 1, sb.len, out);\n+\t\tstrbuf_reset(&sb);\n+\t}\n+\n+\tstrbuf_release(&sb);\n+\treturn 0;\n+}\n+\n+/**\n  * Splits a list of files/directories into individual email patches. Each path\n  * in `paths` must be a file/directory that is formatted according to\n  * `patch_format`.\n@@ -885,6 +953,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,\n \t\treturn split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);\n \tcase PATCH_FORMAT_STGIT_SERIES:\n \t\treturn split_mail_stgit_series(state, paths, keep_cr);\n+\tcase PATCH_FORMAT_HG:\n+\t\treturn split_mail_conv(hg_patch_to_mail, state, paths, keep_cr);\n \tdefault:\n \t\tdie(\"BUG: invalid patch_format\");\n \t}\n@@ -1937,6 +2007,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int\n \t\t*opt_value = PATCH_FORMAT_STGIT;\n \telse if (!strcmp(arg, \"stgit-series\"))\n \t\t*opt_value = PATCH_FORMAT_STGIT_SERIES;\n+\telse if (!strcmp(arg, \"hg\"))\n+\t\t*opt_value = PATCH_FORMAT_HG;\n \telse\n \t\treturn error(_(\"Invalid value for --patch-format: %s\"), arg);\n \treturn 0;\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267457","messageId":"1438696326-19590-43-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 42/45] builtin-am: implement -i/--interactive","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:52:03Z","receivedAt":"2015-08-04T13:52: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),\ngit-am.sh supported the --interactive mode. After parsing the patch mail\nand extracting the patch, commit message and authorship info, an\ninteractive session will begin that allows the user to choose between:\n\n* applying the patch\n\n* applying the patch and all subsequent patches (by disabling\n  interactive mode in subsequent patches)\n\n* skipping the patch\n\n* editing the commit message\n\nSince f89ad67 (Add [v]iew patch in git-am interactive., 2005-10-25),\ngit-am.sh --interactive also supported viewing the patch to be applied.\n\nWhen --resolved-ing in --interactive mode, we need to take care to\nupdate the patch with the contents of the index, such that the correct\npatch will be displayed when the patch is viewed in interactive mode.\n\nRe-implement the above in builtin/am.c\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 104 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 98c10a0..589199f 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -25,6 +25,7 @@\n #include \"log-tree.h\"\n #include \"notes-utils.h\"\n #include \"rerere.h\"\n+#include \"prompt.h\"\n \n /**\n  * Returns 1 if the file is empty or does not exist, 0 otherwise.\n@@ -119,6 +120,7 @@ struct am_state {\n \tint prec;\n \n \t/* various operating modes and command line options */\n+\tint interactive;\n \tint threeway;\n \tint quiet;\n \tint signoff;\n@@ -1171,7 +1173,7 @@ static void NORETURN die_user_resolve(const struct am_state *state)\n \tif (state->resolvemsg) {\n \t\tprintf_ln(\"%s\", state->resolvemsg);\n \t} else {\n-\t\tconst char *cmdline = \"git am\";\n+\t\tconst char *cmdline = state->interactive ? \"git am -i\" : \"git am\";\n \n \t\tprintf_ln(_(\"When you have resolved this problem, run \\\"%s --continue\\\".\"), cmdline);\n \t\tprintf_ln(_(\"If you prefer to skip this patch, run \\\"%s --skip\\\" instead.\"), cmdline);\n@@ -1404,6 +1406,36 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm\n }\n \n /**\n+ * Writes the diff of the index against HEAD as a patch to the state\n+ * directory's \"patch\" file.\n+ */\n+static void write_index_patch(const struct am_state *state)\n+{\n+\tstruct tree *tree;\n+\tunsigned char head[GIT_SHA1_RAWSZ];\n+\tstruct rev_info rev_info;\n+\tFILE *fp;\n+\n+\tif (!get_sha1_tree(\"HEAD\", head))\n+\t\ttree = lookup_tree(head);\n+\telse\n+\t\ttree = lookup_tree(EMPTY_TREE_SHA1_BIN);\n+\n+\tfp = xfopen(am_path(state, \"patch\"), \"w\");\n+\tinit_revisions(&rev_info, NULL);\n+\trev_info.diff = 1;\n+\trev_info.disable_stdin = 1;\n+\trev_info.no_commit_id = 1;\n+\trev_info.diffopt.output_format = DIFF_FORMAT_PATCH;\n+\trev_info.diffopt.use_color = 0;\n+\trev_info.diffopt.file = fp;\n+\trev_info.diffopt.close_file = 1;\n+\tadd_pending_object(&rev_info, &tree->object, \"\");\n+\tdiff_setup_done(&rev_info.diffopt);\n+\trun_diff_index(&rev_info, 1);\n+}\n+\n+/**\n  * Like parse_mail(), but parses the mail by looking up its commit ID\n  * directly. This is used in --rebasing mode to bypass git-mailinfo's munging\n  * of patches.\n@@ -1655,6 +1687,65 @@ static void validate_resume_state(const struct am_state *state)\n }\n \n /**\n+ * Interactively prompt the user on whether the current patch should be\n+ * applied.\n+ *\n+ * Returns 0 if the user chooses to apply the patch, 1 if the user chooses to\n+ * skip it.\n+ */\n+static int do_interactive(struct am_state *state)\n+{\n+\tassert(state->msg);\n+\n+\tif (!isatty(0))\n+\t\tdie(_(\"cannot be interactive without stdin connected to a terminal.\"));\n+\n+\tfor (;;) {\n+\t\tconst char *reply;\n+\n+\t\tputs(_(\"Commit Body is:\"));\n+\t\tputs(\"--------------------------\");\n+\t\tprintf(\"%s\", state->msg);\n+\t\tputs(\"--------------------------\");\n+\n+\t\t/*\n+\t\t * TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]\n+\t\t * in your translation. The program will only accept English\n+\t\t * input at this point.\n+\t\t */\n+\t\treply = git_prompt(_(\"Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: \"), PROMPT_ECHO);\n+\n+\t\tif (!reply) {\n+\t\t\tcontinue;\n+\t\t} else if (*reply == 'y' || *reply == 'Y') {\n+\t\t\treturn 0;\n+\t\t} else if (*reply == 'a' || *reply == 'A') {\n+\t\t\tstate->interactive = 0;\n+\t\t\treturn 0;\n+\t\t} else if (*reply == 'n' || *reply == 'N') {\n+\t\t\treturn 1;\n+\t\t} else if (*reply == 'e' || *reply == 'E') {\n+\t\t\tstruct strbuf msg = STRBUF_INIT;\n+\n+\t\t\tif (!launch_editor(am_path(state, \"final-commit\"), &msg, NULL)) {\n+\t\t\t\tfree(state->msg);\n+\t\t\t\tstate->msg = strbuf_detach(&msg, &state->msg_len);\n+\t\t\t}\n+\t\t\tstrbuf_release(&msg);\n+\t\t} else if (*reply == 'v' || *reply == 'V') {\n+\t\t\tconst char *pager = git_pager(1);\n+\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\t\t\tif (!pager)\n+\t\t\t\tpager = \"cat\";\n+\t\t\targv_array_push(&cp.args, pager);\n+\t\t\targv_array_push(&cp.args, am_path(state, \"patch\"));\n+\t\t\trun_command(&cp);\n+\t\t}\n+\t}\n+}\n+\n+/**\n  * Applies all queued mail.\n  *\n  * If `resume` is true, we are \"resuming\". The \"msg\" and authorship fields, as\n@@ -1702,6 +1793,9 @@ static void am_run(struct am_state *state, int resume)\n \t\t\twrite_commit_msg(state);\n \t\t}\n \n+\t\tif (state->interactive && do_interactive(state))\n+\t\t\tgoto next;\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n \n@@ -1787,10 +1881,17 @@ static void am_resolve(struct am_state *state)\n \t\tdie_user_resolve(state);\n \t}\n \n+\tif (state->interactive) {\n+\t\twrite_index_patch(state);\n+\t\tif (do_interactive(state))\n+\t\t\tgoto next;\n+\t}\n+\n \trerere(0);\n \n \tdo_commit(state);\n \n+next:\n \tam_next(state);\n \tam_run(state, 0);\n }\n@@ -2036,6 +2137,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t};\n \n \tstruct option options[] = {\n+\t\tOPT_BOOL('i', \"interactive\", &state.interactive,\n+\t\t\tN_(\"run interactively\")),\n \t\tOPT_BOOL('3', \"3way\", &state.threeway,\n \t\t\tN_(\"allow fall back on 3way merging if needed\")),\n \t\tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267455","messageId":"1438696326-19590-44-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 43/45] builtin-am: implement legacy -b/--binary option","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:52:04Z","receivedAt":"2015-08-04T13:52:04Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"The -b/--binary option was initially implemented in 087b674 (git-am:\n--binary; document --resume and --binary., 2005-11-16). The option will\npass the --binary flag to git-apply to allow it to apply binary patches.\n\nHowever, in 2b6eef9 (Make apply --binary a no-op., 2006-09-06), --binary\nwas been made a no-op in git-apply. Following that, since cb3a160\n(git-am: ignore --binary option, 2008-08-09), the --binary option in\ngit-am is ignored as well.\n\nIn 6c15a1c (am: officially deprecate -b/--binary option, 2012-03-13),\nthe --binary option was tweaked to its present behavior: when set, the\nmessage:\n\n\tThe -b/--binary option has been a no-op for long time, and it\n\twill be removed. Please do not use it anymore.\n\nwill be printed.\n\nRe-implement this in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 589199f..3c50392 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -2126,6 +2126,7 @@ enum resume_mode {\n int cmd_am(int argc, const char **argv, const char *prefix)\n {\n \tstruct am_state state;\n+\tint binary = -1;\n \tint keep_cr = -1;\n \tint patch_format = PATCH_FORMAT_UNKNOWN;\n \tenum resume_mode resume = RESUME_FALSE;\n@@ -2139,6 +2140,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL('i', \"interactive\", &state.interactive,\n \t\t\tN_(\"run interactively\")),\n+\t\tOPT_HIDDEN_BOOL('b', \"binary\", &binary,\n+\t\t\tN_(\"(historical option -- no-op\")),\n \t\tOPT_BOOL('3', \"3way\", &state.threeway,\n \t\t\tN_(\"allow fall back on 3way merging if needed\")),\n \t\tOPT__QUIET(&state.quiet, N_(\"be quiet\")),\n@@ -2239,6 +2242,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, usage, 0);\n \n+\tif (binary >= 0)\n+\t\tfprintf_ln(stderr, _(\"The -b/--binary option has been a no-op for long time, and\\n\"\n+\t\t\t\t\"it will be removed. Please do not use it anymore.\"));\n+\n \tif (read_index_preload(&the_index, NULL) < 0)\n \t\tdie(_(\"failed to read the index\"));\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267454","messageId":"1438696326-19590-45-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 44/45] builtin-am: check for valid committer ident","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:52:05Z","receivedAt":"2015-08-04T13:52:05Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"When commit_tree() is called, if the user does not have an explicit\ncommitter ident configured, it will attempt to construct a default\ncommitter ident based on the user's and system's info (e.g. gecos field,\nhostname etc.) However, if a default committer ident is unable to be\nconstructed, commit_tree() will die(), but at this point of git-am's\nexecution, there will already be changes made to the index and work\ntree.\n\nThis can be confusing to new users, and as such since d64e6b0 (Keep\nPorcelainish from failing by broken ident after making changes.,\n2006-02-18) git-am.sh will check to see if the committer ident has been\nconfigured, or a default one can be constructed, before even starting to\napply patches.\n\nRe-implement this in builtin/am.c.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n builtin/am.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 3c50392..1ff74ac 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -2246,6 +2246,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tfprintf_ln(stderr, _(\"The -b/--binary option has been a no-op for long time, and\\n\"\n \t\t\t\t\"it will be removed. Please do not use it anymore.\"));\n \n+\t/* Ensure a valid committer ident can be constructed */\n+\tgit_committer_info(IDENT_STRICT);\n+\n \tif (read_index_preload(&the_index, NULL) < 0)\n \t\tdie(_(\"failed to read the index\"));\n \n-- \n2.5.0.280.gd88bd6e\n"},{"id":"267453","messageId":"1438696326-19590-46-git-send-email-pyokagan@gmail.com","threadId":"40011","inReplyTo":"1438696326-19590-1-git-send-email-pyokagan@gmail.com","subject":"[PATCH v7 45/45] builtin-am: remove redirection to git-am.sh","fromName":"Paul Tan","fromEmail":"pyokagan@gmail.com","sentAt":"2015-08-04T13:52:06Z","receivedAt":"2015-08-04T13:52:06Z","isPatch":true,"sender":{"key":"pyokagan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/109479?v=4"},"body":"At the beginning of the rewrite of git-am.sh to C, in order to not break\nexisting test scripts that depended on a functional git-am, a\nredirection to git-am.sh was introduced that would activate if the\nenvironment variable _GIT_USE_BUILTIN_AM was not defined.\n\nNow that all of git-am.sh's functionality has been re-implemented in\nbuiltin/am.c, remove this redirection, and retire git-am.sh into\ncontrib/examples/.\n\nSigned-off-by: Paul Tan <pyokagan@gmail.com>\n---\n Makefile                                |  1 -\n builtin/am.c                            | 15 ---------------\n git-am.sh => contrib/examples/git-am.sh |  0\n git.c                                   |  7 +------\n 4 files changed, 1 insertion(+), 22 deletions(-)\n rename git-am.sh => contrib/examples/git-am.sh (100%)\n\ndiff --git a/Makefile b/Makefile\nindex da451f8..e39ca6c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -467,7 +467,6 @@ TEST_PROGRAMS_NEED_X =\n # interactive shell sessions without exporting it.\n unexport CDPATH\n \n-SCRIPT_SH += git-am.sh\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1ff74ac..84d57d4 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -2221,21 +2221,6 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n-\t/*\n-\t * NEEDSWORK: Once all the features of git-am.sh have been\n-\t * re-implemented in builtin/am.c, this preamble can be removed.\n-\t */\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} else {\n-\t\tprefix = setup_git_directory();\n-\t\ttrace_repo_setup(prefix);\n-\t\tsetup_work_tree();\n-\t}\n-\n \tgit_config(git_default_config, NULL);\n \n \tam_state_init(&state, git_path(\"rebase-apply\"));\ndiff --git a/git-am.sh b/contrib/examples/git-am.sh\nsimilarity index 100%\nrename from git-am.sh\nrename to contrib/examples/git-am.sh\ndiff --git a/git.c b/git.c\nindex 38d9ad5..5feba41 100644\n--- a/git.c\n+++ b/git.c\n@@ -370,12 +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/*\n-\t * NEEDSWORK: Once the redirection to git-am.sh in builtin/am.c has\n-\t * been removed, this entry should be changed to\n-\t * RUN_SETUP | NEED_WORK_TREE\n-\t */\n-\t{ \"am\", cmd_am },\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.5.0.280.gd88bd6e\n"}]}