Merge with crew
authorBryan O'Sullivan <bos@serpentine.com>
Wed, 19 Dec 2007 19:21:30 -0800
changeset 5676 9ed6575897f7
parent 5675 a5fe27b83a4a (current diff)
parent 5673 dd3ce7515f4d (diff)
child 5677 4f977c6d3c03
Merge with crew
--- a/doc/hgrc.5.txt	Wed Dec 19 17:02:31 2007 -0500
+++ b/doc/hgrc.5.txt	Wed Dec 19 19:21:30 2007 -0800
@@ -281,7 +281,7 @@
     commit to proceed.  Non-zero status will cause the commit to fail.
     Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
   preoutgoing;;
-    Run before computing changes to send from the local repository to
+    Run before collecting changes to send from the local repository to
     another.  Non-zero status will cause failure.  This lets you
     prevent pull over http or ssh.  Also prevents against local pull,
     push (outbound) or bundle commands, but not effective, since you
--- a/hgext/mq.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/hgext/mq.py	Wed Dec 19 19:21:30 2007 -0800
@@ -603,6 +603,7 @@
     def new(self, repo, patch, *pats, **opts):
         msg = opts.get('msg')
         force = opts.get('force')
+        user = opts.get('user')
         if os.path.exists(self.join(patch)):
             raise util.Abort(_('patch "%s" already exists') % patch)
         if opts.get('include') or opts.get('exclude') or pats:
@@ -617,7 +618,7 @@
         try:
             insert = self.full_series_end()
             commitmsg = msg and msg or ("[mq]: %s" % patch)
-            n = repo.commit(commitfiles, commitmsg, match=match, force=True)
+            n = repo.commit(commitfiles, commitmsg, user, match=match, force=True)
             if n == None:
                 raise util.Abort(_("repo commit failed"))
             self.full_series[insert:insert] = [patch]
@@ -626,6 +627,8 @@
             self.series_dirty = 1
             self.applied_dirty = 1
             p = self.opener(patch, "w")
+            if user:
+                p.write("From: " + user + "\n\n")
             if msg:
                 msg = msg + "\n"
                 p.write(msg)
@@ -945,6 +948,22 @@
                     while message[mi] != comments[ci]:
                         ci += 1
                     del comments[ci]
+
+            newuser = opts.get('user')
+            if newuser:
+                # Update all references to a user in the patch header.
+                # If none found, add "From: " header.
+                needfrom = True
+                for prefix in ['# User ', 'From: ']:
+                    for i in xrange(len(comments)):
+                        if comments[i].startswith(prefix):
+                            comments[i] = prefix + newuser
+                            needfrom = False
+                            break
+                if needfrom:
+                    comments = ['From: ' + newuser, ''] + comments
+                user = newuser
+
             if msg:
                 comments.append(msg)
 
@@ -1070,9 +1089,12 @@
                 else:
                     message = msg
 
+                if not user:
+                    user = changes[1]
+
                 self.strip(repo, top, update=False,
                            backup='strip')
