Skip to main content

wire/
message_refs.rs

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