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