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/>.
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.
26 * - Robust error handling
27 * - Colorized or paginated output formatting
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
35 * - A sample status formatter that matches the "short" format
45 #define MAX_PATHSPEC 8
48 git_status_options statusopt
;
50 char *pathspec
[MAX_PATHSPEC
];
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
);
65 int main(int argc
, char *argv
[])
67 git_repository
*repo
= NULL
;
68 git_status_list
*status
;
69 struct opts o
= { GIT_STATUS_OPTIONS_INIT
, "." };
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
;
78 parse_opts(&o
, argc
, argv
);
81 * Try to open the repository at the given path (or at the current
82 * directory if none was given).
84 check_lg2(git_repository_open_ext(&repo
, o
.repodir
, 0, NULL
),
85 "Could not open repository", o
.repodir
);
87 if (git_repository_is_bare(repo
))
88 fatal("Cannot report status on bare repository",
89 git_repository_path(repo
));
93 printf("\033[H\033[2J");
96 * Run status on the repository
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
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.
108 check_lg2(git_status_list_new(&status
, repo
, &o
.statusopt
),
109 "Could not get status", NULL
);
112 show_branch(repo
, o
.format
);
115 int submod_count
= 0;
116 check_lg2(git_submodule_foreach(repo
, print_submod
, &submod_count
),
117 "Cannot iterate submodules", o
.repodir
);
120 if (o
.format
== FORMAT_LONG
)
123 print_short(repo
, status
);
125 git_status_list_free(status
);
132 git_repository_free(repo
);
133 git_threads_shutdown();
139 * If the user asked for the branch, let's show the short name of the
142 static void show_branch(git_repository
*repo
, int format
)
145 const char *branch
= NULL
;
146 git_reference
*head
= NULL
;
148 error
= git_repository_head(&head
, repo
);
150 if (error
== GIT_EUNBORNBRANCH
|| error
== GIT_ENOTFOUND
)
153 branch
= git_reference_shorthand(head
);
155 check_lg2(error
, "failed to get current branch", NULL
);
157 if (format
== FORMAT_LONG
)
158 printf("# On branch %s\n",
159 branch
? branch
: "Not currently on any branch.");
161 printf("## %s\n", branch
? branch
: "HEAD (no branch)");
163 git_reference_free(head
);
167 * This function print out an output similar to git's status command
168 * in long form, including the command-line hints.
170 static void print_long(git_status_list
*status
)
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
;
178 /** Print index changes. */
180 for (i
= 0; i
< maxi
; ++i
) {
181 char *istatus
= NULL
;
183 s
= git_status_byindex(status
, i
);
185 if (s
->status
== GIT_STATUS_CURRENT
)
188 if (s
->status
& GIT_STATUS_WT_DELETED
)
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:";
206 printf("# Changes to be committed:\n");
207 printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
212 old_path
= s
->head_to_index
->old_file
.path
;
213 new_path
= s
->head_to_index
->new_file
.path
;
215 if (old_path
&& new_path
&& strcmp(old_path
, new_path
))
216 printf("#\t%s %s -> %s\n", istatus
, old_path
, new_path
);
218 printf("#\t%s %s\n", istatus
, old_path
? old_path
: new_path
);
222 changes_in_index
= 1;
227 /** Print workdir changes to tracked files. */
229 for (i
= 0; i
< maxi
; ++i
) {
230 char *wstatus
= NULL
;
232 s
= git_status_byindex(status
, i
);
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`.
239 if (s
->status
== GIT_STATUS_CURRENT
|| s
->index_to_workdir
== NULL
)
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:";
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");
263 old_path
= s
->index_to_workdir
->old_file
.path
;
264 new_path
= s
->index_to_workdir
->new_file
.path
;
266 if (old_path
&& new_path
&& strcmp(old_path
, new_path
))
267 printf("#\t%s %s -> %s\n", wstatus
, old_path
, new_path
);
269 printf("#\t%s %s\n", wstatus
, old_path
? old_path
: new_path
);
273 changed_in_workdir
= 1;
277 /** Print untracked files. */
281 for (i
= 0; i
< maxi
; ++i
) {
282 s
= git_status_byindex(status
, i
);
284 if (s
->status
== GIT_STATUS_WT_NEW
) {
287 printf("# Untracked files:\n");
288 printf("# (use \"git add <file>...\" to include in what will be committed)\n");
293 printf("#\t%s\n", s
->index_to_workdir
->old_file
.path
);
299 /** Print ignored files. */
301 for (i
= 0; i
< maxi
; ++i
) {
302 s
= git_status_byindex(status
, i
);
304 if (s
->status
== GIT_STATUS_IGNORED
) {
307 printf("# Ignored files:\n");
308 printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
313 printf("#\t%s\n", s
->index_to_workdir
->old_file
.path
);
317 if (!changes_in_index
&& changed_in_workdir
)
318 printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
322 * This version of the output prefixes each path with two status
323 * columns and shows submodule status information.
325 static void print_short(git_repository
*repo
, git_status_list
*status
)
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
;
332 for (i
= 0; i
< maxi
; ++i
) {
333 s
= git_status_byindex(status
, i
);
335 if (s
->status
== GIT_STATUS_CURRENT
)
339 istatus
= wstatus
= ' ';
342 if (s
->status
& GIT_STATUS_INDEX_NEW
)
344 if (s
->status
& GIT_STATUS_INDEX_MODIFIED
)
346 if (s
->status
& GIT_STATUS_INDEX_DELETED
)
348 if (s
->status
& GIT_STATUS_INDEX_RENAMED
)
350 if (s
->status
& GIT_STATUS_INDEX_TYPECHANGE
)
353 if (s
->status
& GIT_STATUS_WT_NEW
) {
358 if (s
->status
& GIT_STATUS_WT_MODIFIED
)
360 if (s
->status
& GIT_STATUS_WT_DELETED
)
362 if (s
->status
& GIT_STATUS_WT_RENAMED
)
364 if (s
->status
& GIT_STATUS_WT_TYPECHANGE
)
367 if (s
->status
& GIT_STATUS_IGNORED
) {
372 if (istatus
== '?' && wstatus
== '?')
376 * A commit in a tree is how submodules are stored, so
377 * let's go take a look at its status.
379 if (s
->index_to_workdir
&&
380 s
->index_to_workdir
->new_file
.mode
== GIT_FILEMODE_COMMIT
)
382 git_submodule
*sm
= NULL
;
383 unsigned int smstatus
= 0;
385 if (!git_submodule_lookup(
386 &sm
, repo
, s
->index_to_workdir
->new_file
.path
)) {
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)";
400 git_submodule_free(sm
);
404 * Now that we have all the information, format the output.
407 if (s
->head_to_index
) {
408 a
= s
->head_to_index
->old_file
.path
;
409 b
= s
->head_to_index
->new_file
.path
;
411 if (s
->index_to_workdir
) {
413 a
= s
->index_to_workdir
->old_file
.path
;
415 b
= s
->index_to_workdir
->old_file
.path
;
416 c
= s
->index_to_workdir
->new_file
.path
;
419 if (istatus
== 'R') {
421 printf("%c%c %s %s %s%s\n", istatus
, wstatus
, a
, b
, c
, extra
);
423 printf("%c%c %s %s%s\n", istatus
, wstatus
, a
, b
, extra
);
426 printf("%c%c %s %s%s\n", istatus
, wstatus
, a
, c
, extra
);
428 printf("%c%c %s%s\n", istatus
, wstatus
, a
, extra
);
432 for (i
= 0; i
< maxi
; ++i
) {
433 s
= git_status_byindex(status
, i
);
435 if (s
->status
== GIT_STATUS_WT_NEW
)
436 printf("?? %s\n", s
->index_to_workdir
->old_file
.path
);
440 static int print_submod(git_submodule
*sm
, const char *name
, void *payload
)
442 int *count
= payload
;
446 printf("# Submodules\n");
449 printf("# - submodule '%s' at %s\n",
450 git_submodule_name(sm
), git_submodule_path(sm
));
456 * Parse options that git's status command supports.
458 static void parse_opts(struct opts
*o
, int argc
, char *argv
[])
460 struct args_info args
= ARGS_INFO_INIT
;
462 for (args
.pos
= 1; args
.pos
< argc
; ++args
.pos
) {
463 char *a
= argv
[args
.pos
];
466 if (o
->npaths
< MAX_PATHSPEC
)
467 o
->pathspec
[o
->npaths
++] = a
;
469 fatal("Example only supports a limited pathspec", NULL
);
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"))
479 else if (!strcmp(a
, "-z")) {
481 if (o
->format
== FORMAT_DEFAULT
)
482 o
->format
= FORMAT_PORCELAIN
;
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"))
502 else if (match_int_arg(&o
->repeat
, &args
, "--repeat", 0))
504 else if (!strcmp(a
, "--list-submodules"))
507 check_lg2(-1, "Unsupported option", a
);
510 if (o
->format
== FORMAT_DEFAULT
)
511 o
->format
= FORMAT_LONG
;
512 if (o
->format
== FORMAT_LONG
)
515 o
->statusopt
.pathspec
.strings
= o
->pathspec
;
516 o
->statusopt
.pathspec
.count
= o
->npaths
;