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 */
37 OUTPUT_DIFF
= (1 << 0),
38 OUTPUT_STAT
= (1 << 1),
39 OUTPUT_SHORTSTAT
= (1 << 2),
40 OUTPUT_NUMSTAT
= (1 << 3),
41 OUTPUT_SUMMARY
= (1 << 4)
50 /** The 'diff_options' struct captures all the various parsed command line options. */
52 git_diff_options diffopts
;
53 git_diff_find_options findopts
;
58 git_diff_format_t format
;
64 /** These functions are implemented at the end */
65 static void usage(const char *message
, const char *arg
);
66 static void parse_opts(struct diff_options
*o
, int argc
, char *argv
[]);
67 static int color_printer(
68 const git_diff_delta
*, const git_diff_hunk
*, const git_diff_line
*, void*);
69 static void diff_print_stats(git_diff
*diff
, struct diff_options
*o
);
70 static void compute_diff_no_index(git_diff
**diff
, struct diff_options
*o
);
72 int lg2_diff(git_repository
*repo
, int argc
, char *argv
[])
74 git_tree
*t1
= NULL
, *t2
= NULL
;
76 struct diff_options o
= {
77 GIT_DIFF_OPTIONS_INIT
, GIT_DIFF_FIND_OPTIONS_INIT
,
78 -1, -1, 0, 0, GIT_DIFF_FORMAT_PATCH
, NULL
, NULL
, "."
81 parse_opts(&o
, argc
, argv
);
84 * Possible argument patterns:
86 * * <sha1> <sha2>
87 * * <sha1> --cached
90 * * --nocache (don't use index data in diff at all)
91 * * --no-index <file1> <file2>
94 * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2>
95 * are not supported in this example
98 if (o
.no_index
>= 0) {
99 compute_diff_no_index(&diff
, &o
);
102 treeish_to_tree(&t1
, repo
, o
.treeish1
);
104 treeish_to_tree(&t2
, repo
, o
.treeish2
);
108 git_diff_tree_to_tree(&diff
, repo
, t1
, t2
, &o
.diffopts
),
110 else if (o
.cache
!= CACHE_NORMAL
) {
112 treeish_to_tree(&t1
, repo
, "HEAD");
114 if (o
.cache
== CACHE_NONE
)
116 git_diff_tree_to_workdir(&diff
, repo
, t1
, &o
.diffopts
),
117 "diff tree to working directory", NULL
);
120 git_diff_tree_to_index(&diff
, repo
, t1
, NULL
, &o
.diffopts
),
121 "diff tree to index", NULL
);
125 git_diff_tree_to_workdir_with_index(&diff
, repo
, t1
, &o
.diffopts
),
126 "diff tree to working directory", NULL
);
129 git_diff_index_to_workdir(&diff
, repo
, NULL
, &o
.diffopts
),
130 "diff index to working directory", NULL
);
132 /** Apply rename and copy detection if requested. */
134 if ((o
.findopts
.flags
& GIT_DIFF_FIND_ALL
) != 0)
136 git_diff_find_similar(diff
, &o
.findopts
),
137 "finding renames and copies", NULL
);
140 /** Generate simple output using libgit2 display helper. */
143 o
.output
= OUTPUT_DIFF
;
145 if (o
.output
!= OUTPUT_DIFF
)
146 diff_print_stats(diff
, &o
);
148 if ((o
.output
& OUTPUT_DIFF
) != 0) {
150 fputs(colors
[0], stdout
);
153 git_diff_print(diff
, o
.format
, color_printer
, &o
.color
),
154 "displaying diff", NULL
);
157 fputs(colors
[0], stdout
);
160 /** Cleanup before exiting. */
168 static void compute_diff_no_index(git_diff
**diff
, struct diff_options
*o
) {
169 git_patch
*patch
= NULL
;
170 char *file1_str
= NULL
;
171 char *file2_str
= NULL
;
174 if (!o
->treeish1
|| !o
->treeish2
) {
175 usage("two files should be provided as arguments", NULL
);
177 file1_str
= read_file(o
->treeish1
);
178 if (file1_str
== NULL
) {
179 usage("file cannot be read", o
->treeish1
);
181 file2_str
= read_file(o
->treeish2
);
182 if (file2_str
== NULL
) {
183 usage("file cannot be read", o
->treeish2
);
186 git_patch_from_buffers(&patch
, file1_str
, strlen(file1_str
), o
->treeish1
, file2_str
, strlen(file2_str
), o
->treeish2
, &o
->diffopts
),
187 "patch buffers", NULL
);
189 git_patch_to_buf(&buf
, patch
),
190 "patch to buf", NULL
);
192 git_diff_from_buffer(diff
, buf
.ptr
, buf
.size
),
193 "diff from patch", NULL
);
194 git_patch_free(patch
);
195 git_buf_dispose(&buf
);
200 static void usage(const char *message
, const char *arg
)
203 fprintf(stderr
, "%s: %s\n", message
, arg
);
205 fprintf(stderr
, "%s\n", message
);
206 fprintf(stderr
, "usage: diff [<tree-oid> [<tree-oid>]]\n");
210 /** This implements very rudimentary colorized output. */
211 static int color_printer(
212 const git_diff_delta
*delta
,
213 const git_diff_hunk
*hunk
,
214 const git_diff_line
*line
,
217 int *last_color
= data
, color
= 0;
219 (void)delta
; (void)hunk
;
221 if (*last_color
>= 0) {
222 switch (line
->origin
) {
223 case GIT_DIFF_LINE_ADDITION
: color
= 3; break;
224 case GIT_DIFF_LINE_DELETION
: color
= 2; break;
225 case GIT_DIFF_LINE_ADD_EOFNL
: color
= 3; break;
226 case GIT_DIFF_LINE_DEL_EOFNL
: color
= 2; break;
227 case GIT_DIFF_LINE_FILE_HDR
: color
= 1; break;
228 case GIT_DIFF_LINE_HUNK_HDR
: color
= 4; break;
232 if (color
!= *last_color
) {
233 if (*last_color
== 1 || color
== 1)
234 fputs(colors
[0], stdout
);
235 fputs(colors
[color
], stdout
);
240 return diff_output(delta
, hunk
, line
, stdout
);
243 /** Parse arguments as copied from git-diff. */
244 static void parse_opts(struct diff_options
*o
, int argc
, char *argv
[])
246 struct args_info args
= ARGS_INFO_INIT
;
248 for (args
.pos
= 1; args
.pos
< argc
; ++args
.pos
) {
249 const char *a
= argv
[args
.pos
];
252 if (o
->treeish1
== NULL
)
254 else if (o
->treeish2
== NULL
)
257 usage("Only one or two tree identifiers can be provided", NULL
);
259 else if (!strcmp(a
, "-p") || !strcmp(a
, "-u") ||
260 !strcmp(a
, "--patch")) {
261 o
->output
|= OUTPUT_DIFF
;
262 o
->format
= GIT_DIFF_FORMAT_PATCH
;
264 else if (!strcmp(a
, "--cached")) {
265 o
->cache
= CACHE_ONLY
;
266 if (o
->no_index
>= 0) usage("--cached and --no-index are incompatible", NULL
);
267 } else if (!strcmp(a
, "--nocache"))
268 o
->cache
= CACHE_NONE
;
269 else if (!strcmp(a
, "--name-only") || !strcmp(a
, "--format=name"))
270 o
->format
= GIT_DIFF_FORMAT_NAME_ONLY
;
271 else if (!strcmp(a
, "--name-status") ||
272 !strcmp(a
, "--format=name-status"))
273 o
->format
= GIT_DIFF_FORMAT_NAME_STATUS
;
274 else if (!strcmp(a
, "--raw") || !strcmp(a
, "--format=raw"))
275 o
->format
= GIT_DIFF_FORMAT_RAW
;
276 else if (!strcmp(a
, "--format=diff-index")) {
277 o
->format
= GIT_DIFF_FORMAT_RAW
;
278 o
->diffopts
.id_abbrev
= 40;
280 else if (!strcmp(a
, "--no-index")) {
282 if (o
->cache
== CACHE_ONLY
) usage("--cached and --no-index are incompatible", NULL
);
283 } else if (!strcmp(a
, "--color"))
285 else if (!strcmp(a
, "--no-color"))
287 else if (!strcmp(a
, "-R"))
288 o
->diffopts
.flags
|= GIT_DIFF_REVERSE
;
289 else if (!strcmp(a
, "-a") || !strcmp(a
, "--text"))
290 o
->diffopts
.flags
|= GIT_DIFF_FORCE_TEXT
;
291 else if (!strcmp(a
, "--ignore-space-at-eol"))
292 o
->diffopts
.flags
|= GIT_DIFF_IGNORE_WHITESPACE_EOL
;
293 else if (!strcmp(a
, "-b") || !strcmp(a
, "--ignore-space-change"))
294 o
->diffopts
.flags
|= GIT_DIFF_IGNORE_WHITESPACE_CHANGE
;
295 else if (!strcmp(a
, "-w") || !strcmp(a
, "--ignore-all-space"))
296 o
->diffopts
.flags
|= GIT_DIFF_IGNORE_WHITESPACE
;
297 else if (!strcmp(a
, "--ignored"))
298 o
->diffopts
.flags
|= GIT_DIFF_INCLUDE_IGNORED
;
299 else if (!strcmp(a
, "--untracked"))
300 o
->diffopts
.flags
|= GIT_DIFF_INCLUDE_UNTRACKED
;
301 else if (!strcmp(a
, "--patience"))
302 o
->diffopts
.flags
|= GIT_DIFF_PATIENCE
;
303 else if (!strcmp(a
, "--minimal"))
304 o
->diffopts
.flags
|= GIT_DIFF_MINIMAL
;
305 else if (!strcmp(a
, "--stat"))
306 o
->output
|= OUTPUT_STAT
;
307 else if (!strcmp(a
, "--numstat"))
308 o
->output
|= OUTPUT_NUMSTAT
;
309 else if (!strcmp(a
, "--shortstat"))
310 o
->output
|= OUTPUT_SHORTSTAT
;
311 else if (!strcmp(a
, "--summary"))
312 o
->output
|= OUTPUT_SUMMARY
;
313 else if (match_uint16_arg(
314 &o
->findopts
.rename_threshold
, &args
, "-M") ||
316 &o
->findopts
.rename_threshold
, &args
, "--find-renames"))
317 o
->findopts
.flags
|= GIT_DIFF_FIND_RENAMES
;
318 else if (match_uint16_arg(
319 &o
->findopts
.copy_threshold
, &args
, "-C") ||
321 &o
->findopts
.copy_threshold
, &args
, "--find-copies"))
322 o
->findopts
.flags
|= GIT_DIFF_FIND_COPIES
;
323 else if (!strcmp(a
, "--find-copies-harder"))
324 o
->findopts
.flags
|= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED
;
325 else if (is_prefixed(a
, "-B") || is_prefixed(a
, "--break-rewrites"))
326 /* TODO: parse thresholds */
327 o
->findopts
.flags
|= GIT_DIFF_FIND_REWRITES
;
328 else if (!match_uint32_arg(
329 &o
->diffopts
.context_lines
, &args
, "-U") &&
331 &o
->diffopts
.context_lines
, &args
, "--unified") &&
333 &o
->diffopts
.interhunk_lines
, &args
, "--inter-hunk-context") &&
335 &o
->diffopts
.id_abbrev
, &args
, "--abbrev") &&
336 !match_str_arg(&o
->diffopts
.old_prefix
, &args
, "--src-prefix") &&
337 !match_str_arg(&o
->diffopts
.new_prefix
, &args
, "--dst-prefix") &&
338 !match_str_arg(&o
->dir
, &args
, "--git-dir"))
339 usage("Unknown command line argument", a
);
343 /** Display diff output with "--stat", "--numstat", or "--shortstat" */
344 static void diff_print_stats(git_diff
*diff
, struct diff_options
*o
)
346 git_diff_stats
*stats
;
347 git_buf b
= GIT_BUF_INIT
;
348 git_diff_stats_format_t format
= 0;
351 git_diff_get_stats(&stats
, diff
), "generating stats for diff", NULL
);
353 if (o
->output
& OUTPUT_STAT
)
354 format
|= GIT_DIFF_STATS_FULL
;
355 if (o
->output
& OUTPUT_SHORTSTAT
)
356 format
|= GIT_DIFF_STATS_SHORT
;
357 if (o
->output
& OUTPUT_NUMSTAT
)
358 format
|= GIT_DIFF_STATS_NUMBER
;
359 if (o
->output
& OUTPUT_SUMMARY
)
360 format
|= GIT_DIFF_STATS_INCLUDE_SUMMARY
;
363 git_diff_stats_to_buf(&b
, stats
, format
, 80), "formatting stats", NULL
);
365 fputs(b
.ptr
, stdout
);
368 git_diff_stats_free(stats
);