Skip to content

Commit 0b55d93

Browse files
dschogitster
authored andcommitted
merge-ort: fix segmentation fault in read-only repositories
If the blob/tree objects cannot be written, we really need the merge operations to fail, and not to continue (and then try to access the tree object which is however still set to `NULL`). Let's stop ignoring the return value of `write_object_file()` and `write_tree()` and set `clean = -1` in the error case. Reviewed-by: Elijah Newren <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1b3d6e1 commit 0b55d93

File tree

2 files changed

+50
-25
lines changed

2 files changed

+50
-25
lines changed

merge-ort.c

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3571,15 +3571,15 @@ static int tree_entry_order(const void *a_, const void *b_)
35713571
b->string, strlen(b->string), bmi->result.mode);
35723572
}
35733573

3574-
static void write_tree(struct object_id *result_oid,
3575-
struct string_list *versions,
3576-
unsigned int offset,
3577-
size_t hash_size)
3574+
static int write_tree(struct object_id *result_oid,
3575+
struct string_list *versions,
3576+
unsigned int offset,
3577+
size_t hash_size)
35783578
{
35793579
size_t maxlen = 0, extra;
35803580
unsigned int nr;
35813581
struct strbuf buf = STRBUF_INIT;
3582-
int i;
3582+
int i, ret = 0;
35833583

35843584
assert(offset <= versions->nr);
35853585
nr = versions->nr - offset;
@@ -3605,8 +3605,10 @@ static void write_tree(struct object_id *result_oid,
36053605
}
36063606

36073607
/* Write this object file out, and record in result_oid */
3608-
write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid);
3608+
if (write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid))
3609+
ret = -1;
36093610
strbuf_release(&buf);
3611+
return ret;
36103612
}
36113613

36123614
static void record_entry_for_tree(struct directory_versions *dir_metadata,
@@ -3625,13 +3627,13 @@ static void record_entry_for_tree(struct directory_versions *dir_metadata,
36253627
basename)->util = &mi->result;
36263628
}
36273629

3628-
static void write_completed_directory(struct merge_options *opt,
3629-
const char *new_directory_name,
3630-
struct directory_versions *info)
3630+
static int write_completed_directory(struct merge_options *opt,
3631+
const char *new_directory_name,
3632+
struct directory_versions *info)
36313633
{
36323634
const char *prev_dir;
36333635
struct merged_info *dir_info = NULL;
3634-
unsigned int offset;
3636+
unsigned int offset, ret = 0;
36353637

36363638
/*
36373639
* Some explanation of info->versions and info->offsets...
@@ -3717,7 +3719,7 @@ static void write_completed_directory(struct merge_options *opt,
37173719
* strcmp here.)
37183720
*/
37193721
if (new_directory_name == info->last_directory)
3720-
return;
3722+
return 0;
37213723

37223724
/*
37233725
* If we are just starting (last_directory is NULL), or last_directory
@@ -3739,7 +3741,7 @@ static void write_completed_directory(struct merge_options *opt,
37393741
*/
37403742
string_list_append(&info->offsets,
37413743
info->last_directory)->util = (void*)offset;
3742-
return;
3744+
return 0;
37433745
}
37443746

37453747
/*
@@ -3769,8 +3771,9 @@ static void write_completed_directory(struct merge_options *opt,
37693771
*/
37703772
dir_info->is_null = 0;
37713773
dir_info->result.mode = S_IFDIR;
3772-
write_tree(&dir_info->result.oid, &info->versions, offset,
3773-
opt->repo->hash_algo->rawsz);
3774+
if (write_tree(&dir_info->result.oid, &info->versions, offset,
3775+
opt->repo->hash_algo->rawsz) < 0)
3776+
ret = -1;
37743777
}
37753778

37763779
/*
@@ -3798,6 +3801,8 @@ static void write_completed_directory(struct merge_options *opt,
37983801
/* And, of course, we need to update last_directory to match. */
37993802
info->last_directory = new_directory_name;
38003803
info->last_directory_len = strlen(info->last_directory);
3804+
3805+
return ret;
38013806
}
38023807

