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