"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
make gotadmin cleanup pack the repository before cleaning
To:
gameoftrees@openbsd.org
Date:
Tue, 21 Jan 2025 11:02:43 +0100

Download raw body.

Thread
Our cleanup implementation is only safe to use after everything
referenced has been packed into a single pack file. Otherwise, the
algorithm we use for checking pack redundancy might remove small
packs which contain objects that other objects depend on.

The easy fix for this issue is to have 'gotadmin cleanup' create the
required pack file before cleaning up, making cleanup safe by default.
This happens to be what 'git gc' does as well.

ok?

git fsck now complains about bad reflog entries after cleanup, but
is happily passing when the --no-reflogs option is given.
We don't care about reflogs (Got uses backup refs instead), and we
don't have any code to read or adjust reflogs. Perhaps this should
be looked into eventually for better interop but this has no high
priority for me.

M  gotadmin/gotadmin.c             |   17+   8-
M  gotd/repo_read.c                |    1+   1-
M  include/got_repository_admin.h  |    8+   5-
M  lib/got_lib_pack_create.h       |    1+   1-
M  lib/pack_create.c               |   11+  10-
M  lib/pack_create_io.c            |    2+   2-
M  lib/pack_create_privsep.c       |    3+   3-
M  lib/repository_admin.c          |  136+  48-
M  lib/send.c                      |    1+   1-
M  regress/cmdline/cleanup.sh      |    6+   6-
M  regress/cmdline/common.sh       |    1+   1-

11 files changed, 187 insertions(+), 86 deletions(-)

commit - 85db99906136b77c3a31a58c53ffc746b1568353
commit + a4990d09b84e46b12ee9ae1a62ea6736ca09900c
blob - 5a8c2bc8ec21628c9260de98ee4077f5f878ff15
blob + e763ebc7adedd20170a3b154ddc89c078d3d05f6
--- gotadmin/gotadmin.c
+++ gotadmin/gotadmin.c
@@ -520,7 +520,7 @@ print_load_info(FILE *out, int print_colored, int prin
 static const struct got_error *
 pack_progress(void *arg, int ncolored, int nfound, int ntrees,
     off_t packfile_size, int ncommits, int nobj_total, int nobj_deltify,
-    int nobj_written)
+    int nobj_written, int pack_done)
 {
 	struct got_pack_progress_arg *a = arg;
 	char scaled_size[FMT_SCALED_STRSIZE];
@@ -621,17 +621,18 @@ pack_progress(void *arg, int ncolored, int nfound, int
 	if (print_written)
 		fprintf(a->out, "; writing pack: %*s %d%%",
 		    FMT_SCALED_STRSIZE - 2, scaled_size, p_written);
-	if (print_searching || print_total || print_deltify ||
-	    print_written) {
+	if (print_searching || print_total || print_deltify || print_written) {
 		a->printed_something = 1;
 		fflush(a->out);
 	}
+	if (pack_done)
+		fprintf(a->out, "\n");
 	return NULL;
 }
 
 static const struct got_error *
 pack_index_progress(void *arg, off_t packfile_size, int nobj_total,
-    int nobj_indexed, int nobj_loose, int nobj_resolved)
+    int nobj_indexed, int nobj_loose, int nobj_resolved, int indexing_done)
 {
 	struct got_pack_progress_arg *a = arg;
 	char scaled_size[FMT_SCALED_STRSIZE];
@@ -678,7 +679,9 @@ pack_index_progress(void *arg, off_t packfile_size, in
 		printf("; indexing %d%%", p_indexed);
 	if (print_resolved)
 		printf("; resolving deltas %d%%", p_resolved);
-	if (print_size || print_indexed || print_resolved)
+	if (indexing_done)
+		printf("\n");
+	if (print_size || print_indexed || print_resolved || indexing_done)
 		fflush(stdout);
 
 	return NULL;
@@ -1266,6 +1269,7 @@ cmd_cleanup(int argc, char *argv[])
 	int ch, dry_run = 0, verbosity = 0;
 	int ncommits = 0, nloose = 0, npacked = 0;
 	int remove_lonely_packidx = 0, ignore_mtime = 0;
+	struct got_pack_progress_arg ppa;
 	struct got_cleanup_progress_arg cpa;
 	struct got_lonely_packidx_progress_arg lpa;
 	off_t loose_before, loose_after;
@@ -1279,7 +1283,7 @@ cmd_cleanup(int argc, char *argv[])
 	int *pack_fds = NULL;
 
 #ifndef PROFILE
-	if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil",
+	if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd unveil",
 	    NULL) == -1)
 		err(1, "pledge");
 #endif
@@ -1353,11 +1357,16 @@ cmd_cleanup(int argc, char *argv[])
 	cpa.dry_run = dry_run;
 	cpa.verbosity = verbosity;
 
+	memset(&ppa, 0, sizeof(ppa));
+	ppa.out = stderr;
+	ppa.verbosity = verbosity;
+
 	error = got_repo_cleanup(repo, &loose_before, &loose_after,
 	    &pack_before, &pack_after, &ncommits, &nloose, &npacked,
 	    dry_run, ignore_mtime, cleanup_progress, &cpa,
+	    pack_progress, &ppa, pack_index_progress, &ppa,
 	    check_cancelled, NULL);
-	if (cpa.printed_something)
+	if (ppa.printed_something || cpa.printed_something)
 		printf("\n");
 	if (error)
 		goto done;
@@ -1562,7 +1571,7 @@ load_progress(void *arg, off_t packfile_size, int nobj
     int nobj_indexed, int nobj_loose, int nobj_resolved)
 {
 	return pack_index_progress(arg, packfile_size, nobj_total,
-	    nobj_indexed, nobj_loose, nobj_resolved);
+	    nobj_indexed, nobj_loose, nobj_resolved, 0);
 }
 
 static int
blob - 62e95bc3c4eee3e045d642b2b1a6496f839d03ce
blob + 654887bcb69f15932fb19f6820fe2a8c5ffb698b
--- gotd/repo_read.c
+++ gotd/repo_read.c
@@ -510,7 +510,7 @@ struct repo_read_pack_progress_arg {
 static const struct got_error *
 pack_progress(void *arg, int ncolored, int nfound, int ntrees,
     off_t packfile_size, int ncommits, int nobj_total, int nobj_deltify,
-    int nobj_written)
+    int nobj_written, int pack_done)
 {
 	struct repo_read_pack_progress_arg *a = arg;
 	struct gotd_imsg_packfile_progress iprog;
blob - a1bb191618ec66c76deecf76d52349088c755a06
blob + e98a374cdbf061646b57eb2ec609497e2698a391
--- include/got_repository_admin.h
+++ include/got_repository_admin.h
@@ -17,7 +17,7 @@
 /* A callback function which gets invoked with progress information to print. */
 typedef const struct got_error *(*got_pack_progress_cb)(void *arg,
     int ncolored, int nfound, int ntrees, off_t packfile_size, int ncommits,
-    int nobj_total, int obj_deltify, int nobj_written);
+    int nobj_total, int obj_deltify, int nobj_written, int pack_done);
 
 /*
  * Attempt to pack objects reachable via 'include_refs' into a new packfile.
@@ -49,7 +49,7 @@ got_repo_find_pack(FILE **packfile, struct got_object_
 /* A callback function which gets invoked with progress information to print. */
 typedef const struct got_error *(*got_pack_index_progress_cb)(void *arg,
     off_t packfile_size, int nobj_total, int nobj_indexed,
-    int nobj_loose, int nobj_resolved);
+    int nobj_loose, int nobj_resolved, int indexing_done);
 
 /* (Re-)Index the pack file identified by the given hash. */
 const struct got_error *
@@ -73,9 +73,10 @@ typedef const struct got_error *(*got_cleanup_progress
     int ncommits, int nloose, int npurged, int nredundant);
 
 /*
- * Walk objects reachable via references to determine whether any loose
- * objects can be removed from disk. Do remove such objects from disk
- * unless the dry_run parameter is set.
+ * Walk objects reachable via references and, unless the dry-run parameter
+ * is set, pack all referenced objects into a single pack.
+ * Then determine whether any loose objects can be removed from disk.
+ * Do remove such objects from disk unless the dry_run parameter is set.
  * Do not remove objects with a modification timestamp above an
  * implementation-defined timestamp threshold, unless ignore_mtime is set.
  * Remove packfiles which objects are either unreachable or provided
@@ -91,6 +92,8 @@ got_repo_cleanup(struct got_repository *repo,
     int *ncommits, int *nloose,
     int *npacked, int dry_run, int ignore_mtime,
     got_cleanup_progress_cb progress_cb, void *progress_arg,
+    got_pack_progress_cb pack_progress_cb, void *pack_progress_arg,
+    got_pack_index_progress_cb index_progress_cb, void *index_progress_arg,
     got_cancel_cb cancel_cb, void *cancel_arg);
 
 /* A callback function which gets invoked with cleanup information to print. */
blob - 8bf80e3bec6bdb3edf09b007a268b9fb8eb96d0b
blob + 265d2bf372d90b0b2c3c124b8d9885056f6f1250
--- lib/got_lib_pack_create.h
+++ lib/got_lib_pack_create.h
@@ -106,7 +106,7 @@ const struct got_error *
 got_pack_report_progress(got_pack_progress_cb progress_cb, void *progress_arg,
     struct got_ratelimit *rl, int ncolored, int nfound, int ntrees,
     off_t packfile_size, int ncommits, int nobj_total, int obj_deltify,
-    int nobj_written);
+    int nobj_written, int pack_done);
 
 const struct got_error *
 got_pack_load_packed_object_ids(int *found_all_objects,
blob - c6787c3643ca1ced91d4ef5d2530f153f72bb6e0
blob + 7cbe1bb9a78f7c06fed0b49bd3658873b9882862
--- lib/pack_create.c
+++ lib/pack_create.c
@@ -445,7 +445,7 @@ const struct got_error *
 got_pack_report_progress(got_pack_progress_cb progress_cb, void *progress_arg,
     struct got_ratelimit *rl, int ncolored, int nfound, int ntrees,
     off_t packfile_size, int ncommits, int nobj_total, int obj_deltify,
-    int nobj_written)
+    int nobj_written, int pack_done)
 {
 	const struct got_error *err;
 	int elapsed;
@@ -458,7 +458,8 @@ got_pack_report_progress(got_pack_progress_cb progress
 		return err;
 
 	return progress_cb(progress_arg, ncolored, nfound, ntrees,
-	    packfile_size, ncommits, nobj_total, obj_deltify, nobj_written);
+	    packfile_size, ncommits, nobj_total, obj_deltify, nobj_written,
+	    pack_done);
 }
 
 const struct got_error *
@@ -566,7 +567,7 @@ pick_deltas(struct got_pack_meta **meta, int nmeta, in
 		}
 		err = got_pack_report_progress(progress_cb, progress_arg, rl,
 		    ncolored, nfound, ntrees, 0L, ncommits, nreused + nmeta,
-		    nreused + i, 0);
+		    nreused + i, 0, 0);
 		if (err)
 			goto done;
 		m = meta[i];
@@ -750,7 +751,7 @@ got_pack_add_object(int want_meta, struct got_object_i
 
 		(*nfound)++;
 		err = got_pack_report_progress(progress_cb, progress_arg, rl,
-		    *ncolored, *nfound, *ntrees, 0L, 0, 0, 0, 0);
+		    *ncolored, *nfound, *ntrees, 0L, 0, 0, 0, 0, 0);
 		if (err) {
 			clear_meta(m);
 			free(m);
@@ -781,7 +782,7 @@ got_pack_load_tree_entries(struct got_object_id_queue 
 
 	(*ntrees)++;
 	err = got_pack_report_progress(progress_cb, progress_arg, rl,
-	    *ncolored, *nfound, *ntrees, 0L, 0, 0, 0, 0);
+	    *ncolored, *nfound, *ntrees, 0L, 0, 0, 0, 0, 0);
 	if (err)
 		return err;
 
@@ -1764,7 +1765,7 @@ genpack(struct got_object_id *pack_hash, int packfd,
 	for (i = 0; i < ndeltify; i++) {
 		err = got_pack_report_progress(progress_cb, progress_arg, rl,
 		    ncolored, nfound, ntrees, packfile_size, nours,
-		    ndeltify + nreuse, ndeltify + nreuse, i);
+		    ndeltify + nreuse, ndeltify + nreuse, i, 0);
 		if (err)
 			goto done;
 		m = deltify[i];
@@ -1793,7 +1794,7 @@ genpack(struct got_object_id *pack_hash, int packfd,
 	for (i = 0; i < nreuse; i++) {
 		err = got_pack_report_progress(progress_cb, progress_arg, rl,
 		    ncolored, nfound, ntrees, packfile_size, nours,
-		    ndeltify + nreuse, ndeltify + nreuse, ndeltify + i);
+		    ndeltify + nreuse, ndeltify + nreuse, ndeltify + i, 0);
 		if (err)
 			goto done;
 		m = reuse[i];
@@ -1813,7 +1814,7 @@ genpack(struct got_object_id *pack_hash, int packfd,
 	if (progress_cb) {
 		err = progress_cb(progress_arg, ncolored, nfound, ntrees,
 		    packfile_size, nours, ndeltify + nreuse,
-		    ndeltify + nreuse, ndeltify + nreuse);
+		    ndeltify + nreuse, ndeltify + nreuse, 1);
 		if (err)
 			goto done;
 	}
@@ -1875,7 +1876,7 @@ got_pack_create(struct got_object_id *packhash, int pa
 
 	if (progress_cb) {
 		err = progress_cb(progress_arg, ncolored, nfound, ntrees,
-		    0L, nours, got_object_idset_num_elements(idset), 0, 0);
+		    0L, nours, got_object_idset_num_elements(idset), 0, 0, 0);
 		if (err)
 			goto done;
 	}
@@ -1946,7 +1947,7 @@ got_pack_create(struct got_object_id *packhash, int pa
 		err = progress_cb(progress_arg, ncolored, nfound, ntrees,
 		    1 /* packfile_size */, nours,
 		    got_object_idset_num_elements(idset),
-		    deltify.nmeta + reuse.nmeta, 0);
+		    deltify.nmeta + reuse.nmeta, 0, 0);
 		if (err)
 			goto done;
 	}
blob - 5aa56e154c58be00385c847ac667f05de6d70d6e
blob + 0d826389e2105b3dffcab6046a757b0fdb83a8ad
--- lib/pack_create_io.c
+++ lib/pack_create_io.c
@@ -156,7 +156,7 @@ search_delta_for_object(struct got_object_id *id, void
 
 		err = got_pack_report_progress(a->progress_cb, a->progress_arg,
 		    a->rl, a->ncolored, a->nfound, a->ntrees, 0L, a->ncommits,
-		    got_object_idset_num_elements(a->idset), a->v->nmeta, 0);
+		    got_object_idset_num_elements(a->idset), a->v->nmeta, 0, 0);
 		if (err)
 			goto done;
 	}
@@ -312,7 +312,7 @@ got_pack_paint_commits(int *ncolored, struct got_objec
 		}
 
 		err = got_pack_report_progress(progress_cb, progress_arg, rl,
-		    *ncolored, 0, 0, 0L, 0, 0, 0, 0);
+		    *ncolored, 0, 0, 0L, 0, 0, 0, 0, 0);
 		if (err)
 			break;
 
blob - 609522bd15cada03e452f340e81a8ffce06233e2
blob + ea20b9c4b08199a59e5e822d3319990fd6f175fc
--- lib/pack_create_privsep.c
+++ lib/pack_create_privsep.c
@@ -190,7 +190,7 @@ got_pack_search_deltas(struct got_packidx **packidx, s
 
 		err = got_pack_report_progress(progress_cb, progress_arg, rl,
 		    ncolored, nfound, ntrees, 0L, ncommits,
-		    got_object_idset_num_elements(idset), v->nmeta, 0);
+		    got_object_idset_num_elements(idset), v->nmeta, 0, 0);
 		if (err)
 			break;
 	}
@@ -263,7 +263,7 @@ recv_painted_commit(void *arg, struct got_object_id *i
 	}
 
 	return got_pack_report_progress(a->progress_cb, a->progress_arg, a->rl,
-	    *a->ncolored, 0, 0, 0L, 0, 0, 0, 0);
+	    *a->ncolored, 0, 0, 0L, 0, 0, 0, 0, 0);
 }
 
 static const struct got_error *
@@ -447,7 +447,7 @@ got_pack_paint_commits(int *ncolored, struct got_objec
 		}
 
 		err = got_pack_report_progress(progress_cb, progress_arg, rl,
-		    *ncolored, 0, 0, 0L, 0, 0, 0, 0);
+		    *ncolored, 0, 0, 0L, 0, 0, 0, 0, 0);
 		if (err)
 			break;
 
blob - ba9e6ae53cbffd025f1157b73322e282138bf3d3
blob + 35a508595f8db16bc13a8f2de74eb628d0b35b15
--- lib/repository_admin.c
+++ lib/repository_admin.c
@@ -142,6 +142,79 @@ done:
 	return err;
 }
 
+static const struct got_error *
+create_temp_packfile(int *packfd, char **tmpfile_path,
+    struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	char *path;
+
+	*packfd = -1;
+
+	if (asprintf(&path, "%s/%s/packing.pack",
+	    got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR) == -1)
+		return got_error_from_errno("asprintf");
+
+	err = got_opentemp_named_fd(tmpfile_path, packfd, path, "");
+	if (err)
+		goto done;
+
+	if (fchmod(*packfd, GOT_DEFAULT_PACK_MODE) == -1)
+		err = got_error_from_errno2("fchmod", *tmpfile_path);
+done:
+	if (err) {
+		close(*packfd);
+		*packfd = -1;
+		free(*tmpfile_path);
+		*tmpfile_path = NULL;
+	}
+	return err;
+}
+
+static const struct got_error *
+install_packfile(FILE **packfile, int *packfd, char **packfile_path,
+    char **tmpfile_path, struct got_object_id *pack_hash,
+    struct got_repository *repo)
+{
+	const struct got_error *err;
+	char *hash_str;
+
+	err = got_object_id_str(&hash_str, pack_hash);
+	if (err)
+		return err;
+
+	if (asprintf(packfile_path, "%s/%s/pack-%s.pack",
+	    got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR,
+	    hash_str) == -1) {
+		err = got_error_from_errno("asprintf");
+		goto done;
+	}
+
+	if (lseek(*packfd, 0L, SEEK_SET) == -1) {
+		err = got_error_from_errno("lseek");
+		goto done;
+	}
+
+	if (rename(*tmpfile_path, *packfile_path) == -1) {
+		err = got_error_from_errno3("rename", *tmpfile_path,
+		    *packfile_path);
+		goto done;
+	}
+
+	free(*tmpfile_path);
+	*tmpfile_path = NULL;
+
+	*packfile = fdopen(*packfd, "w");
+	if (*packfile == NULL) {
+		err = got_error_from_errno2("fdopen", *packfile_path);
+		goto done;
+	}
+	*packfd = -1;
+done:
+	free(hash_str);
+	return err;
+}
+
 const struct got_error *
 got_repo_pack_objects(FILE **packfile, struct got_object_id **pack_hash,
     struct got_reflist_head *include_refs,
@@ -153,8 +226,7 @@ got_repo_pack_objects(FILE **packfile, struct got_obje
 	const struct got_error *err = NULL;
 	struct got_object_id **ours = NULL, **theirs = NULL;
 	int nours = 0, ntheirs = 0, packfd = -1, i;
-	char *tmpfile_path = NULL, *path = NULL, *packfile_path = NULL;
-	char *hash_str = NULL;
+	char *tmpfile_path = NULL, *packfile_path = NULL;
 	FILE *delta_cache = NULL;
 	struct got_ratelimit rl;
 
@@ -163,20 +235,10 @@ got_repo_pack_objects(FILE **packfile, struct got_obje
 
 	got_ratelimit_init(&rl, 0, 500);
 
-	if (asprintf(&path, "%s/%s/packing.pack",
-	    got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR) == -1) {
-		err = got_error_from_errno("asprintf");
-		goto done;
-	}
-	err = got_opentemp_named_fd(&tmpfile_path, &packfd, path, "");
+	err = create_temp_packfile(&packfd, &tmpfile_path, repo);
 	if (err)
-		goto done;
+		return err;
 
-	if (fchmod(packfd, GOT_DEFAULT_PACK_MODE) == -1) {
-		err = got_error_from_errno2("fchmod", tmpfile_path);
-		goto done;
-	}
-
 	delta_cache = got_opentemp();
 	if (delta_cache == NULL) {
 		err = got_error_from_errno("got_opentemp");
@@ -215,34 +277,8 @@ got_repo_pack_objects(FILE **packfile, struct got_obje
 	if (err)
 		goto done;
 
-	err = got_object_id_str(&hash_str, *pack_hash);
-	if (err)
-		goto done;
-	if (asprintf(&packfile_path, "%s/%s/pack-%s.pack",
-	    got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR,
-	    hash_str) == -1) {
-		err = got_error_from_errno("asprintf");
-		goto done;
-	}
-
-	if (lseek(packfd, 0L, SEEK_SET) == -1) {
-		err = got_error_from_errno("lseek");
-		goto done;
-	}
-	if (rename(tmpfile_path, packfile_path) == -1) {
-		err = got_error_from_errno3("rename", tmpfile_path,
-		    packfile_path);
-		goto done;
-	}
-	free(tmpfile_path);
-	tmpfile_path = NULL;
-
-	*packfile = fdopen(packfd, "w");
-	if (*packfile == NULL) {
-		err = got_error_from_errno2("fdopen", tmpfile_path);
-		goto done;
-	}
-	packfd = -1;
+	err = install_packfile(packfile, &packfd, &packfile_path,
+	    &tmpfile_path, *pack_hash, repo);
 done:
 	for (i = 0; i < nours; i++)
 		free(ours[i]);
@@ -258,8 +294,6 @@ done:
 		err = got_error_from_errno2("unlink", tmpfile_path);
 	free(tmpfile_path);
 	free(packfile_path);
-	free(hash_str);
-	free(path);
 	if (err) {
 		free(*pack_hash);
 		*pack_hash = NULL;
@@ -282,6 +316,7 @@ got_repo_index_pack(FILE *packfile, struct got_object_
 	int npackfd = -1, idxfd = -1, nidxfd = -1;
 	int tmpfds[3];
 	int idxstatus, done = 0;
+	int nobj_total = 0, nobj_indexed = 0, nobj_loose = 0, nobj_resolved = 0;
 	const struct got_error *err;
 	struct imsgbuf idxibuf;
 	pid_t idxpid;
@@ -386,8 +421,6 @@ got_repo_index_pack(FILE *packfile, struct got_object_
 	}
 	done = 0;
 	while (!done) {
-		int nobj_total, nobj_indexed, nobj_loose, nobj_resolved;
-
 		if (cancel_cb) {
 			err = cancel_cb(cancel_arg);
 			if (err)
@@ -402,11 +435,18 @@ got_repo_index_pack(FILE *packfile, struct got_object_
 		if (nobj_indexed != 0) {
 			err = progress_cb(progress_arg, sb.st_size,
 			    nobj_total, nobj_indexed, nobj_loose,
-			    nobj_resolved);
+			    nobj_resolved, 0);
 			if (err)
 				break;
 		}
 	}
+	if (done) {
+		err = progress_cb(progress_arg, sb.st_size,
+		    nobj_total, nobj_indexed, nobj_loose,
+		    nobj_resolved, done);
+		if (err)
+			goto done;
+	}
 	if (close(imsg_idxfds[0]) == -1) {
 		err = got_error_from_errno("close");
 		goto done;
@@ -1420,6 +1460,8 @@ got_repo_cleanup(struct got_repository *repo,
     off_t *pack_before, off_t *pack_after,
     int *ncommits, int *nloose, int *npacked, int dry_run, int ignore_mtime,
     got_cleanup_progress_cb progress_cb, void *progress_arg,
+    got_pack_progress_cb pack_progress_cb, void *pack_progress_arg,
+    got_pack_index_progress_cb index_progress_cb, void *index_progress_arg,
     got_cancel_cb cancel_cb, void *cancel_arg)
 {
 	const struct got_error *unlock_err, *err = NULL;
@@ -1430,11 +1472,15 @@ got_repo_cleanup(struct got_repository *repo,
 	struct got_reflist_entry *re;
 	struct got_object_id **referenced_ids;
 	int i, nreferenced;
-	int npurged = 0;
+	int npurged = 0, packfd = -1;
+	char *tmpfile_path = NULL, *packfile_path = NULL;
+	FILE *delta_cache = NULL, *packfile = NULL;
+	struct got_object_id pack_hash;
 	time_t max_mtime = 0;
 
 	TAILQ_INIT(&refs);
 	got_ratelimit_init(&rl, 0, 500);
+	memset(&pack_hash, 0, sizeof(pack_hash));
 
 	*loose_before = 0;
 	*loose_after = 0;
@@ -1448,6 +1494,16 @@ got_repo_cleanup(struct got_repository *repo,
 	if (err)
 		return err;
 
+	err = create_temp_packfile(&packfd, &tmpfile_path, repo);
+	if (err)
+		goto done;
+
+	delta_cache = got_opentemp();
+	if (delta_cache == NULL) {
+		err = got_error_from_errno("got_opentemp");
+		goto done;
+	}
+
 	traversed_ids = got_object_idset_alloc();
 	if (traversed_ids == NULL) {
 		err = got_error_from_errno("got_object_idset_alloc");
@@ -1486,6 +1542,30 @@ got_repo_cleanup(struct got_repository *repo,
 			goto done;
 	}
 
+	if (!dry_run) {
+		err = got_pack_create(&pack_hash, packfd, delta_cache,
+		    NULL, 0, referenced_ids, nreferenced, repo, 0,
+		    0, 0, pack_progress_cb, pack_progress_arg,
+		    &rl, cancel_cb, cancel_arg);
+		if (err)
+			goto done;
+
+		err = install_packfile(&packfile, &packfd, &packfile_path,
+		    &tmpfile_path, &pack_hash, repo);
+		if (err)
+			goto done;
+
+		err = got_repo_index_pack(packfile, &pack_hash, repo,
+		    index_progress_cb, index_progress_arg,
+		    cancel_cb, cancel_arg);
+		if (err)
+			goto done;
+
+		err = got_repo_list_packidx(&repo->packidx_paths, repo);
+		if (err)
+			goto done;
+	}
+
 	err = repo_purge_unreferenced_loose_objects(repo, traversed_ids,
 	    loose_before, loose_after, *ncommits, nloose, npacked, &npurged,
 	    dry_run, ignore_mtime, max_mtime, &rl, progress_cb, progress_arg,
@@ -1508,6 +1588,14 @@ got_repo_cleanup(struct got_repository *repo,
 	}
 	if (traversed_ids)
 		got_object_idset_free(traversed_ids);
+	if (packfd != -1 && close(packfd) == -1 && err == NULL)
+		err = got_error_from_errno2("close", packfile_path);
+	if (delta_cache && fclose(delta_cache) == EOF && err == NULL)
+		err = got_error_from_errno("fclose");
+	if (tmpfile_path && unlink(tmpfile_path) == -1 && err == NULL)
+		err = got_error_from_errno2("unlink", tmpfile_path);
+	free(tmpfile_path);
+	free(packfile_path);
 	return err;
 }
 
blob - ed9e497c5a0fdf9632b1c65ac9a22bdfa1d64a61
blob + 90efc87ae3b506ea594098e26225608af3b9d3f6
--- lib/send.c
+++ lib/send.c
@@ -125,7 +125,7 @@ struct pack_progress_arg {
 static const struct got_error *
 pack_progress(void *arg, int ncolored, int nfound, int ntrees,
     off_t packfile_size, int ncommits, int nobj_total, int nobj_deltify,
-    int nobj_written)
+    int nobj_written, int pack_done)
 {
 	const struct got_error *err;
 	struct pack_progress_arg *a = arg;
blob - 00f32bb02afd89bd6cb1db91f4b9385e33bd5617
blob + f4099794bb7ebc53f96a1addc9b3d92c43e8c93d
--- regress/cmdline/cleanup.sh
+++ regress/cmdline/cleanup.sh
@@ -90,7 +90,7 @@ test_cleanup_unreferenced_loose_objects() {
 		return 1
 	fi
 
-	# cleanup should remove loose objects that belonged to the branch
+	# cleanup should remove all loose objects
 	gotadmin cleanup -a -q -r $testroot/repo > $testroot/stdout
 	ret=$?
 	if [ $ret -ne 0 ]; then
@@ -109,7 +109,7 @@ test_cleanup_unreferenced_loose_objects() {
 
 	nloose2=`gotadmin info -r $testroot/repo | grep '^loose objects:' | \
 		cut -d ':' -f 2 | tr -d ' '`
-	if [ "$nloose2" != "$nloose0" ]; then
+	if [ "$nloose2" != "0" ]; then
 		echo "unexpected number of loose objects: $nloose2" >&2
 		test_done "$testroot" "1"
 		return 1
@@ -304,8 +304,8 @@ test_cleanup_redundant_pack_files() {
 	gotadmin cleanup -a -q -r "$testroot/repo"
 
 	n=$(gotadmin info -r "$testroot/repo" | awk '/^pack files/{print $3}')
-	if [ "$n" -ne 3 ]; then
-		echo "expected 3 pack files left, $n found instead" >&2
+	if [ "$n" -ne 2 ]; then
+		echo "expected 2 pack files left, $n found instead" >&2
 		test_done "$testroot" 1
 		return 1
 	fi
@@ -326,8 +326,8 @@ test_cleanup_redundant_pack_files() {
 
 	gotadmin cleanup -a -q -r "$testroot/repo"
 	n=$(gotadmin info -r "$testroot/repo" | awk '/^pack files/{print $3}')
-	if [ "$n" -ne 3 ]; then
-		echo "expected 3 pack files left, $n found instead" >&2
+	if [ "$n" -ne 1 ]; then
+		echo "expected 1 pack files left, $n found instead" >&2
 		test_done "$testroot" 1
 		return 1
 	fi
blob - 7877214cb032a74a7787849d3f0254ee23d84e2c
blob + a22cf957b235a6c58aa4c61d12180d3ffe2e7e3f
--- regress/cmdline/common.sh
+++ regress/cmdline/common.sh
@@ -156,7 +156,7 @@ git_fsck()
 	local testroot="$1"
 	local repo="$2"
 
-	git -C $repo fsck --strict \
+	git -C $repo fsck --strict --no-reflogs \
 		> $testroot/fsck.stdout 2> $testroot/fsck.stderr
 	ret=$?
 	if [ $ret -ne 0 ]; then