Commit Diff


commit - 85f51bbadd426ab2f3f7fc70e879ac6763354479
commit + 04ca23f4590f27f82af9ef2a837afe86683d7339
blob - b7807ca827952f505c4858f826aa7a828c5ee860
blob + d08753947cf8d25b5a8bf669424775579e7cd6a8
--- got/got.1
+++ got/got.1
@@ -91,11 +91,11 @@ will be checked out.
 .El
 .\".It Cm status
 .\"Show current status of files.
-.It Cm log [ Fl c Ar commit ] [ Fl f ] [ Fl l Ar N ] [ Fl p ] [ Ar repository-path ]
+.It Cm log [ Fl c Ar commit ] [ Fl f ] [ Fl l Ar N ] [ Fl p ] [ Fl r Ar repository-path ] [ path ]
 Display history of a repository.
-If the
-.Ar repository path
-is omitted, assume the repository is located in the current working directory.
+If a
+.Ar path
+is specified, show only commits which modified this path.
 .Pp
 The options for
 .Cm got log
@@ -115,6 +115,10 @@ individual commits which originated on other branches 
 Limit history traversal to a given number of commits.
 .It Fl p
 Display the patch of modifications made in each commit.
+.It Fl r Ar repository-path
+Use the repository at the specified path.
+If not specified, assume the repository is located at or above the current
+working directory.
 .El
 .It Cm diff [ Ar repository-path ] Ar object1 Ar object2
 Display the differences between two objects in the repository.
