Skip to content

Commit 19118cb

Browse files
committed
Merge branch 'js/merge-ort-in-read-only-repo'
In read-only repositories, "git merge-tree" tried to come up with a merge result tree object, which it failed (which is not wrong) and led to a segfault (which is bad), which has been corrected. * js/merge-ort-in-read-only-repo: merge-ort: return early when failing to write a blob merge-ort: fix segmentation fault in read-only repositories
2 parents a215853 + 92481d1 commit 19118cb

File tree

2 files changed

+69
-34
lines changed

2 files changed

+69
-34
lines changed

merge-ort.c

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2807,6 +2807,8 @@ static int process_renames(struct merge_options *opt,
28072807
pathnames,
28082808
1 + 2 * opt->priv->call_depth,
28092809
&merged);
2810+
if (clean_merge < 0)
2811+
return -1;
28102812
if (!clean_merge &&
28112813
merged.mode == side1->stages[1].mode &&
28122814
oideq(&merged.oid, &side1->stages[1].oid))
@@ -2916,7 +2918,7 @@ static int process_renames(struct merge_options *opt,
29162918
struct version_info merged;
29172919

29182920
struct conflict_info *base, *side1, *side2;
2919-
unsigned clean;
2921+
int clean;
29202922

29212923
pathnames[0] = oldpath;
29222924
pathnames[other_source_index] = oldpath;
@@ -2937,6 +2939,8 @@ static int process_renames(struct merge_options *opt,
29372939
pathnames,
29382940
1 + 2 * opt->priv->call_depth,
29392941
&merged);
2942+
if (clean < 0)
2943+
return -1;
29402944

29412945
memcpy(&newinfo->stages[target_index], &merged,
29422946
sizeof(merged));
@@ -3571,15 +3575,15 @@ static int tree_entry_order(const void *a_, const void *b_)
35713575
b->string, strlen(b->string), bmi->result.mode);
35723576
}
35733577

3574-
static void write_tree(struct object_id *result_oid,
3575-
struct string_list *versions,
3576-
unsigned int offset,
3577-
size_t hash_size)
3578+
static int write_tree(struct object_id *result_oid,
3579+
struct string_list *versions,
3580+
unsigned int offset,
3581+
size_t hash_size)
35783582
{
35793583
size_t maxlen = 0, extra;
35803584
unsigned int nr;
35813585
struct strbuf buf = STRBUF_INIT;
3582-
int i;
3586+
int i, ret = 0;
35833587

35843588
assert(offset <= versions->nr);
35853589
nr = versions->nr - offset;
@@ -3605,8 +3609,10 @@ static void write_tree(struct object_id *result_oid,
36053609
}
36063610

36073611
/* Write this object file out, and record in result_oid */
3608-
write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid);
3612+
if (write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid))
3613+
ret = -1;
36093614
strbuf_release(&buf);
3615+
return ret;
36103616
}
36113617

36123618
static void record_entry_for_tree(struct directory_versions *dir_metadata,
@@ -3625,13 +3631,13 @@ static void record_entry_for_tree(struct directory_versions *dir_metadata,
36253631
basename)->util = &mi->result;
36263632
}
36273633

3628-
static void write_completed_directory(struct merge_options *opt,
3629-
const char *new_directory_name,
3630-
struct directory_versions *info)
3634+
static int write_completed_directory(struct merge_options *opt,
3635+
const char *new_directory_name,
3636+
struct directory_versions *info)
36313637
{
36323638
const char *prev_dir;
36333639
struct merged_info *dir_info = NULL;
3634-
unsigned int offset;
3640+
unsigned int offset, ret = 0;
36353641

36363642
/*
36373643
* Some explanation of info->versions and info->offsets...
@@ -3717,7 +3723,7 @@ static void write_completed_directory(struct merge_options *opt,
37173723
* strcmp here.)
37183724
*/
37193725
if (new_directory_name == info->last_directory)
3720-
return;
3726+
return 0;
37213727

37223728
/*
37233729
* If we are just starting (last_directory is NULL), or last_directory
@@ -3739,7 +3745,7 @@ static void write_completed_directory(struct merge_options *opt,
37393745
*/
37403746
string_list_append(&info->offsets,
37413747
info->last_directory)->util = (void*)offset;
3742-
return;
3748+
return 0;
37433749
}
37443750

