patch.py: re-add the ability to use an external patch program
authorBryan O'Sullivan <bos@serpentine.com>
Tue, 17 Jul 2007 09:39:30 -0700
changeset 4900 e56c7e05c7e6
parent 4899 1b7bbc4349e7
child 4901 27414950abf5
patch.py: re-add the ability to use an external patch program This is now invoked by default only if ui.patch is set. Otherwise, we use our built-in patch. If that fails because it can't find any valid hunks, we'll fall back to trying the external patch command.
mercurial/patch.py
--- a/mercurial/patch.py	Tue Jul 17 09:39:30 2007 -0700
+++ b/mercurial/patch.py	Tue Jul 17 09:39:30 2007 -0700
@@ -15,6 +15,9 @@
 class PatchError(Exception):
     pass
 
+class NoHunks(PatchError):
+    pass
+
 # helper functions
 
 def copyfile(src, dst, basedir=None):
@@ -73,7 +76,7 @@
             ui.debug('From: %s\n' % user)
         diffs_seen = 0
         ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
-
+        message = ''
         for part in msg.walk():
             content_type = part.get_content_type()
             ui.debug('Content-Type: %s\n' % content_type)
@@ -213,25 +216,83 @@
     return (dopatch, gitpatches)
 
 def patch(patchname, ui, strip=1, cwd=None, files={}):
-    """apply the patch <patchname> to the working directory.
-    a list of patched files is returned"""
+    """apply <patchname> to the working directory.
+    returns whether patch was applied with fuzz factor."""
+    patcher = ui.config('ui', 'patch')
+    args = []
+    try:
+        if patcher:
+            return externalpatch(patcher, args, patchname, ui, strip, cwd,
+                                 files)
+        else:
+            try:
+                return internalpatch(patchname, ui, strip, cwd, files)
+            except NoHunks:
+                patcher = util.find_exe('gpatch') or util.find_exe('patch')
+                ui.debug('no valid hunks found; trying with %r instead\n' %
+                         patcher)
+                if util.needbinarypatch():
+                    args.append('--binary')
+                return externalpatch(patcher, args, patchname, ui, strip, cwd,
+                                     files)
+    except PatchError, err:
+        s = str(err)
+        if s:
+            raise util.Abort(s)
+        else:
+            raise util.Abort(_('patch failed to apply'))
+
+def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
+    """use <patcher> to apply <patchname> to the working directory.
+    returns whether patch was applied with fuzz factor."""
+
+    fuzz = False
+    if cwd:
+        args.append('-d %s' % util.shellquote(cwd))
+    fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
+                                       util.shellquote(patchname)))
+
+    for line in fp:
+        line = line.rstrip()
+        ui.note(line + '\n')
+        if line.startswith('patching file '):
+            pf = util.parse_patch_output(line)
+            printed_file = False
+            files.setdefault(pf, (None, None))
+        elif line.find('with fuzz') >= 0:
+            fuzz = True
+            if not printed_file:
+                ui.warn(pf + '\n')
+                printed_file = True
+            ui.warn(line + '\n')
+        elif line.find('saving rejects to file') >= 0:
+            ui.warn(line + '\n')
+        elif line.find('FAILED') >= 0:
+            if not printed_file:
+                ui.warn(pf + '\n')
+                printed_file = True
+            ui.warn(line + '\n')
+    code = fp.close()
+    if code:
+        raise PatchError(_("patch command failed: %s") %
+                         util.explain_exit(code)[0])
+    return fuzz
+
+def internalpatch(patchname, ui, strip, cwd, files):
+    """use builtin patch to apply <patchname> to the working directory.
+    returns whether patch was applied with fuzz factor."""
     fp = file(patchname)
-    fuzz = False
     if cwd:
         curdir = os.getcwd()
         os.chdir(cwd)
     try:
         ret = applydiff(ui, fp, files, strip=strip)
-    except PatchError, err:
-        ui.debug(err)
-        raise util.Abort(_("patch failed to apply"))
-    if cwd:
-        os.chdir(curdir)
+    finally:
+        if cwd:
+            os.chdir(curdir)
     if ret < 0:
-        raise util.Abort(_("patch failed to apply"))
-    if ret > 0:
-        fuzz = True
-    return fuzz
+        raise PatchError
+    return ret > 0
 
 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
@@ -936,7 +997,7 @@
     if rejects:
         return -1
     if hunknum == 0 and dopatch and not gitworkdone:
-        raise PatchError(_("no valid hunks found"))
+        raise NoHunks
     return err
 
 def diffopts(ui, opts={}, untrusted=False):