|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2010 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the version control systems interface to Mercurial. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import shutil |
|
12 import urllib.request, urllib.parse, urllib.error |
|
13 |
|
14 from PyQt4.QtCore import QProcess, SIGNAL |
|
15 from PyQt4.QtGui import QMessageBox, QApplication, QDialog, QInputDialog |
|
16 |
|
17 from E5Gui.E5Application import e5App |
|
18 |
|
19 from VCS.VersionControl import VersionControl |
|
20 from VCS.RepositoryInfoDialog import VcsRepositoryInfoDialog |
|
21 |
|
22 from .HgDialog import HgDialog |
|
23 from .HgCommitDialog import HgCommitDialog |
|
24 from .HgOptionsDialog import HgOptionsDialog |
|
25 from .HgNewProjectOptionsDialog import HgNewProjectOptionsDialog |
|
26 from .HgCopyDialog import HgCopyDialog |
|
27 from .HgLogDialog import HgLogDialog |
|
28 from .HgLogBrowserDialog import HgLogBrowserDialog |
|
29 from .HgDiffDialog import HgDiffDialog |
|
30 from .HgRevisionsSelectionDialog import HgRevisionsSelectionDialog |
|
31 from .HgRevisionSelectionDialog import HgRevisionSelectionDialog |
|
32 from .HgMergeDialog import HgMergeDialog |
|
33 from .HgStatusMonitorThread import HgStatusMonitorThread |
|
34 from .HgStatusDialog import HgStatusDialog |
|
35 from .HgAnnotateDialog import HgAnnotateDialog |
|
36 from .HgTagDialog import HgTagDialog |
|
37 from .HgTagBranchListDialog import HgTagBranchListDialog |
|
38 from .HgCommandDialog import HgCommandDialog |
|
39 |
|
40 from .ProjectBrowserHelper import HgProjectBrowserHelper |
|
41 |
|
42 import Preferences |
|
43 import Utilities |
|
44 |
|
45 class Hg(VersionControl): |
|
46 """ |
|
47 Class implementing the version control systems interface to Mercurial. |
|
48 |
|
49 @signal committed() emitted after the commit action has completed |
|
50 """ |
|
51 def __init__(self, plugin, parent=None, name=None): |
|
52 """ |
|
53 Constructor |
|
54 |
|
55 @param plugin reference to the plugin object |
|
56 @param parent parent widget (QWidget) |
|
57 @param name name of this object (string) |
|
58 """ |
|
59 VersionControl.__init__(self, parent, name) |
|
60 self.defaultOptions = { |
|
61 'global' : [''], |
|
62 'commit' : [''], |
|
63 'checkout' : [''], |
|
64 'update' : [''], |
|
65 'add' : [''], |
|
66 'remove' : [''], |
|
67 'diff' : [''], |
|
68 'log' : [''], |
|
69 'history' : [''], |
|
70 'status' : [''], |
|
71 'tag' : [''], |
|
72 'export' : [''] |
|
73 } |
|
74 |
|
75 self.__plugin = plugin |
|
76 self.__ui = parent |
|
77 |
|
78 self.options = self.defaultOptions |
|
79 self.tagsList = [] |
|
80 self.branchesList = [] |
|
81 self.allTagsBranchesList = [] |
|
82 self.showedTags = False |
|
83 self.showedBranches = False |
|
84 |
|
85 self.tagTypeList = [ |
|
86 'tags', |
|
87 'branches', |
|
88 ] |
|
89 |
|
90 self.commandHistory = [] |
|
91 |
|
92 if "HG_ASP_DOT_NET_HACK" in os.environ: |
|
93 self.adminDir = '_hg' |
|
94 else: |
|
95 self.adminDir = '.hg' |
|
96 |
|
97 self.log = None |
|
98 self.diff = None |
|
99 self.status = None |
|
100 self.tagbranchList = None |
|
101 self.annotate = None |
|
102 |
|
103 self.statusCache = {} |
|
104 |
|
105 self.__commitData = {} |
|
106 self.__commitDialog = None |
|
107 |
|
108 def getPlugin(self): |
|
109 """ |
|
110 Public method to get a reference to the plugin object. |
|
111 |
|
112 @return reference to the plugin object (VcsMercurialPlugin) |
|
113 """ |
|
114 return self.__plugin |
|
115 |
|
116 def vcsShutdown(self): |
|
117 """ |
|
118 Public method used to shutdown the Mercurial interface. |
|
119 """ |
|
120 if self.log is not None: |
|
121 self.log.close() |
|
122 if self.diff is not None: |
|
123 self.diff.close() |
|
124 if self.status is not None: |
|
125 self.status.close() |
|
126 if self.tagbranchList is not None: |
|
127 self.tagbranchList.close() |
|
128 if self.annotate is not None: |
|
129 self.annotate.close() |
|
130 |
|
131 def vcsExists(self): |
|
132 """ |
|
133 Public method used to test for the presence of the hg executable. |
|
134 |
|
135 @return flag indicating the existance (boolean) and an error message (string) |
|
136 """ |
|
137 self.versionStr = '' |
|
138 errMsg = "" |
|
139 ioEncoding = Preferences.getSystem("IOEncoding") |
|
140 |
|
141 process = QProcess() |
|
142 process.start('hg', ['version']) |
|
143 procStarted = process.waitForStarted() |
|
144 if procStarted: |
|
145 finished = process.waitForFinished(30000) |
|
146 if finished and process.exitCode() == 0: |
|
147 output = \ |
|
148 str(process.readAllStandardOutput(), ioEncoding, 'replace') |
|
149 self.versionStr = output.splitlines()[0].split()[-1][0:-1] |
|
150 return True, errMsg |
|
151 else: |
|
152 if finished: |
|
153 errMsg = \ |
|
154 self.trUtf8("The hg process finished with the exit code {0}")\ |
|
155 .format(process.exitCode()) |
|
156 else: |
|
157 errMsg = self.trUtf8("The hg process did not finish within 30s.") |
|
158 else: |
|
159 errMsg = self.trUtf8("Could not start the hg executable.") |
|
160 |
|
161 return False, errMsg |
|
162 |
|
163 def vcsInit(self, vcsDir, noDialog = False): |
|
164 """ |
|
165 Public method used to initialize the mercurial repository. |
|
166 |
|
167 The initialization is done, when a project is converted into a Mercurial |
|
168 controlled project. Therefore we always return TRUE without doing anything. |
|
169 |
|
170 @param vcsDir name of the VCS directory (string) |
|
171 @param noDialog flag indicating quiet operations (boolean) |
|
172 @return always TRUE |
|
173 """ |
|
174 return True |
|
175 |
|
176 def vcsConvertProject(self, vcsDataDict, project): |
|
177 """ |
|
178 Public method to convert an uncontrolled project to a version controlled project. |
|
179 |
|
180 @param vcsDataDict dictionary of data required for the conversion |
|
181 @param project reference to the project object |
|
182 """ |
|
183 success = self.vcsImport(vcsDataDict, project.ppath)[0] |
|
184 if not success: |
|
185 QMessageBox.critical(None, |
|
186 self.trUtf8("Create project repository"), |
|
187 self.trUtf8("""The project repository could not be created.""")) |
|
188 else: |
|
189 pfn = project.pfile |
|
190 if not os.path.isfile(pfn): |
|
191 pfn += "z" |
|
192 project.closeProject() |
|
193 project.openProject(pfn) |
|
194 |
|
195 def vcsImport(self, vcsDataDict, projectDir, noDialog = False): |
|
196 """ |
|
197 Public method used to import the project into the Subversion repository. |
|
198 |
|
199 @param vcsDataDict dictionary of data required for the import |
|
200 @param projectDir project directory (string) |
|
201 @param noDialog flag indicating quiet operations |
|
202 @return flag indicating an execution without errors (boolean) |
|
203 and a flag indicating the version controll status (boolean) |
|
204 """ |
|
205 ignorePatterns = [ |
|
206 "glob:.eric5project", |
|
207 "glob:.ropeproject", |
|
208 "glob:.directory", |
|
209 "glob:*.pyc", |
|
210 "glob:*.orig", |
|
211 "glob:*.bak", |
|
212 ] |
|
213 |
|
214 msg = vcsDataDict["message"] |
|
215 if not msg: |
|
216 msg = '***' |
|
217 |
|
218 args = [] |
|
219 args.append('init') |
|
220 args.append(projectDir) |
|
221 dia = HgDialog(self.trUtf8('Creating Mercurial repository')) |
|
222 res = dia.startProcess(args) |
|
223 if res: |
|
224 dia.exec_() |
|
225 status = dia.normalExit() |
|
226 |
|
227 if status: |
|
228 try: |
|
229 # create a .hgignore file |
|
230 ignore = open(os.path.join(projectDir, ".hgignore"), "w") |
|
231 ignore.write("\n".join(ignorePatterns)) |
|
232 ignore.close() |
|
233 except IOError: |
|
234 status = False |
|
235 |
|
236 if status: |
|
237 args = [] |
|
238 args.append('commit') |
|
239 args.append('--addremove') |
|
240 args.append('--message') |
|
241 args.append(msg) |
|
242 args.append(projectDir) |
|
243 dia = HgDialog(self.trUtf8('Initial commit to Mercurial repository')) |
|
244 res = dia.startProcess(args) |
|
245 if res: |
|
246 dia.exec_() |
|
247 status = dia.normalExit() |
|
248 |
|
249 return status, False |
|
250 |
|
251 def vcsCheckout(self, vcsDataDict, projectDir, noDialog = False): |
|
252 """ |
|
253 Public method used to check the project out of a Mercurial repository (clone). |
|
254 |
|
255 @param vcsDataDict dictionary of data required for the checkout |
|
256 @param projectDir project directory to create (string) |
|
257 @param noDialog flag indicating quiet operations |
|
258 @return flag indicating an execution without errors (boolean) |
|
259 """ |
|
260 noDialog = False |
|
261 try: |
|
262 rev = vcsDataDict["revision"] |
|
263 except KeyError: |
|
264 rev = None |
|
265 vcsUrl = self.hgNormalizeURL(vcsDataDict["url"]) |
|
266 if vcsUrl.startswith('/'): |
|
267 vcsUrl = 'file://%s' % vcsUrl |
|
268 elif vcsUrl[1] in ['|', ':']: |
|
269 vcsUrl = 'file:///%s' % vcsUrl |
|
270 |
|
271 args = [] |
|
272 args.append('clone') |
|
273 self.addArguments(args, self.options['global']) |
|
274 self.addArguments(args, self.options['checkout']) |
|
275 if rev: |
|
276 args.append("--rev") |
|
277 args.append(rev) |
|
278 args.append(self.__hgURL(vcsUrl)) |
|
279 args.append(projectDir) |
|
280 |
|
281 if noDialog: |
|
282 return self.startSynchronizedProcess(QProcess(), 'hg', args) |
|
283 else: |
|
284 dia = HgDialog(self.trUtf8('Cloning project from a Mercurial repository')) |
|
285 res = dia.startProcess(args) |
|
286 if res: |
|
287 dia.exec_() |
|
288 return dia.normalExit() |
|
289 |
|
290 def vcsExport(self, vcsDataDict, projectDir): |
|
291 """ |
|
292 Public method used to export a directory from the Subversion repository. |
|
293 |
|
294 @param vcsDataDict dictionary of data required for the checkout |
|
295 @param projectDir project directory to create (string) |
|
296 @return flag indicating an execution without errors (boolean) |
|
297 """ |
|
298 status = self.vcsCheckout(vcsDataDict, projectDir) |
|
299 shutil.rmtree(os.path.join(projectDir, self.adminDir), True) |
|
300 if os.path.exists(os.path.join(projectDir, '.hgignore')): |
|
301 os.remove(os.path.join(projectDir, '.hgignore')) |
|
302 return status |
|
303 |
|
304 def vcsCommit(self, name, message, noDialog = False, closeBranch = False): |
|
305 """ |
|
306 Public method used to make the change of a file/directory permanent in the |
|
307 Mercurial repository. |
|
308 |
|
309 @param name file/directory name to be committed (string or list of strings) |
|
310 @param message message for this operation (string) |
|
311 @param noDialog flag indicating quiet operations |
|
312 @keyparam closeBranch flag indicating a close branch commit (boolean) |
|
313 """ |
|
314 msg = message |
|
315 |
|
316 if not noDialog and not msg: |
|
317 # call CommitDialog and get message from there |
|
318 if self.__commitDialog is None: |
|
319 self.__commitDialog = HgCommitDialog(self, self.__ui) |
|
320 self.connect(self.__commitDialog, SIGNAL("accepted()"), |
|
321 self.__vcsCommit_Step2) |
|
322 self.__commitDialog.show() |
|
323 self.__commitDialog.raise_() |
|
324 self.__commitDialog.activateWindow() |
|
325 |
|
326 self.__commitData["name"] = name |
|
327 self.__commitData["msg"] = msg |
|
328 self.__commitData["noDialog"] = noDialog |
|
329 self.__commitData["closeBranch"] = closeBranch |
|
330 |
|
331 if noDialog: |
|
332 self.__vcsCommit_Step2() |
|
333 |
|
334 def __vcsCommit_Step2(self): |
|
335 """ |
|
336 Private slot performing the second step of the commit action. |
|
337 """ |
|
338 name = self.__commitData["name"] |
|
339 msg = self.__commitData["msg"] |
|
340 noDialog = self.__commitData["noDialog"] |
|
341 closeBranch = self.__commitData["closeBranch"] |
|
342 |
|
343 if self.__commitDialog is not None: |
|
344 msg = self.__commitDialog.logMessage() |
|
345 self.disconnect(self.__commitDialog, SIGNAL("accepted()"), |
|
346 self.__vcsCommit_Step2) |
|
347 self.__commitDialog = None |
|
348 |
|
349 if not msg: |
|
350 msg = '***' |
|
351 |
|
352 args = [] |
|
353 args.append('commit') |
|
354 self.addArguments(args, self.options['global']) |
|
355 self.addArguments(args, self.options['commit']) |
|
356 args.append("-v") |
|
357 if closeBranch: |
|
358 args.append("--close-branch") |
|
359 args.append("--message") |
|
360 args.append(msg) |
|
361 if isinstance(name, list): |
|
362 dname, fnames = self.splitPathList(name) |
|
363 else: |
|
364 dname, fname = self.splitPath(name) |
|
365 |
|
366 # find the root of the repo |
|
367 repodir = dname |
|
368 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
369 repodir = os.path.dirname(repodir) |
|
370 if repodir == os.sep: |
|
371 return |
|
372 |
|
373 if isinstance(name, list): |
|
374 self.addArguments(args, fnames) |
|
375 else: |
|
376 if dname != repodir or fname != ".": |
|
377 args.append(fname) |
|
378 |
|
379 if noDialog: |
|
380 self.startSynchronizedProcess(QProcess(), "hg", args, dname) |
|
381 else: |
|
382 dia = HgDialog(self.trUtf8('Commiting changes to Mercurial repository')) |
|
383 res = dia.startProcess(args, dname) |
|
384 if res: |
|
385 dia.exec_() |
|
386 self.emit(SIGNAL("committed()")) |
|
387 self.checkVCSStatus() |
|
388 |
|
389 def vcsUpdate(self, name, noDialog = False, revision = None): |
|
390 """ |
|
391 Public method used to update a file/directory with the Mercurial repository. |
|
392 |
|
393 @param name file/directory name to be updated (string or list of strings) |
|
394 @param noDialog flag indicating quiet operations (boolean) |
|
395 @keyparam revision revision to update to (string) |
|
396 @return flag indicating, that the update contained an add |
|
397 or delete (boolean) |
|
398 """ |
|
399 args = [] |
|
400 args.append('update') |
|
401 self.addArguments(args, self.options['global']) |
|
402 self.addArguments(args, self.options['update']) |
|
403 if revision is not None: |
|
404 args.append("-r") |
|
405 args.append(revision) |
|
406 |
|
407 if isinstance(name, list): |
|
408 dname, fnames = self.splitPathList(name) |
|
409 else: |
|
410 dname, fname = self.splitPath(name) |
|
411 |
|
412 # find the root of the repo |
|
413 repodir = dname |
|
414 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
415 repodir = os.path.dirname(repodir) |
|
416 if repodir == os.sep: |
|
417 return False |
|
418 |
|
419 if noDialog: |
|
420 self.startSynchronizedProcess(QProcess(), "hg", args, repodir) |
|
421 res = False |
|
422 else: |
|
423 dia = HgDialog(self.trUtf8('Synchronizing with the Mercurial repository')) |
|
424 res = dia.startProcess(args, repodir) |
|
425 if res: |
|
426 dia.exec_() |
|
427 res = dia.hasAddOrDelete() |
|
428 self.checkVCSStatus() |
|
429 return res |
|
430 |
|
431 def vcsAdd(self, name, isDir = False, noDialog = False): |
|
432 """ |
|
433 Public method used to add a file/directory to the Mercurial repository. |
|
434 |
|
435 @param name file/directory name to be added (string) |
|
436 @param isDir flag indicating name is a directory (boolean) |
|
437 @param noDialog flag indicating quiet operations |
|
438 """ |
|
439 args = [] |
|
440 args.append('add') |
|
441 self.addArguments(args, self.options['global']) |
|
442 self.addArguments(args, self.options['add']) |
|
443 args.append("-v") |
|
444 |
|
445 if isinstance(name, list): |
|
446 if isDir: |
|
447 dname, fname = os.path.split(name[0]) |
|
448 else: |
|
449 dname, fnames = self.splitPathList(name) |
|
450 else: |
|
451 if isDir: |
|
452 dname, fname = os.path.split(name) |
|
453 else: |
|
454 dname, fname = self.splitPath(name) |
|
455 |
|
456 # find the root of the repo |
|
457 repodir = dname |
|
458 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
459 repodir = os.path.dirname(repodir) |
|
460 if repodir == os.sep: |
|
461 return |
|
462 |
|
463 if isinstance(name, list): |
|
464 self.addArguments(args, name) |
|
465 else: |
|
466 args.append(name) |
|
467 |
|
468 if noDialog: |
|
469 self.startSynchronizedProcess(QProcess(), "hg", args, repodir) |
|
470 else: |
|
471 dia = HgDialog(\ |
|
472 self.trUtf8('Adding files/directories to the Mercurial repository')) |
|
473 res = dia.startProcess(args, repodir) |
|
474 if res: |
|
475 dia.exec_() |
|
476 |
|
477 def vcsAddBinary(self, name, isDir = False): |
|
478 """ |
|
479 Public method used to add a file/directory in binary mode to the |
|
480 Mercurial repository. |
|
481 |
|
482 @param name file/directory name to be added (string) |
|
483 @param isDir flag indicating name is a directory (boolean) |
|
484 """ |
|
485 self.vcsAdd(name, isDir) |
|
486 |
|
487 def vcsAddTree(self, path): |
|
488 """ |
|
489 Public method to add a directory tree rooted at path to the Mercurial repository. |
|
490 |
|
491 @param path root directory of the tree to be added (string or list of strings)) |
|
492 """ |
|
493 self.vcsAdd(path, isDir = False) |
|
494 |
|
495 def vcsRemove(self, name, project = False, noDialog = False): |
|
496 """ |
|
497 Public method used to remove a file/directory from the Mercurial repository. |
|
498 |
|
499 The default operation is to remove the local copy as well. |
|
500 |
|
501 @param name file/directory name to be removed (string or list of strings)) |
|
502 @param project flag indicating deletion of a project tree (boolean) (not needed) |
|
503 @param noDialog flag indicating quiet operations |
|
504 @return flag indicating successfull operation (boolean) |
|
505 """ |
|
506 args = [] |
|
507 args.append('remove') |
|
508 self.addArguments(args, self.options['global']) |
|
509 self.addArguments(args, self.options['remove']) |
|
510 args.append("-v") |
|
511 if noDialog and '--force' not in args: |
|
512 args.append('--force') |
|
513 |
|
514 if isinstance(name, list): |
|
515 dname, fnames = self.splitPathList(name) |
|
516 self.addArguments(args, name) |
|
517 else: |
|
518 dname, fname = self.splitPath(name) |
|
519 args.append(name) |
|
520 |
|
521 # find the root of the repo |
|
522 repodir = dname |
|
523 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
524 repodir = os.path.dirname(repodir) |
|
525 if repodir == os.sep: |
|
526 return False |
|
527 |
|
528 if noDialog: |
|
529 res = self.startSynchronizedProcess(QProcess(), "hg", args, repodir) |
|
530 else: |
|
531 dia = HgDialog(\ |
|
532 self.trUtf8('Removing files/directories from the Mercurial repository')) |
|
533 res = dia.startProcess(args, repodir) |
|
534 if res: |
|
535 dia.exec_() |
|
536 res = dia.normalExitWithoutErrors() |
|
537 |
|
538 return res |
|
539 |
|
540 def vcsMove(self, name, project, target = None, noDialog = False): |
|
541 """ |
|
542 Public method used to move a file/directory. |
|
543 |
|
544 @param name file/directory name to be moved (string) |
|
545 @param project reference to the project object |
|
546 @param target new name of the file/directory (string) |
|
547 @param noDialog flag indicating quiet operations |
|
548 @return flag indicating successfull operation (boolean) |
|
549 """ |
|
550 isDir = os.path.isdir(name) |
|
551 opts = self.options['global'][:] |
|
552 force = '--force' in opts |
|
553 if force: |
|
554 opts.remove('--force') |
|
555 |
|
556 res = False |
|
557 if noDialog: |
|
558 if target is None: |
|
559 return False |
|
560 force = True |
|
561 accepted = True |
|
562 else: |
|
563 dlg = HgCopyDialog(name, None, True, force) |
|
564 accepted = dlg.exec_() == QDialog.Accepted |
|
565 if accepted: |
|
566 target, force = dlg.getData() |
|
567 |
|
568 if accepted: |
|
569 args = [] |
|
570 args.append('rename') |
|
571 self.addArguments(args, opts) |
|
572 args.append("-v") |
|
573 if force: |
|
574 args.append('--force') |
|
575 args.append(name) |
|
576 args.append(target) |
|
577 |
|
578 dname, fname = self.splitPath(name) |
|
579 # find the root of the repo |
|
580 repodir = dname |
|
581 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
582 repodir = os.path.dirname(repodir) |
|
583 if repodir == os.sep: |
|
584 return False |
|
585 |
|
586 if noDialog: |
|
587 res = self.startSynchronizedProcess(QProcess(), "hg", args, repodir) |
|
588 else: |
|
589 dia = HgDialog(self.trUtf8('Renaming {0}').format(name)) |
|
590 res = dia.startProcess(args, repodir) |
|
591 if res: |
|
592 dia.exec_() |
|
593 res = dia.normalExit() |
|
594 if res: |
|
595 if target.startswith(project.getProjectPath()): |
|
596 if isDir: |
|
597 project.moveDirectory(name, target) |
|
598 else: |
|
599 project.renameFileInPdata(name, target) |
|
600 else: |
|
601 if isDir: |
|
602 project.removeDirectory(name) |
|
603 else: |
|
604 project.removeFile(name) |
|
605 return res |
|
606 |
|
607 def vcsLog(self, name): |
|
608 """ |
|
609 Public method used to view the log of a file/directory from the |
|
610 Mercurial repository. |
|
611 |
|
612 @param name file/directory name to show the log of (string) |
|
613 """ |
|
614 self.log = HgLogDialog(self) |
|
615 self.log.show() |
|
616 self.log.start(name) |
|
617 |
|
618 def vcsDiff(self, name): |
|
619 """ |
|
620 Public method used to view the difference of a file/directory to the |
|
621 Mercurial repository. |
|
622 |
|
623 If name is a directory and is the project directory, all project files |
|
624 are saved first. If name is a file (or list of files), which is/are being edited |
|
625 and has unsaved modification, they can be saved or the operation may be aborted. |
|
626 |
|
627 @param name file/directory name to be diffed (string) |
|
628 """ |
|
629 if isinstance(name, list): |
|
630 names = name[:] |
|
631 else: |
|
632 names = [name] |
|
633 for nam in names: |
|
634 if os.path.isfile(nam): |
|
635 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
|
636 if editor and not editor.checkDirty() : |
|
637 return |
|
638 else: |
|
639 project = e5App().getObject("Project") |
|
640 if nam == project.ppath and not project.saveAllScripts(): |
|
641 return |
|
642 self.diff = HgDiffDialog(self) |
|
643 self.diff.show() |
|
644 QApplication.processEvents() |
|
645 self.diff.start(name) |
|
646 |
|
647 def vcsStatus(self, name): |
|
648 """ |
|
649 Public method used to view the status of files/directories in the |
|
650 Mercurial repository. |
|
651 |
|
652 @param name file/directory name(s) to show the status of |
|
653 (string or list of strings) |
|
654 """ |
|
655 self.status = HgStatusDialog(self) |
|
656 self.status.show() |
|
657 self.status.start(name) |
|
658 |
|
659 def vcsTag(self, name): |
|
660 """ |
|
661 Public method used to set the tag in the Mercurial repository. |
|
662 |
|
663 @param name file/directory name to be tagged (string) |
|
664 """ |
|
665 dname, fname = self.splitPath(name) |
|
666 |
|
667 # find the root of the repo |
|
668 repodir = str(dname) |
|
669 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
670 repodir = os.path.dirname(repodir) |
|
671 if repodir == os.sep: |
|
672 return |
|
673 |
|
674 dlg = HgTagDialog(self.tagsList) |
|
675 if dlg.exec_() == QDialog.Accepted: |
|
676 tag, tagOp = dlg.getParameters() |
|
677 if tag in self.tagsList: |
|
678 self.tagsList.remove(tag) |
|
679 self.tagsList.insert(0, tag) |
|
680 else: |
|
681 return |
|
682 |
|
683 args = [] |
|
684 args.append('tag') |
|
685 if tagOp == HgTagDialog.CreateLocalTag: |
|
686 args.append('--local') |
|
687 elif tagOp == HgTagDialog.DeleteTag: |
|
688 args.append('--remove') |
|
689 args.append('--message') |
|
690 if tagOp != HgTagDialog.DeleteTag: |
|
691 args.append("Created tag <{0}>.".format(tag)) |
|
692 else: |
|
693 args.append("Removed tag <{0}>.".format(tag)) |
|
694 args.append(tag) |
|
695 |
|
696 dia = HgDialog(self.trUtf8('Taging in the Mercurial repository')) |
|
697 res = dia.startProcess(args, repodir) |
|
698 if res: |
|
699 dia.exec_() |
|
700 |
|
701 def vcsRevert(self, name): |
|
702 """ |
|
703 Public method used to revert changes made to a file/directory. |
|
704 |
|
705 @param name file/directory name to be reverted (string) |
|
706 """ |
|
707 args = [] |
|
708 args.append('revert') |
|
709 self.addArguments(args, self.options['global']) |
|
710 args.append("--no-backup") |
|
711 args.append("-v") |
|
712 if isinstance(name, list): |
|
713 dname, fnames = self.splitPathList(name) |
|
714 self.addArguments(args, name) |
|
715 else: |
|
716 dname, fname = self.splitPath(name) |
|
717 args.append(name) |
|
718 |
|
719 # find the root of the repo |
|
720 repodir = dname |
|
721 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
722 repodir = os.path.dirname(repodir) |
|
723 if repodir == os.sep: |
|
724 return |
|
725 |
|
726 dia = HgDialog(self.trUtf8('Reverting changes')) |
|
727 res = dia.startProcess(args, repodir) |
|
728 if res: |
|
729 dia.exec_() |
|
730 self.checkVCSStatus() |
|
731 |
|
732 def vcsMerge(self, name): |
|
733 """ |
|
734 Public method used to merge a URL/revision into the local project. |
|
735 |
|
736 @param name file/directory name to be merged (string) |
|
737 """ |
|
738 dname, fname = self.splitPath(name) |
|
739 |
|
740 opts = self.options['global'][:] |
|
741 force = '--force' in opts |
|
742 if force: |
|
743 del opts[opts.index('--force')] |
|
744 |
|
745 dlg = HgMergeDialog(force, self.tagsList, self.branchesList) |
|
746 if dlg.exec_() == QDialog.Accepted: |
|
747 rev, force = dlg.getParameters() |
|
748 else: |
|
749 return |
|
750 |
|
751 # find the root of the repo |
|
752 repodir = dname |
|
753 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
754 repodir = os.path.dirname(repodir) |
|
755 if repodir == os.sep: |
|
756 return |
|
757 |
|
758 args = [] |
|
759 args.append('merge') |
|
760 self.addArguments(args, opts) |
|
761 if force: |
|
762 args.append("--force") |
|
763 if rev: |
|
764 args.append("--rev") |
|
765 args.append(rev) |
|
766 |
|
767 dia = HgDialog(self.trUtf8('Merging').format(name)) |
|
768 res = dia.startProcess(args, repodir) |
|
769 if res: |
|
770 dia.exec_() |
|
771 self.checkVCSStatus() |
|
772 |
|
773 def vcsSwitch(self, name): |
|
774 """ |
|
775 Public method used to switch a working directory to a different revision. |
|
776 |
|
777 @param name directory name to be switched (string) |
|
778 """ |
|
779 dlg = HgRevisionSelectionDialog(self.tagsList, self.branchesList) |
|
780 if dlg.exec_() == QDialog.Accepted: |
|
781 rev = dlg.getRevision() |
|
782 self.vcsUpdate(name, revision = rev) |
|
783 |
|
784 def vcsRegisteredState(self, name): |
|
785 """ |
|
786 Public method used to get the registered state of a file in the vcs. |
|
787 |
|
788 @param name filename to check (string) |
|
789 @return a combination of canBeCommited and canBeAdded |
|
790 """ |
|
791 if name.endswith(os.sep): |
|
792 name = name[:-1] |
|
793 dname, fname = self.splitPath(name) |
|
794 |
|
795 if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)): |
|
796 return self.canBeCommitted |
|
797 |
|
798 # find the root of the repo |
|
799 repodir = dname |
|
800 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
801 repodir = os.path.dirname(repodir) |
|
802 if repodir == os.sep: |
|
803 return 0 |
|
804 |
|
805 ioEncoding = Preferences.getSystem("IOEncoding") |
|
806 process = QProcess() |
|
807 args = [] |
|
808 args.append('status') |
|
809 args.append('--all') |
|
810 args.append('--noninteractive') |
|
811 process.setWorkingDirectory(repodir) |
|
812 process.start('hg', args) |
|
813 procStarted = process.waitForStarted() |
|
814 if procStarted: |
|
815 finished = process.waitForFinished(30000) |
|
816 if finished and process.exitCode() == 0: |
|
817 output = \ |
|
818 str(process.readAllStandardOutput(), ioEncoding, 'replace') |
|
819 for line in output.splitlines(): |
|
820 flag, path = line.split(" ", 1) |
|
821 absname = os.path.join(repodir, os.path.normcase(path)) |
|
822 if flag not in "?I": |
|
823 if fname == '.': |
|
824 if absname.startswith(dname): |
|
825 return self.canBeCommitted |
|
826 else: |
|
827 if absname == name: |
|
828 return self.canBeCommitted |
|
829 |
|
830 return self.canBeAdded |
|
831 |
|
832 def vcsAllRegisteredStates(self, names, dname, shortcut = True): |
|
833 """ |
|
834 Public method used to get the registered states of a number of files in the vcs. |
|
835 |
|
836 <b>Note:</b> If a shortcut is to be taken, the code will only check, if the named |
|
837 directory has been scanned already. If so, it is assumed, that the states for |
|
838 all files have been populated by the previous run. |
|
839 |
|
840 @param names dictionary with all filenames to be checked as keys |
|
841 @param dname directory to check in (string) |
|
842 @param shortcut flag indicating a shortcut should be taken (boolean) |
|
843 @return the received dictionary completed with a combination of |
|
844 canBeCommited and canBeAdded or None in order to signal an error |
|
845 """ |
|
846 if dname.endswith(os.sep): |
|
847 dname = dname[:-1] |
|
848 |
|
849 found = False |
|
850 for name in list(self.statusCache.keys()): |
|
851 if os.path.dirname(name) == dname: |
|
852 if shortcut: |
|
853 found = True |
|
854 break |
|
855 if name in names: |
|
856 found = True |
|
857 names[name] = self.statusCache[name] |
|
858 |
|
859 if not found: |
|
860 # find the root of the repo |
|
861 repodir = dname |
|
862 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
863 repodir = os.path.dirname(repodir) |
|
864 if repodir == os.sep: |
|
865 return names |
|
866 |
|
867 ioEncoding = Preferences.getSystem("IOEncoding") |
|
868 process = QProcess() |
|
869 args = [] |
|
870 args.append('status') |
|
871 args.append('--all') |
|
872 args.append('--noninteractive') |
|
873 process.setWorkingDirectory(dname) |
|
874 process.start('hg', args) |
|
875 procStarted = process.waitForStarted() |
|
876 if procStarted: |
|
877 finished = process.waitForFinished(30000) |
|
878 if finished and process.exitCode() == 0: |
|
879 dirs = [x for x in names.keys() if os.path.isdir(x)] |
|
880 output = \ |
|
881 str(process.readAllStandardOutput(), ioEncoding, 'replace') |
|
882 for line in output.splitlines(): |
|
883 flag, path = line.split(" ", 1) |
|
884 name = os.path.join(repodir, os.path.normcase(path)) |
|
885 if name.startswith(dname): |
|
886 if flag not in "?I": |
|
887 if name in names: |
|
888 names[name] = self.canBeCommitted |
|
889 dirName = os.path.dirname(name) |
|
890 if dirName in names: |
|
891 names[dirName] = self.canBeCommitted |
|
892 self.statusCache[name] = self.canBeCommitted |
|
893 self.statusCache[dirName] = self.canBeCommitted |
|
894 if dirs: |
|
895 for d in dirs: |
|
896 if name.startswith(d): |
|
897 names[d] = self.canBeCommitted |
|
898 dirs.remove(d) |
|
899 break |
|
900 else: |
|
901 self.statusCache[name] = self.canBeAdded |
|
902 dirName = os.path.dirname(name) |
|
903 if dirName not in self.statusCache: |
|
904 self.statusCache[dirName] = self.canBeAdded |
|
905 |
|
906 return names |
|
907 |
|
908 def clearStatusCache(self): |
|
909 """ |
|
910 Public method to clear the status cache. |
|
911 """ |
|
912 self.statusCache = {} |
|
913 |
|
914 def vcsName(self): |
|
915 """ |
|
916 Public method returning the name of the vcs. |
|
917 |
|
918 @return always 'Mercurial' (string) |
|
919 """ |
|
920 return "Mercurial" |
|
921 |
|
922 def vcsCleanup(self, name): |
|
923 """ |
|
924 Public method used to cleanup the working directory. |
|
925 |
|
926 @param name directory name to be cleaned up (string) |
|
927 """ |
|
928 patterns = ['*.orig', '*.rej'] |
|
929 |
|
930 entries = [] |
|
931 for pat in patterns: |
|
932 entries.extend(Utilities.direntries(name, True, pat)) |
|
933 |
|
934 for entry in entries: |
|
935 try: |
|
936 os.remove(entry) |
|
937 except OSError: |
|
938 pass |
|
939 |
|
940 def vcsCommandLine(self, name): |
|
941 """ |
|
942 Public method used to execute arbitrary mercurial commands. |
|
943 |
|
944 @param name directory name of the working directory (string) |
|
945 """ |
|
946 dlg = HgCommandDialog(self.commandHistory, name) |
|
947 if dlg.exec_() == QDialog.Accepted: |
|
948 command = dlg.getData() |
|
949 commandList = Utilities.parseOptionString(command) |
|
950 |
|
951 # This moves any previous occurrence of these arguments to the head |
|
952 # of the list. |
|
953 if command in self.commandHistory: |
|
954 self.commandHistory.remove(command) |
|
955 self.commandHistory.insert(0, command) |
|
956 |
|
957 args = [] |
|
958 self.addArguments(args, commandList) |
|
959 |
|
960 # find the root of the repo |
|
961 repodir = name |
|
962 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
963 repodir = os.path.dirname(repodir) |
|
964 if repodir == os.sep: |
|
965 return |
|
966 |
|
967 dia = HgDialog(self.trUtf8('Mercurial command')) |
|
968 res = dia.startProcess(args, repodir) |
|
969 if res: |
|
970 dia.exec_() |
|
971 |
|
972 def vcsOptionsDialog(self, project, archive, editable = False, parent = None): |
|
973 """ |
|
974 Public method to get a dialog to enter repository info. |
|
975 |
|
976 @param project reference to the project object |
|
977 @param archive name of the project in the repository (string) |
|
978 @param editable flag indicating that the project name is editable (boolean) |
|
979 @param parent parent widget (QWidget) |
|
980 """ |
|
981 return HgOptionsDialog(self, project, parent) |
|
982 |
|
983 def vcsNewProjectOptionsDialog(self, parent = None): |
|
984 """ |
|
985 Public method to get a dialog to enter repository info for getting a new project. |
|
986 |
|
987 @param parent parent widget (QWidget) |
|
988 """ |
|
989 return HgNewProjectOptionsDialog(self, parent) |
|
990 |
|
991 def vcsRepositoryInfos(self, ppath): |
|
992 """ |
|
993 Public method to retrieve information about the repository. |
|
994 |
|
995 @param ppath local path to get the repository infos (string) |
|
996 @return string with ready formated info for display (string) |
|
997 """ |
|
998 info = [] |
|
999 |
|
1000 process = QProcess() |
|
1001 args = [] |
|
1002 args.append('parents') |
|
1003 args.append('--template') |
|
1004 args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@' |
|
1005 '{date|isodate}@@@{branches}\n') |
|
1006 process.setWorkingDirectory(ppath) |
|
1007 process.start('hg', args) |
|
1008 procStarted = process.waitForStarted() |
|
1009 if procStarted: |
|
1010 finished = process.waitForFinished(30000) |
|
1011 if finished and process.exitCode() == 0: |
|
1012 output = str(process.readAllStandardOutput(), |
|
1013 Preferences.getSystem("IOEncoding"), 'replace') |
|
1014 index = 0 |
|
1015 for line in output.splitlines(): |
|
1016 index += 1 |
|
1017 changeset, tags, author, date, branches = line.split("@@@") |
|
1018 cdate, ctime = date.split()[:2] |
|
1019 info.append("""<p><table>""") |
|
1020 info.append(QApplication.translate("mercurial", |
|
1021 """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n""" |
|
1022 """<tr><td><b>Changeset</b></td><td>{1}</td></tr>""")\ |
|
1023 .format(index, changeset)) |
|
1024 if tags: |
|
1025 info.append(QApplication.translate("mercurial", |
|
1026 """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")\ |
|
1027 .format('<br/>'.join(tags.split()))) |
|
1028 if branches: |
|
1029 info.append(QApplication.translate("mercurial", |
|
1030 """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")\ |
|
1031 .format('<br/>'.join(branches.split()))) |
|
1032 info.append(QApplication.translate("mercurial", |
|
1033 """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n""" |
|
1034 """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n""" |
|
1035 """<tr><td><b>Committed time</b></td><td>{2}</td></tr>""")\ |
|
1036 .format(author, cdate, ctime)) |
|
1037 info.append("""</table></p>""") |
|
1038 |
|
1039 url = "" |
|
1040 args = [] |
|
1041 args.append('showconfig') |
|
1042 args.append('paths.default') |
|
1043 process.setWorkingDirectory(ppath) |
|
1044 process.start('hg', args) |
|
1045 procStarted = process.waitForStarted() |
|
1046 if procStarted: |
|
1047 finished = process.waitForFinished(30000) |
|
1048 if finished and process.exitCode() == 0: |
|
1049 output = str(process.readAllStandardOutput(), |
|
1050 Preferences.getSystem("IOEncoding"), 'replace') |
|
1051 url = output.splitlines()[0].strip() |
|
1052 |
|
1053 return QApplication.translate('mercurial', |
|
1054 """<h3>Repository information</h3>\n""" |
|
1055 """<p><table>\n""" |
|
1056 """<tr><td><b>Mercurial V.</b></td><td>{0}</td></tr>\n""" |
|
1057 """<tr></tr>\n""" |
|
1058 """<tr><td><b>URL</b></td><td>{1}</td></tr>\n""" |
|
1059 """</table></p>\n""" |
|
1060 """{2}""" |
|
1061 ).format(self.versionStr, url, "\n".join(info)) |
|
1062 |
|
1063 ############################################################################ |
|
1064 ## Private Subversion specific methods are below. |
|
1065 ############################################################################ |
|
1066 |
|
1067 def __hgURL(self, url): |
|
1068 """ |
|
1069 Private method to format a url for Mercurial. |
|
1070 |
|
1071 @param url unformatted url string (string) |
|
1072 @return properly formated url for subversion (string) |
|
1073 """ |
|
1074 url = self.hgNormalizeURL(url) |
|
1075 url = url.split(':', 2) |
|
1076 if len(url) == 4: |
|
1077 scheme = url[0] |
|
1078 user = url[1] |
|
1079 host = url[2] |
|
1080 port, path = url[3].split("/",1) |
|
1081 return "%s:%s:%s:%s/%s" % (scheme, user, host, port, urllib.parse.quote(path)) |
|
1082 elif len(url) == 3: |
|
1083 scheme = url[0] |
|
1084 host = url[1] |
|
1085 port, path = url[2].split("/",1) |
|
1086 return "%s:%s:%s/%s" % (scheme, host, port, urllib.parse.quote(path)) |
|
1087 else: |
|
1088 scheme = url[0] |
|
1089 if scheme == "file": |
|
1090 return "%s:%s" % (scheme, urllib.parse.quote(url[1])) |
|
1091 else: |
|
1092 host, path = url[1][2:].split("/",1) |
|
1093 return "%s://%s/%s" % (scheme, host, urllib.parse.quote(path)) |
|
1094 |
|
1095 def hgNormalizeURL(self, url): |
|
1096 """ |
|
1097 Public method to normalize a url for Mercurial. |
|
1098 |
|
1099 @param url url string (string) |
|
1100 @return properly normalized url for subversion (string) |
|
1101 """ |
|
1102 url = url.replace('\\', '/') |
|
1103 if url.endswith('/'): |
|
1104 url = url[:-1] |
|
1105 urll = url.split('//') |
|
1106 return "%s//%s" % (urll[0], '/'.join(urll[1:])) |
|
1107 |
|
1108 def hgCopy(self, name, project): |
|
1109 """ |
|
1110 Public method used to copy a file/directory. |
|
1111 |
|
1112 @param name file/directory name to be copied (string) |
|
1113 @param project reference to the project object |
|
1114 @return flag indicating successfull operation (boolean) |
|
1115 """ |
|
1116 dlg = HgCopyDialog(name) |
|
1117 res = False |
|
1118 if dlg.exec_() == QDialog.Accepted: |
|
1119 target, force = dlg.getData() |
|
1120 |
|
1121 args = [] |
|
1122 args.append('copy') |
|
1123 self.addArguments(args, self.options['global']) |
|
1124 args.append("-v") |
|
1125 args.append(name) |
|
1126 args.append(target) |
|
1127 |
|
1128 dname, fname = self.splitPath(name) |
|
1129 # find the root of the repo |
|
1130 repodir = dname |
|
1131 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1132 repodir = os.path.dirname(repodir) |
|
1133 if repodir == os.sep: |
|
1134 return False |
|
1135 |
|
1136 dia = HgDialog(self.trUtf8('Copying {0}') |
|
1137 .format(name)) |
|
1138 res = dia.startProcess(args, repodir) |
|
1139 if res: |
|
1140 dia.exec_() |
|
1141 res = dia.normalExit() |
|
1142 if res and \ |
|
1143 target.startswith(project.getProjectPath()): |
|
1144 if os.path.isdir(name): |
|
1145 project.copyDirectory(name, target) |
|
1146 else: |
|
1147 project.appendFile(target) |
|
1148 return res |
|
1149 |
|
1150 def hgListTagBranch(self, path, tags = True): |
|
1151 """ |
|
1152 Public method used to list the available tags or branches. |
|
1153 |
|
1154 @param path directory name of the project (string) |
|
1155 @param tags flag indicating listing of branches or tags |
|
1156 (False = branches, True = tags) |
|
1157 """ |
|
1158 self.tagbranchList = HgTagBranchListDialog(self) |
|
1159 self.tagbranchList.show() |
|
1160 if tags: |
|
1161 if not self.showedTags: |
|
1162 self.showedTags = True |
|
1163 allTagsBranchesList = self.allTagsBranchesList |
|
1164 else: |
|
1165 self.tagsList = [] |
|
1166 allTagsBranchesList = None |
|
1167 self.tagbranchList.start(path, tags, |
|
1168 self.tagsList, allTagsBranchesList) |
|
1169 else: |
|
1170 if not self.showedBranches: |
|
1171 self.showedBranches = True |
|
1172 allTagsBranchesList = self.allTagsBranchesList |
|
1173 else: |
|
1174 self.branchesList = [] |
|
1175 allTagsBranchesList = None |
|
1176 self.tagbranchList.start(path, tags, |
|
1177 self.branchesList, self.allTagsBranchesList) |
|
1178 |
|
1179 def hgAnnotate(self, name): |
|
1180 """ |
|
1181 Public method to show the output of the hg annotate command. |
|
1182 |
|
1183 @param name file name to show the annotations for (string) |
|
1184 """ |
|
1185 self.annotate = HgAnnotateDialog(self) |
|
1186 self.annotate.show() |
|
1187 self.annotate.start(name) |
|
1188 |
|
1189 def hgExtendedDiff(self, name): |
|
1190 """ |
|
1191 Public method used to view the difference of a file/directory to the |
|
1192 Mercurial repository. |
|
1193 |
|
1194 If name is a directory and is the project directory, all project files |
|
1195 are saved first. If name is a file (or list of files), which is/are being edited |
|
1196 and has unsaved modification, they can be saved or the operation may be aborted. |
|
1197 |
|
1198 This method gives the chance to enter the revisions to be compared. |
|
1199 |
|
1200 @param name file/directory name to be diffed (string) |
|
1201 """ |
|
1202 if isinstance(name, list): |
|
1203 names = name[:] |
|
1204 else: |
|
1205 names = [name] |
|
1206 for nam in names: |
|
1207 if os.path.isfile(nam): |
|
1208 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
|
1209 if editor and not editor.checkDirty() : |
|
1210 return |
|
1211 else: |
|
1212 project = e5App().getObject("Project") |
|
1213 if nam == project.ppath and not project.saveAllScripts(): |
|
1214 return |
|
1215 dlg = HgRevisionsSelectionDialog(self.tagsList, self.branchesList) |
|
1216 if dlg.exec_() == QDialog.Accepted: |
|
1217 revisions = dlg.getRevisions() |
|
1218 self.diff = HgDiffDialog(self) |
|
1219 self.diff.show() |
|
1220 self.diff.start(name, revisions) |
|
1221 |
|
1222 def hgLogLimited(self, name): |
|
1223 """ |
|
1224 Public method used to view the (limited) log of a file/directory from the |
|
1225 Mercurial repository. |
|
1226 |
|
1227 @param name file/directory name to show the log of (string) |
|
1228 """ |
|
1229 noEntries, ok = QInputDialog.getInteger(\ |
|
1230 None, |
|
1231 self.trUtf8("Mercurial Log"), |
|
1232 self.trUtf8("Select number of entries to show."), |
|
1233 self.getPlugin().getPreferences("LogLimit"), 1, 999999, 1) |
|
1234 if ok: |
|
1235 self.log = HgLogDialog(self) |
|
1236 self.log.show() |
|
1237 self.log.start(name, noEntries) |
|
1238 |
|
1239 def hgLogBrowser(self, path): |
|
1240 """ |
|
1241 Public method used to browse the log of a file/directory from the |
|
1242 Mercurial repository. |
|
1243 |
|
1244 @param path file/directory name to show the log of (string) |
|
1245 """ |
|
1246 self.logBrowser = HgLogBrowserDialog(self) |
|
1247 self.logBrowser.show() |
|
1248 self.logBrowser.start(path) |
|
1249 |
|
1250 def hgIncoming(self, name): |
|
1251 """ |
|
1252 Public method used to view the log of incoming changes from the |
|
1253 Mercurial repository. |
|
1254 |
|
1255 @param name file/directory name to show the log of (string) |
|
1256 """ |
|
1257 self.log = HgLogDialog(self, mode = "incoming") |
|
1258 self.log.show() |
|
1259 self.log.start(name) |
|
1260 |
|
1261 def hgOutgoing(self, name): |
|
1262 """ |
|
1263 Public method used to view the log of outgoing changes from the |
|
1264 Mercurial repository. |
|
1265 |
|
1266 @param name file/directory name to show the log of (string) |
|
1267 """ |
|
1268 self.log = HgLogDialog(self, mode = "outgoing") |
|
1269 self.log.show() |
|
1270 self.log.start(name) |
|
1271 |
|
1272 def hgPull(self, name): |
|
1273 """ |
|
1274 Public method used to pull changes from a remote Mercurial repository. |
|
1275 |
|
1276 @param name directory name of the project to be pulled to (string) |
|
1277 """ |
|
1278 args = [] |
|
1279 args.append('pull') |
|
1280 self.addArguments(args, self.options['global']) |
|
1281 args.append('-v') |
|
1282 |
|
1283 # find the root of the repo |
|
1284 repodir = self.splitPath(name)[0] |
|
1285 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1286 repodir = os.path.dirname(repodir) |
|
1287 if repodir == os.sep: |
|
1288 return |
|
1289 |
|
1290 dia = HgDialog(self.trUtf8('Pulling from a remote Mercurial repository')) |
|
1291 res = dia.startProcess(args, repodir) |
|
1292 if res: |
|
1293 dia.exec_() |
|
1294 res = dia.hasAddOrDelete() |
|
1295 self.checkVCSStatus() |
|
1296 |
|
1297 def hgPush(self, name): |
|
1298 """ |
|
1299 Public method used to push changes to a remote Mercurial repository. |
|
1300 |
|
1301 @param name directory name of the project to be pushed from (string) |
|
1302 """ |
|
1303 args = [] |
|
1304 args.append('push') |
|
1305 self.addArguments(args, self.options['global']) |
|
1306 args.append('-v') |
|
1307 |
|
1308 # find the root of the repo |
|
1309 repodir = self.splitPath(name)[0] |
|
1310 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1311 repodir = os.path.dirname(repodir) |
|
1312 if repodir == os.sep: |
|
1313 return |
|
1314 |
|
1315 dia = HgDialog(self.trUtf8('Pushing to a remote Mercurial repository')) |
|
1316 res = dia.startProcess(args, repodir) |
|
1317 if res: |
|
1318 dia.exec_() |
|
1319 res = dia.hasAddOrDelete() |
|
1320 self.checkVCSStatus() |
|
1321 |
|
1322 def hgInfo(self, ppath, mode = "heads"): |
|
1323 """ |
|
1324 Public method to show information about the heads of the repository. |
|
1325 |
|
1326 @param ppath local path to get the repository infos (string) |
|
1327 @keyparam mode mode of the operation (string, one of heads, parents, tip) |
|
1328 """ |
|
1329 if mode not in ("heads", "parents", "tip"): |
|
1330 mode = "heads" |
|
1331 |
|
1332 info = [] |
|
1333 |
|
1334 # find the root of the repo |
|
1335 repodir = self.splitPath(ppath)[0] |
|
1336 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1337 repodir = os.path.dirname(repodir) |
|
1338 if repodir == os.sep: |
|
1339 return |
|
1340 |
|
1341 process = QProcess() |
|
1342 args = [] |
|
1343 args.append(mode) |
|
1344 args.append('--template') |
|
1345 args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@' |
|
1346 '{date|isodate}@@@{branches}@@@{parents}\n') |
|
1347 |
|
1348 process.setWorkingDirectory(repodir) |
|
1349 process.start('hg', args) |
|
1350 procStarted = process.waitForStarted() |
|
1351 if procStarted: |
|
1352 finished = process.waitForFinished(30000) |
|
1353 if finished and process.exitCode() == 0: |
|
1354 output = str(process.readAllStandardOutput(), |
|
1355 Preferences.getSystem("IOEncoding"), 'replace') |
|
1356 index = 0 |
|
1357 for line in output.splitlines(): |
|
1358 index += 1 |
|
1359 changeset, tags, author, date, branches, parents = line.split("@@@") |
|
1360 cdate, ctime = date.split()[:2] |
|
1361 info.append("""<p><table>""") |
|
1362 if mode == "heads": |
|
1363 info.append(QApplication.translate("mercurial", |
|
1364 """<tr><td><b>Head #{0}</b></td><td></td></tr>\n""" |
|
1365 .format(index, changeset))) |
|
1366 elif mode == "parents": |
|
1367 info.append(QApplication.translate("mercurial", |
|
1368 """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n""" |
|
1369 .format(index, changeset))) |
|
1370 elif mode == "tip": |
|
1371 info.append(QApplication.translate("mercurial", |
|
1372 """<tr><td><b>Tip</b></td><td></td></tr>\n""")) |
|
1373 info.append(QApplication.translate("mercurial", |
|
1374 """<tr><td><b>Changeset</b></td><td>{0}</td></tr>""")\ |
|
1375 .format(changeset)) |
|
1376 if tags: |
|
1377 info.append(QApplication.translate("mercurial", |
|
1378 """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")\ |
|
1379 .format('<br/>'.join(tags.split()))) |
|
1380 if branches: |
|
1381 info.append(QApplication.translate("mercurial", |
|
1382 """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")\ |
|
1383 .format('<br/>'.join(branches.split()))) |
|
1384 if parents: |
|
1385 info.append(QApplication.translate("mercurial", |
|
1386 """<tr><td><b>Parents</b></td><td>{0}</td></tr>""")\ |
|
1387 .format('<br/>'.join(parents.split()))) |
|
1388 info.append(QApplication.translate("mercurial", |
|
1389 """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n""" |
|
1390 """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n""" |
|
1391 """<tr><td><b>Committed time</b></td><td>{2}</td></tr>\n""" |
|
1392 """</table></p>""")\ |
|
1393 .format(author, cdate, ctime)) |
|
1394 |
|
1395 dlg = VcsRepositoryInfoDialog(None, "\n".join(info)) |
|
1396 dlg.exec_() |
|
1397 |
|
1398 |
|
1399 def hgResolve(self, name): |
|
1400 """ |
|
1401 Public method used to resolve conflicts of a file/directory. |
|
1402 |
|
1403 @param name file/directory name to be resolved (string) |
|
1404 """ |
|
1405 args = [] |
|
1406 args.append('resolve') |
|
1407 self.addArguments(args, self.options['global']) |
|
1408 args.append("--mark") |
|
1409 |
|
1410 if isinstance(name, list): |
|
1411 dname, fnames = self.splitPathList(name) |
|
1412 self.addArguments(args, name) |
|
1413 else: |
|
1414 dname, fname = self.splitPath(name) |
|
1415 args.append(name) |
|
1416 |
|
1417 # find the root of the repo |
|
1418 repodir = dname |
|
1419 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1420 repodir = os.path.dirname(repodir) |
|
1421 if repodir == os.sep: |
|
1422 return False |
|
1423 |
|
1424 dia = HgDialog( |
|
1425 self.trUtf8('Resolving files/directories')) |
|
1426 res = dia.startProcess(args, repodir) |
|
1427 if res: |
|
1428 dia.exec_() |
|
1429 self.checkVCSStatus() |
|
1430 |
|
1431 def hgBranch(self, name): |
|
1432 """ |
|
1433 Public method used to set the tag in the Mercurial repository. |
|
1434 |
|
1435 @param name file/directory name to be tagged (string) |
|
1436 """ |
|
1437 dname, fname = self.splitPath(name) |
|
1438 |
|
1439 # find the root of the repo |
|
1440 repodir = str(dname) |
|
1441 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1442 repodir = os.path.dirname(repodir) |
|
1443 if repodir == os.sep: |
|
1444 return |
|
1445 |
|
1446 name, ok = QInputDialog.getItem( |
|
1447 None, |
|
1448 self.trUtf8("Create Branch"), |
|
1449 self.trUtf8("Enter branch name"), |
|
1450 self.branchesList, |
|
1451 0, True) |
|
1452 if ok and name: |
|
1453 args = [] |
|
1454 args.append('branch') |
|
1455 args.append(name) |
|
1456 |
|
1457 dia = HgDialog(self.trUtf8('Creating branch in the Mercurial repository')) |
|
1458 res = dia.startProcess(args, repodir) |
|
1459 if res: |
|
1460 dia.exec_() |
|
1461 |
|
1462 ############################################################################ |
|
1463 ## Methods to get the helper objects are below. |
|
1464 ############################################################################ |
|
1465 |
|
1466 def vcsGetProjectBrowserHelper(self, browser, project, isTranslationsBrowser = False): |
|
1467 """ |
|
1468 Public method to instanciate a helper object for the different project browsers. |
|
1469 |
|
1470 @param browser reference to the project browser object |
|
1471 @param project reference to the project object |
|
1472 @param isTranslationsBrowser flag indicating, the helper is requested for the |
|
1473 translations browser (this needs some special treatment) |
|
1474 @return the project browser helper object |
|
1475 """ |
|
1476 return HgProjectBrowserHelper(self, browser, project, isTranslationsBrowser) |
|
1477 |
|
1478 def vcsGetProjectHelper(self, project): |
|
1479 """ |
|
1480 Public method to instanciate a helper object for the project. |
|
1481 |
|
1482 @param project reference to the project object |
|
1483 @return the project helper object |
|
1484 """ |
|
1485 helper = self.__plugin.getProjectHelper() |
|
1486 helper.setObjects(self, project) |
|
1487 return helper |
|
1488 |
|
1489 ############################################################################ |
|
1490 ## Status Monitor Thread methods |
|
1491 ############################################################################ |
|
1492 |
|
1493 def _createStatusMonitorThread(self, interval, project): |
|
1494 """ |
|
1495 Protected method to create an instance of the VCS status monitor thread. |
|
1496 |
|
1497 @param project reference to the project object |
|
1498 @param interval check interval for the monitor thread in seconds (integer) |
|
1499 @return reference to the monitor thread (QThread) |
|
1500 """ |
|
1501 return HgStatusMonitorThread(interval, project.ppath, self) |