From 3febe2f1845a323d22ba6bbf4b2e02c4fb800b8a Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Thu, 16 Jul 2026 04:28:24 +0200 Subject: [PATCH] Fix auto-refresh: watch .git directory instead of just files Watching individual files (.git/index, .git/HEAD) fails reliably because git replaces them via atomic rename, which removes inotify watches. Watch the .git directory itself via directoryChanged signal which fires on any create/delete/rename inside it. Keep file watches as fallback. Also watch refs/heads for branch tip changes. --- gitplusplus.cpp | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/gitplusplus.cpp b/gitplusplus.cpp index 273ec96..bfd174b 100644 --- a/gitplusplus.cpp +++ b/gitplusplus.cpp @@ -515,8 +515,7 @@ void GitPlusPlusView::setupFileWatcher() delete m_watcher; m_watcher = new QFileSystemWatcher(this); - // Watch .git/index (changes on stage/unstage/commit/pull/merge) - // and .git/HEAD (changes on branch switch) + // Determine the actual .git directory // For worktrees, .git is a file pointing to the real git dir QString gitDir; QString dotGit = m_gitDir + QStringLiteral("/.git"); @@ -536,24 +535,36 @@ void GitPlusPlusView::setupFileWatcher() if (gitDir.isEmpty()) return; + // Watch the git directory itself — picks up renames of index, HEAD, refs, etc. + // This is more robust than watching individual files which get replaced atomically. QStringList watchPaths; + watchPaths << gitDir; + + // Also watch refs/heads for branch changes + QString refsHeads = gitDir + QStringLiteral("/refs/heads"); + if (QFileInfo::exists(refsHeads)) + watchPaths << refsHeads; + + m_watcher->addPaths(watchPaths); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, [this](const QString &) { + m_fsDebounce.start(); + }); + // Still watch individual files as a fallback for systems where dir watching is coarse QString indexFile = gitDir + QStringLiteral("/index"); QString headFile = gitDir + QStringLiteral("/HEAD"); - if (QFileInfo::exists(indexFile)) watchPaths << indexFile; - if (QFileInfo::exists(headFile)) watchPaths << headFile; - - if (!watchPaths.isEmpty()) { - m_watcher->addPaths(watchPaths); + QStringList filePaths; + if (QFileInfo::exists(indexFile)) filePaths << indexFile; + if (QFileInfo::exists(headFile)) filePaths << headFile; + if (!filePaths.isEmpty()) { + m_watcher->addPaths(filePaths); connect(m_watcher, &QFileSystemWatcher::fileChanged, this, [this](const QString &path) { - // Re-add the path: QFileSystemWatcher drops watches when files are - // replaced atomically (git writes a temp file then renames over it) + // Re-add: atomic renames remove the watch if (!m_watcher->files().contains(path)) { - QTimer::singleShot(100, this, [this, path]() { - if (QFileInfo::exists(path)) + QTimer::singleShot(200, this, [this, path]() { + if (QFileInfo::exists(path) && m_watcher) m_watcher->addPath(path); }); } - // Debounce: multiple changes can fire rapidly m_fsDebounce.start(); }); }