373 lines
9.8 KiB
C
373 lines
9.8 KiB
C
#include "git-compat-util.h"
|
|
#include "repack.h"
|
|
#include "hash.h"
|
|
#include "hex.h"
|
|
#include "odb.h"
|
|
#include "oidset.h"
|
|
#include "pack-bitmap.h"
|
|
#include "refs.h"
|
|
#include "run-command.h"
|
|
#include "tempfile.h"
|
|
|
|
struct midx_snapshot_ref_data {
|
|
struct repository *repo;
|
|
struct tempfile *f;
|
|
struct oidset seen;
|
|
int preferred;
|
|
};
|
|
|
|
static int midx_snapshot_ref_one(const char *refname UNUSED,
|
|
const char *referent UNUSED,
|
|
const struct object_id *oid,
|
|
int flag UNUSED, void *_data)
|
|
{
|
|
struct midx_snapshot_ref_data *data = _data;
|
|
struct object_id peeled;
|
|
|
|
if (!peel_iterated_oid(data->repo, oid, &peeled))
|
|
oid = &peeled;
|
|
|
|
if (oidset_insert(&data->seen, oid))
|
|
return 0; /* already seen */
|
|
|
|
if (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)
|
|
return 0;
|
|
|
|
fprintf(data->f->fp, "%s%s\n", data->preferred ? "+" : "",
|
|
oid_to_hex(oid));
|
|
|
|
return 0;
|
|
}
|
|
|
|
void midx_snapshot_refs(struct repository *repo, struct tempfile *f)
|
|
{
|
|
struct midx_snapshot_ref_data data;
|
|
const struct string_list *preferred = bitmap_preferred_tips(repo);
|
|
|
|
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));
|
|
|
|
if (preferred) {
|
|
struct string_list_item *item;
|
|
|
|
data.preferred = 1;
|
|
for_each_string_list_item(item, preferred)
|
|
refs_for_each_ref_in(get_main_ref_store(repo),
|
|
item->string,
|
|
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);
|
|
}
|
|
|
|
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);
|
|
FILE *in;
|
|
int ret = 0;
|
|
|
|
midx_included_packs(&include, opts);
|
|
if (!include.nr)
|
|
goto done;
|
|
|
|
cmd.in = -1;
|
|
cmd.git_cmd = 1;
|
|
|
|
strvec_push(&cmd.args, "multi-pack-index");
|
|
strvec_pushl(&cmd.args, "write", "--stdin-packs", 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");
|
|
|
|
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 = start_command(&cmd);
|
|
if (ret)
|
|
goto done;
|
|
|
|
in = xfdopen(cmd.in, "w");
|
|
for_each_string_list_item(item, &include)
|
|
fprintf(in, "%s\n", item->string);
|
|
fclose(in);
|
|
|
|
ret = finish_command(&cmd);
|
|
done:
|
|
if (!ret && opts->write_bitmaps)
|
|
remove_redundant_bitmaps(&include, opts->packdir);
|
|
|
|
string_list_clear(&include, 0);
|
|
|
|
return ret;
|
|
}
|