#include "git-compat-util.h"
#include "repack.h"
#include "hash.h"
#include "hex.h"
#include "lockfile.h"
#include "midx.h"
#include "odb.h"
#include "oidset.h"
#include "pack-bitmap.h"
#include "path.h"
#include "refs.h"
#include "run-command.h"
#include "tempfile.h"
#include "trace2.h"

struct midx_snapshot_ref_data {
	struct repository *repo;
	struct tempfile *f;
	struct oidset seen;
	int preferred;
};

static int midx_snapshot_ref_one(const struct reference *ref, void *_data)
{
	struct midx_snapshot_ref_data *data = _data;
	const struct object_id *maybe_peeled = ref->oid;
	struct object_id peeled;

	if (!reference_get_peeled_oid(data->repo, ref, &peeled))
		maybe_peeled = &peeled;

	if (oidset_insert(&data->seen, maybe_peeled))
		return 0; /* already seen */

	if (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) != OBJ_COMMIT)
		return 0;

	fprintf(data->f->fp, "%s%s\n", data->preferred ? "+" : "",
		oid_to_hex(maybe_peeled));

	return 0;
}

void midx_snapshot_refs(struct repository *repo, struct tempfile *f)
{
	struct midx_snapshot_ref_data data;

	data.repo = repo;
	data.f = f;
	data.preferred = 0;
	oidset_init(&data.seen, 0);

	if (!fdopen_tempfile(f, "w"))
		 die(_("could not open tempfile %s for writing"),
		     get_tempfile_path(f));

	data.preferred = 1;
	for_each_preferred_bitmap_tip(repo, midx_snapshot_ref_one, &data);
	data.preferred = 0;

	refs_for_each_ref(get_main_ref_store(repo),
			  midx_snapshot_ref_one, &data);

	if (close_tempfile_gently(f)) {
		int save_errno = errno;
		delete_tempfile(&f);
		errno = save_errno;
		die_errno(_("could not close refs snapshot tempfile"));
	}

	oidset_clear(&data.seen);
}

static int midx_has_unknown_packs(struct string_list *include,
				  struct pack_geometry *geometry,
				  struct existing_packs *existing)
{
	struct string_list_item *item;

	string_list_sort(include);

	for_each_string_list_item(item, &existing->midx_packs) {
		const char *pack_name = item->string;

		/*
		 * Determine whether or not each MIDX'd pack from the existing
		 * MIDX (if any) is represented in the new MIDX. For each pack
		 * in the MIDX, it must either be:
		 *
		 *  - In the "include" list of packs to be included in the new
		 *    MIDX. Note this function is called before the include
		 *    list is populated with any cruft pack(s).
		 *
		 *  - Below the geometric split line (if using pack geometry),
		 *    indicating that the pack won't be included in the new
		 *    MIDX, but its contents were rolled up as part of the
		 *    geometric repack.
		 *
		 *  - In the existing non-kept packs list (if not using pack
		 *    geometry), and marked as non-deleted.
		 */
		if (string_list_has_string(include, pack_name)) {
			continue;
		} else if (geometry) {
			struct strbuf buf = STRBUF_INIT;
			uint32_t j;

			for (j = 0; j < geometry->split; j++) {
				strbuf_reset(&buf);
				strbuf_addstr(&buf, pack_basename(geometry->pack[j]));
				strbuf_strip_suffix(&buf, ".pack");
				strbuf_addstr(&buf, ".idx");

				if (!strcmp(pack_name, buf.buf)) {
					strbuf_release(&buf);
					break;
				}
			}

			strbuf_release(&buf);

			if (j < geometry->split)
				continue;
		} else {
			struct string_list_item *item;

			item = string_list_lookup(&existing->non_kept_packs,
						  pack_name);
			if (item && !existing_pack_is_marked_for_deletion(item))
				continue;
		}

		/*
		 * If we got to this point, the MIDX includes some pack that we
		 * don't know about.
		 */
		return 1;
	}

	return 0;
}

static void midx_included_packs(struct string_list *include,
				struct repack_write_midx_opts *opts)
{
	struct existing_packs *existing = opts->existing;
	struct pack_geometry *geometry = opts->geometry;
	struct string_list *names = opts->names;
	struct string_list_item *item;
	struct strbuf buf = STRBUF_INIT;

