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