"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Tracey Emery <tracey@traceyemery.net>
Subject:
Re: add 'got info' command
To:
gameoftrees@openbsd.org, Stefan Sperling <stsp@stsp.name>
Date:
Sat, 25 Jul 2020 21:40:49 -0600

Download raw body.

Thread
On July 25, 2020 12:10:10 PM MDT, Stefan Sperling <stsp@stsp.name> wrote:
>Got lacks a built-in command to examine work tree meta data.
>
>It has always been possible to obtain some information from the files
>in the .got/ directory because some of those files are plain-text:
>
> - Get the current branch:
>   $ cat .got/head-ref
>
> - Get the current base commit:
>   $ cat .got/base-commit
>
>But viewing information stored in the file index is difficult. And
>making
>this information more accessible could help with debugging problems
>with
>a broken work tree.
>
>This first implementation of 'got info' is not very flexible. I did not
>add any options yet. The output can be parsed by scripts but my
>intention
>is to add some options later to make scripting very easy.
>Options which would make 'got info' print individual bits of
>information,
>such as "show me the repository path for this work tree", and so on.
>Similar functionality is offered by 'svn info --show-item' in
>Subversion.
>
>My initial implementation just dumped everything by default but that
>can be
>too much. The default output implemented in the patch below shows
>general
>context information only:
>
>$ got info
>work tree: /usr/src
>work tree base commit: 3ff74ee673f6439bbf658b9cb1b2ff65fbb3ee09
>work tree path prefix: /
>work tree branch reference: refs/heads/iwm-txagg
>work tree UUID: bb7789ce-609a-472c-86e2-a42c1a58caff
>repository: /git/src.git
>$ 
>
>Information stored in the file index can be appended to this general
>info
>by specifying path arguments. (File index content would not be useful
>in
>isolation, so general context info is always displayed.)
>
>$ got info Makefile
>work tree: /usr/src
>work tree base commit: 3ff74ee673f6439bbf658b9cb1b2ff65fbb3ee09
>work tree path prefix: /
>work tree branch reference: refs/heads/iwm-txagg
>work tree UUID: bb7789ce-609a-472c-86e2-a42c1a58caff
>repository: /git/src.git
>-----------------------------------------------
>file: Makefile
>mode: 644
>timestamp: Mon Jul 13 14:53:54 2020 CEST
>based on blob: 5002ac02445f9ab3b2fc89397a45f00b8b287fd2
>based on commit: 20599cf97eccdb9ab18672df969fe10c00802e7d
>
>Entire sub trees can also be shown:
>
>$ got info sbin/ifconfig/
>work tree: /usr/src
>work tree base commit: 3ff74ee673f6439bbf658b9cb1b2ff65fbb3ee09
>work tree path prefix: /
>work tree branch reference: refs/heads/iwm-txagg
>work tree UUID: bb7789ce-609a-472c-86e2-a42c1a58caff
>repository: /git/src.git
>-----------------------------------------------
>file: sbin/ifconfig/Makefile
>mode: 644
>timestamp: Mon Jul 13 14:58:12 2020 CEST
>based on blob: b674f82f00422b46f899c100a77d8ce875a4342b
>based on commit: 20599cf97eccdb9ab18672df969fe10c00802e7d
>-----------------------------------------------
>file: sbin/ifconfig/brconfig.c
>mode: 644
>timestamp: Mon Jul 13 14:58:12 2020 CEST
>based on blob: e33b73ebf2ca9d3cd26e08d63427e45e378c81af
>based on commit: 20599cf97eccdb9ab18672df969fe10c00802e7d
>-----------------------------------------------
>file: sbin/ifconfig/ifconfig.8
>mode: 644
>timestamp: Mon Jul 13 14:58:12 2020 CEST
>based on blob: 10111bf2c454181e92e103a0dd2bbfdbd77e9f90
>based on commit: 20599cf97eccdb9ab18672df969fe10c00802e7d
>-----------------------------------------------
>file: sbin/ifconfig/ifconfig.c
>mode: 644
>timestamp: Mon Jul 13 14:58:12 2020 CEST
>based on blob: cc314b7a44f8cd9a74ec6e32df6ce1cf8183a068
>based on commit: 20599cf97eccdb9ab18672df969fe10c00802e7d
>-----------------------------------------------
>file: sbin/ifconfig/ifconfig.h
>mode: 644
>timestamp: Mon Jul 13 14:58:12 2020 CEST
>based on blob: 1d5b9ccff10dc0a572b16fcff18d9de45bd00fef
>based on commit: 20599cf97eccdb9ab18672df969fe10c00802e7d
>-----------------------------------------------
>file: sbin/ifconfig/sff.c
>mode: 644
>timestamp: Mon Jul 13 14:58:12 2020 CEST
>based on blob: a800a1f55d0ee593deaaf7e091e22c4c4cc5e1b5
>based on commit: 20599cf97eccdb9ab18672df969fe10c00802e7d
>$
>
>It is also possible to show all files, for example by specifying a dot
>as the path argument: got info .
>This is probably not intuitive and I did consider adding an option
>argument
>for this purpose (such as -a, "show all"). But such an option becomes
>awkward
>to handle in combination with path arguments so I decided to stick to
>just
>path arguments for now. The man page explains how it works.
>
>I am dropping the alias 'got in' (for 'got init').
>This alias is too close to either 'init' or 'info'. Both commands are
>short enough so I think the best approach is to remove this alias.
>
>Unfortunately, the pledge for 'got info' is quite large:
>	"stdio rpath wpath flock proc exec sendfd unveil"
>The "proc exec sendfd" pledges are only needed because we are reading
>the
>repository config file with 'got-read-gitconfig' when opening a work
>tree.
>This could be improved later.
>
>Any suggestions or concerns?

