File Coverage

deps/libgit2/src/libgit2/status.c
Criterion Covered Total %
statement 176 295 59.6
branch 101 208 48.5
condition n/a
subroutine n/a
pod n/a
total 277 503 55.0


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
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              
8             #include "status.h"
9              
10             #include "git2.h"
11             #include "futils.h"
12             #include "hash.h"
13             #include "vector.h"
14             #include "tree.h"
15             #include "git2/status.h"
16             #include "repository.h"
17             #include "ignore.h"
18             #include "index.h"
19             #include "wildmatch.h"
20              
21             #include "git2/diff.h"
22             #include "diff.h"
23             #include "diff_generate.h"
24              
25 30           static unsigned int index_delta2status(const git_diff_delta *head2idx)
26             {
27 30           git_status_t st = GIT_STATUS_CURRENT;
28              
29 30           switch (head2idx->status) {
30             case GIT_DELTA_ADDED:
31             case GIT_DELTA_COPIED:
32 14           st = GIT_STATUS_INDEX_NEW;
33 14           break;
34             case GIT_DELTA_DELETED:
35 1           st = GIT_STATUS_INDEX_DELETED;
36 1           break;
37             case GIT_DELTA_MODIFIED:
38 13           st = GIT_STATUS_INDEX_MODIFIED;
39 13           break;
40             case GIT_DELTA_RENAMED:
41 2           st = GIT_STATUS_INDEX_RENAMED;
42              
43 2 50         if (!git_oid_equal(&head2idx->old_file.id, &head2idx->new_file.id))
44 0           st |= GIT_STATUS_INDEX_MODIFIED;
45 2           break;
46             case GIT_DELTA_TYPECHANGE:
47 0           st = GIT_STATUS_INDEX_TYPECHANGE;
48 0           break;
49             case GIT_DELTA_CONFLICTED:
50 0           st = GIT_STATUS_CONFLICTED;
51 0           break;
52             default:
53 0           break;
54             }
55              
56 30           return st;
57             }
58              
59 46           static unsigned int workdir_delta2status(
60             git_diff *diff, git_diff_delta *idx2wd)
61             {
62 46           git_status_t st = GIT_STATUS_CURRENT;
63              
64 46           switch (idx2wd->status) {
65             case GIT_DELTA_ADDED:
66             case GIT_DELTA_COPIED:
67             case GIT_DELTA_UNTRACKED:
68 27           st = GIT_STATUS_WT_NEW;
69 27           break;
70             case GIT_DELTA_UNREADABLE:
71 0           st = GIT_STATUS_WT_UNREADABLE;
72 0           break;
73             case GIT_DELTA_DELETED:
74 1           st = GIT_STATUS_WT_DELETED;
75 1           break;
76             case GIT_DELTA_MODIFIED:
77 12           st = GIT_STATUS_WT_MODIFIED;
78 12           break;
79             case GIT_DELTA_IGNORED:
80 5           st = GIT_STATUS_IGNORED;
81 5           break;
82             case GIT_DELTA_RENAMED:
83 1           st = GIT_STATUS_WT_RENAMED;
84              
85 1 50         if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) {
86             /* if OIDs don't match, we might need to calculate them now to
87             * discern between RENAMED vs RENAMED+MODIFIED
88             */
89 1 50         if (git_oid_is_zero(&idx2wd->old_file.id) &&
    0          
90 0 0         diff->old_src == GIT_ITERATOR_WORKDIR &&
91 0           !git_diff__oid_for_file(
92             &idx2wd->old_file.id, diff, idx2wd->old_file.path,
93 0           idx2wd->old_file.mode, idx2wd->old_file.size))
94 0           idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
95              
96 1 50         if (git_oid_is_zero(&idx2wd->new_file.id) &&
    50          
97 1 50         diff->new_src == GIT_ITERATOR_WORKDIR &&
98 1           !git_diff__oid_for_file(
99             &idx2wd->new_file.id, diff, idx2wd->new_file.path,
100 1           idx2wd->new_file.mode, idx2wd->new_file.size))
101 1           idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
102              
103 1 50         if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id))
104 0           st |= GIT_STATUS_WT_MODIFIED;
105             }
106 1           break;
107             case GIT_DELTA_TYPECHANGE:
108 0           st = GIT_STATUS_WT_TYPECHANGE;
109 0           break;
110             case GIT_DELTA_CONFLICTED:
111 0           st = GIT_STATUS_CONFLICTED;
112 0           break;
113             default:
114 0           break;
115             }
116              
117 46           return st;
118             }
119              
120 66           static bool status_is_included(
121             git_status_list *status,
122             git_diff_delta *head2idx,
123             git_diff_delta *idx2wd)
124             {
125 66 100         if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES))
126 54           return 1;
127              
128             /* if excluding submodules and this is a submodule everywhere */
129 12 100         if (head2idx) {
130 5 100         if (head2idx->status != GIT_DELTA_ADDED &&
    50          
131 3           head2idx->old_file.mode != GIT_FILEMODE_COMMIT)
132 3           return 1;
133 2 50         if (head2idx->status != GIT_DELTA_DELETED &&
    50          
134 2           head2idx->new_file.mode != GIT_FILEMODE_COMMIT)
135 2           return 1;
136             }
137 7 50         if (idx2wd) {
138 7 50         if (idx2wd->status != GIT_DELTA_ADDED &&
    50          
139 7           idx2wd->old_file.mode != GIT_FILEMODE_COMMIT)
140 7           return 1;
141 0 0         if (idx2wd->status != GIT_DELTA_DELETED &&
    0          
142 0           idx2wd->new_file.mode != GIT_FILEMODE_COMMIT)
143 0           return 1;
144             }
145              
146             /* only get here if every valid mode is GIT_FILEMODE_COMMIT */
147 0           return 0;
148             }
149              
150 66           static git_status_t status_compute(
151             git_status_list *status,
152             git_diff_delta *head2idx,
153             git_diff_delta *idx2wd)
154             {
155 66           git_status_t st = GIT_STATUS_CURRENT;
156              
157 66 100         if (head2idx)
158 30           st |= index_delta2status(head2idx);
159              
160 66 100         if (idx2wd)
161 46           st |= workdir_delta2status(status->idx2wd, idx2wd);
162              
163 66           return st;
164             }
165              
166 66           static int status_collect(
167             git_diff_delta *head2idx,
168             git_diff_delta *idx2wd,
169             void *payload)
170             {
171 66           git_status_list *status = payload;
172             git_status_entry *status_entry;
173              
174 66 50         if (!status_is_included(status, head2idx, idx2wd))
175 0           return 0;
176              
177 66           status_entry = git__malloc(sizeof(git_status_entry));
178 66 50         GIT_ERROR_CHECK_ALLOC(status_entry);
179              
180 66           status_entry->status = status_compute(status, head2idx, idx2wd);
181 66           status_entry->head_to_index = head2idx;
182 66           status_entry->index_to_workdir = idx2wd;
183              
184 66           return git_vector_insert(&status->paired, status_entry);
185             }
186              
187 9           GIT_INLINE(int) status_entry_cmp_base(
188             const void *a,
189             const void *b,
190             int (*strcomp)(const char *a, const char *b))
191             {
192 9           const git_status_entry *entry_a = a;
193 9           const git_status_entry *entry_b = b;
194             const git_diff_delta *delta_a, *delta_b;
195              
196 9 100         delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir :
197             entry_a->head_to_index;
198 9 100         delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir :
199             entry_b->head_to_index;
200              
201 9 50         if (!delta_a && delta_b)
    0          
202 0           return -1;
203 9 50         if (delta_a && !delta_b)
    50          
204 0           return 1;
205 9 50         if (!delta_a && !delta_b)
    0          
206 0           return 0;
207              
208 9           return strcomp(delta_a->new_file.path, delta_b->new_file.path);
209             }
210              
211 0           static int status_entry_icmp(const void *a, const void *b)
212             {
213 0           return status_entry_cmp_base(a, b, git__strcasecmp);
214             }
215              
216 9           static int status_entry_cmp(const void *a, const void *b)
217             {
218 9           return status_entry_cmp_base(a, b, git__strcmp);
219             }
220              
221 63           static git_status_list *git_status_list_alloc(git_index *index)
222             {
223 63           git_status_list *status = NULL;
224             int (*entrycmp)(const void *a, const void *b);
225              
226 63 50         if (!(status = git__calloc(1, sizeof(git_status_list))))
227 0           return NULL;
228              
229 63 50         entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp;
230              
231 63 50         if (git_vector_init(&status->paired, 0, entrycmp) < 0) {
232 0           git__free(status);
233 0           return NULL;
234             }
235              
236 63           return status;
237             }
238              
239 63           static int status_validate_options(const git_status_options *opts)
240             {
241 63 50         if (!opts)
242 0           return 0;
243              
244 63 50         GIT_ERROR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
245              
246 63 50         if (opts->show > GIT_STATUS_SHOW_WORKDIR_ONLY) {
247 0           git_error_set(GIT_ERROR_INVALID, "unknown status 'show' option");
248 0           return -1;
249             }
250              
251 63 50         if ((opts->flags & GIT_STATUS_OPT_NO_REFRESH) != 0 &&
    0          
252 0           (opts->flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) {
253 0           git_error_set(GIT_ERROR_INVALID, "updating index from status "
254             "is not allowed when index refresh is disabled");
255 0           return -1;
256             }
257              
258 63           return 0;
259             }
260              
261 63           int git_status_list_new(
262             git_status_list **out,
263             git_repository *repo,
264             const git_status_options *opts)
265             {
266 63           git_index *index = NULL;
267 63           git_status_list *status = NULL;
268 63           git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
269 63           git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT;
270 63           git_tree *head = NULL;
271 63           git_status_show_t show =
272 63 50         opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
273 63           int error = 0;
274 63 50         unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS;
275              
276 63           *out = NULL;
277              
278 63 50         if (status_validate_options(opts) < 0)
279 0           return -1;
280              
281 63 50         if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 ||
    50          
282             (error = git_repository_index(&index, repo)) < 0)
283 0           return error;
284              
285 63 50         if (opts != NULL && opts->baseline != NULL) {
    50          
286 0           head = opts->baseline;
287             } else {
288             /* if there is no HEAD, that's okay - we'll make an empty iterator */
289 63 100         if ((error = git_repository_head_tree(&head, repo)) < 0) {
290 14 50         if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH)
    50          
291 0           goto done;
292 14           git_error_clear();
293             }
294             }
295              
296             /* refresh index from disk unless prevented */
297 126           if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 &&
298 63           git_index_read_safely(index) < 0)
299 0           git_error_clear();
300              
301 63           status = git_status_list_alloc(index);
302 63 50         GIT_ERROR_CHECK_ALLOC(status);
303              
304 63 50         if (opts) {
305 63           memcpy(&status->opts, opts, sizeof(git_status_options));
306 63           memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
307             }
308              
309 63           diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
310 63           findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED;
311              
312 63 100         if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
313 12           diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
314 63 100         if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
315 4           diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
316 63 50         if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
317 0           diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
318 63 100         if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
319 1           diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
320 63 50         if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
321 0           diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
322 63 100         if ((flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
323 1           diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;
324 63 100         if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
325 7           diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
326 63 50         if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0)
327 0           diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX;
328 63 50         if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE) != 0)
329 0           diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE;
330 63 50         if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED) != 0)
331 0           diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED;
332              
333 63 50         if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0)
334 0           findopt.flags = findopt.flags |
335             GIT_DIFF_FIND_AND_BREAK_REWRITES |
336 0           GIT_DIFF_FIND_RENAMES_FROM_REWRITES |
337             GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY;
338              
339 63 50         if (opts != NULL && opts->rename_threshold != 0)
    50          
