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