diff.c 11.1 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
7

8
#include "diff.h"
9 10

#include "git2/version.h"
11 12 13
#include "diff_generate.h"
#include "patch.h"
#include "commit.h"
14
#include "index.h"
15

16 17 18 19 20 21
struct patch_id_args {
	git_hash_ctx ctx;
	git_oid result;
	int first_file;
};

22 23 24 25 26 27 28 29 30 31 32 33 34
GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
{
	const char *str = delta->old_file.path;

	if (!str ||
		delta->status == GIT_DELTA_ADDED ||
		delta->status == GIT_DELTA_RENAMED ||
		delta->status == GIT_DELTA_COPIED)
		str = delta->new_file.path;

	return str;
}

35
int git_diff_delta__cmp(const void *a, const void *b)
36 37
{
	const git_diff_delta *da = a, *db = b;
38
	int val = strcmp(diff_delta__path(da), diff_delta__path(db));
39 40 41
	return val ? val : ((int)da->status - (int)db->status);
}

42 43 44 45 46 47 48
int git_diff_delta__casecmp(const void *a, const void *b)
{
	const git_diff_delta *da = a, *db = b;
	int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
	return val ? val : ((int)da->status - (int)db->status);
}

49
int git_diff__entry_cmp(const void *a, const void *b)
50 51 52 53 54 55 56
{
	const git_index_entry *entry_a = a;
	const git_index_entry *entry_b = b;

	return strcmp(entry_a->path, entry_b->path);
}

57
int git_diff__entry_icmp(const void *a, const void *b)
58 59 60 61 62 63 64
{
	const git_index_entry *entry_a = a;
	const git_index_entry *entry_b = b;

	return strcasecmp(entry_a->path, entry_b->path);
}

65
void git_diff_free(git_diff *diff)
Russell Belfer committed
66 67 68 69
{
	if (!diff)
		return;

70
	GIT_REFCOUNT_DEC(diff, diff->free_fn);
Russell Belfer committed
71 72
}

73
void git_diff_addref(git_diff *diff)
74 75 76 77
{
	GIT_REFCOUNT_INC(diff);
}

Russell Belfer committed
78
size_t git_diff_num_deltas(const git_diff *diff)
79
{
Edward Thomson committed
80
	GIT_ASSERT_ARG(diff);
Russell Belfer committed
81
	return diff->deltas.length;
82 83
}

Russell Belfer committed
84
size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
85 86
{
	size_t i, count = 0;
Russell Belfer committed
87
	const git_diff_delta *delta;
88

Edward Thomson committed
89
	GIT_ASSERT_ARG(diff);
90 91 92 93 94 95 96 97

	git_vector_foreach(&diff->deltas, i, delta) {
		count += (delta->status == type);
	}

	return count;
}

Russell Belfer committed
98 99
const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
{
Edward Thomson committed
100
	GIT_ASSERT_ARG_WITH_RETVAL(diff, NULL);
Russell Belfer committed
101 102 103
	return git_vector_get(&diff->deltas, idx);
}

104
int git_diff_is_sorted_icase(const git_diff *diff)
105
{
Russell Belfer committed
106
	return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
107 108
}

109 110
int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
{
Edward Thomson committed
111
	GIT_ASSERT_ARG(out);
112
	GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
113 114 115 116 117
	out->stat_calls = diff->perf.stat_calls;
	out->oid_calculations = diff->perf.oid_calculations;
	return 0;
}

118 119 120 121 122 123 124 125 126 127 128 129
int git_diff_foreach(
	git_diff *diff,
	git_diff_file_cb file_cb,
	git_diff_binary_cb binary_cb,
	git_diff_hunk_cb hunk_cb,
	git_diff_line_cb data_cb,
	void *payload)
{
	int error = 0;
	git_diff_delta *delta;
	size_t idx;

Edward Thomson committed
130
	GIT_ASSERT_ARG(diff);
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

	git_vector_foreach(&diff->deltas, idx, delta) {
		git_patch *patch;

		/* check flags against patch status */
		if (git_diff_delta__should_skip(&diff->opts, delta))
			continue;

		if ((error = git_patch_from_diff(&patch, diff, idx)) != 0)
			break;

		error = git_patch__invoke_callbacks(patch, file_cb, binary_cb,
						    hunk_cb, data_cb, payload);
		git_patch_free(patch);

		if (error)
			break;
	}

	return error;
}

