git_revise/git/diff.rs
1// use std::cell::RefCell;
2
3// pub trait GitDiff {
4// fn git_diff(
5// repo: &git2::Repository,
6// exclude_files: &[String],
7// ) -> crate::error::ReviseResult<String> {
8// let mut opts = git2::DiffOptions::new();
9
10// // 设置 diff 选项以只包含暂存区的更改
11// opts.include_untracked(false)
12// .show_untracked_content(false)
13// .include_ignored(false);
14
15// // 获取 HEAD 指向的 tree
16// let head = repo.head()?.peel_to_tree()?;
17
18// // 获取暂存区的 index
19// let index = repo.index()?;
20
21// // 比较 HEAD 和暂存区的 index
22// let diff = repo.diff_tree_to_index(
23// Some(&head),
24// Some(&index),
25// Some(&mut opts),
26// )?;
27
28// let content = RefCell::new(String::new());
29// let current_file_excluded = RefCell::new(false);
30
31// diff.foreach(
32// &mut |delta, _| {
33// if let Some(path) = delta.new_file().path() {
34// if let Some(file_name) = path.file_name() {
35// if let Some(file_name_str) = file_name.to_str() {
36// *current_file_excluded.borrow_mut() =
37// exclude_files
38// .contains(&file_name_str.to_string()); }
39// }
40// }
41// true // 总是返回 true 以继续处理下一个文件
42// },
43// None,
44// None,
45// Some(&mut |_, _, line| {
46// if !*current_file_excluded.borrow() {
47// let prefix = match line.origin() {
48// '+' => "+",
49// '-' => "-",
50// _ => " ",
51// };
52// content.borrow_mut().push_str(prefix);
53// content
54// .borrow_mut()
55// .push_str(&String::from_utf8_lossy(line.content()));
56// }
57// true // 总是返回 true 以继续处理下一行
58// }),
59// )?;
60
61// Ok(content.into_inner())
62// }
63// }
64
65// // pub trait GitDiff {
66// // fn git_diff(repo: &git2::Repository) ->
67// // crate::error::ReviseResult<String> { let mut opts =
68// // git2::DiffOptions::new(); // 创建一个新的DiffOptions实例
69
70// // let head = repo.head()?.peel_to_tree()?; // 获取HEAD指向的tree
71// // let diff = repo.diff_tree_to_workdir(Some(&head), Some(&mut
72// opts))?; // // 获取当前工作目录与HEAD的差异
73
74// // let mut content = String::new(); // 预分配内存
75
76// // diff.foreach(
77// // &mut |_, _| true,
78// // None,
79// // None,
80// // Some(&mut |_, _, line| {
81// // let prefix = match line.origin() {
82// // '+' => "+",
83// // '-' => "-",
84// // _ => " ",
85// // };
86// // content.push_str(prefix);
87// //
88// content.push_str(&String::from_utf8_lossy(line.content())); //
89// content.push('\n'); // true
90// // }),
91// // )?;
92// // Ok(content)
93// // }
94// // }
95
96// #[cfg(test)]
97// mod tests {
98// use super::*;
99
100// #[test]
101// fn test_git_diff() {
102// struct GItDiffImpl;
103// impl GitDiff for GItDiffImpl {}
104
105// let _ =
106// GItDiffImpl::git_diff(&git2::Repository::open(".").unwrap(), &[
107// "README.md".to_string(),
108// ])
109// .unwrap();
110// }
111// }
112
113use std::process::Command;
114
115use crate::error::ReviseResult;
116
117pub trait GitDiff {
118 fn git_diff(exclude_files: &[String]) -> ReviseResult<String> {
119 let mut args = vec!["diff", "--staged"];
120
121 let exclude_args: Vec<String> = exclude_files
122 .iter()
123 .map(|file| format!(":!{file}"))
124 .collect();
125 args.extend(exclude_args.iter().map(std::string::String::as_str));
126
127 let mut binding = Command::new("git");
128 let command = binding.args(&args);
129 let output = command.output()?;
130
131 if !output.status.success() {
132 return Err(anyhow::anyhow!(
133 "Git diff failed: {}",
134 String::from_utf8_lossy(&output.stderr)
135 ));
136 }
137 let diff = String::from_utf8(output.stdout)?;
138 Ok(diff)
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 #[ignore]
148 fn test_git_diff() {
149 struct GitDiffImpl;
150 impl GitDiff for GitDiffImpl {}
151
152 let diff =
153 GitDiffImpl::git_diff(&["CHANGELOG.md".to_string()]).unwrap();
154 println!("Git diff: {diff}");
155 }
156}