优化代码冲突解决
This commit is contained in:
@@ -38,6 +38,7 @@ pub struct GitConflictInfo {
|
|||||||
pub has_conflicts: bool,
|
pub has_conflicts: bool,
|
||||||
pub conflicted_files: Vec<String>,
|
pub conflicted_files: Vec<String>,
|
||||||
pub operation: String,
|
pub operation: String,
|
||||||
|
pub upstream_commit: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@@ -553,6 +554,9 @@ pub async fn git_pull(path: String) -> Result<GitPullResult, AppCommandError> {
|
|||||||
conflict: None,
|
conflict: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let upstream_commit = String::from_utf8_lossy(&upstream_check.stdout)
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
// Step 3: check if we can fast-forward
|
// Step 3: check if we can fast-forward
|
||||||
let merge_base = crate::process::tokio_command("git")
|
let merge_base = crate::process::tokio_command("git")
|
||||||
@@ -609,6 +613,7 @@ pub async fn git_pull(path: String) -> Result<GitPullResult, AppCommandError> {
|
|||||||
has_conflicts: true,
|
has_conflicts: true,
|
||||||
conflicted_files,
|
conflicted_files,
|
||||||
operation: "pull".to_string(),
|
operation: "pull".to_string(),
|
||||||
|
upstream_commit: Some(upstream_commit),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -649,10 +654,15 @@ pub async fn git_pull(path: String) -> Result<GitPullResult, AppCommandError> {
|
|||||||
|
|
||||||
/// Start a merge with the upstream branch (used by merge workspace after pull conflict detection).
|
/// Start a merge with the upstream branch (used by merge workspace after pull conflict detection).
|
||||||
/// This recreates the conflict state so that :1:, :2:, :3: stage entries are available.
|
/// This recreates the conflict state so that :1:, :2:, :3: stage entries are available.
|
||||||
|
/// If `upstream_commit` is provided, merge against that specific commit instead of `@{u}`.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn git_start_pull_merge(path: String) -> Result<(), AppCommandError> {
|
pub async fn git_start_pull_merge(
|
||||||
|
path: String,
|
||||||
|
upstream_commit: Option<String>,
|
||||||
|
) -> Result<(), AppCommandError> {
|
||||||
|
let target = upstream_commit.as_deref().unwrap_or("@{u}");
|
||||||
let output = crate::process::tokio_command("git")
|
let output = crate::process::tokio_command("git")
|
||||||
.args(["merge", "--no-commit", "@{u}"])
|
.args(["merge", "--no-commit", target])
|
||||||
.current_dir(&path)
|
.current_dir(&path)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
@@ -671,6 +681,17 @@ pub async fn git_start_pull_merge(path: String) -> Result<(), AppCommandError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn git_has_merge_head(path: String) -> Result<bool, AppCommandError> {
|
||||||
|
let output = crate::process::tokio_command("git")
|
||||||
|
.args(["rev-parse", "--verify", "MERGE_HEAD"])
|
||||||
|
.current_dir(&path)
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.map_err(AppCommandError::io)?;
|
||||||
|
Ok(output.status.success())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn git_fetch(path: String) -> Result<String, AppCommandError> {
|
pub async fn git_fetch(path: String) -> Result<String, AppCommandError> {
|
||||||
let output = crate::process::tokio_command("git")
|
let output = crate::process::tokio_command("git")
|
||||||
@@ -1413,6 +1434,7 @@ pub async fn git_merge(
|
|||||||
has_conflicts: true,
|
has_conflicts: true,
|
||||||
conflicted_files,
|
conflicted_files,
|
||||||
operation: "merge".to_string(),
|
operation: "merge".to_string(),
|
||||||
|
upstream_commit: None,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1445,6 +1467,7 @@ pub async fn git_rebase(
|
|||||||
has_conflicts: true,
|
has_conflicts: true,
|
||||||
conflicted_files,
|
conflicted_files,
|
||||||
operation: "rebase".to_string(),
|
operation: "rebase".to_string(),
|
||||||
|
upstream_commit: None,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use tauri::{AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};
|
use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindowBuilder};
|
||||||
|
|
||||||
use crate::app_error::AppCommandError;
|
use crate::app_error::AppCommandError;
|
||||||
use crate::db::AppDatabase;
|
use crate::db::AppDatabase;
|
||||||
@@ -424,6 +424,7 @@ pub async fn open_merge_window(
|
|||||||
state: tauri::State<'_, MergeWindowState>,
|
state: tauri::State<'_, MergeWindowState>,
|
||||||
folder_id: i32,
|
folder_id: i32,
|
||||||
operation: String,
|
operation: String,
|
||||||
|
upstream_commit: Option<String>,
|
||||||
) -> Result<(), AppCommandError> {
|
) -> Result<(), AppCommandError> {
|
||||||
let owner_label = window.label().to_string();
|
let owner_label = window.label().to_string();
|
||||||
let label = format!("merge-{folder_id}");
|
let label = format!("merge-{folder_id}");
|
||||||
@@ -450,9 +451,11 @@ pub async fn open_merge_window(
|
|||||||
.with_detail(format!("folder_id={folder_id}"))
|
.with_detail(format!("folder_id={folder_id}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let url = WebviewUrl::App(
|
let mut url_str = format!("merge?folderId={folder_id}&operation={operation}");
|
||||||
format!("merge?folderId={folder_id}&operation={operation}").into(),
|
if let Some(ref commit) = upstream_commit {
|
||||||
);
|
url_str.push_str(&format!("&upstreamCommit={commit}"));
|
||||||
|
}
|
||||||
|
let url = WebviewUrl::App(url_str.into());
|
||||||
let builder = WebviewWindowBuilder::new(&app, &label, url)
|
let builder = WebviewWindowBuilder::new(&app, &label, url)
|
||||||
.title(format!("解决冲突 - {}", folder.name))
|
.title(format!("解决冲突 - {}", folder.name))
|
||||||
.inner_size(1400.0, 900.0)
|
.inner_size(1400.0, 900.0)
|
||||||
@@ -493,6 +496,51 @@ pub fn restore_window_after_merge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clean up dangling merge state when a merge window is closed without
|
||||||
|
/// completing or aborting. Checks if MERGE_HEAD exists, aborts the merge,
|
||||||
|
/// and notifies the parent window.
|
||||||
|
pub async fn cleanup_dangling_merge(app: &AppHandle, merge_window_label: &str) {
|
||||||
|
let folder_id: i32 = match merge_window_label
|
||||||
|
.strip_prefix("merge-")
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
{
|
||||||
|
Some(id) => id,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = match app.try_state::<AppDatabase>() {
|
||||||
|
Some(db) => db,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let folder =
|
||||||
|
match crate::db::service::folder_service::get_folder_by_id(&db.conn, folder_id).await {
|
||||||
|
Ok(Some(f)) => f,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if MERGE_HEAD exists
|
||||||
|
let check = crate::process::tokio_command("git")
|
||||||
|
.args(["rev-parse", "--verify", "MERGE_HEAD"])
|
||||||
|
.current_dir(&folder.path)
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
let has_merge_head = check.map(|o| o.status.success()).unwrap_or(false);
|
||||||
|
|
||||||
|
if has_merge_head {
|
||||||
|
let _ = crate::process::tokio_command("git")
|
||||||
|
.args(["merge", "--abort"])
|
||||||
|
.current_dir(&folder.path)
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = app.emit(
|
||||||
|
"folder://merge-aborted",
|
||||||
|
serde_json::json!({ "folder_id": folder_id }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_welcome_window(app: &AppHandle) -> Result<(), AppCommandError> {
|
pub fn open_welcome_window(app: &AppHandle) -> Result<(), AppCommandError> {
|
||||||
if let Some(existing) = app.get_webview_window("welcome") {
|
if let Some(existing) = app.get_webview_window("welcome") {
|
||||||
ensure_windows_undecorated(&existing);
|
ensure_windows_undecorated(&existing);
|
||||||
|
|||||||
@@ -124,6 +124,13 @@ pub fn run() {
|
|||||||
if let Some(state) = app.try_state::<windows::MergeWindowState>() {
|
if let Some(state) = app.try_state::<windows::MergeWindowState>() {
|
||||||
windows::restore_window_after_merge(app, &state, &label);
|
windows::restore_window_after_merge(app, &state, &label);
|
||||||
}
|
}
|
||||||
|
// Clean up dangling merge state (MERGE_HEAD) if window closed
|
||||||
|
// without completing or aborting the merge
|
||||||
|
let app_clone = window.app_handle().clone();
|
||||||
|
let label_clone = label.clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
windows::cleanup_dangling_merge(&app_clone, &label_clone).await;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
||||||
@@ -195,6 +202,7 @@ pub fn run() {
|
|||||||
folders::git_init,
|
folders::git_init,
|
||||||
folders::git_pull,
|
folders::git_pull,
|
||||||
folders::git_start_pull_merge,
|
folders::git_start_pull_merge,
|
||||||
|
folders::git_has_merge_head,
|
||||||
folders::git_fetch,
|
folders::git_fetch,
|
||||||
folders::git_push,
|
folders::git_push,
|
||||||
folders::git_new_branch,
|
folders::git_new_branch,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ function MergePageInner() {
|
|||||||
|
|
||||||
const folderId = Number(searchParams.get("folderId") ?? "0")
|
const folderId = Number(searchParams.get("folderId") ?? "0")
|
||||||
const operation = searchParams.get("operation") ?? "merge"
|
const operation = searchParams.get("operation") ?? "merge"
|
||||||
|
const upstreamCommit = searchParams.get("upstreamCommit") ?? undefined
|
||||||
const normalizedFolderId = Number.isFinite(folderId) ? folderId : 0
|
const normalizedFolderId = Number.isFinite(folderId) ? folderId : 0
|
||||||
const hasValidFolderId = normalizedFolderId > 0
|
const hasValidFolderId = normalizedFolderId > 0
|
||||||
const loading = hasValidFolderId && state.loadedId !== normalizedFolderId
|
const loading = hasValidFolderId && state.loadedId !== normalizedFolderId
|
||||||
@@ -104,6 +105,7 @@ function MergePageInner() {
|
|||||||
folderId={normalizedFolderId}
|
folderId={normalizedFolderId}
|
||||||
folderPath={folder.path}
|
folderPath={folder.path}
|
||||||
operation={operation}
|
operation={operation}
|
||||||
|
upstreamCommit={upstreamCommit}
|
||||||
onCompleted={closeWindow}
|
onCompleted={closeWindow}
|
||||||
onAborted={closeWindow}
|
onAborted={closeWindow}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ import {
|
|||||||
openCommitWindow,
|
openCommitWindow,
|
||||||
setFolderParentBranch,
|
setFolderParentBranch,
|
||||||
gitListConflicts,
|
gitListConflicts,
|
||||||
|
gitHasMergeHead,
|
||||||
} from "@/lib/tauri"
|
} from "@/lib/tauri"
|
||||||
import { RemoteManageDialog } from "@/components/layout/remote-manage-dialog"
|
import { RemoteManageDialog } from "@/components/layout/remote-manage-dialog"
|
||||||
import { ConflictDialog } from "@/components/layout/conflict-dialog"
|
import { ConflictDialog } from "@/components/layout/conflict-dialog"
|
||||||
@@ -292,6 +293,9 @@ export function BranchDropdown({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uses operation "merge" intentionally: MERGE_HEAD exists so merge state is
|
||||||
|
// already active. MergeWorkspace won't call gitStartPullMerge (only for "pull"),
|
||||||
|
// and ConflictDialog abort correctly runs git merge --abort.
|
||||||
async function showMergeConflictDialog() {
|
async function showMergeConflictDialog() {
|
||||||
try {
|
try {
|
||||||
const remaining = await gitListConflicts(folderPath)
|
const remaining = await gitListConflicts(folderPath)
|
||||||
@@ -310,6 +314,16 @@ export function BranchDropdown({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handlePush() {
|
async function handlePush() {
|
||||||
|
// Pre-check: if MERGE_HEAD exists, show conflict dialog immediately
|
||||||
|
try {
|
||||||
|
if (await gitHasMergeHead(folderPath)) {
|
||||||
|
await showMergeConflictDialog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Pre-check failed, continue with normal push flow
|
||||||
|
}
|
||||||
|
|
||||||
const taskId = `git-${++taskSeq.current}-${Date.now()}`
|
const taskId = `git-${++taskSeq.current}-${Date.now()}`
|
||||||
const label = t("tasks.pushCode")
|
const label = t("tasks.pushCode")
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export function ConflictDialog({
|
|||||||
const [resolvedFiles, setResolvedFiles] = useState<Set<string>>(new Set())
|
const [resolvedFiles, setResolvedFiles] = useState<Set<string>>(new Set())
|
||||||
const [aborting, setAborting] = useState(false)
|
const [aborting, setAborting] = useState(false)
|
||||||
const [completing, setCompleting] = useState(false)
|
const [completing, setCompleting] = useState(false)
|
||||||
|
const [done, setDone] = useState(false)
|
||||||
|
|
||||||
const open = conflictInfo !== null
|
const open = conflictInfo !== null
|
||||||
const operation = conflictInfo?.operation ?? "merge"
|
const operation = conflictInfo?.operation ?? "merge"
|
||||||
@@ -53,6 +54,7 @@ export function ConflictDialog({
|
|||||||
if (conflictInfo) {
|
if (conflictInfo) {
|
||||||
setConflictedFiles(conflictInfo.conflicted_files)
|
setConflictedFiles(conflictInfo.conflicted_files)
|
||||||
setResolvedFiles(new Set())
|
setResolvedFiles(new Set())
|
||||||
|
setDone(false)
|
||||||
}
|
}
|
||||||
}, [conflictInfo])
|
}, [conflictInfo])
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ export function ConflictDialog({
|
|||||||
|
|
||||||
let unlistenResolved: UnlistenFn | null = null
|
let unlistenResolved: UnlistenFn | null = null
|
||||||
let unlistenCompleted: UnlistenFn | null = null
|
let unlistenCompleted: UnlistenFn | null = null
|
||||||
|
let unlistenAborted: UnlistenFn | null = null
|
||||||
|
|
||||||
listen<{ folder_id: number; file: string }>(
|
listen<{ folder_id: number; file: string }>(
|
||||||
"folder://merge-conflict-resolved",
|
"folder://merge-conflict-resolved",
|
||||||
@@ -91,6 +94,7 @@ export function ConflictDialog({
|
|||||||
|
|
||||||
listen<{ folder_id: number }>("folder://merge-completed", (event) => {
|
listen<{ folder_id: number }>("folder://merge-completed", (event) => {
|
||||||
if (event.payload.folder_id !== folderId) return
|
if (event.payload.folder_id !== folderId) return
|
||||||
|
setDone(true)
|
||||||
onResolved()
|
onResolved()
|
||||||
onClose()
|
onClose()
|
||||||
})
|
})
|
||||||
@@ -99,12 +103,26 @@ export function ConflictDialog({
|
|||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
|
|
||||||
|
// Merge was aborted (user clicked abort in merge window, or window closed)
|
||||||
|
// Reset resolved state since abort reverts all changes
|
||||||
|
listen<{ folder_id: number }>("folder://merge-aborted", (event) => {
|
||||||
|
if (event.payload.folder_id !== folderId) return
|
||||||
|
setDone(true)
|
||||||
|
setResolvedFiles(new Set())
|
||||||
|
onClose()
|
||||||
|
})
|
||||||
|
.then((fn) => {
|
||||||
|
unlistenAborted = fn
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposeTauriListener(
|
disposeTauriListener(
|
||||||
unlistenResolved,
|
unlistenResolved,
|
||||||
"ConflictDialog.mergeConflictResolved"
|
"ConflictDialog.mergeConflictResolved"
|
||||||
)
|
)
|
||||||
disposeTauriListener(unlistenCompleted, "ConflictDialog.mergeCompleted")
|
disposeTauriListener(unlistenCompleted, "ConflictDialog.mergeCompleted")
|
||||||
|
disposeTauriListener(unlistenAborted, "ConflictDialog.mergeAborted")
|
||||||
}
|
}
|
||||||
}, [open, folderId, onResolved, onClose])
|
}, [open, folderId, onResolved, onClose])
|
||||||
|
|
||||||
@@ -122,7 +140,7 @@ export function ConflictDialog({
|
|||||||
|
|
||||||
async function handleOpenMergeTool() {
|
async function handleOpenMergeTool() {
|
||||||
try {
|
try {
|
||||||
await openMergeWindow(folderId, operation)
|
await openMergeWindow(folderId, operation, conflictInfo?.upstream_commit)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(String(err))
|
toast.error(String(err))
|
||||||
}
|
}
|
||||||
@@ -149,6 +167,7 @@ export function ConflictDialog({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleComplete() {
|
async function handleComplete() {
|
||||||
|
if (done) return
|
||||||
setCompleting(true)
|
setCompleting(true)
|
||||||
try {
|
try {
|
||||||
await gitContinueOperation(folderPath, operation)
|
await gitContinueOperation(folderPath, operation)
|
||||||
@@ -227,7 +246,7 @@ export function ConflictDialog({
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleComplete}
|
onClick={handleComplete}
|
||||||
disabled={completing || aborting}
|
disabled={completing || aborting || done}
|
||||||
>
|
>
|
||||||
{completing && (
|
{completing && (
|
||||||
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
|
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ interface MergeWorkspaceProps {
|
|||||||
folderId: number
|
folderId: number
|
||||||
folderPath: string
|
folderPath: string
|
||||||
operation: string
|
operation: string
|
||||||
|
upstreamCommit?: string
|
||||||
onCompleted: () => void
|
onCompleted: () => void
|
||||||
onAborted: () => void
|
onAborted: () => void
|
||||||
}
|
}
|
||||||
@@ -37,6 +38,7 @@ export function MergeWorkspace({
|
|||||||
folderId,
|
folderId,
|
||||||
folderPath,
|
folderPath,
|
||||||
operation,
|
operation,
|
||||||
|
upstreamCommit,
|
||||||
onCompleted,
|
onCompleted,
|
||||||
onAborted,
|
onAborted,
|
||||||
}: MergeWorkspaceProps) {
|
}: MergeWorkspaceProps) {
|
||||||
@@ -51,6 +53,7 @@ export function MergeWorkspace({
|
|||||||
const [completing, setCompleting] = useState(false)
|
const [completing, setCompleting] = useState(false)
|
||||||
const currentContentRef = useRef<string>("")
|
const currentContentRef = useRef<string>("")
|
||||||
const [hasUnresolvedConflicts, setHasUnresolvedConflicts] = useState(true)
|
const [hasUnresolvedConflicts, setHasUnresolvedConflicts] = useState(true)
|
||||||
|
const [preparing, setPreparing] = useState(false)
|
||||||
|
|
||||||
// Load conflict files on mount
|
// Load conflict files on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -62,7 +65,12 @@ export function MergeWorkspace({
|
|||||||
// For pull operations, the merge was aborted during detection to keep
|
// For pull operations, the merge was aborted during detection to keep
|
||||||
// working tree clean. Re-start the merge to create conflict state.
|
// working tree clean. Re-start the merge to create conflict state.
|
||||||
if (operation === "pull") {
|
if (operation === "pull") {
|
||||||
await gitStartPullMerge(folderPath)
|
setPreparing(true)
|
||||||
|
try {
|
||||||
|
await gitStartPullMerge(folderPath, upstreamCommit)
|
||||||
|
} finally {
|
||||||
|
setPreparing(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const conflictFiles = await gitListConflicts(folderPath)
|
const conflictFiles = await gitListConflicts(folderPath)
|
||||||
setFiles(conflictFiles)
|
setFiles(conflictFiles)
|
||||||
@@ -137,7 +145,7 @@ export function MergeWorkspace({
|
|||||||
try {
|
try {
|
||||||
await gitAbortOperation(folderPath, operation)
|
await gitAbortOperation(folderPath, operation)
|
||||||
toast.success(t("abortSuccess"))
|
toast.success(t("abortSuccess"))
|
||||||
await emit("folder://merge-completed", { folder_id: folderId })
|
await emit("folder://merge-aborted", { folder_id: folderId })
|
||||||
onAborted()
|
onAborted()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(toErrorMessage(err))
|
toast.error(toErrorMessage(err))
|
||||||
@@ -221,7 +229,12 @@ export function MergeWorkspace({
|
|||||||
|
|
||||||
{/* Main area: three-pane merge editor */}
|
{/* Main area: three-pane merge editor */}
|
||||||
<ResizablePanel defaultSize={82}>
|
<ResizablePanel defaultSize={82}>
|
||||||
{loadingVersions ? (
|
{preparing ? (
|
||||||
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
{t("preparingMerge")}
|
||||||
|
</div>
|
||||||
|
) : loadingVersions ? (
|
||||||
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
{t("loadingFile")}
|
{t("loadingFile")}
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "تم حل جميع التعارضات",
|
"allResolved": "تم حل جميع التعارضات",
|
||||||
"conflictFiles": "ملفات متعارضة",
|
"conflictFiles": "ملفات متعارضة",
|
||||||
"loadingFile": "جارٍ تحميل الملف...",
|
"loadingFile": "جارٍ تحميل الملف...",
|
||||||
|
"preparingMerge": "جارٍ تحضير الدمج...",
|
||||||
"selectFile": "اختر ملفًا لحله",
|
"selectFile": "اختر ملفًا لحله",
|
||||||
"noConflicts": "لا توجد ملفات متعارضة",
|
"noConflicts": "لا توجد ملفات متعارضة",
|
||||||
"skipFile": "تخطي",
|
"skipFile": "تخطي",
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "Alle Konflikte gelöst",
|
"allResolved": "Alle Konflikte gelöst",
|
||||||
"conflictFiles": "Konfliktdateien",
|
"conflictFiles": "Konfliktdateien",
|
||||||
"loadingFile": "Datei wird geladen...",
|
"loadingFile": "Datei wird geladen...",
|
||||||
|
"preparingMerge": "Merge wird vorbereitet...",
|
||||||
"selectFile": "Datei zum Lösen auswählen",
|
"selectFile": "Datei zum Lösen auswählen",
|
||||||
"noConflicts": "Keine Konfliktdateien",
|
"noConflicts": "Keine Konfliktdateien",
|
||||||
"skipFile": "Überspringen",
|
"skipFile": "Überspringen",
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "All conflicts resolved",
|
"allResolved": "All conflicts resolved",
|
||||||
"conflictFiles": "Conflict Files",
|
"conflictFiles": "Conflict Files",
|
||||||
"loadingFile": "Loading file...",
|
"loadingFile": "Loading file...",
|
||||||
|
"preparingMerge": "Preparing merge...",
|
||||||
"selectFile": "Select a file to resolve",
|
"selectFile": "Select a file to resolve",
|
||||||
"noConflicts": "No conflict files",
|
"noConflicts": "No conflict files",
|
||||||
"skipFile": "Skip",
|
"skipFile": "Skip",
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "Todos los conflictos resueltos",
|
"allResolved": "Todos los conflictos resueltos",
|
||||||
"conflictFiles": "Archivos en conflicto",
|
"conflictFiles": "Archivos en conflicto",
|
||||||
"loadingFile": "Cargando archivo...",
|
"loadingFile": "Cargando archivo...",
|
||||||
|
"preparingMerge": "Preparando fusión...",
|
||||||
"selectFile": "Seleccionar un archivo para resolver",
|
"selectFile": "Seleccionar un archivo para resolver",
|
||||||
"noConflicts": "No hay archivos en conflicto",
|
"noConflicts": "No hay archivos en conflicto",
|
||||||
"skipFile": "Omitir",
|
"skipFile": "Omitir",
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "Tous les conflits sont résolus",
|
"allResolved": "Tous les conflits sont résolus",
|
||||||
"conflictFiles": "Fichiers en conflit",
|
"conflictFiles": "Fichiers en conflit",
|
||||||
"loadingFile": "Chargement du fichier...",
|
"loadingFile": "Chargement du fichier...",
|
||||||
|
"preparingMerge": "Préparation de la fusion...",
|
||||||
"selectFile": "Sélectionner un fichier à résoudre",
|
"selectFile": "Sélectionner un fichier à résoudre",
|
||||||
"noConflicts": "Aucun fichier en conflit",
|
"noConflicts": "Aucun fichier en conflit",
|
||||||
"skipFile": "Passer",
|
"skipFile": "Passer",
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "すべてのコンフリクトが解決されました",
|
"allResolved": "すべてのコンフリクトが解決されました",
|
||||||
"conflictFiles": "コンフリクトファイル",
|
"conflictFiles": "コンフリクトファイル",
|
||||||
"loadingFile": "ファイルを読み込み中...",
|
"loadingFile": "ファイルを読み込み中...",
|
||||||
|
"preparingMerge": "マージを準備中...",
|
||||||
"selectFile": "解決するファイルを選択してください",
|
"selectFile": "解決するファイルを選択してください",
|
||||||
"noConflicts": "コンフリクトファイルなし",
|
"noConflicts": "コンフリクトファイルなし",
|
||||||
"skipFile": "スキップ",
|
"skipFile": "スキップ",
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "모든 충돌이 해결되었습니다",
|
"allResolved": "모든 충돌이 해결되었습니다",
|
||||||
"conflictFiles": "충돌 파일",
|
"conflictFiles": "충돌 파일",
|
||||||
"loadingFile": "파일 로딩 중...",
|
"loadingFile": "파일 로딩 중...",
|
||||||
|
"preparingMerge": "병합 준비 중...",
|
||||||
"selectFile": "해결할 파일을 선택하세요",
|
"selectFile": "해결할 파일을 선택하세요",
|
||||||
"noConflicts": "충돌 파일 없음",
|
"noConflicts": "충돌 파일 없음",
|
||||||
"skipFile": "건너뛰기",
|
"skipFile": "건너뛰기",
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "Todos os conflitos resolvidos",
|
"allResolved": "Todos os conflitos resolvidos",
|
||||||
"conflictFiles": "Arquivos em conflito",
|
"conflictFiles": "Arquivos em conflito",
|
||||||
"loadingFile": "Carregando arquivo...",
|
"loadingFile": "Carregando arquivo...",
|
||||||
|
"preparingMerge": "Preparando mesclagem...",
|
||||||
"selectFile": "Selecione um arquivo para resolver",
|
"selectFile": "Selecione um arquivo para resolver",
|
||||||
"noConflicts": "Nenhum arquivo em conflito",
|
"noConflicts": "Nenhum arquivo em conflito",
|
||||||
"skipFile": "Pular",
|
"skipFile": "Pular",
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "所有冲突已解决",
|
"allResolved": "所有冲突已解决",
|
||||||
"conflictFiles": "冲突文件",
|
"conflictFiles": "冲突文件",
|
||||||
"loadingFile": "正在加载文件...",
|
"loadingFile": "正在加载文件...",
|
||||||
|
"preparingMerge": "正在准备合并...",
|
||||||
"selectFile": "选择一个文件进行解决",
|
"selectFile": "选择一个文件进行解决",
|
||||||
"noConflicts": "无冲突文件",
|
"noConflicts": "无冲突文件",
|
||||||
"skipFile": "跳过",
|
"skipFile": "跳过",
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"allResolved": "所有衝突已解決",
|
"allResolved": "所有衝突已解決",
|
||||||
"conflictFiles": "衝突檔案",
|
"conflictFiles": "衝突檔案",
|
||||||
"loadingFile": "正在載入檔案...",
|
"loadingFile": "正在載入檔案...",
|
||||||
|
"preparingMerge": "正在準備合併...",
|
||||||
"selectFile": "選擇一個檔案進行解決",
|
"selectFile": "選擇一個檔案進行解決",
|
||||||
"noConflicts": "無衝突檔案",
|
"noConflicts": "無衝突檔案",
|
||||||
"skipFile": "跳過",
|
"skipFile": "跳過",
|
||||||
|
|||||||
@@ -452,8 +452,15 @@ export async function gitPull(path: string): Promise<GitPullResult> {
|
|||||||
return invoke("git_pull", { path })
|
return invoke("git_pull", { path })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function gitStartPullMerge(path: string): Promise<void> {
|
export async function gitStartPullMerge(
|
||||||
return invoke("git_start_pull_merge", { path })
|
path: string,
|
||||||
|
upstreamCommit?: string | null
|
||||||
|
): Promise<void> {
|
||||||
|
return invoke("git_start_pull_merge", { path, upstreamCommit })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function gitHasMergeHead(path: string): Promise<boolean> {
|
||||||
|
return invoke("git_has_merge_head", { path })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function gitFetch(path: string): Promise<string> {
|
export async function gitFetch(path: string): Promise<string> {
|
||||||
@@ -556,9 +563,14 @@ export async function gitContinueOperation(
|
|||||||
|
|
||||||
export async function openMergeWindow(
|
export async function openMergeWindow(
|
||||||
folderId: number,
|
folderId: number,
|
||||||
operation: string
|
operation: string,
|
||||||
|
upstreamCommit?: string | null
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return invoke("open_merge_window", { folderId, operation })
|
return invoke("open_merge_window", {
|
||||||
|
folderId,
|
||||||
|
operation,
|
||||||
|
upstreamCommit: upstreamCommit ?? null,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function gitStash(path: string): Promise<string> {
|
export async function gitStash(path: string): Promise<string> {
|
||||||
|
|||||||
@@ -670,6 +670,7 @@ export interface GitConflictInfo {
|
|||||||
has_conflicts: boolean
|
has_conflicts: boolean
|
||||||
conflicted_files: string[]
|
conflicted_files: string[]
|
||||||
operation: string
|
operation: string
|
||||||
|
upstream_commit?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitPullResult {
|
export interface GitPullResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user