Skip to content

Commit 58756ba

Browse files
committed
Move amended commit detection to background executor
Avoid UI hangs in large repos
1 parent c8b9bfd commit 58756ba

File tree

3 files changed

+82
-57
lines changed

3 files changed

+82
-57
lines changed

qgitc/amendcommitmodel.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class AmendCommitInfo:
1515
author: str
1616
date: str
1717
willAmend: bool = True # Whether this commit will be amended
18+
body: Optional[str] = None
1819

1920

2021
class AmendCommitListModel(QAbstractListModel):

qgitc/commitwindow.py

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ def __init__(self, submodule: str, files: List[str], error: str = None):
151151
self.error = error
152152

153153

154+
class AmendCommitDetectedEvent(QEvent):
155+
Type = QEvent.User + 9
156+
157+
def __init__(self, commitInfo: AmendCommitInfo):
158+
super().__init__(QEvent.Type(AmendCommitDetectedEvent.Type))
159+
self.commitInfo = commitInfo
160+
161+
154162
class CommitWindow(StateWindow):
155163

156164
def __init__(self, parent=None):
@@ -281,6 +289,10 @@ def __init__(self, parent=None):
281289

282290
self.ui.btnCommit.clicked.connect(self._onCommitClicked)
283291

292+
self._amendDetectExecutor = SubmoduleExecutor(self)
293+
self._amendDetectExecutor.finished.connect(self._onAmendDetectFinished)
294+
self._amendDetectionResults: List[AmendCommitInfo] = []
295+
284296
# no UI tasks
285297
self._submoduleExecutor = SubmoduleExecutor(self)
286298
self._submoduleExecutor.finished.connect(
@@ -888,6 +900,32 @@ def _onNonUITaskFinished(self):
888900
self.ui.spinnerUnstaged.stop()
889901
self._updateAmendCommitsIfNeeded()
890902

903+
def _onAmendDetectFinished(self):
904+
"""Called when amend commit detection completes"""
905+
commits = self._amendDetectionResults
906+
mainCommit = None
907+
if commits:
908+
for commit in commits:
909+
if not commit.repoDir or commit.repoDir == ".":
910+
mainCommit = commit
911+
break
912+
if not mainCommit:
913+
mainCommit = commits[0]
914+
915+
ApplicationBase.instance().postEvent(
916+
self, TemplateReadyEvent(mainCommit.body, True))
917+
918+
hasStagedFiles = self._stagedModel.rowCount() > 0
919+
if not hasStagedFiles and commits:
920+
commits = [c for c in commits if c.subject == mainCommit.subject]
921+
922+
# Determine if unchecking is allowed
923+
allowUncheck = not hasStagedFiles and len(commits) > 1
924+
925+
# Update model with commits
926+
self._amendCommitsModel.setAllowUncheck(allowUncheck)
927+
self._amendCommitsModel.setCommits(commits)
928+
891929
def _blockUI(self, blocked=True):
892930
self.ui.tbUnstage.setEnabled(not blocked)
893931
self.ui.tbUnstageAll.setEnabled(not blocked)
@@ -945,6 +983,10 @@ def event(self, evt: QEvent):
945983
evt.ntpDateTime, evt.localDateTime)
946984
return True
947985

986+
if evt.type() == AmendCommitDetectedEvent.Type:
987+
self._handleAmendCommitDetectedEvent(evt.commitInfo)
988+
return True
989+
948990
return super().event(evt)
949991

