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, 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, 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/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] 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/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 } 442