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