950992
def _addBlock(self, key: str, title: str):
@@ -1014,6 +1056,11 @@ def _handleUpdateCommitProgress(self, submodule: str, out: str, error: str, upda
10141056
# add title only
10151057
self._updateBlockOutput(repoName, "", False, action)
10161058

1059+
def _handleAmendCommitDetectedEvent(self, commitInfo: AmendCommitInfo):
1060+
"""Handle single commit detected from worker thread"""
1061+
if commitInfo:
1062+
self._amendDetectionResults.append(commitInfo)
1063+
10171064
def reloadLocalChanges(self):
10181065
self._statusFetcher.cancel()
10191066
self.clear()
@@ -1639,6 +1686,7 @@ def _onCodeReviewClicked(self):
16391686

16401687
def cancel(self, force=False):
16411688
self._aiMessage.cancel(force)
1689+
self._amendDetectExecutor.cancel(force)
16421690
self._submoduleExecutor.cancel(force)
16431691
self._commitExecutor.cancel(force)
16441692
self._statusFetcher.cancel(force)
@@ -2018,56 +2066,35 @@ def _onAmendToggled(self, checked: bool):
20182066

20192067
def _detectAmendCommits(self):
20202068
"""Detect which commits will be amended based on staged files or HEAD"""
2021-
commits = []
2069+
# Cancel any running detection
2070+
if self._amendDetectExecutor.isRunning():
2071+
self._amendDetectExecutor.cancel()
20222072

20232073
# Collect repos that have staged files
20242074
submoduleFiles = self._collectModelFiles(self._stagedModel)
2025-
hasStagedFiles = bool(submoduleFiles)
2075+
2076+
# Clear previous results
2077+
self._amendDetectionResults.clear()
20262078

20272079
if submoduleFiles:
20282080
# If there are staged files, detect commits from those repos
2029-
for repoDir in submoduleFiles.keys():
2030-
fullPath = fullRepoDir(repoDir)
2031-
commitInfo = self._getCommitInfo("HEAD", fullPath, repoDir)
2032-
if commitInfo:
2033-
commits.append(commitInfo)
2081+
repoDirs = list(submoduleFiles.keys())
20342082
else:
2035-
# No staged files - detect from HEAD of main repo
2036-
mainCommitInfo = self._getCommitInfo("HEAD", Git.REPO_DIR, None)
2037-
if not mainCommitInfo:
2038-
return
2039-
2040-
commits.append(mainCommitInfo)
2041-
repoDirs = {mainCommitInfo.repoDir}
2042-
2043-
# Check submodules for commits with the same message
2083+
# No staged files - detect from HEAD of main repo + matching submodules
20442084
app = ApplicationBase.instance()
2045-
if app.submodules:
2046-
for submodule in app.submodules:
2047-
if not submodule or submodule == ".":
2048-
continue
2049-
if submodule in repoDirs:
2050-
continue
2051-
subRepoDir = fullRepoDir(submodule)
2052-
subCommitInfo = self._getCommitInfo(
2053-
"HEAD", subRepoDir, submodule)
2054-
2055-
# Include if commit message matches
2056-
if subCommitInfo and subCommitInfo.subject == mainCommitInfo.subject:
2057-
repoDirs.add(subCommitInfo.repoDir)
2058-
commits.append(subCommitInfo)
2085+
repoDirs = app.submodules
20592086

2060-
allowUncheck = not hasStagedFiles and len(commits) > 1
2061-
self._amendCommitsModel.setAllowUncheck(allowUncheck)
2062-
self._amendCommitsModel.setCommits(commits)
2087+
self._amendDetectExecutor.submit(repoDirs, self._doDetectAmendCommits)
20632088

2064-
# Get the last commit message (from the first repo) when allowed
2065-
if (commits and self._canUpdateMessage() and self._submoduleExecutor
2066-
and not self._submoduleExecutor.isRunning()):
2067-
self._blockUI()
2068-
self.ui.spinnerUnstaged.start()
2069-
self._submoduleExecutor.submit(
2070-
[commits[0].repoDir], self._doGetMessage)
2089+
def _doDetectAmendCommits(self, submodule: str, userData: any, cancelEvent: CancelEvent):
2090+
if cancelEvent.isSet():
2091+
return
2092+
2093+
fullPath = fullRepoDir(submodule)
2094+
commitInfo = self._getCommitInfo("HEAD", fullPath, submodule)
2095+
if commitInfo and not cancelEvent.isSet():
2096+
ApplicationBase.instance().postEvent(
2097+
self, AmendCommitDetectedEvent(commitInfo))
20712098

20722099
def _normalizeRepoDirDisplay(self, repoDir: str):
20732100
if not repoDir or repoDir == ".":
@@ -2076,14 +2103,15 @@ def _normalizeRepoDirDisplay(self, repoDir: str):
20762103

20772104
def _getCommitInfo(self, sha1: str, repoDir: str, repoDirDisplay: str) -> AmendCommitInfo:
20782105
"""Get commit information for a specific commit"""
2079-
summary = Git.commitSummary(sha1, repoDir)
2106+
summary = Git.commitSummary(sha1, repoDir, includeFullMessage=True)
20802107
if not summary:
20812108
return None
20822109

20832110
return AmendCommitInfo(
20842111
repoDir=self._normalizeRepoDirDisplay(repoDirDisplay),
20852112
sha1=summary["sha1"],
20862113
subject=summary["subject"],
2114+
body=summary["body"],
20872115
author=summary["author"],
20882116
date=summary["date"],
20892117
willAmend=True
@@ -2102,16 +2130,6 @@ def _canUpdateMessage(self):
21022130

21032131
return False
21042132

2105-
def _doGetMessage(self, submodule: str, userData, cancelEvent: CancelEvent):
2106-
if cancelEvent.isSet():
2107-
return
2108-
2109-
repoDir = fullRepoDir(submodule)
2110-
message = Git.commitMessage("HEAD", repoDir)
2111-
if cancelEvent.isSet():
2112-
return
2113-
ApplicationBase.instance().postEvent(self, TemplateReadyEvent(message, True))
2114-
21152133
def _onExternalDiff(self):
21162134
ApplicationBase.instance().trackFeatureUsage("commit.external_diff")
21172135
listView: QListView = self._acRestoreFiles.data()

qgitc/gitutils.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,10 @@ def branches():
284284
return data.decode("utf-8").split('\n')
285285

286286
@staticmethod
287-
def commitSummary(sha1, repoDir=None):
287+
def commitSummary(sha1, repoDir=None, includeFullMessage=False):
288288
fmt = "%h%x01%s%x01%ad%x01%an%x01%ae"
289+
if includeFullMessage:
290+
fmt += "%x01%B"
289291
args = ["show", "-s",
290292
"--pretty=format:{0}".format(fmt),
291293
"--date=short", sha1]
@@ -296,11 +298,15 @@ def commitSummary(sha1, repoDir=None):
296298

297299
parts = data.decode("utf-8").split("\x01")
298300

299-
return {"sha1": parts[0],
300-
"subject": parts[1],
301-
"date": parts[2],
302-
"author": parts[3],
303-
"email": parts[4]}
301+
summary = {"sha1": parts[0],
302+
"subject": parts[1],
303+
"date": parts[2],
304+
"author": parts[3],
305+
"email": parts[4]}
306+
if includeFullMessage:
307+
summary["body"] = parts[5].rstrip()
308+
309+
return summary
304310

305311
@staticmethod
306312
def abbrevCommit(sha1):

0 commit comments

Comments
 (0)