public inbox for git@vger.kernel.org
 help / color / mirror / Atom feed
From: Junio C Hamano <gitster@pobox.com>
To: Abraham Samuel Adekunle <abrahamadekunle50@gmail.com>
Cc: git@vger.kernel.org, "Patrick Steinhardt" <ps@pks.im>,
	"Phillip Wood" <phillip.wood123@gmail.com>,
	"SZEDER Gábor" <szeder.dev@gmail.com>,
	"Christian Couder" <christian.couder@gmail.com>,
	"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com>,
	"Ben Knoble" <ben.knoble@gmail.com>
Subject: Re: [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks
Date: Tue, 27 Jan 2026 12:48:22 -0800	[thread overview]
Message-ID: <xmqq7bt2g4tl.fsf@gitster.g> (raw)
In-Reply-To: <9b21cb901ab14397af94b8ed2d09da1a9a6d862b.1769522219.git.abrahamadekunle50@gmail.com> (Abraham Samuel Adekunle's message of "Tue, 27 Jan 2026 16:45:13 +0100")

Abraham Samuel Adekunle <abrahamadekunle50@gmail.com> writes:

> diff --git a/add-patch.c b/add-patch.c
> index 173a53241e..edb2fab3fd 100644
> --- a/add-patch.c
> +++ b/add-patch.c
> @@ -1418,6 +1418,8 @@ N_("j - go to the next undecided hunk, roll over at the bottom\n"
>     "e - manually edit the current hunk\n"
>     "p - print the current hunk\n"
>     "P - print the current hunk using the pager\n"
> +   "> - go to the next file\n"
> +   "< - go to the previous file\n"
>     "? - print help\n");

As I said earlier, these may have to be optional.  It may give
existing users a jarring experience to be given a prompt after
deciding on all the hunks in a file, when they expect to be on
the next file already.

> @@ -1441,6 +1443,17 @@ static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx)
>  	return false;
>  }
>  
> +static size_t get_file_diff_index(struct add_p_state *s, struct file_diff *file_diff) {
> +	size_t idx = 0;
> +	for (size_t i = 0; i < s->file_diff_nr; i++) {
> +		if (s->file_diff + i == file_diff) {
> +			idx = i;
> +			break;
> +		}
> +	}
> +	return idx;
> +}

Yuck.  Can't we lose the need for this function if we change the
interface into patch_update_file so that it takes the index of the
file (i.e., instead of "&s.file_diff[i]", pass "i")?  There is only
one caller to patch_update_file() which is run_add_p(), so such a
clean-up should be trivial.

