diff.c 11 KB
Newer Older
1
/*
2
 * libgit2 "diff" example - shows how to use the diff API
3
 *
4 5 6 7 8 9 10 11 12
 * Written by the libgit2 contributors
 *
 * To the extent possible under law, the author(s) have dedicated all copyright
 * and related and neighboring rights to this software to the public domain
 * worldwide. This software is distributed without any warranty.
 *
 * You should have received a copy of the CC0 Public Domain Dedication along
 * with this software. If not, see
 * <http://creativecommons.org/publicdomain/zero/1.0/>.
13 14 15 16
 */

#include "common.h"

17
/**
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
 * This example demonstrates the use of the libgit2 diff APIs to
 * create `git_diff` objects and display them, emulating a number of
 * core Git `diff` command line options.
 *
 * This covers on a portion of the core Git diff options and doesn't
 * have particularly good error handling, but it should show most of
 * the core libgit2 diff APIs, including various types of diffs and
 * how to do renaming detection and patch formatting.
 */

static const char *colors[] = {
	"\033[m", /* reset */
	"\033[1m", /* bold */
	"\033[31m", /* red */
	"\033[32m", /* green */
	"\033[36m" /* cyan */
};
35

36
enum {
37 38 39 40 41
	OUTPUT_DIFF = (1 << 0),
	OUTPUT_STAT = (1 << 1),
	OUTPUT_SHORTSTAT = (1 << 2),
	OUTPUT_NUMSTAT = (1 << 3),
	OUTPUT_SUMMARY = (1 << 4)
42 43 44 45 46 47 48 49
};

enum {
	CACHE_NORMAL = 0,
	CACHE_ONLY = 1,
	CACHE_NONE = 2
};

50 51
/** The 'diff_options' struct captures all the various parsed command line options. */
struct diff_options {
52 53 54
	git_diff_options diffopts;
	git_diff_find_options findopts;
	int color;
55
	int no_index;
56 57
	int cache;
	int output;
58 59 60 61 62 63
	git_diff_format_t format;
	const char *treeish1;
	const char *treeish2;
	const char *dir;
};

64
/** These functions are implemented at the end */
65
static void usage(const char *message, const char *arg);
66
static void parse_opts(struct diff_options *o, int argc, char *argv[]);
67 68
static int color_printer(
	const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);
69 70
static void diff_print_stats(git_diff *diff, struct diff_options *o);
static void compute_diff_no_index(git_diff **diff, struct diff_options *o);
71

72
int lg2_diff(git_repository *repo, int argc, char *argv[])
73
{
74 75
	git_tree *t1 = NULL, *t2 = NULL;
	git_diff *diff;
76
	struct diff_options o = {
77
		GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
78
		-1, -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
79 80 81 82
	};

	parse_opts(&o, argc, argv);

83 84 85
	/**
	 * Possible argument patterns:
	 *
86 87 88
	 *  * &lt;sha1&gt; &lt;sha2&gt;
	 *  * &lt;sha1&gt; --cached
	 *  * &lt;sha1&gt;
89
	 *  * --cached
90
	 *  * --nocache (don't use index data in diff at all)
91
	 *  * --no-index &lt;file1&gt; &lt;file2&gt;
92
	 *  * nothing
93
	 *
94
	 * Currently ranged arguments like &lt;sha1&gt;..&lt;sha2&gt; and &lt;sha1&gt;...&lt;sha2&gt;
95 96 97
	 * are not supported in this example
	 */

98 99 100 101 102 103 104
	if (o.no_index >= 0) {
		compute_diff_no_index(&diff, &o);
	} else {
		if (o.treeish1)
			treeish_to_tree(&t1, repo, o.treeish1);
		if (o.treeish2)
			treeish_to_tree(&t2, repo, o.treeish2);
105

106
		if (t1 && t2)
107
			check_lg2(
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
				git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts),
				"diff trees", NULL);
		else if (o.cache != CACHE_NORMAL) {
			if (!t1)
				treeish_to_tree(&t1, repo, "HEAD");

			if (o.cache == CACHE_NONE)
				check_lg2(
					git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts),
					"diff tree to working directory", NULL);
			else
				check_lg2(
					git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
					"diff tree to index", NULL);
		}
		else if (t1)
			check_lg2(
				git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts),
126 127 128
				"diff tree to working directory", NULL);
		else
			check_lg2(
129 130
				git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
				"diff index to working directory", NULL);
131

132
		/** Apply rename and copy detection if requested. */
133

134 135 136 137 138
		if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0)
			check_lg2(
				git_diff_find_similar(diff, &o.findopts),
				"finding renames and copies", NULL);
	}
139

140
	/** Generate simple output using libgit2 display helper. */
141

142 143 144 145
	if (!o.output)
		o.output = OUTPUT_DIFF;

	if (o.output != OUTPUT_DIFF)
146 147
		diff_print_stats(diff, &o);

