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