340 0           findopt.rename_threshold = opts->rename_threshold;
341              
342 63 100         if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
343 62 50         if ((error = git_diff_tree_to_index(
344             &status->head2idx, repo, head, index, &diffopt)) < 0)
345 0           goto done;
346              
347 62 100         if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 &&
    50          
348 2           (error = git_diff_find_similar(status->head2idx, &findopt)) < 0)
349 0           goto done;
350             }
351              
352 63 100         if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
353 62 50         if ((error = git_diff_index_to_workdir(
354             &status->idx2wd, repo, index, &diffopt)) < 0) {
355 0           goto done;
356             }
357              
358 62 100         if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 &&
    50          
359 3           (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0)
360 0           goto done;
361             }
362              
363 63           error = git_diff__paired_foreach(
364             status->head2idx, status->idx2wd, status_collect, status);
365 63 50         if (error < 0)
366 0           goto done;
367              
368 63 50         if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY)
369 0           git_vector_set_cmp(&status->paired, status_entry_cmp);
370 63 50         if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)
371 0           git_vector_set_cmp(&status->paired, status_entry_icmp);
372              
373 63 100         if ((flags &
374             (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
375             GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR |
376             GIT_STATUS_OPT_SORT_CASE_SENSITIVELY |
377             GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0)
378 5           git_vector_sort(&status->paired);
379              
380             done:
381 63 50         if (error < 0) {
382 0           git_status_list_free(status);
383 0           status = NULL;
384             }
385              
386 63           *out = status;
387              
388 63 50         if (opts == NULL || opts->baseline != head)
    100          
389 49           git_tree_free(head);
390 63           git_index_free(index);
391              
392 63           return error;
393             }
394              
395 56           size_t git_status_list_entrycount(git_status_list *status)
396             {
397 56 50         GIT_ASSERT_ARG_WITH_RETVAL(status, 0);
398              
399 56           return status->paired.length;
400             }
401              
402 54           const git_status_entry *git_status_byindex(git_status_list *status, size_t i)
403             {
404 54 50         GIT_ASSERT_ARG_WITH_RETVAL(status, NULL);
405              
406 54           return git_vector_get(&status->paired, i);
407             }
408              
409 63           void git_status_list_free(git_status_list *status)
410             {
411 63 50         if (status == NULL)
412 0           return;
413              
414 63           git_diff_free(status->head2idx);
415 63           git_diff_free(status->idx2wd);
416              
417 63           git_vector_free_deep(&status->paired);
418              
419 63           git__memzero(status, sizeof(*status));
420 63           git__free(status);
421             }
422              
423 7           int git_status_foreach_ext(
424             git_repository *repo,
425             const git_status_options *opts,
426             git_status_cb cb,
427             void *payload)
428             {
429             git_status_list *status;
430             const git_status_entry *status_entry;
431             size_t i;
432 7           int error = 0;
433              
434 7 50         if ((error = git_status_list_new(&status, repo, opts)) < 0) {
435 0           return error;
436             }
437              
438 7 100         git_vector_foreach(&status->paired, i, status_entry) {
439 12           const char *path = status_entry->head_to_index ?
440 6 100         status_entry->head_to_index->old_file.path :
441 2           status_entry->index_to_workdir->old_file.path;
442              
443 6 50         if ((error = cb(path, status_entry->status, payload)) != 0) {
444 6           git_error_set_after_callback(error);
445 6           break;
446             }
447             }
448              
449 7           git_status_list_free(status);
450              
451 7           return error;
452             }
453              
454 0           int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload)
455             {
456 0           return git_status_foreach_ext(repo, NULL, cb, payload);
457             }
458              
459             struct status_file_info {
460             char *expected;
461             unsigned int count;
462             unsigned int status;
463             int wildmatch_flags;
464             int ambiguous;
465             };
466              
467 0           static int get_one_status(const char *path, unsigned int status, void *data)
468             {
469 0           struct status_file_info *sfi = data;
470             int (*strcomp)(const char *a, const char *b);
471              
472 0           sfi->count++;
473 0           sfi->status = status;
474              
475 0 0         strcomp = (sfi->wildmatch_flags & WM_CASEFOLD) ? git__strcasecmp : git__strcmp;
476              
477 0           if (sfi->count > 1 ||
478 0 0         (strcomp(sfi->expected, path) != 0 &&
479 0           wildmatch(sfi->expected, path, sfi->wildmatch_flags) != 0))
480             {
481 0           sfi->ambiguous = true;
482 0           return GIT_EAMBIGUOUS; /* git_error_set will be done by caller */
483             }
484              
485 0           return 0;
486             }
487              
488 0           int git_status_file(
489             unsigned int *status_flags,
490             git_repository *repo,
491             const char *path)
492             {
493             int error;
494 0           git_status_options opts = GIT_STATUS_OPTIONS_INIT;
495 0           struct status_file_info sfi = {0};
496             git_index *index;
497              
498 0 0         GIT_ASSERT_ARG(status_flags);
499 0 0         GIT_ASSERT_ARG(repo);
500 0 0         GIT_ASSERT_ARG(path);
501              
502 0 0         if ((error = git_repository_index__weakptr(&index, repo)) < 0)
503 0           return error;
504              
505 0 0         if ((sfi.expected = git__strdup(path)) == NULL)
506 0           return -1;
507 0 0         if (index->ignore_case)
508 0           sfi.wildmatch_flags = WM_CASEFOLD;
509              
510 0           opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
511 0           opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
512             GIT_STATUS_OPT_RECURSE_IGNORED_DIRS |
513             GIT_STATUS_OPT_INCLUDE_UNTRACKED |
514             GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
515             GIT_STATUS_OPT_INCLUDE_UNMODIFIED |
516             GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
517 0           opts.pathspec.count = 1;
518 0           opts.pathspec.strings = &sfi.expected;
519              
520 0           error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi);
521              
522 0 0         if (error < 0 && sfi.ambiguous) {
    0          
523 0           git_error_set(GIT_ERROR_INVALID,
524             "ambiguous path '%s' given to git_status_file", sfi.expected);
525 0           error = GIT_EAMBIGUOUS;
526             }
527              
528 0 0         if (!error && !sfi.count) {
    0          
529 0           git_error_set(GIT_ERROR_INVALID,
530             "attempt to get status of nonexistent file '%s'", path);
531 0           error = GIT_ENOTFOUND;
532             }
533              
534 0           *status_flags = sfi.status;
535              
536 0           git__free(sfi.expected);
537              
538 0           return error;
539             }
540              
541 0           int git_status_should_ignore(
542             int *ignored,
543             git_repository *repo,
544             const char *path)
545             {
546 0           return git_ignore_path_is_ignored(ignored, repo, path);
547             }
548              
549 0           int git_status_options_init(git_status_options *opts, unsigned int version)
550             {
551 0 0         GIT_INIT_STRUCTURE_FROM_TEMPLATE(
552             opts, version, git_status_options, GIT_STATUS_OPTIONS_INIT);
553 0           return 0;
554             }
555              
556             #ifndef GIT_DEPRECATE_HARD
557 0           int git_status_init_options(git_status_options *opts, unsigned int version)
558             {
559 0           return git_status_options_init(opts, version);
560             }
561             #endif
562              
563 0           int git_status_list_get_perfdata(
564             git_diff_perfdata *out, const git_status_list *status)
565             {
566 0 0         GIT_ASSERT_ARG(out);
567              
568 0 0         GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
569              
570 0           out->stat_calls = 0;
571 0           out->oid_calculations = 0;
572              
573 0 0         if (status->head2idx) {
574 0           out->stat_calls += status->head2idx->perf.stat_calls;
575 0           out->oid_calculations += status->head2idx->perf.oid_calculations;
576             }
577 0 0         if (status->idx2wd) {
578 0           out->stat_calls += status->idx2wd->perf.stat_calls;
579 0           out->oid_calculations += status->idx2wd->perf.oid_calculations;
580             }
581              
582 0           return 0;
583             }
584