148
	if ((o.output & OUTPUT_DIFF) != 0) {
149 150
		if (o.color >= 0)
			fputs(colors[0], stdout);
151

152 153 154
		check_lg2(
			git_diff_print(diff, o.format, color_printer, &o.color),
			"displaying diff", NULL);
155

156 157 158
		if (o.color >= 0)
			fputs(colors[0], stdout);
	}
159

160
	/** Cleanup before exiting. */
161 162 163 164 165
	git_diff_free(diff);
	git_tree_free(t1);
	git_tree_free(t2);

	return 0;
166 167
}

168
static void compute_diff_no_index(git_diff **diff, struct diff_options *o) {
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
	git_patch *patch = NULL;
	char *file1_str = NULL;
	char *file2_str = NULL;
	git_buf buf = {0};

	if (!o->treeish1 || !o->treeish2) {
		usage("two files should be provided as arguments", NULL);
	}
	file1_str = read_file(o->treeish1);
	if (file1_str == NULL) {
		usage("file cannot be read", o->treeish1);
	}
	file2_str = read_file(o->treeish2);
	if (file2_str == NULL) {
		usage("file cannot be read", o->treeish2);
	}
	check_lg2(
		git_patch_from_buffers(&patch, file1_str, strlen(file1_str), o->treeish1, file2_str, strlen(file2_str), o->treeish2, &o->diffopts),
		"patch buffers", NULL);
	check_lg2(
		git_patch_to_buf(&buf, patch),
		"patch to buf", NULL);
191 192 193 194 195 196

#ifdef GIT_EXPERIMENTAL_SHA256
	check_lg2(
		git_diff_from_buffer(diff, buf.ptr, buf.size, NULL),
		"diff from patch", NULL);
#else
197 198 199
	check_lg2(
		git_diff_from_buffer(diff, buf.ptr, buf.size),
		"diff from patch", NULL);
200 201
#endif

202 203 204 205 206 207
	git_patch_free(patch);
	git_buf_dispose(&buf);
	free(file1_str);
	free(file2_str);
}

208 209 210 211 212 213 214 215 216
static void usage(const char *message, const char *arg)
{
	if (message && arg)
		fprintf(stderr, "%s: %s\n", message, arg);
	else if (message)
		fprintf(stderr, "%s\n", message);
	fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n");
	exit(1);
}
217

218
/** This implements very rudimentary colorized output. */
219
static int color_printer(
220
	const git_diff_delta *delta,
221 222
	const git_diff_hunk *hunk,
	const git_diff_line *line,
223
	void *data)
224 225 226
{
	int *last_color = data, color = 0;

227
	(void)delta; (void)hunk;
228

229
	if (*last_color >= 0) {
230 231 232
		switch (line->origin) {
		case GIT_DIFF_LINE_ADDITION:  color = 3; break;
		case GIT_DIFF_LINE_DELETION:  color = 2; break;
233 234
		case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break;
		case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break;
235 236 237
		case GIT_DIFF_LINE_FILE_HDR:  color = 1; break;
		case GIT_DIFF_LINE_HUNK_HDR:  color = 4; break;
		default: break;
238
		}
239

240 241 242 243 244 245 246 247
		if (color != *last_color) {
			if (*last_color == 1 || color == 1)
				fputs(colors[0], stdout);
			fputs(colors[color], stdout);
			*last_color = color;
		}
	}

248
	return diff_output(delta, hunk, line, stdout);
249 250
}

