eric6/Plugins/VcsPlugins/vcsMercurial/hg.py

changeset 6942
2602857055c5
parent 6891
93f82da09f22
child 7010
5d6f5a69a952
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the version control systems interface to Mercurial.
8 """
9
10 from __future__ import unicode_literals
11 try:
12 str = unicode
13 except NameError:
14 pass
15
16 import os
17 import shutil
18
19 from PyQt5.QtCore import QProcess, pyqtSignal, QFileInfo, QFileSystemWatcher, \
20 QCoreApplication
21 from PyQt5.QtWidgets import QApplication, QDialog, QInputDialog
22
23 from E5Gui.E5Application import e5App
24 from E5Gui import E5MessageBox, E5FileDialog
25
26 from QScintilla.MiniEditor import MiniEditor
27
28 from VCS.VersionControl import VersionControl
29 from VCS.RepositoryInfoDialog import VcsRepositoryInfoDialog
30
31 from .HgDialog import HgDialog
32
33 import Utilities
34
35
36 class Hg(VersionControl):
37 """
38 Class implementing the version control systems interface to Mercurial.
39
40 @signal committed() emitted after the commit action has completed
41 @signal activeExtensionsChanged() emitted when the list of active
42 extensions has changed
43 @signal iniFileChanged() emitted when a Mercurial/repo configuration file
44 has changed
45 """
46 committed = pyqtSignal()
47 activeExtensionsChanged = pyqtSignal()
48 iniFileChanged = pyqtSignal()
49
50 IgnoreFileName = ".hgignore"
51
52 def __init__(self, plugin, parent=None, name=None):
53 """
54 Constructor
55
56 @param plugin reference to the plugin object
57 @param parent parent widget (QWidget)
58 @param name name of this object (string)
59 """
60 VersionControl.__init__(self, parent, name)
61 self.defaultOptions = {
62 'global': [''],
63 'commit': [''],
64 'checkout': [''],
65 'update': [''],
66 'add': [''],
67 'remove': [''],
68 'diff': [''],
69 'log': [''],
70 'history': [''],
71 'status': [''],
72 'tag': [''],
73 'export': ['']
74 }
75
76 self.__plugin = plugin
77 self.__ui = parent
78
79 self.options = self.defaultOptions
80 self.tagsList = []
81 self.branchesList = []
82 self.allTagsBranchesList = []
83 self.bookmarksList = []
84 self.showedTags = False
85 self.showedBranches = False
86
87 self.tagTypeList = [
88 'tags',
89 'branches',
90 ]
91
92 self.commandHistory = []
93
94 if "HG_ASP_DOT_NET_HACK" in os.environ:
95 self.adminDir = '_hg'
96 else:
97 self.adminDir = '.hg'
98
99 self.logBrowser = None
100 self.logBrowserIncoming = None
101 self.logBrowserOutgoing = None
102 self.diff = None
103 self.sbsDiff = None
104 self.status = None
105 self.summary = None
106 self.tagbranchList = None
107 self.annotate = None
108 self.repoEditor = None
109 self.serveDlg = None
110 self.bookmarksListDlg = None
111 self.bookmarksInOutDlg = None
112 self.conflictsDlg = None
113
114 self.bundleFile = None
115 self.__lastChangeGroupPath = None
116
117 self.statusCache = {}
118
119 self.__commitData = {}
120 self.__commitDialog = None
121
122 self.__forgotNames = []
123
124 self.__activeExtensions = []
125
126 from .HgUtilities import getConfigPath
127 self.__iniWatcher = QFileSystemWatcher(self)
128 self.__iniWatcher.fileChanged.connect(self.__iniFileChanged)
129 cfgFile = getConfigPath()
130 if os.path.exists(cfgFile):
131 self.__iniWatcher.addPath(cfgFile)
132
133 self.__client = None
134
135 self.__repoDir = ""
136 self.__repoIniFile = ""
137 self.__defaultConfigured = False
138 self.__defaultPushConfigured = False
139
140 # instantiate the extensions
141 from .QueuesExtension.queues import Queues
142 from .FetchExtension.fetch import Fetch
143 from .PurgeExtension.purge import Purge
144 from .GpgExtension.gpg import Gpg
145 from .RebaseExtension.rebase import Rebase
146 from .ShelveExtension.shelve import Shelve
147 from .LargefilesExtension.largefiles import Largefiles
148 from .StripExtension.strip import Strip
149 from .HisteditExtension.histedit import Histedit
150 self.__extensions = {
151 "mq": Queues(self),
152 "fetch": Fetch(self),
153 "purge": Purge(self),
154 "gpg": Gpg(self),
155 "rebase": Rebase(self),
156 "shelve": Shelve(self),
157 "largefiles": Largefiles(self),
158 "strip": Strip(self),
159 "histedit": Histedit(self),
160 }
161
162 def getPlugin(self):
163 """
164 Public method to get a reference to the plugin object.
165
166 @return reference to the plugin object (VcsMercurialPlugin)
167 """
168 return self.__plugin
169
170 def getEncoding(self):
171 """
172 Public method to get the encoding to be used by Mercurial.
173
174 @return encoding (string)
175 """
176 return self.__plugin.getPreferences("Encoding")
177
178 def vcsShutdown(self):
179 """
180 Public method used to shutdown the Mercurial interface.
181 """
182 if self.logBrowser is not None:
183 self.logBrowser.close()
184 if self.logBrowserIncoming is not None:
185 self.logBrowserIncoming.close()
186 if self.logBrowserOutgoing is not None:
187 self.logBrowserOutgoing.close()
188 if self.diff is not None:
189 self.diff.close()
190 if self.sbsDiff is not None:
191 self.sbsDiff.close()
192 if self.status is not None:
193 self.status.close()
194 if self.summary is not None:
195 self.summary.close()
196 if self.tagbranchList is not None:
197 self.tagbranchList.close()
198 if self.annotate is not None:
199 self.annotate.close()
200 if self.serveDlg is not None:
201 self.serveDlg.close()
202
203 if self.bookmarksListDlg is not None:
204 self.bookmarksListDlg.close()
205 if self.bookmarksInOutDlg is not None:
206 self.bookmarksInOutDlg.close()
207
208 if self.conflictsDlg is not None:
209 self.conflictsDlg.close()
210
211 if self.bundleFile and os.path.exists(self.bundleFile):
212 os.remove(self.bundleFile)
213
214 # shut down the project helpers
215 self.__projectHelper.shutdown()
216
217 # shut down the extensions
218 for extension in self.__extensions.values():
219 extension.shutdown()
220
221 # shut down the client
222 self.__client and self.__client.stopServer()
223
224 def getClient(self):
225 """
226 Public method to get a reference to the command server interface.
227
228 @return reference to the client (HgClient)
229 """
230 return self.__client
231
232 def initCommand(self, command):
233 """
234 Public method to initialize a command arguments list.
235
236 @param command command name (string)
237 @return list of command options (list of string)
238 """
239 args = [command]
240 self.addArguments(args, self.__plugin.getGlobalOptions())
241 return args
242
243 def vcsExists(self):
244 """
245 Public method used to test for the presence of the hg executable.
246
247 @return flag indicating the existence (boolean) and an error message
248 (string)
249 """
250 from .HgUtilities import hgVersion
251
252 self.versionStr, self.version, errMsg = hgVersion(self.__plugin)
253 hgExists = errMsg == ""
254 if hgExists:
255 self.__getExtensionsInfo()
256 return hgExists, errMsg
257
258 def vcsInit(self, vcsDir, noDialog=False):
259 """
260 Public method used to initialize the mercurial repository.
261
262 The initialization is done, when a project is converted into a
263 Mercurial controlled project. Therefore we always return TRUE without
264 doing anything.
265
266 @param vcsDir name of the VCS directory (string)
267 @param noDialog flag indicating quiet operations (boolean)
268 @return always TRUE
269 """
270 return True
271
272 def vcsConvertProject(self, vcsDataDict, project):
273 """
274 Public method to convert an uncontrolled project to a version
275 controlled project.
276
277 @param vcsDataDict dictionary of data required for the conversion
278 @param project reference to the project object
279 """
280 success = self.vcsImport(vcsDataDict, project.ppath)[0]
281 if not success:
282 E5MessageBox.critical(
283 self.__ui,
284 self.tr("Create project repository"),
285 self.tr(
286 """The project repository could not be created."""))
287 else:
288 pfn = project.pfile
289 if not os.path.isfile(pfn):
290 pfn += "z"
291 project.closeProject()
292 project.openProject(pfn)
293
294 def vcsImport(self, vcsDataDict, projectDir, noDialog=False):
295 """
296 Public method used to import the project into the Mercurial repository.
297
298 @param vcsDataDict dictionary of data required for the import
299 @param projectDir project directory (string)
300 @param noDialog flag indicating quiet operations
301 @return flag indicating an execution without errors (boolean)
302 and a flag indicating the version controll status (boolean)
303 """
304 msg = vcsDataDict["message"]
305 if not msg:
306 msg = '***'
307
308 args = self.initCommand("init")
309 args.append(projectDir)
310 # init is not possible with the command server
311 dia = HgDialog(self.tr('Creating Mercurial repository'), self)
312 res = dia.startProcess(args)
313 if res:
314 dia.exec_()
315 status = dia.normalExit()
316
317 if status:
318 ignoreName = os.path.join(projectDir, Hg.IgnoreFileName)
319 if not os.path.exists(ignoreName):
320 status = self.hgCreateIgnoreFile(projectDir)
321
322 if status:
323 args = self.initCommand("commit")
324 args.append('--addremove')
325 args.append('--message')
326 args.append(msg)
327 dia = HgDialog(
328 self.tr('Initial commit to Mercurial repository'),
329 self)
330 res = dia.startProcess(args, projectDir)
331 if res:
332 dia.exec_()
333 status = dia.normalExit()
334
335 return status, False
336
337 def vcsCheckout(self, vcsDataDict, projectDir, noDialog=False):
338 """
339 Public method used to check the project out of a Mercurial repository
340 (clone).
341
342 @param vcsDataDict dictionary of data required for the checkout
343 @param projectDir project directory to create (string)
344 @param noDialog flag indicating quiet operations
345 @return flag indicating an execution without errors (boolean)
346 """
347 noDialog = False
348 try:
349 rev = vcsDataDict["revision"]
350 except KeyError:
351 rev = None
352 vcsUrl = self.hgNormalizeURL(vcsDataDict["url"])
353
354 args = self.initCommand("clone")
355 if rev:
356 args.append("--rev")
357 args.append(rev)
358 if vcsDataDict["largefiles"]:
359 args.append("--all-largefiles")
360 args.append(vcsUrl)
361 args.append(projectDir)
362
363 if noDialog:
364 if self.__client is None:
365 return self.startSynchronizedProcess(QProcess(), 'hg', args)
366 else:
367 out, err = self.__client.runcommand(args)
368 return err == ""
369 else:
370 dia = HgDialog(
371 self.tr('Cloning project from a Mercurial repository'),
372 self)
373 res = dia.startProcess(args)
374 if res:
375 dia.exec_()
376 return dia.normalExit()
377
378 def vcsExport(self, vcsDataDict, projectDir):
379 """
380 Public method used to export a directory from the Mercurial repository.
381
382 @param vcsDataDict dictionary of data required for the checkout
383 @param projectDir project directory to create (string)
384 @return flag indicating an execution without errors (boolean)
385 """
386 status = self.vcsCheckout(vcsDataDict, projectDir)
387 shutil.rmtree(os.path.join(projectDir, self.adminDir), True)
388 if os.path.exists(os.path.join(projectDir, Hg.IgnoreFileName)):
389 os.remove(os.path.join(projectDir, Hg.IgnoreFileName))
390 return status
391
392 def vcsCommit(self, name, message, noDialog=False, closeBranch=False,
393 mq=False):
394 """
395 Public method used to make the change of a file/directory permanent
396 in the Mercurial repository.
397
398 @param name file/directory name to be committed (string or list of
399 strings)
400 @param message message for this operation (string)
401 @param noDialog flag indicating quiet operations
402 @keyparam closeBranch flag indicating a close branch commit (boolean)
403 @keyparam mq flag indicating a queue commit (boolean)
404 """
405 msg = message
406
407 if mq:
408 # ensure dialog is shown for a queue commit
409 noDialog = False
410
411 if not noDialog:
412 # call CommitDialog and get message from there
413 if self.__commitDialog is None:
414 from .HgCommitDialog import HgCommitDialog
415 self.__commitDialog = HgCommitDialog(self, msg, mq, self.__ui)
416 self.__commitDialog.accepted.connect(self.__vcsCommit_Step2)
417 self.__commitDialog.show()
418 self.__commitDialog.raise_()
419 self.__commitDialog.activateWindow()
420
421 self.__commitData["name"] = name
422 self.__commitData["msg"] = msg
423 self.__commitData["noDialog"] = noDialog
424 self.__commitData["closeBranch"] = closeBranch
425 self.__commitData["mq"] = mq
426
427 if noDialog:
428 self.__vcsCommit_Step2()
429
430 def __vcsCommit_Step2(self):
431 """
432 Private slot performing the second step of the commit action.
433 """
434 name = self.__commitData["name"]
435 msg = self.__commitData["msg"]
436 noDialog = self.__commitData["noDialog"]
437 closeBranch = self.__commitData["closeBranch"]
438 mq = self.__commitData["mq"]
439
440 if not noDialog:
441 # check, if there are unsaved changes, that should be committed
442 if isinstance(name, list):
443 nameList = name
444 else:
445 nameList = [name]
446 ok = True
447 for nam in nameList:
448 # check for commit of the project
449 if os.path.isdir(nam):
450 project = e5App().getObject("Project")
451 if nam == project.getProjectPath():
452 ok &= \
453 project.checkAllScriptsDirty(
454 reportSyntaxErrors=True) and \
455 project.checkDirty()
456 continue
457 elif os.path.isfile(nam):
458 editor = \
459 e5App().getObject("ViewManager").getOpenEditor(nam)
460 if editor:
461 ok &= editor.checkDirty()
462 if not ok:
463 break
464
465 if not ok:
466 res = E5MessageBox.yesNo(
467 self.__ui,
468 self.tr("Commit Changes"),
469 self.tr(
470 """The commit affects files, that have unsaved"""
471 """ changes. Shall the commit be continued?"""),
472 icon=E5MessageBox.Warning)
473 if not res:
474 return
475
476 if isinstance(name, list):
477 dname, fnames = self.splitPathList(name)
478 else:
479 dname, fname = self.splitPath(name)
480
481 # find the root of the repo
482 repodir = dname
483 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
484 repodir = os.path.dirname(repodir)
485 if os.path.splitdrive(repodir)[1] == os.sep:
486 return
487
488 if self.__commitDialog is not None:
489 msg, amend, commitSubrepositories, author, dateTime = \
490 self.__commitDialog.getCommitData()
491 self.__commitDialog.deleteLater()
492 self.__commitDialog = None
493 if amend and not msg:
494 msg = self.__getMostRecentCommitMessage(repodir)
495 else:
496 amend = False
497 commitSubrepositories = False
498 author = ""
499 dateTime = ""
500
501 if not msg and not amend:
502 msg = '***'
503
504 args = self.initCommand("commit")
505 args.append("-v")
506 if mq:
507 args.append("--mq")
508 else:
509 if closeBranch:
510 args.append("--close-branch")
511 if amend:
512 args.append("--amend")
513 if commitSubrepositories:
514 args.append("--subrepos")
515 if author:
516 args.append("--user")
517 args.append(author)
518 if dateTime:
519 args.append("--date")
520 args.append(dateTime)
521 if msg:
522 args.append("--message")
523 args.append(msg)
524 if self.__client:
525 if isinstance(name, list):
526 self.addArguments(args, name)
527 else:
528 if dname != repodir or fname != ".":
529 args.append(name)
530 else:
531 if isinstance(name, list):
532 self.addArguments(args, fnames)
533 else:
534 if dname != repodir or fname != ".":
535 args.append(fname)
536
537 if noDialog:
538 self.startSynchronizedProcess(QProcess(), "hg", args, dname)
539 else:
540 dia = HgDialog(
541 self.tr('Committing changes to Mercurial repository'),
542 self)
543 res = dia.startProcess(args, dname)
544 if res:
545 dia.exec_()
546 self.committed.emit()
547 if self.__forgotNames:
548 model = e5App().getObject("Project").getModel()
549 for name in self.__forgotNames:
550 model.updateVCSStatus(name)
551 self.__forgotNames = []
552 self.checkVCSStatus()
553
554 def __getMostRecentCommitMessage(self, repodir):
555 """
556 Private method to get the most recent commit message.
557
558 Note: This message is extracted from the parent commit of the
559 working directory.
560
561 @param repodir path containing the repository
562 @type str
563 @return most recent commit message
564 @rtype str
565 """
566 args = self.initCommand("log")
567 args.append("--rev")
568 args.append(".")
569 args.append('--template')
570 args.append('{desc}')
571
572 output = ""
573 if self.__client is None:
574 process = QProcess()
575 process.setWorkingDirectory(repodir)
576 process.start('hg', args)
577 procStarted = process.waitForStarted(5000)
578 if procStarted:
579 finished = process.waitForFinished(30000)
580 if finished and process.exitCode() == 0:
581 output = str(process.readAllStandardOutput(),
582 self.getEncoding(), 'replace')
583 else:
584 output, error = self.__client.runcommand(args)
585
586 return output
587
588 def vcsUpdate(self, name, noDialog=False, revision=None):
589 """
590 Public method used to update a file/directory with the Mercurial
591 repository.
592
593 @param name file/directory name to be updated (string or list of
594 strings)
595 @param noDialog flag indicating quiet operations (boolean)
596 @keyparam revision revision to update to (string)
597 @return flag indicating, that the update contained an add
598 or delete (boolean)
599 """
600 args = self.initCommand("update")
601 if "-v" not in args and "--verbose" not in args:
602 args.append("-v")
603 if revision:
604 args.append("-r")
605 args.append(revision)
606
607 if isinstance(name, list):
608 dname, fnames = self.splitPathList(name)
609 else:
610 dname, fname = self.splitPath(name)
611
612 # find the root of the repo
613 repodir = dname
614 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
615 repodir = os.path.dirname(repodir)
616 if os.path.splitdrive(repodir)[1] == os.sep:
617 return False
618
619 if noDialog:
620 if self.__client is None:
621 self.startSynchronizedProcess(QProcess(), 'hg', args, repodir)
622 else:
623 out, err = self.__client.runcommand(args)
624 res = False
625 else:
626 dia = HgDialog(self.tr(
627 'Synchronizing with the Mercurial repository'),
628 self)
629 res = dia.startProcess(args, repodir)
630 if res:
631 dia.exec_()
632 res = dia.hasAddOrDelete()
633 self.checkVCSStatus()
634 return res
635
636 def vcsAdd(self, name, isDir=False, noDialog=False):
637 """
638 Public method used to add a file/directory to the Mercurial repository.
639
640 @param name file/directory name to be added (string)
641 @param isDir flag indicating name is a directory (boolean)
642 @param noDialog flag indicating quiet operations
643 """
644 args = self.initCommand("add")
645 args.append("-v")
646
647 if isinstance(name, list):
648 if isDir:
649 dname, fname = os.path.split(name[0])
650 else:
651 dname, fnames = self.splitPathList(name)
652 else:
653 if isDir:
654 dname, fname = os.path.split(name)
655 else:
656 dname, fname = self.splitPath(name)
657
658 # find the root of the repo
659 repodir = dname
660 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
661 repodir = os.path.dirname(repodir)
662 if os.path.splitdrive(repodir)[1] == os.sep:
663 return
664
665 if isinstance(name, list):
666 self.addArguments(args, name)
667 else:
668 args.append(name)
669
670 if noDialog:
671 if self.__client is None:
672 self.startSynchronizedProcess(QProcess(), 'hg', args, repodir)
673 else:
674 out, err = self.__client.runcommand(args)
675 else:
676 dia = HgDialog(
677 self.tr(
678 'Adding files/directories to the Mercurial repository'),
679 self)
680 res = dia.startProcess(args, repodir)
681 if res:
682 dia.exec_()
683
684 def vcsAddBinary(self, name, isDir=False):
685 """
686 Public method used to add a file/directory in binary mode to the
687 Mercurial repository.
688
689 @param name file/directory name to be added (string)
690 @param isDir flag indicating name is a directory (boolean)
691 """
692 self.vcsAdd(name, isDir)
693
694 def vcsAddTree(self, path):
695 """
696 Public method to add a directory tree rooted at path to the Mercurial
697 repository.
698
699 @param path root directory of the tree to be added (string or list of
700 strings))
701 """
702 self.vcsAdd(path, isDir=False)
703
704 def vcsRemove(self, name, project=False, noDialog=False):
705 """
706 Public method used to remove a file/directory from the Mercurial
707 repository.
708
709 The default operation is to remove the local copy as well.
710
711 @param name file/directory name to be removed (string or list of
712 strings))
713 @param project flag indicating deletion of a project tree (boolean)
714 (not needed)
715 @param noDialog flag indicating quiet operations
716 @return flag indicating successfull operation (boolean)
717 """
718 args = self.initCommand("remove")
719 args.append("-v")
720 if noDialog and '--force' not in args:
721 args.append('--force')
722
723 if isinstance(name, list):
724 dname, fnames = self.splitPathList(name)
725 self.addArguments(args, name)
726 else:
727 dname, fname = self.splitPath(name)
728 args.append(name)
729
730 # find the root of the repo
731 repodir = dname
732 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
733 repodir = os.path.dirname(repodir)
734 if os.path.splitdrive(repodir)[1] == os.sep:
735 return False
736
737 if noDialog:
738 if self.__client is None:
739 res = self.startSynchronizedProcess(
740 QProcess(), 'hg', args, repodir)
741 else:
742 out, err = self.__client.runcommand(args)
743 res = err == ""
744 else:
745 dia = HgDialog(
746 self.tr(
747 'Removing files/directories from the Mercurial'
748 ' repository'),
749 self)
750 res = dia.startProcess(args, repodir)
751 if res:
752 dia.exec_()
753 res = dia.normalExitWithoutErrors()
754
755 return res
756
757 def vcsMove(self, name, project, target=None, noDialog=False):
758 """
759 Public method used to move a file/directory.
760
761 @param name file/directory name to be moved (string)
762 @param project reference to the project object
763 @param target new name of the file/directory (string)
764 @param noDialog flag indicating quiet operations
765 @return flag indicating successfull operation (boolean)
766 """
767 isDir = os.path.isdir(name)
768
769 res = False
770 if noDialog:
771 if target is None:
772 return False
773 force = True
774 accepted = True
775 else:
776 from .HgCopyDialog import HgCopyDialog
777 dlg = HgCopyDialog(name, None, True)
778 accepted = dlg.exec_() == QDialog.Accepted
779 if accepted:
780 target, force = dlg.getData()
781
782 if accepted:
783 args = self.initCommand("rename")
784 args.append("-v")
785 if force:
786 args.append('--force')
787 args.append(name)
788 args.append(target)
789
790 dname, fname = self.splitPath(name)
791 # find the root of the repo
792 repodir = dname
793 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
794 repodir = os.path.dirname(repodir)
795 if os.path.splitdrive(repodir)[1] == os.sep:
796 return False
797
798 if noDialog:
799 if self.__client is None:
800 res = self.startSynchronizedProcess(
801 QProcess(), 'hg', args, repodir)
802 else:
803 out, err = self.__client.runcommand(args)
804 res = err == ""
805 else:
806 dia = HgDialog(self.tr('Renaming {0}').format(name), self)
807 res = dia.startProcess(args, repodir)
808 if res:
809 dia.exec_()
810 res = dia.normalExit()
811 if res:
812 if target.startswith(project.getProjectPath()):
813 if isDir:
814 project.moveDirectory(name, target)
815 else:
816 project.renameFileInPdata(name, target)
817 else:
818 if isDir:
819 project.removeDirectory(name)
820 else:
821 project.removeFile(name)
822 return res
823
824 def vcsDiff(self, name):
825 """
826 Public method used to view the difference of a file/directory to the
827 Mercurial repository.
828
829 If name is a directory and is the project directory, all project files
830 are saved first. If name is a file (or list of files), which is/are
831 being edited and has unsaved modification, they can be saved or the
832 operation may be aborted.
833
834 @param name file/directory name to be diffed (string)
835 """
836 if isinstance(name, list):
837 names = name[:]
838 else:
839 names = [name]
840 for nam in names:
841 if os.path.isfile(nam):
842 editor = e5App().getObject("ViewManager").getOpenEditor(nam)
843 if editor and not editor.checkDirty():
844 return
845 else:
846 project = e5App().getObject("Project")
847 if nam == project.ppath and not project.saveAllScripts():
848 return
849 if self.diff is None:
850 from .HgDiffDialog import HgDiffDialog
851 self.diff = HgDiffDialog(self)
852 self.diff.show()
853 self.diff.raise_()
854 QApplication.processEvents()
855 self.diff.start(name, refreshable=True)
856
857 def vcsStatus(self, name):
858 """
859 Public method used to view the status of files/directories in the
860 Mercurial repository.
861
862 @param name file/directory name(s) to show the status of
863 (string or list of strings)
864 """
865 if self.status is None:
866 from .HgStatusDialog import HgStatusDialog
867 self.status = HgStatusDialog(self)
868 self.status.show()
869 self.status.raise_()
870 self.status.start(name)
871
872 def hgSummary(self, mq=False, largefiles=False):
873 """
874 Public method used to show some summary information of the
875 working directory state.
876
877 @param mq flag indicating to show the queue status as well (boolean)
878 @param largefiles flag indicating to show the largefiles status as
879 well (boolean)
880 """
881 if self.summary is None:
882 from .HgSummaryDialog import HgSummaryDialog
883 self.summary = HgSummaryDialog(self)
884 self.summary.show()
885 self.summary.raise_()
886 self.summary.start(self.__projectHelper.getProject().getProjectPath(),
887 mq=mq, largefiles=largefiles)
888
889 def vcsTag(self, name, revision=None, tagName=None):
890 """
891 Public method used to set/remove a tag in the Mercurial repository.
892
893 @param name file/directory name to determine the repo root from
894 (string)
895 @param revision revision to set tag for (string)
896 @param tagName name of the tag (string)
897 @return flag indicating a performed tag action (boolean)
898 """
899 dname, fname = self.splitPath(name)
900
901 # find the root of the repo
902 repodir = dname
903 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
904 repodir = os.path.dirname(repodir)
905 if os.path.splitdrive(repodir)[1] == os.sep:
906 return False
907
908 from .HgTagDialog import HgTagDialog
909 dlg = HgTagDialog(self.hgGetTagsList(repodir, withType=True),
910 revision, tagName)
911 if dlg.exec_() == QDialog.Accepted:
912 tag, revision, tagOp, force = dlg.getParameters()
913 else:
914 return False
915
916 args = self.initCommand("tag")
917 msgPart = ""
918 if tagOp in [HgTagDialog.CreateLocalTag, HgTagDialog.DeleteLocalTag]:
919 args.append('--local')
920 msgPart = "local "
921 else:
922 msgPart = "global "
923 if tagOp in [HgTagDialog.DeleteGlobalTag, HgTagDialog.DeleteLocalTag]:
924 args.append('--remove')
925 if tagOp in [HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag]:
926 if revision:
927 args.append("--rev")
928 args.append(revision)
929 if force:
930 args.append("--force")
931 args.append('--message')
932 if tagOp in [HgTagDialog.CreateGlobalTag, HgTagDialog.CreateLocalTag]:
933 tag = tag.strip().replace(" ", "_")
934 args.append("Created {1}tag <{0}>.".format(tag, msgPart))
935 else:
936 args.append("Removed {1}tag <{0}>.".format(tag, msgPart))
937 args.append(tag)
938
939 dia = HgDialog(self.tr('Tagging in the Mercurial repository'),
940 self)
941 res = dia.startProcess(args, repodir)
942 if res:
943 dia.exec_()
944
945 return True
946
947 def hgRevert(self, name):
948 """
949 Public method used to revert changes made to a file/directory.
950
951 @param name file/directory name to be reverted (string)
952 @return flag indicating, that the update contained an add
953 or delete (boolean)
954 """
955 args = self.initCommand("revert")
956 if not self.getPlugin().getPreferences("CreateBackup"):
957 args.append("--no-backup")
958 args.append("-v")
959 if isinstance(name, list):
960 dname, fnames = self.splitPathList(name)
961 self.addArguments(args, name)
962 names = name[:]
963 else:
964 dname, fname = self.splitPath(name)
965 args.append(name)
966 names = [name]
967
968 # find the root of the repo
969 repodir = dname
970 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
971 repodir = os.path.dirname(repodir)
972 if os.path.splitdrive(repodir)[1] == os.sep:
973 return False
974
975 project = e5App().getObject("Project")
976 names = [project.getRelativePath(nam) for nam in names]
977 if names[0]:
978 from UI.DeleteFilesConfirmationDialog import \
979 DeleteFilesConfirmationDialog
980 dlg = DeleteFilesConfirmationDialog(
981 self.parent(),
982 self.tr("Revert changes"),
983 self.tr(
984 "Do you really want to revert all changes to these files"
985 " or directories?"),
986 names)
987 yes = dlg.exec_() == QDialog.Accepted
988 else:
989 yes = E5MessageBox.yesNo(
990 None,
991 self.tr("Revert changes"),
992 self.tr("""Do you really want to revert all changes of"""
993 """ the project?"""))
994 if yes:
995 dia = HgDialog(self.tr('Reverting changes'), self)
996 res = dia.startProcess(args, repodir)
997 if res:
998 dia.exec_()
999 res = dia.hasAddOrDelete()
1000 self.checkVCSStatus()
1001 else:
1002 res = False
1003
1004 return res
1005
1006 def vcsMerge(self, name, rev=""):
1007 """
1008 Public method used to merge a URL/revision into the local project.
1009
1010 @param name file/directory name to be merged
1011 @type str
1012 @keyparam rev revision to merge with
1013 @type str
1014 """
1015 dname, fname = self.splitPath(name)
1016
1017 # find the root of the repo
1018 repodir = dname
1019 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1020 repodir = os.path.dirname(repodir)
1021 if os.path.splitdrive(repodir)[1] == os.sep:
1022 return
1023
1024 if not rev:
1025 from .HgMergeDialog import HgMergeDialog
1026 dlg = HgMergeDialog(self.hgGetTagsList(repodir),
1027 self.hgGetBranchesList(repodir),
1028 self.hgGetBookmarksList(repodir))
1029 if dlg.exec_() == QDialog.Accepted:
1030 rev, force = dlg.getParameters()
1031 else:
1032 return
1033 else:
1034 force = False
1035
1036 args = self.initCommand("merge")
1037 if force:
1038 args.append("--force")
1039 if self.getPlugin().getPreferences("InternalMerge"):
1040 args.append("--tool")
1041 args.append("internal:merge")
1042 if rev:
1043 args.append("--rev")
1044 args.append(rev)
1045
1046 dia = HgDialog(self.tr('Merging').format(name), self)
1047 res = dia.startProcess(args, repodir)
1048 if res:
1049 dia.exec_()
1050 self.checkVCSStatus()
1051
1052 def hgReMerge(self, name):
1053 """
1054 Public method used to merge a URL/revision into the local project.
1055
1056 @param name file/directory name to be merged (string)
1057 """
1058 args = self.initCommand("resolve")
1059 if self.getPlugin().getPreferences("InternalMerge"):
1060 args.append("--tool")
1061 args.append("internal:merge")
1062 if isinstance(name, list):
1063 dname, fnames = self.splitPathList(name)
1064 self.addArguments(args, name)
1065 names = name[:]
1066 else:
1067 dname, fname = self.splitPath(name)
1068 args.append(name)
1069 names = [name]
1070
1071 # find the root of the repo
1072 repodir = dname
1073 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1074 repodir = os.path.dirname(repodir)
1075 if os.path.splitdrive(repodir)[1] == os.sep:
1076 return
1077
1078 project = e5App().getObject("Project")
1079 names = [project.getRelativePath(nam) for nam in names]
1080 if names[0]:
1081 from UI.DeleteFilesConfirmationDialog import \
1082 DeleteFilesConfirmationDialog
1083 dlg = DeleteFilesConfirmationDialog(
1084 self.parent(),
1085 self.tr("Re-Merge"),
1086 self.tr(
1087 "Do you really want to re-merge these files"
1088 " or directories?"),
1089 names)
1090 yes = dlg.exec_() == QDialog.Accepted
1091 else:
1092 yes = E5MessageBox.yesNo(
1093 None,
1094 self.tr("Re-Merge"),
1095 self.tr("""Do you really want to re-merge the project?"""))
1096 if yes:
1097 dia = HgDialog(self.tr('Re-Merging').format(name), self)
1098 res = dia.startProcess(args, repodir)
1099 if res:
1100 dia.exec_()
1101 self.checkVCSStatus()
1102
1103 def vcsSwitch(self, name):
1104 """
1105 Public method used to switch a working directory to a different
1106 revision.
1107
1108 @param name directory name to be switched (string)
1109 @return flag indicating, that the switch contained an add
1110 or delete (boolean)
1111 """
1112 dname, fname = self.splitPath(name)
1113
1114 # find the root of the repo
1115 repodir = dname
1116 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1117 repodir = os.path.dirname(repodir)
1118 if os.path.splitdrive(repodir)[1] == os.sep:
1119 return False
1120
1121 from .HgRevisionSelectionDialog import HgRevisionSelectionDialog
1122 dlg = HgRevisionSelectionDialog(self.hgGetTagsList(repodir),
1123 self.hgGetBranchesList(repodir),
1124 self.hgGetBookmarksList(repodir),
1125 self.tr("Current branch tip"))
1126 if dlg.exec_() == QDialog.Accepted:
1127 rev = dlg.getRevision()
1128 return self.vcsUpdate(name, revision=rev)
1129
1130 return False
1131
1132 def vcsRegisteredState(self, name):
1133 """
1134 Public method used to get the registered state of a file in the vcs.
1135
1136 @param name filename to check (string)
1137 @return a combination of canBeCommited and canBeAdded
1138 """
1139 if name.endswith(os.sep):
1140 name = name[:-1]
1141 name = os.path.normcase(name)
1142 dname, fname = self.splitPath(name)
1143
1144 if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)):
1145 return self.canBeCommitted
1146
1147 if name in self.statusCache:
1148 return self.statusCache[name]
1149
1150 # find the root of the repo
1151 repodir = dname
1152 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1153 repodir = os.path.dirname(repodir)
1154 if os.path.splitdrive(repodir)[1] == os.sep:
1155 return 0
1156
1157 args = self.initCommand("status")
1158 args.append('--all')
1159 args.append('--noninteractive')
1160
1161 output = ""
1162 if self.__client is None:
1163 process = QProcess()
1164 process.setWorkingDirectory(repodir)
1165 process.start('hg', args)
1166 procStarted = process.waitForStarted(5000)
1167 if procStarted:
1168 finished = process.waitForFinished(30000)
1169 if finished and process.exitCode() == 0:
1170 output = str(process.readAllStandardOutput(),
1171 self.getEncoding(), 'replace')
1172 else:
1173 output, error = self.__client.runcommand(args)
1174
1175 if output:
1176 for line in output.splitlines():
1177 if len(line) > 2 and line[0] in "MARC!?I" and line[1] == " ":
1178 flag, path = line.split(" ", 1)
1179 absname = os.path.join(repodir, os.path.normcase(path))
1180 if flag not in "?I":
1181 if fname == '.':
1182 if absname.startswith(dname + os.path.sep):
1183 return self.canBeCommitted
1184 if absname == dname:
1185 return self.canBeCommitted
1186 else:
1187 if absname == name:
1188 return self.canBeCommitted
1189
1190 return self.canBeAdded
1191
1192 def vcsAllRegisteredStates(self, names, dname, shortcut=True):
1193 """
1194 Public method used to get the registered states of a number of files
1195 in the vcs.
1196
1197 <b>Note:</b> If a shortcut is to be taken, the code will only check,
1198 if the named directory has been scanned already. If so, it is assumed,
1199 that the states for all files have been populated by the previous run.
1200
1201 @param names dictionary with all filenames to be checked as keys
1202 @param dname directory to check in (string)
1203 @param shortcut flag indicating a shortcut should be taken (boolean)
1204 @return the received dictionary completed with a combination of
1205 canBeCommited and canBeAdded or None in order to signal an error
1206 """
1207 if dname.endswith(os.sep):
1208 dname = dname[:-1]
1209 dname = os.path.normcase(dname)
1210
1211 found = False
1212 for name in list(self.statusCache.keys()):
1213 if name in names:
1214 found = True
1215 names[name] = self.statusCache[name]
1216
1217 if not found:
1218 # find the root of the repo
1219 repodir = dname
1220 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1221 repodir = os.path.dirname(repodir)
1222 if os.path.splitdrive(repodir)[1] == os.sep:
1223 return names
1224
1225 args = self.initCommand("status")
1226 args.append('--all')
1227 args.append('--noninteractive')
1228
1229 output = ""
1230 if self.__client is None:
1231 process = QProcess()
1232 process.setWorkingDirectory(dname)
1233 process.start('hg', args)
1234 procStarted = process.waitForStarted(5000)
1235 if procStarted:
1236 finished = process.waitForFinished(30000)
1237 if finished and process.exitCode() == 0:
1238 output = str(process.readAllStandardOutput(),
1239 self.getEncoding(), 'replace')
1240 else:
1241 output, error = self.__client.runcommand(args)
1242
1243 if output:
1244 dirs = [x for x in names.keys() if os.path.isdir(x)]
1245 for line in output.splitlines():
1246 if line and line[0] in "MARC!?I":
1247 flag, path = line.split(" ", 1)
1248 name = os.path.normcase(os.path.join(repodir, path))
1249 dirName = os.path.dirname(name)
1250 if name.startswith(dname):
1251 if flag not in "?I":
1252 if name in names:
1253 names[name] = self.canBeCommitted
1254 if dirName in names:
1255 names[dirName] = self.canBeCommitted
1256 if dirs:
1257 for d in dirs:
1258 if name.startswith(d):
1259 names[d] = self.canBeCommitted
1260 dirs.remove(d)
1261 break
1262 if flag not in "?I":
1263 self.statusCache[name] = self.canBeCommitted
1264 self.statusCache[dirName] = self.canBeCommitted
1265 else:
1266 self.statusCache[name] = self.canBeAdded
1267 if dirName not in self.statusCache:
1268 self.statusCache[dirName] = self.canBeAdded
1269
1270 return names
1271
1272 def clearStatusCache(self):
1273 """
1274 Public method to clear the status cache.
1275 """
1276 self.statusCache = {}
1277
1278 def vcsName(self):
1279 """
1280 Public method returning the name of the vcs.
1281
1282 @return always 'Mercurial' (string)
1283 """
1284 return "Mercurial"
1285
1286 def vcsInitConfig(self, project):
1287 """
1288 Public method to initialize the VCS configuration.
1289
1290 This method ensures, that an ignore file exists.
1291
1292 @param project reference to the project (Project)
1293 """
1294 ppath = project.getProjectPath()
1295 if ppath:
1296 ignoreName = os.path.join(ppath, Hg.IgnoreFileName)
1297 if not os.path.exists(ignoreName):
1298 self.hgCreateIgnoreFile(project.getProjectPath(), autoAdd=True)
1299
1300 def vcsCleanup(self, name):
1301 """
1302 Public method used to cleanup the working directory.
1303
1304 @param name directory name to be cleaned up (string)
1305 """
1306 patterns = self.getPlugin().getPreferences("CleanupPatterns").split()
1307
1308 entries = []
1309 for pat in patterns:
1310 entries.extend(Utilities.direntries(name, True, pat))
1311
1312 for entry in entries:
1313 try:
1314 os.remove(entry)
1315 except OSError:
1316 pass
1317
1318 def vcsCommandLine(self, name):
1319 """
1320 Public method used to execute arbitrary mercurial commands.
1321
1322 @param name directory name of the working directory (string)
1323 """
1324 from .HgCommandDialog import HgCommandDialog
1325 dlg = HgCommandDialog(self.commandHistory, name)
1326 if dlg.exec_() == QDialog.Accepted:
1327 command = dlg.getData()
1328 commandList = Utilities.parseOptionString(command)
1329
1330 # This moves any previous occurrence of these arguments to the head
1331 # of the list.
1332 if command in self.commandHistory:
1333 self.commandHistory.remove(command)
1334 self.commandHistory.insert(0, command)
1335
1336 args = []
1337 self.addArguments(args, commandList)
1338
1339 # find the root of the repo
1340 repodir = name
1341 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1342 repodir = os.path.dirname(repodir)
1343 if os.path.splitdrive(repodir)[1] == os.sep:
1344 return
1345
1346 dia = HgDialog(self.tr('Mercurial command'), self)
1347 res = dia.startProcess(args, repodir)
1348 if res:
1349 dia.exec_()
1350
1351 def vcsOptionsDialog(self, project, archive, editable=False, parent=None):
1352 """
1353 Public method to get a dialog to enter repository info.
1354
1355 @param project reference to the project object
1356 @param archive name of the project in the repository (string)
1357 @param editable flag indicating that the project name is editable
1358 (boolean)
1359 @param parent parent widget (QWidget)
1360 @return reference to the instantiated options dialog (HgOptionsDialog)
1361 """
1362 from .HgOptionsDialog import HgOptionsDialog
1363 return HgOptionsDialog(self, project, parent)
1364
1365 def vcsNewProjectOptionsDialog(self, parent=None):
1366 """
1367 Public method to get a dialog to enter repository info for getting a
1368 new project.
1369
1370 @param parent parent widget (QWidget)
1371 @return reference to the instantiated options dialog
1372 (HgNewProjectOptionsDialog)
1373 """
1374 from .HgNewProjectOptionsDialog import HgNewProjectOptionsDialog
1375 return HgNewProjectOptionsDialog(self, parent)
1376
1377 def vcsRepositoryInfos(self, ppath):
1378 """
1379 Public method to retrieve information about the repository.
1380
1381 @param ppath local path to get the repository infos (string)
1382 @return string with ready formated info for display (string)
1383 """
1384 args = self.initCommand("parents")
1385 args.append('--template')
1386 args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@'
1387 '{date|isodate}@@@{branches}@@@{bookmarks}\n')
1388
1389 output = ""
1390 if self.__client is None:
1391 process = QProcess()
1392 process.setWorkingDirectory(ppath)
1393 process.start('hg', args)
1394 procStarted = process.waitForStarted(5000)
1395 if procStarted:
1396 finished = process.waitForFinished(30000)
1397 if finished and process.exitCode() == 0:
1398 output = str(process.readAllStandardOutput(),
1399 self.getEncoding(), 'replace')
1400 else:
1401 output, error = self.__client.runcommand(args)
1402
1403 infoBlock = []
1404 if output:
1405 index = 0
1406 for line in output.splitlines():
1407 index += 1
1408 changeset, tags, author, date, branches, bookmarks = \
1409 line.split("@@@")
1410 cdate, ctime = date.split()[:2]
1411 info = []
1412 info.append(QCoreApplication.translate(
1413 "mercurial",
1414 """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n"""
1415 """<tr><td><b>Changeset</b></td><td>{1}</td></tr>""")
1416 .format(index, changeset))
1417 if tags:
1418 info.append(QCoreApplication.translate(
1419 "mercurial",
1420 """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")
1421 .format('<br/>'.join(tags.split())))
1422 if bookmarks:
1423 info.append(QCoreApplication.translate(
1424 "mercurial",
1425 """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")
1426 .format('<br/>'.join(bookmarks.split())))
1427 if branches:
1428 info.append(QCoreApplication.translate(
1429 "mercurial",
1430 """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")
1431 .format('<br/>'.join(branches.split())))
1432 info.append(QCoreApplication.translate(
1433 "mercurial",
1434 """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n"""
1435 """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n"""
1436 """<tr><td><b>Committed time</b></td><td>{2}</td></tr>""")
1437 .format(author, cdate, ctime))
1438 infoBlock.append("\n".join(info))
1439 if infoBlock:
1440 infoStr = """<tr></tr>{0}""".format("<tr></tr>".join(infoBlock))
1441 else:
1442 infoStr = ""
1443
1444 url = ""
1445 args = self.initCommand("showconfig")
1446 args.append('paths.default')
1447
1448 output = ""
1449 if self.__client is None:
1450 process.setWorkingDirectory(ppath)
1451 process.start('hg', args)
1452 procStarted = process.waitForStarted(5000)
1453 if procStarted:
1454 finished = process.waitForFinished(30000)
1455 if finished and process.exitCode() == 0:
1456 output = str(process.readAllStandardOutput(),
1457 self.getEncoding(), 'replace')
1458 else:
1459 output, error = self.__client.runcommand(args)
1460
1461 if output:
1462 url = output.splitlines()[0].strip()
1463 else:
1464 url = ""
1465
1466 return QCoreApplication.translate(
1467 'mercurial',
1468 """<h3>Repository information</h3>\n"""
1469 """<p><table>\n"""
1470 """<tr><td><b>Mercurial V.</b></td><td>{0}</td></tr>\n"""
1471 """<tr></tr>\n"""
1472 """<tr><td><b>URL</b></td><td>{1}</td></tr>\n"""
1473 """{2}"""
1474 """</table></p>\n"""
1475 ).format(self.versionStr, url, infoStr)
1476
1477 def vcsSupportCommandOptions(self):
1478 """
1479 Public method to signal the support of user settable command options.
1480
1481 @return flag indicating the support of user settable command options
1482 (boolean)
1483 """
1484 return False
1485
1486 ###########################################################################
1487 ## Private Mercurial specific methods are below.
1488 ###########################################################################
1489
1490 def hgNormalizeURL(self, url):
1491 """
1492 Public method to normalize a url for Mercurial.
1493
1494 @param url url string (string)
1495 @return properly normalized url for mercurial (string)
1496 """
1497 url = url.replace('\\', '/')
1498 if url.endswith('/'):
1499 url = url[:-1]
1500 urll = url.split('//')
1501 return "{0}//{1}".format(urll[0], '/'.join(urll[1:]))
1502
1503 def hgCopy(self, name, project):
1504 """
1505 Public method used to copy a file/directory.
1506
1507 @param name file/directory name to be copied (string)
1508 @param project reference to the project object
1509 @return flag indicating successful operation (boolean)
1510 """
1511 from .HgCopyDialog import HgCopyDialog
1512 dlg = HgCopyDialog(name)
1513 res = False
1514 if dlg.exec_() == QDialog.Accepted:
1515 target, force = dlg.getData()
1516
1517 args = self.initCommand("copy")
1518 args.append("-v")
1519 args.append(name)
1520 args.append(target)
1521
1522 dname, fname = self.splitPath(name)
1523 # find the root of the repo
1524 repodir = dname
1525 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1526 repodir = os.path.dirname(repodir)
1527 if os.path.splitdrive(repodir)[1] == os.sep:
1528 return False
1529
1530 dia = HgDialog(
1531 self.tr('Copying {0}').format(name), self)
1532 res = dia.startProcess(args, repodir)
1533 if res:
1534 dia.exec_()
1535 res = dia.normalExit()
1536 if res and \
1537 target.startswith(project.getProjectPath()):
1538 if os.path.isdir(name):
1539 project.copyDirectory(name, target)
1540 else:
1541 project.appendFile(target)
1542 return res
1543
1544 def hgGetTagsList(self, repodir, withType=False):
1545 """
1546 Public method to get the list of tags.
1547
1548 @param repodir directory name of the repository (string)
1549 @param withType flag indicating to get the tag type as well (boolean)
1550 @return list of tags (list of string) or list of tuples of
1551 tag name and flag indicating a local tag (list of tuple of string
1552 and boolean), if withType is True
1553 """
1554 args = self.initCommand("tags")
1555 args.append('--verbose')
1556
1557 output = ""
1558 if self.__client is None:
1559 process = QProcess()
1560 process.setWorkingDirectory(repodir)
1561 process.start('hg', args)
1562 procStarted = process.waitForStarted(5000)
1563 if procStarted:
1564 finished = process.waitForFinished(30000)
1565 if finished and process.exitCode() == 0:
1566 output = str(process.readAllStandardOutput(),
1567 self.getEncoding(), 'replace')
1568 else:
1569 output, error = self.__client.runcommand(args)
1570
1571 tagsList = []
1572 if output:
1573 for line in output.splitlines():
1574 li = line.strip().split()
1575 if li[-1][0] in "1234567890":
1576 # last element is a rev:changeset
1577 del li[-1]
1578 isLocal = False
1579 else:
1580 del li[-2:]
1581 isLocal = True
1582 name = " ".join(li)
1583 if name not in ["tip", "default"]:
1584 if withType:
1585 tagsList.append((name, isLocal))
1586 else:
1587 tagsList.append(name)
1588
1589 if withType:
1590 return tagsList
1591 else:
1592 if tagsList:
1593 self.tagsList = tagsList
1594 return self.tagsList[:]
1595
1596 def hgGetBranchesList(self, repodir):
1597 """
1598 Public method to get the list of branches.
1599
1600 @param repodir directory name of the repository (string)
1601 @return list of branches (list of string)
1602 """
1603 args = self.initCommand("branches")
1604 args.append('--closed')
1605
1606 output = ""
1607 if self.__client is None:
1608 process = QProcess()
1609 process.setWorkingDirectory(repodir)
1610 process.start('hg', args)
1611 procStarted = process.waitForStarted(5000)
1612 if procStarted:
1613 finished = process.waitForFinished(30000)
1614 if finished and process.exitCode() == 0:
1615 output = str(process.readAllStandardOutput(),
1616 self.getEncoding(), 'replace')
1617 else:
1618 output, error = self.__client.runcommand(args)
1619
1620 if output:
1621 self.branchesList = []
1622 for line in output.splitlines():
1623 li = line.strip().split()
1624 if li[-1][0] in "1234567890":
1625 # last element is a rev:changeset
1626 del li[-1]
1627 else:
1628 del li[-2:]
1629 name = " ".join(li)
1630 if name not in ["tip", "default"]:
1631 self.branchesList.append(name)
1632
1633 return self.branchesList[:]
1634
1635 def hgListTagBranch(self, path, tags=True):
1636 """
1637 Public method used to list the available tags or branches.
1638
1639 @param path directory name of the project (string)
1640 @param tags flag indicating listing of branches or tags
1641 (False = branches, True = tags)
1642 """
1643 from .HgTagBranchListDialog import HgTagBranchListDialog
1644 self.tagbranchList = HgTagBranchListDialog(self)
1645 self.tagbranchList.show()
1646 if tags:
1647 if not self.showedTags:
1648 self.showedTags = True
1649 allTagsBranchesList = self.allTagsBranchesList
1650 else:
1651 self.tagsList = []
1652 allTagsBranchesList = None
1653 self.tagbranchList.start(path, tags,
1654 self.tagsList, allTagsBranchesList)
1655 else:
1656 if not self.showedBranches:
1657 self.showedBranches = True
1658 allTagsBranchesList = self.allTagsBranchesList
1659 else:
1660 self.branchesList = []
1661 allTagsBranchesList = None
1662 self.tagbranchList.start(path, tags,
1663 self.branchesList,
1664 self.allTagsBranchesList)
1665
1666 def hgAnnotate(self, name):
1667 """
1668 Public method to show the output of the hg annotate command.
1669
1670 @param name file name to show the annotations for (string)
1671 """
1672 if self.annotate is None:
1673 from .HgAnnotateDialog import HgAnnotateDialog
1674 self.annotate = HgAnnotateDialog(self)
1675 self.annotate.show()
1676 self.annotate.raise_()
1677 self.annotate.start(name)
1678
1679 def hgExtendedDiff(self, name):
1680 """
1681 Public method used to view the difference of a file/directory to the
1682 Mercurial repository.
1683
1684 If name is a directory and is the project directory, all project files
1685 are saved first. If name is a file (or list of files), which is/are
1686 being edited and has unsaved modification, they can be saved or the
1687 operation may be aborted.
1688
1689 This method gives the chance to enter the revisions to be compared.
1690
1691 @param name file/directory name to be diffed (string)
1692 """
1693 if isinstance(name, list):
1694 dname, fnames = self.splitPathList(name)
1695 names = name[:]
1696 else:
1697 dname, fname = self.splitPath(name)
1698 names = [name]
1699 for nam in names:
1700 if os.path.isfile(nam):
1701 editor = e5App().getObject("ViewManager").getOpenEditor(nam)
1702 if editor and not editor.checkDirty():
1703 return
1704 else:
1705 project = e5App().getObject("Project")
1706 if nam == project.ppath and not project.saveAllScripts():
1707 return
1708
1709 # find the root of the repo
1710 repodir = dname
1711 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1712 repodir = os.path.dirname(repodir)
1713 if os.path.splitdrive(repodir)[1] == os.sep:
1714 return
1715
1716 from .HgRevisionsSelectionDialog import HgRevisionsSelectionDialog
1717 dlg = HgRevisionsSelectionDialog(self.hgGetTagsList(repodir),
1718 self.hgGetBranchesList(repodir),
1719 self.hgGetBookmarksList(repodir))
1720 if dlg.exec_() == QDialog.Accepted:
1721 revisions = dlg.getRevisions()
1722 if self.diff is None:
1723 from .HgDiffDialog import HgDiffDialog
1724 self.diff = HgDiffDialog(self)
1725 self.diff.show()
1726 self.diff.raise_()
1727 self.diff.start(name, revisions)
1728
1729 def __hgGetFileForRevision(self, name, rev=""):
1730 """
1731 Private method to get a file for a specific revision from the
1732 repository.
1733
1734 @param name file name to get from the repository (string)
1735 @keyparam rev revision to retrieve (string)
1736 @return contents of the file (string) and an error message (string)
1737 """
1738 args = self.initCommand("cat")
1739 if rev:
1740 args.append("--rev")
1741 args.append(rev)
1742 args.append(name)
1743
1744 if self.__client is None:
1745 output = ""
1746 error = ""
1747
1748 # find the root of the repo
1749 repodir = self.splitPath(name)[0]
1750 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1751 repodir = os.path.dirname(repodir)
1752 if os.path.splitdrive(repodir)[1] == os.sep:
1753 return "", ""
1754
1755 process = QProcess()
1756 process.setWorkingDirectory(repodir)
1757 process.start('hg', args)
1758 procStarted = process.waitForStarted(5000)
1759 if procStarted:
1760 finished = process.waitForFinished(30000)
1761 if finished:
1762 if process.exitCode() == 0:
1763 output = str(process.readAllStandardOutput(),
1764 self.getEncoding(), 'replace')
1765 else:
1766 error = str(process.readAllStandardError(),
1767 self.getEncoding(), 'replace')
1768 else:
1769 error = self.tr(
1770 "The hg process did not finish within 30s.")
1771 else:
1772 error = self.tr(
1773 'The process {0} could not be started. '
1774 'Ensure, that it is in the search path.').format('hg')
1775 else:
1776 output, error = self.__client.runcommand(args)
1777
1778 # return file contents with 'universal newlines'
1779 return output.replace('\r\n', '\n').replace('\r', '\n'), error
1780
1781 def hgSbsDiff(self, name, extended=False, revisions=None):
1782 """
1783 Public method used to view the difference of a file to the Mercurial
1784 repository side-by-side.
1785
1786 @param name file name to be diffed (string)
1787 @keyparam extended flag indicating the extended variant (boolean)
1788 @keyparam revisions tuple of two revisions (tuple of strings)
1789 @exception ValueError raised to indicate an invalid name parameter
1790 """
1791 if isinstance(name, list):
1792 raise ValueError("Wrong parameter type")
1793
1794 if extended:
1795 # find the root of the repo
1796 repodir = self.splitPath(name)[0]
1797 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1798 repodir = os.path.dirname(repodir)
1799 if os.path.splitdrive(repodir)[1] == os.sep:
1800 return
1801
1802 from .HgRevisionsSelectionDialog import HgRevisionsSelectionDialog
1803 dlg = HgRevisionsSelectionDialog(self.hgGetTagsList(repodir),
1804 self.hgGetBranchesList(repodir),
1805 self.hgGetBookmarksList(repodir))
1806 if dlg.exec_() == QDialog.Accepted:
1807 rev1, rev2 = dlg.getRevisions()
1808 else:
1809 return
1810 elif revisions:
1811 rev1, rev2 = revisions[0], revisions[1]
1812 else:
1813 rev1, rev2 = "", ""
1814
1815 output1, error = self.__hgGetFileForRevision(name, rev=rev1)
1816 if error:
1817 E5MessageBox.critical(
1818 self.__ui,
1819 self.tr("Mercurial Side-by-Side Difference"),
1820 error)
1821 return
1822 name1 = "{0} (rev. {1})".format(name, rev1 and rev1 or ".")
1823
1824 if rev2:
1825 output2, error = self.__hgGetFileForRevision(name, rev=rev2)
1826 if error:
1827 E5MessageBox.critical(
1828 self.__ui,
1829 self.tr("Mercurial Side-by-Side Difference"),
1830 error)
1831 return
1832 name2 = "{0} (rev. {1})".format(name, rev2)
1833 else:
1834 try:
1835 f1 = open(name, "r", encoding="utf-8")
1836 output2 = f1.read()
1837 f1.close()
1838 name2 = "{0} (Work)".format(name)
1839 except IOError:
1840 E5MessageBox.critical(
1841 self.__ui,
1842 self.tr("Mercurial Side-by-Side Difference"),
1843 self.tr(
1844 """<p>The file <b>{0}</b> could not be read.</p>""")
1845 .format(name))
1846 return
1847
1848 if self.sbsDiff is None:
1849 from UI.CompareDialog import CompareDialog
1850 self.sbsDiff = CompareDialog()
1851 self.sbsDiff.show()
1852 self.sbsDiff.raise_()
1853 self.sbsDiff.compare(output1, output2, name1, name2)
1854
1855 def vcsLogBrowser(self, name, isFile=False):
1856 """
1857 Public method used to browse the log of a file/directory from the
1858 Mercurial repository.
1859
1860 @param name file/directory name to show the log of (string)
1861 @keyparam isFile flag indicating log for a file is to be shown
1862 (boolean)
1863 """
1864 if self.logBrowser is None:
1865 from .HgLogBrowserDialog import HgLogBrowserDialog
1866 self.logBrowser = HgLogBrowserDialog(self)
1867 self.logBrowser.show()
1868 self.logBrowser.raise_()
1869 self.logBrowser.start(name, isFile=isFile)
1870
1871 def hgIncoming(self, name):
1872 """
1873 Public method used to view the log of incoming changes from the
1874 Mercurial repository.
1875
1876 @param name file/directory name to show the log of (string)
1877 """
1878 if self.logBrowserIncoming is None:
1879 from .HgLogBrowserDialog import HgLogBrowserDialog
1880 self.logBrowserIncoming = HgLogBrowserDialog(
1881 self, mode="incoming")
1882 self.logBrowserIncoming.show()
1883 self.logBrowserIncoming.raise_()
1884 self.logBrowserIncoming.start(name)
1885
1886 def hgOutgoing(self, name):
1887 """
1888 Public method used to view the log of outgoing changes from the
1889 Mercurial repository.
1890
1891 @param name file/directory name to show the log of (string)
1892 """
1893 if self.logBrowserOutgoing is None:
1894 from .HgLogBrowserDialog import HgLogBrowserDialog
1895 self.logBrowserOutgoing = HgLogBrowserDialog(
1896 self, mode="outgoing")
1897 self.logBrowserOutgoing.show()
1898 self.logBrowserOutgoing.raise_()
1899 self.logBrowserOutgoing.start(name)
1900
1901 def hgPull(self, name, revisions=None):
1902 """
1903 Public method used to pull changes from a remote Mercurial repository.
1904
1905 @param name directory name of the project to be pulled to
1906 @type str
1907 @param revisions list of revisions to be pulled
1908 @type list of str
1909 @return flag indicating, that the update contained an add
1910 or delete
1911 @rtype bool
1912 """
1913 if self.getPlugin().getPreferences("PreferUnbundle") and \
1914 self.bundleFile and \
1915 os.path.exists(self.bundleFile) and \
1916 revisions is None:
1917 command = "unbundle"
1918 title = self.tr('Apply changegroups')
1919 else:
1920 command = "pull"
1921 title = self.tr('Pulling from a remote Mercurial repository')
1922
1923 args = self.initCommand(command)
1924 args.append('-v')
1925 if self.getPlugin().getPreferences("PullUpdate"):
1926 args.append('--update')
1927 if command == "unbundle":
1928 args.append(self.bundleFile)
1929 if revisions:
1930 for rev in revisions:
1931 args.append("--rev")
1932 args.append(rev)
1933
1934 # find the root of the repo
1935 repodir = self.splitPath(name)[0]
1936 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1937 repodir = os.path.dirname(repodir)
1938 if os.path.splitdrive(repodir)[1] == os.sep:
1939 return False
1940
1941 dia = HgDialog(title, self)
1942 res = dia.startProcess(args, repodir)
1943 if res:
1944 dia.exec_()
1945 res = dia.hasAddOrDelete()
1946 if self.bundleFile and \
1947 os.path.exists(self.bundleFile):
1948 os.remove(self.bundleFile)
1949 self.bundleFile = None
1950 self.checkVCSStatus()
1951 return res
1952
1953 def hgPush(self, name, force=False, newBranch=False, rev=None):
1954 """
1955 Public method used to push changes to a remote Mercurial repository.
1956
1957 @param name directory name of the project to be pushed from (string)
1958 @keyparam force flag indicating a forced push (boolean)
1959 @keyparam newBranch flag indicating to push a new branch (boolean)
1960 @keyparam rev revision to be pushed (including all ancestors) (string)
1961 """
1962 args = self.initCommand("push")
1963 args.append('-v')
1964 if force:
1965 args.append('-f')
1966 if newBranch:
1967 args.append('--new-branch')
1968 if rev:
1969 args.append('--rev')
1970 args.append(rev)
1971
1972 # find the root of the repo
1973 repodir = self.splitPath(name)[0]
1974 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1975 repodir = os.path.dirname(repodir)
1976 if os.path.splitdrive(repodir)[1] == os.sep:
1977 return
1978
1979 dia = HgDialog(
1980 self.tr('Pushing to a remote Mercurial repository'), self)
1981 res = dia.startProcess(args, repodir)
1982 if res:
1983 dia.exec_()
1984 self.checkVCSStatus()
1985
1986 def hgInfo(self, ppath, mode="heads"):
1987 """
1988 Public method to show information about the heads of the repository.
1989
1990 @param ppath local path to get the repository infos (string)
1991 @keyparam mode mode of the operation (string, one of heads, parents,
1992 tip)
1993 """
1994 if mode not in ("heads", "parents", "tip"):
1995 mode = "heads"
1996
1997 info = []
1998
1999 args = self.initCommand(mode)
2000 args.append('--template')
2001 args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@'
2002 '{date|isodate}@@@{branches}@@@{parents}@@@{bookmarks}\n')
2003
2004 output = ""
2005 if self.__client is None:
2006 # find the root of the repo
2007 repodir = self.splitPath(ppath)[0]
2008 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2009 repodir = os.path.dirname(repodir)
2010 if os.path.splitdrive(repodir)[1] == os.sep:
2011 return
2012
2013 process = QProcess()
2014 process.setWorkingDirectory(repodir)
2015 process.start('hg', args)
2016 procStarted = process.waitForStarted(5000)
2017 if procStarted:
2018 finished = process.waitForFinished(30000)
2019 if finished and process.exitCode() == 0:
2020 output = str(process.readAllStandardOutput(),
2021 self.getEncoding(), 'replace')
2022 else:
2023 output, error = self.__client.runcommand(args)
2024
2025 if output:
2026 index = 0
2027 for line in output.splitlines():
2028 index += 1
2029 changeset, tags, author, date, branches, parents, bookmarks = \
2030 line.split("@@@")
2031 cdate, ctime = date.split()[:2]
2032 info.append("""<p><table>""")
2033 if mode == "heads":
2034 info.append(QCoreApplication.translate(
2035 "mercurial",
2036 """<tr><td><b>Head #{0}</b></td><td></td></tr>\n""")
2037 .format(index))
2038 elif mode == "parents":
2039 info.append(QCoreApplication.translate(
2040 "mercurial",
2041 """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n""")
2042 .format(index))
2043 elif mode == "tip":
2044 info.append(QCoreApplication.translate(
2045 "mercurial",
2046 """<tr><td><b>Tip</b></td><td></td></tr>\n"""))
2047 info.append(QCoreApplication.translate(
2048 "mercurial",
2049 """<tr><td><b>Changeset</b></td><td>{0}</td></tr>""")
2050 .format(changeset))
2051 if tags:
2052 info.append(QCoreApplication.translate(
2053 "mercurial",
2054 """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")
2055 .format('<br/>'.join(tags.split())))
2056 if bookmarks:
2057 info.append(QCoreApplication.translate(
2058 "mercurial",
2059 """<tr><td><b>Bookmarks</b></td><td>{0}</td></tr>""")
2060 .format('<br/>'.join(bookmarks.split())))
2061 if branches:
2062 info.append(QCoreApplication.translate(
2063 "mercurial",
2064 """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")
2065 .format('<br/>'.join(branches.split())))
2066 if parents:
2067 info.append(QCoreApplication.translate(
2068 "mercurial",
2069 """<tr><td><b>Parents</b></td><td>{0}</td></tr>""")
2070 .format('<br/>'.join(parents.split())))
2071 info.append(QCoreApplication.translate(
2072 "mercurial",
2073 """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n"""
2074 """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n"""
2075 """<tr><td><b>Committed time</b></td><td>{2}</td></tr>\n"""
2076 """</table></p>""")
2077 .format(author, cdate, ctime))
2078
2079 dlg = VcsRepositoryInfoDialog(None, "\n".join(info))
2080 dlg.exec_()
2081
2082 def hgConflicts(self, name):
2083 """
2084 Public method used to show a list of files containing conflicts.
2085
2086 @param name file/directory name to be resolved (string)
2087 """
2088 if self.conflictsDlg is None:
2089 from .HgConflictsListDialog import HgConflictsListDialog
2090 self.conflictsDlg = HgConflictsListDialog(self)
2091 self.conflictsDlg.show()
2092 self.conflictsDlg.raise_()
2093 self.conflictsDlg.start(name)
2094
2095 def hgResolved(self, name, unresolve=False):
2096 """
2097 Public method used to resolve conflicts of a file/directory.
2098
2099 @param name file/directory name to be resolved (string)
2100 @param unresolve flag indicating to mark the file/directory as
2101 unresolved (boolean)
2102 """
2103 args = self.initCommand("resolve")
2104 if unresolve:
2105 args.append("--unmark")
2106 else:
2107 args.append("--mark")
2108
2109 if isinstance(name, list):
2110 dname, fnames = self.splitPathList(name)
2111 self.addArguments(args, name)
2112 else:
2113 dname, fname = self.splitPath(name)
2114 args.append(name)
2115
2116 # find the root of the repo
2117 repodir = dname
2118 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2119 repodir = os.path.dirname(repodir)
2120 if os.path.splitdrive(repodir)[1] == os.sep:
2121 return
2122
2123 if unresolve:
2124 title = self.tr("Marking as 'unresolved'")
2125 else:
2126 title = self.tr("Marking as 'resolved'")
2127 dia = HgDialog(title, self)
2128 res = dia.startProcess(args, repodir)
2129 if res:
2130 dia.exec_()
2131 self.checkVCSStatus()
2132
2133 def hgCancelMerge(self, name):
2134 """
2135 Public method to cancel an uncommitted merge.
2136
2137 @param name file/directory name (string)
2138 @return flag indicating, that the cancellation contained an add
2139 or delete (boolean)
2140 """
2141 dname, fname = self.splitPath(name)
2142
2143 # find the root of the repo
2144 repodir = dname
2145 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2146 repodir = os.path.dirname(repodir)
2147 if os.path.splitdrive(repodir)[1] == os.sep:
2148 return False
2149
2150 if self.version >= (4, 5, 0):
2151 args = self.initCommand("merge")
2152 args.append("--abort")
2153 else:
2154 args = self.initCommand("update")
2155 args.append("--clean")
2156
2157 dia = HgDialog(
2158 self.tr('Canceling uncommitted merge'),
2159 self)
2160 res = dia.startProcess(args, repodir, False)
2161 if res:
2162 dia.exec_()
2163 res = dia.hasAddOrDelete()
2164 self.checkVCSStatus()
2165 return res
2166
2167 def hgBranch(self, name):
2168 """
2169 Public method used to create a branch in the Mercurial repository.
2170
2171 @param name file/directory name to be branched (string)
2172 """
2173 dname, fname = self.splitPath(name)
2174
2175 # find the root of the repo
2176 repodir = dname
2177 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2178 repodir = os.path.dirname(repodir)
2179 if os.path.splitdrive(repodir)[1] == os.sep:
2180 return
2181
2182 from .HgBranchInputDialog import HgBranchInputDialog
2183 dlg = HgBranchInputDialog(self.hgGetBranchesList(repodir))
2184 if dlg.exec_() == QDialog.Accepted:
2185 name, commit = dlg.getData()
2186 name = name.strip().replace(" ", "_")
2187 args = self.initCommand("branch")
2188 args.append(name)
2189
2190 dia = HgDialog(
2191 self.tr('Creating branch in the Mercurial repository'),
2192 self)
2193 res = dia.startProcess(args, repodir)
2194 if res:
2195 dia.exec_()
2196 if commit:
2197 self.vcsCommit(
2198 repodir,
2199 self.tr("Created new branch <{0}>.").format(
2200 name))
2201
2202 def hgShowBranch(self, name):
2203 """
2204 Public method used to show the current branch of the working directory.
2205
2206 @param name file/directory name (string)
2207 """
2208 dname, fname = self.splitPath(name)
2209
2210 # find the root of the repo
2211 repodir = dname
2212 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2213 repodir = os.path.dirname(repodir)
2214 if os.path.splitdrive(repodir)[1] == os.sep:
2215 return
2216
2217 args = self.initCommand("branch")
2218
2219 dia = HgDialog(self.tr('Showing current branch'), self)
2220 res = dia.startProcess(args, repodir, False)
2221 if res:
2222 dia.exec_()
2223
2224 def hgGetCurrentBranch(self, repodir):
2225 """
2226 Public method to get the current branch of the working directory.
2227
2228 @param repodir directory name of the repository
2229 @type str
2230 @return name of the current branch
2231 @rtype str
2232 """
2233 args = self.initCommand("branch")
2234
2235 output = ""
2236 if self.__client is None:
2237 process = QProcess()
2238 process.setWorkingDirectory(repodir)
2239 process.start('hg', args)
2240 procStarted = process.waitForStarted(5000)
2241 if procStarted:
2242 finished = process.waitForFinished(30000)
2243 if finished and process.exitCode() == 0:
2244 output = str(process.readAllStandardOutput(),
2245 self.getEncoding(), 'replace')
2246 else:
2247 output, error = self.__client.runcommand(args)
2248
2249 return output.strip()
2250
2251 def hgEditUserConfig(self):
2252 """
2253 Public method used to edit the user configuration file.
2254 """
2255 from .HgUserConfigDialog import HgUserConfigDialog
2256 dlg = HgUserConfigDialog(version=self.version)
2257 dlg.exec_()
2258
2259 def hgEditConfig(self, name, withLargefiles=True, largefilesData=None):
2260 """
2261 Public method used to edit the repository configuration file.
2262
2263 @param name file/directory name (string)
2264 @param withLargefiles flag indicating to configure the largefiles
2265 section (boolean)
2266 @param largefilesData dictionary with data for the largefiles
2267 section of the data dialog (dict)
2268 """
2269 dname, fname = self.splitPath(name)
2270
2271 # find the root of the repo
2272 repodir = dname
2273 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2274 repodir = os.path.dirname(repodir)
2275 if os.path.splitdrive(repodir)[1] == os.sep:
2276 return
2277
2278 cfgFile = os.path.join(repodir, self.adminDir, "hgrc")
2279 if not os.path.exists(cfgFile):
2280 # open dialog to enter the initial data
2281 withLargefiles = (self.isExtensionActive("largefiles") and
2282 withLargefiles)
2283 from .HgRepoConfigDataDialog import HgRepoConfigDataDialog
2284 dlg = HgRepoConfigDataDialog(withLargefiles=withLargefiles,
2285 largefilesData=largefilesData)
2286 if dlg.exec_() == QDialog.Accepted:
2287 createContents = True
2288 defaultUrl, defaultPushUrl = dlg.getData()
2289 if withLargefiles:
2290 lfMinSize, lfPattern = dlg.getLargefilesData()
2291 else:
2292 createContents = False
2293 try:
2294 cfg = open(cfgFile, "w")
2295 if createContents:
2296 # write the data entered
2297 cfg.write("[paths]\n")
2298 if defaultUrl:
2299 cfg.write("default = {0}\n".format(defaultUrl))
2300 if defaultPushUrl:
2301 cfg.write("default-push = {0}\n".format(
2302 defaultPushUrl))
2303 if withLargefiles and \
2304 (lfMinSize, lfPattern) != (None, None):
2305 cfg.write("\n[largefiles]\n")
2306 if lfMinSize is not None:
2307 cfg.write("minsize = {0}\n".format(lfMinSize))
2308 if lfPattern is not None:
2309 cfg.write("patterns =\n")
2310 cfg.write(" {0}\n".format(
2311 "\n ".join(lfPattern)))
2312 cfg.close()
2313 self.__monitorRepoIniFile(repodir)
2314 self.__iniFileChanged(cfgFile)
2315 except IOError:
2316 pass
2317 self.repoEditor = MiniEditor(cfgFile, "Properties")
2318 self.repoEditor.show()
2319
2320 def hgVerify(self, name):
2321 """
2322 Public method to verify the integrity of the repository.
2323
2324 @param name file/directory name (string)
2325 """
2326 dname, fname = self.splitPath(name)
2327
2328 # find the root of the repo
2329 repodir = dname
2330 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2331 repodir = os.path.dirname(repodir)
2332 if os.path.splitdrive(repodir)[1] == os.sep:
2333 return
2334
2335 args = self.initCommand("verify")
2336
2337 dia = HgDialog(
2338 self.tr('Verifying the integrity of the Mercurial repository'),
2339 self)
2340 res = dia.startProcess(args, repodir)
2341 if res:
2342 dia.exec_()
2343
2344 def hgShowConfig(self, name):
2345 """
2346 Public method to show the combined configuration.
2347
2348 @param name file/directory name (string)
2349 """
2350 dname, fname = self.splitPath(name)
2351
2352 # find the root of the repo
2353 repodir = dname
2354 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2355 repodir = os.path.dirname(repodir)
2356 if os.path.splitdrive(repodir)[1] == os.sep:
2357 return
2358
2359 args = self.initCommand("showconfig")
2360 args.append("--untrusted")
2361
2362 dia = HgDialog(
2363 self.tr('Showing the combined configuration settings'),
2364 self)
2365 res = dia.startProcess(args, repodir, False)
2366 if res:
2367 dia.exec_()
2368
2369 def hgShowPaths(self, name):
2370 """
2371 Public method to show the path aliases for remote repositories.
2372
2373 @param name file/directory name (string)
2374 """
2375 dname, fname = self.splitPath(name)
2376
2377 # find the root of the repo
2378 repodir = dname
2379 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2380 repodir = os.path.dirname(repodir)
2381 if os.path.splitdrive(repodir)[1] == os.sep:
2382 return
2383
2384 args = self.initCommand("paths")
2385
2386 dia = HgDialog(
2387 self.tr('Showing aliases for remote repositories'),
2388 self)
2389 res = dia.startProcess(args, repodir, False)
2390 if res:
2391 dia.exec_()
2392
2393 def hgRecover(self, name):
2394 """
2395 Public method to recover an interrupted transaction.
2396
2397 @param name file/directory name (string)
2398 """
2399 dname, fname = self.splitPath(name)
2400
2401 # find the root of the repo
2402 repodir = dname
2403 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2404 repodir = os.path.dirname(repodir)
2405 if os.path.splitdrive(repodir)[1] == os.sep:
2406 return
2407
2408 args = self.initCommand("recover")
2409
2410 dia = HgDialog(
2411 self.tr('Recovering from interrupted transaction'),
2412 self)
2413 res = dia.startProcess(args, repodir, False)
2414 if res:
2415 dia.exec_()
2416
2417 def hgIdentify(self, name):
2418 """
2419 Public method to identify the current working directory.
2420
2421 @param name file/directory name (string)
2422 """
2423 dname, fname = self.splitPath(name)
2424
2425 # find the root of the repo
2426 repodir = dname
2427 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2428 repodir = os.path.dirname(repodir)
2429 if os.path.splitdrive(repodir)[1] == os.sep:
2430 return
2431
2432 args = self.initCommand("identify")
2433
2434 dia = HgDialog(self.tr('Identifying project directory'), self)
2435 res = dia.startProcess(args, repodir, False)
2436 if res:
2437 dia.exec_()
2438
2439 def hgCreateIgnoreFile(self, name, autoAdd=False):
2440 """
2441 Public method to create the ignore file.
2442
2443 @param name directory name to create the ignore file in (string)
2444 @param autoAdd flag indicating to add it automatically (boolean)
2445 @return flag indicating success
2446 """
2447 status = False
2448 ignorePatterns = [
2449 "glob:.eric6project",
2450 "glob:.ropeproject",
2451 "glob:.directory",
2452 "glob:**.pyc",
2453 "glob:**.pyo",
2454 "glob:**.orig",
2455 "glob:**.bak",
2456 "glob:**.rej",
2457 "glob:**~",
2458 "glob:cur",
2459 "glob:tmp",
2460 "glob:__pycache__",
2461 "glob:**.DS_Store",
2462 ]
2463
2464 ignoreName = os.path.join(name, Hg.IgnoreFileName)
2465 if os.path.exists(ignoreName):
2466 res = E5MessageBox.yesNo(
2467 self.__ui,
2468 self.tr("Create .hgignore file"),
2469 self.tr("""<p>The file <b>{0}</b> exists already."""
2470 """ Overwrite it?</p>""").format(ignoreName),
2471 icon=E5MessageBox.Warning)
2472 else:
2473 res = True
2474 if res:
2475 try:
2476 # create a .hgignore file
2477 ignore = open(ignoreName, "w")
2478 ignore.write("\n".join(ignorePatterns))
2479 ignore.write("\n")
2480 ignore.close()
2481 status = True
2482 except IOError:
2483 status = False
2484
2485 if status and autoAdd:
2486 self.vcsAdd(ignoreName, noDialog=True)
2487 project = e5App().getObject("Project")
2488 project.appendFile(ignoreName)
2489
2490 return status
2491
2492 def hgBundle(self, name, bundleData=None):
2493 """
2494 Public method to create a changegroup file.
2495
2496 @param name file/directory name
2497 @type str
2498 @param bundleData dictionary containing the bundle creation information
2499 @type dict
2500 """
2501 dname, fname = self.splitPath(name)
2502
2503 # find the root of the repo
2504 repodir = dname
2505 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2506 repodir = os.path.dirname(repodir)
2507 if os.path.splitdrive(repodir)[1] == os.sep:
2508 return
2509
2510 if bundleData is None:
2511 from .HgBundleDialog import HgBundleDialog
2512 dlg = HgBundleDialog(self.hgGetTagsList(repodir),
2513 self.hgGetBranchesList(repodir),
2514 self.hgGetBookmarksList(repodir),
2515 version=self.version)
2516 if dlg.exec_() != QDialog.Accepted:
2517 return
2518
2519 revs, baseRevs, compression, bundleAll = dlg.getParameters()
2520 else:
2521 revs = bundleData["revs"]
2522 if bundleData["base"]:
2523 baseRevs = [bundleData["base"]]
2524 else:
2525 baseRevs = []
2526 compression = ""
2527 bundleAll = bundleData["all"]
2528
2529 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
2530 None,
2531 self.tr("Create changegroup"),
2532 self.__lastChangeGroupPath or repodir,
2533 self.tr("Mercurial Changegroup Files (*.hg)"),
2534 None,
2535 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
2536
2537 if not fname:
2538 return # user aborted
2539
2540 ext = QFileInfo(fname).suffix()
2541 if not ext:
2542 ex = selectedFilter.split("(*")[1].split(")")[0]
2543 if ex:
2544 fname += ex
2545 if QFileInfo(fname).exists():
2546 res = E5MessageBox.yesNo(
2547 self.__ui,
2548 self.tr("Create changegroup"),
2549 self.tr("<p>The Mercurial changegroup file <b>{0}</b> "
2550 "already exists. Overwrite it?</p>")
2551 .format(fname),
2552 icon=E5MessageBox.Warning)
2553 if not res:
2554 return
2555 fname = Utilities.toNativeSeparators(fname)
2556 self.__lastChangeGroupPath = os.path.dirname(fname)
2557
2558 args = self.initCommand("bundle")
2559 if bundleAll:
2560 args.append("--all")
2561 for rev in revs:
2562 args.append("--rev")
2563 args.append(rev)
2564 for baseRev in baseRevs:
2565 args.append("--base")
2566 args.append(baseRev)
2567 if compression:
2568 args.append("--type")
2569 args.append(compression)
2570 args.append(fname)
2571
2572 dia = HgDialog(self.tr('Create changegroup'), self)
2573 res = dia.startProcess(args, repodir)
2574 if res:
2575 dia.exec_()
2576
2577 def hgPreviewBundle(self, name):
2578 """
2579 Public method used to view the log of incoming changes from a
2580 changegroup file.
2581
2582 @param name directory name on which to base the changegroup (string)
2583 """
2584 dname, fname = self.splitPath(name)
2585
2586 # find the root of the repo
2587 repodir = dname
2588 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2589 repodir = os.path.dirname(repodir)
2590 if os.path.splitdrive(repodir)[1] == os.sep:
2591 return
2592
2593 file = E5FileDialog.getOpenFileName(
2594 None,
2595 self.tr("Preview changegroup"),
2596 self.__lastChangeGroupPath or repodir,
2597 self.tr("Mercurial Changegroup Files (*.hg);;All Files (*)"))
2598 if file:
2599 self.__lastChangeGroupPath = os.path.dirname(file)
2600
2601 if self.logBrowserIncoming is None:
2602 from .HgLogBrowserDialog import HgLogBrowserDialog
2603 self.logBrowserIncoming = \
2604 HgLogBrowserDialog(self, mode="incoming")
2605 self.logBrowserIncoming.show()
2606 self.logBrowserIncoming.raise_()
2607 self.logBrowserIncoming.start(name, bundle=file)
2608
2609 def hgUnbundle(self, name, files=None):
2610 """
2611 Public method to apply changegroup files.
2612
2613 @param name directory name
2614 @type str
2615 @param files list of bundle files to be applied
2616 @type list of str
2617 @return flag indicating, that the update contained an add
2618 or delete
2619 @rtype bool
2620 """
2621 dname, fname = self.splitPath(name)
2622
2623 # find the root of the repo
2624 repodir = dname
2625 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2626 repodir = os.path.dirname(repodir)
2627 if os.path.splitdrive(repodir)[1] == os.sep:
2628 return False
2629
2630 res = False
2631 if not files:
2632 files = E5FileDialog.getOpenFileNames(
2633 None,
2634 self.tr("Apply changegroups"),
2635 self.__lastChangeGroupPath or repodir,
2636 self.tr("Mercurial Changegroup Files (*.hg);;All Files (*)"))
2637
2638 if files:
2639 self.__lastChangeGroupPath = os.path.dirname(files[0])
2640
2641 update = E5MessageBox.yesNo(
2642 self.__ui,
2643 self.tr("Apply changegroups"),
2644 self.tr("""Shall the working directory be updated?"""),
2645 yesDefault=True)
2646
2647 args = self.initCommand("unbundle")
2648 if update:
2649 args.append("--update")
2650 args.append("--verbose")
2651 args.extend(files)
2652
2653 dia = HgDialog(self.tr('Apply changegroups'), self)
2654 res = dia.startProcess(args, repodir)
2655 if res:
2656 dia.exec_()
2657 res = dia.hasAddOrDelete()
2658 self.checkVCSStatus()
2659
2660 return res
2661
2662 def hgBisect(self, name, subcommand):
2663 """
2664 Public method to perform bisect commands.
2665
2666 @param name file/directory name (string)
2667 @param subcommand name of the subcommand (string, one of 'good', 'bad',
2668 'skip' or 'reset')
2669 @exception ValueError raised to indicate an invalid bisect subcommand
2670 """
2671 if subcommand not in ("good", "bad", "skip", "reset"):
2672 raise ValueError(
2673 self.tr("Bisect subcommand ({0}) invalid.")
2674 .format(subcommand))
2675
2676 dname, fname = self.splitPath(name)
2677
2678 # find the root of the repo
2679 repodir = dname
2680 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2681 repodir = os.path.dirname(repodir)
2682 if os.path.splitdrive(repodir)[1] == os.sep:
2683 return
2684
2685 rev = ""
2686 if subcommand in ("good", "bad", "skip"):
2687 from .HgRevisionSelectionDialog import HgRevisionSelectionDialog
2688 dlg = HgRevisionSelectionDialog(self.hgGetTagsList(repodir),
2689 self.hgGetBranchesList(repodir),
2690 self.hgGetBookmarksList(repodir))
2691 if dlg.exec_() == QDialog.Accepted:
2692 rev = dlg.getRevision()
2693 else:
2694 return
2695
2696 args = self.initCommand("bisect")
2697 args.append("--{0}".format(subcommand))
2698 if rev:
2699 args.append(rev)
2700
2701 dia = HgDialog(
2702 self.tr('Mercurial Bisect ({0})').format(subcommand), self)
2703 res = dia.startProcess(args, repodir)
2704 if res:
2705 dia.exec_()
2706
2707 def hgForget(self, name):
2708 """
2709 Public method used to remove a file from the Mercurial repository.
2710
2711 This will not remove the file from the project directory.
2712
2713 @param name file/directory name to be removed (string or list of
2714 strings))
2715 """
2716 args = self.initCommand("forget")
2717 args.append('-v')
2718
2719 if isinstance(name, list):
2720 dname, fnames = self.splitPathList(name)
2721 self.addArguments(args, name)
2722 else:
2723 dname, fname = self.splitPath(name)
2724 args.append(name)
2725
2726 # find the root of the repo
2727 repodir = dname
2728 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2729 repodir = os.path.dirname(repodir)
2730 if os.path.splitdrive(repodir)[1] == os.sep:
2731 return
2732
2733 dia = HgDialog(
2734 self.tr('Removing files from the Mercurial repository only'),
2735 self)
2736 res = dia.startProcess(args, repodir)
2737 if res:
2738 dia.exec_()
2739 if isinstance(name, list):
2740 self.__forgotNames.extend(name)
2741 else:
2742 self.__forgotNames.append(name)
2743
2744 def hgBackout(self, name):
2745 """
2746 Public method used to backout an earlier changeset from the Mercurial
2747 repository.
2748
2749 @param name directory name (string or list of strings)
2750 """
2751 dname, fname = self.splitPath(name)
2752
2753 # find the root of the repo
2754 repodir = dname
2755 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2756 repodir = os.path.dirname(repodir)
2757 if os.path.splitdrive(repodir)[1] == os.sep:
2758 return
2759
2760 from .HgBackoutDialog import HgBackoutDialog
2761 dlg = HgBackoutDialog(self.hgGetTagsList(repodir),
2762 self.hgGetBranchesList(repodir),
2763 self.hgGetBookmarksList(repodir))
2764 if dlg.exec_() == QDialog.Accepted:
2765 rev, merge, date, user, message = dlg.getParameters()
2766 if not rev:
2767 E5MessageBox.warning(
2768 self.__ui,
2769 self.tr("Backing out changeset"),
2770 self.tr("""No revision given. Aborting..."""))
2771 return
2772
2773 args = self.initCommand("backout")
2774 args.append('-v')
2775 if merge:
2776 args.append('--merge')
2777 if date:
2778 args.append('--date')
2779 args.append(date)
2780 if user:
2781 args.append('--user')
2782 args.append(user)
2783 args.append('--message')
2784 args.append(message)
2785 args.append(rev)
2786
2787 dia = HgDialog(self.tr('Backing out changeset'), self)
2788 res = dia.startProcess(args, repodir)
2789 if res:
2790 dia.exec_()
2791
2792 def hgRollback(self, name):
2793 """
2794 Public method used to rollback the last transaction.
2795
2796 @param name directory name (string or list of strings)
2797 """
2798 dname, fname = self.splitPath(name)
2799
2800 # find the root of the repo
2801 repodir = dname
2802 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2803 repodir = os.path.dirname(repodir)
2804 if os.path.splitdrive(repodir)[1] == os.sep:
2805 return
2806
2807 res = E5MessageBox.yesNo(
2808 None,
2809 self.tr("Rollback last transaction"),
2810 self.tr("""Are you sure you want to rollback the last"""
2811 """ transaction?"""),
2812 icon=E5MessageBox.Warning)
2813 if res:
2814 dia = HgDialog(self.tr('Rollback last transaction'), self)
2815 res = dia.startProcess(["rollback"], repodir)
2816 if res:
2817 dia.exec_()
2818
2819 def hgServe(self, name):
2820 """
2821 Public method used to serve the project.
2822
2823 @param name directory name (string)
2824 """
2825 dname, fname = self.splitPath(name)
2826
2827 # find the root of the repo
2828 repodir = dname
2829 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2830 repodir = os.path.dirname(repodir)
2831 if os.path.splitdrive(repodir)[1] == os.sep:
2832 return
2833
2834 from .HgServeDialog import HgServeDialog
2835 self.serveDlg = HgServeDialog(self, repodir)
2836 self.serveDlg.show()
2837
2838 def hgImport(self, name):
2839 """
2840 Public method to import a patch file.
2841
2842 @param name directory name of the project to import into (string)
2843 @return flag indicating, that the import contained an add, a delete
2844 or a change to the project file (boolean)
2845 """
2846 dname, fname = self.splitPath(name)
2847
2848 # find the root of the repo
2849 repodir = dname
2850 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2851 repodir = os.path.dirname(repodir)
2852 if os.path.splitdrive(repodir)[1] == os.sep:
2853 return False
2854
2855 from .HgImportDialog import HgImportDialog
2856 dlg = HgImportDialog()
2857 if dlg.exec_() == QDialog.Accepted:
2858 patchFile, noCommit, message, date, user, stripCount, force = \
2859 dlg.getParameters()
2860
2861 args = self.initCommand("import")
2862 args.append("--verbose")
2863 if noCommit:
2864 args.append("--no-commit")
2865 else:
2866 if message:
2867 args.append('--message')
2868 args.append(message)
2869 if date:
2870 args.append('--date')
2871 args.append(date)
2872 if user:
2873 args.append('--user')
2874 args.append(user)
2875 if stripCount != 1:
2876 args.append("--strip")
2877 args.append(str(stripCount))
2878 if force:
2879 args.append("--force")
2880 args.append(patchFile)
2881
2882 dia = HgDialog(self.tr("Import Patch"), self)
2883 res = dia.startProcess(args, repodir)
2884 if res:
2885 dia.exec_()
2886 res = dia.hasAddOrDelete()
2887 self.checkVCSStatus()
2888 else:
2889 res = False
2890
2891 return res
2892
2893 def hgExport(self, name):
2894 """
2895 Public method to export patches to files.
2896
2897 @param name directory name of the project to export from (string)
2898 """
2899 dname, fname = self.splitPath(name)
2900
2901 # find the root of the repo
2902 repodir = dname
2903 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2904 repodir = os.path.dirname(repodir)
2905 if os.path.splitdrive(repodir)[1] == os.sep:
2906 return
2907
2908 from .HgExportDialog import HgExportDialog
2909 dlg = HgExportDialog(self.hgGetBookmarksList(repodir),
2910 self.version >= (4, 7, 0))
2911 if dlg.exec_() == QDialog.Accepted:
2912 filePattern, revisions, bookmark, switchParent, allText, noDates, \
2913 git = dlg.getParameters()
2914
2915 args = self.initCommand("export")
2916 args.append("--output")
2917 args.append(filePattern)
2918 args.append("--verbose")
2919 if switchParent:
2920 args.append("--switch-parent")
2921 if allText:
2922 args.append("--text")
2923 if noDates:
2924 args.append("--nodates")
2925 if git:
2926 args.append("--git")
2927 if bookmark:
2928 args.append("--bookmark")
2929 args.append(bookmark)
2930 else:
2931 for rev in revisions:
2932 args.append(rev)
2933
2934 dia = HgDialog(self.tr("Export Patches"), self)
2935 res = dia.startProcess(args, repodir)
2936 if res:
2937 dia.exec_()
2938
2939 def hgPhase(self, name, data=None):
2940 """
2941 Public method to change the phase of revisions.
2942
2943 @param name directory name of the project to export from (string)
2944 @param data tuple giving phase data (list of revisions, phase, flag
2945 indicating a forced operation) (list of strings, string, boolean)
2946 @return flag indicating success (boolean)
2947 @exception ValueError raised to indicate an invalid phase
2948 """
2949 dname, fname = self.splitPath(name)
2950
2951 # find the root of the repo
2952 repodir = dname
2953 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
2954 repodir = os.path.dirname(repodir)
2955 if os.path.splitdrive(repodir)[1] == os.sep:
2956 return False
2957
2958 if data is None:
2959 from .HgPhaseDialog import HgPhaseDialog
2960 dlg = HgPhaseDialog()
2961 if dlg.exec_() == QDialog.Accepted:
2962 data = dlg.getData()
2963
2964 if data:
2965 revs, phase, force = data
2966
2967 args = self.initCommand("phase")
2968 if phase == "p":
2969 args.append("--public")
2970 elif phase == "d":
2971 args.append("--draft")
2972 elif phase == "s":
2973 args.append("--secret")
2974 else:
2975 raise ValueError("Invalid phase given.")
2976 if force:
2977 args.append("--force")
2978 for rev in revs:
2979 args.append(rev)
2980
2981 dia = HgDialog(self.tr("Change Phase"), self)
2982 res = dia.startProcess(args, repodir)
2983 if res:
2984 dia.exec_()
2985 res = dia.normalExitWithoutErrors()
2986 else:
2987 res = False
2988
2989 return res
2990
2991 def hgGraft(self, path, revs=None):
2992 """
2993 Public method to copy changesets from another branch.
2994
2995 @param path directory name of the project (string)
2996 @param revs list of revisions to show in the revisions pane (list of
2997 strings)
2998 @return flag indicating that the project should be reread (boolean)
2999 """
3000 # find the root of the repo
3001 repodir = self.splitPath(path)[0]
3002 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3003 repodir = os.path.dirname(repodir)
3004 if os.path.splitdrive(repodir)[1] == os.sep:
3005 return False
3006
3007 from .HgGraftDialog import HgGraftDialog
3008 res = False
3009 dlg = HgGraftDialog(self, revs)
3010 if dlg.exec_() == QDialog.Accepted:
3011 revs, (userData, currentUser, userName), \
3012 (dateData, currentDate, dateStr), log, dryrun, \
3013 noCommit = dlg.getData()
3014
3015 args = self.initCommand("graft")
3016 args.append("--verbose")
3017 if userData:
3018 if currentUser:
3019 args.append("--currentuser")
3020 else:
3021 args.append("--user")
3022 args.append(userName)
3023 if dateData:
3024 if currentDate:
3025 args.append("--currentdate")
3026 else:
3027 args.append("--date")
3028 args.append(dateStr)
3029 if log:
3030 args.append("--log")
3031 if dryrun:
3032 args.append("--dry-run")
3033 if noCommit:
3034 args.append("--no-commit")
3035 args.extend(revs)
3036
3037 dia = HgDialog(self.tr('Copy Changesets'), self)
3038 res = dia.startProcess(args, repodir)
3039 if res:
3040 dia.exec_()
3041 res = dia.hasAddOrDelete()
3042 self.checkVCSStatus()
3043 return res
3044
3045 def __hgGraftSubCommand(self, path, subcommand, title):
3046 """
3047 Private method to perform a Mercurial graft subcommand.
3048
3049 @param path directory name of the project
3050 @type str
3051 @param subcommand subcommand flag
3052 @type str
3053 @param title tirle of the dialog
3054 @type str
3055 @return flag indicating that the project should be reread
3056 @rtype bool
3057 """
3058 # find the root of the repo
3059 repodir = self.splitPath(path)[0]
3060 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3061 repodir = os.path.dirname(repodir)
3062 if os.path.splitdrive(repodir)[1] == os.sep:
3063 return False
3064
3065 args = self.initCommand("graft")
3066 args.append(subcommand)
3067 args.append("--verbose")
3068
3069 dia = HgDialog(title, self)
3070 res = dia.startProcess(args, repodir)
3071 if res:
3072 dia.exec_()
3073 res = dia.hasAddOrDelete()
3074 self.checkVCSStatus()
3075 return res
3076
3077 def hgGraftContinue(self, path):
3078 """
3079 Public method to continue copying changesets from another branch.
3080
3081 @param path directory name of the project
3082 @type str
3083 @return flag indicating that the project should be reread
3084 @rtype bool
3085 """
3086 return self.__hgGraftSubCommand(
3087 path, "--continue", self.tr('Copy Changesets (Continue)'))
3088
3089 def hgGraftStop(self, path):
3090 """
3091 Public method to stop an interrupted copying session.
3092
3093 @param path directory name of the project
3094 @type str
3095 @return flag indicating that the project should be reread
3096 @rtype bool
3097 """
3098 return self.__hgGraftSubCommand(
3099 path, "--stop", self.tr('Copy Changesets (Stop)'))
3100
3101 def hgGraftAbort(self, path):
3102 """
3103 Public method to abort an interrupted copying session and perform
3104 a rollback.
3105
3106 @param path directory name of the project
3107 @type str
3108 @return flag indicating that the project should be reread
3109 @rtype bool
3110 """
3111 return self.__hgGraftSubCommand(
3112 path, "--abort", self.tr('Copy Changesets (Abort)'))
3113
3114 def hgArchive(self):
3115 """
3116 Public method to create an unversioned archive from the repository.
3117 """
3118 # find the root of the repo
3119 repodir = self.__projectHelper.getProject().getProjectPath()
3120 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3121 repodir = os.path.dirname(repodir)
3122 if os.path.splitdrive(repodir)[1] == os.sep:
3123 return
3124
3125 from .HgArchiveDialog import HgArchiveDialog
3126 dlg = HgArchiveDialog(self)
3127 if dlg.exec_() == QDialog.Accepted:
3128 archive, type_, prefix, subrepos = dlg.getData()
3129
3130 args = self.initCommand("archive")
3131 if type_:
3132 args.append("--type")
3133 args.append(type_)
3134 if prefix:
3135 args.append("--prefix")
3136 args.append(prefix)
3137 if subrepos:
3138 args.append("--subrepos")
3139 args.append(archive)
3140
3141 dia = HgDialog(self.tr("Create Unversioned Archive"), self)
3142 res = dia.startProcess(args, repodir)
3143 if res:
3144 dia.exec_()
3145
3146 def hgDeleteBackups(self):
3147 """
3148 Public method to delete all backup bundles in the backup area.
3149 """
3150 # find the root of the repo
3151 repodir = self.__projectHelper.getProject().getProjectPath()
3152 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3153 repodir = os.path.dirname(repodir)
3154 if os.path.splitdrive(repodir)[1] == os.sep:
3155 return
3156
3157 backupdir = os.path.join(repodir, self.adminDir, "strip-backup")
3158 yes = E5MessageBox.yesNo(
3159 self.__ui,
3160 self.tr("Delete All Backups"),
3161 self.tr("""<p>Do you really want to delete all backup bundles"""
3162 """ stored the backup area <b>{0}</b>?</p>""").format(
3163 backupdir))
3164 if yes:
3165 shutil.rmtree(backupdir, True)
3166
3167 ###########################################################################
3168 ## Methods to deal with subrepositories are below.
3169 ###########################################################################
3170
3171 def getHgSubPath(self):
3172 """
3173 Public method to get the path to the .hgsub file containing the
3174 definitions of sub-repositories.
3175
3176 @return full path of the .hgsub file (string)
3177 """
3178 ppath = self.__projectHelper.getProject().getProjectPath()
3179 return os.path.join(ppath, ".hgsub")
3180
3181 def hasSubrepositories(self):
3182 """
3183 Public method to check, if the project might have sub-repositories.
3184
3185 @return flag indicating the existence of sub-repositories (boolean)
3186 """
3187 hgsub = self.getHgSubPath()
3188 return os.path.isfile(hgsub) and os.stat(hgsub).st_size > 0
3189
3190 def hgAddSubrepository(self):
3191 """
3192 Public method to add a sub-repository.
3193 """
3194 from .HgAddSubrepositoryDialog import HgAddSubrepositoryDialog
3195 ppath = self.__projectHelper.getProject().getProjectPath()
3196 hgsub = self.getHgSubPath()
3197 dlg = HgAddSubrepositoryDialog(ppath)
3198 if dlg.exec_() == QDialog.Accepted:
3199 relPath, subrepoType, subrepoUrl = dlg.getData()
3200 if subrepoType == "hg":
3201 url = subrepoUrl
3202 else:
3203 url = "[{0}]{1}".format(subrepoType, subrepoUrl)
3204 entry = "{0} = {1}\n".format(relPath, url)
3205
3206 contents = []
3207 if os.path.isfile(hgsub):
3208 # file exists; check, if such an entry exists already
3209 needsAdd = False
3210 try:
3211 f = open(hgsub, "r")
3212 contents = f.readlines()
3213 f.close()
3214 except IOError as err:
3215 E5MessageBox.critical(
3216 self.__ui,
3217 self.tr("Add Sub-repository"),
3218 self.tr(
3219 """<p>The sub-repositories file .hgsub could not"""
3220 """ be read.</p><p>Reason: {0}</p>""")
3221 .format(str(err)))
3222 return
3223
3224 if entry in contents:
3225 E5MessageBox.critical(
3226 self.__ui,
3227 self.tr("Add Sub-repository"),
3228 self.tr(
3229 """<p>The sub-repositories file .hgsub already"""
3230 """ contains an entry <b>{0}</b>."""
3231 """ Aborting...</p>""").format(entry))
3232 return
3233 else:
3234 needsAdd = True
3235
3236 if contents and not contents[-1].endswith("\n"):
3237 contents[-1] = contents[-1] + "\n"
3238 contents.append(entry)
3239 try:
3240 f = open(hgsub, "w")
3241 f.writelines(contents)
3242 f.close()
3243 except IOError as err:
3244 E5MessageBox.critical(
3245 self.__ui,
3246 self.tr("Add Sub-repository"),
3247 self.tr(
3248 """<p>The sub-repositories file .hgsub could not"""
3249 """ be written to.</p><p>Reason: {0}</p>""")
3250 .format(str(err)))
3251 return
3252
3253 if needsAdd:
3254 self.vcsAdd(hgsub)
3255 self.__projectHelper.getProject().appendFile(hgsub)
3256
3257 def hgRemoveSubrepositories(self):
3258 """
3259 Public method to remove sub-repositories.
3260 """
3261 hgsub = self.getHgSubPath()
3262
3263 subrepositories = []
3264 if not os.path.isfile(hgsub):
3265 E5MessageBox.critical(
3266 self.__ui,
3267 self.tr("Remove Sub-repositories"),
3268 self.tr("""<p>The sub-repositories file .hgsub does not"""
3269 """ exist. Aborting...</p>"""))
3270 return
3271
3272 try:
3273 f = open(hgsub, "r")
3274 subrepositories = [line.strip() for line in f.readlines()]
3275 f.close()
3276 except IOError as err:
3277 E5MessageBox.critical(
3278 self.__ui,
3279 self.tr("Remove Sub-repositories"),
3280 self.tr("""<p>The sub-repositories file .hgsub could not"""
3281 """ be read.</p><p>Reason: {0}</p>""")
3282 .format(str(err)))
3283 return
3284
3285 from .HgRemoveSubrepositoriesDialog import \
3286 HgRemoveSubrepositoriesDialog
3287 dlg = HgRemoveSubrepositoriesDialog(subrepositories)
3288 if dlg.exec_() == QDialog.Accepted:
3289 subrepositories, removedSubrepos, deleteSubrepos = dlg.getData()
3290 contents = "\n".join(subrepositories) + "\n"
3291 try:
3292 f = open(hgsub, "w")
3293 f.write(contents)
3294 f.close()
3295 except IOError as err:
3296 E5MessageBox.critical(
3297 self.__ui,
3298 self.tr("Remove Sub-repositories"),
3299 self.tr(
3300 """<p>The sub-repositories file .hgsub could not"""
3301 """ be written to.</p><p>Reason: {0}</p>""")
3302 .format(str(err)))
3303 return
3304
3305 if deleteSubrepos:
3306 ppath = self.__projectHelper.getProject().getProjectPath()
3307 for removedSubrepo in removedSubrepos:
3308 subrepoPath = removedSubrepo.split("=", 1)[0].strip()
3309 subrepoAbsPath = os.path.join(ppath, subrepoPath)
3310 shutil.rmtree(subrepoAbsPath, True)
3311
3312 ###########################################################################
3313 ## Methods to handle configuration dependent stuff are below.
3314 ###########################################################################
3315
3316 def __checkDefaults(self):
3317 """
3318 Private method to check, if the default and default-push URLs
3319 have been configured.
3320 """
3321 args = self.initCommand("showconfig")
3322 args.append('paths')
3323
3324 output = ""
3325 if self.__client is None:
3326 process = QProcess()
3327 self.__repoDir and process.setWorkingDirectory(self.__repoDir)
3328 process.start('hg', args)
3329 procStarted = process.waitForStarted(5000)
3330 if procStarted:
3331 finished = process.waitForFinished(30000)
3332 if finished and process.exitCode() == 0:
3333 output = str(process.readAllStandardOutput(),
3334 self.getEncoding(), 'replace')
3335 else:
3336 output, error = self.__client.runcommand(args)
3337
3338 self.__defaultConfigured = False
3339 self.__defaultPushConfigured = False
3340 if output:
3341 for line in output.splitlines():
3342 if line.startswith("paths.default=") and \
3343 not line.strip().endswith("="):
3344 self.__defaultConfigured = True
3345 if line.startswith("paths.default-push=") and \
3346 not line.strip().endswith("="):
3347 self.__defaultPushConfigured = True
3348
3349 def canPull(self):
3350 """
3351 Public method to check, if pull is possible.
3352
3353 @return flag indicating pull capability (boolean)
3354 """
3355 return self.__defaultConfigured
3356
3357 def canPush(self):
3358 """
3359 Public method to check, if push is possible.
3360
3361 @return flag indicating push capability (boolean)
3362 """
3363 return self.__defaultPushConfigured or self.__defaultConfigured
3364
3365 def __iniFileChanged(self, path):
3366 """
3367 Private slot to handle a change of the Mercurial configuration file.
3368
3369 @param path name of the changed file (string)
3370 """
3371 if self.__client:
3372 ok, err = self.__client.restartServer()
3373 if not ok:
3374 E5MessageBox.warning(
3375 None,
3376 self.tr("Mercurial Command Server"),
3377 self.tr(
3378 """<p>The Mercurial Command Server could not be"""
3379 """ restarted.</p><p>Reason: {0}</p>""").format(err))
3380 self.__client = None
3381
3382 self.__getExtensionsInfo()
3383
3384 if self.__repoIniFile and path == self.__repoIniFile:
3385 self.__checkDefaults()
3386
3387 self.iniFileChanged.emit()
3388
3389 def __monitorRepoIniFile(self, name):
3390 """
3391 Private slot to add a repository configuration file to the list of
3392 monitored files.
3393
3394 @param name directory name pointing into the repository (string)
3395 """
3396 dname, fname = self.splitPath(name)
3397
3398 # find the root of the repo
3399 repodir = dname
3400 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3401 repodir = os.path.dirname(repodir)
3402 if not repodir or os.path.splitdrive(repodir)[1] == os.sep:
3403 return
3404
3405 cfgFile = os.path.join(repodir, self.adminDir, "hgrc")
3406 if os.path.exists(cfgFile):
3407 self.__iniWatcher.addPath(cfgFile)
3408 self.__repoIniFile = cfgFile
3409 self.__checkDefaults()
3410
3411 ###########################################################################
3412 ## Methods to handle extensions are below.
3413 ###########################################################################
3414
3415 def __getExtensionsInfo(self):
3416 """
3417 Private method to get the active extensions from Mercurial.
3418 """
3419 activeExtensions = sorted(self.__activeExtensions)
3420 self.__activeExtensions = []
3421
3422 args = self.initCommand("showconfig")
3423 args.append('extensions')
3424
3425 output = ""
3426 if self.__client is None:
3427 process = QProcess()
3428 self.__repoDir and process.setWorkingDirectory(self.__repoDir)
3429 process.start('hg', args)
3430 procStarted = process.waitForStarted(5000)
3431 if procStarted:
3432 finished = process.waitForFinished(30000)
3433 if finished and process.exitCode() == 0:
3434 output = str(process.readAllStandardOutput(),
3435 self.getEncoding(), 'replace')
3436 else:
3437 output, error = self.__client.runcommand(args)
3438
3439 if output:
3440 for line in output.splitlines():
3441 extensionName = \
3442 line.split("=", 1)[0].strip().split(".")[-1].strip()
3443 self.__activeExtensions.append(extensionName)
3444
3445 if activeExtensions != sorted(self.__activeExtensions):
3446 self.activeExtensionsChanged.emit()
3447
3448 def isExtensionActive(self, extensionName):
3449 """
3450 Public method to check, if an extension is active.
3451
3452 @param extensionName name of the extension to check for (string)
3453 @return flag indicating an active extension (boolean)
3454 """
3455 extensionName = extensionName.strip()
3456 isActive = extensionName in self.__activeExtensions
3457
3458 return isActive
3459
3460 def getExtensionObject(self, extensionName):
3461 """
3462 Public method to get a reference to an extension object.
3463
3464 @param extensionName name of the extension (string)
3465 @return reference to the extension object (boolean)
3466 """
3467 return self.__extensions[extensionName]
3468
3469 ###########################################################################
3470 ## Methods to get the helper objects are below.
3471 ###########################################################################
3472
3473 def vcsGetProjectBrowserHelper(self, browser, project,
3474 isTranslationsBrowser=False):
3475 """
3476 Public method to instantiate a helper object for the different
3477 project browsers.
3478
3479 @param browser reference to the project browser object
3480 @param project reference to the project object
3481 @param isTranslationsBrowser flag indicating, the helper is requested
3482 for the translations browser (this needs some special treatment)
3483 @return the project browser helper object
3484 """
3485 from .ProjectBrowserHelper import HgProjectBrowserHelper
3486 return HgProjectBrowserHelper(self, browser, project,
3487 isTranslationsBrowser)
3488
3489 def vcsGetProjectHelper(self, project):
3490 """
3491 Public method to instantiate a helper object for the project.
3492
3493 @param project reference to the project object
3494 @return the project helper object
3495 """
3496 # find the root of the repo
3497 repodir = project.getProjectPath()
3498 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3499 repodir = os.path.dirname(repodir)
3500 if not repodir or os.path.splitdrive(repodir)[1] == os.sep:
3501 repodir = ""
3502 break
3503 if repodir:
3504 self.__repoDir = repodir
3505
3506 self.__projectHelper = self.__plugin.getProjectHelper()
3507 self.__projectHelper.setObjects(self, project)
3508 self.__monitorRepoIniFile(project.getProjectPath())
3509
3510 if repodir:
3511 from .HgClient import HgClient
3512 client = HgClient(repodir, "utf-8", self)
3513 ok, err = client.startServer()
3514 if ok:
3515 self.__client = client
3516 else:
3517 E5MessageBox.warning(
3518 None,
3519 self.tr("Mercurial Command Server"),
3520 self.tr(
3521 """<p>The Mercurial Command Server could not be"""
3522 """ started.</p><p>Reason: {0}</p>""").format(err))
3523
3524 return self.__projectHelper
3525
3526 ###########################################################################
3527 ## Status Monitor Thread methods
3528 ###########################################################################
3529
3530 def _createStatusMonitorThread(self, interval, project):
3531 """
3532 Protected method to create an instance of the VCS status monitor
3533 thread.
3534
3535 @param interval check interval for the monitor thread in seconds
3536 (integer)
3537 @param project reference to the project object (Project)
3538 @return reference to the monitor thread (QThread)
3539 """
3540 from .HgStatusMonitorThread import HgStatusMonitorThread
3541 return HgStatusMonitorThread(interval, project, self)
3542
3543 ###########################################################################
3544 ## Bookmarks methods
3545 ###########################################################################
3546
3547 def hgListBookmarks(self, path):
3548 """
3549 Public method used to list the available bookmarks.
3550
3551 @param path directory name of the project (string)
3552 """
3553 self.bookmarksList = []
3554
3555 if self.bookmarksListDlg is None:
3556 from .HgBookmarksListDialog import HgBookmarksListDialog
3557 self.bookmarksListDlg = HgBookmarksListDialog(self)
3558 self.bookmarksListDlg.show()
3559 self.bookmarksListDlg.raise_()
3560 self.bookmarksListDlg.start(path, self.bookmarksList)
3561
3562 def hgGetBookmarksList(self, repodir):
3563 """
3564 Public method to get the list of bookmarks.
3565
3566 @param repodir directory name of the repository (string)
3567 @return list of bookmarks (list of string)
3568 """
3569 args = self.initCommand("bookmarks")
3570
3571 client = self.getClient()
3572 output = ""
3573 if client:
3574 output = client.runcommand(args)[0]
3575 else:
3576 process = QProcess()
3577 process.setWorkingDirectory(repodir)
3578 process.start('hg', args)
3579 procStarted = process.waitForStarted(5000)
3580 if procStarted:
3581 finished = process.waitForFinished(30000)
3582 if finished and process.exitCode() == 0:
3583 output = str(process.readAllStandardOutput(),
3584 self.getEncoding(), 'replace')
3585
3586 self.bookmarksList = []
3587 for line in output.splitlines():
3588 li = line.strip().split()
3589 if li[-1][0] in "1234567890":
3590 # last element is a rev:changeset
3591 del li[-1]
3592 if li[0] == "*":
3593 del li[0]
3594 name = " ".join(li)
3595 self.bookmarksList.append(name)
3596
3597 return self.bookmarksList[:]
3598
3599 def hgBookmarkDefine(self, name, revision=None, bookmark=None):
3600 """
3601 Public method to define a bookmark.
3602
3603 @param name file/directory name (string)
3604 @param revision revision to set bookmark for (string)
3605 @param bookmark name of the bookmark (string)
3606 """
3607 # find the root of the repo
3608 repodir = self.splitPath(name)[0]
3609 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3610 repodir = os.path.dirname(repodir)
3611 if os.path.splitdrive(repodir)[1] == os.sep:
3612 return
3613
3614 if bool(revision) and bool(bookmark):
3615 ok = True
3616 else:
3617 from .HgBookmarkDialog import HgBookmarkDialog
3618 dlg = HgBookmarkDialog(HgBookmarkDialog.DEFINE_MODE,
3619 self.hgGetTagsList(repodir),
3620 self.hgGetBranchesList(repodir),
3621 self.hgGetBookmarksList(repodir))
3622 if dlg.exec_() == QDialog.Accepted:
3623 revision, bookmark = dlg.getData()
3624 ok = True
3625 else:
3626 ok = False
3627
3628 if ok:
3629 args = self.initCommand("bookmarks")
3630 if revision:
3631 args.append("--rev")
3632 args.append(revision)
3633 args.append(bookmark)
3634
3635 dia = HgDialog(self.tr('Mercurial Bookmark'), self)
3636 res = dia.startProcess(args, repodir)
3637 if res:
3638 dia.exec_()
3639
3640 def hgBookmarkDelete(self, name, bookmark=None):
3641 """
3642 Public method to delete a bookmark.
3643
3644 @param name file/directory name (string)
3645 @param bookmark name of the bookmark (string)
3646 """
3647 # find the root of the repo
3648 repodir = self.splitPath(name)[0]
3649 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3650 repodir = os.path.dirname(repodir)
3651 if os.path.splitdrive(repodir)[1] == os.sep:
3652 return
3653
3654 if bookmark:
3655 ok = True
3656 else:
3657 bookmark, ok = QInputDialog.getItem(
3658 None,
3659 self.tr("Delete Bookmark"),
3660 self.tr("Select the bookmark to be deleted:"),
3661 [""] + sorted(self.hgGetBookmarksList(repodir)),
3662 0, True)
3663 if ok and bookmark:
3664 args = self.initCommand("bookmarks")
3665 args.append("--delete")
3666 args.append(bookmark)
3667
3668 dia = HgDialog(self.tr('Delete Mercurial Bookmark'), self)
3669 res = dia.startProcess(args, repodir)
3670 if res:
3671 dia.exec_()
3672
3673 def hgBookmarkRename(self, name, renameInfo=None):
3674 """
3675 Public method to rename a bookmark.
3676
3677 @param name file/directory name
3678 @type str
3679 @param renameInfo old and new names of the bookmark
3680 @type tuple of str and str
3681 """
3682 # find the root of the repo
3683 repodir = self.splitPath(name)[0]
3684 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3685 repodir = os.path.dirname(repodir)
3686 if os.path.splitdrive(repodir)[1] == os.sep:
3687 return
3688
3689 if not renameInfo:
3690 from .HgBookmarkRenameDialog import HgBookmarkRenameDialog
3691 dlg = HgBookmarkRenameDialog(self.hgGetBookmarksList(repodir))
3692 if dlg.exec_() == QDialog.Accepted:
3693 renameInfo = dlg.getData()
3694
3695 if renameInfo:
3696 args = self.initCommand("bookmarks")
3697 args.append("--rename")
3698 args.append(renameInfo[0])
3699 args.append(renameInfo[1])
3700
3701 dia = HgDialog(self.tr('Rename Mercurial Bookmark'), self)
3702 res = dia.startProcess(args, repodir)
3703 if res:
3704 dia.exec_()
3705
3706 def hgBookmarkMove(self, name, revision=None, bookmark=None):
3707 """
3708 Public method to move a bookmark.
3709
3710 @param name file/directory name (string)
3711 @param revision revision to set bookmark for (string)
3712 @param bookmark name of the bookmark (string)
3713 """
3714 # find the root of the repo
3715 repodir = self.splitPath(name)[0]
3716 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3717 repodir = os.path.dirname(repodir)
3718 if os.path.splitdrive(repodir)[1] == os.sep:
3719 return
3720
3721 if bool(revision) and bool(bookmark):
3722 ok = True
3723 else:
3724 from .HgBookmarkDialog import HgBookmarkDialog
3725 dlg = HgBookmarkDialog(HgBookmarkDialog.MOVE_MODE,
3726 self.hgGetTagsList(repodir),
3727 self.hgGetBranchesList(repodir),
3728 self.hgGetBookmarksList(repodir))
3729 if dlg.exec_() == QDialog.Accepted:
3730 revision, bookmark = dlg.getData()
3731 ok = True
3732 else:
3733 ok = False
3734
3735 if ok:
3736 args = self.initCommand("bookmarks")
3737 args.append("--force")
3738 if revision:
3739 args.append("--rev")
3740 args.append(revision)
3741 args.append(bookmark)
3742
3743 dia = HgDialog(self.tr('Move Mercurial Bookmark'), self)
3744 res = dia.startProcess(args, repodir)
3745 if res:
3746 dia.exec_()
3747
3748 def hgBookmarkIncoming(self, name):
3749 """
3750 Public method to show a list of incoming bookmarks.
3751
3752 @param name file/directory name (string)
3753 """
3754 from .HgBookmarksInOutDialog import HgBookmarksInOutDialog
3755 self.bookmarksInOutDlg = HgBookmarksInOutDialog(
3756 self, HgBookmarksInOutDialog.INCOMING)
3757 self.bookmarksInOutDlg.show()
3758 self.bookmarksInOutDlg.start(name)
3759
3760 def hgBookmarkOutgoing(self, name):
3761 """
3762 Public method to show a list of outgoing bookmarks.
3763
3764 @param name file/directory name (string)
3765 """
3766 from .HgBookmarksInOutDialog import HgBookmarksInOutDialog
3767 self.bookmarksInOutDlg = HgBookmarksInOutDialog(
3768 self, HgBookmarksInOutDialog.OUTGOING)
3769 self.bookmarksInOutDlg.show()
3770 self.bookmarksInOutDlg.start(name)
3771
3772 def __getInOutBookmarks(self, repodir, incoming):
3773 """
3774 Private method to get the list of incoming or outgoing bookmarks.
3775
3776 @param repodir directory name of the repository (string)
3777 @param incoming flag indicating to get incoming bookmarks (boolean)
3778 @return list of bookmarks (list of string)
3779 """
3780 bookmarksList = []
3781
3782 if incoming:
3783 args = self.initCommand("incoming")
3784 else:
3785 args = self.initCommand("outgoing")
3786 args.append('--bookmarks')
3787
3788 client = self.getClient()
3789 output = ""
3790 if client:
3791 output = client.runcommand(args)[0]
3792 else:
3793 process = QProcess()
3794 process.setWorkingDirectory(repodir)
3795 process.start('hg', args)
3796 procStarted = process.waitForStarted(5000)
3797 if procStarted:
3798 finished = process.waitForFinished(30000)
3799 if finished and process.exitCode() == 0:
3800 output = str(process.readAllStandardOutput(),
3801 self.getEncoding(), 'replace')
3802
3803 for line in output.splitlines():
3804 if line.startswith(" "):
3805 li = line.strip().split()
3806 del li[-1]
3807 name = " ".join(li)
3808 bookmarksList.append(name)
3809
3810 return bookmarksList
3811
3812 def hgBookmarkPull(self, name, current=False, bookmark=None):
3813 """
3814 Public method to pull a bookmark from a remote repository.
3815
3816 @param name file/directory name
3817 @type str
3818 @param current flag indicating to pull the current bookmark
3819 @type bool
3820 @param bookmark name of the bookmark
3821 @type str
3822 """
3823 # find the root of the repo
3824 repodir = self.splitPath(name)[0]
3825 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3826 repodir = os.path.dirname(repodir)
3827 if os.path.splitdrive(repodir)[1] == os.sep:
3828 return
3829
3830 if current:
3831 bookmark = "."
3832 ok = True
3833 elif bookmark:
3834 ok = True
3835 else:
3836 bookmarks = self.__getInOutBookmarks(repodir, True)
3837 bookmark, ok = QInputDialog.getItem(
3838 None,
3839 self.tr("Pull Bookmark"),
3840 self.tr("Select the bookmark to be pulled:"),
3841 [""] + sorted(bookmarks),
3842 0, True)
3843
3844 if ok and bookmark:
3845 args = self.initCommand("pull")
3846 args.append('--bookmark')
3847 args.append(bookmark)
3848
3849 dia = HgDialog(self.tr(
3850 'Pulling bookmark from a remote Mercurial repository'),
3851 self)
3852 res = dia.startProcess(args, repodir)
3853 if res:
3854 dia.exec_()
3855
3856 def hgBookmarkPush(self, name, current=False, bookmark=None):
3857 """
3858 Public method to push a bookmark to a remote repository.
3859
3860 @param name file/directory name
3861 @type str
3862 @param current flag indicating to push the current bookmark
3863 @type bool
3864 @param bookmark name of the bookmark
3865 @type str
3866 """
3867 # find the root of the repo
3868 repodir = self.splitPath(name)[0]
3869 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
3870 repodir = os.path.dirname(repodir)
3871 if os.path.splitdrive(repodir)[1] == os.sep:
3872 return
3873
3874 if current:
3875 bookmark = "."
3876 ok = True
3877 elif bookmark:
3878 ok = True
3879 else:
3880 bookmarks = self.__getInOutBookmarks(repodir, False)
3881 bookmark, ok = QInputDialog.getItem(
3882 None,
3883 self.tr("Push Bookmark"),
3884 self.tr("Select the bookmark to be push:"),
3885 [""] + sorted(bookmarks),
3886 0, True)
3887
3888 if ok and bookmark:
3889 args = self.initCommand("push")
3890 args.append('--bookmark')
3891 args.append(bookmark)
3892
3893 dia = HgDialog(self.tr(
3894 'Pushing bookmark to a remote Mercurial repository'),
3895 self)
3896 res = dia.startProcess(args, repodir)
3897 if res:
3898 dia.exec_()

eric ide

mercurial