git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / mirror / code / Atom feed
* [PATCH v4 00/44] Make git-am a builtin
@ 2015-06-28 14:05 Paul Tan
  2015-06-28 14:05 ` [PATCH v4 01/44] wrapper: implement xopen() Paul Tan
                   ` (44 more replies)
  0 siblings, 45 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

This patch series depends on pt/pull-builtin.

This is a re-roll of [WIP v3]. This patch series is now out of WIP, as the
git-am feature set is complete. I've marked out some features which lack tests
though, which I'm working on fixing.

Thanks Junio, Stefan, Johannes for the reviews last round.

Also, v4, with 44 patches ;-)

Previous versions:

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

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

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

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


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

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

-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 01/44] wrapper: implement xopen()
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-29  4:48   ` Torsten Bögershausen
  2015-06-28 14:05 ` [PATCH v4 02/44] wrapper: implement xfopen() Paul Tan
                   ` (43 subsequent siblings)
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

A common usage pattern of open() is to check if it was successful, and
die() if it was not:

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

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

Helped-by: Torsten Bögershausen <tboegi@web.de>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 git-compat-util.h |  1 +
 wrapper.c         | 25 +++++++++++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/git-compat-util.h b/git-compat-util.h
index c6d391f..e168dfd 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -717,6 +717,7 @@ extern void *xrealloc(void *ptr, size_t size);
 extern void *xcalloc(size_t nmemb, size_t size);
 extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
 extern void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+extern int xopen(const char *path, int flags, ...);
 extern ssize_t xread(int fd, void *buf, size_t len);
 extern ssize_t xwrite(int fd, const void *buf, size_t len);
 extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
diff --git a/wrapper.c b/wrapper.c
index ff49807..7e13ae0 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -189,6 +189,31 @@ void *xcalloc(size_t nmemb, size_t size)
 # endif
 #endif
 
+/**
+ * xopen() is the same as open(), but it die()s if the open() fails.
+ */
+int xopen(const char *path, int oflag, ...)
+{
+	mode_t mode = 0;
+	va_list ap;
+
+	va_start(ap, oflag);
+	if (oflag & O_CREAT)
+		mode = va_arg(ap, mode_t);
+	va_end(ap);
+
+	assert(path);
+
+	for (;;) {
+		int fd = open(path, oflag, mode);
+		if (fd >= 0)
+			return fd;
+		if (errno == EINTR)
+			continue;
+		die_errno(_("could not open '%s'"), path);
+	}
+}
+
 /*
  * xread() is the same a read(), but it automatically restarts read()
  * operations with a recoverable error (EAGAIN and EINTR). xread()
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 02/44] wrapper: implement xfopen()
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
  2015-06-28 14:05 ` [PATCH v4 01/44] wrapper: implement xopen() Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 03/44] builtin-am: implement skeletal builtin am Paul Tan
                   ` (42 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

A common usage pattern of fopen() is to check if it succeeded, and die()
if it failed:

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 git-compat-util.h |  1 +
 wrapper.c         | 18 ++++++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/git-compat-util.h b/git-compat-util.h
index e168dfd..392da79 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -722,6 +722,7 @@ extern ssize_t xread(int fd, void *buf, size_t len);
 extern ssize_t xwrite(int fd, const void *buf, size_t len);
 extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
 extern int xdup(int fd);
+extern FILE *xfopen(const char *path, const char *mode);
 extern FILE *xfdopen(int fd, const char *mode);
 extern int xmkstemp(char *template);
 extern int xmkstemp_mode(char *template, int mode);
diff --git a/wrapper.c b/wrapper.c
index 7e13ae0..f127eb3 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -336,6 +336,24 @@ int xdup(int fd)
 	return ret;
 }
 
+/**
+ * xfopen() is the same as fopen(), but it die()s if the fopen() fails.
+ */
+FILE *xfopen(const char *path, const char *mode)
+{
+	assert(path);
+	assert(mode);
+
+	for (;;) {
+		FILE *fp = fopen(path, mode);
+		if (fp)
+			return fp;
+		if (errno == EINTR)
+			continue;
+		die_errno(_("could not open '%s'"), path);
+	}
+}
+
 FILE *xfdopen(int fd, const char *mode)
 {
 	FILE *stream = fdopen(fd, mode);
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 03/44] builtin-am: implement skeletal builtin am
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
  2015-06-28 14:05 ` [PATCH v4 01/44] wrapper: implement xopen() Paul Tan
  2015-06-28 14:05 ` [PATCH v4 02/44] wrapper: implement xfopen() Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 04/44] builtin-am: implement patch queue mechanism Paul Tan
                   ` (41 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

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

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    v4
    
    * Don't use NO_SETUP
    
    * Comment wording changes

 Makefile     |  1 +
 builtin.h    |  1 +
 builtin/am.c | 29 +++++++++++++++++++++++++++++
 git.c        |  6 ++++++
 4 files changed, 37 insertions(+)
 create mode 100644 builtin/am.c

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

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 04/44] builtin-am: implement patch queue mechanism
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (2 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 03/44] builtin-am: implement skeletal builtin am Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-29  5:08   ` Stefan Beller
  2015-06-28 14:05 ` [PATCH v4 05/44] builtin-am: split out mbox/maildir patches with git-mailsplit Paul Tan
                   ` (40 subsequent siblings)
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

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

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

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

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Stefan Beller <sbeller@google.com>
Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    v4
    
    * Corrected docstring of read_state_file()
    
    * Corrected docstring of am_state_release()
    
    * am_state's "dir" field is now a char*. To help API users,
      am_state_init() takes an additional const char *dir argument.
    
    * The opt_* option variables, am_options[] and am_usage[] have been
      moved into cmd_am()'s scope.
    
    * signature of read_state_file() has been changed to
      read_state_file(strbuf, state, file, trim)

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

diff --git a/builtin/am.c b/builtin/am.c
index fd32caf..5b4e9af 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -6,9 +6,174 @@
 #include "cache.h"
 #include "builtin.h"
 #include "exec_cmd.h"
+#include "parse-options.h"
+#include "dir.h"
+
+struct am_state {
+	/* state directory path */
+	char *dir;
+
+	/* current and last patch numbers, 1-indexed */
+	int cur;
+	int last;
+};
+
+/**
+ * Initializes am_state with the default values. The state directory is set to
+ * dir.
+ */
+static void am_state_init(struct am_state *state, const char *dir)
+{
+	memset(state, 0, sizeof(*state));
+
+	assert(dir);
+	state->dir = xstrdup(dir);
+}
+
+/**
+ * Releases memory allocated by an am_state.
+ */
+static void am_state_release(struct am_state *state)
+{
+	if (state->dir)
+		free(state->dir);
+}
+
+/**
+ * Returns path relative to the am_state directory.
+ */
+static inline const char *am_path(const struct am_state *state, const char *path)
+{
+	assert(state->dir);
+	assert(path);
+	return mkpath("%s/%s", state->dir, path);
+}
+
+/**
+ * Returns 1 if there is an am session in progress, 0 otherwise.
+ */
+static int am_in_progress(const struct am_state *state)
+{
+	struct stat st;
+
+	if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode))
+		return 0;
+	if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode))
+		return 0;
+	if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode))
+		return 0;
+	return 1;
+}
+
+/**
+ * Reads the contents of `file` in the `state` directory into `sb`. Returns the
+ * number of bytes read on success, -1 if the file does not exist. If `trim` is
+ * set, trailing whitespace will be removed.
+ */
+static int read_state_file(struct strbuf *sb, const struct am_state *state,
+			const char *file, int trim)
+{
+	strbuf_reset(sb);
+
+	if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {
+		if (trim)
+			strbuf_trim(sb);
+
+		return sb->len;
+	}
+
+	if (errno == ENOENT)
+		return -1;
+
+	die_errno(_("could not read '%s'"), am_path(state, file));
+}
+
+/**
+ * Loads state from disk.
+ */
+static void am_load(struct am_state *state)
+{
+	struct strbuf sb = STRBUF_INIT;
+
+	if (read_state_file(&sb, state, "next", 1) < 0)
+		die("BUG: state file 'next' does not exist");
+	state->cur = strtol(sb.buf, NULL, 10);
+
+	if (read_state_file(&sb, state, "last", 1) < 0)
+		die("BUG: state file 'last' does not exist");
+	state->last = strtol(sb.buf, NULL, 10);
+
+	strbuf_release(&sb);
+}
+
+/**
+ * Removes the am_state directory, forcefully terminating the current am
+ * session.
+ */
+static void am_destroy(const struct am_state *state)
+{
+	struct strbuf sb = STRBUF_INIT;
+
+	strbuf_addstr(&sb, state->dir);
+	remove_dir_recursively(&sb, 0);
+	strbuf_release(&sb);
+}
+
+/**
+ * Setup a new am session for applying patches
+ */
+static void am_setup(struct am_state *state)
+{
+	if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
+		die_errno(_("failed to create directory '%s'"), state->dir);
+
+	/*
+	 * NOTE: Since the "next" and "last" files determine if an am_state
+	 * session is in progress, they should be written last.
+	 */
+
+	write_file(am_path(state, "next"), 1, "%d", state->cur);
+
+	write_file(am_path(state, "last"), 1, "%d", state->last);
+}
+
+/**
+ * Increments the patch pointer, and cleans am_state for the application of the
+ * next patch.
+ */
+static void am_next(struct am_state *state)
+{
+	state->cur++;
+	write_file(am_path(state, "next"), 1, "%d", state->cur);
+}
+
+/**
+ * Applies all queued mail.
+ */
+static void am_run(struct am_state *state)
+{
+	while (state->cur <= state->last) {
+
+		/* TODO: Patch application not implemented yet */
+
+		am_next(state);
+	}
+
+	am_destroy(state);
+}
 
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
+	struct am_state state;
+
+	const char * const usage[] = {
+		N_("git am [options] [(<mbox>|<Maildir>)...]"),
+		NULL
+	};
+
+	struct option options[] = {
+		OPT_END()
+	};
 
 	/*
 	 * NEEDSWORK: Once all the features of git-am.sh have been
@@ -25,5 +190,20 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		setup_work_tree();
 	}
 
+	git_config(git_default_config, NULL);
+
+	am_state_init(&state, git_path("rebase-apply"));
+
+	argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+	if (am_in_progress(&state))
+		am_load(&state);
+	else
+		am_setup(&state);
+
+	am_run(&state);
+
+	am_state_release(&state);
+
 	return 0;
 }
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 05/44] builtin-am: split out mbox/maildir patches with git-mailsplit
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (3 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 04/44] builtin-am: implement patch queue mechanism Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 06/44] builtin-am: auto-detect mbox patches Paul Tan
                   ` (39 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    v4
    
    * The term "patch" was overloaded to mean "the RFC2822 mail" and the
      "patch diff". To avoid confusion, use "mail" to refer to the RFC2822
      mail, and thus split_patches() has been renamed to split_mail().
    
    * An argv_array is used for `paths` instead of a string_list to avoid
      clunkiness with string_list_item in later patches.

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

diff --git a/builtin/am.c b/builtin/am.c
index 5b4e9af..136ccc6 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -8,6 +8,12 @@
 #include "exec_cmd.h"
 #include "parse-options.h"
 #include "dir.h"
+#include "run-command.h"
+
+enum patch_format {
+	PATCH_FORMAT_UNKNOWN = 0,
+	PATCH_FORMAT_MBOX
+};
 
 struct am_state {
 	/* state directory path */
@@ -16,6 +22,9 @@ struct am_state {
 	/* current and last patch numbers, 1-indexed */
 	int cur;
 	int last;
+
+	/* number of digits in patch filename */
+	int prec;
 };
 
 /**
@@ -28,6 +37,8 @@ static void am_state_init(struct am_state *state, const char *dir)
 
 	assert(dir);
 	state->dir = xstrdup(dir);
+
+	state->prec = 4;
 }
 
 /**
@@ -120,13 +131,71 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * Splits out individual email patches from `paths`, where each path is either
+ * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
+ */
+static int split_mail_mbox(struct am_state *state, const char **paths)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct strbuf last = STRBUF_INIT;
+
+	cp.git_cmd = 1;
+	argv_array_push(&cp.args, "mailsplit");
+	argv_array_pushf(&cp.args, "-d%d", state->prec);
+	argv_array_pushf(&cp.args, "-o%s", state->dir);
+	argv_array_push(&cp.args, "-b");
+	argv_array_push(&cp.args, "--");
+	argv_array_pushv(&cp.args, paths);
+
+	if (capture_command(&cp, &last, 8))
+		return -1;
+
+	state->cur = 1;
+	state->last = strtol(last.buf, NULL, 10);
+
+	return 0;
+}
+
+/**
+ * Splits a list of files/directories into individual email patches. Each path
+ * in `paths` must be a file/directory that is formatted according to
+ * `patch_format`.
+ *
+ * Once split out, the individual email patches will be stored in the state
+ * directory, with each patch's filename being its index, padded to state->prec
+ * digits.
+ *
+ * state->cur will be set to the index of the first mail, and state->last will
+ * be set to the index of the last mail.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail(struct am_state *state, enum patch_format patch_format,
+			const char **paths)
+{
+	switch (patch_format) {
+	case PATCH_FORMAT_MBOX:
+		return split_mail_mbox(state, paths);
+	default:
+		die("BUG: invalid patch_format");
+	}
+	return -1;
+}
+
+/**
  * Setup a new am session for applying patches
  */
-static void am_setup(struct am_state *state)
+static void am_setup(struct am_state *state, enum patch_format patch_format,
+			const char **paths)
 {
 	if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
 		die_errno(_("failed to create directory '%s'"), state->dir);
 
+	if (split_mail(state, patch_format, paths) < 0) {
+		am_destroy(state);
+		die(_("Failed to split patches."));
+	}
+
 	/*
 	 * NOTE: Since the "next" and "last" files determine if an am_state
 	 * session is in progress, they should be written last.
@@ -162,9 +231,25 @@ static void am_run(struct am_state *state)
 	am_destroy(state);
 }
 
+/**
+ * parse_options() callback that validates and sets opt->value to the
+ * PATCH_FORMAT_* enum value corresponding to `arg`.
+ */
+static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	if (!strcmp(arg, "mbox"))
+		*opt_value = PATCH_FORMAT_MBOX;
+	else
+		return error(_("Invalid value for --patch-format: %s"), arg);
+	return 0;
+}
+
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
 	struct am_state state;
+	int patch_format = PATCH_FORMAT_UNKNOWN;
 
 	const char * const usage[] = {
 		N_("git am [options] [(<mbox>|<Maildir>)...]"),
@@ -172,6 +257,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 	};
 
 	struct option options[] = {
+		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
+			N_("format the patch(es) are in"),
+			parse_opt_patchformat),
 		OPT_END()
 	};
 
@@ -198,8 +286,21 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
 	if (am_in_progress(&state))
 		am_load(&state);
-	else
-		am_setup(&state);
+	else {
+		struct argv_array paths = ARGV_ARRAY_INIT;
+		int i;
+
+		for (i = 0; i < argc; i++) {
+			if (is_absolute_path(argv[i]) || !prefix)
+				argv_array_push(&paths, argv[i]);
+			else
+				argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
+		}
+
+		am_setup(&state, patch_format, paths.argv);
+
+		argv_array_clear(&paths);
+	}
 
 	am_run(&state);
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 06/44] builtin-am: auto-detect mbox patches
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (4 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 05/44] builtin-am: split out mbox/maildir patches with git-mailsplit Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 07/44] builtin-am: extract patch and commit info with git-mailinfo Paul Tan
                   ` (38 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    v4
    
    * Using strbuf_trim() to remove the \r from CRLF lines is obviously
      wrong. Instead, implement strbuf_getline_crlf() to do it correctly for
      us.
    
    * Use a regex
    
    * Instead of re-opening the file again in is_email(), rewind the
      already-opened data stream.

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

diff --git a/builtin/am.c b/builtin/am.c
index 136ccc6..31d85eb 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -10,6 +10,21 @@
 #include "dir.h"
 #include "run-command.h"
 
+/**
+ * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators.
+ */
+static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
+{
+	if (strbuf_getwholeline(sb, fp, '\n'))
+		return EOF;
+	if (sb->buf[sb->len - 1] == '\n') {
+		strbuf_setlen(sb, sb->len - 1);
+		if (sb->len > 0 && sb->buf[sb->len - 1] == '\r')
+			strbuf_setlen(sb, sb->len - 1);
+	}
+	return 0;
+}
+
 enum patch_format {
 	PATCH_FORMAT_UNKNOWN = 0,
 	PATCH_FORMAT_MBOX
@@ -131,6 +146,92 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * Determines if the file looks like a piece of RFC2822 mail by grabbing all
+ * non-indented lines and checking if they look like they begin with valid
+ * header field names.
+ *
+ * Returns 1 if the file looks like a piece of mail, 0 otherwise.
+ */
+static int is_mail(FILE *fp)
+{
+	const char *header_regex = "^[!-9;-~]+:";
+	struct strbuf sb = STRBUF_INIT;
+	regex_t regex;
+	int ret = 1;
+
+	if (fseek(fp, 0L, SEEK_SET))
+		die_errno(_("fseek failed"));
+
+	if (regcomp(&regex, header_regex, REG_NOSUB | REG_EXTENDED))
+		die("invalid pattern: %s", header_regex);
+
+	while (!strbuf_getline_crlf(&sb, fp)) {
+		if (!sb.len)
+			break; /* End of header */
+
+		/* Ignore indented folded lines */
+		if (*sb.buf == '\t' || *sb.buf == ' ')
+			continue;
+
+		/* It's a header if it matches header_regex */
+		if (regexec(&regex, sb.buf, 0, NULL, 0)) {
+			ret = 0;
+			goto done;
+		}
+	}
+
+done:
+	regfree(&regex);
+	strbuf_release(&sb);
+	return ret;
+}
+
+/**
+ * Attempts to detect the patch_format of the patches contained in `paths`,
+ * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if
+ * detection fails.
+ */
+static int detect_patch_format(const char **paths)
+{
+	enum patch_format ret = PATCH_FORMAT_UNKNOWN;
+	struct strbuf l1 = STRBUF_INIT;
+	FILE *fp;
+
+	/*
+	 * We default to mbox format if input is from stdin and for directories
+	 */
+	if (!*paths || !strcmp(*paths, "-") || is_directory(*paths))
+		return PATCH_FORMAT_MBOX;
+
+	/*
+	 * Otherwise, check the first few lines of the first patch, starting
+	 * from the first non-blank line, to try to detect its format.
+	 */
+
+	fp = xfopen(*paths, "r");
+
+	while (!strbuf_getline_crlf(&l1, fp)) {
+		if (l1.len)
+			break;
+	}
+
+	if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) {
+		ret = PATCH_FORMAT_MBOX;
+		goto done;
+	}
+
+	if (l1.len && is_mail(fp)) {
+		ret = PATCH_FORMAT_MBOX;
+		goto done;
+	}
+
+done:
+	fclose(fp);
+	strbuf_release(&l1);
+	return ret;
+}
+
+/**
  * Splits out individual email patches from `paths`, where each path is either
  * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
  */
@@ -188,6 +289,14 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
 static void am_setup(struct am_state *state, enum patch_format patch_format,
 			const char **paths)
 {
+	if (!patch_format)
+		patch_format = detect_patch_format(paths);
+
+	if (!patch_format) {
+		fprintf_ln(stderr, _("Patch format detection failed."));
+		exit(128);
+	}
+
 	if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
 		die_errno(_("failed to create directory '%s'"), state->dir);
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 07/44] builtin-am: extract patch and commit info with git-mailinfo
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (5 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 06/44] builtin-am: auto-detect mbox patches Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 08/44] builtin-am: apply patch with git-apply Paul Tan
                   ` (37 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Jeff King <peff@peff.net>
Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    v4
    
    * The word "patch" was overloaded to mean "the RFC2822 mail" and the
      "patch diff". To avoid confusion, we use "mail" to mean "the RFC2822
      mail", and thus parse_patch() has been renamed to parse_mail().
    
    * To discourage people touching the code from modifying the contents of
      the author_name, author_email, author_date and msg fields, as well as
      to make any modifications to them stick out to reviewers, they have
      been changed to char*.
    
    * Besides parse_mail(), the msg field can also be modified in other ways
      such as from am_load(), and in interactive mode when implemented in a
      later patch. At these points, users may sneak in NUL bytes in the
      commit message. As such, we need to store the "msg_len" as well so as
      to properly error out should the user sneak in any NUL bytes, rather
      than ignore the rest of the buffer after the NUL byte.
    
      * To aid in this, reading and writing to the "final-commit" file has
        been factored out into read_commit_msg() and write_commit_msg()
        functions that will properly set the msg_len field as well.
    
    * Restructuring of write_author_script() so only one strbuf needs to be
      used.

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

diff --git a/builtin/am.c b/builtin/am.c
index 31d85eb..2fbad5b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -9,6 +9,23 @@
 #include "parse-options.h"
 #include "dir.h"
 #include "run-command.h"
+#include "quote.h"
+
+/**
+ * Returns 1 if the file is empty or does not exist, 0 otherwise.
+ */
+static int is_empty_file(const char *filename)
+{
+	struct stat st;
+
+	if (stat(filename, &st) < 0) {
+		if (errno == ENOENT)
+			return 1;
+		die_errno(_("could not stat %s"), filename);
+	}
+
+	return !st.st_size;
+}
 
 /**
  * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators.
@@ -38,6 +55,13 @@ struct am_state {
 	int cur;
 	int last;
 
+	/* commit metadata and message */
+	char *author_name;
+	char *author_email;
+	char *author_date;
+	char *msg;
+	size_t msg_len;
+
 	/* number of digits in patch filename */
 	int prec;
 };
@@ -63,6 +87,18 @@ static void am_state_release(struct am_state *state)
 {
 	if (state->dir)
 		free(state->dir);
+
+	if (state->author_name)
+		free(state->author_name);
+
+	if (state->author_email)
+		free(state->author_email);
+
+	if (state->author_date)
+		free(state->author_date);
+
+	if (state->msg)
+		free(state->msg);
 }
 
 /**
@@ -115,6 +151,167 @@ static int read_state_file(struct strbuf *sb, const struct am_state *state,
 }
 
 /**
+ * Reads a KEY=VALUE shell variable assignment from `fp`, returning the VALUE
+ * as a newly-allocated string. VALUE must be a quoted string, and the KEY must
+ * match `key`. Returns NULL on failure.
+ *
+ * This is used by read_author_script() to read the GIT_AUTHOR_* variables from
+ * the author-script.
+ */
+static char *read_shell_var(FILE *fp, const char *key)
+{
+	struct strbuf sb = STRBUF_INIT;
+	const char *str;
+
+	if (strbuf_getline(&sb, fp, '\n'))
+		goto fail;
+
+	if (!skip_prefix(sb.buf, key, &str))
+		goto fail;
+
+	if (!skip_prefix(str, "=", &str))
+		goto fail;
+
+	strbuf_remove(&sb, 0, str - sb.buf);
+
+	str = sq_dequote(sb.buf);
+	if (!str)
+		goto fail;
+
+	return strbuf_detach(&sb, NULL);
+
+fail:
+	strbuf_release(&sb);
+	return NULL;
+}
+
+/**
+ * Reads and parses the state directory's "author-script" file, and sets
+ * state->author_name, state->author_email and state->author_date accordingly.
+ * Returns 0 on success, -1 if the file could not be parsed.
+ *
+ * The author script is of the format:
+ *
+ *	GIT_AUTHOR_NAME='$author_name'
+ *	GIT_AUTHOR_EMAIL='$author_email'
+ *	GIT_AUTHOR_DATE='$author_date'
+ *
+ * where $author_name, $author_email and $author_date are quoted. We are strict
+ * with our parsing, as the file was meant to be eval'd in the old git-am.sh
+ * script, and thus if the file differs from what this function expects, it is
+ * better to bail out than to do something that the user does not expect.
+ */
+static int read_author_script(struct am_state *state)
+{
+	const char *filename = am_path(state, "author-script");
+	FILE *fp;
+
+	assert(!state->author_name);
+	assert(!state->author_email);
+	assert(!state->author_date);
+
+	fp = fopen(filename, "r");
+	if (!fp) {
+		if (errno == ENOENT)
+			return 0;
+		die_errno(_("could not open '%s' for reading"), filename);
+	}
+
+	state->author_name = read_shell_var(fp, "GIT_AUTHOR_NAME");
+	if (!state->author_name) {
+		fclose(fp);
+		return -1;
+	}
+
+	state->author_email = read_shell_var(fp, "GIT_AUTHOR_EMAIL");
+	if (!state->author_email) {
+		fclose(fp);
+		return -1;
+	}
+
+	state->author_date = read_shell_var(fp, "GIT_AUTHOR_DATE");
+	if (!state->author_date) {
+		fclose(fp);
+		return -1;
+	}
+
+	if (fgetc(fp) != EOF) {
+		fclose(fp);
+		return -1;
+	}
+
+	fclose(fp);
+	return 0;
+}
+
+/**
+ * Saves state->author_name, state->author_email and state->author_date in the
+ * state directory's "author-script" file.
+ */
+static void write_author_script(const struct am_state *state)
+{
+	struct strbuf sb = STRBUF_INIT;
+
+	assert(state->author_name);
+	assert(state->author_email);
+	assert(state->author_date);
+
+	strbuf_addstr(&sb, "GIT_AUTHOR_NAME=");
+	sq_quote_buf(&sb, state->author_name);
+	strbuf_addch(&sb, '\n');
+
+	strbuf_addstr(&sb, "GIT_AUTHOR_EMAIL=");
+	sq_quote_buf(&sb, state->author_email);
+	strbuf_addch(&sb, '\n');
+
+	strbuf_addstr(&sb, "GIT_AUTHOR_DATE=");
+	sq_quote_buf(&sb, state->author_date);
+	strbuf_addch(&sb, '\n');
+
+	write_file(am_path(state, "author-script"), 1, "%s", sb.buf);
+
+	strbuf_release(&sb);
+}
+
+/**
+ * Reads the commit message from the state directory's "final-commit" file,
+ * setting state->msg to its contents and state->msg_len to the length of its
+ * contents in bytes.
+ *
+ * Returns 0 on success, -1 if the file does not exist.
+ */
+static int read_commit_msg(struct am_state *state)
+{
+	struct strbuf sb = STRBUF_INIT;
+
+	assert(!state->msg);
+
+	if (read_state_file(&sb, state, "final-commit", 0) < 0) {
+		strbuf_release(&sb);
+		return -1;
+	}
+
+	state->msg = strbuf_detach(&sb, &state->msg_len);
+	return 0;
+}
+
+/**
+ * Saves state->msg in the state directory's "final-commit" file.
+ */
+static void write_commit_msg(const struct am_state *state)
+{
+	int fd;
+	const char *filename = am_path(state, "final-commit");
+
+	assert(state->msg);
+
+	fd = xopen(filename, O_WRONLY | O_CREAT, 0666);
+	if (write_in_full(fd, state->msg, state->msg_len) < 0)
+		die_errno(_("could not write to %s"), filename);
+	close(fd);
+}
+
+/**
  * Loads state from disk.
  */
 static void am_load(struct am_state *state)
