eric6/Plugins/VcsPlugins/vcsGit/GitDiffGenerator.py

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

eric ide

mercurial