src/eric7/Plugins/VcsPlugins/vcsMercurial/HgLogBrowserDialog.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
13 import contextlib 13 import contextlib
14 import pathlib 14 import pathlib
15 15
16 from PyQt6.QtCore import pyqtSlot, Qt, QDate, QSize, QPoint 16 from PyQt6.QtCore import pyqtSlot, Qt, QDate, QSize, QPoint
17 from PyQt6.QtGui import ( 17 from PyQt6.QtGui import (
18 QColor, QPixmap, QPainter, QPen, QBrush, QIcon, QTextCursor, QPalette 18 QColor,
19 QPixmap,
20 QPainter,
21 QPen,
22 QBrush,
23 QIcon,
24 QTextCursor,
25 QPalette,
19 ) 26 )
20 from PyQt6.QtWidgets import ( 27 from PyQt6.QtWidgets import (
21 QWidget, QDialogButtonBox, QHeaderView, QTreeWidgetItem, QApplication, 28 QWidget,
22 QLineEdit, QMenu, QInputDialog 29 QDialogButtonBox,
30 QHeaderView,
31 QTreeWidgetItem,
32 QApplication,
33 QLineEdit,
34 QMenu,
35 QInputDialog,
23 ) 36 )
24 37
25 from EricWidgets.EricApplication import ericApp 38 from EricWidgets.EricApplication import ericApp
26 from EricWidgets import EricMessageBox, EricFileDialog 39 from EricWidgets import EricMessageBox, EricFileDialog
27 from EricGui.EricOverrideCursor import EricOverrideCursor 40 from EricGui.EricOverrideCursor import EricOverrideCursor
33 46
34 import UI.PixmapCache 47 import UI.PixmapCache
35 import Preferences 48 import Preferences
36 import Utilities 49 import Utilities
37 50
38 COLORNAMES = ["blue", "darkgreen", "red", "green", "darkblue", "purple", 51 COLORNAMES = [
39 "cyan", "olive", "magenta", "darkred", "darkmagenta", 52 "blue",
40 "darkcyan", "gray", "yellow"] 53 "darkgreen",
54 "red",
55 "green",
56 "darkblue",
57 "purple",
58 "cyan",
59 "olive",
60 "magenta",
61 "darkred",
62 "darkmagenta",
63 "darkcyan",
64 "gray",
65 "yellow",
66 ]
41 COLORS = [str(QColor(x).name()) for x in COLORNAMES] 67 COLORS = [str(QColor(x).name()) for x in COLORNAMES]
42 68
43 LIGHTCOLORS = ["#aaaaff", "#7faa7f", "#ffaaaa", "#aaffaa", "#7f7faa", 69 LIGHTCOLORS = [
44 "#ffaaff", "#aaffff", "#d5d579", "#ffaaff", "#d57979", 70 "#aaaaff",
45 "#d579d5", "#79d5d5", "#d5d5d5", "#d5d500", 71 "#7faa7f",
46 ] 72 "#ffaaaa",
73 "#aaffaa",
74 "#7f7faa",
75 "#ffaaff",
76 "#aaffff",
77 "#d5d579",
78 "#ffaaff",
79 "#d57979",
80 "#d579d5",
81 "#79d5d5",
82 "#d5d5d5",
83 "#d5d500",
84 ]
47 85
48 86
49 class HgLogBrowserDialog(QWidget, Ui_HgLogBrowserDialog): 87 class HgLogBrowserDialog(QWidget, Ui_HgLogBrowserDialog):
50 """ 88 """
51 Class implementing a dialog to browse the log history. 89 Class implementing a dialog to browse the log history.
52 """ 90 """
91
53 IconColumn = 0 92 IconColumn = 0
54 BranchColumn = 1 93 BranchColumn = 1
55 RevisionColumn = 2 94 RevisionColumn = 2
56 PhaseColumn = 3 95 PhaseColumn = 3
57 AuthorColumn = 4 96 AuthorColumn = 4
58 DateColumn = 5 97 DateColumn = 5
59 MessageColumn = 6 98 MessageColumn = 6
60 TagsColumn = 7 99 TagsColumn = 7
61 BookmarksColumn = 8 100 BookmarksColumn = 8
62 101
63 LargefilesCacheL = ".hglf/" 102 LargefilesCacheL = ".hglf/"
64 LargefilesCacheW = ".hglf\\" 103 LargefilesCacheW = ".hglf\\"
65 PathSeparatorRe = re.compile(r"/|\\") 104 PathSeparatorRe = re.compile(r"/|\\")
66 105
67 GraftedRe = re.compile(r"\(grafted from ([0-9a-fA-F]+)\)") 106 GraftedRe = re.compile(r"\(grafted from ([0-9a-fA-F]+)\)")
68 GraftedTemplate = '(grafted from <a href="chg:{0}">{0}</a>)' 107 GraftedTemplate = '(grafted from <a href="chg:{0}">{0}</a>)'
69 108
70 ClosedIndicator = " \u2612" 109 ClosedIndicator = " \u2612"
71 110
72 def __init__(self, vcs, mode="", parent=None): 111 def __init__(self, vcs, mode="", parent=None):
73 """ 112 """
74 Constructor 113 Constructor
75 114
76 @param vcs reference to the vcs object 115 @param vcs reference to the vcs object
77 @type Hg 116 @type Hg
78 @param mode mode of the dialog 117 @param mode mode of the dialog
79 @type str (one of log, full_log, incoming, outgoing) 118 @type str (one of log, full_log, incoming, outgoing)
80 @param parent parent widget 119 @param parent parent widget
81 @type QWidget 120 @type QWidget
82 """ 121 """
83 super().__init__(parent) 122 super().__init__(parent)
84 self.setupUi(self) 123 self.setupUi(self)
85 124
86 windowFlags = self.windowFlags() 125 windowFlags = self.windowFlags()
87 windowFlags |= Qt.WindowType.WindowContextHelpButtonHint 126 windowFlags |= Qt.WindowType.WindowContextHelpButtonHint
88 self.setWindowFlags(windowFlags) 127 self.setWindowFlags(windowFlags)
89 128
90 self.mainSplitter.setSizes([300, 400]) 129 self.mainSplitter.setSizes([300, 400])
91 self.mainSplitter.setStretchFactor(0, 1) 130 self.mainSplitter.setStretchFactor(0, 1)
92 self.mainSplitter.setStretchFactor(1, 2) 131 self.mainSplitter.setStretchFactor(1, 2)
93 self.diffSplitter.setStretchFactor(0, 1) 132 self.diffSplitter.setStretchFactor(0, 1)
94 self.diffSplitter.setStretchFactor(1, 2) 133 self.diffSplitter.setStretchFactor(1, 2)
95 134
96 if not mode: 135 if not mode:
97 if vcs.getPlugin().getPreferences("LogBrowserShowFullLog"): 136 if vcs.getPlugin().getPreferences("LogBrowserShowFullLog"):
98 mode = "full_log" 137 mode = "full_log"
99 else: 138 else:
100 mode = "log" 139 mode = "log"
101 140
102 if mode == "log": 141 if mode == "log":
103 self.setWindowTitle(self.tr("Mercurial Log")) 142 self.setWindowTitle(self.tr("Mercurial Log"))
104 elif mode == "incoming": 143 elif mode == "incoming":
105 self.setWindowTitle(self.tr("Mercurial Log (Incoming)")) 144 self.setWindowTitle(self.tr("Mercurial Log (Incoming)"))
106 elif mode == "outgoing": 145 elif mode == "outgoing":
107 self.setWindowTitle(self.tr("Mercurial Log (Outgoing)")) 146 self.setWindowTitle(self.tr("Mercurial Log (Outgoing)"))
108 elif mode == "full_log": 147 elif mode == "full_log":
109 self.setWindowTitle(self.tr("Mercurial Full Log")) 148 self.setWindowTitle(self.tr("Mercurial Full Log"))
110 149
111 self.buttonBox.button( 150 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
112 QDialogButtonBox.StandardButton.Close).setEnabled(False) 151 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
113 self.buttonBox.button( 152
114 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
115
116 self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") 153 self.filesTree.headerItem().setText(self.filesTree.columnCount(), "")
117 self.filesTree.header().setSortIndicator( 154 self.filesTree.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder)
118 0, Qt.SortOrder.AscendingOrder) 155
119
120 self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow")) 156 self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow"))
121 self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow")) 157 self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow"))
122 158
123 self.refreshButton = self.buttonBox.addButton( 159 self.refreshButton = self.buttonBox.addButton(
124 self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole) 160 self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole
161 )
125 self.refreshButton.setToolTip( 162 self.refreshButton.setToolTip(
126 self.tr("Press to refresh the list of changesets")) 163 self.tr("Press to refresh the list of changesets")
164 )
127 self.refreshButton.setEnabled(False) 165 self.refreshButton.setEnabled(False)
128 166
129 self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow")) 167 self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow"))
130 self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow")) 168 self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow"))
131 self.__findBackwards = False 169 self.__findBackwards = False
132 170
133 self.modeComboBox.addItem(self.tr("Find"), "find") 171 self.modeComboBox.addItem(self.tr("Find"), "find")
134 self.modeComboBox.addItem(self.tr("Filter"), "filter") 172 self.modeComboBox.addItem(self.tr("Filter"), "filter")
135 173
136 self.fieldCombo.addItem(self.tr("Revision"), "revision") 174 self.fieldCombo.addItem(self.tr("Revision"), "revision")
137 self.fieldCombo.addItem(self.tr("Author"), "author") 175 self.fieldCombo.addItem(self.tr("Author"), "author")
138 self.fieldCombo.addItem(self.tr("Message"), "message") 176 self.fieldCombo.addItem(self.tr("Message"), "message")
139 self.fieldCombo.addItem(self.tr("File"), "file") 177 self.fieldCombo.addItem(self.tr("File"), "file")
140 self.fieldCombo.addItem(self.tr("Phase"), "phase") 178 self.fieldCombo.addItem(self.tr("Phase"), "phase")
141 179
142 font = Preferences.getEditorOtherFonts("MonospacedFont") 180 font = Preferences.getEditorOtherFonts("MonospacedFont")
143 self.diffEdit.document().setDefaultFont(font) 181 self.diffEdit.document().setDefaultFont(font)
144 182
145 self.diffHighlighter = HgDiffHighlighter(self.diffEdit.document()) 183 self.diffHighlighter = HgDiffHighlighter(self.diffEdit.document())
146 self.__diffGenerator = HgDiffGenerator(vcs, self) 184 self.__diffGenerator = HgDiffGenerator(vcs, self)
147 self.__diffGenerator.finished.connect(self.__generatorFinished) 185 self.__diffGenerator.finished.connect(self.__generatorFinished)
148 186
149 self.vcs = vcs 187 self.vcs = vcs
150 if mode in ("log", "incoming", "outgoing", "full_log"): 188 if mode in ("log", "incoming", "outgoing", "full_log"):
151 if mode == "full_log": 189 if mode == "full_log":
152 self.commandMode = "incoming" 190 self.commandMode = "incoming"
153 else: 191 else:
155 self.initialCommandMode = mode 193 self.initialCommandMode = mode
156 else: 194 else:
157 self.commandMode = "log" 195 self.commandMode = "log"
158 self.initialCommandMode = "log" 196 self.initialCommandMode = "log"
159 self.__hgClient = vcs.getClient() 197 self.__hgClient = vcs.getClient()
160 198
161 self.__detailsTemplate = self.tr( 199 self.__detailsTemplate = self.tr(
162 "<table>" 200 "<table>"
163 "<tr><td><b>Revision</b></td><td>{0}</td></tr>" 201 "<tr><td><b>Revision</b></td><td>{0}</td></tr>"
164 "<tr><td><b>Date</b></td><td>{1}</td></tr>" 202 "<tr><td><b>Date</b></td><td>{1}</td></tr>"
165 "<tr><td><b>Author</b></td><td>{2}</td></tr>" 203 "<tr><td><b>Author</b></td><td>{2}</td></tr>"
166 "<tr><td><b>Branch</b></td><td>{3}</td></tr>" 204 "<tr><td><b>Branch</b></td><td>{3}</td></tr>"
167 "{4}" 205 "{4}"
168 "<tr><td><b>Message</b></td><td>{5}</td></tr>" 206 "<tr><td><b>Message</b></td><td>{5}</td></tr>"
169 "</table>" 207 "</table>"
170 ) 208 )
171 self.__parentsTemplate = self.tr( 209 self.__parentsTemplate = self.tr("<tr><td><b>Parents</b></td><td>{0}</td></tr>")
172 "<tr><td><b>Parents</b></td><td>{0}</td></tr>"
173 )
174 self.__childrenTemplate = self.tr( 210 self.__childrenTemplate = self.tr(
175 "<tr><td><b>Children</b></td><td>{0}</td></tr>" 211 "<tr><td><b>Children</b></td><td>{0}</td></tr>"
176 ) 212 )
177 self.__tagsTemplate = self.tr( 213 self.__tagsTemplate = self.tr("<tr><td><b>Tags</b></td><td>{0}</td></tr>")
178 "<tr><td><b>Tags</b></td><td>{0}</td></tr>"
179 )
180 self.__latestTagTemplate = self.tr( 214 self.__latestTagTemplate = self.tr(
181 "<tr><td><b>Latest Tag</b></td><td>{0}</td></tr>" 215 "<tr><td><b>Latest Tag</b></td><td>{0}</td></tr>"
182 ) 216 )
183 self.__bookmarksTemplate = self.tr( 217 self.__bookmarksTemplate = self.tr(
184 "<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>" 218 "<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>"
185 ) 219 )
186 220
187 self.__bundle = "" 221 self.__bundle = ""
188 self.__filename = "" 222 self.__filename = ""
189 self.__isFile = False 223 self.__isFile = False
190 self.__selectedRevisions = [] 224 self.__selectedRevisions = []
191 self.intercept = False 225 self.intercept = False
192 226
193 self.__initData() 227 self.__initData()
194 228
195 self.__allBranchesFilter = self.tr("All") 229 self.__allBranchesFilter = self.tr("All")
196 230
197 self.fromDate.setDisplayFormat("yyyy-MM-dd") 231 self.fromDate.setDisplayFormat("yyyy-MM-dd")
198 self.toDate.setDisplayFormat("yyyy-MM-dd") 232 self.toDate.setDisplayFormat("yyyy-MM-dd")
199 self.__resetUI() 233 self.__resetUI()
200 234
201 # roles used in the log tree 235 # roles used in the log tree
202 self.__messageRole = Qt.ItemDataRole.UserRole 236 self.__messageRole = Qt.ItemDataRole.UserRole
203 self.__changesRole = Qt.ItemDataRole.UserRole + 1 237 self.__changesRole = Qt.ItemDataRole.UserRole + 1
204 self.__edgesRole = Qt.ItemDataRole.UserRole + 2 238 self.__edgesRole = Qt.ItemDataRole.UserRole + 2
205 self.__parentsRole = Qt.ItemDataRole.UserRole + 3 239 self.__parentsRole = Qt.ItemDataRole.UserRole + 3
206 self.__latestTagRole = Qt.ItemDataRole.UserRole + 4 240 self.__latestTagRole = Qt.ItemDataRole.UserRole + 4
207 self.__incomingRole = Qt.ItemDataRole.UserRole + 5 241 self.__incomingRole = Qt.ItemDataRole.UserRole + 5
208 242
209 # roles used in the file tree 243 # roles used in the file tree
210 self.__diffFileLineRole = Qt.ItemDataRole.UserRole 244 self.__diffFileLineRole = Qt.ItemDataRole.UserRole
211 245
212 self.flags = { 246 self.flags = {
213 'A': self.tr('Added'), 247 "A": self.tr("Added"),
214 'D': self.tr('Deleted'), 248 "D": self.tr("Deleted"),
215 'M': self.tr('Modified'), 249 "M": self.tr("Modified"),
216 } 250 }
217 251
218 self.phases = { 252 self.phases = {
219 'draft': self.tr("Draft"), 253 "draft": self.tr("Draft"),
220 'public': self.tr("Public"), 254 "public": self.tr("Public"),
221 'secret': self.tr("Secret"), 255 "secret": self.tr("Secret"),
222 } 256 }
223 257
224 self.__dotRadius = 8 258 self.__dotRadius = 8
225 self.__rowHeight = 20 259 self.__rowHeight = 20
226 260
227 self.logTree.setIconSize( 261 self.logTree.setIconSize(QSize(100 * self.__rowHeight, self.__rowHeight))
228 QSize(100 * self.__rowHeight, self.__rowHeight))
229 self.BookmarksColumn = self.logTree.columnCount() 262 self.BookmarksColumn = self.logTree.columnCount()
230 self.logTree.headerItem().setText( 263 self.logTree.headerItem().setText(self.BookmarksColumn, self.tr("Bookmarks"))
231 self.BookmarksColumn, self.tr("Bookmarks")) 264
232
233 self.__logTreeNormalFont = self.logTree.font() 265 self.__logTreeNormalFont = self.logTree.font()
234 self.__logTreeNormalFont.setBold(False) 266 self.__logTreeNormalFont.setBold(False)
235 self.__logTreeBoldFont = self.logTree.font() 267 self.__logTreeBoldFont = self.logTree.font()
236 self.__logTreeBoldFont.setBold(True) 268 self.__logTreeBoldFont.setBold(True)
237 self.__logTreeHasDarkBackground = ericApp().usesDarkPalette() 269 self.__logTreeHasDarkBackground = ericApp().usesDarkPalette()
238 270
239 self.detailsEdit.anchorClicked.connect(self.__revisionClicked) 271 self.detailsEdit.anchorClicked.connect(self.__revisionClicked)
240 272
241 self.__initActionsMenu() 273 self.__initActionsMenu()
242 274
243 self.__finishCallbacks = [] 275 self.__finishCallbacks = []
244 if self.initialCommandMode == "full_log": 276 if self.initialCommandMode == "full_log":
245 self.__addFinishCallback(self.on_nextButton_clicked) 277 self.__addFinishCallback(self.on_nextButton_clicked)
246 278
247 def __addFinishCallback(self, callback): 279 def __addFinishCallback(self, callback):
248 """ 280 """
249 Private method to add a method to be called once the process finished. 281 Private method to add a method to be called once the process finished.
250 282
251 The callback methods are invoke in a FIFO style and are consumed. If 283 The callback methods are invoke in a FIFO style and are consumed. If
252 a callback method needs to be called again, it must be added again. 284 a callback method needs to be called again, it must be added again.
253 285
254 @param callback callback method 286 @param callback callback method
255 @type function 287 @type function
256 """ 288 """
257 if callback not in self.__finishCallbacks: 289 if callback not in self.__finishCallbacks:
258 self.__finishCallbacks.append(callback) 290 self.__finishCallbacks.append(callback)
259 291
260 def __initActionsMenu(self): 292 def __initActionsMenu(self):
261 """ 293 """
262 Private method to initialize the actions menu. 294 Private method to initialize the actions menu.
263 """ 295 """
264 self.__actionsMenu = QMenu() 296 self.__actionsMenu = QMenu()
265 self.__actionsMenu.setTearOffEnabled(True) 297 self.__actionsMenu.setTearOffEnabled(True)
266 self.__actionsMenu.setToolTipsVisible(True) 298 self.__actionsMenu.setToolTipsVisible(True)
267 299
268 self.__graftAct = self.__actionsMenu.addAction( 300 self.__graftAct = self.__actionsMenu.addAction(
269 UI.PixmapCache.getIcon("vcsGraft"), 301 UI.PixmapCache.getIcon("vcsGraft"),
270 self.tr("Copy Changesets"), self.__graftActTriggered) 302 self.tr("Copy Changesets"),
271 self.__graftAct.setToolTip(self.tr( 303 self.__graftActTriggered,
272 "Copy the selected changesets to the current branch")) 304 )
273 305 self.__graftAct.setToolTip(
306 self.tr("Copy the selected changesets to the current branch")
307 )
308
274 self.__mergeAct = self.__actionsMenu.addAction( 309 self.__mergeAct = self.__actionsMenu.addAction(
275 UI.PixmapCache.getIcon("vcsMerge"), 310 UI.PixmapCache.getIcon("vcsMerge"),
276 self.tr("Merge with Changeset"), self.__mergeActTriggered) 311 self.tr("Merge with Changeset"),
277 self.__mergeAct.setToolTip(self.tr( 312 self.__mergeActTriggered,
278 "Merge the working directory with the selected changeset")) 313 )
279 314 self.__mergeAct.setToolTip(
315 self.tr("Merge the working directory with the selected changeset")
316 )
317
280 self.__phaseAct = self.__actionsMenu.addAction( 318 self.__phaseAct = self.__actionsMenu.addAction(
281 self.tr("Change Phase"), self.__phaseActTriggered) 319 self.tr("Change Phase"), self.__phaseActTriggered
282 self.__phaseAct.setToolTip(self.tr( 320 )
283 "Change the phase of the selected revisions")) 321 self.__phaseAct.setToolTip(
284 self.__phaseAct.setWhatsThis(self.tr( 322 self.tr("Change the phase of the selected revisions")
285 """<b>Change Phase</b>\n<p>This changes the phase of the""" 323 )
286 """ selected revisions. The selected revisions have to have""" 324 self.__phaseAct.setWhatsThis(
287 """ the same current phase.</p>""")) 325 self.tr(
288 326 """<b>Change Phase</b>\n<p>This changes the phase of the"""
327 """ selected revisions. The selected revisions have to have"""
328 """ the same current phase.</p>"""
329 )
330 )
331
289 self.__tagAct = self.__actionsMenu.addAction( 332 self.__tagAct = self.__actionsMenu.addAction(
290 UI.PixmapCache.getIcon("vcsTag"), self.tr("Tag"), 333 UI.PixmapCache.getIcon("vcsTag"), self.tr("Tag"), self.__tagActTriggered
291 self.__tagActTriggered) 334 )
292 self.__tagAct.setToolTip(self.tr("Tag the selected revision")) 335 self.__tagAct.setToolTip(self.tr("Tag the selected revision"))
293 336
294 self.__closeHeadsAct = self.__actionsMenu.addAction( 337 self.__closeHeadsAct = self.__actionsMenu.addAction(
295 UI.PixmapCache.getIcon("closehead"), self.tr("Close Heads"), 338 UI.PixmapCache.getIcon("closehead"),
296 self.__closeHeadsActTriggered) 339 self.tr("Close Heads"),
340 self.__closeHeadsActTriggered,
341 )
297 self.__closeHeadsAct.setToolTip(self.tr("Close the selected heads")) 342 self.__closeHeadsAct.setToolTip(self.tr("Close the selected heads"))
298 343
299 self.__switchAct = self.__actionsMenu.addAction( 344 self.__switchAct = self.__actionsMenu.addAction(
300 UI.PixmapCache.getIcon("vcsSwitch"), self.tr("Switch"), 345 UI.PixmapCache.getIcon("vcsSwitch"),
301 self.__switchActTriggered) 346 self.tr("Switch"),
302 self.__switchAct.setToolTip(self.tr( 347 self.__switchActTriggered,
303 "Switch the working directory to the selected revision")) 348 )
304 349 self.__switchAct.setToolTip(
350 self.tr("Switch the working directory to the selected revision")
351 )
352
305 self.__actionsMenu.addSeparator() 353 self.__actionsMenu.addSeparator()
306 354
307 self.__bookmarkAct = self.__actionsMenu.addAction( 355 self.__bookmarkAct = self.__actionsMenu.addAction(
308 UI.PixmapCache.getIcon("addBookmark"), 356 UI.PixmapCache.getIcon("addBookmark"),
309 self.tr("Define Bookmark..."), self.__bookmarkActTriggered) 357 self.tr("Define Bookmark..."),
310 self.__bookmarkAct.setToolTip( 358 self.__bookmarkActTriggered,
311 self.tr("Bookmark the selected revision")) 359 )
360 self.__bookmarkAct.setToolTip(self.tr("Bookmark the selected revision"))
312 self.__bookmarkMoveAct = self.__actionsMenu.addAction( 361 self.__bookmarkMoveAct = self.__actionsMenu.addAction(
313 UI.PixmapCache.getIcon("moveBookmark"), 362 UI.PixmapCache.getIcon("moveBookmark"),
314 self.tr("Move Bookmark..."), self.__bookmarkMoveActTriggered) 363 self.tr("Move Bookmark..."),
364 self.__bookmarkMoveActTriggered,
365 )
315 self.__bookmarkMoveAct.setToolTip( 366 self.__bookmarkMoveAct.setToolTip(
316 self.tr("Move bookmark to the selected revision")) 367 self.tr("Move bookmark to the selected revision")
317 368 )
369
318 self.__actionsMenu.addSeparator() 370 self.__actionsMenu.addSeparator()
319 371
320 self.__pullAct = self.__actionsMenu.addAction( 372 self.__pullAct = self.__actionsMenu.addAction(
321 UI.PixmapCache.getIcon("vcsUpdate"), self.tr("Pull Changes"), 373 UI.PixmapCache.getIcon("vcsUpdate"),
322 self.__pullActTriggered) 374 self.tr("Pull Changes"),
323 self.__pullAct.setToolTip(self.tr( 375 self.__pullActTriggered,
324 "Pull changes from a remote repository")) 376 )
377 self.__pullAct.setToolTip(self.tr("Pull changes from a remote repository"))
325 self.__lfPullAct = self.__actionsMenu.addAction( 378 self.__lfPullAct = self.__actionsMenu.addAction(
326 self.tr("Pull Large Files"), self.__lfPullActTriggered) 379 self.tr("Pull Large Files"), self.__lfPullActTriggered
327 self.__lfPullAct.setToolTip(self.tr( 380 )
328 "Pull large files for selected revisions")) 381 self.__lfPullAct.setToolTip(self.tr("Pull large files for selected revisions"))
329 382
330 self.__actionsMenu.addSeparator() 383 self.__actionsMenu.addSeparator()
331 384
332 self.__pushAct = self.__actionsMenu.addAction( 385 self.__pushAct = self.__actionsMenu.addAction(
333 UI.PixmapCache.getIcon("vcsCommit"), 386 UI.PixmapCache.getIcon("vcsCommit"),
334 self.tr("Push Selected Changes"), self.__pushActTriggered) 387 self.tr("Push Selected Changes"),
335 self.__pushAct.setToolTip(self.tr( 388 self.__pushActTriggered,
336 "Push changes of the selected changeset and its ancestors" 389 )
337 " to a remote repository")) 390 self.__pushAct.setToolTip(
391 self.tr(
392 "Push changes of the selected changeset and its ancestors"
393 " to a remote repository"
394 )
395 )
338 self.__pushAllAct = self.__actionsMenu.addAction( 396 self.__pushAllAct = self.__actionsMenu.addAction(
339 UI.PixmapCache.getIcon("vcsCommit"), 397 UI.PixmapCache.getIcon("vcsCommit"),
340 self.tr("Push All Changes"), self.__pushAllActTriggered) 398 self.tr("Push All Changes"),
341 self.__pushAllAct.setToolTip(self.tr( 399 self.__pushAllActTriggered,
342 "Push all changes to a remote repository")) 400 )
343 401 self.__pushAllAct.setToolTip(self.tr("Push all changes to a remote repository"))
402
344 self.__actionsMenu.addSeparator() 403 self.__actionsMenu.addSeparator()
345 404
346 self.__bundleAct = self.__actionsMenu.addAction( 405 self.__bundleAct = self.__actionsMenu.addAction(
347 UI.PixmapCache.getIcon("vcsCreateChangegroup"), 406 UI.PixmapCache.getIcon("vcsCreateChangegroup"),
348 self.tr("Create Changegroup"), self.__bundleActTriggered) 407 self.tr("Create Changegroup"),
349 self.__bundleAct.setToolTip(self.tr( 408 self.__bundleActTriggered,
350 "Create a changegroup file containing the selected changesets")) 409 )
351 self.__bundleAct.setWhatsThis(self.tr( 410 self.__bundleAct.setToolTip(
352 """<b>Create Changegroup</b>\n<p>This creates a changegroup""" 411 self.tr("Create a changegroup file containing the selected changesets")
353 """ file containing the selected revisions. If no revisions""" 412 )
354 """ are selected, all changesets will be bundled. If one""" 413 self.__bundleAct.setWhatsThis(
355 """ revision is selected, it will be interpreted as the base""" 414 self.tr(
356 """ revision. Otherwise the lowest revision will be used as""" 415 """<b>Create Changegroup</b>\n<p>This creates a changegroup"""
357 """ the base revision and all other revision will be bundled.""" 416 """ file containing the selected revisions. If no revisions"""
358 """ If the dialog is showing outgoing changesets, all""" 417 """ are selected, all changesets will be bundled. If one"""
359 """ selected changesets will be bundled.</p>""")) 418 """ revision is selected, it will be interpreted as the base"""
419 """ revision. Otherwise the lowest revision will be used as"""
420 """ the base revision and all other revision will be bundled."""
421 """ If the dialog is showing outgoing changesets, all"""
422 """ selected changesets will be bundled.</p>"""
423 )
424 )
360 self.__unbundleAct = self.__actionsMenu.addAction( 425 self.__unbundleAct = self.__actionsMenu.addAction(
361 UI.PixmapCache.getIcon("vcsApplyChangegroup"), 426 UI.PixmapCache.getIcon("vcsApplyChangegroup"),
362 self.tr("Apply Changegroup"), self.__unbundleActTriggered) 427 self.tr("Apply Changegroup"),
363 self.__unbundleAct.setToolTip(self.tr( 428 self.__unbundleActTriggered,
364 "Apply the currently viewed changegroup file")) 429 )
365 430 self.__unbundleAct.setToolTip(
431 self.tr("Apply the currently viewed changegroup file")
432 )
433
366 self.__actionsMenu.addSeparator() 434 self.__actionsMenu.addSeparator()
367 435
368 self.__gpgSignAct = self.__actionsMenu.addAction( 436 self.__gpgSignAct = self.__actionsMenu.addAction(
369 UI.PixmapCache.getIcon("changesetSign"), 437 UI.PixmapCache.getIcon("changesetSign"),
370 self.tr("Sign Revisions"), self.__gpgSignActTriggered) 438 self.tr("Sign Revisions"),
371 self.__gpgSignAct.setToolTip(self.tr( 439 self.__gpgSignActTriggered,
372 "Add a signature for the selected revisions")) 440 )
441 self.__gpgSignAct.setToolTip(
442 self.tr("Add a signature for the selected revisions")
443 )
373 self.__gpgVerifyAct = self.__actionsMenu.addAction( 444 self.__gpgVerifyAct = self.__actionsMenu.addAction(
374 UI.PixmapCache.getIcon("changesetSignVerify"), 445 UI.PixmapCache.getIcon("changesetSignVerify"),
375 self.tr("Verify Signatures"), self.__gpgVerifyActTriggered) 446 self.tr("Verify Signatures"),
376 self.__gpgVerifyAct.setToolTip(self.tr( 447 self.__gpgVerifyActTriggered,
377 "Verify all signatures there may be for the selected revision")) 448 )
378 449 self.__gpgVerifyAct.setToolTip(
450 self.tr("Verify all signatures there may be for the selected revision")
451 )
452
379 self.__actionsMenu.addSeparator() 453 self.__actionsMenu.addSeparator()
380 454
381 self.__stripAct = self.__actionsMenu.addAction( 455 self.__stripAct = self.__actionsMenu.addAction(
382 UI.PixmapCache.getIcon("fileDelete"), 456 UI.PixmapCache.getIcon("fileDelete"),
383 self.tr("Strip Changesets"), self.__stripActTriggered) 457 self.tr("Strip Changesets"),
384 self.__stripAct.setToolTip(self.tr( 458 self.__stripActTriggered,
385 "Strip changesets from a repository")) 459 )
386 460 self.__stripAct.setToolTip(self.tr("Strip changesets from a repository"))
461
387 self.__actionsMenu.addSeparator() 462 self.__actionsMenu.addSeparator()
388 463
389 self.__selectAllAct = self.__actionsMenu.addAction( 464 self.__selectAllAct = self.__actionsMenu.addAction(
390 self.tr("Select All Entries"), self.__selectAllActTriggered) 465 self.tr("Select All Entries"), self.__selectAllActTriggered
466 )
391 self.__unselectAllAct = self.__actionsMenu.addAction( 467 self.__unselectAllAct = self.__actionsMenu.addAction(
392 self.tr("Deselect All Entries"), 468 self.tr("Deselect All Entries"), lambda: self.__selectAllActTriggered(False)
393 lambda: self.__selectAllActTriggered(False)) 469 )
394 470
395 self.actionsButton.setIcon( 471 self.actionsButton.setIcon(UI.PixmapCache.getIcon("actionsToolButton"))
396 UI.PixmapCache.getIcon("actionsToolButton"))
397 self.actionsButton.setMenu(self.__actionsMenu) 472 self.actionsButton.setMenu(self.__actionsMenu)
398 473
399 def __initData(self): 474 def __initData(self):
400 """ 475 """
401 Private method to (re-)initialize some data. 476 Private method to (re-)initialize some data.
402 """ 477 """
403 self.__maxDate = QDate() 478 self.__maxDate = QDate()
404 self.__minDate = QDate() 479 self.__minDate = QDate()
405 self.__filterLogsEnabled = True 480 self.__filterLogsEnabled = True
406 481
407 self.buf = [] # buffer for stdout 482 self.buf = [] # buffer for stdout
408 self.diff = None 483 self.diff = None
409 self.__started = False 484 self.__started = False
410 self.__lastRev = 0 485 self.__lastRev = 0
411 self.projectMode = False 486 self.projectMode = False
412 487
413 # attributes to store log graph data 488 # attributes to store log graph data
414 self.__revs = [] 489 self.__revs = []
415 self.__revColors = {} 490 self.__revColors = {}
416 self.__revColor = 0 491 self.__revColor = 0
417 492
418 self.__branchColors = {} 493 self.__branchColors = {}
419 494
420 self.__projectWorkingDirParents = [] 495 self.__projectWorkingDirParents = []
421 self.__projectBranch = "" 496 self.__projectBranch = ""
422 497
423 self.__childrenInfo = collections.defaultdict(list) 498 self.__childrenInfo = collections.defaultdict(list)
424 499
425 def closeEvent(self, e): 500 def closeEvent(self, e):
426 """ 501 """
427 Protected slot implementing a close event handler. 502 Protected slot implementing a close event handler.
428 503
429 @param e close event (QCloseEvent) 504 @param e close event (QCloseEvent)
430 """ 505 """
431 if self.__hgClient.isExecuting(): 506 if self.__hgClient.isExecuting():
432 self.__hgClient.cancel() 507 self.__hgClient.cancel()
433 508
509 self.vcs.getPlugin().setPreferences("LogBrowserGeometry", self.saveGeometry())
434 self.vcs.getPlugin().setPreferences( 510 self.vcs.getPlugin().setPreferences(
435 "LogBrowserGeometry", self.saveGeometry()) 511 "LogBrowserSplitterStates",
436 self.vcs.getPlugin().setPreferences( 512 [
437 "LogBrowserSplitterStates", [
438 self.mainSplitter.saveState(), 513 self.mainSplitter.saveState(),
439 self.detailsSplitter.saveState(), 514 self.detailsSplitter.saveState(),
440 self.diffSplitter.saveState(), 515 self.diffSplitter.saveState(),
441 ] 516 ],
442 ) 517 )
443 518
444 e.accept() 519 e.accept()
445 520
446 def show(self): 521 def show(self):
447 """ 522 """
448 Public slot to show the dialog. 523 Public slot to show the dialog.
449 """ 524 """
450 self.__reloadGeometry() 525 self.__reloadGeometry()
451 self.__restoreSplitterStates() 526 self.__restoreSplitterStates()
452 self.__resetUI() 527 self.__resetUI()
453 528
454 super().show() 529 super().show()
455 530
456 def __reloadGeometry(self): 531 def __reloadGeometry(self):
457 """ 532 """
458 Private method to restore the geometry. 533 Private method to restore the geometry.
461 if geom.isEmpty(): 536 if geom.isEmpty():
462 s = QSize(1000, 800) 537 s = QSize(1000, 800)
463 self.resize(s) 538 self.resize(s)
464 else: 539 else:
465 self.restoreGeometry(geom) 540 self.restoreGeometry(geom)
466 541
467 def __restoreSplitterStates(self): 542 def __restoreSplitterStates(self):
468 """ 543 """
469 Private method to restore the state of the various splitters. 544 Private method to restore the state of the various splitters.
470 """ 545 """
471 states = self.vcs.getPlugin().getPreferences( 546 states = self.vcs.getPlugin().getPreferences("LogBrowserSplitterStates")
472 "LogBrowserSplitterStates")
473 if len(states) == 3: 547 if len(states) == 3:
474 # we have three splitters 548 # we have three splitters
475 self.mainSplitter.restoreState(states[0]) 549 self.mainSplitter.restoreState(states[0])
476 self.detailsSplitter.restoreState(states[1]) 550 self.detailsSplitter.restoreState(states[1])
477 self.diffSplitter.restoreState(states[2]) 551 self.diffSplitter.restoreState(states[2])
478 552
479 def __resetUI(self): 553 def __resetUI(self):
480 """ 554 """
481 Private method to reset the user interface. 555 Private method to reset the user interface.
482 """ 556 """
483 self.branchCombo.clear() 557 self.branchCombo.clear()
484 self.fromDate.setDate(QDate.currentDate()) 558 self.fromDate.setDate(QDate.currentDate())
485 self.toDate.setDate(QDate.currentDate()) 559 self.toDate.setDate(QDate.currentDate())
486 self.fieldCombo.setCurrentIndex(self.fieldCombo.findData("message")) 560 self.fieldCombo.setCurrentIndex(self.fieldCombo.findData("message"))
487 self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences( 561 self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences("LogLimit"))
488 "LogLimit")) 562 self.stopCheckBox.setChecked(
489 self.stopCheckBox.setChecked(self.vcs.getPlugin().getPreferences( 563 self.vcs.getPlugin().getPreferences("StopLogOnCopy")
490 "StopLogOnCopy")) 564 )
491 565
492 if self.initialCommandMode in ("incoming", "outgoing"): 566 if self.initialCommandMode in ("incoming", "outgoing"):
493 self.nextButton.setEnabled(False) 567 self.nextButton.setEnabled(False)
494 self.limitSpinBox.setEnabled(False) 568 self.limitSpinBox.setEnabled(False)
495 else: 569 else:
496 self.nextButton.setEnabled(True) 570 self.nextButton.setEnabled(True)
497 self.limitSpinBox.setEnabled(True) 571 self.limitSpinBox.setEnabled(True)
498 572
499 self.logTree.clear() 573 self.logTree.clear()
500 574
501 if self.initialCommandMode == "full_log": 575 if self.initialCommandMode == "full_log":
502 self.commandMode = "incoming" 576 self.commandMode = "incoming"
503 else: 577 else:
504 self.commandMode = self.initialCommandMode 578 self.commandMode = self.initialCommandMode
505 579
506 def __resizeColumnsLog(self): 580 def __resizeColumnsLog(self):
507 """ 581 """
508 Private method to resize the log tree columns. 582 Private method to resize the log tree columns.
509 """ 583 """
510 self.logTree.header().resizeSections( 584 self.logTree.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents)
511 QHeaderView.ResizeMode.ResizeToContents)
512 self.logTree.header().setStretchLastSection(True) 585 self.logTree.header().setStretchLastSection(True)
513 586
514 def __resizeColumnsFiles(self): 587 def __resizeColumnsFiles(self):
515 """ 588 """
516 Private method to resize the changed files tree columns. 589 Private method to resize the changed files tree columns.
517 """ 590 """
518 self.filesTree.header().resizeSections( 591 self.filesTree.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents)
519 QHeaderView.ResizeMode.ResizeToContents)
520 self.filesTree.header().setStretchLastSection(True) 592 self.filesTree.header().setStretchLastSection(True)
521 593
522 def __resortFiles(self): 594 def __resortFiles(self):
523 """ 595 """
524 Private method to resort the changed files tree. 596 Private method to resort the changed files tree.
525 """ 597 """
526 sortColumn = self.filesTree.sortColumn() 598 sortColumn = self.filesTree.sortColumn()
599 self.filesTree.sortItems(1, self.filesTree.header().sortIndicatorOrder())
527 self.filesTree.sortItems( 600 self.filesTree.sortItems(
528 1, self.filesTree.header().sortIndicatorOrder()) 601 sortColumn, self.filesTree.header().sortIndicatorOrder()
529 self.filesTree.sortItems( 602 )
530 sortColumn, self.filesTree.header().sortIndicatorOrder()) 603
531
532 def __getColor(self, n): 604 def __getColor(self, n):
533 """ 605 """
534 Private method to get the (rotating) name of the color given an index. 606 Private method to get the (rotating) name of the color given an index.
535 607
536 @param n color index 608 @param n color index
537 @type int 609 @type int
538 @return color name 610 @return color name
539 @rtype str 611 @rtype str
540 """ 612 """
541 if self.__logTreeHasDarkBackground: 613 if self.__logTreeHasDarkBackground:
542 return LIGHTCOLORS[n % len(LIGHTCOLORS)] 614 return LIGHTCOLORS[n % len(LIGHTCOLORS)]
543 else: 615 else:
544 return COLORS[n % len(COLORS)] 616 return COLORS[n % len(COLORS)]
545 617
546 def __branchColor(self, branchName): 618 def __branchColor(self, branchName):
547 """ 619 """
548 Private method to calculate a color for a given branch name. 620 Private method to calculate a color for a given branch name.
549 621
550 @param branchName name of the branch (string) 622 @param branchName name of the branch (string)
551 @return name of the color to use (string) 623 @return name of the color to use (string)
552 """ 624 """
553 if branchName not in self.__branchColors: 625 if branchName not in self.__branchColors:
554 self.__branchColors[branchName] = self.__getColor( 626 self.__branchColors[branchName] = self.__getColor(len(self.__branchColors))
555 len(self.__branchColors))
556 return self.__branchColors[branchName] 627 return self.__branchColors[branchName]
557 628
558 def __generateEdges(self, rev, parents): 629 def __generateEdges(self, rev, parents):
559 """ 630 """
560 Private method to generate edge info for the give data. 631 Private method to generate edge info for the give data.
561 632
562 @param rev revision to calculate edge info for (integer) 633 @param rev revision to calculate edge info for (integer)
563 @param parents list of parent revisions (list of integers) 634 @param parents list of parent revisions (list of integers)
564 @return tuple containing the column and color index for 635 @return tuple containing the column and color index for
565 the given node and a list of tuples indicating the edges 636 the given node and a list of tuples indicating the edges
566 between the given node and its parents 637 between the given node and its parents
569 if rev not in self.__revs: 640 if rev not in self.__revs:
570 # new head 641 # new head
571 self.__revs.append(rev) 642 self.__revs.append(rev)
572 self.__revColors[rev] = self.__revColor 643 self.__revColors[rev] = self.__revColor
573 self.__revColor += 1 644 self.__revColor += 1
574 645
575 col = self.__revs.index(rev) 646 col = self.__revs.index(rev)
576 color = self.__revColors.pop(rev) 647 color = self.__revColors.pop(rev)
577 nextRevs = self.__revs[:] 648 nextRevs = self.__revs[:]
578 649
579 # add parents to next 650 # add parents to next
580 addparents = [p for p in parents if p not in nextRevs] 651 addparents = [p for p in parents if p not in nextRevs]
581 nextRevs[col:col + 1] = addparents 652 nextRevs[col : col + 1] = addparents
582 653
583 # set colors for the parents 654 # set colors for the parents
584 for i, p in enumerate(addparents): 655 for i, p in enumerate(addparents):
585 if not i: 656 if not i:
586 self.__revColors[p] = color 657 self.__revColors[p] = color
587 else: 658 else:
588 self.__revColors[p] = self.__revColor 659 self.__revColors[p] = self.__revColor
589 self.__revColor += 1 660 self.__revColor += 1
590 661
591 # add edges to the graph 662 # add edges to the graph
592 edges = [] 663 edges = []
593 if parents[0] != -1: 664 if parents[0] != -1:
594 for ecol, erev in enumerate(self.__revs): 665 for ecol, erev in enumerate(self.__revs):
595 if erev in nextRevs: 666 if erev in nextRevs:
596 edges.append( 667 edges.append((ecol, nextRevs.index(erev), self.__revColors[erev]))
597 (ecol, nextRevs.index(erev), self.__revColors[erev]))
598 elif erev == rev: 668 elif erev == rev:
599 for p in parents: 669 for p in parents:
600 edges.append( 670 edges.append((ecol, nextRevs.index(p), self.__revColors[p]))
601 (ecol, nextRevs.index(p), self.__revColors[p])) 671
602
603 self.__revs = nextRevs 672 self.__revs = nextRevs
604 return col, color, edges 673 return col, color, edges
605 674
606 def __generateIcon(self, column, color, bottomedges, topedges, dotColor, 675 def __generateIcon(
607 currentRev, closed, isPushableDraft): 676 self,
677 column,
678 color,
679 bottomedges,
680 topedges,
681 dotColor,
682 currentRev,
683 closed,
684 isPushableDraft,
685 ):
608 """ 686 """
609 Private method to generate an icon containing the revision tree for the 687 Private method to generate an icon containing the revision tree for the
610 given data. 688 given data.
611 689
612 @param column column index of the revision 690 @param column column index of the revision
613 @type int 691 @type int
614 @param color color of the node 692 @param color color of the node
615 @type int 693 @type int
616 @param bottomedges list of edges for the bottom of the node 694 @param bottomedges list of edges for the bottom of the node
629 that can by pushed 707 that can by pushed
630 @type bool 708 @type bool
631 @return icon for the node 709 @return icon for the node
632 @rtype QIcon 710 @rtype QIcon
633 """ 711 """
712
634 def col2x(col, radius): 713 def col2x(col, radius):
635 """ 714 """
636 Local function to calculate a x-position for a column. 715 Local function to calculate a x-position for a column.
637 716
638 @param col column number (integer) 717 @param col column number (integer)
639 @param radius radius of the indicator circle (integer) 718 @param radius radius of the indicator circle (integer)
640 """ 719 """
641 return int(1.2 * radius) * col + radius // 2 + 3 720 return int(1.2 * radius) * col + radius // 2 + 3
642 721
643 textColor = self.logTree.palette().color(QPalette.ColorRole.Text) 722 textColor = self.logTree.palette().color(QPalette.ColorRole.Text)
644 723
645 radius = self.__dotRadius 724 radius = self.__dotRadius
646 w = len(bottomedges) * radius + 20 725 w = len(bottomedges) * radius + 20
647 h = self.__rowHeight 726 h = self.__rowHeight
648 727
649 dot_x = col2x(column, radius) - radius // 2 728 dot_x = col2x(column, radius) - radius // 2
650 dot_y = h // 2 729 dot_y = h // 2
651 730
652 pix = QPixmap(w, h) 731 pix = QPixmap(w, h)
653 pix.fill(QColor(0, 0, 0, 0)) # draw transparent background 732 pix.fill(QColor(0, 0, 0, 0)) # draw transparent background
654 painter = QPainter(pix) 733 painter = QPainter(pix)
655 painter.setRenderHint(QPainter.RenderHint.Antialiasing) 734 painter.setRenderHint(QPainter.RenderHint.Antialiasing)
656 735
657 # draw the revision history lines 736 # draw the revision history lines
658 for y1, y2, lines in ((0, h, bottomedges), 737 for y1, y2, lines in ((0, h, bottomedges), (-h, 0, topedges)):
659 (-h, 0, topedges)):
660 if lines: 738 if lines:
661 for start, end, ecolor in lines: 739 for start, end, ecolor in lines:
662 lpen = QPen(QColor(self.__getColor(ecolor))) 740 lpen = QPen(QColor(self.__getColor(ecolor)))
663 lpen.setWidth(2) 741 lpen.setWidth(2)
664 painter.setPen(lpen) 742 painter.setPen(lpen)
665 x1 = col2x(start, radius) 743 x1 = col2x(start, radius)
666 x2 = col2x(end, radius) 744 x2 = col2x(end, radius)
667 painter.drawLine(x1, dot_y + y1, x2, dot_y + y2) 745 painter.drawLine(x1, dot_y + y1, x2, dot_y + y2)
668 746
669 penradius = 1 747 penradius = 1
670 pencolor = textColor 748 pencolor = textColor
671 749
672 dot_y = (h // 2) - radius // 2 750 dot_y = (h // 2) - radius // 2
673 751
674 # draw an indicator for the revision 752 # draw an indicator for the revision
675 if currentRev: 753 if currentRev:
676 # enlarge for the current revision 754 # enlarge for the current revision
677 delta = 1 755 delta = 1
678 radius += 2 * delta 756 radius += 2 * delta
682 painter.setBrush(dotColor) 760 painter.setBrush(dotColor)
683 pen = QPen(pencolor) 761 pen = QPen(pencolor)
684 pen.setWidth(penradius) 762 pen.setWidth(penradius)
685 painter.setPen(pen) 763 painter.setPen(pen)
686 if closed: 764 if closed:
687 painter.drawRect(dot_x - 2, dot_y + 1, 765 painter.drawRect(dot_x - 2, dot_y + 1, radius + 4, radius - 2)
688 radius + 4, radius - 2)
689 elif self.commandMode in ("incoming", "outgoing"): 766 elif self.commandMode in ("incoming", "outgoing"):
690 offset = radius // 2 767 offset = radius // 2
691 if self.commandMode == "incoming": 768 if self.commandMode == "incoming":
692 # incoming: draw a down arrow 769 # incoming: draw a down arrow
693 painter.drawConvexPolygon( 770 painter.drawConvexPolygon(
694 QPoint(dot_x, dot_y), 771 QPoint(dot_x, dot_y),
695 QPoint(dot_x + 2 * offset, dot_y), 772 QPoint(dot_x + 2 * offset, dot_y),
696 QPoint(dot_x + offset, dot_y + 2 * offset) 773 QPoint(dot_x + offset, dot_y + 2 * offset),
697 ) 774 )
698 else: 775 else:
699 # outgoing: draw an up arrow 776 # outgoing: draw an up arrow
700 painter.drawConvexPolygon( 777 painter.drawConvexPolygon(
701 QPoint(dot_x + offset, dot_y), 778 QPoint(dot_x + offset, dot_y),
702 QPoint(dot_x, dot_y + 2 * offset), 779 QPoint(dot_x, dot_y + 2 * offset),
703 QPoint(dot_x + 2 * offset, dot_y + 2 * offset) 780 QPoint(dot_x + 2 * offset, dot_y + 2 * offset),
704 ) 781 )
705 else: 782 else:
706 if isPushableDraft: 783 if isPushableDraft:
707 # 'draft' phase: draw an up arrow like outgoing, 784 # 'draft' phase: draw an up arrow like outgoing,
708 # if it can be pushed 785 # if it can be pushed
709 offset = radius // 2 786 offset = radius // 2
710 painter.drawConvexPolygon( 787 painter.drawConvexPolygon(
711 QPoint(dot_x + offset, dot_y), 788 QPoint(dot_x + offset, dot_y),
712 QPoint(dot_x, dot_y + 2 * offset), 789 QPoint(dot_x, dot_y + 2 * offset),
713 QPoint(dot_x + 2 * offset, dot_y + 2 * offset) 790 QPoint(dot_x + 2 * offset, dot_y + 2 * offset),
714 ) 791 )
715 else: 792 else:
716 painter.drawEllipse(dot_x, dot_y, radius, radius) 793 painter.drawEllipse(dot_x, dot_y, radius, radius)
717 painter.end() 794 painter.end()
718 return QIcon(pix) 795 return QIcon(pix)
719 796
720 def __getParents(self, rev): 797 def __getParents(self, rev):
721 """ 798 """
722 Private method to get the parents of the currently viewed 799 Private method to get the parents of the currently viewed
723 file/directory. 800 file/directory.
724 801
725 @param rev revision number to get parents for (string) 802 @param rev revision number to get parents for (string)
726 @return list of parent revisions (list of integers) 803 @return list of parent revisions (list of integers)
727 """ 804 """
728 errMsg = "" 805 errMsg = ""
729 parents = [-1] 806 parents = [-1]
730 807
731 if int(rev) > 0: 808 if int(rev) > 0:
732 args = self.vcs.initCommand("parents") 809 args = self.vcs.initCommand("parents")
733 if self.commandMode == "incoming": 810 if self.commandMode == "incoming":
734 if self.__bundle: 811 if self.__bundle:
735 args.append("--repository") 812 args.append("--repository")
736 args.append(self.__bundle) 813 args.append(self.__bundle)
737 elif ( 814 elif self.vcs.bundleFile and os.path.exists(self.vcs.bundleFile):
738 self.vcs.bundleFile and
739 os.path.exists(self.vcs.bundleFile)
740 ):
741 args.append("--repository") 815 args.append("--repository")
742 args.append(self.vcs.bundleFile) 816 args.append(self.vcs.bundleFile)
743 args.append("--template") 817 args.append("--template")
744 args.append("{rev}\n") 818 args.append("{rev}\n")
745 args.append("-r") 819 args.append("-r")
746 args.append(rev) 820 args.append(rev)
747 if not self.projectMode: 821 if not self.projectMode:
748 args.append(self.__filename) 822 args.append(self.__filename)
749 823
750 output, errMsg = self.__hgClient.runcommand(args) 824 output, errMsg = self.__hgClient.runcommand(args)
751 825
752 if output: 826 if output:
753 parents = [int(p) for p in output.strip().splitlines()] 827 parents = [int(p) for p in output.strip().splitlines()]
754 828
755 return parents 829 return parents
756 830
757 def __identifyProject(self): 831 def __identifyProject(self):
758 """ 832 """
759 Private method to determine the revision of the project directory. 833 Private method to determine the revision of the project directory.
760 """ 834 """
761 errMsg = "" 835 errMsg = ""
762 836
763 args = self.vcs.initCommand("identify") 837 args = self.vcs.initCommand("identify")
764 args.append("-nb") 838 args.append("-nb")
765 839
766 output, errMsg = self.__hgClient.runcommand(args) 840 output, errMsg = self.__hgClient.runcommand(args)
767 841
768 if errMsg: 842 if errMsg:
769 EricMessageBox.critical( 843 EricMessageBox.critical(self, self.tr("Mercurial Error"), errMsg)
770 self, 844
771 self.tr("Mercurial Error"),
772 errMsg)
773
774 if output: 845 if output:
775 outputList = output.strip().split(None, 1) 846 outputList = output.strip().split(None, 1)
776 if len(outputList) == 2: 847 if len(outputList) == 2:
777 outputRevs = outputList[0].strip() 848 outputRevs = outputList[0].strip()
778 if outputRevs.endswith("+"): 849 if outputRevs.endswith("+"):
779 outputRevs = outputRevs[:-1] 850 outputRevs = outputRevs[:-1]
780 self.__projectWorkingDirParents = outputRevs.split('+') 851 self.__projectWorkingDirParents = outputRevs.split("+")
781 else: 852 else:
782 self.__projectWorkingDirParents = [outputRevs] 853 self.__projectWorkingDirParents = [outputRevs]
783 self.__projectBranch = outputList[1].strip() 854 self.__projectBranch = outputList[1].strip()
784 855
785 def __getClosedBranches(self): 856 def __getClosedBranches(self):
786 """ 857 """
787 Private method to get the list of closed branches. 858 Private method to get the list of closed branches.
788 """ 859 """
789 self.__closedBranchesRevs = [] 860 self.__closedBranchesRevs = []
790 errMsg = "" 861 errMsg = ""
791 862
792 args = self.vcs.initCommand("branches") 863 args = self.vcs.initCommand("branches")
793 args.append("--closed") 864 args.append("--closed")
794 865
795 output, errMsg = self.__hgClient.runcommand(args) 866 output, errMsg = self.__hgClient.runcommand(args)
796 867
797 if errMsg: 868 if errMsg:
798 EricMessageBox.critical( 869 EricMessageBox.critical(self, self.tr("Mercurial Error"), errMsg)
799 self, 870
800 self.tr("Mercurial Error"),
801 errMsg)
802
803 if output: 871 if output:
804 for line in output.splitlines(): 872 for line in output.splitlines():
805 if line.strip().endswith("(closed)"): 873 if line.strip().endswith("(closed)"):
806 parts = line.split() 874 parts = line.split()
807 self.__closedBranchesRevs.append( 875 self.__closedBranchesRevs.append(parts[-2].split(":", 1)[0])
808 parts[-2].split(":", 1)[0]) 876
809
810 def __getHeads(self): 877 def __getHeads(self):
811 """ 878 """
812 Private method to get the list of all heads. 879 Private method to get the list of all heads.
813 """ 880 """
814 self.__headRevisions = [] 881 self.__headRevisions = []
815 errMsg = "" 882 errMsg = ""
816 883
817 args = self.vcs.initCommand("heads") 884 args = self.vcs.initCommand("heads")
818 args.append("--closed") 885 args.append("--closed")
819 args.append("--template") 886 args.append("--template")
820 args.append("{rev}\n") 887 args.append("{rev}\n")
821 888
822 output, errMsg = self.__hgClient.runcommand(args) 889 output, errMsg = self.__hgClient.runcommand(args)
823 890
824 if errMsg: 891 if errMsg:
825 EricMessageBox.critical( 892 EricMessageBox.critical(self, self.tr("Mercurial Error"), errMsg)
826 self, 893
827 self.tr("Mercurial Error"),
828 errMsg)
829
830 if output: 894 if output:
831 for line in output.splitlines(): 895 for line in output.splitlines():
832 line = line.strip() 896 line = line.strip()
833 if line: 897 if line:
834 self.__headRevisions.append(line) 898 self.__headRevisions.append(line)
835 899
836 def __getRevisionOfTag(self, tag): 900 def __getRevisionOfTag(self, tag):
837 """ 901 """
838 Private method to get the revision of a tag. 902 Private method to get the revision of a tag.
839 903
840 @param tag tag name 904 @param tag tag name
841 @type str 905 @type str
842 @return tuple containing the revision and changeset ID 906 @return tuple containing the revision and changeset ID
843 @rtype tuple of (str, str) 907 @rtype tuple of (str, str)
844 """ 908 """
845 errMsg = "" 909 errMsg = ""
846 910
847 args = self.vcs.initCommand("tags") 911 args = self.vcs.initCommand("tags")
848 912
849 output, errMsg = self.__hgClient.runcommand(args) 913 output, errMsg = self.__hgClient.runcommand(args)
850 914
851 if errMsg: 915 if errMsg:
852 EricMessageBox.critical( 916 EricMessageBox.critical(self, self.tr("Mercurial Error"), errMsg)
853 self, 917
854 self.tr("Mercurial Error"),
855 errMsg)
856
857 res = ("", "") 918 res = ("", "")
858 if output: 919 if output:
859 for line in output.splitlines(): 920 for line in output.splitlines():
860 if line.strip(): 921 if line.strip():
861 with contextlib.suppress(ValueError): 922 with contextlib.suppress(ValueError):
862 name, rev = line.strip().rsplit(None, 1) 923 name, rev = line.strip().rsplit(None, 1)
863 if name == tag: 924 if name == tag:
864 res = tuple(rev.split(":", 1)) 925 res = tuple(rev.split(":", 1))
865 break 926 break
866 927
867 return res 928 return res
868 929
869 def __generateLogItem(self, author, date, message, revision, changedPaths, 930 def __generateLogItem(
870 parents, branches, tags, phase, bookmarks, 931 self,
871 latestTag, canPush=False): 932 author,
933 date,
934 message,
935 revision,
936 changedPaths,
937 parents,
938 branches,
939 tags,
940 phase,
941 bookmarks,
942 latestTag,
943 canPush=False,
944 ):
872 """ 945 """
873 Private method to generate a log tree entry. 946 Private method to generate a log tree entry.
874 947
875 @param author author info 948 @param author author info
876 @type str 949 @type str
877 @param date date info 950 @param date date info
878 @type str 951 @type str
879 @param message text of the log message 952 @param message text of the log message
899 @type bool 972 @type bool
900 @return reference to the generated item 973 @return reference to the generated item
901 @rtype QTreeWidgetItem 974 @rtype QTreeWidgetItem
902 """ 975 """
903 logMessageColumnWidth = self.vcs.getPlugin().getPreferences( 976 logMessageColumnWidth = self.vcs.getPlugin().getPreferences(
904 "LogMessageColumnWidth") 977 "LogMessageColumnWidth"
978 )
905 msgtxt = "" 979 msgtxt = ""
906 for line in message: 980 for line in message:
907 if ". " in line: 981 if ". " in line:
908 msgtxt += " " + line.strip().split(". ", 1)[0] + "." 982 msgtxt += " " + line.strip().split(". ", 1)[0] + "."
909 break 983 break
910 else: 984 else:
911 msgtxt += " " + line.strip() 985 msgtxt += " " + line.strip()
912 if len(msgtxt) > logMessageColumnWidth: 986 if len(msgtxt) > logMessageColumnWidth:
913 msgtxt = "{0}...".format(msgtxt[:logMessageColumnWidth]) 987 msgtxt = "{0}...".format(msgtxt[:logMessageColumnWidth])
914 988
915 rev, node = revision.split(":") 989 rev, node = revision.split(":")
916 closedStr = (self.ClosedIndicator 990 closedStr = self.ClosedIndicator if rev in self.__closedBranchesRevs else ""
917 if rev in self.__closedBranchesRevs else "")
918 phaseStr = self.phases.get(phase, phase) 991 phaseStr = self.phases.get(phase, phase)
919 columnLabels = [ 992 columnLabels = [
920 "", 993 "",
921 branches[0] + closedStr, 994 branches[0] + closedStr,
922 "{0:>7}:{1}".format(rev, node), 995 "{0:>7}:{1}".format(rev, node),
927 ", ".join(tags), 1000 ", ".join(tags),
928 ] 1001 ]
929 if bookmarks is not None: 1002 if bookmarks is not None:
930 columnLabels.append(", ".join(bookmarks)) 1003 columnLabels.append(", ".join(bookmarks))
931 itm = QTreeWidgetItem(self.logTree, columnLabels) 1004 itm = QTreeWidgetItem(self.logTree, columnLabels)
932 1005
933 itm.setForeground(self.BranchColumn, 1006 itm.setForeground(
934 QBrush(QColor(self.__branchColor(branches[0])))) 1007 self.BranchColumn, QBrush(QColor(self.__branchColor(branches[0])))
935 1008 )
1009
936 if not self.projectMode: 1010 if not self.projectMode:
937 parents = self.__getParents(rev) 1011 parents = self.__getParents(rev)
938 if not parents: 1012 if not parents:
939 parents = [int(rev) - 1] 1013 parents = [int(rev) - 1]
940 column, color, edges = self.__generateEdges(int(rev), parents) 1014 column, color, edges = self.__generateEdges(int(rev), parents)
941 1015
942 itm.setData(0, self.__messageRole, message) 1016 itm.setData(0, self.__messageRole, message)
943 itm.setData(0, self.__changesRole, changedPaths) 1017 itm.setData(0, self.__changesRole, changedPaths)
944 itm.setData(0, self.__edgesRole, edges) 1018 itm.setData(0, self.__edgesRole, edges)
945 itm.setData(0, self.__latestTagRole, latestTag) 1019 itm.setData(0, self.__latestTagRole, latestTag)
946 if parents == [-1]: 1020 if parents == [-1]:
948 else: 1022 else:
949 itm.setData(0, self.__parentsRole, parents) 1023 itm.setData(0, self.__parentsRole, parents)
950 for parent in parents: 1024 for parent in parents:
951 self.__childrenInfo[parent].append(int(rev)) 1025 self.__childrenInfo[parent].append(int(rev))
952 itm.setData(0, self.__incomingRole, self.commandMode == "incoming") 1026 itm.setData(0, self.__incomingRole, self.commandMode == "incoming")
953 1027
954 topedges = ( 1028 topedges = (
955 self.logTree.topLevelItem( 1029 self.logTree.topLevelItem(self.logTree.indexOfTopLevelItem(itm) - 1).data(
956 self.logTree.indexOfTopLevelItem(itm) - 1 1030 0, self.__edgesRole
957 ).data(0, self.__edgesRole) 1031 )
958 if self.logTree.topLevelItemCount() > 1 else 1032 if self.logTree.topLevelItemCount() > 1
959 None 1033 else None
960 ) 1034 )
961 1035
962 icon = self.__generateIcon(column, color, edges, topedges, 1036 icon = self.__generateIcon(
963 QColor(self.__branchColor(branches[0])), 1037 column,
964 rev in self.__projectWorkingDirParents, 1038 color,
965 rev in self.__closedBranchesRevs, 1039 edges,
966 phase == "draft" and canPush) 1040 topedges,
1041 QColor(self.__branchColor(branches[0])),
1042 rev in self.__projectWorkingDirParents,
1043 rev in self.__closedBranchesRevs,
1044 phase == "draft" and canPush,
1045 )
967 itm.setIcon(0, icon) 1046 itm.setIcon(0, icon)
968 1047
969 try: 1048 try:
970 self.__lastRev = int(revision.split(":")[0]) 1049 self.__lastRev = int(revision.split(":")[0])
971 except ValueError: 1050 except ValueError:
972 self.__lastRev = 0 1051 self.__lastRev = 0
973 1052
974 return itm 1053 return itm
975 1054
976 def __getLogEntries(self, startRev=None, noEntries=0): 1055 def __getLogEntries(self, startRev=None, noEntries=0):
977 """ 1056 """
978 Private method to retrieve log entries from the repository. 1057 Private method to retrieve log entries from the repository.
979 1058
980 @param startRev revision number to start from (integer, string) 1059 @param startRev revision number to start from (integer, string)
981 @param noEntries number of entries to get (0 = default) (int) 1060 @param noEntries number of entries to get (0 = default) (int)
982 """ 1061 """
983 self.buttonBox.button( 1062 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
984 QDialogButtonBox.StandardButton.Close).setEnabled(False) 1063 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
985 self.buttonBox.button( 1064 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
986 QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
987 self.buttonBox.button(
988 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
989 QApplication.processEvents() 1065 QApplication.processEvents()
990 1066
991 with EricOverrideCursor(): 1067 with EricOverrideCursor():
992 self.buf = [] 1068 self.buf = []
993 self.cancelled = False 1069 self.cancelled = False
994 self.errors.clear() 1070 self.errors.clear()
995 self.intercept = False 1071 self.intercept = False
996 1072
997 if noEntries == 0: 1073 if noEntries == 0:
998 noEntries = self.limitSpinBox.value() 1074 noEntries = self.limitSpinBox.value()
999 1075
1000 preargs = [] 1076 preargs = []
1001 args = self.vcs.initCommand(self.commandMode) 1077 args = self.vcs.initCommand(self.commandMode)
1002 args.append('--verbose') 1078 args.append("--verbose")
1003 if self.commandMode not in ("incoming", "outgoing"): 1079 if self.commandMode not in ("incoming", "outgoing"):
1004 args.append('--limit') 1080 args.append("--limit")
1005 args.append(str(noEntries)) 1081 args.append(str(noEntries))
1006 if self.commandMode in ("incoming", "outgoing"): 1082 if self.commandMode in ("incoming", "outgoing"):
1007 args.append("--newest-first") 1083 args.append("--newest-first")
1008 if self.vcs.hasSubrepositories(): 1084 if self.vcs.hasSubrepositories():
1009 args.append("--subrepos") 1085 args.append("--subrepos")
1010 if startRev is not None: 1086 if startRev is not None:
1011 args.append('--rev') 1087 args.append("--rev")
1012 args.append('{0}:0'.format(startRev)) 1088 args.append("{0}:0".format(startRev))
1013 if ( 1089 if not self.projectMode and not self.stopCheckBox.isChecked():
1014 not self.projectMode and 1090 args.append("--follow")
1015 not self.stopCheckBox.isChecked()
1016 ):
1017 args.append('--follow')
1018 if self.commandMode == "log": 1091 if self.commandMode == "log":
1019 args.append('--copies') 1092 args.append("--copies")
1020 args.append('--template') 1093 args.append("--template")
1021 args.append(os.path.join(os.path.dirname(__file__), 1094 args.append(
1022 "templates", 1095 os.path.join(
1023 "logBrowserBookmarkPhase.tmpl")) 1096 os.path.dirname(__file__),
1097 "templates",
1098 "logBrowserBookmarkPhase.tmpl",
1099 )
1100 )
1024 if self.commandMode == "incoming": 1101 if self.commandMode == "incoming":
1025 if self.__bundle: 1102 if self.__bundle:
1026 args.append(self.__bundle) 1103 args.append(self.__bundle)
1027 elif not self.vcs.hasSubrepositories(): 1104 elif not self.vcs.hasSubrepositories():
1028 project = ericApp().getObject("Project") 1105 project = ericApp().getObject("Project")
1029 self.vcs.bundleFile = os.path.join( 1106 self.vcs.bundleFile = os.path.join(
1030 project.getProjectManagementDir(), "hg-bundle.hg") 1107 project.getProjectManagementDir(), "hg-bundle.hg"
1108 )
1031 if os.path.exists(self.vcs.bundleFile): 1109 if os.path.exists(self.vcs.bundleFile):
1032 os.remove(self.vcs.bundleFile) 1110 os.remove(self.vcs.bundleFile)
1033 preargs = args[:] 1111 preargs = args[:]
1034 preargs.append("--quiet") 1112 preargs.append("--quiet")
1035 preargs.append('--bundle') 1113 preargs.append("--bundle")
1036 preargs.append(self.vcs.bundleFile) 1114 preargs.append(self.vcs.bundleFile)
1037 args.append(self.vcs.bundleFile) 1115 args.append(self.vcs.bundleFile)
1038 if not self.projectMode: 1116 if not self.projectMode:
1039 args.append(self.__filename) 1117 args.append(self.__filename)
1040 1118
1041 if preargs: 1119 if preargs:
1042 out, err = self.__hgClient.runcommand(preargs) 1120 out, err = self.__hgClient.runcommand(preargs)
1043 else: 1121 else:
1044 err = "" 1122 err = ""
1045 if err: 1123 if err:
1046 if ( 1124 if (
1047 self.commandMode == "incoming" and 1125 self.commandMode == "incoming"
1048 self.initialCommandMode == "full_log" 1126 and self.initialCommandMode == "full_log"
1049 ): 1127 ):
1050 # ignore the error 1128 # ignore the error
1051 self.commandMode = "log" 1129 self.commandMode = "log"
1052 else: 1130 else:
1053 self.__showError(err) 1131 self.__showError(err)
1054 elif ( 1132 elif (
1055 self.commandMode != "incoming" or 1133 self.commandMode != "incoming"
1056 (self.vcs.bundleFile and 1134 or (self.vcs.bundleFile and os.path.exists(self.vcs.bundleFile))
1057 os.path.exists(self.vcs.bundleFile)) or 1135 or self.__bundle
1058 self.__bundle
1059 ): 1136 ):
1060 out, err = self.__hgClient.runcommand(args) 1137 out, err = self.__hgClient.runcommand(args)
1061 self.buf = out.splitlines(True) 1138 self.buf = out.splitlines(True)
1062 if err: 1139 if err:
1063 self.__showError(err) 1140 self.__showError(err)
1064 self.__processBuffer() 1141 self.__processBuffer()
1065 elif ( 1142 elif (
1066 self.commandMode == "incoming" and 1143 self.commandMode == "incoming" and self.initialCommandMode == "full_log"
1067 self.initialCommandMode == "full_log"
1068 ): 1144 ):
1069 # no incoming changesets, just switch to log mode 1145 # no incoming changesets, just switch to log mode
1070 self.commandMode = "log" 1146 self.commandMode = "log"
1071 self.__finish() 1147 self.__finish()
1072 1148
1073 def start(self, name=None, bundle=None, isFile=False, noEntries=0): 1149 def start(self, name=None, bundle=None, isFile=False, noEntries=0):
1074 """ 1150 """
1075 Public slot to start the hg log command. 1151 Public slot to start the hg log command.
1076 1152
1077 @param name file/directory name to show the log for 1153 @param name file/directory name to show the log for
1078 @type str 1154 @type str
1079 @param bundle name of a bundle file 1155 @param bundle name of a bundle file
1080 @type str 1156 @type str
1081 @param isFile flag indicating log for a file is to be shown 1157 @param isFile flag indicating log for a file is to be shown
1083 @param noEntries number of entries to get (0 = default) 1159 @param noEntries number of entries to get (0 = default)
1084 @type int 1160 @type int
1085 """ 1161 """
1086 self.__bundle = bundle 1162 self.__bundle = bundle
1087 self.__isFile = isFile 1163 self.__isFile = isFile
1088 1164
1089 if self.initialCommandMode == "full_log": 1165 if self.initialCommandMode == "full_log":
1090 if isFile: 1166 if isFile:
1091 self.commandMode = "log" 1167 self.commandMode = "log"
1092 self.__finishCallbacks = [] 1168 self.__finishCallbacks = []
1093 else: 1169 else:
1094 self.commandMode = "incoming" 1170 self.commandMode = "incoming"
1095 self.__addFinishCallback(self.on_nextButton_clicked) 1171 self.__addFinishCallback(self.on_nextButton_clicked)
1096 1172
1097 self.sbsSelectLabel.clear() 1173 self.sbsSelectLabel.clear()
1098 1174
1099 self.errorGroup.hide() 1175 self.errorGroup.hide()
1100 self.errors.clear() 1176 self.errors.clear()
1101 QApplication.processEvents() 1177 QApplication.processEvents()
1102 1178
1103 self.__initData() 1179 self.__initData()
1104 1180
1105 self.__filename = name 1181 self.__filename = name
1106 1182
1107 self.projectMode = name is None 1183 self.projectMode = name is None
1108 self.stopCheckBox.setDisabled(self.projectMode) 1184 self.stopCheckBox.setDisabled(self.projectMode)
1109 self.activateWindow() 1185 self.activateWindow()
1110 self.raise_() 1186 self.raise_()
1111 1187
1112 self.logTree.clear() 1188 self.logTree.clear()
1113 self.__started = True 1189 self.__started = True
1114 self.__identifyProject() 1190 self.__identifyProject()
1115 self.__getClosedBranches() 1191 self.__getClosedBranches()
1116 self.__getHeads() 1192 self.__getHeads()
1117 self.__getLogEntries(noEntries=noEntries) 1193 self.__getLogEntries(noEntries=noEntries)
1118 1194
1119 def __finish(self): 1195 def __finish(self):
1120 """ 1196 """
1121 Private slot called when the process finished or the user pressed 1197 Private slot called when the process finished or the user pressed
1122 the button. 1198 the button.
1123 """ 1199 """
1124 self.buttonBox.button( 1200 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True)
1125 QDialogButtonBox.StandardButton.Close).setEnabled(True) 1201 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
1126 self.buttonBox.button( 1202 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
1127 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) 1203
1128 self.buttonBox.button(
1129 QDialogButtonBox.StandardButton.Close).setDefault(True)
1130
1131 self.refreshButton.setEnabled(True) 1204 self.refreshButton.setEnabled(True)
1132 1205
1133 while self.__finishCallbacks: 1206 while self.__finishCallbacks:
1134 self.__finishCallbacks.pop(0)() 1207 self.__finishCallbacks.pop(0)()
1135 1208
1136 def __modifyForLargeFiles(self, filename): 1209 def __modifyForLargeFiles(self, filename):
1137 """ 1210 """
1138 Private method to convert the displayed file name for a large file. 1211 Private method to convert the displayed file name for a large file.
1139 1212
1140 @param filename file name to be processed (string) 1213 @param filename file name to be processed (string)
1141 @return processed file name (string) 1214 @return processed file name (string)
1142 """ 1215 """
1143 if filename.startswith((self.LargefilesCacheL, self.LargefilesCacheW)): 1216 if filename.startswith((self.LargefilesCacheL, self.LargefilesCacheW)):
1144 return self.tr("{0} (large file)").format( 1217 return self.tr("{0} (large file)").format(
1145 self.PathSeparatorRe.split(filename, 1)[1]) 1218 self.PathSeparatorRe.split(filename, 1)[1]
1219 )
1146 else: 1220 else:
1147 return filename 1221 return filename
1148 1222
1149 def __processBuffer(self): 1223 def __processBuffer(self):
1150 """ 1224 """
1151 Private method to process the buffered output of the hg log command. 1225 Private method to process the buffered output of the hg log command.
1152 """ 1226 """
1153 noEntries = 0 1227 noEntries = 0
1168 log["revision"] = value.strip() 1242 log["revision"] = value.strip()
1169 elif key == "user": 1243 elif key == "user":
1170 log["author"] = value.strip() 1244 log["author"] = value.strip()
1171 elif key == "parents": 1245 elif key == "parents":
1172 log["parents"] = [ 1246 log["parents"] = [
1173 int(x.split(":", 1)[0]) 1247 int(x.split(":", 1)[0]) for x in value.strip().split()
1174 for x in value.strip().split()
1175 ] 1248 ]
1176 elif key == "date": 1249 elif key == "date":
1177 log["date"] = " ".join(value.strip().split()[:2]) 1250 log["date"] = " ".join(value.strip().split()[:2])
1178 elif key == "description": 1251 elif key == "description":
1179 log["message"].append(value.strip()) 1252 log["message"].append(value.strip())
1180 elif key == "file_adds": 1253 elif key == "file_adds":
1181 if value.strip(): 1254 if value.strip():
1182 for f in value.strip().split(", "): 1255 for f in value.strip().split(", "):
1183 if f in fileCopies: 1256 if f in fileCopies:
1184 changedPaths.append({ 1257 changedPaths.append(
1185 "action": "A", 1258 {
1186 "path": self.__modifyForLargeFiles(f), 1259 "action": "A",
1187 "copyfrom": self.__modifyForLargeFiles( 1260 "path": self.__modifyForLargeFiles(f),
1188 fileCopies[f]), 1261 "copyfrom": self.__modifyForLargeFiles(
1189 }) 1262 fileCopies[f]
1263 ),
1264 }
1265 )
1190 else: 1266 else:
1191 changedPaths.append({ 1267 changedPaths.append(
1192 "action": "A", 1268 {
1193 "path": self.__modifyForLargeFiles(f), 1269 "action": "A",
1194 "copyfrom": "", 1270 "path": self.__modifyForLargeFiles(f),
1195 }) 1271 "copyfrom": "",
1272 }
1273 )
1196 elif key == "files_mods": 1274 elif key == "files_mods":
1197 if value.strip(): 1275 if value.strip():
1198 for f in value.strip().split(", "): 1276 for f in value.strip().split(", "):
1199 changedPaths.append({ 1277 changedPaths.append(
1200 "action": "M", 1278 {
1201 "path": self.__modifyForLargeFiles(f), 1279 "action": "M",
1202 "copyfrom": "", 1280 "path": self.__modifyForLargeFiles(f),
1203 }) 1281 "copyfrom": "",
1282 }
1283 )
1204 elif key == "file_dels": 1284 elif key == "file_dels":
1205 if value.strip(): 1285 if value.strip():
1206 for f in value.strip().split(", "): 1286 for f in value.strip().split(", "):
1207 changedPaths.append({ 1287 changedPaths.append(
1208 "action": "D", 1288 {
1209 "path": self.__modifyForLargeFiles(f), 1289 "action": "D",
1210 "copyfrom": "", 1290 "path": self.__modifyForLargeFiles(f),
1211 }) 1291 "copyfrom": "",
1292 }
1293 )
1212 elif key == "file_copies": 1294 elif key == "file_copies":
1213 if value.strip(): 1295 if value.strip():
1214 for entry in value.strip().split(", "): 1296 for entry in value.strip().split(", "):
1215 newName, oldName = entry[:-1].split(" (") 1297 newName, oldName = entry[:-1].split(" (")
1216 fileCopies[newName] = oldName 1298 fileCopies[newName] = oldName
1229 tag = value.strip() 1311 tag = value.strip()
1230 if tag == "null": 1312 if tag == "null":
1231 log["latesttag"] = [] 1313 log["latesttag"] = []
1232 elif ":" in tag: 1314 elif ":" in tag:
1233 log["latesttag"] = [ 1315 log["latesttag"] = [
1234 t.strip() for t in tag.split(":") if t.strip()] 1316 t.strip() for t in tag.split(":") if t.strip()
1317 ]
1235 else: 1318 else:
1236 log["latesttag"] = [tag] 1319 log["latesttag"] = [tag]
1237 else: 1320 else:
1238 if initialText: 1321 if initialText:
1239 continue 1322 continue
1240 if value.strip(): 1323 if value.strip():
1241 log["message"].append(value.strip()) 1324 log["message"].append(value.strip())
1242 else: 1325 else:
1243 if len(log) > 1: 1326 if len(log) > 1:
1244 self.__generateLogItem( 1327 self.__generateLogItem(
1245 log["author"], log["date"], 1328 log["author"],
1246 log["message"], log["revision"], changedPaths, 1329 log["date"],
1247 log["parents"], log["branches"], log["tags"], 1330 log["message"],
1248 log["phase"], log["bookmarks"], log["latesttag"], 1331 log["revision"],
1249 canPush=canPush) 1332 changedPaths,
1333 log["parents"],
1334 log["branches"],
1335 log["tags"],
1336 log["phase"],
1337 log["bookmarks"],
1338 log["latesttag"],
1339 canPush=canPush,
1340 )
1250 dt = QDate.fromString(log["date"], Qt.DateFormat.ISODate) 1341 dt = QDate.fromString(log["date"], Qt.DateFormat.ISODate)
1251 if ( 1342 if not self.__maxDate.isValid() and not self.__minDate.isValid():
1252 not self.__maxDate.isValid() and
1253 not self.__minDate.isValid()
1254 ):
1255 self.__maxDate = dt 1343 self.__maxDate = dt
1256 self.__minDate = dt 1344 self.__minDate = dt
1257 else: 1345 else:
1258 if self.__maxDate < dt: 1346 if self.__maxDate < dt:
1259 self.__maxDate = dt 1347 self.__maxDate = dt
1261 self.__minDate = dt 1349 self.__minDate = dt
1262 noEntries += 1 1350 noEntries += 1
1263 log = {"message": [], "bookmarks": None, "phase": ""} 1351 log = {"message": [], "bookmarks": None, "phase": ""}
1264 changedPaths = [] 1352 changedPaths = []
1265 fileCopies = {} 1353 fileCopies = {}
1266 1354
1267 self.__resizeColumnsLog() 1355 self.__resizeColumnsLog()
1268 1356
1269 if self.__started and not self.__finishCallbacks: 1357 if self.__started and not self.__finishCallbacks:
1270 # we are really done 1358 # we are really done
1271 if self.__selectedRevisions: 1359 if self.__selectedRevisions:
1272 foundItems = self.logTree.findItems( 1360 foundItems = self.logTree.findItems(
1273 self.__selectedRevisions[0], Qt.MatchFlag.MatchExactly, 1361 self.__selectedRevisions[0],
1274 self.RevisionColumn) 1362 Qt.MatchFlag.MatchExactly,
1363 self.RevisionColumn,
1364 )
1275 if foundItems: 1365 if foundItems:
1276 self.logTree.setCurrentItem(foundItems[0]) 1366 self.logTree.setCurrentItem(foundItems[0])
1277 else: 1367 else:
1278 self.logTree.setCurrentItem( 1368 self.logTree.setCurrentItem(self.logTree.topLevelItem(0))
1279 self.logTree.topLevelItem(0))
1280 elif self.__projectWorkingDirParents: 1369 elif self.__projectWorkingDirParents:
1281 for rev in self.__projectWorkingDirParents: 1370 for rev in self.__projectWorkingDirParents:
1282 # rev string format must match with the format of the 1371 # rev string format must match with the format of the
1283 # __generateLogItem() method 1372 # __generateLogItem() method
1284 items = self.logTree.findItems( 1373 items = self.logTree.findItems(
1285 "{0:>7}:".format(rev), 1374 "{0:>7}:".format(rev),
1286 Qt.MatchFlag.MatchStartsWith, 1375 Qt.MatchFlag.MatchStartsWith,
1287 self.RevisionColumn) 1376 self.RevisionColumn,
1377 )
1288 if items: 1378 if items:
1289 self.logTree.setCurrentItem(items[0]) 1379 self.logTree.setCurrentItem(items[0])
1290 break 1380 break
1291 else: 1381 else:
1292 self.logTree.setCurrentItem( 1382 self.logTree.setCurrentItem(self.logTree.topLevelItem(0))
1293 self.logTree.topLevelItem(0))
1294 else: 1383 else:
1295 self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) 1384 self.logTree.setCurrentItem(self.logTree.topLevelItem(0))
1296 self.__started = False 1385 self.__started = False
1297 1386
1298 if self.commandMode in ("incoming", "outgoing"): 1387 if self.commandMode in ("incoming", "outgoing"):
1299 self.commandMode = "log" # switch to log mode 1388 self.commandMode = "log" # switch to log mode
1300 if self.__lastRev > 0: 1389 if self.__lastRev > 0:
1301 self.nextButton.setEnabled(True) 1390 self.nextButton.setEnabled(True)
1302 self.limitSpinBox.setEnabled(True) 1391 self.limitSpinBox.setEnabled(True)
1303 else: 1392 else:
1304 if noEntries < self.limitSpinBox.value() and not self.cancelled: 1393 if noEntries < self.limitSpinBox.value() and not self.cancelled:
1305 self.nextButton.setEnabled(False) 1394 self.nextButton.setEnabled(False)
1306 self.limitSpinBox.setEnabled(False) 1395 self.limitSpinBox.setEnabled(False)
1307 1396
1308 # update the log filters 1397 # update the log filters
1309 self.__filterLogsEnabled = False 1398 self.__filterLogsEnabled = False
1310 self.fromDate.setMinimumDate(self.__minDate) 1399 self.fromDate.setMinimumDate(self.__minDate)
1311 self.fromDate.setMaximumDate(self.__maxDate) 1400 self.fromDate.setMaximumDate(self.__maxDate)
1312 self.fromDate.setDate(self.__minDate) 1401 self.fromDate.setDate(self.__minDate)
1313 self.toDate.setMinimumDate(self.__minDate) 1402 self.toDate.setMinimumDate(self.__minDate)
1314 self.toDate.setMaximumDate(self.__maxDate) 1403 self.toDate.setMaximumDate(self.__maxDate)
1315 self.toDate.setDate(self.__maxDate) 1404 self.toDate.setDate(self.__maxDate)
1316 1405
1317 branchFilter = self.branchCombo.currentText() 1406 branchFilter = self.branchCombo.currentText()
1318 if not branchFilter: 1407 if not branchFilter:
1319 branchFilter = self.__allBranchesFilter 1408 branchFilter = self.__allBranchesFilter
1320 self.branchCombo.clear() 1409 self.branchCombo.clear()
1321 self.branchCombo.addItems( 1410 self.branchCombo.addItems(
1322 [self.__allBranchesFilter] + sorted(self.__branchColors.keys())) 1411 [self.__allBranchesFilter] + sorted(self.__branchColors.keys())
1323 self.branchCombo.setCurrentIndex( 1412 )
1324 self.branchCombo.findText(branchFilter)) 1413 self.branchCombo.setCurrentIndex(self.branchCombo.findText(branchFilter))
1325 1414
1326 self.__filterLogsEnabled = True 1415 self.__filterLogsEnabled = True
1327 if self.__actionMode() == "filter": 1416 if self.__actionMode() == "filter":
1328 self.__filterLogs() 1417 self.__filterLogs()
1329 self.__updateToolMenuActions() 1418 self.__updateToolMenuActions()
1330 1419
1331 # restore selected item 1420 # restore selected item
1332 if self.__selectedRevisions and not self.__finishCallbacks: 1421 if self.__selectedRevisions and not self.__finishCallbacks:
1333 # we are really done 1422 # we are really done
1334 for revision in self.__selectedRevisions: 1423 for revision in self.__selectedRevisions:
1335 items = self.logTree.findItems( 1424 items = self.logTree.findItems(
1336 revision, Qt.MatchFlag.MatchExactly, self.RevisionColumn) 1425 revision, Qt.MatchFlag.MatchExactly, self.RevisionColumn
1426 )
1337 if items: 1427 if items:
1338 items[0].setSelected(True) 1428 items[0].setSelected(True)
1339 self.__selectedRevisions = [] 1429 self.__selectedRevisions = []
1340 1430
1341 def __showError(self, out): 1431 def __showError(self, out):
1342 """ 1432 """
1343 Private slot to show some error. 1433 Private slot to show some error.
1344 1434
1345 @param out error to be shown (string) 1435 @param out error to be shown (string)
1346 """ 1436 """
1347 self.errorGroup.show() 1437 self.errorGroup.show()
1348 self.errors.insertPlainText(out) 1438 self.errors.insertPlainText(out)
1349 self.errors.ensureCursorVisible() 1439 self.errors.ensureCursorVisible()
1350 1440
1351 def on_buttonBox_clicked(self, button): 1441 def on_buttonBox_clicked(self, button):
1352 """ 1442 """
1353 Private slot called by a button of the button box clicked. 1443 Private slot called by a button of the button box clicked.
1354 1444
1355 @param button button that was clicked (QAbstractButton) 1445 @param button button that was clicked (QAbstractButton)
1356 """ 1446 """
1357 if button == self.buttonBox.button( 1447 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close):
1358 QDialogButtonBox.StandardButton.Close
1359 ):
1360 self.close() 1448 self.close()
1361 elif button == self.buttonBox.button( 1449 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel):
1362 QDialogButtonBox.StandardButton.Cancel
1363 ):
1364 self.cancelled = True 1450 self.cancelled = True
1365 self.__hgClient.cancel() 1451 self.__hgClient.cancel()
1366 elif button == self.refreshButton: 1452 elif button == self.refreshButton:
1367 self.on_refreshButton_clicked() 1453 self.on_refreshButton_clicked()
1368 1454
1369 def __updateSbsSelectLabel(self): 1455 def __updateSbsSelectLabel(self):
1370 """ 1456 """
1371 Private slot to update the enabled status of the diff buttons. 1457 Private slot to update the enabled status of the diff buttons.
1372 """ 1458 """
1373 self.sbsSelectLabel.clear() 1459 self.sbsSelectLabel.clear()
1374 if self.__isFile: 1460 if self.__isFile:
1375 selectedItems = self.logTree.selectedItems() 1461 selectedItems = self.logTree.selectedItems()
1376 if len(selectedItems) == 1: 1462 if len(selectedItems) == 1:
1377 currentItem = selectedItems[0] 1463 currentItem = selectedItems[0]
1378 rev2 = ( 1464 rev2 = currentItem.text(self.RevisionColumn).split(":", 1)[0].strip()
1379 currentItem.text(self.RevisionColumn).split(":", 1)[0]
1380 .strip()
1381 )
1382 parents = currentItem.data(0, self.__parentsRole) 1465 parents = currentItem.data(0, self.__parentsRole)
1383 if parents: 1466 if parents:
1384 parentLinks = [] 1467 parentLinks = []
1385 for index in range(len(parents)): 1468 for index in range(len(parents)):
1386 parentLinks.append( 1469 parentLinks.append(
1387 '<a href="sbsdiff:{0}_{1}">&nbsp;{2}&nbsp;</a>' 1470 '<a href="sbsdiff:{0}_{1}">&nbsp;{2}&nbsp;</a>'.format(
1388 .format(parents[index], rev2, index + 1)) 1471 parents[index], rev2, index + 1
1472 )
1473 )
1389 self.sbsSelectLabel.setText( 1474 self.sbsSelectLabel.setText(
1390 self.tr('Side-by-Side Diff to Parent {0}').format( 1475 self.tr("Side-by-Side Diff to Parent {0}").format(
1391 " ".join(parentLinks))) 1476 " ".join(parentLinks)
1477 )
1478 )
1392 elif len(selectedItems) == 2: 1479 elif len(selectedItems) == 2:
1393 rev1 = int(selectedItems[0].text(self.RevisionColumn) 1480 rev1 = int(selectedItems[0].text(self.RevisionColumn).split(":", 1)[0])
1394 .split(":", 1)[0]) 1481 rev2 = int(selectedItems[1].text(self.RevisionColumn).split(":", 1)[0])
1395 rev2 = int(selectedItems[1].text(self.RevisionColumn)
1396 .split(":", 1)[0])
1397 if rev1 > rev2: 1482 if rev1 > rev2:
1398 # Swap the entries, so that rev1 < rev2 1483 # Swap the entries, so that rev1 < rev2
1399 rev1, rev2 = rev2, rev1 1484 rev1, rev2 = rev2, rev1
1400 self.sbsSelectLabel.setText(self.tr( 1485 self.sbsSelectLabel.setText(
1401 '<a href="sbsdiff:{0}_{1}">Side-by-Side Compare</a>') 1486 self.tr(
1402 .format(rev1, rev2)) 1487 '<a href="sbsdiff:{0}_{1}">Side-by-Side Compare</a>'
1403 1488 ).format(rev1, rev2)
1489 )
1490
1404 def __updateToolMenuActions(self): 1491 def __updateToolMenuActions(self):
1405 """ 1492 """
1406 Private slot to update the status of the tool menu actions and 1493 Private slot to update the status of the tool menu actions and
1407 the tool menu button. 1494 the tool menu button.
1408 """ 1495 """
1410 # do the phase action 1497 # do the phase action
1411 # step 1: count entries with changeable phases 1498 # step 1: count entries with changeable phases
1412 secret = 0 1499 secret = 0
1413 draft = 0 1500 draft = 0
1414 public = 0 1501 public = 0
1415 for itm in [item for item in self.logTree.selectedItems() 1502 for itm in [
1416 if not item.data(0, self.__incomingRole)]: 1503 item
1504 for item in self.logTree.selectedItems()
1505 if not item.data(0, self.__incomingRole)
1506 ]:
1417 # count phase for local items only 1507 # count phase for local items only
1418 phase = itm.text(self.PhaseColumn) 1508 phase = itm.text(self.PhaseColumn)
1419 if phase == self.phases["draft"]: 1509 if phase == self.phases["draft"]:
1420 draft += 1 1510 draft += 1
1421 elif phase == self.phases["secret"]: 1511 elif phase == self.phases["secret"]:
1422 secret += 1 1512 secret += 1
1423 else: 1513 else:
1424 public += 1 1514 public += 1
1425 1515
1426 # step 2: set the status of the phase action 1516 # step 2: set the status of the phase action
1427 if ( 1517 if public == 0 and (
1428 public == 0 and 1518 (secret > 0 and draft == 0) or (secret == 0 and draft > 0)
1429 ((secret > 0 and draft == 0) or
1430 (secret == 0 and draft > 0))
1431 ): 1519 ):
1432 self.__phaseAct.setEnabled(True) 1520 self.__phaseAct.setEnabled(True)
1433 else: 1521 else:
1434 self.__phaseAct.setEnabled(False) 1522 self.__phaseAct.setEnabled(False)
1435 1523
1436 # do the graft action 1524 # do the graft action
1437 # step 1: count selected entries not belonging to the 1525 # step 1: count selected entries not belonging to the
1438 # current branch 1526 # current branch
1439 otherBranches = 0 1527 otherBranches = 0
1440 for itm in [item for item in self.logTree.selectedItems() 1528 for itm in [
1441 if not item.data(0, self.__incomingRole)]: 1529 item
1530 for item in self.logTree.selectedItems()
1531 if not item.data(0, self.__incomingRole)
1532 ]:
1442 # for local items only 1533 # for local items only
1443 branch = itm.text(self.BranchColumn) 1534 branch = itm.text(self.BranchColumn)
1444 if branch != self.__projectBranch: 1535 if branch != self.__projectBranch:
1445 otherBranches += 1 1536 otherBranches += 1
1446 1537
1447 # step 2: set the status of the graft action 1538 # step 2: set the status of the graft action
1448 self.__graftAct.setEnabled(otherBranches > 0) 1539 self.__graftAct.setEnabled(otherBranches > 0)
1449 1540
1450 selectedItemsCount = len([ 1541 selectedItemsCount = len(
1451 itm for itm in self.logTree.selectedItems() 1542 [
1452 if not itm.data(0, self.__incomingRole) 1543 itm
1453 ]) 1544 for itm in self.logTree.selectedItems()
1454 selectedIncomingItemsCount = len([ 1545 if not itm.data(0, self.__incomingRole)
1455 itm for itm in self.logTree.selectedItems() 1546 ]
1456 if itm.data(0, self.__incomingRole) 1547 )
1457 ]) 1548 selectedIncomingItemsCount = len(
1458 1549 [
1550 itm
1551 for itm in self.logTree.selectedItems()
1552 if itm.data(0, self.__incomingRole)
1553 ]
1554 )
1555
1459 self.__mergeAct.setEnabled(selectedItemsCount == 1) 1556 self.__mergeAct.setEnabled(selectedItemsCount == 1)
1460 self.__tagAct.setEnabled(selectedItemsCount == 1) 1557 self.__tagAct.setEnabled(selectedItemsCount == 1)
1461 self.__switchAct.setEnabled(selectedItemsCount == 1) 1558 self.__switchAct.setEnabled(selectedItemsCount == 1)
1462 self.__bookmarkAct.setEnabled(selectedItemsCount == 1) 1559 self.__bookmarkAct.setEnabled(selectedItemsCount == 1)
1463 self.__bookmarkMoveAct.setEnabled(selectedItemsCount == 1) 1560 self.__bookmarkMoveAct.setEnabled(selectedItemsCount == 1)
1464 1561
1465 if selectedIncomingItemsCount > 0: 1562 if selectedIncomingItemsCount > 0:
1466 self.__pullAct.setText(self.tr("Pull Selected Changes")) 1563 self.__pullAct.setText(self.tr("Pull Selected Changes"))
1467 else: 1564 else:
1468 self.__pullAct.setText(self.tr("Pull Changes")) 1565 self.__pullAct.setText(self.tr("Pull Changes"))
1469 if self.vcs.canPull(): 1566 if self.vcs.canPull():
1470 self.__pullAct.setEnabled(True) 1567 self.__pullAct.setEnabled(True)
1471 self.__lfPullAct.setEnabled( 1568 self.__lfPullAct.setEnabled(
1472 self.vcs.isExtensionActive("largefiles") and 1569 self.vcs.isExtensionActive("largefiles") and selectedItemsCount > 0
1473 selectedItemsCount > 0) 1570 )
1474 else: 1571 else:
1475 self.__pullAct.setEnabled(False) 1572 self.__pullAct.setEnabled(False)
1476 self.__lfPullAct.setEnabled(False) 1573 self.__lfPullAct.setEnabled(False)
1477 1574
1478 if self.vcs.canPush(): 1575 if self.vcs.canPush():
1479 self.__pushAct.setEnabled( 1576 self.__pushAct.setEnabled(
1480 selectedItemsCount == 1 and 1577 selectedItemsCount == 1
1481 not self.logTree.selectedItems()[0].data( 1578 and not self.logTree.selectedItems()[0].data(0, self.__incomingRole)
1482 0, self.__incomingRole) and 1579 and self.logTree.selectedItems()[0].text(self.PhaseColumn)
1483 self.logTree.selectedItems()[0].text(self.PhaseColumn) == 1580 == self.phases["draft"]
1484 self.phases["draft"]) 1581 )
1485 self.__pushAllAct.setEnabled(True) 1582 self.__pushAllAct.setEnabled(True)
1486 else: 1583 else:
1487 self.__pushAct.setEnabled(False) 1584 self.__pushAct.setEnabled(False)
1488 self.__pushAllAct.setEnabled(False) 1585 self.__pushAllAct.setEnabled(False)
1489 1586
1490 self.__stripAct.setEnabled( 1587 self.__stripAct.setEnabled(
1491 self.vcs.isExtensionActive("strip") and 1588 self.vcs.isExtensionActive("strip") and selectedItemsCount == 1
1492 selectedItemsCount == 1) 1589 )
1493 1590
1494 # count incoming items for 'full_log' 1591 # count incoming items for 'full_log'
1495 if self.initialCommandMode == "full_log": 1592 if self.initialCommandMode == "full_log":
1496 # incoming items are at the top 1593 # incoming items are at the top
1497 incomingCount = 0 1594 incomingCount = 0
1498 for row in range(self.logTree.topLevelItemCount()): 1595 for row in range(self.logTree.topLevelItemCount()):
1499 if self.logTree.topLevelItem(row).data( 1596 if self.logTree.topLevelItem(row).data(0, self.__incomingRole):
1500 0, self.__incomingRole):
1501 incomingCount += 1 1597 incomingCount += 1
1502 else: 1598 else:
1503 break 1599 break
1504 localCount = self.logTree.topLevelItemCount() - incomingCount 1600 localCount = self.logTree.topLevelItemCount() - incomingCount
1505 else: 1601 else:
1506 localCount = self.logTree.topLevelItemCount() 1602 localCount = self.logTree.topLevelItemCount()
1507 self.__bundleAct.setEnabled(localCount > 0) 1603 self.__bundleAct.setEnabled(localCount > 0)
1508 self.__unbundleAct.setEnabled(False) 1604 self.__unbundleAct.setEnabled(False)
1509 1605
1510 self.__gpgSignAct.setEnabled( 1606 self.__gpgSignAct.setEnabled(
1511 self.vcs.isExtensionActive("gpg") and 1607 self.vcs.isExtensionActive("gpg") and selectedItemsCount > 0
1512 selectedItemsCount > 0) 1608 )
1513 self.__gpgVerifyAct.setEnabled( 1609 self.__gpgVerifyAct.setEnabled(
1514 self.vcs.isExtensionActive("gpg") and 1610 self.vcs.isExtensionActive("gpg") and selectedItemsCount == 1
1515 selectedItemsCount == 1) 1611 )
1516 1612
1517 if self.vcs.isExtensionActive("closehead"): 1613 if self.vcs.isExtensionActive("closehead"):
1518 revs = [itm.text(self.RevisionColumn).strip().split(":", 1)[0] 1614 revs = [
1519 for itm in self.logTree.selectedItems() 1615 itm.text(self.RevisionColumn).strip().split(":", 1)[0]
1520 if not itm.data(0, self.__incomingRole)] 1616 for itm in self.logTree.selectedItems()
1617 if not itm.data(0, self.__incomingRole)
1618 ]
1521 revs = [rev for rev in revs if rev in self.__headRevisions] 1619 revs = [rev for rev in revs if rev in self.__headRevisions]
1522 self.__closeHeadsAct.setEnabled(len(revs) > 0) 1620 self.__closeHeadsAct.setEnabled(len(revs) > 0)
1523 else: 1621 else:
1524 self.__closeHeadsAct.setEnabled(False) 1622 self.__closeHeadsAct.setEnabled(False)
1525 self.actionsButton.setEnabled(True) 1623 self.actionsButton.setEnabled(True)
1526 1624
1527 elif self.initialCommandMode == "incoming" and self.projectMode: 1625 elif self.initialCommandMode == "incoming" and self.projectMode:
1528 for act in [self.__phaseAct, self.__graftAct, self.__mergeAct, 1626 for act in [
1529 self.__tagAct, self.__closeHeadsAct, self.__switchAct, 1627 self.__phaseAct,
1530 self.__bookmarkAct, self.__bookmarkMoveAct, 1628 self.__graftAct,
1531 self.__pushAct, self.__pushAllAct, self.__stripAct, 1629 self.__mergeAct,
1532 self.__bundleAct, self.__gpgSignAct, 1630 self.__tagAct,
1533 self.__gpgVerifyAct]: 1631 self.__closeHeadsAct,
1632 self.__switchAct,
1633 self.__bookmarkAct,
1634 self.__bookmarkMoveAct,
1635 self.__pushAct,
1636 self.__pushAllAct,
1637 self.__stripAct,
1638 self.__bundleAct,
1639 self.__gpgSignAct,
1640 self.__gpgVerifyAct,
1641 ]:
1534 act.setEnabled(False) 1642 act.setEnabled(False)
1535 1643
1536 self.__pullAct.setText(self.tr("Pull Selected Changes")) 1644 self.__pullAct.setText(self.tr("Pull Selected Changes"))
1537 if self.vcs.canPull() and not bool(self.__bundle): 1645 if self.vcs.canPull() and not bool(self.__bundle):
1538 selectedIncomingItemsCount = len([ 1646 selectedIncomingItemsCount = len(
1539 itm for itm in self.logTree.selectedItems() 1647 [
1540 if itm.data(0, self.__incomingRole) 1648 itm
1541 ]) 1649 for itm in self.logTree.selectedItems()
1650 if itm.data(0, self.__incomingRole)
1651 ]
1652 )
1542 self.__pullAct.setEnabled(selectedIncomingItemsCount > 0) 1653 self.__pullAct.setEnabled(selectedIncomingItemsCount > 0)
1543 self.__lfPullAct.setEnabled( 1654 self.__lfPullAct.setEnabled(
1544 self.vcs.isExtensionActive("largefiles") and 1655 self.vcs.isExtensionActive("largefiles")
1545 selectedIncomingItemsCount > 0) 1656 and selectedIncomingItemsCount > 0
1657 )
1546 else: 1658 else:
1547 self.__pullAct.setEnabled(False) 1659 self.__pullAct.setEnabled(False)
1548 self.__lfPullAct.setEnabled(False) 1660 self.__lfPullAct.setEnabled(False)
1549 1661
1550 self.__unbundleAct.setEnabled(bool(self.__bundle)) 1662 self.__unbundleAct.setEnabled(bool(self.__bundle))
1551 1663
1552 self.actionsButton.setEnabled(True) 1664 self.actionsButton.setEnabled(True)
1553 1665
1554 elif self.initialCommandMode == "outgoing" and self.projectMode: 1666 elif self.initialCommandMode == "outgoing" and self.projectMode:
1555 for act in [self.__phaseAct, self.__graftAct, self.__mergeAct, 1667 for act in [
1556 self.__tagAct, self.__closeHeadsAct, self.__switchAct, 1668 self.__phaseAct,
1557 self.__bookmarkAct, self.__bookmarkMoveAct, 1669 self.__graftAct,
1558 self.__pullAct, self.__lfPullAct, 1670 self.__mergeAct,
1559 self.__stripAct, self.__gpgSignAct, 1671 self.__tagAct,
1560 self.__gpgVerifyAct, self.__unbundleAct]: 1672 self.__closeHeadsAct,
1673 self.__switchAct,
1674 self.__bookmarkAct,
1675 self.__bookmarkMoveAct,
1676 self.__pullAct,
1677 self.__lfPullAct,
1678 self.__stripAct,
1679 self.__gpgSignAct,
1680 self.__gpgVerifyAct,
1681 self.__unbundleAct,
1682 ]:
1561 act.setEnabled(False) 1683 act.setEnabled(False)
1562 1684
1563 selectedItemsCount = len(self.logTree.selectedItems()) 1685 selectedItemsCount = len(self.logTree.selectedItems())
1564 if self.vcs.canPush(): 1686 if self.vcs.canPush():
1565 self.__pushAct.setEnabled( 1687 self.__pushAct.setEnabled(
1566 selectedItemsCount == 1 and 1688 selectedItemsCount == 1
1567 self.logTree.selectedItems()[0].text(self.PhaseColumn) == 1689 and self.logTree.selectedItems()[0].text(self.PhaseColumn)
1568 self.phases["draft"]) 1690 == self.phases["draft"]
1691 )
1569 self.__pushAllAct.setEnabled(True) 1692 self.__pushAllAct.setEnabled(True)
1570 else: 1693 else:
1571 self.__pushAct.setEnabled(False) 1694 self.__pushAct.setEnabled(False)
1572 self.__pushAllAct.setEnabled(False) 1695 self.__pushAllAct.setEnabled(False)
1573 1696
1574 self.__bundleAct.setEnabled(selectedItemsCount > 0) 1697 self.__bundleAct.setEnabled(selectedItemsCount > 0)
1575 1698
1576 else: 1699 else:
1577 self.actionsButton.setEnabled(False) 1700 self.actionsButton.setEnabled(False)
1578 1701
1579 def __updateDetailsAndFiles(self): 1702 def __updateDetailsAndFiles(self):
1580 """ 1703 """
1581 Private slot to update the details and file changes panes. 1704 Private slot to update the details and file changes panes.
1582 """ 1705 """
1583 self.detailsEdit.clear() 1706 self.detailsEdit.clear()
1584 self.filesTree.clear() 1707 self.filesTree.clear()
1585 self.__diffUpdatesFiles = False 1708 self.__diffUpdatesFiles = False
1586 1709
1587 selectedItems = self.logTree.selectedItems() 1710 selectedItems = self.logTree.selectedItems()
1588 if len(selectedItems) == 1: 1711 if len(selectedItems) == 1:
1589 self.detailsEdit.setHtml( 1712 self.detailsEdit.setHtml(self.__generateDetailsTableText(selectedItems[0]))
1590 self.__generateDetailsTableText(selectedItems[0]))
1591 self.__updateFilesTree(self.filesTree, selectedItems[0]) 1713 self.__updateFilesTree(self.filesTree, selectedItems[0])
1592 self.__resizeColumnsFiles() 1714 self.__resizeColumnsFiles()
1593 self.__resortFiles() 1715 self.__resortFiles()
1594 elif len(selectedItems) == 2: 1716 elif len(selectedItems) == 2:
1595 self.__diffUpdatesFiles = True 1717 self.__diffUpdatesFiles = True
1596 index1 = self.logTree.indexOfTopLevelItem(selectedItems[0]) 1718 index1 = self.logTree.indexOfTopLevelItem(selectedItems[0])
1597 index2 = self.logTree.indexOfTopLevelItem(selectedItems[1]) 1719 index2 = self.logTree.indexOfTopLevelItem(selectedItems[1])
1598 if index1 > index2: 1720 if index1 > index2:
1599 # Swap the entries 1721 # Swap the entries
1600 selectedItems[0], selectedItems[1] = ( 1722 selectedItems[0], selectedItems[1] = (
1601 selectedItems[1], selectedItems[0] 1723 selectedItems[1],
1724 selectedItems[0],
1602 ) 1725 )
1603 html = "{0}<hr/>{1}".format( 1726 html = "{0}<hr/>{1}".format(
1604 self.__generateDetailsTableText(selectedItems[0]), 1727 self.__generateDetailsTableText(selectedItems[0]),
1605 self.__generateDetailsTableText(selectedItems[1]), 1728 self.__generateDetailsTableText(selectedItems[1]),
1606 ) 1729 )
1607 self.detailsEdit.setHtml(html) 1730 self.detailsEdit.setHtml(html)
1608 # self.filesTree is updated by the diff 1731 # self.filesTree is updated by the diff
1609 1732
1610 def __generateDetailsTableText(self, itm): 1733 def __generateDetailsTableText(self, itm):
1611 """ 1734 """
1612 Private method to generate an HTML table with the details of the given 1735 Private method to generate an HTML table with the details of the given
1613 changeset. 1736 changeset.
1614 1737
1615 @param itm reference to the item the table should be based on 1738 @param itm reference to the item the table should be based on
1616 @type QTreeWidgetItem 1739 @type QTreeWidgetItem
1617 @return HTML table containing details 1740 @return HTML table containing details
1618 @rtype str 1741 @rtype str
1619 """ 1742 """
1620 if itm is not None: 1743 if itm is not None:
1621 if itm.text(self.TagsColumn): 1744 if itm.text(self.TagsColumn):
1622 tagsStr = self.__tagsTemplate.format(itm.text(self.TagsColumn)) 1745 tagsStr = self.__tagsTemplate.format(itm.text(self.TagsColumn))
1623 else: 1746 else:
1624 tagsStr = "" 1747 tagsStr = ""
1625 1748
1626 if itm.text(self.BookmarksColumn): 1749 if itm.text(self.BookmarksColumn):
1627 bookmarksStr = self.__bookmarksTemplate.format( 1750 bookmarksStr = self.__bookmarksTemplate.format(
1628 itm.text(self.BookmarksColumn)) 1751 itm.text(self.BookmarksColumn)
1752 )
1629 else: 1753 else:
1630 bookmarksStr = "" 1754 bookmarksStr = ""
1631 1755
1632 if self.projectMode and itm.data(0, self.__latestTagRole): 1756 if self.projectMode and itm.data(0, self.__latestTagRole):
1633 latestTagLinks = [] 1757 latestTagLinks = []
1634 for tag in itm.data(0, self.__latestTagRole): 1758 for tag in itm.data(0, self.__latestTagRole):
1635 latestTagLinks.append('<a href="rev:{0}">{1}</a>'.format( 1759 latestTagLinks.append(
1636 self.__getRevisionOfTag(tag)[0], tag)) 1760 '<a href="rev:{0}">{1}</a>'.format(
1761 self.__getRevisionOfTag(tag)[0], tag
1762 )
1763 )
1637 latestTagStr = self.__latestTagTemplate.format( 1764 latestTagStr = self.__latestTagTemplate.format(
1638 ", ".join(latestTagLinks)) 1765 ", ".join(latestTagLinks)
1766 )
1639 else: 1767 else:
1640 latestTagStr = "" 1768 latestTagStr = ""
1641 1769
1642 rev = int(itm.text(self.RevisionColumn).split(":", 1)[0]) 1770 rev = int(itm.text(self.RevisionColumn).split(":", 1)[0])
1643 1771
1644 if itm.data(0, self.__parentsRole): 1772 if itm.data(0, self.__parentsRole):
1645 parentLinks = [] 1773 parentLinks = []
1646 for parent in [str(x) for x in 1774 for parent in [str(x) for x in itm.data(0, self.__parentsRole)]:
1647 itm.data(0, self.__parentsRole)]: 1775 parentLinks.append('<a href="rev:{0}">{0}</a>'.format(parent))
1648 parentLinks.append( 1776 parentsStr = self.__parentsTemplate.format(", ".join(parentLinks))
1649 '<a href="rev:{0}">{0}</a>'.format(parent))
1650 parentsStr = self.__parentsTemplate.format(
1651 ", ".join(parentLinks))
1652 else: 1777 else:
1653 parentsStr = "" 1778 parentsStr = ""
1654 1779
1655 if self.__childrenInfo[rev]: 1780 if self.__childrenInfo[rev]:
1656 childLinks = [] 1781 childLinks = []
1657 for child in [str(x) for x in self.__childrenInfo[rev]]: 1782 for child in [str(x) for x in self.__childrenInfo[rev]]:
1658 childLinks.append( 1783 childLinks.append('<a href="rev:{0}">{0}</a>'.format(child))
1659 '<a href="rev:{0}">{0}</a>'.format(child)) 1784 childrenStr = self.__childrenTemplate.format(", ".join(childLinks))
1660 childrenStr = self.__childrenTemplate.format(
1661 ", ".join(childLinks))
1662 else: 1785 else:
1663 childrenStr = "" 1786 childrenStr = ""
1664 1787
1665 messagesList = [] 1788 messagesList = []
1666 for line in itm.data(0, self.__messageRole): 1789 for line in itm.data(0, self.__messageRole):
1667 match = HgLogBrowserDialog.GraftedRe.fullmatch(line) 1790 match = HgLogBrowserDialog.GraftedRe.fullmatch(line)
1668 if match: 1791 if match:
1669 messagesList.append( 1792 messagesList.append(
1670 HgLogBrowserDialog.GraftedTemplate.format( 1793 HgLogBrowserDialog.GraftedTemplate.format(match.group(1))
1671 match.group(1))) 1794 )
1672 else: 1795 else:
1673 messagesList.append(Utilities.html_encode(line.strip())) 1796 messagesList.append(Utilities.html_encode(line.strip()))
1674 messageStr = "<br />\n".join(messagesList) 1797 messageStr = "<br />\n".join(messagesList)
1675 1798
1676 html = self.__detailsTemplate.format( 1799 html = self.__detailsTemplate.format(
1677 itm.text(self.RevisionColumn), 1800 itm.text(self.RevisionColumn),
1678 itm.text(self.DateColumn), 1801 itm.text(self.DateColumn),
1679 itm.text(self.AuthorColumn), 1802 itm.text(self.AuthorColumn),
1680 itm.text(self.BranchColumn).replace( 1803 itm.text(self.BranchColumn).replace(self.ClosedIndicator, ""),
1681 self.ClosedIndicator, ""), 1804 parentsStr + childrenStr + tagsStr + latestTagStr + bookmarksStr,
1682 parentsStr + childrenStr + tagsStr + latestTagStr +
1683 bookmarksStr,
1684 messageStr, 1805 messageStr,
1685 ) 1806 )
1686 else: 1807 else:
1687 html = "" 1808 html = ""
1688 1809
1689 return html 1810 return html
1690 1811
1691 def __updateFilesTree(self, parent, itm): 1812 def __updateFilesTree(self, parent, itm):
1692 """ 1813 """
1693 Private method to update the files tree with changes of the given item. 1814 Private method to update the files tree with changes of the given item.
1694 1815
1695 @param parent parent for the items to be added 1816 @param parent parent for the items to be added
1696 @type QTreeWidget or QTreeWidgetItem 1817 @type QTreeWidget or QTreeWidgetItem
1697 @param itm reference to the item the update should be based on 1818 @param itm reference to the item the update should be based on
1698 @type QTreeWidgetItem 1819 @type QTreeWidgetItem
1699 """ 1820 """
1700 if itm is not None: 1821 if itm is not None:
1701 changes = itm.data(0, self.__changesRole) 1822 changes = itm.data(0, self.__changesRole)
1702 if len(changes) > 0: 1823 if len(changes) > 0:
1703 for change in changes: 1824 for change in changes:
1704 QTreeWidgetItem(parent, [ 1825 QTreeWidgetItem(
1705 self.flags[change["action"]], 1826 parent,
1706 change["path"].strip(), 1827 [
1707 change["copyfrom"].strip(), 1828 self.flags[change["action"]],
1708 ]) 1829 change["path"].strip(),
1709 1830 change["copyfrom"].strip(),
1831 ],
1832 )
1833
1710 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) 1834 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
1711 def on_logTree_currentItemChanged(self, current, previous): 1835 def on_logTree_currentItemChanged(self, current, previous):
1712 """ 1836 """
1713 Private slot called, when the current item of the log tree changes. 1837 Private slot called, when the current item of the log tree changes.
1714 1838
1715 @param current reference to the new current item (QTreeWidgetItem) 1839 @param current reference to the new current item (QTreeWidgetItem)
1716 @param previous reference to the old current item (QTreeWidgetItem) 1840 @param previous reference to the old current item (QTreeWidgetItem)
1717 """ 1841 """
1718 self.__updateToolMenuActions() 1842 self.__updateToolMenuActions()
1719 1843
1720 # Highlight the current entry using a bold font 1844 # Highlight the current entry using a bold font
1721 for col in range(self.logTree.columnCount()): 1845 for col in range(self.logTree.columnCount()):
1722 current and current.setFont(col, self.__logTreeBoldFont) 1846 current and current.setFont(col, self.__logTreeBoldFont)
1723 previous and previous.setFont(col, self.__logTreeNormalFont) 1847 previous and previous.setFont(col, self.__logTreeNormalFont)
1724 1848
1725 # set the state of the up and down buttons 1849 # set the state of the up and down buttons
1726 self.upButton.setEnabled( 1850 self.upButton.setEnabled(
1727 current is not None and 1851 current is not None and self.logTree.indexOfTopLevelItem(current) > 0
1728 self.logTree.indexOfTopLevelItem(current) > 0) 1852 )
1729 self.downButton.setEnabled( 1853 self.downButton.setEnabled(
1730 current is not None and 1854 current is not None
1731 int(current.text(self.RevisionColumn).split(":")[0]) > 0 and 1855 and int(current.text(self.RevisionColumn).split(":")[0]) > 0
1732 (self.logTree.indexOfTopLevelItem(current) < 1856 and (
1733 self.logTree.topLevelItemCount() - 1 or 1857 self.logTree.indexOfTopLevelItem(current)
1734 self.nextButton.isEnabled())) 1858 < self.logTree.topLevelItemCount() - 1
1735 1859 or self.nextButton.isEnabled()
1860 )
1861 )
1862
1736 @pyqtSlot() 1863 @pyqtSlot()
1737 def on_logTree_itemSelectionChanged(self): 1864 def on_logTree_itemSelectionChanged(self):
1738 """ 1865 """
1739 Private slot called, when the selection has changed. 1866 Private slot called, when the selection has changed.
1740 """ 1867 """
1741 self.__updateDetailsAndFiles() 1868 self.__updateDetailsAndFiles()
1742 self.__updateSbsSelectLabel() 1869 self.__updateSbsSelectLabel()
1743 self.__updateToolMenuActions() 1870 self.__updateToolMenuActions()
1744 self.__generateDiffs() 1871 self.__generateDiffs()
1745 1872
1746 @pyqtSlot() 1873 @pyqtSlot()
1747 def on_upButton_clicked(self): 1874 def on_upButton_clicked(self):
1748 """ 1875 """
1749 Private slot to move the current item up one entry. 1876 Private slot to move the current item up one entry.
1750 """ 1877 """
1751 itm = self.logTree.itemAbove(self.logTree.currentItem()) 1878 itm = self.logTree.itemAbove(self.logTree.currentItem())
1752 if itm: 1879 if itm:
1753 self.logTree.setCurrentItem(itm) 1880 self.logTree.setCurrentItem(itm)
1754 1881
1755 @pyqtSlot() 1882 @pyqtSlot()
1756 def on_downButton_clicked(self): 1883 def on_downButton_clicked(self):
1757 """ 1884 """
1758 Private slot to move the current item down one entry. 1885 Private slot to move the current item down one entry.
1759 """ 1886 """
1763 else: 1890 else:
1764 # load the next bunch and try again 1891 # load the next bunch and try again
1765 if self.nextButton.isEnabled(): 1892 if self.nextButton.isEnabled():
1766 self.__addFinishCallback(self.on_downButton_clicked) 1893 self.__addFinishCallback(self.on_downButton_clicked)
1767 self.on_nextButton_clicked() 1894 self.on_nextButton_clicked()
1768 1895
1769 @pyqtSlot() 1896 @pyqtSlot()
1770 def on_nextButton_clicked(self): 1897 def on_nextButton_clicked(self):
1771 """ 1898 """
1772 Private slot to handle the Next button. 1899 Private slot to handle the Next button.
1773 """ 1900 """
1774 if self.nextButton.isEnabled(): 1901 if self.nextButton.isEnabled():
1775 if self.__lastRev > 0: 1902 if self.__lastRev > 0:
1776 self.__getLogEntries(startRev=self.__lastRev - 1) 1903 self.__getLogEntries(startRev=self.__lastRev - 1)
1777 else: 1904 else:
1778 self.__getLogEntries() 1905 self.__getLogEntries()
1779 1906
1780 @pyqtSlot(QDate) 1907 @pyqtSlot(QDate)
1781 def on_fromDate_dateChanged(self, date): 1908 def on_fromDate_dateChanged(self, date):
1782 """ 1909 """
1783 Private slot called, when the from date changes. 1910 Private slot called, when the from date changes.
1784 1911
1785 @param date new date (QDate) 1912 @param date new date (QDate)
1786 """ 1913 """
1787 if self.__actionMode() == "filter": 1914 if self.__actionMode() == "filter":
1788 self.__filterLogs() 1915 self.__filterLogs()
1789 1916
1790 @pyqtSlot(QDate) 1917 @pyqtSlot(QDate)
1791 def on_toDate_dateChanged(self, date): 1918 def on_toDate_dateChanged(self, date):
1792 """ 1919 """
1793 Private slot called, when the from date changes. 1920 Private slot called, when the from date changes.
1794 1921
1795 @param date new date (QDate) 1922 @param date new date (QDate)
1796 """ 1923 """
1797 if self.__actionMode() == "filter": 1924 if self.__actionMode() == "filter":
1798 self.__filterLogs() 1925 self.__filterLogs()
1799 1926
1800 @pyqtSlot(int) 1927 @pyqtSlot(int)
1801 def on_branchCombo_activated(self, index): 1928 def on_branchCombo_activated(self, index):
1802 """ 1929 """
1803 Private slot called, when a new branch is selected. 1930 Private slot called, when a new branch is selected.
1804 1931
1805 @param index index of the selected entry 1932 @param index index of the selected entry
1806 @type int 1933 @type int
1807 """ 1934 """
1808 if self.__actionMode() == "filter": 1935 if self.__actionMode() == "filter":
1809 self.__filterLogs() 1936 self.__filterLogs()
1810 1937
1811 @pyqtSlot(int) 1938 @pyqtSlot(int)
1812 def on_fieldCombo_activated(self, index): 1939 def on_fieldCombo_activated(self, index):
1813 """ 1940 """
1814 Private slot called, when a new filter field is selected. 1941 Private slot called, when a new filter field is selected.
1815 1942
1816 @param index index of the selected entry 1943 @param index index of the selected entry
1817 @type int 1944 @type int
1818 """ 1945 """
1819 if self.__actionMode() == "filter": 1946 if self.__actionMode() == "filter":
1820 self.__filterLogs() 1947 self.__filterLogs()
1821 1948
1822 @pyqtSlot(str) 1949 @pyqtSlot(str)
1823 def on_rxEdit_textChanged(self, txt): 1950 def on_rxEdit_textChanged(self, txt):
1824 """ 1951 """
1825 Private slot called, when a filter expression is entered. 1952 Private slot called, when a filter expression is entered.
1826 1953
1827 @param txt filter expression (string) 1954 @param txt filter expression (string)
1828 """ 1955 """
1829 if self.__actionMode() == "filter": 1956 if self.__actionMode() == "filter":
1830 self.__filterLogs() 1957 self.__filterLogs()
1831 elif self.__actionMode() == "find": 1958 elif self.__actionMode() == "find":
1832 self.__findItem(self.__findBackwards, interactive=True) 1959 self.__findItem(self.__findBackwards, interactive=True)
1833 1960
1834 @pyqtSlot() 1961 @pyqtSlot()
1835 def on_rxEdit_returnPressed(self): 1962 def on_rxEdit_returnPressed(self):
1836 """ 1963 """
1837 Private slot handling a press of the Return key in the rxEdit input. 1964 Private slot handling a press of the Return key in the rxEdit input.
1838 """ 1965 """
1839 if self.__actionMode() == "find": 1966 if self.__actionMode() == "find":
1840 self.__findItem(self.__findBackwards, interactive=True) 1967 self.__findItem(self.__findBackwards, interactive=True)
1841 1968
1842 def __filterLogs(self): 1969 def __filterLogs(self):
1843 """ 1970 """
1844 Private method to filter the log entries. 1971 Private method to filter the log entries.
1845 """ 1972 """
1846 if self.__filterLogsEnabled: 1973 if self.__filterLogsEnabled:
1847 from_ = self.fromDate.date().toString("yyyy-MM-dd") 1974 from_ = self.fromDate.date().toString("yyyy-MM-dd")
1848 to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") 1975 to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd")
1849 branch = self.branchCombo.currentText() 1976 branch = self.branchCombo.currentText()
1850 closedBranch = branch + '--' 1977 closedBranch = branch + "--"
1851 fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch() 1978 fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch()
1852 1979
1853 visibleItemCount = self.logTree.topLevelItemCount() 1980 visibleItemCount = self.logTree.topLevelItemCount()
1854 currentItem = self.logTree.currentItem() 1981 currentItem = self.logTree.currentItem()
1855 for topIndex in range(self.logTree.topLevelItemCount()): 1982 for topIndex in range(self.logTree.topLevelItemCount()):
1856 topItem = self.logTree.topLevelItem(topIndex) 1983 topItem = self.logTree.topLevelItem(topIndex)
1857 if indexIsRole: 1984 if indexIsRole:
1858 if fieldIndex == self.__changesRole: 1985 if fieldIndex == self.__changesRole:
1859 changes = topItem.data(0, self.__changesRole) 1986 changes = topItem.data(0, self.__changesRole)
1860 txt = "\n".join( 1987 txt = "\n".join(
1861 [c["path"] for c in changes] + 1988 [c["path"] for c in changes]
1862 [c["copyfrom"] for c in changes] 1989 + [c["copyfrom"] for c in changes]
1863 ) 1990 )
1864 else: 1991 else:
1865 # Find based on complete message text 1992 # Find based on complete message text
1866 txt = "\n".join(topItem.data(0, self.__messageRole)) 1993 txt = "\n".join(topItem.data(0, self.__messageRole))
1867 else: 1994 else:
1868 txt = topItem.text(fieldIndex) 1995 txt = topItem.text(fieldIndex)
1869 if ( 1996 if (
1870 topItem.text(self.DateColumn) <= to_ and 1997 topItem.text(self.DateColumn) <= to_
1871 topItem.text(self.DateColumn) >= from_ and 1998 and topItem.text(self.DateColumn) >= from_
1872 (branch == self.__allBranchesFilter or 1999 and (
1873 topItem.text(self.BranchColumn) in 2000 branch == self.__allBranchesFilter
1874 [branch, closedBranch]) and 2001 or topItem.text(self.BranchColumn) in [branch, closedBranch]
1875 searchRx.search(txt) is not None 2002 )
2003 and searchRx.search(txt) is not None
1876 ): 2004 ):
1877 topItem.setHidden(False) 2005 topItem.setHidden(False)
1878 if topItem is currentItem: 2006 if topItem is currentItem:
1879 self.on_logTree_currentItemChanged(topItem, None) 2007 self.on_logTree_currentItemChanged(topItem, None)
1880 else: 2008 else:
1881 topItem.setHidden(True) 2009 topItem.setHidden(True)
1882 if topItem is currentItem: 2010 if topItem is currentItem:
1883 self.filesTree.clear() 2011 self.filesTree.clear()
1884 visibleItemCount -= 1 2012 visibleItemCount -= 1
1885 self.logTree.header().setSectionHidden( 2013 self.logTree.header().setSectionHidden(
1886 self.IconColumn, 2014 self.IconColumn, visibleItemCount != self.logTree.topLevelItemCount()
1887 visibleItemCount != self.logTree.topLevelItemCount()) 2015 )
1888 2016
1889 def __prepareFieldSearch(self): 2017 def __prepareFieldSearch(self):
1890 """ 2018 """
1891 Private slot to prepare the filed search data. 2019 Private slot to prepare the filed search data.
1892 2020
1893 @return tuple of field index, search expression and flag indicating 2021 @return tuple of field index, search expression and flag indicating
1894 that the field index is a data role (integer, string, boolean) 2022 that the field index is a data role (integer, string, boolean)
1895 """ 2023 """
1896 indexIsRole = False 2024 indexIsRole = False
1897 txt = self.fieldCombo.itemData(self.fieldCombo.currentIndex()) 2025 txt = self.fieldCombo.itemData(self.fieldCombo.currentIndex())
1900 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE) 2028 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE)
1901 elif txt == "revision": 2029 elif txt == "revision":
1902 fieldIndex = self.RevisionColumn 2030 fieldIndex = self.RevisionColumn
1903 txt = self.rxEdit.text() 2031 txt = self.rxEdit.text()
1904 if txt.startswith("^"): 2032 if txt.startswith("^"):
1905 searchRx = re.compile(r"^\s*{0}".format(txt[1:]), 2033 searchRx = re.compile(r"^\s*{0}".format(txt[1:]), re.IGNORECASE)
1906 re.IGNORECASE)
1907 else: 2034 else:
1908 searchRx = re.compile(txt, re.IGNORECASE) 2035 searchRx = re.compile(txt, re.IGNORECASE)
1909 elif txt == "file": 2036 elif txt == "file":
1910 fieldIndex = self.__changesRole 2037 fieldIndex = self.__changesRole
1911 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE) 2038 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE)
1915 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE) 2042 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE)
1916 else: 2043 else:
1917 fieldIndex = self.__messageRole 2044 fieldIndex = self.__messageRole
1918 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE) 2045 searchRx = re.compile(self.rxEdit.text(), re.IGNORECASE)
1919 indexIsRole = True 2046 indexIsRole = True
1920 2047
1921 return fieldIndex, searchRx, indexIsRole 2048 return fieldIndex, searchRx, indexIsRole
1922 2049
1923 @pyqtSlot(bool) 2050 @pyqtSlot(bool)
1924 def on_stopCheckBox_clicked(self, checked): 2051 def on_stopCheckBox_clicked(self, checked):
1925 """ 2052 """
1926 Private slot called, when the stop on copy/move checkbox is clicked. 2053 Private slot called, when the stop on copy/move checkbox is clicked.
1927 2054
1928 @param checked flag indicating the state of the check box (boolean) 2055 @param checked flag indicating the state of the check box (boolean)
1929 """ 2056 """
1930 self.vcs.getPlugin().setPreferences("StopLogOnCopy", 2057 self.vcs.getPlugin().setPreferences(
1931 self.stopCheckBox.isChecked()) 2058 "StopLogOnCopy", self.stopCheckBox.isChecked()
2059 )
1932 self.nextButton.setEnabled(True) 2060 self.nextButton.setEnabled(True)
1933 self.limitSpinBox.setEnabled(True) 2061 self.limitSpinBox.setEnabled(True)
1934 2062
1935 @pyqtSlot() 2063 @pyqtSlot()
1936 def on_refreshButton_clicked(self, addNext=False): 2064 def on_refreshButton_clicked(self, addNext=False):
1937 """ 2065 """
1938 Private slot to refresh the log. 2066 Private slot to refresh the log.
1939 2067
1940 @param addNext flag indicating to get a second batch of log entries as 2068 @param addNext flag indicating to get a second batch of log entries as
1941 well 2069 well
1942 @type bool 2070 @type bool
1943 """ 2071 """
1944 self.buttonBox.button( 2072 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
1945 QDialogButtonBox.StandardButton.Close).setEnabled(False) 2073 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
1946 self.buttonBox.button( 2074 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
1947 QDialogButtonBox.StandardButton.Cancel).setEnabled(True) 2075
1948 self.buttonBox.button(
1949 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
1950
1951 self.refreshButton.setEnabled(False) 2076 self.refreshButton.setEnabled(False)
1952 2077
1953 # save the selected items commit IDs 2078 # save the selected items commit IDs
1954 self.__selectedRevisions = [] 2079 self.__selectedRevisions = []
1955 for item in self.logTree.selectedItems(): 2080 for item in self.logTree.selectedItems():
1956 self.__selectedRevisions.append(item.text(self.RevisionColumn)) 2081 self.__selectedRevisions.append(item.text(self.RevisionColumn))
1957 2082
1958 if self.initialCommandMode in ("incoming", "outgoing"): 2083 if self.initialCommandMode in ("incoming", "outgoing"):
1959 self.nextButton.setEnabled(False) 2084 self.nextButton.setEnabled(False)
1960 self.limitSpinBox.setEnabled(False) 2085 self.limitSpinBox.setEnabled(False)
1961 if addNext: 2086 if addNext:
1962 self.__addFinishCallback(self.on_nextButton_clicked) 2087 self.__addFinishCallback(self.on_nextButton_clicked)
1963 else: 2088 else:
1964 self.nextButton.setEnabled(True) 2089 self.nextButton.setEnabled(True)
1965 self.limitSpinBox.setEnabled(True) 2090 self.limitSpinBox.setEnabled(True)
1966 2091
1967 if self.initialCommandMode == "full_log": 2092 if self.initialCommandMode == "full_log":
1968 self.commandMode = "incoming" 2093 self.commandMode = "incoming"
1969 self.__addFinishCallback(self.on_nextButton_clicked) 2094 self.__addFinishCallback(self.on_nextButton_clicked)
1970 else: 2095 else:
1971 self.commandMode = self.initialCommandMode 2096 self.commandMode = self.initialCommandMode
1972 self.start(self.__filename, bundle=self.__bundle, isFile=self.__isFile, 2097 self.start(
1973 noEntries=self.logTree.topLevelItemCount()) 2098 self.__filename,
1974 2099 bundle=self.__bundle,
2100 isFile=self.__isFile,
2101 noEntries=self.logTree.topLevelItemCount(),
2102 )
2103
1975 @pyqtSlot() 2104 @pyqtSlot()
1976 def __phaseActTriggered(self): 2105 def __phaseActTriggered(self):
1977 """ 2106 """
1978 Private slot to handle the Change Phase action. 2107 Private slot to handle the Change Phase action.
1979 """ 2108 """
1981 if not itm.data(0, self.__incomingRole): 2110 if not itm.data(0, self.__incomingRole):
1982 currentPhase = itm.text(self.PhaseColumn) 2111 currentPhase = itm.text(self.PhaseColumn)
1983 revs = [] 2112 revs = []
1984 for itm in self.logTree.selectedItems(): 2113 for itm in self.logTree.selectedItems():
1985 if itm.text(self.PhaseColumn) == currentPhase: 2114 if itm.text(self.PhaseColumn) == currentPhase:
1986 revs.append( 2115 revs.append(itm.text(self.RevisionColumn).split(":")[0].strip())
1987 itm.text(self.RevisionColumn).split(":")[0].strip()) 2116
1988
1989 if not revs: 2117 if not revs:
1990 self.__phaseAct.setEnabled(False) 2118 self.__phaseAct.setEnabled(False)
1991 return 2119 return
1992 2120
1993 if currentPhase == self.phases["draft"]: 2121 if currentPhase == self.phases["draft"]:
1994 newPhase = self.phases["secret"] 2122 newPhase = self.phases["secret"]
1995 data = (revs, "s", True) 2123 data = (revs, "s", True)
1996 else: 2124 else:
1997 newPhase = self.phases["draft"] 2125 newPhase = self.phases["draft"]
1998 data = (revs, "d", False) 2126 data = (revs, "d", False)
1999 res = self.vcs.hgPhase(data) 2127 res = self.vcs.hgPhase(data)
2000 if res: 2128 if res:
2001 for itm in self.logTree.selectedItems(): 2129 for itm in self.logTree.selectedItems():
2002 itm.setText(self.PhaseColumn, newPhase) 2130 itm.setText(self.PhaseColumn, newPhase)
2003 2131
2004 @pyqtSlot() 2132 @pyqtSlot()
2005 def __graftActTriggered(self): 2133 def __graftActTriggered(self):
2006 """ 2134 """
2007 Private slot to handle the Copy Changesets action. 2135 Private slot to handle the Copy Changesets action.
2008 """ 2136 """
2009 revs = [] 2137 revs = []
2010 2138
2011 for itm in [item for item in self.logTree.selectedItems() 2139 for itm in [
2012 if not item.data(0, self.__incomingRole)]: 2140 item
2141 for item in self.logTree.selectedItems()
2142 if not item.data(0, self.__incomingRole)
2143 ]:
2013 branch = itm.text(self.BranchColumn) 2144 branch = itm.text(self.BranchColumn)
2014 if branch != self.__projectBranch: 2145 if branch != self.__projectBranch:
2015 revs.append( 2146 revs.append(itm.text(self.RevisionColumn).strip().split(":", 1)[0])
2016 itm.text(self.RevisionColumn).strip().split(":", 1)[0]) 2147
2017
2018 if revs: 2148 if revs:
2019 shouldReopen = self.vcs.hgGraft(revs) 2149 shouldReopen = self.vcs.hgGraft(revs)
2020 if shouldReopen: 2150 if shouldReopen:
2021 res = EricMessageBox.yesNo( 2151 res = EricMessageBox.yesNo(
2022 None, 2152 None,
2023 self.tr("Copy Changesets"), 2153 self.tr("Copy Changesets"),
2024 self.tr( 2154 self.tr("""The project should be reread. Do this now?"""),
2025 """The project should be reread. Do this now?"""), 2155 yesDefault=True,
2026 yesDefault=True) 2156 )
2027 if res: 2157 if res:
2028 ericApp().getObject("Project").reopenProject() 2158 ericApp().getObject("Project").reopenProject()
2029 return 2159 return
2030 2160
2031 self.on_refreshButton_clicked() 2161 self.on_refreshButton_clicked()
2032 2162
2033 @pyqtSlot() 2163 @pyqtSlot()
2034 def __tagActTriggered(self): 2164 def __tagActTriggered(self):
2035 """ 2165 """
2036 Private slot to tag the selected revision. 2166 Private slot to tag the selected revision.
2037 """ 2167 """
2038 if len([itm for itm in self.logTree.selectedItems() 2168 if (
2039 if not itm.data(0, self.__incomingRole)]) == 1: 2169 len(
2170 [
2171 itm
2172 for itm in self.logTree.selectedItems()
2173 if not itm.data(0, self.__incomingRole)
2174 ]
2175 )
2176 == 1
2177 ):
2040 itm = self.logTree.selectedItems()[0] 2178 itm = self.logTree.selectedItems()[0]
2041 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0] 2179 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0]
2042 tag = itm.text(self.TagsColumn).strip().split(", ", 1)[0] 2180 tag = itm.text(self.TagsColumn).strip().split(", ", 1)[0]
2043 res = self.vcs.vcsTag(revision=rev, tagName=tag) 2181 res = self.vcs.vcsTag(revision=rev, tagName=tag)
2044 if res: 2182 if res:
2045 self.on_refreshButton_clicked() 2183 self.on_refreshButton_clicked()
2046 2184
2047 @pyqtSlot() 2185 @pyqtSlot()
2048 def __closeHeadsActTriggered(self): 2186 def __closeHeadsActTriggered(self):
2049 """ 2187 """
2050 Private slot to close the selected head revisions. 2188 Private slot to close the selected head revisions.
2051 """ 2189 """
2052 if self.vcs.isExtensionActive("closehead"): 2190 if self.vcs.isExtensionActive("closehead"):
2053 revs = [itm.text(self.RevisionColumn).strip().split(":", 1)[0] 2191 revs = [
2054 for itm in self.logTree.selectedItems() 2192 itm.text(self.RevisionColumn).strip().split(":", 1)[0]
2055 if not itm.data(0, self.__incomingRole)] 2193 for itm in self.logTree.selectedItems()
2194 if not itm.data(0, self.__incomingRole)
2195 ]
2056 revs = [rev for rev in revs if rev in self.__headRevisions] 2196 revs = [rev for rev in revs if rev in self.__headRevisions]
2057 if revs: 2197 if revs:
2058 closeheadExtension = self.vcs.getExtensionObject("closehead") 2198 closeheadExtension = self.vcs.getExtensionObject("closehead")
2059 if closeheadExtension is not None: 2199 if closeheadExtension is not None:
2060 closeheadExtension.hgCloseheads(revisions=revs) 2200 closeheadExtension.hgCloseheads(revisions=revs)
2061 2201
2062 self.on_refreshButton_clicked() 2202 self.on_refreshButton_clicked()
2063 2203
2064 @pyqtSlot() 2204 @pyqtSlot()
2065 def __switchActTriggered(self): 2205 def __switchActTriggered(self):
2066 """ 2206 """
2067 Private slot to switch the working directory to the 2207 Private slot to switch the working directory to the
2068 selected revision. 2208 selected revision.
2069 """ 2209 """
2070 if len([itm for itm in self.logTree.selectedItems() 2210 if (
2071 if not itm.data(0, self.__incomingRole)]) == 1: 2211 len(
2212 [
2213 itm
2214 for itm in self.logTree.selectedItems()
2215 if not itm.data(0, self.__incomingRole)
2216 ]
2217 )
2218 == 1
2219 ):
2072 itm = self.logTree.selectedItems()[0] 2220 itm = self.logTree.selectedItems()[0]
2073 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0] 2221 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0]
2074 bookmarks = [bm.strip() for bm in 2222 bookmarks = [
2075 itm.text(self.BookmarksColumn).strip().split(",") 2223 bm.strip()
2076 if bm.strip()] 2224 for bm in itm.text(self.BookmarksColumn).strip().split(",")
2225 if bm.strip()
2226 ]
2077 if bookmarks: 2227 if bookmarks:
2078 bookmark, ok = QInputDialog.getItem( 2228 bookmark, ok = QInputDialog.getItem(
2079 self, 2229 self,
2080 self.tr("Switch"), 2230 self.tr("Switch"),
2081 self.tr("Select bookmark to switch to (leave empty to" 2231 self.tr(
2082 " use revision):"), 2232 "Select bookmark to switch to (leave empty to" " use revision):"
2233 ),
2083 [""] + bookmarks, 2234 [""] + bookmarks,
2084 0, False) 2235 0,
2236 False,
2237 )
2085 if not ok: 2238 if not ok:
2086 return 2239 return
2087 if bookmark: 2240 if bookmark:
2088 rev = bookmark 2241 rev = bookmark
2089 if rev: 2242 if rev:
2090 shouldReopen = self.vcs.vcsUpdate(revision=rev) 2243 shouldReopen = self.vcs.vcsUpdate(revision=rev)
2091 if shouldReopen: 2244 if shouldReopen:
2092 res = EricMessageBox.yesNo( 2245 res = EricMessageBox.yesNo(
2093 None, 2246 None,
2094 self.tr("Switch"), 2247 self.tr("Switch"),
2095 self.tr( 2248 self.tr("""The project should be reread. Do this now?"""),
2096 """The project should be reread. Do this now?"""), 2249 yesDefault=True,
2097 yesDefault=True) 2250 )
2098 if res: 2251 if res:
2099 ericApp().getObject("Project").reopenProject() 2252 ericApp().getObject("Project").reopenProject()
2100 return 2253 return
2101 2254
2102 self.on_refreshButton_clicked() 2255 self.on_refreshButton_clicked()
2103 2256
2104 @pyqtSlot() 2257 @pyqtSlot()
2105 def __bookmarkActTriggered(self): 2258 def __bookmarkActTriggered(self):
2106 """ 2259 """
2107 Private slot to bookmark the selected revision. 2260 Private slot to bookmark the selected revision.
2108 """ 2261 """
2109 if len([itm for itm in self.logTree.selectedItems() 2262 if (
2110 if not itm.data(0, self.__incomingRole)]) == 1: 2263 len(
2264 [
2265 itm
2266 for itm in self.logTree.selectedItems()
2267 if not itm.data(0, self.__incomingRole)
2268 ]
2269 )
2270 == 1
2271 ):
2111 itm = self.logTree.selectedItems()[0] 2272 itm = self.logTree.selectedItems()[0]
2112 rev, changeset = ( 2273 rev, changeset = itm.text(self.RevisionColumn).strip().split(":", 1)
2113 itm.text(self.RevisionColumn).strip().split(":", 1)
2114 )
2115 bookmark, ok = QInputDialog.getText( 2274 bookmark, ok = QInputDialog.getText(
2116 self, 2275 self,
2117 self.tr("Define Bookmark"), 2276 self.tr("Define Bookmark"),
2118 self.tr('Enter bookmark name for changeset "{0}":').format( 2277 self.tr('Enter bookmark name for changeset "{0}":').format(changeset),
2119 changeset), 2278 QLineEdit.EchoMode.Normal,
2120 QLineEdit.EchoMode.Normal) 2279 )
2121 if ok and bool(bookmark): 2280 if ok and bool(bookmark):
2122 self.vcs.hgBookmarkDefine( 2281 self.vcs.hgBookmarkDefine(
2123 revision="rev({0})".format(rev), 2282 revision="rev({0})".format(rev), bookmark=bookmark
2124 bookmark=bookmark) 2283 )
2125 self.on_refreshButton_clicked() 2284 self.on_refreshButton_clicked()
2126 2285
2127 @pyqtSlot() 2286 @pyqtSlot()
2128 def __bookmarkMoveActTriggered(self): 2287 def __bookmarkMoveActTriggered(self):
2129 """ 2288 """
2130 Private slot to move a bookmark to the selected revision. 2289 Private slot to move a bookmark to the selected revision.
2131 """ 2290 """
2132 if len([itm for itm in self.logTree.selectedItems() 2291 if (
2133 if not itm.data(0, self.__incomingRole)]) == 1: 2292 len(
2293 [
2294 itm
2295 for itm in self.logTree.selectedItems()
2296 if not itm.data(0, self.__incomingRole)
2297 ]
2298 )
2299 == 1
2300 ):
2134 itm = self.logTree.selectedItems()[0] 2301 itm = self.logTree.selectedItems()[0]
2135 rev, changeset = ( 2302 rev, changeset = itm.text(self.RevisionColumn).strip().split(":", 1)
2136 itm.text(self.RevisionColumn).strip().split(":", 1)
2137 )
2138 bookmarksList = self.vcs.hgGetBookmarksList() 2303 bookmarksList = self.vcs.hgGetBookmarksList()
2139 bookmark, ok = QInputDialog.getItem( 2304 bookmark, ok = QInputDialog.getItem(
2140 self, 2305 self,
2141 self.tr("Move Bookmark"), 2306 self.tr("Move Bookmark"),
2142 self.tr('Select the bookmark to be moved to changeset' 2307 self.tr(
2143 ' "{0}":').format(changeset), 2308 "Select the bookmark to be moved to changeset" ' "{0}":'
2309 ).format(changeset),
2144 [""] + bookmarksList, 2310 [""] + bookmarksList,
2145 0, False) 2311 0,
2312 False,
2313 )
2146 if ok and bool(bookmark): 2314 if ok and bool(bookmark):
2147 self.vcs.hgBookmarkMove( 2315 self.vcs.hgBookmarkMove(
2148 revision="rev({0})".format(rev), 2316 revision="rev({0})".format(rev), bookmark=bookmark
2149 bookmark=bookmark) 2317 )
2150 self.on_refreshButton_clicked() 2318 self.on_refreshButton_clicked()
2151 2319
2152 @pyqtSlot() 2320 @pyqtSlot()
2153 def __lfPullActTriggered(self): 2321 def __lfPullActTriggered(self):
2154 """ 2322 """
2155 Private slot to pull large files of selected revisions. 2323 Private slot to pull large files of selected revisions.
2156 """ 2324 """
2157 revs = [] 2325 revs = []
2158 for itm in [item for item in self.logTree.selectedItems() 2326 for itm in [
2159 if not item.data(0, self.__incomingRole)]: 2327 item
2328 for item in self.logTree.selectedItems()
2329 if not item.data(0, self.__incomingRole)
2330 ]:
2160 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0] 2331 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0]
2161 if rev: 2332 if rev:
2162 revs.append(rev) 2333 revs.append(rev)
2163 2334
2164 if revs: 2335 if revs:
2165 self.vcs.getExtensionObject("largefiles").hgLfPull(revisions=revs) 2336 self.vcs.getExtensionObject("largefiles").hgLfPull(revisions=revs)
2166 2337
2167 @pyqtSlot() 2338 @pyqtSlot()
2168 def __pullActTriggered(self): 2339 def __pullActTriggered(self):
2169 """ 2340 """
2170 Private slot to pull changes from a remote repository. 2341 Private slot to pull changes from a remote repository.
2171 """ 2342 """
2172 shouldReopen = False 2343 shouldReopen = False
2173 refresh = False 2344 refresh = False
2174 addNext = False 2345 addNext = False
2175 2346
2176 if self.initialCommandMode in ("log", "full_log", "incoming"): 2347 if self.initialCommandMode in ("log", "full_log", "incoming"):
2177 revs = [] 2348 revs = []
2178 for itm in [item for item in self.logTree.selectedItems() 2349 for itm in [
2179 if item.data(0, self.__incomingRole)]: 2350 item
2351 for item in self.logTree.selectedItems()
2352 if item.data(0, self.__incomingRole)
2353 ]:
2180 rev = itm.text(self.RevisionColumn).split(":")[1].strip() 2354 rev = itm.text(self.RevisionColumn).split(":")[1].strip()
2181 if rev: 2355 if rev:
2182 revs.append(rev) 2356 revs.append(rev)
2183 shouldReopen = self.vcs.hgPull(revisions=revs) 2357 shouldReopen = self.vcs.hgPull(revisions=revs)
2184 refresh = True 2358 refresh = True
2185 if self.initialCommandMode == "incoming": 2359 if self.initialCommandMode == "incoming":
2186 addNext = True 2360 addNext = True
2187 2361
2188 if shouldReopen: 2362 if shouldReopen:
2189 res = EricMessageBox.yesNo( 2363 res = EricMessageBox.yesNo(
2190 None, 2364 None,
2191 self.tr("Pull Changes"), 2365 self.tr("Pull Changes"),
2192 self.tr( 2366 self.tr("""The project should be reread. Do this now?"""),
2193 """The project should be reread. Do this now?"""), 2367 yesDefault=True,
2194 yesDefault=True) 2368 )
2195 if res: 2369 if res:
2196 ericApp().getObject("Project").reopenProject() 2370 ericApp().getObject("Project").reopenProject()
2197 return 2371 return
2198 2372
2199 if refresh: 2373 if refresh:
2200 self.on_refreshButton_clicked(addNext=addNext) 2374 self.on_refreshButton_clicked(addNext=addNext)
2201 2375
2202 @pyqtSlot() 2376 @pyqtSlot()
2203 def __pushActTriggered(self): 2377 def __pushActTriggered(self):
2204 """ 2378 """
2205 Private slot to push changes to a remote repository up to a selected 2379 Private slot to push changes to a remote repository up to a selected
2206 changeset. 2380 changeset.
2209 if not itm.data(0, self.__incomingRole): 2383 if not itm.data(0, self.__incomingRole):
2210 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0] 2384 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[0]
2211 if rev: 2385 if rev:
2212 self.vcs.hgPush(rev=rev) 2386 self.vcs.hgPush(rev=rev)
2213 self.on_refreshButton_clicked( 2387 self.on_refreshButton_clicked(
2214 addNext=self.initialCommandMode == "outgoing") 2388 addNext=self.initialCommandMode == "outgoing"
2215 2389 )
2390
2216 @pyqtSlot() 2391 @pyqtSlot()
2217 def __pushAllActTriggered(self): 2392 def __pushAllActTriggered(self):
2218 """ 2393 """
2219 Private slot to push all changes to a remote repository. 2394 Private slot to push all changes to a remote repository.
2220 """ 2395 """
2221 self.vcs.hgPush() 2396 self.vcs.hgPush()
2222 self.on_refreshButton_clicked() 2397 self.on_refreshButton_clicked()
2223 2398
2224 @pyqtSlot() 2399 @pyqtSlot()
2225 def __stripActTriggered(self): 2400 def __stripActTriggered(self):
2226 """ 2401 """
2227 Private slot to strip changesets from the repository. 2402 Private slot to strip changesets from the repository.
2228 """ 2403 """
2229 itm = self.logTree.selectedItems()[0] 2404 itm = self.logTree.selectedItems()[0]
2230 if not itm.data(0, self.__incomingRole): 2405 if not itm.data(0, self.__incomingRole):
2231 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[1] 2406 rev = itm.text(self.RevisionColumn).strip().split(":", 1)[1]
2232 shouldReopen = self.vcs.getExtensionObject("strip").hgStrip( 2407 shouldReopen = self.vcs.getExtensionObject("strip").hgStrip(rev=rev)
2233 rev=rev)
2234 if shouldReopen: 2408 if shouldReopen:
2235 res = EricMessageBox.yesNo( 2409 res = EricMessageBox.yesNo(
2236 None, 2410 None,
2237 self.tr("Strip Changesets"), 2411 self.tr("Strip Changesets"),
2238 self.tr( 2412 self.tr("""The project should be reread. Do this now?"""),
2239 """The project should be reread. Do this now?"""), 2413 yesDefault=True,
2240 yesDefault=True) 2414 )
2241 if res: 2415 if res:
2242 ericApp().getObject("Project").reopenProject() 2416 ericApp().getObject("Project").reopenProject()
2243 return 2417 return
2244 2418
2245 self.on_refreshButton_clicked() 2419 self.on_refreshButton_clicked()
2246 2420
2247 @pyqtSlot() 2421 @pyqtSlot()
2248 def __mergeActTriggered(self): 2422 def __mergeActTriggered(self):
2249 """ 2423 """
2250 Private slot to merge the working directory with the selected 2424 Private slot to merge the working directory with the selected
2251 changeset. 2425 changeset.
2252 """ 2426 """
2253 itm = self.logTree.selectedItems()[0] 2427 itm = self.logTree.selectedItems()[0]
2254 if not itm.data(0, self.__incomingRole): 2428 if not itm.data(0, self.__incomingRole):
2255 rev = "rev({0})".format( 2429 rev = "rev({0})".format(
2256 itm.text(self.RevisionColumn).strip().split(":", 1)[0]) 2430 itm.text(self.RevisionColumn).strip().split(":", 1)[0]
2431 )
2257 self.vcs.vcsMerge("", rev=rev) 2432 self.vcs.vcsMerge("", rev=rev)
2258 2433
2259 @pyqtSlot() 2434 @pyqtSlot()
2260 def __bundleActTriggered(self): 2435 def __bundleActTriggered(self):
2261 """ 2436 """
2262 Private slot to create a changegroup file. 2437 Private slot to create a changegroup file.
2263 """ 2438 """
2264 if self.initialCommandMode in ("log", "full_log"): 2439 if self.initialCommandMode in ("log", "full_log"):
2265 selectedItems = [itm for itm in self.logTree.selectedItems() 2440 selectedItems = [
2266 if not itm.data(0, self.__incomingRole)] 2441 itm
2442 for itm in self.logTree.selectedItems()
2443 if not itm.data(0, self.__incomingRole)
2444 ]
2267 if len(selectedItems) == 0: 2445 if len(selectedItems) == 0:
2268 # all revisions of the local repository will be bundled 2446 # all revisions of the local repository will be bundled
2269 bundleData = { 2447 bundleData = {
2270 "revs": [], 2448 "revs": [],
2271 "base": "", 2449 "base": "",
2272 "all": True, 2450 "all": True,
2273 } 2451 }
2274 elif len(selectedItems) == 1: 2452 elif len(selectedItems) == 1:
2275 # the selected changeset is the base 2453 # the selected changeset is the base
2276 rev = selectedItems[0].text(self.RevisionColumn).split( 2454 rev = (
2277 ":", 1)[0].strip() 2455 selectedItems[0].text(self.RevisionColumn).split(":", 1)[0].strip()
2456 )
2278 bundleData = { 2457 bundleData = {
2279 "revs": [], 2458 "revs": [],
2280 "base": rev, 2459 "base": rev,
2281 "all": False, 2460 "all": False,
2282 } 2461 }
2288 with contextlib.suppress(ValueError): 2467 with contextlib.suppress(ValueError):
2289 revs.append(int(rev)) 2468 revs.append(int(rev))
2290 baseRev = min(revs) 2469 baseRev = min(revs)
2291 while baseRev in revs: 2470 while baseRev in revs:
2292 revs.remove(baseRev) 2471 revs.remove(baseRev)
2293 2472
2294 bundleData = { 2473 bundleData = {
2295 "revs": [str(rev) for rev in revs], 2474 "revs": [str(rev) for rev in revs],
2296 "base": str(baseRev), 2475 "base": str(baseRev),
2297 "all": False, 2476 "all": False,
2298 } 2477 }
2301 if len(selectedItems) > 0: 2480 if len(selectedItems) > 0:
2302 revs = [] 2481 revs = []
2303 for itm in selectedItems: 2482 for itm in selectedItems:
2304 rev = itm.text(self.RevisionColumn).split(":", 1)[0] 2483 rev = itm.text(self.RevisionColumn).split(":", 1)[0]
2305 revs.append(rev.strip()) 2484 revs.append(rev.strip())
2306 2485
2307 bundleData = { 2486 bundleData = {
2308 "revs": revs, 2487 "revs": revs,
2309 "base": "", 2488 "base": "",
2310 "all": False, 2489 "all": False,
2311 } 2490 }
2312 2491
2313 self.vcs.hgBundle(bundleData=bundleData) 2492 self.vcs.hgBundle(bundleData=bundleData)
2314 2493
2315 @pyqtSlot() 2494 @pyqtSlot()
2316 def __unbundleActTriggered(self): 2495 def __unbundleActTriggered(self):
2317 """ 2496 """
2318 Private slot to apply the currently previewed bundle file. 2497 Private slot to apply the currently previewed bundle file.
2319 """ 2498 """
2322 if shouldReopen: 2501 if shouldReopen:
2323 res = EricMessageBox.yesNo( 2502 res = EricMessageBox.yesNo(
2324 None, 2503 None,
2325 self.tr("Apply Changegroup"), 2504 self.tr("Apply Changegroup"),
2326 self.tr("""The project should be reread. Do this now?"""), 2505 self.tr("""The project should be reread. Do this now?"""),
2327 yesDefault=True) 2506 yesDefault=True,
2507 )
2328 if res: 2508 if res:
2329 ericApp().getObject("Project").reopenProject() 2509 ericApp().getObject("Project").reopenProject()
2330 return 2510 return
2331 2511
2332 self.vcs.vcsLogBrowser() 2512 self.vcs.vcsLogBrowser()
2333 self.close() 2513 self.close()
2334 2514
2335 @pyqtSlot() 2515 @pyqtSlot()
2336 def __gpgSignActTriggered(self): 2516 def __gpgSignActTriggered(self):
2337 """ 2517 """
2338 Private slot to sign the selected revisions. 2518 Private slot to sign the selected revisions.
2339 """ 2519 """
2340 revs = [] 2520 revs = []
2341 for itm in [item for item in self.logTree.selectedItems() 2521 for itm in [
2342 if not item.data(0, self.__incomingRole)]: 2522 item
2523 for item in self.logTree.selectedItems()
2524 if not item.data(0, self.__incomingRole)
2525 ]:
2343 rev = itm.text(self.RevisionColumn).split(":", 1)[0].strip() 2526 rev = itm.text(self.RevisionColumn).split(":", 1)[0].strip()
2344 if rev: 2527 if rev:
2345 revs.append(rev) 2528 revs.append(rev)
2346 2529
2347 if revs: 2530 if revs:
2348 self.vcs.getExtensionObject("gpg").hgGpgSign(revisions=revs) 2531 self.vcs.getExtensionObject("gpg").hgGpgSign(revisions=revs)
2349 2532
2350 @pyqtSlot() 2533 @pyqtSlot()
2351 def __gpgVerifyActTriggered(self): 2534 def __gpgVerifyActTriggered(self):
2352 """ 2535 """
2353 Private slot to verify the signatures of a selected revisions. 2536 Private slot to verify the signatures of a selected revisions.
2354 """ 2537 """
2355 itm = self.logTree.selectedItems()[0] 2538 itm = self.logTree.selectedItems()[0]
2356 if not itm.data(0, self.__incomingRole): 2539 if not itm.data(0, self.__incomingRole):
2357 rev = itm.text(self.RevisionColumn).split(":", 1)[0].strip() 2540 rev = itm.text(self.RevisionColumn).split(":", 1)[0].strip()
2358 if rev: 2541 if rev:
2359 self.vcs.getExtensionObject("gpg").hgGpgVerifySignatures( 2542 self.vcs.getExtensionObject("gpg").hgGpgVerifySignatures(rev=rev)
2360 rev=rev) 2543
2361
2362 def __selectAllActTriggered(self, select=True): 2544 def __selectAllActTriggered(self, select=True):
2363 """ 2545 """
2364 Private method to select or unselect all log entries. 2546 Private method to select or unselect all log entries.
2365 2547
2366 @param select flag indicating to select all entries 2548 @param select flag indicating to select all entries
2367 @type bool 2549 @type bool
2368 """ 2550 """
2369 blocked = self.logTree.blockSignals(True) 2551 blocked = self.logTree.blockSignals(True)
2370 for row in range(self.logTree.topLevelItemCount()): 2552 for row in range(self.logTree.topLevelItemCount()):
2371 self.logTree.topLevelItem(row).setSelected(select) 2553 self.logTree.topLevelItem(row).setSelected(select)
2372 self.logTree.blockSignals(blocked) 2554 self.logTree.blockSignals(blocked)
2373 self.on_logTree_itemSelectionChanged() 2555 self.on_logTree_itemSelectionChanged()
2374 2556
2375 def __actionMode(self): 2557 def __actionMode(self):
2376 """ 2558 """
2377 Private method to get the selected action mode. 2559 Private method to get the selected action mode.
2378 2560
2379 @return selected action mode (string, one of filter or find) 2561 @return selected action mode (string, one of filter or find)
2380 """ 2562 """
2381 return self.modeComboBox.itemData( 2563 return self.modeComboBox.itemData(self.modeComboBox.currentIndex())
2382 self.modeComboBox.currentIndex()) 2564
2383
2384 @pyqtSlot(int) 2565 @pyqtSlot(int)
2385 def on_modeComboBox_currentIndexChanged(self, index): 2566 def on_modeComboBox_currentIndexChanged(self, index):
2386 """ 2567 """
2387 Private slot to react on mode changes. 2568 Private slot to react on mode changes.
2388 2569
2389 @param index index of the selected entry (integer) 2570 @param index index of the selected entry (integer)
2390 """ 2571 """
2391 mode = self.modeComboBox.itemData(index) 2572 mode = self.modeComboBox.itemData(index)
2392 findMode = mode == "find" 2573 findMode = mode == "find"
2393 filterMode = mode == "filter" 2574 filterMode = mode == "filter"
2394 2575
2395 self.fromDate.setEnabled(filterMode) 2576 self.fromDate.setEnabled(filterMode)
2396 self.toDate.setEnabled(filterMode) 2577 self.toDate.setEnabled(filterMode)
2397 self.branchCombo.setEnabled(filterMode) 2578 self.branchCombo.setEnabled(filterMode)
2398 self.findPrevButton.setVisible(findMode) 2579 self.findPrevButton.setVisible(findMode)
2399 self.findNextButton.setVisible(findMode) 2580 self.findNextButton.setVisible(findMode)
2400 2581
2401 if findMode: 2582 if findMode:
2402 for topIndex in range(self.logTree.topLevelItemCount()): 2583 for topIndex in range(self.logTree.topLevelItemCount()):
2403 self.logTree.topLevelItem(topIndex).setHidden(False) 2584 self.logTree.topLevelItem(topIndex).setHidden(False)
2404 self.logTree.header().setSectionHidden(self.IconColumn, False) 2585 self.logTree.header().setSectionHidden(self.IconColumn, False)
2405 elif filterMode: 2586 elif filterMode:
2406 self.__filterLogs() 2587 self.__filterLogs()
2407 2588
2408 @pyqtSlot() 2589 @pyqtSlot()
2409 def on_findPrevButton_clicked(self): 2590 def on_findPrevButton_clicked(self):
2410 """ 2591 """
2411 Private slot to find the previous item matching the entered criteria. 2592 Private slot to find the previous item matching the entered criteria.
2412 """ 2593 """
2413 self.__findItem(True) 2594 self.__findItem(True)
2414 2595
2415 @pyqtSlot() 2596 @pyqtSlot()
2416 def on_findNextButton_clicked(self): 2597 def on_findNextButton_clicked(self):
2417 """ 2598 """
2418 Private slot to find the next item matching the entered criteria. 2599 Private slot to find the next item matching the entered criteria.
2419 """ 2600 """
2420 self.__findItem(False) 2601 self.__findItem(False)
2421 2602
2422 def __findItem(self, backwards=False, interactive=False): 2603 def __findItem(self, backwards=False, interactive=False):
2423 """ 2604 """
2424 Private slot to find an item matching the entered criteria. 2605 Private slot to find an item matching the entered criteria.
2425 2606
2426 @param backwards flag indicating to search backwards (boolean) 2607 @param backwards flag indicating to search backwards (boolean)
2427 @param interactive flag indicating an interactive search (boolean) 2608 @param interactive flag indicating an interactive search (boolean)
2428 """ 2609 """
2429 self.__findBackwards = backwards 2610 self.__findBackwards = backwards
2430 2611
2431 fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch() 2612 fieldIndex, searchRx, indexIsRole = self.__prepareFieldSearch()
2432 currentIndex = self.logTree.indexOfTopLevelItem( 2613 currentIndex = self.logTree.indexOfTopLevelItem(self.logTree.currentItem())
2433 self.logTree.currentItem())
2434 if backwards: 2614 if backwards:
2435 if interactive: 2615 if interactive:
2436 indexes = range(currentIndex, -1, -1) 2616 indexes = range(currentIndex, -1, -1)
2437 else: 2617 else:
2438 indexes = range(currentIndex - 1, -1, -1) 2618 indexes = range(currentIndex - 1, -1, -1)
2439 else: 2619 else:
2440 if interactive: 2620 if interactive:
2441 indexes = range(currentIndex, self.logTree.topLevelItemCount()) 2621 indexes = range(currentIndex, self.logTree.topLevelItemCount())
2442 else: 2622 else:
2443 indexes = range(currentIndex + 1, 2623 indexes = range(currentIndex + 1, self.logTree.topLevelItemCount())
2444 self.logTree.topLevelItemCount()) 2624
2445
2446 for index in indexes: 2625 for index in indexes:
2447 topItem = self.logTree.topLevelItem(index) 2626 topItem = self.logTree.topLevelItem(index)
2448 if indexIsRole: 2627 if indexIsRole:
2449 if fieldIndex == self.__changesRole: 2628 if fieldIndex == self.__changesRole:
2450 changes = topItem.data(0, self.__changesRole) 2629 changes = topItem.data(0, self.__changesRole)
2451 txt = "\n".join( 2630 txt = "\n".join(
2452 [c["path"] for c in changes] + 2631 [c["path"] for c in changes] + [c["copyfrom"] for c in changes]
2453 [c["copyfrom"] for c in changes]
2454 ) 2632 )
2455 else: 2633 else:
2456 # Find based on complete message text 2634 # Find based on complete message text
2457 txt = "\n".join(topItem.data(0, self.__messageRole)) 2635 txt = "\n".join(topItem.data(0, self.__messageRole))
2458 else: 2636 else:
2462 break 2640 break
2463 else: 2641 else:
2464 EricMessageBox.information( 2642 EricMessageBox.information(
2465 self, 2643 self,
2466 self.tr("Find Commit"), 2644 self.tr("Find Commit"),
2467 self.tr("""'{0}' was not found.""").format(self.rxEdit.text())) 2645 self.tr("""'{0}' was not found.""").format(self.rxEdit.text()),
2468 2646 )
2647
2469 def __revisionClicked(self, url): 2648 def __revisionClicked(self, url):
2470 """ 2649 """
2471 Private slot to handle the anchorClicked signal of the changeset 2650 Private slot to handle the anchorClicked signal of the changeset
2472 details pane. 2651 details pane.
2473 2652
2474 @param url URL that was clicked 2653 @param url URL that was clicked
2475 @type QUrl 2654 @type QUrl
2476 """ 2655 """
2477 if url.scheme() in ("rev", "chg"): 2656 if url.scheme() in ("rev", "chg"):
2478 if url.scheme() == "rev": 2657 if url.scheme() == "rev":
2485 # a changeset hash was clicked, show the item 2664 # a changeset hash was clicked, show the item
2486 changeset = url.path() 2665 changeset = url.path()
2487 searchStr = ":{0}".format(changeset[:12]) # max. 12 hash chars 2666 searchStr = ":{0}".format(changeset[:12]) # max. 12 hash chars
2488 # format must be in sync with item generation format 2667 # format must be in sync with item generation format
2489 searchFlags = Qt.MatchFlag.MatchContains 2668 searchFlags = Qt.MatchFlag.MatchContains
2490 items = self.logTree.findItems( 2669 items = self.logTree.findItems(searchStr, searchFlags, self.RevisionColumn)
2491 searchStr, searchFlags, self.RevisionColumn)
2492 if items: 2670 if items:
2493 itm = items[0] 2671 itm = items[0]
2494 if itm.isHidden(): 2672 if itm.isHidden():
2495 itm.setHidden(False) 2673 itm.setHidden(False)
2496 self.logTree.setCurrentItem(itm) 2674 self.logTree.setCurrentItem(itm)
2497 else: 2675 else:
2498 # load the next batch and try again 2676 # load the next batch and try again
2499 if not self.cancelled and self.nextButton.isEnabled(): 2677 if not self.cancelled and self.nextButton.isEnabled():
2500 self.__addFinishCallback( 2678 self.__addFinishCallback(lambda: self.__revisionClicked(url))
2501 lambda: self.__revisionClicked(url))
2502 self.on_nextButton_clicked() 2679 self.on_nextButton_clicked()
2503 2680
2504 ########################################################################### 2681 ###########################################################################
2505 ## Diff handling methods below 2682 ## Diff handling methods below
2506 ########################################################################### 2683 ###########################################################################
2507 2684
2508 def __generateDiffs(self, parent=1): 2685 def __generateDiffs(self, parent=1):
2509 """ 2686 """
2510 Private slot to generate diff outputs for the selected item. 2687 Private slot to generate diff outputs for the selected item.
2511 2688
2512 @param parent number of parent to diff against 2689 @param parent number of parent to diff against
2513 @type int 2690 @type int
2514 """ 2691 """
2515 self.diffEdit.setPlainText(self.tr("Generating differences ...")) 2692 self.diffEdit.setPlainText(self.tr("Generating differences ..."))
2516 self.diffLabel.setText(self.tr("Differences")) 2693 self.diffLabel.setText(self.tr("Differences"))
2517 self.diffSelectLabel.clear() 2694 self.diffSelectLabel.clear()
2518 self.diffHighlighter.regenerateRules() 2695 self.diffHighlighter.regenerateRules()
2519 2696
2520 selectedItems = self.logTree.selectedItems() 2697 selectedItems = self.logTree.selectedItems()
2521 if len(selectedItems) == 1: 2698 if len(selectedItems) == 1:
2522 currentItem = selectedItems[0] 2699 currentItem = selectedItems[0]
2523 rev2 = currentItem.text(self.RevisionColumn).split(":", 1)[0] 2700 rev2 = currentItem.text(self.RevisionColumn).split(":", 1)[0]
2524 parents = currentItem.data(0, self.__parentsRole) 2701 parents = currentItem.data(0, self.__parentsRole)
2525 if len(parents) >= parent: 2702 if len(parents) >= parent:
2526 self.diffLabel.setText( 2703 self.diffLabel.setText(
2527 self.tr("Differences to Parent {0}").format(parent)) 2704 self.tr("Differences to Parent {0}").format(parent)
2705 )
2528 rev1 = parents[parent - 1] 2706 rev1 = parents[parent - 1]
2529 2707
2530 self.__diffGenerator.start(self.__filename, [rev1, rev2], 2708 self.__diffGenerator.start(self.__filename, [rev1, rev2], self.__bundle)
2531 self.__bundle) 2709
2532
2533 if len(parents) > 1: 2710 if len(parents) > 1:
2534 if parent == 1: 2711 if parent == 1:
2535 par1 = "&nbsp;1&nbsp;" 2712 par1 = "&nbsp;1&nbsp;"
2536 else: 2713 else:
2537 par1 = '<a href="diff:1">&nbsp;1&nbsp;</a>' 2714 par1 = '<a href="diff:1">&nbsp;1&nbsp;</a>'
2538 if parent == 2: 2715 if parent == 2:
2539 par2 = "&nbsp;2&nbsp;" 2716 par2 = "&nbsp;2&nbsp;"
2540 else: 2717 else:
2541 par2 = '<a href="diff:2">&nbsp;2&nbsp;</a>' 2718 par2 = '<a href="diff:2">&nbsp;2&nbsp;</a>'
2542 self.diffSelectLabel.setText( 2719 self.diffSelectLabel.setText(
2543 self.tr('Diff to Parent {0}{1}').format(par1, par2)) 2720 self.tr("Diff to Parent {0}{1}").format(par1, par2)
2721 )
2544 elif len(selectedItems) == 2: 2722 elif len(selectedItems) == 2:
2545 rev2 = int(selectedItems[0].text( 2723 rev2 = int(selectedItems[0].text(self.RevisionColumn).split(":")[0])
2546 self.RevisionColumn).split(":")[0]) 2724 rev1 = int(selectedItems[1].text(self.RevisionColumn).split(":")[0])
2547 rev1 = int(selectedItems[1].text( 2725
2548 self.RevisionColumn).split(":")[0]) 2726 self.__diffGenerator.start(
2549 2727 self.__filename, [min(rev1, rev2), max(rev1, rev2)], self.__bundle
2550 self.__diffGenerator.start(self.__filename, 2728 )
2551 [min(rev1, rev2), max(rev1, rev2)],
2552 self.__bundle)
2553 else: 2729 else:
2554 self.diffEdit.clear() 2730 self.diffEdit.clear()
2555 2731
2556 def __generatorFinished(self): 2732 def __generatorFinished(self):
2557 """ 2733 """
2558 Private slot connected to the finished signal of the diff generator. 2734 Private slot connected to the finished signal of the diff generator.
2559 """ 2735 """
2560 diff, errors, fileSeparators = self.__diffGenerator.getResult() 2736 diff, errors, fileSeparators = self.__diffGenerator.getResult()
2561 2737
2562 if diff: 2738 if diff:
2563 self.diffEdit.setPlainText("".join(diff)) 2739 self.diffEdit.setPlainText("".join(diff))
2564 elif errors: 2740 elif errors:
2565 self.diffEdit.setPlainText("".join(errors)) 2741 self.diffEdit.setPlainText("".join(errors))
2566 else: 2742 else:
2567 self.diffEdit.setPlainText(self.tr('There is no difference.')) 2743 self.diffEdit.setPlainText(self.tr("There is no difference."))
2568 2744
2569 self.saveLabel.setVisible(bool(diff)) 2745 self.saveLabel.setVisible(bool(diff))
2570 2746
2571 if self.__diffUpdatesFiles: 2747 if self.__diffUpdatesFiles:
2572 for oldFileName, newFileName, lineNumber in fileSeparators: 2748 for oldFileName, newFileName, lineNumber in fileSeparators:
2573 if oldFileName == newFileName: 2749 if oldFileName == newFileName:
2574 fileName = oldFileName 2750 fileName = oldFileName
2575 elif oldFileName == "__NULL__": 2751 elif oldFileName == "__NULL__":
2583 else: 2759 else:
2584 for oldFileName, newFileName, lineNumber in fileSeparators: 2760 for oldFileName, newFileName, lineNumber in fileSeparators:
2585 for fileName in (oldFileName, newFileName): 2761 for fileName in (oldFileName, newFileName):
2586 if fileName != "__NULL__": 2762 if fileName != "__NULL__":
2587 items = self.filesTree.findItems( 2763 items = self.filesTree.findItems(
2588 fileName, Qt.MatchFlag.MatchExactly, 1) 2764 fileName, Qt.MatchFlag.MatchExactly, 1
2765 )
2589 for item in items: 2766 for item in items:
2590 item.setData(0, self.__diffFileLineRole, 2767 item.setData(0, self.__diffFileLineRole, lineNumber)
2591 lineNumber) 2768
2592
2593 tc = self.diffEdit.textCursor() 2769 tc = self.diffEdit.textCursor()
2594 tc.movePosition(QTextCursor.MoveOperation.Start) 2770 tc.movePosition(QTextCursor.MoveOperation.Start)
2595 self.diffEdit.setTextCursor(tc) 2771 self.diffEdit.setTextCursor(tc)
2596 self.diffEdit.ensureCursorVisible() 2772 self.diffEdit.ensureCursorVisible()
2597 2773
2598 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) 2774 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
2599 def on_filesTree_currentItemChanged(self, current, previous): 2775 def on_filesTree_currentItemChanged(self, current, previous):
2600 """ 2776 """
2601 Private slot called, when the current item of the files tree changes. 2777 Private slot called, when the current item of the files tree changes.
2602 2778
2603 @param current reference to the new current item (QTreeWidgetItem) 2779 @param current reference to the new current item (QTreeWidgetItem)
2604 @param previous reference to the old current item (QTreeWidgetItem) 2780 @param previous reference to the old current item (QTreeWidgetItem)
2605 """ 2781 """
2606 if current: 2782 if current:
2607 para = current.data(0, self.__diffFileLineRole) 2783 para = current.data(0, self.__diffFileLineRole)
2620 # step 1: move cursor to end 2796 # step 1: move cursor to end
2621 tc = self.diffEdit.textCursor() 2797 tc = self.diffEdit.textCursor()
2622 tc.movePosition(QTextCursor.MoveOperation.End) 2798 tc.movePosition(QTextCursor.MoveOperation.End)
2623 self.diffEdit.setTextCursor(tc) 2799 self.diffEdit.setTextCursor(tc)
2624 self.diffEdit.ensureCursorVisible() 2800 self.diffEdit.ensureCursorVisible()
2625 2801
2626 # step 2: move cursor to desired line 2802 # step 2: move cursor to desired line
2627 tc = self.diffEdit.textCursor() 2803 tc = self.diffEdit.textCursor()
2628 delta = tc.blockNumber() - para 2804 delta = tc.blockNumber() - para
2629 tc.movePosition(QTextCursor.MoveOperation.PreviousBlock, 2805 tc.movePosition(
2630 QTextCursor.MoveMode.MoveAnchor, delta) 2806 QTextCursor.MoveOperation.PreviousBlock,
2807 QTextCursor.MoveMode.MoveAnchor,
2808 delta,
2809 )
2631 self.diffEdit.setTextCursor(tc) 2810 self.diffEdit.setTextCursor(tc)
2632 self.diffEdit.ensureCursorVisible() 2811 self.diffEdit.ensureCursorVisible()
2633 2812
2634 @pyqtSlot(str) 2813 @pyqtSlot(str)
2635 def on_diffSelectLabel_linkActivated(self, link): 2814 def on_diffSelectLabel_linkActivated(self, link):
2636 """ 2815 """
2637 Private slot to handle the selection of a diff target. 2816 Private slot to handle the selection of a diff target.
2638 2817
2639 @param link activated link 2818 @param link activated link
2640 @type str 2819 @type str
2641 """ 2820 """
2642 if ":" in link: 2821 if ":" in link:
2643 scheme, parent = link.split(":", 1) 2822 scheme, parent = link.split(":", 1)
2644 if scheme == "diff": 2823 if scheme == "diff":
2645 with contextlib.suppress(ValueError): 2824 with contextlib.suppress(ValueError):
2646 parent = int(parent) 2825 parent = int(parent)
2647 self.__generateDiffs(parent) 2826 self.__generateDiffs(parent)
2648 2827
2649 @pyqtSlot(str) 2828 @pyqtSlot(str)
2650 def on_saveLabel_linkActivated(self, link): 2829 def on_saveLabel_linkActivated(self, link):
2651 """ 2830 """
2652 Private slot to handle the selection of the save link. 2831 Private slot to handle the selection of the save link.
2653 2832
2654 @param link activated link 2833 @param link activated link
2655 @type str 2834 @type str
2656 """ 2835 """
2657 if ":" not in link: 2836 if ":" not in link:
2658 return 2837 return
2659 2838
2660 scheme, rest = link.split(":", 1) 2839 scheme, rest = link.split(":", 1)
2661 if scheme != "save" or rest != "me": 2840 if scheme != "save" or rest != "me":
2662 return 2841 return
2663 2842
2664 if self.projectMode: 2843 if self.projectMode:
2665 if self.__filename is None: 2844 if self.__filename is None:
2666 fname = "{0}.diff".format(os.path.splitext( 2845 fname = "{0}.diff".format(
2667 ericApp().getObject("Project").getProjectFile())[0]) 2846 os.path.splitext(ericApp().getObject("Project").getProjectFile())[0]
2847 )
2668 else: 2848 else:
2669 fname = self.vcs.splitPath(self.__filename)[0] 2849 fname = self.vcs.splitPath(self.__filename)[0]
2670 fname += "/{0}.diff".format(os.path.split(fname)[-1]) 2850 fname += "/{0}.diff".format(os.path.split(fname)[-1])
2671 else: 2851 else:
2672 dname, fname = self.vcs.splitPath(self.__filename) 2852 dname, fname = self.vcs.splitPath(self.__filename)
2673 if fname != '.': 2853 if fname != ".":
2674 fname = "{0}.diff".format(self.__filename) 2854 fname = "{0}.diff".format(self.__filename)
2675 else: 2855 else:
2676 fname = dname 2856 fname = dname
2677 2857
2678 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( 2858 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
2679 self, 2859 self,
2680 self.tr("Save Diff"), 2860 self.tr("Save Diff"),
2681 fname, 2861 fname,
2682 self.tr("Patch Files (*.diff)"), 2862 self.tr("Patch Files (*.diff)"),
2683 None, 2863 None,
2684 EricFileDialog.DontConfirmOverwrite) 2864 EricFileDialog.DontConfirmOverwrite,
2685 2865 )
2866
2686 if not fname: 2867 if not fname:
2687 return # user aborted 2868 return # user aborted
2688 2869
2689 fpath = pathlib.Path(fname) 2870 fpath = pathlib.Path(fname)
2690 if not fpath.suffix: 2871 if not fpath.suffix:
2691 ex = selectedFilter.split("(*")[1].split(")")[0] 2872 ex = selectedFilter.split("(*")[1].split(")")[0]
2692 if ex: 2873 if ex:
2693 fpath = fpath.with_suffix(ex) 2874 fpath = fpath.with_suffix(ex)
2694 if fpath.exists(): 2875 if fpath.exists():
2695 res = EricMessageBox.yesNo( 2876 res = EricMessageBox.yesNo(
2696 self, 2877 self,
2697 self.tr("Save Diff"), 2878 self.tr("Save Diff"),
2698 self.tr("<p>The patch file <b>{0}</b> already exists." 2879 self.tr(
2699 " Overwrite it?</p>").format(fpath), 2880 "<p>The patch file <b>{0}</b> already exists." " Overwrite it?</p>"
2700 icon=EricMessageBox.Warning) 2881 ).format(fpath),
2882 icon=EricMessageBox.Warning,
2883 )
2701 if not res: 2884 if not res:
2702 return 2885 return
2703 2886
2704 eol = ericApp().getObject("Project").getEolString() 2887 eol = ericApp().getObject("Project").getEolString()
2705 try: 2888 try:
2706 with fpath.open("w", encoding="utf-8", newline="") as f: 2889 with fpath.open("w", encoding="utf-8", newline="") as f:
2707 f.write(eol.join(self.diffEdit.toPlainText().splitlines())) 2890 f.write(eol.join(self.diffEdit.toPlainText().splitlines()))
2708 except OSError as why: 2891 except OSError as why:
2709 EricMessageBox.critical( 2892 EricMessageBox.critical(
2710 self, self.tr('Save Diff'), 2893 self,
2894 self.tr("Save Diff"),
2711 self.tr( 2895 self.tr(
2712 '<p>The patch file <b>{0}</b> could not be saved.' 2896 "<p>The patch file <b>{0}</b> could not be saved."
2713 '<br>Reason: {1}</p>') 2897 "<br>Reason: {1}</p>"
2714 .format(fpath, str(why))) 2898 ).format(fpath, str(why)),
2715 2899 )
2900
2716 @pyqtSlot(str) 2901 @pyqtSlot(str)
2717 def on_sbsSelectLabel_linkActivated(self, link): 2902 def on_sbsSelectLabel_linkActivated(self, link):
2718 """ 2903 """
2719 Private slot to handle selection of a side-by-side link. 2904 Private slot to handle selection of a side-by-side link.
2720 2905
2721 @param link text of the selected link 2906 @param link text of the selected link
2722 @type str 2907 @type str
2723 """ 2908 """
2724 if ":" in link and self.__filename is not None: 2909 if ":" in link and self.__filename is not None:
2725 scheme, path = link.split(":", 1) 2910 scheme, path = link.split(":", 1)

eric ide

mercurial