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
+++ b/hgext/mq.py
@@ -77,7 +77,7 @@ class queue:
 
     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 @@ class queue:
         '''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 @@ class queue:
         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 @@ class queue:
         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 @@ def fold(ui, repo, *files, **opts):
     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 @@ def fold(ui, repo, *files, **opts):
     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 @@ cmdtable = {
          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
+++ b/hgext/notify.py
@@ -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 @@ class notifier(object):
         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
+++ b/hgext/patchbomb.py
@@ -241,7 +241,7 @@ def patchbomb(ui, repo, *revs, **opts):
     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 @@ def patchbomb(ui, repo, *revs, **opts):
             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
+++ b/mercurial/commands.py
@@ -183,59 +183,49 @@ def walkchangerevs(ui, repo, pats, opts)
                 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 @@ def diff(ui, repo, *pats, **opts):
     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 @@ def export(ui, repo, *changesets, **opts
     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 @@ def import_(ui, repo, patch1, *patches, 
                 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 @@ def merge(ui, repo, node=None, force=Non
     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 @@ table = {
           ('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 @@ table = {
           ('', '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
+++ b/mercurial/filelog.py
@@ -65,26 +65,25 @@ class filelog(revlog):
             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
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -11,7 +11,7 @@ import os.path
 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 @@ class hgweb(object):
             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])
new file mode 100644
--- /dev/null
+++ b/mercurial/mail.py
@@ -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
+++ b/mercurial/merge.py
@@ -10,6 +10,11 @@ from i18n import gettext as _
 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 @@ def update(repo, node, branchmerge=False
     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 @@ def update(repo, node, branchmerge=False
     # 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 @@ def update(repo, node, branchmerge=False
 
     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 @@ def update(repo, node, branchmerge=False
         # 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 @@ def update(repo, node, branchmerge=False
         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 @@ def update(repo, node, branchmerge=False
                     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 @@ def update(repo, node, branchmerge=False
 
     del mw, m1, m2, ma
 
+    ### apply phase
+
     if overwrite:
         for f in merge:
             get[f] = merge[f][:2]
@@ -257,11 +254,6 @@ def update(repo, node, branchmerge=False
         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 @@ def update(repo, node, branchmerge=False
         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 @@ def update(repo, node, branchmerge=False
             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
+++ b/mercurial/patch.py
@@ -215,14 +215,13 @@ def dogitpatch(patchname, gitpatches):
     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 @@ def patch(patchname, ui, strip=1, cwd=No
 
         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 @@ def patch(patchname, ui, strip=1, cwd=No
     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 @@ def diff(repo, node1=None, node2=None, f
             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 @@ def diff(repo, node1=None, node2=None, f
                 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 @@ def diff(repo, node1=None, node2=None, f
         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
+++ b/mercurial/revlog.py
@@ -766,6 +766,19 @@ class revlog(object):
 
         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
+++ b/mercurial/ui.py
@@ -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 @@ class ui(object):
             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 @@ class ui(object):
 
         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