]> git.proxmox.com Git - libgit2.git/blob - examples/status.c
Merge remote-tracking branch 'upstream/master' into cmn/describe
[libgit2.git] / examples / status.c
1 /*
2 * libgit2 "status" example - shows how to use the status APIs
3 *
4 * Written by the libgit2 contributors
5 *
6 * To the extent possible under law, the author(s) have dedicated all copyright
7 * and related and neighboring rights to this software to the public domain
8 * worldwide. This software is distributed without any warranty.
9 *
10 * You should have received a copy of the CC0 Public Domain Dedication along
11 * with this software. If not, see
12 * <http://creativecommons.org/publicdomain/zero/1.0/>.
13 */
14
15 #include "common.h"
16 #ifdef _WIN32
17 # include <Windows.h>
18 # define sleep(a) Sleep(a * 1000)
19 #else
20 # include <unistd.h>
21 #endif
22
23 /**
24 * This example demonstrates the use of the libgit2 status APIs,
25 * particularly the `git_status_list` object, to roughly simulate the
26 * output of running `git status`. It serves as a simple example of
27 * using those APIs to get basic status information.
28 *
29 * This does not have:
30 *
31 * - Robust error handling
32 * - Colorized or paginated output formatting
33 *
34 * This does have:
35 *
36 * - Examples of translating command line arguments to the status
37 * options settings to mimic `git status` results.
38 * - A sample status formatter that matches the default "long" format
39 * from `git status`
40 * - A sample status formatter that matches the "short" format
41 */
42
43 enum {
44 FORMAT_DEFAULT = 0,
45 FORMAT_LONG = 1,
46 FORMAT_SHORT = 2,
47 FORMAT_PORCELAIN = 3,
48 };
49
50 #define MAX_PATHSPEC 8
51
52 struct opts {
53 git_status_options statusopt;
54 char *repodir;
55 char *pathspec[MAX_PATHSPEC];
56 int npaths;
57 int format;
58 int zterm;
59 int showbranch;
60 int showsubmod;
61 int repeat;
62 };
63
64 static void parse_opts(struct opts *o, int argc, char *argv[]);
65 static void show_branch(git_repository *repo, int format);
66 static void print_long(git_status_list *status);
67 static void print_short(git_repository *repo, git_status_list *status);
68 static int print_submod(git_submodule *sm, const char *name, void *payload);
69
70 int main(int argc, char *argv[])
71 {
72 git_repository *repo = NULL;
73 git_status_list *status;
74 struct opts o = { GIT_STATUS_OPTIONS_INIT, "." };
75
76 git_threads_init();
77
78 o.statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
79 o.statusopt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
80 GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
81 GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
82
83 parse_opts(&o, argc, argv);
84
85 /**
86 * Try to open the repository at the given path (or at the current
87 * directory if none was given).
88 */
89 check_lg2(git_repository_open_ext(&repo, o.repodir, 0, NULL),
90 "Could not open repository", o.repodir);
91
92 if (git_repository_is_bare(repo))
93 fatal("Cannot report status on bare repository",
94 git_repository_path(repo));
95
96 show_status:
97 if (o.repeat)
98 printf("\033[H\033[2J");
99
100 /**
101 * Run status on the repository
102 *
103 * We use `git_status_list_new()` to generate a list of status
104 * information which lets us iterate over it at our
105 * convenience and extract the data we want to show out of
106 * each entry.
107 *
108 * You can use `git_status_foreach()` or
109 * `git_status_foreach_ext()` if you'd prefer to execute a
110 * callback for each entry. The latter gives you more control
111 * about what results are presented.
112 */
113 check_lg2(git_status_list_new(&status, repo, &o.statusopt),
114 "Could not get status", NULL);
115
116 if (o.showbranch)
117 show_branch(repo, o.format);
118
119 if (o.showsubmod) {
120 int submod_count = 0;
121 check_lg2(git_submodule_foreach(repo, print_submod, &submod_count),
122 "Cannot iterate submodules", o.repodir);
123 }
124
125 if (o.format == FORMAT_LONG)
126 print_long(status);
127 else
128 print_short(repo, status);
129
130 git_status_list_free(status);
131
132 if (o.repeat) {
133 sleep(o.repeat);
134 goto show_status;
135 }
136
137 git_repository_free(repo);
138 git_threads_shutdown();
139
140 return 0;
141 }
142
143 /**
144 * If the user asked for the branch, let's show the short name of the
145 * branch.
146 */
147 static void show_branch(git_repository *repo, int format)
148 {
149 int error = 0;
150 const char *branch = NULL;
151 git_reference *head = NULL;
152
153 error = git_repository_head(&head, repo);
154
155 if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
156 branch = NULL;
157 else if (!error) {
158 branch = git_reference_shorthand(head);
159 } else
160 check_lg2(error, "failed to get current branch", NULL);
161
162 if (format == FORMAT_LONG)
163 printf("# On branch %s\n",
164 branch ? branch : "Not currently on any branch.");
165 else
166 printf("## %s\n", branch ? branch : "HEAD (no branch)");
167
168 git_reference_free(head);
169 }
170
171 /**
172 * This function print out an output similar to git's status command
173 * in long form, including the command-line hints.
174 */
175 static void print_long(git_status_list *status)
176 {
177 size_t i, maxi = git_status_list_entrycount(status);
178 const git_status_entry *s;
179 int header = 0, changes_in_index = 0;
180 int changed_in_workdir = 0, rm_in_workdir = 0;
181 const char *old_path, *new_path;
182
183 /** Print index changes. */
184
185 for (i = 0; i < maxi; ++i) {
186 char *istatus = NULL;
187
188 s = git_status_byindex(status, i);
189
190 if (s->status == GIT_STATUS_CURRENT)
191 continue;
192
193 if (s->status & GIT_STATUS_WT_DELETED)
194 rm_in_workdir = 1;
195
196 if (s->status & GIT_STATUS_INDEX_NEW)
197 istatus = "new file: ";
198 if (s->status & GIT_STATUS_INDEX_MODIFIED)
199 istatus = "modified: ";
200 if (s->status & GIT_STATUS_INDEX_DELETED)
201 istatus = "deleted: ";
202 if (s->status & GIT_STATUS_INDEX_RENAMED)
203 istatus = "renamed: ";
204 if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
205 istatus = "typechange:";
206
207 if (istatus == NULL)
208 continue;
209
210 if (!header) {
211 printf("# Changes to be committed:\n");
212 printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
213 printf("#\n");
214 header = 1;
215 }
216
217 old_path = s->head_to_index->old_file.path;
218 new_path = s->head_to_index->new_file.path;
219
220 if (old_path && new_path && strcmp(old_path, new_path))
221 printf("#\t%s %s -> %s\n", istatus, old_path, new_path);
222 else
223 printf("#\t%s %s\n", istatus, old_path ? old_path : new_path);
224 }
225
226 if (header) {
227 changes_in_index = 1;
228 printf("#\n");
229 }
230 header = 0;
231
232 /** Print workdir changes to tracked files. */
233
234 for (i = 0; i < maxi; ++i) {
235 char *wstatus = NULL;
236
237 s = git_status_byindex(status, i);
238
239 /**
240 * With `GIT_STATUS_OPT_INCLUDE_UNMODIFIED` (not used in this example)
241 * `index_to_workdir` may not be `NULL` even if there are
242 * no differences, in which case it will be a `GIT_DELTA_UNMODIFIED`.
243 */
244 if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL)
245 continue;
246
247 /** Print out the output since we know the file has some changes */
248 if (s->status & GIT_STATUS_WT_MODIFIED)
249 wstatus = "modified: ";
250 if (s->status & GIT_STATUS_WT_DELETED)
251 wstatus = "deleted: ";
252 if (s->status & GIT_STATUS_WT_RENAMED)
253 wstatus = "renamed: ";
254 if (s->status & GIT_STATUS_WT_TYPECHANGE)
255 wstatus = "typechange:";
256
257 if (wstatus == NULL)
258 continue;
259
260 if (!header) {
261 printf("# Changes not staged for commit:\n");
262 printf("# (use \"git add%s <file>...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : "");
263 printf("# (use \"git checkout -- <file>...\" to discard changes in working directory)\n");
264 printf("#\n");
265 header = 1;
266 }
267
268 old_path = s->index_to_workdir->old_file.path;
269 new_path = s->index_to_workdir->new_file.path;
270
271 if (old_path && new_path && strcmp(old_path, new_path))
272 printf("#\t%s %s -> %s\n", wstatus, old_path, new_path);
273 else
274 printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path);
275 }
276
277 if (header) {
278 changed_in_workdir = 1;
279 printf("#\n");
280 }
281
282 /** Print untracked files. */
283
284 header = 0;
285
286 for (i = 0; i < maxi; ++i) {
287 s = git_status_byindex(status, i);
288
289 if (s->status == GIT_STATUS_WT_NEW) {
290
291 if (!header) {
292 printf("# Untracked files:\n");
293 printf("# (use \"git add <file>...\" to include in what will be committed)\n");
294 printf("#\n");
295 header = 1;
296 }
297
298 printf("#\t%s\n", s->index_to_workdir->old_file.path);
299 }
300 }
301
302 header = 0;
303
304 /** Print ignored files. */
305
306 for (i = 0; i < maxi; ++i) {
307 s = git_status_byindex(status, i);
308
309 if (s->status == GIT_STATUS_IGNORED) {
310
311 if (!header) {
312 printf("# Ignored files:\n");
313 printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
314 printf("#\n");
315 header = 1;
316 }
317
318 printf("#\t%s\n", s->index_to_workdir->old_file.path);
319 }
320 }
321
322 if (!changes_in_index && changed_in_workdir)
323 printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
324 }
325
326 /**
327 * This version of the output prefixes each path with two status
328 * columns and shows submodule status information.
329 */
330 static void print_short(git_repository *repo, git_status_list *status)
331 {
332 size_t i, maxi = git_status_list_entrycount(status);
333 const git_status_entry *s;
334 char istatus, wstatus;
335 const char *extra, *a, *b, *c;
336
337 for (i = 0; i < maxi; ++i) {
338 s = git_status_byindex(status, i);
339
340 if (s->status == GIT_STATUS_CURRENT)
341 continue;
342
343 a = b = c = NULL;
344 istatus = wstatus = ' ';
345 extra = "";
346
347 if (s->status & GIT_STATUS_INDEX_NEW)
348 istatus = 'A';
349 if (s->status & GIT_STATUS_INDEX_MODIFIED)
350 istatus = 'M';
351 if (s->status & GIT_STATUS_INDEX_DELETED)
352 istatus = 'D';
353 if (s->status & GIT_STATUS_INDEX_RENAMED)
354 istatus = 'R';
355 if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
356 istatus = 'T';
357
358 if (s->status & GIT_STATUS_WT_NEW) {
359 if (istatus == ' ')
360 istatus = '?';
361 wstatus = '?';
362 }
363 if (s->status & GIT_STATUS_WT_MODIFIED)
364 wstatus = 'M';
365 if (s->status & GIT_STATUS_WT_DELETED)
366 wstatus = 'D';
367 if (s->status & GIT_STATUS_WT_RENAMED)
368 wstatus = 'R';
369 if (s->status & GIT_STATUS_WT_TYPECHANGE)
370 wstatus = 'T';
371
372 if (s->status & GIT_STATUS_IGNORED) {
373 istatus = '!';
374 wstatus = '!';
375 }
376
377 if (istatus == '?' && wstatus == '?')
378 continue;
379
380 /**
381 * A commit in a tree is how submodules are stored, so
382 * let's go take a look at its status.
383 */
384 if (s->index_to_workdir &&
385 s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT)
386 {
387 git_submodule *sm = NULL;
388 unsigned int smstatus = 0;
389
390 if (!git_submodule_lookup(
391 &sm, repo, s->index_to_workdir->new_file.path)) {
392
393 if (!git_submodule_status(&smstatus, sm)) {
394 if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED)
395 extra = " (new commits)";
396 else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED)
397 extra = " (modified content)";
398 else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED)
399 extra = " (modified content)";
400 else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED)
401 extra = " (untracked content)";
402 }
403 }
404
405 git_submodule_free(sm);
406 }
407
408 /**
409 * Now that we have all the information, format the output.
410 */
411
412 if (s->head_to_index) {
413 a = s->head_to_index->old_file.path;
414 b = s->head_to_index->new_file.path;
415 }
416 if (s->index_to_workdir) {
417 if (!a)
418 a = s->index_to_workdir->old_file.path;
419 if (!b)
420 b = s->index_to_workdir->old_file.path;
421 c = s->index_to_workdir->new_file.path;
422 }
423
424 if (istatus == 'R') {
425 if (wstatus == 'R')
426 printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra);
427 else
428 printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra);
429 } else {
430 if (wstatus == 'R')
431 printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra);
432 else
433 printf("%c%c %s%s\n", istatus, wstatus, a, extra);
434 }
435 }
436
437 for (i = 0; i < maxi; ++i) {
438 s = git_status_byindex(status, i);
439
440 if (s->status == GIT_STATUS_WT_NEW)
441 printf("?? %s\n", s->index_to_workdir->old_file.path);
442 }
443 }
444
445 static int print_submod(git_submodule *sm, const char *name, void *payload)
446 {
447 int *count = payload;
448 (void)name;
449
450 if (*count == 0)
451 printf("# Submodules\n");
452 (*count)++;
453
454 printf("# - submodule '%s' at %s\n",
455 git_submodule_name(sm), git_submodule_path(sm));
456
457 return 0;
458 }
459
460 /**
461 * Parse options that git's status command supports.
462 */
463 static void parse_opts(struct opts *o, int argc, char *argv[])
464 {
465 struct args_info args = ARGS_INFO_INIT;
466
467 for (args.pos = 1; args.pos < argc; ++args.pos) {
468 char *a = argv[args.pos];
469
470 if (a[0] != '-') {
471 if (o->npaths < MAX_PATHSPEC)
472 o->pathspec[o->npaths++] = a;
473 else
474 fatal("Example only supports a limited pathspec", NULL);
475 }
476 else if (!strcmp(a, "-s") || !strcmp(a, "--short"))
477 o->format = FORMAT_SHORT;
478 else if (!strcmp(a, "--long"))
479 o->format = FORMAT_LONG;
480 else if (!strcmp(a, "--porcelain"))
481 o->format = FORMAT_PORCELAIN;
482 else if (!strcmp(a, "-b") || !strcmp(a, "--branch"))
483 o->showbranch = 1;
484 else if (!strcmp(a, "-z")) {
485 o->zterm = 1;
486 if (o->format == FORMAT_DEFAULT)
487 o->format = FORMAT_PORCELAIN;
488 }
489 else if (!strcmp(a, "--ignored"))
490 o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
491 else if (!strcmp(a, "-uno") ||
492 !strcmp(a, "--untracked-files=no"))
493 o->statusopt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
494 else if (!strcmp(a, "-unormal") ||
495 !strcmp(a, "--untracked-files=normal"))
496 o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
497 else if (!strcmp(a, "-uall") ||
498 !strcmp(a, "--untracked-files=all"))
499 o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
500 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
501 else if (!strcmp(a, "--ignore-submodules=all"))
502 o->statusopt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
503 else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
504 o->repodir = a + strlen("--git-dir=");
505 else if (!strcmp(a, "--repeat"))
506 o->repeat = 10;
507 else if (match_int_arg(&o->repeat, &args, "--repeat", 0))
508 /* okay */;
509 else if (!strcmp(a, "--list-submodules"))
510 o->showsubmod = 1;
511 else
512 check_lg2(-1, "Unsupported option", a);
513 }
514
515 if (o->format == FORMAT_DEFAULT)
516 o->format = FORMAT_LONG;
517 if (o->format == FORMAT_LONG)
518 o->showbranch = 1;
519 if (o->npaths > 0) {
520 o->statusopt.pathspec.strings = o->pathspec;
521 o->statusopt.pathspec.count = o->npaths;
522 }
523 }