feat(git): convert Git commands to async for better performance
This update modifies several Git command functions to be asynchronous, improving the responsiveness of the application. By utilizing async runtime, operations that involve I/O or long-running tasks can now run without blocking the main thread, enhancing user experience. - Converted multiple Git command functions to async - Introduced a helper function to handle async tasks with error management - Improved overall performance and responsiveness of Git operations
This commit is contained in:
+147
-160
@@ -153,6 +153,7 @@
|
||||
RebaseCommit,
|
||||
RebasePlanItem,
|
||||
ReflogEntry,
|
||||
RepositoryBundle,
|
||||
StoredCredential,
|
||||
} from "./lib/types";
|
||||
|
||||
@@ -204,6 +205,15 @@
|
||||
clearIfCurrent: (message: string) => void;
|
||||
}
|
||||
|
||||
interface RepositoryRefreshOptions {
|
||||
branches?: boolean;
|
||||
tags?: boolean;
|
||||
stashes?: boolean;
|
||||
commits?: boolean;
|
||||
files?: boolean;
|
||||
fileHistory?: boolean;
|
||||
}
|
||||
|
||||
const OPEN_REPOS_KEY = "gitlite.openRepos.v1";
|
||||
const RECENT_REPOS_KEY = "gitlite.recentRepos.v1";
|
||||
const FAVORITE_REPOS_KEY = "gitlite.favoriteRepos.v1";
|
||||
@@ -372,7 +382,6 @@
|
||||
const BACKGROUND_FETCH_AFTER_SWITCH_GRACE_MS = 30_000;
|
||||
const BACKGROUND_FETCH_OTHER_BATCH_SIZE = 2;
|
||||
const STARTUP_SPLASH_MIN_VISIBLE_MS = 850;
|
||||
const STARTUP_FETCH_MAX_WAIT_MS = 20_000;
|
||||
let backgroundFetchTimer: ReturnType<typeof setInterval> | undefined;
|
||||
let backgroundRepoStatusTimer: ReturnType<typeof setInterval> | undefined;
|
||||
let backgroundFetchInFlight = false;
|
||||
@@ -482,12 +491,16 @@
|
||||
void getVersion().then((version) => { appVersion = version; }).catch(() => { appVersion = ""; });
|
||||
window.addEventListener("beforeunload", handleAppShutdown);
|
||||
window.addEventListener("pagehide", handleAppShutdown);
|
||||
void getCurrentWindow().onCloseRequested(() => handleAppShutdown()).then((unlisten) => {
|
||||
if (appShuttingDown) unlisten();
|
||||
else unlistenCloseRequested = unlisten;
|
||||
}).catch(() => {
|
||||
// Browser preview has no Tauri window; DOM lifecycle events still cover it.
|
||||
});
|
||||
try {
|
||||
void getCurrentWindow().onCloseRequested(() => handleAppShutdown()).then((unlisten) => {
|
||||
if (appShuttingDown) unlisten();
|
||||
else unlistenCloseRequested = unlisten;
|
||||
}).catch(() => {
|
||||
// Browser preview has no Tauri window; DOM lifecycle events still cover it.
|
||||
});
|
||||
} catch {
|
||||
// getCurrentWindow itself throws synchronously in a plain browser preview.
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
@@ -568,16 +581,15 @@
|
||||
|
||||
try {
|
||||
await waitForStartupPaint();
|
||||
await Promise.race([
|
||||
fetchOpenRepositoriesDuringStartup(),
|
||||
wait(STARTUP_FETCH_MAX_WAIT_MS),
|
||||
]);
|
||||
} finally {
|
||||
const remainingSplashTime = STARTUP_SPLASH_MIN_VISIBLE_MS - (performance.now() - startupStartedAt);
|
||||
if (remainingSplashTime > 0) await wait(remainingSplashTime);
|
||||
|
||||
await closeStartupSplashscreen();
|
||||
startBackgroundTimers();
|
||||
// Remote access can take seconds (offline networks, SSH negotiation,
|
||||
// credential helpers). It must never hold the startup screen hostage.
|
||||
void fetchOpenRepositoriesDuringStartup();
|
||||
void backgroundRepoStatusTick(false);
|
||||
}
|
||||
}
|
||||
@@ -765,23 +777,14 @@
|
||||
// now-active repo's name and data with this one's.
|
||||
if (!sameRepoPath(path, activeRepoPath)) return;
|
||||
if (statusFingerprint(nextStatus) === lastStatusFingerprint) return;
|
||||
applyStatus(nextStatus);
|
||||
// Something changed — reload branches, commits and files in one bundled call.
|
||||
const bundle = await openRepositoryBundle(path, Math.max(COMMIT_HISTORY_PAGE_SIZE, commits.length) + 1);
|
||||
if (!sameRepoPath(path, activeRepoPath)) return;
|
||||
const previousHeadHash = lastFileHistoryHeadHash;
|
||||
await refreshBranchList(path, bundle.branches);
|
||||
await refreshTags(path, bundle.tags);
|
||||
await refreshStashes(path, bundle.stashes);
|
||||
await refreshCommitHistory(path, bundle.commits);
|
||||
await refreshExplorerFiles(path, bundle.files);
|
||||
await applyRepositoryBundle(path, bundle);
|
||||
// File history reflects `git log`, which only changes when HEAD actually moves
|
||||
// (new commit, checkout, merge, ...) — skip the reload otherwise so a plain
|
||||
// working-tree/status change (staging, edits) doesn't keep re-fetching and
|
||||
// flickering the currently viewed file's history.
|
||||
if (lastFileHistoryHeadHash !== previousHeadHash && sameRepoPath(path, activeRepoPath)) {
|
||||
await refreshFileHistory(path);
|
||||
}
|
||||
} catch { /* ignore transient errors */ } finally {
|
||||
autoRefreshInFlight = false;
|
||||
}
|
||||
@@ -1114,10 +1117,7 @@
|
||||
aiCommitSplitOpen = false;
|
||||
aiCommitPlan = null;
|
||||
commitMessage = "";
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("ai_commit_split_applied", { groups: plan.groups.length, files: allFiles.length });
|
||||
});
|
||||
commitAiSplitting = false;
|
||||
@@ -1895,15 +1895,21 @@
|
||||
// ── Refresh helpers ────────────────────────────────────────────────────────
|
||||
|
||||
async function refreshBranchList(path = activeRepoPath, prefetched?: GitBranchInfo[]) {
|
||||
branches = prefetched ?? (await listBranches(path));
|
||||
const nextBranches = prefetched ?? (await listBranches(path));
|
||||
if (!sameRepoPath(path, activeRepoPath)) return;
|
||||
branches = nextBranches;
|
||||
}
|
||||
|
||||
async function refreshTags(path = activeRepoPath, prefetched?: GitTag[]) {
|
||||
tags = prefetched ?? (await listTags(path));
|
||||
const nextTags = prefetched ?? (await listTags(path));
|
||||
if (!sameRepoPath(path, activeRepoPath)) return;
|
||||
tags = nextTags;
|
||||
}
|
||||
|
||||
async function refreshStashes(path = activeRepoPath, prefetched?: GitStash[]) {
|
||||
stashes = prefetched ?? (await listStashes(path));
|
||||
const nextStashes = prefetched ?? (await listStashes(path));
|
||||
if (!sameRepoPath(path, activeRepoPath)) return;
|
||||
stashes = nextStashes;
|
||||
}
|
||||
|
||||
async function refreshCommitHistory(path = activeRepoPath, prefetched?: GitCommit[]) {
|
||||
@@ -1960,7 +1966,9 @@
|
||||
}
|
||||
|
||||
async function refreshExplorerFiles(path = activeRepoPath, prefetched?: GitRepositoryFile[]) {
|
||||
repoFiles = prefetched ?? (await listRepositoryFiles(path));
|
||||
const nextFiles = prefetched ?? (await listRepositoryFiles(path));
|
||||
if (!sameRepoPath(path, activeRepoPath)) return;
|
||||
repoFiles = nextFiles;
|
||||
const folderPaths = allExplorerFolderPaths(repoFiles);
|
||||
expandedExplorerPaths = new Set([...expandedExplorerPaths].filter((folder) => folderPaths.has(folder)));
|
||||
if (selectedExplorerPath && !explorerPathExists(repoFiles, selectedExplorerPath)) {
|
||||
@@ -1970,11 +1978,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshRepositoryViews(
|
||||
path = activeRepoPath,
|
||||
options: RepositoryRefreshOptions = {},
|
||||
) {
|
||||
const tasks: Promise<void>[] = [];
|
||||
if (options.branches ?? true) tasks.push(refreshBranchList(path));
|
||||
if (options.tags ?? false) tasks.push(refreshTags(path));
|
||||
if (options.stashes ?? false) tasks.push(refreshStashes(path));
|
||||
if (options.commits ?? true) tasks.push(refreshCommitHistory(path));
|
||||
if (options.files ?? true) tasks.push(refreshExplorerFiles(path));
|
||||
|
||||
// These reads do not depend on each other. Starting them together removes
|
||||
// several IPC/Git-process waterfalls after every user operation.
|
||||
await Promise.all(tasks);
|
||||
|
||||
// Explorer refresh may invalidate the selected path, so file history runs
|
||||
// after the parallel group rather than racing a disappearing selection.
|
||||
if (options.fileHistory ?? true) await refreshFileHistory(path);
|
||||
}
|
||||
|
||||
async function applyRepositoryBundle(
|
||||
path: string,
|
||||
bundle: RepositoryBundle,
|
||||
forceFileHistory = false,
|
||||
) {
|
||||
const previousHeadHash = lastFileHistoryHeadHash;
|
||||
applyStatus(bundle.status);
|
||||
const resolvedPath = activeRepoPath || path;
|
||||
await Promise.all([
|
||||
refreshBranchList(resolvedPath, bundle.branches),
|
||||
refreshTags(resolvedPath, bundle.tags),
|
||||
refreshStashes(resolvedPath, bundle.stashes),
|
||||
refreshCommitHistory(resolvedPath, bundle.commits),
|
||||
refreshExplorerFiles(resolvedPath, bundle.files),
|
||||
]);
|
||||
|
||||
if (forceFileHistory || lastFileHistoryHeadHash !== previousHeadHash) {
|
||||
await refreshFileHistory(resolvedPath);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshRepositorySnapshot(path = activeRepoPath, forceFileHistory = false) {
|
||||
const bundle = await openRepositoryBundle(
|
||||
path,
|
||||
Math.max(COMMIT_HISTORY_PAGE_SIZE, commits.length) + 1,
|
||||
);
|
||||
if (!sameRepoPath(path, activeRepoPath)) return;
|
||||
await applyRepositoryBundle(path, bundle, forceFileHistory);
|
||||
}
|
||||
|
||||
async function refreshRefsAndCommitGraph(path = activeRepoPath) {
|
||||
const previousHeadHash = lastFileHistoryHeadHash;
|
||||
await refreshBranchList(path);
|
||||
await refreshTags(path);
|
||||
await refreshCommitHistory(path);
|
||||
await Promise.all([
|
||||
refreshBranchList(path),
|
||||
refreshTags(path),
|
||||
refreshCommitHistory(path),
|
||||
]);
|
||||
if (lastFileHistoryHeadHash !== previousHeadHash && fileHistoryDialogOpen) {
|
||||
await refreshFileHistory(path);
|
||||
}
|
||||
@@ -2050,14 +2110,9 @@
|
||||
const bundle = await openRepositoryBundle(path, COMMIT_HISTORY_PAGE_SIZE + 1);
|
||||
if (requestId !== repoOpenRequestId) return;
|
||||
resetRepositoryState(false);
|
||||
applyStatus(bundle.status);
|
||||
await applyRepositoryBundle(path, bundle);
|
||||
if (globalSearchBusy) void cancelGlobalSearch();
|
||||
activeView = "repository";
|
||||
await refreshBranchList(activeRepoPath, bundle.branches);
|
||||
await refreshTags(activeRepoPath, bundle.tags);
|
||||
await refreshStashes(activeRepoPath, bundle.stashes);
|
||||
await refreshCommitHistory(activeRepoPath, bundle.commits);
|
||||
await refreshExplorerFiles(activeRepoPath, bundle.files);
|
||||
lastRepoSwitchAt = Date.now();
|
||||
trackEvent("repository_opened", {
|
||||
changed_files: bundle.status.files.length,
|
||||
@@ -2124,14 +2179,9 @@
|
||||
COMMIT_HISTORY_PAGE_SIZE + 1,
|
||||
);
|
||||
resetRepositoryState(false);
|
||||
applyStatus(bundle.status);
|
||||
await applyRepositoryBundle(bundle.status.repo_path, bundle);
|
||||
if (globalSearchBusy) void cancelGlobalSearch();
|
||||
activeView = "repository";
|
||||
await refreshBranchList(activeRepoPath, bundle.branches);
|
||||
await refreshTags(activeRepoPath, bundle.tags);
|
||||
await refreshStashes(activeRepoPath, bundle.stashes);
|
||||
await refreshCommitHistory(activeRepoPath, bundle.commits);
|
||||
await refreshExplorerFiles(activeRepoPath, bundle.files);
|
||||
cloneDialogOpen = false;
|
||||
pendingClone = null;
|
||||
if (credDialogAction === "clone") {
|
||||
@@ -2350,12 +2400,7 @@
|
||||
async function refreshRepo() {
|
||||
if (!activeRepoPath) { await openRepo(); return; }
|
||||
await runOperation("Refreshing", async () => {
|
||||
applyStatus(await getStatus(activeRepoPath));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshStashes(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositorySnapshot(activeRepoPath, true);
|
||||
trackEvent("repository_refreshed", {
|
||||
changed_files: status?.files.length ?? 0,
|
||||
});
|
||||
@@ -2366,10 +2411,7 @@
|
||||
if (!activeRepoPath || branch.current) return;
|
||||
await runOperation(`Checking out ${branch.name}`, async () => {
|
||||
applyStatus(await checkoutBranch(activeRepoPath, branch.name));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("branch_checked_out", {
|
||||
remote: branch.remote ? 1 : 0,
|
||||
});
|
||||
@@ -2381,10 +2423,7 @@
|
||||
if (!activeRepoPath || !name) return;
|
||||
await runOperation(`Creating ${name}`, async () => {
|
||||
applyStatus(await createBranch(activeRepoPath, name));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("branch_created");
|
||||
});
|
||||
}
|
||||
@@ -2403,10 +2442,7 @@
|
||||
await runOperation(`Renaming ${branch.name}`, async () => {
|
||||
applyStatus(await renameBranch(activeRepoPath, branch.name, name));
|
||||
renameBranchTarget = null;
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("branch_renamed");
|
||||
});
|
||||
}
|
||||
@@ -2454,10 +2490,7 @@
|
||||
applyStatus(await deleteBranch(activeRepoPath, branch.name, deleteBranchForce));
|
||||
deleteBranchTarget = null;
|
||||
deleteBranchForce = false;
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("branch_deleted", {
|
||||
force: forceDelete ? 1 : 0,
|
||||
});
|
||||
@@ -2633,10 +2666,7 @@
|
||||
await runOperation(`Creating ${name}`, async () => {
|
||||
applyStatus(await createBranch(activeRepoPath, name, target.hash));
|
||||
newBranchCommit = null;
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("branch_created_from_commit");
|
||||
});
|
||||
}
|
||||
@@ -2648,10 +2678,7 @@
|
||||
if (!["default", "squash", "ff-only", "no-ff"].includes(strategy)) { errorMessage = "Unknown merge strategy."; return; }
|
||||
await runOperation(`Merging ${branch.name}`, async () => {
|
||||
applyStatus(await mergeBranch(activeRepoPath, branch.name, strategy as import("./lib/types").MergeStrategy));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("branch_merged", {
|
||||
remote: branch.remote ? 1 : 0,
|
||||
});
|
||||
@@ -2662,10 +2689,7 @@
|
||||
if (!activeRepoPath || branch.current || rebaseInProgress || cherryPickInProgress) return;
|
||||
await runOperation(`Rebasing onto ${branch.name}`, async () => {
|
||||
applyStatus(await rebaseBranch(activeRepoPath, branch.name));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("branch_rebased", {
|
||||
remote: branch.remote ? 1 : 0,
|
||||
});
|
||||
@@ -2676,10 +2700,7 @@
|
||||
if (!activeRepoPath || !rebaseInProgress || hasConflicts) return;
|
||||
await runOperation("Continuing rebase", async () => {
|
||||
applyStatus(await rebaseContinue(activeRepoPath));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("rebase_continued");
|
||||
});
|
||||
}
|
||||
@@ -2695,10 +2716,7 @@
|
||||
resolveDialogOpen = false;
|
||||
conflict = null;
|
||||
conflictTarget = "";
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("rebase_aborted");
|
||||
});
|
||||
}
|
||||
@@ -2742,10 +2760,7 @@
|
||||
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);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("interactive_rebase_started", { commits: plan.length });
|
||||
});
|
||||
if (interactiveRebaseOpen && errorMessage) interactiveRebaseError = errorMessage;
|
||||
@@ -2787,10 +2802,7 @@
|
||||
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);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("reflog_recovered");
|
||||
});
|
||||
if (reflogOpen && errorMessage) reflogError = errorMessage;
|
||||
@@ -2843,10 +2855,7 @@
|
||||
if (!activeRepoPath || rebaseInProgress || cherryPickInProgress) return;
|
||||
await runOperation(`Cherry-picking ${commit.short_hash}`, async () => {
|
||||
applyStatus(await cherryPickCommit(activeRepoPath, commit.hash));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("commit_cherry_picked");
|
||||
});
|
||||
}
|
||||
@@ -2855,10 +2864,7 @@
|
||||
if (!activeRepoPath || !cherryPickInProgress || hasConflicts) return;
|
||||
await runOperation("Continuing cherry-pick", async () => {
|
||||
applyStatus(await cherryPickContinue(activeRepoPath));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("cherry_pick_continued");
|
||||
});
|
||||
}
|
||||
@@ -2874,10 +2880,7 @@
|
||||
resolveDialogOpen = false;
|
||||
conflict = null;
|
||||
conflictTarget = "";
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("cherry_pick_aborted");
|
||||
});
|
||||
}
|
||||
@@ -2953,10 +2956,7 @@
|
||||
errorMessage = "";
|
||||
await runOperation("Pulling", async () => {
|
||||
applyStatus(await pull(activeRepoPath, username, password, pullStrategy, selectedRemote || undefined));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("repository_pulled", {
|
||||
from_stored_credential: fromStore ? 1 : 0,
|
||||
changed_files: status?.files.length ?? 0,
|
||||
@@ -2995,9 +2995,7 @@
|
||||
await runOperation("Pushing", async () => {
|
||||
applyStatus(await push(activeRepoPath, username, password, remoteActionForceWithLease, selectedRemote || undefined));
|
||||
remoteActionForceWithLease = false;
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, { files: false });
|
||||
trackEvent("repository_pushed", {
|
||||
from_stored_credential: fromStore ? 1 : 0,
|
||||
changed_files: status?.files.length ?? 0,
|
||||
@@ -3021,10 +3019,7 @@
|
||||
|
||||
await runOperation("Pulling before push", async () => {
|
||||
applyStatus(await pull(activeRepoPath, username, password));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
});
|
||||
|
||||
if (errorMessage) {
|
||||
@@ -3041,9 +3036,7 @@
|
||||
|
||||
await runOperation("Pushing after pull", async () => {
|
||||
applyStatus(await push(activeRepoPath, username, password));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, { files: false });
|
||||
trackEvent("repository_pushed_after_pull", {
|
||||
from_stored_credential: fromStore ? 1 : 0,
|
||||
changed_files: status?.files.length ?? 0,
|
||||
@@ -3137,18 +3130,24 @@
|
||||
if (!activeRepoPath || !window.confirm(`Revert commit ${commit.short_hash} (${commit.summary}) with a new commit?`)) return;
|
||||
await runOperation(`Reverting ${commit.short_hash}`, async () => {
|
||||
applyStatus(await revertCommit(activeRepoPath, commit.hash));
|
||||
await refreshCommitHistory(activeRepoPath); await refreshExplorerFiles(activeRepoPath); await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, { branches: false });
|
||||
});
|
||||
}
|
||||
|
||||
async function continueMerge() {
|
||||
if (!activeRepoPath) return;
|
||||
await runOperation("Continuing merge", async () => { applyStatus(await mergeContinue(activeRepoPath)); await refreshCommitHistory(activeRepoPath); });
|
||||
await runOperation("Continuing merge", async () => {
|
||||
applyStatus(await mergeContinue(activeRepoPath));
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
});
|
||||
}
|
||||
|
||||
async function abortMerge() {
|
||||
if (!activeRepoPath || !window.confirm("Abort the current merge and restore the pre-merge state?")) return;
|
||||
await runOperation("Aborting merge", async () => { applyStatus(await mergeAbort(activeRepoPath)); await refreshCommitHistory(activeRepoPath); await refreshExplorerFiles(activeRepoPath); });
|
||||
await runOperation("Aborting merge", async () => {
|
||||
applyStatus(await mergeAbort(activeRepoPath));
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchPruneRepo() {
|
||||
@@ -3205,9 +3204,11 @@
|
||||
const stashedFiles = changedFiles.length;
|
||||
await runOperation("Stashing changes", async () => {
|
||||
applyStatus(await stashPush(activeRepoPath, message, includeUntracked));
|
||||
await refreshStashes(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, {
|
||||
branches: false,
|
||||
stashes: true,
|
||||
commits: false,
|
||||
});
|
||||
trackEvent("stash_saved", {
|
||||
include_untracked: includeUntracked ? 1 : 0,
|
||||
changed_files: stashedFiles,
|
||||
@@ -3219,9 +3220,11 @@
|
||||
if (!activeRepoPath) return;
|
||||
await runOperation(`Applying ${stash.selector}`, async () => {
|
||||
applyStatus(await stashApply(activeRepoPath, stash.selector));
|
||||
await refreshStashes(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, {
|
||||
branches: false,
|
||||
stashes: true,
|
||||
commits: false,
|
||||
});
|
||||
trackEvent("stash_applied");
|
||||
});
|
||||
}
|
||||
@@ -3230,9 +3233,11 @@
|
||||
if (!activeRepoPath) return;
|
||||
await runOperation(`Popping ${stash.selector}`, async () => {
|
||||
applyStatus(await stashPop(activeRepoPath, stash.selector));
|
||||
await refreshStashes(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, {
|
||||
branches: false,
|
||||
stashes: true,
|
||||
commits: false,
|
||||
});
|
||||
trackEvent("stash_popped");
|
||||
});
|
||||
}
|
||||
@@ -3303,8 +3308,7 @@
|
||||
const paths = files.map((file) => file.path);
|
||||
await runOperation(files.length === 1 ? `Discarding ${baseName(files[0].path)}` : `Discarding ${files.length} files`, async () => {
|
||||
applyStatus(await restoreFiles(activeRepoPath, paths, staged));
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, { branches: false, commits: false });
|
||||
trackEvent("file_discarded", {
|
||||
files: files.length,
|
||||
staged: staged ? 1 : 0,
|
||||
@@ -3323,8 +3327,7 @@
|
||||
if (stagedPaths.length > 0) nextStatus = await restoreFiles(activeRepoPath, stagedPaths, true);
|
||||
if (unstagedPaths.length > 0) nextStatus = await restoreFiles(activeRepoPath, unstagedPaths, false);
|
||||
if (nextStatus) applyStatus(nextStatus);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, { branches: false, commits: false });
|
||||
trackEvent("file_discarded", {
|
||||
files: files.length,
|
||||
staged: 2,
|
||||
@@ -3425,8 +3428,7 @@
|
||||
|
||||
try {
|
||||
applyStatus(await applyFilePatch(activeRepoPath, file.path, patch, action));
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, { branches: false, commits: false });
|
||||
|
||||
const updatedPatch = await getFilePatch(activeRepoPath, file.path, staged);
|
||||
if (updatedPatch.trim()) {
|
||||
@@ -3532,10 +3534,7 @@
|
||||
amendMode = false;
|
||||
preAmendDraftMessage = "";
|
||||
lastLocalAiGeneratedMessage = "";
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("commit_created", { amend: 1 });
|
||||
});
|
||||
return;
|
||||
@@ -3546,10 +3545,7 @@
|
||||
applyStatus(await commit(activeRepoPath, message));
|
||||
commitMessage = "";
|
||||
lastLocalAiGeneratedMessage = "";
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("commit_created", { amend: 0, staged_files: trackedStagedCount });
|
||||
});
|
||||
}
|
||||
@@ -3591,10 +3587,7 @@
|
||||
commitMessage = preAmendDraftMessage;
|
||||
preAmendDraftMessage = "";
|
||||
}
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("commit_undone");
|
||||
});
|
||||
}
|
||||
@@ -3607,10 +3600,7 @@
|
||||
if (!confirmed) return;
|
||||
await runOperation(`Restoring ${target.short_hash}`, async () => {
|
||||
applyStatus(await restoreToCommit(activeRepoPath, target.hash));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath);
|
||||
trackEvent("commit_restored");
|
||||
});
|
||||
}
|
||||
@@ -3621,8 +3611,7 @@
|
||||
if (!confirmed) return false;
|
||||
await runOperation(`Restoring ${file.path}`, async () => {
|
||||
applyStatus(await restoreFileFromCommit(activeRepoPath, target.hash, file.path));
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, { branches: false, commits: false });
|
||||
trackEvent("commit_file_restored");
|
||||
});
|
||||
return !errorMessage;
|
||||
@@ -3767,8 +3756,7 @@
|
||||
if (!confirmed) return;
|
||||
await runOperation(`Restoring ${selectedExplorerPath}`, async () => {
|
||||
applyStatus(await restoreFileFromCommit(activeRepoPath, target.hash, selectedExplorerPath));
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, { branches: false, commits: false });
|
||||
trackEvent("selected_file_restored_from_commit", {
|
||||
kind,
|
||||
});
|
||||
@@ -3960,8 +3948,7 @@
|
||||
}
|
||||
preparedResolutions = {};
|
||||
if (nextStatus) applyStatus(nextStatus);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
await refreshRepositoryViews(activeRepoPath, { branches: false, commits: false });
|
||||
|
||||
const remaining = (nextStatus?.files ?? status?.files ?? []).filter(
|
||||
(f) => f.staged === "conflicted" || f.unstaged === "conflicted",
|
||||
|
||||
Reference in New Issue
Block a user