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