Skip to content

Commit fd7a384

Browse files
authored
Merge pull request libgit2#5159 from pks-t/pks/patch-parse-old-missing-nl
patch_parse: handle missing newline indicator in old file
2 parents f33ca47 + b089328 commit fd7a384

File tree

6 files changed

+109
-20
lines changed

6 files changed

+109
-20
lines changed

src/apply.c

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -199,23 +199,34 @@ static int apply_hunk(
199199

200200
for (i = 0; i < hunk->line_count; i++) {
201201
size_t linenum = hunk->line_start + i;
202-
git_diff_line *line = git_array_get(patch->lines, linenum);
202+
git_diff_line *line = git_array_get(patch->lines, linenum), *prev;
203203

204204
if (!line) {
205205
error = apply_err("preimage does not contain line %"PRIuZ, linenum);
206206
goto done;
207207
}
208208

209-
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
210-
line->origin == GIT_DIFF_LINE_DELETION) {
211-
if ((error = git_vector_insert(&preimage.lines, line)) < 0)
212-
goto done;
213-
}
214-
215-
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
216-
line->origin == GIT_DIFF_LINE_ADDITION) {
217-
if ((error = git_vector_insert(&postimage.lines, line)) < 0)
218-
goto done;
209+
switch (line->origin) {
210+
case GIT_DIFF_LINE_CONTEXT_EOFNL:
211+
case GIT_DIFF_LINE_DEL_EOFNL:
212+
case GIT_DIFF_LINE_ADD_EOFNL:
213+
prev = i ? git_array_get(patch->lines, i - 1) : NULL;
214+
if (prev && prev->content[prev->content_len - 1] == '\n')
215+
prev->content_len -= 1;
216+
break;
217+
case GIT_DIFF_LINE_CONTEXT:
218+
if ((error = git_vector_insert(&preimage.lines, line)) < 0 ||
219+
(error = git_vector_insert(&postimage.lines, line)) < 0)
220+
goto done;
221+
break;
222+
case GIT_DIFF_LINE_DELETION:
223+
if ((error = git_vector_insert(&preimage.lines, line)) < 0)
224+
goto done;
225+
break;
226+
case GIT_DIFF_LINE_ADDITION:
227+
if ((error = git_vector_insert(&postimage.lines, line)) < 0)
228+
goto done;
229+
break;
219230
}
220231
}
221232

src/diff.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ static int file_cb(
460460
return error;
461461
}
462462

463-
static int line_cb(
463+
static int patchid_line_cb(
464464
const git_diff_delta *delta,
465465
const git_diff_hunk *hunk,
466466
const git_diff_line *line,
@@ -482,6 +482,14 @@ static int line_cb(
482482
break;
483483
case GIT_DIFF_LINE_CONTEXT:
484484
break;
485+
case GIT_DIFF_LINE_CONTEXT_EOFNL:
486+
case GIT_DIFF_LINE_ADD_EOFNL:
487+
case GIT_DIFF_LINE_DEL_EOFNL:
488+
/*
489+
* Ignore EOF without newlines for patch IDs as whitespace is
490+
* not supposed to be significant.
491+
*/
492+
return 0;
485493
default:
486494
git_error_set(GIT_ERROR_PATCH, "invalid line origin for patch");
487495
return -1;
@@ -518,7 +526,7 @@ int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opt
518526
if ((error = git_hash_ctx_init(&args.ctx)) < 0)
519527
goto out;
520528

521-
if ((error = git_diff_foreach(diff, file_cb, NULL, NULL, line_cb, &args)) < 0)
529+
if ((error = git_diff_foreach(diff, file_cb, NULL, NULL, patchid_line_cb, &args)) < 0)
522530
goto out;
523531

524532
if ((error = (flush_hunk(&args.result, &args.ctx))) < 0)

src/patch_generate.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,7 @@ static int patch_generated_line_cb(
836836
{
837837
git_patch_generated *patch = payload;
838838
git_patch_hunk *hunk;
839-
git_diff_line *line;
839+
git_diff_line *line;
840840

841841
GIT_UNUSED(delta);
842842
GIT_UNUSED(hunk_);

src/patch_parse.c

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,14 @@ static int parse_hunk_header(
524524
return -1;
525525
}
526526

527+
static int eof_for_origin(int origin) {
528+
if (origin == GIT_DIFF_LINE_ADDITION)
529+
return GIT_DIFF_LINE_ADD_EOFNL;
530+
if (origin == GIT_DIFF_LINE_DELETION)
531+
return GIT_DIFF_LINE_DEL_EOFNL;
532+
return GIT_DIFF_LINE_CONTEXT_EOFNL;
533+
}
534+
527535
static int parse_hunk_body(
528536
git_patch_parsed *patch,
529537
git_patch_hunk *hunk,
@@ -534,6 +542,7 @@ static int parse_hunk_body(
534542

535543
int oldlines = hunk->hunk.old_lines;
536544
int newlines = hunk->hunk.new_lines;
545+
int last_origin = 0;
537546

538547
for (;
539548
ctx->parse_ctx.remain_len > 1 &&
@@ -578,6 +587,21 @@ static int parse_hunk_body(
578587
old_lineno = -1;
579588
break;
580589

590+
case '\\':
591+
/*
592+
* If there are no oldlines left, then this is probably
593+
* the "\ No newline at end of file" marker. Do not
594+
* verify its format, as it may be localized.
595+
*/
596+
if (!oldlines) {
597+
prefix = 0;
598+
origin = eof_for_origin(last_origin);
599+
old_lineno = -1;
600+
new_lineno = -1;
601+
break;
602+
}
603+
/* fall through */
604+
581605
default:
582606
error = git_parse_err("invalid patch hunk at line %"PRIuZ, ctx->parse_ctx.line_num);
583607
goto done;
@@ -597,6 +621,8 @@ static int parse_hunk_body(
597621
line->new_lineno = new_lineno;
598622

599623
hunk->line_count++;
624+
625+
last_origin = origin;
600626
}
601627

602628
if (oldlines || newlines) {
@@ -606,7 +632,8 @@ static int parse_hunk_body(
606632
goto done;
607633
}
608634

609-
/* Handle "\ No newline at end of file". Only expect the leading
635+
/*
636+
* Handle "\ No newline at end of file". Only expect the leading
610637
* backslash, though, because the rest of the string could be
611638
* localized. Because `diff` optimizes for the case where you
612639
* want to apply the patch by hand.
@@ -617,11 +644,24 @@ static int parse_hunk_body(
617644
line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1);
618645

619646
if (line->content_len < 1) {
620-
error = git_parse_err("cannot trim trailing newline of empty line");
647+
error = git_parse_err("last line has no trailing newline");
621648
goto done;
622649
}
623650

624-
line->content_len--;
651+
line = git_array_alloc(patch->base.lines);
652+
GIT_ERROR_CHECK_ALLOC(line);
653+
654+
memset(line, 0x0, sizeof(git_diff_line));
655+
656+
line->content = ctx->parse_ctx.line;
657+
line->content_len = ctx->parse_ctx.line_len;
658+
line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len;
659+
line->origin = eof_for_origin(last_origin);
660+
line->num_lines = 1;
661+
line->old_lineno = -1;
662+
line->new_lineno = -1;
663+
664+
hunk->line_count++;
625665

626666
git_parse_advance_line(&ctx->parse_ctx);
627667
}

tests/patch/parse.c

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ static void ensure_patch_validity(git_patch *patch)
2727
cl_assert_equal_i(0, delta->new_file.size);
2828
}
2929

30+
static void ensure_identical_patch_inout(const char *content) {
31+
git_buf buf = GIT_BUF_INIT;
32+
git_patch *patch;
33+
34+
cl_git_pass(git_patch_from_buffer(&patch, content, strlen(content), NULL));
35+
cl_git_pass(git_patch_to_buf(&buf, patch));
36+
cl_assert_equal_strn(git_buf_cstr(&buf), content, strlen(content));
37+
38+
git_patch_free(patch);
39+
git_buf_dispose(&buf);
40+
}
41+
3042
void test_patch_parse__original_to_change_middle(void)
3143
{
3244
git_patch *patch;
@@ -102,11 +114,19 @@ void test_patch_parse__invalid_patches_fails(void)
102114
strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL));
103115
}
104116

117+
void test_patch_parse__no_newline_at_end_of_new_file(void)
118+
{
119+
ensure_identical_patch_inout(PATCH_APPEND_NO_NL);
120+
}
121+
122+
void test_patch_parse__no_newline_at_end_of_old_file(void)
123+
{
124+
ensure_identical_patch_inout(PATCH_APPEND_NO_NL_IN_OLD_FILE);
125+
}
126+
105127
void test_patch_parse__files_with_whitespaces_succeeds(void)
106128
{
107-
git_patch *patch;
108-
cl_git_pass(git_patch_from_buffer(&patch, PATCH_NAME_WHITESPACE, strlen(PATCH_NAME_WHITESPACE), NULL));
109-
git_patch_free(patch);
129+
ensure_identical_patch_inout(PATCH_NAME_WHITESPACE);
110130
}
111131

112132
void test_patch_parse__lifetime_of_patch_does_not_depend_on_buffer(void)

tests/patch/patch_common.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,16 @@
681681
"+added line with no nl\n" \
682682
"\\ No newline at end of file\n"
683683

684+
#define PATCH_APPEND_NO_NL_IN_OLD_FILE \
685+
"diff --git a/file.txt b/file.txt\n" \
686+
"index 9432026..83759c0 100644\n" \
687+
"--- a/file.txt\n" \
688+
"+++ b/file.txt\n" \
689+
"@@ -1,1 +1,1 @@\n" \
690+
"-foo\n" \
691+
"\\ No newline at end of file\n" \
692+
"+foo\n"
693+
684694
#define PATCH_NAME_WHITESPACE \
685695
"diff --git a/file with spaces.txt b/file with spaces.txt\n" \
686696
"index 9432026..83759c0 100644\n" \

0 commit comments

Comments
 (0)