Git development
 help / color / mirror / Atom feed
* [PATCH 0/2] status: improve rebase todo list parsing
@ 2026-04-20 15:04 Phillip Wood
  2026-04-20 15:04 ` [PATCH 1/2] sequencer: factor out parsing of todo commands Phillip Wood
                   ` (2 more replies)
  0 siblings, 3 replies; 14+ messages in thread
From: Phillip Wood @ 2026-04-20 15:04 UTC (permalink / raw)
  To: git; +Cc: Phillip Wood

When there is rebase in progress "git status" displays the last couple
of completed and the next couple of pending commands from the todo
list. When it does this is tries to abbreviate the object ids of
the commits to be picked. Unfortunately it does not abbreviate the
object ids when the line starts with "fixup -C" or "merge -C". It
also mistakenly replaces the refname in "reset main" and "update-ref
refs/heads/main" with the object id that the ref points to.

This series fixes that. The first patch factors out the sequencer
code that parses the command names in the todo list. The second patch
uses that function in "git status" to parse the command names so that
it knows whether the line may contain "-C" and whether there is an
object id that should be abbreviated.

Base-Commit: 8c9303b1ffae5b745d1b0a1f98330cf7944d8db0
Published-As: https://github.com/phillipwood/git/releases/tag/pw%2Fimprove-status-todo-list-parsing%2Fv1
View-Changes-At: https://github.com/phillipwood/git/compare/8c9303b1f...d20dc1f65
Fetch-It-Via: git fetch https://github.com/phillipwood/git pw/improve-status-todo-list-parsing/v1


Phillip Wood (2):
  sequencer: factor out parsing of todo commands
  status: improve rebase todo list parsing

 sequencer.c            |  45 ++++++++++-----
 sequencer.h            |   1 +
 t/t7512-status-help.sh |  74 ++++++++++++++++---------
 wt-status.c            | 121 ++++++++++++++++++++++++++++++++---------
 4 files changed, 174 insertions(+), 67 deletions(-)

-- 
2.54.0.rc1.174.gd833f386ac5.dirty


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 1/2] sequencer: factor out parsing of todo commands
  2026-04-20 15:04 [PATCH 0/2] status: improve rebase todo list parsing Phillip Wood
@ 2026-04-20 15:04 ` Phillip Wood
  2026-04-22  0:32   ` Elijah Newren
  2026-04-20 15:04 ` [PATCH 2/2] status: improve rebase todo list parsing Phillip Wood
  2026-05-01 15:16 ` [PATCH v2 0/2] " Phillip Wood
  2 siblings, 1 reply; 14+ messages in thread
From: Phillip Wood @ 2026-04-20 15:04 UTC (permalink / raw)
  To: git; +Cc: Phillip Wood

From: Phillip Wood <phillip.wood@dunelm.org.uk>

Move the code that parses todo commands into a separate function so that
it can be shared with "git status" in the next commit.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
 sequencer.c | 45 ++++++++++++++++++++++++++++++---------------
 sequencer.h |  1 +
 2 files changed, 31 insertions(+), 15 deletions(-)

diff --git a/sequencer.c b/sequencer.c
index b7d8dca47f..b8e860434a 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2625,6 +2625,27 @@ static int is_command(enum todo_command command, const char **bol)
 		return 1;
 	}
 	return 0;
+}
+
+bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd)
+{
+	const char *s = *p;
+
+	for (int i = 0; i < TODO_COMMENT; i++)
+		if (is_command(i, p)) {
+			*cmd = i;
+			return true;
+		}
+
+	if (starts_with(s, comment_line_str)) {
+		*cmd = TODO_COMMENT;
+		return true;
+	} else if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n') || !s[0]) {
+		*cmd = TODO_COMMENT;
+		return true;
+	}
+
+	return false;
 }
 
 static int check_label_or_ref_arg(enum todo_command command, const char *arg)
