]> git.proxmox.com Git - libgit2.git/blob - src/status.c
Oh yeah, bugs from my rebase
[libgit2.git] / src / status.c
1 /*
2 * Copyright (C) 2009-2012 the libgit2 contributors
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 "common.h"
9 #include "git2.h"
10 #include "fileops.h"
11 #include "hash.h"
12 #include "vector.h"
13 #include "tree.h"
14 #include "git2/status.h"
15 #include "repository.h"
16 #include "ignore.h"
17
18 #include "git2/diff.h"
19 #include "diff.h"
20 #include "diff_output.h"
21
22 static unsigned int index_delta2status(git_delta_t index_status)
23 {
24 unsigned int st = GIT_STATUS_CURRENT;
25
26 switch (index_status) {
27 case GIT_DELTA_ADDED:
28 case GIT_DELTA_COPIED:
29 st = GIT_STATUS_INDEX_NEW;
30 break;
31 case GIT_DELTA_DELETED:
32 st = GIT_STATUS_INDEX_DELETED;
33 break;
34 case GIT_DELTA_MODIFIED:
35 st = GIT_STATUS_INDEX_MODIFIED;
36 break;
37 case GIT_DELTA_RENAMED:
38 st = GIT_STATUS_INDEX_RENAMED;
39 break;
40 case GIT_DELTA_TYPECHANGE:
41 st = GIT_STATUS_INDEX_TYPECHANGE;
42 break;
43 default:
44 break;
45 }
46
47 return st;
48 }
49
50 static unsigned int workdir_delta2status(git_delta_t workdir_status)
51 {
52 unsigned int st = GIT_STATUS_CURRENT;
53
54 switch (workdir_status) {
55 case GIT_DELTA_ADDED:
56 case GIT_DELTA_RENAMED:
57 case GIT_DELTA_COPIED:
58 case GIT_DELTA_UNTRACKED:
59 st = GIT_STATUS_WT_NEW;
60 break;
61 case GIT_DELTA_DELETED:
62 st = GIT_STATUS_WT_DELETED;
63 break;
64 case GIT_DELTA_MODIFIED:
65 st = GIT_STATUS_WT_MODIFIED;
66 break;
67 case GIT_DELTA_IGNORED:
68 st = GIT_STATUS_IGNORED;
69 break;
70 case GIT_DELTA_TYPECHANGE:
71 st = GIT_STATUS_WT_TYPECHANGE;
72 break;
73 default:
74 break;
75 }
76
77 return st;
78 }
79
80 typedef struct {
81 git_status_cb cb;
82 void *payload;
83 } status_user_callback;
84
85 static int status_invoke_cb(
86 git_diff_delta *i2h, git_diff_delta *w2i, void *payload)
87 {
88 status_user_callback *usercb = payload;
89 const char *path = NULL;
90 unsigned int status = 0;
91
92 if (w2i) {
93 path = w2i->old_file.path;
94 status |= workdir_delta2status(w2i->status);
95 }
96 if (i2h) {
97 path = i2h->old_file.path;
98 status |= index_delta2status(i2h->status);
99 }
100
101 return usercb->cb(path, status, usercb->payload);
102 }
103
104 int git_status_foreach_ext(
105 git_repository *repo,
106 const git_status_options *opts,
107 git_status_cb cb,
108 void *payload)
109 {
110 int err = 0;
111 git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
112 git_diff_list *idx2head = NULL, *wd2idx = NULL;
113 git_tree *head = NULL;
114 git_status_show_t show =
115 opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
116 status_user_callback usercb;
117
118 assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
119
120 GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
121
122 if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
123 (err = git_repository__ensure_not_bare(repo, "status")) < 0)
124 return err;
125
126 /* if there is no HEAD, that's okay - we'll make an empty iterator */
127 if (((err = git_repository_head_tree(&head, repo)) < 0) &&
128 !(err == GIT_ENOTFOUND || err == GIT_EORPHANEDHEAD))
129 return err;
130
131 memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
132
133 diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
134
135 if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
136 diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
137 if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
138 diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
139 if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
140 diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
141 if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
142 diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
143 if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
144 diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
145 /* TODO: support EXCLUDE_SUBMODULES flag */
146
147 if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
148 (err = git_diff_tree_to_index(&idx2head, repo, head, NULL, &diffopt)) < 0)
149 goto cleanup;
150
151 if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
152 (err = git_diff_index_to_workdir(&wd2idx, repo, NULL, &diffopt)) < 0)
153 goto cleanup;
154
155 usercb.cb = cb;
156 usercb.payload = payload;
157
158 if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
159 if ((err = git_diff__paired_foreach(
160 idx2head, NULL, status_invoke_cb, &usercb)) < 0)
161 goto cleanup;
162
163 git_diff_list_free(idx2head);
164 idx2head = NULL;
165 }
166
167 err = git_diff__paired_foreach(idx2head, wd2idx, status_invoke_cb, &usercb);
168
169 cleanup:
170 git_tree_free(head);
171 git_diff_list_free(idx2head);
172 git_diff_list_free(wd2idx);
173
174 if (err == GIT_EUSER)
175 giterr_clear();
176
177 return err;
178 }
179
180 int git_status_foreach(
181 git_repository *repo,
182 git_status_cb callback,
183 void *payload)
184 {
185 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
186
187 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
188 opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
189 GIT_STATUS_OPT_INCLUDE_UNTRACKED |
190 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
191
192 return git_status_foreach_ext(repo, &opts, callback, payload);
193 }
194
195 struct status_file_info {
196 char *expected;
197 unsigned int count;
198 unsigned int status;
199 int ambiguous;
200 };
201
202 static int get_one_status(const char *path, unsigned int status, void *data)
203 {
204 struct status_file_info *sfi = data;
205
206 sfi->count++;
207 sfi->status = status;
208
209 if (sfi->count > 1 ||
210 (strcmp(sfi->expected, path) != 0 &&
211 p_fnmatch(sfi->expected, path, 0) != 0)) {
212 giterr_set(GITERR_INVALID,
213 "Ambiguous path '%s' given to git_status_file", sfi->expected);
214 sfi->ambiguous = true;
215 return GIT_EAMBIGUOUS;
216 }
217
218 return 0;
219 }
220
221 int git_status_file(
222 unsigned int *status_flags,
223 git_repository *repo,
224 const char *path)
225 {
226 int error;
227 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
228 struct status_file_info sfi = {0};
229
230 assert(status_flags && repo && path);
231
232 if ((sfi.expected = git__strdup(path)) == NULL)
233 return -1;
234
235 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
236 opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
237 GIT_STATUS_OPT_INCLUDE_UNTRACKED |
238 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
239 GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
240 opts.pathspec.count = 1;
241 opts.pathspec.strings = &sfi.expected;
242
243 error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi);
244
245 if (error < 0 && sfi.ambiguous)
246 error = GIT_EAMBIGUOUS;
247
248 if (!error && !sfi.count) {
249 git_buf full = GIT_BUF_INIT;
250
251 /* if the file actually exists and we still did not get a callback
252 * for it, then it must be contained inside an ignored directory, so
253 * mark it as such instead of generating an error.
254 */
255 if (!git_buf_joinpath(&full, git_repository_workdir(repo), path) &&
256 git_path_exists(full.ptr))
257 sfi.status = GIT_STATUS_IGNORED;
258 else {
259 giterr_set(GITERR_INVALID,
260 "Attempt to get status of nonexistent file '%s'", path);
261 error = GIT_ENOTFOUND;
262 }
263
264 git_buf_free(&full);
265 }
266
267 *status_flags = sfi.status;
268
269 git__free(sfi.expected);
270
271 return error;
272 }
273
274 int git_status_should_ignore(
275 int *ignored,
276 git_repository *repo,
277 const char *path)
278 {
279 return git_ignore_path_is_ignored(ignored, repo, path);
280 }
281