commit b2118c49a14c29447e228bf9a2b2a38f2da4f10b from: Stefan Sperling date: Tue Jul 28 20:17:19 2020 UTC Add a 'got info' command which displays work tree meta-data. Remove the alias 'got in' for 'got init'. The 'in' alias was too close to either 'init' or 'info'. ok tracey, millert commit - cf07f22bd8beb1ed9e048433d553e394cc323c5f commit + b2118c49a14c29447e228bf9a2b2a38f2da4f10b blob - 136fa542011eaf775871dd87e5f0c0a2ba5ef5a4 blob + f9e5cd6f3135e7407b0775077c23ca64fd500590 --- 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 @@ -1997,6 +2011,11 @@ 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 .Dl $ got add sys/uvm/uvm_ubc.c blob - fb2d55929f89f48f959e0ddfdc2fa639fe2c64d0 blob + b786141f0da3d2e72828aa6588c6bb709920e2f1 --- 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 @@ -9314,6 +9317,187 @@ done: repo_error = got_repo_close(repo); 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 blob + f0548eaf6e23287e3754b08de478a43d1c8206c2 --- include/got_worktree.h +++ include/got_worktree.h @@ -73,6 +73,12 @@ const char *got_worktree_get_repo_path(struct got_work * Get the path prefix associated with a worktree. */ 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. @@ -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 work-tree meta data for paths in the work tree. + * The info callback will be invoked with the provided void * argument, + * a path, and meta-data arguments (see got_worktree_path_info_cb). + */ +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 blob + 0538db4eaaa0eac5a99d717327a150efac52b29b --- 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; } @@ -7671,3 +7683,92 @@ done: 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; +}