From fcb3d0adc13b398c930814a5e0c886717acc1b70 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Date: Sun, 25 Jun 2006 03:51:08 +0200
Subject: [PATCH 1/7] add diff_flush_patch_id() to calculate the patch id

Call it like this:

unsigned char id[20];
if (diff_flush_patch_id(diff_options, id))
	printf("And the patch id is: %s\n", sha1_to_hex(id));

Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 diff.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 diff.h |   2 +
 2 files changed, 141 insertions(+)

diff --git a/diff.c b/diff.c
index fbb6c26cd92..afb629e613e 100644
--- a/diff.c
+++ b/diff.c
@@ -2104,6 +2104,145 @@ static void diff_summary(struct diff_filepair *p)
 	}
 }
 
+struct patch_id_t {
+	struct xdiff_emit_state xm;
+	SHA_CTX *ctx;
+	int patchlen;
+};
+
+static int remove_space(char *line, int len)
+{
+	int i;
+        char *dst = line;
+        unsigned char c;
+
+        for (i = 0; i < len; i++)
+                if (!isspace((c = line[i])))
+                        *dst++ = c;
+
+        return dst - line;
+}
+
+static void patch_id_consume(void *priv, char *line, unsigned long len)
+{
+	struct patch_id_t *data = priv;
+	int new_len;
+
+	/* Ignore line numbers when computing the SHA1 of the patch */
+	if (!strncmp(line, "@@ -", 4))
+		return;
+
+	new_len = remove_space(line, len);
+
+	SHA1_Update(data->ctx, line, new_len);
+	data->patchlen += new_len;
+}
+
+/* returns 0 upon success, and writes result into sha1 */
+static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
+{
+	struct diff_queue_struct *q = &diff_queued_diff;
+	int i;
+	SHA_CTX ctx;
+	struct patch_id_t data;
+	char buffer[PATH_MAX * 4 + 20];
+
+	SHA1_Init(&ctx);
+	memset(&data, 0, sizeof(struct patch_id_t));
+	data.ctx = &ctx;
+	data.xm.consume = patch_id_consume;
+
+	for (i = 0; i < q->nr; i++) {
+		xpparam_t xpp;
+		xdemitconf_t xecfg;
+		xdemitcb_t ecb;
+		mmfile_t mf1, mf2;
+		struct diff_filepair *p = q->queue[i];
+		int len1, len2;
+
+		if (p->status == 0)
+			return error("internal diff status error");
+		if (p->status == DIFF_STATUS_UNKNOWN)
+			continue;
+		if (diff_unmodified_pair(p))
+			continue;
+		if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+		    (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+			continue;
+		if (DIFF_PAIR_UNMERGED(p))
+			continue;
+
+		diff_fill_sha1_info(p->one);
+		diff_fill_sha1_info(p->two);
+		if (fill_mmfile(&mf1, p->one) < 0 ||
+				fill_mmfile(&mf2, p->two) < 0)
+			return error("unable to read files to diff");
+
+		/* Maybe hash p->two? into the patch id? */
+		if (mmfile_is_binary(&mf2))
+			continue;
+
+		len1 = remove_space(p->one->path, strlen(p->one->path));
+		len2 = remove_space(p->two->path, strlen(p->two->path));
+		if (p->one->mode == 0)
+			len1 = snprintf(buffer, sizeof(buffer),
+					"diff--gita/%.*sb/%.*s"
+					"newfilemode%06o"
+					"---/dev/null"
+					"+++b/%.*s",
+					len1, p->one->path,
+					len2, p->two->path,
+					p->two->mode,
+					len2, p->two->path);
+		else if (p->two->mode == 0)
+			len1 = snprintf(buffer, sizeof(buffer),
+					"diff--gita/%.*sb/%.*s"
+					"deletedfilemode%06o"
+					"---a/%.*s"
+					"+++/dev/null",
+					len1, p->one->path,
+					len2, p->two->path,
+					p->one->mode,
+					len1, p->one->path);
+		else
+			len1 = snprintf(buffer, sizeof(buffer),
+					"diff--gita/%.*sb/%.*s"
+					"---a/%.*s"
+					"+++b/%.*s",
+					len1, p->one->path,
+					len2, p->two->path,
+					len1, p->one->path,
+					len2, p->two->path);
+		SHA1_Update(&ctx, buffer, len1);
+
+		xpp.flags = XDF_NEED_MINIMAL;
+		xecfg.ctxlen = 3;
+		xecfg.flags = 3;
+		ecb.outf = xdiff_outf;
+		ecb.priv = &data;
+		xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+	}
+
+	SHA1_Final(sha1, &ctx);
+	return 0;
+}
+
+int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
+{
+	struct diff_queue_struct *q = &diff_queued_diff;
+	int i;
+	int result = diff_get_patch_id(options, sha1);
+
+	for (i = 0; i < q->nr; i++)
+		diff_free_filepair(q->queue[i]);
+
+	free(q->queue);
+	q->queue = NULL;
+	q->nr = q->alloc = 0;
+
+	return result;
+}
+
 void diff_flush(struct diff_options *options)
 {
 	struct diff_queue_struct *q = &diff_queued_diff;
diff --git a/diff.h b/diff.h
index b61fdc8f1e4..d5068af7d16 100644
--- a/diff.h
+++ b/diff.h
@@ -184,4 +184,6 @@ extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
 
 extern int run_diff_index(struct rev_info *revs, int cached);
 
+extern int diff_flush_patch_id(struct diff_options *, unsigned char *);
+
 #endif /* DIFF_H */

From 9c6efa366eed0526e82a052b77227451273af0c3 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Date: Sun, 25 Jun 2006 03:52:01 +0200
Subject: [PATCH 2/7] format-patch: introduce "--ignore-if-in-upstream"

With this flag, format-patch will try very hard not to output patches which
are already in the upstream branch.

Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 builtin-log.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 80 insertions(+)

diff --git a/builtin-log.c b/builtin-log.c
index 44d2d136f58..4ee5891dfd6 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -160,6 +160,71 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
 	freopen(filename, "w", stdout);
 }
 
+static void reset_all_objects_flags()
+{
+	int i;
+
+	for (i = 0; i < obj_allocs; i++)
+		if (objs[i])
+			objs[i]->flags = 0;
+}
+
+static int get_patch_id(struct commit *commit, struct diff_options *options,
+		unsigned char *sha1)
+{
+	diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1,
+			"", options);
+	diffcore_std(options);
+	return diff_flush_patch_id(options, sha1);
+}
+
+static void get_patch_ids(struct rev_info *rev, struct diff_options *options)
+{
+	struct rev_info check_rev;
+	struct commit *commit;
+	struct object *o1, *o2;
+	unsigned flags1, flags2;
+	unsigned char sha1[20];
+
+	if (rev->pending.nr != 2)
+		die("Need exactly one range.");
+
+	o1 = rev->pending.objects[0].item;
+	flags1 = o1->flags;
+	o2 = rev->pending.objects[1].item;
+	flags2 = o2->flags;
+
+	if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
+		die("Not a range.");
+
+	diff_setup(options);
+	options->recursive = 1;
+	if (diff_setup_done(options) < 0)
+		die("diff_setup_done failed");
+
+	/* given a range a..b get all patch ids for b..a */
+	init_revisions(&check_rev);
+	o1->flags ^= UNINTERESTING;
+	o2->flags ^= UNINTERESTING;
+	add_pending_object(&check_rev, o1, "o1");
+	add_pending_object(&check_rev, o2, "o2");
+	prepare_revision_walk(&check_rev);
+
+	while ((commit = get_revision(&check_rev)) != NULL) {
+		/* ignore merges */
+		if (commit->parents && commit->parents->next)
+			continue;
+
+		if (!get_patch_id(commit, options, sha1))
+			created_object(sha1, xcalloc(1, sizeof(struct object)));
+	}
+
+	/* reset for next revision walk */
+	reset_all_objects_flags();
+	o1->flags = flags1;
+	o2->flags = flags2;
+}
+
 int cmd_format_patch(int argc, const char **argv, char **envp)
 {
 	struct commit *commit;
@@ -170,6 +235,8 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
 	int numbered = 0;
 	int start_number = -1;
 	int keep_subject = 0;
+	int ignore_if_in_upstream = 0;
+	struct diff_options patch_id_opts;
 	char *add_signoff = NULL;
 
 	init_revisions(&rev);
@@ -235,6 +302,8 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
 			rev.mime_boundary = git_version_string;
 		else if (!strncmp(argv[i], "--attach=", 9))
 			rev.mime_boundary = argv[i] + 9;
+		else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
+			ignore_if_in_upstream = 1;
 		else
 			argv[j++] = argv[i];
 	}
