Plugins/VcsPlugins/vcsGit/GitDiffGenerator.py

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

eric ide

mercurial