Skip to content

Commit 5bb67fb

Browse files
committed
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
2 parents 493f462 + 5bdedac commit 5bb67fb

File tree

10 files changed

+230
-167
lines changed

10 files changed

+230
-167
lines changed

Documentation/git-checkout.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ SYNOPSIS
1212
'git checkout' [-q] [-f] [-m] --detach [<branch>]
1313
'git checkout' [-q] [-f] [-m] [--detach] <commit>
1414
'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new-branch>] [<start-point>]
15-
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...
16-
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]
15+
'git checkout' [-f] <tree-ish> [--] <pathspec>...
16+
'git checkout' [-f] <tree-ish> --pathspec-from-file=<file> [--pathspec-file-nul]
17+
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [--] <pathspec>...
18+
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] --pathspec-from-file=<file> [--pathspec-file-nul]
1719
'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]
1820

1921
DESCRIPTION
@@ -260,7 +262,8 @@ and mark the resolved paths with `git add` (or `git rm` if the merge
260262
should result in deletion of the path).
261263
+
262264
When checking out paths from the index, this option lets you recreate
263-
the conflicted merge in the specified paths.
265+
the conflicted merge in the specified paths. This option cannot be
266+
used when checking out paths from a tree-ish.
264267
+
265268
When switching branches with `--merge`, staged changes may be lost.
266269

Documentation/git-restore.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ all modified paths.
7878
--theirs::
7979
When restoring files in the working tree from the index, use
8080
stage #2 ('ours') or #3 ('theirs') for unmerged paths.
81+
This option cannot be used when checking out paths from a
82+
tree-ish (i.e. with the `--source` option).
8183
+
8284
Note that during `git rebase` and `git pull --rebase`, 'ours' and
8385
'theirs' may appear swapped. See the explanation of the same options
@@ -87,6 +89,8 @@ in linkgit:git-checkout[1] for details.
8789
--merge::
8890
When restoring files on the working tree from the index,
8991
recreate the conflicted merge in the unmerged paths.
92+
This option cannot be used when checking out paths from a
93+
tree-ish (i.e. with the `--source` option).
9094

9195
--conflict=<style>::
9296
The same as `--merge` option above, but changes the way the

builtin/checkout.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,15 @@ static int checkout_paths(const struct checkout_opts *opts,
523523
"--merge", "--conflict", "--staged");
524524
}
525525

