src/eric7/Plugins/VcsPlugins/vcsGit/GitStatusDialog.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 14
15 from PyQt6.QtCore import pyqtSlot, Qt, QProcess, QTimer, QSize 15 from PyQt6.QtCore import pyqtSlot, Qt, QProcess, QTimer, QSize
16 from PyQt6.QtGui import QTextCursor 16 from PyQt6.QtGui import QTextCursor
17 from PyQt6.QtWidgets import ( 17 from PyQt6.QtWidgets import (
18 QWidget, QDialogButtonBox, QMenu, QHeaderView, QTreeWidgetItem, QLineEdit, 18 QWidget,
19 QInputDialog 19 QDialogButtonBox,
20 QMenu,
21 QHeaderView,
22 QTreeWidgetItem,
23 QLineEdit,
24 QInputDialog,
20 ) 25 )
21 26
22 from EricWidgets.EricApplication import ericApp 27 from EricWidgets.EricApplication import ericApp
23 from EricWidgets import EricMessageBox 28 from EricWidgets import EricMessageBox
24 29
38 class GitStatusDialog(QWidget, Ui_GitStatusDialog): 43 class GitStatusDialog(QWidget, Ui_GitStatusDialog):
39 """ 44 """
40 Class implementing a dialog to show the output of the git status command 45 Class implementing a dialog to show the output of the git status command
41 process. 46 process.
42 """ 47 """
48
43 ConflictStates = ["AA", "AU", "DD", "DU", "UA", "UD", "UU"] 49 ConflictStates = ["AA", "AU", "DD", "DU", "UA", "UD", "UU"]
44 50
45 ConflictRole = Qt.ItemDataRole.UserRole 51 ConflictRole = Qt.ItemDataRole.UserRole
46 52
47 def __init__(self, vcs, parent=None): 53 def __init__(self, vcs, parent=None):
48 """ 54 """
49 Constructor 55 Constructor
50 56
51 @param vcs reference to the vcs object 57 @param vcs reference to the vcs object
52 @param parent parent widget (QWidget) 58 @param parent parent widget (QWidget)
53 """ 59 """
54 super().__init__(parent) 60 super().__init__(parent)
55 self.setupUi(self) 61 self.setupUi(self)
56 62
57 self.__toBeCommittedColumn = 0 63 self.__toBeCommittedColumn = 0
58 self.__statusWorkColumn = 1 64 self.__statusWorkColumn = 1
59 self.__statusIndexColumn = 2 65 self.__statusIndexColumn = 2
60 self.__pathColumn = 3 66 self.__pathColumn = 3
61 self.__lastColumn = self.statusList.columnCount() 67 self.__lastColumn = self.statusList.columnCount()
62 68
63 self.refreshButton = self.buttonBox.addButton( 69 self.refreshButton = self.buttonBox.addButton(
64 self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole) 70 self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole
65 self.refreshButton.setToolTip( 71 )
66 self.tr("Press to refresh the status display")) 72 self.refreshButton.setToolTip(self.tr("Press to refresh the status display"))
67 self.refreshButton.setEnabled(False) 73 self.refreshButton.setEnabled(False)
68 self.buttonBox.button( 74 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
69 QDialogButtonBox.StandardButton.Close).setEnabled(False) 75 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
70 self.buttonBox.button( 76
71 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
72
73 self.diff = None 77 self.diff = None
74 self.vcs = vcs 78 self.vcs = vcs
75 self.vcs.committed.connect(self.__committed) 79 self.vcs.committed.connect(self.__committed)
76 self.process = QProcess() 80 self.process = QProcess()
77 self.process.finished.connect(self.__procFinished) 81 self.process.finished.connect(self.__procFinished)
78 self.process.readyReadStandardOutput.connect(self.__readStdout) 82 self.process.readyReadStandardOutput.connect(self.__readStdout)
79 self.process.readyReadStandardError.connect(self.__readStderr) 83 self.process.readyReadStandardError.connect(self.__readStderr)
80 84
81 self.errorGroup.hide() 85 self.errorGroup.hide()
82 self.inputGroup.hide() 86 self.inputGroup.hide()
83 87
84 self.vDiffSplitter.setStretchFactor(0, 2) 88 self.vDiffSplitter.setStretchFactor(0, 2)
85 self.vDiffSplitter.setStretchFactor(0, 2) 89 self.vDiffSplitter.setStretchFactor(0, 2)
86 self.vDiffSplitter.setSizes([400, 400]) 90 self.vDiffSplitter.setSizes([400, 400])
87 self.__hDiffSplitterState = None 91 self.__hDiffSplitterState = None
88 self.__vDiffSplitterState = None 92 self.__vDiffSplitterState = None
89 93
90 self.statusList.headerItem().setText(self.__lastColumn, "") 94 self.statusList.headerItem().setText(self.__lastColumn, "")
91 self.statusList.header().setSortIndicator( 95 self.statusList.header().setSortIndicator(
92 self.__pathColumn, Qt.SortOrder.AscendingOrder) 96 self.__pathColumn, Qt.SortOrder.AscendingOrder
93 97 )
98
94 font = Preferences.getEditorOtherFonts("MonospacedFont") 99 font = Preferences.getEditorOtherFonts("MonospacedFont")
95 self.lDiffEdit.document().setDefaultFont(font) 100 self.lDiffEdit.document().setDefaultFont(font)
96 self.rDiffEdit.document().setDefaultFont(font) 101 self.rDiffEdit.document().setDefaultFont(font)
97 self.lDiffEdit.customContextMenuRequested.connect( 102 self.lDiffEdit.customContextMenuRequested.connect(self.__showLDiffContextMenu)
98 self.__showLDiffContextMenu) 103 self.rDiffEdit.customContextMenuRequested.connect(self.__showRDiffContextMenu)
99 self.rDiffEdit.customContextMenuRequested.connect( 104
100 self.__showRDiffContextMenu)
101
102 self.__lDiffMenu = QMenu() 105 self.__lDiffMenu = QMenu()
103 self.__stageLinesAct = self.__lDiffMenu.addAction( 106 self.__stageLinesAct = self.__lDiffMenu.addAction(
104 UI.PixmapCache.getIcon("vcsAdd"), 107 UI.PixmapCache.getIcon("vcsAdd"),
105 self.tr("Stage Selected Lines"), 108 self.tr("Stage Selected Lines"),
106 self.__stageHunkOrLines) 109 self.__stageHunkOrLines,
110 )
107 self.__revertLinesAct = self.__lDiffMenu.addAction( 111 self.__revertLinesAct = self.__lDiffMenu.addAction(
108 UI.PixmapCache.getIcon("vcsRevert"), 112 UI.PixmapCache.getIcon("vcsRevert"),
109 self.tr("Revert Selected Lines"), 113 self.tr("Revert Selected Lines"),
110 self.__revertHunkOrLines) 114 self.__revertHunkOrLines,
115 )
111 self.__stageHunkAct = self.__lDiffMenu.addAction( 116 self.__stageHunkAct = self.__lDiffMenu.addAction(
112 UI.PixmapCache.getIcon("vcsAdd"), 117 UI.PixmapCache.getIcon("vcsAdd"),
113 self.tr("Stage Hunk"), 118 self.tr("Stage Hunk"),
114 self.__stageHunkOrLines) 119 self.__stageHunkOrLines,
120 )
115 self.__revertHunkAct = self.__lDiffMenu.addAction( 121 self.__revertHunkAct = self.__lDiffMenu.addAction(
116 UI.PixmapCache.getIcon("vcsRevert"), 122 UI.PixmapCache.getIcon("vcsRevert"),
117 self.tr("Revert Hunk"), 123 self.tr("Revert Hunk"),
118 self.__revertHunkOrLines) 124 self.__revertHunkOrLines,
119 125 )
126
120 self.__rDiffMenu = QMenu() 127 self.__rDiffMenu = QMenu()
121 self.__unstageLinesAct = self.__rDiffMenu.addAction( 128 self.__unstageLinesAct = self.__rDiffMenu.addAction(
122 UI.PixmapCache.getIcon("vcsRemove"), 129 UI.PixmapCache.getIcon("vcsRemove"),
123 self.tr("Unstage Selected Lines"), 130 self.tr("Unstage Selected Lines"),
124 self.__unstageHunkOrLines) 131 self.__unstageHunkOrLines,
132 )
125 self.__unstageHunkAct = self.__rDiffMenu.addAction( 133 self.__unstageHunkAct = self.__rDiffMenu.addAction(
126 UI.PixmapCache.getIcon("vcsRemove"), 134 UI.PixmapCache.getIcon("vcsRemove"),
127 self.tr("Unstage Hunk"), 135 self.tr("Unstage Hunk"),
128 self.__unstageHunkOrLines) 136 self.__unstageHunkOrLines,
129 137 )
138
130 self.lDiffHighlighter = GitDiffHighlighter(self.lDiffEdit.document()) 139 self.lDiffHighlighter = GitDiffHighlighter(self.lDiffEdit.document())
131 self.rDiffHighlighter = GitDiffHighlighter(self.rDiffEdit.document()) 140 self.rDiffHighlighter = GitDiffHighlighter(self.rDiffEdit.document())
132 141
133 self.lDiffParser = None 142 self.lDiffParser = None
134 self.rDiffParser = None 143 self.rDiffParser = None
135 144
136 self.__selectedName = "" 145 self.__selectedName = ""
137 146
138 self.__diffGenerator = GitDiffGenerator(vcs, self) 147 self.__diffGenerator = GitDiffGenerator(vcs, self)
139 self.__diffGenerator.finished.connect(self.__generatorFinished) 148 self.__diffGenerator.finished.connect(self.__generatorFinished)
140 149
141 self.modifiedIndicators = [ 150 self.modifiedIndicators = [
142 self.tr('added'), 151 self.tr("added"),
143 self.tr('copied'), 152 self.tr("copied"),
144 self.tr('deleted'), 153 self.tr("deleted"),
145 self.tr('modified'), 154 self.tr("modified"),
146 self.tr('renamed'), 155 self.tr("renamed"),
147 ] 156 ]
148 self.modifiedOnlyIndicators = [ 157 self.modifiedOnlyIndicators = [
149 self.tr('modified'), 158 self.tr("modified"),
150 ] 159 ]
151 160
152 self.unversionedIndicators = [ 161 self.unversionedIndicators = [
153 self.tr('not tracked'), 162 self.tr("not tracked"),
154 ] 163 ]
155 164
156 self.missingIndicators = [ 165 self.missingIndicators = [
157 self.tr('deleted'), 166 self.tr("deleted"),
158 ] 167 ]
159 168
160 self.unmergedIndicators = [ 169 self.unmergedIndicators = [
161 self.tr('unmerged'), 170 self.tr("unmerged"),
162 ] 171 ]
163 172
164 self.status = { 173 self.status = {
165 ' ': self.tr("unmodified"), 174 " ": self.tr("unmodified"),
166 'A': self.tr('added'), 175 "A": self.tr("added"),
167 'C': self.tr('copied'), 176 "C": self.tr("copied"),
168 'D': self.tr('deleted'), 177 "D": self.tr("deleted"),
169 'M': self.tr('modified'), 178 "M": self.tr("modified"),
170 'R': self.tr('renamed'), 179 "R": self.tr("renamed"),
171 'U': self.tr('unmerged'), 180 "U": self.tr("unmerged"),
172 '?': self.tr('not tracked'), 181 "?": self.tr("not tracked"),
173 '!': self.tr('ignored'), 182 "!": self.tr("ignored"),
174 } 183 }
175 184
176 self.__ioEncoding = Preferences.getSystem("IOEncoding") 185 self.__ioEncoding = Preferences.getSystem("IOEncoding")
177 186
178 self.__initActionsMenu() 187 self.__initActionsMenu()
179 188
180 def __initActionsMenu(self): 189 def __initActionsMenu(self):
181 """ 190 """
182 Private method to initialize the actions menu. 191 Private method to initialize the actions menu.
183 """ 192 """
184 self.__actionsMenu = QMenu() 193 self.__actionsMenu = QMenu()
185 self.__actionsMenu.setTearOffEnabled(True) 194 self.__actionsMenu.setTearOffEnabled(True)
186 self.__actionsMenu.setToolTipsVisible(True) 195 self.__actionsMenu.setToolTipsVisible(True)
187 self.__actionsMenu.aboutToShow.connect(self.__showActionsMenu) 196 self.__actionsMenu.aboutToShow.connect(self.__showActionsMenu)
188 197
189 self.__commitAct = self.__actionsMenu.addAction( 198 self.__commitAct = self.__actionsMenu.addAction(
190 self.tr("Commit"), self.__commit) 199 self.tr("Commit"), self.__commit
200 )
191 self.__commitAct.setToolTip(self.tr("Commit the selected changes")) 201 self.__commitAct.setToolTip(self.tr("Commit the selected changes"))
192 self.__amendAct = self.__actionsMenu.addAction( 202 self.__amendAct = self.__actionsMenu.addAction(self.tr("Amend"), self.__amend)
193 self.tr("Amend"), self.__amend) 203 self.__amendAct.setToolTip(
194 self.__amendAct.setToolTip(self.tr( 204 self.tr("Amend the latest commit with the selected changes")
195 "Amend the latest commit with the selected changes")) 205 )
196 self.__commitSelectAct = self.__actionsMenu.addAction( 206 self.__commitSelectAct = self.__actionsMenu.addAction(
197 self.tr("Select all for commit"), self.__commitSelectAll) 207 self.tr("Select all for commit"), self.__commitSelectAll
208 )
198 self.__commitDeselectAct = self.__actionsMenu.addAction( 209 self.__commitDeselectAct = self.__actionsMenu.addAction(
199 self.tr("Unselect all from commit"), self.__commitDeselectAll) 210 self.tr("Unselect all from commit"), self.__commitDeselectAll
200 211 )
212
201 self.__actionsMenu.addSeparator() 213 self.__actionsMenu.addSeparator()
202 self.__addAct = self.__actionsMenu.addAction( 214 self.__addAct = self.__actionsMenu.addAction(self.tr("Add"), self.__add)
203 self.tr("Add"), self.__add)
204 self.__addAct.setToolTip(self.tr("Add the selected files")) 215 self.__addAct.setToolTip(self.tr("Add the selected files"))
205 self.__stageAct = self.__actionsMenu.addAction( 216 self.__stageAct = self.__actionsMenu.addAction(
206 self.tr("Stage changes"), self.__stage) 217 self.tr("Stage changes"), self.__stage
207 self.__stageAct.setToolTip(self.tr( 218 )
208 "Stages all changes of the selected files")) 219 self.__stageAct.setToolTip(self.tr("Stages all changes of the selected files"))
209 self.__unstageAct = self.__actionsMenu.addAction( 220 self.__unstageAct = self.__actionsMenu.addAction(
210 self.tr("Unstage changes"), self.__unstage) 221 self.tr("Unstage changes"), self.__unstage
211 self.__unstageAct.setToolTip(self.tr( 222 )
212 "Unstages all changes of the selected files")) 223 self.__unstageAct.setToolTip(
213 224 self.tr("Unstages all changes of the selected files")
225 )
226
214 self.__actionsMenu.addSeparator() 227 self.__actionsMenu.addSeparator()
215 228
216 self.__diffAct = self.__actionsMenu.addAction( 229 self.__diffAct = self.__actionsMenu.addAction(
217 self.tr("Differences"), self.__diff) 230 self.tr("Differences"), self.__diff
218 self.__diffAct.setToolTip(self.tr( 231 )
219 "Shows the differences of the selected entry in a" 232 self.__diffAct.setToolTip(
220 " separate dialog")) 233 self.tr(
234 "Shows the differences of the selected entry in a" " separate dialog"
235 )
236 )
221 self.__sbsDiffAct = self.__actionsMenu.addAction( 237 self.__sbsDiffAct = self.__actionsMenu.addAction(
222 self.tr("Differences Side-By-Side"), self.__sbsDiff) 238 self.tr("Differences Side-By-Side"), self.__sbsDiff
223 self.__sbsDiffAct.setToolTip(self.tr( 239 )
224 "Shows the differences of the selected entry side-by-side in" 240 self.__sbsDiffAct.setToolTip(
225 " a separate dialog")) 241 self.tr(
226 242 "Shows the differences of the selected entry side-by-side in"
243 " a separate dialog"
244 )
245 )
246
227 self.__actionsMenu.addSeparator() 247 self.__actionsMenu.addSeparator()
228 248
229 self.__revertAct = self.__actionsMenu.addAction( 249 self.__revertAct = self.__actionsMenu.addAction(
230 self.tr("Revert"), self.__revert) 250 self.tr("Revert"), self.__revert
231 self.__revertAct.setToolTip(self.tr( 251 )
232 "Reverts the changes of the selected files")) 252 self.__revertAct.setToolTip(
233 253 self.tr("Reverts the changes of the selected files")
254 )
255
234 self.__actionsMenu.addSeparator() 256 self.__actionsMenu.addSeparator()
235 257
236 self.__forgetAct = self.__actionsMenu.addAction( 258 self.__forgetAct = self.__actionsMenu.addAction(
237 self.tr("Forget Missing"), self.__forget) 259 self.tr("Forget Missing"), self.__forget
238 self.__forgetAct.setToolTip(self.tr( 260 )
239 "Forgets about the selected missing files")) 261 self.__forgetAct.setToolTip(self.tr("Forgets about the selected missing files"))
240 self.__restoreAct = self.__actionsMenu.addAction( 262 self.__restoreAct = self.__actionsMenu.addAction(
241 self.tr("Restore Missing"), self.__restoreMissing) 263 self.tr("Restore Missing"), self.__restoreMissing
242 self.__restoreAct.setToolTip(self.tr( 264 )
243 "Restores the selected missing files")) 265 self.__restoreAct.setToolTip(self.tr("Restores the selected missing files"))
244 266
245 self.__actionsMenu.addSeparator() 267 self.__actionsMenu.addSeparator()
246 268
247 self.__editAct = self.__actionsMenu.addAction( 269 self.__editAct = self.__actionsMenu.addAction(
248 self.tr("Edit Conflict"), self.__editConflict) 270 self.tr("Edit Conflict"), self.__editConflict
249 self.__editAct.setToolTip(self.tr( 271 )
250 "Edit the selected conflicting file")) 272 self.__editAct.setToolTip(self.tr("Edit the selected conflicting file"))
251 273
252 self.__actionsMenu.addSeparator() 274 self.__actionsMenu.addSeparator()
253 275
254 act = self.__actionsMenu.addAction( 276 act = self.__actionsMenu.addAction(
255 self.tr("Adjust column sizes"), self.__resizeColumns) 277 self.tr("Adjust column sizes"), self.__resizeColumns
256 act.setToolTip(self.tr( 278 )
257 "Adjusts the width of all columns to their contents")) 279 act.setToolTip(self.tr("Adjusts the width of all columns to their contents"))
258 280
259 self.actionsButton.setIcon( 281 self.actionsButton.setIcon(UI.PixmapCache.getIcon("actionsToolButton"))
260 UI.PixmapCache.getIcon("actionsToolButton"))
261 self.actionsButton.setMenu(self.__actionsMenu) 282 self.actionsButton.setMenu(self.__actionsMenu)
262 283
263 def closeEvent(self, e): 284 def closeEvent(self, e):
264 """ 285 """
265 Protected slot implementing a close event handler. 286 Protected slot implementing a close event handler.
266 287
267 @param e close event (QCloseEvent) 288 @param e close event (QCloseEvent)
268 """ 289 """
269 if ( 290 if (
270 self.process is not None and 291 self.process is not None
271 self.process.state() != QProcess.ProcessState.NotRunning 292 and self.process.state() != QProcess.ProcessState.NotRunning
272 ): 293 ):
273 self.process.terminate() 294 self.process.terminate()
274 QTimer.singleShot(2000, self.process.kill) 295 QTimer.singleShot(2000, self.process.kill)
275 self.process.waitForFinished(3000) 296 self.process.waitForFinished(3000)
276 297
298 self.vcs.getPlugin().setPreferences("StatusDialogGeometry", self.saveGeometry())
277 self.vcs.getPlugin().setPreferences( 299 self.vcs.getPlugin().setPreferences(
278 "StatusDialogGeometry", self.saveGeometry()) 300 "StatusDialogSplitterStates",
279 self.vcs.getPlugin().setPreferences( 301 [self.vDiffSplitter.saveState(), self.hDiffSplitter.saveState()],
280 "StatusDialogSplitterStates", [ 302 )
281 self.vDiffSplitter.saveState(), 303
282 self.hDiffSplitter.saveState()
283 ]
284 )
285
286 e.accept() 304 e.accept()
287 305
288 def show(self): 306 def show(self):
289 """ 307 """
290 Public slot to show the dialog. 308 Public slot to show the dialog.
291 """ 309 """
292 super().show() 310 super().show()
293 311
294 geom = self.vcs.getPlugin().getPreferences( 312 geom = self.vcs.getPlugin().getPreferences("StatusDialogGeometry")
295 "StatusDialogGeometry")
296 if geom.isEmpty(): 313 if geom.isEmpty():
297 s = QSize(900, 600) 314 s = QSize(900, 600)
298 self.resize(s) 315 self.resize(s)
299 else: 316 else:
300 self.restoreGeometry(geom) 317 self.restoreGeometry(geom)
301 318
302 states = self.vcs.getPlugin().getPreferences( 319 states = self.vcs.getPlugin().getPreferences("StatusDialogSplitterStates")
303 "StatusDialogSplitterStates")
304 if len(states) == 2: 320 if len(states) == 2:
305 # we have two splitters 321 # we have two splitters
306 self.vDiffSplitter.restoreState(states[0]) 322 self.vDiffSplitter.restoreState(states[0])
307 self.hDiffSplitter.restoreState(states[1]) 323 self.hDiffSplitter.restoreState(states[1])
308 324
309 def __resort(self): 325 def __resort(self):
310 """ 326 """
311 Private method to resort the tree. 327 Private method to resort the tree.
312 """ 328 """
313 self.statusList.sortItems( 329 self.statusList.sortItems(
314 self.statusList.sortColumn(), 330 self.statusList.sortColumn(), self.statusList.header().sortIndicatorOrder()
315 self.statusList.header().sortIndicatorOrder()) 331 )
316 332
317 def __resizeColumns(self): 333 def __resizeColumns(self):
318 """ 334 """
319 Private method to resize the list columns. 335 Private method to resize the list columns.
320 """ 336 """
321 self.statusList.header().resizeSections( 337 self.statusList.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents)
322 QHeaderView.ResizeMode.ResizeToContents)
323 self.statusList.header().setStretchLastSection(True) 338 self.statusList.header().setStretchLastSection(True)
324 339
325 def __generateItem(self, status, path): 340 def __generateItem(self, status, path):
326 """ 341 """
327 Private method to generate a status item in the status list. 342 Private method to generate a status item in the status list.
328 343
329 @param status status indicator (string) 344 @param status status indicator (string)
330 @param path path of the file or directory (string) 345 @param path path of the file or directory (string)
331 """ 346 """
332 statusWorkText = self.status[status[1]] 347 statusWorkText = self.status[status[1]]
333 statusIndexText = self.status[status[0]] 348 statusIndexText = self.status[status[0]]
334 itm = QTreeWidgetItem(self.statusList, [ 349 itm = QTreeWidgetItem(
335 "", 350 self.statusList,
336 statusWorkText, 351 [
337 statusIndexText, 352 "",
338 path, 353 statusWorkText,
339 ]) 354 statusIndexText,
340 355 path,
341 itm.setTextAlignment(self.__statusWorkColumn, 356 ],
342 Qt.AlignmentFlag.AlignHCenter) 357 )
343 itm.setTextAlignment(self.__statusIndexColumn, 358
344 Qt.AlignmentFlag.AlignHCenter) 359 itm.setTextAlignment(self.__statusWorkColumn, Qt.AlignmentFlag.AlignHCenter)
345 itm.setTextAlignment(self.__pathColumn, 360 itm.setTextAlignment(self.__statusIndexColumn, Qt.AlignmentFlag.AlignHCenter)
346 Qt.AlignmentFlag.AlignLeft) 361 itm.setTextAlignment(self.__pathColumn, Qt.AlignmentFlag.AlignLeft)
347 362
348 if ( 363 if (
349 status not in self.ConflictStates + ["??", "!!"] and 364 status not in self.ConflictStates + ["??", "!!"]
350 statusIndexText in self.modifiedIndicators 365 and statusIndexText in self.modifiedIndicators
351 ): 366 ):
352 itm.setFlags(itm.flags() | Qt.ItemFlag.ItemIsUserCheckable) 367 itm.setFlags(itm.flags() | Qt.ItemFlag.ItemIsUserCheckable)
353 itm.setCheckState(self.__toBeCommittedColumn, 368 itm.setCheckState(self.__toBeCommittedColumn, Qt.CheckState.Checked)
354 Qt.CheckState.Checked)
355 else: 369 else:
356 itm.setFlags(itm.flags() & ~Qt.ItemFlag.ItemIsUserCheckable) 370 itm.setFlags(itm.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
357 371
358 if statusWorkText not in self.__statusFilters: 372 if statusWorkText not in self.__statusFilters:
359 self.__statusFilters.append(statusWorkText) 373 self.__statusFilters.append(statusWorkText)
360 if statusIndexText not in self.__statusFilters: 374 if statusIndexText not in self.__statusFilters:
361 self.__statusFilters.append(statusIndexText) 375 self.__statusFilters.append(statusIndexText)
362 376
363 if status in self.ConflictStates: 377 if status in self.ConflictStates:
364 itm.setIcon(self.__statusWorkColumn, 378 itm.setIcon(
365 UI.PixmapCache.getIcon( 379 self.__statusWorkColumn,
366 os.path.join("VcsPlugins", "vcsGit", "icons", 380 UI.PixmapCache.getIcon(
367 "conflict.svg"))) 381 os.path.join("VcsPlugins", "vcsGit", "icons", "conflict.svg")
382 ),
383 )
368 itm.setData(0, self.ConflictRole, status in self.ConflictStates) 384 itm.setData(0, self.ConflictRole, status in self.ConflictStates)
369 385
370 def start(self, fn): 386 def start(self, fn):
371 """ 387 """
372 Public slot to start the git status command. 388 Public slot to start the git status command.
373 389
374 @param fn filename(s)/directoryname(s) to show the status of 390 @param fn filename(s)/directoryname(s) to show the status of
375 (string or list of strings) 391 (string or list of strings)
376 """ 392 """
377 self.errorGroup.hide() 393 self.errorGroup.hide()
378 self.intercept = False 394 self.intercept = False
379 self.args = fn 395 self.args = fn
380 396
381 self.__ioEncoding = Preferences.getSystem("IOEncoding") 397 self.__ioEncoding = Preferences.getSystem("IOEncoding")
382 398
383 self.statusFilterCombo.clear() 399 self.statusFilterCombo.clear()
384 self.__statusFilters = [] 400 self.__statusFilters = []
385 self.statusList.clear() 401 self.statusList.clear()
386 402
387 self.setWindowTitle(self.tr('Git Status')) 403 self.setWindowTitle(self.tr("Git Status"))
388 404
389 args = self.vcs.initCommand("status") 405 args = self.vcs.initCommand("status")
390 args.append('--porcelain') 406 args.append("--porcelain")
391 args.append("--") 407 args.append("--")
392 if isinstance(fn, list): 408 if isinstance(fn, list):
393 self.dname, fnames = self.vcs.splitPathList(fn) 409 self.dname, fnames = self.vcs.splitPathList(fn)
394 self.vcs.addArguments(args, fn) 410 self.vcs.addArguments(args, fn)
395 else: 411 else:
396 self.dname, fname = self.vcs.splitPath(fn) 412 self.dname, fname = self.vcs.splitPath(fn)
397 args.append(fn) 413 args.append(fn)
398 414
399 # find the root of the repo 415 # find the root of the repo
400 self.__repodir = self.dname 416 self.__repodir = self.dname
401 while not os.path.isdir( 417 while not os.path.isdir(os.path.join(self.__repodir, self.vcs.adminDir)):
402 os.path.join(self.__repodir, self.vcs.adminDir)):
403 self.__repodir = os.path.dirname(self.__repodir) 418 self.__repodir = os.path.dirname(self.__repodir)
404 if os.path.splitdrive(self.__repodir)[1] == os.sep: 419 if os.path.splitdrive(self.__repodir)[1] == os.sep:
405 return 420 return
406 421
407 self.process.kill() 422 self.process.kill()
408 self.process.setWorkingDirectory(self.__repodir) 423 self.process.setWorkingDirectory(self.__repodir)
409 424
410 self.process.start('git', args) 425 self.process.start("git", args)
411 procStarted = self.process.waitForStarted(5000) 426 procStarted = self.process.waitForStarted(5000)
412 if not procStarted: 427 if not procStarted:
413 self.inputGroup.setEnabled(False) 428 self.inputGroup.setEnabled(False)
414 self.inputGroup.hide() 429 self.inputGroup.hide()
415 EricMessageBox.critical( 430 EricMessageBox.critical(
416 self, 431 self,
417 self.tr('Process Generation Error'), 432 self.tr("Process Generation Error"),
418 self.tr( 433 self.tr(
419 'The process {0} could not be started. ' 434 "The process {0} could not be started. "
420 'Ensure, that it is in the search path.' 435 "Ensure, that it is in the search path."
421 ).format('git')) 436 ).format("git"),
437 )
422 else: 438 else:
423 self.buttonBox.button( 439 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(
424 QDialogButtonBox.StandardButton.Close).setEnabled(False) 440 False
425 self.buttonBox.button( 441 )
426 QDialogButtonBox.StandardButton.Cancel).setEnabled(True) 442 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(
427 self.buttonBox.button( 443 True
428 QDialogButtonBox.StandardButton.Cancel).setDefault(True) 444 )
429 445 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(
446 True
447 )
448
430 self.refreshButton.setEnabled(False) 449 self.refreshButton.setEnabled(False)
431 450
432 def __finish(self): 451 def __finish(self):
433 """ 452 """
434 Private slot called when the process finished or the user pressed 453 Private slot called when the process finished or the user pressed
435 the button. 454 the button.
436 """ 455 """
437 if ( 456 if (
438 self.process is not None and 457 self.process is not None
439 self.process.state() != QProcess.ProcessState.NotRunning 458 and self.process.state() != QProcess.ProcessState.NotRunning
440 ): 459 ):
441 self.process.terminate() 460 self.process.terminate()
442 QTimer.singleShot(2000, self.process.kill) 461 QTimer.singleShot(2000, self.process.kill)
443 self.process.waitForFinished(3000) 462 self.process.waitForFinished(3000)
444 463
445 self.inputGroup.setEnabled(False) 464 self.inputGroup.setEnabled(False)
446 self.inputGroup.hide() 465 self.inputGroup.hide()
447 self.refreshButton.setEnabled(True) 466 self.refreshButton.setEnabled(True)
448 467
449 self.buttonBox.button( 468 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True)
450 QDialogButtonBox.StandardButton.Close).setEnabled(True) 469 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
451 self.buttonBox.button( 470 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
452 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) 471 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setFocus(
453 self.buttonBox.button( 472 Qt.FocusReason.OtherFocusReason
454 QDialogButtonBox.StandardButton.Close).setDefault(True) 473 )
455 self.buttonBox.button( 474
456 QDialogButtonBox.StandardButton.Close).setFocus(
457 Qt.FocusReason.OtherFocusReason)
458
459 self.__statusFilters.sort() 475 self.__statusFilters.sort()
460 self.__statusFilters.insert(0, "<{0}>".format(self.tr("all"))) 476 self.__statusFilters.insert(0, "<{0}>".format(self.tr("all")))
461 self.statusFilterCombo.addItems(self.__statusFilters) 477 self.statusFilterCombo.addItems(self.__statusFilters)
462 478
463 self.__resort() 479 self.__resort()
464 self.__resizeColumns() 480 self.__resizeColumns()
465 481
466 self.__refreshDiff() 482 self.__refreshDiff()
467 483
468 def on_buttonBox_clicked(self, button): 484 def on_buttonBox_clicked(self, button):
469 """ 485 """
470 Private slot called by a button of the button box clicked. 486 Private slot called by a button of the button box clicked.
471 487
472 @param button button that was clicked (QAbstractButton) 488 @param button button that was clicked (QAbstractButton)
473 """ 489 """
474 if button == self.buttonBox.button( 490 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close):
475 QDialogButtonBox.StandardButton.Close
476 ):
477 self.close() 491 self.close()
478 elif button == self.buttonBox.button( 492 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel):
479 QDialogButtonBox.StandardButton.Cancel
480 ):
481 self.__finish() 493 self.__finish()
482 elif button == self.refreshButton: 494 elif button == self.refreshButton:
483 self.on_refreshButton_clicked() 495 self.on_refreshButton_clicked()
484 496
485 def __procFinished(self, exitCode, exitStatus): 497 def __procFinished(self, exitCode, exitStatus):
486 """ 498 """
487 Private slot connected to the finished signal. 499 Private slot connected to the finished signal.
488 500
489 @param exitCode exit code of the process (integer) 501 @param exitCode exit code of the process (integer)
490 @param exitStatus exit status of the process (QProcess.ExitStatus) 502 @param exitStatus exit status of the process (QProcess.ExitStatus)
491 """ 503 """
492 self.__finish() 504 self.__finish()
493 505
494 def __readStdout(self): 506 def __readStdout(self):
495 """ 507 """
496 Private slot to handle the readyReadStandardOutput signal. 508 Private slot to handle the readyReadStandardOutput signal.
497 509
498 It reads the output of the process, formats it and inserts it into 510 It reads the output of the process, formats it and inserts it into
499 the contents pane. 511 the contents pane.
500 """ 512 """
501 if self.process is not None: 513 if self.process is not None:
502 self.process.setReadChannel(QProcess.ProcessChannel.StandardOutput) 514 self.process.setReadChannel(QProcess.ProcessChannel.StandardOutput)
503 515
504 while self.process.canReadLine(): 516 while self.process.canReadLine():
505 line = str(self.process.readLine(), self.__ioEncoding, 517 line = str(self.process.readLine(), self.__ioEncoding, "replace")
506 'replace') 518
507
508 status = line[:2] 519 status = line[:2]
509 path = line[3:].strip().split(" -> ")[-1].strip('"') 520 path = line[3:].strip().split(" -> ")[-1].strip('"')
510 self.__generateItem(status, path) 521 self.__generateItem(status, path)
511 522
512 def __readStderr(self): 523 def __readStderr(self):
513 """ 524 """
514 Private slot to handle the readyReadStandardError signal. 525 Private slot to handle the readyReadStandardError signal.
515 526
516 It reads the error output of the process and inserts it into the 527 It reads the error output of the process and inserts it into the
517 error pane. 528 error pane.
518 """ 529 """
519 if self.process is not None: 530 if self.process is not None:
520 s = str(self.process.readAllStandardError(), 531 s = str(self.process.readAllStandardError(), self.__ioEncoding, "replace")
521 self.__ioEncoding, 'replace')
522 self.errorGroup.show() 532 self.errorGroup.show()
523 self.errors.insertPlainText(s) 533 self.errors.insertPlainText(s)
524 self.errors.ensureCursorVisible() 534 self.errors.ensureCursorVisible()
525 535
526 # show input in case the process asked for some input 536 # show input in case the process asked for some input
527 self.inputGroup.setEnabled(True) 537 self.inputGroup.setEnabled(True)
528 self.inputGroup.show() 538 self.inputGroup.show()
529 539
530 def on_passwordCheckBox_toggled(self, isOn): 540 def on_passwordCheckBox_toggled(self, isOn):
531 """ 541 """
532 Private slot to handle the password checkbox toggled. 542 Private slot to handle the password checkbox toggled.
533 543
534 @param isOn flag indicating the status of the check box (boolean) 544 @param isOn flag indicating the status of the check box (boolean)
535 """ 545 """
536 if isOn: 546 if isOn:
537 self.input.setEchoMode(QLineEdit.EchoMode.Password) 547 self.input.setEchoMode(QLineEdit.EchoMode.Password)
538 else: 548 else:
539 self.input.setEchoMode(QLineEdit.EchoMode.Normal) 549 self.input.setEchoMode(QLineEdit.EchoMode.Normal)
540 550
541 @pyqtSlot() 551 @pyqtSlot()
542 def on_sendButton_clicked(self): 552 def on_sendButton_clicked(self):
543 """ 553 """
544 Private slot to send the input to the git process. 554 Private slot to send the input to the git process.
545 """ 555 """
546 inputTxt = self.input.text() 556 inputTxt = self.input.text()
547 inputTxt += os.linesep 557 inputTxt += os.linesep
548 558
549 if self.passwordCheckBox.isChecked(): 559 if self.passwordCheckBox.isChecked():
550 self.errors.insertPlainText(os.linesep) 560 self.errors.insertPlainText(os.linesep)
551 self.errors.ensureCursorVisible() 561 self.errors.ensureCursorVisible()
552 else: 562 else:
553 self.errors.insertPlainText(inputTxt) 563 self.errors.insertPlainText(inputTxt)
554 self.errors.ensureCursorVisible() 564 self.errors.ensureCursorVisible()
555 565
556 self.process.write(strToQByteArray(inputTxt)) 566 self.process.write(strToQByteArray(inputTxt))
557 567
558 self.passwordCheckBox.setChecked(False) 568 self.passwordCheckBox.setChecked(False)
559 self.input.clear() 569 self.input.clear()
560 570
561 def on_input_returnPressed(self): 571 def on_input_returnPressed(self):
562 """ 572 """
563 Private slot to handle the press of the return key in the input field. 573 Private slot to handle the press of the return key in the input field.
564 """ 574 """
565 self.intercept = True 575 self.intercept = True
566 self.on_sendButton_clicked() 576 self.on_sendButton_clicked()
567 577
568 def keyPressEvent(self, evt): 578 def keyPressEvent(self, evt):
569 """ 579 """
570 Protected slot to handle a key press event. 580 Protected slot to handle a key press event.
571 581
572 @param evt the key press event (QKeyEvent) 582 @param evt the key press event (QKeyEvent)
573 """ 583 """
574 if self.intercept: 584 if self.intercept:
575 self.intercept = False 585 self.intercept = False
576 evt.accept() 586 evt.accept()
577 return 587 return
578 super().keyPressEvent(evt) 588 super().keyPressEvent(evt)
579 589
580 @pyqtSlot() 590 @pyqtSlot()
581 def on_refreshButton_clicked(self): 591 def on_refreshButton_clicked(self):
582 """ 592 """
583 Private slot to refresh the status display. 593 Private slot to refresh the status display.
584 """ 594 """
585 selectedItems = self.statusList.selectedItems() 595 selectedItems = self.statusList.selectedItems()
586 if len(selectedItems) == 1: 596 if len(selectedItems) == 1:
587 self.__selectedName = selectedItems[0].text(self.__pathColumn) 597 self.__selectedName = selectedItems[0].text(self.__pathColumn)
588 else: 598 else:
589 self.__selectedName = "" 599 self.__selectedName = ""
590 600
591 self.start(self.args) 601 self.start(self.args)
592 602
593 @pyqtSlot(int) 603 @pyqtSlot(int)
594 def on_statusFilterCombo_activated(self, index): 604 def on_statusFilterCombo_activated(self, index):
595 """ 605 """
596 Private slot to react to the selection of a status filter. 606 Private slot to react to the selection of a status filter.
597 607
598 @param index index of the selected entry 608 @param index index of the selected entry
599 @type int 609 @type int
600 """ 610 """
601 txt = self.statusFilterCombo.itemText(index) 611 txt = self.statusFilterCombo.itemText(index)
602 if txt == "<{0}>".format(self.tr("all")): 612 if txt == "<{0}>".format(self.tr("all")):
605 topItem.setHidden(False) 615 topItem.setHidden(False)
606 else: 616 else:
607 for topIndex in range(self.statusList.topLevelItemCount()): 617 for topIndex in range(self.statusList.topLevelItemCount()):
608 topItem = self.statusList.topLevelItem(topIndex) 618 topItem = self.statusList.topLevelItem(topIndex)
609 topItem.setHidden( 619 topItem.setHidden(
610 topItem.text(self.__statusWorkColumn) != txt and 620 topItem.text(self.__statusWorkColumn) != txt
611 topItem.text(self.__statusIndexColumn) != txt 621 and topItem.text(self.__statusIndexColumn) != txt
612 ) 622 )
613 623
614 @pyqtSlot() 624 @pyqtSlot()
615 def on_statusList_itemSelectionChanged(self): 625 def on_statusList_itemSelectionChanged(self):
616 """ 626 """
617 Private slot to act upon changes of selected items. 627 Private slot to act upon changes of selected items.
618 """ 628 """
619 self.__generateDiffs() 629 self.__generateDiffs()
620 630
621 ########################################################################### 631 ###########################################################################
622 ## Menu handling methods 632 ## Menu handling methods
623 ########################################################################### 633 ###########################################################################
624 634
625 def __showActionsMenu(self): 635 def __showActionsMenu(self):
626 """ 636 """
627 Private slot to prepare the actions button menu before it is shown. 637 Private slot to prepare the actions button menu before it is shown.
628 """ 638 """
629 modified = len(self.__getModifiedItems()) 639 modified = len(self.__getModifiedItems())
647 self.__sbsDiffAct.setEnabled(modifiedOnly == 1) 657 self.__sbsDiffAct.setEnabled(modifiedOnly == 1)
648 self.__revertAct.setEnabled(stageable) 658 self.__revertAct.setEnabled(stageable)
649 self.__forgetAct.setEnabled(missing) 659 self.__forgetAct.setEnabled(missing)
650 self.__restoreAct.setEnabled(missing) 660 self.__restoreAct.setEnabled(missing)
651 self.__editAct.setEnabled(conflicting == 1) 661 self.__editAct.setEnabled(conflicting == 1)
652 662
653 def __amend(self): 663 def __amend(self):
654 """ 664 """
655 Private slot to handle the Amend context menu entry. 665 Private slot to handle the Amend context menu entry.
656 """ 666 """
657 self.__commit(amend=True) 667 self.__commit(amend=True)
658 668
659 def __commit(self, amend=False): 669 def __commit(self, amend=False):
660 """ 670 """
661 Private slot to handle the Commit context menu entry. 671 Private slot to handle the Commit context menu entry.
662 672
663 @param amend flag indicating to perform an amend operation (boolean) 673 @param amend flag indicating to perform an amend operation (boolean)
664 """ 674 """
665 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) 675 names = [
666 for itm in self.__getCommitableItems()] 676 os.path.join(self.dname, itm.text(self.__pathColumn))
677 for itm in self.__getCommitableItems()
678 ]
667 if not names: 679 if not names:
668 EricMessageBox.information( 680 EricMessageBox.information(
669 self, 681 self,
670 self.tr("Commit"), 682 self.tr("Commit"),
671 self.tr("""There are no entries selected to be""" 683 self.tr("""There are no entries selected to be""" """ committed."""),
672 """ committed.""")) 684 )
673 return 685 return
674 686
675 if Preferences.getVCS("AutoSaveFiles"): 687 if Preferences.getVCS("AutoSaveFiles"):
676 vm = ericApp().getObject("ViewManager") 688 vm = ericApp().getObject("ViewManager")
677 for name in names: 689 for name in names:
678 vm.saveEditor(name) 690 vm.saveEditor(name)
679 self.vcs.vcsCommit(names, commitAll=False, amend=amend) 691 self.vcs.vcsCommit(names, commitAll=False, amend=amend)
680 # staged changes 692 # staged changes
681 693
682 def __committed(self): 694 def __committed(self):
683 """ 695 """
684 Private slot called after the commit has finished. 696 Private slot called after the commit has finished.
685 """ 697 """
686 if self.isVisible(): 698 if self.isVisible():
687 self.on_refreshButton_clicked() 699 self.on_refreshButton_clicked()
688 self.vcs.checkVCSStatus() 700 self.vcs.checkVCSStatus()
689 701
690 def __commitSelectAll(self): 702 def __commitSelectAll(self):
691 """ 703 """
692 Private slot to select all entries for commit. 704 Private slot to select all entries for commit.
693 """ 705 """
694 self.__commitSelect(True) 706 self.__commitSelect(True)
695 707
696 def __commitDeselectAll(self): 708 def __commitDeselectAll(self):
697 """ 709 """
698 Private slot to deselect all entries from commit. 710 Private slot to deselect all entries from commit.
699 """ 711 """
700 self.__commitSelect(False) 712 self.__commitSelect(False)
701 713
702 def __add(self): 714 def __add(self):
703 """ 715 """
704 Private slot to handle the Add context menu entry. 716 Private slot to handle the Add context menu entry.
705 """ 717 """
706 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) 718 names = [
707 for itm in self.__getUnversionedItems()] 719 os.path.join(self.dname, itm.text(self.__pathColumn))
720 for itm in self.__getUnversionedItems()
721 ]
708 if not names: 722 if not names:
709 EricMessageBox.information( 723 EricMessageBox.information(
710 self, 724 self,
711 self.tr("Add"), 725 self.tr("Add"),
712 self.tr("""There are no unversioned entries""" 726 self.tr(
713 """ available/selected.""")) 727 """There are no unversioned entries""" """ available/selected."""
728 ),
729 )
714 return 730 return
715 731
716 self.vcs.vcsAdd(names) 732 self.vcs.vcsAdd(names)
717 self.on_refreshButton_clicked() 733 self.on_refreshButton_clicked()
718 734
719 project = ericApp().getObject("Project") 735 project = ericApp().getObject("Project")
720 for name in names: 736 for name in names:
721 project.getModel().updateVCSStatus(name) 737 project.getModel().updateVCSStatus(name)
722 self.vcs.checkVCSStatus() 738 self.vcs.checkVCSStatus()
723 739
724 def __stage(self): 740 def __stage(self):
725 """ 741 """
726 Private slot to handle the Stage context menu entry. 742 Private slot to handle the Stage context menu entry.
727 """ 743 """
728 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) 744 names = [
729 for itm in self.__getStageableItems()] 745 os.path.join(self.dname, itm.text(self.__pathColumn))
746 for itm in self.__getStageableItems()
747 ]
730 if not names: 748 if not names:
731 EricMessageBox.information( 749 EricMessageBox.information(
732 self, 750 self,
733 self.tr("Stage"), 751 self.tr("Stage"),
734 self.tr("""There are no stageable entries""" 752 self.tr(
735 """ available/selected.""")) 753 """There are no stageable entries""" """ available/selected."""
754 ),
755 )
736 return 756 return
737 757
738 self.vcs.vcsAdd(names) 758 self.vcs.vcsAdd(names)
739 self.on_refreshButton_clicked() 759 self.on_refreshButton_clicked()
740 760
741 project = ericApp().getObject("Project") 761 project = ericApp().getObject("Project")
742 for name in names: 762 for name in names:
743 project.getModel().updateVCSStatus(name) 763 project.getModel().updateVCSStatus(name)
744 self.vcs.checkVCSStatus() 764 self.vcs.checkVCSStatus()
745 765
746 def __unstage(self): 766 def __unstage(self):
747 """ 767 """
748 Private slot to handle the Unstage context menu entry. 768 Private slot to handle the Unstage context menu entry.
749 """ 769 """
750 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) 770 names = [
751 for itm in self.__getUnstageableItems()] 771 os.path.join(self.dname, itm.text(self.__pathColumn))
772 for itm in self.__getUnstageableItems()
773 ]
752 if not names: 774 if not names:
753 EricMessageBox.information( 775 EricMessageBox.information(
754 self, 776 self,
755 self.tr("Unstage"), 777 self.tr("Unstage"),
756 self.tr("""There are no unstageable entries""" 778 self.tr(
757 """ available/selected.""")) 779 """There are no unstageable entries""" """ available/selected."""
780 ),
781 )
758 return 782 return
759 783
760 self.vcs.gitUnstage(names) 784 self.vcs.gitUnstage(names)
761 self.on_refreshButton_clicked() 785 self.on_refreshButton_clicked()
762 786
763 project = ericApp().getObject("Project") 787 project = ericApp().getObject("Project")
764 for name in names: 788 for name in names:
765 project.getModel().updateVCSStatus(name) 789 project.getModel().updateVCSStatus(name)
766 self.vcs.checkVCSStatus() 790 self.vcs.checkVCSStatus()
767 791
768 def __forget(self): 792 def __forget(self):
769 """ 793 """
770 Private slot to handle the Forget Missing context menu entry. 794 Private slot to handle the Forget Missing context menu entry.
771 """ 795 """
772 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) 796 names = [
773 for itm in self.__getMissingItems()] 797 os.path.join(self.dname, itm.text(self.__pathColumn))
798 for itm in self.__getMissingItems()
799 ]
774 if not names: 800 if not names:
775 EricMessageBox.information( 801 EricMessageBox.information(
776 self, 802 self,
777 self.tr("Forget Missing"), 803 self.tr("Forget Missing"),
778 self.tr("""There are no missing entries""" 804 self.tr("""There are no missing entries""" """ available/selected."""),
779 """ available/selected.""")) 805 )
780 return 806 return
781 807
782 self.vcs.vcsRemove(names, stageOnly=True) 808 self.vcs.vcsRemove(names, stageOnly=True)
783 self.on_refreshButton_clicked() 809 self.on_refreshButton_clicked()
784 810
785 def __revert(self): 811 def __revert(self):
786 """ 812 """
787 Private slot to handle the Revert context menu entry. 813 Private slot to handle the Revert context menu entry.
788 """ 814 """
789 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) 815 names = [
790 for itm in self.__getStageableItems()] 816 os.path.join(self.dname, itm.text(self.__pathColumn))
817 for itm in self.__getStageableItems()
818 ]
791 if not names: 819 if not names:
792 EricMessageBox.information( 820 EricMessageBox.information(
793 self, 821 self,
794 self.tr("Revert"), 822 self.tr("Revert"),
795 self.tr("""There are no uncommitted, unstaged changes""" 823 self.tr(
796 """ available/selected.""")) 824 """There are no uncommitted, unstaged changes"""
825 """ available/selected."""
826 ),
827 )
797 return 828 return
798 829
799 self.vcs.vcsRevert(names) 830 self.vcs.vcsRevert(names)
800 self.raise_() 831 self.raise_()
801 self.activateWindow() 832 self.activateWindow()
808 839
809 def __restoreMissing(self): 840 def __restoreMissing(self):
810 """ 841 """
811 Private slot to handle the Restore Missing context menu entry. 842 Private slot to handle the Restore Missing context menu entry.
812 """ 843 """
813 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) 844 names = [
814 for itm in self.__getMissingItems()] 845 os.path.join(self.dname, itm.text(self.__pathColumn))
846 for itm in self.__getMissingItems()
847 ]
815 if not names: 848 if not names:
816 EricMessageBox.information( 849 EricMessageBox.information(
817 self, 850 self,
818 self.tr("Restore Missing"), 851 self.tr("Restore Missing"),
819 self.tr("""There are no missing entries""" 852 self.tr("""There are no missing entries""" """ available/selected."""),
820 """ available/selected.""")) 853 )
821 return 854 return
822 855
823 self.vcs.vcsRevert(names) 856 self.vcs.vcsRevert(names)
824 self.on_refreshButton_clicked() 857 self.on_refreshButton_clicked()
825 self.vcs.checkVCSStatus() 858 self.vcs.checkVCSStatus()
826 859
827 def __editConflict(self): 860 def __editConflict(self):
828 """ 861 """
829 Private slot to handle the Edit Conflict context menu entry. 862 Private slot to handle the Edit Conflict context menu entry.
830 """ 863 """
831 itm = self.__getConflictingItems()[0] 864 itm = self.__getConflictingItems()[0]
835 868
836 def __diff(self): 869 def __diff(self):
837 """ 870 """
838 Private slot to handle the Diff context menu entry. 871 Private slot to handle the Diff context menu entry.
839 """ 872 """
840 namesW = [os.path.join(self.dname, itm.text(self.__pathColumn)) 873 namesW = [
841 for itm in self.__getStageableItems()] 874 os.path.join(self.dname, itm.text(self.__pathColumn))
842 namesS = [os.path.join(self.dname, itm.text(self.__pathColumn)) 875 for itm in self.__getStageableItems()
843 for itm in self.__getUnstageableItems()] 876 ]
877 namesS = [
878 os.path.join(self.dname, itm.text(self.__pathColumn))
879 for itm in self.__getUnstageableItems()
880 ]
844 if not namesW and not namesS: 881 if not namesW and not namesS:
845 EricMessageBox.information( 882 EricMessageBox.information(
846 self, 883 self,
847 self.tr("Differences"), 884 self.tr("Differences"),
848 self.tr("""There are no uncommitted changes""" 885 self.tr(
849 """ available/selected.""")) 886 """There are no uncommitted changes""" """ available/selected."""
887 ),
888 )
850 return 889 return
851 890
852 diffMode = "work2stage2repo" 891 diffMode = "work2stage2repo"
853 names = namesW + namesS 892 names = namesW + namesS
854 893
855 if self.diff is None: 894 if self.diff is None:
856 from .GitDiffDialog import GitDiffDialog 895 from .GitDiffDialog import GitDiffDialog
896
857 self.diff = GitDiffDialog(self.vcs) 897 self.diff = GitDiffDialog(self.vcs)
858 self.diff.show() 898 self.diff.show()
859 self.diff.start(names, diffMode=diffMode, refreshable=True) 899 self.diff.start(names, diffMode=diffMode, refreshable=True)
860 900
861 def __sbsDiff(self): 901 def __sbsDiff(self):
862 """ 902 """
863 Private slot to handle the Side-By-Side Diff context menu entry. 903 Private slot to handle the Side-By-Side Diff context menu entry.
864 """ 904 """
865 itm = self.__getModifiedOnlyItems()[0] 905 itm = self.__getModifiedOnlyItems()[0]
866 workModified = (itm.text(self.__statusWorkColumn) in 906 workModified = itm.text(self.__statusWorkColumn) in self.modifiedOnlyIndicators
867 self.modifiedOnlyIndicators) 907 stageModified = (
868 stageModified = (itm.text(self.__statusIndexColumn) in 908 itm.text(self.__statusIndexColumn) in self.modifiedOnlyIndicators
869 self.modifiedOnlyIndicators) 909 )
870 names = [os.path.join(self.dname, itm.text(self.__pathColumn))] 910 names = [os.path.join(self.dname, itm.text(self.__pathColumn))]
871 911
872 if workModified and stageModified: 912 if workModified and stageModified:
873 # select from all three variants 913 # select from all three variants
874 messages = [ 914 messages = [
875 self.tr("Working Tree to Staging Area"), 915 self.tr("Working Tree to Staging Area"),
876 self.tr("Staging Area to HEAD Commit"), 916 self.tr("Staging Area to HEAD Commit"),
879 result, ok = QInputDialog.getItem( 919 result, ok = QInputDialog.getItem(
880 None, 920 None,
881 self.tr("Differences Side-by-Side"), 921 self.tr("Differences Side-by-Side"),
882 self.tr("Select the compare method."), 922 self.tr("Select the compare method."),
883 messages, 923 messages,
884 0, False) 924 0,
925 False,
926 )
885 if not ok: 927 if not ok:
886 return 928 return
887 929
888 if result == messages[0]: 930 if result == messages[0]:
889 revisions = ["", ""] 931 revisions = ["", ""]
890 elif result == messages[1]: 932 elif result == messages[1]:
891 revisions = ["HEAD", "Stage"] 933 revisions = ["HEAD", "Stage"]
892 else: 934 else:
900 result, ok = QInputDialog.getItem( 942 result, ok = QInputDialog.getItem(
901 None, 943 None,
902 self.tr("Differences Side-by-Side"), 944 self.tr("Differences Side-by-Side"),
903 self.tr("Select the compare method."), 945 self.tr("Select the compare method."),
904 messages, 946 messages,
905 0, False) 947 0,
948 False,
949 )
906 if not ok: 950 if not ok:
907 return 951 return
908 952
909 if result == messages[0]: 953 if result == messages[0]:
910 revisions = ["", ""] 954 revisions = ["", ""]
911 else: 955 else:
912 revisions = ["HEAD", ""] 956 revisions = ["HEAD", ""]
913 else: 957 else:
914 revisions = ["HEAD", "Stage"] 958 revisions = ["HEAD", "Stage"]
915 959
916 self.vcs.vcsSbsDiff(names[0], revisions=revisions) 960 self.vcs.vcsSbsDiff(names[0], revisions=revisions)
917 961
918 def __getCommitableItems(self): 962 def __getCommitableItems(self):
919 """ 963 """
920 Private method to retrieve all entries the user wants to commit. 964 Private method to retrieve all entries the user wants to commit.
921 965
922 @return list of all items, the user has checked 966 @return list of all items, the user has checked
923 """ 967 """
924 commitableItems = [] 968 commitableItems = []
925 for index in range(self.statusList.topLevelItemCount()): 969 for index in range(self.statusList.topLevelItemCount()):
926 itm = self.statusList.topLevelItem(index) 970 itm = self.statusList.topLevelItem(index)
927 if ( 971 if itm.checkState(self.__toBeCommittedColumn) == Qt.CheckState.Checked:
928 itm.checkState(self.__toBeCommittedColumn) ==
929 Qt.CheckState.Checked
930 ):
931 commitableItems.append(itm) 972 commitableItems.append(itm)
932 return commitableItems 973 return commitableItems
933 974
934 def __getCommitableUnselectedItems(self): 975 def __getCommitableUnselectedItems(self):
935 """ 976 """
936 Private method to retrieve all entries the user may commit but hasn't 977 Private method to retrieve all entries the user may commit but hasn't
937 selected. 978 selected.
938 979
939 @return list of all items, the user has not checked 980 @return list of all items, the user has not checked
940 """ 981 """
941 items = [] 982 items = []
942 for index in range(self.statusList.topLevelItemCount()): 983 for index in range(self.statusList.topLevelItemCount()):
943 itm = self.statusList.topLevelItem(index) 984 itm = self.statusList.topLevelItem(index)
944 if ( 985 if (
945 (itm.flags() & Qt.ItemFlag.ItemIsUserCheckable == 986 itm.flags() & Qt.ItemFlag.ItemIsUserCheckable
946 Qt.ItemFlag.ItemIsUserCheckable) and 987 == Qt.ItemFlag.ItemIsUserCheckable
947 itm.checkState(self.__toBeCommittedColumn) == 988 ) and itm.checkState(self.__toBeCommittedColumn) == Qt.CheckState.Unchecked:
948 Qt.CheckState.Unchecked
949 ):
950 items.append(itm) 989 items.append(itm)
951 return items 990 return items
952 991
953 def __getModifiedItems(self): 992 def __getModifiedItems(self):
954 """ 993 """
955 Private method to retrieve all entries, that have a modified status. 994 Private method to retrieve all entries, that have a modified status.
956 995
957 @return list of all items with a modified status 996 @return list of all items with a modified status
958 """ 997 """
959 modifiedItems = [] 998 modifiedItems = []
960 for itm in self.statusList.selectedItems(): 999 for itm in self.statusList.selectedItems():
961 if (itm.text(self.__statusWorkColumn) in 1000 if (
962 self.modifiedIndicators or 1001 itm.text(self.__statusWorkColumn) in self.modifiedIndicators
963 itm.text(self.__statusIndexColumn) in 1002 or itm.text(self.__statusIndexColumn) in self.modifiedIndicators
964 self.modifiedIndicators): 1003 ):
965 modifiedItems.append(itm) 1004 modifiedItems.append(itm)
966 return modifiedItems 1005 return modifiedItems
967 1006
968 def __getModifiedOnlyItems(self): 1007 def __getModifiedOnlyItems(self):
969 """ 1008 """
970 Private method to retrieve all entries, that have a modified status. 1009 Private method to retrieve all entries, that have a modified status.
971 1010
972 @return list of all items with a modified status 1011 @return list of all items with a modified status
973 """ 1012 """
974 modifiedItems = [] 1013 modifiedItems = []
975 for itm in self.statusList.selectedItems(): 1014 for itm in self.statusList.selectedItems():
976 if (itm.text(self.__statusWorkColumn) in 1015 if (
977 self.modifiedOnlyIndicators or 1016 itm.text(self.__statusWorkColumn) in self.modifiedOnlyIndicators
978 itm.text(self.__statusIndexColumn) in 1017 or itm.text(self.__statusIndexColumn) in self.modifiedOnlyIndicators
979 self.modifiedOnlyIndicators): 1018 ):
980 modifiedItems.append(itm) 1019 modifiedItems.append(itm)
981 return modifiedItems 1020 return modifiedItems
982 1021
983 def __getUnversionedItems(self): 1022 def __getUnversionedItems(self):
984 """ 1023 """
985 Private method to retrieve all entries, that have an unversioned 1024 Private method to retrieve all entries, that have an unversioned
986 status. 1025 status.
987 1026
988 @return list of all items with an unversioned status 1027 @return list of all items with an unversioned status
989 """ 1028 """
990 unversionedItems = [] 1029 unversionedItems = []
991 for itm in self.statusList.selectedItems(): 1030 for itm in self.statusList.selectedItems():
992 if itm.text(self.__statusWorkColumn) in self.unversionedIndicators: 1031 if itm.text(self.__statusWorkColumn) in self.unversionedIndicators:
993 unversionedItems.append(itm) 1032 unversionedItems.append(itm)
994 return unversionedItems 1033 return unversionedItems
995 1034
996 def __getStageableItems(self): 1035 def __getStageableItems(self):
997 """ 1036 """
998 Private method to retrieve all entries, that have a stageable 1037 Private method to retrieve all entries, that have a stageable
999 status. 1038 status.
1000 1039
1001 @return list of all items with a stageable status 1040 @return list of all items with a stageable status
1002 """ 1041 """
1003 stageableItems = [] 1042 stageableItems = []
1004 for itm in self.statusList.selectedItems(): 1043 for itm in self.statusList.selectedItems():
1005 if ( 1044 if (
1006 itm.text(self.__statusWorkColumn) in 1045 itm.text(self.__statusWorkColumn)
1007 self.modifiedIndicators + self.unmergedIndicators 1046 in self.modifiedIndicators + self.unmergedIndicators
1008 ): 1047 ):
1009 stageableItems.append(itm) 1048 stageableItems.append(itm)
1010 return stageableItems 1049 return stageableItems
1011 1050
1012 def __getUnstageableItems(self): 1051 def __getUnstageableItems(self):
1013 """ 1052 """
1014 Private method to retrieve all entries, that have an unstageable 1053 Private method to retrieve all entries, that have an unstageable
1015 status. 1054 status.
1016 1055
1017 @return list of all items with an unstageable status 1056 @return list of all items with an unstageable status
1018 """ 1057 """
1019 unstageableItems = [] 1058 unstageableItems = []
1020 for itm in self.statusList.selectedItems(): 1059 for itm in self.statusList.selectedItems():
1021 if itm.text(self.__statusIndexColumn) in self.modifiedIndicators: 1060 if itm.text(self.__statusIndexColumn) in self.modifiedIndicators:
1022 unstageableItems.append(itm) 1061 unstageableItems.append(itm)
1023 return unstageableItems 1062 return unstageableItems
1024 1063
1025 def __getMissingItems(self): 1064 def __getMissingItems(self):
1026 """ 1065 """
1027 Private method to retrieve all entries, that have a missing status. 1066 Private method to retrieve all entries, that have a missing status.
1028 1067
1029 @return list of all items with a missing status 1068 @return list of all items with a missing status
1030 """ 1069 """
1031 missingItems = [] 1070 missingItems = []
1032 for itm in self.statusList.selectedItems(): 1071 for itm in self.statusList.selectedItems():
1033 if itm.text(self.__statusWorkColumn) in self.missingIndicators: 1072 if itm.text(self.__statusWorkColumn) in self.missingIndicators:
1034 missingItems.append(itm) 1073 missingItems.append(itm)
1035 return missingItems 1074 return missingItems
1036 1075
1037 def __getConflictingItems(self): 1076 def __getConflictingItems(self):
1038 """ 1077 """
1039 Private method to retrieve all entries, that have a conflict status. 1078 Private method to retrieve all entries, that have a conflict status.
1040 1079
1041 @return list of all items with a conflict status 1080 @return list of all items with a conflict status
1042 """ 1081 """
1043 conflictingItems = [] 1082 conflictingItems = []
1044 for itm in self.statusList.selectedItems(): 1083 for itm in self.statusList.selectedItems():
1045 if itm.data(0, self.ConflictRole): 1084 if itm.data(0, self.ConflictRole):
1046 conflictingItems.append(itm) 1085 conflictingItems.append(itm)
1047 return conflictingItems 1086 return conflictingItems
1048 1087
1049 def __commitSelect(self, selected): 1088 def __commitSelect(self, selected):
1050 """ 1089 """
1051 Private slot to select or deselect all entries. 1090 Private slot to select or deselect all entries.
1052 1091
1053 @param selected commit selection state to be set (boolean) 1092 @param selected commit selection state to be set (boolean)
1054 """ 1093 """
1055 for index in range(self.statusList.topLevelItemCount()): 1094 for index in range(self.statusList.topLevelItemCount()):
1056 itm = self.statusList.topLevelItem(index) 1095 itm = self.statusList.topLevelItem(index)
1057 if ( 1096 if (
1058 itm.flags() & Qt.ItemFlag.ItemIsUserCheckable == 1097 itm.flags() & Qt.ItemFlag.ItemIsUserCheckable
1059 Qt.ItemFlag.ItemIsUserCheckable 1098 == Qt.ItemFlag.ItemIsUserCheckable
1060 ): 1099 ):
1061 if selected: 1100 if selected:
1062 itm.setCheckState(self.__toBeCommittedColumn, 1101 itm.setCheckState(self.__toBeCommittedColumn, Qt.CheckState.Checked)
1063 Qt.CheckState.Checked)
1064 else: 1102 else:
1065 itm.setCheckState(self.__toBeCommittedColumn, 1103 itm.setCheckState(
1066 Qt.CheckState.Unchecked) 1104 self.__toBeCommittedColumn, Qt.CheckState.Unchecked
1067 1105 )
1106
1068 ########################################################################### 1107 ###########################################################################
1069 ## Diff handling methods below 1108 ## Diff handling methods below
1070 ########################################################################### 1109 ###########################################################################
1071 1110
1072 def __generateDiffs(self): 1111 def __generateDiffs(self):
1073 """ 1112 """
1074 Private slot to generate diff outputs for the selected item. 1113 Private slot to generate diff outputs for the selected item.
1075 """ 1114 """
1076 self.lDiffEdit.clear() 1115 self.lDiffEdit.clear()
1077 self.rDiffEdit.clear() 1116 self.rDiffEdit.clear()
1078 with contextlib.suppress(AttributeError): 1117 with contextlib.suppress(AttributeError):
1079 self.lDiffHighlighter.regenerateRules() 1118 self.lDiffHighlighter.regenerateRules()
1080 self.rDiffHighlighter.regenerateRules() 1119 self.rDiffHighlighter.regenerateRules()
1081 1120
1082 selectedItems = self.statusList.selectedItems() 1121 selectedItems = self.statusList.selectedItems()
1083 if len(selectedItems) == 1: 1122 if len(selectedItems) == 1:
1084 fn = os.path.join(self.dname, 1123 fn = os.path.join(self.dname, selectedItems[0].text(self.__pathColumn))
1085 selectedItems[0].text(self.__pathColumn))
1086 self.__diffGenerator.start(fn, diffMode="work2stage2repo") 1124 self.__diffGenerator.start(fn, diffMode="work2stage2repo")
1087 1125
1088 def __generatorFinished(self): 1126 def __generatorFinished(self):
1089 """ 1127 """
1090 Private slot connected to the finished signal of the diff generator. 1128 Private slot connected to the finished signal of the diff generator.
1091 """ 1129 """
1092 diff1, diff2 = self.__diffGenerator.getResult()[:2] 1130 diff1, diff2 = self.__diffGenerator.getResult()[:2]
1093 1131
1094 if diff1: 1132 if diff1:
1095 self.lDiffParser = GitDiffParser(diff1) 1133 self.lDiffParser = GitDiffParser(diff1)
1096 for line in diff1[:]: 1134 for line in diff1[:]:
1097 if line.startswith("@@ "): 1135 if line.startswith("@@ "):
1098 break 1136 break
1099 else: 1137 else:
1100 diff1.pop(0) 1138 diff1.pop(0)
1101 self.lDiffEdit.setPlainText("".join(diff1)) 1139 self.lDiffEdit.setPlainText("".join(diff1))
1102 else: 1140 else:
1103 self.lDiffParser = None 1141 self.lDiffParser = None
1104 1142
1105 if diff2: 1143 if diff2:
1106 self.rDiffParser = GitDiffParser(diff2) 1144 self.rDiffParser = GitDiffParser(diff2)
1107 for line in diff2[:]: 1145 for line in diff2[:]:
1108 if line.startswith("@@ "): 1146 if line.startswith("@@ "):
1109 break 1147 break
1110 else: 1148 else:
1111 diff2.pop(0) 1149 diff2.pop(0)
1112 self.rDiffEdit.setPlainText("".join(diff2)) 1150 self.rDiffEdit.setPlainText("".join(diff2))
1113 else: 1151 else:
1114 self.rDiffParser = None 1152 self.rDiffParser = None
1115 1153
1116 for diffEdit in [self.lDiffEdit, self.rDiffEdit]: 1154 for diffEdit in [self.lDiffEdit, self.rDiffEdit]:
1117 tc = diffEdit.textCursor() 1155 tc = diffEdit.textCursor()
1118 tc.movePosition(QTextCursor.MoveOperation.Start) 1156 tc.movePosition(QTextCursor.MoveOperation.Start)
1119 diffEdit.setTextCursor(tc) 1157 diffEdit.setTextCursor(tc)
1120 diffEdit.ensureCursorVisible() 1158 diffEdit.ensureCursorVisible()
1121 1159
1122 def __showLDiffContextMenu(self, coord): 1160 def __showLDiffContextMenu(self, coord):
1123 """ 1161 """
1124 Private slot to show the context menu of the status list. 1162 Private slot to show the context menu of the status list.
1125 1163
1126 @param coord position of the mouse pointer (QPoint) 1164 @param coord position of the mouse pointer (QPoint)
1127 """ 1165 """
1128 if bool(self.lDiffEdit.toPlainText()): 1166 if bool(self.lDiffEdit.toPlainText()):
1129 cursor = self.lDiffEdit.textCursor() 1167 cursor = self.lDiffEdit.textCursor()
1130 if cursor.hasSelection(): 1168 if cursor.hasSelection():
1135 else: 1173 else:
1136 self.__stageLinesAct.setEnabled(False) 1174 self.__stageLinesAct.setEnabled(False)
1137 self.__revertLinesAct.setEnabled(False) 1175 self.__revertLinesAct.setEnabled(False)
1138 self.__stageHunkAct.setEnabled(True) 1176 self.__stageHunkAct.setEnabled(True)
1139 self.__revertHunkAct.setEnabled(True) 1177 self.__revertHunkAct.setEnabled(True)
1140 1178
1141 cursor = self.lDiffEdit.cursorForPosition(coord) 1179 cursor = self.lDiffEdit.cursorForPosition(coord)
1142 self.lDiffEdit.setTextCursor(cursor) 1180 self.lDiffEdit.setTextCursor(cursor)
1143 1181
1144 self.__lDiffMenu.popup(self.lDiffEdit.mapToGlobal(coord)) 1182 self.__lDiffMenu.popup(self.lDiffEdit.mapToGlobal(coord))
1145 1183
1146 def __showRDiffContextMenu(self, coord): 1184 def __showRDiffContextMenu(self, coord):
1147 """ 1185 """
1148 Private slot to show the context menu of the status list. 1186 Private slot to show the context menu of the status list.
1149 1187
1150 @param coord position of the mouse pointer (QPoint) 1188 @param coord position of the mouse pointer (QPoint)
1151 """ 1189 """
1152 if bool(self.rDiffEdit.toPlainText()): 1190 if bool(self.rDiffEdit.toPlainText()):
1153 cursor = self.rDiffEdit.textCursor() 1191 cursor = self.rDiffEdit.textCursor()
1154 if cursor.hasSelection(): 1192 if cursor.hasSelection():
1155 self.__unstageLinesAct.setEnabled(True) 1193 self.__unstageLinesAct.setEnabled(True)
1156 self.__unstageHunkAct.setEnabled(False) 1194 self.__unstageHunkAct.setEnabled(False)
1157 else: 1195 else:
1158 self.__unstageLinesAct.setEnabled(False) 1196 self.__unstageLinesAct.setEnabled(False)
1159 self.__unstageHunkAct.setEnabled(True) 1197 self.__unstageHunkAct.setEnabled(True)
1160 1198
1161 cursor = self.rDiffEdit.cursorForPosition(coord) 1199 cursor = self.rDiffEdit.cursorForPosition(coord)
1162 self.rDiffEdit.setTextCursor(cursor) 1200 self.rDiffEdit.setTextCursor(cursor)
1163 1201
1164 self.__rDiffMenu.popup(self.rDiffEdit.mapToGlobal(coord)) 1202 self.__rDiffMenu.popup(self.rDiffEdit.mapToGlobal(coord))
1165 1203
1166 def __stageHunkOrLines(self): 1204 def __stageHunkOrLines(self):
1167 """ 1205 """
1168 Private method to stage the selected lines or hunk. 1206 Private method to stage the selected lines or hunk.
1169 """ 1207 """
1170 cursor = self.lDiffEdit.textCursor() 1208 cursor = self.lDiffEdit.textCursor()
1171 startIndex, endIndex = self.__selectedLinesIndexes(self.lDiffEdit) 1209 startIndex, endIndex = self.__selectedLinesIndexes(self.lDiffEdit)
1172 patch = ( 1210 patch = (
1173 self.lDiffParser.createLinesPatch(startIndex, endIndex) 1211 self.lDiffParser.createLinesPatch(startIndex, endIndex)
1174 if cursor.hasSelection() else 1212 if cursor.hasSelection()
1175 self.lDiffParser.createHunkPatch(startIndex) 1213 else self.lDiffParser.createHunkPatch(startIndex)
1176 ) 1214 )
1177 if patch: 1215 if patch:
1178 patchFile = self.__tmpPatchFileName() 1216 patchFile = self.__tmpPatchFileName()
1179 try: 1217 try:
1180 with open(patchFile, "w") as f: 1218 with open(patchFile, "w") as f:
1181 f.write(patch) 1219 f.write(patch)
1182 self.vcs.gitApply(self.dname, patchFile, cached=True, 1220 self.vcs.gitApply(self.dname, patchFile, cached=True, noDialog=True)
1183 noDialog=True)
1184 self.on_refreshButton_clicked() 1221 self.on_refreshButton_clicked()
1185 finally: 1222 finally:
1186 os.remove(patchFile) 1223 os.remove(patchFile)
1187 1224
1188 def __unstageHunkOrLines(self): 1225 def __unstageHunkOrLines(self):
1189 """ 1226 """
1190 Private method to unstage the selected lines or hunk. 1227 Private method to unstage the selected lines or hunk.
1191 """ 1228 """
1192 cursor = self.rDiffEdit.textCursor() 1229 cursor = self.rDiffEdit.textCursor()
1193 startIndex, endIndex = self.__selectedLinesIndexes(self.rDiffEdit) 1230 startIndex, endIndex = self.__selectedLinesIndexes(self.rDiffEdit)
1194 patch = ( 1231 patch = (
1195 self.rDiffParser.createLinesPatch(startIndex, endIndex, 1232 self.rDiffParser.createLinesPatch(startIndex, endIndex, reverse=True)
1196 reverse=True) 1233 if cursor.hasSelection()
1197 if cursor.hasSelection() else 1234 else self.rDiffParser.createHunkPatch(startIndex)
1198 self.rDiffParser.createHunkPatch(startIndex)
1199 ) 1235 )
1200 if patch: 1236 if patch:
1201 patchFile = self.__tmpPatchFileName() 1237 patchFile = self.__tmpPatchFileName()
1202 try: 1238 try:
1203 with open(patchFile, "w") as f: 1239 with open(patchFile, "w") as f:
1204 f.write(patch) 1240 f.write(patch)
1205 self.vcs.gitApply(self.dname, patchFile, cached=True, 1241 self.vcs.gitApply(
1206 reverse=True, noDialog=True) 1242 self.dname, patchFile, cached=True, reverse=True, noDialog=True
1243 )
1207 self.on_refreshButton_clicked() 1244 self.on_refreshButton_clicked()
1208 finally: 1245 finally:
1209 os.remove(patchFile) 1246 os.remove(patchFile)
1210 1247
1211 def __revertHunkOrLines(self): 1248 def __revertHunkOrLines(self):
1212 """ 1249 """
1213 Private method to revert the selected lines or hunk. 1250 Private method to revert the selected lines or hunk.
1214 """ 1251 """
1215 cursor = self.lDiffEdit.textCursor() 1252 cursor = self.lDiffEdit.textCursor()
1216 startIndex, endIndex = self.__selectedLinesIndexes(self.lDiffEdit) 1253 startIndex, endIndex = self.__selectedLinesIndexes(self.lDiffEdit)
1217 title = ( 1254 title = (
1218 self.tr("Revert selected lines") 1255 self.tr("Revert selected lines")
1219 if cursor.hasSelection() else 1256 if cursor.hasSelection()
1220 self.tr("Revert hunk") 1257 else self.tr("Revert hunk")
1221 ) 1258 )
1222 res = EricMessageBox.yesNo( 1259 res = EricMessageBox.yesNo(
1223 self, 1260 self,
1224 title, 1261 title,
1225 self.tr("""Are you sure you want to revert the selected""" 1262 self.tr("""Are you sure you want to revert the selected""" """ changes?"""),
1226 """ changes?""")) 1263 )
1227 if res: 1264 if res:
1228 if cursor.hasSelection(): 1265 if cursor.hasSelection():
1229 patch = self.lDiffParser.createLinesPatch(startIndex, endIndex, 1266 patch = self.lDiffParser.createLinesPatch(
1230 reverse=True) 1267 startIndex, endIndex, reverse=True
1268 )
1231 else: 1269 else:
1232 patch = self.lDiffParser.createHunkPatch(startIndex) 1270 patch = self.lDiffParser.createHunkPatch(startIndex)
1233 if patch: 1271 if patch:
1234 patchFile = self.__tmpPatchFileName() 1272 patchFile = self.__tmpPatchFileName()
1235 try: 1273 try:
1236 with open(patchFile, "w") as f: 1274 with open(patchFile, "w") as f:
1237 f.write(patch) 1275 f.write(patch)
1238 self.vcs.gitApply(self.dname, patchFile, reverse=True, 1276 self.vcs.gitApply(
1239 noDialog=True) 1277 self.dname, patchFile, reverse=True, noDialog=True
1278 )
1240 self.on_refreshButton_clicked() 1279 self.on_refreshButton_clicked()
1241 finally: 1280 finally:
1242 os.remove(patchFile) 1281 os.remove(patchFile)
1243 1282
1244 def __selectedLinesIndexes(self, diffEdit): 1283 def __selectedLinesIndexes(self, diffEdit):
1245 """ 1284 """
1246 Private method to extract the indexes of the selected lines. 1285 Private method to extract the indexes of the selected lines.
1247 1286
1248 @param diffEdit reference to the edit widget (QTextEdit) 1287 @param diffEdit reference to the edit widget (QTextEdit)
1249 @return tuple of start and end indexes (integer, integer) 1288 @return tuple of start and end indexes (integer, integer)
1250 """ 1289 """
1251 cursor = diffEdit.textCursor() 1290 cursor = diffEdit.textCursor()
1252 selectionStart = cursor.selectionStart() 1291 selectionStart = cursor.selectionStart()
1253 selectionEnd = cursor.selectionEnd() 1292 selectionEnd = cursor.selectionEnd()
1254 1293
1255 startIndex = -1 1294 startIndex = -1
1256 1295
1257 lineStart = 0 1296 lineStart = 0
1258 for lineIdx, line in enumerate(diffEdit.toPlainText().splitlines()): 1297 for lineIdx, line in enumerate(diffEdit.toPlainText().splitlines()):
1259 lineEnd = lineStart + len(line) 1298 lineEnd = lineStart + len(line)
1260 if lineStart <= selectionStart <= lineEnd: 1299 if lineStart <= selectionStart <= lineEnd:
1261 startIndex = lineIdx 1300 startIndex = lineIdx
1263 endIndex = lineIdx 1302 endIndex = lineIdx
1264 break 1303 break
1265 lineStart = lineEnd + 1 1304 lineStart = lineEnd + 1
1266 1305
1267 return startIndex, endIndex 1306 return startIndex, endIndex
1268 1307
1269 def __tmpPatchFileName(self): 1308 def __tmpPatchFileName(self):
1270 """ 1309 """
1271 Private method to generate a temporary patch file. 1310 Private method to generate a temporary patch file.
1272 1311
1273 @return name of the temporary file (string) 1312 @return name of the temporary file (string)
1274 """ 1313 """
1275 prefix = 'eric-git-{0}-'.format(os.getpid()) 1314 prefix = "eric-git-{0}-".format(os.getpid())
1276 suffix = '-patch' 1315 suffix = "-patch"
1277 fd, path = tempfile.mkstemp(suffix, prefix) 1316 fd, path = tempfile.mkstemp(suffix, prefix)
1278 os.close(fd) 1317 os.close(fd)
1279 return path 1318 return path
1280 1319
1281 def __refreshDiff(self): 1320 def __refreshDiff(self):
1282 """ 1321 """
1283 Private method to refresh the diff output after a refresh. 1322 Private method to refresh the diff output after a refresh.
1284 """ 1323 """
1285 if self.__selectedName: 1324 if self.__selectedName:
1286 for index in range(self.statusList.topLevelItemCount()): 1325 for index in range(self.statusList.topLevelItemCount()):
1287 itm = self.statusList.topLevelItem(index) 1326 itm = self.statusList.topLevelItem(index)
1288 if itm.text(self.__pathColumn) == self.__selectedName: 1327 if itm.text(self.__pathColumn) == self.__selectedName:
1289 itm.setSelected(True) 1328 itm.setSelected(True)
1290 break 1329 break
1291 1330
1292 self.__selectedName = "" 1331 self.__selectedName = ""

eric ide

mercurial