From: Tracey Emery Subject: Re: add 'got info' command To: gameoftrees@openbsd.org, Stefan Sperling Date: Sat, 25 Jul 2020 21:40:49 -0600 On July 25, 2020 12:10:10 PM MDT, 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? 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