]> git.proxmox.com Git - libgit2.git/blob - examples/diff.c
Merge pull request #2289 from libgit2/rb/note-git-diff-index-behavior
[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 enum {
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)
42 };
43
44 enum {
45 CACHE_NORMAL = 0,
46 CACHE_ONLY = 1,
47 CACHE_NONE = 2
48 };
49
50 /** The 'opts' struct captures all the various parsed command line options. */
51 struct opts {
52 git_diff_options diffopts;
53 git_diff_find_options findopts;
54 int color;
55 int cache;
56 int output;
57 git_diff_format_t format;
58 const char *treeish1;
59 const char *treeish2;
60 const char *dir;
61 };
62
63 /** These functions are implemented at the end */
64 static void usage(const char *message, const char *arg);
65 static void parse_opts(struct opts *o, int argc, char *argv[]);
66 static int color_printer(
67 const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);
68 static void diff_print_stats(git_diff *diff, struct opts *o);
69
70 int main(int argc, char *argv[])
71 {
72 git_repository *repo = NULL;
73 git_tree *t1 = NULL, *t2 = NULL;
74 git_diff *diff;
75 struct opts o = {
76 GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
77 -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
78 };
79
80 git_threads_init();
81
82 parse_opts(&o, argc, argv);
83
84 check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL),
85 "Could not open repository", o.dir);
86
87 /**
88 * Possible argument patterns:
89 *
90 * * &lt;sha1&gt; &lt;sha2&gt;
91 * * &lt;sha1&gt; --cached
92 * * &lt;sha1&gt;
93 * * --cached
94 * * --nocache (don't use index data in diff at all)
95 * * nothing
96 *
97 * Currently ranged arguments like &lt;sha1&gt;..&lt;sha2&gt; and &lt;sha1&gt;...&lt;sha2&gt;
98 * are not supported in this example
99 */
100
101 if (o.treeish1)
102 treeish_to_tree(&t1, repo, o.treeish1);
103 if (o.treeish2)
104 treeish_to_tree(&t2, repo, o.treeish2);
105
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)
124 check_lg2(
125 git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts),
126 "diff tree to working directory", NULL);
127 else
128 check_lg2(
129 git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
130 "diff index to working directory", NULL);
131
132 /** Apply rename and copy detection if requested. */
133
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
139 /** Generate simple output using libgit2 display helper. */
140
141 if (!o.output)
142 o.output = OUTPUT_DIFF;
143
144 if (o.output != OUTPUT_DIFF)
145 diff_print_stats(diff, &o);
146
147 if ((o.output & OUTPUT_DIFF) != 0) {
148 if (o.color >= 0)
149 fputs(colors[0], stdout);
150
151 check_lg2(
152 git_diff_print(diff, o.format, color_printer, &o.color),
153 "displaying diff", NULL);
154
155 if (o.color >= 0)
156 fputs(colors[0], stdout);
157 }
158
159 /** Cleanup before exiting. */
160
161 git_diff_free(diff);
162 git_tree_free(t1);
163 git_tree_free(t2);
164 git_repository_free(repo);
165
166 git_threads_shutdown();
167
168 return 0;
169 }
170
171 static void usage(const char *message, const char *arg)
172 {
173 if (message && arg)
174 fprintf(stderr, "%s: %s\n", message, arg);
175 else if (message)
176 fprintf(stderr, "%s\n", message);
177 fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n");
178 exit(1);
179 }
180
181 /** This implements very rudimentary colorized output. */
182 static int color_printer(
183 const git_diff_delta *delta,
184 const git_diff_hunk *hunk,
185 const git_diff_line *line,
186 void *data)
187 {
188 int *last_color = data, color = 0;
189
190 (void)delta; (void)hunk;
191
192 if (*last_color >= 0) {
193 switch (line->origin) {
194 case GIT_DIFF_LINE_ADDITION: color = 3; break;
195 case GIT_DIFF_LINE_DELETION: color = 2; break;
196 case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break;
197 case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break;
198 case GIT_DIFF_LINE_FILE_HDR: color = 1; break;
199 case GIT_DIFF_LINE_HUNK_HDR: color = 4; break;
200 default: break;
201 }
202
203 if (color != *last_color) {
204 if (*last_color == 1 || color == 1)
205 fputs(colors[0], stdout);
206 fputs(colors[color], stdout);
207 *last_color = color;
208 }
209 }
210
211 return diff_output(delta, hunk, line, stdout);
212 }
213
214 /** Parse arguments as copied from git-diff. */
215 static void parse_opts(struct opts *o, int argc, char *argv[])
216 {
217 struct args_info args = ARGS_INFO_INIT;
218
219
220 for (args.pos = 1; args.pos < argc; ++args.pos) {
221 const char *a = argv[args.pos];
222
223 if (a[0] != '-') {
224 if (o->treeish1 == NULL)
225 o->treeish1 = a;
226 else if (o->treeish2 == NULL)
227 o->treeish2 = a;
228 else
229 usage("Only one or two tree identifiers can be provided", NULL);
230 }
231 else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
232 !strcmp(a, "--patch")) {
233 o->output |= OUTPUT_DIFF;
234 o->format = GIT_DIFF_FORMAT_PATCH;
235 }
236 else if (!strcmp(a, "--cached"))
237 o->cache = CACHE_ONLY;
238 else if (!strcmp(a, "--nocache"))
239 o->cache = CACHE_NONE;
240 else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name"))
241 o->format = GIT_DIFF_FORMAT_NAME_ONLY;
242 else if (!strcmp(a, "--name-status") ||
243 !strcmp(a, "--format=name-status"))
244 o->format = GIT_DIFF_FORMAT_NAME_STATUS;
245 else if (!strcmp(a, "--raw") || !strcmp(a, "--format=raw"))
246 o->format = GIT_DIFF_FORMAT_RAW;
247 else if (!strcmp(a, "--format=diff-index")) {
248 o->format = GIT_DIFF_FORMAT_RAW;
249 o->diffopts.id_abbrev = 40;
250 }
251 else if (!strcmp(a, "--color"))
252 o->color = 0;
253 else if (!strcmp(a, "--no-color"))
254 o->color = -1;
255 else if (!strcmp(a, "-R"))
256 o->diffopts.flags |= GIT_DIFF_REVERSE;
257 else if (!strcmp(a, "-a") || !strcmp(a, "--text"))
258 o->diffopts.flags |= GIT_DIFF_FORCE_TEXT;
259 else if (!strcmp(a, "--ignore-space-at-eol"))
260 o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
261 else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change"))
262 o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
263 else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space"))
264 o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
265 else if (!strcmp(a, "--ignored"))
266 o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED;
267 else if (!strcmp(a, "--untracked"))
268 o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
269 else if (!strcmp(a, "--patience"))
270 o->diffopts.flags |= GIT_DIFF_PATIENCE;
271 else if (!strcmp(a, "--minimal"))
272 o->diffopts.flags |= GIT_DIFF_MINIMAL;
273 else if (!strcmp(a, "--stat"))
274 o->output |= OUTPUT_STAT;
275 else if (!strcmp(a, "--numstat"))
276 o->output |= OUTPUT_NUMSTAT;
277 else if (!strcmp(a, "--shortstat"))
278 o->output |= OUTPUT_SHORTSTAT;
279 else if (!strcmp(a, "--summary"))
280 o->output |= OUTPUT_SUMMARY;
281 else if (match_uint16_arg(
282 &o->findopts.rename_threshold, &args, "-M") ||
283 match_uint16_arg(
284 &o->findopts.rename_threshold, &args, "--find-renames"))
285 o->findopts.flags |= GIT_DIFF_FIND_RENAMES;
286 else if (match_uint16_arg(
287 &o->findopts.copy_threshold, &args, "-C") ||
288 match_uint16_arg(
289 &o->findopts.copy_threshold, &args, "--find-copies"))
290 o->findopts.flags |= GIT_DIFF_FIND_COPIES;
291 else if (!strcmp(a, "--find-copies-harder"))
292 o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
293 else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites"))
294 /* TODO: parse thresholds */
295 o->findopts.flags |= GIT_DIFF_FIND_REWRITES;
296 else if (!match_uint16_arg(
297 &o->diffopts.context_lines, &args, "-U") &&
298 !match_uint16_arg(
299 &o->diffopts.context_lines, &args, "--unified") &&
300 !match_uint16_arg(
301 &o->diffopts.interhunk_lines, &args, "--inter-hunk-context") &&
302 !match_uint16_arg(
303 &o->diffopts.id_abbrev, &args, "--abbrev") &&
304 !match_str_arg(&o->diffopts.old_prefix, &args, "--src-prefix") &&
305 !match_str_arg(&o->diffopts.new_prefix, &args, "--dst-prefix") &&
306 !match_str_arg(&o->dir, &args, "--git-dir"))
307 usage("Unknown command line argument", a);
308 }
309 }
310
311 /** Display diff output with "--stat", "--numstat", or "--shortstat" */
312 static void diff_print_stats(git_diff *diff, struct opts *o)
313 {
314 git_diff_stats *stats;
315 git_buf b = GIT_BUF_INIT_CONST(NULL, 0);
316 git_diff_stats_format_t format = 0;
317
318 check_lg2(
319 git_diff_get_stats(&stats, diff), "generating stats for diff", NULL);
320
321 if (o->output & OUTPUT_STAT)
322 format |= GIT_DIFF_STATS_FULL;
323 if (o->output & OUTPUT_SHORTSTAT)
324 format |= GIT_DIFF_STATS_SHORT;
325 if (o->output & OUTPUT_NUMSTAT)
326 format |= GIT_DIFF_STATS_NUMBER;
327 if (o->output & OUTPUT_SUMMARY)
328 format |= GIT_DIFF_STATS_INCLUDE_SUMMARY;
329
330 check_lg2(
331 git_diff_stats_to_buf(&b, stats, format, 80), "formatting stats", NULL);
332
333 fputs(b.ptr, stdout);
334
335 git_buf_free(&b);
336 git_diff_stats_free(stats);
337 }