diff.c 9.64 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
/** The 'opts' struct captures all the various parsed command line options. */
37 38 39 40 41
struct opts {
	git_diff_options diffopts;
	git_diff_find_options findopts;
	int color;
	int cached;
42
	int numstat;
43
	int shortstat;
44 45 46 47 48 49
	git_diff_format_t format;
	const char *treeish1;
	const char *treeish2;
	const char *dir;
};

50
/** These functions are implemented at the end */
51
static void parse_opts(struct opts *o, int argc, char *argv[]);
52 53
static int color_printer(
	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 60 61 62 63
	git_repository *repo = NULL;
	git_tree *t1 = NULL, *t2 = NULL;
	git_diff *diff;
	struct opts o = {
		GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
64
		-1, 0, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
65 66 67 68 69 70 71 72 73
	};

	git_threads_init();

	parse_opts(&o, argc, argv);

	check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL),
		"Could not open repository", o.dir);

74 75 76
	/**
	 * Possible argument patterns:
	 *
77 78 79
	 *  * &lt;sha1&gt; &lt;sha2&gt;
	 *  * &lt;sha1&gt; --cached
	 *  * &lt;sha1&gt;
80 81
	 *  * --cached
	 *  * nothing
82
	 *
83
	 * Currently ranged arguments like &lt;sha1&gt;..&lt;sha2&gt; and &lt;sha1&gt;...&lt;sha2&gt;
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
	 * are not supported in this example
	 */

	if (o.treeish1)
		treeish_to_tree(&t1, repo, o.treeish1);
	if (o.treeish2)
		treeish_to_tree(&t2, repo, o.treeish2);

	if (t1 && t2)
		check_lg2(
			git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts),
			"diff trees", NULL);
	else if (t1 && o.cached)
		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),
			"diff tree to working directory", NULL);
	else if (o.cached) {
		treeish_to_tree(&t1, repo, "HEAD");
		check_lg2(
			git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
			"diff tree to index", NULL);
109
	}
110 111 112 113 114
	else
		check_lg2(
			git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
			"diff index to working directory", NULL);

115
	/** Apply rename and copy detection if requested. */
116 117 118 119 120 121

	if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0)
		check_lg2(
			git_diff_find_similar(diff, &o.findopts),
			"finding renames and copies", NULL);

122
	/** Generate simple output using libgit2 display helper. */
123

124 125
	if (o.numstat == 1)
		diff_print_numstat(diff);
126 127
	else if (o.shortstat == 1)
		diff_print_shortstat(diff);
128 129 130
	else {
		if (o.color >= 0)
			fputs(colors[0], stdout);
131

132 133 134
		check_lg2(
			git_diff_print(diff, o.format, color_printer, &o.color),
			"displaying diff", NULL);
135

136 137 138
		if (o.color >= 0)
			fputs(colors[0], stdout);
	}
139

140
	/** Cleanup before exiting. */
141 142 143 144 145 146 147 148 149

	git_diff_free(diff);
	git_tree_free(t1);
	git_tree_free(t2);
	git_repository_free(repo);

	git_threads_shutdown();

	return 0;
150 151
}

152 153 154 155 156 157 158 159 160
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);
}
161

162
/** This implements very rudimentary colorized output. */
163
static int color_printer(
164
	const git_diff_delta *delta,
165 166
	const git_diff_hunk *hunk,
	const git_diff_line *line,
167
	void *data)