@@ -2716,29 +2737,23 @@ static int parse_insn_line(struct repository *r, struct replay_opts *opts,
 {
 	struct object_id commit_oid;
 	char *end_of_object_name;
-	int i, saved, status, padding;
+	int saved, status, padding;
 
 	item->flags = 0;
 
 	/* left-trim */
 	bol += strspn(bol, " \t");
 
-	if (bol == eol || *bol == '\r' || starts_with_mem(bol, eol - bol, comment_line_str)) {
-		item->command = TODO_COMMENT;
-		item->commit = NULL;
-		item->arg_offset = bol - buf;
-		item->arg_len = eol - bol;
-		return 0;
-	}
-
-	for (i = 0; i < TODO_COMMENT; i++)
-		if (is_command(i, &bol)) {
-			item->command = i;
-			break;
-		}
-	if (i >= TODO_COMMENT)
+	if (!sequencer_parse_todo_command(&bol, &item->command))
 		return error(_("invalid command '%.*s'"),
 			     (int)strcspn(bol, " \t\r\n"), bol);
+
+	if (item->command == TODO_COMMENT) {
+		item->commit = NULL;
+		item->arg_offset = bol - buf;
+		item->arg_len = eol - bol;
+		return 0;
+	}
 
 	/* Eat up extra spaces/ tabs before object name */
 	padding = strspn(bol, " \t");
diff --git a/sequencer.h b/sequencer.h
index a6fa670c7c..20f6fac48a 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -262,6 +262,7 @@ int read_author_script(const char *path, char **name, char **email, char **date,
 int write_basic_state(struct replay_opts *opts, const char *head_name,
 		      struct commit *onto, const struct object_id *orig_head);
 void sequencer_post_commit_cleanup(struct repository *r, int verbose);
+bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd);
 int sequencer_get_last_command(struct repository* r,
 			       enum replay_action *action);
 int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);
-- 
2.54.0.rc1.174.gd833f386ac5.dirty


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 2/2] status: improve rebase todo list parsing
  2026-04-20 15:04 [PATCH 0/2] status: improve rebase todo list parsing Phillip Wood
  2026-04-20 15:04 ` [PATCH 1/2] sequencer: factor out parsing of todo commands Phillip Wood
@ 2026-04-20 15:04 ` Phillip Wood
  2026-04-20 16:38   ` Tian Yuchen
  2026-04-22  0:32   ` Elijah Newren
  2026-05-01 15:16 ` [PATCH v2 0/2] " Phillip Wood
  2 siblings, 2 replies; 14+ messages in thread
From: Phillip Wood @ 2026-04-20 15:04 UTC (permalink / raw)
  To: git; +Cc: Phillip Wood

From: Phillip Wood <phillip.wood@dunelm.org.uk>

When there is rebase in progress "git status" displays the last couple
of completed and the next couple of pending commands from the todo
list. When it does this is tries to abbreviate the object ids of
the commits to be picked. Unfortunately it does not abbreviate the
object ids when the line starts with "fixup -C" or "merge -C". It
also mistakenly replaces the refname in "reset main" and "update-ref
refs/heads/main" with the object id that the ref points to. Use
the function added in the last commit to parse the command name and
only try to abbreviate the argument for commands that take an object
id. When trying to abbreviate an object id, only replace the object
name if it starts with the abbreviated object id so that labels or
branch names that contain only hex digits are left unchanged.

Comments are now processed after stripping any leading
whitespace from the line. This matches what the sequencer does in
parse_insn_line(). The existing test cases are updated to test a
wider variety of commands. Only the pending commands in the tests
are changed to avoid removing existing coverage.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
 t/t7512-status-help.sh |  74 ++++++++++++++++---------
 wt-status.c            | 121 ++++++++++++++++++++++++++++++++---------
 2 files changed, 143 insertions(+), 52 deletions(-)

diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 08e82f7914..aca4b6d332 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -224,7 +224,7 @@ test_expect_success 'status when splitting a commit' '
 	COMMIT3=$(git rev-parse --short split_commit) &&
 	test_commit four_split main.txt four &&
 	COMMIT4=$(git rev-parse --short split_commit) &&
-	FAKE_LINES="1 edit 2 3" &&
+	FAKE_LINES="reword 1 edit 2 fixup_-C 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD~3) &&
@@ -233,10 +233,10 @@ test_expect_success 'status when splitting a commit' '
 	cat >expected <<EOF &&
 interactive rebase in progress; onto $ONTO
 Last commands done (2 commands done):
-   pick $COMMIT2 # two_split
+   reword $COMMIT2 # two_split
    edit $COMMIT3 # three_split
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_split
+   fixup -C $COMMIT4 # four_split
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
@@ -297,7 +297,7 @@ test_expect_success 'prepare for several edits' '
 
 
 test_expect_success 'status: (continue first edit) second edit' '
-	FAKE_LINES="edit 1 edit 2 3" &&
+	FAKE_LINES="edit 1 edit 2 drop 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
@@ -312,7 +312,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   drop $COMMIT4 # four_edits
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
@@ -327,7 +327,7 @@ EOF
 
 test_expect_success 'status: (continue first edit) second edit and split' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
+	FAKE_LINES="edit 1 edit 2 squash 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
@@ -343,7 +343,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   squash $COMMIT4 # four_edits
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
@@ -362,7 +362,7 @@ EOF
 
 test_expect_success 'status: (continue first edit) second edit and amend' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
+	FAKE_LINES="edit 1 edit 2 fixup 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
@@ -378,7 +378,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   fixup $COMMIT4 # four_edits
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
@@ -393,7 +393,7 @@ EOF
 
 test_expect_success 'status: (amend first edit) second edit' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
+	FAKE_LINES="edit 1 edit 2 fixup_-c 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
@@ -409,7 +409,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   fixup -c $COMMIT4 # four_edits
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
@@ -460,14 +460,20 @@ EOF
 
 test_expect_success 'status: (amend first edit) second edit and amend' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
-	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
 	COMMIT3=$(git rev-parse --short several_edits^) &&
 	COMMIT4=$(git rev-parse --short several_edits) &&
 	ONTO=$(git rev-parse --short HEAD~3) &&
-	git rebase -i HEAD~3 &&
+	cat >todo <<-EOF &&
+	edit several_edits^^ # two_edits
+	edit several_edits^ # three_edits
+	merge $(git rev-parse main) $(git rev-parse several_edits)
+	EOF
+	(
+		set_replace_editor todo &&
+		git rebase -i HEAD~3
+	) &&
 	git commit --amend -m "c" &&
 	git rebase --continue &&
 	git commit --amend -m "d" &&
@@ -477,7 +483,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   merge $(git rev-parse --short main) $COMMIT4
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
@@ -525,14 +531,21 @@ EOF
 
 test_expect_success 'status: (split first edit) second edit and split' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
-	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
 	COMMIT3=$(git rev-parse --short several_edits^) &&
 	COMMIT4=$(git rev-parse --short several_edits) &&
+	cat >todo <<-EOF &&
+	edit several_edits^^ # two_edits
+	edit several_edits^ # three_edits
+	reset $(git rev-parse main)
+	merge -C several_edits topic # title
+	EOF
 	ONTO=$(git rev-parse --short HEAD~3) &&
-	git rebase -i HEAD~3 &&
+	(
+		set_replace_editor todo &&
+		git rebase -i HEAD~3
+	) &&
 	git reset HEAD^ &&
 	git add main.txt &&
 	git commit --amend -m "f" &&
@@ -543,8 +556,9 @@ interactive rebase in progress; onto $ONTO
 Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
-Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+Next commands to do (2 remaining commands):
+   reset $(git rev-parse --short main)
+   merge -C $COMMIT4 topic # title
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
@@ -563,14 +577,21 @@ EOF
 
 test_expect_success 'status: (split first edit) second edit and amend' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
-	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
+	git branch cafe main &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
 	COMMIT3=$(git rev-parse --short several_edits^) &&
-	COMMIT4=$(git rev-parse --short several_edits) &&
+	cat >todo <<-EOF &&
+	edit several_edits^^ # two_edits
+	edit several_edits^ # three_edits
+	update-ref refs/heads/main
+	reset cafe
+	EOF
 	ONTO=$(git rev-parse --short HEAD~3) &&
-	git rebase -i HEAD~3 &&
+	(
+		set_replace_editor todo &&
+		git rebase -i HEAD~3
+	) &&
 	git reset HEAD^ &&
 	git add main.txt &&
 	git commit --amend -m "g" &&
@@ -581,8 +602,9 @@ interactive rebase in progress; onto $ONTO
 Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
-Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+Next commands to do (2 remaining commands):
+   update-ref refs/heads/main
+   reset cafe
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
diff --git a/wt-status.c b/wt-status.c
index 479ccc3304..ba93a18fc2 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1363,6 +1363,51 @@ static int split_commit_in_progress(struct wt_status *s)
 	free(rebase_orig_head);
 
 	return split_in_progress;
+}
+
+static void abbrev_oid_in_line(struct repository *r,
+			       struct strbuf *line, char **pp)
+{
+	char *p = *pp;
+	char *end_of_object_name, saved;
+	const char *abbrev;
+	struct object_id oid;
+	bool have_oid;
+
+	p += strspn(p, " \t");
+	end_of_object_name = p + strcspn(p, " \t");
+	/*
+	 * The for "merge" and "reset" the object name may be a label or
+	 * ref rather than a hex object id. Only abbreviate the object
+	 * name if it is a hex object id.
+	 */
+	for (const char *q = p; q < end_of_object_name; q++) {
+		if (!isxdigit(*q))
+			goto out;
+	}
+	saved = *end_of_object_name;
+	*end_of_object_name = '\0';
+	have_oid = !repo_get_oid(r, p, &oid);
+	*end_of_object_name = saved;
+	if (!have_oid)
+		goto out; /* object name was a label */
+	abbrev = repo_find_unique_abbrev(r, &oid, DEFAULT_ABBREV);
+	if (!starts_with(p, abbrev))
+		goto out; /* object name was a refname containing only xdigits */
+	p += strlen(abbrev);
+	strbuf_remove(line, p - line->buf, end_of_object_name - p);
+	end_of_object_name = p;
+out:
+	*pp = end_of_object_name;
+}
+
+static void skip_dash_c(char **pp) {
+	char *p = *pp;
+
+	p += strspn(p, " \t");
+	/* The (void) cast is required to silence -Wunused_value */
+	(void)(skip_prefix(p, "-C", &p) || skip_prefix(p, "-c", &p));
+	*pp = p;
 }
 
 /*
@@ -1371,29 +1416,57 @@ static int split_commit_in_progress(struct wt_status *s)
  * into
  * "pick d6a2f03 some message"
  *
- * The function assumes that the line does not contain useless spaces
- * before or after the command.
+ * Returns false on comment lines, true otherwise
  */
-static void abbrev_oid_in_line(struct repository *r, struct strbuf *line)
+static bool format_todo_line(struct repository *r, struct strbuf *line)
 {
-	struct string_list split = STRING_LIST_INIT_DUP;
-	struct object_id oid;
-
-	if (starts_with(line->buf, "exec ") ||
-	    starts_with(line->buf, "x ") ||
-	    starts_with(line->buf, "label ") ||
-	    starts_with(line->buf, "l "))
-		return;
-
-	if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
-	    !repo_get_oid(r, split.items[1].string, &oid)) {
-		strbuf_reset(line);
-		strbuf_addf(line, "%s ", split.items[0].string);
-		strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
-		for (size_t i = 2; i < split.nr; i++)
-			strbuf_addf(line, " %s", split.items[i].string);
+	enum todo_command cmd;
+	char *p = line->buf;
+
+	if (!sequencer_parse_todo_command((const char**)&p, &cmd))
+		return true; /* keep invalid lines */
+
+	switch (cmd) {
+	case TODO_COMMENT:
+		return false;
+
+	case TODO_MERGE:
+		skip_dash_c(&p);
+		while (true) {
+			p += strspn(p, " \t");
+			if (!p[0] || (p[0] == '#' && (!p[1] || isspace(p[1]))))
+				break;
+			abbrev_oid_in_line(r, line, &p);
+		}
+		break;
+
+	case TODO_FIXUP:
+		skip_dash_c(&p);
+		/* fallthrough */
+	case TODO_DROP:
+	case TODO_EDIT:
+	case TODO_PICK:
+	case TODO_RESET:
+	case TODO_REVERT:
+	case TODO_REWORD:
+	case TODO_SQUASH:
+		abbrev_oid_in_line(r, line, &p);
+		break;
+
+	/*
+	 * Avoid "default" and instead list all the other commands so
+	 * that -Wswitch warns if a new command is added without handling
+	 * it in this function.
+	 */
+	case TODO_BREAK:
+	case TODO_EXEC:
+	case TODO_LABEL:
+	case TODO_NOOP:
+	case TODO_UPDATE_REF:
+		break;
 	}
-	string_list_clear(&split, 0);
+
+	return true;
 }
 
 static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines)
@@ -1411,13 +1484,9 @@ static int read_rebase_todolist(struct repository *r, const char *fname, struct
 			  repo_git_path_replace(r, &buf, "%s", fname));
 	}
 	while (!strbuf_getline_lf(&buf, f)) {
-		if (starts_with(buf.buf, comment_line_str))
-			continue;
 		strbuf_trim(&buf);
-		if (!buf.len)
-			continue;
-		abbrev_oid_in_line(r, &buf);
-		string_list_append(lines, buf.buf);
+		if (format_todo_line(r, &buf))
+			string_list_append(lines, buf.buf);
 	}
 	fclose(f);
 
-- 
2.54.0.rc1.174.gd833f386ac5.dirty


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] status: improve rebase todo list parsing
  2026-04-20 15:04 ` [PATCH 2/2] status: improve rebase todo list parsing Phillip Wood
@ 2026-04-20 16:38   ` Tian Yuchen
  2026-04-21 16:03     ` Phillip Wood
  2026-04-22  0:32   ` Elijah Newren
  1 sibling, 1 reply; 14+ messages in thread
From: Tian Yuchen @ 2026-04-20 16:38 UTC (permalink / raw)
  To: Phillip Wood, git; +Cc: Phillip Wood

Hi Phillip,

On 4/20/26 23:04, Phillip Wood wrote:

> +	if (!starts_with(p, abbrev))
> +		goto out; /* object name was a refname containing only xdigits */
> +	p += strlen(abbrev);
> +	strbuf_remove(line, p - line->buf, end_of_object_name - p);
> +	end_of_object_name = p;


> -	if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
> -	    !repo_get_oid(r, split.items[1].string, &oid)) {
> -		strbuf_reset(line);
> -		strbuf_addf(line, "%s ", split.items[0].string);
> -		strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
> -		for (size_t i = 2; i < split.nr; i++)
> -			strbuf_addf(line, " %s", split.items[i].string);

I noticed that after this patch, refnames shorter than seven characters 
are no longer standardised to the standard seven-character length, 
because the 'start_with()' function always returns FALSE. The code jumps 
directly to 'out', without completing or cutting the refname.

I’m not sure if this was your intention, but I just want to point it out 
for your information.

(Also noted that there is a very rare scenario where the OID of a 
refname longer than 7 characters happens to begin with the refname 
itself; in this case, 'start_with' returns TRUE and the string is cut 
incorrectly. However, I think we can safely ignore this.)

Regards, Yuchen

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] status: improve rebase todo list parsing
  2026-04-20 16:38   ` Tian Yuchen
