[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