]> git.proxmox.com Git - libgit2.git/blame - src/status.c
More tests of canceling from callbacks
[libgit2.git] / src / status.c
CommitLineData
205166d2 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
205166d2 3 *
bb742ede
VM
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.
205166d2
JP
6 */
7
8#include "common.h"
9#include "git2.h"
10#include "fileops.h"
11#include "hash.h"
3af6b34a
JP
12#include "vector.h"
13#include "tree.h"
1ee2ef87 14#include "status.h"
3af6b34a 15#include "git2/status.h"
56453d34 16#include "repository.h"
df743c7d 17#include "ignore.h"
114f5a6c 18#include "index.h"
205166d2 19
a48ea31d
RB
20#include "git2/diff.h"
21#include "diff.h"
22
a1683f28 23static unsigned int index_delta2status(const git_diff_delta *head2idx)
a48ea31d 24{
a1683f28 25 git_status_t st = GIT_STATUS_CURRENT;
a48ea31d 26
a1683f28 27 switch (head2idx->status) {
a48ea31d
RB
28 case GIT_DELTA_ADDED:
29 case GIT_DELTA_COPIED:
a48ea31d
RB
30 st = GIT_STATUS_INDEX_NEW;
31 break;
32 case GIT_DELTA_DELETED:
33 st = GIT_STATUS_INDEX_DELETED;
34 break;
35 case GIT_DELTA_MODIFIED:
36 st = GIT_STATUS_INDEX_MODIFIED;
37 break;
bc16fd3e
RB
38 case GIT_DELTA_RENAMED:
39 st = GIT_STATUS_INDEX_RENAMED;
a1683f28
RB
40
41 if (!git_oid_equal(&head2idx->old_file.oid, &head2idx->new_file.oid))
42 st |= GIT_STATUS_INDEX_MODIFIED;
bc16fd3e
RB
43 break;
44 case GIT_DELTA_TYPECHANGE:
45 st = GIT_STATUS_INDEX_TYPECHANGE;
46 break;
a48ea31d
RB
47 default:
48 break;
49 }
50
51 return st;
52}
53
a1683f28 54static unsigned int workdir_delta2status(
3ff1d123 55 git_diff *diff, git_diff_delta *idx2wd)
a48ea31d 56{
a1683f28 57 git_status_t st = GIT_STATUS_CURRENT;
a48ea31d 58
a1683f28 59 switch (idx2wd->status) {
a48ea31d 60 case GIT_DELTA_ADDED:
bc16fd3e 61 case GIT_DELTA_COPIED:
a48ea31d
RB
62 case GIT_DELTA_UNTRACKED:
63 st = GIT_STATUS_WT_NEW;
64 break;
65 case GIT_DELTA_DELETED:
66 st = GIT_STATUS_WT_DELETED;
67 break;
68 case GIT_DELTA_MODIFIED:
69 st = GIT_STATUS_WT_MODIFIED;
70 break;
71 case GIT_DELTA_IGNORED:
72 st = GIT_STATUS_IGNORED;
73 break;
dfe8c8df
ET
74 case GIT_DELTA_RENAMED:
75 st = GIT_STATUS_WT_RENAMED;
a1683f28
RB
76
77 if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid)) {
78 /* if OIDs don't match, we might need to calculate them now to
79 * discern between RENAMED vs RENAMED+MODIFED
80 */
81 if (git_oid_iszero(&idx2wd->old_file.oid) &&
82 diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
83 !git_diff__oid_for_file(
84 diff->repo, idx2wd->old_file.path, idx2wd->old_file.mode,
85 idx2wd->old_file.size, &idx2wd->old_file.oid))
86 idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
87
88 if (git_oid_iszero(&idx2wd->new_file.oid) &&
89 diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
90 !git_diff__oid_for_file(
91 diff->repo, idx2wd->new_file.path, idx2wd->new_file.mode,
92 idx2wd->new_file.size, &idx2wd->new_file.oid))
93 idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
94
95 if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid))
96 st |= GIT_STATUS_WT_MODIFIED;
97 }
dfe8c8df 98 break;
bc16fd3e
RB
99 case GIT_DELTA_TYPECHANGE:
100 st = GIT_STATUS_WT_TYPECHANGE;
101 break;
a48ea31d
RB
102 default:
103 break;
104 }
105
106 return st;
107}
108
1ee2ef87 109static bool status_is_included(
3a68d7f0 110 git_status_list *status,
1ee2ef87
ET
111 git_diff_delta *head2idx,
112 git_diff_delta *idx2wd)
55cbd05b 113{
3a68d7f0
RB
114 if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES))
115 return 1;
116
37ee70fa 117 /* if excluding submodules and this is a submodule everywhere */
3a68d7f0
RB
118 if (head2idx) {
119 if (head2idx->status != GIT_DELTA_ADDED &&
120 head2idx->old_file.mode != GIT_FILEMODE_COMMIT)
121 return 1;
122 if (head2idx->status != GIT_DELTA_DELETED &&
123 head2idx->new_file.mode != GIT_FILEMODE_COMMIT)
124 return 1;
125 }
126 if (idx2wd) {
127 if (idx2wd->status != GIT_DELTA_ADDED &&
128 idx2wd->old_file.mode != GIT_FILEMODE_COMMIT)
129 return 1;
130 if (idx2wd->status != GIT_DELTA_DELETED &&
131 idx2wd->new_file.mode != GIT_FILEMODE_COMMIT)
132 return 1;
55cbd05b
RB
133 }
134
3a68d7f0
RB
135 /* only get here if every valid mode is GIT_FILEMODE_COMMIT */
136 return 0;
55cbd05b
RB
137}
138
1ee2ef87 139static git_status_t status_compute(
a1683f28 140 git_status_list *status,
1ee2ef87
ET
141 git_diff_delta *head2idx,
142 git_diff_delta *idx2wd)
143{
a1683f28 144 git_status_t st = GIT_STATUS_CURRENT;
1ee2ef87
ET
145
146 if (head2idx)
a1683f28 147 st |= index_delta2status(head2idx);
1ee2ef87
ET
148
149 if (idx2wd)
a1683f28 150 st |= workdir_delta2status(status->idx2wd, idx2wd);
1ee2ef87 151
a1683f28 152 return st;
1ee2ef87
ET
153}
154
155static int status_collect(
156 git_diff_delta *head2idx,
157 git_diff_delta *idx2wd,
793c4385 158 void *payload)
a48ea31d 159{
25e0b157 160 git_status_list *status = payload;
1ee2ef87 161 git_status_entry *status_entry;
351888cf 162
25e0b157 163 if (!status_is_included(status, head2idx, idx2wd))
1ee2ef87 164 return 0;
351888cf 165
1ee2ef87 166 status_entry = git__malloc(sizeof(git_status_entry));
25e0b157 167 GITERR_CHECK_ALLOC(status_entry);
1ee2ef87 168
25e0b157 169 status_entry->status = status_compute(status, head2idx, idx2wd);
1ee2ef87
ET
170 status_entry->head_to_index = head2idx;
171 status_entry->index_to_workdir = idx2wd;
172
25e0b157 173 return git_vector_insert(&status->paired, status_entry);
1ee2ef87
ET
174}
175
dfe8c8df
ET
176GIT_INLINE(int) status_entry_cmp_base(
177 const void *a,
178 const void *b,
179 int (*strcomp)(const char *a, const char *b))
180{
181 const git_status_entry *entry_a = a;
182 const git_status_entry *entry_b = b;
183 const git_diff_delta *delta_a, *delta_b;
184
185 delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir :
186 entry_a->head_to_index;
187 delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir :
188 entry_b->head_to_index;
189
190 if (!delta_a && delta_b)
191 return -1;
192 if (delta_a && !delta_b)
193 return 1;
194 if (!delta_a && !delta_b)
195 return 0;
196
197 return strcomp(delta_a->new_file.path, delta_b->new_file.path);
198}
199
200static int status_entry_icmp(const void *a, const void *b)
201{
202 return status_entry_cmp_base(a, b, git__strcasecmp);
203}
204
205static int status_entry_cmp(const void *a, const void *b)
206{
207 return status_entry_cmp_base(a, b, git__strcmp);
208}
209
210static git_status_list *git_status_list_alloc(git_index *index)
1ee2ef87 211{
351888cf 212 git_status_list *status = NULL;
dfe8c8df
ET
213 int (*entrycmp)(const void *a, const void *b);
214
351888cf
RB
215 if (!(status = git__calloc(1, sizeof(git_status_list))))
216 return NULL;
217
dfe8c8df 218 entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp;
1ee2ef87 219
351888cf
RB
220 if (git_vector_init(&status->paired, 0, entrycmp) < 0) {
221 git__free(status);
1ee2ef87 222 return NULL;
351888cf 223 }
1ee2ef87 224
351888cf 225 return status;
1ee2ef87
ET
226}
227
228int git_status_list_new(
229 git_status_list **out,
230 git_repository *repo,
231 const git_status_options *opts)
232{
dfe8c8df 233 git_index *index = NULL;
351888cf 234 git_status_list *status = NULL;
2f8d30be 235 git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
e38f0d69 236 git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT;
a48ea31d
RB
237 git_tree *head = NULL;
238 git_status_show_t show =
239 opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1ee2ef87 240 int error = 0;
351888cf 241 unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS;
a48ea31d 242
2a16914c 243 assert(show <= GIT_STATUS_SHOW_WORKDIR_ONLY);
a48ea31d 244
1ee2ef87
ET
245 *out = NULL;
246
c7231c45 247 GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
79cfa20d 248
dfe8c8df
ET
249 if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 ||
250 (error = git_repository_index(&index, repo)) < 0)
1ee2ef87 251 return error;
52032ae5 252
5cec896a 253 /* if there is no HEAD, that's okay - we'll make an empty iterator */
4bf630b6
RB
254 if ((error = git_repository_head_tree(&head, repo)) < 0) {
255 if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH)
256 goto done;
257 giterr_clear();
351888cf 258 }
1ee2ef87 259
4bf630b6
RB
260 /* refresh index from disk unless prevented */
261 if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 &&
8e5a8ef8 262 git_index_read(index, false) < 0)
4bf630b6
RB
263 giterr_clear();
264
351888cf
RB
265 status = git_status_list_alloc(index);
266 GITERR_CHECK_ALLOC(status);
a48ea31d 267
351888cf
RB
268 if (opts) {
269 memcpy(&status->opts, opts, sizeof(git_status_options));
270 memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
271 }
4b136a94 272
0d64bef9 273 diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
e38f0d69 274 findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED;
bc16fd3e 275
351888cf 276 if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
66142ae0 277 diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
351888cf 278 if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
66142ae0 279 diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
351888cf 280 if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
66142ae0 281 diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
351888cf 282 if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
4b136a94 283 diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
351888cf 284 if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
a1773f9d 285 diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
351888cf 286 if ((flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
0c289dd7 287 diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;
351888cf 288 if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
37ee70fa 289 diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
a48ea31d 290
e38f0d69 291 if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0)
17c7fbf6
ET
292 findopt.flags = findopt.flags |
293 GIT_DIFF_FIND_AND_BREAK_REWRITES |
294 GIT_DIFF_FIND_RENAMES_FROM_REWRITES |
295 GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY;
dfe8c8df 296
37ee70fa 297 if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
351888cf 298 if ((error = git_diff_tree_to_index(
4bf630b6 299 &status->head2idx, repo, head, index, &diffopt)) < 0)
351888cf 300 goto done;
1ee2ef87 301
351888cf 302 if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 &&
e38f0d69 303 (error = git_diff_find_similar(status->head2idx, &findopt)) < 0)
351888cf 304 goto done;
37ee70fa 305 }
a48ea31d 306
37ee70fa 307 if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
351888cf 308 if ((error = git_diff_index_to_workdir(
4bf630b6 309 &status->idx2wd, repo, index, &diffopt)) < 0)
351888cf 310 goto done;
a48ea31d 311
351888cf 312 if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 &&
e38f0d69 313 (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0)
351888cf 314 goto done;
1ee2ef87 315 }
55cbd05b 316
25e0b157
RB
317 error = git_diff__paired_foreach(
318 status->head2idx, status->idx2wd, status_collect, status);
319 if (error < 0)
320 goto done;
dfe8c8df 321
22b6b82f
RB
322 if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY)
323 git_vector_set_cmp(&status->paired, status_entry_cmp);
324 if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)
325 git_vector_set_cmp(&status->paired, status_entry_icmp);
326
327 if ((flags &
328 (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
329 GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR |
330 GIT_STATUS_OPT_SORT_CASE_SENSITIVELY |
331 GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0)
351888cf 332 git_vector_sort(&status->paired);
dfe8c8df 333
351888cf
RB
334done:
335 if (error < 0) {
336 git_status_list_free(status);
337 status = NULL;
338 }
a48ea31d 339
351888cf 340 *out = status;
1ee2ef87 341
a48ea31d 342 git_tree_free(head);
dfe8c8df 343 git_index_free(index);
5dca2010 344
1ee2ef87
ET
345 return error;
346}
347
351888cf 348size_t git_status_list_entrycount(git_status_list *status)
1ee2ef87 349{
351888cf 350 assert(status);
1ee2ef87 351
351888cf 352 return status->paired.length;
1ee2ef87
ET
353}
354
351888cf 355const git_status_entry *git_status_byindex(git_status_list *status, size_t i)
1ee2ef87 356{
351888cf 357 assert(status);
1ee2ef87 358
351888cf 359 return git_vector_get(&status->paired, i);
1ee2ef87
ET
360}
361
351888cf 362void git_status_list_free(git_status_list *status)
1ee2ef87 363{
351888cf 364 if (status == NULL)
1ee2ef87
ET
365 return;
366
3ff1d123
RB
367 git_diff_free(status->head2idx);
368 git_diff_free(status->idx2wd);
1ee2ef87 369
fcd324c6 370 git_vector_free_all(&status->paired);
1ee2ef87 371
351888cf
RB
372 git__memzero(status, sizeof(*status));
373 git__free(status);
1ee2ef87
ET
374}
375
376int git_status_foreach_ext(
377 git_repository *repo,
378 const git_status_options *opts,
379 git_status_cb cb,
380 void *payload)
381{
351888cf 382 git_status_list *status;
1ee2ef87
ET
383 const git_status_entry *status_entry;
384 size_t i;
385 int error = 0;
386
351888cf 387 if ((error = git_status_list_new(&status, repo, opts)) < 0)
1ee2ef87
ET
388 return error;
389
351888cf 390 git_vector_foreach(&status->paired, i, status_entry) {
1ee2ef87
ET
391 const char *path = status_entry->head_to_index ?
392 status_entry->head_to_index->old_file.path :
393 status_entry->index_to_workdir->old_file.path;
394
c7b3e1b3 395 if ((error = cb(path, status_entry->status, payload)) != 0) {
26c1cb91 396 giterr_set_after_callback(error);
1ee2ef87 397 break;
c7b3e1b3 398 }
1ee2ef87
ET
399 }
400
351888cf 401 git_status_list_free(status);
f335ecd6 402
1ee2ef87 403 return error;
a48ea31d
RB
404}
405
351888cf 406int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload)
a48ea31d 407{
351888cf 408 return git_status_foreach_ext(repo, NULL, cb, payload);
a48ea31d
RB
409}
410
41a82592 411struct status_file_info {
5dca2010 412 char *expected;
41a82592
RB
413 unsigned int count;
414 unsigned int status;
25423d03 415 int fnm_flags;
5dca2010 416 int ambiguous;
3af6b34a
JP
417};
418
41a82592 419static int get_one_status(const char *path, unsigned int status, void *data)
df743c7d 420{
41a82592 421 struct status_file_info *sfi = data;
25423d03 422 int (*strcomp)(const char *a, const char *b);
df743c7d 423
41a82592
RB
424 sfi->count++;
425 sfi->status = status;
df743c7d 426
25423d03
RB
427 strcomp = (sfi->fnm_flags & FNM_CASEFOLD) ? git__strcasecmp : git__strcmp;
428
b90500f0 429 if (sfi->count > 1 ||
25423d03
RB
430 (strcomp(sfi->expected, path) != 0 &&
431 p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0))
5c8bb98c 432 {
5dca2010 433 sfi->ambiguous = true;
2f28219c 434 return GIT_EAMBIGUOUS; /* giterr_set will be done by caller */
0d0fa7c3 435 }
d8b903da 436
41a82592 437 return 0;
d8b903da 438}
439
0d0fa7c3 440int git_status_file(
41a82592
RB
441 unsigned int *status_flags,
442 git_repository *repo,
443 const char *path)
20361b2f 444{
41a82592 445 int error;
79cfa20d
BS
446 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
447 struct status_file_info sfi = {0};
25423d03 448 git_index *index;
20361b2f 449
56453d34 450 assert(status_flags && repo && path);
451
25423d03
RB
452 if ((error = git_repository_index__weakptr(&index, repo)) < 0)
453 return error;
454
41a82592 455 if ((sfi.expected = git__strdup(path)) == NULL)
722c08af 456 return -1;
25423d03
RB
457 if (index->ignore_case)
458 sfi.fnm_flags = FNM_CASEFOLD;
20361b2f 459
dfe8c8df 460 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
41a82592 461 opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
1f9e41ee 462 GIT_STATUS_OPT_RECURSE_IGNORED_DIRS |
41a82592
RB
463 GIT_STATUS_OPT_INCLUDE_UNTRACKED |
464 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
465 GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
466 opts.pathspec.count = 1;
467 opts.pathspec.strings = &sfi.expected;
df743c7d 468
41a82592 469 error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi);
df743c7d 470
5c8bb98c
RB
471 if (error < 0 && sfi.ambiguous) {
472 giterr_set(GITERR_INVALID,
473 "Ambiguous path '%s' given to git_status_file", sfi.expected);
5dca2010 474 error = GIT_EAMBIGUOUS;
5c8bb98c 475 }
5dca2010 476
41a82592 477 if (!error && !sfi.count) {
1f9e41ee
RB
478 giterr_set(GITERR_INVALID,
479 "Attempt to get status of nonexistent file '%s'", path);
480 error = GIT_ENOTFOUND;
df743c7d
RB
481 }
482
41a82592 483 *status_flags = sfi.status;
20361b2f 484
41a82592 485 git__free(sfi.expected);
0d0fa7c3 486
56453d34 487 return error;
20361b2f 488}
d8b903da 489
dc13f1f7 490int git_status_should_ignore(
41a82592
RB
491 int *ignored,
492 git_repository *repo,
493 const char *path)
cfbc880d 494{
2fb4e9b3 495 return git_ignore_path_is_ignored(ignored, repo, path);
cfbc880d
RB
496}
497