@ 2026-04-21 16:03     ` Phillip Wood
  0 siblings, 0 replies; 14+ messages in thread
From: Phillip Wood @ 2026-04-21 16:03 UTC (permalink / raw)
  To: Tian Yuchen, Phillip Wood, git

Hi Tian

On 20/04/2026 17:38, Tian Yuchen wrote:
> On 4/20/26 23:04, Phillip Wood wrote:
> 
>> +    if (!starts_with(p, abbrev))
>> +        goto out; /* object name was a refname containing only 
>> xdigits */
>> +    p += strlen(abbrev);
>> +    strbuf_remove(line, p - line->buf, end_of_object_name - p);
>> +    end_of_object_name = p;
> 
> 
>> -    if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
>> -        !repo_get_oid(r, split.items[1].string, &oid)) {
>> -        strbuf_reset(line);
>> -        strbuf_addf(line, "%s ", split.items[0].string);
>> -        strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
>> -        for (size_t i = 2; i < split.nr; i++)
>> -            strbuf_addf(line, " %s", split.items[i].string);
> 
> I noticed that after this patch, refnames shorter than seven characters 
> are no longer standardised to the standard seven-character length, 
> because the 'start_with()' function always returns FALSE. The code jumps 
> directly to 'out', without completing or cutting the refname.

Do you mean object id's rather than refnames? We do not want to alter 
refnames, but we do want to abbreviate object ids. You're right that if 
the todo list contains an object id that is shorter than the default 
abbreviation length it will not be expanded but "git rebase" rewrites 
the todo list to contain full length object ids after the user has 
edited it. If a user may edit the todo list directly without using "git 
rebase --edit-todo" and then run "git status" before "git rebase 
--continue" the todo list could contain shorter object ids but that 
seems pretty unlikely.
> 
> I’m not sure if this was your intention, but I just want to point it out 
> for your information.
> 
> (Also noted that there is a very rare scenario where the OID of a 
> refname longer than 7 characters happens to begin with the refname 
> itself; in this case, 'start_with' returns TRUE and the string is cut 
> incorrectly. However, I think we can safely ignore this.)

Yes, I think that's unlikely to happen in practice.

Thanks

Phillip
> Regards, Yuchen


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 1/2] sequencer: factor out parsing of todo commands
  2026-04-20 15:04 ` [PATCH 1/2] sequencer: factor out parsing of todo commands Phillip Wood
@ 2026-04-22  0:32   ` Elijah Newren
  0 siblings, 0 replies; 14+ messages in thread
From: Elijah Newren @ 2026-04-22  0:32 UTC (permalink / raw)
  To: Phillip Wood; +Cc: git, Phillip Wood

On Mon, Apr 20, 2026 at 8:41 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>
> Move the code that parses todo commands into a separate function so that
> it can be shared with "git status" in the next commit.
>
> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
> ---
>  sequencer.c | 45 ++++++++++++++++++++++++++++++---------------
>  sequencer.h |  1 +
>  2 files changed, 31 insertions(+), 15 deletions(-)
>
> diff --git a/sequencer.c b/sequencer.c
> index b7d8dca47f..b8e860434a 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -2625,6 +2625,27 @@ static int is_command(enum todo_command command, const char **bol)
>                 return 1;
>         }
>         return 0;
> +}
> +
> +bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd)
> +{
> +       const char *s = *p;
> +
> +       for (int i = 0; i < TODO_COMMENT; i++)
> +               if (is_command(i, p)) {
> +                       *cmd = i;
> +                       return true;
> +               }
> +
> +       if (starts_with(s, comment_line_str)) {
> +               *cmd = TODO_COMMENT;
> +               return true;
> +       } else if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n') || !s[0]) {
> +               *cmd = TODO_COMMENT;
> +               return true;
> +       }
> +
> +       return false;
>  }

Since *p will be advanced by this function, perhaps it's worth a quick
comment before the function explaining how it advances?

>
>  static int check_label_or_ref_arg(enum todo_command command, const char *arg)
> @@ -2716,29 +2737,23 @@ static int parse_insn_line(struct repository *r, struct replay_opts *opts,
>  {
>         struct object_id commit_oid;
>         char *end_of_object_name;
> -       int i, saved, status, padding;
> +       int saved, status, padding;
>
>         item->flags = 0;
>
>         /* left-trim */
>         bol += strspn(bol, " \t");
>
> -       if (bol == eol || *bol == '\r' || starts_with_mem(bol, eol - bol, comment_line_str)) {
> -               item->command = TODO_COMMENT;
> -               item->commit = NULL;
> -               item->arg_offset = bol - buf;
> -               item->arg_len = eol - bol;
> -               return 0;
> -       }
> -
> -       for (i = 0; i < TODO_COMMENT; i++)
> -               if (is_command(i, &bol)) {
> -                       item->command = i;
> -                       break;
> -               }
> -       if (i >= TODO_COMMENT)
> +       if (!sequencer_parse_todo_command(&bol, &item->command))
>                 return error(_("invalid command '%.*s'"),
>                              (int)strcspn(bol, " \t\r\n"), bol);
> +
> +       if (item->command == TODO_COMMENT) {
> +               item->commit = NULL;
> +               item->arg_offset = bol - buf;
> +               item->arg_len = eol - bol;
> +               return 0;
> +       }

The commit message says it's just moving code, but are there some
subtle changes?  It switches the order of parsing commands and
comments, which I think doesn't matter.  I think the previous code
would treat a bare carriage return not immediately followed by a
newline as the start of a comment, whereas the new code only handles
carriage return immediately followed by a newline as the start of a
comment.  I don't think that matters in practice, and was potentially
buggy before, but feels like the commit message isn't quite telling
the whole story.  Maybe it's worth calling out the minor but harmless
tweaks in the commit message?


>         /* Eat up extra spaces/ tabs before object name */
>         padding = strspn(bol, " \t");
> diff --git a/sequencer.h b/sequencer.h
> index a6fa670c7c..20f6fac48a 100644
> --- a/sequencer.h
> +++ b/sequencer.h
> @@ -262,6 +262,7 @@ int read_author_script(const char *path, char **name, char **email, char **date,
>  int write_basic_state(struct replay_opts *opts, const char *head_name,
>                       struct commit *onto, const struct object_id *orig_head);
>  void sequencer_post_commit_cleanup(struct repository *r, int verbose);
> +bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd);
>  int sequencer_get_last_command(struct repository* r,
>                                enum replay_action *action);
>  int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);
> --
> 2.54.0.rc1.174.gd833f386ac5.dirty

Otherwise, looks good.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] status: improve rebase todo list parsing
  2026-04-20 15:04 ` [PATCH 2/2] status: improve rebase todo list parsing Phillip Wood
  2026-04-20 16:38   ` Tian Yuchen
@ 2026-04-22  0:32   ` Elijah Newren
  2026-04-22 13:28     ` Patrick Steinhardt
  2026-04-22 14:15     ` Phillip Wood
  1 sibling, 2 replies; 14+ messages in thread
From: Elijah Newren @ 2026-04-22  0:32 UTC (permalink / raw)
  To: Phillip Wood; +Cc: git, Phillip Wood

On Mon, Apr 20, 2026 at 8:25 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>
> When there is rebase in progress "git status" displays the last couple
> of completed and the next couple of pending commands from the todo
> list. When it does this is tries to abbreviate the object ids of

is tries => it tries ?

[...]
> @@ -1363,6 +1363,51 @@ static int split_commit_in_progress(struct wt_status *s)
>         free(rebase_orig_head);
>
>         return split_in_progress;
> +}
> +
> +static void abbrev_oid_in_line(struct repository *r,
> +                              struct strbuf *line, char **pp)
> +{
> +       char *p = *pp;
> +       char *end_of_object_name, saved;
> +       const char *abbrev;
> +       struct object_id oid;
> +       bool have_oid;

I'll put "thinking out loud" text in square brackets below...

> +
> +       p += strspn(p, " \t");
> +       end_of_object_name = p + strcspn(p, " \t");

[Advances p after whitespace, marks the end of the object with the
next whitespace after that.]

> +       /*
> +        * The for "merge" and "reset" the object name may be a label or

The for => For ?

> +        * ref rather than a hex object id. Only abbreviate the object
> +        * name if it is a hex object id.
> +        */
> +       for (const char *q = p; q < end_of_object_name; q++) {
> +               if (!isxdigit(*q))
> +                       goto out;
> +       }



> +       saved = *end_of_object_name;
> +       *end_of_object_name = '\0';
> +       have_oid = !repo_get_oid(r, p, &oid);
> +       *end_of_object_name = saved;

[Tries to resolve the token, doing NUL-termination and restore dance.]

> +       if (!have_oid)
> +               goto out; /* object name was a label */


> +       abbrev = repo_find_unique_abbrev(r, &oid, DEFAULT_ABBREV);
> +       if (!starts_with(p, abbrev))
> +               goto out; /* object name was a refname containing only xdigits */

[Ensures what we have is an oid rather than a branch name that can be
resolved to an oid]

> +       p += strlen(abbrev);
> +       strbuf_remove(line, p - line->buf, end_of_object_name - p);
> +       end_of_object_name = p;

[Splice out a bunch of characters in the middle?]

> +out:
> +       *pp = end_of_object_name;
> +}

