eric7/Plugins/VcsPlugins/vcsMercurial/hg.py

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

eric ide

mercurial