@@ -262,14 +331,25 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
 		add_head(&rev);
 	}
 
+	if (ignore_if_in_upstream)
+		get_patch_ids(&rev, &patch_id_opts);
+
 	if (!use_stdout)
 		realstdout = fdopen(dup(1), "w");
 
 	prepare_revision_walk(&rev);
 	while ((commit = get_revision(&rev)) != NULL) {
+		unsigned char sha1[20];
+
 		/* ignore merges */
 		if (commit->parents && commit->parents->next)
 			continue;
+
+		if (ignore_if_in_upstream &&
+				!get_patch_id(commit, &patch_id_opts, sha1) &&
+				lookup_object(sha1))
+			continue;
+
 		nr++;
 		list = realloc(list, nr * sizeof(list[0]));
 		list[nr - 1] = commit;

From ece3c67f9c8f0074cae76204a648cbfc6075bb44 Mon Sep 17 00:00:00 2001
From: Junio C Hamano <junkio@cox.net>
Date: Mon, 26 Jun 2006 15:40:09 -0700
Subject: [PATCH 3/7] t4014: add format-patch --ignore-if-in-upstream test

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 t/t4014-format-patch.sh | 69 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)
 create mode 100755 t/t4014-format-patch.sh

diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
new file mode 100755
index 00000000000..c044044f809
--- /dev/null
+++ b/t/t4014-format-patch.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='Format-patch skipping already incorporated patches'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+	for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i"; done >file &&
+	git add file &&
+	git commit -m Initial &&
+	git checkout -b side &&
+
+	for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file &&
+	git update-index file &&
+	git commit -m "Side change #1" &&
+
+	for i in D E F; do echo "$i"; done >>file &&
+	git update-index file &&
+	git commit -m "Side change #2" &&
+	git tag C1 &&
+
+	for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >file &&
+	git update-index file &&
+	git commit -m "Side change #3" &&
+
+	git checkout master &&
+	git diff-tree -p C1 | git apply --index &&
+	git commit -m "Master accepts moral equivalent of #1"
+
+'
+
+test_expect_success "format-patch --ignore-if-in-upstream" '
+
+	git format-patch --stdout master..side >patch0 &&
+	cnt=`grep "^From " patch0 | wc -l` &&
+	test "$cnt" = 3
+
+'
+
+test_expect_success "format-patch --ignore-if-in-upstream" '
+
+	git format-patch --stdout \
+		--ignore-if-in-upstream master..side >patch1 &&
+	cnt=`grep "^From " patch1 | wc -l` &&
+	test "$cnt" = 2
+
+'
+
+test_expect_success "format-patch result applies" '
+
+	git checkout -b rebuild-0 master &&
+	git am -3 patch0 &&
+	cnt=`git rev-list master.. | wc -l` &&
+	test "$cnt" = 2
+'
+
+test_expect_success "format-patch --ignore-if-in-upstream result applies" '
+
+	git checkout -b rebuild-1 master &&
+	git am -3 patch1 &&
+	cnt=`git rev-list master.. | wc -l` &&
+	test "$cnt" = 2
+'
+
+test_done

From 8780bd8fd2fb9a85c39de8d7d3105b94c6a9cf40 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Date: Tue, 27 Jun 2006 10:12:12 +0200
Subject: [PATCH 4/7] t4014: fix for whitespace from "wc -l"

Some "wc" insist on putting a TAB in front of the number.

Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 t/t4014-format-patch.sh | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index c044044f809..ac2fde773d9 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -37,7 +37,7 @@ test_expect_success "format-patch --ignore-if-in-upstream" '
 
 	git format-patch --stdout master..side >patch0 &&
 	cnt=`grep "^From " patch0 | wc -l` &&
-	test "$cnt" = 3
+	test $cnt = 3
 
 '
 
@@ -46,7 +46,7 @@ test_expect_success "format-patch --ignore-if-in-upstream" '
 	git format-patch --stdout \
 		--ignore-if-in-upstream master..side >patch1 &&
 	cnt=`grep "^From " patch1 | wc -l` &&
-	test "$cnt" = 2
+	test $cnt = 2
 
 '
 
@@ -55,7 +55,7 @@ test_expect_success "format-patch result applies" '
 	git checkout -b rebuild-0 master &&
 	git am -3 patch0 &&
 	cnt=`git rev-list master.. | wc -l` &&
-	test "$cnt" = 2
+	test $cnt = 2
 '
 
 test_expect_success "format-patch --ignore-if-in-upstream result applies" '
