|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2014 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the version control systems interface to Git. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 try: |
|
12 str = unicode |
|
13 except NameError: |
|
14 pass |
|
15 |
|
16 import os |
|
17 import shutil |
|
18 import re |
|
19 |
|
20 from PyQt5.QtCore import QProcess, pyqtSignal, QFileInfo |
|
21 from PyQt5.QtWidgets import QApplication, QDialog, QInputDialog, QLineEdit |
|
22 |
|
23 from E5Gui.E5Application import e5App |
|
24 from E5Gui import E5MessageBox, E5FileDialog |
|
25 |
|
26 from QScintilla.MiniEditor import MiniEditor |
|
27 |
|
28 from VCS.VersionControl import VersionControl |
|
29 from VCS.RepositoryInfoDialog import VcsRepositoryInfoDialog |
|
30 |
|
31 from .GitDialog import GitDialog |
|
32 |
|
33 import Utilities |
|
34 import Preferences |
|
35 |
|
36 |
|
37 class Git(VersionControl): |
|
38 """ |
|
39 Class implementing the version control systems interface to Git. |
|
40 |
|
41 @signal committed() emitted after the commit action has completed |
|
42 """ |
|
43 committed = pyqtSignal() |
|
44 |
|
45 IgnoreFileName = ".gitignore" |
|
46 |
|
47 def __init__(self, plugin, parent=None, name=None): |
|
48 """ |
|
49 Constructor |
|
50 |
|
51 @param plugin reference to the plugin object |
|
52 @param parent parent widget (QWidget) |
|
53 @param name name of this object (string) |
|
54 """ |
|
55 VersionControl.__init__(self, parent, name) |
|
56 self.defaultOptions = { |
|
57 'global': [''], |
|
58 'commit': [''], |
|
59 'checkout': [''], |
|
60 'update': [''], |
|
61 'add': [''], |
|
62 'remove': [''], |
|
63 'diff': [''], |
|
64 'log': [''], |
|
65 'history': [''], |
|
66 'status': [''], |
|
67 'tag': [''], |
|
68 'export': [''] |
|
69 } |
|
70 |
|
71 self.__plugin = plugin |
|
72 self.__ui = parent |
|
73 |
|
74 self.options = self.defaultOptions |
|
75 |
|
76 self.tagTypeList = [ |
|
77 'tags', |
|
78 'branches', |
|
79 ] |
|
80 |
|
81 self.commandHistory = [] |
|
82 |
|
83 self.adminDir = '.git' |
|
84 |
|
85 self.log = None |
|
86 self.logBrowser = None |
|
87 self.reflogBrowser = None |
|
88 self.diff = None |
|
89 self.sbsDiff = None |
|
90 self.tagbranchList = None |
|
91 self.status = None |
|
92 self.remotesDialog = None |
|
93 self.describeDialog = None |
|
94 self.blame = None |
|
95 self.stashBrowser = None |
|
96 self.repoEditor = None |
|
97 self.userEditor = None |
|
98 self.bisectlogBrowser = None |
|
99 self.bisectReplayEditor = None |
|
100 self.patchStatisticsDialog = None |
|
101 self.submoduleStatusDialog = None |
|
102 |
|
103 self.__lastBundlePath = None |
|
104 self.__lastReplayPath = None |
|
105 |
|
106 self.statusCache = {} |
|
107 |
|
108 self.__commitData = {} |
|
109 self.__commitDialog = None |
|
110 |
|
111 self.__patchCheckData = None |
|
112 |
|
113 def getPlugin(self): |
|
114 """ |
|
115 Public method to get a reference to the plugin object. |
|
116 |
|
117 @return reference to the plugin object (VcsGitPlugin) |
|
118 """ |
|
119 return self.__plugin |
|
120 |
|
121 def vcsShutdown(self): |
|
122 """ |
|
123 Public method used to shutdown the Git interface. |
|
124 """ |
|
125 if self.log is not None: |
|
126 self.log.close() |
|
127 if self.logBrowser is not None: |
|
128 self.logBrowser.close() |
|
129 if self.reflogBrowser is not None: |
|
130 self.reflogBrowser.close() |
|
131 if self.diff is not None: |
|
132 self.diff.close() |
|
133 if self.sbsDiff is not None: |
|
134 self.sbsDiff.close() |
|
135 if self.tagbranchList is not None: |
|
136 self.tagbranchList.close() |
|
137 if self.status is not None: |
|
138 self.status.close() |
|
139 if self.remotesDialog is not None: |
|
140 self.remotesDialog.close() |
|
141 if self.describeDialog is not None: |
|
142 self.describeDialog.close() |
|
143 if self.blame is not None: |
|
144 self.blame.close() |
|
145 if self.stashBrowser is not None: |
|
146 self.stashBrowser.close() |
|
147 if self.bisectlogBrowser is not None: |
|
148 self.bisectlogBrowser.close() |
|
149 if self.bisectReplayEditor is not None: |
|
150 self.bisectReplayEditor.close() |
|
151 if self.repoEditor is not None: |
|
152 self.repoEditor.close() |
|
153 if self.userEditor is not None: |
|
154 self.userEditor.close() |
|
155 if self.patchStatisticsDialog is not None: |
|
156 self.patchStatisticsDialog.close() |
|
157 if self.submoduleStatusDialog is not None: |
|
158 self.submoduleStatusDialog.close() |
|
159 |
|
160 # shut down the project helpers |
|
161 self.__projectHelper.shutdown() |
|
162 |
|
163 def initCommand(self, command): |
|
164 """ |
|
165 Public method to initialize a command arguments list. |
|
166 |
|
167 @param command command name (string) |
|
168 @return list of command options (list of string) |
|
169 """ |
|
170 args = [command] |
|
171 return args |
|
172 |
|
173 def vcsExists(self): |
|
174 """ |
|
175 Public method used to test for the presence of the git executable. |
|
176 |
|
177 @return flag indicating the existance (boolean) and an error message |
|
178 (string) |
|
179 """ |
|
180 self.versionStr = '' |
|
181 errMsg = "" |
|
182 ioEncoding = Preferences.getSystem("IOEncoding") |
|
183 |
|
184 args = self.initCommand("version") |
|
185 process = QProcess() |
|
186 process.start('git', args) |
|
187 procStarted = process.waitForStarted(5000) |
|
188 if procStarted: |
|
189 finished = process.waitForFinished(30000) |
|
190 if finished and process.exitCode() == 0: |
|
191 output = str(process.readAllStandardOutput(), |
|
192 ioEncoding, 'replace') |
|
193 versionLine = output.splitlines()[0] |
|
194 v = list(re.match(r'.*?(\d+)\.(\d+)\.?(\d+)?\.?(\d+)?', |
|
195 versionLine).groups()) |
|
196 for i in range(4): |
|
197 try: |
|
198 v[i] = int(v[i]) |
|
199 except TypeError: |
|
200 v[i] = 0 |
|
201 except IndexError: |
|
202 v.append(0) |
|
203 self.version = tuple(v) |
|
204 self.versionStr = '.'.join([str(v) for v in self.version]) |
|
205 return True, errMsg |
|
206 else: |
|
207 if finished: |
|
208 errMsg = self.tr( |
|
209 "The git process finished with the exit code {0}")\ |
|
210 .format(process.exitCode()) |
|
211 else: |
|
212 errMsg = self.tr( |
|
213 "The git process did not finish within 30s.") |
|
214 else: |
|
215 errMsg = self.tr("Could not start the git executable.") |
|
216 |
|
217 return False, errMsg |
|
218 |
|
219 def vcsInit(self, vcsDir, noDialog=False): |
|
220 """ |
|
221 Public method used to initialize the Git repository. |
|
222 |
|
223 The initialization is done, when a project is converted into a |
|
224 Git controlled project. Therefore we always return TRUE without |
|
225 doing anything. |
|
226 |
|
227 @param vcsDir name of the VCS directory (string) |
|
228 @param noDialog flag indicating quiet operations (boolean) |
|
229 @return always TRUE |
|
230 """ |
|
231 return True |
|
232 |
|
233 def vcsConvertProject(self, vcsDataDict, project): |
|
234 """ |
|
235 Public method to convert an uncontrolled project to a version |
|
236 controlled project. |
|
237 |
|
238 @param vcsDataDict dictionary of data required for the conversion |
|
239 @param project reference to the project object |
|
240 """ |
|
241 success = self.vcsImport(vcsDataDict, project.ppath)[0] |
|
242 if not success: |
|
243 E5MessageBox.critical( |
|
244 self.__ui, |
|
245 self.tr("Create project repository"), |
|
246 self.tr( |
|
247 """The project repository could not be created.""")) |
|
248 else: |
|
249 pfn = project.pfile |
|
250 if not os.path.isfile(pfn): |
|
251 pfn += "z" |
|
252 project.closeProject() |
|
253 project.openProject(pfn) |
|
254 |
|
255 def vcsImport(self, vcsDataDict, projectDir, noDialog=False): |
|
256 """ |
|
257 Public method used to import the project into the Git repository. |
|
258 |
|
259 @param vcsDataDict dictionary of data required for the import |
|
260 @param projectDir project directory (string) |
|
261 @param noDialog flag indicating quiet operations |
|
262 @return flag indicating an execution without errors (boolean) |
|
263 and a flag indicating the version controll status (boolean) |
|
264 """ |
|
265 msg = vcsDataDict["message"] |
|
266 if not msg: |
|
267 msg = '***' |
|
268 |
|
269 args = self.initCommand("init") |
|
270 args.append(projectDir) |
|
271 dia = GitDialog(self.tr('Creating Git repository'), self) |
|
272 res = dia.startProcess(args) |
|
273 if res: |
|
274 dia.exec_() |
|
275 status = dia.normalExit() |
|
276 |
|
277 if status: |
|
278 ignoreName = os.path.join(projectDir, Git.IgnoreFileName) |
|
279 if not os.path.exists(ignoreName): |
|
280 status = self.gitCreateIgnoreFile(projectDir) |
|
281 |
|
282 if status: |
|
283 args = self.initCommand("add") |
|
284 args.append("-v") |
|
285 args.append(".") |
|
286 dia = GitDialog( |
|
287 self.tr('Adding files to Git repository'), |
|
288 self) |
|
289 res = dia.startProcess(args, projectDir) |
|
290 if res: |
|
291 dia.exec_() |
|
292 status = dia.normalExit() |
|
293 |
|
294 if status: |
|
295 args = self.initCommand("commit") |
|
296 args.append('--message={0}'.format(msg)) |
|
297 dia = GitDialog( |
|
298 self.tr('Initial commit to Git repository'), |
|
299 self) |
|
300 res = dia.startProcess(args, projectDir) |
|
301 if res: |
|
302 dia.exec_() |
|
303 status = dia.normalExit() |
|
304 |
|
305 return status, False |
|
306 |
|
307 def vcsCheckout(self, vcsDataDict, projectDir, noDialog=False): |
|
308 """ |
|
309 Public method used to check the project out of a Git repository |
|
310 (clone). |
|
311 |
|
312 @param vcsDataDict dictionary of data required for the checkout |
|
313 @param projectDir project directory to create (string) |
|
314 @param noDialog flag indicating quiet operations |
|
315 @return flag indicating an execution without errors (boolean) |
|
316 """ |
|
317 noDialog = False |
|
318 vcsUrl = self.gitNormalizeURL(vcsDataDict["url"]) |
|
319 |
|
320 args = self.initCommand("clone") |
|
321 args.append(vcsUrl) |
|
322 args.append(projectDir) |
|
323 |
|
324 if noDialog: |
|
325 return self.startSynchronizedProcess(QProcess(), 'git', args) |
|
326 else: |
|
327 dia = GitDialog( |
|
328 self.tr('Cloning project from a Git repository'), |
|
329 self) |
|
330 res = dia.startProcess(args) |
|
331 if res: |
|
332 dia.exec_() |
|
333 return dia.normalExit() |
|
334 |
|
335 def vcsExport(self, vcsDataDict, projectDir): |
|
336 """ |
|
337 Public method used to export a directory from the Git repository. |
|
338 |
|
339 @param vcsDataDict dictionary of data required for the checkout |
|
340 @param projectDir project directory to create (string) |
|
341 @return flag indicating an execution without errors (boolean) |
|
342 """ |
|
343 status = self.vcsCheckout(vcsDataDict, projectDir) |
|
344 shutil.rmtree(os.path.join(projectDir, self.adminDir), True) |
|
345 if os.path.exists(os.path.join(projectDir, Git.IgnoreFileName)): |
|
346 os.remove(os.path.join(projectDir, Git.IgnoreFileName)) |
|
347 return status |
|
348 |
|
349 def vcsCommit(self, name, message="", noDialog=False, commitAll=True, |
|
350 amend=False): |
|
351 """ |
|
352 Public method used to make the change of a file/directory permanent |
|
353 in the Git repository. |
|
354 |
|
355 @param name file/directory name to be committed (string or list of |
|
356 strings) |
|
357 @param message message for this operation (string) |
|
358 @param noDialog flag indicating quiet operations (boolean) |
|
359 @param commitAll flag indicating to commit all local changes (boolean) |
|
360 @param amend flag indicating to amend the HEAD commit (boolean) |
|
361 """ |
|
362 if not noDialog: |
|
363 # call CommitDialog and get message from there |
|
364 if self.__commitDialog is None: |
|
365 from .GitCommitDialog import GitCommitDialog |
|
366 self.__commitDialog = GitCommitDialog(self, message, amend, |
|
367 commitAll, self.__ui) |
|
368 self.__commitDialog.accepted.connect(self.__vcsCommit_Step2) |
|
369 self.__commitDialog.show() |
|
370 self.__commitDialog.raise_() |
|
371 self.__commitDialog.activateWindow() |
|
372 |
|
373 self.__commitData["name"] = name |
|
374 self.__commitData["msg"] = message |
|
375 self.__commitData["noDialog"] = noDialog |
|
376 self.__commitData["all"] = commitAll |
|
377 |
|
378 if noDialog: |
|
379 self.__vcsCommit_Step2() |
|
380 |
|
381 def __vcsCommit_Step2(self): |
|
382 """ |
|
383 Private slot performing the second step of the commit action. |
|
384 """ |
|
385 name = self.__commitData["name"] |
|
386 msg = self.__commitData["msg"] |
|
387 noDialog = self.__commitData["noDialog"] |
|
388 commitAll = self.__commitData["all"] |
|
389 |
|
390 if not noDialog: |
|
391 # check, if there are unsaved changes, that should be committed |
|
392 if isinstance(name, list): |
|
393 nameList = name |
|
394 else: |
|
395 nameList = [name] |
|
396 ok = True |
|
397 for nam in nameList: |
|
398 # check for commit of the project |
|
399 if os.path.isdir(nam): |
|
400 project = e5App().getObject("Project") |
|
401 if nam == project.getProjectPath(): |
|
402 ok &= \ |
|
403 project.checkAllScriptsDirty( |
|
404 reportSyntaxErrors=True) and \ |
|
405 project.checkDirty() |
|
406 continue |
|
407 elif os.path.isfile(nam): |
|
408 editor = \ |
|
409 e5App().getObject("ViewManager").getOpenEditor(nam) |
|
410 if editor: |
|
411 ok &= editor.checkDirty() |
|
412 if not ok: |
|
413 break |
|
414 |
|
415 if not ok: |
|
416 res = E5MessageBox.yesNo( |
|
417 self.__ui, |
|
418 self.tr("Commit Changes"), |
|
419 self.tr( |
|
420 """The commit affects files, that have unsaved""" |
|
421 """ changes. Shall the commit be continued?"""), |
|
422 icon=E5MessageBox.Warning) |
|
423 if not res: |
|
424 return |
|
425 |
|
426 if self.__commitDialog is not None: |
|
427 msg = self.__commitDialog.logMessage() |
|
428 amend = self.__commitDialog.amend() |
|
429 resetAuthor = self.__commitDialog.resetAuthor() |
|
430 commitAll = not self.__commitDialog.stagedOnly() |
|
431 self.__commitDialog.deleteLater() |
|
432 self.__commitDialog = None |
|
433 else: |
|
434 amend = False |
|
435 resetAuthor = False |
|
436 |
|
437 if not msg and not amend: |
|
438 msg = '***' |
|
439 |
|
440 args = self.initCommand("commit") |
|
441 if amend: |
|
442 args.append("--amend") |
|
443 if resetAuthor: |
|
444 args.append("--reset-author") |
|
445 if msg: |
|
446 args.append("--message") |
|
447 args.append(msg) |
|
448 |
|
449 if isinstance(name, list): |
|
450 dname, fnames = self.splitPathList(name) |
|
451 else: |
|
452 dname, fname = self.splitPath(name) |
|
453 |
|
454 # find the root of the repo |
|
455 repodir = dname |
|
456 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
457 repodir = os.path.dirname(repodir) |
|
458 if os.path.splitdrive(repodir)[1] == os.sep: |
|
459 return |
|
460 |
|
461 if isinstance(name, list): |
|
462 args.append("--") |
|
463 self.addArguments(args, fnames) |
|
464 else: |
|
465 if dname != repodir or fname != ".": |
|
466 args.append("--") |
|
467 args.append(fname) |
|
468 else: |
|
469 if commitAll: |
|
470 args.append("--all") |
|
471 |
|
472 if noDialog: |
|
473 self.startSynchronizedProcess(QProcess(), "git", args, dname) |
|
474 else: |
|
475 dia = GitDialog( |
|
476 self.tr('Committing changes to Git repository'), |
|
477 self) |
|
478 res = dia.startProcess(args, dname) |
|
479 if res: |
|
480 dia.exec_() |
|
481 self.committed.emit() |
|
482 self.checkVCSStatus() |
|
483 |
|
484 def vcsUpdate(self, name, noDialog=False, revision=None): |
|
485 """ |
|
486 Public method used to update a file/directory with the Git |
|
487 repository. |
|
488 |
|
489 @param name file/directory name to be updated (string or list of |
|
490 strings) |
|
491 @param noDialog flag indicating quiet operations (boolean) |
|
492 @keyparam revision revision to update to (string) |
|
493 @return flag indicating, that the update contained an add |
|
494 or delete (boolean) |
|
495 """ |
|
496 args = self.initCommand("checkout") |
|
497 if revision: |
|
498 res = E5MessageBox.yesNo( |
|
499 None, |
|
500 self.tr("Switch"), |
|
501 self.tr("""<p>Do you really want to switch to <b>{0}</b>?""" |
|
502 """</p>""").format(revision), |
|
503 yesDefault=True) |
|
504 if not res: |
|
505 return False |
|
506 args.append(revision) |
|
507 |
|
508 if isinstance(name, list): |
|
509 args.append("--") |
|
510 dname, fnames = self.splitPathList(name) |
|
511 self.addArguments(args, fnames) |
|
512 else: |
|
513 dname, fname = self.splitPath(name) |
|
514 if fname != ".": |
|
515 args.append("--") |
|
516 args.append(fname) |
|
517 |
|
518 # find the root of the repo |
|
519 repodir = dname |
|
520 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
521 repodir = os.path.dirname(repodir) |
|
522 if os.path.splitdrive(repodir)[1] == os.sep: |
|
523 return False |
|
524 |
|
525 if noDialog: |
|
526 self.startSynchronizedProcess(QProcess(), 'git', args, repodir) |
|
527 res = False |
|
528 else: |
|
529 dia = GitDialog(self.tr( |
|
530 'Synchronizing with the Git repository'), |
|
531 self) |
|
532 res = dia.startProcess(args, repodir) |
|
533 if res: |
|
534 dia.exec_() |
|
535 res = dia.hasAddOrDelete() |
|
536 self.checkVCSStatus() |
|
537 return res |
|
538 |
|
539 def vcsAdd(self, name, isDir=False, noDialog=False): |
|
540 """ |
|
541 Public method used to add a file/directory to the Git repository. |
|
542 |
|
543 @param name file/directory name to be added (string) |
|
544 @param isDir flag indicating name is a directory (boolean) |
|
545 @param noDialog flag indicating quiet operations |
|
546 """ |
|
547 args = self.initCommand("add") |
|
548 args.append("-v") |
|
549 |
|
550 if isinstance(name, list): |
|
551 if isDir: |
|
552 dname, fname = os.path.split(name[0]) |
|
553 else: |
|
554 dname, fnames = self.splitPathList(name) |
|
555 else: |
|
556 if isDir: |
|
557 dname, fname = os.path.split(name) |
|
558 else: |
|
559 dname, fname = self.splitPath(name) |
|
560 |
|
561 # find the root of the repo |
|
562 repodir = dname |
|
563 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
564 repodir = os.path.dirname(repodir) |
|
565 if os.path.splitdrive(repodir)[1] == os.sep: |
|
566 return |
|
567 |
|
568 if isinstance(name, list): |
|
569 self.addArguments(args, name) |
|
570 else: |
|
571 args.append(name) |
|
572 |
|
573 if noDialog: |
|
574 self.startSynchronizedProcess(QProcess(), 'git', args, repodir) |
|
575 else: |
|
576 dia = GitDialog( |
|
577 self.tr( |
|
578 'Adding files/directories to the Git repository'), |
|
579 self) |
|
580 res = dia.startProcess(args, repodir) |
|
581 if res: |
|
582 dia.exec_() |
|
583 |
|
584 def vcsAddBinary(self, name, isDir=False): |
|
585 """ |
|
586 Public method used to add a file/directory in binary mode to the |
|
587 Git repository. |
|
588 |
|
589 @param name file/directory name to be added (string) |
|
590 @param isDir flag indicating name is a directory (boolean) |
|
591 """ |
|
592 self.vcsAdd(name, isDir) |
|
593 |
|
594 def vcsAddTree(self, path): |
|
595 """ |
|
596 Public method to add a directory tree rooted at path to the Git |
|
597 repository. |
|
598 |
|
599 @param path root directory of the tree to be added (string or list of |
|
600 strings)) |
|
601 """ |
|
602 self.vcsAdd(path, isDir=False) |
|
603 |
|
604 def vcsRemove(self, name, project=False, noDialog=False, stageOnly=False): |
|
605 """ |
|
606 Public method used to remove a file/directory from the Git |
|
607 repository. |
|
608 |
|
609 The default operation is to remove the local copy as well. |
|
610 |
|
611 @param name file/directory name to be removed (string or list of |
|
612 strings)) |
|
613 @param project flag indicating deletion of a project tree (boolean) |
|
614 (not needed) |
|
615 @param noDialog flag indicating quiet operations (boolean) |
|
616 @param stageOnly flag indicating to just remove the file from the |
|
617 staging area (boolean) |
|
618 @return flag indicating successful operation (boolean) |
|
619 """ |
|
620 args = self.initCommand("rm") |
|
621 if noDialog and '--force' not in args: |
|
622 args.append('--force') |
|
623 if stageOnly: |
|
624 args.append('--cached') |
|
625 |
|
626 if isinstance(name, list): |
|
627 if os.path.isdir(name[0]): |
|
628 args.append("-r") |
|
629 dname, fnames = self.splitPathList(name) |
|
630 args.append("--") |
|
631 self.addArguments(args, name) |
|
632 else: |
|
633 if os.path.isdir(name): |
|
634 args.append("-r") |
|
635 dname, fname = self.splitPath(name) |
|
636 args.append("--") |
|
637 args.append(name) |
|
638 |
|
639 # find the root of the repo |
|
640 repodir = dname |
|
641 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
642 repodir = os.path.dirname(repodir) |
|
643 if os.path.splitdrive(repodir)[1] == os.sep: |
|
644 return False |
|
645 |
|
646 if noDialog: |
|
647 res = self.startSynchronizedProcess( |
|
648 QProcess(), 'git', args, repodir) |
|
649 else: |
|
650 dia = GitDialog( |
|
651 self.tr( |
|
652 'Removing files/directories from the Git' |
|
653 ' repository'), |
|
654 self) |
|
655 res = dia.startProcess(args, repodir) |
|
656 if res: |
|
657 dia.exec_() |
|
658 res = dia.normalExitWithoutErrors() |
|
659 |
|
660 return res |
|
661 |
|
662 def vcsMove(self, name, project, target=None, noDialog=False): |
|
663 """ |
|
664 Public method used to move a file/directory. |
|
665 |
|
666 @param name file/directory name to be moved (string) |
|
667 @param project reference to the project object |
|
668 @param target new name of the file/directory (string) |
|
669 @param noDialog flag indicating quiet operations |
|
670 @return flag indicating successful operation (boolean) |
|
671 """ |
|
672 isDir = os.path.isdir(name) |
|
673 |
|
674 res = False |
|
675 if noDialog: |
|
676 if target is None: |
|
677 return False |
|
678 force = True |
|
679 accepted = True |
|
680 else: |
|
681 from .GitCopyDialog import GitCopyDialog |
|
682 dlg = GitCopyDialog(name, None, True) |
|
683 accepted = dlg.exec_() == QDialog.Accepted |
|
684 if accepted: |
|
685 target, force = dlg.getData() |
|
686 |
|
687 if accepted: |
|
688 args = self.initCommand("mv") |
|
689 args.append("-v") |
|
690 if force: |
|
691 args.append('--force') |
|
692 args.append(name) |
|
693 args.append(target) |
|
694 |
|
695 dname, fname = self.splitPath(name) |
|
696 # find the root of the repo |
|
697 repodir = dname |
|
698 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
699 repodir = os.path.dirname(repodir) |
|
700 if os.path.splitdrive(repodir)[1] == os.sep: |
|
701 return False |
|
702 |
|
703 if noDialog: |
|
704 res = self.startSynchronizedProcess( |
|
705 QProcess(), 'git', args, repodir) |
|
706 else: |
|
707 dia = GitDialog(self.tr('Renaming {0}').format(name), self) |
|
708 res = dia.startProcess(args, repodir) |
|
709 if res: |
|
710 dia.exec_() |
|
711 res = dia.normalExit() |
|
712 if res: |
|
713 if target.startswith(project.getProjectPath()): |
|
714 if isDir: |
|
715 project.moveDirectory(name, target) |
|
716 else: |
|
717 project.renameFileInPdata(name, target) |
|
718 else: |
|
719 if isDir: |
|
720 project.removeDirectory(name) |
|
721 else: |
|
722 project.removeFile(name) |
|
723 return res |
|
724 |
|
725 def vcsLogBrowser(self, name, isFile=False): |
|
726 """ |
|
727 Public method used to browse the log of a file/directory from the |
|
728 Git repository. |
|
729 |
|
730 @param name file/directory name to show the log of (string) |
|
731 @keyparam isFile flag indicating log for a file is to be shown |
|
732 (boolean) |
|
733 """ |
|
734 if self.logBrowser is None: |
|
735 from .GitLogBrowserDialog import GitLogBrowserDialog |
|
736 self.logBrowser = GitLogBrowserDialog(self) |
|
737 self.logBrowser.show() |
|
738 self.logBrowser.raise_() |
|
739 self.logBrowser.start(name, isFile=isFile) |
|
740 |
|
741 def gitReflogBrowser(self, projectDir): |
|
742 """ |
|
743 Public method used to browse the reflog of the project. |
|
744 |
|
745 @param projectDir name of the project directory (string) |
|
746 """ |
|
747 if self.reflogBrowser is None: |
|
748 from .GitReflogBrowserDialog import GitReflogBrowserDialog |
|
749 self.reflogBrowser = GitReflogBrowserDialog(self) |
|
750 self.reflogBrowser.show() |
|
751 self.reflogBrowser.raise_() |
|
752 self.reflogBrowser.start(projectDir) |
|
753 |
|
754 def vcsDiff(self, name): |
|
755 """ |
|
756 Public method used to view the difference of a file/directory to the |
|
757 Git repository. |
|
758 |
|
759 If name is a directory and is the project directory, all project files |
|
760 are saved first. If name is a file (or list of files), which is/are |
|
761 being edited and has unsaved modification, they can be saved or the |
|
762 operation may be aborted. |
|
763 |
|
764 @param name file/directory name to be diffed (string) |
|
765 """ |
|
766 if isinstance(name, list): |
|
767 names = name[:] |
|
768 else: |
|
769 names = [name] |
|
770 for nam in names: |
|
771 if os.path.isfile(nam): |
|
772 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
|
773 if editor and not editor.checkDirty(): |
|
774 return |
|
775 else: |
|
776 project = e5App().getObject("Project") |
|
777 if nam == project.ppath and not project.saveAllScripts(): |
|
778 return |
|
779 if self.diff is None: |
|
780 from .GitDiffDialog import GitDiffDialog |
|
781 self.diff = GitDiffDialog(self) |
|
782 self.diff.show() |
|
783 self.diff.raise_() |
|
784 QApplication.processEvents() |
|
785 self.diff.start(name, diffMode="work2stage2repo", refreshable=True) |
|
786 |
|
787 def vcsStatus(self, name): |
|
788 """ |
|
789 Public method used to view the status of files/directories in the |
|
790 Git repository. |
|
791 |
|
792 @param name file/directory name(s) to show the status of |
|
793 (string or list of strings) |
|
794 """ |
|
795 if self.status is None: |
|
796 from .GitStatusDialog import GitStatusDialog |
|
797 self.status = GitStatusDialog(self) |
|
798 self.status.show() |
|
799 self.status.raise_() |
|
800 self.status.start(name) |
|
801 |
|
802 def gitUnstage(self, name): |
|
803 """ |
|
804 Public method used to unstage a file/directory. |
|
805 |
|
806 @param name file/directory name to be reverted (string) |
|
807 @return flag indicating, that the update contained an add |
|
808 or delete (boolean) |
|
809 """ |
|
810 if isinstance(name, list): |
|
811 dname, fnames = self.splitPathList(name) |
|
812 else: |
|
813 dname, fname = self.splitPath(name) |
|
814 |
|
815 # find the root of the repo |
|
816 repodir = dname |
|
817 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
818 repodir = os.path.dirname(repodir) |
|
819 if os.path.splitdrive(repodir)[1] == os.sep: |
|
820 return False |
|
821 |
|
822 args = self.initCommand("reset") |
|
823 args.append("HEAD") |
|
824 args.append("--") |
|
825 if isinstance(name, list): |
|
826 self.addArguments(args, name) |
|
827 else: |
|
828 args.append(name) |
|
829 |
|
830 dia = GitDialog( |
|
831 self.tr('Unstage files/directories'), |
|
832 self) |
|
833 res = dia.startProcess(args, repodir) |
|
834 if res: |
|
835 dia.exec_() |
|
836 res = dia.hasAddOrDelete() |
|
837 self.checkVCSStatus() |
|
838 |
|
839 return res |
|
840 |
|
841 def gitRevert(self, name): |
|
842 """ |
|
843 Public method used to revert changes made to a file/directory. |
|
844 |
|
845 @param name file/directory name to be reverted (string) |
|
846 @return flag indicating, that the update contained an add |
|
847 or delete (boolean) |
|
848 """ |
|
849 args = self.initCommand("checkout") |
|
850 args.append("--") |
|
851 if isinstance(name, list): |
|
852 dname, fnames = self.splitPathList(name) |
|
853 self.addArguments(args, name) |
|
854 names = name[:] |
|
855 else: |
|
856 dname, fname = self.splitPath(name) |
|
857 args.append(name) |
|
858 names = [name] |
|
859 |
|
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 os.path.splitdrive(repodir)[1] == os.sep: |
|
865 return False |
|
866 |
|
867 project = e5App().getObject("Project") |
|
868 names = [project.getRelativePath(nam) for nam in names] |
|
869 if names[0]: |
|
870 from UI.DeleteFilesConfirmationDialog import \ |
|
871 DeleteFilesConfirmationDialog |
|
872 dlg = DeleteFilesConfirmationDialog( |
|
873 self.parent(), |
|
874 self.tr("Revert changes"), |
|
875 self.tr( |
|
876 "Do you really want to revert all changes to these files" |
|
877 " or directories?"), |
|
878 names) |
|
879 yes = dlg.exec_() == QDialog.Accepted |
|
880 else: |
|
881 yes = E5MessageBox.yesNo( |
|
882 None, |
|
883 self.tr("Revert changes"), |
|
884 self.tr("""Do you really want to revert all changes of""" |
|
885 """ the project?""")) |
|
886 if yes: |
|
887 dia = GitDialog(self.tr('Reverting changes'), self) |
|
888 res = dia.startProcess(args, repodir) |
|
889 if res: |
|
890 dia.exec_() |
|
891 res = dia.hasAddOrDelete() |
|
892 self.checkVCSStatus() |
|
893 else: |
|
894 res = False |
|
895 |
|
896 return res |
|
897 |
|
898 def vcsMerge(self, name): |
|
899 """ |
|
900 Public method used to merge a URL/revision into the local project. |
|
901 |
|
902 @param name file/directory name to be merged (string) |
|
903 """ |
|
904 dname, fname = self.splitPath(name) |
|
905 |
|
906 # find the root of the repo |
|
907 repodir = dname |
|
908 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
909 repodir = os.path.dirname(repodir) |
|
910 if os.path.splitdrive(repodir)[1] == os.sep: |
|
911 return |
|
912 |
|
913 from .GitMergeDialog import GitMergeDialog |
|
914 dlg = GitMergeDialog(self.gitGetTagsList(repodir), |
|
915 self.gitGetBranchesList(repodir, withMaster=True), |
|
916 self.gitGetCurrentBranch(repodir), |
|
917 self.gitGetBranchesList(repodir, remotes=True)) |
|
918 if dlg.exec_() == QDialog.Accepted: |
|
919 commit, doCommit, commitMessage, addLog, diffStat = \ |
|
920 dlg.getParameters() |
|
921 args = self.initCommand("merge") |
|
922 if doCommit: |
|
923 args.append("--commit") |
|
924 args.append("-m") |
|
925 args.append(commitMessage) |
|
926 if addLog: |
|
927 args.append("--log") |
|
928 else: |
|
929 args.append("--no-log") |
|
930 else: |
|
931 args.append("--no-commit") |
|
932 if diffStat: |
|
933 args.append("--stat") |
|
934 else: |
|
935 args.append("--no-stat") |
|
936 if commit: |
|
937 args.append(commit) |
|
938 |
|
939 dia = GitDialog(self.tr('Merging').format(name), self) |
|
940 res = dia.startProcess(args, repodir) |
|
941 if res: |
|
942 dia.exec_() |
|
943 self.checkVCSStatus() |
|
944 |
|
945 def vcsSwitch(self, name): |
|
946 """ |
|
947 Public method used to switch a working directory to a different |
|
948 revision. |
|
949 |
|
950 @param name directory name to be switched (string) |
|
951 @return flag indicating, that the switch contained an add |
|
952 or delete (boolean) |
|
953 """ |
|
954 dname, fname = self.splitPath(name) |
|
955 |
|
956 # find the root of the repo |
|
957 repodir = dname |
|
958 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
959 repodir = os.path.dirname(repodir) |
|
960 if os.path.splitdrive(repodir)[1] == os.sep: |
|
961 return False |
|
962 |
|
963 from .GitRevisionSelectionDialog import GitRevisionSelectionDialog |
|
964 dlg = GitRevisionSelectionDialog( |
|
965 self.gitGetTagsList(repodir), |
|
966 self.gitGetBranchesList(repodir), |
|
967 trackingBranchesList=self.gitGetBranchesList( |
|
968 repodir, remotes=True), |
|
969 noneLabel=self.tr("Master branch head")) |
|
970 if dlg.exec_() == QDialog.Accepted: |
|
971 rev = dlg.getRevision() |
|
972 return self.vcsUpdate(name, revision=rev) |
|
973 |
|
974 return False |
|
975 |
|
976 def vcsRegisteredState(self, name): |
|
977 """ |
|
978 Public method used to get the registered state of a file in the vcs. |
|
979 |
|
980 @param name filename to check (string) |
|
981 @return a combination of canBeCommited and canBeAdded |
|
982 """ |
|
983 if name.endswith(os.sep): |
|
984 name = name[:-1] |
|
985 name = os.path.normcase(name) |
|
986 dname, fname = self.splitPath(name) |
|
987 |
|
988 if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)): |
|
989 return self.canBeCommitted |
|
990 |
|
991 if name in self.statusCache: |
|
992 return self.statusCache[name] |
|
993 |
|
994 # find the root of the repo |
|
995 repodir = dname |
|
996 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
997 repodir = os.path.dirname(repodir) |
|
998 if os.path.splitdrive(repodir)[1] == os.sep: |
|
999 return 0 |
|
1000 |
|
1001 args = self.initCommand("status") |
|
1002 args.append('--porcelain') |
|
1003 args.append(name) |
|
1004 |
|
1005 ioEncoding = Preferences.getSystem("IOEncoding") |
|
1006 output = "" |
|
1007 process = QProcess() |
|
1008 process.setWorkingDirectory(repodir) |
|
1009 process.start('git', args) |
|
1010 procStarted = process.waitForStarted(5000) |
|
1011 if procStarted: |
|
1012 finished = process.waitForFinished(30000) |
|
1013 if finished and process.exitCode() == 0: |
|
1014 output = str(process.readAllStandardOutput(), |
|
1015 ioEncoding, 'replace') |
|
1016 |
|
1017 if output: |
|
1018 for line in output.splitlines(): |
|
1019 if line and line[0] in " MADRCU!?": |
|
1020 flag = line[1] |
|
1021 path = line[3:].split(" -> ")[-1] |
|
1022 absname = os.path.join(repodir, os.path.normcase(path)) |
|
1023 if absname.endswith(("/", "\\")): |
|
1024 absname = absname[:-1] |
|
1025 if flag not in "?!": |
|
1026 if fname == '.': |
|
1027 if absname.startswith(dname + os.path.sep): |
|
1028 return self.canBeCommitted |
|
1029 if absname == dname: |
|
1030 return self.canBeCommitted |
|
1031 else: |
|
1032 if absname == name: |
|
1033 return self.canBeCommitted |
|
1034 else: |
|
1035 return self.canBeCommitted |
|
1036 |
|
1037 return self.canBeAdded |
|
1038 |
|
1039 def vcsAllRegisteredStates(self, names, dname, shortcut=True): |
|
1040 """ |
|
1041 Public method used to get the registered states of a number of files |
|
1042 in the vcs. |
|
1043 |
|
1044 <b>Note:</b> If a shortcut is to be taken, the code will only check, |
|
1045 if the named directory has been scanned already. If so, it is assumed, |
|
1046 that the states for all files have been populated by the previous run. |
|
1047 |
|
1048 @param names dictionary with all filenames to be checked as keys |
|
1049 @param dname directory to check in (string) |
|
1050 @param shortcut flag indicating a shortcut should be taken (boolean) |
|
1051 @return the received dictionary completed with a combination of |
|
1052 canBeCommited and canBeAdded or None in order to signal an error |
|
1053 """ |
|
1054 if dname.endswith(os.sep): |
|
1055 dname = dname[:-1] |
|
1056 dname = os.path.normcase(dname) |
|
1057 |
|
1058 # revert the logic because git status doesn't show unchanged files |
|
1059 for name in names: |
|
1060 names[name] = self.canBeCommitted |
|
1061 |
|
1062 found = False |
|
1063 for name in self.statusCache: |
|
1064 if name in names: |
|
1065 found = True |
|
1066 names[name] = self.statusCache[name] |
|
1067 |
|
1068 if not found: |
|
1069 # find the root of the repo |
|
1070 repodir = dname |
|
1071 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1072 repodir = os.path.dirname(repodir) |
|
1073 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1074 return names |
|
1075 |
|
1076 args = self.initCommand("status") |
|
1077 args.append('--porcelain') |
|
1078 |
|
1079 ioEncoding = Preferences.getSystem("IOEncoding") |
|
1080 output = "" |
|
1081 process = QProcess() |
|
1082 process.setWorkingDirectory(dname) |
|
1083 process.start('git', args) |
|
1084 procStarted = process.waitForStarted(5000) |
|
1085 if procStarted: |
|
1086 finished = process.waitForFinished(30000) |
|
1087 if finished and process.exitCode() == 0: |
|
1088 output = str(process.readAllStandardOutput(), |
|
1089 ioEncoding, 'replace') |
|
1090 |
|
1091 if output: |
|
1092 for line in output.splitlines(): |
|
1093 if line and line[0] in " MADRCU!?": |
|
1094 flag = line[1] |
|
1095 path = line[3:].split(" -> ")[-1] |
|
1096 name = os.path.normcase(os.path.join(repodir, path)) |
|
1097 dirName = os.path.dirname(name) |
|
1098 if name.startswith(dname): |
|
1099 if flag in "?!": |
|
1100 isDir = name.endswith(("/", "\\")) |
|
1101 if isDir: |
|
1102 name = name[:-1] |
|
1103 if name in names: |
|
1104 names[name] = self.canBeAdded |
|
1105 if isDir: |
|
1106 # it's a directory |
|
1107 for nname in names: |
|
1108 if nname.startswith(name): |
|
1109 names[nname] = self.canBeAdded |
|
1110 if flag not in "?!": |
|
1111 self.statusCache[name] = self.canBeCommitted |
|
1112 self.statusCache[dirName] = self.canBeCommitted |
|
1113 else: |
|
1114 self.statusCache[name] = self.canBeAdded |
|
1115 if dirName not in self.statusCache: |
|
1116 self.statusCache[dirName] = self.canBeAdded |
|
1117 |
|
1118 return names |
|
1119 |
|
1120 def clearStatusCache(self): |
|
1121 """ |
|
1122 Public method to clear the status cache. |
|
1123 """ |
|
1124 self.statusCache = {} |
|
1125 |
|
1126 def vcsName(self): |
|
1127 """ |
|
1128 Public method returning the name of the vcs. |
|
1129 |
|
1130 @return always 'Git' (string) |
|
1131 """ |
|
1132 return "Git" |
|
1133 |
|
1134 def vcsInitConfig(self, project): |
|
1135 """ |
|
1136 Public method to initialize the VCS configuration. |
|
1137 |
|
1138 This method ensures, that an ignore file exists. |
|
1139 |
|
1140 @param project reference to the project (Project) |
|
1141 """ |
|
1142 ppath = project.getProjectPath() |
|
1143 if ppath: |
|
1144 ignoreName = os.path.join(ppath, Git.IgnoreFileName) |
|
1145 if not os.path.exists(ignoreName): |
|
1146 self.gitCreateIgnoreFile(project.getProjectPath(), |
|
1147 autoAdd=True) |
|
1148 |
|
1149 def vcsCleanup(self, name): |
|
1150 """ |
|
1151 Public method used to cleanup the working directory. |
|
1152 |
|
1153 @param name directory name to be cleaned up (string) |
|
1154 """ |
|
1155 patterns = self.__plugin.getPreferences("CleanupPatterns").split() |
|
1156 |
|
1157 entries = [] |
|
1158 for pat in patterns: |
|
1159 entries.extend(Utilities.direntries(name, True, pat)) |
|
1160 |
|
1161 for entry in entries: |
|
1162 try: |
|
1163 os.remove(entry) |
|
1164 except OSError: |
|
1165 pass |
|
1166 |
|
1167 def vcsCommandLine(self, name): |
|
1168 """ |
|
1169 Public method used to execute arbitrary Git commands. |
|
1170 |
|
1171 @param name directory name of the working directory (string) |
|
1172 """ |
|
1173 from .GitCommandDialog import GitCommandDialog |
|
1174 dlg = GitCommandDialog(self.commandHistory, name) |
|
1175 if dlg.exec_() == QDialog.Accepted: |
|
1176 command = dlg.getData() |
|
1177 commandList = Utilities.parseOptionString(command) |
|
1178 |
|
1179 # This moves any previous occurrence of these arguments to the head |
|
1180 # of the list. |
|
1181 if command in self.commandHistory: |
|
1182 self.commandHistory.remove(command) |
|
1183 self.commandHistory.insert(0, command) |
|
1184 |
|
1185 args = [] |
|
1186 self.addArguments(args, commandList) |
|
1187 |
|
1188 # find the root of the repo |
|
1189 repodir = name |
|
1190 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1191 repodir = os.path.dirname(repodir) |
|
1192 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1193 return |
|
1194 |
|
1195 dia = GitDialog(self.tr('Git Command'), self) |
|
1196 res = dia.startProcess(args, repodir) |
|
1197 if res: |
|
1198 dia.exec_() |
|
1199 |
|
1200 def vcsOptionsDialog(self, project, archive, editable=False, parent=None): |
|
1201 """ |
|
1202 Public method to get a dialog to enter repository info. |
|
1203 |
|
1204 @param project reference to the project object |
|
1205 @param archive name of the project in the repository (string) |
|
1206 @param editable flag indicating that the project name is editable |
|
1207 (boolean) |
|
1208 @param parent parent widget (QWidget) |
|
1209 @return reference to the instantiated options dialog (GitOptionsDialog) |
|
1210 """ |
|
1211 from .GitOptionsDialog import GitOptionsDialog |
|
1212 return GitOptionsDialog(self, project, parent) |
|
1213 |
|
1214 def vcsNewProjectOptionsDialog(self, parent=None): |
|
1215 """ |
|
1216 Public method to get a dialog to enter repository info for getting a |
|
1217 new project. |
|
1218 |
|
1219 @param parent parent widget (QWidget) |
|
1220 @return reference to the instantiated options dialog |
|
1221 (GitNewProjectOptionsDialog) |
|
1222 """ |
|
1223 from .GitNewProjectOptionsDialog import GitNewProjectOptionsDialog |
|
1224 return GitNewProjectOptionsDialog(self, parent) |
|
1225 |
|
1226 def vcsRepositoryInfos(self, ppath): |
|
1227 """ |
|
1228 Public method to retrieve information about the repository. |
|
1229 |
|
1230 @param ppath local path to get the repository infos (string) |
|
1231 @return string with ready formated info for display (string) |
|
1232 """ |
|
1233 formatTemplate = ( |
|
1234 'format:' |
|
1235 '%h%n' |
|
1236 '%p%n' |
|
1237 '%an%n' |
|
1238 '%ae%n' |
|
1239 '%ai%n' |
|
1240 '%cn%n' |
|
1241 '%ce%n' |
|
1242 '%ci%n' |
|
1243 '%d%n' |
|
1244 '%s') |
|
1245 |
|
1246 args = self.initCommand("show") |
|
1247 args.append("--abbrev-commit") |
|
1248 args.append("--abbrev={0}".format( |
|
1249 self.__plugin.getPreferences("CommitIdLength"))) |
|
1250 args.append("--format={0}".format(formatTemplate)) |
|
1251 args.append("--no-patch") |
|
1252 args.append("HEAD") |
|
1253 |
|
1254 output = "" |
|
1255 process = QProcess() |
|
1256 process.setWorkingDirectory(ppath) |
|
1257 process.start('git', args) |
|
1258 procStarted = process.waitForStarted(5000) |
|
1259 if procStarted: |
|
1260 finished = process.waitForFinished(30000) |
|
1261 if finished and process.exitCode() == 0: |
|
1262 output = str(process.readAllStandardOutput(), |
|
1263 Preferences.getSystem("IOEncoding"), |
|
1264 'replace') |
|
1265 |
|
1266 if output: |
|
1267 info = [] |
|
1268 (commit, parents, author, authorMail, authorDate, |
|
1269 committer, committerMail, committerDate, refs, subject) = \ |
|
1270 output.splitlines() |
|
1271 tags = [] |
|
1272 branches = [] |
|
1273 for name in refs.strip()[1:-1].split(","): |
|
1274 name = name.strip() |
|
1275 if name: |
|
1276 if name.startswith("tag: "): |
|
1277 tags.append(name.split()[1]) |
|
1278 elif name != "HEAD": |
|
1279 branches.append(name) |
|
1280 |
|
1281 info.append(self.tr( |
|
1282 "<tr><td><b>Commit</b></td><td>{0}</td></tr>").format( |
|
1283 commit)) |
|
1284 if parents: |
|
1285 info.append(self.tr( |
|
1286 "<tr><td><b>Parents</b></td><td>{0}</td></tr>").format( |
|
1287 '<br/>'.join(parents.strip().split()))) |
|
1288 if tags: |
|
1289 info.append(self.tr( |
|
1290 "<tr><td><b>Tags</b></td><td>{0}</td></tr>").format( |
|
1291 '<br/>'.join(tags))) |
|
1292 if branches: |
|
1293 info.append(self.tr( |
|
1294 "<tr><td><b>Branches</b></td><td>{0}</td></tr>").format( |
|
1295 '<br/>'.join(branches))) |
|
1296 info.append(self.tr( |
|
1297 "<tr><td><b>Author</b></td><td>{0} <{1}></td></tr>") |
|
1298 .format(author, authorMail)) |
|
1299 info.append(self.tr( |
|
1300 "<tr><td><b>Date</b></td><td>{0}</td></tr>").format( |
|
1301 authorDate.rsplit(":", 1)[0])) |
|
1302 info.append(self.tr( |
|
1303 "<tr><td><b>Committer</b></td><td>{0} <{1}></td></tr>") |
|
1304 .format(committer, committerMail)) |
|
1305 info.append(self.tr( |
|
1306 "<tr><td><b>Committed Date</b></td><td>{0}</td></tr>").format( |
|
1307 committerDate.rsplit(":", 1)[0])) |
|
1308 info.append(self.tr( |
|
1309 "<tr><td><b>Subject</b></td><td>{0}</td></tr>").format( |
|
1310 subject)) |
|
1311 infoStr = "\n".join(info) |
|
1312 else: |
|
1313 infoStr = "" |
|
1314 |
|
1315 return self.tr( |
|
1316 """<h3>Repository information</h3>\n""" |
|
1317 """<p><table>\n""" |
|
1318 """<tr><td><b>Git V.</b></td><td>{0}</td></tr>\n""" |
|
1319 """<tr></tr>\n""" |
|
1320 """{1}""" |
|
1321 """</table></p>\n""" |
|
1322 ).format(self.versionStr, infoStr) |
|
1323 |
|
1324 def vcsSupportCommandOptions(self): |
|
1325 """ |
|
1326 Public method to signal the support of user settable command options. |
|
1327 |
|
1328 @return flag indicating the support of user settable command options |
|
1329 (boolean) |
|
1330 """ |
|
1331 return False |
|
1332 |
|
1333 ########################################################################### |
|
1334 ## Git specific methods are below. |
|
1335 ########################################################################### |
|
1336 |
|
1337 def gitNormalizeURL(self, url): |
|
1338 """ |
|
1339 Public method to normalize a url for Git. |
|
1340 |
|
1341 @param url url string (string) |
|
1342 @return properly normalized url for git (string) |
|
1343 """ |
|
1344 url = url.replace('\\', '/') |
|
1345 if url.endswith('/'): |
|
1346 url = url[:-1] |
|
1347 urll = url.split('//') |
|
1348 if len(urll) > 1: |
|
1349 url = "{0}//{1}".format(urll[0], '/'.join(urll[1:])) |
|
1350 |
|
1351 return url |
|
1352 |
|
1353 def gitCreateIgnoreFile(self, name, autoAdd=False): |
|
1354 """ |
|
1355 Public method to create the ignore file. |
|
1356 |
|
1357 @param name directory name to create the ignore file in (string) |
|
1358 @param autoAdd flag indicating to add it automatically (boolean) |
|
1359 @return flag indicating success |
|
1360 """ |
|
1361 status = False |
|
1362 ignorePatterns = [ |
|
1363 ".eric6project/", |
|
1364 ".ropeproject/", |
|
1365 ".directory/", |
|
1366 "*.pyc", |
|
1367 "*.pyo", |
|
1368 "*.orig", |
|
1369 "*.bak", |
|
1370 "*.rej", |
|
1371 "*~", |
|
1372 "cur/", |
|
1373 "tmp/", |
|
1374 "__pycache__/", |
|
1375 "*.DS_Store", |
|
1376 ] |
|
1377 |
|
1378 ignoreName = os.path.join(name, Git.IgnoreFileName) |
|
1379 if os.path.exists(ignoreName): |
|
1380 res = E5MessageBox.yesNo( |
|
1381 self.__ui, |
|
1382 self.tr("Create {0} file").format(ignoreName), |
|
1383 self.tr("""<p>The file <b>{0}</b> exists already.""" |
|
1384 """ Overwrite it?</p>""").format(ignoreName), |
|
1385 icon=E5MessageBox.Warning) |
|
1386 else: |
|
1387 res = True |
|
1388 if res: |
|
1389 try: |
|
1390 # create a .gitignore file |
|
1391 ignore = open(ignoreName, "w") |
|
1392 ignore.write("\n".join(ignorePatterns)) |
|
1393 ignore.write("\n") |
|
1394 ignore.close() |
|
1395 status = True |
|
1396 except IOError: |
|
1397 status = False |
|
1398 |
|
1399 if status and autoAdd: |
|
1400 self.vcsAdd(ignoreName, noDialog=True) |
|
1401 project = e5App().getObject("Project") |
|
1402 project.appendFile(ignoreName) |
|
1403 |
|
1404 return status |
|
1405 |
|
1406 def gitCopy(self, name, project): |
|
1407 """ |
|
1408 Public method used to copy a file/directory. |
|
1409 |
|
1410 @param name file/directory name to be copied (string) |
|
1411 @param project reference to the project object |
|
1412 @return flag indicating successful operation (boolean) |
|
1413 """ |
|
1414 from .GitCopyDialog import GitCopyDialog |
|
1415 dlg = GitCopyDialog(name) |
|
1416 if dlg.exec_() == QDialog.Accepted: |
|
1417 target, force = dlg.getData() |
|
1418 |
|
1419 # step 1: copy the file/directory: |
|
1420 if os.path.isdir(name): |
|
1421 try: |
|
1422 shutil.copytree(name, target) |
|
1423 except (OSError, shutil.Error) as why: |
|
1424 E5MessageBox.critical( |
|
1425 self, |
|
1426 self.tr("Git Copy"), |
|
1427 self.tr("""<p>Copying the directory <b>{0}</b>""" |
|
1428 """ failed.</p><p>Reason: {1}</p>""").format( |
|
1429 name, str(why))) |
|
1430 return False |
|
1431 self.vcsAdd(target, isDir=True) |
|
1432 if target.startswith(project.getProjectPath()): |
|
1433 project.copyDirectory(name, target) |
|
1434 |
|
1435 else: |
|
1436 try: |
|
1437 shutil.copy2(name, target) |
|
1438 except (OSError, shutil.Error) as why: |
|
1439 E5MessageBox.critical( |
|
1440 self, |
|
1441 self.tr("Git Copy"), |
|
1442 self.tr("""<p>Copying the file <b>{0}</b>""" |
|
1443 """ failed.</p><p>Reason: {1}</p>""").format( |
|
1444 name, str(why))) |
|
1445 return False |
|
1446 self.vcsAdd(target, isDir=False) |
|
1447 if target.startswith(project.getProjectPath()): |
|
1448 project.appendFile(target) |
|
1449 self.checkVCSStatus() |
|
1450 return True |
|
1451 |
|
1452 def gitBlame(self, name): |
|
1453 """ |
|
1454 Public method to show the output of the git blame command. |
|
1455 |
|
1456 @param name file name to show the annotations for (string) |
|
1457 """ |
|
1458 if self.blame is None: |
|
1459 from .GitBlameDialog import GitBlameDialog |
|
1460 self.blame = GitBlameDialog(self) |
|
1461 self.blame.show() |
|
1462 self.blame.raise_() |
|
1463 self.blame.start(name) |
|
1464 |
|
1465 def gitExtendedDiff(self, name): |
|
1466 """ |
|
1467 Public method used to view the difference of a file/directory to the |
|
1468 Git repository. |
|
1469 |
|
1470 If name is a directory and is the project directory, all project files |
|
1471 are saved first. If name is a file (or list of files), which is/are |
|
1472 being edited and has unsaved modification, they can be saved or the |
|
1473 operation may be aborted. |
|
1474 |
|
1475 This method gives the chance to enter the revisions to be compared. |
|
1476 |
|
1477 @param name file/directory name to be diffed (string) |
|
1478 """ |
|
1479 if isinstance(name, list): |
|
1480 dname, fnames = self.splitPathList(name) |
|
1481 names = name[:] |
|
1482 else: |
|
1483 dname, fname = self.splitPath(name) |
|
1484 names = [name] |
|
1485 for nam in names: |
|
1486 if os.path.isfile(nam): |
|
1487 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
|
1488 if editor and not editor.checkDirty(): |
|
1489 return |
|
1490 else: |
|
1491 project = e5App().getObject("Project") |
|
1492 if nam == project.ppath and not project.saveAllScripts(): |
|
1493 return |
|
1494 |
|
1495 # find the root of the repo |
|
1496 repodir = dname |
|
1497 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1498 repodir = os.path.dirname(repodir) |
|
1499 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1500 return |
|
1501 |
|
1502 from .GitRevisionsSelectionDialog import GitRevisionsSelectionDialog |
|
1503 dlg = GitRevisionsSelectionDialog(self.gitGetTagsList(repodir), |
|
1504 self.gitGetBranchesList(repodir)) |
|
1505 if dlg.exec_() == QDialog.Accepted: |
|
1506 revisions = dlg.getRevisions() |
|
1507 if self.diff is None: |
|
1508 from .GitDiffDialog import GitDiffDialog |
|
1509 self.diff = GitDiffDialog(self) |
|
1510 self.diff.show() |
|
1511 self.diff.raise_() |
|
1512 self.diff.start(name, revisions) |
|
1513 |
|
1514 def __gitGetFileForRevision(self, name, rev=""): |
|
1515 """ |
|
1516 Private method to get a file for a specific revision from the |
|
1517 repository. |
|
1518 |
|
1519 @param name file name to get from the repository (string) |
|
1520 @keyparam rev revision to retrieve (string) |
|
1521 @return contents of the file (string) and an error message (string) |
|
1522 """ |
|
1523 # find the root of the repo |
|
1524 repodir = self.splitPath(name)[0] |
|
1525 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1526 repodir = os.path.dirname(repodir) |
|
1527 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1528 return False |
|
1529 |
|
1530 args = self.initCommand("cat-file") |
|
1531 args.append("blob") |
|
1532 args.append("{0}:{1}".format(rev, name.replace(repodir + os.sep, ""))) |
|
1533 |
|
1534 output = "" |
|
1535 error = "" |
|
1536 |
|
1537 process = QProcess() |
|
1538 process.setWorkingDirectory(repodir) |
|
1539 process.start('git', args) |
|
1540 procStarted = process.waitForStarted(5000) |
|
1541 if procStarted: |
|
1542 finished = process.waitForFinished(30000) |
|
1543 if finished: |
|
1544 if process.exitCode() == 0: |
|
1545 output = str(process.readAllStandardOutput(), |
|
1546 Preferences.getSystem("IOEncoding"), |
|
1547 'replace') |
|
1548 else: |
|
1549 error = str(process.readAllStandardError(), |
|
1550 Preferences.getSystem("IOEncoding"), |
|
1551 'replace') |
|
1552 else: |
|
1553 error = self.tr( |
|
1554 "The git process did not finish within 30s.") |
|
1555 else: |
|
1556 error = self.tr( |
|
1557 'The process {0} could not be started. ' |
|
1558 'Ensure, that it is in the search path.').format('git') |
|
1559 |
|
1560 # return file contents with 'universal newlines' |
|
1561 return output.replace('\r\n', '\n').replace('\r', '\n'), error |
|
1562 |
|
1563 def gitSbsDiff(self, name, extended=False, revisions=None): |
|
1564 """ |
|
1565 Public method used to view the difference of a file to the Git |
|
1566 repository side-by-side. |
|
1567 |
|
1568 @param name file name to be diffed (string) |
|
1569 @keyparam extended flag indicating the extended variant (boolean) |
|
1570 @keyparam revisions tuple of two revisions (tuple of strings) |
|
1571 @exception ValueError raised to indicate an invalid name parameter |
|
1572 """ |
|
1573 if isinstance(name, list): |
|
1574 raise ValueError("Wrong parameter type") |
|
1575 |
|
1576 if extended: |
|
1577 # find the root of the repo |
|
1578 repodir = self.splitPath(name)[0] |
|
1579 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1580 repodir = os.path.dirname(repodir) |
|
1581 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1582 return |
|
1583 |
|
1584 from .GitRevisionsSelectionDialog import \ |
|
1585 GitRevisionsSelectionDialog |
|
1586 dlg = GitRevisionsSelectionDialog(self.gitGetTagsList(repodir), |
|
1587 self.gitGetBranchesList(repodir)) |
|
1588 if dlg.exec_() == QDialog.Accepted: |
|
1589 rev1, rev2 = dlg.getRevisions() |
|
1590 elif revisions: |
|
1591 rev1, rev2 = revisions[0], revisions[1] |
|
1592 else: |
|
1593 rev1, rev2 = "", "" |
|
1594 |
|
1595 output1, error = self.__gitGetFileForRevision(name, rev=rev1) |
|
1596 if error: |
|
1597 E5MessageBox.critical( |
|
1598 self.__ui, |
|
1599 self.tr("Git Side-by-Side Difference"), |
|
1600 error) |
|
1601 return |
|
1602 name1 = "{0} (rev. {1})".format(name, rev1 and rev1 or "Stage") |
|
1603 |
|
1604 if rev2: |
|
1605 if rev2 == "Stage": |
|
1606 rev2 = "" |
|
1607 output2, error = self.__gitGetFileForRevision(name, rev=rev2) |
|
1608 if error: |
|
1609 E5MessageBox.critical( |
|
1610 self.__ui, |
|
1611 self.tr("Git Side-by-Side Difference"), |
|
1612 error) |
|
1613 return |
|
1614 name2 = "{0} (rev. {1})".format(name, rev2) |
|
1615 else: |
|
1616 try: |
|
1617 f1 = open(name, "r", encoding="utf-8") |
|
1618 output2 = f1.read() |
|
1619 f1.close() |
|
1620 name2 = "{0} (Work)".format(name) |
|
1621 except IOError: |
|
1622 E5MessageBox.critical( |
|
1623 self.__ui, |
|
1624 self.tr("Git Side-by-Side Difference"), |
|
1625 self.tr( |
|
1626 """<p>The file <b>{0}</b> could not be read.</p>""") |
|
1627 .format(name)) |
|
1628 return |
|
1629 |
|
1630 if self.sbsDiff is None: |
|
1631 from UI.CompareDialog import CompareDialog |
|
1632 self.sbsDiff = CompareDialog() |
|
1633 self.sbsDiff.show() |
|
1634 self.sbsDiff.raise_() |
|
1635 self.sbsDiff.compare(output1, output2, name1, name2) |
|
1636 |
|
1637 def gitFetch(self, name): |
|
1638 """ |
|
1639 Public method to fetch changes from a remote repository. |
|
1640 |
|
1641 @param name directory name (string) |
|
1642 """ |
|
1643 # find the root of the repo |
|
1644 repodir = self.splitPath(name)[0] |
|
1645 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1646 repodir = os.path.dirname(repodir) |
|
1647 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1648 return |
|
1649 |
|
1650 from .GitFetchDialog import GitFetchDialog |
|
1651 dlg = GitFetchDialog(self, repodir) |
|
1652 if dlg.exec_() == QDialog.Accepted: |
|
1653 (remote, url, remoteBranches, localBranch, fetchAll, prune, |
|
1654 includeTags) = dlg.getData() |
|
1655 |
|
1656 args = self.initCommand("fetch") |
|
1657 args.append('--verbose') |
|
1658 if prune: |
|
1659 args.append('--prune') |
|
1660 if includeTags: |
|
1661 args.append("--tags") |
|
1662 if fetchAll: |
|
1663 args.append('--all') |
|
1664 else: |
|
1665 args.append(remote) if remote else args.append(url) |
|
1666 if len(remoteBranches) == 1 and localBranch: |
|
1667 args.append(remoteBranches[0] + ":" + localBranch) |
|
1668 else: |
|
1669 args.extend(remoteBranches) |
|
1670 |
|
1671 dia = GitDialog(self.tr('Fetching from a remote Git repository'), |
|
1672 self) |
|
1673 res = dia.startProcess(args, repodir) |
|
1674 if res: |
|
1675 dia.exec_() |
|
1676 self.checkVCSStatus() |
|
1677 |
|
1678 def gitPull(self, name): |
|
1679 """ |
|
1680 Public method used to pull changes from a remote Git repository. |
|
1681 |
|
1682 @param name directory name of the project to be pulled to (string) |
|
1683 @return flag indicating, that the update contained an add |
|
1684 or delete (boolean) |
|
1685 """ |
|
1686 # find the root of the repo |
|
1687 repodir = self.splitPath(name)[0] |
|
1688 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1689 repodir = os.path.dirname(repodir) |
|
1690 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1691 return False |
|
1692 |
|
1693 from .GitPullDialog import GitPullDialog |
|
1694 dlg = GitPullDialog(self, repodir) |
|
1695 if dlg.exec_() == QDialog.Accepted: |
|
1696 remote, url, branches, pullAll, prune = dlg.getData() |
|
1697 |
|
1698 args = self.initCommand('pull') |
|
1699 args.append('--no-commit') |
|
1700 args.append('--verbose') |
|
1701 if prune: |
|
1702 args.append('--prune') |
|
1703 if pullAll: |
|
1704 args.append('--all') |
|
1705 else: |
|
1706 args.append(remote) if remote else args.append(url) |
|
1707 args.extend(branches) |
|
1708 |
|
1709 dia = GitDialog(self.tr('Pulling from a remote Git repository'), |
|
1710 self) |
|
1711 res = dia.startProcess(args, repodir) |
|
1712 if res: |
|
1713 dia.exec_() |
|
1714 res = dia.hasAddOrDelete() |
|
1715 self.checkVCSStatus() |
|
1716 return res |
|
1717 else: |
|
1718 return False |
|
1719 |
|
1720 def gitPush(self, name): |
|
1721 """ |
|
1722 Public method used to push changes to a remote Git repository. |
|
1723 |
|
1724 @param name directory name of the project to be pushed from (string) |
|
1725 """ |
|
1726 # find the root of the repo |
|
1727 repodir = self.splitPath(name)[0] |
|
1728 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1729 repodir = os.path.dirname(repodir) |
|
1730 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1731 return |
|
1732 |
|
1733 from .GitPushDialog import GitPushDialog |
|
1734 dlg = GitPushDialog(self, repodir) |
|
1735 if dlg.exec_() == QDialog.Accepted: |
|
1736 remote, refspecs, tags, tracking, submodule = dlg.getData() |
|
1737 |
|
1738 args = self.initCommand("push") |
|
1739 args.append('--verbose') |
|
1740 args.append('--porcelain') |
|
1741 if tags: |
|
1742 args.append("--tags") |
|
1743 if tracking: |
|
1744 args.append("--set-upstream") |
|
1745 if submodule: |
|
1746 args.append("--recurse-submodules={0}".format(submodule)) |
|
1747 args.append(remote) |
|
1748 args.extend(refspecs) |
|
1749 |
|
1750 dia = GitDialog( |
|
1751 self.tr('Pushing to a remote Git repository'), self) |
|
1752 res = dia.startProcess(args, repodir) |
|
1753 if res: |
|
1754 dia.exec_() |
|
1755 self.checkVCSStatus() |
|
1756 |
|
1757 def gitCommitMerge(self, name): |
|
1758 """ |
|
1759 Public method to commit a failed merge. |
|
1760 |
|
1761 @param name file/directory name (string) |
|
1762 """ |
|
1763 dname, fname = self.splitPath(name) |
|
1764 |
|
1765 # find the root of the repo |
|
1766 repodir = dname |
|
1767 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1768 repodir = os.path.dirname(repodir) |
|
1769 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1770 return |
|
1771 |
|
1772 import sys |
|
1773 editor = sys.argv[0].replace(".py", "_editor.py") |
|
1774 env = {"GIT_EDITOR": "{0} {1}".format(sys.executable, editor)} |
|
1775 |
|
1776 args = self.initCommand("commit") |
|
1777 |
|
1778 dia = GitDialog(self.tr('Committing failed merge'), self) |
|
1779 res = dia.startProcess(args, repodir, environment=env) |
|
1780 if res: |
|
1781 dia.exec_() |
|
1782 self.committed.emit() |
|
1783 self.checkVCSStatus() |
|
1784 |
|
1785 def gitCancelMerge(self, name): |
|
1786 """ |
|
1787 Public method to cancel an uncommitted or failed merge. |
|
1788 |
|
1789 @param name file/directory name (string) |
|
1790 @return flag indicating, that the cancellation contained an add |
|
1791 or delete (boolean) |
|
1792 """ |
|
1793 dname, fname = self.splitPath(name) |
|
1794 |
|
1795 # find the root of the repo |
|
1796 repodir = dname |
|
1797 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1798 repodir = os.path.dirname(repodir) |
|
1799 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1800 return False |
|
1801 |
|
1802 args = self.initCommand("merge") |
|
1803 args.append("--abort") |
|
1804 |
|
1805 dia = GitDialog( |
|
1806 self.tr('Aborting uncommitted/failed merge'), |
|
1807 self) |
|
1808 res = dia.startProcess(args, repodir, False) |
|
1809 if res: |
|
1810 dia.exec_() |
|
1811 res = dia.hasAddOrDelete() |
|
1812 self.checkVCSStatus() |
|
1813 return res |
|
1814 |
|
1815 def gitApply(self, repodir, patchFile, cached=False, reverse=False, |
|
1816 noDialog=False): |
|
1817 """ |
|
1818 Public method to apply a patch stored in a given file. |
|
1819 |
|
1820 @param repodir directory name of the repository (string) |
|
1821 @param patchFile name of the patch file (string) |
|
1822 @param cached flag indicating to apply the patch to the staging area |
|
1823 (boolean) |
|
1824 @param reverse flag indicating to apply the patch in reverse (boolean) |
|
1825 @param noDialog flag indicating quiet operations (boolean) |
|
1826 """ |
|
1827 args = self.initCommand("apply") |
|
1828 if cached: |
|
1829 args.append("--index") |
|
1830 args.append("--cached") |
|
1831 if reverse: |
|
1832 args.append("--reverse") |
|
1833 args.append(patchFile) |
|
1834 |
|
1835 if noDialog: |
|
1836 self.startSynchronizedProcess(QProcess(), "git", args, repodir) |
|
1837 else: |
|
1838 dia = GitDialog( |
|
1839 self.tr('Applying patch'), |
|
1840 self) |
|
1841 res = dia.startProcess(args, repodir) |
|
1842 if res: |
|
1843 dia.exec_() |
|
1844 |
|
1845 def gitApplyCheckPatches(self, projectDir, check=False): |
|
1846 """ |
|
1847 Public method to apply a list of patch files or check, if they would |
|
1848 apply cleanly. |
|
1849 |
|
1850 @param projectDir directory name of the project (string) |
|
1851 @param check flag indicating to perform a check operation (boolean) |
|
1852 """ |
|
1853 # find the root of the repo |
|
1854 repodir = projectDir |
|
1855 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1856 repodir = os.path.dirname(repodir) |
|
1857 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1858 return |
|
1859 |
|
1860 from .GitPatchFilesDialog import GitPatchFilesDialog |
|
1861 dlg = GitPatchFilesDialog(repodir, self.__patchCheckData) |
|
1862 if dlg.exec_() == QDialog.Accepted: |
|
1863 patchFilesList, stripCount, inaccurateEof, recount = dlg.getData() |
|
1864 if patchFilesList: |
|
1865 args = self.initCommand("apply") |
|
1866 if check: |
|
1867 args.append("--check") |
|
1868 self.__patchCheckData = ( |
|
1869 patchFilesList, stripCount, inaccurateEof, recount) |
|
1870 title = self.tr('Check patch files') |
|
1871 else: |
|
1872 self.__patchCheckData = None |
|
1873 title = self.tr('Apply patch files') |
|
1874 if inaccurateEof: |
|
1875 args.append("--inaccurate-eof") |
|
1876 if recount: |
|
1877 args.append("--recount") |
|
1878 args.append("-p{0}".format(stripCount)) |
|
1879 args.extend(patchFilesList) |
|
1880 |
|
1881 dia = GitDialog( |
|
1882 title, |
|
1883 self) |
|
1884 res = dia.startProcess(args, repodir) |
|
1885 if res: |
|
1886 dia.exec_() |
|
1887 if not check: |
|
1888 self.checkVCSStatus() |
|
1889 |
|
1890 def gitShowPatchesStatistics(self, projectDir): |
|
1891 """ |
|
1892 Public method to show statistics for a set of patch files. |
|
1893 |
|
1894 @param projectDir directory name of the project (string) |
|
1895 """ |
|
1896 if self.patchStatisticsDialog is None: |
|
1897 from .GitPatchStatisticsDialog import GitPatchStatisticsDialog |
|
1898 self.patchStatisticsDialog = GitPatchStatisticsDialog(self) |
|
1899 self.patchStatisticsDialog.show() |
|
1900 self.patchStatisticsDialog.raise_() |
|
1901 self.patchStatisticsDialog.start(projectDir, self.__patchCheckData) |
|
1902 self.__patchCheckData = self.patchStatisticsDialog.getData() |
|
1903 |
|
1904 ########################################################################### |
|
1905 ## Methods for tag handling. |
|
1906 ########################################################################### |
|
1907 |
|
1908 def vcsTag(self, name, revision=None, tagName=None): |
|
1909 """ |
|
1910 Public method used to set/remove a tag in the Git repository. |
|
1911 |
|
1912 @param name file/directory name to determine the repo root from |
|
1913 (string) |
|
1914 @param revision revision to set tag for (string) |
|
1915 @param tagName name of the tag (string) |
|
1916 @return flag indicating a performed tag action (boolean) |
|
1917 """ |
|
1918 dname, fname = self.splitPath(name) |
|
1919 |
|
1920 # find the root of the repo |
|
1921 repodir = dname |
|
1922 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1923 repodir = os.path.dirname(repodir) |
|
1924 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1925 return False |
|
1926 |
|
1927 from .GitTagDialog import GitTagDialog |
|
1928 dlg = GitTagDialog(self.gitGetTagsList(repodir), revision, tagName) |
|
1929 if dlg.exec_() == QDialog.Accepted: |
|
1930 tag, revision, tagOp, tagType, force = dlg.getParameters() |
|
1931 else: |
|
1932 return False |
|
1933 |
|
1934 args = self.initCommand("tag") |
|
1935 if tagOp == GitTagDialog.CreateTag: |
|
1936 msg = "Created tag <{0}>.".format(tag) |
|
1937 if tagType == GitTagDialog.AnnotatedTag: |
|
1938 args.append("--annotate") |
|
1939 args.append("--message={0}".format(msg)) |
|
1940 elif tagType == GitTagDialog.SignedTag: |
|
1941 args.append("--sign") |
|
1942 args.append("--message={0}".format(msg)) |
|
1943 if force: |
|
1944 args.append("--force") |
|
1945 elif tagOp == GitTagDialog.DeleteTag: |
|
1946 args.append("--delete") |
|
1947 elif tagOp == GitTagDialog.VerifyTag: |
|
1948 args.append("--verify") |
|
1949 else: |
|
1950 return False |
|
1951 args.append(tag) |
|
1952 if tagOp == GitTagDialog.CreateTag and revision: |
|
1953 args.append(revision) |
|
1954 |
|
1955 dia = GitDialog(self.tr('Tagging in the Git repository'), |
|
1956 self) |
|
1957 res = dia.startProcess(args, repodir) |
|
1958 if res: |
|
1959 dia.exec_() |
|
1960 |
|
1961 return True |
|
1962 |
|
1963 def gitGetTagsList(self, repodir, withType=False): |
|
1964 """ |
|
1965 Public method to get the list of tags. |
|
1966 |
|
1967 @param repodir directory name of the repository (string) |
|
1968 @param withType flag indicating to get the tag type as well (boolean) |
|
1969 @return list of tags (list of string) or list of tuples of |
|
1970 tag name and flag indicating a local tag (list of tuple of string |
|
1971 and boolean), if withType is True |
|
1972 """ |
|
1973 args = self.initCommand("tag") |
|
1974 args.append('--list') |
|
1975 |
|
1976 output = "" |
|
1977 process = QProcess() |
|
1978 process.setWorkingDirectory(repodir) |
|
1979 process.start('git', args) |
|
1980 procStarted = process.waitForStarted(5000) |
|
1981 if procStarted: |
|
1982 finished = process.waitForFinished(30000) |
|
1983 if finished and process.exitCode() == 0: |
|
1984 output = str(process.readAllStandardOutput(), |
|
1985 Preferences.getSystem("IOEncoding"), |
|
1986 'replace') |
|
1987 |
|
1988 tagsList = [] |
|
1989 if output: |
|
1990 for line in output.splitlines(): |
|
1991 name = line.strip() |
|
1992 tagsList.append(name) |
|
1993 |
|
1994 return tagsList |
|
1995 |
|
1996 def gitListTagBranch(self, path, tags=True, listAll=True, merged=True): |
|
1997 """ |
|
1998 Public method used to list the available tags or branches. |
|
1999 |
|
2000 @param path directory name of the project (string) |
|
2001 @param tags flag indicating listing of branches or tags |
|
2002 (False = branches, True = tags) |
|
2003 @param listAll flag indicating to show all tags or branches (boolean) |
|
2004 @param merged flag indicating to show only merged or non-merged |
|
2005 branches (boolean) |
|
2006 """ |
|
2007 if self.tagbranchList is None: |
|
2008 from .GitTagBranchListDialog import GitTagBranchListDialog |
|
2009 self.tagbranchList = GitTagBranchListDialog(self) |
|
2010 self.tagbranchList.show() |
|
2011 self.tagbranchList.raise_() |
|
2012 if tags: |
|
2013 self.tagbranchList.start(path, tags) |
|
2014 else: |
|
2015 self.tagbranchList.start(path, tags, listAll, merged) |
|
2016 |
|
2017 ########################################################################### |
|
2018 ## Methods for branch handling. |
|
2019 ########################################################################### |
|
2020 |
|
2021 def gitGetBranchesList(self, repodir, withMaster=False, allBranches=False, |
|
2022 remotes=False): |
|
2023 """ |
|
2024 Public method to get the list of branches. |
|
2025 |
|
2026 @param repodir directory name of the repository (string) |
|
2027 @param withMaster flag indicating to get 'master' as well (boolean) |
|
2028 @param allBranches flag indicating to return all branches (boolean) |
|
2029 @param remotes flag indicating to return remote branches only (boolean) |
|
2030 @return list of branches (list of string) |
|
2031 """ |
|
2032 args = self.initCommand("branch") |
|
2033 args.append('--list') |
|
2034 if allBranches: |
|
2035 args.append("--all") |
|
2036 elif remotes: |
|
2037 args.append("--remotes") |
|
2038 |
|
2039 output = "" |
|
2040 process = QProcess() |
|
2041 process.setWorkingDirectory(repodir) |
|
2042 process.start('git', args) |
|
2043 procStarted = process.waitForStarted(5000) |
|
2044 if procStarted: |
|
2045 finished = process.waitForFinished(30000) |
|
2046 if finished and process.exitCode() == 0: |
|
2047 output = str(process.readAllStandardOutput(), |
|
2048 Preferences.getSystem("IOEncoding"), |
|
2049 'replace') |
|
2050 |
|
2051 branchesList = [] |
|
2052 if output: |
|
2053 for line in output.splitlines(): |
|
2054 name = line[2:].strip() |
|
2055 if ( |
|
2056 (name != "master" or withMaster) and |
|
2057 "->" not in name and |
|
2058 not name.startswith("(") and |
|
2059 not name.endswith(")") |
|
2060 ): |
|
2061 branchesList.append(name) |
|
2062 |
|
2063 return branchesList |
|
2064 |
|
2065 def gitGetCurrentBranch(self, repodir): |
|
2066 """ |
|
2067 Public method used to show the current branch of the working directory. |
|
2068 |
|
2069 @param repodir directory name of the repository (string) |
|
2070 @return name of the current branch (string) |
|
2071 """ |
|
2072 args = self.initCommand("branch") |
|
2073 args.append('--list') |
|
2074 |
|
2075 branchName = "" |
|
2076 output = "" |
|
2077 process = QProcess() |
|
2078 process.setWorkingDirectory(repodir) |
|
2079 process.start('git', args) |
|
2080 procStarted = process.waitForStarted(5000) |
|
2081 if procStarted: |
|
2082 finished = process.waitForFinished(30000) |
|
2083 if finished and process.exitCode() == 0: |
|
2084 output = str(process.readAllStandardOutput(), |
|
2085 Preferences.getSystem("IOEncoding"), |
|
2086 'replace') |
|
2087 |
|
2088 if output: |
|
2089 for line in output.splitlines(): |
|
2090 if line.startswith("* "): |
|
2091 branchName = line[2:].strip() |
|
2092 if branchName.startswith("(") and branchName.endswith(")"): |
|
2093 # not a valid branch name, probably detached head |
|
2094 branchName = "" |
|
2095 break |
|
2096 |
|
2097 return branchName |
|
2098 |
|
2099 def gitBranch(self, name, revision=None, branchName=None, branchOp=None): |
|
2100 """ |
|
2101 Public method used to create, delete or move a branch in the Git |
|
2102 repository. |
|
2103 |
|
2104 @param name file/directory name to be branched (string) |
|
2105 @param revision revision to set tag for (string) |
|
2106 @param branchName name of the branch (string) |
|
2107 @param branchOp desired branch operation (integer) |
|
2108 @return flag indicating a performed branch action (boolean) and |
|
2109 a flag indicating, that the branch operation contained an add |
|
2110 or delete (boolean) |
|
2111 """ |
|
2112 dname, fname = self.splitPath(name) |
|
2113 |
|
2114 # find the root of the repo |
|
2115 repodir = dname |
|
2116 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2117 repodir = os.path.dirname(repodir) |
|
2118 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2119 return False, False |
|
2120 |
|
2121 from .GitBranchDialog import GitBranchDialog |
|
2122 dlg = GitBranchDialog( |
|
2123 self.gitGetBranchesList(repodir, allBranches=True), |
|
2124 revision, branchName, branchOp) |
|
2125 if dlg.exec_() == QDialog.Accepted: |
|
2126 branchOp, branch, revision, newBranch, remoteBranch, force = \ |
|
2127 dlg.getParameters() |
|
2128 else: |
|
2129 return False, False |
|
2130 |
|
2131 if branchOp in [GitBranchDialog.CreateBranch, |
|
2132 GitBranchDialog.DeleteBranch, |
|
2133 GitBranchDialog.RenameBranch, |
|
2134 GitBranchDialog.SetTrackingBranch, |
|
2135 GitBranchDialog.UnsetTrackingBranch]: |
|
2136 args = self.initCommand("branch") |
|
2137 if branchOp == GitBranchDialog.CreateBranch: |
|
2138 if force: |
|
2139 args.append("--force") |
|
2140 args.append(branch) |
|
2141 if revision: |
|
2142 args.append(revision) |
|
2143 elif branchOp == GitBranchDialog.DeleteBranch: |
|
2144 if force: |
|
2145 args.append("-D") |
|
2146 else: |
|
2147 args.append("-d") |
|
2148 args.append(branch) |
|
2149 elif branchOp == GitBranchDialog.RenameBranch: |
|
2150 if force: |
|
2151 args.append("-M") |
|
2152 else: |
|
2153 args.append("-m") |
|
2154 args.append(branch) |
|
2155 args.append(newBranch) |
|
2156 elif branchOp == GitBranchDialog.SetTrackingBranch: |
|
2157 args.append("--set-upstream-to={0}".format(remoteBranch)) |
|
2158 if branch: |
|
2159 args.append(branch) |
|
2160 elif branchOp == GitBranchDialog.UnsetTrackingBranch: |
|
2161 args.append("--unset-upstream") |
|
2162 if branch: |
|
2163 args.append(branch) |
|
2164 elif branchOp in [GitBranchDialog.CreateSwitchBranch, |
|
2165 GitBranchDialog.CreateTrackingBranch]: |
|
2166 args = self.initCommand("checkout") |
|
2167 if branchOp == GitBranchDialog.CreateSwitchBranch: |
|
2168 if force: |
|
2169 args.append("-B") |
|
2170 else: |
|
2171 args.append("-b") |
|
2172 args.append(branch) |
|
2173 if revision: |
|
2174 args.append(revision) |
|
2175 elif branchOp == GitBranchDialog.CreateTrackingBranch: |
|
2176 args.append("--track") |
|
2177 args.append(branch) |
|
2178 else: |
|
2179 return False, False |
|
2180 |
|
2181 dia = GitDialog(self.tr('Branching in the Git repository'), |
|
2182 self) |
|
2183 res = dia.startProcess(args, repodir) |
|
2184 if res: |
|
2185 dia.exec_() |
|
2186 if branchOp in [GitBranchDialog.CreateSwitchBranch, |
|
2187 GitBranchDialog.CreateTrackingBranch]: |
|
2188 update = dia.hasAddOrDelete() |
|
2189 self.checkVCSStatus() |
|
2190 else: |
|
2191 update = False |
|
2192 |
|
2193 return True, update |
|
2194 |
|
2195 def gitDeleteRemoteBranch(self, name): |
|
2196 """ |
|
2197 Public method to delete a branch from a remote repository. |
|
2198 |
|
2199 @param name file/directory name (string) |
|
2200 """ |
|
2201 dname, fname = self.splitPath(name) |
|
2202 |
|
2203 # find the root of the repo |
|
2204 repodir = dname |
|
2205 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2206 repodir = os.path.dirname(repodir) |
|
2207 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2208 return |
|
2209 |
|
2210 from .GitBranchPushDialog import GitBranchPushDialog |
|
2211 dlg = GitBranchPushDialog(self.gitGetBranchesList(repodir), |
|
2212 self.gitGetRemotesList(repodir), |
|
2213 delete=True) |
|
2214 if dlg.exec_() == QDialog.Accepted: |
|
2215 branchName, remoteName = dlg.getData()[:2] |
|
2216 |
|
2217 args = self.initCommand("push") |
|
2218 args.append(remoteName) |
|
2219 args.append(":{0}".format(branchName)) |
|
2220 |
|
2221 dia = GitDialog(self.tr('Delete Remote Branch'), self) |
|
2222 res = dia.startProcess(args, repodir) |
|
2223 if res: |
|
2224 dia.exec_() |
|
2225 |
|
2226 def gitShowBranch(self, name): |
|
2227 """ |
|
2228 Public method used to show the current branch of the working directory. |
|
2229 |
|
2230 @param name file/directory name (string) |
|
2231 """ |
|
2232 dname, fname = self.splitPath(name) |
|
2233 |
|
2234 # find the root of the repo |
|
2235 repodir = dname |
|
2236 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2237 repodir = os.path.dirname(repodir) |
|
2238 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2239 return |
|
2240 |
|
2241 branchName = self.gitGetCurrentBranch(repodir) |
|
2242 E5MessageBox.information( |
|
2243 None, |
|
2244 self.tr("Current Branch"), |
|
2245 self.tr("""<p>The current branch is <b>{0}</b>.""" |
|
2246 """</p>""").format(branchName)) |
|
2247 |
|
2248 ########################################################################### |
|
2249 ## Methods for bundles handling. |
|
2250 ########################################################################### |
|
2251 |
|
2252 def gitBundle(self, projectDir): |
|
2253 """ |
|
2254 Public method to create a bundle file. |
|
2255 |
|
2256 @param projectDir name of the project directory (string) |
|
2257 """ |
|
2258 # find the root of the repo |
|
2259 repodir = projectDir |
|
2260 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2261 repodir = os.path.dirname(repodir) |
|
2262 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2263 return |
|
2264 |
|
2265 from .GitBundleDialog import GitBundleDialog |
|
2266 dlg = GitBundleDialog(self.gitGetTagsList(repodir), |
|
2267 self.gitGetBranchesList(repodir)) |
|
2268 if dlg.exec_() == QDialog.Accepted: |
|
2269 revs = dlg.getData() |
|
2270 |
|
2271 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
|
2272 None, |
|
2273 self.tr("Create Bundle"), |
|
2274 self.__lastBundlePath or repodir, |
|
2275 self.tr("Git Bundle Files (*.bundle)"), |
|
2276 None, |
|
2277 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
|
2278 |
|
2279 if not fname: |
|
2280 return # user aborted |
|
2281 |
|
2282 ext = QFileInfo(fname).suffix() |
|
2283 if not ext: |
|
2284 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
2285 if ex: |
|
2286 fname += ex |
|
2287 if QFileInfo(fname).exists(): |
|
2288 res = E5MessageBox.yesNo( |
|
2289 self.__ui, |
|
2290 self.tr("Create Bundle"), |
|
2291 self.tr("<p>The Git bundle file <b>{0}</b> " |
|
2292 "already exists. Overwrite it?</p>") |
|
2293 .format(fname), |
|
2294 icon=E5MessageBox.Warning) |
|
2295 if not res: |
|
2296 return |
|
2297 fname = Utilities.toNativeSeparators(fname) |
|
2298 self.__lastBundlePath = os.path.dirname(fname) |
|
2299 |
|
2300 args = self.initCommand("bundle") |
|
2301 args.append("create") |
|
2302 args.append(fname) |
|
2303 for rev in revs: |
|
2304 args.append(rev) |
|
2305 |
|
2306 dia = GitDialog(self.tr('Create Bundle'), self) |
|
2307 res = dia.startProcess(args, repodir) |
|
2308 if res: |
|
2309 dia.exec_() |
|
2310 |
|
2311 def gitVerifyBundle(self, projectDir): |
|
2312 """ |
|
2313 Public method to verify a bundle file. |
|
2314 |
|
2315 @param projectDir name of the project directory (string) |
|
2316 """ |
|
2317 # find the root of the repo |
|
2318 repodir = projectDir |
|
2319 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2320 repodir = os.path.dirname(repodir) |
|
2321 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2322 return |
|
2323 |
|
2324 fname = E5FileDialog.getOpenFileName( |
|
2325 None, |
|
2326 self.tr("Verify Bundle"), |
|
2327 self.__lastBundlePath or repodir, |
|
2328 self.tr("Git Bundle Files (*.bundle);;All Files (*)")) |
|
2329 if fname: |
|
2330 self.__lastBundlePath = os.path.dirname(fname) |
|
2331 |
|
2332 args = self.initCommand("bundle") |
|
2333 args.append("verify") |
|
2334 args.append(fname) |
|
2335 |
|
2336 dia = GitDialog(self.tr('Verify Bundle'), self) |
|
2337 res = dia.startProcess(args, repodir) |
|
2338 if res: |
|
2339 dia.exec_() |
|
2340 |
|
2341 def gitBundleListHeads(self, projectDir): |
|
2342 """ |
|
2343 Public method to list the heads contained in a bundle file. |
|
2344 |
|
2345 @param projectDir name of the project directory (string) |
|
2346 """ |
|
2347 # find the root of the repo |
|
2348 repodir = projectDir |
|
2349 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2350 repodir = os.path.dirname(repodir) |
|
2351 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2352 return |
|
2353 |
|
2354 fname = E5FileDialog.getOpenFileName( |
|
2355 None, |
|
2356 self.tr("List Bundle Heads"), |
|
2357 self.__lastBundlePath or repodir, |
|
2358 self.tr("Git Bundle Files (*.bundle);;All Files (*)")) |
|
2359 if fname: |
|
2360 self.__lastBundlePath = os.path.dirname(fname) |
|
2361 |
|
2362 args = self.initCommand("bundle") |
|
2363 args.append("list-heads") |
|
2364 args.append(fname) |
|
2365 |
|
2366 dia = GitDialog(self.tr('List Bundle Heads'), self) |
|
2367 res = dia.startProcess(args, repodir) |
|
2368 if res: |
|
2369 dia.exec_() |
|
2370 |
|
2371 def gitGetBundleHeads(self, repodir, bundleFile): |
|
2372 """ |
|
2373 Public method to get a list of heads contained in a bundle file. |
|
2374 |
|
2375 @param repodir directory name of the repository (string) |
|
2376 @param bundleFile file name of a git bundle file (string) |
|
2377 @return list of heads (list of strings) |
|
2378 """ |
|
2379 args = self.initCommand("bundle") |
|
2380 args.append("list-heads") |
|
2381 args.append(bundleFile) |
|
2382 |
|
2383 output = "" |
|
2384 process = QProcess() |
|
2385 process.setWorkingDirectory(repodir) |
|
2386 process.start('git', args) |
|
2387 procStarted = process.waitForStarted(5000) |
|
2388 if procStarted: |
|
2389 finished = process.waitForFinished(30000) |
|
2390 if finished and process.exitCode() == 0: |
|
2391 output = str(process.readAllStandardOutput(), |
|
2392 Preferences.getSystem("IOEncoding"), |
|
2393 'replace') |
|
2394 |
|
2395 heads = [] |
|
2396 if output: |
|
2397 for line in output.splitlines(): |
|
2398 head = line.strip().split(None, 1)[1] # commit id, head |
|
2399 heads.append(head.replace("refs/heads/", "")) |
|
2400 |
|
2401 return heads |
|
2402 |
|
2403 def gitBundleFetch(self, projectDir): |
|
2404 """ |
|
2405 Public method to fetch a head of a bundle file into the local |
|
2406 repository. |
|
2407 |
|
2408 @param projectDir name of the project directory (string) |
|
2409 """ |
|
2410 # find the root of the repo |
|
2411 repodir = projectDir |
|
2412 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2413 repodir = os.path.dirname(repodir) |
|
2414 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2415 return |
|
2416 |
|
2417 fname = E5FileDialog.getOpenFileName( |
|
2418 None, |
|
2419 self.tr("Apply Bundle"), |
|
2420 self.__lastBundlePath or repodir, |
|
2421 self.tr("Git Bundle Files (*.bundle);;All Files (*)")) |
|
2422 if fname: |
|
2423 self.__lastBundlePath = os.path.dirname(fname) |
|
2424 |
|
2425 from .GitApplyBundleDataDialog import GitApplyBundleDataDialog |
|
2426 dlg = GitApplyBundleDataDialog( |
|
2427 self.gitGetBundleHeads(repodir, fname), |
|
2428 self.gitGetBranchesList(repodir)) |
|
2429 if dlg.exec_() == QDialog.Accepted: |
|
2430 bundleHead, branch = dlg.getData() |
|
2431 |
|
2432 args = self.initCommand("fetch") |
|
2433 args.append('--verbose') |
|
2434 args.append(fname) |
|
2435 if branch: |
|
2436 args.append(bundleHead + ":" + branch) |
|
2437 else: |
|
2438 args.append(bundleHead) |
|
2439 |
|
2440 dia = GitDialog(self.tr('Applying a bundle file (fetch)'), |
|
2441 self) |
|
2442 res = dia.startProcess(args, repodir) |
|
2443 if res: |
|
2444 dia.exec_() |
|
2445 res = dia.hasAddOrDelete() |
|
2446 self.checkVCSStatus() |
|
2447 |
|
2448 def gitBundlePull(self, projectDir): |
|
2449 """ |
|
2450 Public method to pull a head of a bundle file into the local |
|
2451 repository and working area. |
|
2452 |
|
2453 @param projectDir name of the project directory (string) |
|
2454 @return flag indicating, that the update contained an add |
|
2455 or delete (boolean) |
|
2456 """ |
|
2457 # find the root of the repo |
|
2458 repodir = projectDir |
|
2459 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2460 repodir = os.path.dirname(repodir) |
|
2461 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2462 return False |
|
2463 |
|
2464 res = False |
|
2465 fname = E5FileDialog.getOpenFileName( |
|
2466 None, |
|
2467 self.tr("Apply Bundle"), |
|
2468 self.__lastBundlePath or repodir, |
|
2469 self.tr("Git Bundle Files (*.bundle);;All Files (*)")) |
|
2470 if fname: |
|
2471 self.__lastBundlePath = os.path.dirname(fname) |
|
2472 |
|
2473 from .GitApplyBundleDataDialog import GitApplyBundleDataDialog |
|
2474 dlg = GitApplyBundleDataDialog( |
|
2475 self.gitGetBundleHeads(repodir, fname), |
|
2476 self.gitGetBranchesList(repodir)) |
|
2477 if dlg.exec_() == QDialog.Accepted: |
|
2478 bundleHead, branch = dlg.getData() |
|
2479 |
|
2480 args = self.initCommand("pull") |
|
2481 args.append('--verbose') |
|
2482 args.append(fname) |
|
2483 if branch: |
|
2484 args.append(bundleHead + ":" + branch) |
|
2485 else: |
|
2486 args.append(bundleHead) |
|
2487 |
|
2488 dia = GitDialog(self.tr('Applying a bundle file (fetch)'), |
|
2489 self) |
|
2490 res = dia.startProcess(args, repodir) |
|
2491 if res: |
|
2492 dia.exec_() |
|
2493 res = dia.hasAddOrDelete() |
|
2494 self.checkVCSStatus() |
|
2495 |
|
2496 return res |
|
2497 |
|
2498 ########################################################################### |
|
2499 ## Methods for bisect handling. |
|
2500 ########################################################################### |
|
2501 |
|
2502 def gitBisect(self, projectDir, subcommand): |
|
2503 """ |
|
2504 Public method to perform bisect commands. |
|
2505 |
|
2506 @param projectDir name of the project directory (string) |
|
2507 @param subcommand name of the subcommand (string, one of 'start', |
|
2508 'start_extended', 'good', 'bad', 'skip' or 'reset') |
|
2509 @return flag indicating, that the update contained an add |
|
2510 or delete (boolean) |
|
2511 @exception ValueError raised to indicate an invalid bisect subcommand |
|
2512 """ |
|
2513 if subcommand not in ("start", "start_extended", "good", "bad", |
|
2514 "skip", "reset"): |
|
2515 raise ValueError( |
|
2516 self.tr("Bisect subcommand ({0}) invalid.") |
|
2517 .format(subcommand)) |
|
2518 |
|
2519 # find the root of the repo |
|
2520 repodir = projectDir |
|
2521 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2522 repodir = os.path.dirname(repodir) |
|
2523 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2524 return False |
|
2525 |
|
2526 res = False |
|
2527 rev = "" |
|
2528 if subcommand in ("good", "bad", "skip", "reset"): |
|
2529 showBranches = subcommand == "reset" |
|
2530 showHead = subcommand == "reset" |
|
2531 from .GitRevisionSelectionDialog import GitRevisionSelectionDialog |
|
2532 dlg = GitRevisionSelectionDialog(self.gitGetTagsList(repodir), |
|
2533 self.gitGetBranchesList(repodir), |
|
2534 showBranches=showBranches, |
|
2535 showHead=showHead) |
|
2536 if dlg.exec_() == QDialog.Accepted: |
|
2537 rev = dlg.getRevision() |
|
2538 else: |
|
2539 return False |
|
2540 |
|
2541 args = self.initCommand("bisect") |
|
2542 if subcommand == "start_extended": |
|
2543 from .GitBisectStartDialog import GitBisectStartDialog |
|
2544 dlg = GitBisectStartDialog() |
|
2545 if dlg.exec_() == QDialog.Accepted: |
|
2546 bad, good, noCheckout = dlg.getData() |
|
2547 args.append("start") |
|
2548 if noCheckout: |
|
2549 args.append("--no-checkout") |
|
2550 args.append(bad) |
|
2551 args.extend(good) |
|
2552 args.append("--") |
|
2553 else: |
|
2554 return False |
|
2555 else: |
|
2556 args.append(subcommand) |
|
2557 if rev: |
|
2558 args.extend(rev.split()) |
|
2559 # treat rev as a list separated by whitespace |
|
2560 |
|
2561 dia = GitDialog( |
|
2562 self.tr('Git Bisect ({0})').format(subcommand), self) |
|
2563 res = dia.startProcess(args, repodir) |
|
2564 if res: |
|
2565 dia.exec_() |
|
2566 res = dia.hasAddOrDelete() |
|
2567 self.checkVCSStatus() |
|
2568 |
|
2569 return res |
|
2570 |
|
2571 def gitBisectLogBrowser(self, projectDir): |
|
2572 """ |
|
2573 Public method used to browse the bisect log of the project. |
|
2574 |
|
2575 @param projectDir name of the project directory (string) |
|
2576 """ |
|
2577 if self.bisectlogBrowser is None: |
|
2578 from .GitBisectLogBrowserDialog import GitBisectLogBrowserDialog |
|
2579 self.bisectlogBrowser = GitBisectLogBrowserDialog(self) |
|
2580 self.bisectlogBrowser.show() |
|
2581 self.bisectlogBrowser.raise_() |
|
2582 self.bisectlogBrowser.start(projectDir) |
|
2583 |
|
2584 def gitBisectCreateReplayFile(self, projectDir): |
|
2585 """ |
|
2586 Public method used to create a bisect replay file for the project. |
|
2587 |
|
2588 @param projectDir name of the project directory (string) |
|
2589 """ |
|
2590 # find the root of the repo |
|
2591 repodir = projectDir |
|
2592 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2593 repodir = os.path.dirname(repodir) |
|
2594 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2595 return |
|
2596 |
|
2597 args = self.initCommand("bisect") |
|
2598 args.append("log") |
|
2599 |
|
2600 output = "" |
|
2601 process = QProcess() |
|
2602 process.setWorkingDirectory(repodir) |
|
2603 process.start('git', args) |
|
2604 procStarted = process.waitForStarted(5000) |
|
2605 if procStarted: |
|
2606 finished = process.waitForFinished(30000) |
|
2607 if finished and process.exitCode() == 0: |
|
2608 output = str(process.readAllStandardOutput(), |
|
2609 Preferences.getSystem("IOEncoding"), |
|
2610 'replace') |
|
2611 else: |
|
2612 E5MessageBox.critical( |
|
2613 self, |
|
2614 self.tr('Process Generation Error'), |
|
2615 self.tr( |
|
2616 'The process {0} could not be started. ' |
|
2617 'Ensure, that it is in the search path.' |
|
2618 ).format('git')) |
|
2619 return |
|
2620 |
|
2621 if output: |
|
2622 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
|
2623 None, |
|
2624 self.tr("Create Bisect Replay File"), |
|
2625 self.__lastBundlePath or repodir, |
|
2626 self.tr("Git Bisect Replay Files (*.replay)"), |
|
2627 None, |
|
2628 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
|
2629 |
|
2630 if not fname: |
|
2631 return # user aborted |
|
2632 |
|
2633 ext = QFileInfo(fname).suffix() |
|
2634 if not ext: |
|
2635 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
2636 if ex: |
|
2637 fname += ex |
|
2638 if QFileInfo(fname).exists(): |
|
2639 res = E5MessageBox.yesNo( |
|
2640 self.__ui, |
|
2641 self.tr("Create Bisect Replay File"), |
|
2642 self.tr("<p>The Git bisect replay file <b>{0}</b> " |
|
2643 "already exists. Overwrite it?</p>") |
|
2644 .format(fname), |
|
2645 icon=E5MessageBox.Warning) |
|
2646 if not res: |
|
2647 return |
|
2648 fname = Utilities.toNativeSeparators(fname) |
|
2649 self.__lastReplayPath = os.path.dirname(fname) |
|
2650 |
|
2651 try: |
|
2652 f = open(fname, "w") |
|
2653 f.write(output) |
|
2654 f.close() |
|
2655 except (OSError, IOError) as err: |
|
2656 E5MessageBox.critical( |
|
2657 self.__ui, |
|
2658 self.tr("Create Bisect Replay File"), |
|
2659 self.tr( |
|
2660 """<p>The file <b>{0}</b> could not be written.</p>""" |
|
2661 """<p>Reason: {1}</p>""") |
|
2662 .format(fname, str(err))) |
|
2663 |
|
2664 def gitBisectEditReplayFile(self, projectDir): |
|
2665 """ |
|
2666 Public method used to edit a bisect replay file. |
|
2667 |
|
2668 @param projectDir name of the project directory (string) |
|
2669 """ |
|
2670 # find the root of the repo |
|
2671 repodir = projectDir |
|
2672 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2673 repodir = os.path.dirname(repodir) |
|
2674 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2675 return |
|
2676 |
|
2677 fname = E5FileDialog.getOpenFileName( |
|
2678 None, |
|
2679 self.tr("Edit Bisect Replay File"), |
|
2680 self.__lastReplayPath or repodir, |
|
2681 self.tr("Git Bisect Replay Files (*.replay);;All Files (*)")) |
|
2682 if fname: |
|
2683 self.__lastReplayPath = os.path.dirname(fname) |
|
2684 |
|
2685 self.bisectReplayEditor = MiniEditor(fname) |
|
2686 self.bisectReplayEditor.show() |
|
2687 |
|
2688 def gitBisectReplay(self, projectDir): |
|
2689 """ |
|
2690 Public method to replay a bisect session. |
|
2691 |
|
2692 @param projectDir name of the project directory (string) |
|
2693 @return flag indicating, that the update contained an add |
|
2694 or delete (boolean) |
|
2695 """ |
|
2696 # find the root of the repo |
|
2697 repodir = projectDir |
|
2698 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2699 repodir = os.path.dirname(repodir) |
|
2700 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2701 return False |
|
2702 |
|
2703 res = False |
|
2704 fname = E5FileDialog.getOpenFileName( |
|
2705 None, |
|
2706 self.tr("Bisect Replay"), |
|
2707 self.__lastReplayPath or repodir, |
|
2708 self.tr("Git Bisect Replay Files (*.replay);;All Files (*)")) |
|
2709 if fname: |
|
2710 self.__lastReplayPath = os.path.dirname(fname) |
|
2711 |
|
2712 args = self.initCommand("bisect") |
|
2713 args.append("replay") |
|
2714 args.append(fname) |
|
2715 |
|
2716 dia = GitDialog( |
|
2717 self.tr('Git Bisect ({0})').format("replay"), self) |
|
2718 res = dia.startProcess(args, repodir) |
|
2719 if res: |
|
2720 dia.exec_() |
|
2721 res = dia.hasAddOrDelete() |
|
2722 self.checkVCSStatus() |
|
2723 |
|
2724 return res |
|
2725 |
|
2726 ########################################################################### |
|
2727 ## Methods for remotes handling. |
|
2728 ########################################################################### |
|
2729 |
|
2730 def gitGetRemotesList(self, repodir): |
|
2731 """ |
|
2732 Public method to get the list of remote repos. |
|
2733 |
|
2734 @param repodir directory name of the repository (string) |
|
2735 @return list of remote repos (list of string) |
|
2736 """ |
|
2737 args = self.initCommand("remote") |
|
2738 |
|
2739 output = "" |
|
2740 process = QProcess() |
|
2741 process.setWorkingDirectory(repodir) |
|
2742 process.start('git', args) |
|
2743 procStarted = process.waitForStarted(5000) |
|
2744 if procStarted: |
|
2745 finished = process.waitForFinished(30000) |
|
2746 if finished and process.exitCode() == 0: |
|
2747 output = str(process.readAllStandardOutput(), |
|
2748 Preferences.getSystem("IOEncoding"), |
|
2749 'replace') |
|
2750 |
|
2751 remotesList = [] |
|
2752 if output: |
|
2753 for line in output.splitlines(): |
|
2754 name = line.strip() |
|
2755 remotesList.append(name) |
|
2756 |
|
2757 return remotesList |
|
2758 |
|
2759 def gitGetRemoteUrlsList(self, repodir, forFetch=True): |
|
2760 """ |
|
2761 Public method to get the list of remote repos and their URLs. |
|
2762 |
|
2763 @param repodir directory name of the repository (string) |
|
2764 @param forFetch flag indicating to get Fetch info (string) |
|
2765 @return list of tuples of remote repo name and repo URL (list of |
|
2766 tuple of two strings) |
|
2767 """ |
|
2768 args = self.initCommand("remote") |
|
2769 args.append("--verbose") |
|
2770 |
|
2771 output = "" |
|
2772 process = QProcess() |
|
2773 process.setWorkingDirectory(repodir) |
|
2774 process.start('git', args) |
|
2775 procStarted = process.waitForStarted(5000) |
|
2776 if procStarted: |
|
2777 finished = process.waitForFinished(30000) |
|
2778 if finished and process.exitCode() == 0: |
|
2779 output = str(process.readAllStandardOutput(), |
|
2780 Preferences.getSystem("IOEncoding"), |
|
2781 'replace') |
|
2782 |
|
2783 remotesList = [] |
|
2784 if output: |
|
2785 for line in output.splitlines(): |
|
2786 name, urlmode = line.strip().split(None, 1) |
|
2787 url, mode = urlmode.rsplit(None, 1) |
|
2788 if forFetch and mode == "(fetch)" or \ |
|
2789 (not forFetch) and mode == "(push)": |
|
2790 remotesList.append((name, url)) |
|
2791 |
|
2792 return remotesList |
|
2793 |
|
2794 def gitGetRemoteUrl(self, repodir, remoteName): |
|
2795 """ |
|
2796 Public method to get the URL of a remote repository. |
|
2797 |
|
2798 @param repodir directory name of the repository |
|
2799 @type str |
|
2800 @param remoteName name of the remote repository |
|
2801 @type str |
|
2802 @return URL of the remote repository |
|
2803 @rtype str |
|
2804 """ |
|
2805 args = self.initCommand("remote") |
|
2806 args.append("get-url") |
|
2807 args.append(remoteName) |
|
2808 |
|
2809 output = "" |
|
2810 process = QProcess() |
|
2811 process.setWorkingDirectory(repodir) |
|
2812 process.start('git', args) |
|
2813 procStarted = process.waitForStarted(5000) |
|
2814 if procStarted: |
|
2815 finished = process.waitForFinished(30000) |
|
2816 if finished and process.exitCode() == 0: |
|
2817 output = str(process.readAllStandardOutput(), |
|
2818 Preferences.getSystem("IOEncoding"), |
|
2819 'replace').strip() |
|
2820 |
|
2821 return output |
|
2822 |
|
2823 def gitGetRemoteBranchesList(self, repodir, remote): |
|
2824 """ |
|
2825 Public method to get the list of a remote repository branches. |
|
2826 |
|
2827 @param repodir directory name of the repository (string) |
|
2828 @param remote remote repository name (string) |
|
2829 @return list of remote repository branches (list of string) |
|
2830 """ |
|
2831 args = self.initCommand("ls-remote") |
|
2832 args.append("--heads") |
|
2833 args.append(remote) |
|
2834 |
|
2835 output = "" |
|
2836 process = QProcess() |
|
2837 process.setWorkingDirectory(repodir) |
|
2838 process.start('git', args) |
|
2839 procStarted = process.waitForStarted(5000) |
|
2840 if procStarted: |
|
2841 finished = process.waitForFinished(30000) |
|
2842 if finished and process.exitCode() == 0: |
|
2843 output = str(process.readAllStandardOutput(), |
|
2844 Preferences.getSystem("IOEncoding"), |
|
2845 'replace') |
|
2846 |
|
2847 remoteBranches = [] |
|
2848 if output: |
|
2849 for line in output.splitlines(): |
|
2850 branch = line.strip().split()[-1].split("/")[-1] |
|
2851 remoteBranches.append(branch) |
|
2852 |
|
2853 return remoteBranches |
|
2854 |
|
2855 def gitShowRemote(self, projectDir, remoteName): |
|
2856 """ |
|
2857 Public method to show information about a remote repository. |
|
2858 |
|
2859 @param projectDir name of the project directory (string) |
|
2860 @param remoteName name of the remote repository (string) |
|
2861 """ |
|
2862 # find the root of the repo |
|
2863 repodir = projectDir |
|
2864 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2865 repodir = os.path.dirname(repodir) |
|
2866 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2867 return |
|
2868 |
|
2869 args = self.initCommand("remote") |
|
2870 args.append("show") |
|
2871 args.append(remoteName) |
|
2872 |
|
2873 dia = GitDialog(self.tr('Show Remote Info'), self) |
|
2874 res = dia.startProcess(args, repodir, showArgs=False) |
|
2875 if res: |
|
2876 dia.exec_() |
|
2877 |
|
2878 def gitShowRemotes(self, projectDir): |
|
2879 """ |
|
2880 Public method to show available remote repositories. |
|
2881 |
|
2882 @param projectDir name of the project directory (string) |
|
2883 """ |
|
2884 if self.remotesDialog is None: |
|
2885 from .GitRemoteRepositoriesDialog import \ |
|
2886 GitRemoteRepositoriesDialog |
|
2887 self.remotesDialog = GitRemoteRepositoriesDialog(self) |
|
2888 self.remotesDialog.show() |
|
2889 self.remotesDialog.raise_() |
|
2890 self.remotesDialog.start(projectDir) |
|
2891 |
|
2892 def gitAddRemote(self, projectDir): |
|
2893 """ |
|
2894 Public method to add a remote repository. |
|
2895 |
|
2896 @param projectDir name of the project directory (string) |
|
2897 """ |
|
2898 # find the root of the repo |
|
2899 repodir = projectDir |
|
2900 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2901 repodir = os.path.dirname(repodir) |
|
2902 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2903 return |
|
2904 |
|
2905 from .GitAddRemoteDialog import GitAddRemoteDialog |
|
2906 dlg = GitAddRemoteDialog() |
|
2907 if dlg.exec_() == QDialog.Accepted: |
|
2908 name, url = dlg.getData() |
|
2909 args = self.initCommand("remote") |
|
2910 args.append("add") |
|
2911 args.append(name) |
|
2912 args.append(url) |
|
2913 |
|
2914 self.startSynchronizedProcess(QProcess(), "git", args, |
|
2915 workingDir=repodir) |
|
2916 |
|
2917 def gitRenameRemote(self, projectDir, remoteName): |
|
2918 """ |
|
2919 Public method to rename a remote repository. |
|
2920 |
|
2921 @param projectDir name of the project directory (string) |
|
2922 @param remoteName name of the remote repository (string) |
|
2923 """ |
|
2924 # find the root of the repo |
|
2925 repodir = projectDir |
|
2926 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2927 repodir = os.path.dirname(repodir) |
|
2928 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2929 return |
|
2930 |
|
2931 newName, ok = QInputDialog.getText( |
|
2932 None, |
|
2933 self.tr("Rename Remote Repository"), |
|
2934 self.tr("Enter new name for remote repository:"), |
|
2935 QLineEdit.Normal) |
|
2936 if ok and newName and newName != remoteName: |
|
2937 args = self.initCommand("remote") |
|
2938 args.append("rename") |
|
2939 args.append(remoteName) |
|
2940 args.append(newName) |
|
2941 |
|
2942 self.startSynchronizedProcess(QProcess(), "git", args, |
|
2943 workingDir=repodir) |
|
2944 |
|
2945 def gitChangeRemoteUrl(self, projectDir, remoteName, remoteUrl=""): |
|
2946 """ |
|
2947 Public method to change the URL of a remote repository. |
|
2948 |
|
2949 @param projectDir name of the project directory |
|
2950 @type str |
|
2951 @param remoteName name of the remote repository |
|
2952 @type str |
|
2953 @param remoteUrl URL of the remote repository |
|
2954 @type str |
|
2955 """ |
|
2956 # find the root of the repo |
|
2957 repodir = projectDir |
|
2958 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2959 repodir = os.path.dirname(repodir) |
|
2960 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2961 return |
|
2962 |
|
2963 if not remoteUrl: |
|
2964 remoteUrl = self.gitGetRemoteUrl(repodir, remoteName) |
|
2965 |
|
2966 from .GitChangeRemoteUrlDialog import GitChangeRemoteUrlDialog |
|
2967 dlg = GitChangeRemoteUrlDialog(remoteName, remoteUrl) |
|
2968 if dlg.exec_() == QDialog.Accepted: |
|
2969 name, url = dlg.getData() |
|
2970 if url != remoteUrl: |
|
2971 args = self.initCommand("remote") |
|
2972 args.append("set-url") |
|
2973 args.append(name) |
|
2974 args.append(url) |
|
2975 |
|
2976 self.startSynchronizedProcess(QProcess(), "git", args, |
|
2977 workingDir=repodir) |
|
2978 |
|
2979 def gitChangeRemoteCredentials(self, projectDir, remoteName, remoteUrl=""): |
|
2980 """ |
|
2981 Public method to change the user credentials of a remote repository. |
|
2982 |
|
2983 @param projectDir name of the project directory |
|
2984 @type str |
|
2985 @param remoteName name of the remote repository |
|
2986 @type str |
|
2987 @param remoteUrl URL of the remote repository |
|
2988 @type str |
|
2989 """ |
|
2990 # find the root of the repo |
|
2991 repodir = projectDir |
|
2992 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
2993 repodir = os.path.dirname(repodir) |
|
2994 if os.path.splitdrive(repodir)[1] == os.sep: |
|
2995 return |
|
2996 |
|
2997 if not remoteUrl: |
|
2998 remoteUrl = self.gitGetRemoteUrl(repodir, remoteName) |
|
2999 |
|
3000 from .GitRemoteCredentialsDialog import GitRemoteCredentialsDialog |
|
3001 dlg = GitRemoteCredentialsDialog(remoteName, remoteUrl) |
|
3002 if dlg.exec_() == QDialog.Accepted: |
|
3003 name, url = dlg.getData() |
|
3004 if url != remoteUrl: |
|
3005 args = self.initCommand("remote") |
|
3006 args.append("set-url") |
|
3007 args.append(name) |
|
3008 args.append(url) |
|
3009 |
|
3010 self.startSynchronizedProcess(QProcess(), "git", args, |
|
3011 workingDir=repodir) |
|
3012 |
|
3013 def gitRemoveRemote(self, projectDir, remoteName): |
|
3014 """ |
|
3015 Public method to remove a remote repository. |
|
3016 |
|
3017 @param projectDir name of the project directory (string) |
|
3018 @param remoteName name of the remote repository (string) |
|
3019 """ |
|
3020 # find the root of the repo |
|
3021 repodir = projectDir |
|
3022 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3023 repodir = os.path.dirname(repodir) |
|
3024 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3025 return |
|
3026 |
|
3027 args = self.initCommand("remote") |
|
3028 args.append("remove") |
|
3029 args.append(remoteName) |
|
3030 |
|
3031 self.startSynchronizedProcess(QProcess(), "git", args, |
|
3032 workingDir=repodir) |
|
3033 |
|
3034 def gitPruneRemote(self, projectDir, remoteName): |
|
3035 """ |
|
3036 Public method to prune stale remote-tracking branches. |
|
3037 |
|
3038 @param projectDir name of the project directory (string) |
|
3039 @param remoteName name of the remote repository (string) |
|
3040 """ |
|
3041 # find the root of the repo |
|
3042 repodir = projectDir |
|
3043 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3044 repodir = os.path.dirname(repodir) |
|
3045 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3046 return |
|
3047 |
|
3048 args = self.initCommand("remote") |
|
3049 args.append("prune") |
|
3050 args.append(remoteName) |
|
3051 |
|
3052 dia = GitDialog(self.tr('Show Remote Info'), self) |
|
3053 res = dia.startProcess(args, repodir) |
|
3054 if res: |
|
3055 dia.exec_() |
|
3056 |
|
3057 def gitShortlog(self, projectDir, commit): |
|
3058 """ |
|
3059 Public method to show a short log suitable for inclusion in release |
|
3060 announcements. |
|
3061 |
|
3062 @param projectDir name of the project directory (string) |
|
3063 @param commit commit to start the log at (strings) |
|
3064 """ |
|
3065 # find the root of the repo |
|
3066 repodir = projectDir |
|
3067 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3068 repodir = os.path.dirname(repodir) |
|
3069 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3070 return |
|
3071 |
|
3072 args = self.initCommand("shortlog") |
|
3073 args.append("-w") |
|
3074 args.append(commit) |
|
3075 |
|
3076 dia = GitDialog(self.tr('Show Shortlog'), self) |
|
3077 res = dia.startProcess(args, repodir, showArgs=False) |
|
3078 if res: |
|
3079 dia.exec_() |
|
3080 |
|
3081 def gitDescribe(self, projectDir, commits): |
|
3082 """ |
|
3083 Public method to find the most recent tag reachable from each commit. |
|
3084 |
|
3085 @param projectDir name of the project directory (string) |
|
3086 @param commits list of commits to start the search from |
|
3087 (list of strings) |
|
3088 """ |
|
3089 if self.describeDialog is None: |
|
3090 from .GitDescribeDialog import GitDescribeDialog |
|
3091 self.describeDialog = GitDescribeDialog(self) |
|
3092 self.describeDialog.show() |
|
3093 self.describeDialog.raise_() |
|
3094 self.describeDialog.start(projectDir, commits) |
|
3095 |
|
3096 ########################################################################### |
|
3097 ## Methods for cherry-pick handling. |
|
3098 ########################################################################### |
|
3099 |
|
3100 def gitCherryPick(self, projectDir, commits=None): |
|
3101 """ |
|
3102 Public method to cherry pick commits and apply them to the current |
|
3103 branch. |
|
3104 |
|
3105 @param projectDir name of the project directory (string) |
|
3106 @param commits list of commits to be applied (list of strings) |
|
3107 @return flag indicating that the project should be reread (boolean) |
|
3108 """ |
|
3109 # find the root of the repo |
|
3110 repodir = projectDir |
|
3111 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3112 repodir = os.path.dirname(repodir) |
|
3113 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3114 return False |
|
3115 |
|
3116 res = False |
|
3117 |
|
3118 from .GitCherryPickDialog import GitCherryPickDialog |
|
3119 dlg = GitCherryPickDialog(commits) |
|
3120 if dlg.exec_() == QDialog.Accepted: |
|
3121 commits, cherrypickInfo, signoff, nocommit = \ |
|
3122 dlg.getData() |
|
3123 |
|
3124 args = self.initCommand("cherry-pick") |
|
3125 args.append("-Xpatience") |
|
3126 if cherrypickInfo: |
|
3127 args.append("-x") |
|
3128 if signoff: |
|
3129 args.append("--signoff") |
|
3130 if nocommit: |
|
3131 args.append("--no-commit") |
|
3132 args.extend(commits) |
|
3133 |
|
3134 dia = GitDialog(self.tr('Cherry-pick'), self) |
|
3135 res = dia.startProcess(args, repodir) |
|
3136 if res: |
|
3137 dia.exec_() |
|
3138 res = dia.hasAddOrDelete() |
|
3139 self.checkVCSStatus() |
|
3140 return res |
|
3141 |
|
3142 def gitCherryPickContinue(self, projectDir): |
|
3143 """ |
|
3144 Public method to continue the last copying session after conflicts |
|
3145 were resolved. |
|
3146 |
|
3147 @param projectDir name of the project directory (string) |
|
3148 @return flag indicating that the project should be reread (boolean) |
|
3149 """ |
|
3150 # find the root of the repo |
|
3151 repodir = projectDir |
|
3152 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3153 repodir = os.path.dirname(repodir) |
|
3154 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3155 return False |
|
3156 |
|
3157 import sys |
|
3158 editor = sys.argv[0].replace(".py", "_editor.py") |
|
3159 env = {"GIT_EDITOR": "{0} {1}".format(sys.executable, editor)} |
|
3160 |
|
3161 args = self.initCommand("cherry-pick") |
|
3162 args.append("--continue") |
|
3163 |
|
3164 dia = GitDialog(self.tr('Copy Changesets (Continue)'), self) |
|
3165 res = dia.startProcess(args, repodir, environment=env) |
|
3166 if res: |
|
3167 dia.exec_() |
|
3168 res = dia.hasAddOrDelete() |
|
3169 self.checkVCSStatus() |
|
3170 return res |
|
3171 |
|
3172 def gitCherryPickQuit(self, projectDir): |
|
3173 """ |
|
3174 Public method to quit the current copying operation. |
|
3175 |
|
3176 @param projectDir name of the project directory (string) |
|
3177 @return flag indicating that the project should be reread (boolean) |
|
3178 """ |
|
3179 # find the root of the repo |
|
3180 repodir = projectDir |
|
3181 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3182 repodir = os.path.dirname(repodir) |
|
3183 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3184 return False |
|
3185 |
|
3186 args = self.initCommand("cherry-pick") |
|
3187 args.append("--quit") |
|
3188 |
|
3189 dia = GitDialog(self.tr('Copy Changesets (Quit)'), self) |
|
3190 res = dia.startProcess(args, repodir) |
|
3191 if res: |
|
3192 dia.exec_() |
|
3193 res = dia.hasAddOrDelete() |
|
3194 self.checkVCSStatus() |
|
3195 return res |
|
3196 |
|
3197 def gitCherryPickAbort(self, projectDir): |
|
3198 """ |
|
3199 Public method to cancel the last copying session and return to |
|
3200 the previous state. |
|
3201 |
|
3202 @param projectDir name of the project directory (string) |
|
3203 @return flag indicating that the project should be reread (boolean) |
|
3204 """ |
|
3205 # find the root of the repo |
|
3206 repodir = projectDir |
|
3207 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3208 repodir = os.path.dirname(repodir) |
|
3209 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3210 return False |
|
3211 |
|
3212 args = self.initCommand("cherry-pick") |
|
3213 args.append("--abort") |
|
3214 |
|
3215 dia = GitDialog(self.tr('Copy Changesets (Cancel)'), self) |
|
3216 res = dia.startProcess(args, repodir) |
|
3217 if res: |
|
3218 dia.exec_() |
|
3219 res = dia.hasAddOrDelete() |
|
3220 self.checkVCSStatus() |
|
3221 return res |
|
3222 |
|
3223 ########################################################################### |
|
3224 ## Methods for stash handling. |
|
3225 ########################################################################### |
|
3226 |
|
3227 def __gitGetStashesList(self, projectDir): |
|
3228 """ |
|
3229 Private method to get a list of stash names. |
|
3230 |
|
3231 @param projectDir name of the project directory (string) |
|
3232 @return list of available stashes (list of string) |
|
3233 """ |
|
3234 # find the root of the repo |
|
3235 repodir = projectDir |
|
3236 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3237 repodir = os.path.dirname(repodir) |
|
3238 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3239 return [] |
|
3240 |
|
3241 args = self.initCommand("stash") |
|
3242 args.append("list") |
|
3243 args.append("--format=format:%gd") |
|
3244 |
|
3245 stashesList = [] |
|
3246 output = "" |
|
3247 process = QProcess() |
|
3248 process.setWorkingDirectory(repodir) |
|
3249 process.start('git', args) |
|
3250 procStarted = process.waitForStarted(5000) |
|
3251 if procStarted: |
|
3252 finished = process.waitForFinished(30000) |
|
3253 if finished and process.exitCode() == 0: |
|
3254 output = str(process.readAllStandardOutput(), |
|
3255 Preferences.getSystem("IOEncoding"), |
|
3256 'replace') |
|
3257 |
|
3258 if output: |
|
3259 stashesList = output.strip().splitlines() |
|
3260 |
|
3261 return stashesList |
|
3262 |
|
3263 def gitStashSave(self, projectDir): |
|
3264 """ |
|
3265 Public method to save the current changes to a new stash. |
|
3266 |
|
3267 @param projectDir name of the project directory (string) |
|
3268 @return flag indicating, that the save contained an add |
|
3269 or delete (boolean) |
|
3270 """ |
|
3271 # find the root of the repo |
|
3272 repodir = projectDir |
|
3273 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3274 repodir = os.path.dirname(repodir) |
|
3275 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3276 return False |
|
3277 |
|
3278 res = False |
|
3279 from .GitStashDataDialog import GitStashDataDialog |
|
3280 dlg = GitStashDataDialog() |
|
3281 if dlg.exec_() == QDialog.Accepted: |
|
3282 message, keepIndex, untracked = dlg.getData() |
|
3283 args = self.initCommand("stash") |
|
3284 args.append("save") |
|
3285 if keepIndex: |
|
3286 args.append("--keep-index") |
|
3287 if untracked == GitStashDataDialog.UntrackedOnly: |
|
3288 args.append("--include-untracked") |
|
3289 elif untracked == GitStashDataDialog.UntrackedAndIgnored: |
|
3290 args.append("--all") |
|
3291 if message: |
|
3292 args.append(message) |
|
3293 |
|
3294 dia = GitDialog(self.tr('Saving stash'), self) |
|
3295 res = dia.startProcess(args, repodir) |
|
3296 if res: |
|
3297 dia.exec_() |
|
3298 res = dia.hasAddOrDelete() |
|
3299 self.checkVCSStatus() |
|
3300 return res |
|
3301 |
|
3302 def gitStashBrowser(self, projectDir): |
|
3303 """ |
|
3304 Public method used to browse the stashed changes. |
|
3305 |
|
3306 @param projectDir name of the project directory (string) |
|
3307 """ |
|
3308 if self.stashBrowser is None: |
|
3309 from .GitStashBrowserDialog import GitStashBrowserDialog |
|
3310 self.stashBrowser = GitStashBrowserDialog(self) |
|
3311 self.stashBrowser.show() |
|
3312 self.stashBrowser.raise_() |
|
3313 self.stashBrowser.start(projectDir) |
|
3314 |
|
3315 def gitStashShowPatch(self, projectDir, stashName=""): |
|
3316 """ |
|
3317 Public method to show the contents of a stash. |
|
3318 |
|
3319 @param projectDir name of the project directory (string) |
|
3320 @param stashName name of a stash (string) |
|
3321 """ |
|
3322 # find the root of the repo |
|
3323 repodir = projectDir |
|
3324 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3325 repodir = os.path.dirname(repodir) |
|
3326 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3327 return |
|
3328 |
|
3329 if not stashName: |
|
3330 availableStashes = self.__gitGetStashesList(repodir) |
|
3331 stashName, ok = QInputDialog.getItem( |
|
3332 None, |
|
3333 self.tr("Show Stash"), |
|
3334 self.tr("Select a stash (empty for latest stash):"), |
|
3335 [""] + availableStashes, |
|
3336 0, False) |
|
3337 if not ok: |
|
3338 return |
|
3339 |
|
3340 if self.diff is None: |
|
3341 from .GitDiffDialog import GitDiffDialog |
|
3342 self.diff = GitDiffDialog(self) |
|
3343 self.diff.show() |
|
3344 self.diff.raise_() |
|
3345 self.diff.start(repodir, diffMode="stash", stashName=stashName) |
|
3346 |
|
3347 def gitStashApply(self, projectDir, stashName=""): |
|
3348 """ |
|
3349 Public method to apply a stash but keep it. |
|
3350 |
|
3351 @param projectDir name of the project directory (string) |
|
3352 @param stashName name of a stash (string) |
|
3353 @return flag indicating, that the restore contained an add |
|
3354 or delete (boolean) |
|
3355 """ |
|
3356 # find the root of the repo |
|
3357 repodir = projectDir |
|
3358 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3359 repodir = os.path.dirname(repodir) |
|
3360 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3361 return False |
|
3362 |
|
3363 if not stashName: |
|
3364 availableStashes = self.__gitGetStashesList(repodir) |
|
3365 stashName, ok = QInputDialog.getItem( |
|
3366 None, |
|
3367 self.tr("Restore Stash"), |
|
3368 self.tr("Select a stash (empty for latest stash):"), |
|
3369 [""] + availableStashes, |
|
3370 0, False) |
|
3371 if not ok: |
|
3372 return False |
|
3373 |
|
3374 args = self.initCommand("stash") |
|
3375 args.append("apply") |
|
3376 if stashName: |
|
3377 args.append(stashName) |
|
3378 |
|
3379 dia = GitDialog(self.tr('Restoring stash'), self) |
|
3380 res = dia.startProcess(args, repodir) |
|
3381 if res: |
|
3382 dia.exec_() |
|
3383 res = dia.hasAddOrDelete() |
|
3384 self.checkVCSStatus() |
|
3385 return res |
|
3386 |
|
3387 def gitStashPop(self, projectDir, stashName=""): |
|
3388 """ |
|
3389 Public method to apply a stash and delete it. |
|
3390 |
|
3391 @param projectDir name of the project directory (string) |
|
3392 @param stashName name of a stash (string) |
|
3393 @return flag indicating, that the restore contained an add |
|
3394 or delete (boolean) |
|
3395 """ |
|
3396 # find the root of the repo |
|
3397 repodir = projectDir |
|
3398 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3399 repodir = os.path.dirname(repodir) |
|
3400 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3401 return False |
|
3402 |
|
3403 if not stashName: |
|
3404 availableStashes = self.__gitGetStashesList(repodir) |
|
3405 stashName, ok = QInputDialog.getItem( |
|
3406 None, |
|
3407 self.tr("Restore Stash"), |
|
3408 self.tr("Select a stash (empty for latest stash):"), |
|
3409 [""] + availableStashes, |
|
3410 0, False) |
|
3411 if not ok: |
|
3412 return False |
|
3413 |
|
3414 args = self.initCommand("stash") |
|
3415 args.append("pop") |
|
3416 if stashName: |
|
3417 args.append(stashName) |
|
3418 |
|
3419 dia = GitDialog(self.tr('Restoring stash'), self) |
|
3420 res = dia.startProcess(args, repodir) |
|
3421 if res: |
|
3422 dia.exec_() |
|
3423 res = dia.hasAddOrDelete() |
|
3424 self.checkVCSStatus() |
|
3425 return res |
|
3426 |
|
3427 def gitStashBranch(self, projectDir, stashName=""): |
|
3428 """ |
|
3429 Public method to create a branch from a stash. |
|
3430 |
|
3431 @param projectDir name of the project directory (string) |
|
3432 @param stashName name of a stash (string) |
|
3433 @return flag indicating, that the restore contained an add |
|
3434 or delete (boolean) |
|
3435 """ |
|
3436 # find the root of the repo |
|
3437 repodir = projectDir |
|
3438 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3439 repodir = os.path.dirname(repodir) |
|
3440 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3441 return False |
|
3442 |
|
3443 branchName, ok = QInputDialog.getText( |
|
3444 None, |
|
3445 self.tr("Create Branch"), |
|
3446 self.tr("Enter a branch name to restore a stash to:"), |
|
3447 QLineEdit.Normal) |
|
3448 if not ok or branchName == "": |
|
3449 return False |
|
3450 |
|
3451 if not stashName: |
|
3452 availableStashes = self.__gitGetStashesList(repodir) |
|
3453 stashName, ok = QInputDialog.getItem( |
|
3454 None, |
|
3455 self.tr("Create Branch"), |
|
3456 self.tr("Select a stash (empty for latest stash):"), |
|
3457 [""] + availableStashes, |
|
3458 0, False) |
|
3459 if not ok: |
|
3460 return False |
|
3461 |
|
3462 args = self.initCommand("stash") |
|
3463 args.append("branch") |
|
3464 args.append(branchName) |
|
3465 if stashName: |
|
3466 args.append(stashName) |
|
3467 |
|
3468 dia = GitDialog(self.tr('Creating branch'), self) |
|
3469 res = dia.startProcess(args, repodir) |
|
3470 if res: |
|
3471 dia.exec_() |
|
3472 res = dia.hasAddOrDelete() |
|
3473 self.checkVCSStatus() |
|
3474 return res |
|
3475 |
|
3476 def gitStashDrop(self, projectDir, stashName=""): |
|
3477 """ |
|
3478 Public method to delete a stash. |
|
3479 |
|
3480 @param projectDir name of the project directory (string) |
|
3481 @param stashName name of a stash (string) |
|
3482 @return flag indicating a successful deletion (boolean) |
|
3483 """ |
|
3484 # find the root of the repo |
|
3485 repodir = projectDir |
|
3486 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3487 repodir = os.path.dirname(repodir) |
|
3488 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3489 return False |
|
3490 |
|
3491 if not stashName: |
|
3492 availableStashes = self.__gitGetStashesList(repodir) |
|
3493 stashName, ok = QInputDialog.getItem( |
|
3494 None, |
|
3495 self.tr("Show Stash"), |
|
3496 self.tr("Select a stash (empty for latest stash):"), |
|
3497 [""] + availableStashes, |
|
3498 0, False) |
|
3499 if not ok: |
|
3500 return False |
|
3501 |
|
3502 res = E5MessageBox.yesNo( |
|
3503 None, |
|
3504 self.tr("Delete Stash"), |
|
3505 self.tr("""Do you really want to delete the stash <b>{0}</b>?""") |
|
3506 .format(stashName)) |
|
3507 if res: |
|
3508 args = self.initCommand("stash") |
|
3509 args.append("drop") |
|
3510 if stashName: |
|
3511 args.append(stashName) |
|
3512 |
|
3513 dia = GitDialog(self.tr('Deleting stash'), self) |
|
3514 res = dia.startProcess(args, repodir) |
|
3515 if res: |
|
3516 dia.exec_() |
|
3517 return res |
|
3518 |
|
3519 def gitStashClear(self, projectDir): |
|
3520 """ |
|
3521 Public method to delete all stashes. |
|
3522 |
|
3523 @param projectDir name of the project directory (string) |
|
3524 @return flag indicating a successful deletion (boolean) |
|
3525 """ |
|
3526 # find the root of the repo |
|
3527 repodir = projectDir |
|
3528 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3529 repodir = os.path.dirname(repodir) |
|
3530 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3531 return False |
|
3532 |
|
3533 res = E5MessageBox.yesNo( |
|
3534 None, |
|
3535 self.tr("Delete All Stashes"), |
|
3536 self.tr("""Do you really want to delete all stashes?""")) |
|
3537 if res: |
|
3538 args = self.initCommand("stash") |
|
3539 args.append("clear") |
|
3540 |
|
3541 dia = GitDialog(self.tr('Deleting all stashes'), self) |
|
3542 res = dia.startProcess(args, repodir) |
|
3543 if res: |
|
3544 dia.exec_() |
|
3545 return res |
|
3546 |
|
3547 def gitEditConfig(self, projectDir): |
|
3548 """ |
|
3549 Public method used to edit the repository configuration file. |
|
3550 |
|
3551 @param projectDir name of the project directory (string) |
|
3552 """ |
|
3553 # find the root of the repo |
|
3554 repodir = projectDir |
|
3555 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3556 repodir = os.path.dirname(repodir) |
|
3557 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3558 return |
|
3559 |
|
3560 cfgFile = os.path.join(repodir, self.adminDir, "config") |
|
3561 if not os.path.exists(cfgFile): |
|
3562 # create an empty one |
|
3563 try: |
|
3564 cfg = open(cfgFile, "w") |
|
3565 cfg.close() |
|
3566 except IOError: |
|
3567 pass |
|
3568 self.repoEditor = MiniEditor(cfgFile, "Properties") |
|
3569 self.repoEditor.show() |
|
3570 |
|
3571 def gitEditUserConfig(self): |
|
3572 """ |
|
3573 Public method used to edit the user configuration file. |
|
3574 """ |
|
3575 from .GitUtilities import getConfigPath |
|
3576 cfgFile = getConfigPath() |
|
3577 if not os.path.exists(cfgFile): |
|
3578 from .GitUserConfigDataDialog import GitUserConfigDataDialog |
|
3579 dlg = GitUserConfigDataDialog() |
|
3580 if dlg.exec_() == QDialog.Accepted: |
|
3581 firstName, lastName, email = dlg.getData() |
|
3582 else: |
|
3583 firstName, lastName, email = ( |
|
3584 "Firstname", "Lastname", "email_address") |
|
3585 try: |
|
3586 f = open(cfgFile, "w") |
|
3587 f.write("[user]\n") |
|
3588 f.write(" name = {0} {1}\n".format(firstName, lastName)) |
|
3589 f.write(" email = {0}\n".format(email)) |
|
3590 f.close() |
|
3591 except (IOError, OSError): |
|
3592 # ignore these |
|
3593 pass |
|
3594 self.userEditor = MiniEditor(cfgFile, "Properties") |
|
3595 self.userEditor.show() |
|
3596 |
|
3597 def gitShowConfig(self, projectDir): |
|
3598 """ |
|
3599 Public method to show the combined configuration. |
|
3600 |
|
3601 @param projectDir name of the project directory (string) |
|
3602 """ |
|
3603 # find the root of the repo |
|
3604 repodir = projectDir |
|
3605 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3606 repodir = os.path.dirname(repodir) |
|
3607 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3608 return |
|
3609 |
|
3610 args = self.initCommand("config") |
|
3611 args.append("--list") |
|
3612 |
|
3613 dia = GitDialog( |
|
3614 self.tr('Showing the combined configuration settings'), |
|
3615 self) |
|
3616 res = dia.startProcess(args, repodir, False) |
|
3617 if res: |
|
3618 dia.exec_() |
|
3619 |
|
3620 def gitVerify(self, projectDir): |
|
3621 """ |
|
3622 Public method to verify the connectivity and validity of objects |
|
3623 of the database. |
|
3624 |
|
3625 @param projectDir name of the project directory (string) |
|
3626 """ |
|
3627 # find the root of the repo |
|
3628 repodir = projectDir |
|
3629 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3630 repodir = os.path.dirname(repodir) |
|
3631 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3632 return |
|
3633 |
|
3634 args = self.initCommand("fsck") |
|
3635 args.append("--strict") |
|
3636 args.append("--full") |
|
3637 args.append("--cache") |
|
3638 |
|
3639 dia = GitDialog( |
|
3640 self.tr('Verifying the integrity of the Git repository'), |
|
3641 self) |
|
3642 res = dia.startProcess(args, repodir, False) |
|
3643 if res: |
|
3644 dia.exec_() |
|
3645 |
|
3646 def gitHouseKeeping(self, projectDir): |
|
3647 """ |
|
3648 Public method to cleanup and optimize the local repository. |
|
3649 |
|
3650 @param projectDir name of the project directory (string) |
|
3651 """ |
|
3652 # find the root of the repo |
|
3653 repodir = projectDir |
|
3654 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3655 repodir = os.path.dirname(repodir) |
|
3656 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3657 return |
|
3658 |
|
3659 args = self.initCommand("gc") |
|
3660 args.append("--prune") |
|
3661 if self.__plugin.getPreferences("AggressiveGC"): |
|
3662 args.append("--aggressive") |
|
3663 |
|
3664 dia = GitDialog( |
|
3665 self.tr('Performing Repository Housekeeping'), |
|
3666 self) |
|
3667 res = dia.startProcess(args, repodir) |
|
3668 if res: |
|
3669 dia.exec_() |
|
3670 |
|
3671 def gitStatistics(self, projectDir): |
|
3672 """ |
|
3673 Public method to show some statistics of the local repository. |
|
3674 |
|
3675 @param projectDir name of the project directory (string) |
|
3676 """ |
|
3677 # find the root of the repo |
|
3678 repodir = projectDir |
|
3679 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3680 repodir = os.path.dirname(repodir) |
|
3681 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3682 return |
|
3683 |
|
3684 args = self.initCommand("count-objects") |
|
3685 args.append("-v") |
|
3686 |
|
3687 output = "" |
|
3688 process = QProcess() |
|
3689 process.setWorkingDirectory(repodir) |
|
3690 process.start('git', args) |
|
3691 procStarted = process.waitForStarted(5000) |
|
3692 if procStarted: |
|
3693 finished = process.waitForFinished(30000) |
|
3694 if finished and process.exitCode() == 0: |
|
3695 output = str(process.readAllStandardOutput(), |
|
3696 Preferences.getSystem("IOEncoding"), |
|
3697 'replace') |
|
3698 |
|
3699 info = [] |
|
3700 if output: |
|
3701 statistics = {} |
|
3702 for line in output.splitlines(): |
|
3703 key, value = line.strip().split(": ", 1) |
|
3704 statistics[key] = value |
|
3705 |
|
3706 info.append("""<p><table>""") |
|
3707 info.append(self.tr("""<tr><td><b>Statistics</b></td></tr>""")) |
|
3708 info.append( |
|
3709 self.tr("""<tr><td>Number of loose objects: </td>""" |
|
3710 """<td>{0}</td></tr>""") |
|
3711 .format(statistics["count"])) |
|
3712 info.append( |
|
3713 self.tr("""<tr><td>Disk space used by loose objects: </td>""" |
|
3714 """<td>{0} KiB</td></tr>""") |
|
3715 .format(statistics["size"])) |
|
3716 info.append( |
|
3717 self.tr("""<tr><td>Number of packed objects: </td>""" |
|
3718 """<td>{0}</td></tr>""") |
|
3719 .format(statistics["in-pack"])) |
|
3720 info.append( |
|
3721 self.tr("""<tr><td>Number of packs: </td>""" |
|
3722 """<td>{0}</td></tr>""") |
|
3723 .format(statistics["packs"])) |
|
3724 info.append( |
|
3725 self.tr("""<tr><td>Disk space used by packed objects: </td>""" |
|
3726 """<td>{0} KiB</td></tr>""") |
|
3727 .format(statistics["size-pack"])) |
|
3728 info.append( |
|
3729 self.tr("""<tr><td>Packed objects waiting for pruning: </td>""" |
|
3730 """<td>{0}</td></tr>""") |
|
3731 .format(statistics["prune-packable"])) |
|
3732 info.append( |
|
3733 self.tr("""<tr><td>Garbage files: </td>""" |
|
3734 """<td>{0}</td></tr>""") |
|
3735 .format(statistics["garbage"])) |
|
3736 info.append( |
|
3737 self.tr("""<tr><td>Disk space used by garbage files: </td>""" |
|
3738 """<td>{0} KiB</td></tr>""") |
|
3739 .format(statistics["size-garbage"])) |
|
3740 info.append("""</table></p>""") |
|
3741 else: |
|
3742 info.append(self.tr("<p><b>No statistics available.</b></p>")) |
|
3743 dlg = VcsRepositoryInfoDialog(None, "\n".join(info)) |
|
3744 dlg.exec_() |
|
3745 |
|
3746 def gitGetArchiveFormats(self, repodir): |
|
3747 """ |
|
3748 Public method to get a list of supported archive formats. |
|
3749 |
|
3750 @param repodir directory name of the repository (string) |
|
3751 @return list of supported archive formats (list of strings) |
|
3752 """ |
|
3753 args = self.initCommand("archive") |
|
3754 args.append("--list") |
|
3755 |
|
3756 output = "" |
|
3757 process = QProcess() |
|
3758 process.setWorkingDirectory(repodir) |
|
3759 process.start('git', args) |
|
3760 procStarted = process.waitForStarted(5000) |
|
3761 if procStarted: |
|
3762 finished = process.waitForFinished(30000) |
|
3763 if finished and process.exitCode() == 0: |
|
3764 output = str(process.readAllStandardOutput(), |
|
3765 Preferences.getSystem("IOEncoding"), |
|
3766 'replace') |
|
3767 |
|
3768 archiveFormats = [] |
|
3769 if output: |
|
3770 for line in output.splitlines(): |
|
3771 archiveFormat = line.strip() |
|
3772 archiveFormats.append(archiveFormat) |
|
3773 |
|
3774 return archiveFormats |
|
3775 |
|
3776 def gitCreateArchive(self, projectDir): |
|
3777 """ |
|
3778 Public method to show some statistics of the local repository. |
|
3779 |
|
3780 @param projectDir name of the project directory (string) |
|
3781 """ |
|
3782 # find the root of the repo |
|
3783 repodir = projectDir |
|
3784 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3785 repodir = os.path.dirname(repodir) |
|
3786 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3787 return |
|
3788 |
|
3789 from .GitArchiveDataDialog import GitArchiveDataDialog |
|
3790 dlg = GitArchiveDataDialog( |
|
3791 self.gitGetTagsList(repodir), |
|
3792 self.gitGetBranchesList(repodir, withMaster=True), |
|
3793 self.gitGetArchiveFormats(repodir) |
|
3794 ) |
|
3795 if dlg.exec_() == QDialog.Accepted: |
|
3796 commit, archiveFormat, fileName, prefix = dlg.getData() |
|
3797 args = self.initCommand("archive") |
|
3798 args.append("--format={0}".format(archiveFormat)) |
|
3799 args.append("--output={0}".format(fileName)) |
|
3800 if prefix: |
|
3801 prefix = Utilities.fromNativeSeparators(prefix) |
|
3802 if not prefix.endswith("/"): |
|
3803 prefix += "/" |
|
3804 args.append("--prefix={0}".format(prefix)) |
|
3805 args.append(commit) |
|
3806 |
|
3807 dia = GitDialog( |
|
3808 self.tr('Creating Archive'), |
|
3809 self) |
|
3810 res = dia.startProcess(args, repodir) |
|
3811 if res: |
|
3812 dia.exec_() |
|
3813 |
|
3814 ########################################################################### |
|
3815 ## Methods related to submodules. |
|
3816 ########################################################################### |
|
3817 |
|
3818 def gitSubmoduleAdd(self, projectDir): |
|
3819 """ |
|
3820 Public method to add a submodule to the project. |
|
3821 |
|
3822 @param projectDir name of the project directory |
|
3823 @type str |
|
3824 """ |
|
3825 # find the root of the repo |
|
3826 repodir = projectDir |
|
3827 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3828 repodir = os.path.dirname(repodir) |
|
3829 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3830 return |
|
3831 |
|
3832 from .GitSubmoduleAddDialog import GitSubmoduleAddDialog |
|
3833 dlg = GitSubmoduleAddDialog(self, repodir) |
|
3834 if dlg.exec_() == QDialog.Accepted: |
|
3835 repo, branch, name, path, force = dlg.getData() |
|
3836 args = self.initCommand("submodule") |
|
3837 args.append("add") |
|
3838 if branch: |
|
3839 args.append("--branch") |
|
3840 args.append(branch) |
|
3841 if force: |
|
3842 args.append("--force") |
|
3843 if name: |
|
3844 args.append("--name") |
|
3845 args.append(name) |
|
3846 args.append(repo) |
|
3847 if path: |
|
3848 args.append(path) |
|
3849 |
|
3850 dia = GitDialog( |
|
3851 self.tr("Add Submodule"), |
|
3852 self) |
|
3853 res = dia.startProcess(args, repodir) |
|
3854 if res: |
|
3855 dia.exec_() |
|
3856 |
|
3857 def __gitSubmodulesList(self, repodir): |
|
3858 """ |
|
3859 Private method to get the data of defined submodules. |
|
3860 |
|
3861 @param repodir name of the directory containing the repo subdirectory |
|
3862 @type str |
|
3863 @return list of dictionaries with submodule name, path, URL and branch |
|
3864 @rtype list of dict |
|
3865 """ |
|
3866 submodulesFile = os.path.join(repodir, ".gitmodules") |
|
3867 if not os.path.exists(submodulesFile): |
|
3868 return [] |
|
3869 |
|
3870 try: |
|
3871 modulesFile = open(submodulesFile, "r") |
|
3872 contents = modulesFile.readlines() |
|
3873 modulesFile.close() |
|
3874 except OSError: |
|
3875 # silently ignore them |
|
3876 return [] |
|
3877 |
|
3878 submodules = [] |
|
3879 submoduleDict = None |
|
3880 for line in contents: |
|
3881 line = line.strip() |
|
3882 if line.startswith("[submodule"): |
|
3883 if submoduleDict: |
|
3884 if "branch" not in submoduleDict: |
|
3885 submoduleDict["branch"] = "" |
|
3886 submodules.append(submoduleDict) |
|
3887 submoduleDict = {"name": line.split(None, 1)[1][1:-2]} |
|
3888 elif "=" in line: |
|
3889 option, value = line.split("=", 1) |
|
3890 submoduleDict[option.strip()] = value.strip() |
|
3891 if submoduleDict: |
|
3892 if "branch" not in submoduleDict: |
|
3893 submoduleDict["branch"] = "" |
|
3894 submodules.append(submoduleDict) |
|
3895 |
|
3896 return submodules |
|
3897 |
|
3898 def gitSubmoduleList(self, projectDir): |
|
3899 """ |
|
3900 Public method to show a list of all submodules of the project. |
|
3901 |
|
3902 @param projectDir name of the project directory |
|
3903 @type str |
|
3904 """ |
|
3905 # find the root of the repo |
|
3906 repodir = projectDir |
|
3907 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3908 repodir = os.path.dirname(repodir) |
|
3909 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3910 return |
|
3911 |
|
3912 submodulesList = self.__gitSubmodulesList(repodir) |
|
3913 if submodulesList: |
|
3914 from .GitSubmodulesListDialog import GitSubmodulesListDialog |
|
3915 dlg = GitSubmodulesListDialog(submodulesList) |
|
3916 dlg.exec_() |
|
3917 else: |
|
3918 E5MessageBox.information( |
|
3919 None, |
|
3920 self.tr("List Submodules"), |
|
3921 self.tr("""No submodules defined for the project.""")) |
|
3922 |
|
3923 def __selectSubmodulePath(self, repodir): |
|
3924 """ |
|
3925 Private method to select a submodule path. |
|
3926 |
|
3927 @param repodir name of the directory containing the repo subdirectory |
|
3928 @type str |
|
3929 @return tuple of selected submodule path and flag indicating |
|
3930 a cancellation |
|
3931 @rtype tuple of (str, bool) |
|
3932 """ |
|
3933 allEntry = self.tr("All") |
|
3934 paths = [submodule["path"] |
|
3935 for submodule in self.__gitSubmodulesList(repodir)] |
|
3936 submodulePath, ok = QInputDialog.getItem( |
|
3937 None, |
|
3938 self.tr("Submodule Path"), |
|
3939 self.tr("Select a submodule path:"), |
|
3940 [allEntry] + sorted(paths), |
|
3941 0, False) |
|
3942 if submodulePath == allEntry: |
|
3943 submodulePath = "" |
|
3944 |
|
3945 return submodulePath, ok |
|
3946 |
|
3947 def __selectSubmodulePaths(self, repodir): |
|
3948 """ |
|
3949 Private method to select a list of submodule paths. |
|
3950 |
|
3951 @param repodir name of the directory containing the repo subdirectory |
|
3952 @type str |
|
3953 @return tuple of selected submodule paths and flag indicating |
|
3954 a cancellation |
|
3955 @rtype tuple of (list of str, bool) |
|
3956 """ |
|
3957 paths = [submodule["path"] |
|
3958 for submodule in self.__gitSubmodulesList(repodir)] |
|
3959 |
|
3960 from .GitListDialog import GitListDialog |
|
3961 dlg = GitListDialog(sorted(paths)) |
|
3962 if dlg.exec_() == QDialog.Accepted: |
|
3963 selectedPaths = dlg.getSelection() |
|
3964 return selectedPaths, True |
|
3965 else: |
|
3966 return [], False |
|
3967 |
|
3968 def gitSubmoduleInit(self, projectDir): |
|
3969 """ |
|
3970 Public method to initialize one or all submodules. |
|
3971 |
|
3972 @param projectDir name of the project directory |
|
3973 @type str |
|
3974 """ |
|
3975 # find the root of the repo |
|
3976 repodir = projectDir |
|
3977 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
3978 repodir = os.path.dirname(repodir) |
|
3979 if os.path.splitdrive(repodir)[1] == os.sep: |
|
3980 return |
|
3981 |
|
3982 submodulePaths, ok = self.__selectSubmodulePaths(repodir) |
|
3983 if ok: |
|
3984 args = self.initCommand("submodule") |
|
3985 args.append("init") |
|
3986 args.extend(submodulePaths) |
|
3987 |
|
3988 dia = GitDialog( |
|
3989 self.tr("Initialize Submodules"), |
|
3990 self) |
|
3991 res = dia.startProcess(args, repodir) |
|
3992 if res: |
|
3993 dia.exec_() |
|
3994 |
|
3995 def gitSubmoduleDeinit(self, projectDir): |
|
3996 """ |
|
3997 Public method to unregister submodules. |
|
3998 |
|
3999 @param projectDir name of the project directory |
|
4000 @type str |
|
4001 """ |
|
4002 # find the root of the repo |
|
4003 repodir = projectDir |
|
4004 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
4005 repodir = os.path.dirname(repodir) |
|
4006 if os.path.splitdrive(repodir)[1] == os.sep: |
|
4007 return |
|
4008 |
|
4009 paths = [submodule["path"] |
|
4010 for submodule in self.__gitSubmodulesList(repodir)] |
|
4011 |
|
4012 from .GitSubmodulesDeinitDialog import GitSubmodulesDeinitDialog |
|
4013 dlg = GitSubmodulesDeinitDialog(paths) |
|
4014 if dlg.exec_() == QDialog.Accepted: |
|
4015 deinitAll, submodulePaths, force = dlg.getData() |
|
4016 args = self.initCommand("submodule") |
|
4017 args.append("deinit") |
|
4018 if deinitAll: |
|
4019 args.append("--all") |
|
4020 else: |
|
4021 args.extend(submodulePaths) |
|
4022 if force: |
|
4023 args.append("--force") |
|
4024 |
|
4025 dia = GitDialog( |
|
4026 self.tr("Unregister Submodules"), |
|
4027 self) |
|
4028 res = dia.startProcess(args, repodir) |
|
4029 if res: |
|
4030 dia.exec_() |
|
4031 |
|
4032 def gitSubmoduleUpdate(self, projectDir, initialize=False, remote=False): |
|
4033 """ |
|
4034 Public method to update submodules. |
|
4035 |
|
4036 @param projectDir name of the project directory |
|
4037 @type str |
|
4038 @param initialize flag indicating an initialize and update operation |
|
4039 @type bool |
|
4040 @param remote flag indicating a fetch and update operation |
|
4041 @type bool |
|
4042 """ |
|
4043 # find the root of the repo |
|
4044 repodir = projectDir |
|
4045 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
4046 repodir = os.path.dirname(repodir) |
|
4047 if os.path.splitdrive(repodir)[1] == os.sep: |
|
4048 return |
|
4049 |
|
4050 submodulePaths, ok = self.__selectSubmodulePaths(repodir) |
|
4051 if ok: |
|
4052 args = self.initCommand("submodule") |
|
4053 args.append("update") |
|
4054 if initialize: |
|
4055 args.append("--init") |
|
4056 if remote: |
|
4057 args.append("--remote") |
|
4058 args.extend(submodulePaths) |
|
4059 |
|
4060 dia = GitDialog( |
|
4061 self.tr("Update Submodules"), |
|
4062 self) |
|
4063 res = dia.startProcess(args, repodir) |
|
4064 if res: |
|
4065 dia.exec_() |
|
4066 |
|
4067 def gitSubmoduleUpdateWithOptions(self, projectDir): |
|
4068 """ |
|
4069 Public method to update submodules offering a dialog to select the |
|
4070 update options. |
|
4071 |
|
4072 @param projectDir name of the project directory |
|
4073 @type str |
|
4074 """ |
|
4075 # find the root of the repo |
|
4076 repodir = projectDir |
|
4077 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
4078 repodir = os.path.dirname(repodir) |
|
4079 if os.path.splitdrive(repodir)[1] == os.sep: |
|
4080 return |
|
4081 |
|
4082 paths = [submodule["path"] |
|
4083 for submodule in self.__gitSubmodulesList(repodir)] |
|
4084 |
|
4085 from .GitSubmodulesUpdateOptionsDialog import \ |
|
4086 GitSubmodulesUpdateOptionsDialog |
|
4087 dlg = GitSubmodulesUpdateOptionsDialog(paths) |
|
4088 if dlg.exec_() == QDialog.Accepted: |
|
4089 procedure, init, remote, noFetch, force, submodulePaths = \ |
|
4090 dlg.getData() |
|
4091 |
|
4092 args = self.initCommand("submodule") |
|
4093 args.append("update") |
|
4094 args.append(procedure) |
|
4095 if init: |
|
4096 args.append("--init") |
|
4097 if remote: |
|
4098 args.append("--remote") |
|
4099 if noFetch: |
|
4100 args.append("--no-fetch") |
|
4101 if force: |
|
4102 args.append("--force") |
|
4103 args.extend(submodulePaths) |
|
4104 |
|
4105 dia = GitDialog( |
|
4106 self.tr("Update Submodules"), |
|
4107 self) |
|
4108 res = dia.startProcess(args, repodir) |
|
4109 if res: |
|
4110 dia.exec_() |
|
4111 |
|
4112 def gitSubmoduleSync(self, projectDir): |
|
4113 """ |
|
4114 Public method to synchronize submodules. |
|
4115 |
|
4116 @param projectDir name of the project directory |
|
4117 @type str |
|
4118 """ |
|
4119 # find the root of the repo |
|
4120 repodir = projectDir |
|
4121 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
4122 repodir = os.path.dirname(repodir) |
|
4123 if os.path.splitdrive(repodir)[1] == os.sep: |
|
4124 return |
|
4125 |
|
4126 paths = [submodule["path"] |
|
4127 for submodule in self.__gitSubmodulesList(repodir)] |
|
4128 |
|
4129 from .GitSubmodulesSyncDialog import GitSubmodulesSyncDialog |
|
4130 dlg = GitSubmodulesSyncDialog(paths) |
|
4131 if dlg.exec_() == QDialog.Accepted: |
|
4132 submodulePaths, recursive = dlg.getData() |
|
4133 args = self.initCommand("submodule") |
|
4134 args.append("sync") |
|
4135 if recursive: |
|
4136 args.append("--recursive") |
|
4137 args.extend(submodulePaths) |
|
4138 |
|
4139 dia = GitDialog( |
|
4140 self.tr("Synchronize Submodules"), |
|
4141 self) |
|
4142 res = dia.startProcess(args, repodir) |
|
4143 if res: |
|
4144 dia.exec_() |
|
4145 |
|
4146 def gitSubmoduleStatus(self, projectDir): |
|
4147 """ |
|
4148 Public method to show the status of the submodules. |
|
4149 |
|
4150 @param projectDir name of the project directory |
|
4151 @type str |
|
4152 """ |
|
4153 if self.submoduleStatusDialog is None: |
|
4154 from .GitSubmodulesStatusDialog import GitSubmodulesStatusDialog |
|
4155 self.submoduleStatusDialog = GitSubmodulesStatusDialog(self) |
|
4156 self.submoduleStatusDialog.show() |
|
4157 self.submoduleStatusDialog.raise_() |
|
4158 self.submoduleStatusDialog.start(projectDir) |
|
4159 |
|
4160 def gitSubmoduleSummary(self, projectDir): |
|
4161 """ |
|
4162 Public method to show the status of the submodules. |
|
4163 |
|
4164 @param projectDir name of the project directory |
|
4165 @type str |
|
4166 """ |
|
4167 # find the root of the repo |
|
4168 repodir = projectDir |
|
4169 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
4170 repodir = os.path.dirname(repodir) |
|
4171 if os.path.splitdrive(repodir)[1] == os.sep: |
|
4172 return |
|
4173 |
|
4174 paths = [submodule["path"] |
|
4175 for submodule in self.__gitSubmodulesList(repodir)] |
|
4176 |
|
4177 from .GitSubmodulesSummaryOptionsDialog import \ |
|
4178 GitSubmodulesSummaryOptionsDialog |
|
4179 dlg = GitSubmodulesSummaryOptionsDialog(paths) |
|
4180 if dlg.exec_() == QDialog.Accepted: |
|
4181 submodulePaths, superProject, index, commit, limit = dlg.getData() |
|
4182 args = self.initCommand("submodule") |
|
4183 args.append("summary") |
|
4184 if superProject: |
|
4185 args.append("--files") |
|
4186 if index: |
|
4187 args.append("--cached") |
|
4188 if limit > -1: |
|
4189 args.append("--summary-limit") |
|
4190 args.append(str(limit)) |
|
4191 if commit: |
|
4192 args.append(commit) |
|
4193 if submodulePaths: |
|
4194 args.append("--") |
|
4195 args.extend(submodulePaths) |
|
4196 |
|
4197 dia = GitDialog( |
|
4198 self.tr("Submodules Summary"), |
|
4199 self) |
|
4200 res = dia.startProcess(args, repodir) |
|
4201 if res: |
|
4202 dia.exec_() |
|
4203 |
|
4204 ########################################################################### |
|
4205 ## Methods to get the helper objects are below. |
|
4206 ########################################################################### |
|
4207 |
|
4208 def vcsGetProjectBrowserHelper(self, browser, project, |
|
4209 isTranslationsBrowser=False): |
|
4210 """ |
|
4211 Public method to instantiate a helper object for the different |
|
4212 project browsers. |
|
4213 |
|
4214 @param browser reference to the project browser object |
|
4215 @param project reference to the project object |
|
4216 @param isTranslationsBrowser flag indicating, the helper is requested |
|
4217 for the translations browser (this needs some special treatment) |
|
4218 @return the project browser helper object |
|
4219 """ |
|
4220 from .ProjectBrowserHelper import GitProjectBrowserHelper |
|
4221 return GitProjectBrowserHelper(self, browser, project, |
|
4222 isTranslationsBrowser) |
|
4223 |
|
4224 def vcsGetProjectHelper(self, project): |
|
4225 """ |
|
4226 Public method to instantiate a helper object for the project. |
|
4227 |
|
4228 @param project reference to the project object |
|
4229 @return the project helper object |
|
4230 """ |
|
4231 self.__projectHelper = self.__plugin.getProjectHelper() |
|
4232 self.__projectHelper.setObjects(self, project) |
|
4233 return self.__projectHelper |
|
4234 |
|
4235 ########################################################################### |
|
4236 ## Status Monitor Thread methods |
|
4237 ########################################################################### |
|
4238 |
|
4239 def _createStatusMonitorThread(self, interval, project): |
|
4240 """ |
|
4241 Protected method to create an instance of the VCS status monitor |
|
4242 thread. |
|
4243 |
|
4244 @param interval check interval for the monitor thread in seconds |
|
4245 (integer) |
|
4246 @param project reference to the project object (Project) |
|
4247 @return reference to the monitor thread (QThread) |
|
4248 """ |
|
4249 from .GitStatusMonitorThread import GitStatusMonitorThread |
|
4250 return GitStatusMonitorThread(interval, project, self) |