From: Tracey Emery Subject: Re: add 'got info' command To: gameoftrees@openbsd.org Date: Sun, 26 Jul 2020 11:22:30 -0600 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