gnostr_asyncgit/sync/
hunks.rs1use git2::{ApplyLocation, ApplyOptions, Diff};
2use scopetime::scope_time;
3
4use super::{
5 diff::{get_diff_raw, DiffOptions, HunkHeader},
6 RepoPath,
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() {
93 result
94 } else {
95 None
96 }
97}
98
99pub fn unstage_hunk(
101 repo_path: &RepoPath,
102 file_path: &str,
103 hunk_hash: u64,
104 options: Option<DiffOptions>,
105) -> Result<bool> {
106 scope_time!("revert_hunk");
107
108 let repo = repo(repo_path)?;
109
110 let diff = get_diff_raw(&repo, file_path, true, false, options)?;
111 let diff_count_positive = diff.deltas().len();
112
113 let hunk_index = find_hunk_index(&diff, hunk_hash);
114 let hunk_index =
115 hunk_index.map_or_else(|| Err(Error::Generic("hunk not found".to_string())), Ok)?;
116
117 let diff = get_diff_raw(&repo, file_path, true, true, options)?;
118
119 if diff.deltas().len() != diff_count_positive {
120 return Err(Error::Generic(format!(
121 "hunk error: {}!={}",
122 diff.deltas().len(),
123 diff_count_positive
124 )));
125 }
126
127 let mut count = 0;
128 {
129 let mut hunk_idx = 0;
130 let mut opt = ApplyOptions::new();
131 opt.hunk_callback(|_hunk| {
132 let res = if hunk_idx == hunk_index {
133 count += 1;
134 true
135 } else {
136 false
137 };
138
139 hunk_idx += 1;
140
141 res
142 });
143
144 repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?;
145 }
146
147 Ok(count == 1)
148}
149
150#[cfg(test)]
151mod tests {
152 use std::{
153 fs::{self, File},
154 io::Write,
155 path::Path,
156 };
157
158 use super::*;
159 use crate::{
160 error::Result,
161 sync::{diff::get_diff, tests::repo_init_empty},
162 };
163
164 #[test]
165 fn reset_untracked_file_which_will_not_find_hunk() -> Result<()> {
166 let file_path = Path::new("foo/foo.txt");
167 let (_td, repo) = repo_init_empty()?;
168 let root = repo.path().parent().unwrap();
169 let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
170 let sub_path = root.join("foo/");
171
172 fs::create_dir_all(&sub_path)?;
173 File::create(root.join(file_path))?.write_all(b"test")?;
174
175 let sub_path: &RepoPath = &sub_path.to_str().unwrap().into();
176 let diff = get_diff(sub_path, file_path.to_str().unwrap(), false, None)?;
177
178 assert!(reset_hunk(
179 repo_path,
180 file_path.to_str().unwrap(),
181 diff.hunks[0].header_hash,
182 None,
183 )
184 .is_err());
185
186 Ok(())
187 }
188}