]>
Commit | Line | Data |
---|---|---|
cd33323b | 1 | /* |
74fa4bfa | 2 | * Copyright (C) 2012 the libgit2 contributors |
cd33323b 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 | */ | |
cd33323b RB |
7 | #include "common.h" |
8 | #include "git2/diff.h" | |
c07d9c95 | 9 | #include "git2/oid.h" |
65b09b1d | 10 | #include "diff.h" |
74fa4bfa | 11 | #include "fileops.h" |
95dfb031 | 12 | #include "config.h" |
14a513e0 | 13 | #include "attr_file.h" |
60b9d3fc | 14 | #include "filter.h" |
cd33323b | 15 | |
41a82592 RB |
16 | static char *diff_prefix_from_pathspec(const git_strarray *pathspec) |
17 | { | |
18 | git_buf prefix = GIT_BUF_INIT; | |
19 | const char *scan; | |
20 | ||
21 | if (git_buf_common_prefix(&prefix, pathspec) < 0) | |
22 | return NULL; | |
23 | ||
24 | /* diff prefix will only be leading non-wildcards */ | |
ffbc689c | 25 | for (scan = prefix.ptr; *scan; ++scan) { |
26 | if (git__iswildcard(*scan) && | |
27 | (scan == prefix.ptr || (*(scan - 1) != '\\'))) | |
28 | break; | |
29 | } | |
41a82592 RB |
30 | git_buf_truncate(&prefix, scan - prefix.ptr); |
31 | ||
ffbc689c | 32 | if (prefix.size <= 0) { |
33 | git_buf_free(&prefix); | |
34 | return NULL; | |
35 | } | |
41a82592 | 36 | |
ffbc689c | 37 | git_buf_unescape(&prefix); |
38 | ||
39 | return git_buf_detach(&prefix); | |
41a82592 RB |
40 | } |
41 | ||
14a513e0 | 42 | static bool diff_pathspec_is_interesting(const git_strarray *pathspec) |
3a437590 | 43 | { |
14a513e0 | 44 | const char *str; |
3a437590 | 45 | |
14a513e0 RB |
46 | if (pathspec == NULL || pathspec->count == 0) |
47 | return false; | |
48 | if (pathspec->count > 1) | |
49 | return true; | |
50 | ||
51 | str = pathspec->strings[0]; | |
52 | if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.'))) | |
53 | return false; | |
54 | return true; | |
55 | } | |
56 | ||
57 | static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) | |
58 | { | |
59 | unsigned int i; | |
60 | git_attr_fnmatch *match; | |
61 | ||
62 | if (!diff->pathspec.length) | |
63 | return true; | |
64 | ||
65 | git_vector_foreach(&diff->pathspec, i, match) { | |
a1773f9d | 66 | int result = strcmp(match->pattern, path) ? FNM_NOMATCH : 0; |
60b9d3fc RB |
67 | |
68 | if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) && | |
a1773f9d | 69 | result == FNM_NOMATCH) |
ffbc689c | 70 | result = p_fnmatch(match->pattern, path, 0); |
14a513e0 RB |
71 | |
72 | /* if we didn't match, look for exact dirname prefix match */ | |
3fbcac89 | 73 | if (result == FNM_NOMATCH && |
14a513e0 RB |
74 | (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && |
75 | strncmp(path, match->pattern, match->length) == 0 && | |
76 | path[match->length] == '/') | |
77 | result = 0; | |
3a437590 | 78 | |
14a513e0 RB |
79 | if (result == 0) |
80 | return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; | |
74fa4bfa | 81 | } |
3a437590 | 82 | |
14a513e0 | 83 | return false; |
3a437590 RB |
84 | } |
85 | ||
74fa4bfa | 86 | static git_diff_delta *diff_delta__alloc( |
a2e895be | 87 | git_diff_list *diff, |
e1bcc191 | 88 | git_delta_t status, |
74fa4bfa | 89 | const char *path) |
3a437590 | 90 | { |
a2e895be | 91 | git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); |
a2e895be | 92 | if (!delta) |
74fa4bfa | 93 | return NULL; |
3a437590 | 94 | |
16b83019 | 95 | delta->old_file.path = git_pool_strdup(&diff->pool, path); |
2de60205 | 96 | if (delta->old_file.path == NULL) { |
a2e895be | 97 | git__free(delta); |
74fa4bfa | 98 | return NULL; |
a2e895be | 99 | } |
40879fac | 100 | |
2de60205 | 101 | delta->new_file.path = delta->old_file.path; |
a2e895be | 102 | |
74fa4bfa RB |
103 | if (diff->opts.flags & GIT_DIFF_REVERSE) { |
104 | switch (status) { | |
e1bcc191 RB |
105 | case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; |
106 | case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; | |
74fa4bfa RB |
107 | default: break; /* leave other status values alone */ |
108 | } | |
109 | } | |
a2e895be RB |
110 | delta->status = status; |
111 | ||
74fa4bfa RB |
112 | return delta; |
113 | } | |
114 | ||
19fa2bc1 RB |
115 | static git_diff_delta *diff_delta__dup( |
116 | const git_diff_delta *d, git_pool *pool) | |
74fa4bfa RB |
117 | { |
118 | git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); | |
119 | if (!delta) | |
120 | return NULL; | |
121 | ||
122 | memcpy(delta, d, sizeof(git_diff_delta)); | |
123 | ||
16b83019 RB |
124 | delta->old_file.path = git_pool_strdup(pool, d->old_file.path); |
125 | if (delta->old_file.path == NULL) | |
19fa2bc1 | 126 | goto fail; |
a2e895be | 127 | |
2de60205 | 128 | if (d->new_file.path != d->old_file.path) { |
16b83019 RB |
129 | delta->new_file.path = git_pool_strdup(pool, d->new_file.path); |
130 | if (delta->new_file.path == NULL) | |
19fa2bc1 | 131 | goto fail; |
74fa4bfa | 132 | } else { |
2de60205 | 133 | delta->new_file.path = delta->old_file.path; |
74fa4bfa | 134 | } |
3a437590 | 135 | |
74fa4bfa | 136 | return delta; |
19fa2bc1 RB |
137 | |
138 | fail: | |
139 | git__free(delta); | |
140 | return NULL; | |
a2e895be RB |
141 | } |
142 | ||
74fa4bfa | 143 | static git_diff_delta *diff_delta__merge_like_cgit( |
19fa2bc1 | 144 | const git_diff_delta *a, const git_diff_delta *b, git_pool *pool) |
a2e895be | 145 | { |
145e696b | 146 | git_diff_delta *dup; |
a2e895be | 147 | |
74fa4bfa RB |
148 | /* Emulate C git for merging two diffs (a la 'git diff <sha>'). |
149 | * | |
150 | * When C git does a diff between the work dir and a tree, it actually | |
151 | * diffs with the index but uses the workdir contents. This emulates | |
152 | * those choices so we can emulate the type of diff. | |
145e696b RB |
153 | * |
154 | * We have three file descriptions here, let's call them: | |
155 | * f1 = a->old_file | |
156 | * f2 = a->new_file AND b->old_file | |
157 | * f3 = b->new_file | |
74fa4bfa | 158 | */ |
145e696b RB |
159 | |
160 | /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */ | |
161 | if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED) | |
162 | return diff_delta__dup(a, pool); | |
163 | ||
164 | /* otherwise, base this diff on the 'b' diff */ | |
165 | if ((dup = diff_delta__dup(b, pool)) == NULL) | |
166 | return NULL; | |
167 | ||
168 | /* If 'a' status is uninteresting, then we're done */ | |
169 | if (a->status == GIT_DELTA_UNMODIFIED) | |
170 | return dup; | |
171 | ||
172 | assert(a->status != GIT_DELTA_UNMODIFIED); | |
173 | assert(b->status != GIT_DELTA_UNMODIFIED); | |
174 | ||
175 | /* A cgit exception is that the diff of a file that is only in the | |
176 | * index (i.e. not in HEAD nor workdir) is given as empty. | |
177 | */ | |
178 | if (dup->status == GIT_DELTA_DELETED) { | |
179 | if (a->status == GIT_DELTA_ADDED) | |
e1bcc191 | 180 | dup->status = GIT_DELTA_UNMODIFIED; |
145e696b RB |
181 | /* else don't overwrite DELETE status */ |
182 | } else { | |
183 | dup->status = a->status; | |
74fa4bfa | 184 | } |
145e696b RB |
185 | |
186 | git_oid_cpy(&dup->old_file.oid, &a->old_file.oid); | |
187 | dup->old_file.mode = a->old_file.mode; | |
188 | dup->old_file.size = a->old_file.size; | |
189 | dup->old_file.flags = a->old_file.flags; | |
3a437590 | 190 | |
74fa4bfa | 191 | return dup; |
3a437590 RB |
192 | } |
193 | ||
74fa4bfa RB |
194 | static int diff_delta__from_one( |
195 | git_diff_list *diff, | |
e1bcc191 | 196 | git_delta_t status, |
74fa4bfa | 197 | const git_index_entry *entry) |
3a437590 | 198 | { |
66142ae0 RB |
199 | git_diff_delta *delta; |
200 | ||
201 | if (status == GIT_DELTA_IGNORED && | |
202 | (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) | |
203 | return 0; | |
204 | ||
205 | if (status == GIT_DELTA_UNTRACKED && | |
206 | (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) | |
207 | return 0; | |
208 | ||
14a513e0 RB |
209 | if (!diff_path_matches_pathspec(diff, entry->path)) |
210 | return 0; | |
211 | ||
66142ae0 | 212 | delta = diff_delta__alloc(diff, status, entry->path); |
ae9e29fd | 213 | GITERR_CHECK_ALLOC(delta); |
3a437590 | 214 | |
74fa4bfa | 215 | /* This fn is just for single-sided diffs */ |
e1bcc191 | 216 | assert(status != GIT_DELTA_MODIFIED); |
3a437590 | 217 | |
e1bcc191 | 218 | if (delta->status == GIT_DELTA_DELETED) { |
2de60205 RB |
219 | delta->old_file.mode = entry->mode; |
220 | delta->old_file.size = entry->file_size; | |
221 | git_oid_cpy(&delta->old_file.oid, &entry->oid); | |
74fa4bfa | 222 | } else /* ADDED, IGNORED, UNTRACKED */ { |
2de60205 RB |
223 | delta->new_file.mode = entry->mode; |
224 | delta->new_file.size = entry->file_size; | |
225 | git_oid_cpy(&delta->new_file.oid, &entry->oid); | |
74fa4bfa | 226 | } |
3a437590 | 227 | |
2de60205 RB |
228 | delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; |
229 | delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; | |
3a437590 | 230 | |
ae9e29fd | 231 | if (git_vector_insert(&diff->deltas, delta) < 0) { |
19fa2bc1 | 232 | git__free(delta); |
ae9e29fd RB |
233 | return -1; |
234 | } | |
3a437590 | 235 | |
ae9e29fd | 236 | return 0; |
3a437590 RB |
237 | } |
238 | ||
74fa4bfa RB |
239 | static int diff_delta__from_two( |
240 | git_diff_list *diff, | |
e1bcc191 | 241 | git_delta_t status, |
2de60205 | 242 | const git_index_entry *old_entry, |
0abd7244 | 243 | uint32_t old_mode, |
2de60205 | 244 | const git_index_entry *new_entry, |
0abd7244 | 245 | uint32_t new_mode, |
74fa4bfa | 246 | git_oid *new_oid) |
65b09b1d | 247 | { |
74fa4bfa | 248 | git_diff_delta *delta; |
65b09b1d | 249 | |
66142ae0 RB |
250 | if (status == GIT_DELTA_UNMODIFIED && |
251 | (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) | |
252 | return 0; | |
253 | ||
74fa4bfa | 254 | if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) { |
0abd7244 RB |
255 | uint32_t temp_mode = old_mode; |
256 | const git_index_entry *temp_entry = old_entry; | |
2de60205 | 257 | old_entry = new_entry; |
0abd7244 RB |
258 | new_entry = temp_entry; |
259 | old_mode = new_mode; | |
260 | new_mode = temp_mode; | |
74fa4bfa | 261 | } |
65b09b1d | 262 | |
2de60205 | 263 | delta = diff_delta__alloc(diff, status, old_entry->path); |
ae9e29fd | 264 | GITERR_CHECK_ALLOC(delta); |
74fa4bfa | 265 | |
2de60205 | 266 | git_oid_cpy(&delta->old_file.oid, &old_entry->oid); |
60b9d3fc RB |
267 | delta->old_file.size = old_entry->file_size; |
268 | delta->old_file.mode = old_mode; | |
2de60205 | 269 | delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; |
74fa4bfa | 270 | |
2de60205 | 271 | git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid); |
60b9d3fc RB |
272 | delta->new_file.size = new_entry->file_size; |
273 | delta->new_file.mode = new_mode; | |
2de60205 RB |
274 | if (new_oid || !git_oid_iszero(&new_entry->oid)) |
275 | delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; | |
74fa4bfa | 276 | |
ae9e29fd | 277 | if (git_vector_insert(&diff->deltas, delta) < 0) { |
19fa2bc1 | 278 | git__free(delta); |
ae9e29fd RB |
279 | return -1; |
280 | } | |
3a437590 | 281 | |
ae9e29fd | 282 | return 0; |
65b09b1d RB |
283 | } |
284 | ||
19fa2bc1 | 285 | static char *diff_strdup_prefix(git_pool *pool, const char *prefix) |
a2e895be RB |
286 | { |
287 | size_t len = strlen(prefix); | |
19fa2bc1 RB |
288 | |
289 | /* append '/' at end if needed */ | |
290 | if (len > 0 && prefix[len - 1] != '/') | |
291 | return git_pool_strcat(pool, prefix, "/"); | |
292 | else | |
293 | return git_pool_strndup(pool, prefix, len + 1); | |
a2e895be RB |
294 | } |
295 | ||
74fa4bfa RB |
296 | static int diff_delta__cmp(const void *a, const void *b) |
297 | { | |
298 | const git_diff_delta *da = a, *db = b; | |
2de60205 | 299 | int val = strcmp(da->old_file.path, db->old_file.path); |
74fa4bfa RB |
300 | return val ? val : ((int)da->status - (int)db->status); |
301 | } | |
302 | ||
95dfb031 RB |
303 | static int config_bool(git_config *cfg, const char *name, int defvalue) |
304 | { | |
305 | int val = defvalue; | |
29e948de VM |
306 | |
307 | if (git_config_get_bool(&val, cfg, name) < 0) | |
95dfb031 | 308 | giterr_clear(); |
29e948de | 309 | |
95dfb031 RB |
310 | return val; |
311 | } | |
312 | ||
65b09b1d RB |
313 | static git_diff_list *git_diff_list_alloc( |
314 | git_repository *repo, const git_diff_options *opts) | |
315 | { | |
95dfb031 | 316 | git_config *cfg; |
14a513e0 | 317 | size_t i; |
65b09b1d | 318 | git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); |
a2e895be RB |
319 | if (diff == NULL) |
320 | return NULL; | |
321 | ||
f335ecd6 | 322 | GIT_REFCOUNT_INC(diff); |
a2e895be | 323 | diff->repo = repo; |
a2e895be | 324 | |
19fa2bc1 RB |
325 | if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 || |
326 | git_pool_init(&diff->pool, 1, 0) < 0) | |
327 | goto fail; | |
328 | ||
95dfb031 | 329 | /* load config values that affect diff behavior */ |
7784bcbb | 330 | if (git_repository_config__weakptr(&cfg, repo) < 0) |
95dfb031 RB |
331 | goto fail; |
332 | if (config_bool(cfg, "core.symlinks", 1)) | |
333 | diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; | |
334 | if (config_bool(cfg, "core.ignorestat", 0)) | |
335 | diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED; | |
336 | if (config_bool(cfg, "core.filemode", 1)) | |
0abd7244 | 337 | diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS; |
95dfb031 RB |
338 | if (config_bool(cfg, "core.trustctime", 1)) |
339 | diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; | |
340 | /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ | |
95dfb031 | 341 | |
a2e895be RB |
342 | if (opts == NULL) |
343 | return diff; | |
344 | ||
345 | memcpy(&diff->opts, opts, sizeof(git_diff_options)); | |
14a513e0 | 346 | memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec)); |
a2e895be | 347 | |
16b83019 | 348 | diff->opts.old_prefix = diff_strdup_prefix(&diff->pool, |
2de60205 | 349 | opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT); |
16b83019 | 350 | diff->opts.new_prefix = diff_strdup_prefix(&diff->pool, |
2de60205 | 351 | opts->new_prefix ? opts->new_prefix : DIFF_NEW_PREFIX_DEFAULT); |
74fa4bfa | 352 | |
16b83019 | 353 | if (!diff->opts.old_prefix || !diff->opts.new_prefix) |
95dfb031 | 354 | goto fail; |
74fa4bfa | 355 | |
a2e895be | 356 | if (diff->opts.flags & GIT_DIFF_REVERSE) { |
2de60205 RB |
357 | char *swap = diff->opts.old_prefix; |
358 | diff->opts.old_prefix = diff->opts.new_prefix; | |
359 | diff->opts.new_prefix = swap; | |
65b09b1d | 360 | } |
a2e895be | 361 | |
14a513e0 RB |
362 | /* only copy pathspec if it is "interesting" so we can test |
363 | * diff->pathspec.length > 0 to know if it is worth calling | |
364 | * fnmatch as we iterate. | |
365 | */ | |
366 | if (!diff_pathspec_is_interesting(&opts->pathspec)) | |
367 | return diff; | |
74fa4bfa | 368 | |
821f6bc7 RB |
369 | if (git_vector_init( |
370 | &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0) | |
14a513e0 RB |
371 | goto fail; |
372 | ||
373 | for (i = 0; i < opts->pathspec.count; ++i) { | |
374 | int ret; | |
375 | const char *pattern = opts->pathspec.strings[i]; | |
19fa2bc1 | 376 | git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); |
14a513e0 RB |
377 | if (!match) |
378 | goto fail; | |
2a99df69 | 379 | match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; |
19fa2bc1 | 380 | ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern); |
904b67e6 | 381 | if (ret == GIT_ENOTFOUND) { |
14a513e0 RB |
382 | git__free(match); |
383 | continue; | |
384 | } else if (ret < 0) | |
385 | goto fail; | |
386 | ||
387 | if (git_vector_insert(&diff->pathspec, match) < 0) | |
388 | goto fail; | |
389 | } | |
a2e895be | 390 | |
65b09b1d | 391 | return diff; |
95dfb031 RB |
392 | |
393 | fail: | |
14a513e0 | 394 | git_diff_list_free(diff); |
95dfb031 | 395 | return NULL; |
65b09b1d RB |
396 | } |
397 | ||
f335ecd6 | 398 | static void diff_list_free(git_diff_list *diff) |
65b09b1d | 399 | { |
3a437590 | 400 | git_diff_delta *delta; |
14a513e0 | 401 | git_attr_fnmatch *match; |
3a437590 RB |
402 | unsigned int i; |
403 | ||
74fa4bfa | 404 | git_vector_foreach(&diff->deltas, i, delta) { |
19fa2bc1 | 405 | git__free(delta); |
74fa4bfa | 406 | diff->deltas.contents[i] = NULL; |
a2e895be | 407 | } |
74fa4bfa | 408 | git_vector_free(&diff->deltas); |
14a513e0 RB |
409 | |
410 | git_vector_foreach(&diff->pathspec, i, match) { | |
19fa2bc1 RB |
411 | git__free(match); |
412 | diff->pathspec.contents[i] = NULL; | |
14a513e0 RB |
413 | } |
414 | git_vector_free(&diff->pathspec); | |
415 | ||
19fa2bc1 | 416 | git_pool_clear(&diff->pool); |
65b09b1d RB |
417 | git__free(diff); |
418 | } | |
419 | ||
f335ecd6 RB |
420 | void git_diff_list_free(git_diff_list *diff) |
421 | { | |
422 | if (!diff) | |
423 | return; | |
424 | ||
425 | GIT_REFCOUNT_DEC(diff, diff_list_free); | |
426 | } | |
427 | ||
74fa4bfa | 428 | static int oid_for_workdir_item( |
65b09b1d | 429 | git_repository *repo, |
74fa4bfa RB |
430 | const git_index_entry *item, |
431 | git_oid *oid) | |
65b09b1d | 432 | { |
ae9e29fd | 433 | int result; |
74fa4bfa | 434 | git_buf full_path = GIT_BUF_INIT; |
a2e895be | 435 | |
ae9e29fd RB |
436 | if (git_buf_joinpath(&full_path, git_repository_workdir(repo), item->path) < 0) |
437 | return -1; | |
a2e895be | 438 | |
ae9e29fd | 439 | /* calculate OID for file if possible*/ |
74fa4bfa | 440 | if (S_ISLNK(item->mode)) |
ae9e29fd RB |
441 | result = git_odb__hashlink(oid, full_path.ptr); |
442 | else if (!git__is_sizet(item->file_size)) { | |
443 | giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); | |
444 | result = -1; | |
445 | } else { | |
60b9d3fc RB |
446 | git_vector filters = GIT_VECTOR_INIT; |
447 | ||
448 | result = git_filters_load( | |
449 | &filters, repo, item->path, GIT_FILTER_TO_ODB); | |
450 | if (result >= 0) { | |
451 | int fd = git_futils_open_ro(full_path.ptr); | |
452 | if (fd < 0) | |
453 | result = fd; | |
454 | else { | |
455 | result = git_odb__hashfd_filtered( | |
456 | oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB, &filters); | |
457 | p_close(fd); | |
458 | } | |
74fa4bfa | 459 | } |
60b9d3fc RB |
460 | |
461 | git_filters_free(&filters); | |
a2e895be | 462 | } |
a2e895be | 463 | |
74fa4bfa | 464 | git_buf_free(&full_path); |
a2e895be | 465 | |
ae9e29fd | 466 | return result; |
a2e895be RB |
467 | } |
468 | ||
0abd7244 | 469 | #define MODE_BITS_MASK 0000777 |
95dfb031 | 470 | |
74fa4bfa | 471 | static int maybe_modified( |
2de60205 | 472 | git_iterator *old_iter, |
74fa4bfa | 473 | const git_index_entry *oitem, |
2de60205 | 474 | git_iterator *new_iter, |
74fa4bfa RB |
475 | const git_index_entry *nitem, |
476 | git_diff_list *diff) | |
e47329b6 | 477 | { |
74fa4bfa | 478 | git_oid noid, *use_noid = NULL; |
66142ae0 | 479 | git_delta_t status = GIT_DELTA_MODIFIED; |
95dfb031 RB |
480 | unsigned int omode = oitem->mode; |
481 | unsigned int nmode = nitem->mode; | |
e47329b6 | 482 | |
2de60205 | 483 | GIT_UNUSED(old_iter); |
e47329b6 | 484 | |
14a513e0 RB |
485 | if (!diff_path_matches_pathspec(diff, oitem->path)) |
486 | return 0; | |
487 | ||
da825c92 | 488 | /* on platforms with no symlinks, preserve mode of existing symlinks */ |
95dfb031 | 489 | if (S_ISLNK(omode) && S_ISREG(nmode) && |
5fdc41e7 RB |
490 | !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS) && |
491 | new_iter->type == GIT_ITERATOR_WORKDIR) | |
da825c92 | 492 | nmode = omode; |
e47329b6 | 493 | |
0abd7244 RB |
494 | /* on platforms with no execmode, just preserve old mode */ |
495 | if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) && | |
496 | (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) && | |
497 | new_iter->type == GIT_ITERATOR_WORKDIR) | |
498 | nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); | |
e47329b6 | 499 | |
0abd7244 | 500 | /* support "assume unchanged" (poorly, b/c we still stat everything) */ |
95dfb031 RB |
501 | if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0) |
502 | status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ? | |
503 | GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED; | |
504 | ||
505 | /* support "skip worktree" index bit */ | |
506 | else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) | |
66142ae0 | 507 | status = GIT_DELTA_UNMODIFIED; |
e47329b6 | 508 | |
66142ae0 | 509 | /* if basic type of file changed, then split into delete and add */ |
95dfb031 | 510 | else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { |
ae9e29fd RB |
511 | if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || |
512 | diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0) | |
513 | return -1; | |
514 | return 0; | |
e47329b6 RB |
515 | } |
516 | ||
66142ae0 RB |
517 | /* if oids and modes match, then file is unmodified */ |
518 | else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 && | |
95dfb031 | 519 | omode == nmode) |
66142ae0 | 520 | status = GIT_DELTA_UNMODIFIED; |
e47329b6 | 521 | |
0abd7244 RB |
522 | /* if modes match and we have an unknown OID and a workdir iterator, |
523 | * then check deeper for matching | |
524 | */ | |
525 | else if (omode == nmode && | |
526 | git_oid_iszero(&nitem->oid) && | |
527 | new_iter->type == GIT_ITERATOR_WORKDIR) | |
528 | { | |
95dfb031 | 529 | /* TODO: add check against index file st_mtime to avoid racy-git */ |
e47329b6 | 530 | |
74fa4bfa RB |
531 | /* if they files look exactly alike, then we'll assume the same */ |
532 | if (oitem->file_size == nitem->file_size && | |
95dfb031 RB |
533 | (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) || |
534 | (oitem->ctime.seconds == nitem->ctime.seconds)) && | |
74fa4bfa | 535 | oitem->mtime.seconds == nitem->mtime.seconds && |
95dfb031 RB |
536 | (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) || |
537 | (oitem->dev == nitem->dev)) && | |
74fa4bfa RB |
538 | oitem->ino == nitem->ino && |
539 | oitem->uid == nitem->uid && | |
540 | oitem->gid == nitem->gid) | |
66142ae0 RB |
541 | status = GIT_DELTA_UNMODIFIED; |
542 | ||
95dfb031 RB |
543 | else if (S_ISGITLINK(nmode)) { |
544 | git_submodule *sub; | |
545 | ||
546 | if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0) | |
547 | status = GIT_DELTA_UNMODIFIED; | |
548 | else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0) | |
549 | return -1; | |
aa13bf05 | 550 | else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) |
95dfb031 RB |
551 | status = GIT_DELTA_UNMODIFIED; |
552 | else { | |
553 | /* TODO: support other GIT_SUBMODULE_IGNORE values */ | |
554 | status = GIT_DELTA_UNMODIFIED; | |
555 | } | |
556 | } | |
e47329b6 | 557 | |
74fa4bfa RB |
558 | /* TODO: check git attributes so we will not have to read the file |
559 | * in if it is marked binary. | |
560 | */ | |
e47329b6 | 561 | |
66142ae0 | 562 | else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0) |
ae9e29fd | 563 | return -1; |
e47329b6 | 564 | |
66142ae0 | 565 | else if (git_oid_cmp(&oitem->oid, &noid) == 0 && |
95dfb031 | 566 | omode == nmode) |
66142ae0 | 567 | status = GIT_DELTA_UNMODIFIED; |
e47329b6 | 568 | |
74fa4bfa RB |
569 | /* store calculated oid so we don't have to recalc later */ |
570 | use_noid = &noid; | |
e47329b6 RB |
571 | } |
572 | ||
0abd7244 RB |
573 | return diff_delta__from_two( |
574 | diff, status, oitem, omode, nitem, nmode, use_noid); | |
e47329b6 RB |
575 | } |
576 | ||
ec40b7f9 PK |
577 | static int git_index_entry_cmp_case(const void *a, const void *b) |
578 | { | |
579 | const git_index_entry *entry_a = a; | |
580 | const git_index_entry *entry_b = b; | |
581 | ||
582 | return strcmp(entry_a->path, entry_b->path); | |
583 | } | |
584 | ||
585 | static int git_index_entry_cmp_icase(const void *a, const void *b) | |
586 | { | |
587 | const git_index_entry *entry_a = a; | |
588 | const git_index_entry *entry_b = b; | |
589 | ||
590 | return strcasecmp(entry_a->path, entry_b->path); | |
591 | } | |
592 | ||
74fa4bfa | 593 | static int diff_from_iterators( |
e47329b6 | 594 | git_repository *repo, |
74fa4bfa | 595 | const git_diff_options *opts, /**< can be NULL for defaults */ |
2de60205 RB |
596 | git_iterator *old_iter, |
597 | git_iterator *new_iter, | |
74fa4bfa | 598 | git_diff_list **diff_ptr) |
e47329b6 | 599 | { |
74fa4bfa | 600 | const git_index_entry *oitem, *nitem; |
b709e951 | 601 | git_buf ignore_prefix = GIT_BUF_INIT; |
74fa4bfa | 602 | git_diff_list *diff = git_diff_list_alloc(repo, opts); |
ec40b7f9 PK |
603 | git_vector_cmp entry_compare; |
604 | ||
ae9e29fd RB |
605 | if (!diff) |
606 | goto fail; | |
e47329b6 | 607 | |
2de60205 RB |
608 | diff->old_src = old_iter->type; |
609 | diff->new_src = new_iter->type; | |
e47329b6 | 610 | |
ec40b7f9 PK |
611 | /* Use case-insensitive compare if either iterator has |
612 | * the ignore_case bit set */ | |
613 | if (!old_iter->ignore_case && !new_iter->ignore_case) { | |
614 | entry_compare = git_index_entry_cmp_case; | |
615 | diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE; | |
616 | } else { | |
617 | entry_compare = git_index_entry_cmp_icase; | |
618 | diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; | |
619 | ||
620 | /* If one of the iterators doesn't have ignore_case set, | |
621 | * then that's unfortunate because we'll have to spool | |
622 | * its data, sort it icase, and then use that for our | |
623 | * merge join to the other iterator that is icase sorted */ | |
624 | if (!old_iter->ignore_case) { | |
625 | if (git_iterator_spoolandsort(&old_iter, old_iter, git_index_entry_cmp_icase, true) < 0) | |
626 | goto fail; | |
627 | } else if (!new_iter->ignore_case) { | |
628 | if (git_iterator_spoolandsort(&new_iter, new_iter, git_index_entry_cmp_icase, true) < 0) | |
629 | goto fail; | |
630 | } | |
631 | } | |
632 | ||
16b83019 RB |
633 | if (git_iterator_current(old_iter, &oitem) < 0 || |
634 | git_iterator_current(new_iter, &nitem) < 0) | |
ae9e29fd | 635 | goto fail; |
65b09b1d | 636 | |
74fa4bfa | 637 | /* run iterators building diffs */ |
ae9e29fd | 638 | while (oitem || nitem) { |
cd33323b | 639 | |
74fa4bfa | 640 | /* create DELETED records for old items not matched in new */ |
ec40b7f9 | 641 | if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) { |
ae9e29fd | 642 | if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || |
16b83019 | 643 | git_iterator_advance(old_iter, &oitem) < 0) |
ae9e29fd | 644 | goto fail; |
65b09b1d | 645 | } |
a2e895be | 646 | |
74fa4bfa RB |
647 | /* create ADDED, TRACKED, or IGNORED records for new items not |
648 | * matched in old (and/or descend into directories as needed) | |
649 | */ | |
ec40b7f9 | 650 | else if (nitem && (!oitem || entry_compare(oitem, nitem) > 0)) { |
bd4ca902 | 651 | git_delta_t delta_type = GIT_DELTA_UNTRACKED; |
a2e895be | 652 | |
bd4ca902 | 653 | /* check if contained in ignored parent directory */ |
b709e951 | 654 | if (git_buf_len(&ignore_prefix) && |
ec40b7f9 | 655 | ITERATOR_PREFIXCMP(*old_iter, nitem->path, git_buf_cstr(&ignore_prefix)) == 0) |
bd4ca902 | 656 | delta_type = GIT_DELTA_IGNORED; |
74fa4bfa RB |
657 | |
658 | if (S_ISDIR(nitem->mode)) { | |
bd4ca902 RB |
659 | /* recurse into directory only if there are tracked items in |
660 | * it or if the user requested the contents of untracked | |
661 | * directories and it is not under an ignored directory. | |
4b136a94 | 662 | */ |
ec40b7f9 | 663 | if ((oitem && ITERATOR_PREFIXCMP(*old_iter, oitem->path, nitem->path) == 0) || |
bd4ca902 RB |
664 | (delta_type == GIT_DELTA_UNTRACKED && |
665 | (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0)) | |
4b136a94 | 666 | { |
bd4ca902 RB |
667 | /* if this directory is ignored, remember it as the |
668 | * "ignore_prefix" for processing contained items | |
669 | */ | |
670 | if (delta_type == GIT_DELTA_UNTRACKED && | |
671 | git_iterator_current_is_ignored(new_iter)) | |
b709e951 | 672 | git_buf_sets(&ignore_prefix, nitem->path); |
40879fac | 673 | |
16b83019 | 674 | if (git_iterator_advance_into_directory(new_iter, &nitem) < 0) |
ae9e29fd | 675 | goto fail; |
40879fac | 676 | |
74fa4bfa RB |
677 | continue; |
678 | } | |
65b09b1d | 679 | } |
bd4ca902 RB |
680 | |
681 | /* In core git, the next two "else if" clauses are effectively | |
682 | * reversed -- i.e. when an untracked file contained in an | |
683 | * ignored directory is individually ignored, it shows up as an | |
684 | * ignored file in the diff list, even though other untracked | |
685 | * files in the same directory are skipped completely. | |
686 | * | |
687 | * To me, this is odd. If the directory is ignored and the file | |
688 | * is untracked, we should skip it consistently, regardless of | |
689 | * whether it happens to match a pattern in the ignore file. | |
690 | * | |
691 | * To match the core git behavior, just reverse the following | |
692 | * two "else if" cases so that individual file ignores are | |
693 | * checked before container directory exclusions are used to | |
694 | * skip the file. | |
695 | */ | |
696 | else if (delta_type == GIT_DELTA_IGNORED) { | |
697 | if (git_iterator_advance(new_iter, &nitem) < 0) | |
698 | goto fail; | |
699 | continue; /* ignored parent directory, so skip completely */ | |
700 | } | |
701 | ||
702 | else if (git_iterator_current_is_ignored(new_iter)) | |
e1bcc191 | 703 | delta_type = GIT_DELTA_IGNORED; |
bd4ca902 RB |
704 | |
705 | else if (new_iter->type != GIT_ITERATOR_WORKDIR) | |
706 | delta_type = GIT_DELTA_ADDED; | |
74fa4bfa | 707 | |
ae9e29fd | 708 | if (diff_delta__from_one(diff, delta_type, nitem) < 0 || |
16b83019 | 709 | git_iterator_advance(new_iter, &nitem) < 0) |
ae9e29fd | 710 | goto fail; |
65b09b1d RB |
711 | } |
712 | ||
74fa4bfa RB |
713 | /* otherwise item paths match, so create MODIFIED record |
714 | * (or ADDED and DELETED pair if type changed) | |
a2e895be | 715 | */ |
ae9e29fd | 716 | else { |
f08c60a5 | 717 | assert(oitem && nitem && entry_compare(oitem, nitem) == 0); |
a2e895be | 718 | |
16b83019 RB |
719 | if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 || |
720 | git_iterator_advance(old_iter, &oitem) < 0 || | |
721 | git_iterator_advance(new_iter, &nitem) < 0) | |
ae9e29fd RB |
722 | goto fail; |
723 | } | |
65b09b1d RB |
724 | } |
725 | ||
2de60205 RB |
726 | git_iterator_free(old_iter); |
727 | git_iterator_free(new_iter); | |
b709e951 RB |
728 | git_buf_free(&ignore_prefix); |
729 | ||
74fa4bfa | 730 | *diff_ptr = diff; |
ae9e29fd | 731 | return 0; |
65b09b1d | 732 | |
ae9e29fd | 733 | fail: |
16b83019 RB |
734 | git_iterator_free(old_iter); |
735 | git_iterator_free(new_iter); | |
b709e951 RB |
736 | git_buf_free(&ignore_prefix); |
737 | ||
ae9e29fd RB |
738 | git_diff_list_free(diff); |
739 | *diff_ptr = NULL; | |
740 | return -1; | |
65b09b1d RB |
741 | } |
742 | ||
74fa4bfa RB |
743 | |
744 | int git_diff_tree_to_tree( | |
745 | git_repository *repo, | |
746 | const git_diff_options *opts, /**< can be NULL for defaults */ | |
2de60205 RB |
747 | git_tree *old_tree, |
748 | git_tree *new_tree, | |
74fa4bfa | 749 | git_diff_list **diff) |
65b09b1d | 750 | { |
74fa4bfa | 751 | git_iterator *a = NULL, *b = NULL; |
41a82592 | 752 | char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; |
3a437590 | 753 | |
2de60205 | 754 | assert(repo && old_tree && new_tree && diff); |
3a437590 | 755 | |
41a82592 RB |
756 | if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || |
757 | git_iterator_for_tree_range(&b, repo, new_tree, prefix, prefix) < 0) | |
ae9e29fd | 758 | return -1; |
3a437590 | 759 | |
41a82592 RB |
760 | git__free(prefix); |
761 | ||
74fa4bfa | 762 | return diff_from_iterators(repo, opts, a, b, diff); |
65b09b1d RB |
763 | } |
764 | ||
74fa4bfa RB |
765 | int git_diff_index_to_tree( |
766 | git_repository *repo, | |
767 | const git_diff_options *opts, | |
2de60205 | 768 | git_tree *old_tree, |
74fa4bfa | 769 | git_diff_list **diff) |
65b09b1d | 770 | { |
74fa4bfa | 771 | git_iterator *a = NULL, *b = NULL; |
41a82592 | 772 | char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; |
3a437590 | 773 | |
7e000ab2 | 774 | assert(repo && diff); |
3a437590 | 775 | |
41a82592 | 776 | if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || |
1d94a7d0 CMN |
777 | git_iterator_for_index_range(&b, repo, prefix, prefix) < 0) |
778 | goto on_error; | |
3a437590 | 779 | |
41a82592 RB |
780 | git__free(prefix); |
781 | ||
74fa4bfa | 782 | return diff_from_iterators(repo, opts, a, b, diff); |
1d94a7d0 CMN |
783 | |
784 | on_error: | |
785 | git__free(prefix); | |
786 | git_iterator_free(a); | |
787 | return -1; | |
65b09b1d RB |
788 | } |
789 | ||
74fa4bfa RB |
790 | int git_diff_workdir_to_index( |
791 | git_repository *repo, | |
792 | const git_diff_options *opts, | |
793 | git_diff_list **diff) | |
65b09b1d | 794 | { |
74fa4bfa | 795 | git_iterator *a = NULL, *b = NULL; |
41a82592 | 796 | char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; |
3a437590 | 797 | |
74fa4bfa | 798 | assert(repo && diff); |
3a437590 | 799 | |
41a82592 | 800 | if (git_iterator_for_index_range(&a, repo, prefix, prefix) < 0 || |
1d94a7d0 CMN |
801 | git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0) |
802 | goto on_error; | |
3a437590 | 803 | |
41a82592 RB |
804 | git__free(prefix); |
805 | ||
74fa4bfa | 806 | return diff_from_iterators(repo, opts, a, b, diff); |
1d94a7d0 CMN |
807 | |
808 | on_error: | |
809 | git__free(prefix); | |
810 | git_iterator_free(a); | |
811 | return -1; | |
65b09b1d RB |
812 | } |
813 | ||
65b09b1d | 814 | |
74fa4bfa RB |
815 | int git_diff_workdir_to_tree( |
816 | git_repository *repo, | |
817 | const git_diff_options *opts, | |
2de60205 | 818 | git_tree *old_tree, |
74fa4bfa | 819 | git_diff_list **diff) |
65b09b1d | 820 | { |
74fa4bfa | 821 | git_iterator *a = NULL, *b = NULL; |
41a82592 | 822 | char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; |
3a437590 | 823 | |
2de60205 | 824 | assert(repo && old_tree && diff); |
3a437590 | 825 | |
41a82592 | 826 | if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || |
1d94a7d0 CMN |
827 | git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0) |
828 | goto on_error; | |
3a437590 | 829 | |
41a82592 RB |
830 | git__free(prefix); |
831 | ||
74fa4bfa | 832 | return diff_from_iterators(repo, opts, a, b, diff); |
1d94a7d0 CMN |
833 | |
834 | on_error: | |
835 | git__free(prefix); | |
836 | git_iterator_free(a); | |
837 | return -1; | |
65b09b1d RB |
838 | } |
839 | ||
74fa4bfa RB |
840 | int git_diff_merge( |
841 | git_diff_list *onto, | |
842 | const git_diff_list *from) | |
65b09b1d | 843 | { |
ae9e29fd | 844 | int error = 0; |
19fa2bc1 | 845 | git_pool onto_pool; |
74fa4bfa RB |
846 | git_vector onto_new; |
847 | git_diff_delta *delta; | |
ec40b7f9 | 848 | bool ignore_case = false; |
1db12b00 RB |
849 | unsigned int i, j; |
850 | ||
851 | assert(onto && from); | |
3a437590 | 852 | |
1db12b00 RB |
853 | if (!from->deltas.length) |
854 | return 0; | |
3a437590 | 855 | |
19fa2bc1 RB |
856 | if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 || |
857 | git_pool_init(&onto_pool, 1, 0) < 0) | |
ae9e29fd | 858 | return -1; |
74fa4bfa | 859 | |
ec40b7f9 PK |
860 | if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 || |
861 | (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0) | |
862 | { | |
863 | ignore_case = true; | |
864 | ||
865 | /* This function currently only supports merging diff lists that | |
866 | * are sorted identically. */ | |
867 | assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 && | |
868 | (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0); | |
869 | } | |
870 | ||
1db12b00 RB |
871 | for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { |
872 | git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); | |
873 | const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); | |
ec40b7f9 | 874 | int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); |
1db12b00 RB |
875 | |
876 | if (cmp < 0) { | |
19fa2bc1 | 877 | delta = diff_delta__dup(o, &onto_pool); |
74fa4bfa | 878 | i++; |
1db12b00 | 879 | } else if (cmp > 0) { |
19fa2bc1 | 880 | delta = diff_delta__dup(f, &onto_pool); |
74fa4bfa RB |
881 | j++; |
882 | } else { | |
19fa2bc1 | 883 | delta = diff_delta__merge_like_cgit(o, f, &onto_pool); |
74fa4bfa RB |
884 | i++; |
885 | j++; | |
886 | } | |
3a437590 | 887 | |
a48ea31d | 888 | if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) |
74fa4bfa | 889 | break; |
a2e895be | 890 | } |
65b09b1d | 891 | |
1db12b00 | 892 | if (!error) { |
74fa4bfa | 893 | git_vector_swap(&onto->deltas, &onto_new); |
19fa2bc1 | 894 | git_pool_swap(&onto->pool, &onto_pool); |
74fa4bfa | 895 | onto->new_src = from->new_src; |
145e696b RB |
896 | |
897 | /* prefix strings also come from old pool, so recreate those.*/ | |
898 | onto->opts.old_prefix = | |
71d27358 | 899 | git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix); |
145e696b | 900 | onto->opts.new_prefix = |
71d27358 | 901 | git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); |
65b09b1d | 902 | } |
cd33323b | 903 | |
74fa4bfa | 904 | git_vector_foreach(&onto_new, i, delta) |
19fa2bc1 | 905 | git__free(delta); |
74fa4bfa | 906 | git_vector_free(&onto_new); |
19fa2bc1 | 907 | git_pool_clear(&onto_pool); |
cd33323b | 908 | |
74fa4bfa | 909 | return error; |
cd33323b | 910 | } |