commit 349dfd1ee9fe854ee194e3c9c46c494e932a7d00 from: Mark Jamsek via: Thomas Adam date: Sun Jul 23 20:19:54 2023 UTC tog: show work tree base commit marker in log view If tog is invoked in a work tree, prefix the base commit log message summary line with a '*' if the work tree is up-to-date, and with a '~' if the base commit is not up-to-date with respect to the branch tip or it contains mixed commits. While here, plug a couple worktree leaks in cmd_ref() and cmd_tree(). ok stsp@ commit - f58fd00625a9f2165ab1608126c23ee8e40226fb commit + 349dfd1ee9fe854ee194e3c9c46c494e932a7d00 blob - 53e320eef8622a3384a31606d0cd2fe64d9ea60a blob + 07cb22fd98f7b10c110a8a5d0c0627770c5b7e76 --- include/got_worktree.h +++ include/got_worktree.h @@ -132,7 +132,19 @@ struct got_object_id *got_worktree_get_base_commit_id( */ const struct got_error *got_worktree_set_base_commit_id(struct got_worktree *, struct got_repository *, struct got_object_id *); + +/* + * Get the state of the work tree. If the work tree's global base commit is + * the tip of the work tree's current branch, and each file in the index is + * based on this same commit, the char out parameter will be + * GOT_WORKTREE_UPTODATE, else it will be GOT_WORKTREE_OUTOFDATE. + */ +const struct got_error *got_worktree_get_state(char *, + struct got_repository *, struct got_worktree *); +#define GOT_WORKTREE_UPTODATE '*' +#define GOT_WORKTREE_OUTOFDATE '~' + /* * Obtain a parsed representation of this worktree's got.conf file. * Return NULL if this configuration file could not be read. blob - 7cfa2e83d5c136bc98a0f67c5afab910de51a462 blob + d0d12d1eae6757e0ccdad130e915914bdfe94838 --- lib/worktree.c +++ lib/worktree.c @@ -3248,6 +3248,53 @@ check_mixed_commits(void *arg, struct got_fileindex_en return NULL; } +const struct got_error * +got_worktree_get_state(char *state, struct got_repository *repo, + struct got_worktree *worktree) +{ + const struct got_error *err; + struct got_object_id *base_id, *head_id = NULL; + struct got_reference *head_ref; + struct got_fileindex *fileindex = NULL; + char *fileindex_path = NULL; + + if (worktree == NULL) + return got_error(GOT_ERR_NOT_WORKTREE); + + err = got_ref_open(&head_ref, repo, + got_worktree_get_head_ref_name(worktree), 0); + if (err) + return err; + + err = got_ref_resolve(&head_id, repo, head_ref); + if (err) + goto done; + + *state = GOT_WORKTREE_OUTOFDATE; + base_id = got_worktree_get_base_commit_id(worktree); + + if (got_object_id_cmp(base_id, head_id) == 0) { + err = open_fileindex(&fileindex, &fileindex_path, worktree); + if (err) + goto done; + + err = got_fileindex_for_each_entry_safe(fileindex, + check_mixed_commits, worktree); + if (err == NULL) + *state = GOT_WORKTREE_UPTODATE; + else if (err->code == GOT_ERR_MIXED_COMMITS) + err = NULL; + } + +done: + free(head_id); + free(fileindex_path); + got_ref_close(head_ref); + if (fileindex != NULL) + got_fileindex_free(fileindex); + return err; +} + struct check_merge_conflicts_arg { struct got_worktree *worktree; struct got_fileindex *fileindex; blob - 2a28c65c9bf9e227cba943ce259438976ac67c02 blob + d10c8e4e269c29522a88fa8b7f94b5658cacd469 --- regress/tog/log.sh +++ regress/tog/log.sh @@ -448,7 +448,7 @@ test_log_commit_keywords() commit $(pop_id 5 $ids) [1/5] $ymd $(pop_id 5 $short_ids) flan_hacker commit 4 $ymd $(pop_id 4 $short_ids) flan_hacker commit 3 - $ymd $(pop_id 3 $short_ids) flan_hacker commit 2 + $ymd $(pop_id 3 $short_ids) flan_hacker ~commit 2 $ymd $(pop_id 2 $short_ids) flan_hacker commit 1 $ymd $(pop_id 1 $short_ids) flan_hacker adding the test tree @@ -498,7 +498,95 @@ test_log_commit_keywords() test_done "$testroot" "$ret" } + +test_log_show_base_commit() +{ + # make view wide enough to show full headline + test_init log_show_base_commit 80 3 + local repo="$testroot/repo" + local id=$(git_show_head "$repo") + + echo "alpha" >> "$repo/alpha" + git_commit "$repo" -m "base commit" + got checkout "$repo" "$testroot/wt" > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got checkout failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + + # move into the work tree (test is run in a subshell) + cd "$testroot/wt" + + local head_id=$(git_show_head "$repo") + local author_time=$(git_show_author_time "$repo") + local ymd=$(date -u -r "$author_time" +"%G-%m-%d") + + # check up-to-date base commit marker prefixes base commit log message + cat <<-EOF >$TOG_TEST_SCRIPT + SCREENDUMP + EOF + + cat <<-EOF >$testroot/view.expected + commit $head_id [1/2] master + $ymd flan_hacker *[master] base commit + $ymd flan_hacker adding the test tree + EOF + + tog log + cmp -s "$testroot/view.expected" "$testroot/view" + ret=$? + if [ $ret -ne 0 ]; then + diff -u "$testroot/view.expected" "$testroot/view" + test_done "$testroot" "$ret" + return 1 + fi + + # check marker is not drawn when not in a work tree + cat <<-EOF >$testroot/view.expected + commit $head_id [1/2] master + $ymd flan_hacker [master] base commit + $ymd flan_hacker adding the test tree + EOF + + tog log -r "$repo" + cmp -s "$testroot/view.expected" "$testroot/view" + ret=$? + if [ $ret -ne 0 ]; then + diff -u "$testroot/view.expected" "$testroot/view" + test_done "$testroot" "$ret" + return 1 + fi + + # check out-of-date marker is shown with a mixed-commit tree + echo "mixed" > alpha + got commit -m "new base mixed-commit" > /dev/null + head_id=$(git_show_head "$repo") + + cat <<-EOF >$TOG_TEST_SCRIPT + SCREENDUMP + EOF + + cat <<-EOF >$testroot/view.expected + commit $head_id [1/3] master + $ymd flan_hacker ~[master] new base mixed-commit + $ymd flan_hacker base commit + EOF + + tog log + cmp -s "$testroot/view.expected" "$testroot/view" + ret=$? + if [ $ret -ne 0 ]; then + diff -u "$testroot/view.expected" "$testroot/view" + test_done "$testroot" "$ret" + return 1 + fi + + test_done "$testroot" "$ret" +} + test_parseargs "$@" run_test test_log_hsplit_diff run_test test_log_vsplit_diff @@ -508,3 +596,4 @@ run_test test_log_hsplit_ref run_test test_log_hsplit_tree run_test test_log_logmsg_widechar run_test test_log_commit_keywords +run_test test_log_show_base_commit blob - 816fab05d74cd0ab51f96eb3cad17d26d95b56e3 blob + 6b4cb836095040c424af47d6a41111f9f98496b5 --- tog/tog.1 +++ tog/tog.1 @@ -146,6 +146,14 @@ If invoked in a work tree, the is interpreted relative to the current working directory, and the work tree's path prefix is implicitly prepended. Otherwise, the path is interpreted relative to the repository root. +.Pp +If invoked in a work tree, the log entry of the work tree's base commit will +be prefixed with one of the following annotations: +.Bl -column YXZ description +.It * Ta work tree's base commit and the base commit of all tracked files +matches the branch tip +.It \(a~ Ta work tree comprises mixed commits or its base commit is out-of-date +.El .Pp This command is also executed if no explicit command is specified. .Pp blob - 904d4efc56bf3771d09c35206b797c2c65e35e07 blob + 06509b3c452eaacfb6f796d10e2064b60fdfd3c0 --- tog/tog.c +++ tog/tog.c @@ -155,6 +155,11 @@ STAILQ_HEAD(tog_colors, tog_color); static struct got_reflist_head tog_refs = TAILQ_HEAD_INITIALIZER(tog_refs); static struct got_reflist_object_id_map *tog_refs_idmap; +static struct { + struct got_object_id *id; + int idx; + char marker; +} tog_base_commit; static enum got_diff_algorithm tog_diff_algo = GOT_DIFF_ALGORITHM_MYERS; static const struct got_error * @@ -2401,12 +2406,13 @@ format_author(wchar_t **wauthor, int *author_width, ch } static const struct got_error * -draw_commit(struct tog_view *view, struct got_commit_object *commit, - struct got_object_id *id, const size_t date_display_cols, - int author_display_cols) +draw_commit(struct tog_view *view, struct commit_queue_entry *entry, + const size_t date_display_cols, int author_display_cols) { struct tog_log_view_state *s = &view->state.log; const struct got_error *err = NULL; + struct got_commit_object *commit = entry->commit; + struct got_object_id *id = entry->id; char datebuf[12]; /* YYYY-MM-DD + SPACE + NUL */ char *refs_str = NULL; char *logmsg0 = NULL, *logmsg = NULL; @@ -2415,7 +2421,7 @@ draw_commit(struct tog_view *view, struct got_commit_o int author_width, refstr_width, logmsg_width; char *newline, *line = NULL; int col, limit, scrollx, logmsg_x; - const int avail = view->ncols; + const int avail = view->ncols, marker_column = author_display_cols + 1; struct tm tm; time_t committer_time; struct tog_color *tc; @@ -2480,7 +2486,12 @@ draw_commit(struct tog_view *view, struct got_commit_o waddwstr(view->window, wauthor); col += author_width; while (col < avail && author_width < author_display_cols + 2) { - waddch(view->window, ' '); + if (tog_base_commit.id != NULL && + author_width == marker_column && + entry->idx == tog_base_commit.idx) + waddch(view->window, tog_base_commit.marker); + else + waddch(view->window, ' '); col++; author_width++; } @@ -2695,6 +2706,10 @@ queue_commits(struct tog_log_thread_args *a) TAILQ_INSERT_TAIL(&a->real_commits->head, entry, entry); a->real_commits->ncommits++; + if (tog_base_commit.id != NULL && tog_base_commit.idx == -1 && + got_object_id_cmp(&id, tog_base_commit.id) == 0) + tog_base_commit.idx = entry->idx; + if (*a->limiting) { err = match_commit(&limit_match, &id, commit, a->limit_regex); @@ -2952,8 +2967,7 @@ draw_commits(struct tog_view *view) break; if (ncommits == s->selected) wstandout(view->window); - err = draw_commit(view, entry->commit, entry->id, - date_display_cols, author_cols); + err = draw_commit(view, entry, date_display_cols, author_cols); if (ncommits == s->selected) wstandend(view->window); if (err) @@ -4369,6 +4383,20 @@ init_curses(void) } static const struct got_error * +set_tog_base_commit(struct got_repository *repo, struct got_worktree *worktree) +{ + tog_base_commit.id = got_object_id_dup( + got_worktree_get_base_commit_id(worktree)); + if (tog_base_commit.id == NULL) + return got_error_from_errno( "got_object_id_dup"); + + tog_base_commit.idx = -1; + + return got_worktree_get_state(&tog_base_commit.marker, repo, + worktree); +} + +static const struct got_error * get_in_repo_path_from_argv0(char **in_repo_path, int argc, char *argv[], struct got_repository *repo, struct got_worktree *worktree) { @@ -4522,13 +4550,21 @@ cmd_log(int argc, char *argv[]) in_repo_path, log_branches); if (error) goto done; + if (worktree) { + error = set_tog_base_commit(repo, worktree); + if (error != NULL) + goto done; + /* Release work tree lock. */ got_worktree_close(worktree); worktree = NULL; } + error = view_loop(view); + done: + free(tog_base_commit.id); free(keyword_idstr); free(in_repo_path); free(repo_path); @@ -6050,8 +6086,17 @@ cmd_diff(int argc, char *argv[]) ignore_whitespace, force_text_diff, NULL, repo); if (error) goto done; + + if (worktree) { + error = set_tog_base_commit(repo, worktree); + if (error != NULL) + goto done; + } + error = view_loop(view); + done: + free(tog_base_commit.id); free(keyword_idstr1); free(keyword_idstr2); free(label1); @@ -7198,13 +7243,21 @@ cmd_blame(int argc, char *argv[]) view_close(view); goto done; } + if (worktree) { + error = set_tog_base_commit(repo, worktree); + if (error != NULL) + goto done; + /* Release work tree lock. */ got_worktree_close(worktree); worktree = NULL; } + error = view_loop(view); + done: + free(tog_base_commit.id); free(repo_path); free(in_repo_path); free(link_target); @@ -8187,12 +8240,19 @@ cmd_tree(int argc, char *argv[]) } if (worktree) { + error = set_tog_base_commit(repo, worktree); + if (error != NULL) + goto done; + /* Release work tree lock. */ got_worktree_close(worktree); worktree = NULL; } + error = view_loop(view); + done: + free(tog_base_commit.id); free(keyword_idstr); free(repo_path); free(cwd); @@ -8200,6 +8260,8 @@ done: free(label); if (ref) got_ref_close(ref); + if (worktree != NULL) + got_worktree_close(worktree); if (repo) { const struct got_error *close_err = got_repo_close(repo); if (error == NULL) @@ -9036,14 +9098,23 @@ cmd_ref(int argc, char *argv[]) goto done; if (worktree) { + error = set_tog_base_commit(repo, worktree); + if (error != NULL) + goto done; + /* Release work tree lock. */ got_worktree_close(worktree); worktree = NULL; } + error = view_loop(view); + done: + free(tog_base_commit.id); free(repo_path); free(cwd); + if (worktree != NULL) + got_worktree_close(worktree); if (repo) { const struct got_error *close_err = got_repo_close(repo); if (close_err)