git_crypt/
git.rs

1use git2::Repository;
2use std::io::{self, Read, Write};
3use std::path::Path;
4use crate::crypto::CryptoKey;
5use crate::error::{GitCryptError, Result};
6
7pub struct GitRepo {
8    repo: Repository,
9}
10
11impl GitRepo {
12    /// Open repository at the given path
13    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
14        let repo = Repository::discover(path)
15            .map_err(|_| GitCryptError::NotInGitRepo)?;
16        Ok(Self { repo })
17    }
18
19    /// Get the git directory path
20    pub fn git_dir(&self) -> &Path {
21        self.repo.path()
22    }
23
24    /// Configure git filters for git-crypt
25    pub fn configure_filters(&self) -> Result<()> {
26        let mut config = self.repo.config()?;
27
28        // Set up clean filter (encrypts on add/commit)
29        config.set_str("filter.git-crypt.clean", "git-crypt clean")?;
30
31        // Set up smudge filter (decrypts on checkout)
32        config.set_str("filter.git-crypt.smudge", "git-crypt smudge")?;
33
34        // Don't diff encrypted files
35        config.set_str("filter.git-crypt.diff", "git-crypt diff")?;
36
37        // Required attribute
38        config.set_bool("filter.git-crypt.required", true)?;
39
40        Ok(())
41    }
42
43    /// Remove git-crypt filters
44    pub fn remove_filters(&self) -> Result<()> {
45        let mut config = self.repo.config()?;
46
47        let _ = config.remove("filter.git-crypt.clean");
48        let _ = config.remove("filter.git-crypt.smudge");
49        let _ = config.remove("filter.git-crypt.diff");
50        let _ = config.remove("filter.git-crypt.required");
51
52        Ok(())
53    }
54
55    /// Get repository root path
56    #[allow(dead_code)]
57    pub fn workdir(&self) -> Result<&Path> {
58        self.repo.workdir().ok_or(GitCryptError::Other(
59            "Repository has no working directory".into(),
60        ))
61    }
62}
63
64/// Clean filter: encrypt file content
65pub fn clean_filter(key: &CryptoKey) -> Result<()> {
66    let mut input = Vec::new();
67    io::stdin().read_to_end(&mut input)?;
68
69    // Check if already encrypted (has magic header)
70    if CryptoKey::is_encrypted(&input) {
71        io::stdout().write_all(&input)?;
72        return Ok(());
73    }
74
75    let encrypted = key.encrypt(&input)?;
76
77    // Write encrypted data to stdout
78    io::stdout().write_all(&encrypted)?;
79
80    Ok(())
81}
82
83/// Smudge filter: decrypt file content
84pub fn smudge_filter(key: &CryptoKey) -> Result<()> {
85    let mut input = Vec::new();
86    io::stdin().read_to_end(&mut input)?;
87
88    // Check if encrypted
89    if !CryptoKey::is_encrypted(&input) {
90        io::stdout().write_all(&input)?;
91        return Ok(());
92    }
93
94    let decrypted = key.decrypt(&input)?;
95
96    // Write decrypted data to stdout
97    io::stdout().write_all(&decrypted)?;
98
99    Ok(())
100}
101
102/// Diff filter: show that file is encrypted
103pub fn diff_filter() -> Result<()> {
104    let mut input = Vec::new();
105    io::stdin().read_to_end(&mut input)?;
106
107    if CryptoKey::is_encrypted(&input) {
108        writeln!(io::stdout(), "*** This file is encrypted with git-crypt ***")?;
109    } else {
110        io::stdout().write_all(&input)?;
111    }
112
113    Ok(())
114}