Commit dbec59df9a61e9cea9251851a5c649ab8981e91b

Stefan Sperling 2020-04-18T21:24:54

add 'got log' -R option to reverse commit display order

diff --git a/got/got.1 b/got/got.1
index 39ad76b..f0afe08 100644
--- a/got/got.1
+++ b/got/got.1
@@ -682,7 +682,7 @@ in a pattern.
 .It Cm st
 Short alias for
 .Cm status .
-.It Cm log Oo Fl b Oc Oo Fl c Ar commit Oc Oo Fl C Ar number Oc Oo Fl l Ar N Oc Oo Fl p Oc Oo Fl s Ar search-pattern Oc Oo Fl r Ar repository-path Oc Oo Fl x Ar commit Oc Op Ar path
+.It Cm log Oo Fl b Oc Oo Fl c Ar commit Oc Oo Fl C Ar number Oc Oo Fl l Ar N Oc Oo Fl p Oc Oo Fl s Ar search-pattern Oc Oo Fl r Ar repository-path Oc Oo Fl R Oc Oo Fl x Ar commit Oc Op Ar path
 Display history of a repository.
 If a
 .Ar path
@@ -741,6 +741,9 @@ working directory.
 If this directory is a
 .Nm
 work tree, use the repository path associated with this work tree.
+.It Fl R
+Determine a set of commits to display as usual, but display these commits
+in reverse order.
 .It Fl x Ar commit
 Stop traversing commit history as soon as the specified
 .Ar commit
diff --git a/got/got.c b/got/got.c
index a6905d4..7163d53 100644
--- a/got/got.c
+++ b/got/got.c
@@ -3135,12 +3135,17 @@ static const struct got_error *
 print_commits(struct got_object_id *root_id, struct got_object_id *end_id,
     struct got_repository *repo, const char *path, int show_patch,
     const char *search_pattern, int diff_context, int limit, int log_branches,
-    struct got_reflist_head *refs)
+    int reverse_display_order, struct got_reflist_head *refs)
 {
 	const struct got_error *err;
 	struct got_commit_graph *graph;
 	regex_t regex;
 	int have_match;
+	struct got_object_id_queue reversed_commits;
+	struct got_object_qid *qid;
+	struct got_commit_object *commit;
+
+	SIMPLEQ_INIT(&reversed_commits);
 
 	if (search_pattern && regcomp(&regex, search_pattern,
 	    REG_EXTENDED | REG_NOSUB | REG_NEWLINE))
@@ -3154,7 +3159,6 @@ print_commits(struct got_object_id *root_id, struct got_object_id *end_id,
 	if (err)
 		goto done;
 	for (;;) {
-		struct got_commit_object *commit;
 		struct got_object_id *id;
 
 		if (sigint_received || sigpipe_received)
@@ -3186,14 +3190,41 @@ print_commits(struct got_object_id *root_id, struct got_object_id *end_id,
 			}
 		}
 
-		err = print_commit(commit, id, repo, path, show_patch,
-		    diff_context, refs);
-		got_object_commit_close(commit);
-		if (err || (limit && --limit == 0) ||
-		    (end_id != NULL && got_object_id_cmp(id, end_id) == 0))
+		if (reverse_display_order) {
+			err = got_object_qid_alloc(&qid, id);
+			if (err)
+				break;
+			SIMPLEQ_INSERT_HEAD(&reversed_commits, qid, entry);
+			got_object_commit_close(commit);
+		} else {
+			err = print_commit(commit, id, repo, path, show_patch,
+			    diff_context, refs);
+			got_object_commit_close(commit);
+			if (err)
+				break;
+		}
+		if ((limit && --limit == 0) ||
+		    (end_id && got_object_id_cmp(id, end_id) == 0))
 			break;
 	}
+	if (reverse_display_order) {
+		SIMPLEQ_FOREACH(qid, &reversed_commits, entry) {
+			err = got_object_open_as_commit(&commit, repo, qid->id);
+			if (err)
+				break;
+			err = print_commit(commit, qid->id, repo, path,
+			    show_patch, diff_context, refs);
+			got_object_commit_close(commit);
+			if (err)
+				break;
+		}
+	}
 done:
+	while (!SIMPLEQ_EMPTY(&reversed_commits)) {
+		qid = SIMPLEQ_FIRST(&reversed_commits);
+		SIMPLEQ_REMOVE_HEAD(&reversed_commits, entry);
+		got_object_qid_free(qid);
+	}
 	if (search_pattern)
 		regfree(&regex);
 	got_commit_graph_close(graph);
@@ -3204,7 +3235,7 @@ __dead static void
 usage_log(void)
 {
 	fprintf(stderr, "usage: %s log [-b] [-c commit] [-C number] [ -l N ] "
-	    "[-p] [-x commit] [-s search-pattern] [-r repository-path] "
+	    "[-p] [-x commit] [-s search-pattern] [-r repository-path] [-R] "
 	    "[path]\n", getprogname());
 	exit(1);
 }
@@ -3285,6 +3316,7 @@ cmd_log(int argc, char *argv[])
 	const char *search_pattern = NULL;
 	int diff_context = -1, ch;
 	int show_patch = 0, limit = 0, log_branches = 0;
+	int reverse_display_order = 0;
 	const char *errstr;
 	struct got_reflist_head refs;
 
@@ -3299,7 +3331,7 @@ cmd_log(int argc, char *argv[])
 
 	limit = get_default_log_limit();
 
-	while ((ch = getopt(argc, argv, "bpc:C:l:r:s:x:")) != -1) {
+	while ((ch = getopt(argc, argv, "bpc:C:l:r:Rs:x:")) != -1) {
 		switch (ch) {
 		case 'p':
 			show_patch = 1;
@@ -3328,6 +3360,9 @@ cmd_log(int argc, char *argv[])
 				    optarg);
 			got_path_strip_trailing_slashes(repo_path);
 			break;
+		case 'R':
+			reverse_display_order = 1;
+			break;
 		case 's':
 			search_pattern = optarg;
 			break;
@@ -3451,7 +3486,8 @@ cmd_log(int argc, char *argv[])
 		goto done;
 
 	error = print_commits(start_id, end_id, repo, path, show_patch,
-	    search_pattern, diff_context, limit, log_branches, &refs);
+	    search_pattern, diff_context, limit, log_branches,
+	    reverse_display_order, &refs);
 done:
 	free(path);
 	free(repo_path);
diff --git a/regress/cmdline/log.sh b/regress/cmdline/log.sh
index 3f4c9bd..6bde801 100755
--- a/regress/cmdline/log.sh
+++ b/regress/cmdline/log.sh
@@ -471,6 +471,82 @@ function test_log_end_at_commit {
 	test_done "$testroot" "0"
 }
 
+function test_log_reverse_display {
+	local testroot=`test_init log_reverse_display`
+	local commit_id0=`git_show_head $testroot/repo`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified alpha" > $testroot/wt/alpha
+	(cd $testroot/wt && got commit -m 'commit1' > /dev/null)
+	local commit_id1=`git_show_head $testroot/repo`
+
+	(cd $testroot/wt && got rm beta >/dev/null)
+	(cd $testroot/wt && got commit -m 'commit2' > /dev/null)
+	local commit_id2=`git_show_head $testroot/repo`
+
+	echo "new file" > $testroot/wt/new
+	(cd $testroot/wt && got add new >/dev/null)
+	(cd $testroot/wt && got commit -m 'commit3' > /dev/null)
+	local commit_id3=`git_show_head $testroot/repo`
+
+	# -R alone should display all commits in reverse
+	echo "commit $commit_id0" > $testroot/stdout.expected
+	echo "commit $commit_id1" >> $testroot/stdout.expected
+	echo "commit $commit_id2" >> $testroot/stdout.expected
+	echo "commit $commit_id3 (master)" >> $testroot/stdout.expected
+	(cd $testroot/wt && got log -R | grep ^commit > $testroot/stdout)
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# -R takes effect after the -l commit traversal limit
+	echo "commit $commit_id2" > $testroot/stdout.expected
+	echo "commit $commit_id3 (master)" >> $testroot/stdout.expected
+	(cd $testroot/wt && got log -R -l2 | grep ^commit > $testroot/stdout)
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# -R works with commit ranges specified via -c and -x
+	echo "commit $commit_id1" > $testroot/stdout.expected
+	echo "commit $commit_id2" >> $testroot/stdout.expected
+	echo "commit $commit_id3 (master)" >> $testroot/stdout.expected
+	(cd $testroot/wt && got log -R -c $commit_id3 -x $commit_id1 | \
+		grep ^commit > $testroot/stdout)
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+	fi
+
+	# commit matching with -s applies before -R
+	echo "commit $commit_id1" > $testroot/stdout.expected
+	echo "commit $commit_id2" >> $testroot/stdout.expected
+	(cd $testroot/wt && got log -R -s 'commit[12]' | \
+		grep ^commit > $testroot/stdout)
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_log_in_repo
 run_test test_log_in_bare_repo
 run_test test_log_in_worktree
@@ -479,3 +555,4 @@ run_test test_log_tag
 run_test test_log_limit
 run_test test_log_nonexistent_path
 run_test test_log_end_at_commit
+run_test test_log_reverse_display