Commit Diff


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)