fix index.lock sticking: drop .git/ and index watches, use document-save events

The QFileSystemWatcher on .git/ directory and .git/index file caused a
feedback loop: every git operation touching the index fired watch events
that triggered git-status, creating lock contention and stale
index.lock files.

Now only concrete events trigger a refresh:
- .git/HEAD file change (branch switch)
- refs/heads/ directory change (branch create/delete)
- KTextEditor document save (documentSavedOrUploaded signal)

No periodic timers. The 500ms debounce remains as the single gate.
This commit is contained in:
Levi Neely 2026-07-20 10:55:41 +02:00
parent 7098d70b40
commit 460247e8a0
1 changed files with 29 additions and 18 deletions

View File

@ -483,11 +483,28 @@ GitPlusPlusView::GitPlusPlusView(GitPlusPlusPlugin *plugin, KTextEditor::MainWin
}
});
// Debounce timer for filesystem changes (avoid spamming refreshes)
// Debounce timer for filesystem/document changes (avoid spamming refreshes)
m_fsDebounce.setSingleShot(true);
m_fsDebounce.setInterval(500);
connect(&m_fsDebounce, &QTimer::timeout, this, &GitPlusPlusView::updateStatus);
// Refresh status when any document is saved — this is a concrete event
// that likely changes the working tree state.
auto connectDocSave = [this](KTextEditor::Document *doc) {
connect(doc, &KTextEditor::Document::documentSavedOrUploaded, this, [this]() {
m_fsDebounce.start();
});
};
connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::documentCreated,
this, [this, connectDocSave](KTextEditor::Editor *, KTextEditor::Document *doc) {
connectDocSave(doc);
});
// Connect already-open documents
const auto docs = KTextEditor::Editor::instance()->documents();
for (auto *doc : docs) {
connectDocSave(doc);
}
// Initial refresh
QMetaObject::invokeMethod(this, &GitPlusPlusView::updateStatus, Qt::QueuedConnection);
QMetaObject::invokeMethod(this, &GitPlusPlusView::refreshBranches, Qt::QueuedConnection);
@ -686,28 +703,22 @@ 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
// Only watch refs/heads for branch creation/deletion — a concrete event.
// Do NOT watch the .git directory itself or the index file:
// those fire on every git operation (index.lock create/delete) and cause
// cascading git-status calls that leave index.lock files behind.
QString refsHeads = gitDir + QStringLiteral("/refs/heads");
if (QFileInfo::exists(refsHeads))
watchPaths << refsHeads;
m_watcher->addPath(refsHeads);
m_watcher->addPaths(watchPaths);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, [this](const QString &) {
m_fsDebounce.start();
});
// Watch HEAD file specifically — changes indicate branch switch
QString indexFile = gitDir + QStringLiteral("/index");
// Watch HEAD file only — changes indicate a branch switch (concrete event).
QString headFile = gitDir + QStringLiteral("/HEAD");
QStringList filePaths;
if (QFileInfo::exists(indexFile)) filePaths << indexFile;
if (QFileInfo::exists(headFile)) filePaths << headFile;
if (!filePaths.isEmpty()) {
m_watcher->addPaths(filePaths);
if (QFileInfo::exists(headFile)) {
m_watcher->addPath(headFile);
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, [this, headFile](const QString &path) {
// Re-add: atomic renames remove the watch
if (!m_watcher->files().contains(path)) {
@ -716,9 +727,9 @@ void GitPlusPlusView::setupFileWatcher()
m_watcher->addPath(path);
});
}
m_fsDebounce.start();
// HEAD change means branch switch — refresh branches
if (path == headFile) {
// Branch switch — refresh branches and status after a short delay
m_fsDebounce.start();
QTimer::singleShot(600, this, [this]() { refreshBranches(); });
}
});