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