Implement interactive hunk staging and discarding
Introduce a new dialog that allows users to view file changes as individual hunks and selectively stage, unstage, or discard them. This provides more granular control over modifications, similar to `git add -p`. Enhance branch panel usability by allowing double-click to checkout a branch.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
import FileHistoryPanel from "./lib/components/FileHistoryPanel.svelte";
|
||||
import GlobalSearchDialog from "./lib/components/GlobalSearchDialog.svelte";
|
||||
import HistoryPanel from "./lib/components/HistoryPanel.svelte";
|
||||
import LinePatchDialog from "./lib/components/LinePatchDialog.svelte";
|
||||
import NewBranchDialog from "./lib/components/NewBranchDialog.svelte";
|
||||
import RepoLoadingOverlay from "./lib/components/RepoLoadingOverlay.svelte";
|
||||
import ResolveDialog from "./lib/components/ResolveDialog.svelte";
|
||||
@@ -25,6 +26,7 @@
|
||||
commit,
|
||||
compareCommits,
|
||||
cancelCodeSearch,
|
||||
applyFilePatch,
|
||||
createBranch,
|
||||
diffFileAgainstWorkingTree,
|
||||
compareFileToParent,
|
||||
@@ -41,6 +43,7 @@
|
||||
credLoad,
|
||||
credSave,
|
||||
credDelete,
|
||||
getFilePatch,
|
||||
readConflict,
|
||||
resolveConflict,
|
||||
resolveConflictSide,
|
||||
@@ -65,6 +68,7 @@
|
||||
GitRepositoryFile,
|
||||
GitSearchHit,
|
||||
GitStatus,
|
||||
PatchApplyAction,
|
||||
PreparedResolution,
|
||||
StoredCredential,
|
||||
} from "./lib/types";
|
||||
@@ -103,6 +107,12 @@
|
||||
let selectedDiffPath = "";
|
||||
let diffHighlightQuery = "";
|
||||
let pendingRestoreFile: { commit: GitCommit; file: GitCommitFile } | null = null;
|
||||
let linePatchOpen = false;
|
||||
let linePatchFile: GitFileStatus | null = null;
|
||||
let linePatchStaged = false;
|
||||
let linePatchText = "";
|
||||
let linePatchLoading = false;
|
||||
let linePatchError = "";
|
||||
let globalSearchOpen = false;
|
||||
let lastSearchQuery = "";
|
||||
let globalSearchResults: GitSearchHit[] = [];
|
||||
@@ -675,6 +685,77 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function openLinePatch(file: GitFileStatus, staged: boolean) {
|
||||
if (!activeRepoPath) return;
|
||||
linePatchOpen = true;
|
||||
linePatchFile = file;
|
||||
linePatchStaged = staged;
|
||||
linePatchText = "";
|
||||
linePatchError = "";
|
||||
linePatchLoading = true;
|
||||
|
||||
try {
|
||||
linePatchText = await getFilePatch(activeRepoPath, file.path, staged);
|
||||
} catch (error) {
|
||||
linePatchError = errorToMessage(error);
|
||||
errorMessage = linePatchError;
|
||||
} finally {
|
||||
linePatchLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshLinePatch() {
|
||||
if (!activeRepoPath || !linePatchFile) return;
|
||||
await openLinePatch(linePatchFile, linePatchStaged);
|
||||
}
|
||||
|
||||
function closeLinePatch() {
|
||||
if (isBusy) return;
|
||||
linePatchOpen = false;
|
||||
linePatchFile = null;
|
||||
linePatchText = "";
|
||||
linePatchError = "";
|
||||
}
|
||||
|
||||
function patchOperationLabel(action: PatchApplyAction, file: GitFileStatus): string {
|
||||
switch (action) {
|
||||
case "stage":
|
||||
return `Staging hunk in ${file.path}`;
|
||||
case "unstage":
|
||||
return `Unstaging hunk in ${file.path}`;
|
||||
default:
|
||||
return `Discarding hunk in ${file.path}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function applyLinePatch(action: PatchApplyAction, patch: string) {
|
||||
if (!activeRepoPath || !linePatchFile || isBusy) return;
|
||||
const file = linePatchFile;
|
||||
operation = patchOperationLabel(action, file);
|
||||
errorMessage = "";
|
||||
linePatchError = "";
|
||||
|
||||
try {
|
||||
applyStatus(await applyFilePatch(activeRepoPath, file.path, patch, action));
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
|
||||
const updatedPatch = await getFilePatch(activeRepoPath, file.path, linePatchStaged);
|
||||
if (updatedPatch.trim()) {
|
||||
linePatchText = updatedPatch;
|
||||
} else {
|
||||
linePatchOpen = false;
|
||||
linePatchFile = null;
|
||||
linePatchText = "";
|
||||
}
|
||||
} catch (error) {
|
||||
linePatchError = errorToMessage(error);
|
||||
errorMessage = linePatchError;
|
||||
} finally {
|
||||
operation = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function stageAllFiles() {
|
||||
const paths = changedFiles.filter((f) => f.unstaged !== null).map((f) => f.path);
|
||||
if (paths.length === 0) return;
|
||||
@@ -1129,6 +1210,7 @@
|
||||
onStage={stageFile}
|
||||
onUnstage={unstageFile}
|
||||
onDiscard={discardFile}
|
||||
onPatch={openLinePatch}
|
||||
onStageAll={stageAllFiles}
|
||||
onUnstageAll={unstageAllFiles}
|
||||
/>
|
||||
@@ -1189,6 +1271,20 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if linePatchOpen && linePatchFile}
|
||||
<LinePatchDialog
|
||||
file={linePatchFile}
|
||||
staged={linePatchStaged}
|
||||
patch={linePatchText}
|
||||
{isBusy}
|
||||
isLoading={linePatchLoading}
|
||||
error={linePatchError}
|
||||
onClose={closeLinePatch}
|
||||
onRefresh={refreshLinePatch}
|
||||
onApply={applyLinePatch}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if globalSearchOpen}
|
||||
<GlobalSearchDialog
|
||||
{hasRepository}
|
||||
|
||||
Reference in New Issue
Block a user