diff_print.c 11.9 KB
Newer Older
1 2 3 4 5 6 7 8
/*
 * 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"
#include "diff.h"
9
#include "diff_patch.h"
10
#include "fileops.h"
11 12

typedef struct {
13
	git_diff *diff;
Russell Belfer committed
14
	git_diff_format_t format;
15
	git_diff_line_cb print_cb;
16 17
	void *payload;
	git_buf *buf;
Russell Belfer committed
18
	uint32_t flags;
19
	int oid_strlen;
20
	git_diff_line line;
21 22 23 24
} diff_print_info;

static int diff_print_info_init(
	diff_print_info *pi,
Russell Belfer committed
25 26 27 28 29
	git_buf *out,
	git_diff *diff,
	git_diff_format_t format,
	git_diff_line_cb cb,
	void *payload)
30 31
{
	pi->diff     = diff;
Russell Belfer committed
32
	pi->format   = format;
33 34 35 36
	pi->print_cb = cb;
	pi->payload  = payload;
	pi->buf      = out;

Russell Belfer committed
37 38 39 40 41 42
	if (diff)
		pi->flags = diff->opts.flags;

	if (diff && diff->opts.oid_abbrev != 0)
		pi->oid_strlen = diff->opts.oid_abbrev;
	else if (!diff || !diff->repo)
43 44 45
		pi->oid_strlen = GIT_ABBREV_DEFAULT;
	else if (git_repository__cvar(
		&pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0)
46 47 48 49 50 51 52 53 54
		return -1;

	pi->oid_strlen += 1; /* for NUL byte */

	if (pi->oid_strlen < 2)
		pi->oid_strlen = 2;
	else if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
		pi->oid_strlen = GIT_OID_HEXSZ + 1;

55 56 57 58 59
	memset(&pi->line, 0, sizeof(pi->line));
	pi->line.old_lineno = -1;
	pi->line.new_lineno = -1;
	pi->line.num_lines  = 1;

60 61 62
	return 0;
}

63
static char diff_pick_suffix(int mode)
64 65 66
{
	if (S_ISDIR(mode))
		return '/';
67
	else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
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 97
		/* in git, modes are very regular, so we must have 0100755 mode */
		return '*';
	else
		return ' ';
}

char git_diff_status_char(git_delta_t status)
{
	char code;

	switch (status) {
	case GIT_DELTA_ADDED:     code = 'A'; break;
	case GIT_DELTA_DELETED:   code = 'D'; break;
	case GIT_DELTA_MODIFIED:  code = 'M'; break;
	case GIT_DELTA_RENAMED:   code = 'R'; break;
	case GIT_DELTA_COPIED:    code = 'C'; break;
	case GIT_DELTA_IGNORED:   code = 'I'; break;
	case GIT_DELTA_UNTRACKED: code = '?'; break;
	default:                  code = ' '; break;
	}

	return code;
}

static int callback_error(void)
{
	giterr_clear();
	return GIT_EUSER;
}

Russell Belfer committed
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
static int diff_print_one_name_only(
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
	git_buf *out = pi->buf;

	GIT_UNUSED(progress);

	if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 &&
		delta->status == GIT_DELTA_UNMODIFIED)
		return 0;

	git_buf_clear(out);

	if (git_buf_puts(out, delta->new_file.path) < 0 ||
		git_buf_putc(out, '\n'))
		return -1;

116 117 118 119 120
	pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
	pi->line.content     = git_buf_cstr(out);
	pi->line.content_len = git_buf_len(out);

	if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
Russell Belfer committed
121 122 123 124 125 126
		return callback_error();

	return 0;
}

