Skip to main content

changeset_git/repository/
mod.rs

1mod commit;
2mod diff;
3mod files;
4mod remote;
5mod staging;
6mod status;
7mod tag;
8
9use std::path::{Path, PathBuf};
10
11use crate::{GitError, Result};
12
13pub struct Repository {
14    pub(crate) inner: git2::Repository,
15    root: PathBuf,
16}
17
18impl Repository {
19    /// # Errors
20    ///
21    /// Returns [`GitError::NotARepository`] if the path is not inside a git repository.
22    pub fn open(path: &Path) -> Result<Self> {
23        let inner = git2::Repository::discover(path).map_err(|_| GitError::NotARepository {
24            path: path.to_path_buf(),
25        })?;
26
27        let root = inner.workdir().ok_or_else(|| GitError::NotARepository {
28            path: path.to_path_buf(),
29        })?;
30
31        // Use dunce to get a path without the \\?\ prefix on Windows
32        let root = dunce::simplified(root).to_path_buf();
33
34        Ok(Self { inner, root })
35    }
36
37    #[must_use]
38    pub fn root(&self) -> &Path {
39        &self.root
40    }
41
42    pub(crate) fn to_relative_path(&self, path: &Path) -> PathBuf {
43        if path.is_absolute() {
44            // Use dunce to normalize the path (removes \\?\ prefix on Windows)
45            let normalized = dunce::simplified(path);
46            normalized
47                .strip_prefix(&self.root)
48                .map_or_else(|_| path.to_path_buf(), Path::to_path_buf)
49        } else {
50            path.to_path_buf()
51        }
52    }
53}
54
55#[cfg(test)]
56pub(crate) mod tests {
57    use super::*;
58    use tempfile::TempDir;
59
60    pub(crate) fn setup_test_repo() -> anyhow::Result<(TempDir, Repository)> {
61        let dir = TempDir::new()?;
62        let repo = git2::Repository::init(dir.path())?;
63
64        let mut config = repo.config()?;
65        config.set_str("user.name", "Test")?;
66        config.set_str("user.email", "test@example.com")?;
67
68        let sig = git2::Signature::now("Test", "test@example.com")?;
69        let tree_id = repo.index()?.write_tree()?;
70        let tree = repo.find_tree(tree_id)?;
71        repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])?;
72
73        let repository = Repository::open(dir.path())?;
74        Ok((dir, repository))
75    }
76
77    #[test]
78    fn open_repository() -> anyhow::Result<()> {
79        let (dir, repo) = setup_test_repo()?;
80        let expected = dir.path().canonicalize()?;
81        let actual = repo.root().canonicalize()?;
82        assert_eq!(actual, expected);
83        Ok(())
84    }
85
86    #[test]
87    fn open_nonexistent_repository() {
88        let dir = TempDir::new().expect("failed to create temp dir");
89        let result = Repository::open(dir.path());
90        assert!(matches!(result, Err(GitError::NotARepository { .. })));
91    }
92}