@@ -129,6 +326,11 @@ static void am_load(struct am_state *state)
 		die("BUG: state file 'last' does not exist");
 	state->last = strtol(sb.buf, NULL, 10);
 
+	if (read_author_script(state) < 0)
+		die(_("could not parse author script"));
+
+	read_commit_msg(state);
+
 	strbuf_release(&sb);
 }
 
@@ -321,19 +523,152 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
  */
 static void am_next(struct am_state *state)
 {
+	if (state->author_name)
+		free(state->author_name);
+	state->author_name = NULL;
+
+	if (state->author_email)
+		free(state->author_email);
+	state->author_email = NULL;
+
+	if (state->author_date)
+		free(state->author_date);
+	state->author_date = NULL;
+
+	if (state->msg)
+		free(state->msg);
+	state->msg = NULL;
+	state->msg_len = 0;
+
+	unlink(am_path(state, "author-script"));
+	unlink(am_path(state, "final-commit"));
+
 	state->cur++;
 	write_file(am_path(state, "next"), 1, "%d", state->cur);
 }
 
 /**
+ * Returns the filename of the current patch email.
+ */
+static const char *msgnum(const struct am_state *state)
+{
+	static struct strbuf sb = STRBUF_INIT;
+
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%0*d", state->prec, state->cur);
+
+	return sb.buf;
+}
+
+/**
+ * Parses `mail` using git-mailinfo, extracting its patch and authorship info.
+ * state->msg will be set to the patch message. state->author_name,
+ * state->author_email and state->author_date will be set to the patch author's
+ * name, email and date respectively. The patch body will be written to the
+ * state directory's "patch" file.
+ *
+ * Returns 1 if the patch should be skipped, 0 otherwise.
+ */
+static int parse_mail(struct am_state *state, const char *mail)
+{
+	FILE *fp;
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct strbuf sb = STRBUF_INIT;
+	struct strbuf msg = STRBUF_INIT;
+	struct strbuf author_name = STRBUF_INIT;
+	struct strbuf author_date = STRBUF_INIT;
+	struct strbuf author_email = STRBUF_INIT;
+	int ret = 0;
+
+	cp.git_cmd = 1;
+	cp.in = xopen(mail, O_RDONLY, 0);
+	cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777);
+
+	argv_array_push(&cp.args, "mailinfo");
+	argv_array_push(&cp.args, am_path(state, "msg"));
+	argv_array_push(&cp.args, am_path(state, "patch"));
+
+	if (run_command(&cp) < 0)
+		die("could not parse patch");
+
+	close(cp.in);
+	close(cp.out);
+
+	/* Extract message and author information */
+	fp = xfopen(am_path(state, "info"), "r");
+	while (!strbuf_getline(&sb, fp, '\n')) {
+		const char *x;
+
+		if (skip_prefix(sb.buf, "Subject: ", &x)) {
+			if (msg.len)
+				strbuf_addch(&msg, '\n');
+			strbuf_addstr(&msg, x);
+		} else if (skip_prefix(sb.buf, "Author: ", &x))
+			strbuf_addstr(&author_name, x);
+		else if (skip_prefix(sb.buf, "Email: ", &x))
+			strbuf_addstr(&author_email, x);
+		else if (skip_prefix(sb.buf, "Date: ", &x))
+			strbuf_addstr(&author_date, x);
+	}
+	fclose(fp);
+
+	/* Skip pine's internal folder data */
+	if (!strcmp(author_name.buf, "Mail System Internal Data")) {
+		ret = 1;
+		goto finish;
+	}
+
+	if (is_empty_file(am_path(state, "patch"))) {
+		printf_ln(_("Patch is empty. Was it split wrong?"));
+		exit(128);
+	}
+
+	strbuf_addstr(&msg, "\n\n");
+	if (strbuf_read_file(&msg, am_path(state, "msg"), 0) < 0)
+		die_errno(_("could not read '%s'"), am_path(state, "msg"));
+	stripspace(&msg, 0);
+
+	assert(!state->author_name);
+	state->author_name = strbuf_detach(&author_name, NULL);
+
+	assert(!state->author_email);
+	state->author_email = strbuf_detach(&author_email, NULL);
+
+	assert(!state->author_date);
+	state->author_date = strbuf_detach(&author_date, NULL);
+
+	assert(!state->msg);
+	state->msg = strbuf_detach(&msg, &state->msg_len);
+
+finish:
+	strbuf_release(&msg);
+	strbuf_release(&author_date);
+	strbuf_release(&author_email);
+	strbuf_release(&author_name);
+	strbuf_release(&sb);
+	return ret;
+}
+
+/**
  * Applies all queued mail.
  */
 static void am_run(struct am_state *state)
 {
 	while (state->cur <= state->last) {
+		const char *mail = am_path(state, msgnum(state));
+
+		if (!file_exists(mail))
+			goto next;
+
+		if (parse_mail(state, mail))
+			goto next; /* mail should be skipped */
+
+		write_author_script(state);
+		write_commit_msg(state);
 
 		/* TODO: Patch application not implemented yet */
 
+next:
 		am_next(state);
 	}
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 08/44] builtin-am: apply patch with git-apply
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (6 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 07/44] builtin-am: extract patch and commit info with git-mailinfo Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 09/44] builtin-am: implement committing applied patch Paul Tan
                   ` (36 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

Implement applying the patch to the index using git-apply.

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

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

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    v4
    
    * Switch firstline() to linelen(), use .%s format specifier.
    
    * Remove blank lines.

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

diff --git a/builtin/am.c b/builtin/am.c
index 2fbad5b..0762c70 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -10,6 +10,7 @@
 #include "dir.h"
 #include "run-command.h"
 #include "quote.h"
+#include "lockfile.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -42,6 +43,14 @@ static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
 	return 0;
 }
 
+/**
+ * Returns the length of the first line of msg.
+ */
+static int linelen(const char *msg)
+{
+	return strchrnul(msg, '\n') - msg;
+}
+
 enum patch_format {
 	PATCH_FORMAT_UNKNOWN = 0,
 	PATCH_FORMAT_MBOX
@@ -561,6 +570,20 @@ static const char *msgnum(const struct am_state *state)
 }
 
 /**
+ * Refresh and write index.
+ */
+static void refresh_and_write_cache(void)
+{
+	static struct lock_file lock_file;
+
+	hold_locked_index(&lock_file, 1);
+	refresh_cache(REFRESH_QUIET);
+	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+		die(_("unable to write index file"));
+	rollback_lock_file(&lock_file);
+}
+
+/**
  * Parses `mail` using git-mailinfo, extracting its patch and authorship info.
  * state->msg will be set to the patch message. state->author_name,
  * state->author_email and state->author_date will be set to the patch author's
@@ -650,10 +673,35 @@ finish:
 }
 
 /**
+ * Applies current patch with git-apply. Returns 0 on success, -1 otherwise.
+ */
+static int run_apply(const struct am_state *state)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	cp.git_cmd = 1;
+
+	argv_array_push(&cp.args, "apply");
+	argv_array_push(&cp.args, "--index");
+	argv_array_push(&cp.args, am_path(state, "patch"));
+
+	if (run_command(&cp))
+		return -1;
+
+	/* Reload index as git-apply will have modified it. */
+	discard_cache();
+	read_cache();
+
+	return 0;
+}
+
+/**
  * Applies all queued mail.
  */
 static void am_run(struct am_state *state)
 {
+	refresh_and_write_cache();
+
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 
@@ -666,7 +714,27 @@ static void am_run(struct am_state *state)
 		write_author_script(state);
 		write_commit_msg(state);
 
-		/* TODO: Patch application not implemented yet */
+		printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg);
+
+		if (run_apply(state) < 0) {
+			int advice_amworkdir = 1;
+
+			printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
+				linelen(state->msg), state->msg);
+
+			git_config_get_bool("advice.amworkdir", &advice_amworkdir);
+
+			if (advice_amworkdir)
+				printf_ln(_("The copy of the patch that failed is found in: %s"),
+						am_path(state, "patch"));
+
+			exit(128);
+		}
+
+		/*
+		 * TODO: After the patch has been applied to the index with
+		 * git-apply, we need to make commit as well.
+		 */
 
 next:
 		am_next(state);
@@ -728,6 +796,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, prefix, options, usage, 0);
 
+	if (read_index_preload(&the_index, NULL) < 0)
+		die(_("failed to read the index"));
+
 	if (am_in_progress(&state))
 		am_load(&state);
 	else {
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 09/44] builtin-am: implement committing applied patch
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (7 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 08/44] builtin-am: apply patch with git-apply Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 10/44] builtin-am: refuse to apply patches if index is dirty Paul Tan
                   ` (35 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

Implement do_commit(), which commits the index which contains the
results of applying the patch, along with the extracted commit message
and authorship information.

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

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

diff --git a/builtin/am.c b/builtin/am.c
index 0762c70..bbb540c 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -11,6 +11,9 @@
 #include "run-command.h"
 #include "quote.h"
 #include "lockfile.h"
+#include "cache-tree.h"
+#include "refs.h"
+#include "commit.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -696,10 +699,56 @@ static int run_apply(const struct am_state *state)
 }
 
 /**
+ * Commits the current index with state->msg as the commit message and
+ * state->author_name, state->author_email and state->author_date as the author
+ * information.
+ */
+static void do_commit(const struct am_state *state)
+{
+	unsigned char tree[GIT_SHA1_RAWSZ], parent[GIT_SHA1_RAWSZ],
+		      commit[GIT_SHA1_RAWSZ];
+	unsigned char *ptr;
+	struct commit_list *parents = NULL;
+	const char *reflog_msg, *author;
+	struct strbuf sb = STRBUF_INIT;
+
+	if (write_cache_as_tree(tree, 0, NULL))
+		die(_("git write-tree failed to write a tree"));
+
+	if (!get_sha1_commit("HEAD", parent)) {
+		ptr = parent;
+		commit_list_insert(lookup_commit(parent), &parents);
+	} else {
+		ptr = NULL;
+		fprintf_ln(stderr, _("applying to an empty history"));
+	}
+
+	author = fmt_ident(state->author_name, state->author_email,
+			state->author_date, IDENT_STRICT);
+
+	if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
+				author, NULL))
+		die(_("failed to write commit object"));
+
+	reflog_msg = getenv("GIT_REFLOG_ACTION");
+	if (!reflog_msg)
+		reflog_msg = "am";
+
+	strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg),
+			state->msg);
+
+	update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR);
+
+	strbuf_release(&sb);
+}
+
+/**
  * Applies all queued mail.
  */
 static void am_run(struct am_state *state)
 {
+	const char *argv_gc_auto[] = {"gc", "--auto", NULL};
+
 	refresh_and_write_cache();
 
 	while (state->cur <= state->last) {
@@ -731,16 +780,14 @@ static void am_run(struct am_state *state)
 			exit(128);
 		}
 
-		/*
-		 * TODO: After the patch has been applied to the index with
-		 * git-apply, we need to make commit as well.
-		 */
+		do_commit(state);
 
 next:
 		am_next(state);
 	}
 
 	am_destroy(state);
+	run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
 }
 
 /**
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 10/44] builtin-am: refuse to apply patches if index is dirty
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (8 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 09/44] builtin-am: implement committing applied patch Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 11/44] builtin-am: implement --resolved/--continue Paul Tan
                   ` (34 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    Note: no tests for this

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

diff --git a/builtin/am.c b/builtin/am.c
index bbb540c..f9a641a 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -14,6 +14,8 @@
 #include "cache-tree.h"
 #include "refs.h"
 #include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -587,6 +589,43 @@ static void refresh_and_write_cache(void)
 }
 
 /**
+ * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn
+ * branch, returns 1 if there are entries in the index, 0 otherwise. If an
+ * strbuf is provided, the space-separated list of files that differ will be
+ * appended to it.
+ */
+static int index_has_changes(struct strbuf *sb)
+{
+	unsigned char head[GIT_SHA1_RAWSZ];
+	int i;
+
+	if (!get_sha1_tree("HEAD", head)) {
+		struct diff_options opt;
+
+		diff_setup(&opt);
+		DIFF_OPT_SET(&opt, EXIT_WITH_STATUS);
+		if (!sb)
+			DIFF_OPT_SET(&opt, QUICK);
+		do_diff_cache(head, &opt);
+		diffcore_std(&opt);
+		for (i = 0; sb && i < diff_queued_diff.nr; i++) {
+			if (i)
+				strbuf_addch(sb, ' ');
+			strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path);
+		}
+		diff_flush(&opt);
+		return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0;
+	} else {
+		for (i = 0; sb && i < active_nr; i++) {
+			if (i)
+				strbuf_addch(sb, ' ');
+			strbuf_addstr(sb, active_cache[i]->name);
+		}
+		return !!active_nr;
+	}
+}
+
+/**
  * Parses `mail` using git-mailinfo, extracting its patch and authorship info.
  * state->msg will be set to the patch message. state->author_name,
  * state->author_email and state->author_date will be set to the patch author's
@@ -748,9 +787,15 @@ static void do_commit(const struct am_state *state)
 static void am_run(struct am_state *state)
 {
 	const char *argv_gc_auto[] = {"gc", "--auto", NULL};
+	struct strbuf sb = STRBUF_INIT;
 
 	refresh_and_write_cache();
 
+	if (index_has_changes(&sb))
+		die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf);
+
+	strbuf_release(&sb);
+
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 11/44] builtin-am: implement --resolved/--continue
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (9 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 10/44] builtin-am: refuse to apply patches if index is dirty Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 12/44] builtin-am: implement --skip Paul Tan
                   ` (33 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index f9a641a..f21565b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -836,6 +836,42 @@ next:
 }
 
 /**
+ * Resume the current am session after patch application failure. The user did
+ * all the hard work, and we do not have to do any patch application. Just
+ * trust and commit what the user has in the index and working tree.
+ */
+static void am_resolve(struct am_state *state)
+{
+	if (!state->msg)
+		die(_("cannot resume: %s does not exist."),
+			am_path(state, "final-commit"));
+
+	if (!state->author_name || !state->author_email || !state->author_date)
+		die(_("cannot resume: %s does not exist."),
+			am_path(state, "author-script"));
+
+	printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg);
+
+	if (!index_has_changes(NULL)) {
+		printf_ln(_("No changes - did you forget to use 'git add'?\n"
+			"If there is nothing left to stage, chances are that something else\n"
+			"already introduced the same changes; you might want to skip this patch."));
+		exit(128);
+	}
+
+	if (unmerged_cache()) {
+		printf_ln(_("You still have unmerged paths in your index.\n"
+			"Did you forget to use 'git add'?"));
+		exit(128);
+	}
+
+	do_commit(state);
+
+	am_next(state);
+	am_run(state);
+}
+
+/**
  * parse_options() callback that validates and sets opt->value to the
  * PATCH_FORMAT_* enum value corresponding to `arg`.
  */
@@ -850,13 +886,20 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
 	return 0;
 }
 
+enum resume_mode {
+	RESUME_FALSE = 0,
+	RESUME_RESOLVED
+};
+
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
 	struct am_state state;
 	int patch_format = PATCH_FORMAT_UNKNOWN;
+	enum resume_mode resume = RESUME_FALSE;
 
 	const char * const usage[] = {
 		N_("git am [options] [(<mbox>|<Maildir>)...]"),
+		N_("git am [options] --continue"),
 		NULL
 	};
 
@@ -864,6 +907,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
+		OPT_CMDMODE(0, "continue", &resume,
+			N_("continue applying patches after resolving a conflict"),
+			RESUME_RESOLVED),
+		OPT_CMDMODE('r', "resolved", &resume,
+			N_("synonyms for --continue"),
+			RESUME_RESOLVED),
 		OPT_END()
 	};
 
@@ -897,6 +946,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		struct argv_array paths = ARGV_ARRAY_INIT;
 		int i;
 
+		if (resume)
+			die(_("Resolve operation not in progress, we are not resuming."));
+
 		for (i = 0; i < argc; i++) {
 			if (is_absolute_path(argv[i]) || !prefix)
 				argv_array_push(&paths, argv[i]);
@@ -909,7 +961,16 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		argv_array_clear(&paths);
 	}
 
-	am_run(&state);
+	switch (resume) {
+	case RESUME_FALSE:
+		am_run(&state);
+		break;
+	case RESUME_RESOLVED:
+		am_resolve(&state);
+		break;
+	default:
+		die("BUG: invalid resume value");
+	}
 
 	am_state_release(&state);
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 12/44] builtin-am: implement --skip
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (10 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 11/44] builtin-am: implement --resolved/--continue Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 13/44] builtin-am: implement --abort Paul Tan
                   ` (32 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 119 insertions(+), 2 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index f21565b..5087094 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -16,6 +16,8 @@
 #include "commit.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "unpack-trees.h"
+#include "branch.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -872,6 +874,114 @@ static void am_resolve(struct am_state *state)
 }
 
 /**
+ * Performs a checkout fast-forward from `head` to `remote`. If `reset` is
+ * true, any unmerged entries will be discarded. Returns 0 on success, -1 on
+ * failure.
+ */
+static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
+{
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+	struct unpack_trees_options opts;
+	struct tree_desc t[2];
+
+	if (parse_tree(head) || parse_tree(remote))
+		return -1;
+
+	hold_locked_index(lock_file, 1);
+
+	refresh_cache(REFRESH_QUIET);
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.merge = 1;
+	opts.reset = reset;
+	opts.fn = twoway_merge;
+	init_tree_desc(&t[0], head->buffer, head->size);
+	init_tree_desc(&t[1], remote->buffer, remote->size);
+
+	if (unpack_trees(2, t, &opts)) {
+		rollback_lock_file(lock_file);
+		return -1;
+	}
+
+	if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+		die(_("unable to write new index file"));
+
+	return 0;
+}
+
+/**
+ * Clean the index without touching entries that are not modified between
+ * `head` and `remote`.
+ */
+static int clean_index(const unsigned char *head, const unsigned char *remote)
+{
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+	struct tree *head_tree, *remote_tree, *index_tree;
+	unsigned char index[GIT_SHA1_RAWSZ];
+	struct pathspec pathspec;
+
+	head_tree = parse_tree_indirect(head);
+	if (!head_tree)
+		return error(_("Could not parse object '%s'."), sha1_to_hex(head));
+
+	remote_tree = parse_tree_indirect(remote);
+	if (!remote_tree)
+		return error(_("Could not parse object '%s'."), sha1_to_hex(remote));
+
+	read_cache_unmerged();
+
+	if (fast_forward_to(head_tree, head_tree, 1))
+		return -1;
+
+	if (write_cache_as_tree(index, 0, NULL))
+		return -1;
+
+	index_tree = parse_tree_indirect(index);
+	if (!index_tree)
+		return error(_("Could not parse object '%s'."), sha1_to_hex(index));
+
+	if (fast_forward_to(index_tree, remote_tree, 0))
+		return -1;
+
+	memset(&pathspec, 0, sizeof(pathspec));
+
+	hold_locked_index(lock_file, 1);
+
+	if (read_tree(remote_tree, 0, &pathspec)) {
+		rollback_lock_file(lock_file);
+		return -1;
+	}
+
+	if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+		die(_("unable to write new index file"));
+
+	remove_branch_state();
+
+	return 0;
+}
+
+/**
+ * Resume the current am session by skipping the current patch.
+ */
+static void am_skip(struct am_state *state)
+{
+	unsigned char head[GIT_SHA1_RAWSZ];
+
+	if (get_sha1("HEAD", head))
+		hashcpy(head, EMPTY_TREE_SHA1_BIN);
+
+	if (clean_index(head, head))
+		die(_("failed to clean index"));
+
+	am_next(state);
+	am_run(state);
+}
+
+/**
  * parse_options() callback that validates and sets opt->value to the
  * PATCH_FORMAT_* enum value corresponding to `arg`.
  */
@@ -888,7 +998,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
 
 enum resume_mode {
 	RESUME_FALSE = 0,
-	RESUME_RESOLVED
+	RESUME_RESOLVED,
+	RESUME_SKIP
 };
 
 int cmd_am(int argc, const char **argv, const char *prefix)
@@ -899,7 +1010,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
 	const char * const usage[] = {
 		N_("git am [options] [(<mbox>|<Maildir>)...]"),
-		N_("git am [options] --continue"),
+		N_("git am [options] (--continue | --skip)"),
 		NULL
 	};
 
@@ -913,6 +1024,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE('r', "resolved", &resume,
 			N_("synonyms for --continue"),
 			RESUME_RESOLVED),
+		OPT_CMDMODE(0, "skip", &resume,
+			N_("skip the current patch"),
+			RESUME_SKIP),
 		OPT_END()
 	};
 
@@ -968,6 +1082,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 	case RESUME_RESOLVED:
 		am_resolve(&state);
 		break;
+	case RESUME_SKIP:
+		am_skip(&state);
+		break;
 	default:
 		die("BUG: invalid resume value");
 	}
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 13/44] builtin-am: implement --abort
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (11 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 12/44] builtin-am: implement --skip Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 14/44] builtin-am: reject patches when there's a session in progress Paul Tan
                   ` (31 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

diff --git a/builtin/am.c b/builtin/am.c
index 5087094..1db95f2 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -507,6 +507,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
 static void am_setup(struct am_state *state, enum patch_format patch_format,
 			const char **paths)
 {
+	unsigned char curr_head[GIT_SHA1_RAWSZ];
+
 	if (!patch_format)
 		patch_format = detect_patch_format(paths);
 
@@ -523,6 +525,14 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 		die(_("Failed to split patches."));
 	}
 
+	if (!get_sha1("HEAD", curr_head)) {
+		write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head));
+		update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+	} else {
+		write_file(am_path(state, "abort-safety"), 1, "%s", "");
+		delete_ref("ORIG_HEAD", NULL, 0);
+	}
+
 	/*
 	 * NOTE: Since the "next" and "last" files determine if an am_state
 	 * session is in progress, they should be written last.
@@ -539,6 +549,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
  */
 static void am_next(struct am_state *state)
 {
+	unsigned char head[GIT_SHA1_RAWSZ];
+
 	if (state->author_name)
 		free(state->author_name);
 	state->author_name = NULL;
@@ -559,6 +571,11 @@ static void am_next(struct am_state *state)
 	unlink(am_path(state, "author-script"));
 	unlink(am_path(state, "final-commit"));
 
+	if (!get_sha1("HEAD", head))
+		write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head));
+	else
+		write_file(am_path(state, "abort-safety"), 1, "%s", "");
+
 	state->cur++;
 	write_file(am_path(state, "next"), 1, "%d", state->cur);
 }
@@ -791,10 +808,14 @@ static void am_run(struct am_state *state)
 	const char *argv_gc_auto[] = {"gc", "--auto", NULL};
 	struct strbuf sb = STRBUF_INIT;
 
+	unlink(am_path(state, "dirtyindex"));
+
 	refresh_and_write_cache();
 
-	if (index_has_changes(&sb))
+	if (index_has_changes(&sb)) {
+		write_file(am_path(state, "dirtyindex"), 1, "t");
 		die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf);
+	}
 
 	strbuf_release(&sb);
 
@@ -982,6 +1003,74 @@ static void am_skip(struct am_state *state)
 }
 
 /**
+ * Returns true if it is safe to reset HEAD to the ORIG_HEAD, false otherwise.
+ *
+ * It is not safe to reset HEAD when:
+ * 1. git-am previously failed because the index was dirty.
+ * 2. HEAD has moved since git-am previously failed.
+ */
+static int safe_to_abort(const struct am_state *state)
+{
+	struct strbuf sb = STRBUF_INIT;
+	unsigned char abort_safety[GIT_SHA1_RAWSZ], head[GIT_SHA1_RAWSZ];
+
+	if (file_exists(am_path(state, "dirtyindex")))
+		return 0;
+
+	if (read_state_file(&sb, state, "abort-safety", 1) > 0) {
+		if (get_sha1_hex(sb.buf, abort_safety))
+			die(_("could not parse %s"), am_path(state, "abort_safety"));
+	} else
+		hashclr(abort_safety);
+
+	if (get_sha1("HEAD", head))
+		hashclr(head);
+
+	if (!hashcmp(head, abort_safety))
+		return 1;
+
+	error(_("You seem to have moved HEAD since the last 'am' failure.\n"
+		"Not rewinding to ORIG_HEAD"));
+
+	return 0;
+}
+
+/**
+ * Aborts the current am session if it is safe to do so.
+ */
+static void am_abort(struct am_state *state)
+{
+	unsigned char curr_head[GIT_SHA1_RAWSZ], orig_head[GIT_SHA1_RAWSZ];
+	int has_curr_head, has_orig_head;
+	const char *curr_branch;
+
+	if (!safe_to_abort(state)) {
+		am_destroy(state);
+		return;
+	}
+
+	curr_branch = resolve_refdup("HEAD", 0, curr_head, NULL);
+	has_curr_head = !is_null_sha1(curr_head);
+	if (!has_curr_head)
+		hashcpy(curr_head, EMPTY_TREE_SHA1_BIN);
+
+	has_orig_head = !get_sha1("ORIG_HEAD", orig_head);
+	if (!has_orig_head)
+		hashcpy(orig_head, EMPTY_TREE_SHA1_BIN);
+
+	clean_index(curr_head, orig_head);
+
+	if (has_orig_head)
+		update_ref("am --abort", "HEAD", orig_head,
+				has_curr_head ? curr_head : NULL, 0,
+				UPDATE_REFS_DIE_ON_ERR);
+	else if (curr_branch)
+		delete_ref(curr_branch, NULL, REF_NODEREF);
+
+	am_destroy(state);
+}
+
+/**
  * parse_options() callback that validates and sets opt->value to the
  * PATCH_FORMAT_* enum value corresponding to `arg`.
  */
