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