hg.py
729 lines
| 24.6 KiB
| text/x-python
|
PythonLexer
|
r8250 | # hg.py - hg backend for convert extension | ||
# | ||||
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r4536 | |||
|
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. | ||||
# | ||||
|
r8596 | # * Using "--config convert.hg.saverev=true" will make the source | ||
# identifier to be stored in the converted revision. This will cause | ||||
# the converted revision to have a different identity than the | ||||
# source. | ||||
|
r28370 | from __future__ import absolute_import | ||
|
r5013 | |||
|
r28370 | import os | ||
import re | ||||
import time | ||||
|
r5013 | |||
|
r29205 | from mercurial.i18n import _ | ||
|
r43355 | from mercurial.pycompat import open | ||
|
r28370 | from mercurial import ( | ||
bookmarks, | ||||
context, | ||||
error, | ||||
exchange, | ||||
hg, | ||||
lock as lockmod, | ||||
merge as mergemod, | ||||
node as nodemod, | ||||
phases, | ||||
|
r43375 | pycompat, | ||
|
r28370 | scmutil, | ||
util, | ||||
) | ||||
|
r36625 | from mercurial.utils import dateutil | ||
|
r43346 | |||
|
r28861 | stringio = util.stringio | ||
|
r28370 | from . import common | ||
|
r43346 | |||
|
r28370 | mapfile = common.mapfile | ||
NoRepo = common.NoRepo | ||||
|
r4536 | |||
|
r36151 | sha1re = re.compile(br'\b[0-9a-f]{12,40}\b') | ||
|
r20372 | |||
|
r43346 | |||
|
r28370 | class mercurial_sink(common.converter_sink): | ||
|
r35168 | def __init__(self, ui, repotype, path): | ||
common.converter_sink.__init__(self, ui, repotype, path) | ||||
|
r43347 | self.branchnames = ui.configbool(b'convert', b'hg.usebranchnames') | ||
self.clonebranches = ui.configbool(b'convert', b'hg.clonebranches') | ||||
self.tagsbranch = ui.config(b'convert', b'hg.tagsbranch') | ||||
|
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(): | ||
|
r43346 | raise NoRepo( | ||
|
r43347 | _(b'%s is not a local Mercurial repository') % path | ||
|
r43346 | ) | ||
|
r25660 | except error.RepoError as err: | ||
|
r8206 | ui.traceback() | ||
|
r5441 | raise NoRepo(err.args[0]) | ||
else: | ||||
try: | ||||
|
r43347 | ui.status(_(b'initializing destination %s repository\n') % path) | ||
|
r5441 | self.repo = hg.repository(self.ui, path, create=True) | ||
|
r5918 | if not self.repo.local(): | ||
|
r43346 | raise NoRepo( | ||
|
r43347 | _(b'%s is not a local Mercurial repository') % path | ||
|
r43346 | ) | ||
|
r5441 | self.created.append(path) | ||
|
r7875 | except error.RepoError: | ||
|
r8206 | ui.traceback() | ||
|
r43346 | raise NoRepo( | ||
|
r43347 | _(b"could not create hg repository %s as sink") % path | ||
|
r43346 | ) | ||
|
r5014 | self.lock = None | ||
self.wlock = None | ||||
|
r5378 | self.filemapmode = False | ||
|
r25558 | self.subrevmaps = {} | ||
|
r5014 | |||
def before(self): | ||||
|
r43347 | self.ui.debug(b'run hg sink pre-conversion action\n') | ||
|
r5052 | self.wlock = self.repo.wlock() | ||
|
r5014 | self.lock = self.repo.lock() | ||
def after(self): | ||||
|
r43347 | self.ui.debug(b'run hg sink post-conversion action\n') | ||
|
r10086 | if self.lock: | ||
self.lock.release() | ||||
if self.wlock: | ||||
self.wlock.release() | ||||
|
r4536 | |||
|
r5011 | def revmapfile(self): | ||
|
r43347 | return self.repo.vfs.join(b"shamap") | ||
|
r4536 | |||
|
r4589 | def authorfile(self): | ||
|
r43347 | return self.repo.vfs.join(b"authormap") | ||
|
r4589 | |||
|
r5934 | def setbranch(self, branch, pbranches): | ||
if not self.clonebranches: | ||||
|
r5173 | return | ||
|
r43346 | setbranch = branch != self.lastbranch | ||
|
r5173 | self.lastbranch = branch | ||
if not branch: | ||||
|
r43347 | branch = b'default' | ||
pbranches = [(b[0], b[1] and b[1] or b'default') for b in pbranches] | ||||
|
r5173 | |||
branchpath = os.path.join(self.path, branch) | ||||
|
r5934 | if setbranch: | ||
self.after() | ||||
try: | ||||
self.repo = hg.repository(self.ui, branchpath) | ||||
|
r16689 | except Exception: | ||
|
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]) | ||||
|
r16689 | except Exception: | ||
|
r5934 | missings.setdefault(b[1], []).append(b[0]) | ||
|
r6210 | |||
|
r5934 | if missings: | ||
self.after() | ||||
|
r43375 | for pbranch, heads in sorted(pycompat.iteritems(missings)): | ||
|
r5934 | pbranchpath = os.path.join(self.path, pbranch) | ||
|
r14556 | prepo = hg.peer(self.ui, {}, pbranchpath) | ||
|
r43347 | self.ui.note( | ||
_(b'pulling from %s into %s\n') % (pbranch, branch) | ||||
) | ||||
|
r43346 | exchange.pull( | ||
self.repo, prepo, [prepo.lookup(h) for h in heads] | ||||
) | ||||
|
r5934 | self.before() | ||
|
r5173 | |||
|
r21076 | def _rewritetags(self, source, revmap, data): | ||
|
r28861 | fp = stringio() | ||
|
r8693 | for line in data.splitlines(): | ||
|
r43347 | s = line.split(b' ', 1) | ||
|
r8693 | if len(s) != 2: | ||
|
r43347 | self.ui.warn(_(b'invalid tag entry: "%s"\n') % line) | ||
fp.write(b'%s\n' % line) # Bogus, but keep for hash stability | ||||
|
r8693 | continue | ||
revid = revmap.get(source.lookuprev(s[0])) | ||||
if not revid: | ||||
|
r28370 | if s[0] == nodemod.nullhex: | ||
|
r25305 | revid = s[0] | ||
else: | ||||
|
r39132 | # missing, but keep for hash stability | ||
|
r43347 | self.ui.warn(_(b'missing tag entry: "%s"\n') % line) | ||
fp.write(b'%s\n' % line) | ||||
|
r25305 | continue | ||
|
r43347 | fp.write(b'%s %s\n' % (revid, s[1])) | ||
|
r8693 | return fp.getvalue() | ||
|
r25558 | def _rewritesubstate(self, source, data): | ||
|
r28861 | fp = stringio() | ||
|
r25558 | for line in data.splitlines(): | ||
|
r43347 | s = line.split(b' ', 1) | ||
|
r25558 | if len(s) != 2: | ||
continue | ||||
revid = s[0] | ||||
subpath = s[1] | ||||
|
r28370 | if revid != nodemod.nullhex: | ||
|
r25558 | revmap = self.subrevmaps.get(subpath) | ||
if revmap is None: | ||||
|
r43346 | revmap = mapfile( | ||
|
r43347 | self.ui, self.repo.wjoin(subpath, b'.hg/shamap') | ||
|
r43346 | ) | ||
|
r25558 | self.subrevmaps[subpath] = revmap | ||
# It is reasonable that one or more of the subrepos don't | ||||
# need to be converted, in which case they can be cloned | ||||
# into place instead of converted. Therefore, only warn | ||||
# once. | ||||
|
r43347 | msg = _(b'no ".hgsubstate" updates will be made for "%s"\n') | ||
|
r25558 | if len(revmap) == 0: | ||
|
r43347 | sub = self.repo.wvfs.reljoin(subpath, b'.hg') | ||
|
r25558 | |||
if self.repo.wvfs.exists(sub): | ||||
self.ui.warn(msg % subpath) | ||||
newid = revmap.get(revid) | ||||
if not newid: | ||||
if len(revmap) > 0: | ||||
|
r43346 | self.ui.warn( | ||
|
r43347 | _(b"%s is missing from %s/.hg/shamap\n") | ||
|
r43346 | % (revid, subpath) | ||
) | ||||
|
r25558 | else: | ||
revid = newid | ||||
|
r43347 | fp.write(b'%s %s\n' % (revid, subpath)) | ||
|
r25558 | |||
return fp.getvalue() | ||||
|
r26037 | def _calculatemergedfiles(self, source, p1ctx, p2ctx): | ||
"""Calculates the files from p2 that we need to pull in when merging p1 | ||||
and p2, given that the merge is coming from the given source. | ||||
This prevents us from losing files that only exist in the target p2 and | ||||
that don't come from the source repo (like if you're merging multiple | ||||
repositories together). | ||||
""" | ||||
anc = [p1ctx.ancestor(p2ctx)] | ||||
# Calculate what files are coming from p2 | ||||
|
r45831 | mresult = mergemod.calculateupdates( | ||
|
r43346 | self.repo, | ||
p1ctx, | ||||
p2ctx, | ||||
anc, | ||||
branchmerge=True, | ||||
force=True, | ||||
acceptremote=False, | ||||
followcopies=False, | ||||
) | ||||
|
r26037 | |||
|
r45831 | for file, (action, info, msg) in pycompat.iteritems(mresult.actions): | ||
|
r26037 | if source.targetfilebelongstosource(file): | ||
# If the file belongs to the source repo, ignore the p2 | ||||
# since it will be covered by the existing fileset. | ||||
continue | ||||
# If the file requires actual merging, abort. We don't have enough | ||||
# context to resolve merges correctly. | ||||
|
r43347 | if action in [b'm', b'dm', b'cd', b'dc']: | ||
|
r43346 | raise error.Abort( | ||
_( | ||||
|
r43347 | b"unable to convert merge commit " | ||
b"since target parents do not merge cleanly (file " | ||||
b"%s, parents %s and %s)" | ||||
|
r43346 | ) | ||
% (file, p1ctx, p2ctx) | ||||
) | ||||
|
r43347 | elif action == b'k': | ||
|
r26037 | # 'keep' means nothing changed from p1 | ||
continue | ||||
else: | ||||
# Any other change means we want to take the p2 version | ||||
yield file | ||||
|
r43346 | def putcommit( | ||
self, files, copies, parents, commit, source, revmap, full, cleanp2 | ||||
): | ||||
|
r6717 | files = dict(files) | ||
|
r24395 | |||
|
r6717 | def getfilectx(repo, memctx, f): | ||
|
r26037 | if p2ctx and f in p2files and f not in copies: | ||
|
r43347 | self.ui.debug(b'reusing %s from p2\n' % f) | ||
|
r26078 | try: | ||
return p2ctx[f] | ||||
except error.ManifestLookupError: | ||||
# If the file doesn't exist in p2, then we're syncing a | ||||
# delete, so just return None. | ||||
return None | ||||
|
r22300 | try: | ||
v = files[f] | ||||
except KeyError: | ||||
return None | ||||
|
r11134 | data, mode = source.getfile(f, v) | ||
|
r22296 | if data is None: | ||
return None | ||||
|
r43347 | if f == b'.hgtags': | ||
|
r21076 | data = self._rewritetags(source, revmap, data) | ||
|
r43347 | if f == b'.hgsubstate': | ||
|
r25558 | data = self._rewritesubstate(source, data) | ||
|
r43346 | return context.memfilectx( | ||
self.repo, | ||||
memctx, | ||||
f, | ||||
data, | ||||
|
r43347 | b'l' in mode, | ||
b'x' in mode, | ||||
|
r43346 | copies.get(f), | ||
) | ||||
|
r6717 | |||
|
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: | ||||
|
r28370 | m1node = self.repo.changelog.read(nodemod.bin(parents[0]))[0] | ||
|
r5378 | parent = parents[0] | ||
|
r4536 | |||
|
r10282 | if len(parents) < 2: | ||
|
r28370 | parents.append(nodemod.nullid) | ||
|
r10282 | if len(parents) < 2: | ||
|
r28370 | parents.append(nodemod.nullid) | ||
|
r4536 | p2 = parents.pop(0) | ||
text = commit.desc | ||||
|
r20372 | |||
sha1s = re.findall(sha1re, text) | ||||
for sha1 in sha1s: | ||||
oldrev = source.lookuprev(sha1) | ||||
newrev = revmap.get(oldrev) | ||||
if newrev is not None: | ||||
|
r43346 | text = text.replace(sha1, newrev[: len(sha1)]) | ||
|
r20372 | |||
|
r5439 | extra = commit.extra.copy() | ||
|
r21765 | |||
|
r43347 | sourcename = self.repo.ui.config(b'convert', b'hg.sourcename') | ||
|
r25750 | if sourcename: | ||
|
r43347 | extra[b'convert_source'] = sourcename | ||
|
r25750 | |||
|
r43346 | for label in ( | ||
|
r43347 | b'source', | ||
b'transplant_source', | ||||
b'rebase_source', | ||||
b'intermediate-source', | ||||
|
r43346 | ): | ||
|
r21765 | node = extra.get(label) | ||
if node is None: | ||||
continue | ||||
# Only transplant stores its reference in binary | ||||
|
r43347 | if label == b'transplant_source': | ||
|
r28370 | node = nodemod.hex(node) | ||
|
r21765 | |||
newrev = revmap.get(node) | ||||
if newrev is not None: | ||||
|
r43347 | if label == b'transplant_source': | ||
|
r28370 | newrev = nodemod.bin(newrev) | ||
|
r21765 | |||
extra[label] = newrev | ||||
|
r5038 | if self.branchnames and commit.branch: | ||
|
r43347 | extra[b'branch'] = commit.branch | ||
|
r25570 | if commit.rev and commit.saverev: | ||
|
r43347 | extra[b'convert_revision'] = commit.rev | ||
|
r4957 | |||
|
r4536 | while parents: | ||
p1 = p2 | ||||
p2 = parents.pop(0) | ||||
|
r26037 | p1ctx = self.repo[p1] | ||
|
r24395 | p2ctx = None | ||
|
r28370 | if p2 != nodemod.nullid: | ||
|
r24395 | p2ctx = self.repo[p2] | ||
|
r22300 | fileset = set(files) | ||
if full: | ||||
|
r22360 | fileset.update(self.repo[p1]) | ||
fileset.update(self.repo[p2]) | ||||
|
r26037 | |||
if p2ctx: | ||||
p2files = set(cleanp2) | ||||
for file in self._calculatemergedfiles(source, p1ctx, p2ctx): | ||||
p2files.add(file) | ||||
fileset.add(file) | ||||
|
r43346 | ctx = context.memctx( | ||
self.repo, | ||||
(p1, p2), | ||||
text, | ||||
fileset, | ||||
getfilectx, | ||||
commit.author, | ||||
commit.date, | ||||
extra, | ||||
) | ||||
|
r25571 | |||
# We won't know if the conversion changes the node until after the | ||||
# commit, so copy the source's phase for now. | ||||
|
r43346 | self.repo.ui.setconfig( | ||
|
r43347 | b'phases', | ||
b'new-commit', | ||||
|
r43346 | phases.phasenames[commit.phase], | ||
|
r43347 | b'convert', | ||
|
r43346 | ) | ||
|
r25571 | |||
|
r43347 | with self.repo.transaction(b"convert") as tr: | ||
if self.repo.ui.config(b'convert', b'hg.preserve-hash'): | ||||
|
r42839 | origctx = commit.ctx | ||
else: | ||||
origctx = None | ||||
node = nodemod.hex(self.repo.commitctx(ctx, origctx=origctx)) | ||||
|
r25571 | |||
# If the node value has changed, but the phase is lower than | ||||
# draft, set it back to draft since it hasn't been exposed | ||||
# anywhere. | ||||
if commit.rev != node: | ||||
ctx = self.repo[node] | ||||
if ctx.phase() < phases.draft: | ||||
|
r43346 | phases.registernew( | ||
self.repo, tr, phases.draft, [ctx.node()] | ||||
) | ||||
|
r25571 | |||
|
r43347 | text = b"(octopus merge fixup)\n" | ||
|
r25697 | p2 = node | ||
|
r4536 | |||
|
r5378 | if self.filemapmode and nparents == 1: | ||
|
r39280 | man = self.repo.manifestlog.getstorage(b'') | ||
|
r28370 | mnode = self.repo.changelog.read(nodemod.bin(p2))[0] | ||
|
r43347 | closed = b'close' in commit.extra | ||
|
r11673 | if not closed and not man.cmp(m1node, man.revision(mnode)): | ||
|
r43347 | self.ui.status(_(b"filtering out empty revision\n")) | ||
|
r15193 | self.repo.rollback(force=True) | ||
|
r5378 | return parent | ||
|
r4536 | return p2 | ||
def puttags(self, tags): | ||||
|
r37413 | tagparent = self.repo.branchtip(self.tagsbranch, ignoremissing=True) | ||
tagparent = tagparent or nodemod.nullid | ||||
|
r4536 | |||
|
r20376 | oldlines = set() | ||
|
r43375 | for branch, heads in pycompat.iteritems(self.repo.branchmap()): | ||
|
r20376 | for h in heads: | ||
|
r43347 | if b'.hgtags' in self.repo[h]: | ||
|
r20376 | oldlines.update( | ||
|
r43347 | set(self.repo[h][b'.hgtags'].data().splitlines(True)) | ||
|
r43346 | ) | ||
|
r20376 | oldlines = sorted(list(oldlines)) | ||
|
r4536 | |||
|
r43347 | newlines = sorted([(b"%s %s\n" % (tags[tag], tag)) for tag in tags]) | ||
|
r7877 | if newlines == oldlines: | ||
|
r9431 | return None, None | ||
|
r20377 | |||
# if the old and new tags match, then there is nothing to update | ||||
oldtags = set() | ||||
newtags = set() | ||||
for line in oldlines: | ||||
|
r43347 | s = line.strip().split(b' ', 1) | ||
|
r20377 | if len(s) != 2: | ||
continue | ||||
oldtags.add(s[1]) | ||||
for line in newlines: | ||||
|
r43347 | s = line.strip().split(b' ', 1) | ||
|
r20377 | if len(s) != 2: | ||
continue | ||||
if s[1] not in oldtags: | ||||
newtags.add(s[1].strip()) | ||||
if not newtags: | ||||
return None, None | ||||
|
r43347 | data = b"".join(newlines) | ||
|
r43346 | |||
|
r7877 | def getfilectx(repo, memctx, f): | ||
|
r35401 | return context.memfilectx(repo, memctx, f, data, False, False, None) | ||
|
r6717 | |||
|
r43347 | self.ui.status(_(b"updating tags\n")) | ||
date = b"%d 0" % int(time.mktime(time.gmtime())) | ||||
extra = {b'branch': self.tagsbranch} | ||||
|
r43346 | ctx = context.memctx( | ||
self.repo, | ||||
(tagparent, None), | ||||
|
r43347 | b"update tags", | ||
[b".hgtags"], | ||||
|
r43346 | getfilectx, | ||
|
r43347 | b"convert-repo", | ||
|
r43346 | date, | ||
extra, | ||||
) | ||||
|
r25697 | node = self.repo.commitctx(ctx) | ||
|
r28370 | return nodemod.hex(node), nodemod.hex(tagparent) | ||
|
r5013 | |||
|
r5378 | def setfilemapmode(self, active): | ||
self.filemapmode = active | ||||
|
r13746 | def putbookmarks(self, updatedbookmark): | ||
if not len(updatedbookmark): | ||||
return | ||||
|
r26974 | wlock = lock = tr = None | ||
try: | ||||
wlock = self.repo.wlock() | ||||
lock = self.repo.lock() | ||||
|
r43347 | tr = self.repo.transaction(b'bookmark') | ||
self.ui.status(_(b"updating bookmarks\n")) | ||||
|
r26973 | destmarks = self.repo._bookmarks | ||
|
r43346 | changes = [ | ||
(bookmark, nodemod.bin(updatedbookmark[bookmark])) | ||||
for bookmark in updatedbookmark | ||||
] | ||||
|
r33487 | destmarks.applychanges(self.repo, tr, changes) | ||
|
r26974 | tr.close() | ||
finally: | ||||
lockmod.release(lock, wlock, tr) | ||||
|
r13746 | |||
|
r21635 | def hascommitfrommap(self, rev): | ||
# the exact semantics of clonebranches is unclear so we can't say no | ||||
return rev in self.repo or self.clonebranches | ||||
|
r21634 | def hascommitforsplicemap(self, rev): | ||
|
r16686 | if rev not in self.repo and self.clonebranches: | ||
|
r43346 | raise error.Abort( | ||
_( | ||||
|
r43347 | b'revision %s not found in destination ' | ||
b'repository (lookups with clonebranches=true ' | ||||
b'are not implemented)' | ||||
|
r43346 | ) | ||
% rev | ||||
) | ||||
|
r16106 | return rev in self.repo | ||
|
r13746 | |||
|
r43346 | |||
|
r28370 | class mercurial_source(common.converter_source): | ||
|
r35168 | def __init__(self, ui, repotype, path, revs=None): | ||
common.converter_source.__init__(self, ui, repotype, path, revs) | ||||
|
r43347 | self.ignoreerrors = ui.configbool(b'convert', b'hg.ignoreerrors') | ||
|
r8456 | self.ignored = set() | ||
|
r43347 | self.saverev = ui.configbool(b'convert', b'hg.saverev') | ||
|
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(): | ||
|
r16687 | raise error.RepoError | ||
|
r7637 | except error.RepoError: | ||
|
r8206 | ui.traceback() | ||
|
r43347 | raise NoRepo(_(b"%s is not a local Mercurial repository") % path) | ||
|
r5013 | self.lastrev = None | ||
self.lastctx = None | ||||
|
r22299 | self._changescache = None, None | ||
|
r5554 | self.convertfp = None | ||
|
r6885 | # Restrict converted revisions to startrev descendants | ||
|
r43347 | startnode = ui.config(b'convert', b'hg.startrev') | ||
hgrevs = ui.config(b'convert', b'hg.revs') | ||||
|
r19891 | if hgrevs is None: | ||
if startnode is not None: | ||||
try: | ||||
startnode = self.repo.lookup(startnode) | ||||
except error.RepoError: | ||||
|
r43346 | raise error.Abort( | ||
|
r43347 | _(b'%s is not a valid start revision') % startnode | ||
|
r43346 | ) | ||
|
r19891 | startrev = self.repo.changelog.rev(startnode) | ||
children = {startnode: 1} | ||||
for r in self.repo.changelog.descendants([startrev]): | ||||
children[self.repo.changelog.node(r)] = 1 | ||||
self.keep = children.__contains__ | ||||
else: | ||||
self.keep = util.always | ||||
|
r25748 | if revs: | ||
|
r37379 | self._heads = [self.repo.lookup(r) for r in revs] | ||
|
r19891 | else: | ||
self._heads = self.repo.heads() | ||||
|
r6885 | else: | ||
|
r25748 | if revs or startnode is not None: | ||
|
r43346 | raise error.Abort( | ||
|
r43347 | _( | ||
b'hg.revs cannot be combined with ' | ||||
b'hg.startrev or --rev' | ||||
) | ||||
|
r43346 | ) | ||
|
r19891 | nodes = set() | ||
parents = set() | ||||
for r in scmutil.revrange(self.repo, [hgrevs]): | ||||
ctx = self.repo[r] | ||||
nodes.add(ctx.node()) | ||||
parents.update(p.node() for p in ctx.parents()) | ||||
self.keep = nodes.__contains__ | ||||
self._heads = nodes - parents | ||||
|
r5013 | |||
|
r27717 | def _changectx(self, rev): | ||
|
r5013 | if self.lastrev != rev: | ||
|
r6747 | self.lastctx = self.repo[rev] | ||
|
r5013 | self.lastrev = rev | ||
return self.lastctx | ||||
|
r27717 | def _parents(self, ctx): | ||
|
r9531 | return [p for p in ctx.parents() if p and self.keep(p.node())] | ||
|
r6885 | |||
|
r5013 | def getheads(self): | ||
|
r28370 | return [nodemod.hex(h) for h in self._heads if self.keep(h)] | ||
|
r5013 | |||
def getfile(self, name, rev): | ||||
try: | ||||
|
r27717 | fctx = self._changectx(rev)[name] | ||
|
r11134 | return fctx.data(), fctx.flags() | ||
|
r22296 | except error.LookupError: | ||
return None, None | ||||
|
r5013 | |||
|
r27718 | def _changedfiles(self, ctx1, ctx2): | ||
|
r27719 | ma, r = [], [] | ||
maappend = ma.append | ||||
rappend = r.append | ||||
d = ctx1.manifest().diff(ctx2.manifest()) | ||||
|
r43375 | for f, ((node1, flag1), (node2, flag2)) in pycompat.iteritems(d): | ||
|
r27719 | if node2 is None: | ||
rappend(f) | ||||
else: | ||||
maappend(f) | ||||
return ma, r | ||||
|
r27718 | |||
|
r22300 | def getchanges(self, rev, full): | ||
|
r27717 | ctx = self._changectx(rev) | ||
parents = self._parents(ctx) | ||||
|
r22300 | if full or not parents: | ||
|
r22299 | files = copyfiles = ctx.manifest() | ||
|
r22300 | if parents: | ||
|
r22299 | if self._changescache[0] == rev: | ||
|
r27718 | ma, r = self._changescache[1] | ||
|
r22299 | else: | ||
|
r27718 | ma, r = self._changedfiles(parents[0], ctx) | ||
|
r22300 | if not full: | ||
|
r27718 | files = ma + r | ||
copyfiles = ma | ||||
|
r27717 | # _getcopies() is also run for roots and before filtering so missing | ||
|
r22299 | # revlogs are detected early | ||
|
r27717 | copies = self._getcopies(ctx, parents, copyfiles) | ||
|
r24395 | cleanp2 = set() | ||
if len(parents) == 2: | ||||
|
r27719 | d = parents[1].manifest().diff(ctx.manifest(), clean=True) | ||
|
r43375 | for f, value in pycompat.iteritems(d): | ||
|
r27719 | if value is None: | ||
cleanp2.add(f) | ||||
|
r22299 | changes = [(f, rev) for f in files if f not in self.ignored] | ||
changes.sort() | ||||
|
r24395 | return changes, copies, cleanp2 | ||
|
r5013 | |||
|
r27717 | def _getcopies(self, ctx, parents, files): | ||
|
r5013 | copies = {} | ||
|
r5280 | for name in files: | ||
|
r7231 | if name in self.ignored: | ||
continue | ||||
|
r5013 | try: | ||
|
r41937 | copysource = ctx.filectx(name).copysource() | ||
|
r19457 | if copysource in self.ignored: | ||
|
r7231 | continue | ||
|
r9532 | # Ignore copy sources not in parent revisions | ||
|
r36358 | if not any(copysource in p for p in parents): | ||
|
r9532 | continue | ||
|
r7231 | copies[name] = copysource | ||
|
r5013 | except TypeError: | ||
pass | ||||
|
r25660 | except error.LookupError as e: | ||
|
r7231 | if not self.ignoreerrors: | ||
raise | ||||
|
r8456 | self.ignored.add(name) | ||
|
r43347 | self.ui.warn(_(b'ignoring: %s\n') % e) | ||
|
r5013 | return copies | ||
|
r5143 | |||
|
r5013 | def getcommit(self, rev): | ||
|
r27717 | ctx = self._changectx(rev) | ||
|
r28900 | _parents = self._parents(ctx) | ||
parents = [p.hex() for p in _parents] | ||||
optparents = [p.hex() for p in ctx.parents() if p and p not in _parents] | ||||
|
r25570 | crev = rev | ||
|
r43346 | return common.commit( | ||
author=ctx.user(), | ||||
|
r43347 | date=dateutil.datestr(ctx.date(), b'%Y-%m-%d %H:%M:%S %1%2'), | ||
|
r43346 | desc=ctx.description(), | ||
rev=crev, | ||||
parents=parents, | ||||
optparents=optparents, | ||||
branch=ctx.branch(), | ||||
extra=ctx.extra(), | ||||
sortkey=ctx.rev(), | ||||
saverev=self.saverev, | ||||
phase=ctx.phase(), | ||||
ctx=ctx, | ||||
) | ||||
|
r5013 | |||
|
r41215 | def numcommits(self): | ||
return len(self.repo) | ||||
|
r5013 | def gettags(self): | ||
|
r21498 | # This will get written to .hgtags, filter non global tags out. | ||
|
r43346 | tags = [ | ||
t | ||||
for t in self.repo.tagslist() | ||||
|
r43347 | if self.repo.tagtype(t[0]) == b'global' | ||
|
r43346 | ] | ||
|
r44937 | return { | ||
name: nodemod.hex(node) for name, node in tags if self.keep(node) | ||||
} | ||||
|
r5379 | |||
def getchangedfiles(self, rev, i): | ||||
|
r27717 | ctx = self._changectx(rev) | ||
parents = self._parents(ctx) | ||||
|
r6885 | if not parents and i is None: | ||
i = 0 | ||||
|
r27718 | ma, r = ctx.manifest().keys(), [] | ||
|
r6885 | else: | ||
i = i or 0 | ||||
|
r27718 | ma, r = self._changedfiles(parents[i], ctx) | ||
ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)] | ||||
|
r5379 | |||
if i == 0: | ||||
|
r27718 | self._changescache = (rev, (ma, r)) | ||
|
r5379 | |||
|
r27718 | return ma + r | ||
|
r5379 | |||
|
r5554 | def converted(self, rev, destrev): | ||
if self.convertfp is None: | ||||
|
r43347 | self.convertfp = open(self.repo.vfs.join(b'shamap'), b'ab') | ||
self.convertfp.write(util.tonativeeol(b'%s %s\n' % (destrev, rev))) | ||||
|
r5554 | self.convertfp.flush() | ||
|
r5805 | |||
def before(self): | ||||
|
r43347 | self.ui.debug(b'run hg source pre-conversion action\n') | ||
|
r5805 | |||
def after(self): | ||||
|
r43347 | self.ui.debug(b'run hg source post-conversion action\n') | ||
|
r8691 | |||
def hasnativeorder(self): | ||||
return True | ||||
|
r8693 | |||
|
r18819 | def hasnativeclose(self): | ||
return True | ||||
|
r8693 | def lookuprev(self, rev): | ||
try: | ||||
|
r28370 | return nodemod.hex(self.repo.lookup(rev)) | ||
|
r23926 | except (error.RepoError, error.LookupError): | ||
|
r8693 | return None | ||
|
r13757 | |||
def getbookmarks(self): | ||||
return bookmarks.listbookmarks(self.repo) | ||||
|
r19120 | |||
|
r43347 | def checkrevformat(self, revstr, mapname=b'splicemap'): | ||
|
r19120 | """ Mercurial, revision string is a 40 byte hex """ | ||
|
r20373 | self.checkhexformat(revstr, mapname) | ||