merge.
authorVadim Gelfer <vadim.gelfer@gmail.com>
Fri, 18 Aug 2006 17:02:38 -0700
changeset 2952 6ba3409f9725
parent 2951 5ddf7d305a27 (current diff)
parent 2950 f7bed40e259a (diff)
child 2954 51ba31494c69
merge.
mercurial/patch.py
--- a/CONTRIBUTORS	Thu Aug 17 21:13:35 2006 +0300
+++ b/CONTRIBUTORS	Fri Aug 18 17:02:38 2006 -0700
@@ -4,6 +4,7 @@
 Muli Ben-Yehuda <mulix at mulix.org>
 Mikael Berthe <mikael at lilotux.net>
 Benoit Boissinot <bboissin at gmail.com>
+Brendan Cully <brendan at kublai.com>
 Vincent Danjean <vdanjean.ml at free.fr>
 Jake Edge <jake at edge2.net>
 Michael Fetterman <michael.fetterman at intel.com>
--- a/hgext/mq.py	Thu Aug 17 21:13:35 2006 +0300
+++ b/hgext/mq.py	Fri Aug 18 17:02:38 2006 -0700
@@ -252,6 +252,9 @@
 
         for line in file(pf):
             line = line.rstrip()
+            if line.startswith('diff --git'):
+                diffstart = 2
+                break
             if diffstart:
                 if line.startswith('+++ '):
                     diffstart = 2
@@ -298,8 +301,10 @@
         return (message, comments, user, date, diffstart > 1)
 
     def printdiff(self, repo, node1, node2=None, files=None,
-                  fp=None, changes=None, opts=None):
-        patch.diff(repo, node1, node2, files,
+                  fp=None, changes=None, opts={}):
+        fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
+
+        patch.diff(repo, node1, node2, fns, match=matchfn,
                    fp=fp, changes=changes, opts=self.diffopts())
 
     def mergeone(self, repo, mergeq, head, patch, rev, wlock):
@@ -408,7 +413,7 @@
                 self.ui.warn("patch failed, unable to continue (try -v)\n")
             return (False, [], False)
 
-        return (True, files.keys(), fuzz)
+        return (True, files, fuzz)
 
     def apply(self, repo, series, list=False, update_status=True,
               strict=False, patchdir=None, merge=None, wlock=None):
@@ -421,42 +426,37 @@
         lock = repo.lock()
         tr = repo.transaction()
         n = None
-        for patch in series:
-            pushable, reason = self.pushable(patch)
+        for patchname in series:
+            pushable, reason = self.pushable(patchname)
             if not pushable:
-                self.explain_pushable(patch, all_patches=True)
+                self.explain_pushable(patchname, all_patches=True)
                 continue
-            self.ui.warn("applying %s\n" % patch)
-            pf = os.path.join(patchdir, patch)
+            self.ui.warn("applying %s\n" % patchname)
+            pf = os.path.join(patchdir, patchname)
 
             try:
-                message, comments, user, date, patchfound = self.readheaders(patch)
+                message, comments, user, date, patchfound = self.readheaders(patchname)
             except:
-                self.ui.warn("Unable to read %s\n" % pf)
+                self.ui.warn("Unable to read %s\n" % patchname)
                 err = 1
                 break
 
             if not message:
-                message = "imported patch %s\n" % patch
+                message = "imported patch %s\n" % patchname
             else:
                 if list:
-                    message.append("\nimported patch %s" % patch)
+                    message.append("\nimported patch %s" % patchname)
                 message = '\n'.join(message)
 
             (patcherr, files, fuzz) = self.patch(repo, pf)
             patcherr = not patcherr
 
-            if merge and len(files) > 0:
+            if merge and files:
                 # Mark as merged and update dirstate parent info
-                repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
+                repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
                 p1, p2 = repo.dirstate.parents()
                 repo.dirstate.setparents(p1, merge)
-            if len(files) > 0:
-                cwd = repo.getcwd()
-                cfiles = files
-                if cwd:
-                    cfiles = [util.pathto(cwd, f) for f in files]
-                cmdutil.addremove(repo, cfiles, wlock=wlock)
+            files = patch.updatedir(self.ui, repo, files, wlock=wlock)
             n = repo.commit(files, message, user, date, force=1, lock=lock,
                             wlock=wlock)
 
@@ -464,11 +464,11 @@
                 raise util.Abort(_("repo commit failed"))
 
             if update_status:
-                self.applied.append(statusentry(revlog.hex(n), patch))
+                self.applied.append(statusentry(revlog.hex(n), patchname))
 
             if patcherr:
                 if not patchfound:
-                    self.ui.warn("patch %s is empty\n" % patch)
+                    self.ui.warn("patch %s is empty\n" % patchname)
                     err = 0
                 else:
                     self.ui.warn("patch failed, rejects left in working dir\n")
@@ -904,15 +904,15 @@
         else:
             self.ui.write("Patch queue now empty\n")
 
-    def diff(self, repo, files):
+    def diff(self, repo, pats, opts):
         top = self.check_toppatch(repo)
         if not top:
             self.ui.write("No patches applied\n")
             return
         qp = self.qparents(repo, top)
-        self.printdiff(repo, qp, files=files)
+        self.printdiff(repo, qp, files=pats, opts=opts)
 
-    def refresh(self, repo, msg='', short=False):
+    def refresh(self, repo, pats=None, **opts):
         if len(self.applied) == 0:
             self.ui.write("No patches applied\n")
             return
@@ -925,7 +925,7 @@
         message, comments, user, date, patchfound = self.readheaders(patch)
 
         patchf = self.opener(patch, "w")
-        msg = msg.rstrip()
+        msg = opts.get('msg', '').rstrip()
         if msg:
             if comments:
                 # Remove existing message.
@@ -939,6 +939,7 @@
             comments = "\n".join(comments) + '\n\n'
             patchf.write(comments)
 
+        fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
         tip = repo.changelog.tip()
         if top == tip:
             # if the top of our patch queue is also the tip, there is an
@@ -956,7 +957,7 @@
             # caching against the next repo.status call
             #
             mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
-            if short:
+            if opts.get('short'):
                 filelist = mm + aa + dd
             else:
                 filelist = None
@@ -992,16 +993,27 @@
             m = list(util.unique(mm))
             r = list(util.unique(dd))
             a = list(util.unique(aa))
-            filelist = list(util.unique(m + r + a))
+            filelist = filter(matchfn, util.unique(m + r + a))
             self.printdiff(repo, patchparent, files=filelist,
                            changes=(m, a, r, [], u), fp=patchf)
             patchf.close()
 
             changes = repo.changelog.read(tip)
             repo.dirstate.setparents(*cparents)
+            copies = [(f, repo.dirstate.copied(f)) for f in a]
             repo.dirstate.update(a, 'a')
+            for dst, src in copies:
+                repo.dirstate.copy(src, dst)
             repo.dirstate.update(r, 'r')
+            # if the patch excludes a modified file, mark that file with mtime=0
+            # so status can see it.
+            mm = []
+            for i in range(len(m)-1, -1, -1):
+                if not matchfn(m[i]):
+                    mm.append(m[i])
+                    del m[i]
             repo.dirstate.update(m, 'n')
+            repo.dirstate.update(mm, 'n', st_mtime=0)
             repo.dirstate.forget(forget)
 
             if not msg:
@@ -1216,7 +1228,7 @@
         if not self.ui.verbose:
             p = pname
         else:
-            p = str(self.series.index(pname)) + " " + p
+            p = str(self.series.index(pname)) + " " + pname
         return p
 
     def top(self, repo):
@@ -1411,17 +1423,24 @@
     changes unless -f is specified, in which case the patch will
     be initialised with them.
 