>  static int patch_update_file(struct add_p_state *s,
>  			     struct file_diff *file_diff)
>  {
> @@ -1448,9 +1461,10 @@ static int patch_update_file(struct add_p_state *s,
>  	ssize_t i, undecided_previous, undecided_next, rendered_hunk_index = -1;
>  	struct hunk *hunk;
>  	char ch;
> -	struct child_process cp = CHILD_PROCESS_INIT;

This is related to the hoisting of the actual patch application to
the caller, but it is not explained why such a change is needed, and
it byitself, even without the "jump to the next file before deciding
on all the hunks" feature.  What problem is it solving???

If it is necessary to move the code to run "git apply" to the
caller, would it make sense to split this patch into at least two
patches, one to do such a move, possibly another patch to change the
function signature of patch_update_file() so that it takes the file
index instead of file_diff struct, and finally another patch to
allow jumping around the files?

>  	int colored = !!s->colored.len, quit = 0, use_pager = 0;
>  	enum prompt_mode_type prompt_mode_type;
> +	size_t file_diff_index = get_file_diff_index(s, file_diff);
> +	int all_decided = 0;
>  
>  	/* Empty added files have no hunks */
>  	if (!file_diff->hunk_nr && !file_diff->added)
> @@ -1467,7 +1481,9 @@ static int patch_update_file(struct add_p_state *s,
>  			ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3,
>  			ALLOW_SEARCH_AND_GOTO = 1 << 4,
>  			ALLOW_SPLIT = 1 << 5,
> -			ALLOW_EDIT = 1 << 6
> +			ALLOW_EDIT = 1 << 6,
> +			ALLOW_GOTO_PREVIOUS_FILE = 1 << 7,
> +			ALLOW_GOTO_NEXT_FILE = 1 << 8
>  		} permitted = 0;
>  
>  		if (hunk_index >= file_diff->hunk_nr)
> @@ -1499,8 +1515,7 @@ static int patch_update_file(struct add_p_state *s,
>  		/* Everything decided? */
>  		if (undecided_previous < 0 && undecided_next < 0 &&
>  		    hunk->use != UNDECIDED_HUNK)
> -			break;
> -
> +				all_decided = 1;
>  		strbuf_reset(&s->buf);
>  		if (file_diff->hunk_nr) {
>  			if (rendered_hunk_index != hunk_index) {
> @@ -1548,6 +1563,16 @@ static int patch_update_file(struct add_p_state *s,
>  				permitted |= ALLOW_EDIT;
>  				strbuf_addstr(&s->buf, ",e");
>  			}
> +			if (file_diff_index >= 0 &&
> +				file_diff_index < s->file_diff_nr - 1) {
> +				permitted |= ALLOW_GOTO_NEXT_FILE;
> +				strbuf_addstr(&s->buf, ",>");
> +			}
> +			if (file_diff_index > 0 &&
> +				file_diff_index <= s->file_diff_nr - 1) {
> +				permitted |= ALLOW_GOTO_PREVIOUS_FILE;
> +				strbuf_addstr(&s->buf, ",<");
> +			}

As can be seen in what patch_update_file() does when the user says
'J' or 'K', hunks in a file are treated as a ring, and these
commands are enabled as long as there are more than one hunks.

Perhaps that is more familiar than "when we hit the floor, we cannot
sink deeper, and when we hit the ceiling, we cannot float more",
which seems to be what the above implements.

>  			strbuf_addstr(&s->buf, ",p,P");
>  		}
>  		if (file_diff->deleted)
> @@ -1566,6 +1591,9 @@ static int patch_update_file(struct add_p_state *s,
>  						: 1));
>  		printf(_(s->mode->prompt_mode[prompt_mode_type]),
>  		       s->buf.buf);
> +		if (all_decided)
> +			printf(_("\n%s All hunks decided. What now? "),
> +				s->s.prompt_color);
>  		if (*s->s.reset_color_interactive)
>  			fputs(s->s.reset_color_interactive, stdout);
>  		fflush(stdout);
> @@ -1618,7 +1646,24 @@ static int patch_update_file(struct add_p_state *s,
>  		} else if (ch == 'q') {
>  			quit = 1;
>  			break;
> -		} else if (s->answer.buf[0] == 'K') {
> +		} else if (s->answer.buf[0] == '>') {
> +			if (permitted & ALLOW_GOTO_NEXT_FILE) {
> +				quit = 0;
> +				break;
> +			} else {
> +				err(s, _("No next file"));
> +				continue;
> +			}
> +		} else if (s->answer.buf[0] == '<') {
> +			if (permitted & ALLOW_GOTO_PREVIOUS_FILE) {
> +				quit = 2;
> +				break;

What's the magic number "2"?  Should "quit" become an enum with
elements that are more meaningfully named?

> +			} else {
> +				err(s, _("No previous file"));
> +				continue;
> +			}
> +		}
> +		else if (s->answer.buf[0] == 'K') {
>  			if (permitted & ALLOW_GOTO_PREVIOUS_HUNK)
>  				hunk_index = dec_mod(hunk_index,
>  						     file_diff->hunk_nr);
> @@ -1775,33 +1820,6 @@ static int patch_update_file(struct add_p_state *s,
>  		}
>  	}
>  
> -	/* Any hunk to be used? */
> -	for (i = 0; i < file_diff->hunk_nr; i++)
> -		if (file_diff->hunk[i].use == USE_HUNK)
> -			break;
> -
> -	if (i < file_diff->hunk_nr ||
> -	    (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) {
> -		/* At least one hunk selected: apply */
> -		strbuf_reset(&s->buf);
> -		reassemble_patch(s, file_diff, 0, &s->buf);
> -
> -		discard_index(s->s.r->index);
> -		if (s->mode->apply_for_checkout)
> -			apply_for_checkout(s, &s->buf,
> -					   s->mode->is_reverse);
> -		else {
> -			setup_child_process(s, &cp, "apply", NULL);
> -			strvec_pushv(&cp.args, s->mode->apply_args);
> -			if (pipe_command(&cp, s->buf.buf, s->buf.len,
> -					 NULL, 0, NULL, 0))
> -				error(_("'git apply' failed"));
> -		}
> -		if (repo_read_index(s->s.r) >= 0)
> -			repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
> -						     1, NULL, NULL, NULL);
> -	}

It is not obvious why the above code needs to be hoisted to the
caller.  

>  	putchar('\n');
>  	return quit;
>  }
> @@ -1813,7 +1831,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
>  	struct add_p_state s = {
>  		{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
>  	};
> -	size_t i, binary_count = 0;
> +	size_t i, j, binary_count = 0;
> +	size_t patch_update_result = 0;

Hmph, I think patch_update_file() returns "int quit".  Why do we
want overly wide type to store the result, which cannot even express
negative number to potentially signal a failure?

> +	struct child_process cp = CHILD_PROCESS_INIT;
>  
>  	init_add_i_state(&s.s, r, o);
>  
> @@ -1852,11 +1872,56 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
>  		return -1;
>  	}
>  
> -	for (i = 0; i < s.file_diff_nr; i++)
> -		if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr)
> +	for (i = 0; i < s.file_diff_nr;) {
> +		if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) {
>  			binary_count++;
> -		else if (patch_update_file(&s, s.file_diff + i))
> -			break;
> +			i++;
> +			continue;
> +		}
> +		else {
> +			patch_update_result = patch_update_file(&s, s.file_diff + i);
> +			if (patch_update_result == 0) {
> +				i++;
> +				continue;
> +			}
> +			if (patch_update_result == 1)
> +				break;
> +			if (patch_update_result == 2) {
> +				i--;
> +				continue;
> +			}
> +		}
> +	}
> +	for (i = 0; i < s.file_diff_nr; i++) {
> +
> +			/* Any hunk to be used? */
> +		for (j = 0; j < s.file_diff[i].hunk_nr; j++)
> +			if (s.file_diff[i].hunk[j].use == USE_HUNK)
> +				break;
> +
> +		if (j < s.file_diff[i].hunk_nr ||
> +	    (!s.file_diff[i].hunk_nr && s.file_diff[i].head.use == USE_HUNK)) {
> +			/* At least one hunk selected: apply */
> +			strbuf_reset(&s.buf);
> +			reassemble_patch(&s, s.file_diff + i, 0, &s.buf);
> +
> +			discard_index(s.s.r->index);
> +			if (s.mode->apply_for_checkout)
> +				apply_for_checkout(&s, &s.buf,
> +						s.mode->is_reverse);
> +			else {
> +				setup_child_process(&s, &cp, "apply", NULL);
> +				strvec_pushv(&cp.args, s.mode->apply_args);
> +				if (pipe_command(&cp, s.buf.buf, s.buf.len,
> +						NULL, 0, NULL, 0))
> +					error(_("'git apply' failed"));
> +			}
> +			if (repo_read_index(s.s.r) >= 0)
> +				repo_refresh_and_write_index(s.s.r, REFRESH_QUIET, 0,
> +								1, NULL, NULL, NULL);
> +		}
> +
> +	}

