hg.py
332 lines
| 11.8 KiB
| text/x-python
|
PythonLexer
|
r4536 | # hg backend for convert extension | ||
|
r5556 | # Notes for hg->hg conversion: | ||
# | ||||
# * Old versions of Mercurial didn't trim the whitespace from the ends | ||||
# of commit messages, but new versions do. Changesets created by | ||||
# those older versions, then converted, may thus have different | ||||
# hashes for changesets that are otherwise identical. | ||||
# | ||||
# * By default, the source revision is stored in the converted | ||||
# revision. This will cause the converted revision to have a | ||||
# different identity than the source. To avoid this, use the | ||||
# following option: "--config convert.hg.saverev=false" | ||||
|
r5013 | |||
|
r4536 | import os, time | ||
|
r5014 | from mercurial.i18n import _ | ||
|
r6217 | from mercurial.repo import RepoError | ||
|
r6211 | from mercurial.node import bin, hex, nullid | ||
|
r6717 | from mercurial import hg, revlog, util, context | ||
|
r4536 | |||
|
r5013 | from common import NoRepo, commit, converter_source, converter_sink | ||
|
r4536 | |||
|
r5013 | class mercurial_sink(converter_sink): | ||
|
r4763 | def __init__(self, ui, path): | ||
|
r5440 | converter_sink.__init__(self, ui, path) | ||
|
r5173 | self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True) | ||
self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False) | ||||
|
r5260 | self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default') | ||
|
r5173 | self.lastbranch = None | ||
|
r5441 | if os.path.isdir(path) and len(os.listdir(path)) > 0: | ||
try: | ||||
self.repo = hg.repository(self.ui, path) | ||||
|
r5918 | if not self.repo.local(): | ||
raise NoRepo(_('%s is not a local Mercurial repo') % path) | ||||
|
r6217 | except RepoError, err: | ||
|
r5441 | ui.print_exc() | ||
raise NoRepo(err.args[0]) | ||||
else: | ||||
try: | ||||
ui.status(_('initializing destination %s repository\n') % path) | ||||
self.repo = hg.repository(self.ui, path, create=True) | ||||
|
r5918 | if not self.repo.local(): | ||
raise NoRepo(_('%s is not a local Mercurial repo') % path) | ||||
|
r5441 | self.created.append(path) | ||
|
r6217 | except RepoError, err: | ||
|
r5441 | ui.print_exc() | ||
raise NoRepo("could not create hg repo %s as sink" % path) | ||||
|
r5014 | self.lock = None | ||
self.wlock = None | ||||
|
r5378 | self.filemapmode = False | ||
|
r5014 | |||
def before(self): | ||||
|
r5805 | self.ui.debug(_('run hg sink pre-conversion action\n')) | ||
|
r5052 | self.wlock = self.repo.wlock() | ||
|
r5014 | self.lock = self.repo.lock() | ||
def after(self): | ||||
|
r5805 | self.ui.debug(_('run hg sink post-conversion action\n')) | ||
|
r5014 | self.lock = None | ||
self.wlock = None | ||||
|
r4536 | |||
|
r5011 | def revmapfile(self): | ||
|
r4536 | return os.path.join(self.path, ".hg", "shamap") | ||
|
r4589 | def authorfile(self): | ||
return os.path.join(self.path, ".hg", "authormap") | ||||
|
r4536 | def getheads(self): | ||
h = self.repo.changelog.heads() | ||||
|
r5017 | return [ hex(x) for x in h ] | ||
|
r4536 | |||
|
r5934 | def setbranch(self, branch, pbranches): | ||
if not self.clonebranches: | ||||
|
r5173 | return | ||
|
r5934 | setbranch = (branch != self.lastbranch) | ||
|
r5173 | self.lastbranch = branch | ||
if not branch: | ||||
branch = 'default' | ||||
|
r5934 | pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches] | ||
pbranch = pbranches and pbranches[0][1] or 'default' | ||||
|
r5173 | |||
branchpath = os.path.join(self.path, branch) | ||||
|
r5934 | if setbranch: | ||
self.after() | ||||
try: | ||||
self.repo = hg.repository(self.ui, branchpath) | ||||
except: | ||||
|
r5173 | self.repo = hg.repository(self.ui, branchpath, create=True) | ||
|
r5934 | self.before() | ||
# pbranches may bring revisions from other branches (merge parents) | ||||
# Make sure we have them, or pull them. | ||||
missings = {} | ||||
for b in pbranches: | ||||
try: | ||||
self.repo.lookup(b[0]) | ||||
except: | ||||
missings.setdefault(b[1], []).append(b[0]) | ||||
|
r6210 | |||
|
r5934 | if missings: | ||
self.after() | ||||
for pbranch, heads in missings.iteritems(): | ||||
pbranchpath = os.path.join(self.path, pbranch) | ||||
prepo = hg.repository(self.ui, pbranchpath) | ||||
self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch)) | ||||
self.repo.pull(prepo, [prepo.lookup(h) for h in heads]) | ||||
self.before() | ||||
|
r5173 | |||
|
r6716 | def putcommit(self, files, copies, parents, commit, source): | ||
|
r6717 | files = dict(files) | ||
def getfilectx(repo, memctx, f): | ||||
v = files[f] | ||||
data = source.getfile(f, v) | ||||
e = source.getmode(f, v) | ||||
return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f)) | ||||
|
r4536 | pl = [] | ||
for p in parents: | ||||
|
r6717 | if p not in pl: | ||
|
r4536 | pl.append(p) | ||
parents = pl | ||||
|
r5378 | nparents = len(parents) | ||
if self.filemapmode and nparents == 1: | ||||
m1node = self.repo.changelog.read(bin(parents[0]))[0] | ||||
parent = parents[0] | ||||
|
r4536 | |||
if len(parents) < 2: parents.append("0" * 40) | ||||
if len(parents) < 2: parents.append("0" * 40) | ||||
p2 = parents.pop(0) | ||||
text = commit.desc | ||||
|
r5439 | extra = commit.extra.copy() | ||
|
r5038 | if self.branchnames and commit.branch: | ||
|
r4873 | extra['branch'] = commit.branch | ||
if commit.rev: | ||||
extra['convert_revision'] = commit.rev | ||||
|
r4957 | |||
|
r4536 | while parents: | ||
p1 = p2 | ||||
p2 = parents.pop(0) | ||||
|
r6717 | ctx = context.memctx(self.repo, (p1, p2), text, files.keys(), getfilectx, | ||
commit.author, commit.date, extra) | ||||
a = self.repo.commitctx(ctx) | ||||
|
r4536 | text = "(octopus merge fixup)\n" | ||
|
r6217 | p2 = hex(self.repo.changelog.tip()) | ||
|
r4536 | |||
|
r5378 | if self.filemapmode and nparents == 1: | ||
man = self.repo.manifest | ||||
mnode = self.repo.changelog.read(bin(p2))[0] | ||||
if not man.cmp(m1node, man.revision(mnode)): | ||||
self.repo.rollback() | ||||
return parent | ||||
|
r4536 | return p2 | ||
def puttags(self, tags): | ||||
|
r6717 | try: | ||
|
r6747 | parentctx = self.repo[self.tagsbranch] | ||
|
r6717 | tagparent = parentctx.node() | ||
except RepoError, inst: | ||||
parentctx = None | ||||
tagparent = nullid | ||||
|
r4536 | |||
|
r6717 | try: | ||
|
r6762 | oldlines = util.sort(parentctx['.hgtags'].data().splitlines(1)) | ||
|
r6717 | except: | ||
oldlines = [] | ||||
|
r4536 | |||
|
r6762 | newlines = util.sort([("%s %s\n" % (tags[tag], tag)) for tag in tags]) | ||
|
r6717 | |||
if newlines == oldlines: | ||||
return None | ||||
data = "".join(newlines) | ||||
def getfilectx(repo, memctx, f): | ||||
return context.memfilectx(f, data, False, False, None) | ||||
|
r6956 | self.ui.status(_("updating tags\n")) | ||
|
r6717 | date = "%s 0" % int(time.mktime(time.gmtime())) | ||
extra = {'branch': self.tagsbranch} | ||||
ctx = context.memctx(self.repo, (tagparent, None), "update tags", | ||||
[".hgtags"], getfilectx, "convert-repo", date, | ||||
extra) | ||||
self.repo.commitctx(ctx) | ||||
return hex(self.repo.changelog.tip()) | ||||
|
r5013 | |||
|
r5378 | def setfilemapmode(self, active): | ||
self.filemapmode = active | ||||
|
r5013 | class mercurial_source(converter_source): | ||
def __init__(self, ui, path, rev=None): | ||||
converter_source.__init__(self, ui, path, rev) | ||||
|
r7231 | self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False) | ||
self.ignored = {} | ||||
|
r5556 | self.saverev = ui.configbool('convert', 'hg.saverev', True) | ||
|
r5358 | try: | ||
self.repo = hg.repository(self.ui, path) | ||||
|
r5437 | # try to provoke an exception if this isn't really a hg | ||
# repo, but some other bogus compatible-looking url | ||||
|
r5522 | if not self.repo.local(): | ||
|
r6217 | raise RepoError() | ||
except RepoError: | ||||
|
r5437 | ui.print_exc() | ||
|
r5522 | raise NoRepo("%s is not a local Mercurial repo" % path) | ||
|
r5013 | self.lastrev = None | ||
self.lastctx = None | ||||
|
r5379 | self._changescache = None | ||
|
r5554 | self.convertfp = None | ||
|
r6885 | # Restrict converted revisions to startrev descendants | ||
startnode = ui.config('convert', 'hg.startrev') | ||||
if startnode is not None: | ||||
try: | ||||
startnode = self.repo.lookup(startnode) | ||||
except repo.RepoError: | ||||
|
r6923 | raise util.Abort(_('%s is not a valid start revision') | ||
|
r6885 | % startnode) | ||
startrev = self.repo.changelog.rev(startnode) | ||||
children = {startnode: 1} | ||||
for rev in self.repo.changelog.descendants(startrev): | ||||
children[self.repo.changelog.node(rev)] = 1 | ||||
self.keep = children.__contains__ | ||||
else: | ||||
self.keep = util.always | ||||
|
r5013 | |||
def changectx(self, rev): | ||||
if self.lastrev != rev: | ||||
|
r6747 | self.lastctx = self.repo[rev] | ||
|
r5013 | self.lastrev = rev | ||
return self.lastctx | ||||
|
r6885 | def parents(self, ctx): | ||
|
r6923 | return [p.node() for p in ctx.parents() | ||
|
r6885 | if p and self.keep(p.node())] | ||
|
r5013 | def getheads(self): | ||
|
r5131 | if self.rev: | ||
|
r6885 | heads = [self.repo[self.rev].node()] | ||
|
r5131 | else: | ||
|
r6885 | heads = self.repo.heads() | ||
return [hex(h) for h in heads if self.keep(h)] | ||||
|
r5013 | |||
def getfile(self, name, rev): | ||||
try: | ||||
|
r6747 | return self.changectx(rev)[name].data() | ||
|
r5013 | except revlog.LookupError, err: | ||
raise IOError(err) | ||||
def getmode(self, name, rev): | ||||
|
r6749 | return self.changectx(rev).manifest().flags(name) | ||
|
r5013 | |||
def getchanges(self, rev): | ||||
ctx = self.changectx(rev) | ||||
|
r6885 | parents = self.parents(ctx) | ||
if not parents: | ||||
files = util.sort(ctx.manifest().keys()) | ||||
|
r7231 | return [(f, rev) for f in files if f not in self.ignored], {} | ||
|
r5379 | if self._changescache and self._changescache[0] == rev: | ||
m, a, r = self._changescache[1] | ||||
else: | ||||
|
r6885 | m, a, r = self.repo.status(parents[0], ctx.node())[:3] | ||
|
r7231 | # getcopies() detects missing revlogs early, run it before | ||
# filtering the changes. | ||||
copies = self.getcopies(ctx, m + a) | ||||
changes = [(name, rev) for name in m + a + r | ||||
if name not in self.ignored] | ||||
return util.sort(changes), copies | ||||
|
r5013 | |||
|
r5280 | def getcopies(self, ctx, files): | ||
|
r5013 | copies = {} | ||
|
r5280 | for name in files: | ||
|
r7231 | if name in self.ignored: | ||
continue | ||||
|
r5013 | try: | ||
|
r7231 | copysource, copynode = ctx.filectx(name).renamed() | ||
if copysource in self.ignored or not self.keep(copynode): | ||||
continue | ||||
copies[name] = copysource | ||||
|
r5013 | except TypeError: | ||
pass | ||||
|
r7231 | except revlog.LookupError, e: | ||
if not self.ignoreerrors: | ||||
raise | ||||
self.ignored[name] = 1 | ||||
self.ui.warn(_('ignoring: %s\n') % e) | ||||
|
r5013 | return copies | ||
|
r5143 | |||
|
r5013 | def getcommit(self, rev): | ||
ctx = self.changectx(rev) | ||||
|
r6885 | parents = [hex(p) for p in self.parents(ctx)] | ||
|
r5556 | if self.saverev: | ||
crev = rev | ||||
else: | ||||
crev = None | ||||
|
r5013 | return commit(author=ctx.user(), date=util.datestr(ctx.date()), | ||
|
r5556 | desc=ctx.description(), rev=crev, parents=parents, | ||
|
r5439 | branch=ctx.branch(), extra=ctx.extra()) | ||
|
r5013 | |||
def gettags(self): | ||||
tags = [t for t in self.repo.tagslist() if t[0] != 'tip'] | ||||
|
r6885 | return dict([(name, hex(node)) for name, node in tags | ||
if self.keep(node)]) | ||||
|
r5379 | |||
def getchangedfiles(self, rev, i): | ||||
ctx = self.changectx(rev) | ||||
|
r6885 | parents = self.parents(ctx) | ||
if not parents and i is None: | ||||
i = 0 | ||||
changes = [], ctx.manifest().keys(), [] | ||||
else: | ||||
i = i or 0 | ||||
changes = self.repo.status(parents[i], ctx.node())[:3] | ||||
|
r7231 | changes = [[f for f in l if f not in self.ignored] for l in changes] | ||
|
r5379 | |||
if i == 0: | ||||
self._changescache = (rev, changes) | ||||
return changes[0] + changes[1] + changes[2] | ||||
|
r5554 | def converted(self, rev, destrev): | ||
if self.convertfp is None: | ||||
self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'), | ||||
'a') | ||||
self.convertfp.write('%s %s\n' % (destrev, rev)) | ||||
self.convertfp.flush() | ||||
|
r5805 | |||
def before(self): | ||||
self.ui.debug(_('run hg source pre-conversion action\n')) | ||||
def after(self): | ||||
self.ui.debug(_('run hg source post-conversion action\n')) | ||||