chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
A branch renamed to one under its old name syncs
.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(); | ||