I had a hard time following the logic in the function and trying to
figure out what it was doing.  I went line by line but had no mental
model to follow.  When I got to the comment that is now above
format_todo_line(), I suddenly understood, but without it, all the
code was hard to follow.  Maybe a small comment at the beginning of
the function along the lines of

 /*
  * If the whitespace-delimited token starting at or just after *pp is a
  * full hex object id that resolves uniquely, rewrite it in place to
  * its default abbreviation, shrinking `line` accordingly. On return
  * *pp points one past the (possibly abbreviated) token. Leaves both
  * `line` and *pp-advanced-past-the-token unchanged in all other cases
  * (non-hex token, unresolvable, or a refname that happens to consist
  * only of hex digits).
  */

?  (Assuming I'm understanding correctly, of course.)

> +
> +static void skip_dash_c(char **pp) {

Move the brace to the next line?

> +       char *p = *pp;
> +
> +       p += strspn(p, " \t");
> +       /* The (void) cast is required to silence -Wunused_value */

-Wunused_value => -Wunused-value ?

> +       (void)(skip_prefix(p, "-C", &p) || skip_prefix(p, "-c", &p));
> +       *pp = p;
>  }
>
>  /*
> @@ -1371,29 +1416,57 @@ static int split_commit_in_progress(struct wt_status *s)
>   * into
>   * "pick d6a2f03 some message"
>   *
> - * The function assumes that the line does not contain useless spaces
> - * before or after the command.
> + * Returns false on comment lines, true otherwise
>   */
> -static void abbrev_oid_in_line(struct repository *r, struct strbuf *line)
> +static bool format_todo_line(struct repository *r, struct strbuf *line)
>  {
> -       struct string_list split = STRING_LIST_INIT_DUP;
> -       struct object_id oid;
> -
> -       if (starts_with(line->buf, "exec ") ||
> -           starts_with(line->buf, "x ") ||
> -           starts_with(line->buf, "label ") ||
> -           starts_with(line->buf, "l "))
> -               return;
> -
> -       if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
> -           !repo_get_oid(r, split.items[1].string, &oid)) {
> -               strbuf_reset(line);
> -               strbuf_addf(line, "%s ", split.items[0].string);
> -               strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
> -               for (size_t i = 2; i < split.nr; i++)
> -                       strbuf_addf(line, " %s", split.items[i].string);
> +       enum todo_command cmd;
> +       char *p = line->buf;
> +
> +       if (!sequencer_parse_todo_command((const char**)&p, &cmd))
> +               return true; /* keep invalid lines */
> +
> +       switch (cmd) {
> +       case TODO_COMMENT:
> +               return false;
> +
> +       case TODO_MERGE:
> +               skip_dash_c(&p);
> +               while (true) {
> +                       p += strspn(p, " \t");
> +                       if (!p[0] || (p[0] == '#' && (!p[1] || isspace(p[1]))))
> +                               break;
> +                       abbrev_oid_in_line(r, line, &p);
> +               }
> +               break;
> +
> +       case TODO_FIXUP:
> +               skip_dash_c(&p);
> +               /* fallthrough */
> +       case TODO_DROP:
> +       case TODO_EDIT:
> +       case TODO_PICK:
> +       case TODO_RESET:
> +       case TODO_REVERT:
> +       case TODO_REWORD:
> +       case TODO_SQUASH:
> +               abbrev_oid_in_line(r, line, &p);
> +               break;
> +
> +       /*
> +        * Avoid "default" and instead list all the other commands so
> +        * that -Wswitch warns if a new command is added without handling
> +        * it in this function.
> +        */

Nice. :-)

> +       case TODO_BREAK:
> +       case TODO_EXEC:
> +       case TODO_LABEL:
> +       case TODO_NOOP:
> +       case TODO_UPDATE_REF:
> +               break;
>         }
> -       string_list_clear(&split, 0);
> +
> +       return true;
>  }
>
>  static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines)
> @@ -1411,13 +1484,9 @@ static int read_rebase_todolist(struct repository *r, const char *fname, struct
>                           repo_git_path_replace(r, &buf, "%s", fname));
>         }
>         while (!strbuf_getline_lf(&buf, f)) {
> -               if (starts_with(buf.buf, comment_line_str))
> -                       continue;
>                 strbuf_trim(&buf);
> -               if (!buf.len)
> -                       continue;
> -               abbrev_oid_in_line(r, &buf);
> -               string_list_append(lines, buf.buf);
> +               if (format_todo_line(r, &buf))
> +                       string_list_append(lines, buf.buf);
>         }
>         fclose(f);
>
> --
> 2.54.0.rc1.174.gd833f386ac5.dirty

Other than the minor comments above, this looks like a nice cleanup.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] status: improve rebase todo list parsing
  2026-04-22  0:32   ` Elijah Newren
@ 2026-04-22 13:28     ` Patrick Steinhardt
  2026-04-22 14:14       ` Phillip Wood
  2026-04-22 14:15     ` Phillip Wood
  1 sibling, 1 reply; 14+ messages in thread
From: Patrick Steinhardt @ 2026-04-22 13:28 UTC (permalink / raw)
  To: Elijah Newren; +Cc: Phillip Wood, git, Phillip Wood

On Tue, Apr 21, 2026 at 05:32:21PM -0700, Elijah Newren wrote:
> On Mon, Apr 20, 2026 at 8:25 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> > +       /*
> > +        * Avoid "default" and instead list all the other commands so
> > +        * that -Wswitch warns if a new command is added without handling
> > +        * it in this function.
> > +        */
> 
> Nice. :-)

Do we actually use -Wswitch anywhere? A quick grep in our code base
didn't surface it, so I'm a bit sceptical that we would actually detect
any missing cases via CI.

Thanks!

Patrick

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] status: improve rebase todo list parsing
  2026-04-22 13:28     ` Patrick Steinhardt
@ 2026-04-22 14:14       ` Phillip Wood
  0 siblings, 0 replies; 14+ messages in thread
From: Phillip Wood @ 2026-04-22 14:14 UTC (permalink / raw)
  To: Patrick Steinhardt, Elijah Newren; +Cc: Phillip Wood, git

On 22/04/2026 14:28, Patrick Steinhardt wrote:
> On Tue, Apr 21, 2026 at 05:32:21PM -0700, Elijah Newren wrote:
>> On Mon, Apr 20, 2026 at 8:25 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>> +       /*
>>> +        * Avoid "default" and instead list all the other commands so
>>> +        * that -Wswitch warns if a new command is added without handling
>>> +        * it in this function.
>>> +        */
>>
>> Nice. :-)
> 
> Do we actually use -Wswitch anywhere? A quick grep in our code base
> didn't surface it, so I'm a bit sceptical that we would actually detect
> any missing cases via CI.

I've just double checked the gcc docs and it's included in -Wall (I'm 
pretty sure I tried deleting one of the case statements to check before 
I submitted the patch)

Thanks

Phillip


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] status: improve rebase todo list parsing
  2026-04-22  0:32   ` Elijah Newren
  2026-04-22 13:28     ` Patrick Steinhardt
@ 2026-04-22 14:15     ` Phillip Wood
  1 sibling, 0 replies; 14+ messages in thread
From: Phillip Wood @ 2026-04-22 14:15 UTC (permalink / raw)
  To: Elijah Newren, Phillip Wood; +Cc: git

Hi Elijah

Thanks for the review, all you suggestions look sensible to me, I'll 
send a re-roll.

Phillip

On 22/04/2026 01:32, Elijah Newren wrote:
> On Mon, Apr 20, 2026 at 8:25 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>>
>> When there is rebase in progress "git status" displays the last couple
>> of completed and the next couple of pending commands from the todo
>> list. When it does this is tries to abbreviate the object ids of
> 
> is tries => it tries ?
> 
> [...]
>> @@ -1363,6 +1363,51 @@ static int split_commit_in_progress(struct wt_status *s)
>>          free(rebase_orig_head);
>>
>>          return split_in_progress;
>> +}
>> +
>> +static void abbrev_oid_in_line(struct repository *r,
>> +                              struct strbuf *line, char **pp)
>> +{
>> +       char *p = *pp;
>> +       char *end_of_object_name, saved;
>> +       const char *abbrev;
>> +       struct object_id oid;
>> +       bool have_oid;
> 
> I'll put "thinking out loud" text in square brackets below...
> 
>> +
>> +       p += strspn(p, " \t");
>> +       end_of_object_name = p + strcspn(p, " \t");
> 
> [Advances p after whitespace, marks the end of the object with the
> next whitespace after that.]
> 
>> +       /*
>> +        * The for "merge" and "reset" the object name may be a label or
> 
> The for => For ?
> 
>> +        * ref rather than a hex object id. Only abbreviate the object
>> +        * name if it is a hex object id.
>> +        */
>> +       for (const char *q = p; q < end_of_object_name; q++) {
>> +               if (!isxdigit(*q))
>> +                       goto out;
>> +       }
> 
> 
> 
>> +       saved = *end_of_object_name;
>> +       *end_of_object_name = '\0';
>> +       have_oid = !repo_get_oid(r, p, &oid);
>> +       *end_of_object_name = saved;
> 
> [Tries to resolve the token, doing NUL-termination and restore dance.]
> 
>> +       if (!have_oid)
>> +               goto out; /* object name was a label */
> 
> 
>> +       abbrev = repo_find_unique_abbrev(r, &oid, DEFAULT_ABBREV);
>> +       if (!starts_with(p, abbrev))
>> +               goto out; /* object name was a refname containing only xdigits */
> 
> [Ensures what we have is an oid rather than a branch name that can be
> resolved to an oid]
> 
>> +       p += strlen(abbrev);
>> +       strbuf_remove(line, p - line->buf, end_of_object_name - p);
>> +       end_of_object_name = p;
> 
> [Splice out a bunch of characters in the middle?]
> 
>> +out:
>> +       *pp = end_of_object_name;
>> +}
> 
> I had a hard time following the logic in the function and trying to
> figure out what it was doing.  I went line by line but had no mental
> model to follow.  When I got to the comment that is now above
> format_todo_line(), I suddenly understood, but without it, all the
> code was hard to follow.  Maybe a small comment at the beginning of
> the function along the lines of
> 
>   /*
>    * If the whitespace-delimited token starting at or just after *pp is a
>    * full hex object id that resolves uniquely, rewrite it in place to
>    * its default abbreviation, shrinking `line` accordingly. On return
>    * *pp points one past the (possibly abbreviated) token. Leaves both
>    * `line` and *pp-advanced-past-the-token unchanged in all other cases
>    * (non-hex token, unresolvable, or a refname that happens to consist
>    * only of hex digits).
>    */
> 
> ?  (Assuming I'm understanding correctly, of course.)
> 
>> +
>> +static void skip_dash_c(char **pp) {
> 
> Move the brace to the next line?
> 
>> +       char *p = *pp;
>> +
>> +       p += strspn(p, " \t");
>> +       /* The (void) cast is required to silence -Wunused_value */
> 
> -Wunused_value => -Wunused-value ?
> 
>> +       (void)(skip_prefix(p, "-C", &p) || skip_prefix(p, "-c", &p));
>> +       *pp = p;
>>   }
>>
>>   /*
>> @@ -1371,29 +1416,57 @@ static int split_commit_in_progress(struct wt_status *s)
>>    * into
>>    * "pick d6a2f03 some message"
>>    *
>> - * The function assumes that the line does not contain useless spaces
>> - * before or after the command.
>> + * Returns false on comment lines, true otherwise
>>    */
>> -static void abbrev_oid_in_line(struct repository *r, struct strbuf *line)
>> +static bool format_todo_line(struct repository *r, struct strbuf *line)
>>   {
>> -       struct string_list split = STRING_LIST_INIT_DUP;
>> -       struct object_id oid;
>> -
>> -       if (starts_with(line->buf, "exec ") ||
>> -           starts_with(line->buf, "x ") ||
>> -           starts_with(line->buf, "label ") ||
>> -           starts_with(line->buf, "l "))
>> -               return;
>> -
>> -       if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
>> -           !repo_get_oid(r, split.items[1].string, &oid)) {
>> -               strbuf_reset(line);
>> -               strbuf_addf(line, "%s ", split.items[0].string);
>> -               strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
>> -               for (size_t i = 2; i < split.nr; i++)
>> -                       strbuf_addf(line, " %s", split.items[i].string);
>> +       enum todo_command cmd;
>> +       char *p = line->buf;
>> +
>> +       if (!sequencer_parse_todo_command((const char**)&p, &cmd))
>> +               return true; /* keep invalid lines */
>> +
>> +       switch (cmd) {
>> +       case TODO_COMMENT:
>> +               return false;
>> +
>> +       case TODO_MERGE:
>> +               skip_dash_c(&p);
>> +               while (true) {
>> +                       p += strspn(p, " \t");
>> +                       if (!p[0] || (p[0] == '#' && (!p[1] || isspace(p[1]))))
>> +                               break;
>> +                       abbrev_oid_in_line(r, line, &p);
>> +               }
>> +               break;
>> +
>> +       case TODO_FIXUP:
>> +               skip_dash_c(&p);
>> +               /* fallthrough */
>> +       case TODO_DROP:
>> +       case TODO_EDIT:
>> +       case TODO_PICK:
>> +       case TODO_RESET:
>> +       case TODO_REVERT:
>> +       case TODO_REWORD:
>> +       case TODO_SQUASH:
>> +               abbrev_oid_in_line(r, line, &p);
>> +               break;
>> +
>> +       /*
>> +        * Avoid "default" and instead list all the other commands so
>> +        * that -Wswitch warns if a new command is added without handling
>> +        * it in this function.
>> +        */
> 
> Nice. :-)
> 
>> +       case TODO_BREAK:
>> +       case TODO_EXEC:
>> +       case TODO_LABEL:
>> +       case TODO_NOOP:
>> +       case TODO_UPDATE_REF:
>> +               break;
>>          }
>> -       string_list_clear(&split, 0);
>> +
>> +       return true;
>>   }
>>
>>   static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines)
>> @@ -1411,13 +1484,9 @@ static int read_rebase_todolist(struct repository *r, const char *fname, struct
>>                            repo_git_path_replace(r, &buf, "%s", fname));
>>          }
>>          while (!strbuf_getline_lf(&buf, f)) {
>> -               if (starts_with(buf.buf, comment_line_str))
>> -                       continue;
>>                  strbuf_trim(&buf);
>> -               if (!buf.len)
>> -                       continue;
>> -               abbrev_oid_in_line(r, &buf);
>> -               string_list_append(lines, buf.buf);
>> +               if (format_todo_line(r, &buf))
>> +                       string_list_append(lines, buf.buf);
>>          }
>>          fclose(f);
>>
>> --
>> 2.54.0.rc1.174.gd833f386ac5.dirty
> 
> Other than the minor comments above, this looks like a nice cleanup.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v2 0/2] status: improve rebase todo list parsing
  2026-04-20 15:04 [PATCH 0/2] status: improve rebase todo list parsing Phillip Wood
  2026-04-20 15:04 ` [PATCH 1/2] sequencer: factor out parsing of todo commands Phillip Wood
  2026-04-20 15:04 ` [PATCH 2/2] status: improve rebase todo list parsing Phillip Wood
@ 2026-05-01 15:16 ` Phillip Wood
  2026-05-01 15:16   ` [PATCH v2 1/2] sequencer: factor out parsing of todo commands Phillip Wood
                     ` (2 more replies)
  2 siblings, 3 replies; 14+ messages in thread
From: Phillip Wood @ 2026-05-01 15:16 UTC (permalink / raw)
  To: git; +Cc: Elijah Newren, Patrick Steinhardt, Phillip Wood

When there is rebase in progress "git status" displays the last couple
of completed and the next couple of pending commands from the todo
list. When it does this is tries to abbreviate the object ids of
the commits to be picked. Unfortunately it does not abbreviate the
object ids when the line starts with "fixup -C" or "merge -C". It
also mistakenly replaces the refname in "reset main" and "update-ref
refs/heads/main" with the object id that the ref points to.

This series fixes that. The first patch factors out the sequencer
code that parses the command names in the todo list. The second patch
uses that function in "git status" to parse the command names so that
it knows whether the line may contain "-C" and whether there is an
object id that should be abbreviated.

Thanks to Elijah and Patrick for their comments in V1.

Changes since V1:

Patch 1 - Expanded commit message and added a code comment.

Patch 2 - Fixed some typos, added a code comment and clarified that -Wswitch
          is included by -Wall.

Base-Commit: 8c9303b1ffae5b745d1b0a1f98330cf7944d8db0
Published-As: https://github.com/phillipwood/git/releases/tag/pw%2Fimprove-status-todo-list-parsing%2Fv2
View-Changes-At: https://github.com/phillipwood/git/compare/8c9303b1f...b80bc1e0a
Fetch-It-Via: git fetch https://github.com/phillipwood/git pw/improve-status-todo-list-parsing/v2


Phillip Wood (2):
  sequencer: factor out parsing of todo commands
  status: improve rebase todo list parsing

 sequencer.c            |  45 +++++++++-----
 sequencer.h            |   8 +++
 t/t7512-status-help.sh |  74 +++++++++++++++--------
 wt-status.c            | 131 +++++++++++++++++++++++++++++++++--------
 4 files changed, 191 insertions(+), 67 deletions(-)

Range-diff against v1:
1:  3d5135a719 ! 1:  d27dddff93 sequencer: factor out parsing of todo commands
    @@ Metadata
      ## Commit message ##
         sequencer: factor out parsing of todo commands
     
    -    Move the code that parses todo commands into a separate function so that
    -    it can be shared with "git status" in the next commit.
    +    Move the code that parses todo commands into a separate function so
    +    that it can be shared with "git status" in the next commit. As we
    +    know the input is NUL terminated we do not pass a pointer to the end
    +    of the line and instead test for a blank line by looking for NUL, CR
    +    LF, or LF. We use starts_with() instead of starts_with_mem() for the
    +    same reason. This results in slightly different behavior when there
    +    a CR at the start of the line that is not followed by LF. Previously
    +    such a line was treated as a comment rather than an invalid line.
     
         Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
     
    @@ sequencer.h: int read_author_script(const char *path, char **name, char **email,
      int write_basic_state(struct replay_opts *opts, const char *head_name,
      		      struct commit *onto, const struct object_id *orig_head);
      void sequencer_post_commit_cleanup(struct repository *r, int verbose);
    ++
    ++/*
    ++ * Try to parse the todo command pointed to by *p. On success sets cmd,
    ++ * advances p and returns true. On failure returns false, leaves p and
    ++ * cmd unchanged.
    ++ */
     +bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd);
    ++
      int sequencer_get_last_command(struct repository* r,
      			       enum replay_action *action);
      int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);
2:  d20dc1f655 ! 2:  b80bc1e0a2 status: improve rebase todo list parsing
    @@ Commit message
     
         When there is rebase in progress "git status" displays the last couple
         of completed and the next couple of pending commands from the todo
    -    list. When it does this is tries to abbreviate the object ids of
    +    list. When it does this it tries to abbreviate the object ids of
         the commits to be picked. Unfortunately it does not abbreviate the
         object ids when the line starts with "fixup -C" or "merge -C". It
         also mistakenly replaces the refname in "reset main" and "update-ref
    @@ Commit message
         wider variety of commands. Only the pending commands in the tests
         are changed to avoid removing existing coverage.
     
    +    Helped-by: Elijah Newren <newren@gmail.com>
         Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
     
      ## t/t7512-status-help.sh ##
    @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
      	return split_in_progress;
     +}
     +
    ++/*
    ++ * If the whitespace-delimited token starting at or just after *pp *
    ++ * is a hex object id that is longer than its default abbreviation, *
    ++ * abbreviate it in-place, shrinking `line` accordingly. On return
    ++ * *pp points one past the (possibly abbreviated) token. Leaves both
    ++ * `line` and *pp-advanced-past-the-token unchanged in all other cases
    ++ * (non-hex token, unresolvable, or a refname that happens to consist
    ++ * only of hex digits).
    ++ */
     +static void abbrev_oid_in_line(struct repository *r,
     +			       struct strbuf *line, char **pp)
     +{
    @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
     +	p += strspn(p, " \t");
     +	end_of_object_name = p + strcspn(p, " \t");
     +	/*
    -+	 * The for "merge" and "reset" the object name may be a label or
    ++	 * For "merge" and "reset" the object name may be a label or
     +	 * ref rather than a hex object id. Only abbreviate the object
     +	 * name if it is a hex object id.
     +	 */
    @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
     +	*pp = end_of_object_name;
     +}
     +
    -+static void skip_dash_c(char **pp) {
    ++static void skip_dash_c(char **pp)
    ++{
     +	char *p = *pp;
     +
     +	p += strspn(p, " \t");
    -+	/* The (void) cast is required to silence -Wunused_value */
    ++	/* The (void) cast is required to silence -Wunused-value */
     +	(void)(skip_prefix(p, "-C", &p) || skip_prefix(p, "-c", &p));
     +	*pp = p;
      }
    @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
     +
     +	/*
     +	 * Avoid "default" and instead list all the other commands so
    -+	 * that -Wswitch warns if a new command is added without handling
    -+	 * it in this function.
    ++	 * that -Wswitch (which is included in -Wall) warns if a new
    ++	 * command is added without handling it in this function.
     +	 */
     +	case TODO_BREAK:
     +	case TODO_EXEC:
-- 
2.54.0.rc1.174.gd833f386ac5.dirty


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v2 1/2] sequencer: factor out parsing of todo commands
  2026-05-01 15:16 ` [PATCH v2 0/2] " Phillip Wood
@ 2026-05-01 15:16   ` Phillip Wood
  2026-05-01 15:16   ` [PATCH v2 2/2] status: improve rebase todo list parsing Phillip Wood
  2026-05-01 18:19   ` [PATCH v2 0/2] " Phillip Wood
  2 siblings, 0 replies; 14+ messages in thread
From: Phillip Wood @ 2026-05-01 15:16 UTC (permalink / raw)
  To: git; +Cc: Elijah Newren, Patrick Steinhardt, Phillip Wood

From: Phillip Wood <phillip.wood@dunelm.org.uk>

Move the code that parses todo commands into a separate function so
that it can be shared with "git status" in the next commit. As we
know the input is NUL terminated we do not pass a pointer to the end
of the line and instead test for a blank line by looking for NUL, CR
LF, or LF. We use starts_with() instead of starts_with_mem() for the
same reason. This results in slightly different behavior when there
a CR at the start of the line that is not followed by LF. Previously
such a line was treated as a comment rather than an invalid line.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
 sequencer.c | 45 ++++++++++++++++++++++++++++++---------------
 sequencer.h |  8 ++++++++
 2 files changed, 38 insertions(+), 15 deletions(-)

diff --git a/sequencer.c b/sequencer.c
index b7d8dca47f..b8e860434a 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2625,6 +2625,27 @@ static int is_command(enum todo_command command, const char **bol)
 		return 1;
 	}
 	return 0;
+}
+
+bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd)
+{
+	const char *s = *p;
+
+	for (int i = 0; i < TODO_COMMENT; i++)
+		if (is_command(i, p)) {
+			*cmd = i;
+			return true;
+		}
+
+	if (starts_with(s, comment_line_str)) {
+		*cmd = TODO_COMMENT;
+		return true;
+	} else if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n') || !s[0]) {
+		*cmd = TODO_COMMENT;
+		return true;
+	}
+
+	return false;
 }
 
 static int check_label_or_ref_arg(enum todo_command command, const char *arg)
@@ -2716,29 +2737,23 @@ static int parse_insn_line(struct repository *r, struct replay_opts *opts,
 {
 	struct object_id commit_oid;
 	char *end_of_object_name;
-	int i, saved, status, padding;
+	int saved, status, padding;
 
 	item->flags = 0;
 
 	/* left-trim */
 	bol += strspn(bol, " \t");
 
-	if (bol == eol || *bol == '\r' || starts_with_mem(bol, eol - bol, comment_line_str)) {
-		item->command = TODO_COMMENT;
-		item->commit = NULL;
-		item->arg_offset = bol - buf;
-		item->arg_len = eol - bol;
-		return 0;
-	}
-
-	for (i = 0; i < TODO_COMMENT; i++)
-		if (is_command(i, &bol)) {
-			item->command = i;
-			break;
-		}
-	if (i >= TODO_COMMENT)
+	if (!sequencer_parse_todo_command(&bol, &item->command))
 		return error(_("invalid command '%.*s'"),
 			     (int)strcspn(bol, " \t\r\n"), bol);
+
+	if (item->command == TODO_COMMENT) {
+		item->commit = NULL;
+		item->arg_offset = bol - buf;
+		item->arg_len = eol - bol;
+		return 0;
+	}
 
 	/* Eat up extra spaces/ tabs before object name */
 	padding = strspn(bol, " \t");
diff --git a/sequencer.h b/sequencer.h
index a6fa670c7c..28fabef926 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -262,6 +262,14 @@ int read_author_script(const char *path, char **name, char **email, char **date,
 int write_basic_state(struct replay_opts *opts, const char *head_name,
 		      struct commit *onto, const struct object_id *orig_head);
 void sequencer_post_commit_cleanup(struct repository *r, int verbose);
+
+/*
+ * Try to parse the todo command pointed to by *p. On success sets cmd,
+ * advances p and returns true. On failure returns false, leaves p and
+ * cmd unchanged.
+ */
+bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd);
+
 int sequencer_get_last_command(struct repository* r,
 			       enum replay_action *action);
 int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);
-- 
2.54.0.rc1.174.gd833f386ac5.dirty


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v2 2/2] status: improve rebase todo list parsing
  2026-05-01 15:16 ` [PATCH v2 0/2] " Phillip Wood
  2026-05-01 15:16   ` [PATCH v2 1/2] sequencer: factor out parsing of todo commands Phillip Wood
@ 2026-05-01 15:16   ` Phillip Wood
  2026-05-01 18:19   ` [PATCH v2 0/2] " Phillip Wood
  2 siblings, 0 replies; 14+ messages in thread
From: Phillip Wood @ 2026-05-01 15:16 UTC (permalink / raw)
  To: git; +Cc: Elijah Newren, Patrick Steinhardt, Phillip Wood

From: Phillip Wood <phillip.wood@dunelm.org.uk>

When there is rebase in progress "git status" displays the last couple
of completed and the next couple of pending commands from the todo
list. When it does this it tries to abbreviate the object ids of
the commits to be picked. Unfortunately it does not abbreviate the
object ids when the line starts with "fixup -C" or "merge -C". It
also mistakenly replaces the refname in "reset main" and "update-ref
refs/heads/main" with the object id that the ref points to. Use
the function added in the last commit to parse the command name and
only try to abbreviate the argument for commands that take an object
id. When trying to abbreviate an object id, only replace the object
name if it starts with the abbreviated object id so that labels or
branch names that contain only hex digits are left unchanged.

Comments are now processed after stripping any leading
whitespace from the line. This matches what the sequencer does in
parse_insn_line(). The existing test cases are updated to test a
wider variety of commands. Only the pending commands in the tests
are changed to avoid removing existing coverage.

Helped-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
 t/t7512-status-help.sh |  74 +++++++++++++++--------
 wt-status.c            | 131 +++++++++++++++++++++++++++++++++--------
 2 files changed, 153 insertions(+), 52 deletions(-)

diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 08e82f7914..aca4b6d332 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -224,7 +224,7 @@ test_expect_success 'status when splitting a commit' '
 	COMMIT3=$(git rev-parse --short split_commit) &&
 	test_commit four_split main.txt four &&
 	COMMIT4=$(git rev-parse --short split_commit) &&
