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/>.
18 # define sleep(a) Sleep(a * 1000)
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.
31 * - Robust error handling
32 * - Colorized or paginated output formatting
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
40 * - A sample status formatter that matches the "short" format
50 #define MAX_PATHSPEC 8
53 git_status_options statusopt
;
55 char *pathspec
[MAX_PATHSPEC
];
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
);
70 int main(int argc
, char *argv
[])
72 git_repository
*repo
= NULL
;
73 git_status_list
*status
;
74 struct opts o
= { GIT_STATUS_OPTIONS_INIT
, "." };
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
;
83 parse_opts(&o
, argc
, argv
);
86 * Try to open the repository at the given path (or at the current
87 * directory if none was given).
89 check_lg2(git_repository_open_ext(&repo
, o
.repodir
, 0, NULL
),
90 "Could not open repository", o
.repodir
);
92 if (git_repository_is_bare(repo
))
93 fatal("Cannot report status on bare repository",
94 git_repository_path(repo
));
98 printf("\033[H\033[2J");
101 * Run status on the repository
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
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.
113 check_lg2(git_status_list_new(&status
, repo
, &o
.statusopt
),
114 "Could not get status", NULL
);
117 show_branch(repo
, o
.format
);
120 int submod_count
= 0;
121 check_lg2(git_submodule_foreach(repo
, print_submod
, &submod_count
),
122 "Cannot iterate submodules", o
.repodir
);
125 if (o
.format
== FORMAT_LONG
)
128 print_short(repo
, status
);
130 git_status_list_free(status
);
137 git_repository_free(repo
);
138 git_threads_shutdown();
144 * If the user asked for the branch, let's show the short name of the
147 static void show_branch(git_repository
*repo
, int format
)
150 const char *branch
= NULL
;
151 git_reference
*head
= NULL
;
153 error
= git_repository_head(&head
, repo
);
155 if (error
== GIT_EUNBORNBRANCH
|| error
== GIT_ENOTFOUND
)
158 branch
= git_reference_shorthand(head
);
160 check_lg2(error
, "failed to get current branch", NULL
);
162 if (format
== FORMAT_LONG
)
163 printf("# On branch %s\n",
164 branch
? branch
: "Not currently on any branch.");
166 printf("## %s\n", branch
? branch
: "HEAD (no branch)");
168 git_reference_free(head
);
172 * This function print out an output similar to git's status command
173 * in long form, including the command-line hints.
175 static void print_long(git_status_list
*status
)
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
;
183 /** Print index changes. */
185 for (i
= 0; i
< maxi
; ++i
) {
186 char *istatus
= NULL
;
188 s
= git_status_byindex(status
, i
);
190 if (s
->status
== GIT_STATUS_CURRENT
)
193 if (s
->status
& GIT_STATUS_WT_DELETED
)
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:";
211 printf("# Changes to be committed:\n");
212 printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
217 old_path
= s
->head_to_index
->old_file
.path
;
218 new_path
= s
->head_to_index
->new_file
.path
;
220 if (old_path
&& new_path
&& strcmp(old_path
, new_path
))
221 printf("#\t%s %s -> %s\n", istatus
, old_path
, new_path
);
223 printf("#\t%s %s\n", istatus
, old_path
? old_path
: new_path
);
227 changes_in_index
= 1;
232 /** Print workdir changes to tracked files. */
234 for (i
= 0; i
< maxi
; ++i
) {
235 char *wstatus
= NULL
;
237 s
= git_status_byindex(status
, i
);
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`.
244 if (s
->status
== GIT_STATUS_CURRENT
|| s
->index_to_workdir
== NULL
)
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:";
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");
268 old_path
= s
->index_to_workdir
->old_file
.path
;
269 new_path
= s
->index_to_workdir
->new_file
.path
;
271 if (old_path
&& new_path
&& strcmp(old_path
, new_path
))
272 printf("#\t%s %s -> %s\n", wstatus
, old_path
, new_path
);
274 printf("#\t%s %s\n", wstatus
, old_path
? old_path
: new_path
);
278 changed_in_workdir
= 1;
282 /** Print untracked files. */
286 for (i
= 0; i
< maxi
; ++i
) {
287 s
= git_status_byindex(status
, i
);
289 if (s
->status
== GIT_STATUS_WT_NEW
) {
292 printf("# Untracked files:\n");
293 printf("# (use \"git add <file>...\" to include in what will be committed)\n");
298 printf("#\t%s\n", s
->index_to_workdir
->old_file
.path
);
304 /** Print ignored files. */
306 for (i
= 0; i
< maxi
; ++i
) {
307 s
= git_status_byindex(status
, i
);
309 if (s
->status
== GIT_STATUS_IGNORED
) {
312 printf("# Ignored files:\n");
313 printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
318 printf("#\t%s\n", s
->index_to_workdir
->old_file
.path
);
322 if (!changes_in_index
&& changed_in_workdir
)
323 printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
327 * This version of the output prefixes each path with two status
328 * columns and shows submodule status information.
330 static void print_short(git_repository
*repo
, git_status_list
*status
)
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
;
337 for (i
= 0; i
< maxi
; ++i
) {
338 s
= git_status_byindex(status
, i
);
340 if (s
->status
== GIT_STATUS_CURRENT
)
344 istatus
= wstatus
= ' ';
347 if (s
->status
& GIT_STATUS_INDEX_NEW
)
349 if (s
->status
& GIT_STATUS_INDEX_MODIFIED
)
351 if (s
->status
& GIT_STATUS_INDEX_DELETED
)
353 if (s
->status
& GIT_STATUS_INDEX_RENAMED
)
355 if (s
->status
& GIT_STATUS_INDEX_TYPECHANGE
)
358 if (s
->status
& GIT_STATUS_WT_NEW
) {
363 if (s
->status
& GIT_STATUS_WT_MODIFIED
)
365 if (s
->status
& GIT_STATUS_WT_DELETED
)
367 if (s
->status
& GIT_STATUS_WT_RENAMED
)
369 if (s
->status
& GIT_STATUS_WT_TYPECHANGE
)
372 if (s
->status
& GIT_STATUS_IGNORED
) {
377 if (istatus
== '?' && wstatus
== '?')
381 * A commit in a tree is how submodules are stored, so
382 * let's go take a look at its status.
384 if (s
->index_to_workdir
&&
385 s
->index_to_workdir
->new_file
.mode
== GIT_FILEMODE_COMMIT
)
387 git_submodule
*sm
= NULL
;
388 unsigned int smstatus
= 0;
390 if (!git_submodule_lookup(
391 &sm
, repo
, s
->index_to_workdir
->new_file
.path
)) {
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)";
405 git_submodule_free(sm
);
409 * Now that we have all the information, format the output.
412 if (s
->head_to_index
) {
413 a
= s
->head_to_index
->old_file
.path
;
414 b
= s
->head_to_index
->new_file
.path
;
416 if (s
->index_to_workdir
) {
418 a
= s
->index_to_workdir
->old_file
.path
;
420 b
= s
->index_to_workdir
->old_file
.path
;
421 c
= s
->index_to_workdir
->new_file
.path
;
424 if (istatus
== 'R') {
426 printf("%c%c %s %s %s%s\n", istatus
, wstatus
, a
, b
, c
, extra
);
428 printf("%c%c %s %s%s\n", istatus
, wstatus
, a
, b
, extra
);
431 printf("%c%c %s %s%s\n", istatus
, wstatus
, a
, c
, extra
);
433 printf("%c%c %s%s\n", istatus
, wstatus
, a
, extra
);
437 for (i
= 0; i
< maxi
; ++i
) {
438 s
= git_status_byindex(status
, i
);
440 if (s
->status
== GIT_STATUS_WT_NEW
)
441 printf("?? %s\n", s
->index_to_workdir
->old_file
.path
);
445 static int print_submod(git_submodule
*sm
, const char *name
, void *payload
)
447 int *count
= payload
;
451 printf("# Submodules\n");
454 printf("# - submodule '%s' at %s\n",
455 git_submodule_name(sm
), git_submodule_path(sm
));
461 * Parse options that git's status command supports.
463 static void parse_opts(struct opts
*o
, int argc
, char *argv
[])
465 struct args_info args
= ARGS_INFO_INIT
;
467 for (args
.pos
= 1; args
.pos
< argc
; ++args
.pos
) {
468 char *a
= argv
[args
.pos
];
471 if (o
->npaths
< MAX_PATHSPEC
)
472 o
->pathspec
[o
->npaths
++] = a
;
474 fatal("Example only supports a limited pathspec", NULL
);
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"))
484 else if (!strcmp(a
, "-z")) {
486 if (o
->format
== FORMAT_DEFAULT
)
487 o
->format
= FORMAT_PORCELAIN
;
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"))
507 else if (match_int_arg(&o
->repeat
, &args
, "--repeat", 0))
509 else if (!strcmp(a
, "--list-submodules"))
512 check_lg2(-1, "Unsupported option", a
);
515 if (o
->format
== FORMAT_DEFAULT
)
516 o
->format
= FORMAT_LONG
;
517 if (o
->format
== FORMAT_LONG
)
520 o
->statusopt
.pathspec
.strings
= o
->pathspec
;
521 o
->statusopt
.pathspec
.count
= o
->npaths
;