add-patch: add support for in-memory index patching

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> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Mar 2, 2026 at 13:13 UTC 0c5583a57d618db191afceeff54e8cafbee89f41
2 files changed +132 -25
add-patch.c
+124 -25
@@ -4,11 +4,13 @@
4 #include "git-compat-util.h"
5 #include "add-patch.h"
6 #include "advice.h"
7 +#include "commit.h"
8 #include "config.h"
9 #include "diff.h"
10 #include "editor.h"
11 #include "environment.h"
12 #include "gettext.h"
13 +#include "hex.h"
14 #include "object-name.h"
15 #include "pager.h"
16 #include "read-cache-ll.h"
@@ -263,6 +265,8 @@ struct hunk {
265
266 struct add_p_state {
267 struct repository *r;
268 + struct index_state *index;
269 + const char *index_file;
270 struct interactive_config cfg;
271 struct strbuf answer, buf;
272
@@ -438,7 +442,7 @@ static void setup_child_process(struct add_p_state *s,
442
443 cp->git_cmd = 1;
444 strvec_pushf(&cp->env,
441 - INDEX_ENVIRONMENT "=%s", s->r->index_file);
445 + INDEX_ENVIRONMENT "=%s", s->index_file);
446 }
447
448 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)
1563 strbuf_reset(&s->buf);
1564 reassemble_patch(s, file_diff, 0, &s->buf);
1565
1562 - discard_index(s->r->index);
1566 + discard_index(s->index);
1567 if (s->mode->apply_for_checkout)
1568 apply_for_checkout(s, &s->buf,
1569 s->mode->is_reverse);
@@ -1570,9 +1574,11 @@ static void apply_patch(struct add_p_state *s, struct file_diff *file_diff)
1574 NULL, 0, NULL, 0))
1575 error(_("'git apply' failed"));
1576 }
1573 - if (repo_read_index(s->r) >= 0)
1577 + if (read_index_from(s->index, s->index_file, s->r->gitdir) >= 0 &&
1578 + s->index == s->r->index) {
1579 repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0,
1580 1, NULL, NULL, NULL);
1581 + }
1582 }
1583
1584 }
@@ -1996,18 +2002,51 @@ soft_increment:
2002 return patch_update_resp;
2003 }
2004
2005 +static int run_add_p_common(struct add_p_state *state,
2006 + const struct pathspec *ps)
2007 +{
2008 + size_t binary_count = 0;
2009 + size_t i;
2010 +
2011 + if (parse_diff(state, ps) < 0)
2012 + return -1;
2013 +
2014 + for (i = 0; i < state->file_diff_nr;) {
2015 + if (state->file_diff[i].binary && !state->file_diff[i].hunk_nr) {
2016 + binary_count++;
2017 + i++;
2018 + continue;
2019 + }
2020 + if ((i = patch_update_file(state, i)) == state->file_diff_nr)
2021 + break;
2022 + }
2023 +
2024 + if (!state->cfg.auto_advance)
2025 + for (i = 0; i < state->file_diff_nr; i++)
2026 + apply_patch(state, state->file_diff + i);
2027 +
2028 + if (state->file_diff_nr == 0)
2029 + err(state, _("No changes."));
2030 + else if (binary_count == state->file_diff_nr)
2031 + err(state, _("Only binary files changed."));
2032 +
2033 + return 0;
2034 +}
2035 +
2036 int run_add_p(struct repository *r, enum add_p_mode mode,
2037 struct interactive_options *opts, const char *revision,
2038 const struct pathspec *ps)
2039 {
2040 struct add_p_state s = {
2041 .r = r,
2042 + .index = r->index,
2043 + .index_file = r->index_file,
2044 .answer = STRBUF_INIT,
2045 .buf = STRBUF_INIT,
2046 .plain = STRBUF_INIT,
2047 .colored = STRBUF_INIT,
2048 };
2010 - size_t i, binary_count = 0;
2049 + int ret;
2050
2051 interactive_config_init(&s.cfg, r, opts);
2052
@@ -2040,30 +2079,90 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
2079 if (repo_read_index(r) < 0 ||
2080 (!s.mode->index_only &&
2081 repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
2043 - NULL, NULL, NULL) < 0) ||
2044 - parse_diff(&s, ps) < 0) {
2045 - add_p_state_clear(&s);
2046 - return -1;
2082 + NULL, NULL, NULL) < 0)) {
2083 + ret = -1;
2084 + goto out;
2085 }
2086
2049 - for (i = 0; i < s.file_diff_nr;) {
2050 - if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) {
2051 - binary_count++;
2052 - i++;
2053 - continue;
2054 - }
2055 - if ((i = patch_update_file(&s, i)) == s.file_diff_nr)
2056 - break;
2057 - }
2058 - if (!s.cfg.auto_advance)
2059 - for (i = 0; i < s.file_diff_nr; i++)
2060 - apply_patch(&s, s.file_diff + i);
2087 + ret = run_add_p_common(&s, ps);
2088 + if (ret < 0)
2089 + goto out;
2090
2062 - if (s.file_diff_nr == 0)
2063 - err(&s, _("No changes."));
2064 - else if (binary_count == s.file_diff_nr)
2065 - err(&s, _("Only binary files changed."));
2091 + ret = 0;
2092
2093 +out:
2094 add_p_state_clear(&s);
2068 - return 0;
2095 + return ret;
2096 +}
2097 +
2098 +int run_add_p_index(struct repository *r,
2099 + struct index_state *index,
2100 + const char *index_file,
2101 + struct interactive_options *opts,
2102 + const char *revision,
2103 + const struct pathspec *ps)
2104 +{
2105 + struct patch_mode mode = {
2106 + .apply_args = { "--cached", NULL },
2107 + .apply_check_args = { "--cached", NULL },
2108 + .prompt_mode = {
2109 + N_("Stage mode change [y,n,q,a,d%s,?]? "),
2110 + N_("Stage deletion [y,n,q,a,d%s,?]? "),
2111 + N_("Stage addition [y,n,q,a,d%s,?]? "),
2112 + N_("Stage this hunk [y,n,q,a,d%s,?]? ")
2113 + },
2114 + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
2115 + "will immediately be marked for staging."),
2116 + .help_patch_text =
2117 + N_("y - stage this hunk\n"
2118 + "n - do not stage this hunk\n"
2119 + "q - quit; do not stage this hunk or any of the remaining "
2120 + "ones\n"
2121 + "a - stage this hunk and all later hunks in the file\n"
2122 + "d - do not stage this hunk or any of the later hunks in "
2123 + "the file\n"),
2124 + .index_only = 1,
2125 + };
2126 + struct add_p_state s = {
2127 + .r = r,
2128 + .index = index,
2129 + .index_file = index_file,
2130 + .answer = STRBUF_INIT,
2131 + .buf = STRBUF_INIT,
2132 + .plain = STRBUF_INIT,
2133 + .colored = STRBUF_INIT,
2134 + .mode = &mode,
2135 + .revision = revision,
2136 + };
2137 + char parent_tree_oid[GIT_MAX_HEXSZ + 1];
2138 + struct commit *commit;
2139 + int ret;
2140 +
2141 + interactive_config_init(&s.cfg, r, opts);
2142 +
2143 + commit = lookup_commit_reference_by_name(revision);
2144 + if (!commit) {
2145 + err(&s, _("Revision does not refer to a commit"));
2146 + ret = -1;
2147 + goto out;
2148 + }
2149 +
2150 + if (commit->parents)
2151 + oid_to_hex_r(parent_tree_oid, get_commit_tree_oid(commit->parents->item));
2152 + else
2153 + oid_to_hex_r(parent_tree_oid, r->hash_algo->empty_tree);
2154 +
2155 + mode.diff_cmd[0] = "diff-tree";
2156 + mode.diff_cmd[1] = "-r";
2157 + mode.diff_cmd[2] = parent_tree_oid;
2158 +
2159 + ret = run_add_p_common(&s, ps);
2160 + if (ret < 0)
2161 + goto out;
2162 +
2163 + ret = 0;
2164 +
2165 +out:
2166 + add_p_state_clear(&s);
2167 + return ret;
2168 }
add-patch.h
+8
@@ -3,6 +3,7 @@
3
4 #include "color.h"
5
6 +struct index_state;
7 struct pathspec;
8 struct repository;
9
@@ -56,4 +57,11 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
57 struct interactive_options *opts, const char *revision,
58 const struct pathspec *ps);
59
60 +int run_add_p_index(struct repository *r,
61 + struct index_state *index,
62 + const char *index_file,
63 + struct interactive_options *opts,
64 + const char *revision,
65 + const struct pathspec *ps);
66 +
67 #endif