2 * Copyright (C) 2009-2012 the libgit2 contributors
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.
14 #include "git2/status.h"
15 #include "repository.h"
18 #include "git2/diff.h"
20 #include "diff_output.h"
22 static unsigned int index_delta2status(git_delta_t index_status
)
24 unsigned int st
= GIT_STATUS_CURRENT
;
26 switch (index_status
) {
28 case GIT_DELTA_COPIED
:
29 st
= GIT_STATUS_INDEX_NEW
;
31 case GIT_DELTA_DELETED
:
32 st
= GIT_STATUS_INDEX_DELETED
;
34 case GIT_DELTA_MODIFIED
:
35 st
= GIT_STATUS_INDEX_MODIFIED
;
37 case GIT_DELTA_RENAMED
:
38 st
= GIT_STATUS_INDEX_RENAMED
;
40 case GIT_DELTA_TYPECHANGE
:
41 st
= GIT_STATUS_INDEX_TYPECHANGE
;
50 static unsigned int workdir_delta2status(git_delta_t workdir_status
)
52 unsigned int st
= GIT_STATUS_CURRENT
;
54 switch (workdir_status
) {
56 case GIT_DELTA_RENAMED
:
57 case GIT_DELTA_COPIED
:
58 case GIT_DELTA_UNTRACKED
:
59 st
= GIT_STATUS_WT_NEW
;
61 case GIT_DELTA_DELETED
:
62 st
= GIT_STATUS_WT_DELETED
;
64 case GIT_DELTA_MODIFIED
:
65 st
= GIT_STATUS_WT_MODIFIED
;
67 case GIT_DELTA_IGNORED
:
68 st
= GIT_STATUS_IGNORED
;
70 case GIT_DELTA_TYPECHANGE
:
71 st
= GIT_STATUS_WT_TYPECHANGE
;
83 } status_user_callback
;
85 static int status_invoke_cb(
86 git_diff_delta
*i2h
, git_diff_delta
*w2i
, void *payload
)
88 status_user_callback
*usercb
= payload
;
89 const char *path
= NULL
;
90 unsigned int status
= 0;
93 path
= w2i
->old_file
.path
;
94 status
|= workdir_delta2status(w2i
->status
);
97 path
= i2h
->old_file
.path
;
98 status
|= index_delta2status(i2h
->status
);
101 return usercb
->cb(path
, status
, usercb
->payload
);
104 int git_status_foreach_ext(
105 git_repository
*repo
,
106 const git_status_options
*opts
,
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
;
118 assert(show
<= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR
);
120 GITERR_CHECK_VERSION(opts
, GIT_STATUS_OPTIONS_VERSION
, "git_status_options");
122 if (show
!= GIT_STATUS_SHOW_INDEX_ONLY
&&
123 (err
= git_repository__ensure_not_bare(repo
, "status")) < 0)
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
))
131 memcpy(&diffopt
.pathspec
, &opts
->pathspec
, sizeof(diffopt
.pathspec
));
133 diffopt
.flags
= GIT_DIFF_INCLUDE_TYPECHANGE
;
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 */
147 if (show
!= GIT_STATUS_SHOW_WORKDIR_ONLY
&&
148 (err
= git_diff_tree_to_index(&idx2head
, repo
, head
, NULL
, &diffopt
)) < 0)
151 if (show
!= GIT_STATUS_SHOW_INDEX_ONLY
&&
152 (err
= git_diff_index_to_workdir(&wd2idx
, repo
, NULL
, &diffopt
)) < 0)
156 usercb
.payload
= payload
;
158 if (show
== GIT_STATUS_SHOW_INDEX_THEN_WORKDIR
) {
159 if ((err
= git_diff__paired_foreach(
160 idx2head
, NULL
, status_invoke_cb
, &usercb
)) < 0)
163 git_diff_list_free(idx2head
);
167 err
= git_diff__paired_foreach(idx2head
, wd2idx
, status_invoke_cb
, &usercb
);
171 git_diff_list_free(idx2head
);
172 git_diff_list_free(wd2idx
);
174 if (err
== GIT_EUSER
)
180 int git_status_foreach(
181 git_repository
*repo
,
182 git_status_cb callback
,
185 git_status_options opts
= GIT_STATUS_OPTIONS_INIT
;
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
;
192 return git_status_foreach_ext(repo
, &opts
, callback
, payload
);
195 struct status_file_info
{
202 static int get_one_status(const char *path
, unsigned int status
, void *data
)
204 struct status_file_info
*sfi
= data
;
207 sfi
->status
= status
;
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
;
222 unsigned int *status_flags
,
223 git_repository
*repo
,
227 git_status_options opts
= GIT_STATUS_OPTIONS_INIT
;
228 struct status_file_info sfi
= {0};
230 assert(status_flags
&& repo
&& path
);
232 if ((sfi
.expected
= git__strdup(path
)) == NULL
)
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
;
243 error
= git_status_foreach_ext(repo
, &opts
, get_one_status
, &sfi
);
245 if (error
< 0 && sfi
.ambiguous
)
246 error
= GIT_EAMBIGUOUS
;
248 if (!error
&& !sfi
.count
) {
249 git_buf full
= GIT_BUF_INIT
;
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.
255 if (!git_buf_joinpath(&full
, git_repository_workdir(repo
), path
) &&
256 git_path_exists(full
.ptr
))
257 sfi
.status
= GIT_STATUS_IGNORED
;
259 giterr_set(GITERR_INVALID
,
260 "Attempt to get status of nonexistent file '%s'", path
);
261 error
= GIT_ENOTFOUND
;
267 *status_flags
= sfi
.status
;
269 git__free(sfi
.expected
);
274 int git_status_should_ignore(
276 git_repository
*repo
,
279 return git_ignore_path_is_ignored(ignored
, repo
, path
);