commit ecb28ae0fb5fc4ab8a9c79cb6607c6fe2db50a90 from: Stefan Sperling date: Mon Jul 16 15:09:17 2018 UTC allow filtering history by path in tog log commit - 55ad411b9660776f942343a075eed49c30c951b5 commit + ecb28ae0fb5fc4ab8a9c79cb6607c6fe2db50a90 blob - c42a383ccd6edee22170796bdb11a16ac15d5a98 blob + a5af6acedcd7c31ac6cf0617ce8de7ce91accbc0 --- tog/tog.1 +++ tog/tog.1 @@ -42,11 +42,11 @@ The commands for .Nm are as follows: .Bl -tag -width blame -.It Cm log [ Fl c Ar commit ] [ Ar repository-path ] +.It Cm log [ Fl c Ar commit ] [ 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 This command is also executed if no explicit command is specified. .Pp @@ -81,6 +81,10 @@ Start traversing history at the specified .Ar commit . The expected argument is the name of a branch or a SHA1 hash which corresponds to a commit object. +.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 - a5ab06009dd0282c73b975355f337b389374f819 blob + 492ec5555d6c92bb2fdc90b5534f5d72884d3eb0 --- tog/tog.c +++ tog/tog.c @@ -91,7 +91,7 @@ static const struct got_error * show_diff_view(struct got_object *, struct got_object *, struct got_repository *); static const struct got_error * -show_log_view(struct got_object_id *, struct got_repository *); +show_log_view(struct got_object_id *, struct got_repository *, const char *); static const struct got_error * show_blame_view(const char *, struct got_object_id *, struct got_repository *); static const struct got_error * @@ -102,7 +102,7 @@ __dead static void usage_log(void) { endwin(); - fprintf(stderr, "usage: %s log [-c commit] [repository-path]\n", + fprintf(stderr, "usage: %s log [-c commit] [-r repository-path] [path]\n", getprogname()); exit(1); } @@ -294,7 +294,11 @@ struct commit_queue_entry { struct got_object_id *id; struct got_commit_object *commit; }; -TAILQ_HEAD(commit_queue, commit_queue_entry); +TAILQ_HEAD(commit_queue_head, commit_queue_entry); +struct commit_queue { + int ncommits; + struct commit_queue_head head; +}; static struct commit_queue_entry * alloc_commit_queue_entry(struct got_commit_object *commit, @@ -316,9 +320,10 @@ pop_commit(struct commit_queue *commits) { struct commit_queue_entry *entry; - entry = TAILQ_FIRST(commits); - TAILQ_REMOVE(commits, entry, entry); + entry = TAILQ_FIRST(&commits->head); + TAILQ_REMOVE(&commits->head, entry, entry); got_object_commit_close(entry->commit); + commits->ncommits--; /* Don't free entry->id! It is owned by the commit graph. */ free(entry); } @@ -326,23 +331,25 @@ pop_commit(struct commit_queue *commits) static void free_commits(struct commit_queue *commits) { - while (!TAILQ_EMPTY(commits)) + while (!TAILQ_EMPTY(&commits->head)) pop_commit(commits); } static const struct got_error * queue_commits(struct got_commit_graph *graph, struct commit_queue *commits, - struct got_object_id *start_id, struct got_repository *repo) + struct got_object_id *start_id, int minqueue, int init, + struct got_repository *repo, const char *path) { const struct got_error *err = NULL; struct got_object_id *id; struct commit_queue_entry *entry; + int nfetched, nqueued = 0, found_obj = 0; err = got_commit_graph_iter_start(graph, start_id); if (err) return err; - entry = TAILQ_LAST(commits, commit_queue); + entry = TAILQ_LAST(&commits->head, commit_queue_head); if (entry && got_object_id_cmp(entry->id, start_id) == 0) { int nfetched; @@ -361,22 +368,74 @@ queue_commits(struct got_commit_graph *graph, struct c err = got_commit_graph_iter_next(&id, graph); if (err) { - if (err->code == GOT_ERR_ITER_NEED_MORE) + if (err->code != GOT_ERR_ITER_NEED_MORE) + break; + if (nqueued >= minqueue) { err = NULL; - break; + break; + } + err = got_commit_graph_fetch_commits(&nfetched, + graph, 1, repo); + if (err) + return err; + continue; } + if (id == NULL) + break; err = got_object_open_as_commit(&commit, repo, id); if (err) break; + if (path) { + struct got_object *obj; + struct got_object_qid *pid; + int changed = 0; + + err = got_object_open_by_path(&obj, repo, id, path); + if (err) { + if (err->code == GOT_ERR_NO_OBJ && + (found_obj || !init)) { + /* History stops here. */ + err = got_error(GOT_ERR_ITER_COMPLETED); + } + 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)) != 0); + got_object_close(pobj); + } + } + if (!changed) { + got_object_commit_close(commit); + continue; + } + } + entry = alloc_commit_queue_entry(commit, id); if (entry == NULL) { err = got_error_from_errno(); break; } - - TAILQ_INSERT_TAIL(commits, entry, entry); + TAILQ_INSERT_TAIL(&commits->head, entry, entry); + nqueued++; + commits->ncommits++; } return err; @@ -385,29 +444,16 @@ queue_commits(struct got_commit_graph *graph, struct c static const struct got_error * fetch_next_commit(struct commit_queue_entry **pentry, struct commit_queue_entry *entry, struct commit_queue *commits, - struct got_commit_graph *graph, struct got_repository *repo) + struct got_commit_graph *graph, struct got_repository *repo, + const char *path) { const struct got_error *err = NULL; - struct got_object_qid *qid; *pentry = NULL; - /* Populate commit graph with entry's parent commits. */ - SIMPLEQ_FOREACH(qid, &entry->commit->parent_ids, entry) { - int nfetched; - err = got_commit_graph_fetch_commits_up_to(&nfetched, - graph, qid->id, repo); - if (err) - return err; - } - - /* Append outstanding commits to queue in graph sort order. */ - err = queue_commits(graph, commits, entry->id, repo); - if (err) { - if (err->code == GOT_ERR_ITER_COMPLETED) - err = NULL; + err = queue_commits(graph, commits, entry->id, 1, 0, repo, path); + if (err) return err; - } /* Next entry to display should now be available. */ *pentry = TAILQ_NEXT(entry, entry); @@ -440,14 +486,15 @@ get_head_commit_id(struct got_object_id **head_id, str } static const struct got_error * -draw_commits(struct commit_queue_entry **last, struct commit_queue_entry **selected, - struct commit_queue_entry *first, int selected_idx, int limit) +draw_commits(struct commit_queue_entry **last, + struct commit_queue_entry **selected, struct commit_queue_entry *first, + int selected_idx, int limit, const char *path) { const struct got_error *err = NULL; struct commit_queue_entry *entry; - int ncommits; + int ncommits, width; char *id_str, *header; - size_t header_len; + wchar_t *wline; entry = first; *selected = NULL; @@ -466,32 +513,39 @@ draw_commits(struct commit_queue_entry **last, struct if (err) return err; - if (asprintf(&header, "commit: %s", id_str) == -1) { + if (path) { + if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) { + err = got_error_from_errno(); + free(id_str); + return err; + } + } else if (asprintf(&header, "commit: %s", id_str) == -1) { err = got_error_from_errno(); free(id_str); return err; } + free(id_str); + err = format_line(&wline, &width, header, COLS); + if (err) { + free(header); + return err; + } + free(header); werase(tog_log_view.window); - header_len = strlen(header); - if (header_len > COLS) { - id_str[COLS + 1] = '\0'; - header_len = COLS; - } - wprintw(tog_log_view.window, header); - while (header_len < COLS) { - waddch(tog_log_view.window, ' '); - header_len++; - } - free(id_str); - free(header); + waddwstr(tog_log_view.window, wline); + if (width < COLS) + waddch(tog_log_view.window, '\n'); + free(wline); + if (limit <= 1) + return NULL; entry = first; *last = first; ncommits = 0; while (entry) { - if (ncommits == limit - 1) + if (ncommits >= limit - 1) break; if (ncommits == selected_idx) { wstandout(tog_log_view.window); @@ -520,13 +574,13 @@ scroll_up(struct commit_queue_entry **first_displayed_ struct commit_queue_entry *entry; int nscrolled = 0; - entry = TAILQ_FIRST(commits); + entry = TAILQ_FIRST(&commits->head); if (*first_displayed_entry == entry) return; entry = *first_displayed_entry; while (entry && nscrolled < maxscroll) { - entry = TAILQ_PREV(entry, commit_queue, entry); + entry = TAILQ_PREV(entry, commit_queue_head, entry); if (entry) { *first_displayed_entry = entry; nscrolled++; @@ -538,7 +592,7 @@ static const struct got_error * scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll, struct commit_queue_entry *last_displayed_entry, struct commit_queue *commits, struct got_commit_graph *graph, - struct got_repository *repo) + struct got_repository *repo, const char *path) { const struct got_error *err = NULL; struct commit_queue_entry *pentry; @@ -548,7 +602,7 @@ scroll_down(struct commit_queue_entry **first_displaye pentry = TAILQ_NEXT(last_displayed_entry, entry); if (pentry == NULL) { err = fetch_next_commit(&pentry, last_displayed_entry, - commits, graph, repo); + commits, graph, repo, path); if (err || pentry == NULL) break; } @@ -563,19 +617,6 @@ scroll_down(struct commit_queue_entry **first_displaye return err; } -static int -num_parents(struct commit_queue_entry *entry) -{ - int nparents = 0; - - while (entry) { - entry = TAILQ_NEXT(entry, entry); - nparents++; - } - - return nparents; -} - static const struct got_error * show_commit(struct commit_queue_entry *entry, struct got_repository *repo) { @@ -619,18 +660,24 @@ browse_commit(struct commit_queue_entry *entry, struct } static const struct got_error * -show_log_view(struct got_object_id *start_id, struct got_repository *repo) +show_log_view(struct got_object_id *start_id, struct got_repository *repo, + const char *path) { const struct got_error *err = NULL; struct got_object_id *head_id = NULL; - int ch, done = 0, selected = 0, nparents, nfetched; - struct got_commit_graph *graph; + int ch, done = 0, selected = 0, nfetched; + struct got_commit_graph *graph = NULL; struct commit_queue commits; - struct commit_queue_entry *entry = NULL; struct commit_queue_entry *first_displayed_entry = NULL; struct commit_queue_entry *last_displayed_entry = NULL; struct commit_queue_entry *selected_entry = NULL; + struct commit_queue_entry *entry; + char *in_repo_path = NULL; + err = got_repo_map_path(&in_repo_path, repo, path); + if (err != NULL) + goto done; + if (tog_log_view.window == NULL) { tog_log_view.window = newwin(0, 0, 0, 0); if (tog_log_view.window == NULL) @@ -648,7 +695,8 @@ show_log_view(struct got_object_id *start_id, struct g if (err) return err; - TAILQ_INIT(&commits); + TAILQ_INIT(&commits.head); + commits.ncommits = 0; err = got_commit_graph_open(&graph, head_id, 0, repo); if (err) @@ -659,9 +707,6 @@ show_log_view(struct got_object_id *start_id, struct g repo); if (err) goto done; - err = got_commit_graph_fetch_commits(&nfetched, graph, LINES, repo); - if (err) - goto done; /* * Open the initial batch of commits, sorted in commit graph order. @@ -669,15 +714,47 @@ show_log_view(struct got_object_id *start_id, struct g * in order to avoid having to re-fetch commits from disk while * updating the display. */ - err = queue_commits(graph, &commits, head_id, repo); + err = queue_commits(graph, &commits, head_id, LINES, 1, repo, + in_repo_path); if (err && err->code != GOT_ERR_ITER_COMPLETED) goto done; - /* Find entry corresponding to the first commit to display. */ - TAILQ_FOREACH(entry, &commits, entry) { - if (got_object_id_cmp(entry->id, start_id) == 0) { - first_displayed_entry = entry; - break; + /* + * Find entry corresponding to the first commit to display. + * if both a path and start commit was specified, the first commit + * shown should be a commit <= start_commit which modified the path. + */ + if (in_repo_path) { + struct got_object_id *id; + + err = got_commit_graph_iter_start(graph, start_id); + if (err) + return err; + do { + err = got_commit_graph_iter_next(&id, graph); + if (err) + goto done; + if (id == NULL) { + err = got_error(GOT_ERR_NO_OBJ); + goto done; + } + /* + * The graph contains all commits. The commit queue + * contains a subset of commits filtered by path. + */ + TAILQ_FOREACH(entry, &commits.head, entry) { + if (got_object_id_cmp(entry->id, id) == 0) { + first_displayed_entry = entry; + break; + } + } + } while (first_displayed_entry == NULL); + } else { + TAILQ_FOREACH(entry, &commits.head, entry) { + if (got_object_id_cmp(entry->id, start_id) == 0) { + first_displayed_entry = entry; + break; + } } } if (first_displayed_entry == NULL) { @@ -688,7 +765,7 @@ show_log_view(struct got_object_id *start_id, struct g selected_entry = first_displayed_entry; while (!done) { err = draw_commits(&last_displayed_entry, &selected_entry, - first_displayed_entry, selected, LINES); + first_displayed_entry, selected, LINES, in_repo_path); if (err) goto done; @@ -714,7 +791,7 @@ show_log_view(struct got_object_id *start_id, struct g scroll_up(&first_displayed_entry, 1, &commits); break; case KEY_PPAGE: - if (TAILQ_FIRST(&commits) == + if (TAILQ_FIRST(&commits.head) == first_displayed_entry) { selected = 0; break; @@ -724,32 +801,38 @@ show_log_view(struct got_object_id *start_id, struct g break; case 'j': case KEY_DOWN: - nparents = num_parents(first_displayed_entry); - if (selected < LINES - 2 && - selected < nparents - 1) { + if (selected < MIN(LINES - 2, + commits.ncommits - 1)) { selected++; break; } err = scroll_down(&first_displayed_entry, 1, last_displayed_entry, &commits, graph, - repo); - if (err) - goto done; + repo, in_repo_path); + if (err) { + if (err->code != GOT_ERR_ITER_COMPLETED) + goto done; + err = NULL; + } break; - case KEY_NPAGE: + case KEY_NPAGE: { + struct commit_queue_entry *first = first_displayed_entry; err = scroll_down(&first_displayed_entry, LINES, last_displayed_entry, &commits, graph, - repo); - if (err) - goto done; - if (last_displayed_entry->commit->nparents > 0) - break; - /* can't scroll any further; move cursor down */ - nparents = num_parents(first_displayed_entry); - if (selected < LINES - 2 || - selected < nparents - 1) - selected = MIN(LINES - 2, nparents - 1); + repo, in_repo_path); + if (err) { + if (err->code != GOT_ERR_ITER_COMPLETED) + goto done; + /* can't scroll any further; move cursor down */ + if (first == first_displayed_entry && selected < + MIN(LINES - 2, commits.ncommits - 1)) { + selected = MIN(LINES - 2, + commits.ncommits - 1); + } + err = NULL; + } break; + } case KEY_RESIZE: if (selected > LINES - 1) selected = LINES - 2; @@ -776,6 +859,7 @@ done: if (graph) got_commit_graph_close(graph); free_commits(&commits); + free(in_repo_path); return err; } @@ -783,9 +867,9 @@ 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 *start_id = NULL; - char *repo_path = NULL; + char *path = NULL, *repo_path = NULL, *cwd = NULL; char *start_commit = NULL; int ch; @@ -794,11 +878,16 @@ cmd_log(int argc, char *argv[]) err(1, "pledge"); #endif - while ((ch = getopt(argc, argv, "c:")) != -1) { + while ((ch = getopt(argc, argv, "c:r:")) != -1) { switch (ch) { case 'c': start_commit = optarg; break; + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) + err(1, "-r option"); + break; default: usage(); /* NOTREACHED */ @@ -808,26 +897,36 @@ 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) { error = get_head_commit_id(&start_id, repo); if (error != NULL) - return error; + goto done; } else { struct got_object *obj; error = got_object_open_by_id_str(&obj, repo, start_commit); @@ -835,13 +934,20 @@ cmd_log(int argc, char *argv[]) start_id = got_object_get_id(obj); if (start_id == NULL) error = got_error_from_errno(); + goto done; } } if (error != NULL) - return error; - error = show_log_view(start_id, repo); + goto done; + + error = show_log_view(start_id, repo, path); +done: + free(repo_path); + free(cwd); + free(path); free(start_id); - got_repo_close(repo); + if (repo) + got_repo_close(repo); return error; }