Commit Diff


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;
+}