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