38033808
/* Per entry merge function */
@@ -4214,8 +4219,8 @@ static void prefetch_for_content_merges(struct merge_options *opt,
42144219
oid_array_clear(&to_fetch);
42154220
}
42164221

4217-
static void process_entries(struct merge_options *opt,
4218-
struct object_id *result_oid)
4222+
static int process_entries(struct merge_options *opt,
4223+
struct object_id *result_oid)
42194224
{
42204225
struct hashmap_iter iter;
42214226
struct strmap_entry *e;
@@ -4224,11 +4229,12 @@ static void process_entries(struct merge_options *opt,
42244229
struct directory_versions dir_metadata = { STRING_LIST_INIT_NODUP,
42254230
STRING_LIST_INIT_NODUP,
42264231
NULL, 0 };
4232+
int ret = 0;
42274233

42284234
trace2_region_enter("merge", "process_entries setup", opt->repo);
42294235
if (strmap_empty(&opt->priv->paths)) {
42304236
oidcpy(result_oid, opt->repo->hash_algo->empty_tree);
4231-
return;
4237+
return 0;
42324238
}
42334239

42344240
/* Hack to pre-allocate plist to the desired size */
@@ -4270,8 +4276,11 @@ static void process_entries(struct merge_options *opt,
42704276
*/
42714277
struct merged_info *mi = entry->util;
42724278

4273-
write_completed_directory(opt, mi->directory_name,
4274-
&dir_metadata);
4279+
if (write_completed_directory(opt, mi->directory_name,
4280+
&dir_metadata) < 0) {
4281+
ret = -1;
4282+
goto cleanup;
4283+
}
42754284
if (mi->clean)
42764285
record_entry_for_tree(&dir_metadata, path, mi);
42774286
else {
@@ -4291,12 +4300,16 @@ static void process_entries(struct merge_options *opt,
42914300
fflush(stdout);
42924301
BUG("dir_metadata accounting completely off; shouldn't happen");
42934302
}
4294-
write_tree(result_oid, &dir_metadata.versions, 0,
4295-
opt->repo->hash_algo->rawsz);
4303+
if (write_tree(result_oid, &dir_metadata.versions, 0,
4304+
opt->repo->hash_algo->rawsz) < 0)
4305+
ret = -1;
4306+
cleanup:
42964307
string_list_clear(&plist, 0);
42974308
string_list_clear(&dir_metadata.versions, 0);
42984309
string_list_clear(&dir_metadata.offsets, 0);
42994310
trace2_region_leave("merge", "process_entries cleanup", opt->repo);
4311+
4312+
return ret;
43004313
}
43014314

43024315
/*** Function Grouping: functions related to merge_switch_to_result() ***/
@@ -4928,15 +4941,18 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
49284941
}
49294942

49304943
trace2_region_enter("merge", "process_entries", opt->repo);
4931-
process_entries(opt, &working_tree_oid);
4944+
if (process_entries(opt, &working_tree_oid) < 0)
4945+
result->clean = -1;
49324946
trace2_region_leave("merge", "process_entries", opt->repo);
49334947

49344948
/* Set return values */
49354949
result->path_messages = &opt->priv->conflicts;
49364950

4937-
result->tree = parse_tree_indirect(&working_tree_oid);
4938-
/* existence of conflicted entries implies unclean */
4939-
result->clean &= strmap_empty(&opt->priv->conflicted);
4951+
if (result->clean >= 0) {
4952+
result->tree = parse_tree_indirect(&working_tree_oid);
4953+
/* existence of conflicted entries implies unclean */
4954+
result->clean &= strmap_empty(&opt->priv->conflicted);
4955+
}
49404956
if (!opt->priv->call_depth) {
49414957
result->priv = opt->priv;
49424958
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)