New mq command qfold: Merge patches into the current patch.
authorBrendan Cully <brendan@kublai.com>
Mon, 31 Jul 2006 20:33:56 -0700
changeset 2748 752b9475a700
parent 2747 0016fc748f61
child 2749 d13e4ffaa79d
child 2753 84218111e80f
New mq command qfold: Merge patches into the current patch. Patches should be in the series file but not yet applied.
hgext/mq.py
tests/test-mq.out
--- a/hgext/mq.py	Mon Jul 31 18:39:31 2006 -0700
+++ b/hgext/mq.py	Mon Jul 31 20:33:56 2006 -0700
@@ -1,3 +1,4 @@
+
 # queue.py - patch queues for mercurial
 #
 # Copyright 2005 Chris Mason <mason@suse.com>
@@ -270,6 +271,48 @@
                 return (err, head)
         return (0, head)
 
+    def patch(self, repo, patchfile):
+        '''Apply patchfile  to the working directory.
+        patchfile: file name of patch'''
+        try:
+            pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
+            f = os.popen("%s -d '%s' -p1 --no-backup-if-mismatch < '%s'" %
+                         (pp, repo.root, 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(l[14:])
+                # when patch finds a space in the file name, it puts
+                # single quotes around the filename.  strip them off
+                if pf[0] == "'" and pf[-1] == "'":
+                    pf = pf[1:-1]
+                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 (not f.close(), files, fuzz)
+        
     def apply(self, repo, series, list=False, update_status=True,
               strict=False, patchdir=None, merge=None, wlock=None):
         # TODO unify with commands.py
@@ -299,44 +342,8 @@
                     message.append("\nimported patch %s" % patch)
                 message = '\n'.join(message)
 
-            try:
-                pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
-                f = os.popen("%s -d '%s' -p1 --no-backup-if-mismatch < '%s'" %
-                             (pp, repo.root, pf))
-            except:
-                self.ui.warn("patch failed, unable to continue (try -v)\n")
-                err = 1
-                break
-            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(l[14:])
-                    # when patch finds a space in the file name, it puts
-                    # single quotes around the filename.  strip them off
-                    if pf[0] == "'" and pf[-1] == "'":
-                        pf = pf[1:-1]
-                    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')
-            patcherr = f.close()
+            (patcherr, files, fuzz) = self.patch(repo, pf)
+            patcherr = not patcherr
 
             if merge and len(files) > 0:
                 # Mark as merged and update dirstate parent info
@@ -1289,6 +1296,47 @@
     repo.mq.diff(repo, list(files))
     return 0
 
+def fold(ui, repo, *files):
+    """fold the named patches into the current patch
+    Patches must not yet be applied."""
+    q = repo.mq
+
+    if not files:
+        raise util.Abort(_('qfold requires at least one patch name'))
+    if not q.check_toppatch(repo):
+        raise util.Abort(_('No patches applied\n'))
+
+    parent = q.lookup('qtip')
+    patches = []
+    messages = []
+    for f in files:
+        patch = q.lookup(f)
+        if patch in patches or patch == parent:
+            self.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)
+
+    for patch in patches:
+        messages.append(q.readheaders(patch)[0])
+        pf = os.path.join(q.path, patch)
+        (patchsuccess, files, fuzz) = q.patch(repo, pf)
+        if not patchsuccess:
+            raise util.Abort(_('Error folding patch %s') % patch)
+
+    message = q.readheaders(parent)[0]
+    for msg in messages:
+        message.append('* * *')
+        message.extend(msg)
+    message = '\n'.join(message)
+
+    q.refresh(repo, msg=message)
+
+    for patch in patches:
+        q.delete(repo, patch)
+
+    q.save_dirty()
+
 def header(ui, repo, patch=None):
     """Print the header of the topmost or specified patch"""
     q = repo.mq
@@ -1462,6 +1510,7 @@
          'hg qcommit [OPTION]... [FILE]...'),
     "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
     "qdelete": (delete, [], 'hg qdelete PATCH'),
+    'qfold': (fold, [], 'hg qfold PATCH...'),
     'qheader': (header, [],
                 _('hg qheader [PATCH]')),
     "^qimport":
--- a/tests/test-mq.out	Mon Jul 31 18:39:31 2006 -0700
+++ b/tests/test-mq.out	Mon Jul 31 20:33:56 2006 -0700
@@ -29,6 +29,7 @@
  qcommit      commit changes in the queue repository
  qdelete      remove a patch from the series file
  qdiff        diff of the current patch
+ qfold        fold the named patches into the current patch
  qheader      Print the header of the topmost or specified patch
  qimport      import a patch
  qinit        init a new queue repository