2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
18 #define MAX_PATHSPEC 8
21 * This example demonstrates the use of the libgit2 status APIs,
22 * particularly the `git_status_list` object, to roughly simulate the
23 * output of running `git status`. It serves as a simple example of
24 * using those APIs to get basic status information.
27 * - Robust error handling
28 * - 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
38 static void check(int error
, const char *message
, const char *extra
)
40 const git_error
*lg2err
;
41 const char *lg2msg
= "", *lg2spacer
= "";
46 if ((lg2err
= giterr_last()) != NULL
&& lg2err
->message
!= NULL
) {
47 lg2msg
= lg2err
->message
;
52 fprintf(stderr
, "%s '%s' [%d]%s%s\n",
53 message
, extra
, error
, lg2spacer
, lg2msg
);
55 fprintf(stderr
, "%s [%d]%s%s\n",
56 message
, error
, lg2spacer
, lg2msg
);
61 static void fail(const char *message
)
63 check(-1, message
, NULL
);
66 static void show_branch(git_repository
*repo
, int format
)
69 const char *branch
= NULL
;
70 git_reference
*head
= NULL
;
72 error
= git_repository_head(&head
, repo
);
74 if (error
== GIT_EORPHANEDHEAD
|| error
== GIT_ENOTFOUND
)
77 branch
= git_reference_name(head
);
78 if (!strncmp(branch
, "refs/heads/", strlen("refs/heads/")))
79 branch
+= strlen("refs/heads/");
81 check(error
, "failed to get current branch", NULL
);
83 if (format
== FORMAT_LONG
)
84 printf("# On branch %s\n",
85 branch
? branch
: "Not currently on any branch.");
87 printf("## %s\n", branch
? branch
: "HEAD (no branch)");
89 git_reference_free(head
);
92 static void print_long(git_repository
*repo
, git_status_list
*status
)
94 size_t i
, maxi
= git_status_list_entrycount(status
);
95 const git_status_entry
*s
;
96 int header
= 0, changes_in_index
= 0;
97 int changed_in_workdir
= 0, rm_in_workdir
= 0;
98 const char *old_path
, *new_path
;
102 /* print index changes */
104 for (i
= 0; i
< maxi
; ++i
) {
105 char *istatus
= NULL
;
107 s
= git_status_byindex(status
, i
);
109 if (s
->status
== GIT_STATUS_CURRENT
)
112 if (s
->status
& GIT_STATUS_WT_DELETED
)
115 if (s
->status
& GIT_STATUS_INDEX_NEW
)
116 istatus
= "new file: ";
117 if (s
->status
& GIT_STATUS_INDEX_MODIFIED
)
118 istatus
= "modified: ";
119 if (s
->status
& GIT_STATUS_INDEX_DELETED
)
120 istatus
= "deleted: ";
121 if (s
->status
& GIT_STATUS_INDEX_RENAMED
)
122 istatus
= "renamed: ";
123 if (s
->status
& GIT_STATUS_INDEX_TYPECHANGE
)
124 istatus
= "typechange:";
130 printf("# Changes to be committed:\n");
131 printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
136 old_path
= s
->head_to_index
->old_file
.path
;
137 new_path
= s
->head_to_index
->new_file
.path
;
139 if (old_path
&& new_path
&& strcmp(old_path
, new_path
))
140 printf("#\t%s %s -> %s\n", istatus
, old_path
, new_path
);
142 printf("#\t%s %s\n", istatus
, old_path
? old_path
: new_path
);
146 changes_in_index
= 1;
151 /* print workdir changes to tracked files */
153 for (i
= 0; i
< maxi
; ++i
) {
154 char *wstatus
= NULL
;
156 s
= git_status_byindex(status
, i
);
158 if (s
->status
== GIT_STATUS_CURRENT
|| s
->index_to_workdir
== NULL
)
161 if (s
->status
& GIT_STATUS_WT_MODIFIED
)
162 wstatus
= "modified: ";
163 if (s
->status
& GIT_STATUS_WT_DELETED
)
164 wstatus
= "deleted: ";
165 if (s
->status
& GIT_STATUS_WT_RENAMED
)
166 wstatus
= "renamed: ";
167 if (s
->status
& GIT_STATUS_WT_TYPECHANGE
)
168 wstatus
= "typechange:";
174 printf("# Changes not staged for commit:\n");
175 printf("# (use \"git add%s <file>...\" to update what will be committed)\n", rm_in_workdir
? "/rm" : "");
176 printf("# (use \"git checkout -- <file>...\" to discard changes in working directory)\n");
181 old_path
= s
->index_to_workdir
->old_file
.path
;
182 new_path
= s
->index_to_workdir
->new_file
.path
;
184 if (old_path
&& new_path
&& strcmp(old_path
, new_path
))
185 printf("#\t%s %s -> %s\n", wstatus
, old_path
, new_path
);
187 printf("#\t%s %s\n", wstatus
, old_path
? old_path
: new_path
);
191 changed_in_workdir
= 1;
196 /* print untracked files */
200 for (i
= 0; i
< maxi
; ++i
) {
201 s
= git_status_byindex(status
, i
);
203 if (s
->status
== GIT_STATUS_WT_NEW
) {
206 printf("# Untracked files:\n");
207 printf("# (use \"git add <file>...\" to include in what will be committed)\n");
212 printf("#\t%s\n", s
->index_to_workdir
->old_file
.path
);
218 /* print ignored files */
220 for (i
= 0; i
< maxi
; ++i
) {
221 s
= git_status_byindex(status
, i
);
223 if (s
->status
== GIT_STATUS_IGNORED
) {
226 printf("# Ignored files:\n");
227 printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
232 printf("#\t%s\n", s
->index_to_workdir
->old_file
.path
);
236 if (!changes_in_index
&& changed_in_workdir
)
237 printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
240 static void print_short(git_repository
*repo
, git_status_list
*status
)
242 size_t i
, maxi
= git_status_list_entrycount(status
);
243 const git_status_entry
*s
;
244 char istatus
, wstatus
;
245 const char *extra
, *a
, *b
, *c
;
247 for (i
= 0; i
< maxi
; ++i
) {
248 s
= git_status_byindex(status
, i
);
250 if (s
->status
== GIT_STATUS_CURRENT
)
254 istatus
= wstatus
= ' ';
257 if (s
->status
& GIT_STATUS_INDEX_NEW
)
259 if (s
->status
& GIT_STATUS_INDEX_MODIFIED
)
261 if (s
->status
& GIT_STATUS_INDEX_DELETED
)
263 if (s
->status
& GIT_STATUS_INDEX_RENAMED
)
265 if (s
->status
& GIT_STATUS_INDEX_TYPECHANGE
)
268 if (s
->status
& GIT_STATUS_WT_NEW
) {
273 if (s
->status
& GIT_STATUS_WT_MODIFIED
)
275 if (s
->status
& GIT_STATUS_WT_DELETED
)
277 if (s
->status
& GIT_STATUS_WT_RENAMED
)
279 if (s
->status
& GIT_STATUS_WT_TYPECHANGE
)
282 if (s
->status
& GIT_STATUS_IGNORED
) {
287 if (istatus
== '?' && wstatus
== '?')
290 if (s
->index_to_workdir
&&
291 s
->index_to_workdir
->new_file
.mode
== GIT_FILEMODE_COMMIT
)
293 git_submodule
*sm
= NULL
;
294 unsigned int smstatus
= 0;
296 if (!git_submodule_lookup(
297 &sm
, repo
, s
->index_to_workdir
->new_file
.path
) &&
298 !git_submodule_status(&smstatus
, sm
))
300 if (smstatus
& GIT_SUBMODULE_STATUS_WD_MODIFIED
)
301 extra
= " (new commits)";
302 else if (smstatus
& GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED
)
303 extra
= " (modified content)";
304 else if (smstatus
& GIT_SUBMODULE_STATUS_WD_WD_MODIFIED
)
305 extra
= " (modified content)";
306 else if (smstatus
& GIT_SUBMODULE_STATUS_WD_UNTRACKED
)
307 extra
= " (untracked content)";
311 if (s
->head_to_index
) {
312 a
= s
->head_to_index
->old_file
.path
;
313 b
= s
->head_to_index
->new_file
.path
;
315 if (s
->index_to_workdir
) {
317 a
= s
->index_to_workdir
->old_file
.path
;
319 b
= s
->index_to_workdir
->old_file
.path
;
320 c
= s
->index_to_workdir
->new_file
.path
;
323 if (istatus
== 'R') {
325 printf("%c%c %s %s %s%s\n", istatus
, wstatus
, a
, b
, c
, extra
);
327 printf("%c%c %s %s%s\n", istatus
, wstatus
, a
, b
, extra
);
330 printf("%c%c %s %s%s\n", istatus
, wstatus
, a
, c
, extra
);
332 printf("%c%c %s%s\n", istatus
, wstatus
, a
, extra
);
336 for (i
= 0; i
< maxi
; ++i
) {
337 s
= git_status_byindex(status
, i
);
339 if (s
->status
== GIT_STATUS_WT_NEW
)
340 printf("?? %s\n", s
->index_to_workdir
->old_file
.path
);
344 int main(int argc
, char *argv
[])
346 git_repository
*repo
= NULL
;
347 int i
, npaths
= 0, format
= FORMAT_DEFAULT
, zterm
= 0, showbranch
= 0;
348 git_status_options opt
= GIT_STATUS_OPTIONS_INIT
;
349 git_status_list
*status
;
350 char *repodir
= ".", *pathspec
[MAX_PATHSPEC
];
352 opt
.show
= GIT_STATUS_SHOW_INDEX_AND_WORKDIR
;
353 opt
.flags
= GIT_STATUS_OPT_INCLUDE_UNTRACKED
|
354 GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
|
355 GIT_STATUS_OPT_SORT_CASE_SENSITIVELY
;
357 for (i
= 1; i
< argc
; ++i
) {
358 if (argv
[i
][0] != '-') {
359 if (npaths
< MAX_PATHSPEC
)
360 pathspec
[npaths
++] = argv
[i
];
362 fail("Example only supports a limited pathspec");
364 else if (!strcmp(argv
[i
], "-s") || !strcmp(argv
[i
], "--short"))
365 format
= FORMAT_SHORT
;
366 else if (!strcmp(argv
[i
], "--long"))
367 format
= FORMAT_LONG
;
368 else if (!strcmp(argv
[i
], "--porcelain"))
369 format
= FORMAT_PORCELAIN
;
370 else if (!strcmp(argv
[i
], "-b") || !strcmp(argv
[i
], "--branch"))
372 else if (!strcmp(argv
[i
], "-z")) {
374 if (format
== FORMAT_DEFAULT
)
375 format
= FORMAT_PORCELAIN
;
377 else if (!strcmp(argv
[i
], "--ignored"))
378 opt
.flags
|= GIT_STATUS_OPT_INCLUDE_IGNORED
;
379 else if (!strcmp(argv
[i
], "-uno") ||
380 !strcmp(argv
[i
], "--untracked-files=no"))
381 opt
.flags
&= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED
;
382 else if (!strcmp(argv
[i
], "-unormal") ||
383 !strcmp(argv
[i
], "--untracked-files=normal"))
384 opt
.flags
|= GIT_STATUS_OPT_INCLUDE_UNTRACKED
;
385 else if (!strcmp(argv
[i
], "-uall") ||
386 !strcmp(argv
[i
], "--untracked-files=all"))
387 opt
.flags
|= GIT_STATUS_OPT_INCLUDE_UNTRACKED
|
388 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS
;
389 else if (!strcmp(argv
[i
], "--ignore-submodules=all"))
390 opt
.flags
|= GIT_STATUS_OPT_EXCLUDE_SUBMODULES
;
391 else if (!strncmp(argv
[i
], "--git-dir=", strlen("--git-dir=")))
392 repodir
= argv
[i
] + strlen("--git-dir=");
394 check(-1, "Unsupported option", argv
[i
]);
397 if (format
== FORMAT_DEFAULT
)
398 format
= FORMAT_LONG
;
399 if (format
== FORMAT_LONG
)
402 opt
.pathspec
.strings
= pathspec
;
403 opt
.pathspec
.count
= npaths
;
407 * Try to open the repository at the given path (or at the current
408 * directory if none was given).
410 check(git_repository_open_ext(&repo
, repodir
, 0, NULL
),
411 "Could not open repository", repodir
);
413 if (git_repository_is_bare(repo
))
414 fail("Cannot report status on bare repository");
417 * Run status on the repository
419 * Because we want to simluate a full "git status" run and want to
420 * support some command line options, we use `git_status_foreach_ext()`
421 * instead of just the plain status call. This allows (a) iterating
422 * over the index and then the workdir and (b) extra flags that control
423 * which files are included. If you just want simple status (e.g. to
424 * enumerate files that are modified) then you probably don't need the
427 check(git_status_list_new(&status
, repo
, &opt
),
428 "Could not get status", NULL
);
431 show_branch(repo
, format
);
433 if (format
== FORMAT_LONG
)
434 print_long(repo
, status
);
436 print_short(repo
, status
);
438 git_status_list_free(status
);
439 git_repository_free(repo
);