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 'opts' struct captures all the various parsed command line options. */
52 git_diff_options diffopts
;
53 git_diff_find_options findopts
;
57 git_diff_format_t format
;
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
);
70 int main(int argc
, char *argv
[])
72 git_repository
*repo
= NULL
;
73 git_tree
*t1
= NULL
, *t2
= NULL
;
76 GIT_DIFF_OPTIONS_INIT
, GIT_DIFF_FIND_OPTIONS_INIT
,
77 -1, 0, 0, GIT_DIFF_FORMAT_PATCH
, NULL
, NULL
, "."
82 parse_opts(&o
, argc
, argv
);
84 check_lg2(git_repository_open_ext(&repo
, o
.dir
, 0, NULL
),
85 "Could not open repository", o
.dir
);
88 * Possible argument patterns:
90 * * <sha1> <sha2>
91 * * <sha1> --cached
94 * * --nocache (don't use index data in diff at all)
97 * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2>
98 * are not supported in this example
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
);
139 /** Generate simple output using libgit2 display helper. */
142 o
.output
= OUTPUT_DIFF
;
144 if (o
.output
!= OUTPUT_DIFF
)
145 diff_print_stats(diff
, &o
);
147 if ((o
.output
& OUTPUT_DIFF
) != 0) {
149 fputs(colors
[0], stdout
);
152 git_diff_print(diff
, o
.format
, color_printer
, &o
.color
),
153 "displaying diff", NULL
);
156 fputs(colors
[0], stdout
);
159 /** Cleanup before exiting. */
164 git_repository_free(repo
);
166 git_threads_shutdown();
171 static void usage(const char *message
, const char *arg
)
174 fprintf(stderr
, "%s: %s\n", message
, arg
);
176 fprintf(stderr
, "%s\n", message
);
177 fprintf(stderr
, "usage: diff [<tree-oid> [<tree-oid>]]\n");
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
,
188 int *last_color
= data
, color
= 0;
190 (void)delta
; (void)hunk
;
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;
203 if (color
!= *last_color
) {
204 if (*last_color
== 1 || color
== 1)
205 fputs(colors
[0], stdout
);
206 fputs(colors
[color
], stdout
);
211 return diff_output(delta
, hunk
, line
, stdout
);
214 /** Parse arguments as copied from git-diff. */
215 static void parse_opts(struct opts
*o
, int argc
, char *argv
[])
217 struct args_info args
= ARGS_INFO_INIT
;
220 for (args
.pos
= 1; args
.pos
< argc
; ++args
.pos
) {
221 const char *a
= argv
[args
.pos
];
224 if (o
->treeish1
== NULL
)
226 else if (o
->treeish2
== NULL
)
229 usage("Only one or two tree identifiers can be provided", NULL
);
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
;
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;
251 else if (!strcmp(a
, "--color"))
253 else if (!strcmp(a
, "--no-color"))
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") ||
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") ||
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") &&
299 &o
->diffopts
.context_lines
, &args
, "--unified") &&
301 &o
->diffopts
.interhunk_lines
, &args
, "--inter-hunk-context") &&
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
);
311 /** Display diff output with "--stat", "--numstat", or "--shortstat" */
312 static void diff_print_stats(git_diff
*diff
, struct opts
*o
)
314 git_diff_stats
*stats
;
315 git_buf b
= GIT_BUF_INIT_CONST(NULL
, 0);
316 git_diff_stats_format_t format
= 0;
319 git_diff_get_stats(&stats
, diff
), "generating stats for diff", NULL
);
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
;
331 git_diff_stats_to_buf(&b
, stats
, format
, 80), "formatting stats", NULL
);
333 fputs(b
.ptr
, stdout
);
336 git_diff_stats_free(stats
);