.
This commit is contained in:
@@ -1245,6 +1245,53 @@ pub fn current_branch(repo: &gix::Repository) -> Result<Option<String>> {
|
||||
Ok(Some(String::from_utf8_lossy(name.shorten()).into_owned()))
|
||||
}
|
||||
|
||||
/// Branch, tag and HEAD refs of a repository, ready for a NIP-34 kind-30618
|
||||
/// repository state announcement.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RepoRefState {
|
||||
/// `(full refname, commit id)` pairs for heads and tags, sorted.
|
||||
pub refs: Vec<(String, String)>,
|
||||
/// Short branch name HEAD points to, or `None` when detached.
|
||||
pub head: Option<String>,
|
||||
}
|
||||
|
||||
/// Collect the refs of `repo`: local branches and tags as
|
||||
/// `(refname, commit-id)` pairs, plus the branch HEAD points to.
|
||||
pub fn repo_ref_state(repo: &gix::Repository) -> Result<RepoRefState> {
|
||||
let mut refs = Vec::new();
|
||||
|
||||
for reference in repo.references()?.local_branches()? {
|
||||
let reference = reference.map_err(|error| anyhow::anyhow!("{error}"))?;
|
||||
refs.push((
|
||||
String::from_utf8_lossy(reference.name().as_bstr()).into_owned(),
|
||||
reference.id().to_string(),
|
||||
));
|
||||
}
|
||||
for reference in repo.references()?.tags()? {
|
||||
let reference = reference.map_err(|error| anyhow::anyhow!("{error}"))?;
|
||||
refs.push((
|
||||
String::from_utf8_lossy(reference.name().as_bstr()).into_owned(),
|
||||
reference.id().to_string(),
|
||||
));
|
||||
}
|
||||
refs.sort();
|
||||
|
||||
let head = match repo.head() {
|
||||
Ok(head) => head
|
||||
.referent_name()
|
||||
.filter(|name| name.as_bstr().starts_with(b"refs/heads/"))
|
||||
.map(|name| String::from_utf8_lossy(name.shorten()).into_owned()),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
Ok(RepoRefState { refs, head })
|
||||
}
|
||||
|
||||
/// [`repo_ref_state`] for the repository at `workdir`.
|
||||
pub fn worktree_ref_state(workdir: &Path) -> Result<RepoRefState> {
|
||||
repo_ref_state(&open_with_cache(workdir)?)
|
||||
}
|
||||
|
||||
/// Everything the browser needs to refresh after a branch or tag switch.
|
||||
pub struct WorktreeSnapshot {
|
||||
/// Relative paths of all worktree entries, directories first.
|
||||
@@ -1375,6 +1422,47 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_ref_state_lists_branches_tags_and_head() {
|
||||
let (_dir, repo) = fixture(&[("a.txt", b"hello")]);
|
||||
commit_all(&repo, "initial");
|
||||
let workdir = repo.workdir().expect("workdir").to_path_buf();
|
||||
|
||||
let state = repo_ref_state(&repo).expect("refs");
|
||||
|
||||
let branch = current_branch(&repo).expect("branch").expect("on a branch");
|
||||
assert_eq!(state.head.as_deref(), Some(branch.as_str()));
|
||||
assert_eq!(state.refs.len(), 1);
|
||||
assert_eq!(state.refs[0].0, format!("refs/heads/{branch}"));
|
||||
assert_eq!(state.refs[0].1.len(), 40);
|
||||
|
||||
// Additional branches and tags are listed alongside.
|
||||
git_run(&workdir, &["branch", "feature"]);
|
||||
git_run(&workdir, &["tag", "v1.0"]);
|
||||
|
||||
let state = repo_ref_state(&repo).expect("refs");
|
||||
let mut expected: Vec<String> = vec![
|
||||
format!("refs/heads/{branch}"),
|
||||
"refs/heads/feature".to_owned(),
|
||||
"refs/tags/v1.0".to_owned(),
|
||||
];
|
||||
expected.sort();
|
||||
assert_eq!(
|
||||
state
|
||||
.refs
|
||||
.iter()
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
);
|
||||
|
||||
// A detached HEAD yields no head branch.
|
||||
git_run(&workdir, &["checkout", "--detach"]);
|
||||
let state = repo_ref_state(&repo).expect("refs");
|
||||
assert!(state.head.is_none());
|
||||
assert_eq!(state.refs.len(), 3);
|
||||
}
|
||||
|
||||
/// Build a throwaway non-bare repository with the given files (rel → bytes).
|
||||
fn fixture(files: &[(&str, &[u8])]) -> (tempfile::TempDir, gix::Repository) {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
Reference in New Issue
Block a user