commit - fc06ba561252d5ccc36fa7a9be53fe33d098ce8f
commit + 01073a5d20f017cbdb065decd25fa6d472cf809d
blob - 3caa70a3b90fb32af006222ca63d2b650bedb146
blob + 5cc02975e9734606b79d513463bbce1e7f4e8fd6
--- got/got.1
+++ got/got.1
.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
__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 *[]);
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" },
{ "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
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;
*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) {
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;
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
/*
* 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 *);
*/
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
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
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:
unstage:
./unstage.sh
+cat:
+ ./cat.sh
.include <bsd.regress.mk>
blob - /dev/null
blob + 280f27add37a11d0725d6fa7e4cd893a6bb74c36 (mode 755)
--- /dev/null
+++ regress/cmdline/cat.sh
+#!/bin/sh
+#
+# Copyright (c) 2019 Stefan Sperling <stsp@openbsd.org>
+#
+# 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 <flan_hacker@openbsd.org>" >> $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
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