Plugins/VcsPlugins/vcsMercurial/HgSummaryDialog.py

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

eric ide

mercurial