1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/// Partial-clone object policies. Explicitly wanted objects are retained.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum BlobFilter {
/// Keep the complete selected closure.
#[default]
All,
/// Omit blobs reached through trees or tags. An explicit blob want wins.
None,
/// Omit blobs of this size or larger, in bytes.
Limit(u64),
/// Omit every other kind, retaining explicitly wanted objects.
Type(ObjectKind),
/// Omit trees and blobs at this depth or deeper, from each root tree.
TreeDepth(u64),
}
impl BlobFilter {
/// Apply the policy at root-tree depth zero. Use `includes_at` when an
/// object is reached below a root tree.
pub const fn includes(self, info: ObjectInfo, explicitly_wanted: bool) -> bool {
self.includes_at(info, explicitly_wanted, 0)
}
/// Apply the policy at the minimum zero-based tree distance. Explicitly
/// requested objects bypass filtering; commits and tags have no tree depth.
pub const fn includes_at(
self,
info: ObjectInfo,
explicitly_wanted: bool,
tree_depth: u32,
) -> bool {
if explicitly_wanted {
return true;
}
match self {
Self::All => true,
Self::None => !matches!(info.kind, ObjectKind::Blob),
Self::Limit(limit) => !matches!(info.kind, ObjectKind::Blob) || info.size < limit,
Self::Type(kind) => info.kind as u8 == kind as u8,
Self::TreeDepth(limit) => {
!matches!(info.kind, ObjectKind::Tree | ObjectKind::Blob)
|| (tree_depth as u64) < limit
}
}
}
/// A tree at the exclusion depth contributes no children, including when
/// the tree itself is retained as an explicit want.
pub const fn follows_tree(self, tree_depth: u32) -> bool {
match self {
Self::TreeDepth(limit) => (tree_depth as u64) < limit,
_ => true,
}
}
}
/// Bounded history and object selection, sharing the ordinary graph-read budget.
#[derive(Clone, Copy, Debug, Default)]
pub struct FetchOptions {
/// Absolute commit depth from the wanted tips (1..=2147483647).
/// None preserves client shallow boundaries. A depth request retransmits
/// its bounded closure so shallow haves cannot hide newly requested history.
/// A relative request without client boundaries keeps common-history pruning.
pub depth: Option<u32>,
/// Add `depth` to the nearest client boundary measured from wanted tips
/// (Git 2.55). Inert without a depth; unrelated client boundaries are preserved.
/// Without a boundary in wanted history, select its complete closure.
pub deepen_relative: bool,
/// Apply the object policy after reachability and common-history exclusion.
/// Filtering does not change commit parents or tree bodies. The receiving
/// host must retain Git's shallow/promisor metadata when importing the pack.
pub filter: BlobFilter,
}
use crate::{Error, ObjectInfo, ObjectKind, Result};
/// History decisions for an external, host-owned graph frontier. The host
/// supplies the shortest commit distance from wanted roots (starting at one)
/// and persists deduplication, exclusions and work budgets outside this value.
#[derive(Clone, Copy, Debug, Default)]
pub struct HistoryPolicy {
pub(crate) limit: Option<u32>,
pub(crate) client: bool,
pub(crate) distance: bool,
}
impl HistoryPolicy {
/// Measure distances to existing client boundaries without crossing them.
/// A host only needs commit-parent and tag-target edges during this probe.
pub const fn boundary_probe() -> Self {
Self {
limit: None,
client: true,
distance: true,
}
}
pub const fn follows_parents(self, depth: u32, client_boundary: bool) -> bool {
!((self.client && client_boundary)
|| match self.limit {
Some(limit) => depth >= limit,
None => false,
})
}
pub const fn parent_depth(self, depth: u32) -> u32 {
if self.limit.is_some() || self.distance {
depth.saturating_add(1)
} else {
1
}
}
}
impl FetchOptions {
/// Shallow haves cannot exclude the newly requested history. A relative
/// request from a complete client preserves ordinary common exclusion.
pub const fn deepens_history(self, has_client_boundaries: bool) -> bool {
self.depth.is_some() && (!self.deepen_relative || has_client_boundaries)
}
/// Derive the wanted-history cut after a relative boundary probe. None
/// means no client boundary occurs in wanted history, leaving it complete.
pub fn history(
self,
has_client_boundaries: bool,
nearest_boundary_depth: Option<u32>,
) -> Result<HistoryPolicy> {
if self
.depth
.is_some_and(|depth| depth == 0 || depth > i32::MAX as u32)
|| nearest_boundary_depth == Some(0)
{
return Err(Error::InvalidRequest);
}
let limit = match self
.depth
.filter(|_| self.deepens_history(has_client_boundaries))
{
Some(depth) if self.deepen_relative => nearest_boundary_depth
.map(|boundary| boundary.checked_add(depth).ok_or(Error::LimitExceeded))
.transpose()?,
depth => depth,
};
Ok(HistoryPolicy {
limit,
client: self.depth.is_none(),
distance: false,
})
}
}