Download raw body.
add 'got info' command
On Sat, Jul 25, 2020 at 08:10:10PM +0200, Stefan Sperling 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?
>
Diff looks good to me. It's a good start.
> 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
add 'got info' command