diff.c 7.05 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

#include "common.h"

/*
 * 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 */
};
28

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
/* this implements very rudimentary colorized output */
static int color_printer(
	const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);

/* the 'opts' struct captures all the various parsed command line options */
struct opts {
	git_diff_options diffopts;
	git_diff_find_options findopts;
	int color;
	int cached;
	git_diff_format_t format;
	const char *treeish1;
	const char *treeish2;
	const char *dir;
};

static void parse_opts(struct opts *o, int argc, char *argv[]);
46

47
int main(int argc, char *argv[])
48
{
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
	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,
		-1, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
	};

	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);

	/* Possible argument patterns:
	 *   <sha1> <sha2>
	 *   <sha1> --cached
	 *   <sha1>
	 *   --cached
	 *   nothing
	 *
	 * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2>
	 * 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);
97
	}
98 99 100 101 102 103 104 105 106 107 108 109 110
	else
		check_lg2(
			git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
			"diff index to working directory", NULL);

	/* apply rename and copy detection if requested */

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

	/* generate simple output using libgit2 display helper */
111

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
	if (o.color >= 0)
		fputs(colors[0], stdout);

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

	if (o.color >= 0)
		fputs(colors[0], stdout);

	/* cleanup before exiting */

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

	git_threads_shutdown();

	return 0;
132 133
}

134 135 136 137 138 139 140 141 142
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);
}
143

144
static int color_printer(
145
	const git_diff_delta *delta,
146 147
	const git_diff_hunk *hunk,
	const git_diff_line *line,
148
	void *data)
149 150 151
{
	int *last_color = data, color = 0;

152
	(void)delta; (void)hunk;
153

154
	if (*last_color >= 0) {
155 156 157
		switch (line->origin) {
		case GIT_DIFF_LINE_ADDITION:  color = 3; break;
		case GIT_DIFF_LINE_DELETION:  color = 2; break;
158 159
		case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break;
		case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break;
160 161 162
		case GIT_DIFF_LINE_FILE_HDR:  color = 1; break;
		case GIT_DIFF_LINE_HUNK_HDR:  color = 4; break;
		default: break;
163
		}
164

165 166 167 168 169 170 171 172
		if (color != *last_color) {
			if (*last_color == 1 || color == 1)
				fputs(colors[0], stdout);
			fputs(colors[color], stdout);
			*last_color = color;
		}
	}

173
	return diff_output(delta, hunk, line, stdout);
174 175
}

176
static void parse_opts(struct opts *o, int argc, char *argv[])
177
{
178
	struct args_info args = ARGS_INFO_INIT;
179

180 181
	/* parse arguments as copied from git-diff */

182 183
	for (args.pos = 1; args.pos < argc; ++args.pos) {
		const char *a = argv[args.pos];
184 185

		if (a[0] != '-') {
186 187 188 189
			if (o->treeish1 == NULL)
				o->treeish1 = a;
			else if (o->treeish2 == NULL)
				o->treeish2 = a;
190 191 192 193 194
			else
				usage("Only one or two tree identifiers can be provided", NULL);
		}
		else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
			!strcmp(a, "--patch"))
195
			o->format = GIT_DIFF_FORMAT_PATCH;
196
		else if (!strcmp(a, "--cached"))
197
			o->cached = 1;
Russell Belfer committed
198
		else if (!strcmp(a, "--name-only"))
199
			o->format = GIT_DIFF_FORMAT_NAME_ONLY;
200
		else if (!strcmp(a, "--name-status"))
201
			o->format = GIT_DIFF_FORMAT_NAME_STATUS;
Russell Belfer committed
202
		else if (!strcmp(a, "--raw"))
203
			o->format = GIT_DIFF_FORMAT_RAW;
204
		else if (!strcmp(a, "--color"))
205
			o->color = 0;
206
		else if (!strcmp(a, "--no-color"))
207
			o->color = -1;
208
		else if (!strcmp(a, "-R"))
209
			o->diffopts.flags |= GIT_DIFF_REVERSE;
210
		else if (!strcmp(a, "-a") || !strcmp(a, "--text"))
211
			o->diffopts.flags |= GIT_DIFF_FORCE_TEXT;
212
		else if (!strcmp(a, "--ignore-space-at-eol"))
213
			o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
214
		else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change"))
215
			o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
216
		else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space"))
217
			o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
218
		else if (!strcmp(a, "--ignored"))
219
			o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED;
220
		else if (!strcmp(a, "--untracked"))
221 222 223 224 225 226 227 228 229 230 231
			o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
		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
232
		else if (!strcmp(a, "--find-copies-harder"))
233 234
			o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
		else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites"))
Russell Belfer committed
235
			/* TODO: parse thresholds */
236 237 238 239 240 241 242 243 244 245 246
			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);
247
	}
248
}