2 * libgit2 "status" example - shows how to use the status APIs
4 * Written by the libgit2 contributors
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.
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/>.
17 #define sleep(a) Sleep(a * 1000)
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.
30 * - Robust error handling
31 * - Colorized or paginated output formatting
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
39 * - A sample status formatter that matches the "short" format
49 #define MAX_PATHSPEC 8
52 git_status_options statusopt
;
54 char *pathspec
[MAX_PATHSPEC
];
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
);
69 int main(int argc
, char *argv
[])
71 git_repository
*repo
= NULL
;
72 git_status_list
*status
;
73 struct opts o
= { GIT_STATUS_OPTIONS_INIT
, "." };
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
;
82 parse_opts(&o
, argc
, argv
);
85 * Try to open the repository at the given path (or at the current
86 * directory if none was given).
88 check_lg2(git_repository_open_ext(&repo
, o
.repodir
, 0, NULL
),
89 "Could not open repository", o
.repodir
);
91 if (git_repository_is_bare(repo
))
92 fatal("Cannot report status on bare repository",
93 git_repository_path(repo
));
97 printf("\033[H\033[2J");
100 * Run status on the repository
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
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.
112 check_lg2(git_status_list_new(&status
, repo
, &o
.statusopt
),
113 "Could not get status", NULL
);
116 show_branch(repo
, o
.format
);
119 int submod_count
= 0;
120 check_lg2(git_submodule_foreach(repo
, print_submod
, &submod_count
),
121 "Cannot iterate submodules", o
.repodir
);
124 if (o
.format
== FORMAT_LONG
)
127 print_short(repo
, status
);
129 git_status_list_free(status
);
136 git_repository_free(repo
);
137 git_threads_shutdown();
143 * If the user asked for the branch, let's show the short name of the
146 static void show_branch(git_repository
*repo
, int format
)
149 const char *branch
= NULL
;
150 git_reference
*head
= NULL
;
152 error
= git_repository_head(&head
, repo
);
154 if (error
== GIT_EUNBORNBRANCH
|| error
== GIT_ENOTFOUND
)
157 branch
= git_reference_shorthand(head
);
159 check_lg2(error
, "failed to get current branch", NULL
);
161 if (format
== FORMAT_LONG
)
162 printf("# On branch %s\n",
163 branch
? branch
: "Not currently on any branch.");
165 printf("## %s\n", branch
? branch
: "HEAD (no branch)");
167 git_reference_free(head
);
171 * This function print out an output similar to git's status command
172 * in long form, including the command-line hints.
174 static void print_long(git_status_list
*status
)
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
;
182 /** Print index changes. */
184 for (i
= 0; i
< maxi
; ++i
) {
185 char *istatus
= NULL
;
187 s
= git_status_byindex(status
, i
);
189 if (s
->status
== GIT_STATUS_CURRENT
)
192 if (s
->status
& GIT_STATUS_WT_DELETED
)
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:";
210 printf("# Changes to be committed:\n");
211 printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
216 old_path
= s
->head_to_index
->old_file
.path
;
217 new_path
= s
->head_to_index
->new_file
.path
;
219 if (old_path
&& new_path
&& strcmp(old_path
, new_path
))
220 printf("#\t%s %s -> %s\n", istatus
, old_path
, new_path
);
222 printf("#\t%s %s\n", istatus
, old_path
? old_path
: new_path
);
226 changes_in_index
= 1;
231 /** Print workdir changes to tracked files. */
233 for (i
= 0; i
< maxi
; ++i
) {
234 char *wstatus
= NULL
;
236 s
= git_status_byindex(status
, i
);
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`.
243 if (s
->status
== GIT_STATUS_CURRENT
|| s
->index_to_workdir
== NULL
)
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:";
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");
267 old_path
= s
->index_to_workdir
->old_file
.path
;
268 new_path
= s
->index_to_workdir
->new_file
.path
;
270 if (old_path
&& new_path
&& strcmp(old_path
, new_path
))
271 printf("#\t%s %s -> %s\n", wstatus
, old_path
, new_path
);
273 printf("#\t%s %s\n", wstatus
, old_path
? old_path
: new_path
);
277 changed_in_workdir
= 1;
281 /** Print untracked files. */
285 for (i
= 0; i
< maxi
; ++i
) {
286 s
= git_status_byindex(status
, i
);
288 if (s
->status
== GIT_STATUS_WT_NEW
) {
291 printf("# Untracked files:\n");
292 printf("# (use \"git add <file>...\" to include in what will be committed)\n");
297 printf("#\t%s\n", s
->index_to_workdir
->old_file
.path
);
303 /** Print ignored files. */
305 for (i
= 0; i
< maxi
; ++i
) {
306 s
= git_status_byindex(status
, i
);
308 if (s
->status
== GIT_STATUS_IGNORED
) {
311 printf("# Ignored files:\n");
312 printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
317 printf("#\t%s\n", s
->index_to_workdir
->old_file
.path
);
321 if (!changes_in_index
&& changed_in_workdir
)
322 printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
326 * This version of the output prefixes each path with two status
327 * columns and shows submodule status information.
329 static void print_short(git_repository
*repo
, git_status_list
*status
)
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
;
336 for (i
= 0; i
< maxi
; ++i
) {
337 s
= git_status_byindex(status
, i
);
339 if (s
->status
== GIT_STATUS_CURRENT
)
343 istatus
= wstatus
= ' ';
346 if (s
->status
& GIT_STATUS_INDEX_NEW
)
348 if (s
->status
& GIT_STATUS_INDEX_MODIFIED
)
350 if (s
->status
& GIT_STATUS_INDEX_DELETED
)
352 if (s
->status
& GIT_STATUS_INDEX_RENAMED
)
354 if (s
->status
& GIT_STATUS_INDEX_TYPECHANGE
)
357 if (s
->status
& GIT_STATUS_WT_NEW
) {
362 if (s
->status
& GIT_STATUS_WT_MODIFIED
)
364 if (s
->status
& GIT_STATUS_WT_DELETED
)
366 if (s
->status
& GIT_STATUS_WT_RENAMED
)
368 if (s
->status
& GIT_STATUS_WT_TYPECHANGE
)
371 if (s
->status
& GIT_STATUS_IGNORED
) {
376 if (istatus
== '?' && wstatus
== '?')
380 * A commit in a tree is how submodules are stored, so
381 * let's go take a look at its status.
383 if (s
->index_to_workdir
&&
384 s
->index_to_workdir
->new_file
.mode
== GIT_FILEMODE_COMMIT
)
386 git_submodule
*sm
= NULL
;
387 unsigned int smstatus
= 0;
389 if (!git_submodule_lookup(
390 &sm
, repo
, s
->index_to_workdir
->new_file
.path
)) {
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)";
404 git_submodule_free(sm
);
408 * Now that we have all the information, format the output.
411 if (s
->head_to_index
) {
412 a
= s
->head_to_index
->old_file
.path
;
413 b
= s
->head_to_index
->new_file
.path
;
415 if (s
->index_to_workdir
) {
417 a
= s
->index_to_workdir
->old_file
.path
;
419 b
= s
->index_to_workdir
->old_file
.path
;
420 c
= s
->index_to_workdir
->new_file
.path
;
423 if (istatus
== 'R') {
425 printf("%c%c %s %s %s%s\n", istatus
, wstatus
, a
, b
, c
, extra
);
427 printf("%c%c %s %s%s\n", istatus
, wstatus
, a
, b
, extra
);
430 printf("%c%c %s %s%s\n", istatus
, wstatus
, a
, c
, extra
);
432 printf("%c%c %s%s\n", istatus
, wstatus
, a
, extra
);
436 for (i
= 0; i
< maxi
; ++i
) {
437 s
= git_status_byindex(status
, i
);
439 if (s
->status
== GIT_STATUS_WT_NEW
)
440 printf("?? %s\n", s
->index_to_workdir
->old_file
.path
);
444 static int print_submod(git_submodule
*sm
, const char *name
, void *payload
)
446 int *count
= payload
;
450 printf("# Submodules\n");
453 printf("# - submodule '%s' at %s\n",
454 git_submodule_name(sm
), git_submodule_path(sm
));
460 * Parse options that git's status command supports.
462 static void parse_opts(struct opts
*o
, int argc
, char *argv
[])
464 struct args_info args
= ARGS_INFO_INIT
;
466 for (args
.pos
= 1; args
.pos
< argc
; ++args
.pos
) {
467 char *a
= argv
[args
.pos
];
470 if (o
->npaths
< MAX_PATHSPEC
)
471 o
->pathspec
[o
->npaths
++] = a
;
473 fatal("Example only supports a limited pathspec", NULL
);
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"))
483 else if (!strcmp(a
, "-z")) {
485 if (o
->format
== FORMAT_DEFAULT
)
486 o
->format
= FORMAT_PORCELAIN
;
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"))
506 else if (match_int_arg(&o
->repeat
, &args
, "--repeat", 0))
508 else if (!strcmp(a
, "--list-submodules"))
511 check_lg2(-1, "Unsupported option", a
);
514 if (o
->format
== FORMAT_DEFAULT
)
515 o
->format
= FORMAT_LONG
;
516 if (o
->format
== FORMAT_LONG
)
519 o
->statusopt
.pathspec
.strings
= o
->pathspec
;
520 o
->statusopt
.pathspec
.count
= o
->npaths
;