Pick and revert ranges use porcelain::replay_sequence instead of single-commit replay_commit. On conflict, grit writes Git-compatible CHERRY_PICK_HEAD or REVERT_HEAD, MERGE_MSG, ORIG_HEAD, and .git/sequencer/ (todo, head, abort-safety, opts) so git cherry-pick --continue and grit pick --continue can interoperate.
ReplayOptions controls mainline parent selection for merge commits, -x / record_origin, empty-commit policy (Stop, Drop, Keep), merge favor, and conflict marker style. start_replay_sequence returns SequenceOutcome — Completed, Conflicts, or Empty. Every commit created so far is a ReplayCreated carrying its source, new oid, stored change id, and subject (also for steps committed before an earlier stop), and a stop reports the subject of the commit it stopped on, so callers can render results without re-reading commits. Resolve conflicts in the worktree, stage paths, then call continue_replay_sequence. skip_replay_step and abort_replay_sequence match Git's skip/abort semantics (abort refuses when HEAD moved past sequencer/abort-safety).
Conflict markers and unmerged index stages reuse the same tree-merge path as porcelain::merge (apply_conflict_merge_worktree). status surfaces cherry-pick/revert operation guidance via state::status_operation. On the returned model, StatusModel::conflict_groups groups unmerged paths by ConflictKind, and StatusModel::guidance picks the next step grit status suggests: the paused operation's runnable steps, the conflict and merge fallbacks, or everyday StatusActions (discard, stage, commit, pull when strictly behind).
One conflict model
Every API that reports conflicts uses operation::Conflict (a path and a ConflictKind): StatusModel::conflict_details, merge and pull outcomes, SequenceOutcome::Conflicts, a stopped rebase's RebaseStop, rebase predictions and HistoryError::Conflict, and StashApplyOutcome. The kind is read from which unmerged index stages a path has (conflict_kind_from_stagemask, the same classification as Git's porcelain v2 u lines), so an operation's conflicts equal what status reports right after it stops, and a UI needs one renderer. Only a tree-merge result (conflicts_from_tree_merge) can add ConflictKind::RenameRename and the directory rename kinds (DirectoryRenameMoved, DirectoryRenameSplit, DirectoryRenameBlocked), which need information the index does not keep; conflicts_from_index classifies an index alone. ConflictKind::as_str gives the snake_case name grit prints in --json, and with the serde-models feature a Conflict serializes to the same {"path", "kind"} object.
use grit_lib::operation::ConflictKind;
use grit_lib::porcelain::status::{status, StatusOptions};
use grit_lib::progress::{NeverCancel, NullProgress};
let model = status(&repo, &StatusOptions::default(), &mut NullProgress, &NeverCancel)?;
for conflict in &model.conflict_details {
if conflict.kind == ConflictKind::DeletedByUs {
println!("{}: deleted here, changed on the incoming side", conflict.path);
}
}