Merge branch 'jc/add-resolved'
'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: reindentmain
commit
230296d560
|
|
@ -11,7 +11,7 @@ SYNOPSIS
|
||||||
git add [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
|
git add [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
|
||||||
[--edit | -e] [--[no-]all | -A | --[no-]ignore-removal | [--update | -u]] [--sparse]
|
[--edit | -e] [--[no-]all | -A | --[no-]ignore-removal | [--update | -u]] [--sparse]
|
||||||
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
|
[--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>...]
|
[--] [<pathspec>...]
|
||||||
|
|
||||||
DESCRIPTION
|
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
|
while a _CRLF_ cleans to _LF_, a _CRCRLF_ sequence is only partially
|
||||||
cleaned to _CRLF_.
|
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`::
|
`--chmod=(+|-)x`::
|
||||||
Override the executable bit of the added files. The executable
|
Override the executable bit of the added files. The executable
|
||||||
bit is only changed in the index, the files on disk are left
|
bit is only changed in the index, the files on disk are left
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#include "strvec.h"
|
#include "strvec.h"
|
||||||
#include "submodule.h"
|
#include "submodule.h"
|
||||||
#include "add-interactive.h"
|
#include "add-interactive.h"
|
||||||
|
#include "merge-ll.h"
|
||||||
|
|
||||||
static const char * const builtin_add_usage[] = {
|
static const char * const builtin_add_usage[] = {
|
||||||
N_("git add [<options>] [--] <pathspec>..."),
|
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 struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT;
|
||||||
static int take_worktree_changes;
|
static int take_worktree_changes;
|
||||||
static int add_renormalize;
|
static int add_renormalize;
|
||||||
|
static int add_resolved;
|
||||||
static int pathspec_file_nul;
|
static int pathspec_file_nul;
|
||||||
static int include_sparse;
|
static int include_sparse;
|
||||||
static const char *pathspec_from_file;
|
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__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
|
||||||
OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
|
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, "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('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_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
|
||||||
OPT_CALLBACK_F(0, "ignore-removal", &addremove_explicit,
|
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;
|
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,
|
int cmd_add(int argc,
|
||||||
const char **argv,
|
const char **argv,
|
||||||
const char *prefix,
|
const char *prefix,
|
||||||
|
|
@ -438,8 +511,9 @@ int cmd_add(int argc,
|
||||||
else if (take_worktree_changes && ADDREMOVE_DEFAULT)
|
else if (take_worktree_changes && ADDREMOVE_DEFAULT)
|
||||||
addremove = 0; /* "-u" was given but not "-A" */
|
addremove = 0; /* "-u" was given but not "-A" */
|
||||||
|
|
||||||
if (addremove && take_worktree_changes)
|
die_for_incompatible_opt3(take_worktree_changes, "-u/--update",
|
||||||
die(_("options '%s' and '%s' cannot be used together"), "-A", "-u");
|
0 < addremove_explicit, "-A/--all",
|
||||||
|
add_resolved, "--resolved");
|
||||||
|
|
||||||
if (!show_only && ignore_missing)
|
if (!show_only && ignore_missing)
|
||||||
die(_("the option '%s' requires '%s'"), "--ignore-missing", "--dry-run");
|
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]))
|
chmod_arg[1] != 'x' || chmod_arg[2]))
|
||||||
die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);
|
die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);
|
||||||
|
|
||||||
add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
|
add_new_files = !take_worktree_changes && !refresh_only &&
|
||||||
require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
|
!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);
|
repo_hold_locked_index(repo, &lock_file, LOCK_DIE_ON_ERROR);
|
||||||
|
|
||||||
|
|
@ -481,7 +558,8 @@ int cmd_add(int argc,
|
||||||
return 0;
|
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..." */
|
/* Turn "git add pathspec..." to "git add -A pathspec..." */
|
||||||
addremove = 1;
|
addremove = 1;
|
||||||
|
|
||||||
|
|
@ -584,7 +662,9 @@ int cmd_add(int argc,
|
||||||
odb_transaction_begin_or_die(repo->objects, &transaction, 0);
|
odb_transaction_begin_or_die(repo->objects, &transaction, 0);
|
||||||
|
|
||||||
ps_matched = xcalloc(pathspec.nr, 1);
|
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);
|
exit_status |= renormalize_tracked_files(repo, &pathspec, flags);
|
||||||
else
|
else
|
||||||
exit_status |= add_files_to_cache(repo, prefix,
|
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;
|
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,
|
static void checkdiff_consume_hunk(void *priv,
|
||||||
long ob UNUSED, long on UNUSED,
|
long ob UNUSED, long on UNUSED,
|
||||||
long nb, long nn UNUSED,
|
long nb, long nn UNUSED,
|
||||||
|
|
@ -3571,7 +3548,7 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len)
|
||||||
if (line[0] == '+') {
|
if (line[0] == '+') {
|
||||||
unsigned bad;
|
unsigned bad;
|
||||||
data->lineno++;
|
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;
|
data->status |= 1;
|
||||||
fprintf(data->o->file,
|
fprintf(data->o->file,
|
||||||
"%s%s:%d: leftover conflict marker\n",
|
"%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;
|
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);
|
const struct ll_merge_options *opts);
|
||||||
|
|
||||||
int ll_merge_marker_size(struct index_state *istate, const char *path);
|
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);
|
void reset_merge_attributes(void);
|
||||||
|
|
||||||
#endif
|
#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);
|
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(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_VERBOSE 1
|
||||||
#define ADD_CACHE_PRETEND 2
|
#define ADD_CACHE_PRETEND 2
|
||||||
#define ADD_CACHE_IGNORE_ERRORS 4
|
#define ADD_CACHE_IGNORE_ERRORS 4
|
||||||
#define ADD_CACHE_IGNORE_REMOVAL 8
|
#define ADD_CACHE_IGNORE_REMOVAL 8
|
||||||
#define ADD_CACHE_INTENT 16
|
#define ADD_CACHE_INTENT 16
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* These two are used to add the contents of the file at path
|
* 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
|
* 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;
|
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)
|
static int compare_name(struct cache_entry *ce, const char *path, int namelen)
|
||||||
{
|
{
|
||||||
return namelen != ce_namelen(ce) || memcmp(path, ce->name, 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,
|
static int skip_submodule(const char *path,
|
||||||
struct repository *repo,
|
struct repository *repo,
|
||||||
struct pathspec *pathspec,
|
struct pathspec *pathspec,
|
||||||
int ignored_too)
|
int ignored_too)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
const struct submodule *sub;
|
const struct submodule *sub;
|
||||||
int pathspec_matches = 0;
|
int pathspec_matches = 0;
|
||||||
int ps_i;
|
int ps_i;
|
||||||
char *norm_pathspec = NULL;
|
char *norm_pathspec = NULL;
|
||||||
|
|
||||||
/* Only consider if path is a directory */
|
/* Only consider if path is a directory */
|
||||||
if (lstat(path, &st) || !S_ISDIR(st.st_mode))
|
if (lstat(path, &st) || !S_ISDIR(st.st_mode))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Check if it's a submodule with ignore=all */
|
/* Check if it's a submodule with ignore=all */
|
||||||
sub = submodule_from_path(repo, null_oid(the_hash_algo), path);
|
sub = submodule_from_path(repo, null_oid(the_hash_algo), path);
|
||||||
if (!sub || !sub->name || !sub->ignore || strcmp(sub->ignore, "all"))
|
if (!sub || !sub->name || !sub->ignore || strcmp(sub->ignore, "all"))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
trace_printf("ignore=all: %s\n", path);
|
trace_printf("ignore=all: %s\n", path);
|
||||||
trace_printf("pathspec %s\n", (pathspec && pathspec->nr)
|
trace_printf("pathspec %s\n",
|
||||||
? "has pathspec"
|
((pathspec && pathspec->nr)
|
||||||
: "no pathspec");
|
? "has pathspec"
|
||||||
|
: "no pathspec"));
|
||||||
|
|
||||||
/* Check if submodule path is explicitly mentioned in pathspec */
|
/* Check if submodule path is explicitly mentioned in pathspec */
|
||||||
if (pathspec) {
|
if (pathspec) {
|
||||||
for (ps_i = 0; ps_i < pathspec->nr; ps_i++) {
|
for (ps_i = 0; ps_i < pathspec->nr; ps_i++) {
|
||||||
const char *m = pathspec->items[ps_i].match;
|
const char *m = pathspec->items[ps_i].match;
|
||||||
if (!m)
|
if (!m)
|
||||||
|
|
@ -3948,28 +3963,29 @@ static int skip_submodule(const char *path,
|
||||||
}
|
}
|
||||||
FREE_AND_NULL(norm_pathspec);
|
FREE_AND_NULL(norm_pathspec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If explicitly matched and forced, allow adding */
|
/* If explicitly matched and forced, allow adding */
|
||||||
if (pathspec_matches) {
|
if (pathspec_matches) {
|
||||||
if (ignored_too && ignored_too > 0) {
|
if (ignored_too && ignored_too > 0) {
|
||||||
trace_printf("Add submodule due to --force: %s\n", path);
|
trace_printf("Add submodule due to --force: %s\n", path);
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
advise_if_enabled(ADVICE_ADD_IGNORED_FILE,
|
advise_if_enabled(ADVICE_ADD_IGNORED_FILE,
|
||||||
_("Skipping submodule due to ignore=all: %s\n"
|
_("Skipping submodule due to ignore=all: %s\n"
|
||||||
"Use --force if you really want to add the submodule."), path);
|
"Use --force if you really want to "
|
||||||
|
"add the submodule."), path);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* No explicit pathspec match -> skip silently */
|
/* No explicit pathspec match -> skip silently */
|
||||||
trace_printf("Pathspec to submodule does not match explicitly: %s\n", path);
|
trace_printf("Pathspec to submodule does not match explicitly: %s\n", path);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_callback(struct diff_queue_struct *q,
|
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;
|
int i;
|
||||||
struct update_callback_data *data = cbdata;
|
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;
|
const char *path = p->one->path;
|
||||||
|
|
||||||
if (!data->include_sparse &&
|
if (!data->include_sparse &&
|
||||||
!path_in_sparse_checkout(path, data->index))
|
!path_in_sparse_checkout(path, data->index))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
switch (fix_unmerged_status(p, data)) {
|
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_MODIFIED:
|
||||||
case DIFF_STATUS_TYPE_CHANGED:
|
case DIFF_STATUS_TYPE_CHANGED:
|
||||||
if (skip_submodule(path, data->repo,
|
if (skip_submodule(path, data->repo,
|
||||||
data->pathspec,
|
data->pathspec,
|
||||||
data->ignored_too))
|
data->ignored_too))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (add_file_to_index(data->index, path, data->flags)) {
|
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:
|
case DIFF_STATUS_DELETED:
|
||||||
if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
|
if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
|
||||||
break;
|
break;
|
||||||
if (!(data->flags & ADD_CACHE_PRETEND))
|
remove_file_from_index_with_flags(data->index, path, data->flags);
|
||||||
remove_file_from_index(data->index, path);
|
|
||||||
if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
|
|
||||||
printf(_("remove '%s'\n"), path);
|
|
||||||
break;
|
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');
|
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)
|
static void rerere_strbuf_putconflict(struct strbuf *buf, int ch, size_t size)
|
||||||
{
|
{
|
||||||
strbuf_addchars(buf, ch, 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;
|
int has_conflicts = -1;
|
||||||
|
|
||||||
while (!io->getline(&buf, io)) {
|
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)
|
if (handle_conflict(&conflict, io, marker_size, NULL) < 0)
|
||||||
break;
|
break;
|
||||||
if (hunk == RR_SIDE_1)
|
if (hunk == RR_SIDE_1)
|
||||||
|
|
@ -383,15 +357,15 @@ static int handle_conflict(struct strbuf *out, struct rerere_io *io,
|
||||||
else
|
else
|
||||||
strbuf_addbuf(&two, &conflict);
|
strbuf_addbuf(&two, &conflict);
|
||||||
strbuf_release(&conflict);
|
strbuf_release(&conflict);
|
||||||
} else if (is_cmarker(buf.buf, '|', marker_size)) {
|
} else if (marker == '|') {
|
||||||
if (hunk != RR_SIDE_1)
|
if (hunk != RR_SIDE_1)
|
||||||
break;
|
break;
|
||||||
hunk = RR_ORIGINAL;
|
hunk = RR_ORIGINAL;
|
||||||
} else if (is_cmarker(buf.buf, '=', marker_size)) {
|
} else if (marker == '=') {
|
||||||
if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
|
if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
|
||||||
break;
|
break;
|
||||||
hunk = RR_SIDE_2;
|
hunk = RR_SIDE_2;
|
||||||
} else if (is_cmarker(buf.buf, '>', marker_size)) {
|
} else if (marker == '>') {
|
||||||
if (hunk != RR_SIDE_2)
|
if (hunk != RR_SIDE_2)
|
||||||
break;
|
break;
|
||||||
if (strbuf_cmp(&one, &two) > 0)
|
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);
|
git_hash_init(&ctx, the_hash_algo);
|
||||||
|
|
||||||
while (!io->getline(&buf, io)) {
|
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,
|
has_conflicts = handle_conflict(&out, io, marker_size,
|
||||||
hash ? &ctx : NULL);
|
hash ? &ctx : NULL);
|
||||||
if (has_conflicts < 0)
|
if (has_conflicts < 0)
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,7 @@ integration_tests = [
|
||||||
't2204-add-ignored.sh',
|
't2204-add-ignored.sh',
|
||||||
't2205-add-worktree-config.sh',
|
't2205-add-worktree-config.sh',
|
||||||
't2206-add-submodule-ignored.sh',
|
't2206-add-submodule-ignored.sh',
|
||||||
|
't2207-add-resolved.sh',
|
||||||
't2300-cd-to-toplevel.sh',
|
't2300-cd-to-toplevel.sh',
|
||||||
't2400-worktree-add.sh',
|
't2400-worktree-add.sh',
|
||||||
't2401-worktree-prune.sh',
|
't2401-worktree-prune.sh',
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue