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