153
static int diff_format_email_append_header_tobuf(
154 155 156 157
	git_buf *out,
	const git_oid *id,
	const git_signature *author,
	const char *summary,
158
	const char *body,
159 160 161 162 163 164 165 166 167 168 169
	size_t patch_no,
	size_t total_patches,
	bool exclude_patchno_marker)
{
	char idstr[GIT_OID_HEXSZ + 1];
	char date_str[GIT_DATE_RFC2822_SZ];
	int error = 0;

	git_oid_fmt(idstr, id);
	idstr[GIT_OID_HEXSZ] = '\0';

170 171
	if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str),
		&author->when)) < 0)
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
		return error;

	error = git_buf_printf(out,
				"From %s Mon Sep 17 00:00:00 2001\n" \
				"From: %s <%s>\n" \
				"Date: %s\n" \
				"Subject: ",
				idstr,
				author->name, author->email,
				date_str);

	if (error < 0)
		return error;

	if (!exclude_patchno_marker) {
		if (total_patches == 1) {
			error = git_buf_puts(out, "[PATCH] ");
		} else {
190 191
			error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ",
				patch_no, total_patches);
192 193 194 195 196 197 198 199
		}

		if (error < 0)
			return error;
	}

	error = git_buf_printf(out, "%s\n\n", summary);

200 201 202 203 204 205 206
	if (body) {
		git_buf_puts(out, body);

		if (out->ptr[out->size - 1] != '\n')
			git_buf_putc(out, '\n');
	}

207 208 209
	return error;
}

210
static int diff_format_email_append_patches_tobuf(
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
	git_buf *out,
	git_diff *diff)
{
	size_t i, deltas;
	int error = 0;

	deltas = git_diff_num_deltas(diff);

	for (i = 0; i < deltas; ++i) {
		git_patch *patch = NULL;

		if ((error = git_patch_from_diff(&patch, diff, i)) >= 0)
			error = git_patch_to_buf(out, patch);

		git_patch_free(patch);

		if (error < 0)
			break;
	}

	return error;
}