526+
/*
527+
* recreating unmerged index entries and writing out data from
528+
* unmerged index entries would make no sense when checking out
529+
* of a tree-ish.
530+
*/
531+
if ((opts->merge || opts->writeout_stage) && opts->source_tree)
532+
die(_("'%s', '%s', or '%s' cannot be used when checking out of a tree"),
533+
"--merge", "--ours", "--theirs");
534+
526535
if (opts->patch_mode) {
527536
enum add_p_mode patch_mode;
528537
const char *rev = new_branch_info->name;
@@ -560,6 +569,8 @@ static int checkout_paths(const struct checkout_opts *opts,
560569

561570
if (opts->source_tree)
562571
read_tree_some(opts->source_tree, &opts->pathspec);
572+
if (opts->merge)
573+
unmerge_index(&the_index, &opts->pathspec, CE_MATCHED);
563574

564575
ps_matched = xcalloc(opts->pathspec.nr, 1);
565576

@@ -583,10 +594,6 @@ static int checkout_paths(const struct checkout_opts *opts,
583594
}
584595
free(ps_matched);
585596

586-
/* "checkout -m path" to recreate conflicted state */
587-
if (opts->merge)
588-
unmerge_marked_index(&the_index);
589-
590597
/* Any unmerged paths? */
591598
for (pos = 0; pos < the_index.cache_nr; pos++) {
592599
const struct cache_entry *ce = the_index.cache[pos];

builtin/update-index.c

Lines changed: 12 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -609,9 +609,6 @@ static const char * const update_index_usage[] = {
609609
NULL
610610
};
611611

612-
static struct object_id head_oid;
613-
static struct object_id merge_head_oid;
614-
615612
static struct cache_entry *read_one_ent(const char *which,
616613
struct object_id *ent, const char *path,
617614
int namelen, int stage)
@@ -642,84 +639,17 @@ static struct cache_entry *read_one_ent(const char *which,
642639

643640
static int unresolve_one(const char *path)
644641
{
645-
int namelen = strlen(path);
646-
int pos;
647-
int ret = 0;
648-
struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
649-
650-
/* See if there is such entry in the index. */
651-
pos = index_name_pos(&the_index, path, namelen);
652-
if (0 <= pos) {
653-
/* already merged */
654-
pos = unmerge_index_entry_at(&the_index, pos);
655-
if (pos < the_index.cache_nr) {
656-
const struct cache_entry *ce = the_index.cache[pos];
657-
if (ce_stage(ce) &&
658-
ce_namelen(ce) == namelen &&
659-
!memcmp(ce->name, path, namelen))
660-
return 0;
661-
}
662-
/* no resolve-undo information; fall back */
663-
} else {
664-
/* If there isn't, either it is unmerged, or
665-
* resolved as "removed" by mistake. We do not
666-
* want to do anything in the former case.
667-
*/
668-
pos = -pos-1;
669-
if (pos < the_index.cache_nr) {
670-
const struct cache_entry *ce = the_index.cache[pos];
671-
if (ce_namelen(ce) == namelen &&
672-
!memcmp(ce->name, path, namelen)) {
673-
fprintf(stderr,
674-
"%s: skipping still unmerged path.\n",
675-
path);
676-
goto free_return;
677-
}
678-
}
679-
}
680-
681-
/* Grab blobs from given path from HEAD and MERGE_HEAD,
682-
* stuff HEAD version in stage #2,
683-
* stuff MERGE_HEAD version in stage #3.
684-
*/
685-
ce_2 = read_one_ent("our", &head_oid, path, namelen, 2);
686-
ce_3 = read_one_ent("their", &merge_head_oid, path, namelen, 3);
687-
688-
if (!ce_2 || !ce_3) {
689-
ret = -1;
690-
goto free_return;
691-
}
692-
if (oideq(&ce_2->oid, &ce_3->oid) &&
693-
ce_2->ce_mode == ce_3->ce_mode) {
694-
fprintf(stderr, "%s: identical in both, skipping.\n",
695-
path);
696-
goto free_return;
697-
}
698-
699-
remove_file_from_index(&the_index, path);
700-
if (add_index_entry(&the_index, ce_2, ADD_CACHE_OK_TO_ADD)) {
701-
error("%s: cannot add our version to the index.", path);
702-
ret = -1;
703-
goto free_return;
704-
}
705-
if (!add_index_entry(&the_index, ce_3, ADD_CACHE_OK_TO_ADD))
706-
return 0;
707-
error("%s: cannot add their version to the index.", path);
708-
ret = -1;
709-
free_return:
710-
discard_cache_entry(ce_2);
711-
discard_cache_entry(ce_3);
712-
return ret;
713-
}
714-
715-
static void read_head_pointers(void)
716-
{
717-
if (read_ref("HEAD", &head_oid))
718-
die("No HEAD -- no initial commit yet?");
719-
if (read_ref("MERGE_HEAD", &merge_head_oid)) {
720-
fprintf(stderr, "Not in the middle of a merge.\n");
721-
exit(0);
722-
}
642+
struct string_list_item *item;
643+
int res = 0;
644+
645+
if (!the_index.resolve_undo)
646+
return res;
647+
item = string_list_lookup(the_index.resolve_undo, path);
648+
if (!item)
649+
return res; /* no resolve-undo record for the path */
650+
res = unmerge_index_entry(&the_index, path, item->util, 0);
651+
FREE_AND_NULL(item->util);
652+
return res;
723653
}
724654

725655
static int do_unresolve(int ac, const char **av,
@@ -728,11 +658,6 @@ static int do_unresolve(int ac, const char **av,
728658
int i;
729659
int err = 0;
730660

731-
/* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
732-
* are not doing a merge, so exit with success status.
733-
*/
734-
read_head_pointers();
735-
736661
for (i = 1; i < ac; i++) {
737662
const char *arg = av[i];
738663
char *p = prefix_path(prefix, prefix_length, arg);
@@ -751,6 +676,7 @@ static int do_reupdate(const char **paths,
751676
int pos;
752677
int has_head = 1;
753678
struct pathspec pathspec;
679+
struct object_id head_oid;
754680

755681
parse_pathspec(&pathspec, 0,
756682
PATHSPEC_PREFER_CWD,

rerere.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,7 @@ int rerere_forget(struct repository *r, struct pathspec *pathspec)
11121112
* recover the original conflicted state and then
11131113
* find the conflicted paths.
11141114
*/
1115-
unmerge_index(r->index, pathspec);
1115+
unmerge_index(r->index, pathspec, 0);
11161116
find_conflict(r, &conflict);
11171117
for (i = 0; i < conflict.nr; i++) {
11181118
struct string_list_item *it = &conflict.items[i];

resolve-undo.c

Lines changed: 37 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -117,86 +117,59 @@ void resolve_undo_clear_index(struct index_state *istate)
117117
istate->cache_changed |= RESOLVE_UNDO_CHANGED;
118118
}
119119

120-
int unmerge_index_entry_at(struct index_state *istate, int pos)
120+
int unmerge_index_entry(struct index_state *istate, const char *path,
121+
struct resolve_undo_info *ru, unsigned ce_flags)
121122
{
122-
const struct cache_entry *ce;
123-
struct string_list_item *item;
124-
struct resolve_undo_info *ru;
125-
int i, err = 0, matched;
126-
char *name;
127-
128-
if (!istate->resolve_undo)
129-
return pos;
130-
131-
ce = istate->cache[pos];
132-
if (ce_stage(ce)) {
133-
/* already unmerged */
134-
while ((pos < istate->cache_nr) &&
135-
! strcmp(istate->cache[pos]->name, ce->name))
136-
pos++;
137-
return pos - 1; /* return the last entry processed */
123+
int i = index_name_pos(istate, path, strlen(path));
124+
125+
if (i < 0) {
126+
/* unmerged? */
127+
i = -i - 1;
128+
if (i < istate->cache_nr &&
129+
!strcmp(istate->cache[i]->name, path))
130+
/* yes, it is already unmerged */
131+
return 0;
132+
/* fallthru: resolved to removal */
133+
} else {
134+
/* merged - remove it to replace it with unmerged entries */
135+
remove_index_entry_at(istate, i);
138136
}
139-
item = string_list_lookup(istate->resolve_undo, ce->name);
140-
if (!item)
141-
return pos;
142-
ru = item->util;
143-
if (!ru)
144-
return pos;
145-
matched = ce->ce_flags & CE_MATCHED;
146-
name = xstrdup(ce->name);
147-
remove_index_entry_at(istate, pos);
137+
148138
for (i = 0; i < 3; i++) {
149-
struct cache_entry *nce;
139+
struct cache_entry *ce;
150140
if (!ru->mode[i])
151141
continue;
152-
nce = make_cache_entry(istate,
153-
ru->mode[i],
154-
&ru->oid[i],
155-
name, i + 1, 0);
156-
if (matched)
157-
nce->ce_flags |= CE_MATCHED;
158-
if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) {
159-
err = 1;
160-
error("cannot unmerge '%s'", name);
161-
}
142+
ce = make_cache_entry(istate, ru->mode[i], &ru->oid[i],
143+
path, i + 1, 0);
144+
ce->ce_flags |= ce_flags;
145+
if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD))
146+
return error("cannot unmerge '%s'", path);
162147
}
163-
free(name);
164-
if (err)
165-
return pos;
166-
free(ru);
167-
item->util = NULL;
168-
return unmerge_index_entry_at(istate, pos);
148+
return 0;
169149
}
170150

171-
void unmerge_marked_index(struct index_state *istate)
151+
void unmerge_index(struct index_state *istate, const struct pathspec *pathspec,
152+
unsigned ce_flags)
172153
{
173-
int i;
154+
struct string_list_item *item;
174155

175156
if (!istate->resolve_undo)
176157
return;
177158

178159
/* TODO: audit for interaction with sparse-index. */
179160
ensure_full_index(istate);
180-
for (i = 0; i < istate->cache_nr; i++) {
181-
const struct cache_entry *ce = istate->cache[i];
182-
if (ce->ce_flags & CE_MATCHED)
183-
i = unmerge_index_entry_at(istate, i);
184-
}
185-
}
186161

187-
void unmerge_index(struct index_state *istate, const struct pathspec *pathspec)
188-
{
189-
int i;
190-
191-
if (!istate->resolve_undo)
192-
return;
193-
194-
/* TODO: audit for interaction with sparse-index. */
195-
ensure_full_index(istate);
196-
for (i = 0; i < istate->cache_nr; i++) {
197-
const struct cache_entry *ce = istate->cache[i];
198-
if (!ce_path_match(istate, ce, pathspec, NULL))
162+
for_each_string_list_item(item, istate->resolve_undo) {
163+
const char *path = item->string;
164+
struct resolve_undo_info *ru = item->util;
165+
if (!item->util)
166+
continue;
167+
if (!match_pathspec(istate, pathspec,
168+
item->string, strlen(item->string),
169+
0, NULL, 0))
199170
continue;
200-
i = unmerge_index_entry_at(istate, i);
171+
unmerge_index_entry(istate, path, ru, ce_flags);
172+
free(ru);
173+
item->util = NULL;
201174
}
202175
}

resolve-undo.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ void record_resolve_undo(struct index_state *, struct cache_entry *);
1717
void resolve_undo_write(struct strbuf *, struct string_list *);
1818
struct string_list *resolve_undo_read(const char *, unsigned long);
1919
void resolve_undo_clear_index(struct index_state *);
20-
int unmerge_index_entry_at(struct index_state *, int);
21-
void unmerge_index(struct index_state *, const struct pathspec *);
22-
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);
2322

2423
#endif

0 commit comments

Comments
 (0)