diff --git a/electron/git/gitHistory.ts b/electron/git/gitHistory.ts index 987834749..f546d26e6 100644 --- a/electron/git/gitHistory.ts +++ b/electron/git/gitHistory.ts @@ -2,7 +2,7 @@ // // Everything else this app does to git changes something. This is the half // that only looks: the log, what a commit touched, what is changed right now, -// a file as it was, and which worktrees exist. Nothing here writes. +// and which worktrees exist. Nothing here writes. // // It is a separate module from gitBranches.js for the same reason that one // exists — so it can be tested against real repositories (test/electron/git/gitHistory.test.js) @@ -14,7 +14,7 @@ // each one is the kind that produces a plausible wrong answer rather than an // error: // -// - `git show --name-status` on a MERGE commit prints NOTHING. Not an error, +// - `--name-status` on a MERGE commit prints NOTHING. Not an error, // not a diff — an empty list, because a diff against several parents is // ambiguous and git declines to pick. A history panel reading it straight // would draw every merge as a commit that changed no files. @@ -27,7 +27,6 @@ // "Home" needs the project's routing, which lives in project/ — so that // translation happens at the IPC edge, and this module stays about git. -import { toRecord } from '../../shared/core/record'; import { gitErrorDetail } from './git'; import type { Git } from './git'; @@ -169,21 +168,6 @@ async function log( return { commits, atEnd: commits.length < limit }; } -/** What one commit changed: `{status, path, from}` per file. */ -async function commitFiles( - git: Git, - { projectPath, ref }: { readonly projectPath: string; readonly ref: string }, -): Promise { - const { stdout } = await git(projectPath, [ - 'show', - '--name-status', - '--format=', - ...DIFF_FLAGS, - ref, - ]); - return parseNameStatus(stdout); -} - function parseNameStatus(stdout: string): FileChange[] { const files: FileChange[] = []; for (const line of stdout.split('\n')) { @@ -320,32 +304,6 @@ async function allFiles( }); } -/** A file's contents as they were at `ref`, or undefined when it wasn't there. */ -async function fileAt( - git: Git, - { - projectPath, - ref, - path: filePath, - }: { readonly projectPath: string; readonly ref: string; readonly path: string }, -): Promise { - try { - const { stdout } = await git(projectPath, ['show', `${ref}:${filePath}`]); - return stdout; - } catch (error: unknown) { - // Not an error worth raising: a file that did not exist yet is a normal - // answer to "what did this look like then", and the caller wants to say - // "this page didn't exist yet" rather than show a failure. - const stderr = toRecord(error)?.['stderr']; - if ( - /does not exist|exists on disk, but not in/i.test(typeof stderr === 'string' ? stderr : '') - ) { - return undefined; - } - throw error; - } -} - export interface WorktreeInfo { readonly path: string; readonly head: string | undefined; @@ -506,14 +464,4 @@ const describeFiles = (files ?? []).map((file) => ({ ...file, ...describeFile(file.path), from: file.from })); -export { - allFiles, - log, - commitFiles, - status, - fileAt, - worktrees, - parseNameStatus, - describeFile, - describeFiles, -}; +export { allFiles, log, status, worktrees, parseNameStatus, describeFile, describeFiles }; diff --git a/electron/handlers/gitHandlers.ts b/electron/handlers/gitHandlers.ts index 46c7a7256..8de38c6a0 100644 --- a/electron/handlers/gitHandlers.ts +++ b/electron/handlers/gitHandlers.ts @@ -169,10 +169,6 @@ function registerGitGhStatusHandlers({ ipcMain }: Pick): vo ipcMain.handle('git:log', async (_event, payload) => readGitLog(payload)); ipcMain.handle('git:commitDiff', async (_event, payload) => readCommitDiff(git, payload)); - ipcMain.handle('git:commitFiles', async (_event, { projectPath, ref }) => - gitHistory.describeFiles(await gitHistory.commitFiles(git, { projectPath, ref })), - ); - // Every file in the project, with what has happened to each — the file // browser's list, and the same status the commit picker reads. ipcMain.handle('git:allFiles', async (_event, { projectPath }) => @@ -183,10 +179,6 @@ function registerGitGhStatusHandlers({ ipcMain }: Pick): vo gitHistory.describeFiles(await gitHistory.status(git, { projectPath })), ); - ipcMain.handle('git:fileAt', async (_event, { projectPath, ref, path: filePath }) => - gitHistory.fileAt(git, { projectPath, ref, path: filePath }), - ); - ipcMain.handle('git:worktrees', async (_event, { projectPath }) => gitHistory.worktrees(git, { projectPath }), ); diff --git a/electron/handlers/thumbnails.ts b/electron/handlers/thumbnails.ts index 78a7f866b..def8d9645 100644 --- a/electron/handlers/thumbnails.ts +++ b/electron/handlers/thumbnails.ts @@ -18,7 +18,7 @@ import { serverAlive } from '../preview/devServerChecks'; import type { DevServers } from './devServers'; import type { MainHost } from './mainHost'; -export interface ThumbnailHost extends Pick { +export interface ThumbnailHost { readonly devServers: Pick; /** Electron's userData folder, where the pictures are kept. */ readonly userDataPath: () => string; @@ -78,16 +78,10 @@ export class Thumbnails { if (!thumbs.isStale(this.host.userDataPath(), projectPath)) { return; } - this.capture(projectPath) - .then((result) => { - if (result?.ok) { - this.host.send('recents:thumb', { projectPath }); - } - }) - .catch((error: unknown) => { - // A background refresh: the old picture stays, and the log says why. - console.error('[thumbs] capture failed:', error); - }); + this.capture(projectPath).catch((error: unknown) => { + // A background refresh: the old picture stays, and the log says why. + console.error('[thumbs] capture failed:', error); + }); }, delay); if (this.thumbTimer.unref) { this.thumbTimer.unref(); diff --git a/electron/main.ts b/electron/main.ts index 5b068ac0d..7406a5ab5 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -79,7 +79,6 @@ let mainWindow: Window | undefined = undefined; // kept by the objects that own it (handlers/devServers.ts, thumbnails.ts). const devServers = new DevServers({ send }); const thumbnails = new Thumbnails({ - send, devServers, userDataPath: () => app.getPath('userData'), }); diff --git a/electron/preload/preloadBridge.ts b/electron/preload/preloadBridge.ts index 7747c0614..77a0d7074 100644 --- a/electron/preload/preloadBridge.ts +++ b/electron/preload/preloadBridge.ts @@ -143,11 +143,6 @@ function dataBridge() { addRecent: invoke('recents:add'), removeRecent: invoke('recents:remove'), refreshThumb: invoke('recents:refreshThumb'), - onThumbUpdated: (callback: (payload: unknown) => void) => { - const listener = (_event: unknown, payload: unknown) => callback(payload); - ipcRenderer.on('recents:thumb', listener); - return () => ipcRenderer.removeListener('recents:thumb', listener); - }, } satisfies Partial; } @@ -197,11 +192,9 @@ function pageBridge() { previewStop: invoke('preview:stop'), checkPreview: invoke('preview:check'), gitLog: invoke('git:log'), - gitCommitFiles: invoke('git:commitFiles'), gitCommitDiff: invoke('git:commitDiff'), gitAllFiles: invoke('git:allFiles'), gitStatus: invoke('git:status'), - gitFileAt: invoke('git:fileAt'), gitWorktrees: invoke('git:worktrees'), gitPark: invoke('git:park'), gitUnpark: invoke('git:unpark'), diff --git a/shared/ipc/ipcPayloads.ts b/shared/ipc/ipcPayloads.ts index f6d4ccea7..4e931b478 100644 --- a/shared/ipc/ipcPayloads.ts +++ b/shared/ipc/ipcPayloads.ts @@ -198,10 +198,8 @@ export const IPC_PAYLOADS = { 'git:allFiles': object({ ...project }), 'git:checkout': object({ ...branch, create: maybeFlag, parkFirst: maybeFlag }), 'git:commit': object({ ...project, message: text, paths: optional(list(pathText)) }), - 'git:commitFiles': object({ ...reference }), 'git:commitDiff': parseCommitDiffRequest, 'git:deleteBranch': object({ ...branch, force: maybeFlag }), - 'git:fileAt': object({ ...reference, path: pathText }), 'git:ghStatus': pathText, 'git:info': pathText, 'git:init': pathText, diff --git a/shared/ipc/ipcResults.ts b/shared/ipc/ipcResults.ts index 3b9a5efd4..0a67a80a2 100644 --- a/shared/ipc/ipcResults.ts +++ b/shared/ipc/ipcResults.ts @@ -30,7 +30,6 @@ import { type WireFileModel, type WireProjectFile, type WireFileDescription, - type WireFileChange, type WireDeleteOutcome, type WireGitInfo, type WireCommitInfo, @@ -392,14 +391,7 @@ export interface IpcResults { readonly ok: true; readonly files: undefined | number; }; - readonly 'git:commitFiles': ReadonlyArray< - WireFileChange & - WireFileDescription & { - readonly from: undefined | string; - } - >; readonly 'git:deleteBranch': WireDeleteOutcome; - readonly 'git:fileAt': undefined | string; readonly 'git:ghStatus': | { readonly installed: false; diff --git a/shared/ipc/preloadApi.ts b/shared/ipc/preloadApi.ts index f540be138..8bac4ec4f 100644 --- a/shared/ipc/preloadApi.ts +++ b/shared/ipc/preloadApi.ts @@ -99,11 +99,9 @@ interface InvokeChannels { readonly previewStop: 'preview:stop'; readonly checkPreview: 'preview:check'; readonly gitLog: 'git:log'; - readonly gitCommitFiles: 'git:commitFiles'; readonly gitCommitDiff: 'git:commitDiff'; readonly gitAllFiles: 'git:allFiles'; readonly gitStatus: 'git:status'; - readonly gitFileAt: 'git:fileAt'; readonly gitWorktrees: 'git:worktrees'; readonly gitPark: 'git:park'; readonly gitUnpark: 'git:unpark'; @@ -159,7 +157,6 @@ export type PreloadBridge = InvokeSurface & { readonly onCssChanged: (callback: () => void) => () => void; readonly onCmsChanged: (callback: () => void) => () => void; readonly onPageMaybeChanged: (callback: (data: unknown) => void) => () => void; - readonly onThumbUpdated: (callback: (data: unknown) => void) => () => void; readonly onTerminalData: (callback: (data: unknown) => void) => () => void; readonly onTerminalExit: (callback: (data: unknown) => void) => () => void; readonly onTerminalProcess: (callback: (data: unknown) => void) => () => void; diff --git a/test/electron/app/mainBoundaries.test.ts b/test/electron/app/mainBoundaries.test.ts index be60429af..edaa76ce4 100644 --- a/test/electron/app/mainBoundaries.test.ts +++ b/test/electron/app/mainBoundaries.test.ts @@ -62,7 +62,7 @@ test('the complete channel inventory matches real main and terminal registration // Account sign-in adds one project-scoped channel for start, status, and cancellation. // Published-site discovery has its own channel so remote lookups cannot block local status. // Git pull and its background incoming check each have a project-scoped channel. - assert.equal(harness.handlers.size, 133); + assert.equal(harness.handlers.size, 131); assert.equal(terminalChannels.length, 4); assert.deepEqual( [...Object.keys(IPC_PAYLOADS)].sort(), diff --git a/test/electron/git/gitHistory.test.js b/test/electron/git/gitHistory.test.js index cd8f5d475..da7053dd8 100644 --- a/test/electron/git/gitHistory.test.js +++ b/test/electron/git/gitHistory.test.js @@ -6,7 +6,7 @@ // git's output rather than a behaviour, and each one fails by producing a // plausible wrong answer instead of an error: // -// - `git show --name-status` on a merge commit prints nothing at all. A +// - `--name-status` on a merge commit prints nothing at all. A // history panel reading it straight draws every merge as a commit that // changed no files, and nothing anywhere reports a problem. That is the // single most important case in this file. @@ -133,17 +133,15 @@ const commit = async (directory, subject) => { JSON.stringify(commits[1].parents), ); - const files = await history.commitFiles(git, { projectPath: directory, ref: commits[0].hash }); + const listed = (await history.log(git, { projectPath: directory, withFiles: true })).commits; + const files = listed[0].files; check('a commit lists what it changed', files.length === 1, JSON.stringify(files)); check('with the path', files[0].path === 'src/pages/about.astro', JSON.stringify(files[0])); check('and the letter for a new file', files[0].status === 'A', JSON.stringify(files[0])); // The root commit: no parent to diff against, and git handles it, but only // if the flags do not assume there is one. - const rootFiles = await history.commitFiles(git, { - projectPath: directory, - ref: commits[1].hash, - }); + const rootFiles = listed[1].files; check( 'the very first commit lists its files too', rootFiles.length === 1, @@ -271,7 +269,8 @@ const commit = async (directory, subject) => { ); check('so the whole history is there', commits.length === 4, String(commits.length)); - const files = await history.commitFiles(git, { projectPath: directory, ref: merge.hash }); + const listed = (await history.log(git, { projectPath: directory, withFiles: true })).commits; + const files = listed.find((item) => item.hash === merge.hash)?.files ?? []; check('a merge reports the files it brought in', files.length > 0, JSON.stringify(files)); check( 'namely the branch’s work', @@ -293,7 +292,8 @@ const commit = async (directory, subject) => { await sh(directory, 'mv', 'src/pages/about.astro', 'src/pages/contact.astro'); const ref = await commit(directory, 'renamed'); - const files = await history.commitFiles(git, { projectPath: directory, ref }); + const listed = (await history.log(git, { projectPath: directory, withFiles: true })).commits; + const files = listed.find((item) => item.hash === ref)?.files ?? []; check( 'a rename is one entry, not a delete and an add', files.length === 1, @@ -350,35 +350,6 @@ const commit = async (directory, subject) => { ); } - // --- A file as it was ---------------------------------------------------- - { - const directory = await repo('fileat'); - cleanup.push(directory); - write(directory, 'src/pages/index.astro', 'the old words\n'); - const first = await commit(directory, 'first'); - write(directory, 'src/pages/index.astro', 'the new words\n'); - await commit(directory, 'second'); - - const then = await history.fileAt(git, { - projectPath: directory, - ref: first, - path: 'src/pages/index.astro', - }); - check('a file can be read as it was', then.trim() === 'the old words', JSON.stringify(then)); - // "It did not exist yet" is a real answer to the question, and the panel - // wants to say so rather than show a failure. - const missing = await history.fileAt(git, { - projectPath: directory, - ref: first, - path: 'src/pages/later.astro', - }); - check( - 'a file that did not exist yet reads as undefined', - missing === undefined, - JSON.stringify(missing), - ); - } - // --- Paging -------------------------------------------------------------- { const directory = await repo('paging'); diff --git a/test/electron/preview/thumbnailStartup.test.ts b/test/electron/preview/thumbnailStartup.test.ts index ab192be0c..90de6ee08 100644 --- a/test/electron/preview/thumbnailStartup.test.ts +++ b/test/electron/preview/thumbnailStartup.test.ts @@ -40,7 +40,6 @@ test('opening a project cancels active and queued thumbnail startups', async (co } }); const thumbnails = new Thumbnails({ - send: () => {}, devServers: { current: () => undefined }, userDataPath: () => '/unused', }); @@ -76,7 +75,6 @@ test('closing during thumbnail port lookup prevents server startup', async (cont throw new Error('A cancelled thumbnail must not start a server'); }); const thumbnails = new Thumbnails({ - send: () => {}, devServers: { current: () => undefined }, userDataPath: () => '/unused', });