@@ -999,7 +1088,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
 enum resume_mode {
 	RESUME_FALSE = 0,
 	RESUME_RESOLVED,
-	RESUME_SKIP
+	RESUME_SKIP,
+	RESUME_ABORT
 };
 
 int cmd_am(int argc, const char **argv, const char *prefix)
@@ -1010,7 +1100,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
 	const char * const usage[] = {
 		N_("git am [options] [(<mbox>|<Maildir>)...]"),
-		N_("git am [options] (--continue | --skip)"),
+		N_("git am [options] (--continue | --skip | --abort)"),
 		NULL
 	};
 
@@ -1027,6 +1117,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "skip", &resume,
 			N_("skip the current patch"),
 			RESUME_SKIP),
+		OPT_CMDMODE(0, "abort", &resume,
+			N_("restore the original branch and abort the patching operation."),
+			RESUME_ABORT),
 		OPT_END()
 	};
 
@@ -1085,6 +1178,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 	case RESUME_SKIP:
 		am_skip(&state);
 		break;
+	case RESUME_ABORT:
+		am_abort(&state);
+		break;
 	default:
 		die("BUG: invalid resume value");
 	}
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 14/44] builtin-am: reject patches when there's a session in progress
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (12 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 13/44] builtin-am: implement --abort Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 15/44] builtin-am: implement -q/--quiet, GIT_QUIET Paul Tan
                   ` (30 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

Re-implement the above two safety checks.

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    NOTE: there's no test for this

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

diff --git a/builtin/am.c b/builtin/am.c
index 1db95f2..e066a97 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1147,9 +1147,24 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 	if (read_index_preload(&the_index, NULL) < 0)
 		die(_("failed to read the index"));
 
-	if (am_in_progress(&state))
+	if (am_in_progress(&state)) {
+		/*
+		 * Catch user error to feed us patches when there is a session
+		 * in progress:
+		 *
+		 * 1. mbox path(s) are provided on the command-line.
+		 * 2. stdin is not a tty: the user is trying to feed us a patch
+		 *    from standard input. This is somewhat unreliable -- stdin
+		 *    could be /dev/null for example and the caller did not
+		 *    intend to feed us a patch but wanted to continue
+		 *    unattended.
+		 */
+		if (argc || (resume == RESUME_FALSE && !isatty(0)))
+			die(_("previous rebase directory %s still exists but mbox given."),
+				state.dir);
+
 		am_load(&state);
-	else {
+	} else {
 		struct argv_array paths = ARGV_ARRAY_INIT;
 		int i;
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 15/44] builtin-am: implement -q/--quiet, GIT_QUIET
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (13 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 14/44] builtin-am: reject patches when there's a session in progress Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 16/44] builtin-am: exit with user friendly message on failure Paul Tan
                   ` (29 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 36 +++++++++++++++++++++++++++++++++---
 1 file changed, 33 insertions(+), 3 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index e066a97..2643b04 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -80,6 +80,8 @@ struct am_state {
 
 	/* number of digits in patch filename */
 	int prec;
+
+	int quiet;
 };
 
 /**
@@ -88,12 +90,18 @@ struct am_state {
  */
 static void am_state_init(struct am_state *state, const char *dir)
 {
+	const char *quiet;
+
 	memset(state, 0, sizeof(*state));
 
 	assert(dir);
 	state->dir = xstrdup(dir);
 
 	state->prec = 4;
+
+	quiet = getenv("GIT_QUIET");
+	if (quiet && *quiet)
+		state->quiet = 1;
 }
 
 /**
@@ -128,6 +136,22 @@ static inline const char *am_path(const struct am_state *state, const char *path
 }
 
 /**
+ * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline
+ * at the end.
+ */
+static void say(const struct am_state *state, FILE *fp, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	if (!state->quiet) {
+		vfprintf(fp, fmt, ap);
+		putc('\n', fp);
+	}
+	va_end(ap);
+}
+
+/**
  * Returns 1 if there is an am session in progress, 0 otherwise.
  */
 static int am_in_progress(const struct am_state *state)
@@ -347,6 +371,9 @@ static void am_load(struct am_state *state)
 
 	read_commit_msg(state);
 
+	read_state_file(&sb, state, "quiet", 1);
+	state->quiet = !strcmp(sb.buf, "t");
+
 	strbuf_release(&sb);
 }
 
@@ -525,6 +552,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 		die(_("Failed to split patches."));
 	}
 
+	write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
+
 	if (!get_sha1("HEAD", curr_head)) {
 		write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head));
 		update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
@@ -778,7 +807,7 @@ static void do_commit(const struct am_state *state)
 		commit_list_insert(lookup_commit(parent), &parents);
 	} else {
 		ptr = NULL;
-		fprintf_ln(stderr, _("applying to an empty history"));
+		say(state, stderr, _("applying to an empty history"));
 	}
 
 	author = fmt_ident(state->author_name, state->author_email,
@@ -831,7 +860,7 @@ static void am_run(struct am_state *state)
 		write_author_script(state);
 		write_commit_msg(state);
 
-		printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg);
+		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
 		if (run_apply(state) < 0) {
 			int advice_amworkdir = 1;
@@ -873,7 +902,7 @@ static void am_resolve(struct am_state *state)
 		die(_("cannot resume: %s does not exist."),
 			am_path(state, "author-script"));
 
-	printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg);
+	say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
 	if (!index_has_changes(NULL)) {
 		printf_ln(_("No changes - did you forget to use 'git add'?\n"
@@ -1105,6 +1134,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 	};
 
 	struct option options[] = {
+		OPT__QUIET(&state.quiet, N_("be quiet")),
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 16/44] builtin-am: exit with user friendly message on failure
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (14 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 15/44] builtin-am: implement -q/--quiet, GIT_QUIET Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 17/44] builtin-am: implement -s/--signoff Paul Tan
                   ` (28 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

diff --git a/builtin/am.c b/builtin/am.c
index 2643b04..7832ecf 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -82,6 +82,9 @@ struct am_state {
 	int prec;
 
 	int quiet;
+
+	/* override error message when patch failure occurs */
+	const char *resolvemsg;
 };
 
 /**
@@ -674,6 +677,25 @@ static int index_has_changes(struct strbuf *sb)
 }
 
 /**
+ * Dies with a user-friendly message on how to proceed after resolving the
+ * problem. This message can be overridden with state->resolvemsg.
+ */
+static void NORETURN die_user_resolve(const struct am_state *state)
+{
+	if (state->resolvemsg) {
+		printf_ln("%s", state->resolvemsg);
+	} else {
+		const char *cmdline = "git am";
+
+		printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
+		printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
+		printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline);
+	}
+
+	exit(128);
+}
+
+/**
  * Parses `mail` using git-mailinfo, extracting its patch and authorship info.
  * state->msg will be set to the patch message. state->author_name,
  * state->author_email and state->author_date will be set to the patch author's
@@ -733,7 +755,7 @@ static int parse_mail(struct am_state *state, const char *mail)
 
 	if (is_empty_file(am_path(state, "patch"))) {
 		printf_ln(_("Patch is empty. Was it split wrong?"));
-		exit(128);
+		die_user_resolve(state);
 	}
 
 	strbuf_addstr(&msg, "\n\n");
@@ -874,7 +896,7 @@ static void am_run(struct am_state *state)
 				printf_ln(_("The copy of the patch that failed is found in: %s"),
 						am_path(state, "patch"));
 
-			exit(128);
+			die_user_resolve(state);
 		}
 
 		do_commit(state);
@@ -908,13 +930,13 @@ static void am_resolve(struct am_state *state)
 		printf_ln(_("No changes - did you forget to use 'git add'?\n"
 			"If there is nothing left to stage, chances are that something else\n"
 			"already introduced the same changes; you might want to skip this patch."));
-		exit(128);
+		die_user_resolve(state);
 	}
 
 	if (unmerged_cache()) {
 		printf_ln(_("You still have unmerged paths in your index.\n"
 			"Did you forget to use 'git add'?"));
-		exit(128);
+		die_user_resolve(state);
 	}
 
 	do_commit(state);
@@ -1138,6 +1160,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
+		OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
+			N_("override error message when patch failure occurs")),
 		OPT_CMDMODE(0, "continue", &resume,
 			N_("continue applying patches after resolving a conflict"),
 			RESUME_RESOLVED),
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 17/44] builtin-am: implement -s/--signoff
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (15 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 16/44] builtin-am: exit with user friendly message on failure Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 18/44] cache-tree: introduce write_index_as_tree() Paul Tan
                   ` (27 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 7832ecf..1ffbba1 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -18,6 +18,7 @@
 #include "diffcore.h"
 #include "unpack-trees.h"
 #include "branch.h"
+#include "sequencer.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -83,6 +84,8 @@ struct am_state {
 
 	int quiet;
 
+	int append_signoff;
+
 	/* override error message when patch failure occurs */
 	const char *resolvemsg;
 };
@@ -377,6 +380,9 @@ static void am_load(struct am_state *state)
 	read_state_file(&sb, state, "quiet", 1);
 	state->quiet = !strcmp(sb.buf, "t");
 
+	read_state_file(&sb, state, "sign", 1);
+	state->append_signoff = !strcmp(sb.buf, "t");
+
 	strbuf_release(&sb);
 }
 
@@ -557,6 +563,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 
 	write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
 
+	write_file(am_path(state, "sign"), 1, state->append_signoff ? "t" : "f");
+
 	if (!get_sha1("HEAD", curr_head)) {
 		write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head));
 		update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
@@ -763,6 +771,9 @@ static int parse_mail(struct am_state *state, const char *mail)
 		die_errno(_("could not read '%s'"), am_path(state, "msg"));
 	stripspace(&msg, 0);
 
+	if (state->append_signoff)
+		append_signoff(&msg, 0, 0);
+
 	assert(!state->author_name);
 	state->author_name = strbuf_detach(&author_name, NULL);
 
@@ -1157,6 +1168,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
 	struct option options[] = {
 		OPT__QUIET(&state.quiet, N_("be quiet")),
+		OPT_BOOL('s', "signoff", &state.append_signoff,
+			N_("add a Signed-off-by line to the commit message")),
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 18/44] cache-tree: introduce write_index_as_tree()
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (16 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 17/44] builtin-am: implement -s/--signoff Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 19/44] builtin-am: implement --3way, am.threeway Paul Tan
                   ` (26 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 19/44] builtin-am: implement --3way, am.threeway
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (17 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 18/44] cache-tree: introduce write_index_as_tree() Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-30  0:18   ` Stefan Beller
  2015-06-28 14:05 ` [PATCH v4 20/44] builtin-am: implement --rebasing mode Paul Tan
                   ` (25 subsequent siblings)
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 153 insertions(+), 4 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 1ffbba1..e870a1b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -19,6 +19,8 @@
 #include "unpack-trees.h"
 #include "branch.h"
 #include "sequencer.h"
+#include "revision.h"
+#include "merge-recursive.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -82,6 +84,8 @@ struct am_state {
 	/* number of digits in patch filename */
 	int prec;
 
+	int threeway;
+
 	int quiet;
 
 	int append_signoff;
@@ -105,6 +109,8 @@ static void am_state_init(struct am_state *state, const char *dir)
 
 	state->prec = 4;
 
+	git_config_get_bool("am.threeway", &state->threeway);
+
 	quiet = getenv("GIT_QUIET");
 	if (quiet && *quiet)
 		state->quiet = 1;
@@ -377,6 +383,9 @@ static void am_load(struct am_state *state)
 
 	read_commit_msg(state);
 
+	read_state_file(&sb, state, "threeway", 1);
+	state->threeway = !strcmp(sb.buf, "t");
+
 	read_state_file(&sb, state, "quiet", 1);
 	state->quiet = !strcmp(sb.buf, "t");
 
@@ -561,6 +570,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 		die(_("Failed to split patches."));
 	}
 
+	write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f");
+
 	write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
 
 	write_file(am_path(state, "sign"), 1, state->append_signoff ? "t" : "f");
@@ -796,16 +807,34 @@ finish:
 }
 
 /**
- * Applies current patch with git-apply. Returns 0 on success, -1 otherwise.
+ * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If
+ * `index_file` is not NULL, the patch will be applied to that index.
  */
-static int run_apply(const struct am_state *state)
+static int run_apply(const struct am_state *state, const char *index_file)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
 
 	cp.git_cmd = 1;
 
+	if (index_file)
+		argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file);
+
+	/*
+	 * If we are allowed to fall back on 3-way merge, don't give false
+	 * errors during the initial attempt.
+	 */
+	if (state->threeway && !index_file) {
+		cp.no_stdout = 1;
+		cp.no_stderr = 1;
+	}
+
 	argv_array_push(&cp.args, "apply");
-	argv_array_push(&cp.args, "--index");
+
+	if (index_file)
+		argv_array_push(&cp.args, "--cached");
+	else
+		argv_array_push(&cp.args, "--index");
+
 	argv_array_push(&cp.args, am_path(state, "patch"));
 
 	if (run_command(&cp))
@@ -813,8 +842,106 @@ static int run_apply(const struct am_state *state)
 
 	/* Reload index as git-apply will have modified it. */
 	discard_cache();
+	read_cache_from(index_file ? index_file : get_index_file());
+
+	return 0;
+}
+
+/**
+ * Builds a index that contains just the blobs needed for a 3way merge.
+ */
+static int build_fake_ancestor(const struct am_state *state, const char *index_file)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	cp.git_cmd = 1;
+	argv_array_push(&cp.args, "apply");
+	argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
+	argv_array_push(&cp.args, am_path(state, "patch"));
+
+	if (run_command(&cp))
+		return -1;
+
+	return 0;
+}
+
+/**
+ * Attempt a threeway merge, using index_path as the temporary index.
+ */
+static int fall_back_threeway(const struct am_state *state, const char *index_path)
+{
+	unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ],
+		      our_tree[GIT_SHA1_RAWSZ];
+	const unsigned char *bases[1] = {orig_tree};
+	struct merge_options o;
+	struct commit *result;
+	char *his_tree_name;
+
+	if (get_sha1("HEAD", our_tree) < 0)
+		hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
+
+	if (build_fake_ancestor(state, index_path))
+		return error("could not build fake ancestor");
+
+	discard_cache();
+	read_cache_from(index_path);
+
+	if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
+		return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
+
+	say(state, stdout, _("Using index info to reconstruct a base tree..."));
+
+	if (!state->quiet) {
+		/*
+		 * List paths that needed 3-way fallback, so that the user can
+		 * review them with extra care to spot mismerges.
+		 */
+		struct rev_info rev_info;
+		const char *diff_filter_str = "--diff-filter=AM";
+
+		init_revisions(&rev_info, NULL);
+		rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
+		diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1);
+		add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
+		diff_setup_done(&rev_info.diffopt);
+		run_diff_index(&rev_info, 1);
+	}
+
+	if (run_apply(state, index_path))
+		return error(_("Did you hand edit your patch?\n"
+				"It does not apply to blobs recorded in its index."));
+
+	if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL))
+		return error("could not write tree");
+
+	say(state, stdout, _("Falling back to patching base and 3-way merge..."));
+
+	discard_cache();
 	read_cache();
 
+	/*
+	 * This is not so wrong. Depending on which base we picked, orig_tree
+	 * may be wildly different from ours, but his_tree has the same set of
+	 * wildly different changes in parts the patch did not touch, so
+	 * recursive ends up canceling them, saying that we reverted all those
+	 * changes.
+	 */
+
+	init_merge_options(&o);
+
+	o.branch1 = "HEAD";
+	his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
+	o.branch2 = his_tree_name;
+
+	if (state->quiet)
+		o.verbosity = 0;
+
+	if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) {
+		free(his_tree_name);
+		return error(_("Failed to merge in the changes."));
+	}
+
+	free(his_tree_name);
 	return 0;
 }
 
@@ -883,6 +1010,7 @@ static void am_run(struct am_state *state)
 
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
+		int apply_status;
 
 		if (!file_exists(mail))
 			goto next;
@@ -895,7 +1023,26 @@ static void am_run(struct am_state *state)
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
-		if (run_apply(state) < 0) {
+		apply_status = run_apply(state, NULL);
+
+		if (apply_status && state->threeway) {
+			struct strbuf sb = STRBUF_INIT;
+
+			strbuf_addstr(&sb, am_path(state, "patch-merge-index"));
+			apply_status = fall_back_threeway(state, sb.buf);
+			strbuf_release(&sb);
+
+			/*
+			 * Applying the patch to an earlier tree and merging
+			 * the result may have produced the same tree as ours.
+			 */
+			if (!apply_status && !index_has_changes(NULL)) {
+				say(state, stdout, _("No changes -- Patch already applied."));
+				goto next;
+			}
+		}
+
+		if (apply_status) {
 			int advice_amworkdir = 1;
 
 			printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
@@ -1167,6 +1314,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 	};
 
 	struct option options[] = {
+		OPT_BOOL('3', "3way", &state.threeway,
+			N_("allow fall back on 3way merging if needed")),
 		OPT__QUIET(&state.quiet, N_("be quiet")),
 		OPT_BOOL('s', "signoff", &state.append_signoff,
 			N_("add a Signed-off-by line to the commit message")),
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 20/44] builtin-am: implement --rebasing mode
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (18 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 19/44] builtin-am: implement --3way, am.threeway Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 21/44] builtin-am: bypass git-mailinfo when --rebasing Paul Tan
                   ` (24 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

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

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

diff --git a/builtin/am.c b/builtin/am.c
index e870a1b..ca8f07b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -92,6 +92,8 @@ struct am_state {
 
 	/* override error message when patch failure occurs */
 	const char *resolvemsg;
+
+	int rebasing;
 };
 
 /**
@@ -392,6 +394,8 @@ static void am_load(struct am_state *state)
 	read_state_file(&sb, state, "sign", 1);
 	state->append_signoff = !strcmp(sb.buf, "t");
 
+	state->rebasing = !!file_exists(am_path(state, "rebasing"));
+
 	strbuf_release(&sb);
 }
 
@@ -570,18 +574,29 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 		die(_("Failed to split patches."));
 	}
 
+	if (state->rebasing)
+		state->threeway = 1;
+
 	write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f");
 
 	write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
 
 	write_file(am_path(state, "sign"), 1, state->append_signoff ? "t" : "f");
 
+	if (state->rebasing)
+		write_file(am_path(state, "rebasing"), 1, "%s", "");
+	else
+		write_file(am_path(state, "applying"), 1, "%s", "");
+
 	if (!get_sha1("HEAD", curr_head)) {
 		write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head));
-		update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+		if (!state->rebasing)
+			update_ref("am", "ORIG_HEAD", curr_head, NULL, 0,
+					UPDATE_REFS_DIE_ON_ERR);
 	} else {
 		write_file(am_path(state, "abort-safety"), 1, "%s", "");
-		delete_ref("ORIG_HEAD", NULL, 0);
+		if (!state->rebasing)
+			delete_ref("ORIG_HEAD", NULL, 0);
 	}
 
 	/*
@@ -1063,8 +1078,14 @@ next:
 		am_next(state);
 	}
 
-	am_destroy(state);
-	run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+	/*
+	 * In rebasing mode, it's up to the caller to take care of
+	 * housekeeping.
+	 */
+	if (!state->rebasing) {
+		am_destroy(state);
+		run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+	}
 }
 
 /**
@@ -1336,6 +1357,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "abort", &resume,
 			N_("restore the original branch and abort the patching operation."),
 			RESUME_ABORT),
+		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
+			N_("(internal use for git-rebase)")),
 		OPT_END()
 	};
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 21/44] builtin-am: bypass git-mailinfo when --rebasing
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (19 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 20/44] builtin-am: implement --rebasing mode Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 22/44] builtin-am: handle stray state directory Paul Tan
                   ` (23 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

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

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

diff --git a/builtin/am.c b/builtin/am.c
index ca8f07b..9af8ad2 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -21,6 +21,8 @@
 #include "sequencer.h"
 #include "revision.h"
 #include "merge-recursive.h"
+#include "revision.h"
+#include "log-tree.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -822,6 +824,129 @@ finish:
 }
 
 /**
+ * Sets commit_id to the commit hash where the mail was generated from.
+ * Returns 0 on success, -1 on failure.
+ */
+static int get_mail_commit_sha1(unsigned char *commit_id, const char *mail)
+{
+	struct strbuf sb = STRBUF_INIT;
+	FILE *fp = xfopen(mail, "r");
+	const char *x;
+
+	if (strbuf_getline(&sb, fp, '\n'))
+		return -1;
+
+	if (!skip_prefix(sb.buf, "From ", &x))
+		return -1;
+
+	if (get_sha1_hex(x, commit_id) < 0)
+		return -1;
+
+	strbuf_release(&sb);
+	fclose(fp);
+	return 0;
+}
+
+/**
+ * Sets state->msg, state->author_name, state->author_email, state->author_date
+ * to the commit's respective info.
+ */
+static void get_commit_info(struct am_state *state, struct commit *commit)
+{
+	const char *buffer, *ident_line, *author_date, *msg;
+	size_t ident_len;
+	struct ident_split ident_split;
+	struct strbuf sb = STRBUF_INIT;
+
+	buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());
+
+	ident_line = find_commit_header(buffer, "author", &ident_len);
+
+	if (split_ident_line(&ident_split, ident_line, ident_len) < 0) {
+		strbuf_add(&sb, ident_line, ident_len);
+		die(_("invalid ident line: %s"), sb.buf);
+	}
+
+	assert(!state->author_name);
+	if (ident_split.name_begin) {
+		strbuf_add(&sb, ident_split.name_begin,
+			ident_split.name_end - ident_split.name_begin);
+		state->author_name = strbuf_detach(&sb, NULL);
+	} else
+		state->author_name = xstrdup("");
+
+	assert(!state->author_email);
+	if (ident_split.mail_begin) {
+		strbuf_add(&sb, ident_split.mail_begin,
+			ident_split.mail_end - ident_split.mail_begin);
+		state->author_email = strbuf_detach(&sb, NULL);
+	} else
+		state->author_email = xstrdup("");
+
+	author_date = show_ident_date(&ident_split, DATE_NORMAL);
+	strbuf_addstr(&sb, author_date);
+	assert(!state->author_date);
+	state->author_date = strbuf_detach(&sb, NULL);
+
+	assert(!state->msg);
+	msg = strstr(buffer, "\n\n");
+	if (!msg)
+		die(_("unable to parse commit %s"), sha1_to_hex(commit->object.sha1));
+	state->msg = xstrdup(msg + 2);
+	state->msg_len = strlen(state->msg);
+}
+
+/**
+ * Writes `commit` as a patch to the state directory's "patch" file.
+ */
+static void write_commit_patch(const struct am_state *state, struct commit *commit)
+{
+	struct rev_info rev_info;
+	FILE *fp;
+
+	fp = xfopen(am_path(state, "patch"), "w");
+	init_revisions(&rev_info, NULL);
+	rev_info.diff = 1;
+	rev_info.abbrev = 0;
+	rev_info.disable_stdin = 1;
+	rev_info.show_root_diff = 1;
+	rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
+	rev_info.no_commit_id = 1;
+	DIFF_OPT_SET(&rev_info.diffopt, BINARY);
+	DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX);
+	rev_info.diffopt.use_color = 0;
+	rev_info.diffopt.file = fp;
+	rev_info.diffopt.close_file = 1;
+	add_pending_object(&rev_info, &commit->object, "");
+	diff_setup_done(&rev_info.diffopt);
+	log_tree_commit(&rev_info, commit);
+}
+
+/**
+ * Like parse_mail(), but parses the mail by looking up its commit ID
+ * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
+ * of patches.
+ *
+ * Will always return 0 as the patch should never be skipped.
+ */
+static int parse_mail_rebase(struct am_state *state, const char *mail)
+{
+	struct commit *commit;
+	unsigned char commit_sha1[GIT_SHA1_RAWSZ];
+
+	if (get_mail_commit_sha1(commit_sha1, mail) < 0)
+		die(_("could not parse %s"), mail);
+
+	commit = lookup_commit_or_die(commit_sha1, mail);
+
+	get_commit_info(state, commit);
+
+	write_commit_patch(state, commit);
+
+	return 0;
+}
+
+/**
  * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If
  * `index_file` is not NULL, the patch will be applied to that index.
  */
@@ -1025,12 +1150,17 @@ static void am_run(struct am_state *state)
 
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
-		int apply_status;
+		int apply_status, skip;
 
 		if (!file_exists(mail))
 			goto next;
 
