]> git.proxmox.com Git - libgit2.git/blame - examples/diff.c
Add ci
[libgit2.git] / examples / diff.c
CommitLineData
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
28static 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 36enum {
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
44enum {
45 CACHE_NORMAL = 0,
46 CACHE_ONLY = 1,
47 CACHE_NONE = 2
48};
49
0c9c969a
UG
50/** The 'diff_options' struct captures all the various parsed command line options. */
51struct diff_options {
66902d47
RB
52 git_diff_options diffopts;
53 git_diff_find_options findopts;
54 int color;
0c9c969a 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 65static void usage(const char *message, const char *arg);
0c9c969a 66static void parse_opts(struct diff_options *o, int argc, char *argv[]);
85c6730c
BS
67static int color_printer(
68 const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);
0c9c969a
UG
69static void diff_print_stats(git_diff *diff, struct diff_options *o);
70static void compute_diff_no_index(git_diff **diff, struct diff_options *o);
3a437590 71
0c9c969a 72int lg2_diff(git_repository *repo, int argc, char *argv[])
3a437590 73{
66902d47
RB
74 git_tree *t1 = NULL, *t2 = NULL;
75 git_diff *diff;
0c9c969a 76 struct diff_options o = {
66902d47 77 GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
0c9c969a 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 * * &lt;sha1&gt; &lt;sha2&gt;
87 * * &lt;sha1&gt; --cached
88 * * &lt;sha1&gt;
85c6730c 89 * * --cached
12e422a0 90 * * --nocache (don't use index data in diff at all)
0c9c969a 91 * * --no-index &lt;file1&gt; &lt;file2&gt;
85c6730c 92 * * nothing
66902d47 93 *
c44820c6 94 * Currently ranged arguments like &lt;sha1&gt;..&lt;sha2&gt; and &lt;sha1&gt;...&lt;sha2&gt;
66902d47
RB
95 * are not supported in this example
96 */
97
0c9c969a
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
0c9c969a
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(
0c9c969a 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(
0c9c969a
UG
129 git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
130 "diff index to working directory", NULL);
66902d47 131
0c9c969a 132 /** Apply rename and copy detection if requested. */
66902d47 133
0c9c969a
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
0c9c969a
UG
168static 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
200static 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 211static 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. */
0c9c969a 244static 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 }
0c9c969a 264 else if (!strcmp(a, "--cached")) {
12e422a0 265 o->cache = CACHE_ONLY;
0c9c969a
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 }
0c9c969a
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" */
0c9c969a 344static void diff_print_stats(git_diff *diff, struct diff_options *o)
45d2e8dc 345{
8d09efa2
RB
346 git_diff_stats *stats;
347 git_buf b = GIT_BUF_INIT_CONST(NULL, 0);
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}