Mercurial > evolve
view hgext/qsync.py @ 190:f111e19b763f
fix qsync-enable.sh
extension name is qsync not obsolete (terrible terrible trouble)
author | Pierre-Yves David <pierre-yves.david@logilab.fr> |
---|---|
date | Wed, 28 Mar 2012 11:55:59 +0200 |
parents | c088f503cc97 |
children | ccbadfae1d06 |
line wrap: on
line source
import re from cStringIO import StringIO 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 import re import json ### old compat code ############################# BRANCHNAME="qsubmit2" OLDBRANCHNAME="pyves-qsubmit" ### 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 = None review = 'edit' if opts['review_all']: review = 'all' mqrepo = repo.mq.qrepo() try: parent = mqrepo[BRANCHNAME] except error.RepoLookupError: try: parent = mqrepo[OLDBRANCHNAME] except error.RepoLookupError: parent = initqsubmit(mqrepo) store, data, touched = fillstore(repo, parent) 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 = [] if review: for patch_name in touched: try: store.getfile(patch_name) review_list.append(patch_name) except IOError: pass if review: message += '\n'.join('* %s ready for review' % x for x in review_list) 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() 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 not previous data exists""" data = [] for ctx in repo.set('draft() - obsolete()'): name = makename(ctx) data.append([ctx.hex(), makename(ctx)]) 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() obsolete = extensions.find('obsolete') for oldhex, oldname in olddata: if oldhex in usedold: continue # no duplicate usedold.add(oldhex) oldname = str(oldname) oldnode = bin(oldhex) newnodes = obsolete.newerversion(repo, oldnode) if newnodes: newnodes = [n for n in newnodes if n] # remove killing if len(newnodes) > 1: 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] return finaldata, stouched def sort_key(ctx): """ctx sort key: (branch, rev)""" return (ctx.branch(), ctx.rev()) def fillstore(repo, basemqctx): """file 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 series ='\n'.join(d[1] for d in data) + '\n' store.setfile('qsubmitdata', json.dumps(data, indent=True), (False, False)) finally: store.close() return store, data, touched def initqsubmit(mqrepo): """create initial qsubmit branch""" store = patch.filestore() try: files = set() 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]