	for_each_string_list_item(item, &existing->kept_packs) {
		strbuf_reset(&buf);
		strbuf_addf(&buf, "%s.idx", item->string);
		string_list_insert(include, buf.buf);
	}

	for_each_string_list_item(item, names) {
		strbuf_reset(&buf);
		strbuf_addf(&buf, "pack-%s.idx", item->string);
		string_list_insert(include, buf.buf);
	}

	if (geometry->split_factor) {
		uint32_t i;

		for (i = geometry->split; i < geometry->pack_nr; i++) {
			struct packed_git *p = geometry->pack[i];

			/*
			 * The multi-pack index never refers to packfiles part
			 * of an alternate object database, so we skip these.
			 * While git-multi-pack-index(1) would silently ignore
			 * them anyway, this allows us to skip executing the
			 * command completely when we have only non-local
			 * packfiles.
			 */
			if (!p->pack_local)
				continue;

			strbuf_reset(&buf);
			strbuf_addstr(&buf, pack_basename(p));
			strbuf_strip_suffix(&buf, ".pack");
			strbuf_addstr(&buf, ".idx");

			string_list_insert(include, buf.buf);
		}
	} else {
		for_each_string_list_item(item, &existing->non_kept_packs) {
			if (existing_pack_is_marked_for_deletion(item))
				continue;

			strbuf_reset(&buf);
			strbuf_addf(&buf, "%s.idx", item->string);
			string_list_insert(include, buf.buf);
		}
	}

	if (opts->midx_must_contain_cruft ||
	    midx_has_unknown_packs(include, geometry, existing)) {
		/*
		 * If there are one or more unknown pack(s) present (see
		 * midx_has_unknown_packs() for what makes a pack
		 * "unknown") in the MIDX before the repack, keep them
		 * as they may be required to form a reachability
		 * closure if the MIDX is bitmapped.
		 *
		 * For example, a cruft pack can be required to form a
		 * reachability closure if the MIDX is bitmapped and one
		 * or more of the bitmap's selected commits reaches a
		 * once-cruft object that was later made reachable.
		 */
		for_each_string_list_item(item, &existing->cruft_packs) {
			/*
			 * When doing a --geometric repack, there is no
			 * need to check for deleted packs, since we're
			 * by definition not doing an ALL_INTO_ONE
			 * repack (hence no packs will be deleted).
			 * Otherwise we must check for and exclude any
			 * packs which are enqueued for deletion.
			 *
			 * So we could omit the conditional below in the
			 * --geometric case, but doing so is unnecessary
			 *  since no packs are marked as pending
			 *  deletion (since we only call
			 *  `existing_packs_mark_for_deletion()` when
			 *  doing an all-into-one repack).
			 */
			if (existing_pack_is_marked_for_deletion(item))
				continue;

			strbuf_reset(&buf);
			strbuf_addf(&buf, "%s.idx", item->string);
			string_list_insert(include, buf.buf);
		}
	} else {
		/*
		 * Modern versions of Git (with the appropriate
		 * configuration setting) will write new copies of
		 * once-cruft objects when doing a --geometric repack.
		 *
		 * If the MIDX has no cruft pack, new packs written
		 * during a --geometric repack will not rely on the
		 * cruft pack to form a reachability closure, so we can
		 * avoid including them in the MIDX in that case.
		 */
		;
	}

	strbuf_release(&buf);
}

static void remove_redundant_bitmaps(struct string_list *include,
				     const char *packdir)
{
	struct strbuf path = STRBUF_INIT;
	struct string_list_item *item;
	size_t packdir_len;

	strbuf_addstr(&path, packdir);
	strbuf_addch(&path, '/');
	packdir_len = path.len;

	/*
	 * Remove any pack bitmaps corresponding to packs which are now
	 * included in the MIDX.
	 */
	for_each_string_list_item(item, include) {
		strbuf_addstr(&path, item->string);
		strbuf_strip_suffix(&path, ".idx");
		strbuf_addstr(&path, ".bitmap");

		if (unlink(path.buf) && errno != ENOENT)
			warning_errno(_("could not remove stale bitmap: %s"),
				      path.buf);

		strbuf_setlen(&path, packdir_len);
	}
	strbuf_release(&path);
}

static void repack_prepare_midx_command(struct child_process *cmd,
					struct repack_write_midx_opts *opts,
					const char *subcommand)
{
	cmd->git_cmd = 1;

	strvec_pushl(&cmd->args, "multi-pack-index", subcommand, NULL);

	if (opts->show_progress)
		strvec_push(&cmd->args, "--progress");
	else
		strvec_push(&cmd->args, "--no-progress");

	if (opts->write_bitmaps)
		strvec_push(&cmd->args, "--bitmap");
}

static int repack_fill_midx_stdin_packs(struct child_process *cmd,
					struct string_list *include,
					struct string_list *out)
{
	struct strbuf in_buf = STRBUF_INIT;
	struct strbuf out_buf = STRBUF_INIT;
	struct string_list_item *item;
	int ret;

	strvec_push(&cmd->args, "--stdin-packs");

	for_each_string_list_item(item, include)
		strbuf_addf(&in_buf, "%s\n", item->string);

	ret = pipe_command(cmd, in_buf.buf, in_buf.len,
			   out ? &out_buf : NULL, 0, NULL, 0);

	if (out)
		string_list_split_f(out, out_buf.buf, "\n", -1,
				    STRING_LIST_SPLIT_NONEMPTY);

	strbuf_release(&in_buf);
	strbuf_release(&out_buf);

	return ret;
}

static int write_midx_included_packs(struct repack_write_midx_opts *opts)
{
	struct child_process cmd = CHILD_PROCESS_INIT;
	struct string_list include = STRING_LIST_INIT_DUP;
	struct string_list_item *item;
	struct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);
	int ret = 0;

	midx_included_packs(&include, opts);
	if (!include.nr)
		goto done;

	repack_prepare_midx_command(&cmd, opts, "write");

	if (preferred)
		strvec_pushf(&cmd.args, "--preferred-pack=%s",
			     pack_basename(preferred));
	else if (opts->names->nr) {
		/* The largest pack was repacked, meaning that either
		 * one or two packs exist depending on whether the
		 * repository has a cruft pack or not.
		 *
		 * Select the non-cruft one as preferred to encourage
		 * pack-reuse among packs containing reachable objects
		 * over unreachable ones.
		 *
		 * (Note we could write multiple packs here if
		 * `--max-pack-size` was given, but any one of them
		 * will suffice, so pick the first one.)
		 */
		for_each_string_list_item(item, opts->names) {
			struct generated_pack *pack = item->util;
			if (generated_pack_has_ext(pack, ".mtimes"))
				continue;

			strvec_pushf(&cmd.args, "--preferred-pack=pack-%s.pack",
				     item->string);
			break;
		}
	} else {
		/*
		 * No packs were kept, and no packs were written. The
		 * only thing remaining are .keep packs (unless
		 * --pack-kept-objects was given).
		 *
		 * Set the `--preferred-pack` arbitrarily here.
		 */
		;
	}

	if (opts->refs_snapshot)
		strvec_pushf(&cmd.args, "--refs-snapshot=%s",
			     opts->refs_snapshot);

	ret = repack_fill_midx_stdin_packs(&cmd, &include, NULL);
done:
	if (!ret && opts->write_bitmaps)
		remove_redundant_bitmaps(&include, opts->packdir);

	string_list_clear(&include, 0);

	return ret;
}

struct midx_compaction_step {
	union {
		struct multi_pack_index *copy;
		struct string_list write;
		struct {
			struct multi_pack_index *from;
			struct multi_pack_index *to;
		} compact;
	} u;

	uint32_t objects_nr;
	char *csum;

	enum {
		MIDX_COMPACTION_STEP_UNKNOWN,
		MIDX_COMPACTION_STEP_COPY,
		MIDX_COMPACTION_STEP_WRITE,
		MIDX_COMPACTION_STEP_COMPACT,
	} type;
};

static const char *midx_compaction_step_base(const struct midx_compaction_step *step)
{
	switch (step->type) {
	case MIDX_COMPACTION_STEP_UNKNOWN:
		BUG("cannot use UNKNOWN step as a base");
	case MIDX_COMPACTION_STEP_COPY:
		return midx_get_checksum_hex(step->u.copy);
	case MIDX_COMPACTION_STEP_WRITE:
		BUG("cannot use WRITE step as a base");
	case MIDX_COMPACTION_STEP_COMPACT:
		return midx_get_checksum_hex(step->u.compact.to);
	default:
		BUG("unhandled midx compaction step type %d", step->type);
	}
}

static int midx_compaction_step_exec_copy(struct midx_compaction_step *step)
{
	step->csum = xstrdup(midx_get_checksum_hex(step->u.copy));
	return 0;
}

static int midx_compaction_step_exec_write(struct midx_compaction_step *step,
					   struct repack_write_midx_opts *opts,
					   const char *base)
{
	struct child_process cmd = CHILD_PROCESS_INIT;
	struct string_list hash = STRING_LIST_INIT_DUP;
	struct string_list_item *item;
	const char *preferred_pack = NULL;
	int ret = 0;

	if (!step->u.write.nr) {
		ret = error(_("no packs to write MIDX during compaction"));
		goto out;
	}

	for_each_string_list_item(item, &step->u.write) {
		if (item->util)
			preferred_pack = item->string;
	}

	repack_prepare_midx_command(&cmd, opts, "write");
	strvec_pushl(&cmd.args, "--incremental", "--no-write-chain-file", NULL);
	strvec_pushf(&cmd.args, "--base=%s", base ? base : "none");

	if (preferred_pack) {
		struct strbuf buf = STRBUF_INIT;

		strbuf_addstr(&buf, preferred_pack);
		strbuf_strip_suffix(&buf, ".idx");
		strbuf_addstr(&buf, ".pack");

		strvec_pushf(&cmd.args, "--preferred-pack=%s", buf.buf);

		strbuf_release(&buf);
	}

	ret = repack_fill_midx_stdin_packs(&cmd, &step->u.write, &hash);
	if (hash.nr != 1) {
		ret = error(_("expected exactly one line during MIDX write, "
			      "got: %"PRIuMAX),
			    (uintmax_t)hash.nr);
		goto out;
	}

	step->csum = xstrdup(hash.items[0].string);

out:
	string_list_clear(&hash, 0);

	return ret;
}

static int midx_compaction_step_exec_compact(struct midx_compaction_step *step,
					     struct repack_write_midx_opts *opts)
{
	struct child_process cmd = CHILD_PROCESS_INIT;
	struct strbuf buf = STRBUF_INIT;
	FILE *out = NULL;
	int ret;

	repack_prepare_midx_command(&cmd, opts, "compact");
	strvec_pushl(&cmd.args, "--incremental", "--no-write-chain-file",
		     midx_get_checksum_hex(step->u.compact.from),
		     midx_get_checksum_hex(step->u.compact.to), NULL);

	cmd.out = -1;

	ret = start_command(&cmd);
	if (ret)
		goto out;

	out = xfdopen(cmd.out, "r");
	while (strbuf_getline_lf(&buf, out) != EOF) {
		if (step->csum) {
			ret = error(_("unexpected MIDX output: '%s'"), buf.buf);
			fclose(out);
			out = NULL;
			finish_command(&cmd);
			goto out;
		}
		step->csum = strbuf_detach(&buf, NULL);
	}

	ret = finish_command(&cmd);

out:
	if (out)
		fclose(out);
	strbuf_release(&buf);

	return ret;
}

static int midx_compaction_step_exec(struct midx_compaction_step *step,
				     struct repack_write_midx_opts *opts,
				     const char *base)
{
	switch (step->type) {
	case MIDX_COMPACTION_STEP_UNKNOWN:
		BUG("cannot execute UNKNOWN midx compaction step");
	case MIDX_COMPACTION_STEP_COPY:
		return midx_compaction_step_exec_copy(step);
	case MIDX_COMPACTION_STEP_WRITE:
		return midx_compaction_step_exec_write(step, opts, base);
	case MIDX_COMPACTION_STEP_COMPACT:
		return midx_compaction_step_exec_compact(step, opts);
	default:
		BUG("unhandled midx compaction step type %d", step->type);
	}
}

static void midx_compaction_step_release(struct midx_compaction_step *step)
{
	if (step->type == MIDX_COMPACTION_STEP_WRITE)
		string_list_clear(&step->u.write, 0);
	free(step->csum);
}

/*
 * Build an append-only MIDX plan: a single WRITE step for the freshly
 * written packs, plus COPY steps for every existing layer.  No
 * compaction or merging is performed.
 */
static void repack_make_midx_append_plan(struct repack_write_midx_opts *opts,
					 struct midx_compaction_step **steps_p,
					 size_t *steps_nr_p)
{
	struct multi_pack_index *m;
	struct midx_compaction_step *steps = NULL;
	struct midx_compaction_step *step;
	size_t steps_nr = 0, steps_alloc = 0;

	odb_reprepare(opts->existing->repo->objects);
	m = get_multi_pack_index(opts->existing->source);

	if (opts->names->nr) {
		struct strbuf buf = STRBUF_INIT;
		uint32_t i;

		ALLOC_GROW(steps, st_add(steps_nr, 1), steps_alloc);

		step = &steps[steps_nr++];
		memset(step, 0, sizeof(*step));

		step->type = MIDX_COMPACTION_STEP_WRITE;
		string_list_init_dup(&step->u.write);

		for (i = 0; i < opts->names->nr; i++) {
			strbuf_reset(&buf);
			strbuf_addf(&buf, "pack-%s.idx",
				    opts->names->items[i].string);
			string_list_append(&step->u.write, buf.buf);
		}

		strbuf_release(&buf);
	}

	for (; m; m = m->base_midx) {
		ALLOC_GROW(steps, st_add(steps_nr, 1), steps_alloc);

		step = &steps[steps_nr++];
		memset(step, 0, sizeof(*step));

		step->type = MIDX_COMPACTION_STEP_COPY;
		step->u.copy = m;
		step->objects_nr = m->num_objects;
	}

	*steps_p = steps;
	*steps_nr_p = steps_nr;
}

static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,
					    struct midx_compaction_step **steps_p,
					    size_t *steps_nr_p)
{
	struct multi_pack_index *m;
	struct midx_compaction_step *steps = NULL;
	struct midx_compaction_step step = { 0 };
	struct strbuf buf = STRBUF_INIT;
	size_t steps_nr = 0, steps_alloc = 0;
	uint32_t i;
	int ret = 0;

	trace2_region_enter("repack", "make_midx_compaction_plan",
			    opts->existing->repo);

	odb_reprepare(opts->existing->repo->objects);
	m = get_multi_pack_index(opts->existing->source);

	for (i = 0; m && i < m->num_packs + m->num_packs_in_base; i++) {
		if (prepare_midx_pack(m, i)) {
			ret = error(_("could not load pack %"PRIu32" from MIDX"),
				    i);
			goto out;
		}
	}

	trace2_region_enter("repack", "steps:write", opts->existing->repo);

	/*
	 * The first MIDX in the resulting chain is always going to be
	 * new.
	 *
	 * At a minimum, it will include all of the newly written packs.
	 * If there is an existing MIDX whose tip layer contains packs
	 * that were repacked, it will also include any of its packs
	 * which were *not* rolled up as part of the geometric repack
	 * (if any), and the previous tip will be replaced.
	 *
	 * It may grow to include the packs from zero or more MIDXs from
	 * the old chain, beginning either at the old tip (if the MIDX
	 * was *not* rewritten) or the old tip's base MIDX layer
	 * (otherwise).
	 */
	step.type = MIDX_COMPACTION_STEP_WRITE;
	string_list_init_dup(&step.u.write);

	for (i = 0; i < opts->names->nr; i++) {
		strbuf_reset(&buf);
		strbuf_addf(&buf, "pack-%s.idx", opts->names->items[i].string);
		string_list_append(&step.u.write, buf.buf);

		trace2_data_string("repack", opts->existing->repo,
				   "include:fresh",
				   step.u.write.items[step.u.write.nr - 1].string);
	}
	for (i = 0; i < opts->geometry->split; i++) {
		struct packed_git *p = opts->geometry->pack[i];
		if (unsigned_add_overflows(step.objects_nr, p->num_objects)) {
			ret = error(_("too many objects in MIDX compaction step"));
			goto out;
		}

		step.objects_nr += p->num_objects;
	}
	trace2_data_intmax("repack", opts->existing->repo,
			   "include:fresh:objects_nr",
			   (uintmax_t)step.objects_nr);

	/*
	 * Now handle any existing packs which were *not* rewritten.
	 *
	 * The list of packs in opts->geometry only contains MIDX'd
	 * packs from the newest layer when that layer has more than
	 * 'repack.midxNewLayerThreshold' number of packs.
	 *
	 * If the MIDX tip was rewritten (that is, one or more of those
	 * packs appear below the split line), then add all packs above
	 * the split line to the new layer, as the old one is no longer
	 * usable.
	 *
	 * If the MIDX tip was not rewritten (that is, all MIDX'd packs
	 * from the youngest layer appear below the split line, or were
	 * not included in the geometric repack at all because there
	 * were too few of them), ignore them since we'll retain the
	 * existing layer as-is.
	 */
	for (i = opts->geometry->split; i < opts->geometry->pack_nr; i++) {
		struct packed_git *p = opts->geometry->pack[i];
		struct string_list_item *item;

		strbuf_reset(&buf);
		strbuf_addstr(&buf, pack_basename(p));
		strbuf_strip_suffix(&buf, ".pack");
		strbuf_addstr(&buf, ".idx");

		if (p->multi_pack_index &&
		    !opts->geometry->midx_tip_rewritten) {
			trace2_data_string("repack", opts->existing->repo,
					   "exclude:unmodified", buf.buf);
			continue;
		}

		trace2_data_string("repack", opts->existing->repo,
				   "include:unmodified", buf.buf);
		trace2_data_string("repack", opts->existing->repo,
				   "include:unmodified:midx",
				   p->multi_pack_index ? "true" : "false");

		item = string_list_append(&step.u.write, buf.buf);
		if (p->multi_pack_index || i == opts->geometry->pack_nr - 1)
			item->util = (void *)1; /* mark as preferred */

		if (unsigned_add_overflows(step.objects_nr, p->num_objects)) {
			ret = error(_("too many objects in MIDX compaction step"));
			goto out;
		}

		step.objects_nr += p->num_objects;
	}
	trace2_data_intmax("repack", opts->existing->repo,
			   "include:unmodified:objects_nr",
			   (uintmax_t)step.objects_nr);

	/*
	 * If the MIDX tip was rewritten, then we no longer consider it
	 * a candidate for compaction, since it will not exist in the
	 * MIDX chain being built.
	 */
	if (opts->geometry->midx_tip_rewritten)
		m = m->base_midx;

	trace2_data_string("repack", opts->existing->repo, "midx:rewrote-tip",
			   opts->geometry->midx_tip_rewritten ? "true" : "false");

	trace2_region_enter("repack", "compact", opts->existing->repo);

	/*
	 * Compact additional MIDX layers into this proposed one until
	 * the merging condition is violated.
	 */
	while (m) {
		uint32_t preferred_pack_idx;

		trace2_data_string("repack", opts->existing->repo,
				   "candidate", midx_get_checksum_hex(m));

		if (step.objects_nr < m->num_objects / opts->midx_split_factor) {
			/*
			 * Stop compacting MIDX layer as soon as the
			 * merged size is less than half the size of the
			 * next layer in the chain.
			 */
			trace2_data_string("repack", opts->existing->repo,
					   "compact", "violated");
			trace2_data_intmax("repack", opts->existing->repo,
					   "objects_nr",
					   (uintmax_t)step.objects_nr);
			trace2_data_intmax("repack", opts->existing->repo,
					   "next_objects_nr",
					   (uintmax_t)m->num_objects);
			trace2_data_intmax("repack", opts->existing->repo,
					   "split_factor",
					   (uintmax_t)opts->midx_split_factor);

			break;
		}

		if (midx_preferred_pack(m, &preferred_pack_idx) < 0) {
			ret = error(_("could not find preferred pack for MIDX "
				      "%s"), midx_get_checksum_hex(m));
			goto out;
		}

		for (i = 0; i < m->num_packs; i++) {
			struct string_list_item *item;
			uint32_t pack_int_id = i + m->num_packs_in_base;
			struct packed_git *p = nth_midxed_pack(m, pack_int_id);

			strbuf_reset(&buf);
			strbuf_addstr(&buf, pack_basename(p));
			strbuf_strip_suffix(&buf, ".pack");
			strbuf_addstr(&buf, ".idx");

			trace2_data_string("repack", opts->existing->repo,
					   "midx:pack", buf.buf);

			item = string_list_append(&step.u.write, buf.buf);
			if (pack_int_id == preferred_pack_idx)
				item->util = (void *)1; /* mark as preferred */
		}

		if (unsigned_add_overflows(step.objects_nr, m->num_objects)) {
			ret = error(_("too many objects in MIDX compaction step"));
			goto out;
		}
		step.objects_nr += m->num_objects;

		m = m->base_midx;
	}

	if (step.u.write.nr > 0) {
		/*
		 * As long as there is at least one new pack to write
		 * (and thus the MIDX is non-empty), add it to the plan.
		 */
		ALLOC_GROW(steps, steps_nr + 1, steps_alloc);
		steps[steps_nr++] = step;
	}

	trace2_data_intmax("repack", opts->existing->repo,
			   "step:objects_nr", (uintmax_t)step.objects_nr);
	trace2_data_intmax("repack", opts->existing->repo,
			   "step:packs_nr", (uintmax_t)step.u.write.nr);

	trace2_region_leave("repack", "compact", opts->existing->repo);
	trace2_region_leave("repack", "steps:write", opts->existing->repo);

	trace2_region_enter("repack", "steps:rest", opts->existing->repo);

	/*
	 * Then start over, repeat, and either compact or keep as-is
	 * each MIDX layer until we have exhausted the chain.
	 *
	 * Finally, evaluate the remainder of the chain (if any) and
	 * either compact a sequence of adjacent layers, or keep
	 * individual layers as-is according to the same merging
	 * condition as above.
	 */
	while (m) {
		struct multi_pack_index *next = m;

		ALLOC_GROW(steps, steps_nr + 1, steps_alloc);

		memset(&step, 0, sizeof(step));
		step.type = MIDX_COMPACTION_STEP_UNKNOWN;

		trace2_region_enter("repack", "step", opts->existing->repo);

		trace2_data_string("repack", opts->existing->repo,
				   "from", midx_get_checksum_hex(m));

		while (next) {
			uint32_t proposed_objects_nr;
			if (unsigned_add_overflows(step.objects_nr, next->num_objects)) {
				ret = error(_("too many objects in MIDX compaction step"));
				trace2_region_leave("repack", "step", opts->existing->repo);
				goto out;
			}

			proposed_objects_nr = step.objects_nr + next->num_objects;

			trace2_data_string("repack", opts->existing->repo,
					   "proposed",
					   midx_get_checksum_hex(next));
			trace2_data_intmax("repack", opts->existing->repo,
					   "proposed:objects_nr",
					   (uintmax_t)next->num_objects);

			if (!next->base_midx) {
				/*
				 * If we are at the end of the MIDX
				 * chain, there is nothing to compact,
				 * so mark it and stop.
				 */
				step.objects_nr = proposed_objects_nr;
				break;
			}

			if (proposed_objects_nr < next->base_midx->num_objects / opts->midx_split_factor) {
				/*
				 * If there is a MIDX following this
				 * one, but our accumulated size is less
				 * than half of its size, compacting
				 * them would violate the merging
				 * condition, so stop here.
				 */

				trace2_data_string("repack", opts->existing->repo,
						   "compact:violated:at",
						   midx_get_checksum_hex(next->base_midx));
				trace2_data_intmax("repack", opts->existing->repo,
						   "compact:violated:at:objects_nr",
						   (uintmax_t)next->base_midx->num_objects);
				break;
			}

			/*
			 * Otherwise, it is OK to compact the next layer
			 * into this one. Do so, and then continue
			 * through the remainder of the chain.
			 */
			step.objects_nr = proposed_objects_nr;
			trace2_data_intmax("repack", opts->existing->repo,
					   "step:objects_nr",
					   (uintmax_t)step.objects_nr);
			next = next->base_midx;
		}

		if (m == next) {
			step.type = MIDX_COMPACTION_STEP_COPY;
			step.u.copy = m;

			trace2_data_string("repack", opts->existing->repo,
					   "type", "copy");
		} else {
			step.type = MIDX_COMPACTION_STEP_COMPACT;
			step.u.compact.from = next;
			step.u.compact.to = m;

			trace2_data_string("repack", opts->existing->repo,
					   "to", midx_get_checksum_hex(m));
			trace2_data_string("repack", opts->existing->repo,
					   "type", "compact");
		}

		m = next->base_midx;
		steps[steps_nr++] = step;
		trace2_region_leave("repack", "step", opts->existing->repo);
	}

	trace2_region_leave("repack", "steps:rest", opts->existing->repo);

out:
	*steps_p = steps;
	*steps_nr_p = steps_nr;

	strbuf_release(&buf);

	trace2_region_leave("repack", "make_midx_compaction_plan",
			    opts->existing->repo);

	return ret;
}

static int write_midx_incremental(struct repack_write_midx_opts *opts)
{
	struct midx_compaction_step *steps = NULL;
	struct strbuf lock_name = STRBUF_INIT;
	struct lock_file lf;
	struct strvec keep_hashes = STRVEC_INIT;
	size_t steps_nr = 0;
	size_t i;
	int ret = 0;

	get_midx_chain_filename(opts->existing->source, &lock_name);
	if (safe_create_leading_directories(opts->existing->repo,
					    lock_name.buf))
		die_errno(_("unable to create leading directories of %s"),
			  lock_name.buf);
	hold_lock_file_for_update(&lf, lock_name.buf, LOCK_DIE_ON_ERROR);

	if (!fdopen_lock_file(&lf, "w")) {
		ret = error_errno(_("unable to open multi-pack-index chain file"));
		goto done;
	}

	if (opts->geometry->split_factor) {
		if (repack_make_midx_compaction_plan(opts, &steps, &steps_nr) < 0) {
			ret = error(_("unable to generate compaction plan"));
			goto done;
		}
	} else {
		repack_make_midx_append_plan(opts, &steps, &steps_nr);
	}

	for (i = 0; i < steps_nr; i++) {
		struct midx_compaction_step *step = &steps[i];
		char *base = NULL;

		if (i + 1 < steps_nr)
			base = xstrdup(midx_compaction_step_base(&steps[i + 1]));

		if (midx_compaction_step_exec(step, opts, base) < 0) {
			ret = error(_("unable to execute compaction step %"PRIuMAX),
				    (uintmax_t)i);
			free(base);
			goto done;
		}

		free(base);
	}

	i = steps_nr;
	while (i--) {
		struct midx_compaction_step *step = &steps[i];
		if (!step->csum)
			BUG("missing result for compaction step %"PRIuMAX,
			    (uintmax_t)i);
		fprintf(get_lock_file_fp(&lf), "%s\n", step->csum);
		strvec_push(&keep_hashes, step->csum);
	}

	commit_lock_file(&lf);

	clear_incremental_midx_files(opts->existing->repo, &keep_hashes);

done:
	strvec_clear(&keep_hashes);
	strbuf_release(&lock_name);
	for (i = 0; i < steps_nr; i++)
		midx_compaction_step_release(&steps[i]);
	free(steps);
	return ret;
}

int repack_write_midx(struct repack_write_midx_opts *opts)
{
	switch (opts->mode) {
	case REPACK_WRITE_MIDX_NONE:
		BUG("write_midx mode is NONE?");
	case REPACK_WRITE_MIDX_DEFAULT:
		return write_midx_included_packs(opts);
	case REPACK_WRITE_MIDX_INCREMENTAL:
		return write_midx_incremental(opts);
	default:
		BUG("unhandled write_midx mode: %d", opts->mode);
	}
}
