feat(integrations): add merge-method selection and provider-specific payloads
Introduce dedicated merge handling for integration review merges: - Add src-tauri/src/integrations/merge.rs: implements merge_options (read provider repo settings), merge_payload (build provider-specific merge body) and a Tauri command get_integration_review_merge_options. Includes unit tests for behavior. - Wire merge module into integrations.rs and pass an optional merge_method into provider-specific review action functions (GitHub, GitLab, Gitea, Azure DevOps). run_integration_review_action now accepts an optional merge_method, validates it early, and includes provider-specific merge payloads when performing a merge. - Export the new command in src-tauri/src/main.rs so the frontend can request merge options. Frontend changes to support selecting a merge method before merging: - ConfirmDialog.svelte: add SelectMenu support and a select field to confirm requests. - ReviewCenter.svelte: fetch integration merge options, show a merge-method selector in the merge confirmation, and pass the chosen method to the review action. - Update types and git bindings to surface IntegrationMergeOptions / IntegrationMergeMethod and the getIntegrationReviewMergeOptions call (git.ts / types.ts changes staged). Effect: users can pick a merge method appropriate to the provider/project; the integration layer generates the correct API payload per provider. Tests added for merge logic.
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
mod merge;
|
||||
pub use merge::get_integration_review_merge_options;
|
||||
use merge::merge_payload;
|
||||
mod issue_creation;
|
||||
pub use issue_creation::*;
|
||||
mod issue_actions;
|
||||
@@ -864,13 +867,14 @@ fn github_review_action(
|
||||
repository_name: &str,
|
||||
number: u64,
|
||||
action: &str,
|
||||
merge_method: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
if repository_name.split('/').count() != 2 {
|
||||
return Err("GitHub returned an invalid repository name.".to_string());
|
||||
}
|
||||
let endpoint = format!("{}/repos/{repository_name}/pulls/{number}", github_api_base_url(base_url)?);
|
||||
let request = match action {
|
||||
"merge" => client.put(format!("{endpoint}/merge")).json(&serde_json::json!({})),
|
||||
"merge" => client.put(format!("{endpoint}/merge")).json(&merge_payload("github", merge_method)?),
|
||||
"approve" => client.post(format!("{endpoint}/reviews")).json(&serde_json::json!({ "event": "APPROVE" })),
|
||||
"close" => client.patch(&endpoint).json(&serde_json::json!({ "state": "closed" })),
|
||||
"reopen" => client.patch(&endpoint).json(&serde_json::json!({ "state": "open" })),
|
||||
@@ -893,13 +897,14 @@ fn gitlab_review_action(
|
||||
repository_id: &str,
|
||||
number: u64,
|
||||
action: &str,
|
||||
merge_method: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
if repository_id.trim().is_empty() {
|
||||
return Err("GitLab returned an invalid project identifier.".to_string());
|
||||
}
|
||||
let endpoint = format!("{base_url}/api/v4/projects/{repository_id}/merge_requests/{number}");
|
||||
let request = match action {
|
||||
"merge" => client.put(format!("{endpoint}/merge")),
|
||||
"merge" => client.put(format!("{endpoint}/merge")).json(&merge_payload("gitlab", merge_method)?),
|
||||
"approve" => client.post(format!("{endpoint}/approve")),
|
||||
"close" => client.put(&endpoint).query(&[("state_event", "close")]),
|
||||
"reopen" => client.put(&endpoint).query(&[("state_event", "reopen")]),
|
||||
@@ -921,13 +926,14 @@ fn gitea_review_action(
|
||||
repository_name: &str,
|
||||
number: u64,
|
||||
action: &str,
|
||||
merge_method: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
if repository_name.split('/').count() != 2 {
|
||||
return Err("Gitea returned an invalid repository name.".to_string());
|
||||
}
|
||||
let endpoint = format!("{base_url}/api/v1/repos/{repository_name}/pulls/{number}");
|
||||
let request = match action {
|
||||
"merge" => client.post(format!("{endpoint}/merge")).json(&serde_json::json!({ "Do": "merge" })),
|
||||
"merge" => client.post(format!("{endpoint}/merge")).json(&merge_payload("gitea", merge_method)?),
|
||||
"approve" => client.post(format!("{endpoint}/reviews")).json(&serde_json::json!({ "event": "APPROVED", "body": "" })),
|
||||
"close" => client.patch(&endpoint).json(&serde_json::json!({ "state": "closed" })),
|
||||
"reopen" => client.patch(&endpoint).json(&serde_json::json!({ "state": "open" })),
|
||||
@@ -978,6 +984,7 @@ fn azure_review_action(
|
||||
repository_id: &str,
|
||||
number: u64,
|
||||
action: &str,
|
||||
merge_method: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
let endpoint = azure_review_endpoint(base_url, repository_name, repository_id, number)?;
|
||||
let auth_user = if username.trim().is_empty() { "gitty" } else { username };
|
||||
@@ -1007,7 +1014,12 @@ fn azure_review_action(
|
||||
let payload = current.json::<serde_json::Value>().map_err(|err| format!("Azure DevOps returned an unreadable pull request: {err}"))?;
|
||||
let commit_id = value_string(&payload, &["lastMergeSourceCommit", "commitId"]);
|
||||
if commit_id.is_empty() { return Err("Azure DevOps did not return the current source commit.".to_string()); }
|
||||
serde_json::json!({ "status": "completed", "lastMergeSourceCommit": { "commitId": commit_id } })
|
||||
{
|
||||
let mut body = merge_payload("azure-devops", merge_method)?;
|
||||
body["status"] = serde_json::json!("completed");
|
||||
body["lastMergeSourceCommit"] = serde_json::json!({ "commitId": commit_id });
|
||||
body
|
||||
}
|
||||
}
|
||||
_ => return Err("Unsupported review action.".to_string()),
|
||||
};
|
||||
@@ -1184,19 +1196,21 @@ pub async fn run_integration_review_action(
|
||||
repository_name: String,
|
||||
number: u64,
|
||||
action: String,
|
||||
merge_method: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
tokio::time::timeout(
|
||||
REVIEW_REQUEST_TIMEOUT,
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
if token.trim().is_empty() { return Err("No token is stored for this integration.".to_string()); }
|
||||
if !matches!(action.as_str(), "merge" | "approve" | "close" | "reopen") { return Err("Unsupported review action.".to_string()); }
|
||||
if action == "merge" { merge_payload(&provider, merge_method.as_deref())?; }
|
||||
let base_url = normalized_base_url(&base_url)?;
|
||||
let client = client()?;
|
||||
match provider.as_str() {
|
||||
"github" => github_review_action(&client, &base_url, &token, &repository_name, number, &action),
|
||||
"gitlab" | "gitlab-self-hosted" => gitlab_review_action(&client, &base_url, &token, &repository_id, number, &action),
|
||||
"gitea" => gitea_review_action(&client, &base_url, &token, &repository_name, number, &action),
|
||||
"azure-devops" => azure_review_action(&client, &base_url, &username, &token, &repository_name, &repository_id, number, &action),
|
||||
"github" => github_review_action(&client, &base_url, &token, &repository_name, number, &action, merge_method.as_deref()),
|
||||
"gitlab" | "gitlab-self-hosted" => gitlab_review_action(&client, &base_url, &token, &repository_id, number, &action, merge_method.as_deref()),
|
||||
"gitea" => gitea_review_action(&client, &base_url, &token, &repository_name, number, &action, merge_method.as_deref()),
|
||||
"azure-devops" => azure_review_action(&client, &base_url, &username, &token, &repository_name, &repository_id, number, &action, merge_method.as_deref()),
|
||||
_ => Err("Unsupported integration provider.".to_string()),
|
||||
}
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user