Merge branch 'fc/remote-bzr'
[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
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                 try:
704                     peer.bzrdir.push_branch(branch, revision_id=revid)
705                 except bzrlib.errors.DivergedBranches:
706                     print "error %s non-fast forward" % ref
707                     continue
708
709             try:
710                 wt = branch.bzrdir.open_workingtree()
711                 wt.update()
712             except bzrlib.errors.NoWorkingTree:
713                 pass
714         elif ref.startswith('refs/tags/'):
715             # TODO: implement tag push
716             print "error %s pushing tags not supported" % ref
717             continue
718         else:
719             # transport-helper/fast-export bugs
720             continue
721
722         print "ok %s" % ref
723
724     print
725
726 def do_capabilities(parser):
727     global dirname
728
729     print "import"
730     print "export"
731     print "refspec refs/heads/*:%s/heads/*" % prefix
732     print "refspec refs/tags/*:%s/tags/*" % prefix
733
734     path = os.path.join(dirname, 'marks-git')
735
736     if os.path.exists(path):
737         print "*import-marks %s" % path
738     print "*export-marks %s" % path
739
740     print
741
742 def ref_is_valid(name):
743     return not True in [c in name for c in '~^: \\']
744
745 def do_list(parser):
746     global tags
747
748     master_branch = None
749
750     for name in branches:
751         if not master_branch:
752             master_branch = name
753         print "? refs/heads/%s" % name
754
755     branch = get_remote_branch(master_branch)
756     branch.lock_read()
757     for tag, revid in branch.tags.get_tag_dict().items():
758         try:
759             branch.revision_id_to_dotted_revno(revid)
760         except bzrlib.errors.NoSuchRevision:
761             continue
762         if not ref_is_valid(tag):
763             continue
764         print "? refs/tags/%s" % tag
765         tags[tag] = revid
766     branch.unlock()
767
768     print "@refs/heads/%s HEAD" % master_branch
769     print
770
771 def clone(path, remote_branch):
772     try:
773         bdir = bzrlib.bzrdir.BzrDir.create(path)
774     except bzrlib.errors.AlreadyControlDirError:
775         bdir = bzrlib.bzrdir.BzrDir.open(path)
776     repo = bdir.find_repository()
777     repo.fetch(remote_branch.repository)
778     return remote_branch.sprout(bdir, repository=repo)
779
780 def get_remote_branch(name):
781     global dirname, branches
782
783     remote_branch = bzrlib.branch.Branch.open(branches[name])
784     if isinstance(remote_branch.user_transport, bzrlib.transport.local.LocalTransport):
785         return remote_branch
786
787     branch_path = os.path.join(dirname, 'clone', name)
788
789     try:
790         branch = bzrlib.branch.Branch.open(branch_path)
791     except bzrlib.errors.NotBranchError:
792         # clone
793         branch = clone(branch_path, remote_branch)
794     else:
795         # pull
796         try:
797             branch.pull(remote_branch, overwrite=True)
798         except bzrlib.errors.DivergedBranches:
799             # use remote branch for now
800             return remote_branch
801
802     return branch
803
804 def find_branches(repo):
805     transport = repo.bzrdir.root_transport
806
807     for fn in transport.iter_files_recursive():
808         if not fn.endswith('.bzr/branch-format'):
809             continue
810
811         name = subdir = fn[:-len('/.bzr/branch-format')]
812         name = name if name != '' else 'master'
813         name = name.replace('/', '+')
814
815         try:
816             cur = transport.clone(subdir)
817             branch = bzrlib.branch.Branch.open_from_transport(cur)
818         except bzrlib.errors.NotBranchError:
819             continue
820         else:
821             yield name, branch.base
822
823 def get_repo(url, alias):
824     global dirname, peer, branches
825
826     normal_url = bzrlib.urlutils.normalize_url(url)
827     origin = bzrlib.bzrdir.BzrDir.open(url)
828     is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
829
830     shared_path = os.path.join(gitdir, 'bzr')
831     try:
832         shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path)
833     except bzrlib.errors.NotBranchError:
834         shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path)
835     try:
836         shared_repo = shared_dir.open_repository()
837     except bzrlib.errors.NoRepositoryPresent:
838         shared_repo = shared_dir.create_repository(shared=True)
839
840     if not is_local:
841         clone_path = os.path.join(dirname, 'clone')
842         if not os.path.exists(clone_path):
843             os.mkdir(clone_path)
844         else:
845             # check and remove old organization
846             try:
847                 bdir = bzrlib.bzrdir.BzrDir.open(clone_path)
848                 bdir.destroy_repository()
849             except bzrlib.errors.NotBranchError:
850                 pass
851             except bzrlib.errors.NoRepositoryPresent:
852                 pass
853
854     wanted = get_config('remote-bzr.branches').rstrip().split(', ')
855     # stupid python
856     wanted = [e for e in wanted if e]
857
858     if not wanted:
859         try:
860             repo = origin.open_repository()
861             if not repo.user_transport.listable():
862                 # this repository is not usable for us
863                 raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
864         except bzrlib.errors.NoRepositoryPresent:
865             wanted = ['master']
866
867     if wanted:
868         def list_wanted(url, wanted):
869             for name in wanted:
870                 subdir = name if name != 'master' else ''
871                 yield name, bzrlib.urlutils.join(url, subdir)
872
873         branch_list = list_wanted(url, wanted)
874     else:
875         branch_list = find_branches(repo)
876
877     for name, url in branch_list:
878         if not is_local:
879             peers[name] = url
880         branches[name] = url
881
882     return origin
883
884 def fix_path(alias, orig_url):
885     url = urlparse.urlparse(orig_url, 'file')
886     if url.scheme != 'file' or os.path.isabs(url.path):
887         return
888     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
889     cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
890     subprocess.call(cmd)
891
892 def main(args):
893     global marks, prefix, gitdir, dirname
894     global tags, filenodes
895     global blob_marks
896     global parsed_refs
897     global files_cache
898     global is_tmp
899     global branches, peers
900
901     alias = args[1]
902     url = args[2]
903
904     tags = {}
905     filenodes = {}
906     blob_marks = {}
907     parsed_refs = {}
908     files_cache = {}
909     marks = None
910     branches = {}
911     peers = {}
912
913     if alias[5:] == url:
914         is_tmp = True
915         alias = hashlib.sha1(alias).hexdigest()
916     else:
917         is_tmp = False
918
919     prefix = 'refs/bzr/%s' % alias
920     gitdir = os.environ['GIT_DIR']
921     dirname = os.path.join(gitdir, 'bzr', alias)
922
923     if not is_tmp:
924         fix_path(alias, url)
925
926     if not os.path.exists(dirname):
927         os.makedirs(dirname)
928
929     if hasattr(bzrlib.ui.ui_factory, 'be_quiet'):
930         bzrlib.ui.ui_factory.be_quiet(True)
931
932     repo = get_repo(url, alias)
933
934     marks_path = os.path.join(dirname, 'marks-int')
935     marks = Marks(marks_path)
936
937     parser = Parser(repo)
938     for line in parser:
939         if parser.check('capabilities'):
940             do_capabilities(parser)
941         elif parser.check('list'):
942             do_list(parser)
943         elif parser.check('import'):
944             do_import(parser)
945         elif parser.check('export'):
946             do_export(parser)
947         else:
948             die('unhandled command: %s' % line)
949         sys.stdout.flush()
950
951 def bye():
952     if not marks:
953         return
954     if not is_tmp:
955         marks.store()
956     else:
957         shutil.rmtree(dirname)
958
959 atexit.register(bye)
960 sys.exit(main(sys.argv))