-		if (parse_mail(state, mail))
+		if (state->rebasing)
+			skip = parse_mail_rebase(state, mail);
+		else
+			skip = parse_mail(state, mail);
+
+		if (skip)
 			goto next; /* mail should be skipped */
 
 		write_author_script(state);
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 22/44] builtin-am: handle stray state directory
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (20 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 21/44] builtin-am: bypass git-mailinfo when --rebasing Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 23/44] builtin-am: implement -u/--utf8 Paul Tan
                   ` (22 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 9af8ad2..49a6840 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1537,6 +1537,23 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		struct argv_array paths = ARGV_ARRAY_INIT;
 		int i;
 
+		/*
+		 * Handle stray state directory in the independent-run case. In
+		 * the --rebasing case, it is up to the caller to take care of
+		 * stray directories.
+		 */
+		if (file_exists(state.dir) && !state.rebasing) {
+			if (resume == RESUME_ABORT) {
+				am_destroy(&state);
+				am_state_release(&state);
+				return 0;
+			}
+
+			die(_("Stray %s directory found.\n"
+				"Use \"git am --abort\" to remove it."),
+				state.dir);
+		}
+
 		if (resume)
 			die(_("Resolve operation not in progress, we are not resuming."));
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 23/44] builtin-am: implement -u/--utf8
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (21 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 22/44] builtin-am: handle stray state directory Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 24/44] builtin-am: implement -k/--keep, --keep-non-patch Paul Tan
                   ` (21 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 49a6840..bc44225 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -92,6 +92,8 @@ struct am_state {
 
 	int append_signoff;
 
+	int utf8;
+
 	/* override error message when patch failure occurs */
 	const char *resolvemsg;
 
@@ -118,6 +120,8 @@ static void am_state_init(struct am_state *state, const char *dir)
 	quiet = getenv("GIT_QUIET");
 	if (quiet && *quiet)
 		state->quiet = 1;
+
+	state->utf8 = 1;
 }
 
 /**
@@ -396,6 +400,9 @@ static void am_load(struct am_state *state)
 	read_state_file(&sb, state, "sign", 1);
 	state->append_signoff = !strcmp(sb.buf, "t");
 
+	read_state_file(&sb, state, "utf8", 1);
+	state->utf8 = !strcmp(sb.buf, "t");
+
 	state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
 	strbuf_release(&sb);
@@ -585,6 +592,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 
 	write_file(am_path(state, "sign"), 1, state->append_signoff ? "t" : "f");
 
+	write_file(am_path(state, "utf8"), 1, state->utf8 ? "t" : "f");
+
 	if (state->rebasing)
 		write_file(am_path(state, "rebasing"), 1, "%s", "");
 	else
@@ -756,6 +765,7 @@ static int parse_mail(struct am_state *state, const char *mail)
 	cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777);
 
 	argv_array_push(&cp.args, "mailinfo");
+	argv_array_push(&cp.args, state->utf8 ? "-u" : "-n");
 	argv_array_push(&cp.args, am_path(state, "msg"));
 	argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1470,6 +1480,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		OPT__QUIET(&state.quiet, N_("be quiet")),
 		OPT_BOOL('s', "signoff", &state.append_signoff,
 			N_("add a Signed-off-by line to the commit message")),
+		OPT_BOOL('u', "utf8", &state.utf8,
+			N_("recode into utf8 (default)")),
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 24/44] builtin-am: implement -k/--keep, --keep-non-patch
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (22 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 23/44] builtin-am: implement -u/--utf8 Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 25/44] builtin-am: implement --[no-]message-id, am.messageid Paul Tan
                   ` (20 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index bc44225..3410601 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -68,6 +68,12 @@ enum patch_format {
 	PATCH_FORMAT_MBOX
 };
 
+enum keep_type {
+	KEEP_FALSE = 0,
+	KEEP_TRUE,      /* pass -k flag to git-mailinfo */
+	KEEP_NON_PATCH  /* pass -b flag to git-mailinfo */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -94,6 +100,9 @@ struct am_state {
 
 	int utf8;
 
+	/* one of the enum keep_type values */
+	int keep;
+
 	/* override error message when patch failure occurs */
 	const char *resolvemsg;
 
@@ -403,6 +412,14 @@ static void am_load(struct am_state *state)
 	read_state_file(&sb, state, "utf8", 1);
 	state->utf8 = !strcmp(sb.buf, "t");
 
+	read_state_file(&sb, state, "keep", 1);
+	if (!strcmp(sb.buf, "t"))
+		state->keep = KEEP_TRUE;
+	else if (!strcmp(sb.buf, "b"))
+		state->keep = KEEP_NON_PATCH;
+	else
+		state->keep = KEEP_FALSE;
+
 	state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
 	strbuf_release(&sb);
@@ -566,6 +583,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 			const char **paths)
 {
 	unsigned char curr_head[GIT_SHA1_RAWSZ];
+	const char *str;
 
 	if (!patch_format)
 		patch_format = detect_patch_format(paths);
@@ -594,6 +612,22 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 
 	write_file(am_path(state, "utf8"), 1, state->utf8 ? "t" : "f");
 
+	switch (state->keep) {
+	case KEEP_FALSE:
+		str = "f";
+		break;
+	case KEEP_TRUE:
+		str = "t";
+		break;
+	case KEEP_NON_PATCH:
+		str = "b";
+		break;
+	default:
+		die("BUG: invalid value for state->keep");
+	}
+
+	write_file(am_path(state, "keep"), 1, "%s", str);
+
 	if (state->rebasing)
 		write_file(am_path(state, "rebasing"), 1, "%s", "");
 	else
@@ -766,6 +800,20 @@ static int parse_mail(struct am_state *state, const char *mail)
 
 	argv_array_push(&cp.args, "mailinfo");
 	argv_array_push(&cp.args, state->utf8 ? "-u" : "-n");
+
+	switch (state->keep) {
+	case KEEP_FALSE:
+		break;
+	case KEEP_TRUE:
+		argv_array_push(&cp.args, "-k");
+		break;
+	case KEEP_NON_PATCH:
+		argv_array_push(&cp.args, "-b");
+		break;
+	default:
+		die("BUG: invalid value for state->keep");
+	}
+
 	argv_array_push(&cp.args, am_path(state, "msg"));
 	argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1482,6 +1530,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 			N_("add a Signed-off-by line to the commit message")),
 		OPT_BOOL('u', "utf8", &state.utf8,
 			N_("recode into utf8 (default)")),
+		OPT_SET_INT('k', "keep", &state.keep,
+			N_("pass -k flag to git-mailinfo"), KEEP_TRUE),
+		OPT_SET_INT(0, "keep-non-patch", &state.keep,
+			N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH),
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 25/44] builtin-am: implement --[no-]message-id, am.messageid
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (23 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 24/44] builtin-am: implement -k/--keep, --keep-non-patch Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 26/44] builtin-am: support --keep-cr, am.keepcr Paul Tan
                   ` (19 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    No test for am.messageid

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

diff --git a/builtin/am.c b/builtin/am.c
index 3410601..52e5b5a 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -103,6 +103,9 @@ struct am_state {
 	/* one of the enum keep_type values */
 	int keep;
 
+	/* pass -m flag to git-mailinfo */
+	int message_id;
+
 	/* override error message when patch failure occurs */
 	const char *resolvemsg;
 
@@ -131,6 +134,8 @@ static void am_state_init(struct am_state *state, const char *dir)
 		state->quiet = 1;
 
 	state->utf8 = 1;
+
+	git_config_get_bool("am.messageid", &state->message_id);
 }
 
 /**
@@ -420,6 +425,9 @@ static void am_load(struct am_state *state)
 	else
 		state->keep = KEEP_FALSE;
 
+	read_state_file(&sb, state, "messageid", 1);
+	state->message_id = !strcmp(sb.buf, "t");
+
 	state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
 	strbuf_release(&sb);
@@ -628,6 +636,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 
 	write_file(am_path(state, "keep"), 1, "%s", str);
 
+	write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : "f");
+
 	if (state->rebasing)
 		write_file(am_path(state, "rebasing"), 1, "%s", "");
 	else
@@ -814,6 +824,9 @@ static int parse_mail(struct am_state *state, const char *mail)
 		die("BUG: invalid value for state->keep");
 	}
 
+	if (state->message_id)
+		argv_array_push(&cp.args, "-m");
+
 	argv_array_push(&cp.args, am_path(state, "msg"));
 	argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1534,6 +1547,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 			N_("pass -k flag to git-mailinfo"), KEEP_TRUE),
 		OPT_SET_INT(0, "keep-non-patch", &state.keep,
 			N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH),
+		OPT_BOOL('m', "message-id", &state.message_id,
+			N_("pass -m flag to git-mailinfo")),
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 26/44] builtin-am: support --keep-cr, am.keepcr
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (24 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 25/44] builtin-am: implement --[no-]message-id, am.messageid Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 27/44] builtin-am: implement --[no-]scissors Paul Tan
                   ` (18 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

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

diff --git a/builtin/am.c b/builtin/am.c
index 52e5b5a..2387726 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -536,7 +536,7 @@ done:
  * Splits out individual email patches from `paths`, where each path is either
  * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
  */
-static int split_mail_mbox(struct am_state *state, const char **paths)
+static int split_mail_mbox(struct am_state *state, const char **paths, int keep_cr)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
 	struct strbuf last = STRBUF_INIT;
@@ -546,6 +546,8 @@ static int split_mail_mbox(struct am_state *state, const char **paths)
 	argv_array_pushf(&cp.args, "-d%d", state->prec);
 	argv_array_pushf(&cp.args, "-o%s", state->dir);
 	argv_array_push(&cp.args, "-b");
+	if (keep_cr)
+		argv_array_push(&cp.args, "--keep-cr");
 	argv_array_push(&cp.args, "--");
 	argv_array_pushv(&cp.args, paths);
 
@@ -570,14 +572,22 @@ static int split_mail_mbox(struct am_state *state, const char **paths)
  * state->cur will be set to the index of the first mail, and state->last will
  * be set to the index of the last mail.
  *
+ * Set keep_cr to 0 to convert all lines ending with \r\n to end with \n, 1
+ * to disable this behavior, -1 to use the default configured setting.
+ *
  * Returns 0 on success, -1 on failure.
  */
 static int split_mail(struct am_state *state, enum patch_format patch_format,
-			const char **paths)
+			const char **paths, int keep_cr)
 {
+	if (keep_cr < 0) {
+		keep_cr = 0;
+		git_config_get_bool("am.keepcr", &keep_cr);
+	}
+
 	switch (patch_format) {
 	case PATCH_FORMAT_MBOX:
-		return split_mail_mbox(state, paths);
+		return split_mail_mbox(state, paths, keep_cr);
 	default:
 		die("BUG: invalid patch_format");
 	}
@@ -588,7 +598,7 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
  * Setup a new am session for applying patches
  */
 static void am_setup(struct am_state *state, enum patch_format patch_format,
-			const char **paths)
+			const char **paths, int keep_cr)
 {
 	unsigned char curr_head[GIT_SHA1_RAWSZ];
 	const char *str;
@@ -604,7 +614,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 	if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
 		die_errno(_("failed to create directory '%s'"), state->dir);
 
-	if (split_mail(state, patch_format, paths) < 0) {
+	if (split_mail(state, patch_format, paths, keep_cr) < 0) {
 		am_destroy(state);
 		die(_("Failed to split patches."));
 	}
@@ -1526,6 +1536,7 @@ enum resume_mode {
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
 	struct am_state state;
+	int keep_cr = -1;
 	int patch_format = PATCH_FORMAT_UNKNOWN;
 	enum resume_mode resume = RESUME_FALSE;
 
@@ -1549,6 +1560,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 			N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH),
 		OPT_BOOL('m', "message-id", &state.message_id,
 			N_("pass -m flag to git-mailinfo")),
+		{ OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL,
+		  N_("pass --keep-cr flag to git-mailsplit for mbox format"),
+		  PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
+		{ OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL,
+		  N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
+		  PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
@@ -1643,7 +1660,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 				argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
 		}
 
-		am_setup(&state, patch_format, paths.argv);
+		am_setup(&state, patch_format, paths.argv, keep_cr);
 
 		argv_array_clear(&paths);
 	}
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 27/44] builtin-am: implement --[no-]scissors
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (25 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 26/44] builtin-am: support --keep-cr, am.keepcr Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-07-07  8:23   ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 28/44] builtin-am: pass git-apply's options to git-apply Paul Tan
                   ` (17 subsequent siblings)
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    There are tests for mailinfo --scissors, but not am --scissors, or
    mailinfo.scissors.

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

diff --git a/builtin/am.c b/builtin/am.c
index 2387726..55989e5 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -74,6 +74,12 @@ enum keep_type {
 	KEEP_NON_PATCH  /* pass -b flag to git-mailinfo */
 };
 
+enum scissors_type {
+	SCISSORS_UNSET = -1,
+	SCISSORS_TRUE,  /* pass --scissors to git-mailinfo */
+	SCISSORS_FALSE  /* pass --no-scissors to git-mailinfo */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -106,6 +112,9 @@ struct am_state {
 	/* pass -m flag to git-mailinfo */
 	int message_id;
 
+	/* one of the enum scissors_type values */
+	int scissors;
+
 	/* override error message when patch failure occurs */
 	const char *resolvemsg;
 
@@ -136,6 +145,8 @@ static void am_state_init(struct am_state *state, const char *dir)
 	state->utf8 = 1;
 
 	git_config_get_bool("am.messageid", &state->message_id);
+
+	state->scissors = SCISSORS_UNSET;
 }
 
 /**
@@ -428,6 +439,14 @@ static void am_load(struct am_state *state)
 	read_state_file(&sb, state, "messageid", 1);
 	state->message_id = !strcmp(sb.buf, "t");
 
+	read_state_file(&sb, state, "scissors", 1);
+	if (!strcmp(sb.buf, "t"))
+		state->scissors = SCISSORS_TRUE;
+	else if (!strcmp(sb.buf, "f"))
+		state->scissors = SCISSORS_FALSE;
+	else
+		state->scissors = SCISSORS_UNSET;
+
 	state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
 	strbuf_release(&sb);
@@ -648,6 +667,22 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 
 	write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : "f");
 
+	switch (state->scissors) {
+	case SCISSORS_UNSET:
+		str = "";
+		break;
+	case SCISSORS_FALSE:
+		str = "f";
+		break;
+	case SCISSORS_TRUE:
+		str = "t";
+		break;
+	default:
+		die("BUG: invalid value for state->scissors");
+	}
+
+	write_file(am_path(state, "scissors"), 1, "%s", str);
+
 	if (state->rebasing)
 		write_file(am_path(state, "rebasing"), 1, "%s", "");
 	else
@@ -837,6 +872,19 @@ static int parse_mail(struct am_state *state, const char *mail)
 	if (state->message_id)
 		argv_array_push(&cp.args, "-m");
 
+	switch (state->scissors) {
+	case SCISSORS_UNSET:
+		break;
+	case SCISSORS_FALSE:
+		argv_array_push(&cp.args, "--no-scissors");
+		break;
+	case SCISSORS_TRUE:
+		argv_array_push(&cp.args, "--scissors");
+		break;
+	default:
+		die("BUG: invalid value for state->scissors");
+	}
+
 	argv_array_push(&cp.args, am_path(state, "msg"));
 	argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1566,6 +1614,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL,
 		  N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
 		  PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+		OPT_BOOL('c', "scissors", &state.scissors,
+			N_("strip everything before a scissors line")),
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 28/44] builtin-am: pass git-apply's options to git-apply
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (26 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 27/44] builtin-am: implement --[no-]scissors Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-29 23:56   ` Stefan Beller
  2015-06-28 14:05 ` [PATCH v4 29/44] builtin-am: implement --ignore-date Paul Tan
                   ` (16 subsequent siblings)
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

git-am.sh recognizes some of git-apply's options, and would pass them to
git-apply:

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 55989e5..5aab627 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -115,6 +115,8 @@ struct am_state {
 	/* one of the enum scissors_type values */
 	int scissors;
 
+	struct argv_array git_apply_opts;
+
 	/* override error message when patch failure occurs */
 	const char *resolvemsg;
 
@@ -147,6 +149,8 @@ static void am_state_init(struct am_state *state, const char *dir)
 	git_config_get_bool("am.messageid", &state->message_id);
 
 	state->scissors = SCISSORS_UNSET;
+
+	argv_array_init(&state->git_apply_opts);
 }
 
 /**
@@ -168,6 +172,8 @@ static void am_state_release(struct am_state *state)
 
 	if (state->msg)
 		free(state->msg);
+
+	argv_array_clear(&state->git_apply_opts);
 }
 
 /**
@@ -447,6 +453,11 @@ static void am_load(struct am_state *state)
 	else
 		state->scissors = SCISSORS_UNSET;
 
+	read_state_file(&sb, state, "apply-opt", 1);
+	argv_array_clear(&state->git_apply_opts);
+	if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0)
+		die(_("could not parse %s"), am_path(state, "apply-opt"));
+
 	state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
 	strbuf_release(&sb);
@@ -621,6 +632,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 {
 	unsigned char curr_head[GIT_SHA1_RAWSZ];
 	const char *str;
+	struct strbuf sb = STRBUF_INIT;
 
 	if (!patch_format)
 		patch_format = detect_patch_format(paths);
@@ -683,6 +695,9 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 
 	write_file(am_path(state, "scissors"), 1, "%s", str);
 
+	sq_quote_argv(&sb, state->git_apply_opts.argv, 0);
+	write_file(am_path(state, "apply-opt"), 1, "%s", sb.buf);
+
 	if (state->rebasing)
 		write_file(am_path(state, "rebasing"), 1, "%s", "");
 	else
@@ -707,6 +722,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 	write_file(am_path(state, "next"), 1, "%d", state->cur);
 
 	write_file(am_path(state, "last"), 1, "%d", state->last);
+
+	strbuf_release(&sb);
 }
 
 /**
@@ -1099,6 +1116,8 @@ static int run_apply(const struct am_state *state, const char *index_file)
 
 	argv_array_push(&cp.args, "apply");
 
+	argv_array_pushv(&cp.args, state->git_apply_opts.argv);
+
 	if (index_file)
 		argv_array_push(&cp.args, "--cached");
 	else
@@ -1125,6 +1144,7 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
 
 	cp.git_cmd = 1;
 	argv_array_push(&cp.args, "apply");
+	argv_array_pushv(&cp.args, state->git_apply_opts.argv);
 	argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
 	argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1616,9 +1636,36 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		  PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
 		OPT_BOOL('c', "scissors", &state.scissors,
 			N_("strip everything before a scissors line")),
+		OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
+			N_("pass it through git-apply"),
+			0),
+		OPT_PASSTHRU_ARGV(0, "ignore-space-change", &state.git_apply_opts, NULL,
+			N_("pass it through git-apply"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &state.git_apply_opts, NULL,
+			N_("pass it through git-apply"),
+			PARSE_OPT_NOARG),
+		OPT_PASSTHRU_ARGV(0, "directory", &state.git_apply_opts, N_("root"),
+			N_("pass it through git-apply"),
+			0),
+		OPT_PASSTHRU_ARGV(0, "exclude", &state.git_apply_opts, N_("path"),
+			N_("pass it through git-apply"),
+			0),
+		OPT_PASSTHRU_ARGV(0, "include", &state.git_apply_opts, N_("path"),
+			N_("pass it through git-apply"),
+			0),
+		OPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_("n"),
+			N_("pass it through git-apply"),
+			0),
+		OPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_("num"),
+			N_("pass it through git-apply"),
+			0),
 		OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 			N_("format the patch(es) are in"),
 			parse_opt_patchformat),
+		OPT_PASSTHRU_ARGV(0, "reject", &state.git_apply_opts, NULL,
+			N_("pass it through git-apply"),
+			PARSE_OPT_NOARG),
 		OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
 			N_("override error message when patch failure occurs")),
 		OPT_CMDMODE(0, "continue", &resume,
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 29/44] builtin-am: implement --ignore-date
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (27 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 28/44] builtin-am: pass git-apply's options to git-apply Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 30/44] builtin-am: implement --committer-date-is-author-date Paul Tan
                   ` (15 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 5aab627..d4164de 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -120,6 +120,8 @@ struct am_state {
 	/* override error message when patch failure occurs */
 	const char *resolvemsg;
 
+	int ignore_date;
+
 	int rebasing;
 };
 
@@ -1260,7 +1262,8 @@ static void do_commit(const struct am_state *state)
 	}
 
 	author = fmt_ident(state->author_name, state->author_email,
-			state->author_date, IDENT_STRICT);
+			state->ignore_date ? NULL : state->author_date,
+			IDENT_STRICT);
 
 	if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
 				author, NULL))
@@ -1680,6 +1683,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "abort", &resume,
 			N_("restore the original branch and abort the patching operation."),
 			RESUME_ABORT),
+		OPT_BOOL(0, "ignore-date", &state.ignore_date,
+			N_("use current timestamp for author date")),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 30/44] builtin-am: implement --committer-date-is-author-date
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (28 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 29/44] builtin-am: implement --ignore-date Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 31/44] builtin-am: implement -S/--gpg-sign, commit.gpgsign Paul Tan
                   ` (14 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index d4164de..80850e8 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -120,6 +120,8 @@ struct am_state {
 	/* override error message when patch failure occurs */
 	const char *resolvemsg;
 
+	int committer_date_is_author_date;
+
 	int ignore_date;
 
 	int rebasing;
@@ -1265,6 +1267,10 @@ static void do_commit(const struct am_state *state)
 			state->ignore_date ? NULL : state->author_date,
 			IDENT_STRICT);
 
+	if (state->committer_date_is_author_date)
+		setenv("GIT_COMMITTER_DATE",
+			state->ignore_date ? "" : state->author_date, 1);
+
 	if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
 				author, NULL))
 		die(_("failed to write commit object"));
@@ -1683,6 +1689,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "abort", &resume,
 			N_("restore the original branch and abort the patching operation."),
 			RESUME_ABORT),
+		OPT_BOOL(0, "committer-date-is-author-date",
+			&state.committer_date_is_author_date,
+			N_("lie about committer date")),
 		OPT_BOOL(0, "ignore-date", &state.ignore_date,
 			N_("use current timestamp for author date")),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 31/44] builtin-am: implement -S/--gpg-sign, commit.gpgsign
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (29 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 30/44] builtin-am: implement --committer-date-is-author-date Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-29 23:51   ` Stefan Beller
  2015-06-28 14:05 ` [PATCH v4 32/44] builtin-am: invoke post-rewrite hook Paul Tan
                   ` (13 subsequent siblings)
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 80850e8..d44f5e2 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -124,6 +124,8 @@ struct am_state {
 
 	int ignore_date;
 
+	const char *sign_commit;
+
 	int rebasing;
 };
 
@@ -134,6 +136,7 @@ struct am_state {
 static void am_state_init(struct am_state *state, const char *dir)
 {
 	const char *quiet;
+	int sign_commit;
 
 	memset(state, 0, sizeof(*state));
 
@@ -155,6 +158,9 @@ static void am_state_init(struct am_state *state, const char *dir)
 	state->scissors = SCISSORS_UNSET;
 
 	argv_array_init(&state->git_apply_opts);
+
+	if (!git_config_get_bool("commit.gpgsign", &sign_commit))
+		state->sign_commit = sign_commit ? "" : NULL;
 }
 
 /**
@@ -1272,7 +1278,7 @@ static void do_commit(const struct am_state *state)
 			state->ignore_date ? "" : state->author_date, 1);
 
 	if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
-				author, NULL))
+				author, state->sign_commit))
 		die(_("failed to write commit object"));
 
 	reflog_msg = getenv("GIT_REFLOG_ACTION");
@@ -1694,6 +1700,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 			N_("lie about committer date")),
 		OPT_BOOL(0, "ignore-date", &state.ignore_date,
 			N_("use current timestamp for author date")),
+		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
+		  N_("GPG-sign commits"),
+		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 32/44] builtin-am: invoke post-rewrite hook
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (30 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 31/44] builtin-am: implement -S/--gpg-sign, commit.gpgsign Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 33/44] builtin-am: support automatic notes copying Paul Tan
                   ` (12 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index d44f5e2..27b79f7 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -95,6 +95,9 @@ struct am_state {
 	char *msg;
 	size_t msg_len;
 
+	/* when --rebasing, records the original commit the patch came from */
+	unsigned char orig_commit[GIT_SHA1_RAWSZ];
+
 	/* number of digits in patch filename */
 	int prec;
 
@@ -432,6 +435,11 @@ static void am_load(struct am_state *state)
 
 	read_commit_msg(state);
 
+	if (read_state_file(&sb, state, "original-commit", 1) < 0)
+		hashclr(state->orig_commit);
+	else if (get_sha1_hex(sb.buf, state->orig_commit) < 0)
+		die(_("could not parse %s"), am_path(state, "original-commit"));
+
 	read_state_file(&sb, state, "threeway", 1);
 	state->threeway = !strcmp(sb.buf, "t");
 
@@ -487,6 +495,30 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * Runs post-rewrite hook. Returns it exit code.
+ */
+static int run_post_rewrite_hook(const struct am_state *state)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	const char *hook = find_hook("post-rewrite");
+	int ret;
+
+	if (!hook)
+		return 0;
+
+	argv_array_push(&cp.args, hook);
+	argv_array_push(&cp.args, "rebase");
+
+	cp.in = xopen(am_path(state, "rewritten"), O_RDONLY);
+	cp.stdout_to_stderr = 1;
+
+	ret = run_command(&cp);
+
+	close(cp.in);
+	return ret;
+}
+
+/**
  * Determines if the file looks like a piece of RFC2822 mail by grabbing all
  * non-indented lines and checking if they look like they begin with valid
  * header field names.
@@ -764,6 +796,9 @@ static void am_next(struct am_state *state)
 	unlink(am_path(state, "author-script"));
 	unlink(am_path(state, "final-commit"));
 
+	hashclr(state->orig_commit);
+	unlink(am_path(state, "original-commit"));
+
 	if (!get_sha1("HEAD", head))
 		write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head));
 	else
@@ -1083,6 +1118,8 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm
  * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
  * of patches.
  *
+ * state->orig_commit will be set to the original commit ID.
+ *
  * Will always return 0 as the patch should never be skipped.
  */
 static int parse_mail_rebase(struct am_state *state, const char *mail)
@@ -1099,6 +1136,10 @@ static int parse_mail_rebase(struct am_state *state, const char *mail)
 
 	write_commit_patch(state, commit);
 
+	hashcpy(state->orig_commit, commit_sha1);
+	write_file(am_path(state, "original-commit"), 1, "%s",
+			sha1_to_hex(commit_sha1));
+
 	return 0;
 }
 
