git_workspace/providers/
gitea.rs1use crate::providers::{
2 create_exclude_regex_set, create_include_regex_set, Provider, APP_USER_AGENT,
3};
4use crate::repository::Repository;
5use anyhow::Context;
6use console::style;
7use serde::{Deserialize, Serialize};
8use std::env;
9use std::fmt;
10
11#[derive(Deserialize, Debug)]
12struct GiteaRepository {
13 full_name: String,
14 clone_url: String,
15 ssh_url: String,
16 default_branch: String,
17 archived: bool,
18 fork: bool,
19}
20
21fn default_env_var() -> String {
22 String::from("GITEA_TOKEN")
23}
24
25static DEFAULT_GITEA_URL: &str = "https://gitea.com";
26
27fn public_gitea_url() -> String {
28 DEFAULT_GITEA_URL.to_string()
29}
30
31#[derive(Deserialize, Serialize, Debug, Eq, Ord, PartialEq, PartialOrd, clap::Parser)]
32#[serde(rename_all = "lowercase")]
33#[command(about = "Add a Gitea user or organization by name")]
34pub struct GiteaProvider {
35 pub name: String,
37
38 #[arg(long = "path", default_value = "gitea")]
39 path: String,
41
42 #[arg(long = "env-name", short = 'e', default_value = "GITEA_TOKEN")]
43 #[serde(default = "default_env_var")]
44 env_var: String,
46
47 #[arg(long = "skip-forks")]
48 #[serde(default)]
49 skip_forks: bool,
51
52 #[arg(long = "include")]
53 #[serde(default)]
54 include: Vec<String>,
56
57 #[arg(long = "auth-http")]
58 #[serde(default)]
59 auth_http: bool,
61
62 #[arg(long = "exclude")]
63 #[serde(default)]
64 exclude: Vec<String>,
66
67 #[arg(long = "url", default_value = DEFAULT_GITEA_URL)]
68 #[serde(default = "public_gitea_url")]
69 pub url: String,
71}
72
73impl fmt::Display for GiteaProvider {
74 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 write!(
76 f,
77 "Gitea user/org {} at {} in directory {}, using the token stored in {}",
78 style(&self.name.to_lowercase()).green(),
79 style(&self.url).green(),
80 style(&self.path).green(),
81 style(&self.env_var).green(),
82 )
83 }
84}
85
86impl Provider for GiteaProvider {
87 fn correctly_configured(&self) -> bool {
88 let token = env::var(&self.env_var);
89 if token.is_err() {
90 println!(
91 "{}",
92 style(format!(
93 "Error: {} environment variable is not defined",
94 self.env_var
95 ))
96 .red()
97 );
98 println!("Create an access token in your Gitea Settings -> Applications");
99 println!(
100 "Then set a {} environment variable with the value",
101 style(&self.env_var).green()
102 );
103 return false;
104 }
105 if self.name.ends_with('/') {
106 println!(
107 "{}",
108 style("Error: Ensure that names do not end in forward slashes").red()
109 );
110 println!("You specified: {}", self.name);
111 return false;
112 }
113 true
114 }
115
116 fn fetch_repositories(&self) -> anyhow::Result<Vec<Repository>> {
117 let gitea_token = env::var(&self.env_var)
118 .with_context(|| format!("Missing {} environment variable", self.env_var))?;
119
120 let include_regex_set = create_include_regex_set(&self.include)?;
121 let exclude_regex_set = create_exclude_regex_set(&self.exclude)?;
122
123 let agent = ureq::AgentBuilder::new()
124 .https_only(true)
125 .user_agent(APP_USER_AGENT)
126 .build();
127
128 let mut page = 1;
129 let mut repositories = Vec::new();
130
131 loop {
132 let url = format!(
133 "{}/api/v1/users/{}/repos?page={}&limit=50",
134 self.url, self.name, page
135 );
136
137 let response = agent
138 .get(&url)
139 .set("Authorization", &format!("token {}", gitea_token))
140 .call()?;
141
142 let repos: Vec<GiteaRepository> = response.into_json()?;
143 if repos.is_empty() {
144 break;
145 }
146
147 repositories.extend(
148 repos
149 .into_iter()
150 .filter(|r| !r.archived)
151 .filter(|r| !self.skip_forks || !r.fork)
152 .filter(|r| include_regex_set.is_match(&r.full_name))
153 .filter(|r| !exclude_regex_set.is_match(&r.full_name))
154 .map(|r| {
155 Repository::new(
156 format!("{}/{}", self.path, r.full_name),
157 if self.auth_http {
158 r.clone_url
159 } else {
160 r.ssh_url
161 },
162 Some(r.default_branch),
163 None,
164 )
165 }),
166 );
167
168 page += 1;
169 }
170
171 Ok(repositories)
172 }
173}