patch.c 20.6 KB
Newer Older
1
#include "clar_libgit2.h"
2 3
#include "git2/sys/repository.h"

4
#include "diff_helpers.h"
5 6
#include "repository.h"
#include "buf_text.h"
7 8 9 10 11 12 13 14 15 16 17 18

static git_repository *g_repo = NULL;

void test_diff_patch__initialize(void)
{
}

void test_diff_patch__cleanup(void)
{
	cl_git_sandbox_cleanup();
}

19
#define EXPECTED_HEADER "diff --git a/subdir.txt b/subdir.txt\n" \
20 21 22 23 24
	"deleted file mode 100644\n" \
	"index e8ee89e..0000000\n" \
	"--- a/subdir.txt\n" \
	"+++ /dev/null\n"

25 26
#define EXPECTED_HUNK "@@ -1,2 +0,0 @@\n"

27
static int check_removal_cb(
28
	const git_diff_delta *delta,
29 30
	const git_diff_hunk *hunk,
	const git_diff_line *line,
31
	void *payload)
32
{
33
	switch (line->origin) {
34
	case GIT_DIFF_LINE_FILE_HDR:
35 36
		cl_assert_equal_s(EXPECTED_HEADER, line->content);
		cl_assert(hunk == NULL);
37 38 39
		goto check_delta;

	case GIT_DIFF_LINE_HUNK_HDR:
40
		cl_assert_equal_s(EXPECTED_HUNK, line->content);
41
		goto check_hunk;
42 43 44

	case GIT_DIFF_LINE_CONTEXT:
	case GIT_DIFF_LINE_DELETION:
45 46
		if (payload != NULL)
			return *(int *)payload;
47
		goto check_hunk;
48 49 50 51 52 53

	default:
		/* unexpected code path */
		return -1;
	}

54 55 56 57 58 59
check_hunk:
	cl_assert(hunk != NULL);
	cl_assert_equal_i(1, hunk->old_start);
	cl_assert_equal_i(2, hunk->old_lines);
	cl_assert_equal_i(0, hunk->new_start);
	cl_assert_equal_i(0, hunk->new_lines);
60

61
check_delta:
62 63
	cl_assert_equal_s("subdir.txt", delta->old_file.path);
	cl_assert_equal_s("subdir.txt", delta->new_file.path);
64
	cl_assert_equal_i(GIT_DELTA_DELETED, delta->status);
65

66
	return 0;
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
}

void test_diff_patch__can_properly_display_the_removal_of_a_file(void)
{
	/*
	* $ git diff 26a125e..735b6a2
	* diff --git a/subdir.txt b/subdir.txt
	* deleted file mode 100644
	* index e8ee89e..0000000
	* --- a/subdir.txt
	* +++ /dev/null
	* @@ -1,2 +0,0 @@
	* -Is it a bird?
	* -Is it a plane?
	*/

	const char *one_sha = "26a125e";
	const char *another_sha = "735b6a2";
	git_tree *one, *another;
86
	git_diff *diff;
87

88 89
	g_repo = cl_git_sandbox_init("status");

90 91 92
	one = resolve_commit_oid_to_tree(g_repo, one_sha);
	another = resolve_commit_oid_to_tree(g_repo, another_sha);

93
	cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL));
94

Russell Belfer committed
95 96
	cl_git_pass(git_diff_print(
		diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, NULL));
97

98
	git_diff_free(diff);
99 100 101 102

	git_tree_free(another);
	git_tree_free(one);
}
103

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
void test_diff_patch__can_cancel_diff_print(void)
{
	const char *one_sha = "26a125e";
	const char *another_sha = "735b6a2";
	git_tree *one, *another;
	git_diff *diff;
	int fail_with;

	g_repo = cl_git_sandbox_init("status");

	one = resolve_commit_oid_to_tree(g_repo, one_sha);
	another = resolve_commit_oid_to_tree(g_repo, another_sha);

	cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL));

	fail_with = -2323;

	cl_git_fail_with(git_diff_print(
		diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with),
		fail_with);

	fail_with = 45;

	cl_git_fail_with(git_diff_print(
		diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with),
		fail_with);

	git_diff_free(diff);

	git_tree_free(another);
	git_tree_free(one);
}

137 138 139 140 141
void test_diff_patch__to_string(void)
{
	const char *one_sha = "26a125e";
	const char *another_sha = "735b6a2";
	git_tree *one, *another;
142 143
	git_diff *diff;
	git_patch *patch;
Nicolas Hake committed
144
	git_buf buf = GIT_BUF_INIT;
145 146
	const char *expected = "diff --git a/subdir.txt b/subdir.txt\ndeleted file mode 100644\nindex e8ee89e..0000000\n--- a/subdir.txt\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-Is it a bird?\n-Is it a plane?\n";

147 148
	g_repo = cl_git_sandbox_init("status");

149 150 151
	one = resolve_commit_oid_to_tree(g_repo, one_sha);
	another = resolve_commit_oid_to_tree(g_repo, another_sha);

152
	cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL));
153

154
	cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
155

Russell Belfer committed
156
	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
157

Nicolas Hake committed
158
	cl_git_pass(git_patch_to_buf(&buf, patch));
159

Nicolas Hake committed
160
	cl_assert_equal_s(expected, buf.ptr);
161

162 163 164 165
	cl_assert_equal_sz(31, git_patch_size(patch, 0, 0, 0));
	cl_assert_equal_sz(31, git_patch_size(patch, 1, 0, 0));
	cl_assert_equal_sz(31 + 16, git_patch_size(patch, 1, 1, 0));
	cl_assert_equal_sz(strlen(expected), git_patch_size(patch, 1, 1, 1));
166

Nicolas Hake committed
167
	git_buf_free(&buf);
168 169
	git_patch_free(patch);
	git_diff_free(diff);
170 171 172
	git_tree_free(another);
	git_tree_free(one);
}
173

174 175 176 177 178
void test_diff_patch__config_options(void)
{
	const char *one_sha = "26a125e"; /* current HEAD */
	git_tree *one;
	git_config *cfg;
179 180
	git_diff *diff;
	git_patch *patch;
Nicolas Hake committed
181
	git_buf buf = GIT_BUF_INIT;
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
	char *onefile = "staged_changes_modified_file";
	const char *expected1 = "diff --git c/staged_changes_modified_file i/staged_changes_modified_file\nindex 70bd944..906ee77 100644\n--- c/staged_changes_modified_file\n+++ i/staged_changes_modified_file\n@@ -1 +1,2 @@\n staged_changes_modified_file\n+staged_changes_modified_file\n";
	const char *expected2 = "diff --git i/staged_changes_modified_file w/staged_changes_modified_file\nindex 906ee77..011c344 100644\n--- i/staged_changes_modified_file\n+++ w/staged_changes_modified_file\n@@ -1,2 +1,3 @@\n staged_changes_modified_file\n staged_changes_modified_file\n+staged_changes_modified_file\n";
	const char *expected3 = "diff --git staged_changes_modified_file staged_changes_modified_file\nindex 906ee77..011c344 100644\n--- staged_changes_modified_file\n+++ staged_changes_modified_file\n@@ -1,2 +1,3 @@\n staged_changes_modified_file\n staged_changes_modified_file\n+staged_changes_modified_file\n";
	const char *expected4 = "diff --git staged_changes_modified_file staged_changes_modified_file\nindex 70bd9443ada0..906ee7711f4f 100644\n--- staged_changes_modified_file\n+++ staged_changes_modified_file\n@@ -1 +1,2 @@\n staged_changes_modified_file\n+staged_changes_modified_file\n";

	g_repo = cl_git_sandbox_init("status");
	cl_git_pass(git_repository_config(&cfg, g_repo));
	one = resolve_commit_oid_to_tree(g_repo, one_sha);
	opts.pathspec.count = 1;
	opts.pathspec.strings = &onefile;


	cl_git_pass(git_config_set_string(cfg, "diff.mnemonicprefix", "true"));

	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts));

	cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
Russell Belfer committed
201
	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
Nicolas Hake committed
202 203
	cl_git_pass(git_patch_to_buf(&buf, patch));
	cl_assert_equal_s(expected1, buf.ptr);
204

Nicolas Hake committed
205
	git_buf_clear(&buf);
206 207
	git_patch_free(patch);
	git_diff_free(diff);
208 209 210 211

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));

	cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
Russell Belfer committed
212
	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
Nicolas Hake committed
213 214
	cl_git_pass(git_patch_to_buf(&buf, patch));
	cl_assert_equal_s(expected2, buf.ptr);
215

Nicolas Hake committed
216
	git_buf_clear(&buf);
217 218
	git_patch_free(patch);
	git_diff_free(diff);
219 220 221 222 223 224 225


	cl_git_pass(git_config_set_string(cfg, "diff.noprefix", "true"));

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));

	cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
Russell Belfer committed
226
	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
Nicolas Hake committed
227 228
	cl_git_pass(git_patch_to_buf(&buf, patch));
	cl_assert_equal_s(expected3, buf.ptr);
229

Nicolas Hake committed
230
	git_buf_clear(&buf);
231 232
	git_patch_free(patch);
	git_diff_free(diff);
233 234 235 236 237 238 239


	cl_git_pass(git_config_set_int32(cfg, "core.abbrev", 12));

	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts));

	cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
Russell Belfer committed
240
	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
Nicolas Hake committed
241 242
	cl_git_pass(git_patch_to_buf(&buf, patch));
	cl_assert_equal_s(expected4, buf.ptr);
243

Nicolas Hake committed
244
	git_buf_clear(&buf);
245 246
	git_patch_free(patch);
	git_diff_free(diff);
247

Nicolas Hake committed
248
	git_buf_free(&buf);
249 250 251 252
	git_tree_free(one);
	git_config_free(cfg);
}

253 254
void test_diff_patch__hunks_have_correct_line_numbers(void)
{
255
	git_config *cfg;
256
	git_tree *head;
257
	git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
258 259
	git_diff *diff;
	git_patch *patch;
260
	const git_diff_delta *delta;
261 262 263
	const git_diff_hunk *hunk;
	const git_diff_line *line;
	size_t hunklen;
264
	git_buf old_content = GIT_BUF_INIT, actual = GIT_BUF_INIT;
265
	const char *new_content = "The Song of Seven Cities\n------------------------\n\nI WAS Lord of Cities very sumptuously builded.\nSeven roaring Cities paid me tribute from afar.\nIvory their outposts were--the guardrooms of them gilded,\nAnd garrisoned with Amazons invincible in war.\n\nThis is some new text;\nNot as good as the old text;\nBut here it is.\n\nSo they warred and trafficked only yesterday, my Cities.\nTo-day there is no mark or mound of where my Cities stood.\nFor the River rose at midnight and it washed away my Cities.\nThey are evened with Atlantis and the towns before the Flood.\n\nRain on rain-gorged channels raised the water-levels round them,\nFreshet backed on freshet swelled and swept their world from sight,\nTill the emboldened floods linked arms and, flashing forward, drowned them--\nDrowned my Seven Cities and their peoples in one night!\n\nLow among the alders lie their derelict foundations,\nThe beams wherein they trusted and the plinths whereon they built--\nMy rulers and their treasure and their unborn populations,\nDead, destroyed, aborted, and defiled with mud and silt!\n\nAnother replacement;\nBreaking up the poem;\nGenerating some hunks.\n\nTo the sound of trumpets shall their seed restore my Cities\nWealthy and well-weaponed, that once more may I behold\nAll the world go softly when it walks before my Cities,\nAnd the horses and the chariots fleeing from them as of old!\n\n  -- Rudyard Kipling\n";
266 267 268

	g_repo = cl_git_sandbox_init("renames");

269 270
	cl_git_pass(git_config_new(&cfg));
	git_repository_set_config(g_repo, cfg);
271 272
	git_config_free(cfg);

273
	git_repository_reinit_filesystem(g_repo, false);
274

275 276 277
	cl_git_pass(
		git_futils_readbuffer(&old_content, "renames/songof7cities.txt"));

278
	cl_git_rewritefile("renames/songof7cities.txt", new_content);
279 280 281

	cl_git_pass(git_repository_head_tree(&head, g_repo));

282
	cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt));
283 284 285

	cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));

Russell Belfer committed
286 287
	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
	cl_assert((delta = git_patch_get_delta(patch)) != NULL);
288 289

	cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
290
	cl_assert_equal_i(2, (int)git_patch_num_hunks(patch));
291 292 293 294

	/* check hunk 0 */

	cl_git_pass(
295
		git_patch_get_hunk(&hunk, &hunklen, patch, 0));
296 297 298

	cl_assert_equal_i(18, (int)hunklen);

299 300 301 302
	cl_assert_equal_i(6, (int)hunk->old_start);
	cl_assert_equal_i(15, (int)hunk->old_lines);
	cl_assert_equal_i(6, (int)hunk->new_start);
	cl_assert_equal_i(9, (int)hunk->new_lines);
303

304
	cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 0));
305

306 307 308
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 0));
	cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
309
	cl_assert_equal_s("Ivory their outposts were--the guardrooms of them gilded,\n", actual.ptr);
310 311
	cl_assert_equal_i(6, line->old_lineno);
	cl_assert_equal_i(6, line->new_lineno);
312
	cl_assert_equal_i(-1, line->content_offset);
313

314 315 316
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3));
	cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
317
	cl_assert_equal_s("All the world went softly when it walked before my Cities--\n", actual.ptr);
318 319
	cl_assert_equal_i(9, line->old_lineno);
	cl_assert_equal_i(-1, line->new_lineno);
320
	cl_assert_equal_i(252, line->content_offset);
321

322 323 324
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 12));
	cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
325
	cl_assert_equal_s("This is some new text;\n", actual.ptr);
326 327
	cl_assert_equal_i(-1, line->old_lineno);
	cl_assert_equal_i(9, line->new_lineno);
328
	cl_assert_equal_i(252, line->content_offset);
329 330 331

	/* check hunk 1 */

332
	cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 1));
333 334 335

	cl_assert_equal_i(18, (int)hunklen);

336 337 338 339
	cl_assert_equal_i(31, (int)hunk->old_start);
	cl_assert_equal_i(15, (int)hunk->old_lines);
	cl_assert_equal_i(25, (int)hunk->new_start);
	cl_assert_equal_i(9, (int)hunk->new_lines);
340

341
	cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 1));
342

343 344 345
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 0));
	cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
346
	cl_assert_equal_s("My rulers and their treasure and their unborn populations,\n", actual.ptr);
347 348
	cl_assert_equal_i(31, line->old_lineno);
	cl_assert_equal_i(25, line->new_lineno);
349
	cl_assert_equal_i(-1, line->content_offset);
350

351 352 353
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 3));
	cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
354
	cl_assert_equal_s("The Daughters of the Palace whom they cherished in my Cities,\n", actual.ptr);
355 356
	cl_assert_equal_i(34, line->old_lineno);
	cl_assert_equal_i(-1, line->new_lineno);
357
	cl_assert_equal_i(1468, line->content_offset);
358

359 360 361
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 12));
	cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
362
	cl_assert_equal_s("Another replacement;\n", actual.ptr);
363 364
	cl_assert_equal_i(-1, line->old_lineno);
	cl_assert_equal_i(28, line->new_lineno);
365
	cl_assert_equal_i(1066, line->content_offset);
366

367 368
	git_patch_free(patch);
	git_diff_free(diff);
369 370 371 372 373 374 375 376 377 378

	/* Let's check line numbers when there is no newline */

	git_buf_rtrim(&old_content);
	cl_git_rewritefile("renames/songof7cities.txt", old_content.ptr);

	cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt));

	cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));

Russell Belfer committed
379 380
	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
	cl_assert((delta = git_patch_get_delta(patch)) != NULL);
381 382

	cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
383
	cl_assert_equal_i(1, (int)git_patch_num_hunks(patch));
384 385 386

	/* check hunk 0 */

387
	cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 0));
388 389 390

	cl_assert_equal_i(6, (int)hunklen);

