src/eric7/VCS/ProjectHelper.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) 2005 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the base class of the VCS project helper.
8 """
9
10 import os
11 import shutil
12 import copy
13 import pathlib
14
15 from PyQt6.QtCore import pyqtSlot, QObject, QCoreApplication
16 from PyQt6.QtWidgets import QDialog, QInputDialog, QToolBar
17
18 from EricGui.EricAction import EricAction
19 from EricWidgets import EricMessageBox
20 from EricWidgets.EricApplication import ericApp
21
22 import Preferences
23 import UI.PixmapCache
24 import UI.Config
25
26
27 class VcsProjectHelper(QObject):
28 """
29 Class implementing the base class of the VCS project helper.
30 """
31 def __init__(self, vcsObject, projectObject, parent=None, name=None):
32 """
33 Constructor
34
35 @param vcsObject reference to the vcs object
36 @param projectObject reference to the project object
37 @param parent parent widget (QWidget)
38 @param name name of this object (string)
39 """
40 super().__init__(parent)
41 if name:
42 self.setObjectName(name)
43
44 self.vcs = vcsObject
45 self.project = projectObject
46
47 self.actions = []
48
49 self.vcsAddAct = None
50
51 self.initActions()
52
53 def setObjects(self, vcsObject, projectObject):
54 """
55 Public method to set references to the vcs and project objects.
56
57 @param vcsObject reference to the vcs object
58 @param projectObject reference to the project object
59 """
60 self.vcs = vcsObject
61 self.project = projectObject
62
63 def initActions(self):
64 """
65 Public method to generate the action objects.
66 """
67 self.vcsNewAct = EricAction(
68 QCoreApplication.translate(
69 "VcsProjectHelper", 'New from repository'),
70 UI.PixmapCache.getIcon("vcsCheckout"),
71 QCoreApplication.translate(
72 "VcsProjectHelper", '&New from repository...'),
73 0, 0, self, 'vcs_new')
74 self.vcsNewAct.setStatusTip(QCoreApplication.translate(
75 "VcsProjectHelper",
76 'Create a new project from the VCS repository'
77 ))
78 self.vcsNewAct.setWhatsThis(QCoreApplication.translate(
79 "VcsProjectHelper",
80 """<b>New from repository</b>"""
81 """<p>This creates a new local project from the VCS"""
82 """ repository.</p>"""
83 ))
84 self.vcsNewAct.triggered.connect(self._vcsCheckout)
85 self.actions.append(self.vcsNewAct)
86
87 self.vcsExportAct = EricAction(
88 QCoreApplication.translate(
89 "VcsProjectHelper", 'Export from repository'),
90 UI.PixmapCache.getIcon("vcsExport"),
91 QCoreApplication.translate(
92 "VcsProjectHelper", '&Export from repository...'),
93 0, 0, self, 'vcs_export')
94 self.vcsExportAct.setStatusTip(QCoreApplication.translate(
95 "VcsProjectHelper",
96 'Export a project from the repository'
97 ))
98 self.vcsExportAct.setWhatsThis(QCoreApplication.translate(
99 "VcsProjectHelper",
100 """<b>Export from repository</b>"""
101 """<p>This exports a project from the repository.</p>"""
102 ))
103 self.vcsExportAct.triggered.connect(self._vcsExport)
104 self.actions.append(self.vcsExportAct)
105
106 self.vcsAddAct = EricAction(
107 QCoreApplication.translate(
108 "VcsProjectHelper", 'Add to repository'),
109 UI.PixmapCache.getIcon("vcsCommit"),
110 QCoreApplication.translate(
111 "VcsProjectHelper", '&Add to repository...'),
112 0, 0, self, 'vcs_add')
113 self.vcsAddAct.setStatusTip(QCoreApplication.translate(
114 "VcsProjectHelper",
115 'Add the local project to the VCS repository'
116 ))
117 self.vcsAddAct.setWhatsThis(QCoreApplication.translate(
118 "VcsProjectHelper",
119 """<b>Add to repository</b>"""
120 """<p>This adds (imports) the local project to the VCS"""
121 """ repository.</p>"""
122 ))
123 self.vcsAddAct.triggered.connect(self._vcsImport)
124 self.actions.append(self.vcsAddAct)
125
126 def initMenu(self, menu):
127 """
128 Public method to generate the VCS menu.
129
130 @param menu reference to the menu to be populated (QMenu)
131 """
132 menu.clear()
133
134 menu.addAction(self.vcsNewAct)
135 menu.addAction(self.vcsExportAct)
136 menu.addSeparator()
137 menu.addAction(self.vcsAddAct)
138 menu.addSeparator()
139
140 def initToolbar(self, ui, toolbarManager):
141 """
142 Public slot to initialize the VCS toolbar.
143
144 @param ui reference to the main window (UserInterface)
145 @param toolbarManager reference to a toolbar manager object
146 (EricToolBarManager)
147 @return the toolbar generated (QToolBar)
148 """
149 return None # __IGNORE_WARNING_M831__
150
151 def initBasicToolbar(self, ui, toolbarManager):
152 """
153 Public slot to initialize the basic VCS toolbar.
154
155 @param ui reference to the main window (UserInterface)
156 @param toolbarManager reference to a toolbar manager object
157 (EricToolBarManager)
158 @return the toolbar generated (QToolBar)
159 """
160 tb = QToolBar(QCoreApplication.translate("VcsProjectHelper", "VCS"),
161 ui)
162 tb.setIconSize(UI.Config.ToolBarIconSize)
163 tb.setObjectName("VersionControlToolbar")
164 tb.setToolTip(QCoreApplication.translate("VcsProjectHelper", 'VCS'))
165
166 tb.addAction(self.vcsNewAct)
167 tb.addAction(self.vcsExportAct)
168 tb.addSeparator()
169 tb.addAction(self.vcsAddAct)
170
171 toolbarManager.addToolBar(tb, tb.windowTitle())
172
173 return tb
174
175 def showMenu(self):
176 """
177 Public slot called before the vcs menu is shown.
178 """
179 if self.vcsAddAct:
180 self.vcsAddAct.setEnabled(self.project and self.project.isOpen())
181
182 @pyqtSlot()
183 def _vcsCheckout(self, export=False):
184 """
185 Protected slot used to create a local project from the repository.
186
187 @param export flag indicating whether an export or a checkout
188 should be performed
189 """
190 if not self.project or not self.project.checkDirty():
191 return
192
193 vcsSystemsDict = ericApp().getObject(
194 "PluginManager").getPluginDisplayStrings("version_control")
195 if not vcsSystemsDict:
196 # no version control system found
197 return
198
199 vcsSystemsDisplay = []
200 keys = sorted(vcsSystemsDict.keys())
201 for key in keys:
202 vcsSystemsDisplay.append(vcsSystemsDict[key])
203 vcsSelected, ok = QInputDialog.getItem(
204 None,
205 QCoreApplication.translate("VcsProjectHelper", "New Project"),
206 QCoreApplication.translate(
207 "VcsProjectHelper",
208 "Select version control system for the project"),
209 vcsSystemsDisplay,
210 0, False)
211 if not ok:
212 return
213
214 selectedVcsSystem = None
215 for vcsSystem, vcsSystemDisplay in list(vcsSystemsDict.items()):
216 if vcsSystemDisplay == vcsSelected:
217 selectedVcsSystem = vcsSystem
218 break
219
220 if not self.project.closeProject():
221 return
222
223 vcs = self.project.initVCS(selectedVcsSystem)
224 if vcs is not None:
225 vcsdlg = vcs.vcsNewProjectOptionsDialog()
226 if vcsdlg.exec() == QDialog.DialogCode.Accepted:
227 projectdir, vcsDataDict = vcsdlg.getData()
228 # edit VCS command options
229 if vcs.vcsSupportCommandOptions():
230 vcores = EricMessageBox.yesNo(
231 self.parent(),
232 QCoreApplication.translate(
233 "VcsProjectHelper", "New Project"),
234 QCoreApplication.translate(
235 "VcsProjectHelper",
236 """Would you like to edit the VCS command"""
237 """ options?"""))
238 else:
239 vcores = False
240 if vcores:
241 from .CommandOptionsDialog import VcsCommandOptionsDialog
242 codlg = VcsCommandOptionsDialog(vcs)
243 if codlg.exec() == QDialog.DialogCode.Accepted:
244 vcs.vcsSetOptions(codlg.getOptions())
245
246 # create the project directory if it doesn't exist already
247 if not os.path.isdir(projectdir):
248 try:
249 os.makedirs(projectdir)
250 except OSError:
251 EricMessageBox.critical(
252 self.parent(),
253 QCoreApplication.translate(
254 "VcsProjectHelper",
255 "Create project directory"),
256 QCoreApplication.translate(
257 "VcsProjectHelper",
258 "<p>The project directory <b>{0}</b> could not"
259 " be created.</p>").format(projectdir))
260 self.project.resetVCS()
261 return
262
263 # create the project from the VCS
264 vcs.vcsSetDataFromDict(vcsDataDict)
265 if export:
266 ok = vcs.vcsExport(vcsDataDict, projectdir)
267 else:
268 ok = vcs.vcsCheckout(vcsDataDict, projectdir, False)
269 if ok:
270 projectdir = os.path.normpath(projectdir)
271 dpath = pathlib.Path(projectdir)
272 # look for JSON style project file first
273 plist = list(dpath.glob("*.epj"))
274 if not plist:
275 # look for XML style project file second
276 plist = list(dpath.glob("*.e4p"))
277 if plist:
278 if len(plist) == 1:
279 self.project.openProject(str(plist[0].resolve()))
280 else:
281 pfilenamelist = [p.name for p in plist]
282 pfilename, ok = QInputDialog.getItem(
283 None,
284 QCoreApplication.translate(
285 "VcsProjectHelper",
286 "New project from repository"),
287 QCoreApplication.translate(
288 "VcsProjectHelper",
289 "Select a project file to open."),
290 pfilenamelist, 0, False)
291 if ok:
292 self.project.openProject(
293 str(dpath / pfilename))
294 if export:
295 self.project.pdata["VCS"] = 'None'
296 self.project.vcs = self.project.initVCS()
297 self.project.setDirty(True)
298 self.project.saveProject()
299 else:
300 res = EricMessageBox.yesNo(
301 self.parent(),
302 QCoreApplication.translate(
303 "VcsProjectHelper",
304 "New project from repository"),
305 QCoreApplication.translate(
306 "VcsProjectHelper",
307 "The project retrieved from the repository"
308 " does not contain an eric project file"
309 " (*.epj). Create it?"),
310 yesDefault=True)
311 if res:
312 self.project.ppath = projectdir
313 self.project.opened = True
314
315 from Project.PropertiesDialog import (
316 PropertiesDialog
317 )
318 dlg = PropertiesDialog(self.project, False)
319 if dlg.exec() == QDialog.DialogCode.Accepted:
320 dlg.storeData()
321 self.project.initFileTypes()
322 self.project.pdata["VCS"] = selectedVcsSystem
323 self.project.setDirty(True)
324 if self.project.pdata["MAINSCRIPT"]:
325 ms = os.path.join(
326 self.project.ppath,
327 self.project.pdata["MAINSCRIPT"])
328 if os.path.exists(ms):
329 self.project.appendFile(ms)
330 else:
331 ms = ""
332 self.project.newProjectAddFiles(ms)
333 self.project.createProjectManagementDir()
334 self.project.saveProject()
335 self.project.openProject(self.project.pfile)
336 if not export:
337 res = EricMessageBox.yesNo(
338 self.parent(),
339 QCoreApplication.translate(
340 "VcsProjectHelper",
341 "New project from repository"),
342 QCoreApplication.translate(
343 "VcsProjectHelper",
344 "Shall the project file be added"
345 " to the repository?"),
346 yesDefault=True)
347 if res:
348 self.project.vcs.vcsAdd(
349 self.project.pfile)
350 else:
351 EricMessageBox.critical(
352 self.parent(),
353 QCoreApplication.translate(
354 "VcsProjectHelper", "New project from repository"),
355 QCoreApplication.translate(
356 "VcsProjectHelper",
357 """The project could not be retrieved from"""
358 """ the repository."""))
359 self.project.resetVCS()
360
361 def _vcsExport(self):
362 """
363 Protected slot used to export a project from the repository.
364 """
365 self._vcsCheckout(True)
366
367 def _vcsImport(self):
368 """
369 Protected slot used to import the local project into the repository.
370
371 <b>NOTE</b>:
372 This does not necessarily make the local project a vcs controlled
373 project. You may have to checkout the project from the repository
374 in order to accomplish that.
375 """
376 def revertChanges():
377 """
378 Local function to revert the changes made to the project object.
379 """
380 self.project.pdata["VCS"] = pdata_vcs
381 self.project.pdata["VCSOPTIONS"] = copy.deepcopy(pdata_vcsoptions)
382 self.project.pdata["VCSOTHERDATA"] = copy.deepcopy(pdata_vcsother)
383 self.project.vcs = vcs
384 self.project.vcsProjectHelper = vcsHelper
385 self.project.vcsBasicHelper = vcs is None
386 self.initMenu(self.project.vcsMenu)
387 self.project.setDirty(True)
388 self.project.saveProject()
389
390 pdata_vcs = self.project.pdata["VCS"]
391 pdata_vcsoptions = copy.deepcopy(self.project.pdata["VCSOPTIONS"])
392 pdata_vcsother = copy.deepcopy(self.project.pdata["VCSOTHERDATA"])
393 vcs = self.project.vcs
394 vcsHelper = self.project.vcsProjectHelper
395 vcsSystemsDict = ericApp().getObject(
396 "PluginManager").getPluginDisplayStrings("version_control")
397 if not vcsSystemsDict:
398 # no version control system found
399 return
400
401 vcsSystemsDisplay = []
402 keys = sorted(vcsSystemsDict.keys())
403 for key in keys:
404 vcsSystemsDisplay.append(vcsSystemsDict[key])
405 vcsSelected, ok = QInputDialog.getItem(
406 None,
407 QCoreApplication.translate("VcsProjectHelper", "Import Project"),
408 QCoreApplication.translate(
409 "VcsProjectHelper",
410 "Select version control system for the project"),
411 vcsSystemsDisplay,
412 0, False)
413 if not ok:
414 return
415
416 selectedVcsSystem = None
417 for vcsSystem, vcsSystemDisplay in list(vcsSystemsDict.items()):
418 if vcsSystemDisplay == vcsSelected:
419 selectedVcsSystem = vcsSystem
420 break
421
422 if selectedVcsSystem is not None:
423 self.project.pdata["VCS"] = selectedVcsSystem
424 self.project.vcs = self.project.initVCS(selectedVcsSystem)
425 if self.project.vcs is not None:
426 vcsdlg = self.project.vcs.vcsOptionsDialog(
427 self.project, self.project.name, 1)
428 if vcsdlg.exec() == QDialog.DialogCode.Accepted:
429 vcsDataDict = vcsdlg.getData()
430 # edit VCS command options
431 if self.project.vcs.vcsSupportCommandOptions():
432 vcores = EricMessageBox.yesNo(
433 self.parent(),
434 QCoreApplication.translate(
435 "VcsProjectHelper", "Import Project"),
436 QCoreApplication.translate(
437 "VcsProjectHelper",
438 """Would you like to edit the VCS command"""
439 """ options?"""))
440 else:
441 vcores = False
442 if vcores:
443 from .CommandOptionsDialog import (
444 VcsCommandOptionsDialog
445 )
446 codlg = VcsCommandOptionsDialog(self.project.vcs)
447 if codlg.exec() == QDialog.DialogCode.Accepted:
448 self.project.vcs.vcsSetOptions(codlg.getOptions())
449 self.project.setDirty(True)
450 self.project.vcs.vcsSetDataFromDict(vcsDataDict)
451 self.project.saveProject()
452 isVcsControlled = self.project.vcs.vcsImport(
453 vcsDataDict, self.project.ppath)[0]
454 if isVcsControlled:
455 # reopen the project
456 self.project.openProject(self.project.pfile)
457 else:
458 # revert the changes to the local project
459 # because the project dir is not a VCS directory
460 revertChanges()
461 else:
462 # revert the changes because user cancelled
463 revertChanges()
464
465 def _vcsUpdate(self):
466 """
467 Protected slot used to update the local project from the repository.
468 """
469 if self.vcs is None:
470 # just in case
471 return
472
473 shouldReopen = self.vcs.vcsUpdate(self.project.ppath)
474 if shouldReopen:
475 res = EricMessageBox.yesNo(
476 self.parent(),
477 QCoreApplication.translate("VcsProjectHelper", "Update"),
478 QCoreApplication.translate(
479 "VcsProjectHelper",
480 """The project should be reread. Do this now?"""),
481 yesDefault=True)
482 if res:
483 self.project.reopenProject()
484
485 def _vcsCommit(self):
486 """
487 Protected slot used to commit changes to the local project to the
488 repository.
489 """
490 if self.vcs is None:
491 # just in case
492 return
493
494 if Preferences.getVCS("AutoSaveProject"):
495 self.project.saveProject()
496 if Preferences.getVCS("AutoSaveFiles"):
497 self.project.saveAllScripts()
498 self.vcs.vcsCommit(self.project.ppath, '')
499
500 def _vcsRemove(self):
501 """
502 Protected slot used to remove the local project from the repository.
503
504 Depending on the parameters set in the vcs object the project
505 may be removed from the local disk as well.
506 """
507 if self.vcs is None:
508 # just in case
509 return
510
511 res = EricMessageBox.yesNo(
512 self.parent(),
513 QCoreApplication.translate(
514 "VcsProjectHelper",
515 "Remove project from repository"),
516 QCoreApplication.translate(
517 "VcsProjectHelper",
518 "Dou you really want to remove this project from"
519 " the repository (and disk)?"))
520 if res:
521 self.vcs.vcsRemove(self.project.ppath, True)
522 self._vcsCommit()
523 if not os.path.exists(self.project.pfile):
524 ppath = self.project.ppath
525 self.setDirty(False)
526 self.project.closeProject()
527 shutil.rmtree(ppath, True)
528
529 def _vcsCommandOptions(self):
530 """
531 Protected slot to edit the VCS command options.
532 """
533 if self.vcs is None:
534 # just in case
535 return
536
537 if self.vcs.vcsSupportCommandOptions():
538 from .CommandOptionsDialog import VcsCommandOptionsDialog
539 codlg = VcsCommandOptionsDialog(self.vcs)
540 if codlg.exec() == QDialog.DialogCode.Accepted:
541 self.vcs.vcsSetOptions(codlg.getOptions())
542 self.project.setDirty(True)
543
544 def _vcsLogBrowser(self):
545 """
546 Protected slot used to show the log of the local project with a
547 log browser dialog.
548 """
549 if self.vcs is None:
550 # just in case
551 return
552
553 self.vcs.vcsLogBrowser(self.project.ppath)
554
555 def _vcsDiff(self):
556 """
557 Protected slot used to show the difference of the local project to
558 the repository.
559 """
560 if self.vcs is None:
561 # just in case
562 return
563
564 self.vcs.vcsDiff(self.project.ppath)
565
566 def _vcsStatus(self):
567 """
568 Protected slot used to show the status of the local project.
569 """
570 if self.vcs is None:
571 # just in case
572 return
573
574 self.vcs.vcsStatus(self.project.ppath)
575
576 def _vcsTag(self):
577 """
578 Protected slot used to tag the local project in the repository.
579 """
580 if self.vcs is None:
581 # just in case
582 return
583
584 self.vcs.vcsTag(self.project.ppath)
585
586 def _vcsRevert(self):
587 """
588 Protected slot used to revert changes made to the local project.
589 """
590 if self.vcs is None:
591 # just in case
592 return
593
594 self.vcs.vcsRevert(self.project.ppath)
595
596 def _vcsSwitch(self):
597 """
598 Protected slot used to switch the local project to another tag/branch.
599 """
600 if self.vcs is None:
601 # just in case
602 return
603
604 shouldReopen = self.vcs.vcsSwitch(self.project.ppath)
605 if shouldReopen:
606 res = EricMessageBox.yesNo(
607 self.parent(),
608 QCoreApplication.translate("VcsProjectHelper", "Switch"),
609 QCoreApplication.translate(
610 "VcsProjectHelper",
611 """The project should be reread. Do this now?"""),
612 yesDefault=True)
613 if res:
614 self.project.reopenProject()
615
616 def _vcsMerge(self):
617 """
618 Protected slot used to merge changes of a tag/revision into the local
619 project.
620 """
621 if self.vcs is None:
622 # just in case
623 return
624
625 self.vcs.vcsMerge(self.project.ppath)
626
627 def _vcsCleanup(self):
628 """
629 Protected slot used to cleanup the local project.
630 """
631 if self.vcs is None:
632 # just in case
633 return
634
635 self.vcs.vcsCleanup(self.project.ppath)
636
637 def _vcsCommand(self):
638 """
639 Protected slot used to execute an arbitrary vcs command.
640 """
641 if self.vcs is None:
642 # just in case
643 return
644
645 self.vcs.vcsCommandLine(self.project.ppath)
646
647 def _vcsInfoDisplay(self):
648 """
649 Protected slot called to show some vcs information.
650 """
651 if self.vcs is None:
652 # just in case
653 return
654
655 from .RepositoryInfoDialog import VcsRepositoryInfoDialog
656 info = self.vcs.vcsRepositoryInfos(self.project.ppath)
657 dlg = VcsRepositoryInfoDialog(None, info)
658 dlg.exec()

eric ide

mercurial