[O] Wording
This commit is contained in:
+13
-7
@@ -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> {
|
||||||
|
|||||||
+57
-48
@@ -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,66 +577,73 @@ 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);
|
||||||
crate::logln!(
|
|
||||||
" {} {} {}",
|
|
||||||
style(if task.dry_run {
|
|
||||||
"would install"
|
|
||||||
} else {
|
|
||||||
"install"
|
|
||||||
})
|
|
||||||
.green()
|
|
||||||
.bold(),
|
|
||||||
style(&task.repo.name).cyan(),
|
|
||||||
style(format!("webhook on {}", task.endpoint.label())).dim()
|
|
||||||
);
|
|
||||||
if task.dry_run {
|
if task.dry_run {
|
||||||
|
crate::logln!(
|
||||||
|
" {} {} {}",
|
||||||
|
style("would install").green().bold(),
|
||||||
|
style(&task.repo.name).cyan(),
|
||||||
|
style(format!("webhook on {}", task.endpoint.label())).dim()
|
||||||
|
);
|
||||||
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) => {
|
||||||
if is_duplicate_webhook_error(&error) {
|
let action = match outcome {
|
||||||
|
WebhookInstallOutcome::Created => "install",
|
||||||
|
WebhookInstallOutcome::Existing => "exists",
|
||||||
|
};
|
||||||
crate::logln!(
|
crate::logln!(
|
||||||
" {} {} {}",
|
" {} {} {}",
|
||||||
style("exists").green().bold(),
|
style(action).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()
|
||||||
);
|
);
|
||||||
record_webhook_installation(state, key, task);
|
record_webhook_installation(state, key, task);
|
||||||
return Ok(());
|
Ok(())
|
||||||
}
|
}
|
||||||
if let Some(reason) = non_actionable_webhook_failure_reason(&error) {
|
Err(error) => {
|
||||||
crate::logln!(
|
if is_duplicate_webhook_error(&error) {
|
||||||
" {} {} {}",
|
crate::logln!(
|
||||||
style("skip").yellow().bold(),
|
" {} {} {}",
|
||||||
style(&task.repo.name).cyan(),
|
style("exists").green().bold(),
|
||||||
style(format!("webhook on {}: {reason}", task.endpoint.label())).dim()
|
style(&task.repo.name).cyan(),
|
||||||
);
|
style(format!("webhook on {}", task.endpoint.label())).dim()
|
||||||
let mut state = state
|
);
|
||||||
.lock()
|
record_webhook_installation(state, key, task);
|
||||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
return Ok(());
|
||||||
state.skipped.insert(
|
}
|
||||||
key,
|
if let Some(reason) = non_actionable_webhook_failure_reason(&error) {
|
||||||
SkippedWebhookInstallation {
|
crate::logln!(
|
||||||
group: task.group,
|
" {} {} {}",
|
||||||
endpoint: task.endpoint,
|
style("skip").yellow().bold(),
|
||||||
repo: task.repo.name,
|
style(&task.repo.name).cyan(),
|
||||||
url: task.url,
|
style(format!("webhook on {}: {reason}", task.endpoint.label())).dim()
|
||||||
reason,
|
);
|
||||||
},
|
let mut state = state
|
||||||
);
|
.lock()
|
||||||
return Ok(());
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||||
|
state.skipped.insert(
|
||||||
|
key,
|
||||||
|
SkippedWebhookInstallation {
|
||||||
|
group: task.group,
|
||||||
|
endpoint: task.endpoint,
|
||||||
|
repo: task.repo.name,
|
||||||
|
url: task.url,
|
||||||
|
reason,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(error).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to install webhook for {} on {}",
|
||||||
|
task.repo.name,
|
||||||
|
task.endpoint.label()
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return 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(
|
fn record_webhook_installation(
|
||||||
|
|||||||
+58
-1
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user