# HG changeset patch # User Bryan O'Sullivan # Date 1127328248 25200 # Node ID a6ffcebd331532a3ebafa77b42c3949561cc64c1 # Parent 141951276ba1d5729a67215ae9f28fc641f87607 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. diff --git a/doc/hgrc.5.txt b/doc/hgrc.5.txt --- a/doc/hgrc.5.txt +++ b/doc/hgrc.5.txt @@ -67,20 +67,53 @@ decode/encode:: 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. diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -12,10 +12,10 @@ platform-specific details from the core. 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 @@ def filter(s, cmd): 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 to the working directory. a list of patched files is returned""" @@ -43,7 +82,7 @@ def patch(strip, patchname, ui): 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):