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