]> git.proxmox.com Git - libgit2.git/blob - tests/diff/patch.c
7eb353627add3c59510f414e475a8968634b919b
[libgit2.git] / tests / diff / patch.c
1 #include "clar_libgit2.h"
2 #include "git2/sys/repository.h"
3
4 #include "diff_helpers.h"
5 #include "diff.h"
6 #include "repository.h"
7 #include "buf_text.h"
8
9 static git_repository *g_repo = NULL;
10
11 void test_diff_patch__initialize(void)
12 {
13 }
14
15 void test_diff_patch__cleanup(void)
16 {
17 cl_git_sandbox_cleanup();
18 }
19
20 #define EXPECTED_HEADER "diff --git a/subdir.txt b/subdir.txt\n" \
21 "deleted file mode 100644\n" \
22 "index e8ee89e..0000000\n" \
23 "--- a/subdir.txt\n" \
24 "+++ /dev/null\n"
25
26 #define EXPECTED_HUNK "@@ -1,2 +0,0 @@\n"
27
28 #define UTF8_HUNK_HEADER "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\n"
29
30 #define UTF8_TRUNCATED_A_HUNK_HEADER "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\n"
31
32 #define UTF8_TRUNCATED_L_HUNK_HEADER "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\n"
33
34 static int check_removal_cb(
35 const git_diff_delta *delta,
36 const git_diff_hunk *hunk,
37 const git_diff_line *line,
38 void *payload)
39 {
40 switch (line->origin) {
41 case GIT_DIFF_LINE_FILE_HDR:
42 cl_assert_equal_s(EXPECTED_HEADER, line->content);
43 cl_assert(hunk == NULL);
44 goto check_delta;
45
46 case GIT_DIFF_LINE_HUNK_HDR:
47 cl_assert_equal_s(EXPECTED_HUNK, line->content);
48 goto check_hunk;
49
50 case GIT_DIFF_LINE_CONTEXT:
51 case GIT_DIFF_LINE_DELETION:
52 if (payload != NULL)
53 return *(int *)payload;
54 goto check_hunk;
55
56 default:
57 /* unexpected code path */
58 return -1;
59 }
60
61 check_hunk:
62 cl_assert(hunk != NULL);
63 cl_assert_equal_i(1, hunk->old_start);
64 cl_assert_equal_i(2, hunk->old_lines);
65 cl_assert_equal_i(0, hunk->new_start);
66 cl_assert_equal_i(0, hunk->new_lines);
67
68 check_delta:
69 cl_assert_equal_s("subdir.txt", delta->old_file.path);
70 cl_assert_equal_s("subdir.txt", delta->new_file.path);
71 cl_assert_equal_i(GIT_DELTA_DELETED, delta->status);
72
73 return 0;
74 }
75
76 void test_diff_patch__can_properly_display_the_removal_of_a_file(void)
77 {
78 /*
79 * $ git diff 26a125e..735b6a2
80 * diff --git a/subdir.txt b/subdir.txt
81 * deleted file mode 100644
82 * index e8ee89e..0000000
83 * --- a/subdir.txt
84 * +++ /dev/null
85 * @@ -1,2 +0,0 @@
86 * -Is it a bird?
87 * -Is it a plane?
88 */
89
90 const char *one_sha = "26a125e";
91 const char *another_sha = "735b6a2";
92 git_tree *one, *another;
93 git_diff *diff;
94
95 g_repo = cl_git_sandbox_init("status");
96
97 one = resolve_commit_oid_to_tree(g_repo, one_sha);
98 another = resolve_commit_oid_to_tree(g_repo, another_sha);
99
100 cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL));
101
102 cl_git_pass(git_diff_print(
103 diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, NULL));
104
105 git_diff_free(diff);
106
107 git_tree_free(another);
108 git_tree_free(one);
109 }
110
111 void test_diff_patch__can_cancel_diff_print(void)
112 {
113 const char *one_sha = "26a125e";
114 const char *another_sha = "735b6a2";
115 git_tree *one, *another;
116 git_diff *diff;
117 int fail_with;
118
119 g_repo = cl_git_sandbox_init("status");
120
121 one = resolve_commit_oid_to_tree(g_repo, one_sha);
122 another = resolve_commit_oid_to_tree(g_repo, another_sha);
123
124 cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL));
125
126 fail_with = -2323;
127
128 cl_git_fail_with(git_diff_print(
129 diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with),
130 fail_with);
131
132 fail_with = 45;
133
134 cl_git_fail_with(git_diff_print(
135 diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with),
136 fail_with);
137
138 git_diff_free(diff);
139
140 git_tree_free(another);
141 git_tree_free(one);
142 }
143
144 void test_diff_patch__to_string(void)
145 {
146 const char *one_sha = "26a125e";
147 const char *another_sha = "735b6a2";
148 git_tree *one, *another;
149 git_diff *diff;
150 git_patch *patch;
151 git_buf buf = GIT_BUF_INIT;
152 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";
153
154 g_repo = cl_git_sandbox_init("status");
155
156 one = resolve_commit_oid_to_tree(g_repo, one_sha);
157 another = resolve_commit_oid_to_tree(g_repo, another_sha);
158
159 cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL));
160
161 cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
162
163 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
164
165 cl_git_pass(git_patch_to_buf(&buf, patch));
166
167 cl_assert_equal_s(expected, buf.ptr);
168
169 cl_assert_equal_sz(31, git_patch_size(patch, 0, 0, 0));
170 cl_assert_equal_sz(31, git_patch_size(patch, 1, 0, 0));
171 cl_assert_equal_sz(31 + 16, git_patch_size(patch, 1, 1, 0));
172 cl_assert_equal_sz(strlen(expected), git_patch_size(patch, 1, 1, 1));
173
174 git_buf_dispose(&buf);
175 git_patch_free(patch);
176 git_diff_free(diff);
177 git_tree_free(another);
178 git_tree_free(one);
179 }
180
181 void test_diff_patch__config_options(void)
182 {
183 const char *one_sha = "26a125e"; /* current HEAD */
184 git_tree *one;
185 git_config *cfg;
186 git_diff *diff;
187 git_patch *patch;
188 git_buf buf = GIT_BUF_INIT;
189 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
190 char *onefile = "staged_changes_modified_file";
191 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";
192 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";
193 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";
194 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";
195
196 g_repo = cl_git_sandbox_init("status");
197 cl_git_pass(git_repository_config(&cfg, g_repo));
198 one = resolve_commit_oid_to_tree(g_repo, one_sha);
199 opts.pathspec.count = 1;
200 opts.pathspec.strings = &onefile;
201
202
203 cl_git_pass(git_config_set_string(cfg, "diff.mnemonicprefix", "true"));
204
205 cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts));
206
207 cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
208 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
209 cl_git_pass(git_patch_to_buf(&buf, patch));
210 cl_assert_equal_s(expected1, buf.ptr);
211
212 git_buf_clear(&buf);
213 git_patch_free(patch);
214 git_diff_free(diff);
215
216 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
217
218 cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
219 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
220 cl_git_pass(git_patch_to_buf(&buf, patch));
221 cl_assert_equal_s(expected2, buf.ptr);
222
223 git_buf_clear(&buf);
224 git_patch_free(patch);
225 git_diff_free(diff);
226
227
228 cl_git_pass(git_config_set_string(cfg, "diff.noprefix", "true"));
229
230 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
231
232 cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
233 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
234 cl_git_pass(git_patch_to_buf(&buf, patch));
235 cl_assert_equal_s(expected3, buf.ptr);
236
237 git_buf_clear(&buf);
238 git_patch_free(patch);
239 git_diff_free(diff);
240
241
242 cl_git_pass(git_config_set_int32(cfg, "core.abbrev", 12));
243
244 cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts));
245
246 cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
247 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
248 cl_git_pass(git_patch_to_buf(&buf, patch));
249 cl_assert_equal_s(expected4, buf.ptr);
250
251 git_buf_clear(&buf);
252 git_patch_free(patch);
253 git_diff_free(diff);
254
255 git_buf_dispose(&buf);
256 git_tree_free(one);
257 git_config_free(cfg);
258 }
259
260 void test_diff_patch__hunks_have_correct_line_numbers(void)
261 {
262 git_config *cfg;
263 git_tree *head;
264 git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
265 git_diff *diff;
266 git_patch *patch;
267 const git_diff_delta *delta;
268 const git_diff_hunk *hunk;
269 const git_diff_line *line;
270 size_t hunklen;
271 git_buf old_content = GIT_BUF_INIT, actual = GIT_BUF_INIT;
272 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";
273
274 g_repo = cl_git_sandbox_init("renames");
275
276 cl_git_pass(git_config_new(&cfg));
277 git_repository_set_config(g_repo, cfg);
278 git_config_free(cfg);
279
280 git_repository_reinit_filesystem(g_repo, false);
281
282 cl_git_pass(
283 git_futils_readbuffer(&old_content, "renames/songof7cities.txt"));
284
285 cl_git_rewritefile("renames/songof7cities.txt", new_content);
286
287 cl_git_pass(git_repository_head_tree(&head, g_repo));
288
289 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt));
290
291 cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
292
293 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
294 cl_assert((delta = git_patch_get_delta(patch)) != NULL);
295
296 cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
297 cl_assert_equal_i(2, (int)git_patch_num_hunks(patch));
298
299 /* check hunk 0 */
300
301 cl_git_pass(
302 git_patch_get_hunk(&hunk, &hunklen, patch, 0));
303
304 cl_assert_equal_i(18, (int)hunklen);
305
306 cl_assert_equal_i(6, (int)hunk->old_start);
307 cl_assert_equal_i(15, (int)hunk->old_lines);
308 cl_assert_equal_i(6, (int)hunk->new_start);
309 cl_assert_equal_i(9, (int)hunk->new_lines);
310
311 cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 0));
312
313 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 0));
314 cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
315 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
316 cl_assert_equal_s("Ivory their outposts were--the guardrooms of them gilded,\n", actual.ptr);
317 cl_assert_equal_i(6, line->old_lineno);
318 cl_assert_equal_i(6, line->new_lineno);
319 cl_assert_equal_i(-1, line->content_offset);
320
321 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3));
322 cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
323 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
324 cl_assert_equal_s("All the world went softly when it walked before my Cities--\n", actual.ptr);
325 cl_assert_equal_i(9, line->old_lineno);
326 cl_assert_equal_i(-1, line->new_lineno);
327 cl_assert_equal_i(252, line->content_offset);
328
329 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 12));
330 cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
331 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
332 cl_assert_equal_s("This is some new text;\n", actual.ptr);
333 cl_assert_equal_i(-1, line->old_lineno);
334 cl_assert_equal_i(9, line->new_lineno);
335 cl_assert_equal_i(252, line->content_offset);
336
337 /* check hunk 1 */
338
339 cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 1));
340
341 cl_assert_equal_i(18, (int)hunklen);
342
343 cl_assert_equal_i(31, (int)hunk->old_start);
344 cl_assert_equal_i(15, (int)hunk->old_lines);
345 cl_assert_equal_i(25, (int)hunk->new_start);
346 cl_assert_equal_i(9, (int)hunk->new_lines);
347
348 cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 1));
349
350 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 0));
351 cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
352 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
353 cl_assert_equal_s("My rulers and their treasure and their unborn populations,\n", actual.ptr);
354 cl_assert_equal_i(31, line->old_lineno);
355 cl_assert_equal_i(25, line->new_lineno);
356 cl_assert_equal_i(-1, line->content_offset);
357
358 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 3));
359 cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
360 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
361 cl_assert_equal_s("The Daughters of the Palace whom they cherished in my Cities,\n", actual.ptr);
362 cl_assert_equal_i(34, line->old_lineno);
363 cl_assert_equal_i(-1, line->new_lineno);
364 cl_assert_equal_i(1468, line->content_offset);
365
366 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 12));
367 cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
368 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
369 cl_assert_equal_s("Another replacement;\n", actual.ptr);
370 cl_assert_equal_i(-1, line->old_lineno);
371 cl_assert_equal_i(28, line->new_lineno);
372 cl_assert_equal_i(1066, line->content_offset);
373
374 git_patch_free(patch);
375 git_diff_free(diff);
376
377 /* Let's check line numbers when there is no newline */
378
379 git_buf_rtrim(&old_content);
380 cl_git_rewritefile("renames/songof7cities.txt", old_content.ptr);
381
382 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt));
383
384 cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
385
386 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
387 cl_assert((delta = git_patch_get_delta(patch)) != NULL);
388
389 cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
390 cl_assert_equal_i(1, (int)git_patch_num_hunks(patch));
391
392 /* check hunk 0 */
393
394 cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 0));
395
396 cl_assert_equal_i(6, (int)hunklen);
397
398 cl_assert_equal_i(46, (int)hunk->old_start);
399 cl_assert_equal_i(4, (int)hunk->old_lines);
400 cl_assert_equal_i(46, (int)hunk->new_start);
401 cl_assert_equal_i(4, (int)hunk->new_lines);
402
403 cl_assert_equal_i(6, (int)git_patch_num_lines_in_hunk(patch, 0));
404
405 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 1));
406 cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
407 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
408 cl_assert_equal_s("And the horses and the chariots fleeing from them as of old!\n", actual.ptr);
409 cl_assert_equal_i(47, line->old_lineno);
410 cl_assert_equal_i(47, line->new_lineno);
411
412 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 2));
413 cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
414 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
415 cl_assert_equal_s("\n", actual.ptr);
416 cl_assert_equal_i(48, line->old_lineno);
417 cl_assert_equal_i(48, line->new_lineno);
418
419 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3));
420 cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
421 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
422 cl_assert_equal_s(" -- Rudyard Kipling\n", actual.ptr);
423 cl_assert_equal_i(49, line->old_lineno);
424 cl_assert_equal_i(-1, line->new_lineno);
425
426 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4));
427 cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
428 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
429 cl_assert_equal_s(" -- Rudyard Kipling", actual.ptr);
430 cl_assert_equal_i(-1, line->old_lineno);
431 cl_assert_equal_i(49, line->new_lineno);
432
433 cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 5));
434 cl_assert_equal_i(GIT_DIFF_LINE_DEL_EOFNL, (int)line->origin);
435 cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
436 cl_assert_equal_s("\n\\ No newline at end of file\n", actual.ptr);
437 cl_assert_equal_i(-1, line->old_lineno);
438 cl_assert_equal_i(49, line->new_lineno);
439
440 git_patch_free(patch);
441 git_diff_free(diff);
442
443 git_buf_dispose(&actual);
444 git_buf_dispose(&old_content);
445 git_tree_free(head);
446 }
447
448 static void check_single_patch_stats(
449 git_repository *repo, size_t hunks,
450 size_t adds, size_t dels, size_t ctxt, size_t *sizes,
451 const char *expected)
452 {
453 git_diff *diff;
454 git_patch *patch;
455 const git_diff_delta *delta;
456 size_t actual_ctxt, actual_adds, actual_dels;
457
458 cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));
459
460 cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
461
462 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
463 cl_assert((delta = git_patch_get_delta(patch)) != NULL);
464 cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
465
466 cl_assert_equal_i((int)hunks, (int)git_patch_num_hunks(patch));
467
468 cl_git_pass( git_patch_line_stats(
469 &actual_ctxt, &actual_adds, &actual_dels, patch) );
470
471 cl_assert_equal_sz(ctxt, actual_ctxt);
472 cl_assert_equal_sz(adds, actual_adds);
473 cl_assert_equal_sz(dels, actual_dels);
474
475 if (expected != NULL) {
476 git_buf buf = GIT_BUF_INIT;
477 cl_git_pass(git_patch_to_buf(&buf, patch));
478 cl_assert_equal_s(expected, buf.ptr);
479 git_buf_dispose(&buf);
480
481 cl_assert_equal_sz(
482 strlen(expected), git_patch_size(patch, 1, 1, 1));
483 }
484
485 if (sizes) {
486 if (sizes[0])
487 cl_assert_equal_sz(sizes[0], git_patch_size(patch, 0, 0, 0));
488 if (sizes[1])
489 cl_assert_equal_sz(sizes[1], git_patch_size(patch, 1, 0, 0));
490 if (sizes[2])
491 cl_assert_equal_sz(sizes[2], git_patch_size(patch, 1, 1, 0));
492 }
493
494 /* walk lines in hunk with basic sanity checks */
495 for (; hunks > 0; --hunks) {
496 size_t i, max_i;
497 const git_diff_line *line;
498 int last_new_lineno = -1, last_old_lineno = -1;
499
500 max_i = git_patch_num_lines_in_hunk(patch, hunks - 1);
501
502 for (i = 0; i < max_i; ++i) {
503 int expected = 1;
504
505 cl_git_pass(
506 git_patch_get_line_in_hunk(&line, patch, hunks - 1, i));
507
508 if (line->origin == GIT_DIFF_LINE_ADD_EOFNL ||
509 line->origin == GIT_DIFF_LINE_DEL_EOFNL ||
510 line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
511 expected = 0;
512
513 if (line->old_lineno >= 0) {
514 if (last_old_lineno >= 0)
515 cl_assert_equal_i(
516 expected, line->old_lineno - last_old_lineno);
517 last_old_lineno = line->old_lineno;
518 }
519
520 if (line->new_lineno >= 0) {
521 if (last_new_lineno >= 0)
522 cl_assert_equal_i(
523 expected, line->new_lineno - last_new_lineno);
524 last_new_lineno = line->new_lineno;
525 }
526 }
527 }
528
529 git_patch_free(patch);
530 git_diff_free(diff);
531 }
532
533 void test_diff_patch__line_counts_with_eofnl(void)
534 {
535 git_config *cfg;
536 git_buf content = GIT_BUF_INIT;
537 const char *end;
538 git_index *index;
539 const char *expected =
540 /* below is pasted output of 'git diff' with fn context removed */
541 "diff --git a/songof7cities.txt b/songof7cities.txt\n"
542 "index 378a7d9..3d0154e 100644\n"
543 "--- a/songof7cities.txt\n"
544 "+++ b/songof7cities.txt\n"
545 "@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n"
546 " \n"
547 " To the sound of trumpets shall their seed restore my Cities\n"
548 " Wealthy and well-weaponed, that once more may I behold\n"
549 "-All the world go softly when it walks before my Cities,\n"
550 "+#All the world go softly when it walks before my Cities,\n"
551 " And the horses and the chariots fleeing from them as of old!\n"
552 " \n"
553 " -- Rudyard Kipling\n"
554 "\\ No newline at end of file\n";
555 size_t expected_sizes[3] = { 115, 119 + 115 + 114, 119 + 115 + 114 + 71 };
556
557 g_repo = cl_git_sandbox_init("renames");
558
559 cl_git_pass(git_config_new(&cfg));
560 git_repository_set_config(g_repo, cfg);
561 git_config_free(cfg);
562
563 git_repository_reinit_filesystem(g_repo, false);
564
565 cl_git_pass(git_futils_readbuffer(&content, "renames/songof7cities.txt"));
566
567 /* remove first line */
568
569 end = git_buf_cstr(&content) + git_buf_find(&content, '\n') + 1;
570 git_buf_consume(&content, end);
571 cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
572
573 check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL, NULL);
574
575 /* remove trailing whitespace */
576
577 git_buf_rtrim(&content);
578 cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
579
580 check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL, NULL);
581
582 /* add trailing whitespace */
583
584 cl_git_pass(git_repository_index(&index, g_repo));
585 cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
586 cl_git_pass(git_index_write(index));
587 git_index_free(index);
588
589 cl_git_pass(git_buf_putc(&content, '\n'));
590 cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
591
592 check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL, NULL);
593
594 /* no trailing whitespace as context line */
595
596 {
597 /* walk back a couple lines, make space and insert char */
598 char *scan = content.ptr + content.size;
599 int i;
600
601 for (i = 0; i < 5; ++i) {
602 for (--scan; scan > content.ptr && *scan != '\n'; --scan)
603 /* seek to prev \n */;
604 }
605 cl_assert(scan > content.ptr);
606
607 /* overwrite trailing \n with right-shifted content */
608 memmove(scan + 1, scan, content.size - (scan - content.ptr) - 1);
609 /* insert '#' char into space we created */
610 scan[1] = '#';
611 }
612 cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
613
614 check_single_patch_stats(
615 g_repo, 1, 1, 1, 6, expected_sizes, expected);
616
617 git_buf_dispose(&content);
618 }
619
620 void test_diff_patch__can_strip_bad_utf8(void)
621 {
622 const char *a = "A " UTF8_HUNK_HEADER
623 " B\n"
624 " C\n"
625 " D\n"
626 " E\n"
627 " F\n"
628 " G\n"
629 " H\n"
630 " I\n"
631 " J\n"
632 " K\n"
633 "L " UTF8_HUNK_HEADER
634 " M\n"
635 " N\n"
636 " O\n"
637 " P\n"
638 " Q\n"
639 " R\n"
640 " S\n"
641 " T\n"
642 " U\n"
643 " V\n";
644
645 const char *b = "A " UTF8_HUNK_HEADER
646 " B\n"
647 " C\n"
648 " D\n"
649 " E modified\n"
650 " F\n"
651 " G\n"
652 " H\n"
653 " I\n"
654 " J\n"
655 " K\n"
656 "L " UTF8_HUNK_HEADER
657 " M\n"
658 " N\n"
659 " O\n"
660 " P modified\n"
661 " Q\n"
662 " R\n"
663 " S\n"
664 " T\n"
665 " U\n"
666 " V\n";
667
668 const char *expected = "diff --git a/file b/file\n"
669 "index d0647c4..7827ce5 100644\n"
670 "--- a/file\n"
671 "+++ b/file\n"
672 "@@ -2,7 +2,7 @@ A " UTF8_TRUNCATED_A_HUNK_HEADER
673 " B\n"
674 " C\n"
675 " D\n"
676 "- E\n"
677 "+ E modified\n"
678 " F\n"
679 " G\n"
680 " H\n"
681 "@@ -13,7 +13,7 @@ L " UTF8_TRUNCATED_L_HUNK_HEADER
682 " M\n"
683 " N\n"
684 " O\n"
685 "- P\n"
686 "+ P modified\n"
687 " Q\n"
688 " R\n"
689 " S\n";
690
691 git_diff_options opts;
692 git_patch *patch;
693 git_buf buf = GIT_BUF_INIT;
694
695 cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION));
696
697 cl_git_pass(git_patch_from_buffers(&patch, a, strlen(a), NULL, b, strlen(b), NULL, &opts));
698 cl_git_pass(git_patch_to_buf(&buf, patch));
699
700 cl_assert_equal_s(expected, buf.ptr);
701
702 git_patch_free(patch);
703 git_buf_dispose(&buf);
704 }