adds correct git watcher
Some checks failed
CI / check (push) Has been cancelled

This commit is contained in:
2026-06-16 11:42:23 +02:00
parent 7c69853e85
commit 29c90725ae

View File

@@ -1,4 +1,5 @@
import { join, sep } from 'node:path' import { join, resolve, sep } from 'node:path'
import { readFileSync, statSync } from 'node:fs'
import { app, dialog, shell, BrowserWindow, ipcMain, Menu } from 'electron' import { app, dialog, shell, BrowserWindow, ipcMain, Menu } from 'electron'
import { watch, type FSWatcher } from 'chokidar' import { watch, type FSWatcher } from 'chokidar'
import { addRecentProject, getName, getRecentProjects, getRoot, openDialog, setRoot } from './project' import { addRecentProject, getName, getRecentProjects, getRoot, openDialog, setRoot } from './project'
@@ -18,7 +19,9 @@ const WATCH_IGNORE = new Set([
let watcher: FSWatcher | null = null let watcher: FSWatcher | null = null
let configWatcher: FSWatcher | null = null let configWatcher: FSWatcher | null = null
let gitWatcher: FSWatcher | null = null
let watchTimer: ReturnType<typeof setTimeout> | null = null let watchTimer: ReturnType<typeof setTimeout> | null = null
let gitTimer: ReturnType<typeof setTimeout> | null = null
function broadcast(channel: string): void { function broadcast(channel: string): void {
for (const w of BrowserWindow.getAllWindows()) w.webContents.send(channel) for (const w of BrowserWindow.getAllWindows()) w.webContents.send(channel)
@@ -34,6 +37,47 @@ function startConfigWatcher(): void {
configWatcher.on('add', reload).on('change', reload).on('unlink', reload) configWatcher.on('add', reload).on('change', reload).on('unlink', reload)
} }
/** Resolve the real git directory for a project root. Normally this is
* `<root>/.git`, but for worktrees/submodules `.git` is a file containing
* `gitdir: <path>` pointing at the actual directory. */
function gitDir(root: string): string {
const dot = join(root, '.git')
try {
if (statSync(dot).isFile()) {
const m = readFileSync(dot, 'utf8').trim().match(/^gitdir:\s*(.+)$/)
if (m) return resolve(root, m[1].trim())
}
} catch { /* not a worktree, or .git missing */ }
return dot
}
/** The main watcher ignores `.git`, so branch switches / commits / staging
* done by external tools (Sublime Merge, the CLI, …) would never refresh the
* git column. Watch the few git files that mark those events so the renderer
* re-reads status: HEAD (branch switch), index (staging), refs/heads + logs
* (commits), MERGE_HEAD (in-progress merge). */
function startGitWatcher(): void {
if (gitWatcher) { gitWatcher.close(); gitWatcher = null }
const root = getRoot()
if (!root) return
const dir = gitDir(root)
gitWatcher = watch(
[
join(dir, 'HEAD'),
join(dir, 'index'),
join(dir, 'refs', 'heads'),
join(dir, 'logs', 'HEAD'),
join(dir, 'MERGE_HEAD'),
],
{ ignoreInitial: true },
)
const ping = (): void => {
if (gitTimer) clearTimeout(gitTimer)
gitTimer = setTimeout(() => broadcast('project:changed'), 200)
}
gitWatcher.on('add', ping).on('change', ping).on('unlink', ping).on('addDir', ping).on('unlinkDir', ping)
}
/** Show the folder picker; if a new folder is chosen, switch the project /** Show the folder picker; if a new folder is chosen, switch the project
* (config + watchers). Returns whether the project changed. */ * (config + watchers). Returns whether the project changed. */
async function openFolderFlow(win: BrowserWindow | null): Promise<boolean> { async function openFolderFlow(win: BrowserWindow | null): Promise<boolean> {
@@ -42,6 +86,7 @@ async function openFolderFlow(win: BrowserWindow | null): Promise<boolean> {
await resolveConfig(next) await resolveConfig(next)
startWatcher() startWatcher()
startConfigWatcher() startConfigWatcher()
startGitWatcher()
return true return true
} }
@@ -95,7 +140,7 @@ function registerIpc(): void {
ipcMain.handle('project:openPath', async (_e, path: string) => { ipcMain.handle('project:openPath', async (_e, path: string) => {
setRoot(path) setRoot(path)
const r = getRoot() const r = getRoot()
if (r) { await resolveConfig(r); startWatcher(); startConfigWatcher() } if (r) { await resolveConfig(r); startWatcher(); startConfigWatcher(); startGitWatcher() }
broadcast('project:changed') broadcast('project:changed')
return { root: getRoot(), name: getName() } return { root: getRoot(), name: getName() }
}) })
@@ -185,6 +230,7 @@ app.whenReady().then(async () => {
await addRecentProject(initialRoot) await addRecentProject(initialRoot)
startWatcher() startWatcher()
startConfigWatcher() startConfigWatcher()
startGitWatcher()
} }
createWindow() createWindow()
@@ -196,6 +242,7 @@ app.whenReady().then(async () => {
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
if (watcher) { watcher.close(); watcher = null } if (watcher) { watcher.close(); watcher = null }
if (configWatcher) { configWatcher.close(); configWatcher = null } if (configWatcher) { configWatcher.close(); configWatcher = null }
if (gitWatcher) { gitWatcher.close(); gitWatcher = null }
killAllPtys() killAllPtys()
if (!isMac) app.quit() if (!isMac) app.quit()
}) })