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: