Commit Diff


commit - ccecc9fd21b05e615c5ea47b2308c048b0fd7816
commit + d1fe46f97f891c3ad339bdac5b2eabcd93621b32
blob - 3b3b0751571a29882714bb2e35cf3e14420f4113
blob + 6583cc3872bc92bf71d69523894f2b671cdfb368
--- got/got.1
+++ got/got.1
@@ -669,7 +669,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 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 x Ar commit Oc Op Ar path
 Display history of a repository.
 If a
 .Ar path
@@ -728,6 +728,13 @@ working directory.
 If this directory is a
 .Nm
 work tree, use the repository path associated with this work tree.
+.It Fl x Ar commit
+Stop displaying commits as soon as the specified
+.Ar commit
+has been displayed.
+This option has no effect if the specified
+.Ar commit
+is never traversed.
 .El
 .It Cm diff Oo Fl C Ar number Oc Oo Fl r Ar repository-path Oc Oo Fl s Oc Oo Fl w Oc Op Ar object1 Ar object2 | Ar path
 When invoked within a work tree with less than two arguments, display
blob - cd3415665c119c7b002ed7110eaacbf0a182f345
blob + d37d12527fa04f6fa2b72c9e239519f6a7884246
--- got/got.c
+++ got/got.c
@@ -3099,9 +3099,9 @@ print_commit(struct got_commit_object *commit, struct 
 }
 
 static const struct got_error *
-print_commits(struct got_object_id *root_id, struct got_repository *repo,
-    const char *path, int show_patch, const char *search_pattern,
-    int diff_context, int limit, int log_branches,
+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)
 {
 	const struct got_error *err;
@@ -3156,7 +3156,8 @@ print_commits(struct got_object_id *root_id, struct go
 		err = print_commit(commit, id, repo, path, show_patch,
 		    diff_context, refs);
 		got_object_commit_close(commit);
-		if (err || (limit && --limit == 0))
+		if (err || (limit && --limit == 0) ||
+		    (end_id != NULL && got_object_id_cmp(id, end_id) == 0))
 			break;
 	}
 done:
@@ -3169,8 +3170,9 @@ done:
 __dead static void
 usage_log(void)
 {
-	fprintf(stderr, "usage: %s log [-b] [-c commit] [-C number] [ -l N ] [-p] "
-	    "[-s search-pattern] [-r repository-path] [path]\n", getprogname());
+	fprintf(stderr, "usage: %s log [-b] [-c commit] [-C number] [ -l N ] "
+	    "[-p] [-x commit] [-s search-pattern] [-r repository-path] "
+	    "[path]\n", getprogname());
 	exit(1);
 }
 
@@ -3188,6 +3190,54 @@ get_default_log_limit(void)
 	if (errstr != NULL)
 		return 0;
 	return n;
+}
+
+static const struct got_error *
+resolve_commit_arg(struct got_object_id **id, const char *commit_arg,
+    struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_reference *ref;
+
+	*id = NULL;
+
+	err = got_ref_open(&ref, repo, commit_arg, 0);
+	if (err == NULL) {
+		int obj_type;
+		err = got_ref_resolve(id, repo, ref);
+		got_ref_close(ref);
+		if (err)
+			return err;
+		err = got_object_get_type(&obj_type, repo, *id);
+		if (err)
+			return err;
+		if (obj_type == GOT_OBJ_TYPE_TAG) {
+			struct got_tag_object *tag;
+			err = got_object_open_as_tag(&tag, repo, *id);
+			if (err)
+				return err;
+			if (got_object_tag_get_object_type(tag) !=
+			    GOT_OBJ_TYPE_COMMIT) {
+				got_object_tag_close(tag);
+				return got_error(GOT_ERR_OBJ_TYPE);
+			}
+			free(*id);
+			*id = got_object_id_dup(
+			    got_object_tag_get_object_id(tag));
+			if (*id == NULL)
+				err = got_error_from_errno(
+				    "got_object_id_dup");
+			got_object_tag_close(tag);
+			if (err)
+				return err;
+		} else if (obj_type != GOT_OBJ_TYPE_COMMIT)
+			return got_error(GOT_ERR_OBJ_TYPE);
+	} else {
+		err = got_repo_match_object_id_prefix(id, commit_arg,
+		    GOT_OBJ_TYPE_COMMIT, repo);
+	}
+
+	return err;
 }
 
 static const struct got_error *
@@ -3196,10 +3246,10 @@ cmd_log(int argc, char *argv[])
 	const struct got_error *error;
 	struct got_repository *repo = NULL;
 	struct got_worktree *worktree = NULL;
-	struct got_commit_object *commit = NULL;
-	struct got_object_id *id = NULL;
+	struct got_object_id *start_id = NULL, *end_id = NULL;
 	char *repo_path = NULL, *path = NULL, *cwd = NULL, *in_repo_path = NULL;
-	const char *start_commit = NULL, *search_pattern = NULL;
+	const char *start_commit = NULL, *end_commit = NULL;
+	const char *search_pattern = NULL;
 	int diff_context = -1, ch;
 	int show_patch = 0, limit = 0, log_branches = 0;
 	const char *errstr;
@@ -3216,7 +3266,7 @@ cmd_log(int argc, char *argv[])
 
 	limit = get_default_log_limit();
 
-	while ((ch = getopt(argc, argv, "bpc:C:l:r:s:")) != -1) {
+	while ((ch = getopt(argc, argv, "bpc:C:l:r:s:x:")) != -1) {
 		switch (ch) {
 		case 'p':
 			show_patch = 1;
@@ -3248,6 +3298,9 @@ cmd_log(int argc, char *argv[])
 		case 's':
 			search_pattern = optarg;
 			break;
+		case 'x':
+			end_commit = optarg;
+			break;
 		default:
 			usage_log();
 			/* NOTREACHED */
@@ -3315,65 +3368,31 @@ cmd_log(int argc, char *argv[])
 
 	if (start_commit == NULL) {
 		struct got_reference *head_ref;
+		struct got_commit_object *commit = NULL;
 		error = got_ref_open(&head_ref, repo,
 		    worktree ? got_worktree_get_head_ref_name(worktree)
 		    : GOT_REF_HEAD, 0);
 		if (error != NULL)
-			return error;
-		error = got_ref_resolve(&id, repo, head_ref);
+			goto done;
+		error = got_ref_resolve(&start_id, repo, head_ref);
 		got_ref_close(head_ref);
 		if (error != NULL)
-			return error;
-		error = got_object_open_as_commit(&commit, repo, id);
+			goto done;
+		error = got_object_open_as_commit(&commit, repo,
+		    start_id);
+		if (error != NULL)
+			goto done;
+		got_object_commit_close(commit);
 	} else {
-		struct got_reference *ref;
-		error = got_ref_open(&ref, repo, start_commit, 0);
-		if (error == NULL) {
-			int obj_type;
-			error = got_ref_resolve(&id, repo, ref);
-			got_ref_close(ref);
-			if (error != NULL)
-				goto done;
-			error = got_object_get_type(&obj_type, repo, id);
-			if (error != NULL)
-				goto done;
-			if (obj_type == GOT_OBJ_TYPE_TAG) {
-				struct got_tag_object *tag;
-				error = got_object_open_as_tag(&tag, repo, id);
-				if (error != NULL)
-					goto done;
-				if (got_object_tag_get_object_type(tag) !=
-				    GOT_OBJ_TYPE_COMMIT) {
-					got_object_tag_close(tag);
-					error = got_error(GOT_ERR_OBJ_TYPE);
-					goto done;
-				}
-				free(id);
-				id = got_object_id_dup(
-				    got_object_tag_get_object_id(tag));
-				if (id == NULL)
-					error = got_error_from_errno(
-					    "got_object_id_dup");
-				got_object_tag_close(tag);
-				if (error)
-					goto done;
-			} else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
-				error = got_error(GOT_ERR_OBJ_TYPE);
-				goto done;
-			}
-			error = got_object_open_as_commit(&commit, repo, id);
-			if (error != NULL)
-				goto done;
-		}
-		if (commit == NULL) {
-			error = got_repo_match_object_id_prefix(&id,
-			    start_commit, GOT_OBJ_TYPE_COMMIT, repo);
-			if (error != NULL)
-				return error;
-		}
+		error = resolve_commit_arg(&start_id, start_commit, repo);
+		if (error != NULL)
+			goto done;
 	}
-	if (error != NULL)
-		goto done;
+	if (end_commit != NULL) {
+		error = resolve_commit_arg(&end_id, end_commit, repo);
+		if (error != NULL)
+			goto done;
+	}
 
 	if (worktree) {
 		const char *prefix = got_worktree_get_path_prefix(worktree);
@@ -3398,13 +3417,12 @@ cmd_log(int argc, char *argv[])
 	if (error)
 		goto done;
 
-	error = print_commits(id, repo, path, show_patch, search_pattern,
-	    diff_context, limit, log_branches, &refs);
+	error = print_commits(start_id, end_id, repo, path, show_patch,
+	    search_pattern, diff_context, limit, log_branches, &refs);
 done:
 	free(path);
 	free(repo_path);
 	free(cwd);
-	free(id);
 	if (worktree)
 		got_worktree_close(worktree);
 	if (repo) {
blob - a903a0ec15c99cef90eb0ed9aacd554443df3aef
blob + 3f4c9bd9cc92924eee23f9724c484d587cab4334
--- regress/cmdline/log.sh
+++ regress/cmdline/log.sh
@@ -316,7 +316,161 @@ function test_log_nonexistent_path {
 	fi
 	test_done "$testroot" "$ret"
 }
+
+function test_log_end_at_commit {
+	local testroot=`test_init log_end_at_commit`
+	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 'test log_limit' > /dev/null)
+	local commit_id1=`git_show_head $testroot/repo`
+
+	(cd $testroot/wt && got rm beta >/dev/null)
+	(cd $testroot/wt && got commit -m 'test log_limit' > /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 'test log_limit' > /dev/null)
+	local commit_id3=`git_show_head $testroot/repo`
 
+	# Print commit 3 only
+	echo "commit $commit_id3 (master)" > $testroot/stdout.expected
+	(cd $testroot/wt && got log -x $commit_id3 | 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
+
+	# Print commit 3 up to commit 1 inclusive
+	echo "commit $commit_id3 (master)" > $testroot/stdout.expected
+	echo "commit $commit_id2" >> $testroot/stdout.expected
+	echo "commit $commit_id1" >> $testroot/stdout.expected
+	(cd $testroot/wt && got log -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"
+		return 1
+	fi
+
+	# Create commits on an unrelated branch
+	(cd $testroot/wt && got br foo > /dev/null)
+	echo bar >> $testroot/wt/alpha
+	(cd $testroot/wt && got commit -m "change on branch foo" >/dev/null)
+	local commit_id4=`git_show_branch_head $testroot/repo foo`
+
+	# Print commit 4 only (in work tree)
+	echo "commit $commit_id4 (foo)" > $testroot/stdout.expected
+	(cd $testroot/wt && got log -x foo | 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
+
+	# Print commit 4 only (in repository)
+	echo "commit $commit_id4 (foo)" > $testroot/stdout.expected
+	(cd $testroot/repo && got log -c foo -x foo | 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
+
+	# Repository's HEAD is on master branch so -x foo without an explicit
+	# '-c foo' start commit has no effect there
+	echo "commit $commit_id3 (master)" > $testroot/stdout.expected
+	echo "commit $commit_id2" >> $testroot/stdout.expected
+	echo "commit $commit_id1" >> $testroot/stdout.expected
+	echo "commit $commit_id0" >> $testroot/stdout.expected
+	(cd $testroot/repo && got log -x foo | 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
+
+	# got will refuse -x with a non-existent commit
+	(cd $testroot/wt && got log -x nonexistent \
+		> $testroot/stdout 2> $testroot/stderr) 
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "log command succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	echo -n > $testroot/stdout.expected
+	echo "got: nonexistent: bad object id string" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	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
+
+	# try the same with the hash of an empty string which is very
+	# unlikely to match any object
+	(cd $testroot/wt && \
+		got log -x da39a3ee5e6b4b0d3255bfef95601890afd80709 \
+		> $testroot/stdout 2> $testroot/stderr) 
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "log command succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+	echo -n > $testroot/stdout.expected
+	echo "got: object not found" > $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	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
+
+	test_done "$testroot" "0"
+}
+
 run_test test_log_in_repo
 run_test test_log_in_bare_repo
 run_test test_log_in_worktree
@@ -324,3 +478,4 @@ run_test test_log_in_worktree_with_path_prefix
 run_test test_log_tag
 run_test test_log_limit
 run_test test_log_nonexistent_path
+run_test test_log_end_at_commit