2 * libgit2 "diff" example - shows how to use the diff API
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 use of the libgit2 diff APIs to
19 * create `git_diff` objects and display them, emulating a number of
20 * core Git `diff` command line options.
22 * This covers on a portion of the core Git diff options and doesn't
23 * have particularly good error handling, but it should show most of
24 * the core libgit2 diff APIs, including various types of diffs and
25 * how to do renaming detection and patch formatting.
28 static const char *colors
[] = {
32 "\033[32m", /* green */
36 /** The 'opts' struct captures all the various parsed command line options. */
38 git_diff_options diffopts
;
39 git_diff_find_options findopts
;
44 git_diff_format_t format
;
50 /** These functions are implemented at the end */
51 static void parse_opts(struct opts
*o
, int argc
, char *argv
[]);
52 static int color_printer(
53 const git_diff_delta
*, const git_diff_hunk
*, const git_diff_line
*, void*);
54 static void diff_print_numstat(git_diff
*diff
);
55 static void diff_print_shortstat(git_diff
*diff
);
57 int main(int argc
, char *argv
[])
59 git_repository
*repo
= NULL
;
60 git_tree
*t1
= NULL
, *t2
= NULL
;
63 GIT_DIFF_OPTIONS_INIT
, GIT_DIFF_FIND_OPTIONS_INIT
,
64 -1, 0, 0, 0, GIT_DIFF_FORMAT_PATCH
, NULL
, NULL
, "."
69 parse_opts(&o
, argc
, argv
);
71 check_lg2(git_repository_open_ext(&repo
, o
.dir
, 0, NULL
),
72 "Could not open repository", o
.dir
);
75 * Possible argument patterns:
77 * * <sha1> <sha2>
78 * * <sha1> --cached
83 * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2>
84 * are not supported in this example
88 treeish_to_tree(&t1
, repo
, o
.treeish1
);
90 treeish_to_tree(&t2
, repo
, o
.treeish2
);
94 git_diff_tree_to_tree(&diff
, repo
, t1
, t2
, &o
.diffopts
),
96 else if (t1
&& o
.cached
)
98 git_diff_tree_to_index(&diff
, repo
, t1
, NULL
, &o
.diffopts
),
99 "diff tree to index", NULL
);
102 git_diff_tree_to_workdir_with_index(&diff
, repo
, t1
, &o
.diffopts
),
103 "diff tree to working directory", NULL
);
105 treeish_to_tree(&t1
, repo
, "HEAD");
107 git_diff_tree_to_index(&diff
, repo
, t1
, NULL
, &o
.diffopts
),
108 "diff tree to index", NULL
);
112 git_diff_index_to_workdir(&diff
, repo
, NULL
, &o
.diffopts
),
113 "diff index to working directory", NULL
);
115 /** Apply rename and copy detection if requested. */
117 if ((o
.findopts
.flags
& GIT_DIFF_FIND_ALL
) != 0)
119 git_diff_find_similar(diff
, &o
.findopts
),
120 "finding renames and copies", NULL
);
122 /** Generate simple output using libgit2 display helper. */
125 diff_print_numstat(diff
);
126 else if (o
.shortstat
== 1)
127 diff_print_shortstat(diff
);
130 fputs(colors
[0], stdout
);
133 git_diff_print(diff
, o
.format
, color_printer
, &o
.color
),
134 "displaying diff", NULL
);
137 fputs(colors
[0], stdout
);
140 /** Cleanup before exiting. */
145 git_repository_free(repo
);
147 git_threads_shutdown();
152 static void usage(const char *message
, const char *arg
)
155 fprintf(stderr
, "%s: %s\n", message
, arg
);
157 fprintf(stderr
, "%s\n", message
);
158 fprintf(stderr
, "usage: diff [<tree-oid> [<tree-oid>]]\n");
162 /** This implements very rudimentary colorized output. */
163 static int color_printer(
164 const git_diff_delta
*delta
,
165 const git_diff_hunk
*hunk
,
166 const git_diff_line
*line
,
169 int *last_color
= data
, color
= 0;
171 (void)delta
; (void)hunk
;
173 if (*last_color
>= 0) {
174 switch (line
->origin
) {
175 case GIT_DIFF_LINE_ADDITION
: color
= 3; break;
176 case GIT_DIFF_LINE_DELETION
: color
= 2; break;
177 case GIT_DIFF_LINE_ADD_EOFNL
: color
= 3; break;
178 case GIT_DIFF_LINE_DEL_EOFNL
: color
= 2; break;
179 case GIT_DIFF_LINE_FILE_HDR
: color
= 1; break;
180 case GIT_DIFF_LINE_HUNK_HDR
: color
= 4; break;
184 if (color
!= *last_color
) {
185 if (*last_color
== 1 || color
== 1)
186 fputs(colors
[0], stdout
);
187 fputs(colors
[color
], stdout
);
192 return diff_output(delta
, hunk
, line
, stdout
);
195 /** Parse arguments as copied from git-diff. */
196 static void parse_opts(struct opts
*o
, int argc
, char *argv
[])
198 struct args_info args
= ARGS_INFO_INIT
;
201 for (args
.pos
= 1; args
.pos
< argc
; ++args
.pos
) {
202 const char *a
= argv
[args
.pos
];
205 if (o
->treeish1
== NULL
)
207 else if (o
->treeish2
== NULL
)
210 usage("Only one or two tree identifiers can be provided", NULL
);
212 else if (!strcmp(a
, "-p") || !strcmp(a
, "-u") ||
213 !strcmp(a
, "--patch"))
214 o
->format
= GIT_DIFF_FORMAT_PATCH
;
215 else if (!strcmp(a
, "--cached"))
217 else if (!strcmp(a
, "--name-only"))
218 o
->format
= GIT_DIFF_FORMAT_NAME_ONLY
;
219 else if (!strcmp(a
, "--name-status"))
220 o
->format
= GIT_DIFF_FORMAT_NAME_STATUS
;
221 else if (!strcmp(a
, "--raw"))
222 o
->format
= GIT_DIFF_FORMAT_RAW
;
223 else if (!strcmp(a
, "--color"))
225 else if (!strcmp(a
, "--no-color"))
227 else if (!strcmp(a
, "-R"))
228 o
->diffopts
.flags
|= GIT_DIFF_REVERSE
;
229 else if (!strcmp(a
, "-a") || !strcmp(a
, "--text"))
230 o
->diffopts
.flags
|= GIT_DIFF_FORCE_TEXT
;
231 else if (!strcmp(a
, "--ignore-space-at-eol"))
232 o
->diffopts
.flags
|= GIT_DIFF_IGNORE_WHITESPACE_EOL
;
233 else if (!strcmp(a
, "-b") || !strcmp(a
, "--ignore-space-change"))
234 o
->diffopts
.flags
|= GIT_DIFF_IGNORE_WHITESPACE_CHANGE
;
235 else if (!strcmp(a
, "-w") || !strcmp(a
, "--ignore-all-space"))
236 o
->diffopts
.flags
|= GIT_DIFF_IGNORE_WHITESPACE
;
237 else if (!strcmp(a
, "--ignored"))
238 o
->diffopts
.flags
|= GIT_DIFF_INCLUDE_IGNORED
;
239 else if (!strcmp(a
, "--untracked"))
240 o
->diffopts
.flags
|= GIT_DIFF_INCLUDE_UNTRACKED
;
241 else if (!strcmp(a
, "--patience"))
242 o
->diffopts
.flags
|= GIT_DIFF_PATIENCE
;
243 else if (!strcmp(a
, "--minimal"))
244 o
->diffopts
.flags
|= GIT_DIFF_MINIMAL
;
245 else if (!strcmp(a
, "--numstat"))
247 else if (!strcmp(a
, "--shortstat"))
249 else if (match_uint16_arg(
250 &o
->findopts
.rename_threshold
, &args
, "-M") ||
252 &o
->findopts
.rename_threshold
, &args
, "--find-renames"))
253 o
->findopts
.flags
|= GIT_DIFF_FIND_RENAMES
;
254 else if (match_uint16_arg(
255 &o
->findopts
.copy_threshold
, &args
, "-C") ||
257 &o
->findopts
.copy_threshold
, &args
, "--find-copies"))
258 o
->findopts
.flags
|= GIT_DIFF_FIND_COPIES
;
259 else if (!strcmp(a
, "--find-copies-harder"))
260 o
->findopts
.flags
|= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED
;
261 else if (is_prefixed(a
, "-B") || is_prefixed(a
, "--break-rewrites"))
262 /* TODO: parse thresholds */
263 o
->findopts
.flags
|= GIT_DIFF_FIND_REWRITES
;
264 else if (!match_uint16_arg(
265 &o
->diffopts
.context_lines
, &args
, "-U") &&
267 &o
->diffopts
.context_lines
, &args
, "--unified") &&
269 &o
->diffopts
.interhunk_lines
, &args
, "--inter-hunk-context") &&
270 !match_str_arg(&o
->diffopts
.old_prefix
, &args
, "--src-prefix") &&
271 !match_str_arg(&o
->diffopts
.new_prefix
, &args
, "--dst-prefix") &&
272 !match_str_arg(&o
->dir
, &args
, "--git-dir"))
273 usage("Unknown command line argument", a
);
277 /** Display diff output with "--numstat".*/
278 static void diff_print_numstat(git_diff
*diff
)
281 const git_diff_delta
*delta
;
282 size_t d
, ndeltas
= git_diff_num_deltas(diff
);
283 size_t nadditions
, ndeletions
;
285 for (d
= 0; d
< ndeltas
; d
++){
287 git_patch_from_diff(&patch
, diff
, d
),
288 "generating patch from diff", NULL
);
291 git_patch_line_stats(NULL
, &nadditions
, &ndeletions
, patch
),
292 "generating the number of additions and deletions", NULL
);
294 delta
= git_patch_get_delta(patch
);
296 printf("%ld\t%ld\t%s\n",
297 (long)nadditions
, (long)ndeletions
, delta
->new_file
.path
);
299 git_patch_free(patch
);
303 /** Display diff output with "--shortstat".*/
304 static void diff_print_shortstat(git_diff
*diff
)
307 size_t d
, ndeltas
= git_diff_num_deltas(diff
);
308 size_t nadditions
, ndeletions
;
309 long nadditions_sum
, ndeletions_sum
;
314 for (d
= 0; d
< ndeltas
; d
++){
316 git_patch_from_diff(&patch
, diff
, d
),
317 "generating patch from diff", NULL
);
320 git_patch_line_stats(NULL
, &nadditions
, &ndeletions
, patch
),
321 "generating the number of additions and deletions", NULL
);
323 nadditions_sum
+= nadditions
;
324 ndeletions_sum
+= ndeletions
;
326 git_patch_free(patch
);
331 printf(" %ld ", (long)ndeltas
);
332 printf("%s", 1==ndeltas
? "file changed" : "files changed");
335 printf(", %ld ",nadditions_sum
);
336 printf("%s", 1==nadditions_sum
? "insertion(+)" : "insertions(+)");
340 printf(", %ld ",ndeletions_sum
);
341 printf("%s", 1==ndeletions_sum
? "deletion(-)" : "deletions(-)");