eric6/Plugins/VcsPlugins/vcsMercurial/HgSummaryDialog.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7192
a22eee00b052
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show some summary information of the working
8 directory state.
9 """
10
11 from __future__ import unicode_literals
12 try:
13 str = unicode
14 except NameError:
15 pass
16
17 import os
18
19 from PyQt5.QtCore import pyqtSlot, QProcess, QTimer
20 from PyQt5.QtWidgets import QDialog, QDialogButtonBox
21
22 from E5Gui import E5MessageBox
23
24 from .HgUtilities import prepareProcess
25
26 from .Ui_HgSummaryDialog import Ui_HgSummaryDialog
27
28
29 class HgSummaryDialog(QDialog, Ui_HgSummaryDialog):
30 """
31 Class implementing a dialog to show some summary information of the working
32 directory state.
33 """
34 def __init__(self, vcs, parent=None):
35 """
36 Constructor
37
38 @param vcs reference to the vcs object
39 @param parent parent widget (QWidget)
40 """
41 super(HgSummaryDialog, self).__init__(parent)
42 self.setupUi(self)
43
44 self.refreshButton = self.buttonBox.addButton(
45 self.tr("Refresh"), QDialogButtonBox.ActionRole)
46 self.refreshButton.setToolTip(
47 self.tr("Press to refresh the summary display"))
48 self.refreshButton.setEnabled(False)
49
50 self.vcs = vcs
51 self.vcs.committed.connect(self.__committed)
52
53 self.process = QProcess()
54 prepareProcess(self.process, language="C")
55 self.process.finished.connect(self.__procFinished)
56 self.process.readyReadStandardOutput.connect(self.__readStdout)
57 self.process.readyReadStandardError.connect(self.__readStderr)
58
59 def closeEvent(self, e):
60 """
61 Protected slot implementing a close event handler.
62
63 @param e close event (QCloseEvent)
64 """
65 if self.process is not None and \
66 self.process.state() != QProcess.NotRunning:
67 self.process.terminate()
68 QTimer.singleShot(2000, self.process.kill)
69 self.process.waitForFinished(3000)
70
71 e.accept()
72
73 def start(self, path, mq=False, largefiles=False):
74 """
75 Public slot to start the hg summary command.
76
77 @param path path name of the working directory (string)
78 @param mq flag indicating to show the queue status as well (boolean)
79 @param largefiles flag indicating to show the largefiles status as
80 well (boolean)
81 """
82 self.errorGroup.hide()
83 self.refreshButton.setEnabled(False)
84 self.summary.clear()
85
86 self.__path = path
87 self.__mq = mq
88 self.__largefiles = largefiles
89
90 args = self.vcs.initCommand("summary")
91 if self.vcs.canPull():
92 args.append("--remote")
93 if self.__mq:
94 args.append("--mq")
95 if self.__largefiles:
96 args.append("--large")
97
98 # find the root of the repo
99 repodir = self.__path
100 while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
101 repodir = os.path.dirname(repodir)
102 if os.path.splitdrive(repodir)[1] == os.sep:
103 return
104
105 if self.process:
106 self.process.kill()
107
108 self.process.setWorkingDirectory(repodir)
109
110 self.__buffer = []
111
112 self.process.start('hg', args)
113 procStarted = self.process.waitForStarted(5000)
114 if not procStarted:
115 E5MessageBox.critical(
116 self,
117 self.tr('Process Generation Error'),
118 self.tr(
119 'The process {0} could not be started. '
120 'Ensure, that it is in the search path.'
121 ).format('hg'))
122
123 def __finish(self):
124 """
125 Private slot called when the process finished or the user pressed
126 the button.
127 """
128 if self.process is not None and \
129 self.process.state() != QProcess.NotRunning:
130 self.process.terminate()
131 QTimer.singleShot(2000, self.process.kill)
132 self.process.waitForFinished(3000)
133
134 self.refreshButton.setEnabled(True)
135
136 def on_buttonBox_clicked(self, button):
137 """
138 Private slot called by a button of the button box clicked.
139
140 @param button button that was clicked (QAbstractButton)
141 """
142 if button == self.buttonBox.button(QDialogButtonBox.Close):
143 self.close()
144 elif button == self.refreshButton:
145 self.on_refreshButton_clicked()
146
147 def __procFinished(self, exitCode, exitStatus):
148 """
149 Private slot connected to the finished signal.
150
151 @param exitCode exit code of the process (integer)
152 @param exitStatus exit status of the process (QProcess.ExitStatus)
153 """
154 self.__processOutput(self.__buffer)
155 self.__finish()
156
157 def __readStdout(self):
158 """
159 Private slot to handle the readyReadStandardOutput signal.
160
161 It reads the output of the process, formats it and inserts it into
162 the contents pane.
163 """
164 if self.process is not None:
165 self.process.setReadChannel(QProcess.StandardOutput)
166
167 while self.process.canReadLine():
168 line = str(self.process.readLine(), self.vcs.getEncoding(),
169 'replace')
170 self.__buffer.append(line)
171
172 def __readStderr(self):
173 """
174 Private slot to handle the readyReadStandardError signal.
175
176 It reads the error output of the process and inserts it into the
177 error pane.
178 """
179 if self.process is not None:
180 s = str(self.process.readAllStandardError(),
181 self.vcs.getEncoding(), 'replace')
182 self.__showError(s)
183
184 def __showError(self, out):
185 """
186 Private slot to show some error.
187
188 @param out error to be shown (string)
189 """
190 self.errorGroup.show()
191 self.errors.insertPlainText(out)
192 self.errors.ensureCursorVisible()
193
194 @pyqtSlot()
195 def on_refreshButton_clicked(self):
196 """
197 Private slot to refresh the status display.
198 """
199 self.start(self.__path, mq=self.__mq)
200
201 def __committed(self):
202 """
203 Private slot called after the commit has finished.
204 """
205 if self.isVisible():
206 self.on_refreshButton_clicked()
207
208 def __processOutput(self, output):
209 """
210 Private method to process the output into nice readable text.
211
212 @param output output from the summary command (string)
213 """
214 infoDict = {}
215
216 # step 1: parse the output
217 while output:
218 line = output.pop(0)
219 if ':' not in line:
220 continue
221 name, value = line.split(": ", 1)
222 value = value.strip()
223
224 if name == "parent":
225 if " " in value:
226 parent, tags = value.split(" ", 1)
227 else:
228 parent = value
229 tags = ""
230 rev, node = parent.split(":")
231
232 remarks = []
233 if tags:
234 if " (empty repository)" in tags:
235 remarks.append("@EMPTY@")
236 tags = tags.replace(" (empty repository)", "")
237 if " (no revision checked out)" in tags:
238 remarks.append("@NO_REVISION@")
239 tags = tags.replace(" (no revision checked out)", "")
240 else:
241 tags = None
242
243 value = infoDict.get(name, [])
244
245 if rev == "-1":
246 value.append((int(rev), node, tags, None, remarks))
247 else:
248 message = output.pop(0).strip()
249 value.append((int(rev), node, tags, message, remarks))
250 elif name == "branch":
251 pass
252 elif name == "bookmarks":
253 pass
254 elif name == "commit":
255 stateDict = {}
256 if "(" in value:
257 if value.startswith("("):
258 states = ""
259 remark = value[1:-1]
260 else:
261 states, remark = value.rsplit(" (", 1)
262 remark = remark[:-1]
263 else:
264 states = value
265 remark = ""
266 states = states.split(", ")
267 for state in states:
268 if state:
269 count, category = state.split(" ")
270 stateDict[category] = count
271 value = (stateDict, remark)
272 elif name == "update":
273 if value.endswith("(current)"):
274 value = ("@CURRENT@", 0, 0)
275 elif value.endswith("(update)"):
276 value = ("@UPDATE@", int(value.split(" ", 1)[0]), 0)
277 elif value.endswith("(merge)"):
278 parts = value.split(", ")
279 value = ("@MERGE@", int(parts[0].split(" ", 1)[0]),
280 int(parts[1].split(" ", 1)[0]))
281 else:
282 value = ("@UNKNOWN@", 0, 0)
283 elif name == "remote":
284 if value == "(synced)":
285 value = (0, 0, 0, 0)
286 else:
287 inc = incb = outg = outgb = 0
288 for val in value.split(", "):
289 count, category = val.split(" ", 1)
290 if category == "outgoing":
291 outg = int(count)
292 elif category.endswith("incoming"):
293 inc = int(count)
294 elif category == "incoming bookmarks":
295 incb = int(count)
296 elif category == "outgoing bookmarks":
297 outgb = int(count)
298 value = (inc, outg, incb, outgb)
299 elif name == "mq":
300 if value == "(empty queue)":
301 value = (0, 0)
302 else:
303 applied = unapplied = 0
304 for val in value.split(", "):
305 count, category = val.split(" ", 1)
306 if category == "applied":
307 applied = int(count)
308 elif category == "unapplied":
309 unapplied = int(count)
310 value = (applied, unapplied)
311 elif name == "largefiles":
312 if not value[0].isdigit():
313 value = 0
314 else:
315 value = int(value.split(None, 1)[0])
316 else:
317 # ignore unknown entries
318 continue
319
320 infoDict[name] = value
321
322 # step 2: build the output
323 if infoDict:
324 info = ["<table>"]
325 pindex = 0
326 for rev, node, tags, message, remarks in infoDict["parent"]:
327 pindex += 1
328 changeset = "{0}:{1}".format(rev, node)
329 if len(infoDict["parent"]) > 1:
330 info.append(self.tr(
331 "<tr><td><b>Parent #{0}</b></td><td>{1}</td></tr>")
332 .format(pindex, changeset))
333 else:
334 info.append(self.tr(
335 "<tr><td><b>Parent</b></td><td>{0}</td></tr>")
336 .format(changeset))
337 if tags:
338 info.append(self.tr(
339 "<tr><td><b>Tags</b></td><td>{0}</td></tr>")
340 .format('<br/>'.join(tags.split())))
341 if message:
342 info.append(self.tr(
343 "<tr><td><b>Commit Message</b></td><td>{0}</td></tr>")
344 .format(message))
345 if remarks:
346 rem = []
347 if "@EMPTY@" in remarks:
348 rem.append(self.tr("empty repository"))
349 if "@NO_REVISION@" in remarks:
350 rem.append(self.tr("no revision checked out"))
351 info.append(self.tr(
352 "<tr><td><b>Remarks</b></td><td>{0}</td></tr>")
353 .format(", ".join(rem)))
354 if "branch" in infoDict:
355 info.append(self.tr(
356 "<tr><td><b>Branch</b></td><td>{0}</td></tr>")
357 .format(infoDict["branch"]))
358 if "bookmarks" in infoDict:
359 bookmarks = infoDict["bookmarks"].split()
360 for i in range(len(bookmarks)):
361 if bookmarks[i].startswith("*"):
362 bookmarks[i] = "<b>{0}</b>".format(bookmarks[i])
363 info.append(self.tr(
364 "<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>")
365 .format('<br/>'.join(bookmarks)))
366 if "commit" in infoDict:
367 cinfo = []
368 for category, count in infoDict["commit"][0].items():
369 if category == "modified":
370 cinfo.append(self.tr("{0} modified").format(count))
371 elif category == "added":
372 cinfo.append(self.tr("{0} added").format(count))
373 elif category == "removed":
374 cinfo.append(self.tr("{0} removed").format(count))
375 elif category == "renamed":
376 cinfo.append(self.tr("{0} renamed").format(count))
377 elif category == "copied":
378 cinfo.append(self.tr("{0} copied").format(count))
379 elif category == "deleted":
380 cinfo.append(self.tr("{0} deleted").format(count))
381 elif category == "unknown":
382 cinfo.append(self.tr("{0} unknown").format(count))
383 elif category == "ignored":
384 cinfo.append(self.tr("{0} ignored").format(count))
385 elif category == "unresolved":
386 cinfo.append(
387 self.tr("{0} unresolved").format(count))
388 elif category == "subrepos":
389 cinfo.append(self.tr("{0} subrepos").format(count))
390 remark = infoDict["commit"][1]
391 if remark == "merge":
392 cinfo.append(self.tr("Merge needed"))
393 elif remark == "new branch":
394 cinfo.append(self.tr("New Branch"))
395 elif remark == "head closed":
396 cinfo.append(self.tr("Head is closed"))
397 elif remark == "clean":
398 cinfo.append(self.tr("No commit required"))
399 elif remark == "new branch head":
400 cinfo.append(self.tr("New Branch Head"))
401 info.append(self.tr(
402 "<tr><td><b>Commit Status</b></td><td>{0}</td></tr>")
403 .format("<br/>".join(cinfo)))
404 if "update" in infoDict:
405 if infoDict["update"][0] == "@CURRENT@":
406 uinfo = self.tr("current")
407 elif infoDict["update"][0] == "@UPDATE@":
408 uinfo = self.tr(
409 "%n new changeset(s)<br/>Update required", "",
410 infoDict["update"][1])
411 elif infoDict["update"][0] == "@MERGE@":
412 uinfo1 = self.tr(
413 "%n new changeset(s)", "", infoDict["update"][1])
414 uinfo2 = self.tr(
415 "%n branch head(s)", "", infoDict["update"][2])
416 uinfo = self.tr(
417 "{0}<br/>{1}<br/>Merge required",
418 "0 is changesets, 1 is branch heads")\
419 .format(uinfo1, uinfo2)
420 else:
421 uinfo = self.tr("unknown status")
422 info.append(self.tr(
423 "<tr><td><b>Update Status</b></td><td>{0}</td></tr>")
424 .format(uinfo))
425 if "remote" in infoDict:
426 if infoDict["remote"] == (0, 0, 0, 0):
427 rinfo = self.tr("synched")
428 else:
429 li = []
430 if infoDict["remote"][0]:
431 li.append(self.tr("1 or more incoming changesets"))
432 if infoDict["remote"][1]:
433 li.append(self.tr("%n outgoing changeset(s)", "",
434 infoDict["remote"][1]))
435 if infoDict["remote"][2]:
436 li.append(self.tr("%n incoming bookmark(s)", "",
437 infoDict["remote"][2]))
438 if infoDict["remote"][3]:
439 li.append(self.tr("%n outgoing bookmark(s)", "",
440 infoDict["remote"][3]))
441 rinfo = "<br/>".join(li)
442 info.append(self.tr(
443 "<tr><td><b>Remote Status</b></td><td>{0}</td></tr>")
444 .format(rinfo))
445 if "mq" in infoDict:
446 if infoDict["mq"] == (0, 0):
447 qinfo = self.tr("empty queue")
448 else:
449 li = []
450 if infoDict["mq"][0]:
451 li.append(self.tr("{0} applied")
452 .format(infoDict["mq"][0]))
453 if infoDict["mq"][1]:
454 li.append(self.tr("{0} unapplied")
455 .format(infoDict["mq"][1]))
456 qinfo = "<br/>".join(li)
457 info.append(self.tr(
458 "<tr><td><b>Queues Status</b></td><td>{0}</td></tr>")
459 .format(qinfo))
460 if "largefiles" in infoDict:
461 if infoDict["largefiles"] == 0:
462 lfInfo = self.tr("No files to upload")
463 else:
464 lfInfo = self.tr("%n file(s) to upload", "",
465 infoDict["largefiles"])
466 info.append(self.tr(
467 "<tr><td><b>Large Files</b></td><td>{0}</td></tr>")
468 .format(lfInfo))
469 info.append("</table>")
470 else:
471 info = [self.tr("<p>No status information available.</p>")]
472
473 self.summary.insertHtml("\n".join(info))

eric ide

mercurial