JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

Change to new UTC chrono types

Emilia - ⁨1⁩ year ago

parent: tbd commit: ⁨779637c

⁨giterated-daemon/src/backend/git/branches.rs⁩ - ⁨7493⁩ bytes
Raw
1 use anyhow::Error;
2 use git2::BranchType;
3 use giterated_models::{
4 object::Object,
5 repository::{
6 BranchStaleAfter, DefaultBranch, Repository, RepositoryBranch, RepositoryBranchFilter,
7 RepositoryBranchRequest, RepositoryBranchesRequest,
8 },
9 };
10 use giterated_stack::{AuthenticatedUser, GiteratedStack, OperationState, StackOperationState};
11
12 use super::{GitBackend, GitBackendError};
13
14 impl GitBackend {
15 /// .0: List of branches filtering by passed requirements.
16 /// .1: Total amount of branches after being filtered
17 pub async fn handle_repository_get_branches(
18 &mut self,
19 requester: &Option<AuthenticatedUser>,
20 repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>,
21 OperationState(operation_state): OperationState<StackOperationState>,
22 request: &RepositoryBranchesRequest,
23 ) -> Result<(Vec<RepositoryBranch>, usize), Error> {
24 let repository = repository_object.object();
25 let git = self
26 .open_repository_and_check_permissions(&repository.owner, &repository.name, requester)
27 .await?;
28
29 let default_branch_name = repository_object
30 .get::<DefaultBranch>(&operation_state)
31 .await?;
32 let default_branch = git
33 .find_branch(&default_branch_name.0, BranchType::Local)
34 .map_err(|_| GitBackendError::DefaultNotFound)?;
35
36 // Get the stale(after X seconds) setting
37 let stale_after = repository_object
38 .get::<BranchStaleAfter>(&operation_state)
39 .await
40 .unwrap_or_default()
41 .0;
42
43 // Could be done better with the RepositoryBranchFilter::None check done beforehand.
44 let mut filtered_branches = git
45 .branches(None)?
46 .filter_map(|branch| {
47 let branch = branch.ok()?.0;
48
49 let Some(name) = branch.name().ok().flatten() else {
50 return None;
51 };
52
53 // TODO: Non UTF-8?
54 let Some(commit) = GitBackend::get_last_commit_in_rev(
55 &git,
56 branch.get().name().unwrap(),
57 &default_branch_name,
58 )
59 .ok() else {
60 return None;
61 };
62
63 let stale = chrono::Utc::now()
64 .signed_duration_since(commit.time)
65 .num_seconds()
66 > stale_after.into();
67
68 // Filter based on if the branch is stale or not
69 if request.filter != RepositoryBranchFilter::None {
70 #[allow(clippy::if_same_then_else)]
71 if stale && request.filter == RepositoryBranchFilter::Active {
72 return None;
73 } else if !stale && request.filter == RepositoryBranchFilter::Stale {
74 return None;
75 }
76 }
77
78 Some((name.to_string(), branch, stale, commit))
79 })
80 .collect::<Vec<_>>();
81
82 // Get the total amount of filtered branches
83 let branch_count = filtered_branches.len();
84
85 if let Some(search) = &request.search {
86 // TODO: Caching
87 // Search by sorting using a simple fuzzy search algorithm
88 filtered_branches.sort_by(|(n1, _, _, _), (n2, _, _, _)| {
89 strsim::damerau_levenshtein(search, n1)
90 .cmp(&strsim::damerau_levenshtein(search, n2))
91 });
92 } else {
93 // Sort the branches by commit date
94 filtered_branches.sort_by(|(_, _, _, c1), (_, _, _, c2)| c2.time.cmp(&c1.time));
95 }
96
97 // Go to the requested position
98 let mut filtered_branches = filtered_branches.iter().skip(request.range.0);
99
100 let mut branches = vec![];
101
102 // Iterate through the filtered branches using the passed range
103 for _ in request.range.0..request.range.1 {
104 let Some((name, branch, stale, commit)) = filtered_branches.next() else {
105 break;
106 };
107
108 // Get how many commits are ahead of and behind of the head
109 let ahead_behind_default =
110 if default_branch.get().target().is_some() && branch.get().target().is_some() {
111 git.graph_ahead_behind(
112 branch.get().target().unwrap(),
113 default_branch.get().target().unwrap(),
114 )
115 .ok()
116 } else {
117 None
118 };
119
120 branches.push(RepositoryBranch {
121 name: name.to_string(),
122 stale: *stale,
123 last_commit: Some(commit.clone()),
124 ahead_behind_default,
125 })
126 }
127
128 Ok((branches, branch_count))
129 }
130
131 pub async fn handle_repository_get_branch(
132 &mut self,
133 requester: &Option<AuthenticatedUser>,
134 repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>,
135 OperationState(operation_state): OperationState<StackOperationState>,
136 request: &RepositoryBranchRequest,
137 ) -> Result<RepositoryBranch, Error> {
138 let repository = repository_object.object();
139 let git = self
140 .open_repository_and_check_permissions(&repository.owner, &repository.name, requester)
141 .await?;
142
143 // TODO: Don't duplicate search when the default branch and the requested one are the same
144 // Get the default branch to compare against
145 let default_branch_name = repository_object
146 .get::<DefaultBranch>(&operation_state)
147 .await?;
148 let default_branch = git
149 .find_branch(&default_branch_name.0, BranchType::Local)
150 .map_err(|_| GitBackendError::DefaultNotFound)?;
151
152 // Find the requested branch
153 let branch = git
154 .find_branch(&request.name, BranchType::Local)
155 .map_err(|_| GitBackendError::BranchNotFound(request.name.clone()))?;
156
157 // Get the stale(after X seconds) setting
158 let stale_after = repository_object
159 .get::<BranchStaleAfter>(&operation_state)
160 .await
161 .unwrap_or_default()
162 .0;
163
164 // TODO: Non UTF-8?
165 let last_commit = GitBackend::get_last_commit_in_rev(
166 &git,
167 branch.get().name().unwrap(),
168 &default_branch_name,
169 )
170 .ok();
171
172 let stale = if let Some(ref last_commit) = last_commit {
173 chrono::Utc::now()
174 .signed_duration_since(last_commit.time)
175 .num_seconds()
176 > stale_after.into()
177 } else {
178 // TODO: Make sure it's acceptable to return false here
179 false
180 };
181
182 // Get how many commits are ahead of and behind of the head
183 let ahead_behind_default =
184 if default_branch.get().target().is_some() && branch.get().target().is_some() {
185 git.graph_ahead_behind(
186 branch.get().target().unwrap(),
187 default_branch.get().target().unwrap(),
188 )
189 .ok()
190 } else {
191 None
192 };
193
194 Ok(RepositoryBranch {
195 name: request.name.clone(),
196 stale,
197 last_commit,
198 ahead_behind_default,
199 })
200 }
201 }
202