blob - bbdd8feb7717e3603a45f96b3801a65d6f196eb5
blob + 005060e75e9b9ee5cb674c01e425e0bffdbcb634
--- got/got.c
+++ got/got.c
@@ -386,12 +386,12 @@ print_commit(struct got_commit_object *commit, struct 
 
 static const struct got_error *
 print_commits(struct got_object *root_obj, struct got_object_id *root_id,
-    struct got_repository *repo, int show_patch, int limit,
+    struct got_repository *repo, char *path, int show_patch, int limit,
     int first_parent_traversal)
 {
 	const struct got_error *err;
 	struct got_commit_graph *graph;
-	int ncommits;
+	int ncommits, found_obj = 0;
 
 	err = got_commit_graph_open(&graph, root_id, first_parent_traversal,
 	    repo);
@@ -425,6 +425,48 @@ print_commits(struct got_object *root_obj, struct got_
 		err = got_object_open_as_commit(&commit, repo, id);
 		if (err)
 			return err;
+		if (path) {
+			struct got_object *obj;
+			struct got_object_qid *pid;
+			int changed = 1;
+
+			err = got_object_open_by_path(&obj, repo, id, path);
+			if (err) {
+				if (err->code == GOT_ERR_NO_OBJ && found_obj) {
+					/*
+					 * Object was added in previous commit.
+					 * History stops here.
+					 */
+					err = NULL;
+				}
+				break;
+			}
+			found_obj = 1;
+
+			pid = SIMPLEQ_FIRST(&commit->parent_ids);
+			if (pid != NULL) {
+				struct got_object *pobj;
+				err = got_object_open_by_path(&pobj, repo,
+				    pid->id, path);
+				if (err) {
+					if (err->code != GOT_ERR_NO_OBJ) {
+						got_object_close(obj);
+						break;
+					}
+					err = NULL;
+					changed = 1;
+				} else {
+					changed = got_object_id_cmp(
+					    got_object_get_id(obj),
+					    got_object_get_id(pobj));
+					got_object_close(pobj);
+				}
+			}
+			if (!changed) {
+				got_object_commit_close(commit);
+				continue;
+			}
+		}
 		err = print_commit(commit, id, repo, show_patch);
 		got_object_commit_close(commit);
 		if (err || (limit && --limit == 0))
@@ -439,7 +481,7 @@ __dead static void
 usage_log(void)
 {
 	fprintf(stderr, "usage: %s log [-c commit] [-f] [ -l N ] [-p] "
-	    "[repository-path]\n", getprogname());
+	    "[-r repository-path] [path]\n", getprogname());
 	exit(1);
 }
 
@@ -447,10 +489,10 @@ static const struct got_error *
 cmd_log(int argc, char *argv[])
 {
 	const struct got_error *error;
-	struct got_repository *repo;
+	struct got_repository *repo = NULL;
 	struct got_object_id *id = NULL;
 	struct got_object *obj = NULL;
-	char *repo_path = NULL;
+	char *repo_path = NULL, *path = NULL, *cwd = NULL, *in_repo_path = NULL;
 	char *start_commit = NULL;
 	int ch;
 	int show_patch = 0, limit = 0, first_parent_traversal = 0;
@@ -461,7 +503,7 @@ cmd_log(int argc, char *argv[])
 		err(1, "pledge");
 #endif
 
-	while ((ch = getopt(argc, argv, "pc:l:f")) != -1) {
+	while ((ch = getopt(argc, argv, "pc:l:fr:")) != -1) {
 		switch (ch) {
 		case 'p':
 			show_patch = 1;
@@ -477,6 +519,11 @@ cmd_log(int argc, char *argv[])
 		case 'f':
 			first_parent_traversal = 1;
 			break;
+		case 'r':
+			repo_path = realpath(optarg, NULL);
+			if (repo_path == NULL)
+				err(1, "-r option");
+			break;
 		default:
 			usage();
 			/* NOTREACHED */
@@ -486,21 +533,31 @@ cmd_log(int argc, char *argv[])
 	argc -= optind;
 	argv += optind;
 
-	if (argc == 0) {
-		repo_path = getcwd(NULL, 0);
-		if (repo_path == NULL)
-			return got_error_from_errno();
-	} else if (argc == 1) {
-		repo_path = realpath(argv[0], NULL);
-		if (repo_path == NULL)
-			return got_error_from_errno();
-	} else
+	if (argc == 0)
+		path = strdup("");
+	else if (argc == 1)
+		path = strdup(argv[0]);
+	else
 		usage_log();
+	if (path == NULL)
+		return got_error_from_errno();
 
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL) {
+		error = got_error_from_errno();
+		goto done;
+	}
+	if (repo_path == NULL) {
+		repo_path = strdup(cwd);
+		if (repo_path == NULL) {
+			error = got_error_from_errno();
+			goto done;
+		}
+	}
+
 	error = got_repo_open(&repo, repo_path);
-	free(repo_path);
 	if (error != NULL)
-		return error;
+		goto done;
 
 	if (start_commit == NULL) {
 		struct got_reference *head_ref;
@@ -535,15 +592,31 @@ cmd_log(int argc, char *argv[])
 		}
 	}
 	if (error != NULL)
-		return error;
-	if (got_object_get_type(obj) == GOT_OBJ_TYPE_COMMIT)
-		error = print_commits(obj, id, repo, show_patch, limit,
-		    first_parent_traversal);
-	else
+		goto done;
+	if (got_object_get_type(obj) != GOT_OBJ_TYPE_COMMIT) {
 		error = got_error(GOT_ERR_OBJ_TYPE);
-	got_object_close(obj);
+		goto done;
+	}
+
+	error = got_repo_map_path(&in_repo_path, repo, path);
+	if (error != NULL)
+		goto done;
+	if (in_repo_path) {
+		free(path);
+		path = in_repo_path;
+	}
+
+	error = print_commits(obj, id, repo, path, show_patch,
+	    limit, first_parent_traversal);
+done:
+	free(path);
+	free(repo_path);
+	free(cwd);
+	if (obj)
+		got_object_close(obj);
 	free(id);
-	got_repo_close(repo);
+	if (repo)
+		got_repo_close(repo);
 	return error;
 }
 
blob - c60c51ce815bea1a4ee25e038ac6d97210ae026b
blob + 3f8ef8cb512966482a3b4410b0c4b51505fa5063
--- include/got_repository.h
+++ include/got_repository.h
@@ -38,3 +38,11 @@ struct got_reference;
  */
 const struct got_error *got_repo_get_reference(struct got_reference **,
     struct got_repository *, const char *);
+
+
+/* Indicate whether this is a bare repositiry (contains no git working tree). */
+int got_repo_is_bare(struct got_repository *);
+
+/* Attempt to map an arbitrary path to a path within the repository. */
+const struct got_error *got_repo_map_path(char **, struct got_repository *,
+    const char *);
blob - af1767503396d95abc5ad97aeaf9ecc73226817f
blob + 1d1d15583fd5c7e74bb6d530ad71a9b5702b0127
--- lib/got_lib_path.h
+++ lib/got_lib_path.h
@@ -40,3 +40,11 @@ char *got_path_normalize(const char *);
  * Relative paths are copied from input to buf as-is.
  */
 const struct got_error *got_canonpath(const char *, char *, size_t);
+
+/*
+ * Get child part of two absolute paths. The second path must equal the first
+ * path up to some path component, and must be longer than the first path.
+ * The result is allocated with malloc(3).
+ */
+const struct got_error *got_path_skip_common_ancestor(char **, const char *,
+    const char *);
blob - d14f81c3e51d80411824abe499bce05da745ce55
blob + e874cb7cc2f3f27a2112d82cb89f69b26cfecb40
--- lib/path.c
+++ lib/path.c
@@ -105,3 +105,31 @@ got_canonpath(const char *input, char *buf, size_t buf
 	} else
 		return got_error(GOT_ERR_NO_SPACE);
 }
+
+const struct got_error *
+got_path_skip_common_ancestor(char **child, const char *parent_abspath,
+    const char *abspath)
+{
+	const struct got_error *err = NULL;
+	size_t len_parent, len, bufsize;
+
+	len_parent = strlen(parent_abspath);
+	len = strlen(abspath);
+	if (len_parent >= len)
+		return got_error(GOT_ERR_BAD_PATH);
+	if (strncmp(parent_abspath, abspath, len_parent) != 0)
+		return got_error(GOT_ERR_BAD_PATH);
+	if (abspath[len_parent] != '/')
+		return got_error(GOT_ERR_BAD_PATH);
+	bufsize = len - len_parent + 1;
+	*child = malloc(bufsize);
+	if (*child == NULL)
+		return got_error_from_errno();
+	if (strlcpy(*child, abspath + len_parent, bufsize) >= bufsize) {
+		err = got_error_from_errno();
+		free(*child);
+		*child = NULL;
+		return err;
+	}
+	return NULL;
+}
blob - ed9663ebba15d6ad33dcc70aa70b8c8af92d0086
blob + 59b728c96939fc5eb660e65375c48497faa5fb0d
--- lib/repository.c
+++ lib/repository.c
@@ -67,6 +67,12 @@ char *
 got_repo_get_path_git_dir(struct got_repository *repo)
 {
 	return strdup(repo->path_git_dir);
+}
+
+int
+got_repo_is_bare(struct got_repository *repo)
+{
+	return (strcmp(repo->path, repo->path_git_dir) == 0);
 }
 
 static char *
@@ -550,3 +556,138 @@ got_repo_close(struct got_repository *repo)
 		got_object_idcache_free(repo->commitcache.idcache);
 	free(repo);
 }
+
+const struct got_error *
+got_repo_map_path(char **in_repo_path, struct got_repository *repo,
+    const char *input_path)
+{
+	const struct got_error *err = NULL;
+	char *repo_abspath = NULL, *cwd = NULL;
+	struct stat sb;
+	size_t repolen, cwdlen, len;
+	char *canonpath, *path;
+
+	*in_repo_path = NULL;
+
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL)
+		return got_error_from_errno();
+
+	canonpath = strdup(input_path);
+	if (canonpath == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	err = got_canonpath(input_path, canonpath, strlen(canonpath) + 1);
+	if (err)
+		goto done;
+
+	repo_abspath = got_repo_get_path(repo);
+	if (repo_abspath == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	/* TODO: Call "get in-repository path of work-tree node" API. */
+
+	if (lstat(canonpath, &sb) != 0) {
+		if (errno != ENOENT) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		/*
+		 * Path is not on disk.
+		 * Assume it is already relative to repository root.
+		 */
+		path = strdup(canonpath);
+	} else {
+		int is_repo_child = 0, is_cwd_child = 0;
+
+		path = realpath(canonpath, NULL);
+		if (path == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+
+		repolen = strlen(repo_abspath);
+		cwdlen = strlen(cwd);
+		len = strlen(path);
+
+		if (len > repolen && strncmp(path, repo_abspath, repolen) == 0)
+			is_repo_child = 1;
+		if (len > cwdlen && strncmp(path, cwd, cwdlen) == 0)
+			is_cwd_child = 1;
+
+		if (strcmp(path, repo_abspath) == 0) {
+			free(path);
+			path = strdup("");
+			if (path == NULL) {
+				err = got_error_from_errno();
+				goto done;
+			}
+		} else if (is_repo_child && is_cwd_child) {
+			char *child;
+			/* TODO: Is path inside a got worktree? */
+			/* Strip common prefix with repository path. */
+			err = got_path_skip_common_ancestor(&child,
+			    repo_abspath, path);
+			if (err)
+				goto done;
+			free(path);
+			path = child;
+		} else if (is_repo_child) {
+			/* Matched an on-disk path inside repository. */
+			if (got_repo_is_bare(repo)) {
+				/*
+				 * Matched an on-disk path inside repository
+				 * database. Treat as repository-relative.
+				 */
+			} else {
+				char *child;
+				/* Strip common prefix with repository path. */
+				err = got_path_skip_common_ancestor(&child,
+				    repo_abspath, path);
+				if (err)
+					goto done;
+				free(path);
+				path = child;
+			}
+		} else if (is_cwd_child) {
+			char *child;
+			/* TODO: Is path inside a got worktree? */
+			/* Strip common prefix with cwd. */
+			err = got_path_skip_common_ancestor(&child, cwd,
+			    path);
+			if (err)
+				goto done;
+			free(path);
+			path = child;
+		} else {
+			/*
+			 * Matched unrelated on-disk path.
+			 * Treat it as repository-relative.
+			 */
+		}
+	}
+
+	/* Make in-repository path absolute */
+	if (path[0] != '/') {
+		char *abspath;
+		if (asprintf(&abspath, "/%s", path) == -1) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		free(path);
+		path = abspath;
+	}
+
+done:
+	free(repo_abspath);
+	free(cwd);
+	free(canonpath);
+	if (err)
+		free(path);
+	else
+		*in_repo_path = path;
+	return err;
+}