Download raw body.
add 'got info' command
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 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;
add 'got info' command