src/eric7/Plugins/VcsPlugins/vcsMercurial/hg.py

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

eric ide

mercurial