ralph_workflow/git_helpers/repo/
diff.rs1use std::io;
2use std::path::Path;
3
4use crate::git_helpers::git2_to_io_error;
5use crate::workspace::Workspace;
6
7pub fn git_diff() -> io::Result<String> {
19 let repo = git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?;
20 git_diff_impl(&repo)
21}
22
23pub fn git_diff_in_repo(repo_root: &Path) -> io::Result<String> {
31 let repo = git2::Repository::discover(repo_root).map_err(|e| git2_to_io_error(&e))?;
32 git_diff_impl(&repo)
33}
34
35pub fn git_diff_from(start_oid: &str) -> io::Result<String> {
45 let repo = git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?;
46
47 let oid = git2::Oid::from_str(start_oid).map_err(|_| {
49 io::Error::new(
50 io::ErrorKind::InvalidInput,
51 format!("Invalid commit OID: {start_oid}"),
52 )
53 })?;
54
55 git_diff_from_oid(&repo, oid)
56}
57
58pub fn get_git_diff_from_start() -> io::Result<String> {
68 use crate::git_helpers::start_commit::{load_start_point, save_start_commit, StartPoint};
69
70 save_start_commit()?;
73
74 let repo = git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?;
75
76 match load_start_point()? {
77 StartPoint::Commit(oid) => git_diff_from(&oid.to_string()),
78 StartPoint::EmptyRepo => git_diff_from_empty_tree(&repo),
79 }
80}
81
82pub fn get_git_diff_from_start_with_workspace(workspace: &dyn Workspace) -> io::Result<String> {
93 use crate::git_helpers::start_commit::{
94 load_start_point_with_workspace, save_start_commit_with_workspace, StartPoint,
95 };
96
97 if !workspace.exists(std::path::Path::new(".git")) {
101 return Err(io::Error::new(
102 io::ErrorKind::NotFound,
103 "Workspace has no on-disk git repository",
104 ));
105 }
106
107 let repo = git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?;
108
109 save_start_commit_with_workspace(workspace, &repo)?;
112
113 match load_start_point_with_workspace(workspace, &repo)? {
114 StartPoint::Commit(oid) => git_diff_from_oid(&repo, oid),
115 StartPoint::EmptyRepo => git_diff_from_empty_tree(&repo),
116 }
117}
118
119pub fn get_git_diff_for_review_with_workspace(
134 workspace: &dyn Workspace,
135) -> io::Result<(String, String)> {
136 use crate::git_helpers::review_baseline::{
137 load_review_baseline_with_workspace, ReviewBaseline,
138 };
139 use crate::git_helpers::start_commit::{
140 load_start_point_with_workspace, save_start_commit_with_workspace, StartPoint,
141 };
142
143 let repo = git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?;
147
148 let baseline = load_review_baseline_with_workspace(workspace).unwrap_or(ReviewBaseline::NotSet);
149 match baseline {
150 ReviewBaseline::Commit(oid) => {
151 let diff = git_diff_from_oid(&repo, oid)?;
152 Ok((diff, oid.to_string()))
153 }
154 ReviewBaseline::NotSet => {
155 save_start_commit_with_workspace(workspace, &repo)?;
157
158 match load_start_point_with_workspace(workspace, &repo)? {
159 StartPoint::Commit(oid) => {
160 let diff = git_diff_from_oid(&repo, oid)?;
161 Ok((diff, oid.to_string()))
162 }
163 StartPoint::EmptyRepo => Ok((git_diff_from_empty_tree(&repo)?, String::new())),
164 }
165 }
166 }
167}
168
169fn git_diff_impl(repo: &git2::Repository) -> io::Result<String> {
171 let head_tree = match repo.head() {
173 Ok(head) => Some(head.peel_to_tree().map_err(|e| git2_to_io_error(&e))?),
174 Err(ref e) if e.code() == git2::ErrorCode::UnbornBranch => {
175 let mut diff_opts = git2::DiffOptions::new();
177 diff_opts.include_untracked(true);
178 diff_opts.recurse_untracked_dirs(true);
179
180 let diff = repo
181 .diff_tree_to_workdir_with_index(None, Some(&mut diff_opts))
182 .map_err(|e| git2_to_io_error(&e))?;
183
184 let mut result = Vec::new();
185 diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
186 result.extend_from_slice(line.content());
187 true
188 })
189 .map_err(|e| git2_to_io_error(&e))?;
190
191 return Ok(String::from_utf8_lossy(&result).to_string());
192 }
193 Err(e) => return Err(git2_to_io_error(&e)),
194 };
195
196 let mut diff_opts = git2::DiffOptions::new();
198 diff_opts.include_untracked(true);
199 diff_opts.recurse_untracked_dirs(true);
200
201 let diff = repo
202 .diff_tree_to_workdir_with_index(head_tree.as_ref(), Some(&mut diff_opts))
203 .map_err(|e| git2_to_io_error(&e))?;
204
205 let mut result = Vec::new();
206 diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
207 result.extend_from_slice(line.content());
208 true
209 })
210 .map_err(|e| git2_to_io_error(&e))?;
211
212 Ok(String::from_utf8_lossy(&result).to_string())
213}
214
215fn git_diff_from_oid(repo: &git2::Repository, oid: git2::Oid) -> io::Result<String> {
216 let start_commit = repo.find_commit(oid).map_err(|e| git2_to_io_error(&e))?;
217 let start_tree = start_commit.tree().map_err(|e| git2_to_io_error(&e))?;
218
219 let mut diff_opts = git2::DiffOptions::new();
220 diff_opts.include_untracked(true);
221 diff_opts.recurse_untracked_dirs(true);
222
223 let diff = repo
224 .diff_tree_to_workdir_with_index(Some(&start_tree), Some(&mut diff_opts))
225 .map_err(|e| git2_to_io_error(&e))?;
226
227 let mut result = Vec::new();
228 diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
229 result.extend_from_slice(line.content());
230 true
231 })
232 .map_err(|e| git2_to_io_error(&e))?;
233
234 Ok(String::from_utf8_lossy(&result).to_string())
235}
236
237fn git_diff_from_empty_tree(repo: &git2::Repository) -> io::Result<String> {
239 let mut diff_opts = git2::DiffOptions::new();
240 diff_opts.include_untracked(true);
241 diff_opts.recurse_untracked_dirs(true);
242
243 let diff = repo
244 .diff_tree_to_workdir_with_index(None, Some(&mut diff_opts))
245 .map_err(|e| git2_to_io_error(&e))?;
246
247 let mut result = Vec::new();
248 diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
249 result.extend_from_slice(line.content());
250 true
251 })
252 .map_err(|e| git2_to_io_error(&e))?;
253
254 Ok(String::from_utf8_lossy(&result).to_string())
255}