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