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

ambee/giterated

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

Move towards having GitBackend split into files

Emilia - ⁨1⁩ year ago

parent: tbd commit: ⁨e55da0e

⁨giterated-daemon/src/backend/git/branches.rs⁩ - ⁨7555⁩ 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 .naive_utc()
65 .signed_duration_since(commit.time)
66 .num_seconds()
67 > stale_after.into();
68
69 // Filter based on if the branch is stale or not
70 if request.filter != RepositoryBranchFilter::None {
71 #[allow(clippy::if_same_then_else)]
72 if stale && request.filter == RepositoryBranchFilter::Active {
73 return None;
74 } else if !stale && request.filter == RepositoryBranchFilter::Stale {
75 return None;
76 }
77 }
78
79 Some((name.to_string(), branch, stale, commit))
80 })
81 .collect::<Vec<_>>();
82
83 // Get the total amount of filtered branches
84 let branch_count = filtered_branches.len();
85
86 if let Some(search) = &request.search {
87 // TODO: Caching
88 // Search by sorting using a simple fuzzy search algorithm
89 filtered_branches.sort_by(|(n1, _, _, _), (n2, _, _, _)| {
90 strsim::damerau_levenshtein(search, n1)
91 .cmp(&strsim::damerau_levenshtein(search, n2))
92 });
93 } else {
94 // Sort the branches by commit date
95 filtered_branches.sort_by(|(_, _, _, c1), (_, _, _, c2)| c2.time.cmp(&c1.time));
96 }
97
98 // Go to the requested position
99 let mut filtered_branches = filtered_branches.iter().skip(request.range.0);
100
101 let mut branches = vec![];
102
103 // Iterate through the filtered branches using the passed range
104 for _ in request.range.0..request.range.1 {
105 let Some((name, branch, stale, commit)) = filtered_branches.next() else {
106 break;
107 };
108
109 // Get how many commits are ahead of and behind of the head
110 let ahead_behind_default =
111 if default_branch.get().target().is_some() && branch.get().target().is_some() {
112 git.graph_ahead_behind(
113 branch.get().target().unwrap(),
114 default_branch.get().target().unwrap(),
115 )
116 .ok()
117 } else {
118 None
119 };
120
121 branches.push(RepositoryBranch {
122 name: name.to_string(),
123 stale: *stale,
124 last_commit: Some(commit.clone()),
125 ahead_behind_default,
126 })
127 }
128
129 Ok((branches, branch_count))
130 }
131
132 pub async fn handle_repository_get_branch(
133 &mut self,
134 requester: &Option<AuthenticatedUser>,
135 repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>,
136 OperationState(operation_state): OperationState<StackOperationState>,
137 request: &RepositoryBranchRequest,
138 ) -> Result<RepositoryBranch, Error> {
139 let repository = repository_object.object();
140 let git = self
141 .open_repository_and_check_permissions(&repository.owner, &repository.name, requester)
142 .await?;
143
144 // TODO: Don't duplicate search when the default branch and the requested one are the same
145 // Get the default branch to compare against
146 let default_branch_name = repository_object
147 .get::<DefaultBranch>(&operation_state)
148 .await?;
149 let default_branch = git
150 .find_branch(&default_branch_name.0, BranchType::Local)
151 .map_err(|_| GitBackendError::DefaultNotFound)?;
152
153 // Find the requested branch
154 let branch = git
155 .find_branch(&request.name, BranchType::Local)
156 .map_err(|_| GitBackendError::BranchNotFound(request.name.clone()))?;
157
158 // Get the stale(after X seconds) setting
159 let stale_after = repository_object
160 .get::<BranchStaleAfter>(&operation_state)
161 .await
162 .unwrap_or_default()
163 .0;
164
165 // TODO: Non UTF-8?
166 let last_commit = GitBackend::get_last_commit_in_rev(
167 &git,
168 branch.get().name().unwrap(),
169 &default_branch_name,
170 )
171 .ok();
172
173 let stale = if let Some(ref last_commit) = last_commit {
174 chrono::Utc::now()
175 .naive_utc()
176 .signed_duration_since(last_commit.time)
177 .num_seconds()
178 > stale_after.into()
179 } else {
180 // TODO: Make sure it's acceptable to return false here
181 false
182 };
183
184 // Get how many commits are ahead of and behind of the head
185 let ahead_behind_default =
186 if default_branch.get().target().is_some() && branch.get().target().is_some() {
187 git.graph_ahead_behind(
188 branch.get().target().unwrap(),
189 default_branch.get().target().unwrap(),
190 )
191 .ok()
192 } else {
193 None
194 };
195
196 Ok(RepositoryBranch {
197 name: request.name.clone(),
198 stale,
199 last_commit,
200 ahead_behind_default,
201 })
202 }
203 }
204