@@ -1290,6 +1331,15 @@ static void do_commit(const struct am_state *state)
 
 	update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR);
 
+	if (state->rebasing) {
+		FILE *fp = xfopen(am_path(state, "rewritten"), "a");
+
+		assert(!is_null_sha1(state->orig_commit));
+		fprintf(fp, "%s ", sha1_to_hex(state->orig_commit));
+		fprintf(fp, "%s\n", sha1_to_hex(commit));
+		fclose(fp);
+	}
+
 	strbuf_release(&sb);
 }
 
@@ -1372,6 +1422,11 @@ next:
 		am_next(state);
 	}
 
+	if (!is_empty_file(am_path(state, "rewritten"))) {
+		assert(state->rebasing);
+		run_post_rewrite_hook(state);
+	}
+
 	/*
 	 * In rebasing mode, it's up to the caller to take care of
 	 * housekeeping.
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 33/44] builtin-am: support automatic notes copying
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (31 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 32/44] builtin-am: invoke post-rewrite hook Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 34/44] builtin-am: invoke applypatch-msg hook Paul Tan
                   ` (11 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index 27b79f7..f618ff1 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -23,6 +23,7 @@
 #include "merge-recursive.h"
 #include "revision.h"
 #include "log-tree.h"
+#include "notes-utils.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -519,6 +520,64 @@ static int run_post_rewrite_hook(const struct am_state *state)
 }
 
 /**
+ * Reads the state directory's "rewritten" file, and copies notes from the old
+ * commits listed in the file to their rewritten commits.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int copy_notes_for_rebase(const struct am_state *state)
+{
+	struct notes_rewrite_cfg *c;
+	struct strbuf sb = STRBUF_INIT;
+	const char *invalid_line = _("Malformed input line: '%s'.");
+	const char *msg = "Notes added by 'git rebase'";
+	FILE *fp;
+	int ret = 0;
+
+	assert(state->rebasing);
+
+	c = init_copy_notes_for_rewrite("rebase");
+	if (!c)
+		return 0;
+
+	fp = xfopen(am_path(state, "rewritten"), "r");
+
+	while (!strbuf_getline(&sb, fp, '\n')) {
+		unsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ];
+
+		if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) {
+			ret = error(invalid_line, sb.buf);
+			goto finish;
+		}
+
+		if (get_sha1_hex(sb.buf, from_obj)) {
+			ret = error(invalid_line, sb.buf);
+			goto finish;
+		}
+
+		if (sb.buf[GIT_SHA1_HEXSZ] != ' ') {
+			ret = error(invalid_line, sb.buf);
+			goto finish;
+		}
+
+		if (get_sha1_hex(sb.buf + GIT_SHA1_HEXSZ + 1, to_obj)) {
+			ret = error(invalid_line, sb.buf);
+			goto finish;
+		}
+
+		if (copy_note_for_rewrite(c, from_obj, to_obj))
+			ret = error(_("Failed to copy notes from '%s' to '%s'"),
+					sha1_to_hex(from_obj), sha1_to_hex(to_obj));
+	}
+
+finish:
+	finish_copy_notes_for_rewrite(c, msg);
+	fclose(fp);
+	strbuf_release(&sb);
+	return ret;
+}
+
+/**
  * Determines if the file looks like a piece of RFC2822 mail by grabbing all
  * non-indented lines and checking if they look like they begin with valid
  * header field names.
@@ -1424,6 +1483,7 @@ next:
 
 	if (!is_empty_file(am_path(state, "rewritten"))) {
 		assert(state->rebasing);
+		copy_notes_for_rebase(state);
 		run_post_rewrite_hook(state);
 	}
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 34/44] builtin-am: invoke applypatch-msg hook
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (32 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 33/44] builtin-am: support automatic notes copying Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 35/44] builtin-am: invoke pre-applypatch hook Paul Tan
                   ` (10 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    No tests in master.

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

diff --git a/builtin/am.c b/builtin/am.c
index f618ff1..814a8b7 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -496,6 +496,27 @@ static void am_destroy(const struct am_state *state)
 }
 
 /**
+ * Runs applypatch-msg hook. Returns its exit code.
+ */
+static int run_applypatch_msg_hook(struct am_state *state)
+{
+	int ret;
+
+	assert(state->msg);
+	ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+
+	if (!ret) {
+		free(state->msg);
+		state->msg = NULL;
+		if (read_commit_msg(state) < 0)
+			die(_("'%s' was deleted by the applypatch-msg hook"),
+				am_path(state, "final-commit"));
+	}
+
+	return ret;
+}
+
+/**
  * Runs post-rewrite hook. Returns it exit code.
  */
 static int run_post_rewrite_hook(const struct am_state *state)
@@ -1439,6 +1460,9 @@ static void am_run(struct am_state *state)
 		write_author_script(state);
 		write_commit_msg(state);
 
+		if (run_applypatch_msg_hook(state))
+			exit(1);
+
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
 		apply_status = run_apply(state, NULL);
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 35/44] builtin-am: invoke pre-applypatch hook
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (33 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 34/44] builtin-am: invoke applypatch-msg hook Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 36/44] builtin-am: invoke post-applypatch hook Paul Tan
                   ` (9 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    No tests in master

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

diff --git a/builtin/am.c b/builtin/am.c
index 814a8b7..a2b0d63 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1379,6 +1379,9 @@ static void do_commit(const struct am_state *state)
 	const char *reflog_msg, *author;
 	struct strbuf sb = STRBUF_INIT;
 
+	if (run_hook_le(NULL, "pre-applypatch", NULL))
+		exit(1);
+
 	if (write_cache_as_tree(tree, 0, NULL))
 		die(_("git write-tree failed to write a tree"));
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 36/44] builtin-am: invoke post-applypatch hook
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (34 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 35/44] builtin-am: invoke pre-applypatch hook Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:05 ` [PATCH v4 37/44] builtin-am: rerere support Paul Tan
                   ` (8 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    No tests in master.

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

diff --git a/builtin/am.c b/builtin/am.c
index a2b0d63..be85b97 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1423,6 +1423,8 @@ static void do_commit(const struct am_state *state)
 		fclose(fp);
 	}
 
+	run_hook_le(NULL, "post-applypatch", NULL);
+
 	strbuf_release(&sb);
 }
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 37/44] builtin-am: rerere support
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (35 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 36/44] builtin-am: invoke post-applypatch hook Paul Tan
@ 2015-06-28 14:05 ` Paul Tan
  2015-06-28 14:06 ` [PATCH v4 38/44] builtin-am: support and auto-detect StGit patches Paul Tan
                   ` (7 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

git-am.sh will call git-rerere at the following events:

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

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

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

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

Notes:
    No tests in master.

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

diff --git a/builtin/am.c b/builtin/am.c
index be85b97..b54fdbd 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -24,6 +24,7 @@
 #include "revision.h"
 #include "log-tree.h"
 #include "notes-utils.h"
+#include "rerere.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -128,6 +129,8 @@ struct am_state {
 
 	int ignore_date;
 
+	int allow_rerere_autoupdate;
+
 	const char *sign_commit;
 
 	int rebasing;
@@ -1357,6 +1360,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		o.verbosity = 0;
 
 	if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) {
+		rerere(state->allow_rerere_autoupdate);
 		free(his_tree_name);
 		return error(_("Failed to merge in the changes."));
 	}
@@ -1556,6 +1560,8 @@ static void am_resolve(struct am_state *state)
 		die_user_resolve(state);
 	}
 
+	rerere(0);
+
 	do_commit(state);
 
 	am_next(state);
@@ -1654,12 +1660,29 @@ static int clean_index(const unsigned char *head, const unsigned char *remote)
 }
 
 /**
+ * Resets rerere's merge resolution metadata.
+ */
+static void am_rerere_clear(void)
+{
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+	int fd = setup_rerere(&merge_rr, 0);
+
+	if (fd < 0)
+		return;
+
+	rerere_clear(&merge_rr);
+	string_list_clear(&merge_rr, 1);
+}
+
+/**
  * Resume the current am session by skipping the current patch.
  */
 static void am_skip(struct am_state *state)
 {
 	unsigned char head[GIT_SHA1_RAWSZ];
 
+	am_rerere_clear();
+
 	if (get_sha1("HEAD", head))
 		hashcpy(head, EMPTY_TREE_SHA1_BIN);
 
@@ -1717,6 +1740,8 @@ static void am_abort(struct am_state *state)
 		return;
 	}
 
+	am_rerere_clear();
+
 	curr_branch = resolve_refdup("HEAD", 0, curr_head, NULL);
 	has_curr_head = !is_null_sha1(curr_head);
 	if (!has_curr_head)
@@ -1844,6 +1869,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 			N_("lie about committer date")),
 		OPT_BOOL(0, "ignore-date", &state.ignore_date,
 			N_("use current timestamp for author date")),
+		OPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate),
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 38/44] builtin-am: support and auto-detect StGit patches
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (36 preceding siblings ...)
  2015-06-28 14:05 ` [PATCH v4 37/44] builtin-am: rerere support Paul Tan
@ 2015-06-28 14:06 ` Paul Tan
  2015-06-29 20:42   ` Stefan Beller
  2015-06-28 14:06 ` [PATCH v4 39/44] builtin-am: support and auto-detect StGit series files Paul Tan
                   ` (6 subsequent siblings)
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:06 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

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

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 131 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index b54fdbd..b73498e 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -65,9 +65,22 @@ static int linelen(const char *msg)
 	return strchrnul(msg, '\n') - msg;
 }
 
+/**
+ * Returns true if `str` consists of only whitespace, false otherwise.
+ */
+static int str_isspace(const char *str)
+{
+	while (*str)
+		if (!isspace(*(str)++))
+			return 0;
+
+	return 1;
+}
+
 enum patch_format {
 	PATCH_FORMAT_UNKNOWN = 0,
-	PATCH_FORMAT_MBOX
+	PATCH_FORMAT_MBOX,
+	PATCH_FORMAT_STGIT
 };
 
 enum keep_type {
@@ -651,6 +664,8 @@ static int detect_patch_format(const char **paths)
 {
 	enum patch_format ret = PATCH_FORMAT_UNKNOWN;
 	struct strbuf l1 = STRBUF_INIT;
+	struct strbuf l2 = STRBUF_INIT;
+	struct strbuf l3 = STRBUF_INIT;
 	FILE *fp;
 
 	/*
@@ -676,6 +691,23 @@ static int detect_patch_format(const char **paths)
 		goto done;
 	}
 
+	strbuf_reset(&l2);
+	strbuf_getline_crlf(&l2, fp);
+	strbuf_reset(&l3);
+	strbuf_getline_crlf(&l3, fp);
+
+	/*
+	 * If the second line is empty and the third is a From, Author or Date
+	 * entry, this is likely an StGit patch.
+	 */
+	if (l1.len && !l2.len &&
+		(starts_with(l3.buf, "From:") ||
+		 starts_with(l3.buf, "Author:") ||
+		 starts_with(l3.buf, "Date:"))) {
+		ret = PATCH_FORMAT_STGIT;
+		goto done;
+	}
+
 	if (l1.len && is_mail(fp)) {
 		ret = PATCH_FORMAT_MBOX;
 		goto done;
@@ -716,6 +748,100 @@ static int split_mail_mbox(struct am_state *state, const char **paths, int keep_
 }
 
 /**
+ * Callback signature for split_mail_conv(). The foreign patch should be
+ * read from `in`, and the converted patch (in RFC2822 mail format) should be
+ * written to `out`. Return 0 on success, or -1 on failure.
+ */
+typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr);
+
+/**
+ * Calls `fn` for each file in `paths` to convert the foreign patch to the
+ * RFC2822 mail format suitable for parsing with git-mailinfo.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail_conv(mail_conv_fn fn, struct am_state *state,
+			const char **paths, int keep_cr)
+{
+	static const char *stdin_only[] = {"-", NULL};
+	int i;
+
+	if (!*paths)
+		paths = stdin_only;
+
+	for (i = 0; *paths; paths++, i++) {
+		FILE *in, *out;
+		const char *mail;
+		int ret;
+
+		if (!strcmp(*paths, "-"))
+			in = stdin;
+		else
+			in = fopen(*paths, "r");
+
+		if (!in)
+			return error(_("could not open '%s' for reading: %s"),
+					*paths, strerror(errno));
+
+		mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
+
+		out = fopen(mail, "w");
+		if (!out)
+			return error(_("could not open '%s' for writing: %s"),
+					mail, strerror(errno));
+
+		ret = fn(out, in, keep_cr);
+
+		fclose(out);
+		fclose(in);
+
+		if (ret)
+			return error(_("could not parse patch '%s'"), *paths);
+	}
+
+	state->cur = 1;
+	state->last = i;
+	return 0;
+}
+
+/**
+ * A split_mail_conv() callback that converts an StGit patch to an RFC2822
+ * message suitable for parsing with git-mailinfo.
+ */
+static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)
+{
+	struct strbuf sb = STRBUF_INIT;
+	int subject_printed = 0;
+
+	while (!strbuf_getline(&sb, in, '\n')) {
+		const char *str;
+
+		if (str_isspace(sb.buf))
+			continue;
+		else if (skip_prefix(sb.buf, "Author:", &str))
+			fprintf(out, "From:%s\n", str);
+		else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date"))
+			fprintf(out, "%s\n", sb.buf);
+		else if (!subject_printed) {
+			fprintf(out, "Subject: %s\n", sb.buf);
+			subject_printed = 1;
+		} else {
+			fprintf(out, "\n%s\n", sb.buf);
+			break;
+		}
+	}
+
+	strbuf_reset(&sb);
+	while (strbuf_fread(&sb, 8192, in) > 0) {
+		fwrite(sb.buf, 1, sb.len, out);
+		strbuf_reset(&sb);
+	}
+
+	strbuf_release(&sb);
+	return 0;
+}
+
+/**
  * Splits a list of files/directories into individual email patches. Each path
  * in `paths` must be a file/directory that is formatted according to
  * `patch_format`.
@@ -743,6 +869,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
 	switch (patch_format) {
 	case PATCH_FORMAT_MBOX:
 		return split_mail_mbox(state, paths, keep_cr);
+	case PATCH_FORMAT_STGIT:
+		return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
 	default:
 		die("BUG: invalid patch_format");
 	}
@@ -1773,6 +1901,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
 
 	if (!strcmp(arg, "mbox"))
 		*opt_value = PATCH_FORMAT_MBOX;
+	else if (!strcmp(arg, "stgit"))
+		*opt_value = PATCH_FORMAT_STGIT;
 	else
 		return error(_("Invalid value for --patch-format: %s"), arg);
 	return 0;
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 39/44] builtin-am: support and auto-detect StGit series files
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (37 preceding siblings ...)
  2015-06-28 14:06 ` [PATCH v4 38/44] builtin-am: support and auto-detect StGit patches Paul Tan
@ 2015-06-28 14:06 ` Paul Tan
  2015-06-28 14:06 ` [PATCH v4 40/44] builtin-am: support and auto-detect mercurial patches Paul Tan
                   ` (5 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:06 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

	# This series applies on GIT commit

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

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

diff --git a/builtin/am.c b/builtin/am.c
index b73498e..1576bd4 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -80,7 +80,8 @@ static int str_isspace(const char *str)
 enum patch_format {
 	PATCH_FORMAT_UNKNOWN = 0,
 	PATCH_FORMAT_MBOX,
-	PATCH_FORMAT_STGIT
+	PATCH_FORMAT_STGIT,
+	PATCH_FORMAT_STGIT_SERIES
 };
 
 enum keep_type {
@@ -691,6 +692,11 @@ static int detect_patch_format(const char **paths)
 		goto done;
 	}
 
+	if (starts_with(l1.buf, "# This series applies on GIT commit")) {
+		ret = PATCH_FORMAT_STGIT_SERIES;
+		goto done;
+	}
+
 	strbuf_reset(&l2);
 	strbuf_getline_crlf(&l2, fp);
 	strbuf_reset(&l3);
@@ -842,6 +848,53 @@ static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)
 }
 
 /**
+ * This function only supports a single StGit series file in `paths`.
+ *
+ * Given an StGit series file, converts the StGit patches in the series into
+ * RFC2822 messages suitable for parsing with git-mailinfo, and queues them in
+ * the state directory.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail_stgit_series(struct am_state *state, const char **paths,
+					int keep_cr)
+{
+	const char *series_dir;
+	char *series_dir_buf;
+	FILE *fp;
+	struct argv_array patches = ARGV_ARRAY_INIT;
+	struct strbuf sb = STRBUF_INIT;
+	int ret;
+
+	if (!paths[0] || paths[1])
+		return error(_("Only one StGIT patch series can be applied at once"));
+
+	series_dir_buf = xstrdup(*paths);
+	series_dir = dirname(series_dir_buf);
+
+	fp = fopen(*paths, "r");
+	if (!fp)
+		return error(_("could not open '%s' for reading: %s"), *paths,
+				strerror(errno));
+
+	while (!strbuf_getline(&sb, fp, '\n')) {
+		if (*sb.buf == '#')
+			continue; /* skip comment lines */
+
+		argv_array_push(&patches, mkpath("%s/%s", series_dir, sb.buf));
+	}
+
+	fclose(fp);
+	strbuf_release(&sb);
+	free(series_dir_buf);
+
+	ret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr);
+
+	argv_array_clear(&patches);
+	return ret;
+}
+
+/**
  * Splits a list of files/directories into individual email patches. Each path
  * in `paths` must be a file/directory that is formatted according to
  * `patch_format`.
@@ -871,6 +924,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
 		return split_mail_mbox(state, paths, keep_cr);
 	case PATCH_FORMAT_STGIT:
 		return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
+	case PATCH_FORMAT_STGIT_SERIES:
+		return split_mail_stgit_series(state, paths, keep_cr);
 	default:
 		die("BUG: invalid patch_format");
 	}
@@ -1903,6 +1958,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
 		*opt_value = PATCH_FORMAT_MBOX;
 	else if (!strcmp(arg, "stgit"))
 		*opt_value = PATCH_FORMAT_STGIT;
+	else if (!strcmp(arg, "stgit-series"))
+		*opt_value = PATCH_FORMAT_STGIT_SERIES;
 	else
 		return error(_("Invalid value for --patch-format: %s"), arg);
 	return 0;
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 40/44] builtin-am: support and auto-detect mercurial patches
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (38 preceding siblings ...)
  2015-06-28 14:06 ` [PATCH v4 39/44] builtin-am: support and auto-detect StGit series files Paul Tan
@ 2015-06-28 14:06 ` Paul Tan
  2015-06-29 20:32   ` Stefan Beller
  2015-06-28 14:06 ` [PATCH v4 41/44] builtin-am: implement -i/--interactive Paul Tan
                   ` (4 subsequent siblings)
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:06 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

	# HG changeset patch

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

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 72 insertions(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 1576bd4..5c86e6f 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -81,7 +81,8 @@ enum patch_format {
 	PATCH_FORMAT_UNKNOWN = 0,
 	PATCH_FORMAT_MBOX,
 	PATCH_FORMAT_STGIT,
-	PATCH_FORMAT_STGIT_SERIES
+	PATCH_FORMAT_STGIT_SERIES,
+	PATCH_FORMAT_HG
 };
 
 enum keep_type {
@@ -697,6 +698,11 @@ static int detect_patch_format(const char **paths)
 		goto done;
 	}
 
+	if (!strcmp(l1.buf, "# HG changeset patch")) {
+		ret = PATCH_FORMAT_HG;
+		goto done;
+	}
+
 	strbuf_reset(&l2);
 	strbuf_getline_crlf(&l2, fp);
 	strbuf_reset(&l3);
@@ -895,6 +901,67 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths,
 }
 
 /**
+ * A split_patches_conv() callback that converts a mercurial patch to a RFC2822
+ * message suitable for parsing with git-mailinfo.
+ */
+static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)
+{
+	struct strbuf sb = STRBUF_INIT;
+
+	while (!strbuf_getline(&sb, in, '\n')) {
+		const char *str;
+
+		if (skip_prefix(sb.buf, "# User ", &str))
+			fprintf(out, "From: %s\n", str);
+		else if (skip_prefix(sb.buf, "# Date ", &str)) {
+			unsigned long timestamp;
+			long tz;
+			char *end;
+
+			errno = 0;
+			timestamp = strtoul(str, &end, 10);
+			if (errno)
+				return error(_("invalid timestamp"));
+
+			if (!skip_prefix(end, " ", &str))
+				return error(_("invalid Date line"));
+
+			errno = 0;
+			tz = strtol(str, &end, 10);
+			if (errno)
+				return error(_("invalid timezone offset"));
+
+			if (*end)
+				return error(_("invalid Date line"));
+
+			/*
+			 * mercurial's timezone is in seconds west of UTC,
+			 * however git's timezone is in hours + minutes east of
+			 * UTC. Convert it.
+			 */
+			tz = tz / (60 * 60) * 100 + tz % (60 * 60);
+			tz = -tz;
+
+			fprintf(out, "Date: %s\n", show_date(timestamp, tz, DATE_RFC2822));
+		} else if (starts_with(sb.buf, "# ")) {
+			continue;
+		} else {
+			fprintf(out, "\n%s\n", sb.buf);
+			break;
+		}
+	}
+
+	strbuf_reset(&sb);
+	while (strbuf_fread(&sb, 8192, in) > 0) {
+		fwrite(sb.buf, 1, sb.len, out);
+		strbuf_reset(&sb);
+	}
+
+	strbuf_release(&sb);
+	return 0;
+}
+
+/**
  * Splits a list of files/directories into individual email patches. Each path
  * in `paths` must be a file/directory that is formatted according to
  * `patch_format`.
@@ -926,6 +993,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
 		return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
 	case PATCH_FORMAT_STGIT_SERIES:
 		return split_mail_stgit_series(state, paths, keep_cr);
+	case PATCH_FORMAT_HG:
+		return split_mail_conv(hg_patch_to_mail, state, paths, keep_cr);
 	default:
 		die("BUG: invalid patch_format");
 	}
@@ -1960,6 +2029,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
 		*opt_value = PATCH_FORMAT_STGIT;
 	else if (!strcmp(arg, "stgit-series"))
 		*opt_value = PATCH_FORMAT_STGIT_SERIES;
+	else if (!strcmp(arg, "hg"))
+		*opt_value = PATCH_FORMAT_HG;
 	else
 		return error(_("Invalid value for --patch-format: %s"), arg);
 	return 0;
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 41/44] builtin-am: implement -i/--interactive
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (39 preceding siblings ...)
  2015-06-28 14:06 ` [PATCH v4 40/44] builtin-am: support and auto-detect mercurial patches Paul Tan
@ 2015-06-28 14:06 ` Paul Tan
  2015-06-28 14:06 ` [PATCH v4 42/44] builtin-am: implement legacy -b/--binary option Paul Tan
                   ` (3 subsequent siblings)
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:06 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

* applying the patch

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

* skipping the patch

* editing the commit message

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

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

Re-implement the above in builtin/am.c

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---

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

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

diff --git a/builtin/am.c b/builtin/am.c
index 5c86e6f..f148f05 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -25,6 +25,7 @@
 #include "log-tree.h"
 #include "notes-utils.h"
 #include "rerere.h"
+#include "prompt.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -118,6 +119,8 @@ struct am_state {
 	/* number of digits in patch filename */
 	int prec;
 
+	int interactive;
+
 	int threeway;
 
 	int quiet;
@@ -1216,7 +1219,7 @@ static void NORETURN die_user_resolve(const struct am_state *state)
 	if (state->resolvemsg) {
 		printf_ln("%s", state->resolvemsg);
 	} else {
-		const char *cmdline = "git am";
+		const char *cmdline = state->interactive ? "git am -i" : "git am";
 
 		printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
 		printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
@@ -1449,6 +1452,36 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm
 }
 
 /**
+ * Writes the diff of the index against HEAD as a patch to the state
+ * directory's "patch" file.
+ */
+static void write_index_patch(const struct am_state *state)
+{
+	struct tree *tree;
+	unsigned char head[GIT_SHA1_RAWSZ];
+	struct rev_info rev_info;
+	FILE *fp;
+
+	if (!get_sha1_tree("HEAD", head))
+		tree = lookup_tree(head);
+	else
+		tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
+
+	fp = xfopen(am_path(state, "patch"), "w");
+	init_revisions(&rev_info, NULL);
+	rev_info.diff = 1;
+	rev_info.disable_stdin = 1;
+	rev_info.no_commit_id = 1;
+	rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
+	rev_info.diffopt.use_color = 0;
+	rev_info.diffopt.file = fp;
+	rev_info.diffopt.close_file = 1;
+	add_pending_object(&rev_info, &tree->object, "");
+	diff_setup_done(&rev_info.diffopt);
+	run_diff_index(&rev_info, 1);
+}
+
+/**
  * Like parse_mail(), but parses the mail by looking up its commit ID
  * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
  * of patches.
@@ -1685,6 +1718,65 @@ static void do_commit(const struct am_state *state)
 }
 
 /**
+ * Interactively prompt the user on whether the current patch should be
+ * applied.
+ *
+ * Returns 0 if the user chooses to apply the patch, 1 if the user chooses to
+ * skip it.
+ */
+static int do_interactive(struct am_state *state)
+{
+	assert(state->msg);
+
+	if (!isatty(0))
+		die(_("cannot be interactive without stdin connected to a terminal."));
+
+	for (;;) {
+		const char *reply;
+
+		puts(_("Commit Body is:"));
+		puts("--------------------------");
+		printf("%s", state->msg);
+		puts("--------------------------");
+
+		/*
+		 * TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+		 * in your translation. The program will only accept English
+		 * input at this point.
+		 */
+		reply = git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "), PROMPT_ECHO);
+
+		if (!reply) {
+			continue;
+		} else if (*reply == 'y' || *reply == 'Y') {
+			return 0;
+		} else if (*reply == 'a' || *reply == 'A') {
+			state->interactive = 0;
+			return 0;
+		} else if (*reply == 'n' || *reply == 'N') {
+			return 1;
+		} else if (*reply == 'e' || *reply == 'E') {
+			struct strbuf msg = STRBUF_INIT;
+
+			if (!launch_editor(am_path(state, "final-commit"), &msg, NULL)) {
+				free(state->msg);
+				state->msg = strbuf_detach(&msg, &state->msg_len);
+			}
+			strbuf_release(&msg);
+		} else if (*reply == 'v' || *reply == 'V') {
+			const char *pager = git_pager(1);
+			struct child_process cp = CHILD_PROCESS_INIT;
+
+			if (!pager)
+				pager = "cat";
+			argv_array_push(&cp.args, pager);
+			argv_array_push(&cp.args, am_path(state, "patch"));
+			run_command(&cp);
+		}
+	}
+}
+
+/**
  * Applies all queued mail.
  */
 static void am_run(struct am_state *state)
