Line | Count | Source (jump to first uncovered line) |
1 | | #define USE_THE_REPOSITORY_VARIABLE |
2 | | |
3 | | #include "git-compat-util.h" |
4 | | #include "advice.h" |
5 | | #include "commit.h" |
6 | | #include "gettext.h" |
7 | | #include "refs.h" |
8 | | #include "object-file.h" |
9 | | #include "object-name.h" |
10 | | #include "object-store-ll.h" |
11 | | #include "path.h" |
12 | | #include "repository.h" |
13 | | #include "diff.h" |
14 | | #include "diffcore.h" |
15 | | #include "hex.h" |
16 | | #include "xdiff-interface.h" |
17 | | #include "merge-ll.h" |
18 | | #include "dir.h" |
19 | | #include "notes.h" |
20 | | #include "notes-merge.h" |
21 | | #include "strbuf.h" |
22 | | #include "trace.h" |
23 | | #include "notes-utils.h" |
24 | | #include "commit-reach.h" |
25 | | |
26 | | struct notes_merge_pair { |
27 | | struct object_id obj, base, local, remote; |
28 | | }; |
29 | | |
30 | | void init_notes_merge_options(struct repository *r, |
31 | | struct notes_merge_options *o) |
32 | 0 | { |
33 | 0 | memset(o, 0, sizeof(struct notes_merge_options)); |
34 | 0 | strbuf_init(&(o->commit_msg), 0); |
35 | 0 | o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT; |
36 | 0 | o->repo = r; |
37 | 0 | } |
38 | | |
39 | | static int path_to_oid(const char *path, struct object_id *oid) |
40 | 0 | { |
41 | 0 | char hex_oid[GIT_MAX_HEXSZ]; |
42 | 0 | int i = 0; |
43 | 0 | while (*path && i < the_hash_algo->hexsz) { |
44 | 0 | if (*path != '/') |
45 | 0 | hex_oid[i++] = *path; |
46 | 0 | path++; |
47 | 0 | } |
48 | 0 | if (*path || i != the_hash_algo->hexsz) |
49 | 0 | return -1; |
50 | 0 | return get_oid_hex(hex_oid, oid); |
51 | 0 | } |
52 | | |
53 | | static int verify_notes_filepair(struct diff_filepair *p, struct object_id *oid) |
54 | 0 | { |
55 | 0 | switch (p->status) { |
56 | 0 | case DIFF_STATUS_MODIFIED: |
57 | 0 | assert(p->one->mode == p->two->mode); |
58 | 0 | assert(!is_null_oid(&p->one->oid)); |
59 | 0 | assert(!is_null_oid(&p->two->oid)); |
60 | 0 | break; |
61 | 0 | case DIFF_STATUS_ADDED: |
62 | 0 | assert(is_null_oid(&p->one->oid)); |
63 | 0 | break; |
64 | 0 | case DIFF_STATUS_DELETED: |
65 | 0 | assert(is_null_oid(&p->two->oid)); |
66 | 0 | break; |
67 | 0 | default: |
68 | 0 | return -1; |
69 | 0 | } |
70 | 0 | assert(!strcmp(p->one->path, p->two->path)); |
71 | 0 | return path_to_oid(p->one->path, oid); |
72 | 0 | } |
73 | | |
74 | | static struct notes_merge_pair *find_notes_merge_pair_pos( |
75 | | struct notes_merge_pair *list, int len, struct object_id *obj, |
76 | | int insert_new, int *occupied) |
77 | 0 | { |
78 | | /* |
79 | | * Both diff_tree_remote() and diff_tree_local() tend to process |
80 | | * merge_pairs in ascending order. Therefore, cache last returned |
81 | | * index, and search sequentially from there until the appropriate |
82 | | * position is found. |
83 | | * |
84 | | * Since inserts only happen from diff_tree_remote() (which mainly |
85 | | * _appends_), we don't care that inserting into the middle of the |
86 | | * list is expensive (using memmove()). |
87 | | */ |
88 | 0 | static int last_index; |
89 | 0 | int i = last_index < len ? last_index : len - 1; |
90 | 0 | int prev_cmp = 0, cmp = -1; |
91 | 0 | while (i >= 0 && i < len) { |
92 | 0 | cmp = oidcmp(obj, &list[i].obj); |
93 | 0 | if (!cmp) /* obj belongs @ i */ |
94 | 0 | break; |
95 | 0 | else if (cmp < 0 && prev_cmp <= 0) /* obj belongs < i */ |
96 | 0 | i--; |
97 | 0 | else if (cmp < 0) /* obj belongs between i-1 and i */ |
98 | 0 | break; |
99 | 0 | else if (cmp > 0 && prev_cmp >= 0) /* obj belongs > i */ |
100 | 0 | i++; |
101 | 0 | else /* if (cmp > 0) */ { /* obj belongs between i and i+1 */ |
102 | 0 | i++; |
103 | 0 | break; |
104 | 0 | } |
105 | 0 | prev_cmp = cmp; |
106 | 0 | } |
107 | 0 | if (i < 0) |
108 | 0 | i = 0; |
109 | | /* obj belongs at, or immediately preceding, index i (0 <= i <= len) */ |
110 | |
|
111 | 0 | if (!cmp) |
112 | 0 | *occupied = 1; |
113 | 0 | else { |
114 | 0 | *occupied = 0; |
115 | 0 | if (insert_new && i < len) { |
116 | 0 | MOVE_ARRAY(list + i + 1, list + i, len - i); |
117 | 0 | memset(list + i, 0, sizeof(struct notes_merge_pair)); |
118 | 0 | } |
119 | 0 | } |
120 | 0 | last_index = i; |
121 | 0 | return list + i; |
122 | 0 | } |
123 | | |
124 | | static struct object_id uninitialized = { |
125 | | .hash = |
126 | | "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \ |
127 | | "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" |
128 | | }; |
129 | | |
130 | | static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, |
131 | | const struct object_id *base, |
132 | | const struct object_id *remote, |
133 | | int *num_changes) |
134 | 0 | { |
135 | 0 | struct diff_options opt; |
136 | 0 | struct notes_merge_pair *changes; |
137 | 0 | int i, len = 0; |
138 | |
|
139 | 0 | trace_printf("\tdiff_tree_remote(base = %.7s, remote = %.7s)\n", |
140 | 0 | oid_to_hex(base), oid_to_hex(remote)); |
141 | |
|
142 | 0 | repo_diff_setup(o->repo, &opt); |
143 | 0 | opt.flags.recursive = 1; |
144 | 0 | opt.output_format = DIFF_FORMAT_NO_OUTPUT; |
145 | 0 | diff_setup_done(&opt); |
146 | 0 | diff_tree_oid(base, remote, "", &opt); |
147 | 0 | diffcore_std(&opt); |
148 | |
|
149 | 0 | CALLOC_ARRAY(changes, diff_queued_diff.nr); |
150 | |
|
151 | 0 | for (i = 0; i < diff_queued_diff.nr; i++) { |
152 | 0 | struct diff_filepair *p = diff_queued_diff.queue[i]; |
153 | 0 | struct notes_merge_pair *mp; |
154 | 0 | int occupied; |
155 | 0 | struct object_id obj; |
156 | |
|
157 | 0 | if (verify_notes_filepair(p, &obj)) { |
158 | 0 | trace_printf("\t\tCannot merge entry '%s' (%c): " |
159 | 0 | "%.7s -> %.7s. Skipping!\n", p->one->path, |
160 | 0 | p->status, oid_to_hex(&p->one->oid), |
161 | 0 | oid_to_hex(&p->two->oid)); |
162 | 0 | continue; |
163 | 0 | } |
164 | 0 | mp = find_notes_merge_pair_pos(changes, len, &obj, 1, &occupied); |
165 | 0 | if (occupied) { |
166 | | /* We've found an addition/deletion pair */ |
167 | 0 | assert(oideq(&mp->obj, &obj)); |
168 | 0 | if (is_null_oid(&p->one->oid)) { /* addition */ |
169 | 0 | assert(is_null_oid(&mp->remote)); |
170 | 0 | oidcpy(&mp->remote, &p->two->oid); |
171 | 0 | } else if (is_null_oid(&p->two->oid)) { /* deletion */ |
172 | 0 | assert(is_null_oid(&mp->base)); |
173 | 0 | oidcpy(&mp->base, &p->one->oid); |
174 | 0 | } else |
175 | 0 | assert(!"Invalid existing change recorded"); |
176 | 0 | } else { |
177 | 0 | oidcpy(&mp->obj, &obj); |
178 | 0 | oidcpy(&mp->base, &p->one->oid); |
179 | 0 | oidcpy(&mp->local, &uninitialized); |
180 | 0 | oidcpy(&mp->remote, &p->two->oid); |
181 | 0 | len++; |
182 | 0 | } |
183 | 0 | trace_printf("\t\tStored remote change for %s: %.7s -> %.7s\n", |
184 | 0 | oid_to_hex(&mp->obj), oid_to_hex(&mp->base), |
185 | 0 | oid_to_hex(&mp->remote)); |
186 | 0 | } |
187 | 0 | diff_flush(&opt); |
188 | |
|
189 | 0 | *num_changes = len; |
190 | 0 | return changes; |
191 | 0 | } |
192 | | |
193 | | static void diff_tree_local(struct notes_merge_options *o, |
194 | | struct notes_merge_pair *changes, int len, |
195 | | const struct object_id *base, |
196 | | const struct object_id *local) |
197 | 0 | { |
198 | 0 | struct diff_options opt; |
199 | 0 | int i; |
200 | |
|
201 | 0 | trace_printf("\tdiff_tree_local(len = %i, base = %.7s, local = %.7s)\n", |
202 | 0 | len, oid_to_hex(base), oid_to_hex(local)); |
203 | |
|
204 | 0 | repo_diff_setup(o->repo, &opt); |
205 | 0 | opt.flags.recursive = 1; |
206 | 0 | opt.output_format = DIFF_FORMAT_NO_OUTPUT; |
207 | 0 | diff_setup_done(&opt); |
208 | 0 | diff_tree_oid(base, local, "", &opt); |
209 | 0 | diffcore_std(&opt); |
210 | |
|
211 | 0 | for (i = 0; i < diff_queued_diff.nr; i++) { |
212 | 0 | struct diff_filepair *p = diff_queued_diff.queue[i]; |
213 | 0 | struct notes_merge_pair *mp; |
214 | 0 | int match; |
215 | 0 | struct object_id obj; |
216 | |
|
217 | 0 | if (verify_notes_filepair(p, &obj)) { |
218 | 0 | trace_printf("\t\tCannot merge entry '%s' (%c): " |
219 | 0 | "%.7s -> %.7s. Skipping!\n", p->one->path, |
220 | 0 | p->status, oid_to_hex(&p->one->oid), |
221 | 0 | oid_to_hex(&p->two->oid)); |
222 | 0 | continue; |
223 | 0 | } |
224 | 0 | mp = find_notes_merge_pair_pos(changes, len, &obj, 0, &match); |
225 | 0 | if (!match) { |
226 | 0 | trace_printf("\t\tIgnoring local-only change for %s: " |
227 | 0 | "%.7s -> %.7s\n", oid_to_hex(&obj), |
228 | 0 | oid_to_hex(&p->one->oid), |
229 | 0 | oid_to_hex(&p->two->oid)); |
230 | 0 | continue; |
231 | 0 | } |
232 | | |
233 | 0 | assert(oideq(&mp->obj, &obj)); |
234 | 0 | if (is_null_oid(&p->two->oid)) { /* deletion */ |
235 | | /* |
236 | | * Either this is a true deletion (1), or it is part |
237 | | * of an A/D pair (2), or D/A pair (3): |
238 | | * |
239 | | * (1) mp->local is uninitialized; set it to null_sha1 |
240 | | * (2) mp->local is not uninitialized; don't touch it |
241 | | * (3) mp->local is uninitialized; set it to null_sha1 |
242 | | * (will be overwritten by following addition) |
243 | | */ |
244 | 0 | if (oideq(&mp->local, &uninitialized)) |
245 | 0 | oidclr(&mp->local, the_repository->hash_algo); |
246 | 0 | } else if (is_null_oid(&p->one->oid)) { /* addition */ |
247 | | /* |
248 | | * Either this is a true addition (1), or it is part |
249 | | * of an A/D pair (2), or D/A pair (3): |
250 | | * |
251 | | * (1) mp->local is uninitialized; set to p->two->sha1 |
252 | | * (2) mp->local is uninitialized; set to p->two->sha1 |
253 | | * (3) mp->local is null_sha1; set to p->two->sha1 |
254 | | */ |
255 | 0 | assert(is_null_oid(&mp->local) || |
256 | 0 | oideq(&mp->local, &uninitialized)); |
257 | 0 | oidcpy(&mp->local, &p->two->oid); |
258 | 0 | } else { /* modification */ |
259 | | /* |
260 | | * This is a true modification. p->one->sha1 shall |
261 | | * match mp->base, and mp->local shall be uninitialized. |
262 | | * Set mp->local to p->two->sha1. |
263 | | */ |
264 | 0 | assert(oideq(&p->one->oid, &mp->base)); |
265 | 0 | assert(oideq(&mp->local, &uninitialized)); |
266 | 0 | oidcpy(&mp->local, &p->two->oid); |
267 | 0 | } |
268 | 0 | trace_printf("\t\tStored local change for %s: %.7s -> %.7s\n", |
269 | 0 | oid_to_hex(&mp->obj), oid_to_hex(&mp->base), |
270 | 0 | oid_to_hex(&mp->local)); |
271 | 0 | } |
272 | 0 | diff_flush(&opt); |
273 | 0 | } |
274 | | |
275 | | static void check_notes_merge_worktree(struct notes_merge_options *o) |
276 | 0 | { |
277 | 0 | if (!o->has_worktree) { |
278 | | /* |
279 | | * Must establish NOTES_MERGE_WORKTREE. |
280 | | * Abort if NOTES_MERGE_WORKTREE already exists |
281 | | */ |
282 | 0 | if (file_exists(git_path(NOTES_MERGE_WORKTREE)) && |
283 | 0 | !is_empty_dir(git_path(NOTES_MERGE_WORKTREE))) { |
284 | 0 | if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) |
285 | 0 | die(_("You have not concluded your previous " |
286 | 0 | "notes merge (%s exists).\nPlease, use " |
287 | 0 | "'git notes merge --commit' or 'git notes " |
288 | 0 | "merge --abort' to commit/abort the " |
289 | 0 | "previous merge before you start a new " |
290 | 0 | "notes merge."), git_path("NOTES_MERGE_*")); |
291 | 0 | else |
292 | 0 | die(_("You have not concluded your notes merge " |
293 | 0 | "(%s exists)."), git_path("NOTES_MERGE_*")); |
294 | 0 | } |
295 | | |
296 | 0 | if (safe_create_leading_directories_const(git_path( |
297 | 0 | NOTES_MERGE_WORKTREE "/.test"))) |
298 | 0 | die_errno("unable to create directory %s", |
299 | 0 | git_path(NOTES_MERGE_WORKTREE)); |
300 | 0 | o->has_worktree = 1; |
301 | 0 | } else if (!file_exists(git_path(NOTES_MERGE_WORKTREE))) |
302 | | /* NOTES_MERGE_WORKTREE should already be established */ |
303 | 0 | die("missing '%s'. This should not happen", |
304 | 0 | git_path(NOTES_MERGE_WORKTREE)); |
305 | 0 | } |
306 | | |
307 | | static void write_buf_to_worktree(const struct object_id *obj, |
308 | | const char *buf, unsigned long size) |
309 | 0 | { |
310 | 0 | int fd; |
311 | 0 | char *path = git_pathdup(NOTES_MERGE_WORKTREE "/%s", oid_to_hex(obj)); |
312 | 0 | if (safe_create_leading_directories_const(path)) |
313 | 0 | die_errno("unable to create directory for '%s'", path); |
314 | | |
315 | 0 | fd = xopen(path, O_WRONLY | O_EXCL | O_CREAT, 0666); |
316 | |
|
317 | 0 | while (size > 0) { |
318 | 0 | ssize_t ret = write_in_full(fd, buf, size); |
319 | 0 | if (ret < 0) { |
320 | | /* Ignore epipe */ |
321 | 0 | if (errno == EPIPE) |
322 | 0 | break; |
323 | 0 | die_errno("notes-merge"); |
324 | 0 | } |
325 | 0 | size -= ret; |
326 | 0 | buf += ret; |
327 | 0 | } |
328 | | |
329 | 0 | close(fd); |
330 | 0 | free(path); |
331 | 0 | } |
332 | | |
333 | | static void write_note_to_worktree(const struct object_id *obj, |
334 | | const struct object_id *note) |
335 | 0 | { |
336 | 0 | enum object_type type; |
337 | 0 | unsigned long size; |
338 | 0 | void *buf = repo_read_object_file(the_repository, note, &type, &size); |
339 | |
|
340 | 0 | if (!buf) |
341 | 0 | die("cannot read note %s for object %s", |
342 | 0 | oid_to_hex(note), oid_to_hex(obj)); |
343 | 0 | if (type != OBJ_BLOB) |
344 | 0 | die("blob expected in note %s for object %s", |
345 | 0 | oid_to_hex(note), oid_to_hex(obj)); |
346 | 0 | write_buf_to_worktree(obj, buf, size); |
347 | 0 | free(buf); |
348 | 0 | } |
349 | | |
350 | | static int ll_merge_in_worktree(struct notes_merge_options *o, |
351 | | struct notes_merge_pair *p) |
352 | 0 | { |
353 | 0 | mmbuffer_t result_buf; |
354 | 0 | mmfile_t base, local, remote; |
355 | 0 | enum ll_merge_result status; |
356 | |
|
357 | 0 | read_mmblob(&base, &p->base); |
358 | 0 | read_mmblob(&local, &p->local); |
359 | 0 | read_mmblob(&remote, &p->remote); |
360 | |
|
361 | 0 | status = ll_merge(&result_buf, oid_to_hex(&p->obj), &base, NULL, |
362 | 0 | &local, o->local_ref, &remote, o->remote_ref, |
363 | 0 | o->repo->index, NULL); |
364 | |
|
365 | 0 | free(base.ptr); |
366 | 0 | free(local.ptr); |
367 | 0 | free(remote.ptr); |
368 | |
|
369 | 0 | if (status == LL_MERGE_BINARY_CONFLICT) |
370 | 0 | warning("Cannot merge binary files: %s (%s vs. %s)", |
371 | 0 | oid_to_hex(&p->obj), o->local_ref, o->remote_ref); |
372 | 0 | if ((status < 0) || !result_buf.ptr) |
373 | 0 | die("Failed to execute internal merge"); |
374 | | |
375 | 0 | write_buf_to_worktree(&p->obj, result_buf.ptr, result_buf.size); |
376 | 0 | free(result_buf.ptr); |
377 | |
|
378 | 0 | return status; |
379 | 0 | } |
380 | | |
381 | | static int merge_one_change_manual(struct notes_merge_options *o, |
382 | | struct notes_merge_pair *p, |
383 | | struct notes_tree *t) |
384 | 0 | { |
385 | 0 | const char *lref = o->local_ref ? o->local_ref : "local version"; |
386 | 0 | const char *rref = o->remote_ref ? o->remote_ref : "remote version"; |
387 | |
|
388 | 0 | trace_printf("\t\t\tmerge_one_change_manual(obj = %.7s, base = %.7s, " |
389 | 0 | "local = %.7s, remote = %.7s)\n", |
390 | 0 | oid_to_hex(&p->obj), oid_to_hex(&p->base), |
391 | 0 | oid_to_hex(&p->local), oid_to_hex(&p->remote)); |
392 | | |
393 | | /* add "Conflicts:" section to commit message first time through */ |
394 | 0 | if (!o->has_worktree) |
395 | 0 | strbuf_addstr(&(o->commit_msg), "\n\nConflicts:\n"); |
396 | |
|
397 | 0 | strbuf_addf(&(o->commit_msg), "\t%s\n", oid_to_hex(&p->obj)); |
398 | |
|
399 | 0 | if (o->verbosity >= 2) |
400 | 0 | printf("Auto-merging notes for %s\n", oid_to_hex(&p->obj)); |
401 | 0 | check_notes_merge_worktree(o); |
402 | 0 | if (is_null_oid(&p->local)) { |
403 | | /* D/F conflict, checkout p->remote */ |
404 | 0 | assert(!is_null_oid(&p->remote)); |
405 | 0 | if (o->verbosity >= 1) |
406 | 0 | printf("CONFLICT (delete/modify): Notes for object %s " |
407 | 0 | "deleted in %s and modified in %s. Version from %s " |
408 | 0 | "left in tree.\n", |
409 | 0 | oid_to_hex(&p->obj), lref, rref, rref); |
410 | 0 | write_note_to_worktree(&p->obj, &p->remote); |
411 | 0 | } else if (is_null_oid(&p->remote)) { |
412 | | /* D/F conflict, checkout p->local */ |
413 | 0 | assert(!is_null_oid(&p->local)); |
414 | 0 | if (o->verbosity >= 1) |
415 | 0 | printf("CONFLICT (delete/modify): Notes for object %s " |
416 | 0 | "deleted in %s and modified in %s. Version from %s " |
417 | 0 | "left in tree.\n", |
418 | 0 | oid_to_hex(&p->obj), rref, lref, lref); |
419 | 0 | write_note_to_worktree(&p->obj, &p->local); |
420 | 0 | } else { |
421 | | /* "regular" conflict, checkout result of ll_merge() */ |
422 | 0 | const char *reason = "content"; |
423 | 0 | if (is_null_oid(&p->base)) |
424 | 0 | reason = "add/add"; |
425 | 0 | assert(!is_null_oid(&p->local)); |
426 | 0 | assert(!is_null_oid(&p->remote)); |
427 | 0 | if (o->verbosity >= 1) |
428 | 0 | printf("CONFLICT (%s): Merge conflict in notes for " |
429 | 0 | "object %s\n", reason, |
430 | 0 | oid_to_hex(&p->obj)); |
431 | 0 | ll_merge_in_worktree(o, p); |
432 | 0 | } |
433 | | |
434 | 0 | trace_printf("\t\t\tremoving from partial merge result\n"); |
435 | 0 | remove_note(t, p->obj.hash); |
436 | |
|
437 | 0 | return 1; |
438 | 0 | } |
439 | | |
440 | | static int merge_one_change(struct notes_merge_options *o, |
441 | | struct notes_merge_pair *p, struct notes_tree *t) |
442 | 0 | { |
443 | | /* |
444 | | * Return 0 if change is successfully resolved (stored in notes_tree). |
445 | | * Return 1 is change results in a conflict (NOT stored in notes_tree, |
446 | | * but instead written to NOTES_MERGE_WORKTREE with conflict markers). |
447 | | */ |
448 | 0 | switch (o->strategy) { |
449 | 0 | case NOTES_MERGE_RESOLVE_MANUAL: |
450 | 0 | return merge_one_change_manual(o, p, t); |
451 | 0 | case NOTES_MERGE_RESOLVE_OURS: |
452 | 0 | if (o->verbosity >= 2) |
453 | 0 | printf("Using local notes for %s\n", |
454 | 0 | oid_to_hex(&p->obj)); |
455 | | /* nothing to do */ |
456 | 0 | return 0; |
457 | 0 | case NOTES_MERGE_RESOLVE_THEIRS: |
458 | 0 | if (o->verbosity >= 2) |
459 | 0 | printf("Using remote notes for %s\n", |
460 | 0 | oid_to_hex(&p->obj)); |
461 | 0 | if (add_note(t, &p->obj, &p->remote, combine_notes_overwrite)) |
462 | 0 | BUG("combine_notes_overwrite failed"); |
463 | 0 | return 0; |
464 | 0 | case NOTES_MERGE_RESOLVE_UNION: |
465 | 0 | if (o->verbosity >= 2) |
466 | 0 | printf("Concatenating local and remote notes for %s\n", |
467 | 0 | oid_to_hex(&p->obj)); |
468 | 0 | if (add_note(t, &p->obj, &p->remote, combine_notes_concatenate)) |
469 | 0 | die("failed to concatenate notes " |
470 | 0 | "(combine_notes_concatenate)"); |
471 | 0 | return 0; |
472 | 0 | case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ: |
473 | 0 | if (o->verbosity >= 2) |
474 | 0 | printf("Concatenating unique lines in local and remote " |
475 | 0 | "notes for %s\n", oid_to_hex(&p->obj)); |
476 | 0 | if (add_note(t, &p->obj, &p->remote, combine_notes_cat_sort_uniq)) |
477 | 0 | die("failed to concatenate notes " |
478 | 0 | "(combine_notes_cat_sort_uniq)"); |
479 | 0 | return 0; |
480 | 0 | } |
481 | 0 | die("Unknown strategy (%i).", o->strategy); |
482 | 0 | } |
483 | | |
484 | | static int merge_changes(struct notes_merge_options *o, |
485 | | struct notes_merge_pair *changes, int *num_changes, |
486 | | struct notes_tree *t) |
487 | 0 | { |
488 | 0 | int i, conflicts = 0; |
489 | |
|
490 | 0 | trace_printf("\tmerge_changes(num_changes = %i)\n", *num_changes); |
491 | 0 | for (i = 0; i < *num_changes; i++) { |
492 | 0 | struct notes_merge_pair *p = changes + i; |
493 | 0 | trace_printf("\t\t%.7s: %.7s -> %.7s/%.7s\n", |
494 | 0 | oid_to_hex(&p->obj), oid_to_hex(&p->base), |
495 | 0 | oid_to_hex(&p->local), |
496 | 0 | oid_to_hex(&p->remote)); |
497 | |
|
498 | 0 | if (oideq(&p->base, &p->remote)) { |
499 | | /* no remote change; nothing to do */ |
500 | 0 | trace_printf("\t\t\tskipping (no remote change)\n"); |
501 | 0 | } else if (oideq(&p->local, &p->remote)) { |
502 | | /* same change in local and remote; nothing to do */ |
503 | 0 | trace_printf("\t\t\tskipping (local == remote)\n"); |
504 | 0 | } else if (oideq(&p->local, &uninitialized) || |
505 | 0 | oideq(&p->local, &p->base)) { |
506 | | /* no local change; adopt remote change */ |
507 | 0 | trace_printf("\t\t\tno local change, adopted remote\n"); |
508 | 0 | if (add_note(t, &p->obj, &p->remote, |
509 | 0 | combine_notes_overwrite)) |
510 | 0 | BUG("combine_notes_overwrite failed"); |
511 | 0 | } else { |
512 | | /* need file-level merge between local and remote */ |
513 | 0 | trace_printf("\t\t\tneed content-level merge\n"); |
514 | 0 | conflicts += merge_one_change(o, p, t); |
515 | 0 | } |
516 | 0 | } |
517 | | |
518 | 0 | return conflicts; |
519 | 0 | } |
520 | | |
521 | | static int merge_from_diffs(struct notes_merge_options *o, |
522 | | const struct object_id *base, |
523 | | const struct object_id *local, |
524 | | const struct object_id *remote, |
525 | | struct notes_tree *t) |
526 | 0 | { |
527 | 0 | struct notes_merge_pair *changes; |
528 | 0 | int num_changes, conflicts; |
529 | |
|
530 | 0 | trace_printf("\tmerge_from_diffs(base = %.7s, local = %.7s, " |
531 | 0 | "remote = %.7s)\n", oid_to_hex(base), oid_to_hex(local), |
532 | 0 | oid_to_hex(remote)); |
533 | |
|
534 | 0 | changes = diff_tree_remote(o, base, remote, &num_changes); |
535 | 0 | diff_tree_local(o, changes, num_changes, base, local); |
536 | |
|
537 | 0 | conflicts = merge_changes(o, changes, &num_changes, t); |
538 | 0 | free(changes); |
539 | |
|
540 | 0 | if (o->verbosity >= 4) |
541 | 0 | printf(t->dirty ? |
542 | 0 | "Merge result: %i unmerged notes and a dirty notes tree\n" : |
543 | 0 | "Merge result: %i unmerged notes and a clean notes tree\n", |
544 | 0 | conflicts); |
545 | |
|
546 | 0 | return conflicts ? -1 : 1; |
547 | 0 | } |
548 | | |
549 | | int notes_merge(struct notes_merge_options *o, |
550 | | struct notes_tree *local_tree, |
551 | | struct object_id *result_oid) |
552 | 0 | { |
553 | 0 | struct object_id local_oid, remote_oid; |
554 | 0 | struct commit *local, *remote; |
555 | 0 | struct commit_list *bases = NULL; |
556 | 0 | const struct object_id *base_oid, *base_tree_oid; |
557 | 0 | int result = 0; |
558 | |
|
559 | 0 | assert(o->local_ref && o->remote_ref); |
560 | 0 | assert(!strcmp(o->local_ref, local_tree->ref)); |
561 | 0 | oidclr(result_oid, the_repository->hash_algo); |
562 | |
|
563 | 0 | trace_printf("notes_merge(o->local_ref = %s, o->remote_ref = %s)\n", |
564 | 0 | o->local_ref, o->remote_ref); |
565 | | |
566 | | /* Dereference o->local_ref into local_sha1 */ |
567 | 0 | if (refs_read_ref_full(get_main_ref_store(the_repository), o->local_ref, 0, &local_oid, NULL)) |
568 | 0 | die("Failed to resolve local notes ref '%s'", o->local_ref); |
569 | 0 | else if (!check_refname_format(o->local_ref, 0) && |
570 | 0 | is_null_oid(&local_oid)) |
571 | 0 | local = NULL; /* local_oid == null_oid indicates unborn ref */ |
572 | 0 | else if (!(local = lookup_commit_reference(o->repo, &local_oid))) |
573 | 0 | die("Could not parse local commit %s (%s)", |
574 | 0 | oid_to_hex(&local_oid), o->local_ref); |
575 | 0 | trace_printf("\tlocal commit: %.7s\n", oid_to_hex(&local_oid)); |
576 | | |
577 | | /* Dereference o->remote_ref into remote_oid */ |
578 | 0 | if (repo_get_oid(the_repository, o->remote_ref, &remote_oid)) { |
579 | | /* |
580 | | * Failed to get remote_oid. If o->remote_ref looks like an |
581 | | * unborn ref, perform the merge using an empty notes tree. |
582 | | */ |
583 | 0 | if (!check_refname_format(o->remote_ref, 0)) { |
584 | 0 | oidclr(&remote_oid, the_repository->hash_algo); |
585 | 0 | remote = NULL; |
586 | 0 | } else { |
587 | 0 | die("Failed to resolve remote notes ref '%s'", |
588 | 0 | o->remote_ref); |
589 | 0 | } |
590 | 0 | } else if (!(remote = lookup_commit_reference(o->repo, &remote_oid))) { |
591 | 0 | die("Could not parse remote commit %s (%s)", |
592 | 0 | oid_to_hex(&remote_oid), o->remote_ref); |
593 | 0 | } |
594 | 0 | trace_printf("\tremote commit: %.7s\n", oid_to_hex(&remote_oid)); |
595 | |
|
596 | 0 | if (!local && !remote) |
597 | 0 | die("Cannot merge empty notes ref (%s) into empty notes ref " |
598 | 0 | "(%s)", o->remote_ref, o->local_ref); |
599 | 0 | if (!local) { |
600 | | /* result == remote commit */ |
601 | 0 | oidcpy(result_oid, &remote_oid); |
602 | 0 | goto found_result; |
603 | 0 | } |
604 | 0 | if (!remote) { |
605 | | /* result == local commit */ |
606 | 0 | oidcpy(result_oid, &local_oid); |
607 | 0 | goto found_result; |
608 | 0 | } |
609 | 0 | assert(local && remote); |
610 | | |
611 | | /* Find merge bases */ |
612 | 0 | if (repo_get_merge_bases(the_repository, local, remote, &bases) < 0) |
613 | 0 | exit(128); |
614 | 0 | if (!bases) { |
615 | 0 | base_oid = null_oid(); |
616 | 0 | base_tree_oid = the_hash_algo->empty_tree; |
617 | 0 | if (o->verbosity >= 4) |
618 | 0 | printf("No merge base found; doing history-less merge\n"); |
619 | 0 | } else if (!bases->next) { |
620 | 0 | base_oid = &bases->item->object.oid; |
621 | 0 | base_tree_oid = get_commit_tree_oid(bases->item); |
622 | 0 | if (o->verbosity >= 4) |
623 | 0 | printf("One merge base found (%.7s)\n", |
624 | 0 | oid_to_hex(base_oid)); |
625 | 0 | } else { |
626 | | /* TODO: How to handle multiple merge-bases? */ |
627 | 0 | base_oid = &bases->item->object.oid; |
628 | 0 | base_tree_oid = get_commit_tree_oid(bases->item); |
629 | 0 | if (o->verbosity >= 3) |
630 | 0 | printf("Multiple merge bases found. Using the first " |
631 | 0 | "(%.7s)\n", oid_to_hex(base_oid)); |
632 | 0 | } |
633 | |
|
634 | 0 | if (o->verbosity >= 4) |
635 | 0 | printf("Merging remote commit %.7s into local commit %.7s with " |
636 | 0 | "merge-base %.7s\n", oid_to_hex(&remote->object.oid), |
637 | 0 | oid_to_hex(&local->object.oid), |
638 | 0 | oid_to_hex(base_oid)); |
639 | |
|
640 | 0 | if (oideq(&remote->object.oid, base_oid)) { |
641 | | /* Already merged; result == local commit */ |
642 | 0 | if (o->verbosity >= 2) |
643 | 0 | printf_ln("Already up to date."); |
644 | 0 | oidcpy(result_oid, &local->object.oid); |
645 | 0 | goto found_result; |
646 | 0 | } |
647 | 0 | if (oideq(&local->object.oid, base_oid)) { |
648 | | /* Fast-forward; result == remote commit */ |
649 | 0 | if (o->verbosity >= 2) |
650 | 0 | printf("Fast-forward\n"); |
651 | 0 | oidcpy(result_oid, &remote->object.oid); |
652 | 0 | goto found_result; |
653 | 0 | } |
654 | | |
655 | 0 | result = merge_from_diffs(o, base_tree_oid, |
656 | 0 | get_commit_tree_oid(local), |
657 | 0 | get_commit_tree_oid(remote), local_tree); |
658 | |
|
659 | 0 | if (result != 0) { /* non-trivial merge (with or without conflicts) */ |
660 | | /* Commit (partial) result */ |
661 | 0 | struct commit_list *parents = NULL; |
662 | 0 | commit_list_insert(remote, &parents); /* LIFO order */ |
663 | 0 | commit_list_insert(local, &parents); |
664 | 0 | create_notes_commit(o->repo, local_tree, parents, o->commit_msg.buf, |
665 | 0 | o->commit_msg.len, result_oid); |
666 | 0 | free_commit_list(parents); |
667 | 0 | } |
668 | |
|
669 | 0 | found_result: |
670 | 0 | free_commit_list(bases); |
671 | 0 | strbuf_release(&(o->commit_msg)); |
672 | 0 | trace_printf("notes_merge(): result = %i, result_oid = %.7s\n", |
673 | 0 | result, oid_to_hex(result_oid)); |
674 | 0 | return result; |
675 | 0 | } |
676 | | |
677 | | int notes_merge_commit(struct notes_merge_options *o, |
678 | | struct notes_tree *partial_tree, |
679 | | struct commit *partial_commit, |
680 | | struct object_id *result_oid) |
681 | 0 | { |
682 | | /* |
683 | | * Iterate through files in .git/NOTES_MERGE_WORKTREE and add all |
684 | | * found notes to 'partial_tree'. Write the updated notes tree to |
685 | | * the DB, and commit the resulting tree object while reusing the |
686 | | * commit message and parents from 'partial_commit'. |
687 | | * Finally store the new commit object OID into 'result_oid'. |
688 | | */ |
689 | 0 | DIR *dir; |
690 | 0 | struct dirent *e; |
691 | 0 | struct strbuf path = STRBUF_INIT; |
692 | 0 | const char *buffer = repo_get_commit_buffer(the_repository, |
693 | 0 | partial_commit, NULL); |
694 | 0 | const char *msg = strstr(buffer, "\n\n"); |
695 | 0 | int baselen; |
696 | |
|
697 | 0 | git_path_buf(&path, NOTES_MERGE_WORKTREE); |
698 | 0 | if (o->verbosity >= 3) |
699 | 0 | printf("Committing notes in notes merge worktree at %s\n", |
700 | 0 | path.buf); |
701 | |
|
702 | 0 | if (!msg || msg[2] == '\0') |
703 | 0 | die("partial notes commit has empty message"); |
704 | 0 | msg += 2; |
705 | |
|
706 | 0 | dir = opendir(path.buf); |
707 | 0 | if (!dir) |
708 | 0 | die_errno("could not open %s", path.buf); |
709 | | |
710 | 0 | strbuf_addch(&path, '/'); |
711 | 0 | baselen = path.len; |
712 | 0 | while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) { |
713 | 0 | struct stat st; |
714 | 0 | struct object_id obj_oid, blob_oid; |
715 | |
|
716 | 0 | if (get_oid_hex(e->d_name, &obj_oid)) { |
717 | 0 | if (o->verbosity >= 3) |
718 | 0 | printf("Skipping non-SHA1 entry '%s%s'\n", |
719 | 0 | path.buf, e->d_name); |
720 | 0 | continue; |
721 | 0 | } |
722 | | |
723 | 0 | strbuf_addstr(&path, e->d_name); |
724 | | /* write file as blob, and add to partial_tree */ |
725 | 0 | if (stat(path.buf, &st)) |
726 | 0 | die_errno("Failed to stat '%s'", path.buf); |
727 | 0 | if (index_path(o->repo->index, &blob_oid, path.buf, &st, HASH_WRITE_OBJECT)) |
728 | 0 | die("Failed to write blob object from '%s'", path.buf); |
729 | 0 | if (add_note(partial_tree, &obj_oid, &blob_oid, NULL)) |
730 | 0 | die("Failed to add resolved note '%s' to notes tree", |
731 | 0 | path.buf); |
732 | 0 | if (o->verbosity >= 4) |
733 | 0 | printf("Added resolved note for object %s: %s\n", |
734 | 0 | oid_to_hex(&obj_oid), oid_to_hex(&blob_oid)); |
735 | 0 | strbuf_setlen(&path, baselen); |
736 | 0 | } |
737 | | |
738 | 0 | create_notes_commit(o->repo, partial_tree, partial_commit->parents, msg, |
739 | 0 | strlen(msg), result_oid); |
740 | 0 | repo_unuse_commit_buffer(the_repository, partial_commit, buffer); |
741 | 0 | if (o->verbosity >= 4) |
742 | 0 | printf("Finalized notes merge commit: %s\n", |
743 | 0 | oid_to_hex(result_oid)); |
744 | 0 | strbuf_release(&path); |
745 | 0 | closedir(dir); |
746 | 0 | return 0; |
747 | 0 | } |
748 | | |
749 | | int notes_merge_abort(struct notes_merge_options *o) |
750 | 0 | { |
751 | | /* |
752 | | * Remove all files within .git/NOTES_MERGE_WORKTREE. We do not remove |
753 | | * the .git/NOTES_MERGE_WORKTREE directory itself, since it might be |
754 | | * the current working directory of the user. |
755 | | */ |
756 | 0 | struct strbuf buf = STRBUF_INIT; |
757 | 0 | int ret; |
758 | |
|
759 | 0 | git_path_buf(&buf, NOTES_MERGE_WORKTREE); |
760 | 0 | if (o->verbosity >= 3) |
761 | 0 | printf("Removing notes merge worktree at %s/*\n", buf.buf); |
762 | 0 | ret = remove_dir_recursively(&buf, REMOVE_DIR_KEEP_TOPLEVEL); |
763 | 0 | strbuf_release(&buf); |
764 | 0 | return ret; |
765 | 0 | } |