]> git.proxmox.com Git - rustc.git/blame - extra/git2/src/rebase.rs
New upstream version 1.73.0+dfsg1
[rustc.git] / extra / git2 / src / rebase.rs
CommitLineData
0a29b90c
FG
1use std::ffi::CString;
2use std::{marker, mem, ptr, str};
3
4use crate::build::CheckoutBuilder;
5use crate::util::Binding;
6use crate::{raw, Error, Index, MergeOptions, Oid, Signature};
7
8/// Rebase options
9///
10/// Use to tell the rebase machinery how to operate.
11pub 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
18impl<'cb> Default for RebaseOptions<'cb> {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl<'cb> RebaseOptions<'cb> {
25 /// Creates a new default set of rebase options.
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.
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 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()`.
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.
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.
85 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
104pub struct Rebase<'repo> {
105 raw: *mut raw::git_rebase,
106 _marker: marker::PhantomData<&'repo raw::git_rebase>,
107}
108
109impl<'repo> Rebase<'repo> {
110 /// Gets the count of rebase operations that are to be applied.
111 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.
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.
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.
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.
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.
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
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.
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.
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
212impl<'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.
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
229impl<'repo> Binding for Rebase<'repo> {
230 type Raw = *mut raw::git_rebase;
231 unsafe fn from_raw(raw: *mut raw::git_rebase) -> Rebase<'repo> {
232 Rebase {
233 raw,
234 _marker: marker::PhantomData,
235 }
236 }
237 fn raw(&self) -> *mut raw::git_rebase {
238 self.raw
239 }
240}
241
242impl<'repo> Drop for Rebase<'repo> {
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)]
253pub 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
279impl RebaseOperationType {
280 /// Convert from the int into an enum. Returns None if invalid.
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)]
299pub struct RebaseOperation<'rebase> {
300 raw: *const raw::git_rebase_operation,
301 _marker: marker::PhantomData<Rebase<'rebase>>,
302}
303
304impl<'rebase> RebaseOperation<'rebase> {
305 /// The type of rebase operation
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`.
312 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
318 pub fn exec(&self) -> Option<&str> {
319 unsafe { str::from_utf8(crate::opt_bytes(self, (*self.raw).exec).unwrap()).ok() }
320 }
321}
322
323impl<'rebase> Binding for RebaseOperation<'rebase> {
324 type Raw = *const raw::git_rebase_operation;
325 unsafe fn from_raw(raw: *const raw::git_rebase_operation) -> RebaseOperation<'rebase> {
326 RebaseOperation {
327 raw,
328 _marker: marker::PhantomData,
329 }
330 }
331 fn raw(&self) -> *const raw::git_rebase_operation {
332 self.raw
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use crate::{RebaseOperationType, RebaseOptions, Signature};
339 use std::{fs, path};
340
341 #[test]
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/main"), &sig, &sig, "foo", &tree, &[&tip])
353 .unwrap();
354 let c1 = repo.find_commit(c1).unwrap();
355 let c2 = repo
356 .commit(Some("refs/heads/main"), &sig, &sig, "foo", &tree, &[&c1])
357 .unwrap();
358
359 let head = repo.find_reference("refs/heads/main").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/main"), 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]
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/main"), &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/main"), &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}