2 * libgit2 "log" example - shows how to walk history and get commit info
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 * This example demonstrates the libgit2 rev walker APIs to roughly
19 * simulate the output of `git log` and a few of command line arguments.
20 * `git log` has many many options and this only shows a few of them.
24 * - Robust error handling
25 * - Colorized or paginated output formatting
26 * - Most of the `git log` options
30 * - Examples of translating command line arguments to equivalent libgit2
31 * revwalker configuration calls
32 * - Simplified options to apply pathspec limits and to show basic diffs
35 /** log_state represents walker being configured while handling options */
45 /** utility functions that are called to configure the walker */
46 static void set_sorting(struct log_state
*s
, unsigned int sort_mode
);
47 static void push_rev(struct log_state
*s
, git_object
*obj
, int hide
);
48 static int add_revision(struct log_state
*s
, const char *revstr
);
50 /** log_options holds other command line options that affect log output */
55 int min_parents
, max_parents
;
59 const char *committer
;
63 /** utility functions that parse options and help with log output */
64 static int parse_options(
65 struct log_state
*s
, struct log_options
*opt
, int argc
, char **argv
);
66 static void print_time(const git_time
*intime
, const char *prefix
);
67 static void print_commit(git_commit
*commit
, struct log_options
*opts
);
68 static int match_with_parent(git_commit
*commit
, int i
, git_diff_options
*);
70 /** utility functions for filtering */
71 static int signature_matches(const git_signature
*sig
, const char *filter
);
72 static int log_message_matches(const git_commit
*commit
, const char *filter
);
74 int lg2_log(git_repository
*repo
, int argc
, char *argv
[])
76 int i
, count
= 0, printed
= 0, parents
, last_arg
;
78 struct log_options opt
;
79 git_diff_options diffopts
= GIT_DIFF_OPTIONS_INIT
;
81 git_commit
*commit
= NULL
;
82 git_pathspec
*ps
= NULL
;
84 /** Parse arguments and set up revwalker. */
85 last_arg
= parse_options(&s
, &opt
, argc
, argv
);
88 diffopts
.pathspec
.strings
= &argv
[last_arg
];
89 diffopts
.pathspec
.count
= argc
- last_arg
;
90 if (diffopts
.pathspec
.count
> 0)
91 check_lg2(git_pathspec_new(&ps
, &diffopts
.pathspec
),
92 "Building pathspec", NULL
);
95 add_revision(&s
, NULL
);
97 /** Use the revwalker to traverse the history. */
101 for (; !git_revwalk_next(&oid
, s
.walker
); git_commit_free(commit
)) {
102 check_lg2(git_commit_lookup(&commit
, s
.repo
, &oid
),
103 "Failed to look up commit", NULL
);
105 parents
= (int)git_commit_parentcount(commit
);
106 if (parents
< opt
.min_parents
)
108 if (opt
.max_parents
> 0 && parents
> opt
.max_parents
)
111 if (diffopts
.pathspec
.count
> 0) {
112 int unmatched
= parents
;
116 check_lg2(git_commit_tree(&tree
, commit
), "Get tree", NULL
);
117 if (git_pathspec_match_tree(
118 NULL
, tree
, GIT_PATHSPEC_NO_MATCH_ERROR
, ps
) != 0)
121 } else if (parents
== 1) {
122 unmatched
= match_with_parent(commit
, 0, &diffopts
) ? 0 : 1;
124 for (i
= 0; i
< parents
; ++i
) {
125 if (match_with_parent(commit
, i
, &diffopts
))
134 if (!signature_matches(git_commit_author(commit
), opt
.author
))
137 if (!signature_matches(git_commit_committer(commit
), opt
.committer
))
140 if (!log_message_matches(commit
, opt
.grep
))
143 if (count
++ < opt
.skip
)
145 if (opt
.limit
!= -1 && printed
++ >= opt
.limit
) {
146 git_commit_free(commit
);
150 print_commit(commit
, &opt
);
153 git_tree
*a
= NULL
, *b
= NULL
;
154 git_diff
*diff
= NULL
;
158 check_lg2(git_commit_tree(&b
, commit
), "Get tree", NULL
);
161 check_lg2(git_commit_parent(&parent
, commit
, 0), "Get parent", NULL
);
162 check_lg2(git_commit_tree(&a
, parent
), "Tree for parent", NULL
);
163 git_commit_free(parent
);
166 check_lg2(git_diff_tree_to_tree(
167 &diff
, git_commit_owner(commit
), a
, b
, &diffopts
),
168 "Diff commit with parent", NULL
);
170 git_diff_print(diff
, GIT_DIFF_FORMAT_PATCH
, diff_output
, NULL
),
171 "Displaying diff", NULL
);
179 git_pathspec_free(ps
);
180 git_revwalk_free(s
.walker
);
185 /** Determine if the given git_signature does not contain the filter text. */
186 static int signature_matches(const git_signature
*sig
, const char *filter
) {
191 (strstr(sig
->name
, filter
) != NULL
||
192 strstr(sig
->email
, filter
) != NULL
))
198 static int log_message_matches(const git_commit
*commit
, const char *filter
) {
199 const char *message
= NULL
;
204 if ((message
= git_commit_message(commit
)) != NULL
&&
205 strstr(message
, filter
) != NULL
)
211 /** Push object (for hide or show) onto revwalker. */
212 static void push_rev(struct log_state
*s
, git_object
*obj
, int hide
)
214 hide
= s
->hide
^ hide
;
216 /** Create revwalker on demand if it doesn't already exist. */
218 check_lg2(git_revwalk_new(&s
->walker
, s
->repo
),
219 "Could not create revision walker", NULL
);
220 git_revwalk_sorting(s
->walker
, s
->sorting
);
224 check_lg2(git_revwalk_push_head(s
->walker
),
225 "Could not find repository HEAD", NULL
);
227 check_lg2(git_revwalk_hide(s
->walker
, git_object_id(obj
)),
228 "Reference does not refer to a commit", NULL
);
230 check_lg2(git_revwalk_push(s
->walker
, git_object_id(obj
)),
231 "Reference does not refer to a commit", NULL
);
233 git_object_free(obj
);
236 /** Parse revision string and add revs to walker. */
237 static int add_revision(struct log_state
*s
, const char *revstr
)
243 push_rev(s
, NULL
, hide
);
247 if (*revstr
== '^') {
248 revs
.flags
= GIT_REVPARSE_SINGLE
;
251 if (git_revparse_single(&revs
.from
, s
->repo
, revstr
+ 1) < 0)
253 } else if (git_revparse(&revs
, s
->repo
, revstr
) < 0)
256 if ((revs
.flags
& GIT_REVPARSE_SINGLE
) != 0)
257 push_rev(s
, revs
.from
, hide
);
259 push_rev(s
, revs
.to
, hide
);
261 if ((revs
.flags
& GIT_REVPARSE_MERGE_BASE
) != 0) {
263 check_lg2(git_merge_base(&base
, s
->repo
,
264 git_object_id(revs
.from
), git_object_id(revs
.to
)),
265 "Could not find merge base", revstr
);
267 git_object_lookup(&revs
.to
, s
->repo
, &base
, GIT_OBJECT_COMMIT
),
268 "Could not find merge base commit", NULL
);
270 push_rev(s
, revs
.to
, hide
);
273 push_rev(s
, revs
.from
, !hide
);
279 /** Update revwalker with sorting mode. */
280 static void set_sorting(struct log_state
*s
, unsigned int sort_mode
)
282 /** Open repo on demand if it isn't already open. */
284 if (!s
->repodir
) s
->repodir
= ".";
285 check_lg2(git_repository_open_ext(&s
->repo
, s
->repodir
, 0, NULL
),
286 "Could not open repository", s
->repodir
);
289 /** Create revwalker on demand if it doesn't already exist. */
291 check_lg2(git_revwalk_new(&s
->walker
, s
->repo
),
292 "Could not create revision walker", NULL
);
294 if (sort_mode
== GIT_SORT_REVERSE
)
295 s
->sorting
= s
->sorting
^ GIT_SORT_REVERSE
;
297 s
->sorting
= sort_mode
| (s
->sorting
& GIT_SORT_REVERSE
);
299 git_revwalk_sorting(s
->walker
, s
->sorting
);
302 /** Helper to format a git_time value like Git. */
303 static void print_time(const git_time
*intime
, const char *prefix
)
307 int offset
, hours
, minutes
;
310 offset
= intime
->offset
;
319 minutes
= offset
% 60;
321 t
= (time_t)intime
->time
+ (intime
->offset
* 60);
324 strftime(out
, sizeof(out
), "%a %b %e %T %Y", intm
);
326 printf("%s%s %c%02d%02d\n", prefix
, out
, sign
, hours
, minutes
);
329 /** Helper to print a commit object. */
330 static void print_commit(git_commit
*commit
, struct log_options
*opts
)
332 char buf
[GIT_OID_HEXSZ
+ 1];
334 const git_signature
*sig
;
335 const char *scan
, *eol
;
337 git_oid_tostr(buf
, sizeof(buf
), git_commit_id(commit
));
338 printf("commit %s\n", buf
);
340 if (opts
->show_log_size
) {
341 printf("log size %d\n", (int)strlen(git_commit_message(commit
)));
344 if ((count
= (int)git_commit_parentcount(commit
)) > 1) {
346 for (i
= 0; i
< count
; ++i
) {
347 git_oid_tostr(buf
, 8, git_commit_parent_id(commit
, i
));
353 if ((sig
= git_commit_author(commit
)) != NULL
) {
354 printf("Author: %s <%s>\n", sig
->name
, sig
->email
);
355 print_time(&sig
->when
, "Date: ");
359 for (scan
= git_commit_message(commit
); scan
&& *scan
; ) {
360 for (eol
= scan
; *eol
&& *eol
!= '\n'; ++eol
) /* find eol */;
362 printf(" %.*s\n", (int)(eol
- scan
), scan
);
363 scan
= *eol
? eol
+ 1 : NULL
;
368 /** Helper to find how many files in a commit changed from its nth parent. */
369 static int match_with_parent(git_commit
*commit
, int i
, git_diff_options
*opts
)
377 git_commit_parent(&parent
, commit
, (size_t)i
), "Get parent", NULL
);
378 check_lg2(git_commit_tree(&a
, parent
), "Tree for parent", NULL
);
379 check_lg2(git_commit_tree(&b
, commit
), "Tree for commit", NULL
);
381 git_diff_tree_to_tree(&diff
, git_commit_owner(commit
), a
, b
, opts
),
382 "Checking diff between parent and commit", NULL
);
384 ndeltas
= (int)git_diff_num_deltas(diff
);
389 git_commit_free(parent
);
394 /** Print a usage message for the program. */
395 static void usage(const char *message
, const char *arg
)
398 fprintf(stderr
, "%s: %s\n", message
, arg
);
400 fprintf(stderr
, "%s\n", message
);
401 fprintf(stderr
, "usage: log [<options>]\n");
405 /** Parse some log command line options. */
406 static int parse_options(
407 struct log_state
*s
, struct log_options
*opt
, int argc
, char **argv
)
409 struct args_info args
= ARGS_INFO_INIT
;
411 memset(s
, 0, sizeof(*s
));
412 s
->sorting
= GIT_SORT_TIME
;
414 memset(opt
, 0, sizeof(*opt
));
415 opt
->max_parents
= -1;
418 for (args
.pos
= 1; args
.pos
< argc
; ++args
.pos
) {
419 const char *a
= argv
[args
.pos
];
422 if (!add_revision(s
, a
))
425 /** Try failed revision parse as filename. */
427 } else if (!match_arg_separator(&args
)) {
430 else if (!strcmp(a
, "--date-order"))
431 set_sorting(s
, GIT_SORT_TIME
);
432 else if (!strcmp(a
, "--topo-order"))
433 set_sorting(s
, GIT_SORT_TOPOLOGICAL
);
434 else if (!strcmp(a
, "--reverse"))
435 set_sorting(s
, GIT_SORT_REVERSE
);
436 else if (match_str_arg(&opt
->author
, &args
, "--author"))
437 /** Found valid --author */;
438 else if (match_str_arg(&opt
->committer
, &args
, "--committer"))
439 /** Found valid --committer */;
440 else if (match_str_arg(&opt
->grep
, &args
, "--grep"))
441 /** Found valid --grep */;
442 else if (match_str_arg(&s
->repodir
, &args
, "--git-dir"))
443 /** Found git-dir. */;
444 else if (match_int_arg(&opt
->skip
, &args
, "--skip", 0))
445 /** Found valid --skip. */;
446 else if (match_int_arg(&opt
->limit
, &args
, "--max-count", 0))
447 /** Found valid --max-count. */;
448 else if (a
[1] >= '0' && a
[1] <= '9')
449 is_integer(&opt
->limit
, a
+ 1, 0);
450 else if (match_int_arg(&opt
->limit
, &args
, "-n", 0))
451 /** Found valid -n. */;
452 else if (!strcmp(a
, "--merges"))
453 opt
->min_parents
= 2;
454 else if (!strcmp(a
, "--no-merges"))
455 opt
->max_parents
= 1;
456 else if (!strcmp(a
, "--no-min-parents"))
457 opt
->min_parents
= 0;
458 else if (!strcmp(a
, "--no-max-parents"))
459 opt
->max_parents
= -1;
460 else if (match_int_arg(&opt
->max_parents
, &args
, "--max-parents=", 1))
461 /** Found valid --max-parents. */;
462 else if (match_int_arg(&opt
->min_parents
, &args
, "--min-parents=", 0))
463 /** Found valid --min_parents. */;
464 else if (!strcmp(a
, "-p") || !strcmp(a
, "-u") || !strcmp(a
, "--patch"))
466 else if (!strcmp(a
, "--log-size"))
467 opt
->show_log_size
= 1;
469 usage("Unsupported argument", a
);