]> git.proxmox.com Git - cargo.git/blob - vendor/git2/src/rebase.rs
New upstream version 0.35.0
[cargo.git] / vendor / git2 / src / rebase.rs
1 use std::{marker, ptr, mem, str};
2 use std::ffi::CString;
3
4 use {raw, Oid, Error, Signature, MergeOptions, Index};
5 use build::CheckoutBuilder;
6 use util::Binding;
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 merge_options: Option<MergeOptions>,
14 checkout_options: Option<CheckoutBuilder<'cb>>,
15 }
16
17 impl<'cb> Default for RebaseOptions<'cb> {
18 fn default() -> Self {
19 Self::new()
20 }
21 }
22
23 impl<'cb> RebaseOptions<'cb> {
24 /// Creates a new default set of rebase options.
25 pub fn new() -> RebaseOptions<'cb> {
26 let mut opts = RebaseOptions {
27 raw: unsafe { mem::zeroed() },
28 merge_options: None,
29 checkout_options: None,
30 };
31 assert_eq!(unsafe {
32 raw::git_rebase_init_options(&mut opts.raw, 1)
33 }, 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.
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).
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.
63 pub fn rewrite_notes_ref(&mut self, rewrite_notes_ref: &str) -> &mut RebaseOptions<'cb> {
64 let s = CString::new(rewrite_notes_ref).unwrap().as_ptr();
65 self.raw.rewrite_notes_ref = s;
66 self
67 }
68
69 /// Options to control how trees are merged during `next()`.
70 pub fn merge_options(&mut self, opts: MergeOptions) -> &mut RebaseOptions<'cb> {
71 self.merge_options = Some(opts);
72 self
73 }
74
75 /// Options to control how files are written during `Repository::rebase`,
76 /// `next()` and `abort()`. Note that a minimum strategy of
77 /// `GIT_CHECKOUT_SAFE` is defaulted in `init` and `next`, and a minimum
78 /// strategy of `GIT_CHECKOUT_FORCE` is defaulted in `abort` to match git
79 /// semantics.
80 pub fn checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut RebaseOptions<'cb> {
81 self.checkout_options = Some(opts);
82 self
83 }
84
85 /// Acquire a pointer to the underlying raw options.
86 pub fn raw(&mut self) -> *const raw::git_rebase_options {
87 unsafe {
88 if let Some(opts) = self.merge_options.as_mut().take() {
89 ptr::copy_nonoverlapping(opts.raw(), &mut self.raw.merge_options, 1);
90 mem::forget(opts);
91 }
92 if let Some(opts) = self.checkout_options.as_mut() {
93 opts.configure(&mut self.raw.checkout_options);
94 }
95 }
96 &self.raw
97 }
98
99 }
100
101 /// Representation of a rebase
102 pub struct Rebase<'repo> {
103 raw: *mut raw::git_rebase,
104 _marker: marker::PhantomData<&'repo raw::git_rebase>,
105 }
106
107 impl <'repo> Rebase<'repo> {
108 /// Gets the count of rebase operations that are to be applied.
109 pub fn len(&self) -> usize {
110 unsafe { raw::git_rebase_operation_entrycount(self.raw) }
111 }
112
113 /// Gets the rebase operation specified by the given index.
114 pub fn nth(&mut self, n: usize) -> Option<RebaseOperation> {
115 unsafe {
116 let op = raw::git_rebase_operation_byindex(self.raw, n);
117 if op.is_null() {
118 None
119 } else {
120 Some(RebaseOperation::from_raw(op))
121 }
122 }
123 }
124
125 /// Gets the index of the rebase operation that is currently being applied.
126 /// If the first operation has not yet been applied (because you have called
127 /// `init` but not yet `next`) then this returns None.
128 pub fn operation_current(&mut self) -> Option<usize> {
129 let cur = unsafe { raw::git_rebase_operation_current(self.raw) };
130 if cur == raw::GIT_REBASE_NO_OPERATION {
131 None
132 } else {
133 Some(cur)
134 }
135 }
136
137 /// Gets the index produced by the last operation, which is the result of
138 /// `next()` and which will be committed by the next invocation of
139 /// `commit()`. This is useful for resolving conflicts in an in-memory
140 /// rebase before committing them.
141 ///
142 /// This is only applicable for in-memory rebases; for rebases within a
143 /// working directory, the changes were applied to the repository's index.
144 pub fn inmemory_index(&mut self) -> Result<Index, Error> {
145 let mut idx = ptr::null_mut();
146 unsafe {
147 try_call!(raw::git_rebase_inmemory_index(&mut idx, self.raw));
148 Ok(Binding::from_raw(idx))
149 }
150 }
151
152 /// Commits the current patch. You must have resolved any conflicts that
153 /// were introduced during the patch application from the `git_rebase_next`
154 /// invocation.
155 pub fn commit(&mut self, author: &Signature, committer: &Signature, message: &str) -> Result<Oid, Error> {
156 let mut id: raw::git_oid = unsafe { mem::zeroed() };
157 let message = try!(CString::new(message));
158 unsafe {
159 try_call!(raw::git_rebase_commit(&mut id, self.raw, author.raw(), committer.raw(), ptr::null(), message));
160 Ok(Binding::from_raw(&id as *const _))
161 }
162 }
163
164 /// Aborts a rebase that is currently in progress, resetting the repository
165 /// and working directory to their state before rebase began.
166 pub fn abort(&mut self) -> Result<(), Error> {
167 unsafe {
168 try_call!(raw::git_rebase_abort(self.raw));
169 }
170
171 Ok(())
172 }
173
174 /// Finishes a rebase that is currently in progress once all patches have
175 /// been applied.
176 pub fn finish(&mut self, signature: &Signature) -> Result<(), Error> {
177 unsafe {
178 try_call!(raw::git_rebase_finish(self.raw, signature.raw()));
179 }
180
181 Ok(())
182 }
183 }
184
185 impl <'rebase> Iterator for Rebase<'rebase> {
186 type Item = Result<RebaseOperation<'rebase>, Error>;
187
188 /// Performs the next rebase operation and returns the information about it.
189 /// If the operation is one that applies a patch (which is any operation except
190 /// GitRebaseOperation::Exec) then the patch will be applied and the index and
191 /// working directory will be updated with the changes. If there are conflicts,
192 /// you will need to address those before committing the changes.
193 fn next(&mut self) -> Option<Result<RebaseOperation<'rebase>, Error>> {
194 let mut out = ptr::null_mut();
195 unsafe {
196 try_call_iter!(raw::git_rebase_next(&mut out, self.raw));
197 Some(Ok(RebaseOperation::from_raw(out)))
198 }
199 }
200 }
201
202
203 impl<'repo> Binding for Rebase<'repo> {
204 type Raw = *mut raw::git_rebase;
205 unsafe fn from_raw(raw: *mut raw::git_rebase)
206 -> Rebase<'repo> {
207 Rebase {
208 raw: raw,
209 _marker: marker::PhantomData,
210 }
211 }
212 fn raw(&self) -> *mut raw::git_rebase { self.raw }
213 }
214
215
216 impl<'repo> Drop for Rebase<'repo> {
217 fn drop(&mut self) {
218 unsafe { raw::git_rebase_free(self.raw) }
219 }
220 }
221
222 /// A rebase operation
223 ///
224 /// Describes a single instruction/operation to be performed during the
225 /// rebase.
226 #[derive(Debug, PartialEq)]
227 pub enum RebaseOperationType {
228 /// The given commit is to be cherry-picked. The client should commit the
229 /// changes and continue if there are no conflicts.
230 Pick,
231
232 /// The given commit is to be cherry-picked, but the client should prompt
233 /// the user to provide an updated commit message.
234 Reword,
235
236 /// The given commit is to be cherry-picked, but the client should stop to
237 /// allow the user to edit the changes before committing them.
238 Edit,
239
240 /// The given commit is to be squashed into the previous commit. The commit
241 /// message will be merged with the previous message.
242 Squash,
243
244 /// The given commit is to be squashed into the previous commit. The commit
245 /// message from this commit will be discarded.
246 Fixup,
247
248 /// No commit will be cherry-picked. The client should run the given command
249 /// and (if successful) continue.
250 Exec,
251 }
252
253 impl RebaseOperationType {
254 /// Convert from the int into an enum. Returns None if invalid.
255 pub fn from_raw(raw: raw::git_rebase_operation_t) -> Option<RebaseOperationType> {
256 match raw {
257 raw::GIT_REBASE_OPERATION_PICK => Some(RebaseOperationType::Pick),
258 raw::GIT_REBASE_OPERATION_REWORD => Some(RebaseOperationType::Reword),
259 raw::GIT_REBASE_OPERATION_EDIT => Some(RebaseOperationType::Edit),
260 raw::GIT_REBASE_OPERATION_SQUASH => Some(RebaseOperationType::Squash),
261 raw::GIT_REBASE_OPERATION_FIXUP => Some(RebaseOperationType::Fixup),
262 raw::GIT_REBASE_OPERATION_EXEC => Some(RebaseOperationType::Exec),
263 _ => None,
264 }
265 }
266 }
267
268 /// A rebase operation
269 ///
270 /// Describes a single instruction/operation to be performed during the
271 /// rebase.
272 #[derive(Debug)]
273 pub struct RebaseOperation<'rebase> {
274 raw: *const raw::git_rebase_operation,
275 _marker: marker::PhantomData<Rebase<'rebase>>,
276 }
277
278 impl<'rebase> RebaseOperation<'rebase> {
279 /// The type of rebase operation
280 pub fn kind(&self) -> Option<RebaseOperationType> {
281 unsafe {
282 RebaseOperationType::from_raw((*self.raw).kind)
283 }
284 }
285
286 /// The commit ID being cherry-picked. This will be populated for all
287 /// operations except those of type `GIT_REBASE_OPERATION_EXEC`.
288 pub fn id(&self) -> Oid {
289 unsafe {
290 Binding::from_raw(&(*self.raw).id as *const _)
291 }
292 }
293
294 ///The executable the user has requested be run. This will only
295 /// be populated for operations of type RebaseOperationType::Exec
296 pub fn exec(&self) -> Option<&str> {
297 unsafe {
298 str::from_utf8(::opt_bytes(self, (*self.raw).exec).unwrap()).ok()
299 }
300 }
301 }
302
303 impl<'rebase> Binding for RebaseOperation<'rebase> {
304 type Raw = *const raw::git_rebase_operation;
305 unsafe fn from_raw(raw: *const raw::git_rebase_operation) -> RebaseOperation<'rebase> {
306 RebaseOperation {
307 raw: raw,
308 _marker: marker::PhantomData,
309 }
310 }
311 fn raw(&self) -> *const raw::git_rebase_operation { self.raw }
312 }
313
314 #[cfg(test)]
315 mod tests {
316 use {RebaseOptions, RebaseOperationType};
317
318 #[test]
319 fn smoke() {
320 let (_td, repo) = ::test::repo_init();
321 let head_target = repo.head().unwrap().target().unwrap();
322 let tip = repo.find_commit(head_target).unwrap();
323 let sig = tip.author();
324 let tree = tip.tree().unwrap();
325
326 // We just want to see the iteration work so we can create commits with
327 // no changes
328 let c1 = repo.commit(Some("refs/heads/master"), &sig, &sig, "foo", &tree, &[&tip]).unwrap();
329 let c1 = repo.find_commit(c1).unwrap();
330 let c2 = repo.commit(Some("refs/heads/master"), &sig, &sig, "foo", &tree, &[&c1]).unwrap();
331
332 let branch = repo.find_annotated_commit(c2).unwrap();
333 let upstream = repo .find_annotated_commit(tip.id()).unwrap();
334 let mut opts: RebaseOptions = Default::default();
335 opts.inmemory(true);
336 let mut rebase = repo.rebase(Some(&branch), Some(&upstream), None, Some(&mut opts)).unwrap();
337
338 assert_eq!(rebase.len(), 2);
339 {
340 let op = rebase.next().unwrap().unwrap();
341 assert_eq!(op.kind(), Some(RebaseOperationType::Pick));
342 assert_eq!(op.id(), c1.id());
343 }
344 {
345 let op = rebase.next().unwrap().unwrap();
346 assert_eq!(op.kind(), Some(RebaseOperationType::Pick));
347 assert_eq!(op.id(), c2);
348 }
349 {
350 let op = rebase.next();
351 assert!(op.is_none());
352 }
353 }
354 }