1 use std::ffi::CString;
2 use std::{marker, mem, ptr, str};
3 
4 use crate::build::CheckoutBuilder;
5 use crate::util::Binding;
6 use crate::{raw, Error, Index, MergeOptions, Oid, Signature};
7 
8 /// Rebase options
9 ///
10 /// Use to tell the rebase machinery how to operate.
11 pub struct RebaseOptions<'cb> {
12     raw: raw::git_rebase_options,
13     rewrite_notes_ref: Option<CString>,
14     merge_options: Option<MergeOptions>,
15     checkout_options: Option<CheckoutBuilder<'cb>>,
16 }
17 
18 impl<'cb> Default for RebaseOptions<'cb> {
default() -> Self19     fn default() -> Self {
20         Self::new()
21     }
22 }
23 
24 impl<'cb> RebaseOptions<'cb> {
25     /// Creates a new default set of rebase options.
new() -> RebaseOptions<'cb>26     pub fn new() -> RebaseOptions<'cb> {
27         let mut opts = RebaseOptions {
28             raw: unsafe { mem::zeroed() },
29             rewrite_notes_ref: None,
30             merge_options: None,
31             checkout_options: None,
32         };
33         assert_eq!(unsafe { raw::git_rebase_init_options(&mut opts.raw, 1) }, 0);
34         opts
35     }
36 
37     /// Used by `Repository::rebase`, this will instruct other clients working on this
38     /// rebase that you want a quiet rebase experience, which they may choose to
39     /// provide in an application-specific manner. This has no effect upon
40     /// libgit2 directly, but is provided for interoperability between Git
41     /// tools.
quiet(&mut self, quiet: bool) -> &mut RebaseOptions<'cb>42     pub fn quiet(&mut self, quiet: bool) -> &mut RebaseOptions<'cb> {
43         self.raw.quiet = quiet as i32;
44         self
45     }
46 
47     /// Used by `Repository::rebase`, this will begin an in-memory rebase,
48     /// which will allow callers to step through the rebase operations and
49     /// commit the rebased changes, but will not rewind HEAD or update the
50     /// repository to be in a rebasing state.  This will not interfere with
51     /// the working directory (if there is one).
inmemory(&mut self, inmemory: bool) -> &mut RebaseOptions<'cb>52     pub fn inmemory(&mut self, inmemory: bool) -> &mut RebaseOptions<'cb> {
53         self.raw.inmemory = inmemory as i32;
54         self
55     }
56 
57     /// Used by `finish()`, this is the name of the notes reference
58     /// used to rewrite notes for rebased commits when finishing the rebase;
59     /// if NULL, the contents of the configuration option `notes.rewriteRef`
60     /// is examined, unless the configuration option `notes.rewrite.rebase`
61     /// is set to false.  If `notes.rewriteRef` is also NULL, notes will
62     /// not be rewritten.
rewrite_notes_ref(&mut self, rewrite_notes_ref: &str) -> &mut RebaseOptions<'cb>63     pub fn rewrite_notes_ref(&mut self, rewrite_notes_ref: &str) -> &mut RebaseOptions<'cb> {
64         self.rewrite_notes_ref = Some(CString::new(rewrite_notes_ref).unwrap());
65         self
66     }
67 
68     /// Options to control how trees are merged during `next()`.
merge_options(&mut self, opts: MergeOptions) -> &mut RebaseOptions<'cb>69     pub fn merge_options(&mut self, opts: MergeOptions) -> &mut RebaseOptions<'cb> {
70         self.merge_options = Some(opts);
71         self
72     }
73 
74     /// Options to control how files are written during `Repository::rebase`,
75     /// `next()` and `abort()`. Note that a minimum strategy of
76     /// `GIT_CHECKOUT_SAFE` is defaulted in `init` and `next`, and a minimum
77     /// strategy of `GIT_CHECKOUT_FORCE` is defaulted in `abort` to match git
78     /// semantics.
checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut RebaseOptions<'cb>79     pub fn checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut RebaseOptions<'cb> {
80         self.checkout_options = Some(opts);
81         self
82     }
83 
84     /// Acquire a pointer to the underlying raw options.
raw(&mut self) -> *const raw::git_rebase_options85     pub fn raw(&mut self) -> *const raw::git_rebase_options {
86         unsafe {
87             if let Some(opts) = self.merge_options.as_mut().take() {
88                 ptr::copy_nonoverlapping(opts.raw(), &mut self.raw.merge_options, 1);
89             }
90             if let Some(opts) = self.checkout_options.as_mut() {
91                 opts.configure(&mut self.raw.checkout_options);
92             }
93             self.raw.rewrite_notes_ref = self
94                 .rewrite_notes_ref
95                 .as_ref()
96                 .map(|s| s.as_ptr())
97                 .unwrap_or(ptr::null());
98         }
99         &self.raw
100     }
101 }
102 
103 /// Representation of a rebase
104 pub struct Rebase<'repo> {
105     raw: *mut raw::git_rebase,
106     _marker: marker::PhantomData<&'repo raw::git_rebase>,
107 }
108 
109 impl<'repo> Rebase<'repo> {
110     /// Gets the count of rebase operations that are to be applied.
len(&self) -> usize111     pub fn len(&self) -> usize {
112         unsafe { raw::git_rebase_operation_entrycount(self.raw) }
113     }
114 
115     /// Gets the original `HEAD` ref name for merge rebases.
orig_head_name(&self) -> Option<&str>116     pub fn orig_head_name(&self) -> Option<&str> {
117         let name_bytes =
118             unsafe { crate::opt_bytes(self, raw::git_rebase_orig_head_name(self.raw)) };
119         name_bytes.and_then(|s| str::from_utf8(s).ok())
120     }
121 
122     /// Gets the original HEAD id for merge rebases.
orig_head_id(&self) -> Option<Oid>123     pub fn orig_head_id(&self) -> Option<Oid> {
124         unsafe { Oid::from_raw_opt(raw::git_rebase_orig_head_id(self.raw)) }
125     }
126 
127     ///  Gets the rebase operation specified by the given index.
nth(&mut self, n: usize) -> Option<RebaseOperation<'_>>128     pub fn nth(&mut self, n: usize) -> Option<RebaseOperation<'_>> {
129         unsafe {
130             let op = raw::git_rebase_operation_byindex(self.raw, n);
131             if op.is_null() {
132                 None
133             } else {
134                 Some(RebaseOperation::from_raw(op))
135             }
136         }
137     }
138 
139     /// Gets the index of the rebase operation that is currently being applied.
140     /// If the first operation has not yet been applied (because you have called
141     /// `init` but not yet `next`) then this returns None.
operation_current(&mut self) -> Option<usize>142     pub fn operation_current(&mut self) -> Option<usize> {
143         let cur = unsafe { raw::git_rebase_operation_current(self.raw) };
144         if cur == raw::GIT_REBASE_NO_OPERATION {
145             None
146         } else {
147             Some(cur)
148         }
149     }
150 
151     /// Gets the index produced by the last operation, which is the result of
152     /// `next()` and which will be committed by the next invocation of
153     /// `commit()`. This is useful for resolving conflicts in an in-memory
154     /// rebase before committing them.
155     ///
156     /// This is only applicable for in-memory rebases; for rebases within a
157     /// working directory, the changes were applied to the repository's index.
inmemory_index(&mut self) -> Result<Index, Error>158     pub fn inmemory_index(&mut self) -> Result<Index, Error> {
159         let mut idx = ptr::null_mut();
160         unsafe {
161             try_call!(raw::git_rebase_inmemory_index(&mut idx, self.raw));
162             Ok(Binding::from_raw(idx))
163         }
164     }
165 
166     /// Commits the current patch.  You must have resolved any conflicts that
167     /// were introduced during the patch application from the `git_rebase_next`
168     /// invocation. To keep the author and message from the original commit leave
169     /// them as None
commit( &mut self, author: Option<&Signature<'_>>, committer: &Signature<'_>, message: Option<&str>, ) -> Result<Oid, Error>170     pub fn commit(
171         &mut self,
172         author: Option<&Signature<'_>>,
173         committer: &Signature<'_>,
174         message: Option<&str>,
175     ) -> Result<Oid, Error> {
176         let mut id: raw::git_oid = unsafe { mem::zeroed() };
177         let message = crate::opt_cstr(message)?;
178         unsafe {
179             try_call!(raw::git_rebase_commit(
180                 &mut id,
181                 self.raw,
182                 author.map(|a| a.raw()),
183                 committer.raw(),
184                 ptr::null(),
185                 message
186             ));
187             Ok(Binding::from_raw(&id as *const _))
188         }
189     }
190 
191     /// Aborts a rebase that is currently in progress, resetting the repository
192     /// and working directory to their state before rebase began.
abort(&mut self) -> Result<(), Error>193     pub fn abort(&mut self) -> Result<(), Error> {
194         unsafe {
195             try_call!(raw::git_rebase_abort(self.raw));
196         }
197 
198         Ok(())
199     }
200 
201     /// Finishes a rebase that is currently in progress once all patches have
202     /// been applied.
finish(&mut self, signature: Option<&Signature<'_>>) -> Result<(), Error>203     pub fn finish(&mut self, signature: Option<&Signature<'_>>) -> Result<(), Error> {
204         unsafe {
205             try_call!(raw::git_rebase_finish(self.raw, signature.map(|s| s.raw())));
206         }
207 
208         Ok(())
209     }
210 }
211 
212 impl<'rebase> Iterator for Rebase<'rebase> {
213     type Item = Result<RebaseOperation<'rebase>, Error>;
214 
215     /// Performs the next rebase operation and returns the information about it.
216     /// If the operation is one that applies a patch (which is any operation except
217     /// GitRebaseOperation::Exec) then the patch will be applied and the index and
218     /// working directory will be updated with the changes.  If there are conflicts,
219     /// you will need to address those before committing the changes.
next(&mut self) -> Option<Result<RebaseOperation<'rebase>, Error>>220     fn next(&mut self) -> Option<Result<RebaseOperation<'rebase>, Error>> {
221         let mut out = ptr::null_mut();
222         unsafe {
223             try_call_iter!(raw::git_rebase_next(&mut out, self.raw));
224             Some(Ok(RebaseOperation::from_raw(out)))
225         }
226     }
227 }
228 
229 impl<'repo> Binding for Rebase<'repo> {
230     type Raw = *mut raw::git_rebase;
from_raw(raw: *mut raw::git_rebase) -> Rebase<'repo>231     unsafe fn from_raw(raw: *mut raw::git_rebase) -> Rebase<'repo> {
232         Rebase {
233             raw: raw,
234             _marker: marker::PhantomData,
235         }
236     }
raw(&self) -> *mut raw::git_rebase237     fn raw(&self) -> *mut raw::git_rebase {
238         self.raw
239     }
240 }
241 
242 impl<'repo> Drop for Rebase<'repo> {
drop(&mut self)243     fn drop(&mut self) {
244         unsafe { raw::git_rebase_free(self.raw) }
245     }
246 }
247 
248 /// A rebase operation
249 ///
250 /// Describes a single instruction/operation to be performed during the
251 /// rebase.
252 #[derive(Debug, PartialEq)]
253 pub enum RebaseOperationType {
254     /// The given commit is to be cherry-picked. The client should commit the
255     /// changes and continue if there are no conflicts.
256     Pick,
257 
258     /// The given commit is to be cherry-picked, but the client should prompt
259     /// the user to provide an updated commit message.
260     Reword,
261 
262     /// The given commit is to be cherry-picked, but the client should stop to
263     /// allow the user to edit the changes before committing them.
264     Edit,
265 
266     /// The given commit is to be squashed into the previous commit. The commit
267     /// message will be merged with the previous message.
268     Squash,
269 
270     /// The given commit is to be squashed into the previous commit. The commit
271     /// message from this commit will be discarded.
272     Fixup,
273 
274     /// No commit will be cherry-picked. The client should run the given command
275     /// and (if successful) continue.
276     Exec,
277 }
278 
279 impl RebaseOperationType {
280     /// Convert from the int into an enum. Returns None if invalid.
from_raw(raw: raw::git_rebase_operation_t) -> Option<RebaseOperationType>281     pub fn from_raw(raw: raw::git_rebase_operation_t) -> Option<RebaseOperationType> {
282         match raw {
283             raw::GIT_REBASE_OPERATION_PICK => Some(RebaseOperationType::Pick),
284             raw::GIT_REBASE_OPERATION_REWORD => Some(RebaseOperationType::Reword),
285             raw::GIT_REBASE_OPERATION_EDIT => Some(RebaseOperationType::Edit),
286             raw::GIT_REBASE_OPERATION_SQUASH => Some(RebaseOperationType::Squash),
287             raw::GIT_REBASE_OPERATION_FIXUP => Some(RebaseOperationType::Fixup),
288             raw::GIT_REBASE_OPERATION_EXEC => Some(RebaseOperationType::Exec),
289             _ => None,
290         }
291     }
292 }
293 
294 /// A rebase operation
295 ///
296 /// Describes a single instruction/operation to be performed during the
297 /// rebase.
298 #[derive(Debug)]
299 pub struct RebaseOperation<'rebase> {
300     raw: *const raw::git_rebase_operation,
301     _marker: marker::PhantomData<Rebase<'rebase>>,
302 }
303 
304 impl<'rebase> RebaseOperation<'rebase> {
305     /// The type of rebase operation
kind(&self) -> Option<RebaseOperationType>306     pub fn kind(&self) -> Option<RebaseOperationType> {
307         unsafe { RebaseOperationType::from_raw((*self.raw).kind) }
308     }
309 
310     /// The commit ID being cherry-picked. This will be populated for all
311     /// operations except those of type `GIT_REBASE_OPERATION_EXEC`.
id(&self) -> Oid312     pub fn id(&self) -> Oid {
313         unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
314     }
315 
316     ///The executable the user has requested be run.  This will only
317     /// be populated for operations of type RebaseOperationType::Exec
exec(&self) -> Option<&str>318     pub fn exec(&self) -> Option<&str> {
319         unsafe { str::from_utf8(crate::opt_bytes(self, (*self.raw).exec).unwrap()).ok() }
320     }
321 }
322 
323 impl<'rebase> Binding for RebaseOperation<'rebase> {
324     type Raw = *const raw::git_rebase_operation;
from_raw(raw: *const raw::git_rebase_operation) -> RebaseOperation<'rebase>325     unsafe fn from_raw(raw: *const raw::git_rebase_operation) -> RebaseOperation<'rebase> {
326         RebaseOperation {
327             raw: raw,
328             _marker: marker::PhantomData,
329         }
330     }
raw(&self) -> *const raw::git_rebase_operation331     fn raw(&self) -> *const raw::git_rebase_operation {
332         self.raw
333     }
334 }
335 
336 #[cfg(test)]
337 mod tests {
338     use crate::{RebaseOperationType, RebaseOptions, Signature};
339     use std::{fs, path};
340 
341     #[test]
smoke()342     fn smoke() {
343         let (_td, repo) = crate::test::repo_init();
344         let head_target = repo.head().unwrap().target().unwrap();
345         let tip = repo.find_commit(head_target).unwrap();
346         let sig = tip.author();
347         let tree = tip.tree().unwrap();
348 
349         // We just want to see the iteration work so we can create commits with
350         // no changes
351         let c1 = repo
352             .commit(Some("refs/heads/master"), &sig, &sig, "foo", &tree, &[&tip])
353             .unwrap();
354         let c1 = repo.find_commit(c1).unwrap();
355         let c2 = repo
356             .commit(Some("refs/heads/master"), &sig, &sig, "foo", &tree, &[&c1])
357             .unwrap();
358 
359         let head = repo.find_reference("refs/heads/master").unwrap();
360         let branch = repo.reference_to_annotated_commit(&head).unwrap();
361         let upstream = repo.find_annotated_commit(tip.id()).unwrap();
362         let mut rebase = repo
363             .rebase(Some(&branch), Some(&upstream), None, None)
364             .unwrap();
365 
366         assert_eq!(Some("refs/heads/master"), rebase.orig_head_name());
367         assert_eq!(Some(c2), rebase.orig_head_id());
368 
369         assert_eq!(rebase.len(), 2);
370         {
371             let op = rebase.next().unwrap().unwrap();
372             assert_eq!(op.kind(), Some(RebaseOperationType::Pick));
373             assert_eq!(op.id(), c1.id());
374         }
375         {
376             let op = rebase.next().unwrap().unwrap();
377             assert_eq!(op.kind(), Some(RebaseOperationType::Pick));
378             assert_eq!(op.id(), c2);
379         }
380         {
381             let op = rebase.next();
382             assert!(op.is_none());
383         }
384     }
385 
386     #[test]
keeping_original_author_msg()387     fn keeping_original_author_msg() {
388         let (td, repo) = crate::test::repo_init();
389         let head_target = repo.head().unwrap().target().unwrap();
390         let tip = repo.find_commit(head_target).unwrap();
391         let sig = Signature::now("testname", "testemail").unwrap();
392         let mut index = repo.index().unwrap();
393 
394         fs::File::create(td.path().join("file_a")).unwrap();
395         index.add_path(path::Path::new("file_a")).unwrap();
396         index.write().unwrap();
397         let tree_id_a = index.write_tree().unwrap();
398         let tree_a = repo.find_tree(tree_id_a).unwrap();
399         let c1 = repo
400             .commit(Some("refs/heads/master"), &sig, &sig, "A", &tree_a, &[&tip])
401             .unwrap();
402         let c1 = repo.find_commit(c1).unwrap();
403 
404         fs::File::create(td.path().join("file_b")).unwrap();
405         index.add_path(path::Path::new("file_b")).unwrap();
406         index.write().unwrap();
407         let tree_id_b = index.write_tree().unwrap();
408         let tree_b = repo.find_tree(tree_id_b).unwrap();
409         let c2 = repo
410             .commit(Some("refs/heads/master"), &sig, &sig, "B", &tree_b, &[&c1])
411             .unwrap();
412 
413         let branch = repo.find_annotated_commit(c2).unwrap();
414         let upstream = repo.find_annotated_commit(tip.id()).unwrap();
415         let mut opts: RebaseOptions<'_> = Default::default();
416         let mut rebase = repo
417             .rebase(Some(&branch), Some(&upstream), None, Some(&mut opts))
418             .unwrap();
419 
420         assert_eq!(rebase.len(), 2);
421 
422         {
423             rebase.next().unwrap().unwrap();
424             let id = rebase.commit(None, &sig, None).unwrap();
425             let commit = repo.find_commit(id).unwrap();
426             assert_eq!(commit.message(), Some("A"));
427             assert_eq!(commit.author().name(), Some("testname"));
428             assert_eq!(commit.author().email(), Some("testemail"));
429         }
430 
431         {
432             rebase.next().unwrap().unwrap();
433             let id = rebase.commit(None, &sig, None).unwrap();
434             let commit = repo.find_commit(id).unwrap();
435             assert_eq!(commit.message(), Some("B"));
436             assert_eq!(commit.author().name(), Some("testname"));
437             assert_eq!(commit.author().email(), Some("testemail"));
438         }
439         rebase.finish(None).unwrap();
440     }
441 }
442