Merge with crew
authorMatt Mackall <mpm@selenic.com>
Fri, 23 Mar 2007 01:04:21 -0500
changeset 4265 94bb953b43e5
parent 4261 cd7b36b7869c (current diff)
parent 4264 bda63383d529 (diff)
child 4268 f38f90a177dc
Merge with crew
mercurial/commands.py
--- a/hgext/patchbomb.py	Thu Mar 22 23:37:44 2007 -0500
+++ b/hgext/patchbomb.py	Fri Mar 23 01:04:21 2007 -0500
@@ -86,7 +86,13 @@
     The message contains two or three body parts.  First, the rest of
     the changeset description.  Next, (optionally) if the diffstat
     program is installed, the result of running diffstat on the patch.
-    Finally, the patch itself, as generated by "hg export".'''
+    Finally, the patch itself, as generated by "hg export".
+
+    With --outgoing, emails will be generated for patches not
+    found in the target repository (or only those which are
+    ancestors of the specified revisions if any are provided)
+    '''
+
     def prompt(prompt, default = None, rest = ': ', empty_ok = False):
         if default: prompt += ' [%s]' % default
         prompt += rest
@@ -165,6 +171,36 @@
         msg['X-Mercurial-Node'] = node
         return msg
 
+    def outgoing(dest, revs):
+        '''Return the revisions present locally but not in dest'''
+        dest = ui.expandpath(dest or 'default-push', dest or 'default')
+        revs = [repo.lookup(rev) for rev in revs]
+        other = hg.repository(ui, dest)
+        ui.status(_('comparing with %s\n') % dest)
+        o = repo.findoutgoing(other)
+        if not o:
+            ui.status(_("no changes found\n"))
+            return []
+        o = repo.changelog.nodesbetween(o, revs or None)[0]
+        return [str(repo.changelog.rev(r)) for r in o]
+
+    # option handling
+    commands.setremoteconfig(ui, opts)
+    if opts.get('outgoing'):
+        if len(revs) > 1:
+            raise util.Abort(_("too many destinations"))
+        dest = revs and revs[0] or None
+        revs = []
+
+    if opts.get('rev'):
+        if revs:
+            raise util.Abort(_('use only one form to specify the revision'))
+        revs = opts.get('rev')
+
+    if opts.get('outgoing'):
+        revs = outgoing(dest, opts.get('rev'))
+
+    # start
     start_time = util.makedate()
 
     def genmsgid(id):
@@ -299,7 +335,9 @@
       ('', 'plain', None, 'omit hg patch header'),
       ('n', 'test', None, 'print messages that would be sent'),
       ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
+      ('o', 'outgoing', None, _('send changes not found in the target repository')),
+      ('r', 'rev', [], _('a revision to send')),
       ('s', 'subject', '', 'subject of first message (intro or single patch)'),
-      ('t', 'to', [], 'email addresses of recipients')],
-     "hg email [OPTION]... [REV]...")
+      ('t', 'to', [], 'email addresses of recipients')] + commands.remoteopts,
+     "hg email [OPTION]... [DEST]...")
     }
--- a/mercurial/commands.py	Thu Mar 22 23:37:44 2007 -0500
+++ b/mercurial/commands.py	Fri Mar 23 01:04:21 2007 -0500
@@ -1476,15 +1476,21 @@
     text/plain body parts before first diff are added to commit
     message.
 
-    If imported patch was generated by hg export, user and description
+    If the imported patch was generated by hg export, user and description
     from patch override values from message headers and body.  Values
     given on command line with -m and -u override these.
 
+    If --exact is specified, import will set the working directory
+    to the parent of each patch before applying it, and will abort
+    if the resulting changeset has a different ID than the one
+    recorded in the patch. This may happen due to character set
+    problems or other deficiencies in the text patch format.
+
     To read a patch from standard input, use patch name "-".
     """
     patches = (patch1,) + patches
 
-    if not opts['force']:
+    if opts.get('exact') or not opts['force']:
         bail_if_changed(repo)
 
     d = opts["base"]
@@ -1498,10 +1504,10 @@
 
         if pf == '-':
             ui.status(_("applying patch from stdin\n"))
-            tmpname, message, user, date = patch.extract(ui, sys.stdin)
+            tmpname, message, user, date, nodeid, p1, p2 = patch.extract(ui, sys.stdin)
         else:
             ui.status(_("applying %s\n") % p)
-            tmpname, message, user, date = patch.extract(ui, file(pf))
+            tmpname, message, user, date, nodeid, p1, p2 = patch.extract(ui, file(pf))
 
         if tmpname is None:
             raise util.Abort(_('no diffs found'))
@@ -1519,13 +1525,36 @@
                 message = None
             ui.debug(_('message:\n%s\n') % message)
 
+            wp = repo.workingctx().parents()
+            if opts.get('exact'):
+                if not nodeid or not p1:
+                    raise util.Abort(_('not a mercurial patch'))
+                p1 = repo.lookup(p1)
+                p2 = repo.lookup(p2 or hex(nullid))
+
+                if p1 != wp[0].node():
+                    hg.clean(repo, p1, wlock=wlock)
+                repo.dirstate.setparents(p1, p2)
+            elif p2:
+                try:
+                    p1 = repo.lookup(p1)
+                    p2 = repo.lookup(p2)
+                    if p1 == wp[0].node():
+                        repo.dirstate.setparents(p1, p2)
+                except RepoError:
+                    pass
+
             files = {}
             try:
                 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
                                    files=files)
             finally:
                 files = patch.updatedir(ui, repo, files, wlock=wlock)
-            repo.commit(files, message, user, date, wlock=wlock, lock=lock)
+            n = repo.commit(files, message, user, date, wlock=wlock, lock=lock)
+            if opts.get('exact'):
+                if hex(n) != nodeid:
+                    repo.rollback()
+                    raise util.Abort(_('patch is damaged or loses information'))
         finally:
             os.unlink(tmpname)
 
@@ -2771,7 +2800,9 @@
              'meaning as the corresponding patch option')),
           ('b', 'base', '', _('base path')),
           ('f', 'force', None,
-           _('skip check for outstanding uncommitted changes'))] + commitopts,
+           _('skip check for outstanding uncommitted changes')),
+          ('', 'exact', None,
+           _('apply patch to the nodes from which it was generated'))] + commitopts,
          _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
     "incoming|in": (incoming,
          [('M', 'no-merges', None, _('do not show merges')),
--- a/mercurial/patch.py	Thu Mar 22 23:37:44 2007 -0500
+++ b/mercurial/patch.py	Fri Mar 23 01:04:21 2007 -0500
@@ -33,11 +33,11 @@
 def extract(ui, fileobj):
     '''extract patch from data read from fileobj.
 
-    patch can be normal patch or contained in email message.
+    patch can be a normal patch or contained in an email message.
 
-    return tuple (filename, message, user, date). any item in returned
-    tuple can be None.  if filename is None, fileobj did not contain
-    patch. caller must unlink filename when done.'''
+    return tuple (filename, message, user, date, node, p1, p2).
+    Any item in the returned tuple can be None. If filename is None,
+    fileobj did not contain a patch. Caller must unlink filename when done.'''
 
     # attempt to detect the start of a patch
     # (this heuristic is borrowed from quilt)
@@ -54,6 +54,8 @@
         user = msg['From']
         # should try to parse msg['Date']
         date = None
+        nodeid = None
+        parents = []
 
         if message:
             if message.startswith('[PATCH'):
@@ -97,6 +99,10 @@
                             ui.debug('From: %s\n' % user)
                         elif line.startswith("# Date "):
                             date = line[7:]
+                        elif line.startswith("# Node ID "):
+                            nodeid = line[10:]
+                        elif line.startswith("# Parent "):
+                            parents.append(line[10:])
                     elif line == '---' and 'git-send-email' in msg['X-Mailer']:
                         ignoretext = True
                     if not line.startswith('# ') and not ignoretext:
@@ -117,8 +123,10 @@
     tmpfp.close()
     if not diffs_seen:
         os.unlink(tmpname)
-        return None, message, user, date
-    return tmpname, message, user, date
+        return None, message, user, date, None, None, None
+    p1 = parents and parents.pop(0) or None
+    p2 = parents and parents.pop(0) or None
+    return tmpname, message, user, date, nodeid, p1, p2
 
 GP_PATCH  = 1 << 0  # we have to run patch
 GP_FILTER = 1 << 1  # there's some copy/rename operation