gnostr_asyncgit/sync/
hunks.rs1use git2::{ApplyLocation, ApplyOptions, Diff};
2use scopetime::scope_time;
3
4use super::{
5 RepoPath,
6 diff::{DiffOptions, HunkHeader, get_diff_raw},
7};
8use crate::{
9 error::{Error, Result},
10 hash,
11 sync::repository::repo,
12};
13
14pub fn stage_hunk(
16 repo_path: &RepoPath,
17 file_path: &str,
18 hunk_hash: u64,
19 options: Option<DiffOptions>,
20) -> Result<()> {
21 scope_time!("stage_hunk");
22
23 let repo = repo(repo_path)?;
24
25 let diff = get_diff_raw(&repo, file_path, false, false, options)?;
26
27 let mut opt = ApplyOptions::new();
28 opt.hunk_callback(|hunk| {
29 hunk.map_or(false, |hunk| {
30 let header = HunkHeader::from(hunk);
31 hash(&header) == hunk_hash
32 })
33 });
34
35 repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?;
36
37 Ok(())
38}
39
40pub fn reset_hunk(
42 repo_path: &RepoPath,
43 file_path: &str,
44 hunk_hash: u64,
45 options: Option<DiffOptions>,
46) -> Result<()> {
47 scope_time!("reset_hunk");
48
49 let repo = repo(repo_path)?;
50
51 let diff = get_diff_raw(&repo, file_path, false, false, options)?;
52
53 let hunk_index = find_hunk_index(&diff, hunk_hash);
54 if let Some(hunk_index) = hunk_index {
55 let mut hunk_idx = 0;
56 let mut opt = ApplyOptions::new();
57 opt.hunk_callback(|_hunk| {
58 let res = hunk_idx == hunk_index;
59 hunk_idx += 1;
60 res
61 });
62
63 let diff = get_diff_raw(&repo, file_path, false, true, None)?;
64
65 repo.apply(&diff, ApplyLocation::WorkDir, Some(&mut opt))?;
66
67 Ok(())
68 } else {
69 Err(Error::Generic("hunk not found".to_string()))
70 }
71}
72
73fn find_hunk_index(diff: &Diff, hunk_hash: u64) -> Option<usize> {
74 let mut result = None;
75
76 let mut hunk_count = 0;
77
78 let foreach_result = diff.foreach(
79 &mut |_, _| true,
80 None,
81 Some(&mut |_, hunk| {
82 let header = HunkHeader::from(hunk);
83 if hash(&header) == hunk_hash {
84 result = Some(hunk_count);
85 }
86 hunk_count += 1;
87 true
88 }),
89 None,
90 );
91
92 if foreach_result.is_ok() { result } else { None }
93}
94
95pub fn unstage_hunk(
97 repo_path: &RepoPath,
98 file_path: &str,
99 hunk_hash: u64,
100 options: Option<DiffOptions>,
101) -> Result<bool> {
102 scope_time!("revert_hunk");
103
104 let repo = repo(repo_path)?;
105
106 let diff = get_diff_raw(&repo, file_path, true, false, options)?;
107 let diff_count_positive = diff.deltas().len();
108
109 let hunk_index = find_hunk_index(&diff, hunk_hash);
110 let hunk_index = hunk_index.map_or_else(
111 || Err(Error::Generic("hunk not found".to_string())),
112 Ok,
113 )?;
114
115 let diff = get_diff_raw(&repo, file_path, true, true, options)?;
116
117 if diff.deltas().len() != diff_count_positive {
118 return Err(Error::Generic(format!(
119 "hunk error: {}!={}",
120 diff.deltas().len(),
121 diff_count_positive
122 )));
123 }
124
125 let mut count = 0;
126 {
127 let mut hunk_idx = 0;
128 let mut opt = ApplyOptions::new();
129 opt.hunk_callback(|_hunk| {
130 let res = if hunk_idx == hunk_index {
131 count += 1;
132 true
133 } else {
134 false
135 };
136
137 hunk_idx += 1;
138
139 res
140 });
141
142 repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?;
143 }
144
145 Ok(count == 1)
146}
147
148#[cfg(test)]
149mod tests {
150 use std::{
151 fs::{self, File},
152 io::Write,
153 path::Path,
154 };
155
156 use super::*;
157 use crate::{
158 error::Result,
159 sync::{diff::get_diff, tests::repo_init_empty},
160 };
161
162 #[test]
163 fn reset_untracked_file_which_will_not_find_hunk() -> Result<()> {
164 let file_path = Path::new("foo/foo.txt");
165 let (_td, repo) = repo_init_empty()?;
166 let root = repo.path().parent().unwrap();
167 let repo_path: &RepoPath =
168 &root.as_os_str().to_str().unwrap().into();
169 let sub_path = root.join("foo/");
170
171 fs::create_dir_all(&sub_path)?;
172 File::create(root.join(file_path))?.write_all(b"test")?;
173
174 let sub_path: &RepoPath = &sub_path.to_str().unwrap().into();
175 let diff = get_diff(
176 sub_path,
177 file_path.to_str().unwrap(),
178 false,
179 None,
180 )?;
181
182 assert!(
183 reset_hunk(
184 repo_path,
185 file_path.to_str().unwrap(),
186 diff.hunks[0].header_hash,
187 None,
188 )
189 .is_err()
190 );
191
192 Ok(())
193 }
194}