summaryrefslogtreecommitdiff
path: root/src/libgit2/merge.h
blob: 23932905e8089ca94d2935f193d6d8008eb0916f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
#ifndef INCLUDE_merge_h__
#define INCLUDE_merge_h__

#include "common.h"

#include "vector.h"
#include "commit_list.h"
#include "pool.h"
#include "iterator.h"

#include "git2/types.h"
#include "git2/merge.h"
#include "git2/sys/merge.h"

#define GIT_MERGE_MSG_FILE		"MERGE_MSG"
#define GIT_MERGE_MODE_FILE		"MERGE_MODE"
#define GIT_MERGE_FILE_MODE		0666

#define GIT_MERGE_DEFAULT_RENAME_THRESHOLD	50
#define GIT_MERGE_DEFAULT_TARGET_LIMIT		1000

/** Types of changes when files are merged from branch to branch. */
typedef enum {
	/* No conflict - a change only occurs in one branch. */
	GIT_MERGE_DIFF_NONE = 0,

	/* Occurs when a file is modified in both branches. */
	GIT_MERGE_DIFF_BOTH_MODIFIED = (1 << 0),

	/* Occurs when a file is added in both branches. */
	GIT_MERGE_DIFF_BOTH_ADDED = (1 << 1),

	/* Occurs when a file is deleted in both branches. */
	GIT_MERGE_DIFF_BOTH_DELETED = (1 << 2),

	/* Occurs when a file is modified in one branch and deleted in the other. */
	GIT_MERGE_DIFF_MODIFIED_DELETED = (1 << 3),

	/* Occurs when a file is renamed in one branch and modified in the other. */
	GIT_MERGE_DIFF_RENAMED_MODIFIED = (1 << 4),

	/* Occurs when a file is renamed in one branch and deleted in the other. */
	GIT_MERGE_DIFF_RENAMED_DELETED = (1 << 5),

	/* Occurs when a file is renamed in one branch and a file with the same
	 * name is added in the other.  Eg, A->B and new file B.  Core git calls
	 * this a "rename/delete". */
	GIT_MERGE_DIFF_RENAMED_ADDED = (1 << 6),

	/* Occurs when both a file is renamed to the same name in the ours and
	 * theirs branches.  Eg, A->B and A->B in both.  Automergeable. */
	GIT_MERGE_DIFF_BOTH_RENAMED = (1 << 7),

	/* Occurs when a file is renamed to different names in the ours and theirs
	 * branches.  Eg, A->B and A->C. */
	GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 = (1 << 8),

	/* Occurs when two files are renamed to the same name in the ours and
	 * theirs branches.  Eg, A->C and B->C. */
	GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 = (1 << 9),

	/* Occurs when an item at a path in one branch is a directory, and an
	 * item at the same path in a different branch is a file. */
	GIT_MERGE_DIFF_DIRECTORY_FILE = (1 << 10),

	/* The child of a folder that is in a directory/file conflict. */
	GIT_MERGE_DIFF_DF_CHILD = (1 << 11)
} git_merge_diff_t;

typedef struct {
	git_repository *repo;
	git_pool pool;

	/* Vector of git_index_entry that represent the merged items that
	 * have been staged, either because only one side changed, or because
	 * the two changes were non-conflicting and mergeable.  These items
	 * will be written as staged entries in the main index.
	 */
	git_vector staged;

	/* Vector of git_merge_diff entries that represent the conflicts that
	 * have not been automerged.  These items will be written to high-stage
	 * entries in the main index.
	 */
	git_vector conflicts;

	/* Vector of git_merge_diff that have been automerged.  These items
	 * will be written to the REUC when the index is produced.
	 */
	git_vector resolved;
} git_merge_diff_list;

/**
 * Description of changes to one file across three trees.
 */
typedef struct {
	git_merge_diff_t type;

	git_index_entry ancestor_entry;

	git_index_entry our_entry;
	git_delta_t our_status;

	git_index_entry their_entry;
	git_delta_t their_status;

} git_merge_diff;

int git_merge__bases_many(
	git_commit_list **out,
	git_revwalk *walk,
	git_commit_list_node *one,
	git_vector *twos,
	uint32_t minimum_generation);

/*
 * Three-way tree differencing
 */

git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo);

int git_merge_diff_list__find_differences(
	git_merge_diff_list *merge_diff_list,
	git_iterator *ancestor_iterator,
	git_iterator *ours_iter,
	git_iterator *theirs_iter);

int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_options *opts);

void git_merge_diff_list__free(git_merge_diff_list *diff_list);

/* Merge metadata setup */

int git_merge__setup(
	git_repository *repo,
	const git_annotated_commit *our_head,
	const git_annotated_commit *heads[],
	size_t heads_len);

int git_merge__iterators(
	git_index **out,
	git_repository *repo,
	git_iterator *ancestor_iter,
	git_iterator *our_iter,
	git_iterator *their_iter,
	const git_merge_options *given_opts);

int git_merge__check_result(git_repository *repo, git_index *index_new);

int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index);

/* Merge files */

GIT_INLINE(const char *) git_merge_file__best_path(
	const char *ancestor,
	const char *ours,
	const char *theirs)
{
	if (!ancestor) {
		if (ours && theirs && strcmp(ours, theirs) == 0)
			return ours;

		return NULL;
	}

	if (ours && strcmp(ancestor, ours) == 0)
		return theirs;
	else if(theirs && strcmp(ancestor, theirs) == 0)
		return ours;

	return NULL;
}

GIT_INLINE(uint32_t) git_merge_file__best_mode(
	uint32_t ancestor, uint32_t ours, uint32_t theirs)
{
	/*
	 * If ancestor didn't exist and either ours or theirs is executable,
	 * assume executable.  Otherwise, if any mode changed from the ancestor,
	 * use that one.
	 */
	if (!ancestor) {
		if (ours == GIT_FILEMODE_BLOB_EXECUTABLE ||
			theirs == GIT_FILEMODE_BLOB_EXECUTABLE)
			return GIT_FILEMODE_BLOB_EXECUTABLE;

		return GIT_FILEMODE_BLOB;
	} else if (ours && theirs) {
		if (ancestor == ours)
			return theirs;

		return ours;
	}

	return 0;
}

#endif