One upside of having "git apply" at the end of patch_update_file()
is that you can "^C" out of "git add -p" or your terminal connection
can be cut off, after dealing with hunks in a few early files, and
these early part of your work that you have already done are already
reflected to the working tree files.  By hoisting the logic to the
caller, this is making the update all-or-none, which is good in
transactional systems, but can make a horrible experience for an
interactive use where you make progress while thinking.

So I am not yet convinced if this change makes sense---it could be
because of the lack of justification for this change.



>  
>  	if (s.file_diff_nr == 0)
>  		err(&s, _("No changes."));

  reply	other threads:[~2026-01-27 20:48 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-23 11:56 [RFC PATCH 0/1] add-patch: Allow reworking with a file after deciding on its hunks Abraham Samuel Adekunle
2026-01-23 11:58 ` [RFC PATCH 1/1] add-patch: Allow reworking with a file after deciding on all " Abraham Samuel Adekunle
2026-01-23 16:38   ` Junio C Hamano
2026-01-23 21:43     ` Samuel Abraham
2026-01-27 15:43 ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Abraham Samuel Adekunle
2026-01-27 15:45   ` [PATCH v2 1/1] Allow reworking with a file after deciding on all its hunks Abraham Samuel Adekunle
2026-01-27 20:48     ` Junio C Hamano [this message]
2026-01-28 11:26       ` Samuel Abraham
2026-01-30  9:22         ` Samuel Abraham
2026-01-30 16:29           ` Junio C Hamano
2026-01-30 17:36             ` Samuel Abraham
2026-01-31 19:25           ` Junio C Hamano
2026-02-02 11:14             ` Samuel Abraham
2026-02-02 17:26               ` Junio C Hamano
2026-02-03  9:55                 ` Samuel Abraham
2026-01-27 17:04   ` [PATCH v2 0/1] Allow reworking with a file when making hunk decisions Junio C Hamano
2026-01-28  9:49     ` Samuel Abraham
2026-02-06 15:52   ` [PATCH v3 0/3] introduce new option `rework-with-file` Abraham Samuel Adekunle
2026-02-06 15:54     ` [PATCH v3 1/3] interactive -p: add new `--rework-with-file` flag to interactive machinery Abraham Samuel Adekunle
2026-02-06 18:25       ` Junio C Hamano
2026-02-06 20:21         ` Samuel Abraham
2026-02-06 15:56     ` [PATCH v3 2/3] add-patch: Allow interfile navigation when selecting hunks Abraham Samuel Adekunle
2026-02-06 18:35       ` Junio C Hamano
2026-02-06 20:22         ` Samuel Abraham
2026-02-06 18:54       ` Junio C Hamano
2026-02-06 20:32         ` Samuel Abraham
2026-02-06 19:21       ` Junio C Hamano
2026-02-06 20:37         ` Samuel Abraham
2026-02-12 10:32         ` Samuel Abraham
2026-02-12 17:25           ` Junio C Hamano
2026-02-12 21:13             ` Samuel Abraham
2026-02-12 21:31               ` Junio C Hamano
2026-02-12 22:20                 ` Samuel Abraham
2026-02-06 15:57     ` [PATCH v3 3/3] add-patch: Allow proper 'git apply' when using the --rework-with-file flag Abraham Samuel Adekunle
2026-02-06 19:02       ` Junio C Hamano
2026-02-06 20:39         ` Samuel Abraham
2026-02-06 19:19     ` [PATCH v3 0/3] introduce new option `rework-with-file` Junio C Hamano
2026-02-06 20:40       ` Samuel Abraham
2026-02-13 22:08     ` [PATCH v4 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle
2026-02-13 22:09       ` [PATCH v4 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle
2026-02-13 23:04         ` Junio C Hamano
2026-02-14  9:16           ` Samuel Abraham
2026-02-13 22:10       ` [PATCH v4 2/4] add-patch: modify patch_update_file() signature Abraham Samuel Adekunle
2026-02-13 23:33         ` Junio C Hamano
2026-02-14 10:14           ` Samuel Abraham
2026-02-13 22:11       ` [PATCH v4 3/4] add-patch: allow all-or-none application of patches Abraham Samuel Adekunle
2026-02-13 22:12       ` [PATCH v4 4/4] add-patch: allow interfile navigation when selecting hunks Abraham Samuel Adekunle
2026-02-14 11:01       ` [PATCH v5 0/4] introduce new option `--auto-advance` Abraham Samuel Adekunle
2026-02-14 11:03         ` [PATCH v5 1/4] interactive -p: add new `--auto-advance` flag Abraham Samuel Adekunle
2026-02-14 11:04         ` [PATCH v5 2/4] add-patch: modify patch_update_file() signature Abraham Samuel Adekunle
2026-02-14 11:06         ` [PATCH v5 3/4] add-patch: allow all-or-none application of patches Abraham Samuel Adekunle
2026-02-14 11:06         ` [PATCH v5 4/4] add-patch: allow interfile navigation when selecting hunks Abraham Samuel Adekunle
2026-02-20 22:32         ` [PATCH v5 0/4] introduce new option `--auto-advance` Junio C Hamano
2026-02-21  9:06           ` Samuel Abraham

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=xmqq7bt2g4tl.fsf@gitster.g \
    --to=gitster@pobox.com \
    --cc=abrahamadekunle50@gmail.com \
    --cc=ben.knoble@gmail.com \
    --cc=christian.couder@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=kristofferhaugsbakk@fastmail.com \
    --cc=phillip.wood123@gmail.com \
    --cc=ps@pks.im \
    --cc=szeder.dev@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox