src/eric7/Plugins/VcsPlugins/vcsGit/GitDiffParser.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
13 13
14 class GitDiffParser: 14 class GitDiffParser:
15 """ 15 """
16 Class implementing a class to store and parse diff output. 16 Class implementing a class to store and parse diff output.
17 """ 17 """
18 HunkHeaderRegexp = re.compile(r'^@@ -([0-9,]+) \+([0-9,]+) @@(.*)', 18
19 re.DOTALL) 19 HunkHeaderRegexp = re.compile(r"^@@ -([0-9,]+) \+([0-9,]+) @@(.*)", re.DOTALL)
20 20
21 def __init__(self, diff): 21 def __init__(self, diff):
22 """ 22 """
23 Constructor 23 Constructor
24 24
25 @param diff output of the diff command (list of string) 25 @param diff output of the diff command (list of string)
26 """ 26 """
27 self.__diff = diff[:] 27 self.__diff = diff[:]
28 28
29 self.__headerLines = [] 29 self.__headerLines = []
30 self.__hunks = [] 30 self.__hunks = []
31 self.__parsed = False 31 self.__parsed = False
32 # diff parsing is done on demand 32 # diff parsing is done on demand
33 33
34 def __initHunk(self): 34 def __initHunk(self):
35 """ 35 """
36 Private method to initialize a hunk data structure. 36 Private method to initialize a hunk data structure.
37 37
38 @return hunk data structure (dictionary) 38 @return hunk data structure (dictionary)
39 """ 39 """
40 hunk = { 40 hunk = {
41 "start": -1, 41 "start": -1,
42 "end": -1, 42 "end": -1,
46 "newStart": -1, 46 "newStart": -1,
47 "newCount": -1, 47 "newCount": -1,
48 "heading": "", 48 "heading": "",
49 } 49 }
50 return hunk 50 return hunk
51 51
52 def __parseRange(self, headerRange): 52 def __parseRange(self, headerRange):
53 """ 53 """
54 Private method to parse the hunk header range part. 54 Private method to parse the hunk header range part.
55 55
56 @param headerRange hunk header range (string) 56 @param headerRange hunk header range (string)
57 @return tuple of hunk start and hunk length (integer, integer) 57 @return tuple of hunk start and hunk length (integer, integer)
58 """ 58 """
59 if ',' in headerRange: 59 if "," in headerRange:
60 begin, end = headerRange.split(',', 1) 60 begin, end = headerRange.split(",", 1)
61 return int(begin), int(end) 61 return int(begin), int(end)
62 else: 62 else:
63 return int(headerRange), 1 63 return int(headerRange), 1
64 64
65 def __parseDiff(self): 65 def __parseDiff(self):
66 """ 66 """
67 Private method to parse the diff output. 67 Private method to parse the diff output.
68 68
69 @exception AssertionError raised when a malformed hunk header is 69 @exception AssertionError raised when a malformed hunk header is
70 encountered 70 encountered
71 """ 71 """
72 if not self.__parsed: 72 if not self.__parsed:
73 # step 1: extract the diff header 73 # step 1: extract the diff header
74 for line in self.__diff: 74 for line in self.__diff:
75 if not line.startswith("@@ "): 75 if not line.startswith("@@ "):
76 self.__headerLines.append(line) 76 self.__headerLines.append(line)
77 else: 77 else:
78 break 78 break
79 79
80 # step 2: break the rest into diff hunks 80 # step 2: break the rest into diff hunks
81 for lineIdx, line in enumerate( 81 for lineIdx, line in enumerate(self.__diff[len(self.__headerLines) :]):
82 self.__diff[len(self.__headerLines):]):
83 # disect the hunk header line 82 # disect the hunk header line
84 m = self.HunkHeaderRegexp.match(line) 83 m = self.HunkHeaderRegexp.match(line)
85 if m: 84 if m:
86 self.__hunks.append(self.__initHunk()) 85 self.__hunks.append(self.__initHunk())
87 self.__hunks[-1]["start"] = lineIdx 86 self.__hunks[-1]["start"] = lineIdx
88 (self.__hunks[-1]["oldStart"], 87 (
89 self.__hunks[-1]["oldCount"]) = self.__parseRange( 88 self.__hunks[-1]["oldStart"],
90 m.group(1)) 89 self.__hunks[-1]["oldCount"],
91 (self.__hunks[-1]["newStart"], 90 ) = self.__parseRange(m.group(1))
92 self.__hunks[-1]["newCount"]) = self.__parseRange( 91 (
93 m.group(2)) 92 self.__hunks[-1]["newStart"],
93 self.__hunks[-1]["newCount"],
94 ) = self.__parseRange(m.group(2))
94 self.__hunks[-1]["heading"] = m.group(3) 95 self.__hunks[-1]["heading"] = m.group(3)
95 elif not self.__hunks: 96 elif not self.__hunks:
96 raise AssertionError("Malformed hunk header: '{0}'" 97 raise AssertionError("Malformed hunk header: '{0}'".format(line))
97 .format(line))
98 self.__hunks[-1]["lines"].append(line) 98 self.__hunks[-1]["lines"].append(line)
99 # step 3: calculate hunk end lines 99 # step 3: calculate hunk end lines
100 for hunk in self.__hunks: 100 for hunk in self.__hunks:
101 hunk["end"] = hunk["start"] + len(hunk["lines"]) - 1 101 hunk["end"] = hunk["start"] + len(hunk["lines"]) - 1
102 102
103 def __generateRange(self, start, count): 103 def __generateRange(self, start, count):
104 """ 104 """
105 Private method to generate a hunk header range. 105 Private method to generate a hunk header range.
106 106
107 @param start start line (integer) 107 @param start start line (integer)
108 @param count line count (integer) 108 @param count line count (integer)
109 @return hunk header range (string) 109 @return hunk header range (string)
110 """ 110 """
111 if count == 1: 111 if count == 1:
112 return "{0}".format(start) 112 return "{0}".format(start)
113 else: 113 else:
114 return "{0},{1}".format(start, count) 114 return "{0},{1}".format(start, count)
115 115
116 def __generateHunkHeader(self, oldStart, oldCount, newStart, newCount, 116 def __generateHunkHeader(
117 heading=os.linesep): 117 self, oldStart, oldCount, newStart, newCount, heading=os.linesep
118 ):
118 """ 119 """
119 Private method to generate a hunk header line. 120 Private method to generate a hunk header line.
120 121
121 @param oldStart start line of the old part (integer) 122 @param oldStart start line of the old part (integer)
122 @param oldCount line count of the old part (integer) 123 @param oldCount line count of the old part (integer)
123 @param newStart start line of the new part (integer) 124 @param newStart start line of the new part (integer)
124 @param newCount line count of the new part (integer) 125 @param newCount line count of the new part (integer)
125 @param heading hunk heading (string) 126 @param heading hunk heading (string)
126 @return hunk header (string) 127 @return hunk header (string)
127 """ 128 """
128 return "@@ -{0} +{1} @@{2}".format( 129 return "@@ -{0} +{1} @@{2}".format(
129 self.__generateRange(oldStart, oldCount), 130 self.__generateRange(oldStart, oldCount),
130 self.__generateRange(newStart, newCount), 131 self.__generateRange(newStart, newCount),
131 heading) 132 heading,
132 133 )
134
133 def headerLength(self): 135 def headerLength(self):
134 """ 136 """
135 Public method to get the header length. 137 Public method to get the header length.
136 138
137 @return length of the header (integer) 139 @return length of the header (integer)
138 """ 140 """
139 self.__parseDiff() 141 self.__parseDiff()
140 return len(self.__headerLines) 142 return len(self.__headerLines)
141 143
142 def createHunkPatch(self, lineIndex): 144 def createHunkPatch(self, lineIndex):
143 """ 145 """
144 Public method to create a hunk based patch. 146 Public method to create a hunk based patch.
145 147
146 @param lineIndex line number of the hunk (integer) 148 @param lineIndex line number of the hunk (integer)
147 @return diff lines of the patch (string) 149 @return diff lines of the patch (string)
148 """ 150 """
149 self.__parseDiff() 151 self.__parseDiff()
150 152
151 patch = self.__headerLines[:] 153 patch = self.__headerLines[:]
152 for hunk in self.__hunks: 154 for hunk in self.__hunks:
153 if hunk["start"] <= lineIndex <= hunk["end"]: 155 if hunk["start"] <= lineIndex <= hunk["end"]:
154 patch.extend(hunk["lines"]) 156 patch.extend(hunk["lines"])
155 break 157 break
156 158
157 return "".join(patch) 159 return "".join(patch)
158 160
159 def createLinesPatch(self, startIndex, endIndex, reverse=False): 161 def createLinesPatch(self, startIndex, endIndex, reverse=False):
160 """ 162 """
161 Public method to create a selected lines based patch. 163 Public method to create a selected lines based patch.
162 164
163 @param startIndex start line number (integer) 165 @param startIndex start line number (integer)
164 @param endIndex end line number (integer) 166 @param endIndex end line number (integer)
165 @param reverse flag indicating a reverse patch (boolean) 167 @param reverse flag indicating a reverse patch (boolean)
166 @return diff lines of the patch (string) 168 @return diff lines of the patch (string)
167 """ 169 """
168 self.__parseDiff() 170 self.__parseDiff()
169 171
170 ADDITION = "+" 172 ADDITION = "+"
171 DELETION = "-" 173 DELETION = "-"
172 CONTEXT = " " 174 CONTEXT = " "
173 NONEWLINE = "\\" 175 NONEWLINE = "\\"
174 176
175 patch = [] 177 patch = []
176 startOffset = 0 178 startOffset = 0
177 179
178 for hunk in self.__hunks: 180 for hunk in self.__hunks:
179 if hunk["end"] < startIndex: 181 if hunk["end"] < startIndex:
180 # skip hunks before the selected lines 182 # skip hunks before the selected lines
181 continue 183 continue
182 184
183 if hunk["start"] > endIndex: 185 if hunk["start"] > endIndex:
184 # done, exit the loop 186 # done, exit the loop
185 break 187 break
186 188
187 counts = { 189 counts = {
188 ADDITION: 0, 190 ADDITION: 0,
189 DELETION: 0, 191 DELETION: 0,
190 CONTEXT: 0, 192 CONTEXT: 0,
191 } 193 }
192 previousLineSkipped = False 194 previousLineSkipped = False
193 processedLines = [] 195 processedLines = []
194 196
195 for lineIndex, line in enumerate(hunk["lines"][1:], 197 for lineIndex, line in enumerate(
196 start=hunk["start"] + 1): 198 hunk["lines"][1:], start=hunk["start"] + 1
199 ):
197 lineType = line[0] 200 lineType = line[0]
198 lineContent = line[1:] 201 lineContent = line[1:]
199 202
200 if not (startIndex <= lineIndex <= endIndex): 203 if not (startIndex <= lineIndex <= endIndex):
201 if ( 204 if (not reverse and lineType == ADDITION) or (
202 (not reverse and lineType == ADDITION) or 205 reverse and lineType == DELETION
203 (reverse and lineType == DELETION)
204 ): 206 ):
205 previousLineSkipped = True 207 previousLineSkipped = True
206 continue 208 continue
207 209
208 elif ( 210 elif (not reverse and lineType == DELETION) or (
209 (not reverse and lineType == DELETION) or 211 reverse and lineType == ADDITION
210 (reverse and lineType == ADDITION)
211 ): 212 ):
212 lineType = CONTEXT 213 lineType = CONTEXT
213 214
214 if lineType == NONEWLINE and previousLineSkipped: 215 if lineType == NONEWLINE and previousLineSkipped:
215 continue 216 continue
216 217
217 processedLines.append(lineType + lineContent) 218 processedLines.append(lineType + lineContent)
218 counts[lineType] += 1 219 counts[lineType] += 1
219 previousLineSkipped = False 220 previousLineSkipped = False
220 221
221 # hunks consisting of pure context lines are excluded 222 # hunks consisting of pure context lines are excluded
222 if counts[ADDITION] == 0 and counts[DELETION] == 0: 223 if counts[ADDITION] == 0 and counts[DELETION] == 0:
223 continue 224 continue
224 225
225 oldCount = counts[CONTEXT] + counts[DELETION] 226 oldCount = counts[CONTEXT] + counts[DELETION]
226 newCount = counts[CONTEXT] + counts[ADDITION] 227 newCount = counts[CONTEXT] + counts[ADDITION]
227 oldStart = hunk["oldStart"] 228 oldStart = hunk["oldStart"]
228 newStart = oldStart + startOffset 229 newStart = oldStart + startOffset
229 if oldCount == 0: 230 if oldCount == 0:
230 newStart += 1 231 newStart += 1
231 if newCount == 0: 232 if newCount == 0:
232 newStart -= 1 233 newStart -= 1
233 234
234 startOffset += counts[ADDITION] - counts[DELETION] 235 startOffset += counts[ADDITION] - counts[DELETION]
235 236
236 patch.append(self.__generateHunkHeader(oldStart, oldCount, 237 patch.append(
237 newStart, newCount, 238 self.__generateHunkHeader(
238 hunk["heading"])) 239 oldStart, oldCount, newStart, newCount, hunk["heading"]
240 )
241 )
239 patch.extend(processedLines) 242 patch.extend(processedLines)
240 243
241 if not patch: 244 if not patch:
242 return "" 245 return ""
243 else: 246 else:
244 return "".join(self.__headerLines[:] + patch) 247 return "".join(self.__headerLines[:] + patch)

eric ide

mercurial