@@ -63,7 +63,7 @@ test_expect_success "format-patch --ignore-if-in-upstream result applies" '
 	git checkout -b rebuild-1 master &&
 	git am -3 patch1 &&
 	cnt=`git rev-list master.. | wc -l` &&
-	test "$cnt" = 2
+	test $cnt = 2
 '
 
 test_done

From 81db094107df9a73a0be4538a74a26115a84dd13 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Date: Tue, 27 Jun 2006 22:38:04 +0200
Subject: [PATCH 5/7] format-patch: use clear_commit_marks() instead of some
 ad-hockery

It is cleaner, and it describes better what the idea behind the code is.

Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 builtin-log.c | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/builtin-log.c b/builtin-log.c
index 4ee5891dfd6..f9515a8a4ac 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -160,15 +160,6 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
 	freopen(filename, "w", stdout);
 }
 
-static void reset_all_objects_flags()
-{
-	int i;
-
-	for (i = 0; i < obj_allocs; i++)
-		if (objs[i])
-			objs[i]->flags = 0;
-}
-
 static int get_patch_id(struct commit *commit, struct diff_options *options,
 		unsigned char *sha1)
 {
@@ -220,7 +211,10 @@ static void get_patch_ids(struct rev_info *rev, struct diff_options *options)
 	}
 
 	/* reset for next revision walk */
-	reset_all_objects_flags();
+	clear_commit_marks((struct commit *)o1,
+			SEEN | UNINTERESTING | SHOWN | ADDED);
+	clear_commit_marks((struct commit *)o2,
+			SEEN | UNINTERESTING | SHOWN | ADDED);
 	o1->flags = flags1;
 	o2->flags = flags2;
 }

From 982b64e4ccc993e51a7e7a1e1713953ecf95e4f2 Mon Sep 17 00:00:00 2001
From: Junio C Hamano <junkio@cox.net>
Date: Wed, 28 Jun 2006 22:48:34 -0700
Subject: [PATCH 6/7] t4014: fix test commit labels.

The commit tag and commit comments used in the test claimed that
the #1 commit was merged upstream where the test actually let the
upstream merge #2 commit.  Fix them.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 t/t4014-format-patch.sh | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index ac2fde773d9..4795872a776 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -21,15 +21,15 @@ test_expect_success setup '
 	for i in D E F; do echo "$i"; done >>file &&
 	git update-index file &&
 	git commit -m "Side change #2" &&
-	git tag C1 &&
+	git tag C2 &&
 
 	for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >file &&
 	git update-index file &&
 	git commit -m "Side change #3" &&
 
 	git checkout master &&
-	git diff-tree -p C1 | git apply --index &&
-	git commit -m "Master accepts moral equivalent of #1"
+	git diff-tree -p C2 | git apply --index &&
+	git commit -m "Master accepts moral equivalent of #2"
 
 '
 

From 9fdc3bb5c20e37e410e03ed5b6c71a7e647ccee8 Mon Sep 17 00:00:00 2001
From: Junio C Hamano <junkio@cox.net>
Date: Wed, 28 Jun 2006 22:49:42 -0700
Subject: [PATCH 7/7] diff.c: fix get_patch_id()

The function internally generated diff to get the patch id but
passed a wrong emit flags to the xdiff layer when it did so.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 diff.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/diff.c b/diff.c
index afb629e613e..5a71489a471 100644
--- a/diff.c
+++ b/diff.c
@@ -2217,7 +2217,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
 
 		xpp.flags = XDF_NEED_MINIMAL;
 		xecfg.ctxlen = 3;
-		xecfg.flags = 3;
+		xecfg.flags = XDL_EMIT_FUNCNAMES;
 		ecb.outf = xdiff_outf;
 		ecb.priv = &data;
 		xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);