checkout: allow "checkout -m path" to unmerge removed paths

"git checkout -m -- path" uses the unmerge_marked_index() API, whose implementation is incapable of unresolving a path that was resolved as removed. Extend the unmerge_index() API function so that we can mark the ce_flags member of the cache entries we add to the index as unmerged, and replace use of unmerge_marked_index() with it. Now, together with its unmerge_index_entry_at() helper function, unmerge_marked_index() function is no longer called by anybody, and can safely be removed. This makes two known test failures in t2070 and t7201 to succeed. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Jul 31, 2023 at 15:44 UTC 5bdedac3c7b0894643256304cd5f0dd29cf8eec9
7 files changed +13 -82
builtin/checkout.c
+2 -4
@@ -566,6 +566,8 @@ static int checkout_paths(const struct checkout_opts *opts,
566
567 if (opts->source_tree)
568 read_tree_some(opts->source_tree, &opts->pathspec);
569 + if (opts->merge)
570 + unmerge_index(&the_index, &opts->pathspec, CE_MATCHED);
571
572 ps_matched = xcalloc(opts->pathspec.nr, 1);
573
@@ -589,10 +591,6 @@ static int checkout_paths(const struct checkout_opts *opts,
591 }
592 free(ps_matched);
593
592 - /* "checkout -m path" to recreate conflicted state */
593 - if (opts->merge)
594 - unmerge_marked_index(&the_index);
595 -
594 /* Any unmerged paths? */
595 for (pos = 0; pos < the_index.cache_nr; pos++) {
596 const struct cache_entry *ce = the_index.cache[pos];
builtin/update-index.c
+1 -1
@@ -646,7 +646,7 @@ static int unresolve_one(const char *path)
646 item = string_list_lookup(the_index.resolve_undo, path);
647 if (!item)
648 return res; /* no resolve-undo record for the path */
649 - res = unmerge_index_entry(&the_index, path, item->util);
649 + res = unmerge_index_entry(&the_index, path, item->util, 0);
650 FREE_AND_NULL(item->util);
651 return res;
652 }
rerere.c
+1 -1
@@ -1112,7 +1112,7 @@ int rerere_forget(struct repository *r, struct pathspec *pathspec)
1112 * recover the original conflicted state and then
1113 * find the conflicted paths.
1114 */
1115 - unmerge_index(r->index, pathspec);
1115 + unmerge_index(r->index, pathspec, 0);
1116 find_conflict(r, &conflict);
1117 for (i = 0; i < conflict.nr; i++) {
1118 struct string_list_item *it = &conflict.items[i];
resolve-undo.c
+5 -70
@@ -115,75 +115,8 @@ void resolve_undo_clear_index(struct index_state *istate)
115 istate->cache_changed |= RESOLVE_UNDO_CHANGED;
116 }
117
118 -int unmerge_index_entry_at(struct index_state *istate, int pos)
119 -{
120 - const struct cache_entry *ce;
121 - struct string_list_item *item;
122 - struct resolve_undo_info *ru;
123 - int i, err = 0, matched;
124 - char *name;
125 -
126 - if (!istate->resolve_undo)
127 - return pos;
128 -
129 - ce = istate->cache[pos];
130 - if (ce_stage(ce)) {
131 - /* already unmerged */
132 - while ((pos < istate->cache_nr) &&
133 - ! strcmp(istate->cache[pos]->name, ce->name))
134 - pos++;
135 - return pos - 1; /* return the last entry processed */
136 - }
137 - item = string_list_lookup(istate->resolve_undo, ce->name);
138 - if (!item)
139 - return pos;
140 - ru = item->util;
141 - if (!ru)
142 - return pos;
143 - matched = ce->ce_flags & CE_MATCHED;
144 - name = xstrdup(ce->name);
145 - remove_index_entry_at(istate, pos);
146 - for (i = 0; i < 3; i++) {
147 - struct cache_entry *nce;
148 - if (!ru->mode[i])
149 - continue;
150 - nce = make_cache_entry(istate,
151 - ru->mode[i],
152 - &ru->oid[i],
153 - name, i + 1, 0);
154 - if (matched)
155 - nce->ce_flags |= CE_MATCHED;
156 - if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) {
157 - err = 1;
158 - error("cannot unmerge '%s'", name);
159 - }
160 - }
161 - free(name);
162 - if (err)
163 - return pos;
164 - free(ru);
165 - item->util = NULL;
166 - return unmerge_index_entry_at(istate, pos);
167 -}
168 -
169 -void unmerge_marked_index(struct index_state *istate)
170 -{
171 - int i;
172 -
173 - if (!istate->resolve_undo)
174 - return;
175 -
176 - /* TODO: audit for interaction with sparse-index. */
177 - ensure_full_index(istate);
178 - for (i = 0; i < istate->cache_nr; i++) {
179 - const struct cache_entry *ce = istate->cache[i];
180 - if (ce->ce_flags & CE_MATCHED)
181 - i = unmerge_index_entry_at(istate, i);
182 - }
183 -}
184 -
118 int unmerge_index_entry(struct index_state *istate, const char *path,
186 - struct resolve_undo_info *ru)
119 + struct resolve_undo_info *ru, unsigned ce_flags)
120 {
121 int i = index_name_pos(istate, path, strlen(path));
122
@@ -206,13 +139,15 @@ int unmerge_index_entry(struct index_state *istate, const char *path,
139 continue;
140 ce = make_cache_entry(istate, ru->mode[i], &ru->oid[i],
141 path, i + 1, 0);
142 + ce->ce_flags |= ce_flags;
143 if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD))
144 return error("cannot unmerge '%s'", path);
145 }
146 return 0;
147 }
148
215 -void unmerge_index(struct index_state *istate, const struct pathspec *pathspec)
149 +void unmerge_index(struct index_state *istate, const struct pathspec *pathspec,
150 + unsigned ce_flags)
151 {
152 struct string_list_item *item;
153
@@ -231,7 +166,7 @@ void unmerge_index(struct index_state *istate, const struct pathspec *pathspec)
166 item->string, strlen(item->string),
167 0, NULL, 0))
168 continue;
234 - unmerge_index_entry(istate, path, ru);
169 + unmerge_index_entry(istate, path, ru, ce_flags);
170 free(ru);
171 item->util = NULL;
172 }
resolve-undo.h
+2 -4
@@ -17,9 +17,7 @@ void record_resolve_undo(struct index_state *, struct cache_entry *);
17 void resolve_undo_write(struct strbuf *, struct string_list *);
18 struct string_list *resolve_undo_read(const char *, unsigned long);
19 void resolve_undo_clear_index(struct index_state *);
20 -int unmerge_index_entry_at(struct index_state *, int);
21 -int unmerge_index_entry(struct index_state *, const char *, struct resolve_undo_info *);
22 -void unmerge_index(struct index_state *, const struct pathspec *);
23 -void unmerge_marked_index(struct index_state *);
20 +int unmerge_index_entry(struct index_state *, const char *, struct resolve_undo_info *, unsigned);
21 +void unmerge_index(struct index_state *, const struct pathspec *, unsigned);
22
23 #endif
t/t2070-restore.sh
+1 -1
@@ -180,7 +180,7 @@ test_expect_success 'restore --merge to unresolve after (mistaken) resolution' '
180 test_cmp expect file
181 '
182
183 -test_expect_failure 'restore --merge to unresolve after (mistaken) resolution' '
183 +test_expect_success 'restore --merge to unresolve after (mistaken) resolution' '
184 O=$(echo original | git hash-object -w --stdin) &&
185 A=$(echo ourside | git hash-object -w --stdin) &&
186 B=$(echo theirside | git hash-object -w --stdin) &&
t/t7201-co.sh
+1 -1
@@ -543,7 +543,7 @@ test_expect_success 'checkout -m works after (mistaken) resolution' '
543 test_cmp merged file
544 '
545
546 -test_expect_failure 'checkout -m works after (mistaken) resolution to remove' '
546 +test_expect_success 'checkout -m works after (mistaken) resolution to remove' '
547 setup_conflicting_index &&
548 echo "none of the above" >sample &&
549 cat sample >fild &&