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