Skip to main content

changeset_git/repository/
status.rs

1use crate::{GitError, Result};
2
3use super::Repository;
4
5impl Repository {
6    /// # Errors
7    ///
8    /// Returns [`GitError::DetachedHead`] if HEAD is not on a branch.
9    pub fn current_branch(&self) -> Result<String> {
10        let head = self.inner.head()?;
11
12        if !head.is_branch() {
13            return Err(GitError::DetachedHead);
14        }
15
16        head.shorthand()
17            .map(String::from)
18            .ok_or(GitError::DetachedHead)
19    }
20
21    /// # Errors
22    ///
23    /// Returns an error if the git status operation fails.
24    pub fn is_working_tree_clean(&self) -> Result<bool> {
25        let statuses = self.inner.statuses(Some(
26            git2::StatusOptions::new()
27                .include_untracked(true)
28                .recurse_untracked_dirs(true),
29        ))?;
30
31        Ok(statuses.is_empty())
32    }
33
34    /// # Errors
35    ///
36    /// Returns [`GitError::DirtyWorkingTree`] if there are uncommitted changes.
37    pub fn require_clean_working_tree(&self) -> Result<()> {
38        if self.is_working_tree_clean()? {
39            Ok(())
40        } else {
41            Err(GitError::DirtyWorkingTree)
42        }
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::super::tests::setup_test_repo;
49    use crate::GitError;
50    use std::fs;
51
52    #[test]
53    fn current_branch_on_main() -> anyhow::Result<()> {
54        let (_dir, repo) = setup_test_repo()?;
55        let branch = repo.current_branch()?;
56        assert!(branch == "main" || branch == "master");
57        Ok(())
58    }
59
60    #[test]
61    fn clean_working_tree() -> anyhow::Result<()> {
62        let (_dir, repo) = setup_test_repo()?;
63        assert!(repo.is_working_tree_clean()?);
64        Ok(())
65    }
66
67    #[test]
68    fn dirty_working_tree_with_untracked_file() -> anyhow::Result<()> {
69        let (dir, repo) = setup_test_repo()?;
70        fs::write(dir.path().join("new_file.txt"), "content")?;
71        assert!(!repo.is_working_tree_clean()?);
72        Ok(())
73    }
74
75    #[test]
76    fn require_clean_fails_on_dirty() -> anyhow::Result<()> {
77        let (dir, repo) = setup_test_repo()?;
78        fs::write(dir.path().join("new_file.txt"), "content")?;
79
80        let result = repo.require_clean_working_tree();
81        assert!(result.is_err());
82        Ok(())
83    }
84
85    #[test]
86    fn current_branch_detached_head_fails() -> anyhow::Result<()> {
87        let (_dir, repo) = setup_test_repo()?;
88        let head_commit = repo.inner.head()?.peel_to_commit()?;
89        repo.inner.set_head_detached(head_commit.id())?;
90
91        let result = repo.current_branch();
92        assert!(matches!(result, Err(GitError::DetachedHead)));
93        Ok(())
94    }
95}