Merge branch 'opt/light_mode'
publish / publish-tauri (appimage, 1, ubuntu-22.04, linux-x86_64) (release) Successful in 18m59s
publish / publish-tauri (nsis, , windows-latest, windows-x86_64) (release) Successful in 27m53s

This commit is contained in:
Christoph Brandau
2026-07-10 22:59:20 +02:00
9 changed files with 1389 additions and 106 deletions
+154 -1
View File
@@ -22,9 +22,11 @@
import FileHistoryPanel from "./lib/components/FileHistoryPanel.svelte";
import GlobalSearchDialog from "./lib/components/GlobalSearchDialog.svelte";
import HistoryPanel from "./lib/components/HistoryPanel.svelte";
import InteractiveRebaseDialog from "./lib/components/InteractiveRebaseDialog.svelte";
import LinePatchDialog from "./lib/components/LinePatchDialog.svelte";
import NewBranchDialog from "./lib/components/NewBranchDialog.svelte";
import RenameBranchDialog from "./lib/components/RenameBranchDialog.svelte";
import ReflogDialog from "./lib/components/ReflogDialog.svelte";
import RepoLoadingOverlay from "./lib/components/RepoLoadingOverlay.svelte";
import ResolveDialog from "./lib/components/ResolveDialog.svelte";
import StashPanel from "./lib/components/StashPanel.svelte";
@@ -62,6 +64,8 @@
listTags,
listCommits,
listFileHistory,
listInteractiveRebaseCommits,
listReflog,
listRepositoryFiles,
mergeBranch,
openRepoInExplorer,
@@ -83,9 +87,11 @@
resolveConflict,
resolveConflictSide,
restoreFileFromCommit,
restoreReflogEntry,
restoreFiles,
restoreToCommit,
searchCodeIntroductions,
startInteractiveRebase,
setSyncBadge,
stageFiles,
stashApply,
@@ -119,6 +125,9 @@
LocalModelOption,
PatchApplyAction,
PreparedResolution,
RebaseCommit,
RebasePlanItem,
ReflogEntry,
StoredCredential,
} from "./lib/types";
@@ -254,6 +263,15 @@
let deleteBranchForce = false;
let compareSelectOpen = false;
let compareDialogOpen = false;
let interactiveRebaseOpen = false;
let interactiveRebaseBase = "";
let interactiveRebaseCommits: RebaseCommit[] = [];
let interactiveRebaseLoading = false;
let interactiveRebaseError = "";
let reflogOpen = false;
let reflogEntries: ReflogEntry[] = [];
let reflogLoading = false;
let reflogError = "";
let selectedDiffPath = "";
let diffHighlightQuery = "";
let pendingRestoreFile: { commit: GitCommit; file: GitCommitFile } | null = null;
@@ -654,7 +672,7 @@
}
async function autoRefreshTick() {
if (!autoRefreshEnabled || activeView !== "repository" || !activeRepoPath || isBusy || autoRefreshInFlight || resolveDialogOpen || compareDialogOpen || compareSelectOpen || newBranchCommit || globalSearchOpen) return;
if (!autoRefreshEnabled || activeView !== "repository" || !activeRepoPath || isBusy || autoRefreshInFlight || resolveDialogOpen || compareDialogOpen || compareSelectOpen || interactiveRebaseOpen || reflogOpen || newBranchCommit || globalSearchOpen) return;
const path = activeRepoPath;
autoRefreshInFlight = true;
try {
@@ -1580,6 +1598,13 @@
comparison = null;
compareSelectOpen = false;
compareDialogOpen = false;
interactiveRebaseOpen = false;
interactiveRebaseBase = "";
interactiveRebaseCommits = [];
interactiveRebaseError = "";
reflogOpen = false;
reflogEntries = [];
reflogError = "";
selectedDiffPath = "";
pendingRestoreFile = null;
newBranchCommit = null;
@@ -2250,6 +2275,99 @@
});
}
function preferredInteractiveRebaseBase(): string {
const candidates = [status?.upstream, "origin/main", "main", "origin/master", "master"]
.filter((value): value is string => Boolean(value) && value !== status?.current_branch);
for (const candidate of candidates) {
if (branches.some((branch) => branch.name === candidate)) return candidate;
}
return branches.find((branch) => !branch.current)?.name ?? "";
}
async function loadInteractiveRebaseRange(base: string) {
interactiveRebaseBase = base;
interactiveRebaseCommits = [];
interactiveRebaseError = "";
if (!activeRepoPath || !base) return;
interactiveRebaseLoading = true;
try {
const result = await listInteractiveRebaseCommits(activeRepoPath, base);
if (interactiveRebaseBase === base) interactiveRebaseCommits = result;
} catch (error) {
if (interactiveRebaseBase === base) interactiveRebaseError = errorToMessage(error);
} finally {
if (interactiveRebaseBase === base) interactiveRebaseLoading = false;
}
}
function openInteractiveRebase() {
if (!hasRepository || rebaseInProgress || cherryPickInProgress || isBusy) return;
interactiveRebaseOpen = true;
const base = preferredInteractiveRebaseBase();
void loadInteractiveRebaseRange(base);
trackEvent("interactive_rebase_opened");
}
async function runInteractiveRebase(plan: RebasePlanItem[]) {
if (!activeRepoPath || !interactiveRebaseBase || isBusy) return;
interactiveRebaseError = "";
await runOperation("Starting interactive rebase", async () => {
applyStatus(await startInteractiveRebase(activeRepoPath, interactiveRebaseBase, plan));
interactiveRebaseOpen = false;
await refreshBranchList(activeRepoPath);
await refreshCommitHistory(activeRepoPath);
await refreshExplorerFiles(activeRepoPath);
await refreshFileHistory(activeRepoPath);
trackEvent("interactive_rebase_started", { commits: plan.length });
});
if (interactiveRebaseOpen && errorMessage) interactiveRebaseError = errorMessage;
}
async function openReflog() {
if (!hasRepository || isBusy) return;
reflogOpen = true;
reflogEntries = [];
reflogError = "";
reflogLoading = true;
try {
reflogEntries = await listReflog(activeRepoPath, 300);
trackEvent("reflog_opened", { entries: reflogEntries.length });
} catch (error) {
reflogError = errorToMessage(error);
} finally {
reflogLoading = false;
}
}
async function previewReflogEntry(entry: ReflogEntry) {
if (!activeRepoPath || isBusy) return;
await runOperation("Previewing reflog entry", async () => {
const result = await compareCommits(activeRepoPath, entry.hash, "HEAD");
comparison = result;
selectedDiffPath = result.files[0]?.path ?? "";
diffHighlightQuery = "";
pendingRestoreFile = null;
reflogOpen = false;
compareDialogOpen = true;
trackEvent("reflog_previewed", { files: result.files.length });
});
}
async function recoverReflogEntry(entry: ReflogEntry, branch: string) {
if (!activeRepoPath || !branch.trim() || isBusy) return;
reflogError = "";
await runOperation("Restoring reflog entry", async () => {
applyStatus(await restoreReflogEntry(activeRepoPath, entry.hash, branch.trim()));
reflogOpen = false;
await refreshBranchList(activeRepoPath);
await refreshCommitHistory(activeRepoPath);
await refreshExplorerFiles(activeRepoPath);
await refreshFileHistory(activeRepoPath);
trackEvent("reflog_recovered");
});
if (reflogOpen && errorMessage) reflogError = errorMessage;
}
async function createNewTag(name: string, message: string) {
const trimmed = name.trim();
if (!activeRepoPath || !trimmed) return;
@@ -3337,6 +3455,8 @@
else if (event.key === "Escape" && newBranchCommit) newBranchCommit = null;
else if (event.key === "Escape" && renameBranchTarget) renameBranchTarget = null;
else if (event.key === "Escape" && deleteBranchTarget) closeDeleteBranchDialog();
else if (event.key === "Escape" && interactiveRebaseOpen && !isBusy) interactiveRebaseOpen = false;
else if (event.key === "Escape" && reflogOpen && !isBusy) reflogOpen = false;
else if (event.key === "Escape" && compareSelectOpen) compareSelectOpen = false;
else if (event.key === "Escape" && globalSearchOpen) closeGlobalSearchDialog();
}
@@ -3374,6 +3494,8 @@
onRefresh={refreshRepo}
onSearch={openGlobalSearchDialog}
onCompare={openCompareSelect}
onInteractiveRebase={openInteractiveRebase}
onReflog={openReflog}
onOpenInExplorer={openActiveRepoInExplorer}
onToggleAutoRefresh={toggleAutoRefresh}
onOpenSettings={() => { appSettingsOpen = true; }}
@@ -4093,6 +4215,37 @@
/>
{/if}
<!-- Compare: pick the two commits to diff -->
{#if interactiveRebaseOpen}
<InteractiveRebaseDialog
{branches}
currentBranch={status?.current_branch ?? ""}
base={interactiveRebaseBase}
commits={interactiveRebaseCommits}
isLoading={interactiveRebaseLoading}
{isBusy}
{operation}
error={interactiveRebaseError}
onBaseChange={loadInteractiveRebaseRange}
onStart={runInteractiveRebase}
onClose={() => { if (!isBusy) interactiveRebaseOpen = false; }}
/>
{/if}
{#if reflogOpen}
<ReflogDialog
entries={reflogEntries}
currentHash={reflogEntries.find((entry) => entry.selector === "HEAD@{0}")?.hash ?? ""}
isLoading={reflogLoading}
{isBusy}
{operation}
error={reflogError}
onPreview={previewReflogEntry}
onRestore={recoverReflogEntry}
onClose={() => { if (!isBusy) reflogOpen = false; }}
/>
{/if}
<!-- Compare: pick the two commits to diff -->
{#if compareSelectOpen}
<CompareSelectDialog