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