src/eric7/Plugins/VcsPlugins/vcsGit/GitLogBrowserDialog.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
11 import collections 11 import collections
12 import re 12 import re
13 import contextlib 13 import contextlib
14 import pathlib 14 import pathlib
15 15
16 from PyQt6.QtCore import ( 16 from PyQt6.QtCore import pyqtSlot, Qt, QDate, QProcess, QTimer, QSize, QPoint
17 pyqtSlot, Qt, QDate, QProcess, QTimer, QSize, QPoint 17 from PyQt6.QtGui import QColor, QPixmap, QPainter, QPen, QIcon, QTextCursor, QPalette
18 )
19 from PyQt6.QtGui import (
20 QColor, QPixmap, QPainter, QPen, QIcon, QTextCursor, QPalette
21 )
22 from PyQt6.QtWidgets import ( 18 from PyQt6.QtWidgets import (
23 QWidget, QDialogButtonBox, QHeaderView, QTreeWidgetItem, QApplication, 19 QWidget,
24 QLineEdit, QMenu, QInputDialog 20 QDialogButtonBox,
21 QHeaderView,
22 QTreeWidgetItem,
23 QApplication,
24 QLineEdit,
25 QMenu,
26 QInputDialog,
25 ) 27 )
26 28
27 from EricWidgets.EricApplication import ericApp 29 from EricWidgets.EricApplication import ericApp
28 from EricWidgets import EricMessageBox, EricFileDialog 30 from EricWidgets import EricMessageBox, EricFileDialog
29 from EricGui.EricOverrideCursor import EricOverrideCursorProcess 31 from EricGui.EricOverrideCursor import EricOverrideCursorProcess
36 from .GitDiffGenerator import GitDiffGenerator 38 from .GitDiffGenerator import GitDiffGenerator
37 39
38 import UI.PixmapCache 40 import UI.PixmapCache
39 import Preferences 41 import Preferences
40 42
41 COLORNAMES = ["red", "green", "purple", "cyan", "olive", "magenta", 43 COLORNAMES = [
42 "gray", "yellow", "darkred", "darkgreen", "darkblue", 44 "red",
43 "darkcyan", "darkmagenta", "blue"] 45 "green",
46 "purple",
47 "cyan",
48 "olive",
49 "magenta",
50 "gray",
51 "yellow",
52 "darkred",
53 "darkgreen",
54 "darkblue",
55 "darkcyan",
56 "darkmagenta",
57 "blue",
58 ]
44 COLORS = [str(QColor(x).name()) for x in COLORNAMES] 59 COLORS = [str(QColor(x).name()) for x in COLORNAMES]
45 60
46 LIGHTCOLORS = ["#aaaaff", "#7faa7f", "#ffaaaa", "#aaffaa", "#7f7faa", 61 LIGHTCOLORS = [
47 "#ffaaff", "#aaffff", "#d5d579", "#ffaaff", "#d57979", 62 "#aaaaff",
48 "#d579d5", "#79d5d5", "#d5d5d5", "#d5d500", 63 "#7faa7f",
49 ] 64 "#ffaaaa",
65 "#aaffaa",
66 "#7f7faa",
67 "#ffaaff",
68 "#aaffff",
69 "#d5d579",
70 "#ffaaff",
71 "#d57979",
72 "#d579d5",
73 "#79d5d5",
74 "#d5d5d5",
75 "#d5d500",
76 ]
50 77
51 78
52 class GitLogBrowserDialog(QWidget, Ui_GitLogBrowserDialog): 79 class GitLogBrowserDialog(QWidget, Ui_GitLogBrowserDialog):
53 """ 80 """
54 Class implementing a dialog to browse the log history. 81 Class implementing a dialog to browse the log history.
55 """ 82 """
83
56 IconColumn = 0 84 IconColumn = 0
57 CommitIdColumn = 1 85 CommitIdColumn = 1
58 AuthorColumn = 2 86 AuthorColumn = 2
59 DateColumn = 3 87 DateColumn = 3
60 CommitterColumn = 4 88 CommitterColumn = 4
61 CommitDateColumn = 5 89 CommitDateColumn = 5
62 SubjectColumn = 6 90 SubjectColumn = 6
63 BranchColumn = 7 91 BranchColumn = 7
64 TagsColumn = 8 92 TagsColumn = 8
65 93
66 def __init__(self, vcs, parent=None): 94 def __init__(self, vcs, parent=None):
67 """ 95 """
68 Constructor 96 Constructor
69 97
70 @param vcs reference to the vcs object 98 @param vcs reference to the vcs object
71 @param parent parent widget (QWidget) 99 @param parent parent widget (QWidget)
72 """ 100 """
73 super().__init__(parent) 101 super().__init__(parent)
74 self.setupUi(self) 102 self.setupUi(self)
75 103
76 windowFlags = self.windowFlags() 104 windowFlags = self.windowFlags()
77 windowFlags |= Qt.WindowType.WindowContextHelpButtonHint 105 windowFlags |= Qt.WindowType.WindowContextHelpButtonHint
78 self.setWindowFlags(windowFlags) 106 self.setWindowFlags(windowFlags)
79 107
80 self.mainSplitter.setSizes([300, 400]) 108 self.mainSplitter.setSizes([300, 400])
81 self.mainSplitter.setStretchFactor(0, 1) 109 self.mainSplitter.setStretchFactor(0, 1)
82 self.mainSplitter.setStretchFactor(1, 2) 110 self.mainSplitter.setStretchFactor(1, 2)
83 self.diffSplitter.setStretchFactor(0, 1) 111 self.diffSplitter.setStretchFactor(0, 1)
84 self.diffSplitter.setStretchFactor(1, 2) 112 self.diffSplitter.setStretchFactor(1, 2)
85 113
86 self.buttonBox.button( 114 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
87 QDialogButtonBox.StandardButton.Close).setEnabled(False) 115 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
88 self.buttonBox.button( 116
89 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
90
91 self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") 117 self.filesTree.headerItem().setText(self.filesTree.columnCount(), "")
92 self.filesTree.header().setSortIndicator( 118 self.filesTree.header().setSortIndicator(1, Qt.SortOrder.AscendingOrder)
93 1, Qt.SortOrder.AscendingOrder) 119
94
95 self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow")) 120 self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow"))
96 self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow")) 121 self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow"))
97 122
98 self.refreshButton = self.buttonBox.addButton( 123 self.refreshButton = self.buttonBox.addButton(
99 self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole) 124 self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole
100 self.refreshButton.setToolTip( 125 )
101 self.tr("Press to refresh the list of commits")) 126 self.refreshButton.setToolTip(self.tr("Press to refresh the list of commits"))
102 self.refreshButton.setEnabled(False) 127 self.refreshButton.setEnabled(False)
103 128
104 self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow")) 129 self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow"))
105 self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow")) 130 self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow"))
106 self.__findBackwards = False 131 self.__findBackwards = False
107 132
108 self.modeComboBox.addItem(self.tr("Find"), "find") 133 self.modeComboBox.addItem(self.tr("Find"), "find")
109 self.modeComboBox.addItem(self.tr("Filter"), "filter") 134 self.modeComboBox.addItem(self.tr("Filter"), "filter")
110 135
111 self.fieldCombo.addItem(self.tr("Commit ID"), "commitId") 136 self.fieldCombo.addItem(self.tr("Commit ID"), "commitId")
112 self.fieldCombo.addItem(self.tr("Author"), "author") 137 self.fieldCombo.addItem(self.tr("Author"), "author")
113 self.fieldCombo.addItem(self.tr("Committer"), "committer") 138 self.fieldCombo.addItem(self.tr("Committer"), "committer")
114 self.fieldCombo.addItem(self.tr("Subject"), "subject") 139 self.fieldCombo.addItem(self.tr("Subject"), "subject")
115 self.fieldCombo.addItem(self.tr("File"), "file") 140 self.fieldCombo.addItem(self.tr("File"), "file")
116 141
117 self.__logTreeNormalFont = self.logTree.font() 142 self.__logTreeNormalFont = self.logTree.font()
118 self.__logTreeNormalFont.setBold(False) 143 self.__logTreeNormalFont.setBold(False)
119 self.__logTreeBoldFont = self.logTree.font() 144 self.__logTreeBoldFont = self.logTree.font()
120 self.__logTreeBoldFont.setBold(True) 145 self.__logTreeBoldFont.setBold(True)
121 self.__logTreeHasDarkBackground = ericApp().usesDarkPalette() 146 self.__logTreeHasDarkBackground = ericApp().usesDarkPalette()
122 147
123 font = Preferences.getEditorOtherFonts("MonospacedFont") 148 font = Preferences.getEditorOtherFonts("MonospacedFont")
124 self.diffEdit.document().setDefaultFont(font) 149 self.diffEdit.document().setDefaultFont(font)
125 150
126 self.diffHighlighter = GitDiffHighlighter(self.diffEdit.document()) 151 self.diffHighlighter = GitDiffHighlighter(self.diffEdit.document())
127 self.__diffGenerator = GitDiffGenerator(vcs, self) 152 self.__diffGenerator = GitDiffGenerator(vcs, self)
128 self.__diffGenerator.finished.connect(self.__generatorFinished) 153 self.__diffGenerator.finished.connect(self.__generatorFinished)
129 154
130 self.vcs = vcs 155 self.vcs = vcs
131 156
132 self.__detailsTemplate = self.tr( 157 self.__detailsTemplate = self.tr(
133 "<table>" 158 "<table>"
134 "<tr><td><b>Commit ID</b></td><td>{0}</td></tr>" 159 "<tr><td><b>Commit ID</b></td><td>{0}</td></tr>"
135 "<tr><td><b>Date</b></td><td>{1}</td></tr>" 160 "<tr><td><b>Date</b></td><td>{1}</td></tr>"
136 "<tr><td><b>Author</b></td><td>{2} &lt;{3}&gt;</td></tr>" 161 "<tr><td><b>Author</b></td><td>{2} &lt;{3}&gt;</td></tr>"
139 "{7}" 164 "{7}"
140 "<tr><td><b>Subject</b></td><td>{8}</td></tr>" 165 "<tr><td><b>Subject</b></td><td>{8}</td></tr>"
141 "{9}" 166 "{9}"
142 "</table>" 167 "</table>"
143 ) 168 )
144 self.__parentsTemplate = self.tr( 169 self.__parentsTemplate = self.tr("<tr><td><b>Parents</b></td><td>{0}</td></tr>")
145 "<tr><td><b>Parents</b></td><td>{0}</td></tr>"
146 )
147 self.__childrenTemplate = self.tr( 170 self.__childrenTemplate = self.tr(
148 "<tr><td><b>Children</b></td><td>{0}</td></tr>" 171 "<tr><td><b>Children</b></td><td>{0}</td></tr>"
149 ) 172 )
150 self.__branchesTemplate = self.tr( 173 self.__branchesTemplate = self.tr(
151 "<tr><td><b>Branches</b></td><td>{0}</td></tr>" 174 "<tr><td><b>Branches</b></td><td>{0}</td></tr>"
152 ) 175 )
153 self.__tagsTemplate = self.tr( 176 self.__tagsTemplate = self.tr("<tr><td><b>Tags</b></td><td>{0}</td></tr>")
154 "<tr><td><b>Tags</b></td><td>{0}</td></tr>" 177 self.__mesageTemplate = self.tr("<tr><td><b>Message</b></td><td>{0}</td></tr>")
155 ) 178
156 self.__mesageTemplate = self.tr(
157 "<tr><td><b>Message</b></td><td>{0}</td></tr>"
158 )
159
160 self.__formatTemplate = ( 179 self.__formatTemplate = (
161 'format:recordstart%n' 180 "format:recordstart%n"
162 'commit|%h%n' 181 "commit|%h%n"
163 'parents|%p%n' 182 "parents|%p%n"
164 'author|%an%n' 183 "author|%an%n"
165 'authormail|%ae%n' 184 "authormail|%ae%n"
166 'authordate|%ai%n' 185 "authordate|%ai%n"
167 'committer|%cn%n' 186 "committer|%cn%n"
168 'committermail|%ce%n' 187 "committermail|%ce%n"
169 'committerdate|%ci%n' 188 "committerdate|%ci%n"
170 'refnames|%d%n' 189 "refnames|%d%n"
171 'subject|%s%n' 190 "subject|%s%n"
172 'bodystart%n' 191 "bodystart%n"
173 '%b%n' 192 "%b%n"
174 'bodyend%n' 193 "bodyend%n"
175 ) 194 )
176 195
177 self.__filename = "" 196 self.__filename = ""
178 self.__isFile = False 197 self.__isFile = False
179 self.__selectedCommitIDs = [] 198 self.__selectedCommitIDs = []
180 self.intercept = False 199 self.intercept = False
181 200
182 self.__initData() 201 self.__initData()
183 202
184 self.fromDate.setDisplayFormat("yyyy-MM-dd") 203 self.fromDate.setDisplayFormat("yyyy-MM-dd")
185 self.toDate.setDisplayFormat("yyyy-MM-dd") 204 self.toDate.setDisplayFormat("yyyy-MM-dd")
186 self.__resetUI() 205 self.__resetUI()
187 206
188 # roles used in the log tree 207 # roles used in the log tree
189 self.__subjectRole = Qt.ItemDataRole.UserRole 208 self.__subjectRole = Qt.ItemDataRole.UserRole
190 self.__messageRole = Qt.ItemDataRole.UserRole + 1 209 self.__messageRole = Qt.ItemDataRole.UserRole + 1
191 self.__changesRole = Qt.ItemDataRole.UserRole + 2 210 self.__changesRole = Qt.ItemDataRole.UserRole + 2
192 self.__edgesRole = Qt.ItemDataRole.UserRole + 3 211 self.__edgesRole = Qt.ItemDataRole.UserRole + 3
193 self.__parentsRole = Qt.ItemDataRole.UserRole + 4 212 self.__parentsRole = Qt.ItemDataRole.UserRole + 4
194 self.__branchesRole = Qt.ItemDataRole.UserRole + 5 213 self.__branchesRole = Qt.ItemDataRole.UserRole + 5
195 self.__authorMailRole = Qt.ItemDataRole.UserRole + 6 214 self.__authorMailRole = Qt.ItemDataRole.UserRole + 6
196 self.__committerMailRole = Qt.ItemDataRole.UserRole + 7 215 self.__committerMailRole = Qt.ItemDataRole.UserRole + 7
197 216
198 # roles used in the file tree 217 # roles used in the file tree
199 self.__diffFileLineRole = Qt.ItemDataRole.UserRole 218 self.__diffFileLineRole = Qt.ItemDataRole.UserRole
200 219
201 self.__process = EricOverrideCursorProcess() 220 self.__process = EricOverrideCursorProcess()
202 self.__process.finished.connect(self.__procFinished) 221 self.__process.finished.connect(self.__procFinished)
203 self.__process.readyReadStandardOutput.connect(self.__readStdout) 222 self.__process.readyReadStandardOutput.connect(self.__readStdout)
204 self.__process.readyReadStandardError.connect(self.__readStderr) 223 self.__process.readyReadStandardError.connect(self.__readStderr)
205 224
206 self.flags = { 225 self.flags = {
207 'A': self.tr('Added'), 226 "A": self.tr("Added"),
208 'D': self.tr('Deleted'), 227 "D": self.tr("Deleted"),
209 'M': self.tr('Modified'), 228 "M": self.tr("Modified"),
210 'C': self.tr('Copied'), 229 "C": self.tr("Copied"),
211 'R': self.tr('Renamed'), 230 "R": self.tr("Renamed"),
212 'T': self.tr('Type changed'), 231 "T": self.tr("Type changed"),
213 'U': self.tr('Unmerged'), 232 "U": self.tr("Unmerged"),
214 'X': self.tr('Unknown'), 233 "X": self.tr("Unknown"),
215 } 234 }
216 235
217 self.__dotRadius = 8 236 self.__dotRadius = 8
218 self.__rowHeight = 20 237 self.__rowHeight = 20
219 238
220 self.logTree.setIconSize( 239 self.logTree.setIconSize(QSize(100 * self.__rowHeight, self.__rowHeight))
221 QSize(100 * self.__rowHeight, self.__rowHeight)) 240
222
223 self.detailsEdit.anchorClicked.connect(self.__commitIdClicked) 241 self.detailsEdit.anchorClicked.connect(self.__commitIdClicked)
224 242
225 self.__initLogTreeContextMenu() 243 self.__initLogTreeContextMenu()
226 self.__initActionsMenu() 244 self.__initActionsMenu()
227 245
228 self.__finishCallbacks = [] 246 self.__finishCallbacks = []
229 247
230 def __addFinishCallback(self, callback): 248 def __addFinishCallback(self, callback):
231 """ 249 """
232 Private method to add a method to be called once the process finished. 250 Private method to add a method to be called once the process finished.
233 251
234 The callback methods are invoke in a FIFO style and are consumed. If 252 The callback methods are invoke in a FIFO style and are consumed. If
235 a callback method needs to be called again, it must be added again. 253 a callback method needs to be called again, it must be added again.
236 254
237 @param callback callback method 255 @param callback callback method
238 @type function 256 @type function
239 """ 257 """
240 if callback not in self.__finishCallbacks: 258 if callback not in self.__finishCallbacks:
241 self.__finishCallbacks.append(callback) 259 self.__finishCallbacks.append(callback)
242 260
243 def __initLogTreeContextMenu(self): 261 def __initLogTreeContextMenu(self):
244 """ 262 """
245 Private method to initialize the log tree context menu. 263 Private method to initialize the log tree context menu.
246 """ 264 """
247 self.__logTreeMenu = QMenu() 265 self.__logTreeMenu = QMenu()
248 266
249 # commit ID column 267 # commit ID column
250 act = self.__logTreeMenu.addAction( 268 act = self.__logTreeMenu.addAction(self.tr("Show Commit ID Column"))
251 self.tr("Show Commit ID Column")) 269 act.setToolTip(self.tr("Press to show the commit ID column"))
252 act.setToolTip(self.tr(
253 "Press to show the commit ID column"))
254 act.setCheckable(True) 270 act.setCheckable(True)
255 act.setChecked(self.vcs.getPlugin().getPreferences( 271 act.setChecked(self.vcs.getPlugin().getPreferences("ShowCommitIdColumn"))
256 "ShowCommitIdColumn"))
257 act.triggered.connect(self.__showCommitIdColumn) 272 act.triggered.connect(self.__showCommitIdColumn)
258 273
259 # author and date columns 274 # author and date columns
260 act = self.__logTreeMenu.addAction( 275 act = self.__logTreeMenu.addAction(self.tr("Show Author Columns"))
261 self.tr("Show Author Columns")) 276 act.setToolTip(self.tr("Press to show the author columns"))
262 act.setToolTip(self.tr(
263 "Press to show the author columns"))
264 act.setCheckable(True) 277 act.setCheckable(True)
265 act.setChecked(self.vcs.getPlugin().getPreferences( 278 act.setChecked(self.vcs.getPlugin().getPreferences("ShowAuthorColumns"))
266 "ShowAuthorColumns"))
267 act.triggered.connect(self.__showAuthorColumns) 279 act.triggered.connect(self.__showAuthorColumns)
268 280
269 # committer and commit date columns 281 # committer and commit date columns
270 act = self.__logTreeMenu.addAction( 282 act = self.__logTreeMenu.addAction(self.tr("Show Committer Columns"))
271 self.tr("Show Committer Columns")) 283 act.setToolTip(self.tr("Press to show the committer columns"))
272 act.setToolTip(self.tr(
273 "Press to show the committer columns"))
274 act.setCheckable(True) 284 act.setCheckable(True)
275 act.setChecked(self.vcs.getPlugin().getPreferences( 285 act.setChecked(self.vcs.getPlugin().getPreferences("ShowCommitterColumns"))
276 "ShowCommitterColumns"))
277 act.triggered.connect(self.__showCommitterColumns) 286 act.triggered.connect(self.__showCommitterColumns)
278 287
279 # branches column 288 # branches column
280 act = self.__logTreeMenu.addAction( 289 act = self.__logTreeMenu.addAction(self.tr("Show Branches Column"))
281 self.tr("Show Branches Column")) 290 act.setToolTip(self.tr("Press to show the branches column"))
282 act.setToolTip(self.tr(
283 "Press to show the branches column"))
284 act.setCheckable(True) 291 act.setCheckable(True)
285 act.setChecked(self.vcs.getPlugin().getPreferences( 292 act.setChecked(self.vcs.getPlugin().getPreferences("ShowBranchesColumn"))
286 "ShowBranchesColumn"))
287 act.triggered.connect(self.__showBranchesColumn) 293 act.triggered.connect(self.__showBranchesColumn)
288 294
289 # tags column 295 # tags column
290 act = self.__logTreeMenu.addAction( 296 act = self.__logTreeMenu.addAction(self.tr("Show Tags Column"))
291 self.tr("Show Tags Column")) 297 act.setToolTip(self.tr("Press to show the Tags column"))
292 act.setToolTip(self.tr(
293 "Press to show the Tags column"))
294 act.setCheckable(True) 298 act.setCheckable(True)
295 act.setChecked(self.vcs.getPlugin().getPreferences( 299 act.setChecked(self.vcs.getPlugin().getPreferences("ShowTagsColumn"))
296 "ShowTagsColumn"))
297 act.triggered.connect(self.__showTagsColumn) 300 act.triggered.connect(self.__showTagsColumn)
298 301
299 # set column visibility as configured 302 # set column visibility as configured
300 self.__showCommitIdColumn(self.vcs.getPlugin().getPreferences( 303 self.__showCommitIdColumn(
301 "ShowCommitIdColumn")) 304 self.vcs.getPlugin().getPreferences("ShowCommitIdColumn")
302 self.__showAuthorColumns(self.vcs.getPlugin().getPreferences( 305 )
303 "ShowAuthorColumns")) 306 self.__showAuthorColumns(
304 self.__showCommitterColumns(self.vcs.getPlugin().getPreferences( 307 self.vcs.getPlugin().getPreferences("ShowAuthorColumns")
305 "ShowCommitterColumns")) 308 )
306 self.__showBranchesColumn(self.vcs.getPlugin().getPreferences( 309 self.__showCommitterColumns(
307 "ShowBranchesColumn")) 310 self.vcs.getPlugin().getPreferences("ShowCommitterColumns")
308 self.__showTagsColumn(self.vcs.getPlugin().getPreferences( 311 )
309 "ShowTagsColumn")) 312 self.__showBranchesColumn(
310 313 self.vcs.getPlugin().getPreferences("ShowBranchesColumn")
314 )
315 self.__showTagsColumn(self.vcs.getPlugin().getPreferences("ShowTagsColumn"))
316
311 def __initActionsMenu(self): 317 def __initActionsMenu(self):
312 """ 318 """
313 Private method to initialize the actions menu. 319 Private method to initialize the actions menu.
314 """ 320 """
315 self.__actionsMenu = QMenu() 321 self.__actionsMenu = QMenu()
316 self.__actionsMenu.setTearOffEnabled(True) 322 self.__actionsMenu.setTearOffEnabled(True)
317 self.__actionsMenu.setToolTipsVisible(True) 323 self.__actionsMenu.setToolTipsVisible(True)
318 324
319 self.__cherryAct = self.__actionsMenu.addAction( 325 self.__cherryAct = self.__actionsMenu.addAction(
320 self.tr("Copy Commits"), self.__cherryActTriggered) 326 self.tr("Copy Commits"), self.__cherryActTriggered
321 self.__cherryAct.setToolTip(self.tr( 327 )
322 "Cherry-pick the selected commits to the current branch")) 328 self.__cherryAct.setToolTip(
323 329 self.tr("Cherry-pick the selected commits to the current branch")
330 )
331
324 self.__actionsMenu.addSeparator() 332 self.__actionsMenu.addSeparator()
325 333
326 self.__tagAct = self.__actionsMenu.addAction( 334 self.__tagAct = self.__actionsMenu.addAction(
327 self.tr("Tag"), self.__tagActTriggered) 335 self.tr("Tag"), self.__tagActTriggered
336 )
328 self.__tagAct.setToolTip(self.tr("Tag the selected commit")) 337 self.__tagAct.setToolTip(self.tr("Tag the selected commit"))
329 338
330 self.__branchAct = self.__actionsMenu.addAction( 339 self.__branchAct = self.__actionsMenu.addAction(
331 self.tr("Branch"), self.__branchActTriggered) 340 self.tr("Branch"), self.__branchActTriggered
332 self.__branchAct.setToolTip(self.tr( 341 )
333 "Create a new branch at the selected commit.")) 342 self.__branchAct.setToolTip(
343 self.tr("Create a new branch at the selected commit.")
344 )
334 self.__branchSwitchAct = self.__actionsMenu.addAction( 345 self.__branchSwitchAct = self.__actionsMenu.addAction(
335 self.tr("Branch && Switch"), self.__branchSwitchActTriggered) 346 self.tr("Branch && Switch"), self.__branchSwitchActTriggered
336 self.__branchSwitchAct.setToolTip(self.tr( 347 )
337 "Create a new branch at the selected commit and switch" 348 self.__branchSwitchAct.setToolTip(
338 " the work tree to it.")) 349 self.tr(
339 350 "Create a new branch at the selected commit and switch"
351 " the work tree to it."
352 )
353 )
354
340 self.__switchAct = self.__actionsMenu.addAction( 355 self.__switchAct = self.__actionsMenu.addAction(
341 self.tr("Switch"), self.__switchActTriggered) 356 self.tr("Switch"), self.__switchActTriggered
342 self.__switchAct.setToolTip(self.tr( 357 )
343 "Switch the working directory to the selected commit")) 358 self.__switchAct.setToolTip(
359 self.tr("Switch the working directory to the selected commit")
360 )
344 self.__actionsMenu.addSeparator() 361 self.__actionsMenu.addSeparator()
345 362
346 self.__shortlogAct = self.__actionsMenu.addAction( 363 self.__shortlogAct = self.__actionsMenu.addAction(
347 self.tr("Show Short Log"), self.__shortlogActTriggered) 364 self.tr("Show Short Log"), self.__shortlogActTriggered
348 self.__shortlogAct.setToolTip(self.tr( 365 )
349 "Show a dialog with a log output for release notes")) 366 self.__shortlogAct.setToolTip(
350 367 self.tr("Show a dialog with a log output for release notes")
368 )
369
351 self.__describeAct = self.__actionsMenu.addAction( 370 self.__describeAct = self.__actionsMenu.addAction(
352 self.tr("Describe"), self.__describeActTriggered) 371 self.tr("Describe"), self.__describeActTriggered
353 self.__describeAct.setToolTip(self.tr( 372 )
354 "Show the most recent tag reachable from a commit")) 373 self.__describeAct.setToolTip(
355 374 self.tr("Show the most recent tag reachable from a commit")
356 self.actionsButton.setIcon( 375 )
357 UI.PixmapCache.getIcon("actionsToolButton")) 376
377 self.actionsButton.setIcon(UI.PixmapCache.getIcon("actionsToolButton"))
358 self.actionsButton.setMenu(self.__actionsMenu) 378 self.actionsButton.setMenu(self.__actionsMenu)
359 379
360 def __initData(self): 380 def __initData(self):
361 """ 381 """
362 Private method to (re-)initialize some data. 382 Private method to (re-)initialize some data.
363 """ 383 """
364 self.__maxDate = QDate() 384 self.__maxDate = QDate()
365 self.__minDate = QDate() 385 self.__minDate = QDate()
366 self.__filterLogsEnabled = True 386 self.__filterLogsEnabled = True
367 387
368 self.buf = [] # buffer for stdout 388 self.buf = [] # buffer for stdout
369 self.diff = None 389 self.diff = None
370 self.__started = False 390 self.__started = False
371 self.__skipEntries = 0 391 self.__skipEntries = 0
372 self.projectMode = False 392 self.projectMode = False
373 393
374 # attributes to store log graph data 394 # attributes to store log graph data
375 self.__commitIds = [] 395 self.__commitIds = []
376 self.__commitColors = {} 396 self.__commitColors = {}
377 self.__commitColor = 0 397 self.__commitColor = 0
378 398
379 self.__projectRevision = "" 399 self.__projectRevision = ""
380 400
381 self.__childrenInfo = collections.defaultdict(list) 401 self.__childrenInfo = collections.defaultdict(list)
382 402
383 def closeEvent(self, e): 403 def closeEvent(self, e):
384 """ 404 """
385 Protected slot implementing a close event handler. 405 Protected slot implementing a close event handler.
386 406
387 @param e close event (QCloseEvent) 407 @param e close event (QCloseEvent)
388 """ 408 """
389 if ( 409 if (
390 self.__process is not None and 410 self.__process is not None
391 self.__process.state() != QProcess.ProcessState.NotRunning 411 and self.__process.state() != QProcess.ProcessState.NotRunning
392 ): 412 ):
393 self.__process.terminate() 413 self.__process.terminate()
394 QTimer.singleShot(2000, self.__process.kill) 414 QTimer.singleShot(2000, self.__process.kill)
395 self.__process.waitForFinished(3000) 415 self.__process.waitForFinished(3000)
396 416
417 self.vcs.getPlugin().setPreferences("LogBrowserGeometry", self.saveGeometry())
397 self.vcs.getPlugin().setPreferences( 418 self.vcs.getPlugin().setPreferences(
398 "LogBrowserGeometry", self.saveGeometry()) 419 "LogBrowserSplitterStates",
399 self.vcs.getPlugin().setPreferences( 420 [
400 "LogBrowserSplitterStates", [
401 self.mainSplitter.saveState(), 421 self.mainSplitter.saveState(),
402 self.detailsSplitter.saveState(), 422 self.detailsSplitter.saveState(),
403 self.diffSplitter.saveState(), 423 self.diffSplitter.saveState(),
404 ] 424 ],
405 ) 425 )
406 426
407 e.accept() 427 e.accept()
408 428
409 def show(self): 429 def show(self):
410 """ 430 """
411 Public slot to show the dialog. 431 Public slot to show the dialog.
412 """ 432 """
413 self.__reloadGeometry() 433 self.__reloadGeometry()
414 self.__restoreSplitterStates() 434 self.__restoreSplitterStates()
415 self.__resetUI() 435 self.__resetUI()
416 436
417 super().show() 437 super().show()
418 438
419 def __reloadGeometry(self): 439 def __reloadGeometry(self):
420 """ 440 """
421 Private method to restore the geometry. 441 Private method to restore the geometry.
422 """ 442 """
423 geom = self.vcs.getPlugin().getPreferences("LogBrowserGeometry") 443 geom = self.vcs.getPlugin().getPreferences("LogBrowserGeometry")
424 if geom.isEmpty(): 444 if geom.isEmpty():
425 s = QSize(1000, 800) 445 s = QSize(1000, 800)
426 self.resize(s) 446 self.resize(s)
427 else: 447 else:
428 self.restoreGeometry(geom) 448 self.restoreGeometry(geom)
429 449
430 def __restoreSplitterStates(self): 450 def __restoreSplitterStates(self):
431 """ 451 """
432 Private method to restore the state of the various splitters. 452 Private method to restore the state of the various splitters.
433 """ 453 """
434 states = self.vcs.getPlugin().getPreferences( 454 states = self.vcs.getPlugin().getPreferences("LogBrowserSplitterStates")
435 "LogBrowserSplitterStates")
436 if len(states) == 3: 455 if len(states) == 3:
437 # we have three splitters 456 # we have three splitters
438 self.mainSplitter.restoreState(states[0]) 457 self.mainSplitter.restoreState(states[0])
439 self.detailsSplitter.restoreState(states[1]) 458 self.detailsSplitter.restoreState(states[1])
440 self.diffSplitter.restoreState(states[2]) 459 self.diffSplitter.restoreState(states[2])
441 460
442 def __resetUI(self): 461 def __resetUI(self):
443 """ 462 """
444 Private method to reset the user interface. 463 Private method to reset the user interface.
445 """ 464 """
446 self.fromDate.setDate(QDate.currentDate()) 465 self.fromDate.setDate(QDate.currentDate())
447 self.toDate.setDate(QDate.currentDate()) 466 self.toDate.setDate(QDate.currentDate())
448 self.fieldCombo.setCurrentIndex(self.fieldCombo.findData("subject")) 467 self.fieldCombo.setCurrentIndex(self.fieldCombo.findData("subject"))
449 self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences( 468 self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences("LogLimit"))
450 "LogLimit")) 469 self.stopCheckBox.setChecked(
451 self.stopCheckBox.setChecked(self.vcs.getPlugin().getPreferences( 470 self.vcs.getPlugin().getPreferences("StopLogOnCopy")
452 "StopLogOnCopy")) 471 )
453 472
454 self.logTree.clear() 473 self.logTree.clear()
455 474
456 def __resizeColumnsLog(self): 475 def __resizeColumnsLog(self):
457 """ 476 """
458 Private method to resize the log tree columns. 477 Private method to resize the log tree columns.
459 """ 478 """
460 self.logTree.header().resizeSections( 479 self.logTree.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents)
461 QHeaderView.ResizeMode.ResizeToContents)
462 self.logTree.header().setStretchLastSection(True) 480 self.logTree.header().setStretchLastSection(True)
463 481
464 def __resizeColumnsFiles(self): 482 def __resizeColumnsFiles(self):
465 """ 483 """
466 Private method to resize the changed files tree columns. 484 Private method to resize the changed files tree columns.
467 """ 485 """
468 self.filesTree.header().resizeSections( 486 self.filesTree.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents)
469 QHeaderView.ResizeMode.ResizeToContents)
470 self.filesTree.header().setStretchLastSection(True) 487 self.filesTree.header().setStretchLastSection(True)
471 488
472 def __resortFiles(self): 489 def __resortFiles(self):
473 """ 490 """
474 Private method to resort the changed files tree. 491 Private method to resort the changed files tree.
475 """ 492 """
476 self.filesTree.setSortingEnabled(True) 493 self.filesTree.setSortingEnabled(True)
477 self.filesTree.sortItems(1, Qt.SortOrder.AscendingOrder) 494 self.filesTree.sortItems(1, Qt.SortOrder.AscendingOrder)
478 self.filesTree.setSortingEnabled(False) 495 self.filesTree.setSortingEnabled(False)
479 496
480 def __getColor(self, n): 497 def __getColor(self, n):
481 """ 498 """
482 Private method to get the (rotating) name of the color given an index. 499 Private method to get the (rotating) name of the color given an index.
483 500
484 @param n color index 501 @param n color index
485 @type int 502 @type int
486 @return color name 503 @return color name
487 @rtype str 504 @rtype str
488 """ 505 """
489 if self.__logTreeHasDarkBackground: 506 if self.__logTreeHasDarkBackground:
490 return LIGHTCOLORS[n % len(LIGHTCOLORS)] 507 return LIGHTCOLORS[n % len(LIGHTCOLORS)]
491 else: 508 else:
492 return COLORS[n % len(COLORS)] 509 return COLORS[n % len(COLORS)]
493 510
494 def __generateEdges(self, commitId, parents): 511 def __generateEdges(self, commitId, parents):
495 """ 512 """
496 Private method to generate edge info for the give data. 513 Private method to generate edge info for the give data.
497 514
498 @param commitId commit id to calculate edge info for (string) 515 @param commitId commit id to calculate edge info for (string)
499 @param parents list of parent commits (list of strings) 516 @param parents list of parent commits (list of strings)
500 @return tuple containing the column and color index for 517 @return tuple containing the column and color index for
501 the given node and a list of tuples indicating the edges 518 the given node and a list of tuples indicating the edges
502 between the given node and its parents 519 between the given node and its parents
505 if commitId not in self.__commitIds: 522 if commitId not in self.__commitIds:
506 # new head 523 # new head
507 self.__commitIds.append(commitId) 524 self.__commitIds.append(commitId)
508 self.__commitColors[commitId] = self.__commitColor 525 self.__commitColors[commitId] = self.__commitColor
509 self.__commitColor += 1 526 self.__commitColor += 1
510 527
511 col = self.__commitIds.index(commitId) 528 col = self.__commitIds.index(commitId)
512 color = self.__commitColors.pop(commitId) 529 color = self.__commitColors.pop(commitId)
513 nextCommitIds = self.__commitIds[:] 530 nextCommitIds = self.__commitIds[:]
514 531
515 # add parents to next 532 # add parents to next
516 addparents = [p for p in parents if p not in nextCommitIds] 533 addparents = [p for p in parents if p not in nextCommitIds]
517 nextCommitIds[col:col + 1] = addparents 534 nextCommitIds[col : col + 1] = addparents
518 535
519 # set colors for the parents 536 # set colors for the parents
520 for i, p in enumerate(addparents): 537 for i, p in enumerate(addparents):
521 if not i: 538 if not i:
522 self.__commitColors[p] = color 539 self.__commitColors[p] = color
523 else: 540 else:
524 self.__commitColors[p] = self.__commitColor 541 self.__commitColors[p] = self.__commitColor
525 self.__commitColor += 1 542 self.__commitColor += 1
526 543
527 # add edges to the graph 544 # add edges to the graph
528 edges = [] 545 edges = []
529 if parents: 546 if parents:
530 for ecol, ecommitId in enumerate(self.__commitIds): 547 for ecol, ecommitId in enumerate(self.__commitIds):
531 if ecommitId in nextCommitIds: 548 if ecommitId in nextCommitIds:
532 edges.append( 549 edges.append(
533 (ecol, nextCommitIds.index(ecommitId), 550 (
534 self.__commitColors[ecommitId])) 551 ecol,
552 nextCommitIds.index(ecommitId),
553 self.__commitColors[ecommitId],
554 )
555 )
535 elif ecommitId == commitId: 556 elif ecommitId == commitId:
536 for p in parents: 557 for p in parents:
537 edges.append( 558 edges.append(
538 (ecol, nextCommitIds.index(p), 559 (ecol, nextCommitIds.index(p), self.__commitColors[p])
539 self.__commitColors[p])) 560 )
540 561
541 self.__commitIds = nextCommitIds 562 self.__commitIds = nextCommitIds
542 return col, color, edges 563 return col, color, edges
543 564
544 def __generateIcon(self, column, color, bottomedges, topedges, dotColor, 565 def __generateIcon(
545 currentCommit): 566 self, column, color, bottomedges, topedges, dotColor, currentCommit
567 ):
546 """ 568 """
547 Private method to generate an icon containing the revision tree for the 569 Private method to generate an icon containing the revision tree for the
548 given data. 570 given data.
549 571
550 @param column column index of the revision (integer) 572 @param column column index of the revision (integer)
551 @param color color of the node (integer) 573 @param color color of the node (integer)
552 @param bottomedges list of edges for the bottom of the node 574 @param bottomedges list of edges for the bottom of the node
553 (list of tuples of three integers) 575 (list of tuples of three integers)
554 @param topedges list of edges for the top of the node 576 @param topedges list of edges for the top of the node
556 @param dotColor color to be used for the dot (QColor) 578 @param dotColor color to be used for the dot (QColor)
557 @param currentCommit flag indicating to draw the icon for the 579 @param currentCommit flag indicating to draw the icon for the
558 current commit (boolean) 580 current commit (boolean)
559 @return icon for the node (QIcon) 581 @return icon for the node (QIcon)
560 """ 582 """
583
561 def col2x(col, radius): 584 def col2x(col, radius):
562 """ 585 """
563 Local function to calculate a x-position for a column. 586 Local function to calculate a x-position for a column.
564 587
565 @param col column number (integer) 588 @param col column number (integer)
566 @param radius radius of the indicator circle (integer) 589 @param radius radius of the indicator circle (integer)
567 """ 590 """
568 return int(1.2 * radius) * col + radius // 2 + 3 591 return int(1.2 * radius) * col + radius // 2 + 3
569 592
570 radius = self.__dotRadius 593 radius = self.__dotRadius
571 w = len(bottomedges) * radius + 20 594 w = len(bottomedges) * radius + 20
572 h = self.__rowHeight 595 h = self.__rowHeight
573 596
574 dot_x = col2x(column, radius) - radius // 2 597 dot_x = col2x(column, radius) - radius // 2
575 dot_y = h // 2 598 dot_y = h // 2
576 599
577 pix = QPixmap(w, h) 600 pix = QPixmap(w, h)
578 pix.fill(QColor(0, 0, 0, 0)) # draw transparent background 601 pix.fill(QColor(0, 0, 0, 0)) # draw transparent background
579 painter = QPainter(pix) 602 painter = QPainter(pix)
580 painter.setRenderHint(QPainter.RenderHint.Antialiasing) 603 painter.setRenderHint(QPainter.RenderHint.Antialiasing)
581 604
582 # draw the revision history lines 605 # draw the revision history lines
583 for y1, y2, lines in ((0, h, bottomedges), 606 for y1, y2, lines in ((0, h, bottomedges), (-h, 0, topedges)):
584 (-h, 0, topedges)):
585 if lines: 607 if lines:
586 for start, end, ecolor in lines: 608 for start, end, ecolor in lines:
587 lpen = QPen(QColor(self.__getColor(ecolor))) 609 lpen = QPen(QColor(self.__getColor(ecolor)))
588 lpen.setWidth(2) 610 lpen.setWidth(2)
589 painter.setPen(lpen) 611 painter.setPen(lpen)
590 x1 = col2x(start, radius) 612 x1 = col2x(start, radius)
591 x2 = col2x(end, radius) 613 x2 = col2x(end, radius)
592 painter.drawLine(x1, dot_y + y1, x2, dot_y + y2) 614 painter.drawLine(x1, dot_y + y1, x2, dot_y + y2)
593 615
594 penradius = 1 616 penradius = 1
595 pencolor = self.logTree.palette().color(QPalette.ColorRole.Text) 617 pencolor = self.logTree.palette().color(QPalette.ColorRole.Text)
596 618
597 dot_y = (h // 2) - radius // 2 619 dot_y = (h // 2) - radius // 2
598 620
599 # draw a dot for the revision 621 # draw a dot for the revision
600 if currentCommit: 622 if currentCommit:
601 # enlarge dot for the current revision 623 # enlarge dot for the current revision
602 delta = 2 624 delta = 2
603 radius += 2 * delta 625 radius += 2 * delta
608 pen.setWidth(penradius) 630 pen.setWidth(penradius)
609 painter.setPen(pen) 631 painter.setPen(pen)
610 painter.drawEllipse(dot_x, dot_y, radius, radius) 632 painter.drawEllipse(dot_x, dot_y, radius, radius)
611 painter.end() 633 painter.end()
612 return QIcon(pix) 634 return QIcon(pix)
613 635
614 def __identifyProject(self): 636 def __identifyProject(self):
615 """ 637 """
616 Private method to determine the revision of the project directory. 638 Private method to determine the revision of the project directory.
617 """ 639 """
618 errMsg = "" 640 errMsg = ""
619 641
620 args = self.vcs.initCommand("show") 642 args = self.vcs.initCommand("show")
621 args.append("--abbrev={0}".format( 643 args.append(
622 self.vcs.getPlugin().getPreferences("CommitIdLength"))) 644 "--abbrev={0}".format(self.vcs.getPlugin().getPreferences("CommitIdLength"))
645 )
623 args.append("--format=%h") 646 args.append("--format=%h")
624 args.append("--no-patch") 647 args.append("--no-patch")
625 args.append("HEAD") 648 args.append("HEAD")
626 649
627 output = "" 650 output = ""
628 process = QProcess() 651 process = QProcess()
629 process.setWorkingDirectory(self.repodir) 652 process.setWorkingDirectory(self.repodir)
630 process.start('git', args) 653 process.start("git", args)
631 procStarted = process.waitForStarted(5000) 654 procStarted = process.waitForStarted(5000)
632 if procStarted: 655 if procStarted:
633 finished = process.waitForFinished(30000) 656 finished = process.waitForFinished(30000)
634 if finished and process.exitCode() == 0: 657 if finished and process.exitCode() == 0:
635 output = str(process.readAllStandardOutput(), 658 output = str(
636 Preferences.getSystem("IOEncoding"), 659 process.readAllStandardOutput(),
637 'replace') 660 Preferences.getSystem("IOEncoding"),
661 "replace",
662 )
638 else: 663 else:
639 if not finished: 664 if not finished:
640 errMsg = self.tr( 665 errMsg = self.tr("The git process did not finish within 30s.")
641 "The git process did not finish within 30s.")
642 else: 666 else:
643 errMsg = self.tr("Could not start the git executable.") 667 errMsg = self.tr("Could not start the git executable.")
644 668
645 if errMsg: 669 if errMsg:
646 EricMessageBox.critical( 670 EricMessageBox.critical(self, self.tr("Git Error"), errMsg)
647 self, 671
648 self.tr("Git Error"),
649 errMsg)
650
651 if output: 672 if output:
652 self.__projectRevision = output.strip() 673 self.__projectRevision = output.strip()
653 674
654 def __generateLogItem(self, author, date, committer, commitDate, subject, 675 def __generateLogItem(
655 message, commitId, changedPaths, parents, refnames, 676 self,
656 authorMail, committerMail): 677 author,
678 date,
679 committer,
680 commitDate,
681 subject,
682 message,
683 commitId,
684 changedPaths,
685 parents,
686 refnames,
687 authorMail,
688 committerMail,
689 ):
657 """ 690 """
658 Private method to generate a log tree entry. 691 Private method to generate a log tree entry.
659 692
660 @param author author info (string) 693 @param author author info (string)
661 @param date date info (string) 694 @param date date info (string)
662 @param committer committer info (string) 695 @param committer committer info (string)
663 @param commitDate commit date info (string) 696 @param commitDate commit date info (string)
664 @param subject subject of the log entry (string) 697 @param subject subject of the log entry (string)
690 bname = name.replace("refs/", "").split("-", 1)[0] 723 bname = name.replace("refs/", "").split("-", 1)[0]
691 branches.append(bname) 724 branches.append(bname)
692 else: 725 else:
693 branches.append(name) 726 branches.append(name)
694 allBranches.append(name) 727 allBranches.append(name)
695 728
696 logMessageColumnWidth = self.vcs.getPlugin().getPreferences( 729 logMessageColumnWidth = self.vcs.getPlugin().getPreferences(
697 "LogSubjectColumnWidth") 730 "LogSubjectColumnWidth"
731 )
698 msgtxt = subject 732 msgtxt = subject
699 if logMessageColumnWidth and len(msgtxt) > logMessageColumnWidth: 733 if logMessageColumnWidth and len(msgtxt) > logMessageColumnWidth:
700 msgtxt = "{0}...".format(msgtxt[:logMessageColumnWidth]) 734 msgtxt = "{0}...".format(msgtxt[:logMessageColumnWidth])
701 columnLabels = [ 735 columnLabels = [
702 "", 736 "",
708 msgtxt, 742 msgtxt,
709 ", ".join(branches), 743 ", ".join(branches),
710 ", ".join(tags), 744 ", ".join(tags),
711 ] 745 ]
712 itm = QTreeWidgetItem(self.logTree, columnLabels) 746 itm = QTreeWidgetItem(self.logTree, columnLabels)
713 747
714 parents = [p.strip() for p in parents.split()] 748 parents = [p.strip() for p in parents.split()]
715 column, color, edges = self.__generateEdges(commitId, parents) 749 column, color, edges = self.__generateEdges(commitId, parents)
716 750
717 itm.setData(0, self.__subjectRole, subject) 751 itm.setData(0, self.__subjectRole, subject)
718 itm.setData(0, self.__messageRole, message) 752 itm.setData(0, self.__messageRole, message)
719 itm.setData(0, self.__changesRole, changedPaths) 753 itm.setData(0, self.__changesRole, changedPaths)
720 itm.setData(0, self.__edgesRole, edges) 754 itm.setData(0, self.__edgesRole, edges)
721 itm.setData(0, self.__branchesRole, allBranches) 755 itm.setData(0, self.__branchesRole, allBranches)
725 itm.setData(0, self.__parentsRole, []) 759 itm.setData(0, self.__parentsRole, [])
726 else: 760 else:
727 itm.setData(0, self.__parentsRole, parents) 761 itm.setData(0, self.__parentsRole, parents)
728 for parent in parents: 762 for parent in parents:
729 self.__childrenInfo[parent].append(commitId) 763 self.__childrenInfo[parent].append(commitId)
730 764
731 topedges = ( 765 topedges = (
732 self.logTree.topLevelItem( 766 self.logTree.topLevelItem(self.logTree.indexOfTopLevelItem(itm) - 1).data(
733 self.logTree.indexOfTopLevelItem(itm) - 1 767 0, self.__edgesRole
734 ).data(0, self.__edgesRole) 768 )
735 if self.logTree.topLevelItemCount() > 1 else 769 if self.logTree.topLevelItemCount() > 1
736 None 770 else None
737 ) 771 )
738 772
739 icon = self.__generateIcon(column, color, edges, topedges, 773 icon = self.__generateIcon(
740 QColor("blue"), 774 column,
741 commitId == self.__projectRevision) 775 color,
776 edges,
777 topedges,
778 QColor("blue"),
779 commitId == self.__projectRevision,
780 )
742 itm.setIcon(0, icon) 781 itm.setIcon(0, icon)
743 782
744 return itm 783 return itm
745 784
746 def __generateFileItem(self, action, path, copyfrom, additions, deletions): 785 def __generateFileItem(self, action, path, copyfrom, additions, deletions):
747 """ 786 """
748 Private method to generate a changed files tree entry. 787 Private method to generate a changed files tree entry.
749 788
750 @param action indicator for the change action ("A", "C", "D", "M", 789 @param action indicator for the change action ("A", "C", "D", "M",
751 "R", "T", "U", "X") 790 "R", "T", "U", "X")
752 @param path path of the file in the repository (string) 791 @param path path of the file in the repository (string)
753 @param copyfrom path the file was copied from (string) 792 @param copyfrom path the file was copied from (string)
754 @param additions number of added lines (int) 793 @param additions number of added lines (int)
757 """ 796 """
758 if len(action) > 1: 797 if len(action) > 1:
759 # includes confidence level 798 # includes confidence level
760 confidence = int(action[1:]) 799 confidence = int(action[1:])
761 actionTxt = self.tr("{0} ({1}%)", "action, confidence").format( 800 actionTxt = self.tr("{0} ({1}%)", "action, confidence").format(
762 self.flags[action[0]], confidence) 801 self.flags[action[0]], confidence
802 )
763 else: 803 else:
764 actionTxt = self.flags[action] 804 actionTxt = self.flags[action]
765 itm = QTreeWidgetItem(self.filesTree, [ 805 itm = QTreeWidgetItem(
766 actionTxt, 806 self.filesTree,
767 path, 807 [
768 str(additions), 808 actionTxt,
769 str(deletions), 809 path,
770 copyfrom, 810 str(additions),
771 ]) 811 str(deletions),
772 812 copyfrom,
813 ],
814 )
815
773 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) 816 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight)
774 itm.setTextAlignment(3, Qt.AlignmentFlag.AlignRight) 817 itm.setTextAlignment(3, Qt.AlignmentFlag.AlignRight)
775 818
776 return itm 819 return itm
777 820
778 def __getLogEntries(self, skip=0, noEntries=0): 821 def __getLogEntries(self, skip=0, noEntries=0):
779 """ 822 """
780 Private method to retrieve log entries from the repository. 823 Private method to retrieve log entries from the repository.
781 824
782 @param skip number of log entries to skip (integer) 825 @param skip number of log entries to skip (integer)
783 @param noEntries number of entries to get (0 = default) (int) 826 @param noEntries number of entries to get (0 = default) (int)
784 """ 827 """
785 self.buttonBox.button( 828 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
786 QDialogButtonBox.StandardButton.Close).setEnabled(False) 829 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
787 self.buttonBox.button( 830 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
788 QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
789 self.buttonBox.button(
790 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
791 QApplication.processEvents() 831 QApplication.processEvents()
792 832
793 self.buf = [] 833 self.buf = []
794 self.cancelled = False 834 self.cancelled = False
795 self.errors.clear() 835 self.errors.clear()
796 self.intercept = False 836 self.intercept = False
797 837
798 if noEntries == 0: 838 if noEntries == 0:
799 noEntries = self.limitSpinBox.value() 839 noEntries = self.limitSpinBox.value()
800 840
801 args = self.vcs.initCommand("log") 841 args = self.vcs.initCommand("log")
802 args.append('--max-count={0}'.format(noEntries)) 842 args.append("--max-count={0}".format(noEntries))
803 args.append('--numstat') 843 args.append("--numstat")
804 args.append('--abbrev={0}'.format( 844 args.append(
805 self.vcs.getPlugin().getPreferences("CommitIdLength"))) 845 "--abbrev={0}".format(self.vcs.getPlugin().getPreferences("CommitIdLength"))
846 )
806 if self.vcs.getPlugin().getPreferences("FindCopiesHarder"): 847 if self.vcs.getPlugin().getPreferences("FindCopiesHarder"):
807 args.append('--find-copies-harder') 848 args.append("--find-copies-harder")
808 args.append('--format={0}'.format(self.__formatTemplate)) 849 args.append("--format={0}".format(self.__formatTemplate))
809 args.append('--full-history') 850 args.append("--full-history")
810 args.append('--all') 851 args.append("--all")
811 args.append('--skip={0}'.format(skip)) 852 args.append("--skip={0}".format(skip))
812 if not self.projectMode: 853 if not self.projectMode:
813 if not self.stopCheckBox.isChecked(): 854 if not self.stopCheckBox.isChecked():
814 args.append('--follow') 855 args.append("--follow")
815 args.append('--') 856 args.append("--")
816 args.append(self.__filename) 857 args.append(self.__filename)
817 858
818 self.__process.kill() 859 self.__process.kill()
819 860
820 self.__process.setWorkingDirectory(self.repodir) 861 self.__process.setWorkingDirectory(self.repodir)
821 862
822 self.__process.start('git', args) 863 self.__process.start("git", args)
823 procStarted = self.__process.waitForStarted(5000) 864 procStarted = self.__process.waitForStarted(5000)
824 if not procStarted: 865 if not procStarted:
825 self.inputGroup.setEnabled(False) 866 self.inputGroup.setEnabled(False)
826 self.inputGroup.hide() 867 self.inputGroup.hide()
827 EricMessageBox.critical( 868 EricMessageBox.critical(
828 self, 869 self,
829 self.tr('Process Generation Error'), 870 self.tr("Process Generation Error"),
830 self.tr( 871 self.tr(
831 'The process {0} could not be started. ' 872 "The process {0} could not be started. "
832 'Ensure, that it is in the search path.' 873 "Ensure, that it is in the search path."
833 ).format('git')) 874 ).format("git"),
834 875 )
876
835 def start(self, fn, isFile=False, noEntries=0): 877 def start(self, fn, isFile=False, noEntries=0):
836 """ 878 """
837 Public slot to start the git log command. 879 Public slot to start the git log command.
838 880
839 @param fn filename to show the log for (string) 881 @param fn filename to show the log for (string)
840 @param isFile flag indicating log for a file is to be shown 882 @param isFile flag indicating log for a file is to be shown
841 (boolean) 883 (boolean)
842 @param noEntries number of entries to get (0 = default) (int) 884 @param noEntries number of entries to get (0 = default) (int)
843 """ 885 """
844 self.__isFile = isFile 886 self.__isFile = isFile
845 887
846 self.sbsSelectLabel.clear() 888 self.sbsSelectLabel.clear()
847 889
848 self.errorGroup.hide() 890 self.errorGroup.hide()
849 QApplication.processEvents() 891 QApplication.processEvents()
850 892
851 self.__initData() 893 self.__initData()
852 894
853 self.__filename = fn 895 self.__filename = fn
854 self.dname, self.fname = self.vcs.splitPath(fn) 896 self.dname, self.fname = self.vcs.splitPath(fn)
855 897
856 # find the root of the repo 898 # find the root of the repo
857 self.repodir = self.dname 899 self.repodir = self.dname
858 while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)): 900 while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)):
859 self.repodir = os.path.dirname(self.repodir) 901 self.repodir = os.path.dirname(self.repodir)
860 if os.path.splitdrive(self.repodir)[1] == os.sep: 902 if os.path.splitdrive(self.repodir)[1] == os.sep:
861 return 903 return
862 904
863 self.projectMode = (self.fname == "." and self.dname == self.repodir) 905 self.projectMode = self.fname == "." and self.dname == self.repodir
864 self.stopCheckBox.setDisabled(self.projectMode or self.fname == ".") 906 self.stopCheckBox.setDisabled(self.projectMode or self.fname == ".")
865 self.activateWindow() 907 self.activateWindow()
866 self.raise_() 908 self.raise_()
867 909
868 self.logTree.clear() 910 self.logTree.clear()
869 self.__started = True 911 self.__started = True
870 self.__identifyProject() 912 self.__identifyProject()
871 self.__getLogEntries(noEntries=noEntries) 913 self.__getLogEntries(noEntries=noEntries)
872 914
873 def __procFinished(self, exitCode, exitStatus): 915 def __procFinished(self, exitCode, exitStatus):
874 """ 916 """
875 Private slot connected to the finished signal. 917 Private slot connected to the finished signal.
876 918
877 @param exitCode exit code of the process (integer) 919 @param exitCode exit code of the process (integer)
878 @param exitStatus exit status of the process (QProcess.ExitStatus) 920 @param exitStatus exit status of the process (QProcess.ExitStatus)
879 """ 921 """
880 self.__processBuffer() 922 self.__processBuffer()
881 self.__finish() 923 self.__finish()
882 924
883 def __finish(self): 925 def __finish(self):
884 """ 926 """
885 Private slot called when the process finished or the user pressed 927 Private slot called when the process finished or the user pressed
886 the button. 928 the button.
887 """ 929 """
888 if ( 930 if (
889 self.__process is not None and 931 self.__process is not None
890 self.__process.state() != QProcess.ProcessState.NotRunning 932 and self.__process.state() != QProcess.ProcessState.NotRunning
891 ): 933 ):
892 self.__process.terminate() 934 self.__process.terminate()
893 QTimer.singleShot(2000, self.__process.kill) 935 QTimer.singleShot(2000, self.__process.kill)
894 self.__process.waitForFinished(3000) 936 self.__process.waitForFinished(3000)
895 937
896 self.buttonBox.button( 938 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True)
897 QDialogButtonBox.StandardButton.Close).setEnabled(True) 939 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
898 self.buttonBox.button( 940 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
899 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) 941
900 self.buttonBox.button(
901 QDialogButtonBox.StandardButton.Close).setDefault(True)
902
903 self.inputGroup.setEnabled(False) 942 self.inputGroup.setEnabled(False)
904 self.inputGroup.hide() 943 self.inputGroup.hide()
905 self.refreshButton.setEnabled(True) 944 self.refreshButton.setEnabled(True)
906 945
907 while self.__finishCallbacks: 946 while self.__finishCallbacks:
908 self.__finishCallbacks.pop(0)() 947 self.__finishCallbacks.pop(0)()
909 948
910 def __processBufferItem(self, logEntry): 949 def __processBufferItem(self, logEntry):
911 """ 950 """
912 Private method to process a log entry. 951 Private method to process a log entry.
913 952
914 @param logEntry dictionary as generated by __processBuffer 953 @param logEntry dictionary as generated by __processBuffer
915 """ 954 """
916 self.__generateLogItem( 955 self.__generateLogItem(
917 logEntry["author"], logEntry["authordate"], 956 logEntry["author"],
918 logEntry["committer"], logEntry["committerdate"], 957 logEntry["authordate"],
919 logEntry["subject"], logEntry["body"], 958 logEntry["committer"],
920 logEntry["commit"], logEntry["changed_files"], 959 logEntry["committerdate"],
921 logEntry["parents"], logEntry["refnames"], 960 logEntry["subject"],
922 logEntry["authormail"], logEntry["committermail"] 961 logEntry["body"],
962 logEntry["commit"],
963 logEntry["changed_files"],
964 logEntry["parents"],
965 logEntry["refnames"],
966 logEntry["authormail"],
967 logEntry["committermail"],
923 ) 968 )
924 for date in [logEntry["authordate"], logEntry["committerdate"]]: 969 for date in [logEntry["authordate"], logEntry["committerdate"]]:
925 dt = QDate.fromString(date, Qt.DateFormat.ISODate) 970 dt = QDate.fromString(date, Qt.DateFormat.ISODate)
926 if ( 971 if not self.__maxDate.isValid() and not self.__minDate.isValid():
927 not self.__maxDate.isValid() and
928 not self.__minDate.isValid()
929 ):
930 self.__maxDate = dt 972 self.__maxDate = dt
931 self.__minDate = dt 973 self.__minDate = dt
932 else: 974 else:
933 if self.__maxDate < dt: 975 if self.__maxDate < dt:
934 self.__maxDate = dt 976 self.__maxDate = dt
935 if self.__minDate > dt: 977 if self.__minDate > dt:
936 self.__minDate = dt 978 self.__minDate = dt
937 979
938 def __processBuffer(self): 980 def __processBuffer(self):
939 """ 981 """
940 Private method to process the buffered output of the git log command. 982 Private method to process the buffered output of the git log command.
941 """ 983 """
942 noEntries = 0 984 noEntries = 0
943 logEntry = {"changed_files": []} 985 logEntry = {"changed_files": []}
944 descriptionBody = False 986 descriptionBody = False
945 987
946 for line in self.buf: 988 for line in self.buf:
947 line = line.rstrip() 989 line = line.rstrip()
948 if line == "recordstart": 990 if line == "recordstart":
949 if len(logEntry) > 1: 991 if len(logEntry) > 1:
950 self.__processBufferItem(logEntry) 992 self.__processBufferItem(logEntry)
978 middleSrc, middleDst = middle.split("=>") 1020 middleSrc, middleDst = middle.split("=>")
979 src = head + middleSrc.strip() + tail 1021 src = head + middleSrc.strip() + tail
980 dst = head + middleDst.strip() + tail 1022 dst = head + middleDst.strip() + tail
981 else: 1023 else:
982 src, dst = changeInfo[2].split("=>") 1024 src, dst = changeInfo[2].split("=>")
983 logEntry["changed_files"].append({ 1025 logEntry["changed_files"].append(
984 "action": "C", 1026 {
985 "added": changeInfo[0].strip(), 1027 "action": "C",
986 "deleted": changeInfo[1].strip(), 1028 "added": changeInfo[0].strip(),
987 "path": dst.strip(), 1029 "deleted": changeInfo[1].strip(),
988 "copyfrom": src.strip(), 1030 "path": dst.strip(),
989 }) 1031 "copyfrom": src.strip(),
1032 }
1033 )
990 else: 1034 else:
991 logEntry["changed_files"].append({ 1035 logEntry["changed_files"].append(
992 "action": "M", 1036 {
993 "added": changeInfo[0].strip(), 1037 "action": "M",
994 "deleted": changeInfo[1].strip(), 1038 "added": changeInfo[0].strip(),
995 "path": changeInfo[2].strip(), 1039 "deleted": changeInfo[1].strip(),
996 "copyfrom": "", 1040 "path": changeInfo[2].strip(),
997 }) 1041 "copyfrom": "",
1042 }
1043 )
998 else: 1044 else:
999 try: 1045 try:
1000 key, value = line.split("|", 1) 1046 key, value = line.split("|", 1)
1001 except ValueError: 1047 except ValueError:
1002 key = "" 1048 key = ""
1003 value = line 1049 value = line
1004 if key in ("commit", "parents", "author", "authormail", 1050 if key in (
1005 "authordate", "committer", "committermail", 1051 "commit",
1006 "committerdate", "refnames", "subject"): 1052 "parents",
1053 "author",
1054 "authormail",
1055 "authordate",
1056 "committer",
1057 "committermail",
1058 "committerdate",
1059 "refnames",
1060 "subject",
1061 ):
1007 logEntry[key] = value.strip() 1062 logEntry[key] = value.strip()
1008 if len(logEntry) > 1: 1063 if len(logEntry) > 1:
1009 self.__processBufferItem(logEntry) 1064 self.__processBufferItem(logEntry)
1010 noEntries += 1 1065 noEntries += 1
1011 1066
1012 self.__resizeColumnsLog() 1067 self.__resizeColumnsLog()
1013 1068
1014 if self.__started: 1069 if self.__started:
1015 if self.__selectedCommitIDs: 1070 if self.__selectedCommitIDs:
1016 self.logTree.setCurrentItem(self.logTree.findItems( 1071 self.logTree.setCurrentItem(
1017 self.__selectedCommitIDs[0], Qt.MatchFlag.MatchExactly, 1072 self.logTree.findItems(
1018 self.CommitIdColumn)[0]) 1073 self.__selectedCommitIDs[0],
1074 Qt.MatchFlag.MatchExactly,
1075 self.CommitIdColumn,
1076 )[0]
1077 )
1019 else: 1078 else:
1020 self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) 1079 self.logTree.setCurrentItem(self.logTree.topLevelItem(0))
1021 self.__started = False 1080 self.__started = False
1022 1081
1023 self.__skipEntries += noEntries 1082 self.__skipEntries += noEntries
1024 if noEntries < self.limitSpinBox.value() and not self.cancelled: 1083 if noEntries < self.limitSpinBox.value() and not self.cancelled:
1025 self.nextButton.setEnabled(False) 1084 self.nextButton.setEnabled(False)
1026 self.limitSpinBox.setEnabled(False) 1085 self.limitSpinBox.setEnabled(False)
1027 else: 1086 else:
1028 self.nextButton.setEnabled(True) 1087 self.nextButton.setEnabled(True)
1029 self.limitSpinBox.setEnabled(True) 1088 self.limitSpinBox.setEnabled(True)
1030 1089
1031 # update the log filters 1090 # update the log filters
1032 self.__filterLogsEnabled = False 1091 self.__filterLogsEnabled = False
1033 self.fromDate.setMinimumDate(self.__minDate) 1092 self.fromDate.setMinimumDate(self.__minDate)
1034 self.fromDate.setMaximumDate(self.__maxDate) 1093 self.fromDate.setMaximumDate(self.__maxDate)
1035 self.fromDate.setDate(self.__minDate) 1094 self.fromDate.setDate(self.__minDate)
1036 self.toDate.setMinimumDate(self.__minDate) 1095 self.toDate.setMinimumDate(self.__minDate)
1037 self.toDate.setMaximumDate(self.__maxDate) 1096 self.toDate.setMaximumDate(self.__maxDate)
1038 self.toDate.setDate(self.__maxDate) 1097 self.toDate.setDate(self.__maxDate)
1039 1098
1040 self.__filterLogsEnabled = True 1099 self.__filterLogsEnabled = True
1041 if self.__actionMode() == "filter": 1100 if self.__actionMode() == "filter":
1042 self.__filterLogs() 1101 self.__filterLogs()
1043 1102
1044 self.__updateToolMenuActions() 1103 self.__updateToolMenuActions()
1045 1104
1046 # restore selected items 1105 # restore selected items
1047 if self.__selectedCommitIDs: 1106 if self.__selectedCommitIDs:
1048 for commitID in self.__selectedCommitIDs: 1107 for commitID in self.__selectedCommitIDs:
1049 items = self.logTree.findItems( 1108 items = self.logTree.findItems(
1050 commitID, Qt.MatchFlag.MatchExactly, self.CommitIdColumn) 1109 commitID, Qt.MatchFlag.MatchExactly, self.CommitIdColumn
1110 )
1051 if items: 1111 if items:
1052 items[0].setSelected(True) 1112 items[0].setSelected(True)
1053 self.__selectedCommitIDs = [] 1113 self.__selectedCommitIDs = []
1054 1114
1055 def __readStdout(self): 1115 def __readStdout(self):
1056 """ 1116 """
1057 Private slot to handle the readyReadStandardOutput signal. 1117 Private slot to handle the readyReadStandardOutput signal.
1058 1118
1059 It reads the output of the process and inserts it into a buffer. 1119 It reads the output of the process and inserts it into a buffer.
1060 """ 1120 """
1061 self.__process.setReadChannel(QProcess.ProcessChannel.StandardOutput) 1121 self.__process.setReadChannel(QProcess.ProcessChannel.StandardOutput)
1062 1122
1063 while self.__process.canReadLine(): 1123 while self.__process.canReadLine():
1064 line = str(self.__process.readLine(), 1124 line = str(
1065 Preferences.getSystem("IOEncoding"), 1125 self.__process.readLine(),
1066 'replace') 1126 Preferences.getSystem("IOEncoding"),
1127 "replace",
1128 )
1067 self.buf.append(line) 1129 self.buf.append(line)
1068 1130
1069 def __readStderr(self): 1131 def __readStderr(self):
1070 """ 1132 """
1071 Private slot to handle the readyReadStandardError signal. 1133 Private slot to handle the readyReadStandardError signal.
1072 1134
1073 It reads the error output of the process and inserts it into the 1135 It reads the error output of the process and inserts it into the
1074 error pane. 1136 error pane.
1075 """ 1137 """
1076 if self.__process is not None: 1138 if self.__process is not None:
1077 s = str(self.__process.readAllStandardError(), 1139 s = str(
1078 Preferences.getSystem("IOEncoding"), 1140 self.__process.readAllStandardError(),
1079 'replace') 1141 Preferences.getSystem("IOEncoding"),
1142 "replace",
1143 )
1080 self.__showError(s) 1144 self.__showError(s)
1081 1145
1082 def __showError(self, out): 1146 def __showError(self, out):
1083 """ 1147 """
1084 Private slot to show some error. 1148 Private slot to show some error.
1085 1149
1086 @param out error to be shown (string) 1150 @param out error to be shown (string)
1087 """ 1151 """
1088 self.errorGroup.show() 1152 self.errorGroup.show()
1089 self.errors.insertPlainText(out) 1153 self.errors.insertPlainText(out)
1090 self.errors.ensureCursorVisible() 1154 self.errors.ensureCursorVisible()
1091 1155
1092 # show input in case the process asked for some input 1156 # show input in case the process asked for some input
1093 self.inputGroup.setEnabled(True) 1157 self.inputGroup.setEnabled(True)
1094 self.inputGroup.show() 1158 self.inputGroup.show()
1095 1159
1096 def on_buttonBox_clicked(self, button): 1160 def on_buttonBox_clicked(self, button):
1097 """ 1161 """
1098 Private slot called by a button of the button box clicked. 1162 Private slot called by a button of the button box clicked.
1099 1163
1100 @param button button that was clicked (QAbstractButton) 1164 @param button button that was clicked (QAbstractButton)
1101 """ 1165 """
1102 if button == self.buttonBox.button( 1166 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close):
1103 QDialogButtonBox.StandardButton.Close
1104 ):
1105 self.close() 1167 self.close()
1106 elif button == self.buttonBox.button( 1168 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel):
1107 QDialogButtonBox.StandardButton.Cancel
1108 ):
1109 self.cancelled = True 1169 self.cancelled = True
1110 self.__finish() 1170 self.__finish()
1111 elif button == self.refreshButton: 1171 elif button == self.refreshButton:
1112 self.on_refreshButton_clicked() 1172 self.on_refreshButton_clicked()
1113 1173
1114 @pyqtSlot() 1174 @pyqtSlot()
1115 def on_refreshButton_clicked(self): 1175 def on_refreshButton_clicked(self):
1116 """ 1176 """
1117 Private slot to refresh the log. 1177 Private slot to refresh the log.
1118 """ 1178 """
1119 self.buttonBox.button( 1179 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
1120 QDialogButtonBox.StandardButton.Close).setEnabled(False) 1180 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
1121 self.buttonBox.button( 1181 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
1122 QDialogButtonBox.StandardButton.Cancel).setEnabled(True) 1182
1123 self.buttonBox.button(
1124 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
1125
1126 self.refreshButton.setEnabled(False) 1183 self.refreshButton.setEnabled(False)
1127 1184
1128 # save the selected items commit IDs 1185 # save the selected items commit IDs
1129 self.__selectedCommitIDs = [] 1186 self.__selectedCommitIDs = []
1130 for item in self.logTree.selectedItems(): 1187 for item in self.logTree.selectedItems():
1131 self.__selectedCommitIDs.append(item.text(self.CommitIdColumn)) 1188 self.__selectedCommitIDs.append(item.text(self.CommitIdColumn))
1132 1189
1133 self.start(self.__filename, isFile=self.__isFile, 1190 self.start(
1134 noEntries=self.logTree.topLevelItemCount()) 1191 self.__filename,
1135 1192 isFile=self.__isFile,
1193 noEntries=self.logTree.topLevelItemCount(),
1194 )
1195
1136 def on_passwordCheckBox_toggled(self, isOn): 1196 def on_passwordCheckBox_toggled(self, isOn):
1137 """ 1197 """
1138 Private slot to handle the password checkbox toggled. 1198 Private slot to handle the password checkbox toggled.
1139 1199
1140 @param isOn flag indicating the status of the check box (boolean) 1200 @param isOn flag indicating the status of the check box (boolean)
1141 """ 1201 """
1142 if isOn: 1202 if isOn:
1143 self.input.setEchoMode(QLineEdit.EchoMode.Password) 1203 self.input.setEchoMode(QLineEdit.EchoMode.Password)
1144 else: 1204 else:
1145 self.input.setEchoMode(QLineEdit.EchoMode.Normal) 1205 self.input.setEchoMode(QLineEdit.EchoMode.Normal)
1146 1206
1147 @pyqtSlot() 1207 @pyqtSlot()
1148 def on_sendButton_clicked(self): 1208 def on_sendButton_clicked(self):
1149 """ 1209 """
1150 Private slot to send the input to the git process. 1210 Private slot to send the input to the git process.
1151 """ 1211 """
1152 inputTxt = self.input.text() 1212 inputTxt = self.input.text()
1153 inputTxt += os.linesep 1213 inputTxt += os.linesep
1154 1214
1155 if self.passwordCheckBox.isChecked(): 1215 if self.passwordCheckBox.isChecked():
1156 self.errors.insertPlainText(os.linesep) 1216 self.errors.insertPlainText(os.linesep)
1157 self.errors.ensureCursorVisible() 1217 self.errors.ensureCursorVisible()
1158 else: 1218 else:
1159 self.errors.insertPlainText(inputTxt) 1219 self.errors.insertPlainText(inputTxt)
1160 self.errors.ensureCursorVisible() 1220 self.errors.ensureCursorVisible()
1161 self.errorGroup.show() 1221 self.errorGroup.show()
1162 1222
1163 self.__process.write(strToQByteArray(inputTxt)) 1223 self.__process.write(strToQByteArray(inputTxt))
1164 1224
1165 self.passwordCheckBox.setChecked(False) 1225 self.passwordCheckBox.setChecked(False)
1166 self.input.clear() 1226 self.input.clear()
1167 1227
1168 def on_input_returnPressed(self): 1228 def on_input_returnPressed(self):
1169 """ 1229 """
1170 Private slot to handle the press of the return key in the input field. 1230 Private slot to handle the press of the return key in the input field.
1171 """ 1231 """
1172 self.intercept = True 1232 self.intercept = True
1173 self.on_sendButton_clicked() 1233 self.on_sendButton_clicked()
1174 1234
1175 def keyPressEvent(self, evt): 1235 def keyPressEvent(self, evt):
1176 """ 1236 """
1177 Protected slot to handle a key press event. 1237 Protected slot to handle a key press event.
1178 1238
1179 @param evt the key press event (QKeyEvent) 1239 @param evt the key press event (QKeyEvent)
1180 """ 1240 """
1181 if self.intercept: 1241 if self.intercept:
1182 self.intercept = False 1242 self.intercept = False
1183 evt.accept() 1243 evt.accept()
1184 return 1244 return
1185 super().keyPressEvent(evt) 1245 super().keyPressEvent(evt)
1186 1246
1187 def __prepareFieldSearch(self): 1247 def __prepareFieldSearch(self):
1188 """ 1248 """
1189 Private slot to prepare the filed search data. 1249 Private slot to prepare the filed search data.
1190 1250
1191 @return tuple of field index, search expression and flag indicating 1251 @return tuple of field index, search expression and flag indicating
1192 that the field index is a data role (integer, string, boolean) 1252 that the field index is a data role (integer, string, boolean)
1193 """ 1253 """
1194 indexIsRole = False 1254 indexIsRole = False
1195 txt = self.fieldCombo.itemData(self.fieldCombo.currentIndex()) 1255 txt = self.fieldCombo.itemData(self.fieldCombo.currentIndex())
1201 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE) 1261 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE)
1202 elif txt == "commitId": 1262 elif txt == "commitId":
1203 fieldIndex = self.CommitIdColumn 1263 fieldIndex = self.CommitIdColumn
1204 txt = self.rxEdit.text() 1264 txt = self.rxEdit.text()
1205 if txt.startswith("^"): 1265 if txt.startswith("^"):
1206 searchRx = re.compile(r"^\s*{0}".format(txt[1:]), 1266 searchRx = re.compile(r"^\s*{0}".format(txt[1:]), re.IGNORECASE)
1207 re.IGNORECASE)
1208 else: 1267 else:
1209 searchRx = re.compile(txt, re.IGNORECASE) 1268 searchRx = re.compile(txt, re.IGNORECASE)
1210 elif txt == "file": 1269 elif txt == "file":
1211 fieldIndex = self.__changesRole 1270 fieldIndex = self.__changesRole
1212 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE) 1271 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE)
1213 indexIsRole = True 1272 indexIsRole = True
1214 else: 1273 else:
1215 fieldIndex = self.__subjectRole 1274 fieldIndex = self.__subjectRole
1216 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE) 1275 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE)
1217 indexIsRole = True 1276 indexIsRole = True
1218 1277
1219 return fieldIndex, searchRx, indexIsRole 1278 return fieldIndex, searchRx, indexIsRole
1220 1279
1221 def __filterLogs(self): 1280 def __filterLogs(self):
1222 """ 1281 """
1223 Private method to filter the log entries. 1282 Private method to filter the log entries.
1224 """ 1283 """
1225 if self.__filterLogsEnabled: 1284 if self.__filterLogsEnabled:
1226 from_ = self.fromDate.date().toString("yyyy-MM-dd") 1285 from_ = self.fromDate.date().toString("yyyy-MM-dd")
1227 to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") 1286 to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd")
1228 fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch() 1287 fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch()
1229 1288
1230 visibleItemCount = self.logTree.topLevelItemCount() 1289 visibleItemCount = self.logTree.topLevelItemCount()
1231 currentItem = self.logTree.currentItem() 1290 currentItem = self.logTree.currentItem()
1232 for topIndex in range(self.logTree.topLevelItemCount()): 1291 for topIndex in range(self.logTree.topLevelItemCount()):
1233 topItem = self.logTree.topLevelItem(topIndex) 1292 topItem = self.logTree.topLevelItem(topIndex)
1234 if indexIsRole: 1293 if indexIsRole:
1235 if fieldIndex == self.__changesRole: 1294 if fieldIndex == self.__changesRole:
1236 changes = topItem.data(0, self.__changesRole) 1295 changes = topItem.data(0, self.__changesRole)
1237 txt = "\n".join( 1296 txt = "\n".join(
1238 [c["path"] for c in changes] + 1297 [c["path"] for c in changes]
1239 [c["copyfrom"] for c in changes] 1298 + [c["copyfrom"] for c in changes]
1240 ) 1299 )
1241 else: 1300 else:
1242 # Filter based on complete subject text 1301 # Filter based on complete subject text
1243 txt = topItem.data(0, self.__subjectRole) 1302 txt = topItem.data(0, self.__subjectRole)
1244 else: 1303 else:
1245 txt = topItem.text(fieldIndex) 1304 txt = topItem.text(fieldIndex)
1246 if ( 1305 if (
1247 topItem.text(self.DateColumn) <= to_ and 1306 topItem.text(self.DateColumn) <= to_
1248 topItem.text(self.DateColumn) >= from_ and 1307 and topItem.text(self.DateColumn) >= from_
1249 searchRx.search(txt) is not None 1308 and searchRx.search(txt) is not None
1250 ): 1309 ):
1251 topItem.setHidden(False) 1310 topItem.setHidden(False)
1252 if topItem is currentItem: 1311 if topItem is currentItem:
1253 self.on_logTree_currentItemChanged(topItem, None) 1312 self.on_logTree_currentItemChanged(topItem, None)
1254 else: 1313 else:
1255 topItem.setHidden(True) 1314 topItem.setHidden(True)
1256 if topItem is currentItem: 1315 if topItem is currentItem:
1257 self.filesTree.clear() 1316 self.filesTree.clear()
1258 visibleItemCount -= 1 1317 visibleItemCount -= 1
1259 self.logTree.header().setSectionHidden( 1318 self.logTree.header().setSectionHidden(
1260 self.IconColumn, 1319 self.IconColumn, visibleItemCount != self.logTree.topLevelItemCount()
1261 visibleItemCount != self.logTree.topLevelItemCount()) 1320 )
1262 1321
1263 def __updateSbsSelectLabel(self): 1322 def __updateSbsSelectLabel(self):
1264 """ 1323 """
1265 Private slot to update the enabled status of the diff buttons. 1324 Private slot to update the enabled status of the diff buttons.
1266 """ 1325 """
1267 self.sbsSelectLabel.clear() 1326 self.sbsSelectLabel.clear()
1273 parents = currentItem.data(0, self.__parentsRole) 1332 parents = currentItem.data(0, self.__parentsRole)
1274 if parents: 1333 if parents:
1275 parentLinks = [] 1334 parentLinks = []
1276 for index in range(len(parents)): 1335 for index in range(len(parents)):
1277 parentLinks.append( 1336 parentLinks.append(
1278 '<a href="sbsdiff:{0}_{1}">&nbsp;{2}&nbsp;</a>' 1337 '<a href="sbsdiff:{0}_{1}">&nbsp;{2}&nbsp;</a>'.format(
1279 .format(parents[index], commit2, index + 1)) 1338 parents[index], commit2, index + 1
1339 )
1340 )
1280 self.sbsSelectLabel.setText( 1341 self.sbsSelectLabel.setText(
1281 self.tr('Side-by-Side Diff to Parent {0}').format( 1342 self.tr("Side-by-Side Diff to Parent {0}").format(
1282 " ".join(parentLinks))) 1343 " ".join(parentLinks)
1344 )
1345 )
1283 elif len(selectedItems) == 2: 1346 elif len(selectedItems) == 2:
1284 commit2 = selectedItems[0].text(self.CommitIdColumn) 1347 commit2 = selectedItems[0].text(self.CommitIdColumn)
1285 commit1 = selectedItems[1].text(self.CommitIdColumn) 1348 commit1 = selectedItems[1].text(self.CommitIdColumn)
1286 index2 = self.logTree.indexOfTopLevelItem(selectedItems[0]) 1349 index2 = self.logTree.indexOfTopLevelItem(selectedItems[0])
1287 index1 = self.logTree.indexOfTopLevelItem(selectedItems[1]) 1350 index1 = self.logTree.indexOfTopLevelItem(selectedItems[1])
1288 1351
1289 if index2 < index1: 1352 if index2 < index1:
1290 # swap to always compare old to new 1353 # swap to always compare old to new
1291 commit1, commit2 = commit2, commit1 1354 commit1, commit2 = commit2, commit1
1292 self.sbsSelectLabel.setText(self.tr( 1355 self.sbsSelectLabel.setText(
1293 '<a href="sbsdiff:{0}_{1}">Side-by-Side Compare</a>') 1356 self.tr(
1294 .format(commit1, commit2)) 1357 '<a href="sbsdiff:{0}_{1}">Side-by-Side Compare</a>'
1295 1358 ).format(commit1, commit2)
1359 )
1360
1296 def __updateToolMenuActions(self): 1361 def __updateToolMenuActions(self):
1297 """ 1362 """
1298 Private slot to update the status of the tool menu actions and 1363 Private slot to update the status of the tool menu actions and
1299 the tool menu button. 1364 the tool menu button.
1300 """ 1365 """
1305 self.__tagAct.setEnabled(selectCount == 1) 1370 self.__tagAct.setEnabled(selectCount == 1)
1306 self.__switchAct.setEnabled(selectCount == 1) 1371 self.__switchAct.setEnabled(selectCount == 1)
1307 self.__branchAct.setEnabled(selectCount == 1) 1372 self.__branchAct.setEnabled(selectCount == 1)
1308 self.__branchSwitchAct.setEnabled(selectCount == 1) 1373 self.__branchSwitchAct.setEnabled(selectCount == 1)
1309 self.__shortlogAct.setEnabled(selectCount == 1) 1374 self.__shortlogAct.setEnabled(selectCount == 1)
1310 1375
1311 self.actionsButton.setEnabled(True) 1376 self.actionsButton.setEnabled(True)
1312 else: 1377 else:
1313 self.actionsButton.setEnabled(False) 1378 self.actionsButton.setEnabled(False)
1314 1379
1315 def __updateDetailsAndFiles(self): 1380 def __updateDetailsAndFiles(self):
1316 """ 1381 """
1317 Private slot to update the details and file changes panes. 1382 Private slot to update the details and file changes panes.
1318 """ 1383 """
1319 self.detailsEdit.clear() 1384 self.detailsEdit.clear()
1320 self.filesTree.clear() 1385 self.filesTree.clear()
1321 self.__diffUpdatesFiles = False 1386 self.__diffUpdatesFiles = False
1322 1387
1323 selectedItems = self.logTree.selectedItems() 1388 selectedItems = self.logTree.selectedItems()
1324 if len(selectedItems) == 1: 1389 if len(selectedItems) == 1:
1325 self.detailsEdit.setHtml( 1390 self.detailsEdit.setHtml(self.__generateDetailsTableText(selectedItems[0]))
1326 self.__generateDetailsTableText(selectedItems[0]))
1327 self.__updateFilesTree(self.filesTree, selectedItems[0]) 1391 self.__updateFilesTree(self.filesTree, selectedItems[0])
1328 self.__resizeColumnsFiles() 1392 self.__resizeColumnsFiles()
1329 self.__resortFiles() 1393 self.__resortFiles()
1330 if self.filesTree.topLevelItemCount() == 0: 1394 if self.filesTree.topLevelItemCount() == 0:
1331 self.__diffUpdatesFiles = True 1395 self.__diffUpdatesFiles = True
1335 index1 = self.logTree.indexOfTopLevelItem(selectedItems[0]) 1399 index1 = self.logTree.indexOfTopLevelItem(selectedItems[0])
1336 index2 = self.logTree.indexOfTopLevelItem(selectedItems[1]) 1400 index2 = self.logTree.indexOfTopLevelItem(selectedItems[1])
1337 if index1 > index2: 1401 if index1 > index2:
1338 # Swap the entries 1402 # Swap the entries
1339 selectedItems[0], selectedItems[1] = ( 1403 selectedItems[0], selectedItems[1] = (
1340 selectedItems[1], selectedItems[0] 1404 selectedItems[1],
1405 selectedItems[0],
1341 ) 1406 )
1342 html = "{0}<hr/>{1}".format( 1407 html = "{0}<hr/>{1}".format(
1343 self.__generateDetailsTableText(selectedItems[0]), 1408 self.__generateDetailsTableText(selectedItems[0]),
1344 self.__generateDetailsTableText(selectedItems[1]), 1409 self.__generateDetailsTableText(selectedItems[1]),
1345 ) 1410 )
1346 self.detailsEdit.setHtml(html) 1411 self.detailsEdit.setHtml(html)
1347 # self.filesTree is updated by the diff 1412 # self.filesTree is updated by the diff
1348 1413
1349 def __generateDetailsTableText(self, itm): 1414 def __generateDetailsTableText(self, itm):
1350 """ 1415 """
1351 Private method to generate an HTML table with the details of the given 1416 Private method to generate an HTML table with the details of the given
1352 changeset. 1417 changeset.
1353 1418
1354 @param itm reference to the item the table should be based on 1419 @param itm reference to the item the table should be based on
1355 @type QTreeWidgetItem 1420 @type QTreeWidgetItem
1356 @return HTML table containing details 1421 @return HTML table containing details
1357 @rtype str 1422 @rtype str
1358 """ 1423 """
1359 if itm is not None: 1424 if itm is not None:
1360 commitId = itm.text(self.CommitIdColumn) 1425 commitId = itm.text(self.CommitIdColumn)
1361 1426
1362 parentLinks = [] 1427 parentLinks = []
1363 for parent in [str(x) for x in itm.data(0, self.__parentsRole)]: 1428 for parent in [str(x) for x in itm.data(0, self.__parentsRole)]:
1364 parentLinks.append('<a href="rev:{0}">{0}</a>'.format(parent)) 1429 parentLinks.append('<a href="rev:{0}">{0}</a>'.format(parent))
1365 if parentLinks: 1430 if parentLinks:
1366 parentsStr = self.__parentsTemplate.format( 1431 parentsStr = self.__parentsTemplate.format(", ".join(parentLinks))
1367 ", ".join(parentLinks))
1368 else: 1432 else:
1369 parentsStr = "" 1433 parentsStr = ""
1370 1434
1371 childLinks = [] 1435 childLinks = []
1372 for child in [str(x) for x in self.__childrenInfo[commitId]]: 1436 for child in [str(x) for x in self.__childrenInfo[commitId]]:
1373 childLinks.append('<a href="rev:{0}">{0}</a>'.format(child)) 1437 childLinks.append('<a href="rev:{0}">{0}</a>'.format(child))
1374 if childLinks: 1438 if childLinks:
1375 childrenStr = self.__childrenTemplate.format( 1439 childrenStr = self.__childrenTemplate.format(", ".join(childLinks))
1376 ", ".join(childLinks))
1377 else: 1440 else:
1378 childrenStr = "" 1441 childrenStr = ""
1379 1442
1380 branchLinks = [] 1443 branchLinks = []
1381 for branch, branchHead in self.__getBranchesForCommit(commitId): 1444 for branch, branchHead in self.__getBranchesForCommit(commitId):
1382 branchLinks.append('<a href="rev:{0}">{1}</a>'.format( 1445 branchLinks.append(
1383 branchHead, branch)) 1446 '<a href="rev:{0}">{1}</a>'.format(branchHead, branch)
1447 )
1384 if branchLinks: 1448 if branchLinks:
1385 branchesStr = self.__branchesTemplate.format( 1449 branchesStr = self.__branchesTemplate.format(", ".join(branchLinks))
1386 ", ".join(branchLinks))
1387 else: 1450 else:
1388 branchesStr = "" 1451 branchesStr = ""
1389 1452
1390 tagLinks = [] 1453 tagLinks = []
1391 for tag, tagCommit in self.__getTagsForCommit(commitId): 1454 for tag, tagCommit in self.__getTagsForCommit(commitId):
1392 if tagCommit: 1455 if tagCommit:
1393 tagLinks.append('<a href="rev:{0}">{1}</a>'.format( 1456 tagLinks.append('<a href="rev:{0}">{1}</a>'.format(tagCommit, tag))
1394 tagCommit, tag))
1395 else: 1457 else:
1396 tagLinks.append(tag) 1458 tagLinks.append(tag)
1397 if tagLinks: 1459 if tagLinks:
1398 tagsStr = self.__tagsTemplate.format( 1460 tagsStr = self.__tagsTemplate.format(", ".join(tagLinks))
1399 ", ".join(tagLinks))
1400 else: 1461 else:
1401 tagsStr = "" 1462 tagsStr = ""
1402 1463
1403 if itm.data(0, self.__messageRole): 1464 if itm.data(0, self.__messageRole):
1404 messageStr = self.__mesageTemplate.format( 1465 messageStr = self.__mesageTemplate.format(
1405 "<br/>".join(itm.data(0, self.__messageRole))) 1466 "<br/>".join(itm.data(0, self.__messageRole))
1467 )
1406 else: 1468 else:
1407 messageStr = "" 1469 messageStr = ""
1408 1470
1409 html = self.__detailsTemplate.format( 1471 html = self.__detailsTemplate.format(
1410 commitId, 1472 commitId,
1411 itm.text(self.DateColumn), 1473 itm.text(self.DateColumn),
1412 itm.text(self.AuthorColumn), 1474 itm.text(self.AuthorColumn),
1413 itm.data(0, self.__authorMailRole).strip(), 1475 itm.data(0, self.__authorMailRole).strip(),
1418 itm.data(0, self.__subjectRole), 1480 itm.data(0, self.__subjectRole),
1419 messageStr, 1481 messageStr,
1420 ) 1482 )
1421 else: 1483 else:
1422 html = "" 1484 html = ""
1423 1485
1424 return html 1486 return html
1425 1487
1426 def __updateFilesTree(self, parent, itm): 1488 def __updateFilesTree(self, parent, itm):
1427 """ 1489 """
1428 Private method to update the files tree with changes of the given item. 1490 Private method to update the files tree with changes of the given item.
1429 1491
1430 @param parent parent for the items to be added 1492 @param parent parent for the items to be added
1431 @type QTreeWidget or QTreeWidgetItem 1493 @type QTreeWidget or QTreeWidgetItem
1432 @param itm reference to the item the update should be based on 1494 @param itm reference to the item the update should be based on
1433 @type QTreeWidgetItem 1495 @type QTreeWidgetItem
1434 """ 1496 """
1435 if itm is not None: 1497 if itm is not None:
1436 changes = itm.data(0, self.__changesRole) 1498 changes = itm.data(0, self.__changesRole)
1437 if len(changes) > 0: 1499 if len(changes) > 0:
1438 for change in changes: 1500 for change in changes:
1439 self.__generateFileItem( 1501 self.__generateFileItem(
1440 change["action"], change["path"], change["copyfrom"], 1502 change["action"],
1441 change["added"], change["deleted"]) 1503 change["path"],
1504 change["copyfrom"],
1505 change["added"],
1506 change["deleted"],
1507 )
1442 self.__resizeColumnsFiles() 1508 self.__resizeColumnsFiles()
1443 self.__resortFiles() 1509 self.__resortFiles()
1444 1510
1445 def __getBranchesForCommit(self, commitId): 1511 def __getBranchesForCommit(self, commitId):
1446 """ 1512 """
1447 Private method to get all branches reachable from a commit ID. 1513 Private method to get all branches reachable from a commit ID.
1448 1514
1449 @param commitId commit ID to get the branches for 1515 @param commitId commit ID to get the branches for
1450 @type str 1516 @type str
1451 @return list of tuples containing the branch name and the associated 1517 @return list of tuples containing the branch name and the associated
1452 commit ID of its branch head 1518 commit ID of its branch head
1453 @rtype tuple of (str, str) 1519 @rtype tuple of (str, str)
1454 """ 1520 """
1455 branches = [] 1521 branches = []
1456 1522
1457 args = self.vcs.initCommand("branch") 1523 args = self.vcs.initCommand("branch")
1458 args.append("--list") 1524 args.append("--list")
1459 args.append("--verbose") 1525 args.append("--verbose")
1460 args.append("--contains") 1526 args.append("--contains")
1461 args.append(commitId) 1527 args.append(commitId)
1462 1528
1463 output = "" 1529 output = ""
1464 process = QProcess() 1530 process = QProcess()
1465 process.setWorkingDirectory(self.repodir) 1531 process.setWorkingDirectory(self.repodir)
1466 process.start('git', args) 1532 process.start("git", args)
1467 procStarted = process.waitForStarted(5000) 1533 procStarted = process.waitForStarted(5000)
1468 if procStarted: 1534 if procStarted:
1469 finished = process.waitForFinished(30000) 1535 finished = process.waitForFinished(30000)
1470 if finished and process.exitCode() == 0: 1536 if finished and process.exitCode() == 0:
1471 output = str(process.readAllStandardOutput(), 1537 output = str(
1472 Preferences.getSystem("IOEncoding"), 1538 process.readAllStandardOutput(),
1473 'replace') 1539 Preferences.getSystem("IOEncoding"),
1474 1540 "replace",
1541 )
1542
1475 if output: 1543 if output:
1476 for line in output.splitlines(): 1544 for line in output.splitlines():
1477 name, commitId = line[2:].split(None, 2)[:2] 1545 name, commitId = line[2:].split(None, 2)[:2]
1478 branches.append((name, commitId)) 1546 branches.append((name, commitId))
1479 1547
1480 return branches 1548 return branches
1481 1549
1482 def __getTagsForCommit(self, commitId): 1550 def __getTagsForCommit(self, commitId):
1483 """ 1551 """
1484 Private method to get all tags reachable from a commit ID. 1552 Private method to get all tags reachable from a commit ID.
1485 1553
1486 @param commitId commit ID to get the tags for 1554 @param commitId commit ID to get the tags for
1487 @type str 1555 @type str
1488 @return list of tuples containing the tag name and the associated 1556 @return list of tuples containing the tag name and the associated
1489 commit ID 1557 commit ID
1490 @rtype tuple of (str, str) 1558 @rtype tuple of (str, str)
1491 """ 1559 """
1492 tags = [] 1560 tags = []
1493 1561
1494 args = self.vcs.initCommand("tag") 1562 args = self.vcs.initCommand("tag")
1495 args.append("--list") 1563 args.append("--list")
1496 args.append("--contains") 1564 args.append("--contains")
1497 args.append(commitId) 1565 args.append(commitId)
1498 1566
1499 output = "" 1567 output = ""
1500 process = QProcess() 1568 process = QProcess()
1501 process.setWorkingDirectory(self.repodir) 1569 process.setWorkingDirectory(self.repodir)
1502 process.start('git', args) 1570 process.start("git", args)
1503 procStarted = process.waitForStarted(5000) 1571 procStarted = process.waitForStarted(5000)
1504 if procStarted: 1572 if procStarted:
1505 finished = process.waitForFinished(30000) 1573 finished = process.waitForFinished(30000)
1506 if finished and process.exitCode() == 0: 1574 if finished and process.exitCode() == 0:
1507 output = str(process.readAllStandardOutput(), 1575 output = str(
1508 Preferences.getSystem("IOEncoding"), 1576 process.readAllStandardOutput(),
1509 'replace') 1577 Preferences.getSystem("IOEncoding"),
1510 1578 "replace",
1579 )
1580
1511 if output: 1581 if output:
1512 tagNames = [] 1582 tagNames = []
1513 for line in output.splitlines(): 1583 for line in output.splitlines():
1514 tagNames.append(line.strip()) 1584 tagNames.append(line.strip())
1515 1585
1516 # determine the commit IDs for the tags 1586 # determine the commit IDs for the tags
1517 for tagName in tagNames: 1587 for tagName in tagNames:
1518 commitId = self.__getCommitForTag(tagName) 1588 commitId = self.__getCommitForTag(tagName)
1519 tags.append((tagName, commitId)) 1589 tags.append((tagName, commitId))
1520 1590
1521 return tags 1591 return tags
1522 1592
1523 def __getCommitForTag(self, tag): 1593 def __getCommitForTag(self, tag):
1524 """ 1594 """
1525 Private method to get the commit id for a tag. 1595 Private method to get the commit id for a tag.
1526 1596
1527 @param tag tag name (string) 1597 @param tag tag name (string)
1528 @return commit id shortened to 10 characters (string) 1598 @return commit id shortened to 10 characters (string)
1529 """ 1599 """
1530 args = self.vcs.initCommand("show") 1600 args = self.vcs.initCommand("show")
1531 args.append("--abbrev-commit") 1601 args.append("--abbrev-commit")
1532 args.append("--abbrev={0}".format( 1602 args.append(
1533 self.vcs.getPlugin().getPreferences("CommitIdLength"))) 1603 "--abbrev={0}".format(self.vcs.getPlugin().getPreferences("CommitIdLength"))
1604 )
1534 args.append("--no-patch") 1605 args.append("--no-patch")
1535 args.append(tag) 1606 args.append(tag)
1536 1607
1537 output = "" 1608 output = ""
1538 process = QProcess() 1609 process = QProcess()
1539 process.setWorkingDirectory(self.repodir) 1610 process.setWorkingDirectory(self.repodir)
1540 process.start('git', args) 1611 process.start("git", args)
1541 procStarted = process.waitForStarted(5000) 1612 procStarted = process.waitForStarted(5000)
1542 if procStarted: 1613 if procStarted:
1543 finished = process.waitForFinished(30000) 1614 finished = process.waitForFinished(30000)
1544 if finished and process.exitCode() == 0: 1615 if finished and process.exitCode() == 0:
1545 output = str(process.readAllStandardOutput(), 1616 output = str(
1546 Preferences.getSystem("IOEncoding"), 1617 process.readAllStandardOutput(),
1547 'replace') 1618 Preferences.getSystem("IOEncoding"),
1548 1619 "replace",
1620 )
1621
1549 if output: 1622 if output:
1550 for line in output.splitlines(): 1623 for line in output.splitlines():
1551 if line.startswith("commit "): 1624 if line.startswith("commit "):
1552 commitId = line.split()[1].strip() 1625 commitId = line.split()[1].strip()
1553 return commitId 1626 return commitId
1554 1627
1555 return "" 1628 return ""
1556 1629
1557 @pyqtSlot(QPoint) 1630 @pyqtSlot(QPoint)
1558 def on_logTree_customContextMenuRequested(self, pos): 1631 def on_logTree_customContextMenuRequested(self, pos):
1559 """ 1632 """
1560 Private slot to show the context menu of the log tree. 1633 Private slot to show the context menu of the log tree.
1561 1634
1562 @param pos position of the mouse pointer (QPoint) 1635 @param pos position of the mouse pointer (QPoint)
1563 """ 1636 """
1564 self.__logTreeMenu.popup(self.logTree.mapToGlobal(pos)) 1637 self.__logTreeMenu.popup(self.logTree.mapToGlobal(pos))
1565 1638
1566 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) 1639 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
1567 def on_logTree_currentItemChanged(self, current, previous): 1640 def on_logTree_currentItemChanged(self, current, previous):
1568 """ 1641 """
1569 Private slot called, when the current item of the log tree changes. 1642 Private slot called, when the current item of the log tree changes.
1570 1643
1571 @param current reference to the new current item (QTreeWidgetItem) 1644 @param current reference to the new current item (QTreeWidgetItem)
1572 @param previous reference to the old current item (QTreeWidgetItem) 1645 @param previous reference to the old current item (QTreeWidgetItem)
1573 """ 1646 """
1574 self.__updateToolMenuActions() 1647 self.__updateToolMenuActions()
1575 1648
1576 # Highlight the current entry using a bold font 1649 # Highlight the current entry using a bold font
1577 for col in range(self.logTree.columnCount()): 1650 for col in range(self.logTree.columnCount()):
1578 current and current.setFont(col, self.__logTreeBoldFont) 1651 current and current.setFont(col, self.__logTreeBoldFont)
1579 previous and previous.setFont(col, self.__logTreeNormalFont) 1652 previous and previous.setFont(col, self.__logTreeNormalFont)
1580 1653
1581 # set the state of the up and down buttons 1654 # set the state of the up and down buttons
1582 self.upButton.setEnabled( 1655 self.upButton.setEnabled(
1583 current is not None and 1656 current is not None and self.logTree.indexOfTopLevelItem(current) > 0
1584 self.logTree.indexOfTopLevelItem(current) > 0) 1657 )
1585 self.downButton.setEnabled( 1658 self.downButton.setEnabled(
1586 current is not None and 1659 current is not None
1587 len(current.data(0, self.__parentsRole)) > 0 and 1660 and len(current.data(0, self.__parentsRole)) > 0
1588 (self.logTree.indexOfTopLevelItem(current) < 1661 and (
1589 self.logTree.topLevelItemCount() - 1 or 1662 self.logTree.indexOfTopLevelItem(current)
1590 self.nextButton.isEnabled())) 1663 < self.logTree.topLevelItemCount() - 1
1591 1664 or self.nextButton.isEnabled()
1665 )
1666 )
1667
1592 @pyqtSlot() 1668 @pyqtSlot()
1593 def on_logTree_itemSelectionChanged(self): 1669 def on_logTree_itemSelectionChanged(self):
1594 """ 1670 """
1595 Private slot called, when the selection has changed. 1671 Private slot called, when the selection has changed.
1596 """ 1672 """
1597 self.__updateDetailsAndFiles() 1673 self.__updateDetailsAndFiles()
1598 self.__updateSbsSelectLabel() 1674 self.__updateSbsSelectLabel()
1599 self.__updateToolMenuActions() 1675 self.__updateToolMenuActions()
1600 self.__generateDiffs() 1676 self.__generateDiffs()
1601 1677
1602 @pyqtSlot() 1678 @pyqtSlot()
1603 def on_upButton_clicked(self): 1679 def on_upButton_clicked(self):
1604 """ 1680 """
1605 Private slot to move the current item up one entry. 1681 Private slot to move the current item up one entry.
1606 """ 1682 """
1607 itm = self.logTree.itemAbove(self.logTree.currentItem()) 1683 itm = self.logTree.itemAbove(self.logTree.currentItem())
1608 if itm: 1684 if itm:
1609 self.logTree.setCurrentItem(itm) 1685 self.logTree.setCurrentItem(itm)
1610 1686
1611 @pyqtSlot() 1687 @pyqtSlot()
1612 def on_downButton_clicked(self): 1688 def on_downButton_clicked(self):
1613 """ 1689 """
1614 Private slot to move the current item down one entry. 1690 Private slot to move the current item down one entry.
1615 """ 1691 """
1619 else: 1695 else:
1620 # load the next bunch and try again 1696 # load the next bunch and try again
1621 if self.nextButton.isEnabled(): 1697 if self.nextButton.isEnabled():
1622 self.__addFinishCallback(self.on_downButton_clicked) 1698 self.__addFinishCallback(self.on_downButton_clicked)
1623 self.on_nextButton_clicked() 1699 self.on_nextButton_clicked()
1624 1700
1625 @pyqtSlot() 1701 @pyqtSlot()
1626 def on_nextButton_clicked(self): 1702 def on_nextButton_clicked(self):
1627 """ 1703 """
1628 Private slot to handle the Next button. 1704 Private slot to handle the Next button.
1629 """ 1705 """
1630 if self.__skipEntries > 0 and self.nextButton.isEnabled(): 1706 if self.__skipEntries > 0 and self.nextButton.isEnabled():
1631 self.__getLogEntries(skip=self.__skipEntries) 1707 self.__getLogEntries(skip=self.__skipEntries)
1632 1708
1633 @pyqtSlot(QDate) 1709 @pyqtSlot(QDate)
1634 def on_fromDate_dateChanged(self, date): 1710 def on_fromDate_dateChanged(self, date):
1635 """ 1711 """
1636 Private slot called, when the from date changes. 1712 Private slot called, when the from date changes.
1637 1713
1638 @param date new date (QDate) 1714 @param date new date (QDate)
1639 """ 1715 """
1640 if self.__actionMode() == "filter": 1716 if self.__actionMode() == "filter":
1641 self.__filterLogs() 1717 self.__filterLogs()
1642 1718
1643 @pyqtSlot(QDate) 1719 @pyqtSlot(QDate)
1644 def on_toDate_dateChanged(self, date): 1720 def on_toDate_dateChanged(self, date):
1645 """ 1721 """
1646 Private slot called, when the from date changes. 1722 Private slot called, when the from date changes.
1647 1723
1648 @param date new date (QDate) 1724 @param date new date (QDate)
1649 """ 1725 """
1650 if self.__actionMode() == "filter": 1726 if self.__actionMode() == "filter":
1651 self.__filterLogs() 1727 self.__filterLogs()
1652 1728
1653 @pyqtSlot(int) 1729 @pyqtSlot(int)
1654 def on_fieldCombo_activated(self, index): 1730 def on_fieldCombo_activated(self, index):
1655 """ 1731 """
1656 Private slot called, when a new filter field is selected. 1732 Private slot called, when a new filter field is selected.
1657 1733
1658 @param index index of the selected entry 1734 @param index index of the selected entry
1659 @type int 1735 @type int
1660 """ 1736 """
1661 if self.__actionMode() == "filter": 1737 if self.__actionMode() == "filter":
1662 self.__filterLogs() 1738 self.__filterLogs()
1663 1739
1664 @pyqtSlot(str) 1740 @pyqtSlot(str)
1665 def on_rxEdit_textChanged(self, txt): 1741 def on_rxEdit_textChanged(self, txt):
1666 """ 1742 """
1667 Private slot called, when a filter expression is entered. 1743 Private slot called, when a filter expression is entered.
1668 1744
1669 @param txt filter expression (string) 1745 @param txt filter expression (string)
1670 """ 1746 """
1671 if self.__actionMode() == "filter": 1747 if self.__actionMode() == "filter":
1672 self.__filterLogs() 1748 self.__filterLogs()
1673 elif self.__actionMode() == "find": 1749 elif self.__actionMode() == "find":
1674 self.__findItem(self.__findBackwards, interactive=True) 1750 self.__findItem(self.__findBackwards, interactive=True)
1675 1751
1676 @pyqtSlot() 1752 @pyqtSlot()
1677 def on_rxEdit_returnPressed(self): 1753 def on_rxEdit_returnPressed(self):
1678 """ 1754 """
1679 Private slot handling a press of the Return key in the rxEdit input. 1755 Private slot handling a press of the Return key in the rxEdit input.
1680 """ 1756 """
1681 if self.__actionMode() == "find": 1757 if self.__actionMode() == "find":
1682 self.__findItem(self.__findBackwards, interactive=True) 1758 self.__findItem(self.__findBackwards, interactive=True)
1683 1759
1684 @pyqtSlot(bool) 1760 @pyqtSlot(bool)
1685 def on_stopCheckBox_clicked(self, checked): 1761 def on_stopCheckBox_clicked(self, checked):
1686 """ 1762 """
1687 Private slot called, when the stop on copy/move checkbox is clicked. 1763 Private slot called, when the stop on copy/move checkbox is clicked.
1688 1764
1689 @param checked flag indicating the state of the check box (boolean) 1765 @param checked flag indicating the state of the check box (boolean)
1690 """ 1766 """
1691 self.vcs.getPlugin().setPreferences("StopLogOnCopy", 1767 self.vcs.getPlugin().setPreferences(
1692 self.stopCheckBox.isChecked()) 1768 "StopLogOnCopy", self.stopCheckBox.isChecked()
1769 )
1693 self.nextButton.setEnabled(True) 1770 self.nextButton.setEnabled(True)
1694 self.limitSpinBox.setEnabled(True) 1771 self.limitSpinBox.setEnabled(True)
1695 1772
1696 ################################################################## 1773 ##################################################################
1697 ## Tool button menu action methods below 1774 ## Tool button menu action methods below
1698 ################################################################## 1775 ##################################################################
1699 1776
1700 @pyqtSlot() 1777 @pyqtSlot()
1701 def __cherryActTriggered(self): 1778 def __cherryActTriggered(self):
1702 """ 1779 """
1703 Private slot to handle the Copy Commits action. 1780 Private slot to handle the Copy Commits action.
1704 """ 1781 """
1705 commits = {} 1782 commits = {}
1706 1783
1707 for itm in self.logTree.selectedItems(): 1784 for itm in self.logTree.selectedItems():
1708 index = self.logTree.indexOfTopLevelItem(itm) 1785 index = self.logTree.indexOfTopLevelItem(itm)
1709 commits[index] = itm.text(self.CommitIdColumn) 1786 commits[index] = itm.text(self.CommitIdColumn)
1710 1787
1711 if commits: 1788 if commits:
1712 pfile = ( 1789 pfile = pathlib.Path(ericApp().getObject("Project").getProjectFile())
1713 pathlib.Path(ericApp().getObject("Project").getProjectFile())
1714 )
1715 lastModified = pfile.stat().st_mtime 1790 lastModified = pfile.stat().st_mtime
1716 shouldReopen = ( 1791 shouldReopen = (
1717 self.vcs.gitCherryPick( 1792 self.vcs.gitCherryPick(
1718 self.repodir, 1793 self.repodir,
1719 [commits[i] for i in sorted(commits.keys(), reverse=True)] 1794 [commits[i] for i in sorted(commits.keys(), reverse=True)],
1720 ) or 1795 )
1721 pfile.stat().st_mtime != lastModified 1796 or pfile.stat().st_mtime != lastModified
1722 ) 1797 )
1723 if shouldReopen: 1798 if shouldReopen:
1724 res = EricMessageBox.yesNo( 1799 res = EricMessageBox.yesNo(
1725 None, 1800 None,
1726 self.tr("Copy Changesets"), 1801 self.tr("Copy Changesets"),
1727 self.tr( 1802 self.tr("""The project should be reread. Do this now?"""),
1728 """The project should be reread. Do this now?"""), 1803 yesDefault=True,
1729 yesDefault=True) 1804 )
1730 if res: 1805 if res:
1731 ericApp().getObject("Project").reopenProject() 1806 ericApp().getObject("Project").reopenProject()
1732 return 1807 return
1733 1808
1734 self.on_refreshButton_clicked() 1809 self.on_refreshButton_clicked()
1735 1810
1736 @pyqtSlot() 1811 @pyqtSlot()
1737 def __tagActTriggered(self): 1812 def __tagActTriggered(self):
1738 """ 1813 """
1739 Private slot to tag the selected commit. 1814 Private slot to tag the selected commit.
1740 """ 1815 """
1743 commit = itm.text(self.CommitIdColumn) 1818 commit = itm.text(self.CommitIdColumn)
1744 tag = itm.text(self.TagsColumn).strip().split(", ", 1)[0] 1819 tag = itm.text(self.TagsColumn).strip().split(", ", 1)[0]
1745 res = self.vcs.vcsTag(self.repodir, revision=commit, tagName=tag) 1820 res = self.vcs.vcsTag(self.repodir, revision=commit, tagName=tag)
1746 if res: 1821 if res:
1747 self.on_refreshButton_clicked() 1822 self.on_refreshButton_clicked()
1748 1823
1749 @pyqtSlot() 1824 @pyqtSlot()
1750 def __switchActTriggered(self): 1825 def __switchActTriggered(self):
1751 """ 1826 """
1752 Private slot to switch the working directory to the 1827 Private slot to switch the working directory to the
1753 selected commit. 1828 selected commit.
1754 """ 1829 """
1755 if len(self.logTree.selectedItems()) == 1: 1830 if len(self.logTree.selectedItems()) == 1:
1756 itm = self.logTree.selectedItems()[0] 1831 itm = self.logTree.selectedItems()[0]
1757 commit = itm.text(self.CommitIdColumn) 1832 commit = itm.text(self.CommitIdColumn)
1758 branches = [b for b in itm.text(self.BranchColumn).split(", ") 1833 branches = [
1759 if "/" not in b] 1834 b for b in itm.text(self.BranchColumn).split(", ") if "/" not in b
1835 ]
1760 if len(branches) == 1: 1836 if len(branches) == 1:
1761 branch = branches[0] 1837 branch = branches[0]
1762 elif len(branches) > 1: 1838 elif len(branches) > 1:
1763 branch, ok = QInputDialog.getItem( 1839 branch, ok = QInputDialog.getItem(
1764 self, 1840 self,
1765 self.tr("Switch"), 1841 self.tr("Switch"),
1766 self.tr("Select a branch"), 1842 self.tr("Select a branch"),
1767 [""] + branches, 1843 [""] + branches,
1768 0, False) 1844 0,
1845 False,
1846 )
1769 if not ok: 1847 if not ok:
1770 return 1848 return
1771 else: 1849 else:
1772 branch = "" 1850 branch = ""
1773 if branch: 1851 if branch:
1774 rev = branch 1852 rev = branch
1775 else: 1853 else:
1776 rev = commit 1854 rev = commit
1777 pfile = ( 1855 pfile = pathlib.Path(ericApp().getObject("Project").getProjectFile())
1778 pathlib.Path(ericApp().getObject("Project").getProjectFile())
1779 )
1780 lastModified = pfile.stat().st_mtime 1856 lastModified = pfile.stat().st_mtime
1781 shouldReopen = ( 1857 shouldReopen = (
1782 self.vcs.vcsUpdate(self.repodir, revision=rev) or 1858 self.vcs.vcsUpdate(self.repodir, revision=rev)
1783 pfile.stat().st_mtime != lastModified 1859 or pfile.stat().st_mtime != lastModified
1784 ) 1860 )
1785 if shouldReopen: 1861 if shouldReopen:
1786 res = EricMessageBox.yesNo( 1862 res = EricMessageBox.yesNo(
1787 None, 1863 None,
1788 self.tr("Switch"), 1864 self.tr("Switch"),
1789 self.tr( 1865 self.tr("""The project should be reread. Do this now?"""),
1790 """The project should be reread. Do this now?"""), 1866 yesDefault=True,
1791 yesDefault=True) 1867 )
1792 if res: 1868 if res:
1793 ericApp().getObject("Project").reopenProject() 1869 ericApp().getObject("Project").reopenProject()
1794 return 1870 return
1795 1871
1796 self.on_refreshButton_clicked() 1872 self.on_refreshButton_clicked()
1797 1873
1798 @pyqtSlot() 1874 @pyqtSlot()
1799 def __branchActTriggered(self): 1875 def __branchActTriggered(self):
1800 """ 1876 """
1801 Private slot to create a new branch starting at the selected commit. 1877 Private slot to create a new branch starting at the selected commit.
1802 """ 1878 """
1803 if len(self.logTree.selectedItems()) == 1: 1879 if len(self.logTree.selectedItems()) == 1:
1804 from .GitBranchDialog import GitBranchDialog 1880 from .GitBranchDialog import GitBranchDialog
1881
1805 itm = self.logTree.selectedItems()[0] 1882 itm = self.logTree.selectedItems()[0]
1806 commit = itm.text(self.CommitIdColumn) 1883 commit = itm.text(self.CommitIdColumn)
1807 branches = [b for b in itm.text(self.BranchColumn).split(", ") 1884 branches = [
1808 if "/" not in b] 1885 b for b in itm.text(self.BranchColumn).split(", ") if "/" not in b
1886 ]
1809 if len(branches) == 1: 1887 if len(branches) == 1:
1810 branch = branches[0] 1888 branch = branches[0]
1811 elif len(branches) > 1: 1889 elif len(branches) > 1:
1812 branch, ok = QInputDialog.getItem( 1890 branch, ok = QInputDialog.getItem(
1813 self, 1891 self,
1814 self.tr("Branch"), 1892 self.tr("Branch"),
1815 self.tr("Select a default branch"), 1893 self.tr("Select a default branch"),
1816 [""] + branches, 1894 [""] + branches,
1817 0, False) 1895 0,
1896 False,
1897 )
1818 if not ok: 1898 if not ok:
1819 return 1899 return
1820 else: 1900 else:
1821 branch = "" 1901 branch = ""
1822 res = self.vcs.gitBranch( 1902 res = self.vcs.gitBranch(
1823 self.repodir, revision=commit, branchName=branch, 1903 self.repodir,
1824 branchOp=GitBranchDialog.CreateBranch) 1904 revision=commit,
1905 branchName=branch,
1906 branchOp=GitBranchDialog.CreateBranch,
1907 )
1825 if res: 1908 if res:
1826 self.on_refreshButton_clicked() 1909 self.on_refreshButton_clicked()
1827 1910
1828 @pyqtSlot() 1911 @pyqtSlot()
1829 def __branchSwitchActTriggered(self): 1912 def __branchSwitchActTriggered(self):
1830 """ 1913 """
1831 Private slot to create a new branch starting at the selected commit 1914 Private slot to create a new branch starting at the selected commit
1832 and switch the work tree to it. 1915 and switch the work tree to it.
1833 """ 1916 """
1834 if len(self.logTree.selectedItems()) == 1: 1917 if len(self.logTree.selectedItems()) == 1:
1835 from .GitBranchDialog import GitBranchDialog 1918 from .GitBranchDialog import GitBranchDialog
1919
1836 itm = self.logTree.selectedItems()[0] 1920 itm = self.logTree.selectedItems()[0]
1837 commit = itm.text(self.CommitIdColumn) 1921 commit = itm.text(self.CommitIdColumn)
1838 branches = [b for b in itm.text(self.BranchColumn).split(", ") 1922 branches = [
1839 if "/" not in b] 1923 b for b in itm.text(self.BranchColumn).split(", ") if "/" not in b
1924 ]
1840 if len(branches) == 1: 1925 if len(branches) == 1:
1841 branch = branches[0] 1926 branch = branches[0]
1842 elif len(branches) > 1: 1927 elif len(branches) > 1:
1843 branch, ok = QInputDialog.getItem( 1928 branch, ok = QInputDialog.getItem(
1844 self, 1929 self,
1845 self.tr("Branch & Switch"), 1930 self.tr("Branch & Switch"),
1846 self.tr("Select a default branch"), 1931 self.tr("Select a default branch"),
1847 [""] + branches, 1932 [""] + branches,
1848 0, False) 1933 0,
1934 False,
1935 )
1849 if not ok: 1936 if not ok:
1850 return 1937 return
1851 else: 1938 else:
1852 branch = "" 1939 branch = ""
1853 pfile = ( 1940 pfile = pathlib.Path(ericApp().getObject("Project").getProjectFile())
1854 pathlib.Path(ericApp().getObject("Project").getProjectFile())
1855 )
1856 lastModified = pfile.stat().st_mtime 1941 lastModified = pfile.stat().st_mtime
1857 res, shouldReopen = self.vcs.gitBranch( 1942 res, shouldReopen = self.vcs.gitBranch(
1858 self.repodir, revision=commit, branchName=branch, 1943 self.repodir,
1859 branchOp=GitBranchDialog.CreateSwitchBranch) 1944 revision=commit,
1945 branchName=branch,
1946 branchOp=GitBranchDialog.CreateSwitchBranch,
1947 )
1860 shouldReopen |= pfile.stat().st_mtime != lastModified 1948 shouldReopen |= pfile.stat().st_mtime != lastModified
1861 if res: 1949 if res:
1862 if shouldReopen: 1950 if shouldReopen:
1863 res = EricMessageBox.yesNo( 1951 res = EricMessageBox.yesNo(
1864 None, 1952 None,
1865 self.tr("Switch"), 1953 self.tr("Switch"),
1866 self.tr( 1954 self.tr("""The project should be reread. Do this now?"""),
1867 """The project should be reread. Do this now?"""), 1955 yesDefault=True,
1868 yesDefault=True) 1956 )
1869 if res: 1957 if res:
1870 ericApp().getObject("Project").reopenProject() 1958 ericApp().getObject("Project").reopenProject()
1871 return 1959 return
1872 1960
1873 self.on_refreshButton_clicked() 1961 self.on_refreshButton_clicked()
1874 1962
1875 @pyqtSlot() 1963 @pyqtSlot()
1876 def __shortlogActTriggered(self): 1964 def __shortlogActTriggered(self):
1877 """ 1965 """
1878 Private slot to show a short log suitable for release announcements. 1966 Private slot to show a short log suitable for release announcements.
1879 """ 1967 """
1880 if len(self.logTree.selectedItems()) == 1: 1968 if len(self.logTree.selectedItems()) == 1:
1881 itm = self.logTree.selectedItems()[0] 1969 itm = self.logTree.selectedItems()[0]
1882 commit = itm.text(self.CommitIdColumn) 1970 commit = itm.text(self.CommitIdColumn)
1883 branch = itm.text(self.BranchColumn).split(", ", 1)[0] 1971 branch = itm.text(self.BranchColumn).split(", ", 1)[0]
1884 branches = [b for b in itm.text(self.BranchColumn).split(", ") 1972 branches = [
1885 if "/" not in b] 1973 b for b in itm.text(self.BranchColumn).split(", ") if "/" not in b
1974 ]
1886 if len(branches) == 1: 1975 if len(branches) == 1:
1887 branch = branches[0] 1976 branch = branches[0]
1888 elif len(branches) > 1: 1977 elif len(branches) > 1:
1889 branch, ok = QInputDialog.getItem( 1978 branch, ok = QInputDialog.getItem(
1890 self, 1979 self,
1891 self.tr("Show Short Log"), 1980 self.tr("Show Short Log"),
1892 self.tr("Select a branch"), 1981 self.tr("Select a branch"),
1893 [""] + branches, 1982 [""] + branches,
1894 0, False) 1983 0,
1984 False,
1985 )
1895 if not ok: 1986 if not ok:
1896 return 1987 return
1897 else: 1988 else:
1898 branch = "" 1989 branch = ""
1899 if branch: 1990 if branch:
1900 rev = branch 1991 rev = branch
1901 else: 1992 else:
1902 rev = commit 1993 rev = commit
1903 self.vcs.gitShortlog(self.repodir, commit=rev) 1994 self.vcs.gitShortlog(self.repodir, commit=rev)
1904 1995
1905 @pyqtSlot() 1996 @pyqtSlot()
1906 def __describeActTriggered(self): 1997 def __describeActTriggered(self):
1907 """ 1998 """
1908 Private slot to show the most recent tag reachable from a commit. 1999 Private slot to show the most recent tag reachable from a commit.
1909 """ 2000 """
1910 commits = [] 2001 commits = []
1911 2002
1912 for itm in self.logTree.selectedItems(): 2003 for itm in self.logTree.selectedItems():
1913 commits.append(itm.text(self.CommitIdColumn)) 2004 commits.append(itm.text(self.CommitIdColumn))
1914 2005
1915 if commits: 2006 if commits:
1916 self.vcs.gitDescribe(self.repodir, commits) 2007 self.vcs.gitDescribe(self.repodir, commits)
1917 2008
1918 ################################################################## 2009 ##################################################################
1919 ## Log context menu action methods below 2010 ## Log context menu action methods below
1920 ################################################################## 2011 ##################################################################
1921 2012
1922 @pyqtSlot(bool) 2013 @pyqtSlot(bool)
1923 def __showCommitterColumns(self, on): 2014 def __showCommitterColumns(self, on):
1924 """ 2015 """
1925 Private slot to show/hide the committer columns. 2016 Private slot to show/hide the committer columns.
1926 2017
1927 @param on flag indicating the selection state (boolean) 2018 @param on flag indicating the selection state (boolean)
1928 """ 2019 """
1929 self.logTree.setColumnHidden(self.CommitterColumn, not on) 2020 self.logTree.setColumnHidden(self.CommitterColumn, not on)
1930 self.logTree.setColumnHidden(self.CommitDateColumn, not on) 2021 self.logTree.setColumnHidden(self.CommitDateColumn, not on)
1931 self.vcs.getPlugin().setPreferences("ShowCommitterColumns", on) 2022 self.vcs.getPlugin().setPreferences("ShowCommitterColumns", on)
1932 self.__resizeColumnsLog() 2023 self.__resizeColumnsLog()
1933 2024
1934 @pyqtSlot(bool) 2025 @pyqtSlot(bool)
1935 def __showAuthorColumns(self, on): 2026 def __showAuthorColumns(self, on):
1936 """ 2027 """
1937 Private slot to show/hide the committer columns. 2028 Private slot to show/hide the committer columns.
1938 2029
1939 @param on flag indicating the selection state (boolean) 2030 @param on flag indicating the selection state (boolean)
1940 """ 2031 """
1941 self.logTree.setColumnHidden(self.AuthorColumn, not on) 2032 self.logTree.setColumnHidden(self.AuthorColumn, not on)
1942 self.logTree.setColumnHidden(self.DateColumn, not on) 2033 self.logTree.setColumnHidden(self.DateColumn, not on)
1943 self.vcs.getPlugin().setPreferences("ShowAuthorColumns", on) 2034 self.vcs.getPlugin().setPreferences("ShowAuthorColumns", on)
1944 self.__resizeColumnsLog() 2035 self.__resizeColumnsLog()
1945 2036
1946 @pyqtSlot(bool) 2037 @pyqtSlot(bool)
1947 def __showCommitIdColumn(self, on): 2038 def __showCommitIdColumn(self, on):
1948 """ 2039 """
1949 Private slot to show/hide the commit ID column. 2040 Private slot to show/hide the commit ID column.
1950 2041
1951 @param on flag indicating the selection state (boolean) 2042 @param on flag indicating the selection state (boolean)
1952 """ 2043 """
1953 self.logTree.setColumnHidden(self.CommitIdColumn, not on) 2044 self.logTree.setColumnHidden(self.CommitIdColumn, not on)
1954 self.vcs.getPlugin().setPreferences("ShowCommitIdColumn", on) 2045 self.vcs.getPlugin().setPreferences("ShowCommitIdColumn", on)
1955 self.__resizeColumnsLog() 2046 self.__resizeColumnsLog()
1956 2047
1957 @pyqtSlot(bool) 2048 @pyqtSlot(bool)
1958 def __showBranchesColumn(self, on): 2049 def __showBranchesColumn(self, on):
1959 """ 2050 """
1960 Private slot to show/hide the branches column. 2051 Private slot to show/hide the branches column.
1961 2052
1962 @param on flag indicating the selection state (boolean) 2053 @param on flag indicating the selection state (boolean)
1963 """ 2054 """
1964 self.logTree.setColumnHidden(self.BranchColumn, not on) 2055 self.logTree.setColumnHidden(self.BranchColumn, not on)
1965 self.vcs.getPlugin().setPreferences("ShowBranchesColumn", on) 2056 self.vcs.getPlugin().setPreferences("ShowBranchesColumn", on)
1966 self.__resizeColumnsLog() 2057 self.__resizeColumnsLog()
1967 2058
1968 @pyqtSlot(bool) 2059 @pyqtSlot(bool)
1969 def __showTagsColumn(self, on): 2060 def __showTagsColumn(self, on):
1970 """ 2061 """
1971 Private slot to show/hide the tags column. 2062 Private slot to show/hide the tags column.
1972 2063
1973 @param on flag indicating the selection state (boolean) 2064 @param on flag indicating the selection state (boolean)
1974 """ 2065 """
1975 self.logTree.setColumnHidden(self.TagsColumn, not on) 2066 self.logTree.setColumnHidden(self.TagsColumn, not on)
1976 self.vcs.getPlugin().setPreferences("ShowTagsColumn", on) 2067 self.vcs.getPlugin().setPreferences("ShowTagsColumn", on)
1977 self.__resizeColumnsLog() 2068 self.__resizeColumnsLog()
1978 2069
1979 ################################################################## 2070 ##################################################################
1980 ## Search and filter methods below 2071 ## Search and filter methods below
1981 ################################################################## 2072 ##################################################################
1982 2073
1983 def __actionMode(self): 2074 def __actionMode(self):
1984 """ 2075 """
1985 Private method to get the selected action mode. 2076 Private method to get the selected action mode.
1986 2077
1987 @return selected action mode (string, one of filter or find) 2078 @return selected action mode (string, one of filter or find)
1988 """ 2079 """
1989 return self.modeComboBox.itemData( 2080 return self.modeComboBox.itemData(self.modeComboBox.currentIndex())
1990 self.modeComboBox.currentIndex()) 2081
1991
1992 @pyqtSlot(int) 2082 @pyqtSlot(int)
1993 def on_modeComboBox_currentIndexChanged(self, index): 2083 def on_modeComboBox_currentIndexChanged(self, index):
1994 """ 2084 """
1995 Private slot to react on mode changes. 2085 Private slot to react on mode changes.
1996 2086
1997 @param index index of the selected entry (integer) 2087 @param index index of the selected entry (integer)
1998 """ 2088 """
1999 mode = self.modeComboBox.itemData(index) 2089 mode = self.modeComboBox.itemData(index)
2000 findMode = mode == "find" 2090 findMode = mode == "find"
2001 filterMode = mode == "filter" 2091 filterMode = mode == "filter"
2002 2092
2003 self.fromDate.setEnabled(filterMode) 2093 self.fromDate.setEnabled(filterMode)
2004 self.toDate.setEnabled(filterMode) 2094 self.toDate.setEnabled(filterMode)
2005 self.findPrevButton.setVisible(findMode) 2095 self.findPrevButton.setVisible(findMode)
2006 self.findNextButton.setVisible(findMode) 2096 self.findNextButton.setVisible(findMode)
2007 2097
2008 if findMode: 2098 if findMode:
2009 for topIndex in range(self.logTree.topLevelItemCount()): 2099 for topIndex in range(self.logTree.topLevelItemCount()):
2010 self.logTree.topLevelItem(topIndex).setHidden(False) 2100 self.logTree.topLevelItem(topIndex).setHidden(False)
2011 self.logTree.header().setSectionHidden(self.IconColumn, False) 2101 self.logTree.header().setSectionHidden(self.IconColumn, False)
2012 elif filterMode: 2102 elif filterMode:
2013 self.__filterLogs() 2103 self.__filterLogs()
2014 2104
2015 @pyqtSlot() 2105 @pyqtSlot()
2016 def on_findPrevButton_clicked(self): 2106 def on_findPrevButton_clicked(self):
2017 """ 2107 """
2018 Private slot to find the previous item matching the entered criteria. 2108 Private slot to find the previous item matching the entered criteria.
2019 """ 2109 """
2020 self.__findItem(True) 2110 self.__findItem(True)
2021 2111
2022 @pyqtSlot() 2112 @pyqtSlot()
2023 def on_findNextButton_clicked(self): 2113 def on_findNextButton_clicked(self):
2024 """ 2114 """
2025 Private slot to find the next item matching the entered criteria. 2115 Private slot to find the next item matching the entered criteria.
2026 """ 2116 """
2027 self.__findItem(False) 2117 self.__findItem(False)
2028 2118
2029 def __findItem(self, backwards=False, interactive=False): 2119 def __findItem(self, backwards=False, interactive=False):
2030 """ 2120 """
2031 Private slot to find an item matching the entered criteria. 2121 Private slot to find an item matching the entered criteria.
2032 2122
2033 @param backwards flag indicating to search backwards (boolean) 2123 @param backwards flag indicating to search backwards (boolean)
2034 @param interactive flag indicating an interactive search (boolean) 2124 @param interactive flag indicating an interactive search (boolean)
2035 """ 2125 """
2036 self.__findBackwards = backwards 2126 self.__findBackwards = backwards
2037 2127
2038 fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch() 2128 fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch()
2039 currentIndex = self.logTree.indexOfTopLevelItem( 2129 currentIndex = self.logTree.indexOfTopLevelItem(self.logTree.currentItem())
2040 self.logTree.currentItem())
2041 if backwards: 2130 if backwards:
2042 if interactive: 2131 if interactive:
2043 indexes = range(currentIndex, -1, -1) 2132 indexes = range(currentIndex, -1, -1)
2044 else: 2133 else:
2045 indexes = range(currentIndex - 1, -1, -1) 2134 indexes = range(currentIndex - 1, -1, -1)
2046 else: 2135 else:
2047 if interactive: 2136 if interactive:
2048 indexes = range(currentIndex, self.logTree.topLevelItemCount()) 2137 indexes = range(currentIndex, self.logTree.topLevelItemCount())
2049 else: 2138 else:
2050 indexes = range(currentIndex + 1, 2139 indexes = range(currentIndex + 1, self.logTree.topLevelItemCount())
2051 self.logTree.topLevelItemCount()) 2140
2052
2053 for index in indexes: 2141 for index in indexes:
2054 topItem = self.logTree.topLevelItem(index) 2142 topItem = self.logTree.topLevelItem(index)
2055 if indexIsRole: 2143 if indexIsRole:
2056 if fieldIndex == self.__changesRole: 2144 if fieldIndex == self.__changesRole:
2057 changes = topItem.data(0, self.__changesRole) 2145 changes = topItem.data(0, self.__changesRole)
2058 txt = "\n".join( 2146 txt = "\n".join(
2059 [c["path"] for c in changes] + 2147 [c["path"] for c in changes] + [c["copyfrom"] for c in changes]
2060 [c["copyfrom"] for c in changes]
2061 ) 2148 )
2062 else: 2149 else:
2063 # Filter based on complete subject text 2150 # Filter based on complete subject text
2064 txt = topItem.data(0, self.__subjectRole) 2151 txt = topItem.data(0, self.__subjectRole)
2065 else: 2152 else:
2069 break 2156 break
2070 else: 2157 else:
2071 EricMessageBox.information( 2158 EricMessageBox.information(
2072 self, 2159 self,
2073 self.tr("Find Commit"), 2160 self.tr("Find Commit"),
2074 self.tr("""'{0}' was not found.""").format(self.rxEdit.text())) 2161 self.tr("""'{0}' was not found.""").format(self.rxEdit.text()),
2075 2162 )
2163
2076 ################################################################## 2164 ##################################################################
2077 ## Commit navigation methods below 2165 ## Commit navigation methods below
2078 ################################################################## 2166 ##################################################################
2079 2167
2080 def __commitIdClicked(self, url): 2168 def __commitIdClicked(self, url):
2081 """ 2169 """
2082 Private slot to handle the anchorClicked signal of the changeset 2170 Private slot to handle the anchorClicked signal of the changeset
2083 details pane. 2171 details pane.
2084 2172
2085 @param url URL that was clicked 2173 @param url URL that was clicked
2086 @type QUrl 2174 @type QUrl
2087 """ 2175 """
2088 if url.scheme() == "rev": 2176 if url.scheme() == "rev":
2089 # a commit ID was clicked, show the respective item 2177 # a commit ID was clicked, show the respective item
2090 commitId = url.path() 2178 commitId = url.path()
2091 items = self.logTree.findItems( 2179 items = self.logTree.findItems(
2092 commitId, Qt.MatchFlag.MatchStartsWith, self.CommitIdColumn) 2180 commitId, Qt.MatchFlag.MatchStartsWith, self.CommitIdColumn
2181 )
2093 if items: 2182 if items:
2094 itm = items[0] 2183 itm = items[0]
2095 if itm.isHidden(): 2184 if itm.isHidden():
2096 itm.setHidden(False) 2185 itm.setHidden(False)
2097 self.logTree.setCurrentItem(itm) 2186 self.logTree.setCurrentItem(itm)
2098 else: 2187 else:
2099 # load the next batch and try again 2188 # load the next batch and try again
2100 if self.nextButton.isEnabled(): 2189 if self.nextButton.isEnabled():
2101 self.__addFinishCallback( 2190 self.__addFinishCallback(lambda: self.__commitIdClicked(url))
2102 lambda: self.__commitIdClicked(url))
2103 self.on_nextButton_clicked() 2191 self.on_nextButton_clicked()
2104 2192
2105 ########################################################################### 2193 ###########################################################################
2106 ## Diff handling methods below 2194 ## Diff handling methods below
2107 ########################################################################### 2195 ###########################################################################
2108 2196
2109 def __generateDiffs(self, parent=1): 2197 def __generateDiffs(self, parent=1):
2110 """ 2198 """
2111 Private slot to generate diff outputs for the selected item. 2199 Private slot to generate diff outputs for the selected item.
2112 2200
2113 @param parent number of parent to diff against 2201 @param parent number of parent to diff against
2114 @type int 2202 @type int
2115 """ 2203 """
2116 self.diffEdit.clear() 2204 self.diffEdit.clear()
2117 self.diffLabel.setText(self.tr("Differences")) 2205 self.diffLabel.setText(self.tr("Differences"))
2118 self.diffSelectLabel.clear() 2206 self.diffSelectLabel.clear()
2119 with contextlib.suppress(AttributeError): 2207 with contextlib.suppress(AttributeError):
2120 self.diffHighlighter.regenerateRules() 2208 self.diffHighlighter.regenerateRules()
2121 2209
2122 selectedItems = self.logTree.selectedItems() 2210 selectedItems = self.logTree.selectedItems()
2123 if len(selectedItems) == 1: 2211 if len(selectedItems) == 1:
2124 currentItem = selectedItems[0] 2212 currentItem = selectedItems[0]
2125 commit2 = currentItem.text(self.CommitIdColumn) 2213 commit2 = currentItem.text(self.CommitIdColumn)
2126 parents = currentItem.data(0, self.__parentsRole) 2214 parents = currentItem.data(0, self.__parentsRole)
2127 if len(parents) >= parent: 2215 if len(parents) >= parent:
2128 self.diffLabel.setText( 2216 self.diffLabel.setText(
2129 self.tr("Differences to Parent {0}").format(parent)) 2217 self.tr("Differences to Parent {0}").format(parent)
2218 )
2130 commit1 = parents[parent - 1] 2219 commit1 = parents[parent - 1]
2131 2220
2132 self.__diffGenerator.start(self.__filename, [commit1, commit2]) 2221 self.__diffGenerator.start(self.__filename, [commit1, commit2])
2133 2222
2134 if len(parents) > 1: 2223 if len(parents) > 1:
2135 parentLinks = [] 2224 parentLinks = []
2136 for index in range(1, len(parents) + 1): 2225 for index in range(1, len(parents) + 1):
2137 if parent == index: 2226 if parent == index:
2138 parentLinks.append("&nbsp;{0}&nbsp;".format(index)) 2227 parentLinks.append("&nbsp;{0}&nbsp;".format(index))
2139 else: 2228 else:
2140 parentLinks.append( 2229 parentLinks.append(
2141 '<a href="diff:{0}">&nbsp;{0}&nbsp;</a>' 2230 '<a href="diff:{0}">&nbsp;{0}&nbsp;</a>'.format(index)
2142 .format(index)) 2231 )
2143 self.diffSelectLabel.setText( 2232 self.diffSelectLabel.setText(
2144 self.tr('Diff to Parent {0}') 2233 self.tr("Diff to Parent {0}").format(" ".join(parentLinks))
2145 .format(" ".join(parentLinks))) 2234 )
2146 elif len(selectedItems) == 2: 2235 elif len(selectedItems) == 2:
2147 commit2 = selectedItems[0].text(self.CommitIdColumn) 2236 commit2 = selectedItems[0].text(self.CommitIdColumn)
2148 commit1 = selectedItems[1].text(self.CommitIdColumn) 2237 commit1 = selectedItems[1].text(self.CommitIdColumn)
2149 index2 = self.logTree.indexOfTopLevelItem(selectedItems[0]) 2238 index2 = self.logTree.indexOfTopLevelItem(selectedItems[0])
2150 index1 = self.logTree.indexOfTopLevelItem(selectedItems[1]) 2239 index1 = self.logTree.indexOfTopLevelItem(selectedItems[1])
2151 2240
2152 if index2 < index1: 2241 if index2 < index1:
2153 # swap to always compare old to new 2242 # swap to always compare old to new
2154 commit1, commit2 = commit2, commit1 2243 commit1, commit2 = commit2, commit1
2155 2244
2156 self.__diffGenerator.start(self.__filename, [commit1, commit2]) 2245 self.__diffGenerator.start(self.__filename, [commit1, commit2])
2157 2246
2158 def __generatorFinished(self): 2247 def __generatorFinished(self):
2159 """ 2248 """
2160 Private slot connected to the finished signal of the diff generator. 2249 Private slot connected to the finished signal of the diff generator.
2161 """ 2250 """
2162 diff, _, errors, fileSeparators = self.__diffGenerator.getResult() 2251 diff, _, errors, fileSeparators = self.__diffGenerator.getResult()
2163 2252
2164 if diff: 2253 if diff:
2165 self.diffEdit.setPlainText("".join(diff)) 2254 self.diffEdit.setPlainText("".join(diff))
2166 elif errors: 2255 elif errors:
2167 self.diffEdit.setPlainText("".join(errors)) 2256 self.diffEdit.setPlainText("".join(errors))
2168 else: 2257 else:
2169 self.diffEdit.setPlainText(self.tr('There is no difference.')) 2258 self.diffEdit.setPlainText(self.tr("There is no difference."))
2170 2259
2171 self.saveLabel.setVisible(bool(diff)) 2260 self.saveLabel.setVisible(bool(diff))
2172 2261
2173 fileSeparators = self.__mergeFileSeparators(fileSeparators) 2262 fileSeparators = self.__mergeFileSeparators(fileSeparators)
2174 if self.__diffUpdatesFiles: 2263 if self.__diffUpdatesFiles:
2175 for oldFileName, newFileName, lineNumber, _ in fileSeparators: 2264 for oldFileName, newFileName, lineNumber, _ in fileSeparators:
2176 if oldFileName == newFileName: 2265 if oldFileName == newFileName:
2177 item = QTreeWidgetItem(self.filesTree, ["", oldFileName]) 2266 item = QTreeWidgetItem(self.filesTree, ["", oldFileName])
2178 elif oldFileName == "/dev/null": 2267 elif oldFileName == "/dev/null":
2179 item = QTreeWidgetItem(self.filesTree, ["", newFileName]) 2268 item = QTreeWidgetItem(self.filesTree, ["", newFileName])
2180 else: 2269 else:
2181 item = QTreeWidgetItem( 2270 item = QTreeWidgetItem(
2182 self.filesTree, ["", newFileName, "", "", oldFileName]) 2271 self.filesTree, ["", newFileName, "", "", oldFileName]
2272 )
2183 item.setData(0, self.__diffFileLineRole, lineNumber) 2273 item.setData(0, self.__diffFileLineRole, lineNumber)
2184 self.__resizeColumnsFiles() 2274 self.__resizeColumnsFiles()
2185 self.__resortFiles() 2275 self.__resortFiles()
2186 else: 2276 else:
2187 for oldFileName, newFileName, lineNumber, _ in fileSeparators: 2277 for oldFileName, newFileName, lineNumber, _ in fileSeparators:
2188 for fileName in (oldFileName, newFileName): 2278 for fileName in (oldFileName, newFileName):
2189 if fileName != "/dev/null": 2279 if fileName != "/dev/null":
2190 items = self.filesTree.findItems( 2280 items = self.filesTree.findItems(
2191 fileName, Qt.MatchFlag.MatchExactly, 1) 2281 fileName, Qt.MatchFlag.MatchExactly, 1
2282 )
2192 for item in items: 2283 for item in items:
2193 item.setData(0, self.__diffFileLineRole, 2284 item.setData(0, self.__diffFileLineRole, lineNumber)
2194 lineNumber) 2285
2195
2196 tc = self.diffEdit.textCursor() 2286 tc = self.diffEdit.textCursor()
2197 tc.movePosition(QTextCursor.MoveOperation.Start) 2287 tc.movePosition(QTextCursor.MoveOperation.Start)
2198 self.diffEdit.setTextCursor(tc) 2288 self.diffEdit.setTextCursor(tc)
2199 self.diffEdit.ensureCursorVisible() 2289 self.diffEdit.ensureCursorVisible()
2200 2290
2201 def __mergeFileSeparators(self, fileSeparators): 2291 def __mergeFileSeparators(self, fileSeparators):
2202 """ 2292 """
2203 Private method to merge the file separator entries. 2293 Private method to merge the file separator entries.
2204 2294
2205 @param fileSeparators list of file separator entries to be merged 2295 @param fileSeparators list of file separator entries to be merged
2206 @return merged list of file separator entries 2296 @return merged list of file separator entries
2207 """ 2297 """
2208 separators = {} 2298 separators = {}
2209 for oldFile, newFile, pos1, pos2 in sorted(fileSeparators): 2299 for oldFile, newFile, pos1, pos2 in sorted(fileSeparators):
2213 if pos1 != -2: 2303 if pos1 != -2:
2214 separators[(oldFile, newFile)][2] = pos1 2304 separators[(oldFile, newFile)][2] = pos1
2215 if pos2 != -2: 2305 if pos2 != -2:
2216 separators[(oldFile, newFile)][3] = pos2 2306 separators[(oldFile, newFile)][3] = pos2
2217 return list(separators.values()) 2307 return list(separators.values())
2218 2308
2219 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) 2309 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
2220 def on_filesTree_currentItemChanged(self, current, previous): 2310 def on_filesTree_currentItemChanged(self, current, previous):
2221 """ 2311 """
2222 Private slot called, when the current item of the files tree changes. 2312 Private slot called, when the current item of the files tree changes.
2223 2313
2224 @param current reference to the new current item (QTreeWidgetItem) 2314 @param current reference to the new current item (QTreeWidgetItem)
2225 @param previous reference to the old current item (QTreeWidgetItem) 2315 @param previous reference to the old current item (QTreeWidgetItem)
2226 """ 2316 """
2227 if current: 2317 if current:
2228 para = current.data(0, self.__diffFileLineRole) 2318 para = current.data(0, self.__diffFileLineRole)
2241 # step 1: move cursor to end 2331 # step 1: move cursor to end
2242 tc = self.diffEdit.textCursor() 2332 tc = self.diffEdit.textCursor()
2243 tc.movePosition(QTextCursor.MoveOperation.End) 2333 tc.movePosition(QTextCursor.MoveOperation.End)
2244 self.diffEdit.setTextCursor(tc) 2334 self.diffEdit.setTextCursor(tc)
2245 self.diffEdit.ensureCursorVisible() 2335 self.diffEdit.ensureCursorVisible()
2246 2336
2247 # step 2: move cursor to desired line 2337 # step 2: move cursor to desired line
2248 tc = self.diffEdit.textCursor() 2338 tc = self.diffEdit.textCursor()
2249 delta = tc.blockNumber() - para 2339 delta = tc.blockNumber() - para
2250 tc.movePosition(QTextCursor.MoveOperation.PreviousBlock, 2340 tc.movePosition(
2251 QTextCursor.MoveMode.MoveAnchor, delta) 2341 QTextCursor.MoveOperation.PreviousBlock,
2342 QTextCursor.MoveMode.MoveAnchor,
2343 delta,
2344 )
2252 self.diffEdit.setTextCursor(tc) 2345 self.diffEdit.setTextCursor(tc)
2253 self.diffEdit.ensureCursorVisible() 2346 self.diffEdit.ensureCursorVisible()
2254 2347
2255 @pyqtSlot(str) 2348 @pyqtSlot(str)
2256 def on_diffSelectLabel_linkActivated(self, link): 2349 def on_diffSelectLabel_linkActivated(self, link):
2257 """ 2350 """
2258 Private slot to handle the selection of a diff target. 2351 Private slot to handle the selection of a diff target.
2259 2352
2260 @param link activated link 2353 @param link activated link
2261 @type str 2354 @type str
2262 """ 2355 """
2263 if ":" in link: 2356 if ":" in link:
2264 scheme, parent = link.split(":", 1) 2357 scheme, parent = link.split(":", 1)
2265 if scheme == "diff": 2358 if scheme == "diff":
2266 with contextlib.suppress(ValueError): 2359 with contextlib.suppress(ValueError):
2267 parent = int(parent) 2360 parent = int(parent)
2268 self.__generateDiffs(parent) 2361 self.__generateDiffs(parent)
2269 2362
2270 @pyqtSlot(str) 2363 @pyqtSlot(str)
2271 def on_saveLabel_linkActivated(self, link): 2364 def on_saveLabel_linkActivated(self, link):
2272 """ 2365 """
2273 Private slot to handle the selection of the save link. 2366 Private slot to handle the selection of the save link.
2274 2367
2275 @param link activated link 2368 @param link activated link
2276 @type str 2369 @type str
2277 """ 2370 """
2278 if ":" not in link: 2371 if ":" not in link:
2279 return 2372 return
2280 2373
2281 scheme, rest = link.split(":", 1) 2374 scheme, rest = link.split(":", 1)
2282 if scheme != "save" or rest != "me": 2375 if scheme != "save" or rest != "me":
2283 return 2376 return
2284 2377
2285 if self.projectMode: 2378 if self.projectMode:
2286 fname = self.vcs.splitPath(self.__filename)[0] 2379 fname = self.vcs.splitPath(self.__filename)[0]
2287 fname += "/{0}.diff".format(os.path.split(fname)[-1]) 2380 fname += "/{0}.diff".format(os.path.split(fname)[-1])
2288 else: 2381 else:
2289 dname, fname = self.vcs.splitPath(self.__filename) 2382 dname, fname = self.vcs.splitPath(self.__filename)
2290 if fname != '.': 2383 if fname != ".":
2291 fname = "{0}.diff".format(self.__filename) 2384 fname = "{0}.diff".format(self.__filename)
2292 else: 2385 else:
2293 fname = dname 2386 fname = dname
2294 2387
2295 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( 2388 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
2296 self, 2389 self,
2297 self.tr("Save Diff"), 2390 self.tr("Save Diff"),
2298 fname, 2391 fname,
2299 self.tr("Patch Files (*.diff)"), 2392 self.tr("Patch Files (*.diff)"),
2300 None, 2393 None,
2301 EricFileDialog.DontConfirmOverwrite) 2394 EricFileDialog.DontConfirmOverwrite,
2302 2395 )
2396
2303 if not fname: 2397 if not fname:
2304 return # user aborted 2398 return # user aborted
2305 2399
2306 fpath = pathlib.Path(fname) 2400 fpath = pathlib.Path(fname)
2307 if not fpath.suffix: 2401 if not fpath.suffix:
2308 ex = selectedFilter.split("(*")[1].split(")")[0] 2402 ex = selectedFilter.split("(*")[1].split(")")[0]
2309 if ex: 2403 if ex:
2310 fpath = fpath.with_suffix(ex) 2404 fpath = fpath.with_suffix(ex)
2311 if fpath.exists(): 2405 if fpath.exists():
2312 res = EricMessageBox.yesNo( 2406 res = EricMessageBox.yesNo(
2313 self, 2407 self,
2314 self.tr("Save Diff"), 2408 self.tr("Save Diff"),
2315 self.tr("<p>The patch file <b>{0}</b> already exists." 2409 self.tr(
2316 " Overwrite it?</p>").format(fpath), 2410 "<p>The patch file <b>{0}</b> already exists." " Overwrite it?</p>"
2317 icon=EricMessageBox.Warning) 2411 ).format(fpath),
2412 icon=EricMessageBox.Warning,
2413 )
2318 if not res: 2414 if not res:
2319 return 2415 return
2320 2416
2321 eol = ericApp().getObject("Project").getEolString() 2417 eol = ericApp().getObject("Project").getEolString()
2322 try: 2418 try:
2323 with fpath.open("w", encoding="utf-8", newline="") as f: 2419 with fpath.open("w", encoding="utf-8", newline="") as f:
2324 f.write(eol.join(self.diffEdit.toPlainText().splitlines())) 2420 f.write(eol.join(self.diffEdit.toPlainText().splitlines()))
2325 f.write(eol) 2421 f.write(eol)
2326 except OSError as why: 2422 except OSError as why:
2327 EricMessageBox.critical( 2423 EricMessageBox.critical(
2328 self, self.tr('Save Diff'), 2424 self,
2425 self.tr("Save Diff"),
2329 self.tr( 2426 self.tr(
2330 '<p>The patch file <b>{0}</b> could not be saved.' 2427 "<p>The patch file <b>{0}</b> could not be saved."
2331 '<br>Reason: {1}</p>') 2428 "<br>Reason: {1}</p>"
2332 .format(fpath, str(why))) 2429 ).format(fpath, str(why)),
2333 2430 )
2431
2334 @pyqtSlot(str) 2432 @pyqtSlot(str)
2335 def on_sbsSelectLabel_linkActivated(self, link): 2433 def on_sbsSelectLabel_linkActivated(self, link):
2336 """ 2434 """
2337 Private slot to handle selection of a side-by-side link. 2435 Private slot to handle selection of a side-by-side link.
2338 2436
2339 @param link text of the selected link 2437 @param link text of the selected link
2340 @type str 2438 @type str
2341 """ 2439 """
2342 if ":" in link: 2440 if ":" in link:
2343 scheme, path = link.split(":", 1) 2441 scheme, path = link.split(":", 1)
2344 if scheme == "sbsdiff" and "_" in path: 2442 if scheme == "sbsdiff" and "_" in path:
2345 commit1, commit2 = path.split("_", 1) 2443 commit1, commit2 = path.split("_", 1)
2346 self.vcs.vcsSbsDiff(self.__filename, 2444 self.vcs.vcsSbsDiff(self.__filename, revisions=(commit1, commit2))
2347 revisions=(commit1, commit2))

eric ide

mercurial