@@ -1721,6 +1813,9 @@ static void am_run(struct am_state *state)
 		write_author_script(state);
 		write_commit_msg(state);
 
+		if (state->interactive && do_interactive(state))
+			goto next;
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
 
@@ -1812,10 +1907,17 @@ static void am_resolve(struct am_state *state)
 		die_user_resolve(state);
 	}
 
+	if (state->interactive) {
+		write_index_patch(state);
+		if (do_interactive(state))
+			goto next;
+	}
+
 	rerere(0);
 
 	do_commit(state);
 
+next:
 	am_next(state);
 	am_run(state);
 }
@@ -2057,6 +2159,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 	};
 
 	struct option options[] = {
+		OPT_BOOL('i', "interactive", &state.interactive,
+			N_("run interactively")),
 		OPT_BOOL('3', "3way", &state.threeway,
 			N_("allow fall back on 3way merging if needed")),
 		OPT__QUIET(&state.quiet, N_("be quiet")),
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 42/44] builtin-am: implement legacy -b/--binary option
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (40 preceding siblings ...)
  2015-06-28 14:06 ` [PATCH v4 41/44] builtin-am: implement -i/--interactive Paul Tan
@ 2015-06-28 14:06 ` Paul Tan
  2015-06-29 20:05   ` Stefan Beller
  2015-06-28 14:06 ` [PATCH v4 43/44] builtin-am: check for valid committer ident Paul Tan
                   ` (2 subsequent siblings)
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:06 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

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

will be printed.

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index f148f05..a46aa74 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2148,6 +2148,7 @@ enum resume_mode {
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
 	struct am_state state;
+	int binary = -1;
 	int keep_cr = -1;
 	int patch_format = PATCH_FORMAT_UNKNOWN;
 	enum resume_mode resume = RESUME_FALSE;
@@ -2161,6 +2162,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 	struct option options[] = {
 		OPT_BOOL('i', "interactive", &state.interactive,
 			N_("run interactively")),
+		OPT_HIDDEN_BOOL('b', "binary", &binary,
+			N_("(historical option -- no-op")),
 		OPT_BOOL('3', "3way", &state.threeway,
 			N_("allow fall back on 3way merging if needed")),
 		OPT__QUIET(&state.quiet, N_("be quiet")),
@@ -2261,6 +2264,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, prefix, options, usage, 0);
 
+	if (binary >= 0)
+		fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n"
+				"it will be removed. Please do not use it anymore."));
+
 	if (read_index_preload(&the_index, NULL) < 0)
 		die(_("failed to read the index"));
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 43/44] builtin-am: check for valid committer ident
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (41 preceding siblings ...)
  2015-06-28 14:06 ` [PATCH v4 42/44] builtin-am: implement legacy -b/--binary option Paul Tan
@ 2015-06-28 14:06 ` Paul Tan
  2015-06-29 20:02   ` Stefan Beller
  2015-06-28 14:06 ` [PATCH v4 44/44] builtin-am: remove redirection to git-am.sh Paul Tan
  2015-06-29  5:01 ` [PATCH v4 00/44] Make git-am a builtin Stefan Beller
  44 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:06 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

Re-implement this in builtin/am.c.

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/builtin/am.c b/builtin/am.c
index a46aa74..1cb02c8 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2268,6 +2268,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n"
 				"it will be removed. Please do not use it anymore."));
 
+	/* Ensure a valid committer ident can be constructed */
+	git_committer_info(IDENT_STRICT);
+
 	if (read_index_preload(&the_index, NULL) < 0)
 		die(_("failed to read the index"));
 
-- 
2.5.0.rc0.76.gb2c6e93

^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH v4 44/44] builtin-am: remove redirection to git-am.sh
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (42 preceding siblings ...)
  2015-06-28 14:06 ` [PATCH v4 43/44] builtin-am: check for valid committer ident Paul Tan
@ 2015-06-28 14:06 ` Paul Tan
  2015-06-29  5:01 ` [PATCH v4 00/44] Make git-am a builtin Stefan Beller
  44 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-06-28 14:06 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

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

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

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

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

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 01/44] wrapper: implement xopen()
  2015-06-28 14:05 ` [PATCH v4 01/44] wrapper: implement xopen() Paul Tan
@ 2015-06-29  4:48   ` Torsten Bögershausen
  2015-06-29  5:04     ` Stefan Beller
                       ` (2 more replies)
  0 siblings, 3 replies; 74+ messages in thread
From: Torsten Bögershausen @ 2015-06-29  4:48 UTC (permalink / raw)
  To: Paul Tan, git; +Cc: Johannes Schindelin, Stefan Beller

On 06/28/2015 04:05 PM, Paul Tan wrote:
> A common usage pattern of open() is to check if it was successful, and
> die() if it was not:
>
> 	int fd = open(path, O_WRONLY | O_CREAT, 0777);
> 	if (fd < 0)
> 		die_errno(_("Could not open '%s' for writing."), path);
>
> Implement a wrapper function xopen() that does the above so that we can
> save a few lines of code, and make the die() messages consistent.
>
> Helped-by: Torsten Bögershausen <tboegi@web.de>
> Helped-by: Jeff King <peff@peff.net>
> Signed-off-by: Paul Tan <pyokagan@gmail.com>
> ---
>   git-compat-util.h |  1 +
>   wrapper.c         | 25 +++++++++++++++++++++++++
>   2 files changed, 26 insertions(+)
>
> diff --git a/git-compat-util.h b/git-compat-util.h
> index c6d391f..e168dfd 100644
> --- a/git-compat-util.h
> +++ b/git-compat-util.h
> @@ -717,6 +717,7 @@ extern void *xrealloc(void *ptr, size_t size);
>   extern void *xcalloc(size_t nmemb, size_t size);
>   extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
>   extern void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
> +extern int xopen(const char *path, int flags, ...);
>   extern ssize_t xread(int fd, void *buf, size_t len);
>   extern ssize_t xwrite(int fd, const void *buf, size_t len);
>   extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
> diff --git a/wrapper.c b/wrapper.c
> index ff49807..7e13ae0 100644
> --- a/wrapper.c
> +++ b/wrapper.c
> @@ -189,6 +189,31 @@ void *xcalloc(size_t nmemb, size_t size)
>   # endif
>   #endif
>   
> +/**
> + * xopen() is the same as open(), but it die()s if the open() fails.
> + */
> +int xopen(const char *path, int oflag, ...)
> +{
> +	mode_t mode = 0;
> +	va_list ap;
> +
> +	va_start(ap, oflag);
> +	if (oflag & O_CREAT)
> +		mode = va_arg(ap, mode_t);
> +	va_end(ap);
> +
> +	assert(path);
> +
2 remarks:
- I don't know if and why we need the assert() here (but don't know if 
we have a strategie in Git for assert())
- Having xopen() with 2 or 3 parameter is good, but the current may need 
some tweaks for better portability:

int xopen(const char *path, int oflag, ...)
{
	mode_t mode = 0;
	if (oflag & O_CREAT) {
		va_list ap;
		va_start(ap, oflag);
		mode = va_arg(ap, int);
		va_end(ap);

             }

See e.g.
<http://blitiri.com.ar/git/r/libfiu/c/37f6a98110e3bb59bbb4971241baa3a385c3f724/>
why va_arg(ap, int) should be used:


+
+		/* va_arg() can only take fully promoted types, and mode_t
+		 * sometimes is smaller than an int, so we should always pass
+		 * int to it, and not mode_t. Not doing so would may result in
+		 * a compile-time warning and run-time error. We asume that it
+		 * is never bigger than an int, which holds in practise. */
+		mode = va_arg(l, int);
+

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 00/44] Make git-am a builtin
  2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
                   ` (43 preceding siblings ...)
  2015-06-28 14:06 ` [PATCH v4 44/44] builtin-am: remove redirection to git-am.sh Paul Tan
@ 2015-06-29  5:01 ` Stefan Beller
  2015-06-30  0:19   ` Stefan Beller
  44 siblings, 1 reply; 74+ messages in thread
From: Stefan Beller @ 2015-06-29  5:01 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

> This is a re-roll of [WIP v3]. This patch series is now out of WIP, as ...

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

I assume the later is just a left over from an old cover letter. :)

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 01/44] wrapper: implement xopen()
  2015-06-29  4:48   ` Torsten Bögershausen
@ 2015-06-29  5:04     ` Stefan Beller
  2015-06-29 17:18     ` Junio C Hamano
  2015-07-01  6:04     ` Paul Tan
  2 siblings, 0 replies; 74+ messages in thread
From: Stefan Beller @ 2015-06-29  5:04 UTC (permalink / raw)
  To: Torsten Bögershausen; +Cc: Paul Tan, git, Johannes Schindelin

On Sun, Jun 28, 2015 at 9:48 PM, Torsten Bögershausen <tboegi@web.de> wrote:
> On 06/28/2015 04:05 PM, Paul Tan wrote:
>>
>> A common usage pattern of open() is to check if it was successful, and
>> die() if it was not:
>>
>>         int fd = open(path, O_WRONLY | O_CREAT, 0777);
>>         if (fd < 0)
>>                 die_errno(_("Could not open '%s' for writing."), path);
>>
>> Implement a wrapper function xopen() that does the above so that we can
>> save a few lines of code, and make the die() messages consistent.
>>
>> Helped-by: Torsten Bögershausen <tboegi@web.de>
>> Helped-by: Jeff King <peff@peff.net>
>> Signed-off-by: Paul Tan <pyokagan@gmail.com>
>> ---
>>   git-compat-util.h |  1 +
>>   wrapper.c         | 25 +++++++++++++++++++++++++
>>   2 files changed, 26 insertions(+)
>>
>> diff --git a/git-compat-util.h b/git-compat-util.h
>> index c6d391f..e168dfd 100644
>> --- a/git-compat-util.h
>> +++ b/git-compat-util.h
>> @@ -717,6 +717,7 @@ extern void *xrealloc(void *ptr, size_t size);
>>   extern void *xcalloc(size_t nmemb, size_t size);
>>   extern void *xmmap(void *start, size_t length, int prot, int flags, int
>> fd, off_t offset);
>>   extern void *xmmap_gently(void *start, size_t length, int prot, int
>> flags, int fd, off_t offset);
>> +extern int xopen(const char *path, int flags, ...);
>>   extern ssize_t xread(int fd, void *buf, size_t len);
>>   extern ssize_t xwrite(int fd, const void *buf, size_t len);
>>   extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
>> diff --git a/wrapper.c b/wrapper.c
>> index ff49807..7e13ae0 100644
>> --- a/wrapper.c
>> +++ b/wrapper.c
>> @@ -189,6 +189,31 @@ void *xcalloc(size_t nmemb, size_t size)
>>   # endif
>>   #endif
>>   +/**
>> + * xopen() is the same as open(), but it die()s if the open() fails.
>> + */
>> +int xopen(const char *path, int oflag, ...)
>> +{
>> +       mode_t mode = 0;
>> +       va_list ap;
>> +
>> +       va_start(ap, oflag);
>> +       if (oflag & O_CREAT)
>> +               mode = va_arg(ap, mode_t);
>> +       va_end(ap);
>> +
>> +       assert(path);
>> +
>
> 2 remarks:
> - I don't know if and why we need the assert() here (but don't know if we
> have a strategie in Git for assert())

Yeah I think we usually do

    if (!assertion)
        die("BUG: assertion not met");

> - Having xopen() with 2 or 3 parameter is good, but the current may need
> some tweaks for better portability:
>
> int xopen(const char *path, int oflag, ...)
> {
>         mode_t mode = 0;
>         if (oflag & O_CREAT) {
>                 va_list ap;
>                 va_start(ap, oflag);
>                 mode = va_arg(ap, int);
>                 va_end(ap);
>
>             }
>
> See e.g.
> <http://blitiri.com.ar/git/r/libfiu/c/37f6a98110e3bb59bbb4971241baa3a385c3f724/>
> why va_arg(ap, int) should be used:
>
>
> +
> +               /* va_arg() can only take fully promoted types, and mode_t
> +                * sometimes is smaller than an int, so we should always
> pass
> +                * int to it, and not mode_t. Not doing so would may result
> in
> +                * a compile-time warning and run-time error. We asume that
> it
> +                * is never bigger than an int, which holds in practise. */
> +               mode = va_arg(l, int);
> +
>
>

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 04/44] builtin-am: implement patch queue mechanism
  2015-06-28 14:05 ` [PATCH v4 04/44] builtin-am: implement patch queue mechanism Paul Tan
@ 2015-06-29  5:08   ` Stefan Beller
  2015-07-07 12:50     ` Paul Tan
  0 siblings, 1 reply; 74+ messages in thread
From: Stefan Beller @ 2015-06-29  5:08 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Sun, Jun 28, 2015 at 7:05 AM, Paul Tan <pyokagan@gmail.com> wrote:
> git-am applies a series of patches. If the process terminates
> abnormally, we want to be able to resume applying the series of patches.
> This requires the session state to be saved in a persistent location.
>
> Implement the mechanism of a "patch queue", represented by 2 integers --
> the index of the current patch we are applying and the index of the last
> patch, as well as its lifecycle through the following functions:
>
> * am_setup(), which will set up the state directory
>   $GIT_DIR/rebase-apply. As such, even if the process exits abnormally,
>   the last-known state will still persist.
>
> * am_load(), which is called if there is an am session in
>   progress, to load the last known state from the state directory so we
>   can resume applying patches.
>
> * am_run(), which will do the actual patch application. After applying a
>   patch, it calls am_next() to increment the current patch index. The
>   logic for applying and committing a patch is not implemented yet.
>
> * am_destroy(), which is finally called when we successfully applied all
>   the patches in the queue, to clean up by removing the state directory
>   and its contents.
>
> Helped-by: Junio C Hamano <gitster@pobox.com>
> Helped-by: Stefan Beller <sbeller@google.com>
> Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> Signed-off-by: Paul Tan <pyokagan@gmail.com>
> ---
>
> Notes:
>     v4
>
>     * Corrected docstring of read_state_file()
>
>     * Corrected docstring of am_state_release()
>
>     * am_state's "dir" field is now a char*. To help API users,
>       am_state_init() takes an additional const char *dir argument.
>
>     * The opt_* option variables, am_options[] and am_usage[] have been
>       moved into cmd_am()'s scope.
>
>     * signature of read_state_file() has been changed to
>       read_state_file(strbuf, state, file, trim)
>
>  builtin/am.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 180 insertions(+)
>
> diff --git a/builtin/am.c b/builtin/am.c
> index fd32caf..5b4e9af 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -6,9 +6,174 @@
>  #include "cache.h"
>  #include "builtin.h"
>  #include "exec_cmd.h"
> +#include "parse-options.h"
> +#include "dir.h"
> +
> +struct am_state {
> +       /* state directory path */
> +       char *dir;
> +
> +       /* current and last patch numbers, 1-indexed */
> +       int cur;
> +       int last;
> +};
> +
> +/**
> + * Initializes am_state with the default values. The state directory is set to
> + * dir.
> + */
> +static void am_state_init(struct am_state *state, const char *dir)
> +{
> +       memset(state, 0, sizeof(*state));
> +
> +       assert(dir);
> +       state->dir = xstrdup(dir);
> +}
> +
> +/**
> + * Releases memory allocated by an am_state.
> + */
> +static void am_state_release(struct am_state *state)
> +{
> +       if (state->dir)
> +               free(state->dir);
> +}
> +
> +/**
> + * Returns path relative to the am_state directory.
> + */
> +static inline const char *am_path(const struct am_state *state, const char *path)
> +{
> +       assert(state->dir);
> +       assert(path);
> +       return mkpath("%s/%s", state->dir, path);
> +}
> +
> +/**
> + * Returns 1 if there is an am session in progress, 0 otherwise.
> + */
> +static int am_in_progress(const struct am_state *state)
> +{
> +       struct stat st;
> +
> +       if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode))
> +               return 0;
> +       if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode))
> +               return 0;
> +       if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode))
> +               return 0;
> +       return 1;
> +}
> +
> +/**
> + * Reads the contents of `file` in the `state` directory into `sb`. Returns the
> + * number of bytes read on success, -1 if the file does not exist. If `trim` is
> + * set, trailing whitespace will be removed.
> + */
> +static int read_state_file(struct strbuf *sb, const struct am_state *state,
> +                       const char *file, int trim)
> +{
> +       strbuf_reset(sb);
> +
> +       if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {
> +               if (trim)
> +                       strbuf_trim(sb);
> +
> +               return sb->len;
> +       }
> +
> +       if (errno == ENOENT)
> +               return -1;
> +
> +       die_errno(_("could not read '%s'"), am_path(state, file));
> +}
> +
> +/**
> + * Loads state from disk.
> + */
> +static void am_load(struct am_state *state)
> +{
> +       struct strbuf sb = STRBUF_INIT;
> +
> +       if (read_state_file(&sb, state, "next", 1) < 0)
> +               die("BUG: state file 'next' does not exist");
> +       state->cur = strtol(sb.buf, NULL, 10);
> +
> +       if (read_state_file(&sb, state, "last", 1) < 0)
> +               die("BUG: state file 'last' does not exist");
> +       state->last = strtol(sb.buf, NULL, 10);
> +
> +       strbuf_release(&sb);
> +}
> +
> +/**
> + * Removes the am_state directory, forcefully terminating the current am
> + * session.
> + */
> +static void am_destroy(const struct am_state *state)
> +{
> +       struct strbuf sb = STRBUF_INIT;
> +
> +       strbuf_addstr(&sb, state->dir);
> +       remove_dir_recursively(&sb, 0);
> +       strbuf_release(&sb);
> +}
> +
> +/**
> + * Setup a new am session for applying patches
> + */
> +static void am_setup(struct am_state *state)
> +{
> +       if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
> +               die_errno(_("failed to create directory '%s'"), state->dir);
> +
> +       /*
> +        * NOTE: Since the "next" and "last" files determine if an am_state
> +        * session is in progress, they should be written last.
> +        */
> +
> +       write_file(am_path(state, "next"), 1, "%d", state->cur);
> +
> +       write_file(am_path(state, "last"), 1, "%d", state->last);
> +}
> +
> +/**
> + * Increments the patch pointer, and cleans am_state for the application of the
> + * next patch.
> + */
> +static void am_next(struct am_state *state)
> +{
> +       state->cur++;
> +       write_file(am_path(state, "next"), 1, "%d", state->cur);
> +}
> +
> +/**
> + * Applies all queued mail.
> + */
> +static void am_run(struct am_state *state)
> +{
> +       while (state->cur <= state->last) {
> +
> +               /* TODO: Patch application not implemented yet */

(optional nit, bikeshedding)
In conjunction with the previous patch I just wonder when we put a
TODO and when we want to put a NEEDSWORK, or if we're being
inconsistent here as both issues will be resolved in a later patch
in the series.

> +
> +               am_next(state);
> +       }
> +
> +       am_destroy(state);
> +}
>
>  int cmd_am(int argc, const char **argv, const char *prefix)
>  {
> +       struct am_state state;
> +
> +       const char * const usage[] = {
> +               N_("git am [options] [(<mbox>|<Maildir>)...]"),
> +               NULL
> +       };
> +
> +       struct option options[] = {
> +               OPT_END()
> +       };
>
>         /*
>          * NEEDSWORK: Once all the features of git-am.sh have been
> @@ -25,5 +190,20 @@ int cmd_am(int argc, const char **argv, const char *prefix)
>                 setup_work_tree();
>         }
>
> +       git_config(git_default_config, NULL);
> +
> +       am_state_init(&state, git_path("rebase-apply"));
> +
> +       argc = parse_options(argc, argv, prefix, options, usage, 0);
> +
> +       if (am_in_progress(&state))
> +               am_load(&state);
> +       else
> +               am_setup(&state);
> +
> +       am_run(&state);
> +
> +       am_state_release(&state);
> +
>         return 0;
>  }
> --
> 2.5.0.rc0.76.gb2c6e93
>

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 01/44] wrapper: implement xopen()
  2015-06-29  4:48   ` Torsten Bögershausen
  2015-06-29  5:04     ` Stefan Beller
@ 2015-06-29 17:18     ` Junio C Hamano
  2015-07-01  6:05       ` Paul Tan
  2015-07-01  6:04     ` Paul Tan
  2 siblings, 1 reply; 74+ messages in thread
From: Junio C Hamano @ 2015-06-29 17:18 UTC (permalink / raw)
  To: Torsten Bögershausen
  Cc: Paul Tan, git, Johannes Schindelin, Stefan Beller

Torsten Bögershausen <tboegi@web.de> writes:

>> +int xopen(const char *path, int oflag, ...)
>> +{
>> +	mode_t mode = 0;
>> +	va_list ap;
>> +
>> +	va_start(ap, oflag);
>> +	if (oflag & O_CREAT)
>> +		mode = va_arg(ap, mode_t);
>> +	va_end(ap);
>> +
>> +	assert(path);
>> +
> 2 remarks:
> - I don't know if and why we need the assert() here (but don't know if
> we have a strategie in Git for assert())

There is no bright-line rules, but I think it is sensible to remove
this.  Nobody sane would throw a NULL at open(2) and xopen() is
supposed to imitate that interface.  We do protect ourselves from
careless use of our own API, but no need to clutter the code with
overly zealous check against insane code, I would say.

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 43/44] builtin-am: check for valid committer ident
  2015-06-28 14:06 ` [PATCH v4 43/44] builtin-am: check for valid committer ident Paul Tan
@ 2015-06-29 20:02   ` Stefan Beller
  2015-07-01  6:43     ` Paul Tan
  0 siblings, 1 reply; 74+ messages in thread
From: Stefan Beller @ 2015-06-29 20:02 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan <pyokagan@gmail.com> wrote:
> When commit_tree() is called, if the user does not have an explicit
> committer ident configured, it will attempt to construct a default
> committer ident based on the user's and system's info (e.g. gecos field,
> hostname etc.) However, if a default committer ident is unable to be
> constructed, commit_tree() will die(). However, at this point of git-am,

s/. However,/, but/ ?

> there will already be changes made to the index and work tree.
>
> This can be confusing to new users, and as such since d64e6b0 (Keep
> Porcelainish from failing by broken ident after making changes.,
> 2006-02-18) git-am.sh will check to see if the committer ident has been
> configured, or a default one can be constructed, before even starting to
> apply patches.
>
> Re-implement this in builtin/am.c.
>
> Signed-off-by: Paul Tan <pyokagan@gmail.com>
> ---
>  builtin/am.c | 3 +++
>  1 file changed, 3 insertions(+)
>
> diff --git a/builtin/am.c b/builtin/am.c
> index a46aa74..1cb02c8 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -2268,6 +2268,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
>                 fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n"
>                                 "it will be removed. Please do not use it anymore."));
>
> +       /* Ensure a valid committer ident can be constructed */
> +       git_committer_info(IDENT_STRICT);
> +
>         if (read_index_preload(&the_index, NULL) < 0)
>                 die(_("failed to read the index"));
>
> --
> 2.5.0.rc0.76.gb2c6e93
>

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 42/44] builtin-am: implement legacy -b/--binary option
  2015-06-28 14:06 ` [PATCH v4 42/44] builtin-am: implement legacy -b/--binary option Paul Tan
@ 2015-06-29 20:05   ` Stefan Beller
       [not found]     ` <CABPQNSakaoyNRuNz=bcDYWdy4e2O3M4UuYoOT5JAV1mt-BiAOw@mail.gmail.com>
  0 siblings, 1 reply; 74+ messages in thread
From: Stefan Beller @ 2015-06-29 20:05 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan <pyokagan@gmail.com> wrote:
> The -b/--binary option was initially implemented in 087b674 (git-am:
> --binary; document --resume and --binary., 2005-11-16). The option will
> pass the --binary flag to git-apply to allow it to apply binary patches.
>
> However, in 2b6eef9 (Make apply --binary a no-op., 2006-09-06), --binary
> was been made a no-op in git-apply. Following that, since cb3a160
> (git-am: ignore --binary option, 2008-08-09), the --binary option in
> git-am is ignored as well.
>
> In 6c15a1c (am: officially deprecate -b/--binary option, 2012-03-13),
> the --binary option was tweaked to its present behavior: when set, the
> message:
>
>         The -b/--binary option has been a no-op for long time, and it
>         will be removed. Please do not use it anymore.
>
> will be printed.

I wonder if now would be the right time? The rewrite aim's at full
feature compatibility, but we may want to revert this commit on
top of patch 44 later.

>
> Re-implement this in builtin/am.c.
>
> Signed-off-by: Paul Tan <pyokagan@gmail.com>
> ---
>  builtin/am.c | 7 +++++++
>  1 file changed, 7 insertions(+)
>
> diff --git a/builtin/am.c b/builtin/am.c
> index f148f05..a46aa74 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -2148,6 +2148,7 @@ enum resume_mode {
>  int cmd_am(int argc, const char **argv, const char *prefix)
>  {
>         struct am_state state;
> +       int binary = -1;
>         int keep_cr = -1;
>         int patch_format = PATCH_FORMAT_UNKNOWN;
>         enum resume_mode resume = RESUME_FALSE;
> @@ -2161,6 +2162,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
>         struct option options[] = {
>                 OPT_BOOL('i', "interactive", &state.interactive,
>                         N_("run interactively")),
> +               OPT_HIDDEN_BOOL('b', "binary", &binary,
> +                       N_("(historical option -- no-op")),
>                 OPT_BOOL('3', "3way", &state.threeway,
>                         N_("allow fall back on 3way merging if needed")),
>                 OPT__QUIET(&state.quiet, N_("be quiet")),
> @@ -2261,6 +2264,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
>
>         argc = parse_options(argc, argv, prefix, options, usage, 0);
>
> +       if (binary >= 0)
> +               fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n"
> +                               "it will be removed. Please do not use it anymore."));
> +
>         if (read_index_preload(&the_index, NULL) < 0)
>                 die(_("failed to read the index"));
>
> --
> 2.5.0.rc0.76.gb2c6e93
>

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 40/44] builtin-am: support and auto-detect mercurial patches
  2015-06-28 14:06 ` [PATCH v4 40/44] builtin-am: support and auto-detect mercurial patches Paul Tan
