[PATCH 4 of 4] mq: Add --rev argument to qimport, to adopt existing changesets

Brendan Cully brendan at kublai.com
Tue Sep 19 07:15:28 UTC 2006


Here is another update with a more comprehensive test suite (and no
custom revrange function).

On Monday, 18 September 2006 at 23:38, Brendan Cully wrote:
> Wait on this for a little bit - I've got an improved patch (better
> validation that the revision range to be imported is sane) but need to
> update the test suite...
> 
> On Thursday, 14 September 2006 at 11:56, Brendan Cully wrote:
> > Here's an updated patch, which uses cmdutil.revpair (so it depends on
> > the revrange refactoring patch I sent out a bit earlier). The code
> > savings isn't much, but I guess it's good for consistency.
> > 
> > I've also added some tests.
> > 
> > On Thursday, 14 September 2006 at 10:30, Brendan Cully wrote:
> > > On Thursday, 14 September 2006 at 15:34, Benoit Boissinot wrote:
> > > > On 9/14/06, Brendan Cully <brendan at kublai.com> wrote:
> > > > ># HG changeset patch
> > > > ># User Brendan Cully <brendan at kublai.com>
> > > > ># Date 1158190863 25200
> > > > ># Node ID bac8e38ab09506fab3037b56f362b6624ff98972
> > > > ># Parent  76bc1240a1d759ad50f2c64377e3198be3bc5bbe
> > > > >mq: Add --rev argument to qimport, to adopt existing changesets.
> > > > >
> > > > >[snip]
> > > > >+        def revrange(rev):
> > > > >+            revs = [repo.lookup(r) for r in rev.split(':', 1)]
> > > > >+            if len(revs) == 2:
> > > > >+                s, e = [repo.changelog.rev(r) for r in revs]
> > > > >+                if s < e:
> > > > >+                    s, e = e, s
> > > > >+                return range(s, e-1, -1)
> > > > >+            else:
> > > > >+                return [repo.changelog.rev(revs[0])]
> > > > >+
> > > > How is it different from commands.revrange ?
> > > 
> > > It's a bit simpler, since it only handles a single rev argument
> > > (qimport can't handle gaps in the revision list). I suppose revpair
> > > would also do the job - should I move revpair/revfix into util?
> > > 
> > > > Is it possible to have some tests for this feature ?
> > > 
> > > Sure, I'll add some a little later.
> > > 
> > > Thanks for looking these over.
> 
> > # HG changeset patch
> > # User Brendan Cully <brendan at kublai.com>
> > # Date 1158260031 25200
> > # Node ID b0ce92d747c52d4c6ba849c60bed7334091a905b
> > # Parent  eeaf9bcdfa25c16efcb9d7b045ba047da03866a6
> > mq: Add --rev argument to qimport, to adopt existing changesets.
> > 
> > diff --git a/hgext/mq.py b/hgext/mq.py
> > +++ b/hgext/mq.py
> > @@ -1276,39 +1276,94 @@ class queue:
> >              self.ui.write("No patches applied\n")
> >              return 1
> >  
> > -    def qimport(self, repo, files, patch=None, existing=None, force=None):
> > -        if len(files) > 1 and patch:
> > +    def qimport(self, repo, files, patchname=None, rev=None, existing=None,
> > +                force=None):
> > +        def revrange(rev):
> > +            revs = [repo.changelog.rev(r)
> > +                    for r in cmdutil.revpair(self.ui, repo, [rev]) if r]
> > +            if len(revs) == 2:
> > +                s, e = revs
> > +                if s < e:
> > +                    s, e = e, s
> > +                return range(s, e-1, -1)
> > +            else:
> > +                return revs
> > +
> > +        revs = []
> > +        if rev:
> > +            if files:
> > +                raise util.Abort(_('option "-r" not valid when importing '
> > +                                   'files'))
> > +            revs = revrange(rev)
> > +        if (len(files) > 1 or len(revs) > 1) and patchname:
> >              raise util.Abort(_('option "-n" not valid when importing multiple '
> > -                               'files'))
> > +                               'patches'))
> >          i = 0
> >          added = []
> > +        if revs:
> > +            # ensure we don't adopt a changeset below a non-mq changeset
> > +            # or reimport a changeset already in mq
> > +            if self.applied:
> > +                baserev = repo.changelog.rev(revlog.bin(self.applied[0].rev))
> > +                if revs[0] >= baserev:
> > +                    raise util.Abort(_('revision %d is already in mq')
> > +                                     % revs[0])
> > +                if baserev - revs[0] != 1:
> > +                    raise util.Abort(_('cannot import revision under non-mq '
> > +                                       'revision %d') % (revs[0] + 1))
> > +            for r in revs:
> > +                p1, p2 = repo.changelog.parentrevs(r)
> > +                n = repo.changelog.node(r)
> > +                if p2 != -1:
> > +                    raise util.Abort(_('cannot import merge revision %d') % r)
> > +                if p1 != r - 1:
> > +                    raise util.Abort(_('cannot import nonlinear revision %d') % r)
> > +                if not patchname:
> > +                    patchname = '%d.diff' % r
> > +                if patchname in self.series:
> > +                    raise util.Abort(_('patch %s is already in the series file')
> > +                                     % patchname)
> > +                self.full_series.insert(0, patchname)
> > +
> > +                patchf = self.opener(patchname, "w")
> > +                patch.export(repo, [n], fp=patchf, opts=self.diffopts())
> > +                patchf.close()
> > +
> > +                se = statusentry(revlog.hex(n), patchname)
> > +                self.applied.insert(0, se)
> > +
> > +                added.append(patchname)
> > +                patchname = None
> > +            self.parse_series()
> > +            self.applied_dirty = 1
> > +
> >          for filename in files:
> >              if existing:
> > -                if not patch:
> > -                    patch = filename
> > -                if not os.path.isfile(self.join(patch)):
> > -                    raise util.Abort(_("patch %s does not exist") % patch)
> > +                if not patchname:
> > +                    patchname = filename
> > +                if not os.path.isfile(self.join(patchname)):
> > +                    raise util.Abort(_("patch %s does not exist") % patchname)
> >              else:
> >                  try:
> >                      text = file(filename).read()
> >                  except IOError:
> > -                    raise util.Abort(_("unable to read %s") % patch)
> > -                if not patch:
> > -                    patch = os.path.split(filename)[1]
> > -                if not force and os.path.exists(self.join(patch)):
> > -                    raise util.Abort(_('patch "%s" already exists') % patch)
> > -                patchf = self.opener(patch, "w")
> > +                    raise util.Abort(_("unable to read %s") % patchname)
> > +                if not patchname:
> > +                    patchname = os.path.basename(filename)
> > +                if not force and os.path.exists(self.join(patchname)):
> > +                    raise util.Abort(_('patch "%s" already exists') % patchname)
> > +                patchf = self.opener(patchname, "w")
> >                  patchf.write(text)
> > -            if patch in self.series:
> > +            if patchname in self.series:
> >                  raise util.Abort(_('patch %s is already in the series file')
> > -                                 % patch)
> > +                                 % patchname)
> >              index = self.full_series_end() + i
> > -            self.full_series[index:index] = [patch]
> > +            self.full_series[index:index] = [patchname]
> >              self.parse_series()
> > -            self.ui.warn("adding %s to series file\n" % patch)
> > +            self.ui.warn("adding %s to series file\n" % patchname)
> >              i += 1
> > -            added.append(patch)
> > -            patch = None
> > +            added.append(patchname)
> > +            patchname = None
> >          self.series_dirty = 1
> >          qrepo = self.qrepo()
> >          if qrepo:
> > @@ -1342,10 +1397,22 @@ def unapplied(ui, repo, patch=None, **op
> >          ui.write("%s\n" % p)
> >  
> >  def qimport(ui, repo, *filename, **opts):
> > -    """import a patch"""
> > +    """import a patch
> > +
> > +    The patch will have the same name as its source file unless you
> > +    give it a new one with --name.
> > +
> > +    You can register an existing patch inside the patch directory
> > +    with the --existing flag.
> > +
> > +    With --force, an existing patch of the same name will be overwritten.
> > +
> > +    An existing changeset may be placed under mq control with --rev
> > +    (e.g. qimport --rev tip -n patch will place tip under mq control).
> > +    """
> >      q = repo.mq
> > -    q.qimport(repo, filename, patch=opts['name'],
> > -              existing=opts['existing'], force=opts['force'])
> > +    q.qimport(repo, filename, patchname=opts['name'],
> > +              existing=opts['existing'], force=opts['force'], rev=opts['rev'])
> >      q.save_dirty()
> >      return 0
> >  
> > @@ -1952,8 +2019,9 @@ cmdtable = {
> >          (qimport,
> >           [('e', 'existing', None, 'import file in patch dir'),
> >            ('n', 'name', '', 'patch file name'),
> > -          ('f', 'force', None, 'overwrite existing files')],
> > -         'hg qimport [-e] [-n NAME] [-f] FILE...'),
> > +          ('f', 'force', None, 'overwrite existing files'),
> > +          ('r', 'rev', '', 'place existing revisions under mq control')],
> > +         'hg qimport [-e] [-n NAME] [-f] [-r REV] FILE...'),
> >      "^qinit":
> >          (init,
> >           [('c', 'create-repo', None, 'create queue repository')],
> > diff --git a/tests/test-mq-qimport b/tests/test-mq-qimport
> > new file mode 100755
> > +++ b/tests/test-mq-qimport
> > @@ -0,0 +1,27 @@
> > +#!/bin/sh
> > +
> > +echo "[extensions]" >> $HGRCPATH
> > +echo "mq=" >> $HGRCPATH
> > +
> > +hg init a
> > +cd a
> > +
> > +echo 'base' > base
> > +hg ci -Ambase -d '1 0'
> > +echo 'b1' >> base
> > +hg ci -mb1 -d '2 0'
> > +echo 'b2' >> base
> > +hg ci -mb2 -d '3 0'
> > +echo 'b3' >> base
> > +hg ci -mb3 -d '4 0'
> > +
> > +hg qinit
> > +hg qimport -r3
> > +hg qseries -s
> > +hg qimport -r0
> > +hg qimport -r1:2
> > +hg qpop -a
> > +hg log --template '{rev} {desc}\n'
> > +hg qpush -a
> > +hg log --template '{rev} {desc}\n'
> > +cat base
> > diff --git a/tests/test-mq-qimport.out b/tests/test-mq-qimport.out
> > new file mode 100644
> > +++ b/tests/test-mq-qimport.out
> > @@ -0,0 +1,17 @@
> > +adding base
> > +3.diff: b3
> > +abort: cannot import revision under non-mq revision 1
> > +Patch queue now empty
> > +0 base
> > +applying 1.diff
> > +applying 2.diff
> > +applying 3.diff
> > +Now at: 3.diff
> > +3 b3
> > +2 b2
> > +1 b1
> > +0 base
> > +base
> > +b1
> > +b2
> > +b3
> 
> > _______________________________________________
> > Mercurial mailing list
> > Mercurial at selenic.com
> > http://selenic.com/mailman/listinfo/mercurial
> 
> _______________________________________________
> Mercurial mailing list
> Mercurial at selenic.com
> http://selenic.com/mailman/listinfo/mercurial
-------------- next part --------------
# HG changeset patch
# User Brendan Cully <brendan at kublai.com>
# Date 1158650041 25200
# Node ID e805d5be56b20dd2d02c9e88f3aa825a0966529d
# Parent  0c0e9adfc6ae5eaf9b00bfcf5550dea0e0923105
mq: Add --rev argument to qimport, to adopt existing changesets.

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -1247,39 +1247,102 @@ class queue:
             p = str(self.series.index(pname)) + " " + pname
         return p
 
-    def qimport(self, repo, files, patch=None, existing=None, force=None):
-        if len(files) > 1 and patch:
+    def qimport(self, repo, files, patchname=None, rev=None, existing=None,
+                force=None):
+        revs = []
+        if rev:
+            if files:
+                raise util.Abort(_('option "-r" not valid when importing '
+                                   'files'))
+            revs = [int(r) for r in cmdutil.revrange(self.ui, repo, rev)]
+            revs.sort()
+            if revs[0] < revs[-1]:
+                revs.reverse()
+        if (len(files) > 1 or len(revs) > 1) and patchname:
             raise util.Abort(_('option "-n" not valid when importing multiple '
-                               'files'))
+                               'patches'))
         i = 0
         added = []
+        if revs:
+            # If mq patches are applied, we can only import revisions
+            # that form a linear path to qbase.
+            # Otherwise, they should form a linear path to a head.
+            heads = repo.changelog.heads(repo.changelog.node(revs[-1]))
+            if len(heads) > 1:
+                raise util.Abort(_('revision %d is the root of more than one '
+                                   'branch') % revs[-1])
+            if self.applied:
+                base = revlog.hex(repo.changelog.node(revs[0]))
+                if base in [n.rev for n in self.applied]:
+                    raise util.Abort(_('revision %d is already managed')
+                                     % revs[0])
+                if heads != [revlog.bin(self.applied[-1].rev)]:
+                    raise util.Abort(_('revision %d is not the parent of '
+                                       'the queue') % revs[0])
+                base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
+                lastparent = repo.changelog.parentrevs(base)[0]
+            else:
+                if heads != [repo.changelog.node(revs[0])]:
+                    raise util.Abort(_('revision %d has unmanaged children')
+                                     % revs[0])
+                lastparent = None
+
+            for r in revs:
+                p1, p2 = repo.changelog.parentrevs(r)
+                n = repo.changelog.node(r)
+                if p2 != -1:
+                    raise util.Abort(_('cannot import merge revision %d') % r)
+                if lastparent and lastparent != r:
+                    raise util.Abort(_('revision %d is not the parent of %d')
+                                     % (r, lastparent))
+                lastparent = p1
+
+                if not patchname:
+                    patchname = '%d.diff' % r
+                if patchname in self.series:
+                    raise util.Abort(_('patch %s is already in the series file')
+                                     % patchname)
+                self.full_series.insert(0, patchname)
+
+                patchf = self.opener(patchname, "w")
+                patch.export(repo, [n], fp=patchf, opts=self.diffopts())
+                patchf.close()
+
+                se = statusentry(revlog.hex(n), patchname)
+                self.applied.insert(0, se)
+
+                added.append(patchname)
+                patchname = None
+            self.parse_series()
+            self.applied_dirty = 1
+
         for filename in files:
             if existing:
-                if not patch:
-                    patch = filename
-                if not os.path.isfile(self.join(patch)):
-                    raise util.Abort(_("patch %s does not exist") % patch)
+                if not patchname:
+                    patchname = filename
+                if not os.path.isfile(self.join(patchname)):
+                    raise util.Abort(_("patch %s does not exist") % patchname)
             else:
                 try:
                     text = file(filename).read()
                 except IOError:
-                    raise util.Abort(_("unable to read %s") % patch)
-                if not patch:
-                    patch = os.path.split(filename)[1]
-                if not force and os.path.exists(self.join(patch)):
-                    raise util.Abort(_('patch "%s" already exists') % patch)
-                patchf = self.opener(patch, "w")
+                    raise util.Abort(_("unable to read %s") % patchname)
+                if not patchname:
+                    patchname = os.path.basename(filename)
+                if not force and os.path.exists(self.join(patchname)):
+                    raise util.Abort(_('patch "%s" already exists') % patchname)
+                patchf = self.opener(patchname, "w")
                 patchf.write(text)
-            if patch in self.series:
+            if patchname in self.series:
                 raise util.Abort(_('patch %s is already in the series file')
-                                 % patch)
+                                 % patchname)
             index = self.full_series_end() + i
-            self.full_series[index:index] = [patch]
+            self.full_series[index:index] = [patchname]
             self.parse_series()
-            self.ui.warn("adding %s to series file\n" % patch)
+            self.ui.warn("adding %s to series file\n" % patchname)
             i += 1
-            added.append(patch)
-            patch = None
+            added.append(patchname)
+            patchname = None
         self.series_dirty = 1
         qrepo = self.qrepo()
         if qrepo:
@@ -1323,10 +1386,22 @@ def unapplied(ui, repo, patch=None, **op
     q.qseries(repo, start=start, summary=opts.get('summary'))
 
 def qimport(ui, repo, *filename, **opts):
-    """import a patch"""
+    """import a patch
+
+    The patch will have the same name as its source file unless you
+    give it a new one with --name.
+
+    You can register an existing patch inside the patch directory
+    with the --existing flag.
+
+    With --force, an existing patch of the same name will be overwritten.
+
+    An existing changeset may be placed under mq control with --rev
+    (e.g. qimport --rev tip -n patch will place tip under mq control).
+    """
     q = repo.mq
-    q.qimport(repo, filename, patch=opts['name'],
-              existing=opts['existing'], force=opts['force'])
+    q.qimport(repo, filename, patchname=opts['name'],
+              existing=opts['existing'], force=opts['force'], rev=opts['rev'])
     q.save_dirty()
     return 0
 
@@ -1956,8 +2031,9 @@ cmdtable = {
         (qimport,
          [('e', 'existing', None, 'import file in patch dir'),
           ('n', 'name', '', 'patch file name'),
-          ('f', 'force', None, 'overwrite existing files')],
-         'hg qimport [-e] [-n NAME] [-f] FILE...'),
+          ('f', 'force', None, 'overwrite existing files'),
+          ('r', 'rev', [], 'place existing revisions under mq control')],
+         'hg qimport [-e] [-n NAME] [-f] [-r REV] FILE...'),
     "^qinit":
         (init,
          [('c', 'create-repo', None, 'create queue repository')],
diff --git a/tests/test-mq-qimport b/tests/test-mq-qimport
new file mode 100755
--- /dev/null
+++ b/tests/test-mq-qimport
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+echo "[extensions]" >> $HGRCPATH
+echo "mq=" >> $HGRCPATH
+
+hg init a
+cd a
+
+echo 'base' > base
+hg ci -Ambase -d '1 0'
+echo 'b1' >> base
+hg ci -mb1 -d '2 0'
+echo 'b2' >> base
+hg ci -mb2 -d '3 0'
+echo 'b3' >> base
+hg ci -mb3 -d '4 0'
+
+hg up -C 0
+echo c1 >> base
+hg ci -mc1 -d '5 0'
+hg up -C 3
+echo b4 >> base
+hg ci -mb4 -d '6 0'
+
+hg qinit
+hg qimport -r0
+hg qimport -r4
+hg qimport -r3
+rm .hg/patches/*
+hg qimport -r3
+hg qimport -r3 -r5
+hg qimport -r1:2
+hg qimport -r3
+hg qseries -s
+# this causes a strip
+hg qpop -a 2> /dev/null
+hg log --template '{rev} {desc}\n'
+hg qpush -a
+hg log --template '{rev} {desc}\n'
+cat base
diff --git a/tests/test-mq-qimport.out b/tests/test-mq-qimport.out
new file mode 100644
--- /dev/null
+++ b/tests/test-mq-qimport.out
@@ -0,0 +1,36 @@
+adding base
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+abort: revision 0 is the root of more than one branch
+abort: revision 3 is not the parent of the queue
+abort: revision 3 has unmanaged children
+abort: revision 3 is already managed
+1.diff: b1
+2.diff: b2
+3.diff: b3
+5.diff: b4
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+(run 'hg update' to get a working copy)
+Patch queue now empty
+1 c1
+0 base
+applying 1.diff
+applying 2.diff
+applying 3.diff
+applying 5.diff
+Now at: 5.diff
+5 b4
+4 b3
+3 b2
+2 b1
+1 c1
+0 base
+base
+b1
+b2
+b3
+b4


More information about the Mercurial mailing list