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) |