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