[O] Wording

This commit is contained in:
2026-05-10 11:07:08 +00:00
parent 7240b05feb
commit d9726c4235
3 changed files with 128 additions and 56 deletions
+13 -7
View File
@@ -40,6 +40,12 @@ pub struct PullRequestInfo {
pub url: Option<String>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WebhookInstallOutcome {
Created,
Existing,
}
pub fn list_mirror_repos(
config: &Config,
mirror: &MirrorConfig,
@@ -172,7 +178,7 @@ impl<'a> ProviderClient<'a> {
repo: &RemoteRepo,
url: &str,
secret: &str,
) -> Result<()> {
) -> Result<WebhookInstallOutcome> {
dispatch_provider!(self.site.provider,
github => self.github_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,
url: &str,
secret: &str,
) -> Result<()> {
) -> Result<WebhookInstallOutcome> {
let hooks_url = self.repo_hooks_url(endpoint, &repo.name, "GitHub")?;
let body = json!({
"name": "web",
@@ -526,7 +532,7 @@ impl<'a> ProviderClient<'a> {
repo: &RemoteRepo,
url: &str,
secret: &str,
) -> Result<()> {
) -> Result<WebhookInstallOutcome> {
let hooks_url = self.gitlab_hooks_url(endpoint, &repo.name);
let body = json!({
"url": url,
@@ -695,7 +701,7 @@ impl<'a> ProviderClient<'a> {
repo: &RemoteRepo,
url: &str,
secret: &str,
) -> Result<()> {
) -> Result<WebhookInstallOutcome> {
let hooks_url = self.repo_hooks_url(endpoint, &repo.name, "Gitea/Forgejo")?;
let body = json!({
"type": "gitea",
@@ -875,10 +881,10 @@ impl<'a> ProviderClient<'a> {
target_url: &str,
body: &serde_json::Value,
put_on_update: bool,
) -> Result<()> {
) -> Result<WebhookInstallOutcome> {
let Some(hook) = self.find_existing_hook(hooks_url, target_url)? else {
self.post_json::<serde_json::Value>(hooks_url, body)?;
return Ok(());
return Ok(WebhookInstallOutcome::Created);
};
let update_url = format!("{hooks_url}/{}", hook.id);
@@ -887,7 +893,7 @@ impl<'a> ProviderClient<'a> {
} else {
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> {
+24 -15
View File
@@ -18,7 +18,9 @@ use crate::config::{
Config, EndpointConfig, MirrorConfig, ProviderKind, RepoNameFilter, default_work_dir,
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::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<()> {
let key = webhook_installation_key(&task.group, &task.endpoint, &task.repo.name);
if task.dry_run {
crate::logln!(
" {} {} {}",
style(if task.dry_run {
"would install"
} else {
"install"
})
.green()
.bold(),
style("would install").green().bold(),
style(&task.repo.name).cyan(),
style(format!("webhook on {}", task.endpoint.label())).dim()
);
if task.dry_run {
return Ok(());
}
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) {
crate::logln!(
" {} {} {}",
@@ -625,16 +635,15 @@ fn install_webhook_task(task: WebhookInstallTask, state: &Arc<Mutex<WebhookState
);
return Ok(());
}
return Err(error).with_context(|| {
Err(error).with_context(|| {
format!(
"failed to install webhook for {} on {}",
task.repo.name,
task.endpoint.label()
)
});
})
}
}
record_webhook_installation(state, key, task);
Ok(())
}
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();
client
let outcome = client
.install_webhook(
&EndpointConfig {
site: "github".to_string(),
@@ -326,6 +326,63 @@ fn install_webhook_posts_github_hook_when_missing() {
"secret",
)
.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();
}