1use crate::cherry_pick::{CherryPickError, cherry_pick};
7use crate::commit::CommitsTable;
8use crate::history::log;
9use crate::object_store::GitObjectStore;
10
11#[derive(Debug, thiserror::Error)]
13pub enum RebaseError {
14 #[error("Cherry-pick failed: {0}")]
15 CherryPick(#[from] CherryPickError),
16
17 #[error("Commit not found: {0}")]
18 CommitNotFound(String),
19
20 #[error("Nothing to rebase (start equals onto)")]
21 NothingToRebase,
22}
23
24pub struct RebaseResult {
26 pub new_head: String,
28 pub replayed: usize,
30}
31
32pub fn rebase(
40 obj_store: &mut GitObjectStore,
41 commits_table: &mut CommitsTable,
42 start_commit_id: &str, end_commit_id: &str, onto_commit_id: &str, author: &str,
46) -> Result<RebaseResult, RebaseError> {
47 if start_commit_id == onto_commit_id {
48 return Err(RebaseError::NothingToRebase);
49 }
50
51 let all = log(commits_table, end_commit_id, 0);
55 let mut to_replay: Vec<String> = Vec::new();
56 for commit in &all {
57 if commit.commit_id == start_commit_id {
58 break;
59 }
60 to_replay.push(commit.commit_id.clone());
61 }
62
63 to_replay.reverse();
65
66 if to_replay.is_empty() {
67 return Err(RebaseError::NothingToRebase);
68 }
69
70 let mut current_head = onto_commit_id.to_string();
72 let mut replayed = 0;
73
74 for commit_id in &to_replay {
75 let new_id = cherry_pick(obj_store, commits_table, commit_id, ¤t_head, author)?;
76 current_head = new_id;
77 replayed += 1;
78 }
79
80 Ok(RebaseResult {
81 new_head: current_head,
82 replayed,
83 })
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::{CommitsTable, GitObjectStore, checkout, create_commit};
90 use nusy_arrow_core::{Namespace, Triple, YLayer};
91
92 fn make_triple(s: &str, p: &str, o: &str) -> Triple {
93 Triple {
94 subject: s.to_string(),
95 predicate: p.to_string(),
96 object: o.to_string(),
97 graph: None,
98 confidence: Some(1.0),
99 source_document: None,
100 source_chunk_id: None,
101 extracted_by: None,
102 caused_by: None,
103 derived_from: None,
104 consolidated_at: None,
105 certifiability_class: None,
106 }
107 }
108
109 #[test]
110 fn test_rebase_linear_chain() {
111 let tmp = tempfile::tempdir().unwrap();
112 let mut obj = GitObjectStore::with_snapshot_dir(tmp.path());
113 let mut commits = CommitsTable::new();
114
115 obj.store
117 .add_triple(
118 &make_triple("a", "r", "1"),
119 Namespace::World,
120 YLayer::Semantic,
121 )
122 .unwrap();
123 let base = create_commit(&obj, &mut commits, vec![], "base", "test").unwrap();
124
125 obj.store
127 .add_triple(
128 &make_triple("b", "r", "2"),
129 Namespace::World,
130 YLayer::Semantic,
131 )
132 .unwrap();
133 let c1 = create_commit(
134 &obj,
135 &mut commits,
136 vec![base.commit_id.clone()],
137 "c1",
138 "test",
139 )
140 .unwrap();
141
142 obj.store
143 .add_triple(
144 &make_triple("c", "r", "3"),
145 Namespace::World,
146 YLayer::Semantic,
147 )
148 .unwrap();
149 let c2 =
150 create_commit(&obj, &mut commits, vec![c1.commit_id.clone()], "c2", "test").unwrap();
151
152 checkout(&mut obj, &commits, &base.commit_id).unwrap();
154 obj.store
155 .add_triple(
156 &make_triple("d", "r", "4"),
157 Namespace::World,
158 YLayer::Semantic,
159 )
160 .unwrap();
161 let new_base = create_commit(
162 &obj,
163 &mut commits,
164 vec![base.commit_id.clone()],
165 "new_base",
166 "test",
167 )
168 .unwrap();
169
170 let result = rebase(
172 &mut obj,
173 &mut commits,
174 &base.commit_id,
175 &c2.commit_id,
176 &new_base.commit_id,
177 "test",
178 )
179 .unwrap();
180
181 assert_eq!(result.replayed, 2);
182 assert_ne!(result.new_head, c2.commit_id);
184 }
185
186 #[test]
187 fn test_rebase_nothing_to_rebase() {
188 let tmp = tempfile::tempdir().unwrap();
189 let mut obj = GitObjectStore::with_snapshot_dir(tmp.path());
190 let mut commits = CommitsTable::new();
191
192 obj.store
193 .add_triple(
194 &make_triple("a", "r", "1"),
195 Namespace::World,
196 YLayer::Semantic,
197 )
198 .unwrap();
199 let base = create_commit(&obj, &mut commits, vec![], "base", "test").unwrap();
200
201 let result = rebase(
203 &mut obj,
204 &mut commits,
205 &base.commit_id,
206 &base.commit_id,
207 &base.commit_id,
208 "test",
209 );
210 assert!(result.is_err());
211 }
212
213 #[test]
214 fn test_rebase_single_commit() {
215 let tmp = tempfile::tempdir().unwrap();
216 let mut obj = GitObjectStore::with_snapshot_dir(tmp.path());
217 let mut commits = CommitsTable::new();
218
219 obj.store
220 .add_triple(
221 &make_triple("a", "r", "1"),
222 Namespace::World,
223 YLayer::Semantic,
224 )
225 .unwrap();
226 let base = create_commit(&obj, &mut commits, vec![], "base", "test").unwrap();
227
228 obj.store
229 .add_triple(
230 &make_triple("b", "r", "2"),
231 Namespace::World,
232 YLayer::Semantic,
233 )
234 .unwrap();
235 let c1 = create_commit(
236 &obj,
237 &mut commits,
238 vec![base.commit_id.clone()],
239 "c1",
240 "test",
241 )
242 .unwrap();
243
244 checkout(&mut obj, &commits, &base.commit_id).unwrap();
246 obj.store
247 .add_triple(
248 &make_triple("x", "r", "9"),
249 Namespace::World,
250 YLayer::Semantic,
251 )
252 .unwrap();
253 let new_base = create_commit(
254 &obj,
255 &mut commits,
256 vec![base.commit_id.clone()],
257 "new_base",
258 "test",
259 )
260 .unwrap();
261
262 let result = rebase(
263 &mut obj,
264 &mut commits,
265 &base.commit_id,
266 &c1.commit_id,
267 &new_base.commit_id,
268 "test",
269 )
270 .unwrap();
271
272 assert_eq!(result.replayed, 1);
273 }
274}