static int diff_print_one_name_status(
127 128 129
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
130
	git_buf *out = pi->buf;
131
	char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
132 133
	int (*strcomp)(const char *, const char *) =
		pi->diff ? pi->diff->strcomp : git__strcmp;
134 135 136

	GIT_UNUSED(progress);

Russell Belfer committed
137
	if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
138 139
		return 0;

140 141
	old_suffix = diff_pick_suffix(delta->old_file.mode);
	new_suffix = diff_pick_suffix(delta->new_file.mode);
142

143
	git_buf_clear(out);
144 145

	if (delta->old_file.path != delta->new_file.path &&
146
		strcomp(delta->old_file.path,delta->new_file.path) != 0)
147
		git_buf_printf(out, "%c\t%s%c %s%c\n", code,
148 149 150
			delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
	else if (delta->old_file.mode != delta->new_file.mode &&
		delta->old_file.mode != 0 && delta->new_file.mode != 0)
151 152
		git_buf_printf(out, "%c\t%s%c %s%c\n", code,
			delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
153
	else if (old_suffix != ' ')
154
		git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
155
	else
156
		git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
157

158
	if (git_buf_oom(out))
159 160
		return -1;

161 162 163 164 165
	pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
	pi->line.content     = git_buf_cstr(out);
	pi->line.content_len = git_buf_len(out);

	if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
166 167 168 169 170
		return callback_error();

	return 0;
}

171
static int diff_print_one_raw(
172 173 174
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
175
	git_buf *out = pi->buf;
176 177 178 179 180
	char code = git_diff_status_char(delta->status);
	char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];

	GIT_UNUSED(progress);

Russell Belfer committed
181
	if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
182 183
		return 0;

184
	git_buf_clear(out);
185 186 187 188 189

	git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
	git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);

	git_buf_printf(
190
		out, ":%06o %06o %s... %s... %c",
191 192 193
		delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);

	if (delta->similarity > 0)
194
		git_buf_printf(out, "%03u", delta->similarity);
195

196
	if (delta->old_file.path != delta->new_file.path)
197
		git_buf_printf(
198
			out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
199 200
	else
		git_buf_printf(
201
			out, "\t%s\n", delta->old_file.path ?
202 203
			delta->old_file.path : delta->new_file.path);

204
	if (git_buf_oom(out))
205 206
		return -1;

207 208 209 210 211
	pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
	pi->line.content     = git_buf_cstr(out);
	pi->line.content_len = git_buf_len(out);

	if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
212 213 214 215 216
		return callback_error();

	return 0;
}

217 218
static int diff_print_oid_range(
	git_buf *out, const git_diff_delta *delta, int oid_strlen)
219 220 221
{
	char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];

222 223
	git_oid_tostr(start_oid, oid_strlen, &delta->old_file.oid);
	git_oid_tostr(end_oid, oid_strlen, &delta->new_file.oid);
224 225 226

	/* TODO: Match git diff more closely */
	if (delta->old_file.mode == delta->new_file.mode) {
227
		git_buf_printf(out, "index %s..%s %o\n",
228 229 230
			start_oid, end_oid, delta->old_file.mode);
	} else {
		if (delta->old_file.mode == 0) {
231
			git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
232
		} else if (delta->new_file.mode == 0) {
233
			git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
234
		} else {
235 236
			git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
			git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
237
		}
238
		git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
239 240
	}

241
	if (git_buf_oom(out))
242 243 244 245 246
		return -1;

	return 0;
}

247
static int diff_delta_format_with_paths(
248 249 250 251
	git_buf *out,
	const git_diff_delta *delta,
	const char *oldpfx,
	const char *newpfx,
252
	const char *template)
253 254 255 256
{
	const char *oldpath = delta->old_file.path;
	const char *newpath = delta->new_file.path;

257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
	if (git_oid_iszero(&delta->old_file.oid)) {
		oldpfx = "";
		oldpath = "/dev/null";
	}
	if (git_oid_iszero(&delta->new_file.oid)) {
		newpfx = "";
		newpath = "/dev/null";
	}

	return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
}

int git_diff_delta__format_file_header(
	git_buf *out,
	const git_diff_delta *delta,
	const char *oldpfx,
	const char *newpfx,
	int oid_strlen)
{
276 277 278 279
	if (!oldpfx)
		oldpfx = DIFF_OLD_PREFIX_DEFAULT;
	if (!newpfx)
		newpfx = DIFF_NEW_PREFIX_DEFAULT;
280 281
	if (!oid_strlen)
		oid_strlen = GIT_ABBREV_DEFAULT + 1;
282

283 284 285
	git_buf_clear(out);

	git_buf_printf(out, "diff --git %s%s %s%s\n",
286
		oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
287

288
	if (diff_print_oid_range(out, delta, oid_strlen) < 0)
289 290
		return -1;

291 292 293
	if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
		diff_delta_format_with_paths(
			out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
294

295 296
	return git_buf_oom(out) ? -1 : 0;
}
297

298 299 300 301
static int diff_print_patch_file(
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
302 303 304 305
	const char *oldpfx =
		pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
	const char *newpfx =
		pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
306 307

	GIT_UNUSED(progress);
308

309 310 311 312
	if (S_ISDIR(delta->new_file.mode) ||
		delta->status == GIT_DELTA_UNMODIFIED ||
		delta->status == GIT_DELTA_IGNORED ||
		(delta->status == GIT_DELTA_UNTRACKED &&
Russell Belfer committed
313
		 (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
314 315
		return 0;

316 317
	if (git_diff_delta__format_file_header(
			pi->buf, delta, oldpfx, newpfx, pi->oid_strlen) < 0)
318 319
		return -1;

320 321 322 323 324
	pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
	pi->line.content     = git_buf_cstr(pi->buf);
	pi->line.content_len = git_buf_len(pi->buf);

	if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
325 326 327 328 329 330 331 332 333 334 335 336
		return callback_error();

	if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
		return 0;

	git_buf_clear(pi->buf);

	if (diff_delta_format_with_paths(
			pi->buf, delta, oldpfx, newpfx,
			"Binary files %s%s and %s%s differ\n") < 0)
		return -1;

337 338 339 340 341 342
	pi->line.origin      = GIT_DIFF_LINE_BINARY;
	pi->line.content     = git_buf_cstr(pi->buf);
	pi->line.content_len = git_buf_len(pi->buf);
	pi->line.num_lines   = 1;

	if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
343 344 345 346 347
		return callback_error();

	return 0;
}

348
static int diff_print_patch_hunk(
349
	const git_diff_delta *d,
350
	const git_diff_hunk *h,
351 352 353 354 355 356 357
	void *data)
{
	diff_print_info *pi = data;

	if (S_ISDIR(d->new_file.mode))
		return 0;

358 359 360
	pi->line.origin      = GIT_DIFF_LINE_HUNK_HDR;
	pi->line.content     = h->header;
	pi->line.content_len = h->header_len;
361

362
	if (pi->print_cb(d, h, &pi->line, pi->payload))
363 364 365 366 367
		return callback_error();

	return 0;
}

368
static int diff_print_patch_line(
369
	const git_diff_delta *delta,
370 371
	const git_diff_hunk *hunk,
	const git_diff_line *line,
372 373 374 375 376 377 378
	void *data)
{
	diff_print_info *pi = data;

	if (S_ISDIR(delta->new_file.mode))
		return 0;

379
	if (pi->print_cb(delta, hunk, line, pi->payload))
380 381 382 383 384
		return callback_error();

	return 0;
}

Russell Belfer committed
385 386
/* print a git_diff to an output callback */
int git_diff_print(
387
	git_diff *diff,
Russell Belfer committed
388
	git_diff_format_t format,
389
	git_diff_line_cb print_cb,
390 391 392 393 394
	void *payload)
{
	int error;
	git_buf buf = GIT_BUF_INIT;
	diff_print_info pi;
Russell Belfer committed
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
	git_diff_file_cb print_file = NULL;
	git_diff_hunk_cb print_hunk = NULL;
	git_diff_line_cb print_line = NULL;

	switch (format) {
	case GIT_DIFF_FORMAT_PATCH:
		print_file = diff_print_patch_file;
		print_hunk = diff_print_patch_hunk;
		print_line = diff_print_patch_line;
		break;
	case GIT_DIFF_FORMAT_PATCH_HEADER:
		print_file = diff_print_patch_file;
		break;
	case GIT_DIFF_FORMAT_RAW:
		print_file = diff_print_one_raw;
		break;
	case GIT_DIFF_FORMAT_NAME_ONLY:
		print_file = diff_print_one_name_only;
		break;
	case GIT_DIFF_FORMAT_NAME_STATUS:
		print_file = diff_print_one_name_status;
		break;
	default:
		giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format);
		return -1;
	}
421

Russell Belfer committed
422 423
	if (!(error = diff_print_info_init(
			&pi, &buf, diff, format, print_cb, payload)))
424
		error = git_diff_foreach(
Russell Belfer committed
425
			diff, print_file, print_hunk, print_line, &pi);
426 427 428 429 430 431

	git_buf_free(&buf);

	return error;
}

432 433 434 435
/* print a git_patch to an output callback */
int git_patch_print(
	git_patch *patch,
	git_diff_line_cb print_cb,
436 437 438 439 440 441 442 443 444
	void *payload)
{
	int error;
	git_buf temp = GIT_BUF_INIT;
	diff_print_info pi;

	assert(patch && print_cb);

	if (!(error = diff_print_info_init(
Russell Belfer committed
445 446
			&pi, &temp, git_patch__diff(patch),
			GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
447
		error = git_patch__invoke_callbacks(
448 449
			patch, diff_print_patch_file, diff_print_patch_hunk,
			diff_print_patch_line, &pi);
450 451 452 453 454 455

	git_buf_free(&temp);

	return error;
}

456 457
static int diff_print_to_buffer_cb(
	const git_diff_delta *delta,
458 459
	const git_diff_hunk *hunk,
	const git_diff_line *line,
460 461 462
	void *payload)
{
	git_buf *output = payload;
463 464 465 466 467 468 469 470
	GIT_UNUSED(delta); GIT_UNUSED(hunk);

	if (line->origin == GIT_DIFF_LINE_ADDITION ||
		line->origin == GIT_DIFF_LINE_DELETION ||
		line->origin == GIT_DIFF_LINE_CONTEXT)
		git_buf_putc(output, line->origin);

	return git_buf_put(output, line->content, line->content_len);
471 472
}

473 474
/* print a git_patch to a string buffer */
int git_patch_to_str(
475
	char **string,
476
	git_patch *patch)
477 478 479 480
{
	int error;
	git_buf output = GIT_BUF_INIT;

481
	error = git_patch_print(patch, diff_print_to_buffer_cb, &output);
482 483 484 485 486 487 488 489 490 491 492

	/* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,
	 * meaning a memory allocation failure, so just map to -1...
	 */
	if (error == GIT_EUSER)
		error = -1;

	*string = git_buf_detach(&output);

	return error;
}