168 169 170
{
	int *last_color = data, color = 0;

171
	(void)delta; (void)hunk;
172

173
	if (*last_color >= 0) {
174 175 176
		switch (line->origin) {
		case GIT_DIFF_LINE_ADDITION:  color = 3; break;
		case GIT_DIFF_LINE_DELETION:  color = 2; break;
177 178
		case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break;
		case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break;
179 180 181
		case GIT_DIFF_LINE_FILE_HDR:  color = 1; break;
		case GIT_DIFF_LINE_HUNK_HDR:  color = 4; break;
		default: break;
182
		}
183

184 185 186 187 188 189 190 191
		if (color != *last_color) {
			if (*last_color == 1 || color == 1)
				fputs(colors[0], stdout);
			fputs(colors[color], stdout);
			*last_color = color;
		}
	}

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 202
	for (args.pos = 1; args.pos < argc; ++args.pos) {
		const char *a = argv[args.pos];
203 204

		if (a[0] != '-') {
205 206 207 208
			if (o->treeish1 == NULL)
				o->treeish1 = a;
			else if (o->treeish2 == NULL)
				o->treeish2 = a;
209 210 211 212 213
			else
				usage("Only one or two tree identifiers can be provided", NULL);
		}
		else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
			!strcmp(a, "--patch"))
214
			o->format = GIT_DIFF_FORMAT_PATCH;
215
		else if (!strcmp(a, "--cached"))
216
			o->cached = 1;
Russell Belfer committed
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;
Russell Belfer committed
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 242 243 244
		else if (!strcmp(a, "--patience"))
			o->diffopts.flags |= GIT_DIFF_PATIENCE;
		else if (!strcmp(a, "--minimal"))
			o->diffopts.flags |= GIT_DIFF_MINIMAL;
245 246
		else if (!strcmp(a, "--numstat"))
			o->numstat = 1;
247 248
		else if (!strcmp(a, "--shortstat"))
			o->shortstat = 1;
249 250 251 252 253 254 255 256 257 258
		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
259
		else if (!strcmp(a, "--find-copies-harder"))
260 261
			o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
		else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites"))
Russell Belfer committed
262
			/* TODO: parse thresholds */
263 264 265 266 267 268 269 270 271 272 273
			o->findopts.flags |= GIT_DIFF_FIND_REWRITES;
		else if (!match_uint16_arg(
				&o->diffopts.context_lines, &args, "-U") &&
			!match_uint16_arg(
				&o->diffopts.context_lines, &args, "--unified") &&
			!match_uint16_arg(
				&o->diffopts.interhunk_lines, &args, "--inter-hunk-context") &&
			!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);
274
	}
275
}
276 277 278 279

/** Display diff output with "--numstat".*/
static void diff_print_numstat(git_diff *diff)
{
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 297 298 299

		printf("%ld\t%ld\t%s\n",
			   (long)nadditions, (long)ndeletions, delta->new_file.path);

		git_patch_free(patch);
300 301
	}
}
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330

/** Display diff output with "--shortstat".*/
static void diff_print_shortstat(git_diff *diff)
{
	git_patch *patch;
	size_t d, ndeltas = git_diff_num_deltas(diff);
	size_t nadditions, ndeletions;
	long nadditions_sum, ndeletions_sum;

	nadditions_sum = 0;
	ndeletions_sum = 0;

	for (d = 0; d < ndeltas; d++){
		check_lg2(
			git_patch_from_diff(&patch, diff, d),
			"generating patch from diff", NULL);

		check_lg2(
			git_patch_line_stats(NULL, &nadditions, &ndeletions, patch),
			"generating the number of additions and deletions", NULL);

		nadditions_sum += nadditions;
		ndeletions_sum += ndeletions;

		git_patch_free(patch);
	}

	if (ndeltas) {

Sun He committed
331
	    printf(" %ld ", (long)ndeltas);
332 333 334 335 336 337 338 339 340 341 342
	    printf("%s", 1==ndeltas ? "file changed" : "files changed");

	    if(nadditions_sum) {
		printf(", %ld ",nadditions_sum);
		printf("%s", 1==nadditions_sum ? "insertion(+)" : "insertions(+)");
	    }

	    if(ndeletions_sum) {
		printf(", %ld ",ndeletions_sum);
		printf("%s", 1==ndeletions_sum ? "deletion(-)" : "deletions(-)");
	    }
Sun He committed
343
	    printf("\n");
344 345
	}
}