git_stack/legacy/graph/
mod.rs1mod 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 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(¤t_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 break;
157 }
158 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}