commit 896e9b6f30150bb0dfff1345dc5935a4b65b6838 from: Stefan Sperling date: Mon Aug 26 13:10:12 2019 UTC add support for path arguments to 'got cat' commit - 44392932de0367a4f50fdeb8ecb541cda02a86f8 commit + 896e9b6f30150bb0dfff1345dc5935a4b65b6838 blob - 3697535d7f7ebd36d8a374d195ebe42d1c658497 blob + e4ba15352f9f9e0ff9b741ca38676a601434bcde --- got/got.1 +++ got/got.1 @@ -1151,19 +1151,38 @@ file instead of prompting interactively. .It Cm ug Short alias for .Cm unstage . -.It Cm cat Oo Fl r Ar repository-path Oc Ar object ... -Parse and print contents of specified objects to standard output -in a line-based text format. -Treat each argument as a reference, a tag name, or an object ID SHA1 hash. +.It Cm cat Oo Fl c Ar commit Oc Oo Fl r Ar repository-path Oc Ar arg ... +Parse and print contents of objects to standard output in a line-based +text format. +Content of commit, tree, and tag objects is printed in a way similar +to the actual content stored in such objects. +Blob object contents are printed as they would appear in files on disk. +.Pp +Attempt to interpret each argument as a reference, a tag name, or +an object ID SHA1 hash. References will be resolved to an object ID. Tag names will resolved to a tag object. An abbreviated hash argument will be expanded to a full SHA1 hash automatically, provided the abbreviation is unique. .Pp +If none of the above interpretations produce a valid result, or if the +.Fl P +option is used, attempt to interpret the argument as a path which will +be resolved to the ID of an object found at this path in the repository. +.Pp The options for .Cm got cat are as follows: .Bl -tag -width Ds +.It Fl c Ar commit +Look up paths in the specified +.Ar commit . +If this option is not used, paths are looked up in the commit resolved +via the repository's HEAD reference. +The expected argument is a commit ID SHA1 hash or an existing reference +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. .It Fl r Ar repository-path Use the repository at the specified path. If not specified, assume the repository is located at or above the current @@ -1171,6 +1190,10 @@ working directory. If this directory is a .Nm work tree, use the repository path associated with this work tree. +.It Fl P +Interpret all arguments as paths only. +This option can be used to resolve ambiguity in cases where paths +look like tag names, reference names, or object IDs. .El .El .Sh ENVIRONMENT blob - 178ad9f5321f4259fd91762efec539efe3a0683c blob + 54e235accacaac5767caaa2b55bc299d21a1353e --- got/got.c +++ got/got.c @@ -6544,8 +6544,8 @@ done: __dead static void usage_cat(void) { - fprintf(stderr, "usage: %s cat [-r repository ] object1 " - "[object2 ...]\n", getprogname()); + fprintf(stderr, "usage: %s cat [-r repository ] [ -c commit ] [ -P ] " + "arg1 [arg2 ...]\n", getprogname()); exit(1); } @@ -6703,8 +6703,9 @@ cmd_cat(int argc, char *argv[]) struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; char *cwd = NULL, *repo_path = NULL, *label = NULL; - struct got_object_id *id = NULL; - int ch, obj_type, i; + const char *commit_id_str = NULL; + struct got_object_id *id = NULL, *commit_id = NULL; + int ch, obj_type, i, force_path = 0; #ifndef PROFILE if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", @@ -6712,14 +6713,20 @@ cmd_cat(int argc, char *argv[]) err(1, "pledge"); #endif - while ((ch = getopt(argc, argv, "r:")) != -1) { + while ((ch = getopt(argc, argv, "c:r:P")) != -1) { switch (ch) { + case 'c': + commit_id_str = optarg; + break; case 'r': repo_path = realpath(optarg, NULL); if (repo_path == NULL) err(1, "-r option"); got_path_strip_trailing_slashes(repo_path); break; + case 'P': + force_path = 1; + break; default: usage_cat(); /* NOTREACHED */ @@ -6760,14 +6767,34 @@ cmd_cat(int argc, char *argv[]) goto done; error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + + if (commit_id_str == NULL) + commit_id_str = GOT_REF_HEAD; + error = resolve_commit_arg(&commit_id, commit_id_str, repo); if (error) goto done; for (i = 0; i < argc; i++) { - error = match_object_id(&id, &label, argv[i], - GOT_OBJ_TYPE_ANY, 0, repo); - if (error) - break; + if (force_path) { + error = got_object_id_by_path(&id, repo, commit_id, + argv[i]); + if (error) + break; + } else { + error = match_object_id(&id, &label, argv[i], + GOT_OBJ_TYPE_ANY, 0, repo); + if (error) { + if (error->code != GOT_ERR_BAD_OBJ_ID_STR && + error->code != GOT_ERR_NOT_REF) + break; + error = got_object_id_by_path(&id, repo, + commit_id, argv[i]); + if (error) + break; + } + } error = got_object_get_type(&obj_type, repo, id); if (error) @@ -6801,6 +6828,7 @@ cmd_cat(int argc, char *argv[]) done: free(label); free(id); + free(commit_id); if (worktree) got_worktree_close(worktree); if (repo) { blob - ff28334685c4012d71ab8d57303196127b851018 blob + e94f00225a6d92aed87516ccb71ed5c81a284b32 --- regress/cmdline/cat.sh +++ regress/cmdline/cat.sh @@ -52,7 +52,8 @@ function test_cat_basic { echo >> $testroot/stdout.expected echo "numparents 0" >> $testroot/stdout.expected echo "author $GOT_AUTHOR $author_time +0000" >> $testroot/stdout.expected - echo "committer Flan Hacker $author_time +0000" >> $testroot/stdout.expected + echo "committer $GOT_AUTHOR $author_time +0000" \ + >> $testroot/stdout.expected echo "messagelen 22" >> $testroot/stdout.expected printf "\nadding the test tree\n" >> $testroot/stdout.expected @@ -71,4 +72,135 @@ function test_cat_basic { } +function test_cat_path { + local testroot=`test_init cat_path` + local commit_id=`git_show_head $testroot/repo` + local author_time=`git_show_author_time $testroot/repo` + local alpha_id=`got tree -r $testroot/repo -i | grep 'alpha$' | cut -d' ' -f 1` + local gamma_id=`got tree -r $testroot/repo -i | grep 'gamma/$' | cut -d' ' -f 1` + local delta_id=`got tree -r $testroot/repo -i gamma | grep 'delta$' | cut -d' ' -f 1` + + # cat blob by path + echo "alpha" > $testroot/stdout.expected + got cat -r $testroot/repo alpha > $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 + + # cat tree by path + echo "$delta_id 0100644 delta" > $testroot/stdout.expected + got cat -r $testroot/repo gamma > $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 + + (cd $testroot && got checkout repo wt > /dev/null) + echo "modified alpha" > $testroot/wt/alpha + (cd $testroot/wt && got commit -m "changed alpha" > /dev/null) + local commit_id2=`git_show_head $testroot/repo` + local author_time2=`git_show_author_time $testroot/repo` + local tree_commit2=`git_show_tree $testroot/repo` + + # cat blob by path in specific commit + echo "alpha" > $testroot/stdout.expected + got cat -r $testroot/repo -c $commit_id alpha > $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 + echo "modified alpha" > $testroot/stdout.expected + got cat -r $testroot/repo -c $commit_id2 alpha > $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 + + # resolve ambiguities between paths and other arguments + echo "new file called master" > $testroot/wt/master + echo "new file called $commit_id2" > $testroot/wt/$commit_id2 + (cd $testroot/wt && got add master $commit_id2 > /dev/null) + (cd $testroot/wt && got commit -m "added clashing paths" > /dev/null) + local commit_id3=`git_show_head $testroot/repo` + local author_time3=`git_show_author_time $testroot/repo` + + # references and object IDs override paths: + echo -n "tree " > $testroot/stdout.expected + git_show_tree $testroot/repo >> $testroot/stdout.expected + echo >> $testroot/stdout.expected + echo "numparents 1" >> $testroot/stdout.expected + echo "parent $commit_id2" >> $testroot/stdout.expected + echo "author $GOT_AUTHOR $author_time3 +0000" >> $testroot/stdout.expected + echo "committer $GOT_AUTHOR $author_time3 +0000" \ + >> $testroot/stdout.expected + echo "messagelen 22" >> $testroot/stdout.expected + printf "\nadded clashing paths\n" >> $testroot/stdout.expected + + for arg in master $commit_id3; do + got cat -r $testroot/repo $arg > $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 + done + + echo "tree $tree_commit2" > $testroot/stdout.expected + echo "numparents 1" >> $testroot/stdout.expected + echo "parent $commit_id" >> $testroot/stdout.expected + echo "author $GOT_AUTHOR $author_time2 +0000" >> $testroot/stdout.expected + echo "committer $GOT_AUTHOR $author_time2 +0000" \ + >> $testroot/stdout.expected + echo "messagelen 15" >> $testroot/stdout.expected + printf "\nchanged alpha\n" >> $testroot/stdout.expected + + got cat -r $testroot/repo $commit_id2 > $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 + + # force resolution of path 'master' + echo "new file called master" > $testroot/stdout.expected + got cat -r $testroot/repo -P master > $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 + + # force resolution of path "$commit_id2" + echo "new file called $commit_id2" > $testroot/stdout.expected + got cat -r $testroot/repo -P $commit_id2 > $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 + test_done "$testroot" "$ret" +} + run_test test_cat_basic +run_test test_cat_path