Merge branch 'nd/clone-local-with-colon'
[git] / contrib / remote-helpers / git-remote-bzr
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2012 Felipe Contreras
4 #
5
6 #
7 # Just copy to your ~/bin, or anywhere in your $PATH.
8 # Then you can clone with:
9 # % git clone bzr::/path/to/bzr/repo/or/url
10 #
11 # For example:
12 # % git clone bzr::$HOME/myrepo
13 # or
14 # % git clone bzr::lp:myrepo
15 #
16 # If you want to specify which branches you want track (per repo):
17 # git config remote-bzr.branches 'trunk, devel, test'
18 #
19
20 import sys
21
22 import bzrlib
23 if hasattr(bzrlib, "initialize"):
24     bzrlib.initialize()
25
26 import bzrlib.plugin
27 bzrlib.plugin.load_plugins()
28
29 import bzrlib.generate_ids
30 import bzrlib.transport
31 import bzrlib.errors
32 import bzrlib.ui
33 import bzrlib.urlutils
34 import bzrlib.branch
35
36 import sys
37 import os
38 import json
39 import re
40 import StringIO
41 import atexit, shutil, hashlib, urlparse, subprocess
42
43 NAME_RE = re.compile('^([^<>]+)')
44 AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
45 EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
46 RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
47
48 def die(msg, *args):
49     sys.stderr.write('ERROR: %s\n' % (msg % args))
50     sys.exit(1)
51
52 def warn(msg, *args):
53     sys.stderr.write('WARNING: %s\n' % (msg % args))
54
55 def gittz(tz):
56     return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
57
58 def get_config(config):
59     cmd = ['git', 'config', '--get', config]
60     process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
61     output, _ = process.communicate()
62     return output
63
64 class Marks:
65
66     def __init__(self, path):
67         self.path = path
68         self.tips = {}
69         self.marks = {}
70         self.rev_marks = {}
71         self.last_mark = 0
72         self.load()
73
74     def load(self):
75         if not os.path.exists(self.path):
76             return
77
78         tmp = json.load(open(self.path))
79         self.tips = tmp['tips']
80         self.marks = tmp['marks']
81         self.last_mark = tmp['last-mark']
82
83         for rev, mark in self.marks.iteritems():
84             self.rev_marks[mark] = rev
85
86     def dict(self):
87         return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
88
89     def store(self):
90         json.dump(self.dict(), open(self.path, 'w'))
91
92     def __str__(self):
93         return str(self.dict())
94
95     def from_rev(self, rev):
96         return self.marks[rev]
97
98     def to_rev(self, mark):
99         return str(self.rev_marks[mark])
100
101     def next_mark(self):
102         self.last_mark += 1
103         return self.last_mark
104
105     def get_mark(self, rev):
106         self.last_mark += 1
107         self.marks[rev] = self.last_mark
108         return self.last_mark
109
110     def is_marked(self, rev):
111         return rev in self.marks
112
113     def new_mark(self, rev, mark):
114         self.marks[rev] = mark
115         self.rev_marks[mark] = rev
116         self.last_mark = mark
117
118     def get_tip(self, branch):
119         return self.tips.get(branch, None)
120
121     def set_tip(self, branch, tip):
122         self.tips[branch] = tip
123
124 class Parser:
125
126     def __init__(self, repo):
127         self.repo = repo
128         self.line = self.get_line()
129
130     def get_line(self):
131         return sys.stdin.readline().strip()
132
133     def __getitem__(self, i):
134         return self.line.split()[i]
135
136     def check(self, word):
137         return self.line.startswith(word)
138
139     def each_block(self, separator):
140         while self.line != separator:
141             yield self.line
142             self.line = self.get_line()
143
144     def __iter__(self):
145         return self.each_block('')
146
147     def next(self):
148         self.line = self.get_line()
149         if self.line == 'done':
150             self.line = None
151
152     def get_mark(self):
153         i = self.line.index(':') + 1
154         return int(self.line[i:])
155
156     def get_data(self):
157         if not self.check('data'):
158             return None
159         i = self.line.index(' ') + 1
160         size = int(self.line[i:])
161         return sys.stdin.read(size)
162
163     def get_author(self):
164         m = RAW_AUTHOR_RE.match(self.line)
165         if not m:
166             return None
167         _, name, email, date, tz = m.groups()
168         committer = '%s <%s>' % (name, email)
169         tz = int(tz)
170         tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
171         return (committer, int(date), tz)
172
173 def rev_to_mark(rev):
174     global marks
175     return marks.from_rev(rev)
176
177 def mark_to_rev(mark):
178     global marks
179     return marks.to_rev(mark)
180
181 def fixup_user(user):
182     name = mail = None
183     user = user.replace('"', '')
184     m = AUTHOR_RE.match(user)
185     if m:
186         name = m.group(1)
187         mail = m.group(2).strip()
188     else:
189         m = EMAIL_RE.match(user)
190         if m:
191             name = m.group(1)
192             mail = m.group(2)
193         else:
194             m = NAME_RE.match(user)
195             if m:
196                 name = m.group(1).strip()
197
198     if not name:
199         name = 'unknown'
200     if not mail:
201         mail = 'Unknown'
202
203     return '%s <%s>' % (name, mail)
204
205 def get_filechanges(cur, prev):
206     modified = {}
207     removed = {}
208
209     changes = cur.changes_from(prev)
210
211     def u(s):
212         return s.encode('utf-8')
213
214     for path, fid, kind in changes.added:
215         modified[u(path)] = fid
216     for path, fid, kind in changes.removed:
217         removed[u(path)] = None
218     for path, fid, kind, mod, _ in changes.modified:
219         modified[u(path)] = fid
220     for oldpath, newpath, fid, kind, mod, _ in changes.renamed:
221         removed[u(oldpath)] = None
222         if kind == 'directory':
223             lst = cur.list_files(from_dir=newpath, recursive=True)
224             for path, file_class, kind, fid, entry in lst:
225                 if kind != 'directory':
226                     modified[u(newpath + '/' + path)] = fid
227         else:
228             modified[u(newpath)] = fid
229
230     return modified, removed
231
232 def export_files(tree, files):
233     global marks, filenodes
234
235     final = []
236     for path, fid in files.iteritems():
237         kind = tree.kind(fid)
238
239         h = tree.get_file_sha1(fid)
240
241         if kind == 'symlink':
242             d = tree.get_symlink_target(fid)
243             mode = '120000'
244         elif kind == 'file':
245
246             if tree.is_executable(fid):
247                 mode = '100755'
248             else:
249                 mode = '100644'
250
251             # is the blob already exported?
252             if h in filenodes:
253                 mark = filenodes[h]
254                 final.append((mode, mark, path))
255                 continue
256
257             d = tree.get_file_text(fid)
258         elif kind == 'directory':
259             continue
260         else:
261             die("Unhandled kind '%s' for path '%s'" % (kind, path))
262
263         mark = marks.next_mark()
264         filenodes[h] = mark
265
266         print "blob"
267         print "mark :%u" % mark
268         print "data %d" % len(d)
269         print d
270
271         final.append((mode, mark, path))
272
273     return final
274
275 def export_branch(repo, name):
276     global prefix
277
278     ref = '%s/heads/%s' % (prefix, name)
279     tip = marks.get_tip(name)
280
281     branch = bzrlib.branch.Branch.open(branches[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     global tags, prefix
379
380     ref = '%s/tags/%s' % (prefix, name)
381     print "reset %s" % ref
382     print "from :%u" % rev_to_mark(tags[name])
383     print
384
385 def do_import(parser):
386     global dirname
387
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     global blob_marks
414
415     parser.next()
416     mark = parser.get_mark()
417     parser.next()
418     data = parser.get_data()
419     blob_marks[mark] = data
420     parser.next()
421
422 class CustomTree():
423
424     def __init__(self, branch, revid, parents, files):
425         global files_cache
426
427         self.updates = {}
428         self.branch = branch
429
430         def copy_tree(revid):
431             files = files_cache[revid] = {}
432             branch.lock_read()
433             tree = branch.repository.revision_tree(revid)
434             try:
435                 for path, entry in tree.iter_entries_by_dir():
436                     files[path] = [entry.file_id, None]
437             finally:
438                 branch.unlock()
439             return files
440
441         if len(parents) == 0:
442             self.base_id = bzrlib.revision.NULL_REVISION
443             self.base_files = {}
444         else:
445             self.base_id = parents[0]
446             self.base_files = files_cache.get(self.base_id, None)
447             if not self.base_files:
448                 self.base_files = copy_tree(self.base_id)
449
450         self.files = files_cache[revid] = self.base_files.copy()
451         self.rev_files = {}
452
453         for path, data in self.files.iteritems():
454             fid, mark = data
455             self.rev_files[fid] = [path, mark]
456
457         for path, f in files.iteritems():
458             fid, mark = self.files.get(path, [None, None])
459             if not fid:
460                 fid = bzrlib.generate_ids.gen_file_id(path)
461             f['path'] = path
462             self.rev_files[fid] = [path, mark]
463             self.updates[fid] = f
464
465     def last_revision(self):
466         return self.base_id
467
468     def iter_changes(self):
469         changes = []
470
471         def get_parent(dirname, basename):
472             parent_fid, mark = self.base_files.get(dirname, [None, None])
473             if parent_fid:
474                 return parent_fid
475             parent_fid, mark = self.files.get(dirname, [None, None])
476             if parent_fid:
477                 return parent_fid
478             if basename == '':
479                 return None
480             fid = bzrlib.generate_ids.gen_file_id(path)
481             add_entry(fid, dirname, 'directory')
482             return fid
483
484         def add_entry(fid, path, kind, mode = None):
485             dirname, basename = os.path.split(path)
486             parent_fid = get_parent(dirname, basename)
487
488             executable = False
489             if mode == '100755':
490                 executable = True
491             elif mode == '120000':
492                 kind = 'symlink'
493
494             change = (fid,
495                     (None, path),
496                     True,
497                     (False, True),
498                     (None, parent_fid),
499                     (None, basename),
500                     (None, kind),
501                     (None, executable))
502             self.files[path] = [change[0], None]
503             changes.append(change)
504
505         def update_entry(fid, path, kind, mode = None):
506             dirname, basename = os.path.split(path)
507             parent_fid = get_parent(dirname, basename)
508
509             executable = False
510             if mode == '100755':
511                 executable = True
512             elif mode == '120000':
513                 kind = 'symlink'
514
515             change = (fid,
516                     (path, path),
517                     True,
518                     (True, True),
519                     (None, parent_fid),
520                     (None, basename),
521                     (None, kind),
522                     (None, executable))
523             self.files[path] = [change[0], None]
524             changes.append(change)
525
526         def remove_entry(fid, path, kind):
527             dirname, basename = os.path.split(path)
528             parent_fid = get_parent(dirname, basename)
529             change = (fid,
530                     (path, None),
531                     True,
532                     (True, False),
533                     (parent_fid, None),
534                     (None, None),
535                     (None, None),
536                     (None, None))
537             del self.files[path]
538             changes.append(change)
539
540         for fid, f in self.updates.iteritems():
541             path = f['path']
542
543             if 'deleted' in f:
544                 remove_entry(fid, path, 'file')
545                 continue
546
547             if path in self.base_files:
548                 update_entry(fid, path, 'file', f['mode'])
549             else:
550                 add_entry(fid, path, 'file', f['mode'])
551
552             self.files[path][1] = f['mark']
553             self.rev_files[fid][1] = f['mark']
554
555         return changes
556
557     def get_content(self, file_id):
558         path, mark = self.rev_files[file_id]
559         if mark:
560             return blob_marks[mark]
561
562         # last resort
563         tree = self.branch.repository.revision_tree(self.base_id)
564         return tree.get_file_text(file_id)
565
566     def get_file_with_stat(self, file_id, path=None):
567         content = self.get_content(file_id)
568         return (StringIO.StringIO(content), None)
569
570     def get_symlink_target(self, file_id):
571         return self.get_content(file_id)
572
573     def id2path(self, file_id):
574         path, mark = self.rev_files[file_id]
575         return path
576
577 def c_style_unescape(string):
578     if string[0] == string[-1] == '"':
579         return string.decode('string-escape')[1:-1]
580     return string
581
582 def parse_commit(parser):
583     global marks, blob_marks, parsed_refs
584     global mode
585
586     parents = []
587
588     ref = parser[1]
589     parser.next()
590
591     if ref.startswith('refs/heads/'):
592         name = ref[len('refs/heads/'):]
593         branch = bzrlib.branch.Branch.open(branches[name])
594     else:
595         die('unknown ref')
596
597     commit_mark = parser.get_mark()
598     parser.next()
599     author = parser.get_author()
600     parser.next()
601     committer = parser.get_author()
602     parser.next()
603     data = parser.get_data()
604     parser.next()
605     if parser.check('from'):
606         parents.append(parser.get_mark())
607         parser.next()
608     while parser.check('merge'):
609         parents.append(parser.get_mark())
610         parser.next()
611
612     # fast-export adds an extra newline
613     if data[-1] == '\n':
614         data = data[:-1]
615
616     files = {}
617
618     for line in parser:
619         if parser.check('M'):
620             t, m, mark_ref, path = line.split(' ', 3)
621             mark = int(mark_ref[1:])
622             f = { 'mode' : m, 'mark' : mark }
623         elif parser.check('D'):
624             t, path = line.split(' ')
625             f = { 'deleted' : True }
626         else:
627             die('Unknown file command: %s' % line)
628         path = c_style_unescape(path).decode('utf-8')
629         files[path] = f
630
631     committer, date, tz = committer
632     parents = [mark_to_rev(p) for p in parents]
633     revid = bzrlib.generate_ids.gen_revision_id(committer, date)
634     props = {}
635     props['branch-nick'] = branch.nick
636
637     mtree = CustomTree(branch, revid, parents, files)
638     changes = mtree.iter_changes()
639
640     branch.lock_write()
641     try:
642         builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid)
643         try:
644             list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
645             builder.finish_inventory()
646             builder.commit(data.decode('utf-8', 'replace'))
647         except Exception, e:
648             builder.abort()
649             raise
650     finally:
651         branch.unlock()
652
653     parsed_refs[ref] = revid
654     marks.new_mark(revid, commit_mark)
655
656 def parse_reset(parser):
657     global parsed_refs
658
659     ref = parser[1]
660     parser.next()
661
662     # ugh
663     if parser.check('commit'):
664         parse_commit(parser)
665         return
666     if not parser.check('from'):
667         return
668     from_mark = parser.get_mark()
669     parser.next()
670
671     parsed_refs[ref] = mark_to_rev(from_mark)
672
673 def do_export(parser):
674     global parsed_refs, dirname
675
676     parser.next()
677
678     for line in parser.each_block('done'):
679         if parser.check('blob'):
680             parse_blob(parser)
681         elif parser.check('commit'):
682             parse_commit(parser)
683         elif parser.check('reset'):
684             parse_reset(parser)
685         elif parser.check('tag'):
686             pass
687         elif parser.check('feature'):
688             pass
689         else:
690             die('unhandled export command: %s' % line)
691
692     for ref, revid in parsed_refs.iteritems():
693         if ref.startswith('refs/heads/'):
694             name = ref[len('refs/heads/'):]
695             branch = bzrlib.branch.Branch.open(branches[name])
696             branch.generate_revision_history(revid, marks.get_tip(name))
697
698             if name in peers:
699                 peer = bzrlib.branch.Branch.open(peers[name])
700                 try:
701                     peer.bzrdir.push_branch(branch, revision_id=revid)
702                 except bzrlib.errors.DivergedBranches:
703                     print "error %s non-fast forward" % ref
704                     continue
705
706             try:
707                 wt = branch.bzrdir.open_workingtree()
708                 wt.update()
709             except bzrlib.errors.NoWorkingTree:
710                 pass
711         elif ref.startswith('refs/tags/'):
712             # TODO: implement tag push
713             print "error %s pushing tags not supported" % ref
714             continue
715         else:
716             # transport-helper/fast-export bugs
717             continue
718
719         print "ok %s" % ref
720
721     print
722
723 def do_capabilities(parser):
724     global dirname
725
726     print "import"
727     print "export"
728     print "refspec refs/heads/*:%s/heads/*" % prefix
729     print "refspec refs/tags/*:%s/tags/*" % prefix
730
731     path = os.path.join(dirname, 'marks-git')
732
733     if os.path.exists(path):
734         print "*import-marks %s" % path
735     print "*export-marks %s" % path
736
737     print
738
739 def ref_is_valid(name):
740     return not True in [c in name for c in '~^: \\']
741
742 def do_list(parser):
743     global tags
744
745     master_branch = None
746
747     for name in branches:
748         if not master_branch:
749             master_branch = name
750         print "? refs/heads/%s" % name
751
752     branch = bzrlib.branch.Branch.open(branches[master_branch])
753     branch.lock_read()
754     for tag, revid in branch.tags.get_tag_dict().items():
755         try:
756             branch.revision_id_to_dotted_revno(revid)
757         except bzrlib.errors.NoSuchRevision:
758             continue
759         if not ref_is_valid(tag):
760             continue
761         print "? refs/tags/%s" % tag
762         tags[tag] = revid
763     branch.unlock()
764
765     print "@refs/heads/%s HEAD" % master_branch
766     print
767
768 def get_remote_branch(origin, remote_branch, name):
769     global dirname, peers
770
771     branch_path = os.path.join(dirname, 'clone', name)
772     if os.path.exists(branch_path):
773         # pull
774         d = bzrlib.bzrdir.BzrDir.open(branch_path)
775         branch = d.open_branch()
776         try:
777             branch.pull(remote_branch, [], None, False)
778         except bzrlib.errors.DivergedBranches:
779             # use remote branch for now
780             return remote_branch
781     else:
782         # clone
783         d = origin.sprout(branch_path, None,
784                 hardlink=True, create_tree_if_local=False,
785                 force_new_repo=False,
786                 source_branch=remote_branch)
787         branch = d.open_branch()
788
789     return branch
790
791 def find_branches(repo, wanted):
792     transport = repo.bzrdir.root_transport
793
794     for fn in transport.iter_files_recursive():
795         if not fn.endswith('.bzr/branch-format'):
796             continue
797
798         name = subdir = fn[:-len('/.bzr/branch-format')]
799         name = name if name != '' else 'master'
800         name = name.replace('/', '+')
801
802         if wanted and not name in wanted:
803             continue
804
805         try:
806             cur = transport.clone(subdir)
807             branch = bzrlib.branch.Branch.open_from_transport(cur)
808         except bzrlib.errors.NotBranchError:
809             continue
810         else:
811             yield name, branch
812
813 def get_repo(url, alias):
814     global dirname, peer, branches
815
816     normal_url = bzrlib.urlutils.normalize_url(url)
817     origin = bzrlib.bzrdir.BzrDir.open(url)
818     is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
819
820     shared_path = os.path.join(gitdir, 'bzr')
821     try:
822         shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path)
823     except bzrlib.errors.NotBranchError:
824         shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path)
825     try:
826         shared_repo = shared_dir.open_repository()
827     except bzrlib.errors.NoRepositoryPresent:
828         shared_repo = shared_dir.create_repository(shared=True)
829
830     if not is_local:
831         clone_path = os.path.join(dirname, 'clone')
832         if not os.path.exists(clone_path):
833             os.mkdir(clone_path)
834         else:
835             # check and remove old organization
836             try:
837                 bdir = bzrlib.bzrdir.BzrDir.open(clone_path)
838                 bdir.destroy_repository()
839             except bzrlib.errors.NotBranchError:
840                 pass
841             except bzrlib.errors.NoRepositoryPresent:
842                 pass
843
844     try:
845         repo = origin.open_repository()
846         if not repo.user_transport.listable():
847             # this repository is not usable for us
848             raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
849     except bzrlib.errors.NoRepositoryPresent:
850         # branch
851
852         name = 'master'
853         remote_branch = origin.open_branch()
854
855         if not is_local:
856             peers[name] = remote_branch.base
857             branch = get_remote_branch(origin, remote_branch, name)
858         else:
859             branch = remote_branch
860
861         branches[name] = branch.base
862
863         return branch.repository
864     else:
865         # repository
866
867         wanted = get_config('remote-bzr.branches').rstrip().split(', ')
868         # stupid python
869         wanted = [e for e in wanted if e]
870
871         for name, remote_branch in find_branches(repo, wanted):
872
873             if not is_local:
874                 peers[name] = remote_branch.base
875                 branch = get_remote_branch(origin, remote_branch, name)
876             else:
877                 branch = remote_branch
878
879             branches[name] = branch.base
880
881         return repo
882
883 def fix_path(alias, orig_url):
884     url = urlparse.urlparse(orig_url, 'file')
885     if url.scheme != 'file' or os.path.isabs(url.path):
886         return
887     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
888     cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
889     subprocess.call(cmd)
890
891 def main(args):
892     global marks, prefix, gitdir, dirname
893     global tags, filenodes
894     global blob_marks
895     global parsed_refs
896     global files_cache
897     global is_tmp
898     global branches, peers
899
900     alias = args[1]
901     url = args[2]
902
903     tags = {}
904     filenodes = {}
905     blob_marks = {}
906     parsed_refs = {}
907     files_cache = {}
908     marks = None
909     branches = {}
910     peers = {}
911
912     if alias[5:] == url:
913         is_tmp = True
914         alias = hashlib.sha1(alias).hexdigest()
915     else:
916         is_tmp = False
917
918     prefix = 'refs/bzr/%s' % alias
919     gitdir = os.environ['GIT_DIR']
920     dirname = os.path.join(gitdir, 'bzr', alias)
921
922     if not is_tmp:
923         fix_path(alias, url)
924
925     if not os.path.exists(dirname):
926         os.makedirs(dirname)
927
928     if hasattr(bzrlib.ui.ui_factory, 'be_quiet'):
929         bzrlib.ui.ui_factory.be_quiet(True)
930
931     repo = get_repo(url, alias)
932
933     marks_path = os.path.join(dirname, 'marks-int')
934     marks = Marks(marks_path)
935
936     parser = Parser(repo)
937     for line in parser:
938         if parser.check('capabilities'):
939             do_capabilities(parser)
940         elif parser.check('list'):
941             do_list(parser)
942         elif parser.check('import'):
943             do_import(parser)
944         elif parser.check('export'):
945             do_export(parser)
946         else:
947             die('unhandled command: %s' % line)
948         sys.stdout.flush()
949
950 def bye():
951     if not marks:
952         return
953     if not is_tmp:
954         marks.store()
955     else:
956         shutil.rmtree(dirname)
957
958 atexit.register(bye)
959 sys.exit(main(sys.argv))