Package conary :: Package lib :: Module patch
[hide private]
[frames] | no frames]

Source Code for Module conary.lib.patch

  1  # 
  2  # Copyright (c) 2004-2008 rPath, Inc. 
  3  # 
  4  # This program is distributed under the terms of the Common Public License, 
  5  # version 1.0. A copy of this license should have been distributed with this 
  6  # source file in a file called LICENSE. If it is not present, the license 
  7  # is always available at http://www.rpath.com/permanent/licenses/CPL-1.0. 
  8  # 
  9  # This program is distributed in the hope that it will be useful, but 
 10  # without any warranty; without even the implied warranty of merchantability 
 11  # or fitness for a particular purpose. See the Common Public License for 
 12  # full details. 
 13  # 
 14  """ 
 15  Applies unified format diffs 
 16  """ 
 17   
 18  from conary.lib import fixeddifflib, log 
 19  import types 
 20   
21 -class Hunk:
22 23 # The new lines which result from this hunk are returned. This does 24 # not check for conflicts; countConflicts() should be used for that
25 - def apply(self, src, srcLine):
26 result = [] 27 fromLine = srcLine 28 for line in self.lines: 29 if line[0] == " ": 30 if fromLine >= len(src): 31 continue 32 33 result.append(src[fromLine]) 34 fromLine = fromLine + 1 35 elif line[0] == "+": 36 result.append(line[1:]) 37 elif line[0] == "-": 38 fromLine = fromLine + 1 39 40 return result
41
42 - def countConflicts(self, src, origSrcLine):
43 # returns -1 if the patch was already applied, or the number of lines 44 # which conflict 45 conflicts = 0 46 srcLen = len(src) 47 srcLine = origSrcLine 48 for line in self.lines: 49 if line[0] == " ": 50 if srcLine >= srcLen: 51 conflicts += 1 52 elif src[srcLine] != line[1:]: 53 conflicts += 1 54 srcLine += 1 55 elif line[0] == "-": 56 # - lines need to be exact matches; we don't want to erase 57 # a line which has been changed. Return that every line 58 # conflicts to ensure we don't apply this. 59 if srcLine >= srcLen: 60 conflicts = len(self.lines) 61 elif src[srcLine] != line[1:]: 62 conflicts = len(self.lines) 63 srcLine += 1 64 65 if conflicts: 66 # has this patch already been applied? 67 applied = True 68 srcLine = origSrcLine 69 for line in self.lines: 70 if line[0] == " " or line[0] == "+": 71 if srcLine >= srcLen: 72 applied = False 73 break 74 elif src[srcLine] != line[1:]: 75 applied = False 76 break 77 78 srcLine += 1 79 80 if applied: 81 return -1 82 83 return conflicts
84
85 - def write(self, f):
86 f.write(self.asString())
87
88 - def asString(self):
89 str = "@@ -%d,%s +%d,%d @@\n" % (self.fromStart, self.fromLen, 90 self.toStart, self.toLen) 91 str += "".join(self.lines) 92 return str
93
94 - def __init__(self, fromStart, fromLen, toStart, toLen, lines, contextCount):
95 self.fromStart = fromStart 96 self.toStart = toStart 97 self.fromLen = fromLen 98 self.toLen = toLen 99 self.lines = lines 100 self.contextCount = contextCount
101
102 -class FailedHunkList(list):
103
104 - def write(self, filename, oldName, newName):
105 f = open(filename, "w") 106 f.write("--- %s\n" % oldName) 107 f.write("+++ %s\n" % newName) 108 for hunk in self: 109 hunk.write(f)
110 111 # this just applies hunks, not files... in other words, the --- and +++ 112 # lines should be omitted, and only a single file can be patched
113 -def patch(oldLines, unifiedDiff):
114 i = 0 115 116 if type(unifiedDiff) == types.GeneratorType: 117 # convert to a proper list 118 unifiedDiff = [ l for l in unifiedDiff ] 119 120 # split the diff up into a set of hunks 121 last = len(unifiedDiff) 122 hunks = [] 123 while i < last: 124 (magic1, fromRange, toRange, magic2) = unifiedDiff[i].split() 125 if magic1 != "@@" or magic2 != "@@" or fromRange[0] != "-" or \ 126 toRange[0] != "+": 127 raise BadHunkHeader() 128 129 (fromStart, fromLen) = fromRange.split(",") 130 fromStart = int(fromStart[1:]) - 1 131 fromLen = int(fromLen) 132 133 (toStart, toLen) = toRange.split(",") 134 toStart = int(toStart[1:]) - 1 135 toLen = int(toLen) 136 137 fromCount = 0 138 toCount = 0 139 contextCount = 0 140 lines = [] 141 i += 1 142 while i < last and unifiedDiff[i][0] != '@': 143 lines.append(unifiedDiff[i]) 144 ch = unifiedDiff[i][0] 145 if ch == " ": 146 fromCount += 1 147 toCount += 1 148 contextCount += 1 149 elif ch == "-": 150 fromCount += 1 151 elif ch == "+": 152 toCount += 1 153 elif unifiedDiff[i] == '\ No newline at end of file\n': 154 del lines[-1] 155 lines[-1] = lines[-1][:-1] 156 else: 157 raise BadHunk() 158 159 i += 1 160 161 if toCount != toLen or fromCount != fromLen: 162 raise BadHunk() 163 164 hunks.append(Hunk(fromStart, fromLen, toStart, toLen, lines, 165 contextCount)) 166 167 i = 0 168 result = [] 169 failedHunks = FailedHunkList() 170 171 fromLine = 0 172 offset = 0 173 174 numHunks = len(hunks) 175 for idx, hunk in enumerate(hunks): 176 log.info('patch: applying hunk %s of %s', idx + 1, numHunks) 177 start = hunk.fromStart + offset 178 conflicts = hunk.countConflicts(oldLines, start) 179 best = (conflicts, 0) 180 181 i = 0 182 while best[0]: 183 i = i + 1 184 tried = 0 185 if (start - abs(i) >= 0): 186 tried = 1 187 conflicts = hunk.countConflicts(oldLines, start - i) 188 if conflicts < best[0]: 189 best = (conflicts, -i) 190 191 if not conflicts: 192 i = -i 193 break 194 195 if ((start + i) <= (len(oldLines) - hunk.fromLen)): 196 tried = 1 197 conflicts = hunk.countConflicts(oldLines, start + i) 198 if conflicts < best[0]: 199 best = (conflicts, i) 200 if not conflicts: 201 break 202 203 if not tried: 204 break 205 206 conflictCount = best[0] 207 208 if conflictCount == -1: 209 # this hunk has already been applied; skip it 210 continue 211 212 if conflictCount and (hunk.contextCount - conflictCount) < 2: 213 failedHunks.append(hunk) 214 continue 215 216 offset = best[1] 217 start += offset 218 219 while (fromLine < start): 220 result.append(oldLines[fromLine]) 221 fromLine = fromLine + 1 222 223 result += hunk.apply(oldLines, start) 224 fromLine += hunk.fromLen 225 226 while (fromLine < len(oldLines)): 227 result.append(oldLines[fromLine]) 228 fromLine = fromLine + 1 229 230 return (result, failedHunks)
231
232 -class BadHunkHeader(Exception):
233 234 pass
235
236 -class BadHunk(Exception):
237 238 pass
239
240 -def reverse(lines):
241 for line in lines: 242 if line[0] == " ": 243 yield line 244 elif line[0] == "+": 245 yield "-" + line[1:] 246 elif line[0] == "-": 247 yield "+" + line[1:] 248 elif line[0] == "@": 249 fields = line.split() 250 new = [ fields[0], "-" + fields[2][1:], 251 "+" + fields[1][1:], fields[3] ] 252 yield " ".join(new) + "\n"
253
254 -def unifiedDiff(first, second, *args, **kwargs):
255 # return a unified diff like difflib.unified_diff, but add the right magic 256 # for missing trailing newlines 257 258 diff = list(fixeddifflib.unified_diff(first, second, *args, **kwargs)) 259 missingNewLines = [] 260 for i, line in enumerate(diff): 261 if line[-1] != '\n': 262 missingNewLines.append(i) 263 264 for i in reversed(missingNewLines): 265 diff.insert(i + 1, '\ No newline at end of file\n') 266 diff[i] += '\n' 267 268 # be a drop in replacement for difflib.unified_diff 269 for line in diff: 270 yield line
271