feat(create-review): load repository branches and suggest local branch
Add a backend command to enumerate a repository's branches and default branch for configured integrations, and expose it to the UI. The create review dialog now fetches branches, shows loading and error states, and uses searchable SelectMenu controls for repositories and branches. If a local repository path is available the dialog will try to match remotes and preselect a local branch that exists on the remote to streamline review creation. - Add integration branch listing command and wire it into the dialog - Replace plain selects with searchable SelectMenu and improved UX - Attempt to detect and suggest a matching local source branch when possible
This commit is contained in:
@@ -6,7 +6,7 @@ use std::collections::BTreeSet;
|
||||
use std::time::Duration;
|
||||
|
||||
mod creation;
|
||||
pub use creation::create_integration_review_request;
|
||||
pub use creation::{create_integration_review_request, list_integration_repository_branches};
|
||||
|
||||
const PAGE_SIZE: usize = 100;
|
||||
const REVIEW_REQUEST_TIMEOUT: Duration = Duration::from_secs(35);
|
||||
|
||||
@@ -103,3 +103,66 @@ mod tests {
|
||||
assert_eq!(creation_endpoint("azure-devops", "https://dev.azure.com/org", &repository).unwrap().as_str(), "https://dev.azure.com/org/_apis/git/repositories/17/pullrequests?api-version=7.1");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RepositoryBranches {
|
||||
branches: Vec<String>,
|
||||
default_branch: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_integration_repository_branches(provider: String, base_url: String, username: String, token: String, repository: IntegrationRepository) -> Result<RepositoryBranches, String> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
if token.trim().is_empty() { return Err("No token is stored for this integration.".into()); }
|
||||
let client = client()?;
|
||||
let mut metadata_url = creation_endpoint(&provider, &base_url, &repository)?;
|
||||
metadata_url.path_segments_mut().map_err(|_| "Invalid repository URL")?.pop();
|
||||
let get = |url: reqwest::Url| -> Result<Response, String> {
|
||||
let request = client.get(url).header(USER_AGENT, "Gitty").header(ACCEPT, "application/json");
|
||||
let request = match provider.as_str() {
|
||||
"github" => request.bearer_auth(&token),
|
||||
"gitea" => request.header("Authorization", format!("token {token}")),
|
||||
"gitlab" | "gitlab-self-hosted" => request.header("PRIVATE-TOKEN", &token),
|
||||
"azure-devops" => request.basic_auth(&username, Some(&token)),
|
||||
_ => return Err("Unsupported integration provider.".into()),
|
||||
};
|
||||
let response = request.send().map_err(|e| format!("Could not load branches: {e}"))?;
|
||||
if !response.status().is_success() { return Err(response_error(response, &provider)); }
|
||||
Ok(response)
|
||||
};
|
||||
let metadata: serde_json::Value = get(metadata_url.clone())?.json().map_err(|e| format!("Invalid repository response: {e}"))?;
|
||||
let default_branch = value_string(&metadata, &[if provider == "azure-devops" { "defaultBranch" } else { "default_branch" }]).trim_start_matches("refs/heads/").to_string();
|
||||
let mut branches = BTreeSet::new();
|
||||
let mut continuation = String::new();
|
||||
for page in 1..=1000 {
|
||||
let mut url = metadata_url.clone();
|
||||
{
|
||||
let mut path = url.path_segments_mut().map_err(|_| "Invalid repository URL")?;
|
||||
if provider.starts_with("gitlab") { path.push("repository"); }
|
||||
path.push(if provider == "azure-devops" { "refs" } else { "branches" });
|
||||
}
|
||||
if provider == "azure-devops" {
|
||||
url.query_pairs_mut().append_pair("filter", "heads/").append_pair("$top", "100");
|
||||
if !continuation.is_empty() { url.query_pairs_mut().append_pair("continuationToken", &continuation); }
|
||||
} else {
|
||||
url.query_pairs_mut().append_pair("page", &page.to_string()).append_pair(if provider == "gitea" { "limit" } else { "per_page" }, "100");
|
||||
}
|
||||
let response = get(url)?;
|
||||
let next = response.headers().get("x-ms-continuationtoken").and_then(|h| h.to_str().ok()).unwrap_or_default().to_string();
|
||||
let data: serde_json::Value = response.json().map_err(|e| format!("Invalid branch response: {e}"))?;
|
||||
let items = if provider == "azure-devops" { data.get("value") } else { Some(&data) }.and_then(serde_json::Value::as_array).ok_or("Invalid branch list.")?;
|
||||
for item in items {
|
||||
let name = value_string(item, &["name"]);
|
||||
let name = if provider == "azure-devops" { name.strip_prefix("refs/heads/").unwrap_or(&name) } else { &name };
|
||||
if !name.is_empty() { branches.insert(name.to_string()); }
|
||||
}
|
||||
if (provider == "azure-devops" && next.is_empty()) || (provider != "azure-devops" && items.len() < 100) {
|
||||
return Ok(RepositoryBranches { branches: branches.into_iter().collect(), default_branch });
|
||||
}
|
||||
if provider == "azure-devops" && next == continuation { return Err("The server repeated its branch pagination token.".into()); }
|
||||
continuation = next;
|
||||
}
|
||||
Err("The repository has too many branches to load completely.".into())
|
||||
}).await.map_err(|e| format!("Could not load branches: {e}"))?
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ use git::{
|
||||
unstage_files, untrack_paths, update_remote,
|
||||
};
|
||||
use integrations::{
|
||||
create_integration_review_request, add_integration_review_comment, get_integration_review_details, list_integration_repositories, list_integration_review_requests, open_in_browser,
|
||||
list_integration_repository_branches, create_integration_review_request, add_integration_review_comment, get_integration_review_details, list_integration_repositories, list_integration_review_requests, open_in_browser,
|
||||
run_integration_review_action,
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -437,6 +437,7 @@ async fn main() {
|
||||
cred_load,
|
||||
cred_save,
|
||||
cred_delete,
|
||||
list_integration_repository_branches,
|
||||
create_integration_review_request,
|
||||
list_integration_repositories,
|
||||
list_integration_review_requests,
|
||||
|
||||
Reference in New Issue
Block a user