be4b9a34edc68b7112c925e6023d61805fffb91e
   1#!/usr/bin/env python
   2#
   3# Copyright (c) 2012 Felipe Contreras
   4#
   5
   6#
   7# Just copy to your ~/bin, or anywhere in your $PATH.
   8# Then you can clone with:
   9# % git clone bzr::/path/to/bzr/repo/or/url
  10#
  11# For example:
  12# % git clone bzr::$HOME/myrepo
  13# or
  14# % git clone bzr::lp:myrepo
  15#
  16# If you want to specify which branches you want to track (per repo):
  17# % git config remote.origin.bzr-branches 'trunk, devel, test'
  18#
  19# Where 'origin' is the name of the repository you want to specify the
  20# branches.
  21#
  22
  23import sys
  24
  25import bzrlib
  26if hasattr(bzrlib, "initialize"):
  27    bzrlib.initialize()
  28
  29import bzrlib.plugin
  30bzrlib.plugin.load_plugins()
  31
  32import bzrlib.generate_ids
  33import bzrlib.transport
  34import bzrlib.errors
  35import bzrlib.ui
  36import bzrlib.urlutils
  37import bzrlib.branch
  38
  39import sys
  40import os
  41import json
  42import re
  43import StringIO
  44import atexit, shutil, hashlib, urlparse, subprocess
  45
  46sys.stderr.write('WARNING: git-remote-bzr is now maintained independently.\n')
  47sys.stderr.write('WARNING: For more information visit https://github.com/felipec/git-remote-bzr\n')
  48
  49NAME_RE = re.compile('^([^<>]+)')
  50AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
  51EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
  52RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
  53
  54def die(msg, *args):
  55    sys.stderr.write('ERROR: %s\n' % (msg % args))
  56    sys.exit(1)
  57
  58def warn(msg, *args):
  59    sys.stderr.write('WARNING: %s\n' % (msg % args))
  60
  61def gittz(tz):
  62    return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
  63
  64def get_config(config):
  65    cmd = ['git', 'config', '--get', config]
  66    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
  67    output, _ = process.communicate()
  68    return output
  69
  70class Marks:
  71
  72    def __init__(self, path):
  73        self.path = path
  74        self.tips = {}
  75        self.marks = {}
  76        self.rev_marks = {}
  77        self.last_mark = 0
  78        self.load()
  79
  80    def load(self):
  81        if not os.path.exists(self.path):
  82            return
  83
  84        tmp = json.load(open(self.path))
  85        self.tips = tmp['tips']
  86        self.marks = tmp['marks']
  87        self.last_mark = tmp['last-mark']
  88
  89        for rev, mark in self.marks.iteritems():
  90            self.rev_marks[mark] = rev
  91
  92    def dict(self):
  93        return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
  94
  95    def store(self):
  96        json.dump(self.dict(), open(self.path, 'w'))
  97
  98    def __str__(self):
  99        return str(self.dict())
 100
 101    def from_rev(self, rev):
 102        return self.marks[rev]
 103
 104    def to_rev(self, mark):
 105        return str(self.rev_marks[mark])
 106
 107    def next_mark(self):
 108        self.last_mark += 1
 109        return self.last_mark
 110
 111    def get_mark(self, rev):
 112        self.last_mark += 1
 113        self.marks[rev] = self.last_mark
 114        return self.last_mark
 115
 116    def is_marked(self, rev):
 117        return rev in self.marks
 118
 119    def new_mark(self, rev, mark):
 120        self.marks[rev] = mark
 121        self.rev_marks[mark] = rev
 122        self.last_mark = mark
 123
 124    def get_tip(self, branch):
 125        try:
 126            return str(self.tips[branch])
 127        except KeyError:
 128            return None
 129
 130    def set_tip(self, branch, tip):
 131        self.tips[branch] = tip
 132
 133class Parser:
 134
 135    def __init__(self, repo):
 136        self.repo = repo
 137        self.line = self.get_line()
 138
 139    def get_line(self):
 140        return sys.stdin.readline().strip()
 141
 142    def __getitem__(self, i):
 143        return self.line.split()[i]
 144
 145    def check(self, word):
 146        return self.line.startswith(word)
 147
 148    def each_block(self, separator):
 149        while self.line != separator:
 150            yield self.line
 151            self.line = self.get_line()
 152
 153    def __iter__(self):
 154        return self.each_block('')
 155
 156    def next(self):
 157        self.line = self.get_line()
 158        if self.line == 'done':
 159            self.line = None
 160
 161    def get_mark(self):
 162        i = self.line.index(':') + 1
 163        return int(self.line[i:])
 164
 165    def get_data(self):
 166        if not self.check('data'):
 167            return None
 168        i = self.line.index(' ') + 1
 169        size = int(self.line[i:])
 170        return sys.stdin.read(size)
 171
 172    def get_author(self):
 173        m = RAW_AUTHOR_RE.match(self.line)
 174        if not m:
 175            return None
 176        _, name, email, date, tz = m.groups()
 177        name = name.decode('utf-8')
 178        committer = '%s <%s>' % (name, email)
 179        tz = int(tz)
 180        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
 181        return (committer, int(date), tz)
 182
 183def rev_to_mark(rev):
 184    return marks.from_rev(rev)
 185
 186def mark_to_rev(mark):
 187    return marks.to_rev(mark)
 188
 189def fixup_user(user):
 190    name = mail = None
 191    user = user.replace('"', '')
 192    m = AUTHOR_RE.match(user)
 193    if m:
 194        name = m.group(1)
 195        mail = m.group(2).strip()
 196    else:
 197        m = EMAIL_RE.match(user)
 198        if m:
 199            mail = m.group(1)
 200        else:
 201            m = NAME_RE.match(user)
 202            if m:
 203                name = m.group(1).strip()
 204
 205    if not name:
 206        name = 'unknown'
 207    if not mail:
 208        mail = 'Unknown'
 209
 210    return '%s <%s>' % (name, mail)
 211
 212def get_filechanges(cur, prev):
 213    modified = {}
 214    removed = {}
 215
 216    changes = cur.changes_from(prev)
 217
 218    def u(s):
 219        return s.encode('utf-8')
 220
 221    for path, fid, kind in changes.added:
 222        modified[u(path)] = fid
 223    for path, fid, kind in changes.removed:
 224        removed[u(path)] = None
 225    for path, fid, kind, mod, _ in changes.modified:
 226        modified[u(path)] = fid
 227    for oldpath, newpath, fid, kind, mod, _ in changes.renamed:
 228        removed[u(oldpath)] = None
 229        if kind == 'directory':
 230            lst = cur.list_files(from_dir=newpath, recursive=True)
 231            for path, file_class, kind, fid, entry in lst:
 232                if kind != 'directory':
 233                    modified[u(newpath + '/' + path)] = fid
 234        else:
 235            modified[u(newpath)] = fid
 236
 237    return modified, removed
 238
 239def export_files(tree, files):
 240    final = []
 241    for path, fid in files.iteritems():
 242        kind = tree.kind(fid)
 243
 244        h = tree.get_file_sha1(fid)
 245
 246        if kind == 'symlink':
 247            d = tree.get_symlink_target(fid)
 248            mode = '120000'
 249        elif kind == 'file':
 250
 251            if tree.is_executable(fid):
 252                mode = '100755'
 253            else:
 254                mode = '100644'
 255
 256            # is the blob already exported?
 257            if h in filenodes:
 258                mark = filenodes[h]
 259                final.append((mode, mark, path))
 260                continue
 261
 262            d = tree.get_file_text(fid)
 263        elif kind == 'directory':
 264            continue
 265        else:
 266            die("Unhandled kind '%s' for path '%s'" % (kind, path))
 267
 268        mark = marks.next_mark()
 269        filenodes[h] = mark
 270
 271        print "blob"
 272        print "mark :%u" % mark
 273        print "data %d" % len(d)
 274        print d
 275
 276        final.append((mode, mark, path))
 277
 278    return final
 279
 280def export_branch(repo, name):
 281    ref = '%s/heads/%s' % (prefix, name)
 282    tip = marks.get_tip(name)
 283
 284    branch = get_remote_branch(name)
 285    repo = branch.repository
 286
 287    branch.lock_read()
 288    revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
 289    try:
 290        tip_revno = branch.revision_id_to_revno(tip)
 291        last_revno, _ = branch.last_revision_info()
 292        total = last_revno - tip_revno
 293    except bzrlib.errors.NoSuchRevision:
 294        tip_revno = 0
 295        total = 0
 296
 297    for revid, _, seq, _ in revs:
 298
 299        if marks.is_marked(revid):
 300            continue
 301
 302        rev = repo.get_revision(revid)
 303        revno = seq[0]
 304
 305        parents = rev.parent_ids
 306        time = rev.timestamp
 307        tz = rev.timezone
 308        committer = rev.committer.encode('utf-8')
 309        committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
 310        authors = rev.get_apparent_authors()
 311        if authors:
 312            author = authors[0].encode('utf-8')
 313            author = "%s %u %s" % (fixup_user(author), time, gittz(tz))
 314        else:
 315            author = committer
 316        msg = rev.message.encode('utf-8')
 317
 318        msg += '\n'
 319
 320        if len(parents) == 0:
 321            parent = bzrlib.revision.NULL_REVISION
 322        else:
 323            parent = parents[0]
 324
 325        cur_tree = repo.revision_tree(revid)
 326        prev = repo.revision_tree(parent)
 327        modified, removed = get_filechanges(cur_tree, prev)
 328
 329        modified_final = export_files(cur_tree, modified)
 330
 331        if len(parents) == 0:
 332            print 'reset %s' % ref
 333
 334        print "commit %s" % ref
 335        print "mark :%d" % (marks.get_mark(revid))
 336        print "author %s" % (author)
 337        print "committer %s" % (committer)
 338        print "data %d" % (len(msg))
 339        print msg
 340
 341        for i, p in enumerate(parents):
 342            try:
 343                m = rev_to_mark(p)
 344            except KeyError:
 345                # ghost?
 346                continue
 347            if i == 0:
 348                print "from :%s" % m
 349            else:
 350                print "merge :%s" % m
 351
 352        for f in removed:
 353            print "D %s" % (f,)
 354        for f in modified_final:
 355            print "M %s :%u %s" % f
 356        print
 357
 358        if len(seq) > 1:
 359            # let's skip branch revisions from the progress report
 360            continue
 361
 362        progress = (revno - tip_revno)
 363        if (progress % 100 == 0):
 364            if total:
 365                print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total)
 366            else:
 367                print "progress revision %d '%s' (%d)" % (revno, name, progress)
 368
 369    branch.unlock()
 370
 371    revid = branch.last_revision()
 372
 373    # make sure the ref is updated
 374    print "reset %s" % ref
 375    print "from :%u" % rev_to_mark(revid)
 376    print
 377
 378    marks.set_tip(name, revid)
 379
 380def export_tag(repo, name):
 381    ref = '%s/tags/%s' % (prefix, name)
 382    print "reset %s" % ref
 383    print "from :%u" % rev_to_mark(tags[name])
 384    print
 385
 386def do_import(parser):
 387    repo = parser.repo
 388    path = os.path.join(dirname, 'marks-git')
 389
 390    print "feature done"
 391    if os.path.exists(path):
 392        print "feature import-marks=%s" % path
 393    print "feature export-marks=%s" % path
 394    print "feature force"
 395    sys.stdout.flush()
 396
 397    while parser.check('import'):
 398        ref = parser[1]
 399        if ref.startswith('refs/heads/'):
 400            name = ref[len('refs/heads/'):]
 401            export_branch(repo, name)
 402        if ref.startswith('refs/tags/'):
 403            name = ref[len('refs/tags/'):]
 404            export_tag(repo, name)
 405        parser.next()
 406
 407    print 'done'
 408
 409    sys.stdout.flush()
 410
 411def parse_blob(parser):
 412    parser.next()
 413    mark = parser.get_mark()
 414    parser.next()
 415    data = parser.get_data()
 416    blob_marks[mark] = data
 417    parser.next()
 418
 419class CustomTree():
 420
 421    def __init__(self, branch, revid, parents, files):
 422        self.updates = {}
 423        self.branch = branch
 424
 425        def copy_tree(revid):
 426            files = files_cache[revid] = {}
 427            branch.lock_read()
 428            tree = branch.repository.revision_tree(revid)
 429            try:
 430                for path, entry in tree.iter_entries_by_dir():
 431                    files[path] = [entry.file_id, None]
 432            finally:
 433                branch.unlock()
 434            return files
 435
 436        if len(parents) == 0:
 437            self.base_id = bzrlib.revision.NULL_REVISION
 438            self.base_files = {}
 439        else:
 440            self.base_id = parents[0]
 441            self.base_files = files_cache.get(self.base_id, None)
 442            if not self.base_files:
 443                self.base_files = copy_tree(self.base_id)
 444
 445        self.files = files_cache[revid] = self.base_files.copy()
 446        self.rev_files = {}
 447
 448        for path, data in self.files.iteritems():
 449            fid, mark = data
 450            self.rev_files[fid] = [path, mark]
 451
 452        for path, f in files.iteritems():
 453            fid, mark = self.files.get(path, [None, None])
 454            if not fid:
 455                fid = bzrlib.generate_ids.gen_file_id(path)
 456            f['path'] = path
 457            self.rev_files[fid] = [path, mark]
 458            self.updates[fid] = f
 459
 460    def last_revision(self):
 461        return self.base_id
 462
 463    def iter_changes(self):
 464        changes = []
 465
 466        def get_parent(dirname, basename):
 467            parent_fid, mark = self.base_files.get(dirname, [None, None])
 468            if parent_fid:
 469                return parent_fid
 470            parent_fid, mark = self.files.get(dirname, [None, None])
 471            if parent_fid:
 472                return parent_fid
 473            if basename == '':
 474                return None
 475            fid = bzrlib.generate_ids.gen_file_id(path)
 476            add_entry(fid, dirname, 'directory')
 477            return fid
 478
 479        def add_entry(fid, path, kind, mode=None):
 480            dirname, basename = os.path.split(path)
 481            parent_fid = get_parent(dirname, basename)
 482
 483            executable = False
 484            if mode == '100755':
 485                executable = True
 486            elif mode == '120000':
 487                kind = 'symlink'
 488
 489            change = (fid,
 490                    (None, path),
 491                    True,
 492                    (False, True),
 493                    (None, parent_fid),
 494                    (None, basename),
 495                    (None, kind),
 496                    (None, executable))
 497            self.files[path] = [change[0], None]
 498            changes.append(change)
 499
 500        def update_entry(fid, path, kind, mode=None):
 501            dirname, basename = os.path.split(path)
 502            parent_fid = get_parent(dirname, basename)
 503
 504            executable = False
 505            if mode == '100755':
 506                executable = True
 507            elif mode == '120000':
 508                kind = 'symlink'
 509
 510            change = (fid,
 511                    (path, path),
 512                    True,
 513                    (True, True),
 514                    (None, parent_fid),
 515                    (None, basename),
 516                    (None, kind),
 517                    (None, executable))
 518            self.files[path] = [change[0], None]
 519            changes.append(change)
 520
 521        def remove_entry(fid, path, kind):
 522            dirname, basename = os.path.split(path)
 523            parent_fid = get_parent(dirname, basename)
 524            change = (fid,
 525                    (path, None),
 526                    True,
 527                    (True, False),
 528                    (parent_fid, None),
 529                    (None, None),
 530                    (None, None),
 531                    (None, None))
 532            del self.files[path]
 533            changes.append(change)
 534
 535        for fid, f in self.updates.iteritems():
 536            path = f['path']
 537
 538            if 'deleted' in f:
 539                remove_entry(fid, path, 'file')
 540                continue
 541
 542            if path in self.base_files:
 543                update_entry(fid, path, 'file', f['mode'])
 544            else:
 545                add_entry(fid, path, 'file', f['mode'])
 546
 547            self.files[path][1] = f['mark']
 548            self.rev_files[fid][1] = f['mark']
 549
 550        return changes
 551
 552    def get_content(self, file_id):
 553        path, mark = self.rev_files[file_id]
 554        if mark:
 555            return blob_marks[mark]
 556
 557        # last resort
 558        tree = self.branch.repository.revision_tree(self.base_id)
 559        return tree.get_file_text(file_id)
 560
 561    def get_file_with_stat(self, file_id, path=None):
 562        content = self.get_content(file_id)
 563        return (StringIO.StringIO(content), None)
 564
 565    def get_symlink_target(self, file_id):
 566        return self.get_content(file_id)
 567
 568    def id2path(self, file_id):
 569        path, mark = self.rev_files[file_id]
 570        return path
 571
 572def c_style_unescape(string):
 573    if string[0] == string[-1] == '"':
 574        return string.decode('string-escape')[1:-1]
 575    return string
 576
 577def parse_commit(parser):
 578    parents = []
 579
 580    ref = parser[1]
 581    parser.next()
 582
 583    if ref.startswith('refs/heads/'):
 584        name = ref[len('refs/heads/'):]
 585        branch = get_remote_branch(name)
 586    else:
 587        die('unknown ref')
 588
 589    commit_mark = parser.get_mark()
 590    parser.next()
 591    author = parser.get_author()
 592    parser.next()
 593    committer = parser.get_author()
 594    parser.next()
 595    data = parser.get_data()
 596    parser.next()
 597    if parser.check('from'):
 598        parents.append(parser.get_mark())
 599        parser.next()
 600    while parser.check('merge'):
 601        parents.append(parser.get_mark())
 602        parser.next()
 603
 604    # fast-export adds an extra newline
 605    if data[-1] == '\n':
 606        data = data[:-1]
 607
 608    files = {}
 609
 610    for line in parser:
 611        if parser.check('M'):
 612            t, m, mark_ref, path = line.split(' ', 3)
 613            mark = int(mark_ref[1:])
 614            f = { 'mode' : m, 'mark' : mark }
 615        elif parser.check('D'):
 616            t, path = line.split(' ', 1)
 617            f = { 'deleted' : True }
 618        else:
 619            die('Unknown file command: %s' % line)
 620        path = c_style_unescape(path).decode('utf-8')
 621        files[path] = f
 622
 623    committer, date, tz = committer
 624    author, _, _ = author
 625    parents = [mark_to_rev(p) for p in parents]
 626    revid = bzrlib.generate_ids.gen_revision_id(committer, date)
 627    props = {}
 628    props['branch-nick'] = branch.nick
 629    props['authors'] = author
 630
 631    mtree = CustomTree(branch, revid, parents, files)
 632    changes = mtree.iter_changes()
 633
 634    branch.lock_write()
 635    try:
 636        builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid)
 637        try:
 638            list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
 639            builder.finish_inventory()
 640            builder.commit(data.decode('utf-8', 'replace'))
 641        except Exception, e:
 642            builder.abort()
 643            raise
 644    finally:
 645        branch.unlock()
 646
 647    parsed_refs[ref] = revid
 648    marks.new_mark(revid, commit_mark)
 649
 650def parse_reset(parser):
 651    ref = parser[1]
 652    parser.next()
 653
 654    # ugh
 655    if parser.check('commit'):
 656        parse_commit(parser)
 657        return
 658    if not parser.check('from'):
 659        return
 660    from_mark = parser.get_mark()
 661    parser.next()
 662
 663    parsed_refs[ref] = mark_to_rev(from_mark)
 664
 665def do_export(parser):
 666    parser.next()
 667
 668    for line in parser.each_block('done'):
 669        if parser.check('blob'):
 670            parse_blob(parser)
 671        elif parser.check('commit'):
 672            parse_commit(parser)
 673        elif parser.check('reset'):
 674            parse_reset(parser)
 675        elif parser.check('tag'):
 676            pass
 677        elif parser.check('feature'):
 678            pass
 679        else:
 680            die('unhandled export command: %s' % line)
 681
 682    for ref, revid in parsed_refs.iteritems():
 683        if ref.startswith('refs/heads/'):
 684            name = ref[len('refs/heads/'):]
 685            branch = get_remote_branch(name)
 686            branch.generate_revision_history(revid, marks.get_tip(name))
 687
 688            if name in peers:
 689                peer = bzrlib.branch.Branch.open(peers[name],
 690                                                 possible_transports=transports)
 691                try:
 692                    peer.bzrdir.push_branch(branch, revision_id=revid,
 693                                            overwrite=force)
 694                except bzrlib.errors.DivergedBranches:
 695                    print "error %s non-fast forward" % ref
 696                    continue
 697
 698            try:
 699                wt = branch.bzrdir.open_workingtree()
 700                wt.update()
 701            except bzrlib.errors.NoWorkingTree:
 702                pass
 703        elif ref.startswith('refs/tags/'):
 704            # TODO: implement tag push
 705            print "error %s pushing tags not supported" % ref
 706            continue
 707        else:
 708            # transport-helper/fast-export bugs
 709            continue
 710
 711        print "ok %s" % ref
 712
 713    print
 714
 715def do_capabilities(parser):
 716    print "import"
 717    print "export"
 718    print "refspec refs/heads/*:%s/heads/*" % prefix
 719    print "refspec refs/tags/*:%s/tags/*" % prefix
 720
 721    path = os.path.join(dirname, 'marks-git')
 722
 723    if os.path.exists(path):
 724        print "*import-marks %s" % path
 725    print "*export-marks %s" % path
 726
 727    print "option"
 728    print
 729
 730class InvalidOptionValue(Exception):
 731    pass
 732
 733def get_bool_option(val):
 734    if val == 'true':
 735        return True
 736    elif val == 'false':
 737        return False
 738    else:
 739        raise InvalidOptionValue()
 740
 741def do_option(parser):
 742    global force
 743    opt, val = parser[1:3]
 744    try:
 745        if opt == 'force':
 746            force = get_bool_option(val)
 747            print 'ok'
 748        else:
 749            print 'unsupported'
 750    except InvalidOptionValue:
 751        print "error '%s' is not a valid value for option '%s'" % (val, opt)
 752
 753def ref_is_valid(name):
 754    return not True in [c in name for c in '~^: \\']
 755
 756def do_list(parser):
 757    master_branch = None
 758
 759    for name in branches:
 760        if not master_branch:
 761            master_branch = name
 762        print "? refs/heads/%s" % name
 763
 764    branch = get_remote_branch(master_branch)
 765    branch.lock_read()
 766    for tag, revid in branch.tags.get_tag_dict().items():
 767        try:
 768            branch.revision_id_to_dotted_revno(revid)
 769        except bzrlib.errors.NoSuchRevision:
 770            continue
 771        if not ref_is_valid(tag):
 772            continue
 773        print "? refs/tags/%s" % tag
 774        tags[tag] = revid
 775    branch.unlock()
 776
 777    print "@refs/heads/%s HEAD" % master_branch
 778    print
 779
 780def clone(path, remote_branch):
 781    try:
 782        bdir = bzrlib.bzrdir.BzrDir.create(path, possible_transports=transports)
 783    except bzrlib.errors.AlreadyControlDirError:
 784        bdir = bzrlib.bzrdir.BzrDir.open(path, possible_transports=transports)
 785    repo = bdir.find_repository()
 786    repo.fetch(remote_branch.repository)
 787    return remote_branch.sprout(bdir, repository=repo)
 788
 789def get_remote_branch(name):
 790    remote_branch = bzrlib.branch.Branch.open(branches[name],
 791                                              possible_transports=transports)
 792    if isinstance(remote_branch.bzrdir.root_transport, bzrlib.transport.local.LocalTransport):
 793        return remote_branch
 794
 795    branch_path = os.path.join(dirname, 'clone', name)
 796
 797    try:
 798        branch = bzrlib.branch.Branch.open(branch_path,
 799                                           possible_transports=transports)
 800    except bzrlib.errors.NotBranchError:
 801        # clone
 802        branch = clone(branch_path, remote_branch)
 803    else:
 804        # pull
 805        try:
 806            branch.pull(remote_branch, overwrite=True)
 807        except bzrlib.errors.DivergedBranches:
 808            # use remote branch for now
 809            return remote_branch
 810
 811    return branch
 812
 813def find_branches(repo):
 814    transport = repo.bzrdir.root_transport
 815
 816    for fn in transport.iter_files_recursive():
 817        if not fn.endswith('.bzr/branch-format'):
 818            continue
 819
 820        name = subdir = fn[:-len('/.bzr/branch-format')]
 821        name = name if name != '' else 'master'
 822        name = name.replace('/', '+')
 823
 824        try:
 825            cur = transport.clone(subdir)
 826            branch = bzrlib.branch.Branch.open_from_transport(cur)
 827        except bzrlib.errors.NotBranchError:
 828            continue
 829        else:
 830            yield name, branch.base
 831
 832def get_repo(url, alias):
 833    normal_url = bzrlib.urlutils.normalize_url(url)
 834    origin = bzrlib.bzrdir.BzrDir.open(url, possible_transports=transports)
 835    is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
 836
 837    shared_path = os.path.join(gitdir, 'bzr')
 838    try:
 839        shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path,
 840                                               possible_transports=transports)
 841    except bzrlib.errors.NotBranchError:
 842        shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path,
 843                                                 possible_transports=transports)
 844    try:
 845        shared_repo = shared_dir.open_repository()
 846    except bzrlib.errors.NoRepositoryPresent:
 847        shared_repo = shared_dir.create_repository(shared=True)
 848
 849    if not is_local:
 850        clone_path = os.path.join(dirname, 'clone')
 851        if not os.path.exists(clone_path):
 852            os.mkdir(clone_path)
 853        else:
 854            # check and remove old organization
 855            try:
 856                bdir = bzrlib.bzrdir.BzrDir.open(clone_path,
 857                                                 possible_transports=transports)
 858                bdir.destroy_repository()
 859            except bzrlib.errors.NotBranchError:
 860                pass
 861            except bzrlib.errors.NoRepositoryPresent:
 862                pass
 863
 864    wanted = get_config('remote.%s.bzr-branches' % alias).rstrip().split(', ')
 865    # stupid python
 866    wanted = [e for e in wanted if e]
 867    if not wanted:
 868        wanted = get_config('remote-bzr.branches').rstrip().split(', ')
 869        # stupid python
 870        wanted = [e for e in wanted if e]
 871
 872    if not wanted:
 873        try:
 874            repo = origin.open_repository()
 875            if not repo.bzrdir.root_transport.listable():
 876                # this repository is not usable for us
 877                raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
 878        except bzrlib.errors.NoRepositoryPresent:
 879            wanted = ['master']
 880
 881    if wanted:
 882        def list_wanted(url, wanted):
 883            for name in wanted:
 884                subdir = name if name != 'master' else ''
 885                yield name, bzrlib.urlutils.join(url, subdir)
 886
 887        branch_list = list_wanted(url, wanted)
 888    else:
 889        branch_list = find_branches(repo)
 890
 891    for name, url in branch_list:
 892        if not is_local:
 893            peers[name] = url
 894        branches[name] = url
 895
 896    return origin
 897
 898def fix_path(alias, orig_url):
 899    url = urlparse.urlparse(orig_url, 'file')
 900    if url.scheme != 'file' or os.path.isabs(url.path):
 901        return
 902    abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
 903    cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
 904    subprocess.call(cmd)
 905
 906def main(args):
 907    global marks, prefix, gitdir, dirname
 908    global tags, filenodes
 909    global blob_marks
 910    global parsed_refs
 911    global files_cache
 912    global is_tmp
 913    global branches, peers
 914    global transports
 915    global force
 916
 917    marks = None
 918    is_tmp = False
 919    gitdir = os.environ.get('GIT_DIR', None)
 920
 921    if len(args) < 3:
 922        die('Not enough arguments.')
 923
 924    if not gitdir:
 925        die('GIT_DIR not set')
 926
 927    alias = args[1]
 928    url = args[2]
 929
 930    tags = {}
 931    filenodes = {}
 932    blob_marks = {}
 933    parsed_refs = {}
 934    files_cache = {}
 935    branches = {}
 936    peers = {}
 937    transports = []
 938    force = False
 939
 940    if alias[5:] == url:
 941        is_tmp = True
 942        alias = hashlib.sha1(alias).hexdigest()
 943
 944    prefix = 'refs/bzr/%s' % alias
 945    dirname = os.path.join(gitdir, 'bzr', alias)
 946
 947    if not is_tmp:
 948        fix_path(alias, url)
 949
 950    if not os.path.exists(dirname):
 951        os.makedirs(dirname)
 952
 953    if hasattr(bzrlib.ui.ui_factory, 'be_quiet'):
 954        bzrlib.ui.ui_factory.be_quiet(True)
 955
 956    repo = get_repo(url, alias)
 957
 958    marks_path = os.path.join(dirname, 'marks-int')
 959    marks = Marks(marks_path)
 960
 961    parser = Parser(repo)
 962    for line in parser:
 963        if parser.check('capabilities'):
 964            do_capabilities(parser)
 965        elif parser.check('list'):
 966            do_list(parser)
 967        elif parser.check('import'):
 968            do_import(parser)
 969        elif parser.check('export'):
 970            do_export(parser)
 971        elif parser.check('option'):
 972            do_option(parser)
 973        else:
 974            die('unhandled command: %s' % line)
 975        sys.stdout.flush()
 976
 977def bye():
 978    if not marks:
 979        return
 980    if not is_tmp:
 981        marks.store()
 982    else:
 983        shutil.rmtree(dirname)
 984
 985atexit.register(bye)
 986sys.exit(main(sys.argv))