changeset 1293:a6ffcebd3315

Enhance the file filtering capabilities. We now allow filtering through either pipes or pairs of temporary files. The latter appear to be mandatory for use on Windows.
author Bryan O'Sullivan <bos@serpentine.com>
date Wed, 21 Sep 2005 11:44:08 -0700
parents 141951276ba1
children 372971e1c40d
files doc/hgrc.5.txt mercurial/util.py
diffstat 2 files changed, 81 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/doc/hgrc.5.txt	Wed Sep 21 09:56:30 2005 -0700
+++ b/doc/hgrc.5.txt	Wed Sep 21 11:44:08 2005 -0700
@@ -67,20 +67,53 @@
   localization/canonicalization of files.
 
   Filters consist of a filter pattern followed by a filter command.
-  The command must accept data on stdin and return the transformed
-  data on stdout.
+  Filter patterns are globs by default, rooted at the repository
+  root.  For example, to match any file ending in ".txt" in the root
+  directory only, use the pattern "*.txt".  To match any file ending
+  in ".c" anywhere in the repository, use the pattern "**.c".
 
-  Example:
+  The filter command can start with a specifier, either "pipe:" or
+  "tempfile:".  If no specifier is given, "pipe:" is used by default.
+
+  A "pipe:" command must accept data on stdin and return the
+  transformed data on stdout.
+
+  Pipe example:
 
     [encode]
     # uncompress gzip files on checkin to improve delta compression
     # note: not necessarily a good idea, just an example
-    *.gz = gunzip
+    *.gz = pipe: gunzip
 
     [decode]
-    # recompress gzip files when writing them to the working dir
+    # recompress gzip files when writing them to the working dir (we
+    # can safely omit "pipe:", because it's the default)
     *.gz = gzip
 
+  A "tempfile:" command is a template.  The string INFILE is replaced
+  with the name of a temporary file that contains the data to be
+  filtered by the command.  The string OUTFILE is replaced with the
+  name of an empty temporary file, where the filtered data must be
+  written by the command.
+
+  NOTE: the tempfile mechanism is recommended for Windows systems,
+  where the standard shell I/O redirection operators often have
+  strange effects.  In particular, if you are doing line ending
+  conversion on Windows using the popular dos2unix and unix2dos
+  programs, you *must* use the tempfile mechanism, as using pipes will
+  corrupt the contents of your files.
+
+  Tempfile example:
+
+    [encode]
+    # convert files to unix line ending conventions on checkin
+    **.txt = tempfile: dos2unix -n INFILE OUTFILE
+
+    [decode]
+    # convert files to windows line ending conventions when writing
+    # them to the working dir
+    **.txt = tempfile: unix2dos -n INFILE OUTFILE
+
 hooks::
   Commands that get automatically executed by various actions such as
   starting or finishing a commit.
--- a/mercurial/util.py	Wed Sep 21 09:56:30 2005 -0700
+++ b/mercurial/util.py	Wed Sep 21 11:44:08 2005 -0700
@@ -12,10 +12,10 @@
 
 import os, errno
 from demandload import *
-demandload(globals(), "re cStringIO shutil popen2 threading")
+demandload(globals(), "re cStringIO shutil popen2 tempfile threading")
 
-def filter(s, cmd):
-    "filter a string through a command that transforms its input to its output"
+def pipefilter(s, cmd):
+    '''filter string S through command CMD, returning its output'''
     (pout, pin) = popen2.popen2(cmd, -1, 'b')
     def writer():
         pin.write(s)
@@ -30,6 +30,45 @@
     w.join()
     return f
 
+def tempfilter(s, cmd):
+    '''filter string S through a pair of temporary files with CMD.
+    CMD is used as a template to create the real command to be run,
+    with the strings INFILE and OUTFILE replaced by the real names of
+    the temporary files generated.'''
+    inname, outname = None, None
+    try:
+        infd, inname = tempfile.mkstemp(prefix='hgfin')
+        fp = os.fdopen(infd, 'wb')
+        fp.write(s)
+        fp.close()
+        outfd, outname = tempfile.mkstemp(prefix='hgfout')
+        os.close(outfd)
+        cmd = cmd.replace('INFILE', inname)
+        cmd = cmd.replace('OUTFILE', outname)
+        code = os.system(cmd)
+        if code: raise Abort("command '%s' failed: %s" %
+                             (cmd, explain_exit(code)))
+        return open(outname, 'rb').read()
+    finally:
+        try:
+            if inname: os.unlink(inname)
+        except: pass
+        try:
+            if outname: os.unlink(outname)
+        except: pass
+
+filtertable = {
+    'tempfile:': tempfilter,
+    'pipe:': pipefilter,
+    }
+
+def filter(s, cmd):
+    "filter a string through a command that transforms its input to its output"
+    for name, fn in filtertable.iteritems():
+        if cmd.startswith(name):
+            return fn(s, cmd[len(name):].lstrip())
+    return pipefilter(s, cmd)
+
 def patch(strip, patchname, ui):
     """apply the patch <patchname> to the working directory.
     a list of patched files is returned"""
@@ -43,7 +82,7 @@
             files.setdefault(pf, 1)
     code = fp.close()
     if code:
-        raise Abort("patch command failed: exit status %s " % code)
+        raise Abort("patch command failed: %s" % explain_exit(code))
     return files.keys()
 
 def binary(s):