1use super::apply_selection;
2use crate::{
3 error::{Error, Result},
4 sync::{
5 diff::DiffLinePosition, patches::get_file_diff_patch,
6 patches::patch_get_hunklines, repository::repo, RepoPath,
7 },
8};
9use easy_cast::Conv;
10use scopetime::scope_time;
11use std::path::Path;
12
13pub fn stage_lines(
15 repo_path: &RepoPath,
16 file_path: &str,
17 is_stage: bool,
18 lines: &[DiffLinePosition],
19) -> Result<()> {
20 scope_time!("stage_lines");
21
22 if lines.is_empty() {
23 return Ok(());
24 }
25
26 let repo = repo(repo_path)?;
27 let mut index = repo.index()?;
30 index.read(true)?;
31 let mut idx =
32 index.get_path(Path::new(file_path), 0).ok_or_else(|| {
33 Error::Generic(String::from(
34 "only non new files supported",
35 ))
36 })?;
37 let blob = repo.find_blob(idx.id)?;
38 let indexed_content = String::from_utf8(blob.content().into())?;
39
40 let new_content = {
41 let patch =
42 get_file_diff_patch(&repo, file_path, is_stage, false)?;
43 let hunks = patch_get_hunklines(&patch)?;
44
45 let old_lines = indexed_content.lines().collect::<Vec<_>>();
46
47 apply_selection(lines, &hunks, &old_lines, is_stage, false)?
48 };
49
50 let blob_id = repo.blob(new_content.as_bytes())?;
51
52 idx.id = blob_id;
53 idx.file_size = u32::try_conv(new_content.len())?;
54 index.add(&idx)?;
55
56 index.write()?;
57 index.read(true)?;
58
59 Ok(())
60}
61
62#[cfg(test)]
63mod test {
64 use super::*;
65 use crate::sync::{
66 diff::get_diff,
67 tests::{get_statuses, repo_init, write_commit_file},
68 utils::{repo_write_file, stage_add_file},
69 };
70
71 #[test]
72 fn test_stage() {
73 static FILE_1: &str = r"0
74";
75
76 static FILE_2: &str = r"0
771
782
793
80";
81
82 let (path, repo) = repo_init().unwrap();
83 let path: &RepoPath = &path.path().to_str().unwrap().into();
84
85 write_commit_file(&repo, "test.txt", FILE_1, "c1");
86
87 repo_write_file(&repo, "test.txt", FILE_2).unwrap();
88
89 stage_lines(
90 path,
91 "test.txt",
92 false,
93 &[DiffLinePosition {
94 old_lineno: None,
95 new_lineno: Some(2),
96 }],
97 )
98 .unwrap();
99
100 let diff = get_diff(path, "test.txt", true, None).unwrap();
101
102 assert_eq!(diff.lines, 3);
103 assert_eq!(&*diff.hunks[0].lines[0].content, "@@ -1 +1,2 @@");
104 }
105
106 #[test]
107 fn test_panic_stage_no_newline() {
108 static FILE_1: &str = r"a = 1
109b = 2";
110
111 static FILE_2: &str = r"a = 2
112b = 3
113c = 4";
114
115 let (path, repo) = repo_init().unwrap();
116 let path: &RepoPath = &path.path().to_str().unwrap().into();
117
118 write_commit_file(&repo, "test.txt", FILE_1, "c1");
119
120 repo_write_file(&repo, "test.txt", FILE_2).unwrap();
121
122 stage_lines(
123 path,
124 "test.txt",
125 false,
126 &[
127 DiffLinePosition {
128 old_lineno: Some(1),
129 new_lineno: None,
130 },
131 DiffLinePosition {
132 old_lineno: Some(2),
133 new_lineno: None,
134 },
135 ],
136 )
137 .unwrap();
138
139 let diff = get_diff(path, "test.txt", true, None).unwrap();
140
141 assert_eq!(diff.lines, 5);
142 assert_eq!(&*diff.hunks[0].lines[0].content, "@@ -1,2 +1 @@");
143 }
144
145 #[test]
146 fn test_unstage() {
147 static FILE_1: &str = r"0
148";
149
150 static FILE_2: &str = r"0
1511
1522
1533
154";
155
156 let (path, repo) = repo_init().unwrap();
157 let path: &RepoPath = &path.path().to_str().unwrap().into();
158
159 write_commit_file(&repo, "test.txt", FILE_1, "c1");
160
161 repo_write_file(&repo, "test.txt", FILE_2).unwrap();
162
163 assert_eq!(get_statuses(path), (1, 0));
164
165 stage_add_file(path, Path::new("test.txt")).unwrap();
166
167 assert_eq!(get_statuses(path), (0, 1));
168
169 let diff_before =
170 get_diff(path, "test.txt", true, None).unwrap();
171
172 assert_eq!(diff_before.lines, 5);
173
174 stage_lines(
175 path,
176 "test.txt",
177 true,
178 &[DiffLinePosition {
179 old_lineno: None,
180 new_lineno: Some(2),
181 }],
182 )
183 .unwrap();
184
185 assert_eq!(get_statuses(path), (1, 1));
186
187 let diff = get_diff(path, "test.txt", true, None).unwrap();
188
189 assert_eq!(diff.lines, 4);
190 }
191}