branchless/core/rewrite/
evolve.rs

1use tracing::instrument;
2
3use crate::core::dag::{CommitSet, Dag};
4use crate::core::eventlog::{Event, EventCursor, EventReplayer};
5use crate::git::{MaybeZeroOid, NonZeroOid};
6
7/// For a rewritten commit, find the newest version of the commit.
8///
9/// For example, if we amend commit `abc` into commit `def1`, and then amend
10/// `def1` into `def2`, then we can traverse the event log to find out that `def2`
11/// is the newest version of `abc`.
12///
13/// If a commit was rewritten into itself through some chain of events, then
14/// returns `None`, rather than the same commit OID.
15#[instrument]
16pub fn find_rewrite_target(
17    event_replayer: &EventReplayer,
18    event_cursor: EventCursor,
19    oid: NonZeroOid,
20) -> Option<MaybeZeroOid> {
21    let event = event_replayer.get_cursor_commit_latest_event(event_cursor, oid);
22    let event = match event {
23        Some(event) => event,
24        None => return None,
25    };
26    match event {
27        Event::RewriteEvent {
28            timestamp: _,
29            event_tx_id: _,
30            old_commit_oid: MaybeZeroOid::NonZero(old_commit_oid),
31            new_commit_oid,
32        } => {
33            if *old_commit_oid == oid && *new_commit_oid != MaybeZeroOid::NonZero(oid) {
34                match new_commit_oid {
35                    MaybeZeroOid::Zero => Some(MaybeZeroOid::Zero),
36                    MaybeZeroOid::NonZero(new_commit_oid) => {
37                        let possible_newer_oid =
38                            find_rewrite_target(event_replayer, event_cursor, *new_commit_oid);
39                        match possible_newer_oid {
40                            Some(newer_commit_oid) => Some(newer_commit_oid),
41                            None => Some(MaybeZeroOid::NonZero(*new_commit_oid)),
42                        }
43                    }
44                }
45            } else {
46                None
47            }
48        }
49
50        Event::RewriteEvent {
51            timestamp: _,
52            event_tx_id: _,
53            old_commit_oid: MaybeZeroOid::Zero,
54            new_commit_oid: _,
55        }
56        | Event::RefUpdateEvent { .. }
57        | Event::CommitEvent { .. }
58        | Event::ObsoleteEvent { .. }
59        | Event::UnobsoleteEvent { .. }
60        | Event::WorkingCopySnapshot { .. } => None,
61    }
62}
63
64/// Find commits which have been "abandoned" in the commit graph.
65///
66/// A commit is considered "abandoned" if it's not obsolete, but one of its
67/// parents is.
68#[instrument]
69pub fn find_abandoned_children(
70    dag: &Dag,
71    event_replayer: &EventReplayer,
72    event_cursor: EventCursor,
73    oid: NonZeroOid,
74) -> eyre::Result<Option<(NonZeroOid, Vec<NonZeroOid>)>> {
75    let rewritten_oid = match find_rewrite_target(event_replayer, event_cursor, oid) {
76        Some(MaybeZeroOid::NonZero(rewritten_oid)) => rewritten_oid,
77        Some(MaybeZeroOid::Zero) => oid,
78        None => return Ok(None),
79    };
80    let children = dag.query_children(CommitSet::from(oid))?;
81    let children = dag.filter_visible_commits(children)?;
82    let non_obsolete_children = children.difference(&dag.query_obsolete_commits());
83    let non_obsolete_children_oids = dag.commit_set_to_vec(&non_obsolete_children)?;
84
85    Ok(Some((rewritten_oid, non_obsolete_children_oids)))
86}