]> git.proxmox.com Git - libgit2.git/blob - examples/status.c
Merge pull request #1462 from yorah/fix/libgit2sharp-issue-379
[libgit2.git] / examples / status.c
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 #include <git2.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11
12 enum {
13 FORMAT_DEFAULT = 0,
14 FORMAT_LONG = 1,
15 FORMAT_SHORT = 2,
16 FORMAT_PORCELAIN = 3,
17 };
18 #define MAX_PATHSPEC 8
19
20 /*
21 * This example demonstrates the use of the libgit2 status APIs,
22 * particularly the `git_status_list` object, to roughly simulate the
23 * output of running `git status`. It serves as a simple example of
24 * using those APIs to get basic status information.
25 *
26 * This does not have:
27 * - Robust error handling
28 * - Colorized or paginated output formatting
29 *
30 * This does have:
31 * - Examples of translating command line arguments to the status
32 * options settings to mimic `git status` results.
33 * - A sample status formatter that matches the default "long" format
34 * from `git status`
35 * - A sample status formatter that matches the "short" format
36 */
37
38 static void check(int error, const char *message, const char *extra)
39 {
40 const git_error *lg2err;
41 const char *lg2msg = "", *lg2spacer = "";
42
43 if (!error)
44 return;
45
46 if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) {
47 lg2msg = lg2err->message;
48 lg2spacer = " - ";
49 }
50
51 if (extra)
52 fprintf(stderr, "%s '%s' [%d]%s%s\n",
53 message, extra, error, lg2spacer, lg2msg);
54 else
55 fprintf(stderr, "%s [%d]%s%s\n",
56 message, error, lg2spacer, lg2msg);
57
58 exit(1);
59 }
60
61 static void fail(const char *message)
62 {
63 check(-1, message, NULL);
64 }
65
66 static void show_branch(git_repository *repo, int format)
67 {
68 int error = 0;
69 const char *branch = NULL;
70 git_reference *head = NULL;
71
72 error = git_repository_head(&head, repo);
73
74 if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
75 branch = NULL;
76 else if (!error) {
77 branch = git_reference_name(head);
78 if (!strncmp(branch, "refs/heads/", strlen("refs/heads/")))
79 branch += strlen("refs/heads/");
80 } else
81 check(error, "failed to get current branch", NULL);
82
83 if (format == FORMAT_LONG)
84 printf("# On branch %s\n",
85 branch ? branch : "Not currently on any branch.");
86 else
87 printf("## %s\n", branch ? branch : "HEAD (no branch)");
88
89 git_reference_free(head);
90 }
91
92 static void print_long(git_repository *repo, git_status_list *status)
93 {
94 size_t i, maxi = git_status_list_entrycount(status);
95 const git_status_entry *s;
96 int header = 0, changes_in_index = 0;
97 int changed_in_workdir = 0, rm_in_workdir = 0;
98 const char *old_path, *new_path;
99
100 (void)repo;
101
102 /* print index changes */
103
104 for (i = 0; i < maxi; ++i) {
105 char *istatus = NULL;
106
107 s = git_status_byindex(status, i);
108
109 if (s->status == GIT_STATUS_CURRENT)
110 continue;
111
112 if (s->status & GIT_STATUS_WT_DELETED)
113 rm_in_workdir = 1;
114
115 if (s->status & GIT_STATUS_INDEX_NEW)
116 istatus = "new file: ";
117 if (s->status & GIT_STATUS_INDEX_MODIFIED)
118 istatus = "modified: ";
119 if (s->status & GIT_STATUS_INDEX_DELETED)
120 istatus = "deleted: ";
121 if (s->status & GIT_STATUS_INDEX_RENAMED)
122 istatus = "renamed: ";
123 if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
124 istatus = "typechange:";
125
126 if (istatus == NULL)
127 continue;
128
129 if (!header) {
130 printf("# Changes to be committed:\n");
131 printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
132 printf("#\n");
133 header = 1;
134 }
135
136 old_path = s->head_to_index->old_file.path;
137 new_path = s->head_to_index->new_file.path;
138
139 if (old_path && new_path && strcmp(old_path, new_path))
140 printf("#\t%s %s -> %s\n", istatus, old_path, new_path);
141 else
142 printf("#\t%s %s\n", istatus, old_path ? old_path : new_path);
143 }
144
145 if (header) {
146 changes_in_index = 1;
147 printf("#\n");
148 }
149 header = 0;
150
151 /* print workdir changes to tracked files */
152
153 for (i = 0; i < maxi; ++i) {
154 char *wstatus = NULL;
155
156 s = git_status_byindex(status, i);
157
158 if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL)
159 continue;
160
161 if (s->status & GIT_STATUS_WT_MODIFIED)
162 wstatus = "modified: ";
163 if (s->status & GIT_STATUS_WT_DELETED)
164 wstatus = "deleted: ";
165 if (s->status & GIT_STATUS_WT_RENAMED)
166 wstatus = "renamed: ";
167 if (s->status & GIT_STATUS_WT_TYPECHANGE)
168 wstatus = "typechange:";
169
170 if (wstatus == NULL)
171 continue;
172
173 if (!header) {
174 printf("# Changes not staged for commit:\n");
175 printf("# (use \"git add%s <file>...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : "");
176 printf("# (use \"git checkout -- <file>...\" to discard changes in working directory)\n");
177 printf("#\n");
178 header = 1;
179 }
180
181 old_path = s->index_to_workdir->old_file.path;
182 new_path = s->index_to_workdir->new_file.path;
183
184 if (old_path && new_path && strcmp(old_path, new_path))
185 printf("#\t%s %s -> %s\n", wstatus, old_path, new_path);
186 else
187 printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path);
188 }
189
190 if (header) {
191 changed_in_workdir = 1;
192 printf("#\n");
193 }
194 header = 0;
195
196 /* print untracked files */
197
198 header = 0;
199
200 for (i = 0; i < maxi; ++i) {
201 s = git_status_byindex(status, i);
202
203 if (s->status == GIT_STATUS_WT_NEW) {
204
205 if (!header) {
206 printf("# Untracked files:\n");
207 printf("# (use \"git add <file>...\" to include in what will be committed)\n");
208 printf("#\n");
209 header = 1;
210 }
211
212 printf("#\t%s\n", s->index_to_workdir->old_file.path);
213 }
214 }
215
216 header = 0;
217
218 /* print ignored files */
219
220 for (i = 0; i < maxi; ++i) {
221 s = git_status_byindex(status, i);
222
223 if (s->status == GIT_STATUS_IGNORED) {
224
225 if (!header) {
226 printf("# Ignored files:\n");
227 printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
228 printf("#\n");
229 header = 1;
230 }
231
232 printf("#\t%s\n", s->index_to_workdir->old_file.path);
233 }
234 }
235
236 if (!changes_in_index && changed_in_workdir)
237 printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
238 }
239
240 static void print_short(git_repository *repo, git_status_list *status)
241 {
242 size_t i, maxi = git_status_list_entrycount(status);
243 const git_status_entry *s;
244 char istatus, wstatus;
245 const char *extra, *a, *b, *c;
246
247 for (i = 0; i < maxi; ++i) {
248 s = git_status_byindex(status, i);
249
250 if (s->status == GIT_STATUS_CURRENT)
251 continue;
252
253 a = b = c = NULL;
254 istatus = wstatus = ' ';
255 extra = "";
256
257 if (s->status & GIT_STATUS_INDEX_NEW)
258 istatus = 'A';
259 if (s->status & GIT_STATUS_INDEX_MODIFIED)
260 istatus = 'M';
261 if (s->status & GIT_STATUS_INDEX_DELETED)
262 istatus = 'D';
263 if (s->status & GIT_STATUS_INDEX_RENAMED)
264 istatus = 'R';
265 if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
266 istatus = 'T';
267
268 if (s->status & GIT_STATUS_WT_NEW) {
269 if (istatus == ' ')
270 istatus = '?';
271 wstatus = '?';
272 }
273 if (s->status & GIT_STATUS_WT_MODIFIED)
274 wstatus = 'M';
275 if (s->status & GIT_STATUS_WT_DELETED)
276 wstatus = 'D';
277 if (s->status & GIT_STATUS_WT_RENAMED)
278 wstatus = 'R';
279 if (s->status & GIT_STATUS_WT_TYPECHANGE)
280 wstatus = 'T';
281
282 if (s->status & GIT_STATUS_IGNORED) {
283 istatus = '!';
284 wstatus = '!';
285 }
286
287 if (istatus == '?' && wstatus == '?')
288 continue;
289
290 if (s->index_to_workdir &&
291 s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT)
292 {
293 git_submodule *sm = NULL;
294 unsigned int smstatus = 0;
295
296 if (!git_submodule_lookup(
297 &sm, repo, s->index_to_workdir->new_file.path) &&
298 !git_submodule_status(&smstatus, sm))
299 {
300 if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED)
301 extra = " (new commits)";
302 else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED)
303 extra = " (modified content)";
304 else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED)
305 extra = " (modified content)";
306 else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED)
307 extra = " (untracked content)";
308 }
309 }
310
311 if (s->head_to_index) {
312 a = s->head_to_index->old_file.path;
313 b = s->head_to_index->new_file.path;
314 }
315 if (s->index_to_workdir) {
316 if (!a)
317 a = s->index_to_workdir->old_file.path;
318 if (!b)
319 b = s->index_to_workdir->old_file.path;
320 c = s->index_to_workdir->new_file.path;
321 }
322
323 if (istatus == 'R') {
324 if (wstatus == 'R')
325 printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra);
326 else
327 printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra);
328 } else {
329 if (wstatus == 'R')
330 printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra);
331 else
332 printf("%c%c %s%s\n", istatus, wstatus, a, extra);
333 }
334 }
335
336 for (i = 0; i < maxi; ++i) {
337 s = git_status_byindex(status, i);
338
339 if (s->status == GIT_STATUS_WT_NEW)
340 printf("?? %s\n", s->index_to_workdir->old_file.path);
341 }
342 }
343
344 int main(int argc, char *argv[])
345 {
346 git_repository *repo = NULL;
347 int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0;
348 git_status_options opt = GIT_STATUS_OPTIONS_INIT;
349 git_status_list *status;
350 char *repodir = ".", *pathspec[MAX_PATHSPEC];
351
352 opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
353 opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
354 GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
355 GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
356
357 for (i = 1; i < argc; ++i) {
358 if (argv[i][0] != '-') {
359 if (npaths < MAX_PATHSPEC)
360 pathspec[npaths++] = argv[i];
361 else
362 fail("Example only supports a limited pathspec");
363 }
364 else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short"))
365 format = FORMAT_SHORT;
366 else if (!strcmp(argv[i], "--long"))
367 format = FORMAT_LONG;
368 else if (!strcmp(argv[i], "--porcelain"))
369 format = FORMAT_PORCELAIN;
370 else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch"))
371 showbranch = 1;
372 else if (!strcmp(argv[i], "-z")) {
373 zterm = 1;
374 if (format == FORMAT_DEFAULT)
375 format = FORMAT_PORCELAIN;
376 }
377 else if (!strcmp(argv[i], "--ignored"))
378 opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
379 else if (!strcmp(argv[i], "-uno") ||
380 !strcmp(argv[i], "--untracked-files=no"))
381 opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
382 else if (!strcmp(argv[i], "-unormal") ||
383 !strcmp(argv[i], "--untracked-files=normal"))
384 opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
385 else if (!strcmp(argv[i], "-uall") ||
386 !strcmp(argv[i], "--untracked-files=all"))
387 opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
388 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
389 else if (!strcmp(argv[i], "--ignore-submodules=all"))
390 opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
391 else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir=")))
392 repodir = argv[i] + strlen("--git-dir=");
393 else
394 check(-1, "Unsupported option", argv[i]);
395 }
396
397 if (format == FORMAT_DEFAULT)
398 format = FORMAT_LONG;
399 if (format == FORMAT_LONG)
400 showbranch = 1;
401 if (npaths > 0) {
402 opt.pathspec.strings = pathspec;
403 opt.pathspec.count = npaths;
404 }
405
406 /*
407 * Try to open the repository at the given path (or at the current
408 * directory if none was given).
409 */
410 check(git_repository_open_ext(&repo, repodir, 0, NULL),
411 "Could not open repository", repodir);
412
413 if (git_repository_is_bare(repo))
414 fail("Cannot report status on bare repository");
415
416 /*
417 * Run status on the repository
418 *
419 * Because we want to simluate a full "git status" run and want to
420 * support some command line options, we use `git_status_foreach_ext()`
421 * instead of just the plain status call. This allows (a) iterating
422 * over the index and then the workdir and (b) extra flags that control
423 * which files are included. If you just want simple status (e.g. to
424 * enumerate files that are modified) then you probably don't need the
425 * extended API.
426 */
427 check(git_status_list_new(&status, repo, &opt),
428 "Could not get status", NULL);
429
430 if (showbranch)
431 show_branch(repo, format);
432
433 if (format == FORMAT_LONG)
434 print_long(repo, status);
435 else
436 print_short(repo, status);
437
438 git_status_list_free(status);
439 git_repository_free(repo);
440
441 return 0;
442 }
443