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