git_stack/legacy/graph/
mod.rs

1mod actions;
2mod node;
3mod ops;
4
5pub use actions::*;
6pub use node::*;
7pub use ops::*;
8
9use std::collections::BTreeMap;
10use std::collections::VecDeque;
11use std::collections::btree_map::Entry;
12
13#[derive(Clone, Debug)]
14pub struct Graph {
15    root_id: git2::Oid,
16    nodes: BTreeMap<git2::Oid, Node>,
17}
18
19impl Graph {
20    pub fn new(node: Node) -> Self {
21        let root_id = node.commit.id;
22        let mut nodes = BTreeMap::new();
23        nodes.insert(root_id, node);
24        Self { root_id, nodes }
25    }
26
27    pub fn from_branches(
28        repo: &dyn crate::legacy::git::Repo,
29        mut branches: crate::legacy::git::Branches,
30    ) -> eyre::Result<Self> {
31        if branches.is_empty() {
32            eyre::bail!("No branches to graph");
33        }
34
35        let mut branch_ids: Vec<_> = branches.oids().collect();
36        // Be more reproducible to make it easier to debug
37        branch_ids.sort_by_key(|id| {
38            let first_branch = &branches.get(*id).unwrap()[0];
39            (first_branch.remote.as_deref(), first_branch.name.as_str())
40        });
41
42        let branch_id = branch_ids.remove(0);
43        let branch_commit = repo.find_commit(branch_id).unwrap();
44        let root = Node::new(branch_commit).with_branches(&mut branches);
45        let mut graph = Self::new(root);
46
47        for branch_id in branch_ids {
48            let branch_commit = repo.find_commit(branch_id).unwrap();
49            let node = Node::new(branch_commit).with_branches(&mut branches);
50            graph.insert(repo, node)?;
51        }
52
53        Ok(graph)
54    }
55
56    pub fn insert(&mut self, repo: &dyn crate::legacy::git::Repo, node: Node) -> eyre::Result<()> {
57        let node_id = node.commit.id;
58        if let Some(local) = self.get_mut(node_id) {
59            local.update(node);
60        } else {
61            let merge_base_id = repo
62                .merge_base(self.root_id, node_id)
63                .ok_or_else(|| eyre::eyre!("Could not find merge base"))?;
64            if merge_base_id != self.root_id {
65                let root_action = self.root().action;
66                self.populate(repo, merge_base_id, self.root_id, root_action)?;
67                self.root_id = merge_base_id;
68            }
69            if merge_base_id != node_id {
70                self.populate(repo, merge_base_id, node_id, node.action)?;
71            }
72            self.get_mut(node_id)
73                .expect("populate added node_id")
74                .update(node);
75        }
76        Ok(())
77    }
78
79    pub fn extend(&mut self, repo: &dyn crate::legacy::git::Repo, other: Self) -> eyre::Result<()> {
80        if self.get(other.root_id).is_none() {
81            self.insert(repo, other.root().clone())?;
82        }
83        for node in other.nodes.into_values() {
84            match self.nodes.entry(node.commit.id) {
85                Entry::Occupied(mut o) => o.get_mut().update(node),
86                Entry::Vacant(v) => {
87                    v.insert(node);
88                }
89            }
90        }
91
92        Ok(())
93    }
94
95    pub fn remove_child(&mut self, parent_id: git2::Oid, child_id: git2::Oid) -> Option<Self> {
96        let parent = self.get_mut(parent_id)?;
97        if !parent.children.remove(&child_id) {
98            return None;
99        }
100
101        let child = self.nodes.remove(&child_id)?;
102        let mut node_queue = VecDeque::new();
103        node_queue.extend(child.children.iter().copied());
104        let mut removed = Self::new(child);
105        while let Some(current_id) = node_queue.pop_front() {
106            let current = self.nodes.remove(&current_id).expect("all children exist");
107            node_queue.extend(current.children.iter().copied());
108            removed.nodes.insert(current_id, current);
109        }
110
111        Some(removed)
112    }
113
114    pub fn root(&self) -> &Node {
115        self.nodes.get(&self.root_id).expect("root always exists")
116    }
117
118    pub fn root_id(&self) -> git2::Oid {
119        self.root_id
120    }
121
122    pub fn get(&self, id: git2::Oid) -> Option<&Node> {
123        self.nodes.get(&id)
124    }
125
126    pub fn get_mut(&mut self, id: git2::Oid) -> Option<&mut Node> {
127        self.nodes.get_mut(&id)
128    }
129
130    pub fn breadth_first_iter(&self) -> BreadthFirstIter<'_> {
131        BreadthFirstIter::new(self, self.root_id())
132    }
133
134    fn populate(
135        &mut self,
136        repo: &dyn crate::legacy::git::Repo,
137        base_oid: git2::Oid,
138        head_oid: git2::Oid,
139        default_action: Action,
140    ) -> Result<(), git2::Error> {
141        log::trace!("Populating data for {}..{}", base_oid, head_oid);
142        debug_assert_eq!(
143            repo.merge_base(base_oid, head_oid),
144            Some(base_oid),
145            "HEAD must be a descendant of base"
146        );
147
148        let mut child_id = None;
149        for commit_id in crate::legacy::git::commit_range(repo, head_oid..=base_oid)? {
150            match self.nodes.entry(commit_id) {
151                Entry::Occupied(mut o) => {
152                    let current = o.get_mut();
153                    if let Some(child_id) = child_id {
154                        current.children.insert(child_id);
155                        // Tapped into previous entries, don't bother going further
156                        break;
157                    }
158                    // `head_oid` might already exist but none of its parents, so keep going
159                    child_id = Some(current.commit.id);
160                }
161                Entry::Vacant(v) => {
162                    let commit = repo
163                        .find_commit(commit_id)
164                        .expect("commit_range always returns valid ids");
165                    let current = v.insert(Node::new(commit));
166                    current.action = default_action;
167                    if let Some(child_id) = child_id {
168                        current.children.insert(child_id);
169                    }
170
171                    child_id = Some(current.commit.id);
172                }
173            }
174        }
175
176        Ok(())
177    }
178}
179
180pub struct BreadthFirstIter<'g> {
181    graph: &'g Graph,
182    node_queue: VecDeque<git2::Oid>,
183}
184
185impl<'g> BreadthFirstIter<'g> {
186    pub fn new(graph: &'g Graph, root_id: git2::Oid) -> Self {
187        let mut node_queue = VecDeque::new();
188        if graph.nodes.contains_key(&root_id) {
189            node_queue.push_back(root_id);
190        }
191        Self { graph, node_queue }
192    }
193}
194
195impl<'g> Iterator for BreadthFirstIter<'g> {
196    type Item = &'g Node;
197    fn next(&mut self) -> Option<Self::Item> {
198        let next_id = self.node_queue.pop_front()?;
199        let next = self.graph.get(next_id)?;
200        self.node_queue.extend(next.children.iter().copied());
201        Some(next)
202    }
203}