1use std::cmp::{Ordering, Reverse};
9use std::collections::{BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
10use std::fs;
11use std::io::Write;
12use std::path::Path;
13
14use crate::commit_graph_file::{BloomPrecheck, BloomWalkStatsHandle, CommitGraphChain};
15use crate::config::ConfigSet;
16use crate::diff::zero_oid;
17use crate::error::{Error, Result};
18use crate::ident::{committer_unix_seconds_for_ordering, parse_signature_times};
19use crate::ignore::{parse_sparse_patterns_from_blob, path_in_sparse_checkout};
20use crate::index::{CacheTreeNode, Index, MODE_GITLINK, MODE_TREE};
21use crate::objects::{parse_commit, parse_tag, parse_tree, ObjectId, ObjectKind};
22use crate::pack;
23use crate::patch_ids::{compute_patch_id, compute_patch_id_for_paths};
24use crate::ref_exclusions::{git_namespace_prefix, strip_git_namespace, RefExclusions};
25use crate::reflog::{list_reflog_refs, read_reflog};
26use crate::refs;
27use crate::repo::Repository;
28use crate::rev_parse::{resolve_revision_for_range_end, resolve_treeish_path, split_treeish_spec};
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum OutputMode {
33 OidOnly,
35 Parents,
37 Format(String),
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum MissingAction {
44 Error,
46 Print,
48 PrintInfo,
50 Allow,
52}
53
54impl MissingAction {
55 fn reports_missing(self) -> bool {
56 matches!(self, Self::Print | Self::PrintInfo)
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum FilterObjectKind {
63 Blob,
64 Tree,
65 Commit,
66 Tag,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum ObjectFilter {
72 BlobNone,
74 BlobLimit(u64),
76 TreeDepth(u64),
78 SparseOid(String),
80 ObjectType(FilterObjectKind),
82 Combine(Vec<ObjectFilter>),
84}
85
86impl ObjectFilter {
87 pub fn parse(spec: &str) -> std::result::Result<Self, String> {
89 Self::parse_inner(spec.trim(), false)
90 }
91
92 fn parse_inner(spec: &str, from_combine_subfilter: bool) -> std::result::Result<Self, String> {
93 if spec == "blob:none" {
94 return Ok(ObjectFilter::BlobNone);
95 }
96 if let Some(rest) = spec.strip_prefix("blob:limit=") {
97 let bytes = parse_size_suffix(rest)
98 .ok_or_else(|| format!("invalid blob:limit value: {rest}"))?;
99 return Ok(ObjectFilter::BlobLimit(bytes));
100 }
101 if let Some(rest) = spec.strip_prefix("tree:") {
102 if rest.is_empty() || !rest.chars().all(|c| c.is_ascii_digit()) {
103 return Err(if from_combine_subfilter {
104 "expected 'tree:<depth>'.".to_owned()
105 } else {
106 format!("invalid tree depth: {rest}")
107 });
108 }
109 let depth: u64 = rest.parse().map_err(|_| {
110 if from_combine_subfilter {
111 "expected 'tree:<depth>'.".to_owned()
112 } else {
113 format!("invalid tree depth: {rest}")
114 }
115 })?;
116 return Ok(ObjectFilter::TreeDepth(depth));
117 }
118 if let Some(rest) = spec.strip_prefix("object:type=") {
119 let kind = match rest {
120 "blob" => FilterObjectKind::Blob,
121 "tree" => FilterObjectKind::Tree,
122 "commit" => FilterObjectKind::Commit,
123 "tag" => FilterObjectKind::Tag,
124 "" => return Err("invalid object type".to_owned()),
125 _ => return Err(format!("invalid object type: {rest}")),
126 };
127 return Ok(ObjectFilter::ObjectType(kind));
128 }
129 if let Some(rest) = spec.strip_prefix("sparse:oid=") {
130 if rest.is_empty() {
131 return Err("invalid sparse:oid value: ".to_owned());
132 }
133 return Ok(ObjectFilter::SparseOid(rest.to_owned()));
134 }
135 if let Some(rest) = spec.strip_prefix("combine:") {
136 if rest.is_empty() {
137 return Err("expected something after combine:".to_owned());
138 }
139 let parts = split_combine_raw_parts(rest);
140 if parts.is_empty() {
141 return Err("expected something after combine:".to_owned());
142 }
143 let mut filters = Vec::new();
144 for part in parts {
145 filters.push(Self::parse_from_combine_subfilter(part)?);
146 }
147 return Ok(ObjectFilter::Combine(filters));
148 }
149 Err(format!("invalid filter-spec '{spec}'"))
150 }
151
152 fn parse_from_combine_subfilter(encoded: &str) -> std::result::Result<Self, String> {
153 if let Some(ch) = combine_subfilter_has_reserved(encoded) {
154 return Err(format!("must escape char in sub-filter-spec: '{ch}'"));
155 }
156 let decoded = url_decode(encoded);
157 Self::parse_inner(&decoded, true)
158 }
159
160 #[must_use]
162 pub fn merge_with(self, other: Self) -> Self {
163 match (self, other) {
164 (ObjectFilter::Combine(mut a), ObjectFilter::Combine(mut b)) => {
165 a.append(&mut b);
166 ObjectFilter::Combine(a)
167 }
168 (ObjectFilter::Combine(mut a), b) => {
169 a.push(b);
170 ObjectFilter::Combine(a)
171 }
172 (a, ObjectFilter::Combine(mut b)) => {
173 let mut v = vec![a];
174 v.append(&mut b);
175 ObjectFilter::Combine(v)
176 }
177 (a, b) => ObjectFilter::Combine(vec![a, b]),
178 }
179 }
180
181 pub fn includes_blob(&self, size: u64) -> bool {
183 match self {
184 ObjectFilter::BlobNone => false,
185 ObjectFilter::BlobLimit(limit) => size < *limit,
186 ObjectFilter::TreeDepth(_) => true,
189 ObjectFilter::SparseOid(_) => true,
190 ObjectFilter::ObjectType(kind) => *kind == FilterObjectKind::Blob,
191 ObjectFilter::Combine(filters) => filters.iter().all(|f| f.includes_blob(size)),
192 }
193 }
194
195 #[must_use]
200 pub fn includes_blob_under_tree(&self, size: u64, parent_tree_depth: u64) -> bool {
201 match self {
202 ObjectFilter::BlobNone => false,
203 ObjectFilter::BlobLimit(limit) => size < *limit,
204 ObjectFilter::TreeDepth(max_depth) => parent_tree_depth.saturating_add(1) < *max_depth,
205 ObjectFilter::SparseOid(_) => true,
206 ObjectFilter::ObjectType(kind) => *kind == FilterObjectKind::Blob,
207 ObjectFilter::Combine(filters) => filters
208 .iter()
209 .all(|f| f.includes_blob_under_tree(size, parent_tree_depth)),
210 }
211 }
212
213 pub fn includes_tree(&self, depth: u64) -> bool {
215 match self {
216 ObjectFilter::BlobNone => true,
217 ObjectFilter::BlobLimit(_) => true,
218 ObjectFilter::TreeDepth(max_depth) => depth < *max_depth,
219 ObjectFilter::SparseOid(_) => true,
220 ObjectFilter::ObjectType(kind) => *kind == FilterObjectKind::Tree,
221 ObjectFilter::Combine(filters) => filters.iter().all(|f| f.includes_tree(depth)),
222 }
223 }
224
225 pub fn includes_commit_or_tag_object(&self, kind: ObjectKind) -> bool {
227 let expected = match kind {
228 ObjectKind::Commit => Some(FilterObjectKind::Commit),
229 ObjectKind::Tag => Some(FilterObjectKind::Tag),
230 _ => None,
231 };
232 match self {
233 ObjectFilter::BlobNone | ObjectFilter::BlobLimit(_) => true,
234 ObjectFilter::TreeDepth(_) => true,
235 ObjectFilter::SparseOid(_) => true,
236 ObjectFilter::ObjectType(t) => expected == Some(*t),
237 ObjectFilter::Combine(filters) => filters
238 .iter()
239 .all(|f| f.includes_commit_or_tag_object(kind)),
240 }
241 }
242
243 pub fn includes_loose_object(&self, kind: ObjectKind, size: u64) -> bool {
245 match kind {
246 ObjectKind::Blob => self.includes_blob(size),
247 ObjectKind::Tree => self.includes_tree(0),
248 ObjectKind::Commit | ObjectKind::Tag => self.includes_commit_or_tag_object(kind),
249 }
250 }
251
252 #[must_use]
254 pub fn passes_for_object(&self, kind: ObjectKind, size: usize) -> bool {
255 self.includes_loose_object(kind, size as u64)
256 }
257}
258
259#[must_use]
262pub fn reachable_object_ids_for_cat_file(
263 repo: &Repository,
264 filter: Option<&ObjectFilter>,
265 filter_provided_objects: bool,
266) -> Result<Vec<ObjectId>> {
267 let opts = RevListOptions {
268 all_refs: true,
269 objects: true,
270 no_object_names: true,
271 quiet: true,
272 filter: filter.cloned(),
273 filter_provided_objects,
274 ..Default::default()
275 };
276 let result = rev_list(repo, &[], &[], &opts)?;
277 let mut set = BTreeSet::new();
278 for (i, oid) in result.commits.iter().enumerate() {
279 if result.objects_print_commit.get(i).copied().unwrap_or(true) {
280 set.insert(*oid);
281 }
282 }
283 for (oid, _) in &result.objects {
284 set.insert(*oid);
285 }
286 Ok(set.into_iter().collect())
287}
288
289#[must_use]
292pub fn object_ids_for_cat_file_filtered(
293 repo: &Repository,
294 filter: &ObjectFilter,
295) -> Result<Vec<ObjectId>> {
296 reachable_object_ids_for_cat_file(repo, Some(filter), true)
297}
298
299fn parse_size_suffix(s: &str) -> Option<u64> {
301 let s = s.trim();
302 if s.is_empty() {
303 return None;
304 }
305 let (num_str, multiplier) = match s.as_bytes().last()? {
306 b'k' | b'K' => (&s[..s.len() - 1], 1024u64),
307 b'm' | b'M' => (&s[..s.len() - 1], 1024 * 1024),
308 b'g' | b'G' => (&s[..s.len() - 1], 1024 * 1024 * 1024),
309 _ => (s, 1u64),
310 };
311 let num: u64 = num_str.parse().ok()?;
312 Some(num * multiplier)
313}
314
315fn split_combine_raw_parts(spec: &str) -> Vec<&str> {
317 spec.split('+').filter(|s| !s.is_empty()).collect()
318}
319
320fn combine_subfilter_has_reserved(encoded: &str) -> Option<char> {
323 const RESERVED: &str = "~`!@#$^&*()[]{}\\;'\",<>?";
324 for ch in encoded.chars() {
325 if ch.is_control() || ch.is_whitespace() {
326 return Some(ch);
327 }
328 if RESERVED.contains(ch) {
329 return Some(ch);
330 }
331 }
332 None
333}
334
335#[must_use]
337pub fn expand_object_filter_for_protocol(spec: &str) -> std::result::Result<String, String> {
338 let f = ObjectFilter::parse(spec)?;
339 match f {
340 ObjectFilter::BlobLimit(n) => Ok(format!("blob:limit={n}")),
341 _ => Ok(spec.to_owned()),
342 }
343}
344
345fn combine_filter_allow_unencoded(ch: char) -> bool {
346 if ch.is_control() || ch.is_whitespace() || ch == '%' || ch == '+' {
347 return false;
348 }
349 !"~`!@#$^&*()[]{}\\;'\",<>?".contains(ch)
350}
351
352pub fn url_encode_object_filter_subspec(raw: &str) -> String {
354 let mut out = String::new();
355 for b in raw.as_bytes() {
356 let ch = *b as char;
357 if combine_filter_allow_unencoded(ch) {
358 out.push(ch);
359 } else {
360 out.push_str(&format!("%{:02x}", b));
361 }
362 }
363 out
364}
365
366pub fn trace_combine_filter_append(encoded_segment: &str) {
368 let Ok(trace_val) = std::env::var("GIT_TRACE") else {
369 return;
370 };
371 if trace_val.is_empty() || trace_val == "0" || trace_val.eq_ignore_ascii_case("false") {
372 return;
373 }
374 let line = format!("Add to combine filter-spec: {encoded_segment}\n");
375 match trace_val.as_str() {
376 "1" | "true" | "2" => {
377 let _ = std::io::stderr().write_all(line.as_bytes());
378 }
379 path => {
380 if let Ok(mut f) = std::fs::OpenOptions::new()
381 .create(true)
382 .append(true)
383 .open(path)
384 {
385 let _ = f.write_all(line.as_bytes());
386 }
387 }
388 }
389}
390
391fn url_decode(s: &str) -> String {
393 let mut result = String::new();
394 let mut chars = s.chars();
395 while let Some(ch) = chars.next() {
396 if ch == '%' {
397 let hi = chars.next().unwrap_or('0');
398 let lo = chars.next().unwrap_or('0');
399 let byte = u8::from_str_radix(&format!("{hi}{lo}"), 16).unwrap_or(b'?');
400 result.push(byte as char);
401 } else {
402 result.push(ch);
403 }
404 }
405 result
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
410pub enum OrderingMode {
411 Default,
413 DateOrderWalk,
415 AuthorDateWalk,
417 Topo,
419 AuthorDateTopo,
421}
422
423#[derive(Debug, Clone)]
425pub struct RevListOptions {
426 pub all_refs: bool,
428 pub first_parent: bool,
430 pub ancestry_path: bool,
432 pub ancestry_path_bottoms: Vec<ObjectId>,
434 pub simplify_by_decoration: bool,
436 pub output_mode: OutputMode,
438 pub quiet: bool,
440 pub count: bool,
442 pub skip: usize,
444 pub max_count: Option<usize>,
446 pub ordering: OrderingMode,
448 pub reverse: bool,
450 pub maximal_only: bool,
452 pub objects: bool,
454 pub no_object_names: bool,
456 pub boundary: bool,
458 pub left_right: bool,
460 pub left_right_explicit: bool,
462 pub left_only: bool,
464 pub right_only: bool,
466 pub cherry_mark: bool,
468 pub cherry_pick: bool,
470 pub min_parents: Option<usize>,
472 pub max_parents: Option<usize>,
474 pub symmetric_left: Option<ObjectId>,
476 pub symmetric_right: Option<ObjectId>,
478 pub paths: Vec<String>,
480 pub full_history: bool,
482 pub parent_rewrite: bool,
484 pub sparse: bool,
486 pub simplify_merges: bool,
488 pub preserve_simplify_merges_graph_merges: bool,
490 pub show_pulls: bool,
492 pub exclude_first_parent_only: bool,
494 pub filter: Option<ObjectFilter>,
496 pub filter_raw_specs: Vec<String>,
498 pub filter_provided_objects: bool,
500 pub filter_print_omitted: bool,
502 pub in_commit_order: bool,
504 pub no_kept_objects: bool,
506 pub missing_action: MissingAction,
508 pub exclude_promisor_objects: bool,
510 pub ignore_missing: bool,
512 pub use_bitmap_index: bool,
514 pub unpacked_only: bool,
516 pub bitmap_oid_only_objects: bool,
519 pub path_graph_reorder: bool,
522 pub until_cutoff: Option<i64>,
524 pub since_cutoff: Option<i64>,
526 pub include_reflog_entries: bool,
528 pub include_indexed_objects: bool,
530 pub exclude_indexed_objects: bool,
532 pub use_commit_graph_bloom: bool,
534 pub commit_graph_read_changed_paths: bool,
536 pub commit_graph_changed_paths_version: i32,
538 pub bloom_stats: Option<BloomWalkStatsHandle>,
540}
541
542impl Default for RevListOptions {
543 fn default() -> Self {
544 Self {
545 all_refs: false,
546 first_parent: false,
547 ancestry_path: false,
548 ancestry_path_bottoms: Vec::new(),
549 simplify_by_decoration: false,
550 output_mode: OutputMode::OidOnly,
551 quiet: false,
552 count: false,
553 skip: 0,
554 max_count: None,
555 ordering: OrderingMode::Default,
556 reverse: false,
557 maximal_only: false,
558 objects: false,
559 no_object_names: false,
560 boundary: false,
561 left_right: false,
562 left_right_explicit: false,
563 left_only: false,
564 right_only: false,
565 cherry_mark: false,
566 cherry_pick: false,
567 min_parents: None,
568 max_parents: None,
569 symmetric_left: None,
570 symmetric_right: None,
571 paths: Vec::new(),
572 full_history: false,
573 parent_rewrite: false,
574 sparse: false,
575 simplify_merges: false,
576 preserve_simplify_merges_graph_merges: false,
577 show_pulls: false,
578 exclude_first_parent_only: false,
579 filter: None,
580 filter_raw_specs: Vec::new(),
581 filter_provided_objects: false,
582 filter_print_omitted: false,
583 in_commit_order: false,
584 no_kept_objects: false,
585 missing_action: MissingAction::Error,
586 exclude_promisor_objects: false,
587 ignore_missing: false,
588 use_bitmap_index: false,
589 unpacked_only: false,
590 bitmap_oid_only_objects: false,
591 path_graph_reorder: true,
592 until_cutoff: None,
593 since_cutoff: None,
594 include_reflog_entries: false,
595 include_indexed_objects: false,
596 exclude_indexed_objects: false,
597 use_commit_graph_bloom: false,
598 commit_graph_read_changed_paths: true,
599 commit_graph_changed_paths_version: -1,
600 bloom_stats: None,
601 }
602 }
603}
604
605#[derive(Debug, Clone)]
607pub struct RevListResult {
608 pub commits: Vec<ObjectId>,
610 pub objects: Vec<(ObjectId, String)>,
613 pub omitted_objects: Vec<ObjectId>,
615 pub missing_objects: Vec<ObjectId>,
617 pub boundary_commits: Vec<ObjectId>,
619 pub left_right_map: HashMap<ObjectId, bool>,
621 pub cherry_equivalent: HashSet<ObjectId>,
623 pub per_commit_object_counts: Vec<usize>,
627 pub object_walk_tips: Vec<ObjectId>,
629 pub objects_print_commit: Vec<bool>,
632 pub object_segments: Vec<Vec<(ObjectId, String)>>,
635 pub bitmap_object_format: bool,
637 pub tip_annotated_tag_by_commit: HashMap<ObjectId, ObjectId>,
639}
640
641#[derive(Debug, Clone, PartialEq, Eq)]
643pub struct BisectEntry {
644 pub oid: ObjectId,
646 pub reaches: usize,
648 pub distance: usize,
650}
651
652#[derive(Debug, Clone, PartialEq, Eq)]
654pub struct BisectSelection {
655 pub best: Option<BisectEntry>,
657 pub all: Vec<BisectEntry>,
659 pub total: usize,
661}
662
663pub fn select_bisect_commits(
679 repo: &Repository,
680 commits: &[ObjectId],
681 first_parent: bool,
682) -> Result<BisectSelection> {
683 let total = commits.len();
684 if total == 0 {
685 return Ok(BisectSelection {
686 best: None,
687 all: Vec::new(),
688 total: 0,
689 });
690 }
691
692 let candidates: HashSet<ObjectId> = commits.iter().copied().collect();
693 let mut graph = CommitGraph::new(repo, first_parent);
694 let mut entries = Vec::with_capacity(commits.len());
695 for &oid in commits {
696 let reaches = count_reachable_candidates(&mut graph, oid, &candidates)?;
697 let distance = reaches.min(total.saturating_sub(reaches));
698 entries.push(BisectEntry {
699 oid,
700 reaches,
701 distance,
702 });
703 }
704
705 let mut sorted = entries.clone();
706 sorted.sort_by(|a, b| b.distance.cmp(&a.distance).then_with(|| a.oid.cmp(&b.oid)));
707
708 let mut best = None;
709 for entry in &entries {
710 if best
711 .as_ref()
712 .is_none_or(|current: &BisectEntry| entry.distance > current.distance)
713 {
714 best = Some(entry.clone());
715 }
716 }
717 Ok(BisectSelection {
718 best,
719 all: sorted,
720 total,
721 })
722}
723
724fn count_reachable_candidates(
725 graph: &mut CommitGraph<'_>,
726 oid: ObjectId,
727 candidates: &HashSet<ObjectId>,
728) -> Result<usize> {
729 let mut stack = vec![oid];
730 let mut seen = HashSet::new();
731 while let Some(current) = stack.pop() {
732 if !seen.insert(current) {
733 continue;
734 }
735 for parent in graph.parents_of(current)? {
736 if candidates.contains(&parent) {
737 stack.push(parent);
738 }
739 }
740 }
741 Ok(seen.len())
742}
743
744fn retain_maximal_commits(
745 graph: &mut CommitGraph<'_>,
746 candidates: &mut HashSet<ObjectId>,
747) -> Result<()> {
748 if candidates.len() < 2 {
749 return Ok(());
750 }
751
752 let candidate_list: Vec<ObjectId> = candidates.iter().copied().collect();
753 let candidate_set: HashSet<ObjectId> = candidate_list.iter().copied().collect();
754 let mut reachable_from_another = HashSet::new();
755
756 for tip in candidate_list {
757 for reachable in walk_closure(graph, &[tip])? {
758 if reachable != tip && candidate_set.contains(&reachable) {
759 reachable_from_another.insert(reachable);
760 }
761 }
762 }
763
764 candidates.retain(|oid| !reachable_from_another.contains(oid));
765 Ok(())
766}
767
768pub fn rev_list(
782 repo: &Repository,
783 positive_specs: &[String],
784 negative_specs: &[String],
785 options: &RevListOptions,
786) -> Result<RevListResult> {
787 let mut graph = CommitGraph::new(repo, options.first_parent);
788
789 let (mut include, object_roots, tip_annotated_tag_by_commit) = if options.objects {
790 resolve_specs_for_objects_with_options(
791 repo,
792 positive_specs,
793 options.ignore_missing,
794 options.missing_action,
795 )?
796 } else {
797 (
798 resolve_specs_with_options(repo, positive_specs, options.ignore_missing)?,
799 Vec::new(),
800 HashMap::new(),
801 )
802 };
803 let (exclude, negative_object_roots) = if options.objects {
804 let (commits, roots, _) = resolve_specs_for_objects_with_options(
805 repo,
806 negative_specs,
807 options.ignore_missing,
808 options.missing_action,
809 )?;
810 (commits, roots)
811 } else {
812 (
813 resolve_specs_with_options(repo, negative_specs, options.ignore_missing)?,
814 Vec::new(),
815 )
816 };
817
818 if options.all_refs {
819 include.extend(all_ref_tips(repo, &RefExclusions::default())?);
820 }
821
822 if options.objects && options.include_reflog_entries {
823 include.extend(reflog_commit_tips(repo)?);
824 }
825
826 let index_object_roots = if options.objects
827 && (options.include_indexed_objects || options.exclude_indexed_objects)
828 {
829 indexed_object_roots(repo)?
830 } else {
831 Vec::new()
832 };
833
834 let object_roots = if !options.include_indexed_objects || index_object_roots.is_empty() {
835 object_roots
836 } else {
837 let mut merged = object_roots;
838 merged.extend(index_object_roots.iter().cloned());
839 merged
840 };
841 let negative_object_roots = if options.exclude_indexed_objects && !index_object_roots.is_empty()
842 {
843 let mut merged = negative_object_roots;
844 merged.extend(index_object_roots);
845 merged
846 } else {
847 negative_object_roots
848 };
849
850 if include.is_empty() && object_roots.is_empty() {
851 if options.ignore_missing {
852 return Ok(RevListResult {
853 commits: Vec::new(),
854 objects: Vec::new(),
855 omitted_objects: Vec::new(),
856 missing_objects: Vec::new(),
857 boundary_commits: Vec::new(),
858 left_right_map: HashMap::new(),
859 cherry_equivalent: HashSet::new(),
860 per_commit_object_counts: Vec::new(),
861 object_walk_tips: Vec::new(),
862 objects_print_commit: Vec::new(),
863 object_segments: Vec::new(),
864 bitmap_object_format: false,
865 tip_annotated_tag_by_commit: HashMap::new(),
866 });
867 }
868 return Err(Error::InvalidRef("no revisions specified".to_owned()));
869 }
870
871 let object_walk_tip_commits: Vec<ObjectId> = if options.objects {
872 include.clone()
873 } else {
874 Vec::new()
875 };
876
877 let excluded_promisor = if options.exclude_promisor_objects {
878 crate::promisor::promisor_expanded_object_ids(repo)?
879 } else {
880 HashSet::new()
881 };
882
883 let mut traversal_missing = Vec::new();
884 let mut traversal_missing_seen = HashSet::new();
885 let (mut included, discovery_order) = if include.is_empty() {
886 (HashSet::new(), Vec::new())
887 } else if options.exclude_promisor_objects {
888 walk_closure_ordered_excluding(&mut graph, &include, &excluded_promisor)?
889 } else if options.missing_action != MissingAction::Error {
890 walk_closure_ordered_with_missing(
891 &mut graph,
892 &include,
893 &HashSet::new(),
894 options.missing_action,
895 &mut traversal_missing,
896 &mut traversal_missing_seen,
897 )?
898 } else {
899 walk_closure_ordered(&mut graph, &include)?
900 };
901 let excluded = if exclude.is_empty() {
902 HashSet::new()
903 } else if options.exclude_promisor_objects {
904 walk_closure_ordered_excluding(&mut graph, &exclude, &excluded_promisor)?.0
905 } else if options.exclude_first_parent_only {
906 walk_closure_first_parent_only(&mut graph, &exclude)?
907 } else if options.missing_action != MissingAction::Error {
908 walk_closure_ordered_with_missing(
909 &mut graph,
910 &exclude,
911 &HashSet::new(),
912 options.missing_action,
913 &mut traversal_missing,
914 &mut traversal_missing_seen,
915 )?
916 .0
917 } else {
918 walk_closure(&mut graph, &exclude)?
919 };
920 included.retain(|oid| !excluded.contains(oid));
921
922 if !options.paths.is_empty()
927 && !options.full_history
928 && !options.ancestry_path
929 && !options.simplify_merges
930 {
931 included = walk_dense_path_limited_closure(
932 repo,
933 &mut graph,
934 &include,
935 &excluded,
936 &options.paths,
937 options.sparse,
938 options.show_pulls,
939 )?;
940 }
941
942 if options.simplify_by_decoration {
943 let decorated = all_ref_tips(repo, &RefExclusions::default())?;
944 included.retain(|oid| decorated.contains(oid));
945 }
946
947 let mut effective_ancestry_path_bottoms = Vec::new();
948 if options.ancestry_path {
949 let mut bottoms = options.ancestry_path_bottoms.clone();
950 if bottoms.is_empty() {
951 bottoms.extend(exclude.iter().copied());
952 }
953 if bottoms.is_empty() {
954 return Err(Error::InvalidRef(
955 "--ancestry-path requires a range with excluded tips".to_owned(),
956 ));
957 }
958 limit_to_ancestry(&mut graph, &mut included, &bottoms)?;
959 effective_ancestry_path_bottoms = bottoms;
960 }
961
962 let path_effective_full = options.full_history
965 || options.ancestry_path
966 || (options.simplify_merges && !options.paths.is_empty());
967
968 if options.min_parents.is_some() || options.max_parents.is_some() {
970 let min_p = options.min_parents.unwrap_or(0);
971 let max_p = options.max_parents.unwrap_or(usize::MAX);
972 included.retain(|oid| {
973 let count = graph.parents_of(*oid).map(|p| p.len()).unwrap_or(0);
974 count >= min_p && count <= max_p
975 });
976 }
977
978 if options.maximal_only {
979 retain_maximal_commits(&mut graph, &mut included)?;
980 }
981
982 let mut ordered = match options.ordering {
983 OrderingMode::Default | OrderingMode::DateOrderWalk | OrderingMode::AuthorDateWalk => {
984 let author_dates = options.ordering == OrderingMode::AuthorDateWalk;
985 let parent_count_filter_active =
986 options.min_parents.is_some() || options.max_parents.is_some();
987 if parent_count_filter_active {
988 let mut tips: Vec<ObjectId> = Vec::new();
992 let mut tip_seen = HashSet::new();
993 for &tip in &include {
994 if included.contains(&tip) {
995 if tip_seen.insert(tip) {
996 tips.push(tip);
997 }
998 continue;
999 }
1000 let parents = graph.parents_of(tip).unwrap_or_default();
1001 for p in parents {
1002 if tip_seen.insert(p) {
1003 tips.push(p);
1004 }
1005 }
1006 }
1007 date_order_walk_with_tips(&mut graph, &tips, &included, author_dates)?
1008 } else {
1009 date_order_walk(&mut graph, &included, author_dates)?
1010 }
1011 }
1012 OrderingMode::Topo => graph_order_topo_sort(&mut graph, &included, &discovery_order)?,
1013 OrderingMode::AuthorDateTopo => topo_sort(&mut graph, &included, true)?,
1014 };
1015
1016 if !options.paths.is_empty() {
1018 let paths = &options.paths;
1019 let cfg = ConfigSet::load(Some(&repo.git_dir), true).unwrap_or_default();
1020 let mut core_cg = match cfg.get_bool("core.commitgraph") {
1021 Some(Ok(b)) => b,
1022 _ => true,
1023 };
1024 if std::env::var("GIT_TEST_COMMIT_GRAPH").ok().as_deref() == Some("0") {
1025 core_cg = false;
1026 }
1027 let read_paths = cfg
1028 .get("commitgraph.readchangedpaths")
1029 .and_then(|v| crate::config::parse_bool(&v).ok())
1030 .unwrap_or(true);
1031 let version = cfg
1032 .get("commitgraph.changedpathsversion")
1033 .and_then(|s| s.parse::<i32>().ok())
1034 .unwrap_or(-1);
1035
1036 let use_bloom = core_cg
1037 && options.use_commit_graph_bloom
1038 && crate::pathspec::pathspecs_allow_bloom(paths);
1039 let read_changed = read_paths && options.commit_graph_read_changed_paths;
1040 let bloom_chain = if use_bloom {
1041 CommitGraphChain::load(&repo.git_dir.join("objects"))
1042 } else {
1043 None
1044 };
1045 let bloom_version = if options.commit_graph_changed_paths_version != -1 {
1046 options.commit_graph_changed_paths_version
1047 } else {
1048 version
1049 };
1050 let bloom_cwd = repo.bloom_pathspec_cwd();
1051
1052 ordered.retain(|oid| {
1053 commit_touches_paths(
1054 repo,
1055 &mut graph,
1056 *oid,
1057 paths,
1058 path_effective_full,
1059 options.sparse,
1060 options.simplify_merges,
1061 options.preserve_simplify_merges_graph_merges,
1062 options.show_pulls,
1063 options.parent_rewrite,
1064 options.ancestry_path,
1065 &effective_ancestry_path_bottoms,
1066 &excluded,
1067 bloom_chain.as_ref(),
1068 read_changed,
1069 bloom_version,
1070 options.bloom_stats.as_ref(),
1071 bloom_cwd.as_deref(),
1072 )
1073 .unwrap_or(false)
1074 });
1075 }
1076
1077 if !options.paths.is_empty() && options.simplify_merges && !ordered.is_empty() {
1078 ordered = simplify_merges_commit_list(
1079 repo,
1080 &ordered,
1081 &options.paths,
1082 &excluded,
1083 &effective_ancestry_path_bottoms,
1084 options.ordering,
1085 options.show_pulls,
1086 options.preserve_simplify_merges_graph_merges,
1087 )?;
1088 }
1089
1090 let path_needs_graph_reorder = !options.paths.is_empty()
1093 && options.path_graph_reorder
1094 && !options.simplify_merges
1095 && (!options.full_history || options.sparse);
1096 if path_needs_graph_reorder && !ordered.is_empty() {
1097 if options.sparse {
1098 let mut dense_opts = options.clone();
1099 dense_opts.sparse = false;
1100 dense_opts.path_graph_reorder = false;
1101 let dense_result = rev_list(repo, positive_specs, negative_specs, &dense_opts)?;
1102 let dense_ordered = reorder_path_limited_graph_commits(
1103 repo,
1104 &dense_result.commits,
1105 options.first_parent,
1106 )?;
1107 ordered = expand_sparse_path_limited_graph_history(repo, &dense_ordered)?;
1108 } else {
1109 ordered = reorder_path_limited_graph_commits(repo, &ordered, options.first_parent)?;
1110 }
1111 }
1112
1113 if matches!(
1114 options.ordering,
1115 OrderingMode::Topo | OrderingMode::AuthorDateTopo
1116 ) && !options.left_right
1117 && !options.left_only
1118 && !options.right_only
1119 && !options.cherry_mark
1120 && !options.cherry_pick
1121 {
1122 if let (Some(left_oid), Some(right_oid)) = (options.symmetric_left, options.symmetric_right)
1123 {
1124 reorder_symmetric_topo_right_first(&mut graph, &mut ordered, left_oid, right_oid)?;
1125 }
1126 }
1127
1128 let mut left_right_map = HashMap::new();
1130 if options.left_right
1131 || options.left_only
1132 || options.right_only
1133 || options.cherry_mark
1134 || options.cherry_pick
1135 {
1136 if let (Some(left_oid), Some(right_oid)) = (options.symmetric_left, options.symmetric_right)
1137 {
1138 let left_reach = walk_closure(&mut graph, &[left_oid])?;
1143 let right_reach = walk_closure(&mut graph, &[right_oid])?;
1144 for &oid in &ordered {
1145 let from_left = left_reach.contains(&oid);
1146 let from_right = right_reach.contains(&oid);
1147 left_right_map.insert(oid, from_left && !from_right);
1148 }
1149 }
1150 }
1151
1152 let mut cherry_equivalent = HashSet::new();
1155 if options.cherry_pick || options.cherry_mark {
1156 let left_commits: Vec<_> = ordered
1157 .iter()
1158 .filter(|o| left_right_map.get(o) == Some(&true))
1159 .copied()
1160 .collect();
1161 let right_commits: Vec<_> = ordered
1162 .iter()
1163 .filter(|o| left_right_map.get(o) == Some(&false))
1164 .copied()
1165 .collect();
1166 let left_first = !left_commits.is_empty()
1167 && !right_commits.is_empty()
1168 && left_commits.len() < right_commits.len();
1169
1170 let mut by_patch: HashMap<ObjectId, Vec<ObjectId>> = HashMap::new();
1171 if left_first {
1172 for oid in &left_commits {
1173 if let Ok(Some(pid)) = compute_cherry_patch_id(repo, oid, &options.paths) {
1174 by_patch.entry(pid).or_default().push(*oid);
1175 }
1176 }
1177 for oid in &right_commits {
1178 if let Ok(Some(pid)) = compute_cherry_patch_id(repo, oid, &options.paths) {
1179 if let Some(others) = by_patch.get(&pid) {
1180 cherry_equivalent.insert(*oid);
1181 cherry_equivalent.extend(others.iter().copied());
1182 }
1183 }
1184 }
1185 } else {
1186 for oid in &right_commits {
1187 if let Ok(Some(pid)) = compute_cherry_patch_id(repo, oid, &options.paths) {
1188 by_patch.entry(pid).or_default().push(*oid);
1189 }
1190 }
1191 for oid in &left_commits {
1192 if let Ok(Some(pid)) = compute_cherry_patch_id(repo, oid, &options.paths) {
1193 if let Some(others) = by_patch.get(&pid) {
1194 cherry_equivalent.insert(*oid);
1195 cherry_equivalent.extend(others.iter().copied());
1196 }
1197 }
1198 }
1199 }
1200 }
1201
1202 if options.left_only {
1204 ordered.retain(|oid| left_right_map.get(oid) == Some(&true));
1205 }
1206 if options.right_only {
1207 ordered.retain(|oid| left_right_map.get(oid) == Some(&false));
1208 }
1209
1210 if options.cherry_pick {
1212 ordered.retain(|oid| !cherry_equivalent.contains(oid));
1213 }
1214
1215 if options.until_cutoff.is_some() || options.since_cutoff.is_some() {
1216 let until = options.until_cutoff;
1217 let since = options.since_cutoff;
1218 ordered.retain(|oid| {
1219 let ts = graph.committer_time(*oid);
1220 if let Some(u) = until {
1221 if ts > u {
1222 return false;
1223 }
1224 }
1225 if let Some(s) = since {
1226 if ts < s {
1227 return false;
1228 }
1229 }
1230 true
1231 });
1232 }
1233
1234 if options.skip > 0 {
1235 ordered = ordered.into_iter().skip(options.skip).collect();
1236 }
1237 if let Some(max_count) = options.max_count {
1238 ordered.truncate(max_count);
1239 }
1240 if options.reverse {
1241 ordered.reverse();
1242 }
1243
1244 let boundary_commits = if options.boundary {
1246 let included_set: HashSet<ObjectId> = ordered.iter().copied().collect();
1247 let mut boundary = Vec::new();
1248 let mut boundary_seen = HashSet::new();
1249 for &oid in &ordered {
1250 if let Ok(parents) = graph.parents_of(oid).map(|p| p.to_vec()) {
1251 for parent in parents {
1252 if !included_set.contains(&parent) && boundary_seen.insert(parent) {
1253 boundary.push(parent);
1254 }
1255 }
1256 }
1257 }
1258 boundary
1259 } else {
1260 Vec::new()
1261 };
1262
1263 let kept_set = if options.no_kept_objects {
1265 kept_object_ids(repo).unwrap_or_default()
1266 } else {
1267 HashSet::new()
1268 };
1269
1270 if options.no_kept_objects {
1271 ordered.retain(|oid| !kept_set.contains(oid));
1272 }
1273
1274 if options.unpacked_only {
1275 let packed = packed_object_set(repo);
1276 ordered.retain(|oid| !packed.contains(oid));
1277 }
1278
1279 let commit_tips_set: HashSet<ObjectId> = object_walk_tip_commits.iter().copied().collect();
1280 let objects_print_commit: Vec<bool> = if options.objects {
1281 ordered
1282 .iter()
1283 .map(|&c| {
1284 let user_given = !options.filter_provided_objects && commit_tips_set.contains(&c);
1285 object_walk_print_commit_line(
1286 options.filter_provided_objects,
1287 options.filter.as_ref(),
1288 user_given,
1289 )
1290 })
1291 .collect()
1292 } else {
1293 Vec::new()
1294 };
1295
1296 let sparse_lines = sparse_oid_lines_from_filter(repo, options.filter.as_ref())?;
1297 let skip_trees = skip_tree_descent_for_object_type_filter(options.filter.as_ref());
1298 let walk_cfg = ConfigSet::load(Some(&repo.git_dir), true).unwrap_or_default();
1299 let promisor_repo = crate::promisor::repo_treats_promisor_packs(&repo.git_dir, &walk_cfg);
1300 let object_walk_missing_action = if options.objects
1301 && options.missing_action == MissingAction::Error
1302 && (promisor_repo || options.exclude_promisor_objects)
1303 {
1304 MissingAction::Allow
1305 } else {
1306 options.missing_action
1307 };
1308 let bitmap_object_format = options.objects
1309 && options.use_bitmap_index
1310 && (options.bitmap_oid_only_objects || !object_roots.is_empty() || options.unpacked_only);
1311 let omit_object_paths = bitmap_object_format;
1312 let packed_set = None;
1316
1317 let collect_tree_omits = options.filter_print_omitted;
1319
1320 let (objects, omitted_objects, missing_objects, per_commit_object_counts, object_segments) =
1322 if options.objects {
1323 let excluded_object_ids = excluded_object_root_ids(
1324 repo,
1325 &negative_object_roots,
1326 object_walk_missing_action,
1327 sparse_lines.as_deref(),
1328 skip_trees,
1329 omit_object_paths,
1330 collect_tree_omits,
1331 )?;
1332 let filter_provided = options.filter_provided_objects;
1333 let (mut objs, mut omit, mut miss, mut counts, mut segments) =
1334 if options.in_commit_order {
1335 let (o, om, mi, c) = collect_reachable_objects_in_commit_order(
1336 repo,
1337 &mut graph,
1338 &ordered,
1339 &object_roots,
1340 &tip_annotated_tag_by_commit,
1341 options.filter.as_ref(),
1342 filter_provided,
1343 object_walk_missing_action,
1344 sparse_lines.as_deref(),
1345 skip_trees,
1346 omit_object_paths,
1347 packed_set.as_ref(),
1348 collect_tree_omits,
1349 )?;
1350 (o, om, mi, c, Vec::new())
1351 } else {
1352 let (o, om, mi, seg) = collect_reachable_objects_segmented(
1353 repo,
1354 &mut graph,
1355 &ordered,
1356 &object_roots,
1357 &tip_annotated_tag_by_commit,
1358 options.filter.as_ref(),
1359 filter_provided,
1360 object_walk_missing_action,
1361 sparse_lines.as_deref(),
1362 skip_trees,
1363 omit_object_paths,
1364 packed_set.as_ref(),
1365 collect_tree_omits,
1366 )?;
1367 (o, om, mi, Vec::new(), seg)
1368 };
1369 if !excluded_object_ids.is_empty() {
1370 if !counts.is_empty() {
1371 let mut offset = 0usize;
1372 for count in &mut counts {
1373 let end = offset.saturating_add(*count);
1374 *count = objs[offset..end]
1375 .iter()
1376 .filter(|(oid, _)| !excluded_object_ids.contains(oid))
1377 .count();
1378 offset = end;
1379 }
1380 }
1381 objs.retain(|(oid, _)| !excluded_object_ids.contains(oid));
1382 omit.retain(|oid| !excluded_object_ids.contains(oid));
1383 miss.retain(|oid| !excluded_object_ids.contains(oid));
1384 for segment in &mut segments {
1385 segment.retain(|(oid, _)| !excluded_object_ids.contains(oid));
1386 }
1387 }
1388 if options.no_kept_objects {
1389 objs.retain(|(oid, _)| !kept_set.contains(oid));
1390 }
1391 if options.exclude_promisor_objects {
1392 objs.retain(|(oid, _)| !excluded_promisor.contains(oid));
1393 for segment in &mut segments {
1394 segment.retain(|(oid, _)| !excluded_promisor.contains(oid));
1395 }
1396 }
1397 if !options.paths.is_empty() && !omit_object_paths {
1398 retain_objects_matching_pathspecs(&mut objs, &options.paths);
1399 let mut seen_oids: HashSet<ObjectId> = objs.iter().map(|(oid, _)| *oid).collect();
1400 for (oid, path) in
1401 collect_pathspec_matching_tree_objects(repo, &ordered, &options.paths)?
1402 {
1403 if seen_oids.insert(oid) {
1404 objs.push((oid, path));
1405 }
1406 }
1407 for segment in &mut segments {
1408 retain_objects_matching_pathspecs(segment, &options.paths);
1409 }
1410 if !counts.is_empty() {
1411 counts = segments.iter().map(Vec::len).collect();
1412 }
1413 }
1414 (objs, omit, miss, counts, segments)
1415 } else {
1416 (Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new())
1417 };
1418
1419 let omitted_objects = if omitted_objects.is_empty() {
1420 omitted_objects
1421 } else {
1422 let emitted: HashSet<ObjectId> = objects.iter().map(|(o, _)| *o).collect();
1423 omitted_objects
1424 .into_iter()
1425 .filter(|o| !emitted.contains(o))
1426 .collect::<BTreeSet<_>>()
1427 .into_iter()
1428 .collect()
1429 };
1430
1431 let missing_objects = if traversal_missing.is_empty() {
1432 missing_objects
1433 } else {
1434 let mut seen = HashSet::new();
1435 traversal_missing
1436 .into_iter()
1437 .chain(missing_objects)
1438 .filter(|oid| seen.insert(*oid))
1439 .collect()
1440 };
1441
1442 Ok(RevListResult {
1443 commits: ordered,
1444 objects,
1445 omitted_objects,
1446 missing_objects,
1447 boundary_commits,
1448 left_right_map,
1449 cherry_equivalent,
1450 per_commit_object_counts,
1451 object_walk_tips: object_walk_tip_commits,
1452 objects_print_commit,
1453 object_segments,
1454 bitmap_object_format,
1455 tip_annotated_tag_by_commit,
1456 })
1457}
1458
1459#[must_use]
1466pub fn split_revision_token(token: &str) -> (Vec<String>, Vec<String>) {
1467 if let Some((lhs, rhs)) = crate::rev_parse::split_double_dot_range(token) {
1468 let positive = if rhs.is_empty() {
1469 "HEAD".to_owned()
1470 } else {
1471 rhs.to_owned()
1472 };
1473 let negative = if lhs.is_empty() {
1474 "HEAD".to_owned()
1475 } else {
1476 lhs.to_owned()
1477 };
1478 return (vec![positive], vec![negative]);
1479 }
1480 if let Some(rest) = token.strip_prefix('^') {
1481 return (Vec::new(), vec![rest.to_owned()]);
1482 }
1483 (vec![token.to_owned()], Vec::new())
1484}
1485
1486fn ansi_color_from_name(name: &str) -> String {
1487 match name {
1488 "red" => "\x1b[31m".to_owned(),
1489 "green" => "\x1b[32m".to_owned(),
1490 "yellow" => "\x1b[33m".to_owned(),
1491 "blue" => "\x1b[34m".to_owned(),
1492 "magenta" => "\x1b[35m".to_owned(),
1493 "cyan" => "\x1b[36m".to_owned(),
1494 "white" => "\x1b[37m".to_owned(),
1495 "bold" => "\x1b[1m".to_owned(),
1496 "dim" => "\x1b[2m".to_owned(),
1497 "ul" | "underline" => "\x1b[4m".to_owned(),
1498 "blink" => "\x1b[5m".to_owned(),
1499 "reverse" => "\x1b[7m".to_owned(),
1500 "reset" => "\x1b[m".to_owned(),
1501 _ => String::new(),
1502 }
1503}
1504
1505fn color_name_to_code(name: &str) -> Option<u8> {
1506 match name {
1507 "black" => Some(0),
1508 "red" => Some(1),
1509 "green" => Some(2),
1510 "yellow" => Some(3),
1511 "blue" => Some(4),
1512 "magenta" => Some(5),
1513 "cyan" => Some(6),
1514 "white" => Some(7),
1515 "default" => Some(9),
1516 _ => None,
1517 }
1518}
1519
1520fn ansi_color_from_spec(spec: &str) -> String {
1521 if spec == "reset" {
1522 return "\x1b[m".to_owned();
1523 }
1524 let mut attrs = Vec::new();
1525 let mut fg = None;
1526 let mut bg = None;
1527 for part in spec.split_whitespace() {
1528 match part {
1529 "bold" => attrs.push("1".to_owned()),
1530 "dim" => attrs.push("2".to_owned()),
1531 "italic" => attrs.push("3".to_owned()),
1532 "ul" | "underline" => attrs.push("4".to_owned()),
1533 "blink" => attrs.push("5".to_owned()),
1534 "reverse" => attrs.push("7".to_owned()),
1535 "strike" => attrs.push("9".to_owned()),
1536 "nobold" | "nodim" => attrs.push("22".to_owned()),
1537 "noitalic" => attrs.push("23".to_owned()),
1538 "noul" | "nounderline" => attrs.push("24".to_owned()),
1539 "noblink" => attrs.push("25".to_owned()),
1540 "noreverse" => attrs.push("27".to_owned()),
1541 "nostrike" => attrs.push("29".to_owned()),
1542 _ => {
1543 if let Some(code) = color_name_to_code(part) {
1544 if fg.is_none() {
1545 fg = Some(format!("{}", 30 + code));
1546 } else {
1547 bg = Some(format!("{}", 40 + code));
1548 }
1549 }
1550 }
1551 }
1552 }
1553 let mut codes = attrs;
1554 if let Some(code) = fg {
1555 codes.push(code);
1556 }
1557 if let Some(code) = bg {
1558 codes.push(code);
1559 }
1560 if codes.is_empty() {
1561 String::new()
1562 } else {
1563 format!("\x1b[{}m", codes.join(";"))
1564 }
1565}
1566
1567fn format_relative_date(diff: i64) -> String {
1568 if diff < 0 {
1569 "in the future".to_owned()
1570 } else if diff < 60 {
1571 format!("{} seconds ago", diff)
1572 } else if diff < 3600 {
1573 let m = diff / 60;
1574 if m == 1 {
1575 "1 minute ago".to_owned()
1576 } else {
1577 format!("{m} minutes ago")
1578 }
1579 } else if diff < 86400 {
1580 let h = diff / 3600;
1581 if h == 1 {
1582 "1 hour ago".to_owned()
1583 } else {
1584 format!("{h} hours ago")
1585 }
1586 } else if diff < 86400 * 30 {
1587 let d = diff / 86400;
1588 if d == 1 {
1589 "1 day ago".to_owned()
1590 } else {
1591 format!("{d} days ago")
1592 }
1593 } else if diff < 86400 * 365 {
1594 let months = diff / (86400 * 30);
1595 if months == 1 {
1596 "1 month ago".to_owned()
1597 } else {
1598 format!("{months} months ago")
1599 }
1600 } else {
1601 let years = diff / (86400 * 365);
1602 if years == 1 {
1603 "1 year ago".to_owned()
1604 } else {
1605 format!("{years} years ago")
1606 }
1607 }
1608}
1609
1610pub fn render_commit(
1616 repo: &Repository,
1617 oid: ObjectId,
1618 mode: &OutputMode,
1619 abbrev_len: usize,
1620) -> Result<String> {
1621 render_commit_with_color(repo, oid, mode, abbrev_len, false)
1622}
1623
1624pub fn render_commit_with_color(
1626 repo: &Repository,
1627 oid: ObjectId,
1628 mode: &OutputMode,
1629 abbrev_len: usize,
1630 use_color: bool,
1631) -> Result<String> {
1632 match mode {
1633 OutputMode::OidOnly => Ok(format!("{oid}")),
1634 OutputMode::Parents => {
1635 let mut out = format!("{oid}");
1636 let commit = load_commit(repo, oid)?;
1637 for parent in commit.parents {
1638 out.push(' ');
1639 out.push_str(&parent.to_hex());
1640 }
1641 Ok(out)
1642 }
1643 OutputMode::Format(fmt) => {
1644 let commit = load_commit(repo, oid)?;
1645 let subject = commit.message.lines().next().unwrap_or_default();
1646 let hex = oid.to_hex();
1647
1648 match fmt.as_str() {
1650 "oneline" => {
1651 return Ok(format!("{} {}", hex, subject));
1652 }
1653 "short" => {
1654 fn fmt_ident(ident: &str) -> String {
1655 let name = if let Some(bracket) = ident.find('<') {
1656 ident[..bracket].trim()
1657 } else {
1658 ident.trim()
1659 };
1660 let email = if let Some(start) = ident.find('<') {
1661 if let Some(end) = ident.find('>') {
1662 &ident[start..=end]
1663 } else {
1664 ""
1665 }
1666 } else {
1667 ""
1668 };
1669 format!("{} {}", name, email)
1670 }
1671 let mut out = String::new();
1672 out.push_str(&format!("Author: {}\n", fmt_ident(&commit.author)));
1673 out.push('\n');
1674 out.push_str(&format!(" {}\n", subject));
1675 out.push('\n');
1676 return Ok(out);
1677 }
1678 "medium" => {
1679 fn extract_ident_display(ident: &str) -> String {
1680 let name = if let Some(bracket) = ident.find('<') {
1681 ident[..bracket].trim()
1682 } else {
1683 ident.trim()
1684 };
1685 let email = if let Some(start) = ident.find('<') {
1686 if let Some(end) = ident.find('>') {
1687 &ident[start..=end]
1688 } else {
1689 ""
1690 }
1691 } else {
1692 ""
1693 };
1694 format!("{} {}", name, email)
1695 }
1696 fn format_default_date(ident: &str) -> String {
1697 let parts: Vec<&str> = ident.rsplitn(3, ' ').collect();
1698 if parts.len() < 2 {
1699 return String::new();
1700 }
1701 let ts_str = parts[1];
1702 let offset_str = parts[0];
1703 let ts: i64 = match ts_str.parse() {
1704 Ok(v) => v,
1705 Err(_) => return format!("{ts_str} {offset_str}"),
1706 };
1707 let tz_bytes = offset_str.as_bytes();
1708 let tz_secs: i64 = if tz_bytes.len() >= 5 {
1709 let sign = if tz_bytes[0] == b'-' { -1i64 } else { 1i64 };
1710 let h: i64 = offset_str[1..3].parse().unwrap_or(0);
1711 let m: i64 = offset_str[3..5].parse().unwrap_or(0);
1712 sign * (h * 3600 + m * 60)
1713 } else {
1714 0
1715 };
1716 let adjusted = ts + tz_secs;
1717 let dt = time::OffsetDateTime::from_unix_timestamp(adjusted)
1718 .unwrap_or(time::OffsetDateTime::UNIX_EPOCH);
1719 let weekday = match dt.weekday() {
1720 time::Weekday::Monday => "Mon",
1721 time::Weekday::Tuesday => "Tue",
1722 time::Weekday::Wednesday => "Wed",
1723 time::Weekday::Thursday => "Thu",
1724 time::Weekday::Friday => "Fri",
1725 time::Weekday::Saturday => "Sat",
1726 time::Weekday::Sunday => "Sun",
1727 };
1728 let month = match dt.month() {
1729 time::Month::January => "Jan",
1730 time::Month::February => "Feb",
1731 time::Month::March => "Mar",
1732 time::Month::April => "Apr",
1733 time::Month::May => "May",
1734 time::Month::June => "Jun",
1735 time::Month::July => "Jul",
1736 time::Month::August => "Aug",
1737 time::Month::September => "Sep",
1738 time::Month::October => "Oct",
1739 time::Month::November => "Nov",
1740 time::Month::December => "Dec",
1741 };
1742 format!(
1743 "{} {} {} {:02}:{:02}:{:02} {} {}",
1744 weekday,
1745 month,
1746 dt.day(),
1747 dt.hour(),
1748 dt.minute(),
1749 dt.second(),
1750 dt.year(),
1751 offset_str
1752 )
1753 }
1754 let mut out = String::new();
1755 out.push_str(&format!(
1756 "Author: {}\n",
1757 extract_ident_display(&commit.author)
1758 ));
1759 out.push_str(&format!(
1760 "Date: {}\n",
1761 format_default_date(&commit.author)
1762 ));
1763 out.push('\n');
1764 for line in commit.message.lines() {
1765 out.push_str(&format!(" {}\n", line));
1766 }
1767 return Ok(out);
1768 }
1769 _ => {}
1770 }
1771
1772 let raw_fmt = if let Some(t) = fmt.strip_prefix("format:") {
1773 t
1774 } else if let Some(t) = fmt.strip_prefix("tformat:") {
1775 t
1776 } else {
1777 fmt.as_str()
1778 };
1779
1780 let signature: Option<crate::signing::SignatureCheck> = if raw_fmt.contains("%G") {
1783 repo.odb.read(&oid).ok().map(|obj| {
1784 let config = ConfigSet::load(Some(&repo.git_dir), true).unwrap_or_default();
1785 match crate::signing::GpgConfig::from_config(&config) {
1786 Ok(cfg) => crate::signing::verify_commit(&cfg, &obj.data)
1787 .unwrap_or_else(|_| crate::signing::SignatureCheck::default_none()),
1788 Err(_) => crate::signing::SignatureCheck::default_none(),
1789 }
1790 })
1791 } else {
1792 None
1793 };
1794 let body = {
1796 let mut lines = commit.message.lines();
1797 lines.next(); if let Some(blank) = lines.next() {
1800 if blank.is_empty() {
1801 lines.collect::<Vec<_>>().join("\n")
1802 } else {
1803 std::iter::once(blank)
1804 .chain(lines)
1805 .collect::<Vec<_>>()
1806 .join("\n")
1807 }
1808 } else {
1809 String::new()
1810 }
1811 };
1812 let tree_hex = commit.tree.to_hex();
1813 let parent_hexes: Vec<String> = commit.parents.iter().map(|p| p.to_hex()).collect();
1814 let parent_abbrevs: Vec<String> = commit
1815 .parents
1816 .iter()
1817 .map(|p| {
1818 let hex = p.to_hex();
1819 let n = abbrev_len.clamp(4, 40).min(hex.len());
1820 hex[..n].to_string()
1821 })
1822 .collect();
1823
1824 fn extract_name(ident: &str) -> &str {
1826 if let Some(bracket) = ident.find('<') {
1827 ident[..bracket].trim()
1828 } else {
1829 ident.trim()
1830 }
1831 }
1832 fn extract_email(ident: &str) -> &str {
1833 if let Some(start) = ident.find('<') {
1834 if let Some(end) = ident.find('>') {
1835 return &ident[start + 1..end];
1836 }
1837 }
1838 ""
1839 }
1840 fn extract_timestamp(ident: &str) -> String {
1841 match parse_signature_times(ident) {
1842 Some(p) => p.unix_seconds.to_string(),
1843 None => String::new(),
1844 }
1845 }
1846 fn weekday_str(dt: &time::OffsetDateTime) -> &'static str {
1847 match dt.weekday() {
1848 time::Weekday::Monday => "Mon",
1849 time::Weekday::Tuesday => "Tue",
1850 time::Weekday::Wednesday => "Wed",
1851 time::Weekday::Thursday => "Thu",
1852 time::Weekday::Friday => "Fri",
1853 time::Weekday::Saturday => "Sat",
1854 time::Weekday::Sunday => "Sun",
1855 }
1856 }
1857 fn month_str(dt: &time::OffsetDateTime) -> &'static str {
1858 match dt.month() {
1859 time::Month::January => "Jan",
1860 time::Month::February => "Feb",
1861 time::Month::March => "Mar",
1862 time::Month::April => "Apr",
1863 time::Month::May => "May",
1864 time::Month::June => "Jun",
1865 time::Month::July => "Jul",
1866 time::Month::August => "Aug",
1867 time::Month::September => "Sep",
1868 time::Month::October => "Oct",
1869 time::Month::November => "Nov",
1870 time::Month::December => "Dec",
1871 }
1872 }
1873 fn extract_email_local(ident: &str) -> &str {
1874 let email = extract_email(ident);
1875 if let Some(at) = email.find('@') {
1876 &email[..at]
1877 } else {
1878 email
1879 }
1880 }
1881 fn extract_date_default(ident: &str) -> String {
1882 let Some(p) = parse_signature_times(ident) else {
1883 return String::new();
1884 };
1885 let offset_str = ident.get(p.tz_hhmm_range.clone()).unwrap_or("+0000");
1886 let adjusted = p.unix_seconds + p.tz_offset_secs;
1887 let dt = time::OffsetDateTime::from_unix_timestamp(adjusted)
1888 .unwrap_or(time::OffsetDateTime::UNIX_EPOCH);
1889 format!(
1890 "{} {} {} {:02}:{:02}:{:02} {} {}",
1891 weekday_str(&dt),
1892 month_str(&dt),
1893 dt.day(),
1894 dt.hour(),
1895 dt.minute(),
1896 dt.second(),
1897 dt.year(),
1898 offset_str
1899 )
1900 }
1901 fn extract_date_rfc2822(ident: &str) -> String {
1902 let Some(p) = parse_signature_times(ident) else {
1903 return String::new();
1904 };
1905 let offset_str = ident.get(p.tz_hhmm_range.clone()).unwrap_or("+0000");
1906 let adjusted = p.unix_seconds + p.tz_offset_secs;
1907 let dt = time::OffsetDateTime::from_unix_timestamp(adjusted)
1908 .unwrap_or(time::OffsetDateTime::UNIX_EPOCH);
1909 format!(
1910 "{}, {} {} {} {:02}:{:02}:{:02} {}",
1911 weekday_str(&dt),
1912 dt.day(),
1913 month_str(&dt),
1914 dt.year(),
1915 dt.hour(),
1916 dt.minute(),
1917 dt.second(),
1918 offset_str
1919 )
1920 }
1921 fn extract_date_short(ident: &str) -> String {
1922 let Some(p) = parse_signature_times(ident) else {
1923 return String::new();
1924 };
1925 let adjusted = p.unix_seconds + p.tz_offset_secs;
1926 let dt = time::OffsetDateTime::from_unix_timestamp(adjusted)
1927 .unwrap_or(time::OffsetDateTime::UNIX_EPOCH);
1928 format!("{:04}-{:02}-{:02}", dt.year(), dt.month() as u8, dt.day())
1929 }
1930 fn extract_date_iso(ident: &str) -> String {
1931 let Some(p) = parse_signature_times(ident) else {
1932 return String::new();
1933 };
1934 let offset_str = ident.get(p.tz_hhmm_range.clone()).unwrap_or("+0000");
1935 let adjusted = p.unix_seconds + p.tz_offset_secs;
1936 let dt = time::OffsetDateTime::from_unix_timestamp(adjusted)
1937 .unwrap_or(time::OffsetDateTime::UNIX_EPOCH);
1938 format!(
1939 "{:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
1940 dt.year(),
1941 dt.month() as u8,
1942 dt.day(),
1943 dt.hour(),
1944 dt.minute(),
1945 dt.second(),
1946 offset_str
1947 )
1948 }
1949
1950 #[derive(Clone, Copy)]
1952 enum Align {
1953 Left,
1954 Right,
1955 Center,
1956 }
1957 #[derive(Clone, Copy)]
1958 enum Trunc {
1959 None,
1960 Trunc,
1961 LTrunc,
1962 MTrunc,
1963 }
1964 struct ColSpec {
1965 width: usize,
1966 align: Align,
1967 trunc: Trunc,
1968 }
1969 fn apply_col(spec: &ColSpec, s: &str) -> String {
1970 let char_len = s.chars().count();
1971 if char_len > spec.width {
1972 match spec.trunc {
1973 Trunc::None => s.to_owned(),
1974 Trunc::Trunc => {
1975 let mut out: String =
1976 s.chars().take(spec.width.saturating_sub(2)).collect();
1977 out.push_str("..");
1978 out
1979 }
1980 Trunc::LTrunc => {
1981 let skip = char_len - spec.width + 2;
1982 let mut out = String::from("..");
1983 out.extend(s.chars().skip(skip));
1984 out
1985 }
1986 Trunc::MTrunc => {
1987 let keep = spec.width.saturating_sub(2);
1988 let left_half = keep / 2;
1989 let right_half = keep - left_half;
1990 let mut out: String = s.chars().take(left_half).collect();
1991 out.push_str("..");
1992 out.extend(s.chars().skip(char_len - right_half));
1993 out
1994 }
1995 }
1996 } else {
1997 let pad = spec.width - char_len;
1998 match spec.align {
1999 Align::Left => {
2000 let mut out = s.to_owned();
2001 for _ in 0..pad {
2002 out.push(' ');
2003 }
2004 out
2005 }
2006 Align::Right => {
2007 let mut out = String::new();
2008 for _ in 0..pad {
2009 out.push(' ');
2010 }
2011 out.push_str(s);
2012 out
2013 }
2014 Align::Center => {
2015 let left = pad / 2;
2016 let right = pad - left;
2017 let mut out = String::new();
2018 for _ in 0..left {
2019 out.push(' ');
2020 }
2021 out.push_str(s);
2022 for _ in 0..right {
2023 out.push(' ');
2024 }
2025 out
2026 }
2027 }
2028 }
2029 }
2030 fn parse_col_spec(
2031 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
2032 align: Align,
2033 ) -> Option<ColSpec> {
2034 if chars.peek() != Some(&'(') {
2036 return None;
2037 }
2038 chars.next();
2039 let mut num_str = String::new();
2040 while let Some(&c) = chars.peek() {
2041 if c.is_ascii_digit() {
2042 num_str.push(c);
2043 chars.next();
2044 } else {
2045 break;
2046 }
2047 }
2048 let width: usize = num_str.parse().ok()?;
2049 let trunc = if chars.peek() == Some(&',') {
2050 chars.next(); let mut mode = String::new();
2052 while let Some(&c) = chars.peek() {
2053 if c == ')' {
2054 break;
2055 }
2056 mode.push(c);
2057 chars.next();
2058 }
2059 match mode.as_str() {
2060 "trunc" => Trunc::Trunc,
2061 "ltrunc" => Trunc::LTrunc,
2062 "mtrunc" => Trunc::MTrunc,
2063 _ => Trunc::None,
2064 }
2065 } else {
2066 Trunc::None
2067 };
2068 if chars.peek() == Some(&')') {
2070 chars.next();
2071 }
2072 Some(ColSpec {
2073 width,
2074 align,
2075 trunc,
2076 })
2077 }
2078
2079 let mut pending_col: Option<ColSpec> = None;
2080 let mut rendered = String::new();
2081 let mut chars = raw_fmt.chars().peekable();
2082 while let Some(ch) = chars.next() {
2083 if ch != '%' {
2084 rendered.push(ch);
2085 continue;
2086 }
2087 if chars.peek() == Some(&'<') {
2089 chars.next();
2090 if let Some(spec) = parse_col_spec(&mut chars, Align::Left) {
2091 pending_col = Some(spec);
2092 }
2093 continue;
2094 }
2095 if chars.peek() == Some(&'>') {
2096 chars.next();
2097 if chars.peek() == Some(&'<') {
2098 chars.next(); if let Some(spec) = parse_col_spec(&mut chars, Align::Center) {
2100 pending_col = Some(spec);
2101 }
2102 } else if chars.peek() == Some(&'>') {
2103 chars.next(); if let Some(spec) = parse_col_spec(&mut chars, Align::Right) {
2105 pending_col = Some(spec);
2106 }
2107 } else if let Some(spec) = parse_col_spec(&mut chars, Align::Right) {
2108 pending_col = Some(spec);
2109 }
2110 continue;
2111 }
2112
2113 let mut expanded = String::new();
2115 let target = if pending_col.is_some() {
2116 &mut expanded
2117 } else {
2118 &mut rendered
2119 };
2120 match chars.peek() {
2121 Some('%') => {
2122 chars.next();
2123 target.push('%');
2124 }
2125 Some('H') => {
2126 chars.next();
2127 target.push_str(&oid.to_hex());
2128 }
2129 Some('h') => {
2130 chars.next();
2131 let hex = oid.to_hex();
2132 let n = abbrev_len.clamp(4, 40).min(hex.len());
2133 target.push_str(&hex[..n]);
2134 }
2135 Some('T') => {
2136 chars.next();
2137 target.push_str(&tree_hex);
2138 }
2139 Some('t') => {
2140 chars.next();
2141 let n = abbrev_len.clamp(4, 40).min(tree_hex.len());
2142 target.push_str(&tree_hex[..n]);
2143 }
2144 Some('P') => {
2145 chars.next();
2146 target.push_str(&parent_hexes.join(" "));
2147 }
2148 Some('p') => {
2149 chars.next();
2150 target.push_str(&parent_abbrevs.join(" "));
2151 }
2152 Some('n') => {
2153 chars.next();
2154 target.push('\n');
2155 }
2156 Some('s') => {
2157 chars.next();
2158 target.push_str(subject);
2159 }
2160 Some('b') => {
2161 chars.next();
2162 target.push_str(&body);
2163 if !body.is_empty() {
2164 target.push('\n');
2165 }
2166 }
2167 Some('B') => {
2168 chars.next();
2169 target.push_str(&commit.message);
2170 }
2171 Some('a') => {
2172 chars.next();
2173 match chars.next() {
2174 Some('n') => target.push_str(extract_name(&commit.author)),
2175 Some('N') => target.push_str(extract_name(&commit.author)),
2176 Some('e') => target.push_str(extract_email(&commit.author)),
2177 Some('E') => target.push_str(extract_email(&commit.author)),
2178 Some('l') => target.push_str(extract_email_local(&commit.author)),
2179 Some('d') => target.push_str(&extract_date_default(&commit.author)),
2180 Some('D') => target.push_str(&extract_date_rfc2822(&commit.author)),
2181 Some('t') => target.push_str(&extract_timestamp(&commit.author)),
2182 Some('s') => target.push_str(&extract_date_short(&commit.author)),
2183 Some('i') => target.push_str(&extract_date_iso(&commit.author)),
2184 Some('I') => {
2185 let Some(p) = parse_signature_times(&commit.author) else {
2186 break;
2187 };
2188 let adjusted = p.unix_seconds + p.tz_offset_secs;
2189 let dt = time::OffsetDateTime::from_unix_timestamp(adjusted)
2190 .unwrap_or(time::OffsetDateTime::UNIX_EPOCH);
2191 let sign_ch = if p.tz_offset_secs >= 0 { '+' } else { '-' };
2192 let abs_off = p.tz_offset_secs.unsigned_abs();
2193 target.push_str(&format!(
2194 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}{:02}:{:02}",
2195 dt.year(),
2196 dt.month() as u8,
2197 dt.day(),
2198 dt.hour(),
2199 dt.minute(),
2200 dt.second(),
2201 sign_ch,
2202 abs_off / 3600,
2203 (abs_off % 3600) / 60
2204 ));
2205 }
2206 Some('r') => {
2207 let Some(p) = parse_signature_times(&commit.author) else {
2208 break;
2209 };
2210 let now = std::time::SystemTime::now()
2211 .duration_since(std::time::UNIX_EPOCH)
2212 .unwrap_or_default()
2213 .as_secs() as i64;
2214 target.push_str(&format_relative_date(now - p.unix_seconds));
2215 }
2216 Some(other) => {
2217 target.push('%');
2218 target.push('a');
2219 target.push(other);
2220 }
2221 None => {
2222 target.push('%');
2223 target.push('a');
2224 }
2225 }
2226 }
2227 Some('c') => {
2228 chars.next();
2229 match chars.next() {
2230 Some('n') => target.push_str(extract_name(&commit.committer)),
2231 Some('N') => target.push_str(extract_name(&commit.committer)),
2232 Some('e') => target.push_str(extract_email(&commit.committer)),
2233 Some('E') => target.push_str(extract_email(&commit.committer)),
2234 Some('l') => target.push_str(extract_email_local(&commit.committer)),
2235 Some('d') => target.push_str(&extract_date_default(&commit.committer)),
2236 Some('D') => target.push_str(&extract_date_rfc2822(&commit.committer)),
2237 Some('t') => target.push_str(&extract_timestamp(&commit.committer)),
2238 Some('s') => target.push_str(&extract_date_short(&commit.committer)),
2239 Some('i') => target.push_str(&extract_date_iso(&commit.committer)),
2240 Some('I') => {
2241 let Some(p) = parse_signature_times(&commit.committer) else {
2242 break;
2243 };
2244 let adjusted = p.unix_seconds + p.tz_offset_secs;
2245 let dt = time::OffsetDateTime::from_unix_timestamp(adjusted)
2246 .unwrap_or(time::OffsetDateTime::UNIX_EPOCH);
2247 let sign_ch = if p.tz_offset_secs >= 0 { '+' } else { '-' };
2248 let abs_off = p.tz_offset_secs.unsigned_abs();
2249 target.push_str(&format!(
2250 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}{:02}:{:02}",
2251 dt.year(),
2252 dt.month() as u8,
2253 dt.day(),
2254 dt.hour(),
2255 dt.minute(),
2256 dt.second(),
2257 sign_ch,
2258 abs_off / 3600,
2259 (abs_off % 3600) / 60
2260 ));
2261 }
2262 Some('r') => {
2263 let Some(p) = parse_signature_times(&commit.committer) else {
2264 break;
2265 };
2266 let now = std::time::SystemTime::now()
2267 .duration_since(std::time::UNIX_EPOCH)
2268 .unwrap_or_default()
2269 .as_secs() as i64;
2270 target.push_str(&format_relative_date(now - p.unix_seconds));
2271 }
2272 Some(other) => {
2273 target.push('%');
2274 target.push('c');
2275 target.push(other);
2276 }
2277 None => {
2278 target.push('%');
2279 target.push('c');
2280 }
2281 }
2282 }
2283 Some('x') => {
2284 chars.next();
2286 let mut hex_str = String::new();
2287 if let Some(&c1) = chars.peek() {
2288 if c1.is_ascii_hexdigit() {
2289 hex_str.push(c1);
2290 chars.next();
2291 }
2292 }
2293 if let Some(&c2) = chars.peek() {
2294 if c2.is_ascii_hexdigit() {
2295 hex_str.push(c2);
2296 chars.next();
2297 }
2298 }
2299 if let Ok(byte) = u8::from_str_radix(&hex_str, 16) {
2300 target.push(byte as char);
2301 }
2302 }
2303 Some('C') => {
2304 chars.next();
2305 if chars.peek() == Some(&'(') {
2306 chars.next();
2307 let mut spec = String::new();
2308 for c in chars.by_ref() {
2309 if c == ')' {
2310 break;
2311 }
2312 spec.push(c);
2313 }
2314 let (force, color_spec) =
2315 if let Some(rest) = spec.strip_prefix("always,") {
2316 (true, rest)
2317 } else if let Some(rest) = spec.strip_prefix("auto,") {
2318 (false, rest)
2319 } else if spec == "auto" {
2320 if use_color {
2321 target.push_str("\x1b[m");
2322 }
2323 continue;
2324 } else {
2325 (false, spec.as_str())
2326 };
2327 if use_color || force {
2328 target.push_str(&ansi_color_from_spec(color_spec));
2329 }
2330 } else {
2331 let remaining: String = chars.clone().collect();
2334 let known = [
2335 "reset", "red", "green", "blue", "yellow", "magenta", "cyan",
2336 "white", "bold", "dim", "ul",
2337 ];
2338 let mut matched = false;
2339 for name in &known {
2340 if remaining.starts_with(name) {
2341 for _ in 0..name.len() {
2342 chars.next();
2343 }
2344 if use_color {
2345 target.push_str(&ansi_color_from_name(name));
2346 }
2347 matched = true;
2348 break;
2349 }
2350 }
2351 if !matched {
2352 while let Some(&c) = chars.peek() {
2354 if c.is_alphanumeric() {
2355 chars.next();
2356 } else {
2357 break;
2358 }
2359 }
2360 }
2361 }
2362 }
2363 Some('w') => {
2364 chars.next();
2366 if chars.peek() == Some(&'(') {
2367 chars.next();
2368 for c in chars.by_ref() {
2369 if c == ')' {
2370 break;
2371 }
2372 }
2373 }
2374 }
2375 Some('+') => {
2376 chars.next();
2378 if chars.peek() == Some(&'%') {
2380 }
2384 let mut sub = String::new();
2386 if let Some(&nc) = chars.peek() {
2387 match nc {
2388 'b' => {
2389 chars.next();
2390 sub.push_str(&body);
2391 if !body.is_empty() {
2392 sub.push('\n');
2393 }
2394 }
2395 's' => {
2396 chars.next();
2397 sub.push_str(subject);
2398 }
2399 _ => {
2400 chars.next();
2401 sub.push('%');
2402 sub.push('+');
2403 sub.push(nc);
2404 }
2405 }
2406 }
2407 if !sub.is_empty() {
2408 target.push('\n');
2409 target.push_str(&sub);
2410 }
2411 }
2412 Some('-') => {
2413 chars.next();
2415 if let Some(&nc) = chars.peek() {
2417 match nc {
2418 'b' => {
2419 chars.next();
2420 if !body.is_empty() {
2421 target.push_str(&body);
2422 target.push('\n');
2423 }
2424 }
2425 's' => {
2426 chars.next();
2427 target.push_str(subject);
2428 }
2429 _ => {
2430 chars.next();
2431 target.push('%');
2432 target.push('-');
2433 target.push(nc);
2434 }
2435 }
2436 }
2437 }
2438 Some('d') => {
2439 chars.next();
2441 }
2442 Some('D') => {
2443 chars.next();
2445 }
2446 Some('e') => {
2447 chars.next();
2449 if let Some(encoding) = &commit.encoding {
2450 target.push_str(encoding);
2451 }
2452 }
2453 Some('g') => {
2454 chars.next();
2456 if let Some(&_nc) = chars.peek() {
2457 chars.next(); }
2460 }
2461 Some('G') => {
2462 use crate::signing::{SignatureCheck, TrustLevel};
2466 let default_sig;
2467 let sig: &SignatureCheck = match &signature {
2468 Some(s) => s,
2469 None => {
2470 default_sig = SignatureCheck::default_none();
2471 &default_sig
2472 }
2473 };
2474 let mut lookahead = chars.clone();
2475 lookahead.next(); let sub = lookahead.peek().copied();
2477 let handled = match sub {
2478 Some('?') => {
2479 let ch = if sig.result == 'G'
2480 && matches!(
2481 sig.trust_level,
2482 TrustLevel::Undefined | TrustLevel::Never
2483 ) {
2484 'U'
2485 } else {
2486 sig.result
2487 };
2488 target.push(ch);
2489 true
2490 }
2491 Some('S') => {
2492 target.push_str(sig.signer.as_deref().unwrap_or(""));
2493 true
2494 }
2495 Some('K') => {
2496 target.push_str(sig.key.as_deref().unwrap_or(""));
2497 true
2498 }
2499 Some('F') => {
2500 target.push_str(sig.fingerprint.as_deref().unwrap_or(""));
2501 true
2502 }
2503 Some('P') => {
2504 target
2505 .push_str(sig.primary_key_fingerprint.as_deref().unwrap_or(""));
2506 true
2507 }
2508 Some('T') => {
2509 target.push_str(sig.trust_level.display_key());
2510 true
2511 }
2512 Some('G') => {
2513 target.push_str(&sig.output);
2514 true
2515 }
2516 _ => false,
2517 };
2518 if handled {
2519 chars.next(); chars.next(); } else {
2522 target.push('%');
2523 }
2524 }
2525 Some(&other) => {
2526 chars.next();
2527 target.push('%');
2528 target.push(other);
2529 }
2530 None => target.push('%'),
2531 }
2532 if let Some(spec) = pending_col.take() {
2534 let formatted = apply_col(&spec, &expanded);
2535 rendered.push_str(&formatted);
2536 }
2537 }
2538 Ok(rendered)
2539 }
2540 }
2541}
2542
2543#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2544enum ExpectedObjectKind {
2545 Commit,
2546 Tree,
2547 Blob,
2548 Tag,
2549}
2550
2551impl ExpectedObjectKind {
2552 fn from_object_kind(kind: ObjectKind) -> Option<Self> {
2553 match kind {
2554 ObjectKind::Commit => Some(Self::Commit),
2555 ObjectKind::Tree => Some(Self::Tree),
2556 ObjectKind::Blob => Some(Self::Blob),
2557 ObjectKind::Tag => Some(Self::Tag),
2558 }
2559 }
2560
2561 fn from_tag_type(kind: &str) -> Option<Self> {
2562 match kind {
2563 "commit" => Some(Self::Commit),
2564 "tree" => Some(Self::Tree),
2565 "blob" => Some(Self::Blob),
2566 "tag" => Some(Self::Tag),
2567 _ => None,
2568 }
2569 }
2570
2571 fn matches(self, kind: ObjectKind) -> bool {
2572 matches!(
2573 (self, kind),
2574 (Self::Commit, ObjectKind::Commit)
2575 | (Self::Tree, ObjectKind::Tree)
2576 | (Self::Blob, ObjectKind::Blob)
2577 | (Self::Tag, ObjectKind::Tag)
2578 )
2579 }
2580
2581 fn as_str(self) -> &'static str {
2582 match self {
2583 Self::Commit => "commit",
2584 Self::Tree => "tree",
2585 Self::Blob => "blob",
2586 Self::Tag => "tag",
2587 }
2588 }
2589}
2590
2591#[derive(Clone, Debug)]
2593pub struct ObjectWalkRoot {
2594 pub oid: ObjectId,
2596 pub input: String,
2597 pub root_path: Option<String>,
2599}
2600
2601#[derive(Clone, Debug)]
2602struct RootObject {
2603 oid: ObjectId,
2604 input: String,
2605 expected_kind: Option<ExpectedObjectKind>,
2606 root_path: Option<String>,
2607 wrap_with_tag: Option<ObjectId>,
2609}
2610
2611fn indexed_object_roots(repo: &Repository) -> Result<Vec<RootObject>> {
2612 let Some(_) = &repo.work_tree else {
2613 return Ok(Vec::new());
2614 };
2615 let index_path = repo.git_dir.join("index");
2616 if !index_path.is_file() {
2617 return Ok(Vec::new());
2618 }
2619
2620 let idx = Index::load(&index_path)?;
2621 let mut roots = Vec::new();
2622 for e in &idx.entries {
2623 if e.stage() != 0 || e.mode == MODE_GITLINK || e.mode == MODE_TREE {
2624 continue;
2625 }
2626 let path_str = String::from_utf8_lossy(&e.path).into_owned();
2627 roots.push(RootObject {
2628 oid: e.oid,
2629 input: format!(":{path_str}"),
2630 expected_kind: Some(ExpectedObjectKind::Blob),
2631 root_path: Some(path_str),
2632 wrap_with_tag: None,
2633 });
2634 }
2635
2636 if let Some(cache_tree) = &idx.cache_tree {
2637 push_index_cache_tree_child_roots(&mut roots, cache_tree, "");
2638 }
2639
2640 Ok(roots)
2641}
2642
2643fn push_index_cache_tree_child_roots(
2644 roots: &mut Vec<RootObject>,
2645 node: &CacheTreeNode,
2646 parent_path: &str,
2647) {
2648 for child in &node.children {
2649 let name = String::from_utf8_lossy(&child.name);
2650 let path = if parent_path.is_empty() {
2651 name.into_owned()
2652 } else {
2653 format!("{parent_path}/{name}")
2654 };
2655
2656 if child.entry_count >= 0 {
2657 if let Some(oid) = child.oid {
2658 roots.push(RootObject {
2659 oid,
2660 input: format!(":{path}"),
2661 expected_kind: Some(ExpectedObjectKind::Tree),
2662 root_path: Some(path.clone()),
2663 wrap_with_tag: None,
2664 });
2665 }
2666 }
2667
2668 push_index_cache_tree_child_roots(roots, child, &path);
2669 }
2670}
2671
2672fn object_walk_print_commit_line(
2673 filter_provided_objects: bool,
2674 filter: Option<&ObjectFilter>,
2675 user_given_tip: bool,
2676) -> bool {
2677 if !filter_provided_objects {
2678 return user_given_tip || filter_shows_commit_line_when_not_user_given(filter);
2679 }
2680 match filter {
2681 Some(ObjectFilter::ObjectType(FilterObjectKind::Commit)) => {
2682 user_given_tip || filter_shows_commit_line_when_not_user_given(filter)
2683 }
2684 Some(ObjectFilter::ObjectType(_)) => false,
2685 Some(ObjectFilter::Combine(parts)) => {
2686 if parts
2687 .iter()
2688 .all(|p| matches!(p, ObjectFilter::ObjectType(FilterObjectKind::Commit)))
2689 {
2690 user_given_tip || filter_shows_commit_line_when_not_user_given(filter)
2691 } else {
2692 false
2693 }
2694 }
2695 _ => user_given_tip || filter_shows_commit_line_when_not_user_given(filter),
2696 }
2697}
2698
2699fn filter_shows_commit_line_when_not_user_given(filter: Option<&ObjectFilter>) -> bool {
2701 match filter {
2702 None => true,
2703 Some(ObjectFilter::BlobNone)
2704 | Some(ObjectFilter::BlobLimit(_))
2705 | Some(ObjectFilter::TreeDepth(_))
2706 | Some(ObjectFilter::SparseOid(_)) => true,
2707 Some(ObjectFilter::ObjectType(FilterObjectKind::Commit)) => true,
2708 Some(ObjectFilter::ObjectType(
2709 FilterObjectKind::Blob | FilterObjectKind::Tree | FilterObjectKind::Tag,
2710 )) => false,
2711 Some(ObjectFilter::Combine(parts)) => parts
2712 .iter()
2713 .all(|p| filter_shows_commit_line_when_not_user_given(Some(p))),
2714 }
2715}
2716
2717fn skip_tree_descent_for_object_type_filter(filter: Option<&ObjectFilter>) -> bool {
2718 match filter {
2719 Some(ObjectFilter::ObjectType(FilterObjectKind::Commit | FilterObjectKind::Tag)) => true,
2720 Some(ObjectFilter::Combine(parts)) => parts
2721 .iter()
2722 .any(|p| skip_tree_descent_for_object_type_filter(Some(p))),
2723 _ => false,
2724 }
2725}
2726
2727fn sparse_filter_includes_path(
2728 repo: &Repository,
2729 path: &str,
2730 sparse_lines: Option<&[String]>,
2731) -> bool {
2732 sparse_lines
2733 .map(|lines| path_in_sparse_checkout(path, lines, repo.work_tree.as_deref()))
2734 .unwrap_or(true)
2735}
2736
2737fn sparse_oid_lines_from_filter(
2738 repo: &Repository,
2739 filter: Option<&ObjectFilter>,
2740) -> Result<Option<Vec<String>>> {
2741 let Some(f) = filter else {
2742 return Ok(None);
2743 };
2744 match f {
2745 ObjectFilter::SparseOid(spec) => {
2746 let blob_oid = if let Ok(oid) = spec.parse::<ObjectId>() {
2750 oid
2751 } else if let Some((treeish, path)) = split_treeish_spec(spec) {
2752 let treeish_oid = resolve_revision_for_range_end(repo, treeish)
2753 .map_err(|_| sparse_blob_access_error(spec))?;
2754 resolve_treeish_path(repo, treeish_oid, path)
2755 .map_err(|_| sparse_blob_access_error(spec))?
2756 } else {
2757 resolve_revision_for_range_end(repo, spec)
2762 .map_err(|_| sparse_blob_access_error(spec))?
2763 };
2764 let obj = repo
2765 .odb
2766 .read(&blob_oid)
2767 .map_err(|_| sparse_blob_access_error(spec))?;
2768 if obj.kind != ObjectKind::Blob {
2771 return Err(Error::Message(format!(
2772 "fatal: unable to parse sparse filter data in {}",
2773 blob_oid.to_hex()
2774 )));
2775 }
2776 let text = std::str::from_utf8(&obj.data).map_err(|_| {
2777 Error::Message(format!(
2778 "fatal: unable to parse sparse filter data in {}",
2779 blob_oid.to_hex()
2780 ))
2781 })?;
2782 Ok(Some(parse_sparse_patterns_from_blob(text)))
2783 }
2784 ObjectFilter::Combine(parts) => {
2785 for p in parts {
2786 if let Some(lines) = sparse_oid_lines_from_filter(repo, Some(p))? {
2787 return Ok(Some(lines));
2788 }
2789 }
2790 Ok(None)
2791 }
2792 _ => Ok(None),
2793 }
2794}
2795
2796fn sparse_blob_access_error(spec: &str) -> Error {
2798 Error::Message(format!("fatal: unable to access sparse blob in '{spec}'"))
2799}
2800
2801fn packed_object_set(repo: &Repository) -> HashSet<ObjectId> {
2802 let mut out = HashSet::new();
2803 let objects_dir = repo.odb.objects_dir();
2804 if let Ok(indexes) = pack::read_local_pack_indexes(objects_dir) {
2805 for idx in indexes {
2806 for e in idx.entries {
2807 if let Ok(oid) = ObjectId::from_bytes(&e.oid) {
2808 out.insert(oid);
2809 }
2810 }
2811 }
2812 }
2813 out
2814}
2815
2816fn resolve_specs(repo: &Repository, specs: &[String]) -> Result<Vec<ObjectId>> {
2817 resolve_specs_with_options(repo, specs, false)
2818}
2819
2820fn resolve_specs_with_options(
2821 repo: &Repository,
2822 specs: &[String],
2823 ignore_missing: bool,
2824) -> Result<Vec<ObjectId>> {
2825 let mut out = Vec::with_capacity(specs.len());
2826 for spec in specs {
2827 match resolve_revision_for_range_end(repo, spec).and_then(|oid| peel_to_commit(repo, oid)) {
2828 Ok(commit_oid) => out.push(commit_oid),
2829 Err(Error::ObjectNotFound(_) | Error::InvalidRef(_)) if ignore_missing => {}
2830 Err(err) => return Err(err),
2831 }
2832 }
2833 Ok(out)
2834}
2835
2836pub fn resolve_revision_commits(repo: &Repository, specs: &[String]) -> Result<Vec<ObjectId>> {
2841 resolve_specs(repo, specs)
2842}
2843
2844pub fn resolve_revision_specs_to_commits(
2850 repo: &Repository,
2851 specs: &[String],
2852) -> Result<Vec<ObjectId>> {
2853 resolve_specs(repo, specs)
2854}
2855
2856pub fn resolve_object_walk_roots(
2857 repo: &Repository,
2858 specs: &[String],
2859) -> Result<(Vec<ObjectId>, Vec<ObjectWalkRoot>)> {
2860 let (commits, roots, _tip_annotated_tag_by_commit) = resolve_specs_for_objects(repo, specs)?;
2861 Ok((
2862 commits,
2863 roots
2864 .into_iter()
2865 .map(|r| ObjectWalkRoot {
2866 oid: r.oid,
2867 input: r.input,
2868 root_path: r.root_path,
2869 })
2870 .collect(),
2871 ))
2872}
2873
2874fn resolve_specs_for_objects(
2875 repo: &Repository,
2876 specs: &[String],
2877) -> Result<(Vec<ObjectId>, Vec<RootObject>, HashMap<ObjectId, ObjectId>)> {
2878 resolve_specs_for_objects_with_options(repo, specs, false, MissingAction::Error)
2879}
2880
2881fn resolve_specs_for_objects_with_options(
2882 repo: &Repository,
2883 specs: &[String],
2884 ignore_missing: bool,
2885 missing_action: MissingAction,
2886) -> Result<(Vec<ObjectId>, Vec<RootObject>, HashMap<ObjectId, ObjectId>)> {
2887 let mut commits = Vec::new();
2888 let mut roots = Vec::new();
2889 let mut tip_annotated_tag_by_commit: HashMap<ObjectId, ObjectId> = HashMap::new();
2890
2891 for spec in specs {
2892 if let Ok(raw_oid) = spec.parse::<ObjectId>() {
2893 let raw_object = match repo.odb.read(&raw_oid) {
2894 Ok(obj) => obj,
2895 Err(Error::ObjectNotFound(_)) if ignore_missing => continue,
2896 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
2897 roots.push(RootObject {
2898 oid: raw_oid,
2899 input: spec.clone(),
2900 expected_kind: None,
2901 root_path: None,
2902 wrap_with_tag: None,
2903 });
2904 continue;
2905 }
2906 Err(err) => return Err(err),
2907 };
2908 match raw_object.kind {
2909 ObjectKind::Commit => {
2910 commits.push(raw_oid);
2911 }
2912 ObjectKind::Tag => {
2913 let tag = parse_tag(&raw_object.data)?;
2914 let expected_kind = ExpectedObjectKind::from_tag_type(&tag.object_type)
2915 .ok_or_else(|| {
2916 Error::CorruptObject(format!(
2917 "object {spec} has unsupported tag type '{}'",
2918 tag.object_type
2919 ))
2920 })?;
2921 if expected_kind == ExpectedObjectKind::Commit {
2922 tip_annotated_tag_by_commit.insert(tag.object, raw_oid);
2923 }
2924 roots.push(RootObject {
2925 oid: tag.object,
2926 input: spec.clone(),
2927 expected_kind: Some(expected_kind),
2928 root_path: None,
2929 wrap_with_tag: Some(raw_oid),
2930 });
2931 }
2932 ObjectKind::Tree | ObjectKind::Blob => roots.push(RootObject {
2933 oid: raw_oid,
2934 input: spec.clone(),
2935 expected_kind: None,
2936 root_path: None,
2937 wrap_with_tag: None,
2938 }),
2939 }
2940 continue;
2941 }
2942
2943 if let Some((treeish, path)) = split_treeish_spec(spec) {
2944 if !path.is_empty() {
2945 let treeish_oid = match resolve_revision_for_range_end(repo, treeish) {
2946 Ok(oid) => oid,
2947 Err(Error::ObjectNotFound(_) | Error::InvalidRef(_)) if ignore_missing => {
2948 continue;
2949 }
2950 Err(err) => return Err(err),
2951 };
2952 let object_oid = match resolve_treeish_path(repo, treeish_oid, path) {
2953 Ok(oid) => oid,
2954 Err(Error::ObjectNotFound(_) | Error::InvalidRef(_)) if ignore_missing => {
2955 continue;
2956 }
2957 Err(err) => return Err(err),
2958 };
2959 let expected_kind = match repo.odb.read(&object_oid) {
2960 Ok(object) => ExpectedObjectKind::from_object_kind(object.kind),
2961 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => None,
2962 Err(Error::ObjectNotFound(_)) if ignore_missing => continue,
2963 Err(err) => return Err(err),
2964 };
2965 roots.push(RootObject {
2966 oid: object_oid,
2967 input: spec.clone(),
2968 expected_kind,
2969 root_path: Some(path.to_owned()),
2970 wrap_with_tag: None,
2971 });
2972 continue;
2973 }
2974 }
2975
2976 let oid = match resolve_revision_for_range_end(repo, spec) {
2977 Ok(oid) => oid,
2978 Err(Error::ObjectNotFound(_) | Error::InvalidRef(_)) if ignore_missing => continue,
2979 Err(err) => return Err(err),
2980 };
2981 if let Ok(obj) = repo.odb.read(&oid) {
2982 if obj.kind == ObjectKind::Tag {
2983 if let Ok(commit_oid) = peel_to_commit(repo, oid) {
2984 tip_annotated_tag_by_commit.insert(commit_oid, oid);
2985 }
2986 }
2987 }
2988 match peel_to_commit(repo, oid) {
2989 Ok(commit_oid) => commits.push(commit_oid),
2990 Err(Error::CorruptObject(_)) if ignore_missing => {}
2991 Err(Error::ObjectNotFound(_)) if ignore_missing => {}
2992 Err(Error::CorruptObject(_)) | Err(Error::ObjectNotFound(_)) => {
2993 roots.push(RootObject {
2994 oid,
2995 input: spec.clone(),
2996 expected_kind: None,
2997 root_path: None,
2998 wrap_with_tag: None,
2999 })
3000 }
3001 Err(err) => return Err(err),
3002 }
3003 }
3004
3005 Ok((commits, roots, tip_annotated_tag_by_commit))
3006}
3007
3008fn peel_to_commit(repo: &Repository, mut oid: ObjectId) -> Result<ObjectId> {
3010 loop {
3011 let object = repo.odb.read(&oid)?;
3012 match object.kind {
3013 ObjectKind::Commit => return Ok(oid),
3014 ObjectKind::Tag => {
3015 let tag = parse_tag(&object.data)?;
3016 oid = tag.object;
3017 }
3018 other => {
3019 return Err(Error::CorruptObject(format!(
3020 "object {oid} is a {other:?}, not a commit"
3021 )));
3022 }
3023 }
3024 }
3025}
3026
3027fn reflog_commit_tips(repo: &Repository) -> Result<Vec<ObjectId>> {
3028 let z = zero_oid();
3029 let mut out = Vec::new();
3030 let mut seen = HashSet::new();
3031 for refname in list_reflog_refs(&repo.git_dir)? {
3032 let entries = read_reflog(&repo.git_dir, &refname)?;
3033 for e in entries {
3034 for oid in [e.old_oid, e.new_oid] {
3035 if oid == z {
3036 continue;
3037 }
3038 match peel_to_commit(repo, oid) {
3039 Ok(c) if seen.insert(c) => out.push(c),
3040 Err(_) => {}
3041 _ => {}
3042 }
3043 }
3044 }
3045 }
3046 Ok(out)
3047}
3048
3049pub(crate) fn walk_closure(
3050 graph: &mut CommitGraph<'_>,
3051 starts: &[ObjectId],
3052) -> Result<HashSet<ObjectId>> {
3053 let (seen, _) = walk_closure_ordered(graph, starts)?;
3054 Ok(seen)
3055}
3056
3057fn walk_closure_first_parent_only(
3060 graph: &mut CommitGraph<'_>,
3061 starts: &[ObjectId],
3062) -> Result<HashSet<ObjectId>> {
3063 let mut seen = HashSet::new();
3064 let mut queue = VecDeque::new();
3065 for &start in starts {
3066 queue.push_back(start);
3067 }
3068 while let Some(oid) = queue.pop_front() {
3069 if !seen.insert(oid) {
3070 continue;
3071 }
3072 let parents = graph.parents_of(oid)?;
3073 if let Some(&p) = parents.first() {
3074 queue.push_back(p);
3075 }
3076 }
3077 Ok(seen)
3078}
3079
3080pub(crate) fn walk_closure_ordered(
3082 graph: &mut CommitGraph<'_>,
3083 starts: &[ObjectId],
3084) -> Result<(HashSet<ObjectId>, Vec<ObjectId>)> {
3085 walk_closure_ordered_excluding(graph, starts, &HashSet::new())
3086}
3087
3088fn walk_closure_ordered_excluding(
3089 graph: &mut CommitGraph<'_>,
3090 starts: &[ObjectId],
3091 excluded: &HashSet<ObjectId>,
3092) -> Result<(HashSet<ObjectId>, Vec<ObjectId>)> {
3093 let mut seen = HashSet::new();
3094 let mut order = Vec::new();
3095 let mut queue = VecDeque::new();
3096 for &start in starts {
3097 queue.push_back(start);
3098 }
3099 while let Some(oid) = queue.pop_front() {
3100 if excluded.contains(&oid) {
3101 continue;
3102 }
3103 if !seen.insert(oid) {
3104 continue;
3105 }
3106 order.push(oid);
3107 for parent in graph.parents_of(oid)? {
3108 queue.push_back(parent);
3109 }
3110 }
3111 Ok((seen, order))
3112}
3113
3114fn walk_closure_ordered_with_missing(
3115 graph: &mut CommitGraph<'_>,
3116 starts: &[ObjectId],
3117 excluded: &HashSet<ObjectId>,
3118 missing_action: MissingAction,
3119 missing: &mut Vec<ObjectId>,
3120 missing_seen: &mut HashSet<ObjectId>,
3121) -> Result<(HashSet<ObjectId>, Vec<ObjectId>)> {
3122 let mut seen = HashSet::new();
3123 let mut order = Vec::new();
3124 let mut queue = VecDeque::new();
3125 for &start in starts {
3126 queue.push_back(start);
3127 }
3128 while let Some(oid) = queue.pop_front() {
3129 if excluded.contains(&oid) || seen.contains(&oid) {
3130 continue;
3131 }
3132 let parents = match graph.parents_of(oid) {
3133 Ok(parents) => parents,
3134 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
3135 if missing_action.reports_missing() && missing_seen.insert(oid) {
3136 missing.push(oid);
3137 }
3138 continue;
3139 }
3140 Err(err) => return Err(err),
3141 };
3142 seen.insert(oid);
3143 order.push(oid);
3144 for parent in parents {
3145 queue.push_back(parent);
3146 }
3147 }
3148 Ok((seen, order))
3149}
3150
3151fn date_order_walk_with_tips(
3155 graph: &mut CommitGraph<'_>,
3156 tips: &[ObjectId],
3157 selected: &HashSet<ObjectId>,
3158 author_dates: bool,
3159) -> Result<Vec<ObjectId>> {
3160 let mut unfinished_children: HashMap<ObjectId, usize> =
3161 selected.iter().map(|&oid| (oid, 0usize)).collect();
3162 for &child in selected {
3163 for parent in graph.parents_of(child)? {
3164 if selected.contains(&parent) {
3165 if let Some(count) = unfinished_children.get_mut(&parent) {
3166 *count += 1;
3167 }
3168 }
3169 }
3170 }
3171
3172 let mut heap = BinaryHeap::new();
3173 for &tip in tips {
3174 if selected.contains(&tip) {
3175 heap.push(CommitDateKey {
3176 oid: tip,
3177 date: graph.sort_key(tip, author_dates),
3178 });
3179 }
3180 }
3181
3182 let mut emitted = HashSet::new();
3183 let mut out = Vec::with_capacity(selected.len());
3184 while let Some(item) = heap.pop() {
3185 if !emitted.insert(item.oid) {
3186 continue;
3187 }
3188 out.push(item.oid);
3189 for parent in graph.parents_of(item.oid)? {
3190 if !selected.contains(&parent) {
3191 continue;
3192 }
3193 let Some(count) = unfinished_children.get_mut(&parent) else {
3194 continue;
3195 };
3196 *count = count.saturating_sub(1);
3197 if *count == 0 {
3198 heap.push(CommitDateKey {
3199 oid: parent,
3200 date: graph.sort_key(parent, author_dates),
3201 });
3202 }
3203 }
3204 }
3205
3206 Ok(out)
3207}
3208
3209pub(crate) fn date_order_walk(
3216 graph: &mut CommitGraph<'_>,
3217 selected: &HashSet<ObjectId>,
3218 author_dates: bool,
3219) -> Result<Vec<ObjectId>> {
3220 let mut unfinished_children: HashMap<ObjectId, usize> =
3221 selected.iter().map(|&oid| (oid, 0usize)).collect();
3222 for &child in selected {
3223 for parent in graph.parents_of(child)? {
3224 if selected.contains(&parent) {
3225 if let Some(count) = unfinished_children.get_mut(&parent) {
3226 *count += 1;
3227 }
3228 }
3229 }
3230 }
3231
3232 let mut heap = BinaryHeap::new();
3238 for &oid in selected {
3239 if unfinished_children.get(&oid).copied().unwrap_or(0) == 0 {
3240 heap.push(CommitDateKey {
3241 oid,
3242 date: graph.sort_key(oid, author_dates),
3243 });
3244 }
3245 }
3246
3247 let mut emitted = HashSet::new();
3248 let mut out = Vec::with_capacity(selected.len());
3249 while let Some(item) = heap.pop() {
3250 if !emitted.insert(item.oid) {
3251 continue;
3252 }
3253 out.push(item.oid);
3254 for parent in graph.parents_of(item.oid)? {
3255 if !selected.contains(&parent) {
3256 continue;
3257 }
3258 let Some(count) = unfinished_children.get_mut(&parent) else {
3259 continue;
3260 };
3261 *count = count.saturating_sub(1);
3262 if *count == 0 {
3263 heap.push(CommitDateKey {
3264 oid: parent,
3265 date: graph.sort_key(parent, author_dates),
3266 });
3267 }
3268 }
3269 }
3270
3271 Ok(out)
3272}
3273
3274fn topo_sort(
3275 graph: &mut CommitGraph<'_>,
3276 selected: &HashSet<ObjectId>,
3277 author_dates: bool,
3278) -> Result<Vec<ObjectId>> {
3279 let mut child_count: HashMap<ObjectId, usize> = selected.iter().map(|&oid| (oid, 0)).collect();
3280
3281 for &oid in selected {
3282 for parent in graph.parents_of(oid)? {
3283 if !selected.contains(&parent) {
3284 continue;
3285 }
3286 if let Some(count) = child_count.get_mut(&parent) {
3287 *count += 1;
3288 }
3289 }
3290 }
3291
3292 let mut ready: BinaryHeap<Reverse<CommitDateKey>> = BinaryHeap::new();
3296 for (&oid, &count) in &child_count {
3297 if count == 0 {
3298 ready.push(Reverse(CommitDateKey {
3299 oid,
3300 date: graph.sort_key(oid, author_dates),
3301 }));
3302 }
3303 }
3304
3305 let mut out = Vec::with_capacity(selected.len());
3306 while let Some(Reverse(item)) = ready.pop() {
3307 let oid = item.oid;
3308 out.push(oid);
3309 for parent in graph.parents_of(oid)? {
3310 if !selected.contains(&parent) {
3311 continue;
3312 }
3313 if let Some(count) = child_count.get_mut(&parent) {
3314 *count = count.saturating_sub(1);
3315 if *count == 0 {
3316 ready.push(Reverse(CommitDateKey {
3317 oid: parent,
3318 date: graph.sort_key(parent, author_dates),
3319 }));
3320 }
3321 }
3322 }
3323 }
3324
3325 reorder_adjacent_merge_parent_blocks(graph, &mut out)?;
3326 Ok(out)
3327}
3328
3329fn graph_order_topo_sort(
3330 graph: &mut CommitGraph<'_>,
3331 selected: &HashSet<ObjectId>,
3332 discovery_order: &[ObjectId],
3333) -> Result<Vec<ObjectId>> {
3334 let mut child_count: HashMap<ObjectId, usize> = selected.iter().map(|&oid| (oid, 0)).collect();
3335
3336 for &oid in selected {
3337 for parent in graph.parents_of(oid)? {
3338 if !selected.contains(&parent) {
3339 continue;
3340 }
3341 if let Some(count) = child_count.get_mut(&parent) {
3342 *count += 1;
3343 }
3344 }
3345 }
3346
3347 let discovery_rank: HashMap<ObjectId, usize> = discovery_order
3348 .iter()
3349 .copied()
3350 .enumerate()
3351 .map(|(rank, oid)| (oid, rank))
3352 .collect();
3353 let mut ready: Vec<ObjectId> = child_count
3354 .iter()
3355 .filter_map(|(&oid, &count)| (count == 0).then_some(oid))
3356 .collect();
3357
3358 ready.sort_by(|&a, &b| {
3359 graph
3360 .committer_time(a)
3361 .cmp(&graph.committer_time(b))
3362 .then_with(|| {
3363 discovery_rank
3364 .get(&b)
3365 .copied()
3366 .unwrap_or(usize::MAX)
3367 .cmp(&discovery_rank.get(&a).copied().unwrap_or(usize::MAX))
3368 })
3369 .then_with(|| a.cmp(&b))
3370 });
3371
3372 let mut out = Vec::with_capacity(selected.len());
3373 while let Some(oid) = ready.pop() {
3374 out.push(oid);
3375 for parent in graph.parents_of(oid)? {
3376 if !selected.contains(&parent) {
3377 continue;
3378 }
3379 if let Some(count) = child_count.get_mut(&parent) {
3380 *count = count.saturating_sub(1);
3381 if *count == 0 {
3382 ready.push(parent);
3383 }
3384 }
3385 }
3386 }
3387
3388 reorder_adjacent_merge_parent_blocks(graph, &mut out)?;
3389 Ok(out)
3390}
3391
3392fn reorder_adjacent_merge_parent_blocks(
3393 graph: &mut CommitGraph<'_>,
3394 ordered: &mut [ObjectId],
3395) -> Result<()> {
3396 for index in 0..ordered.len() {
3397 let parents = graph.parents_of(ordered[index])?;
3398 if parents.len() <= 1 || index + parents.len() >= ordered.len() {
3399 continue;
3400 }
3401 let parent_set: HashSet<ObjectId> = parents.iter().copied().collect();
3402 let end = index + 1 + parents.len();
3403 if ordered[index + 1..end]
3404 .iter()
3405 .all(|oid| parent_set.contains(oid))
3406 {
3407 let rank: HashMap<ObjectId, usize> = parents
3408 .iter()
3409 .rev()
3410 .copied()
3411 .enumerate()
3412 .map(|(rank, oid)| (oid, rank))
3413 .collect();
3414 ordered[index + 1..end].sort_by_key(|oid| rank.get(oid).copied().unwrap_or(usize::MAX));
3415 }
3416 }
3417 Ok(())
3418}
3419
3420fn reorder_symmetric_topo_right_first(
3421 graph: &mut CommitGraph<'_>,
3422 ordered: &mut Vec<ObjectId>,
3423 left_oid: ObjectId,
3424 right_oid: ObjectId,
3425) -> Result<()> {
3426 if ordered.len() < 2 {
3427 return Ok(());
3428 }
3429
3430 let left_reach = walk_closure(graph, &[left_oid])?;
3431 let right_reach = walk_closure(graph, &[right_oid])?;
3432 let mut right_only = Vec::new();
3433 let mut rest = Vec::new();
3434
3435 for oid in ordered.drain(..) {
3436 if right_reach.contains(&oid) && !left_reach.contains(&oid) {
3437 right_only.push(oid);
3438 } else {
3439 rest.push(oid);
3440 }
3441 }
3442
3443 right_only.extend(rest);
3444 *ordered = right_only;
3445 Ok(())
3446}
3447
3448fn simplify_merges_commit_list(
3449 repo: &Repository,
3450 commits: &[ObjectId],
3451 paths: &[String],
3452 excluded: &HashSet<ObjectId>,
3453 ancestry_path_bottoms: &[ObjectId],
3454 ordering: OrderingMode,
3455 show_pulls: bool,
3456 preserve_graph_merges: bool,
3457) -> Result<Vec<ObjectId>> {
3458 let selected: HashSet<ObjectId> = commits.iter().copied().collect();
3459 let mut out = Vec::new();
3460 let mut simplified_parents: HashMap<ObjectId, Vec<ObjectId>> = HashMap::new();
3461 for oid in commits {
3462 let raw_parents = load_commit(repo, *oid)?.parents;
3463 let rewritten = visible_parents_for_graph_lib(repo, *oid, &selected, false)?;
3464 let path_survives =
3465 path_merge_survives_simplify(repo, *oid, paths, excluded, ancestry_path_bottoms)?;
3466 let pull_merge = show_pulls
3467 && raw_parents.len() > 1
3468 && path_merge_survives_simplify(repo, *oid, paths, excluded, &[])?;
3469 if raw_parents.len() > 1 && rewritten.len() <= 1 {
3470 if rewritten.is_empty() || pull_merge || path_survives {
3471 simplified_parents.insert(*oid, rewritten);
3472 out.push(*oid);
3473 }
3474 continue;
3475 }
3476 if rewritten.len() <= 1 {
3477 simplified_parents.insert(*oid, rewritten);
3478 out.push(*oid);
3479 continue;
3480 }
3481 let mut simplified = if preserve_graph_merges {
3482 let treesame_rewritten = path_treesame_parents(repo, *oid, paths, &rewritten)?;
3483 let all_rewritten_treesame =
3484 !rewritten.is_empty() && treesame_rewritten.len() == rewritten.len();
3485 if all_rewritten_treesame {
3486 vec![rewritten[0]]
3487 } else {
3488 graph_simplify_parent_list_lib(repo, &selected, &rewritten, &treesame_rewritten)?
3489 }
3490 } else {
3491 graph_simplify_parent_list_lib(repo, &selected, &rewritten, &HashSet::new())?
3492 };
3493 simplified.sort_unstable();
3494 simplified.dedup();
3495 if simplified.len() > 1 || pull_merge {
3496 simplified_parents.insert(*oid, simplified);
3497 out.push(*oid);
3498 }
3499 }
3500 if ordering == OrderingMode::AuthorDateTopo {
3501 return simplify_merges_author_date_order(repo, &out, &simplified_parents);
3502 }
3503 Ok(out)
3504}
3505
3506fn simplify_merges_author_date_order(
3507 repo: &Repository,
3508 commits: &[ObjectId],
3509 simplified_parents: &HashMap<ObjectId, Vec<ObjectId>>,
3510) -> Result<Vec<ObjectId>> {
3511 let selected: HashSet<ObjectId> = commits.iter().copied().collect();
3512 let mut child_count: HashMap<ObjectId, usize> =
3513 commits.iter().copied().map(|oid| (oid, 0)).collect();
3514 for parents in simplified_parents.values() {
3515 for parent in parents {
3516 if selected.contains(parent) {
3517 if let Some(count) = child_count.get_mut(parent) {
3518 *count += 1;
3519 }
3520 }
3521 }
3522 }
3523
3524 let mut graph = CommitGraph::new(repo, false);
3525 let mut ready = BinaryHeap::new();
3526 for oid in commits {
3527 if child_count.get(oid).copied().unwrap_or_default() == 0 {
3528 ready.push(CommitDateKey {
3529 oid: *oid,
3530 date: graph.sort_key(*oid, true),
3531 });
3532 }
3533 }
3534
3535 let mut out = Vec::with_capacity(commits.len());
3536 let mut emitted = HashSet::new();
3537 while let Some(item) = ready.pop() {
3538 if !emitted.insert(item.oid) {
3539 continue;
3540 }
3541 out.push(item.oid);
3542 if let Some(parents) = simplified_parents.get(&item.oid) {
3543 for parent in parents {
3544 if !selected.contains(parent) {
3545 continue;
3546 }
3547 let Some(count) = child_count.get_mut(parent) else {
3548 continue;
3549 };
3550 *count = count.saturating_sub(1);
3551 if *count == 0 {
3552 ready.push(CommitDateKey {
3553 oid: *parent,
3554 date: graph.sort_key(*parent, true),
3555 });
3556 }
3557 }
3558 }
3559 }
3560 Ok(out)
3561}
3562
3563fn path_merge_survives_simplify(
3564 repo: &Repository,
3565 oid: ObjectId,
3566 paths: &[String],
3567 excluded: &HashSet<ObjectId>,
3568 ancestry_path_bottoms: &[ObjectId],
3569) -> Result<bool> {
3570 if paths.is_empty() {
3571 return Ok(false);
3572 }
3573 let commit = load_commit(repo, oid)?;
3574 if commit.parents.len() <= 1 {
3575 return Ok(false);
3576 }
3577 let commit_map: HashMap<String, ObjectId> =
3578 flatten_tree(repo, commit.tree, "")?.into_iter().collect();
3579 let mut treesame_parents = 0usize;
3580 let mut first_parent_differs = false;
3581 let mut differing_parent_is_excluded = false;
3582 let mut differing_parent_oids = Vec::new();
3583 for (nth, parent_oid) in commit.parents.iter().enumerate() {
3584 let parent = load_commit(repo, *parent_oid)?;
3585 let parent_map: HashMap<String, ObjectId> =
3586 flatten_tree(repo, parent.tree, "")?.into_iter().collect();
3587 let differs = path_differs_for_specs(&commit_map, &parent_map, paths);
3588 if differs {
3589 differing_parent_oids.push(*parent_oid);
3590 if nth == 0 {
3591 first_parent_differs = true;
3592 }
3593 if excluded.contains(parent_oid) {
3594 differing_parent_is_excluded = true;
3595 }
3596 } else {
3597 treesame_parents += 1;
3598 }
3599 }
3600 if treesame_parents != 1 {
3601 return Ok(false);
3602 }
3603 if first_parent_differs || differing_parent_is_excluded {
3604 return Ok(true);
3605 }
3606 if !ancestry_path_bottoms.is_empty() {
3607 let mut graph = CommitGraph::new(repo, false);
3608 return treesame_parent_is_on_ancestry_bottom_side(
3609 &mut graph,
3610 &differing_parent_oids,
3611 ancestry_path_bottoms,
3612 );
3613 }
3614 Ok(false)
3615}
3616
3617fn graph_simplify_parent_list_lib(
3618 repo: &Repository,
3619 selected: &HashSet<ObjectId>,
3620 parents: &[ObjectId],
3621 protected_treesame: &HashSet<ObjectId>,
3622) -> Result<Vec<ObjectId>> {
3623 let mut out = Vec::new();
3624 let protected_count = parents
3625 .iter()
3626 .filter(|parent| protected_treesame.contains(parent))
3627 .count();
3628 for parent in parents {
3629 if parent_reachable_via_others_lib(repo, selected, *parent, parents)? {
3630 if protected_count == 1 && protected_treesame.contains(parent) {
3631 out.push(*parent);
3632 }
3633 continue;
3634 }
3635 out.push(*parent);
3636 }
3637 Ok(out)
3638}
3639
3640fn path_treesame_parents(
3641 repo: &Repository,
3642 oid: ObjectId,
3643 paths: &[String],
3644 parents: &[ObjectId],
3645) -> Result<HashSet<ObjectId>> {
3646 let mut treesame = HashSet::new();
3647 if paths.is_empty() || parents.is_empty() {
3648 return Ok(treesame);
3649 }
3650
3651 let commit = load_commit(repo, oid)?;
3652 let commit_map: HashMap<String, ObjectId> =
3653 flatten_tree(repo, commit.tree, "")?.into_iter().collect();
3654 for parent_oid in parents {
3655 let parent = load_commit(repo, *parent_oid)?;
3656 let parent_map: HashMap<String, ObjectId> =
3657 flatten_tree(repo, parent.tree, "")?.into_iter().collect();
3658 if !path_differs_for_specs(&commit_map, &parent_map, paths) {
3659 treesame.insert(*parent_oid);
3660 }
3661 }
3662 Ok(treesame)
3663}
3664
3665fn parent_reachable_via_others_lib(
3666 repo: &Repository,
3667 selected: &HashSet<ObjectId>,
3668 target: ObjectId,
3669 parents: &[ObjectId],
3670) -> Result<bool> {
3671 for parent in parents {
3672 if *parent == target {
3673 continue;
3674 }
3675 if graph_reaches_lib(repo, selected, *parent, target)? {
3676 return Ok(true);
3677 }
3678 }
3679 Ok(false)
3680}
3681
3682fn graph_reaches_lib(
3683 repo: &Repository,
3684 selected: &HashSet<ObjectId>,
3685 start: ObjectId,
3686 target: ObjectId,
3687) -> Result<bool> {
3688 let mut stack = vec![start];
3689 let mut seen = HashSet::new();
3690 while let Some(oid) = stack.pop() {
3691 if !seen.insert(oid) {
3692 continue;
3693 }
3694 if oid == target {
3695 return Ok(true);
3696 }
3697 let mut parents = load_commit(repo, oid)?.parents;
3698 parents.retain(|p| selected.contains(p));
3699 stack.extend(parents);
3700 }
3701 Ok(false)
3702}
3703
3704fn load_raw_parents_lib(repo: &Repository, oid: ObjectId) -> Result<Vec<ObjectId>> {
3705 Ok(load_commit(repo, oid)?.parents)
3706}
3707
3708fn load_navigation_parents_lib(
3709 repo: &Repository,
3710 oid: ObjectId,
3711 grafts: &HashMap<ObjectId, Vec<ObjectId>>,
3712) -> Result<Vec<ObjectId>> {
3713 if let Some(grafted) = grafts.get(&oid) {
3714 return Ok(grafted.clone());
3715 }
3716 load_raw_parents_lib(repo, oid)
3717}
3718
3719fn first_parent_of_commit_lib(
3720 repo: &Repository,
3721 oid: ObjectId,
3722 grafts: &HashMap<ObjectId, Vec<ObjectId>>,
3723) -> Result<Option<ObjectId>> {
3724 let parents = load_navigation_parents_lib(repo, oid, grafts)?;
3725 Ok(parents.first().copied())
3726}
3727
3728fn first_parent_anchor_in_set_lib(
3729 repo: &Repository,
3730 start: ObjectId,
3731 anchors: &HashSet<ObjectId>,
3732 grafts: &HashMap<ObjectId, Vec<ObjectId>>,
3733) -> Result<Option<ObjectId>> {
3734 let mut seen = HashSet::new();
3735 let mut cursor = Some(start);
3736 while let Some(oid) = cursor {
3737 if !seen.insert(oid) {
3738 break;
3739 }
3740 if anchors.contains(&oid) {
3741 return Ok(Some(oid));
3742 }
3743 cursor = first_parent_of_commit_lib(repo, oid, grafts)?;
3744 }
3745 Ok(None)
3746}
3747
3748fn collect_visible_parent_for_graph_lib(
3749 repo: &Repository,
3750 candidate: ObjectId,
3751 included: &HashSet<ObjectId>,
3752 first_parent_only: bool,
3753 grafts: &HashMap<ObjectId, Vec<ObjectId>>,
3754 seen: &mut HashSet<ObjectId>,
3755 out: &mut Vec<ObjectId>,
3756) -> Result<()> {
3757 if !seen.insert(candidate) {
3758 return Ok(());
3759 }
3760 if included.contains(&candidate) {
3761 out.push(candidate);
3762 return Ok(());
3763 }
3764 let mut parents = load_navigation_parents_lib(repo, candidate, grafts)?;
3765 if parents.is_empty() {
3766 return Ok(());
3767 }
3768 if parents.len() > 1 {
3769 parents.truncate(1);
3770 }
3771 for parent in parents {
3772 collect_visible_parent_for_graph_lib(
3773 repo,
3774 parent,
3775 included,
3776 first_parent_only,
3777 grafts,
3778 seen,
3779 out,
3780 )?;
3781 }
3782 Ok(())
3783}
3784
3785fn visible_parents_for_graph_lib(
3786 repo: &Repository,
3787 oid: ObjectId,
3788 included: &HashSet<ObjectId>,
3789 first_parent_only: bool,
3790) -> Result<Vec<ObjectId>> {
3791 let grafts = crate::rev_parse::load_graft_parents(&repo.git_dir);
3792 let mut direct = load_navigation_parents_lib(repo, oid, &grafts)?;
3793 if first_parent_only && direct.len() > 1 {
3794 direct.truncate(1);
3795 }
3796 let mut seen = HashSet::new();
3797 let mut out = Vec::new();
3798 for parent in direct {
3799 collect_visible_parent_for_graph_lib(
3800 repo,
3801 parent,
3802 included,
3803 first_parent_only,
3804 &grafts,
3805 &mut seen,
3806 &mut out,
3807 )?;
3808 }
3809 let mut dedup = HashSet::new();
3810 out.retain(|parent| dedup.insert(*parent));
3811 Ok(out)
3812}
3813
3814fn reorder_path_limited_graph_commits(
3815 repo: &Repository,
3816 commits: &[ObjectId],
3817 first_parent_only: bool,
3818) -> Result<Vec<ObjectId>> {
3819 if commits.is_empty() {
3820 return Ok(Vec::new());
3821 }
3822
3823 if !first_parent_only {
3831 return Ok(commits.to_vec());
3832 }
3833
3834 let included: HashSet<ObjectId> = commits.iter().copied().collect();
3835 let grafts = crate::rev_parse::load_graft_parents(&repo.git_dir);
3836 let mut chain = Vec::new();
3837 let mut chain_seen = HashSet::new();
3838 let mut cursor = Some(commits[0]);
3839 while let Some(oid) = cursor {
3840 if !included.contains(&oid) || !chain_seen.insert(oid) {
3841 break;
3842 }
3843 chain.push(oid);
3844 let visible = visible_parents_for_graph_lib(repo, oid, &included, first_parent_only)?;
3845 cursor = visible.first().copied();
3846 }
3847
3848 let chain_set: HashSet<ObjectId> = chain.iter().copied().collect();
3849 let mut grouped: HashMap<Option<ObjectId>, Vec<ObjectId>> = HashMap::new();
3850 for oid in commits {
3851 if chain_set.contains(oid) {
3852 continue;
3853 }
3854 let anchor = first_parent_anchor_in_set_lib(repo, *oid, &chain_set, &grafts)?;
3855 grouped.entry(anchor).or_default().push(*oid);
3856 }
3857
3858 let mut ordered = Vec::new();
3859 for chain_oid in chain {
3860 if let Some(group) = grouped.remove(&Some(chain_oid)) {
3861 ordered.extend(group);
3862 }
3863 ordered.push(chain_oid);
3864 }
3865 if let Some(group) = grouped.remove(&None) {
3866 ordered.extend(group);
3867 }
3868 for (_anchor, group) in grouped {
3869 ordered.extend(group);
3870 }
3871 Ok(ordered)
3872}
3873
3874fn expand_sparse_path_limited_graph_history(
3875 repo: &Repository,
3876 commits: &[ObjectId],
3877) -> Result<Vec<ObjectId>> {
3878 if commits.is_empty() {
3879 return Ok(Vec::new());
3880 }
3881
3882 let mut expanded = Vec::new();
3883 let mut seen = HashSet::new();
3884 let grafts = crate::rev_parse::load_graft_parents(&repo.git_dir);
3885 let mut push_unique = |oid: ObjectId, out: &mut Vec<ObjectId>| {
3886 if seen.insert(oid) {
3887 out.push(oid);
3888 }
3889 };
3890
3891 for window in commits.windows(2) {
3892 let from = window[0];
3893 let to = window[1];
3894 push_unique(from, &mut expanded);
3895
3896 let mut cursor = first_parent_of_commit_lib(repo, from, &grafts)?;
3897 let mut chain = Vec::new();
3898 let mut found_target = false;
3899 let mut local_seen = HashSet::new();
3900 while let Some(oid) = cursor {
3901 if !local_seen.insert(oid) {
3902 break;
3903 }
3904 if oid == to {
3905 found_target = true;
3906 break;
3907 }
3908 chain.push(oid);
3909 cursor = first_parent_of_commit_lib(repo, oid, &grafts)?;
3910 }
3911 if found_target {
3912 for oid in chain {
3913 push_unique(oid, &mut expanded);
3914 }
3915 }
3916 }
3917
3918 if let Some(&last) = commits.last() {
3919 push_unique(last, &mut expanded);
3920 let mut cursor = first_parent_of_commit_lib(repo, last, &grafts)?;
3921 let mut tail_seen = HashSet::new();
3922 while let Some(oid) = cursor {
3923 if !tail_seen.insert(oid) {
3924 break;
3925 }
3926 push_unique(oid, &mut expanded);
3927 cursor = first_parent_of_commit_lib(repo, oid, &grafts)?;
3928 }
3929 }
3930
3931 Ok(expanded)
3932}
3933
3934fn limit_to_ancestry(
3935 graph: &mut CommitGraph<'_>,
3936 selected: &mut HashSet<ObjectId>,
3937 bottoms: &[ObjectId],
3938) -> Result<()> {
3939 let mut keep = HashSet::new();
3940
3941 for &bottom in bottoms {
3942 let ancestors = walk_closure(graph, &[bottom])?;
3943 keep.extend(
3944 ancestors
3945 .iter()
3946 .copied()
3947 .filter(|oid| selected.contains(oid)),
3948 );
3949 }
3950
3951 let mut descendants: HashSet<ObjectId> = bottoms.iter().copied().collect();
3952 loop {
3953 let mut made_progress = false;
3954 for &candidate in selected.iter() {
3955 if descendants.contains(&candidate) {
3956 continue;
3957 }
3958
3959 let parents = graph.parents_of(candidate)?;
3960 if parents.iter().any(|parent| descendants.contains(parent)) {
3961 descendants.insert(candidate);
3962 made_progress = true;
3963 }
3964 }
3965
3966 if !made_progress {
3967 break;
3968 }
3969 }
3970
3971 keep.extend(
3972 descendants
3973 .iter()
3974 .copied()
3975 .filter(|oid| selected.contains(oid)),
3976 );
3977 selected.retain(|oid| keep.contains(oid));
3978 Ok(())
3979}
3980
3981#[allow(clippy::too_many_arguments)]
3987fn commit_touches_paths(
3988 repo: &Repository,
3989 graph: &mut CommitGraph<'_>,
3990 oid: ObjectId,
3991 paths: &[String],
3992 full_history: bool,
3993 sparse: bool,
3994 simplify_merges: bool,
3995 preserve_simplify_merges_graph_merges: bool,
3996 show_pulls: bool,
3997 parent_rewrite: bool,
3998 ancestry_path: bool,
3999 ancestry_path_bottoms: &[ObjectId],
4000 excluded: &HashSet<ObjectId>,
4001 bloom_chain: Option<&CommitGraphChain>,
4002 read_changed_paths: bool,
4003 changed_paths_version: i32,
4004 bloom_stats: Option<&BloomWalkStatsHandle>,
4005 bloom_cwd: Option<&str>,
4006) -> Result<bool> {
4007 let commit = load_commit(repo, oid)?;
4008 let parents = graph.parents_of(oid)?;
4009 let commit_entries = flatten_tree(repo, commit.tree, "")?;
4010 let commit_map: HashMap<String, ObjectId> = commit_entries.into_iter().collect();
4011
4012 if parents.is_empty() {
4014 if sparse {
4015 return Ok(true);
4016 }
4017 let ctx = crate::pathspec::PathspecMatchContext {
4018 is_directory: false,
4019 is_git_submodule: false,
4020 };
4021 return Ok(commit_map
4022 .keys()
4023 .any(|path| crate::pathspec::matches_pathspec_list_with_context(path, paths, ctx)));
4024 }
4025
4026 if parents.len() == 1 {
4028 let mut bloom_ret = BloomPrecheck::Inapplicable;
4029 if let Some(chain) = bloom_chain {
4030 bloom_ret = chain.bloom_precheck_for_paths(
4031 &repo.odb,
4032 oid,
4033 paths,
4034 bloom_cwd,
4035 changed_paths_version,
4036 read_changed_paths,
4037 )?;
4038 if let Some(stats) = bloom_stats {
4039 if let Ok(mut g) = stats.lock() {
4040 g.record_precheck(bloom_ret);
4041 }
4042 }
4043 if bloom_ret == BloomPrecheck::DefinitelyNot {
4044 return Ok(false);
4045 }
4046 }
4047
4048 let parent = load_commit(repo, parents[0])?;
4049 let parent_map: HashMap<String, ObjectId> =
4050 flatten_tree(repo, parent.tree, "")?.into_iter().collect();
4051 let differs = path_differs_for_specs(&commit_map, &parent_map, paths);
4052 if bloom_ret == BloomPrecheck::Maybe && !differs {
4053 if let Some(stats) = bloom_stats {
4054 if let Ok(mut g) = stats.lock() {
4055 g.record_false_positive();
4056 }
4057 }
4058 }
4059 if differs {
4060 return Ok(true);
4061 }
4062 if sparse {
4063 return Ok(true);
4064 }
4065 return Ok(false);
4066 }
4067
4068 let mut treesame_parents = 0usize;
4070 let mut treesame_parent_oids = Vec::new();
4071 let mut differing_parent_oids = Vec::new();
4072 let mut differs_any = false;
4073 let mut first_parent_differs = false;
4074 for (nth, parent_oid) in parents.iter().enumerate() {
4075 let mut bloom_ret = BloomPrecheck::Inapplicable;
4081 if nth == 0 {
4082 if let Some(chain) = bloom_chain {
4083 bloom_ret = chain.bloom_precheck_for_paths(
4084 &repo.odb,
4085 oid,
4086 paths,
4087 bloom_cwd,
4088 changed_paths_version,
4089 read_changed_paths,
4090 )?;
4091 if let Some(stats) = bloom_stats {
4092 if let Ok(mut g) = stats.lock() {
4093 g.record_precheck(bloom_ret);
4094 }
4095 }
4096 }
4097 }
4098
4099 let differs = if bloom_ret == BloomPrecheck::DefinitelyNot {
4100 false
4101 } else {
4102 let parent = load_commit(repo, *parent_oid)?;
4103 let parent_map: HashMap<String, ObjectId> =
4104 flatten_tree(repo, parent.tree, "")?.into_iter().collect();
4105 path_differs_for_specs(&commit_map, &parent_map, paths)
4106 };
4107 if nth == 0 {
4108 first_parent_differs = differs;
4109 if bloom_ret == BloomPrecheck::Maybe && !differs {
4110 if let Some(stats) = bloom_stats {
4111 if let Ok(mut g) = stats.lock() {
4112 g.record_false_positive();
4113 }
4114 }
4115 }
4116 }
4117 if differs {
4118 differs_any = true;
4119 differing_parent_oids.push(*parent_oid);
4120 } else {
4121 treesame_parents += 1;
4122 treesame_parent_oids.push(*parent_oid);
4123 }
4124 }
4125
4126 if ancestry_path
4127 && !ancestry_path_bottoms.is_empty()
4128 && if parent_rewrite {
4129 treesame_parents != parents.len()
4130 && treesame_parent_is_ancestry_bottom_or_ancestor(
4131 graph,
4132 &treesame_parent_oids,
4133 ancestry_path_bottoms,
4134 )?
4135 } else {
4136 treesame_parent_is_on_ancestry_bottom_side(
4137 graph,
4138 &treesame_parent_oids,
4139 ancestry_path_bottoms,
4140 )?
4141 }
4142 {
4143 return Ok(sparse);
4144 }
4145
4146 if full_history && !simplify_merges && parents.len() > 1 && treesame_parents == parents.len() {
4150 return Ok(parent_rewrite || sparse);
4151 }
4152
4153 if show_pulls && first_parent_differs && treesame_parents > 0 {
4154 return Ok(true);
4155 }
4156
4157 if !full_history && treesame_parents == 1 {
4158 return Ok(false);
4159 }
4160
4161 if full_history && simplify_merges {
4162 if preserve_simplify_merges_graph_merges {
4163 return Ok(true);
4164 }
4165 if treesame_parents == parents.len() {
4166 return Ok(true);
4167 }
4168 if treesame_parents > 0 && !differs_any {
4169 return Ok(true);
4170 }
4171 if show_pulls && first_parent_differs && treesame_parents > 0 {
4172 return Ok(true);
4173 }
4174 if treesame_parents == 1 {
4175 if ancestry_path
4176 && !ancestry_path_bottoms.is_empty()
4177 && treesame_parent_is_on_ancestry_bottom_side(
4178 graph,
4179 &differing_parent_oids,
4180 ancestry_path_bottoms,
4181 )?
4182 {
4183 return Ok(true);
4184 }
4185 return Ok(first_parent_differs
4186 || differing_parent_oids
4187 .iter()
4188 .any(|parent| excluded.contains(parent)));
4189 }
4190 return Ok(differs_any || sparse);
4191 }
4192
4193 if differs_any {
4194 return Ok(true);
4195 }
4196
4197 Ok(sparse)
4198}
4199
4200fn treesame_parent_is_on_ancestry_bottom_side(
4201 graph: &mut CommitGraph<'_>,
4202 treesame_parents: &[ObjectId],
4203 bottoms: &[ObjectId],
4204) -> Result<bool> {
4205 if treesame_parents.is_empty() {
4206 return Ok(false);
4207 }
4208 let treesame: HashSet<ObjectId> = treesame_parents.iter().copied().collect();
4209 for bottom in bottoms {
4210 if treesame.contains(bottom) {
4211 return Ok(true);
4212 }
4213 for parent in treesame_parents {
4214 let parent_closure = walk_closure(graph, &[*parent])?;
4215 if parent_closure.contains(bottom) {
4216 return Ok(true);
4217 }
4218 }
4219 }
4220 Ok(false)
4221}
4222
4223fn treesame_parent_is_ancestry_bottom_or_ancestor(
4224 graph: &mut CommitGraph<'_>,
4225 treesame_parents: &[ObjectId],
4226 bottoms: &[ObjectId],
4227) -> Result<bool> {
4228 if treesame_parents.is_empty() {
4229 return Ok(false);
4230 }
4231 let treesame: HashSet<ObjectId> = treesame_parents.iter().copied().collect();
4232 for bottom in bottoms {
4233 if treesame.contains(bottom) {
4234 return Ok(true);
4235 }
4236 let bottom_closure = walk_closure(graph, &[*bottom])?;
4237 if bottom_closure.iter().any(|oid| treesame.contains(oid)) {
4238 return Ok(true);
4239 }
4240 }
4241 Ok(false)
4242}
4243
4244fn walk_dense_path_limited_closure(
4245 repo: &Repository,
4246 graph: &mut CommitGraph<'_>,
4247 tips: &[ObjectId],
4248 excluded: &HashSet<ObjectId>,
4249 paths: &[String],
4250 sparse: bool,
4251 show_pulls: bool,
4252) -> Result<HashSet<ObjectId>> {
4253 let mut walked = HashSet::new();
4254 let mut selected = HashSet::new();
4255 let mut queue: VecDeque<ObjectId> = tips.iter().copied().collect();
4256
4257 while let Some(oid) = queue.pop_front() {
4258 if excluded.contains(&oid) || !walked.insert(oid) {
4259 continue;
4260 }
4261
4262 let action = dense_path_limited_action(repo, graph, oid, paths, sparse, show_pulls)?;
4263 if action.visible {
4264 selected.insert(oid);
4265 }
4266
4267 for parent in action.parents_to_walk {
4268 if !excluded.contains(&parent) && !walked.contains(&parent) {
4269 queue.push_back(parent);
4270 }
4271 }
4272 }
4273
4274 Ok(selected)
4275}
4276
4277struct DensePathAction {
4278 visible: bool,
4279 parents_to_walk: Vec<ObjectId>,
4280}
4281
4282fn dense_path_limited_action(
4283 repo: &Repository,
4284 graph: &mut CommitGraph<'_>,
4285 oid: ObjectId,
4286 paths: &[String],
4287 sparse: bool,
4288 show_pulls: bool,
4289) -> Result<DensePathAction> {
4290 let commit = load_commit(repo, oid)?;
4291 let parents = graph.parents_of(oid)?;
4292 let commit_map: HashMap<String, ObjectId> =
4293 flatten_tree(repo, commit.tree, "")?.into_iter().collect();
4294
4295 if parents.is_empty() {
4296 let ctx = crate::pathspec::PathspecMatchContext {
4297 is_directory: false,
4298 is_git_submodule: false,
4299 };
4300 let visible = sparse
4301 || commit_map
4302 .keys()
4303 .any(|path| crate::pathspec::matches_pathspec_list_with_context(path, paths, ctx));
4304 return Ok(DensePathAction {
4305 visible,
4306 parents_to_walk: Vec::new(),
4307 });
4308 }
4309
4310 let mut treesame = Vec::new();
4311 let mut differs_any = false;
4312 let mut first_parent_differs = false;
4313 for (nth, parent_oid) in parents.iter().enumerate() {
4314 let parent = load_commit(repo, *parent_oid)?;
4315 let parent_map: HashMap<String, ObjectId> =
4316 flatten_tree(repo, parent.tree, "")?.into_iter().collect();
4317 if path_differs_for_specs(&commit_map, &parent_map, paths) {
4318 if nth == 0 {
4319 first_parent_differs = true;
4320 }
4321 differs_any = true;
4322 } else {
4323 treesame.push(*parent_oid);
4324 }
4325 }
4326
4327 let parents_to_walk = if parents.len() > 1 {
4328 match treesame.first().copied() {
4329 Some(parent) => vec![parent],
4330 None => parents.clone(),
4331 }
4332 } else {
4333 parents.clone()
4334 };
4335
4336 let visible = if sparse {
4337 true
4338 } else if parents.len() > 1 {
4339 treesame.is_empty() || (show_pulls && first_parent_differs && !treesame.is_empty())
4340 } else {
4341 differs_any
4342 };
4343
4344 Ok(DensePathAction {
4345 visible,
4346 parents_to_walk,
4347 })
4348}
4349
4350pub fn commit_visible_for_dense_pathspecs(
4357 repo: &Repository,
4358 oid: ObjectId,
4359 paths: &[String],
4360) -> Result<bool> {
4361 if paths.is_empty() {
4362 return Ok(true);
4363 }
4364 let commit = load_commit(repo, oid)?;
4365 let parents = commit.parents.clone();
4366 let commit_entries = flatten_tree(repo, commit.tree, "")?;
4367 let commit_map: HashMap<String, ObjectId> = commit_entries.into_iter().collect();
4368
4369 if parents.is_empty() {
4370 return Ok(commit_map.keys().any(|path| {
4371 paths.iter().any(|spec| {
4372 crate::pathspec::matches_pathspec_with_context(
4373 spec,
4374 path,
4375 crate::pathspec::PathspecMatchContext {
4376 is_directory: false,
4377 is_git_submodule: false,
4378 },
4379 )
4380 })
4381 }));
4382 }
4383
4384 if parents.len() == 1 {
4385 let parent = load_commit(repo, parents[0])?;
4386 let parent_map: HashMap<String, ObjectId> =
4387 flatten_tree(repo, parent.tree, "")?.into_iter().collect();
4388 return Ok(path_differs_for_specs(&commit_map, &parent_map, paths));
4389 }
4390
4391 let mut treesame_parents = 0usize;
4392 let mut differs_any = false;
4393 for parent_oid in &parents {
4394 let parent = load_commit(repo, *parent_oid)?;
4395 let parent_map: HashMap<String, ObjectId> =
4396 flatten_tree(repo, parent.tree, "")?.into_iter().collect();
4397 let differs = path_differs_for_specs(&commit_map, &parent_map, paths);
4398 if differs {
4399 differs_any = true;
4400 } else {
4401 treesame_parents += 1;
4402 }
4403 }
4404
4405 if treesame_parents == 1 {
4406 return Ok(false);
4407 }
4408 if differs_any {
4409 return Ok(true);
4410 }
4411 Ok(false)
4412}
4413
4414fn compute_cherry_patch_id(
4415 repo: &Repository,
4416 oid: &ObjectId,
4417 paths: &[String],
4418) -> Result<Option<ObjectId>> {
4419 if paths.is_empty() {
4420 compute_patch_id(&repo.odb, oid)
4421 } else {
4422 compute_patch_id_for_paths(&repo.odb, oid, paths)
4423 }
4424}
4425
4426fn path_differs_for_specs(
4427 current: &HashMap<String, ObjectId>,
4428 parent: &HashMap<String, ObjectId>,
4429 specs: &[String],
4430) -> bool {
4431 let mut paths = std::collections::BTreeSet::new();
4432 paths.extend(current.keys().cloned());
4433 paths.extend(parent.keys().cloned());
4434
4435 for path in &paths {
4436 if !crate::pathspec::matches_pathspec_list(path, specs) {
4437 continue;
4438 }
4439 if current.get(path) != parent.get(path) {
4440 return true;
4441 }
4442 }
4443 false
4444}
4445
4446fn load_commit(repo: &Repository, oid: ObjectId) -> Result<crate::objects::CommitData> {
4447 let object = repo.odb.read(&oid)?;
4448 if object.kind != ObjectKind::Commit {
4449 return Err(Error::CorruptObject(format!(
4450 "object {oid} is not a commit"
4451 )));
4452 }
4453 parse_commit(&object.data)
4454}
4455
4456fn extend_split_token(
4457 token: &str,
4458 not_mode: bool,
4459 positive: &mut Vec<String>,
4460 negative: &mut Vec<String>,
4461) {
4462 let (pos, neg) = split_revision_token(token);
4463 if not_mode {
4464 positive.extend(neg);
4465 negative.extend(pos);
4466 } else {
4467 positive.extend(pos);
4468 negative.extend(neg);
4469 }
4470}
4471
4472fn parse_long_opt_value(opt: &str, argv0: &str, argv1: Option<&str>) -> Option<(usize, String)> {
4477 let rest = argv0.strip_prefix("--")?;
4478 let rest = rest.strip_prefix(opt)?;
4479 if let Some(stripped) = rest.strip_prefix('=') {
4480 return Some((1, stripped.to_owned()));
4481 }
4482 if !rest.is_empty() {
4483 return None;
4484 }
4485 let Some(next) = argv1 else {
4486 return None;
4487 };
4488 Some((2, next.to_owned()))
4489}
4490
4491fn stdin_die_requires_value(opt: &str) -> Error {
4492 Error::Message(format!("fatal: Option '{opt}' requires a value"))
4493}
4494
4495fn apply_stdin_pseudo_opt(
4496 git_dir: &Path,
4497 line: &str,
4498 next_line: Option<&str>,
4499 not_mode: bool,
4500 positive: &mut Vec<String>,
4501 negative: &mut Vec<String>,
4502 stdin_all_refs: &mut bool,
4503) -> Result<Option<usize>> {
4504 if line == "--end-of-options" {
4505 return Ok(Some(1));
4506 }
4507 if line == "--all" {
4508 if not_mode {
4509 for (_, oid) in refs::list_refs(git_dir, "refs/")? {
4510 let s = oid.to_hex();
4511 negative.push(s);
4512 }
4513 if let Ok(head_oid) = refs::resolve_ref(git_dir, "HEAD") {
4514 negative.push(head_oid.to_hex());
4515 }
4516 } else {
4517 *stdin_all_refs = true;
4518 }
4519 return Ok(Some(1));
4520 }
4521 if line == "--not" {
4522 return Ok(Some(1));
4523 }
4524 if line == "--branches" {
4525 for (_, oid) in refs::list_refs(git_dir, "refs/heads/")? {
4526 let s = oid.to_hex();
4527 if not_mode {
4528 negative.push(s);
4529 } else {
4530 positive.push(s);
4531 }
4532 }
4533 return Ok(Some(1));
4534 }
4535 if line == "--tags" {
4536 for (_, oid) in refs::list_refs(git_dir, "refs/tags/")? {
4537 let s = oid.to_hex();
4538 if not_mode {
4539 negative.push(s);
4540 } else {
4541 positive.push(s);
4542 }
4543 }
4544 return Ok(Some(1));
4545 }
4546 if line == "--remotes" {
4547 for (_, oid) in refs::list_refs(git_dir, "refs/remotes/")? {
4548 let s = oid.to_hex();
4549 if not_mode {
4550 negative.push(s);
4551 } else {
4552 positive.push(s);
4553 }
4554 }
4555 return Ok(Some(1));
4556 }
4557 if let Some((consumed, pattern)) = parse_long_opt_value("branches", line, next_line) {
4558 let full_pattern = format!("refs/heads/{pattern}");
4559 for (_, oid) in refs::list_refs_glob(git_dir, &full_pattern)? {
4560 let s = oid.to_hex();
4561 if not_mode {
4562 negative.push(s);
4563 } else {
4564 positive.push(s);
4565 }
4566 }
4567 return Ok(Some(consumed));
4568 }
4569 if let Some((1, pattern)) = parse_long_opt_value("tags", line, None) {
4570 let full_pattern = format!("refs/tags/{pattern}");
4571 for (_, oid) in refs::list_refs_glob(git_dir, &full_pattern)? {
4572 let s = oid.to_hex();
4573 if not_mode {
4574 negative.push(s);
4575 } else {
4576 positive.push(s);
4577 }
4578 }
4579 return Ok(Some(1));
4580 }
4581 if let Some((consumed, pattern)) = parse_long_opt_value("tags", line, next_line) {
4582 let full_pattern = format!("refs/tags/{pattern}");
4583 for (_, oid) in refs::list_refs_glob(git_dir, &full_pattern)? {
4584 let s = oid.to_hex();
4585 if not_mode {
4586 negative.push(s);
4587 } else {
4588 positive.push(s);
4589 }
4590 }
4591 return Ok(Some(consumed));
4592 }
4593 if let Some((1, pattern)) = parse_long_opt_value("remotes", line, None) {
4594 let full_pattern = format!("refs/remotes/{pattern}");
4595 for (_, oid) in refs::list_refs_glob(git_dir, &full_pattern)? {
4596 let s = oid.to_hex();
4597 if not_mode {
4598 negative.push(s);
4599 } else {
4600 positive.push(s);
4601 }
4602 }
4603 return Ok(Some(1));
4604 }
4605 if let Some((consumed, pattern)) = parse_long_opt_value("remotes", line, next_line) {
4606 let full_pattern = format!("refs/remotes/{pattern}");
4607 for (_, oid) in refs::list_refs_glob(git_dir, &full_pattern)? {
4608 let s = oid.to_hex();
4609 if not_mode {
4610 negative.push(s);
4611 } else {
4612 positive.push(s);
4613 }
4614 }
4615 return Ok(Some(consumed));
4616 }
4617 if line == "--glob" {
4618 return Err(stdin_die_requires_value("--glob"));
4619 }
4620 if let Some((1, pattern)) = parse_long_opt_value("glob", line, None) {
4621 for (_, oid) in refs::list_refs_glob(git_dir, &pattern)? {
4622 let s = oid.to_hex();
4623 if not_mode {
4624 negative.push(s);
4625 } else {
4626 positive.push(s);
4627 }
4628 }
4629 return Ok(Some(1));
4630 }
4631 if let Some((consumed, pattern)) = parse_long_opt_value("glob", line, next_line) {
4632 for (_, oid) in refs::list_refs_glob(git_dir, &pattern)? {
4633 let s = oid.to_hex();
4634 if not_mode {
4635 negative.push(s);
4636 } else {
4637 positive.push(s);
4638 }
4639 }
4640 return Ok(Some(consumed));
4641 }
4642 if line == "--no-walk" || line.starts_with("--no-walk=") {
4643 if let Some(rest) = line.strip_prefix("--no-walk=") {
4644 if rest == "sorted" || rest == "unsorted" {
4645 return Ok(Some(1));
4646 }
4647 eprintln!("error: invalid argument to --no-walk");
4648 return Err(Error::Message(format!(
4649 "fatal: invalid option '{line}' in --stdin mode"
4650 )));
4651 }
4652 return Ok(Some(1));
4653 }
4654 if line.starts_with("--") {
4655 return Err(Error::Message(format!(
4656 "fatal: invalid option '{line}' in --stdin mode"
4657 )));
4658 }
4659 if line.starts_with('-') {
4660 return Err(Error::Message(format!(
4661 "fatal: invalid option '{line}' in --stdin mode"
4662 )));
4663 }
4664 Ok(None)
4665}
4666
4667fn read_revisions_from_stdin_lines(
4669 git_dir: &Path,
4670) -> Result<(Vec<String>, Vec<String>, bool, Vec<String>)> {
4671 let stdin = std::io::read_to_string(std::io::stdin()).map_err(Error::Io)?;
4672 let lines: Vec<String> = stdin.lines().map(std::borrow::ToOwned::to_owned).collect();
4673
4674 let mut positive = Vec::new();
4675 let mut negative = Vec::new();
4676 let mut stdin_all_refs = false;
4677 let mut stdin_not_mode = false;
4678 let mut seen_end_of_options = false;
4679 let mut i = 0usize;
4680
4681 while i < lines.len() {
4682 let line = lines[i].as_str();
4683 if line.is_empty() {
4684 break;
4685 }
4686 if line == "--" {
4687 i += 1;
4688 let paths: Vec<String> = lines[i..].to_vec();
4689 return Ok((positive, negative, stdin_all_refs, paths));
4690 }
4691
4692 if !seen_end_of_options && line.starts_with('-') {
4693 if line == "--end-of-options" {
4694 seen_end_of_options = true;
4695 i += 1;
4696 continue;
4697 }
4698 let next = lines.get(i + 1).map(|s| s.as_str());
4699 match apply_stdin_pseudo_opt(
4700 git_dir,
4701 line,
4702 next,
4703 stdin_not_mode,
4704 &mut positive,
4705 &mut negative,
4706 &mut stdin_all_refs,
4707 )? {
4708 Some(consumed) => {
4709 if line == "--not" && consumed == 1 {
4710 stdin_not_mode = !stdin_not_mode;
4711 }
4712 i += consumed;
4713 }
4714 None => {
4715 extend_split_token(line, stdin_not_mode, &mut positive, &mut negative);
4716 i += 1;
4717 }
4718 }
4719 continue;
4720 }
4721
4722 extend_split_token(line, stdin_not_mode, &mut positive, &mut negative);
4723 i += 1;
4724 }
4725
4726 Ok((positive, negative, stdin_all_refs, Vec::new()))
4727}
4728
4729pub fn collect_revision_specs_with_stdin(
4740 git_dir: &Path,
4741 args_specs: &[String],
4742 read_stdin: bool,
4743) -> Result<(Vec<String>, Vec<String>, bool, Vec<String>)> {
4744 let mut positive = Vec::new();
4745 let mut negative = Vec::new();
4746
4747 for spec in args_specs {
4748 let (pos, neg) = split_revision_token(spec);
4749 positive.extend(pos);
4750 negative.extend(neg);
4751 }
4752
4753 if !read_stdin {
4754 return Ok((positive, negative, false, Vec::new()));
4755 }
4756
4757 let (s_pos, s_neg, stdin_all_refs, stdin_paths) = read_revisions_from_stdin_lines(git_dir)?;
4758 positive.extend(s_pos);
4759 negative.extend(s_neg);
4760
4761 Ok((positive, negative, stdin_all_refs, stdin_paths))
4762}
4763
4764pub fn tag_targets(git_dir: &Path) -> Result<HashSet<ObjectId>> {
4766 Ok(refs::list_refs(git_dir, "refs/tags/")?
4767 .into_iter()
4768 .map(|(_, oid)| oid)
4769 .collect())
4770}
4771
4772pub(crate) struct CommitGraph<'r> {
4773 repo: &'r Repository,
4774 first_parent_only: bool,
4775 parents: HashMap<ObjectId, Vec<ObjectId>>,
4776 committer_time: HashMap<ObjectId, i64>,
4777 author_time: HashMap<ObjectId, i64>,
4778 shallow_boundaries: HashSet<ObjectId>,
4779 graft_parents: HashMap<ObjectId, Vec<ObjectId>>,
4780}
4781
4782impl<'r> CommitGraph<'r> {
4783 pub(crate) fn new(repo: &'r Repository, first_parent_only: bool) -> Self {
4784 let shallow_boundaries = load_shallow_boundaries(&repo.git_dir);
4785 let graft_parents = crate::rev_parse::load_graft_parents(&repo.git_dir);
4786 Self {
4787 repo,
4788 first_parent_only,
4789 parents: HashMap::new(),
4790 committer_time: HashMap::new(),
4791 author_time: HashMap::new(),
4792 shallow_boundaries,
4793 graft_parents,
4794 }
4795 }
4796
4797 pub(crate) fn parents_of(&mut self, oid: ObjectId) -> Result<Vec<ObjectId>> {
4798 self.populate(oid)?;
4799 Ok(self.parents.get(&oid).cloned().unwrap_or_default())
4800 }
4801
4802 fn committer_time(&mut self, oid: ObjectId) -> i64 {
4803 if self.populate(oid).is_err() {
4804 return 0;
4805 }
4806 self.committer_time.get(&oid).copied().unwrap_or(0)
4807 }
4808
4809 fn author_time(&mut self, oid: ObjectId) -> i64 {
4810 if self.populate(oid).is_err() {
4811 return 0;
4812 }
4813 self.author_time.get(&oid).copied().unwrap_or(0)
4814 }
4815
4816 fn sort_key(&mut self, oid: ObjectId, author: bool) -> i64 {
4817 if author {
4818 self.author_time(oid)
4819 } else {
4820 self.committer_time(oid)
4821 }
4822 }
4823
4824 fn populate(&mut self, oid: ObjectId) -> Result<()> {
4825 if self.parents.contains_key(&oid) {
4826 return Ok(());
4827 }
4828 let commit = load_commit(self.repo, oid)?;
4829 let mut parents = if self.shallow_boundaries.contains(&oid) {
4831 Vec::new()
4832 } else {
4833 commit.parents
4834 };
4835 if let Some(graft_parents) = self.graft_parents.get(&oid) {
4836 parents = graft_parents.clone();
4837 }
4838 if self.first_parent_only && parents.len() > 1 {
4839 parents.truncate(1);
4840 }
4841 self.committer_time
4842 .insert(oid, committer_unix_seconds_for_ordering(&commit.committer));
4843 self.author_time
4844 .insert(oid, committer_unix_seconds_for_ordering(&commit.author));
4845 self.parents.insert(oid, parents);
4846 Ok(())
4847 }
4848}
4849
4850fn load_shallow_boundaries(git_dir: &Path) -> HashSet<ObjectId> {
4852 let shallow_path = git_dir.join("shallow");
4853 let mut set = HashSet::new();
4854 if let Ok(contents) = fs::read_to_string(&shallow_path) {
4855 for line in contents.lines() {
4856 let line = line.trim();
4857 if !line.is_empty() {
4858 if let Ok(oid) = line.parse::<ObjectId>() {
4859 set.insert(oid);
4860 }
4861 }
4862 }
4863 }
4864 set
4865}
4866
4867fn commit_tips_from_ref_pairs(
4868 repo: &Repository,
4869 pairs: &[(String, ObjectId)],
4870 exclusions: &RefExclusions,
4871) -> Result<Vec<ObjectId>> {
4872 let namespace_prefix = git_namespace_prefix();
4873 let mut raw = Vec::new();
4874 for (refname, oid) in pairs {
4875 if exclusions.ref_excluded(strip_git_namespace(refname, &namespace_prefix), refname) {
4876 continue;
4877 }
4878 raw.push(*oid);
4879 }
4880 peel_ref_oids_to_unique_commits(repo, raw)
4881}
4882
4883fn peel_ref_oids_to_unique_commits(repo: &Repository, raw: Vec<ObjectId>) -> Result<Vec<ObjectId>> {
4884 let mut out = Vec::new();
4885 let mut seen = HashSet::new();
4886 for oid in raw {
4887 match peel_to_commit(repo, oid) {
4888 Ok(commit_oid) if seen.insert(commit_oid) => out.push(commit_oid),
4889 Err(_) => {}
4890 _ => {}
4891 }
4892 }
4893 out.sort();
4894 Ok(out)
4895}
4896
4897fn all_ref_tips(repo: &Repository, exclusions: &RefExclusions) -> Result<Vec<ObjectId>> {
4898 let mut pairs = Vec::new();
4899 if let Ok(head) = refs::resolve_ref(&repo.git_dir, "HEAD") {
4900 pairs.push(("HEAD".to_owned(), head));
4901 }
4902 pairs.extend(refs::list_refs(&repo.git_dir, "refs/")?);
4903 commit_tips_from_ref_pairs(repo, &pairs, exclusions)
4904}
4905
4906pub fn commit_tips_from_named_refs(
4908 repo: &Repository,
4909 pairs: &[(String, ObjectId)],
4910 exclusions: &RefExclusions,
4911) -> Result<Vec<ObjectId>> {
4912 commit_tips_from_ref_pairs(repo, pairs, exclusions)
4913}
4914
4915#[must_use]
4920pub fn shallow_boundary_oids(git_dir: &Path) -> HashSet<ObjectId> {
4921 crate::shallow::load_shallow_boundaries(git_dir)
4922}
4923
4924#[must_use]
4930pub fn shallow_borders_reachable_from_wants(
4931 repo: &Repository,
4932 wants: &[ObjectId],
4933) -> Vec<ObjectId> {
4934 let boundaries = shallow_boundary_oids(&repo.git_dir);
4935 if boundaries.is_empty() || wants.is_empty() {
4936 return Vec::new();
4937 }
4938 let mut out: Vec<ObjectId> = Vec::new();
4939 let mut seen_out = HashSet::new();
4940 let mut visited = HashSet::new();
4941 let mut q: VecDeque<ObjectId> = wants.iter().copied().collect();
4942 while let Some(oid) = q.pop_front() {
4943 if !visited.insert(oid) {
4944 continue;
4945 }
4946 if boundaries.contains(&oid) {
4947 if seen_out.insert(oid) {
4948 out.push(oid);
4949 }
4950 continue;
4951 }
4952 for p in commit_parent_ids(repo, oid) {
4953 q.push_back(p);
4954 }
4955 }
4956 out.sort();
4957 out
4958}
4959
4960#[must_use]
4973pub fn shallow_grafts_for_upload_pack_deepen(
4974 repo: &Repository,
4975 wants: &[ObjectId],
4976 client_shallow: &[ObjectId],
4977 deepen: usize,
4978) -> Vec<ObjectId> {
4979 if deepen == 0 || wants.is_empty() {
4980 return Vec::new();
4981 }
4982
4983 let server_shallow = shallow_boundary_oids(&repo.git_dir);
4984 let client_set: HashSet<ObjectId> = client_shallow.iter().copied().collect();
4985
4986 let min_hit = min_client_shallow_distance(repo, wants, &client_set, &server_shallow);
4987 let base = min_hit.unwrap_or(1);
4988 let target_depth = base.saturating_add(deepen);
4989
4990 let included = commits_within_parent_depth(repo, wants, target_depth, &server_shallow);
4991 border_commits_not_in_client_shallow(repo, &included, &client_set)
4992}
4993
4994fn commit_parent_ids(repo: &Repository, oid: ObjectId) -> Vec<ObjectId> {
4995 let Ok(obj) = repo.odb.read(&oid) else {
4996 return Vec::new();
4997 };
4998 if obj.kind != ObjectKind::Commit {
4999 return Vec::new();
5000 }
5001 parse_commit(&obj.data)
5002 .map(|c| c.parents)
5003 .unwrap_or_default()
5004}
5005
5006fn min_client_shallow_distance(
5007 repo: &Repository,
5008 wants: &[ObjectId],
5009 client_shallow: &HashSet<ObjectId>,
5010 server_shallow: &HashSet<ObjectId>,
5011) -> Option<usize> {
5012 if client_shallow.is_empty() {
5013 return None;
5014 }
5015 let mut best: Option<usize> = None;
5016 let mut dist: HashMap<ObjectId, usize> = HashMap::new();
5017 let mut q: VecDeque<(ObjectId, usize)> = VecDeque::new();
5018 for &w in wants {
5019 dist.insert(w, 0);
5020 q.push_back((w, 0));
5021 }
5022 while let Some((oid, d)) = q.pop_front() {
5023 if client_shallow.contains(&oid) {
5024 best = Some(best.map(|b| b.min(d)).unwrap_or(d));
5025 }
5026 if server_shallow.contains(&oid) {
5027 continue;
5028 }
5029 for p in commit_parent_ids(repo, oid) {
5030 let nd = d.saturating_add(1);
5031 let prev = dist.get(&p).copied().unwrap_or(usize::MAX);
5032 if nd < prev {
5033 dist.insert(p, nd);
5034 q.push_back((p, nd));
5035 }
5036 }
5037 }
5038 best
5039}
5040
5041fn commits_within_parent_depth(
5042 repo: &Repository,
5043 wants: &[ObjectId],
5044 max_depth: usize,
5045 server_shallow: &HashSet<ObjectId>,
5046) -> HashSet<ObjectId> {
5047 let mut best_depth: HashMap<ObjectId, usize> = HashMap::new();
5048 let mut q: VecDeque<(ObjectId, usize)> = VecDeque::new();
5049 for &w in wants {
5050 best_depth.insert(w, 1);
5051 q.push_back((w, 1));
5052 }
5053 while let Some((oid, depth)) = q.pop_front() {
5054 if depth > max_depth {
5055 continue;
5056 }
5057 if best_depth.get(&oid).copied() != Some(depth) {
5058 continue;
5059 }
5060 if depth == max_depth {
5061 continue;
5062 }
5063 if server_shallow.contains(&oid) {
5064 continue;
5065 }
5066 for p in commit_parent_ids(repo, oid) {
5067 let nd = depth.saturating_add(1);
5068 if nd > max_depth {
5069 continue;
5070 }
5071 let prev = best_depth.get(&p).copied().unwrap_or(usize::MAX);
5072 if nd < prev {
5073 best_depth.insert(p, nd);
5074 q.push_back((p, nd));
5075 }
5076 }
5077 }
5078 best_depth.into_keys().collect()
5079}
5080
5081fn border_commits_not_in_client_shallow(
5082 repo: &Repository,
5083 included: &HashSet<ObjectId>,
5084 client_shallow: &HashSet<ObjectId>,
5085) -> Vec<ObjectId> {
5086 let mut out = Vec::new();
5087 let mut seen_out = HashSet::new();
5088 for &c in included {
5089 if client_shallow.contains(&c) {
5090 continue;
5091 }
5092 let parents = commit_parent_ids(repo, c);
5093 let is_border = parents.iter().any(|p| !included.contains(p));
5094 if is_border && seen_out.insert(c) {
5095 out.push(c);
5096 }
5097 }
5098 out.sort();
5099 out
5100}
5101
5102pub fn shallow_grafts_for_upload_pack_rev_list(
5107 repo: &Repository,
5108 wants: &[ObjectId],
5109 client_shallow: &[ObjectId],
5110 deepen_since: Option<i64>,
5111 deepen_not: &[ObjectId],
5112) -> Result<Vec<ObjectId>> {
5113 if wants.is_empty() || (deepen_since.is_none() && deepen_not.is_empty()) {
5114 return Ok(Vec::new());
5115 }
5116
5117 let positive: Vec<String> = wants.iter().map(|o| o.to_hex()).collect();
5118 let negative: Vec<String> = deepen_not
5119 .iter()
5120 .map(|o| format!("^{}", o.to_hex()))
5121 .collect();
5122
5123 let options = RevListOptions {
5124 since_cutoff: deepen_since,
5125 missing_action: MissingAction::Allow,
5126 ..Default::default()
5127 };
5128
5129 let res = rev_list(repo, &positive, &negative, &options)?;
5130 let included: HashSet<ObjectId> = res.commits.iter().copied().collect();
5131 let client_set: HashSet<ObjectId> = client_shallow.iter().copied().collect();
5132 Ok(border_commits_not_in_client_shallow(
5133 repo,
5134 &included,
5135 &client_set,
5136 ))
5137}
5138
5139#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5140struct CommitDateKey {
5141 oid: ObjectId,
5142 date: i64,
5143}
5144
5145impl Ord for CommitDateKey {
5146 fn cmp(&self, other: &Self) -> Ordering {
5147 match self.date.cmp(&other.date) {
5148 Ordering::Equal => self.oid.cmp(&other.oid),
5149 ord => ord,
5150 }
5151 }
5152}
5153
5154impl PartialOrd for CommitDateKey {
5155 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
5156 Some(self.cmp(other))
5157 }
5158}
5159
5160pub fn read_lines(path: &Path) -> Result<Vec<String>> {
5166 let content = fs::read_to_string(path)?;
5167 Ok(content.lines().map(|line| line.to_owned()).collect())
5168}
5169
5170#[must_use]
5172pub fn is_symmetric_diff(token: &str) -> bool {
5173 token.contains("...") && !token.contains("....")
5174}
5175
5176#[must_use]
5178pub fn split_symmetric_diff(token: &str) -> Option<(String, String)> {
5179 token
5180 .split_once("...")
5181 .map(|(l, r)| (l.to_owned(), r.to_owned()))
5182}
5183
5184#[derive(Debug, Default)]
5187struct TreeWalkState {
5188 seen_at_depth: HashMap<ObjectId, u64>,
5189}
5190
5191impl TreeWalkState {
5192 fn new() -> Self {
5193 Self::default()
5194 }
5195
5196 fn should_skip_tree(&mut self, oid: ObjectId, depth: u64) -> bool {
5199 match self.seen_at_depth.get(&oid).copied() {
5200 None => {
5201 self.seen_at_depth.insert(oid, depth);
5202 false
5203 }
5204 Some(prev) if depth >= prev => true,
5205 Some(_) => {
5206 self.seen_at_depth.insert(oid, depth);
5207 false
5208 }
5209 }
5210 }
5211}
5212
5213fn collect_tree_closure_objects(
5215 repo: &Repository,
5216 tree_oid: ObjectId,
5217 into: &mut HashSet<ObjectId>,
5218 missing_action: MissingAction,
5219 missing: &mut Vec<ObjectId>,
5220 missing_seen: &mut HashSet<ObjectId>,
5221) -> Result<()> {
5222 let mut stack = vec![tree_oid];
5223 let mut expanded_trees = HashSet::new();
5224 while let Some(t) = stack.pop() {
5225 if !expanded_trees.insert(t) {
5226 continue;
5227 }
5228 into.insert(t);
5229 let object = match repo.odb.read(&t) {
5230 Ok(o) => o,
5231 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
5232 if missing_action.reports_missing() && missing_seen.insert(t) {
5233 missing.push(t);
5234 }
5235 continue;
5236 }
5237 Err(e) => return Err(e),
5238 };
5239 if object.kind != ObjectKind::Tree {
5240 continue;
5241 }
5242 let entries = parse_tree(&object.data)?;
5243 for entry in entries {
5244 if entry.mode == 0o160000 {
5245 continue;
5246 }
5247 into.insert(entry.oid);
5248 if entry.mode == 0o040000 {
5249 stack.push(entry.oid);
5250 }
5251 }
5252 }
5253 Ok(())
5254}
5255
5256fn union_parent_reachable_objects(
5257 repo: &Repository,
5258 parents: &[ObjectId],
5259 missing_action: MissingAction,
5260 missing: &mut Vec<ObjectId>,
5261 missing_seen: &mut HashSet<ObjectId>,
5262) -> Result<HashSet<ObjectId>> {
5263 let mut out = HashSet::new();
5264 for &p in parents {
5265 let commit = match load_commit(repo, p) {
5266 Ok(c) => c,
5267 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
5268 if missing_action.reports_missing() && missing_seen.insert(p) {
5269 missing.push(p);
5270 }
5271 continue;
5272 }
5273 Err(e) => return Err(e),
5274 };
5275 collect_tree_closure_objects(
5276 repo,
5277 commit.tree,
5278 &mut out,
5279 missing_action,
5280 missing,
5281 missing_seen,
5282 )?;
5283 }
5284 Ok(out)
5285}
5286
5287#[allow(dead_code)]
5290fn collect_reachable_objects(
5291 repo: &Repository,
5292 graph: &mut CommitGraph<'_>,
5293 commits: &[ObjectId],
5294 object_roots: &[RootObject],
5295 tip_annotated_tags: &HashMap<ObjectId, ObjectId>,
5296 filter: Option<&ObjectFilter>,
5297 filter_provided: bool,
5298 missing_action: MissingAction,
5299 sparse_lines: Option<&[String]>,
5300 skip_trees_for_type_filter: bool,
5301 omit_object_paths: bool,
5302 packed_set: Option<&HashSet<ObjectId>>,
5303 collect_tree_omits: bool,
5304) -> Result<(Vec<(ObjectId, String)>, Vec<ObjectId>, Vec<ObjectId>)> {
5305 let mut tree_state = TreeWalkState::new();
5306 let mut top_tree_omit =
5307 walk_needs_top_tree_omit_set(filter, collect_tree_omits).then(HashSet::<ObjectId>::new);
5308 let mut combine_states = CombineSubState::prepare_sub_states(filter, collect_tree_omits);
5309 let mut emitted = HashSet::new();
5310 let mut result = Vec::new();
5311 let mut omitted = Vec::new();
5312 let mut missing = Vec::new();
5313 let mut missing_seen = HashSet::new();
5314 for &commit_oid in commits {
5315 let commit = match load_commit(repo, commit_oid) {
5316 Ok(commit) => commit,
5317 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
5318 if missing_seen.insert(commit_oid) && missing_action.reports_missing() {
5319 missing.push(commit_oid);
5320 }
5321 continue;
5322 }
5323 Err(err) => return Err(err),
5324 };
5325 let parents = graph.parents_of(commit_oid)?;
5326 let parent_union = union_parent_reachable_objects(
5327 repo,
5328 &parents,
5329 missing_action,
5330 &mut missing,
5331 &mut missing_seen,
5332 )?;
5333 if let Some(&tag_oid) = tip_annotated_tags.get(&commit_oid) {
5334 if emitted.insert(tag_oid) {
5335 result.push((tag_oid, "tag".to_owned()));
5336 }
5337 }
5338 collect_tree_objects_filtered(
5339 repo,
5340 commit.tree,
5341 "",
5342 0,
5343 false,
5344 Some(&parent_union),
5345 &mut tree_state,
5346 &mut emitted,
5347 &mut result,
5348 &mut omitted,
5349 &mut missing,
5350 &mut missing_seen,
5351 filter,
5352 filter_provided,
5353 missing_action,
5354 sparse_lines,
5355 skip_trees_for_type_filter,
5356 omit_object_paths,
5357 packed_set,
5358 collect_tree_omits,
5359 &mut top_tree_omit,
5360 &mut combine_states,
5361 )?;
5362 }
5363
5364 for root in object_roots {
5365 collect_root_object(
5366 repo,
5367 root,
5368 &mut tree_state,
5369 &mut emitted,
5370 &mut result,
5371 &mut omitted,
5372 &mut missing,
5373 &mut missing_seen,
5374 filter,
5375 filter_provided,
5376 missing_action,
5377 sparse_lines,
5378 skip_trees_for_type_filter,
5379 omit_object_paths,
5380 packed_set,
5381 collect_tree_omits,
5382 &mut top_tree_omit,
5383 &mut combine_states,
5384 )?;
5385 }
5386
5387 Ok((result, omitted, missing))
5388}
5389
5390fn excluded_object_root_ids(
5391 repo: &Repository,
5392 object_roots: &[RootObject],
5393 missing_action: MissingAction,
5394 sparse_lines: Option<&[String]>,
5395 skip_trees_for_type_filter: bool,
5396 omit_object_paths: bool,
5397 collect_tree_omits: bool,
5398) -> Result<HashSet<ObjectId>> {
5399 if object_roots.is_empty() {
5400 return Ok(HashSet::new());
5401 }
5402
5403 let mut tree_state = TreeWalkState::new();
5404 let mut emitted = HashSet::new();
5405 let mut objects = Vec::new();
5406 let mut omitted = Vec::new();
5407 let mut missing = Vec::new();
5408 let mut missing_seen = HashSet::new();
5409 let mut top_tree_omit = None;
5410 let mut combine_states = None;
5411
5412 for root in object_roots {
5413 collect_root_object(
5414 repo,
5415 root,
5416 &mut tree_state,
5417 &mut emitted,
5418 &mut objects,
5419 &mut omitted,
5420 &mut missing,
5421 &mut missing_seen,
5422 None,
5423 false,
5424 missing_action,
5425 sparse_lines,
5426 skip_trees_for_type_filter,
5427 omit_object_paths,
5428 None,
5429 collect_tree_omits,
5430 &mut top_tree_omit,
5431 &mut combine_states,
5432 )?;
5433 }
5434
5435 Ok(objects.into_iter().map(|(oid, _)| oid).collect())
5436}
5437
5438fn retain_objects_matching_pathspecs(objects: &mut Vec<(ObjectId, String)>, pathspecs: &[String]) {
5439 objects.retain(|(_, path)| {
5440 path.is_empty()
5441 || crate::pathspec::matches_pathspec_list(path, pathspecs)
5442 || pathspecs
5443 .iter()
5444 .any(|spec| crate::pathspec::pathspec_wants_descent_into_tree(spec, path))
5445 });
5446}
5447
5448fn collect_pathspec_matching_tree_objects(
5449 repo: &Repository,
5450 commits: &[ObjectId],
5451 pathspecs: &[String],
5452) -> Result<Vec<(ObjectId, String)>> {
5453 let mut seen = HashSet::new();
5454 let mut out = Vec::new();
5455 for commit_oid in commits {
5456 let commit = load_commit(repo, *commit_oid)?;
5457 collect_pathspec_matching_tree_objects_inner(
5458 repo,
5459 commit.tree,
5460 "",
5461 pathspecs,
5462 &mut seen,
5463 &mut out,
5464 )?;
5465 }
5466 Ok(out)
5467}
5468
5469fn collect_pathspec_matching_tree_objects_inner(
5470 repo: &Repository,
5471 tree_oid: ObjectId,
5472 prefix: &str,
5473 pathspecs: &[String],
5474 seen: &mut HashSet<ObjectId>,
5475 out: &mut Vec<(ObjectId, String)>,
5476) -> Result<()> {
5477 let object = repo.odb.read(&tree_oid)?;
5478 if object.kind != ObjectKind::Tree {
5479 return Err(Error::CorruptObject(format!(
5480 "object {tree_oid} is not a tree"
5481 )));
5482 }
5483 let entries = parse_tree(&object.data)?;
5484 for entry in entries {
5485 if entry.mode == 0o160000 {
5486 continue;
5487 }
5488 let name = String::from_utf8_lossy(&entry.name);
5489 let path = if prefix.is_empty() {
5490 name.into_owned()
5491 } else {
5492 format!("{prefix}/{name}")
5493 };
5494 let is_tree = entry.mode == 0o040000;
5495 let matches = crate::pathspec::matches_pathspec_list(&path, pathspecs);
5496 let wants_descent = pathspecs
5497 .iter()
5498 .any(|spec| crate::pathspec::pathspec_wants_descent_into_tree(spec, &path));
5499 if (matches || (is_tree && wants_descent)) && seen.insert(entry.oid) {
5500 out.push((entry.oid, path.clone()));
5501 }
5502 if is_tree && wants_descent {
5503 collect_pathspec_matching_tree_objects_inner(
5504 repo, entry.oid, &path, pathspecs, seen, out,
5505 )?;
5506 }
5507 }
5508 Ok(())
5509}
5510
5511fn collect_reachable_objects_segmented(
5517 repo: &Repository,
5518 graph: &mut CommitGraph<'_>,
5519 commits: &[ObjectId],
5520 object_roots: &[RootObject],
5521 tip_annotated_tags: &HashMap<ObjectId, ObjectId>,
5522 filter: Option<&ObjectFilter>,
5523 filter_provided: bool,
5524 missing_action: MissingAction,
5525 sparse_lines: Option<&[String]>,
5526 skip_trees_for_type_filter: bool,
5527 omit_object_paths: bool,
5528 packed_set: Option<&HashSet<ObjectId>>,
5529 collect_tree_omits: bool,
5530) -> Result<(
5531 Vec<(ObjectId, String)>,
5532 Vec<ObjectId>,
5533 Vec<ObjectId>,
5534 Vec<Vec<(ObjectId, String)>>,
5535)> {
5536 let mut emitted = HashSet::new();
5537 let mut result = Vec::new();
5538 let mut omitted = Vec::new();
5539 let mut missing = Vec::new();
5540 let mut missing_seen = HashSet::new();
5541 let mut segments: Vec<Vec<(ObjectId, String)>> = Vec::with_capacity(commits.len() + 1);
5542 let mut tree_state = TreeWalkState::new();
5543 let mut top_tree_omit =
5544 walk_needs_top_tree_omit_set(filter, collect_tree_omits).then(HashSet::<ObjectId>::new);
5545 let mut combine_states = CombineSubState::prepare_sub_states(filter, collect_tree_omits);
5546
5547 for &commit_oid in commits {
5548 let start = result.len();
5549 let commit = match load_commit(repo, commit_oid) {
5550 Ok(commit) => commit,
5551 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
5552 if missing_action.reports_missing() && missing_seen.insert(commit_oid) {
5553 missing.push(commit_oid);
5554 }
5555 segments.push(Vec::new());
5556 continue;
5557 }
5558 Err(err) => return Err(err),
5559 };
5560 if let Some(&tag_oid) = tip_annotated_tags.get(&commit_oid) {
5561 if emitted.insert(tag_oid) {
5562 result.push((tag_oid, "tag".to_owned()));
5563 }
5564 }
5565 let parents = graph.parents_of(commit_oid)?;
5566 let parent_union = union_parent_reachable_objects(
5567 repo,
5568 &parents,
5569 missing_action,
5570 &mut missing,
5571 &mut missing_seen,
5572 )?;
5573 collect_tree_objects_filtered(
5574 repo,
5575 commit.tree,
5576 "",
5577 0,
5578 false,
5579 Some(&parent_union),
5580 &mut tree_state,
5581 &mut emitted,
5582 &mut result,
5583 &mut omitted,
5584 &mut missing,
5585 &mut missing_seen,
5586 filter,
5587 filter_provided,
5588 missing_action,
5589 sparse_lines,
5590 skip_trees_for_type_filter,
5591 omit_object_paths,
5592 packed_set,
5593 collect_tree_omits,
5594 &mut top_tree_omit,
5595 &mut combine_states,
5596 )?;
5597 segments.push(result[start..].to_vec());
5598 }
5599
5600 let roots_start = result.len();
5601 for root in object_roots {
5602 collect_root_object(
5603 repo,
5604 root,
5605 &mut tree_state,
5606 &mut emitted,
5607 &mut result,
5608 &mut omitted,
5609 &mut missing,
5610 &mut missing_seen,
5611 filter,
5612 filter_provided,
5613 missing_action,
5614 sparse_lines,
5615 skip_trees_for_type_filter,
5616 omit_object_paths,
5617 packed_set,
5618 collect_tree_omits,
5619 &mut top_tree_omit,
5620 &mut combine_states,
5621 )?;
5622 }
5623 segments.push(result[roots_start..].to_vec());
5624
5625 Ok((result, omitted, missing, segments))
5626}
5627
5628#[derive(Clone, Copy, Debug)]
5629struct ListFilterBits {
5630 mark_seen: bool,
5631 do_show: bool,
5632 skip_tree: bool,
5633}
5634
5635impl ListFilterBits {
5636 fn merge_combine(subs: &[ListFilterBits], sub_skipping: &[bool]) -> Self {
5637 let mut out = ListFilterBits {
5638 mark_seen: true,
5639 do_show: true,
5640 skip_tree: true,
5641 };
5642 for (sub, skipping) in subs.iter().zip(sub_skipping.iter()) {
5643 if !sub.do_show {
5644 out.do_show = false;
5645 }
5646 if !sub.mark_seen {
5647 out.mark_seen = false;
5648 }
5649 if !skipping {
5650 out.skip_tree = false;
5651 }
5652 }
5653 out
5654 }
5655}
5656
5657fn walk_needs_top_tree_omit_set(filter: Option<&ObjectFilter>, collect_omits: bool) -> bool {
5658 collect_omits && matches!(filter, Some(ObjectFilter::TreeDepth(_)))
5659}
5660
5661fn trace_skip_tree_contents(prefix: &str) {
5662 let Ok(trace_val) = std::env::var("GIT_TRACE") else {
5663 return;
5664 };
5665 if trace_val.is_empty() || trace_val == "0" || trace_val.eq_ignore_ascii_case("false") {
5666 return;
5667 }
5668 let path = if prefix.is_empty() {
5669 String::new()
5670 } else {
5671 format!("{prefix}/")
5672 };
5673 let line = format!("Skipping contents of tree {path}...\n");
5674 match trace_val.as_str() {
5675 "1" | "true" | "2" => {
5676 let _ = std::io::stderr().write_all(line.as_bytes());
5677 }
5678 path_dest => {
5679 if let Ok(mut f) = std::fs::OpenOptions::new()
5680 .create(true)
5681 .append(true)
5682 .open(path_dest)
5683 {
5684 let _ = f.write_all(line.as_bytes());
5685 }
5686 }
5687 }
5688}
5689
5690fn tree_depth_begin_tree(
5691 tree_oid: ObjectId,
5692 depth: u64,
5693 exclude_depth: u64,
5694 tree_state: &mut TreeWalkState,
5695 tree_omit_set: &mut Option<HashSet<ObjectId>>,
5696 collect_omits: bool,
5697) -> ListFilterBits {
5698 let include_it = depth < exclude_depth;
5699 let already_seen = tree_state.should_skip_tree(tree_oid, depth);
5700 if already_seen {
5701 return ListFilterBits {
5702 mark_seen: false,
5703 do_show: false,
5704 skip_tree: true,
5705 };
5706 }
5707
5708 let been_omitted = if collect_omits {
5709 if let Some(omits) = tree_omit_set.as_mut() {
5710 if include_it {
5711 omits.remove(&tree_oid);
5712 false
5713 } else {
5714 !omits.insert(tree_oid)
5715 }
5716 } else {
5717 false
5718 }
5719 } else {
5720 false
5721 };
5722
5723 let skip_tree = if include_it {
5724 false
5725 } else {
5726 !(collect_omits && !been_omitted)
5727 };
5728
5729 let do_show = include_it;
5730 ListFilterBits {
5731 mark_seen: true,
5732 do_show,
5733 skip_tree,
5734 }
5735}
5736
5737fn tree_depth_blob(
5738 oid: ObjectId,
5739 _size: u64,
5740 parent_depth: u64,
5741 exclude_depth: u64,
5742 tree_omit_set: &mut Option<HashSet<ObjectId>>,
5743 collect_omits: bool,
5744) -> ListFilterBits {
5745 let include_it = parent_depth.saturating_add(1) < exclude_depth;
5746 if collect_omits {
5747 if let Some(omits) = tree_omit_set.as_mut() {
5748 if include_it {
5749 omits.remove(&oid);
5750 } else {
5751 omits.insert(oid);
5752 }
5753 }
5754 }
5755 ListFilterBits {
5756 mark_seen: include_it,
5758 do_show: include_it,
5759 skip_tree: false,
5760 }
5761}
5762
5763fn filter_object_bits_tree_begin(
5764 f: &ObjectFilter,
5765 tree_oid: ObjectId,
5766 depth: u64,
5767 tree_state: &mut TreeWalkState,
5768 tree_omit_set: &mut Option<HashSet<ObjectId>>,
5769 collect_omits: bool,
5770 sub_states: Option<&mut [CombineSubState]>,
5771) -> ListFilterBits {
5772 match f {
5773 ObjectFilter::BlobNone | ObjectFilter::BlobLimit(_) => ListFilterBits {
5774 mark_seen: true,
5775 do_show: true,
5776 skip_tree: false,
5777 },
5778 ObjectFilter::TreeDepth(excl) => tree_depth_begin_tree(
5779 tree_oid,
5780 depth,
5781 *excl,
5782 tree_state,
5783 tree_omit_set,
5784 collect_omits,
5785 ),
5786 ObjectFilter::SparseOid(_) => ListFilterBits {
5787 mark_seen: true,
5788 do_show: true,
5789 skip_tree: false,
5790 },
5791 ObjectFilter::ObjectType(k) => {
5792 let show = *k == FilterObjectKind::Tree;
5795 let skip_tree = matches!(k, FilterObjectKind::Commit | FilterObjectKind::Tag);
5796 ListFilterBits {
5797 mark_seen: true,
5798 do_show: show,
5799 skip_tree,
5800 }
5801 }
5802 ObjectFilter::Combine(parts) => {
5803 let Some(states) = sub_states else {
5806 return ListFilterBits::merge_combine(&[], &[]);
5807 };
5808 debug_assert_eq!(states.len(), parts.len());
5809 let mut bits = Vec::with_capacity(parts.len());
5810 let mut skipping = Vec::with_capacity(parts.len());
5811 for (i, p) in parts.iter().enumerate() {
5812 let b = filter_object_bits_tree_begin(
5813 p,
5814 tree_oid,
5815 depth,
5816 &mut states[i].tree_state,
5817 &mut states[i].tree_omit_set,
5818 collect_omits,
5819 None,
5820 );
5821 if b.skip_tree {
5822 states[i].is_skipping_tree = true;
5823 states[i].skip_tree_oid = Some(tree_oid);
5824 } else {
5825 states[i].is_skipping_tree = false;
5826 states[i].skip_tree_oid = None;
5827 }
5828 bits.push(b);
5829 skipping.push(states[i].is_skipping_tree);
5830 }
5831 ListFilterBits::merge_combine(&bits, &skipping)
5832 }
5833 }
5834}
5835
5836fn filter_object_bits_blob(
5837 f: &ObjectFilter,
5838 oid: ObjectId,
5839 size: u64,
5840 parent_depth: u64,
5841 tree_omit_set: &mut Option<HashSet<ObjectId>>,
5842 collect_omits: bool,
5843 sub_states: Option<&mut [CombineSubState]>,
5844) -> ListFilterBits {
5845 match f {
5846 ObjectFilter::BlobNone => ListFilterBits {
5847 mark_seen: true,
5848 do_show: false,
5849 skip_tree: false,
5850 },
5851 ObjectFilter::BlobLimit(limit) => {
5852 let include = size < *limit;
5853 ListFilterBits {
5854 mark_seen: true,
5855 do_show: include,
5856 skip_tree: false,
5857 }
5858 }
5859 ObjectFilter::TreeDepth(excl) => {
5860 tree_depth_blob(oid, size, parent_depth, *excl, tree_omit_set, collect_omits)
5861 }
5862 ObjectFilter::SparseOid(_) => ListFilterBits {
5863 mark_seen: true,
5864 do_show: true,
5865 skip_tree: false,
5866 },
5867 ObjectFilter::ObjectType(k) => {
5868 let show = *k == FilterObjectKind::Blob;
5869 ListFilterBits {
5870 mark_seen: true,
5871 do_show: show,
5872 skip_tree: false,
5873 }
5874 }
5875 ObjectFilter::Combine(parts) => {
5876 let Some(states) = sub_states else {
5879 return ListFilterBits::merge_combine(&[], &[]);
5880 };
5881 let mut bits = Vec::with_capacity(parts.len());
5882 let mut skipping = Vec::with_capacity(parts.len());
5883 for (i, p) in parts.iter().enumerate() {
5884 let b = filter_object_bits_blob(
5885 p,
5886 oid,
5887 size,
5888 parent_depth,
5889 &mut states[i].tree_omit_set,
5890 collect_omits,
5891 None,
5892 );
5893 bits.push(b);
5894 skipping.push(states[i].is_skipping_tree);
5895 }
5896 ListFilterBits::merge_combine(&bits, &skipping)
5897 }
5898 }
5899}
5900
5901#[derive(Debug)]
5902struct CombineSubState {
5903 tree_state: TreeWalkState,
5904 tree_omit_set: Option<HashSet<ObjectId>>,
5905 is_skipping_tree: bool,
5906 skip_tree_oid: Option<ObjectId>,
5907}
5908
5909impl CombineSubState {
5910 fn new(parts_len: usize, collect_omits: bool) -> Vec<Self> {
5911 (0..parts_len)
5912 .map(|_| Self {
5913 tree_state: TreeWalkState::new(),
5914 tree_omit_set: collect_omits.then(HashSet::new),
5915 is_skipping_tree: false,
5916 skip_tree_oid: None,
5917 })
5918 .collect()
5919 }
5920
5921 fn prepare_sub_states(
5922 filter: Option<&ObjectFilter>,
5923 collect_omits: bool,
5924 ) -> Option<Vec<CombineSubState>> {
5925 match filter {
5926 Some(ObjectFilter::Combine(parts)) => {
5927 Some(CombineSubState::new(parts.len(), collect_omits))
5928 }
5929 _ => None,
5930 }
5931 }
5932}
5933
5934#[allow(dead_code)]
5935fn collect_tree_objects_filtered(
5936 repo: &Repository,
5937 tree_oid: ObjectId,
5938 prefix: &str,
5939 depth: u64,
5940 explicit_root: bool,
5941 parent_union: Option<&HashSet<ObjectId>>,
5942 tree_state: &mut TreeWalkState,
5943 emitted: &mut HashSet<ObjectId>,
5944 result: &mut Vec<(ObjectId, String)>,
5945 omitted: &mut Vec<ObjectId>,
5946 missing: &mut Vec<ObjectId>,
5947 missing_seen: &mut HashSet<ObjectId>,
5948 filter: Option<&ObjectFilter>,
5949 filter_provided: bool,
5950 missing_action: MissingAction,
5951 sparse_lines: Option<&[String]>,
5952 skip_trees_for_type_filter: bool,
5953 omit_object_paths: bool,
5954 packed_set: Option<&HashSet<ObjectId>>,
5955 collect_tree_omits: bool,
5956 tree_omit_set: &mut Option<HashSet<ObjectId>>,
5957 combine_states: &mut Option<Vec<CombineSubState>>,
5958) -> Result<()> {
5959 if !explicit_root {
5960 if let Some(pu) = parent_union {
5961 if pu.contains(&tree_oid) {
5962 return Ok(());
5963 }
5964 }
5965 }
5966 let object = match repo.odb.read(&tree_oid) {
5967 Ok(object) => object,
5968 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
5969 if missing_action.reports_missing() && missing_seen.insert(tree_oid) {
5970 missing.push(tree_oid);
5971 }
5972 return Ok(());
5973 }
5974 Err(err) => return Err(err),
5975 };
5976 if object.kind != ObjectKind::Tree {
5977 return Err(Error::CorruptObject(format!(
5978 "object {tree_oid} is not a tree"
5979 )));
5980 }
5981
5982 let bits = match filter {
5983 None => ListFilterBits {
5984 mark_seen: true,
5985 do_show: true,
5986 skip_tree: false,
5987 },
5988 Some(f) => {
5989 if explicit_root && matches!(f, ObjectFilter::TreeDepth(0)) {
5990 ListFilterBits {
5991 mark_seen: true,
5992 do_show: true,
5993 skip_tree: true,
5994 }
5995 } else if explicit_root && !filter_provided {
5996 ListFilterBits {
5997 mark_seen: true,
5998 do_show: true,
5999 skip_tree: false,
6000 }
6001 } else {
6002 match f {
6003 ObjectFilter::Combine(_) => filter_object_bits_tree_begin(
6004 f,
6005 tree_oid,
6006 depth,
6007 tree_state,
6008 tree_omit_set,
6009 collect_tree_omits,
6010 combine_states.as_mut().map(Vec::as_mut_slice),
6011 ),
6012 _ => filter_object_bits_tree_begin(
6013 f,
6014 tree_oid,
6015 depth,
6016 tree_state,
6017 tree_omit_set,
6018 collect_tree_omits,
6019 None,
6020 ),
6021 }
6022 }
6023 }
6024 };
6025
6026 let tree_included = bits.do_show;
6029 if tree_included {
6030 if !packed_set.is_some_and(|p| p.contains(&tree_oid)) && emitted.insert(tree_oid) {
6031 let out_path = if omit_object_paths {
6032 String::new()
6033 } else {
6034 prefix.to_owned()
6035 };
6036 result.push((tree_oid, out_path));
6037 }
6038 } else if bits.mark_seen {
6039 omitted.push(tree_oid);
6040 }
6041
6042 if bits.skip_tree {
6043 trace_skip_tree_contents(prefix);
6044 }
6045
6046 if skip_trees_for_type_filter && depth == 0 && !explicit_root {
6047 return Ok(());
6048 }
6049
6050 if bits.skip_tree {
6051 return Ok(());
6052 }
6053
6054 let entries = parse_tree(&object.data)?;
6055 for entry in entries {
6056 if entry.mode == 0o160000 {
6057 continue;
6058 }
6059 let name = String::from_utf8_lossy(&entry.name).to_string();
6060 let path = if prefix.is_empty() {
6061 name.clone()
6062 } else {
6063 format!("{prefix}/{name}")
6064 };
6065 let child_obj = match repo.odb.read(&entry.oid) {
6066 Ok(object) => object,
6067 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
6068 if missing_action.reports_missing() && missing_seen.insert(entry.oid) {
6069 missing.push(entry.oid);
6070 }
6071 continue;
6072 }
6073 Err(err) => return Err(err),
6074 };
6075 if entry.mode == 0o040000 {
6076 if child_obj.kind != ObjectKind::Tree {
6077 return Err(Error::CorruptObject(format!(
6078 "object {} is not a tree",
6079 entry.oid
6080 )));
6081 }
6082 if let Some(pu) = parent_union {
6083 if pu.contains(&entry.oid) {
6084 continue;
6085 }
6086 }
6087 let child_tree_depth = depth + 1;
6088 collect_tree_objects_filtered(
6089 repo,
6090 entry.oid,
6091 &path,
6092 child_tree_depth,
6093 false,
6094 parent_union,
6095 tree_state,
6096 emitted,
6097 result,
6098 omitted,
6099 missing,
6100 missing_seen,
6101 filter,
6102 filter_provided,
6103 missing_action,
6104 sparse_lines,
6105 skip_trees_for_type_filter,
6106 omit_object_paths,
6107 packed_set,
6108 collect_tree_omits,
6109 tree_omit_set,
6110 combine_states,
6111 )?;
6112 } else {
6113 if let Some(pu) = parent_union {
6114 if pu.contains(&entry.oid) {
6115 continue;
6116 }
6117 }
6118 if child_obj.kind == ObjectKind::Blob {
6119 let sparse_blob = sparse_filter_includes_path(repo, &path, sparse_lines);
6120 let blob_bits = match filter {
6121 None => ListFilterBits {
6122 mark_seen: true,
6123 do_show: true,
6124 skip_tree: false,
6125 },
6126 Some(f) => {
6127 if explicit_root && !filter_provided {
6128 ListFilterBits {
6129 mark_seen: true,
6130 do_show: true,
6131 skip_tree: false,
6132 }
6133 } else {
6134 match f {
6135 ObjectFilter::Combine(_) => filter_object_bits_blob(
6136 f,
6137 entry.oid,
6138 child_obj.data.len() as u64,
6139 depth,
6140 tree_omit_set,
6141 collect_tree_omits,
6142 combine_states.as_mut().map(Vec::as_mut_slice),
6143 ),
6144 _ => filter_object_bits_blob(
6145 f,
6146 entry.oid,
6147 child_obj.data.len() as u64,
6148 depth,
6149 tree_omit_set,
6150 collect_tree_omits,
6151 None,
6152 ),
6153 }
6154 }
6155 }
6156 };
6157 let blob_included = blob_bits.do_show && sparse_blob;
6158 if !blob_included {
6159 omitted.push(entry.oid);
6160 } else if blob_bits.mark_seen
6161 && emitted.insert(entry.oid)
6162 && !packed_set.is_some_and(|p| p.contains(&entry.oid))
6163 {
6164 let out_path = if omit_object_paths {
6165 String::new()
6166 } else {
6167 path.clone()
6168 };
6169 result.push((entry.oid, out_path));
6170 }
6171 } else {
6172 if emitted.contains(&entry.oid) {
6173 return Err(Error::CorruptObject(format!(
6174 "object {} is not a blob",
6175 entry.oid
6176 )));
6177 }
6178 if emitted.insert(entry.oid) {
6179 result.push((entry.oid, path));
6180 }
6181 }
6182 }
6183 }
6184
6185 if let Some(f) = filter {
6186 if let ObjectFilter::Combine(_) = f {
6187 if let Some(states) = combine_states.as_mut() {
6188 for st in states.iter_mut() {
6189 if st.is_skipping_tree && st.skip_tree_oid == Some(tree_oid) {
6190 st.is_skipping_tree = false;
6191 st.skip_tree_oid = None;
6192 }
6193 }
6194 }
6195 }
6196 }
6197
6198 Ok(())
6199}
6200
6201fn collect_root_object(
6202 repo: &Repository,
6203 root: &RootObject,
6204 tree_state: &mut TreeWalkState,
6205 emitted: &mut HashSet<ObjectId>,
6206 result: &mut Vec<(ObjectId, String)>,
6207 omitted: &mut Vec<ObjectId>,
6208 missing: &mut Vec<ObjectId>,
6209 missing_seen: &mut HashSet<ObjectId>,
6210 filter: Option<&ObjectFilter>,
6211 filter_provided: bool,
6212 missing_action: MissingAction,
6213 sparse_lines: Option<&[String]>,
6214 skip_trees_for_type_filter: bool,
6215 omit_object_paths: bool,
6216 packed_set: Option<&HashSet<ObjectId>>,
6217 collect_tree_omits: bool,
6218 tree_omit_set: &mut Option<HashSet<ObjectId>>,
6219 combine_states: &mut Option<Vec<CombineSubState>>,
6220) -> Result<()> {
6221 if let Some(tag_oid) = root.wrap_with_tag {
6222 if root.expected_kind != Some(ExpectedObjectKind::Commit) {
6223 let show_tag = match filter {
6224 None => true,
6225 Some(f) => f.includes_commit_or_tag_object(ObjectKind::Tag),
6226 };
6227 if show_tag && emitted.insert(tag_oid) {
6228 result.push((tag_oid, "tag".to_owned()));
6229 }
6230 }
6231 }
6232
6233 let object = match repo.odb.read(&root.oid) {
6234 Ok(object) => object,
6235 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
6236 if missing_action.reports_missing() && missing_seen.insert(root.oid) {
6237 missing.push(root.oid);
6238 }
6239 return Ok(());
6240 }
6241 Err(err) => return Err(err),
6242 };
6243
6244 if let Some(expected) = root.expected_kind {
6245 if !expected.matches(object.kind) {
6246 return Err(Error::CorruptObject(format!(
6247 "object {} is not a {}",
6248 root.input,
6249 expected.as_str()
6250 )));
6251 }
6252 }
6253
6254 match object.kind {
6255 ObjectKind::Commit => {
6256 if emitted.insert(root.oid) {
6257 result.push((root.oid, "\0".to_owned()));
6258 }
6259 if let Some(tag_oid) = root.wrap_with_tag {
6260 let show_tag = match filter {
6261 None => true,
6262 Some(f) => f.includes_commit_or_tag_object(ObjectKind::Tag),
6263 };
6264 if show_tag && emitted.insert(tag_oid) {
6265 result.push((tag_oid, "tag".to_owned()));
6266 }
6267 }
6268 let commit = parse_commit(&object.data)?;
6269 let parent_union = union_parent_reachable_objects(
6270 repo,
6271 &commit.parents,
6272 missing_action,
6273 missing,
6274 missing_seen,
6275 )?;
6276 collect_tree_objects_filtered(
6277 repo,
6278 commit.tree,
6279 "",
6280 0,
6281 false,
6282 Some(&parent_union),
6283 tree_state,
6284 emitted,
6285 result,
6286 omitted,
6287 missing,
6288 missing_seen,
6289 filter,
6290 filter_provided,
6291 missing_action,
6292 sparse_lines,
6293 skip_trees_for_type_filter,
6294 omit_object_paths,
6295 packed_set,
6296 collect_tree_omits,
6297 tree_omit_set,
6298 combine_states,
6299 )?;
6300 }
6301 ObjectKind::Tree => {
6302 let root_path = root.root_path.as_deref().unwrap_or("");
6303 collect_tree_objects_filtered(
6304 repo,
6305 root.oid,
6306 root_path,
6307 0,
6308 true,
6309 None,
6310 tree_state,
6311 emitted,
6312 result,
6313 omitted,
6314 missing,
6315 missing_seen,
6316 filter,
6317 filter_provided,
6318 missing_action,
6319 sparse_lines,
6320 skip_trees_for_type_filter,
6321 omit_object_paths,
6322 packed_set,
6323 collect_tree_omits,
6324 tree_omit_set,
6325 combine_states,
6326 )?;
6327 }
6328 ObjectKind::Blob => {
6329 let path_for_sparse = root.root_path.as_deref().unwrap_or("");
6330 let sparse_blob = sparse_filter_includes_path(repo, path_for_sparse, sparse_lines);
6331 let blob_bits = match filter {
6332 None => ListFilterBits {
6333 mark_seen: true,
6334 do_show: true,
6335 skip_tree: false,
6336 },
6337 Some(f) => {
6338 if !filter_provided {
6339 ListFilterBits {
6340 mark_seen: true,
6341 do_show: true,
6342 skip_tree: false,
6343 }
6344 } else {
6345 match f {
6346 ObjectFilter::Combine(_) => filter_object_bits_blob(
6347 f,
6348 root.oid,
6349 object.data.len() as u64,
6350 0,
6351 tree_omit_set,
6352 collect_tree_omits,
6353 combine_states.as_mut().map(Vec::as_mut_slice),
6354 ),
6355 _ => filter_object_bits_blob(
6356 f,
6357 root.oid,
6358 object.data.len() as u64,
6359 0,
6360 tree_omit_set,
6361 collect_tree_omits,
6362 None,
6363 ),
6364 }
6365 }
6366 }
6367 };
6368 let blob_included = blob_bits.do_show && sparse_blob;
6369 if !blob_included {
6370 if blob_bits.mark_seen {
6371 omitted.push(root.oid);
6372 }
6373 return Ok(());
6374 }
6375 if packed_set.is_some_and(|p| p.contains(&root.oid)) {
6376 return Ok(());
6377 }
6378 if blob_bits.mark_seen && !emitted.insert(root.oid) {
6379 return Ok(());
6380 }
6381 let out_path = if omit_object_paths {
6382 String::new()
6383 } else {
6384 path_for_sparse.to_owned()
6385 };
6386 result.push((root.oid, out_path));
6387 }
6388 ObjectKind::Tag => {
6389 let tag = parse_tag(&object.data)?;
6390 let expected_kind =
6391 ExpectedObjectKind::from_tag_type(&tag.object_type).ok_or_else(|| {
6392 Error::CorruptObject(format!(
6393 "object {} has unsupported tag type '{}'",
6394 root.input, tag.object_type
6395 ))
6396 })?;
6397 let nested = RootObject {
6398 oid: tag.object,
6399 input: root.input.clone(),
6400 expected_kind: Some(expected_kind),
6401 root_path: None,
6402 wrap_with_tag: None,
6403 };
6404 collect_root_object(
6405 repo,
6406 &nested,
6407 tree_state,
6408 emitted,
6409 result,
6410 omitted,
6411 missing,
6412 missing_seen,
6413 filter,
6414 filter_provided,
6415 missing_action,
6416 sparse_lines,
6417 skip_trees_for_type_filter,
6418 omit_object_paths,
6419 packed_set,
6420 collect_tree_omits,
6421 tree_omit_set,
6422 combine_states,
6423 )?;
6424 }
6425 }
6426
6427 Ok(())
6428}
6429
6430fn collect_reachable_objects_in_commit_order(
6434 repo: &Repository,
6435 _graph: &mut CommitGraph<'_>,
6436 commits: &[ObjectId],
6437 object_roots: &[RootObject],
6438 tip_annotated_tags: &HashMap<ObjectId, ObjectId>,
6439 filter: Option<&ObjectFilter>,
6440 filter_provided: bool,
6441 missing_action: MissingAction,
6442 sparse_lines: Option<&[String]>,
6443 skip_trees_for_type_filter: bool,
6444 omit_object_paths: bool,
6445 packed_set: Option<&HashSet<ObjectId>>,
6446 collect_tree_omits: bool,
6447) -> Result<(
6448 Vec<(ObjectId, String)>,
6449 Vec<ObjectId>,
6450 Vec<ObjectId>,
6451 Vec<usize>,
6452)> {
6453 let mut tree_state = TreeWalkState::new();
6454 let mut top_tree_omit =
6455 walk_needs_top_tree_omit_set(filter, collect_tree_omits).then(HashSet::<ObjectId>::new);
6456 let mut combine_states = CombineSubState::prepare_sub_states(filter, collect_tree_omits);
6457 let mut emitted = HashSet::new();
6458 let mut result = Vec::new();
6459 let mut omitted = Vec::new();
6460 let mut missing = Vec::new();
6461 let mut missing_seen = HashSet::new();
6462 let mut counts = Vec::with_capacity(commits.len());
6463 for &commit_oid in commits {
6464 let commit = match load_commit(repo, commit_oid) {
6465 Ok(commit) => commit,
6466 Err(Error::ObjectNotFound(_)) if missing_action != MissingAction::Error => {
6467 if missing_action.reports_missing() && missing_seen.insert(commit_oid) {
6468 missing.push(commit_oid);
6469 }
6470 counts.push(0);
6471 continue;
6472 }
6473 Err(err) => return Err(err),
6474 };
6475 let before = result.len();
6476 if let Some(&tag_oid) = tip_annotated_tags.get(&commit_oid) {
6477 if emitted.insert(tag_oid) {
6478 result.push((tag_oid, "tag".to_owned()));
6479 }
6480 }
6481 collect_tree_objects_filtered(
6485 repo,
6486 commit.tree,
6487 "",
6488 0,
6489 false,
6490 None,
6491 &mut tree_state,
6492 &mut emitted,
6493 &mut result,
6494 &mut omitted,
6495 &mut missing,
6496 &mut missing_seen,
6497 filter,
6498 filter_provided,
6499 missing_action,
6500 sparse_lines,
6501 skip_trees_for_type_filter,
6502 omit_object_paths,
6503 packed_set,
6504 collect_tree_omits,
6505 &mut top_tree_omit,
6506 &mut combine_states,
6507 )?;
6508 counts.push(result.len() - before);
6509 }
6510
6511 for root in object_roots {
6512 collect_root_object(
6513 repo,
6514 root,
6515 &mut tree_state,
6516 &mut emitted,
6517 &mut result,
6518 &mut omitted,
6519 &mut missing,
6520 &mut missing_seen,
6521 filter,
6522 filter_provided,
6523 missing_action,
6524 sparse_lines,
6525 skip_trees_for_type_filter,
6526 omit_object_paths,
6527 packed_set,
6528 collect_tree_omits,
6529 &mut top_tree_omit,
6530 &mut combine_states,
6531 )?;
6532 }
6533
6534 Ok((result, omitted, missing, counts))
6535}
6536
6537fn kept_object_ids(repo: &Repository) -> Result<HashSet<ObjectId>> {
6539 let pack_dir = repo.git_dir.join("objects/pack");
6540 let mut kept = HashSet::new();
6541 if !pack_dir.is_dir() {
6542 return Ok(kept);
6543 }
6544 for entry in std::fs::read_dir(&pack_dir)? {
6545 let entry = entry?;
6546 let path = entry.path();
6547 if path.extension().is_some_and(|ext| ext == "keep") {
6548 let idx_path = path.with_extension("idx");
6550 if idx_path.exists() {
6551 if let Ok(oids) = crate::pack::read_idx_object_ids(&idx_path) {
6552 kept.extend(oids);
6553 }
6554 }
6555 }
6556 }
6557 Ok(kept)
6558}
6559
6560fn flatten_tree(
6561 repo: &Repository,
6562 tree_oid: ObjectId,
6563 prefix: &str,
6564) -> Result<Vec<(String, ObjectId)>> {
6565 let mut result = Vec::new();
6566 let object = match repo.odb.read(&tree_oid) {
6567 Ok(o) => o,
6568 Err(_) => return Ok(result),
6569 };
6570 if object.kind != ObjectKind::Tree {
6571 return Ok(result);
6572 }
6573 let entries = parse_tree(&object.data)?;
6574 for entry in entries {
6575 let name = String::from_utf8_lossy(&entry.name).to_string();
6576 let path = if prefix.is_empty() {
6577 name
6578 } else {
6579 format!("{prefix}/{name}")
6580 };
6581 let child = match repo.odb.read(&entry.oid) {
6582 Ok(o) => o,
6583 Err(Error::ObjectNotFound(_)) => continue,
6584 Err(err) => return Err(err),
6585 };
6586 if child.kind == ObjectKind::Tree {
6587 result.extend(flatten_tree(repo, entry.oid, &path)?);
6588 } else {
6589 result.push((path, entry.oid));
6590 }
6591 }
6592 Ok(result)
6593}
6594
6595pub fn merge_bases(
6597 repo: &Repository,
6598 a: ObjectId,
6599 b: ObjectId,
6600 first_parent_only: bool,
6601) -> Result<Vec<ObjectId>> {
6602 let mut graph = CommitGraph::new(repo, first_parent_only);
6603 let ancestors_a = walk_closure(&mut graph, &[a])?;
6604 let ancestors_b = walk_closure(&mut graph, &[b])?;
6605 let common: HashSet<ObjectId> = ancestors_a.intersection(&ancestors_b).copied().collect();
6606 if common.is_empty() {
6607 return Ok(Vec::new());
6608 }
6609 let mut bases = Vec::new();
6611 for &c in &common {
6612 let is_dominated = common.iter().any(|&other| {
6613 if other == c {
6614 return false;
6615 }
6616 let other_anc = walk_closure(&mut graph, &[other]).unwrap_or_default();
6617 other_anc.contains(&c)
6618 });
6619 if !is_dominated {
6620 bases.push(c);
6621 }
6622 }
6623 if bases.is_empty() {
6624 let mut sorted: Vec<_> = common.into_iter().collect();
6625 sorted.sort_by_key(|b| std::cmp::Reverse(graph.committer_time(*b)));
6626 bases.push(sorted[0]);
6627 }
6628 Ok(bases)
6629}