--- a/hgext/qsync.py Tue Jan 07 15:52:47 2014 -0800
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,266 +0,0 @@
-# Copyright 2011 Logilab SA <contact@logilab.fr>
-"""synchronize patches queues and evolving changesets"""
-
-import re
-from cStringIO import StringIO
-import json
-
-from mercurial.i18n import _
-from mercurial import commands
-from mercurial import patch
-from mercurial import util
-from mercurial.node import nullid, hex, short, bin
-from mercurial import cmdutil
-from mercurial import hg
-from mercurial import scmutil
-from mercurial import error
-from mercurial import extensions
-from mercurial import phases
-from mercurial import obsolete
-
-### old compat code
-#############################
-
-BRANCHNAME="qsubmit2"
-
-### new command
-#############################
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-@command('^qsync|sync',
- [
- ('a', 'review-all', False, _('mark all touched patches ready for review (no editor)')),
- ],
- '')
-def cmdsync(ui, repo, **opts):
- '''Export draft changeset as mq patch in a mq patches repository commit.
-
- This command get all changesets in draft phase and create an mq changeset:
-
- * on a "qsubmit2" branch (based on the last changeset)
-
- * one patch per draft changeset
-
- * a series files listing all generated patch
-
- * qsubmitdata holding useful information
-
- It does use obsolete relation to update patches that already existing in the qsubmit2 branch.
-
- Already existing patch which became public, draft or got killed are remove from the mq repo.
-
- Patch name are generated using the summary line for changeset description.
-
- .. warning:: Series files is ordered topologically. So two series with
- interleaved changeset will appear interleaved.
- '''
-
- review = 'edit'
- if opts['review_all']:
- review = 'all'
- mqrepo = repo.mq.qrepo()
- if mqrepo is None:
- raise util.Abort('No patches repository')
-
- try:
- parent = mqrepo[BRANCHNAME]
- except error.RepoLookupError:
- parent = initqsubmit(mqrepo)
- store, data, touched = fillstore(repo, parent)
- try:
- if not touched:
- raise util.Abort('Nothing changed')
- files = ['qsubmitdata', 'series'] + touched
- # mark some as ready for review
- message = 'qsubmit commit\n\n'
- review_list = []
- applied_list = []
- if review:
- olddata = get_old_data(parent)
- oldfiles = dict([(name, bin(ctxhex)) for ctxhex, name in olddata])
-
- for patch_name in touched:
- try:
- store.getfile(patch_name)
- review_list.append(patch_name)
- except IOError:
- oldnode = oldfiles[patch_name]
- newnodes = obsolete.successorssets(repo, oldnode)
- if newnodes:
- newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
- if not newnodes:
- # changeset has been killed (eg. reject)
- pass
- else:
- assert len(newnodes) == 1 # conflict!!!
- newnode = newnodes[0]
- assert len(newnode) == 1 # split unsupported for now
- newnode = list(newnode)[0]
- # XXX unmanaged case where a cs is obsoleted by an unavailable one
- #if newnode.node() not in repo.changelog.nodemap:
- # raise util.Abort('%s is obsoleted by an unknown node %s'% (oldnode, newnode))
- ctx = repo[newnode]
- if ctx.phase() == phases.public:
- # applied
- applied_list.append(patch_name)
- elif ctx.phase() == phases.secret:
- # already exported changeset is now secret
- repo.ui.warn("An already exported changeset is now secret!!!")
- else:
- # draft
- assert False, "Should be exported"
-
- if review:
- if applied_list:
- message += '\n'.join('* applied %s' % x for x in applied_list) + '\n'
- if review_list:
- message += '\n'.join('* %s ready for review' % x for x in review_list) + '\n'
- memctx = patch.makememctx(mqrepo, (parent.node(), nullid),
- message,
- None,
- None,
- parent.branch(), files, store,
- editor=None)
- if review == 'edit':
- memctx._text = cmdutil.commitforceeditor(mqrepo, memctx, [])
- mqrepo.savecommitmessage(memctx.description())
- n = memctx.commit()
- finally:
- store.close()
- return 0
-
-
-def makename(ctx):
- """create a patch name form a changeset"""
- descsummary = ctx.description().splitlines()[0]
- descsummary = re.sub(r'\s+', '_', descsummary)
- descsummary = re.sub(r'\W+', '', descsummary)
- if len(descsummary) > 45:
- descsummary = descsummary[:42] + '.'
- return '%s-%s.diff' % (ctx.branch().upper(), descsummary)
-
-
-def get_old_data(mqctx):
- """read qsubmit data to fetch previous export data
-
- get old data from the content of an mq commit"""
- try:
- old_data = mqctx['qsubmitdata']
- return json.loads(old_data.data())
- except error.LookupError:
- return []
-
-def get_current_data(repo):
- """Return what would be exported if no previous data exists"""
- data = []
- for ctx in repo.set('draft() - (obsolete() + merge())'):
- name = makename(ctx)
- data.append([ctx.hex(), makename(ctx)])
- merges = repo.revs('draft() and merge()')
- if merges:
- repo.ui.warn('ignoring %i merge\n' % len(merges))
- return data
-
-
-def patchmq(repo, store, olddata, newdata):
- """export the mq patches and return all useful data to be exported"""
- finaldata = []
- touched = set()
- currentdrafts = set(d[0] for d in newdata)
- usednew = set()
- usedold = set()
- evolve = extensions.find('evolve')
- for oldhex, oldname in olddata:
- if oldhex in usedold:
- continue # no duplicate
- usedold.add(oldhex)
- oldname = str(oldname)
- oldnode = bin(oldhex)
- newnodes = obsolete.successorssets(repo, oldnode)
- if newnodes:
- newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
- if len(newnodes) > 1:
- newnodes = [short(nodes[0]) for nodes in newnodes]
- raise util.Abort('%s have more than one newer version: %s'% (oldname, newnodes))
- if newnodes:
- # else, changeset have been killed
- newnode = list(newnodes)[0][0]
- ctx = repo[newnode]
- if ctx.hex() != oldhex and ctx.phase():
- fp = StringIO()
- cmdutil.export(repo, [ctx.rev()], fp=fp)
- data = fp.getvalue()
- store.setfile(oldname, data, (None, None))
- finaldata.append([ctx.hex(), oldname])
- usednew.add(ctx.hex())
- touched.add(oldname)
- continue
- if oldhex in currentdrafts:
- # else changeset is now public or secret
- finaldata.append([oldhex, oldname])
- usednew.add(ctx.hex())
- continue
- touched.add(oldname)
-
- for newhex, newname in newdata:
- if newhex in usednew:
- continue
- newnode = bin(newhex)
- ctx = repo[newnode]
- fp = StringIO()
- cmdutil.export(repo, [ctx.rev()], fp=fp)
- data = fp.getvalue()
- store.setfile(newname, data, (None, None))
- finaldata.append([ctx.hex(), newname])
- touched.add(newname)
- # sort by branchrev number
- finaldata.sort(key=lambda x: sort_key(repo[x[0]]))
- # sort touched too (ease review list)
- stouched = [f[1] for f in finaldata if f[1] in touched]
- stouched += [x for x in touched if x not in stouched]
- return finaldata, stouched
-
-def sort_key(ctx):
- """ctx sort key: (branch, rev)"""
- return (ctx.branch(), ctx.rev())
-
-
-def fillstore(repo, basemqctx):
- """fill store with patch data"""
- olddata = get_old_data(basemqctx)
- newdata = get_current_data(repo)
- store = patch.filestore()
- try:
- data, touched = patchmq(repo, store, olddata, newdata)
- # put all name in the series
- series ='\n'.join(d[1] for d in data) + '\n'
- store.setfile('series', series, (False, False))
-
- # export data to ease futur work
- store.setfile('qsubmitdata', json.dumps(data, indent=True),
- (False, False))
- except:
- store.close()
- raise
- return store, data, touched
-
-
-def initqsubmit(mqrepo):
- """create initial qsubmit branch"""
- store = patch.filestore()
- try:
- files = set()
- store.setfile('DO-NOT-EDIT-THIS-WORKING-COPY-BY-HAND', 'WE WARNED YOU!', (False, False))
- store.setfile('.hgignore', '^status$\n', (False, False))
- memctx = patch.makememctx(mqrepo, (nullid, nullid),
- 'qsubmit init',
- None,
- None,
- BRANCHNAME, ('.hgignore',), store,
- editor=None)
- mqrepo.savecommitmessage(memctx.description())
- n = memctx.commit()
- finally:
- store.close()
- return mqrepo[n]
--- a/tests/test-qsync.t Tue Jan 07 15:52:47 2014 -0800
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,239 +0,0 @@
- $ cat >> $HGRCPATH <<EOF
- > [defaults]
- > amend=-d "0 0"
- > [web]
- > push_ssl = false
- > allow_push = *
- > [phases]
- > publish = False
- > [alias]
- > qlog = log --template='{rev} - {node|short} {desc} ({phase})\n'
- > mqlog = log --mq --template='{rev} - {desc}\n'
- > [diff]
- > git = 1
- > unified = 0
- > [extensions]
- > hgext.rebase=
- > hgext.graphlog=
- > hgext.mq=
- > EOF
- $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
- $ echo "qsync=$(echo $(dirname $TESTDIR))/hgext/qsync.py" >> $HGRCPATH
- $ mkcommit() {
- > echo "$1" > "$1"
- > hg add "$1"
- > hg ci -m "add $1"
- > }
-
-basic sync
-
- $ hg init local
- $ cd local
- $ hg qinit -c
- $ hg qci -m "initial commit"
- $ mkcommit a
- $ mkcommit b
- $ hg qlog
- 1 - 7c3bad9141dc add b (draft)
- 0 - 1f0dee641bb7 add a (draft)
- $ hg qsync -a
- $ hg mqlog
- 2 - qsubmit commit
-
- * DEFAULT-add_a.diff ready for review
- * DEFAULT-add_b.diff ready for review
- 1 - qsubmit init
- 0 - initial commit
-
-basic sync II
-
- $ hg init local
- $ cd local
- $ hg qinit -c
- $ hg qci -m "initial commit"
- $ mkcommit a
- $ mkcommit b
- $ hg qlog
- 1 - 7c3bad9141dc add b (draft)
- 0 - 1f0dee641bb7 add a (draft)
- $ hg qsync -a
- $ hg mqlog
- 2 - qsubmit commit
-
- * DEFAULT-add_a.diff ready for review
- * DEFAULT-add_b.diff ready for review
- 1 - qsubmit init
- 0 - initial commit
-
- $ echo "b" >> b
- $ hg amend
- $ hg qsync -a
- $ hg mqlog
- 3 - qsubmit commit
-
- * DEFAULT-add_b.diff ready for review
- 2 - qsubmit commit
-
- * DEFAULT-add_a.diff ready for review
- * DEFAULT-add_b.diff ready for review
- 1 - qsubmit init
- 0 - initial commit
-
- $ hg up -r 0
- 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
- $ echo "a" >> a
- $ hg amend
- 1 new unstable changesets
- $ hg graft -O 3
- grafting revision 3
- $ hg qsync -a
- $ hg mqlog
- 4 - qsubmit commit
-
- * DEFAULT-add_a.diff ready for review
- * DEFAULT-add_b.diff ready for review
- 3 - qsubmit commit
-
- * DEFAULT-add_b.diff ready for review
- 2 - qsubmit commit
-
- * DEFAULT-add_a.diff ready for review
- * DEFAULT-add_b.diff ready for review
- 1 - qsubmit init
- 0 - initial commit
-
-sync with published changeset
-
- $ hg init local
- $ cd local
- $ hg qinit -c
- $ hg qci -m "initial commit"
- $ mkcommit a
- $ mkcommit b
- $ hg qlog
- 1 - 7c3bad9141dc add b (draft)
- 0 - 1f0dee641bb7 add a (draft)
- $ hg qsync -a
- $ hg mqlog
- 2 - qsubmit commit
-
- * DEFAULT-add_a.diff ready for review
- * DEFAULT-add_b.diff ready for review
- 1 - qsubmit init
- 0 - initial commit
-
- $ hg phase -p 0
- $ hg qsync -a
- $ hg mqlog
- 3 - qsubmit commit
-
- * applied DEFAULT-add_a.diff
- 2 - qsubmit commit
-
- * DEFAULT-add_a.diff ready for review
- * DEFAULT-add_b.diff ready for review
- 1 - qsubmit init
- 0 - initial commit
-
- $ mkcommit c
- $ mkcommit d
- $ hg qsync -a
- $ hg mqlog
- 4 - qsubmit commit
-
- * DEFAULT-add_c.diff ready for review
- * DEFAULT-add_d.diff ready for review
- 3 - qsubmit commit
-
- * applied DEFAULT-add_a.diff
- 2 - qsubmit commit
-
- * DEFAULT-add_a.diff ready for review
- * DEFAULT-add_b.diff ready for review
- 1 - qsubmit init
- 0 - initial commit
-
- $ cd ..
- $ hg qclone -U local local2
- $ cd local2
- $ hg qlog
- 3 - 47d2a3944de8 add d (draft)
- 2 - 4538525df7e2 add c (draft)
- 1 - 7c3bad9141dc add b (draft)
- 0 - 1f0dee641bb7 add a (public)
- $ hg strip -n 1 --no-backup
- $ hg up
- 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg up --mq 4
- 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ hg qseries
- DEFAULT-add_b.diff
- DEFAULT-add_c.diff
- DEFAULT-add_d.diff
- $ hg qpush
- applying DEFAULT-add_b.diff
- now at: DEFAULT-add_b.diff
- $ hg qfinish -a
- $ hg phase -p .
- $ hg qci -m "applied DEFAULT-add_b.diff"
- $ cd ../local
- $ hg pull ../local2
- pulling from ../local2
- searching for changes
- no changes found
- $ hg pull --mq ../local2/.hg/patches
- pulling from ../local2/.hg/patches
- searching for changes
- adding changesets
- adding manifests
- adding file changes
- added 1 changesets with 1 changes to 1 files
- (run 'hg update' to get a working copy)
- $ hg qlog
- 3 - 47d2a3944de8 add d (draft)
- 2 - 4538525df7e2 add c (draft)
- 1 - 7c3bad9141dc add b (public)
- 0 - 1f0dee641bb7 add a (public)
- $ hg mqlog -l 1
- 5 - applied DEFAULT-add_b.diff
- $ hg status --mq --rev tip:-2
- M series
- A DEFAULT-add_b.diff
- $ hg qsync -a
- $ hg status --mq --rev tip:-2
- M qsubmitdata
- $ hg mqlog -l 1
- 6 - qsubmit commit
-
- * applied DEFAULT-add_b.diff
- $ hg qsync -a
- abort: Nothing changed
- [255]
-
-mixed sync
-
- $ hg init local
- $ cd local
- $ hg qinit -c
- $ mkcommit a
- $ mkcommit b
- $ hg qlog
- 1 - 7c3bad9141dc add b (draft)
- 0 - 1f0dee641bb7 add a (draft)
- $ hg qsync -a
- $ hg mqlog
- 1 - qsubmit commit
-
- * DEFAULT-add_a.diff ready for review
- * DEFAULT-add_b.diff ready for review
- 0 - qsubmit init
- $ hg phase -p 0
- $ echo "b" >> b
- $ hg amend
- $ hg qsync -a
- $ hg mqlog -l 1
- 2 - qsubmit commit
-
- * applied DEFAULT-add_a.diff
- * DEFAULT-add_b.diff ready for review
-