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