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 <newren@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Johannes Schindelin 2022-09-28 07:29:21 +00:00 committed by Junio C Hamano
parent 1b3d6e17fe
commit 0b55d930a6
2 changed files with 50 additions and 25 deletions

View File

@ -3571,15 +3571,15 @@ static int tree_entry_order(const void *a_, const void *b_)
b->string, strlen(b->string), bmi->result.mode); b->string, strlen(b->string), bmi->result.mode);
} }
static void write_tree(struct object_id *result_oid, static int write_tree(struct object_id *result_oid,
struct string_list *versions, struct string_list *versions,
unsigned int offset, unsigned int offset,
size_t hash_size) size_t hash_size)
{ {
size_t maxlen = 0, extra; size_t maxlen = 0, extra;
unsigned int nr; unsigned int nr;
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
int i; int i, ret = 0;
assert(offset <= versions->nr); assert(offset <= versions->nr);
nr = versions->nr - offset; nr = versions->nr - offset;
@ -3605,8 +3605,10 @@ static void write_tree(struct object_id *result_oid,
} }
/* Write this object file out, and record in result_oid */ /* Write this object file out, and record in result_oid */
write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid); if (write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid))
ret = -1;
strbuf_release(&buf); strbuf_release(&buf);
return ret;
} }
static void record_entry_for_tree(struct directory_versions *dir_metadata, 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,
basename)->util = &mi->result; basename)->util = &mi->result;
} }
static void write_completed_directory(struct merge_options *opt, static int write_completed_directory(struct merge_options *opt,
const char *new_directory_name, const char *new_directory_name,
struct directory_versions *info) struct directory_versions *info)
{ {
const char *prev_dir; const char *prev_dir;
struct merged_info *dir_info = NULL; struct merged_info *dir_info = NULL;
unsigned int offset; unsigned int offset, ret = 0;
/* /*
* Some explanation of info->versions and info->offsets... * Some explanation of info->versions and info->offsets...
@ -3717,7 +3719,7 @@ static void write_completed_directory(struct merge_options *opt,
* strcmp here.) * strcmp here.)
*/ */
if (new_directory_name == info->last_directory) if (new_directory_name == info->last_directory)
return; return 0;
/* /*
* If we are just starting (last_directory is NULL), or last_directory * 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,
*/ */
string_list_append(&info->offsets, string_list_append(&info->offsets,
info->last_directory)->util = (void*)offset; info->last_directory)->util = (void*)offset;
return; return 0;
} }
/* /*
@ -3769,8 +3771,9 @@ static void write_completed_directory(struct merge_options *opt,
*/ */
dir_info->is_null = 0; dir_info->is_null = 0;
dir_info->result.mode = S_IFDIR; dir_info->result.mode = S_IFDIR;
write_tree(&dir_info->result.oid, &info->versions, offset, if (write_tree(&dir_info->result.oid, &info->versions, offset,
opt->repo->hash_algo->rawsz); opt->repo->hash_algo->rawsz) < 0)
ret = -1;
} }
/* /*
@ -3798,6 +3801,8 @@ static void write_completed_directory(struct merge_options *opt,
/* And, of course, we need to update last_directory to match. */ /* And, of course, we need to update last_directory to match. */
info->last_directory = new_directory_name; info->last_directory = new_directory_name;
info->last_directory_len = strlen(info->last_directory); info->last_directory_len = strlen(info->last_directory);
return ret;
} }
/* Per entry merge function */ /* Per entry merge function */
@ -4214,8 +4219,8 @@ static void prefetch_for_content_merges(struct merge_options *opt,
oid_array_clear(&to_fetch); oid_array_clear(&to_fetch);
} }
static void process_entries(struct merge_options *opt, static int process_entries(struct merge_options *opt,
struct object_id *result_oid) struct object_id *result_oid)
{ {
struct hashmap_iter iter; struct hashmap_iter iter;
struct strmap_entry *e; struct strmap_entry *e;
@ -4224,11 +4229,12 @@ static void process_entries(struct merge_options *opt,
struct directory_versions dir_metadata = { STRING_LIST_INIT_NODUP, struct directory_versions dir_metadata = { STRING_LIST_INIT_NODUP,
STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP,
NULL, 0 }; NULL, 0 };
int ret = 0;
trace2_region_enter("merge", "process_entries setup", opt->repo); trace2_region_enter("merge", "process_entries setup", opt->repo);
if (strmap_empty(&opt->priv->paths)) { if (strmap_empty(&opt->priv->paths)) {
oidcpy(result_oid, opt->repo->hash_algo->empty_tree); oidcpy(result_oid, opt->repo->hash_algo->empty_tree);
return; return 0;
} }
/* Hack to pre-allocate plist to the desired size */ /* Hack to pre-allocate plist to the desired size */
@ -4270,8 +4276,11 @@ static void process_entries(struct merge_options *opt,
*/ */
struct merged_info *mi = entry->util; struct merged_info *mi = entry->util;
write_completed_directory(opt, mi->directory_name, if (write_completed_directory(opt, mi->directory_name,
&dir_metadata); &dir_metadata) < 0) {
ret = -1;
goto cleanup;
}
if (mi->clean) if (mi->clean)
record_entry_for_tree(&dir_metadata, path, mi); record_entry_for_tree(&dir_metadata, path, mi);
else { else {
@ -4291,12 +4300,16 @@ static void process_entries(struct merge_options *opt,
fflush(stdout); fflush(stdout);
BUG("dir_metadata accounting completely off; shouldn't happen"); BUG("dir_metadata accounting completely off; shouldn't happen");
} }
write_tree(result_oid, &dir_metadata.versions, 0, if (write_tree(result_oid, &dir_metadata.versions, 0,
opt->repo->hash_algo->rawsz); opt->repo->hash_algo->rawsz) < 0)
ret = -1;
cleanup:
string_list_clear(&plist, 0); string_list_clear(&plist, 0);
string_list_clear(&dir_metadata.versions, 0); string_list_clear(&dir_metadata.versions, 0);
string_list_clear(&dir_metadata.offsets, 0); string_list_clear(&dir_metadata.offsets, 0);
trace2_region_leave("merge", "process_entries cleanup", opt->repo); trace2_region_leave("merge", "process_entries cleanup", opt->repo);
return ret;
} }
/*** Function Grouping: functions related to merge_switch_to_result() ***/ /*** Function Grouping: functions related to merge_switch_to_result() ***/
@ -4928,15 +4941,18 @@ redo:
} }
trace2_region_enter("merge", "process_entries", opt->repo); trace2_region_enter("merge", "process_entries", opt->repo);
process_entries(opt, &working_tree_oid); if (process_entries(opt, &working_tree_oid) < 0)
result->clean = -1;
trace2_region_leave("merge", "process_entries", opt->repo); trace2_region_leave("merge", "process_entries", opt->repo);
/* Set return values */ /* Set return values */
result->path_messages = &opt->priv->conflicts; result->path_messages = &opt->priv->conflicts;
result->tree = parse_tree_indirect(&working_tree_oid); if (result->clean >= 0) {
/* existence of conflicted entries implies unclean */ result->tree = parse_tree_indirect(&working_tree_oid);
result->clean &= strmap_empty(&opt->priv->conflicted); /* existence of conflicted entries implies unclean */
result->clean &= strmap_empty(&opt->priv->conflicted);
}
if (!opt->priv->call_depth) { if (!opt->priv->call_depth) {
result->priv = opt->priv; result->priv = opt->priv;
result->_properly_initialized = RESULT_INITIALIZED; result->_properly_initialized = RESULT_INITIALIZED;

View File

@ -810,4 +810,13 @@ test_expect_success 'can override merge of unrelated histories' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success SANITY 'merge-ort fails gracefully in a read-only repository' '
git init --bare read-only &&
git push read-only side1 side2 side3 &&
test_when_finished "chmod -R u+w read-only" &&
chmod -R a-w read-only &&
test_must_fail git -C read-only merge-tree side1 side3 &&
test_must_fail git -C read-only merge-tree side1 side2
'
test_done test_done