]> git.proxmox.com Git - libgit2.git/blame - src/diff_tform.c
New upstream version 0.27.7+dfsg.1
[libgit2.git] / src / diff_tform.c
CommitLineData
db106d01 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
db106d01
RB
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
eae0bfdc
PP
7
8#include "diff_tform.h"
114f5a6c 9
db106d01 10#include "git2/config.h"
960a04dd 11#include "git2/blob.h"
737b5051 12#include "git2/sys/hashsig.h"
114f5a6c
RB
13
14#include "diff.h"
9be638ec 15#include "diff_generate.h"
114f5a6c
RB
16#include "path.h"
17#include "fileops.h"
9f77b3f6 18#include "config.h"
db106d01 19
90177111 20git_diff_delta *git_diff__delta_dup(
db106d01
RB
21 const git_diff_delta *d, git_pool *pool)
22{
23 git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
24 if (!delta)
25 return NULL;
26
27 memcpy(delta, d, sizeof(git_diff_delta));
c68b09dc 28 GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
db106d01 29
d958e37a
RB
30 if (d->old_file.path != NULL) {
31 delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
32 if (delta->old_file.path == NULL)
33 goto fail;
34 }
db106d01 35
d958e37a 36 if (d->new_file.path != d->old_file.path && d->new_file.path != NULL) {
db106d01
RB
37 delta->new_file.path = git_pool_strdup(pool, d->new_file.path);
38 if (delta->new_file.path == NULL)
39 goto fail;
40 } else {
41 delta->new_file.path = delta->old_file.path;
42 }
43
44 return delta;
45
46fail:
47 git__free(delta);
48 return NULL;
49}
50
90177111 51git_diff_delta *git_diff__merge_like_cgit(
3940310e
RB
52 const git_diff_delta *a,
53 const git_diff_delta *b,
54 git_pool *pool)
db106d01
RB
55{
56 git_diff_delta *dup;
57
58 /* Emulate C git for merging two diffs (a la 'git diff <sha>').
59 *
60 * When C git does a diff between the work dir and a tree, it actually
61 * diffs with the index but uses the workdir contents. This emulates
62 * those choices so we can emulate the type of diff.
63 *
64 * We have three file descriptions here, let's call them:
65 * f1 = a->old_file
66 * f2 = a->new_file AND b->old_file
67 * f3 = b->new_file
68 */
69
cb63e7e8
POL
70 /* If one of the diffs is a conflict, just dup it */
71 if (b->status == GIT_DELTA_CONFLICTED)
90177111 72 return git_diff__delta_dup(b, pool);
cb63e7e8 73 if (a->status == GIT_DELTA_CONFLICTED)
90177111 74 return git_diff__delta_dup(a, pool);
cb63e7e8 75
db106d01
RB
76 /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
77 if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
90177111 78 return git_diff__delta_dup(a, pool);
db106d01
RB
79
80 /* otherwise, base this diff on the 'b' diff */
90177111 81 if ((dup = git_diff__delta_dup(b, pool)) == NULL)
db106d01
RB
82 return NULL;
83
84 /* If 'a' status is uninteresting, then we're done */
83ba5e36
ET
85 if (a->status == GIT_DELTA_UNMODIFIED ||
86 a->status == GIT_DELTA_UNTRACKED ||
87 a->status == GIT_DELTA_UNREADABLE)
db106d01
RB
88 return dup;
89
db106d01
RB
90 assert(b->status != GIT_DELTA_UNMODIFIED);
91
92 /* A cgit exception is that the diff of a file that is only in the
93 * index (i.e. not in HEAD nor workdir) is given as empty.
94 */
95 if (dup->status == GIT_DELTA_DELETED) {
b9780823 96 if (a->status == GIT_DELTA_ADDED) {
db106d01 97 dup->status = GIT_DELTA_UNMODIFIED;
b9780823
POL
98 dup->nfiles = 2;
99 }
db106d01
RB
100 /* else don't overwrite DELETE status */
101 } else {
102 dup->status = a->status;
b9780823 103 dup->nfiles = a->nfiles;
db106d01
RB
104 }
105
9950bb4e 106 git_oid_cpy(&dup->old_file.id, &a->old_file.id);
db106d01
RB
107 dup->old_file.mode = a->old_file.mode;
108 dup->old_file.size = a->old_file.size;
109 dup->old_file.flags = a->old_file.flags;
110
111 return dup;
112}
113
90177111 114int git_diff__merge(
5ef43d41 115 git_diff *onto, const git_diff *from, git_diff__merge_cb cb)
db106d01
RB
116{
117 int error = 0;
118 git_pool onto_pool;
119 git_vector onto_new;
120 git_diff_delta *delta;
e7c85120 121 bool ignore_case, reversed;
db106d01
RB
122 unsigned int i, j;
123
124 assert(onto && from);
125
126 if (!from->deltas.length)
127 return 0;
128
e7c85120
RB
129 ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0);
130 reversed = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0);
131
132 if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) ||
133 reversed != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) {
3940310e 134 giterr_set(GITERR_INVALID,
909d5494 135 "attempt to merge diffs created with conflicting options");
3940310e
RB
136 return -1;
137 }
138
1e5e02b4 139 if (git_vector_init(&onto_new, onto->deltas.length, git_diff_delta__cmp) < 0)
db106d01
RB
140 return -1;
141
1e5e02b4
VM
142 git_pool_init(&onto_pool, 1);
143
db106d01
RB
144 for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
145 git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
146 const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
3940310e
RB
147 int cmp = !f ? -1 : !o ? 1 :
148 STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
db106d01
RB
149
150 if (cmp < 0) {
90177111 151 delta = git_diff__delta_dup(o, &onto_pool);
db106d01
RB
152 i++;
153 } else if (cmp > 0) {
90177111 154 delta = git_diff__delta_dup(f, &onto_pool);
db106d01
RB
155 j++;
156 } else {
83ba5e36
ET
157 const git_diff_delta *left = reversed ? f : o;
158 const git_diff_delta *right = reversed ? o : f;
159
5ef43d41 160 delta = cb(left, right, &onto_pool);
db106d01
RB
161 i++;
162 j++;
163 }
164
165 /* the ignore rules for the target may not match the source
166 * or the result of a merged delta could be skippable...
167 */
5ef43d41 168 if (delta && git_diff_delta__should_skip(&onto->opts, delta)) {
db106d01
RB
169 git__free(delta);
170 continue;
171 }
172
173 if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
174 break;
175 }
176
177 if (!error) {
178 git_vector_swap(&onto->deltas, &onto_new);
179 git_pool_swap(&onto->pool, &onto_pool);
3940310e
RB
180
181 if ((onto->opts.flags & GIT_DIFF_REVERSE) != 0)
182 onto->old_src = from->old_src;
183 else
184 onto->new_src = from->new_src;
db106d01
RB
185
186 /* prefix strings also come from old pool, so recreate those.*/
187 onto->opts.old_prefix =
188 git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix);
189 onto->opts.new_prefix =
190 git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix);
191 }
192
9cfce273 193 git_vector_free_deep(&onto_new);
db106d01
RB
194 git_pool_clear(&onto_pool);
195
196 return error;
197}
198
5ef43d41
ET
199int git_diff_merge(git_diff *onto, const git_diff *from)
200{
90177111 201 return git_diff__merge(onto, from, git_diff__merge_like_cgit);
5ef43d41
ET
202}
203
0462fba5 204int git_diff_find_similar__hashsig_for_file(
f8275890
RB
205 void **out, const git_diff_file *f, const char *path, void *p)
206{
c8893d1f 207 git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p;
aa408cbf 208
f8275890 209 GIT_UNUSED(f);
36fc5497 210 return git_hashsig_create_fromfile((git_hashsig **)out, path, opt);
f8275890 211}
9bc8be3d 212
0462fba5 213int git_diff_find_similar__hashsig_for_buf(
f8275890
RB
214 void **out, const git_diff_file *f, const char *buf, size_t len, void *p)
215{
c8893d1f 216 git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p;
0462fba5 217
f8275890 218 GIT_UNUSED(f);
36fc5497 219 return git_hashsig_create((git_hashsig **)out, buf, len, opt);
f8275890 220}
9bc8be3d 221
0462fba5 222void git_diff_find_similar__hashsig_free(void *sig, void *payload)
9bc8be3d
RB
223{
224 GIT_UNUSED(payload);
225 git_hashsig_free(sig);
226}
227
0462fba5 228int git_diff_find_similar__calc_similarity(
9bc8be3d
RB
229 int *score, void *siga, void *sigb, void *payload)
230{
36fc5497
POL
231 int error;
232
9bc8be3d 233 GIT_UNUSED(payload);
36fc5497
POL
234 error = git_hashsig_compare(siga, sigb);
235 if (error < 0)
236 return error;
237
238 *score = error;
9bc8be3d
RB
239 return 0;
240}
241
db106d01
RB
242#define DEFAULT_THRESHOLD 50
243#define DEFAULT_BREAK_REWRITE_THRESHOLD 60
a21cbb12 244#define DEFAULT_RENAME_LIMIT 200
db106d01
RB
245
246static int normalize_find_opts(
3ff1d123 247 git_diff *diff,
db106d01 248 git_diff_find_options *opts,
10672e3e 249 const git_diff_find_options *given)
db106d01
RB
250{
251 git_config *cfg = NULL;
36fc5497 252 git_hashsig_option_t hashsig_opts;
db106d01 253
5a52d6be
BS
254 GITERR_CHECK_VERSION(given, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options");
255
db106d01
RB
256 if (diff->repo != NULL &&
257 git_repository_config__weakptr(&cfg, diff->repo) < 0)
258 return -1;
259
7e3ed419 260 if (given)
db106d01 261 memcpy(opts, given, sizeof(*opts));
db106d01 262
c56c6d69
BS
263 if (!given ||
264 (given->flags & GIT_DIFF_FIND_ALL) == GIT_DIFF_FIND_BY_CONFIG)
265 {
1a8c11f4 266 if (cfg) {
32f07984
PS
267 char *rule =
268 git_config__get_string_force(cfg, "diff.renames", "true");
269 int boolval;
270
271 if (!git__parse_bool(&boolval, rule) && !boolval)
272 /* don't set FIND_RENAMES if bool value is false */;
273 else if (!strcasecmp(rule, "copies") || !strcasecmp(rule, "copy"))
274 opts->flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
275 else
276 opts->flags |= GIT_DIFF_FIND_RENAMES;
277
278 git__free(rule);
279 } else {
280 /* set default flag */
9f77b3f6 281 opts->flags |= GIT_DIFF_FIND_RENAMES;
32f07984 282 }
db106d01 283 }
ca901e7b 284
db106d01
RB
285 /* some flags imply others */
286
9be5be47
RB
287 if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) {
288 /* if we are only looking for exact matches, then don't turn
289 * MODIFIED items into ADD/DELETE pairs because it's too picky
290 */
291 opts->flags &= ~(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES);
292
293 /* similarly, don't look for self-rewrites to split */
294 opts->flags &= ~GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
295 }
296
db106d01
RB
297 if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES)
298 opts->flags |= GIT_DIFF_FIND_RENAMES;
299
300 if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)
301 opts->flags |= GIT_DIFF_FIND_COPIES;
302
d958e37a
RB
303 if (opts->flags & GIT_DIFF_BREAK_REWRITES)
304 opts->flags |= GIT_DIFF_FIND_REWRITES;
305
db106d01
RB
306#define USE_DEFAULT(X) ((X) == 0 || (X) > 100)
307
308 if (USE_DEFAULT(opts->rename_threshold))
309 opts->rename_threshold = DEFAULT_THRESHOLD;
310
311 if (USE_DEFAULT(opts->rename_from_rewrite_threshold))
312 opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD;
313
314 if (USE_DEFAULT(opts->copy_threshold))
315 opts->copy_threshold = DEFAULT_THRESHOLD;
316
317 if (USE_DEFAULT(opts->break_rewrite_threshold))
318 opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD;
319
320#undef USE_DEFAULT
321
a21cbb12 322 if (!opts->rename_limit) {
1a8c11f4
PS
323 if (cfg) {
324 opts->rename_limit = git_config__get_int_force(
325 cfg, "diff.renamelimit", DEFAULT_RENAME_LIMIT);
326 }
db106d01 327
9f77b3f6
RB
328 if (opts->rename_limit <= 0)
329 opts->rename_limit = DEFAULT_RENAME_LIMIT;
db106d01
RB
330 }
331
f8275890 332 /* assign the internal metric with whitespace flag as payload */
9bc8be3d 333 if (!opts->metric) {
f8275890
RB
334 opts->metric = git__malloc(sizeof(git_diff_similarity_metric));
335 GITERR_CHECK_ALLOC(opts->metric);
336
0462fba5
ET
337 opts->metric->file_signature = git_diff_find_similar__hashsig_for_file;
338 opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf;
339 opts->metric->free_signature = git_diff_find_similar__hashsig_free;
340 opts->metric->similarity = git_diff_find_similar__calc_similarity;
f8275890 341
9bc8be3d 342 if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE)
36fc5497 343 hashsig_opts = GIT_HASHSIG_IGNORE_WHITESPACE;
9bc8be3d 344 else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE)
36fc5497 345 hashsig_opts = GIT_HASHSIG_NORMAL;
9bc8be3d 346 else
36fc5497
POL
347 hashsig_opts = GIT_HASHSIG_SMART_WHITESPACE;
348 hashsig_opts |= GIT_HASHSIG_ALLOW_SMALL_FILES;
349 opts->metric->payload = (void *)hashsig_opts;
9bc8be3d
RB
350 }
351
db106d01
RB
352 return 0;
353}
354
2123a17f
RB
355static int insert_delete_side_of_split(
356 git_diff *diff, git_vector *onto, const git_diff_delta *delta)
357{
358 /* make new record for DELETED side of split */
90177111 359 git_diff_delta *deleted = git_diff__delta_dup(delta, &diff->pool);
2123a17f
RB
360 GITERR_CHECK_ALLOC(deleted);
361
362 deleted->status = GIT_DELTA_DELETED;
363 deleted->nfiles = 1;
364 memset(&deleted->new_file, 0, sizeof(deleted->new_file));
365 deleted->new_file.path = deleted->old_file.path;
9950bb4e 366 deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
2123a17f
RB
367
368 return git_vector_insert(onto, deleted);
369}
370
d958e37a 371static int apply_splits_and_deletes(
3ff1d123 372 git_diff *diff, size_t expected_size, bool actually_split)
db106d01
RB
373{
374 git_vector onto = GIT_VECTOR_INIT;
375 size_t i;
2123a17f 376 git_diff_delta *delta;
db106d01
RB
377
378 if (git_vector_init(&onto, expected_size, git_diff_delta__cmp) < 0)
379 return -1;
380
381 /* build new delta list without TO_DELETE and splitting TO_SPLIT */
382 git_vector_foreach(&diff->deltas, i, delta) {
71a3d27e 383 if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
db106d01 384 continue;
db106d01 385
a21cbb12 386 if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0 && actually_split) {
d958e37a
RB
387 delta->similarity = 0;
388
2123a17f 389 if (insert_delete_side_of_split(diff, &onto, delta) < 0)
11d9f6b3 390 goto on_error;
db106d01 391
9be5be47
RB
392 if (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR)
393 delta->status = GIT_DELTA_UNTRACKED;
394 else
395 delta->status = GIT_DELTA_ADDED;
74a627f0 396 delta->nfiles = 1;
db106d01
RB
397 memset(&delta->old_file, 0, sizeof(delta->old_file));
398 delta->old_file.path = delta->new_file.path;
9950bb4e 399 delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
db106d01
RB
400 }
401
c68b09dc
RB
402 /* clean up delta before inserting into new list */
403 GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
404
405 if (delta->status != GIT_DELTA_COPIED &&
406 delta->status != GIT_DELTA_RENAMED &&
407 (delta->status != GIT_DELTA_MODIFIED || actually_split))
408 delta->similarity = 0;
409
410 /* insert into new list */
11d9f6b3
PK
411 if (git_vector_insert(&onto, delta) < 0)
412 goto on_error;
db106d01
RB
413 }
414
11d9f6b3 415 /* cannot return an error past this point */
c68b09dc
RB
416
417 /* free deltas from old list that didn't make it to the new one */
a21cbb12 418 git_vector_foreach(&diff->deltas, i, delta) {
71a3d27e 419 if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
11d9f6b3 420 git__free(delta);
a21cbb12
RB
421 }
422
db106d01 423 /* swap new delta list into place */
db106d01
RB
424 git_vector_swap(&diff->deltas, &onto);
425 git_vector_free(&onto);
a21cbb12 426 git_vector_sort(&diff->deltas);
db106d01
RB
427
428 return 0;
11d9f6b3
PK
429
430on_error:
9cfce273 431 git_vector_free_deep(&onto);
11d9f6b3
PK
432
433 return -1;
db106d01
RB
434}
435
3ff1d123 436GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx)
960a04dd
RB
437{
438 git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2);
439 return (idx & 1) ? &delta->new_file : &delta->old_file;
440}
99ba8f23 441
a5140f4d
RB
442typedef struct {
443 size_t idx;
444 git_iterator_type_t src;
445 git_repository *repo;
446 git_diff_file *file;
447 git_buf data;
effdbeb3 448 git_odb_object *odb_obj;
a5140f4d 449 git_blob *blob;
a5140f4d
RB
450} similarity_info;
451
effdbeb3 452static int similarity_init(
3ff1d123 453 similarity_info *info, git_diff *diff, size_t file_idx)
a5140f4d
RB
454{
455 info->idx = file_idx;
456 info->src = (file_idx & 1) ? diff->new_src : diff->old_src;
457 info->repo = diff->repo;
458 info->file = similarity_get_file(diff, file_idx);
effdbeb3 459 info->odb_obj = NULL;
a5140f4d 460 info->blob = NULL;
a5140f4d 461 git_buf_init(&info->data, 0);
09fae31d 462
410a8e6f 463 if (info->file->size > 0 || info->src == GIT_ITERATOR_TYPE_WORKDIR)
effdbeb3 464 return 0;
5e5848eb 465
effdbeb3
RB
466 return git_diff_file__resolve_zero_size(
467 info->file, &info->odb_obj, info->repo);
a5140f4d 468}
960a04dd 469
d730d3f4 470static int similarity_sig(
a5140f4d
RB
471 similarity_info *info,
472 const git_diff_find_options *opts,
473 void **cache)
474{
475 int error = 0;
effdbeb3 476 git_diff_file *file = info->file;
8cfd54f0 477
effdbeb3
RB
478 if (info->src == GIT_ITERATOR_TYPE_WORKDIR) {
479 if ((error = git_buf_joinpath(
480 &info->data, git_repository_workdir(info->repo), file->path)) < 0)
481 return error;
960a04dd 482
effdbeb3
RB
483 /* if path is not a regular file, just skip this item */
484 if (!git_path_isfile(info->data.ptr))
485 return 0;
a5140f4d 486
a5140f4d
RB
487 /* TODO: apply wd-to-odb filters to file data if necessary */
488
489 error = opts->metric->file_signature(
490 &cache[info->idx], info->file,
491 info->data.ptr, opts->metric->payload);
492 } else {
effdbeb3
RB
493 /* if we didn't initially know the size, we might have an odb_obj
494 * around from earlier, so convert that, otherwise load the blob now
495 */
496 if (info->odb_obj != NULL)
497 error = git_object__from_odb_object(
498 (git_object **)&info->blob, info->repo,
499 info->odb_obj, GIT_OBJ_BLOB);
500 else
9950bb4e 501 error = git_blob_lookup(&info->blob, info->repo, &file->id);
effdbeb3
RB
502
503 if (error < 0) {
504 /* if lookup fails, just skip this item in similarity calc */
505 giterr_clear();
506 } else {
a16e4172
RB
507 size_t sz;
508
509 /* index size may not be actual blob size if filtered */
510 if (file->size != git_blob_rawsize(info->blob))
511 file->size = git_blob_rawsize(info->blob);
512
513 sz = (size_t)(git__is_sizet(file->size) ? file->size : -1);
effdbeb3
RB
514
515 error = opts->metric->buffer_signature(
516 &cache[info->idx], info->file,
517 git_blob_rawcontent(info->blob), sz, opts->metric->payload);
518 }
960a04dd
RB
519 }
520
521 return error;
522}
523
effdbeb3
RB
524static void similarity_unload(similarity_info *info)
525{
526 if (info->odb_obj)
527 git_odb_object_free(info->odb_obj);
528
529 if (info->blob)
530 git_blob_free(info->blob);
531 else
532 git_buf_free(&info->data);
533}
534
a21cbb12 535#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0)
9be5be47
RB
536
537/* - score < 0 means files cannot be compared
538 * - score >= 100 means files are exact match
539 * - score == 0 means files are completely different
540 */
960a04dd 541static int similarity_measure(
9be5be47 542 int *score,
3ff1d123 543 git_diff *diff,
a21cbb12 544 const git_diff_find_options *opts,
960a04dd
RB
545 void **cache,
546 size_t a_idx,
547 size_t b_idx)
548{
960a04dd
RB
549 git_diff_file *a_file = similarity_get_file(diff, a_idx);
550 git_diff_file *b_file = similarity_get_file(diff, b_idx);
a21cbb12 551 bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY);
a5140f4d
RB
552 int error = 0;
553 similarity_info a_info, b_info;
9be5be47
RB
554
555 *score = -1;
960a04dd 556
191474a1
ET
557 /* don't try to compare things that aren't files */
558 if (!GIT_MODE_ISBLOB(a_file->mode) || !GIT_MODE_ISBLOB(b_file->mode))
960a04dd
RB
559 return 0;
560
a1683f28 561 /* if exact match is requested, force calculation of missing OIDs now */
9be5be47 562 if (exact_match) {
9950bb4e 563 if (git_oid_iszero(&a_file->id) &&
9be5be47 564 diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
240f4af3
RB
565 !git_diff__oid_for_file(&a_file->id,
566 diff, a_file->path, a_file->mode, a_file->size))
9950bb4e 567 a_file->flags |= GIT_DIFF_FLAG_VALID_ID;
9be5be47 568
9950bb4e 569 if (git_oid_iszero(&b_file->id) &&
9be5be47 570 diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
240f4af3
RB
571 !git_diff__oid_for_file(&b_file->id,
572 diff, b_file->path, b_file->mode, b_file->size))
9950bb4e 573 b_file->flags |= GIT_DIFF_FLAG_VALID_ID;
9be5be47
RB
574 }
575
576 /* check OID match as a quick test */
9950bb4e 577 if (git_oid__cmp(&a_file->id, &b_file->id) == 0) {
9be5be47
RB
578 *score = 100;
579 return 0;
580 }
581
582 /* don't calculate signatures if we are doing exact match */
583 if (exact_match) {
584 *score = 0;
585 return 0;
586 }
db106d01 587
effdbeb3
RB
588 memset(&a_info, 0, sizeof(a_info));
589 memset(&b_info, 0, sizeof(b_info));
a5140f4d 590
effdbeb3
RB
591 /* set up similarity data (will try to update missing file sizes) */
592 if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0)
593 return error;
594 if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0)
595 goto cleanup;
a5140f4d 596
f5c4d022 597 /* check if file sizes are nowhere near each other */
18e9efc4
RB
598 if (a_file->size > 127 &&
599 b_file->size > 127 &&
d730d3f4
RB
600 (a_file->size > (b_file->size << 3) ||
601 b_file->size > (a_file->size << 3)))
effdbeb3 602 goto cleanup;
18e9efc4 603
960a04dd 604 /* update signature cache if needed */
d730d3f4
RB
605 if (!cache[a_idx]) {
606 if ((error = similarity_sig(&a_info, opts, cache)) < 0)
607 goto cleanup;
608 }
609 if (!cache[b_idx]) {
610 if ((error = similarity_sig(&b_info, opts, cache)) < 0)
611 goto cleanup;
612 }
1fed6b07 613
a5140f4d
RB
614 /* calculate similarity provided that the metric choose to process
615 * both the a and b files (some may not if file is too big, etc).
616 */
617 if (cache[a_idx] && cache[b_idx])
618 error = opts->metric->similarity(
619 score, cache[a_idx], cache[b_idx], opts->metric->payload);
db106d01 620
effdbeb3 621cleanup:
a5140f4d
RB
622 similarity_unload(&a_info);
623 similarity_unload(&b_info);
624
625 return error;
db106d01
RB
626}
627
a21cbb12 628static int calc_self_similarity(
3ff1d123 629 git_diff *diff,
a21cbb12
RB
630 const git_diff_find_options *opts,
631 size_t delta_idx,
632 void **cache)
9be5be47 633{
a21cbb12
RB
634 int error, similarity = -1;
635 git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
636
637 if ((delta->flags & GIT_DIFF_FLAG__HAS_SELF_SIMILARITY) != 0)
638 return 0;
639
640 error = similarity_measure(
641 &similarity, diff, opts, cache, 2 * delta_idx, 2 * delta_idx + 1);
642 if (error < 0)
643 return error;
644
645 if (similarity >= 0) {
74a627f0 646 delta->similarity = (uint16_t)similarity;
a21cbb12
RB
647 delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY;
648 }
649
650 return 0;
651}
652
653static bool is_rename_target(
3ff1d123 654 git_diff *diff,
a21cbb12
RB
655 const git_diff_find_options *opts,
656 size_t delta_idx,
657 void **cache)
658{
659 git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
660
661 /* skip things that aren't plain blobs */
662 if (!GIT_MODE_ISBLOB(delta->new_file.mode))
663 return false;
664
665 /* only consider ADDED, RENAMED, COPIED, and split MODIFIED as
50456801 666 * targets; maybe include UNTRACKED if requested.
a21cbb12
RB
667 */
668 switch (delta->status) {
669 case GIT_DELTA_UNMODIFIED:
670 case GIT_DELTA_DELETED:
50456801
POL
671 case GIT_DELTA_IGNORED:
672 case GIT_DELTA_CONFLICTED:
a21cbb12
RB
673 return false;
674
675 case GIT_DELTA_MODIFIED:
676 if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
677 !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
678 return false;
679
680 if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
681 return false;
682
683 if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
684 delta->similarity < opts->break_rewrite_threshold) {
685 delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
686 break;
687 }
688 if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
eae0bfdc
PP
689 delta->similarity < opts->rename_from_rewrite_threshold) {
690 delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
a21cbb12 691 break;
eae0bfdc 692 }
a21cbb12
RB
693
694 return false;
695
696 case GIT_DELTA_UNTRACKED:
a21cbb12
RB
697 if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED))
698 return false;
699 break;
700
701 default: /* all other status values should be checked */
702 break;
703 }
704
705 delta->flags |= GIT_DIFF_FLAG__IS_RENAME_TARGET;
706 return true;
707}
708
709static bool is_rename_source(
3ff1d123 710 git_diff *diff,
a21cbb12
RB
711 const git_diff_find_options *opts,
712 size_t delta_idx,
713 void **cache)
714{
715 git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
716
717 /* skip things that aren't blobs */
718 if (!GIT_MODE_ISBLOB(delta->old_file.mode))
719 return false;
720
721 switch (delta->status) {
722 case GIT_DELTA_ADDED:
723 case GIT_DELTA_UNTRACKED:
61bef72d 724 case GIT_DELTA_UNREADABLE:
a21cbb12 725 case GIT_DELTA_IGNORED:
50456801 726 case GIT_DELTA_CONFLICTED:
a21cbb12
RB
727 return false;
728
729 case GIT_DELTA_DELETED:
730 case GIT_DELTA_TYPECHANGE:
731 break;
732
733 case GIT_DELTA_UNMODIFIED:
734 if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))
735 return false;
f62c174d 736 if (FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED))
97ad85b8 737 delta->flags |= GIT_DIFF_FLAG__TO_DELETE;
a21cbb12
RB
738 break;
739
740 default: /* MODIFIED, RENAMED, COPIED */
741 /* if we're finding copies, this could be a source */
742 if (FLAG_SET(opts, GIT_DIFF_FIND_COPIES))
743 break;
744
745 /* otherwise, this is only a source if we can split it */
746 if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
747 !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
748 return false;
749
750 if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
751 return false;
752
753 if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
754 delta->similarity < opts->break_rewrite_threshold) {
755 delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
756 break;
757 }
758
759 if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
760 delta->similarity < opts->rename_from_rewrite_threshold)
761 break;
762
763 return false;
764 }
765
766 delta->flags |= GIT_DIFF_FLAG__IS_RENAME_SOURCE;
767 return true;
768}
769
770GIT_INLINE(bool) delta_is_split(git_diff_delta *delta)
771{
772 return (delta->status == GIT_DELTA_TYPECHANGE ||
773 (delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0);
774}
775
776GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta)
777{
778 return (delta->status == GIT_DELTA_ADDED ||
779 delta->status == GIT_DELTA_UNTRACKED ||
61bef72d 780 delta->status == GIT_DELTA_UNREADABLE ||
a21cbb12 781 delta->status == GIT_DELTA_IGNORED);
9be5be47 782}
db106d01 783
e4acc3ba 784GIT_INLINE(void) delta_make_rename(
74a627f0 785 git_diff_delta *to, const git_diff_delta *from, uint16_t similarity)
e4acc3ba
RB
786{
787 to->status = GIT_DELTA_RENAMED;
788 to->similarity = similarity;
74a627f0 789 to->nfiles = 2;
e4acc3ba
RB
790 memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
791 to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
792}
793
d958e37a 794typedef struct {
74a627f0
RB
795 size_t idx;
796 uint16_t similarity;
d958e37a
RB
797} diff_find_match;
798
db106d01 799int git_diff_find_similar(
3ff1d123 800 git_diff *diff,
10672e3e 801 const git_diff_find_options *given_opts)
db106d01 802{
d730d3f4 803 size_t s, t;
74a627f0
RB
804 int error = 0, result;
805 uint16_t similarity;
d730d3f4 806 git_diff_delta *src, *tgt;
7e3ed419 807 git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
d730d3f4
RB
808 size_t num_deltas, num_srcs = 0, num_tgts = 0;
809 size_t tried_srcs = 0, tried_tgts = 0;
e4acc3ba 810 size_t num_rewrites = 0, num_updates = 0, num_bumped = 0;
f1453c59 811 size_t sigcache_size;
7e3ed419 812 void **sigcache = NULL; /* cache of similarity metric file signatures */
d730d3f4
RB
813 diff_find_match *tgt2src = NULL;
814 diff_find_match *src2tgt = NULL;
815 diff_find_match *tgt2src_copy = NULL;
816 diff_find_match *best_match;
e4acc3ba 817 git_diff_file swap;
db106d01 818
960a04dd 819 if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0)
628e92cd
BS
820 return error;
821
d730d3f4
RB
822 num_deltas = diff->deltas.length;
823
a21cbb12 824 /* TODO: maybe abort if deltas.length > rename_limit ??? */
6c7cee42 825 if (!num_deltas || !git__is_uint32(num_deltas))
7e3ed419
RB
826 goto cleanup;
827
828 /* No flags set; nothing to do */
829 if ((opts.flags & GIT_DIFF_FIND_ALL) == 0)
830 goto cleanup;
960a04dd 831
f1453c59
ET
832 GITERR_CHECK_ALLOC_MULTIPLY(&sigcache_size, num_deltas, 2);
833 sigcache = git__calloc(sigcache_size, sizeof(void *));
e4acc3ba
RB
834 GITERR_CHECK_ALLOC(sigcache);
835
836 /* Label rename sources and targets
837 *
838 * This will also set self-similarity scores for MODIFIED files and
839 * mark them for splitting if break-rewrites is enabled
840 */
d730d3f4
RB
841 git_vector_foreach(&diff->deltas, t, tgt) {
842 if (is_rename_source(diff, &opts, t, sigcache))
e4acc3ba
RB
843 ++num_srcs;
844
d730d3f4 845 if (is_rename_target(diff, &opts, t, sigcache))
e4acc3ba 846 ++num_tgts;
17c7fbf6
ET
847
848 if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0)
849 num_rewrites++;
e4acc3ba 850 }
960a04dd 851
e4acc3ba
RB
852 /* if there are no candidate srcs or tgts, we're done */
853 if (!num_srcs || !num_tgts)
854 goto cleanup;
960a04dd 855
d730d3f4
RB
856 src2tgt = git__calloc(num_deltas, sizeof(diff_find_match));
857 GITERR_CHECK_ALLOC(src2tgt);
858 tgt2src = git__calloc(num_deltas, sizeof(diff_find_match));
859 GITERR_CHECK_ALLOC(tgt2src);
860
861 if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) {
862 tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match));
863 GITERR_CHECK_ALLOC(tgt2src_copy);
864 }
db106d01 865
e4acc3ba
RB
866 /*
867 * Find best-fit matches for rename / copy candidates
868 */
d958e37a 869
e4acc3ba
RB
870find_best_matches:
871 tried_tgts = num_bumped = 0;
db106d01 872
d730d3f4 873 git_vector_foreach(&diff->deltas, t, tgt) {
a21cbb12 874 /* skip things that are not rename targets */
d730d3f4 875 if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
d958e37a
RB
876 continue;
877
e4acc3ba 878 tried_srcs = 0;
db106d01 879
d730d3f4 880 git_vector_foreach(&diff->deltas, s, src) {
a21cbb12 881 /* skip things that are not rename sources */
d730d3f4 882 if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0)
960a04dd
RB
883 continue;
884
d958e37a 885 /* calculate similarity for this pair and find best match */
d730d3f4 886 if (s == t)
74a627f0 887 result = -1; /* don't measure self-similarity here */
e4acc3ba 888 else if ((error = similarity_measure(
74a627f0 889 &result, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0)
960a04dd 890 goto cleanup;
a21cbb12 891
74a627f0 892 if (result < 0)
d730d3f4 893 continue;
74a627f0 894 similarity = (uint16_t)result;
d730d3f4
RB
895
896 /* is this a better rename? */
74a627f0
RB
897 if (tgt2src[t].similarity < similarity &&
898 src2tgt[s].similarity < similarity)
e4acc3ba 899 {
d730d3f4
RB
900 /* eject old mapping */
901 if (src2tgt[s].similarity > 0) {
902 tgt2src[src2tgt[s].idx].similarity = 0;
903 num_bumped++;
904 }
905 if (tgt2src[t].similarity > 0) {
906 src2tgt[tgt2src[t].idx].similarity = 0;
907 num_bumped++;
e4acc3ba
RB
908 }
909
d730d3f4 910 /* write new mapping */
74a627f0
RB
911 tgt2src[t].idx = s;
912 tgt2src[t].similarity = similarity;
913 src2tgt[s].idx = t;
914 src2tgt[s].similarity = similarity;
d730d3f4 915 }
a21cbb12 916
d730d3f4
RB
917 /* keep best absolute match for copies */
918 if (tgt2src_copy != NULL &&
74a627f0 919 tgt2src_copy[t].similarity < similarity)
d730d3f4 920 {
74a627f0
RB
921 tgt2src_copy[t].idx = s;
922 tgt2src_copy[t].similarity = similarity;
db106d01 923 }
e4acc3ba
RB
924
925 if (++tried_srcs >= num_srcs)
926 break;
927
d730d3f4 928 /* cap on maximum targets we'll examine (per "tgt" file) */
e4acc3ba
RB
929 if (tried_srcs > opts.rename_limit)
930 break;
db106d01 931 }
e4acc3ba
RB
932
933 if (++tried_tgts >= num_tgts)
934 break;
db106d01
RB
935 }
936
e4acc3ba
RB
937 if (num_bumped > 0) /* try again if we bumped some items */
938 goto find_best_matches;
939
940 /*
941 * Rewrite the diffs with renames / copies
942 */
943
d730d3f4 944 git_vector_foreach(&diff->deltas, t, tgt) {
e4acc3ba 945 /* skip things that are not rename targets */
d730d3f4 946 if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
a21cbb12 947 continue;
690bf41c 948
e4acc3ba 949 /* check if this delta was the target of a similarity */
d730d3f4
RB
950 if (tgt2src[t].similarity)
951 best_match = &tgt2src[t];
952 else if (tgt2src_copy && tgt2src_copy[t].similarity)
953 best_match = &tgt2src_copy[t];
954 else
e4acc3ba 955 continue;
d958e37a 956
d730d3f4
RB
957 s = best_match->idx;
958 src = GIT_VECTOR_GET(&diff->deltas, s);
d958e37a 959
a21cbb12
RB
960 /* possible scenarios:
961 * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME
962 * 2. from DELETE to SPLIT/TYPECHANGE = RENAME + DELETE
963 * 3. from SPLIT/TYPECHANGE to ADD/UNTRACK/IGNORE = ADD + RENAME
964 * 4. from SPLIT/TYPECHANGE to SPLIT/TYPECHANGE = RENAME + SPLIT
965 * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY
db106d01
RB
966 */
967
d730d3f4 968 if (src->status == GIT_DELTA_DELETED) {
db106d01 969
d730d3f4 970 if (delta_is_new_only(tgt)) {
db106d01 971
e4acc3ba 972 if (best_match->similarity < opts.rename_threshold)
a21cbb12 973 continue;
960a04dd 974
d730d3f4 975 delta_make_rename(tgt, src, best_match->similarity);
d958e37a 976
d730d3f4 977 src->flags |= GIT_DIFF_FLAG__TO_DELETE;
a21cbb12
RB
978 num_rewrites++;
979 } else {
d730d3f4 980 assert(delta_is_split(tgt));
960a04dd 981
e4acc3ba 982 if (best_match->similarity < opts.rename_from_rewrite_threshold)
a21cbb12 983 continue;
db106d01 984
d730d3f4 985 memcpy(&swap, &tgt->old_file, sizeof(swap));
db106d01 986
d730d3f4 987 delta_make_rename(tgt, src, best_match->similarity);
e4acc3ba
RB
988 num_rewrites--;
989
74a627f0 990 assert(src->status == GIT_DELTA_DELETED);
d730d3f4
RB
991 memcpy(&src->old_file, &swap, sizeof(src->old_file));
992 memset(&src->new_file, 0, sizeof(src->new_file));
993 src->new_file.path = src->old_file.path;
9950bb4e 994 src->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
db106d01 995
d958e37a 996 num_updates++;
7edb74d3
RB
997
998 if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
999 /* what used to be at src t is now at src s */
74a627f0 1000 tgt2src[src2tgt[t].idx].idx = s;
7edb74d3 1001 }
db106d01
RB
1002 }
1003 }
1004
d730d3f4 1005 else if (delta_is_split(src)) {
a21cbb12 1006
d730d3f4 1007 if (delta_is_new_only(tgt)) {
db106d01 1008
e4acc3ba 1009 if (best_match->similarity < opts.rename_threshold)
a21cbb12 1010 continue;
d958e37a 1011
d730d3f4 1012 delta_make_rename(tgt, src, best_match->similarity);
a21cbb12 1013
d730d3f4 1014 src->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ?
a21cbb12 1015 GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED;
74a627f0 1016 src->nfiles = 1;
d730d3f4
RB
1017 memset(&src->old_file, 0, sizeof(src->old_file));
1018 src->old_file.path = src->new_file.path;
9950bb4e 1019 src->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
e4acc3ba 1020
d730d3f4 1021 src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
e4acc3ba 1022 num_rewrites--;
a21cbb12
RB
1023
1024 num_updates++;
1025 } else {
d730d3f4 1026 assert(delta_is_split(src));
a21cbb12 1027
e4acc3ba 1028 if (best_match->similarity < opts.rename_from_rewrite_threshold)
a21cbb12
RB
1029 continue;
1030
d730d3f4 1031 memcpy(&swap, &tgt->old_file, sizeof(swap));
a21cbb12 1032
d730d3f4 1033 delta_make_rename(tgt, src, best_match->similarity);
e4acc3ba
RB
1034 num_rewrites--;
1035 num_updates++;
a21cbb12 1036
d730d3f4 1037 memcpy(&src->old_file, &swap, sizeof(src->old_file));
a21cbb12 1038
e4acc3ba
RB
1039 /* if we've just swapped the new element into the correct
1040 * place, clear the SPLIT flag
67db583d 1041 */
d730d3f4
RB
1042 if (tgt2src[s].idx == t &&
1043 tgt2src[s].similarity >
67db583d 1044 opts.rename_from_rewrite_threshold) {
d730d3f4
RB
1045 src->status = GIT_DELTA_RENAMED;
1046 src->similarity = tgt2src[s].similarity;
1047 tgt2src[s].similarity = 0;
1048 src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
67db583d
RB
1049 num_rewrites--;
1050 }
e4acc3ba 1051 /* otherwise, if we just overwrote a source, update mapping */
7edb74d3 1052 else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
d730d3f4 1053 /* what used to be at src t is now at src s */
74a627f0 1054 tgt2src[src2tgt[t].idx].idx = s;
e4acc3ba 1055 }
67db583d 1056
a21cbb12
RB
1057 num_updates++;
1058 }
1059 }
1060
2123a17f 1061 else if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) {
d730d3f4 1062 if (tgt2src_copy[t].similarity < opts.copy_threshold)
a21cbb12
RB
1063 continue;
1064
d730d3f4
RB
1065 /* always use best possible source for copy */
1066 best_match = &tgt2src_copy[t];
1067 src = GIT_VECTOR_GET(&diff->deltas, best_match->idx);
1068
2123a17f
RB
1069 if (delta_is_split(tgt)) {
1070 error = insert_delete_side_of_split(diff, &diff->deltas, tgt);
1071 if (error < 0)
1072 goto cleanup;
1073 num_rewrites--;
1074 }
1075
1076 if (!delta_is_split(tgt) && !delta_is_new_only(tgt))
1077 continue;
1078
d730d3f4
RB
1079 tgt->status = GIT_DELTA_COPIED;
1080 tgt->similarity = best_match->similarity;
74a627f0 1081 tgt->nfiles = 2;
d730d3f4 1082 memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file));
2123a17f 1083 tgt->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
a21cbb12
RB
1084
1085 num_updates++;
1086 }
db106d01
RB
1087 }
1088
e4acc3ba
RB
1089 /*
1090 * Actually split and delete entries as needed
1091 */
1092
a21cbb12 1093 if (num_rewrites > 0 || num_updates > 0)
960a04dd 1094 error = apply_splits_and_deletes(
d958e37a 1095 diff, diff->deltas.length - num_rewrites,
17c7fbf6
ET
1096 FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) &&
1097 !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY));
d958e37a 1098
960a04dd 1099cleanup:
d730d3f4
RB
1100 git__free(tgt2src);
1101 git__free(src2tgt);
1102 git__free(tgt2src_copy);
db106d01 1103
7e3ed419
RB
1104 if (sigcache) {
1105 for (t = 0; t < num_deltas * 2; ++t) {
1106 if (sigcache[t] != NULL)
1107 opts.metric->free_signature(sigcache[t], opts.metric->payload);
1108 }
1109 git__free(sigcache);
db106d01
RB
1110 }
1111
f8275890
RB
1112 if (!given_opts || !given_opts->metric)
1113 git__free(opts.metric);
1114
960a04dd 1115 return error;
db106d01
RB
1116}
1117
1118#undef FLAG_SET