-    -m or -l set the patch header as well as the commit message.
-    If neither is specified, the patch header is empty and the
+    -e, -m or -l set the patch header as well as the commit message.
+    If none is specified, the patch header is empty and the
     commit message is 'New patch: PATCH'"""
     q = repo.mq
     message = commands.logmessage(opts)
+    if opts['edit']:
+        message = ui.edit(message, ui.username())
     q.new(repo, patch, msg=message, force=opts['force'])
     q.save_dirty()
     return 0
 
-def refresh(ui, repo, **opts):
-    """update the current patch"""
+def refresh(ui, repo, *pats, **opts):
+    """update the current patch
+
+    If any file patterns are provided, the refreshed patch will contain only
+    the modifications that match those patterns; the remaining modifications
+    will remain in the working directory.
+    """
     q = repo.mq
     message = commands.logmessage(opts)
     if opts['edit']:
@@ -1430,14 +1449,13 @@
         patch = q.applied[-1].name
         (message, comment, user, date, hasdiff) = q.readheaders(patch)
         message = ui.edit('\n'.join(message), user or ui.username())
-    q.refresh(repo, msg=message, short=opts['short'])
+    q.refresh(repo, pats, msg=message, **opts)
     q.save_dirty()
     return 0
 
-def diff(ui, repo, *files, **opts):
+def diff(ui, repo, *pats, **opts):
     """diff of the current patch"""
-    # deep in the dirstate code, the walkhelper method wants a list, not a tuple
-    repo.mq.diff(repo, list(files))
+    repo.mq.diff(repo, pats, opts)
     return 0
 
 def fold(ui, repo, *files, **opts):
@@ -1469,20 +1487,21 @@
     patches = []
     messages = []
     for f in files:
-        patch = q.lookup(f)
-        if patch in patches or patch == parent:
-            ui.warn(_('Skipping already folded patch %s') % patch)
-        if q.isapplied(patch):
-            raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
-        patches.append(patch)
+        p = q.lookup(f)
+        if p in patches or p == parent:
+            ui.warn(_('Skipping already folded patch %s') % p)
+        if q.isapplied(p):
+            raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
+        patches.append(p)
 
-    for patch in patches:
+    for p in patches:
         if not message:
-            messages.append(q.readheaders(patch)[0])
-        pf = q.join(patch)
+            messages.append(q.readheaders(p)[0])
+        pf = q.join(p)
         (patchsuccess, files, fuzz) = q.patch(repo, pf)
         if not patchsuccess:
-            raise util.Abort(_('Error folding patch %s') % patch)
+            raise util.Abort(_('Error folding patch %s') % p)
+        patch.updatedir(ui, repo, files)
 
     if not message:
         message, comments, user = q.readheaders(parent)[0:3]
@@ -1495,29 +1514,26 @@
         message = ui.edit(message, user or ui.username())
 
     q.refresh(repo, msg=message)
-
-    for patch in patches:
-        q.delete(repo, patch, keep=opts['keep'])
-
+    q.delete(repo, patches, keep=opts['keep'])
     q.save_dirty()
 
 def guard(ui, repo, *args, **opts):
     '''set or print guards for a patch
 
-    guards control whether a patch can be pushed.  a patch with no
-    guards is aways pushed.  a patch with posative guard ("+foo") is
-    pushed only if qselect command enables guard "foo".  a patch with
-    nagative guard ("-foo") is never pushed if qselect command enables
-    guard "foo".
+    Guards control whether a patch can be pushed. A patch with no
+    guards is always pushed. A patch with a positive guard ("+foo") is
+    pushed only if the qselect command has activated it. A patch with
+    a negative guard ("-foo") is never pushed if the qselect command
+    has activated it.
 
-    with no arguments, default is to print current active guards.
-    with arguments, set active guards for patch.
+    With no arguments, print the currently active guards.
+    With arguments, set guards for the named patch.
 
-    to set nagative guard "-foo" on topmost patch ("--" is needed so
-    hg will not interpret "-foo" as argument):
+    To set a negative guard "-foo" on topmost patch ("--" is needed so
+    hg will not interpret "-foo" as an option):
       hg qguard -- -foo
 
-    to set guards on other patch:
+    To set guards on another patch:
       hg qguard other.patch +2.6.17 -stable    
     '''
     def status(idx):
@@ -1723,32 +1739,34 @@
 def select(ui, repo, *args, **opts):
     '''set or print guarded patches to push
 
-    use qguard command to set or print guards on patch.  then use
-    qselect to tell mq which guards to use.  example:
+    Use the qguard command to set or print guards on patch, then use
+    qselect to tell mq which guards to use. A patch will be pushed if it
+    has no guards or any positive guards match the currently selected guard,
+    but will not be pushed if any negative guards match the current guard.
+    For example:
 
-        qguard foo.patch -stable    (nagative guard)
-        qguard bar.patch +stable    (posative guard)
+        qguard foo.patch -stable    (negative guard)
+        qguard bar.patch +stable    (positive guard)
         qselect stable
 
-    this sets "stable" guard.  mq will skip foo.patch (because it has
-    nagative match) but push bar.patch (because it has posative
-    match).  patch is pushed if any posative guards match and no
-    nagative guards match.
+    This activates the "stable" guard. mq will skip foo.patch (because
+    it has a negative match) but push bar.patch (because it
+    has a positive match).
 
-    with no arguments, default is to print current active guards.
-    with arguments, set active guards as given.
+    With no arguments, prints the currently active guards.
+    With one argument, sets the active guard.
     
-    use -n/--none to deactivate guards (no other arguments needed).
-    when no guards active, patches with posative guards are skipped,
-    patches with nagative guards are pushed.
+    Use -n/--none to deactivate guards (no other arguments needed).
+    When no guards are active, patches with positive guards are skipped
+    and patches with negative guards are pushed.
 
-    qselect can change guards of applied patches. it does not pop
-    guarded patches by default.  use --pop to pop back to last applied
-    patch that is not guarded.  use --reapply (implies --pop) to push
-    back to current patch afterwards, but skip guarded patches.
+    qselect can change the guards on applied patches. It does not pop
+    guarded patches by default. Use --pop to pop back to the last applied
+    patch that is not guarded. Use --reapply (which implies --pop) to push
+    back to the current patch afterwards, but skip guarded patches.
 
-    use -s/--series to print list of all guards in series file (no
-    other arguments needed).  use -v for more information.'''
+    Use -s/--series to print a list of all guards in the series file (no
+    other arguments needed). Use -v for more information.'''
 
     q = repo.mq
     guards = q.active()
@@ -1885,7 +1903,10 @@
         (commit,
          commands.table["^commit|ci"][1],
          'hg qcommit [OPTION]... [FILE]...'),
-    "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
+    "^qdiff": (diff,
+               [('I', 'include', [], _('include names matching the given patterns')),
+                ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+               'hg qdiff [-I] [-X] [FILE]...'),
     "qdelete|qremove|qrm":
         (delete,
          [('k', 'keep', None, _('keep patch file'))],
@@ -1914,10 +1935,11 @@
          'hg qinit [-c]'),
     "qnew":
         (new,
-         [('m', 'message', '', _('use <text> as commit message')),
+         [('e', 'edit', None, _('edit commit message')),
+          ('m', 'message', '', _('use <text> as commit message')),
           ('l', 'logfile', '', _('read the commit message from <file>')),
           ('f', 'force', None, _('import uncommitted changes into patch'))],
-         'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
+         'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
     "qnext": (next, [], 'hg qnext'),
     "qprev": (prev, [], 'hg qprev'),
     "^qpop":
@@ -1939,8 +1961,10 @@
          [('e', 'edit', None, _('edit commit message')),
           ('m', 'message', '', _('change commit message with <text>')),
           ('l', 'logfile', '', _('change commit message with <file> content')),
-          ('s', 'short', None, 'short refresh')],
-         'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
+          ('s', 'short', None, 'short refresh'),
+          ('I', 'include', [], _('include names matching the given patterns')),
+          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
     'qrename|qmv':
         (rename, [], 'hg qrename PATCH1 [PATCH2]'),
     "qrestore":
--- a/mercurial/commands.py	Thu Aug 17 21:13:35 2006 +0300
+++ b/mercurial/commands.py	Fri Aug 18 17:02:38 2006 -0700
@@ -1526,7 +1526,6 @@
         if st == 'window':
             incrementing = rev
             matches.clear()
-            copies.clear()
         elif st == 'add':
             change = repo.changelog.read(repo.lookup(str(rev)))
             mf = repo.manifest.read(change[0])
@@ -1535,20 +1534,19 @@
                 if fn in skip:
                     continue
                 fstate.setdefault(fn, {})
-                copies.setdefault(rev, {})
                 try:
                     grepbody(fn, rev, getfile(fn).read(mf[fn]))
                     if follow:
                         copied = getfile(fn).renamed(mf[fn])
                         if copied:
-                            copies[rev][fn] = copied[0]
+                            copies.setdefault(rev, {})[fn] = copied[0]
                 except KeyError:
                     pass
         elif st == 'iter':
             states = matches[rev].items()
             states.sort()
             for fn, m in states:
-                copy = copies[rev].get(fn)
+                copy = copies.get(rev, {}).get(fn)
                 if fn in skip:
                     if copy:
                         skip[copy] = True
@@ -1571,7 +1569,7 @@
         for fn, state in fstate:
             if fn in skip:
                 continue
-            if fn not in copies[prev[fn]]:
+            if fn not in copies.get(prev[fn], {}):
                 display(fn, rev, {}, state)
     return (count == 0 and 1) or 0
 
@@ -1683,44 +1681,7 @@
             ui.debug(_('message:\n%s\n') % message)
 
             files, fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root)
-            removes = []
-            if len(files) > 0:
-                cfiles = files.keys()
-                copies = []
-                copts = {'after': False, 'force': False}
-                cwd = repo.getcwd()
-                if cwd:
-                    cfiles = [util.pathto(cwd, f) for f in files.keys()]
-                for f in files:
-                    ctype, gp = files[f]
-                    if ctype == 'RENAME':
-                        copies.append((gp.oldpath, gp.path, gp.copymod))
-                        removes.append(gp.oldpath)
-                    elif ctype == 'COPY':
-                        copies.append((gp.oldpath, gp.path, gp.copymod))
-                    elif ctype == 'DELETE':
-                        removes.append(gp.path)
-                for src, dst, after in copies:
-                    absdst = os.path.join(repo.root, dst)
-                    if not after and os.path.exists(absdst):
-                        raise util.Abort(_('patch creates existing file %s') % dst)
-                    if cwd:
-                        src, dst = [util.pathto(cwd, f) for f in (src, dst)]
-                    copts['after'] = after
-                    errs, copied = docopy(ui, repo, (src, dst), copts, wlock=wlock)
-                    if errs:
-                        raise util.Abort(errs)
-                if removes:
-                    repo.remove(removes, True, wlock=wlock)
-                for f in files:
-                    ctype, gp = files[f]
-                    if gp and gp.mode:
-                        x = gp.mode & 0100 != 0
-                        dst = os.path.join(repo.root, gp.path)
-                        util.set_exec(dst, x)
-                cmdutil.addremove(repo, cfiles, wlock=wlock)
-            files = files.keys()
-            files.extend([r for r in removes if r not in files])
+            files = patch.updatedir(ui, repo, files, wlock=wlock)
             repo.commit(files, message, user, date, wlock=wlock, lock=lock)
         finally:
             os.unlink(tmpname)
@@ -3281,18 +3242,11 @@
                 return sys.modules[v]
         raise KeyError(name)
 
-def dispatch(args):
-    for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
-        num = getattr(signal, name, None)
-        if num: signal.signal(num, catchterm)
-
-    try:
-        u = ui.ui(traceback='--traceback' in sys.argv[1:])
-    except util.Abort, inst:
-        sys.stderr.write(_("abort: %s\n") % inst)
-        return -1
-
-    for ext_name, load_from_name in u.extensions():
+def load_extensions(ui):
+    added = []
+    for ext_name, load_from_name in ui.extensions():
+        if ext_name in external:
+            continue
         try:
             if load_from_name:
                 # the module will be loaded in sys.modules
@@ -3312,23 +3266,36 @@
                 except ImportError:
                     mod = importh(ext_name)
             external[ext_name] = mod.__name__
+            added.append((mod, ext_name))
         except (util.SignalInterrupt, KeyboardInterrupt):
             raise
         except Exception, inst:
-            u.warn(_("*** failed to import extension %s: %s\n") % (ext_name, inst))
-            if u.print_exc():
+            ui.warn(_("*** failed to import extension %s: %s\n") %
+                    (ext_name, inst))
+            if ui.print_exc():
                 return 1
 
-    for name in external.itervalues():
-        mod = sys.modules[name]
+    for mod, name in added:
         uisetup = getattr(mod, 'uisetup', None)
         if uisetup:
-            uisetup(u)
+            uisetup(ui)
         cmdtable = getattr(mod, 'cmdtable', {})
         for t in cmdtable:
             if t in table:
-                u.warn(_("module %s overrides %s\n") % (name, t))
+                ui.warn(_("module %s overrides %s\n") % (name, t))
         table.update(cmdtable)
+    
+def dispatch(args):
+    for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
+        num = getattr(signal, name, None)
+        if num: signal.signal(num, catchterm)
+
+    try:
+        u = ui.ui(traceback='--traceback' in sys.argv[1:],
+                  readhooks=[load_extensions])
+    except util.Abort, inst:
+        sys.stderr.write(_("abort: %s\n") % inst)
+        return -1
 
     try:
         cmd, func, args, options, cmdoptions = parse(u, args)
--- a/mercurial/filelog.py	Thu Aug 17 21:13:35 2006 +0300
+++ b/mercurial/filelog.py	Fri Aug 18 17:02:38 2006 -0700
@@ -96,31 +96,59 @@
             return child
 
         # find all ancestors
-        needed = {node:1}
-        visit = [node]
+        needed = {(self, node):1}
+        files = [self]
+        visit = [(self, node)]
         while visit:
-            n = visit.pop(0)
-            for p in self.parents(n):
-                if p not in needed:
-                    needed[p] = 1
-                    visit.append(p)
+            f, n = visit.pop(0)
+            rn = f.renamed(n)
+            if rn:
+                f, n = rn
+                f = filelog(self.opener, f, self.defversion)
+                files.insert(0, f)
+                if (f, n) not in needed:
+                    needed[(f, n)] = 1
+                else:
+                    needed[(f, n)] += 1
+            for p in f.parents(n):
+                if p == nullid:
+                    continue
+                if (f, p) not in needed:
+                    needed[(f, p)] = 1
+                    visit.append((f, p))
                 else:
                     # count how many times we'll use this
-                    needed[p] += 1
+                    needed[(f, p)] += 1
 
-        # sort by revision which is a topological order
-        visit = [ (self.rev(n), n) for n in needed.keys() ]
-        visit.sort()
+        # sort by revision (per file) which is a topological order
+        visit = []
+        for f in files:
+            fn = [(f.rev(n[1]), f, n[1]) for n in needed.keys() if n[0] == f]
+            fn.sort()
+            visit.extend(fn)
         hist = {}
 
-        for r,n in visit:
-            curr = decorate(self.read(n), self.linkrev(n))
-            for p in self.parents(n):
+        for i in range(len(visit)):
+            r, f, n = visit[i]
+            curr = decorate(f.read(n), f.linkrev(n))
+            if r == -1:
+                continue
+            parents = f.parents(n)
+            # follow parents across renames
+            if r < 1 and i > 0:
+                j = i
+                while j > 0 and visit[j][1] == f:
+                    j -= 1
+                parents = (visit[j][2],)
+                f = visit[j][1]
+            else:
+                parents = f.parents(n)
+            for p in parents:
                 if p != nullid:
                     curr = pair(hist[p], curr)
                     # trim the history of unneeded revs
-                    needed[p] -= 1
-                    if not needed[p]:
+                    needed[(f, p)] -= 1
+                    if not needed[(f, p)]:
                         del hist[p]
             hist[n] = curr
 
--- a/mercurial/patch.py	Thu Aug 17 21:13:35 2006 +0300
+++ b/mercurial/patch.py	Fri Aug 18 17:02:38 2006 -0700
@@ -11,6 +11,28 @@
 demandload(globals(), "cmdutil mdiff util")
 demandload(globals(), "cStringIO email.Parser errno os re shutil sys tempfile")
 
+# helper functions
+
+def copyfile(src, dst, basedir=None):
+    if not basedir:
+        basedir = os.getcwd()
+
+    abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
+    if os.path.exists(absdst):
+        raise util.Abort(_("cannot create %s: destination already exists") %
+                         dst)
+
+    targetdir = os.path.dirname(absdst)
+    if not os.path.isdir(targetdir):
+        os.makedirs(targetdir)
+    try:
+        shutil.copyfile(abssrc, absdst)
+        shutil.copymode(abssrc, absdst)
+    except shutil.Error, inst:
+        raise util.Abort(str(inst))
+
+# public functions
+
 def extract(ui, fileobj):
     '''extract patch from data read from fileobj.
 
@@ -174,21 +196,7 @@
             if not p.copymod:
                 continue
 
-            if os.path.exists(p.path):
-                raise util.Abort(_("cannot create %s: destination already exists") %
-                            p.path)
-
-            (src, dst) = [os.path.join(os.getcwd(), n)
-                          for n in (p.oldpath, p.path)]
-
-            targetdir = os.path.dirname(dst)
-            if not os.path.isdir(targetdir):
-                os.makedirs(targetdir)
-            try:
-                shutil.copyfile(src, dst)
-                shutil.copymode(src, dst)
-            except shutil.Error, inst:
-                raise util.Abort(str(inst))
+            copyfile(p.oldpath, p.path)
 
             # rewrite patch hunk
             while pfline < p.lineno:
@@ -281,6 +289,45 @@
         ignoreblanklines=(opts.get('ignore_blank_lines') or
                           ui.configbool('diff', 'ignoreblanklines', None)))
 
+def updatedir(ui, repo, patches, wlock=None):
+    '''Update dirstate after patch application according to metadata'''
+    if not patches:
+        return
+    copies = []
+    removes = []
+    cfiles = patches.keys()
+    copts = {'after': False, 'force': False}
+    cwd = repo.getcwd()
+    if cwd:
+        cfiles = [util.pathto(cwd, f) for f in patches.keys()]
+    for f in patches:
+        ctype, gp = patches[f]
+        if ctype == 'RENAME':
+            copies.append((gp.oldpath, gp.path, gp.copymod))
+            removes.append(gp.oldpath)
+        elif ctype == 'COPY':
+            copies.append((gp.oldpath, gp.path, gp.copymod))
+        elif ctype == 'DELETE':
+            removes.append(gp.path)
+    for src, dst, after in copies:
+        if not after:
+            copyfile(src, dst, repo.root)
+        repo.copy(src, dst, wlock=wlock)
+    if removes:
+        repo.remove(removes, True, wlock=wlock)
+    for f in patches:
+        ctype, gp = patches[f]
+        if gp and gp.mode:
+            x = gp.mode & 0100 != 0
+            dst = os.path.join(repo.root, gp.path)
+            util.set_exec(dst, x)
+    cmdutil.addremove(repo, cfiles, wlock=wlock)
+    files = patches.keys()
+    files.extend([r for r in removes if r not in files])
+    files.sort()
+
+    return files
+
 def diff(repo, node1=None, node2=None, files=None, match=util.always,
          fp=None, changes=None, opts=None):
     '''print diff of changes to files between two nodes, or node and
@@ -296,10 +343,27 @@
 
     if not node1:
         node1 = repo.dirstate.parents()[0]
+
+    clcache = {}
+    def getchangelog(n):
+        if n not in clcache:
+            clcache[n] = repo.changelog.read(n)
+        return clcache[n]
+    mcache = {}
+    def getmanifest(n):
+        if n not in mcache:
+            mcache[n] = repo.manifest.read(n)
+        return mcache[n]
+    fcache = {}
+    def getfile(f):
+        if f not in fcache:
+            fcache[f] = repo.file(f)
+        return fcache[f]
+
     # reading the data for node1 early allows it to play nicely
     # with repo.status and the revlog cache.
-    change = repo.changelog.read(node1)
-    mmap = repo.manifest.read(change[0])
+    change = getchangelog(node1)
+    mmap = getmanifest(change[0])
     date1 = util.datestr(change[2])
 
     if not changes:
@@ -320,17 +384,32 @@
     if not modified and not added and not removed:
         return
 
+    def renamedbetween(f, n1, n2):
+        r1, r2 = map(repo.changelog.rev, (n1, n2))
+        src = None
+        while r2 > r1:
+            cl = getchangelog(n2)[0]
+            m = getmanifest(cl)
+            try:
+                src = getfile(f).renamed(m[f])
+            except KeyError:
+                return None
+            if src:
+                f = src[0]
+            n2 = repo.changelog.parents(n2)[0]
+            r2 = repo.changelog.rev(n2)
+        return src
+
     if node2:
-        change = repo.changelog.read(node2)
-        mmap2 = repo.manifest.read(change[0])
+        change = getchangelog(node2)
+        mmap2 = getmanifest(change[0])
         _date2 = util.datestr(change[2])
         def date2(f):
             return _date2
         def read(f):
-            return repo.file(f).read(mmap2[f])
+            return getfile(f).read(mmap2[f])
         def renamed(f):
-            src = repo.file(f).renamed(mmap2[f])
-            return src and src[0] or None
+            return renamedbetween(f, node1, node2)
     else:
         tz = util.makedate()[1]
         _date2 = util.datestr()
@@ -343,7 +422,18 @@
         def read(f):
             return repo.wread(f)
         def renamed(f):
-            return repo.dirstate.copies.get(f)
+            src = repo.dirstate.copies.get(f)
+            parent = repo.dirstate.parents()[0]
+            if src:
+                f = src[0]
+            of = renamedbetween(f, node1, parent)
+            if of:
+                return of
+            elif src:
+                cl = getchangelog(parent)[0]
+                return (src, getmanifest(cl)[src])
+            else:
+                return None
 
     if repo.ui.quiet:
         r = None
@@ -357,7 +447,7 @@
             src = renamed(f)
             if src:
                 copied[f] = src
-        srcs = [x[1] for x in copied.items()]
+        srcs = [x[1][0] for x in copied.items()]
 
     all = modified + added + removed
     all.sort()
@@ -366,7 +456,7 @@
         tn = None
         dodiff = True
         if f in mmap:
-            to = repo.file(f).read(mmap[f])
+            to = getfile(f).read(mmap[f])
         if f not in removed:
             tn = read(f)
         if opts.git:
@@ -385,13 +475,13 @@
                 else:
                     mode = gitmode(util.is_exec(repo.wjoin(f), None))
                 if f in copied:
-                    a = copied[f]
+                    a, arev = copied[f]
                     omode = gitmode(mmap.execf(a))
                     addmodehdr(header, omode, mode)
                     op = a in removed and 'rename' or 'copy'
                     header.append('%s from %s\n' % (op, a))
                     header.append('%s to %s\n' % (op, f))
-                    to = repo.file(a).read(mmap[a])
+                    to = getfile(a).read(arev)
                 else:
                     header.append('new file mode %s\n' % mode)
             elif f in removed:
--- a/mercurial/ui.py	Thu Aug 17 21:13:35 2006 +0300
+++ b/mercurial/ui.py	Fri Aug 18 17:02:38 2006 -0700
@@ -12,11 +12,13 @@
 
 class ui(object):
     def __init__(self, verbose=False, debug=False, quiet=False,
-                 interactive=True, traceback=False, parentui=None):
+                 interactive=True, traceback=False, parentui=None,
+                 readhooks=[]):
         self.overlay = {}
         if parentui is None:
             # this is the parent of all ui children
             self.parentui = None
+            self.readhooks = list(readhooks)
             self.cdata = ConfigParser.SafeConfigParser()
             self.readconfig(util.rcpath())
 
@@ -34,6 +36,7 @@
         else:
             # parentui may point to an ui object which is already a child
             self.parentui = parentui.parentui or parentui
+            self.readhooks = list(parentui.readhooks or readhooks)
             parent_cdata = self.parentui.cdata
             self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
             # make interpolation work
@@ -78,6 +81,8 @@
         for name, path in self.configitems("paths"):
             if path and "://" not in path and not os.path.isabs(path):
                 self.cdata.set("paths", name, os.path.join(root, path))
+        for hook in self.readhooks:
+            hook(self)
 
     def setconfig(self, section, name, val):
         self.overlay[(section, name)] = val
--- a/tests/README	Thu Aug 17 21:13:35 2006 +0300
+++ b/tests/README	Fri Aug 18 17:02:38 2006 -0700
@@ -28,6 +28,6 @@
 
 - diff will show the current time
 
-  use hg diff | sed "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/" to strip
-  dates
-
+  use hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+                    -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
+  to strip dates
--- a/tests/test-bisect	Thu Aug 17 21:13:35 2006 +0300
+++ b/tests/test-bisect	Fri Aug 18 17:02:38 2006 -0700
@@ -17,7 +17,7 @@
     test $count -eq 0 && hg add
     hg ci -m "msg $count" -d "$count 0"
     echo % committed changeset $count
-    count=$(( $count + 1 ))
+    count=`expr $count + 1`
 done
 
 echo % log
--- a/tests/test-extdiff	Thu Aug 17 21:13:35 2006 +0300
+++ b/tests/test-extdiff	Fri Aug 18 17:02:38 2006 -0700
@@ -8,7 +8,11 @@
 cd a
 echo a > a
 hg add
-hg extdiff -o -Nr
+diff -N /dev/null /dev/null 2> /dev/null
+if [ $? -ne 0 ]; then
+	opt="-p gdiff"
+fi
+hg extdiff -o -Nr $opt
 
 echo "[extdiff]" >> $HGTMP/.hgrc
 echo "cmd.falabala=echo" >> $HGTMP/.hgrc
--- a/tests/test-git-export	Thu Aug 17 21:13:35 2006 +0300
+++ b/tests/test-git-export	Fri Aug 18 17:02:38 2006 -0700
@@ -8,22 +8,26 @@
 echo new > new
 hg ci -Amnew -d '0 0'
 echo '% new file'
-hg diff --git -r 0 | sed "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/"
+hg diff --git -r 0 | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+    -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
 
 hg cp new copy
 hg ci -mcopy -d '0 0'
 echo '% copy'
-hg diff --git -r 1:tip | sed "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/"
+hg diff --git -r 1:tip | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+    -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
 
 hg mv copy rename
 hg ci -mrename -d '0 0'
 echo '% rename'
-hg diff --git -r 2:tip | sed "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/"
+hg diff --git -r 2:tip | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+    -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
 
 hg rm rename
 hg ci -mdelete -d '0 0'
 echo '% delete'
-hg diff --git -r 3:tip | sed "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/"
+hg diff --git -r 3:tip | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+    -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
 
 cat > src <<EOF
 1
@@ -36,11 +40,13 @@
 chmod +x src
 hg ci -munexec -d '0 0'
 echo '% chmod 644'
-hg diff --git -r 5:tip | sed "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/"
+hg diff --git -r 5:tip | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+    -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
 
 hg mv src dst
 chmod -x dst
 echo a >> dst
 hg ci -mrenamemod -d '0 0'
 echo '% rename+mod+chmod'
-hg diff --git -r 6:tip | sed "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/"
+hg diff --git -r 6:tip | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+    -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-issue322	Fri Aug 18 17:02:38 2006 -0700
@@ -0,0 +1,45 @@
+#!/bin/sh
+# http://www.selenic.com/mercurial/bts/issue322
+
+echo % file replaced with directory
+
+hg init a
+cd a       
+echo a > a 
+hg commit -Ama 
+rm a       
+mkdir a    
+echo a > a/a
+
+echo % should fail - would corrupt dirstate
+hg add a/a
+
+echo % should fail - if add succeeded, would corrupt manifest
+hg commit -mb
+
+echo % should fail if commit succeeded - manifest is corrupt
+hg verify
+
+cd ..      
+echo % should succeed, but manifest is corrupt
+hg --debug --traceback clone a b
+
+echo % directory replaced with file
+
+hg init c
+cd c
+mkdir a
+echo a > a/a
+hg commit -Ama
+
+rm -rf a
+echo a > a
+
+echo % should fail - would corrupt dirstate
+hg add a
+
+echo % should fail - if add succeeded, would corrupt manifest
+hg commit -mb a
+
+echo % should fail if commit succeeded - manifest is corrupt
+hg verify
--- a/tests/test-mq	Thu Aug 17 21:13:35 2006 +0300
+++ b/tests/test-mq	Fri Aug 18 17:02:38 2006 -0700
@@ -126,3 +126,30 @@
 hg ci -Ama
 hg strip tip 2>&1 | sed 's/\(saving bundle to \).*/\1/'
 hg unbundle .hg/strip-backup/*
+
+cat >>$HGTMP/.hgrc <<EOF
+[diff]
+git = True
+EOF
+cd ..
+hg init git
+cd git
+hg qinit
+
+hg qnew -m'new file' new
+echo foo > new
+chmod +x new
+hg add new
+hg qrefresh
+sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+    -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/new
+
+hg qnew -m'copy file' copy
+hg cp new copy
+hg qrefresh
+sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+    -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/copy
+
+hg qpop
+hg qpush
+hg qdiff
--- a/tests/test-mq.out	Thu Aug 17 21:13:35 2006 +0300
+++ b/tests/test-mq.out	Fri Aug 18 17:02:38 2006 -0700
@@ -127,3 +127,22 @@
 adding file changes
 added 1 changesets with 1 changes to 1 files
 (run 'hg update' to get a working copy)
+new file
+
+diff --git a/new b/new
+new file mode 100755
+--- /dev/null
++++ b/new
+@@ -0,0 +1,1 @@
++foo
+copy file
+
+diff --git a/new b/copy
+copy from new
+copy to copy
+Now at: new
+applying copy
+Now at: copy
+diff --git a/new b/copy
+copy from new
+copy to copy