251
/** Parse arguments as copied from git-diff. */
252
static void parse_opts(struct diff_options *o, int argc, char *argv[])
253
{
254
	struct args_info args = ARGS_INFO_INIT;
255

256 257
	for (args.pos = 1; args.pos < argc; ++args.pos) {
		const char *a = argv[args.pos];
258 259

		if (a[0] != '-') {
260 261 262 263
			if (o->treeish1 == NULL)
				o->treeish1 = a;
			else if (o->treeish2 == NULL)
				o->treeish2 = a;
264 265 266 267
			else
				usage("Only one or two tree identifiers can be provided", NULL);
		}
		else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
268 269
				 !strcmp(a, "--patch")) {
			o->output |= OUTPUT_DIFF;
270
			o->format = GIT_DIFF_FORMAT_PATCH;
271
		}
272
		else if (!strcmp(a, "--cached")) {
273
			o->cache = CACHE_ONLY;
274 275
			if (o->no_index >= 0) usage("--cached and --no-index are incompatible", NULL);
		} else if (!strcmp(a, "--nocache"))
276 277
			o->cache = CACHE_NONE;
		else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name"))
278
			o->format = GIT_DIFF_FORMAT_NAME_ONLY;
279 280
		else if (!strcmp(a, "--name-status") ||
				!strcmp(a, "--format=name-status"))
281
			o->format = GIT_DIFF_FORMAT_NAME_STATUS;
282
		else if (!strcmp(a, "--raw") || !strcmp(a, "--format=raw"))
283
			o->format = GIT_DIFF_FORMAT_RAW;
284 285 286 287
		else if (!strcmp(a, "--format=diff-index")) {
			o->format = GIT_DIFF_FORMAT_RAW;
			o->diffopts.id_abbrev = 40;
		}
288 289 290 291
		else if (!strcmp(a, "--no-index")) {
			o->no_index = 0;
			if (o->cache == CACHE_ONLY) usage("--cached and --no-index are incompatible", NULL);
		} else if (!strcmp(a, "--color"))
292
			o->color = 0;
293
		else if (!strcmp(a, "--no-color"))
294
			o->color = -1;
295
		else if (!strcmp(a, "-R"))
296
			o->diffopts.flags |= GIT_DIFF_REVERSE;
297
		else if (!strcmp(a, "-a") || !strcmp(a, "--text"))
298
			o->diffopts.flags |= GIT_DIFF_FORCE_TEXT;
299
		else if (!strcmp(a, "--ignore-space-at-eol"))
300
			o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
301
		else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change"))
302
			o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
303
		else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space"))
304
			o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
305
		else if (!strcmp(a, "--ignored"))
306
			o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED;
307
		else if (!strcmp(a, "--untracked"))
308
			o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
309 310 311 312
		else if (!strcmp(a, "--patience"))
			o->diffopts.flags |= GIT_DIFF_PATIENCE;
		else if (!strcmp(a, "--minimal"))
			o->diffopts.flags |= GIT_DIFF_MINIMAL;
313
		else if (!strcmp(a, "--stat"))
314
			o->output |= OUTPUT_STAT;
315
		else if (!strcmp(a, "--numstat"))
316
			o->output |= OUTPUT_NUMSTAT;
317
		else if (!strcmp(a, "--shortstat"))
318 319 320
			o->output |= OUTPUT_SHORTSTAT;
		else if (!strcmp(a, "--summary"))
			o->output |= OUTPUT_SUMMARY;
321 322 323 324 325 326 327 328 329 330
		else if (match_uint16_arg(
				&o->findopts.rename_threshold, &args, "-M") ||
			match_uint16_arg(
				&o->findopts.rename_threshold, &args, "--find-renames"))
			o->findopts.flags |= GIT_DIFF_FIND_RENAMES;
		else if (match_uint16_arg(
				&o->findopts.copy_threshold, &args, "-C") ||
			match_uint16_arg(
				&o->findopts.copy_threshold, &args, "--find-copies"))
			o->findopts.flags |= GIT_DIFF_FIND_COPIES;
Russell Belfer committed
331
		else if (!strcmp(a, "--find-copies-harder"))
332 333
			o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
		else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites"))
Russell Belfer committed
334
			/* TODO: parse thresholds */
335
			o->findopts.flags |= GIT_DIFF_FIND_REWRITES;
336
		else if (!match_uint32_arg(
337
				&o->diffopts.context_lines, &args, "-U") &&
338
			!match_uint32_arg(
339
				&o->diffopts.context_lines, &args, "--unified") &&
340
			!match_uint32_arg(
341
				&o->diffopts.interhunk_lines, &args, "--inter-hunk-context") &&
342 343
			!match_uint16_arg(
				&o->diffopts.id_abbrev, &args, "--abbrev") &&
344 345 346 347
			!match_str_arg(&o->diffopts.old_prefix, &args, "--src-prefix") &&
			!match_str_arg(&o->diffopts.new_prefix, &args, "--dst-prefix") &&
			!match_str_arg(&o->dir, &args, "--git-dir"))
			usage("Unknown command line argument", a);
348
	}
349
}
350

351
/** Display diff output with "--stat", "--numstat", or "--shortstat" */
352
static void diff_print_stats(git_diff *diff, struct diff_options *o)
353
{
354
	git_diff_stats *stats;
355
	git_buf b = GIT_BUF_INIT;
356
	git_diff_stats_format_t format = 0;
357

358 359
	check_lg2(
		git_diff_get_stats(&stats, diff), "generating stats for diff", NULL);
360

361 362 363 364 365 366 367 368
	if (o->output & OUTPUT_STAT)
		format |= GIT_DIFF_STATS_FULL;
	if (o->output & OUTPUT_SHORTSTAT)
		format |= GIT_DIFF_STATS_SHORT;
	if (o->output & OUTPUT_NUMSTAT)
		format |= GIT_DIFF_STATS_NUMBER;
	if (o->output & OUTPUT_SUMMARY)
		format |= GIT_DIFF_STATS_INCLUDE_SUMMARY;
369

370 371
	check_lg2(
		git_diff_stats_to_buf(&b, stats, format, 80), "formatting stats", NULL);
372

373
	fputs(b.ptr, stdout);
374

375
	git_buf_dispose(&b);
376
	git_diff_stats_free(stats);
377
}