# Subversion 1.4/1.5 Python API backend## Copyright(C) 2007 Daniel Holth et al import osimport reimport sysimport cPickle as pickleimport tempfileimport urllib from mercurial import strutil, util, encodingfrom mercurial.i18n import _ # Subversion stuff. Works best with very recent Python SVN bindings# e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing# these bindings. from cStringIO import StringIO from common import NoRepo, MissingTool, commit, encodeargs, decodeargsfrom common import commandline, converter_source, converter_sink, mapfile try: from svn.core import SubversionException, Pool import svn import svn.client import svn.core import svn.ra import svn.delta import transport import warnings warnings.filterwarnings('ignore', module='svn.core', category=DeprecationWarning) except ImportError: pass class SvnPathNotFound(Exception): pass def geturl(path): try: return svn.client.url_from_path(svn.core.svn_path_canonicalize(path)) except SubversionException: pass if os.path.isdir(path): path = os.path.normpath(os.path.abspath(path)) if os.name == 'nt': path = '/' + util.normpath(path) # Module URL is later compared with the repository URL returned # by svn API, which is UTF-8. path = encoding.tolocal(path) return 'file://%s' % urllib.quote(path) return path def optrev(number): optrev = svn.core.svn_opt_revision_t() optrev.kind = svn.core.svn_opt_revision_number optrev.value.number = number return optrev class changedpath(object): def __init__(self, p): self.copyfrom_path = p.copyfrom_path self.copyfrom_rev = p.copyfrom_rev self.action = p.action def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True, strict_node_history=False): protocol = -1 def receiver(orig_paths, revnum, author, date, message, pool): if orig_paths is not None: for k, v in orig_paths.iteritems(): orig_paths[k] = changedpath(v) pickle.dump((orig_paths, revnum, author, date, message), fp, protocol) try: # Use an ra of our own so that our parent can consume # our results without confusing the server. t = transport.SvnRaTransport(url=url) svn.ra.get_log(t.ra, paths, start, end, limit, discover_changed_paths, strict_node_history, receiver) except SubversionException, (inst, num): pickle.dump(num, fp, protocol) except IOError: # Caller may interrupt the iteration pickle.dump(None, fp, protocol) else: pickle.dump(None, fp, protocol) fp.close() # With large history, cleanup process goes crazy and suddenly # consumes *huge* amount of memory. The output file being closed, # there is no need for clean termination. os._exit(0) def debugsvnlog(ui, **opts): """Fetch SVN log in a subprocess and channel them back to parent to avoid memory collection issues. """ util.set_binary(sys.stdin) util.set_binary(sys.stdout) args = decodeargs(sys.stdin.read()) get_log_child(sys.stdout, *args) class logstream(object): """Interruptible revision log iterator.""" def __init__(self, stdout): self._stdout = stdout def __iter__(self): while True: entry = pickle.load(self._stdout) try: orig_paths, revnum, author, date, message = entry except: if entry is None: break raise SubversionException("child raised exception", entry) yield entry def close(self): if self._stdout: self._stdout.close() self._stdout = None # Check to see if the given path is a local Subversion repo. Verify this by# looking for several svn-specific files and directories in the given# directory.def filecheck(path, proto): for x in ('locks', 'hooks', 'format', 'db', ): if not os.path.exists(os.path.join(path, x)): return False return True # Check to see if a given path is the root of an svn repo over http. We verify# this by requesting a version-controlled URL we know can't exist and looking# for the svn-specific "not found" XML.def httpcheck(path, proto): return ('<m:human-readable errcode="160013">' in urllib.urlopen('%s://%s/!svn/ver/0/.svn' % (proto, path)).read()) protomap = {'http': httpcheck, 'https': httpcheck, 'file': filecheck, }def issvnurl(url): try: proto, path = url.split('://', 1) path = urllib.url2pathname(path) except ValueError: proto = 'file' path = os.path.abspath(url) path = path.replace(os.sep, '/') check = protomap.get(proto, lambda p, p2: False) while '/' in path: if check(path, proto): return True path = path.rsplit('/', 1)[0] return False # SVN conversion code stolen from bzr-svn and tailor## Subversion looks like a versioned filesystem, branches structures# are defined by conventions and not enforced by the tool. First,# we define the potential branches (modules) as "trunk" and "branches"# children directories. Revisions are then identified by their# module and revision number (and a repository identifier).## The revision graph is really a tree (or a forest). By default, a# revision parent is the previous revision in the same module. If the# module directory is copied/moved from another module then the# revision is the module root and its parent the source revision in# the parent module. A revision has at most one parent.#class svn_source(converter_source): def __init__(self, ui, url, rev=None): super(svn_source, self).__init__(ui, url, rev=rev) if not (url.startswith('svn://') or url.startswith('svn+ssh://') or (os.path.exists(url) and os.path.exists(os.path.join(url, '.svn'))) or issvnurl(url)): raise NoRepo("%s does not look like a Subversion repo" % url) try: SubversionException except NameError: raise MissingTool(_('Subversion python bindings could not be loaded')) try: version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR if version < (1, 4): raise MissingTool(_('Subversion python bindings %d.%d found, ' '1.4 or later required') % version) except AttributeError: raise MissingTool(_('Subversion python bindings are too old, 1.4 ' 'or later required')) self.lastrevs = {} latest = None try: # Support file://path@rev syntax. Useful e.g. to convert # deleted branches. at = url.rfind('@') if at >= 0: latest = int(url[at+1:]) url = url[:at] except ValueError: pass self.url = geturl(url) self.encoding = 'UTF-8' # Subversion is always nominal UTF-8 try: self.transport = transport.SvnRaTransport(url=self.url) self.ra = self.transport.ra self.ctx = self.transport.client self.baseurl = svn.ra.get_repos_root(self.ra) # Module is either empty or a repository path starting with # a slash and not ending with a slash. self.module = urllib.unquote(self.url[len(self.baseurl):]) self.prevmodule = None self.rootmodule = self.module self.commits = {} self.paths = {} self.uuid = svn.ra.get_uuid(self.ra) except SubversionException: ui.traceback() raise NoRepo("%s does not look like a Subversion repo" % self.url) if rev: try: latest = int(rev) except ValueError: raise util.Abort(_('svn: revision %s is not an integer') % rev) self.startrev = self.ui.config('convert', 'svn.startrev', default=0) try: self.startrev = int(self.startrev) if self.startrev < 0: self.startrev = 0 except ValueError: raise util.Abort(_('svn: start revision %s is not an integer') % self.startrev) self.head = self.latest(self.module, latest) if not self.head: raise util.Abort(_('no revision found in module %s') % self.module) self.last_changed = self.revnum(self.head) self._changescache = None if os.path.exists(os.path.join(url, '.svn/entries')): self.wc = url else: self.wc = None self.convertfp = None def setrevmap(self, revmap): lastrevs = {} for revid in revmap.iterkeys(): uuid, module, revnum = self.revsplit(revid) lastrevnum = lastrevs.setdefault(module, revnum) if revnum > lastrevnum: lastrevs[module] = revnum self.lastrevs = lastrevs def exists(self, path, optrev): try: svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path), optrev, False, self.ctx) return True except SubversionException: return False def getheads(self): def isdir(path, revnum): kind = self._checkpath(path, revnum) return kind == svn.core.svn_node_dir def getcfgpath(name, rev): cfgpath = self.ui.config('convert', 'svn.' + name) if cfgpath is not None and cfgpath.strip() == '': return None path = (cfgpath or name).strip('/') if not self.exists(path, rev): if cfgpath: raise util.Abort(_('expected %s to be at %r, but not found') % (name, path)) return None self.ui.note(_('found %s at %r\n') % (name, path)) return path rev = optrev(self.last_changed) oldmodule = '' trunk = getcfgpath('trunk', rev) self.tags = getcfgpath('tags', rev) branches = getcfgpath('branches', rev) # If the project has a trunk or branches, we will extract heads # from them. We keep the project root otherwise. if trunk: oldmodule = self.module or '' self.module += '/' + trunk self.head = self.latest(self.module, self.last_changed) if not self.head: raise util.Abort(_('no revision found in module %s') % self.module) # First head in the list is the module's head self.heads = [self.head] if self.tags is not None: self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags')) # Check if branches bring a few more heads to the list if branches: rpath = self.url.strip('/') branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches), rev, False, self.ctx) for branch in branchnames.keys(): module = '%s/%s/%s' % (oldmodule, branches, branch) if not isdir(module, self.last_changed): continue brevid = self.latest(module, self.last_changed) if not brevid: self.ui.note(_('ignoring empty branch %s\n') % branch) continue self.ui.note(_('found branch %s at %d\n') % (branch, self.revnum(brevid))) self.heads.append(brevid) if self.startrev and self.heads: if len(self.heads) > 1: raise util.Abort(_('svn: start revision is not supported ' 'with more than one branch')) revnum = self.revnum(self.heads[0]) if revnum < self.startrev: raise util.Abort(_('svn: no revision found after start revision %d') % self.startrev) return self.heads def getfile(self, file, rev): data, mode = self._getfile(file, rev) self.modecache[(file, rev)] = mode return data def getmode(self, file, rev): return self.modecache[(file, rev)] def getchanges(self, rev): if self._changescache and self._changescache[0] == rev: return self._changescache[1] self._changescache = None self.modecache = {} (paths, parents) = self.paths[rev] if parents: files, copies = self.expandpaths(rev, paths, parents) else: # Perform a full checkout on roots uuid, module, revnum = self.revsplit(rev) entries = svn.client.ls(self.baseurl + urllib.quote(module), optrev(revnum), True, self.ctx) files = [n for n,e in entries.iteritems() if e.kind == svn.core.svn_node_file] copies = {} files.sort() files = zip(files, [rev] * len(files)) # caller caches the result, so free it here to release memory del self.paths[rev] return (files, copies) def getchangedfiles(self, rev, i): changes = self.getchanges(rev) self._changescache = (rev, changes) return [f[0] for f in changes[0]] def getcommit(self, rev): if rev not in self.commits: uuid, module, revnum = self.revsplit(rev) self.module = module self.reparent(module) # We assume that: # - requests for revisions after "stop" come from the # revision graph backward traversal. Cache all of them # down to stop, they will be used eventually. # - requests for revisions before "stop" come to get # isolated branches parents. Just fetch what is needed. stop = self.lastrevs.get(module, 0) if revnum < stop: stop = revnum + 1 self._fetch_revisions(revnum, stop) commit = self.commits[rev] # caller caches the result, so free it here to release memory del self.commits[rev] return commit def gettags(self): tags = {} if self.tags is None: return tags # svn tags are just a convention, project branches left in a # 'tags' directory. There is no other relationship than # ancestry, which is expensive to discover and makes them hard # to update incrementally. Worse, past revisions may be # referenced by tags far away in the future, requiring a deep # history traversal on every calculation. Current code # performs a single backward traversal, tracking moves within # the tags directory (tag renaming) and recording a new tag # everytime a project is copied from outside the tags # directory. It also lists deleted tags, this behaviour may # change in the future. pendings = [] tagspath = self.tags start = svn.ra.get_latest_revnum(self.ra) try: for entry in self._getlog([self.tags], start, self.startrev): origpaths, revnum, author, date, message = entry copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e in origpaths.iteritems() if e.copyfrom_path] # Apply moves/copies from more specific to general copies.sort(reverse=True) srctagspath = tagspath if copies and copies[-1][2] == tagspath: # Track tags directory moves srctagspath = copies.pop()[0] for source, sourcerev, dest in copies: if not dest.startswith(tagspath + '/'): continue for tag in pendings: if tag[0].startswith(dest): tagpath = source + tag[0][len(dest):] tag[:2] = [tagpath, sourcerev] break else: pendings.append([source, sourcerev, dest]) # Filter out tags with children coming from different # parts of the repository like: # /tags/tag.1 (from /trunk:10) # /tags/tag.1/foo (from /branches/foo:12) # Here/tags/tag.1 discarded as well as its children. # It happens with tools like cvs2svn. Such tags cannot # be represented in mercurial. addeds = dict((p, e.copyfrom_path) for p, e in origpaths.iteritems() if e.action == 'A' and e.copyfrom_path) badroots = set() for destroot in addeds: for source, sourcerev, dest in pendings: if (not dest.startswith(destroot + '/') or source.startswith(addeds[destroot] + '/')): continue badroots.add(destroot) break for badroot in badroots: pendings = [p for p in pendings if p[2] != badroot and not p[2].startswith(badroot + '/')] # Tell tag renamings from tag creations remainings = [] for source, sourcerev, dest in pendings: tagname = dest.split('/')[-1] if source.startswith(srctagspath): remainings.append([source, sourcerev, tagname]) continue if tagname in tags: # Keep the latest tag value continue # From revision may be fake, get one with changes try: tagid = self.latest(source, sourcerev) if tagid and tagname not in tags: tags[tagname] = tagid except SvnPathNotFound: # It happens when we are following directories # we assumed were copied with their parents # but were really created in the tag # directory. pass pendings = remainings tagspath = srctagspath except SubversionException: self.ui.note(_('no tags found at revision %d\n') % start) return tags Lines 1–500 of 1136. Load more
# Subversion 1.4/1.5 Python API backend # # Copyright(C) 2007 Daniel Holth et al import os import re import sys import cPickle as pickle import tempfile import urllib from mercurial import strutil, util, encoding from mercurial.i18n import _ # Subversion stuff. Works best with very recent Python SVN bindings # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing # these bindings. from cStringIO import StringIO from common import NoRepo, MissingTool, commit, encodeargs, decodeargs from common import commandline, converter_source, converter_sink, mapfile try: from svn.core import SubversionException, Pool import svn import svn.client import svn.core import svn.ra import svn.delta import transport import warnings warnings.filterwarnings('ignore', module='svn.core', category=DeprecationWarning) except ImportError: pass class SvnPathNotFound(Exception): pass def geturl(path): try: return svn.client.url_from_path(svn.core.svn_path_canonicalize(path)) except SubversionException: pass if os.path.isdir(path): path = os.path.normpath(os.path.abspath(path)) if os.name == 'nt': path = '/' + util.normpath(path) # Module URL is later compared with the repository URL returned # by svn API, which is UTF-8. path = encoding.tolocal(path) return 'file://%s' % urllib.quote(path) return path def optrev(number): optrev = svn.core.svn_opt_revision_t() optrev.kind = svn.core.svn_opt_revision_number optrev.value.number = number return optrev class changedpath(object): def __init__(self, p): self.copyfrom_path = p.copyfrom_path self.copyfrom_rev = p.copyfrom_rev self.action = p.action def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True, strict_node_history=False): protocol = -1 def receiver(orig_paths, revnum, author, date, message, pool): if orig_paths is not None: for k, v in orig_paths.iteritems(): orig_paths[k] = changedpath(v) pickle.dump((orig_paths, revnum, author, date, message), fp, protocol) try: # Use an ra of our own so that our parent can consume # our results without confusing the server. t = transport.SvnRaTransport(url=url) svn.ra.get_log(t.ra, paths, start, end, limit, discover_changed_paths, strict_node_history, receiver) except SubversionException, (inst, num): pickle.dump(num, fp, protocol) except IOError: # Caller may interrupt the iteration pickle.dump(None, fp, protocol) else: pickle.dump(None, fp, protocol) fp.close() # With large history, cleanup process goes crazy and suddenly # consumes *huge* amount of memory. The output file being closed, # there is no need for clean termination. os._exit(0) def debugsvnlog(ui, **opts): """Fetch SVN log in a subprocess and channel them back to parent to avoid memory collection issues. """ util.set_binary(sys.stdin) util.set_binary(sys.stdout) args = decodeargs(sys.stdin.read()) get_log_child(sys.stdout, *args) class logstream(object): """Interruptible revision log iterator.""" def __init__(self, stdout): self._stdout = stdout def __iter__(self): while True: entry = pickle.load(self._stdout) try: orig_paths, revnum, author, date, message = entry except: if entry is None: break raise SubversionException("child raised exception", entry) yield entry def close(self): if self._stdout: self._stdout.close() self._stdout = None # Check to see if the given path is a local Subversion repo. Verify this by # looking for several svn-specific files and directories in the given # directory. def filecheck(path, proto): for x in ('locks', 'hooks', 'format', 'db', ): if not os.path.exists(os.path.join(path, x)): return False return True # Check to see if a given path is the root of an svn repo over http. We verify # this by requesting a version-controlled URL we know can't exist and looking # for the svn-specific "not found" XML. def httpcheck(path, proto): return ('<m:human-readable errcode="160013">' in urllib.urlopen('%s://%s/!svn/ver/0/.svn' % (proto, path)).read()) protomap = {'http': httpcheck, 'https': httpcheck, 'file': filecheck, } def issvnurl(url): try: proto, path = url.split('://', 1) path = urllib.url2pathname(path) except ValueError: proto = 'file' path = os.path.abspath(url) path = path.replace(os.sep, '/') check = protomap.get(proto, lambda p, p2: False) while '/' in path: if check(path, proto): return True path = path.rsplit('/', 1)[0] return False # SVN conversion code stolen from bzr-svn and tailor # # Subversion looks like a versioned filesystem, branches structures # are defined by conventions and not enforced by the tool. First, # we define the potential branches (modules) as "trunk" and "branches" # children directories. Revisions are then identified by their # module and revision number (and a repository identifier). # # The revision graph is really a tree (or a forest). By default, a # revision parent is the previous revision in the same module. If the # module directory is copied/moved from another module then the # revision is the module root and its parent the source revision in # the parent module. A revision has at most one parent. # class svn_source(converter_source): def __init__(self, ui, url, rev=None): super(svn_source, self).__init__(ui, url, rev=rev) if not (url.startswith('svn://') or url.startswith('svn+ssh://') or (os.path.exists(url) and os.path.exists(os.path.join(url, '.svn'))) or issvnurl(url)): raise NoRepo("%s does not look like a Subversion repo" % url) try: SubversionException except NameError: raise MissingTool(_('Subversion python bindings could not be loaded')) try: version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR if version < (1, 4): raise MissingTool(_('Subversion python bindings %d.%d found, ' '1.4 or later required') % version) except AttributeError: raise MissingTool(_('Subversion python bindings are too old, 1.4 ' 'or later required')) self.lastrevs = {} latest = None try: # Support file://path@rev syntax. Useful e.g. to convert # deleted branches. at = url.rfind('@') if at >= 0: latest = int(url[at+1:]) url = url[:at] except ValueError: pass self.url = geturl(url) self.encoding = 'UTF-8' # Subversion is always nominal UTF-8 try: self.transport = transport.SvnRaTransport(url=self.url) self.ra = self.transport.ra self.ctx = self.transport.client self.baseurl = svn.ra.get_repos_root(self.ra) # Module is either empty or a repository path starting with # a slash and not ending with a slash. self.module = urllib.unquote(self.url[len(self.baseurl):]) self.prevmodule = None self.rootmodule = self.module self.commits = {} self.paths = {} self.uuid = svn.ra.get_uuid(self.ra) except SubversionException: ui.traceback() raise NoRepo("%s does not look like a Subversion repo" % self.url) if rev: try: latest = int(rev) except ValueError: raise util.Abort(_('svn: revision %s is not an integer') % rev) self.startrev = self.ui.config('convert', 'svn.startrev', default=0) try: self.startrev = int(self.startrev) if self.startrev < 0: self.startrev = 0 except ValueError: raise util.Abort(_('svn: start revision %s is not an integer') % self.startrev) self.head = self.latest(self.module, latest) if not self.head: raise util.Abort(_('no revision found in module %s') % self.module) self.last_changed = self.revnum(self.head) self._changescache = None if os.path.exists(os.path.join(url, '.svn/entries')): self.wc = url else: self.wc = None self.convertfp = None def setrevmap(self, revmap): lastrevs = {} for revid in revmap.iterkeys(): uuid, module, revnum = self.revsplit(revid) lastrevnum = lastrevs.setdefault(module, revnum) if revnum > lastrevnum: lastrevs[module] = revnum self.lastrevs = lastrevs def exists(self, path, optrev): try: svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path), optrev, False, self.ctx) return True except SubversionException: return False def getheads(self): def isdir(path, revnum): kind = self._checkpath(path, revnum) return kind == svn.core.svn_node_dir def getcfgpath(name, rev): cfgpath = self.ui.config('convert', 'svn.' + name) if cfgpath is not None and cfgpath.strip() == '': return None path = (cfgpath or name).strip('/') if not self.exists(path, rev): if cfgpath: raise util.Abort(_('expected %s to be at %r, but not found') % (name, path)) return None self.ui.note(_('found %s at %r\n') % (name, path)) return path rev = optrev(self.last_changed) oldmodule = '' trunk = getcfgpath('trunk', rev) self.tags = getcfgpath('tags', rev) branches = getcfgpath('branches', rev) # If the project has a trunk or branches, we will extract heads # from them. We keep the project root otherwise. if trunk: oldmodule = self.module or '' self.module += '/' + trunk self.head = self.latest(self.module, self.last_changed) if not self.head: raise util.Abort(_('no revision found in module %s') % self.module) # First head in the list is the module's head self.heads = [self.head] if self.tags is not None: self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags')) # Check if branches bring a few more heads to the list if branches: rpath = self.url.strip('/') branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches), rev, False, self.ctx) for branch in branchnames.keys(): module = '%s/%s/%s' % (oldmodule, branches, branch) if not isdir(module, self.last_changed): continue brevid = self.latest(module, self.last_changed) if not brevid: self.ui.note(_('ignoring empty branch %s\n') % branch) continue self.ui.note(_('found branch %s at %d\n') % (branch, self.revnum(brevid))) self.heads.append(brevid) if self.startrev and self.heads: if len(self.heads) > 1: raise util.Abort(_('svn: start revision is not supported ' 'with more than one branch')) revnum = self.revnum(self.heads[0]) if revnum < self.startrev: raise util.Abort(_('svn: no revision found after start revision %d') % self.startrev) return self.heads def getfile(self, file, rev): data, mode = self._getfile(file, rev) self.modecache[(file, rev)] = mode return data def getmode(self, file, rev): return self.modecache[(file, rev)] def getchanges(self, rev): if self._changescache and self._changescache[0] == rev: return self._changescache[1] self._changescache = None self.modecache = {} (paths, parents) = self.paths[rev] if parents: files, copies = self.expandpaths(rev, paths, parents) else: # Perform a full checkout on roots uuid, module, revnum = self.revsplit(rev) entries = svn.client.ls(self.baseurl + urllib.quote(module), optrev(revnum), True, self.ctx) files = [n for n,e in entries.iteritems() if e.kind == svn.core.svn_node_file] copies = {} files.sort() files = zip(files, [rev] * len(files)) # caller caches the result, so free it here to release memory del self.paths[rev] return (files, copies) def getchangedfiles(self, rev, i): changes = self.getchanges(rev) self._changescache = (rev, changes) return [f[0] for f in changes[0]] def getcommit(self, rev): if rev not in self.commits: uuid, module, revnum = self.revsplit(rev) self.module = module self.reparent(module) # We assume that: # - requests for revisions after "stop" come from the # revision graph backward traversal. Cache all of them # down to stop, they will be used eventually. # - requests for revisions before "stop" come to get # isolated branches parents. Just fetch what is needed. stop = self.lastrevs.get(module, 0) if revnum < stop: stop = revnum + 1 self._fetch_revisions(revnum, stop) commit = self.commits[rev] # caller caches the result, so free it here to release memory del self.commits[rev] return commit def gettags(self): tags = {} if self.tags is None: return tags # svn tags are just a convention, project branches left in a # 'tags' directory. There is no other relationship than # ancestry, which is expensive to discover and makes them hard # to update incrementally. Worse, past revisions may be # referenced by tags far away in the future, requiring a deep # history traversal on every calculation. Current code # performs a single backward traversal, tracking moves within # the tags directory (tag renaming) and recording a new tag # everytime a project is copied from outside the tags # directory. It also lists deleted tags, this behaviour may # change in the future. pendings = [] tagspath = self.tags start = svn.ra.get_latest_revnum(self.ra) try: for entry in self._getlog([self.tags], start, self.startrev): origpaths, revnum, author, date, message = entry copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e in origpaths.iteritems() if e.copyfrom_path] # Apply moves/copies from more specific to general copies.sort(reverse=True) srctagspath = tagspath if copies and copies[-1][2] == tagspath: # Track tags directory moves srctagspath = copies.pop()[0] for source, sourcerev, dest in copies: if not dest.startswith(tagspath + '/'): continue for tag in pendings: if tag[0].startswith(dest): tagpath = source + tag[0][len(dest):] tag[:2] = [tagpath, sourcerev] break else: pendings.append([source, sourcerev, dest]) # Filter out tags with children coming from different # parts of the repository like: # /tags/tag.1 (from /trunk:10) # /tags/tag.1/foo (from /branches/foo:12) # Here/tags/tag.1 discarded as well as its children. # It happens with tools like cvs2svn. Such tags cannot # be represented in mercurial. addeds = dict((p, e.copyfrom_path) for p, e in origpaths.iteritems() if e.action == 'A' and e.copyfrom_path) badroots = set() for destroot in addeds: for source, sourcerev, dest in pendings: if (not dest.startswith(destroot + '/') or source.startswith(addeds[destroot] + '/')): continue badroots.add(destroot) break for badroot in badroots: pendings = [p for p in pendings if p[2] != badroot and not p[2].startswith(badroot + '/')] # Tell tag renamings from tag creations remainings = [] for source, sourcerev, dest in pendings: tagname = dest.split('/')[-1] if source.startswith(srctagspath): remainings.append([source, sourcerev, tagname]) continue if tagname in tags: # Keep the latest tag value continue # From revision may be fake, get one with changes try: tagid = self.latest(source, sourcerev) if tagid and tagname not in tags: tags[tagname] = tagid except SvnPathNotFound: # It happens when we are following directories # we assumed were copied with their parents # but were really created in the tag # directory. pass pendings = remainings tagspath = srctagspath except SubversionException: self.ui.note(_('no tags found at revision %d\n') % start) return tags