2008-08-12 16:45:14 +00:00
|
|
|
/*
|
|
|
|
* Recursive Merge algorithm stolen from git-merge-recursive.py by
|
|
|
|
* Fredrik Kuivinen.
|
|
|
|
* The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
|
|
|
|
*/
|
2023-05-16 06:33:57 +00:00
|
|
|
#include "git-compat-util.h"
|
2019-08-17 18:41:44 +00:00
|
|
|
#include "merge-recursive.h"
|
|
|
|
|
|
|
|
#include "alloc.h"
|
|
|
|
#include "cache-tree.h"
|
|
|
|
#include "commit.h"
|
|
|
|
#include "commit-reach.h"
|
|
|
|
#include "config.h"
|
2008-08-12 16:45:14 +00:00
|
|
|
#include "diff.h"
|
|
|
|
#include "diffcore.h"
|
2019-08-17 18:41:44 +00:00
|
|
|
#include "dir.h"
|
2023-03-21 06:26:03 +00:00
|
|
|
#include "environment.h"
|
2023-03-21 06:25:54 +00:00
|
|
|
#include "gettext.h"
|
2023-02-24 00:09:27 +00:00
|
|
|
#include "hex.h"
|
merge-ll: rename from ll-merge
A long term (but rather minor) pet-peeve of mine was the name
ll-merge.[ch]. I thought it made it harder to realize what stuff was
related to merging when I was working on the merge machinery and trying
to improve it.
Further, back in d1cbe1e6d8a ("hash-ll.h: split out of hash.h to remove
dependency on repository.h", 2023-04-22), we have split the portions of
hash.h that do not depend upon repository.h into a "hash-ll.h" (due to
the recommendation to use "ll" for "low-level" in its name[1], but which
I used as a suffix precisely because of my distaste for "ll-merge").
When we discussed adding additional "*-ll.h" files, a request was made
that we use "ll" consistently as either a prefix or a suffix. Since it
is already in use as both a prefix and a suffix, the only way to do so
is to rename some files.
Besides my distaste for the ll-merge.[ch] name, let me also note that
the files
ll-fsmonitor.h, ll-hash.h, ll-merge.h, ll-object-store.h, ll-read-cache.h
would have essentially nothing to do with each other and make no sense
to group. But giving them the common "ll-" prefix would group them. Using
"-ll" as a suffix thus seems just much more logical to me. Rename
ll-merge.[ch] to merge-ll.[ch] to achieve this consistency, and to
ensure we get a more logical grouping of files.
[1] https://lore.kernel.org/git/kl6lsfcu1g8w.fsf@chooglen-macbookpro.roam.corp.google.com/
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-05-16 06:34:04 +00:00
|
|
|
#include "merge-ll.h"
|
2019-08-17 18:41:44 +00:00
|
|
|
#include "lockfile.h"
|
2023-04-22 20:17:15 +00:00
|
|
|
#include "match-trees.h"
|
2023-05-16 06:33:50 +00:00
|
|
|
#include "name-hash.h"
|
2023-04-11 07:41:53 +00:00
|
|
|
#include "object-file.h"
|
2023-04-11 07:41:49 +00:00
|
|
|
#include "object-name.h"
|
2023-05-16 06:34:06 +00:00
|
|
|
#include "object-store-ll.h"
|
2023-05-16 06:33:59 +00:00
|
|
|
#include "path.h"
|
2019-08-17 18:41:44 +00:00
|
|
|
#include "repository.h"
|
|
|
|
#include "revision.h"
|
2023-05-16 06:33:51 +00:00
|
|
|
#include "sparse-index.h"
|
2019-08-17 18:41:44 +00:00
|
|
|
#include "string-list.h"
|
2023-04-22 20:17:09 +00:00
|
|
|
#include "symlinks.h"
|
2008-08-12 16:45:14 +00:00
|
|
|
#include "tag.h"
|
2019-08-17 18:41:44 +00:00
|
|
|
#include "tree-walk.h"
|
2008-08-12 16:45:14 +00:00
|
|
|
#include "unpack-trees.h"
|
|
|
|
#include "xdiff-interface.h"
|
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
struct merge_options_internal {
|
|
|
|
int call_depth;
|
|
|
|
int needed_rename_limit;
|
|
|
|
struct hashmap current_file_dir_set;
|
|
|
|
struct string_list df_conflict_file_set;
|
|
|
|
struct unpack_trees_options unpack_opts;
|
|
|
|
struct index_state orig_index;
|
|
|
|
};
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2017-09-07 16:25:56 +00:00
|
|
|
struct path_hashmap_entry {
|
|
|
|
struct hashmap_entry e;
|
|
|
|
char path[FLEX_ARRAY];
|
|
|
|
};
|
|
|
|
|
2022-08-25 17:09:48 +00:00
|
|
|
static int path_hashmap_cmp(const void *cmp_data UNUSED,
|
2019-10-06 23:30:37 +00:00
|
|
|
const struct hashmap_entry *eptr,
|
|
|
|
const struct hashmap_entry *entry_or_key,
|
2017-09-07 16:25:56 +00:00
|
|
|
const void *keydata)
|
|
|
|
{
|
2019-10-06 23:30:37 +00:00
|
|
|
const struct path_hashmap_entry *a, *b;
|
2017-09-07 16:25:56 +00:00
|
|
|
const char *key = keydata;
|
|
|
|
|
2019-10-06 23:30:37 +00:00
|
|
|
a = container_of(eptr, const struct path_hashmap_entry, e);
|
|
|
|
b = container_of(entry_or_key, const struct path_hashmap_entry, e);
|
|
|
|
|
2021-08-28 21:30:49 +00:00
|
|
|
return fspathcmp(a->path, key ? key : b->path);
|
2017-09-07 16:25:56 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:37 +00:00
|
|
|
/*
|
|
|
|
* For dir_rename_entry, directory names are stored as a full path from the
|
|
|
|
* toplevel of the repository and do not include a trailing '/'. Also:
|
|
|
|
*
|
|
|
|
* dir: original name of directory being renamed
|
|
|
|
* non_unique_new_dir: if true, could not determine new_dir
|
|
|
|
* new_dir: final name of directory being renamed
|
|
|
|
* possible_new_dirs: temporary used to help determine new_dir; see comments
|
|
|
|
* in get_directory_renames() for details
|
|
|
|
*/
|
|
|
|
struct dir_rename_entry {
|
2019-10-15 04:48:01 +00:00
|
|
|
struct hashmap_entry ent;
|
2019-08-17 18:41:37 +00:00
|
|
|
char *dir;
|
|
|
|
unsigned non_unique_new_dir:1;
|
|
|
|
struct strbuf new_dir;
|
|
|
|
struct string_list possible_new_dirs;
|
|
|
|
};
|
|
|
|
|
2018-04-19 17:58:05 +00:00
|
|
|
static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,
|
|
|
|
char *dir)
|
|
|
|
{
|
|
|
|
struct dir_rename_entry key;
|
|
|
|
|
2022-05-02 16:50:37 +00:00
|
|
|
if (!dir)
|
2018-04-19 17:58:05 +00:00
|
|
|
return NULL;
|
2019-10-06 23:30:27 +00:00
|
|
|
hashmap_entry_init(&key.ent, strhash(dir));
|
2018-04-19 17:58:05 +00:00
|
|
|
key.dir = dir;
|
2019-10-06 23:30:42 +00:00
|
|
|
return hashmap_get_entry(hashmap, &key, ent, NULL);
|
2018-04-19 17:58:05 +00:00
|
|
|
}
|
|
|
|
|
2022-08-25 17:09:48 +00:00
|
|
|
static int dir_rename_cmp(const void *cmp_data UNUSED,
|
2019-10-06 23:30:37 +00:00
|
|
|
const struct hashmap_entry *eptr,
|
|
|
|
const struct hashmap_entry *entry_or_key,
|
2022-08-25 17:09:48 +00:00
|
|
|
const void *keydata UNUSED)
|
2018-04-19 17:58:05 +00:00
|
|
|
{
|
2019-10-06 23:30:37 +00:00
|
|
|
const struct dir_rename_entry *e1, *e2;
|
|
|
|
|
|
|
|
e1 = container_of(eptr, const struct dir_rename_entry, ent);
|
|
|
|
e2 = container_of(entry_or_key, const struct dir_rename_entry, ent);
|
2018-04-19 17:58:05 +00:00
|
|
|
|
|
|
|
return strcmp(e1->dir, e2->dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dir_rename_init(struct hashmap *map)
|
|
|
|
{
|
|
|
|
hashmap_init(map, dir_rename_cmp, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dir_rename_entry_init(struct dir_rename_entry *entry,
|
|
|
|
char *directory)
|
|
|
|
{
|
2019-10-06 23:30:27 +00:00
|
|
|
hashmap_entry_init(&entry->ent, strhash(directory));
|
2018-04-19 17:58:05 +00:00
|
|
|
entry->dir = directory;
|
|
|
|
entry->non_unique_new_dir = 0;
|
|
|
|
strbuf_init(&entry->new_dir, 0);
|
2021-07-01 10:51:29 +00:00
|
|
|
string_list_init_nodup(&entry->possible_new_dirs);
|
2018-04-19 17:58:05 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:37 +00:00
|
|
|
struct collision_entry {
|
2019-10-15 04:48:01 +00:00
|
|
|
struct hashmap_entry ent;
|
2019-08-17 18:41:37 +00:00
|
|
|
char *target_file;
|
|
|
|
struct string_list source_files;
|
|
|
|
unsigned reported_already:1;
|
|
|
|
};
|
|
|
|
|
2018-04-19 17:58:07 +00:00
|
|
|
static struct collision_entry *collision_find_entry(struct hashmap *hashmap,
|
|
|
|
char *target_file)
|
|
|
|
{
|
|
|
|
struct collision_entry key;
|
|
|
|
|
2019-10-06 23:30:27 +00:00
|
|
|
hashmap_entry_init(&key.ent, strhash(target_file));
|
2018-04-19 17:58:07 +00:00
|
|
|
key.target_file = target_file;
|
2019-10-06 23:30:42 +00:00
|
|
|
return hashmap_get_entry(hashmap, &key, ent, NULL);
|
2018-04-19 17:58:07 +00:00
|
|
|
}
|
|
|
|
|
2022-08-25 17:09:48 +00:00
|
|
|
static int collision_cmp(const void *cmp_data UNUSED,
|
2019-10-06 23:30:37 +00:00
|
|
|
const struct hashmap_entry *eptr,
|
|
|
|
const struct hashmap_entry *entry_or_key,
|
2022-08-25 17:09:48 +00:00
|
|
|
const void *keydata UNUSED)
|
2018-04-19 17:58:07 +00:00
|
|
|
{
|
2019-10-06 23:30:37 +00:00
|
|
|
const struct collision_entry *e1, *e2;
|
|
|
|
|
|
|
|
e1 = container_of(eptr, const struct collision_entry, ent);
|
|
|
|
e2 = container_of(entry_or_key, const struct collision_entry, ent);
|
|
|
|
|
2018-04-19 17:58:07 +00:00
|
|
|
return strcmp(e1->target_file, e2->target_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void collision_init(struct hashmap *map)
|
|
|
|
{
|
2019-10-06 23:30:37 +00:00
|
|
|
hashmap_init(map, collision_cmp, NULL, 0);
|
2018-04-19 17:58:07 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static void flush_output(struct merge_options *opt)
|
2016-08-01 11:44:37 +00:00
|
|
|
{
|
2019-04-05 15:00:13 +00:00
|
|
|
if (opt->buffer_output < 2 && opt->obuf.len) {
|
|
|
|
fputs(opt->obuf.buf, stdout);
|
|
|
|
strbuf_reset(&opt->obuf);
|
2016-08-01 11:44:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-13 08:05:18 +00:00
|
|
|
__attribute__((format (printf, 2, 3)))
|
2019-04-05 15:00:13 +00:00
|
|
|
static int err(struct merge_options *opt, const char *err, ...)
|
2016-08-01 11:44:37 +00:00
|
|
|
{
|
|
|
|
va_list params;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (opt->buffer_output < 2)
|
|
|
|
flush_output(opt);
|
2016-08-01 11:44:50 +00:00
|
|
|
else {
|
2019-04-05 15:00:13 +00:00
|
|
|
strbuf_complete(&opt->obuf, '\n');
|
|
|
|
strbuf_addstr(&opt->obuf, "error: ");
|
2016-08-01 11:44:50 +00:00
|
|
|
}
|
2016-08-01 11:44:37 +00:00
|
|
|
va_start(params, err);
|
2019-04-05 15:00:13 +00:00
|
|
|
strbuf_vaddf(&opt->obuf, err, params);
|
2016-08-01 11:44:37 +00:00
|
|
|
va_end(params);
|
2019-04-05 15:00:13 +00:00
|
|
|
if (opt->buffer_output > 1)
|
|
|
|
strbuf_addch(&opt->obuf, '\n');
|
2016-08-01 11:44:50 +00:00
|
|
|
else {
|
2019-04-05 15:00:13 +00:00
|
|
|
error("%s", opt->obuf.buf);
|
|
|
|
strbuf_reset(&opt->obuf);
|
2016-08-01 11:44:50 +00:00
|
|
|
}
|
2016-08-01 11:44:37 +00:00
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-01-12 02:13:30 +00:00
|
|
|
static struct tree *shift_tree_object(struct repository *repo,
|
|
|
|
struct tree *one, struct tree *two,
|
2008-07-01 05:18:57 +00:00
|
|
|
const char *subtree_shift)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2015-11-10 02:22:28 +00:00
|
|
|
struct object_id shifted;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2008-07-01 05:18:57 +00:00
|
|
|
if (!*subtree_shift) {
|
2019-06-27 09:28:51 +00:00
|
|
|
shift_tree(repo, &one->object.oid, &two->object.oid, &shifted, 0);
|
2008-07-01 05:18:57 +00:00
|
|
|
} else {
|
2019-06-27 09:28:51 +00:00
|
|
|
shift_tree_by(repo, &one->object.oid, &two->object.oid, &shifted,
|
2008-07-01 05:18:57 +00:00
|
|
|
subtree_shift);
|
|
|
|
}
|
convert "oidcmp() == 0" to oideq()
Using the more restrictive oideq() should, in the long run,
give the compiler more opportunities to optimize these
callsites. For now, this conversion should be a complete
noop with respect to the generated code.
The result is also perhaps a little more readable, as it
avoids the "zero is equal" idiom. Since it's so prevalent in
C, I think seasoned programmers tend not to even notice it
anymore, but it can sometimes make for awkward double
negations (e.g., we can drop a few !!oidcmp() instances
here).
This patch was generated almost entirely by the included
coccinelle patch. This mechanical conversion should be
completely safe, because we check explicitly for cases where
oidcmp() is compared to 0, which is what oideq() is doing
under the hood. Note that we don't have to catch "!oidcmp()"
separately; coccinelle's standard isomorphisms make sure the
two are treated equivalently.
I say "almost" because I did hand-edit the coccinelle output
to fix up a few style violations (it mostly keeps the
original formatting, but sometimes unwraps long lines).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-28 21:22:40 +00:00
|
|
|
if (oideq(&two->object.oid, &shifted))
|
2008-08-12 16:45:14 +00:00
|
|
|
return two;
|
2019-01-12 02:13:30 +00:00
|
|
|
return lookup_tree(repo, &shifted);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-16 09:33:18 +00:00
|
|
|
static inline void set_commit_tree(struct commit *c, struct tree *t)
|
|
|
|
{
|
|
|
|
c->maybe_tree = t;
|
|
|
|
}
|
|
|
|
|
2019-01-12 02:13:30 +00:00
|
|
|
static struct commit *make_virtual_commit(struct repository *repo,
|
|
|
|
struct tree *tree,
|
|
|
|
const char *comment)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2019-01-12 02:13:30 +00:00
|
|
|
struct commit *commit = alloc_commit_node(repo);
|
2011-11-07 21:26:22 +00:00
|
|
|
|
2016-08-13 12:16:04 +00:00
|
|
|
set_merge_remote_desc(commit, comment, (struct object *)commit);
|
2019-04-16 09:33:18 +00:00
|
|
|
set_commit_tree(commit, tree);
|
2008-08-12 16:45:14 +00:00
|
|
|
commit->object.parsed = 1;
|
|
|
|
return commit;
|
|
|
|
}
|
|
|
|
|
2010-09-20 08:28:53 +00:00
|
|
|
enum rename_type {
|
|
|
|
RENAME_NORMAL = 0,
|
2018-06-10 04:16:14 +00:00
|
|
|
RENAME_VIA_DIR,
|
2018-11-08 04:40:26 +00:00
|
|
|
RENAME_ADD,
|
2010-09-20 08:28:53 +00:00
|
|
|
RENAME_DELETE,
|
2011-08-12 05:20:08 +00:00
|
|
|
RENAME_ONE_FILE_TO_ONE,
|
2011-08-12 05:20:15 +00:00
|
|
|
RENAME_ONE_FILE_TO_TWO,
|
|
|
|
RENAME_TWO_FILES_TO_ONE
|
2010-09-20 08:28:53 +00:00
|
|
|
};
|
|
|
|
|
2008-08-12 16:45:14 +00:00
|
|
|
/*
|
|
|
|
* Since we want to write the index eventually, we cannot reuse the index
|
|
|
|
* for these (temporary) data.
|
|
|
|
*/
|
2011-03-16 07:08:34 +00:00
|
|
|
struct stage_data {
|
2019-04-05 15:00:22 +00:00
|
|
|
struct diff_filespec stages[4]; /* mostly for oid & mode; maybe path */
|
2011-08-12 05:20:08 +00:00
|
|
|
struct rename_conflict_info *rename_conflict_info;
|
2008-08-12 16:45:14 +00:00
|
|
|
unsigned processed:1;
|
|
|
|
};
|
|
|
|
|
2019-04-05 15:00:17 +00:00
|
|
|
struct rename {
|
2019-04-05 15:00:24 +00:00
|
|
|
unsigned processed:1;
|
2019-04-05 15:00:17 +00:00
|
|
|
struct diff_filepair *pair;
|
2019-04-05 15:00:20 +00:00
|
|
|
const char *branch; /* branch that the rename occurred on */
|
2019-04-05 15:00:24 +00:00
|
|
|
/*
|
|
|
|
* If directory rename detection affected this rename, what was its
|
|
|
|
* original type ('A' or 'R') and it's original destination before
|
|
|
|
* the directory rename (otherwise, '\0' and NULL for these two vars).
|
|
|
|
*/
|
|
|
|
char dir_rename_original_type;
|
|
|
|
char *dir_rename_original_dest;
|
2019-04-05 15:00:17 +00:00
|
|
|
/*
|
|
|
|
* Purpose of src_entry and dst_entry:
|
|
|
|
*
|
|
|
|
* If 'before' is renamed to 'after' then src_entry will contain
|
|
|
|
* the versions of 'before' from the merge_base, HEAD, and MERGE in
|
|
|
|
* stages 1, 2, and 3; dst_entry will contain the respective
|
|
|
|
* versions of 'after' in corresponding locations. Thus, we have a
|
|
|
|
* total of six modes and oids, though some will be null. (Stage 0
|
|
|
|
* is ignored; we're interested in handling conflicts.)
|
|
|
|
*
|
|
|
|
* Since we don't turn on break-rewrites by default, neither
|
|
|
|
* src_entry nor dst_entry can have all three of their stages have
|
|
|
|
* non-null oids, meaning at most four of the six will be non-null.
|
|
|
|
* Also, since this is a rename, both src_entry and dst_entry will
|
|
|
|
* have at least one non-null oid, meaning at least two will be
|
|
|
|
* non-null. Of the six oids, a typical rename will have three be
|
|
|
|
* non-null. Only two implies a rename/delete, and four implies a
|
|
|
|
* rename/add.
|
|
|
|
*/
|
|
|
|
struct stage_data *src_entry;
|
|
|
|
struct stage_data *dst_entry;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct rename_conflict_info {
|
|
|
|
enum rename_type rename_type;
|
2019-04-05 15:00:18 +00:00
|
|
|
struct rename *ren1;
|
|
|
|
struct rename *ren2;
|
2019-04-05 15:00:17 +00:00
|
|
|
};
|
|
|
|
|
2011-08-12 05:20:08 +00:00
|
|
|
static inline void setup_rename_conflict_info(enum rename_type rename_type,
|
2019-04-05 15:00:18 +00:00
|
|
|
struct merge_options *opt,
|
|
|
|
struct rename *ren1,
|
2019-04-05 15:00:20 +00:00
|
|
|
struct rename *ren2)
|
2010-09-20 08:28:53 +00:00
|
|
|
{
|
2018-10-16 20:19:48 +00:00
|
|
|
struct rename_conflict_info *ci;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* When we have two renames involved, it's easiest to get the
|
|
|
|
* correct things into stage 2 and 3, and to make sure that the
|
|
|
|
* content merge puts HEAD before the other branch if we just
|
2019-04-05 15:00:13 +00:00
|
|
|
* ensure that branch1 == opt->branch1. So, simply flip arguments
|
2018-10-16 20:19:48 +00:00
|
|
|
* around if we don't have that.
|
|
|
|
*/
|
2019-04-05 15:00:20 +00:00
|
|
|
if (ren2 && ren1->branch != opt->branch1) {
|
|
|
|
setup_rename_conflict_info(rename_type, opt, ren2, ren1);
|
2018-10-16 20:19:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-13 16:17:22 +00:00
|
|
|
CALLOC_ARRAY(ci, 1);
|
2010-09-20 08:28:53 +00:00
|
|
|
ci->rename_type = rename_type;
|
2019-04-05 15:00:18 +00:00
|
|
|
ci->ren1 = ren1;
|
|
|
|
ci->ren2 = ren2;
|
2010-09-20 08:28:53 +00:00
|
|
|
|
2019-04-05 15:00:18 +00:00
|
|
|
ci->ren1->dst_entry->processed = 0;
|
|
|
|
ci->ren1->dst_entry->rename_conflict_info = ci;
|
|
|
|
if (ren2) {
|
|
|
|
ci->ren2->dst_entry->rename_conflict_info = ci;
|
2010-09-20 08:28:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int show(struct merge_options *opt, int v)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2019-08-17 18:41:41 +00:00
|
|
|
return (!opt->priv->call_depth && opt->verbosity >= v) ||
|
|
|
|
opt->verbosity >= 5;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2009-11-14 21:33:13 +00:00
|
|
|
__attribute__((format (printf, 3, 4)))
|
2019-04-05 15:00:13 +00:00
|
|
|
static void output(struct merge_options *opt, int v, const char *fmt, ...)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (!show(opt, v))
|
2008-08-12 16:45:14 +00:00
|
|
|
return;
|
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
strbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
|
|
|
va_start(ap, fmt);
|
2019-04-05 15:00:13 +00:00
|
|
|
strbuf_vaddf(&opt->obuf, fmt, ap);
|
2008-08-12 16:45:14 +00:00
|
|
|
va_end(ap);
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
strbuf_addch(&opt->obuf, '\n');
|
|
|
|
if (!opt->buffer_output)
|
|
|
|
flush_output(opt);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 21:08:17 +00:00
|
|
|
static void repo_output_commit_title(struct merge_options *opt,
|
|
|
|
struct repository *repo,
|
|
|
|
struct commit *commit)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2018-05-19 05:28:30 +00:00
|
|
|
struct merge_remote_desc *desc;
|
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
strbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);
|
2018-05-19 05:28:30 +00:00
|
|
|
desc = merge_remote_util(commit);
|
|
|
|
if (desc)
|
2019-04-05 15:00:13 +00:00
|
|
|
strbuf_addf(&opt->obuf, "virtual %s\n", desc->name);
|
2008-08-12 16:45:14 +00:00
|
|
|
else {
|
2021-10-08 21:08:17 +00:00
|
|
|
strbuf_repo_add_unique_abbrev(&opt->obuf, repo,
|
|
|
|
&commit->object.oid,
|
|
|
|
DEFAULT_ABBREV);
|
2019-04-05 15:00:13 +00:00
|
|
|
strbuf_addch(&opt->obuf, ' ');
|
2021-10-08 21:08:17 +00:00
|
|
|
if (repo_parse_commit(repo, commit) != 0)
|
2019-04-05 15:00:13 +00:00
|
|
|
strbuf_addstr(&opt->obuf, _("(bad commit)\n"));
|
2008-08-12 16:45:14 +00:00
|
|
|
else {
|
2010-07-22 13:18:34 +00:00
|
|
|
const char *title;
|
2021-10-08 21:08:17 +00:00
|
|
|
const char *msg = repo_get_commit_buffer(repo, commit, NULL);
|
2014-06-10 21:41:51 +00:00
|
|
|
int len = find_commit_subject(msg, &title);
|
2010-07-22 13:18:34 +00:00
|
|
|
if (len)
|
2019-04-05 15:00:13 +00:00
|
|
|
strbuf_addf(&opt->obuf, "%.*s\n", len, title);
|
2021-10-08 21:08:17 +00:00
|
|
|
repo_unuse_commit_buffer(repo, commit, msg);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-05 15:00:13 +00:00
|
|
|
flush_output(opt);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 21:08:17 +00:00
|
|
|
static void output_commit_title(struct merge_options *opt, struct commit *commit)
|
|
|
|
{
|
|
|
|
repo_output_commit_title(opt, the_repository, commit);
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int add_cacheinfo(struct merge_options *opt,
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *blob,
|
2018-06-10 04:16:12 +00:00
|
|
|
const char *path, int stage, int refresh, int options)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2019-04-05 15:00:13 +00:00
|
|
|
struct index_state *istate = opt->repo->index;
|
2008-08-12 16:45:14 +00:00
|
|
|
struct cache_entry *ce;
|
merge: avoid "safer crlf" during recording of merge results
When merge_recursive() decides what the correct blob object merge
result for a path should be, it uses update_file_flags() helper
function to write it out to a working tree file and then calls
add_cacheinfo(). The add_cacheinfo() function in turn calls
make_cache_entry() to create a new cache entry to replace the
higher-stage entries for the path that represents the conflict.
The make_cache_entry() function calls refresh_cache_entry() to fill
in the cached stat information. To mark a cache entry as
up-to-date, the data is re-read from the file in the working tree,
and goes through convert_to_git() conversion to be compared with the
blob object name the new cache entry records.
It is important to note that this happens while the higher-stage
entries, which are going to be replaced with the new entry, are
still in the index. Unfortunately, the convert_to_git() conversion
has a misguided "safer crlf" mechanism baked in, and looks at the
existing cache entry for the path to decide how to convert the
contents in the working tree file. If our side (i.e. stage#2)
records a text blob with CRLF in it, even when the system is
configured to record LF in blobs and convert them to CRLF upon
checkout (and back to LF upon checkin), the "safer crlf" mechanism
stops us doing so.
This especially poses a problem during a renormalizing merge, where
the merge result for the path is computed by first "normalizing" the
blobs involved in the merge by using convert_to_working_tree()
followed by convert_to_git() with "safer crlf" disabled. The merge
result that is computed correctly and fed to add_cacheinfo() via
update_file_flags() does _not_ match what refresh_cache_entry() sees
by converting the working tree file via convert_to_git().
We can work this around by not refreshing the new cache entry in
make_cache_entry() called by add_cacheinfo(). After add_cacheinfo()
adds the new entry, we can call refresh_cache_entry() on that,
knowing that addition of this new cache entry would have removed the
stale cache entries that had CRLF in stage #2 that were carried over
before the renormalizing merge started and will not interfere with
the correct recording of the result.
The test update was taken from a series by Torsten Bögershausen
that attempted to fix this with a different approach.
Signed-off-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Reviewed-by: Torsten Bögershausen <tboegi@web.de>
2016-07-08 17:59:15 +00:00
|
|
|
int ret;
|
|
|
|
|
2019-04-05 15:00:22 +00:00
|
|
|
ce = make_cache_entry(istate, blob->mode, &blob->oid, path, stage, 0);
|
2008-08-12 16:45:14 +00:00
|
|
|
if (!ce)
|
2019-04-05 15:00:13 +00:00
|
|
|
return err(opt, _("add_cacheinfo failed for path '%s'; merge aborting."), path);
|
merge: avoid "safer crlf" during recording of merge results
When merge_recursive() decides what the correct blob object merge
result for a path should be, it uses update_file_flags() helper
function to write it out to a working tree file and then calls
add_cacheinfo(). The add_cacheinfo() function in turn calls
make_cache_entry() to create a new cache entry to replace the
higher-stage entries for the path that represents the conflict.
The make_cache_entry() function calls refresh_cache_entry() to fill
in the cached stat information. To mark a cache entry as
up-to-date, the data is re-read from the file in the working tree,
and goes through convert_to_git() conversion to be compared with the
blob object name the new cache entry records.
It is important to note that this happens while the higher-stage
entries, which are going to be replaced with the new entry, are
still in the index. Unfortunately, the convert_to_git() conversion
has a misguided "safer crlf" mechanism baked in, and looks at the
existing cache entry for the path to decide how to convert the
contents in the working tree file. If our side (i.e. stage#2)
records a text blob with CRLF in it, even when the system is
configured to record LF in blobs and convert them to CRLF upon
checkout (and back to LF upon checkin), the "safer crlf" mechanism
stops us doing so.
This especially poses a problem during a renormalizing merge, where
the merge result for the path is computed by first "normalizing" the
blobs involved in the merge by using convert_to_working_tree()
followed by convert_to_git() with "safer crlf" disabled. The merge
result that is computed correctly and fed to add_cacheinfo() via
update_file_flags() does _not_ match what refresh_cache_entry() sees
by converting the working tree file via convert_to_git().
We can work this around by not refreshing the new cache entry in
make_cache_entry() called by add_cacheinfo(). After add_cacheinfo()
adds the new entry, we can call refresh_cache_entry() on that,
knowing that addition of this new cache entry would have removed the
stale cache entries that had CRLF in stage #2 that were carried over
before the renormalizing merge started and will not interfere with
the correct recording of the result.
The test update was taken from a series by Torsten Bögershausen
that attempted to fix this with a different approach.
Signed-off-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Reviewed-by: Torsten Bögershausen <tboegi@web.de>
2016-07-08 17:59:15 +00:00
|
|
|
|
2019-01-12 02:13:29 +00:00
|
|
|
ret = add_index_entry(istate, ce, options);
|
merge: avoid "safer crlf" during recording of merge results
When merge_recursive() decides what the correct blob object merge
result for a path should be, it uses update_file_flags() helper
function to write it out to a working tree file and then calls
add_cacheinfo(). The add_cacheinfo() function in turn calls
make_cache_entry() to create a new cache entry to replace the
higher-stage entries for the path that represents the conflict.
The make_cache_entry() function calls refresh_cache_entry() to fill
in the cached stat information. To mark a cache entry as
up-to-date, the data is re-read from the file in the working tree,
and goes through convert_to_git() conversion to be compared with the
blob object name the new cache entry records.
It is important to note that this happens while the higher-stage
entries, which are going to be replaced with the new entry, are
still in the index. Unfortunately, the convert_to_git() conversion
has a misguided "safer crlf" mechanism baked in, and looks at the
existing cache entry for the path to decide how to convert the
contents in the working tree file. If our side (i.e. stage#2)
records a text blob with CRLF in it, even when the system is
configured to record LF in blobs and convert them to CRLF upon
checkout (and back to LF upon checkin), the "safer crlf" mechanism
stops us doing so.
This especially poses a problem during a renormalizing merge, where
the merge result for the path is computed by first "normalizing" the
blobs involved in the merge by using convert_to_working_tree()
followed by convert_to_git() with "safer crlf" disabled. The merge
result that is computed correctly and fed to add_cacheinfo() via
update_file_flags() does _not_ match what refresh_cache_entry() sees
by converting the working tree file via convert_to_git().
We can work this around by not refreshing the new cache entry in
make_cache_entry() called by add_cacheinfo(). After add_cacheinfo()
adds the new entry, we can call refresh_cache_entry() on that,
knowing that addition of this new cache entry would have removed the
stale cache entries that had CRLF in stage #2 that were carried over
before the renormalizing merge started and will not interfere with
the correct recording of the result.
The test update was taken from a series by Torsten Bögershausen
that attempted to fix this with a different approach.
Signed-off-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Reviewed-by: Torsten Bögershausen <tboegi@web.de>
2016-07-08 17:59:15 +00:00
|
|
|
if (refresh) {
|
|
|
|
struct cache_entry *nce;
|
|
|
|
|
2019-01-12 02:13:29 +00:00
|
|
|
nce = refresh_cache_entry(istate, ce,
|
|
|
|
CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING);
|
2016-11-26 12:48:06 +00:00
|
|
|
if (!nce)
|
2019-04-05 15:00:13 +00:00
|
|
|
return err(opt, _("add_cacheinfo failed to refresh for path '%s'; merge aborting."), path);
|
merge: avoid "safer crlf" during recording of merge results
When merge_recursive() decides what the correct blob object merge
result for a path should be, it uses update_file_flags() helper
function to write it out to a working tree file and then calls
add_cacheinfo(). The add_cacheinfo() function in turn calls
make_cache_entry() to create a new cache entry to replace the
higher-stage entries for the path that represents the conflict.
The make_cache_entry() function calls refresh_cache_entry() to fill
in the cached stat information. To mark a cache entry as
up-to-date, the data is re-read from the file in the working tree,
and goes through convert_to_git() conversion to be compared with the
blob object name the new cache entry records.
It is important to note that this happens while the higher-stage
entries, which are going to be replaced with the new entry, are
still in the index. Unfortunately, the convert_to_git() conversion
has a misguided "safer crlf" mechanism baked in, and looks at the
existing cache entry for the path to decide how to convert the
contents in the working tree file. If our side (i.e. stage#2)
records a text blob with CRLF in it, even when the system is
configured to record LF in blobs and convert them to CRLF upon
checkout (and back to LF upon checkin), the "safer crlf" mechanism
stops us doing so.
This especially poses a problem during a renormalizing merge, where
the merge result for the path is computed by first "normalizing" the
blobs involved in the merge by using convert_to_working_tree()
followed by convert_to_git() with "safer crlf" disabled. The merge
result that is computed correctly and fed to add_cacheinfo() via
update_file_flags() does _not_ match what refresh_cache_entry() sees
by converting the working tree file via convert_to_git().
We can work this around by not refreshing the new cache entry in
make_cache_entry() called by add_cacheinfo(). After add_cacheinfo()
adds the new entry, we can call refresh_cache_entry() on that,
knowing that addition of this new cache entry would have removed the
stale cache entries that had CRLF in stage #2 that were carried over
before the renormalizing merge started and will not interfere with
the correct recording of the result.
The test update was taken from a series by Torsten Bögershausen
that attempted to fix this with a different approach.
Signed-off-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Reviewed-by: Torsten Bögershausen <tboegi@web.de>
2016-07-08 17:59:15 +00:00
|
|
|
if (nce != ce)
|
2019-01-12 02:13:29 +00:00
|
|
|
ret = add_index_entry(istate, nce, options);
|
merge: avoid "safer crlf" during recording of merge results
When merge_recursive() decides what the correct blob object merge
result for a path should be, it uses update_file_flags() helper
function to write it out to a working tree file and then calls
add_cacheinfo(). The add_cacheinfo() function in turn calls
make_cache_entry() to create a new cache entry to replace the
higher-stage entries for the path that represents the conflict.
The make_cache_entry() function calls refresh_cache_entry() to fill
in the cached stat information. To mark a cache entry as
up-to-date, the data is re-read from the file in the working tree,
and goes through convert_to_git() conversion to be compared with the
blob object name the new cache entry records.
It is important to note that this happens while the higher-stage
entries, which are going to be replaced with the new entry, are
still in the index. Unfortunately, the convert_to_git() conversion
has a misguided "safer crlf" mechanism baked in, and looks at the
existing cache entry for the path to decide how to convert the
contents in the working tree file. If our side (i.e. stage#2)
records a text blob with CRLF in it, even when the system is
configured to record LF in blobs and convert them to CRLF upon
checkout (and back to LF upon checkin), the "safer crlf" mechanism
stops us doing so.
This especially poses a problem during a renormalizing merge, where
the merge result for the path is computed by first "normalizing" the
blobs involved in the merge by using convert_to_working_tree()
followed by convert_to_git() with "safer crlf" disabled. The merge
result that is computed correctly and fed to add_cacheinfo() via
update_file_flags() does _not_ match what refresh_cache_entry() sees
by converting the working tree file via convert_to_git().
We can work this around by not refreshing the new cache entry in
make_cache_entry() called by add_cacheinfo(). After add_cacheinfo()
adds the new entry, we can call refresh_cache_entry() on that,
knowing that addition of this new cache entry would have removed the
stale cache entries that had CRLF in stage #2 that were carried over
before the renormalizing merge started and will not interfere with
the correct recording of the result.
The test update was taken from a series by Torsten Bögershausen
that attempted to fix this with a different approach.
Signed-off-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Reviewed-by: Torsten Bögershausen <tboegi@web.de>
2016-07-08 17:59:15 +00:00
|
|
|
}
|
|
|
|
return ret;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:37 +00:00
|
|
|
static inline int merge_detect_rename(struct merge_options *opt)
|
|
|
|
{
|
2019-08-17 18:41:38 +00:00
|
|
|
return (opt->detect_renames >= 0) ? opt->detect_renames : 1;
|
2019-08-17 18:41:37 +00:00
|
|
|
}
|
|
|
|
|
2008-08-12 16:45:14 +00:00
|
|
|
static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
|
|
|
|
{
|
2024-02-23 08:34:23 +00:00
|
|
|
if (parse_tree(tree) < 0)
|
|
|
|
exit(128);
|
2023-10-02 02:40:28 +00:00
|
|
|
init_tree_desc(desc, &tree->object.oid, tree->buffer, tree->size);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int unpack_trees_start(struct merge_options *opt,
|
2018-05-20 10:17:35 +00:00
|
|
|
struct tree *common,
|
|
|
|
struct tree *head,
|
|
|
|
struct tree *merge)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct tree_desc t[3];
|
treewide: always have a valid "index_state.repo" member
When the "repo" member was added to "the_index" in [1] the
repo_read_index() was made to populate it, but the unpopulated
"the_index" variable didn't get the same treatment.
Let's do that in initialize_the_repository() when we set it up, and
likewise for all of the current callers initialized an empty "struct
index_state".
This simplifies code that needs to deal with "the_index" or a custom
"struct index_state", we no longer need to second-guess this part of
the "index_state" deep in the stack. A recent example of such
second-guessing is the "istate->repo ? istate->repo : the_repository"
code in [2]. We can now simply use "istate->repo".
We're doing this by making use of the INDEX_STATE_INIT() macro (and
corresponding function) added in [3], which now have mandatory "repo"
arguments.
Because we now call index_state_init() in repository.c's
initialize_the_repository() we don't need to handle the case where we
have a "repo->index" whose "repo" member doesn't match the "repo"
we're setting up, i.e. the "Complete the double-reference" code in
repo_read_index() being altered here. That logic was originally added
in [1], and was working around the lack of what we now have in
initialize_the_repository().
For "fsmonitor-settings.c" we can remove the initialization of a NULL
"r" argument to "the_repository". This was added back in [4], and was
needed at the time for callers that would pass us the "r" from an
"istate->repo". Before this change such a change to
"fsmonitor-settings.c" would segfault all over the test suite (e.g. in
t0002-gitfile.sh).
This change has wider eventual implications for
"fsmonitor-settings.c". The reason the other lazy loading behavior in
it is required (starting with "if (!r->settings.fsmonitor) ..." is
because of the previously passed "r" being "NULL".
I have other local changes on top of this which move its configuration
reading to "prepare_repo_settings()" in "repo-settings.c", as we could
now start to rely on it being called for our "r". But let's leave all
of that for now, and narrowly remove this particular part of the
lazy-loading.
1. 1fd9ae517c4 (repository: add repo reference to index_state,
2021-01-23)
2. ee1f0c242ef (read-cache: add index.skipHash config option,
2023-01-06)
3. 2f6b1eb794e (cache API: add a "INDEX_STATE_INIT" macro/function,
add release_index(), 2023-01-12)
4. 1e0ea5c4316 (fsmonitor: config settings are repository-specific,
2022-03-25)
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Acked-by: Derrick Stolee <derrickstolee@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-01-17 13:57:00 +00:00
|
|
|
struct index_state tmp_index = INDEX_STATE_INIT(opt->repo);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
memset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));
|
|
|
|
if (opt->priv->call_depth)
|
|
|
|
opt->priv->unpack_opts.index_only = 1;
|
read-tree, merge-recursive: overwrite ignored files by default
This fixes a long-standing patchwork of ignored files handling in
read-tree and merge-recursive, called out and suggested by Junio long
ago. Quoting from commit dcf0c16ef1 ("core.excludesfile clean-up"
2007-11-16):
git-read-tree takes --exclude-per-directory=<gitignore>,
not because the flexibility was needed. Again, this was
because the option predates the standardization of the ignore
files.
...
On the other hand, I think it makes perfect sense to fix
git-read-tree, git-merge-recursive and git-clean to follow the
same rule as other commands. I do not think of a valid use case
to give an exclude-per-directory that is nonstandard to
read-tree command, outside a "negative" test in the t1004 test
script.
This patch is the first step to untangle this mess.
The next step would be to teach read-tree, merge-recursive and
clean (in C) to use setup_standard_excludes().
History shows each of these were partially or fully fixed:
* clean was taught the new trick in 1617adc7a0 ("Teach git clean to
use setup_standard_excludes()", 2007-11-14).
* read-tree was primarily used by checkout & merge scripts. checkout
and merge later became builtins and were both fixed to use the new
setup_standard_excludes() handling in fc001b526c ("checkout,merge:
loosen overwriting untracked file check based on info/exclude",
2011-11-27). So the primary users were fixed, though read-tree
itself was not.
* merge-recursive has now been replaced as the default merge backend
by merge-ort. merge-ort fixed this by using
setup_standard_excludes() starting early in its implementation; see
commit 6681ce5cf6 ("merge-ort: add implementation of checkout()",
2020-12-13), largely due to its design depending on checkout() and
thus being influenced by the checkout code. However,
merge-recursive itself was not fixed here, in part because its
design meant it had difficulty differentiating between untracked
files, ignored files, leftover tracked files that haven't been
removed yet due to order of processing files, and files written by
itself due to collisions).
Make the conversion more complete by now handling read-tree and
handling at least the unpack_trees() portion of merge-recursive. While
merge-recursive is on its way out, fixing the unpack_trees() portion is
easy and facilitates some of the later changes in this series. Note
that fixing read-tree makes the --exclude-per-directory option to
read-tree useless, so we remove it from the documentation (though we
continue to accept it if passed).
The read-tree changes happen to fix a bug in t1013.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-09-27 16:33:40 +00:00
|
|
|
else {
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->unpack_opts.update = 1;
|
read-tree, merge-recursive: overwrite ignored files by default
This fixes a long-standing patchwork of ignored files handling in
read-tree and merge-recursive, called out and suggested by Junio long
ago. Quoting from commit dcf0c16ef1 ("core.excludesfile clean-up"
2007-11-16):
git-read-tree takes --exclude-per-directory=<gitignore>,
not because the flexibility was needed. Again, this was
because the option predates the standardization of the ignore
files.
...
On the other hand, I think it makes perfect sense to fix
git-read-tree, git-merge-recursive and git-clean to follow the
same rule as other commands. I do not think of a valid use case
to give an exclude-per-directory that is nonstandard to
read-tree command, outside a "negative" test in the t1004 test
script.
This patch is the first step to untangle this mess.
The next step would be to teach read-tree, merge-recursive and
clean (in C) to use setup_standard_excludes().
History shows each of these were partially or fully fixed:
* clean was taught the new trick in 1617adc7a0 ("Teach git clean to
use setup_standard_excludes()", 2007-11-14).
* read-tree was primarily used by checkout & merge scripts. checkout
and merge later became builtins and were both fixed to use the new
setup_standard_excludes() handling in fc001b526c ("checkout,merge:
loosen overwriting untracked file check based on info/exclude",
2011-11-27). So the primary users were fixed, though read-tree
itself was not.
* merge-recursive has now been replaced as the default merge backend
by merge-ort. merge-ort fixed this by using
setup_standard_excludes() starting early in its implementation; see
commit 6681ce5cf6 ("merge-ort: add implementation of checkout()",
2020-12-13), largely due to its design depending on checkout() and
thus being influenced by the checkout code. However,
merge-recursive itself was not fixed here, in part because its
design meant it had difficulty differentiating between untracked
files, ignored files, leftover tracked files that haven't been
removed yet due to order of processing files, and files written by
itself due to collisions).
Make the conversion more complete by now handling read-tree and
handling at least the unpack_trees() portion of merge-recursive. While
merge-recursive is on its way out, fixing the unpack_trees() portion is
easy and facilitates some of the later changes in this series. Note
that fixing read-tree makes the --exclude-per-directory option to
read-tree useless, so we remove it from the documentation (though we
continue to accept it if passed).
The read-tree changes happen to fix a bug in t1013.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-09-27 16:33:40 +00:00
|
|
|
/* FIXME: should only do this if !overwrite_ignore */
|
2021-09-27 16:33:41 +00:00
|
|
|
opt->priv->unpack_opts.preserve_ignored = 0;
|
read-tree, merge-recursive: overwrite ignored files by default
This fixes a long-standing patchwork of ignored files handling in
read-tree and merge-recursive, called out and suggested by Junio long
ago. Quoting from commit dcf0c16ef1 ("core.excludesfile clean-up"
2007-11-16):
git-read-tree takes --exclude-per-directory=<gitignore>,
not because the flexibility was needed. Again, this was
because the option predates the standardization of the ignore
files.
...
On the other hand, I think it makes perfect sense to fix
git-read-tree, git-merge-recursive and git-clean to follow the
same rule as other commands. I do not think of a valid use case
to give an exclude-per-directory that is nonstandard to
read-tree command, outside a "negative" test in the t1004 test
script.
This patch is the first step to untangle this mess.
The next step would be to teach read-tree, merge-recursive and
clean (in C) to use setup_standard_excludes().
History shows each of these were partially or fully fixed:
* clean was taught the new trick in 1617adc7a0 ("Teach git clean to
use setup_standard_excludes()", 2007-11-14).
* read-tree was primarily used by checkout & merge scripts. checkout
and merge later became builtins and were both fixed to use the new
setup_standard_excludes() handling in fc001b526c ("checkout,merge:
loosen overwriting untracked file check based on info/exclude",
2011-11-27). So the primary users were fixed, though read-tree
itself was not.
* merge-recursive has now been replaced as the default merge backend
by merge-ort. merge-ort fixed this by using
setup_standard_excludes() starting early in its implementation; see
commit 6681ce5cf6 ("merge-ort: add implementation of checkout()",
2020-12-13), largely due to its design depending on checkout() and
thus being influenced by the checkout code. However,
merge-recursive itself was not fixed here, in part because its
design meant it had difficulty differentiating between untracked
files, ignored files, leftover tracked files that haven't been
removed yet due to order of processing files, and files written by
itself due to collisions).
Make the conversion more complete by now handling read-tree and
handling at least the unpack_trees() portion of merge-recursive. While
merge-recursive is on its way out, fixing the unpack_trees() portion is
easy and facilitates some of the later changes in this series. Note
that fixing read-tree makes the --exclude-per-directory option to
read-tree useless, so we remove it from the documentation (though we
continue to accept it if passed).
The read-tree changes happen to fix a bug in t1013.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-09-27 16:33:40 +00:00
|
|
|
}
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->unpack_opts.merge = 1;
|
|
|
|
opt->priv->unpack_opts.head_idx = 2;
|
|
|
|
opt->priv->unpack_opts.fn = threeway_merge;
|
|
|
|
opt->priv->unpack_opts.src_index = opt->repo->index;
|
|
|
|
opt->priv->unpack_opts.dst_index = &tmp_index;
|
|
|
|
opt->priv->unpack_opts.aggressive = !merge_detect_rename(opt);
|
|
|
|
setup_unpack_trees_porcelain(&opt->priv->unpack_opts, "merge");
|
2008-08-12 16:45:14 +00:00
|
|
|
|
|
|
|
init_tree_desc_from_tree(t+0, common);
|
|
|
|
init_tree_desc_from_tree(t+1, head);
|
|
|
|
init_tree_desc_from_tree(t+2, merge);
|
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
rc = unpack_trees(3, t, &opt->priv->unpack_opts);
|
2019-04-05 15:00:13 +00:00
|
|
|
cache_tree_free(&opt->repo->index->cache_tree);
|
2018-04-19 17:58:20 +00:00
|
|
|
|
2018-04-19 17:58:12 +00:00
|
|
|
/*
|
2019-08-17 18:41:41 +00:00
|
|
|
* Update opt->repo->index to match the new results, AFTER saving a
|
|
|
|
* copy in opt->priv->orig_index. Update src_index to point to the
|
|
|
|
* saved copy. (verify_uptodate() checks src_index, and the original
|
|
|
|
* index is the one that had the necessary modification timestamps.)
|
2018-04-19 17:58:12 +00:00
|
|
|
*/
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->orig_index = *opt->repo->index;
|
2019-04-05 15:00:13 +00:00
|
|
|
*opt->repo->index = tmp_index;
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->unpack_opts.src_index = &opt->priv->orig_index;
|
2018-04-19 17:58:20 +00:00
|
|
|
|
2008-08-12 16:45:14 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static void unpack_trees_finish(struct merge_options *opt)
|
2018-05-20 10:17:35 +00:00
|
|
|
{
|
2019-08-17 18:41:41 +00:00
|
|
|
discard_index(&opt->priv->orig_index);
|
|
|
|
clear_unpack_trees_porcelain(&opt->priv->unpack_opts);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2022-08-25 17:09:48 +00:00
|
|
|
static int save_files_dirs(const struct object_id *oid UNUSED,
|
2018-06-10 04:16:12 +00:00
|
|
|
struct strbuf *base, const char *path,
|
2021-03-20 22:37:51 +00:00
|
|
|
unsigned int mode, void *context)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2017-09-07 16:25:56 +00:00
|
|
|
struct path_hashmap_entry *entry;
|
2014-11-30 09:05:00 +00:00
|
|
|
int baselen = base->len;
|
2019-04-05 15:00:13 +00:00
|
|
|
struct merge_options *opt = context;
|
2008-09-03 17:08:56 +00:00
|
|
|
|
2014-11-30 09:05:00 +00:00
|
|
|
strbuf_addstr(base, path);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2017-09-07 16:25:56 +00:00
|
|
|
FLEX_ALLOC_MEM(entry, path, base->buf, base->len);
|
2021-07-30 19:06:58 +00:00
|
|
|
hashmap_entry_init(&entry->e, fspathhash(entry->path));
|
2019-10-15 04:48:01 +00:00
|
|
|
hashmap_add(&opt->priv->current_file_dir_set, &entry->e);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2014-11-30 09:05:00 +00:00
|
|
|
strbuf_setlen(base, baselen);
|
2009-01-25 00:52:05 +00:00
|
|
|
return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static void get_files_dirs(struct merge_options *opt, struct tree *tree)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2011-03-25 09:34:19 +00:00
|
|
|
struct pathspec match_all;
|
2013-07-14 08:35:59 +00:00
|
|
|
memset(&match_all, 0, sizeof(match_all));
|
2021-03-20 22:37:51 +00:00
|
|
|
read_tree(opt->repo, tree,
|
|
|
|
&match_all, save_files_dirs, opt);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-06-27 09:28:52 +00:00
|
|
|
static int get_tree_entry_if_blob(struct repository *r,
|
|
|
|
const struct object_id *tree,
|
2018-04-19 17:58:09 +00:00
|
|
|
const char *path,
|
2019-04-05 15:00:22 +00:00
|
|
|
struct diff_filespec *dfs)
|
2018-04-19 17:58:09 +00:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2019-06-27 09:28:52 +00:00
|
|
|
ret = get_tree_entry(r, tree, path, &dfs->oid, &dfs->mode);
|
2019-04-05 15:00:22 +00:00
|
|
|
if (S_ISDIR(dfs->mode)) {
|
2021-04-26 01:02:56 +00:00
|
|
|
oidcpy(&dfs->oid, null_oid());
|
2019-04-05 15:00:22 +00:00
|
|
|
dfs->mode = 0;
|
2018-04-19 17:58:09 +00:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-08-12 16:45:14 +00:00
|
|
|
/*
|
|
|
|
* Returns an index_entry instance which doesn't have to correspond to
|
|
|
|
* a real cache entry in Git's index.
|
|
|
|
*/
|
2019-06-27 09:28:52 +00:00
|
|
|
static struct stage_data *insert_stage_data(struct repository *r,
|
|
|
|
const char *path,
|
2008-08-12 16:45:14 +00:00
|
|
|
struct tree *o, struct tree *a, struct tree *b,
|
|
|
|
struct string_list *entries)
|
|
|
|
{
|
|
|
|
struct string_list_item *item;
|
|
|
|
struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
|
2019-06-27 09:28:52 +00:00
|
|
|
get_tree_entry_if_blob(r, &o->object.oid, path, &e->stages[1]);
|
|
|
|
get_tree_entry_if_blob(r, &a->object.oid, path, &e->stages[2]);
|
|
|
|
get_tree_entry_if_blob(r, &b->object.oid, path, &e->stages[3]);
|
2010-06-25 23:41:35 +00:00
|
|
|
item = string_list_insert(entries, path);
|
2008-08-12 16:45:14 +00:00
|
|
|
item->util = e;
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create a dictionary mapping file names to stage_data objects. The
|
|
|
|
* dictionary contains one entry for every path with a non-zero stage entry.
|
|
|
|
*/
|
2019-01-12 02:13:29 +00:00
|
|
|
static struct string_list *get_unmerged(struct index_state *istate)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2022-04-13 20:01:32 +00:00
|
|
|
struct string_list *unmerged = xmalloc(sizeof(struct string_list));
|
2008-08-12 16:45:14 +00:00
|
|
|
int i;
|
|
|
|
|
2022-04-13 20:01:32 +00:00
|
|
|
string_list_init_dup(unmerged);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2021-04-01 01:49:56 +00:00
|
|
|
/* TODO: audit for interaction with sparse-index. */
|
|
|
|
ensure_full_index(istate);
|
2019-01-12 02:13:29 +00:00
|
|
|
for (i = 0; i < istate->cache_nr; i++) {
|
2008-08-12 16:45:14 +00:00
|
|
|
struct string_list_item *item;
|
|
|
|
struct stage_data *e;
|
2019-01-12 02:13:29 +00:00
|
|
|
const struct cache_entry *ce = istate->cache[i];
|
2008-08-12 16:45:14 +00:00
|
|
|
if (!ce_stage(ce))
|
|
|
|
continue;
|
|
|
|
|
2010-06-25 23:41:37 +00:00
|
|
|
item = string_list_lookup(unmerged, ce->name);
|
2008-08-12 16:45:14 +00:00
|
|
|
if (!item) {
|
2010-06-25 23:41:35 +00:00
|
|
|
item = string_list_insert(unmerged, ce->name);
|
2008-08-12 16:45:14 +00:00
|
|
|
item->util = xcalloc(1, sizeof(struct stage_data));
|
|
|
|
}
|
|
|
|
e = item->util;
|
|
|
|
e->stages[ce_stage(ce)].mode = ce->ce_mode;
|
2016-09-05 20:07:52 +00:00
|
|
|
oidcpy(&e->stages[ce_stage(ce)].oid, &ce->oid);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return unmerged;
|
|
|
|
}
|
|
|
|
|
2016-11-24 11:45:36 +00:00
|
|
|
static int string_list_df_name_compare(const char *one, const char *two)
|
2010-09-20 08:29:09 +00:00
|
|
|
{
|
2016-11-24 11:45:36 +00:00
|
|
|
int onelen = strlen(one);
|
|
|
|
int twolen = strlen(two);
|
2011-08-12 05:19:56 +00:00
|
|
|
/*
|
|
|
|
* Here we only care that entries for D/F conflicts are
|
|
|
|
* adjacent, in particular with the file of the D/F conflict
|
|
|
|
* appearing before files below the corresponding directory.
|
|
|
|
* The order of the rest of the list is irrelevant for us.
|
2010-09-20 08:29:09 +00:00
|
|
|
*
|
2011-08-12 05:19:56 +00:00
|
|
|
* To achieve this, we sort with df_name_compare and provide
|
|
|
|
* the mode S_IFDIR so that D/F conflicts will sort correctly.
|
|
|
|
* We use the mode S_IFDIR for everything else for simplicity,
|
|
|
|
* since in other cases any changes in their order due to
|
|
|
|
* sorting cause no problems for us.
|
|
|
|
*/
|
2016-11-24 11:45:36 +00:00
|
|
|
int cmp = df_name_compare(one, onelen, S_IFDIR,
|
|
|
|
two, twolen, S_IFDIR);
|
2011-08-12 05:19:56 +00:00
|
|
|
/*
|
|
|
|
* Now that 'foo' and 'foo/bar' compare equal, we have to make sure
|
|
|
|
* that 'foo' comes before 'foo/bar'.
|
2010-09-20 08:29:09 +00:00
|
|
|
*/
|
2011-08-12 05:19:56 +00:00
|
|
|
if (cmp)
|
|
|
|
return cmp;
|
|
|
|
return onelen - twolen;
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static void record_df_conflict_files(struct merge_options *opt,
|
2011-08-12 05:19:58 +00:00
|
|
|
struct string_list *entries)
|
2010-09-20 08:29:09 +00:00
|
|
|
{
|
2011-08-12 05:19:58 +00:00
|
|
|
/* If there is a D/F conflict and the file for such a conflict
|
2018-06-10 04:16:11 +00:00
|
|
|
* currently exists in the working tree, we want to allow it to be
|
2011-08-12 05:20:07 +00:00
|
|
|
* removed to make room for the corresponding directory if needed.
|
|
|
|
* The files underneath the directories of such D/F conflicts will
|
|
|
|
* be processed before the corresponding file involved in the D/F
|
|
|
|
* conflict. If the D/F directory ends up being removed by the
|
|
|
|
* merge, then we won't have to touch the D/F file. If the D/F
|
|
|
|
* directory needs to be written to the working copy, then the D/F
|
|
|
|
* file will simply be removed (in make_room_for_path()) to make
|
|
|
|
* room for the necessary paths. Note that if both the directory
|
|
|
|
* and the file need to be present, then the D/F file will be
|
|
|
|
* reinstated with a new unique name at the time it is processed.
|
2010-09-20 08:29:09 +00:00
|
|
|
*/
|
2016-08-05 20:42:12 +00:00
|
|
|
struct string_list df_sorted_entries = STRING_LIST_INIT_NODUP;
|
2010-09-20 08:29:09 +00:00
|
|
|
const char *last_file = NULL;
|
2010-10-21 14:34:33 +00:00
|
|
|
int last_len = 0;
|
2010-09-20 08:29:09 +00:00
|
|
|
int i;
|
|
|
|
|
2011-08-12 05:19:54 +00:00
|
|
|
/*
|
|
|
|
* If we're merging merge-bases, we don't want to bother with
|
|
|
|
* any working directory changes.
|
|
|
|
*/
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opt->priv->call_depth)
|
2011-08-12 05:19:54 +00:00
|
|
|
return;
|
|
|
|
|
2011-08-12 05:19:56 +00:00
|
|
|
/* Ensure D/F conflicts are adjacent in the entries list. */
|
2010-09-20 08:29:09 +00:00
|
|
|
for (i = 0; i < entries->nr; i++) {
|
2011-08-13 02:23:51 +00:00
|
|
|
struct string_list_item *next = &entries->items[i];
|
|
|
|
string_list_append(&df_sorted_entries, next->string)->util =
|
|
|
|
next->util;
|
|
|
|
}
|
2016-11-24 11:45:36 +00:00
|
|
|
df_sorted_entries.cmp = string_list_df_name_compare;
|
|
|
|
string_list_sort(&df_sorted_entries);
|
2011-08-12 05:19:56 +00:00
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
string_list_clear(&opt->priv->df_conflict_file_set, 1);
|
2011-08-13 02:23:51 +00:00
|
|
|
for (i = 0; i < df_sorted_entries.nr; i++) {
|
|
|
|
const char *path = df_sorted_entries.items[i].string;
|
2010-09-20 08:29:09 +00:00
|
|
|
int len = strlen(path);
|
2011-08-13 02:23:51 +00:00
|
|
|
struct stage_data *e = df_sorted_entries.items[i].util;
|
2010-09-20 08:29:09 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if last_file & path correspond to a D/F conflict;
|
|
|
|
* i.e. whether path is last_file+'/'+<something>.
|
2011-08-12 05:19:58 +00:00
|
|
|
* If so, record that it's okay to remove last_file to make
|
|
|
|
* room for path and friends if needed.
|
2010-09-20 08:29:09 +00:00
|
|
|
*/
|
|
|
|
if (last_file &&
|
|
|
|
len > last_len &&
|
|
|
|
memcmp(path, last_file, last_len) == 0 &&
|
|
|
|
path[last_len] == '/') {
|
2019-08-17 18:41:41 +00:00
|
|
|
string_list_insert(&opt->priv->df_conflict_file_set, last_file);
|
2010-09-20 08:29:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Determine whether path could exist as a file in the
|
|
|
|
* working directory as a possible D/F conflict. This
|
|
|
|
* will only occur when it exists in stage 2 as a
|
|
|
|
* file.
|
|
|
|
*/
|
|
|
|
if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) {
|
|
|
|
last_file = path;
|
|
|
|
last_len = len;
|
|
|
|
} else {
|
|
|
|
last_file = NULL;
|
|
|
|
}
|
|
|
|
}
|
2011-08-13 02:23:51 +00:00
|
|
|
string_list_clear(&df_sorted_entries, 0);
|
2010-09-20 08:29:09 +00:00
|
|
|
}
|
|
|
|
|
2016-08-01 11:44:37 +00:00
|
|
|
static int update_stages(struct merge_options *opt, const char *path,
|
|
|
|
const struct diff_filespec *o,
|
2011-08-12 05:19:52 +00:00
|
|
|
const struct diff_filespec *a,
|
|
|
|
const struct diff_filespec *b)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2011-08-12 05:20:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* NOTE: It is usually a bad idea to call update_stages on a path
|
|
|
|
* before calling update_file on that same path, since it can
|
|
|
|
* sometimes lead to spurious "refusing to lose untracked file..."
|
|
|
|
* messages from update_file (via make_room_for path via
|
|
|
|
* would_lose_untracked). Instead, reverse the order of the calls
|
|
|
|
* (executing update_file first and then update_stages).
|
|
|
|
*/
|
2011-08-12 05:19:52 +00:00
|
|
|
int clear = 1;
|
|
|
|
int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
|
2008-08-12 16:45:14 +00:00
|
|
|
if (clear)
|
2019-01-12 02:13:29 +00:00
|
|
|
if (remove_file_from_index(opt->repo->index, path))
|
2008-08-12 16:45:14 +00:00
|
|
|
return -1;
|
|
|
|
if (o)
|
2019-04-05 15:00:22 +00:00
|
|
|
if (add_cacheinfo(opt, o, path, 1, 0, options))
|
2008-08-12 16:45:14 +00:00
|
|
|
return -1;
|
|
|
|
if (a)
|
2019-04-05 15:00:22 +00:00
|
|
|
if (add_cacheinfo(opt, a, path, 2, 0, options))
|
2008-08-12 16:45:14 +00:00
|
|
|
return -1;
|
|
|
|
if (b)
|
2019-04-05 15:00:22 +00:00
|
|
|
if (add_cacheinfo(opt, b, path, 3, 0, options))
|
2008-08-12 16:45:14 +00:00
|
|
|
return -1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-08-12 05:20:02 +00:00
|
|
|
static void update_entry(struct stage_data *entry,
|
|
|
|
struct diff_filespec *o,
|
|
|
|
struct diff_filespec *a,
|
|
|
|
struct diff_filespec *b)
|
2010-09-20 08:28:54 +00:00
|
|
|
{
|
|
|
|
entry->processed = 0;
|
|
|
|
entry->stages[1].mode = o->mode;
|
|
|
|
entry->stages[2].mode = a->mode;
|
|
|
|
entry->stages[3].mode = b->mode;
|
2016-06-24 23:09:25 +00:00
|
|
|
oidcpy(&entry->stages[1].oid, &o->oid);
|
|
|
|
oidcpy(&entry->stages[2].oid, &a->oid);
|
|
|
|
oidcpy(&entry->stages[3].oid, &b->oid);
|
2010-09-20 08:28:54 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int remove_file(struct merge_options *opt, int clean,
|
2008-09-02 21:53:47 +00:00
|
|
|
const char *path, int no_wd)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2019-08-17 18:41:41 +00:00
|
|
|
int update_cache = opt->priv->call_depth || clean;
|
|
|
|
int update_working_directory = !opt->priv->call_depth && !no_wd;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
|
|
|
if (update_cache) {
|
2019-04-05 15:00:13 +00:00
|
|
|
if (remove_file_from_index(opt->repo->index, path))
|
2008-08-12 16:45:14 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (update_working_directory) {
|
2014-05-02 00:21:09 +00:00
|
|
|
if (ignore_case) {
|
|
|
|
struct cache_entry *ce;
|
2019-04-05 15:00:13 +00:00
|
|
|
ce = index_file_exists(opt->repo->index, path, strlen(path),
|
2019-01-12 02:13:29 +00:00
|
|
|
ignore_case);
|
merge-recursive: ignore_case shouldn't reject intentional removals
In commit ae352c7f3 (merge-recursive.c: fix case-changing merge bug,
2014-05-01), it was observed that removing files could be problematic on
case insensitive file systems, because we could end up removing files
that differed in case only rather than deleting the intended file --
something that happened when files were renamed on one branch in a way
that differed only in case. To avoid that problem, that commit added
logic to avoid removing files other than the one intended, rejecting the
removal if the files differed only in case.
Unfortunately, the logic it used didn't fully implement that condition as
stated above; instead it merely checked that a case-insensitive lookup of
the file that was requested resulted in finding a file in the index at
stage 0, not that the file found in the index actually differed in case.
Alternatively, one could view the implementation as making an implicit
assumption that the file we actually wanted to remove would never appear
in the index with a stage of 0, and thus that if we found a file with our
lookup, that it had to be a different file (but different in case only).
The net result of this implementation is that it can ignore more requests
than it should, leaving a file around in the working copy that should
have been removed. Make sure that the file found in the index actually
differs in case before silently ignoring the request to remove the file.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-11-24 19:59:01 +00:00
|
|
|
if (ce && ce_stage(ce) == 0 && strcmp(path, ce->name))
|
2014-05-02 00:21:09 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2010-03-26 15:25:35 +00:00
|
|
|
if (remove_path(path))
|
2008-08-12 16:45:14 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-06-19 21:30:26 +00:00
|
|
|
/* add a string to a strbuf, but converting "/" to "_" */
|
|
|
|
static void add_flattened_path(struct strbuf *out, const char *s)
|
|
|
|
{
|
|
|
|
size_t i = out->len;
|
|
|
|
strbuf_addstr(out, s);
|
|
|
|
for (; i < out->len; i++)
|
|
|
|
if (out->buf[i] == '/')
|
|
|
|
out->buf[i] = '_';
|
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:33 +00:00
|
|
|
static char *unique_path(struct merge_options *opt,
|
|
|
|
const char *path,
|
|
|
|
const char *branch)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2017-09-07 16:25:56 +00:00
|
|
|
struct path_hashmap_entry *entry;
|
2014-06-19 21:30:26 +00:00
|
|
|
struct strbuf newpath = STRBUF_INIT;
|
2008-08-12 16:45:14 +00:00
|
|
|
int suffix = 0;
|
2014-06-19 21:30:26 +00:00
|
|
|
size_t base_len;
|
|
|
|
|
|
|
|
strbuf_addf(&newpath, "%s~", path);
|
|
|
|
add_flattened_path(&newpath, branch);
|
|
|
|
|
|
|
|
base_len = newpath.len;
|
2019-08-17 18:41:41 +00:00
|
|
|
while (hashmap_get_from_hash(&opt->priv->current_file_dir_set,
|
2021-07-30 19:06:58 +00:00
|
|
|
fspathhash(newpath.buf), newpath.buf) ||
|
2019-08-17 18:41:41 +00:00
|
|
|
(!opt->priv->call_depth && file_exists(newpath.buf))) {
|
2014-06-19 21:30:26 +00:00
|
|
|
strbuf_setlen(&newpath, base_len);
|
|
|
|
strbuf_addf(&newpath, "_%d", suffix++);
|
|
|
|
}
|
|
|
|
|
2017-09-07 16:25:56 +00:00
|
|
|
FLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len);
|
2021-07-30 19:06:58 +00:00
|
|
|
hashmap_entry_init(&entry->e, fspathhash(entry->path));
|
2019-10-15 04:48:01 +00:00
|
|
|
hashmap_add(&opt->priv->current_file_dir_set, &entry->e);
|
2014-06-19 21:30:26 +00:00
|
|
|
return strbuf_detach(&newpath, NULL);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2016-11-07 18:31:31 +00:00
|
|
|
/**
|
|
|
|
* Check whether a directory in the index is in the way of an incoming
|
|
|
|
* file. Return 1 if so. If check_working_copy is non-zero, also
|
|
|
|
* check the working directory. If empty_ok is non-zero, also return
|
|
|
|
* 0 in the case where the working-tree dir exists but is empty.
|
|
|
|
*/
|
2019-01-12 02:13:29 +00:00
|
|
|
static int dir_in_way(struct index_state *istate, const char *path,
|
|
|
|
int check_working_copy, int empty_ok)
|
2011-08-12 05:19:57 +00:00
|
|
|
{
|
2015-09-24 21:07:43 +00:00
|
|
|
int pos;
|
|
|
|
struct strbuf dirpath = STRBUF_INIT;
|
2011-08-12 05:19:57 +00:00
|
|
|
struct stat st;
|
|
|
|
|
2015-09-24 21:07:43 +00:00
|
|
|
strbuf_addstr(&dirpath, path);
|
|
|
|
strbuf_addch(&dirpath, '/');
|
2011-08-12 05:19:57 +00:00
|
|
|
|
2019-01-12 02:13:29 +00:00
|
|
|
pos = index_name_pos(istate, dirpath.buf, dirpath.len);
|
2011-08-12 05:19:57 +00:00
|
|
|
|
|
|
|
if (pos < 0)
|
|
|
|
pos = -1 - pos;
|
2019-01-12 02:13:29 +00:00
|
|
|
if (pos < istate->cache_nr &&
|
|
|
|
!strncmp(dirpath.buf, istate->cache[pos]->name, dirpath.len)) {
|
2015-09-24 21:07:43 +00:00
|
|
|
strbuf_release(&dirpath);
|
2011-08-12 05:19:57 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2015-09-24 21:07:43 +00:00
|
|
|
strbuf_release(&dirpath);
|
2016-11-07 18:31:31 +00:00
|
|
|
return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode) &&
|
2019-09-18 20:27:38 +00:00
|
|
|
!(empty_ok && is_empty_dir(path)) &&
|
|
|
|
!has_symlink_leading_path(path, strlen(path));
|
2011-08-12 05:19:57 +00:00
|
|
|
}
|
|
|
|
|
merge-recursive: fix check for skipability of working tree updates
The can-working-tree-updates-be-skipped check has had a long and blemished
history. The update can be skipped iff:
a) The merge is clean
b) The merge matches what was in HEAD (content, mode, pathname)
c) The target path is usable (i.e. not involved in D/F conflict)
Traditionally, we split b into parts:
b1) The merged result matches the content and mode found in HEAD
b2) The merged target path existed in HEAD
Steps a & b1 are easy to check; we have always gotten those right. While
it is easy to overlook step c, this was fixed seven years ago with commit
4ab9a157d069 ("merge_content(): Check whether D/F conflicts are still
present", 2010-09-20). merge-recursive didn't have a readily available
way to directly check step b2, so various approximations were used:
* In commit b2c8c0a76274 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-02-28), it was noted that although
the code claimed it was skipping the update, it did not actually skip
the update. The code was made to skip it, but used lstat(path, ...)
as an approximation to path-was-tracked-in-index-before-merge.
* In commit 5b448b853030 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-08-11), the problem with using
lstat was noted. It was changed to the approximation
path2 && strcmp(path, path2)
which is also wrong. !path2 || strcmp(path, path2) would have been
better, but would have fallen short with directory renames.
* In c5b761fb2711 ("merge-recursive: ensure we write updates for
directory-renamed file", 2018-02-14), the problem with the previous
approximation was noted and changed to
was_tracked(path)
That looks close to what we were trying to answer, but was_tracked()
as implemented at the time should have been named is_tracked(); it
returned something different than what we were looking for.
* To make matters more complex, fixing was_tracked() isn't sufficient
because the splitting of b into b1 and b2 is wrong. Consider the
following merge with a rename/add conflict:
side A: modify foo, add unrelated bar
side B: rename foo->bar (but don't modify the mode or contents)
In this case, the three-way merge of original foo, A's foo, and B's
bar will result in a desired pathname of bar with the same
mode/contents that A had for foo. Thus, A had the right mode and
contents for the file, and it had the right pathname present (namely,
bar), but the bar that was present was unrelated to the contents, so
the working tree update was not skippable.
Fix this by introducing a new function:
was_tracked_and_matches(o, path, &mfi.oid, mfi.mode)
and use it to directly check for condition b.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-19 17:58:23 +00:00
|
|
|
/*
|
|
|
|
* Returns whether path was tracked in the index before the merge started,
|
|
|
|
* and its oid and mode match the specified values
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
static int was_tracked_and_matches(struct merge_options *opt, const char *path,
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *blob)
|
2008-12-15 10:41:24 +00:00
|
|
|
{
|
2019-08-17 18:41:41 +00:00
|
|
|
int pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));
|
merge-recursive: fix check for skipability of working tree updates
The can-working-tree-updates-be-skipped check has had a long and blemished
history. The update can be skipped iff:
a) The merge is clean
b) The merge matches what was in HEAD (content, mode, pathname)
c) The target path is usable (i.e. not involved in D/F conflict)
Traditionally, we split b into parts:
b1) The merged result matches the content and mode found in HEAD
b2) The merged target path existed in HEAD
Steps a & b1 are easy to check; we have always gotten those right. While
it is easy to overlook step c, this was fixed seven years ago with commit
4ab9a157d069 ("merge_content(): Check whether D/F conflicts are still
present", 2010-09-20). merge-recursive didn't have a readily available
way to directly check step b2, so various approximations were used:
* In commit b2c8c0a76274 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-02-28), it was noted that although
the code claimed it was skipping the update, it did not actually skip
the update. The code was made to skip it, but used lstat(path, ...)
as an approximation to path-was-tracked-in-index-before-merge.
* In commit 5b448b853030 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-08-11), the problem with using
lstat was noted. It was changed to the approximation
path2 && strcmp(path, path2)
which is also wrong. !path2 || strcmp(path, path2) would have been
better, but would have fallen short with directory renames.
* In c5b761fb2711 ("merge-recursive: ensure we write updates for
directory-renamed file", 2018-02-14), the problem with the previous
approximation was noted and changed to
was_tracked(path)
That looks close to what we were trying to answer, but was_tracked()
as implemented at the time should have been named is_tracked(); it
returned something different than what we were looking for.
* To make matters more complex, fixing was_tracked() isn't sufficient
because the splitting of b into b1 and b2 is wrong. Consider the
following merge with a rename/add conflict:
side A: modify foo, add unrelated bar
side B: rename foo->bar (but don't modify the mode or contents)
In this case, the three-way merge of original foo, A's foo, and B's
bar will result in a desired pathname of bar with the same
mode/contents that A had for foo. Thus, A had the right mode and
contents for the file, and it had the right pathname present (namely,
bar), but the bar that was present was unrelated to the contents, so
the working tree update was not skippable.
Fix this by introducing a new function:
was_tracked_and_matches(o, path, &mfi.oid, mfi.mode)
and use it to directly check for condition b.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-19 17:58:23 +00:00
|
|
|
struct cache_entry *ce;
|
|
|
|
|
|
|
|
if (0 > pos)
|
|
|
|
/* we were not tracking this path before the merge */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* See if the file we were tracking before matches */
|
2019-08-17 18:41:41 +00:00
|
|
|
ce = opt->priv->orig_index.cache[pos];
|
merge-recursive: remove unnecessary oid_eq function
Back when merge-recursive was first introduced in commit 6d297f8137
(Status update on merge-recursive in C, 2006-07-08), it created a
sha_eq() function. This function pre-dated the introduction of
hashcmp() to cache.h by about a month, but was switched over to using
hashcmp() as part of commit 9047ebbc22 (Split out merge_recursive() to
merge-recursive.c, 2008-08-12). In commit b4da9d62f9 (merge-recursive:
convert leaf functions to use struct object_id, 2016-06-24), sha_eq() was
renamed to oid_eq() and its hashcmp() call was switched to oideq().
oid_eq() is basically just a wrapper around oideq() that has some extra
checks to protect against NULL arguments or to allow short-circuiting if
one of the arguments is NULL. I don't know if any caller ever tried to
call with NULL arguments, but certainly none do now which means the
extra checks serve no purpose. (Also, if these checks were genuinely
useful, then they probably should be added to the main oideq() so all
callers could benefit from them.)
Reduce the cognitive overhead of having both oid_eq() and oideq(), by
getting rid of merge-recursive's special oid_eq() wrapper.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-01 05:20:57 +00:00
|
|
|
return (oideq(&ce->oid, &blob->oid) && ce->ce_mode == blob->mode);
|
merge-recursive: fix check for skipability of working tree updates
The can-working-tree-updates-be-skipped check has had a long and blemished
history. The update can be skipped iff:
a) The merge is clean
b) The merge matches what was in HEAD (content, mode, pathname)
c) The target path is usable (i.e. not involved in D/F conflict)
Traditionally, we split b into parts:
b1) The merged result matches the content and mode found in HEAD
b2) The merged target path existed in HEAD
Steps a & b1 are easy to check; we have always gotten those right. While
it is easy to overlook step c, this was fixed seven years ago with commit
4ab9a157d069 ("merge_content(): Check whether D/F conflicts are still
present", 2010-09-20). merge-recursive didn't have a readily available
way to directly check step b2, so various approximations were used:
* In commit b2c8c0a76274 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-02-28), it was noted that although
the code claimed it was skipping the update, it did not actually skip
the update. The code was made to skip it, but used lstat(path, ...)
as an approximation to path-was-tracked-in-index-before-merge.
* In commit 5b448b853030 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-08-11), the problem with using
lstat was noted. It was changed to the approximation
path2 && strcmp(path, path2)
which is also wrong. !path2 || strcmp(path, path2) would have been
better, but would have fallen short with directory renames.
* In c5b761fb2711 ("merge-recursive: ensure we write updates for
directory-renamed file", 2018-02-14), the problem with the previous
approximation was noted and changed to
was_tracked(path)
That looks close to what we were trying to answer, but was_tracked()
as implemented at the time should have been named is_tracked(); it
returned something different than what we were looking for.
* To make matters more complex, fixing was_tracked() isn't sufficient
because the splitting of b into b1 and b2 is wrong. Consider the
following merge with a rename/add conflict:
side A: modify foo, add unrelated bar
side B: rename foo->bar (but don't modify the mode or contents)
In this case, the three-way merge of original foo, A's foo, and B's
bar will result in a desired pathname of bar with the same
mode/contents that A had for foo. Thus, A had the right mode and
contents for the file, and it had the right pathname present (namely,
bar), but the bar that was present was unrelated to the contents, so
the working tree update was not skippable.
Fix this by introducing a new function:
was_tracked_and_matches(o, path, &mfi.oid, mfi.mode)
and use it to directly check for condition b.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-19 17:58:23 +00:00
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:20 +00:00
|
|
|
/*
|
|
|
|
* Returns whether path was tracked in the index before the merge started
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
static int was_tracked(struct merge_options *opt, const char *path)
|
2008-12-15 10:41:24 +00:00
|
|
|
{
|
2019-08-17 18:41:41 +00:00
|
|
|
int pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));
|
2008-12-15 10:41:24 +00:00
|
|
|
|
2016-07-26 16:05:57 +00:00
|
|
|
if (0 <= pos)
|
2018-04-19 17:58:20 +00:00
|
|
|
/* we were tracking this path before the merge */
|
2016-07-26 16:05:57 +00:00
|
|
|
return 1;
|
|
|
|
|
2011-08-12 05:19:59 +00:00
|
|
|
return 0;
|
2008-12-15 10:41:24 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int would_lose_untracked(struct merge_options *opt, const char *path)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2019-04-05 15:00:13 +00:00
|
|
|
struct index_state *istate = opt->repo->index;
|
2019-01-12 02:13:29 +00:00
|
|
|
|
2018-04-19 17:58:20 +00:00
|
|
|
/*
|
|
|
|
* This may look like it can be simplified to:
|
2019-04-05 15:00:13 +00:00
|
|
|
* return !was_tracked(opt, path) && file_exists(path)
|
2018-04-19 17:58:20 +00:00
|
|
|
* but it can't. This function needs to know whether path was in
|
|
|
|
* the working tree due to EITHER having been tracked in the index
|
|
|
|
* before the merge OR having been put into the working copy and
|
|
|
|
* index by unpack_trees(). Due to that either-or requirement, we
|
|
|
|
* check the current index instead of the original one.
|
|
|
|
*
|
|
|
|
* Note that we do not need to worry about merge-recursive itself
|
|
|
|
* updating the index after unpack_trees() and before calling this
|
|
|
|
* function, because we strictly require all code paths in
|
|
|
|
* merge-recursive to update the working tree first and the index
|
|
|
|
* second. Doing otherwise would break
|
|
|
|
* update_file()/would_lose_untracked(); see every comment in this
|
|
|
|
* file which mentions "update_stages".
|
|
|
|
*/
|
2019-01-12 02:13:29 +00:00
|
|
|
int pos = index_name_pos(istate, path, strlen(path));
|
2018-04-19 17:58:20 +00:00
|
|
|
|
|
|
|
if (pos < 0)
|
|
|
|
pos = -1 - pos;
|
2019-01-12 02:13:29 +00:00
|
|
|
while (pos < istate->cache_nr &&
|
|
|
|
!strcmp(path, istate->cache[pos]->name)) {
|
2018-04-19 17:58:20 +00:00
|
|
|
/*
|
|
|
|
* If stage #0, it is definitely tracked.
|
|
|
|
* If it has stage #2 then it was tracked
|
|
|
|
* before this merge started. All other
|
|
|
|
* cases the path was not tracked.
|
|
|
|
*/
|
2019-01-12 02:13:29 +00:00
|
|
|
switch (ce_stage(istate->cache[pos])) {
|
2018-04-19 17:58:20 +00:00
|
|
|
case 0:
|
|
|
|
case 2:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
return file_exists(path);
|
2008-12-15 10:41:24 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int was_dirty(struct merge_options *opt, const char *path)
|
2018-04-19 17:58:12 +00:00
|
|
|
{
|
|
|
|
struct cache_entry *ce;
|
|
|
|
int dirty = 1;
|
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opt->priv->call_depth || !was_tracked(opt, path))
|
2018-04-19 17:58:12 +00:00
|
|
|
return !dirty;
|
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
ce = index_file_exists(opt->priv->unpack_opts.src_index,
|
2018-04-19 17:58:21 +00:00
|
|
|
path, strlen(path), ignore_case);
|
2019-08-17 18:41:41 +00:00
|
|
|
dirty = verify_uptodate(ce, &opt->priv->unpack_opts) != 0;
|
2018-04-19 17:58:12 +00:00
|
|
|
return dirty;
|
2008-12-15 10:41:24 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int make_room_for_path(struct merge_options *opt, const char *path)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2011-08-12 05:20:01 +00:00
|
|
|
int status, i;
|
2012-07-25 14:53:13 +00:00
|
|
|
const char *msg = _("failed to create path '%s'%s");
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2011-08-12 05:20:01 +00:00
|
|
|
/* Unlink any D/F conflict files that are in the way */
|
2019-08-17 18:41:41 +00:00
|
|
|
for (i = 0; i < opt->priv->df_conflict_file_set.nr; i++) {
|
|
|
|
const char *df_path = opt->priv->df_conflict_file_set.items[i].string;
|
2011-08-12 05:20:01 +00:00
|
|
|
size_t pathlen = strlen(path);
|
|
|
|
size_t df_pathlen = strlen(df_path);
|
|
|
|
if (df_pathlen < pathlen &&
|
|
|
|
path[df_pathlen] == '/' &&
|
|
|
|
strncmp(path, df_path, df_pathlen) == 0) {
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 3,
|
2012-07-25 14:53:13 +00:00
|
|
|
_("Removing %s to make room for subdirectory\n"),
|
2011-08-12 05:20:01 +00:00
|
|
|
df_path);
|
|
|
|
unlink(df_path);
|
2019-08-17 18:41:41 +00:00
|
|
|
unsorted_string_list_delete_item(&opt->priv->df_conflict_file_set,
|
2011-08-12 05:20:01 +00:00
|
|
|
i, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make sure leading directories are created */
|
2008-08-12 16:45:14 +00:00
|
|
|
status = safe_create_leading_directories_const(path);
|
|
|
|
if (status) {
|
2016-07-26 16:06:26 +00:00
|
|
|
if (status == SCLD_EXISTS)
|
2008-08-12 16:45:14 +00:00
|
|
|
/* something else exists */
|
2019-04-05 15:00:13 +00:00
|
|
|
return err(opt, msg, path, _(": perhaps a D/F conflict?"));
|
|
|
|
return err(opt, msg, path, "");
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2008-12-15 10:41:24 +00:00
|
|
|
/*
|
|
|
|
* Do not unlink a file in the work tree if we are not
|
|
|
|
* tracking it.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
if (would_lose_untracked(opt, path))
|
|
|
|
return err(opt, _("refusing to lose untracked file at '%s'"),
|
2018-06-10 04:16:12 +00:00
|
|
|
path);
|
2008-12-15 10:41:24 +00:00
|
|
|
|
2008-08-12 16:45:14 +00:00
|
|
|
/* Successful unlink is good.. */
|
|
|
|
if (!unlink(path))
|
|
|
|
return 0;
|
|
|
|
/* .. and so is no existing file */
|
|
|
|
if (errno == ENOENT)
|
|
|
|
return 0;
|
|
|
|
/* .. but not some other error (who really cares what?) */
|
2019-04-05 15:00:13 +00:00
|
|
|
return err(opt, msg, path, _(": perhaps a D/F conflict?"));
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int update_file_flags(struct merge_options *opt,
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *contents,
|
2016-07-26 16:06:21 +00:00
|
|
|
const char *path,
|
|
|
|
int update_cache,
|
|
|
|
int update_wd)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2016-07-26 16:06:26 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opt->priv->call_depth)
|
2008-08-12 16:45:14 +00:00
|
|
|
update_wd = 0;
|
|
|
|
|
|
|
|
if (update_wd) {
|
|
|
|
enum object_type type;
|
|
|
|
void *buf;
|
|
|
|
unsigned long size;
|
|
|
|
|
2019-04-05 15:00:22 +00:00
|
|
|
if (S_ISGITLINK(contents->mode)) {
|
2009-04-29 18:08:18 +00:00
|
|
|
/*
|
|
|
|
* We may later decide to recursively descend into
|
|
|
|
* the submodule directory and update its index
|
|
|
|
* and/or work tree, but we do not do that now.
|
|
|
|
*/
|
2010-07-07 13:39:13 +00:00
|
|
|
update_wd = 0;
|
2009-04-29 18:08:18 +00:00
|
|
|
goto update_index;
|
2010-07-07 13:39:13 +00:00
|
|
|
}
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2023-03-28 13:58:50 +00:00
|
|
|
buf = repo_read_object_file(the_repository, &contents->oid,
|
|
|
|
&type, &size);
|
2019-08-17 18:41:26 +00:00
|
|
|
if (!buf) {
|
|
|
|
ret = err(opt, _("cannot read object %s '%s'"),
|
|
|
|
oid_to_hex(&contents->oid), path);
|
|
|
|
goto free_buf;
|
|
|
|
}
|
2016-07-26 16:06:26 +00:00
|
|
|
if (type != OBJ_BLOB) {
|
2019-04-05 15:00:22 +00:00
|
|
|
ret = err(opt, _("blob expected for %s '%s'"),
|
|
|
|
oid_to_hex(&contents->oid), path);
|
2016-07-26 16:06:26 +00:00
|
|
|
goto free_buf;
|
|
|
|
}
|
2019-04-05 15:00:22 +00:00
|
|
|
if (S_ISREG(contents->mode)) {
|
2008-10-09 19:12:12 +00:00
|
|
|
struct strbuf strbuf = STRBUF_INIT;
|
2019-08-17 18:41:33 +00:00
|
|
|
if (convert_to_working_tree(opt->repo->index,
|
convert: permit passing additional metadata to filter processes
There are a variety of situations where a filter process can make use of
some additional metadata. For example, some people find the ident
filter too limiting and would like to include the commit or the branch
in their smudged files. This information isn't available during
checkout as HEAD hasn't been updated at that point, and it wouldn't be
available in archives either.
Let's add a way to pass this metadata down to the filter. We pass the
blob we're operating on, the treeish (preferring the commit over the
tree if one exists), and the ref we're operating on. Note that we won't
pass this information in all cases, such as when renormalizing or when
we're performing diffs, since it doesn't make sense in those cases.
The data we currently get from the filter process looks like the
following:
command=smudge
pathname=git.c
0000
With this change, we'll get data more like this:
command=smudge
pathname=git.c
refname=refs/tags/v2.25.1
treeish=c522f061d551c9bb8684a7c3859b2ece4499b56b
blob=7be7ad34bd053884ec48923706e70c81719a8660
0000
There are a couple things to note about this approach. For operations
like checkout, treeish will always be a commit, since we cannot check
out individual trees, but for other operations, like archive, we can end
up operating on only a particular tree, so we'll provide only a tree as
the treeish. Similar comments apply for refname, since there are a
variety of cases in which we won't have a ref.
This commit wires up the code to print this information, but doesn't
pass any of it at this point. In a future commit, we'll have various
code paths pass the actual useful data down.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-03-16 18:05:02 +00:00
|
|
|
path, buf, size, &strbuf, NULL)) {
|
2008-08-12 16:45:14 +00:00
|
|
|
free(buf);
|
|
|
|
size = strbuf.len;
|
|
|
|
buf = strbuf_detach(&strbuf, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (make_room_for_path(opt, path) < 0) {
|
2008-08-12 16:45:14 +00:00
|
|
|
update_wd = 0;
|
2016-07-26 16:06:21 +00:00
|
|
|
goto free_buf;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
2019-04-05 15:00:22 +00:00
|
|
|
if (S_ISREG(contents->mode) ||
|
|
|
|
(!has_symlinks && S_ISLNK(contents->mode))) {
|
2008-08-12 16:45:14 +00:00
|
|
|
int fd;
|
2019-04-05 15:00:22 +00:00
|
|
|
int mode = (contents->mode & 0100 ? 0777 : 0666);
|
|
|
|
|
2008-08-12 16:45:14 +00:00
|
|
|
fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
|
2016-07-26 16:06:26 +00:00
|
|
|
if (fd < 0) {
|
2019-04-05 15:00:13 +00:00
|
|
|
ret = err(opt, _("failed to open '%s': %s"),
|
2016-08-01 11:44:37 +00:00
|
|
|
path, strerror(errno));
|
2016-07-26 16:06:26 +00:00
|
|
|
goto free_buf;
|
|
|
|
}
|
2012-08-03 12:16:25 +00:00
|
|
|
write_in_full(fd, buf, size);
|
2008-08-12 16:45:14 +00:00
|
|
|
close(fd);
|
2019-04-05 15:00:22 +00:00
|
|
|
} else if (S_ISLNK(contents->mode)) {
|
2008-08-12 16:45:14 +00:00
|
|
|
char *lnk = xmemdupz(buf, size);
|
|
|
|
safe_create_leading_directories_const(path);
|
|
|
|
unlink(path);
|
2008-12-05 00:39:14 +00:00
|
|
|
if (symlink(lnk, path))
|
2019-04-05 15:00:13 +00:00
|
|
|
ret = err(opt, _("failed to symlink '%s': %s"),
|
2018-06-10 04:16:12 +00:00
|
|
|
path, strerror(errno));
|
2008-08-12 16:45:14 +00:00
|
|
|
free(lnk);
|
|
|
|
} else
|
2019-04-05 15:00:13 +00:00
|
|
|
ret = err(opt,
|
2016-08-01 11:44:37 +00:00
|
|
|
_("do not know what to do with %06o %s '%s'"),
|
2019-04-05 15:00:22 +00:00
|
|
|
contents->mode, oid_to_hex(&contents->oid), path);
|
2018-06-10 04:16:13 +00:00
|
|
|
free_buf:
|
2008-08-12 16:45:14 +00:00
|
|
|
free(buf);
|
|
|
|
}
|
2018-06-10 04:16:13 +00:00
|
|
|
update_index:
|
2020-02-19 17:04:07 +00:00
|
|
|
if (!ret && update_cache) {
|
|
|
|
int refresh = (!opt->priv->call_depth &&
|
|
|
|
contents->mode != S_IFGITLINK);
|
|
|
|
if (add_cacheinfo(opt, contents, path, 0, refresh,
|
merge-recursive: improve add_cacheinfo error handling
Four closely related changes all with the purpose of fixing error handling
in this function:
- fix reported function name in add_cacheinfo error messages
- differentiate between the two error messages
- abort early when we hit the error (stop ignoring return code)
- mark a test which was hitting this error as failing until we get the
right fix
In more detail...
In commit 0424138d5715 ("Fix bogus error message from merge-recursive
error path", 2007-04-01), it was noted that the name of the function which
the error message claimed it was reported from did not match the actual
function name. This was changed to something closer to the real function
name, but it still didn't match the actual function name. Fix the
reported name to match.
Second, the two errors in this function had identical messages, preventing
us from knowing which error had been triggered. Add a couple words to the
second error message to differentiate the two.
Next, make sure callers do not ignore the return code so that it will stop
processing further entries (processing further entries could result in
more output which could cause the error to scroll off the screen, or at
least be missed by the user) and make it clear the error is the cause of
the early abort. These errors should never be triggered in production; if
either one is, it represents a bug in the calling path somewhere and is
likely to have resulted in mis-merged content. The combination of
ignoring of the return code and continuing to print other standard
messages after hitting the error resulted in the following bug report from
Junio: "...the command pretends that everything went well and merged
cleanly in that path...[Behaving] in a buggy and unexplainable way is bad
enough, doing so silently is unexcusable." Fix this.
Finally, there was one test in the testsuite that did hit this error path,
but was passing anyway. This would have been easy to miss since it had a
test_must_fail and thus could have failed for the wrong reason, but in a
separate testing step I added an intentional NULL-dereference to the
codepath where these error messages are printed in order to flush out such
cases. I could modify that test to explicitly check for this error and
fail the test if it is hit, but since this test operates in a bit of a
gray area and needed other changes, I went for a different fix. The gray
area this test operates in is the following: If the merge of a certain
file results in the same version of the file that existed in HEAD, but
there are dirty modifications to the file, is that an error with a
"Refusing to overwrite existing file" expected, or a case where the merge
should succeed since we shouldn't have to touch the dirty file anyway?
Recent discussion on the list leaned towards saying it should be a
success. Therefore, change the expected behavior of this test to match.
As a side effect, this makes the failed-due-to-hitting-add_cacheinfo-error
very clear, and we can mark the test as test_expect_failure. A subsequent
commit will implement the necessary changes to get this test to pass
again.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-19 17:58:16 +00:00
|
|
|
ADD_CACHE_OK_TO_ADD))
|
|
|
|
return -1;
|
2020-02-19 17:04:07 +00:00
|
|
|
}
|
2016-07-26 16:06:26 +00:00
|
|
|
return ret;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int update_file(struct merge_options *opt,
|
2016-07-26 16:06:21 +00:00
|
|
|
int clean,
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *contents,
|
2016-07-26 16:06:21 +00:00
|
|
|
const char *path)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2019-04-05 15:00:22 +00:00
|
|
|
return update_file_flags(opt, contents, path,
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->call_depth || clean, !opt->priv->call_depth);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Low level file merging, update and removal */
|
|
|
|
|
2011-03-16 07:08:34 +00:00
|
|
|
struct merge_file_info {
|
2019-04-05 15:00:22 +00:00
|
|
|
struct diff_filespec blob; /* mostly use oid & mode; sometimes path */
|
2008-08-12 16:45:14 +00:00
|
|
|
unsigned clean:1,
|
|
|
|
merge:1;
|
|
|
|
};
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int merge_3way(struct merge_options *opt,
|
2008-09-02 21:53:47 +00:00
|
|
|
mmbuffer_t *result_buf,
|
2019-04-05 15:00:14 +00:00
|
|
|
const struct diff_filespec *o,
|
2011-08-12 05:19:51 +00:00
|
|
|
const struct diff_filespec *a,
|
|
|
|
const struct diff_filespec *b,
|
2008-08-12 16:45:14 +00:00
|
|
|
const char *branch1,
|
2018-11-08 04:40:24 +00:00
|
|
|
const char *branch2,
|
|
|
|
const int extra_marker_size)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
|
|
|
mmfile_t orig, src1, src2;
|
2024-03-14 17:05:04 +00:00
|
|
|
struct ll_merge_options ll_opts = LL_MERGE_OPTIONS_INIT;
|
2019-08-15 21:40:32 +00:00
|
|
|
char *base, *name1, *name2;
|
2022-02-02 02:37:30 +00:00
|
|
|
enum ll_merge_result merge_status;
|
2009-11-26 02:23:55 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
ll_opts.renormalize = opt->renormalize;
|
2018-11-08 04:40:24 +00:00
|
|
|
ll_opts.extra_marker_size = extra_marker_size;
|
2019-04-05 15:00:13 +00:00
|
|
|
ll_opts.xdl_opts = opt->xdl_opts;
|
2024-03-14 17:05:05 +00:00
|
|
|
ll_opts.conflict_style = opt->conflict_style;
|
2010-08-26 05:49:53 +00:00
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opt->priv->call_depth) {
|
2010-08-26 05:49:53 +00:00
|
|
|
ll_opts.virtual_ancestor = 1;
|
|
|
|
ll_opts.variant = 0;
|
|
|
|
} else {
|
2019-04-05 15:00:13 +00:00
|
|
|
switch (opt->recursive_variant) {
|
2019-08-17 18:41:42 +00:00
|
|
|
case MERGE_VARIANT_OURS:
|
2010-08-26 05:49:53 +00:00
|
|
|
ll_opts.variant = XDL_MERGE_FAVOR_OURS;
|
2009-11-26 02:23:55 +00:00
|
|
|
break;
|
2019-08-17 18:41:42 +00:00
|
|
|
case MERGE_VARIANT_THEIRS:
|
2010-08-26 05:49:53 +00:00
|
|
|
ll_opts.variant = XDL_MERGE_FAVOR_THEIRS;
|
2009-11-26 02:23:55 +00:00
|
|
|
break;
|
|
|
|
default:
|
2010-08-26 05:49:53 +00:00
|
|
|
ll_opts.variant = 0;
|
2009-11-26 02:23:55 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-08-15 21:40:32 +00:00
|
|
|
assert(a->path && b->path && o->path && opt->ancestor);
|
|
|
|
if (strcmp(a->path, b->path) || strcmp(a->path, o->path) != 0) {
|
|
|
|
base = mkpathdup("%s:%s", opt->ancestor, o->path);
|
2012-09-04 17:31:14 +00:00
|
|
|
name1 = mkpathdup("%s:%s", branch1, a->path);
|
|
|
|
name2 = mkpathdup("%s:%s", branch2, b->path);
|
2009-07-01 20:18:04 +00:00
|
|
|
} else {
|
2019-08-15 21:40:32 +00:00
|
|
|
base = mkpathdup("%s", opt->ancestor);
|
2012-09-04 17:31:14 +00:00
|
|
|
name1 = mkpathdup("%s", branch1);
|
|
|
|
name2 = mkpathdup("%s", branch2);
|
2009-07-01 20:18:04 +00:00
|
|
|
}
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-04-05 15:00:14 +00:00
|
|
|
read_mmblob(&orig, &o->oid);
|
2016-09-05 20:08:02 +00:00
|
|
|
read_mmblob(&src1, &a->oid);
|
|
|
|
read_mmblob(&src2, &b->oid);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2021-03-20 00:03:53 +00:00
|
|
|
/*
|
|
|
|
* FIXME: Using a->path for normalization rules in ll_merge could be
|
|
|
|
* wrong if we renamed from a->path to b->path. We should use the
|
|
|
|
* target path for where the file will be written.
|
|
|
|
*/
|
2019-08-15 21:40:32 +00:00
|
|
|
merge_status = ll_merge(result_buf, a->path, &orig, base,
|
2018-09-21 15:57:27 +00:00
|
|
|
&src1, name1, &src2, name2,
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->repo->index, &ll_opts);
|
2022-02-02 02:37:30 +00:00
|
|
|
if (merge_status == LL_MERGE_BINARY_CONFLICT)
|
|
|
|
warning("Cannot merge binary files: %s (%s vs. %s)",
|
|
|
|
a->path, name1, name2);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-08-15 21:40:32 +00:00
|
|
|
free(base);
|
2008-08-12 16:45:14 +00:00
|
|
|
free(name1);
|
|
|
|
free(name2);
|
|
|
|
free(orig.ptr);
|
|
|
|
free(src1.ptr);
|
|
|
|
free(src2.ptr);
|
|
|
|
return merge_status;
|
|
|
|
}
|
|
|
|
|
2019-01-12 02:13:29 +00:00
|
|
|
static int find_first_merges(struct repository *repo,
|
|
|
|
struct object_array *result, const char *path,
|
2018-06-10 04:16:12 +00:00
|
|
|
struct commit *a, struct commit *b)
|
2018-05-15 20:00:28 +00:00
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
struct object_array merges = OBJECT_ARRAY_INIT;
|
|
|
|
struct commit *commit;
|
|
|
|
int contains_another;
|
|
|
|
|
2019-02-19 00:04:59 +00:00
|
|
|
char merged_revision[GIT_MAX_HEXSZ + 2];
|
2018-05-15 20:00:28 +00:00
|
|
|
const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
|
|
|
|
"--all", merged_revision, NULL };
|
|
|
|
struct rev_info revs;
|
|
|
|
struct setup_revision_opt rev_opts;
|
|
|
|
|
|
|
|
memset(result, 0, sizeof(struct object_array));
|
|
|
|
memset(&rev_opts, 0, sizeof(rev_opts));
|
|
|
|
|
|
|
|
/* get all revisions that merge commit a */
|
|
|
|
xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
|
2018-06-10 04:16:12 +00:00
|
|
|
oid_to_hex(&a->object.oid));
|
2019-01-12 02:13:29 +00:00
|
|
|
repo_init_revisions(repo, &revs, NULL);
|
2018-05-15 20:00:28 +00:00
|
|
|
/* FIXME: can't handle linked worktrees in submodules yet */
|
|
|
|
revs.single_worktree = path != NULL;
|
|
|
|
setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
|
|
|
|
|
|
|
|
/* save all revisions from the above list that contain b */
|
|
|
|
if (prepare_revision_walk(&revs))
|
|
|
|
die("revision walk setup failed");
|
|
|
|
while ((commit = get_revision(&revs)) != NULL) {
|
|
|
|
struct object *o = &(commit->object);
|
2024-02-28 09:44:09 +00:00
|
|
|
int ret = repo_in_merge_bases(repo, b, commit);
|
|
|
|
if (ret < 0) {
|
|
|
|
object_array_clear(&merges);
|
|
|
|
release_revisions(&revs);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
if (ret)
|
2018-05-15 20:00:28 +00:00
|
|
|
add_object_array(o, NULL, &merges);
|
|
|
|
}
|
|
|
|
reset_revision_walk();
|
|
|
|
|
|
|
|
/* Now we've got all merges that contain a and b. Prune all
|
|
|
|
* merges that contain another found merge and save them in
|
|
|
|
* result.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < merges.nr; i++) {
|
|
|
|
struct commit *m1 = (struct commit *) merges.objects[i].item;
|
|
|
|
|
|
|
|
contains_another = 0;
|
|
|
|
for (j = 0; j < merges.nr; j++) {
|
|
|
|
struct commit *m2 = (struct commit *) merges.objects[j].item;
|
2024-02-28 09:44:09 +00:00
|
|
|
if (i != j) {
|
|
|
|
int ret = repo_in_merge_bases(repo, m2, m1);
|
|
|
|
if (ret < 0) {
|
|
|
|
object_array_clear(&merges);
|
|
|
|
release_revisions(&revs);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
if (ret > 0) {
|
|
|
|
contains_another = 1;
|
|
|
|
break;
|
|
|
|
}
|
2018-05-15 20:00:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!contains_another)
|
|
|
|
add_object_array(merges.objects[i].item, NULL, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
object_array_clear(&merges);
|
2022-04-13 20:01:36 +00:00
|
|
|
release_revisions(&revs);
|
2018-05-15 20:00:28 +00:00
|
|
|
return result->nr;
|
|
|
|
}
|
|
|
|
|
2021-10-08 21:08:17 +00:00
|
|
|
static void print_commit(struct repository *repo, struct commit *commit)
|
2018-05-15 20:00:28 +00:00
|
|
|
{
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
struct pretty_print_context ctx = {0};
|
|
|
|
ctx.date_mode.type = DATE_NORMAL;
|
2021-03-20 00:03:53 +00:00
|
|
|
/* FIXME: Merge this with output_commit_title() */
|
|
|
|
assert(!merge_remote_util(commit));
|
2021-10-08 21:08:17 +00:00
|
|
|
repo_format_commit_message(repo, commit, " %h: %m %s", &sb, &ctx);
|
2018-05-15 20:00:28 +00:00
|
|
|
fprintf(stderr, "%s\n", sb.buf);
|
|
|
|
strbuf_release(&sb);
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:22 +00:00
|
|
|
static int is_valid(const struct diff_filespec *dfs)
|
|
|
|
{
|
|
|
|
return dfs->mode != 0 && !is_null_oid(&dfs->oid);
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int merge_submodule(struct merge_options *opt,
|
2018-05-15 20:00:29 +00:00
|
|
|
struct object_id *result, const char *path,
|
2018-05-15 20:00:28 +00:00
|
|
|
const struct object_id *base, const struct object_id *a,
|
2018-05-15 20:00:29 +00:00
|
|
|
const struct object_id *b)
|
2018-05-15 20:00:28 +00:00
|
|
|
{
|
2021-09-09 18:47:29 +00:00
|
|
|
struct repository subrepo;
|
2024-02-28 09:44:09 +00:00
|
|
|
int ret = 0, ret2;
|
2018-05-15 20:00:28 +00:00
|
|
|
struct commit *commit_base, *commit_a, *commit_b;
|
|
|
|
int parent_count;
|
|
|
|
struct object_array merges;
|
|
|
|
|
|
|
|
int i;
|
2019-08-17 18:41:41 +00:00
|
|
|
int search = !opt->priv->call_depth;
|
2018-05-15 20:00:28 +00:00
|
|
|
|
|
|
|
/* store a in result in case we fail */
|
2021-03-20 00:03:53 +00:00
|
|
|
/* FIXME: This is the WRONG resolution for the recursive case when
|
|
|
|
* we need to be careful to avoid accidentally matching either side.
|
|
|
|
* Should probably use o instead there, much like we do for merging
|
|
|
|
* binaries.
|
|
|
|
*/
|
2018-05-15 20:00:28 +00:00
|
|
|
oidcpy(result, a);
|
|
|
|
|
|
|
|
/* we can not handle deletion conflicts */
|
|
|
|
if (is_null_oid(base))
|
|
|
|
return 0;
|
|
|
|
if (is_null_oid(a))
|
|
|
|
return 0;
|
|
|
|
if (is_null_oid(b))
|
|
|
|
return 0;
|
|
|
|
|
2021-09-09 18:47:29 +00:00
|
|
|
if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
|
|
|
|
output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path);
|
2018-05-15 20:00:28 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-09-09 18:47:29 +00:00
|
|
|
if (!(commit_base = lookup_commit_reference(&subrepo, base)) ||
|
|
|
|
!(commit_a = lookup_commit_reference(&subrepo, a)) ||
|
|
|
|
!(commit_b = lookup_commit_reference(&subrepo, b))) {
|
|
|
|
output(opt, 1, _("Failed to merge submodule %s (commits not present)"), path);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2018-05-15 20:00:28 +00:00
|
|
|
/* check whether both changes are forward */
|
2024-02-28 09:44:09 +00:00
|
|
|
ret2 = repo_in_merge_bases(&subrepo, commit_base, commit_a);
|
|
|
|
if (ret2 < 0) {
|
|
|
|
output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path);
|
2024-03-09 14:09:57 +00:00
|
|
|
ret = -1;
|
2024-02-28 09:44:09 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (ret2 > 0)
|
|
|
|
ret2 = repo_in_merge_bases(&subrepo, commit_base, commit_b);
|
|
|
|
if (ret2 < 0) {
|
|
|
|
output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path);
|
2024-03-09 14:09:57 +00:00
|
|
|
ret = -1;
|
2024-02-28 09:44:09 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (!ret2) {
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("Failed to merge submodule %s (commits don't follow merge-base)"), path);
|
2021-09-09 18:47:29 +00:00
|
|
|
goto cleanup;
|
2018-05-15 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Case #1: a is contained in b or vice versa */
|
2024-02-28 09:44:09 +00:00
|
|
|
ret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);
|
|
|
|
if (ret2 < 0) {
|
|
|
|
output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path);
|
2024-03-09 14:09:57 +00:00
|
|
|
ret = -1;
|
2024-02-28 09:44:09 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (ret2) {
|
2018-05-15 20:00:28 +00:00
|
|
|
oidcpy(result, b);
|
2019-04-05 15:00:13 +00:00
|
|
|
if (show(opt, 3)) {
|
|
|
|
output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
|
2021-10-08 21:08:17 +00:00
|
|
|
repo_output_commit_title(opt, &subrepo, commit_b);
|
2019-04-05 15:00:13 +00:00
|
|
|
} else if (show(opt, 2))
|
|
|
|
output(opt, 2, _("Fast-forwarding submodule %s"), path);
|
2018-05-17 18:40:08 +00:00
|
|
|
else
|
|
|
|
; /* no output */
|
|
|
|
|
2021-09-09 18:47:29 +00:00
|
|
|
ret = 1;
|
|
|
|
goto cleanup;
|
2018-05-15 20:00:28 +00:00
|
|
|
}
|
2024-02-28 09:44:09 +00:00
|
|
|
ret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);
|
|
|
|
if (ret2 < 0) {
|
|
|
|
output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path);
|
2024-03-09 14:09:57 +00:00
|
|
|
ret = -1;
|
2024-02-28 09:44:09 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (ret2) {
|
2018-05-15 20:00:28 +00:00
|
|
|
oidcpy(result, a);
|
2019-04-05 15:00:13 +00:00
|
|
|
if (show(opt, 3)) {
|
|
|
|
output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
|
2021-10-08 21:08:17 +00:00
|
|
|
repo_output_commit_title(opt, &subrepo, commit_a);
|
2019-04-05 15:00:13 +00:00
|
|
|
} else if (show(opt, 2))
|
|
|
|
output(opt, 2, _("Fast-forwarding submodule %s"), path);
|
2018-05-17 18:40:08 +00:00
|
|
|
else
|
|
|
|
; /* no output */
|
|
|
|
|
2021-09-09 18:47:29 +00:00
|
|
|
ret = 1;
|
|
|
|
goto cleanup;
|
2018-05-15 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Case #2: There are one or more merges that contain a and b in
|
|
|
|
* the submodule. If there is only one, then present it as a
|
|
|
|
* suggestion to the user, but leave it marked unmerged so the
|
|
|
|
* user needs to confirm the resolution.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Skip the search if makes no sense to the calling context. */
|
|
|
|
if (!search)
|
2021-09-09 18:47:29 +00:00
|
|
|
goto cleanup;
|
2018-05-15 20:00:28 +00:00
|
|
|
|
|
|
|
/* find commit which merges them */
|
2021-09-09 18:47:29 +00:00
|
|
|
parent_count = find_first_merges(&subrepo, &merges, path,
|
2019-01-12 02:13:29 +00:00
|
|
|
commit_a, commit_b);
|
2018-05-15 20:00:28 +00:00
|
|
|
switch (parent_count) {
|
2024-03-09 14:09:57 +00:00
|
|
|
case -1:
|
|
|
|
output(opt, 1,_("Failed to merge submodule %s (repository corrupt)"), path);
|
|
|
|
ret = -1;
|
|
|
|
break;
|
2018-05-15 20:00:28 +00:00
|
|
|
case 0:
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("Failed to merge submodule %s (merge following commits not found)"), path);
|
2018-05-15 20:00:28 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("Failed to merge submodule %s (not fast-forward)"), path);
|
|
|
|
output(opt, 2, _("Found a possible merge resolution for the submodule:\n"));
|
2021-10-08 21:08:17 +00:00
|
|
|
print_commit(&subrepo, (struct commit *) merges.objects[0].item);
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 2, _(
|
2018-06-10 04:16:12 +00:00
|
|
|
"If this is correct simply add it to the index "
|
|
|
|
"for example\n"
|
|
|
|
"by using:\n\n"
|
|
|
|
" git update-index --cacheinfo 160000 %s \"%s\"\n\n"
|
|
|
|
"which will accept this suggestion.\n"),
|
|
|
|
oid_to_hex(&merges.objects[0].item->oid), path);
|
2018-05-15 20:00:28 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("Failed to merge submodule %s (multiple merges found)"), path);
|
2018-05-15 20:00:28 +00:00
|
|
|
for (i = 0; i < merges.nr; i++)
|
2021-10-08 21:08:17 +00:00
|
|
|
print_commit(&subrepo, (struct commit *) merges.objects[i].item);
|
2018-05-15 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
object_array_clear(&merges);
|
2021-09-09 18:47:29 +00:00
|
|
|
cleanup:
|
|
|
|
repo_clear(&subrepo);
|
|
|
|
return ret;
|
2018-05-15 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int merge_mode_and_contents(struct merge_options *opt,
|
2019-04-05 15:00:14 +00:00
|
|
|
const struct diff_filespec *o,
|
2018-09-19 16:14:34 +00:00
|
|
|
const struct diff_filespec *a,
|
|
|
|
const struct diff_filespec *b,
|
|
|
|
const char *filename,
|
|
|
|
const char *branch1,
|
|
|
|
const char *branch2,
|
2018-11-08 04:40:24 +00:00
|
|
|
const int extra_marker_size,
|
2018-09-19 16:14:34 +00:00
|
|
|
struct merge_file_info *result)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2019-04-05 15:00:13 +00:00
|
|
|
if (opt->branch1 != branch1) {
|
2018-10-16 20:19:48 +00:00
|
|
|
/*
|
|
|
|
* It's weird getting a reverse merge with HEAD on the bottom
|
|
|
|
* side of the conflict markers and the other branch on the
|
|
|
|
* top. Fix that.
|
|
|
|
*/
|
2019-04-05 15:00:14 +00:00
|
|
|
return merge_mode_and_contents(opt, o, b, a,
|
2018-10-16 20:19:48 +00:00
|
|
|
filename,
|
2018-11-08 04:40:24 +00:00
|
|
|
branch2, branch1,
|
|
|
|
extra_marker_size, result);
|
2018-10-16 20:19:48 +00:00
|
|
|
}
|
|
|
|
|
2016-07-26 16:06:10 +00:00
|
|
|
result->merge = 0;
|
|
|
|
result->clean = 1;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
|
|
|
if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
|
2016-07-26 16:06:10 +00:00
|
|
|
result->clean = 0;
|
2021-03-20 00:03:53 +00:00
|
|
|
/*
|
|
|
|
* FIXME: This is a bad resolution for recursive case; for
|
|
|
|
* the recursive case we want something that is unlikely to
|
|
|
|
* accidentally match either side. Also, while it makes
|
|
|
|
* sense to prefer regular files over symlinks, it doesn't
|
|
|
|
* make sense to prefer regular files over submodules.
|
|
|
|
*/
|
2008-08-12 16:45:14 +00:00
|
|
|
if (S_ISREG(a->mode)) {
|
2019-04-05 15:00:22 +00:00
|
|
|
result->blob.mode = a->mode;
|
|
|
|
oidcpy(&result->blob.oid, &a->oid);
|
2008-08-12 16:45:14 +00:00
|
|
|
} else {
|
2019-04-05 15:00:22 +00:00
|
|
|
result->blob.mode = b->mode;
|
|
|
|
oidcpy(&result->blob.oid, &b->oid);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
} else {
|
merge-recursive: remove unnecessary oid_eq function
Back when merge-recursive was first introduced in commit 6d297f8137
(Status update on merge-recursive in C, 2006-07-08), it created a
sha_eq() function. This function pre-dated the introduction of
hashcmp() to cache.h by about a month, but was switched over to using
hashcmp() as part of commit 9047ebbc22 (Split out merge_recursive() to
merge-recursive.c, 2008-08-12). In commit b4da9d62f9 (merge-recursive:
convert leaf functions to use struct object_id, 2016-06-24), sha_eq() was
renamed to oid_eq() and its hashcmp() call was switched to oideq().
oid_eq() is basically just a wrapper around oideq() that has some extra
checks to protect against NULL arguments or to allow short-circuiting if
one of the arguments is NULL. I don't know if any caller ever tried to
call with NULL arguments, but certainly none do now which means the
extra checks serve no purpose. (Also, if these checks were genuinely
useful, then they probably should be added to the main oideq() so all
callers could benefit from them.)
Reduce the cognitive overhead of having both oid_eq() and oideq(), by
getting rid of merge-recursive's special oid_eq() wrapper.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-01 05:20:57 +00:00
|
|
|
if (!oideq(&a->oid, &o->oid) && !oideq(&b->oid, &o->oid))
|
2016-07-26 16:06:10 +00:00
|
|
|
result->merge = 1;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Merge modes
|
|
|
|
*/
|
2019-04-05 15:00:14 +00:00
|
|
|
if (a->mode == b->mode || a->mode == o->mode)
|
2019-04-05 15:00:22 +00:00
|
|
|
result->blob.mode = b->mode;
|
2008-08-12 16:45:14 +00:00
|
|
|
else {
|
2019-04-05 15:00:22 +00:00
|
|
|
result->blob.mode = a->mode;
|
2019-04-05 15:00:14 +00:00
|
|
|
if (b->mode != o->mode) {
|
2016-07-26 16:06:10 +00:00
|
|
|
result->clean = 0;
|
|
|
|
result->merge = 1;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
merge-recursive: remove unnecessary oid_eq function
Back when merge-recursive was first introduced in commit 6d297f8137
(Status update on merge-recursive in C, 2006-07-08), it created a
sha_eq() function. This function pre-dated the introduction of
hashcmp() to cache.h by about a month, but was switched over to using
hashcmp() as part of commit 9047ebbc22 (Split out merge_recursive() to
merge-recursive.c, 2008-08-12). In commit b4da9d62f9 (merge-recursive:
convert leaf functions to use struct object_id, 2016-06-24), sha_eq() was
renamed to oid_eq() and its hashcmp() call was switched to oideq().
oid_eq() is basically just a wrapper around oideq() that has some extra
checks to protect against NULL arguments or to allow short-circuiting if
one of the arguments is NULL. I don't know if any caller ever tried to
call with NULL arguments, but certainly none do now which means the
extra checks serve no purpose. (Also, if these checks were genuinely
useful, then they probably should be added to the main oideq() so all
callers could benefit from them.)
Reduce the cognitive overhead of having both oid_eq() and oideq(), by
getting rid of merge-recursive's special oid_eq() wrapper.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-01 05:20:57 +00:00
|
|
|
if (oideq(&a->oid, &b->oid) || oideq(&a->oid, &o->oid))
|
2019-04-05 15:00:22 +00:00
|
|
|
oidcpy(&result->blob.oid, &b->oid);
|
merge-recursive: remove unnecessary oid_eq function
Back when merge-recursive was first introduced in commit 6d297f8137
(Status update on merge-recursive in C, 2006-07-08), it created a
sha_eq() function. This function pre-dated the introduction of
hashcmp() to cache.h by about a month, but was switched over to using
hashcmp() as part of commit 9047ebbc22 (Split out merge_recursive() to
merge-recursive.c, 2008-08-12). In commit b4da9d62f9 (merge-recursive:
convert leaf functions to use struct object_id, 2016-06-24), sha_eq() was
renamed to oid_eq() and its hashcmp() call was switched to oideq().
oid_eq() is basically just a wrapper around oideq() that has some extra
checks to protect against NULL arguments or to allow short-circuiting if
one of the arguments is NULL. I don't know if any caller ever tried to
call with NULL arguments, but certainly none do now which means the
extra checks serve no purpose. (Also, if these checks were genuinely
useful, then they probably should be added to the main oideq() so all
callers could benefit from them.)
Reduce the cognitive overhead of having both oid_eq() and oideq(), by
getting rid of merge-recursive's special oid_eq() wrapper.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-01 05:20:57 +00:00
|
|
|
else if (oideq(&b->oid, &o->oid))
|
2019-04-05 15:00:22 +00:00
|
|
|
oidcpy(&result->blob.oid, &a->oid);
|
2008-08-12 16:45:14 +00:00
|
|
|
else if (S_ISREG(a->mode)) {
|
|
|
|
mmbuffer_t result_buf;
|
2016-07-26 16:06:26 +00:00
|
|
|
int ret = 0, merge_status;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-04-05 15:00:14 +00:00
|
|
|
merge_status = merge_3way(opt, &result_buf, o, a, b,
|
2018-11-08 04:40:24 +00:00
|
|
|
branch1, branch2,
|
|
|
|
extra_marker_size);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
|
|
|
if ((merge_status < 0) || !result_buf.ptr)
|
2023-09-16 22:11:46 +00:00
|
|
|
ret = err(opt, _("failed to execute internal merge"));
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2018-01-28 00:13:19 +00:00
|
|
|
if (!ret &&
|
|
|
|
write_object_file(result_buf.ptr, result_buf.size,
|
2022-02-04 23:48:26 +00:00
|
|
|
OBJ_BLOB, &result->blob.oid))
|
2023-09-16 22:11:46 +00:00
|
|
|
ret = err(opt, _("unable to add %s to database"),
|
2016-08-01 11:44:37 +00:00
|
|
|
a->path);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
|
|
|
free(result_buf.ptr);
|
2016-07-26 16:06:26 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2021-03-20 00:03:53 +00:00
|
|
|
/* FIXME: bug, what if modes didn't match? */
|
2016-07-26 16:06:10 +00:00
|
|
|
result->clean = (merge_status == 0);
|
2008-08-12 16:45:14 +00:00
|
|
|
} else if (S_ISGITLINK(a->mode)) {
|
2024-03-09 14:09:56 +00:00
|
|
|
int clean = merge_submodule(opt, &result->blob.oid,
|
|
|
|
o->path,
|
|
|
|
&o->oid,
|
|
|
|
&a->oid,
|
|
|
|
&b->oid);
|
|
|
|
if (clean < 0)
|
2024-02-28 09:44:09 +00:00
|
|
|
return -1;
|
2024-03-09 14:09:56 +00:00
|
|
|
result->clean = clean;
|
2008-08-12 16:45:14 +00:00
|
|
|
} else if (S_ISLNK(a->mode)) {
|
2019-04-05 15:00:13 +00:00
|
|
|
switch (opt->recursive_variant) {
|
2019-08-17 18:41:42 +00:00
|
|
|
case MERGE_VARIANT_NORMAL:
|
2019-04-05 15:00:22 +00:00
|
|
|
oidcpy(&result->blob.oid, &a->oid);
|
merge-recursive: remove unnecessary oid_eq function
Back when merge-recursive was first introduced in commit 6d297f8137
(Status update on merge-recursive in C, 2006-07-08), it created a
sha_eq() function. This function pre-dated the introduction of
hashcmp() to cache.h by about a month, but was switched over to using
hashcmp() as part of commit 9047ebbc22 (Split out merge_recursive() to
merge-recursive.c, 2008-08-12). In commit b4da9d62f9 (merge-recursive:
convert leaf functions to use struct object_id, 2016-06-24), sha_eq() was
renamed to oid_eq() and its hashcmp() call was switched to oideq().
oid_eq() is basically just a wrapper around oideq() that has some extra
checks to protect against NULL arguments or to allow short-circuiting if
one of the arguments is NULL. I don't know if any caller ever tried to
call with NULL arguments, but certainly none do now which means the
extra checks serve no purpose. (Also, if these checks were genuinely
useful, then they probably should be added to the main oideq() so all
callers could benefit from them.)
Reduce the cognitive overhead of having both oid_eq() and oideq(), by
getting rid of merge-recursive's special oid_eq() wrapper.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-01 05:20:57 +00:00
|
|
|
if (!oideq(&a->oid, &b->oid))
|
2017-09-26 02:40:42 +00:00
|
|
|
result->clean = 0;
|
|
|
|
break;
|
2019-08-17 18:41:42 +00:00
|
|
|
case MERGE_VARIANT_OURS:
|
2019-04-05 15:00:22 +00:00
|
|
|
oidcpy(&result->blob.oid, &a->oid);
|
2017-09-26 02:40:42 +00:00
|
|
|
break;
|
2019-08-17 18:41:42 +00:00
|
|
|
case MERGE_VARIANT_THEIRS:
|
2019-04-05 15:00:22 +00:00
|
|
|
oidcpy(&result->blob.oid, &b->oid);
|
2017-09-26 02:40:42 +00:00
|
|
|
break;
|
|
|
|
}
|
2016-07-26 16:05:50 +00:00
|
|
|
} else
|
2018-05-02 09:38:39 +00:00
|
|
|
BUG("unsupported object type in the tree");
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:22 +00:00
|
|
|
if (result->merge)
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 2, _("Auto-merging %s"), filename);
|
2018-04-19 17:58:22 +00:00
|
|
|
|
2016-07-26 16:06:10 +00:00
|
|
|
return 0;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int handle_rename_via_dir(struct merge_options *opt,
|
2019-04-05 15:00:21 +00:00
|
|
|
struct rename_conflict_info *ci)
|
2018-04-19 17:58:10 +00:00
|
|
|
{
|
2018-06-10 04:16:14 +00:00
|
|
|
/*
|
|
|
|
* Handle file adds that need to be renamed due to directory rename
|
|
|
|
* detection. This differs from handle_rename_normal, because
|
|
|
|
* there is no content merge to do; just move the file into the
|
|
|
|
* desired final location.
|
|
|
|
*/
|
2019-04-05 15:00:21 +00:00
|
|
|
const struct rename *ren = ci->ren1;
|
|
|
|
const struct diff_filespec *dest = ren->pair->two;
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
char *file_path = dest->path;
|
2019-08-17 18:41:25 +00:00
|
|
|
int mark_conflicted = (opt->detect_directory_renames ==
|
|
|
|
MERGE_DIRECTORY_RENAMES_CONFLICT);
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
assert(ren->dir_rename_original_dest);
|
2018-04-19 17:58:10 +00:00
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
if (!opt->priv->call_depth && would_lose_untracked(opt, dest->path)) {
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
mark_conflicted = 1;
|
|
|
|
file_path = unique_path(opt, dest->path, ren->branch);
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("Error: Refusing to lose untracked file at %s; "
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
"writing to %s instead."),
|
|
|
|
dest->path, file_path);
|
|
|
|
}
|
2018-04-19 17:58:11 +00:00
|
|
|
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
if (mark_conflicted) {
|
2018-04-19 17:58:11 +00:00
|
|
|
/*
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
* Write the file in worktree at file_path. In the index,
|
|
|
|
* only record the file at dest->path in the appropriate
|
|
|
|
* higher stage.
|
2018-04-19 17:58:11 +00:00
|
|
|
*/
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
if (update_file(opt, 0, dest, file_path))
|
2018-04-19 17:58:11 +00:00
|
|
|
return -1;
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
if (file_path != dest->path)
|
|
|
|
free(file_path);
|
|
|
|
if (update_stages(opt, dest->path, NULL,
|
|
|
|
ren->branch == opt->branch1 ? dest : NULL,
|
|
|
|
ren->branch == opt->branch1 ? NULL : dest))
|
|
|
|
return -1;
|
|
|
|
return 0; /* not clean, but conflicted */
|
|
|
|
} else {
|
|
|
|
/* Update dest->path both in index and in worktree */
|
|
|
|
if (update_file(opt, 1, dest, dest->path))
|
|
|
|
return -1;
|
|
|
|
return 1; /* clean */
|
2018-04-19 17:58:11 +00:00
|
|
|
}
|
2011-08-12 05:20:12 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int handle_change_delete(struct merge_options *opt,
|
2018-06-10 04:16:12 +00:00
|
|
|
const char *path, const char *old_path,
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *o,
|
|
|
|
const struct diff_filespec *changed,
|
2018-06-10 04:16:12 +00:00
|
|
|
const char *change_branch,
|
|
|
|
const char *delete_branch,
|
|
|
|
const char *change, const char *change_past)
|
2011-08-12 05:20:19 +00:00
|
|
|
{
|
2017-01-28 20:37:01 +00:00
|
|
|
char *alt_path = NULL;
|
|
|
|
const char *update_path = path;
|
2016-07-26 16:06:21 +00:00
|
|
|
int ret = 0;
|
2017-01-28 20:37:01 +00:00
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
if (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0) ||
|
|
|
|
(!opt->priv->call_depth && would_lose_untracked(opt, path))) {
|
2019-04-05 15:00:13 +00:00
|
|
|
update_path = alt_path = unique_path(opt, path, change_branch);
|
2011-08-12 05:20:19 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opt->priv->call_depth) {
|
2011-08-12 05:20:19 +00:00
|
|
|
/*
|
|
|
|
* We cannot arbitrarily accept either a_sha or b_sha as
|
|
|
|
* correct; since there is no true "middle point" between
|
|
|
|
* them, simply reuse the base version for virtual merge base.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
ret = remove_file_from_index(opt->repo->index, path);
|
2016-07-26 16:06:21 +00:00
|
|
|
if (!ret)
|
2019-04-05 15:00:22 +00:00
|
|
|
ret = update_file(opt, 0, o, update_path);
|
2011-08-12 05:20:19 +00:00
|
|
|
} else {
|
2018-06-10 04:16:16 +00:00
|
|
|
/*
|
|
|
|
* Despite the four nearly duplicate messages and argument
|
|
|
|
* lists below and the ugliness of the nested if-statements,
|
|
|
|
* having complete messages makes the job easier for
|
|
|
|
* translators.
|
|
|
|
*
|
|
|
|
* The slight variance among the cases is due to the fact
|
|
|
|
* that:
|
|
|
|
* 1) directory/file conflicts (in effect if
|
|
|
|
* !alt_path) could cause us to need to write the
|
|
|
|
* file to a different path.
|
|
|
|
* 2) renames (in effect if !old_path) could mean that
|
|
|
|
* there are two names for the path that the user
|
|
|
|
* may know the file by.
|
|
|
|
*/
|
2017-01-28 20:37:01 +00:00
|
|
|
if (!alt_path) {
|
|
|
|
if (!old_path) {
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (%s/delete): %s deleted in %s "
|
2017-01-28 20:37:01 +00:00
|
|
|
"and %s in %s. Version %s of %s left in tree."),
|
|
|
|
change, path, delete_branch, change_past,
|
|
|
|
change_branch, change_branch, path);
|
|
|
|
} else {
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (%s/delete): %s deleted in %s "
|
2017-01-28 20:37:01 +00:00
|
|
|
"and %s to %s in %s. Version %s of %s left in tree."),
|
|
|
|
change, old_path, delete_branch, change_past, path,
|
|
|
|
change_branch, change_branch, path);
|
|
|
|
}
|
2012-07-25 14:53:13 +00:00
|
|
|
} else {
|
2017-01-28 20:37:01 +00:00
|
|
|
if (!old_path) {
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (%s/delete): %s deleted in %s "
|
2017-01-28 20:37:01 +00:00
|
|
|
"and %s in %s. Version %s of %s left in tree at %s."),
|
|
|
|
change, path, delete_branch, change_past,
|
|
|
|
change_branch, change_branch, path, alt_path);
|
|
|
|
} else {
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (%s/delete): %s deleted in %s "
|
2017-01-28 20:37:01 +00:00
|
|
|
"and %s to %s in %s. Version %s of %s left in tree at %s."),
|
|
|
|
change, old_path, delete_branch, change_past, path,
|
|
|
|
change_branch, change_branch, path, alt_path);
|
|
|
|
}
|
2012-07-25 14:53:13 +00:00
|
|
|
}
|
2011-08-12 05:20:27 +00:00
|
|
|
/*
|
2017-01-28 20:37:01 +00:00
|
|
|
* No need to call update_file() on path when change_branch ==
|
2019-04-05 15:00:13 +00:00
|
|
|
* opt->branch1 && !alt_path, since that would needlessly touch
|
2017-01-28 20:37:01 +00:00
|
|
|
* path. We could call update_file_flags() with update_cache=0
|
|
|
|
* and update_wd=0, but that's a no-op.
|
2011-08-12 05:20:27 +00:00
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
if (change_branch != opt->branch1 || alt_path)
|
2019-04-05 15:00:22 +00:00
|
|
|
ret = update_file(opt, 0, changed, update_path);
|
2011-08-12 05:20:19 +00:00
|
|
|
}
|
2017-01-28 20:37:01 +00:00
|
|
|
free(alt_path);
|
2016-07-26 16:06:21 +00:00
|
|
|
|
|
|
|
return ret;
|
2011-08-12 05:20:19 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int handle_rename_delete(struct merge_options *opt,
|
2019-04-05 15:00:21 +00:00
|
|
|
struct rename_conflict_info *ci)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2019-04-05 15:00:21 +00:00
|
|
|
const struct rename *ren = ci->ren1;
|
|
|
|
const struct diff_filespec *orig = ren->pair->one;
|
|
|
|
const struct diff_filespec *dest = ren->pair->two;
|
|
|
|
const char *rename_branch = ren->branch;
|
|
|
|
const char *delete_branch = (opt->branch1 == ren->branch ?
|
|
|
|
opt->branch2 : opt->branch1);
|
2010-09-20 08:28:50 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (handle_change_delete(opt,
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->call_depth ? orig->path : dest->path,
|
|
|
|
opt->priv->call_depth ? NULL : orig->path,
|
2019-04-05 15:00:22 +00:00
|
|
|
orig, dest,
|
2017-01-28 20:37:01 +00:00
|
|
|
rename_branch, delete_branch,
|
2016-07-26 16:06:21 +00:00
|
|
|
_("rename"), _("renamed")))
|
|
|
|
return -1;
|
2011-08-12 05:20:20 +00:00
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opt->priv->call_depth)
|
2019-04-05 15:00:13 +00:00
|
|
|
return remove_file_from_index(opt->repo->index, dest->path);
|
2016-07-26 16:06:21 +00:00
|
|
|
else
|
2019-04-05 15:00:13 +00:00
|
|
|
return update_stages(opt, dest->path, NULL,
|
|
|
|
rename_branch == opt->branch1 ? dest : NULL,
|
|
|
|
rename_branch == opt->branch1 ? NULL : dest);
|
2010-09-20 08:28:50 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int handle_file_collision(struct merge_options *opt,
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
const char *collide_path,
|
|
|
|
const char *prev_path1,
|
|
|
|
const char *prev_path2,
|
|
|
|
const char *branch1, const char *branch2,
|
2019-04-05 15:00:22 +00:00
|
|
|
struct diff_filespec *a,
|
|
|
|
struct diff_filespec *b)
|
2011-08-12 05:20:22 +00:00
|
|
|
{
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
struct merge_file_info mfi;
|
2019-04-05 15:00:22 +00:00
|
|
|
struct diff_filespec null;
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
char *alt_path = NULL;
|
|
|
|
const char *update_path = collide_path;
|
2011-08-12 05:20:22 +00:00
|
|
|
|
2018-11-08 04:40:26 +00:00
|
|
|
/*
|
|
|
|
* It's easiest to get the correct things into stage 2 and 3, and
|
|
|
|
* to make sure that the content merge puts HEAD before the other
|
2019-04-05 15:00:13 +00:00
|
|
|
* branch if we just ensure that branch1 == opt->branch1. So, simply
|
2018-11-08 04:40:26 +00:00
|
|
|
* flip arguments around if we don't have that.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
if (branch1 != opt->branch1) {
|
|
|
|
return handle_file_collision(opt, collide_path,
|
2018-11-08 04:40:26 +00:00
|
|
|
prev_path2, prev_path1,
|
|
|
|
branch2, branch1,
|
2019-04-05 15:00:22 +00:00
|
|
|
b, a);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
2011-08-12 05:20:22 +00:00
|
|
|
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
/* Remove rename sources if rename/add or rename/rename(2to1) */
|
|
|
|
if (prev_path1)
|
2019-04-05 15:00:13 +00:00
|
|
|
remove_file(opt, 1, prev_path1,
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->call_depth || would_lose_untracked(opt, prev_path1));
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
if (prev_path2)
|
2019-04-05 15:00:13 +00:00
|
|
|
remove_file(opt, 1, prev_path2,
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->call_depth || would_lose_untracked(opt, prev_path2));
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Remove the collision path, if it wouldn't cause dirty contents
|
|
|
|
* or an untracked file to get lost. We'll either overwrite with
|
|
|
|
* merged contents, or just write out to differently named files.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
if (was_dirty(opt, collide_path)) {
|
|
|
|
output(opt, 1, _("Refusing to lose dirty file at %s"),
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
collide_path);
|
2019-04-05 15:00:13 +00:00
|
|
|
update_path = alt_path = unique_path(opt, collide_path, "merged");
|
|
|
|
} else if (would_lose_untracked(opt, collide_path)) {
|
2018-04-19 17:58:13 +00:00
|
|
|
/*
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
* Only way we get here is if both renames were from
|
|
|
|
* a directory rename AND user had an untracked file
|
|
|
|
* at the location where both files end up after the
|
|
|
|
* two directory renames. See testcase 10d of t6043.
|
2018-04-19 17:58:13 +00:00
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("Refusing to lose untracked file at "
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
"%s, even though it's in the way."),
|
|
|
|
collide_path);
|
2019-04-05 15:00:13 +00:00
|
|
|
update_path = alt_path = unique_path(opt, collide_path, "merged");
|
2011-08-12 05:20:22 +00:00
|
|
|
} else {
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
/*
|
|
|
|
* FIXME: It's possible that the two files are identical
|
|
|
|
* and that the current working copy happens to match, in
|
|
|
|
* which case we are unnecessarily touching the working
|
|
|
|
* tree file. It's not a likely enough scenario that I
|
|
|
|
* want to code up the checks for it and a better fix is
|
|
|
|
* available if we restructure how unpack_trees() and
|
|
|
|
* merge-recursive interoperate anyway, so punting for
|
|
|
|
* now...
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
remove_file(opt, 0, collide_path, 0);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
2011-08-12 05:20:22 +00:00
|
|
|
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
/* Store things in diff_filespecs for functions that need it */
|
2019-04-05 15:00:22 +00:00
|
|
|
null.path = (char *)collide_path;
|
2021-04-26 01:02:56 +00:00
|
|
|
oidcpy(&null.oid, null_oid());
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
null.mode = 0;
|
2019-04-05 15:00:22 +00:00
|
|
|
|
|
|
|
if (merge_mode_and_contents(opt, &null, a, b, collide_path,
|
2019-08-17 18:41:41 +00:00
|
|
|
branch1, branch2, opt->priv->call_depth * 2, &mfi))
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
return -1;
|
|
|
|
mfi.clean &= !alt_path;
|
2019-04-05 15:00:22 +00:00
|
|
|
if (update_file(opt, mfi.clean, &mfi.blob, update_path))
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
return -1;
|
2019-08-17 18:41:41 +00:00
|
|
|
if (!mfi.clean && !opt->priv->call_depth &&
|
2019-04-05 15:00:22 +00:00
|
|
|
update_stages(opt, collide_path, NULL, a, b))
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
return -1;
|
|
|
|
free(alt_path);
|
|
|
|
/*
|
|
|
|
* FIXME: If both a & b both started with conflicts (only possible
|
|
|
|
* if they came from a rename/rename(2to1)), but had IDENTICAL
|
|
|
|
* contents including those conflicts, then in the next line we claim
|
|
|
|
* it was clean. If someone cares about this case, we should have the
|
|
|
|
* caller notify us if we started with conflicts.
|
|
|
|
*/
|
|
|
|
return mfi.clean;
|
|
|
|
}
|
2018-11-08 04:40:26 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int handle_rename_add(struct merge_options *opt,
|
2018-11-08 04:40:26 +00:00
|
|
|
struct rename_conflict_info *ci)
|
|
|
|
{
|
|
|
|
/* a was renamed to c, and a separate c was added. */
|
2019-04-05 15:00:18 +00:00
|
|
|
struct diff_filespec *a = ci->ren1->pair->one;
|
|
|
|
struct diff_filespec *c = ci->ren1->pair->two;
|
2018-11-08 04:40:26 +00:00
|
|
|
char *path = c->path;
|
|
|
|
char *prev_path_desc;
|
|
|
|
struct merge_file_info mfi;
|
|
|
|
|
2019-04-05 15:00:20 +00:00
|
|
|
const char *rename_branch = ci->ren1->branch;
|
|
|
|
const char *add_branch = (opt->branch1 == rename_branch ?
|
|
|
|
opt->branch2 : opt->branch1);
|
|
|
|
int other_stage = (ci->ren1->branch == opt->branch1 ? 3 : 2);
|
2018-11-08 04:40:26 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (rename/add): "
|
2018-11-08 04:40:26 +00:00
|
|
|
"Rename %s->%s in %s. Added %s in %s"),
|
2019-04-05 15:00:20 +00:00
|
|
|
a->path, c->path, rename_branch,
|
|
|
|
c->path, add_branch);
|
2018-11-08 04:40:26 +00:00
|
|
|
|
|
|
|
prev_path_desc = xstrfmt("version of %s from %s", path, a->path);
|
merge-recursive: restore accidentally dropped setting of path
In commit 8daec1df03de ("merge-recursive: switch from (oid,mode) pairs
to a diff_filespec", 2019-04-05), we actually switched from
(oid,mode,path) triplets to a diff_filespec -- but most callsites in the
patch only needed to worry about oid and mode so the commit message
focused on that. The oversight in the commit message apparently spilled
over to the code as well; one of the dozen or so callsites accidentally
dropped the setting of the path in the conversion. Restore the path
setting in that location.
Also, this pointed out that our testsuite was lacking a good rename/add
test, at least one that involved the need for merge content with the
rename. Add such a test, and since rename/add vs. add/rename could
possibly be important, redo the merge the opposite direction to make
sure we don't have issues with the direction of the merge. These
testcases failed before restoring the setting of path, but with the
paths appropriately set the testcases both pass.
Reported-by: Ben Humphreys <behumphreys@atlassian.com>
Based-on-patch-by: SZEDER Gábor <szeder.dev@gmail.com>
Tested-by: Ben Humphreys <behumphreys@atlassian.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-06-04 20:27:50 +00:00
|
|
|
ci->ren1->src_entry->stages[other_stage].path = a->path;
|
2019-04-05 15:00:22 +00:00
|
|
|
if (merge_mode_and_contents(opt, a, c,
|
|
|
|
&ci->ren1->src_entry->stages[other_stage],
|
2019-04-05 15:00:19 +00:00
|
|
|
prev_path_desc,
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->branch1, opt->branch2,
|
2019-08-17 18:41:41 +00:00
|
|
|
1 + opt->priv->call_depth * 2, &mfi))
|
2018-11-08 04:40:26 +00:00
|
|
|
return -1;
|
|
|
|
free(prev_path_desc);
|
|
|
|
|
2019-04-05 15:00:22 +00:00
|
|
|
ci->ren1->dst_entry->stages[other_stage].path = mfi.blob.path = c->path;
|
2019-04-05 15:00:13 +00:00
|
|
|
return handle_file_collision(opt,
|
2018-11-08 04:40:26 +00:00
|
|
|
c->path, a->path, NULL,
|
2019-04-05 15:00:20 +00:00
|
|
|
rename_branch, add_branch,
|
2019-04-05 15:00:22 +00:00
|
|
|
&mfi.blob,
|
|
|
|
&ci->ren1->dst_entry->stages[other_stage]);
|
2018-11-08 04:40:26 +00:00
|
|
|
}
|
merge-recursive: new function for better colliding conflict resolutions
There are three conflict types that represent two (possibly entirely
unrelated) files colliding at the same location:
* add/add
* rename/add
* rename/rename(2to1)
These three conflict types already share more similarity than might be
immediately apparent from their description: (1) the handling of the
rename variants already involves removing any entries from the index
corresponding to the original file names[*], thus only leaving entries
in the index for the colliding path; (2) likewise, any trace of the
original file name in the working tree is also removed. So, in all
three cases we're left with how to represent two colliding files in both
the index and the working copy.
[*] Technically, this isn't quite true because rename/rename(2to1)
conflicts in the recursive (o->call_depth > 0) case do an "unrename"
since about seven years ago. But even in that case, Junio felt
compelled to explain that my decision to "unrename" wasn't necessarily
the only or right answer -- search for "Comment from Junio" in t6036 for
details.
My initial motivation for looking at these three conflict types was that
if the handling of these three conflict types is the same, at least in
the limited set of cases where a renamed file is unmodified on the side
of history where the file is not renamed, then a significant performance
improvement for rename detection during merges is possible. However,
while that served as motivation to look at these three types of
conflicts, the actual goal of this new function is to try to improve the
handling for all three cases, not to merely make them the same as each
other in that special circumstance.
=== Handling the working tree ===
The previous behavior for these conflict types in regards to the
working tree (assuming the file collision occurs at 'foo') was:
* add/add does a two-way merge of the two files and records it as 'foo'.
* rename/rename(2to1) records the two different files into two new
uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'
from the working tree.
* rename/add records the two different files into two different
locations, recording the add at foo~$SIDE and, oddly, recording
the rename at foo (why is the rename more important than the add?)
So, the question for what to write to the working tree boils down to
whether the two colliding files should be two-way merged and recorded in
place, or recorded into separate files. As per discussion on the git
mailing lit, two-way merging was deemed to always be preferred, as that
makes these cases all more like content conflicts that users can handle
from within their favorite editor, IDE, or merge tool. Note that since
renames already involve a content merge, rename/add and
rename/rename(2to1) conflicts could result in nested conflict markers.
=== Handling of the index ===
For a typical rename, unpack_trees() would set up the index in the
following fashion:
old_path new_path
stage1: 5ca1ab1e 00000000
stage2: f005ba11 00000000
stage3: 00000000 b0a710ad
And merge-recursive would rewrite this to
new_path
stage1: 5ca1ab1e
stage2: f005ba11
stage3: b0a710ad
Removing old_path from the index means the user won't have to `git rm
old_path` manually every time a renamed path has a content conflict.
It also means they can use `git checkout [--ours|--theirs|--conflict|-m]
new_path`, `git diff [--ours|--theirs]` and various other commands that
would be difficult otherwise.
This strategy becomes a problem when we have a rename/add or
rename/rename(2to1) conflict, however, because then we have only three
slots to store blob sha1s and we need either four or six. Previously,
this was handled by continuing to delete old_path from the index, and
just outright ignoring any blob shas from old_path. That had the
downside of deleting any trace of changes made to old_path on the other
side of history. This function instead does a three-way content merge of
the renamed file, and stores the blob sha1 for that at either stage2 or
stage3 for new_path (depending on which side the rename came from). That
has the advantage of bringing information about changes on both sides and
still allows for easy resolution (no need to git rm old_path, etc.), but
does have the downside that if the content merge had conflict markers,
then what we store in the index is the sha1 of a blob with conflict
markers. While that is a downside, it seems less problematic than the
downsides of any obvious alternatives, and certainly makes more sense
than the previous handling. Further, it has a precedent in that when we
do recursive merges, we may accept a file with conflict markers as the
resolution for the merge of the merge-bases, which will then show up in
the index of the outer merge at stage 1 if a conflict exists at the outer
level.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-08 04:40:25 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static char *find_path_for_conflict(struct merge_options *opt,
|
2018-11-08 04:40:31 +00:00
|
|
|
const char *path,
|
|
|
|
const char *branch1,
|
|
|
|
const char *branch2)
|
|
|
|
{
|
|
|
|
char *new_path = NULL;
|
2019-08-17 18:41:41 +00:00
|
|
|
if (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0)) {
|
2019-04-05 15:00:13 +00:00
|
|
|
new_path = unique_path(opt, path, branch1);
|
|
|
|
output(opt, 1, _("%s is a directory in %s adding "
|
2018-11-08 04:40:31 +00:00
|
|
|
"as %s instead"),
|
|
|
|
path, branch2, new_path);
|
2019-04-05 15:00:13 +00:00
|
|
|
} else if (would_lose_untracked(opt, path)) {
|
|
|
|
new_path = unique_path(opt, path, branch1);
|
|
|
|
output(opt, 1, _("Refusing to lose untracked file"
|
2018-11-08 04:40:31 +00:00
|
|
|
" at %s; adding as %s instead"),
|
|
|
|
path, new_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new_path;
|
2011-08-12 05:20:22 +00:00
|
|
|
}
|
|
|
|
|
merge-recursive: silence -Wxor-used-as-pow warning
The merge-recursive code uses stage number constants like this:
add = &ci->ren1->dst_entry->stages[2 ^ 1];
...
add = &ci->ren2->dst_entry->stages[3 ^ 1];
The xor has the effect of flipping the "1" bit, so that "2 ^ 1" becomes
"3" and "3 ^ 1" becomes "2", which correspond to the "ours" and "theirs"
stages respectively.
Unfortunately, clang-10 and up issue a warning for this code:
merge-recursive.c:1759:40: error: result of '2 ^ 1' is 3; did you mean '1 << 1' (2)? [-Werror,-Wxor-used-as-pow]
add = &ci->ren1->dst_entry->stages[2 ^ 1];
~~^~~
1 << 1
merge-recursive.c:1759:40: note: replace expression with '0x2 ^ 1' to silence this warning
We could silence it by using 0x2, as the compiler mentions. Or by just
using the constants "2" and "3" directly. But after digging into it, I
do think this bit-flip is telling us something. If we just wrote:
add = &ci->ren2->dst_entry->stages[2];
for the second one, you might think that "ren2" and "2" correspond. But
they don't. The logic is: ren2 is theirs, which is stage 3, but we
are interested in the opposite side's stage, so flip it to 2.
So let's keep the bit-flipping, but let's also put it behind a named
function, which will make its purpose a bit clearer. This also has the
side effect of suppressing the warning (and an optimizing compiler
should be able to easily turn it into a constant as before).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-25 05:37:23 +00:00
|
|
|
/*
|
2020-01-25 23:57:45 +00:00
|
|
|
* Toggle the stage number between "ours" and "theirs" (2 and 3).
|
merge-recursive: silence -Wxor-used-as-pow warning
The merge-recursive code uses stage number constants like this:
add = &ci->ren1->dst_entry->stages[2 ^ 1];
...
add = &ci->ren2->dst_entry->stages[3 ^ 1];
The xor has the effect of flipping the "1" bit, so that "2 ^ 1" becomes
"3" and "3 ^ 1" becomes "2", which correspond to the "ours" and "theirs"
stages respectively.
Unfortunately, clang-10 and up issue a warning for this code:
merge-recursive.c:1759:40: error: result of '2 ^ 1' is 3; did you mean '1 << 1' (2)? [-Werror,-Wxor-used-as-pow]
add = &ci->ren1->dst_entry->stages[2 ^ 1];
~~^~~
1 << 1
merge-recursive.c:1759:40: note: replace expression with '0x2 ^ 1' to silence this warning
We could silence it by using 0x2, as the compiler mentions. Or by just
using the constants "2" and "3" directly. But after digging into it, I
do think this bit-flip is telling us something. If we just wrote:
add = &ci->ren2->dst_entry->stages[2];
for the second one, you might think that "ren2" and "2" correspond. But
they don't. The logic is: ren2 is theirs, which is stage 3, but we
are interested in the opposite side's stage, so flip it to 2.
So let's keep the bit-flipping, but let's also put it behind a named
function, which will make its purpose a bit clearer. This also has the
side effect of suppressing the warning (and an optimizing compiler
should be able to easily turn it into a constant as before).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-25 05:37:23 +00:00
|
|
|
*/
|
|
|
|
static inline int flip_stage(int stage)
|
|
|
|
{
|
2020-01-25 23:57:45 +00:00
|
|
|
return (2 + 3) - stage;
|
merge-recursive: silence -Wxor-used-as-pow warning
The merge-recursive code uses stage number constants like this:
add = &ci->ren1->dst_entry->stages[2 ^ 1];
...
add = &ci->ren2->dst_entry->stages[3 ^ 1];
The xor has the effect of flipping the "1" bit, so that "2 ^ 1" becomes
"3" and "3 ^ 1" becomes "2", which correspond to the "ours" and "theirs"
stages respectively.
Unfortunately, clang-10 and up issue a warning for this code:
merge-recursive.c:1759:40: error: result of '2 ^ 1' is 3; did you mean '1 << 1' (2)? [-Werror,-Wxor-used-as-pow]
add = &ci->ren1->dst_entry->stages[2 ^ 1];
~~^~~
1 << 1
merge-recursive.c:1759:40: note: replace expression with '0x2 ^ 1' to silence this warning
We could silence it by using 0x2, as the compiler mentions. Or by just
using the constants "2" and "3" directly. But after digging into it, I
do think this bit-flip is telling us something. If we just wrote:
add = &ci->ren2->dst_entry->stages[2];
for the second one, you might think that "ren2" and "2" correspond. But
they don't. The logic is: ren2 is theirs, which is stage 3, but we
are interested in the opposite side's stage, so flip it to 2.
So let's keep the bit-flipping, but let's also put it behind a named
function, which will make its purpose a bit clearer. This also has the
side effect of suppressing the warning (and an optimizing compiler
should be able to easily turn it into a constant as before).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-25 05:37:23 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int handle_rename_rename_1to2(struct merge_options *opt,
|
2018-06-10 04:16:15 +00:00
|
|
|
struct rename_conflict_info *ci)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2010-09-20 08:28:48 +00:00
|
|
|
/* One file was renamed in both branches, but to different names. */
|
2018-11-08 04:40:29 +00:00
|
|
|
struct merge_file_info mfi;
|
|
|
|
struct diff_filespec *add;
|
2019-04-05 15:00:18 +00:00
|
|
|
struct diff_filespec *o = ci->ren1->pair->one;
|
|
|
|
struct diff_filespec *a = ci->ren1->pair->two;
|
|
|
|
struct diff_filespec *b = ci->ren2->pair->two;
|
2018-11-08 04:40:29 +00:00
|
|
|
char *path_desc;
|
2011-08-12 05:20:08 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (rename/rename): "
|
2011-08-12 05:20:08 +00:00
|
|
|
"Rename \"%s\"->\"%s\" in branch \"%s\" "
|
2012-07-25 14:53:13 +00:00
|
|
|
"rename \"%s\"->\"%s\" in \"%s\"%s"),
|
2019-04-05 15:00:20 +00:00
|
|
|
o->path, a->path, ci->ren1->branch,
|
|
|
|
o->path, b->path, ci->ren2->branch,
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->call_depth ? _(" (left unresolved)") : "");
|
2016-07-26 16:06:21 +00:00
|
|
|
|
2018-11-08 04:40:29 +00:00
|
|
|
path_desc = xstrfmt("%s and %s, both renamed from %s",
|
2019-04-05 15:00:14 +00:00
|
|
|
a->path, b->path, o->path);
|
|
|
|
if (merge_mode_and_contents(opt, o, a, b, path_desc,
|
2019-04-05 15:00:20 +00:00
|
|
|
ci->ren1->branch, ci->ren2->branch,
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->call_depth * 2, &mfi))
|
2018-11-08 04:40:29 +00:00
|
|
|
return -1;
|
|
|
|
free(path_desc);
|
|
|
|
|
2020-02-27 00:05:05 +00:00
|
|
|
if (opt->priv->call_depth)
|
|
|
|
remove_file_from_index(opt->repo->index, o->path);
|
2010-09-20 08:29:00 +00:00
|
|
|
|
2020-02-27 00:05:05 +00:00
|
|
|
/*
|
|
|
|
* For each destination path, we need to see if there is a
|
|
|
|
* rename/add collision. If not, we can write the file out
|
|
|
|
* to the specified location.
|
|
|
|
*/
|
|
|
|
add = &ci->ren1->dst_entry->stages[flip_stage(2)];
|
|
|
|
if (is_valid(add)) {
|
|
|
|
add->path = mfi.blob.path = a->path;
|
|
|
|
if (handle_file_collision(opt, a->path,
|
|
|
|
NULL, NULL,
|
|
|
|
ci->ren1->branch,
|
|
|
|
ci->ren2->branch,
|
|
|
|
&mfi.blob, add) < 0)
|
|
|
|
return -1;
|
2018-11-08 04:40:29 +00:00
|
|
|
} else {
|
2020-02-27 00:05:05 +00:00
|
|
|
char *new_path = find_path_for_conflict(opt, a->path,
|
|
|
|
ci->ren1->branch,
|
|
|
|
ci->ren2->branch);
|
|
|
|
if (update_file(opt, 0, &mfi.blob,
|
|
|
|
new_path ? new_path : a->path))
|
|
|
|
return -1;
|
|
|
|
free(new_path);
|
|
|
|
if (!opt->priv->call_depth &&
|
|
|
|
update_stages(opt, a->path, NULL, a, NULL))
|
|
|
|
return -1;
|
|
|
|
}
|
2018-11-08 04:40:29 +00:00
|
|
|
|
2020-05-13 23:56:32 +00:00
|
|
|
if (!mfi.clean && mfi.blob.mode == a->mode &&
|
|
|
|
oideq(&mfi.blob.oid, &a->oid)) {
|
|
|
|
/*
|
|
|
|
* Getting here means we were attempting to merge a binary
|
|
|
|
* blob. Since we can't merge binaries, the merge algorithm
|
|
|
|
* just takes one side. But we don't want to copy the
|
|
|
|
* contents of one side to both paths; we'd rather use the
|
|
|
|
* original content at the given path for each path.
|
|
|
|
*/
|
|
|
|
oidcpy(&mfi.blob.oid, &b->oid);
|
|
|
|
mfi.blob.mode = b->mode;
|
|
|
|
}
|
2020-02-27 00:05:05 +00:00
|
|
|
add = &ci->ren2->dst_entry->stages[flip_stage(3)];
|
|
|
|
if (is_valid(add)) {
|
|
|
|
add->path = mfi.blob.path = b->path;
|
|
|
|
if (handle_file_collision(opt, b->path,
|
|
|
|
NULL, NULL,
|
|
|
|
ci->ren1->branch,
|
|
|
|
ci->ren2->branch,
|
|
|
|
add, &mfi.blob) < 0)
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
char *new_path = find_path_for_conflict(opt, b->path,
|
|
|
|
ci->ren2->branch,
|
|
|
|
ci->ren1->branch);
|
|
|
|
if (update_file(opt, 0, &mfi.blob,
|
|
|
|
new_path ? new_path : b->path))
|
|
|
|
return -1;
|
|
|
|
free(new_path);
|
|
|
|
if (!opt->priv->call_depth &&
|
|
|
|
update_stages(opt, b->path, NULL, NULL, b))
|
|
|
|
return -1;
|
2018-11-08 04:40:29 +00:00
|
|
|
}
|
2016-07-26 16:06:21 +00:00
|
|
|
|
|
|
|
return 0;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int handle_rename_rename_2to1(struct merge_options *opt,
|
2018-06-10 04:16:15 +00:00
|
|
|
struct rename_conflict_info *ci)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2011-08-12 05:20:15 +00:00
|
|
|
/* Two files, a & b, were renamed to the same thing, c. */
|
2019-04-05 15:00:18 +00:00
|
|
|
struct diff_filespec *a = ci->ren1->pair->one;
|
|
|
|
struct diff_filespec *b = ci->ren2->pair->one;
|
|
|
|
struct diff_filespec *c1 = ci->ren1->pair->two;
|
|
|
|
struct diff_filespec *c2 = ci->ren2->pair->two;
|
2011-08-12 05:20:15 +00:00
|
|
|
char *path = c1->path; /* == c2->path */
|
2018-04-19 17:58:22 +00:00
|
|
|
char *path_side_1_desc;
|
|
|
|
char *path_side_2_desc;
|
merge-recursive: Consider modifications in rename/rename(2to1) conflicts
Our previous conflict resolution for renaming two different files to the
same name ignored the fact that each of those files may have modifications
from both sides of history to consider. We need to do a three-way merge
for each of those files, and then handle the conflict of both sets of
merged contents trying to be recorded with the same name.
It is important to note that this changes our strategy in the recursive
case. After doing a three-way content merge of each of the files
involved, we still are faced with the fact that we are trying to put both
of the results (including conflict markers) into the same path. We could
do another two-way merge, but I think that becomes confusing. Also,
taking a hint from the modify/delete and rename/delete cases we handled
earlier, a more useful "common ground" would be to keep the three-way
content merge but record it with the original filename. The renames can
still be detected, we just allow it to be done in the o->call_depth=0
case. This seems to result in simpler & easier to understand merge
conflicts as well, as evidenced by some of the changes needed in our
testsuite in t6036. (However, it should be noted that this change will
cause problems those renames also occur along with a file being added
whose name matches the source of the rename. Since git currently cannot
detect rename/add-source situations, though, this codepath is not
currently used for those cases anyway.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 05:20:18 +00:00
|
|
|
struct merge_file_info mfi_c1;
|
|
|
|
struct merge_file_info mfi_c2;
|
2019-04-05 15:00:22 +00:00
|
|
|
int ostage1, ostage2;
|
2011-08-12 05:20:15 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (rename/rename): "
|
2011-08-12 05:20:15 +00:00
|
|
|
"Rename %s->%s in %s. "
|
2012-07-25 14:53:13 +00:00
|
|
|
"Rename %s->%s in %s"),
|
2019-04-05 15:00:20 +00:00
|
|
|
a->path, c1->path, ci->ren1->branch,
|
|
|
|
b->path, c2->path, ci->ren2->branch);
|
2011-08-12 05:20:15 +00:00
|
|
|
|
2018-10-16 20:19:47 +00:00
|
|
|
path_side_1_desc = xstrfmt("version of %s from %s", path, a->path);
|
|
|
|
path_side_2_desc = xstrfmt("version of %s from %s", path, b->path);
|
2019-04-05 15:00:22 +00:00
|
|
|
ostage1 = ci->ren1->branch == opt->branch1 ? 3 : 2;
|
merge-recursive: silence -Wxor-used-as-pow warning
The merge-recursive code uses stage number constants like this:
add = &ci->ren1->dst_entry->stages[2 ^ 1];
...
add = &ci->ren2->dst_entry->stages[3 ^ 1];
The xor has the effect of flipping the "1" bit, so that "2 ^ 1" becomes
"3" and "3 ^ 1" becomes "2", which correspond to the "ours" and "theirs"
stages respectively.
Unfortunately, clang-10 and up issue a warning for this code:
merge-recursive.c:1759:40: error: result of '2 ^ 1' is 3; did you mean '1 << 1' (2)? [-Werror,-Wxor-used-as-pow]
add = &ci->ren1->dst_entry->stages[2 ^ 1];
~~^~~
1 << 1
merge-recursive.c:1759:40: note: replace expression with '0x2 ^ 1' to silence this warning
We could silence it by using 0x2, as the compiler mentions. Or by just
using the constants "2" and "3" directly. But after digging into it, I
do think this bit-flip is telling us something. If we just wrote:
add = &ci->ren2->dst_entry->stages[2];
for the second one, you might think that "ren2" and "2" correspond. But
they don't. The logic is: ren2 is theirs, which is stage 3, but we
are interested in the opposite side's stage, so flip it to 2.
So let's keep the bit-flipping, but let's also put it behind a named
function, which will make its purpose a bit clearer. This also has the
side effect of suppressing the warning (and an optimizing compiler
should be able to easily turn it into a constant as before).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-25 05:37:23 +00:00
|
|
|
ostage2 = flip_stage(ostage1);
|
2019-04-05 15:00:22 +00:00
|
|
|
ci->ren1->src_entry->stages[ostage1].path = a->path;
|
|
|
|
ci->ren2->src_entry->stages[ostage2].path = b->path;
|
|
|
|
if (merge_mode_and_contents(opt, a, c1,
|
|
|
|
&ci->ren1->src_entry->stages[ostage1],
|
|
|
|
path_side_1_desc,
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->branch1, opt->branch2,
|
2019-08-17 18:41:41 +00:00
|
|
|
1 + opt->priv->call_depth * 2, &mfi_c1) ||
|
2019-04-05 15:00:22 +00:00
|
|
|
merge_mode_and_contents(opt, b,
|
|
|
|
&ci->ren2->src_entry->stages[ostage2],
|
|
|
|
c2, path_side_2_desc,
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->branch1, opt->branch2,
|
2019-08-17 18:41:41 +00:00
|
|
|
1 + opt->priv->call_depth * 2, &mfi_c2))
|
2016-07-26 16:06:21 +00:00
|
|
|
return -1;
|
2018-04-19 17:58:22 +00:00
|
|
|
free(path_side_1_desc);
|
|
|
|
free(path_side_2_desc);
|
2019-04-05 15:00:22 +00:00
|
|
|
mfi_c1.blob.path = path;
|
|
|
|
mfi_c2.blob.path = path;
|
merge-recursive: Consider modifications in rename/rename(2to1) conflicts
Our previous conflict resolution for renaming two different files to the
same name ignored the fact that each of those files may have modifications
from both sides of history to consider. We need to do a three-way merge
for each of those files, and then handle the conflict of both sets of
merged contents trying to be recorded with the same name.
It is important to note that this changes our strategy in the recursive
case. After doing a three-way content merge of each of the files
involved, we still are faced with the fact that we are trying to put both
of the results (including conflict markers) into the same path. We could
do another two-way merge, but I think that becomes confusing. Also,
taking a hint from the modify/delete and rename/delete cases we handled
earlier, a more useful "common ground" would be to keep the three-way
content merge but record it with the original filename. The renames can
still be detected, we just allow it to be done in the o->call_depth=0
case. This seems to result in simpler & easier to understand merge
conflicts as well, as evidenced by some of the changes needed in our
testsuite in t6036. (However, it should be noted that this change will
cause problems those renames also occur along with a file being added
whose name matches the source of the rename. Since git currently cannot
detect rename/add-source situations, though, this codepath is not
currently used for those cases anyway.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 05:20:18 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
return handle_file_collision(opt, path, a->path, b->path,
|
2019-04-05 15:00:20 +00:00
|
|
|
ci->ren1->branch, ci->ren2->branch,
|
2019-04-05 15:00:22 +00:00
|
|
|
&mfi_c1.blob, &mfi_c2.blob);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2018-04-19 17:57:59 +00:00
|
|
|
/*
|
2018-04-19 17:58:03 +00:00
|
|
|
* Get the diff_filepairs changed between o_tree and tree.
|
2018-04-19 17:57:59 +00:00
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,
|
2018-04-19 17:58:03 +00:00
|
|
|
struct tree *o_tree,
|
|
|
|
struct tree *tree)
|
2018-04-19 17:57:59 +00:00
|
|
|
{
|
2018-04-19 17:58:03 +00:00
|
|
|
struct diff_queue_struct *ret;
|
2018-04-19 17:57:59 +00:00
|
|
|
struct diff_options opts;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
repo_diff_setup(opt->repo, &opts);
|
2018-04-19 17:57:59 +00:00
|
|
|
opts.flags.recursive = 1;
|
|
|
|
opts.flags.rename_empty = 0;
|
2019-04-05 15:00:13 +00:00
|
|
|
opts.detect_rename = merge_detect_rename(opt);
|
2018-05-02 16:01:14 +00:00
|
|
|
/*
|
|
|
|
* We do not have logic to handle the detection of copies. In
|
|
|
|
* fact, it may not even make sense to add such logic: would we
|
|
|
|
* really want a change to a base file to be propagated through
|
|
|
|
* multiple other files by a merge?
|
|
|
|
*/
|
|
|
|
if (opts.detect_rename > DIFF_DETECT_RENAME)
|
|
|
|
opts.detect_rename = DIFF_DETECT_RENAME;
|
rename: bump limit defaults yet again
These were last bumped in commit 92c57e5c1d29 (bump rename limit
defaults (again), 2011-02-19), and were bumped both because processors
had gotten faster, and because people were getting ugly merges that
caused problems and reporting it to the mailing list (suggesting that
folks were willing to spend more time waiting).
Since that time:
* Linus has continued recommending kernel folks to set
diff.renameLimit=0 (maps to 32767, currently)
* Folks with repositories with lots of renames were happy to set
merge.renameLimit above 32767, once the code supported that, to
get correct cherry-picks
* Processors have gotten faster
* It has been discovered that the timing methodology used last time
probably used too large example files.
The last point is probably worth explaining a bit more:
* The "average" file size used appears to have been average blob size
in the linux kernel history at the time (probably v2.6.25 or
something close to it).
* Since bigger files are modified more frequently, such a computation
weights towards larger files.
* Larger files may be more likely to be modified over time, but are
not more likely to be renamed -- the mean and median blob size
within a tree are a bit higher than the mean and median of blob
sizes in the history leading up to that version for the linux
kernel.
* The mean blob size in v2.6.25 was half the average blob size in
history leading to that point
* The median blob size in v2.6.25 was about 40% of the mean blob size
in v2.6.25.
* Since the mean blob size is more than double the median blob size,
any file as big as the mean will not be compared to any files of
median size or less (because they'd be more than 50% dissimilar).
* Since it is the number of files compared that provides the O(n^2)
behavior, median-sized files should matter more than mean-sized
ones.
The combined effect of the above is that the file size used in past
calculations was likely about 5x too large. Combine that with a CPU
performance improvement of ~30%, and we can increase the limits by
a factor of sqrt(5/(1-.3)) = 2.67, while keeping the original stated
time limits.
Keeping the same approximate time limit probably makes sense for
diff.renameLimit (there is no progress feedback in e.g. git log -p),
but the experience above suggests merge.renameLimit could be extended
significantly. In fact, it probably would make sense to have an
unlimited default setting for merge.renameLimit, but that would
likely need to be coupled with changes to how progress is displayed.
(See https://lore.kernel.org/git/YOx+Ok%2FEYvLqRMzJ@coredump.intra.peff.net/
for details in that area.) For now, let's just bump the approximate
time limit from 10s to 1m.
(Note: We do not want to use actual time limits, because getting results
that depend on how loaded your system is that day feels bad, and because
we don't discover that we won't get all the renames until after we've
put in a lot of work rather than just upfront telling the user there are
too many files involved.)
Using the original time limit of 2s for diff.renameLimit, and bumping
merge.renameLimit from 10s to 60s, I found the following timings using
the simple script at the end of this commit message (on an AWS c5.xlarge
which reports as "Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz"):
N Timing
1300 1.995s
7100 59.973s
So let's round down to nice even numbers and bump the limits from
400->1000, and from 1000->7000.
Here is the measure_rename_perf script (adapted from
https://lore.kernel.org/git/20080211113516.GB6344@coredump.intra.peff.net/
in particular to avoid triggering the linear handling from
basename-guided rename detection):
#!/bin/bash
n=$1; shift
rm -rf repo
mkdir repo && cd repo
git init -q -b main
mkdata() {
mkdir $1
for i in `seq 1 $2`; do
(sed "s/^/$i /" <../sample
echo tag: $1
) >$1/$i
done
}
mkdata initial $n
git add .
git commit -q -m initial
mkdata new $n
git add .
cd new
for i in *; do git mv $i $i.renamed; done
cd ..
git rm -q -rf initial
git commit -q -m new
time git diff-tree -M -l0 --summary HEAD^ HEAD
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-07-15 00:45:24 +00:00
|
|
|
opts.rename_limit = (opt->rename_limit >= 0) ? opt->rename_limit : 7000;
|
2019-04-05 15:00:13 +00:00
|
|
|
opts.rename_score = opt->rename_score;
|
|
|
|
opts.show_rename_progress = opt->show_rename_progress;
|
2018-04-19 17:57:59 +00:00
|
|
|
opts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
|
|
|
diff_setup_done(&opts);
|
|
|
|
diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts);
|
|
|
|
diffcore_std(&opts);
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opts.needed_rename_limit > opt->priv->needed_rename_limit)
|
|
|
|
opt->priv->needed_rename_limit = opts.needed_rename_limit;
|
2018-04-19 17:58:03 +00:00
|
|
|
|
|
|
|
ret = xmalloc(sizeof(*ret));
|
|
|
|
*ret = diff_queued_diff;
|
|
|
|
|
|
|
|
opts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
|
|
|
diff_queued_diff.nr = 0;
|
|
|
|
diff_queued_diff.queue = NULL;
|
|
|
|
diff_flush(&opts);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-06-27 09:28:52 +00:00
|
|
|
static int tree_has_path(struct repository *r, struct tree *tree,
|
|
|
|
const char *path)
|
2018-04-19 17:58:06 +00:00
|
|
|
{
|
|
|
|
struct object_id hashy;
|
2019-04-05 15:00:12 +00:00
|
|
|
unsigned short mode_o;
|
2018-04-19 17:58:06 +00:00
|
|
|
|
2019-06-27 09:28:52 +00:00
|
|
|
return !get_tree_entry(r,
|
2019-06-27 09:28:49 +00:00
|
|
|
&tree->object.oid, path,
|
2018-04-19 17:58:06 +00:00
|
|
|
&hashy, &mode_o);
|
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:07 +00:00
|
|
|
/*
|
|
|
|
* Return a new string that replaces the beginning portion (which matches
|
|
|
|
* entry->dir), with entry->new_dir. In perl-speak:
|
|
|
|
* new_path_name = (old_path =~ s/entry->dir/entry->new_dir/);
|
|
|
|
* NOTE:
|
|
|
|
* Caller must ensure that old_path starts with entry->dir + '/'.
|
|
|
|
*/
|
|
|
|
static char *apply_dir_rename(struct dir_rename_entry *entry,
|
|
|
|
const char *old_path)
|
|
|
|
{
|
|
|
|
struct strbuf new_path = STRBUF_INIT;
|
|
|
|
int oldlen, newlen;
|
|
|
|
|
|
|
|
if (entry->non_unique_new_dir)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
oldlen = strlen(entry->dir);
|
2019-10-22 21:22:50 +00:00
|
|
|
if (entry->new_dir.len == 0)
|
|
|
|
/*
|
|
|
|
* If someone renamed/merged a subdirectory into the root
|
|
|
|
* directory (e.g. 'some/subdir' -> ''), then we want to
|
|
|
|
* avoid returning
|
|
|
|
* '' + '/filename'
|
|
|
|
* as the rename; we need to make old_path + oldlen advance
|
|
|
|
* past the '/' character.
|
|
|
|
*/
|
|
|
|
oldlen++;
|
2018-04-19 17:58:07 +00:00
|
|
|
newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
|
|
|
|
strbuf_grow(&new_path, newlen);
|
|
|
|
strbuf_addbuf(&new_path, &entry->new_dir);
|
|
|
|
strbuf_addstr(&new_path, &old_path[oldlen]);
|
|
|
|
|
|
|
|
return strbuf_detach(&new_path, NULL);
|
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:05 +00:00
|
|
|
static void get_renamed_dir_portion(const char *old_path, const char *new_path,
|
|
|
|
char **old_dir, char **new_dir)
|
|
|
|
{
|
|
|
|
char *end_of_old, *end_of_new;
|
|
|
|
|
2019-10-22 21:22:49 +00:00
|
|
|
/* Default return values: NULL, meaning no rename */
|
2018-04-19 17:58:05 +00:00
|
|
|
*old_dir = NULL;
|
|
|
|
*new_dir = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For
|
|
|
|
* "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c"
|
|
|
|
* the "e/foo.c" part is the same, we just want to know that
|
|
|
|
* "a/b/c/d" was renamed to "a/b/some/thing/else"
|
|
|
|
* so, for this example, this function returns "a/b/c/d" in
|
|
|
|
* *old_dir and "a/b/some/thing/else" in *new_dir.
|
2019-10-22 21:22:49 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the basename of the file changed, we don't care. We want
|
|
|
|
* to know which portion of the directory, if any, changed.
|
2018-04-19 17:58:05 +00:00
|
|
|
*/
|
|
|
|
end_of_old = strrchr(old_path, '/');
|
|
|
|
end_of_new = strrchr(new_path, '/');
|
|
|
|
|
2019-10-22 21:22:50 +00:00
|
|
|
/*
|
|
|
|
* If end_of_old is NULL, old_path wasn't in a directory, so there
|
|
|
|
* could not be a directory rename (our rule elsewhere that a
|
|
|
|
* directory which still exists is not considered to have been
|
|
|
|
* renamed means the root directory can never be renamed -- because
|
|
|
|
* the root directory always exists).
|
|
|
|
*/
|
2022-05-02 16:50:37 +00:00
|
|
|
if (!end_of_old)
|
2019-10-22 21:22:50 +00:00
|
|
|
return; /* Note: *old_dir and *new_dir are still NULL */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If new_path contains no directory (end_of_new is NULL), then we
|
|
|
|
* have a rename of old_path's directory to the root directory.
|
|
|
|
*/
|
2022-05-02 16:50:37 +00:00
|
|
|
if (!end_of_new) {
|
2019-10-22 21:22:50 +00:00
|
|
|
*old_dir = xstrndup(old_path, end_of_old - old_path);
|
|
|
|
*new_dir = xstrdup("");
|
2018-04-19 17:58:05 +00:00
|
|
|
return;
|
2019-10-22 21:22:50 +00:00
|
|
|
}
|
2019-10-22 21:22:49 +00:00
|
|
|
|
|
|
|
/* Find the first non-matching character traversing backwards */
|
2018-04-19 17:58:05 +00:00
|
|
|
while (*--end_of_new == *--end_of_old &&
|
|
|
|
end_of_old != old_path &&
|
|
|
|
end_of_new != new_path)
|
|
|
|
; /* Do nothing; all in the while loop */
|
2019-10-22 21:22:49 +00:00
|
|
|
|
2018-04-19 17:58:05 +00:00
|
|
|
/*
|
2019-10-22 21:22:49 +00:00
|
|
|
* If both got back to the beginning of their strings, then the
|
|
|
|
* directory didn't change at all, only the basename did.
|
2018-04-19 17:58:05 +00:00
|
|
|
*/
|
2019-10-22 21:22:49 +00:00
|
|
|
if (end_of_old == old_path && end_of_new == new_path &&
|
|
|
|
*end_of_old == *end_of_new)
|
2019-10-22 21:22:50 +00:00
|
|
|
return; /* Note: *old_dir and *new_dir are still NULL */
|
2018-04-19 17:58:05 +00:00
|
|
|
|
|
|
|
/*
|
2019-10-22 21:22:50 +00:00
|
|
|
* If end_of_new got back to the beginning of its string, and
|
|
|
|
* end_of_old got back to the beginning of some subdirectory, then
|
|
|
|
* we have a rename/merge of a subdirectory into the root, which
|
|
|
|
* needs slightly special handling.
|
|
|
|
*
|
|
|
|
* Note: There is no need to consider the opposite case, with a
|
|
|
|
* rename/merge of the root directory into some subdirectory
|
|
|
|
* because as noted above the root directory always exists so it
|
|
|
|
* cannot be considered to be renamed.
|
2018-04-19 17:58:05 +00:00
|
|
|
*/
|
2019-10-22 21:22:50 +00:00
|
|
|
if (end_of_new == new_path &&
|
|
|
|
end_of_old != old_path && end_of_old[-1] == '/') {
|
|
|
|
*old_dir = xstrndup(old_path, --end_of_old - old_path);
|
|
|
|
*new_dir = xstrdup("");
|
|
|
|
return;
|
2018-04-19 17:58:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2019-10-22 21:22:49 +00:00
|
|
|
* We've found the first non-matching character in the directory
|
|
|
|
* paths. That means the current characters we were looking at
|
|
|
|
* were part of the first non-matching subdir name going back from
|
|
|
|
* the end of the strings. Get the whole name by advancing both
|
|
|
|
* end_of_old and end_of_new to the NEXT '/' character. That will
|
|
|
|
* represent the entire directory rename.
|
|
|
|
*
|
|
|
|
* The reason for the increment is cases like
|
|
|
|
* a/b/star/foo/whatever.c -> a/b/tar/foo/random.c
|
|
|
|
* After dropping the basename and going back to the first
|
|
|
|
* non-matching character, we're now comparing:
|
|
|
|
* a/b/s and a/b/
|
|
|
|
* and we want to be comparing:
|
|
|
|
* a/b/star/ and a/b/tar/
|
|
|
|
* but without the pre-increment, the one on the right would stay
|
|
|
|
* a/b/.
|
2018-04-19 17:58:05 +00:00
|
|
|
*/
|
2019-10-22 21:22:49 +00:00
|
|
|
end_of_old = strchr(++end_of_old, '/');
|
|
|
|
end_of_new = strchr(++end_of_new, '/');
|
2018-04-19 17:58:05 +00:00
|
|
|
|
2019-10-22 21:22:49 +00:00
|
|
|
/* Copy the old and new directories into *old_dir and *new_dir. */
|
|
|
|
*old_dir = xstrndup(old_path, end_of_old - old_path);
|
|
|
|
*new_dir = xstrndup(new_path, end_of_new - new_path);
|
2018-04-19 17:58:05 +00:00
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:06 +00:00
|
|
|
static void remove_hashmap_entries(struct hashmap *dir_renames,
|
|
|
|
struct string_list *items_to_remove)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct dir_rename_entry *entry;
|
|
|
|
|
|
|
|
for (i = 0; i < items_to_remove->nr; i++) {
|
|
|
|
entry = items_to_remove->items[i].util;
|
2019-10-06 23:30:31 +00:00
|
|
|
hashmap_remove(dir_renames, &entry->ent, NULL);
|
2018-04-19 17:58:06 +00:00
|
|
|
}
|
|
|
|
string_list_clear(items_to_remove, 0);
|
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:08 +00:00
|
|
|
/*
|
|
|
|
* See if there is a directory rename for path, and if there are any file
|
|
|
|
* level conflicts for the renamed location. If there is a rename and
|
|
|
|
* there are no conflicts, return the new name. Otherwise, return NULL.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
static char *handle_path_level_conflicts(struct merge_options *opt,
|
2018-04-19 17:58:08 +00:00
|
|
|
const char *path,
|
|
|
|
struct dir_rename_entry *entry,
|
|
|
|
struct hashmap *collisions,
|
|
|
|
struct tree *tree)
|
|
|
|
{
|
|
|
|
char *new_path = NULL;
|
|
|
|
struct collision_entry *collision_ent;
|
|
|
|
int clean = 1;
|
|
|
|
struct strbuf collision_paths = STRBUF_INIT;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* entry has the mapping of old directory name to new directory name
|
|
|
|
* that we want to apply to path.
|
|
|
|
*/
|
|
|
|
new_path = apply_dir_rename(entry, path);
|
|
|
|
|
|
|
|
if (!new_path) {
|
|
|
|
/* This should only happen when entry->non_unique_new_dir set */
|
|
|
|
if (!entry->non_unique_new_dir)
|
2022-11-25 17:37:45 +00:00
|
|
|
BUG("entry->non_unique_new_dir not set and !new_path");
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (directory rename split): "
|
2018-04-19 17:58:08 +00:00
|
|
|
"Unclear where to place %s because directory "
|
|
|
|
"%s was renamed to multiple other directories, "
|
|
|
|
"with no destination getting a majority of the "
|
|
|
|
"files."),
|
|
|
|
path, entry->dir);
|
|
|
|
clean = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The caller needs to have ensured that it has pre-populated
|
|
|
|
* collisions with all paths that map to new_path. Do a quick check
|
|
|
|
* to ensure that's the case.
|
|
|
|
*/
|
|
|
|
collision_ent = collision_find_entry(collisions, new_path);
|
2022-05-02 16:50:37 +00:00
|
|
|
if (!collision_ent)
|
2018-04-19 17:58:08 +00:00
|
|
|
BUG("collision_ent is NULL");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for one-sided add/add/.../add conflicts, i.e.
|
|
|
|
* where implicit renames from the other side doing
|
|
|
|
* directory rename(s) can affect this side of history
|
|
|
|
* to put multiple paths into the same location. Warn
|
|
|
|
* and bail on directory renames for such paths.
|
|
|
|
*/
|
|
|
|
if (collision_ent->reported_already) {
|
|
|
|
clean = 0;
|
2019-06-27 09:28:52 +00:00
|
|
|
} else if (tree_has_path(opt->repo, tree, new_path)) {
|
2018-04-19 17:58:08 +00:00
|
|
|
collision_ent->reported_already = 1;
|
|
|
|
strbuf_add_separated_string_list(&collision_paths, ", ",
|
|
|
|
&collision_ent->source_files);
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (implicit dir rename): Existing "
|
2018-04-19 17:58:08 +00:00
|
|
|
"file/dir at %s in the way of implicit "
|
|
|
|
"directory rename(s) putting the following "
|
|
|
|
"path(s) there: %s."),
|
|
|
|
new_path, collision_paths.buf);
|
|
|
|
clean = 0;
|
|
|
|
} else if (collision_ent->source_files.nr > 1) {
|
|
|
|
collision_ent->reported_already = 1;
|
|
|
|
strbuf_add_separated_string_list(&collision_paths, ", ",
|
|
|
|
&collision_ent->source_files);
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (implicit dir rename): Cannot map "
|
2018-04-19 17:58:08 +00:00
|
|
|
"more than one path to %s; implicit directory "
|
|
|
|
"renames tried to put these paths there: %s"),
|
|
|
|
new_path, collision_paths.buf);
|
|
|
|
clean = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free memory we no longer need */
|
|
|
|
strbuf_release(&collision_paths);
|
|
|
|
if (!clean && new_path) {
|
|
|
|
free(new_path);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new_path;
|
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:06 +00:00
|
|
|
/*
|
|
|
|
* There are a couple things we want to do at the directory level:
|
|
|
|
* 1. Check for both sides renaming to the same thing, in order to avoid
|
|
|
|
* implicit renaming of files that should be left in place. (See
|
|
|
|
* testcase 6b in t6043 for details.)
|
|
|
|
* 2. Prune directory renames if there are still files left in the
|
2021-06-11 11:18:50 +00:00
|
|
|
* original directory. These represent a partial directory rename,
|
2018-04-19 17:58:06 +00:00
|
|
|
* i.e. a rename where only some of the files within the directory
|
|
|
|
* were renamed elsewhere. (Technically, this could be done earlier
|
|
|
|
* in get_directory_renames(), except that would prevent us from
|
|
|
|
* doing the previous check and thus failing testcase 6b.)
|
|
|
|
* 3. Check for rename/rename(1to2) conflicts (at the directory level).
|
|
|
|
* In the future, we could potentially record this info as well and
|
|
|
|
* omit reporting rename/rename(1to2) conflicts for each path within
|
|
|
|
* the affected directories, thus cleaning up the merge output.
|
|
|
|
* NOTE: We do NOT check for rename/rename(2to1) conflicts at the
|
|
|
|
* directory level, because merging directories is fine. If it
|
|
|
|
* causes conflicts for files within those merged directories, then
|
|
|
|
* that should be detected at the individual path level.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
static void handle_directory_level_conflicts(struct merge_options *opt,
|
2018-04-19 17:58:06 +00:00
|
|
|
struct hashmap *dir_re_head,
|
|
|
|
struct tree *head,
|
|
|
|
struct hashmap *dir_re_merge,
|
|
|
|
struct tree *merge)
|
|
|
|
{
|
|
|
|
struct hashmap_iter iter;
|
|
|
|
struct dir_rename_entry *head_ent;
|
|
|
|
struct dir_rename_entry *merge_ent;
|
|
|
|
|
|
|
|
struct string_list remove_from_head = STRING_LIST_INIT_NODUP;
|
|
|
|
struct string_list remove_from_merge = STRING_LIST_INIT_NODUP;
|
|
|
|
|
2019-10-06 23:30:38 +00:00
|
|
|
hashmap_for_each_entry(dir_re_head, &iter, head_ent,
|
|
|
|
ent /* member name */) {
|
2018-04-19 17:58:06 +00:00
|
|
|
merge_ent = dir_rename_find_entry(dir_re_merge, head_ent->dir);
|
|
|
|
if (merge_ent &&
|
|
|
|
!head_ent->non_unique_new_dir &&
|
|
|
|
!merge_ent->non_unique_new_dir &&
|
|
|
|
!strbuf_cmp(&head_ent->new_dir, &merge_ent->new_dir)) {
|
|
|
|
/* 1. Renamed identically; remove it from both sides */
|
|
|
|
string_list_append(&remove_from_head,
|
|
|
|
head_ent->dir)->util = head_ent;
|
|
|
|
strbuf_release(&head_ent->new_dir);
|
|
|
|
string_list_append(&remove_from_merge,
|
|
|
|
merge_ent->dir)->util = merge_ent;
|
|
|
|
strbuf_release(&merge_ent->new_dir);
|
2019-06-27 09:28:52 +00:00
|
|
|
} else if (tree_has_path(opt->repo, head, head_ent->dir)) {
|
2018-04-19 17:58:06 +00:00
|
|
|
/* 2. This wasn't a directory rename after all */
|
|
|
|
string_list_append(&remove_from_head,
|
|
|
|
head_ent->dir)->util = head_ent;
|
|
|
|
strbuf_release(&head_ent->new_dir);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
remove_hashmap_entries(dir_re_head, &remove_from_head);
|
|
|
|
remove_hashmap_entries(dir_re_merge, &remove_from_merge);
|
|
|
|
|
2019-10-06 23:30:38 +00:00
|
|
|
hashmap_for_each_entry(dir_re_merge, &iter, merge_ent,
|
|
|
|
ent /* member name */) {
|
2018-04-19 17:58:06 +00:00
|
|
|
head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir);
|
2019-06-27 09:28:52 +00:00
|
|
|
if (tree_has_path(opt->repo, merge, merge_ent->dir)) {
|
2018-04-19 17:58:06 +00:00
|
|
|
/* 2. This wasn't a directory rename after all */
|
|
|
|
string_list_append(&remove_from_merge,
|
|
|
|
merge_ent->dir)->util = merge_ent;
|
|
|
|
} else if (head_ent &&
|
|
|
|
!head_ent->non_unique_new_dir &&
|
|
|
|
!merge_ent->non_unique_new_dir) {
|
|
|
|
/* 3. rename/rename(1to2) */
|
|
|
|
/*
|
|
|
|
* We can assume it's not rename/rename(1to1) because
|
|
|
|
* that was case (1), already checked above. So we
|
|
|
|
* know that head_ent->new_dir and merge_ent->new_dir
|
|
|
|
* are different strings.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (rename/rename): "
|
2018-04-19 17:58:06 +00:00
|
|
|
"Rename directory %s->%s in %s. "
|
|
|
|
"Rename directory %s->%s in %s"),
|
2019-04-05 15:00:13 +00:00
|
|
|
head_ent->dir, head_ent->new_dir.buf, opt->branch1,
|
|
|
|
head_ent->dir, merge_ent->new_dir.buf, opt->branch2);
|
2018-04-19 17:58:06 +00:00
|
|
|
string_list_append(&remove_from_head,
|
|
|
|
head_ent->dir)->util = head_ent;
|
|
|
|
strbuf_release(&head_ent->new_dir);
|
|
|
|
string_list_append(&remove_from_merge,
|
|
|
|
merge_ent->dir)->util = merge_ent;
|
|
|
|
strbuf_release(&merge_ent->new_dir);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
remove_hashmap_entries(dir_re_head, &remove_from_head);
|
|
|
|
remove_hashmap_entries(dir_re_merge, &remove_from_merge);
|
|
|
|
}
|
|
|
|
|
2019-02-14 05:50:02 +00:00
|
|
|
static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs)
|
2018-04-19 17:58:05 +00:00
|
|
|
{
|
|
|
|
struct hashmap *dir_renames;
|
|
|
|
struct hashmap_iter iter;
|
|
|
|
struct dir_rename_entry *entry;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Typically, we think of a directory rename as all files from a
|
|
|
|
* certain directory being moved to a target directory. However,
|
|
|
|
* what if someone first moved two files from the original
|
|
|
|
* directory in one commit, and then renamed the directory
|
|
|
|
* somewhere else in a later commit? At merge time, we just know
|
|
|
|
* that files from the original directory went to two different
|
|
|
|
* places, and that the bulk of them ended up in the same place.
|
|
|
|
* We want each directory rename to represent where the bulk of the
|
|
|
|
* files from that directory end up; this function exists to find
|
|
|
|
* where the bulk of the files went.
|
|
|
|
*
|
|
|
|
* The first loop below simply iterates through the list of file
|
|
|
|
* renames, finding out how often each directory rename pair
|
|
|
|
* possibility occurs.
|
|
|
|
*/
|
|
|
|
dir_renames = xmalloc(sizeof(*dir_renames));
|
|
|
|
dir_rename_init(dir_renames);
|
|
|
|
for (i = 0; i < pairs->nr; ++i) {
|
|
|
|
struct string_list_item *item;
|
|
|
|
int *count;
|
|
|
|
struct diff_filepair *pair = pairs->queue[i];
|
|
|
|
char *old_dir, *new_dir;
|
|
|
|
|
|
|
|
/* File not part of directory rename if it wasn't renamed */
|
|
|
|
if (pair->status != 'R')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
get_renamed_dir_portion(pair->one->path, pair->two->path,
|
|
|
|
&old_dir, &new_dir);
|
|
|
|
if (!old_dir)
|
|
|
|
/* Directory didn't change at all; ignore this one. */
|
|
|
|
continue;
|
|
|
|
|
|
|
|
entry = dir_rename_find_entry(dir_renames, old_dir);
|
|
|
|
if (!entry) {
|
|
|
|
entry = xmalloc(sizeof(*entry));
|
|
|
|
dir_rename_entry_init(entry, old_dir);
|
2019-10-06 23:30:32 +00:00
|
|
|
hashmap_put(dir_renames, &entry->ent);
|
2018-04-19 17:58:05 +00:00
|
|
|
} else {
|
|
|
|
free(old_dir);
|
|
|
|
}
|
|
|
|
item = string_list_lookup(&entry->possible_new_dirs, new_dir);
|
|
|
|
if (!item) {
|
|
|
|
item = string_list_insert(&entry->possible_new_dirs,
|
|
|
|
new_dir);
|
|
|
|
item->util = xcalloc(1, sizeof(int));
|
|
|
|
} else {
|
|
|
|
free(new_dir);
|
|
|
|
}
|
|
|
|
count = item->util;
|
|
|
|
*count += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For each directory with files moved out of it, we find out which
|
|
|
|
* target directory received the most files so we can declare it to
|
|
|
|
* be the "winning" target location for the directory rename. This
|
|
|
|
* winner gets recorded in new_dir. If there is no winner
|
|
|
|
* (multiple target directories received the same number of files),
|
|
|
|
* we set non_unique_new_dir. Once we've determined the winner (or
|
|
|
|
* that there is no winner), we no longer need possible_new_dirs.
|
|
|
|
*/
|
2019-10-06 23:30:38 +00:00
|
|
|
hashmap_for_each_entry(dir_renames, &iter, entry,
|
|
|
|
ent /* member name */) {
|
2018-04-19 17:58:05 +00:00
|
|
|
int max = 0;
|
|
|
|
int bad_max = 0;
|
|
|
|
char *best = NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < entry->possible_new_dirs.nr; i++) {
|
|
|
|
int *count = entry->possible_new_dirs.items[i].util;
|
|
|
|
|
|
|
|
if (*count == max)
|
|
|
|
bad_max = max;
|
|
|
|
else if (*count > max) {
|
|
|
|
max = *count;
|
|
|
|
best = entry->possible_new_dirs.items[i].string;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (bad_max == max)
|
|
|
|
entry->non_unique_new_dir = 1;
|
|
|
|
else {
|
|
|
|
assert(entry->new_dir.len == 0);
|
|
|
|
strbuf_addstr(&entry->new_dir, best);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* The relevant directory sub-portion of the original full
|
|
|
|
* filepaths were xstrndup'ed before inserting into
|
|
|
|
* possible_new_dirs, and instead of manually iterating the
|
|
|
|
* list and free'ing each, just lie and tell
|
|
|
|
* possible_new_dirs that it did the strdup'ing so that it
|
|
|
|
* will free them for us.
|
|
|
|
*/
|
|
|
|
entry->possible_new_dirs.strdup_strings = 1;
|
|
|
|
string_list_clear(&entry->possible_new_dirs, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return dir_renames;
|
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:07 +00:00
|
|
|
static struct dir_rename_entry *check_dir_renamed(const char *path,
|
|
|
|
struct hashmap *dir_renames)
|
|
|
|
{
|
2018-06-10 10:56:31 +00:00
|
|
|
char *temp = xstrdup(path);
|
2018-04-19 17:58:07 +00:00
|
|
|
char *end;
|
2018-09-05 17:03:07 +00:00
|
|
|
struct dir_rename_entry *entry = NULL;
|
2018-04-19 17:58:07 +00:00
|
|
|
|
|
|
|
while ((end = strrchr(temp, '/'))) {
|
|
|
|
*end = '\0';
|
|
|
|
entry = dir_rename_find_entry(dir_renames, temp);
|
|
|
|
if (entry)
|
2018-06-10 10:56:31 +00:00
|
|
|
break;
|
2018-04-19 17:58:07 +00:00
|
|
|
}
|
2018-06-10 10:56:31 +00:00
|
|
|
free(temp);
|
|
|
|
return entry;
|
2018-04-19 17:58:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void compute_collisions(struct hashmap *collisions,
|
|
|
|
struct hashmap *dir_renames,
|
|
|
|
struct diff_queue_struct *pairs)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Multiple files can be mapped to the same path due to directory
|
|
|
|
* renames done by the other side of history. Since that other
|
|
|
|
* side of history could have merged multiple directories into one,
|
|
|
|
* if our side of history added the same file basename to each of
|
|
|
|
* those directories, then all N of them would get implicitly
|
|
|
|
* renamed by the directory rename detection into the same path,
|
|
|
|
* and we'd get an add/add/.../add conflict, and all those adds
|
|
|
|
* from *this* side of history. This is not representable in the
|
|
|
|
* index, and users aren't going to easily be able to make sense of
|
|
|
|
* it. So we need to provide a good warning about what's
|
|
|
|
* happening, and fall back to no-directory-rename detection
|
|
|
|
* behavior for those paths.
|
|
|
|
*
|
|
|
|
* See testcases 9e and all of section 5 from t6043 for examples.
|
|
|
|
*/
|
|
|
|
collision_init(collisions);
|
|
|
|
|
|
|
|
for (i = 0; i < pairs->nr; ++i) {
|
|
|
|
struct dir_rename_entry *dir_rename_ent;
|
|
|
|
struct collision_entry *collision_ent;
|
|
|
|
char *new_path;
|
|
|
|
struct diff_filepair *pair = pairs->queue[i];
|
|
|
|
|
2018-04-19 17:58:15 +00:00
|
|
|
if (pair->status != 'A' && pair->status != 'R')
|
2018-04-19 17:58:07 +00:00
|
|
|
continue;
|
|
|
|
dir_rename_ent = check_dir_renamed(pair->two->path,
|
|
|
|
dir_renames);
|
|
|
|
if (!dir_rename_ent)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
new_path = apply_dir_rename(dir_rename_ent, pair->two->path);
|
|
|
|
if (!new_path)
|
|
|
|
/*
|
|
|
|
* dir_rename_ent->non_unique_new_path is true, which
|
|
|
|
* means there is no directory rename for us to use,
|
|
|
|
* which means it won't cause us any additional
|
|
|
|
* collisions.
|
|
|
|
*/
|
|
|
|
continue;
|
|
|
|
collision_ent = collision_find_entry(collisions, new_path);
|
|
|
|
if (!collision_ent) {
|
2021-03-13 16:17:22 +00:00
|
|
|
CALLOC_ARRAY(collision_ent, 1);
|
2019-10-06 23:30:27 +00:00
|
|
|
hashmap_entry_init(&collision_ent->ent,
|
|
|
|
strhash(new_path));
|
2019-10-06 23:30:32 +00:00
|
|
|
hashmap_put(collisions, &collision_ent->ent);
|
2018-04-19 17:58:07 +00:00
|
|
|
collision_ent->target_file = new_path;
|
|
|
|
} else {
|
|
|
|
free(new_path);
|
|
|
|
}
|
|
|
|
string_list_insert(&collision_ent->source_files,
|
|
|
|
pair->two->path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static char *check_for_directory_rename(struct merge_options *opt,
|
2018-04-19 17:58:08 +00:00
|
|
|
const char *path,
|
|
|
|
struct tree *tree,
|
|
|
|
struct hashmap *dir_renames,
|
|
|
|
struct hashmap *dir_rename_exclusions,
|
|
|
|
struct hashmap *collisions,
|
|
|
|
int *clean_merge)
|
|
|
|
{
|
|
|
|
char *new_path = NULL;
|
|
|
|
struct dir_rename_entry *entry = check_dir_renamed(path, dir_renames);
|
|
|
|
struct dir_rename_entry *oentry = NULL;
|
|
|
|
|
|
|
|
if (!entry)
|
|
|
|
return new_path;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This next part is a little weird. We do not want to do an
|
|
|
|
* implicit rename into a directory we renamed on our side, because
|
|
|
|
* that will result in a spurious rename/rename(1to2) conflict. An
|
|
|
|
* example:
|
|
|
|
* Base commit: dumbdir/afile, otherdir/bfile
|
|
|
|
* Side 1: smrtdir/afile, otherdir/bfile
|
|
|
|
* Side 2: dumbdir/afile, dumbdir/bfile
|
|
|
|
* Here, while working on Side 1, we could notice that otherdir was
|
|
|
|
* renamed/merged to dumbdir, and change the diff_filepair for
|
|
|
|
* otherdir/bfile into a rename into dumbdir/bfile. However, Side
|
|
|
|
* 2 will notice the rename from dumbdir to smrtdir, and do the
|
|
|
|
* transitive rename to move it from dumbdir/bfile to
|
|
|
|
* smrtdir/bfile. That gives us bfile in dumbdir vs being in
|
|
|
|
* smrtdir, a rename/rename(1to2) conflict. We really just want
|
|
|
|
* the file to end up in smrtdir. And the way to achieve that is
|
|
|
|
* to not let Side1 do the rename to dumbdir, since we know that is
|
|
|
|
* the source of one of our directory renames.
|
|
|
|
*
|
|
|
|
* That's why oentry and dir_rename_exclusions is here.
|
|
|
|
*
|
|
|
|
* As it turns out, this also prevents N-way transient rename
|
|
|
|
* confusion; See testcases 9c and 9d of t6043.
|
|
|
|
*/
|
|
|
|
oentry = dir_rename_find_entry(dir_rename_exclusions, entry->new_dir.buf);
|
|
|
|
if (oentry) {
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("WARNING: Avoiding applying %s -> %s rename "
|
2018-04-19 17:58:08 +00:00
|
|
|
"to %s, because %s itself was renamed."),
|
|
|
|
entry->dir, entry->new_dir.buf, path, entry->new_dir.buf);
|
|
|
|
} else {
|
2019-04-05 15:00:13 +00:00
|
|
|
new_path = handle_path_level_conflicts(opt, path, entry,
|
2018-04-19 17:58:08 +00:00
|
|
|
collisions, tree);
|
|
|
|
*clean_merge &= (new_path != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new_path;
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static void apply_directory_rename_modifications(struct merge_options *opt,
|
2018-04-19 17:58:10 +00:00
|
|
|
struct diff_filepair *pair,
|
|
|
|
char *new_path,
|
|
|
|
struct rename *re,
|
|
|
|
struct tree *tree,
|
|
|
|
struct tree *o_tree,
|
|
|
|
struct tree *a_tree,
|
|
|
|
struct tree *b_tree,
|
2019-02-14 05:50:02 +00:00
|
|
|
struct string_list *entries)
|
2018-04-19 17:58:10 +00:00
|
|
|
{
|
|
|
|
struct string_list_item *item;
|
|
|
|
int stage = (tree == a_tree ? 2 : 3);
|
2018-04-19 17:58:13 +00:00
|
|
|
int update_wd;
|
2018-04-19 17:58:10 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* In all cases where we can do directory rename detection,
|
|
|
|
* unpack_trees() will have read pair->two->path into the
|
|
|
|
* index and the working copy. We need to remove it so that
|
|
|
|
* we can instead place it at new_path. It is guaranteed to
|
|
|
|
* not be untracked (unpack_trees() would have errored out
|
|
|
|
* saying the file would have been overwritten), but it might
|
|
|
|
* be dirty, though.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
update_wd = !was_dirty(opt, pair->two->path);
|
2018-04-19 17:58:13 +00:00
|
|
|
if (!update_wd)
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("Refusing to lose dirty file at %s"),
|
2018-04-19 17:58:13 +00:00
|
|
|
pair->two->path);
|
2019-04-05 15:00:13 +00:00
|
|
|
remove_file(opt, 1, pair->two->path, !update_wd);
|
2018-04-19 17:58:10 +00:00
|
|
|
|
|
|
|
/* Find or create a new re->dst_entry */
|
|
|
|
item = string_list_lookup(entries, new_path);
|
|
|
|
if (item) {
|
|
|
|
/*
|
|
|
|
* Since we're renaming on this side of history, and it's
|
|
|
|
* due to a directory rename on the other side of history
|
|
|
|
* (which we only allow when the directory in question no
|
|
|
|
* longer exists on the other side of history), the
|
|
|
|
* original entry for re->dst_entry is no longer
|
|
|
|
* necessary...
|
|
|
|
*/
|
|
|
|
re->dst_entry->processed = 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ...because we'll be using this new one.
|
|
|
|
*/
|
|
|
|
re->dst_entry = item->util;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* re->dst_entry is for the before-dir-rename path, and we
|
|
|
|
* need it to hold information for the after-dir-rename
|
|
|
|
* path. Before creating a new entry, we need to mark the
|
|
|
|
* old one as unnecessary (...unless it is shared by
|
|
|
|
* src_entry, i.e. this didn't use to be a rename, in which
|
|
|
|
* case we can just allow the normal processing to happen
|
|
|
|
* for it).
|
|
|
|
*/
|
|
|
|
if (pair->status == 'R')
|
|
|
|
re->dst_entry->processed = 1;
|
|
|
|
|
2019-06-27 09:28:52 +00:00
|
|
|
re->dst_entry = insert_stage_data(opt->repo, new_path,
|
2018-04-19 17:58:10 +00:00
|
|
|
o_tree, a_tree, b_tree,
|
|
|
|
entries);
|
|
|
|
item = string_list_insert(entries, new_path);
|
|
|
|
item->util = re->dst_entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update the stage_data with the information about the path we are
|
|
|
|
* moving into place. That slot will be empty and available for us
|
|
|
|
* to write to because of the collision checks in
|
|
|
|
* handle_path_level_conflicts(). In other words,
|
|
|
|
* re->dst_entry->stages[stage].oid will be the null_oid, so it's
|
|
|
|
* open for us to write to.
|
|
|
|
*
|
|
|
|
* It may be tempting to actually update the index at this point as
|
|
|
|
* well, using update_stages_for_stage_data(), but as per the big
|
|
|
|
* "NOTE" in update_stages(), doing so will modify the current
|
|
|
|
* in-memory index which will break calls to would_lose_untracked()
|
|
|
|
* that we need to make. Instead, we need to just make sure that
|
2018-06-10 04:16:15 +00:00
|
|
|
* the various handle_rename_*() functions update the index
|
2018-04-19 17:58:10 +00:00
|
|
|
* explicitly rather than relying on unpack_trees() to have done it.
|
|
|
|
*/
|
2019-06-27 09:28:49 +00:00
|
|
|
get_tree_entry(opt->repo,
|
|
|
|
&tree->object.oid,
|
2018-04-19 17:58:10 +00:00
|
|
|
pair->two->path,
|
|
|
|
&re->dst_entry->stages[stage].oid,
|
|
|
|
&re->dst_entry->stages[stage].mode);
|
|
|
|
|
2019-04-05 15:00:24 +00:00
|
|
|
/*
|
|
|
|
* Record the original change status (or 'type' of change). If it
|
|
|
|
* was originally an add ('A'), this lets us differentiate later
|
|
|
|
* between a RENAME_DELETE conflict and RENAME_VIA_DIR (they
|
|
|
|
* otherwise look the same). If it was originally a rename ('R'),
|
|
|
|
* this lets us remember and report accurately about the transitive
|
|
|
|
* renaming that occurred via the directory rename detection. Also,
|
|
|
|
* record the original destination name.
|
|
|
|
*/
|
|
|
|
re->dir_rename_original_type = pair->status;
|
|
|
|
re->dir_rename_original_dest = pair->two->path;
|
|
|
|
|
2018-04-19 17:58:10 +00:00
|
|
|
/*
|
|
|
|
* We don't actually look at pair->status again, but it seems
|
|
|
|
* pedagogically correct to adjust it.
|
|
|
|
*/
|
|
|
|
pair->status = 'R';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Finally, record the new location.
|
|
|
|
*/
|
|
|
|
pair->two->path = new_path;
|
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:03 +00:00
|
|
|
/*
|
|
|
|
* Get information of all renames which occurred in 'pairs', making use of
|
|
|
|
* any implicit directory renames inferred from the other side of history.
|
|
|
|
* We need the three trees in the merge ('o_tree', 'a_tree' and 'b_tree')
|
|
|
|
* to be able to associate the correct cache entries with the rename
|
|
|
|
* information; tree is always equal to either a_tree or b_tree.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
static struct string_list *get_renames(struct merge_options *opt,
|
2019-04-05 15:00:20 +00:00
|
|
|
const char *branch,
|
2018-04-19 17:58:03 +00:00
|
|
|
struct diff_queue_struct *pairs,
|
2018-04-19 17:58:07 +00:00
|
|
|
struct hashmap *dir_renames,
|
2018-04-19 17:58:08 +00:00
|
|
|
struct hashmap *dir_rename_exclusions,
|
2018-04-19 17:58:03 +00:00
|
|
|
struct tree *tree,
|
|
|
|
struct tree *o_tree,
|
|
|
|
struct tree *a_tree,
|
|
|
|
struct tree *b_tree,
|
2018-04-19 17:58:08 +00:00
|
|
|
struct string_list *entries,
|
|
|
|
int *clean_merge)
|
2018-04-19 17:58:03 +00:00
|
|
|
{
|
|
|
|
int i;
|
2018-04-19 17:58:07 +00:00
|
|
|
struct hashmap collisions;
|
|
|
|
struct hashmap_iter iter;
|
|
|
|
struct collision_entry *e;
|
2018-04-19 17:58:03 +00:00
|
|
|
struct string_list *renames;
|
|
|
|
|
2018-04-19 17:58:07 +00:00
|
|
|
compute_collisions(&collisions, dir_renames, pairs);
|
2021-03-13 16:17:22 +00:00
|
|
|
CALLOC_ARRAY(renames, 1);
|
2018-04-19 17:58:03 +00:00
|
|
|
|
|
|
|
for (i = 0; i < pairs->nr; ++i) {
|
2018-04-19 17:57:59 +00:00
|
|
|
struct string_list_item *item;
|
|
|
|
struct rename *re;
|
2018-04-19 17:58:03 +00:00
|
|
|
struct diff_filepair *pair = pairs->queue[i];
|
2018-04-19 17:58:08 +00:00
|
|
|
char *new_path; /* non-NULL only with directory renames */
|
2018-04-19 17:57:59 +00:00
|
|
|
|
2018-04-19 17:58:15 +00:00
|
|
|
if (pair->status != 'A' && pair->status != 'R') {
|
2018-04-19 17:57:59 +00:00
|
|
|
diff_free_filepair(pair);
|
|
|
|
continue;
|
|
|
|
}
|
2019-04-05 15:00:13 +00:00
|
|
|
new_path = check_for_directory_rename(opt, pair->two->path, tree,
|
2018-04-19 17:58:08 +00:00
|
|
|
dir_renames,
|
|
|
|
dir_rename_exclusions,
|
|
|
|
&collisions,
|
|
|
|
clean_merge);
|
|
|
|
if (pair->status != 'R' && !new_path) {
|
|
|
|
diff_free_filepair(pair);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-04-19 17:57:59 +00:00
|
|
|
re = xmalloc(sizeof(*re));
|
|
|
|
re->processed = 0;
|
|
|
|
re->pair = pair;
|
2019-04-05 15:00:20 +00:00
|
|
|
re->branch = branch;
|
2019-04-05 15:00:24 +00:00
|
|
|
re->dir_rename_original_type = '\0';
|
|
|
|
re->dir_rename_original_dest = NULL;
|
2018-04-19 17:57:59 +00:00
|
|
|
item = string_list_lookup(entries, re->pair->one->path);
|
|
|
|
if (!item)
|
2019-06-27 09:28:52 +00:00
|
|
|
re->src_entry = insert_stage_data(opt->repo,
|
|
|
|
re->pair->one->path,
|
2018-04-19 17:57:59 +00:00
|
|
|
o_tree, a_tree, b_tree, entries);
|
|
|
|
else
|
|
|
|
re->src_entry = item->util;
|
|
|
|
|
|
|
|
item = string_list_lookup(entries, re->pair->two->path);
|
|
|
|
if (!item)
|
2019-06-27 09:28:52 +00:00
|
|
|
re->dst_entry = insert_stage_data(opt->repo,
|
|
|
|
re->pair->two->path,
|
2018-04-19 17:57:59 +00:00
|
|
|
o_tree, a_tree, b_tree, entries);
|
|
|
|
else
|
|
|
|
re->dst_entry = item->util;
|
|
|
|
item = string_list_insert(renames, pair->one->path);
|
|
|
|
item->util = re;
|
2018-04-19 17:58:10 +00:00
|
|
|
if (new_path)
|
2019-04-05 15:00:13 +00:00
|
|
|
apply_directory_rename_modifications(opt, pair, new_path,
|
2018-04-19 17:58:10 +00:00
|
|
|
re, tree, o_tree,
|
|
|
|
a_tree, b_tree,
|
2019-02-14 05:50:02 +00:00
|
|
|
entries);
|
2018-04-19 17:57:59 +00:00
|
|
|
}
|
2018-04-19 17:58:07 +00:00
|
|
|
|
2019-10-06 23:30:38 +00:00
|
|
|
hashmap_for_each_entry(&collisions, &iter, e,
|
|
|
|
ent /* member name */) {
|
2018-04-19 17:58:07 +00:00
|
|
|
free(e->target_file);
|
|
|
|
string_list_clear(&e->source_files, 0);
|
|
|
|
}
|
2020-11-02 18:55:05 +00:00
|
|
|
hashmap_clear_and_free(&collisions, struct collision_entry, ent);
|
2018-04-19 17:57:59 +00:00
|
|
|
return renames;
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int process_renames(struct merge_options *opt,
|
2008-08-25 14:25:57 +00:00
|
|
|
struct string_list *a_renames,
|
|
|
|
struct string_list *b_renames)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
|
|
|
int clean_merge = 1, i, j;
|
2010-07-04 19:46:19 +00:00
|
|
|
struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
|
|
|
|
struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
|
2008-08-12 16:45:14 +00:00
|
|
|
const struct rename *sre;
|
|
|
|
|
2021-03-20 00:03:53 +00:00
|
|
|
/*
|
|
|
|
* FIXME: As string-list.h notes, it's O(n^2) to build a sorted
|
|
|
|
* string_list one-by-one, but O(n log n) to build it unsorted and
|
|
|
|
* then sort it. Note that as we build the list, we do not need to
|
|
|
|
* check if the existing destination path is already in the list,
|
|
|
|
* because the structure of diffcore_rename guarantees we won't
|
|
|
|
* have duplicates.
|
|
|
|
*/
|
2008-08-12 16:45:14 +00:00
|
|
|
for (i = 0; i < a_renames->nr; i++) {
|
|
|
|
sre = a_renames->items[i].util;
|
2010-06-25 23:41:35 +00:00
|
|
|
string_list_insert(&a_by_dst, sre->pair->two->path)->util
|
2011-08-12 05:20:04 +00:00
|
|
|
= (void *)sre;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
for (i = 0; i < b_renames->nr; i++) {
|
|
|
|
sre = b_renames->items[i].util;
|
2010-06-25 23:41:35 +00:00
|
|
|
string_list_insert(&b_by_dst, sre->pair->two->path)->util
|
2011-08-12 05:20:04 +00:00
|
|
|
= (void *)sre;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
|
2009-03-15 21:01:20 +00:00
|
|
|
struct string_list *renames1, *renames2Dst;
|
2008-08-12 16:45:14 +00:00
|
|
|
struct rename *ren1 = NULL, *ren2 = NULL;
|
|
|
|
const char *ren1_src, *ren1_dst;
|
2011-08-12 05:20:15 +00:00
|
|
|
struct string_list_item *lookup;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
|
|
|
if (i >= a_renames->nr) {
|
|
|
|
ren2 = b_renames->items[j++].util;
|
|
|
|
} else if (j >= b_renames->nr) {
|
|
|
|
ren1 = a_renames->items[i++].util;
|
|
|
|
} else {
|
2009-03-15 21:01:20 +00:00
|
|
|
int compare = strcmp(a_renames->items[i].string,
|
|
|
|
b_renames->items[j].string);
|
2008-08-12 16:45:14 +00:00
|
|
|
if (compare <= 0)
|
|
|
|
ren1 = a_renames->items[i++].util;
|
|
|
|
if (compare >= 0)
|
|
|
|
ren2 = b_renames->items[j++].util;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO: refactor, so that 1/2 are not needed */
|
|
|
|
if (ren1) {
|
|
|
|
renames1 = a_renames;
|
|
|
|
renames2Dst = &b_by_dst;
|
|
|
|
} else {
|
|
|
|
renames1 = b_renames;
|
|
|
|
renames2Dst = &a_by_dst;
|
2017-01-28 21:40:58 +00:00
|
|
|
SWAP(ren2, ren1);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ren1->processed)
|
|
|
|
continue;
|
|
|
|
ren1->processed = 1;
|
|
|
|
ren1->dst_entry->processed = 1;
|
2011-08-12 05:20:05 +00:00
|
|
|
/* BUG: We should only mark src_entry as processed if we
|
|
|
|
* are not dealing with a rename + add-source case.
|
|
|
|
*/
|
2008-08-12 16:45:14 +00:00
|
|
|
ren1->src_entry->processed = 1;
|
|
|
|
|
|
|
|
ren1_src = ren1->pair->one->path;
|
|
|
|
ren1_dst = ren1->pair->two->path;
|
|
|
|
|
|
|
|
if (ren2) {
|
2011-08-12 05:20:15 +00:00
|
|
|
/* One file renamed on both sides */
|
2008-08-12 16:45:14 +00:00
|
|
|
const char *ren2_src = ren2->pair->one->path;
|
|
|
|
const char *ren2_dst = ren2->pair->two->path;
|
2011-08-12 05:20:08 +00:00
|
|
|
enum rename_type rename_type;
|
2008-08-12 16:45:14 +00:00
|
|
|
if (strcmp(ren1_src, ren2_src) != 0)
|
2018-05-02 09:38:39 +00:00
|
|
|
BUG("ren1_src != ren2_src");
|
2008-08-12 16:45:14 +00:00
|
|
|
ren2->dst_entry->processed = 1;
|
|
|
|
ren2->processed = 1;
|
|
|
|
if (strcmp(ren1_dst, ren2_dst) != 0) {
|
2011-08-12 05:20:08 +00:00
|
|
|
rename_type = RENAME_ONE_FILE_TO_TWO;
|
2011-08-12 05:20:15 +00:00
|
|
|
clean_merge = 0;
|
2008-08-12 16:45:14 +00:00
|
|
|
} else {
|
2011-08-12 05:20:08 +00:00
|
|
|
rename_type = RENAME_ONE_FILE_TO_ONE;
|
2011-08-12 05:20:05 +00:00
|
|
|
/* BUG: We should only remove ren1_src in
|
|
|
|
* the base stage (think of rename +
|
|
|
|
* add-source cases).
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
remove_file(opt, 1, ren1_src, 1);
|
2011-08-12 05:20:02 +00:00
|
|
|
update_entry(ren1->dst_entry,
|
|
|
|
ren1->pair->one,
|
|
|
|
ren1->pair->two,
|
|
|
|
ren2->pair->two);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
2019-04-05 15:00:20 +00:00
|
|
|
setup_rename_conflict_info(rename_type, opt, ren1, ren2);
|
2011-08-12 05:20:15 +00:00
|
|
|
} else if ((lookup = string_list_lookup(renames2Dst, ren1_dst))) {
|
|
|
|
/* Two different files renamed to the same thing */
|
|
|
|
char *ren2_dst;
|
|
|
|
ren2 = lookup->util;
|
|
|
|
ren2_dst = ren2->pair->two->path;
|
|
|
|
if (strcmp(ren1_dst, ren2_dst) != 0)
|
2018-05-02 09:38:39 +00:00
|
|
|
BUG("ren1_dst != ren2_dst");
|
2011-08-12 05:20:15 +00:00
|
|
|
|
|
|
|
clean_merge = 0;
|
|
|
|
ren2->processed = 1;
|
|
|
|
/*
|
|
|
|
* BUG: We should only mark src_entry as processed
|
|
|
|
* if we are not dealing with a rename + add-source
|
|
|
|
* case.
|
|
|
|
*/
|
|
|
|
ren2->src_entry->processed = 1;
|
|
|
|
|
|
|
|
setup_rename_conflict_info(RENAME_TWO_FILES_TO_ONE,
|
2019-04-05 15:00:20 +00:00
|
|
|
opt, ren1, ren2);
|
2008-08-12 16:45:14 +00:00
|
|
|
} else {
|
|
|
|
/* Renamed in 1, maybe changed in 2 */
|
|
|
|
/* we only use sha1 and mode of these */
|
|
|
|
struct diff_filespec src_other, dst_other;
|
2010-09-20 08:28:47 +00:00
|
|
|
int try_merge;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2010-09-20 08:28:47 +00:00
|
|
|
/*
|
|
|
|
* unpack_trees loads entries from common-commit
|
|
|
|
* into stage 1, from head-commit into stage 2, and
|
|
|
|
* from merge-commit into stage 3. We keep track
|
|
|
|
* of which side corresponds to the rename.
|
|
|
|
*/
|
|
|
|
int renamed_stage = a_renames == renames1 ? 2 : 3;
|
|
|
|
int other_stage = a_renames == renames1 ? 3 : 2;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
merge-recursive: handle rename-to-self case
Directory rename detection can cause transitive renames, e.g. if the two
different sides of history each do one half of:
A/file -> B/file
B/ -> C/
then directory rename detection transitively renames to give us
A/file -> C/file
However, when C/ == A/, note that this gives us
A/file -> A/file.
merge-recursive assumed that any rename D -> E would have D != E. While
that is almost always true, the above is a special case where it is not.
So we cannot do things like delete the rename source, we cannot assume
that a file existing at path E implies a rename/add conflict and we have
to be careful about what stages end up in the output.
This change feels a bit hackish. It took me surprisingly many hours to
find, and given merge-recursive's design causing it to attempt to
enumerate all combinations of edge and corner cases with special code
for each combination, I'm worried there are other similar fixes needed
elsewhere if we can just come up with the right special testcase.
Perhaps an audit would rule it out, but I have not the energy.
merge-recursive deserves to die, and since it is on its way out anyway,
fixing this particular bug narrowly will have to be good enough.
Reported-by: Anders Kaseorg <andersk@mit.edu>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-06-30 17:30:00 +00:00
|
|
|
/*
|
|
|
|
* Directory renames have a funny corner case...
|
|
|
|
*/
|
|
|
|
int renamed_to_self = !strcmp(ren1_src, ren1_dst);
|
|
|
|
|
2011-08-12 05:20:05 +00:00
|
|
|
/* BUG: We should only remove ren1_src in the base
|
|
|
|
* stage and in other_stage (think of rename +
|
|
|
|
* add-source case).
|
|
|
|
*/
|
merge-recursive: handle rename-to-self case
Directory rename detection can cause transitive renames, e.g. if the two
different sides of history each do one half of:
A/file -> B/file
B/ -> C/
then directory rename detection transitively renames to give us
A/file -> C/file
However, when C/ == A/, note that this gives us
A/file -> A/file.
merge-recursive assumed that any rename D -> E would have D != E. While
that is almost always true, the above is a special case where it is not.
So we cannot do things like delete the rename source, we cannot assume
that a file existing at path E implies a rename/add conflict and we have
to be careful about what stages end up in the output.
This change feels a bit hackish. It took me surprisingly many hours to
find, and given merge-recursive's design causing it to attempt to
enumerate all combinations of edge and corner cases with special code
for each combination, I'm worried there are other similar fixes needed
elsewhere if we can just come up with the right special testcase.
Perhaps an audit would rule it out, but I have not the energy.
merge-recursive deserves to die, and since it is on its way out anyway,
fixing this particular bug narrowly will have to be good enough.
Reported-by: Anders Kaseorg <andersk@mit.edu>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-06-30 17:30:00 +00:00
|
|
|
if (!renamed_to_self)
|
|
|
|
remove_file(opt, 1, ren1_src,
|
|
|
|
renamed_stage == 2 ||
|
|
|
|
!was_tracked(opt, ren1_src));
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2016-06-24 23:09:25 +00:00
|
|
|
oidcpy(&src_other.oid,
|
|
|
|
&ren1->src_entry->stages[other_stage].oid);
|
2010-09-20 08:28:47 +00:00
|
|
|
src_other.mode = ren1->src_entry->stages[other_stage].mode;
|
2016-06-24 23:09:25 +00:00
|
|
|
oidcpy(&dst_other.oid,
|
|
|
|
&ren1->dst_entry->stages[other_stage].oid);
|
2010-09-20 08:28:47 +00:00
|
|
|
dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
|
2008-08-12 16:45:14 +00:00
|
|
|
try_merge = 0;
|
|
|
|
|
2021-04-26 01:02:56 +00:00
|
|
|
if (oideq(&src_other.oid, null_oid()) &&
|
2019-04-05 15:00:24 +00:00
|
|
|
ren1->dir_rename_original_type == 'A') {
|
2018-06-10 04:16:14 +00:00
|
|
|
setup_rename_conflict_info(RENAME_VIA_DIR,
|
2019-04-05 15:00:20 +00:00
|
|
|
opt, ren1, NULL);
|
merge-recursive: handle rename-to-self case
Directory rename detection can cause transitive renames, e.g. if the two
different sides of history each do one half of:
A/file -> B/file
B/ -> C/
then directory rename detection transitively renames to give us
A/file -> C/file
However, when C/ == A/, note that this gives us
A/file -> A/file.
merge-recursive assumed that any rename D -> E would have D != E. While
that is almost always true, the above is a special case where it is not.
So we cannot do things like delete the rename source, we cannot assume
that a file existing at path E implies a rename/add conflict and we have
to be careful about what stages end up in the output.
This change feels a bit hackish. It took me surprisingly many hours to
find, and given merge-recursive's design causing it to attempt to
enumerate all combinations of edge and corner cases with special code
for each combination, I'm worried there are other similar fixes needed
elsewhere if we can just come up with the right special testcase.
Perhaps an audit would rule it out, but I have not the energy.
merge-recursive deserves to die, and since it is on its way out anyway,
fixing this particular bug narrowly will have to be good enough.
Reported-by: Anders Kaseorg <andersk@mit.edu>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-06-30 17:30:00 +00:00
|
|
|
} else if (renamed_to_self) {
|
|
|
|
setup_rename_conflict_info(RENAME_NORMAL,
|
|
|
|
opt, ren1, NULL);
|
2021-04-26 01:02:56 +00:00
|
|
|
} else if (oideq(&src_other.oid, null_oid())) {
|
2011-08-12 05:20:08 +00:00
|
|
|
setup_rename_conflict_info(RENAME_DELETE,
|
2019-04-05 15:00:20 +00:00
|
|
|
opt, ren1, NULL);
|
2010-09-01 20:15:32 +00:00
|
|
|
} else if ((dst_other.mode == ren1->pair->two->mode) &&
|
merge-recursive: remove unnecessary oid_eq function
Back when merge-recursive was first introduced in commit 6d297f8137
(Status update on merge-recursive in C, 2006-07-08), it created a
sha_eq() function. This function pre-dated the introduction of
hashcmp() to cache.h by about a month, but was switched over to using
hashcmp() as part of commit 9047ebbc22 (Split out merge_recursive() to
merge-recursive.c, 2008-08-12). In commit b4da9d62f9 (merge-recursive:
convert leaf functions to use struct object_id, 2016-06-24), sha_eq() was
renamed to oid_eq() and its hashcmp() call was switched to oideq().
oid_eq() is basically just a wrapper around oideq() that has some extra
checks to protect against NULL arguments or to allow short-circuiting if
one of the arguments is NULL. I don't know if any caller ever tried to
call with NULL arguments, but certainly none do now which means the
extra checks serve no purpose. (Also, if these checks were genuinely
useful, then they probably should be added to the main oideq() so all
callers could benefit from them.)
Reduce the cognitive overhead of having both oid_eq() and oideq(), by
getting rid of merge-recursive's special oid_eq() wrapper.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-01 05:20:57 +00:00
|
|
|
oideq(&dst_other.oid, &ren1->pair->two->oid)) {
|
2011-08-12 05:20:27 +00:00
|
|
|
/*
|
|
|
|
* Added file on the other side identical to
|
|
|
|
* the file being renamed: clean merge.
|
|
|
|
* Also, there is no need to overwrite the
|
|
|
|
* file already in the working copy, so call
|
|
|
|
* update_file_flags() instead of
|
|
|
|
* update_file().
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
if (update_file_flags(opt,
|
2019-04-05 15:00:22 +00:00
|
|
|
ren1->pair->two,
|
2016-07-26 16:06:21 +00:00
|
|
|
ren1_dst,
|
|
|
|
1, /* update_cache */
|
|
|
|
0 /* update_wd */))
|
|
|
|
clean_merge = -1;
|
2021-04-26 01:02:56 +00:00
|
|
|
} else if (!oideq(&dst_other.oid, null_oid())) {
|
2018-11-08 04:40:26 +00:00
|
|
|
/*
|
|
|
|
* Probably not a clean merge, but it's
|
|
|
|
* premature to set clean_merge to 0 here,
|
|
|
|
* because if the rename merges cleanly and
|
|
|
|
* the merge exactly matches the newly added
|
|
|
|
* file, then the merge will be clean.
|
|
|
|
*/
|
|
|
|
setup_rename_conflict_info(RENAME_ADD,
|
2019-04-05 15:00:20 +00:00
|
|
|
opt, ren1, NULL);
|
2008-08-12 16:45:14 +00:00
|
|
|
} else
|
|
|
|
try_merge = 1;
|
|
|
|
|
2016-07-26 16:06:21 +00:00
|
|
|
if (clean_merge < 0)
|
|
|
|
goto cleanup_and_return;
|
2008-08-12 16:45:14 +00:00
|
|
|
if (try_merge) {
|
2019-04-05 15:00:14 +00:00
|
|
|
struct diff_filespec *o, *a, *b;
|
2008-08-12 16:45:14 +00:00
|
|
|
src_other.path = (char *)ren1_src;
|
|
|
|
|
2019-04-05 15:00:14 +00:00
|
|
|
o = ren1->pair->one;
|
2008-08-12 16:45:14 +00:00
|
|
|
if (a_renames == renames1) {
|
|
|
|
a = ren1->pair->two;
|
|
|
|
b = &src_other;
|
|
|
|
} else {
|
|
|
|
b = ren1->pair->two;
|
|
|
|
a = &src_other;
|
|
|
|
}
|
2019-04-05 15:00:14 +00:00
|
|
|
update_entry(ren1->dst_entry, o, a, b);
|
2011-08-12 05:20:08 +00:00
|
|
|
setup_rename_conflict_info(RENAME_NORMAL,
|
2019-04-05 15:00:20 +00:00
|
|
|
opt, ren1, NULL);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-07-26 16:06:21 +00:00
|
|
|
cleanup_and_return:
|
2008-08-12 16:45:14 +00:00
|
|
|
string_list_clear(&a_by_dst, 0);
|
|
|
|
string_list_clear(&b_by_dst, 0);
|
|
|
|
|
|
|
|
return clean_merge;
|
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:00 +00:00
|
|
|
struct rename_info {
|
|
|
|
struct string_list *head_renames;
|
|
|
|
struct string_list *merge_renames;
|
|
|
|
};
|
|
|
|
|
2018-04-19 17:58:05 +00:00
|
|
|
static void initial_cleanup_rename(struct diff_queue_struct *pairs,
|
|
|
|
struct hashmap *dir_renames)
|
2018-04-19 17:58:04 +00:00
|
|
|
{
|
2018-04-19 17:58:05 +00:00
|
|
|
struct hashmap_iter iter;
|
|
|
|
struct dir_rename_entry *e;
|
|
|
|
|
2019-10-06 23:30:38 +00:00
|
|
|
hashmap_for_each_entry(dir_renames, &iter, e,
|
|
|
|
ent /* member name */) {
|
2018-04-19 17:58:05 +00:00
|
|
|
free(e->dir);
|
|
|
|
strbuf_release(&e->new_dir);
|
|
|
|
/* possible_new_dirs already cleared in get_directory_renames */
|
|
|
|
}
|
2020-11-02 18:55:05 +00:00
|
|
|
hashmap_clear_and_free(dir_renames, struct dir_rename_entry, ent);
|
2018-04-19 17:58:05 +00:00
|
|
|
free(dir_renames);
|
|
|
|
|
2018-04-19 17:58:04 +00:00
|
|
|
free(pairs->queue);
|
|
|
|
free(pairs);
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int detect_and_process_renames(struct merge_options *opt,
|
2018-06-10 04:16:15 +00:00
|
|
|
struct tree *common,
|
|
|
|
struct tree *head,
|
|
|
|
struct tree *merge,
|
|
|
|
struct string_list *entries,
|
|
|
|
struct rename_info *ri)
|
2018-04-19 17:58:00 +00:00
|
|
|
{
|
2018-04-19 17:58:03 +00:00
|
|
|
struct diff_queue_struct *head_pairs, *merge_pairs;
|
2018-04-19 17:58:05 +00:00
|
|
|
struct hashmap *dir_re_head, *dir_re_merge;
|
2018-04-19 17:58:08 +00:00
|
|
|
int clean = 1;
|
2018-04-19 17:58:03 +00:00
|
|
|
|
2018-04-19 17:58:02 +00:00
|
|
|
ri->head_renames = NULL;
|
|
|
|
ri->merge_renames = NULL;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (!merge_detect_rename(opt))
|
2018-04-19 17:58:02 +00:00
|
|
|
return 1;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
head_pairs = get_diffpairs(opt, common, head);
|
|
|
|
merge_pairs = get_diffpairs(opt, common, merge);
|
2018-04-19 17:58:03 +00:00
|
|
|
|
2019-08-17 18:41:25 +00:00
|
|
|
if ((opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) ||
|
|
|
|
(opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&
|
2019-08-17 18:41:41 +00:00
|
|
|
!opt->priv->call_depth)) {
|
2019-02-14 05:50:02 +00:00
|
|
|
dir_re_head = get_directory_renames(head_pairs);
|
|
|
|
dir_re_merge = get_directory_renames(merge_pairs);
|
2018-04-19 17:58:05 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
handle_directory_level_conflicts(opt,
|
2018-08-29 07:06:12 +00:00
|
|
|
dir_re_head, head,
|
|
|
|
dir_re_merge, merge);
|
|
|
|
} else {
|
|
|
|
dir_re_head = xmalloc(sizeof(*dir_re_head));
|
|
|
|
dir_re_merge = xmalloc(sizeof(*dir_re_merge));
|
|
|
|
dir_rename_init(dir_re_head);
|
|
|
|
dir_rename_init(dir_re_merge);
|
|
|
|
}
|
2018-04-19 17:58:06 +00:00
|
|
|
|
2019-04-05 15:00:20 +00:00
|
|
|
ri->head_renames = get_renames(opt, opt->branch1, head_pairs,
|
2018-04-19 17:58:08 +00:00
|
|
|
dir_re_merge, dir_re_head, head,
|
|
|
|
common, head, merge, entries,
|
|
|
|
&clean);
|
|
|
|
if (clean < 0)
|
|
|
|
goto cleanup;
|
2019-04-05 15:00:20 +00:00
|
|
|
ri->merge_renames = get_renames(opt, opt->branch2, merge_pairs,
|
2018-04-19 17:58:08 +00:00
|
|
|
dir_re_head, dir_re_merge, merge,
|
|
|
|
common, head, merge, entries,
|
|
|
|
&clean);
|
|
|
|
if (clean < 0)
|
|
|
|
goto cleanup;
|
2019-04-05 15:00:13 +00:00
|
|
|
clean &= process_renames(opt, ri->head_renames, ri->merge_renames);
|
2018-04-19 17:58:08 +00:00
|
|
|
|
|
|
|
cleanup:
|
2018-04-19 17:58:03 +00:00
|
|
|
/*
|
|
|
|
* Some cleanup is deferred until cleanup_renames() because the
|
|
|
|
* data structures are still needed and referenced in
|
|
|
|
* process_entry(). But there are a few things we can free now.
|
|
|
|
*/
|
2018-04-19 17:58:05 +00:00
|
|
|
initial_cleanup_rename(head_pairs, dir_re_head);
|
|
|
|
initial_cleanup_rename(merge_pairs, dir_re_merge);
|
2018-04-19 17:58:03 +00:00
|
|
|
|
|
|
|
return clean;
|
2018-04-19 17:58:00 +00:00
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:04 +00:00
|
|
|
static void final_cleanup_rename(struct string_list *rename)
|
2018-04-19 17:58:00 +00:00
|
|
|
{
|
2018-04-19 17:58:01 +00:00
|
|
|
const struct rename *re;
|
|
|
|
int i;
|
2018-04-19 17:58:00 +00:00
|
|
|
|
2022-05-02 16:50:37 +00:00
|
|
|
if (!rename)
|
2018-04-19 17:58:02 +00:00
|
|
|
return;
|
|
|
|
|
2018-04-19 17:58:01 +00:00
|
|
|
for (i = 0; i < rename->nr; i++) {
|
|
|
|
re = rename->items[i].util;
|
|
|
|
diff_free_filepair(re->pair);
|
|
|
|
}
|
|
|
|
string_list_clear(rename, 1);
|
|
|
|
free(rename);
|
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:04 +00:00
|
|
|
static void final_cleanup_renames(struct rename_info *re_info)
|
2018-04-19 17:58:01 +00:00
|
|
|
{
|
2018-04-19 17:58:04 +00:00
|
|
|
final_cleanup_rename(re_info->head_renames);
|
|
|
|
final_cleanup_rename(re_info->merge_renames);
|
2018-04-19 17:58:00 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int read_oid_strbuf(struct merge_options *opt,
|
2018-06-10 04:16:12 +00:00
|
|
|
const struct object_id *oid,
|
|
|
|
struct strbuf *dst)
|
2010-07-02 19:20:48 +00:00
|
|
|
{
|
|
|
|
void *buf;
|
|
|
|
enum object_type type;
|
|
|
|
unsigned long size;
|
2023-03-28 13:58:50 +00:00
|
|
|
buf = repo_read_object_file(the_repository, oid, &type, &size);
|
2010-07-02 19:20:48 +00:00
|
|
|
if (!buf)
|
2019-04-05 15:00:13 +00:00
|
|
|
return err(opt, _("cannot read object %s"), oid_to_hex(oid));
|
2010-07-02 19:20:48 +00:00
|
|
|
if (type != OBJ_BLOB) {
|
|
|
|
free(buf);
|
2019-04-05 15:00:13 +00:00
|
|
|
return err(opt, _("object %s is not a blob"), oid_to_hex(oid));
|
2010-07-02 19:20:48 +00:00
|
|
|
}
|
|
|
|
strbuf_attach(dst, buf, size, size + 1);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-08-01 11:44:37 +00:00
|
|
|
static int blob_unchanged(struct merge_options *opt,
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *o,
|
|
|
|
const struct diff_filespec *a,
|
2010-08-05 11:13:49 +00:00
|
|
|
int renormalize, const char *path)
|
2010-07-02 19:20:48 +00:00
|
|
|
{
|
2019-04-05 15:00:15 +00:00
|
|
|
struct strbuf obuf = STRBUF_INIT;
|
|
|
|
struct strbuf abuf = STRBUF_INIT;
|
2010-07-02 19:20:48 +00:00
|
|
|
int ret = 0; /* assume changed for safety */
|
2021-04-01 01:49:39 +00:00
|
|
|
struct index_state *idx = opt->repo->index;
|
2010-07-02 19:20:48 +00:00
|
|
|
|
2019-04-05 15:00:22 +00:00
|
|
|
if (a->mode != o->mode)
|
2015-10-26 21:39:39 +00:00
|
|
|
return 0;
|
merge-recursive: remove unnecessary oid_eq function
Back when merge-recursive was first introduced in commit 6d297f8137
(Status update on merge-recursive in C, 2006-07-08), it created a
sha_eq() function. This function pre-dated the introduction of
hashcmp() to cache.h by about a month, but was switched over to using
hashcmp() as part of commit 9047ebbc22 (Split out merge_recursive() to
merge-recursive.c, 2008-08-12). In commit b4da9d62f9 (merge-recursive:
convert leaf functions to use struct object_id, 2016-06-24), sha_eq() was
renamed to oid_eq() and its hashcmp() call was switched to oideq().
oid_eq() is basically just a wrapper around oideq() that has some extra
checks to protect against NULL arguments or to allow short-circuiting if
one of the arguments is NULL. I don't know if any caller ever tried to
call with NULL arguments, but certainly none do now which means the
extra checks serve no purpose. (Also, if these checks were genuinely
useful, then they probably should be added to the main oideq() so all
callers could benefit from them.)
Reduce the cognitive overhead of having both oid_eq() and oideq(), by
getting rid of merge-recursive's special oid_eq() wrapper.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-01 05:20:57 +00:00
|
|
|
if (oideq(&o->oid, &a->oid))
|
2010-07-02 19:20:48 +00:00
|
|
|
return 1;
|
2010-08-05 11:13:49 +00:00
|
|
|
if (!renormalize)
|
2010-07-02 19:20:48 +00:00
|
|
|
return 0;
|
|
|
|
|
2019-04-05 15:00:22 +00:00
|
|
|
if (read_oid_strbuf(opt, &o->oid, &obuf) ||
|
|
|
|
read_oid_strbuf(opt, &a->oid, &abuf))
|
2010-07-02 19:20:48 +00:00
|
|
|
goto error_return;
|
|
|
|
/*
|
|
|
|
* Note: binary | is used so that both renormalizations are
|
|
|
|
* performed. Comparison can be skipped if both files are
|
|
|
|
* unchanged since their sha1s have already been compared.
|
|
|
|
*/
|
2019-04-05 15:00:15 +00:00
|
|
|
if (renormalize_buffer(idx, path, obuf.buf, obuf.len, &obuf) |
|
|
|
|
renormalize_buffer(idx, path, abuf.buf, abuf.len, &abuf))
|
|
|
|
ret = (obuf.len == abuf.len && !memcmp(obuf.buf, abuf.buf, obuf.len));
|
2010-07-02 19:20:48 +00:00
|
|
|
|
|
|
|
error_return:
|
2019-04-05 15:00:15 +00:00
|
|
|
strbuf_release(&obuf);
|
|
|
|
strbuf_release(&abuf);
|
2010-07-02 19:20:48 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int handle_modify_delete(struct merge_options *opt,
|
2018-06-10 04:16:12 +00:00
|
|
|
const char *path,
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *o,
|
|
|
|
const struct diff_filespec *a,
|
|
|
|
const struct diff_filespec *b)
|
2010-09-20 08:28:51 +00:00
|
|
|
{
|
2017-01-28 20:37:01 +00:00
|
|
|
const char *modify_branch, *delete_branch;
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *changed;
|
2017-01-28 20:37:01 +00:00
|
|
|
|
2019-04-05 15:00:22 +00:00
|
|
|
if (is_valid(a)) {
|
2019-04-05 15:00:13 +00:00
|
|
|
modify_branch = opt->branch1;
|
|
|
|
delete_branch = opt->branch2;
|
2019-04-05 15:00:22 +00:00
|
|
|
changed = a;
|
2017-01-28 20:37:01 +00:00
|
|
|
} else {
|
2019-04-05 15:00:13 +00:00
|
|
|
modify_branch = opt->branch2;
|
|
|
|
delete_branch = opt->branch1;
|
2019-04-05 15:00:22 +00:00
|
|
|
changed = b;
|
2017-01-28 20:37:01 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
return handle_change_delete(opt,
|
2017-01-28 20:37:01 +00:00
|
|
|
path, NULL,
|
2019-04-05 15:00:22 +00:00
|
|
|
o, changed,
|
2017-01-28 20:37:01 +00:00
|
|
|
modify_branch, delete_branch,
|
2016-07-26 16:06:21 +00:00
|
|
|
_("modify"), _("modified"));
|
2010-09-20 08:28:51 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:25 +00:00
|
|
|
static int handle_content_merge(struct merge_file_info *mfi,
|
|
|
|
struct merge_options *opt,
|
2018-09-19 16:14:34 +00:00
|
|
|
const char *path,
|
|
|
|
int is_dirty,
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *o,
|
|
|
|
const struct diff_filespec *a,
|
|
|
|
const struct diff_filespec *b,
|
2019-04-05 15:00:16 +00:00
|
|
|
struct rename_conflict_info *ci)
|
2010-09-20 08:28:52 +00:00
|
|
|
{
|
2012-07-25 14:53:13 +00:00
|
|
|
const char *reason = _("content");
|
2010-09-20 08:29:07 +00:00
|
|
|
unsigned df_conflict_remains = 0;
|
2010-09-20 08:28:52 +00:00
|
|
|
|
2019-04-05 15:00:22 +00:00
|
|
|
if (!is_valid(o))
|
2012-07-25 14:53:13 +00:00
|
|
|
reason = _("add/add");
|
2019-04-05 15:00:22 +00:00
|
|
|
|
|
|
|
assert(o->path && a->path && b->path);
|
2019-08-17 18:41:41 +00:00
|
|
|
if (ci && dir_in_way(opt->repo->index, path, !opt->priv->call_depth,
|
2019-04-05 15:00:22 +00:00
|
|
|
S_ISGITLINK(ci->ren1->pair->two->mode)))
|
|
|
|
df_conflict_remains = 1;
|
|
|
|
|
|
|
|
if (merge_mode_and_contents(opt, o, a, b, path,
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->branch1, opt->branch2,
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->call_depth * 2, mfi))
|
2016-07-26 16:06:10 +00:00
|
|
|
return -1;
|
2010-09-20 08:29:07 +00:00
|
|
|
|
merge-recursive: fix check for skipability of working tree updates
The can-working-tree-updates-be-skipped check has had a long and blemished
history. The update can be skipped iff:
a) The merge is clean
b) The merge matches what was in HEAD (content, mode, pathname)
c) The target path is usable (i.e. not involved in D/F conflict)
Traditionally, we split b into parts:
b1) The merged result matches the content and mode found in HEAD
b2) The merged target path existed in HEAD
Steps a & b1 are easy to check; we have always gotten those right. While
it is easy to overlook step c, this was fixed seven years ago with commit
4ab9a157d069 ("merge_content(): Check whether D/F conflicts are still
present", 2010-09-20). merge-recursive didn't have a readily available
way to directly check step b2, so various approximations were used:
* In commit b2c8c0a76274 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-02-28), it was noted that although
the code claimed it was skipping the update, it did not actually skip
the update. The code was made to skip it, but used lstat(path, ...)
as an approximation to path-was-tracked-in-index-before-merge.
* In commit 5b448b853030 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-08-11), the problem with using
lstat was noted. It was changed to the approximation
path2 && strcmp(path, path2)
which is also wrong. !path2 || strcmp(path, path2) would have been
better, but would have fallen short with directory renames.
* In c5b761fb2711 ("merge-recursive: ensure we write updates for
directory-renamed file", 2018-02-14), the problem with the previous
approximation was noted and changed to
was_tracked(path)
That looks close to what we were trying to answer, but was_tracked()
as implemented at the time should have been named is_tracked(); it
returned something different than what we were looking for.
* To make matters more complex, fixing was_tracked() isn't sufficient
because the splitting of b into b1 and b2 is wrong. Consider the
following merge with a rename/add conflict:
side A: modify foo, add unrelated bar
side B: rename foo->bar (but don't modify the mode or contents)
In this case, the three-way merge of original foo, A's foo, and B's
bar will result in a desired pathname of bar with the same
mode/contents that A had for foo. Thus, A had the right mode and
contents for the file, and it had the right pathname present (namely,
bar), but the bar that was present was unrelated to the contents, so
the working tree update was not skippable.
Fix this by introducing a new function:
was_tracked_and_matches(o, path, &mfi.oid, mfi.mode)
and use it to directly check for condition b.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-19 17:58:23 +00:00
|
|
|
/*
|
|
|
|
* We can skip updating the working tree file iff:
|
|
|
|
* a) The merge is clean
|
|
|
|
* b) The merge matches what was in HEAD (content, mode, pathname)
|
|
|
|
* c) The target path is usable (i.e. not involved in D/F conflict)
|
|
|
|
*/
|
2019-04-05 15:00:25 +00:00
|
|
|
if (mfi->clean && was_tracked_and_matches(opt, path, &mfi->blob) &&
|
merge-recursive: fix check for skipability of working tree updates
The can-working-tree-updates-be-skipped check has had a long and blemished
history. The update can be skipped iff:
a) The merge is clean
b) The merge matches what was in HEAD (content, mode, pathname)
c) The target path is usable (i.e. not involved in D/F conflict)
Traditionally, we split b into parts:
b1) The merged result matches the content and mode found in HEAD
b2) The merged target path existed in HEAD
Steps a & b1 are easy to check; we have always gotten those right. While
it is easy to overlook step c, this was fixed seven years ago with commit
4ab9a157d069 ("merge_content(): Check whether D/F conflicts are still
present", 2010-09-20). merge-recursive didn't have a readily available
way to directly check step b2, so various approximations were used:
* In commit b2c8c0a76274 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-02-28), it was noted that although
the code claimed it was skipping the update, it did not actually skip
the update. The code was made to skip it, but used lstat(path, ...)
as an approximation to path-was-tracked-in-index-before-merge.
* In commit 5b448b853030 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-08-11), the problem with using
lstat was noted. It was changed to the approximation
path2 && strcmp(path, path2)
which is also wrong. !path2 || strcmp(path, path2) would have been
better, but would have fallen short with directory renames.
* In c5b761fb2711 ("merge-recursive: ensure we write updates for
directory-renamed file", 2018-02-14), the problem with the previous
approximation was noted and changed to
was_tracked(path)
That looks close to what we were trying to answer, but was_tracked()
as implemented at the time should have been named is_tracked(); it
returned something different than what we were looking for.
* To make matters more complex, fixing was_tracked() isn't sufficient
because the splitting of b into b1 and b2 is wrong. Consider the
following merge with a rename/add conflict:
side A: modify foo, add unrelated bar
side B: rename foo->bar (but don't modify the mode or contents)
In this case, the three-way merge of original foo, A's foo, and B's
bar will result in a desired pathname of bar with the same
mode/contents that A had for foo. Thus, A had the right mode and
contents for the file, and it had the right pathname present (namely,
bar), but the bar that was present was unrelated to the contents, so
the working tree update was not skippable.
Fix this by introducing a new function:
was_tracked_and_matches(o, path, &mfi.oid, mfi.mode)
and use it to directly check for condition b.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-19 17:58:23 +00:00
|
|
|
!df_conflict_remains) {
|
2018-07-27 12:59:44 +00:00
|
|
|
int pos;
|
|
|
|
struct cache_entry *ce;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 3, _("Skipped %s (merged same as existing)"), path);
|
2019-04-05 15:00:25 +00:00
|
|
|
if (add_cacheinfo(opt, &mfi->blob, path,
|
2019-08-17 18:41:41 +00:00
|
|
|
0, (!opt->priv->call_depth && !is_dirty), 0))
|
merge-recursive: fix check for skipability of working tree updates
The can-working-tree-updates-be-skipped check has had a long and blemished
history. The update can be skipped iff:
a) The merge is clean
b) The merge matches what was in HEAD (content, mode, pathname)
c) The target path is usable (i.e. not involved in D/F conflict)
Traditionally, we split b into parts:
b1) The merged result matches the content and mode found in HEAD
b2) The merged target path existed in HEAD
Steps a & b1 are easy to check; we have always gotten those right. While
it is easy to overlook step c, this was fixed seven years ago with commit
4ab9a157d069 ("merge_content(): Check whether D/F conflicts are still
present", 2010-09-20). merge-recursive didn't have a readily available
way to directly check step b2, so various approximations were used:
* In commit b2c8c0a76274 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-02-28), it was noted that although
the code claimed it was skipping the update, it did not actually skip
the update. The code was made to skip it, but used lstat(path, ...)
as an approximation to path-was-tracked-in-index-before-merge.
* In commit 5b448b853030 ("merge-recursive: When we detect we can skip
an update, actually skip it", 2011-08-11), the problem with using
lstat was noted. It was changed to the approximation
path2 && strcmp(path, path2)
which is also wrong. !path2 || strcmp(path, path2) would have been
better, but would have fallen short with directory renames.
* In c5b761fb2711 ("merge-recursive: ensure we write updates for
directory-renamed file", 2018-02-14), the problem with the previous
approximation was noted and changed to
was_tracked(path)
That looks close to what we were trying to answer, but was_tracked()
as implemented at the time should have been named is_tracked(); it
returned something different than what we were looking for.
* To make matters more complex, fixing was_tracked() isn't sufficient
because the splitting of b into b1 and b2 is wrong. Consider the
following merge with a rename/add conflict:
side A: modify foo, add unrelated bar
side B: rename foo->bar (but don't modify the mode or contents)
In this case, the three-way merge of original foo, A's foo, and B's
bar will result in a desired pathname of bar with the same
mode/contents that A had for foo. Thus, A had the right mode and
contents for the file, and it had the right pathname present (namely,
bar), but the bar that was present was unrelated to the contents, so
the working tree update was not skippable.
Fix this by introducing a new function:
was_tracked_and_matches(o, path, &mfi.oid, mfi.mode)
and use it to directly check for condition b.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-19 17:58:23 +00:00
|
|
|
return -1;
|
2018-07-27 12:59:44 +00:00
|
|
|
/*
|
|
|
|
* However, add_cacheinfo() will delete the old cache entry
|
|
|
|
* and add a new one. We need to copy over any skip_worktree
|
|
|
|
* flag to avoid making the file appear as if it were
|
|
|
|
* deleted by the user.
|
|
|
|
*/
|
2019-08-17 18:41:41 +00:00
|
|
|
pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));
|
|
|
|
ce = opt->priv->orig_index.cache[pos];
|
2018-07-27 12:59:44 +00:00
|
|
|
if (ce_skip_worktree(ce)) {
|
2019-04-05 15:00:13 +00:00
|
|
|
pos = index_name_pos(opt->repo->index, path, strlen(path));
|
|
|
|
ce = opt->repo->index->cache[pos];
|
2018-07-27 12:59:44 +00:00
|
|
|
ce->ce_flags |= CE_SKIP_WORKTREE;
|
|
|
|
}
|
2019-04-05 15:00:25 +00:00
|
|
|
return mfi->clean;
|
2018-04-19 17:58:22 +00:00
|
|
|
}
|
2010-09-20 08:28:52 +00:00
|
|
|
|
2019-04-05 15:00:25 +00:00
|
|
|
if (!mfi->clean) {
|
|
|
|
if (S_ISGITLINK(mfi->blob.mode))
|
2012-07-25 14:53:13 +00:00
|
|
|
reason = _("submodule");
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (%s): Merge conflict in %s"),
|
2010-09-20 08:28:52 +00:00
|
|
|
reason, path);
|
2019-04-05 15:00:16 +00:00
|
|
|
if (ci && !df_conflict_remains)
|
2019-04-05 15:00:22 +00:00
|
|
|
if (update_stages(opt, path, o, a, b))
|
2016-07-26 16:06:21 +00:00
|
|
|
return -1;
|
2010-09-20 08:28:52 +00:00
|
|
|
}
|
|
|
|
|
2018-04-19 17:58:17 +00:00
|
|
|
if (df_conflict_remains || is_dirty) {
|
2011-08-12 05:19:53 +00:00
|
|
|
char *new_path;
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opt->priv->call_depth) {
|
2019-04-05 15:00:13 +00:00
|
|
|
remove_file_from_index(opt->repo->index, path);
|
2011-08-12 05:20:06 +00:00
|
|
|
} else {
|
2019-04-05 15:00:25 +00:00
|
|
|
if (!mfi->clean) {
|
2019-04-05 15:00:22 +00:00
|
|
|
if (update_stages(opt, path, o, a, b))
|
2016-07-26 16:06:21 +00:00
|
|
|
return -1;
|
|
|
|
} else {
|
2019-04-05 15:00:13 +00:00
|
|
|
int file_from_stage2 = was_tracked(opt, path);
|
2011-08-12 05:20:06 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (update_stages(opt, path, NULL,
|
2019-04-05 15:00:25 +00:00
|
|
|
file_from_stage2 ? &mfi->blob : NULL,
|
|
|
|
file_from_stage2 ? NULL : &mfi->blob))
|
2016-07-26 16:06:21 +00:00
|
|
|
return -1;
|
2011-08-12 05:20:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-04-05 15:00:20 +00:00
|
|
|
new_path = unique_path(opt, path, ci->ren1->branch);
|
2018-04-19 17:58:17 +00:00
|
|
|
if (is_dirty) {
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("Refusing to lose dirty file at %s"),
|
2018-04-19 17:58:17 +00:00
|
|
|
path);
|
|
|
|
}
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("Adding as %s instead"), new_path);
|
2019-04-05 15:00:25 +00:00
|
|
|
if (update_file(opt, 0, &mfi->blob, new_path)) {
|
2016-07-26 16:06:21 +00:00
|
|
|
free(new_path);
|
|
|
|
return -1;
|
|
|
|
}
|
2011-08-12 05:19:53 +00:00
|
|
|
free(new_path);
|
2019-04-05 15:00:25 +00:00
|
|
|
mfi->clean = 0;
|
|
|
|
} else if (update_file(opt, mfi->clean, &mfi->blob, path))
|
2016-07-26 16:06:21 +00:00
|
|
|
return -1;
|
2019-04-05 15:00:25 +00:00
|
|
|
return !is_dirty && mfi->clean;
|
2010-09-20 08:28:52 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static int handle_rename_normal(struct merge_options *opt,
|
2018-06-10 04:16:15 +00:00
|
|
|
const char *path,
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *o,
|
|
|
|
const struct diff_filespec *a,
|
|
|
|
const struct diff_filespec *b,
|
2018-06-10 04:16:15 +00:00
|
|
|
struct rename_conflict_info *ci)
|
2018-04-19 17:58:12 +00:00
|
|
|
{
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
struct rename *ren = ci->ren1;
|
2019-04-05 15:00:25 +00:00
|
|
|
struct merge_file_info mfi;
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
int clean;
|
|
|
|
|
2018-04-19 17:58:12 +00:00
|
|
|
/* Merge the content and write it out */
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
clean = handle_content_merge(&mfi, opt, path, was_dirty(opt, path),
|
|
|
|
o, a, b, ci);
|
|
|
|
|
2019-08-17 18:41:25 +00:00
|
|
|
if (clean &&
|
|
|
|
opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
ren->dir_rename_original_dest) {
|
|
|
|
if (update_stages(opt, path,
|
merge-recursive: handle rename-to-self case
Directory rename detection can cause transitive renames, e.g. if the two
different sides of history each do one half of:
A/file -> B/file
B/ -> C/
then directory rename detection transitively renames to give us
A/file -> C/file
However, when C/ == A/, note that this gives us
A/file -> A/file.
merge-recursive assumed that any rename D -> E would have D != E. While
that is almost always true, the above is a special case where it is not.
So we cannot do things like delete the rename source, we cannot assume
that a file existing at path E implies a rename/add conflict and we have
to be careful about what stages end up in the output.
This change feels a bit hackish. It took me surprisingly many hours to
find, and given merge-recursive's design causing it to attempt to
enumerate all combinations of edge and corner cases with special code
for each combination, I'm worried there are other similar fixes needed
elsewhere if we can just come up with the right special testcase.
Perhaps an audit would rule it out, but I have not the energy.
merge-recursive deserves to die, and since it is on its way out anyway,
fixing this particular bug narrowly will have to be good enough.
Reported-by: Anders Kaseorg <andersk@mit.edu>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-06-30 17:30:00 +00:00
|
|
|
&mfi.blob, &mfi.blob, &mfi.blob))
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
return -1;
|
|
|
|
clean = 0; /* not clean, but conflicted */
|
|
|
|
}
|
|
|
|
return clean;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dir_rename_warning(const char *msg,
|
|
|
|
int is_add,
|
|
|
|
int clean,
|
|
|
|
struct merge_options *opt,
|
|
|
|
struct rename *ren)
|
|
|
|
{
|
|
|
|
const char *other_branch;
|
|
|
|
other_branch = (ren->branch == opt->branch1 ?
|
|
|
|
opt->branch2 : opt->branch1);
|
|
|
|
if (is_add) {
|
|
|
|
output(opt, clean ? 2 : 1, msg,
|
|
|
|
ren->pair->one->path, ren->branch,
|
|
|
|
other_branch, ren->pair->two->path);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
output(opt, clean ? 2 : 1, msg,
|
|
|
|
ren->pair->one->path, ren->dir_rename_original_dest, ren->branch,
|
|
|
|
other_branch, ren->pair->two->path);
|
|
|
|
}
|
|
|
|
static int warn_about_dir_renamed_entries(struct merge_options *opt,
|
|
|
|
struct rename *ren)
|
|
|
|
{
|
|
|
|
const char *msg;
|
|
|
|
int clean = 1, is_add;
|
|
|
|
|
|
|
|
if (!ren)
|
|
|
|
return clean;
|
|
|
|
|
|
|
|
/* Return early if ren was not affected/created by a directory rename */
|
|
|
|
if (!ren->dir_rename_original_dest)
|
|
|
|
return clean;
|
|
|
|
|
|
|
|
/* Sanity checks */
|
2019-08-17 18:41:25 +00:00
|
|
|
assert(opt->detect_directory_renames > MERGE_DIRECTORY_RENAMES_NONE);
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
assert(ren->dir_rename_original_type == 'A' ||
|
|
|
|
ren->dir_rename_original_type == 'R');
|
|
|
|
|
|
|
|
/* Check whether to treat directory renames as a conflict */
|
2019-08-17 18:41:25 +00:00
|
|
|
clean = (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE);
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
|
|
|
|
is_add = (ren->dir_rename_original_type == 'A');
|
|
|
|
if (ren->dir_rename_original_type == 'A' && clean) {
|
|
|
|
msg = _("Path updated: %s added in %s inside a "
|
|
|
|
"directory that was renamed in %s; moving it to %s.");
|
|
|
|
} else if (ren->dir_rename_original_type == 'A' && !clean) {
|
|
|
|
msg = _("CONFLICT (file location): %s added in %s "
|
|
|
|
"inside a directory that was renamed in %s, "
|
|
|
|
"suggesting it should perhaps be moved to %s.");
|
|
|
|
} else if (ren->dir_rename_original_type == 'R' && clean) {
|
|
|
|
msg = _("Path updated: %s renamed to %s in %s, inside a "
|
|
|
|
"directory that was renamed in %s; moving it to %s.");
|
|
|
|
} else if (ren->dir_rename_original_type == 'R' && !clean) {
|
|
|
|
msg = _("CONFLICT (file location): %s renamed to %s in %s, "
|
|
|
|
"inside a directory that was renamed in %s, "
|
|
|
|
"suggesting it should perhaps be moved to %s.");
|
|
|
|
} else {
|
|
|
|
BUG("Impossible dir_rename_original_type/clean combination");
|
|
|
|
}
|
|
|
|
dir_rename_warning(msg, is_add, clean, opt, ren);
|
|
|
|
|
|
|
|
return clean;
|
2010-09-20 08:28:52 +00:00
|
|
|
}
|
|
|
|
|
2008-08-12 16:45:14 +00:00
|
|
|
/* Per entry merge function */
|
2019-04-05 15:00:13 +00:00
|
|
|
static int process_entry(struct merge_options *opt,
|
2008-08-25 14:25:57 +00:00
|
|
|
const char *path, struct stage_data *entry)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
|
|
|
int clean_merge = 1;
|
2019-04-05 15:00:13 +00:00
|
|
|
int normalize = opt->renormalize;
|
2019-04-05 15:00:22 +00:00
|
|
|
|
|
|
|
struct diff_filespec *o = &entry->stages[1];
|
|
|
|
struct diff_filespec *a = &entry->stages[2];
|
|
|
|
struct diff_filespec *b = &entry->stages[3];
|
|
|
|
int o_valid = is_valid(o);
|
|
|
|
int a_valid = is_valid(a);
|
|
|
|
int b_valid = is_valid(b);
|
|
|
|
o->path = a->path = b->path = (char*)path;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2010-07-09 13:10:53 +00:00
|
|
|
entry->processed = 1;
|
2011-08-12 05:20:08 +00:00
|
|
|
if (entry->rename_conflict_info) {
|
2019-04-05 15:00:16 +00:00
|
|
|
struct rename_conflict_info *ci = entry->rename_conflict_info;
|
2019-04-05 15:00:22 +00:00
|
|
|
struct diff_filespec *temp;
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
int path_clean;
|
|
|
|
|
|
|
|
path_clean = warn_about_dir_renamed_entries(opt, ci->ren1);
|
|
|
|
path_clean &= warn_about_dir_renamed_entries(opt, ci->ren2);
|
2019-04-05 15:00:22 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* For cases with a single rename, {o,a,b}->path have all been
|
|
|
|
* set to the rename target path; we need to set two of these
|
|
|
|
* back to the rename source.
|
|
|
|
* For rename/rename conflicts, we'll manually fix paths below.
|
|
|
|
*/
|
|
|
|
temp = (opt->branch1 == ci->ren1->branch) ? b : a;
|
|
|
|
o->path = temp->path = ci->ren1->pair->one->path;
|
|
|
|
if (ci->ren2) {
|
|
|
|
assert(opt->branch1 == ci->ren1->branch);
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:16 +00:00
|
|
|
switch (ci->rename_type) {
|
2010-09-20 08:29:03 +00:00
|
|
|
case RENAME_NORMAL:
|
2011-08-12 05:20:08 +00:00
|
|
|
case RENAME_ONE_FILE_TO_ONE:
|
2019-04-05 15:00:22 +00:00
|
|
|
clean_merge = handle_rename_normal(opt, path, o, a, b,
|
2019-04-05 15:00:16 +00:00
|
|
|
ci);
|
2010-09-20 08:29:03 +00:00
|
|
|
break;
|
2018-06-10 04:16:14 +00:00
|
|
|
case RENAME_VIA_DIR:
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
clean_merge = handle_rename_via_dir(opt, ci);
|
2010-09-20 08:29:03 +00:00
|
|
|
break;
|
2018-11-08 04:40:26 +00:00
|
|
|
case RENAME_ADD:
|
|
|
|
/*
|
|
|
|
* Probably unclean merge, but if the renamed file
|
|
|
|
* merges cleanly and the result can then be
|
|
|
|
* two-way merged cleanly with the added file, I
|
|
|
|
* guess it's a clean merge?
|
|
|
|
*/
|
2019-04-05 15:00:16 +00:00
|
|
|
clean_merge = handle_rename_add(opt, ci);
|
2018-11-08 04:40:26 +00:00
|
|
|
break;
|
2010-09-20 08:29:02 +00:00
|
|
|
case RENAME_DELETE:
|
|
|
|
clean_merge = 0;
|
2019-04-05 15:00:21 +00:00
|
|
|
if (handle_rename_delete(opt, ci))
|
2016-07-26 16:06:21 +00:00
|
|
|
clean_merge = -1;
|
2010-09-20 08:29:02 +00:00
|
|
|
break;
|
2010-09-20 08:29:00 +00:00
|
|
|
case RENAME_ONE_FILE_TO_TWO:
|
2019-04-05 15:00:22 +00:00
|
|
|
/*
|
|
|
|
* Manually fix up paths; note:
|
|
|
|
* ren[12]->pair->one->path are equal.
|
|
|
|
*/
|
|
|
|
o->path = ci->ren1->pair->one->path;
|
|
|
|
a->path = ci->ren1->pair->two->path;
|
|
|
|
b->path = ci->ren2->pair->two->path;
|
|
|
|
|
2010-09-20 08:29:00 +00:00
|
|
|
clean_merge = 0;
|
2019-04-05 15:00:16 +00:00
|
|
|
if (handle_rename_rename_1to2(opt, ci))
|
2016-07-26 16:06:21 +00:00
|
|
|
clean_merge = -1;
|
2010-09-20 08:29:00 +00:00
|
|
|
break;
|
2011-08-12 05:20:15 +00:00
|
|
|
case RENAME_TWO_FILES_TO_ONE:
|
2019-04-05 15:00:22 +00:00
|
|
|
/*
|
|
|
|
* Manually fix up paths; note,
|
|
|
|
* ren[12]->pair->two->path are actually equal.
|
|
|
|
*/
|
|
|
|
o->path = NULL;
|
|
|
|
a->path = ci->ren1->pair->two->path;
|
|
|
|
b->path = ci->ren2->pair->two->path;
|
|
|
|
|
2018-11-08 04:40:27 +00:00
|
|
|
/*
|
|
|
|
* Probably unclean merge, but if the two renamed
|
|
|
|
* files merge cleanly and the two resulting files
|
|
|
|
* can then be two-way merged cleanly, I guess it's
|
|
|
|
* a clean merge?
|
|
|
|
*/
|
2019-04-05 15:00:16 +00:00
|
|
|
clean_merge = handle_rename_rename_2to1(opt, ci);
|
2010-09-20 08:29:00 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
entry->processed = 0;
|
|
|
|
break;
|
|
|
|
}
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
if (path_clean < clean_merge)
|
|
|
|
clean_merge = path_clean;
|
2019-04-05 15:00:22 +00:00
|
|
|
} else if (o_valid && (!a_valid || !b_valid)) {
|
2011-08-12 05:20:07 +00:00
|
|
|
/* Case A: Deleted in one */
|
2019-04-05 15:00:22 +00:00
|
|
|
if ((!a_valid && !b_valid) ||
|
|
|
|
(!b_valid && blob_unchanged(opt, o, a, normalize, path)) ||
|
|
|
|
(!a_valid && blob_unchanged(opt, o, b, normalize, path))) {
|
2011-08-12 05:20:07 +00:00
|
|
|
/* Deleted in both or deleted in one and
|
|
|
|
* unchanged in the other */
|
2019-04-05 15:00:22 +00:00
|
|
|
if (a_valid)
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 2, _("Removing %s"), path);
|
2011-08-12 05:20:07 +00:00
|
|
|
/* do not touch working file if it did not exist */
|
2019-04-05 15:00:22 +00:00
|
|
|
remove_file(opt, 1, path, !a_valid);
|
2011-08-12 05:20:07 +00:00
|
|
|
} else {
|
|
|
|
/* Modify/delete; deleted side may have put a directory in the way */
|
|
|
|
clean_merge = 0;
|
2019-04-05 15:00:22 +00:00
|
|
|
if (handle_modify_delete(opt, path, o, a, b))
|
2016-07-26 16:06:21 +00:00
|
|
|
clean_merge = -1;
|
2011-08-12 05:19:53 +00:00
|
|
|
}
|
2019-04-05 15:00:22 +00:00
|
|
|
} else if ((!o_valid && a_valid && !b_valid) ||
|
|
|
|
(!o_valid && !a_valid && b_valid)) {
|
2011-08-12 05:20:07 +00:00
|
|
|
/* Case B: Added in one. */
|
|
|
|
/* [nothing|directory] -> ([nothing|directory], file) */
|
|
|
|
|
2010-09-20 08:28:56 +00:00
|
|
|
const char *add_branch;
|
|
|
|
const char *other_branch;
|
|
|
|
const char *conf;
|
2019-04-05 15:00:22 +00:00
|
|
|
const struct diff_filespec *contents;
|
2010-07-09 13:10:53 +00:00
|
|
|
|
2019-04-05 15:00:22 +00:00
|
|
|
if (a_valid) {
|
2019-04-05 15:00:13 +00:00
|
|
|
add_branch = opt->branch1;
|
|
|
|
other_branch = opt->branch2;
|
2019-04-05 15:00:22 +00:00
|
|
|
contents = a;
|
2012-07-25 14:53:13 +00:00
|
|
|
conf = _("file/directory");
|
2010-09-20 08:28:56 +00:00
|
|
|
} else {
|
2019-04-05 15:00:13 +00:00
|
|
|
add_branch = opt->branch2;
|
|
|
|
other_branch = opt->branch1;
|
2019-04-05 15:00:22 +00:00
|
|
|
contents = b;
|
2012-07-25 14:53:13 +00:00
|
|
|
conf = _("directory/file");
|
2010-09-20 08:28:56 +00:00
|
|
|
}
|
2019-04-05 15:00:13 +00:00
|
|
|
if (dir_in_way(opt->repo->index, path,
|
2019-08-17 18:41:41 +00:00
|
|
|
!opt->priv->call_depth && !S_ISGITLINK(a->mode),
|
2017-11-14 17:31:24 +00:00
|
|
|
0)) {
|
2019-04-05 15:00:13 +00:00
|
|
|
char *new_path = unique_path(opt, path, add_branch);
|
2010-09-20 08:28:56 +00:00
|
|
|
clean_merge = 0;
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
|
2012-07-25 14:53:13 +00:00
|
|
|
"Adding %s as %s"),
|
2010-09-20 08:28:56 +00:00
|
|
|
conf, path, other_branch, path, new_path);
|
2019-04-05 15:00:22 +00:00
|
|
|
if (update_file(opt, 0, contents, new_path))
|
2016-07-26 16:06:21 +00:00
|
|
|
clean_merge = -1;
|
2019-08-17 18:41:41 +00:00
|
|
|
else if (opt->priv->call_depth)
|
2019-04-05 15:00:13 +00:00
|
|
|
remove_file_from_index(opt->repo->index, path);
|
2011-08-12 05:19:53 +00:00
|
|
|
free(new_path);
|
2010-09-20 08:28:56 +00:00
|
|
|
} else {
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 2, _("Adding %s"), path);
|
2011-08-12 05:20:27 +00:00
|
|
|
/* do not overwrite file if already present */
|
2019-04-05 15:00:22 +00:00
|
|
|
if (update_file_flags(opt, contents, path, 1, !a_valid))
|
2016-07-26 16:06:21 +00:00
|
|
|
clean_merge = -1;
|
2010-09-20 08:28:56 +00:00
|
|
|
}
|
2019-04-05 15:00:22 +00:00
|
|
|
} else if (a_valid && b_valid) {
|
|
|
|
if (!o_valid) {
|
2018-11-08 04:40:28 +00:00
|
|
|
/* Case C: Added in both (check for same permissions) */
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 1,
|
2018-11-08 04:40:28 +00:00
|
|
|
_("CONFLICT (add/add): Merge conflict in %s"),
|
|
|
|
path);
|
2019-04-05 15:00:13 +00:00
|
|
|
clean_merge = handle_file_collision(opt,
|
2018-11-08 04:40:28 +00:00
|
|
|
path, NULL, NULL,
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->branch1,
|
|
|
|
opt->branch2,
|
2019-04-05 15:00:22 +00:00
|
|
|
a, b);
|
2018-11-08 04:40:28 +00:00
|
|
|
} else {
|
|
|
|
/* case D: Modified in both, but differently. */
|
2019-04-05 15:00:25 +00:00
|
|
|
struct merge_file_info mfi;
|
2018-11-08 04:40:28 +00:00
|
|
|
int is_dirty = 0; /* unpack_trees would have bailed if dirty */
|
2019-04-05 15:00:25 +00:00
|
|
|
clean_merge = handle_content_merge(&mfi, opt, path,
|
2018-11-08 04:40:28 +00:00
|
|
|
is_dirty,
|
2019-04-05 15:00:22 +00:00
|
|
|
o, a, b, NULL);
|
2018-11-08 04:40:28 +00:00
|
|
|
}
|
2019-04-05 15:00:22 +00:00
|
|
|
} else if (!o_valid && !a_valid && !b_valid) {
|
2011-08-12 05:20:07 +00:00
|
|
|
/*
|
|
|
|
* this entry was deleted altogether. a_mode == 0 means
|
|
|
|
* we had that path and want to actively remove it.
|
|
|
|
*/
|
2019-04-05 15:00:22 +00:00
|
|
|
remove_file(opt, 1, path, !a->mode);
|
2011-08-12 05:20:07 +00:00
|
|
|
} else
|
2018-05-02 09:38:39 +00:00
|
|
|
BUG("fatal merge failure, shouldn't happen.");
|
2010-07-09 13:10:53 +00:00
|
|
|
|
|
|
|
return clean_merge;
|
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:29 +00:00
|
|
|
static int merge_trees_internal(struct merge_options *opt,
|
|
|
|
struct tree *head,
|
|
|
|
struct tree *merge,
|
2019-08-17 18:41:34 +00:00
|
|
|
struct tree *merge_base,
|
2019-08-17 18:41:29 +00:00
|
|
|
struct tree **result)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2019-04-05 15:00:13 +00:00
|
|
|
struct index_state *istate = opt->repo->index;
|
2008-08-12 16:45:14 +00:00
|
|
|
int code, clean;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (opt->subtree_shift) {
|
2019-08-17 18:41:34 +00:00
|
|
|
merge = shift_tree_object(opt->repo, head, merge,
|
|
|
|
opt->subtree_shift);
|
|
|
|
merge_base = shift_tree_object(opt->repo, head, merge_base,
|
|
|
|
opt->subtree_shift);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
merge-recursive: remove unnecessary oid_eq function
Back when merge-recursive was first introduced in commit 6d297f8137
(Status update on merge-recursive in C, 2006-07-08), it created a
sha_eq() function. This function pre-dated the introduction of
hashcmp() to cache.h by about a month, but was switched over to using
hashcmp() as part of commit 9047ebbc22 (Split out merge_recursive() to
merge-recursive.c, 2008-08-12). In commit b4da9d62f9 (merge-recursive:
convert leaf functions to use struct object_id, 2016-06-24), sha_eq() was
renamed to oid_eq() and its hashcmp() call was switched to oideq().
oid_eq() is basically just a wrapper around oideq() that has some extra
checks to protect against NULL arguments or to allow short-circuiting if
one of the arguments is NULL. I don't know if any caller ever tried to
call with NULL arguments, but certainly none do now which means the
extra checks serve no purpose. (Also, if these checks were genuinely
useful, then they probably should be added to the main oideq() so all
callers could benefit from them.)
Reduce the cognitive overhead of having both oid_eq() and oideq(), by
getting rid of merge-recursive's special oid_eq() wrapper.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-01 05:20:57 +00:00
|
|
|
if (oideq(&merge_base->object.oid, &merge->object.oid)) {
|
2021-05-02 05:14:22 +00:00
|
|
|
output(opt, 0, _("Already up to date."));
|
2008-08-12 16:45:14 +00:00
|
|
|
*result = head;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:34 +00:00
|
|
|
code = unpack_trees_start(opt, merge_base, head, merge);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
merge-recursive: give less scary messages when merge did not start
When unpack_trees() three-way merge logic is called from merge-recursive
and finds that local changes are going to be clobbered, its plumbing level
messages were given as errors first, and then the merge driver added even
more scary message "fatal: merging of trees <a long object name> and
<another long object name> failed".
This is most often encountered by new CVS/SVN migrants who are used to
start a merge from a dirty work tree. The saddest part is that the merge
refused to run to prevent _any_ damage from being done to your work tree
when these messages are given, but the messages look a lot more scarier
than the conflicted case where the user needs to resolve them.
Replace the plumbing level messages so that they talk about what it is
protecting the user from, and end the messages with "Aborting." so that it
becomes clear that the command did not do any harm.
The final "merging of trees failed" message is superfluous, unless you are
interested in debugging the merge-recursive itself. Squelch the current
die() message by default, but allow it to help people who debug git with
verbosity level 4 or greater.
Unless there is some bug, an inner merge that does not touch working tree
should not trigger any such error, so emit the current die() message when
we see an error return from it while running the inner merge, too. It
would also help people who debug git.
We could later add instructions on how to recover (i.e. "stash changes
away or commit on a side branch and retry") instead of the silent
exit(128) I have in this patch, and then use Peff's advice.* mechanism to
squelch it (e.g. "advice.mergeindirtytree"), but they are separate topics.
Tested-by: Nanako Shiraishi <nanako3@lavabit.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-09-08 05:43:11 +00:00
|
|
|
if (code != 0) {
|
2019-08-17 18:41:41 +00:00
|
|
|
if (show(opt, 4) || opt->priv->call_depth)
|
2019-04-05 15:00:13 +00:00
|
|
|
err(opt, _("merging of trees %s and %s failed"),
|
2015-11-10 02:22:28 +00:00
|
|
|
oid_to_hex(&head->object.oid),
|
|
|
|
oid_to_hex(&merge->object.oid));
|
2019-04-05 15:00:13 +00:00
|
|
|
unpack_trees_finish(opt);
|
2016-07-26 16:06:26 +00:00
|
|
|
return -1;
|
merge-recursive: give less scary messages when merge did not start
When unpack_trees() three-way merge logic is called from merge-recursive
and finds that local changes are going to be clobbered, its plumbing level
messages were given as errors first, and then the merge driver added even
more scary message "fatal: merging of trees <a long object name> and
<another long object name> failed".
This is most often encountered by new CVS/SVN migrants who are used to
start a merge from a dirty work tree. The saddest part is that the merge
refused to run to prevent _any_ damage from being done to your work tree
when these messages are given, but the messages look a lot more scarier
than the conflicted case where the user needs to resolve them.
Replace the plumbing level messages so that they talk about what it is
protecting the user from, and end the messages with "Aborting." so that it
becomes clear that the command did not do any harm.
The final "merging of trees failed" message is superfluous, unless you are
interested in debugging the merge-recursive itself. Squelch the current
die() message by default, but allow it to help people who debug git with
verbosity level 4 or greater.
Unless there is some bug, an inner merge that does not touch working tree
should not trigger any such error, so emit the current die() message when
we see an error return from it while running the inner merge, too. It
would also help people who debug git.
We could later add instructions on how to recover (i.e. "stash changes
away or commit on a side branch and retry") instead of the silent
exit(128) I have in this patch, and then use Peff's advice.* mechanism to
squelch it (e.g. "advice.mergeindirtytree"), but they are separate topics.
Tested-by: Nanako Shiraishi <nanako3@lavabit.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-09-08 05:43:11 +00:00
|
|
|
}
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-01-12 02:13:29 +00:00
|
|
|
if (unmerged_index(istate)) {
|
2018-04-19 17:58:00 +00:00
|
|
|
struct string_list *entries;
|
|
|
|
struct rename_info re_info;
|
2008-08-12 16:45:14 +00:00
|
|
|
int i;
|
2017-09-07 16:25:56 +00:00
|
|
|
/*
|
|
|
|
* Only need the hashmap while processing entries, so
|
|
|
|
* initialize it here and free it when we are done running
|
|
|
|
* through the entries. Keeping it in the merge_options as
|
|
|
|
* opposed to decaring a local hashmap is for convenience
|
|
|
|
* so that we don't have to pass it to around.
|
|
|
|
*/
|
2019-08-17 18:41:41 +00:00
|
|
|
hashmap_init(&opt->priv->current_file_dir_set, path_hashmap_cmp,
|
2019-08-17 18:41:33 +00:00
|
|
|
NULL, 512);
|
2019-04-05 15:00:13 +00:00
|
|
|
get_files_dirs(opt, head);
|
|
|
|
get_files_dirs(opt, merge);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
entries = get_unmerged(opt->repo->index);
|
2019-08-17 18:41:34 +00:00
|
|
|
clean = detect_and_process_renames(opt, merge_base, head, merge,
|
2018-06-10 04:16:15 +00:00
|
|
|
entries, &re_info);
|
2019-04-05 15:00:13 +00:00
|
|
|
record_df_conflict_files(opt, entries);
|
2016-07-26 16:06:21 +00:00
|
|
|
if (clean < 0)
|
2017-08-28 20:28:27 +00:00
|
|
|
goto cleanup;
|
2011-08-12 05:20:07 +00:00
|
|
|
for (i = entries->nr-1; 0 <= i; i--) {
|
2008-08-12 16:45:14 +00:00
|
|
|
const char *path = entries->items[i].string;
|
|
|
|
struct stage_data *e = entries->items[i].util;
|
2016-07-26 16:06:21 +00:00
|
|
|
if (!e->processed) {
|
2019-04-05 15:00:13 +00:00
|
|
|
int ret = process_entry(opt, path, e);
|
2016-07-26 16:06:21 +00:00
|
|
|
if (!ret)
|
|
|
|
clean = 0;
|
2017-08-28 20:28:27 +00:00
|
|
|
else if (ret < 0) {
|
|
|
|
clean = ret;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2016-07-26 16:06:21 +00:00
|
|
|
}
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
2010-09-20 08:28:35 +00:00
|
|
|
for (i = 0; i < entries->nr; i++) {
|
|
|
|
struct stage_data *e = entries->items[i].util;
|
|
|
|
if (!e->processed)
|
2018-05-02 09:38:39 +00:00
|
|
|
BUG("unprocessed path??? %s",
|
2010-09-20 08:28:35 +00:00
|
|
|
entries->items[i].string);
|
|
|
|
}
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2018-06-10 04:16:13 +00:00
|
|
|
cleanup:
|
2018-04-19 17:58:04 +00:00
|
|
|
final_cleanup_renames(&re_info);
|
2018-04-19 17:58:00 +00:00
|
|
|
|
2008-08-12 16:45:14 +00:00
|
|
|
string_list_clear(entries, 1);
|
2018-04-19 17:58:00 +00:00
|
|
|
free(entries);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2020-11-02 18:55:05 +00:00
|
|
|
hashmap_clear_and_free(&opt->priv->current_file_dir_set,
|
2019-10-06 23:30:40 +00:00
|
|
|
struct path_hashmap_entry, e);
|
2017-09-07 16:25:56 +00:00
|
|
|
|
2018-05-20 10:17:35 +00:00
|
|
|
if (clean < 0) {
|
2019-04-05 15:00:13 +00:00
|
|
|
unpack_trees_finish(opt);
|
2017-08-28 20:28:27 +00:00
|
|
|
return clean;
|
2018-05-20 10:17:35 +00:00
|
|
|
}
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
clean = 1;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
unpack_trees_finish(opt);
|
2018-04-19 17:58:20 +00:00
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opt->priv->call_depth &&
|
2019-08-17 18:41:32 +00:00
|
|
|
!(*result = write_in_core_index_as_tree(opt->repo)))
|
2016-07-26 16:06:17 +00:00
|
|
|
return -1;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
|
|
|
return clean;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2020-08-02 03:14:27 +00:00
|
|
|
* Merge the commits h1 and h2, returning a flag (int) indicating the
|
|
|
|
* cleanness of the merge. Also, if opt->priv->call_depth, create a
|
|
|
|
* virtual commit and write its location to *result.
|
2008-08-12 16:45:14 +00:00
|
|
|
*/
|
2019-08-17 18:41:29 +00:00
|
|
|
static int merge_recursive_internal(struct merge_options *opt,
|
|
|
|
struct commit *h1,
|
|
|
|
struct commit *h2,
|
2019-08-17 18:41:34 +00:00
|
|
|
struct commit_list *merge_bases,
|
2019-08-17 18:41:29 +00:00
|
|
|
struct commit **result)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
|
|
|
struct commit_list *iter;
|
2019-08-17 18:41:34 +00:00
|
|
|
struct commit *merged_merge_bases;
|
2019-08-17 18:41:35 +00:00
|
|
|
struct tree *result_tree;
|
2008-08-12 16:45:14 +00:00
|
|
|
int clean;
|
2019-08-17 18:41:24 +00:00
|
|
|
const char *ancestor_name;
|
|
|
|
struct strbuf merge_base_abbrev = STRBUF_INIT;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (show(opt, 4)) {
|
|
|
|
output(opt, 4, _("Merging:"));
|
|
|
|
output_commit_title(opt, h1);
|
|
|
|
output_commit_title(opt, h2);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:34 +00:00
|
|
|
if (!merge_bases) {
|
2024-02-28 09:44:14 +00:00
|
|
|
if (repo_get_merge_bases(the_repository, h1, h2,
|
|
|
|
&merge_bases) < 0)
|
|
|
|
return -1;
|
2019-08-17 18:41:34 +00:00
|
|
|
merge_bases = reverse_commit_list(merge_bases);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (show(opt, 5)) {
|
2019-08-17 18:41:34 +00:00
|
|
|
unsigned cnt = commit_list_count(merge_bases);
|
2012-08-05 17:56:38 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
output(opt, 5, Q_("found %u common ancestor:",
|
2012-08-05 17:56:38 +00:00
|
|
|
"found %u common ancestors:", cnt), cnt);
|
2019-08-17 18:41:34 +00:00
|
|
|
for (iter = merge_bases; iter; iter = iter->next)
|
2019-04-05 15:00:13 +00:00
|
|
|
output_commit_title(opt, iter->item);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:34 +00:00
|
|
|
merged_merge_bases = pop_commit(&merge_bases);
|
2022-05-02 16:50:37 +00:00
|
|
|
if (!merged_merge_bases) {
|
2011-08-16 18:27:39 +00:00
|
|
|
/* if there is no common ancestor, use an empty tree */
|
|
|
|
struct tree *tree;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
tree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);
|
2019-08-17 18:41:34 +00:00
|
|
|
merged_merge_bases = make_virtual_commit(opt->repo, tree,
|
|
|
|
"ancestor");
|
2019-08-17 18:41:24 +00:00
|
|
|
ancestor_name = "empty tree";
|
merge-recursive: fix the fix to the diff3 common ancestor label
In commit 8e4ec337 ("merge-recursive: fix the diff3 common ancestor
label for virtual commits", 2019-10-01), which was a fix to commit
743474cbfa8b ("merge-recursive: provide a better label for diff3
common ancestor", 2019-08-17), the label for the common ancestor was
changed from always being
"merged common ancestors"
to instead be based on the number of merge bases and whether the merge
base was a real commit or a virtual one:
>=2: "merged common ancestors"
1, via merge_recursive_generic: "constructed merge base"
1, otherwise: <abbreviated commit hash>
0: "<empty tree>"
The handling for "constructed merge base" worked by allowing
opt->ancestor to be set in merge_recursive_generic(), so we paid
attention to the setting of that variable in merge_recursive_internal().
Now, for the outer merge, the code flow was simply the following:
ancestor_name = "merged merge bases"
loop over merge_bases: merge_recursive_internal()
The first merge base not needing recursion would determine its own
ancestor_name however necessary and thus run
ancestor_name = $SOMETHING
empty loop over merge_bases...
opt->ancestor = ancestor_name
merge_trees_internal()
Now, the next set of merge_bases that would need to be merged after this
particular merge had completed would note that opt->ancestor has been
set to something (to a local ancestor_name variable that has since been
popped off the stack), and thus it would run:
... else if (opt->ancestor) {
ancestor_name = opt->ancestor; /* OOPS! */
loop over merge_bases: merge_recursive_internal()
opt->ancestor = ancestor_name
merge_trees_internal()
This resulted in garbage strings being printed for the virtual merge
bases, which was visible in git.git by just merging commit b744c3af07
into commit 6d8cb22a4f. There are two ways to fix this: set
opt->ancestor to NULL after using it to avoid re-use, or add a
!opt->priv->call_depth check to the if block for using a pre-defined
opt->ancestor. Apply both fixes.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-10-07 15:52:11 +00:00
|
|
|
} else if (opt->ancestor && !opt->priv->call_depth) {
|
merge-recursive: fix the diff3 common ancestor label for virtual commits
In commit 743474cbfa8b ("merge-recursive: provide a better label for
diff3 common ancestor", 2019-08-17), the label for the common ancestor
was changed from always being
"merged common ancestors"
to instead be based on the number of merge bases:
>=2: "merged common ancestors"
1: <abbreviated commit hash>
0: "<empty tree>"
Unfortunately, this did not take into account that when we have a single
merge base, that merge base could be fake or constructed. In such
cases, this resulted in a label of "00000000". Of course, the previous
label of "merged common ancestors" was also misleading for this case.
Since we have an API that is explicitly about creating fake merge base
commits in merge_recursive_generic(), we should provide a better label
when using that API with one merge base. So, when
merge_recursive_generic() is called with one merge base, set the label
to:
"constructed merge base"
Note that callers of merge_recursive_generic() include the builtin
commands git-am (in combination with git apply --build-fake-ancestor),
git-merge-recursive, and git-stash.
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-10-01 18:17:27 +00:00
|
|
|
ancestor_name = opt->ancestor;
|
2019-08-17 18:41:34 +00:00
|
|
|
} else if (merge_bases) {
|
2019-08-17 18:41:24 +00:00
|
|
|
ancestor_name = "merged common ancestors";
|
|
|
|
} else {
|
|
|
|
strbuf_add_unique_abbrev(&merge_base_abbrev,
|
2019-08-17 18:41:34 +00:00
|
|
|
&merged_merge_bases->object.oid,
|
2019-08-17 18:41:24 +00:00
|
|
|
DEFAULT_ABBREV);
|
|
|
|
ancestor_name = merge_base_abbrev.buf;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:34 +00:00
|
|
|
for (iter = merge_bases; iter; iter = iter->next) {
|
2008-08-25 14:25:57 +00:00
|
|
|
const char *saved_b1, *saved_b2;
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->call_depth++;
|
2008-08-12 16:45:14 +00:00
|
|
|
/*
|
|
|
|
* When the merge fails, the result contains files
|
|
|
|
* with conflict markers. The cleanness flag is
|
2016-07-26 16:06:07 +00:00
|
|
|
* ignored (unless indicating an error), it was never
|
|
|
|
* actually used, as result of merge_trees has always
|
|
|
|
* overwritten it: the committed "conflicts" were
|
|
|
|
* already resolved.
|
2008-08-12 16:45:14 +00:00
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
discard_index(opt->repo->index);
|
|
|
|
saved_b1 = opt->branch1;
|
|
|
|
saved_b2 = opt->branch2;
|
|
|
|
opt->branch1 = "Temporary merge branch 1";
|
|
|
|
opt->branch2 = "Temporary merge branch 2";
|
2019-08-17 18:41:34 +00:00
|
|
|
if (merge_recursive_internal(opt, merged_merge_bases, iter->item,
|
|
|
|
NULL, &merged_merge_bases) < 0)
|
2016-07-26 16:06:07 +00:00
|
|
|
return -1;
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->branch1 = saved_b1;
|
|
|
|
opt->branch2 = saved_b2;
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->call_depth--;
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-08-17 18:41:34 +00:00
|
|
|
if (!merged_merge_bases)
|
2019-04-05 15:00:13 +00:00
|
|
|
return err(opt, _("merge returned no commit"));
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2021-03-20 00:03:53 +00:00
|
|
|
/*
|
|
|
|
* FIXME: Since merge_recursive_internal() is only ever called by
|
|
|
|
* places that ensure the index is loaded first
|
|
|
|
* (e.g. builtin/merge.c, rebase/sequencer, etc.), in the common
|
|
|
|
* case where the merge base was unique that means when we get here
|
|
|
|
* we immediately discard the index and re-read it, which is a
|
|
|
|
* complete waste of time. We should only be discarding and
|
|
|
|
* re-reading if we were forced to recurse.
|
|
|
|
*/
|
2019-04-05 15:00:13 +00:00
|
|
|
discard_index(opt->repo->index);
|
2019-08-17 18:41:41 +00:00
|
|
|
if (!opt->priv->call_depth)
|
2019-04-05 15:00:13 +00:00
|
|
|
repo_read_index(opt->repo);
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-08-17 18:41:24 +00:00
|
|
|
opt->ancestor = ancestor_name;
|
2019-08-17 18:41:29 +00:00
|
|
|
clean = merge_trees_internal(opt,
|
|
|
|
repo_get_commit_tree(opt->repo, h1),
|
|
|
|
repo_get_commit_tree(opt->repo, h2),
|
|
|
|
repo_get_commit_tree(opt->repo,
|
2019-08-17 18:41:34 +00:00
|
|
|
merged_merge_bases),
|
2019-08-17 18:41:35 +00:00
|
|
|
&result_tree);
|
2019-08-17 18:41:24 +00:00
|
|
|
strbuf_release(&merge_base_abbrev);
|
merge-recursive: fix the fix to the diff3 common ancestor label
In commit 8e4ec337 ("merge-recursive: fix the diff3 common ancestor
label for virtual commits", 2019-10-01), which was a fix to commit
743474cbfa8b ("merge-recursive: provide a better label for diff3
common ancestor", 2019-08-17), the label for the common ancestor was
changed from always being
"merged common ancestors"
to instead be based on the number of merge bases and whether the merge
base was a real commit or a virtual one:
>=2: "merged common ancestors"
1, via merge_recursive_generic: "constructed merge base"
1, otherwise: <abbreviated commit hash>
0: "<empty tree>"
The handling for "constructed merge base" worked by allowing
opt->ancestor to be set in merge_recursive_generic(), so we paid
attention to the setting of that variable in merge_recursive_internal().
Now, for the outer merge, the code flow was simply the following:
ancestor_name = "merged merge bases"
loop over merge_bases: merge_recursive_internal()
The first merge base not needing recursion would determine its own
ancestor_name however necessary and thus run
ancestor_name = $SOMETHING
empty loop over merge_bases...
opt->ancestor = ancestor_name
merge_trees_internal()
Now, the next set of merge_bases that would need to be merged after this
particular merge had completed would note that opt->ancestor has been
set to something (to a local ancestor_name variable that has since been
popped off the stack), and thus it would run:
... else if (opt->ancestor) {
ancestor_name = opt->ancestor; /* OOPS! */
loop over merge_bases: merge_recursive_internal()
opt->ancestor = ancestor_name
merge_trees_internal()
This resulted in garbage strings being printed for the virtual merge
bases, which was visible in git.git by just merging commit b744c3af07
into commit 6d8cb22a4f. There are two ways to fix this: set
opt->ancestor to NULL after using it to avoid re-use, or add a
!opt->priv->call_depth check to the if block for using a pre-defined
opt->ancestor. Apply both fixes.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-10-07 15:52:11 +00:00
|
|
|
opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */
|
2016-08-01 11:44:57 +00:00
|
|
|
if (clean < 0) {
|
2019-04-05 15:00:13 +00:00
|
|
|
flush_output(opt);
|
2016-07-26 16:06:07 +00:00
|
|
|
return clean;
|
2016-08-01 11:44:57 +00:00
|
|
|
}
|
2008-08-12 16:45:14 +00:00
|
|
|
|
2019-08-17 18:41:41 +00:00
|
|
|
if (opt->priv->call_depth) {
|
2019-08-17 18:41:35 +00:00
|
|
|
*result = make_virtual_commit(opt->repo, result_tree,
|
|
|
|
"merged tree");
|
2008-08-12 16:45:14 +00:00
|
|
|
commit_list_insert(h1, &(*result)->parents);
|
|
|
|
commit_list_insert(h2, &(*result)->parents->next);
|
|
|
|
}
|
|
|
|
return clean;
|
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:29 +00:00
|
|
|
static int merge_start(struct merge_options *opt, struct tree *head)
|
|
|
|
{
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
|
2019-08-17 18:41:43 +00:00
|
|
|
/* Sanity checks on opt */
|
|
|
|
assert(opt->repo);
|
|
|
|
|
|
|
|
assert(opt->branch1 && opt->branch2);
|
|
|
|
|
|
|
|
assert(opt->detect_renames >= -1 &&
|
|
|
|
opt->detect_renames <= DIFF_DETECT_COPY);
|
|
|
|
assert(opt->detect_directory_renames >= MERGE_DIRECTORY_RENAMES_NONE &&
|
|
|
|
opt->detect_directory_renames <= MERGE_DIRECTORY_RENAMES_TRUE);
|
|
|
|
assert(opt->rename_limit >= -1);
|
|
|
|
assert(opt->rename_score >= 0 && opt->rename_score <= MAX_SCORE);
|
|
|
|
assert(opt->show_rename_progress >= 0 && opt->show_rename_progress <= 1);
|
|
|
|
|
|
|
|
assert(opt->xdl_opts >= 0);
|
|
|
|
assert(opt->recursive_variant >= MERGE_VARIANT_NORMAL &&
|
|
|
|
opt->recursive_variant <= MERGE_VARIANT_THEIRS);
|
|
|
|
|
|
|
|
assert(opt->verbosity >= 0 && opt->verbosity <= 5);
|
|
|
|
assert(opt->buffer_output <= 2);
|
|
|
|
assert(opt->obuf.len == 0);
|
|
|
|
|
|
|
|
assert(opt->priv == NULL);
|
|
|
|
|
2022-02-02 02:37:33 +00:00
|
|
|
/* Not supported; option specific to merge-ort */
|
|
|
|
assert(!opt->record_conflict_msgs_as_headers);
|
|
|
|
assert(!opt->msg_header_prefix);
|
|
|
|
|
2019-08-17 18:41:43 +00:00
|
|
|
/* Sanity check on repo state; index must match head */
|
2019-08-17 18:41:29 +00:00
|
|
|
if (repo_index_has_changes(opt->repo, head, &sb)) {
|
|
|
|
err(opt, _("Your local changes to the following files would be overwritten by merge:\n %s"),
|
|
|
|
sb.buf);
|
|
|
|
strbuf_release(&sb);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-03-13 16:17:22 +00:00
|
|
|
CALLOC_ARRAY(opt->priv, 1);
|
2021-07-01 10:51:29 +00:00
|
|
|
string_list_init_dup(&opt->priv->df_conflict_file_set);
|
2019-08-17 18:41:29 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void merge_finalize(struct merge_options *opt)
|
|
|
|
{
|
2019-04-05 15:00:13 +00:00
|
|
|
flush_output(opt);
|
2019-08-17 18:41:41 +00:00
|
|
|
if (!opt->priv->call_depth && opt->buffer_output < 2)
|
2019-04-05 15:00:13 +00:00
|
|
|
strbuf_release(&opt->obuf);
|
|
|
|
if (show(opt, 2))
|
2011-01-06 21:50:06 +00:00
|
|
|
diff_warn_rename_limit("merge.renamelimit",
|
2019-08-17 18:41:41 +00:00
|
|
|
opt->priv->needed_rename_limit, 0);
|
|
|
|
FREE_AND_NULL(opt->priv);
|
2019-08-17 18:41:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int merge_trees(struct merge_options *opt,
|
|
|
|
struct tree *head,
|
|
|
|
struct tree *merge,
|
2019-08-17 18:41:34 +00:00
|
|
|
struct tree *merge_base)
|
2019-08-17 18:41:29 +00:00
|
|
|
{
|
|
|
|
int clean;
|
2019-08-17 18:41:30 +00:00
|
|
|
struct tree *ignored;
|
2019-08-17 18:41:29 +00:00
|
|
|
|
|
|
|
assert(opt->ancestor != NULL);
|
|
|
|
|
|
|
|
if (merge_start(opt, head))
|
|
|
|
return -1;
|
2019-08-17 18:41:34 +00:00
|
|
|
clean = merge_trees_internal(opt, head, merge, merge_base, &ignored);
|
2019-08-17 18:41:29 +00:00
|
|
|
merge_finalize(opt);
|
|
|
|
|
|
|
|
return clean;
|
|
|
|
}
|
|
|
|
|
|
|
|
int merge_recursive(struct merge_options *opt,
|
|
|
|
struct commit *h1,
|
|
|
|
struct commit *h2,
|
2019-08-17 18:41:34 +00:00
|
|
|
struct commit_list *merge_bases,
|
2019-08-17 18:41:29 +00:00
|
|
|
struct commit **result)
|
|
|
|
{
|
|
|
|
int clean;
|
|
|
|
|
merge-recursive: fix the diff3 common ancestor label for virtual commits
In commit 743474cbfa8b ("merge-recursive: provide a better label for
diff3 common ancestor", 2019-08-17), the label for the common ancestor
was changed from always being
"merged common ancestors"
to instead be based on the number of merge bases:
>=2: "merged common ancestors"
1: <abbreviated commit hash>
0: "<empty tree>"
Unfortunately, this did not take into account that when we have a single
merge base, that merge base could be fake or constructed. In such
cases, this resulted in a label of "00000000". Of course, the previous
label of "merged common ancestors" was also misleading for this case.
Since we have an API that is explicitly about creating fake merge base
commits in merge_recursive_generic(), we should provide a better label
when using that API with one merge base. So, when
merge_recursive_generic() is called with one merge base, set the label
to:
"constructed merge base"
Note that callers of merge_recursive_generic() include the builtin
commands git-am (in combination with git apply --build-fake-ancestor),
git-merge-recursive, and git-stash.
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-10-01 18:17:27 +00:00
|
|
|
assert(opt->ancestor == NULL ||
|
|
|
|
!strcmp(opt->ancestor, "constructed merge base"));
|
2019-08-17 18:41:29 +00:00
|
|
|
|
2021-09-08 11:23:57 +00:00
|
|
|
prepare_repo_settings(opt->repo);
|
|
|
|
opt->repo->settings.command_requires_full_index = 1;
|
|
|
|
|
2019-08-17 18:41:29 +00:00
|
|
|
if (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))
|
|
|
|
return -1;
|
2019-08-17 18:41:34 +00:00
|
|
|
clean = merge_recursive_internal(opt, h1, h2, merge_bases, result);
|
2019-08-17 18:41:29 +00:00
|
|
|
merge_finalize(opt);
|
|
|
|
|
2008-08-12 16:45:14 +00:00
|
|
|
return clean;
|
|
|
|
}
|
|
|
|
|
2019-08-17 18:41:33 +00:00
|
|
|
static struct commit *get_ref(struct repository *repo,
|
|
|
|
const struct object_id *oid,
|
2019-01-12 02:13:30 +00:00
|
|
|
const char *name)
|
2008-08-12 20:13:59 +00:00
|
|
|
{
|
|
|
|
struct object *object;
|
|
|
|
|
2019-01-12 02:13:30 +00:00
|
|
|
object = deref_tag(repo, parse_object(repo, oid),
|
|
|
|
name, strlen(name));
|
2008-08-12 20:13:59 +00:00
|
|
|
if (!object)
|
|
|
|
return NULL;
|
|
|
|
if (object->type == OBJ_TREE)
|
2019-01-12 02:13:30 +00:00
|
|
|
return make_virtual_commit(repo, (struct tree*)object, name);
|
2008-08-12 20:13:59 +00:00
|
|
|
if (object->type != OBJ_COMMIT)
|
|
|
|
return NULL;
|
libs: use "struct repository *" argument, not "the_repository"
As can easily be seen from grepping in our sources, we had these uses
of "the_repository" in various library code in cases where the
function in question was already getting a "struct repository *"
argument. Let's use that argument instead.
Out of these changes only the changes to "cache-tree.c",
"commit-reach.c", "shallow.c" and "upload-pack.c" would have cleanly
applied before the migration away from the "repo_*()" wrapper macros
in the preceding commits.
The rest aren't new, as we'd previously implicitly refer to
"the_repository", but it's now more obvious that we were doing the
wrong thing all along, and should have used the parameter instead.
The change to change "get_index_format_default(the_repository)" in
"read-cache.c" to use the "r" variable instead should arguably have
been part of [1], or in the subsequent cleanup in [2]. Let's do it
here, as can be seen from the initial code in [3] it's not important
that we use "the_repository" there, but would prefer to always use the
current repository.
This change excludes the "the_repository" use in "upload-pack.c"'s
upload_pack_advertise(), as the in-flight [4] makes that change.
1. ee1f0c242ef (read-cache: add index.skipHash config option,
2023-01-06)
2. 6269f8eaad0 (treewide: always have a valid "index_state.repo"
member, 2023-01-17)
3. 7211b9e7534 (repo-settings: consolidate some config settings,
2019-08-13)
4. <Y/hbUsGPVNAxTdmS@coredump.intra.peff.net>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-03-28 13:58:58 +00:00
|
|
|
if (repo_parse_commit(repo, (struct commit *)object))
|
2008-08-12 20:13:59 +00:00
|
|
|
return NULL;
|
|
|
|
return (struct commit *)object;
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
int merge_recursive_generic(struct merge_options *opt,
|
2016-06-24 23:09:28 +00:00
|
|
|
const struct object_id *head,
|
|
|
|
const struct object_id *merge,
|
2019-08-17 18:41:34 +00:00
|
|
|
int num_merge_bases,
|
|
|
|
const struct object_id **merge_bases,
|
2008-08-25 14:25:57 +00:00
|
|
|
struct commit **result)
|
2008-08-12 20:13:59 +00:00
|
|
|
{
|
2014-06-13 12:19:23 +00:00
|
|
|
int clean;
|
2017-10-05 20:32:04 +00:00
|
|
|
struct lock_file lock = LOCK_INIT;
|
2019-04-05 15:00:13 +00:00
|
|
|
struct commit *head_commit = get_ref(opt->repo, head, opt->branch1);
|
|
|
|
struct commit *next_commit = get_ref(opt->repo, merge, opt->branch2);
|
2008-08-12 20:13:59 +00:00
|
|
|
struct commit_list *ca = NULL;
|
|
|
|
|
2019-08-17 18:41:34 +00:00
|
|
|
if (merge_bases) {
|
2008-08-12 20:13:59 +00:00
|
|
|
int i;
|
2019-08-17 18:41:34 +00:00
|
|
|
for (i = 0; i < num_merge_bases; ++i) {
|
2008-08-12 20:13:59 +00:00
|
|
|
struct commit *base;
|
2019-08-17 18:41:34 +00:00
|
|
|
if (!(base = get_ref(opt->repo, merge_bases[i],
|
|
|
|
oid_to_hex(merge_bases[i]))))
|
2019-04-05 15:00:13 +00:00
|
|
|
return err(opt, _("Could not parse object '%s'"),
|
2019-08-17 18:41:34 +00:00
|
|
|
oid_to_hex(merge_bases[i]));
|
2008-08-12 20:13:59 +00:00
|
|
|
commit_list_insert(base, &ca);
|
|
|
|
}
|
merge-recursive: fix the diff3 common ancestor label for virtual commits
In commit 743474cbfa8b ("merge-recursive: provide a better label for
diff3 common ancestor", 2019-08-17), the label for the common ancestor
was changed from always being
"merged common ancestors"
to instead be based on the number of merge bases:
>=2: "merged common ancestors"
1: <abbreviated commit hash>
0: "<empty tree>"
Unfortunately, this did not take into account that when we have a single
merge base, that merge base could be fake or constructed. In such
cases, this resulted in a label of "00000000". Of course, the previous
label of "merged common ancestors" was also misleading for this case.
Since we have an API that is explicitly about creating fake merge base
commits in merge_recursive_generic(), we should provide a better label
when using that API with one merge base. So, when
merge_recursive_generic() is called with one merge base, set the label
to:
"constructed merge base"
Note that callers of merge_recursive_generic() include the builtin
commands git-am (in combination with git apply --build-fake-ancestor),
git-merge-recursive, and git-stash.
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-10-01 18:17:27 +00:00
|
|
|
if (num_merge_bases == 1)
|
|
|
|
opt->ancestor = "constructed merge base";
|
2008-08-12 20:13:59 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);
|
|
|
|
clean = merge_recursive(opt, head_commit, next_commit, ca,
|
2018-06-10 04:16:12 +00:00
|
|
|
result);
|
2018-02-28 19:07:56 +00:00
|
|
|
if (clean < 0) {
|
|
|
|
rollback_lock_file(&lock);
|
2016-07-26 16:06:07 +00:00
|
|
|
return clean;
|
2018-02-28 19:07:56 +00:00
|
|
|
}
|
2016-07-26 16:06:07 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
if (write_locked_index(opt->repo->index, &lock,
|
2018-03-01 20:40:20 +00:00
|
|
|
COMMIT_LOCK | SKIP_IF_UNCHANGED))
|
2019-04-05 15:00:13 +00:00
|
|
|
return err(opt, _("Unable to write index."));
|
2008-08-12 20:13:59 +00:00
|
|
|
|
|
|
|
return clean ? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
static void merge_recursive_config(struct merge_options *opt)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2018-05-02 16:01:14 +00:00
|
|
|
char *value = NULL;
|
2020-08-03 18:41:19 +00:00
|
|
|
int renormalize = 0;
|
2019-04-05 15:00:13 +00:00
|
|
|
git_config_get_int("merge.verbosity", &opt->verbosity);
|
2019-08-17 18:41:38 +00:00
|
|
|
git_config_get_int("diff.renamelimit", &opt->rename_limit);
|
|
|
|
git_config_get_int("merge.renamelimit", &opt->rename_limit);
|
2020-08-03 18:41:19 +00:00
|
|
|
git_config_get_bool("merge.renormalize", &renormalize);
|
|
|
|
opt->renormalize = renormalize;
|
2018-05-02 16:01:14 +00:00
|
|
|
if (!git_config_get_string("diff.renames", &value)) {
|
2019-08-17 18:41:38 +00:00
|
|
|
opt->detect_renames = git_config_rename("diff.renames", value);
|
2018-05-02 16:01:14 +00:00
|
|
|
free(value);
|
|
|
|
}
|
|
|
|
if (!git_config_get_string("merge.renames", &value)) {
|
2019-08-17 18:41:38 +00:00
|
|
|
opt->detect_renames = git_config_rename("merge.renames", value);
|
2018-05-02 16:01:14 +00:00
|
|
|
free(value);
|
|
|
|
}
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
if (!git_config_get_string("merge.directoryrenames", &value)) {
|
|
|
|
int boolval = git_parse_maybe_bool(value);
|
|
|
|
if (0 <= boolval) {
|
2019-08-17 18:41:25 +00:00
|
|
|
opt->detect_directory_renames = boolval ?
|
|
|
|
MERGE_DIRECTORY_RENAMES_TRUE :
|
|
|
|
MERGE_DIRECTORY_RENAMES_NONE;
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
} else if (!strcasecmp(value, "conflict")) {
|
2019-08-17 18:41:25 +00:00
|
|
|
opt->detect_directory_renames =
|
|
|
|
MERGE_DIRECTORY_RENAMES_CONFLICT;
|
merge-recursive: switch directory rename detection default
When all of x/a, x/b, and x/c have moved to z/a, z/b, and z/c on one
branch, there is a question about whether x/d added on a different
branch should remain at x/d or appear at z/d when the two branches are
merged. There are different possible viewpoints here:
A) The file was placed at x/d; it's unrelated to the other files in
x/ so it doesn't matter that all the files from x/ moved to z/ on
one branch; x/d should still remain at x/d.
B) x/d is related to the other files in x/, and x/ was renamed to z/;
therefore x/d should be moved to z/d.
Since there was no ability to detect directory renames prior to
git-2.18, users experienced (A) regardless of context. Choice (B) was
implemented in git-2.18, with no option to go back to (A), and has been
in use since. However, one user reported that the merge results did not
match their expectations, making the change of default problematic,
especially since there was no notice printed when directory rename
detection moved files.
Note that there is also a third possibility here:
C) There are different answers depending on the context and content
that cannot be determined by git, so this is a conflict. Use a
higher stage in the index to record the conflict and notify the
user of the potential issue instead of silently selecting a
resolution for them.
Add an option for users to specify their preference for whether to use
directory rename detection, and default to (C). Even when directory
rename detection is on, add notice messages about files moved into new
directories.
As a sidenote, x/d did not have to be a new file here; it could have
already existed at some other path and been renamed to x/d, with
directory rename detection just renaming it again to z/d. Thus, it's
not just new files, but also a modification to all rename types (normal
renames, rename/add, rename/delete, rename/rename(1to1),
rename/rename(1to2), and rename/rename(2to1)).
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-05 15:00:26 +00:00
|
|
|
} /* avoid erroring on values from future versions of git */
|
2018-05-02 16:01:14 +00:00
|
|
|
free(value);
|
|
|
|
}
|
2014-08-13 08:22:01 +00:00
|
|
|
git_config(git_xmerge_config, NULL);
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
void init_merge_options(struct merge_options *opt,
|
2019-01-12 02:13:29 +00:00
|
|
|
struct repository *repo)
|
2008-08-12 16:45:14 +00:00
|
|
|
{
|
2017-10-31 09:09:13 +00:00
|
|
|
const char *merge_verbosity;
|
2019-04-05 15:00:13 +00:00
|
|
|
memset(opt, 0, sizeof(struct merge_options));
|
2019-08-17 18:41:39 +00:00
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->repo = repo;
|
2019-08-17 18:41:39 +00:00
|
|
|
|
|
|
|
opt->detect_renames = -1;
|
|
|
|
opt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;
|
|
|
|
opt->rename_limit = -1;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->verbosity = 2;
|
|
|
|
opt->buffer_output = 1;
|
2019-08-17 18:41:39 +00:00
|
|
|
strbuf_init(&opt->obuf, 0);
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->renormalize = 0;
|
2019-08-17 18:41:39 +00:00
|
|
|
|
2024-03-14 17:05:05 +00:00
|
|
|
opt->conflict_style = -1;
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
merge_recursive_config(opt);
|
2017-10-31 09:09:13 +00:00
|
|
|
merge_verbosity = getenv("GIT_MERGE_VERBOSITY");
|
|
|
|
if (merge_verbosity)
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->verbosity = strtol(merge_verbosity, NULL, 10);
|
|
|
|
if (opt->verbosity >= 5)
|
|
|
|
opt->buffer_output = 0;
|
2008-08-12 16:45:14 +00:00
|
|
|
}
|
2010-08-26 05:47:58 +00:00
|
|
|
|
2023-10-11 20:37:47 +00:00
|
|
|
/*
|
|
|
|
* For now, members of merge_options do not need deep copying, but
|
|
|
|
* it may change in the future, in which case we would need to update
|
|
|
|
* this, and also make a matching change to clear_merge_options() to
|
|
|
|
* release the resources held by a copied instance.
|
|
|
|
*/
|
|
|
|
void copy_merge_options(struct merge_options *dst, struct merge_options *src)
|
|
|
|
{
|
|
|
|
*dst = *src;
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear_merge_options(struct merge_options *opt UNUSED)
|
|
|
|
{
|
|
|
|
; /* no-op as our copy is shallow right now */
|
|
|
|
}
|
|
|
|
|
2019-04-05 15:00:13 +00:00
|
|
|
int parse_merge_opt(struct merge_options *opt, const char *s)
|
2010-08-26 05:47:58 +00:00
|
|
|
{
|
2014-06-18 19:48:29 +00:00
|
|
|
const char *arg;
|
|
|
|
|
2010-08-26 05:47:58 +00:00
|
|
|
if (!s || !*s)
|
|
|
|
return -1;
|
|
|
|
if (!strcmp(s, "ours"))
|
2019-08-17 18:41:42 +00:00
|
|
|
opt->recursive_variant = MERGE_VARIANT_OURS;
|
2010-08-26 05:47:58 +00:00
|
|
|
else if (!strcmp(s, "theirs"))
|
2019-08-17 18:41:42 +00:00
|
|
|
opt->recursive_variant = MERGE_VARIANT_THEIRS;
|
2010-08-26 05:47:58 +00:00
|
|
|
else if (!strcmp(s, "subtree"))
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->subtree_shift = "";
|
2014-06-18 19:48:29 +00:00
|
|
|
else if (skip_prefix(s, "subtree=", &arg))
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->subtree_shift = arg;
|
2010-08-26 05:50:45 +00:00
|
|
|
else if (!strcmp(s, "patience"))
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->xdl_opts = DIFF_WITH_ALG(opt, PATIENCE_DIFF);
|
2011-07-12 06:10:25 +00:00
|
|
|
else if (!strcmp(s, "histogram"))
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF);
|
2014-06-18 19:48:29 +00:00
|
|
|
else if (skip_prefix(s, "diff-algorithm=", &arg)) {
|
|
|
|
long value = parse_algorithm_value(arg);
|
2013-01-16 07:51:58 +00:00
|
|
|
if (value < 0)
|
|
|
|
return -1;
|
|
|
|
/* clear out previous settings */
|
2019-04-05 15:00:13 +00:00
|
|
|
DIFF_XDL_CLR(opt, NEED_MINIMAL);
|
|
|
|
opt->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
|
|
|
|
opt->xdl_opts |= value;
|
2013-01-16 07:51:58 +00:00
|
|
|
}
|
2010-08-26 05:51:47 +00:00
|
|
|
else if (!strcmp(s, "ignore-space-change"))
|
2019-04-05 15:00:13 +00:00
|
|
|
DIFF_XDL_SET(opt, IGNORE_WHITESPACE_CHANGE);
|
2010-08-26 05:51:47 +00:00
|
|
|
else if (!strcmp(s, "ignore-all-space"))
|
2019-04-05 15:00:13 +00:00
|
|
|
DIFF_XDL_SET(opt, IGNORE_WHITESPACE);
|
2010-08-26 05:51:47 +00:00
|
|
|
else if (!strcmp(s, "ignore-space-at-eol"))
|
2019-04-05 15:00:13 +00:00
|
|
|
DIFF_XDL_SET(opt, IGNORE_WHITESPACE_AT_EOL);
|
2017-10-26 06:32:27 +00:00
|
|
|
else if (!strcmp(s, "ignore-cr-at-eol"))
|
2019-04-05 15:00:13 +00:00
|
|
|
DIFF_XDL_SET(opt, IGNORE_CR_AT_EOL);
|
2010-08-26 05:47:58 +00:00
|
|
|
else if (!strcmp(s, "renormalize"))
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->renormalize = 1;
|
2010-08-26 05:47:58 +00:00
|
|
|
else if (!strcmp(s, "no-renormalize"))
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->renormalize = 0;
|
2016-02-17 03:15:25 +00:00
|
|
|
else if (!strcmp(s, "no-renames"))
|
2019-08-17 18:41:38 +00:00
|
|
|
opt->detect_renames = 0;
|
2016-02-21 22:59:05 +00:00
|
|
|
else if (!strcmp(s, "find-renames")) {
|
2019-08-17 18:41:38 +00:00
|
|
|
opt->detect_renames = 1;
|
2019-04-05 15:00:13 +00:00
|
|
|
opt->rename_score = 0;
|
2016-02-21 22:59:05 +00:00
|
|
|
}
|
2016-02-17 03:15:26 +00:00
|
|
|
else if (skip_prefix(s, "find-renames=", &arg) ||
|
|
|
|
skip_prefix(s, "rename-threshold=", &arg)) {
|
2019-04-05 15:00:13 +00:00
|
|
|
if ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)
|
2010-09-27 23:58:25 +00:00
|
|
|
return -1;
|
2019-08-17 18:41:38 +00:00
|
|
|
opt->detect_renames = 1;
|
2010-09-27 23:58:25 +00:00
|
|
|
}
|
2019-02-16 11:24:41 +00:00
|
|
|
/*
|
|
|
|
* Please update $__git_merge_strategy_options in
|
|
|
|
* git-completion.bash when you add new options
|
|
|
|
*/
|
2010-08-26 05:47:58 +00:00
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
return 0;
|
|
|
|
}
|