Subject: [PATCH v4] add --summary option to git-push and git-fetch

From: Larry D'Anna <[email-masked]>
Date: Sat, 11 Jul 2009 13:41:56 -0400

--summary will cause git-push to output a one-line of each commit pushed.
--summary=n will display at most n commits for each ref pushed.

$ git push --dry-run --summary origin :
To /home/larry/gitsandbox/a
   80f0e50..5593a38  master -> master
    > 5593a38 foo
    > 81c03f8 bar

Fetch works the same way.

Signed-off-by: Larry D'Anna <[email-masked]>
---
 
 Changes sicne last version of this patch: 

    * added some tests

 Documentation/fetch-options.txt |    7 ++++
 Documentation/git-push.txt      |    6 +++
 builtin-fetch.c                 |   24 +++++++++---
 builtin-log.c                   |   42 +++++++++++++++++++++
 builtin-push.c                  |   12 +++++--
 builtin.h                       |    2 +
 t/t5516-fetch-push.sh           |   77 +++++++++++++++++++++++++++++++++++++++
 transport.c                     |   39 ++++++++++++++------
 transport.h                     |    2 +-
 9 files changed, 189 insertions(+), 22 deletions(-)

diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index d313795..2e66d5e 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -27,6 +27,13 @@
 	fetches is a descendant of `<lbranch>`.  This option
 	overrides that check.
 
+--summary::
+	Print a one-line summary of each commit fetched.
+
+--summary=<n>::
+	Like --summary, but with a limit of <n> commits per ref.
+
+
 ifdef::git-pull[]
 --no-tags::
 endif::git-pull[]
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 2653388..803fe36 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -85,6 +85,12 @@ nor in any Push line of the corresponding remotes file---see below).
 --dry-run::
 	Do everything except actually send the updates.
 
+--summary::
+	Print a one-line summary of each commit pushed.
+
+--summary=<n>::
+	Like --summary, but with a limit of <n> commits per ref.
+
 --porcelain::
 	Produce machine-readable output.  The output status line for each ref
 	will be tab-separated and sent to stdout instead of stderr.  The full
diff --git a/builtin-fetch.c b/builtin-fetch.c
index cd5eb9a..c98d06b 100644
--- a/builtin-fetch.c
+++ b/builtin-fetch.c
@@ -29,6 +29,7 @@ static const char *depth;
 static const char *upload_pack;
 static struct strbuf default_rla = STRBUF_INIT;
 static struct transport *transport;
+static int summary;
 
 static struct option builtin_fetch_options[] = {
 	OPT__VERBOSITY(&verbosity),
@@ -47,6 +48,9 @@ static struct option builtin_fetch_options[] = {
 		    "allow updating of HEAD ref"),
 	OPT_STRING(0, "depth", &depth, "DEPTH",
 		   "deepen history of shallow clone"),
+	{ OPTION_INTEGER, 0, "summary", &summary, "n", "print a summary of [at most n] fetched commits",
+	  PARSE_OPT_OPTARG, NULL, 20
+	},
 	OPT_END()
 };
 
@@ -197,7 +201,8 @@ static int s_update_ref(const char *action,
 
 static int update_local_ref(struct ref *ref,
 			    const char *remote,
-			    char *display)
+			    char *display,
+			    char *quickref)
 {
 	struct commit *current = NULL, *updated;
 	enum object_type type;
@@ -260,11 +265,12 @@ static int update_local_ref(struct ref *ref,
 		sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
 			SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
 			r ? "  (unable to update local ref)" : "");
+		if (!r)
+			strcpy(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
 		return r;
 	}
 
 	if (in_merge_bases(current, &updated, 1)) {
-		char quickref[83];
 		int r;
 		strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
 		strcat(quickref, "..");
@@ -275,7 +281,6 @@ static int update_local_ref(struct ref *ref,
 			pretty_ref, r ? "  (unable to update local ref)" : "");
 		return r;
 	} else if (force || ref->force) {
-		char quickref[84];
 		int r;
 		strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
 		strcat(quickref, "...");
@@ -301,6 +306,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 	struct commit *commit;
 	int url_len, i, note_len, shown_url = 0, rc = 0;
 	char note[1024];
+	char quickref[84];
 	const char *what, *kind;
 	struct ref *rm;
 	char *url, *filename = git_path("FETCH_HEAD");
@@ -373,12 +379,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 				fputc(url[i], fp);
 		fputc('\n', fp);
 
-		if (ref)
-			rc |= update_local_ref(ref, what, note);
-		else
+		if (ref) {
+			*quickref = 0;
+			rc |= update_local_ref(ref, what, note, quickref);
+		} else {
+			strcpy(quickref, find_unique_abbrev(rm->old_sha1, DEFAULT_ABBREV));
 			sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
 				SUMMARY_WIDTH, *kind ? kind : "branch",
 				 REFCOL_WIDTH, *what ? what : "HEAD");
+		}
 		if (*note) {
 			if (verbosity >= 0 && !shown_url) {
 				fprintf(stderr, "From %.*s\n",
@@ -388,6 +397,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 			if (verbosity >= 0)
 				fprintf(stderr, " %s\n", note);
 		}
+		if (summary && quickref[0])
+			print_summary_for_push_or_fetch(quickref, summary);
+
 	}
 	free(url);
 	fclose(fp);
diff --git a/builtin-log.c b/builtin-log.c
index 0c2fa0a..a09670c 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -1293,3 +1293,45 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
 	free_patch_ids(&ids);
 	return 0;
 }
+
+
+void print_summary_for_push_or_fetch(const char *quickref, int limit)
+{
+	struct rev_info rev;
+	int i, max;
+	struct object *obj;
+	struct commit *commit;
+
+	max = get_max_object_index();
+	for (i = 0; i < max; i++)  {
+		obj = get_indexed_object(i);
+		if (obj)
+			obj->flags &= ~ALL_REV_FLAGS;
+	}
+
+	init_revisions(&rev, NULL);
+	rev.prune = 0;
+	assert(!handle_revision_arg(quickref, &rev, 0, 1));
+	assert(!prepare_revision_walk(&rev));
+
+	while ((commit = get_revision(&rev)) != NULL) {
+		struct strbuf buf = STRBUF_INIT;
+		if (limit == 0) {
+			fprintf(stderr, "    ...\n");
+			break;
+		}
+		if (!commit->buffer) {
+			enum object_type type;
+			unsigned long size;
+			commit->buffer =
+				read_sha1_file(commit->object.sha1, &type, &size);
+			if (!commit->buffer)
+				die("Cannot read commit %s", sha1_to_hex(commit->object.sha1));
+		}
+		format_commit_message(commit, "    %m %h %s\n", &buf, 0);
+		fputs(buf.buf, stderr);
+		strbuf_release(&buf);
+		limit--;
+	}
+	fputs("\n", stderr);
+}
diff --git a/builtin-push.c b/builtin-push.c
index 0a0297f..d38f9a8 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -113,7 +113,7 @@ static void setup_default_push_refspecs(void)
 	}
 }
 
-static int do_push(const char *repo, int flags)
+static int do_push(const char *repo, int flags, int summary)
 {
 	int i, errs;
 	struct remote *remote = remote_get(repo);
@@ -173,7 +173,7 @@ static int do_push(const char *repo, int flags)
 
 		if (flags & TRANSPORT_PUSH_VERBOSE)
 			fprintf(stderr, "Pushing to %s\n", url[i]);
-		err = transport_push(transport, refspec_nr, refspec, flags);
+		err = transport_push(transport, refspec_nr, refspec, flags, summary);
 		err |= transport_disconnect(transport);
 
 		if (!err)
@@ -192,6 +192,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 	int rc;
 	const char *repo = NULL;	/* default repository */
 
+	int summary = 0;
+
 	struct option options[] = {
 		OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE),
 		OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
@@ -205,6 +207,10 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 		OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
 		OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
 		OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
+		{ OPTION_INTEGER, 0, "summary", &summary, "n", "print a summary of [at most n] pushed commits",
+		  PARSE_OPT_OPTARG, NULL, 20
+		},
+
 		OPT_END()
 	};
 
@@ -218,7 +224,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 		set_refspecs(argv + 1, argc - 1);
 	}
 
-	rc = do_push(repo, flags);
+	rc = do_push(repo, flags, summary);
 	if (rc == -1)
 		usage_with_options(push_usage, options);
 	else
diff --git a/builtin.h b/builtin.h
index 20427d2..5aea3a3 100644
--- a/builtin.h
+++ b/builtin.h
@@ -113,4 +113,6 @@ extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_refs(int argc, const char **argv, const char *prefix);
 
+extern void print_summary_for_push_or_fetch(const char *quickref, int limit);
+
 #endif
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 2d2633f..cae270d 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -90,6 +90,48 @@ test_expect_success 'fetch without wildcard' '
 	)
 '
 
+test_expect_success 'fetch --summary' '
+	mk_empty &&
+	(
+		cd testrepo &&
+		git fetch --summary .. refs/heads/master:refs/remotes/origin/master 2>stderr &&
+
+		r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+		test "z$r" = "z$the_commit" &&
+
+		test 1 = $(git for-each-ref refs/remotes/origin | wc -l) &&
+
+		grep -E  "^    > [a-fA-F0-9]+ second$" stderr &&
+		grep -E  "^    > [a-fA-F0-9]+ repo$" stderr
+	)
+        '
+
+test_expect_success 'fetch --summary forced update' '
+	mk_empty &&
+	(
+		cd testrepo &&
+		git fetch .. refs/heads/master:refs/remotes/origin/master &&
+
+		git checkout refs/remotes/origin/master^ &&
+		: >path3 &&
+		git add path3 &&
+		test_tick &&
+		git commit -a -m third &&
+		git update-ref refs/remotes/origin/master HEAD &&
+
+		git fetch .. -f --summary refs/heads/master:refs/remotes/origin/master 2>stderr &&
+
+		r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+		test "z$r" = "z$the_commit" &&
+
+		test 1 = $(git for-each-ref refs/remotes/origin | wc -l) &&
+
+		grep -E  "^    < [a-fA-F0-9]+ third$" stderr &&
+		grep -E  "^    > [a-fA-F0-9]+ second$" stderr
+	)
+
+'
+
 test_expect_success 'fetch with wildcard' '
 	mk_empty &&
 	(
@@ -135,6 +177,41 @@ test_expect_success 'push without wildcard' '
 	)
 '
 
+test_expect_success 'push --summary' '
+	mk_empty &&
+
+	git push --summary testrepo refs/heads/master:refs/remotes/origin/master 2>stderr &&
+	(
+		cd testrepo &&
+		r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+		test "z$r" = "z$the_commit" &&
+
+		test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+	) &&
+
+	grep -E  "^    > [a-fA-F0-9]+ second$" stderr &&
+	grep -E  "^    > [a-fA-F0-9]+ repo$" stderr
+'
+
+test_expect_success 'push --summary forced update' '
+	mk_empty &&
+
+	git push testrepo refs/heads/master:refs/remotes/origin/master &&
+
+	git checkout master^ &&
+	: >path3 &&
+	git add path3 &&
+	test_tick &&
+	git commit -a -m third &&
+
+	git push --summary -f testrepo HEAD:refs/remotes/origin/master 2>stderr &&
+
+	grep -E  "^    < [a-fA-F0-9]+ second$" stderr &&
+	grep -E  "^    > [a-fA-F0-9]+ third$" stderr &&
+
+	git checkout master
+'
+
 test_expect_success 'push with wildcard' '
 	mk_empty &&
 
diff --git a/transport.c b/transport.c
index de0d587..80105ae 100644
--- a/transport.c
+++ b/transport.c
@@ -11,6 +11,7 @@
 #include "bundle.h"
 #include "dir.h"
 #include "refs.h"
+#include "builtin.h"
 
 /* rsync support */
 
@@ -750,17 +751,20 @@ static const char *status_abbrev(unsigned char sha1[20])
 	return find_unique_abbrev(sha1, DEFAULT_ABBREV);
 }
 
-static void print_ok_ref_status(struct ref *ref, int porcelain)
+static void print_ok_ref_status(struct ref *ref, int porcelain, int summary)
 {
+	char quickref[84];
+	int summary_impossible = 0;
+
 	if (ref->deletion)
 		print_ref_status('-', "[deleted]", ref, NULL, NULL, porcelain);
-	else if (is_null_sha1(ref->old_sha1))
+	else if (is_null_sha1(ref->old_sha1)) {
 		print_ref_status('*',
 			(!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
 			"[new branch]"),
 			ref, ref->peer_ref, NULL, porcelain);
-	else {
-		char quickref[84];
+		strcpy(quickref, status_abbrev(ref->new_sha1));
+	} else {
 		char type;
 		const char *msg;
 
@@ -769,6 +773,8 @@ static void print_ok_ref_status(struct ref *ref, int porcelain)
 			strcat(quickref, "...");
 			type = '+';
 			msg = "forced update";
+			if (!lookup_commit_reference_gently(ref->old_sha1, 1))
+				summary_impossible = 1;
 		} else {
 			strcat(quickref, "..");
 			type = ' ';
@@ -778,9 +784,17 @@ static void print_ok_ref_status(struct ref *ref, int porcelain)
 
 		print_ref_status(type, quickref, ref, ref->peer_ref, msg, porcelain);
 	}
+
+	if (summary) {
+		if (summary_impossible) {
+			fprintf(stderr, "    %s is unavailable\n", status_abbrev(ref->old_sha1));
+		} else {
+			print_summary_for_push_or_fetch(quickref, summary);
+		}
+	}
 }
 
-static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain)
+static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain, int summary)
 {
 	if (!count)
 		fprintf(stderr, "To %s\n", dest);
@@ -812,7 +826,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
 						 "remote failed to report status", porcelain);
 		break;
 	case REF_STATUS_OK:
-		print_ok_ref_status(ref, porcelain);
+		print_ok_ref_status(ref, porcelain, summary);
 		break;
 	}
 
@@ -820,7 +834,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
 }
 
 static void print_push_status(const char *dest, struct ref *refs,
-							  int verbose, int porcelain)
+							  int verbose, int porcelain, int summary)
 {
 	struct ref *ref;
 	int n = 0;
@@ -828,18 +842,18 @@ static void print_push_status(const char *dest, struct ref *refs,
 	if (verbose) {
 		for (ref = refs; ref; ref = ref->next)
 			if (ref->status == REF_STATUS_UPTODATE)
-				n += print_one_push_status(ref, dest, n, porcelain);
+				n += print_one_push_status(ref, dest, n, porcelain, summary);
 	}
 
 	for (ref = refs; ref; ref = ref->next)
 		if (ref->status == REF_STATUS_OK)
-			n += print_one_push_status(ref, dest, n, porcelain);
+			n += print_one_push_status(ref, dest, n, porcelain, summary);
 
 	for (ref = refs; ref; ref = ref->next) {
 		if (ref->status != REF_STATUS_NONE &&
 		    ref->status != REF_STATUS_UPTODATE &&
 		    ref->status != REF_STATUS_OK)
-			n += print_one_push_status(ref, dest, n, porcelain);
+			n += print_one_push_status(ref, dest, n, porcelain, summary);
 	}
 }
 
@@ -997,7 +1011,8 @@ int transport_set_option(struct transport *transport,
 }
 
 int transport_push(struct transport *transport,
-		   int refspec_nr, const char **refspec, int flags)
+				   int refspec_nr, const char **refspec,
+				   int flags, int summary)
 {
 	verify_remote_names(refspec_nr, refspec);
 
@@ -1024,7 +1039,7 @@ int transport_push(struct transport *transport,
 
 		ret = transport->push_refs(transport, remote_refs, flags);
 
-		print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain);
+		print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, summary);
 
 		if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
 			struct ref *ref;
diff --git a/transport.h b/transport.h
index 51b5397..360051e 100644
--- a/transport.h
+++ b/transport.h
@@ -68,7 +68,7 @@ int transport_set_option(struct transport *transport, const char *name,
 			 const char *value);
 
 int transport_push(struct transport *connection,
-		   int refspec_nr, const char **refspec, int flags);
+				   int refspec_nr, const char **refspec, int flags, int summary);
 
 const struct ref *transport_get_remote_refs(struct transport *transport);
 
-- 
1.6.3.3.415.gbe1e