int git_diff_format_email(
	git_buf *out,
	git_diff *diff,
	const git_diff_format_email_options *opts)
{
	git_diff_stats *stats = NULL;
240
	char *summary = NULL, *loc = NULL;
241 242
	bool ignore_marker;
	unsigned int format_flags = 0;
243
	size_t allocsize;
244 245
	int error;

Edward Thomson committed
246 247 248
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(diff);
	GIT_ASSERT_ARG(opts && opts->summary && opts->id && opts->author);
249

250
	GIT_ERROR_CHECK_VERSION(opts,
251 252 253 254 255
		GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION,
		"git_format_email_options");

	ignore_marker = (opts->flags &
		GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0;
256

257
	if (!ignore_marker) {
258
		if (opts->patch_no > opts->total_patches) {
259
			git_error_set(GIT_ERROR_INVALID,
260 261
				"patch %"PRIuZ" out of range. max %"PRIuZ,
				opts->patch_no, opts->total_patches);
262 263 264 265
			return -1;
		}

		if (opts->patch_no == 0) {
266
			git_error_set(GIT_ERROR_INVALID,
267
				"invalid patch no %"PRIuZ". should be >0", opts->patch_no);
268 269 270 271
			return -1;
		}
	}

272 273 274 275 276 277 278
	/* the summary we receive may not be clean.
	 * it could potentially contain new line characters
	 * or not be set, sanitize, */
	if ((loc = strpbrk(opts->summary, "\r\n")) != NULL) {
		size_t offset = 0;

		if ((offset = (loc - opts->summary)) == 0) {
279
			git_error_set(GIT_ERROR_INVALID, "summary is empty");
280
			error = -1;
281
			goto on_error;
282 283
		}

284
		GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, offset, 1);
285
		summary = git__calloc(allocsize, sizeof(char));
286
		GIT_ERROR_CHECK_ALLOC(summary);
287

288 289 290
		strncpy(summary, opts->summary, offset);
	}

291
	error = diff_format_email_append_header_tobuf(out,
292 293
		opts->id, opts->author, summary == NULL ? opts->summary : summary,
		opts->body, opts->patch_no, opts->total_patches, ignore_marker);
294 295 296 297 298 299 300 301

	if (error < 0)
		goto on_error;

	format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;

	if ((error = git_buf_puts(out, "---\n")) < 0 ||
		(error = git_diff_get_stats(&stats, diff)) < 0 ||
302 303
		(error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
		(error = git_buf_putc(out, '\n')) < 0 ||
304
		(error = diff_format_email_append_patches_tobuf(out, diff)) < 0)
305 306 307 308 309
			goto on_error;

	error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");

on_error:
310
	git__free(summary);
311 312 313 314 315 316 317 318 319 320 321
	git_diff_stats_free(stats);

	return error;
}

int git_diff_commit_as_email(
	git_buf *out,
	git_repository *repo,
	git_commit *commit,
	size_t patch_no,
	size_t total_patches,
322
	uint32_t flags,
323 324 325
	const git_diff_options *diff_opts)
{
	git_diff *diff = NULL;
326 327
	git_diff_format_email_options opts =
		GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
328 329
	int error;

Edward Thomson committed
330 331 332
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(repo);
	GIT_ASSERT_ARG(commit);
333 334 335 336 337 338

	opts.flags = flags;
	opts.patch_no = patch_no;
	opts.total_patches = total_patches;
	opts.id = git_commit_id(commit);
	opts.summary = git_commit_summary(commit);
339
	opts.body = git_commit_body(commit);
340 341 342 343 344 345 346 347 348 349 350
	opts.author = git_commit_author(commit);

	if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
		return error;

	error = git_diff_format_email(out, diff, &opts);

	git_diff_free(diff);
	return error;
}

351
int git_diff_options_init(git_diff_options *opts, unsigned int version)
352
{
353 354
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
355
	return 0;
356 357
}

358
#ifndef GIT_DEPRECATE_HARD
359 360 361 362
int git_diff_init_options(git_diff_options *opts, unsigned int version)
{
	return git_diff_options_init(opts, version);
}
363
#endif
364 365

int git_diff_find_options_init(
366
	git_diff_find_options *opts, unsigned int version)
367
{
368 369
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
370
	return 0;
371
}
372

373
#ifndef GIT_DEPRECATE_HARD
374 375 376 377 378
int git_diff_find_init_options(
	git_diff_find_options *opts, unsigned int version)
{
	return git_diff_find_options_init(opts, version);
}
379
#endif
380 381

int git_diff_format_email_options_init(
382
	git_diff_format_email_options *opts, unsigned int version)
383
{
384 385 386
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_format_email_options,
		GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
387
	return 0;
388
}
389

390
#ifndef GIT_DEPRECATE_HARD
391 392 393 394 395
int git_diff_format_email_init_options(
	git_diff_format_email_options *opts, unsigned int version)
{
	return git_diff_format_email_options_init(opts, version);
}
396
#endif
397

398 399 400 401 402 403 404 405 406 407 408 409
static int flush_hunk(git_oid *result, git_hash_ctx *ctx)
{
	git_oid hash;
	unsigned short carry = 0;
	int error, i;

	if ((error = git_hash_final(&hash, ctx)) < 0 ||
	    (error = git_hash_init(ctx)) < 0)
		return error;

	for (i = 0; i < GIT_OID_RAWSZ; i++) {
		carry += result->id[i] + hash.id[i];
410
		result->id[i] = (unsigned char)carry;
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
		carry >>= 8;
	}

	return 0;
}

static void strip_spaces(git_buf *buf)
{
	char *src = buf->ptr, *dst = buf->ptr;
	char c;
	size_t len = 0;

	while ((c = *src++) != '\0') {
		if (!git__isspace(c)) {
			*dst++ = c;
			len++;
		}
	}

	git_buf_truncate(buf, len);
}

433
static int diff_patchid_print_callback_to_buf(
434
	const git_diff_delta *delta,
435 436
	const git_diff_hunk *hunk,
	const git_diff_line *line,
437 438 439 440
	void *payload)
{
	struct patch_id_args *args = (struct patch_id_args *) payload;
	git_buf buf = GIT_BUF_INIT;
441
	int error = 0;
442

443 444 445
	if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL ||
	    line->origin == GIT_DIFF_LINE_ADD_EOFNL ||
	    line->origin == GIT_DIFF_LINE_DEL_EOFNL)
446 447
		goto out;

448 449
	if ((error = git_diff_print_callback__to_buf(delta, hunk,
						     line, &buf)) < 0)
450 451 452 453
		goto out;

	strip_spaces(&buf);

454 455 456 457 458
	if (line->origin == GIT_DIFF_LINE_FILE_HDR &&
	    !args->first_file &&
	    (error = flush_hunk(&args->result, &args->ctx) < 0))
			goto out;

459 460 461
	if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0)
		goto out;

462 463 464
	if (line->origin == GIT_DIFF_LINE_FILE_HDR && args->first_file)
		args->first_file = 0;

465
out:
466
	git_buf_dispose(&buf);
467 468 469
	return error;
}

470
int git_diff_patchid_options_init(git_diff_patchid_options *opts, unsigned int version)
471 472 473 474 475 476 477 478 479 480 481
{
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_INIT);
	return 0;
}

int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts)
{
	struct patch_id_args args;
	int error;

482
	GIT_ERROR_CHECK_VERSION(
483 484 485 486 487 488 489
		opts, GIT_DIFF_PATCHID_OPTIONS_VERSION, "git_diff_patchid_options");

	memset(&args, 0, sizeof(args));
	args.first_file = 1;
	if ((error = git_hash_ctx_init(&args.ctx)) < 0)
		goto out;

490 491
	if ((error = git_diff_print(diff,
				    GIT_DIFF_FORMAT_PATCH_ID,
492
				    diff_patchid_print_callback_to_buf,
493
				    &args)) < 0)
494 495 496 497 498 499 500 501
		goto out;

	if ((error = (flush_hunk(&args.result, &args.ctx))) < 0)
		goto out;

	git_oid_cpy(out, &args.result);

out:
502
	git_hash_ctx_cleanup(&args.ctx);
503 504
	return error;
}