]>
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" | |
65b09b1d | 9 | #include "diff.h" |
74fa4bfa | 10 | #include "fileops.h" |
95dfb031 | 11 | #include "config.h" |
14a513e0 RB |
12 | #include "attr_file.h" |
13 | ||
14 | static bool diff_pathspec_is_interesting(const git_strarray *pathspec) | |
15 | { | |
16 | const char *str; | |
17 | ||
18 | if (pathspec == NULL || pathspec->count == 0) | |
19 | return false; | |
20 | if (pathspec->count > 1) | |
21 | return true; | |
22 | ||
23 | str = pathspec->strings[0]; | |
24 | if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.'))) | |
25 | return false; | |
26 | return true; | |
27 | } | |
28 | ||
29 | static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) | |
30 | { | |
31 | unsigned int i; | |
32 | git_attr_fnmatch *match; | |
33 | ||
34 | if (!diff->pathspec.length) | |
35 | return true; | |
36 | ||
37 | git_vector_foreach(&diff->pathspec, i, match) { | |
38 | int result = git__fnmatch(match->pattern, path, 0); | |
39 | ||
40 | /* if we didn't match, look for exact dirname prefix match */ | |
41 | if (result == GIT_ENOMATCH && | |
42 | (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && | |
43 | strncmp(path, match->pattern, match->length) == 0 && | |
44 | path[match->length] == '/') | |
45 | result = 0; | |
46 | ||
47 | if (result == 0) | |
48 | return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; | |
49 | ||
50 | if (result != GIT_ENOMATCH) | |
51 | giterr_clear(); | |
52 | } | |
53 | ||
54 | return false; | |
55 | } | |
cd33323b | 56 | |
74fa4bfa | 57 | static git_diff_delta *diff_delta__alloc( |
a2e895be | 58 | git_diff_list *diff, |
e1bcc191 | 59 | git_delta_t status, |
74fa4bfa | 60 | const char *path) |
3a437590 | 61 | { |
a2e895be | 62 | git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); |
a2e895be | 63 | if (!delta) |
74fa4bfa | 64 | return NULL; |
3a437590 | 65 | |
19fa2bc1 | 66 | delta->old.path = git_pool_strdup(&diff->pool, path); |
74fa4bfa | 67 | if (delta->old.path == NULL) { |
a2e895be | 68 | git__free(delta); |
74fa4bfa | 69 | return NULL; |
a2e895be | 70 | } |
74fa4bfa | 71 | delta->new.path = delta->old.path; |
a2e895be | 72 | |
74fa4bfa RB |
73 | if (diff->opts.flags & GIT_DIFF_REVERSE) { |
74 | switch (status) { | |
e1bcc191 RB |
75 | case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; |
76 | case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; | |
74fa4bfa RB |
77 | default: break; /* leave other status values alone */ |
78 | } | |
79 | } | |
a2e895be RB |
80 | delta->status = status; |
81 | ||
74fa4bfa RB |
82 | return delta; |
83 | } | |
84 | ||
19fa2bc1 RB |
85 | static git_diff_delta *diff_delta__dup( |
86 | const git_diff_delta *d, git_pool *pool) | |
74fa4bfa RB |
87 | { |
88 | git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); | |
89 | if (!delta) | |
90 | return NULL; | |
91 | ||
92 | memcpy(delta, d, sizeof(git_diff_delta)); | |
93 | ||
19fa2bc1 RB |
94 | delta->old.path = git_pool_strdup(pool, d->old.path); |
95 | if (delta->old.path == NULL) | |
96 | goto fail; | |
a2e895be | 97 | |
74fa4bfa | 98 | if (d->new.path != d->old.path) { |
19fa2bc1 RB |
99 | delta->new.path = git_pool_strdup(pool, d->new.path); |
100 | if (delta->new.path == NULL) | |
101 | goto fail; | |
74fa4bfa RB |
102 | } else { |
103 | delta->new.path = delta->old.path; | |
74fa4bfa | 104 | } |
3a437590 | 105 | |
74fa4bfa | 106 | return delta; |
19fa2bc1 RB |
107 | |
108 | fail: | |
109 | git__free(delta); | |
110 | return NULL; | |
a2e895be RB |
111 | } |
112 | ||
74fa4bfa | 113 | static git_diff_delta *diff_delta__merge_like_cgit( |
19fa2bc1 | 114 | const git_diff_delta *a, const git_diff_delta *b, git_pool *pool) |
a2e895be | 115 | { |
19fa2bc1 | 116 | git_diff_delta *dup = diff_delta__dup(a, pool); |
74fa4bfa RB |
117 | if (!dup) |
118 | return NULL; | |
a2e895be | 119 | |
74fa4bfa RB |
120 | if (git_oid_cmp(&dup->new.oid, &b->new.oid) == 0) |
121 | return dup; | |
a2e895be | 122 | |
74fa4bfa | 123 | git_oid_cpy(&dup->new.oid, &b->new.oid); |
a2e895be | 124 | |
74fa4bfa RB |
125 | dup->new.mode = b->new.mode; |
126 | dup->new.size = b->new.size; | |
19fa2bc1 | 127 | dup->new.flags = b->new.flags; |
a2e895be | 128 | |
74fa4bfa RB |
129 | /* Emulate C git for merging two diffs (a la 'git diff <sha>'). |
130 | * | |
131 | * When C git does a diff between the work dir and a tree, it actually | |
132 | * diffs with the index but uses the workdir contents. This emulates | |
133 | * those choices so we can emulate the type of diff. | |
134 | */ | |
135 | if (git_oid_cmp(&dup->old.oid, &dup->new.oid) == 0) { | |
e1bcc191 | 136 | if (dup->status == GIT_DELTA_DELETED) |
74fa4bfa | 137 | /* preserve pending delete info */; |
e1bcc191 RB |
138 | else if (b->status == GIT_DELTA_UNTRACKED || |
139 | b->status == GIT_DELTA_IGNORED) | |
74fa4bfa RB |
140 | dup->status = b->status; |
141 | else | |
e1bcc191 | 142 | dup->status = GIT_DELTA_UNMODIFIED; |
74fa4bfa | 143 | } |
e1bcc191 RB |
144 | else if (dup->status == GIT_DELTA_UNMODIFIED || |
145 | b->status == GIT_DELTA_DELETED) | |
74fa4bfa | 146 | dup->status = b->status; |
3a437590 | 147 | |
74fa4bfa | 148 | return dup; |
3a437590 RB |
149 | } |
150 | ||
74fa4bfa RB |
151 | static int diff_delta__from_one( |
152 | git_diff_list *diff, | |
e1bcc191 | 153 | git_delta_t status, |
74fa4bfa | 154 | const git_index_entry *entry) |
3a437590 | 155 | { |
66142ae0 RB |
156 | git_diff_delta *delta; |
157 | ||
158 | if (status == GIT_DELTA_IGNORED && | |
159 | (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) | |
160 | return 0; | |
161 | ||
162 | if (status == GIT_DELTA_UNTRACKED && | |
163 | (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) | |
164 | return 0; | |
165 | ||
14a513e0 RB |
166 | if (!diff_path_matches_pathspec(diff, entry->path)) |
167 | return 0; | |
168 | ||
66142ae0 | 169 | delta = diff_delta__alloc(diff, status, entry->path); |
ae9e29fd | 170 | GITERR_CHECK_ALLOC(delta); |
3a437590 | 171 | |
74fa4bfa | 172 | /* This fn is just for single-sided diffs */ |
e1bcc191 | 173 | assert(status != GIT_DELTA_MODIFIED); |
3a437590 | 174 | |
e1bcc191 | 175 | if (delta->status == GIT_DELTA_DELETED) { |
74fa4bfa RB |
176 | delta->old.mode = entry->mode; |
177 | delta->old.size = entry->file_size; | |
178 | git_oid_cpy(&delta->old.oid, &entry->oid); | |
179 | } else /* ADDED, IGNORED, UNTRACKED */ { | |
180 | delta->new.mode = entry->mode; | |
181 | delta->new.size = entry->file_size; | |
182 | git_oid_cpy(&delta->new.oid, &entry->oid); | |
183 | } | |
3a437590 | 184 | |
74fa4bfa RB |
185 | delta->old.flags |= GIT_DIFF_FILE_VALID_OID; |
186 | delta->new.flags |= GIT_DIFF_FILE_VALID_OID; | |
3a437590 | 187 | |
ae9e29fd | 188 | if (git_vector_insert(&diff->deltas, delta) < 0) { |
19fa2bc1 | 189 | git__free(delta); |
ae9e29fd RB |
190 | return -1; |
191 | } | |
3a437590 | 192 | |
ae9e29fd | 193 | return 0; |
3a437590 RB |
194 | } |
195 | ||
74fa4bfa RB |
196 | static int diff_delta__from_two( |
197 | git_diff_list *diff, | |
e1bcc191 | 198 | git_delta_t status, |
74fa4bfa RB |
199 | const git_index_entry *old, |
200 | const git_index_entry *new, | |
201 | git_oid *new_oid) | |
65b09b1d | 202 | { |
74fa4bfa | 203 | git_diff_delta *delta; |
65b09b1d | 204 | |
66142ae0 RB |
205 | if (status == GIT_DELTA_UNMODIFIED && |
206 | (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) | |
207 | return 0; | |
208 | ||
74fa4bfa RB |
209 | if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) { |
210 | const git_index_entry *temp = old; | |
211 | old = new; | |
212 | new = temp; | |
213 | } | |
65b09b1d | 214 | |
74fa4bfa | 215 | delta = diff_delta__alloc(diff, status, old->path); |
ae9e29fd | 216 | GITERR_CHECK_ALLOC(delta); |
74fa4bfa RB |
217 | |
218 | delta->old.mode = old->mode; | |
219 | git_oid_cpy(&delta->old.oid, &old->oid); | |
220 | delta->old.flags |= GIT_DIFF_FILE_VALID_OID; | |
221 | ||
222 | delta->new.mode = new->mode; | |
223 | git_oid_cpy(&delta->new.oid, new_oid ? new_oid : &new->oid); | |
224 | if (new_oid || !git_oid_iszero(&new->oid)) | |
225 | delta->new.flags |= GIT_DIFF_FILE_VALID_OID; | |
226 | ||
ae9e29fd | 227 | if (git_vector_insert(&diff->deltas, delta) < 0) { |
19fa2bc1 | 228 | git__free(delta); |
ae9e29fd RB |
229 | return -1; |
230 | } | |
3a437590 | 231 | |
ae9e29fd | 232 | return 0; |
65b09b1d RB |
233 | } |
234 | ||
19fa2bc1 | 235 | static char *diff_strdup_prefix(git_pool *pool, const char *prefix) |
a2e895be RB |
236 | { |
237 | size_t len = strlen(prefix); | |
19fa2bc1 RB |
238 | |
239 | /* append '/' at end if needed */ | |
240 | if (len > 0 && prefix[len - 1] != '/') | |
241 | return git_pool_strcat(pool, prefix, "/"); | |
242 | else | |
243 | return git_pool_strndup(pool, prefix, len + 1); | |
a2e895be RB |
244 | } |
245 | ||
74fa4bfa RB |
246 | static int diff_delta__cmp(const void *a, const void *b) |
247 | { | |
248 | const git_diff_delta *da = a, *db = b; | |
249 | int val = strcmp(da->old.path, db->old.path); | |
250 | return val ? val : ((int)da->status - (int)db->status); | |
251 | } | |
252 | ||
95dfb031 RB |
253 | static int config_bool(git_config *cfg, const char *name, int defvalue) |
254 | { | |
255 | int val = defvalue; | |
256 | if (git_config_get_bool(cfg, name, &val) < 0) | |
257 | giterr_clear(); | |
258 | return val; | |
259 | } | |
260 | ||
65b09b1d RB |
261 | static git_diff_list *git_diff_list_alloc( |
262 | git_repository *repo, const git_diff_options *opts) | |
263 | { | |
95dfb031 | 264 | git_config *cfg; |
14a513e0 | 265 | size_t i; |
65b09b1d | 266 | git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); |
a2e895be RB |
267 | if (diff == NULL) |
268 | return NULL; | |
269 | ||
270 | diff->repo = repo; | |
a2e895be | 271 | |
19fa2bc1 RB |
272 | if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 || |
273 | git_pool_init(&diff->pool, 1, 0) < 0) | |
274 | goto fail; | |
275 | ||
95dfb031 | 276 | /* load config values that affect diff behavior */ |
7784bcbb | 277 | if (git_repository_config__weakptr(&cfg, repo) < 0) |
95dfb031 RB |
278 | goto fail; |
279 | if (config_bool(cfg, "core.symlinks", 1)) | |
280 | diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; | |
281 | if (config_bool(cfg, "core.ignorestat", 0)) | |
282 | diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED; | |
283 | if (config_bool(cfg, "core.filemode", 1)) | |
284 | diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT; | |
285 | if (config_bool(cfg, "core.trustctime", 1)) | |
286 | diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; | |
287 | /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ | |
95dfb031 | 288 | |
a2e895be RB |
289 | if (opts == NULL) |
290 | return diff; | |
291 | ||
292 | memcpy(&diff->opts, opts, sizeof(git_diff_options)); | |
14a513e0 | 293 | memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec)); |
a2e895be | 294 | |
19fa2bc1 | 295 | diff->opts.src_prefix = diff_strdup_prefix(&diff->pool, |
74fa4bfa | 296 | opts->src_prefix ? opts->src_prefix : DIFF_SRC_PREFIX_DEFAULT); |
19fa2bc1 | 297 | diff->opts.dst_prefix = diff_strdup_prefix(&diff->pool, |
74fa4bfa RB |
298 | opts->dst_prefix ? opts->dst_prefix : DIFF_DST_PREFIX_DEFAULT); |
299 | ||
95dfb031 RB |
300 | if (!diff->opts.src_prefix || !diff->opts.dst_prefix) |
301 | goto fail; | |
74fa4bfa | 302 | |
a2e895be RB |
303 | if (diff->opts.flags & GIT_DIFF_REVERSE) { |
304 | char *swap = diff->opts.src_prefix; | |
305 | diff->opts.src_prefix = diff->opts.dst_prefix; | |
306 | diff->opts.dst_prefix = swap; | |
65b09b1d | 307 | } |
a2e895be | 308 | |
14a513e0 RB |
309 | /* only copy pathspec if it is "interesting" so we can test |
310 | * diff->pathspec.length > 0 to know if it is worth calling | |
311 | * fnmatch as we iterate. | |
312 | */ | |
313 | if (!diff_pathspec_is_interesting(&opts->pathspec)) | |
314 | return diff; | |
315 | ||
821f6bc7 RB |
316 | if (git_vector_init( |
317 | &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0) | |
14a513e0 RB |
318 | goto fail; |
319 | ||
320 | for (i = 0; i < opts->pathspec.count; ++i) { | |
321 | int ret; | |
322 | const char *pattern = opts->pathspec.strings[i]; | |
19fa2bc1 | 323 | git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); |
14a513e0 RB |
324 | if (!match) |
325 | goto fail; | |
19fa2bc1 | 326 | ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern); |
14a513e0 RB |
327 | if (ret == GIT_ENOTFOUND) { |
328 | git__free(match); | |
329 | continue; | |
330 | } else if (ret < 0) | |
331 | goto fail; | |
332 | ||
333 | if (git_vector_insert(&diff->pathspec, match) < 0) | |
334 | goto fail; | |
335 | } | |
a2e895be | 336 | |
65b09b1d | 337 | return diff; |
95dfb031 RB |
338 | |
339 | fail: | |
14a513e0 | 340 | git_diff_list_free(diff); |
95dfb031 | 341 | return NULL; |
65b09b1d RB |
342 | } |
343 | ||
344 | void git_diff_list_free(git_diff_list *diff) | |
345 | { | |
3a437590 | 346 | git_diff_delta *delta; |
14a513e0 | 347 | git_attr_fnmatch *match; |
3a437590 RB |
348 | unsigned int i; |
349 | ||
65b09b1d RB |
350 | if (!diff) |
351 | return; | |
3a437590 | 352 | |
74fa4bfa | 353 | git_vector_foreach(&diff->deltas, i, delta) { |
19fa2bc1 | 354 | git__free(delta); |
74fa4bfa | 355 | diff->deltas.contents[i] = NULL; |
a2e895be | 356 | } |
74fa4bfa | 357 | git_vector_free(&diff->deltas); |
14a513e0 RB |
358 | |
359 | git_vector_foreach(&diff->pathspec, i, match) { | |
19fa2bc1 RB |
360 | git__free(match); |
361 | diff->pathspec.contents[i] = NULL; | |
14a513e0 RB |
362 | } |
363 | git_vector_free(&diff->pathspec); | |
364 | ||
19fa2bc1 | 365 | git_pool_clear(&diff->pool); |
65b09b1d RB |
366 | git__free(diff); |
367 | } | |
368 | ||
74fa4bfa | 369 | static int oid_for_workdir_item( |
65b09b1d | 370 | git_repository *repo, |
74fa4bfa RB |
371 | const git_index_entry *item, |
372 | git_oid *oid) | |
65b09b1d | 373 | { |
ae9e29fd | 374 | int result; |
74fa4bfa | 375 | git_buf full_path = GIT_BUF_INIT; |
a2e895be | 376 | |
ae9e29fd RB |
377 | if (git_buf_joinpath(&full_path, git_repository_workdir(repo), item->path) < 0) |
378 | return -1; | |
a2e895be | 379 | |
ae9e29fd | 380 | /* calculate OID for file if possible*/ |
74fa4bfa | 381 | if (S_ISLNK(item->mode)) |
ae9e29fd RB |
382 | result = git_odb__hashlink(oid, full_path.ptr); |
383 | else if (!git__is_sizet(item->file_size)) { | |
384 | giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); | |
385 | result = -1; | |
386 | } else { | |
387 | int fd = git_futils_open_ro(full_path.ptr); | |
388 | if (fd < 0) | |
389 | result = fd; | |
74fa4bfa | 390 | else { |
ae9e29fd | 391 | result = git_odb__hashfd( |
74fa4bfa RB |
392 | oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB); |
393 | p_close(fd); | |
394 | } | |
a2e895be | 395 | } |
a2e895be | 396 | |
74fa4bfa | 397 | git_buf_free(&full_path); |
a2e895be | 398 | |
ae9e29fd | 399 | return result; |
a2e895be RB |
400 | } |
401 | ||
95dfb031 RB |
402 | #define EXEC_BIT_MASK 0000111 |
403 | ||
74fa4bfa RB |
404 | static int maybe_modified( |
405 | git_iterator *old, | |
406 | const git_index_entry *oitem, | |
407 | git_iterator *new, | |
408 | const git_index_entry *nitem, | |
409 | git_diff_list *diff) | |
e47329b6 | 410 | { |
74fa4bfa | 411 | git_oid noid, *use_noid = NULL; |
66142ae0 | 412 | git_delta_t status = GIT_DELTA_MODIFIED; |
95dfb031 RB |
413 | unsigned int omode = oitem->mode; |
414 | unsigned int nmode = nitem->mode; | |
e47329b6 | 415 | |
854eccbb | 416 | GIT_UNUSED(old); |
e47329b6 | 417 | |
14a513e0 RB |
418 | if (!diff_path_matches_pathspec(diff, oitem->path)) |
419 | return 0; | |
420 | ||
95dfb031 RB |
421 | /* on platforms with no symlinks, promote plain files to symlinks */ |
422 | if (S_ISLNK(omode) && S_ISREG(nmode) && | |
423 | !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) | |
424 | nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK); | |
425 | ||
426 | /* on platforms with no execmode, clear exec bit from comparisons */ | |
427 | if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) { | |
428 | omode = omode & ~EXEC_BIT_MASK; | |
429 | nmode = nmode & ~EXEC_BIT_MASK; | |
430 | } | |
431 | ||
432 | /* support "assume unchanged" (badly, b/c we still stat everything) */ | |
433 | if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0) | |
434 | status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ? | |
435 | GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED; | |
436 | ||
437 | /* support "skip worktree" index bit */ | |
438 | else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) | |
66142ae0 | 439 | status = GIT_DELTA_UNMODIFIED; |
e47329b6 | 440 | |
66142ae0 | 441 | /* if basic type of file changed, then split into delete and add */ |
95dfb031 | 442 | else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { |
ae9e29fd RB |
443 | if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || |
444 | diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0) | |
445 | return -1; | |
446 | return 0; | |
e47329b6 RB |
447 | } |
448 | ||
66142ae0 RB |
449 | /* if oids and modes match, then file is unmodified */ |
450 | else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 && | |
95dfb031 | 451 | omode == nmode) |
66142ae0 | 452 | status = GIT_DELTA_UNMODIFIED; |
e47329b6 | 453 | |
66142ae0 RB |
454 | /* if we have a workdir item with an unknown oid, check deeper */ |
455 | else if (git_oid_iszero(&nitem->oid) && new->type == GIT_ITERATOR_WORKDIR) { | |
95dfb031 RB |
456 | /* TODO: add check against index file st_mtime to avoid racy-git */ |
457 | ||
74fa4bfa RB |
458 | /* if they files look exactly alike, then we'll assume the same */ |
459 | if (oitem->file_size == nitem->file_size && | |
95dfb031 RB |
460 | (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) || |
461 | (oitem->ctime.seconds == nitem->ctime.seconds)) && | |
74fa4bfa | 462 | oitem->mtime.seconds == nitem->mtime.seconds && |
95dfb031 RB |
463 | (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) || |
464 | (oitem->dev == nitem->dev)) && | |
74fa4bfa RB |
465 | oitem->ino == nitem->ino && |
466 | oitem->uid == nitem->uid && | |
467 | oitem->gid == nitem->gid) | |
66142ae0 RB |
468 | status = GIT_DELTA_UNMODIFIED; |
469 | ||
95dfb031 RB |
470 | else if (S_ISGITLINK(nmode)) { |
471 | git_submodule *sub; | |
472 | ||
473 | if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0) | |
474 | status = GIT_DELTA_UNMODIFIED; | |
475 | else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0) | |
476 | return -1; | |
477 | else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL) | |
478 | status = GIT_DELTA_UNMODIFIED; | |
479 | else { | |
480 | /* TODO: support other GIT_SUBMODULE_IGNORE values */ | |
481 | status = GIT_DELTA_UNMODIFIED; | |
482 | } | |
483 | } | |
e47329b6 | 484 | |
74fa4bfa RB |
485 | /* TODO: check git attributes so we will not have to read the file |
486 | * in if it is marked binary. | |
487 | */ | |
ae9e29fd | 488 | |
66142ae0 | 489 | else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0) |
ae9e29fd | 490 | return -1; |
e47329b6 | 491 | |
66142ae0 | 492 | else if (git_oid_cmp(&oitem->oid, &noid) == 0 && |
95dfb031 | 493 | omode == nmode) |
66142ae0 | 494 | status = GIT_DELTA_UNMODIFIED; |
e47329b6 | 495 | |
74fa4bfa RB |
496 | /* store calculated oid so we don't have to recalc later */ |
497 | use_noid = &noid; | |
e47329b6 RB |
498 | } |
499 | ||
66142ae0 | 500 | return diff_delta__from_two(diff, status, oitem, nitem, use_noid); |
e47329b6 RB |
501 | } |
502 | ||
74fa4bfa | 503 | static int diff_from_iterators( |
e47329b6 | 504 | git_repository *repo, |
74fa4bfa RB |
505 | const git_diff_options *opts, /**< can be NULL for defaults */ |
506 | git_iterator *old, | |
507 | git_iterator *new, | |
508 | git_diff_list **diff_ptr) | |
e47329b6 | 509 | { |
74fa4bfa RB |
510 | const git_index_entry *oitem, *nitem; |
511 | char *ignore_prefix = NULL; | |
512 | git_diff_list *diff = git_diff_list_alloc(repo, opts); | |
ae9e29fd RB |
513 | if (!diff) |
514 | goto fail; | |
e47329b6 | 515 | |
74fa4bfa RB |
516 | diff->old_src = old->type; |
517 | diff->new_src = new->type; | |
e47329b6 | 518 | |
ae9e29fd RB |
519 | if (git_iterator_current(old, &oitem) < 0 || |
520 | git_iterator_current(new, &nitem) < 0) | |
521 | goto fail; | |
65b09b1d | 522 | |
74fa4bfa | 523 | /* run iterators building diffs */ |
ae9e29fd | 524 | while (oitem || nitem) { |
cd33323b | 525 | |
74fa4bfa RB |
526 | /* create DELETED records for old items not matched in new */ |
527 | if (oitem && (!nitem || strcmp(oitem->path, nitem->path) < 0)) { | |
ae9e29fd RB |
528 | if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || |
529 | git_iterator_advance(old, &oitem) < 0) | |
530 | goto fail; | |
65b09b1d | 531 | } |
a2e895be | 532 | |
74fa4bfa RB |
533 | /* create ADDED, TRACKED, or IGNORED records for new items not |
534 | * matched in old (and/or descend into directories as needed) | |
535 | */ | |
ae9e29fd | 536 | else if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) { |
74fa4bfa | 537 | int is_ignored; |
e1bcc191 | 538 | git_delta_t delta_type = GIT_DELTA_ADDED; |
a2e895be | 539 | |
74fa4bfa RB |
540 | /* contained in ignored parent directory, so this can be skipped. */ |
541 | if (ignore_prefix != NULL && | |
542 | git__prefixcmp(nitem->path, ignore_prefix) == 0) | |
65b09b1d | 543 | { |
ae9e29fd RB |
544 | if (git_iterator_advance(new, &nitem) < 0) |
545 | goto fail; | |
74fa4bfa | 546 | continue; |
65b09b1d RB |
547 | } |
548 | ||
74fa4bfa RB |
549 | is_ignored = git_iterator_current_is_ignored(new); |
550 | ||
551 | if (S_ISDIR(nitem->mode)) { | |
4b136a94 RB |
552 | /* recurse into directory if explicitly requested or |
553 | * if there are tracked items inside the directory | |
554 | */ | |
555 | if ((diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) || | |
556 | (oitem && git__prefixcmp(oitem->path, nitem->path) == 0)) | |
557 | { | |
74fa4bfa RB |
558 | if (is_ignored) |
559 | ignore_prefix = nitem->path; | |
ae9e29fd RB |
560 | if (git_iterator_advance_into_directory(new, &nitem) < 0) |
561 | goto fail; | |
74fa4bfa RB |
562 | continue; |
563 | } | |
e1bcc191 | 564 | delta_type = GIT_DELTA_UNTRACKED; |
65b09b1d | 565 | } |
74fa4bfa | 566 | else if (is_ignored) |
e1bcc191 | 567 | delta_type = GIT_DELTA_IGNORED; |
74fa4bfa | 568 | else if (new->type == GIT_ITERATOR_WORKDIR) |
e1bcc191 | 569 | delta_type = GIT_DELTA_UNTRACKED; |
74fa4bfa | 570 | |
ae9e29fd RB |
571 | if (diff_delta__from_one(diff, delta_type, nitem) < 0 || |
572 | git_iterator_advance(new, &nitem) < 0) | |
573 | goto fail; | |
65b09b1d RB |
574 | } |
575 | ||
74fa4bfa RB |
576 | /* otherwise item paths match, so create MODIFIED record |
577 | * (or ADDED and DELETED pair if type changed) | |
a2e895be | 578 | */ |
ae9e29fd RB |
579 | else { |
580 | assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0); | |
a2e895be | 581 | |
ae9e29fd RB |
582 | if (maybe_modified(old, oitem, new, nitem, diff) < 0 || |
583 | git_iterator_advance(old, &oitem) < 0 || | |
584 | git_iterator_advance(new, &nitem) < 0) | |
585 | goto fail; | |
586 | } | |
65b09b1d RB |
587 | } |
588 | ||
74fa4bfa RB |
589 | git_iterator_free(old); |
590 | git_iterator_free(new); | |
74fa4bfa | 591 | *diff_ptr = diff; |
ae9e29fd | 592 | return 0; |
65b09b1d | 593 | |
ae9e29fd RB |
594 | fail: |
595 | git_iterator_free(old); | |
596 | git_iterator_free(new); | |
597 | git_diff_list_free(diff); | |
598 | *diff_ptr = NULL; | |
599 | return -1; | |
65b09b1d RB |
600 | } |
601 | ||
74fa4bfa RB |
602 | |
603 | int git_diff_tree_to_tree( | |
604 | git_repository *repo, | |
605 | const git_diff_options *opts, /**< can be NULL for defaults */ | |
606 | git_tree *old, | |
607 | git_tree *new, | |
608 | git_diff_list **diff) | |
65b09b1d | 609 | { |
74fa4bfa | 610 | git_iterator *a = NULL, *b = NULL; |
3a437590 | 611 | |
74fa4bfa | 612 | assert(repo && old && new && diff); |
3a437590 | 613 | |
ae9e29fd RB |
614 | if (git_iterator_for_tree(repo, old, &a) < 0 || |
615 | git_iterator_for_tree(repo, new, &b) < 0) | |
616 | return -1; | |
3a437590 | 617 | |
74fa4bfa | 618 | return diff_from_iterators(repo, opts, a, b, diff); |
65b09b1d RB |
619 | } |
620 | ||
74fa4bfa RB |
621 | int git_diff_index_to_tree( |
622 | git_repository *repo, | |
623 | const git_diff_options *opts, | |
624 | git_tree *old, | |
625 | git_diff_list **diff) | |
65b09b1d | 626 | { |
74fa4bfa | 627 | git_iterator *a = NULL, *b = NULL; |
3a437590 | 628 | |
74fa4bfa | 629 | assert(repo && old && diff); |
3a437590 | 630 | |
ae9e29fd RB |
631 | if (git_iterator_for_tree(repo, old, &a) < 0 || |
632 | git_iterator_for_index(repo, &b) < 0) | |
633 | return -1; | |
3a437590 | 634 | |
74fa4bfa | 635 | return diff_from_iterators(repo, opts, a, b, diff); |
65b09b1d RB |
636 | } |
637 | ||
74fa4bfa RB |
638 | int git_diff_workdir_to_index( |
639 | git_repository *repo, | |
640 | const git_diff_options *opts, | |
641 | git_diff_list **diff) | |
65b09b1d | 642 | { |
74fa4bfa | 643 | git_iterator *a = NULL, *b = NULL; |
3a437590 | 644 | |
74fa4bfa | 645 | assert(repo && diff); |
3a437590 | 646 | |
ae9e29fd RB |
647 | if (git_iterator_for_index(repo, &a) < 0 || |
648 | git_iterator_for_workdir(repo, &b) < 0) | |
649 | return -1; | |
3a437590 | 650 | |
74fa4bfa | 651 | return diff_from_iterators(repo, opts, a, b, diff); |
65b09b1d RB |
652 | } |
653 | ||
65b09b1d | 654 | |
74fa4bfa RB |
655 | int git_diff_workdir_to_tree( |
656 | git_repository *repo, | |
657 | const git_diff_options *opts, | |
658 | git_tree *old, | |
659 | git_diff_list **diff) | |
65b09b1d | 660 | { |
74fa4bfa | 661 | git_iterator *a = NULL, *b = NULL; |
3a437590 | 662 | |
74fa4bfa | 663 | assert(repo && old && diff); |
3a437590 | 664 | |
ae9e29fd RB |
665 | if (git_iterator_for_tree(repo, old, &a) < 0 || |
666 | git_iterator_for_workdir(repo, &b) < 0) | |
667 | return -1; | |
3a437590 | 668 | |
74fa4bfa | 669 | return diff_from_iterators(repo, opts, a, b, diff); |
65b09b1d RB |
670 | } |
671 | ||
74fa4bfa RB |
672 | int git_diff_merge( |
673 | git_diff_list *onto, | |
674 | const git_diff_list *from) | |
65b09b1d | 675 | { |
ae9e29fd | 676 | int error = 0; |
19fa2bc1 | 677 | git_pool onto_pool; |
74fa4bfa | 678 | git_vector onto_new; |
1db12b00 RB |
679 | git_diff_delta *delta; |
680 | unsigned int i, j; | |
681 | ||
682 | assert(onto && from); | |
683 | ||
684 | if (!from->deltas.length) | |
685 | return 0; | |
3a437590 | 686 | |
19fa2bc1 RB |
687 | if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 || |
688 | git_pool_init(&onto_pool, 1, 0) < 0) | |
ae9e29fd | 689 | return -1; |
3a437590 | 690 | |
1db12b00 RB |
691 | for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { |
692 | git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); | |
693 | const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); | |
694 | int cmp = !f ? -1 : !o ? 1 : strcmp(o->old.path, f->old.path); | |
695 | ||
696 | if (cmp < 0) { | |
19fa2bc1 | 697 | delta = diff_delta__dup(o, &onto_pool); |
1db12b00 RB |
698 | i++; |
699 | } else if (cmp > 0) { | |
19fa2bc1 | 700 | delta = diff_delta__dup(f, &onto_pool); |
1db12b00 RB |
701 | j++; |
702 | } else { | |
19fa2bc1 | 703 | delta = diff_delta__merge_like_cgit(o, f, &onto_pool); |
1db12b00 RB |
704 | i++; |
705 | j++; | |
706 | } | |
707 | ||
a48ea31d RB |
708 | if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) |
709 | break; | |
1db12b00 | 710 | } |
65b09b1d | 711 | |
1db12b00 | 712 | if (!error) { |
74fa4bfa | 713 | git_vector_swap(&onto->deltas, &onto_new); |
19fa2bc1 | 714 | git_pool_swap(&onto->pool, &onto_pool); |
74fa4bfa | 715 | onto->new_src = from->new_src; |
65b09b1d | 716 | } |
cd33323b | 717 | |
74fa4bfa | 718 | git_vector_foreach(&onto_new, i, delta) |
19fa2bc1 | 719 | git__free(delta); |
74fa4bfa | 720 | git_vector_free(&onto_new); |
19fa2bc1 | 721 | git_pool_clear(&onto_pool); |
cd33323b | 722 | |
74fa4bfa | 723 | return error; |
cd33323b | 724 | } |
a48ea31d | 725 |