ralph_workflow/git_helpers/repo/
diff.rs1use std::io;
2
3use crate::git_helpers::git2_to_io_error;
4use crate::workspace::Workspace;
5
6pub fn git_diff() -> io::Result<String> {
14 let repo = git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?;
15 git_diff_impl(&repo)
16}
17
18pub fn git_diff_from(start_oid: &str) -> io::Result<String> {
24 let repo = git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?;
25
26 let oid = git2::Oid::from_str(start_oid).map_err(|_| {
28 io::Error::new(
29 io::ErrorKind::InvalidInput,
30 format!("Invalid commit OID: {start_oid}"),
31 )
32 })?;
33
34 git_diff_from_oid(&repo, oid)
35}
36
37pub fn get_git_diff_from_start() -> io::Result<String> {
43 use crate::git_helpers::start_commit::{load_start_point, save_start_commit, StartPoint};
44
45 save_start_commit()?;
48
49 let repo = git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?;
50
51 match load_start_point()? {
52 StartPoint::Commit(oid) => git_diff_from(&oid.to_string()),
53 StartPoint::EmptyRepo => git_diff_from_empty_tree(&repo),
54 }
55}
56
57pub fn get_git_diff_from_start_with_workspace(workspace: &dyn Workspace) -> io::Result<String> {
64 use crate::git_helpers::start_commit::{
65 load_start_point_with_workspace, save_start_commit_with_workspace, StartPoint,
66 };
67
68 let repo_root =
77 crate::git_helpers::get_repo_root().unwrap_or_else(|_| std::path::PathBuf::from("."));
78 let repo = if workspace.exists(std::path::Path::new(".git")) {
79 git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?
80 } else {
81 git2::Repository::discover(&repo_root).map_err(|e| git2_to_io_error(&e))?
82 };
83
84 if !workspace.exists(std::path::Path::new(".git")) {
85 return Err(io::Error::new(
88 io::ErrorKind::NotFound,
89 "Workspace has no on-disk git repository",
90 ));
91 }
92
93 save_start_commit_with_workspace(workspace, &repo)?;
96
97 match load_start_point_with_workspace(workspace, &repo)? {
98 StartPoint::Commit(oid) => git_diff_from_oid(&repo, oid),
99 StartPoint::EmptyRepo => git_diff_from_empty_tree(&repo),
100 }
101}
102
103pub fn get_git_diff_for_review_with_workspace(
114 workspace: &dyn Workspace,
115) -> io::Result<(String, String)> {
116 use crate::git_helpers::review_baseline::{
117 load_review_baseline_with_workspace, ReviewBaseline,
118 };
119 use crate::git_helpers::start_commit::{
120 load_start_point_with_workspace, save_start_commit_with_workspace, StartPoint,
121 };
122
123 let repo = git2::Repository::discover(".").map_err(|e| git2_to_io_error(&e))?;
125
126 let baseline = load_review_baseline_with_workspace(workspace).unwrap_or(ReviewBaseline::NotSet);
127 match baseline {
128 ReviewBaseline::Commit(oid) => {
129 let diff = git_diff_from_oid(&repo, oid)?;
130 Ok((diff, oid.to_string()))
131 }
132 ReviewBaseline::NotSet => {
133 save_start_commit_with_workspace(workspace, &repo)?;
135
136 match load_start_point_with_workspace(workspace, &repo)? {
137 StartPoint::Commit(oid) => {
138 let diff = git_diff_from_oid(&repo, oid)?;
139 Ok((diff, oid.to_string()))
140 }
141 StartPoint::EmptyRepo => Ok((git_diff_from_empty_tree(&repo)?, String::new())),
142 }
143 }
144 }
145}
146
147fn git_diff_impl(repo: &git2::Repository) -> io::Result<String> {
149 let head_tree = match repo.head() {
151 Ok(head) => Some(head.peel_to_tree().map_err(|e| git2_to_io_error(&e))?),
152 Err(ref e) if e.code() == git2::ErrorCode::UnbornBranch => {
153 let mut diff_opts = git2::DiffOptions::new();
155 diff_opts.include_untracked(true);
156 diff_opts.recurse_untracked_dirs(true);
157
158 let diff = repo
159 .diff_tree_to_workdir_with_index(None, Some(&mut diff_opts))
160 .map_err(|e| git2_to_io_error(&e))?;
161
162 let mut result = Vec::new();
163 diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
164 result.extend_from_slice(line.content());
165 true
166 })
167 .map_err(|e| git2_to_io_error(&e))?;
168
169 return Ok(String::from_utf8_lossy(&result).to_string());
170 }
171 Err(e) => return Err(git2_to_io_error(&e)),
172 };
173
174 let mut diff_opts = git2::DiffOptions::new();
176 diff_opts.include_untracked(true);
177 diff_opts.recurse_untracked_dirs(true);
178
179 let diff = repo
180 .diff_tree_to_workdir_with_index(head_tree.as_ref(), Some(&mut diff_opts))
181 .map_err(|e| git2_to_io_error(&e))?;
182
183 let mut result = Vec::new();
184 diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
185 result.extend_from_slice(line.content());
186 true
187 })
188 .map_err(|e| git2_to_io_error(&e))?;
189
190 Ok(String::from_utf8_lossy(&result).to_string())
191}
192
193fn git_diff_from_oid(repo: &git2::Repository, oid: git2::Oid) -> io::Result<String> {
194 let start_commit = repo.find_commit(oid).map_err(|e| git2_to_io_error(&e))?;
195 let start_tree = start_commit.tree().map_err(|e| git2_to_io_error(&e))?;
196
197 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(Some(&start_tree), 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_empty_tree(repo: &git2::Repository) -> io::Result<String> {
217 let mut diff_opts = git2::DiffOptions::new();
218 diff_opts.include_untracked(true);
219 diff_opts.recurse_untracked_dirs(true);
220
221 let diff = repo
222 .diff_tree_to_workdir_with_index(None, Some(&mut diff_opts))
223 .map_err(|e| git2_to_io_error(&e))?;
224
225 let mut result = Vec::new();
226 diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
227 result.extend_from_slice(line.content());
228 true
229 })
230 .map_err(|e| git2_to_io_error(&e))?;
231
232 Ok(String::from_utf8_lossy(&result).to_string())
233}