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