Branch menu UI and remote branch checkout
Add UI improvements and switching state for branch menu and implement remote-branch-aware checkout logic. Frontend: add CSS for disabled branch items, compact branch name layout, subtitles and messages; show switching indicator and disable branch actions while switching; display local/remote names and remote subtitles; surface branch switch errors and in-progress messages. Add state.branches.switchingTo and update event handlers to set/clear it. Backend (tauri): extend LocalRepoBranch with is_remote, remote_name, local_name; add git_ref_exists helper; enhance checkout_branch to detect local vs remote refs and automatically checkout or create tracking branches for remotes; parse refs to include remote branches (skip ones with existing local counterparts) and sort branches with current/local/remote order. Minor formatting tweaks.
This commit is contained in:
+101
-12
@@ -37,6 +37,9 @@ struct ServerConnectionResult {
|
||||
struct LocalRepoBranch {
|
||||
name: String,
|
||||
current: bool,
|
||||
is_remote: bool,
|
||||
remote_name: Option<String>,
|
||||
local_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -812,6 +815,21 @@ fn normalize_reference(reference: &str) -> Result<String, String> {
|
||||
Ok(trimmed.to_string())
|
||||
}
|
||||
|
||||
fn git_ref_exists(repo_path: &str, git_path: Option<String>, ref_name: &str) -> bool {
|
||||
run_git_command(
|
||||
Some(repo_path),
|
||||
git_path,
|
||||
vec![
|
||||
"show-ref".to_string(),
|
||||
"--verify".to_string(),
|
||||
"--quiet".to_string(),
|
||||
ref_name.to_string(),
|
||||
],
|
||||
)
|
||||
.map(|result| result.success)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn treeish(reference: &str, path: &str) -> String {
|
||||
if path.is_empty() {
|
||||
reference.to_string()
|
||||
@@ -1393,6 +1411,35 @@ fn checkout_branch(
|
||||
) -> Result<GitCommandResult, String> {
|
||||
let repo_path = validate_repo_dir(&repo_path)?;
|
||||
let branch = normalize_reference(&branch)?;
|
||||
let local_ref = format!("refs/heads/{branch}");
|
||||
if git_ref_exists(&repo_path, git_path.clone(), &local_ref) {
|
||||
return ensure_git_success(run_git_command(
|
||||
Some(&repo_path),
|
||||
git_path,
|
||||
vec!["checkout".to_string(), branch],
|
||||
)?);
|
||||
}
|
||||
|
||||
let remote_ref = format!("refs/remotes/{branch}");
|
||||
if git_ref_exists(&repo_path, git_path.clone(), &remote_ref) {
|
||||
if let Some((_, local_branch)) = branch.split_once('/') {
|
||||
let tracking_local_ref = format!("refs/heads/{local_branch}");
|
||||
if git_ref_exists(&repo_path, git_path.clone(), &tracking_local_ref) {
|
||||
return ensure_git_success(run_git_command(
|
||||
Some(&repo_path),
|
||||
git_path,
|
||||
vec!["checkout".to_string(), local_branch.to_string()],
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
return ensure_git_success(run_git_command(
|
||||
Some(&repo_path),
|
||||
git_path,
|
||||
vec!["checkout".to_string(), "--track".to_string(), branch],
|
||||
)?);
|
||||
}
|
||||
|
||||
ensure_git_success(run_git_command(
|
||||
Some(&repo_path),
|
||||
git_path,
|
||||
@@ -1806,20 +1853,59 @@ fn local_repo_branches(
|
||||
],
|
||||
)?;
|
||||
|
||||
let branches = String::from_utf8_lossy(&output)
|
||||
let ref_names = String::from_utf8_lossy(&output)
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty() && !line.ends_with("/HEAD"))
|
||||
.map(str::to_string)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let local_names = ref_names
|
||||
.iter()
|
||||
.filter_map(|ref_name| ref_name.strip_prefix("refs/heads/"))
|
||||
.map(str::to_string)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut branches = ref_names
|
||||
.iter()
|
||||
.filter_map(|ref_name| {
|
||||
ref_name
|
||||
.strip_prefix("refs/heads/")
|
||||
.or_else(|| ref_name.strip_prefix("refs/remotes/"))
|
||||
if let Some(name) = ref_name.strip_prefix("refs/heads/") {
|
||||
return Some(LocalRepoBranch {
|
||||
name: name.to_string(),
|
||||
current: current.as_deref() == Some(name),
|
||||
is_remote: false,
|
||||
remote_name: None,
|
||||
local_name: name.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let name = ref_name.strip_prefix("refs/remotes/")?;
|
||||
let (remote_name, local_name) = name.split_once('/')?;
|
||||
if local_names.contains(local_name) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(LocalRepoBranch {
|
||||
name: name.to_string(),
|
||||
current: false,
|
||||
is_remote: true,
|
||||
remote_name: Some(remote_name.to_string()),
|
||||
local_name: local_name.to_string(),
|
||||
})
|
||||
})
|
||||
.map(|name| LocalRepoBranch {
|
||||
name: name.to_string(),
|
||||
current: current.as_deref() == Some(name),
|
||||
})
|
||||
.collect();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
branches.sort_by(|a, b| {
|
||||
b.current
|
||||
.cmp(&a.current)
|
||||
.then_with(|| a.is_remote.cmp(&b.is_remote))
|
||||
.then_with(|| {
|
||||
a.local_name
|
||||
.to_lowercase()
|
||||
.cmp(&b.local_name.to_lowercase())
|
||||
})
|
||||
.then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))
|
||||
});
|
||||
|
||||
Ok(branches)
|
||||
}
|
||||
@@ -1920,7 +2006,11 @@ fn local_repo_file(
|
||||
let raw = run_git_output(
|
||||
repo_path.trim(),
|
||||
git_path,
|
||||
vec!["show".to_string(), "--no-ext-diff".to_string(), spec.clone()],
|
||||
vec![
|
||||
"show".to_string(),
|
||||
"--no-ext-diff".to_string(),
|
||||
spec.clone(),
|
||||
],
|
||||
)?;
|
||||
let (preview_mime, preview_base64) = image_preview_pair(&path, &raw);
|
||||
return Ok(LocalRepoFile {
|
||||
@@ -2033,8 +2123,7 @@ pub fn run() {
|
||||
.setup(|app| {
|
||||
use tauri::Manager;
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window
|
||||
.set_background_color(Some(tauri::utils::config::Color(0, 0, 0, 0)));
|
||||
let _ = window.set_background_color(Some(tauri::utils::config::Color(0, 0, 0, 0)));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user