Commit Diff


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 <bsd.regress.mk>
blob - /dev/null
blob + 280f27add37a11d0725d6fa7e4cd893a6bb74c36 (mode 755)
--- /dev/null
+++ regress/cmdline/cat.sh
@@ -0,0 +1,76 @@
+#!/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
@@ -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