|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2013 - 2022 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 PyQt6.QtCore import pyqtSlot |
|
12 from PyQt6.QtWidgets import QDialog, QDialogButtonBox |
|
13 |
|
14 from .Ui_HgSummaryDialog import Ui_HgSummaryDialog |
|
15 |
|
16 |
|
17 class HgSummaryDialog(QDialog, Ui_HgSummaryDialog): |
|
18 """ |
|
19 Class implementing a dialog to show some summary information of the working |
|
20 directory state. |
|
21 """ |
|
22 def __init__(self, vcs, parent=None): |
|
23 """ |
|
24 Constructor |
|
25 |
|
26 @param vcs reference to the vcs object |
|
27 @param parent parent widget (QWidget) |
|
28 """ |
|
29 super().__init__(parent) |
|
30 self.setupUi(self) |
|
31 |
|
32 self.refreshButton = self.buttonBox.addButton( |
|
33 self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole) |
|
34 self.refreshButton.setToolTip( |
|
35 self.tr("Press to refresh the summary display")) |
|
36 self.refreshButton.setEnabled(False) |
|
37 |
|
38 self.vcs = vcs |
|
39 self.vcs.committed.connect(self.__committed) |
|
40 |
|
41 def start(self, mq=False, largefiles=False): |
|
42 """ |
|
43 Public slot to start the hg summary command. |
|
44 |
|
45 @param mq flag indicating to show the queue status as well (boolean) |
|
46 @param largefiles flag indicating to show the largefiles status as |
|
47 well (boolean) |
|
48 """ |
|
49 self.errorGroup.hide() |
|
50 self.refreshButton.setEnabled(False) |
|
51 self.summary.clear() |
|
52 |
|
53 self.__mq = mq |
|
54 self.__largefiles = largefiles |
|
55 |
|
56 args = self.vcs.initCommand("summary") |
|
57 if self.vcs.canPull(): |
|
58 args.append("--remote") |
|
59 if self.__mq: |
|
60 args.append("--mq") |
|
61 if self.__largefiles: |
|
62 args.append("--large") |
|
63 |
|
64 client = self.vcs.getClient() |
|
65 output, error = client.runcommand(args) |
|
66 if error: |
|
67 self.__showError(error) |
|
68 else: |
|
69 self.__processOutput(output.splitlines()) |
|
70 |
|
71 self.refreshButton.setEnabled(True) |
|
72 |
|
73 def on_buttonBox_clicked(self, button): |
|
74 """ |
|
75 Private slot called by a button of the button box clicked. |
|
76 |
|
77 @param button button that was clicked (QAbstractButton) |
|
78 """ |
|
79 if button == self.buttonBox.button( |
|
80 QDialogButtonBox.StandardButton.Close |
|
81 ): |
|
82 self.close() |
|
83 elif button == self.refreshButton: |
|
84 self.on_refreshButton_clicked() |
|
85 |
|
86 @pyqtSlot() |
|
87 def on_refreshButton_clicked(self): |
|
88 """ |
|
89 Private slot to refresh the status display. |
|
90 """ |
|
91 self.start(mq=self.__mq) |
|
92 |
|
93 def __committed(self): |
|
94 """ |
|
95 Private slot called after the commit has finished. |
|
96 """ |
|
97 if self.isVisible(): |
|
98 self.on_refreshButton_clicked() |
|
99 |
|
100 def __showError(self, out): |
|
101 """ |
|
102 Private slot to show some error. |
|
103 |
|
104 @param out error to be shown (string) |
|
105 """ |
|
106 self.errorGroup.show() |
|
107 self.errors.insertPlainText(out) |
|
108 self.errors.ensureCursorVisible() |
|
109 |
|
110 def __processOutput(self, output): |
|
111 """ |
|
112 Private method to process the output into nice readable text. |
|
113 |
|
114 @param output output from the summary command (string) |
|
115 """ |
|
116 infoDict = {} |
|
117 |
|
118 # step 1: parse the output |
|
119 while output: |
|
120 line = output.pop(0) |
|
121 if ':' not in line: |
|
122 continue |
|
123 name, value = line.split(": ", 1) |
|
124 value = value.strip() |
|
125 |
|
126 if name == "parent": |
|
127 if " " in value: |
|
128 parent, tags = value.split(" ", 1) |
|
129 else: |
|
130 parent = value |
|
131 tags = "" |
|
132 rev, node = parent.split(":") |
|
133 |
|
134 remarks = [] |
|
135 if tags: |
|
136 if " (empty repository)" in tags: |
|
137 remarks.append("@EMPTY@") |
|
138 tags = tags.replace(" (empty repository)", "") |
|
139 if " (no revision checked out)" in tags: |
|
140 remarks.append("@NO_REVISION@") |
|
141 tags = tags.replace(" (no revision checked out)", "") |
|
142 else: |
|
143 tags = None |
|
144 |
|
145 value = infoDict.get(name, []) |
|
146 |
|
147 if rev == "-1": |
|
148 value.append((int(rev), node, tags, None, remarks)) |
|
149 else: |
|
150 message = output.pop(0).strip() |
|
151 value.append((int(rev), node, tags, message, remarks)) |
|
152 elif name in ("branch", "bookmarks"): |
|
153 pass |
|
154 elif name == "commit": |
|
155 stateDict = {} |
|
156 if "(" in value: |
|
157 if value.startswith("("): |
|
158 states = "" |
|
159 remark = value[1:-1] |
|
160 else: |
|
161 states, remark = value.rsplit(" (", 1) |
|
162 remark = remark[:-1] |
|
163 else: |
|
164 states = value |
|
165 remark = "" |
|
166 states = states.split(", ") |
|
167 for state in states: |
|
168 if state: |
|
169 count, category = state.split(" ") |
|
170 stateDict[category] = count |
|
171 value = (stateDict, remark) |
|
172 elif name == "update": |
|
173 if value.endswith("(current)"): |
|
174 value = ("@CURRENT@", 0, 0) |
|
175 elif value.endswith("(update)"): |
|
176 value = ("@UPDATE@", int(value.split(" ", 1)[0]), 0) |
|
177 elif value.endswith("(merge)"): |
|
178 parts = value.split(", ") |
|
179 value = ("@MERGE@", int(parts[0].split(" ", 1)[0]), |
|
180 int(parts[1].split(" ", 1)[0])) |
|
181 else: |
|
182 value = ("@UNKNOWN@", 0, 0) |
|
183 elif name == "remote": |
|
184 if value == "(synced)": |
|
185 value = (0, 0, 0, 0) |
|
186 else: |
|
187 inc = incb = outg = outgb = 0 |
|
188 for val in value.split(", "): |
|
189 count, category = val.split(" ", 1) |
|
190 if category == "outgoing": |
|
191 outg = int(count) |
|
192 elif category.endswith("incoming"): |
|
193 inc = int(count) |
|
194 elif category == "incoming bookmarks": |
|
195 incb = int(count) |
|
196 elif category == "outgoing bookmarks": |
|
197 outgb = int(count) |
|
198 value = (inc, outg, incb, outgb) |
|
199 elif name == "mq": |
|
200 if value == "(empty queue)": |
|
201 value = (0, 0) |
|
202 else: |
|
203 applied = unapplied = 0 |
|
204 for val in value.split(", "): |
|
205 count, category = val.split(" ", 1) |
|
206 if category == "applied": |
|
207 applied = int(count) |
|
208 elif category == "unapplied": |
|
209 unapplied = int(count) |
|
210 value = (applied, unapplied) |
|
211 elif name == "largefiles": |
|
212 if not value[0].isdigit(): |
|
213 value = 0 |
|
214 else: |
|
215 value = int(value.split(None, 1)[0]) |
|
216 else: |
|
217 # ignore unknown entries |
|
218 continue |
|
219 |
|
220 infoDict[name] = value |
|
221 |
|
222 # step 2: build the output |
|
223 if infoDict: |
|
224 info = ["<table>"] |
|
225 for pindex, (rev, node, tags, message, remarks) in enumerate( |
|
226 infoDict["parent"], start=1 |
|
227 ): |
|
228 changeset = "{0}:{1}".format(rev, node) |
|
229 if len(infoDict["parent"]) > 1: |
|
230 info.append(self.tr( |
|
231 "<tr><td><b>Parent #{0}</b></td><td>{1}</td></tr>") |
|
232 .format(pindex, changeset)) |
|
233 else: |
|
234 info.append(self.tr( |
|
235 "<tr><td><b>Parent</b></td><td>{0}</td></tr>") |
|
236 .format(changeset)) |
|
237 if tags: |
|
238 info.append(self.tr( |
|
239 "<tr><td><b>Tags</b></td><td>{0}</td></tr>") |
|
240 .format('<br/>'.join(tags.split()))) |
|
241 if message: |
|
242 info.append(self.tr( |
|
243 "<tr><td><b>Commit Message</b></td><td>{0}</td></tr>") |
|
244 .format(message)) |
|
245 if remarks: |
|
246 rem = [] |
|
247 if "@EMPTY@" in remarks: |
|
248 rem.append(self.tr("empty repository")) |
|
249 if "@NO_REVISION@" in remarks: |
|
250 rem.append(self.tr("no revision checked out")) |
|
251 info.append(self.tr( |
|
252 "<tr><td><b>Remarks</b></td><td>{0}</td></tr>") |
|
253 .format(", ".join(rem))) |
|
254 if "branch" in infoDict: |
|
255 info.append(self.tr( |
|
256 "<tr><td><b>Branch</b></td><td>{0}</td></tr>") |
|
257 .format(infoDict["branch"])) |
|
258 if "bookmarks" in infoDict: |
|
259 bookmarks = infoDict["bookmarks"].split() |
|
260 for i in range(len(bookmarks)): |
|
261 if bookmarks[i].startswith("*"): |
|
262 bookmarks[i] = "<b>{0}</b>".format(bookmarks[i]) |
|
263 info.append(self.tr( |
|
264 "<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>") |
|
265 .format('<br/>'.join(bookmarks))) |
|
266 if "commit" in infoDict: |
|
267 cinfo = [] |
|
268 for category, count in infoDict["commit"][0].items(): |
|
269 if category == "modified": |
|
270 cinfo.append(self.tr("{0} modified").format(count)) |
|
271 elif category == "added": |
|
272 cinfo.append(self.tr("{0} added").format(count)) |
|
273 elif category == "removed": |
|
274 cinfo.append(self.tr("{0} removed").format(count)) |
|
275 elif category == "renamed": |
|
276 cinfo.append(self.tr("{0} renamed").format(count)) |
|
277 elif category == "copied": |
|
278 cinfo.append(self.tr("{0} copied").format(count)) |
|
279 elif category == "deleted": |
|
280 cinfo.append(self.tr("{0} deleted").format(count)) |
|
281 elif category == "unknown": |
|
282 cinfo.append(self.tr("{0} unknown").format(count)) |
|
283 elif category == "ignored": |
|
284 cinfo.append(self.tr("{0} ignored").format(count)) |
|
285 elif category == "unresolved": |
|
286 cinfo.append( |
|
287 self.tr("{0} unresolved").format(count)) |
|
288 elif category == "subrepos": |
|
289 cinfo.append(self.tr("{0} subrepos").format(count)) |
|
290 remark = infoDict["commit"][1] |
|
291 if remark == "merge": |
|
292 cinfo.append(self.tr("Merge needed")) |
|
293 elif remark == "new branch": |
|
294 cinfo.append(self.tr("New Branch")) |
|
295 elif remark == "head closed": |
|
296 cinfo.append(self.tr("Head is closed")) |
|
297 elif remark == "clean": |
|
298 cinfo.append(self.tr("No commit required")) |
|
299 elif remark == "new branch head": |
|
300 cinfo.append(self.tr("New Branch Head")) |
|
301 info.append(self.tr( |
|
302 "<tr><td><b>Commit Status</b></td><td>{0}</td></tr>") |
|
303 .format("<br/>".join(cinfo))) |
|
304 if "update" in infoDict: |
|
305 if infoDict["update"][0] == "@CURRENT@": |
|
306 uinfo = self.tr("current") |
|
307 elif infoDict["update"][0] == "@UPDATE@": |
|
308 uinfo = self.tr( |
|
309 "%n new changeset(s)<br/>Update required", "", |
|
310 infoDict["update"][1]) |
|
311 elif infoDict["update"][0] == "@MERGE@": |
|
312 uinfo1 = self.tr( |
|
313 "%n new changeset(s)", "", infoDict["update"][1]) |
|
314 uinfo2 = self.tr( |
|
315 "%n branch head(s)", "", infoDict["update"][2]) |
|
316 uinfo = self.tr( |
|
317 "{0}<br/>{1}<br/>Merge required", |
|
318 "0 is changesets, 1 is branch heads" |
|
319 ).format(uinfo1, uinfo2) |
|
320 else: |
|
321 uinfo = self.tr("unknown status") |
|
322 info.append(self.tr( |
|
323 "<tr><td><b>Update Status</b></td><td>{0}</td></tr>") |
|
324 .format(uinfo)) |
|
325 if "remote" in infoDict: |
|
326 if infoDict["remote"] == (0, 0, 0, 0): |
|
327 rinfo = self.tr("synched") |
|
328 else: |
|
329 li = [] |
|
330 if infoDict["remote"][0]: |
|
331 li.append(self.tr("1 or more incoming changesets")) |
|
332 if infoDict["remote"][1]: |
|
333 li.append(self.tr("%n outgoing changeset(s)", "", |
|
334 infoDict["remote"][1])) |
|
335 if infoDict["remote"][2]: |
|
336 li.append(self.tr("%n incoming bookmark(s)", "", |
|
337 infoDict["remote"][2])) |
|
338 if infoDict["remote"][3]: |
|
339 li.append(self.tr("%n outgoing bookmark(s)", "", |
|
340 infoDict["remote"][3])) |
|
341 rinfo = "<br/>".join(li) |
|
342 info.append(self.tr( |
|
343 "<tr><td><b>Remote Status</b></td><td>{0}</td></tr>") |
|
344 .format(rinfo)) |
|
345 if "mq" in infoDict: |
|
346 if infoDict["mq"] == (0, 0): |
|
347 qinfo = self.tr("empty queue") |
|
348 else: |
|
349 li = [] |
|
350 if infoDict["mq"][0]: |
|
351 li.append(self.tr("{0} applied") |
|
352 .format(infoDict["mq"][0])) |
|
353 if infoDict["mq"][1]: |
|
354 li.append(self.tr("{0} unapplied") |
|
355 .format(infoDict["mq"][1])) |
|
356 qinfo = "<br/>".join(li) |
|
357 info.append(self.tr( |
|
358 "<tr><td><b>Queues Status</b></td><td>{0}</td></tr>") |
|
359 .format(qinfo)) |
|
360 if "largefiles" in infoDict: |
|
361 if infoDict["largefiles"] == 0: |
|
362 lfInfo = self.tr("No files to upload") |
|
363 else: |
|
364 lfInfo = self.tr("%n file(s) to upload", "", |
|
365 infoDict["largefiles"]) |
|
366 info.append(self.tr( |
|
367 "<tr><td><b>Large Files</b></td><td>{0}</td></tr>") |
|
368 .format(lfInfo)) |
|
369 info.append("</table>") |
|
370 else: |
|
371 info = [self.tr("<p>No status information available.</p>")] |
|
372 |
|
373 self.summary.insertHtml("\n".join(info)) |