commit d1fe46f97f891c3ad339bdac5b2eabcd93621b32 from: Stefan Sperling date: Sat Apr 18 10:28:24 2020 UTC add 'got log' -x option to stop logging when a specific commit was traversed 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