@@ -73,6 +73,17 @@ gc::
It can also be disruptive in some situations, as it deletes stale
data.
+loose-objects::
+ The `loose-objects` job cleans up loose objects and places them into
+ pack-files. In order to prevent race conditions with concurrent Git
+ commands, it follows a two-step process. First, it deletes any loose
+ objects that already exist in a pack-file; concurrent Git processes
+ will examine the pack-file for the object data instead of the loose
+ object. Second, it creates a new pack-file (starting with "loose-")
+ containing a batch of loose objects. The batch size is limited to 50
+ thousand objects to prevent the job from taking too long on a
+ repository with many loose objects.
+
OPTIONS
-------
--auto::
@@ -706,7 +706,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
return 0;
}
-#define MAX_NUM_TASKS 3
+#define MAX_NUM_TASKS 4
static const char * const builtin_maintenance_usage[] = {
N_("git maintenance run [<options>]"),
@@ -866,6 +866,107 @@ static int maintenance_task_gc(struct repository *r)
return result;
}
+
+static int prune_packed(struct repository *r)
+{
+ struct argv_array cmd = ARGV_ARRAY_INIT;
+ argv_array_pushl(&cmd, "-C", r->worktree, "prune-packed", NULL);
+
+ if (opts.quiet)
+ argv_array_push(&cmd, "--quiet");
+
+ return run_command_v_opt(cmd.argv, RUN_GIT_CMD);
+}
+
+struct write_loose_object_data {
+ FILE *in;
+ int count;
+ int batch_size;
+};
+
+static int loose_object_exists(const struct object_id *oid,
+ const char *path,
+ void *data)
+{
+ return 1;
+}
+
+static int write_loose_object_to_stdin(const struct object_id *oid,
+ const char *path,
+ void *data)
+{
+ struct write_loose_object_data *d = (struct write_loose_object_data *)data;
+
+ fprintf(d->in, "%s\n", oid_to_hex(oid));
+
+ return ++(d->count) > d->batch_size;
+}
+
+static int pack_loose(struct repository *r)
+{
+ int result = 0;
+ struct write_loose_object_data data;
+ struct strbuf prefix = STRBUF_INIT;
+ struct child_process *pack_proc;
+
+ /*
+ * Do not start pack-objects process
+ * if there are no loose objects.
+ */
+ if (!for_each_loose_file_in_objdir(r->objects->odb->path,
+ loose_object_exists,
+ NULL, NULL, NULL))
+ return 0;
+
+ pack_proc = xmalloc(sizeof(*pack_proc));
+
+ child_process_init(pack_proc);
+
+ strbuf_addstr(&prefix, r->objects->odb->path);
+ strbuf_addstr(&prefix, "/pack/loose");
+
+ argv_array_pushl(&pack_proc->args, "git", "-C", r->worktree,
+ "pack-objects", NULL);
+ if (opts.quiet)
+ argv_array_push(&pack_proc->args, "--quiet");
+ argv_array_push(&pack_proc->args, prefix.buf);
+
+ pack_proc->in = -1;
+
+ if (start_command(pack_proc)) {
+ error(_("failed to start 'git pack-objects' process"));
+ result = 1;
+ goto cleanup;
+ }
+
+ data.in = xfdopen(pack_proc->in, "w");
+ data.count = 0;
+ data.batch_size = 50000;
+
+ for_each_loose_file_in_objdir(r->objects->odb->path,
+ write_loose_object_to_stdin,
+ NULL,
+ NULL,
+ &data);
+
+ fclose(data.in);
+
+ if (finish_command(pack_proc)) {
+ error(_("failed to finish 'git pack-objects' process"));
+ result = 1;
+ }
+
+cleanup:
+ strbuf_release(&prefix);
+ free(pack_proc);
+ return result;
+}
+
+static int maintenance_task_loose_objects(struct repository *r)
+{
+ return prune_packed(r) || pack_loose(r);
+}
+
typedef int maintenance_task_fn(struct repository *r);
struct maintenance_task {
@@ -956,6 +1057,10 @@ static void initialize_tasks(void)
tasks[num_tasks]->fn = maintenance_task_fetch;
num_tasks++;
+ tasks[num_tasks]->name = "loose-objects";
+ tasks[num_tasks]->fn = maintenance_task_loose_objects;
+ num_tasks++;
+
tasks[num_tasks]->name = "gc";
tasks[num_tasks]->fn = maintenance_task_gc;
tasks[num_tasks]->enabled = 1;
@@ -68,4 +68,39 @@ test_expect_success 'fetch multiple remotes' '
git log hidden/remote2/two
'
+test_expect_success 'loose-objects task' '
+ # Repack everything so we know the state of the object dir
+ git repack -adk &&
+
+ # Hack to stop maintenance from running during "git commit"
+ echo in use >.git/objects/maintenance.lock &&
+ test_commit create-loose-object &&
+ rm .git/objects/maintenance.lock &&
+
+ ls .git/objects >obj-dir-before &&
+ test_file_not_empty obj-dir-before &&
+ ls .git/objects/pack/*.pack >packs-before &&
+ test_line_count = 1 packs-before &&
+
+ # The first run creates a pack-file
+ # but does not delete loose objects.
+ git maintenance run --task=loose-objects &&
+ ls .git/objects >obj-dir-between &&
+ test_cmp obj-dir-before obj-dir-between &&
+ ls .git/objects/pack/*.pack >packs-between &&
+ test_line_count = 2 packs-between &&
+
+ # The second run deletes loose objects
+ # but does not create a pack-file.
+ git maintenance run --task=loose-objects &&
+ ls .git/objects >obj-dir-after &&
+ cat >expect <<-\EOF &&
+ info
+ pack
+ EOF
+ test_cmp expect obj-dir-after &&
+ ls .git/objects/pack/*.pack >packs-after &&
+ test_cmp packs-between packs-after
+'
+
test_done