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