commit 9139e0049a78ea0a4d285e4e5e4874893c0b9a09 from: Mark Jamsek via: Thomas Adam date: Mon Jul 17 16:31:44 2023 UTC implement support for keywords as got arguments This begins enabling the use of keywords in got wherever commit ids or references are used, with more work intended to expand support across all such instances (e.g., branch, checkout, etc.), and add more keywords. The keywords ":base" and ":head" can be passed to 'got {diff,log,update} -c' commands as a substitute for the corresponding commit hash id. Keywords and references can also be modified by appending a ':+' or ':-' and an optional integer N to specify by first parent traversal the Nth generation descendant or antecedent, respectively. If N is omitted, a '1' is implicitly appended. tweaks + ok op and stsp commit - 5a44570065e95758417914d5380e371907987be5 commit + 9139e0049a78ea0a4d285e4e5e4874893c0b9a09 blob - 19956b285901018b3d037f8f9a305d91091998ff blob + 5b91be6d0235be28a9c29087548985e36fa8da90 --- got/got.1 +++ got/got.1 @@ -699,6 +699,42 @@ The expected argument is a commit ID SHA1 hash or an e or tag name which will be resolved to a commit ID. An abbreviated hash argument will be expanded to a full SHA1 hash automatically, provided the abbreviation is unique. +The special +.Ar commit +keywords +.Qq :base +and +.Qq :head +can also be used to represent the work tree's base commit +and HEAD reference, respectively. +Keywords and reference names may be appended with +.Qq :+ +or +.Qq :- +modifiers and an optional integer N to denote the +Nth descendant or antecedent, respectively, by first parent traversal; +for example, +.Sy :head:-2 +denotes the HEAD reference's 2nd generation ancestor, and +.Sy :base:+4 +denotes the 4th generation descendant of the work tree's base commit. +Similarly, +.Sy foo:-3 +will denote the 3rd generation ancestor of the commit resolved by the +.Qq foo +reference. +If an integer does not follow the +.Qq :+ +or +.Qq :- +modifier, a +.Qq 1 +is implicitly appended +.Po e.g., +.Sy :head:- +is equivalent to +.Sy :head:-1 +.Pc . If this option is not specified, the most recent commit on the work tree's branch will be used. .It Fl q @@ -858,6 +894,45 @@ The expected argument is a commit ID SHA1 hash or an e or tag name which will be resolved to a commit ID. An abbreviated hash argument will be expanded to a full SHA1 hash automatically, provided the abbreviation is unique. +The special +.Ar commit +keywords +.Qq :base +and +.Qq :head +can also be used to represent the work tree's base commit +and HEAD reference, respectively. +The former is only valid if invoked in a work tree, while the latter will +resolve to the tip of the work tree's current branch if invoked in a +work tree, otherwise it will resolve to the repository's HEAD reference. +Keywords and references may be appended with +.Qq :+ +or +.Qq :- +modifiers and an optional integer N to denote the +Nth descendant or antecedent, respectively, by first parent traversal; +for example, +.Sy :head:-2 +denotes the HEAD reference's 2nd generation ancestor, and +.Sy :base:+4 +denotes the 4th generation descendant of the work tree's base commit. +Similarly, +.Sy bar:+3 +will denote the 3rd generation descendant of the commit resolved by the +.Qq bar +reference. +A +.Qq :+ +or +.Qq :- +modifier without a trailing integer has an implicit +.Qq 1 +appended +.Po e.g., +.Sy :base:+ +is equivalent to +.Sy :base:+1 +.Pc . If this option is not specified, default to the work tree's current branch if invoked in a work tree, or to the repository's HEAD reference. .It Fl d @@ -982,6 +1057,45 @@ The expected argument is a commit ID SHA1 hash or an e or tag name which will be resolved to a commit ID. An abbreviated hash argument will be expanded to a full SHA1 hash automatically, provided the abbreviation is unique. +The special +.Ar commit +keywords +.Qq :base +and +.Qq :head +can also be used to represent the work tree's base commit +and HEAD reference, respectively. +The former is only valid if invoked in a work tree, while the latter will +resolve to the tip of the work tree's current branch if invoked in a +work tree, otherwise it will resolve to the repository's HEAD reference. +Keywords and references may be appended with +.Qq :+ +or +.Qq :- +modifiers and an optional integer N to denote the +Nth descendant or antecedent, respectively, by first parent traversal; +for example, +.Sy :head:-2 +denotes the HEAD reference's 2nd generation ancestor, and +.Sy :base:+4 +denotes the 4th generation descendant of the work tree's base commit. +Similarly, +.Sy baz:+8 +will denote the 8th generation descendant of the commit resolved by the +.Qq baz +reference. +If an integer does not follow the +.Qq :+ +or +.Qq :- +modifier, a +.Qq 1 +is implicitly appended +.Po e.g., +.Sy :head:- +is equivalent to +.Sy :head:-1 +.Pc . .Pp If the .Fl c blob - 4b372f0a31d36f58a9d7949e4f3329bc417a803c blob + c10750066ae9c0758aa2f1f088ec8fc28322b045 --- got/got.c +++ got/got.c @@ -61,6 +61,7 @@ #include "got_patch.h" #include "got_sigs.h" #include "got_date.h" +#include "got_keyword.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) @@ -3564,11 +3565,24 @@ cmd_update(int argc, char *argv[]) goto done; } else { struct got_reflist_head refs; + char *keyword_idstr = NULL; + TAILQ_INIT(&refs); + error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL); if (error) + goto done; + + error = got_keyword_to_idstr(&keyword_idstr, commit_id_str, + repo, worktree); + if (error != NULL) goto done; + if (keyword_idstr != NULL) { + free(commit_id_str); + commit_id_str = keyword_idstr; + } + error = got_repo_match_object_id(&commit_id, NULL, commit_id_str, GOT_OBJ_TYPE_COMMIT, &refs, repo); got_ref_list_free(&refs); @@ -4729,8 +4743,18 @@ cmd_log(int argc, char *argv[]) goto done; got_object_commit_close(commit); } else { + char *keyword_idstr = NULL; + + error = got_keyword_to_idstr(&keyword_idstr, start_commit, + repo, worktree); + if (error != NULL) + goto done; + if (keyword_idstr != NULL) + start_commit = keyword_idstr; + error = got_repo_match_object_id(&start_id, NULL, start_commit, GOT_OBJ_TYPE_COMMIT, &refs, repo); + free(keyword_idstr); if (error != NULL) goto done; } @@ -5231,13 +5255,24 @@ cmd_diff(int argc, char *argv[]) if (error) goto done; for (i = 0; i < (ncommit_args > 0 ? ncommit_args : argc); i++) { - const char *arg; + const char *arg; + char *keyword_idstr = NULL; + if (ncommit_args > 0) arg = commit_args[i]; else arg = argv[i]; + + error = got_keyword_to_idstr(&keyword_idstr, arg, + repo, worktree); + if (error != NULL) + goto done; + if (keyword_idstr != NULL) + arg = keyword_idstr; + error = got_repo_match_object_id(&ids[i], &labels[i], arg, obj_type, &refs, repo); + free(keyword_idstr); if (error) { if (error->code != GOT_ERR_NOT_REF && error->code != GOT_ERR_NO_OBJ) blob - 105744fb64ab3e71162d690183b908dcd64cc259 blob + 60384f611195c8632002438feac20a8570779eae --- include/got_error.h +++ include/got_error.h @@ -186,6 +186,7 @@ #define GOT_ERR_NO_PROG 169 #define GOT_ERR_MERGE_COMMIT_OUT_OF_DATE 170 #define GOT_ERR_BUNDLE_FORMAT 171 +#define GOT_ERR_BAD_KEYWORD 172 struct got_error { int code; blob - /dev/null blob + c7a8dc04900df90b19f64e5ccbf759b1f0ae49e8 (mode 644) --- /dev/null +++ include/got_keyword.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 Mark Jamsek + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Commit keywords to specify references in the repository + * (cf. svn keywords, fossil special tags, hg revsets). + */ +#define GOT_KEYWORD_BASE "base" /* work tree base commit */ +#define GOT_KEYWORD_HEAD "head" /* worktree/repo HEAD commit */ + +/* + * Parse a commit id string for keywords and/or lineage modifiers "(+|-)[N]". + * Valid keywords are "base" or "head" and must be prefixed with a ":". + * Lineage modifiers must be prefixed with a ":" and may suffix keywords or + * reference names: + * :keyword:(+/-)N Nth generation descendant/antecedent of keyword + * :keyword:(+/-) 1st generation descendant/antecedent of keyword + * :keyword commit pointed to by keyword + * ref:(+/-)[N] Nth generation descendant/antecedent of ref + * If a match is found, return the corresponding commit id string in the + * char ** out parameter, of which the caller takes ownership and must free. + * Otherwise it will contain NULL, indicating a match was not found. + * If the modifier is greater than the number of ancestors/descendants, the id + * string of the oldest/most recent commit (i.e., ROOT/HEAD) will be returned. + */ +const struct got_error *got_keyword_to_idstr(char **, const char *, + struct got_repository *, struct got_worktree *); blob - fe7d920734c160cb1eccc3cb89288357c7328f39 blob + 6a29089f0442bf1d89182fbe94b4324e943dfb94 --- lib/error.c +++ lib/error.c @@ -244,6 +244,7 @@ static const struct got_error got_errors[] = { "the work tree is no longer up-to-date; merge must be aborted " "and retried" }, { GOT_ERR_BUNDLE_FORMAT, "unknown git bundle version" }, + { GOT_ERR_BAD_KEYWORD, "invalid commit keyword" } }; static struct got_custom_error { blob - /dev/null blob + f639075dadbb90313b88ab456af852512a3432ba (mode 644) --- /dev/null +++ lib/keyword.c @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2023 Mark Jamsek + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "got_reference.h" +#include "got_error.h" +#include "got_object.h" +#include "got_repository.h" +#include "got_cancel.h" +#include "got_worktree.h" +#include "got_commit_graph.h" +#include "got_keyword.h" + +struct keyword_mod { + char *kw; + uint64_t n; + uint8_t sym; + uint8_t iskeyword; + uint8_t ismodified; +}; + +#define GOT_KEYWORD_DESCENDANT '+' +#define GOT_KEYWORD_ANCESTOR '-' + +static const struct got_error * +parse_keyword(struct keyword_mod *kwm, const char *keyword) +{ + const char *kw; + char *p; + + if (keyword == NULL) + return NULL; + + /* check if it is a (modified) keyword or modified reference */ + if (*keyword == ':') { + kwm->iskeyword = 1; + kw = keyword + 1; + } else + kw = keyword; + + kwm->kw = strdup(kw); + if (kwm->kw == NULL) + return got_error_from_errno("strdup"); + + p = strchr(kwm->kw, ':'); + + if (p != NULL) { + *p = '\0'; + ++p; + if (*p != GOT_KEYWORD_DESCENDANT && *p != GOT_KEYWORD_ANCESTOR) + return got_error_fmt(GOT_ERR_BAD_KEYWORD, + "'%s'", keyword); + + kwm->ismodified = 1; + kwm->sym = *p; + ++p; + + if (*p) { + const char *errstr; + long long n; + + n = strtonum(p, 0, LLONG_MAX, &errstr); + if (errstr != NULL) + return got_error_fmt(GOT_ERR_BAD_KEYWORD, + "'%s'", keyword); + + kwm->n = n; + } else + kwm->n = 1; /* :(+/-) == :(+/-)1 */ + } + + return NULL; +} + +const struct got_error * +got_keyword_to_idstr(char **ret, const char *keyword, + struct got_repository *repo, struct got_worktree *wt) +{ + const struct got_error *err = NULL; + struct got_commit_graph *graph = NULL; + struct got_object_id *head_id = NULL, *kwid = NULL; + struct got_object_id iter_id; + struct got_reflist_head refs; + struct got_object_id_queue commits; + struct got_object_qid *qid; + struct keyword_mod kwm; + const char *kw = NULL; + char *kwid_str = NULL; + uint64_t n = 0; + + *ret = NULL; + TAILQ_INIT(&refs); + STAILQ_INIT(&commits); + memset(&kwm, 0, sizeof(kwm)); + + err = parse_keyword(&kwm, keyword); + if (err != NULL) + goto done; + + kw = kwm.kw; + + if (kwm.iskeyword) { + if (strcmp(kw, GOT_KEYWORD_BASE) == 0) { + if (wt == NULL) { + err = got_error_msg(GOT_ERR_NOT_WORKTREE, + "'-c :base' requires work tree"); + goto done; + } + + err = got_object_id_str(&kwid_str, + got_worktree_get_base_commit_id(wt)); + if (err != NULL) + goto done; + } else if (strcmp(kw, GOT_KEYWORD_HEAD) == 0) { + struct got_reference *head_ref; + + err = got_ref_open(&head_ref, repo, wt != NULL ? + got_worktree_get_head_ref_name(wt) : + GOT_REF_HEAD, 0); + if (err != NULL) + goto done; + + kwid_str = got_ref_to_str(head_ref); + got_ref_close(head_ref); + if (kwid_str == NULL) { + err = got_error_from_errno("got_ref_to_str"); + goto done; + } + } else { + err = got_error_fmt(GOT_ERR_BAD_KEYWORD, "'%s'", kw); + goto done; + } + } else if (kwm.ismodified) { + /* reference:[(+|-)[N]] */ + kwid_str = strdup(kw); + if (kwid_str == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + } else + goto done; + + if (kwm.n == 0) + goto done; /* unmodified keyword */ + + err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL); + if (err) + goto done; + + err = got_repo_match_object_id(&kwid, NULL, kwid_str, + GOT_OBJ_TYPE_COMMIT, &refs, repo); + if (err != NULL) + goto done; + + /* + * If looking for a descendant, we need to iterate from + * HEAD so grab its id now if it's not already in kwid. + */ + if (kwm.sym == GOT_KEYWORD_DESCENDANT && kw != NULL && + strcmp(kw, GOT_KEYWORD_HEAD) != 0) { + struct got_reference *head_ref; + + err = got_ref_open(&head_ref, repo, wt != NULL ? + got_worktree_get_head_ref_name(wt) : GOT_REF_HEAD, 0); + if (err != NULL) + goto done; + err = got_ref_resolve(&head_id, repo, head_ref); + got_ref_close(head_ref); + if (err != NULL) + goto done; + } + + err = got_commit_graph_open(&graph, "/", 1); + if (err) + goto done; + + err = got_commit_graph_iter_start(graph, + head_id != NULL ? head_id : kwid, repo, NULL, NULL); + if (err) + goto done; + + while (n <= kwm.n) { + err = got_commit_graph_iter_next(&iter_id, graph, repo, + NULL, NULL); + if (err) { + if (err->code == GOT_ERR_ITER_COMPLETED) + err = NULL; + break; + } + + if (kwm.sym == GOT_KEYWORD_DESCENDANT) { + /* + * We want the Nth generation descendant of KEYWORD, + * so queue all commits from HEAD to KEYWORD then we + * can walk from KEYWORD to its Nth gen descendent. + */ + err = got_object_qid_alloc(&qid, &iter_id); + if (err) + goto done; + STAILQ_INSERT_HEAD(&commits, qid, entry); + + if (got_object_id_cmp(&iter_id, kwid) == 0) + break; + continue; + } + ++n; + } + + if (kwm.sym == GOT_KEYWORD_DESCENDANT) { + n = 0; + + STAILQ_FOREACH(qid, &commits, entry) { + if (qid == STAILQ_LAST(&commits, got_object_qid, entry) + || n == kwm.n) + break; + ++n; + } + + memcpy(&iter_id, &qid->id, sizeof(iter_id)); + } + + free(kwid_str); + err = got_object_id_str(&kwid_str, &iter_id); + +done: + free(kwid); + free(kwm.kw); + free(head_id); + got_ref_list_free(&refs); + got_object_id_queue_free(&commits); + if (graph != NULL) + got_commit_graph_close(graph); + + if (err != NULL) { + free(kwid_str); + return err; + } + + *ret = kwid_str; + return NULL; +} blob - 895c13c80ed1183667d0ac4879512c80824f8320 blob + b2a87a49a5886634e759140de82dec6cb7fd5d8d --- regress/cmdline/common.sh +++ regress/cmdline/common.sh @@ -166,6 +166,12 @@ trim_obj_id() done echo ${id%$pat} +} + +pop_id() +{ + shift "$1" + printf '%s' "${1:-index-out-of-bounds}" } git_commit_tree() blob - 03e05611a400a2ffc9c91e307d0d5c048fdc6cf3 blob + 592e477643b13ce4e2a8ce6037f11ee4ba539cdf --- regress/cmdline/diff.sh +++ regress/cmdline/diff.sh @@ -2021,7 +2021,255 @@ EOF test_done "$testroot" "$ret" } + +test_diff_commit_keywords() { + local testroot=`test_init diff_commit_keywords` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "checkout failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + set -A ids "$(git_show_head $testroot/repo)" + set -A alpha_ids "$(get_blob_id $testroot/repo "" alpha)" + set -A beta_ids "$(get_blob_id $testroot/repo "" beta)" + + for i in `seq 8`; do + if [ $(( i % 2 )) -eq 0 ]; then + echo "alpha change $i" > "$testroot/wt/alpha" + else + echo "beta change $i" > "$testroot/wt/beta" + fi + + (cd "$testroot/wt" && got ci -m "commit number $i" > /dev/null) + ret=$? + if [ $ret -ne 0 ]; then + echo "commit failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + if [ $(( i % 2 )) -eq 0 ]; then + set -- "$alpha_ids" \ + "$(get_blob_id $testroot/repo "" alpha)" + alpha_ids=$* + else + set -- "$beta_ids" \ + "$(get_blob_id $testroot/repo "" beta)" + beta_ids=$* + fi + + set -- "$ids" "$(git_show_head $testroot/repo)" + ids=$* + done + + echo "diff $(pop_id 7 $ids) $(pop_id 8 $ids)" > \ + $testroot/stdout.expected + echo "commit - $(pop_id 7 $ids)" >> $testroot/stdout.expected + echo "commit + $(pop_id 8 $ids)" >> $testroot/stdout.expected + echo "blob - $(pop_id 4 $beta_ids)" >> $testroot/stdout.expected + echo "blob + $(pop_id 5 $beta_ids)" >> $testroot/stdout.expected + echo '--- beta' >> $testroot/stdout.expected + echo '+++ beta' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-beta change 5' >> $testroot/stdout.expected + echo '+beta change 7' >> $testroot/stdout.expected + + (cd $testroot/wt && got diff -cmaster:- > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "diff failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got update -c:head:-6 > /dev/null) + ret=$? + if [ $ret -ne 0 ]; then + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "diff $(pop_id 1 $ids) $(pop_id 2 $ids)" > \ + $testroot/stdout.expected + echo "commit - $(pop_id 1 $ids)" >> $testroot/stdout.expected + echo "commit + $(pop_id 2 $ids)" >> $testroot/stdout.expected + echo "blob - $(pop_id 1 $beta_ids)" >> $testroot/stdout.expected + echo "blob + $(pop_id 2 $beta_ids)" >> $testroot/stdout.expected + echo '--- beta' >> $testroot/stdout.expected + echo '+++ beta' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-beta' >> $testroot/stdout.expected + echo '+beta change 1' >> $testroot/stdout.expected + + (cd $testroot/wt && got diff -c:base:- > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "diff failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "diff $(pop_id 3 $ids) $(pop_id 4 $ids)" > \ + $testroot/stdout.expected + echo "commit - $(pop_id 3 $ids)" >> $testroot/stdout.expected + echo "commit + $(pop_id 4 $ids)" >> $testroot/stdout.expected + echo "blob - $(pop_id 2 $beta_ids)" >> $testroot/stdout.expected + echo "blob + $(pop_id 3 $beta_ids)" >> $testroot/stdout.expected + echo '--- beta' >> $testroot/stdout.expected + echo '+++ beta' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-beta change 1' >> $testroot/stdout.expected + echo '+beta change 3' >> $testroot/stdout.expected + + (cd $testroot/wt && got diff -c:base:+ > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "diff failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # if modifier extends beyond HEAD, we should use HEAD ref + echo "diff $(pop_id 8 $ids) $(pop_id 9 $ids)" > \ + $testroot/stdout.expected + echo "commit - $(pop_id 8 $ids)" >> $testroot/stdout.expected + echo "commit + $(pop_id 9 $ids)" >> $testroot/stdout.expected + echo "blob - $(pop_id 4 $alpha_ids)" >> $testroot/stdout.expected + echo "blob + $(pop_id 5 $alpha_ids)" >> $testroot/stdout.expected + echo '--- alpha' >> $testroot/stdout.expected + echo '+++ alpha' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-alpha change 6' >> $testroot/stdout.expected + echo '+alpha change 8' >> $testroot/stdout.expected + + (cd $testroot/wt && got diff -c:base:+20 > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "diff failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "diff $(pop_id 3 $ids) $(pop_id 9 $ids)" > \ + $testroot/stdout.expected + echo "commit - $(pop_id 3 $ids)" >> $testroot/stdout.expected + echo "commit + $(pop_id 9 $ids)" >> $testroot/stdout.expected + echo "blob - $(pop_id 2 $alpha_ids)" >> $testroot/stdout.expected + echo "blob + $(pop_id 5 $alpha_ids)" >> $testroot/stdout.expected + echo '--- alpha' >> $testroot/stdout.expected + echo '+++ alpha' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-alpha change 2' >> $testroot/stdout.expected + echo '+alpha change 8' >> $testroot/stdout.expected + echo "blob - $(pop_id 2 $beta_ids)" >> $testroot/stdout.expected + echo "blob + $(pop_id 5 $beta_ids)" >> $testroot/stdout.expected + echo '--- beta' >> $testroot/stdout.expected + echo '+++ beta' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-beta change 1' >> $testroot/stdout.expected + echo '+beta change 7' >> $testroot/stdout.expected + + (cd $testroot/wt && got diff -c:base -c:head > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "diff failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "diff $(pop_id 6 $ids) $(pop_id 8 $ids)" > \ + $testroot/stdout.expected + echo "commit - $(pop_id 6 $ids)" >> $testroot/stdout.expected + echo "commit + $(pop_id 8 $ids)" >> $testroot/stdout.expected + echo "blob - $(pop_id 3 $alpha_ids)" >> $testroot/stdout.expected + echo "blob + $(pop_id 4 $alpha_ids)" >> $testroot/stdout.expected + echo '--- alpha' >> $testroot/stdout.expected + echo '+++ alpha' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-alpha change 4' >> $testroot/stdout.expected + echo '+alpha change 6' >> $testroot/stdout.expected + echo "blob - $(pop_id 4 $beta_ids)" >> $testroot/stdout.expected + echo "blob + $(pop_id 5 $beta_ids)" >> $testroot/stdout.expected + echo '--- beta' >> $testroot/stdout.expected + echo '+++ beta' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-beta change 5' >> $testroot/stdout.expected + echo '+beta change 7' >> $testroot/stdout.expected + + got diff -r "$testroot/repo" -cmaster:-3 -c:head:-1 > $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + echo "diff failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "'-c BASE' requires work tree" > "$testroot/stderr.expected" + + got diff -r "$testroot/repo" -c:base -c:head 2> $testroot/stderr + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + + test_done "$testroot" "$ret" +} + test_parseargs "$@" run_test test_diff_basic run_test test_diff_shows_conflict @@ -2041,3 +2289,4 @@ run_test test_diff_worktree_diffstat run_test test_diff_file_to_dir run_test test_diff_dir_to_file run_test test_diff_path_in_root_commit +run_test test_diff_commit_keywords blob - aa6a545fe216654b76ca23bbe186211d313c59a2 blob + 96004ce8b8a631507d11bbb3ba545448a1a16429 --- regress/cmdline/log.sh +++ regress/cmdline/log.sh @@ -899,7 +899,155 @@ EOF fi test_done "$testroot" "$ret" } + +test_log_commit_keywords() { + local testroot=$(test_init log_commit_keywords) + local commit_time=`git_show_author_time $testroot/repo` + local d=`date -u -r $commit_time +"%G-%m-%d"` + + set -A ids "$(git_show_head $testroot/repo)" + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "checkout failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + for i in $(seq 16); do + echo "alpha change $i" > "$testroot/wt/alpha" + + (cd "$testroot/wt" && got ci -m "commit number $i" > /dev/null) + ret=$? + if [ $ret -ne 0 ]; then + echo "commit failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + set -- "$ids" "$(git_show_head $testroot/repo)" + ids=$* + done + + for i in $(seq 16 2); do + printf '%s %.7s commit number %s\n' \ + "$d" $(pop_id $i $ids) "$(( i-1 ))" \ + >> $testroot/stdout.expected + done + + got log -r "$testroot/repo" -scmaster:- -l15 > $testroot/stdout + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + (cd $testroot/wt && got update -c:head:-8 > /dev/null) + ret=$? + if [ $ret -ne 0 ]; then + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo -n > "$testroot/stdout.expected" + + for i in $(seq 9 2); do + printf '%s %.7s commit number %s\n' \ + "$d" $(pop_id $i $ids) "$(( i-1 ))" \ + >> $testroot/stdout.expected + done + printf '%s %.7s adding the test tree\n' "$d" $(pop_id 1 $ids) >> \ + $testroot/stdout.expected + + (cd $testroot/wt && got log -sc:base > $testroot/stdout) + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # if + modifier is too great, use HEAD commit + printf '%s %-7s commit number %s\n' "$d" master 16 > \ + $testroot/stdout.expected + printf '%s %.7s commit number %s\n' "$d" $(pop_id 16 $ids) 15 >> \ + $testroot/stdout.expected + + (cd $testroot/wt && got log -sc:base:+20 -l2 > $testroot/stdout) + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # if - modifier is too great, use root commit + printf '%s %.7s adding the test tree\n' "$d" $(pop_id 1 $ids) > \ + $testroot/stdout.expected + + (cd $testroot/wt && got log -sc:base:-10 > $testroot/stdout) + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + got br -r "$testroot/repo" -c $(pop_id 1 $ids) base+ + + printf '%s %.7s commit number 1\n' "$d" $(pop_id 2 $ids) > \ + $testroot/stdout.expected + + (cd $testroot/wt && got log -scbase+:+ -l1 > $testroot/stdout) + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + got br -r "$testroot/repo" -c $(pop_id 3 $ids) head-1 + + printf '%s %.7s commit number 1\n' "$d" $(pop_id 2 $ids) > \ + $testroot/stdout.expected + + (cd $testroot/wt && got log -schead-1:- -l1 > $testroot/stdout) + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + got br -r "$testroot/repo" -c $(pop_id 16 $ids) base-1+2 + + printf '%s %.7s commit number 12\n' "$d" $(pop_id 13 $ids) > \ + $testroot/stdout.expected + + (cd $testroot/wt && got log -scbase-1+2:-3 -l1 > $testroot/stdout) + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + + test_done "$testroot" "$ret" +} + test_parseargs "$@" run_test test_log_in_repo run_test test_log_in_bare_repo @@ -916,3 +1064,4 @@ run_test test_log_in_worktree_different_repo run_test test_log_changed_paths run_test test_log_submodule run_test test_log_diffstat +run_test test_log_commit_keywords blob - c08237c6cd1b14453b8e4f8e39789a3c6c017f1c blob + 9c979ff35454c34d02151d8a65d66ad2cdf10d86 --- regress/cmdline/update.sh +++ regress/cmdline/update.sh @@ -3228,6 +3228,345 @@ test_update_umask() { fi test_done "$testroot" 0 +} + +test_update_commit_keywords() { + local testroot=`test_init update_commit_keywords` + + set -A ids "$(git_show_head $testroot/repo)" + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "checkout failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + for i in `seq 8`; do + if [ $(( i % 2 )) -eq 0 ]; then + echo "alpha change $i" > "$testroot/wt/alpha" + else + echo "beta change $i" > "$testroot/wt/beta" + fi + + (cd "$testroot/wt" && got ci -m "commit number $i" > /dev/null) + ret=$? + if [ $ret -ne 0 ]; then + echo "commit failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + set -- "$ids" "$(git_show_head $testroot/repo)" + ids=$* + done + + echo "got: reference base not found" > $testroot/stderr.expected + + (cd $testroot/wt && got update -cbase:-2 2> $testroot/stderr) + ret=$? + if [ $ret -ne 1 ]; then + echo "update succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "got: 'basefoo': invalid commit keyword" > \ + $testroot/stderr.expected + + (cd $testroot/wt && got update -c:basefoo:-2 2> $testroot/stderr) + ret=$? + if [ $ret -ne 1 ]; then + echo "update succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "got: ':base::': invalid commit keyword" > \ + $testroot/stderr.expected + + (cd $testroot/wt && got update -c:base:: 2> $testroot/stderr) + ret=$? + if [ $ret -ne 1 ]; then + echo "update succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "got: ':head:++': invalid commit keyword" > \ + $testroot/stderr.expected + + (cd $testroot/wt && got update -c:head:++ 2> $testroot/stderr) + ret=$? + if [ $ret -ne 1 ]; then + echo "update succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "got: ':head:+x': invalid commit keyword" > \ + $testroot/stderr.expected + + (cd $testroot/wt && got update -c:head:+x 2> $testroot/stderr) + ret=$? + if [ $ret -ne 1 ]; then + echo "update succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "got: 'master::': invalid commit keyword" > \ + $testroot/stderr.expected + + (cd $testroot/wt && got update -cmaster:: 2> $testroot/stderr) + ret=$? + if [ $ret -ne 1 ]; then + echo "update succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "got: 'master:++': invalid commit keyword" > \ + $testroot/stderr.expected + + (cd $testroot/wt && got update -cmaster:++ 2> $testroot/stderr) + ret=$? + if [ $ret -ne 1 ]; then + echo "update succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "got: 'master:+x': invalid commit keyword" > \ + $testroot/stderr.expected + + (cd $testroot/wt && got update -cmaster:+x 2> $testroot/stderr) + ret=$? + if [ $ret -ne 1 ]; then + echo "update succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stderr.expected $testroot/stderr + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stderr.expected $testroot/stderr + test_done "$testroot" "$ret" + return 1 + fi + + echo "U alpha" > $testroot/stdout.expected + echo "U beta" >> $testroot/stdout.expected + echo -n "Updated to refs/heads/master: " >> $testroot/stdout.expected + echo $(pop_id 7 $ids) >> "$testroot/stdout.expected" + + (cd $testroot/wt && got update -c:base:-2 > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "U beta" > $testroot/stdout.expected + echo -n "Updated to refs/heads/master: " >> $testroot/stdout.expected + echo $(pop_id 8 $ids) >> "$testroot/stdout.expected" + + (cd $testroot/wt && got update -cmaster:- > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "alpha change 6" > $testroot/content.expected + cat $testroot/wt/alpha > $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + echo "U alpha" > $testroot/stdout.expected + echo "U beta" >> $testroot/stdout.expected + echo -n "Updated to refs/heads/master: " >> $testroot/stdout.expected + echo $(pop_id 2 $ids) >> "$testroot/stdout.expected" + + (cd $testroot/wt && got update -c:base:-6 > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "beta change 1" > $testroot/content.expected + cat $testroot/wt/beta > $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + echo "U alpha" > $testroot/stdout.expected + echo -n "Updated to refs/heads/master: " >> $testroot/stdout.expected + echo $(pop_id 3 $ids) >> "$testroot/stdout.expected" + + (cd $testroot/wt && got update -c:base:+ > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "alpha change 2" > $testroot/content.expected + cat $testroot/wt/alpha > $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + echo "U alpha" > $testroot/stdout.expected + echo "U beta" >> $testroot/stdout.expected + echo -n "Updated to refs/heads/master: " >> $testroot/stdout.expected + echo $(pop_id 7 $ids) >> "$testroot/stdout.expected" + + (cd $testroot/wt && got update -c:head:-2 > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "alpha change 6" > $testroot/content.expected + cat $testroot/wt/alpha > $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + # if - modifier is too great, use root commit + echo "U alpha" > $testroot/stdout.expected + echo "U beta" >> $testroot/stdout.expected + echo -n "Updated to refs/heads/master: " >> $testroot/stdout.expected + echo $(pop_id 1 $ids) >> "$testroot/stdout.expected" + + (cd $testroot/wt && got update -c:base:-20 > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "alpha" > $testroot/content.expected + cat $testroot/wt/alpha > $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + # if + modifier is too great, use HEAD commit + echo "U alpha" > $testroot/stdout.expected + echo "U beta" >> $testroot/stdout.expected + echo -n "Updated to refs/heads/master: " >> $testroot/stdout.expected + echo $(pop_id 9 $ids) >> "$testroot/stdout.expected" + + (cd $testroot/wt && got update -c:head:+10 > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "alpha change 8" > $testroot/content.expected + cat $testroot/wt/alpha > $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/content.expected $testroot/content + fi + test_done "$testroot" "$ret" } test_parseargs "$@" @@ -3278,3 +3617,4 @@ run_test test_update_file_skipped_due_to_obstruction run_test test_update_quiet run_test test_update_binary_file run_test test_update_umask +run_test test_update_commit_keywords