Skip to content

Commit df4258a

Browse files
committed
apply: handle multiple deltas to the same file
git allows a patch file to contain multiple deltas to the same file: although it does not produce files in this format itself, this could be the result of concatenating two different patch files that affected the same file. git apply behaves by applying this next delta to the existing postimage of the file. We should do the same. If we have previously seen a file, and produced a postimage for it, we will load that postimage and apply the current delta to that. If we have not, get the file from the preimage.
1 parent c71e964 commit df4258a

File tree

3 files changed

+79
-7
lines changed

3 files changed

+79
-7
lines changed

src/apply.c

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ static int apply_one(
422422
git_repository *repo,
423423
git_reader *preimage_reader,
424424
git_index *preimage,
425+
git_reader *postimage_reader,
425426
git_index *postimage,
426427
git_diff *diff,
427428
size_t i,
@@ -435,6 +436,7 @@ static int apply_one(
435436
git_oid pre_id, post_id;
436437
git_filemode_t pre_filemode;
437438
git_index_entry pre_entry, post_entry;
439+
bool skip_preimage = false;
438440
int error;
439441

440442
if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
@@ -453,7 +455,26 @@ static int apply_one(
453455
}
454456
}
455457

456-
if (delta->status != GIT_DELTA_ADDED) {
458+
/*
459+
* We may be applying a second delta to an already seen file. If so,
460+
* use the already modified data in the postimage instead of the
461+
* content from the index or working directory. (Renames must be
462+
* specified before additional deltas since we are applying deltas
463+
* to the _target_ filename.)
464+
*/
465+
if (delta->status != GIT_DELTA_RENAMED) {
466+
if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
467+
postimage_reader, delta->old_file.path)) == 0) {
468+
skip_preimage = true;
469+
} else if (error == GIT_ENOTFOUND) {
470+
giterr_clear();
471+
error = 0;
472+
} else {
473+
goto done;
474+
}
475+
}
476+
477+
if (!skip_preimage && delta->status != GIT_DELTA_ADDED) {
457478
error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
458479
preimage_reader, delta->old_file.path);
459480

@@ -527,7 +548,7 @@ int git_apply_to_tree(
527548
const git_apply_options *given_opts)
528549
{
529550
git_index *postimage = NULL;
530-
git_reader *pre_reader = NULL;
551+
git_reader *pre_reader = NULL, *post_reader = NULL;
531552
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
532553
const git_diff_delta *delta;
533554
size_t i;
@@ -548,7 +569,8 @@ int git_apply_to_tree(
548569
* replace any entries contained therein
549570
*/
550571
if ((error = git_index_new(&postimage)) < 0 ||
551-
(error = git_index_read_tree(postimage, preimage)) < 0)
572+
(error = git_index_read_tree(postimage, preimage)) < 0 ||
573+
(error = git_reader_for_index(&post_reader, repo, postimage)) < 0)
552574
goto done;
553575

554576
/*
@@ -565,7 +587,7 @@ int git_apply_to_tree(
565587
}
566588

567589
for (i = 0; i < git_diff_num_deltas(diff); i++) {
568-
if ((error = apply_one(repo, pre_reader, NULL, postimage, diff, i, &opts)) < 0)
590+
if ((error = apply_one(repo, pre_reader, NULL, post_reader, postimage, diff, i, &opts)) < 0)
569591
goto done;
570592
}
571593

@@ -576,6 +598,7 @@ int git_apply_to_tree(
576598
git_index_free(postimage);
577599

578600
git_reader_free(pre_reader);
601+
git_reader_free(post_reader);
579602

580603
return error;
581604
}
@@ -700,7 +723,7 @@ int git_apply(
700723
{
701724
git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
702725
git_index *index = NULL, *preimage = NULL, *postimage = NULL;
703-
git_reader *pre_reader = NULL;
726+
git_reader *pre_reader = NULL, *post_reader = NULL;
704727
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
705728
size_t i;
706729
int error = GIT_EINVALID;
@@ -743,15 +766,16 @@ int git_apply(
743766
* to only write these files that were affected by the diff.
744767
*/
745768
if ((error = git_index_new(&preimage)) < 0 ||
746-
(error = git_index_new(&postimage)) < 0)
769+
(error = git_index_new(&postimage)) < 0 ||
770+
(error = git_reader_for_index(&post_reader, repo, postimage)) < 0)
747771
goto done;
748772

749773
if ((error = git_repository_index(&index, repo)) < 0 ||
750774
(error = git_indexwriter_init(&indexwriter, index)) < 0)
751775
goto done;
752776

753777
for (i = 0; i < git_diff_num_deltas(diff); i++) {
754-
if ((error = apply_one(repo, pre_reader, preimage, postimage, diff, i, &opts)) < 0)
778+
if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, i, &opts)) < 0)
755779
goto done;
756780
}
757781

@@ -780,6 +804,7 @@ int git_apply(
780804
git_index_free(preimage);
781805
git_index_free(index);
782806
git_reader_free(pre_reader);
807+
git_reader_free(post_reader);
783808

784809
return error;
785810
}

tests/apply/apply_helpers.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,28 @@
255255
"rename from asparagus.txt\n" \
256256
"rename to 2.txt\n"
257257

258+
#define DIFF_TWO_DELTAS_ONE_FILE \
259+
"diff --git a/beef.txt b/beef.txt\n" \
260+
"index 68f6182..235069d 100644\n" \
261+
"--- a/beef.txt\n" \
262+
"+++ b/beef.txt\n" \
263+
"@@ -1,4 +1,4 @@\n" \
264+
"-BEEF SOUP.\n" \
265+
"+BEEF SOUP!\n" \
266+
"\n" \
267+
" Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \
268+
" which must be taken away entirely, or the soup will be greasy. Wash the\n" \
269+
"diff --git a/beef.txt b/beef.txt\n" \
270+
"index 68f6182..e059eb5 100644\n" \
271+
"--- a/beef.txt\n" \
272+
"+++ b/beef.txt\n" \
273+
"@@ -19,4 +19,4 @@ a ladle full of the soup, a little at a time; stirring it all the while.\n" \
274+
" Strain this browning and mix it well with the soup; take out the bundle\n" \
275+
" of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \
276+
" pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \
277+
"-and serve it up.\n" \
278+
"+and serve it up!\n"
279+
258280
struct iterator_compare_data {
259281
struct merge_index_entry *expected;
260282
size_t cnt;

tests/apply/both.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,3 +574,28 @@ void test_apply_both__rename_1_to_2(void)
574574

575575
git_diff_free(diff);
576576
}
577+
578+
void test_apply_both__two_deltas_one_file(void)
579+
{
580+
git_diff *diff;
581+
582+
struct merge_index_entry both_expected[] = {
583+
{ 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" },
584+
{ 0100644, "0a9fd4415635e72573f0f6b5e68084cfe18f5075", 0, "beef.txt" },
585+
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
586+
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
587+
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
588+
{ 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }
589+
};
590+
size_t both_expected_cnt = sizeof(both_expected) /
591+
sizeof(struct merge_index_entry);
592+
593+
cl_git_pass(git_diff_from_buffer(&diff, DIFF_TWO_DELTAS_ONE_FILE,
594+
strlen(DIFF_TWO_DELTAS_ONE_FILE)));
595+
cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL));
596+
597+
validate_apply_index(repo, both_expected, both_expected_cnt);
598+
validate_apply_workdir(repo, both_expected, both_expected_cnt);
599+
600+
git_diff_free(diff);
601+
}

0 commit comments

Comments
 (0)