Skip to content

Commit cd57d3a

Browse files
committed
support partial decrypt
1 parent 1797803 commit cd57d3a

File tree

8 files changed

+143
-16
lines changed

8 files changed

+143
-16
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description = "Encrypt/decrypt files in git repo using one password"
55
homepage = "https://github.com/lxl66566/git-simple-encrypt"
66
repository = "https://github.com/lxl66566/git-simple-encrypt"
77
license = "MIT"
8-
version = "1.1.1"
8+
version = "1.2.0"
99
edition = "2021"
1010
readme = "./README.md"
1111
categories = ["cryptography"]

docs/README_zh-CN.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
```sh
2929
cargo +nightly install git-simple-encrypt
3030
```
31+
[cargo-binstall](https://github.com/cargo-bins/cargo-binstall)
32+
```sh
33+
cargo binstall git-simple-encrypt
34+
```
3135

3236
## 使用
3337

@@ -37,8 +41,11 @@ git-se add file.txt # 将 `file.txt` 添加到加密列表
3741
git-se add mydir # 将 `mydir` 添加到加密列表
3842
git-se e # 加密当前仓库所有列表内的文件
3943
git-se d # 解密...
44+
git-se d 'src/*' # 部分解密
4045
```
4146

47+
`git-se -h``git-se [subcommand] -h` 查看更多帮助。
48+
4249
## 注意事项
4350

4451
- 加密时会自动执行 `git add -A`,请确保已妥善处理 `.gitignore`
@@ -56,3 +63,8 @@ graph TD;
5663

5764
- 如果 zstd 压缩后具有反效果,则跳过压缩。
5865
- 解密时对所有 `.enc`, `.zst.enc` 进行解密。
66+
67+
## TODO
68+
69+
- [ ] zstd effect checking
70+
- [x] partial decrypt

readme.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ There are several different ways to install it, you can choose **any** of them.
2828
```sh
2929
cargo +nightly install git-simple-encrypt
3030
```
31+
or [cargo-binstall](https://github.com/cargo-bins/cargo-binstall):
32+
```sh
33+
cargo binstall git-simple-encrypt
34+
```
3135

3236
## Usage
3337

@@ -36,9 +40,12 @@ git-se set key 123456 # Set the password to `123456`.
3640
git-se add file.txt # Add `file.txt` to the need-to-be-encrypted list.
3741
git-se add mydir # Add `mydir` to the need-to-be-encrypted list.
3842
git-se e # Encrypt files in list in the current repository.
39-
git-se d # Decrypt...
43+
git-se d # Decrypt all files with extension `.enc`, `.zst.enc`.
44+
git-se d 'src/*' # Decrypt all encrypted files in `src` folder.
4045
```
4146

47+
Type `git-se -h` and `git-se [subcommand] -h` to get more information.
48+
4249
## Caution
4350

4451
- `git add -A` is automatically executed when encrypting, so make sure that `.gitignore` is handled properly.
@@ -56,3 +63,8 @@ graph TD;
5663

5764
- If zstd compression has the opposite effect, skip compression.
5865
- Decrypt all files with extension `.enc`, `.zst.enc`.
66+
67+
## TODO
68+
69+
- [ ] zstd effect checking
70+
- [x] partial decrypt

src/cli.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ git-se set key 123456 # set password as `123456`
1111
git-se add file.txt # mark `file.txt` as need-to-be-crypted
1212
git-se e # encrypt current repo with all marked files
1313
git-se d # decrypt current repo
14+
git-se d 'src/*' # decrypt all encrypted files in `src` folder
1415
"#)]
1516
#[clap(args_conflicts_with_subcommands = true)]
1617
pub struct Cli {
@@ -40,9 +41,15 @@ pub enum SubCommand {
4041
Encrypt,
4142
/// Decrypt all files with crypt attr and `.enc` extension.
4243
#[clap(alias("d"))]
43-
Decrypt,
44+
Decrypt {
45+
/// The files or folders to be decrypted, use wildcard matches.
46+
path: Option<String>,
47+
},
4448
/// Mark files or folders as need-to-be-crypted.
45-
Add { path: Vec<String> },
49+
Add {
50+
#[clap(required = true)]
51+
paths: Vec<String>,
52+
},
4653
/// Set key or other config items.
4754
Set { field: SetField, value: String },
4855
}

src/crypt.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use aes_gcm_siv::{
1010
};
1111
use anyhow::{anyhow, Context, Result};
1212
use colored::Colorize;
13-
use die_exit::die;
13+
use die_exit::{die, DieWith};
14+
use glob::Pattern;
1415
use log::{debug, info};
1516
use tap::Tap;
1617

@@ -203,13 +204,40 @@ pub async fn encrypt_repo(repo: &'static Repo) -> anyhow::Result<()> {
203204
Ok(())
204205
}
205206

206-
pub async fn decrypt_repo(repo: &'static Repo) -> anyhow::Result<()> {
207+
pub async fn decrypt_repo(repo: &'static Repo, path: &Option<String>) -> anyhow::Result<()> {
207208
assert!(!repo.get_key().is_empty(), "Key must not be empty");
208209
let dot_pattern = String::from("*.") + ENCRYPTED_EXTENSION;
210+
211+
// partial decrypt filter
212+
let pattern: Option<Pattern> = path.as_ref().map(|x| {
213+
glob::Pattern::new(
214+
repo.path
215+
.join(Path::new(x.as_str()).patch())
216+
.to_string_lossy()
217+
.as_ref()
218+
.tap(|x| println!("Decrypting with pattern: {}", x.green())),
219+
)
220+
.die_with(|e| format!("Invalid pattern: {e}"))
221+
});
222+
let decrypt_path_filter: Box<dyn Fn(&PathBuf) -> bool> = if path.is_some() {
223+
Box::new(|path: &PathBuf| -> bool {
224+
// SAFETY: pattern is always Some in this case
225+
unsafe {
226+
pattern
227+
.as_ref()
228+
.unwrap_unchecked()
229+
.matches(path.to_string_lossy().as_ref())
230+
}
231+
})
232+
} else {
233+
Box::new(|_: &PathBuf| -> bool { true })
234+
};
235+
209236
let decrypt_futures = repo
210237
.ls_files_absolute_with_given_patterns(&[dot_pattern.as_str()])?
211238
.into_iter()
212239
.filter(|x| x.is_file())
240+
.filter(decrypt_path_filter)
213241
.map(|f| decrypt_file(f, repo))
214242
.map(tokio::task::spawn)
215243
.collect::<Vec<_>>();

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ pub async fn run(cli: &Cli) -> Result<()> {
2222
let repo = Box::leak(Box::new(repo));
2323
match &cli.command {
2424
SubCommand::Encrypt => encrypt_repo(repo).await?,
25-
SubCommand::Decrypt => decrypt_repo(repo).await?,
26-
SubCommand::Add { path } => repo
25+
SubCommand::Decrypt { path } => decrypt_repo(repo, path).await?,
26+
SubCommand::Add { paths } => repo
2727
.conf
28-
.add_to_crypt_list(&path.iter().map(|s| s.as_ref()).collect::<Vec<_>>())?,
28+
.add_to_crypt_list(&paths.iter().map(|s| s.as_ref()).collect::<Vec<_>>())?,
2929
SubCommand::Set { field, value } => field.set(repo, value)?,
3030
}
3131
Ok(())

tests/intergration_test.rs

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ async fn test() -> anyhow::Result<()> {
5353

5454
// Add file
5555
run!(SubCommand::Add {
56-
path: ["t1.txt", "t2.txt", "dir"]
56+
paths: ["t1.txt", "t2.txt", "dir"]
5757
.map(ToString::to_string)
5858
.to_vec(),
5959
});
@@ -73,7 +73,7 @@ async fn test() -> anyhow::Result<()> {
7373
assert!(!temp_dir.join("dir/t4.txt").exists());
7474

7575
// Decrypt
76-
run!(SubCommand::Decrypt);
76+
run!(SubCommand::Decrypt { path: None });
7777
println!("{}", "After Decrypt".green());
7878

7979
// Test
@@ -134,7 +134,7 @@ async fn test_reencrypt() -> anyhow::Result<()> {
134134

135135
// Add file
136136
run!(SubCommand::Add {
137-
path: ["t1.txt", "dir"].map(ToString::to_string).to_vec(),
137+
paths: ["t1.txt", "dir"].map(ToString::to_string).to_vec(),
138138
});
139139

140140
// Encrypt multiple times
@@ -154,7 +154,7 @@ async fn test_reencrypt() -> anyhow::Result<()> {
154154
assert!(!temp_dir.join("dir/t4.txt").exists());
155155

156156
// Decrypt
157-
run!(SubCommand::Decrypt);
157+
run!(SubCommand::Decrypt { path: None });
158158
println!("{}", "After Decrypt".green());
159159

160160
// Test
@@ -214,13 +214,13 @@ async fn test_many_files() -> anyhow::Result<()> {
214214

215215
// Add file
216216
run!(SubCommand::Add {
217-
path: vec!["dir".into()]
217+
paths: vec!["dir".into()]
218218
});
219219

220220
// Encrypt
221221
run!(SubCommand::Encrypt);
222222
// Decrypt
223-
run!(SubCommand::Decrypt);
223+
run!(SubCommand::Decrypt { path: None });
224224

225225
// Test
226226
for _ in 1..10 {
@@ -231,3 +231,71 @@ async fn test_many_files() -> anyhow::Result<()> {
231231

232232
Ok(())
233233
}
234+
235+
#[tokio::test]
236+
async fn test_partial_decrypt() -> anyhow::Result<()> {
237+
let _ = env_logger::try_init();
238+
let temp_dir = &TempDir::default();
239+
let exec = |cmd: &str| -> std::io::Result<Output> {
240+
let mut temp = cmd.split_whitespace();
241+
let mut command = Command::new(temp.next().unwrap());
242+
command.args(temp).current_dir(temp_dir).output()
243+
};
244+
macro_rules! run {
245+
($cmd:expr) => {
246+
run(&Cli {
247+
command: $cmd,
248+
repo: temp_dir.to_path_buf(),
249+
})
250+
.await?;
251+
};
252+
}
253+
254+
exec("git init")?;
255+
std::fs::create_dir(temp_dir.join("dir"))?;
256+
std::fs::write(temp_dir.join("t1.txt"), "Hello, world!")?;
257+
std::fs::write(temp_dir.join("dir/t4.txt"), "dir test")?;
258+
259+
// Set key
260+
run!(SubCommand::Set {
261+
field: SetField::key,
262+
value: "123".to_owned(),
263+
});
264+
265+
// Add file
266+
run!(SubCommand::Add {
267+
paths: ["t1.txt", "dir"].map(ToString::to_string).to_vec(),
268+
});
269+
270+
// Encrypt
271+
run!(SubCommand::Encrypt);
272+
273+
// Partial decrypt
274+
run!(SubCommand::Decrypt {
275+
path: Some("dir/**".into())
276+
});
277+
278+
// Test
279+
for entry in temp_dir.read_dir()? {
280+
println!("{:?}", entry?);
281+
}
282+
assert!(temp_dir.join("t1.txt.enc").exists());
283+
assert!(temp_dir.join("dir/t4.txt").exists());
284+
285+
// Reencrypt
286+
run!(SubCommand::Encrypt);
287+
288+
// Partial decrypt
289+
run!(SubCommand::Decrypt {
290+
path: Some("t1.txt.enc".into())
291+
});
292+
293+
// Test
294+
for entry in temp_dir.read_dir()? {
295+
println!("{:?}", entry?);
296+
}
297+
assert!(temp_dir.join("t1.txt").exists());
298+
assert!(temp_dir.join("dir/t4.txt.enc").exists());
299+
300+
Ok(())
301+
}

0 commit comments

Comments
 (0)