@ 2015-06-29 20:32   ` Stefan Beller
  2015-06-29 20:32     ` Stefan Beller
  2015-07-01  8:48     ` Paul Tan
  0 siblings, 2 replies; 74+ messages in thread
From: Stefan Beller @ 2015-06-29 20:32 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan <pyokagan@gmail.com> wrote:
> Since 0cfd112 (am: preliminary support for hg patches, 2011-08-29),
> git-am.sh could convert mercurial patches to an RFC2822 mail patch
> suitable for parsing with git-mailinfo, and queue them in the state
> directory for application.
>
> Since 15ced75 (git-am foreign patch support: autodetect some patch
> formats, 2009-05-27), git-am.sh was able to auto-detect mercurial
> patches by checking if the file begins with the line:
>
>         # HG changeset patch
>
> Re-implement the above in builtin/am.c.
>
> Signed-off-by: Paul Tan <pyokagan@gmail.com>
> ---
>  builtin/am.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 72 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/am.c b/builtin/am.c
> index 1576bd4..5c86e6f 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -81,7 +81,8 @@ enum patch_format {
>         PATCH_FORMAT_UNKNOWN = 0,
>         PATCH_FORMAT_MBOX,
>         PATCH_FORMAT_STGIT,
> -       PATCH_FORMAT_STGIT_SERIES
> +       PATCH_FORMAT_STGIT_SERIES,
> +       PATCH_FORMAT_HG
>  };
>
>  enum keep_type {
> @@ -697,6 +698,11 @@ static int detect_patch_format(const char **paths)
>                 goto done;
>         }
>
> +       if (!strcmp(l1.buf, "# HG changeset patch")) {
> +               ret = PATCH_FORMAT_HG;
> +               goto done;
> +       }
> +
>         strbuf_reset(&l2);
>         strbuf_getline_crlf(&l2, fp);
>         strbuf_reset(&l3);
> @@ -895,6 +901,67 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths,
>  }
>
>  /**
> + * A split_patches_conv() callback that converts a mercurial patch to a RFC2822
> + * message suitable for parsing with git-mailinfo.
> + */
> +static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)
> +{
> +       struct strbuf sb = STRBUF_INIT;
> +
> +       while (!strbuf_getline(&sb, in, '\n')) {
> +               const char *str;
> +
> +               if (skip_prefix(sb.buf, "# User ", &str))
> +                       fprintf(out, "From: %s\n", str);
> +               else if (skip_prefix(sb.buf, "# Date ", &str)) {
> +                       unsigned long timestamp;
> +                       long tz;
> +                       char *end;
> +
> +                       errno = 0;
> +                       timestamp = strtoul(str, &end, 10);
> +                       if (errno)
> +                               return error(_("invalid timestamp"));
> +
> +                       if (!skip_prefix(end, " ", &str))
> +                               return error(_("invalid Date line"));
> +
> +                       errno = 0;
> +                       tz = strtol(str, &end, 10);
> +                       if (errno)
> +                               return error(_("invalid timezone offset"));
> +
> +                       if (*end)
> +                               return error(_("invalid Date line"));
> +
> +                       /*
> +                        * mercurial's timezone is in seconds west of UTC,
> +                        * however git's timezone is in hours + minutes east of
> +                        * UTC. Convert it.
> +                        */
> +                       tz = tz / (60 * 60) * 100 + tz % (60 * 60);

What happens if we have a negative input not matching a full hour, say -5400 ?
(would equate to 0130 in git)

for calculating the minutes we would only need to take % 3600 (which
you do), but
then we still need to divide by 60 to convert seconds to minutes?

> +                       tz = -tz;
> +
> +                       fprintf(out, "Date: %s\n", show_date(timestamp, tz, DATE_RFC2822));
> +               } else if (starts_with(sb.buf, "# ")) {
> +                       continue;
> +               } else {
> +                       fprintf(out, "\n%s\n", sb.buf);
> +                       break;
> +               }
> +       }
> +
> +       strbuf_reset(&sb);
> +       while (strbuf_fread(&sb, 8192, in) > 0) {
> +               fwrite(sb.buf, 1, sb.len, out);
> +               strbuf_reset(&sb);
> +       }
> +
> +       strbuf_release(&sb);
> +       return 0;
> +}
> +
> +/**
>   * Splits a list of files/directories into individual email patches. Each path
>   * in `paths` must be a file/directory that is formatted according to
>   * `patch_format`.
> @@ -926,6 +993,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
>                 return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
>         case PATCH_FORMAT_STGIT_SERIES:
>                 return split_mail_stgit_series(state, paths, keep_cr);
> +       case PATCH_FORMAT_HG:
> +               return split_mail_conv(hg_patch_to_mail, state, paths, keep_cr);
>         default:
>                 die("BUG: invalid patch_format");
>         }
> @@ -1960,6 +2029,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
>                 *opt_value = PATCH_FORMAT_STGIT;
>         else if (!strcmp(arg, "stgit-series"))
>                 *opt_value = PATCH_FORMAT_STGIT_SERIES;
> +       else if (!strcmp(arg, "hg"))
> +               *opt_value = PATCH_FORMAT_HG;
>         else
>                 return error(_("Invalid value for --patch-format: %s"), arg);
>         return 0;
> --
> 2.5.0.rc0.76.gb2c6e93
>

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 40/44] builtin-am: support and auto-detect mercurial patches
  2015-06-29 20:32   ` Stefan Beller
@ 2015-06-29 20:32     ` Stefan Beller
  2015-07-01  9:07       ` Paul Tan
  2015-07-01  8:48     ` Paul Tan
  1 sibling, 1 reply; 74+ messages in thread
From: Stefan Beller @ 2015-06-29 20:32 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Mon, Jun 29, 2015 at 1:32 PM, Stefan Beller <sbeller@google.com> wrote:
> On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan <pyokagan@gmail.com> wrote:
>> +                       tz = tz / (60 * 60) * 100 + tz % (60 * 60);
>
> What happens if we have a negative input not matching a full hour, say -5400 ?
> (would equate to 0130 in git)
>
> for calculating the minutes we would only need to take % 3600 (which
> you do), but
> then we still need to divide by 60 to convert seconds to minutes?
>

That said, I wonder if we have some helper functions around somewhere as we
need to convert the timezone data at many places.

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 38/44] builtin-am: support and auto-detect StGit patches
  2015-06-28 14:06 ` [PATCH v4 38/44] builtin-am: support and auto-detect StGit patches Paul Tan
@ 2015-06-29 20:42   ` Stefan Beller
  2015-06-29 20:51     ` Eric Sunshine
  0 siblings, 1 reply; 74+ messages in thread
From: Stefan Beller @ 2015-06-29 20:42 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan <pyokagan@gmail.com> wrote:
> Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27),
> git-am.sh supported converting StGit patches into RFC2822 mail patches
> that can be parsed with git-mailinfo.
>
> Implement this by introducing two functions in builtin/am.c:
> stgit_patch_to_mail() and split_mail_conv().
>
> stgit_patch_to_mail() is a callback function for split_mail_conv(), and
> contains the logic for converting an StGit patch into an RFC2822 mail
> patch.
>
> split_mail_conv() implements the logic to go through each file in the
> `paths` list, reading from stdin where specified, and calls the callback
> function to write the converted patch to the corresponding output file
> in the state directory. This interface should be generic enough to
> support other foreign patch formats in the future.
>
> Since 15ced75 (git-am foreign patch support: autodetect some patch
> formats, 2009-05-27), git-am.sh is able to auto-detect StGit patches.
> Re-implement this in builtin/am.c.
>
> Signed-off-by: Paul Tan <pyokagan@gmail.com>
> ---
>  builtin/am.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 131 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/am.c b/builtin/am.c
> index b54fdbd..b73498e 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -65,9 +65,22 @@ static int linelen(const char *msg)
>         return strchrnul(msg, '\n') - msg;
>  }
>
> +/**
> + * Returns true if `str` consists of only whitespace, false otherwise.
> + */
> +static int str_isspace(const char *str)
> +{
> +       while (*str)
> +               if (!isspace(*(str)++))
> +                       return 0;

(nit:)
This looks a bit weird when first reading it, maybe combine the 2 conditions?

    while (*str && !isspace(*(str)++))
        return 0;

The isspace checks for both tabs and whitespaces IIRC, so SP TAB SP
would be valid here
(returning 1).


> +
> +       return 1;
> +}
> +
>  enum patch_format {
>         PATCH_FORMAT_UNKNOWN = 0,
> -       PATCH_FORMAT_MBOX
> +       PATCH_FORMAT_MBOX,
> +       PATCH_FORMAT_STGIT
>  };
>
>  enum keep_type {
> @@ -651,6 +664,8 @@ static int detect_patch_format(const char **paths)
>  {
>         enum patch_format ret = PATCH_FORMAT_UNKNOWN;
>         struct strbuf l1 = STRBUF_INIT;
> +       struct strbuf l2 = STRBUF_INIT;
> +       struct strbuf l3 = STRBUF_INIT;
>         FILE *fp;
>
>         /*
> @@ -676,6 +691,23 @@ static int detect_patch_format(const char **paths)
>                 goto done;
>         }
>
> +       strbuf_reset(&l2);
> +       strbuf_getline_crlf(&l2, fp);
> +       strbuf_reset(&l3);
> +       strbuf_getline_crlf(&l3, fp);
> +
> +       /*
> +        * If the second line is empty and the third is a From, Author or Date
> +        * entry, this is likely an StGit patch.
> +        */
> +       if (l1.len && !l2.len &&
> +               (starts_with(l3.buf, "From:") ||
> +                starts_with(l3.buf, "Author:") ||
> +                starts_with(l3.buf, "Date:"))) {
> +               ret = PATCH_FORMAT_STGIT;
> +               goto done;
> +       }
> +
>         if (l1.len && is_mail(fp)) {
>                 ret = PATCH_FORMAT_MBOX;
>                 goto done;
> @@ -716,6 +748,100 @@ static int split_mail_mbox(struct am_state *state, const char **paths, int keep_
>  }
>
>  /**
> + * Callback signature for split_mail_conv(). The foreign patch should be
> + * read from `in`, and the converted patch (in RFC2822 mail format) should be
> + * written to `out`. Return 0 on success, or -1 on failure.
> + */
> +typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr);
> +
> +/**
> + * Calls `fn` for each file in `paths` to convert the foreign patch to the
> + * RFC2822 mail format suitable for parsing with git-mailinfo.
> + *
> + * Returns 0 on success, -1 on failure.
> + */
> +static int split_mail_conv(mail_conv_fn fn, struct am_state *state,
> +                       const char **paths, int keep_cr)
> +{
> +       static const char *stdin_only[] = {"-", NULL};
> +       int i;
> +
> +       if (!*paths)
> +               paths = stdin_only;
> +
> +       for (i = 0; *paths; paths++, i++) {
> +               FILE *in, *out;
> +               const char *mail;
> +               int ret;
> +
> +               if (!strcmp(*paths, "-"))
> +                       in = stdin;
> +               else
> +                       in = fopen(*paths, "r");
> +
> +               if (!in)
> +                       return error(_("could not open '%s' for reading: %s"),
> +                                       *paths, strerror(errno));
> +
> +               mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
> +
> +               out = fopen(mail, "w");
> +               if (!out)
> +                       return error(_("could not open '%s' for writing: %s"),
> +                                       mail, strerror(errno));
> +
> +               ret = fn(out, in, keep_cr);
> +
> +               fclose(out);
> +               fclose(in);
> +
> +               if (ret)
> +                       return error(_("could not parse patch '%s'"), *paths);
> +       }
> +
> +       state->cur = 1;
> +       state->last = i;
> +       return 0;
> +}
> +
> +/**
> + * A split_mail_conv() callback that converts an StGit patch to an RFC2822
> + * message suitable for parsing with git-mailinfo.
> + */
> +static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)
> +{
> +       struct strbuf sb = STRBUF_INIT;
> +       int subject_printed = 0;
> +
> +       while (!strbuf_getline(&sb, in, '\n')) {
> +               const char *str;
> +
> +               if (str_isspace(sb.buf))
> +                       continue;
> +               else if (skip_prefix(sb.buf, "Author:", &str))
> +                       fprintf(out, "From:%s\n", str);
> +               else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date"))
> +                       fprintf(out, "%s\n", sb.buf);
> +               else if (!subject_printed) {
> +                       fprintf(out, "Subject: %s\n", sb.buf);
> +                       subject_printed = 1;
> +               } else {
> +                       fprintf(out, "\n%s\n", sb.buf);
> +                       break;
> +               }
> +       }
> +
> +       strbuf_reset(&sb);
> +       while (strbuf_fread(&sb, 8192, in) > 0) {
> +               fwrite(sb.buf, 1, sb.len, out);
> +               strbuf_reset(&sb);
> +       }
> +
> +       strbuf_release(&sb);
> +       return 0;
> +}
> +
> +/**
>   * Splits a list of files/directories into individual email patches. Each path
>   * in `paths` must be a file/directory that is formatted according to
>   * `patch_format`.
> @@ -743,6 +869,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
>         switch (patch_format) {
>         case PATCH_FORMAT_MBOX:
>                 return split_mail_mbox(state, paths, keep_cr);
> +       case PATCH_FORMAT_STGIT:
> +               return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
>         default:
>                 die("BUG: invalid patch_format");
>         }
> @@ -1773,6 +1901,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
>
>         if (!strcmp(arg, "mbox"))
>                 *opt_value = PATCH_FORMAT_MBOX;
> +       else if (!strcmp(arg, "stgit"))
> +               *opt_value = PATCH_FORMAT_STGIT;
>         else
>                 return error(_("Invalid value for --patch-format: %s"), arg);
>         return 0;
> --
> 2.5.0.rc0.76.gb2c6e93
>

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 38/44] builtin-am: support and auto-detect StGit patches
  2015-06-29 20:42   ` Stefan Beller
@ 2015-06-29 20:51     ` Eric Sunshine
  2015-06-29 21:39       ` Junio C Hamano
  0 siblings, 1 reply; 74+ messages in thread
From: Eric Sunshine @ 2015-06-29 20:51 UTC (permalink / raw)
  To: Stefan Beller; +Cc: Paul Tan, git, Johannes Schindelin

On Mon, Jun 29, 2015 at 4:42 PM, Stefan Beller <sbeller@google.com> wrote:
> On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan <pyokagan@gmail.com> wrote:
>> +/**
>> + * Returns true if `str` consists of only whitespace, false otherwise.
>> + */
>> +static int str_isspace(const char *str)
>> +{
>> +       while (*str)
>> +               if (!isspace(*(str)++))
>> +                       return 0;
>
> (nit:)
> This looks a bit weird when first reading it, maybe combine the 2 conditions?
>
>     while (*str && !isspace(*(str)++))
>         return 0;
>
> The isspace checks for both tabs and whitespaces IIRC, so SP TAB SP
> would be valid here
> (returning 1).

Ugh. Please don't break the logic with this strange and bogus transformation.

If you really want it to read more idiomatically, try:

    for (; *s; s++)
        if (!isspace(*s))
            return 0;

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 38/44] builtin-am: support and auto-detect StGit patches
  2015-06-29 20:51     ` Eric Sunshine
@ 2015-06-29 21:39       ` Junio C Hamano
  2015-07-01  7:25         ` Paul Tan
  0 siblings, 1 reply; 74+ messages in thread
From: Junio C Hamano @ 2015-06-29 21:39 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Stefan Beller, Paul Tan, git, Johannes Schindelin

Eric Sunshine <sunshine@sunshineco.com> writes:

> On Mon, Jun 29, 2015 at 4:42 PM, Stefan Beller <sbeller@google.com> wrote:
>> On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan <pyokagan@gmail.com> wrote:
>>> +/**
>>> + * Returns true if `str` consists of only whitespace, false otherwise.
>>> + */
>>> +static int str_isspace(const char *str)
>>> +{
>>> +       while (*str)
>>> +               if (!isspace(*(str)++))
>>> +                       return 0;
>> ...
>>     while (*str && !isspace(*(str)++))
>>         return 0;
> ...
> Ugh. Please don't break the logic with this strange and bogus transformation.
>
> If you really want it to read more idiomatically, try:
>
>     for (; *s; s++)
>         if (!isspace(*s))
>             return 0;

;-).

Regardless of the loop structure, I find

	*(str)++

especially ugly and confusing.  I'd understand if it were

	*(str++)

but the parentheses pair is unnecessary.

Not using any increment inside isspace(), like you showed, is the
most readable.

Thanks.

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 31/44] builtin-am: implement -S/--gpg-sign, commit.gpgsign
  2015-06-28 14:05 ` [PATCH v4 31/44] builtin-am: implement -S/--gpg-sign, commit.gpgsign Paul Tan
@ 2015-06-29 23:51   ` Stefan Beller
  2015-07-01  8:01     ` Paul Tan
  0 siblings, 1 reply; 74+ messages in thread
From: Stefan Beller @ 2015-06-29 23:51 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Sun, Jun 28, 2015 at 7:05 AM, Paul Tan <pyokagan@gmail.com> wrote:
> Since 3b4e395 (am: add the --gpg-sign option, 2014-02-01), git-am.sh
> supported the --gpg-sign option, and would pass it to git-commit-tree,
> thus GPG-signing the commit object.
>
> Re-implement this option in builtin/am.c.
>
> git-commit-tree would also sign the commit by default if the
> commit.gpgsign setting is true. Since we do not run commit-tree, we
> re-implement this behavior by handling the commit.gpgsign setting
> ourselves.
>
> Signed-off-by: Paul Tan <pyokagan@gmail.com>
> ---
>  builtin/am.c | 11 ++++++++++-
>  1 file changed, 10 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/am.c b/builtin/am.c
> index 80850e8..d44f5e2 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -124,6 +124,8 @@ struct am_state {
>
>         int ignore_date;
>
> +       const char *sign_commit;
> +
>         int rebasing;
>  };
>
> @@ -134,6 +136,7 @@ struct am_state {
>  static void am_state_init(struct am_state *state, const char *dir)
>  {
>         const char *quiet;
> +       int sign_commit;

I needed to read this patch a few times as this patch introduces `sign_commit`
twice. This is mostly a review problem I'd guess as in the code it
just affects this
method and you'd see all the code of the method easily compared to hunks sent
via email. But renaming this variable doesn't hurt.

>
>         memset(state, 0, sizeof(*state));
>
> @@ -155,6 +158,9 @@ static void am_state_init(struct am_state *state, const char *dir)
>         state->scissors = SCISSORS_UNSET;
>
>         argv_array_init(&state->git_apply_opts);
> +
> +       if (!git_config_get_bool("commit.gpgsign", &sign_commit))
> +               state->sign_commit = sign_commit ? "" : NULL;
>  }
>
>  /**
> @@ -1272,7 +1278,7 @@ static void do_commit(const struct am_state *state)
>                         state->ignore_date ? "" : state->author_date, 1);
>
>         if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
> -                               author, NULL))
> +                               author, state->sign_commit))
>                 die(_("failed to write commit object"));
>
>         reflog_msg = getenv("GIT_REFLOG_ACTION");
> @@ -1694,6 +1700,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
>                         N_("lie about committer date")),
>                 OPT_BOOL(0, "ignore-date", &state.ignore_date,
>                         N_("use current timestamp for author date")),
> +               { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
> +                 N_("GPG-sign commits"),
> +                 PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
>                 OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
>                         N_("(internal use for git-rebase)")),
>                 OPT_END()
> --
> 2.5.0.rc0.76.gb2c6e93
>

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 28/44] builtin-am: pass git-apply's options to git-apply
  2015-06-28 14:05 ` [PATCH v4 28/44] builtin-am: pass git-apply's options to git-apply Paul Tan
@ 2015-06-29 23:56   ` Stefan Beller
  2015-07-01 10:22     ` Paul Tan
  0 siblings, 1 reply; 74+ messages in thread
From: Stefan Beller @ 2015-06-29 23:56 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Sun, Jun 28, 2015 at 7:05 AM, Paul Tan <pyokagan@gmail.com> wrote:
> git-am.sh recognizes some of git-apply's options, and would pass them to
> git-apply:
>
> * --whitespace, since 8c31cb8 (git-am: --whitespace=x option.,
>   2006-02-28)
>
> * -C, since 67dad68 (add -C[NUM] to git-am, 2007-02-08)
>
> * -p, since 2092a1f (Teach git-am to pass -p option down to git-apply,
>   2007-02-11)
>
> * --directory, since b47dfe9 (git-am: add --directory=<dir> option,
>   2009-01-11)
>
> * --reject, since b80da42 (git-am: implement --reject option passed to
>   git-apply, 2009-01-23)
>
> * --ignore-space-change, --ignore-whitespace, since 86c91f9 (git apply:
>   option to ignore whitespace differences, 2009-08-04)
>
> * --exclude, since 77e9e49 (am: pass exclude down to apply, 2011-08-03)
>
> * --include, since 58725ef (am: support --include option, 2012-03-28)
>
> * --reject, since b80da42 (git-am: implement --reject option passed to
>   git-apply, 2009-01-23)
>
> Re-implement support for these options in builtin/am.c.
>
> Signed-off-by: Paul Tan <pyokagan@gmail.com>
> ---
>  builtin/am.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 47 insertions(+)
>
> diff --git a/builtin/am.c b/builtin/am.c
> index 55989e5..5aab627 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -115,6 +115,8 @@ struct am_state {
>         /* one of the enum scissors_type values */
>         int scissors;
>
> +       struct argv_array git_apply_opts;
> +
>         /* override error message when patch failure occurs */
>         const char *resolvemsg;
>
> @@ -147,6 +149,8 @@ static void am_state_init(struct am_state *state, const char *dir)
>         git_config_get_bool("am.messageid", &state->message_id);
>
>         state->scissors = SCISSORS_UNSET;
> +
> +       argv_array_init(&state->git_apply_opts);
>  }
>
>  /**
> @@ -168,6 +172,8 @@ static void am_state_release(struct am_state *state)
>
>         if (state->msg)
>                 free(state->msg);
> +
> +       argv_array_clear(&state->git_apply_opts);
>  }
>
>  /**
> @@ -447,6 +453,11 @@ static void am_load(struct am_state *state)
>         else
>                 state->scissors = SCISSORS_UNSET;
>
> +       read_state_file(&sb, state, "apply-opt", 1);
> +       argv_array_clear(&state->git_apply_opts);
> +       if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0)
> +               die(_("could not parse %s"), am_path(state, "apply-opt"));
> +
>         state->rebasing = !!file_exists(am_path(state, "rebasing"));
>
>         strbuf_release(&sb);
> @@ -621,6 +632,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
>  {
>         unsigned char curr_head[GIT_SHA1_RAWSZ];
>         const char *str;
> +       struct strbuf sb = STRBUF_INIT;
>
>         if (!patch_format)
>                 patch_format = detect_patch_format(paths);
> @@ -683,6 +695,9 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
>
>         write_file(am_path(state, "scissors"), 1, "%s", str);
>
> +       sq_quote_argv(&sb, state->git_apply_opts.argv, 0);
> +       write_file(am_path(state, "apply-opt"), 1, "%s", sb.buf);
> +
>         if (state->rebasing)
>                 write_file(am_path(state, "rebasing"), 1, "%s", "");
>         else
> @@ -707,6 +722,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
>         write_file(am_path(state, "next"), 1, "%d", state->cur);
>
>         write_file(am_path(state, "last"), 1, "%d", state->last);
> +
> +       strbuf_release(&sb);
>  }
>
>  /**
> @@ -1099,6 +1116,8 @@ static int run_apply(const struct am_state *state, const char *index_file)
>
>         argv_array_push(&cp.args, "apply");
>
> +       argv_array_pushv(&cp.args, state->git_apply_opts.argv);
> +
>         if (index_file)
>                 argv_array_push(&cp.args, "--cached");
>         else
> @@ -1125,6 +1144,7 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
>
>         cp.git_cmd = 1;
>         argv_array_push(&cp.args, "apply");
> +       argv_array_pushv(&cp.args, state->git_apply_opts.argv);
>         argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
>         argv_array_push(&cp.args, am_path(state, "patch"));
>
> @@ -1616,9 +1636,36 @@ int cmd_am(int argc, const char **argv, const char *prefix)
>                   PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
>                 OPT_BOOL('c', "scissors", &state.scissors,
>                         N_("strip everything before a scissors line")),
> +               OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
> +                       N_("pass it through git-apply"),
> +                       0),
> +               OPT_PASSTHRU_ARGV(0, "ignore-space-change", &state.git_apply_opts, NULL,
> +                       N_("pass it through git-apply"),
> +                       PARSE_OPT_NOARG),
> +               OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &state.git_apply_opts, NULL,
> +                       N_("pass it through git-apply"),
> +                       PARSE_OPT_NOARG),
> +               OPT_PASSTHRU_ARGV(0, "directory", &state.git_apply_opts, N_("root"),
> +                       N_("pass it through git-apply"),
> +                       0),
> +               OPT_PASSTHRU_ARGV(0, "exclude", &state.git_apply_opts, N_("path"),
> +                       N_("pass it through git-apply"),
> +                       0),
> +               OPT_PASSTHRU_ARGV(0, "include", &state.git_apply_opts, N_("path"),
> +                       N_("pass it through git-apply"),
> +                       0),
> +               OPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_("n"),
> +                       N_("pass it through git-apply"),
> +                       0),
> +               OPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_("num"),
> +                       N_("pass it through git-apply"),
> +                       0),

I realize this was in am.sh as well, but I find the help strings a bit
unfortunate.
(Yes, you actually need to look them up at another place as most people are
not familiar with the apply options).

>                 OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
>                         N_("format the patch(es) are in"),
>                         parse_opt_patchformat),
> +               OPT_PASSTHRU_ARGV(0, "reject", &state.git_apply_opts, NULL,
> +                       N_("pass it through git-apply"),
> +                       PARSE_OPT_NOARG),
>                 OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
>                         N_("override error message when patch failure occurs")),
>                 OPT_CMDMODE(0, "continue", &resume,
> --
> 2.5.0.rc0.76.gb2c6e93
>

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 19/44] builtin-am: implement --3way, am.threeway
  2015-06-28 14:05 ` [PATCH v4 19/44] builtin-am: implement --3way, am.threeway Paul Tan
@ 2015-06-30  0:18   ` Stefan Beller
  0 siblings, 0 replies; 74+ messages in thread
From: Stefan Beller @ 2015-06-30  0:18 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

> +/**
> + * Builds a index that contains just the blobs needed for a 3way merge.

an index

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 00/44] Make git-am a builtin
  2015-06-29  5:01 ` [PATCH v4 00/44] Make git-am a builtin Stefan Beller
@ 2015-06-30  0:19   ` Stefan Beller
  0 siblings, 0 replies; 74+ messages in thread
From: Stefan Beller @ 2015-06-30  0:19 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Sun, Jun 28, 2015 at 10:01 PM, Stefan Beller <sbeller@google.com> wrote:
>> This is a re-roll of [WIP v3]. This patch series is now out of WIP, as ...
>
>> This WIP patch series rewrites git-am.sh into optimized C builtin/am.c, and is
>> part of my GSoC project to rewrite git-pull and git-am into C builtins[1].
>>
>
> I assume the later is just a left over from an old cover letter. :)

The series makes sense to me, all the comments were minor nits.

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 01/44] wrapper: implement xopen()
  2015-06-29  4:48   ` Torsten Bögershausen
  2015-06-29  5:04     ` Stefan Beller
  2015-06-29 17:18     ` Junio C Hamano
@ 2015-07-01  6:04     ` Paul Tan
  2 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-07-01  6:04 UTC (permalink / raw)
  To: Torsten Bögershausen; +Cc: Git List, Johannes Schindelin, Stefan Beller

On Mon, Jun 29, 2015 at 12:48 PM, Torsten Bögershausen <tboegi@web.de> wrote:
> - Having xopen() with 2 or 3 parameter is good, but the current may need
> some tweaks for better portability:
>
> int xopen(const char *path, int oflag, ...)
> {
>         mode_t mode = 0;
>         if (oflag & O_CREAT) {
>                 va_list ap;
>                 va_start(ap, oflag);
>                 mode = va_arg(ap, int);
>                 va_end(ap);
>
>             }
>
> See e.g.
> <http://blitiri.com.ar/git/r/libfiu/c/37f6a98110e3bb59bbb4971241baa3a385c3f724/>
> why va_arg(ap, int) should be used:

Ah OK. I've learned about yet another dark corner of C :-).

Thanks,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 01/44] wrapper: implement xopen()
  2015-06-29 17:18     ` Junio C Hamano
@ 2015-07-01  6:05       ` Paul Tan
  0 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-07-01  6:05 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Torsten Bögershausen, Git List, Johannes Schindelin, Stefan Beller

On Tue, Jun 30, 2015 at 1:18 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Torsten Bögershausen <tboegi@web.de> writes:
>> 2 remarks:
>> - I don't know if and why we need the assert() here (but don't know if
>> we have a strategie in Git for assert())
>
> There is no bright-line rules, but I think it is sensible to remove
> this.  Nobody sane would throw a NULL at open(2) and xopen() is
> supposed to imitate that interface.  We do protect ourselves from
> careless use of our own API, but no need to clutter the code with
> overly zealous check against insane code, I would say.

Ah, okay. Will remove these assert()s.

Regards,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 43/44] builtin-am: check for valid committer ident
  2015-06-29 20:02   ` Stefan Beller
@ 2015-07-01  6:43     ` Paul Tan
  0 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-07-01  6:43 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, Johannes Schindelin

On Tue, Jun 30, 2015 at 4:02 AM, Stefan Beller <sbeller@google.com> wrote:
> On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan <pyokagan@gmail.com> wrote:
>> When commit_tree() is called, if the user does not have an explicit
>> committer ident configured, it will attempt to construct a default
>> committer ident based on the user's and system's info (e.g. gecos field,
>> hostname etc.) However, if a default committer ident is unable to be
>> constructed, commit_tree() will die(). However, at this point of git-am,
>
> s/. However,/, but/ ?

Yeah, thanks.

Regards,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 38/44] builtin-am: support and auto-detect StGit patches
  2015-06-29 21:39       ` Junio C Hamano
@ 2015-07-01  7:25         ` Paul Tan
  0 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-07-01  7:25 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Eric Sunshine, Stefan Beller, git, Johannes Schindelin

On Tue, Jun 30, 2015 at 5:39 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Not using any increment inside isspace(), like you showed, is the
> most readable.

Yup, I agree.

Thanks,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 31/44] builtin-am: implement -S/--gpg-sign, commit.gpgsign
  2015-06-29 23:51   ` Stefan Beller
@ 2015-07-01  8:01     ` Paul Tan
  2015-07-01 16:43       ` Stefan Beller
  0 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-07-01  8:01 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, Johannes Schindelin

On Tue, Jun 30, 2015 at 7:51 AM, Stefan Beller <sbeller@google.com> wrote:
> I needed to read this patch a few times as this patch introduces `sign_commit`
> twice. This is mostly a review problem I'd guess as in the code it
> just affects this
> method and you'd see all the code of the method easily compared to hunks sent
> via email. But renaming this variable doesn't hurt.

OK. What variable name do you want? Would "gpgsign" (to match
commit.gpgsign) do?

Regards,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 40/44] builtin-am: support and auto-detect mercurial patches
  2015-06-29 20:32   ` Stefan Beller
  2015-06-29 20:32     ` Stefan Beller
@ 2015-07-01  8:48     ` Paul Tan
  1 sibling, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-07-01  8:48 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, Johannes Schindelin

On Tue, Jun 30, 2015 at 4:32 AM, Stefan Beller <sbeller@google.com> wrote:
> for calculating the minutes we would only need to take % 3600 (which
> you do), but
> then we still need to divide by 60 to convert seconds to minutes?

Whoops, yes we do. It should be:

tz = tz / 3600 * 100 + tz % 3600 / 60;
tz = -tz;

However...

> What happens if we have a negative input not matching a full hour, say -5400 ?
> (would equate to 0130 in git)

Hmm, I assumed that in C, integer division would always truncate to
zero, but turns out that is only so in C99. In C89, it is
implementation defined whether it rounds towards zero or towards
negative infinity.

So, if the compiler rounds towards negative infinity, then the above
will give the incorrect result. The best solution is probably to
ensure that all the integers are positive:

tz2 = labs(tz) / 3600 * 100 + labs(tz) % 3600 / 60;
if (tz > 0) tz2 = -tz2;

Thanks for bringing this up.

Regards,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 40/44] builtin-am: support and auto-detect mercurial patches
  2015-06-29 20:32     ` Stefan Beller
@ 2015-07-01  9:07       ` Paul Tan
  0 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-07-01  9:07 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, Johannes Schindelin

On Tue, Jun 30, 2015 at 4:32 AM, Stefan Beller <sbeller@google.com> wrote:
> On Mon, Jun 29, 2015 at 1:32 PM, Stefan Beller <sbeller@google.com> wrote:
>> On Sun, Jun 28, 2015 at 7:06 AM, Paul Tan <pyokagan@gmail.com> wrote:
>>> +                       tz = tz / (60 * 60) * 100 + tz % (60 * 60);
>>
>> What happens if we have a negative input not matching a full hour, say -5400 ?
>> (would equate to 0130 in git)
>>
>> for calculating the minutes we would only need to take % 3600 (which
>> you do), but
>> then we still need to divide by 60 to convert seconds to minutes?
>>
>
> That said, I wonder if we have some helper functions around somewhere as we
> need to convert the timezone data at many places.

We convert timezone data at many places? Hmm, I thought all the time
related stuff was confined to date.c.

As far as I know from browsing around the source code, Git keeps the
timezone in the 24-hour format at all times, so there isn't a helper
function for converting from mercurial's "seconds" format to git's
24-hour format.

Thanks,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 28/44] builtin-am: pass git-apply's options to git-apply
  2015-06-29 23:56   ` Stefan Beller
@ 2015-07-01 10:22     ` Paul Tan
  2015-07-01 17:01       ` Stefan Beller
  0 siblings, 1 reply; 74+ messages in thread
From: Paul Tan @ 2015-07-01 10:22 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, Johannes Schindelin

On Tue, Jun 30, 2015 at 7:56 AM, Stefan Beller <sbeller@google.com> wrote:
> I realize this was in am.sh as well, but I find the help strings a bit
> unfortunate.
> (Yes, you actually need to look them up at another place as most people are
> not familiar with the apply options).

Yeah I agree, it would be an improvement. I think the same can be said
for git-mailinfo's and git-mailsplit's options. e.g. "pass -k flag to
git-mailinfo" is not very descriptive either, so we should change
their help strings as well.

Since git-am combines most of the options from git-mailsplit,
git-mailinfo and git-apply together, I wonder if we should split their
options into different groups, e.g:

usage: git am [options] [(<mbox>|<Maildir>)...]
   or: git am [options] (--continue | --skip | --abort)

    -i, --interactive     run interactively
    -3, --3way            allow fall back on 3way merging if needed
    -q, --quiet           be quiet
    -s, --signoff         add a Signed-off-by line to the commit message
    --patch-format <format>
                          format the patch(es) are in
    --resolvemsg ...      override error message when patch failure occurs
    --continue            continue applying patches after resolving a conflict
    -r, --resolved        synonyms for --continue
    --skip                skip the current patch
    --abort               restore the original branch and abort the
patching operation.
    --committer-date-is-author-date
                          lie about committer date
    --ignore-date         use current timestamp for author date
    --rerere-autoupdate   update the index with reused conflict
resolution if possible
    -S, --gpg-sign[=<key-id>]
                          GPG-sign commits

options for git-mailsplit
    --keep-cr             pass --keep-cr flag to git-mailsplit for mbox format
    --no-keep-cr          do not pass --keep-cr flag to git-mailsplit
independent of am.keepcr

options for git-mailinfo
    -u, --utf8            recode into utf8 (default)
    -m, --message-id      pass -m flag to git-mailinfo
    -c, --scissors        strip everything before a scissors line
    -k, --keep            pass -k flag to git-mailinfo
    --keep-non-patch      pass -b flag to git-mailinfo

options for git-apply
    --whitespace <action>
                          detect new or modified lines that have
whitespace errors
    --ignore-space-change
                          ignore changes in whitespace when finding context
    --ignore-whitespace   ignore changes in whitespace when finding context
    --directory <root>    prepend <root> to all filenames
    --exclude <path>      don't apply changes matching the given path
    --include <path>      apply changes matching the given path
    -C <n>                ensure at least <n> lines of context match
    -p <num>              remove <num> leading slashes from
traditional diff paths
    --reject              leave the rejected hunks in corresponding *.rej files

We may wish to put these changes in their own preparatory patch series though.

What do you think?

Regards,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 31/44] builtin-am: implement -S/--gpg-sign, commit.gpgsign
  2015-07-01  8:01     ` Paul Tan
@ 2015-07-01 16:43       ` Stefan Beller
  0 siblings, 0 replies; 74+ messages in thread
From: Stefan Beller @ 2015-07-01 16:43 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Wed, Jul 1, 2015 at 1:01 AM, Paul Tan <pyokagan@gmail.com> wrote:
> On Tue, Jun 30, 2015 at 7:51 AM, Stefan Beller <sbeller@google.com> wrote:
>> I needed to read this patch a few times as this patch introduces `sign_commit`
>> twice. This is mostly a review problem I'd guess as in the code it
>> just affects this
>> method and you'd see all the code of the method easily compared to hunks sent
>> via email. But renaming this variable doesn't hurt.
>
> OK. What variable name do you want? Would "gpgsign" (to match
> commit.gpgsign) do?

Fine with me.

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 28/44] builtin-am: pass git-apply's options to git-apply
  2015-07-01 10:22     ` Paul Tan
@ 2015-07-01 17:01       ` Stefan Beller
  0 siblings, 0 replies; 74+ messages in thread
From: Stefan Beller @ 2015-07-01 17:01 UTC (permalink / raw)
  To: Paul Tan; +Cc: git, Johannes Schindelin

On Wed, Jul 1, 2015 at 3:22 AM, Paul Tan <pyokagan@gmail.com> wrote:
> On Tue, Jun 30, 2015 at 7:56 AM, Stefan Beller <sbeller@google.com> wrote:
>> I realize this was in am.sh as well, but I find the help strings a bit
>> unfortunate.
>> (Yes, you actually need to look them up at another place as most people are
>> not familiar with the apply options).
>
> Yeah I agree, it would be an improvement. I think the same can be said
> for git-mailinfo's and git-mailsplit's options. e.g. "pass -k flag to
> git-mailinfo" is not very descriptive either, so we should change
> their help strings as well.
>
> Since git-am combines most of the options from git-mailsplit,
> git-mailinfo and git-apply together, I wonder if we should split their
> options into different groups, e.g:
>
> usage: git am [options] [(<mbox>|<Maildir>)...]
>    or: git am [options] (--continue | --skip | --abort)
>
>     -i, --interactive     run interactively
>     -3, --3way            allow fall back on 3way merging if needed
>     -q, --quiet           be quiet
>     -s, --signoff         add a Signed-off-by line to the commit message
>     --patch-format <format>
>                           format the patch(es) are in
>     --resolvemsg ...      override error message when patch failure occurs
>     --continue            continue applying patches after resolving a conflict
>     -r, --resolved        synonyms for --continue
>     --skip                skip the current patch
>     --abort               restore the original branch and abort the
> patching operation.
>     --committer-date-is-author-date
>                           lie about committer date
>     --ignore-date         use current timestamp for author date
>     --rerere-autoupdate   update the index with reused conflict
> resolution if possible
>     -S, --gpg-sign[=<key-id>]
>                           GPG-sign commits
>
> options for git-mailsplit
>     --keep-cr             pass --keep-cr flag to git-mailsplit for mbox format
>     --no-keep-cr          do not pass --keep-cr flag to git-mailsplit
> independent of am.keepcr
>
> options for git-mailinfo
>     -u, --utf8            recode into utf8 (default)
>     -m, --message-id      pass -m flag to git-mailinfo
>     -c, --scissors        strip everything before a scissors line
>     -k, --keep            pass -k flag to git-mailinfo
>     --keep-non-patch      pass -b flag to git-mailinfo
>
> options for git-apply
>     --whitespace <action>
>                           detect new or modified lines that have
> whitespace errors
>     --ignore-space-change
>                           ignore changes in whitespace when finding context
>     --ignore-whitespace   ignore changes in whitespace when finding context
>     --directory <root>    prepend <root> to all filenames
>     --exclude <path>      don't apply changes matching the given path
>     --include <path>      apply changes matching the given path
>     -C <n>                ensure at least <n> lines of context match
>     -p <num>              remove <num> leading slashes from
> traditional diff paths
>     --reject              leave the rejected hunks in corresponding *.rej files
>
> We may wish to put these changes in their own preparatory patch series though.
>
> What do you think?

I think this is an improvement!
But as you said, we should do it as an additional patch on top of the series.

Thanks,
Stefan

>
> Regards,
> Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 27/44] builtin-am: implement --[no-]scissors
  2015-06-28 14:05 ` [PATCH v4 27/44] builtin-am: implement --[no-]scissors Paul Tan
@ 2015-07-07  8:23   ` Paul Tan
  0 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-07-07  8:23 UTC (permalink / raw)
  To: Git List; +Cc: Johannes Schindelin, Stefan Beller, Paul Tan

On Sun, Jun 28, 2015 at 10:05 PM, Paul Tan <pyokagan@gmail.com> wrote:
> diff --git a/builtin/am.c b/builtin/am.c
> index 2387726..55989e5 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -74,6 +74,12 @@ enum keep_type {
>         KEEP_NON_PATCH  /* pass -b flag to git-mailinfo */
>  };
>
> +enum scissors_type {
> +       SCISSORS_UNSET = -1,
> +       SCISSORS_TRUE,  /* pass --scissors to git-mailinfo */
> +       SCISSORS_FALSE  /* pass --no-scissors to git-mailinfo */
> +};
> +

