commit 04ca23f4590f27f82af9ef2a837afe86683d7339 from: Stefan Sperling date: Mon Jul 16 11:05:23 2018 UTC allow filtering history by paths in 'got log' 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; +}