mirror of
https://github.com/git/git.git
synced 2026-08-08 17:11:48 +00:00
Merge branch 'jc/add-resolved' into jch
'git add' has been taught a new '--resolved' option to stage conflict-resolved paths, while leaving unrelated local changes unstaged. It scans the unmerged paths for leftover conflict markers and aborts if any are found. * jc/add-resolved: add: introduce '--resolved' option read-cache: add remove_file_from_index_with_flags() merge-ll: consolidate conflict marker scanning logic read-cache: reindent
This commit is contained in:
@@ -11,7 +11,7 @@ SYNOPSIS
|
||||
git add [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
|
||||
[--edit | -e] [--[no-]all | -A | --[no-]ignore-removal | [--update | -u]] [--sparse]
|
||||
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
|
||||
[--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
|
||||
[--resolved] [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
|
||||
[--] [<pathspec>...]
|
||||
|
||||
DESCRIPTION
|
||||
@@ -195,6 +195,14 @@ for `git add --no-all <pathspec>...`, i.e. ignored removed files.
|
||||
while a _CRLF_ cleans to _LF_, a _CRCRLF_ sequence is only partially
|
||||
cleaned to _CRLF_.
|
||||
|
||||
`--resolved`::
|
||||
Update the index for unmerged paths matching _<pathspec>_ where
|
||||
no conflict markers remain in the working tree. Unmerged paths
|
||||
without conflict markers (including binary files and file
|
||||
deletions) are staged as resolved, while any path with leftover
|
||||
conflict markers causes the command to refuse to stage any files.
|
||||
Cannot be combined with `-u` or `-A`.
|
||||
|
||||
`--chmod=(+|-)x`::
|
||||
Override the executable bit of the added files. The executable
|
||||
bit is only changed in the index, the files on disk are left
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "strvec.h"
|
||||
#include "submodule.h"
|
||||
#include "add-interactive.h"
|
||||
#include "merge-ll.h"
|
||||
|
||||
static const char * const builtin_add_usage[] = {
|
||||
N_("git add [<options>] [--] <pathspec>..."),
|
||||
@@ -35,6 +36,7 @@ static int patch_interactive, add_interactive, edit_interactive;
|
||||
static struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT;
|
||||
static int take_worktree_changes;
|
||||
static int add_renormalize;
|
||||
static int add_resolved;
|
||||
static int pathspec_file_nul;
|
||||
static int include_sparse;
|
||||
static const char *pathspec_from_file;
|
||||
@@ -265,6 +267,7 @@ static struct option builtin_add_options[] = {
|
||||
OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
|
||||
OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
|
||||
OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")),
|
||||
OPT_BOOL(0, "resolved", &add_resolved, N_("add conflict-resolved tracked files")),
|
||||
OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
|
||||
OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
|
||||
OPT_CALLBACK_F(0, "ignore-removal", &addremove_explicit,
|
||||
@@ -379,6 +382,76 @@ static int add_files(struct repository *repo, struct dir_struct *dir, int flags)
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
static int failed_to_add(int flags, const char *path)
|
||||
{
|
||||
if (!(flags & ADD_CACHE_IGNORE_ERRORS))
|
||||
die(_("updating file '%s' failed"), path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int add_resolved_files(struct repository *repo,
|
||||
const struct pathspec *pathspec,
|
||||
int flags)
|
||||
{
|
||||
struct index_state *istate = repo->index;
|
||||
struct string_list unmerged_paths = STRING_LIST_INIT_DUP;
|
||||
struct string_list unresolved_paths = STRING_LIST_INIT_DUP;
|
||||
int exit_status = 0;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < istate->cache_nr; i++) {
|
||||
struct cache_entry *ce = istate->cache[i];
|
||||
if (!ce_stage(ce))
|
||||
continue;
|
||||
if (pathspec->nr && !ce_path_match(istate, ce, pathspec, NULL))
|
||||
continue;
|
||||
if (!unmerged_paths.nr ||
|
||||
strcmp(unmerged_paths.items[unmerged_paths.nr - 1].string, ce->name))
|
||||
string_list_append(&unmerged_paths, ce->name);
|
||||
}
|
||||
|
||||
if (!unmerged_paths.nr) {
|
||||
string_list_clear(&unmerged_paths, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < unmerged_paths.nr; i++) {
|
||||
const char *path = unmerged_paths.items[i].string;
|
||||
struct stat st;
|
||||
|
||||
if (!lstat(path, &st) && S_ISREG(st.st_mode)) {
|
||||
if (has_conflict_markers(istate, path))
|
||||
string_list_append(&unresolved_paths, path);
|
||||
}
|
||||
}
|
||||
|
||||
if (unresolved_paths.nr) {
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
for (i = 0; i < unresolved_paths.nr; i++)
|
||||
strbuf_addf(&sb, "\t%s\n", unresolved_paths.items[i].string);
|
||||
die(_("the following paths still have conflict markers:\n%s"), sb.buf);
|
||||
}
|
||||
|
||||
for (i = 0; i < unmerged_paths.nr; i++) {
|
||||
const char *path = unmerged_paths.items[i].string;
|
||||
struct stat st;
|
||||
|
||||
if (lstat(path, &st)) {
|
||||
if (errno != ENOENT)
|
||||
die_errno(_("cannot lstat: '%s'"), path);
|
||||
if (remove_file_from_index_with_flags(istate, path, flags))
|
||||
exit_status = failed_to_add(flags, path);
|
||||
} else {
|
||||
if (add_file_to_index(istate, path, flags))
|
||||
exit_status = failed_to_add(flags, path);
|
||||
}
|
||||
}
|
||||
|
||||
string_list_clear(&unmerged_paths, 0);
|
||||
string_list_clear(&unresolved_paths, 0);
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
int cmd_add(int argc,
|
||||
const char **argv,
|
||||
const char *prefix,
|
||||
@@ -438,8 +511,9 @@ int cmd_add(int argc,
|
||||
else if (take_worktree_changes && ADDREMOVE_DEFAULT)
|
||||
addremove = 0; /* "-u" was given but not "-A" */
|
||||
|
||||
if (addremove && take_worktree_changes)
|
||||
die(_("options '%s' and '%s' cannot be used together"), "-A", "-u");
|
||||
die_for_incompatible_opt3(take_worktree_changes, "-u/--update",
|
||||
0 < addremove_explicit, "-A/--all",
|
||||
add_resolved, "--resolved");
|
||||
|
||||
if (!show_only && ignore_missing)
|
||||
die(_("the option '%s' requires '%s'"), "--ignore-missing", "--dry-run");
|
||||
@@ -448,8 +522,11 @@ int cmd_add(int argc,
|
||||
chmod_arg[1] != 'x' || chmod_arg[2]))
|
||||
die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);
|
||||
|
||||
add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
|
||||
require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
|
||||
add_new_files = !take_worktree_changes && !refresh_only &&
|
||||
!add_renormalize && !add_resolved;
|
||||
require_pathspec = !(take_worktree_changes ||
|
||||
(0 < addremove_explicit) ||
|
||||
add_resolved);
|
||||
|
||||
repo_hold_locked_index(repo, &lock_file, LOCK_DIE_ON_ERROR);
|
||||
|
||||
@@ -481,7 +558,8 @@ int cmd_add(int argc,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)
|
||||
if (!take_worktree_changes && !add_resolved &&
|
||||
addremove_explicit < 0 && pathspec.nr)
|
||||
/* Turn "git add pathspec..." to "git add -A pathspec..." */
|
||||
addremove = 1;
|
||||
|
||||
@@ -584,7 +662,9 @@ int cmd_add(int argc,
|
||||
odb_transaction_begin_or_die(repo->objects, &transaction, 0);
|
||||
|
||||
ps_matched = xcalloc(pathspec.nr, 1);
|
||||
if (add_renormalize)
|
||||
if (add_resolved)
|
||||
exit_status |= add_resolved_files(repo, &pathspec, flags);
|
||||
else if (add_renormalize)
|
||||
exit_status |= renormalize_tracked_files(repo, &pathspec, flags);
|
||||
else
|
||||
exit_status |= add_files_to_cache(repo, prefix,
|
||||
|
||||
25
diff.c
25
diff.c
@@ -3519,29 +3519,6 @@ struct checkdiff_t {
|
||||
int last_line_kind;
|
||||
};
|
||||
|
||||
static int is_conflict_marker(const char *line, int marker_size, unsigned long len)
|
||||
{
|
||||
char firstchar;
|
||||
int cnt;
|
||||
|
||||
if (len < marker_size + 1)
|
||||
return 0;
|
||||
firstchar = line[0];
|
||||
switch (firstchar) {
|
||||
case '=': case '>': case '<': case '|':
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
for (cnt = 1; cnt < marker_size; cnt++)
|
||||
if (line[cnt] != firstchar)
|
||||
return 0;
|
||||
/* line[1] through line[marker_size-1] are same as firstchar */
|
||||
if (len < marker_size + 1 || !isspace(line[marker_size]))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void checkdiff_consume_hunk(void *priv,
|
||||
long ob UNUSED, long on UNUSED,
|
||||
long nb, long nn UNUSED,
|
||||
@@ -3571,7 +3548,7 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len)
|
||||
if (line[0] == '+') {
|
||||
unsigned bad;
|
||||
data->lineno++;
|
||||
if (is_conflict_marker(line + 1, marker_size, len - 1)) {
|
||||
if (is_conflict_marker_line(line + 1, len - 1, marker_size)) {
|
||||
data->status |= 1;
|
||||
fprintf(data->o->file,
|
||||
"%s%s:%d: leftover conflict marker\n",
|
||||
|
||||
56
merge-ll.c
56
merge-ll.c
@@ -468,3 +468,59 @@ int ll_merge_marker_size(struct index_state *istate, const char *path)
|
||||
}
|
||||
return marker_size;
|
||||
}
|
||||
|
||||
int is_conflict_marker_line(const char *line, unsigned long len, int marker_size)
|
||||
{
|
||||
char firstchar;
|
||||
int cnt;
|
||||
|
||||
if (len < marker_size + 1)
|
||||
return 0;
|
||||
|
||||
firstchar = line[0];
|
||||
switch (firstchar) {
|
||||
case '=': case '>': case '<': case '|':
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (cnt = 1; cnt < marker_size; cnt++) {
|
||||
if (line[cnt] != firstchar)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (((firstchar == '<') || (firstchar == '>')) &&
|
||||
line[marker_size] != ' ')
|
||||
return 0;
|
||||
|
||||
if (!isspace((unsigned char)line[marker_size]))
|
||||
return 0;
|
||||
|
||||
return firstchar;
|
||||
}
|
||||
|
||||
int has_conflict_markers(struct index_state *istate, const char *path)
|
||||
{
|
||||
FILE *f;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
int marker_size = ll_merge_marker_size(istate, path);
|
||||
int has_markers = 0;
|
||||
|
||||
f = fopen(path, "r");
|
||||
if (!f)
|
||||
return 0;
|
||||
|
||||
while (strbuf_getwholeline(&sb, f, '\n') != EOF) {
|
||||
if (is_conflict_marker_line(sb.buf, sb.len, marker_size)) {
|
||||
has_markers = 1;
|
||||
break;
|
||||
}
|
||||
if (buffer_is_binary(sb.buf,
|
||||
ULONG_MAX <= sb.len ? ULONG_MAX : sb.len))
|
||||
break;
|
||||
}
|
||||
fclose(f);
|
||||
strbuf_release(&sb);
|
||||
return has_markers;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,8 @@ enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
|
||||
const struct ll_merge_options *opts);
|
||||
|
||||
int ll_merge_marker_size(struct index_state *istate, const char *path);
|
||||
int is_conflict_marker_line(const char *line, unsigned long len, int marker_size);
|
||||
int has_conflict_markers(struct index_state *istate, const char *path);
|
||||
void reset_merge_attributes(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -391,11 +391,14 @@ int remove_index_entry_at(struct index_state *, int pos);
|
||||
|
||||
void remove_marked_cache_entries(struct index_state *istate, int invalidate);
|
||||
int remove_file_from_index(struct index_state *, const char *path);
|
||||
int remove_file_from_index_with_flags(struct index_state *, const char *, int);
|
||||
|
||||
#define ADD_CACHE_VERBOSE 1
|
||||
#define ADD_CACHE_PRETEND 2
|
||||
#define ADD_CACHE_IGNORE_ERRORS 4
|
||||
#define ADD_CACHE_IGNORE_REMOVAL 8
|
||||
#define ADD_CACHE_INTENT 16
|
||||
|
||||
/*
|
||||
* These two are used to add the contents of the file at path
|
||||
* to the index, marking the working tree up-to-date by storing
|
||||
|
||||
85
read-cache.c
85
read-cache.c
@@ -636,6 +636,20 @@ int remove_file_from_index(struct index_state *istate, const char *path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int remove_file_from_index_with_flags(struct index_state *istate,
|
||||
const char *path,
|
||||
int flags)
|
||||
{
|
||||
int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND);
|
||||
int pretend = flags & ADD_CACHE_PRETEND;
|
||||
|
||||
if (verbose)
|
||||
printf(_("remove '%s'\n"), path);
|
||||
if (pretend)
|
||||
return 0;
|
||||
return remove_file_from_index(istate, path);
|
||||
}
|
||||
|
||||
static int compare_name(struct cache_entry *ce, const char *path, int namelen)
|
||||
{
|
||||
return namelen != ce_namelen(ce) || memcmp(path, ce->name, namelen);
|
||||
@@ -3909,32 +3923,33 @@ static int fix_unmerged_status(struct diff_filepair *p,
|
||||
}
|
||||
|
||||
static int skip_submodule(const char *path,
|
||||
struct repository *repo,
|
||||
struct pathspec *pathspec,
|
||||
int ignored_too)
|
||||
struct repository *repo,
|
||||
struct pathspec *pathspec,
|
||||
int ignored_too)
|
||||
{
|
||||
struct stat st;
|
||||
const struct submodule *sub;
|
||||
int pathspec_matches = 0;
|
||||
int ps_i;
|
||||
char *norm_pathspec = NULL;
|
||||
struct stat st;
|
||||
const struct submodule *sub;
|
||||
int pathspec_matches = 0;
|
||||
int ps_i;
|
||||
char *norm_pathspec = NULL;
|
||||
|
||||
/* Only consider if path is a directory */
|
||||
if (lstat(path, &st) || !S_ISDIR(st.st_mode))
|
||||
/* Only consider if path is a directory */
|
||||
if (lstat(path, &st) || !S_ISDIR(st.st_mode))
|
||||
return 0;
|
||||
|
||||
/* Check if it's a submodule with ignore=all */
|
||||
sub = submodule_from_path(repo, null_oid(the_hash_algo), path);
|
||||
if (!sub || !sub->name || !sub->ignore || strcmp(sub->ignore, "all"))
|
||||
/* Check if it's a submodule with ignore=all */
|
||||
sub = submodule_from_path(repo, null_oid(the_hash_algo), path);
|
||||
if (!sub || !sub->name || !sub->ignore || strcmp(sub->ignore, "all"))
|
||||
return 0;
|
||||
|
||||
trace_printf("ignore=all: %s\n", path);
|
||||
trace_printf("pathspec %s\n", (pathspec && pathspec->nr)
|
||||
? "has pathspec"
|
||||
: "no pathspec");
|
||||
trace_printf("ignore=all: %s\n", path);
|
||||
trace_printf("pathspec %s\n",
|
||||
((pathspec && pathspec->nr)
|
||||
? "has pathspec"
|
||||
: "no pathspec"));
|
||||
|
||||
/* Check if submodule path is explicitly mentioned in pathspec */
|
||||
if (pathspec) {
|
||||
/* Check if submodule path is explicitly mentioned in pathspec */
|
||||
if (pathspec) {
|
||||
for (ps_i = 0; ps_i < pathspec->nr; ps_i++) {
|
||||
const char *m = pathspec->items[ps_i].match;
|
||||
if (!m)
|
||||
@@ -3948,28 +3963,29 @@ static int skip_submodule(const char *path,
|
||||
}
|
||||
FREE_AND_NULL(norm_pathspec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If explicitly matched and forced, allow adding */
|
||||
if (pathspec_matches) {
|
||||
/* If explicitly matched and forced, allow adding */
|
||||
if (pathspec_matches) {
|
||||
if (ignored_too && ignored_too > 0) {
|
||||
trace_printf("Add submodule due to --force: %s\n", path);
|
||||
return 0;
|
||||
} else {
|
||||
advise_if_enabled(ADVICE_ADD_IGNORED_FILE,
|
||||
_("Skipping submodule due to ignore=all: %s\n"
|
||||
"Use --force if you really want to add the submodule."), path);
|
||||
_("Skipping submodule due to ignore=all: %s\n"
|
||||
"Use --force if you really want to "
|
||||
"add the submodule."), path);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* No explicit pathspec match -> skip silently */
|
||||
trace_printf("Pathspec to submodule does not match explicitly: %s\n", path);
|
||||
return 1;
|
||||
/* No explicit pathspec match -> skip silently */
|
||||
trace_printf("Pathspec to submodule does not match explicitly: %s\n", path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void update_callback(struct diff_queue_struct *q,
|
||||
struct diff_options *opt UNUSED, void *cbdata)
|
||||
struct diff_options *opt UNUSED, void *cbdata)
|
||||
{
|
||||
int i;
|
||||
struct update_callback_data *data = cbdata;
|
||||
@@ -3979,7 +3995,7 @@ static void update_callback(struct diff_queue_struct *q,
|
||||
const char *path = p->one->path;
|
||||
|
||||
if (!data->include_sparse &&
|
||||
!path_in_sparse_checkout(path, data->index))
|
||||
!path_in_sparse_checkout(path, data->index))
|
||||
continue;
|
||||
|
||||
switch (fix_unmerged_status(p, data)) {
|
||||
@@ -3988,8 +4004,8 @@ static void update_callback(struct diff_queue_struct *q,
|
||||
case DIFF_STATUS_MODIFIED:
|
||||
case DIFF_STATUS_TYPE_CHANGED:
|
||||
if (skip_submodule(path, data->repo,
|
||||
data->pathspec,
|
||||
data->ignored_too))
|
||||
data->pathspec,
|
||||
data->ignored_too))
|
||||
continue;
|
||||
|
||||
if (add_file_to_index(data->index, path, data->flags)) {
|
||||
@@ -4001,10 +4017,7 @@ static void update_callback(struct diff_queue_struct *q,
|
||||
case DIFF_STATUS_DELETED:
|
||||
if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
|
||||
break;
|
||||
if (!(data->flags & ADD_CACHE_PRETEND))
|
||||
remove_file_from_index(data->index, path);
|
||||
if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
|
||||
printf(_("remove '%s'\n"), path);
|
||||
remove_file_from_index_with_flags(data->index, path, data->flags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
38
rerere.c
38
rerere.c
@@ -331,33 +331,6 @@ static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_)
|
||||
return strbuf_getwholeline(sb, io->input, '\n');
|
||||
}
|
||||
|
||||
/*
|
||||
* Require the exact number of conflict marker letters, no more, no
|
||||
* less, followed by SP or any whitespace
|
||||
* (including LF).
|
||||
*/
|
||||
static int is_cmarker(char *buf, int marker_char, int marker_size)
|
||||
{
|
||||
int want_sp;
|
||||
|
||||
/*
|
||||
* The beginning of our version and the end of their version
|
||||
* always are labeled like "<<<<< ours" or ">>>>> theirs",
|
||||
* hence we set want_sp for them. Note that the version from
|
||||
* the common ancestor in diff3-style output is not always
|
||||
* labelled (e.g. "||||| common" is often seen but "|||||"
|
||||
* alone is also valid), so we do not set want_sp.
|
||||
*/
|
||||
want_sp = (marker_char == '<') || (marker_char == '>');
|
||||
|
||||
while (marker_size--)
|
||||
if (*buf++ != marker_char)
|
||||
return 0;
|
||||
if (want_sp && *buf != ' ')
|
||||
return 0;
|
||||
return isspace(*buf);
|
||||
}
|
||||
|
||||
static void rerere_strbuf_putconflict(struct strbuf *buf, int ch, size_t size)
|
||||
{
|
||||
strbuf_addchars(buf, ch, size);
|
||||
@@ -375,7 +348,8 @@ static int handle_conflict(struct strbuf *out, struct rerere_io *io,
|
||||
int has_conflicts = -1;
|
||||
|
||||
while (!io->getline(&buf, io)) {
|
||||
if (is_cmarker(buf.buf, '<', marker_size)) {
|
||||
int marker = is_conflict_marker_line(buf.buf, buf.len, marker_size);
|
||||
if (marker == '<') {
|
||||
if (handle_conflict(&conflict, io, marker_size, NULL) < 0)
|
||||
break;
|
||||
if (hunk == RR_SIDE_1)
|
||||
@@ -383,15 +357,15 @@ static int handle_conflict(struct strbuf *out, struct rerere_io *io,
|
||||
else
|
||||
strbuf_addbuf(&two, &conflict);
|
||||
strbuf_release(&conflict);
|
||||
} else if (is_cmarker(buf.buf, '|', marker_size)) {
|
||||
} else if (marker == '|') {
|
||||
if (hunk != RR_SIDE_1)
|
||||
break;
|
||||
hunk = RR_ORIGINAL;
|
||||
} else if (is_cmarker(buf.buf, '=', marker_size)) {
|
||||
} else if (marker == '=') {
|
||||
if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
|
||||
break;
|
||||
hunk = RR_SIDE_2;
|
||||
} else if (is_cmarker(buf.buf, '>', marker_size)) {
|
||||
} else if (marker == '>') {
|
||||
if (hunk != RR_SIDE_2)
|
||||
break;
|
||||
if (strbuf_cmp(&one, &two) > 0)
|
||||
@@ -442,7 +416,7 @@ static int handle_path(unsigned char *hash, struct rerere_io *io, int marker_siz
|
||||
git_hash_init(&ctx, the_hash_algo);
|
||||
|
||||
while (!io->getline(&buf, io)) {
|
||||
if (is_cmarker(buf.buf, '<', marker_size)) {
|
||||
if (is_conflict_marker_line(buf.buf, buf.len, marker_size) == '<') {
|
||||
has_conflicts = handle_conflict(&out, io, marker_size,
|
||||
hash ? &ctx : NULL);
|
||||
if (has_conflicts < 0)
|
||||
|
||||
@@ -305,6 +305,7 @@ integration_tests = [
|
||||
't2204-add-ignored.sh',
|
||||
't2205-add-worktree-config.sh',
|
||||
't2206-add-submodule-ignored.sh',
|
||||
't2207-add-resolved.sh',
|
||||
't2300-cd-to-toplevel.sh',
|
||||
't2400-worktree-add.sh',
|
||||
't2401-worktree-prune.sh',
|
||||
|
||||
108
t/t2207-add-resolved.sh
Executable file
108
t/t2207-add-resolved.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='git add --resolved
|
||||
|
||||
Test that "git add --resolved" stages conflict-resolved paths and
|
||||
refuses to stage when conflict markers remain.'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'setup repo' '
|
||||
echo base >file1.txt &&
|
||||
echo base >file2.txt &&
|
||||
echo base >file3.txt &&
|
||||
echo base >file4.txt &&
|
||||
git add file1.txt file2.txt file3.txt file4.txt &&
|
||||
git commit -m initial &&
|
||||
|
||||
git branch topic &&
|
||||
echo "ours 1" >file1.txt &&
|
||||
echo "ours 2" >file2.txt &&
|
||||
echo "ours 3" >file3.txt &&
|
||||
git commit -a -m ours &&
|
||||
|
||||
git checkout topic &&
|
||||
echo "theirs 1" >file1.txt &&
|
||||
echo "theirs 2" >file2.txt &&
|
||||
echo "theirs 3" >file3.txt &&
|
||||
git commit -a -m theirs &&
|
||||
|
||||
git checkout @{-1}
|
||||
'
|
||||
|
||||
test_expect_success 'git add --resolved refuses files with conflict markers' '
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
test_must_fail git merge topic &&
|
||||
echo "resolved 1" >file1.txt &&
|
||||
test_must_fail git add --resolved 2>err &&
|
||||
test_grep "the following paths still have conflict markers:" err &&
|
||||
test_grep "file2.txt" err &&
|
||||
test_grep "file3.txt" err &&
|
||||
# Index should remain unmerged for all files
|
||||
git ls-files -u file1.txt >unmerged &&
|
||||
test_line_count = 3 unmerged
|
||||
'
|
||||
|
||||
test_expect_success 'git add --resolved succeeds when all conflict markers are removed' '
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
test_must_fail git merge topic &&
|
||||
echo "resolved 1" >file1.txt &&
|
||||
echo "resolved 2" >file2.txt &&
|
||||
echo "resolved 3" >file3.txt &&
|
||||
git add --resolved &&
|
||||
git ls-files -u >unmerged &&
|
||||
test_must_be_empty unmerged &&
|
||||
git ls-files -s file1.txt file2.txt file3.txt >staged &&
|
||||
test_line_count = 3 staged
|
||||
'
|
||||
|
||||
test_expect_success 'git add --resolved ignores unconflicted modified files' '
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
echo "unconflicted local change" >>file4.txt &&
|
||||
test_must_fail git merge topic &&
|
||||
echo "resolved 1" >file1.txt &&
|
||||
echo "resolved 2" >file2.txt &&
|
||||
echo "resolved 3" >file3.txt &&
|
||||
git add --resolved &&
|
||||
# file1, file2, file3 should be staged as resolved
|
||||
git ls-files -u >unmerged &&
|
||||
test_must_be_empty unmerged &&
|
||||
# file4 should remain unstaged in working tree
|
||||
git diff file4.txt >diff_out &&
|
||||
test_grep "unconflicted local change" diff_out &&
|
||||
git diff --cached file4.txt >cached_out &&
|
||||
test_must_be_empty cached_out
|
||||
'
|
||||
|
||||
test_expect_success 'git add --resolved handles file removals' '
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
test_must_fail git merge topic &&
|
||||
echo "resolved 1" >file1.txt &&
|
||||
rm file2.txt &&
|
||||
echo "resolved 3" >file3.txt &&
|
||||
git add --resolved &&
|
||||
git ls-files -s file2.txt >out &&
|
||||
test_must_be_empty out
|
||||
'
|
||||
|
||||
test_expect_success 'git add --resolved honors pathspec' '
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
test_must_fail git merge topic &&
|
||||
echo "resolved 1" >file1.txt &&
|
||||
# file2.txt and file3.txt still have conflict markers,
|
||||
# but pathspec targets only file1.txt
|
||||
git add --resolved file1.txt &&
|
||||
git ls-files -u file1.txt >unmerged1 &&
|
||||
test_must_be_empty unmerged1 &&
|
||||
git ls-files -u file2.txt >unmerged2 &&
|
||||
test_line_count = 3 unmerged2
|
||||
'
|
||||
|
||||
test_expect_success 'git add --resolved incompatibility with -u and -A' '
|
||||
test_must_fail git add --resolved -u 2>err1 &&
|
||||
test_grep "cannot be used together" err1 &&
|
||||
test_must_fail git add --resolved -A 2>err2 &&
|
||||
test_grep "cannot be used together" err2
|
||||
'
|
||||
|
||||
test_done
|
||||
Reference in New Issue
Block a user