]> git.proxmox.com Git - libgit2.git/blob - examples/diff.c
Merge pull request #2014 from mgbowen/cpp-options-init
[libgit2.git] / examples / diff.c
1 /*
2 * libgit2 "diff" example - shows how to use the diff API
3 *
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/>.
13 */
14
15 #include "common.h"
16
17 /**
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 };
35
36 /** The 'opts' struct captures all the various parsed command line options. */
37 struct opts {
38 git_diff_options diffopts;
39 git_diff_find_options findopts;
40 int color;
41 int cached;
42 int numstat;
43 int shortstat;
44 git_diff_format_t format;
45 const char *treeish1;
46 const char *treeish2;
47 const char *dir;
48 };
49
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);
56
57 int main(int argc, char *argv[])
58 {
59 git_repository *repo = NULL;
60 git_tree *t1 = NULL, *t2 = NULL;
61 git_diff *diff;
62 struct opts o = {
63 GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
64 -1, 0, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
65 };
66
67 git_threads_init();
68
69 parse_opts(&o, argc, argv);
70
71 check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL),
72 "Could not open repository", o.dir);
73
74 /**
75 * Possible argument patterns:
76 *
77 * * &lt;sha1&gt; &lt;sha2&gt;
78 * * &lt;sha1&gt; --cached
79 * * &lt;sha1&gt;
80 * * --cached
81 * * nothing
82 *
83 * Currently ranged arguments like &lt;sha1&gt;..&lt;sha2&gt; and &lt;sha1&gt;...&lt;sha2&gt;
84 * are not supported in this example
85 */
86
87 if (o.treeish1)
88 treeish_to_tree(&t1, repo, o.treeish1);
89 if (o.treeish2)
90 treeish_to_tree(&t2, repo, o.treeish2);
91
92 if (t1 && t2)
93 check_lg2(
94 git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts),
95 "diff trees", NULL);
96 else if (t1 && o.cached)
97 check_lg2(
98 git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
99 "diff tree to index", NULL);
100 else if (t1)
101 check_lg2(
102 git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts),
103 "diff tree to working directory", NULL);
104 else if (o.cached) {
105 treeish_to_tree(&t1, repo, "HEAD");
106 check_lg2(
107 git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
108 "diff tree to index", NULL);
109 }
110 else
111 check_lg2(
112 git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
113 "diff index to working directory", NULL);
114
115 /** Apply rename and copy detection if requested. */
116
117 if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0)
118 check_lg2(
119 git_diff_find_similar(diff, &o.findopts),
120 "finding renames and copies", NULL);
121
122 /** Generate simple output using libgit2 display helper. */
123
124 if (o.numstat == 1)
125 diff_print_numstat(diff);
126 else if (o.shortstat == 1)
127 diff_print_shortstat(diff);
128 else {
129 if (o.color >= 0)
130 fputs(colors[0], stdout);
131
132 check_lg2(
133 git_diff_print(diff, o.format, color_printer, &o.color),
134 "displaying diff", NULL);
135
136 if (o.color >= 0)
137 fputs(colors[0], stdout);
138 }
139
140 /** Cleanup before exiting. */
141
142 git_diff_free(diff);
143 git_tree_free(t1);
144 git_tree_free(t2);
145 git_repository_free(repo);
146
147 git_threads_shutdown();
148
149 return 0;
150 }
151
152 static void usage(const char *message, const char *arg)
153 {
154 if (message && arg)
155 fprintf(stderr, "%s: %s\n", message, arg);
156 else if (message)
157 fprintf(stderr, "%s\n", message);
158 fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n");
159 exit(1);
160 }
161
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,
167 void *data)
168 {
169 int *last_color = data, color = 0;
170
171 (void)delta; (void)hunk;
172
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;
181 default: break;
182 }
183
184 if (color != *last_color) {
185 if (*last_color == 1 || color == 1)
186 fputs(colors[0], stdout);
187 fputs(colors[color], stdout);
188 *last_color = color;
189 }
190 }
191
192 return diff_output(delta, hunk, line, stdout);
193 }
194
195 /** Parse arguments as copied from git-diff. */
196 static void parse_opts(struct opts *o, int argc, char *argv[])
197 {
198 struct args_info args = ARGS_INFO_INIT;
199
200
201 for (args.pos = 1; args.pos < argc; ++args.pos) {
202 const char *a = argv[args.pos];
203
204 if (a[0] != '-') {
205 if (o->treeish1 == NULL)
206 o->treeish1 = a;
207 else if (o->treeish2 == NULL)
208 o->treeish2 = a;
209 else
210 usage("Only one or two tree identifiers can be provided", NULL);
211 }
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"))
216 o->cached = 1;
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"))
224 o->color = 0;
225 else if (!strcmp(a, "--no-color"))
226 o->color = -1;
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"))
246 o->numstat = 1;
247 else if (!strcmp(a, "--shortstat"))
248 o->shortstat = 1;
249 else if (match_uint16_arg(
250 &o->findopts.rename_threshold, &args, "-M") ||
251 match_uint16_arg(
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") ||
256 match_uint16_arg(
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") &&
266 !match_uint16_arg(
267 &o->diffopts.context_lines, &args, "--unified") &&
268 !match_uint16_arg(
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);
274 }
275 }
276
277 /** Display diff output with "--numstat".*/
278 static void diff_print_numstat(git_diff *diff)
279 {
280 git_patch *patch;
281 const git_diff_delta *delta;
282 size_t d, ndeltas = git_diff_num_deltas(diff);
283 size_t nadditions, ndeletions;
284
285 for (d = 0; d < ndeltas; d++){
286 check_lg2(
287 git_patch_from_diff(&patch, diff, d),
288 "generating patch from diff", NULL);
289
290 check_lg2(
291 git_patch_line_stats(NULL, &nadditions, &ndeletions, patch),
292 "generating the number of additions and deletions", NULL);
293
294 delta = git_patch_get_delta(patch);
295
296 printf("%ld\t%ld\t%s\n",
297 (long)nadditions, (long)ndeletions, delta->new_file.path);
298
299 git_patch_free(patch);
300 }
301 }
302
303 /** Display diff output with "--shortstat".*/
304 static void diff_print_shortstat(git_diff *diff)
305 {
306 git_patch *patch;
307 size_t d, ndeltas = git_diff_num_deltas(diff);
308 size_t nadditions, ndeletions;
309 long nadditions_sum, ndeletions_sum;
310
311 nadditions_sum = 0;
312 ndeletions_sum = 0;
313
314 for (d = 0; d < ndeltas; d++){
315 check_lg2(
316 git_patch_from_diff(&patch, diff, d),
317 "generating patch from diff", NULL);
318
319 check_lg2(
320 git_patch_line_stats(NULL, &nadditions, &ndeletions, patch),
321 "generating the number of additions and deletions", NULL);
322
323 nadditions_sum += nadditions;
324 ndeletions_sum += ndeletions;
325
326 git_patch_free(patch);
327 }
328
329 if (ndeltas) {
330
331 printf(" %ld ", (long)ndeltas);
332 printf("%s", 1==ndeltas ? "file changed" : "files changed");
333
334 if(nadditions_sum) {
335 printf(", %ld ",nadditions_sum);
336 printf("%s", 1==nadditions_sum ? "insertion(+)" : "insertions(+)");
337 }
338
339 if(ndeletions_sum) {
340 printf(", %ld ",ndeletions_sum);
341 printf("%s", 1==ndeletions_sum ? "deletion(-)" : "deletions(-)");
342 }
343 printf("\n");
344 }
345 }