mirror of
https://github.com/git/git
synced 2024-08-24 18:26:02 +00:00
Merge branch 'jc/apply-3way'
"git apply" learned to wiggle the base version and perform three-way merge when a patch does not exactly apply to the version you have. * jc/apply-3way: apply: tests for the --3way option apply: document --3way option apply: allow rerere() to work on --3way results apply: register conflicted stages to the index apply: --3way with add/add conflict apply: move verify_index_match() higher apply: plug the three-way merge logic in apply: fall back on three-way merge apply: accept -3/--3way command line option apply: move "already exists" logic to check_to_create() apply: move check_to_create_blob() closer to its sole caller apply: further split load_preimage() apply: refactor "previous patch" logic apply: split load_preimage() helper function out apply: factor out checkout_target() helper function apply: refactor read_file_or_gitlink() apply: clear_image() clears things a bit more apply: a bit more comments on PATH_TO_BE_DELETED apply: fix an incomplete comment in check_patch()
This commit is contained in:
commit
f247b10aa0
|
@ -9,7 +9,7 @@ git-apply - Apply a patch to files and/or to the index
|
|||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git apply' [--stat] [--numstat] [--summary] [--check] [--index]
|
||||
'git apply' [--stat] [--numstat] [--summary] [--check] [--index] [--3way]
|
||||
[--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
|
||||
[--allow-binary-replacement | --binary] [--reject] [-z]
|
||||
[-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]
|
||||
|
@ -72,6 +72,15 @@ OPTIONS
|
|||
cached data, apply the patch, and store the result in the index
|
||||
without using the working tree. This implies `--index`.
|
||||
|
||||
-3::
|
||||
--3way::
|
||||
When the patch does not apply cleanly, fall back on 3-way merge if
|
||||
the patch records the identity of blobs it is supposed to apply to,
|
||||
and we have those blobs available locally, possibly leaving the
|
||||
conflict markers in the files in the working tree for the user to
|
||||
resolve. This option implies the `--index` option, and is incompatible
|
||||
with the `--reject` and the `--cached` options.
|
||||
|
||||
--build-fake-ancestor=<file>::
|
||||
Newer 'git diff' output has embedded 'index information'
|
||||
for each blob to help identify the original version that
|
||||
|
|
575
builtin/apply.c
575
builtin/apply.c
|
@ -16,6 +16,9 @@
|
|||
#include "dir.h"
|
||||
#include "diff.h"
|
||||
#include "parse-options.h"
|
||||
#include "xdiff-interface.h"
|
||||
#include "ll-merge.h"
|
||||
#include "rerere.h"
|
||||
|
||||
/*
|
||||
* --check turns on checking that the working tree matches the
|
||||
|
@ -46,6 +49,7 @@ static int apply_with_reject;
|
|||
static int apply_verbosely;
|
||||
static int allow_overlap;
|
||||
static int no_add;
|
||||
static int threeway;
|
||||
static const char *fake_ancestor;
|
||||
static int line_termination = '\n';
|
||||
static unsigned int p_context = UINT_MAX;
|
||||
|
@ -193,12 +197,17 @@ struct patch {
|
|||
unsigned int is_copy:1;
|
||||
unsigned int is_rename:1;
|
||||
unsigned int recount:1;
|
||||
unsigned int conflicted_threeway:1;
|
||||
unsigned int direct_to_threeway:1;
|
||||
struct fragment *fragments;
|
||||
char *result;
|
||||
size_t resultsize;
|
||||
char old_sha1_prefix[41];
|
||||
char new_sha1_prefix[41];
|
||||
struct patch *next;
|
||||
|
||||
/* three-way fallback result */
|
||||
unsigned char threeway_stage[3][20];
|
||||
};
|
||||
|
||||
static void free_fragment_list(struct fragment *list)
|
||||
|
@ -371,8 +380,8 @@ static void prepare_image(struct image *image, char *buf, size_t len,
|
|||
static void clear_image(struct image *image)
|
||||
{
|
||||
free(image->buf);
|
||||
image->buf = NULL;
|
||||
image->len = 0;
|
||||
free(image->line_allocated);
|
||||
memset(image, 0, sizeof(*image));
|
||||
}
|
||||
|
||||
/* fmt must contain _one_ %s and no other substitution */
|
||||
|
@ -2937,20 +2946,17 @@ static int apply_fragments(struct image *img, struct patch *patch)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
|
||||
static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsigned mode)
|
||||
{
|
||||
if (!ce)
|
||||
return 0;
|
||||
|
||||
if (S_ISGITLINK(ce->ce_mode)) {
|
||||
if (S_ISGITLINK(mode)) {
|
||||
strbuf_grow(buf, 100);
|
||||
strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
|
||||
strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(sha1));
|
||||
} else {
|
||||
enum object_type type;
|
||||
unsigned long sz;
|
||||
char *result;
|
||||
|
||||
result = read_sha1_file(ce->sha1, &type, &sz);
|
||||
result = read_sha1_file(sha1, &type, &sz);
|
||||
if (!result)
|
||||
return -1;
|
||||
/* XXX read_sha1_file NUL-terminates */
|
||||
|
@ -2959,6 +2965,13 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
|
||||
{
|
||||
if (!ce)
|
||||
return 0;
|
||||
return read_blob_object(buf, ce->sha1, ce->ce_mode);
|
||||
}
|
||||
|
||||
static struct patch *in_fn_table(const char *name)
|
||||
{
|
||||
struct string_list_item *item;
|
||||
|
@ -2977,9 +2990,15 @@ static struct patch *in_fn_table(const char *name)
|
|||
* item->util in the filename table records the status of the path.
|
||||
* Usually it points at a patch (whose result records the contents
|
||||
* of it after applying it), but it could be PATH_WAS_DELETED for a
|
||||
* path that a previously applied patch has already removed.
|
||||
* path that a previously applied patch has already removed, or
|
||||
* PATH_TO_BE_DELETED for a path that a later patch would remove.
|
||||
*
|
||||
* The latter is needed to deal with a case where two paths A and B
|
||||
* are swapped by first renaming A to B and then renaming B to A;
|
||||
* moving A to B should not be prevented due to presense of B as we
|
||||
* will remove it in a later patch.
|
||||
*/
|
||||
#define PATH_TO_BE_DELETED ((struct patch *) -2)
|
||||
#define PATH_TO_BE_DELETED ((struct patch *) -2)
|
||||
#define PATH_WAS_DELETED ((struct patch *) -1)
|
||||
|
||||
static int to_be_deleted(struct patch *patch)
|
||||
|
@ -3031,82 +3050,37 @@ static void prepare_fn_table(struct patch *patch)
|
|||
}
|
||||
}
|
||||
|
||||
static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
|
||||
static int checkout_target(struct cache_entry *ce, struct stat *st)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct image image;
|
||||
size_t len;
|
||||
char *img;
|
||||
struct patch *tpatch;
|
||||
|
||||
if (!(patch->is_copy || patch->is_rename) &&
|
||||
(tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
|
||||
if (was_deleted(tpatch)) {
|
||||
return error(_("patch %s has been renamed/deleted"),
|
||||
patch->old_name);
|
||||
}
|
||||
/* We have a patched copy in memory; use that. */
|
||||
strbuf_add(&buf, tpatch->result, tpatch->resultsize);
|
||||
} else if (cached) {
|
||||
if (read_file_or_gitlink(ce, &buf))
|
||||
return error(_("read of %s failed"), patch->old_name);
|
||||
} else if (patch->old_name) {
|
||||
if (S_ISGITLINK(patch->old_mode)) {
|
||||
if (ce) {
|
||||
read_file_or_gitlink(ce, &buf);
|
||||
} else {
|
||||
/*
|
||||
* There is no way to apply subproject
|
||||
* patch without looking at the index.
|
||||
* NEEDSWORK: shouldn't this be flagged
|
||||
* as an error???
|
||||
*/
|
||||
free_fragment_list(patch->fragments);
|
||||
patch->fragments = NULL;
|
||||
}
|
||||
} else {
|
||||
if (read_old_data(st, patch->old_name, &buf))
|
||||
return error(_("read of %s failed"), patch->old_name);
|
||||
}
|
||||
}
|
||||
|
||||
img = strbuf_detach(&buf, &len);
|
||||
prepare_image(&image, img, len, !patch->is_binary);
|
||||
|
||||
if (apply_fragments(&image, patch) < 0)
|
||||
return -1; /* note with --reject this succeeds. */
|
||||
patch->result = image.buf;
|
||||
patch->resultsize = image.len;
|
||||
add_to_fn_table(patch);
|
||||
free(image.line_allocated);
|
||||
|
||||
if (0 < patch->is_delete && patch->resultsize)
|
||||
return error(_("removal patch leaves file contents"));
|
||||
struct checkout costate;
|
||||
|
||||
memset(&costate, 0, sizeof(costate));
|
||||
costate.base_dir = "";
|
||||
costate.refresh_cache = 1;
|
||||
if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st))
|
||||
return error(_("cannot checkout %s"), ce->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_to_create_blob(const char *new_name, int ok_if_exists)
|
||||
static struct patch *previous_patch(struct patch *patch, int *gone)
|
||||
{
|
||||
struct stat nst;
|
||||
if (!lstat(new_name, &nst)) {
|
||||
if (S_ISDIR(nst.st_mode) || ok_if_exists)
|
||||
return 0;
|
||||
/*
|
||||
* A leading component of new_name might be a symlink
|
||||
* that is going to be removed with this patch, but
|
||||
* still pointing at somewhere that has the path.
|
||||
* In such a case, path "new_name" does not exist as
|
||||
* far as git is concerned.
|
||||
*/
|
||||
if (has_symlink_leading_path(new_name, strlen(new_name)))
|
||||
return 0;
|
||||
struct patch *previous;
|
||||
|
||||
return error(_("%s: already exists in working directory"), new_name);
|
||||
}
|
||||
else if ((errno != ENOENT) && (errno != ENOTDIR))
|
||||
return error("%s: %s", new_name, strerror(errno));
|
||||
return 0;
|
||||
*gone = 0;
|
||||
if (patch->is_copy || patch->is_rename)
|
||||
return NULL; /* "git" patches do not depend on the order */
|
||||
|
||||
previous = in_fn_table(patch->old_name);
|
||||
if (!previous)
|
||||
return NULL;
|
||||
|
||||
if (to_be_deleted(previous))
|
||||
return NULL; /* the deletion hasn't happened yet */
|
||||
|
||||
if (was_deleted(previous))
|
||||
*gone = 1;
|
||||
|
||||
return previous;
|
||||
}
|
||||
|
||||
static int verify_index_match(struct cache_entry *ce, struct stat *st)
|
||||
|
@ -3119,39 +3093,281 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st)
|
|||
return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
|
||||
}
|
||||
|
||||
#define SUBMODULE_PATCH_WITHOUT_INDEX 1
|
||||
|
||||
static int load_patch_target(struct strbuf *buf,
|
||||
struct cache_entry *ce,
|
||||
struct stat *st,
|
||||
const char *name,
|
||||
unsigned expected_mode)
|
||||
{
|
||||
if (cached) {
|
||||
if (read_file_or_gitlink(ce, buf))
|
||||
return error(_("read of %s failed"), name);
|
||||
} else if (name) {
|
||||
if (S_ISGITLINK(expected_mode)) {
|
||||
if (ce)
|
||||
return read_file_or_gitlink(ce, buf);
|
||||
else
|
||||
return SUBMODULE_PATCH_WITHOUT_INDEX;
|
||||
} else {
|
||||
if (read_old_data(st, name, buf))
|
||||
return error(_("read of %s failed"), name);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* We are about to apply "patch"; populate the "image" with the
|
||||
* current version we have, from the working tree or from the index,
|
||||
* depending on the situation e.g. --cached/--index. If we are
|
||||
* applying a non-git patch that incrementally updates the tree,
|
||||
* we read from the result of a previous diff.
|
||||
*/
|
||||
static int load_preimage(struct image *image,
|
||||
struct patch *patch, struct stat *st, struct cache_entry *ce)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
size_t len;
|
||||
char *img;
|
||||
struct patch *previous;
|
||||
int status;
|
||||
|
||||
previous = previous_patch(patch, &status);
|
||||
if (status)
|
||||
return error(_("path %s has been renamed/deleted"),
|
||||
patch->old_name);
|
||||
if (previous) {
|
||||
/* We have a patched copy in memory; use that. */
|
||||
strbuf_add(&buf, previous->result, previous->resultsize);
|
||||
} else {
|
||||
status = load_patch_target(&buf, ce, st,
|
||||
patch->old_name, patch->old_mode);
|
||||
if (status < 0)
|
||||
return status;
|
||||
else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) {
|
||||
/*
|
||||
* There is no way to apply subproject
|
||||
* patch without looking at the index.
|
||||
* NEEDSWORK: shouldn't this be flagged
|
||||
* as an error???
|
||||
*/
|
||||
free_fragment_list(patch->fragments);
|
||||
patch->fragments = NULL;
|
||||
} else if (status) {
|
||||
return error(_("read of %s failed"), patch->old_name);
|
||||
}
|
||||
}
|
||||
|
||||
img = strbuf_detach(&buf, &len);
|
||||
prepare_image(image, img, len, !patch->is_binary);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int three_way_merge(struct image *image,
|
||||
char *path,
|
||||
const unsigned char *base,
|
||||
const unsigned char *ours,
|
||||
const unsigned char *theirs)
|
||||
{
|
||||
mmfile_t base_file, our_file, their_file;
|
||||
mmbuffer_t result = { NULL };
|
||||
int status;
|
||||
|
||||
read_mmblob(&base_file, base);
|
||||
read_mmblob(&our_file, ours);
|
||||
read_mmblob(&their_file, theirs);
|
||||
status = ll_merge(&result, path,
|
||||
&base_file, "base",
|
||||
&our_file, "ours",
|
||||
&their_file, "theirs", NULL);
|
||||
free(base_file.ptr);
|
||||
free(our_file.ptr);
|
||||
free(their_file.ptr);
|
||||
if (status < 0 || !result.ptr) {
|
||||
free(result.ptr);
|
||||
return -1;
|
||||
}
|
||||
clear_image(image);
|
||||
image->buf = result.ptr;
|
||||
image->len = result.size;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* When directly falling back to add/add three-way merge, we read from
|
||||
* the current contents of the new_name. In no cases other than that
|
||||
* this function will be called.
|
||||
*/
|
||||
static int load_current(struct image *image, struct patch *patch)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int status, pos;
|
||||
size_t len;
|
||||
char *img;
|
||||
struct stat st;
|
||||
struct cache_entry *ce;
|
||||
char *name = patch->new_name;
|
||||
unsigned mode = patch->new_mode;
|
||||
|
||||
if (!patch->is_new)
|
||||
die("BUG: patch to %s is not a creation", patch->old_name);
|
||||
|
||||
pos = cache_name_pos(name, strlen(name));
|
||||
if (pos < 0)
|
||||
return error(_("%s: does not exist in index"), name);
|
||||
ce = active_cache[pos];
|
||||
if (lstat(name, &st)) {
|
||||
if (errno != ENOENT)
|
||||
return error(_("%s: %s"), name, strerror(errno));
|
||||
if (checkout_target(ce, &st))
|
||||
return -1;
|
||||
}
|
||||
if (verify_index_match(ce, &st))
|
||||
return error(_("%s: does not match index"), name);
|
||||
|
||||
status = load_patch_target(&buf, ce, &st, name, mode);
|
||||
if (status < 0)
|
||||
return status;
|
||||
else if (status)
|
||||
return -1;
|
||||
img = strbuf_detach(&buf, &len);
|
||||
prepare_image(image, img, len, !patch->is_binary);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int try_threeway(struct image *image, struct patch *patch,
|
||||
struct stat *st, struct cache_entry *ce)
|
||||
{
|
||||
unsigned char pre_sha1[20], post_sha1[20], our_sha1[20];
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
size_t len;
|
||||
int status;
|
||||
char *img;
|
||||
struct image tmp_image;
|
||||
|
||||
/* No point falling back to 3-way merge in these cases */
|
||||
if (patch->is_delete ||
|
||||
S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode))
|
||||
return -1;
|
||||
|
||||
/* Preimage the patch was prepared for */
|
||||
if (patch->is_new)
|
||||
write_sha1_file("", 0, blob_type, pre_sha1);
|
||||
else if (get_sha1(patch->old_sha1_prefix, pre_sha1) ||
|
||||
read_blob_object(&buf, pre_sha1, patch->old_mode))
|
||||
return error("repository lacks the necessary blob to fall back on 3-way merge.");
|
||||
|
||||
fprintf(stderr, "Falling back to three-way merge...\n");
|
||||
|
||||
img = strbuf_detach(&buf, &len);
|
||||
prepare_image(&tmp_image, img, len, 1);
|
||||
/* Apply the patch to get the post image */
|
||||
if (apply_fragments(&tmp_image, patch) < 0) {
|
||||
clear_image(&tmp_image);
|
||||
return -1;
|
||||
}
|
||||
/* post_sha1[] is theirs */
|
||||
write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, post_sha1);
|
||||
clear_image(&tmp_image);
|
||||
|
||||
/* our_sha1[] is ours */
|
||||
if (patch->is_new) {
|
||||
if (load_current(&tmp_image, patch))
|
||||
return error("cannot read the current contents of '%s'",
|
||||
patch->new_name);
|
||||
} else {
|
||||
if (load_preimage(&tmp_image, patch, st, ce))
|
||||
return error("cannot read the current contents of '%s'",
|
||||
patch->old_name);
|
||||
}
|
||||
write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, our_sha1);
|
||||
clear_image(&tmp_image);
|
||||
|
||||
/* in-core three-way merge between post and our using pre as base */
|
||||
status = three_way_merge(image, patch->new_name,
|
||||
pre_sha1, our_sha1, post_sha1);
|
||||
if (status < 0) {
|
||||
fprintf(stderr, "Failed to fall back on three-way merge...\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
patch->conflicted_threeway = 1;
|
||||
if (patch->is_new)
|
||||
hashclr(patch->threeway_stage[0]);
|
||||
else
|
||||
hashcpy(patch->threeway_stage[0], pre_sha1);
|
||||
hashcpy(patch->threeway_stage[1], our_sha1);
|
||||
hashcpy(patch->threeway_stage[2], post_sha1);
|
||||
fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name);
|
||||
} else {
|
||||
fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
|
||||
{
|
||||
struct image image;
|
||||
|
||||
if (load_preimage(&image, patch, st, ce) < 0)
|
||||
return -1;
|
||||
|
||||
if (patch->direct_to_threeway ||
|
||||
apply_fragments(&image, patch) < 0) {
|
||||
/* Note: with --reject, apply_fragments() returns 0 */
|
||||
if (!threeway || try_threeway(&image, patch, st, ce) < 0)
|
||||
return -1;
|
||||
}
|
||||
patch->result = image.buf;
|
||||
patch->resultsize = image.len;
|
||||
add_to_fn_table(patch);
|
||||
free(image.line_allocated);
|
||||
|
||||
if (0 < patch->is_delete && patch->resultsize)
|
||||
return error(_("removal patch leaves file contents"));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If "patch" that we are looking at modifies or deletes what we have,
|
||||
* we would want it not to lose any local modification we have, either
|
||||
* in the working tree or in the index.
|
||||
*
|
||||
* This also decides if a non-git patch is a creation patch or a
|
||||
* modification to an existing empty file. We do not check the state
|
||||
* of the current tree for a creation patch in this function; the caller
|
||||
* check_patch() separately makes sure (and errors out otherwise) that
|
||||
* the path the patch creates does not exist in the current tree.
|
||||
*/
|
||||
static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
|
||||
{
|
||||
const char *old_name = patch->old_name;
|
||||
struct patch *tpatch = NULL;
|
||||
int stat_ret = 0;
|
||||
struct patch *previous = NULL;
|
||||
int stat_ret = 0, status;
|
||||
unsigned st_mode = 0;
|
||||
|
||||
/*
|
||||
* Make sure that we do not have local modifications from the
|
||||
* index when we are looking at the index. Also make sure
|
||||
* we have the preimage file to be patched in the work tree,
|
||||
* unless --cached, which tells git to apply only in the index.
|
||||
*/
|
||||
if (!old_name)
|
||||
return 0;
|
||||
|
||||
assert(patch->is_new <= 0);
|
||||
previous = previous_patch(patch, &status);
|
||||
|
||||
if (!(patch->is_copy || patch->is_rename) &&
|
||||
(tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
|
||||
if (was_deleted(tpatch))
|
||||
return error(_("%s: has been deleted/renamed"), old_name);
|
||||
st_mode = tpatch->new_mode;
|
||||
if (status)
|
||||
return error(_("path %s has been renamed/deleted"), old_name);
|
||||
if (previous) {
|
||||
st_mode = previous->new_mode;
|
||||
} else if (!cached) {
|
||||
stat_ret = lstat(old_name, st);
|
||||
if (stat_ret && errno != ENOENT)
|
||||
return error(_("%s: %s"), old_name, strerror(errno));
|
||||
}
|
||||
|
||||
if (to_be_deleted(tpatch))
|
||||
tpatch = NULL;
|
||||
|
||||
if (check_index && !tpatch) {
|
||||
if (check_index && !previous) {
|
||||
int pos = cache_name_pos(old_name, strlen(old_name));
|
||||
if (pos < 0) {
|
||||
if (patch->is_new < 0)
|
||||
|
@ -3160,13 +3376,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
|
|||
}
|
||||
*ce = active_cache[pos];
|
||||
if (stat_ret < 0) {
|
||||
struct checkout costate;
|
||||
/* checkout */
|
||||
memset(&costate, 0, sizeof(costate));
|
||||
costate.base_dir = "";
|
||||
costate.refresh_cache = 1;
|
||||
if (checkout_entry(*ce, &costate, NULL) ||
|
||||
lstat(old_name, st))
|
||||
if (checkout_target(*ce, st))
|
||||
return -1;
|
||||
}
|
||||
if (!cached && verify_index_match(*ce, st))
|
||||
|
@ -3179,7 +3389,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
|
|||
return error(_("%s: %s"), old_name, strerror(errno));
|
||||
}
|
||||
|
||||
if (!cached && !tpatch)
|
||||
if (!cached && !previous)
|
||||
st_mode = ce_mode_from_stat(*ce, st->st_mode);
|
||||
|
||||
if (patch->is_new < 0)
|
||||
|
@ -3203,6 +3413,41 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#define EXISTS_IN_INDEX 1
|
||||
#define EXISTS_IN_WORKTREE 2
|
||||
|
||||
static int check_to_create(const char *new_name, int ok_if_exists)
|
||||
{
|
||||
struct stat nst;
|
||||
|
||||
if (check_index &&
|
||||
cache_name_pos(new_name, strlen(new_name)) >= 0 &&
|
||||
!ok_if_exists)
|
||||
return EXISTS_IN_INDEX;
|
||||
if (cached)
|
||||
return 0;
|
||||
|
||||
if (!lstat(new_name, &nst)) {
|
||||
if (S_ISDIR(nst.st_mode) || ok_if_exists)
|
||||
return 0;
|
||||
/*
|
||||
* A leading component of new_name might be a symlink
|
||||
* that is going to be removed with this patch, but
|
||||
* still pointing at somewhere that has the path.
|
||||
* In such a case, path "new_name" does not exist as
|
||||
* far as git is concerned.
|
||||
*/
|
||||
if (has_symlink_leading_path(new_name, strlen(new_name)))
|
||||
return 0;
|
||||
|
||||
return EXISTS_IN_WORKTREE;
|
||||
} else if ((errno != ENOENT) && (errno != ENOTDIR)) {
|
||||
return error("%s: %s", new_name, strerror(errno));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check and apply the patch in-core; leave the result in patch->result
|
||||
* for the caller to write it out to the final destination.
|
||||
|
@ -3225,31 +3470,45 @@ static int check_patch(struct patch *patch)
|
|||
return status;
|
||||
old_name = patch->old_name;
|
||||
|
||||
/*
|
||||
* A type-change diff is always split into a patch to delete
|
||||
* old, immediately followed by a patch to create new (see
|
||||
* diff.c::run_diff()); in such a case it is Ok that the entry
|
||||
* to be deleted by the previous patch is still in the working
|
||||
* tree and in the index.
|
||||
*
|
||||
* A patch to swap-rename between A and B would first rename A
|
||||
* to B and then rename B to A. While applying the first one,
|
||||
* the presense of B should not stop A from getting renamed to
|
||||
* B; ask to_be_deleted() about the later rename. Removal of
|
||||
* B and rename from A to B is handled the same way by asking
|
||||
* was_deleted().
|
||||
*/
|
||||
if ((tpatch = in_fn_table(new_name)) &&
|
||||
(was_deleted(tpatch) || to_be_deleted(tpatch)))
|
||||
/*
|
||||
* A type-change diff is always split into a patch to
|
||||
* delete old, immediately followed by a patch to
|
||||
* create new (see diff.c::run_diff()); in such a case
|
||||
* it is Ok that the entry to be deleted by the
|
||||
* previous patch is still in the working tree and in
|
||||
* the index.
|
||||
*/
|
||||
(was_deleted(tpatch) || to_be_deleted(tpatch)))
|
||||
ok_if_exists = 1;
|
||||
else
|
||||
ok_if_exists = 0;
|
||||
|
||||
if (new_name &&
|
||||
((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
|
||||
if (check_index &&
|
||||
cache_name_pos(new_name, strlen(new_name)) >= 0 &&
|
||||
!ok_if_exists)
|
||||
int err = check_to_create(new_name, ok_if_exists);
|
||||
|
||||
if (err && threeway) {
|
||||
patch->direct_to_threeway = 1;
|
||||
} else switch (err) {
|
||||
case 0:
|
||||
break; /* happy */
|
||||
case EXISTS_IN_INDEX:
|
||||
return error(_("%s: already exists in index"), new_name);
|
||||
if (!cached) {
|
||||
int err = check_to_create_blob(new_name, ok_if_exists);
|
||||
if (err)
|
||||
return err;
|
||||
break;
|
||||
case EXISTS_IN_WORKTREE:
|
||||
return error(_("%s: already exists in working directory"),
|
||||
new_name);
|
||||
default:
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!patch->new_mode) {
|
||||
if (0 < patch->is_new)
|
||||
patch->new_mode = S_IFREG | 0644;
|
||||
|
@ -3612,6 +3871,32 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
|
|||
die_errno(_("unable to write file '%s' mode %o"), path, mode);
|
||||
}
|
||||
|
||||
static void add_conflicted_stages_file(struct patch *patch)
|
||||
{
|
||||
int stage, namelen;
|
||||
unsigned ce_size, mode;
|
||||
struct cache_entry *ce;
|
||||
|
||||
if (!update_index)
|
||||
return;
|
||||
namelen = strlen(patch->new_name);
|
||||
ce_size = cache_entry_size(namelen);
|
||||
mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644);
|
||||
|
||||
remove_file_from_cache(patch->new_name);
|
||||
for (stage = 1; stage < 4; stage++) {
|
||||
if (is_null_sha1(patch->threeway_stage[stage - 1]))
|
||||
continue;
|
||||
ce = xcalloc(1, ce_size);
|
||||
memcpy(ce->name, patch->new_name, namelen);
|
||||
ce->ce_mode = create_ce_mode(mode);
|
||||
ce->ce_flags = create_ce_flags(namelen, stage);
|
||||
hashcpy(ce->sha1, patch->threeway_stage[stage - 1]);
|
||||
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
|
||||
die(_("unable to add cache entry for %s"), patch->new_name);
|
||||
}
|
||||
}
|
||||
|
||||
static void create_file(struct patch *patch)
|
||||
{
|
||||
char *path = patch->new_name;
|
||||
|
@ -3622,7 +3907,11 @@ static void create_file(struct patch *patch)
|
|||
if (!mode)
|
||||
mode = S_IFREG | 0644;
|
||||
create_one_file(path, mode, buf, size);
|
||||
add_index_file(path, mode, buf, size);
|
||||
|
||||
if (patch->conflicted_threeway)
|
||||
add_conflicted_stages_file(patch);
|
||||
else
|
||||
add_index_file(path, mode, buf, size);
|
||||
}
|
||||
|
||||
/* phase zero is to remove, phase one is to create */
|
||||
|
@ -3724,6 +4013,7 @@ static int write_out_results(struct patch *list)
|
|||
int phase;
|
||||
int errs = 0;
|
||||
struct patch *l;
|
||||
struct string_list cpath = STRING_LIST_INIT_DUP;
|
||||
|
||||
for (phase = 0; phase < 2; phase++) {
|
||||
l = list;
|
||||
|
@ -3732,12 +4022,30 @@ static int write_out_results(struct patch *list)
|
|||
errs = 1;
|
||||
else {
|
||||
write_out_one_result(l, phase);
|
||||
if (phase == 1 && write_out_one_reject(l))
|
||||
errs = 1;
|
||||
if (phase == 1) {
|
||||
if (write_out_one_reject(l))
|
||||
errs = 1;
|
||||
if (l->conflicted_threeway) {
|
||||
string_list_append(&cpath, l->new_name);
|
||||
errs = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
l = l->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (cpath.nr) {
|
||||
struct string_list_item *item;
|
||||
|
||||
sort_string_list(&cpath);
|
||||
for_each_string_list_item(item, &cpath)
|
||||
fprintf(stderr, "U %s\n", item->string);
|
||||
string_list_clear(&cpath, 0);
|
||||
|
||||
rerere(0);
|
||||
}
|
||||
|
||||
return errs;
|
||||
}
|
||||
|
||||
|
@ -3860,8 +4168,12 @@ static int apply_patch(int fd, const char *filename, int options)
|
|||
!apply_with_reject)
|
||||
exit(1);
|
||||
|
||||
if (apply && write_out_results(list))
|
||||
exit(1);
|
||||
if (apply && write_out_results(list)) {
|
||||
if (apply_with_reject)
|
||||
exit(1);
|
||||
/* with --3way, we still need to write the index out */
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (fake_ancestor)
|
||||
build_fake_ancestor(list, fake_ancestor);
|
||||
|
@ -3994,6 +4306,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
|
|||
N_("apply a patch without touching the working tree")),
|
||||
OPT_BOOLEAN(0, "apply", &force_apply,
|
||||
N_("also apply the patch (use with --stat/--summary/--check)")),
|
||||
OPT_BOOL('3', "3way", &threeway,
|
||||
N_( "attempt three-way merge if a patch does not apply")),
|
||||
OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
|
||||
N_("build a temporary index based on embedded index information")),
|
||||
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
|
||||
|
@ -4042,6 +4356,15 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
|
|||
argc = parse_options(argc, argv, prefix, builtin_apply_options,
|
||||
apply_usage, 0);
|
||||
|
||||
if (apply_with_reject && threeway)
|
||||
die("--reject and --3way cannot be used together.");
|
||||
if (cached && threeway)
|
||||
die("--cached and --3way cannot be used together.");
|
||||
if (threeway) {
|
||||
if (is_not_gitdir)
|
||||
die(_("--3way outside a repository"));
|
||||
check_index = 1;
|
||||
}
|
||||
if (apply_with_reject)
|
||||
apply = apply_verbosely = 1;
|
||||
if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
|
||||
|
|
157
t/t4108-apply-threeway.sh
Executable file
157
t/t4108-apply-threeway.sh
Executable file
|
@ -0,0 +1,157 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='git apply --3way'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
create_file () {
|
||||
for i
|
||||
do
|
||||
echo "$i"
|
||||
done
|
||||
}
|
||||
|
||||
sanitize_conflicted_diff () {
|
||||
sed -e '
|
||||
/^index /d
|
||||
s/^\(+[<>][<>][<>][<>]*\) .*/\1/
|
||||
'
|
||||
}
|
||||
|
||||
test_expect_success setup '
|
||||
test_tick &&
|
||||
create_file >one 1 2 3 4 5 6 7 &&
|
||||
cat one >two &&
|
||||
git add one two &&
|
||||
git commit -m initial &&
|
||||
|
||||
git branch side &&
|
||||
|
||||
test_tick &&
|
||||
create_file >one 1 two 3 4 5 six 7 &&
|
||||
create_file >two 1 two 3 4 5 6 7 &&
|
||||
git commit -a -m master &&
|
||||
|
||||
git checkout side &&
|
||||
create_file >one 1 2 3 4 five 6 7 &&
|
||||
create_file >two 1 2 3 4 five 6 7 &&
|
||||
git commit -a -m side &&
|
||||
|
||||
git checkout master
|
||||
'
|
||||
|
||||
test_expect_success 'apply without --3way' '
|
||||
git diff side^ side >P.diff &&
|
||||
|
||||
# should fail to apply
|
||||
git reset --hard &&
|
||||
git checkout master^0 &&
|
||||
test_must_fail git apply --index P.diff &&
|
||||
# should leave things intact
|
||||
git diff-files --exit-code &&
|
||||
git diff-index --exit-code --cached HEAD
|
||||
'
|
||||
|
||||
test_expect_success 'apply with --3way' '
|
||||
# Merging side should be similar to applying this patch
|
||||
git diff ...side >P.diff &&
|
||||
|
||||
# The corresponding conflicted merge
|
||||
git reset --hard &&
|
||||
git checkout master^0 &&
|
||||
test_must_fail git merge --no-commit side &&
|
||||
git ls-files -s >expect.ls &&
|
||||
git diff HEAD | sanitize_conflicted_diff >expect.diff &&
|
||||
|
||||
# should fail to apply
|
||||
git reset --hard &&
|
||||
git checkout master^0 &&
|
||||
test_must_fail git apply --index --3way P.diff &&
|
||||
git ls-files -s >actual.ls &&
|
||||
git diff HEAD | sanitize_conflicted_diff >actual.diff &&
|
||||
|
||||
# The result should resemble the corresponding merge
|
||||
test_cmp expect.ls actual.ls &&
|
||||
test_cmp expect.diff actual.diff
|
||||
'
|
||||
|
||||
test_expect_success 'apply with --3way with rerere enabled' '
|
||||
git config rerere.enabled true &&
|
||||
|
||||
# Merging side should be similar to applying this patch
|
||||
git diff ...side >P.diff &&
|
||||
|
||||
# The corresponding conflicted merge
|
||||
git reset --hard &&
|
||||
git checkout master^0 &&
|
||||
test_must_fail git merge --no-commit side &&
|
||||
|
||||
# Manually resolve and record the resolution
|
||||
create_file 1 two 3 4 five six 7 >one &&
|
||||
git rerere &&
|
||||
cat one >expect &&
|
||||
|
||||
# should fail to apply
|
||||
git reset --hard &&
|
||||
git checkout master^0 &&
|
||||
test_must_fail git apply --index --3way P.diff &&
|
||||
|
||||
# but rerere should have replayed the recorded resolution
|
||||
test_cmp expect one
|
||||
'
|
||||
|
||||
test_expect_success 'apply -3 with add/add conflict setup' '
|
||||
git reset --hard &&
|
||||
|
||||
git checkout -b adder &&
|
||||
create_file 1 2 3 4 5 6 7 >three &&
|
||||
create_file 1 2 3 4 5 6 7 >four &&
|
||||
git add three four &&
|
||||
git commit -m "add three and four" &&
|
||||
|
||||
git checkout -b another adder^ &&
|
||||
create_file 1 2 3 4 5 6 7 >three &&
|
||||
create_file 1 2 3 four 5 6 7 >four &&
|
||||
git add three four &&
|
||||
git commit -m "add three and four" &&
|
||||
|
||||
# Merging another should be similar to applying this patch
|
||||
git diff adder...another >P.diff &&
|
||||
|
||||
git checkout adder^0 &&
|
||||
test_must_fail git merge --no-commit another &&
|
||||
git ls-files -s >expect.ls &&
|
||||
git diff HEAD | sanitize_conflicted_diff >expect.diff
|
||||
'
|
||||
|
||||
test_expect_success 'apply -3 with add/add conflict' '
|
||||
# should fail to apply ...
|
||||
git reset --hard &&
|
||||
git checkout adder^0 &&
|
||||
test_must_fail git apply --index --3way P.diff &&
|
||||
# ... and leave conflicts in the index and in the working tree
|
||||
git ls-files -s >actual.ls &&
|
||||
git diff HEAD | sanitize_conflicted_diff >actual.diff &&
|
||||
|
||||
# The result should resemble the corresponding merge
|
||||
test_cmp expect.ls actual.ls &&
|
||||
test_cmp expect.diff actual.diff
|
||||
'
|
||||
|
||||
test_expect_success 'apply -3 with add/add conflict (dirty working tree)' '
|
||||
# should fail to apply ...
|
||||
git reset --hard &&
|
||||
git checkout adder^0 &&
|
||||
echo >>four &&
|
||||
cat four >four.save &&
|
||||
cat three >three.save &&
|
||||
git ls-files -s >expect.ls &&
|
||||
test_must_fail git apply --index --3way P.diff &&
|
||||
# ... and should not touch anything
|
||||
git ls-files -s >actual.ls &&
|
||||
test_cmp expect.ls actual.ls &&
|
||||
test_cmp four.save four &&
|
||||
test_cmp three.save three
|
||||
'
|
||||
|
||||
test_done
|
|
@ -46,6 +46,14 @@ test_expect_success setup '
|
|||
cat file1 >saved.file1
|
||||
'
|
||||
|
||||
test_expect_success 'apply --reject is incompatible with --3way' '
|
||||
test_when_finished "cat saved.file1 >file1" &&
|
||||
git diff >patch.0 &&
|
||||
git checkout file1 &&
|
||||
test_must_fail git apply --reject --3way patch.0 &&
|
||||
git diff --exit-code
|
||||
'
|
||||
|
||||
test_expect_success 'apply without --reject should fail' '
|
||||
|
||||
if git apply patch.1
|
||||
|
|
Loading…
Reference in a new issue