Merge branch 'mh/multimail'
[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 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     parents = [mark_to_rev(p) for p in parents]
622     revid = bzrlib.generate_ids.gen_revision_id(committer, date)
623     props = {}
624     props['branch-nick'] = branch.nick
625
626     mtree = CustomTree(branch, revid, parents, files)
627     changes = mtree.iter_changes()
628
629     branch.lock_write()
630     try:
631         builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid)
632         try:
633             list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
634             builder.finish_inventory()
635             builder.commit(data.decode('utf-8', 'replace'))
636         except Exception, e:
637             builder.abort()
638             raise
639     finally:
640         branch.unlock()
641
642     parsed_refs[ref] = revid
643     marks.new_mark(revid, commit_mark)
644
645 def parse_reset(parser):
646     ref = parser[1]
647     parser.next()
648
649     # ugh
650     if parser.check('commit'):
651         parse_commit(parser)
652         return
653     if not parser.check('from'):
654         return
655     from_mark = parser.get_mark()
656     parser.next()
657
658     parsed_refs[ref] = mark_to_rev(from_mark)
659
660 def do_export(parser):
661     parser.next()
662
663     for line in parser.each_block('done'):
664         if parser.check('blob'):
665             parse_blob(parser)
666         elif parser.check('commit'):
667             parse_commit(parser)
668         elif parser.check('reset'):
669             parse_reset(parser)
670         elif parser.check('tag'):
671             pass
672         elif parser.check('feature'):
673             pass
674         else:
675             die('unhandled export command: %s' % line)
676
677     for ref, revid in parsed_refs.iteritems():
678         if ref.startswith('refs/heads/'):
679             name = ref[len('refs/heads/'):]
680             branch = get_remote_branch(name)
681             branch.generate_revision_history(revid, marks.get_tip(name))
682
683             if name in peers:
684                 peer = bzrlib.branch.Branch.open(peers[name],
685                                                  possible_transports=transports)
686                 try:
687                     peer.bzrdir.push_branch(branch, revision_id=revid,
688                                             overwrite=force)
689                 except bzrlib.errors.DivergedBranches:
690                     print "error %s non-fast forward" % ref
691                     continue
692
693             try:
694                 wt = branch.bzrdir.open_workingtree()
695                 wt.update()
696             except bzrlib.errors.NoWorkingTree:
697                 pass
698         elif ref.startswith('refs/tags/'):
699             # TODO: implement tag push
700             print "error %s pushing tags not supported" % ref
701             continue
702         else:
703             # transport-helper/fast-export bugs
704             continue
705
706         print "ok %s" % ref
707
708     print
709
710 def do_capabilities(parser):
711     print "import"
712     print "export"
713     print "refspec refs/heads/*:%s/heads/*" % prefix
714     print "refspec refs/tags/*:%s/tags/*" % prefix
715
716     path = os.path.join(dirname, 'marks-git')
717
718     if os.path.exists(path):
719         print "*import-marks %s" % path
720     print "*export-marks %s" % path
721
722     print "option"
723     print
724
725 class InvalidOptionValue(Exception):
726     pass
727
728 def get_bool_option(val):
729     if val == 'true':
730         return True
731     elif val == 'false':
732         return False
733     else:
734         raise InvalidOptionValue()
735
736 def do_option(parser):
737     global force
738     opt, val = parser[1:3]
739     try:
740         if opt == 'force':
741             force = get_bool_option(val)
742             print 'ok'
743         else:
744             print 'unsupported'
745     except InvalidOptionValue:
746         print "error '%s' is not a valid value for option '%s'" % (val, opt)
747
748 def ref_is_valid(name):
749     return not True in [c in name for c in '~^: \\']
750
751 def do_list(parser):
752     master_branch = None
753
754     for name in branches:
755         if not master_branch:
756             master_branch = name
757         print "? refs/heads/%s" % name
758
759     branch = get_remote_branch(master_branch)
760     branch.lock_read()
761     for tag, revid in branch.tags.get_tag_dict().items():
762         try:
763             branch.revision_id_to_dotted_revno(revid)
764         except bzrlib.errors.NoSuchRevision:
765             continue
766         if not ref_is_valid(tag):
767             continue
768         print "? refs/tags/%s" % tag
769         tags[tag] = revid
770     branch.unlock()
771
772     print "@refs/heads/%s HEAD" % master_branch
773     print
774
775 def clone(path, remote_branch):
776     try:
777         bdir = bzrlib.bzrdir.BzrDir.create(path, possible_transports=transports)
778     except bzrlib.errors.AlreadyControlDirError:
779         bdir = bzrlib.bzrdir.BzrDir.open(path, possible_transports=transports)
780     repo = bdir.find_repository()
781     repo.fetch(remote_branch.repository)
782     return remote_branch.sprout(bdir, repository=repo)
783
784 def get_remote_branch(name):
785     remote_branch = bzrlib.branch.Branch.open(branches[name],
786                                               possible_transports=transports)
787     if isinstance(remote_branch.user_transport, bzrlib.transport.local.LocalTransport):
788         return remote_branch
789
790     branch_path = os.path.join(dirname, 'clone', name)
791
792     try:
793         branch = bzrlib.branch.Branch.open(branch_path,
794                                            possible_transports=transports)
795     except bzrlib.errors.NotBranchError:
796         # clone
797         branch = clone(branch_path, remote_branch)
798     else:
799         # pull
800         try:
801             branch.pull(remote_branch, overwrite=True)
802         except bzrlib.errors.DivergedBranches:
803             # use remote branch for now
804             return remote_branch
805
806     return branch
807
808 def find_branches(repo):
809     transport = repo.bzrdir.root_transport
810
811     for fn in transport.iter_files_recursive():
812         if not fn.endswith('.bzr/branch-format'):
813             continue
814
815         name = subdir = fn[:-len('/.bzr/branch-format')]
816         name = name if name != '' else 'master'
817         name = name.replace('/', '+')
818
819         try:
820             cur = transport.clone(subdir)
821             branch = bzrlib.branch.Branch.open_from_transport(cur)
822         except bzrlib.errors.NotBranchError:
823             continue
824         else:
825             yield name, branch.base
826
827 def get_repo(url, alias):
828     normal_url = bzrlib.urlutils.normalize_url(url)
829     origin = bzrlib.bzrdir.BzrDir.open(url, possible_transports=transports)
830     is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
831
832     shared_path = os.path.join(gitdir, 'bzr')
833     try:
834         shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path,
835                                                possible_transports=transports)
836     except bzrlib.errors.NotBranchError:
837         shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path,
838                                                  possible_transports=transports)
839     try:
840         shared_repo = shared_dir.open_repository()
841     except bzrlib.errors.NoRepositoryPresent:
842         shared_repo = shared_dir.create_repository(shared=True)
843
844     if not is_local:
845         clone_path = os.path.join(dirname, 'clone')
846         if not os.path.exists(clone_path):
847             os.mkdir(clone_path)
848         else:
849             # check and remove old organization
850             try:
851                 bdir = bzrlib.bzrdir.BzrDir.open(clone_path,
852                                                  possible_transports=transports)
853                 bdir.destroy_repository()
854             except bzrlib.errors.NotBranchError:
855                 pass
856             except bzrlib.errors.NoRepositoryPresent:
857                 pass
858
859     wanted = get_config('remote.%s.bzr-branches' % alias).rstrip().split(', ')
860     # stupid python
861     wanted = [e for e in wanted if e]
862     if not wanted:
863         wanted = get_config('remote-bzr.branches').rstrip().split(', ')
864         # stupid python
865         wanted = [e for e in wanted if e]
866
867     if not wanted:
868         try:
869             repo = origin.open_repository()
870             if not repo.user_transport.listable():
871                 # this repository is not usable for us
872                 raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
873         except bzrlib.errors.NoRepositoryPresent:
874             wanted = ['master']
875
876     if wanted:
877         def list_wanted(url, wanted):
878             for name in wanted:
879                 subdir = name if name != 'master' else ''
880                 yield name, bzrlib.urlutils.join(url, subdir)
881
882         branch_list = list_wanted(url, wanted)
883     else:
884         branch_list = find_branches(repo)
885
886     for name, url in branch_list:
887         if not is_local:
888             peers[name] = url
889         branches[name] = url
890
891     return origin
892
893 def fix_path(alias, orig_url):
894     url = urlparse.urlparse(orig_url, 'file')
895     if url.scheme != 'file' or os.path.isabs(url.path):
896         return
897     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
898     cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
899     subprocess.call(cmd)
900
901 def main(args):
902     global marks, prefix, gitdir, dirname
903     global tags, filenodes
904     global blob_marks
905     global parsed_refs
906     global files_cache
907     global is_tmp
908     global branches, peers
909     global transports
910     global force
911
912     marks = None
913     is_tmp = False
914     gitdir = os.environ.get('GIT_DIR', None)
915
916     if len(args) < 3:
917         die('Not enough arguments.')
918
919     if not gitdir:
920         die('GIT_DIR not set')
921
922     alias = args[1]
923     url = args[2]
924
925     tags = {}
926     filenodes = {}
927     blob_marks = {}
928     parsed_refs = {}
929     files_cache = {}
930     branches = {}
931     peers = {}
932     transports = []
933     force = False
934
935     if alias[5:] == url:
936         is_tmp = True
937         alias = hashlib.sha1(alias).hexdigest()
938
939     prefix = 'refs/bzr/%s' % alias
940     dirname = os.path.join(gitdir, 'bzr', alias)
941
942     if not is_tmp:
943         fix_path(alias, url)
944
945     if not os.path.exists(dirname):
946         os.makedirs(dirname)
947
948     if hasattr(bzrlib.ui.ui_factory, 'be_quiet'):
949         bzrlib.ui.ui_factory.be_quiet(True)
950
951     repo = get_repo(url, alias)
952
953     marks_path = os.path.join(dirname, 'marks-int')
954     marks = Marks(marks_path)
955
956     parser = Parser(repo)
957     for line in parser:
958         if parser.check('capabilities'):
959             do_capabilities(parser)
960         elif parser.check('list'):
961             do_list(parser)
962         elif parser.check('import'):
963             do_import(parser)
964         elif parser.check('export'):
965             do_export(parser)
966         elif parser.check('option'):
967             do_option(parser)
968         else:
969             die('unhandled command: %s' % line)
970         sys.stdout.flush()
971
972 def bye():
973     if not marks:
974         return
975     if not is_tmp:
976         marks.store()
977     else:
978         shutil.rmtree(dirname)
979
980 atexit.register(bye)
981 sys.exit(main(sys.argv))