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    pub fn workdir(&self) -> Result<&Path> {
57        self.repo.workdir().ok_or(GitCryptError::Other(
58            "Repository has no working directory".into(),
59        ))
60    }
61}
62
63/// Clean filter: encrypt file content
64pub fn clean_filter(key: &CryptoKey) -> Result<()> {
65    let mut input = Vec::new();
66    io::stdin().read_to_end(&mut input)?;
67
68    // Check if already encrypted (has magic header)
69    if CryptoKey::is_encrypted(&input) {
70        io::stdout().write_all(&input)?;
71        return Ok(());
72    }
73
74    let encrypted = key.encrypt(&input)?;
75
76    // Write encrypted data to stdout
77    io::stdout().write_all(&encrypted)?;
78
79    Ok(())
80}
81
82/// Smudge filter: decrypt file content
83pub fn smudge_filter(key: &CryptoKey) -> Result<()> {
84    let mut input = Vec::new();
85    io::stdin().read_to_end(&mut input)?;
86
87    // Check if encrypted
88    if !CryptoKey::is_encrypted(&input) {
89        io::stdout().write_all(&input)?;
90        return Ok(());
91    }
92
93    let decrypted = key.decrypt(&input)?;
94
95    // Write decrypted data to stdout
96    io::stdout().write_all(&decrypted)?;
97
98    Ok(())
99}
100
101/// Diff filter: show that file is encrypted
102pub fn diff_filter() -> Result<()> {
103    let mut input = Vec::new();
104    io::stdin().read_to_end(&mut input)?;
105
106    if CryptoKey::is_encrypted(&input) {
107        writeln!(io::stdout(), "*** This file is encrypted with git-crypt ***")?;
108    } else {
109        io::stdout().write_all(&input)?;
110    }
111
112    Ok(())
113}