mercurial/patch.py
changeset 14348 c1c719103392
parent 14347 e8debe1eb255
child 14349 776ae95b8835
equal deleted inserted replaced
14347:e8debe1eb255 14348:c1c719103392
   379             l = self.readline()
   379             l = self.readline()
   380             if not l:
   380             if not l:
   381                 break
   381                 break
   382             yield l
   382             yield l
   383 
   383 
   384 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
   384 class abstractbackend(object):
   385 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
   385     def __init__(self, ui):
   386 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
   386         self.ui = ui
   387 eolmodes = ['strict', 'crlf', 'lf', 'auto']
   387 
   388 
   388     def readlines(self, fname):
   389 class patchfile(object):
   389         """Return target file lines, or its content as a single line
   390     def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
   390         for symlinks.
   391         self.fname = fname
   391         """
   392         self.eolmode = eolmode
   392         raise NotImplementedError
   393         self.eol = None
   393 
       
   394     def writelines(self, fname, lines):
       
   395         """Write lines to target file."""
       
   396         raise NotImplementedError
       
   397 
       
   398     def unlink(self, fname):
       
   399         """Unlink target file."""
       
   400         raise NotImplementedError
       
   401 
       
   402     def writerej(self, fname, failed, total, lines):
       
   403         """Write rejected lines for fname. total is the number of hunks
       
   404         which failed to apply and total the total number of hunks for this
       
   405         files.
       
   406         """
       
   407         pass
       
   408 
       
   409 class fsbackend(abstractbackend):
       
   410     def __init__(self, ui, opener):
       
   411         super(fsbackend, self).__init__(ui)
   394         self.opener = opener
   412         self.opener = opener
   395         self.ui = ui
       
   396         self.lines = []
       
   397         self.exists = False
       
   398         self.missing = missing
       
   399         if not missing:
       
   400             try:
       
   401                 self.lines = self.readlines(fname)
       
   402                 self.exists = True
       
   403             except IOError:
       
   404                 pass
       
   405         else:
       
   406             self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
       
   407 
       
   408         self.hash = {}
       
   409         self.dirty = False
       
   410         self.offset = 0
       
   411         self.skew = 0
       
   412         self.rej = []
       
   413         self.fileprinted = False
       
   414         self.printfile(False)
       
   415         self.hunks = 0
       
   416 
   413 
   417     def readlines(self, fname):
   414     def readlines(self, fname):
   418         if os.path.islink(fname):
   415         if os.path.islink(fname):
   419             return [os.readlink(fname)]
   416             return [os.readlink(fname)]
   420         fp = self.opener(fname, 'r')
   417         fp = self.opener(fname, 'r')
   421         try:
   418         try:
   422             lr = linereader(fp, self.eolmode != 'strict')
   419             return list(fp)
   423             lines = list(lr)
       
   424             self.eol = lr.eol
       
   425             return lines
       
   426         finally:
   420         finally:
   427             fp.close()
   421             fp.close()
   428 
   422 
   429     def writelines(self, fname, lines):
   423     def writelines(self, fname, lines):
   430         # Ensure supplied data ends in fname, being a regular file or
   424         # Ensure supplied data ends in fname, being a regular file or
   440             except OSError, e:
   434             except OSError, e:
   441                 if e.errno != errno.ENOENT:
   435                 if e.errno != errno.ENOENT:
   442                     raise
   436                     raise
   443             fp = self.opener(fname, 'w')
   437             fp = self.opener(fname, 'w')
   444         try:
   438         try:
   445             if self.eolmode == 'auto':
   439             fp.writelines(lines)
   446                 eol = self.eol
       
   447             elif self.eolmode == 'crlf':
       
   448                 eol = '\r\n'
       
   449             else:
       
   450                 eol = '\n'
       
   451 
       
   452             if self.eolmode != 'strict' and eol and eol != '\n':
       
   453                 for l in lines:
       
   454                     if l and l[-1] == '\n':
       
   455                         l = l[:-1] + eol
       
   456                     fp.write(l)
       
   457             else:
       
   458                 fp.writelines(lines)
       
   459             if islink:
   440             if islink:
   460                 self.opener.symlink(fp.getvalue(), fname)
   441                 self.opener.symlink(fp.getvalue(), fname)
   461             if st_mode is not None:
   442             if st_mode is not None:
   462                 os.chmod(fname, st_mode)
   443                 os.chmod(fname, st_mode)
   463         finally:
   444         finally:
   464             fp.close()
   445             fp.close()
   465 
   446 
   466     def unlink(self, fname):
   447     def unlink(self, fname):
   467         os.unlink(fname)
   448         os.unlink(fname)
       
   449 
       
   450     def writerej(self, fname, failed, total, lines):
       
   451         fname = fname + ".rej"
       
   452         self.ui.warn(
       
   453             _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
       
   454             (failed, total, fname))
       
   455         fp = self.opener(fname, 'w')
       
   456         fp.writelines(lines)
       
   457         fp.close()
       
   458 
       
   459 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
       
   460 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
       
   461 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
       
   462 eolmodes = ['strict', 'crlf', 'lf', 'auto']
       
   463 
       
   464 class patchfile(object):
       
   465     def __init__(self, ui, fname, backend, missing=False, eolmode='strict'):
       
   466         self.fname = fname
       
   467         self.eolmode = eolmode
       
   468         self.eol = None
       
   469         self.backend = backend
       
   470         self.ui = ui
       
   471         self.lines = []
       
   472         self.exists = False
       
   473         self.missing = missing
       
   474         if not missing:
       
   475             try:
       
   476                 self.lines = self.backend.readlines(fname)
       
   477                 if self.lines:
       
   478                     # Normalize line endings
       
   479                     if self.lines[0].endswith('\r\n'):
       
   480                         self.eol = '\r\n'
       
   481                     elif self.lines[0].endswith('\n'):
       
   482                         self.eol = '\n'
       
   483                     if eolmode != 'strict':
       
   484                         nlines = []
       
   485                         for l in self.lines:
       
   486                             if l.endswith('\r\n'):
       
   487                                 l = l[:-2] + '\n'
       
   488                             nlines.append(l)
       
   489                         self.lines = nlines
       
   490                 self.exists = True
       
   491             except IOError:
       
   492                 pass
       
   493         else:
       
   494             self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
       
   495 
       
   496         self.hash = {}
       
   497         self.dirty = 0
       
   498         self.offset = 0
       
   499         self.skew = 0
       
   500         self.rej = []
       
   501         self.fileprinted = False
       
   502         self.printfile(False)
       
   503         self.hunks = 0
       
   504 
       
   505     def writelines(self, fname, lines):
       
   506         if self.eolmode == 'auto':
       
   507             eol = self.eol
       
   508         elif self.eolmode == 'crlf':
       
   509             eol = '\r\n'
       
   510         else:
       
   511             eol = '\n'
       
   512 
       
   513         if self.eolmode != 'strict' and eol and eol != '\n':
       
   514             rawlines = []
       
   515             for l in lines:
       
   516                 if l and l[-1] == '\n':
       
   517                     l = l[:-1] + eol
       
   518                 rawlines.append(l)
       
   519             lines = rawlines
       
   520 
       
   521         self.backend.writelines(fname, lines)
   468 
   522 
   469     def printfile(self, warn):
   523     def printfile(self, warn):
   470         if self.fileprinted:
   524         if self.fileprinted:
   471             return
   525             return
   472         if warn or self.ui.verbose:
   526         if warn or self.ui.verbose:
   501     def write_rej(self):
   555     def write_rej(self):
   502         # our rejects are a little different from patch(1).  This always
   556         # our rejects are a little different from patch(1).  This always
   503         # creates rejects in the same form as the original patch.  A file
   557         # creates rejects in the same form as the original patch.  A file
   504         # header is inserted so that you can run the reject through patch again
   558         # header is inserted so that you can run the reject through patch again
   505         # without having to type the filename.
   559         # without having to type the filename.
   506 
       
   507         if not self.rej:
   560         if not self.rej:
   508             return
   561             return
   509 
   562         self.backend.writerej(self.fname, len(self.rej), self.hunks,
   510         fname = self.fname + ".rej"
   563                               self.makerejlines(self.fname))
   511         self.ui.warn(
       
   512             _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
       
   513             (len(self.rej), self.hunks, fname))
       
   514 
       
   515         fp = self.opener(fname, 'w')
       
   516         fp.writelines(self.makerejlines(self.fname))
       
   517         fp.close()
       
   518 
   564 
   519     def apply(self, h):
   565     def apply(self, h):
   520         if not h.complete():
   566         if not h.complete():
   521             raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
   567             raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
   522                             (h.number, h.desc, len(h.a), h.lena, len(h.b),
   568                             (h.number, h.desc, len(h.a), h.lena, len(h.b),
   533             self.rej.append(h)
   579             self.rej.append(h)
   534             return -1
   580             return -1
   535 
   581 
   536         if isinstance(h, binhunk):
   582         if isinstance(h, binhunk):
   537             if h.rmfile():
   583             if h.rmfile():
   538                 self.unlink(self.fname)
   584                 self.backend.unlink(self.fname)
   539             else:
   585             else:
   540                 self.lines[:] = h.new()
   586                 self.lines[:] = h.new()
   541                 self.offset += len(h.new())
   587                 self.offset += len(h.new())
   542                 self.dirty = True
   588                 self.dirty = True
   543             return 0
   589             return 0
   561         # if there's skew we want to emit the "(offset %d lines)" even
   607         # if there's skew we want to emit the "(offset %d lines)" even
   562         # when the hunk cleanly applies at start + skew, so skip the
   608         # when the hunk cleanly applies at start + skew, so skip the
   563         # fast case code
   609         # fast case code
   564         if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
   610         if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
   565             if h.rmfile():
   611             if h.rmfile():
   566                 self.unlink(self.fname)
   612                 self.backend.unlink(self.fname)
   567             else:
   613             else:
   568                 self.lines[start : start + h.lena] = h.new()
   614                 self.lines[start : start + h.lena] = h.new()
   569                 self.offset += h.lenb - h.lena
   615                 self.offset += h.lenb - h.lena
   570                 self.dirty = True
   616                 self.dirty = True
   571             return 0
   617             return 0
  1110 def _applydiff(ui, fp, patcher, copyfn, changed, strip=1, eolmode='strict'):
  1156 def _applydiff(ui, fp, patcher, copyfn, changed, strip=1, eolmode='strict'):
  1111     rejects = 0
  1157     rejects = 0
  1112     err = 0
  1158     err = 0
  1113     current_file = None
  1159     current_file = None
  1114     cwd = os.getcwd()
  1160     cwd = os.getcwd()
  1115     opener = scmutil.opener(cwd)
  1161     backend = fsbackend(ui, scmutil.opener(cwd))
  1116 
  1162 
  1117     for state, values in iterhunks(fp):
  1163     for state, values in iterhunks(fp):
  1118         if state == 'hunk':
  1164         if state == 'hunk':
  1119             if not current_file:
  1165             if not current_file:
  1120                 continue
  1166                 continue
  1128                 rejects += current_file.close()
  1174                 rejects += current_file.close()
  1129             afile, bfile, first_hunk = values
  1175             afile, bfile, first_hunk = values
  1130             try:
  1176             try:
  1131                 current_file, missing = selectfile(afile, bfile,
  1177                 current_file, missing = selectfile(afile, bfile,
  1132                                                    first_hunk, strip)
  1178                                                    first_hunk, strip)
  1133                 current_file = patcher(ui, current_file, opener,
  1179                 current_file = patcher(ui, current_file, backend,
  1134                                        missing=missing, eolmode=eolmode)
  1180                                        missing=missing, eolmode=eolmode)
  1135             except PatchError, inst:
  1181             except PatchError, inst:
  1136                 ui.warn(str(inst) + '\n')
  1182                 ui.warn(str(inst) + '\n')
  1137                 current_file = None
  1183                 current_file = None
  1138                 rejects += 1
  1184                 rejects += 1