use std::collections::BTreeMap;
use crate::change_id::ChangeId;
use crate::objects::ObjectId;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewCommit {
pub tree: ObjectId,
pub author: String,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommitEdit {
Rewrite {
tree: Option<ObjectId>,
message: Option<String>,
author: Option<String>,
parents: Option<Vec<ObjectId>>,
},
Drop,
InsertBefore(NewCommit),
SplitBefore {
insert: NewCommit,
rest_message: Option<String>,
},
Reparent {
parent: ObjectId,
},
Reuse(ObjectId),
}
pub type Pathspec<'a> = &'a [String];
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SplitOptions {
pub rest_message: Option<String>,
}
pub type HistoryEdits = BTreeMap<ObjectId, CommitEdit>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub enum RefScope {
#[default]
Branches,
Head,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub enum EmptyPolicy {
#[default]
Drop,
Keep,
Abort,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RewriteOptions {
pub scope: RefScope,
pub empty: EmptyPolicy,
pub committer: String,
pub preserve_committer: bool,
pub sign: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RewrittenCommit {
pub old: ObjectId,
pub new: Option<ObjectId>,
pub change_id: Option<ChangeId>,
pub subject: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlannedRefUpdate {
pub name: String,
pub old: ObjectId,
pub new: ObjectId,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct HeadEffect {
pub commit_changes: bool,
pub old_commit: Option<ObjectId>,
pub head_ref: Option<String>,
pub old_tree: Option<ObjectId>,
pub new_tree: Option<ObjectId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RewritePlan {
pub commits: Vec<RewrittenCommit>,
pub ref_updates: Vec<PlannedRefUpdate>,
pub head: HeadEffect,
}
impl RewritePlan {
pub fn subjects(
&self,
repo: &crate::repo::Repository,
) -> Result<Vec<Option<String>>, super::HistoryError> {
self.commits
.iter()
.map(|row| super::subject::rewritten_row_subject(repo, row))
.collect()
}
#[must_use]
pub fn mapped_tip(&self, old_tip: ObjectId) -> Option<ObjectId> {
self.ref_updates
.iter()
.find(|u| u.old == old_tip)
.map(|u| u.new)
}
#[must_use]
pub fn replacement(&self, old: ObjectId) -> Option<ObjectId> {
self.commits
.iter()
.find(|c| c.old == old)
.and_then(|c| c.new)
}
#[must_use]
pub fn head_change(&self) -> Option<(ObjectId, ObjectId)> {
if !self.head.commit_changes {
return None;
}
let old = self.head.old_commit?;
let new = self
.mapped_tip(old)
.or_else(|| self.replacement(old))
.unwrap_or(old);
Some((old, new))
}
#[must_use]
pub fn ref_update_lines(&self) -> Vec<String> {
self.ref_updates
.iter()
.map(|u| format!("update {} {} {}", u.name, u.new.to_hex(), u.old.to_hex()))
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ApplyOptions {
pub identity: String,
pub reflog_message: String,
pub record_operation: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UndoOutcome {
pub ref_updates: Vec<PlannedRefUpdate>,
pub head: ObjectId,
pub head_change: Option<(ObjectId, ObjectId)>,
pub dry_run: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AppliedRewrite {
pub ref_updates: Vec<PlannedRefUpdate>,
pub head: ObjectId,
}
#[cfg(test)]
mod tests {
use super::*;
fn oid(byte: u8) -> ObjectId {
let mut bytes = [0u8; 20];
bytes[19] = byte;
ObjectId::from_bytes(&bytes).expect("oid")
}
fn make_plan(
commits: &[(u8, Option<u8>)],
ref_updates: &[(u8, u8)],
head: Option<u8>,
commit_changes: bool,
) -> RewritePlan {
RewritePlan {
commits: commits
.iter()
.map(|&(old, new)| RewrittenCommit {
old: oid(old),
new: new.map(oid),
change_id: None,
subject: None,
})
.collect(),
ref_updates: ref_updates
.iter()
.map(|&(old, new)| PlannedRefUpdate {
name: "refs/heads/main".into(),
old: oid(old),
new: oid(new),
})
.collect(),
head: HeadEffect {
commit_changes,
old_commit: head.map(oid),
..HeadEffect::default()
},
}
}
#[test]
fn replacement_finds_rewritten_commits_only() {
let plan = make_plan(&[(1, Some(11)), (2, None)], &[], None, false);
assert_eq!(plan.replacement(oid(1)), Some(oid(11)));
assert_eq!(plan.replacement(oid(2)), None);
assert_eq!(plan.replacement(oid(3)), None);
}
#[test]
fn head_change_is_none_when_head_commit_stays() {
let plan = make_plan(&[(1, Some(11))], &[(1, 11)], Some(1), false);
assert_eq!(plan.head_change(), None);
let unborn = make_plan(&[(1, Some(11))], &[], None, true);
assert_eq!(unborn.head_change(), None);
}
#[test]
fn head_change_prefers_ref_update_tip() {
let plan = make_plan(&[(1, Some(11)), (2, Some(12))], &[(2, 13)], Some(2), true);
assert_eq!(plan.head_change(), Some((oid(2), oid(13))));
}
#[test]
fn head_change_falls_back_to_replacement_then_old() {
let detached = make_plan(&[(1, Some(11))], &[], Some(1), true);
assert_eq!(detached.head_change(), Some((oid(1), oid(11))));
let dropped = make_plan(&[(1, None)], &[], Some(1), true);
assert_eq!(dropped.head_change(), Some((oid(1), oid(1))));
}
}