chore: and reconcile pushes with server only refs
This commit is contained in:
@@ -1222,22 +1222,25 @@ impl Repo {
|
||||
base_url,
|
||||
owner,
|
||||
repo_id,
|
||||
&["refs/heads/main:refs/heads/main"],
|
||||
&["+refs/heads/main:refs/heads/main"],
|
||||
)
|
||||
}
|
||||
|
||||
/// Mirrors all branches and tags to the grasp repository at `base_url`.
|
||||
///
|
||||
/// Force refspecs keep the server ref map following the local repository,
|
||||
/// grasp authorizes pushes by ref map equality, not fast-forwards.
|
||||
pub fn push_all(&self, base_url: &str, owner: &str, repo_id: &str) -> Result<()> {
|
||||
self.push_refspecs(
|
||||
base_url,
|
||||
owner,
|
||||
repo_id,
|
||||
&["refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"],
|
||||
&["+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*"],
|
||||
)
|
||||
}
|
||||
|
||||
/// Pushes explicit refspecs to the grasp repository at `base_url`.
|
||||
fn push_refspecs(
|
||||
pub fn push_refspecs(
|
||||
&self,
|
||||
base_url: &str,
|
||||
owner: &str,
|
||||
@@ -1262,6 +1265,33 @@ impl Repo {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lists branches and tags advertised by the remote at `url`.
|
||||
pub fn ls_remote(&self, url: &str) -> Result<Vec<(String, String)>> {
|
||||
let url = Self::transport_url(url);
|
||||
let output = Self::run_git(self.workdir_or_dot(), &["ls-remote", &url], "git ls-remote")?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"git ls-remote on {url} failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
);
|
||||
}
|
||||
|
||||
let refs = String::from_utf8_lossy(&output.stdout)
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let (oid, name) = line.split_once('\t')?;
|
||||
let name = name.trim();
|
||||
|
||||
(name.starts_with("refs/heads/") || name.starts_with("refs/tags/"))
|
||||
.then(|| (name.to_owned(), oid.to_owned()))
|
||||
.filter(|(name, _)| !name.ends_with("^{}"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(refs)
|
||||
}
|
||||
|
||||
/// Sets the `origin` URL when the repository has no remote yet.
|
||||
pub fn ensure_origin(&self, url: &str) -> Result<()> {
|
||||
if self.inner.find_remote("origin").is_ok() {
|
||||
|
||||
@@ -122,6 +122,166 @@ fn push_all_mirrors_branches_and_tags() {
|
||||
assert!(refs.contains("refs/tags/v1.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ls_remote_lists_branches_and_tags_only() {
|
||||
let server = tempfile::tempdir().unwrap();
|
||||
let server_repo = bare_server(server.path(), "npub1test", "my-repo");
|
||||
|
||||
let (dir, repo) = fixture(&[("a.txt", b"one")]);
|
||||
commit_all(&repo, "initial");
|
||||
let dir = dir.path();
|
||||
|
||||
git_run(dir, &["branch", "feature"]);
|
||||
git_run(dir, &["tag", "v1"]);
|
||||
git_run(dir, &["tag", "-a", "v2", "-m", "annotated"]);
|
||||
|
||||
let base_url = format!("file://{}", server.path().display());
|
||||
let local = Repo::open(dir).expect("open");
|
||||
local
|
||||
.push_all(&base_url, "npub1test", "my-repo")
|
||||
.expect("push");
|
||||
|
||||
// Refs outside heads and tags, and the HEAD symref line, are not listed.
|
||||
let main = git_in(dir, &["rev-parse", "refs/heads/main"]).expect("main oid");
|
||||
git_run(&server_repo, &["update-ref", "refs/nostr/abc", &main]);
|
||||
|
||||
let remote = local
|
||||
.ls_remote(&format!("file://{}", server_repo.display()))
|
||||
.expect("ls-remote");
|
||||
let names: Vec<String> = remote.iter().map(|(name, _)| name.clone()).collect();
|
||||
|
||||
assert_eq!(
|
||||
names,
|
||||
vec![
|
||||
"refs/heads/feature".to_owned(),
|
||||
"refs/heads/main".to_owned(),
|
||||
"refs/tags/v1".to_owned(),
|
||||
"refs/tags/v2".to_owned(),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
remote
|
||||
.iter()
|
||||
.find(|(name, _)| name == "refs/heads/main")
|
||||
.map(|(_, oid)| oid.as_str()),
|
||||
Some(main.as_str())
|
||||
);
|
||||
// The annotated tag lists the tag object, matching the local ref state,
|
||||
// without a peeled `^{}` duplicate.
|
||||
let v2 = git_in(dir, &["rev-parse", "refs/tags/v2"]).expect("v2 oid");
|
||||
assert_eq!(
|
||||
remote
|
||||
.iter()
|
||||
.find(|(name, _)| name == "refs/tags/v2")
|
||||
.map(|(_, oid)| oid.as_str()),
|
||||
Some(v2.as_str())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_all_force_updates_diverged_remote_branches() {
|
||||
let server = tempfile::tempdir().unwrap();
|
||||
let server_repo = bare_server(server.path(), "npub1test", "my-repo");
|
||||
|
||||
let (dir, repo) = fixture(&[("a.txt", b"one")]);
|
||||
commit_all(&repo, "initial");
|
||||
std::fs::write(dir.path().join("b.txt"), b"two").expect("write");
|
||||
commit_all(&repo, "second");
|
||||
let dir = dir.path();
|
||||
|
||||
let base_url = format!("file://{}", server.path().display());
|
||||
let local = Repo::open(dir).expect("open");
|
||||
local
|
||||
.push_all(&base_url, "npub1test", "my-repo")
|
||||
.expect("initial push");
|
||||
|
||||
// Diverge local main from the pushed state.
|
||||
git_run(dir, &["reset", "-q", "--hard", "HEAD~1"]);
|
||||
std::fs::write(dir.join("c.txt"), b"three").expect("write");
|
||||
commit_all(&repo, "diverged");
|
||||
|
||||
local
|
||||
.push_all(&base_url, "npub1test", "my-repo")
|
||||
.expect("force push");
|
||||
|
||||
assert_eq!(
|
||||
git_in(dir, &["rev-parse", "refs/heads/main"]).expect("local main"),
|
||||
git_in(&server_repo, &["rev-parse", "refs/heads/main"]).expect("server main")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_refspecs_deletes_server_refs_missing_locally() {
|
||||
let server = tempfile::tempdir().unwrap();
|
||||
let server_repo = bare_server(server.path(), "npub1test", "my-repo");
|
||||
|
||||
let (dir, repo) = fixture(&[("a.txt", b"one")]);
|
||||
commit_all(&repo, "initial");
|
||||
let dir = dir.path();
|
||||
|
||||
git_run(dir, &["branch", "feature"]);
|
||||
git_run(dir, &["tag", "v1"]);
|
||||
|
||||
let base_url = format!("file://{}", server.path().display());
|
||||
let local = Repo::open(dir).expect("open");
|
||||
local
|
||||
.push_all(&base_url, "npub1test", "my-repo")
|
||||
.expect("initial push");
|
||||
|
||||
git_run(dir, &["branch", "-D", "feature"]);
|
||||
git_run(dir, &["tag", "-d", "v1"]);
|
||||
|
||||
local
|
||||
.push_refspecs(
|
||||
&base_url,
|
||||
"npub1test",
|
||||
"my-repo",
|
||||
&[
|
||||
"+refs/heads/*:refs/heads/*",
|
||||
"+refs/tags/*:refs/tags/*",
|
||||
":refs/heads/feature",
|
||||
":refs/tags/v1",
|
||||
],
|
||||
)
|
||||
.expect("push with deletions");
|
||||
|
||||
let refs = git_in(&server_repo, &["show-ref"]).expect("server refs");
|
||||
assert!(refs.contains("refs/heads/main"));
|
||||
assert!(!refs.contains("refs/heads/feature"));
|
||||
assert!(!refs.contains("refs/tags/v1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_refs_brings_server_only_branches_into_local() {
|
||||
let server = tempfile::tempdir().unwrap();
|
||||
bare_server(server.path(), "npub1test", "my-repo");
|
||||
|
||||
let (dir, repo) = fixture(&[("a.txt", b"one")]);
|
||||
commit_all(&repo, "initial");
|
||||
let dir = dir.path();
|
||||
git_run(dir, &["branch", "feature"]);
|
||||
|
||||
let base_url = format!("file://{}", server.path().display());
|
||||
Repo::open(dir)
|
||||
.expect("open")
|
||||
.push_all(&base_url, "npub1test", "my-repo")
|
||||
.expect("push");
|
||||
let feature = git_in(dir, &["rev-parse", "refs/heads/feature"]).expect("feature oid");
|
||||
|
||||
// A repository that never had the branch fetches it by name.
|
||||
let (other_dir, _other_repo) = fixture(&[("unrelated.txt", b"x")]);
|
||||
let other = Repo::open(other_dir.path()).expect("open other");
|
||||
let server_url = format!("file://{}/npub1test/my-repo.git", server.path().display());
|
||||
other
|
||||
.fetch_refs(&[&server_url], "+refs/heads/feature:refs/heads/feature")
|
||||
.expect("fetch ref");
|
||||
|
||||
assert_eq!(
|
||||
git_in(other_dir.path(), &["rev-parse", "refs/heads/feature"]).expect("fetched feature"),
|
||||
feature
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_state_matches_the_git_cli_ref_listing() {
|
||||
let (dir, repo) = fixture(&[("a.txt", b"one")]);
|
||||
|
||||
Reference in New Issue
Block a user