changeset 2920:ef8ee4477019

merge with mpm.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Wed, 16 Aug 2006 10:46:24 -0700
parents b70740aefa4d (current diff) 8743188f4d2e (diff)
children 773c5b82d052
files hgext/mq.py mercurial/commands.py mercurial/hgweb/hgweb_mod.py mercurial/patch.py mercurial/ui.py
diffstat 11 files changed, 263 insertions(+), 320 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/mq.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/hgext/mq.py	Wed Aug 16 10:46:24 2006 -0700
@@ -77,7 +77,7 @@
 
     def diffopts(self):
         if self._diffopts is None:
-            self._diffopts = self.ui.diffopts()
+            self._diffopts = patch.diffopts(self.ui)
         return self._diffopts
 
     def join(self, *p):
@@ -400,15 +400,39 @@
         '''Apply patchfile  to the working directory.
         patchfile: file name of patch'''
         try:
-            (files, fuzz) = patch.patch(patchfile, self.ui, strip=1,
-                                        cwd=repo.root)
-        except Exception, inst:
-            self.ui.note(str(inst) + '\n')
-            if not self.ui.verbose:
-                self.ui.warn("patch failed, unable to continue (try -v)\n")
-            return (False, [], False)
+            pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
+            f = os.popen("%s -d %s -p1 --no-backup-if-mismatch < %s" %
+                         (pp, util.shellquote(repo.root), util.shellquote(patchfile)))
+        except:
+            self.ui.warn("patch failed, unable to continue (try -v)\n")
+            return (None, [], False)
+        files = []
+        fuzz = False
+        for l in f:
+            l = l.rstrip('\r\n');
+            if self.ui.verbose:
+                self.ui.warn(l + "\n")
+            if l[:14] == 'patching file ':
+                pf = os.path.normpath(util.parse_patch_output(l))
+                if pf not in files:
+                    files.append(pf)
+                printed_file = False
+                file_str = l
+            elif l.find('with fuzz') >= 0:
+                if not printed_file:
+                    self.ui.warn(file_str + '\n')
+                    printed_file = True
+                self.ui.warn(l + '\n')
+                fuzz = True
+            elif l.find('saving rejects to file') >= 0:
+                self.ui.warn(l + '\n')
+            elif l.find('FAILED') >= 0:
+                if not printed_file:
+                    self.ui.warn(file_str + '\n')
+                    printed_file = True
+                self.ui.warn(l + '\n')
 
-        return (True, files.keys(), fuzz)
+        return (not f.close(), files, fuzz)
 
     def apply(self, repo, series, list=False, update_status=True,
               strict=False, patchdir=None, merge=None, wlock=None):
@@ -482,28 +506,21 @@
         tr.close()
         return (err, n)
 
-    def delete(self, repo, patches, keep=False):
-        realpatches = []
-        for patch in patches:
-            patch = self.lookup(patch, strict=True)
-            info = self.isapplied(patch)
-            if info:
-                raise util.Abort(_("cannot delete applied patch %s") % patch)
-            if patch not in self.series:
-                raise util.Abort(_("patch %s not in series file") % patch)
-            realpatches.append(patch)
-
-        if not keep:
+    def delete(self, repo, patch, force=False):
+        patch = self.lookup(patch, strict=True)
+        info = self.isapplied(patch)
+        if info:
+            raise util.Abort(_("cannot delete applied patch %s") % patch)
+        if patch not in self.series:
+            raise util.Abort(_("patch %s not in series file") % patch)
+        if force:
             r = self.qrepo()
             if r:
-                r.remove(realpatches, True)
+                r.remove([patch], True)
             else:
                 os.unlink(self.join(patch))
-
-        indices = [self.find_series(p) for p in realpatches]
-        indices.sort()
-        for i in indices[-1::-1]:
-            del self.full_series[i]
+        i = self.find_series(patch)
+        del self.full_series[i]
         self.parse_series()
         self.series_dirty = 1
 
@@ -1283,13 +1300,13 @@
         if qrepo:
             qrepo.add(added)
 
-def delete(ui, repo, patch, *patches, **opts):
-    """remove patches from queue
+def delete(ui, repo, patch, **opts):
+    """remove a patch from the series file
 
-    The patches must not be applied.
-    With -k, the patch files are preserved in the patch directory."""
+    The patch must not be applied.
+    With -f, deletes the patch file as well as the series entry."""
     q = repo.mq
-    q.delete(repo, (patch,) + patches, keep=opts.get('keep'))
+    q.delete(repo, patch, force=opts.get('force'))
     q.save_dirty()
     return 0
 
@@ -1447,7 +1464,7 @@
     applied to the current patch in the order given. If all the
     patches apply successfully, the current patch will be refreshed
     with the new cumulative patch, and the folded patches will
-    be deleted. With -k/--keep, the folded patch files will not
+    be deleted. With -f/--force, the folded patch files will
     be removed afterwards.
 
     The header for each folded patch will be concatenated with
@@ -1497,7 +1514,7 @@
     q.refresh(repo, msg=message)
 
     for patch in patches:
-        q.delete(repo, patch, keep=opts['keep'])
+        q.delete(repo, patch, force=opts['force'])
 
     q.save_dirty()
 
@@ -1886,14 +1903,14 @@
          commands.table["^commit|ci"][1],
          'hg qcommit [OPTION]... [FILE]...'),
     "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
-    "qdelete|qremove|qrm":
+    "qdelete":
         (delete,
-         [('k', 'keep', None, _('keep patch file'))],
-          'hg qdelete [-k] PATCH'),
+         [('f', 'force', None, _('delete patch file'))],
+          'hg qdelete [-f] PATCH'),
     'qfold':
         (fold,
          [('e', 'edit', None, _('edit patch header')),
-          ('k', 'keep', None, _('keep folded patch files')),
+          ('f', 'force', None, _('delete folded patch files')),
           ('m', 'message', '', _('set patch header to <text>')),
           ('l', 'logfile', '', _('set patch header to contents of <file>'))],
          'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
--- a/hgext/notify.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/hgext/notify.py	Wed Aug 16 10:46:24 2006 -0700
@@ -67,8 +67,8 @@
 from mercurial.demandload import *
 from mercurial.i18n import gettext as _
 from mercurial.node import *
-demandload(globals(), 'email.Parser mercurial:commands,patch,templater,util')
-demandload(globals(), 'fnmatch socket time')
+demandload(globals(), 'mercurial:commands,patch,templater,util,mail')
+demandload(globals(), 'email.Parser fnmatch socket time')
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -229,8 +229,8 @@
         else:
             self.ui.status(_('notify: sending %d subscribers %d changes\n') %
                              (len(self.subs), count))
-            mail = self.ui.sendmail()
-            mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
+            mail.sendmail(self.ui, templater.email(msg['From']),
+                          self.subs, msgtext)
 
     def diff(self, node, ref):
         maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
--- a/hgext/patchbomb.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/hgext/patchbomb.py	Wed Aug 16 10:46:24 2006 -0700
@@ -241,7 +241,7 @@
     ui.write('\n')
 
     if not opts['test'] and not opts['mbox']:
-        mail = ui.sendmail()
+        mailer = mail.connect(ui)
     parent = None
 
     # Calculate UTC offset
@@ -290,7 +290,7 @@
             ui.status('Sending ', m['Subject'], ' ...\n')
             # Exim does not remove the Bcc field
             del m['Bcc']
-            mail.sendmail(sender, to + bcc + cc, m.as_string(0))
+            mailer.sendmail(sender, to + bcc + cc, m.as_string(0))
 
 cmdtable = {
     'email':
--- a/mercurial/commands.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/mercurial/commands.py	Wed Aug 16 10:46:24 2006 -0700
@@ -183,59 +183,49 @@
                 fncache[rev] = matches
                 wanted[rev] = 1
 
-    class followfilter:
-        def __init__(self, onlyfirst=False):
-            self.startrev = -1
-            self.roots = []
-            self.onlyfirst = onlyfirst
-
-        def match(self, rev):
-            def realparents(rev):
-                if self.onlyfirst:
-                    return repo.changelog.parentrevs(rev)[0:1]
+    def iterate():
+        class followfilter:
+            def __init__(self, onlyfirst=False):
+                self.startrev = -1
+                self.roots = []
+                self.onlyfirst = onlyfirst
+
+            def match(self, rev):
+                def realparents(rev):
+                    if self.onlyfirst:
+                        return repo.changelog.parentrevs(rev)[0:1]
+                    else:
+                        return filter(lambda x: x != -1, repo.changelog.parentrevs(rev))
+
+                if self.startrev == -1:
+                    self.startrev = rev
+                    return True
+
+                if rev > self.startrev:
+                    # forward: all descendants
+                    if not self.roots:
+                        self.roots.append(self.startrev)
+                    for parent in realparents(rev):
+                        if parent in self.roots:
+                            self.roots.append(rev)
+                            return True
                 else:
-                    return filter(lambda x: x != -1, repo.changelog.parentrevs(rev))
-
-            if self.startrev == -1:
-                self.startrev = rev
-                return True
-
-            if rev > self.startrev:
-                # forward: all descendants
-                if not self.roots:
-                    self.roots.append(self.startrev)
-                for parent in realparents(rev):
-                    if parent in self.roots:
-                        self.roots.append(rev)
+                    # backwards: all parents
+                    if not self.roots:
+                        self.roots.extend(realparents(self.startrev))
+                    if rev in self.roots:
+                        self.roots.remove(rev)
+                        self.roots.extend(realparents(rev))
                         return True
-            else:
-                # backwards: all parents
-                if not self.roots:
-                    self.roots.extend(realparents(self.startrev))
-                if rev in self.roots:
-                    self.roots.remove(rev)
-                    self.roots.extend(realparents(rev))
-                    return True
-
-            return False
-
-    # it might be worthwhile to do this in the iterator if the rev range
-    # is descending and the prune args are all within that range
-    for rev in opts.get('prune', ()):
-        rev = repo.changelog.rev(repo.lookup(rev))
-        ff = followfilter()
-        stop = min(revs[0], revs[-1])
-        for x in range(rev, stop-1, -1):
-            if ff.match(x) and wanted.has_key(x):
-                del wanted[x]
-
-    def iterate():
+
+                return False
+
         if follow and not files:
             ff = followfilter(onlyfirst=opts.get('follow_first'))
             def want(rev):
-                if ff.match(rev) and rev in wanted:
-                    return True
-                return False
+                if rev not in wanted:
+                    return False
+                return ff.match(rev)
         else:
             def want(rev):
                 return rev in wanted
@@ -1347,7 +1337,7 @@
     fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
 
     patch.diff(repo, node1, node2, fns, match=matchfn,
-               opts=ui.diffopts(opts))
+               opts=patch.diffopts(ui, opts))
 
 def export(ui, repo, *changesets, **opts):
     """dump the header and diffs for one or more changesets
@@ -1384,7 +1374,8 @@
     else:
         ui.note(_('exporting patch:\n'))
     patch.export(repo, map(repo.lookup, revs), template=opts['output'],
-                 switch_parent=opts['switch_parent'], opts=ui.diffopts(opts))
+                 switch_parent=opts['switch_parent'],
+                 opts=patch.diffopts(ui, opts))
 
 def forget(ui, repo, *pats, **opts):
     """don't add the specified files on the next commit (DEPRECATED)
@@ -1681,7 +1672,7 @@
                 message = None
             ui.debug(_('message:\n%s\n') % message)
 
-            files, fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root)
+            files = patch.patch(strip, tmpname, ui, cwd=repo.root)
             removes = []
             if len(files) > 0:
                 cfiles = files.keys()