-	FAKE_LINES="1 edit 2 3" &&
+	FAKE_LINES="reword 1 edit 2 fixup_-C 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD~3) &&
@@ -233,10 +233,10 @@ test_expect_success 'status when splitting a commit' '
 	cat >expected <<EOF &&
 interactive rebase in progress; onto $ONTO
 Last commands done (2 commands done):
-   pick $COMMIT2 # two_split
+   reword $COMMIT2 # two_split
    edit $COMMIT3 # three_split
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_split
+   fixup -C $COMMIT4 # four_split
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
@@ -297,7 +297,7 @@ test_expect_success 'prepare for several edits' '
 
 
 test_expect_success 'status: (continue first edit) second edit' '
-	FAKE_LINES="edit 1 edit 2 3" &&
+	FAKE_LINES="edit 1 edit 2 drop 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
@@ -312,7 +312,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   drop $COMMIT4 # four_edits
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
@@ -327,7 +327,7 @@ EOF
 
 test_expect_success 'status: (continue first edit) second edit and split' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
+	FAKE_LINES="edit 1 edit 2 squash 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
@@ -343,7 +343,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   squash $COMMIT4 # four_edits
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
@@ -362,7 +362,7 @@ EOF
 
 test_expect_success 'status: (continue first edit) second edit and amend' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
+	FAKE_LINES="edit 1 edit 2 fixup 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
@@ -378,7 +378,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   fixup $COMMIT4 # four_edits
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
@@ -393,7 +393,7 @@ EOF
 
 test_expect_success 'status: (amend first edit) second edit' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
+	FAKE_LINES="edit 1 edit 2 fixup_-c 3" &&
 	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
@@ -409,7 +409,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   fixup -c $COMMIT4 # four_edits
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
@@ -460,14 +460,20 @@ EOF
 
 test_expect_success 'status: (amend first edit) second edit and amend' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
-	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
 	COMMIT3=$(git rev-parse --short several_edits^) &&
 	COMMIT4=$(git rev-parse --short several_edits) &&
 	ONTO=$(git rev-parse --short HEAD~3) &&
-	git rebase -i HEAD~3 &&
+	cat >todo <<-EOF &&
+	edit several_edits^^ # two_edits
+	edit several_edits^ # three_edits
+	merge $(git rev-parse main) $(git rev-parse several_edits)
+	EOF
+	(
+		set_replace_editor todo &&
+		git rebase -i HEAD~3
+	) &&
 	git commit --amend -m "c" &&
 	git rebase --continue &&
 	git commit --amend -m "d" &&
@@ -477,7 +483,7 @@ Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
 Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+   merge $(git rev-parse --short main) $COMMIT4
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
@@ -525,14 +531,21 @@ EOF
 
 test_expect_success 'status: (split first edit) second edit and split' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
-	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
 	COMMIT3=$(git rev-parse --short several_edits^) &&
 	COMMIT4=$(git rev-parse --short several_edits) &&
+	cat >todo <<-EOF &&
+	edit several_edits^^ # two_edits
+	edit several_edits^ # three_edits
+	reset $(git rev-parse main)
+	merge -C several_edits topic # title
+	EOF
 	ONTO=$(git rev-parse --short HEAD~3) &&
-	git rebase -i HEAD~3 &&
+	(
+		set_replace_editor todo &&
+		git rebase -i HEAD~3
+	) &&
 	git reset HEAD^ &&
 	git add main.txt &&
 	git commit --amend -m "f" &&
@@ -543,8 +556,9 @@ interactive rebase in progress; onto $ONTO
 Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
-Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+Next commands to do (2 remaining commands):
+   reset $(git rev-parse --short main)
+   merge -C $COMMIT4 topic # title
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
@@ -563,14 +577,21 @@ EOF
 
 test_expect_success 'status: (split first edit) second edit and amend' '
 	git reset --hard several_edits &&
-	FAKE_LINES="edit 1 edit 2 3" &&
-	export FAKE_LINES &&
 	test_when_finished "git rebase --abort" &&
+	git branch cafe main &&
 	COMMIT2=$(git rev-parse --short several_edits^^) &&
 	COMMIT3=$(git rev-parse --short several_edits^) &&
-	COMMIT4=$(git rev-parse --short several_edits) &&
+	cat >todo <<-EOF &&
+	edit several_edits^^ # two_edits
+	edit several_edits^ # three_edits
+	update-ref refs/heads/main
+	reset cafe
+	EOF
 	ONTO=$(git rev-parse --short HEAD~3) &&
-	git rebase -i HEAD~3 &&
+	(
+		set_replace_editor todo &&
+		git rebase -i HEAD~3
+	) &&
 	git reset HEAD^ &&
 	git add main.txt &&
 	git commit --amend -m "g" &&
@@ -581,8 +602,9 @@ interactive rebase in progress; onto $ONTO
 Last commands done (2 commands done):
    edit $COMMIT2 # two_edits
    edit $COMMIT3 # three_edits
-Next command to do (1 remaining command):
-   pick $COMMIT4 # four_edits
+Next commands to do (2 remaining commands):
+   update-ref refs/heads/main
+   reset cafe
   (use "git rebase --edit-todo" to view and edit)
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
diff --git a/wt-status.c b/wt-status.c
index 479ccc3304..94c159d9d4 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1363,6 +1363,61 @@ static int split_commit_in_progress(struct wt_status *s)
 	free(rebase_orig_head);
 
 	return split_in_progress;
+}
+
+/*
+ * If the whitespace-delimited token starting at or just after *pp *
+ * is a hex object id that is longer than its default abbreviation, *
+ * abbreviate it in-place, shrinking `line` accordingly. On return
+ * *pp points one past the (possibly abbreviated) token. Leaves both
+ * `line` and *pp-advanced-past-the-token unchanged in all other cases
+ * (non-hex token, unresolvable, or a refname that happens to consist
+ * only of hex digits).
+ */
+static void abbrev_oid_in_line(struct repository *r,
+			       struct strbuf *line, char **pp)
+{
+	char *p = *pp;
+	char *end_of_object_name, saved;
+	const char *abbrev;
+	struct object_id oid;
+	bool have_oid;
+
+	p += strspn(p, " \t");
+	end_of_object_name = p + strcspn(p, " \t");
+	/*
+	 * For "merge" and "reset" the object name may be a label or
+	 * ref rather than a hex object id. Only abbreviate the object
+	 * name if it is a hex object id.
+	 */
+	for (const char *q = p; q < end_of_object_name; q++) {
+		if (!isxdigit(*q))
+			goto out;
+	}
+	saved = *end_of_object_name;
+	*end_of_object_name = '\0';
+	have_oid = !repo_get_oid(r, p, &oid);
+	*end_of_object_name = saved;
+	if (!have_oid)
+		goto out; /* object name was a label */
+	abbrev = repo_find_unique_abbrev(r, &oid, DEFAULT_ABBREV);
+	if (!starts_with(p, abbrev))
+		goto out; /* object name was a refname containing only xdigits */
+	p += strlen(abbrev);
+	strbuf_remove(line, p - line->buf, end_of_object_name - p);
+	end_of_object_name = p;
+out:
+	*pp = end_of_object_name;
+}
+
+static void skip_dash_c(char **pp)
+{
+	char *p = *pp;
+
+	p += strspn(p, " \t");
+	/* The (void) cast is required to silence -Wunused-value */
+	(void)(skip_prefix(p, "-C", &p) || skip_prefix(p, "-c", &p));
+	*pp = p;
 }
 
 /*
@@ -1371,29 +1426,57 @@ static int split_commit_in_progress(struct wt_status *s)
  * into
  * "pick d6a2f03 some message"
  *
- * The function assumes that the line does not contain useless spaces
- * before or after the command.
+ * Returns false on comment lines, true otherwise
  */
-static void abbrev_oid_in_line(struct repository *r, struct strbuf *line)
+static bool format_todo_line(struct repository *r, struct strbuf *line)
 {
-	struct string_list split = STRING_LIST_INIT_DUP;
-	struct object_id oid;
-
-	if (starts_with(line->buf, "exec ") ||
-	    starts_with(line->buf, "x ") ||
-	    starts_with(line->buf, "label ") ||
-	    starts_with(line->buf, "l "))
-		return;
-
-	if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
-	    !repo_get_oid(r, split.items[1].string, &oid)) {
-		strbuf_reset(line);
-		strbuf_addf(line, "%s ", split.items[0].string);
-		strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
-		for (size_t i = 2; i < split.nr; i++)
-			strbuf_addf(line, " %s", split.items[i].string);
+	enum todo_command cmd;
+	char *p = line->buf;
+
+	if (!sequencer_parse_todo_command((const char**)&p, &cmd))
+		return true; /* keep invalid lines */
+
+	switch (cmd) {
+	case TODO_COMMENT:
+		return false;
+
+	case TODO_MERGE:
+		skip_dash_c(&p);
+		while (true) {
+			p += strspn(p, " \t");
+			if (!p[0] || (p[0] == '#' && (!p[1] || isspace(p[1]))))
+				break;
+			abbrev_oid_in_line(r, line, &p);
+		}
+		break;
+
+	case TODO_FIXUP:
+		skip_dash_c(&p);
+		/* fallthrough */
+	case TODO_DROP:
+	case TODO_EDIT:
+	case TODO_PICK:
+	case TODO_RESET:
+	case TODO_REVERT:
+	case TODO_REWORD:
+	case TODO_SQUASH:
+		abbrev_oid_in_line(r, line, &p);
+		break;
+
+	/*
+	 * Avoid "default" and instead list all the other commands so
+	 * that -Wswitch (which is included in -Wall) warns if a new
+	 * command is added without handling it in this function.
+	 */
+	case TODO_BREAK:
+	case TODO_EXEC:
+	case TODO_LABEL:
+	case TODO_NOOP:
+	case TODO_UPDATE_REF:
+		break;
 	}
-	string_list_clear(&split, 0);
+
+	return true;
 }
 
 static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines)
