Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/patch.py @ 14367:468d7d1744b4
patch: set desired mode when patching, not in updatedir()
This patch and the following aim at merging _updatedir() actions into
_applydiff().
author | Patrick Mezard <pmezard@gmail.com> |
---|---|
date | Wed, 18 May 2011 23:48:13 +0200 |
parents | 992a7e398ddd |
children | baf2807b9a7d |
comparison
equal
deleted
inserted
replaced
14366:992a7e398ddd | 14367:468d7d1744b4 |
---|---|
370 """Return target file lines, or its content as a single line | 370 """Return target file lines, or its content as a single line |
371 for symlinks. | 371 for symlinks. |
372 """ | 372 """ |
373 raise NotImplementedError | 373 raise NotImplementedError |
374 | 374 |
375 def writelines(self, fname, lines): | 375 def writelines(self, fname, lines, mode): |
376 """Write lines to target file.""" | 376 """Write lines to target file. mode is a (islink, isexec) |
377 tuple, or None if there is no mode information. | |
378 """ | |
377 raise NotImplementedError | 379 raise NotImplementedError |
378 | 380 |
379 def unlink(self, fname): | 381 def unlink(self, fname): |
380 """Unlink target file.""" | 382 """Unlink target file.""" |
381 raise NotImplementedError | 383 raise NotImplementedError |
393 directory. | 395 directory. |
394 """ | 396 """ |
395 raise NotImplementedError | 397 raise NotImplementedError |
396 | 398 |
397 def exists(self, fname): | 399 def exists(self, fname): |
400 raise NotImplementedError | |
401 | |
402 def setmode(self, fname, islink, isexec): | |
403 """Change target file mode.""" | |
398 raise NotImplementedError | 404 raise NotImplementedError |
399 | 405 |
400 class fsbackend(abstractbackend): | 406 class fsbackend(abstractbackend): |
401 def __init__(self, ui, basedir): | 407 def __init__(self, ui, basedir): |
402 super(fsbackend, self).__init__(ui) | 408 super(fsbackend, self).__init__(ui) |
412 try: | 418 try: |
413 return list(fp) | 419 return list(fp) |
414 finally: | 420 finally: |
415 fp.close() | 421 fp.close() |
416 | 422 |
417 def writelines(self, fname, lines): | 423 def writelines(self, fname, lines, mode): |
418 # Ensure supplied data ends in fname, being a regular file or | 424 if not mode: |
419 # a symlink. _updatedir will -too magically- take care | 425 # Preserve mode information |
420 # of setting it to the proper type afterwards. | 426 isexec, islink = False, False |
421 st_mode = None | |
422 islink = os.path.islink(self._join(fname)) | |
423 if islink: | |
424 fp = cStringIO.StringIO() | |
425 else: | |
426 try: | 427 try: |
427 st_mode = os.lstat(self._join(fname)).st_mode & 0777 | 428 isexec = os.lstat(self._join(fname)).st_mode & 0100 != 0 |
429 islink = os.path.islink(self._join(fname)) | |
428 except OSError, e: | 430 except OSError, e: |
429 if e.errno != errno.ENOENT: | 431 if e.errno != errno.ENOENT: |
430 raise | 432 raise |
431 fp = self.opener(fname, 'w') | 433 else: |
432 try: | 434 islink, isexec = mode |
433 fp.writelines(lines) | 435 if islink: |
434 if islink: | 436 self.opener.symlink(''.join(lines), fname) |
435 self.opener.symlink(fp.getvalue(), fname) | 437 else: |
436 if st_mode is not None: | 438 self.opener(fname, 'w').writelines(lines) |
437 os.chmod(self._join(fname), st_mode) | 439 if isexec: |
438 finally: | 440 util.setflags(self._join(fname), False, True) |
439 fp.close() | |
440 | 441 |
441 def unlink(self, fname): | 442 def unlink(self, fname): |
442 os.unlink(self._join(fname)) | 443 os.unlink(self._join(fname)) |
443 | 444 |
444 def writerej(self, fname, failed, total, lines): | 445 def writerej(self, fname, failed, total, lines): |
468 util.copyfile(abssrc, absdst) | 469 util.copyfile(abssrc, absdst) |
469 | 470 |
470 def exists(self, fname): | 471 def exists(self, fname): |
471 return os.path.lexists(self._join(fname)) | 472 return os.path.lexists(self._join(fname)) |
472 | 473 |
474 def setmode(self, fname, islink, isexec): | |
475 util.setflags(self._join(fname), islink, isexec) | |
476 | |
473 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 | 477 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 |
474 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') | 478 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') |
475 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') | 479 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') |
476 eolmodes = ['strict', 'crlf', 'lf', 'auto'] | 480 eolmodes = ['strict', 'crlf', 'lf', 'auto'] |
477 | 481 |
478 class patchfile(object): | 482 class patchfile(object): |
479 def __init__(self, ui, fname, backend, missing=False, eolmode='strict'): | 483 def __init__(self, ui, fname, backend, mode, missing=False, |
484 eolmode='strict'): | |
480 self.fname = fname | 485 self.fname = fname |
481 self.eolmode = eolmode | 486 self.eolmode = eolmode |
482 self.eol = None | 487 self.eol = None |
483 self.backend = backend | 488 self.backend = backend |
484 self.ui = ui | 489 self.ui = ui |
485 self.lines = [] | 490 self.lines = [] |
486 self.exists = False | 491 self.exists = False |
487 self.missing = missing | 492 self.missing = missing |
493 self.mode = mode | |
488 if not missing: | 494 if not missing: |
489 try: | 495 try: |
490 self.lines = self.backend.readlines(fname) | 496 self.lines = self.backend.readlines(fname) |
491 if self.lines: | 497 if self.lines: |
492 # Normalize line endings | 498 # Normalize line endings |
514 self.rej = [] | 520 self.rej = [] |
515 self.fileprinted = False | 521 self.fileprinted = False |
516 self.printfile(False) | 522 self.printfile(False) |
517 self.hunks = 0 | 523 self.hunks = 0 |
518 | 524 |
519 def writelines(self, fname, lines): | 525 def writelines(self, fname, lines, mode): |
520 if self.eolmode == 'auto': | 526 if self.eolmode == 'auto': |
521 eol = self.eol | 527 eol = self.eol |
522 elif self.eolmode == 'crlf': | 528 elif self.eolmode == 'crlf': |
523 eol = '\r\n' | 529 eol = '\r\n' |
524 else: | 530 else: |
530 if l and l[-1] == '\n': | 536 if l and l[-1] == '\n': |
531 l = l[:-1] + eol | 537 l = l[:-1] + eol |
532 rawlines.append(l) | 538 rawlines.append(l) |
533 lines = rawlines | 539 lines = rawlines |
534 | 540 |
535 self.backend.writelines(fname, lines) | 541 self.backend.writelines(fname, lines, mode) |
536 | 542 |
537 def printfile(self, warn): | 543 def printfile(self, warn): |
538 if self.fileprinted: | 544 if self.fileprinted: |
539 return | 545 return |
540 if warn or self.ui.verbose: | 546 if warn or self.ui.verbose: |
668 self.rej.append(horig) | 674 self.rej.append(horig) |
669 return -1 | 675 return -1 |
670 | 676 |
671 def close(self): | 677 def close(self): |
672 if self.dirty: | 678 if self.dirty: |
673 self.writelines(self.fname, self.lines) | 679 self.writelines(self.fname, self.lines, self.mode) |
674 self.write_rej() | 680 self.write_rej() |
675 return len(self.rej) | 681 return len(self.rej) |
676 | 682 |
677 class hunk(object): | 683 class hunk(object): |
678 def __init__(self, desc, num, lr, context, create=False, remove=False): | 684 def __init__(self, desc, num, lr, context, create=False, remove=False): |
1085 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE' | 1091 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE' |
1086 h = hunk(x, hunknum + 1, lr, context, create, remove) | 1092 h = hunk(x, hunknum + 1, lr, context, create, remove) |
1087 hunknum += 1 | 1093 hunknum += 1 |
1088 if emitfile: | 1094 if emitfile: |
1089 emitfile = False | 1095 emitfile = False |
1090 yield 'file', (afile, bfile, h) | 1096 yield 'file', (afile, bfile, h, gpatch and gpatch.mode or None) |
1091 yield 'hunk', h | 1097 yield 'hunk', h |
1092 elif state == BFILE and x.startswith('GIT binary patch'): | 1098 elif state == BFILE and x.startswith('GIT binary patch'): |
1093 h = binhunk(changed[bfile]) | 1099 gpatch = changed[bfile] |
1100 h = binhunk(gpatch) | |
1094 hunknum += 1 | 1101 hunknum += 1 |
1095 if emitfile: | 1102 if emitfile: |
1096 emitfile = False | 1103 emitfile = False |
1097 yield 'file', ('a/' + afile, 'b/' + bfile, h) | 1104 yield 'file', ('a/' + afile, 'b/' + bfile, h, |
1105 gpatch and gpatch.mode or None) | |
1098 h.extract(lr) | 1106 h.extract(lr) |
1099 yield 'hunk', h | 1107 yield 'hunk', h |
1100 elif x.startswith('diff --git'): | 1108 elif x.startswith('diff --git'): |
1101 # check for git diff, scanning the whole patch file if needed | 1109 # check for git diff, scanning the whole patch file if needed |
1102 m = gitre.match(x) | 1110 m = gitre.match(x) |
1179 if ret > 0: | 1187 if ret > 0: |
1180 err = 1 | 1188 err = 1 |
1181 elif state == 'file': | 1189 elif state == 'file': |
1182 if current_file: | 1190 if current_file: |
1183 rejects += current_file.close() | 1191 rejects += current_file.close() |
1184 afile, bfile, first_hunk = values | 1192 afile, bfile, first_hunk, mode = values |
1185 try: | 1193 try: |
1186 current_file, missing = selectfile(backend, afile, bfile, | 1194 current_file, missing = selectfile(backend, afile, bfile, |
1187 first_hunk, strip) | 1195 first_hunk, strip) |
1188 current_file = patcher(ui, current_file, backend, | 1196 current_file = patcher(ui, current_file, backend, mode, |
1189 missing=missing, eolmode=eolmode) | 1197 missing=missing, eolmode=eolmode) |
1190 except PatchError, inst: | 1198 except PatchError, inst: |
1191 ui.warn(str(inst) + '\n') | 1199 ui.warn(str(inst) + '\n') |
1192 current_file = None | 1200 current_file = None |
1193 rejects += 1 | 1201 rejects += 1 |
1205 else: | 1213 else: |
1206 raise util.Abort(_('unsupported parser state: %s') % state) | 1214 raise util.Abort(_('unsupported parser state: %s') % state) |
1207 | 1215 |
1208 if current_file: | 1216 if current_file: |
1209 rejects += current_file.close() | 1217 rejects += current_file.close() |
1218 | |
1219 # Handle mode changes without hunk | |
1220 for gp in changed.itervalues(): | |
1221 if not gp or not gp.mode: | |
1222 continue | |
1223 if gp.op == 'ADD' and not backend.exists(gp.path): | |
1224 # Added files without content have no hunk and must be created | |
1225 backend.writelines(gp.path, [], gp.mode) | |
1226 else: | |
1227 backend.setmode(gp.path, gp.mode[0], gp.mode[1]) | |
1210 | 1228 |
1211 if rejects: | 1229 if rejects: |
1212 return -1 | 1230 return -1 |
1213 return err | 1231 return err |
1214 | 1232 |
1238 for src, dst in copies: | 1256 for src, dst in copies: |
1239 scmutil.dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd) | 1257 scmutil.dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd) |
1240 if (not similarity) and removes: | 1258 if (not similarity) and removes: |
1241 wctx.remove(sorted(removes), True) | 1259 wctx.remove(sorted(removes), True) |
1242 | 1260 |
1243 for f in patches: | |
1244 gp = patches[f] | |
1245 if gp and gp.mode: | |
1246 islink, isexec = gp.mode | |
1247 dst = repo.wjoin(gp.path) | |
1248 # patch won't create empty files | |
1249 if gp.op == 'ADD' and not os.path.lexists(dst): | |
1250 flags = (isexec and 'x' or '') + (islink and 'l' or '') | |
1251 repo.wwrite(gp.path, '', flags) | |
1252 util.setflags(dst, islink, isexec) | |
1253 scmutil.addremove(repo, cfiles, similarity=similarity) | 1261 scmutil.addremove(repo, cfiles, similarity=similarity) |
1254 files = patches.keys() | 1262 files = patches.keys() |
1255 files.extend([r for r in removes if r not in files]) | 1263 files.extend([r for r in removes if r not in files]) |
1256 return sorted(files) | 1264 return sorted(files) |
1257 | 1265 |
1357 changed = set() | 1365 changed = set() |
1358 for state, values in iterhunks(fp): | 1366 for state, values in iterhunks(fp): |
1359 if state == 'hunk': | 1367 if state == 'hunk': |
1360 continue | 1368 continue |
1361 elif state == 'file': | 1369 elif state == 'file': |
1362 afile, bfile, first_hunk = values | 1370 afile, bfile, first_hunk, mode = values |
1363 current_file, missing = selectfile(backend, afile, bfile, | 1371 current_file, missing = selectfile(backend, afile, bfile, |
1364 first_hunk, strip) | 1372 first_hunk, strip) |
1365 changed.add(current_file) | 1373 changed.add(current_file) |
1366 elif state == 'git': | 1374 elif state == 'git': |
1367 for gp in values: | 1375 for gp in values: |