Heh, the improved test coverage[1] caught a bug here. Whooops.

[1] http://thread.gmane.org/gmane.comp.version-control.git/273254/focus=273264

Regards,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 04/44] builtin-am: implement patch queue mechanism
  2015-06-29  5:08   ` Stefan Beller
@ 2015-07-07 12:50     ` Paul Tan
  0 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-07-07 12:50 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, Johannes Schindelin

On Mon, Jun 29, 2015 at 1:08 PM, Stefan Beller <sbeller@google.com> wrote:
> (optional nit, bikeshedding)
> In conjunction with the previous patch I just wonder when we put a
> TODO and when we want to put a NEEDSWORK, or if we're being
> inconsistent here as both issues will be resolved in a later patch
> in the series.

That's a code style thing that I don't personally have a strong opinion about.

Not sure if the following means anything, but on master,

git grep '\bTODO\b' | wc -l
102

git grep '\bNEEDSWORK\b' | wc -l
45

git log -G'\bTODO\b --oneline | wc -l
185

git log -G'\bNEEDSWORK\b' --oneline | wc -l
120

So it does seem that temporary stuff is usually tagged with
"NEEDSWORK", rather than "TODO".

Anyway, it's probably better to be consistent, so I will switch to "NEEDSWORK".

Thanks,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

* Re: [PATCH v4 42/44] builtin-am: implement legacy -b/--binary option
       [not found]     ` <CABPQNSakaoyNRuNz=bcDYWdy4e2O3M4UuYoOT5JAV1mt-BiAOw@mail.gmail.com>
@ 2015-07-14 10:09       ` Paul Tan
  0 siblings, 0 replies; 74+ messages in thread
From: Paul Tan @ 2015-07-14 10:09 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Johannes Schindelin, GIT Mailing-list, kusmabite

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

Junio, what do you think?

Regards,
Paul

^ permalink raw reply	[flat|nested] 74+ messages in thread

end of thread, other threads:[~2015-07-14 10:10 UTC | newest]

Thread overview: 74+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-06-28 14:05 [PATCH v4 00/44] Make git-am a builtin Paul Tan
2015-06-28 14:05 ` [PATCH v4 01/44] wrapper: implement xopen() Paul Tan
2015-06-29  4:48   ` Torsten Bögershausen
2015-06-29  5:04     ` Stefan Beller
2015-06-29 17:18     ` Junio C Hamano
2015-07-01  6:05       ` Paul Tan
2015-07-01  6:04     ` Paul Tan
2015-06-28 14:05 ` [PATCH v4 02/44] wrapper: implement xfopen() Paul Tan
2015-06-28 14:05 ` [PATCH v4 03/44] builtin-am: implement skeletal builtin am Paul Tan
2015-06-28 14:05 ` [PATCH v4 04/44] builtin-am: implement patch queue mechanism Paul Tan
2015-06-29  5:08   ` Stefan Beller
2015-07-07 12:50     ` Paul Tan
2015-06-28 14:05 ` [PATCH v4 05/44] builtin-am: split out mbox/maildir patches with git-mailsplit Paul Tan
2015-06-28 14:05 ` [PATCH v4 06/44] builtin-am: auto-detect mbox patches Paul Tan
2015-06-28 14:05 ` [PATCH v4 07/44] builtin-am: extract patch and commit info with git-mailinfo Paul Tan
2015-06-28 14:05 ` [PATCH v4 08/44] builtin-am: apply patch with git-apply Paul Tan
2015-06-28 14:05 ` [PATCH v4 09/44] builtin-am: implement committing applied patch Paul Tan
2015-06-28 14:05 ` [PATCH v4 10/44] builtin-am: refuse to apply patches if index is dirty Paul Tan
2015-06-28 14:05 ` [PATCH v4 11/44] builtin-am: implement --resolved/--continue Paul Tan
2015-06-28 14:05 ` [PATCH v4 12/44] builtin-am: implement --skip Paul Tan
2015-06-28 14:05 ` [PATCH v4 13/44] builtin-am: implement --abort Paul Tan
2015-06-28 14:05 ` [PATCH v4 14/44] builtin-am: reject patches when there's a session in progress Paul Tan
2015-06-28 14:05 ` [PATCH v4 15/44] builtin-am: implement -q/--quiet, GIT_QUIET Paul Tan
2015-06-28 14:05 ` [PATCH v4 16/44] builtin-am: exit with user friendly message on failure Paul Tan
2015-06-28 14:05 ` [PATCH v4 17/44] builtin-am: implement -s/--signoff Paul Tan
2015-06-28 14:05 ` [PATCH v4 18/44] cache-tree: introduce write_index_as_tree() Paul Tan
2015-06-28 14:05 ` [PATCH v4 19/44] builtin-am: implement --3way, am.threeway Paul Tan
2015-06-30  0:18   ` Stefan Beller
2015-06-28 14:05 ` [PATCH v4 20/44] builtin-am: implement --rebasing mode Paul Tan
2015-06-28 14:05 ` [PATCH v4 21/44] builtin-am: bypass git-mailinfo when --rebasing Paul Tan
2015-06-28 14:05 ` [PATCH v4 22/44] builtin-am: handle stray state directory Paul Tan
2015-06-28 14:05 ` [PATCH v4 23/44] builtin-am: implement -u/--utf8 Paul Tan
2015-06-28 14:05 ` [PATCH v4 24/44] builtin-am: implement -k/--keep, --keep-non-patch Paul Tan
2015-06-28 14:05 ` [PATCH v4 25/44] builtin-am: implement --[no-]message-id, am.messageid Paul Tan
2015-06-28 14:05 ` [PATCH v4 26/44] builtin-am: support --keep-cr, am.keepcr Paul Tan
2015-06-28 14:05 ` [PATCH v4 27/44] builtin-am: implement --[no-]scissors Paul Tan
2015-07-07  8:23   ` Paul Tan
2015-06-28 14:05 ` [PATCH v4 28/44] builtin-am: pass git-apply's options to git-apply Paul Tan
2015-06-29 23:56   ` Stefan Beller
2015-07-01 10:22     ` Paul Tan
2015-07-01 17:01       ` Stefan Beller
2015-06-28 14:05 ` [PATCH v4 29/44] builtin-am: implement --ignore-date Paul Tan
2015-06-28 14:05 ` [PATCH v4 30/44] builtin-am: implement --committer-date-is-author-date Paul Tan
2015-06-28 14:05 ` [PATCH v4 31/44] builtin-am: implement -S/--gpg-sign, commit.gpgsign Paul Tan
2015-06-29 23:51   ` Stefan Beller
2015-07-01  8:01     ` Paul Tan
2015-07-01 16:43       ` Stefan Beller
2015-06-28 14:05 ` [PATCH v4 32/44] builtin-am: invoke post-rewrite hook Paul Tan
2015-06-28 14:05 ` [PATCH v4 33/44] builtin-am: support automatic notes copying Paul Tan
2015-06-28 14:05 ` [PATCH v4 34/44] builtin-am: invoke applypatch-msg hook Paul Tan
2015-06-28 14:05 ` [PATCH v4 35/44] builtin-am: invoke pre-applypatch hook Paul Tan
2015-06-28 14:05 ` [PATCH v4 36/44] builtin-am: invoke post-applypatch hook Paul Tan
2015-06-28 14:05 ` [PATCH v4 37/44] builtin-am: rerere support Paul Tan
2015-06-28 14:06 ` [PATCH v4 38/44] builtin-am: support and auto-detect StGit patches Paul Tan
2015-06-29 20:42   ` Stefan Beller
2015-06-29 20:51     ` Eric Sunshine
2015-06-29 21:39       ` Junio C Hamano
2015-07-01  7:25         ` Paul Tan
2015-06-28 14:06 ` [PATCH v4 39/44] builtin-am: support and auto-detect StGit series files Paul Tan
2015-06-28 14:06 ` [PATCH v4 40/44] builtin-am: support and auto-detect mercurial patches Paul Tan
2015-06-29 20:32   ` Stefan Beller
2015-06-29 20:32     ` Stefan Beller
2015-07-01  9:07       ` Paul Tan
2015-07-01  8:48     ` Paul Tan
2015-06-28 14:06 ` [PATCH v4 41/44] builtin-am: implement -i/--interactive Paul Tan
2015-06-28 14:06 ` [PATCH v4 42/44] builtin-am: implement legacy -b/--binary option Paul Tan
2015-06-29 20:05   ` Stefan Beller
     [not found]     ` <CABPQNSakaoyNRuNz=bcDYWdy4e2O3M4UuYoOT5JAV1mt-BiAOw@mail.gmail.com>
2015-07-14 10:09       ` Paul Tan
2015-06-28 14:06 ` [PATCH v4 43/44] builtin-am: check for valid committer ident Paul Tan
2015-06-29 20:02   ` Stefan Beller
2015-07-01  6:43     ` Paul Tan
2015-06-28 14:06 ` [PATCH v4 44/44] builtin-am: remove redirection to git-am.sh Paul Tan
2015-06-29  5:01 ` [PATCH v4 00/44] Make git-am a builtin Stefan Beller
2015-06-30  0:19   ` Stefan Beller

Code repositories for project(s) associated with this inbox:

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).