gix_revision/merge_base/
function.rs1use std::cmp::Ordering;
2
3use gix_hash::ObjectId;
4use gix_revwalk::graph;
5
6use super::Error;
7use crate::{merge_base::Flags, Graph, PriorityQueue};
8
9pub fn merge_base(
26 first: ObjectId,
27 others: &[ObjectId],
28 graph: &mut Graph<'_, '_, graph::Commit<Flags>>,
29) -> Result<Option<Vec<ObjectId>>, Error> {
30 let _span = gix_trace::coarse!("gix_revision::merge_base()", ?first, ?others);
31 if others.is_empty() || others.contains(&first) {
32 return Ok(Some(vec![first]));
33 }
34
35 graph.clear_commit_data(|f| *f = Flags::empty());
36 let bases = paint_down_to_common(first, others, graph)?;
37
38 let bases = remove_redundant(&bases, graph)?;
39 Ok((!bases.is_empty()).then_some(bases))
40}
41
42fn remove_redundant(
45 commits: &[(ObjectId, GenThenTime)],
46 graph: &mut Graph<'_, '_, graph::Commit<Flags>>,
47) -> Result<Vec<ObjectId>, Error> {
48 if commits.is_empty() {
49 return Ok(Vec::new());
50 }
51 graph.clear_commit_data(|f| *f = Flags::empty());
52 let _span = gix_trace::detail!("gix_revision::remove_redundant()", num_commits = %commits.len());
53 let sorted_commits = {
54 let mut v = commits.to_vec();
55 v.sort_by(|a, b| a.1.cmp(&b.1));
56 v
57 };
58 let mut min_gen_pos = 0;
59 let mut min_gen = sorted_commits[min_gen_pos].1.generation;
60
61 let mut walk_start = Vec::with_capacity(commits.len());
62 for (id, _) in commits {
63 let commit = graph.get_mut(id).expect("previously added");
64 commit.data |= Flags::RESULT;
65 for parent_id in commit.parents.clone() {
66 graph.get_or_insert_full_commit(parent_id, |parent| {
67 if !parent.data.contains(Flags::STALE) {
69 parent.data |= Flags::STALE;
70 walk_start.push((parent_id, GenThenTime::from(&*parent)));
71 }
72 })?;
73 }
74 }
75 walk_start.sort_by(|a, b| a.0.cmp(&b.0));
76 walk_start
78 .iter_mut()
79 .for_each(|(id, _)| graph.get_mut(id).expect("added previously").data.remove(Flags::STALE));
80 let mut count_still_independent = commits.len();
81
82 let mut stack = Vec::new();
83 while let Some((commit_id, commit_info)) = walk_start.pop().filter(|_| count_still_independent > 1) {
84 stack.clear();
85 graph.get_mut(&commit_id).expect("added").data |= Flags::STALE;
86 stack.push((commit_id, commit_info));
87
88 while let Some((commit_id, commit_info)) = stack.last().copied() {
89 let commit = graph.get_mut(&commit_id).expect("all commits have been added");
90 let commit_parents = commit.parents.clone();
91 if commit.data.contains(Flags::RESULT) {
92 commit.data.remove(Flags::RESULT);
93 count_still_independent -= 1;
94 if count_still_independent <= 1 {
95 break;
96 }
97 if *commit_id == *sorted_commits[min_gen_pos].0 {
98 while min_gen_pos < commits.len() - 1
99 && graph
100 .get(&sorted_commits[min_gen_pos].0)
101 .expect("already added")
102 .data
103 .contains(Flags::STALE)
104 {
105 min_gen_pos += 1;
106 }
107 min_gen = sorted_commits[min_gen_pos].1.generation;
108 }
109 }
110
111 if commit_info.generation < min_gen {
112 stack.pop();
113 continue;
114 }
115
116 let previous_len = stack.len();
117 for parent_id in &commit_parents {
118 if graph
119 .get_or_insert_full_commit(*parent_id, |parent| {
120 if !parent.data.contains(Flags::STALE) {
121 parent.data |= Flags::STALE;
122 stack.push((*parent_id, GenThenTime::from(&*parent)));
123 }
124 })?
125 .is_some()
126 {
127 break;
128 }
129 }
130
131 if previous_len == stack.len() {
132 stack.pop();
133 }
134 }
135 }
136
137 Ok(commits
138 .iter()
139 .filter_map(|(id, _info)| {
140 graph
141 .get(id)
142 .filter(|commit| !commit.data.contains(Flags::STALE))
143 .map(|_| *id)
144 })
145 .collect())
146}
147
148fn paint_down_to_common(
149 first: ObjectId,
150 others: &[ObjectId],
151 graph: &mut Graph<'_, '_, graph::Commit<Flags>>,
152) -> Result<Vec<(ObjectId, GenThenTime)>, Error> {
153 let mut queue = PriorityQueue::<GenThenTime, ObjectId>::new();
154 graph.get_or_insert_full_commit(first, |commit| {
155 commit.data |= Flags::COMMIT1;
156 queue.insert(GenThenTime::from(&*commit), first);
157 })?;
158
159 for other in others {
160 graph.get_or_insert_full_commit(*other, |commit| {
161 commit.data |= Flags::COMMIT2;
162 queue.insert(GenThenTime::from(&*commit), *other);
163 })?;
164 }
165
166 let mut out = Vec::new();
167 while queue
168 .iter_unordered()
169 .any(|id| graph.get(id).is_some_and(|commit| !commit.data.contains(Flags::STALE)))
170 {
171 let (info, commit_id) = queue.pop().expect("we have non-stale");
172 let commit = graph.get_mut(&commit_id).expect("everything queued is in graph");
173 let mut flags_without_result = commit.data & (Flags::COMMIT1 | Flags::COMMIT2 | Flags::STALE);
174 if flags_without_result == (Flags::COMMIT1 | Flags::COMMIT2) {
175 if !commit.data.contains(Flags::RESULT) {
176 commit.data |= Flags::RESULT;
177 out.push((commit_id, info));
178 }
179 flags_without_result |= Flags::STALE;
180 }
181
182 for parent_id in commit.parents.clone() {
183 graph.get_or_insert_full_commit(parent_id, |parent| {
184 if (parent.data & flags_without_result) != flags_without_result {
185 parent.data |= flags_without_result;
186 queue.insert(GenThenTime::from(&*parent), parent_id);
187 }
188 })?;
189 }
190 }
191
192 Ok(out)
193}
194
195#[derive(Debug, Clone, Copy)]
197struct GenThenTime {
198 generation: gix_revwalk::graph::Generation,
201 time: gix_date::SecondsSinceUnixEpoch,
202}
203
204impl From<&graph::Commit<Flags>> for GenThenTime {
205 fn from(commit: &graph::Commit<Flags>) -> Self {
206 GenThenTime {
207 generation: commit.generation.unwrap_or(gix_commitgraph::GENERATION_NUMBER_INFINITY),
208 time: commit.commit_time,
209 }
210 }
211}
212
213impl Eq for GenThenTime {}
214
215impl PartialEq<Self> for GenThenTime {
216 fn eq(&self, other: &Self) -> bool {
217 self.cmp(other).is_eq()
218 }
219}
220
221impl PartialOrd<Self> for GenThenTime {
222 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
223 Some(self.cmp(other))
224 }
225}
226
227impl Ord for GenThenTime {
228 fn cmp(&self, other: &Self) -> Ordering {
229 self.generation.cmp(&other.generation).then(self.time.cmp(&other.time))
230 }
231}