eric7/Plugins/VcsPlugins/vcsGit/GitDiffGenerator.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a class to generate the output of the git diff command
8 process.
9 """
10
11 import os
12
13 from PyQt5.QtCore import pyqtSignal, QProcess, QTimer, QObject
14
15 from E5Gui.E5OverrideCursor import E5OverrideCursorProcess
16
17 import Preferences
18
19
20 class GitDiffGenerator(QObject):
21 """
22 Class implementing the generation of output of the git diff command.
23
24 @signal finished() emitted when all processes have finished
25 """
26 finished = pyqtSignal()
27
28 def __init__(self, vcs, parent=None):
29 """
30 Constructor
31
32 @param vcs reference to the vcs object
33 @param parent parent widget (QWidget)
34 """
35 super().__init__(parent)
36
37 self.vcs = vcs
38
39 self.__process = E5OverrideCursorProcess()
40 self.__process.finished.connect(self.__procFinished)
41 self.__process.readyReadStandardOutput.connect(
42 lambda: self.__readStdout(self.__process))
43 self.__process.readyReadStandardError.connect(
44 lambda: self.__readStderr(self.__process))
45
46 self.__process2 = E5OverrideCursorProcess()
47 self.__process2.finished.connect(self.__procFinished)
48 self.__process2.readyReadStandardOutput.connect(
49 lambda: self.__readStdout(self.__process2))
50 self.__process2.readyReadStandardError.connect(
51 lambda: self.__readStderr(self.__process2))
52
53 def stopProcesses(self):
54 """
55 Public slot to stop the diff processes.
56 """
57 for process in [self.__process, self.__process2]:
58 if (
59 process is not None and
60 process.state() != QProcess.ProcessState.NotRunning
61 ):
62 process.terminate()
63 QTimer.singleShot(2000, process.kill)
64 process.waitForFinished(3000)
65
66 def start(self, fn, versions=None, diffMode="work2repo", stashName=""):
67 """
68 Public slot to start the git diff command.
69
70 @param fn filename to be diffed (string)
71 @param versions list of versions to be diffed (list of up to 2 strings
72 or None)
73 @param diffMode indication for the type of diff to be performed (
74 'work2repo' compares the working tree with the HEAD commit,
75 'work2stage' compares the working tree with the staging area,
76 'stage2repo' compares the staging area with the HEAD commit,
77 'work2stage2repo' compares the working tree with the staging area
78 and the staging area with the HEAD commit,
79 'stash' shows the diff for a stash)
80 @param stashName name of the stash to show a diff for (string)
81 @return flag indicating the start status (boolean)
82 @exception ValueError raised to indicate a bad value for the 'diffMode'
83 parameter.
84 """
85 if diffMode not in ["work2repo", "work2stage", "stage2repo",
86 "work2stage2repo", "stash"]:
87 raise ValueError("Bad value for 'diffMode' parameter.")
88
89 self.__output1 = []
90 self.__output2 = []
91 self.__errors = []
92 self.__fileSeparators = []
93 args2 = []
94
95 self.__ioEncoding = Preferences.getSystem("IOEncoding")
96
97 if diffMode in ["work2repo", "work2stage", "stage2repo",
98 "work2stage2repo"]:
99 args = self.vcs.initCommand("diff")
100 args.append("--patch")
101 args.append("--find-copies-harder")
102
103 if versions is not None:
104 for version in versions:
105 if version:
106 args.append(version)
107 else:
108 if diffMode == "work2stage2repo":
109 args2 = args[:]
110 args2.append("--cached")
111 args2.append("--")
112 elif diffMode == "stage2repo":
113 args.append("--cached")
114 elif diffMode == "work2repo":
115 args.append("HEAD")
116
117 args.append("--")
118 if isinstance(fn, list):
119 dname, fnames = self.vcs.splitPathList(fn)
120 self.vcs.addArguments(args, fn)
121 if args2:
122 self.vcs.addArguments(args2, fn)
123 else:
124 dname, fname = self.vcs.splitPath(fn)
125 args.append(fn)
126 if args2:
127 args2.append(fn)
128 elif diffMode == "stash":
129 dname, fname = self.vcs.splitPath(fn)
130 args = self.vcs.initCommand("stash")
131 args.append("show")
132 args.append("--patch")
133 if stashName:
134 args.append(stashName)
135
136 # find the root of the repo
137 repodir = dname
138 while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
139 repodir = os.path.dirname(repodir)
140 if os.path.splitdrive(repodir)[1] == os.sep:
141 return False
142
143 self.__process.kill()
144 self.__process.setWorkingDirectory(repodir)
145 self.__process.start('git', args)
146 procStarted = self.__process.waitForStarted(5000)
147 if not procStarted:
148 return False
149
150 if diffMode == "work2stage2repo":
151 self.__process2.kill()
152 self.__process2.setWorkingDirectory(repodir)
153 self.__process2.start('git', args2)
154 procStarted = self.__process2.waitForStarted(5000)
155 if not procStarted:
156 return False
157
158 return True
159
160 def __procFinished(self, exitCode, exitStatus):
161 """
162 Private slot connected to the finished signal.
163
164 @param exitCode exit code of the process (integer)
165 @param exitStatus exit status of the process (QProcess.ExitStatus)
166 """
167 if (
168 self.__process.state() == QProcess.ProcessState.NotRunning and
169 self.__process2.state() == QProcess.ProcessState.NotRunning
170 ):
171 self.finished.emit()
172
173 def getResult(self):
174 """
175 Public method to return the result data.
176
177 @return tuple of lists of string containing lines of the diff, the diff
178 between stage and repo for 'work2stage2repo' mode (empty
179 otherwise), the list of errors and a list of tuples of filenames
180 and the line into the diff output.
181 """
182 return (self.__output1, self.__output2, self.__errors,
183 self.__fileSeparators)
184
185 def __processFileLine(self, line, isTopDiff):
186 """
187 Private slot to process a line giving the old/new file.
188
189 @param line line to be processed (string)
190 @param isTopDiff flag indicating to show the output in the top
191 output widget (boolean)
192 """
193 prefix, filenames = line.split(" a/", 1)
194 oldFile, newFile = filenames.split(" b/", 1)
195 if isTopDiff:
196 self.__fileSeparators.append((oldFile.strip(), newFile.strip(),
197 len(self.__output1), -2))
198 else:
199 self.__fileSeparators.append((oldFile.strip(), newFile.strip(),
200 -2, len(self.__output2)))
201
202 def __processLine(self, line, isTopDiff):
203 """
204 Private method to process one line of output.
205
206 @param line output line to process (string)
207 @param isTopDiff flag indicating to show the output in the top
208 output widget (boolean)
209 """
210 if line.startswith("diff --git"):
211 self.__processFileLine(line, isTopDiff)
212
213 if isTopDiff:
214 self.__output1.append(line)
215 else:
216 self.__output2.append(line)
217
218 def __readStdout(self, process):
219 """
220 Private slot to handle the readyReadStandardOutput signal.
221
222 It reads the output of the process, formats it and inserts it into
223 the contents pane.
224
225 @param process reference to the process providing output
226 @type QProcess
227 """
228 process.setReadChannel(QProcess.ProcessChannel.StandardOutput)
229
230 isTopDiff = process == self.__process
231
232 while process.canReadLine():
233 line = str(process.readLine(), self.__ioEncoding,
234 'replace')
235 self.__processLine(line, isTopDiff)
236
237 def __readStderr(self, process):
238 """
239 Private slot to handle the readyReadStandardError signal.
240
241 It reads the error output of the process and inserts it into the
242 error pane.
243
244 @param process reference to the process providing error output
245 @type QProcess
246 """
247 s = str(process.readAllStandardError(), self.__ioEncoding,
248 'replace')
249 self.__errors.append(s)

eric ide

mercurial