init local repo

This commit is contained in:
2026-08-31 10:25:28 +07:00
parent 6fcc945dae
commit 675e40ca1c
8 changed files with 1119 additions and 183 deletions
+76
View File
@@ -289,6 +289,57 @@ pub fn push_main(repo_path: &Path, base_url: &str, owner: &str, repo_id: &str) -
Ok(())
}
/// Push every local branch and tag of the repository at `repo_path` to a
/// grasp server (like `git push <url> --all --tags`), so an initialized
/// repository's whole history is mirrored, not just `main`.
pub fn push_all(repo_path: &Path, base_url: &str, owner: &str, repo_id: &str) -> Result<()> {
let url = format!("{base_url}/{owner}/{repo_id}.git");
let output = Command::new("git")
.arg("-C")
.arg(repo_path)
.args(["push", "--all", "--tags"])
.arg(&url)
.env("GIT_TERMINAL_PROMPT", "0")
.stderr(Stdio::piped())
.output()
.context("failed to spawn `git push`")?;
if !output.status.success() {
bail!(
"git push to {base_url} failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
);
}
Ok(())
}
/// The earliest unique commit of the repository at `repo_path` (a root
/// commit, like `git rev-list --max-parents=0 HEAD`), used as the NIP-34
/// announcement's `euc` marker. `None` for a repository without commits.
pub fn root_commit(repo_path: &Path) -> Result<Option<String>> {
let output = Command::new("git")
.arg("-C")
.arg(repo_path)
.args(["rev-list", "--max-parents=0", "HEAD"])
.env("GIT_TERMINAL_PROMPT", "0")
.stderr(Stdio::piped())
.output()
.context("failed to spawn `git rev-list`")?;
// An unborn HEAD (no commits yet) makes `rev-list` fail,
// there is no unique commit to report then.
if !output.status.success() {
return Ok(None);
}
Ok(String::from_utf8_lossy(&output.stdout)
.lines()
.next()
.map(str::to_owned)
.filter(|id| id.len() == 40))
}
/// Add `origin` pointing at `url` when the repository has no remote yet.
/// No-op if `origin` already exists.
pub fn ensure_origin(repo_path: &Path, url: &str) -> Result<()> {
@@ -1653,6 +1704,31 @@ mod tests {
assert_eq!(found, expected);
}
#[test]
fn root_commit_reports_the_first_ancestor() {
let (dir, repo) = fixture(&[("a.txt", b"one")]);
commit_all(&repo, "initial");
let dir = dir.path();
let root = root_commit(dir).expect("root").expect("commit");
assert_eq!(root.len(), 40);
// The root commit does not change when history grows.
std::fs::write(dir.join("b.txt"), b"two").expect("write");
commit_all(&repo, "second");
assert_eq!(
root_commit(dir).expect("root").as_deref(),
Some(root.as_str())
);
}
#[test]
fn root_commit_is_none_without_commits() {
let (_dir, repo) = fixture(&[("a.txt", b"one")]);
let workdir = repo.workdir().expect("workdir");
assert_eq!(root_commit(workdir).expect("root"), None);
}
#[test]
fn repo_ref_state_lists_branches_tags_and_head() {
let (_dir, repo) = fixture(&[("a.txt", b"hello")]);