Plugins/VcsPlugins/vcsMercurial/hg.py

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

eric ide

mercurial