1#!/usr/bin/env python
2#
3# Copyright (c) 2012 Felipe Contreras
4#
56
# Inspired by Rocco Rutte's hg-fast-export
78
# Just copy to your ~/bin, or anywhere in your $PATH.
9# Then you can clone with:
10# git clone hg::/path/to/mercurial/repo/
11#
12# For remote repositories a local clone is stored in
13# "$GIT_DIR/hg/origin/clone/.hg/".
1415
from mercurial import hg, ui, bookmarks, context, util, encoding, node, error, extensions
1617
import re
18import sys
19import os
20import json
21import shutil
22import subprocess
23import urllib
24import atexit
25import urlparse
2627
#
28# If you want to switch to hg-git compatibility mode:
29# git config --global remote-hg.hg-git-compat true
30#
31# If you are not in hg-git-compat mode and want to disable the tracking of
32# named branches:
33# git config --global remote-hg.track-branches false
34#
35# If you don't want to force pushes (and thus risk creating new remote heads):
36# git config --global remote-hg.force-push false
37#
38# If you want the equivalent of hg's clone/pull--insecure option:
39# git config remote-hg.insecure true
40#
41# git:
42# Sensible defaults for git.
43# hg bookmarks are exported as git branches, hg branches are prefixed
44# with 'branches/', HEAD is a special case.
45#
46# hg:
47# Emulate hg-git.
48# Only hg bookmarks are exported as git branches.
49# Commits are modified to preserve hg information and allow bidirectionality.
50#
5152
NAME_RE = re.compile('^([^<>]+)')
53AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
54EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
55AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
56RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
5758
def die(msg, *args):
59sys.stderr.write('ERROR: %s\n' % (msg % args))
60sys.exit(1)
6162
def warn(msg, *args):
63sys.stderr.write('WARNING: %s\n' % (msg % args))
6465
def gitmode(flags):
66return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
6768
def gittz(tz):
69return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
7071
def hgmode(mode):
72m = { '100755': 'x', '120000': 'l' }
73return m.get(mode, '')
7475
def hghex(node):
76return hg.node.hex(node)
7778
def get_config(config):
79cmd = ['git', 'config', '--get', config]
80process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
81output, _ = process.communicate()
82return output
8384
class Marks:
8586
def __init__(self, path):
87self.path = path
88self.tips = {}
89self.marks = {}
90self.rev_marks = {}
91self.last_mark = 0
9293
self.load()
9495
def load(self):
96if not os.path.exists(self.path):
97return
9899
tmp = json.load(open(self.path))
100101
self.tips = tmp['tips']
102self.marks = tmp['marks']
103self.last_mark = tmp['last-mark']
104105
for rev, mark in self.marks.iteritems():
106self.rev_marks[mark] = int(rev)
107108
def dict(self):
109return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
110111
def store(self):
112json.dump(self.dict(), open(self.path, 'w'))
113114
def __str__(self):
115return str(self.dict())
116117
def from_rev(self, rev):
118return self.marks[str(rev)]
119120
def to_rev(self, mark):
121return self.rev_marks[mark]
122123
def get_mark(self, rev):
124self.last_mark += 1
125self.marks[str(rev)] = self.last_mark
126return self.last_mark
127128
def new_mark(self, rev, mark):
129self.marks[str(rev)] = mark
130self.rev_marks[mark] = rev
131self.last_mark = mark
132133
def is_marked(self, rev):
134return str(rev) in self.marks
135136
def get_tip(self, branch):
137return self.tips.get(branch, 0)
138139
def set_tip(self, branch, tip):
140self.tips[branch] = tip
141142
class Parser:
143144
def __init__(self, repo):
145self.repo = repo
146self.line = self.get_line()
147148
def get_line(self):
149return sys.stdin.readline().strip()
150151
def __getitem__(self, i):
152return self.line.split()[i]
153154
def check(self, word):
155return self.line.startswith(word)
156157
def each_block(self, separator):
158while self.line != separator:
159yield self.line
160self.line = self.get_line()
161162
def __iter__(self):
163return self.each_block('')
164165
def next(self):
166self.line = self.get_line()
167if self.line == 'done':
168self.line = None
169170
def get_mark(self):
171i = self.line.index(':') + 1
172return int(self.line[i:])
173174
def get_data(self):
175if not self.check('data'):
176return None
177i = self.line.index(' ') + 1
178size = int(self.line[i:])
179return sys.stdin.read(size)
180181
def get_author(self):
182global bad_mail
183184
ex = None
185m = RAW_AUTHOR_RE.match(self.line)
186if not m:
187return None
188_, name, email, date, tz = m.groups()
189if name and 'ext:' in name:
190m = re.match('^(.+?) ext:\((.+)\)$', name)
191if m:
192name = m.group(1)
193ex = urllib.unquote(m.group(2))
194195
if email != bad_mail:
196if name:
197user = '%s <%s>' % (name, email)
198else:
199user = '<%s>' % (email)
200else:
201user = name
202203
if ex:
204user += ex
205206
tz = int(tz)
207tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
208return (user, int(date), -tz)
209210
def fix_file_path(path):
211if not os.path.isabs(path):
212return path
213return os.path.relpath(path, '/')
214215
def export_file(fc):
216d = fc.data()
217path = fix_file_path(fc.path())
218print "M %s inline %s" % (gitmode(fc.flags()), path)
219print "data %d" % len(d)
220print d
221222
def get_filechanges(repo, ctx, parent):
223modified = set()
224added = set()
225removed = set()
226227
cur = ctx.manifest()
228prev = repo[parent].manifest().copy()
229230
for fn in cur:
231if fn in prev:
232if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
233modified.add(fn)
234del prev[fn]
235else:
236added.add(fn)
237removed |= set(prev.keys())
238239
return added | modified, removed
240241
def fixup_user_git(user):
242name = mail = None
243user = user.replace('"', '')
244m = AUTHOR_RE.match(user)
245if m:
246name = m.group(1)
247mail = m.group(2).strip()
248else:
249m = EMAIL_RE.match(user)
250if m:
251name = m.group(1)
252mail = m.group(2)
253else:
254m = NAME_RE.match(user)
255if m:
256name = m.group(1).strip()
257return (name, mail)
258259
def fixup_user_hg(user):
260def sanitize(name):
261# stole this from hg-git
262return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
263264
m = AUTHOR_HG_RE.match(user)
265if m:
266name = sanitize(m.group(1))
267mail = sanitize(m.group(2))
268ex = m.group(3)
269if ex:
270name += ' ext:(' + urllib.quote(ex) + ')'
271else:
272name = sanitize(user)
273if '@' in user:
274mail = name
275else:
276mail = None
277278
return (name, mail)
279280
def fixup_user(user):
281global mode, bad_mail
282283
if mode == 'git':
284name, mail = fixup_user_git(user)
285else:
286name, mail = fixup_user_hg(user)
287288
if not name:
289name = bad_name
290if not mail:
291mail = bad_mail
292293
return '%s <%s>' % (name, mail)
294295
def get_repo(url, alias):
296global dirname, peer
297298
myui = ui.ui()
299myui.setconfig('ui', 'interactive', 'off')
300myui.fout = sys.stderr
301302
try:
303if get_config('remote-hg.insecure') == 'true\n':
304myui.setconfig('web', 'cacerts', '')
305except subprocess.CalledProcessError:
306pass
307308
try:
309mod = extensions.load(myui, 'hgext.schemes', None)
310mod.extsetup(myui)
311except ImportError:
312pass
313314
if hg.islocal(url):
315repo = hg.repository(myui, url)
316else:
317local_path = os.path.join(dirname, 'clone')
318if not os.path.exists(local_path):
319try:
320peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True)
321except:
322die('Repository error')
323repo = dstpeer.local()
324else:
325repo = hg.repository(myui, local_path)
326try:
327peer = hg.peer(myui, {}, url)
328except:
329die('Repository error')
330repo.pull(peer, heads=None, force=True)
331332
return repo
333334
def rev_to_mark(rev):
335global marks
336return marks.from_rev(rev)
337338
def mark_to_rev(mark):
339global marks
340return marks.to_rev(mark)
341342
def export_ref(repo, name, kind, head):
343global prefix, marks, mode
344345
ename = '%s/%s' % (kind, name)
346tip = marks.get_tip(ename)
347348
# mercurial takes too much time checking this
349if tip and tip == head.rev():
350# nothing to do
351return
352revs = xrange(tip, head.rev() + 1)
353count = 0
354355
revs = [rev for rev in revs if not marks.is_marked(rev)]
356357
for rev in revs:
358359
c = repo[rev]
360(manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
361rev_branch = extra['branch']
362363
author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
364if 'committer' in extra:
365user, time, tz = extra['committer'].rsplit(' ', 2)
366committer = "%s %s %s" % (user, time, gittz(int(tz)))
367else:
368committer = author
369370
parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
371372
if len(parents) == 0:
373modified = c.manifest().keys()
374removed = []
375else:
376modified, removed = get_filechanges(repo, c, parents[0])
377378
desc += '\n'
379380
if mode == 'hg':
381extra_msg = ''
382383
if rev_branch != 'default':
384extra_msg += 'branch : %s\n' % rev_branch
385386
renames = []
387for f in c.files():
388if f not in c.manifest():
389continue
390rename = c.filectx(f).renamed()
391if rename:
392renames.append((rename[0], f))
393394
for e in renames:
395extra_msg += "rename : %s => %s\n" % e
396397
for key, value in extra.iteritems():
398if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
399continue
400else:
401extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
402403
if extra_msg:
404desc += '\n--HG--\n' + extra_msg
405406
if len(parents) == 0 and rev:
407print 'reset %s/%s' % (prefix, ename)
408409
print "commit %s/%s" % (prefix, ename)
410print "mark :%d" % (marks.get_mark(rev))
411print "author %s" % (author)
412print "committer %s" % (committer)
413print "data %d" % (len(desc))
414print desc
415416
if len(parents) > 0:
417print "from :%s" % (rev_to_mark(parents[0]))
418if len(parents) > 1:
419print "merge :%s" % (rev_to_mark(parents[1]))
420421
for f in modified:
422export_file(c.filectx(f))
423for f in removed:
424print "D %s" % (fix_file_path(f))
425427
count += 1
428if (count % 100 == 0):
429print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
430print "#############################################################"
431432
# make sure the ref is updated
433print "reset %s/%s" % (prefix, ename)
434print "from :%u" % rev_to_mark(rev)
435437
marks.set_tip(ename, rev)
438439
def export_tag(repo, tag):
440export_ref(repo, tag, 'tags', repo[tag])
441442
def export_bookmark(repo, bmark):
443head = bmarks[bmark]
444export_ref(repo, bmark, 'bookmarks', head)
445446
def export_branch(repo, branch):
447tip = get_branch_tip(repo, branch)
448head = repo[tip]
449export_ref(repo, branch, 'branches', head)
450451
def export_head(repo):
452global g_head
453export_ref(repo, g_head[0], 'bookmarks', g_head[1])
454455
def do_capabilities(parser):
456global prefix, dirname
457458
print "import"
459print "export"
460print "refspec refs/heads/branches/*:%s/branches/*" % prefix
461print "refspec refs/heads/*:%s/bookmarks/*" % prefix
462print "refspec refs/tags/*:%s/tags/*" % prefix
463464
path = os.path.join(dirname, 'marks-git')
465466
if os.path.exists(path):
467print "*import-marks %s" % path
468print "*export-marks %s" % path
469470
472
def branch_tip(repo, branch):
473# older versions of mercurial don't have this
474if hasattr(repo, 'branchtip'):
475return repo.branchtip(branch)
476else:
477return repo.branchtags()[branch]
478479
def get_branch_tip(repo, branch):
480global branches
481482
heads = branches.get(branch, None)
483if not heads:
484return None
485486
# verify there's only one head
487if (len(heads) > 1):
488warn("Branch '%s' has more than one head, consider merging" % branch)
489return branch_tip(repo, branch)
490491
return heads[0]
492493
def list_head(repo, cur):
494global g_head, bmarks
495496
head = bookmarks.readcurrent(repo)
497if head:
498node = repo[head]
499else:
500# fake bookmark from current branch
501head = cur
502node = repo['.']
503if not node:
504node = repo['tip']
505if not node:
506return
507if head == 'default':
508head = 'master'
509bmarks[head] = node
510511
print "@refs/heads/%s HEAD" % head
512g_head = (head, node)
513514
def do_list(parser):
515global branches, bmarks, mode, track_branches
516517
repo = parser.repo
518for bmark, node in bookmarks.listbookmarks(repo).iteritems():
519bmarks[bmark] = repo[node]
520521
cur = repo.dirstate.branch()
522523
list_head(repo, cur)
524525
if track_branches:
526for branch in repo.branchmap():
527heads = repo.branchheads(branch)
528if len(heads):
529branches[branch] = heads
530531
for branch in branches:
532print "? refs/heads/branches/%s" % branch
533534
for bmark in bmarks:
535print "? refs/heads/%s" % bmark
536537
for tag, node in repo.tagslist():
538if tag == 'tip':
539continue
540print "? refs/tags/%s" % tag
541542
544
def do_import(parser):
545repo = parser.repo
546547
path = os.path.join(dirname, 'marks-git')
548549
print "feature done"
550if os.path.exists(path):
551print "feature import-marks=%s" % path
552print "feature export-marks=%s" % path
553sys.stdout.flush()
554555
tmp = encoding.encoding
556encoding.encoding = 'utf-8'
557558
# lets get all the import lines
559while parser.check('import'):
560ref = parser[1]
561562
if (ref == 'HEAD'):
563export_head(repo)
564elif ref.startswith('refs/heads/branches/'):
565branch = ref[len('refs/heads/branches/'):]
566export_branch(repo, branch)
567elif ref.startswith('refs/heads/'):
568bmark = ref[len('refs/heads/'):]
569export_bookmark(repo, bmark)
570elif ref.startswith('refs/tags/'):
571tag = ref[len('refs/tags/'):]
572export_tag(repo, tag)
573574
parser.next()
575576
encoding.encoding = tmp
577578
print 'done'
579580
def parse_blob(parser):
581global blob_marks
582583
parser.next()
584mark = parser.get_mark()
585parser.next()
586data = parser.get_data()
587blob_marks[mark] = data
588parser.next()
589590
def get_merge_files(repo, p1, p2, files):
591for e in repo[p1].files():
592if e not in files:
593if e not in repo[p1].manifest():
594continue
595f = { 'ctx' : repo[p1][e] }
596files[e] = f
597598
def parse_commit(parser):
599global marks, blob_marks, parsed_refs
600global mode
601602
from_mark = merge_mark = None
603604
ref = parser[1]
605parser.next()
606607
commit_mark = parser.get_mark()
608parser.next()
609author = parser.get_author()
610parser.next()
611committer = parser.get_author()
612parser.next()
613data = parser.get_data()
614parser.next()
615if parser.check('from'):
616from_mark = parser.get_mark()
617parser.next()
618if parser.check('merge'):
619merge_mark = parser.get_mark()
620parser.next()
621if parser.check('merge'):
622die('octopus merges are not supported yet')
623624
files = {}
625626
for line in parser:
627if parser.check('M'):
628t, m, mark_ref, path = line.split(' ', 3)
629mark = int(mark_ref[1:])
630f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
631elif parser.check('D'):
632t, path = line.split(' ', 1)
633f = { 'deleted' : True }
634else:
635die('Unknown file command: %s' % line)
636files[path] = f
637638
def getfilectx(repo, memctx, f):
639of = files[f]
640if 'deleted' in of:
641raise IOError
642if 'ctx' in of:
643return of['ctx']
644is_exec = of['mode'] == 'x'
645is_link = of['mode'] == 'l'
646rename = of.get('rename', None)
647return context.memfilectx(f, of['data'],
648is_link, is_exec, rename)
649650
repo = parser.repo
651652
user, date, tz = author
653extra = {}
654655
if committer != author:
656extra['committer'] = "%s %u %u" % committer
657658
if from_mark:
659p1 = repo.changelog.node(mark_to_rev(from_mark))
660else:
661p1 = '\0' * 20
662663
if merge_mark:
664p2 = repo.changelog.node(mark_to_rev(merge_mark))
665else:
666p2 = '\0' * 20
667668
#
669# If files changed from any of the parents, hg wants to know, but in git if
670# nothing changed from the first parent, nothing changed.
671#
672if merge_mark:
673get_merge_files(repo, p1, p2, files)
674675
# Check if the ref is supposed to be a named branch
676if ref.startswith('refs/heads/branches/'):
677extra['branch'] = ref[len('refs/heads/branches/'):]
678679
if mode == 'hg':
680i = data.find('\n--HG--\n')
681if i >= 0:
682tmp = data[i + len('\n--HG--\n'):].strip()
683for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
684if k == 'rename':
685old, new = v.split(' => ', 1)
686files[new]['rename'] = old
687elif k == 'branch':
688extra[k] = v
689elif k == 'extra':
690ek, ev = v.split(' : ', 1)
691extra[ek] = urllib.unquote(ev)
692data = data[:i]
693694
ctx = context.memctx(repo, (p1, p2), data,
695files.keys(), getfilectx,
696user, (date, tz), extra)
697698
tmp = encoding.encoding
699encoding.encoding = 'utf-8'
700701
node = repo.commitctx(ctx)
702703
encoding.encoding = tmp
704705
rev = repo[node].rev()
706707
parsed_refs[ref] = node
708marks.new_mark(rev, commit_mark)
709710
def parse_reset(parser):
711global parsed_refs
712713
ref = parser[1]
714parser.next()
715# ugh
716if parser.check('commit'):
717parse_commit(parser)
718return
719if not parser.check('from'):
720return
721from_mark = parser.get_mark()
722parser.next()
723724
node = parser.repo.changelog.node(mark_to_rev(from_mark))
725parsed_refs[ref] = node
726727
def parse_tag(parser):
728name = parser[1]
729parser.next()
730from_mark = parser.get_mark()
731parser.next()
732tagger = parser.get_author()
733parser.next()
734data = parser.get_data()
735parser.next()
736737
parsed_tags[name] = (tagger, data)
738739
def write_tag(repo, tag, node, msg, author):
740branch = repo[node].branch()
741tip = branch_tip(repo, branch)
742tip = repo[tip]
743744
def getfilectx(repo, memctx, f):
745try:
746fctx = tip.filectx(f)
747data = fctx.data()
748except error.ManifestLookupError:
749data = ""
750content = data + "%s %s\n" % (hghex(node), tag)
751return context.memfilectx(f, content, False, False, None)
752753
p1 = tip.hex()
754p2 = '\0' * 20
755if not author:
756author = (None, 0, 0)
757user, date, tz = author
758759
ctx = context.memctx(repo, (p1, p2), msg,
760['.hgtags'], getfilectx,
761user, (date, tz), {'branch' : branch})
762763
tmp = encoding.encoding
764encoding.encoding = 'utf-8'
765766
tagnode = repo.commitctx(ctx)
767768
encoding.encoding = tmp
769770
return tagnode
771772
def do_export(parser):
773global parsed_refs, bmarks, peer
774775
p_bmarks = []
776777
parser.next()
778779
for line in parser.each_block('done'):
780if parser.check('blob'):
781parse_blob(parser)
782elif parser.check('commit'):
783parse_commit(parser)
784elif parser.check('reset'):
785parse_reset(parser)
786elif parser.check('tag'):
787parse_tag(parser)
788elif parser.check('feature'):
789pass
790else:
791die('unhandled export command: %s' % line)
792793
for ref, node in parsed_refs.iteritems():
794if ref.startswith('refs/heads/branches'):
795branch = ref[len('refs/heads/branches/'):]
796if branch in branches and node in branches[branch]:
797# up to date
798continue
799print "ok %s" % ref
800elif ref.startswith('refs/heads/'):
801bmark = ref[len('refs/heads/'):]
802p_bmarks.append((bmark, node))
803continue
804elif ref.startswith('refs/tags/'):
805tag = ref[len('refs/tags/'):]
806author, msg = parsed_tags.get(tag, (None, None))
807if mode == 'git':
808if not msg:
809msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
810write_tag(parser.repo, tag, node, msg, author)
811else:
812fp = parser.repo.opener('localtags', 'a')
813fp.write('%s %s\n' % (hghex(node), tag))
814fp.close()
815print "ok %s" % ref
816else:
817# transport-helper/fast-export bugs
818continue
819820
if peer:
821parser.repo.push(peer, force=force_push)
822823
# handle bookmarks
824for bmark, node in p_bmarks:
825ref = 'refs/heads/' + bmark
826new = hghex(node)
827828
if bmark in bmarks:
829old = bmarks[bmark].hex()
830else:
831old = ''
832833
if old == new:
834continue
835836
if bmark == 'master' and 'master' not in parser.repo._bookmarks:
837# fake bookmark
838pass
839elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
840# updated locally
841pass
842else:
843print "error %s" % ref
844continue
845846
if peer:
847rb = peer.listkeys('bookmarks')
848old = rb.get(bmark, '')
849if not peer.pushkey('bookmarks', bmark, old, new):
850print "error %s" % ref
851continue
852853
print "ok %s" % ref
854855
857
def fix_path(alias, repo, orig_url):
858url = urlparse.urlparse(orig_url, 'file')
859if url.scheme != 'file' or os.path.isabs(url.path):
860return
861abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
862cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
863subprocess.call(cmd)
864865
def main(args):
866global prefix, dirname, branches, bmarks
867global marks, blob_marks, parsed_refs
868global peer, mode, bad_mail, bad_name
869global track_branches, force_push, is_tmp
870global parsed_tags
871872
alias = args[1]
873url = args[2]
874peer = None
875876
hg_git_compat = False
877track_branches = True
878force_push = True
879880
try:
881if get_config('remote-hg.hg-git-compat') == 'true\n':
882hg_git_compat = True
883track_branches = False
884if get_config('remote-hg.track-branches') == 'false\n':
885track_branches = False
886if get_config('remote-hg.force-push') == 'false\n':
887force_push = False
888except subprocess.CalledProcessError:
889pass
890891
if hg_git_compat:
892mode = 'hg'
893bad_mail = 'none@none'
894bad_name = ''
895else:
896mode = 'git'
897bad_mail = 'unknown'
898bad_name = 'Unknown'
899900
if alias[4:] == url:
901is_tmp = True
902alias = util.sha1(alias).hexdigest()
903else:
904is_tmp = False
905906
gitdir = os.environ['GIT_DIR']
907dirname = os.path.join(gitdir, 'hg', alias)
908branches = {}
909bmarks = {}
910blob_marks = {}
911parsed_refs = {}
912marks = None
913parsed_tags = {}
914915
repo = get_repo(url, alias)
916prefix = 'refs/hg/%s' % alias
917918
if not is_tmp:
919fix_path(alias, peer or repo, url)
920921
if not os.path.exists(dirname):
922os.makedirs(dirname)
923924
marks_path = os.path.join(dirname, 'marks-hg')
925marks = Marks(marks_path)
926927
parser = Parser(repo)
928for line in parser:
929if parser.check('capabilities'):
930do_capabilities(parser)
931elif parser.check('list'):
932do_list(parser)
933elif parser.check('import'):
934do_import(parser)
935elif parser.check('export'):
936do_export(parser)
937else:
938die('unhandled command: %s' % line)
939sys.stdout.flush()
940941
def bye():
942if not marks:
943return
944if not is_tmp:
945marks.store()
946else:
947shutil.rmtree(dirname)
948949
atexit.register(bye)
950sys.exit(main(sys.argv))