-                n = repo.commit(filelist, message, changes[1], match=matchfn,
+                n = repo.commit(filelist, message, user, match=matchfn,
                                 force=1)
                 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
                 self.applied_dirty = 1
@@ -1605,6 +1627,12 @@
     return q.qseries(repo, start=l-2, length=1, status='A',
                      summary=opts.get('summary'))
 
+def setupheaderopts(ui, opts):
+    def do(opt,val):
+        if not opts[opt] and opts['current' + opt]:
+            opts[opt] = val
+    do('user', ui.username())
+
 def new(ui, repo, patch, *args, **opts):
     """create a new patch
 
@@ -1623,6 +1651,7 @@
     if opts['edit']:
         message = ui.edit(message, ui.username())
     opts['msg'] = message
+    setupheaderopts(ui, opts)
     q.new(repo, patch, *args, **opts)
     q.save_dirty()
     return 0
@@ -1648,6 +1677,7 @@
         patch = q.applied[-1].name
         (message, comment, user, date, hasdiff) = q.readheaders(patch)
         message = ui.edit('\n'.join(message), user or ui.username())
+    setupheaderopts(ui, opts)
     ret = q.refresh(repo, pats, msg=message, **opts)
     q.save_dirty()
     return ret
@@ -2138,6 +2168,10 @@
 
 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
 
+headeropts = [
+    ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
+    ('u', 'user', '', _('add "From: <given user>" to patch'))]
+
 cmdtable = {
     "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
     "qclone":
@@ -2196,7 +2230,7 @@
          [('e', 'edit', None, _('edit commit message')),
           ('f', 'force', None, _('import uncommitted changes into patch')),
           ('g', 'git', None, _('use git extended diff format')),
-          ] + commands.walkopts + commands.commitopts,
+          ] + commands.walkopts + commands.commitopts + headeropts,
          _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
     "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
     "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
@@ -2219,7 +2253,7 @@
          [('e', 'edit', None, _('edit commit message')),
           ('g', 'git', None, _('use git extended diff format')),
           ('s', 'short', None, _('refresh only files already in the patch')),
-          ] + commands.walkopts + commands.commitopts,
+          ] + commands.walkopts + commands.commitopts + headeropts,
          _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
     'qrename|qmv':
         (rename, [], _('hg qrename PATCH1 [PATCH2]')),
--- a/mercurial/commands.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/mercurial/commands.py	Wed Dec 19 19:21:30 2007 -0800
@@ -2029,6 +2029,7 @@
                 forget.append(abs)
                 continue
             reason = _('has been marked for add (use -f to force removal)')
+            exact = 1 # force the message
         elif abs not in repo.dirstate:
             reason = _('is not managed')
         elif opts['after'] and not exact and abs not in deleted:
--- a/mercurial/demandimport.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/mercurial/demandimport.py	Wed Dec 19 19:21:30 2007 -0800
@@ -67,7 +67,7 @@
             return "<proxied module '%s'>" % self._data[0]
         return "<unloaded module '%s'>" % self._data[0]
     def __call__(self, *args, **kwargs):
-        raise TypeError("'unloaded module' object is not callable")
+        raise TypeError("%s object is not callable" % repr(self))
     def __getattribute__(self, attr):
         if attr in ('_data', '_extend', '_load', '_module'):
             return object.__getattribute__(self, attr)
--- a/mercurial/dispatch.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/mercurial/dispatch.py	Wed Dec 19 19:21:30 2007 -0800
@@ -133,6 +133,8 @@
 
     except util.Abort, inst:
         ui.warn(_("abort: %s\n") % inst)
+    except MemoryError:
+        ui.warn(_("abort: out of memory\n"))
     except SystemExit, inst:
         # Commands shouldn't sys.exit directly, but give a return code.
         # Just in case catch this and and pass exit code to caller.
--- a/mercurial/fancyopts.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/mercurial/fancyopts.py	Wed Dec 19 19:21:30 2007 -0800
@@ -1,35 +1,74 @@
 import getopt
 
 def fancyopts(args, options, state):
-    long = []
-    short = ''
-    map = {}
-    dt = {}
+    """
+    read args, parse options, and store options in state
+
+    each option is a tuple of:
+
+      short option or ''
+      long option
+      default value
+      description
+
+    option types include:
+
+      boolean or none - option sets variable in state to true
+      string - parameter string is stored in state
+      list - parameter string is added to a list
+      integer - parameter strings is stored as int
+      function - call function with parameter
 
-    for s, l, d, c in options:
-        pl = l.replace('-', '_')
-        map['-'+s] = map['--'+l] = pl
-        if isinstance(d, list):
-            state[pl] = d[:]
+    non-option args are returned
+    """
+    namelist = []
+    shortlist = ''
+    argmap = {}
+    defmap = {}
+
+    for short, name, default, comment in options:
+        # convert opts to getopt format
+        oname = name
+        name = name.replace('-', '_')
+
+        argmap['-' + short] = argmap['--' + oname] = name
+        defmap[name] = default
+
+        # copy defaults to state
+        if isinstance(default, list):
+            state[name] = default[:]
+        elif callable(default):
+            print "whoa", name, default
+            state[name] = None
         else:
-            state[pl] = d
-        dt[pl] = type(d)
-        if (d is not None and d is not True and d is not False and
-            not callable(d)):
-            if s: s += ':'
-            if l: l += '='
-        if s: short = short + s
-        if l: long.append(l)
+            state[name] = default
 
-    opts, args = getopt.getopt(args, short, long)
+        # does it take a parameter?
+        if not (default is None or default is True or default is False):
+            if short: short += ':'
+            if oname: oname += '='
+        if short:
+            shortlist += short
+        if name:
+            namelist.append(oname)
+
+    # parse arguments
+    opts, args = getopt.getopt(args, shortlist, namelist)
 
-    for opt, arg in opts:
-        if dt[map[opt]] is type(fancyopts): state[map[opt]](state, map[opt], arg)
-        elif dt[map[opt]] is type(1): state[map[opt]] = int(arg)
-        elif dt[map[opt]] is type(''): state[map[opt]] = arg
-        elif dt[map[opt]] is type([]): state[map[opt]].append(arg)
-        elif dt[map[opt]] is type(None): state[map[opt]] = True
-        elif dt[map[opt]] is type(False): state[map[opt]] = True
+    # transfer result to state
+    for opt, val in opts:
+        name = argmap[opt]
+        t = type(defmap[name])
+        if t is type(fancyopts):
+            state[name] = defmap[name](val)
+        elif t is type(1):
+            state[name] = int(val)
+        elif t is type(''):
+            state[name] = val
+        elif t is type([]):
+            state[name].append(val)
+        elif t is type(None) or t is type(False):
+            state[name] = True
 
+    # return unparsed args
     return args
-
--- a/mercurial/hg.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/mercurial/hg.py	Wed Dec 19 19:21:30 2007 -0800
@@ -280,13 +280,13 @@
         # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
         repo.ui.status(_("  hg update %s\n  hg update %s\n")
                        % (pl[0].rev(), repo.changectx(node).rev()))
-    return stats[3]
+    return stats[3] > 0
 
 def clean(repo, node, show_stats=True):
     """forcibly switch the working directory to node, clobbering changes"""
     stats = _merge.update(repo, node, False, True, None)
     if show_stats: _showstats(repo, stats)
-    return stats[3]
+    return stats[3] > 0
 
 def merge(repo, node, force=None, remind=True):
     """branch merge with node, resolving changes"""
@@ -301,11 +301,11 @@
                        % (pl[0].rev(), pl[1].rev()))
     elif remind:
         repo.ui.status(_("(branch merge, don't forget to commit)\n"))
-    return stats[3]
+    return stats[3] > 0
 
 def revert(repo, node, choose):
     """revert changes to revision in node without updating dirstate"""
-    return _merge.update(repo, node, False, True, choose)[3]
+    return _merge.update(repo, node, False, True, choose)[3] > 0
 
 def verify(repo):
     """verify the consistency of a repository"""
--- a/mercurial/ignore.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/mercurial/ignore.py	Wed Dec 19 19:21:30 2007 -0800
@@ -6,18 +6,21 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from i18n import _
-import util
+import util, re
+
+_commentre = None
 
 def _parselines(fp):
     for line in fp:
-        if not line.endswith('\n'):
-            line += '\n'
-        escape = False
-        for i in xrange(len(line)):
-            if escape: escape = False
-            elif line[i] == '\\': escape = True
-            elif line[i] == '#': break
-        line = line[:i].rstrip()
+        if "#" in line:
+            global _commentre
+            if not _commentre:
+                _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
+            # remove comments prefixed by an even number of escapes
+            line = _commentre.sub(r'\1', line)
+            # fixup properly escaped comments that survived the above
+            line = line.replace("\\#", "#")
+        line = line.rstrip()
         if line:
             yield line
 
--- a/mercurial/localrepo.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/mercurial/localrepo.py	Wed Dec 19 19:21:30 2007 -0800
@@ -661,6 +661,7 @@
                match=util.always, force=False, force_editor=False,
                p1=None, p2=None, extra={}, empty_ok=False):
         wlock = lock = tr = None
+        valid = 0 # don't save the dirstate if this isn't set
         try:
             commit = []
             remove = []
@@ -747,6 +748,9 @@
                         if old_exec != new_exec or old_link != new_link:
                             changed.append(f)
                     m1.set(f, new_exec, new_link)
+                    if use_dirstate:
+                        self.dirstate.normal(f)
+
                 except (OSError, IOError):
                     if use_dirstate:
                         self.ui.warn(_("trouble committing %s!\n") % f)
@@ -817,14 +821,15 @@
             if use_dirstate or update_dirstate:
                 self.dirstate.setparents(n)
                 if use_dirstate:
-                    for f in new:
-                        self.dirstate.normal(f)
                     for f in removed:
                         self.dirstate.forget(f)
+            valid = 1 # our dirstate updates are complete
 
             self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
             return n
         finally:
+            if not valid: # don't save our updated dirstate
+                self.dirstate.invalidate()
             del tr, lock, wlock
 
     def walk(self, node=None, files=[], match=util.always, badmatch=None):
--- a/mercurial/patch.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/mercurial/patch.py	Wed Dec 19 19:21:30 2007 -0800
@@ -302,24 +302,26 @@
 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
 
 class patchfile:
-    def __init__(self, ui, fname):
+    def __init__(self, ui, fname, missing=False):
         self.fname = fname
         self.ui = ui
-        try:
-            fp = file(fname, 'rb')
-            self.lines = fp.readlines()
-            self.exists = True
-        except IOError:
+        self.lines = []
+        self.exists = False
+        self.missing = missing
+        if not missing:
+            try:
+                fp = file(fname, 'rb')
+                self.lines = fp.readlines()
+                self.exists = True
+            except IOError:
+                pass
+        else:
+            self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
+
+        if not self.exists:
             dirname = os.path.dirname(fname)
             if dirname and not os.path.isdir(dirname):
-                dirs = dirname.split(os.path.sep)
-                d = ""
-                for x in dirs:
-                    d = os.path.join(d, x)
-                    if not os.path.isdir(d):
-                        os.mkdir(d)
-            self.lines = []
-            self.exists = False
+                os.makedirs(dirname)
 
         self.hash = {}
         self.dirty = 0
@@ -427,6 +429,10 @@
         if reverse:
             h.reverse()
 
+        if self.missing:
+            self.rej.append(h)
+            return -1
+
         if self.exists and h.createfile():
             self.ui.warn(_("file %s already exists\n") % self.fname)
             self.rej.append(h)
@@ -796,31 +802,32 @@
     nulla = afile_orig == "/dev/null"
     nullb = bfile_orig == "/dev/null"
     afile = pathstrip(afile_orig, strip)
-    gooda = os.path.exists(afile) and not nulla
+    gooda = not nulla and os.path.exists(afile)
     bfile = pathstrip(bfile_orig, strip)
     if afile == bfile:
         goodb = gooda
     else:
-        goodb = os.path.exists(bfile) and not nullb
+        goodb = not nullb and os.path.exists(bfile)
     createfunc = hunk.createfile
     if reverse:
         createfunc = hunk.rmfile
-    if not goodb and not gooda and not createfunc():
-        raise PatchError(_("unable to find %s or %s for patching") %
-                         (afile, bfile))
-    if gooda and goodb:
-        fname = bfile
-        if afile in bfile:
+    missing = not goodb and not gooda and not createfunc()
+    fname = None
+    if not missing:
+        if gooda and goodb:
+            fname = (afile in bfile) and afile or bfile
+        elif gooda:
             fname = afile
-    elif gooda:
-        fname = afile
-    elif not nullb:
-        fname = bfile
-        if afile in bfile:
+    
+    if not fname:
+        if not nullb:
+            fname = (afile in bfile) and afile or bfile
+        elif not nulla:
             fname = afile
-    elif not nulla:
-        fname = afile
-    return fname
+        else:
+            raise PatchError(_("undefined source and destination files"))
+        
+    return fname, missing
 
 class linereader:
     # simple class to allow pushing lines back into the input stream
@@ -838,14 +845,16 @@
             return l
         return self.fp.readline()
 
-def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
-              rejmerge=None, updatedir=None):
-    """reads a patch from fp and tries to apply it.  The dict 'changed' is
-       filled in with all of the filenames changed by the patch.  Returns 0
-       for a clean patch, -1 if any rejects were found and 1 if there was
-       any fuzz."""
+def iterhunks(ui, fp, sourcefile=None):
+    """Read a patch and yield the following events:
+    - ("file", afile, bfile, firsthunk): select a new target file.
+    - ("hunk", hunk): a new hunk is ready to be applied, follows a
+    "file" event.
+    - ("git", gitchanges): current diff is in git format, gitchanges
+    maps filenames to gitpatch records. Unique event.
+    """
 
-    def scangitpatch(fp, firstline, cwd=None):
+    def scangitpatch(fp, firstline):
         '''git patches can modify a file, then copy that file to
         a new file, but expect the source to be the unmodified form.
         So we scan the patch looking for that case so we can do
@@ -858,46 +867,28 @@
             fp = cStringIO.StringIO(fp.read())
 
         (dopatch, gitpatches) = readgitpatch(fp, firstline)
-        for gp in gitpatches:
-            if gp.op in ('COPY', 'RENAME'):
-                copyfile(gp.oldpath, gp.path, basedir=cwd)
-
         fp.seek(pos)
 
         return fp, dopatch, gitpatches
 
+    changed = {}
     current_hunk = None
-    current_file = None
     afile = ""
     bfile = ""
     state = None
     hunknum = 0
-    rejects = 0
+    emitfile = False
 
     git = False
     gitre = re.compile('diff --git (a/.*) (b/.*)')
 
     # our states
     BFILE = 1
-    err = 0
     context = None
     lr = linereader(fp)
     dopatch = True
     gitworkdone = False
 
-    def getpatchfile(afile, bfile, hunk):
-         try:
-             if sourcefile:
-                 targetfile = patchfile(ui, sourcefile)
-             else:
-                 targetfile = selectfile(afile, bfile, hunk,
-                                         strip, reverse)
-                 targetfile = patchfile(ui, targetfile)
-             return targetfile
-         except PatchError, err:
-             ui.warn(str(err) + '\n')
-             return None
-
     while True:
         newfile = False
         x = lr.readline()
@@ -906,11 +897,7 @@
         if current_hunk:
             if x.startswith('\ '):
                 current_hunk.fix_newline()
-            ret = current_file.apply(current_hunk, reverse)
-            if ret >= 0:
-                changed.setdefault(current_file.fname, (None, None))
-                if ret > 0:
-                    err = 1
+            yield 'hunk', current_hunk
             current_hunk = None
             gitworkdone = False
         if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
@@ -924,21 +911,15 @@
                 current_hunk = None
                 continue
             hunknum += 1
-            if not current_file:
-                current_file = getpatchfile(afile, bfile, current_hunk)
-                if not current_file:
-                    current_file, current_hunk = None, None
-                    rejects += 1
-                    continue
+            if emitfile:
+                emitfile = False
+                yield 'file', (afile, bfile, current_hunk)
         elif state == BFILE and x.startswith('GIT binary patch'):
             current_hunk = binhunk(changed[bfile[2:]][1])
             hunknum += 1
-            if not current_file:
-                current_file = getpatchfile(afile, bfile, current_hunk)
-                if not current_file:
-                    current_file, current_hunk = None, None
-                    rejects += 1
-                    continue
+            if emitfile:
+                emitfile = False
+                yield 'file', (afile, bfile, current_hunk)
             current_hunk.extract(fp)
         elif x.startswith('diff --git'):
             # check for git diff, scanning the whole patch file if needed
@@ -948,6 +929,7 @@
                 if not git:
                     git = True
                     fp, dopatch, gitpatches = scangitpatch(fp, x)
+                    yield 'git', gitpatches
                     for gp in gitpatches:
                         changed[gp.path] = (gp.op, gp)
                 # else error?
@@ -984,36 +966,79 @@
             bfile = parsefilename(l2)
 
         if newfile:
-            if current_file:
-                current_file.close()
-                if rejmerge:
-                    rejmerge(current_file)
-                rejects += len(current_file.rej)
+            emitfile = True
             state = BFILE
-            current_file = None
             hunknum = 0
     if current_hunk:
         if current_hunk.complete():
+            yield 'hunk', current_hunk
+        else:
+            raise PatchError(_("malformed patch %s %s") % (afile,
+                             current_hunk.desc))
+
+    if hunknum == 0 and dopatch and not gitworkdone:
+        raise NoHunks
+
+def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
+              rejmerge=None, updatedir=None):
+    """reads a patch from fp and tries to apply it.  The dict 'changed' is
+       filled in with all of the filenames changed by the patch.  Returns 0
+       for a clean patch, -1 if any rejects were found and 1 if there was
+       any fuzz."""
+
+    rejects = 0
+    err = 0
+    current_file = None
+    gitpatches = None
+
+    def closefile():
+        if not current_file:
+            return 0
+        current_file.close()
+        if rejmerge:
+            rejmerge(current_file)
+        return len(current_file.rej)
+
+    for state, values in iterhunks(ui, fp, sourcefile):
+        if state == 'hunk':
+            if not current_file:
+                continue
+            current_hunk = values
             ret = current_file.apply(current_hunk, reverse)
             if ret >= 0:
                 changed.setdefault(current_file.fname, (None, None))
                 if ret > 0:
                     err = 1
+        elif state == 'file':
+            rejects += closefile()
+            afile, bfile, first_hunk = values
+            try:
+                if sourcefile:
+                    current_file = patchfile(ui, sourcefile)
+                else:
+                    current_file, missing = selectfile(afile, bfile, first_hunk,
+                                            strip, reverse)
+                    current_file = patchfile(ui, current_file, missing)
+            except PatchError, err:
+                ui.warn(str(err) + '\n')
+                current_file, current_hunk = None, None
+                rejects += 1
+                continue
+        elif state == 'git':
+            gitpatches = values
+            for gp in gitpatches:
+                if gp.op in ('COPY', 'RENAME'):
+                    copyfile(gp.oldpath, gp.path)
+                changed[gp.path] = (gp.op, gp)                
         else:
-            fname = current_file and current_file.fname or None
-            raise PatchError(_("malformed patch %s %s") % (fname,
-                             current_hunk.desc))
-    if current_file:
-        current_file.close()
-        if rejmerge:
-            rejmerge(current_file)
-        rejects += len(current_file.rej)
-    if updatedir and git:
+            raise util.Abort(_('unsupported parser state: %s') % state)
+
+    rejects += closefile()
+
+    if updatedir and gitpatches:
         updatedir(gitpatches)
     if rejects:
         return -1
-    if hunknum == 0 and dopatch and not gitworkdone:
-        raise NoHunks
     return err
 
 def diffopts(ui, opts={}, untrusted=False):
--- a/mercurial/util.py	Wed Dec 19 17:02:31 2007 -0500
+++ b/mercurial/util.py	Wed Dec 19 19:21:30 2007 -0800
@@ -919,7 +919,15 @@
 
         def write(self, s):
             try:
-                return self.fp.write(s)
+                # This is workaround for "Not enough space" error on
+                # writing large size of data to console.
+                limit = 16000
+                l = len(s)
+                start = 0
+                while start < l:
+                    end = start + limit
+                    self.fp.write(s[start:end])
+                    start = end
             except IOError, inst:
                 if inst.errno != 0: raise
                 self.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-mq-header-from	Wed Dec 19 19:21:30 2007 -0800
@@ -0,0 +1,108 @@
+#!/bin/sh
+
+echo "[extensions]" >> $HGRCPATH
+echo "mq=" >> $HGRCPATH
+echo "[diff]" >> $HGRCPATH
+echo "nodates=true" >> $HGRCPATH
+
+
+catlog() {
+    cat .hg/patches/$1.patch | sed -e "s/^diff \-r [0-9a-f]* /diff -r ... /"
+    hg log --template "{rev}: {desc} - {author}\n"
+}
+
+
+echo ==== init
+hg init a
+cd a
+hg qinit
+
+
+echo ==== qnew -U
+hg qnew -U 1.patch
+catlog 1
+
+echo ==== qref
+echo "1" >1
+hg add
+hg qref
+catlog 1
+
+echo ==== qref -u
+hg qref -u mary
+catlog 1
+
+echo ==== qnew
+hg qnew 2.patch
+echo "2" >2
+hg add
+hg qref
+catlog 2
+
+echo ==== qref -u
+hg qref -u jane
+catlog 2
+
+
+echo ==== qnew -U -m
+hg qnew -U -m "Three" 3.patch
+catlog 3
+
+echo ==== qref
+echo "3" >3
+hg add
+hg qref
+catlog 3
+
+echo ==== qref -m
+hg qref -m "Drei"
+catlog 3
+
+echo ==== qref -u
+hg qref -u mary
+catlog 3
+
+echo ==== qref -u -m
+hg qref -u maria -m "Three (again)"
+catlog 3
+
+echo ==== qnew -m
+hg qnew -m "Four" 4.patch
+echo "4" >4
+hg add
+hg qref
+catlog 4
+
+echo ==== qref -u
+hg qref -u jane
+catlog 4
+
+
+echo ==== qnew with HG header
+hg qnew 5.patch
+hg qpop
+echo "# HG changeset patch" >>.hg/patches/5.patch
+echo "# User johndoe" >>.hg/patches/5.patch
+# Drop patch specific error line
+hg qpush 2>&1 | grep -v garbage
+catlog 5
+
+echo ==== hg qref
+echo "5" >5
+hg add
+hg qref
+catlog 5
+
+echo ==== hg qref -U
+hg qref -U
+catlog 5
+
+echo ==== hg qref -u
+hg qref -u johndeere
+catlog 5
+
+
+echo ==== "qpop -a / qpush -a"
+hg qpop -a
+hg qpush -a
+hg log --template "{rev}: {desc} - {author}\n"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-mq-header-from.out	Wed Dec 19 19:21:30 2007 -0800
@@ -0,0 +1,200 @@
+==== init
+==== qnew -U
+From: test
+
+0: [mq]: 1.patch - test
+==== qref
+adding 1
+From: test
+
+diff -r ... 1
+--- /dev/null
++++ b/1
+@@ -0,0 +1,1 @@
++1
+0: [mq]: 1.patch - test
+==== qref -u
+From: mary
+
+diff -r ... 1
+--- /dev/null
++++ b/1
+@@ -0,0 +1,1 @@
++1
+0: [mq]: 1.patch - mary
+==== qnew
+adding 2
+diff -r ... 2
+--- /dev/null
++++ b/2
+@@ -0,0 +1,1 @@
++2
+1: [mq]: 2.patch - test
+0: [mq]: 1.patch - mary
+==== qref -u
+From: jane
+
+
+diff -r ... 2
+--- /dev/null
++++ b/2
+@@ -0,0 +1,1 @@
++2
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== qnew -U -m
+From: test
+
+Three
+2: Three - test
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== qref
+adding 3
+From: test
+
+Three
+
+diff -r ... 3
+--- /dev/null
++++ b/3
+@@ -0,0 +1,1 @@
++3
+2: Three - test
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== qref -m
+From: test
+
+Drei
+
+diff -r ... 3
+--- /dev/null
++++ b/3
+@@ -0,0 +1,1 @@
++3
+2: Drei - test
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== qref -u
+From: mary
+
+Drei
+
+diff -r ... 3
+--- /dev/null
++++ b/3
+@@ -0,0 +1,1 @@
++3
+2: Drei - mary
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== qref -u -m
+From: maria
+
+Three (again)
+
+diff -r ... 3
+--- /dev/null
++++ b/3
+@@ -0,0 +1,1 @@
++3
+2: Three (again) - maria
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== qnew -m
+adding 4
+Four
+
+diff -r ... 4
+--- /dev/null
++++ b/4
+@@ -0,0 +1,1 @@
++4
+3: Four - test
+2: Three (again) - maria
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== qref -u
+From: jane
+
+Four
+
+diff -r ... 4
+--- /dev/null
++++ b/4
+@@ -0,0 +1,1 @@
++4
+3: Four - jane
+2: Three (again) - maria
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== qnew with HG header
+Now at: 4.patch
+applying 5.patch
+patch failed, unable to continue (try -v)
+patch 5.patch is empty
+Now at: 5.patch
+# HG changeset patch
+# User johndoe
+4: imported patch 5.patch - johndoe
+3: Four - jane
+2: Three (again) - maria
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== hg qref
+adding 5
+# HG changeset patch
+# User johndoe
+
+diff -r ... 5
+--- /dev/null
++++ b/5
+@@ -0,0 +1,1 @@
++5
+4: [mq]: 5.patch - johndoe
+3: Four - jane
+2: Three (again) - maria
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== hg qref -U
+# HG changeset patch
+# User test
+
+diff -r ... 5
+--- /dev/null
++++ b/5
+@@ -0,0 +1,1 @@
++5
+4: [mq]: 5.patch - test
+3: Four - jane
+2: Three (again) - maria
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== hg qref -u
+# HG changeset patch
+# User johndeere
+
+diff -r ... 5
+--- /dev/null
++++ b/5
+@@ -0,0 +1,1 @@
++5
+4: [mq]: 5.patch - johndeere
+3: Four - jane
+2: Three (again) - maria
+1: [mq]: 2.patch - jane
+0: [mq]: 1.patch - mary
+==== qpop -a / qpush -a
+Patch queue now empty
+applying 1.patch
+applying 2.patch
+applying 3.patch
+applying 4.patch
+applying 5.patch
+Now at: 5.patch
+4: imported patch 5.patch - johndeere
+3: Four - jane
+2: Three (again) - maria
+1: imported patch 2.patch - jane
+0: imported patch 1.patch - mary
--- a/tests/test-mq-missingfiles	Wed Dec 19 17:02:31 2007 -0500
+++ b/tests/test-mq-missingfiles	Wed Dec 19 19:21:30 2007 -0800
@@ -41,6 +41,8 @@
 echo % display added files
 cat a
 cat c
+echo % display rejections
+cat b.rej
 cd ..
 
 
@@ -65,5 +67,7 @@
 echo % display added files
 cat a
 cat c
+echo % display rejections
+cat b.rej
 cd ..
 
--- a/tests/test-mq-missingfiles.out	Wed Dec 19 17:02:31 2007 -0500
+++ b/tests/test-mq-missingfiles.out	Wed Dec 19 19:21:30 2007 -0800
@@ -2,24 +2,48 @@
 Patch queue now empty
 % push patch with missing target
 applying changeb
-unable to find b or b for patching
-unable to find b or b for patching
+unable to find 'b' for patching
+2 out of 2 hunks FAILED -- saving rejects to file b.rej
 patch failed, unable to continue (try -v)
 patch failed, rejects left in working dir
 Errors during apply, please fix and refresh changeb
 % display added files
 a
 c
+% display rejections
+--- b
++++ b
+@@ -1,3 +1,5 @@ a
++b
++b
+ a
+ a
+ a
+@@ -8,3 +10,5 @@ a
+ a
+ a
+ a
++c
++c
 adding b
 Patch queue now empty
 % push git patch with missing target
 applying changeb
-unable to find b or b for patching
+unable to find 'b' for patching
+1 out of 1 hunk FAILED -- saving rejects to file b.rej
 patch failed, unable to continue (try -v)
 b: No such file or directory
 b not tracked!
 patch failed, rejects left in working dir
 Errors during apply, please fix and refresh changeb
+? b.rej
 % display added files
 a
 c
+% display rejections
+--- b
++++ b
+GIT binary patch
+literal 2
+Jc${No0000400IC2
+