Add Git worktree pane

This commit is contained in:
Georg Bauer
2026-07-27 19:44:12 +02:00
parent aebfe7aeb6
commit b9933ae076
9 changed files with 2630 additions and 5 deletions

View File

@@ -1,4 +1,5 @@
mod generation;
mod git;
mod model_manager;
mod preferences;
mod projects;
@@ -7,6 +8,7 @@ mod view;
pub(crate) use view::app_theme;
use generation::ChatMessage;
use git::{ActiveGitOperation, GitDiff, GitDiffMode, GitWorktree};
use model_manager::{ActiveDownload, ModelDownload, ModelOperation};
use preferences::PreferenceDraft;
#[cfg(test)]
@@ -41,6 +43,7 @@ const APP_ID: &str = "de.rfc1437.ds4server";
const METRICS_SAMPLE_INTERVAL: Duration = Duration::from_millis(200);
/// Disc scans are cheap but not free; the explorer does not need 5 Hz.
const CACHE_SCAN_INTERVAL: Duration = Duration::from_secs(2);
const GIT_SCAN_INTERVAL: Duration = Duration::from_secs(2);
pub(super) const MIN_SIDEBAR_WIDTH: i32 = 180;
pub(super) const MAX_SIDEBAR_WIDTH: i32 = 520;
@@ -67,6 +70,15 @@ pub(crate) struct App {
/// when its first chat turn is stored, so empty ones vanish on restart.
drafts: HashMap<i32, String>,
git_states: HashMap<i32, GitState>,
git_worktrees: HashMap<i32, GitWorktree>,
git_selected_project: Option<i32>,
git_selected_files: HashSet<PathBuf>,
git_commit_message: String,
git_diff: Option<GitDiff>,
git_diff_mode: GitDiffMode,
git_commit_all_confirmation: bool,
git_operation: Option<ActiveGitOperation>,
last_git_scan: Instant,
#[cfg(target_os = "macos")]
background_chats: HashMap<i32, ChatSnapshot>,
/// Session whose quick-actions menu is open.
@@ -227,6 +239,7 @@ pub(super) enum DetailTab {
#[default]
Chat,
A2ui,
Git,
Stats,
}
@@ -401,6 +414,18 @@ pub(crate) enum Message {
CreateSession(i32),
DraftProjectChanged(i32),
SwitchGitBranch(String),
ToggleGitFile(PathBuf),
OpenGitDiff(PathBuf),
SetGitDiffMode(GitDiffMode),
GitCommitMessageChanged(String),
GitStageSelected,
GitUnstageSelected,
GitCommit,
ConfirmGitCommitAll,
GitFetch,
GitPull,
GitPush,
GitOperationTick,
DiscardSession(i32),
SelectSession(i32, i32),
RequestDeleteSession(i32),
@@ -416,6 +441,7 @@ pub(crate) enum Message {
ToggleArchivedSessions(i32),
ShowChat,
ShowA2ui,
ShowGit,
ShowStats,
ToggleSidebar,
StartSidebarDrag,
@@ -479,6 +505,15 @@ impl App {
selected_session: None,
drafts,
git_states: HashMap::new(),
git_worktrees: HashMap::new(),
git_selected_project: None,
git_selected_files: HashSet::new(),
git_commit_message: String::new(),
git_diff: None,
git_diff_mode: GitDiffMode::default(),
git_commit_all_confirmation: false,
git_operation: None,
last_git_scan: Instant::now() - GIT_SCAN_INTERVAL,
#[cfg(target_os = "macos")]
background_chats: HashMap::new(),
session_menu: None,
@@ -612,6 +647,15 @@ impl App {
selected_session: None,
drafts: HashMap::new(),
git_states: HashMap::new(),
git_worktrees: HashMap::new(),
git_selected_project: None,
git_selected_files: HashSet::new(),
git_commit_message: String::new(),
git_diff: None,
git_diff_mode: GitDiffMode::default(),
git_commit_all_confirmation: false,
git_operation: None,
last_git_scan: Instant::now() - GIT_SCAN_INTERVAL,
#[cfg(target_os = "macos")]
background_chats: HashMap::new(),
session_menu: None,
@@ -987,6 +1031,10 @@ impl App {
Message::DismissPanel => {
if self.quit_confirmation {
self.quit_confirmation = false;
} else if self.git_diff.is_some() {
self.git_diff = None;
} else if self.git_commit_all_confirmation {
self.git_commit_all_confirmation = false;
} else if self.pending_session_delete.is_some() {
self.pending_session_delete = None;
} else if self.pending_a2ui_dismissal.is_some() {
@@ -1012,6 +1060,13 @@ impl App {
return scroll_chat_to_end();
}
Message::ShowA2ui => self.detail_tab = DetailTab::A2ui,
Message::ShowGit => {
self.detail_tab = DetailTab::Git;
if let Some(project_id) = self.selected_project {
self.refresh_git_state();
self.refresh_git_worktree(project_id);
}
}
Message::ShowStats => {
self.detail_tab = DetailTab::Stats;
self.scan_kv_cache();
@@ -1625,6 +1680,11 @@ impl App {
}
self.drafts.remove(&project_id);
self.git_states.remove(&project_id);
self.git_worktrees.remove(&project_id);
if self.git_selected_project == Some(project_id) {
self.git_selected_project = None;
self.git_selected_files.clear();
}
if self.config.interface.last_project_id == Some(project_id) {
self.config.interface.last_project_id = None;
let _ = self.config.save(&config_path());
@@ -1656,6 +1716,18 @@ impl App {
}
Message::DraftProjectChanged(project_id) => self.move_draft_to_project(project_id),
Message::SwitchGitBranch(branch) => self.switch_git_branch(&branch),
Message::ToggleGitFile(path) => self.toggle_git_file(path),
Message::OpenGitDiff(path) => self.open_git_diff(&path),
Message::SetGitDiffMode(mode) => self.git_diff_mode = mode,
Message::GitCommitMessageChanged(message) => self.git_commit_message = message,
Message::GitStageSelected => self.stage_selected_git_files(),
Message::GitUnstageSelected => self.unstage_selected_git_files(),
Message::GitCommit => self.commit_git(),
Message::ConfirmGitCommitAll => self.confirm_commit_all_git(),
Message::GitFetch => self.start_git_remote("fetch"),
Message::GitPull => self.start_git_remote("pull"),
Message::GitPush => self.start_git_remote("push"),
Message::GitOperationTick => self.poll_git_operation(),
Message::DiscardSession(project_id) => self.discard_session(project_id),
Message::OpenSessionMenu(session_id) => {
self.session_rename = None;
@@ -1961,6 +2033,11 @@ impl App {
iced::time::every(Duration::from_secs(1)).map(|_| Message::DownloadProgressTick),
);
}
if self.git_operation.is_some() {
subscriptions.push(
iced::time::every(Duration::from_millis(100)).map(|_| Message::GitOperationTick),
);
}
#[cfg(target_os = "macos")]
let titling = self.active_titling.is_some();
#[cfg(not(target_os = "macos"))]
@@ -2054,6 +2131,14 @@ impl App {
{
self.scan_kv_cache();
}
if self.detail_tab == DetailTab::Git
&& self.git_operation.is_none()
&& self.last_git_scan.elapsed() >= GIT_SCAN_INTERVAL
&& let Some(project_id) = self.selected_project
{
self.refresh_git_state();
self.refresh_git_worktree(project_id);
}
}
/// Removes one cache file. Checkpoints are disposable: the next turn on that