]>
Commit | Line | Data |
---|---|---|
66902d47 | 1 | /* |
6cb831bd | 2 | * libgit2 "diff" example - shows how to use the diff API |
66902d47 | 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/>. | |
66902d47 RB |
13 | */ |
14 | ||
15 | #include "common.h" | |
16 | ||
85c6730c | 17 | /** |
66902d47 RB |
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. | |
21 | * | |
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. | |
26 | */ | |
27 | ||
28 | static const char *colors[] = { | |
29 | "\033[m", /* reset */ | |
30 | "\033[1m", /* bold */ | |
31 | "\033[31m", /* red */ | |
32 | "\033[32m", /* green */ | |
33 | "\033[36m" /* cyan */ | |
34 | }; | |
3a437590 | 35 | |
12e422a0 | 36 | enum { |
8d09efa2 RB |
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) | |
12e422a0 RB |
42 | }; |
43 | ||
44 | enum { | |
45 | CACHE_NORMAL = 0, | |
46 | CACHE_ONLY = 1, | |
47 | CACHE_NONE = 2 | |
48 | }; | |
49 | ||
22a2d3d5 UG |
50 | /** The 'diff_options' struct captures all the various parsed command line options. */ |
51 | struct diff_options { | |
66902d47 RB |
52 | git_diff_options diffopts; |
53 | git_diff_find_options findopts; | |
54 | int color; | |
22a2d3d5 | 55 | int no_index; |
12e422a0 RB |
56 | int cache; |
57 | int output; | |
66902d47 RB |
58 | git_diff_format_t format; |
59 | const char *treeish1; | |
60 | const char *treeish2; | |
61 | const char *dir; | |
62 | }; | |
63 | ||
c44820c6 | 64 | /** These functions are implemented at the end */ |
12e422a0 | 65 | static void usage(const char *message, const char *arg); |
22a2d3d5 | 66 | static void parse_opts(struct diff_options *o, int argc, char *argv[]); |
85c6730c BS |
67 | static int color_printer( |
68 | const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*); | |
22a2d3d5 UG |
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); | |
3a437590 | 71 | |
22a2d3d5 | 72 | int lg2_diff(git_repository *repo, int argc, char *argv[]) |
3a437590 | 73 | { |
66902d47 RB |
74 | git_tree *t1 = NULL, *t2 = NULL; |
75 | git_diff *diff; | |
22a2d3d5 | 76 | struct diff_options o = { |
66902d47 | 77 | GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT, |
22a2d3d5 | 78 | -1, -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "." |
66902d47 RB |
79 | }; |
80 | ||
66902d47 RB |
81 | parse_opts(&o, argc, argv); |
82 | ||
85c6730c BS |
83 | /** |
84 | * Possible argument patterns: | |
85 | * | |
c44820c6 CMN |
86 | * * <sha1> <sha2> |
87 | * * <sha1> --cached | |
88 | * * <sha1> | |
85c6730c | 89 | * * --cached |
12e422a0 | 90 | * * --nocache (don't use index data in diff at all) |
22a2d3d5 | 91 | * * --no-index <file1> <file2> |
85c6730c | 92 | * * nothing |
66902d47 | 93 | * |
c44820c6 | 94 | * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2> |
66902d47 RB |
95 | * are not supported in this example |
96 | */ | |
97 | ||
22a2d3d5 UG |
98 | if (o.no_index >= 0) { |
99 | compute_diff_no_index(&diff, &o); | |
100 | } else { | |
101 | if (o.treeish1) | |
102 | treeish_to_tree(&t1, repo, o.treeish1); | |
103 | if (o.treeish2) | |
104 | treeish_to_tree(&t2, repo, o.treeish2); | |
12e422a0 | 105 | |
22a2d3d5 UG |
106 | if (t1 && t2) |
107 | check_lg2( | |
108 | git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts), | |
109 | "diff trees", NULL); | |
110 | else if (o.cache != CACHE_NORMAL) { | |
111 | if (!t1) | |
112 | treeish_to_tree(&t1, repo, "HEAD"); | |
113 | ||
114 | if (o.cache == CACHE_NONE) | |
115 | check_lg2( | |
116 | git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts), | |
117 | "diff tree to working directory", NULL); | |
118 | else | |
119 | check_lg2( | |
120 | git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts), | |
121 | "diff tree to index", NULL); | |
122 | } | |
123 | else if (t1) | |
12e422a0 | 124 | check_lg2( |
22a2d3d5 | 125 | git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts), |
12e422a0 RB |
126 | "diff tree to working directory", NULL); |
127 | else | |
128 | check_lg2( | |
22a2d3d5 UG |
129 | git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts), |
130 | "diff index to working directory", NULL); | |
66902d47 | 131 | |
22a2d3d5 | 132 | /** Apply rename and copy detection if requested. */ |
66902d47 | 133 | |
22a2d3d5 UG |
134 | if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0) |
135 | check_lg2( | |
136 | git_diff_find_similar(diff, &o.findopts), | |
137 | "finding renames and copies", NULL); | |
138 | } | |
66902d47 | 139 | |
85c6730c | 140 | /** Generate simple output using libgit2 display helper. */ |
3a437590 | 141 | |
8d09efa2 RB |
142 | if (!o.output) |
143 | o.output = OUTPUT_DIFF; | |
144 | ||
145 | if (o.output != OUTPUT_DIFF) | |
12e422a0 | 146 | diff_print_stats(diff, &o); |
12e422a0 | 147 | |
8d09efa2 | 148 | if ((o.output & OUTPUT_DIFF) != 0) { |
4f5a3f40 | 149 | if (o.color >= 0) |
150 | fputs(colors[0], stdout); | |
66902d47 | 151 | |
4f5a3f40 | 152 | check_lg2( |
153 | git_diff_print(diff, o.format, color_printer, &o.color), | |
154 | "displaying diff", NULL); | |
66902d47 | 155 | |
4f5a3f40 | 156 | if (o.color >= 0) |
157 | fputs(colors[0], stdout); | |
158 | } | |
66902d47 | 159 | |
85c6730c | 160 | /** Cleanup before exiting. */ |
66902d47 RB |
161 | git_diff_free(diff); |
162 | git_tree_free(t1); | |
163 | git_tree_free(t2); | |
66902d47 RB |
164 | |
165 | return 0; | |
3a437590 RB |
166 | } |
167 | ||
22a2d3d5 UG |
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; | |
172 | git_buf buf = {0}; | |
173 | ||
174 | if (!o->treeish1 || !o->treeish2) { | |
175 | usage("two files should be provided as arguments", NULL); | |
176 | } | |
177 | file1_str = read_file(o->treeish1); | |
178 | if (file1_str == NULL) { | |
179 | usage("file cannot be read", o->treeish1); | |
180 | } | |
181 | file2_str = read_file(o->treeish2); | |
182 | if (file2_str == NULL) { | |
183 | usage("file cannot be read", o->treeish2); | |
184 | } | |
185 | check_lg2( | |
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); | |
188 | check_lg2( | |
189 | git_patch_to_buf(&buf, patch), | |
190 | "patch to buf", NULL); | |
191 | check_lg2( | |
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); | |
196 | free(file1_str); | |
197 | free(file2_str); | |
198 | } | |
199 | ||
66902d47 RB |
200 | static void usage(const char *message, const char *arg) |
201 | { | |
202 | if (message && arg) | |
203 | fprintf(stderr, "%s: %s\n", message, arg); | |
204 | else if (message) | |
205 | fprintf(stderr, "%s\n", message); | |
206 | fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n"); | |
207 | exit(1); | |
208 | } | |
3a437590 | 209 | |
85c6730c | 210 | /** This implements very rudimentary colorized output. */ |
66902d47 | 211 | static int color_printer( |
b90500f0 | 212 | const git_diff_delta *delta, |
3b5f7954 RB |
213 | const git_diff_hunk *hunk, |
214 | const git_diff_line *line, | |
793c4385 | 215 | void *data) |
3a437590 RB |
216 | { |
217 | int *last_color = data, color = 0; | |
218 | ||
3b5f7954 | 219 | (void)delta; (void)hunk; |
b90500f0 | 220 | |
3a437590 | 221 | if (*last_color >= 0) { |
3b5f7954 RB |
222 | switch (line->origin) { |
223 | case GIT_DIFF_LINE_ADDITION: color = 3; break; | |
224 | case GIT_DIFF_LINE_DELETION: color = 2; break; | |
3a437590 RB |
225 | case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break; |
226 | case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break; | |
3b5f7954 RB |
227 | case GIT_DIFF_LINE_FILE_HDR: color = 1; break; |
228 | case GIT_DIFF_LINE_HUNK_HDR: color = 4; break; | |
229 | default: break; | |
3a437590 | 230 | } |
66902d47 | 231 | |
3a437590 RB |
232 | if (color != *last_color) { |
233 | if (*last_color == 1 || color == 1) | |
234 | fputs(colors[0], stdout); | |
235 | fputs(colors[color], stdout); | |
236 | *last_color = color; | |
237 | } | |
238 | } | |
239 | ||
66902d47 | 240 | return diff_output(delta, hunk, line, stdout); |
a2e895be RB |
241 | } |
242 | ||
c44820c6 | 243 | /** Parse arguments as copied from git-diff. */ |
22a2d3d5 | 244 | static void parse_opts(struct diff_options *o, int argc, char *argv[]) |
a2e895be | 245 | { |
66902d47 | 246 | struct args_info args = ARGS_INFO_INIT; |
3a437590 | 247 | |
66902d47 RB |
248 | for (args.pos = 1; args.pos < argc; ++args.pos) { |
249 | const char *a = argv[args.pos]; | |
a2e895be RB |
250 | |
251 | if (a[0] != '-') { | |
66902d47 RB |
252 | if (o->treeish1 == NULL) |
253 | o->treeish1 = a; | |
254 | else if (o->treeish2 == NULL) | |
255 | o->treeish2 = a; | |
a2e895be RB |
256 | else |
257 | usage("Only one or two tree identifiers can be provided", NULL); | |
258 | } | |
259 | else if (!strcmp(a, "-p") || !strcmp(a, "-u") || | |
8d09efa2 RB |
260 | !strcmp(a, "--patch")) { |
261 | o->output |= OUTPUT_DIFF; | |
66902d47 | 262 | o->format = GIT_DIFF_FORMAT_PATCH; |
8d09efa2 | 263 | } |
22a2d3d5 | 264 | else if (!strcmp(a, "--cached")) { |
12e422a0 | 265 | o->cache = CACHE_ONLY; |
22a2d3d5 UG |
266 | if (o->no_index >= 0) usage("--cached and --no-index are incompatible", NULL); |
267 | } else if (!strcmp(a, "--nocache")) | |
12e422a0 RB |
268 | o->cache = CACHE_NONE; |
269 | else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name")) | |
66902d47 | 270 | o->format = GIT_DIFF_FORMAT_NAME_ONLY; |
12e422a0 RB |
271 | else if (!strcmp(a, "--name-status") || |
272 | !strcmp(a, "--format=name-status")) | |
66902d47 | 273 | o->format = GIT_DIFF_FORMAT_NAME_STATUS; |
12e422a0 | 274 | else if (!strcmp(a, "--raw") || !strcmp(a, "--format=raw")) |
66902d47 | 275 | o->format = GIT_DIFF_FORMAT_RAW; |
12e422a0 RB |
276 | else if (!strcmp(a, "--format=diff-index")) { |
277 | o->format = GIT_DIFF_FORMAT_RAW; | |
278 | o->diffopts.id_abbrev = 40; | |
279 | } | |
22a2d3d5 UG |
280 | else if (!strcmp(a, "--no-index")) { |
281 | o->no_index = 0; | |
282 | if (o->cache == CACHE_ONLY) usage("--cached and --no-index are incompatible", NULL); | |
283 | } else if (!strcmp(a, "--color")) | |
66902d47 | 284 | o->color = 0; |
a2e895be | 285 | else if (!strcmp(a, "--no-color")) |
66902d47 | 286 | o->color = -1; |
a2e895be | 287 | else if (!strcmp(a, "-R")) |
66902d47 | 288 | o->diffopts.flags |= GIT_DIFF_REVERSE; |
a2e895be | 289 | else if (!strcmp(a, "-a") || !strcmp(a, "--text")) |
66902d47 | 290 | o->diffopts.flags |= GIT_DIFF_FORCE_TEXT; |
a2e895be | 291 | else if (!strcmp(a, "--ignore-space-at-eol")) |
66902d47 | 292 | o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; |
a2e895be | 293 | else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) |
66902d47 | 294 | o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; |
a2e895be | 295 | else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) |
66902d47 | 296 | o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE; |
74fa4bfa | 297 | else if (!strcmp(a, "--ignored")) |
66902d47 | 298 | o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED; |
74fa4bfa | 299 | else if (!strcmp(a, "--untracked")) |
66902d47 | 300 | o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; |
0d3c8a9d BG |
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; | |
12e422a0 | 305 | else if (!strcmp(a, "--stat")) |
8d09efa2 | 306 | o->output |= OUTPUT_STAT; |
4f5a3f40 | 307 | else if (!strcmp(a, "--numstat")) |
8d09efa2 | 308 | o->output |= OUTPUT_NUMSTAT; |
45d2e8dc | 309 | else if (!strcmp(a, "--shortstat")) |
8d09efa2 RB |
310 | o->output |= OUTPUT_SHORTSTAT; |
311 | else if (!strcmp(a, "--summary")) | |
312 | o->output |= OUTPUT_SUMMARY; | |
66902d47 RB |
313 | else if (match_uint16_arg( |
314 | &o->findopts.rename_threshold, &args, "-M") || | |
315 | match_uint16_arg( | |
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") || | |
320 | match_uint16_arg( | |
321 | &o->findopts.copy_threshold, &args, "--find-copies")) | |
322 | o->findopts.flags |= GIT_DIFF_FIND_COPIES; | |
5c8f37a3 | 323 | else if (!strcmp(a, "--find-copies-harder")) |
66902d47 RB |
324 | o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; |
325 | else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites")) | |
5c8f37a3 | 326 | /* TODO: parse thresholds */ |
66902d47 | 327 | o->findopts.flags |= GIT_DIFF_FIND_REWRITES; |
5c2a8361 | 328 | else if (!match_uint32_arg( |
66902d47 | 329 | &o->diffopts.context_lines, &args, "-U") && |
5c2a8361 | 330 | !match_uint32_arg( |
66902d47 | 331 | &o->diffopts.context_lines, &args, "--unified") && |
5c2a8361 | 332 | !match_uint32_arg( |
66902d47 | 333 | &o->diffopts.interhunk_lines, &args, "--inter-hunk-context") && |
12e422a0 RB |
334 | !match_uint16_arg( |
335 | &o->diffopts.id_abbrev, &args, "--abbrev") && | |
66902d47 RB |
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); | |
74fa4bfa | 340 | } |
3a437590 | 341 | } |
4f5a3f40 | 342 | |
8d09efa2 | 343 | /** Display diff output with "--stat", "--numstat", or "--shortstat" */ |
22a2d3d5 | 344 | static void diff_print_stats(git_diff *diff, struct diff_options *o) |
45d2e8dc | 345 | { |
8d09efa2 | 346 | git_diff_stats *stats; |
e579e0f7 | 347 | git_buf b = GIT_BUF_INIT; |
8d09efa2 | 348 | git_diff_stats_format_t format = 0; |
45d2e8dc | 349 | |
8d09efa2 RB |
350 | check_lg2( |
351 | git_diff_get_stats(&stats, diff), "generating stats for diff", NULL); | |
45d2e8dc | 352 | |
8d09efa2 RB |
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; | |
45d2e8dc | 361 | |
8d09efa2 RB |
362 | check_lg2( |
363 | git_diff_stats_to_buf(&b, stats, format, 80), "formatting stats", NULL); | |
12e422a0 | 364 | |
8d09efa2 | 365 | fputs(b.ptr, stdout); |
45d2e8dc | 366 | |
ac3d33df | 367 | git_buf_dispose(&b); |
8d09efa2 | 368 | git_diff_stats_free(stats); |
45d2e8dc | 369 | } |