[O] Wording

This commit is contained in:
2026-05-10 11:07:08 +00:00
parent 7a699aee81
commit 582ea7c490
3 changed files with 128 additions and 56 deletions
+13 -7
View File
@@ -40,6 +40,12 @@ pub struct PullRequestInfo {
pub url: Option<String>, pub url: Option<String>,
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WebhookInstallOutcome {
Created,
Existing,
}
pub fn list_mirror_repos( pub fn list_mirror_repos(
config: &Config, config: &Config,
mirror: &MirrorConfig, mirror: &MirrorConfig,
@@ -172,7 +178,7 @@ impl<'a> ProviderClient<'a> {
repo: &RemoteRepo, repo: &RemoteRepo,
url: &str, url: &str,
secret: &str, secret: &str,
) -> Result<()> { ) -> Result<WebhookInstallOutcome> {
dispatch_provider!(self.site.provider, dispatch_provider!(self.site.provider,
github => self.github_install_webhook(endpoint, repo, url, secret), github => self.github_install_webhook(endpoint, repo, url, secret),
gitlab => self.gitlab_install_webhook(endpoint, repo, url, secret), gitlab => self.gitlab_install_webhook(endpoint, repo, url, secret),
@@ -317,7 +323,7 @@ impl<'a> ProviderClient<'a> {
repo: &RemoteRepo, repo: &RemoteRepo,
url: &str, url: &str,
secret: &str, secret: &str,
) -> Result<()> { ) -> Result<WebhookInstallOutcome> {
let hooks_url = self.repo_hooks_url(endpoint, &repo.name, "GitHub")?; let hooks_url = self.repo_hooks_url(endpoint, &repo.name, "GitHub")?;
let body = json!({ let body = json!({
"name": "web", "name": "web",
@@ -526,7 +532,7 @@ impl<'a> ProviderClient<'a> {
repo: &RemoteRepo, repo: &RemoteRepo,
url: &str, url: &str,
secret: &str, secret: &str,
) -> Result<()> { ) -> Result<WebhookInstallOutcome> {
let hooks_url = self.gitlab_hooks_url(endpoint, &repo.name); let hooks_url = self.gitlab_hooks_url(endpoint, &repo.name);
let body = json!({ let body = json!({
"url": url, "url": url,
@@ -695,7 +701,7 @@ impl<'a> ProviderClient<'a> {
repo: &RemoteRepo, repo: &RemoteRepo,
url: &str, url: &str,
secret: &str, secret: &str,
) -> Result<()> { ) -> Result<WebhookInstallOutcome> {
let hooks_url = self.repo_hooks_url(endpoint, &repo.name, "Gitea/Forgejo")?; let hooks_url = self.repo_hooks_url(endpoint, &repo.name, "Gitea/Forgejo")?;
let body = json!({ let body = json!({
"type": "gitea", "type": "gitea",
@@ -875,10 +881,10 @@ impl<'a> ProviderClient<'a> {
target_url: &str, target_url: &str,
body: &serde_json::Value, body: &serde_json::Value,
put_on_update: bool, put_on_update: bool,
) -> Result<()> { ) -> Result<WebhookInstallOutcome> {
let Some(hook) = self.find_existing_hook(hooks_url, target_url)? else { let Some(hook) = self.find_existing_hook(hooks_url, target_url)? else {
self.post_json::<serde_json::Value>(hooks_url, body)?; self.post_json::<serde_json::Value>(hooks_url, body)?;
return Ok(()); return Ok(WebhookInstallOutcome::Created);
}; };
let update_url = format!("{hooks_url}/{}", hook.id); let update_url = format!("{hooks_url}/{}", hook.id);
@@ -887,7 +893,7 @@ impl<'a> ProviderClient<'a> {
} else { } else {
self.patch_json::<serde_json::Value>(&update_url, body)?; self.patch_json::<serde_json::Value>(&update_url, body)?;
} }
Ok(()) Ok(WebhookInstallOutcome::Existing)
} }
fn delete_matching_hook(&self, hooks_url: &str, target_url: &str) -> Result<bool> { fn delete_matching_hook(&self, hooks_url: &str, target_url: &str) -> Result<bool> {
+24 -15
View File
@@ -18,7 +18,9 @@ use crate::config::{
Config, EndpointConfig, MirrorConfig, ProviderKind, RepoNameFilter, default_work_dir, Config, EndpointConfig, MirrorConfig, ProviderKind, RepoNameFilter, default_work_dir,
validate_config, validate_config,
}; };
use crate::provider::{EndpointRepo, ProviderClient, RemoteRepo, list_mirror_repos}; use crate::provider::{
EndpointRepo, ProviderClient, RemoteRepo, WebhookInstallOutcome, list_mirror_repos,
};
use crate::state::{load_toml_or_default, save_toml}; use crate::state::{load_toml_or_default, save_toml};
use crate::sync::{SyncOptions, sync_all}; use crate::sync::{SyncOptions, sync_all};
@@ -575,24 +577,32 @@ fn run_uninstall_tasks(tasks: Vec<WebhookUninstallTask>, jobs: usize) -> Result<
fn install_webhook_task(task: WebhookInstallTask, state: &Arc<Mutex<WebhookState>>) -> Result<()> { fn install_webhook_task(task: WebhookInstallTask, state: &Arc<Mutex<WebhookState>>) -> Result<()> {
let key = webhook_installation_key(&task.group, &task.endpoint, &task.repo.name); let key = webhook_installation_key(&task.group, &task.endpoint, &task.repo.name);
if task.dry_run {
crate::logln!( crate::logln!(
" {} {} {}", " {} {} {}",
style(if task.dry_run { style("would install").green().bold(),
"would install"
} else {
"install"
})
.green()
.bold(),
style(&task.repo.name).cyan(), style(&task.repo.name).cyan(),
style(format!("webhook on {}", task.endpoint.label())).dim() style(format!("webhook on {}", task.endpoint.label())).dim()
); );
if task.dry_run {
return Ok(()); return Ok(());
} }
let client = ProviderClient::new(&task.site)?; let client = ProviderClient::new(&task.site)?;
if let Err(error) = client.install_webhook(&task.endpoint, &task.repo, &task.url, &task.secret) match client.install_webhook(&task.endpoint, &task.repo, &task.url, &task.secret) {
{ Ok(outcome) => {
let action = match outcome {
WebhookInstallOutcome::Created => "install",
WebhookInstallOutcome::Existing => "exists",
};
crate::logln!(
" {} {} {}",
style(action).green().bold(),
style(&task.repo.name).cyan(),
style(format!("webhook on {}", task.endpoint.label())).dim()
);
record_webhook_installation(state, key, task);
Ok(())
}
Err(error) => {
if is_duplicate_webhook_error(&error) { if is_duplicate_webhook_error(&error) {
crate::logln!( crate::logln!(
" {} {} {}", " {} {} {}",
@@ -625,16 +635,15 @@ fn install_webhook_task(task: WebhookInstallTask, state: &Arc<Mutex<WebhookState
); );
return Ok(()); return Ok(());
} }
return Err(error).with_context(|| { Err(error).with_context(|| {
format!( format!(
"failed to install webhook for {} on {}", "failed to install webhook for {} on {}",
task.repo.name, task.repo.name,
task.endpoint.label() task.endpoint.label()
) )
}); })
}
} }
record_webhook_installation(state, key, task);
Ok(())
} }
fn record_webhook_installation( fn record_webhook_installation(
+58 -1
View File
@@ -309,7 +309,7 @@ fn install_webhook_posts_github_hook_when_missing() {
}; };
let client = ProviderClient::new(&site).unwrap(); let client = ProviderClient::new(&site).unwrap();
client let outcome = client
.install_webhook( .install_webhook(
&EndpointConfig { &EndpointConfig {
site: "github".to_string(), site: "github".to_string(),
@@ -326,6 +326,63 @@ fn install_webhook_posts_github_hook_when_missing() {
"secret", "secret",
) )
.unwrap(); .unwrap();
assert_eq!(outcome, WebhookInstallOutcome::Created);
handle.join().unwrap();
}
#[test]
fn install_webhook_reports_existing_forgejo_hook() {
let (api_url, handle) = request_server(
vec![
(
"200 OK",
r#"[{"id":42,"config":{"url":"https://mirror.example.test/webhook/"}}]"#,
),
("200 OK", r#"{"id":42}"#),
],
|index, request| match index {
0 => assert!(
request.starts_with("GET /repos/alice/repo/hooks "),
"request was {request}"
),
1 => {
assert!(
request.starts_with("PATCH /repos/alice/repo/hooks/42 "),
"request was {request}"
);
assert!(request.contains("https://mirror.example.test/webhook"));
assert!(request.contains("secret"));
assert!(request.contains("push"));
}
_ => unreachable!(),
},
);
let site = SiteConfig {
api_url: Some(api_url),
..site(ProviderKind::Forgejo, None)
};
let client = ProviderClient::new(&site).unwrap();
let outcome = client
.install_webhook(
&EndpointConfig {
site: "forgejo".to_string(),
kind: NamespaceKind::User,
namespace: "alice".to_string(),
},
&RemoteRepo {
name: "repo".to_string(),
clone_url: "https://codeberg.org/alice/repo.git".to_string(),
private: true,
description: None,
},
"https://mirror.example.test/webhook",
"secret",
)
.unwrap();
assert_eq!(outcome, WebhookInstallOutcome::Existing);
handle.join().unwrap(); handle.join().unwrap();
} }