279 def setmode(self, mode): |
279 def setmode(self, mode): |
280 islink = mode & 020000 |
280 islink = mode & 020000 |
281 isexec = mode & 0100 |
281 isexec = mode & 0100 |
282 self.mode = (islink, isexec) |
282 self.mode = (islink, isexec) |
283 |
283 |
|
284 def copy(self): |
|
285 other = patchmeta(self.path) |
|
286 other.oldpath = self.oldpath |
|
287 other.mode = self.mode |
|
288 other.op = self.op |
|
289 other.binary = self.binary |
|
290 return other |
|
291 |
284 def __repr__(self): |
292 def __repr__(self): |
285 return "<patchmeta %s %r>" % (self.op, self.path) |
293 return "<patchmeta %s %r>" % (self.op, self.path) |
286 |
294 |
287 def readgitpatch(lr): |
295 def readgitpatch(lr): |
288 """extract git-style metadata about patches from <patchname>""" |
296 """extract git-style metadata about patches from <patchname>""" |
507 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') |
515 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') |
508 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') |
516 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') |
509 eolmodes = ['strict', 'crlf', 'lf', 'auto'] |
517 eolmodes = ['strict', 'crlf', 'lf', 'auto'] |
510 |
518 |
511 class patchfile(object): |
519 class patchfile(object): |
512 def __init__(self, ui, fname, backend, store, mode, create, remove, |
520 def __init__(self, ui, gp, backend, store, eolmode='strict'): |
513 eolmode='strict', copysource=None): |
521 self.fname = gp.path |
514 self.fname = fname |
|
515 self.eolmode = eolmode |
522 self.eolmode = eolmode |
516 self.eol = None |
523 self.eol = None |
517 self.backend = backend |
524 self.backend = backend |
518 self.ui = ui |
525 self.ui = ui |
519 self.lines = [] |
526 self.lines = [] |
520 self.exists = False |
527 self.exists = False |
521 self.missing = True |
528 self.missing = True |
522 self.mode = mode |
529 self.mode = gp.mode |
523 self.copysource = copysource |
530 self.copysource = gp.oldpath |
524 self.create = create |
531 self.create = gp.op in ('ADD', 'COPY', 'RENAME') |
525 self.remove = remove |
532 self.remove = gp.op == 'DELETE' |
526 try: |
533 try: |
527 if copysource is None: |
534 if self.copysource is None: |
528 data, mode = backend.getfile(fname) |
535 data, mode = backend.getfile(self.fname) |
529 self.exists = True |
536 self.exists = True |
530 else: |
537 else: |
531 data, mode = store.getfile(copysource) |
538 data, mode = store.getfile(self.copysource) |
532 self.exists = backend.exists(fname) |
539 self.exists = backend.exists(self.fname) |
533 self.missing = False |
540 self.missing = False |
534 if data: |
541 if data: |
535 self.lines = data.splitlines(True) |
542 self.lines = data.splitlines(True) |
536 if self.mode is None: |
543 if self.mode is None: |
537 self.mode = mode |
544 self.mode = mode |
1014 while i < pathlen - 1 and path[i] == '/': |
1021 while i < pathlen - 1 and path[i] == '/': |
1015 i += 1 |
1022 i += 1 |
1016 count -= 1 |
1023 count -= 1 |
1017 return path[:i].lstrip(), path[i:].rstrip() |
1024 return path[:i].lstrip(), path[i:].rstrip() |
1018 |
1025 |
1019 def selectfile(backend, afile_orig, bfile_orig, hunk, strip, gp): |
1026 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip): |
1020 if gp: |
|
1021 # Git patches do not play games. Excluding copies from the |
|
1022 # following heuristic avoids a lot of confusion |
|
1023 fname = pathstrip(gp.path, strip - 1)[1] |
|
1024 create = gp.op in ('ADD', 'COPY', 'RENAME') |
|
1025 remove = gp.op == 'DELETE' |
|
1026 return fname, create, remove |
|
1027 nulla = afile_orig == "/dev/null" |
1027 nulla = afile_orig == "/dev/null" |
1028 nullb = bfile_orig == "/dev/null" |
1028 nullb = bfile_orig == "/dev/null" |
1029 create = nulla and hunk.starta == 0 and hunk.lena == 0 |
1029 create = nulla and hunk.starta == 0 and hunk.lena == 0 |
1030 remove = nullb and hunk.startb == 0 and hunk.lenb == 0 |
1030 remove = nullb and hunk.startb == 0 and hunk.lenb == 0 |
1031 abase, afile = pathstrip(afile_orig, strip) |
1031 abase, afile = pathstrip(afile_orig, strip) |
1132 context = True |
1137 context = True |
1133 h = hunk(x, hunknum + 1, lr, context) |
1138 h = hunk(x, hunknum + 1, lr, context) |
1134 hunknum += 1 |
1139 hunknum += 1 |
1135 if emitfile: |
1140 if emitfile: |
1136 emitfile = False |
1141 emitfile = False |
1137 yield 'file', (afile, bfile, h, gp) |
1142 yield 'file', (afile, bfile, h, gp and gp.copy() or None) |
1138 yield 'hunk', h |
1143 yield 'hunk', h |
1139 elif x.startswith('diff --git'): |
1144 elif x.startswith('diff --git'): |
1140 m = gitre.match(x) |
1145 m = gitre.match(x) |
1141 if not m: |
1146 if not m: |
1142 continue |
1147 continue |
1143 if gitpatches is None: |
1148 if gitpatches is None: |
1144 # scan whole input for git metadata |
1149 # scan whole input for git metadata |
1145 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp |
1150 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp |
1146 in scangitpatch(lr, x)] |
1151 in scangitpatch(lr, x)] |
1147 yield 'git', [g[2] for g in gitpatches |
1152 yield 'git', [g[2].copy() for g in gitpatches |
1148 if g[2].op in ('COPY', 'RENAME')] |
1153 if g[2].op in ('COPY', 'RENAME')] |
1149 gitpatches.reverse() |
1154 gitpatches.reverse() |
1150 afile = 'a/' + m.group(1) |
1155 afile = 'a/' + m.group(1) |
1151 bfile = 'b/' + m.group(2) |
1156 bfile = 'b/' + m.group(2) |
1152 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]: |
1157 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]: |
1153 gp = gitpatches.pop()[2] |
1158 gp = gitpatches.pop()[2] |
1154 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp) |
1159 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) |
1155 gp = gitpatches[-1][2] |
1160 gp = gitpatches[-1][2] |
1156 # copy/rename + modify should modify target, not source |
1161 # copy/rename + modify should modify target, not source |
1157 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode: |
1162 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode: |
1158 afile = bfile |
1163 afile = bfile |
1159 newfile = True |
1164 newfile = True |
1189 state = BFILE |
1194 state = BFILE |
1190 hunknum = 0 |
1195 hunknum = 0 |
1191 |
1196 |
1192 while gitpatches: |
1197 while gitpatches: |
1193 gp = gitpatches.pop()[2] |
1198 gp = gitpatches.pop()[2] |
1194 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp) |
1199 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) |
1195 |
1200 |
1196 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'): |
1201 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'): |
1197 """Reads a patch from fp and tries to apply it. |
1202 """Reads a patch from fp and tries to apply it. |
1198 |
1203 |
1199 Returns 0 for a clean patch, -1 if any rejects were found and 1 if |
1204 Returns 0 for a clean patch, -1 if any rejects were found and 1 if |
1226 elif state == 'file': |
1231 elif state == 'file': |
1227 if current_file: |
1232 if current_file: |
1228 rejects += current_file.close() |
1233 rejects += current_file.close() |
1229 current_file = None |
1234 current_file = None |
1230 afile, bfile, first_hunk, gp = values |
1235 afile, bfile, first_hunk, gp = values |
1231 copysource = None |
|
1232 if gp: |
1236 if gp: |
1233 path = pstrip(gp.path) |
1237 path = pstrip(gp.path) |
|
1238 gp.path = pstrip(gp.path) |
1234 if gp.oldpath: |
1239 if gp.oldpath: |
1235 copysource = pstrip(gp.oldpath) |
1240 gp.oldpath = pstrip(gp.oldpath) |
1236 if gp.op == 'RENAME': |
1241 else: |
1237 backend.unlink(copysource) |
1242 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip) |
1238 if not first_hunk: |
1243 if gp.op == 'RENAME': |
1239 if gp.op == 'DELETE': |
1244 backend.unlink(gp.oldpath) |
1240 backend.unlink(path) |
|
1241 continue |
|
1242 data, mode = None, None |
|
1243 if gp.op in ('RENAME', 'COPY'): |
|
1244 data, mode = store.getfile(copysource) |
|
1245 if gp.mode: |
|
1246 mode = gp.mode |
|
1247 if gp.op == 'ADD': |
|
1248 # Added files without content have no hunk and |
|
1249 # must be created |
|
1250 data = '' |
|
1251 if data or mode: |
|
1252 if (gp.op in ('ADD', 'RENAME', 'COPY') |
|
1253 and backend.exists(path)): |
|
1254 raise PatchError(_("cannot create %s: destination " |
|
1255 "already exists") % path) |
|
1256 backend.setfile(path, data, mode, copysource) |
|
1257 if not first_hunk: |
1245 if not first_hunk: |
|
1246 if gp.op == 'DELETE': |
|
1247 backend.unlink(gp.path) |
|
1248 continue |
|
1249 data, mode = None, None |
|
1250 if gp.op in ('RENAME', 'COPY'): |
|
1251 data, mode = store.getfile(gp.oldpath) |
|
1252 if gp.mode: |
|
1253 mode = gp.mode |
|
1254 if gp.op == 'ADD': |
|
1255 # Added files without content have no hunk and |
|
1256 # must be created |
|
1257 data = '' |
|
1258 if data or mode: |
|
1259 if (gp.op in ('ADD', 'RENAME', 'COPY') |
|
1260 and backend.exists(gp.path)): |
|
1261 raise PatchError(_("cannot create %s: destination " |
|
1262 "already exists") % gp.path) |
|
1263 backend.setfile(gp.path, data, mode, gp.oldpath) |
1258 continue |
1264 continue |
1259 try: |
1265 try: |
1260 mode = gp and gp.mode or None |
1266 current_file = patcher(ui, gp, backend, store, |
1261 current_file, create, remove = selectfile( |
1267 eolmode=eolmode) |
1262 backend, afile, bfile, first_hunk, strip, gp) |
|
1263 current_file = patcher(ui, current_file, backend, store, mode, |
|
1264 create, remove, eolmode=eolmode, |
|
1265 copysource=copysource) |
|
1266 except PatchError, inst: |
1268 except PatchError, inst: |
1267 ui.warn(str(inst) + '\n') |
1269 ui.warn(str(inst) + '\n') |
1268 current_file = None |
1270 current_file = None |
1269 rejects += 1 |
1271 rejects += 1 |
1270 continue |
1272 continue |
1393 changed = set() |
1395 changed = set() |
1394 for state, values in iterhunks(fp): |
1396 for state, values in iterhunks(fp): |
1395 if state == 'file': |
1397 if state == 'file': |
1396 afile, bfile, first_hunk, gp = values |
1398 afile, bfile, first_hunk, gp = values |
1397 if gp: |
1399 if gp: |
1398 changed.add(pathstrip(gp.path, strip - 1)[1]) |
1400 gp.path = pathstrip(gp.path, strip - 1)[1] |
1399 if gp.op == 'RENAME': |
1401 if gp.oldpath: |
1400 changed.add(pathstrip(gp.oldpath, strip - 1)[1]) |
1402 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1] |
1401 if not first_hunk: |
1403 else: |
1402 continue |
1404 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip) |
1403 current_file, create, remove = selectfile( |
1405 changed.add(gp.path) |
1404 backend, afile, bfile, first_hunk, strip, gp) |
1406 if gp.op == 'RENAME': |
1405 changed.add(current_file) |
1407 changed.add(gp.oldpath) |
1406 elif state not in ('hunk', 'git'): |
1408 elif state not in ('hunk', 'git'): |
1407 raise util.Abort(_('unsupported parser state: %s') % state) |
1409 raise util.Abort(_('unsupported parser state: %s') % state) |
1408 return changed |
1410 return changed |
1409 finally: |
1411 finally: |
1410 fp.close() |
1412 fp.close() |