391 392 393 394
	cl_assert_equal_i(46, (int)hunk->old_start);
	cl_assert_equal_i(4, (int)hunk->old_lines);
	cl_assert_equal_i(46, (int)hunk->new_start);
	cl_assert_equal_i(4, (int)hunk->new_lines);
395

396
	cl_assert_equal_i(6, (int)git_patch_num_lines_in_hunk(patch, 0));
397

398 399 400
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 1));
	cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
401
	cl_assert_equal_s("And the horses and the chariots fleeing from them as of old!\n", actual.ptr);
402 403
	cl_assert_equal_i(47, line->old_lineno);
	cl_assert_equal_i(47, line->new_lineno);
404

405 406 407
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 2));
	cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
408
	cl_assert_equal_s("\n", actual.ptr);
409 410
	cl_assert_equal_i(48, line->old_lineno);
	cl_assert_equal_i(48, line->new_lineno);
411

412 413 414
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3));
	cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
415
	cl_assert_equal_s("  -- Rudyard Kipling\n", actual.ptr);
416 417
	cl_assert_equal_i(49, line->old_lineno);
	cl_assert_equal_i(-1, line->new_lineno);
418

419 420 421
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4));
	cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
422
	cl_assert_equal_s("  -- Rudyard Kipling", actual.ptr);
423 424
	cl_assert_equal_i(-1, line->old_lineno);
	cl_assert_equal_i(49, line->new_lineno);
425

426 427 428
	cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 5));
	cl_assert_equal_i(GIT_DIFF_LINE_DEL_EOFNL, (int)line->origin);
	cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
429
	cl_assert_equal_s("\n\\ No newline at end of file\n", actual.ptr);
430 431
	cl_assert_equal_i(-1, line->old_lineno);
	cl_assert_equal_i(49, line->new_lineno);
432

433 434
	git_patch_free(patch);
	git_diff_free(diff);
435 436 437

	git_buf_free(&actual);
	git_buf_free(&old_content);
438 439
	git_tree_free(head);
}
440 441

static void check_single_patch_stats(
442
	git_repository *repo, size_t hunks,
443
	size_t adds, size_t dels, size_t ctxt, size_t *sizes,
444
	const char *expected)
445
{
446 447
	git_diff *diff;
	git_patch *patch;
448
	const git_diff_delta *delta;
449
	size_t actual_ctxt, actual_adds, actual_dels;
450 451 452 453 454

	cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));

	cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));

Russell Belfer committed
455 456
	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
	cl_assert((delta = git_patch_get_delta(patch)) != NULL);
457 458
	cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);

459
	cl_assert_equal_i((int)hunks, (int)git_patch_num_hunks(patch));
460

461
	cl_git_pass( git_patch_line_stats(
462
		&actual_ctxt, &actual_adds, &actual_dels, patch) );
463

464
	cl_assert_equal_sz(ctxt, actual_ctxt);
465 466
	cl_assert_equal_sz(adds, actual_adds);
	cl_assert_equal_sz(dels, actual_dels);
467

468
	if (expected != NULL) {
Nicolas Hake committed
469 470 471 472
		git_buf buf = GIT_BUF_INIT;
		cl_git_pass(git_patch_to_buf(&buf, patch));
		cl_assert_equal_s(expected, buf.ptr);
		git_buf_free(&buf);
473

474
		cl_assert_equal_sz(
475
			strlen(expected), git_patch_size(patch, 1, 1, 1));
476 477 478 479
	}

	if (sizes) {
		if (sizes[0])
480
			cl_assert_equal_sz(sizes[0], git_patch_size(patch, 0, 0, 0));
481
		if (sizes[1])
482
			cl_assert_equal_sz(sizes[1], git_patch_size(patch, 1, 0, 0));
483
		if (sizes[2])
484
			cl_assert_equal_sz(sizes[2], git_patch_size(patch, 1, 1, 0));
485
	}
486

