Merge branch 'jc/unresolve-removal'

"checkout --merge -- path" and "update-index --unresolve path" did
not resurrect conflicted state that was resolved to remove path,
but now they do.

* jc/unresolve-removal:
  checkout: allow "checkout -m path" to unmerge removed paths
  checkout/restore: add basic tests for --merge
  checkout/restore: refuse unmerging paths unless checking out of the index
  update-index: remove stale fallback code for "--unresolve"
  update-index: use unmerge_index_entry() to support removal
  resolve-undo: allow resurrecting conflicted state that resolved to deletion
  update-index: do not read HEAD and MERGE_HEAD unconditionally
This commit is contained in:
Junio C Hamano
2023-10-02 11:20:00 -07:00
10 changed files with 228 additions and 165 deletions

View File

@ -609,9 +609,6 @@ static const char * const update_index_usage[] = {
NULL
};
static struct object_id head_oid;
static struct object_id merge_head_oid;
static struct cache_entry *read_one_ent(const char *which,
struct object_id *ent, const char *path,
int namelen, int stage)
@ -642,84 +639,17 @@ static struct cache_entry *read_one_ent(const char *which,
static int unresolve_one(const char *path)
{
int namelen = strlen(path);
int pos;
int ret = 0;
struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
struct string_list_item *item;
int res = 0;
/* See if there is such entry in the index. */
pos = index_name_pos(&the_index, path, namelen);
if (0 <= pos) {
/* already merged */
pos = unmerge_index_entry_at(&the_index, pos);
if (pos < the_index.cache_nr) {
const struct cache_entry *ce = the_index.cache[pos];
if (ce_stage(ce) &&
ce_namelen(ce) == namelen &&
!memcmp(ce->name, path, namelen))
return 0;
}
/* no resolve-undo information; fall back */
} else {
/* If there isn't, either it is unmerged, or
* resolved as "removed" by mistake. We do not
* want to do anything in the former case.
*/
pos = -pos-1;
if (pos < the_index.cache_nr) {
const struct cache_entry *ce = the_index.cache[pos];
if (ce_namelen(ce) == namelen &&
!memcmp(ce->name, path, namelen)) {
fprintf(stderr,
"%s: skipping still unmerged path.\n",
path);
goto free_return;
}
}
}
/* Grab blobs from given path from HEAD and MERGE_HEAD,
* stuff HEAD version in stage #2,
* stuff MERGE_HEAD version in stage #3.
*/
ce_2 = read_one_ent("our", &head_oid, path, namelen, 2);
ce_3 = read_one_ent("their", &merge_head_oid, path, namelen, 3);
if (!ce_2 || !ce_3) {
ret = -1;
goto free_return;
}
if (oideq(&ce_2->oid, &ce_3->oid) &&
ce_2->ce_mode == ce_3->ce_mode) {
fprintf(stderr, "%s: identical in both, skipping.\n",
path);
goto free_return;
}
remove_file_from_index(&the_index, path);
if (add_index_entry(&the_index, ce_2, ADD_CACHE_OK_TO_ADD)) {
error("%s: cannot add our version to the index.", path);
ret = -1;
goto free_return;
}
if (!add_index_entry(&the_index, ce_3, ADD_CACHE_OK_TO_ADD))
return 0;
error("%s: cannot add their version to the index.", path);
ret = -1;
free_return:
discard_cache_entry(ce_2);
discard_cache_entry(ce_3);
return ret;
}
static void read_head_pointers(void)
{
if (read_ref("HEAD", &head_oid))
die("No HEAD -- no initial commit yet?");
if (read_ref("MERGE_HEAD", &merge_head_oid)) {
fprintf(stderr, "Not in the middle of a merge.\n");
exit(0);
}
if (!the_index.resolve_undo)
return res;
item = string_list_lookup(the_index.resolve_undo, path);
if (!item)
return res; /* no resolve-undo record for the path */
res = unmerge_index_entry(&the_index, path, item->util, 0);
FREE_AND_NULL(item->util);
return res;
}
static int do_unresolve(int ac, const char **av,
@ -728,11 +658,6 @@ static int do_unresolve(int ac, const char **av,
int i;
int err = 0;
/* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
* are not doing a merge, so exit with success status.
*/
read_head_pointers();
for (i = 1; i < ac; i++) {
const char *arg = av[i];
char *p = prefix_path(prefix, prefix_length, arg);
@ -751,6 +676,7 @@ static int do_reupdate(const char **paths,
int pos;
int has_head = 1;
struct pathspec pathspec;
struct object_id head_oid;
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD,