增强git贮藏(stash)功能,支持可视化操作
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["welcome", "folder-*", "commit-*", "merge-*", "settings"],
|
||||
"windows": ["welcome", "folder-*", "commit-*", "merge-*", "stash-*", "settings"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:default",
|
||||
|
||||
@@ -78,6 +78,15 @@ pub struct GitCommitResult {
|
||||
pub committed_files: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GitStashEntry {
|
||||
pub index: usize,
|
||||
pub message: String,
|
||||
pub branch: String,
|
||||
pub date: String,
|
||||
pub ref_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GitRemote {
|
||||
pub name: String,
|
||||
@@ -867,24 +876,47 @@ pub async fn git_list_branches(path: String) -> Result<Vec<String>, AppCommandEr
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_stash(path: String) -> Result<String, AppCommandError> {
|
||||
pub async fn git_stash_push(
|
||||
path: String,
|
||||
message: Option<String>,
|
||||
keep_index: bool,
|
||||
) -> Result<String, AppCommandError> {
|
||||
let mut args = vec!["stash".to_string(), "push".to_string()];
|
||||
if let Some(msg) = message {
|
||||
if !msg.is_empty() {
|
||||
args.push("-m".to_string());
|
||||
args.push(msg);
|
||||
}
|
||||
}
|
||||
if keep_index {
|
||||
args.push("--keep-index".to_string());
|
||||
}
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["stash"])
|
||||
.args(&args)
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("stash", &output.stderr));
|
||||
return Err(git_command_error("stash push", &output.stderr));
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_stash_pop(path: String) -> Result<String, AppCommandError> {
|
||||
pub async fn git_stash_pop(
|
||||
path: String,
|
||||
stash_ref: Option<String>,
|
||||
) -> Result<String, AppCommandError> {
|
||||
let mut args = vec!["stash", "pop"];
|
||||
let stash_ref_val;
|
||||
if let Some(ref r) = stash_ref {
|
||||
stash_ref_val = r.clone();
|
||||
args.push(&stash_ref_val);
|
||||
}
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["stash", "pop"])
|
||||
.args(&args)
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
@@ -896,6 +928,149 @@ pub async fn git_stash_pop(path: String) -> Result<String, AppCommandError> {
|
||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_stash_list(path: String) -> Result<Vec<GitStashEntry>, AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["stash", "list", "--format=%gd||%gs||%ci"])
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("stash list", &output.stderr));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let entries = stdout
|
||||
.lines()
|
||||
.filter(|l| !l.is_empty())
|
||||
.enumerate()
|
||||
.filter_map(|(i, line)| {
|
||||
let parts: Vec<&str> = line.splitn(3, "||").collect();
|
||||
if parts.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let ref_name = parts[0].to_string();
|
||||
let subject = parts[1];
|
||||
let date = parts[2].to_string();
|
||||
|
||||
// Parse branch and message from subject like "On branch: message" or "WIP on branch: hash"
|
||||
let (branch, message) = if let Some(rest) = subject.strip_prefix("On ") {
|
||||
if let Some(colon_pos) = rest.find(": ") {
|
||||
let branch = rest[..colon_pos].to_string();
|
||||
let msg = rest[colon_pos + 2..].to_string();
|
||||
(branch, msg)
|
||||
} else {
|
||||
(String::new(), subject.to_string())
|
||||
}
|
||||
} else if let Some(rest) = subject.strip_prefix("WIP on ") {
|
||||
if let Some(colon_pos) = rest.find(": ") {
|
||||
let branch = rest[..colon_pos].to_string();
|
||||
let msg = rest[colon_pos + 2..].to_string();
|
||||
(branch, msg)
|
||||
} else {
|
||||
(String::new(), subject.to_string())
|
||||
}
|
||||
} else {
|
||||
(String::new(), subject.to_string())
|
||||
};
|
||||
|
||||
Some(GitStashEntry {
|
||||
index: i,
|
||||
message,
|
||||
branch,
|
||||
date,
|
||||
ref_name,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_stash_apply(
|
||||
path: String,
|
||||
stash_ref: String,
|
||||
) -> Result<String, AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["stash", "apply", &stash_ref])
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("stash apply", &output.stderr));
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_stash_drop(
|
||||
path: String,
|
||||
stash_ref: String,
|
||||
) -> Result<String, AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["stash", "drop", &stash_ref])
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("stash drop", &output.stderr));
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_stash_clear(path: String) -> Result<String, AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["stash", "clear"])
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("stash clear", &output.stderr));
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_stash_show(
|
||||
path: String,
|
||||
stash_ref: String,
|
||||
) -> Result<Vec<GitStatusEntry>, AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["stash", "show", "--name-status", &stash_ref])
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("stash show", &output.stderr));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let entries = stdout
|
||||
.lines()
|
||||
.filter(|l| !l.is_empty())
|
||||
.filter_map(|line| {
|
||||
let mut parts = line.splitn(2, '\t');
|
||||
let status = parts.next()?.trim().to_string();
|
||||
let file = parts.next()?.trim().to_string();
|
||||
Some(GitStatusEntry { status, file })
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_status(path: String) -> Result<Vec<GitStatusEntry>, AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
|
||||
@@ -558,3 +558,42 @@ pub fn open_welcome_window(app: &AppHandle) -> Result<(), AppCommandError> {
|
||||
ensure_windows_undecorated(&welcome_window);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_stash_window(
|
||||
app: AppHandle,
|
||||
db: tauri::State<'_, AppDatabase>,
|
||||
folder_id: i32,
|
||||
) -> Result<(), AppCommandError> {
|
||||
let label = format!("stash-{folder_id}");
|
||||
|
||||
if let Some(existing) = app.get_webview_window(&label) {
|
||||
ensure_windows_undecorated(&existing);
|
||||
let _ = existing.unminimize();
|
||||
existing
|
||||
.set_focus()
|
||||
.map_err(|e| AppCommandError::window("Failed to focus stash window", e.to_string()))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let folder = crate::db::service::folder_service::get_folder_by_id(&db.conn, folder_id)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?
|
||||
.ok_or_else(|| {
|
||||
AppCommandError::not_found(format!("Folder {folder_id} not found"))
|
||||
.with_detail(format!("folder_id={folder_id}"))
|
||||
})?;
|
||||
|
||||
let url = WebviewUrl::App(format!("stash?folderId={folder_id}").into());
|
||||
let builder = WebviewWindowBuilder::new(&app, &label, url)
|
||||
.title(format!("Stash - {}", folder.name))
|
||||
.inner_size(1100.0, 700.0)
|
||||
.min_inner_size(800.0, 500.0)
|
||||
.center();
|
||||
let stash_window = apply_platform_window_style(builder)
|
||||
.build()
|
||||
.map_err(|e| AppCommandError::window("Failed to open stash window", e.to_string()))?;
|
||||
ensure_windows_undecorated(&stash_window);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -209,8 +209,13 @@ pub fn run() {
|
||||
folders::git_worktree_add,
|
||||
folders::git_checkout,
|
||||
folders::git_list_branches,
|
||||
folders::git_stash,
|
||||
folders::git_stash_push,
|
||||
folders::git_stash_pop,
|
||||
folders::git_stash_list,
|
||||
folders::git_stash_apply,
|
||||
folders::git_stash_drop,
|
||||
folders::git_stash_clear,
|
||||
folders::git_stash_show,
|
||||
folders::git_status,
|
||||
folders::git_is_tracked,
|
||||
folders::git_diff,
|
||||
@@ -254,6 +259,7 @@ pub fn run() {
|
||||
windows::list_open_folders,
|
||||
windows::focus_folder_window,
|
||||
windows::open_merge_window,
|
||||
windows::open_stash_window,
|
||||
system_settings::get_system_proxy_settings,
|
||||
system_settings::update_system_proxy_settings,
|
||||
system_settings::get_system_language_settings,
|
||||
|
||||
Reference in New Issue
Block a user