~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

Mirrors keep a commit-graph, so walks of history read one file

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:serve/commit-graph
crates/knotless/src/git.rs+26−0
@@ -207,6 +207,32 @@ impl Git {
Ok(())
}
/// Whether the mirror has a commit-graph, as [`Git::graph`] writes.
pub fn has_graph(&self) -> bool {
let info = self.dir.join("objects/info");
info.join("commit-graph").exists() || info.join("commit-graphs/commit-graph-chain").exists()
}
/// Adds to the commit-graph the commits the refs reach that it does
/// not hold yet: each commit's parents and date in one file, which
/// walks of history read rather than each commit from the packs, many
/// times faster. Split, it writes only what is new, and merges its
/// layers as git does.
pub async fn graph(&self) -> Result<()> {
self.run(
&[
"commit-graph",
"write",
"--reachable",
"--split",
"--no-progress",
],
None,
)
.await?;
Ok(())
}
/// Applies `update-ref --stdin` instructions as one transaction.
pub async fn update_refs(&self, script: &str) -> Result<()> {
if !script.is_empty() {
crates/knotless/src/mirror.rs+5−1
@@ -98,7 +98,8 @@ impl Mirror {
let mut want = live;
want.sort();
let local = self.git.refs().await?;
if local != want {
let moved = local != want;
if moved {
let mut script = String::new();
for (name, sha) in &want {
script.push_str(&format!("update {name} {sha}\n"));
@@ -111,6 +112,9 @@ impl Mirror {
self.git.update_refs(&script).await?;
}
self.git.tidy(PACKS).await?;
if moved || !self.git.has_graph() {
self.git.graph().await?;
crates/knotless/src/mirror.rsline 116

A failed graph write fails the sync even though the refs are already updated and the graph is only an optimisation. Consider logging the error and continuing.

}
if let Some(branch) = snap
.repo
.as_ref()
crates/knotless/tests/library.rs+2−0
@@ -784,6 +784,8 @@ fn a_push_stops_at_the_rate_limit() {
.await
.unwrap();
mirror.sync(&r, &snap).await.unwrap();
// Its history is in a commit-graph, for walks to read.
assert!(mirror.git.has_graph());
// New refs at a commit the PDS has: no pack, one create each, so
// two calls.
let updates: Vec<Update> = (0..250)
docs/design/storage.md+8−0
@@ -438,6 +438,14 @@ A mirror is created with git's own gc off (`gc.auto=0`). Instead, a sync
that leaves it holding more than 50 packs repacks them into one, keeping
objects no ref reaches.
A sync that moves the refs, or finds a mirror with no commit-graph, then
writes one (`git commit-graph write --reachable --split`): each commit's
parents and date in one file beside the packs, which walks of history read
rather than each commit from the packs. On the rust repository's 345,000
commits, a walk that takes 2.4 seconds without one takes 0.17 with it. Split,
the write adds only the commits the graph does not hold yet, so a push costs
it milliseconds. Only the first, of a mirror's whole history, takes long.
## The git remote helper
git runs `git-remote-knotless` for `knotless://` URLs. It lists live refs,