Right now, I love the idea and have wanted something like this for awhile now. Will read more thoroughly tomorrow. Thanks!

>
>diff cf07f22bd8beb1ed9e048433d553e394cc323c5f /home/stsp/src/got
>blob - 136fa542011eaf775871dd87e5f0c0a2ba5ef5a4
>file + got/got.1
>--- got/got.1
>+++ got/got.1
>@@ -73,9 +73,6 @@ the
> command must be used to populate the empty repository before
> .Cm got checkout
> can be used.
>-.It Cm in
>-Short alias for
>-.Cm init .
>.It Cm import Oo Fl b Ar branch Oc Oo Fl m Ar message Oc Oo Fl r Ar
>repository-path Oc Oo Fl I Ar pattern Oc Ar directory
> Create an initial commit in a repository from the file hierarchy
> within the specified
>@@ -1862,6 +1859,23 @@ 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
>+.It Cm info Op Ar path ...
>+Display meta-data stored in a work tree.
>+See
>+.Xr got-worktree 5
>+for details.
>+.Pp
>+The work tree to use is resolved implicitly by walking upwards from
>the
>+current working directory.
>+.Pp
>+If one or more
>+.Ar path
>+arguments are specified, show additional per-file information for
>tracked
>+files located at or within these paths.
>+If a
>+.Ar path
>+argument corresponds to the work tree's root directory, display
>information
>+for all tracked files.
> .El
> .Sh ENVIRONMENT
> .Bl -tag -width GOT_AUTHOR
>@@ -1996,6 +2010,11 @@ working directory, so this command will log the
>subdir
> And this command has the same effect:
> .Pp
> .Dl $ cd sys/dev/usb && got log ../../uvm
>+.Pp
>+And this command displays work tree meta-data about all tracked files:
>+.Pp
>+.Dl $ cd /usr/src
>+.Dl $ got info\ . | less
> .Pp
> Add new files and remove obsolete files in a work tree directory:
> .Pp
>blob - fb2d55929f89f48f959e0ddfdc2fa639fe2c64d0
>file + got/got.c
>--- got/got.c
>+++ got/got.c
>@@ -109,6 +109,7 @@ __dead static void	usage_integrate(void);
> __dead static void	usage_stage(void);
> __dead static void	usage_unstage(void);
> __dead static void	usage_cat(void);
>+__dead static void	usage_info(void);
> 
> static const struct got_error*		cmd_init(int, char *[]);
> static const struct got_error*		cmd_import(int, char *[]);
>@@ -136,9 +137,10 @@ static const struct got_error*		cmd_integrate(int,
>cha
> 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 const struct got_error*		cmd_info(int, char *[]);
> 
> static struct got_cmd got_commands[] = {
>-	{ "init",	cmd_init,	usage_init,	"in" },
>+	{ "init",	cmd_init,	usage_init,	"" },
> 	{ "import",	cmd_import,	usage_import,	"im" },
> 	{ "clone",	cmd_clone,	usage_clone,	"cl" },
> 	{ "fetch",	cmd_fetch,	usage_fetch,	"fe" },
>@@ -164,6 +166,7 @@ static struct got_cmd got_commands[] = {
> 	{ "stage",	cmd_stage,	usage_stage,	"sg" },
> 	{ "unstage",	cmd_unstage,	usage_unstage,	"ug" },
> 	{ "cat",	cmd_cat,	usage_cat,	"" },
>+	{ "info",	cmd_info,	usage_info,	"" },
> };
> 
> static void
>@@ -9315,5 +9318,186 @@ done:
> 		if (error == NULL)
> 			error = repo_error;
> 	}
>+	return error;
>+}
>+
>+__dead static void
>+usage_info(void)
>+{
>+	fprintf(stderr, "usage: %s info [path ...]\n",
>+	    getprogname());
>+	exit(1);
>+}
>+
>+static const struct got_error *
>+print_path_info(void *arg, const char *path, mode_t mode, time_t
>mtime,
>+    struct got_object_id *blob_id, struct got_object_id
>*staged_blob_id,
>+    struct got_object_id *commit_id)
>+{
>+	const struct got_error *err = NULL;
>+	char *id_str = NULL;
>+	char datebuf[128];
>+	struct tm mytm, *tm;
>+	struct got_pathlist_head *paths = arg;
>+	struct got_pathlist_entry *pe;
>+
>+	/*
>+	 * Clear error indication from any of the path arguments which
>+	 * would cause this file index entry to be displayed.
>+	 */
>+	TAILQ_FOREACH(pe, paths, entry) {
>+		if (got_path_cmp(path, pe->path, strlen(path),
>+		    pe->path_len) == 0 ||
>+		    got_path_is_child(path, pe->path, pe->path_len))
>+			pe->data = NULL; /* no error */
>+	}
>+
>+	printf(GOT_COMMIT_SEP_STR);
>+	if (S_ISLNK(mode))
>+		printf("symlink: %s\n", path);
>+	else if (S_ISREG(mode)) {
>+		printf("file: %s\n", path);
>+		printf("mode: %o\n", mode & (S_IRWXU | S_IRWXG | S_IRWXO));
>+	} else if (S_ISDIR(mode))
>+		printf("directory: %s\n", path);
>+	else
>+		printf("something: %s\n", path);
>+
>+	tm = localtime_r(&mtime, &mytm);
>+	if (tm == NULL)
>+		return NULL;
>+	if (strftime(datebuf, sizeof(datebuf), "%c %Z", tm) >=
>sizeof(datebuf))
>+		return got_error(GOT_ERR_NO_SPACE);
>+	printf("timestamp: %s\n", datebuf);
>+
>+	if (blob_id) {
>+		err = got_object_id_str(&id_str, blob_id);
>+		if (err)
>+			return err;
>+		printf("based on blob: %s\n", id_str);
>+		free(id_str);
>+	}
>+
>+	if (staged_blob_id) {
>+		err = got_object_id_str(&id_str, staged_blob_id);
>+		if (err)
>+			return err;
>+		printf("based on staged blob: %s\n", id_str);
>+		free(id_str);
>+	}
>+
>+	if (commit_id) {
>+		err = got_object_id_str(&id_str, commit_id);
>+		if (err)
>+			return err;
>+		printf("based on commit: %s\n", id_str);
>+		free(id_str);
>+	}
>+
>+	return NULL;
>+}
>+
>+static const struct got_error *
>+cmd_info(int argc, char *argv[])
>+{
>+	const struct got_error *error = NULL;
>+	struct got_worktree *worktree = NULL;
>+	char *cwd = NULL, *id_str = NULL;
>+	struct got_pathlist_head paths;
>+	struct got_pathlist_entry *pe;
>+	char *uuidstr = NULL;
>+	int ch, show_files = 0;
>+
>+	TAILQ_INIT(&paths);
>+
>+	while ((ch = getopt(argc, argv, "")) != -1) {
>+		switch (ch) {
>+		default:
>+			usage_info();
>+			/* NOTREACHED */
>+		}
>+	}
>+
>+	argc -= optind;
>+	argv += optind;
>+
>+#ifndef PROFILE
>+	if (pledge("stdio rpath wpath flock proc exec sendfd unveil",
>+	    NULL) == -1)
>+		err(1, "pledge");
>+#endif
>+	cwd = getcwd(NULL, 0);
>+	if (cwd == NULL) {
>+		error = got_error_from_errno("getcwd");
>+		goto done;
>+	}
>+
>+	error = got_worktree_open(&worktree, cwd);
>+	if (error) {
>+		if (error->code == GOT_ERR_NOT_WORKTREE)
>+			error = wrap_not_worktree_error(error, "status", cwd);
>+		goto done;
>+	}
>+
>+	error = apply_unveil(NULL, 0, got_worktree_get_root_path(worktree));
>+	if (error)
>+		goto done;
>+
>+	if (argc >= 1) {
>+		error = get_worktree_paths_from_argv(&paths, argc, argv,
>+		    worktree);
>+		if (error)
>+			goto done;
>+		show_files = 1;
>+	}
>+
>+	error = got_object_id_str(&id_str,
>+	    got_worktree_get_base_commit_id(worktree));
>+	if (error)
>+		goto done;
>+
>+	error = got_worktree_get_uuid(&uuidstr, worktree);
>+	if (error)
>+		goto done;
>+
>+	printf("work tree: %s\n", got_worktree_get_root_path(worktree));
>+	printf("work tree base commit: %s\n", id_str);
>+	printf("work tree path prefix: %s\n",
>+	    got_worktree_get_path_prefix(worktree));
>+	printf("work tree branch reference: %s\n",
>+	    got_worktree_get_head_ref_name(worktree));
>+	printf("work tree UUID: %s\n", uuidstr);
>+	printf("repository: %s\n", got_worktree_get_repo_path(worktree));
>+
>+	if (show_files) {
>+		struct got_pathlist_entry *pe;
>+		TAILQ_FOREACH(pe, &paths, entry) {
>+			if (pe->path_len == 0)
>+				continue;
>+			/*
>+			 * Assume this path will fail. This will be corrected
>+			 * in print_path_info() in case the path does suceeed.
>+			 */
>+			pe->data = (void *)got_error_path(pe->path,
>+			    GOT_ERR_BAD_PATH);
>+		}
>+		error = got_worktree_path_info(worktree, &paths,
>+		    print_path_info, &paths, check_cancelled, NULL);
>+		if (error)
>+			goto done;
>+		TAILQ_FOREACH(pe, &paths, entry) {
>+			if (pe->data != NULL) {
>+				error = pe->data; /* bad path */
>+				break;
>+			}
>+		}
>+	}
>+done:
>+	TAILQ_FOREACH(pe, &paths, entry)
>+		free((char *)pe->path);
>+	got_pathlist_free(&paths);
>+	free(cwd);
>+	free(id_str);
>+	free(uuidstr);
> 	return error;
> }
>blob - 277c66b0096dcee60d7ae5305877516328376f34
>file + include/got_worktree.h
>--- include/got_worktree.h
>+++ include/got_worktree.h
>@@ -75,6 +75,12 @@ const char *got_worktree_get_repo_path(struct
>got_work
> const char *got_worktree_get_path_prefix(struct got_worktree *);
> 
> /*
>+ * Get the UUID of a work tree as a string.
>+ * The caller must dispose of the returned UUID string with free(3).
>+ */
>+const struct got_error *got_worktree_get_uuid(char **, struct
>got_worktree *);
>+
>+/*
>  * Check if a user-provided path prefix matches that of the worktree.
>  */
> const struct got_error *got_worktree_match_path_prefix(int *,
>@@ -442,3 +448,18 @@ const struct got_error *got_worktree_stage(struct
>got_
> const struct got_error *got_worktree_unstage(struct got_worktree *,
>     struct got_pathlist_head *, got_worktree_checkout_cb, void *,
>     got_worktree_patch_cb, void *, struct got_repository *);
>+
>+/* A callback function which is invoked with per-path info. */
>+typedef const struct got_error *(*got_worktree_path_info_cb)(void *,
>+    const char *path, mode_t mode, time_t mtime,
>+    struct got_object_id *blob_id, struct got_object_id
>*staged_blob_id,
>+    struct got_object_id *commit_id);
>+
>+/* 
>+ * Report the status of paths in the work tree.
>+ * The info callback will be invoked with the provided void *
>argument,
>+ * a path, and a corresponding status code.
>+ */
>+const struct got_error *
>+got_worktree_path_info(struct got_worktree *, struct got_pathlist_head
>*,
>+    got_worktree_path_info_cb, void *, got_cancel_cb , void *);
>blob - 0a072932c20e3ee4dec70243cb7cdc3739d3c8ba
>file + lib/worktree.c
>--- lib/worktree.c
>+++ lib/worktree.c
>@@ -2143,21 +2143,33 @@ diff_new(void *arg, struct got_tree_entry *te,
>const c
> 	return err;
> }
> 
>+const struct got_error *
>+got_worktree_get_uuid(char **uuidstr, struct got_worktree *worktree)
>+{
>+	uint32_t uuid_status;
>+
>+	uuid_to_string(&worktree->uuid, uuidstr, &uuid_status);
>+	if (uuid_status != uuid_s_ok) {
>+		*uuidstr = NULL;
>+		return got_error_uuid(uuid_status, "uuid_to_string");
>+	}
>+
>+	return NULL;
>+}
>+
> static const struct got_error *
>get_ref_name(char **refname, struct got_worktree *worktree, const char
>*prefix)
> {
> 	const struct got_error *err = NULL;
> 	char *uuidstr = NULL;
>-	uint32_t uuid_status;
> 
> 	*refname = NULL;
> 
>-	uuid_to_string(&worktree->uuid, &uuidstr, &uuid_status);
>-	if (uuid_status != uuid_s_ok)
>-		return got_error_uuid(uuid_status, "uuid_to_string");
>+	err = got_worktree_get_uuid(&uuidstr, worktree);
>+	if (err)
>+		return err;
> 
>-	if (asprintf(refname, "%s-%s", prefix, uuidstr)
>-	    == -1) {
>+	if (asprintf(refname, "%s-%s", prefix, uuidstr) == -1) {
> 		err = got_error_from_errno("asprintf");
> 		*refname = NULL;
> 	}
>@@ -7667,6 +7679,95 @@ done:
> 	if (fileindex)
> 		got_fileindex_free(fileindex);
> 	unlockerr = lock_worktree(worktree, LOCK_SH);
>+	if (unlockerr && err == NULL)
>+		err = unlockerr;
>+	return err;
>+}
>+
>+struct report_file_info_arg {
>+	struct got_worktree *worktree;
>+	got_worktree_path_info_cb info_cb;
>+	void *info_arg;
>+	struct got_pathlist_head *paths;
>+	got_cancel_cb cancel_cb;
>+	void *cancel_arg;
>+};
>+
>+static const struct got_error *
>+report_file_info(void *arg, struct got_fileindex_entry *ie)
>+{
>+	struct report_file_info_arg *a = arg;
>+	struct got_pathlist_entry *pe;
>+	struct got_object_id blob_id, staged_blob_id, commit_id;
>+	struct got_object_id *blob_idp = NULL, *staged_blob_idp = NULL;
>+	struct got_object_id *commit_idp = NULL;
>+	int stage;
>+
>+	if (a->cancel_cb && a->cancel_cb(a->cancel_arg))
>+		return got_error(GOT_ERR_CANCELLED);
>+
>+	TAILQ_FOREACH(pe, a->paths, entry) {
>+		if (pe->path_len == 0 || strcmp(pe->path, ie->path) == 0 ||
>+		    got_path_is_child(ie->path, pe->path, pe->path_len))
>+			break;
>+	}
>+	if (pe == NULL) /* not found */
>+		return NULL;
>+
>+	if (got_fileindex_entry_has_blob(ie)) {
>+		memcpy(blob_id.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH);
>+		blob_idp = &blob_id;
>+	}
>+	stage = got_fileindex_entry_stage_get(ie);
>+	if (stage == GOT_FILEIDX_STAGE_MODIFY ||
>+	    stage == GOT_FILEIDX_STAGE_ADD) {
>+		memcpy(staged_blob_id.sha1, ie->staged_blob_sha1,
>+		    SHA1_DIGEST_LENGTH);
>+		staged_blob_idp = &staged_blob_id;
>+	}
>+
>+	if (got_fileindex_entry_has_commit(ie)) {
>+		memcpy(commit_id.sha1, ie->commit_sha1, SHA1_DIGEST_LENGTH);
>+		commit_idp = &commit_id;
>+	}
>+
>+	return a->info_cb(a->info_arg, ie->path,
>got_fileindex_perms_to_st(ie),
>+	    (time_t)ie->mtime_sec, blob_idp, staged_blob_idp, commit_idp);
>+}
>+
>+const struct got_error *
>+got_worktree_path_info(struct got_worktree *worktree,
>+    struct got_pathlist_head *paths,
>+    got_worktree_path_info_cb info_cb, void *info_arg,
>+    got_cancel_cb cancel_cb, void *cancel_arg)
>+
>+{
>+	const struct got_error *err = NULL, *unlockerr;
>+	struct got_fileindex *fileindex = NULL;
>+	char *fileindex_path = NULL;
>+	struct report_file_info_arg arg;
>+
>+	err = lock_worktree(worktree, LOCK_SH);
>+	if (err)
>+		return err;
>+
>+	err = open_fileindex(&fileindex, &fileindex_path, worktree);
>+	if (err)
>+		goto done;
>+
>+	arg.worktree = worktree;
>+	arg.info_cb = info_cb;
>+	arg.info_arg = info_arg;
>+	arg.paths = paths;
>+	arg.cancel_cb = cancel_cb;
>+	arg.cancel_arg = cancel_arg;
>+	err = got_fileindex_for_each_entry_safe(fileindex, report_file_info,
>+	    &arg);
>+done:
>+	free(fileindex_path);
>+	if (fileindex)
>+		got_fileindex_free(fileindex);
>+	unlockerr = lock_worktree(worktree, LOCK_UN);
> 	if (unlockerr && err == NULL)
> 		err = unlockerr;
> 	return err;


-- 
Tracey Emery