mercurial/patch.py
changeset 8810 ac92775b3b80
parent 8778 c5f36402daad
child 8817 6c9dce20ed70
equal deleted inserted replaced
8809:6fce36336e42 8810:ac92775b3b80
   226     if not gitpatches:
   226     if not gitpatches:
   227         dopatch = GP_PATCH
   227         dopatch = GP_PATCH
   228 
   228 
   229     return (dopatch, gitpatches)
   229     return (dopatch, gitpatches)
   230 
   230 
       
   231 class linereader:
       
   232     # simple class to allow pushing lines back into the input stream
       
   233     def __init__(self, fp, textmode=False):
       
   234         self.fp = fp
       
   235         self.buf = []
       
   236         self.textmode = textmode
       
   237 
       
   238     def push(self, line):
       
   239         if line is not None:
       
   240             self.buf.append(line)
       
   241 
       
   242     def readline(self):
       
   243         if self.buf:
       
   244             l = self.buf[0]
       
   245             del self.buf[0]
       
   246             return l
       
   247         l = self.fp.readline()
       
   248         if self.textmode and l.endswith('\r\n'):
       
   249             l = l[:-2] + '\n'
       
   250         return l
       
   251 
       
   252     def __iter__(self):
       
   253         while 1:
       
   254             l = self.readline()
       
   255             if not l:
       
   256                 break
       
   257             yield l
       
   258 
   231 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
   259 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
   232 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
   260 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
   233 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
   261 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
   234 
   262 
   235 class patchfile(object):
   263 class patchfile(object):
   236     def __init__(self, ui, fname, opener, missing=False):
   264     def __init__(self, ui, fname, opener, missing=False, eol=None):
   237         self.fname = fname
   265         self.fname = fname
       
   266         self.eol = eol
   238         self.opener = opener
   267         self.opener = opener
   239         self.ui = ui
   268         self.ui = ui
   240         self.lines = []
   269         self.lines = []
   241         self.exists = False
   270         self.exists = False
   242         self.missing = missing
   271         self.missing = missing
   258         self.hunks = 0
   287         self.hunks = 0
   259 
   288 
   260     def readlines(self, fname):
   289     def readlines(self, fname):
   261         fp = self.opener(fname, 'r')
   290         fp = self.opener(fname, 'r')
   262         try:
   291         try:
   263             return fp.readlines()
   292             return list(linereader(fp, self.eol is not None))
   264         finally:
   293         finally:
   265             fp.close()
   294             fp.close()
   266 
   295 
   267     def writelines(self, fname, lines):
   296     def writelines(self, fname, lines):
   268         fp = self.opener(fname, 'w')
   297         fp = self.opener(fname, 'w')
   269         try:
   298         try:
   270             fp.writelines(lines)
   299             if self.eol and self.eol != '\n':
       
   300                 for l in lines:
       
   301                     if l and l[-1] == '\n':
       
   302                         l = l[:1] + self.eol
       
   303                     fp.write(l)
       
   304             else:
       
   305                 fp.writelines(lines)
   271         finally:
   306         finally:
   272             fp.close()
   307             fp.close()
   273 
   308 
   274     def unlink(self, fname):
   309     def unlink(self, fname):
   275         os.unlink(fname)
   310         os.unlink(fname)
   780         else:
   815         else:
   781             raise PatchError(_("undefined source and destination files"))
   816             raise PatchError(_("undefined source and destination files"))
   782 
   817 
   783     return fname, missing
   818     return fname, missing
   784 
   819 
   785 class linereader(object):
       
   786     # simple class to allow pushing lines back into the input stream
       
   787     def __init__(self, fp):
       
   788         self.fp = fp
       
   789         self.buf = []
       
   790 
       
   791     def push(self, line):
       
   792         if line is not None:
       
   793             self.buf.append(line)
       
   794 
       
   795     def readline(self):
       
   796         if self.buf:
       
   797             return self.buf.pop(0)
       
   798         return self.fp.readline()
       
   799 
       
   800     def __iter__(self):
       
   801         while 1:
       
   802             l = self.readline()
       
   803             if not l:
       
   804                 break
       
   805             yield l
       
   806 
       
   807 def scangitpatch(lr, firstline):
   820 def scangitpatch(lr, firstline):
   808     """
   821     """
   809     Git patches can emit:
   822     Git patches can emit:
   810     - rename a to b
   823     - rename a to b
   811     - change b
   824     - change b
   822     try:
   835     try:
   823         pos = lr.fp.tell()
   836         pos = lr.fp.tell()
   824         fp = lr.fp
   837         fp = lr.fp
   825     except IOError:
   838     except IOError:
   826         fp = cStringIO.StringIO(lr.fp.read())
   839         fp = cStringIO.StringIO(lr.fp.read())
   827     gitlr = linereader(fp)
   840     gitlr = linereader(fp, lr.textmode)
   828     gitlr.push(firstline)
   841     gitlr.push(firstline)
   829     (dopatch, gitpatches) = readgitpatch(gitlr)
   842     (dopatch, gitpatches) = readgitpatch(gitlr)
   830     fp.seek(pos)
   843     fp.seek(pos)
   831     return dopatch, gitpatches
   844     return dopatch, gitpatches
   832 
   845 
   833 def iterhunks(ui, fp, sourcefile=None):
   846 def iterhunks(ui, fp, sourcefile=None, textmode=False):
   834     """Read a patch and yield the following events:
   847     """Read a patch and yield the following events:
   835     - ("file", afile, bfile, firsthunk): select a new target file.
   848     - ("file", afile, bfile, firsthunk): select a new target file.
   836     - ("hunk", hunk): a new hunk is ready to be applied, follows a
   849     - ("hunk", hunk): a new hunk is ready to be applied, follows a
   837     "file" event.
   850     "file" event.
   838     - ("git", gitchanges): current diff is in git format, gitchanges
   851     - ("git", gitchanges): current diff is in git format, gitchanges
   839     maps filenames to gitpatch records. Unique event.
   852     maps filenames to gitpatch records. Unique event.
       
   853 
       
   854     If textmode is True, input line-endings are normalized to LF.
   840     """
   855     """
   841     changed = {}
   856     changed = {}
   842     current_hunk = None
   857     current_hunk = None
   843     afile = ""
   858     afile = ""
   844     bfile = ""
   859     bfile = ""
   848     git = False
   863     git = False
   849 
   864 
   850     # our states
   865     # our states
   851     BFILE = 1
   866     BFILE = 1
   852     context = None
   867     context = None
   853     lr = linereader(fp)
   868     lr = linereader(fp, textmode)
   854     dopatch = True
   869     dopatch = True
   855     # gitworkdone is True if a git operation (copy, rename, ...) was
   870     # gitworkdone is True if a git operation (copy, rename, ...) was
   856     # performed already for the current file. Useful when the file
   871     # performed already for the current file. Useful when the file
   857     # section may have no hunk.
   872     # section may have no hunk.
   858     gitworkdone = False
   873     gitworkdone = False
   952                              current_hunk.desc))
   967                              current_hunk.desc))
   953 
   968 
   954     if hunknum == 0 and dopatch and not gitworkdone:
   969     if hunknum == 0 and dopatch and not gitworkdone:
   955         raise NoHunks
   970         raise NoHunks
   956 
   971 
   957 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
   972 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
   958     """reads a patch from fp and tries to apply it.  The dict 'changed' is
   973               eol=None):
   959        filled in with all of the filenames changed by the patch.  Returns 0
   974     """
   960        for a clean patch, -1 if any rejects were found and 1 if there was
   975     Reads a patch from fp and tries to apply it. 
   961        any fuzz."""
   976 
   962 
   977     The dict 'changed' is filled in with all of the filenames changed
       
   978     by the patch. Returns 0 for a clean patch, -1 if any rejects were
       
   979     found and 1 if there was any fuzz.
       
   980 
       
   981     If 'eol' is None, the patch content and patched file are read in
       
   982     binary mode. Otherwise, line endings are ignored when patching then
       
   983     normalized to 'eol' (usually '\n' or \r\n').
       
   984     """
   963     rejects = 0
   985     rejects = 0
   964     err = 0
   986     err = 0
   965     current_file = None
   987     current_file = None
   966     gitpatches = None
   988     gitpatches = None
   967     opener = util.opener(os.getcwd())
   989     opener = util.opener(os.getcwd())
       
   990     textmode = eol is not None
   968 
   991 
   969     def closefile():
   992     def closefile():
   970         if not current_file:
   993         if not current_file:
   971             return 0
   994             return 0
   972         current_file.close()
   995         current_file.close()
   973         return len(current_file.rej)
   996         return len(current_file.rej)
   974 
   997 
   975     for state, values in iterhunks(ui, fp, sourcefile):
   998     for state, values in iterhunks(ui, fp, sourcefile, textmode):
   976         if state == 'hunk':
   999         if state == 'hunk':
   977             if not current_file:
  1000             if not current_file:
   978                 continue
  1001                 continue
   979             current_hunk = values
  1002             current_hunk = values
   980             ret = current_file.apply(current_hunk, reverse)
  1003             ret = current_file.apply(current_hunk, reverse)
   985         elif state == 'file':
  1008         elif state == 'file':
   986             rejects += closefile()
  1009             rejects += closefile()
   987             afile, bfile, first_hunk = values
  1010             afile, bfile, first_hunk = values
   988             try:
  1011             try:
   989                 if sourcefile:
  1012                 if sourcefile:
   990                     current_file = patchfile(ui, sourcefile, opener)
  1013                     current_file = patchfile(ui, sourcefile, opener, eol=eol)
   991                 else:
  1014                 else:
   992                     current_file, missing = selectfile(afile, bfile, first_hunk,
  1015                     current_file, missing = selectfile(afile, bfile, first_hunk,
   993                                             strip, reverse)
  1016                                             strip, reverse)
   994                     current_file = patchfile(ui, current_file, opener, missing)
  1017                     current_file = patchfile(ui, current_file, opener, missing, eol)
   995             except PatchError, err:
  1018             except PatchError, err:
   996                 ui.warn(str(err) + '\n')
  1019                 ui.warn(str(err) + '\n')
   997                 current_file, current_hunk = None, None
  1020                 current_file, current_hunk = None, None
   998                 rejects += 1
  1021                 rejects += 1
   999                 continue
  1022                 continue
  1102     if code:
  1125     if code:
  1103         raise PatchError(_("patch command failed: %s") %
  1126         raise PatchError(_("patch command failed: %s") %
  1104                          util.explain_exit(code)[0])
  1127                          util.explain_exit(code)[0])
  1105     return fuzz
  1128     return fuzz
  1106 
  1129 
  1107 def internalpatch(patchobj, ui, strip, cwd, files={}):
  1130 def internalpatch(patchobj, ui, strip, cwd, files={}, eolmode='strict'):
  1108     """use builtin patch to apply <patchobj> to the working directory.
  1131     """use builtin patch to apply <patchobj> to the working directory.
  1109     returns whether patch was applied with fuzz factor."""
  1132     returns whether patch was applied with fuzz factor."""
       
  1133 
       
  1134     if eolmode is None:
       
  1135         eolmode = ui.config('patch', 'eol', 'strict')
       
  1136     try:
       
  1137         eol = {'strict': None, 'crlf': '\r\n', 'lf': '\n'}[eolmode.lower()]
       
  1138     except KeyError:
       
  1139         raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
       
  1140             
  1110     try:
  1141     try:
  1111         fp = file(patchobj, 'rb')
  1142         fp = file(patchobj, 'rb')
  1112     except TypeError:
  1143     except TypeError:
  1113         fp = patchobj
  1144         fp = patchobj
  1114     if cwd:
  1145     if cwd:
  1115         curdir = os.getcwd()
  1146         curdir = os.getcwd()
  1116         os.chdir(cwd)
  1147         os.chdir(cwd)
  1117     try:
  1148     try:
  1118         ret = applydiff(ui, fp, files, strip=strip)
  1149         ret = applydiff(ui, fp, files, strip=strip, eol=eol)
  1119     finally:
  1150     finally:
  1120         if cwd:
  1151         if cwd:
  1121             os.chdir(curdir)
  1152             os.chdir(curdir)
  1122     if ret < 0:
  1153     if ret < 0:
  1123         raise PatchError
  1154         raise PatchError
  1124     return ret > 0
  1155     return ret > 0
  1125 
  1156 
  1126 def patch(patchname, ui, strip=1, cwd=None, files={}):
  1157 def patch(patchname, ui, strip=1, cwd=None, files={}, eolmode='strict'):
  1127     """apply <patchname> to the working directory.
  1158     """Apply <patchname> to the working directory.
  1128     returns whether patch was applied with fuzz factor."""
  1159 
       
  1160     'eolmode' specifies how end of lines should be handled. It can be:
       
  1161     - 'strict': inputs are read in binary mode, EOLs are preserved
       
  1162     - 'crlf': EOLs are ignored when patching and reset to CRLF
       
  1163     - 'lf': EOLs are ignored when patching and reset to LF
       
  1164     - None: get it from user settings, default to 'strict'
       
  1165     'eolmode' is ignored when using an external patcher program.
       
  1166 
       
  1167     Returns whether patch was applied with fuzz factor.
       
  1168     """
  1129     patcher = ui.config('ui', 'patch')
  1169     patcher = ui.config('ui', 'patch')
  1130     args = []
  1170     args = []
  1131     try:
  1171     try:
  1132         if patcher:
  1172         if patcher:
  1133             return externalpatch(patcher, args, patchname, ui, strip, cwd,
  1173             return externalpatch(patcher, args, patchname, ui, strip, cwd,
  1134                                  files)
  1174                                  files)
  1135         else:
  1175         else:
  1136             try:
  1176             try:
  1137                 return internalpatch(patchname, ui, strip, cwd, files)
  1177                 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
  1138             except NoHunks:
  1178             except NoHunks:
  1139                 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
  1179                 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
  1140                 ui.debug(_('no valid hunks found; trying with %r instead\n') %
  1180                 ui.debug(_('no valid hunks found; trying with %r instead\n') %
  1141                          patcher)
  1181                          patcher)
  1142                 if util.needbinarypatch():
  1182                 if util.needbinarypatch():