eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/convert/bzr.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # bzr.py - bzr support for the convert extension
       
     2 #
       
     3 #  Copyright 2008, 2009 Marek Kubica <marek@xivilization.net> and others
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
       
     9 # it cannot access 'bar' repositories, but they were never used very much
       
    10 
       
    11 import os
       
    12 from mercurial import demandimport
       
    13 # these do not work with demandimport, blacklist
       
    14 demandimport.ignore.extend([
       
    15         'bzrlib.transactions',
       
    16         'bzrlib.urlutils',
       
    17         'ElementPath',
       
    18     ])
       
    19 
       
    20 from mercurial.i18n import _
       
    21 from mercurial import util
       
    22 from common import NoRepo, commit, converter_source
       
    23 
       
    24 try:
       
    25     # bazaar imports
       
    26     from bzrlib import branch, revision, errors
       
    27     from bzrlib.revisionspec import RevisionSpec
       
    28 except ImportError:
       
    29     pass
       
    30 
       
    31 supportedkinds = ('file', 'symlink')
       
    32 
       
    33 class bzr_source(converter_source):
       
    34     """Reads Bazaar repositories by using the Bazaar Python libraries"""
       
    35 
       
    36     def __init__(self, ui, path, rev=None):
       
    37         super(bzr_source, self).__init__(ui, path, rev=rev)
       
    38 
       
    39         if not os.path.exists(os.path.join(path, '.bzr')):
       
    40             raise NoRepo(_('%s does not look like a Bazaar repository')
       
    41                          % path)
       
    42 
       
    43         try:
       
    44             # access bzrlib stuff
       
    45             branch
       
    46         except NameError:
       
    47             raise NoRepo(_('Bazaar modules could not be loaded'))
       
    48 
       
    49         path = os.path.abspath(path)
       
    50         self._checkrepotype(path)
       
    51         self.branch = branch.Branch.open(path)
       
    52         self.sourcerepo = self.branch.repository
       
    53         self._parentids = {}
       
    54 
       
    55     def _checkrepotype(self, path):
       
    56         # Lightweight checkouts detection is informational but probably
       
    57         # fragile at API level. It should not terminate the conversion.
       
    58         try:
       
    59             from bzrlib import bzrdir
       
    60             dir = bzrdir.BzrDir.open_containing(path)[0]
       
    61             try:
       
    62                 tree = dir.open_workingtree(recommend_upgrade=False)
       
    63                 branch = tree.branch
       
    64             except (errors.NoWorkingTree, errors.NotLocalUrl):
       
    65                 tree = None
       
    66                 branch = dir.open_branch()
       
    67             if (tree is not None and tree.bzrdir.root_transport.base !=
       
    68                 branch.bzrdir.root_transport.base):
       
    69                 self.ui.warn(_('warning: lightweight checkouts may cause '
       
    70                                'conversion failures, try with a regular '
       
    71                                'branch instead.\n'))
       
    72         except:
       
    73             self.ui.note(_('bzr source type could not be determined\n'))
       
    74 
       
    75     def before(self):
       
    76         """Before the conversion begins, acquire a read lock
       
    77         for all the operations that might need it. Fortunately
       
    78         read locks don't block other reads or writes to the
       
    79         repository, so this shouldn't have any impact on the usage of
       
    80         the source repository.
       
    81 
       
    82         The alternative would be locking on every operation that
       
    83         needs locks (there are currently two: getting the file and
       
    84         getting the parent map) and releasing immediately after,
       
    85         but this approach can take even 40% longer."""
       
    86         self.sourcerepo.lock_read()
       
    87 
       
    88     def after(self):
       
    89         self.sourcerepo.unlock()
       
    90 
       
    91     def getheads(self):
       
    92         if not self.rev:
       
    93             return [self.branch.last_revision()]
       
    94         try:
       
    95             r = RevisionSpec.from_string(self.rev)
       
    96             info = r.in_history(self.branch)
       
    97         except errors.BzrError:
       
    98             raise util.Abort(_('%s is not a valid revision in current branch')
       
    99                              % self.rev)
       
   100         return [info.rev_id]
       
   101 
       
   102     def getfile(self, name, rev):
       
   103         revtree = self.sourcerepo.revision_tree(rev)
       
   104         fileid = revtree.path2id(name.decode(self.encoding or 'utf-8'))
       
   105         kind = None
       
   106         if fileid is not None:
       
   107             kind = revtree.kind(fileid)
       
   108         if kind not in supportedkinds:
       
   109             # the file is not available anymore - was deleted
       
   110             raise IOError(_('%s is not available in %s anymore') %
       
   111                     (name, rev))
       
   112         mode = self._modecache[(name, rev)]
       
   113         if kind == 'symlink':
       
   114             target = revtree.get_symlink_target(fileid)
       
   115             if target is None:
       
   116                 raise util.Abort(_('%s.%s symlink has no target')
       
   117                                  % (name, rev))
       
   118             return target, mode
       
   119         else:
       
   120             sio = revtree.get_file(fileid)
       
   121             return sio.read(), mode
       
   122 
       
   123     def getchanges(self, version):
       
   124         # set up caches: modecache and revtree
       
   125         self._modecache = {}
       
   126         self._revtree = self.sourcerepo.revision_tree(version)
       
   127         # get the parentids from the cache
       
   128         parentids = self._parentids.pop(version)
       
   129         # only diff against first parent id
       
   130         prevtree = self.sourcerepo.revision_tree(parentids[0])
       
   131         return self._gettreechanges(self._revtree, prevtree)
       
   132 
       
   133     def getcommit(self, version):
       
   134         rev = self.sourcerepo.get_revision(version)
       
   135         # populate parent id cache
       
   136         if not rev.parent_ids:
       
   137             parents = []
       
   138             self._parentids[version] = (revision.NULL_REVISION,)
       
   139         else:
       
   140             parents = self._filterghosts(rev.parent_ids)
       
   141             self._parentids[version] = parents
       
   142 
       
   143         return commit(parents=parents,
       
   144                 date='%d %d' % (rev.timestamp, -rev.timezone),
       
   145                 author=self.recode(rev.committer),
       
   146                 # bzr returns bytestrings or unicode, depending on the content
       
   147                 desc=self.recode(rev.message),
       
   148                 rev=version)
       
   149 
       
   150     def gettags(self):
       
   151         if not self.branch.supports_tags():
       
   152             return {}
       
   153         tagdict = self.branch.tags.get_tag_dict()
       
   154         bytetags = {}
       
   155         for name, rev in tagdict.iteritems():
       
   156             bytetags[self.recode(name)] = rev
       
   157         return bytetags
       
   158 
       
   159     def getchangedfiles(self, rev, i):
       
   160         self._modecache = {}
       
   161         curtree = self.sourcerepo.revision_tree(rev)
       
   162         if i is not None:
       
   163             parentid = self._parentids[rev][i]
       
   164         else:
       
   165             # no parent id, get the empty revision
       
   166             parentid = revision.NULL_REVISION
       
   167 
       
   168         prevtree = self.sourcerepo.revision_tree(parentid)
       
   169         changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
       
   170         return changes
       
   171 
       
   172     def _gettreechanges(self, current, origin):
       
   173         revid = current._revision_id
       
   174         changes = []
       
   175         renames = {}
       
   176         for (fileid, paths, changed_content, versioned, parent, name,
       
   177             kind, executable) in current.iter_changes(origin):
       
   178 
       
   179             if paths[0] == u'' or paths[1] == u'':
       
   180                 # ignore changes to tree root
       
   181                 continue
       
   182 
       
   183             # bazaar tracks directories, mercurial does not, so
       
   184             # we have to rename the directory contents
       
   185             if kind[1] == 'directory':
       
   186                 if kind[0] not in (None, 'directory'):
       
   187                     # Replacing 'something' with a directory, record it
       
   188                     # so it can be removed.
       
   189                     changes.append((self.recode(paths[0]), revid))
       
   190 
       
   191                 if None not in paths and paths[0] != paths[1]:
       
   192                     # neither an add nor an delete - a move
       
   193                     # rename all directory contents manually
       
   194                     subdir = origin.inventory.path2id(paths[0])
       
   195                     # get all child-entries of the directory
       
   196                     for name, entry in origin.inventory.iter_entries(subdir):
       
   197                         # hg does not track directory renames
       
   198                         if entry.kind == 'directory':
       
   199                             continue
       
   200                         frompath = self.recode(paths[0] + '/' + name)
       
   201                         topath = self.recode(paths[1] + '/' + name)
       
   202                         # register the files as changed
       
   203                         changes.append((frompath, revid))
       
   204                         changes.append((topath, revid))
       
   205                         # add to mode cache
       
   206                         mode = ((entry.executable and 'x')
       
   207                                 or (entry.kind == 'symlink' and 's')
       
   208                                 or '')
       
   209                         self._modecache[(topath, revid)] = mode
       
   210                         # register the change as move
       
   211                         renames[topath] = frompath
       
   212 
       
   213                 # no futher changes, go to the next change
       
   214                 continue
       
   215 
       
   216             # we got unicode paths, need to convert them
       
   217             path, topath = [self.recode(part) for part in paths]
       
   218 
       
   219             if topath is None:
       
   220                 # file deleted
       
   221                 changes.append((path, revid))
       
   222                 continue
       
   223 
       
   224             # renamed
       
   225             if path and path != topath:
       
   226                 renames[topath] = path
       
   227                 changes.append((path, revid))
       
   228 
       
   229             # populate the mode cache
       
   230             kind, executable = [e[1] for e in (kind, executable)]
       
   231             mode = ((executable and 'x') or (kind == 'symlink' and 'l')
       
   232                     or '')
       
   233             self._modecache[(topath, revid)] = mode
       
   234             changes.append((topath, revid))
       
   235 
       
   236         return changes, renames
       
   237 
       
   238     def _filterghosts(self, ids):
       
   239         """Filters out ghost revisions which hg does not support, see
       
   240         <http://bazaar-vcs.org/GhostRevision>
       
   241         """
       
   242         parentmap = self.sourcerepo.get_parent_map(ids)
       
   243         parents = tuple([parent for parent in ids if parent in parentmap])
       
   244         return parents
       
   245 
       
   246     def recode(self, s, encoding=None):
       
   247         """This version of recode tries to encode unicode to bytecode,
       
   248         and preferably using the UTF-8 codec.
       
   249         Other types than Unicode are silently returned, this is by
       
   250         intention, e.g. the None-type is not going to be encoded but instead
       
   251         just passed through
       
   252         """
       
   253         if not encoding:
       
   254             encoding = self.encoding or 'utf-8'
       
   255 
       
   256         if isinstance(s, unicode):
       
   257             return s.encode(encoding)
       
   258         else:
       
   259             # leave it alone
       
   260             return s