Skip to content

Commit 6fecf4d

Browse files
committed
apply: handle exact renames
Deltas containing exact renames are special; they simple indicate that a file was renamed without providing additional metadata (like the filemode). Teach the reader to provide the file mode and use the preimage's filemode in the case that the delta does not provide one.)
1 parent 12f9ac1 commit 6fecf4d

File tree

5 files changed

+54
-4
lines changed

5 files changed

+54
-4
lines changed

src/apply.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ static int apply_one(
433433
char *filename = NULL;
434434
unsigned int mode;
435435
git_oid pre_id, post_id;
436+
git_filemode_t pre_filemode;
436437
git_index_entry pre_entry, post_entry;
437438
int error;
438439

@@ -453,7 +454,7 @@ static int apply_one(
453454
}
454455

455456
if (delta->status != GIT_DELTA_ADDED) {
456-
error = git_reader_read(&pre_contents, &pre_id,
457+
error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
457458
preimage_reader, delta->old_file.path);
458459

459460
/* ENOTFOUND means the preimage was not found; apply failed. */
@@ -477,11 +478,15 @@ static int apply_one(
477478
* application. (Without this, we would fail to write the
478479
* postimage contents to any file that had been modified
479480
* from HEAD on-disk, even if the patch application succeeded.)
481+
* Use the contents from the delta where available - some
482+
* fields may not be available, like the old file mode (eg in
483+
* an exact rename situation) so trust the patch parsing to
484+
* validate and use the preimage data in that case.
480485
*/
481486
if (preimage) {
482487
memset(&pre_entry, 0, sizeof(git_index_entry));
483488
pre_entry.path = delta->old_file.path;
484-
pre_entry.mode = delta->old_file.mode;
489+
pre_entry.mode = delta->old_file.mode ? delta->old_file.mode : pre_filemode;
485490
git_oid_cpy(&pre_entry.id, &pre_id);
486491

487492
if ((error = git_index_add(preimage, &pre_entry)) < 0)

src/reader.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ typedef struct {
2525
static int tree_reader_read(
2626
git_buf *out,
2727
git_oid *out_id,
28+
git_filemode_t *out_filemode,
2829
git_reader *_reader,
2930
const char *filename)
3031
{
@@ -41,6 +42,9 @@ static int tree_reader_read(
4142
if (out_id)
4243
git_oid_cpy(out_id, git_tree_entry_id(tree_entry));
4344

45+
if (out_filemode)
46+
*out_filemode = git_tree_entry_filemode(tree_entry);
47+
4448
done:
4549
git_blob_free(blob);
4650
git_tree_entry_free(tree_entry);
@@ -74,6 +78,7 @@ typedef struct {
7478
static int workdir_reader_read(
7579
git_buf *out,
7680
git_oid *out_id,
81+
git_filemode_t *out_filemode,
7782
git_reader *_reader,
7883
const char *filename)
7984
{
@@ -130,6 +135,9 @@ static int workdir_reader_read(
130135
if (out_id)
131136
git_oid_cpy(out_id, &id);
132137

138+
if (out_filemode)
139+
*out_filemode = filemode;
140+
133141
done:
134142
git_filter_list_free(filters);
135143
git_buf_dispose(&path);
@@ -173,6 +181,7 @@ typedef struct {
173181
static int index_reader_read(
174182
git_buf *out,
175183
git_oid *out_id,
184+
git_filemode_t *out_filemode,
176185
git_reader *_reader,
177186
const char *filename)
178187
{
@@ -190,6 +199,9 @@ static int index_reader_read(
190199
if (out_id)
191200
git_oid_cpy(out_id, &entry->id);
192201

202+
if (out_filemode)
203+
*out_filemode = entry->mode;
204+
193205
error = git_blob__getbuf(out, blob);
194206

195207
done:
@@ -229,12 +241,13 @@ int git_reader_for_index(
229241
int git_reader_read(
230242
git_buf *out,
231243
git_oid *out_id,
244+
git_filemode_t *out_filemode,
232245
git_reader *reader,
233246
const char *filename)
234247
{
235248
assert(out && reader && filename);
236249

237-
return reader->read(out, out_id, reader, filename);
250+
return reader->read(out, out_id, out_filemode, reader, filename);
238251
}
239252

240253
void git_reader_free(git_reader *reader)

src/reader.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ typedef struct git_reader git_reader;
2525
* reader after disposing the underlying object that it reads.
2626
*/
2727
struct git_reader {
28-
int (*read)(git_buf *out, git_oid *out_oid, git_reader *reader, const char *filename);
28+
int (*read)(git_buf *out, git_oid *out_oid, git_filemode_t *mode, git_reader *reader, const char *filename);
2929
};
3030

3131
/**
@@ -93,6 +93,7 @@ extern int git_reader_for_workdir(
9393
extern int git_reader_read(
9494
git_buf *out,
9595
git_oid *out_id,
96+
git_filemode_t *out_filemode,
9697
git_reader *reader,
9798
const char *filename);
9899

tests/apply/apply_helpers.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@
112112
"+but the shin OR knuckle is the nicest.\n" \
113113
"+Another new line.\n" \
114114

115+
#define DIFF_RENAME_FILE \
116+
"diff --git a/beef.txt b/notbeef.txt\n" \
117+
"similarity index 100%\n" \
118+
"rename from beef.txt\n" \
119+
"rename to notbeef.txt\n"
120+
115121
#define DIFF_RENAME_AND_MODIFY_FILE \
116122
"diff --git a/beef.txt b/notbeef.txt\n" \
117123
"similarity index 97%\n" \

tests/apply/both.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,31 @@ void test_apply_both__honors_crlf_attributes(void)
400400
git_diff_free(diff);
401401
}
402402

403+
void test_apply_both__rename(void)
404+
{
405+
git_diff *diff;
406+
407+
struct merge_index_entry both_expected[] = {
408+
{ 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" },
409+
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
410+
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
411+
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" },
412+
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
413+
{ 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" },
414+
};
415+
size_t both_expected_cnt = sizeof(both_expected) /
416+
sizeof(struct merge_index_entry);
417+
418+
cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_FILE,
419+
strlen(DIFF_RENAME_FILE)));
420+
cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL));
421+
422+
validate_apply_index(repo, both_expected, both_expected_cnt);
423+
validate_apply_workdir(repo, both_expected, both_expected_cnt);
424+
425+
git_diff_free(diff);
426+
}
427+
403428
void test_apply_both__rename_and_modify(void)
404429
{
405430
git_diff *diff;

0 commit comments

Comments
 (0)