commit 01073a5d20f017cbdb065decd25fa6d472cf809d from: Stefan Sperling date: Thu Aug 22 12:17:38 2019 UTC initial 'got cat' implementation commit - fc06ba561252d5ccc36fa7a9be53fe33d098ce8f commit + 01073a5d20f017cbdb065decd25fa6d472cf809d blob - 3caa70a3b90fb32af006222ca63d2b650bedb146 blob + 5cc02975e9734606b79d513463bbce1e7f4e8fd6 --- got/got.1 +++ got/got.1 @@ -1084,7 +1084,28 @@ 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. +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 +The options for +.Cm got cat +are as follows: +.Bl -tag -width Ds +.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 +working directory. +If this directory is a +.Nm +work tree, use the repository path associated with this work tree. .El +.El .Sh ENVIRONMENT .Bl -tag -width GOT_AUTHOR .It Ev GOT_AUTHOR blob - 9062a445d85049f837f67d8e1cca4798c73df9e6 blob + 0b3586f662285bd616e529dc71368b14a7e6501e --- got/got.c +++ got/got.c @@ -98,6 +98,7 @@ __dead static void usage_rebase(void); __dead static void usage_histedit(void); __dead static void usage_stage(void); __dead static void usage_unstage(void); +__dead static void usage_cat(void); static const struct got_error* cmd_init(int, char *[]); static const struct got_error* cmd_import(int, char *[]); @@ -120,6 +121,7 @@ static const struct got_error* cmd_rebase(int, char * static const struct got_error* cmd_histedit(int, char *[]); static const struct got_error* cmd_stage(int, char *[]); static const struct got_error* cmd_unstage(int, char *[]); +static const struct got_error* cmd_cat(int, char *[]); static struct got_cmd got_commands[] = { { "init", cmd_init, usage_init, "in" }, @@ -143,6 +145,7 @@ static struct got_cmd got_commands[] = { { "histedit", cmd_histedit, usage_histedit, "he" }, { "stage", cmd_stage, usage_stage, "sg" }, { "unstage", cmd_unstage, usage_unstage, "ug" }, + { "cat", cmd_cat, usage_cat, "" }, }; static void @@ -1905,7 +1908,8 @@ done: static const struct got_error * match_object_id(struct got_object_id **id, char **label, - const char *id_str, int obj_type, struct got_repository *repo) + const char *id_str, int obj_type, int resolve_tags, + struct got_repository *repo) { const struct got_error *err; struct got_tag_object *tag; @@ -1914,18 +1918,25 @@ match_object_id(struct got_object_id **id, char **labe *id = NULL; *label = NULL; - err = got_repo_object_match_tag(&tag, id_str, GOT_OBJ_TYPE_ANY, repo); - if (err == NULL) { - *id = got_object_id_dup(got_object_tag_get_object_id(tag)); - if (*id == NULL) - err = got_error_from_errno("got_object_id_dup"); - if (asprintf(label, "refs/tags/%s", - got_object_tag_get_name(tag)) == -1) - err = got_error_from_errno("asprintf"); - got_object_tag_close(tag); - return err; - } else if (err->code != GOT_ERR_NO_OBJ) - return err; + if (resolve_tags) { + err = got_repo_object_match_tag(&tag, id_str, GOT_OBJ_TYPE_ANY, + repo); + if (err == NULL) { + *id = got_object_id_dup( + got_object_tag_get_object_id(tag)); + if (*id == NULL) + err = got_error_from_errno("got_object_id_dup"); + else if (asprintf(label, "refs/tags/%s", + got_object_tag_get_name(tag)) == -1) { + err = got_error_from_errno("asprintf"); + free(id); + *id = NULL; + } + got_object_tag_close(tag); + return err; + } else if (err->code != GOT_ERR_NO_OBJ) + return err; + } err = got_repo_match_object_id_prefix(id, id_str, obj_type, repo); if (err) { @@ -2095,11 +2106,13 @@ cmd_diff(int argc, char *argv[]) goto done; } - error = match_object_id(&id1, &label1, id_str1, GOT_OBJ_TYPE_ANY, repo); + error = match_object_id(&id1, &label1, id_str1, GOT_OBJ_TYPE_ANY, 1, + repo); if (error) goto done; - error = match_object_id(&id2, &label2, id_str2, GOT_OBJ_TYPE_ANY, repo); + error = match_object_id(&id2, &label2, id_str2, GOT_OBJ_TYPE_ANY, 1, + repo); if (error) goto done; @@ -5947,5 +5960,276 @@ done: free((char *)pe->path); got_pathlist_free(&paths); free(cwd); + return error; +} + +__dead static void +usage_cat(void) +{ + fprintf(stderr, "usage: %s cat [-r repository ] object1 " + "[object2 ...]\n", getprogname()); + exit(1); +} + +static const struct got_error * +cat_blob(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_blob_object *blob; + + err = got_object_open_as_blob(&blob, repo, id, 8192); + if (err) + return err; + + err = got_object_blob_dump_to_file(NULL, NULL, NULL, outfile, blob); + got_object_blob_close(blob); + return err; +} + +static const struct got_error * +cat_tree(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_tree_object *tree; + const struct got_tree_entries *entries; + struct got_tree_entry *te; + + err = got_object_open_as_tree(&tree, repo, id); + if (err) + return err; + + entries = got_object_tree_get_entries(tree); + te = SIMPLEQ_FIRST(&entries->head); + while (te) { + char *id_str; + if (sigint_received || sigpipe_received) + break; + err = got_object_id_str(&id_str, te->id); + if (err) + break; + fprintf(outfile, "%s %.7o %s\n", id_str, te->mode, te->name); + free(id_str); + te = SIMPLEQ_NEXT(te, entry); + } + + got_object_tree_close(tree); + return err; +} + +static const struct got_error * +cat_commit(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_commit_object *commit; + const struct got_object_id_queue *parent_ids; + struct got_object_qid *pid; + char *id_str = NULL; + char *logmsg = NULL; + int i; + + err = got_object_open_as_commit(&commit, repo, id); + if (err) + return err; + + err = got_object_id_str(&id_str, got_object_commit_get_tree_id(commit)); + if (err) + goto done; + + fprintf(outfile, "tree: %s\n", id_str); + parent_ids = got_object_commit_get_parent_ids(commit); + fprintf(outfile, "parents: %d\n", + got_object_commit_get_nparents(commit)); + i = 1; + SIMPLEQ_FOREACH(pid, parent_ids, entry) { + char *pid_str; + err = got_object_id_str(&pid_str, pid->id); + if (err) + goto done; + fprintf(outfile, "parent %d: %s\n", i++, pid_str); + free(pid_str); + } + fprintf(outfile, "author: %s\n", + got_object_commit_get_author(commit)); + fprintf(outfile, "author-time: %lld\n", + got_object_commit_get_author_time(commit)); + + fprintf(outfile, "committer: %s\n", + got_object_commit_get_author(commit)); + fprintf(outfile, "committer-time: %lld\n", + got_object_commit_get_committer_time(commit)); + + err = got_object_commit_get_logmsg(&logmsg, commit); + fprintf(outfile, "log-message: %zd bytes\n", strlen(logmsg)); + fprintf(outfile, "%s", logmsg); +done: + free(id_str); + free(logmsg); + got_object_commit_close(commit); + return err; +} + +static const struct got_error * +cat_tag(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_tag_object *tag; + char *id_str = NULL; + const char *tagmsg = NULL; + + err = got_object_open_as_tag(&tag, repo, id); + if (err) + return err; + + err = got_object_id_str(&id_str, got_object_tag_get_object_id(tag)); + if (err) + goto done; + + fprintf(outfile, "tag-name: %s\n", got_object_tag_get_name(tag)); + switch (got_object_tag_get_object_type(tag)) { + case GOT_OBJ_TYPE_BLOB: + fprintf(outfile, "tagged-object-type: blob\n"); + break; + case GOT_OBJ_TYPE_TREE: + fprintf(outfile, "tagged-object-type: tree\n"); + break; + case GOT_OBJ_TYPE_COMMIT: + fprintf(outfile, "tagged-object-type: commit\n"); + break; + case GOT_OBJ_TYPE_TAG: + fprintf(outfile, "tagged-object-type: tag\n"); + break; + default: + break; + } + fprintf(outfile, "tagged-object: %s\n", id_str); + + fprintf(outfile, "tagger: %s\n", + got_object_tag_get_tagger(tag)); + fprintf(outfile, "tagger-time: %lld %lld\n", + got_object_tag_get_tagger_time(tag), + got_object_tag_get_tagger_gmtoff(tag)); + + tagmsg = got_object_tag_get_message(tag); + fprintf(outfile, "tag-message: %zd bytes\n", strlen(tagmsg)); + fprintf(outfile, "%s", tagmsg); +done: + free(id_str); + got_object_tag_close(tag); + return err; +} + +static const struct got_error * +cmd_cat(int argc, char *argv[]) +{ + const struct got_error *error; + 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; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "r:")) != -1) { + switch (ch) { + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) + err(1, "-r option"); + got_path_strip_trailing_slashes(repo_path); + break; + default: + usage_cat(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + if (worktree) { + if (repo_path == NULL) { + repo_path = strdup( + got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + if (repo_path == NULL) { + repo_path = getcwd(NULL, 0); + if (repo_path == NULL) + return got_error_from_errno("getcwd"); + } + + error = got_repo_open(&repo, repo_path); + free(repo_path); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + 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; + + error = got_object_get_type(&obj_type, repo, id); + if (error) + break; + + switch (obj_type) { + case GOT_OBJ_TYPE_BLOB: + error = cat_blob(id, repo, stdout); + break; + case GOT_OBJ_TYPE_TREE: + error = cat_tree(id, repo, stdout); + break; + case GOT_OBJ_TYPE_COMMIT: + error = cat_commit(id, repo, stdout); + break; + case GOT_OBJ_TYPE_TAG: + error = cat_tag(id, repo, stdout); + break; + default: + error = got_error(GOT_ERR_OBJ_TYPE); + break; + } + if (error) + break; + free(label); + label = NULL; + free(id); + id = NULL; + } + +done: + free(label); + free(id); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } return error; } blob - 5504da8782308033d1df67e50aee808789db7e73 blob + ceae59cbb94ce2b6ea9842e276aa873a7b7c483b --- include/got_object.h +++ include/got_object.h @@ -214,7 +214,7 @@ const struct got_error *got_object_blob_dump_to_file(s /* * Attempt to open a tag object in a repository. - * The caller must dispose of the tree with got_object_tag_close(). + * The caller must dispose of the tree with got_tag_object_close(). */ const struct got_error *got_object_open_as_tag(struct got_tag_object **, struct got_repository *, struct got_object_id *); @@ -234,5 +234,18 @@ int got_object_tag_get_object_type(struct got_tag_obje */ struct got_object_id *got_object_tag_get_object_id(struct got_tag_object *); + +/* Get the timestamp of the tag. */ +time_t got_object_tag_get_tagger_time(struct got_tag_object *); + +/* Get the tag's timestamp's GMT offset. */ +time_t got_object_tag_get_tagger_gmtoff(struct got_tag_object *); + +/* Get the author of the tag. */ +const char *got_object_tag_get_tagger(struct got_tag_object *); + +/* Get the tag message associated with the tag. */ +const char *got_object_tag_get_message(struct got_tag_object *); + const struct got_error *got_object_commit_add_parent(struct got_commit_object *, const char *); blob - a47efb07e8c0310f225ecd2a079dafb706f795fb blob + 72be8b628623db608bfe5c38242bcd135e8425cd --- lib/object.c +++ lib/object.c @@ -1422,8 +1422,32 @@ struct got_object_id * got_object_tag_get_object_id(struct got_tag_object *tag) { return &tag->id; +} + +time_t +got_object_tag_get_tagger_time(struct got_tag_object *tag) +{ + return tag->tagger_time; +} + +time_t +got_object_tag_get_tagger_gmtoff(struct got_tag_object *tag) +{ + return tag->tagger_gmtoff; +} + +const char * +got_object_tag_get_tagger(struct got_tag_object *tag) +{ + return tag->tagger; } +const char * +got_object_tag_get_message(struct got_tag_object *tag) +{ + return tag->tagmsg; +} + static struct got_tree_entry * find_entry_by_name(struct got_tree_object *tree, const char *name, size_t len) { blob - c7f57651b4255a40c5e239acede9d76dae99f6dc blob + 010a6bbd9da121a641c0c2f1321b9919f736476f --- regress/cmdline/Makefile +++ regress/cmdline/Makefile @@ -1,5 +1,5 @@ REGRESS_TARGETS=checkout update status log add rm diff blame branch ref commit \ - revert cherrypick backout rebase import histedit stage unstage + revert cherrypick backout rebase import histedit stage unstage cat NOOBJ=Yes checkout: @@ -59,4 +59,6 @@ stage: unstage: ./unstage.sh +cat: + ./cat.sh .include blob - /dev/null blob + 280f27add37a11d0725d6fa7e4cd893a6bb74c36 (mode 755) --- /dev/null +++ regress/cmdline/cat.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# +# Copyright (c) 2019 Stefan Sperling +# +# 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. + +. ./common.sh + +function test_cat_basic { + local testroot=`test_init cat_basic` + 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 + echo "alpha" > $testroot/stdout.expected + got cat -r $testroot/repo $alpha_id > $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 + echo "$delta_id 0100644 delta" > $testroot/stdout.expected + got cat -r $testroot/repo $gamma_id > $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 commit + echo -n "tree: " > $testroot/stdout.expected + git_show_tree $testroot/repo >> $testroot/stdout.expected + echo >> $testroot/stdout.expected + echo "parents: 0" >> $testroot/stdout.expected + echo "author: $GOT_AUTHOR" >> $testroot/stdout.expected + echo "author-time: $author_time" >> $testroot/stdout.expected + echo "committer: Flan Hacker " >> $testroot/stdout.expected + echo "committer-time: $author_time" >> $testroot/stdout.expected + echo "log-message: 22 bytes" >> $testroot/stdout.expected + printf "\nadding the test tree\n" >> $testroot/stdout.expected + + got cat -r $testroot/repo $commit_id > $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 + + # TODO: test cat tag + + test_done "$testroot" "$ret" + +} + +run_test test_cat_basic blob - 5c3679ee79c0ba85d613cf0d46cf0fae5ffbe290 blob + 346b80d2069558d1f86a09e0cce41f0e2d7f7a94 --- regress/cmdline/common.sh +++ regress/cmdline/common.sh @@ -52,7 +52,8 @@ function git_show_head function git_show_author_time { local repo="$1" - (cd $repo && git show --no-patch --pretty='format:%at') + local object="$2" + (cd $repo && git show --no-patch --pretty='format:%at' $object) } function git_show_parent_commit