37453751
/*
@@ -3769,8 +3775,9 @@ static void write_completed_directory(struct merge_options *opt,
37693775
*/
37703776
dir_info->is_null = 0;
37713777
dir_info->result.mode = S_IFDIR;
3772-
write_tree(&dir_info->result.oid, &info->versions, offset,
3773-
opt->repo->hash_algo->rawsz);
3778+
if (write_tree(&dir_info->result.oid, &info->versions, offset,
3779+
opt->repo->hash_algo->rawsz) < 0)
3780+
ret = -1;
37743781
}
37753782

37763783
/*
@@ -3798,13 +3805,15 @@ static void write_completed_directory(struct merge_options *opt,
37983805
/* And, of course, we need to update last_directory to match. */
37993806
info->last_directory = new_directory_name;
38003807
info->last_directory_len = strlen(info->last_directory);
3808+
3809+
return ret;
38013810
}
38023811

38033812
/* Per entry merge function */
3804-
static void process_entry(struct merge_options *opt,
3805-
const char *path,
3806-
struct conflict_info *ci,
3807-
struct directory_versions *dir_metadata)
3813+
static int process_entry(struct merge_options *opt,
3814+
const char *path,
3815+
struct conflict_info *ci,
3816+
struct directory_versions *dir_metadata)
38083817
{
38093818
int df_file_index = 0;
38103819

@@ -3818,7 +3827,7 @@ static void process_entry(struct merge_options *opt,
38183827
record_entry_for_tree(dir_metadata, path, &ci->merged);
38193828
if (ci->filemask == 0)
38203829
/* nothing else to handle */
3821-
return;
3830+
return 0;
38223831
assert(ci->df_conflict);
38233832
}
38243833

@@ -3865,7 +3874,7 @@ static void process_entry(struct merge_options *opt,
38653874
*/
38663875
if (ci->filemask == 1) {
38673876
ci->filemask = 0;
3868-
return;
3877+
return 0;
38693878
}
38703879

38713880
/*
@@ -4060,7 +4069,7 @@ static void process_entry(struct merge_options *opt,
40604069
} else if (ci->filemask >= 6) {
40614070
/* Need a two-way or three-way content merge */
40624071
struct version_info merged_file;
4063-
unsigned clean_merge;
4072+
int clean_merge;
40644073
struct version_info *o = &ci->stages[0];
40654074
struct version_info *a = &ci->stages[1];
40664075
struct version_info *b = &ci->stages[2];
@@ -4069,6 +4078,8 @@ static void process_entry(struct merge_options *opt,
40694078
ci->pathnames,
40704079
opt->priv->call_depth * 2,
40714080
&merged_file);
4081+
if (clean_merge < 0)
4082+
return -1;
40724083
ci->merged.clean = clean_merge &&
40734084
!ci->df_conflict && !ci->path_conflict;
40744085
ci->merged.result.mode = merged_file.mode;
@@ -4164,6 +4175,7 @@ static void process_entry(struct merge_options *opt,
41644175

41654176
/* Record metadata for ci->merged in dir_metadata */
41664177
record_entry_for_tree(dir_metadata, path, &ci->merged);
4178+
return 0;
41674179
}
41684180

41694181
static void prefetch_for_content_merges(struct merge_options *opt,
@@ -4214,8 +4226,8 @@ static void prefetch_for_content_merges(struct merge_options *opt,
42144226
oid_array_clear(&to_fetch);
42154227
}
42164228

4217-
static void process_entries(struct merge_options *opt,
4218-
struct object_id *result_oid)
4229+
static int process_entries(struct merge_options *opt,
4230+
struct object_id *result_oid)
42194231
{
42204232
struct hashmap_iter iter;
42214233
struct strmap_entry *e;
@@ -4224,11 +4236,12 @@ static void process_entries(struct merge_options *opt,
42244236
struct directory_versions dir_metadata = { STRING_LIST_INIT_NODUP,
42254237
STRING_LIST_INIT_NODUP,
42264238
NULL, 0 };
4239+
int ret = 0;
42274240

42284241
trace2_region_enter("merge", "process_entries setup", opt->repo);
42294242
if (strmap_empty(&opt->priv->paths)) {
42304243
oidcpy(result_oid, opt->repo->hash_algo->empty_tree);
4231-
return;
4244+
return 0;
42324245
}
42334246

42344247
/* Hack to pre-allocate plist to the desired size */
@@ -4270,13 +4283,19 @@ static void process_entries(struct merge_options *opt,
42704283
*/
42714284
struct merged_info *mi = entry->util;
42724285

4273-
write_completed_directory(opt, mi->directory_name,
4274-
&dir_metadata);
4286+
if (write_completed_directory(opt, mi->directory_name,
4287+
&dir_metadata) < 0) {
4288+
ret = -1;
4289+
goto cleanup;
4290+
}
42754291
if (mi->clean)
42764292
record_entry_for_tree(&dir_metadata, path, mi);
42774293
else {
42784294
struct conflict_info *ci = (struct conflict_info *)mi;
4279-
process_entry(opt, path, ci, &dir_metadata);
4295+
if (process_entry(opt, path, ci, &dir_metadata) < 0) {
4296+
ret = -1;
4297+
goto cleanup;
4298+
};
42804299
}
42814300
}
42824301
trace2_region_leave("merge", "processing", opt->repo);
@@ -4291,12 +4310,16 @@ static void process_entries(struct merge_options *opt,
42914310
fflush(stdout);
42924311
BUG("dir_metadata accounting completely off; shouldn't happen");
42934312
}
4294-
write_tree(result_oid, &dir_metadata.versions, 0,
4295-
opt->repo->hash_algo->rawsz);
4313+
if (write_tree(result_oid, &dir_metadata.versions, 0,
4314+
opt->repo->hash_algo->rawsz) < 0)
4315+
ret = -1;
4316+
cleanup:
42964317
string_list_clear(&plist, 0);
42974318
string_list_clear(&dir_metadata.versions, 0);
42984319
string_list_clear(&dir_metadata.offsets, 0);
42994320
trace2_region_leave("merge", "process_entries cleanup", opt->repo);
4321+
4322+
return ret;
43004323
}
43014324

43024325
/*** Function Grouping: functions related to merge_switch_to_result() ***/
@@ -4928,15 +4951,18 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
49284951
}
49294952

49304953
trace2_region_enter("merge", "process_entries", opt->repo);
4931-
process_entries(opt, &working_tree_oid);
4954+
if (process_entries(opt, &working_tree_oid) < 0)
4955+
result->clean = -1;
49324956
trace2_region_leave("merge", "process_entries", opt->repo);
49334957

49344958
/* Set return values */
49354959
result->path_messages = &opt->priv->conflicts;
49364960

4937-
result->tree = parse_tree_indirect(&working_tree_oid);
4938-
/* existence of conflicted entries implies unclean */
4939-
result->clean &= strmap_empty(&opt->priv->conflicted);
4961+
if (result->clean >= 0) {
4962+
result->tree = parse_tree_indirect(&working_tree_oid);
4963+
/* existence of conflicted entries implies unclean */
4964+
result->clean &= strmap_empty(&opt->priv->conflicted);
4965+
}
49404966
if (!opt->priv->call_depth) {
49414967
result->priv = opt->priv;
49424968
result->_properly_initialized = RESULT_INITIALIZED;

t/t4301-merge-tree-write-tree.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,4 +810,13 @@ test_expect_success 'can override merge of unrelated histories' '
810810
test_cmp expect actual
811811
'
812812

813+
test_expect_success SANITY 'merge-ort fails gracefully in a read-only repository' '
814+
git init --bare read-only &&
815+
git push read-only side1 side2 side3 &&
816+
test_when_finished "chmod -R u+w read-only" &&
817+
chmod -R a-w read-only &&
818+
test_must_fail git -C read-only merge-tree side1 side3 &&
819+
test_must_fail git -C read-only merge-tree side1 side2
820+
'
821+
813822
test_done

0 commit comments

Comments
 (0)