487 488 489
	/* walk lines in hunk with basic sanity checks */
	for (; hunks > 0; --hunks) {
		size_t i, max_i;
490 491
		const git_diff_line *line;
		int last_new_lineno = -1, last_old_lineno = -1;
492

493
		max_i = git_patch_num_lines_in_hunk(patch, hunks - 1);
494 495 496 497

		for (i = 0; i < max_i; ++i) {
			int expected = 1;

498 499
			cl_git_pass(
				git_patch_get_line_in_hunk(&line, patch, hunks - 1, i));
500

501 502 503
			if (line->origin == GIT_DIFF_LINE_ADD_EOFNL ||
				line->origin == GIT_DIFF_LINE_DEL_EOFNL ||
				line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
504 505
				expected = 0;

506 507 508 509 510
			if (line->old_lineno >= 0) {
				if (last_old_lineno >= 0)
					cl_assert_equal_i(
						expected, line->old_lineno - last_old_lineno);
				last_old_lineno = line->old_lineno;
511
			}
512 513 514 515 516 517

			if (line->new_lineno >= 0) {
				if (last_new_lineno >= 0)
					cl_assert_equal_i(
						expected, line->new_lineno - last_new_lineno);
				last_new_lineno = line->new_lineno;
518 519 520 521
			}
		}
	}

522 523
	git_patch_free(patch);
	git_diff_free(diff);
524 525 526 527
}

void test_diff_patch__line_counts_with_eofnl(void)
{
528
	git_config *cfg;
529 530 531
	git_buf content = GIT_BUF_INIT;
	const char *end;
	git_index *index;
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
	const char *expected =
		/* below is pasted output of 'git diff' with fn context removed */
		"diff --git a/songof7cities.txt b/songof7cities.txt\n"
		"index 378a7d9..3d0154e 100644\n"
		"--- a/songof7cities.txt\n"
		"+++ b/songof7cities.txt\n"
		"@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n"
		" \n"
		" To the sound of trumpets shall their seed restore my Cities\n"
		" Wealthy and well-weaponed, that once more may I behold\n"
		"-All the world go softly when it walks before my Cities,\n"
		"+#All the world go softly when it walks before my Cities,\n"
		" And the horses and the chariots fleeing from them as of old!\n"
		" \n"
		"   -- Rudyard Kipling\n"
		"\\ No newline at end of file\n";
	size_t expected_sizes[3] = { 115, 119 + 115 + 114, 119 + 115 + 114 + 71 };
549 550 551

	g_repo = cl_git_sandbox_init("renames");

552 553
	cl_git_pass(git_config_new(&cfg));
	git_repository_set_config(g_repo, cfg);
554 555
	git_config_free(cfg);

556
	git_repository_reinit_filesystem(g_repo, false);
557

558
	cl_git_pass(git_futils_readbuffer(&content, "renames/songof7cities.txt"));
559 560 561 562 563

	/* remove first line */

	end = git_buf_cstr(&content) + git_buf_find(&content, '\n') + 1;
	git_buf_consume(&content, end);
564
	cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
565

566
	check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL, NULL);
567 568 569 570

	/* remove trailing whitespace */

	git_buf_rtrim(&content);
571
	cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
572

573
	check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL, NULL);
574 575 576 577

	/* add trailing whitespace */

	cl_git_pass(git_repository_index(&index, g_repo));
578
	cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
579 580 581 582
	cl_git_pass(git_index_write(index));
	git_index_free(index);

	cl_git_pass(git_buf_putc(&content, '\n'));
583
	cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
584

585
	check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL, NULL);
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606

	/* no trailing whitespace as context line */

	{
		/* walk back a couple lines, make space and insert char */
		char *scan = content.ptr + content.size;
		int i;

		for (i = 0; i < 5; ++i) {
			for (--scan; scan > content.ptr && *scan != '\n'; --scan)
				/* seek to prev \n */;
		}
		cl_assert(scan > content.ptr);

		/* overwrite trailing \n with right-shifted content */
		memmove(scan + 1, scan, content.size - (scan - content.ptr) - 1);
		/* insert '#' char into space we created */
		scan[1] = '#';
	}
	cl_git_rewritefile("renames/songof7cities.txt", content.ptr);

607
	check_single_patch_stats(
608
		g_repo, 1, 1, 1, 6, expected_sizes, expected);
609 610

	git_buf_free(&content);
611
}