remote-bzr: avoid bad refs
[alt-git.git] / contrib / remote-helpers / git-remote-bzr
blob0ef30f8d558da0fc2061f98c5670dc515c4dc12b
1 #!/usr/bin/env python
3 # Copyright (c) 2012 Felipe Contreras
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
11 # For example:
12 # % git clone bzr::$HOME/myrepo
13 # or
14 # % git clone bzr::lp:myrepo
16 # If you want to specify which branches you want track (per repo):
17 # git config remote-bzr.branches 'trunk, devel, test'
20 import sys
22 import bzrlib
23 if hasattr(bzrlib, "initialize"):
24 bzrlib.initialize()
26 import bzrlib.plugin
27 bzrlib.plugin.load_plugins()
29 import bzrlib.generate_ids
30 import bzrlib.transport
31 import bzrlib.errors
32 import bzrlib.ui
33 import bzrlib.urlutils
35 import sys
36 import os
37 import json
38 import re
39 import StringIO
40 import atexit, shutil, hashlib, urlparse, subprocess
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+)')
47 def die(msg, *args):
48 sys.stderr.write('ERROR: %s\n' % (msg % args))
49 sys.exit(1)
51 def warn(msg, *args):
52 sys.stderr.write('WARNING: %s\n' % (msg % args))
54 def gittz(tz):
55 return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
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
63 class Marks:
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()
73 def load(self):
74 if not os.path.exists(self.path):
75 return
77 tmp = json.load(open(self.path))
78 self.tips = tmp['tips']
79 self.marks = tmp['marks']
80 self.last_mark = tmp['last-mark']
82 for rev, mark in self.marks.iteritems():
83 self.rev_marks[mark] = rev
85 def dict(self):
86 return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
88 def store(self):
89 json.dump(self.dict(), open(self.path, 'w'))
91 def __str__(self):
92 return str(self.dict())
94 def from_rev(self, rev):
95 return self.marks[rev]
97 def to_rev(self, mark):
98 return str(self.rev_marks[mark])
100 def next_mark(self):
101 self.last_mark += 1
102 return self.last_mark
104 def get_mark(self, rev):
105 self.last_mark += 1
106 self.marks[rev] = self.last_mark
107 return self.last_mark
109 def is_marked(self, rev):
110 return rev in self.marks
112 def new_mark(self, rev, mark):
113 self.marks[rev] = mark
114 self.rev_marks[mark] = rev
115 self.last_mark = mark
117 def get_tip(self, branch):
118 return self.tips.get(branch, None)
120 def set_tip(self, branch, tip):
121 self.tips[branch] = tip
123 class Parser:
125 def __init__(self, repo):
126 self.repo = repo
127 self.line = self.get_line()
129 def get_line(self):
130 return sys.stdin.readline().strip()
132 def __getitem__(self, i):
133 return self.line.split()[i]
135 def check(self, word):
136 return self.line.startswith(word)
138 def each_block(self, separator):
139 while self.line != separator:
140 yield self.line
141 self.line = self.get_line()
143 def __iter__(self):
144 return self.each_block('')
146 def next(self):
147 self.line = self.get_line()
148 if self.line == 'done':
149 self.line = None
151 def get_mark(self):
152 i = self.line.index(':') + 1
153 return int(self.line[i:])
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)
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)
172 def rev_to_mark(rev):
173 global marks
174 return marks.from_rev(rev)
176 def mark_to_rev(mark):
177 global marks
178 return marks.to_rev(mark)
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()
197 if not name:
198 name = 'unknown'
199 if not mail:
200 mail = 'Unknown'
202 return '%s <%s>' % (name, mail)
204 def get_filechanges(cur, prev):
205 modified = {}
206 removed = {}
208 changes = cur.changes_from(prev)
210 def u(s):
211 return s.encode('utf-8')
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
229 return modified, removed
231 def export_files(tree, files):
232 global marks, filenodes
234 final = []
235 for path, fid in files.iteritems():
236 kind = tree.kind(fid)
238 h = tree.get_file_sha1(fid)
240 if kind == 'symlink':
241 d = tree.get_symlink_target(fid)
242 mode = '120000'
243 elif kind == 'file':
245 if tree.is_executable(fid):
246 mode = '100755'
247 else:
248 mode = '100644'
250 # is the blob already exported?
251 if h in filenodes:
252 mark = filenodes[h]
253 final.append((mode, mark, path))
254 continue
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))
262 mark = marks.next_mark()
263 filenodes[h] = mark
265 print "blob"
266 print "mark :%u" % mark
267 print "data %d" % len(d)
268 print d
270 final.append((mode, mark, path))
272 return final
274 def export_branch(repo, name):
275 global prefix
277 ref = '%s/heads/%s' % (prefix, name)
278 tip = marks.get_tip(name)
280 branch = bzrlib.branch.Branch.open(branches[name])
281 repo = branch.repository
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
289 for revid, _, seq, _ in revs:
291 if marks.is_marked(revid):
292 continue
294 rev = repo.get_revision(revid)
295 revno = seq[0]
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')
310 msg += '\n'
312 if len(parents) == 0:
313 parent = bzrlib.revision.NULL_REVISION
314 else:
315 parent = parents[0]
317 cur_tree = repo.revision_tree(revid)
318 prev = repo.revision_tree(parent)
319 modified, removed = get_filechanges(cur_tree, prev)
321 modified_final = export_files(cur_tree, modified)
323 if len(parents) == 0:
324 print 'reset %s' % ref
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
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
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
350 if len(seq) > 1:
351 # let's skip branch revisions from the progress report
352 continue
354 progress = (revno - tip_revno)
355 if (progress % 100 == 0):
356 print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total)
358 branch.unlock()
360 revid = branch.last_revision()
362 # make sure the ref is updated
363 print "reset %s" % ref
364 print "from :%u" % rev_to_mark(revid)
365 print
367 marks.set_tip(name, revid)
369 def export_tag(repo, name):
370 global tags, prefix
372 ref = '%s/tags/%s' % (prefix, name)
373 print "reset %s" % ref
374 print "from :%u" % rev_to_mark(tags[name])
375 print
377 def do_import(parser):
378 global dirname
380 repo = parser.repo
381 path = os.path.join(dirname, 'marks-git')
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()
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()
400 print 'done'
402 sys.stdout.flush()
404 def parse_blob(parser):
405 global blob_marks
407 parser.next()
408 mark = parser.get_mark()
409 parser.next()
410 data = parser.get_data()
411 blob_marks[mark] = data
412 parser.next()
414 class CustomTree():
416 def __init__(self, branch, revid, parents, files):
417 global files_cache
419 self.updates = {}
420 self.branch = branch
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
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)
442 self.files = files_cache[revid] = self.base_files.copy()
443 self.rev_files = {}
445 for path, data in self.files.iteritems():
446 fid, mark = data
447 self.rev_files[fid] = [path, mark]
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
457 def last_revision(self):
458 return self.base_id
460 def iter_changes(self):
461 changes = []
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
476 def add_entry(fid, path, kind, mode = None):
477 dirname, basename = os.path.split(path)
478 parent_fid = get_parent(dirname, basename)
480 executable = False
481 if mode == '100755':
482 executable = True
483 elif mode == '120000':
484 kind = 'symlink'
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)
497 def update_entry(fid, path, kind, mode = None):
498 dirname, basename = os.path.split(path)
499 parent_fid = get_parent(dirname, basename)
501 executable = False
502 if mode == '100755':
503 executable = True
504 elif mode == '120000':
505 kind = 'symlink'
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)
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)
532 for fid, f in self.updates.iteritems():
533 path = f['path']
535 if 'deleted' in f:
536 remove_entry(fid, path, 'file')
537 continue
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'])
544 self.files[path][1] = f['mark']
545 self.rev_files[fid][1] = f['mark']
547 return changes
549 def get_content(self, file_id):
550 path, mark = self.rev_files[file_id]
551 if mark:
552 return blob_marks[mark]
554 # last resort
555 tree = self.branch.repository.revision_tree(self.base_id)
556 return tree.get_file_text(file_id)
558 def get_file_with_stat(self, file_id, path=None):
559 content = self.get_content(file_id)
560 return (StringIO.StringIO(content), None)
562 def get_symlink_target(self, file_id):
563 return self.get_content(file_id)
565 def id2path(self, file_id):
566 path, mark = self.rev_files[file_id]
567 return path
569 def c_style_unescape(string):
570 if string[0] == string[-1] == '"':
571 return string.decode('string-escape')[1:-1]
572 return string
574 def parse_commit(parser):
575 global marks, blob_marks, parsed_refs
576 global mode
578 parents = []
580 ref = parser[1]
581 parser.next()
583 if ref.startswith('refs/heads/'):
584 name = ref[len('refs/heads/'):]
585 branch = bzrlib.branch.Branch.open(branches[name])
586 else:
587 die('unknown ref')
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()
604 # fast-export adds an extra newline
605 if data[-1] == '\n':
606 data = data[:-1]
608 files = {}
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
623 committer, date, tz = committer
624 parents = [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
629 mtree = CustomTree(branch, revid, parents, files)
630 changes = mtree.iter_changes()
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()
645 parsed_refs[ref] = revid
646 marks.new_mark(revid, commit_mark)
648 def parse_reset(parser):
649 global parsed_refs
651 ref = parser[1]
652 parser.next()
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()
663 parsed_refs[ref] = mark_to_rev(from_mark)
665 def do_export(parser):
666 global parsed_refs, dirname
668 parser.next()
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)
684 for ref, revid in parsed_refs.iteritems():
685 if ref.startswith('refs/heads/'):
686 name = ref[len('refs/heads/'):]
687 branch = bzrlib.branch.Branch.open(branches[name])
688 branch.generate_revision_history(revid, marks.get_tip(name))
690 if name in peers:
691 peer = bzrlib.branch.Branch.open(peers[name])
692 try:
693 peer.bzrdir.push_branch(branch, revision_id=revid)
694 except bzrlib.errors.DivergedBranches:
695 print "error %s non-fast forward" % ref
696 continue
698 try:
699 wt = branch.bzrdir.open_workingtree()
700 wt.update()
701 except bzrlib.errors.NoWorkingTree:
702 pass
703 elif ref.startswith('refs/tags/'):
704 # TODO: implement tag push
705 print "error %s pushing tags not supported" % ref
706 continue
707 else:
708 # transport-helper/fast-export bugs
709 continue
711 print "ok %s" % ref
713 print
715 def do_capabilities(parser):
716 global dirname
718 print "import"
719 print "export"
720 print "refspec refs/heads/*:%s/heads/*" % prefix
721 print "refspec refs/tags/*:%s/tags/*" % prefix
723 path = os.path.join(dirname, 'marks-git')
725 if os.path.exists(path):
726 print "*import-marks %s" % path
727 print "*export-marks %s" % path
729 print
731 def ref_is_valid(name):
732 return not True in [c in name for c in '~^: \\']
734 def do_list(parser):
735 global tags
737 master_branch = None
739 for name in branches:
740 if not master_branch:
741 master_branch = name
742 print "? refs/heads/%s" % name
744 branch = bzrlib.branch.Branch.open(branches[master_branch])
745 branch.lock_read()
746 for tag, revid in branch.tags.get_tag_dict().items():
747 try:
748 branch.revision_id_to_dotted_revno(revid)
749 except bzrlib.errors.NoSuchRevision:
750 continue
751 if not ref_is_valid(tag):
752 continue
753 print "? refs/tags/%s" % tag
754 tags[tag] = revid
755 branch.unlock()
757 print "@refs/heads/%s HEAD" % master_branch
758 print
760 def get_remote_branch(origin, remote_branch, name):
761 global dirname, peers
763 branch_path = os.path.join(dirname, 'clone', name)
764 if os.path.exists(branch_path):
765 # pull
766 d = bzrlib.bzrdir.BzrDir.open(branch_path)
767 branch = d.open_branch()
768 try:
769 branch.pull(remote_branch, [], None, False)
770 except bzrlib.errors.DivergedBranches:
771 # use remote branch for now
772 return remote_branch
773 else:
774 # clone
775 d = origin.sprout(branch_path, None,
776 hardlink=True, create_tree_if_local=False,
777 force_new_repo=False,
778 source_branch=remote_branch)
779 branch = d.open_branch()
781 return branch
783 def find_branches(repo, wanted):
784 transport = repo.user_transport
786 for fn in transport.iter_files_recursive():
787 if not fn.endswith('.bzr/branch-format'):
788 continue
790 name = subdir = fn[:-len('/.bzr/branch-format')]
791 name = name if name != '' else 'master'
792 name = name.replace('/', '+')
794 if wanted and not name in wanted:
795 continue
797 try:
798 cur = transport.clone(subdir)
799 branch = bzrlib.branch.Branch.open_from_transport(cur)
800 except bzrlib.errors.NotBranchError:
801 continue
802 else:
803 yield name, branch
805 def get_repo(url, alias):
806 global dirname, peer, branches
808 normal_url = bzrlib.urlutils.normalize_url(url)
809 origin = bzrlib.bzrdir.BzrDir.open(url)
810 is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
812 shared_path = os.path.join(gitdir, 'bzr')
813 try:
814 shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path)
815 except bzrlib.errors.NotBranchError:
816 shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path)
817 try:
818 shared_repo = shared_dir.open_repository()
819 except bzrlib.errors.NoRepositoryPresent:
820 shared_repo = shared_dir.create_repository(shared=True)
822 if not is_local:
823 clone_path = os.path.join(dirname, 'clone')
824 if not os.path.exists(clone_path):
825 os.mkdir(clone_path)
827 try:
828 repo = origin.open_repository()
829 except bzrlib.errors.NoRepositoryPresent:
830 # branch
832 name = 'master'
833 remote_branch = origin.open_branch()
835 if not is_local:
836 peers[name] = remote_branch.base
837 branch = get_remote_branch(origin, remote_branch, name)
838 else:
839 branch = remote_branch
841 branches[name] = branch.base
843 return branch.repository
844 else:
845 # repository
847 wanted = get_config('remote-bzr.branches').rstrip().split(', ')
848 # stupid python
849 wanted = [e for e in wanted if e]
851 for name, remote_branch in find_branches(repo, wanted):
853 if not is_local:
854 peers[name] = remote_branch.base
855 branch = get_remote_branch(origin, remote_branch, name)
856 else:
857 branch = remote_branch
859 branches[name] = branch.base
861 return repo
863 def fix_path(alias, orig_url):
864 url = urlparse.urlparse(orig_url, 'file')
865 if url.scheme != 'file' or os.path.isabs(url.path):
866 return
867 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
868 cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
869 subprocess.call(cmd)
871 def main(args):
872 global marks, prefix, gitdir, dirname
873 global tags, filenodes
874 global blob_marks
875 global parsed_refs
876 global files_cache
877 global is_tmp
878 global branches, peers
880 alias = args[1]
881 url = args[2]
883 tags = {}
884 filenodes = {}
885 blob_marks = {}
886 parsed_refs = {}
887 files_cache = {}
888 marks = None
889 branches = {}
890 peers = {}
892 if alias[5:] == url:
893 is_tmp = True
894 alias = hashlib.sha1(alias).hexdigest()
895 else:
896 is_tmp = False
898 prefix = 'refs/bzr/%s' % alias
899 gitdir = os.environ['GIT_DIR']
900 dirname = os.path.join(gitdir, 'bzr', alias)
902 if not is_tmp:
903 fix_path(alias, url)
905 if not os.path.exists(dirname):
906 os.makedirs(dirname)
908 bzrlib.ui.ui_factory.be_quiet(True)
910 repo = get_repo(url, alias)
912 marks_path = os.path.join(dirname, 'marks-int')
913 marks = Marks(marks_path)
915 parser = Parser(repo)
916 for line in parser:
917 if parser.check('capabilities'):
918 do_capabilities(parser)
919 elif parser.check('list'):
920 do_list(parser)
921 elif parser.check('import'):
922 do_import(parser)
923 elif parser.check('export'):
924 do_export(parser)
925 else:
926 die('unhandled command: %s' % line)
927 sys.stdout.flush()
929 def bye():
930 if not marks:
931 return
932 if not is_tmp:
933 marks.store()
934 else:
935 shutil.rmtree(dirname)
937 atexit.register(bye)
938 sys.exit(main(sys.argv))