1use objects::object::StateId;
3use serde::{Deserialize, Serialize};
4
5mod kind;
6
7pub use kind::{AdvertisedRef, AdvertisedRefError, RefKind};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct RefFilter {
15 #[serde(default)]
16 pub names: Vec<String>,
17 #[serde(default)]
18 pub patterns: Vec<String>,
19 #[serde(default = "default_true")]
20 pub include_threads: bool,
21 #[serde(default = "default_true")]
22 pub include_markers: bool,
23 #[serde(default)]
26 pub include_synthetic: bool,
27 #[serde(default)]
28 pub limit: Option<usize>,
29}
30
31fn default_true() -> bool {
32 true
33}
34
35impl Default for RefFilter {
36 fn default() -> Self {
37 Self {
38 names: Vec::new(),
39 patterns: Vec::new(),
40 include_threads: true,
41 include_markers: true,
42 include_synthetic: false,
43 limit: None,
44 }
45 }
46}
47
48impl RefFilter {
49 pub fn matches(&self, name: &str) -> bool {
50 if !self.names.is_empty() && self.names.iter().any(|candidate| candidate == name) {
51 return true;
52 }
53
54 if self.patterns.is_empty() {
55 return self.names.is_empty();
56 }
57
58 self.patterns
59 .iter()
60 .any(|pattern| Self::matches_pattern(name, pattern))
61 }
62
63 pub fn includes_kind(&self, kind: RefKind) -> bool {
64 match kind {
65 RefKind::Thread => self.include_threads,
66 RefKind::Marker => self.include_markers,
67 RefKind::SyntheticFrontierRoot => self.include_synthetic,
68 }
69 }
70
71 fn matches_pattern(name: &str, pattern: &str) -> bool {
72 if pattern == "*" {
73 return true;
74 }
75 if pattern.starts_with('*') && pattern.ends_with('*') && pattern.len() >= 2 {
76 return name.contains(&pattern[1..pattern.len() - 1]);
77 }
78 if let Some(suffix) = pattern.strip_prefix('*') {
79 return name.ends_with(suffix);
80 }
81 if let Some(prefix) = pattern.strip_suffix('*') {
82 return name.starts_with(prefix);
83 }
84 name == pattern
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub enum HeadInfo {
91 Attached { thread: String },
92 Detached { state: StateId },
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct RefsList {
98 pub head: HeadInfo,
99 pub head_state: Option<StateId>,
100 pub refs: Vec<RefEntry>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct RefEntry {
106 pub name: String,
107 pub state_id: StateId,
108 pub kind: RefKind,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct RefUpdated {
113 pub success: bool,
114 pub old_value: Option<StateId>,
115 pub error: Option<String>,
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn ref_filter_matches_union_of_names_and_patterns() {
124 let filter = RefFilter {
125 names: vec!["refs/heads/main".to_string()],
126 patterns: vec!["refs/tags/v*".to_string()],
127 ..RefFilter::default()
128 };
129
130 assert!(filter.matches("refs/heads/main"));
131 assert!(filter.matches("refs/tags/v1.0.0"));
132 assert!(!filter.matches("refs/heads/feature"));
133 assert!(!filter.matches("refs/tags/nightly"));
134 }
135
136 #[test]
137 fn ref_filter_without_names_or_patterns_matches_everything() {
138 let filter = RefFilter::default();
139
140 assert!(filter.matches("refs/heads/main"));
141 assert!(filter.matches("refs/tags/v1.0.0"));
142 assert!(filter.matches("threads/alice"));
143 assert!(!filter.includes_kind(RefKind::SyntheticFrontierRoot));
144 assert!(filter.includes_kind(RefKind::Thread));
145 assert!(filter.includes_kind(RefKind::Marker));
146 }
147
148 #[test]
149 fn ref_filter_exact_names_do_not_expand_without_patterns() {
150 let filter = RefFilter {
151 names: vec!["refs/heads/main".to_string()],
152 patterns: Vec::new(),
153 ..RefFilter::default()
154 };
155
156 assert!(filter.matches("refs/heads/main"));
157 assert!(!filter.matches("refs/heads/mainline"));
158 }
159}