1use std::collections::{HashMap, HashSet};
4use std::fs;
5use std::path::Path;
6
7use serde::{Deserialize, Serialize};
8
9use crate::change_id::logical_change_id_for_commit;
10use crate::hooks::{
11 run_post_checkout, run_post_rewrite, HookName, PostOperationHooks, RewriteKind,
12};
13use crate::lockfile::{LockFile, LockWait};
14use crate::objects::ObjectId;
15use crate::operation::conflicts_from_tree_merge;
16use crate::porcelain::checkout::checkout_hard_to_tree_from_head_tree;
17use crate::porcelain::merge::apply_conflict_merge_worktree;
18use crate::porcelain::reset::{reset_to_commit, ResetMode, ResetOptions};
19use crate::porcelain::worktree_guard::{
20 ensure_safe_for_rebase_stop, ensure_worktree_clean_for_rebase_exec,
21};
22use crate::progress::{
23 poll_cancel, Cancel, NeverCancel, NullProgress, ProgressSink, UncancellableProgress,
24};
25use crate::refs;
26use crate::repo::Repository;
27use crate::state::{resolve_head_for_repository, write_orig_head, HeadState};
28use crate::write_tree::{write_tree_update_index, WriteTreeFlags};
29
30use super::apply::{apply_rewrite, log_head_move};
31use super::error::HistoryError;
32use super::last::{write_history_last, HistoryLastRecord};
33use super::plan::{
34 commit_parents, plan_rewrite_with_order_resumable_seeded, write_rebase_staged_resolution,
35 ResumablePlanOutcome,
36};
37use super::rebase::{
38 commits_in_rebase_range, plan_rebase_resumable, PreRebase, RebaseOntoOptions, RebasePlan,
39 RebaseRequest,
40};
41use super::types::RefScope;
42use super::types::{
43 AppliedRewrite, ApplyOptions, CommitEdit, HistoryEdits, PlannedRefUpdate, RewriteOptions,
44 RewritePlan, RewrittenCommit,
45};
46use super::{commit_tree, load_commit};
47use crate::operation::Conflict;
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct StartRebaseOptions {
52 pub request: RebaseRequest,
54 pub rebase: RebaseOntoOptions,
56 pub apply: ApplyOptions,
58 pub pre_rebase: PreRebase,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65#[cfg_attr(feature = "serde-models", derive(serde::Serialize))]
66pub struct RebaseStop {
67 pub commit: ObjectId,
69 pub conflicts: Vec<Conflict>,
71 pub done: Vec<ObjectId>,
73 pub remaining: Vec<ObjectId>,
75 pub rerere: Vec<crate::rerere::RerereEvent>,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81#[cfg_attr(feature = "serde-models", derive(serde::Serialize))]
82#[cfg_attr(feature = "serde-models", serde(rename_all = "snake_case"))]
83pub enum RebaseOutcome {
84 Done {
86 applied: AppliedRewrite,
88 plan: Box<RewritePlan>,
90 head_change: Option<(ObjectId, ObjectId)>,
97 rerere: Vec<crate::rerere::RerereEvent>,
99 },
100 ExecDone {
103 applied: AppliedRewrite,
105 plan: Box<RewritePlan>,
107 exec: super::ExecReport,
110 },
111 Stopped(RebaseStop),
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Default)]
117#[cfg_attr(feature = "serde-models", derive(serde::Serialize))]
118pub struct RebaseAbort {
119 pub overwritten: Vec<PlannedRefUpdate>,
124}
125
126#[derive(Debug, thiserror::Error)]
128#[non_exhaustive]
129pub enum RebaseSessionError {
130 #[error("no rebase in progress")]
132 NoRebaseInProgress,
133
134 #[error("rebase is already in progress")]
136 RebaseAlreadyInProgress,
137
138 #[error("a rebase started by git is in progress")]
144 ForeignRebaseInProgress,
145
146 #[error("cannot continue: unmerged paths remain in the index")]
148 UnmergedPaths,
149
150 #[error("nothing to commit after conflict resolution")]
152 NothingToCommit,
153
154 #[error(
157 "HEAD moved away from the conflict resolution parent (expected {expected}); save commits on a branch or reset before `grit rebase --continue`"
158 )]
159 ResolutionHeadChanged {
160 expected: ObjectId,
162 },
163
164 #[error("rebase session requires HEAD on a branch")]
166 NotOnBranch,
167
168 #[error("exec replay HEAD changed; expected {expected}; save new commits on a branch, then abort and restart the rebase")]
171 ExecHeadChanged {
172 expected: ObjectId,
174 },
175
176 #[error(
180 "ref {name} moved from {expected} to {} while the rebase was stopped; abort the rebase to restore it",
181 .actual.map_or_else(|| "(deleted)".to_owned(), |oid| oid.to_hex())
182 )]
183 StaleExecPlan {
184 name: String,
186 expected: ObjectId,
188 actual: Option<ObjectId>,
190 },
191
192 #[error("ref {name} moved while aborting the rebase; retry the abort")]
195 AbortRefMoved {
196 name: String,
198 },
199
200 #[error("rebase state records an invalid object id: {value:?}")]
203 InvalidStateOid {
204 value: String,
206 },
207
208 #[error(transparent)]
210 History(#[from] HistoryError),
211
212 #[error(transparent)]
214 Library(#[from] crate::error::Error),
215}
216
217impl RebaseSessionError {
218 #[must_use]
220 pub fn is_cancelled(&self) -> bool {
221 match self {
222 Self::Library(e) => e.is_cancelled(),
223 Self::History(e) => e.is_cancelled(),
224 Self::NoRebaseInProgress
225 | Self::RebaseAlreadyInProgress
226 | Self::ForeignRebaseInProgress
227 | Self::UnmergedPaths
228 | Self::NothingToCommit
229 | Self::ResolutionHeadChanged { .. }
230 | Self::NotOnBranch
231 | Self::ExecHeadChanged { .. }
232 | Self::StaleExecPlan { .. }
233 | Self::AbortRefMoved { .. }
234 | Self::InvalidStateOid { .. } => false,
235 }
236 }
237}
238
239pub fn start_rebase(
270 repo: &Repository,
271 opts: &StartRebaseOptions,
272 progress: &mut dyn ProgressSink,
273 cancel: &dyn Cancel,
274) -> Result<RebaseOutcome, RebaseSessionError> {
275 super::ensure_rewrite_apply_ready(repo)?;
276 if let Some(active) = active_rebase(repo.git_dir()) {
277 return Err(match active {
278 ActiveRebase::Foreign(_) => RebaseSessionError::ForeignRebaseInProgress,
279 ActiveRebase::Exec | ActiveRebase::Grit => RebaseSessionError::RebaseAlreadyInProgress,
280 });
281 }
282 let (head_ref, orig_head) = branch_head(repo)?;
283 let plan = plan_rebase_resumable(repo, &opts.request, &opts.rebase, &opts.pre_rebase)?;
284 poll_cancel(progress, cancel).map_err(map_lib)?;
285 match plan {
286 Err(RebasePlan::UpToDate) => Ok(up_to_date_outcome(orig_head)),
287 Err(RebasePlan::Plan(_) | RebasePlan::Conflict { .. }) => Err(HistoryError::Planning {
288 message: "unexpected non-resumable rebase plan".into(),
289 }
290 .into()),
291 Ok(ResumablePlanOutcome::Complete(plan)) => {
292 let applied =
293 apply_rewrite(repo, &plan, &opts.apply, progress, cancel).map_err(map_apply_err)?;
294 write_orig_head(repo, orig_head).map_err(map_lib)?;
295 Ok(RebaseOutcome::Done {
296 head_change: (orig_head != applied.head).then_some((orig_head, applied.head)),
297 applied,
298 plan,
299 rerere: Vec::new(),
300 })
301 }
302 Ok(ResumablePlanOutcome::Partial(partial)) => {
303 let orig_tree = commit_tree(repo, orig_head)?;
304 let stop_tree = commit_tree(repo, partial.conflict.replayed_parent)?;
305 ensure_safe_for_rebase_stop(repo, &orig_tree, &stop_tree)?;
306 let stop = install_stopped_rebase(
307 repo,
308 &head_ref,
309 orig_head,
310 &opts.request,
311 &opts.rebase,
312 &opts.apply,
313 *partial,
314 Vec::new(),
315 progress,
316 cancel,
317 )?;
318 write_orig_head(repo, orig_head).map_err(map_lib)?;
319 if opts.apply.hooks.enabled() {
320 let onto = opts.request.onto.unwrap_or(opts.request.upstream);
321 run_post_checkout(repo, Some(orig_head), onto, true);
322 }
323 Ok(RebaseOutcome::Stopped(stop))
324 }
325 }
326}
327
328pub fn start_rebase_with_exec(
352 repo: &Repository,
353 opts: &StartRebaseOptions,
354 spec: &super::ExecSpec,
355 runner: &dyn crate::command_runner::CommandRunner,
356 progress: &mut dyn ProgressSink,
357 cancel: &dyn Cancel,
358) -> Result<RebaseOutcome, RebaseSessionError> {
359 super::ensure_rewrite_apply_ready(repo)?;
360 if let Some(active) = active_rebase(repo.git_dir()) {
361 return Err(match active {
362 ActiveRebase::Foreign(_) => RebaseSessionError::ForeignRebaseInProgress,
363 ActiveRebase::Exec | ActiveRebase::Grit => RebaseSessionError::RebaseAlreadyInProgress,
364 });
365 }
366 let (head_ref, orig_head) = branch_head(repo)?;
367 let plan = plan_rebase_resumable(repo, &opts.request, &opts.rebase, &opts.pre_rebase)?;
368 poll_cancel(progress, cancel).map_err(map_lib)?;
369 match plan {
370 Err(RebasePlan::UpToDate) => {
371 if spec.commands.is_empty() {
372 return Ok(up_to_date_outcome(orig_head));
373 }
374 let fork =
375 super::rebase::rebase_fork(repo, opts.request.upstream, opts.request.branch_tip)?;
376 let effective_onto = opts.request.onto.unwrap_or(opts.request.upstream);
377 if opts.request.branch_tip == effective_onto {
378 return Ok(up_to_date_outcome(orig_head));
379 }
380 let range =
381 super::rebase::commits_in_rebase_range(repo, fork, &[opts.request.branch_tip])?;
382 if range.is_empty() {
383 return Ok(up_to_date_outcome(orig_head));
384 }
385 opts.pre_rebase.run(repo)?;
386 let mut plan = super::rebase::plan_rebase_identity_exec(
387 repo,
388 fork,
389 effective_onto,
390 &range,
391 &opts.rebase,
392 )?;
393 super::apply::verify_rewrite_plan(repo, &plan)?;
394 let exec = match super::verify_rebase_with_exec(
395 repo,
396 &mut plan,
397 spec,
398 &opts.rebase.rewrite,
399 runner,
400 ) {
401 Ok(exec) => exec,
402 Err(
403 stop @ (HistoryError::ExecDirty { .. } | HistoryError::ExecConflict { .. }),
404 ) => {
405 write_orig_head(repo, orig_head).map_err(map_lib)?;
406 return Err(stop.into());
407 }
408 Err(err) => return Err(err.into()),
409 };
410 let applied =
411 apply_rewrite(repo, &plan, &opts.apply, progress, cancel).map_err(map_apply_err)?;
412 write_orig_head(repo, orig_head).map_err(map_lib)?;
413 Ok(RebaseOutcome::ExecDone {
414 applied,
415 plan: Box::new(plan),
416 exec,
417 })
418 }
419 Err(RebasePlan::Plan(_) | RebasePlan::Conflict { .. }) => Err(HistoryError::Planning {
420 message: "unexpected non-resumable rebase plan".into(),
421 }
422 .into()),
423 Ok(ResumablePlanOutcome::Partial(partial)) => {
424 let orig_tree = commit_tree(repo, orig_head)?;
425 let stop_tree = commit_tree(repo, partial.conflict.replayed_parent)?;
426 ensure_safe_for_rebase_stop(repo, &orig_tree, &stop_tree)?;
427 let stop = install_stopped_rebase(
428 repo,
429 &head_ref,
430 orig_head,
431 &opts.request,
432 &opts.rebase,
433 &opts.apply,
434 *partial,
435 spec.commands.clone(),
436 progress,
437 cancel,
438 )?;
439 write_orig_head(repo, orig_head).map_err(map_lib)?;
440 if opts.apply.hooks.enabled() {
441 let onto = opts.request.onto.unwrap_or(opts.request.upstream);
442 run_post_checkout(repo, Some(orig_head), onto, true);
443 }
444 Ok(RebaseOutcome::Stopped(stop))
445 }
446 Ok(ResumablePlanOutcome::Complete(plan)) => {
447 let mut plan = *plan;
448 super::apply::verify_rewrite_plan(repo, &plan)?;
449 let exec = match super::verify_rebase_with_exec(
450 repo,
451 &mut plan,
452 spec,
453 &opts.rebase.rewrite,
454 runner,
455 ) {
456 Ok(exec) => exec,
457 Err(
458 stop @ (HistoryError::ExecDirty { .. } | HistoryError::ExecConflict { .. }),
459 ) => {
460 write_orig_head(repo, orig_head).map_err(map_lib)?;
461 return Err(stop.into());
462 }
463 Err(err) => return Err(err.into()),
464 };
465 let applied =
466 apply_rewrite(repo, &plan, &opts.apply, progress, cancel).map_err(map_apply_err)?;
467 write_orig_head(repo, orig_head).map_err(map_lib)?;
468 Ok(RebaseOutcome::ExecDone {
469 applied,
470 plan: Box::new(plan),
471 exec,
472 })
473 }
474 }
475}
476
477fn up_to_date_outcome(head: ObjectId) -> RebaseOutcome {
478 RebaseOutcome::Done {
479 head_change: None,
480 applied: AppliedRewrite {
481 ref_updates: Vec::new(),
482 head,
483 hooks_run: Vec::new(),
484 },
485 plan: Box::new(RewritePlan {
486 commits: Vec::new(),
487 ref_updates: Vec::new(),
488 head: super::types::HeadEffect::default(),
489 }),
490 rerere: Vec::new(),
491 }
492}
493
494pub(crate) fn persist_rebase_stopped_head_if_missing(
497 repo: &Repository,
498) -> Result<(), RebaseSessionError> {
499 if !crate::state::grit_rebase_conflict_stop_active(repo.git_dir()) {
500 return Ok(());
501 }
502 let path = repo.git_dir().join("rebase-merge/grit-plan");
503 let raw = fs::read_to_string(&path).map_err(|_| RebaseSessionError::NoRebaseInProgress)?;
504 let mut parsed: PersistedRebaseState =
505 serde_json::from_str(&raw).map_err(|_| RebaseSessionError::NoRebaseInProgress)?;
506 if parsed.stopped_head.is_some() {
507 return Ok(());
508 }
509 let head = resolve_head_oid(repo)?;
510 parsed.stopped_head = Some(oid_hex(&head));
511 write_persisted_state(repo.git_dir(), &parsed)
512}
513
514fn replay_base_parent(state: &LoadedState, oid: ObjectId) -> bool {
515 oid == state.onto || state.mapping.values().any(|mapped| *mapped == oid)
516}
517
518fn stopped_head_for_continue(
519 state: &LoadedState,
520 repo: &Repository,
521) -> Result<ObjectId, RebaseSessionError> {
522 if let Some(oid) = state.stopped_head {
523 return Ok(oid);
524 }
525 let head = resolve_head_oid(repo)?;
526 if replay_base_parent(state, head) {
527 return Ok(head);
528 }
529 let head_commit = load_commit(repo, head)?;
530 if head_commit.parents.len() == 1 {
531 if let Some(&parent) = head_commit.parents.first() {
532 if replay_base_parent(state, parent) {
533 return Ok(parent);
534 }
535 }
536 } else {
537 for &parent in &head_commit.parents {
538 if replay_base_parent(state, parent) {
539 return Ok(parent);
540 }
541 }
542 }
543 Err(RebaseSessionError::NoRebaseInProgress)
544}
545
546fn is_first_parent_descendant_of(
547 repo: &Repository,
548 ancestor: ObjectId,
549 tip: ObjectId,
550) -> Result<bool, RebaseSessionError> {
551 if tip == ancestor {
552 return Ok(false);
553 }
554 let mut current = Some(tip);
555 while let Some(oid) = current {
556 let parents = commit_parents(repo, oid).map_err(RebaseSessionError::from)?;
557 let Some(first) = parents.first().copied() else {
558 break;
559 };
560 if first == ancestor {
561 return Ok(true);
562 }
563 current = Some(first);
564 }
565 Ok(false)
566}
567
568fn run_exec_after_resolution(
569 repo: &Repository,
570 state: &LoadedState,
571 head_oid: ObjectId,
572) -> Result<ObjectId, RebaseSessionError> {
573 if state.exec_commands.is_empty() {
574 return Ok(head_oid);
575 }
576 let spec = super::ExecSpec {
577 commands: state.exec_commands.clone(),
578 };
579 let runner = repo.command_runner();
580 let (tip, _) = super::exec::run_exec_at_tip(repo, &spec, runner.as_ref(), head_oid)?;
581 Ok(tip)
582}
583
584#[allow(clippy::too_many_arguments)]
585fn advance_after_user_resolution_commit(
586 repo: &Repository,
587 state: &LoadedState,
588 opts: &StartRebaseOptions,
589 current: ObjectId,
590 head_oid: ObjectId,
591 rerere: Vec<crate::rerere::RerereEvent>,
592 progress: &mut dyn ProgressSink,
593 cancel: &dyn Cancel,
594) -> Result<RebaseOutcome, RebaseSessionError> {
595 let head_oid = run_exec_after_resolution(repo, state, head_oid)?;
596 let current_data = load_commit(repo, current)?;
597 let mut mapping = state.mapping.clone();
598 mapping.insert(current, head_oid);
599 let mut rewritten = state.rewritten.clone();
600 rewritten.push(RewrittenCommit {
601 old: current,
602 new: Some(head_oid),
603 change_id: logical_change_id_for_commit(repo, head_oid).map_err(map_lib)?,
604 subject: super::subject::subject_for_rewrite_row(repo, ¤t_data, Some(head_oid))
605 .map_err(RebaseSessionError::from)?,
606 reused: false,
607 });
608 let resume_order: Vec<ObjectId> = replay_order_for_resume(state, current)
609 .into_iter()
610 .filter(|commit| *commit != current)
611 .collect();
612 let mut edits = state.edits.clone();
613 edits.retain(|commit, _| resume_order.contains(commit));
614 let rewrite = replan_rewrite_options(state);
615 let outcome = plan_rewrite_with_order_resumable_seeded(
616 repo,
617 &edits,
618 &rewrite,
619 Some(&resume_order),
620 state.flatten_leaf_merges.then_some(state.onto),
621 state.rebase.favor,
622 Some((mapping, rewritten)),
623 Some(state.scoped_refs.clone()),
624 )?;
625 advance_after_plan(
626 repo,
627 state,
628 &opts.apply,
629 outcome,
630 head_oid,
631 rerere,
632 progress,
633 cancel,
634 )
635}
636
637pub fn continue_rebase(
657 repo: &Repository,
658 opts: &StartRebaseOptions,
659 progress: &mut dyn ProgressSink,
660 cancel: &dyn Cancel,
661) -> Result<RebaseOutcome, RebaseSessionError> {
662 if matches!(active_rebase(repo.git_dir()), Some(ActiveRebase::Exec)) {
663 return super::exec::resume_exec_rebase(repo, &opts.apply, false, progress, cancel);
664 }
665 super::ensure_rewrite_apply_ready(repo)?;
666 let state = load_persisted_state(repo.git_dir())?;
667 if repo.load_index()?.has_unmerged_entries() {
668 return Err(RebaseSessionError::UnmergedPaths);
669 }
670 let rerere = crate::rerere::finish_merge_rerere(repo).map_err(map_lib)?;
671 let current = state
672 .stopped_commit
673 .or_else(|| state.remaining.first().copied())
674 .ok_or(RebaseSessionError::NoRebaseInProgress)?;
675 let new_tree = {
676 let mut index = repo.load_index()?;
677 write_tree_update_index(repo.odb(), &mut index, "", WriteTreeFlags::silent())
678 .map_err(map_lib)?
679 };
680 let head_oid = resolve_head_oid(repo)?;
681 let head_tree = commit_tree(repo, head_oid)?;
682 if new_tree == head_tree {
683 let stopped_head = stopped_head_for_continue(&state, repo)?;
684 if head_oid == stopped_head {
685 return Err(RebaseSessionError::NothingToCommit);
686 }
687 if is_first_parent_descendant_of(repo, stopped_head, head_oid)? {
688 ensure_worktree_clean_for_rebase_exec(repo).map_err(map_lib)?;
689 return advance_after_user_resolution_commit(
690 repo, &state, opts, current, head_oid, rerere, progress, cancel,
691 );
692 }
693 return Err(RebaseSessionError::ResolutionHeadChanged {
694 expected: stopped_head,
695 });
696 }
697
698 let original_parents = commit_parents(repo, current).map_err(RebaseSessionError::from)?;
699 let parents = if original_parents.len() <= 1 {
700 vec![head_oid]
701 } else {
702 let mut mapped = vec![head_oid];
703 mapped.extend(
704 original_parents[1..]
705 .iter()
706 .map(|parent| state.mapping.get(parent).copied().unwrap_or(*parent)),
707 );
708 mapped
709 };
710
711 if state.remaining.len() > 1 {
715 let stopped_head = stopped_head_for_continue(&state, repo)?;
716 if head_oid != stopped_head {
717 return Err(RebaseSessionError::ResolutionHeadChanged {
718 expected: stopped_head,
719 });
720 }
721 let resolved =
722 write_rebase_staged_resolution(repo, &state.rewrite, current, new_tree, &parents)
723 .map_err(RebaseSessionError::from)?;
724 repo.odb().flush_for_publish().map_err(map_lib)?;
725 refs::update_direct_ref_on_store(repo.refs(), repo.git_dir(), "HEAD", &resolved)
726 .map_err(map_lib)?;
727 ensure_worktree_clean_for_rebase_exec(repo).map_err(map_lib)?;
728 return advance_after_user_resolution_commit(
729 repo, &state, opts, current, resolved, rerere, progress, cancel,
730 );
731 }
732
733 if !state.exec_commands.is_empty() && new_tree != head_tree {
734 let stopped_head = stopped_head_for_continue(&state, repo)?;
735 if head_oid != stopped_head {
736 return Err(RebaseSessionError::ResolutionHeadChanged {
737 expected: stopped_head,
738 });
739 }
740 let resolved =
741 write_rebase_staged_resolution(repo, &state.rewrite, current, new_tree, &parents)
742 .map_err(RebaseSessionError::from)?;
743 repo.odb().flush_for_publish().map_err(map_lib)?;
744 refs::update_direct_ref_on_store(repo.refs(), repo.git_dir(), "HEAD", &resolved)
745 .map_err(map_lib)?;
746 ensure_worktree_clean_for_rebase_exec(repo).map_err(map_lib)?;
747 return advance_after_user_resolution_commit(
748 repo, &state, opts, current, resolved, rerere, progress, cancel,
749 );
750 }
751
752 let resume_order = replay_order_for_resume(&state, current);
753 let mut edits = state.edits.clone();
754 edits.insert(
755 current,
756 CommitEdit::Rewrite {
757 tree: Some(new_tree),
758 message: None,
759 author: None,
760 parents: Some(parents),
761 },
762 );
763 edits.retain(|commit, _| resume_order.contains(commit));
764
765 let seed = (state.mapping.clone(), state.rewritten.clone());
766 let rewrite = replan_rewrite_options(&state);
767 let outcome = plan_rewrite_with_order_resumable_seeded(
768 repo,
769 &edits,
770 &rewrite,
771 Some(&resume_order),
772 state.flatten_leaf_merges.then_some(state.onto),
773 state.rebase.favor,
774 Some(seed),
775 Some(state.scoped_refs.clone()),
776 )?;
777 advance_after_plan(
778 repo,
779 &state,
780 &opts.apply,
781 outcome,
782 head_oid,
783 rerere,
784 progress,
785 cancel,
786 )
787}
788
789pub fn skip_rebase(
803 repo: &Repository,
804 opts: &StartRebaseOptions,
805 progress: &mut dyn ProgressSink,
806 cancel: &dyn Cancel,
807) -> Result<RebaseOutcome, RebaseSessionError> {
808 if matches!(active_rebase(repo.git_dir()), Some(ActiveRebase::Exec)) {
809 return super::exec::resume_exec_rebase(repo, &opts.apply, true, progress, cancel);
810 }
811 let state = load_persisted_state(repo.git_dir())?;
812 let current = state
813 .remaining
814 .first()
815 .copied()
816 .ok_or(RebaseSessionError::NoRebaseInProgress)?;
817 let head_oid = resolve_head_oid(repo)?;
818 reset_index_worktree_to_head(repo, head_oid, progress, cancel)?;
819
820 let replayed_parent = run_exec_after_resolution(repo, &state, head_oid)?;
821 let mut mapping = state.mapping.clone();
822 mapping.insert(current, replayed_parent);
823 let mut rewritten = state.rewritten.clone();
824 rewritten.push(RewrittenCommit {
825 old: current,
826 new: None,
827 change_id: None,
828 subject: None,
829 reused: false,
830 });
831
832 let resume_order: Vec<ObjectId> = state
833 .remaining
834 .iter()
835 .copied()
836 .filter(|c| *c != current)
837 .collect();
838 let mut edits = state.edits.clone();
839 edits.retain(|commit, _| resume_order.contains(commit));
840 let rewrite = replan_rewrite_options(&state);
841 let outcome = plan_rewrite_with_order_resumable_seeded(
842 repo,
843 &edits,
844 &rewrite,
845 Some(&resume_order),
846 state.flatten_leaf_merges.then_some(state.onto),
847 state.rebase.favor,
848 Some((mapping, rewritten)),
849 Some(state.scoped_refs.clone()),
850 )?;
851 advance_after_plan(
852 repo,
853 &state,
854 &opts.apply,
855 outcome,
856 head_oid,
857 Vec::new(),
858 progress,
859 cancel,
860 )
861}
862
863pub fn abort_rebase(
884 repo: &Repository,
885 identity: &crate::ident::Signature,
886) -> Result<RebaseAbort, RebaseSessionError> {
887 identity
888 .validate()
889 .map_err(|err| map_lib(crate::error::Error::InvalidSignature(err)))?;
890 let had_journal = super::rewrite_apply_journal_active(repo.git_dir());
891 if had_journal {
892 super::abort_interrupted_rewrite_apply(repo)?;
893 }
894 match active_rebase(repo.git_dir()) {
895 None => {
896 if had_journal {
897 Ok(RebaseAbort::default())
898 } else {
899 Err(RebaseSessionError::NoRebaseInProgress)
900 }
901 }
902 Some(ActiveRebase::Foreign(dir)) => abort_foreign_rebase(repo, &dir, identity),
903 Some(ActiveRebase::Exec) => super::exec::abort_exec_rebase(repo, identity),
904 Some(ActiveRebase::Grit) => {
905 let state = load_persisted_state(repo.git_dir())?;
906 crate::rerere::rerere_clear(repo.git_dir()).map_err(map_lib)?;
907 let abort = if rebase_head_is_branch(&state.head_ref) {
908 hard_reset_branch(repo, &state.head_ref, state.orig_head, identity)?
909 } else {
910 hard_reset_head(repo, state.orig_head, identity)?;
911 RebaseAbort::default()
912 };
913 clear_rebase_state(repo.git_dir())?;
914 Ok(abort)
915 }
916 }
917}
918
919fn abort_foreign_rebase(
920 repo: &Repository,
921 state_dir: &Path,
922 identity: &crate::ident::Signature,
923) -> Result<RebaseAbort, RebaseSessionError> {
924 if state_dir.join("autostash").exists() {
925 return Err(RebaseSessionError::ForeignRebaseInProgress);
926 }
927 let orig_head = first_line(&state_dir.join("orig-head"))
928 .and_then(|hex| ObjectId::from_hex(&hex).ok())
929 .ok_or(RebaseSessionError::ForeignRebaseInProgress)?;
930 crate::rerere::rerere_clear(repo.git_dir()).map_err(map_lib)?;
931 let abort =
932 match first_line(&state_dir.join("head-name")).filter(|name| name.starts_with("refs/")) {
933 Some(head_ref) => hard_reset_branch(repo, &head_ref, orig_head, identity)?,
934 None => {
935 hard_reset_head(repo, orig_head, identity)?;
936 RebaseAbort::default()
937 }
938 };
939 for name in ["REBASE_HEAD", "AUTO_MERGE", "MERGE_MSG"] {
940 remove_if_exists(&repo.git_dir().join(name))?;
941 }
942 fs::remove_dir_all(state_dir).map_err(map_lib)?;
943 Ok(abort)
944}
945
946enum ActiveRebase {
948 Exec,
950 Grit,
952 Foreign(std::path::PathBuf),
954}
955
956fn active_rebase(git_dir: &Path) -> Option<ActiveRebase> {
957 let merge = git_dir.join("rebase-merge");
958 if merge.join("grit-exec").is_file() {
959 return Some(ActiveRebase::Exec);
960 }
961 if merge.join("grit-plan").is_file() {
962 return Some(ActiveRebase::Grit);
963 }
964 if merge.is_dir() {
965 return Some(ActiveRebase::Foreign(merge));
966 }
967 let apply = git_dir.join("rebase-apply");
968 (apply.is_dir() && !apply.join("applying").exists()).then_some(ActiveRebase::Foreign(apply))
969}
970
971fn first_line(path: &Path) -> Option<String> {
972 let raw = fs::read_to_string(path).ok()?;
973 let line = raw.lines().next()?.trim();
974 (!line.is_empty()).then(|| line.to_owned())
975}
976
977fn remove_if_exists(path: &Path) -> Result<(), RebaseSessionError> {
978 match fs::remove_file(path) {
979 Err(e) if e.kind() != std::io::ErrorKind::NotFound => Err(map_lib(e)),
980 _ => Ok(()),
981 }
982}
983
984#[derive(Debug, Clone, Serialize, Deserialize)]
987struct PersistedRebaseState {
988 mapping: HashMap<String, String>,
989 rewritten: Vec<PersistedRewrittenRow>,
990 edits: Vec<PersistedEdit>,
991 rewrite: RewriteOptions,
992 rebase: RebaseOntoOptions,
993 apply: ApplyOptions,
994 replay_order: Vec<String>,
995 remaining: Vec<String>,
996 done: Vec<String>,
997 fork: String,
998 onto: String,
999 upstream: String,
1000 head_ref: String,
1001 orig_head: String,
1002 branch_tip: String,
1003 flatten_leaf_merges: bool,
1004 scoped_refs: Vec<(String, String)>,
1005 #[serde(default)]
1007 stopped_head: Option<String>,
1008 #[serde(default)]
1010 stopped_commit: Option<String>,
1011 #[serde(default)]
1013 exec_commands: Vec<String>,
1014}
1015
1016#[derive(Debug, Clone, Serialize, Deserialize)]
1017struct PersistedRewrittenRow {
1018 old: String,
1019 new: Option<String>,
1020 #[serde(default)]
1021 reused: bool,
1022}
1023
1024#[derive(Debug, Clone, Serialize, Deserialize)]
1025enum PersistedEdit {
1026 Reparent {
1027 commit: String,
1028 parent: String,
1029 },
1030 Reuse {
1031 commit: String,
1032 existing: String,
1033 #[serde(default)]
1035 parent: Option<String>,
1036 },
1037 Drop {
1038 commit: String,
1039 },
1040 Rewrite {
1041 commit: String,
1042 tree: Option<String>,
1043 message: Option<String>,
1044 author: Option<String>,
1045 },
1046}
1047
1048fn oid_hex(oid: &ObjectId) -> String {
1049 oid.to_hex()
1050}
1051
1052fn parse_oid(hex: &str) -> Result<ObjectId, RebaseSessionError> {
1053 ObjectId::from_hex(hex).map_err(|_| RebaseSessionError::InvalidStateOid {
1054 value: hex.to_owned(),
1055 })
1056}
1057
1058fn edits_to_persist(edits: &HistoryEdits) -> Vec<PersistedEdit> {
1059 edits
1060 .iter()
1061 .map(|(commit, edit)| match edit {
1062 CommitEdit::Reparent { parent } => PersistedEdit::Reparent {
1063 commit: oid_hex(commit),
1064 parent: oid_hex(parent),
1065 },
1066 CommitEdit::Reuse { existing, parent } => PersistedEdit::Reuse {
1067 commit: oid_hex(commit),
1068 existing: oid_hex(existing),
1069 parent: Some(oid_hex(parent)),
1070 },
1071 CommitEdit::Drop => PersistedEdit::Drop {
1072 commit: oid_hex(commit),
1073 },
1074 CommitEdit::Rewrite {
1075 tree,
1076 message,
1077 author,
1078 parents: _,
1079 } => PersistedEdit::Rewrite {
1080 commit: oid_hex(commit),
1081 tree: tree.as_ref().map(oid_hex),
1082 message: message.clone(),
1083 author: author.clone(),
1084 },
1085 CommitEdit::InsertBefore(_) | CommitEdit::SplitBefore { .. } => PersistedEdit::Drop {
1086 commit: oid_hex(commit),
1087 },
1088 })
1089 .collect()
1090}
1091
1092fn edits_from_persist(rows: &[PersistedEdit]) -> Result<HistoryEdits, RebaseSessionError> {
1093 let mut edits = HistoryEdits::new();
1094 for row in rows {
1095 match row {
1096 PersistedEdit::Reparent { commit, parent } => {
1097 edits.insert(
1098 parse_oid(commit)?,
1099 CommitEdit::Reparent {
1100 parent: parse_oid(parent)?,
1101 },
1102 );
1103 }
1104 PersistedEdit::Reuse {
1105 commit,
1106 existing,
1107 parent,
1108 } => {
1109 let existing = parse_oid(existing)?;
1110 let parent = parent.as_deref().map(parse_oid).transpose()?;
1111 edits.insert(
1112 parse_oid(commit)?,
1113 CommitEdit::Reuse {
1114 existing,
1115 parent: parent.unwrap_or(existing),
1116 },
1117 );
1118 }
1119 PersistedEdit::Drop { commit } => {
1120 edits.insert(parse_oid(commit)?, CommitEdit::Drop);
1121 }
1122 PersistedEdit::Rewrite {
1123 commit,
1124 tree,
1125 message,
1126 author,
1127 } => {
1128 edits.insert(
1129 parse_oid(commit)?,
1130 CommitEdit::Rewrite {
1131 tree: tree.as_deref().map(parse_oid).transpose()?,
1132 message: message.clone(),
1133 author: author.clone(),
1134 parents: None,
1135 },
1136 );
1137 }
1138 }
1139 }
1140 Ok(edits)
1141}
1142
1143fn mapping_to_persist(map: &HashMap<ObjectId, ObjectId>) -> HashMap<String, String> {
1144 map.iter().map(|(k, v)| (oid_hex(k), oid_hex(v))).collect()
1145}
1146
1147fn mapping_from_persist(
1148 map: &HashMap<String, String>,
1149) -> Result<HashMap<ObjectId, ObjectId>, RebaseSessionError> {
1150 map.iter()
1151 .map(|(k, v)| Ok((parse_oid(k)?, parse_oid(v)?)))
1152 .collect()
1153}
1154
1155fn replay_order_for_resume(state: &LoadedState, current: ObjectId) -> Vec<ObjectId> {
1157 if let Some(pos) = state.replay_order.iter().position(|c| *c == current) {
1158 return state.replay_order[pos..].to_vec();
1159 }
1160 let mut order = if state.remaining.is_empty() {
1161 state.replay_order.clone()
1162 } else {
1163 state.remaining.clone()
1164 };
1165 if !order.contains(¤t) {
1166 order.insert(0, current);
1167 } else if order.first() != Some(¤t) {
1168 order.retain(|c| *c != current);
1169 order.insert(0, current);
1170 }
1171 order
1172}
1173
1174#[allow(clippy::too_many_arguments)]
1175fn state_from_partial(
1176 partial: &super::plan::PartialRewritePlan,
1177 head_ref: &str,
1178 orig_head: ObjectId,
1179 request: &RebaseRequest,
1180 rebase: &RebaseOntoOptions,
1181 apply: &ApplyOptions,
1182 effective_onto: ObjectId,
1183 fork: ObjectId,
1184) -> PersistedRebaseState {
1185 let conflict = partial.conflict.commit;
1186 let stop_idx = partial
1187 .commits_to_replay
1188 .iter()
1189 .position(|c| *c == conflict)
1190 .unwrap_or(0);
1191 let done: Vec<ObjectId> = partial.commits_to_replay[..stop_idx]
1192 .iter()
1193 .copied()
1194 .filter(|c| partial.mapping.contains_key(c))
1195 .collect();
1196 let remaining: Vec<ObjectId> = partial.commits_to_replay[stop_idx..].to_vec();
1197 PersistedRebaseState {
1198 mapping: mapping_to_persist(&partial.mapping),
1199 rewritten: partial
1200 .rewritten
1201 .iter()
1202 .map(|r| PersistedRewrittenRow {
1203 old: oid_hex(&r.old),
1204 new: r.new.as_ref().map(oid_hex),
1205 reused: r.reused,
1206 })
1207 .collect(),
1208 edits: edits_to_persist(&partial.edits),
1209 rewrite: partial.opts.clone(),
1210 rebase: rebase.clone(),
1211 apply: apply.clone(),
1212 replay_order: partial.commits_to_replay.iter().map(oid_hex).collect(),
1213 remaining: remaining.iter().map(oid_hex).collect(),
1214 done: done.iter().map(oid_hex).collect(),
1215 fork: oid_hex(&fork),
1216 onto: oid_hex(&effective_onto),
1217 upstream: oid_hex(&request.upstream),
1218 head_ref: head_ref.to_owned(),
1219 orig_head: oid_hex(&orig_head),
1220 branch_tip: oid_hex(&request.branch_tip),
1221 flatten_leaf_merges: partial.linearize_onto.is_some(),
1222 scoped_refs: partial
1223 .scoped_refs
1224 .iter()
1225 .map(|(n, o)| (n.clone(), oid_hex(o)))
1226 .collect(),
1227 stopped_head: None,
1228 stopped_commit: Some(oid_hex(&conflict)),
1229 exec_commands: Vec::new(),
1230 }
1231}
1232
1233struct LoadedState {
1234 mapping: HashMap<ObjectId, ObjectId>,
1235 rewritten: Vec<RewrittenCommit>,
1236 edits: HistoryEdits,
1237 rewrite: RewriteOptions,
1238 rebase: RebaseOntoOptions,
1239 replay_order: Vec<ObjectId>,
1240 remaining: Vec<ObjectId>,
1241 head_ref: String,
1242 orig_head: ObjectId,
1243 onto: ObjectId,
1244 upstream: ObjectId,
1245 branch_tip: ObjectId,
1246 flatten_leaf_merges: bool,
1247 scoped_refs: Vec<(String, ObjectId)>,
1248 stopped_head: Option<ObjectId>,
1249 stopped_commit: Option<ObjectId>,
1250 exec_commands: Vec<String>,
1251}
1252
1253fn load_persisted_state(git_dir: &Path) -> Result<LoadedState, RebaseSessionError> {
1254 match active_rebase(git_dir) {
1255 None => return Err(RebaseSessionError::NoRebaseInProgress),
1256 Some(ActiveRebase::Foreign(_)) => return Err(RebaseSessionError::ForeignRebaseInProgress),
1257 Some(ActiveRebase::Exec) => return Err(RebaseSessionError::NoRebaseInProgress),
1258 Some(ActiveRebase::Grit) => {}
1259 }
1260 let path = git_dir.join("rebase-merge").join("grit-plan");
1261 let raw = fs::read_to_string(&path).map_err(|_| RebaseSessionError::NoRebaseInProgress)?;
1262 let parsed: PersistedRebaseState =
1263 serde_json::from_str(&raw).map_err(|_| RebaseSessionError::NoRebaseInProgress)?;
1264 Ok(LoadedState {
1265 mapping: mapping_from_persist(&parsed.mapping)?,
1266 rewritten: parsed
1267 .rewritten
1268 .iter()
1269 .map(|r| {
1270 Ok(RewrittenCommit {
1271 old: parse_oid(&r.old)?,
1272 new: r.new.as_deref().map(parse_oid).transpose()?,
1273 change_id: None,
1274 subject: None,
1275 reused: r.reused,
1276 })
1277 })
1278 .collect::<Result<_, RebaseSessionError>>()?,
1279 edits: edits_from_persist(&parsed.edits)?,
1280 rewrite: parsed.rewrite,
1281 rebase: parsed.rebase,
1282 replay_order: parsed
1283 .replay_order
1284 .iter()
1285 .map(|h| parse_oid(h))
1286 .collect::<Result<_, _>>()?,
1287 remaining: parsed
1288 .remaining
1289 .iter()
1290 .map(|h| parse_oid(h))
1291 .collect::<Result<_, _>>()?,
1292 head_ref: parsed.head_ref,
1293 orig_head: parse_oid(&parsed.orig_head)?,
1294 onto: parse_oid(&parsed.onto)?,
1295 upstream: parse_oid(&parsed.upstream)?,
1296 branch_tip: parse_oid(&parsed.branch_tip)?,
1297 flatten_leaf_merges: parsed.flatten_leaf_merges,
1298 scoped_refs: parsed
1299 .scoped_refs
1300 .iter()
1301 .map(|(name, hex)| parse_oid(hex).map(|oid| (name.clone(), oid)))
1302 .collect::<Result<_, _>>()?,
1303 stopped_head: parsed.stopped_head.as_deref().map(parse_oid).transpose()?,
1304 stopped_commit: parsed
1305 .stopped_commit
1306 .as_deref()
1307 .map(parse_oid)
1308 .transpose()?,
1309 exec_commands: parsed.exec_commands.clone(),
1310 })
1311}
1312
1313fn write_persisted_state(
1314 git_dir: &Path,
1315 state: &PersistedRebaseState,
1316) -> Result<(), RebaseSessionError> {
1317 let dir = git_dir.join("rebase-merge");
1318 fs::create_dir_all(&dir).map_err(map_lib)?;
1319 let lock_path = dir.join("grit-plan.lock");
1320 let mut lock = LockFile::acquire(&lock_path, LockWait::Immediately)
1321 .map_err(|e| RebaseSessionError::Library(crate::error::Error::Message(e.to_string())))?;
1322 let body = serde_json::to_vec_pretty(state).map_err(map_json)?;
1323 lock.write_all(&body).map_err(map_lib)?;
1324 lock.commit_to(&dir.join("grit-plan"))
1325 .map_err(|e| RebaseSessionError::Library(crate::error::Error::Message(e.to_string())))?;
1326 Ok(())
1327}
1328
1329#[allow(clippy::too_many_arguments)]
1330fn install_stopped_rebase(
1331 repo: &Repository,
1332 head_ref: &str,
1333 orig_head: ObjectId,
1334 request: &RebaseRequest,
1335 rebase_opts: &RebaseOntoOptions,
1336 apply: &ApplyOptions,
1337 partial: super::plan::PartialRewritePlan,
1338 exec_commands: Vec<String>,
1339 progress: &mut dyn ProgressSink,
1340 cancel: &dyn Cancel,
1341) -> Result<RebaseStop, RebaseSessionError> {
1342 poll_cancel(progress, cancel).map_err(map_lib)?;
1343 let effective_onto = request.onto.unwrap_or(request.upstream);
1344 let fork = super::rebase::rebase_fork(repo, request.upstream, request.branch_tip)?;
1345 let mut persisted = state_from_partial(
1346 &partial,
1347 head_ref,
1348 orig_head,
1349 request,
1350 rebase_opts,
1351 apply,
1352 effective_onto,
1353 fork,
1354 );
1355 let conflict = partial.conflict;
1356 persisted.stopped_head = Some(oid_hex(&conflict.replayed_parent));
1357 persisted.exec_commands = exec_commands;
1358 let done = persisted
1359 .done
1360 .iter()
1361 .map(|h| parse_oid(h))
1362 .collect::<Result<Vec<_>, _>>()?;
1363 let remaining = persisted
1364 .remaining
1365 .iter()
1366 .map(|h| parse_oid(h))
1367 .collect::<Result<Vec<_>, _>>()?;
1368 let git_progress = git_visible_rebase_progress(
1369 repo,
1370 fork,
1371 request.branch_tip,
1372 &rebase_opts.rewrite,
1373 &done,
1374 &remaining,
1375 conflict.commit,
1376 )?;
1377 write_git_rebase_merge_files(
1378 repo,
1379 head_ref,
1380 orig_head,
1381 effective_onto,
1382 &git_progress.done,
1383 &git_progress.remaining,
1384 git_progress.stop.as_ref(),
1385 )?;
1386 write_persisted_state(repo.git_dir(), &persisted)?;
1387
1388 repo.odb().flush_for_publish().map_err(map_lib)?;
1389 refs::update_direct_ref_on_store(
1390 repo.refs(),
1391 repo.git_dir(),
1392 "HEAD",
1393 &conflict.replayed_parent,
1394 )
1395 .map_err(map_lib)?;
1396 let mut progress = UncancellableProgress::new(progress);
1399 reset_index_worktree_to_head(repo, conflict.replayed_parent, &mut progress, &NeverCancel)?;
1400 let ours_tree = commit_tree(repo, conflict.replayed_parent)?;
1401 apply_conflict_merge_worktree(
1402 repo,
1403 &ours_tree,
1404 &conflict.merge,
1405 &mut progress,
1406 &NeverCancel,
1407 )
1408 .map_err(map_lib)?;
1409
1410 let message = load_commit(repo, conflict.commit)?.message;
1411 fs::write(repo.git_dir().join("MERGE_MSG"), &message).map_err(map_lib)?;
1412
1413 let rerere = crate::rerere::repo_rerere(repo, crate::rerere::RerereAutoupdate::FromConfig)
1414 .map_err(map_lib)?;
1415
1416 Ok(RebaseStop {
1417 commit: conflict.commit,
1418 conflicts: conflicts_from_tree_merge(&conflict.merge),
1419 done,
1420 remaining,
1421 rerere,
1422 })
1423}
1424
1425#[allow(clippy::too_many_arguments)]
1426fn advance_after_plan(
1427 repo: &Repository,
1428 prev: &LoadedState,
1429 apply: &ApplyOptions,
1430 outcome: ResumablePlanOutcome,
1431 head_before: ObjectId,
1432 rerere: Vec<crate::rerere::RerereEvent>,
1433 progress: &mut dyn ProgressSink,
1434 cancel: &dyn Cancel,
1435) -> Result<RebaseOutcome, RebaseSessionError> {
1436 poll_cancel(progress, cancel).map_err(map_lib)?;
1437 match outcome {
1438 ResumablePlanOutcome::Complete(mut plan) => {
1439 if !prev.exec_commands.is_empty() {
1440 let spec = super::ExecSpec {
1441 commands: prev.exec_commands.clone(),
1442 };
1443 let runner = repo.command_runner();
1444 super::exec::verify_rebase_with_exec_resume(
1445 repo,
1446 &mut plan,
1447 &spec,
1448 &prev.rewrite,
1449 runner.as_ref(),
1450 )?;
1451 }
1452 let movable_head = rebase_movable_ref_name(&prev.head_ref);
1453 let head_ref_old = if rebase_head_is_branch(&prev.head_ref) {
1454 prev.orig_head
1455 } else {
1456 head_before
1457 };
1458 if !rebase_head_is_branch(&prev.head_ref) {
1459 for update in &mut plan.ref_updates {
1460 if update.name == "HEAD" {
1461 update.old = head_before;
1462 }
1463 }
1464 }
1465 if let Some(new_tip) = plan.mapped_tip(prev.orig_head) {
1466 if !plan.ref_updates.iter().any(|u| u.name == movable_head) {
1467 plan.ref_updates.push(PlannedRefUpdate {
1468 name: movable_head.to_owned(),
1469 old: head_ref_old,
1470 new: new_tip,
1471 });
1472 }
1473 }
1474 let apply_without_hooks = ApplyOptions {
1475 hooks: PostOperationHooks::Skip,
1476 ..apply.clone()
1477 };
1478 let mut applied = apply_rewrite(repo, &plan, &apply_without_hooks, progress, cancel)
1479 .map_err(map_apply_err)?;
1480 let final_head = if rebase_head_is_branch(&prev.head_ref) {
1484 if let Some(branch) = plan
1485 .ref_updates
1486 .iter()
1487 .find(|u| u.name == prev.head_ref && u.old != u.new)
1488 {
1489 log_head_move(repo, branch, apply)?;
1490 }
1491 refs::write_symbolic_ref_on_store(
1492 repo.refs(),
1493 repo.git_dir(),
1494 "HEAD",
1495 &prev.head_ref,
1496 )
1497 .map_err(map_lib)?;
1498 repo.resolve_ref_oid(&prev.head_ref).map_err(map_lib)?
1499 } else {
1500 applied.head
1501 };
1502 reset_index_worktree_to_head(repo, final_head, &mut NullProgress, &NeverCancel)?;
1504 applied.head = final_head;
1505 if let Some(operation) = apply.record_operation.as_deref() {
1506 let head_old_tree = commit_tree(repo, prev.orig_head)?;
1507 let head_new_tree = commit_tree(repo, final_head)?;
1508 let record = HistoryLastRecord {
1509 operation: operation.into(),
1510 head_ref: Some(prev.head_ref.clone()),
1511 head_old_commit: Some(prev.orig_head),
1512 head_new_commit: Some(final_head),
1513 head_old_tree: Some(head_old_tree),
1514 head_new_tree: Some(head_new_tree),
1515 ref_updates: plan.ref_updates.clone(),
1516 };
1517 write_history_last(repo, &record, None)?;
1518 }
1519 clear_rebase_state(repo.git_dir())?;
1520 if apply.hooks.enabled()
1521 && run_post_rewrite(repo, RewriteKind::Rebase, &plan.rewritten_pairs())
1522 {
1523 applied.hooks_run.push(HookName::PostRewrite);
1524 }
1525 Ok(RebaseOutcome::Done {
1526 head_change: (head_before != applied.head).then_some((head_before, applied.head)),
1527 applied,
1528 plan,
1529 rerere,
1530 })
1531 }
1532 ResumablePlanOutcome::Partial(partial) => {
1533 let request = RebaseRequest {
1534 upstream: prev.upstream,
1535 onto: Some(prev.onto),
1536 branch_tip: prev.branch_tip,
1537 };
1538 let stop = install_stopped_rebase(
1539 repo,
1540 &prev.head_ref,
1541 prev.orig_head,
1542 &request,
1543 &prev.rebase,
1544 apply,
1545 *partial,
1546 prev.exec_commands.clone(),
1547 progress,
1548 cancel,
1549 )?;
1550 Ok(RebaseOutcome::Stopped(stop))
1551 }
1552 }
1553}
1554
1555struct GitVisibleRebaseProgress {
1556 done: Vec<ObjectId>,
1557 remaining: Vec<ObjectId>,
1558 stop: Option<ObjectId>,
1559}
1560
1561fn git_visible_rebase_progress(
1567 repo: &Repository,
1568 fork: ObjectId,
1569 branch_tip: ObjectId,
1570 rewrite: &RewriteOptions,
1571 done: &[ObjectId],
1572 remaining: &[ObjectId],
1573 conflict: ObjectId,
1574) -> Result<GitVisibleRebaseProgress, RebaseSessionError> {
1575 if matches!(rewrite.scope, RefScope::Head) {
1576 return Ok(GitVisibleRebaseProgress {
1577 done: done.to_vec(),
1578 remaining: remaining.to_vec(),
1579 stop: Some(conflict),
1580 });
1581 }
1582 let primary = commits_in_rebase_range(repo, fork, &[branch_tip])?;
1583 let primary_set: HashSet<ObjectId> = primary.iter().copied().collect();
1584 let git_done: Vec<ObjectId> = done
1585 .iter()
1586 .copied()
1587 .filter(|oid| primary_set.contains(oid))
1588 .collect();
1589 let git_remaining: Vec<ObjectId> = remaining
1590 .iter()
1591 .copied()
1592 .filter(|oid| primary_set.contains(oid))
1593 .collect();
1594 let git_stop = primary_set.contains(&conflict).then_some(conflict);
1595 Ok(GitVisibleRebaseProgress {
1596 done: git_done,
1597 remaining: git_remaining,
1598 stop: git_stop,
1599 })
1600}
1601
1602fn write_git_rebase_merge_files(
1603 repo: &Repository,
1604 head_ref: &str,
1605 orig_head: ObjectId,
1606 onto: ObjectId,
1607 done: &[ObjectId],
1608 remaining: &[ObjectId],
1609 current: Option<&ObjectId>,
1610) -> Result<(), RebaseSessionError> {
1611 let dir = repo.git_dir().join("rebase-merge");
1612 fs::create_dir_all(&dir).map_err(map_lib)?;
1613 fs::write(dir.join("head-name"), format!("{head_ref}\n")).map_err(map_lib)?;
1614 fs::write(dir.join("onto"), format!("{}\n", onto.to_hex())).map_err(map_lib)?;
1615 fs::write(dir.join("orig-head"), format!("{}\n", orig_head.to_hex())).map_err(map_lib)?;
1616 let todo_remaining: Vec<ObjectId> = match current {
1617 Some(current) if remaining.first() == Some(current) => {
1618 remaining.iter().skip(1).copied().collect()
1619 }
1620 _ => remaining.to_vec(),
1621 };
1622 fs::write(
1623 dir.join("git-rebase-todo"),
1624 todo_lines(repo, &todo_remaining)?,
1625 )
1626 .map_err(map_lib)?;
1627 let done_with_current: Vec<ObjectId> = match current {
1630 Some(current) => done.iter().chain([current]).copied().collect(),
1631 None => done.to_vec(),
1632 };
1633 fs::write(dir.join("done"), todo_lines(repo, &done_with_current)?).map_err(map_lib)?;
1634 let msgnum = done_with_current.len();
1635 let end = done_with_current.len() + todo_remaining.len();
1636 fs::write(dir.join("msgnum"), format!("{msgnum}\n")).map_err(map_lib)?;
1637 fs::write(dir.join("end"), format!("{end}\n")).map_err(map_lib)?;
1638 if let Some(current) = current {
1639 fs::write(dir.join("stopped-sha"), format!("{}\n", current.to_hex())).map_err(map_lib)?;
1640 let commit = load_commit(repo, *current)?;
1641 match author_script(&commit.author) {
1642 Some(script) => fs::write(dir.join("author-script"), script).map_err(map_lib)?,
1643 None => remove_if_exists(&dir.join("author-script"))?,
1644 }
1645 fs::write(dir.join("message"), commit.message).map_err(map_lib)?;
1646 } else {
1647 remove_if_exists(&dir.join("stopped-sha"))?;
1648 remove_if_exists(&dir.join("author-script"))?;
1649 remove_if_exists(&dir.join("message"))?;
1650 }
1651 Ok(())
1652}
1653
1654fn todo_lines(repo: &Repository, commits: &[ObjectId]) -> Result<String, RebaseSessionError> {
1655 let mut out = String::new();
1656 for &oid in commits {
1657 let subject = super::subject::commit_message_subject_line(&load_commit(repo, oid)?.message);
1658 let abbrev = oid.to_short_hex();
1659 out.push_str(&format!("pick {abbrev} {subject}\n"));
1660 }
1661 Ok(out)
1662}
1663
1664fn author_script(author: &str) -> Option<String> {
1668 let lt = author.find('<')?;
1669 let gt = lt + author[lt..].find('>')?;
1670 let name = author[..lt].trim_end();
1671 let email = &author[lt + 1..gt];
1672 let date = format!("@{}", author[gt + 1..].trim());
1673 let quote = crate::transport::sq_quote_shell_arg;
1674 Some(format!(
1675 "GIT_AUTHOR_NAME={}\nGIT_AUTHOR_EMAIL={}\nGIT_AUTHOR_DATE={}\n",
1676 quote(name),
1677 quote(email),
1678 quote(&date)
1679 ))
1680}
1681
1682pub(super) fn clear_rebase_state(git_dir: &Path) -> Result<(), RebaseSessionError> {
1683 remove_if_exists(&git_dir.join("MERGE_MSG"))?;
1684 let dir = git_dir.join("rebase-merge");
1685 if dir.is_dir() {
1686 fs::remove_dir_all(&dir).map_err(map_lib)?;
1687 }
1688 Ok(())
1689}
1690
1691const REBASE_DETACHED_HEAD_NAME: &str = "detached HEAD";
1692
1693fn rebase_head_is_branch(head_ref: &str) -> bool {
1694 head_ref.starts_with("refs/")
1695}
1696
1697fn rebase_movable_ref_name(head_ref: &str) -> &str {
1701 if rebase_head_is_branch(head_ref) {
1702 head_ref
1703 } else {
1704 "HEAD"
1705 }
1706}
1707
1708fn branch_head(repo: &Repository) -> Result<(String, ObjectId), RebaseSessionError> {
1709 match resolve_head_for_repository(repo).map_err(map_lib)? {
1710 HeadState::Branch {
1711 refname,
1712 oid: Some(oid),
1713 ..
1714 } => Ok((refname, oid)),
1715 HeadState::Detached { oid } => Ok((REBASE_DETACHED_HEAD_NAME.to_owned(), oid)),
1716 HeadState::Branch { oid: None, .. } | HeadState::Invalid => {
1717 Err(RebaseSessionError::NotOnBranch)
1718 }
1719 }
1720}
1721
1722fn resolve_head_oid(repo: &Repository) -> Result<ObjectId, RebaseSessionError> {
1723 match resolve_head_for_repository(repo).map_err(map_lib)? {
1724 HeadState::Branch { oid: Some(oid), .. } | HeadState::Detached { oid } => Ok(oid),
1725 HeadState::Branch { oid: None, .. } | HeadState::Invalid => {
1726 Err(RebaseSessionError::NotOnBranch)
1727 }
1728 }
1729}
1730
1731fn reset_index_worktree_to_head(
1732 repo: &Repository,
1733 head_oid: ObjectId,
1734 progress: &mut dyn ProgressSink,
1735 cancel: &dyn Cancel,
1736) -> Result<(), RebaseSessionError> {
1737 let head_tree = commit_tree(repo, head_oid)?;
1738 checkout_hard_to_tree_from_head_tree(repo, &head_tree, &head_tree, progress, cancel)
1741 .map_err(map_lib)?;
1742 Ok(())
1743}
1744
1745fn hard_reset_branch(
1748 repo: &Repository,
1749 refname: &str,
1750 target: ObjectId,
1751 identity: &crate::ident::Signature,
1752) -> Result<RebaseAbort, RebaseSessionError> {
1753 let before = repo
1754 .references()
1755 .resolve_if_exists(refname)
1756 .map_err(map_lib)?;
1757 refs::write_symbolic_ref_on_store(repo.refs(), repo.git_dir(), "HEAD", refname)
1758 .map_err(map_lib)?;
1759 hard_reset_head(repo, target, identity)?;
1760 let overwritten = (before != Some(target)).then(|| PlannedRefUpdate {
1761 name: refname.to_owned(),
1762 old: before.unwrap_or_else(crate::diff::zero_oid),
1763 new: target,
1764 });
1765 Ok(RebaseAbort {
1766 overwritten: overwritten.into_iter().collect(),
1767 })
1768}
1769
1770fn hard_reset_head(
1771 repo: &Repository,
1772 target: ObjectId,
1773 identity: &crate::ident::Signature,
1774) -> Result<(), RebaseSessionError> {
1775 reset_to_commit(
1776 repo,
1777 &ResetOptions::new(target, ResetMode::Hard, identity.clone(), "rebase (abort)"),
1778 &mut crate::progress::NullProgress,
1779 &crate::progress::NeverCancel,
1780 )
1781 .map_err(map_lib)?;
1782 Ok(())
1783}
1784
1785use std::io::Write;
1786
1787fn map_lib<E: Into<crate::error::Error>>(err: E) -> RebaseSessionError {
1788 RebaseSessionError::Library(err.into())
1789}
1790
1791fn map_apply_err(err: HistoryError) -> RebaseSessionError {
1792 if err.is_cancelled() {
1793 RebaseSessionError::Library(crate::error::Error::Cancelled)
1794 } else {
1795 err.into()
1796 }
1797}
1798
1799fn replan_rewrite_options(state: &LoadedState) -> RewriteOptions {
1800 state.rewrite.clone()
1801}
1802
1803fn map_json(err: serde_json::Error) -> RebaseSessionError {
1804 RebaseSessionError::Library(crate::error::Error::Message(err.to_string()))
1805}
1806
1807#[cfg(test)]
1808mod tests {
1809 use super::{
1810 author_script, edits_from_persist, edits_to_persist, parse_oid, PersistedEdit,
1811 RebaseSessionError,
1812 };
1813 use crate::history::types::{CommitEdit, HistoryEdits};
1814 use crate::objects::ObjectId;
1815
1816 fn oid(byte: u8) -> ObjectId {
1817 ObjectId::from_bytes(&[byte; 20]).unwrap_or_else(|_| ObjectId::zero())
1818 }
1819
1820 #[test]
1821 fn reuse_edit_round_trips_its_continuation_parent() {
1822 let mut edits = HistoryEdits::new();
1823 edits.insert(
1824 oid(1),
1825 CommitEdit::Reuse {
1826 existing: oid(2),
1827 parent: oid(3),
1828 },
1829 );
1830 let rows = edits_to_persist(&edits);
1831 let json = serde_json::to_string(&rows).unwrap_or_default();
1832 let parsed: Vec<PersistedEdit> = serde_json::from_str(&json).unwrap_or_default();
1833 assert_eq!(edits_from_persist(&parsed).ok(), Some(edits));
1834 }
1835
1836 #[test]
1837 fn reuse_edit_saved_without_parent_continues_from_the_reused_commit() {
1838 let json = format!(
1839 r#"[{{"Reuse":{{"commit":"{}","existing":"{}"}}}}]"#,
1840 oid(1).to_hex(),
1841 oid(2).to_hex()
1842 );
1843 let parsed: Vec<PersistedEdit> = serde_json::from_str(&json).unwrap_or_default();
1844 let edits = edits_from_persist(&parsed).ok();
1845 assert_eq!(
1846 edits.and_then(|edits| edits.get(&oid(1)).cloned()),
1847 Some(CommitEdit::Reuse {
1848 existing: oid(2),
1849 parent: oid(2),
1850 })
1851 );
1852 }
1853
1854 #[test]
1855 fn parse_oid_reports_the_bad_value() {
1856 assert_eq!(parse_oid(&oid(7).to_hex()).ok(), Some(oid(7)));
1857 let err = parse_oid("abc").err();
1858 assert!(
1859 matches!(&err, Some(RebaseSessionError::InvalidStateOid { value }) if value == "abc"),
1860 "{err:?}"
1861 );
1862 }
1863
1864 #[test]
1865 fn persisted_edit_with_bad_oid_is_rejected() {
1866 let rows = vec![PersistedEdit::Drop {
1867 commit: "zz".into(),
1868 }];
1869 assert!(matches!(
1870 edits_from_persist(&rows),
1871 Err(RebaseSessionError::InvalidStateOid { value }) if value == "zz"
1872 ));
1873 }
1874
1875 #[test]
1876 fn author_script_quotes_values_and_prefixes_date() {
1877 assert_eq!(
1878 author_script("A U Thor <a@example.com> 1700000000 -0130").as_deref(),
1879 Some(
1880 "GIT_AUTHOR_NAME='A U Thor'\nGIT_AUTHOR_EMAIL='a@example.com'\nGIT_AUTHOR_DATE='@1700000000 -0130'\n"
1881 )
1882 );
1883 assert_eq!(
1884 author_script("O'Brien <o@example.com> 1 +0000").as_deref(),
1885 Some(
1886 "GIT_AUTHOR_NAME='O'\\''Brien'\nGIT_AUTHOR_EMAIL='o@example.com'\nGIT_AUTHOR_DATE='@1 +0000'\n"
1887 )
1888 );
1889 assert_eq!(author_script("no email here 1 +0000"), None);
1890 }
1891}