@@ -1411,13 +1494,9 @@ static int read_rebase_todolist(struct repository *r, const char *fname, struct
 			  repo_git_path_replace(r, &buf, "%s", fname));
 	}
 	while (!strbuf_getline_lf(&buf, f)) {
-		if (starts_with(buf.buf, comment_line_str))
-			continue;
 		strbuf_trim(&buf);
-		if (!buf.len)
-			continue;
-		abbrev_oid_in_line(r, &buf);
-		string_list_append(lines, buf.buf);
+		if (format_todo_line(r, &buf))
+			string_list_append(lines, buf.buf);
 	}
 	fclose(f);
 
-- 
2.54.0.rc1.174.gd833f386ac5.dirty


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 0/2] status: improve rebase todo list parsing
  2026-05-01 15:16 ` [PATCH v2 0/2] " Phillip Wood
  2026-05-01 15:16   ` [PATCH v2 1/2] sequencer: factor out parsing of todo commands Phillip Wood
  2026-05-01 15:16   ` [PATCH v2 2/2] status: improve rebase todo list parsing Phillip Wood
@ 2026-05-01 18:19   ` Phillip Wood
  2 siblings, 0 replies; 14+ messages in thread
From: Phillip Wood @ 2026-05-01 18:19 UTC (permalink / raw)
  To: Phillip Wood, git; +Cc: Elijah Newren, Patrick Steinhardt, Tian Yuchen

On 01/05/2026 16:16, Phillip Wood wrote:
> When there is rebase in progress "git status" displays the last couple
> of completed and the next couple of pending commands from the todo
> list. When it does this is tries to abbreviate the object ids of
> the commits to be picked. Unfortunately it does not abbreviate the
> object ids when the line starts with "fixup -C" or "merge -C". It
> also mistakenly replaces the refname in "reset main" and "update-ref
> refs/heads/main" with the object id that the ref points to.
> 
> This series fixes that. The first patch factors out the sequencer
> code that parses the command names in the todo list. The second patch
> uses that function in "git status" to parse the command names so that
> it knows whether the line may contain "-C" and whether there is an
> object id that should be abbreviated.
> 
> Thanks to Elijah and Patrick for their comments in V1.

Sorry Tian, I'd forgotten that you'd commented on these patches as well 
- thanks for your comments too.

Phillip

> 
> Changes since V1:
> 
> Patch 1 - Expanded commit message and added a code comment.
> 
> Patch 2 - Fixed some typos, added a code comment and clarified that -Wswitch
>            is included by -Wall.
> 
> Base-Commit: 8c9303b1ffae5b745d1b0a1f98330cf7944d8db0
> Published-As: https://github.com/phillipwood/git/releases/tag/pw%2Fimprove-status-todo-list-parsing%2Fv2
> View-Changes-At: https://github.com/phillipwood/git/compare/8c9303b1f...b80bc1e0a
> Fetch-It-Via: git fetch https://github.com/phillipwood/git pw/improve-status-todo-list-parsing/v2
> 
> 
> Phillip Wood (2):
>    sequencer: factor out parsing of todo commands
>    status: improve rebase todo list parsing
> 
>   sequencer.c            |  45 +++++++++-----
>   sequencer.h            |   8 +++
>   t/t7512-status-help.sh |  74 +++++++++++++++--------
>   wt-status.c            | 131 +++++++++++++++++++++++++++++++++--------
>   4 files changed, 191 insertions(+), 67 deletions(-)
> 
> Range-diff against v1:
> 1:  3d5135a719 ! 1:  d27dddff93 sequencer: factor out parsing of todo commands
>      @@ Metadata
>        ## Commit message ##
>           sequencer: factor out parsing of todo commands
>       
>      -    Move the code that parses todo commands into a separate function so that
>      -    it can be shared with "git status" in the next commit.
>      +    Move the code that parses todo commands into a separate function so
>      +    that it can be shared with "git status" in the next commit. As we
>      +    know the input is NUL terminated we do not pass a pointer to the end
>      +    of the line and instead test for a blank line by looking for NUL, CR
>      +    LF, or LF. We use starts_with() instead of starts_with_mem() for the
>      +    same reason. This results in slightly different behavior when there
>      +    a CR at the start of the line that is not followed by LF. Previously
>      +    such a line was treated as a comment rather than an invalid line.
>       
>           Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
>       
>      @@ sequencer.h: int read_author_script(const char *path, char **name, char **email,
>        int write_basic_state(struct replay_opts *opts, const char *head_name,
>        		      struct commit *onto, const struct object_id *orig_head);
>        void sequencer_post_commit_cleanup(struct repository *r, int verbose);
>      ++
>      ++/*
>      ++ * Try to parse the todo command pointed to by *p. On success sets cmd,
>      ++ * advances p and returns true. On failure returns false, leaves p and
>      ++ * cmd unchanged.
>      ++ */
>       +bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd);
>      ++
>        int sequencer_get_last_command(struct repository* r,
>        			       enum replay_action *action);
>        int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);
> 2:  d20dc1f655 ! 2:  b80bc1e0a2 status: improve rebase todo list parsing
>      @@ Commit message
>       
>           When there is rebase in progress "git status" displays the last couple
>           of completed and the next couple of pending commands from the todo
>      -    list. When it does this is tries to abbreviate the object ids of
>      +    list. When it does this it tries to abbreviate the object ids of
>           the commits to be picked. Unfortunately it does not abbreviate the
>           object ids when the line starts with "fixup -C" or "merge -C". It
>           also mistakenly replaces the refname in "reset main" and "update-ref
>      @@ Commit message
>           wider variety of commands. Only the pending commands in the tests
>           are changed to avoid removing existing coverage.
>       
>      +    Helped-by: Elijah Newren <newren@gmail.com>
>           Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
>       
>        ## t/t7512-status-help.sh ##
>      @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
>        	return split_in_progress;
>       +}
>       +
>      ++/*
>      ++ * If the whitespace-delimited token starting at or just after *pp *
>      ++ * is a hex object id that is longer than its default abbreviation, *
>      ++ * abbreviate it in-place, shrinking `line` accordingly. On return
>      ++ * *pp points one past the (possibly abbreviated) token. Leaves both
>      ++ * `line` and *pp-advanced-past-the-token unchanged in all other cases
>      ++ * (non-hex token, unresolvable, or a refname that happens to consist
>      ++ * only of hex digits).
>      ++ */
>       +static void abbrev_oid_in_line(struct repository *r,
>       +			       struct strbuf *line, char **pp)
>       +{
>      @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
>       +	p += strspn(p, " \t");
>       +	end_of_object_name = p + strcspn(p, " \t");
>       +	/*
>      -+	 * The for "merge" and "reset" the object name may be a label or
>      ++	 * For "merge" and "reset" the object name may be a label or
>       +	 * ref rather than a hex object id. Only abbreviate the object
>       +	 * name if it is a hex object id.
>       +	 */
>      @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
>       +	*pp = end_of_object_name;
>       +}
>       +
>      -+static void skip_dash_c(char **pp) {
>      ++static void skip_dash_c(char **pp)
>      ++{
>       +	char *p = *pp;
>       +
>       +	p += strspn(p, " \t");
>      -+	/* The (void) cast is required to silence -Wunused_value */
>      ++	/* The (void) cast is required to silence -Wunused-value */
>       +	(void)(skip_prefix(p, "-C", &p) || skip_prefix(p, "-c", &p));
>       +	*pp = p;
>        }
>      @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
>       +
>       +	/*
>       +	 * Avoid "default" and instead list all the other commands so
>      -+	 * that -Wswitch warns if a new command is added without handling
>      -+	 * it in this function.
>      ++	 * that -Wswitch (which is included in -Wall) warns if a new
>      ++	 * command is added without handling it in this function.
>       +	 */
>       +	case TODO_BREAK:
>       +	case TODO_EXEC:


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-05-01 18:19 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-20 15:04 [PATCH 0/2] status: improve rebase todo list parsing Phillip Wood
2026-04-20 15:04 ` [PATCH 1/2] sequencer: factor out parsing of todo commands Phillip Wood
2026-04-22  0:32   ` Elijah Newren
2026-04-20 15:04 ` [PATCH 2/2] status: improve rebase todo list parsing Phillip Wood
2026-04-20 16:38   ` Tian Yuchen
2026-04-21 16:03     ` Phillip Wood
2026-04-22  0:32   ` Elijah Newren
2026-04-22 13:28     ` Patrick Steinhardt
2026-04-22 14:14       ` Phillip Wood
2026-04-22 14:15     ` Phillip Wood
2026-05-01 15:16 ` [PATCH v2 0/2] " Phillip Wood
2026-05-01 15:16   ` [PATCH v2 1/2] sequencer: factor out parsing of todo commands Phillip Wood
2026-05-01 15:16   ` [PATCH v2 2/2] status: improve rebase todo list parsing Phillip Wood
2026-05-01 18:19   ` [PATCH v2 0/2] " Phillip Wood

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox