diff --git a/meldapp.py b/meldapp.py
index 2e0434a..1ee7e58 100644
--- a/meldapp.py
+++ b/meldapp.py
@@ -32,6 +32,7 @@ import filediff
 import vcview
 import dirdiff
 import task
+import patchutil
 
 # optional
 sourceview_available = 0
@@ -498,6 +499,8 @@ class MeldApp(gnomeglade.GnomeApp):
         self.idle_hooked = 0
         self.scheduler = task.LifoScheduler()
         self.scheduler.connect("runnable", self.on_scheduler_runnable )
+        # FIXME: self.patches is a hack to ease cleanup
+        self.patches = []
         self.widget.set_default_size(self.prefs.window_size_x, self.prefs.window_size_y)
         self.widget.show()
 
@@ -598,6 +601,8 @@ class MeldApp(gnomeglade.GnomeApp):
                     break
         for c in self.notebook.get_children():
             c.get_data("pyobject").on_quit_event()
+        for p in self.patches:
+            p.cleanup()
         self.quit()
         return gtk.RESPONSE_CLOSE
 
@@ -729,7 +734,42 @@ class MeldApp(gnomeglade.GnomeApp):
     def append_diff(self, paths):
         aredirs = [ os.path.isdir(p) for p in paths ]
         arefiles = [ os.path.isfile(p) for p in paths ]
-        if (1 in aredirs) and (1 in arefiles):
+        dontexist = [not d and not f for (d, f) in zip(aredirs, arefiles)]
+
+        if True in dontexist:
+            misc.run_dialog(_("\"%s\" does not exist.\n") % paths[dontexist.index(True)],
+                    parent = self,
+                    buttonstype = gtk.BUTTONS_OK)
+            return
+
+        # FIXME: should use mime-type detection here, but extension will do for now
+        ispatch = lambda x: x[-6:] == ".patch"
+        if len(paths) == 2 and arefiles[1] and ispatch(paths[1]):
+            # FIXME: need some way to indicate to the user that the patched versions are temporary, and are going to be deleted
+            # FIXME: duplicate code
+            if arefiles[0]:
+                patch = patchutil.Patch(paths[1], paths[0])
+                patched = patch.apply()
+                if patched:
+                    self.patches.append(patch)
+                    return self.append_filediff([paths[0], patched])
+                else:
+                    misc.run_dialog(_("Could not apply patch.\n"),
+                            parent = self,
+                            buttonstype = gtk.BUTTONS_CLOSE)
+                    return
+            elif aredirs[0]:
+                patch = patchutil.Patch(paths[1], paths[0])
+                patched = patch.apply()
+                if patched:
+                    self.patches.append(patch)
+                    return self.append_dirdiff([paths[0], patched])
+                else:
+                    misc.run_dialog(_("Could not apply patch.\n"),
+                            parent = self,
+                            buttonstype = gtk.BUTTONS_CLOSE)
+                    return
+        elif (1 in aredirs) and (1 in arefiles):
             misc.run_dialog( _("Cannot compare a mixture of files and directories.\n"),
                     parent = self,
                     buttonstype = gtk.BUTTONS_OK)
diff --git a/patchutil.py b/patchutil.py
new file mode 100644
index 0000000..ac519d9
--- /dev/null
+++ b/patchutil.py
@@ -0,0 +1,142 @@
+
+import os
+import subprocess
+import tempfile
+import shutil
+
+# FIXME: while I don't think it's likely to be a big issue, we could
+# be better at confining patch files to be inside the temp directory
+# by making sure that all relative file paths contained no '..', etc.
+
+# FIXME: a lot of this code assumes set-once state. For example, you
+# *can't* change the source and expect things like cleanup to work
+
+class Patch(object):
+    def __init__(self, patch, source):
+        """Create a Patch object to apply patch to source
+
+        This object represents a patch intended to be applied to source, which
+        can be either a file or directory.
+        """
+        # FIXME: should all be private
+        self.filename = os.path.abspath(patch)
+        self.source = os.path.abspath(source)
+        self.patched_file = None
+        self.patched_dir = None
+
+    def cleanup(self):
+        if self.patched_file:
+            self._remove_temp_file(self.patched_file)
+            self._remove_temp_file(self.patched_file + ".orig")
+        self._remove_temp_dir(self.patched_dir)
+
+    def _remove_temp_file(self, filename):
+        if not filename:
+            return
+        f = os.path.abspath(filename)
+        if os.path.exists(f) and os.path.isfile(f):
+            os.remove(f)
+
+    def _remove_temp_dir(self, d):
+        # Simple sanity check
+        temp_dir = tempfile.gettempdir()
+        isintemp = lambda x: os.path.commonprefix([temp_dir, x]) == temp_dir
+        if not d:
+            return
+        temp_dir = tempfile.gettempdir()
+        for f in self.get_affected_files():
+            if not isintemp(os.path.join(d, f)):
+                continue
+            self._remove_temp_file(os.path.join(d, f))
+            self._remove_temp_file(os.path.join(d, f + ".orig"))
+            self._remove_temp_file(os.path.join(d, f + ".rej"))
+            f_dir = os.path.join(d, os.path.dirname(f))
+            if os.path.exists(f_dir):
+                try:
+                    os.removedirs(f_dir)
+                except OSError, e:
+                    pass
+
+    def get_affected_files(self):
+        patchlevel = self.guess_p_level()
+        args = ["diffstat", "-l", "-p %d" % patchlevel, "%s" % self.filename]
+        proc = subprocess.Popen(args, stdout=subprocess.PIPE)
+        output = proc.communicate()[0]
+        if proc.returncode != 0:
+            return []
+        else:
+            files = [line for line in output.split("\n") if line != ""]
+            return files
+
+    def guess_p_level(self):
+        if self.source == None or os.path.isfile(self.source):
+            return 1
+        # FIXME
+        return 1
+
+    def apply(self):
+        """Returns the path to the patched source(s)"""
+        if os.path.isfile(self.source):
+            if len(self.get_affected_files()) > 1:
+                return None
+            else:
+                return self.make_patched_file()
+        elif os.path.isdir(self.source):
+            return self.make_patched_dir()
+        else:
+            return None
+
+    def duplicate_patched_files(self, toplevel):
+        if not os.path.isdir(self.source):
+            return False
+        source_files = self.get_affected_files()
+        try:
+            for f in source_files:
+                target_dir = os.path.join(toplevel, os.path.dirname(f))
+                if not os.path.exists(target_dir):
+                    os.makedirs(target_dir)
+                source_file = os.path.join(self.source, f)
+                # This seems silly, but otherwise we crash and burn with new files in a patch
+                if os.path.exists(source_file):
+                    shutil.copy(source_file, target_dir)
+        except (IOError, os.error), e:
+            return False
+        return True
+
+    # FIXME: these patch command lines don't prohibit asking for user input, and they should
+
+    def make_patched_file(self):
+        (fd, patched_file) = tempfile.mkstemp(prefix="meld")
+        patched_file = os.path.abspath(patched_file)
+        try:
+            patch_file_tmpl = "-s -i %s -o %s %s"
+            args = patch_file_tmpl % (self.filename, patched_file, self.source)
+            retcode = subprocess.call("patch " + args, shell=True)
+            if retcode != 0:
+                raise subprocess.CalledProcessError('Patch failed', None)
+        except (OSError, subprocess.CalledProcessError), e:
+            self._remove_temp_file(patched_file)
+            self._remove_temp_file(patched_file + ".rej")
+            return None
+        self.patched_file = patched_file
+        return self.patched_file
+
+    def make_patched_dir(self):
+        # FIXME: can be slow. would be nice to have:
+        # (a) a gui up, (b) a progress indicator
+        patched_dir = tempfile.mkdtemp(prefix="meld")
+        if not self.duplicate_patched_files(patched_dir):
+            return None
+        try:
+            patchlevel = self.guess_p_level()
+            patch_dir_tmpl = "-s -p %d -i %s -d %s"
+            args = patch_dir_tmpl % (patchlevel, self.filename, patched_dir)
+            retcode = subprocess.call("patch " + args, shell=True)
+            if retcode != 0:
+                raise subprocess.CalledProcessError('Patch failed', None)
+        except (OSError, subprocess.CalledProcessError), e:
+            self._remove_temp_dir(patched_dir)
+            return None
+        self.patched_dir = patched_dir
+        return self.patched_dir
+
