~ 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

A branch renamed to one under its old name syncs

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:mirror/branch-renamed-under-itself
.changeset/branch-renamed-under-itself.mdnew+7−0
@@ -0,0 +1,7 @@
---
default: patch
---
# A branch renamed to one under its old name syncs
A mirror of a repository whose branch `a` was renamed to `a/b`, or back, now takes the new branch, rather than failing every read with git's "cannot lock ref" until the mirror is removed.
crates/knotless/src/mirror.rs+44−5
@@ -150,15 +150,22 @@ async fn settle(git: Git, live: Vec<(String, String)>, branch: Option<String>) -
let moved = local != want;
if moved {
unstick(&git.dir.join("refs"))?;
let mut script = String::new();
for (name, sha) in &want {
script.push_str(&format!("update {name} {sha}\n"));
}
// The refs gone are deleted before the rest are set, apart: git
// refuses in one transaction to make `a/b` while `a` is there,
// even with `a` deleted in it, as after a branch's rename from
// `a` to `a/b`. Between the two each ref is as it was or as it
// will be.
let mut gone = String::new();
for (name, _) in &local {
if !want.iter().any(|(n, _)| n == name) {
script.push_str(&format!("delete {name}\n"));
gone.push_str(&format!("delete {name}\n"));
}
}
git.update_refs(&gone).await?;
let mut script = String::new();
for (name, sha) in &want {
script.push_str(&format!("update {name} {sha}\n"));
}
git.update_refs(&script).await?;
}
if moved || !git.has_graph() {
@@ -232,6 +239,38 @@ mod tests {
assert_eq!(git.refs().await.unwrap(), fewer);
}
#[tokio::test]
async fn a_branch_renamed_under_its_old_name_moves() {
let dir = tempfile::tempdir().unwrap();
let git = Git::init_bare(&dir.path().join("m.git")).await.unwrap();
let tree = git.run(&["mktree"], Some(b"")).await.unwrap();
let tree = String::from_utf8(tree).unwrap();
let tree = tree.trim();
let commit = [
"-c",
"user.name=a",
"-c",
"user.email=a@a",
"commit-tree",
"-m",
"a",
tree,
];
let sha = git.run(&commit, None).await.unwrap();
let sha = String::from_utf8(sha).unwrap();
let sha = sha.trim().to_string();
let at = |name: &str| vec![(name.to_string(), sha.clone())];
// `compat` renamed to `compat/pdsjs`, then back.
for refs in [
at("refs/heads/compat"),
at("refs/heads/compat/pdsjs"),
at("refs/heads/compat"),
] {
settle(git.clone(), refs.clone(), None).await.unwrap();
assert_eq!(git.refs().await.unwrap(), refs);
}
}
#[tokio::test]
async fn a_tidy_under_way_is_left_to_finish() {
let dir = tempfile::tempdir().unwrap();