@@ -1969,29 +1960,9 @@
     requested revision. Files that changed between either parent are
     marked as changed for the next commit and a commit must be
     performed before any further updates are allowed.
-
-    If no revision is specified, the working directory's parent is a
-    head revision, and the repository contains exactly one other head,
-    the other head is merged with by default.  Otherwise, an explicit
-    revision to merge with must be provided.
     """
 
-    if node:
-        node = _lookup(repo, node, branch)
-    else:
-        heads = repo.heads()
-        if len(heads) > 2:
-            raise util.Abort(_('repo has %d heads - '
-                               'please merge with an explicit rev') %
-                             len(heads))
-        if len(heads) == 1:
-            raise util.Abort(_('there is nothing to merge - '
-                               'use "hg update" instead'))
-        parent = repo.dirstate.parents()[0]
-        if parent not in heads:
-            raise util.Abort(_('working dir not at a head rev - '
-                               'use "hg update" or merge with an explicit rev'))
-        node = parent == heads[0] and heads[-1] or heads[0]
+    node = _lookup(repo, node, branch)
     return hg.merge(repo, node, force=force)
 
 def outgoing(ui, repo, dest=None, **opts):
@@ -2897,7 +2868,6 @@
           ('a', 'text', None, _('treat all files as text')),
           ('p', 'show-function', None,
            _('show which function each change is in')),
-          ('g', 'git', None, _('use git extended diff format')),
           ('w', 'ignore-all-space', None,
            _('ignore white space when comparing lines')),
           ('b', 'ignore-space-change', None,
@@ -2997,7 +2967,6 @@
           ('', 'style', '', _('display using template map file')),
           ('m', 'only-merges', None, _('show only merges')),
           ('p', 'patch', None, _('show patch')),
-          ('P', 'prune', [], _('do not display revision or any of its ancestors')),
           ('', 'template', '', _('display with template')),
           ('I', 'include', [], _('include names matching the given patterns')),
           ('X', 'exclude', [], _('exclude names matching the given patterns'))],
--- a/mercurial/filelog.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/mercurial/filelog.py	Wed Aug 16 10:46:24 2006 -0700
@@ -65,26 +65,25 @@
             return (m["copy"], bin(m["copyrev"]))
         return False
 
+    def size(self, rev):
+        """return the size of a given revision"""
+
+        # for revisions with renames, we have to go the slow way
+        node = self.node(rev)
+        if self.renamed(node):
+            return len(self.read(node))
+
+        return revlog.size(self, rev)
+
     def cmp(self, node, text):
         """compare text with a given file revision"""
 
         # for renames, we have to go the slow way
         if self.renamed(node):
             t2 = self.read(node)
-            return t2 == text
-
-        p1, p2 = self.parents(node)
-        h = hash(text, p1, p2)
-
-        return h != node
+            return t2 != text
 
-    def makenode(self, node, text):
-        """calculate a file nodeid for text, descended or possibly
-        unchanged from node"""
-
-        if self.cmp(node, text):
-            return hash(text, node, nullid)
-        return node
+        return revlog.cmp(self, node, text)
 
     def annotate(self, node):
 
--- a/mercurial/hgweb/hgweb_mod.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/mercurial/hgweb/hgweb_mod.py	Wed Aug 16 10:46:24 2006 -0700
@@ -11,7 +11,7 @@
 import mimetypes
 from mercurial.demandload import demandload
 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
-demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone")
+demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
 demandload(globals(), "mercurial:templater")
 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
 from mercurial.node import *
@@ -134,7 +134,7 @@
             modified, added, removed = map(lambda x: filterfiles(files, x),
                                            (modified, added, removed))
 
-        diffopts = self.repo.ui.diffopts()
+        diffopts = patch.diffopts(ui)
         for f in modified:
             to = r.file(f).read(mmap1[f])
             tn = r.file(f).read(mmap2[f])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/mail.py	Wed Aug 16 10:46:24 2006 -0700
@@ -0,0 +1,68 @@
+# mail.py - mail sending bits for mercurial
+#
+# Copyright 2006 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from i18n import gettext as _
+from demandload import *
+demandload(globals(), "os re smtplib templater util")
+
+def _smtp(ui):
+    '''send mail using smtp.'''
+
+    local_hostname = ui.config('smtp', 'local_hostname')
+    s = smtplib.SMTP(local_hostname=local_hostname)
+    mailhost = ui.config('smtp', 'host')
+    if not mailhost:
+        raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
+    mailport = int(ui.config('smtp', 'port', 25))
+    self.note(_('sending mail: smtp host %s, port %s\n') %
+              (mailhost, mailport))
+    s.connect(host=mailhost, port=mailport)
+    if ui.configbool('smtp', 'tls'):
+        ui.note(_('(using tls)\n'))
+        s.ehlo()
+        s.starttls()
+        s.ehlo()
+    username = ui.config('smtp', 'username')
+    password = ui.config('smtp', 'password')
+    if username and password:
+        ui.note(_('(authenticating to mail server as %s)\n') %
+                  (username))
+        s.login(username, password)
+    return s
+
+class _sendmail(object):
+    '''send mail using sendmail.'''
+
+    def __init__(self, ui, program):
+        self.ui = ui
+        self.program = program
+
+    def sendmail(self, sender, recipients, msg):
+        cmdline = '%s -f %s %s' % (
+            self.program, templater.email(sender),
+            ' '.join(map(templater.email, recipients)))
+        self.ui.note(_('sending mail: %s\n') % cmdline)
+        fp = os.popen(cmdline, 'w')
+        fp.write(msg)
+        ret = fp.close()
+        if ret:
+            raise util.Abort('%s %s' % (
+                os.path.basename(self.program.split(None, 1)[0]),
+                util.explain_exit(ret)[0]))
+
+def connect(ui):
+    '''make a mail connection. object returned has one method, sendmail.
+    call as sendmail(sender, list-of-recipients, msg).'''
+
+    method = ui.config('email', 'method', 'smtp')
+    if method == 'smtp':
+        return smtp(ui)
+
+    return sendmail(ui, method)
+
+def sendmail(ui, sender, recipients, msg):
+    return connect(ui).sendmail(sender, recipients, msg)
--- a/mercurial/merge.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/mercurial/merge.py	Wed Aug 16 10:46:24 2006 -0700
@@ -10,6 +10,11 @@
 from demandload import *
 demandload(globals(), "util os tempfile")
 
+def fmerge(f, local, other, ancestor):
+    """merge executable flags"""
+    a, b, c = ancestor.execf(f), local.execf(f), other.execf(f)
+    return ((a^b) | (a^c)) ^ a
+
 def merge3(repo, fn, my, other, p1, p2):
     """perform a 3-way merge in the working directory"""
 
@@ -90,9 +95,7 @@
     if not force:
         for f in unknown:
             if f in m2:
-                t1 = repo.wread(f)
-                t2 = repo.file(f).read(m2[f])
-                if cmp(t1, t2) != 0:
+                if repo.file(f).cmp(m2[f], repo.wread(f)):
                     raise util.Abort(_("'%s' already exists in the working"
                                        " dir and differs from remote") % f)
 
@@ -100,13 +103,14 @@
     # we care about merging
     repo.ui.note(_("resolving manifests\n"))
     repo.ui.debug(_(" overwrite %s branchmerge %s partial %s linear %s\n") %
-                  (overwrite, branchmerge, partial and True or False, linear_path))
+                  (overwrite, branchmerge, bool(partial), linear_path))
     repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
                   (short(man), short(m1n), short(m2n)))
 
     merge = {}
     get = {}
     remove = []
+    forget = []
 
     # construct a working dir manifest
     mw = m1.copy()
@@ -114,6 +118,11 @@
 
     for f in added + modified + unknown:
         mw[f] = ""
+        # is the wfile new and matches m2?
+        if (f not in m1 and f in m2 and
+            not repo.file(f).cmp(m2[f], repo.wread(f))):
+            mw[f] = m2[f]
+
         mw.set(f, util.is_exec(repo.wjoin(f), mw.execf(f)))
 
     for f in deleted + removed:
@@ -125,8 +134,8 @@
         # the file, then we need to remove it from the dirstate, to
         # prevent the dirstate from listing the file when it is no
         # longer in the manifest.
-        if not partial and linear_path and f not in m2:
-            repo.dirstate.forget((f,))
+        if linear_path and f not in m2:
+            forget.append(f)
 
     # Compare manifests
     for f, n in mw.iteritems():
@@ -135,25 +144,13 @@
         if f in m2:
             s = 0
 
-            # is the wfile new since m1, and match m2?
-            if f not in m1:
-                t1 = repo.wread(f)
-                t2 = repo.file(f).read(m2[f])
-                if cmp(t1, t2) == 0:
-                    n = m2[f]
-                del t1, t2
-
             # are files different?
             if n != m2[f]:
                 a = ma.get(f, nullid)
                 # are both different from the ancestor?
                 if n != a and m2[f] != a:
                     repo.ui.debug(_(" %s versions differ, resolve\n") % f)
-                    # merge executable bits
-                    # "if we changed or they changed, change in merge"
-                    a, b, c = ma.execf(f), mw.execf(f), m2.execf(f)
-                    mode = ((a^b) | (a^c)) ^ a
-                    merge[f] = (mode, m1.get(f, nullid), m2[f])
+                    merge[f] = (fmerge(f, mw, m2, ma), m1.get(f, nullid), m2[f])
                     s = 1
                 # are we clobbering?
                 # is remote's version newer?
@@ -172,9 +169,7 @@
                     repo.ui.debug(_(" updating permissions for %s\n") % f)
                     util.set_exec(repo.wjoin(f), m2.execf(f))
                 else:
-                    a, b, c = ma.execf(f), mw.execf(f), m2.execf(f)
-                    mode = ((a^b) | (a^c)) ^ a
-                    if mode != b:
+                    if fmerge(f, mw, m2, ma) != mw.execf(f):
                         repo.ui.debug(_(" updating permissions for %s\n")
                                       % f)
                         util.set_exec(repo.wjoin(f), mode)
@@ -230,6 +225,8 @@
 
     del mw, m1, m2, ma
 
+    ### apply phase
+
     if overwrite:
         for f in merge:
             get[f] = merge[f][:2]
@@ -257,11 +254,6 @@
         t = repo.file(f).read(node)
         repo.wwrite(f, t)
         util.set_exec(repo.wjoin(f), flag)
-        if not partial:
-            if branchmerge:
-                repo.dirstate.update([f], 'n', st_mtime=-1)
-            else:
-                repo.dirstate.update([f], 'n')
 
     # merge the tricky bits
     unresolved = []
@@ -274,19 +266,6 @@
         if ret:
             unresolved.append(f)
         util.set_exec(repo.wjoin(f), flag)
-        if not partial:
-            if branchmerge:
-                # We've done a branch merge, mark this file as merged
-                # so that we properly record the merger later
-                repo.dirstate.update([f], 'm')
-            else:
-                # We've update-merged a locally modified file, so
-                # we set the dirstate to emulate a normal checkout
-                # of that file some time in the past. Thus our
-                # merge will appear as a normal local file
-                # modification.
-                f_len = len(repo.file(f).read(other))
-                repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
 
     remove.sort()
     for f in remove:
@@ -298,14 +277,40 @@
             if inst.errno != errno.ENOENT:
                 repo.ui.warn(_("update failed to remove %s: %s!\n") %
                              (f, inst.strerror))
+
+    # update dirstate
     if not partial:
+        repo.dirstate.setparents(p1, p2)
+        repo.dirstate.forget(forget)
         if branchmerge:
             repo.dirstate.update(remove, 'r')
         else:
             repo.dirstate.forget(remove)
 
-    if not partial:
-        repo.dirstate.setparents(p1, p2)
+        files = get.keys()
+        files.sort()
+        for f in files:
+            if branchmerge:
+                repo.dirstate.update([f], 'n', st_mtime=-1)
+            else:
+                repo.dirstate.update([f], 'n')
+
+        files = merge.keys()
+        files.sort()
+        for f in files:
+            if branchmerge:
+                # We've done a branch merge, mark this file as merged
+                # so that we properly record the merger later
+                repo.dirstate.update([f], 'm')
+            else:
+                # We've update-merged a locally modified file, so
+                # we set the dirstate to emulate a normal checkout
+                # of that file some time in the past. Thus our
+                # merge will appear as a normal local file
+                # modification.
+                fl = repo.file(f)
+                f_len = fl.size(fl.rev(other))
+                repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
 
     if show_stats:
         stats = ((len(get), _("updated")),
--- a/mercurial/patch.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/mercurial/patch.py	Wed Aug 16 10:46:24 2006 -0700
@@ -215,14 +215,13 @@
     tmpfp.close()
     return patchname
 
-def patch(patchname, ui, strip=1, cwd=None):
+def patch(strip, patchname, ui, cwd=None):
     """apply the patch <patchname> to the working directory.
     a list of patched files is returned"""
 
     (dopatch, gitpatches) = readgitpatch(patchname)
 
     files = {}
-    fuzz = False
     if dopatch:
         if dopatch == 'filter':
             patchname = dogitpatch(patchname, gitpatches)
@@ -238,25 +237,10 @@
 
         for line in fp:
             line = line.rstrip()
-            ui.note(line + '\n')
+            ui.status("%s\n" % line)
             if line.startswith('patching file '):
                 pf = util.parse_patch_output(line)
-                printed_file = False
                 files.setdefault(pf, (None, None))
-            elif line.find('with fuzz') >= 0:
-                fuzz = True
-                if not printed_file:
-                    ui.warn(pf + '\n')
-                    printed_file = True
-                ui.warn(line + '\n')
-            elif line.find('saving rejects to file') >= 0:
-                ui.warn(line + '\n')
-            elif line.find('FAILED') >= 0:
-                if not printed_file:
-                    ui.warn(pf + '\n')
-                    printed_file = True
-                ui.warn(line + '\n')
-            
         code = fp.close()
         if code:
             raise util.Abort(_("patch command failed: %s") %
@@ -265,7 +249,19 @@
     for gp in gitpatches:
         files[gp.path] = (gp.op, gp)
 
-    return (files, fuzz)
+    return files
+
+def diffopts(ui, opts={}):
+    return mdiff.diffopts(
+        text=opts.get('text'),
+        showfunc=(opts.get('show_function') or
+                  ui.configbool('diff', 'showfunc', None)),
+        ignorews=(opts.get('ignore_all_space') or
+                  ui.configbool('diff', 'ignorews', None)),
+        ignorewsamount=(opts.get('ignore_space_change') or
+                        ui.configbool('diff', 'ignorewsamount', None)),
+        ignoreblanklines=(opts.get('ignore_blank_lines') or
+                          ui.configbool('diff', 'ignoreblanklines', None)))
 
 def diff(repo, node1=None, node2=None, files=None, match=util.always,
          fp=None, changes=None, opts=None):
@@ -314,9 +310,6 @@
             return _date2
         def read(f):
             return repo.file(f).read(mmap2[f])
-        def renamed(f):
-            src = repo.file(f).renamed(mmap2[f])
-            return src and src[0] or None
     else:
         tz = util.makedate()[1]
         _date2 = util.datestr()
@@ -328,8 +321,6 @@
                 return _date2
         def read(f):
             return repo.wread(f)
-        def renamed(f):
-            return repo.dirstate.copies.get(f)
 
     if repo.ui.quiet:
         r = None
@@ -337,65 +328,16 @@
         hexfunc = repo.ui.verbose and hex or short
         r = [hexfunc(node) for node in [node1, node2] if node]
 
-    if opts.git:
-        copied = {}
-        for f in added:
-            src = renamed(f)
-            if src:
-                copied[f] = src
-        srcs = [x[1] for x in copied.items()]
-
     all = modified + added + removed
     all.sort()
     for f in all:
         to = None
         tn = None
-        dodiff = True
         if f in mmap:
             to = repo.file(f).read(mmap[f])
         if f not in removed:
             tn = read(f)
-        if opts.git:
-            def gitmode(x):
-                return x and '100755' or '100644'
-            def addmodehdr(header, omode, nmode):
-                if omode != nmode:
-                    header.append('old mode %s\n' % omode)
-                    header.append('new mode %s\n' % nmode)
-
-            a, b = f, f
-            header = []
-            if f in added:
-                if node2:
-                    mode = gitmode(mmap2.execf(f))
-                else:
-                    mode = gitmode(util.is_exec(repo.wjoin(f), None))
-                if f in copied:
-                    a = 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])
-                else:
-                    header.append('new file mode %s\n' % mode)
-            elif f in removed:
-                if f in srcs:
-                    dodiff = False
-                else:
-                    mode = gitmode(mmap.execf(f))
-                    header.append('deleted file mode %s\n' % mode)
-            else:
-                omode = gitmode(mmap.execf(f))
-                nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
-                addmodehdr(header, omode, nmode)
-            r = None
-            if dodiff:
-                header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
-                fp.write(''.join(header))
-        if dodiff:
-            fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
+        fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
 
 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
            opts=None):
--- a/mercurial/revlog.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/mercurial/revlog.py	Wed Aug 16 10:46:24 2006 -0700
@@ -766,6 +766,19 @@
 
         raise RevlogError(_("No match found"))
 
+    def cmp(self, node, text):
+        """compare text with a given file revision"""
+        p1, p2 = self.parents(node)
+        return hash(text, p1, p2) != node
+
+    def makenode(self, node, text):
+        """calculate a file nodeid for text, descended or possibly
+        unchanged from node"""
+
+        if self.cmp(node, text):
+            return hash(text, node, nullid)
+        return node
+
     def diff(self, a, b):
         """return a delta between two revisions"""
         return mdiff.textdiff(a, b)
--- a/mercurial/ui.py	Tue Aug 15 11:28:50 2006 -0700
+++ b/mercurial/ui.py	Wed Aug 16 10:46:24 2006 -0700
@@ -7,7 +7,7 @@
 
 from i18n import gettext as _
 from demandload import *
-demandload(globals(), "errno getpass os re smtplib socket sys tempfile")
+demandload(globals(), "errno getpass os re socket sys tempfile")
 demandload(globals(), "ConfigParser mdiff templater traceback util")
 
 class ui(object):
@@ -169,20 +169,6 @@
             result[key.lower()] = value
         return result
 
-    def diffopts(self, opts={}):
-        return mdiff.diffopts(
-            text=opts.get('text'),
-            showfunc=(opts.get('show_function') or
-                      self.configbool('diff', 'showfunc', None)),
-            git=(opts.get('git') or
-                 self.configbool('diff', 'git', None)),
-            ignorews=(opts.get('ignore_all_space') or
-                      self.configbool('diff', 'ignorews', None)),
-            ignorewsamount=(opts.get('ignore_space_change') or
-                            self.configbool('diff', 'ignorewsamount', None)),
-            ignoreblanklines=(opts.get('ignore_blank_lines') or
-                              self.configbool('diff', 'ignoreblanklines', None)))
-
     def username(self):
         """Return default username to be used in commits.
 
@@ -295,62 +281,6 @@
 
         return t
 
-    def sendmail(self):
-        '''send mail message. object returned has one method, sendmail.
-        call as sendmail(sender, list-of-recipients, msg).'''
-
-        def smtp():
-            '''send mail using smtp.'''
-
-            local_hostname = self.config('smtp', 'local_hostname')
-            s = smtplib.SMTP(local_hostname=local_hostname)
-            mailhost = self.config('smtp', 'host')
-            if not mailhost:
-                raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
-            mailport = int(self.config('smtp', 'port', 25))
-            self.note(_('sending mail: smtp host %s, port %s\n') %
-                      (mailhost, mailport))
-            s.connect(host=mailhost, port=mailport)
-            if self.configbool('smtp', 'tls'):
-                self.note(_('(using tls)\n'))
-                s.ehlo()
-                s.starttls()
-                s.ehlo()
-            username = self.config('smtp', 'username')
-            password = self.config('smtp', 'password')
-            if username and password:
-                self.note(_('(authenticating to mail server as %s)\n') %
-                          (username))
-                s.login(username, password)
-            return s
-
-        class sendmail(object):
-            '''send mail using sendmail.'''
-
-            def __init__(self, ui, program):
-                self.ui = ui
-                self.program = program
-
-            def sendmail(self, sender, recipients, msg):
-                cmdline = '%s -f %s %s' % (
-                    self.program, templater.email(sender),
-                    ' '.join(map(templater.email, recipients)))
-                self.ui.note(_('sending mail: %s\n') % cmdline)
-                fp = os.popen(cmdline, 'w')
-                fp.write(msg)
-                ret = fp.close()
-                if ret:
-                    raise util.Abort('%s %s' % (
-                        os.path.basename(self.program.split(None, 1)[0]),
-                        util.explain_exit(ret)[0]))
-
-        method = self.config('email', 'method', 'smtp')
-        if method == 'smtp':
-            mail = smtp()
-        else:
-            mail = sendmail(self, method)
-        return mail
-
     def print_exc(self):
         '''print exception traceback if traceback printing enabled.
         only to call in exception handler. returns true if traceback