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