mercurial/patch.py
changeset 14367 468d7d1744b4
parent 14366 992a7e398ddd
child 14368 baf2807b9a7d
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: