git/list[1] front-page[2] threads[3] people[4] search[5] about
wed 2026-10-07 17:27 UTC

[PATCH 4/8] add-patch: add support for in-memory index patching

From
Patrick Steinhardt <ps@pks.im>
Date
Mar 2, 2026, 12:13 UTC
Message-ID
<20260302-pks-history-split-v1-4-444fc987a324@pks.im>
In-Reply-To
<20260302-pks-history-split-v1-0-444fc987a324@pks.im>

With `run_add_p()` callers have the ability to apply changes from a specific revision to a repository's index. This infra supports several different modes, like for example applying changes to the index, working tree or both.

One feature that is missing though is the ability to apply changes to an in-memory index different from the repository's index. Add a new function `run_add_p_index()` to plug this gap.

This new function will be used in a subsequent commit.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 add-patch.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++----------
 add-patch.h |   8 ++++
 2 files changed, 132 insertions(+), 25 deletions(-)
diff --git a/add-patch.c b/add-patch.c
index 4f089c82d0..b4dc7d2293 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -4,11 +4,13 @@
 #include "git-compat-util.h"
 #include "add-patch.h"
 #include "advice.h"
+#include "commit.h"
 #include "config.h"
 #include "diff.h"
 #include "editor.h"
 #include "environment.h"
 #include "gettext.h"
+#include "hex.h"
 #include "object-name.h"
 #include "pager.h"
 #include "read-cache-ll.h"
@@ -263,6 +265,8 @@ struct hunk {
 
 struct add_p_state {
 	struct repository *r;
+	struct index_state *index;
+	const char *index_file;
 	struct interactive_config cfg;
 	struct strbuf answer, buf;
 
@@ -438,7 +442,7 @@ static void setup_child_process(struct add_p_state *s,
 
 	cp->git_cmd = 1;
 	strvec_pushf(&cp->env,
-		     INDEX_ENVIRONMENT "=%s", s->r->index_file);
+		     INDEX_ENVIRONMENT "=%s", s->index_file);
 }
 
 static int parse_range(const char **p,
@@ -1559,7 +1563,7 @@ static void apply_patch(struct add_p_state *s, struct file_diff *file_diff)
 		strbuf_reset(&s->buf);
 		reassemble_patch(s, file_diff, 0, &s->buf);
 
-		discard_index(s->r->index);
+		discard_index(s->index);
 		if (s->mode->apply_for_checkout)
 			apply_for_checkout(s, &s->buf,
 					s->mode->is_reverse);
@@ -1570,9 +1574,11 @@ static void apply_patch(struct add_p_state *s, struct file_diff *file_diff)
 					NULL, 0, NULL, 0))
 				error(_("'git apply' failed"));
 		}
-		if (repo_read_index(s->r) >= 0)
+		if (read_index_from(s->index, s->index_file, s->r->gitdir) >= 0 &&
+		    s->index == s->r->index) {
 			repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0,
 						     1, NULL, NULL, NULL);
+		}
 	}
 
 }
@@ -1996,18 +2002,51 @@ static size_t patch_update_file(struct add_p_state *s, size_t idx)
 	return patch_update_resp;
 }
 
+static int run_add_p_common(struct add_p_state *state,
+			    const struct pathspec *ps)
+{
+	size_t binary_count = 0;
+	size_t i;
+
+	if (parse_diff(state, ps) < 0)
+		return -1;
+
+	for (i = 0; i < state->file_diff_nr;) {
+		if (state->file_diff[i].binary && !state->file_diff[i].hunk_nr) {
+			binary_count++;
+			i++;
+			continue;
+		}
+		if ((i = patch_update_file(state, i)) == state->file_diff_nr)
+			break;
+	}
+
+	if (!state->cfg.auto_advance)
+		for (i = 0; i < state->file_diff_nr; i++)
+			apply_patch(state, state->file_diff + i);
+
+	if (state->file_diff_nr == 0)
+		err(state, _("No changes."));
+	else if (binary_count == state->file_diff_nr)
+		err(state, _("Only binary files changed."));
+
+	return 0;
+}
+
 int run_add_p(struct repository *r, enum add_p_mode mode,
 	      struct interactive_options *opts, const char *revision,
 	      const struct pathspec *ps)
 {
 	struct add_p_state s = {
 		.r = r,
+		.index = r->index,
+		.index_file = r->index_file,
 		.answer = STRBUF_INIT,
 		.buf = STRBUF_INIT,
 		.plain = STRBUF_INIT,
 		.colored = STRBUF_INIT,
 	};
-	size_t i, binary_count = 0;
+	int ret;
 
 	interactive_config_init(&s.cfg, r, opts);
 
@@ -2040,30 +2079,90 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
 	if (repo_read_index(r) < 0 ||
 	    (!s.mode->index_only &&
 	     repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
-					  NULL, NULL, NULL) < 0) ||
-	    parse_diff(&s, ps) < 0) {
-		add_p_state_clear(&s);
-		return -1;
+					  NULL, NULL, NULL) < 0)) {
+		ret = -1;
+		goto out;
 	}
 
-	for (i = 0; i < s.file_diff_nr;) {
-		if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) {
-			binary_count++;
-			i++;
-			continue;
-		}
-		 if ((i = patch_update_file(&s, i)) == s.file_diff_nr)
-			break;
-    }
-	if (!s.cfg.auto_advance)
-		for (i = 0; i < s.file_diff_nr; i++)
-			apply_patch(&s, s.file_diff + i);
+	ret = run_add_p_common(&s, ps);
+	if (ret < 0)
+		goto out;
 
-	if (s.file_diff_nr == 0)
-		err(&s, _("No changes."));
-	else if (binary_count == s.file_diff_nr)
-		err(&s, _("Only binary files changed."));
+	ret = 0;
 
+out:
 	add_p_state_clear(&s);
-	return 0;
+	return ret;
+}
+
+int run_add_p_index(struct repository *r,
+		    struct index_state *index,
+		    const char *index_file,
+		    struct interactive_options *opts,
+		    const char *revision,
+		    const struct pathspec *ps)
+{
+	struct patch_mode mode = {
+		.apply_args = { "--cached", NULL },
+		.apply_check_args = { "--cached", NULL },
+		.prompt_mode = {
+			N_("Stage mode change [y,n,q,a,d%s,?]? "),
+			N_("Stage deletion [y,n,q,a,d%s,?]? "),
+			N_("Stage addition [y,n,q,a,d%s,?]? "),
+			N_("Stage this hunk [y,n,q,a,d%s,?]? ")
+		},
+		.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+				     "will immediately be marked for staging."),
+		.help_patch_text =
+			N_("y - stage this hunk\n"
+			   "n - do not stage this hunk\n"
+			   "q - quit; do not stage this hunk or any of the remaining "
+				"ones\n"
+			   "a - stage this hunk and all later hunks in the file\n"
+			   "d - do not stage this hunk or any of the later hunks in "
+				"the file\n"),
+		.index_only = 1,
+	};
+	struct add_p_state s = {
+		.r = r,
+		.index = index,
+		.index_file = index_file,
+		.answer = STRBUF_INIT,
+		.buf = STRBUF_INIT,
+		.plain = STRBUF_INIT,
+		.colored = STRBUF_INIT,
+		.mode = &mode,
+		.revision = revision,
+	};
+	char parent_tree_oid[GIT_MAX_HEXSZ + 1];
+	struct commit *commit;
+	int ret;
+
+	interactive_config_init(&s.cfg, r, opts);
+
+	commit = lookup_commit_reference_by_name(revision);
+	if (!commit) {
+		err(&s, _("Revision does not refer to a commit"));
+		ret = -1;
+		goto out;
+	}
+
+	if (commit->parents)
+		oid_to_hex_r(parent_tree_oid, get_commit_tree_oid(commit->parents->item));
+	else
+		oid_to_hex_r(parent_tree_oid, r->hash_algo->empty_tree);
+
+	mode.diff_cmd[0] = "diff-tree";
+	mode.diff_cmd[1] = "-r";
+	mode.diff_cmd[2] = parent_tree_oid;
+
+	ret = run_add_p_common(&s, ps);
+	if (ret < 0)
+		goto out;
+
+	ret = 0;
+
+out:
+	add_p_state_clear(&s);
+	return ret;
 }
diff --git a/add-patch.h b/add-patch.h
index e6868c60a2..cf2a31a40f 100644
--- a/add-patch.h
+++ b/add-patch.h
@@ -3,6 +3,7 @@
 
 #include "color.h"
 
+struct index_state;
 struct pathspec;
 struct repository;
 
@@ -56,4 +57,11 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
 	      struct interactive_options *opts, const char *revision,
 	      const struct pathspec *ps);
 
+int run_add_p_index(struct repository *r,
+		    struct index_state *index,
+		    const char *index_file,
+		    struct interactive_options *opts,
+		    const char *revision,
+		    const struct pathspec *ps);
+
 #endif
-- 
2.53.0.697.g625c4fb2da.dirty
Previous: Patrick SteinhardtNext: Patrick Steinhardt
Message 5 of 16 in “history: introduce "split" subcommand”
  1. 0/8 history: introduce "split" subcommandPatrick Steinhardt, Mar 2, 2026
  2. 1/8 add-patch: split out header from "add-interactive.h"Patrick Steinhardt, Mar 2, 2026
  3. 2/8 add-patch: split out `struct interactive_options`Patrick Steinhardt, Mar 2, 2026
  4. 3/8 add-patch: remove dependency on "add-interactive" subsystemPatrick Steinhardt, Mar 2, 2026
  5. 4/8 add-patch: add support for in-memory index patchingPatrick Steinhardt, Mar 2, 2026
  6. 5/8 add-patch: allow disabling editing of hunksPatrick Steinhardt, Mar 2, 2026
  7. 6/8 cache-tree: allow writing in-memory index as treePatrick Steinhardt, Mar 2, 2026
  8. 7/8 builtin/history: split out extended function to create commitsPatrick Steinhardt, Mar 2, 2026
  9. 8/8 builtin/history: implement "split" subcommandPatrick Steinhardt, Mar 2, 2026
  10. D. Ben KnobleMar 3, 2026
  11. D. Ben KnobleMar 3, 2026
  12. D. Ben KnobleMar 3, 2026
  13. Patrick SteinhardtMar 11, 2026
  14. Patrick SteinhardtMar 11, 2026
  15. Junio C HamanoMar 13, 2026
  16. Patrick SteinhardtMar 16, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.