Skip to main content

crate_seq_git/
checkout.rs

1//! Temp-directory tag checkout via tree walking.
2
3use gix::bstr::ByteSlice;
4use gix::object::tree::EntryKind;
5use std::path::Path;
6
7use crate::Error;
8
9/// Checks out the tree of `tag_name` into a fresh temporary directory.
10///
11/// Only git-tracked files at the tagged commit's tree are written. Returns
12/// the `TempDir`; the directory is cleaned up on drop.
13///
14/// # Errors
15///
16/// Returns `Error::OpenRepo` if the repository cannot be opened, `Error::TagNotFound`
17/// if the tag does not exist, `Error::Peel` if the tag cannot be peeled to a tree,
18/// or I/O errors if the temporary directory or files cannot be created.
19pub fn checkout_tag(
20    repo_path: &Path,
21    tag_name: &str,
22) -> Result<tempfile::TempDir, Error> {
23    let repo = gix::discover(repo_path).map_err(|e| Error::OpenRepo {
24        path: repo_path.to_owned(),
25        source: Box::new(e),
26    })?;
27
28    let ref_name = format!("refs/tags/{tag_name}");
29    let mut tag_ref = repo
30        .try_find_reference(&ref_name)
31        .map_err(|e| Error::Object(e.to_string()))?
32        .ok_or_else(|| Error::TagNotFound(tag_name.to_owned()))?;
33
34    let tree = tag_ref
35        .peel_to_tree()
36        .map_err(|e| Error::Peel(e.to_string()))?;
37
38    let tmp = tempfile::TempDir::new()?;
39    write_tree_to_dir(&tree, tmp.path())?;
40    Ok(tmp)
41}
42
43/// Recursively writes a git tree's contents into `base` on disk.
44///
45/// Blobs are written as files. Subtrees become directories. Symlinks and
46/// submodules are skipped (unsupported in v1).
47fn write_tree_to_dir(tree: &gix::Tree<'_>, base: &Path) -> Result<(), Error> {
48    for entry_result in tree.iter() {
49        let entry = entry_result.map_err(|e| Error::TreeTraversal(e.to_string()))?;
50        let raw_name = entry.filename().to_str_lossy();
51        let path = base.join(Path::new(raw_name.as_ref()));
52
53        match entry.kind() {
54            EntryKind::Tree => {
55                std::fs::create_dir_all(&path)?;
56                let obj = entry.object().map_err(|e| Error::Object(e.to_string()))?;
57                let subtree = obj
58                    .try_into_tree()
59                    .map_err(|e| Error::Object(e.to_string()))?;
60                write_tree_to_dir(&subtree, &path)?;
61            }
62            EntryKind::Blob | EntryKind::BlobExecutable => {
63                let obj = entry.object().map_err(|e| Error::Object(e.to_string()))?;
64                let blob = obj
65                    .try_into_blob()
66                    .map_err(|e| Error::Object(e.to_string()))?;
67                std::fs::write(&path, &blob.data)?;
68            }
69            EntryKind::Link | EntryKind::Commit => {}
70        }
71    }
72    Ok(())
73}