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
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
if hg.islocal(url):
309repo = hg.repository(myui, url)
310else:
311local_path = os.path.join(dirname, 'clone')
312if not os.path.exists(local_path):
313try:
314peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True)
315except:
316die('Repository error')
317repo = dstpeer.local()
318else:
319repo = hg.repository(myui, local_path)
320try:
321peer = hg.peer(myui, {}, url)
322except:
323die('Repository error')
324repo.pull(peer, heads=None, force=True)
325326
return repo
327328
def rev_to_mark(rev):
329global marks
330return marks.from_rev(rev)
331332
def mark_to_rev(mark):
333global marks
334return marks.to_rev(mark)
335336
def export_ref(repo, name, kind, head):
337global prefix, marks, mode
338339
ename = '%s/%s' % (kind, name)
340tip = marks.get_tip(ename)
341342
# mercurial takes too much time checking this
343if tip and tip == head.rev():
344# nothing to do
345return
346revs = xrange(tip, head.rev() + 1)
347count = 0
348349
revs = [rev for rev in revs if not marks.is_marked(rev)]
350351
for rev in revs:
352353
c = repo[rev]
354(manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
355rev_branch = extra['branch']
356357
author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
358if 'committer' in extra:
359user, time, tz = extra['committer'].rsplit(' ', 2)
360committer = "%s %s %s" % (user, time, gittz(int(tz)))
361else:
362committer = author
363364
parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
365366
if len(parents) == 0:
367modified = c.manifest().keys()
368removed = []
369else:
370modified, removed = get_filechanges(repo, c, parents[0])
371372
desc += '\n'
373374
if mode == 'hg':
375extra_msg = ''
376377
if rev_branch != 'default':
378extra_msg += 'branch : %s\n' % rev_branch
379380
renames = []
381for f in c.files():
382if f not in c.manifest():
383continue
384rename = c.filectx(f).renamed()
385if rename:
386renames.append((rename[0], f))
387388
for e in renames:
389extra_msg += "rename : %s => %s\n" % e
390391
for key, value in extra.iteritems():
392if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
393continue
394else:
395extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
396397
if extra_msg:
398desc += '\n--HG--\n' + extra_msg
399400
if len(parents) == 0 and rev:
401print 'reset %s/%s' % (prefix, ename)
402403
print "commit %s/%s" % (prefix, ename)
404print "mark :%d" % (marks.get_mark(rev))
405print "author %s" % (author)
406print "committer %s" % (committer)
407print "data %d" % (len(desc))
408print desc
409410
if len(parents) > 0:
411print "from :%s" % (rev_to_mark(parents[0]))
412if len(parents) > 1:
413print "merge :%s" % (rev_to_mark(parents[1]))
414415
for f in modified:
416export_file(c.filectx(f))
417for f in removed:
418print "D %s" % (fix_file_path(f))
419421
count += 1
422if (count % 100 == 0):
423print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
424print "#############################################################"
425426
# make sure the ref is updated
427print "reset %s/%s" % (prefix, ename)
428print "from :%u" % rev_to_mark(rev)
429431
marks.set_tip(ename, rev)
432433
def export_tag(repo, tag):
434export_ref(repo, tag, 'tags', repo[tag])
435436
def export_bookmark(repo, bmark):
437head = bmarks[bmark]
438export_ref(repo, bmark, 'bookmarks', head)
439440
def export_branch(repo, branch):
441tip = get_branch_tip(repo, branch)
442head = repo[tip]
443export_ref(repo, branch, 'branches', head)
444445
def export_head(repo):
446global g_head
447export_ref(repo, g_head[0], 'bookmarks', g_head[1])
448449
def do_capabilities(parser):
450global prefix, dirname
451452
print "import"
453print "export"
454print "refspec refs/heads/branches/*:%s/branches/*" % prefix
455print "refspec refs/heads/*:%s/bookmarks/*" % prefix
456print "refspec refs/tags/*:%s/tags/*" % prefix
457458
path = os.path.join(dirname, 'marks-git')
459460
if os.path.exists(path):
461print "*import-marks %s" % path
462print "*export-marks %s" % path
463464
466
def branch_tip(repo, branch):
467# older versions of mercurial don't have this
468if hasattr(repo, 'branchtip'):
469return repo.branchtip(branch)
470else:
471return repo.branchtags()[branch]
472473
def get_branch_tip(repo, branch):
474global branches
475476
heads = branches.get(branch, None)
477if not heads:
478return None
479480
# verify there's only one head
481if (len(heads) > 1):
482warn("Branch '%s' has more than one head, consider merging" % branch)
483return branch_tip(repo, branch)
484485
return heads[0]
486487
def list_head(repo, cur):
488global g_head, bmarks
489490
head = bookmarks.readcurrent(repo)
491if head:
492node = repo[head]
493else:
494# fake bookmark from current branch
495head = cur
496node = repo['.']
497if not node:
498node = repo['tip']
499if not node:
500return
501if head == 'default':
502head = 'master'
503bmarks[head] = node
504505
print "@refs/heads/%s HEAD" % head
506g_head = (head, node)
507508
def do_list(parser):
509global branches, bmarks, mode, track_branches
510511
repo = parser.repo
512for bmark, node in bookmarks.listbookmarks(repo).iteritems():
513bmarks[bmark] = repo[node]
514515
cur = repo.dirstate.branch()
516517
list_head(repo, cur)
518519
if track_branches:
520for branch in repo.branchmap():
521heads = repo.branchheads(branch)
522if len(heads):
523branches[branch] = heads
524525
for branch in branches:
526print "? refs/heads/branches/%s" % branch
527528
for bmark in bmarks:
529print "? refs/heads/%s" % bmark
530531
for tag, node in repo.tagslist():
532if tag == 'tip':
533continue
534print "? refs/tags/%s" % tag
535536
538
def do_import(parser):
539repo = parser.repo
540541
path = os.path.join(dirname, 'marks-git')
542543
print "feature done"
544if os.path.exists(path):
545print "feature import-marks=%s" % path
546print "feature export-marks=%s" % path
547sys.stdout.flush()
548549
tmp = encoding.encoding
550encoding.encoding = 'utf-8'
551552
# lets get all the import lines
553while parser.check('import'):
554ref = parser[1]
555556
if (ref == 'HEAD'):
557export_head(repo)
558elif ref.startswith('refs/heads/branches/'):
559branch = ref[len('refs/heads/branches/'):]
560export_branch(repo, branch)
561elif ref.startswith('refs/heads/'):
562bmark = ref[len('refs/heads/'):]
563export_bookmark(repo, bmark)
564elif ref.startswith('refs/tags/'):
565tag = ref[len('refs/tags/'):]
566export_tag(repo, tag)
567568
parser.next()
569570
encoding.encoding = tmp
571572
print 'done'
573574
def parse_blob(parser):
575global blob_marks
576577
parser.next()
578mark = parser.get_mark()
579parser.next()
580data = parser.get_data()
581blob_marks[mark] = data
582parser.next()
583584
def get_merge_files(repo, p1, p2, files):
585for e in repo[p1].files():
586if e not in files:
587if e not in repo[p1].manifest():
588continue
589f = { 'ctx' : repo[p1][e] }
590files[e] = f
591592
def parse_commit(parser):
593global marks, blob_marks, parsed_refs
594global mode
595596
from_mark = merge_mark = None
597598
ref = parser[1]
599parser.next()
600601
commit_mark = parser.get_mark()
602parser.next()
603author = parser.get_author()
604parser.next()
605committer = parser.get_author()
606parser.next()
607data = parser.get_data()
608parser.next()
609if parser.check('from'):
610from_mark = parser.get_mark()
611parser.next()
612if parser.check('merge'):
613merge_mark = parser.get_mark()
614parser.next()
615if parser.check('merge'):
616die('octopus merges are not supported yet')
617618
files = {}
619620
for line in parser:
621if parser.check('M'):
622t, m, mark_ref, path = line.split(' ', 3)
623mark = int(mark_ref[1:])
624f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
625elif parser.check('D'):
626t, path = line.split(' ', 1)
627f = { 'deleted' : True }
628else:
629die('Unknown file command: %s' % line)
630files[path] = f
631632
def getfilectx(repo, memctx, f):
633of = files[f]
634if 'deleted' in of:
635raise IOError
636if 'ctx' in of:
637return of['ctx']
638is_exec = of['mode'] == 'x'
639is_link = of['mode'] == 'l'
640rename = of.get('rename', None)
641return context.memfilectx(f, of['data'],
642is_link, is_exec, rename)
643644
repo = parser.repo
645646
user, date, tz = author
647extra = {}
648649
if committer != author:
650extra['committer'] = "%s %u %u" % committer
651652
if from_mark:
653p1 = repo.changelog.node(mark_to_rev(from_mark))
654else:
655p1 = '\0' * 20
656657
if merge_mark:
658p2 = repo.changelog.node(mark_to_rev(merge_mark))
659else:
660p2 = '\0' * 20
661662
#
663# If files changed from any of the parents, hg wants to know, but in git if
664# nothing changed from the first parent, nothing changed.
665#
666if merge_mark:
667get_merge_files(repo, p1, p2, files)
668669
# Check if the ref is supposed to be a named branch
670if ref.startswith('refs/heads/branches/'):
671extra['branch'] = ref[len('refs/heads/branches/'):]
672673
if mode == 'hg':
674i = data.find('\n--HG--\n')
675if i >= 0:
676tmp = data[i + len('\n--HG--\n'):].strip()
677for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
678if k == 'rename':
679old, new = v.split(' => ', 1)
680files[new]['rename'] = old
681elif k == 'branch':
682extra[k] = v
683elif k == 'extra':
684ek, ev = v.split(' : ', 1)
685extra[ek] = urllib.unquote(ev)
686data = data[:i]
687688
ctx = context.memctx(repo, (p1, p2), data,
689files.keys(), getfilectx,
690user, (date, tz), extra)
691692
tmp = encoding.encoding
693encoding.encoding = 'utf-8'
694695
node = repo.commitctx(ctx)
696697
encoding.encoding = tmp
698699
rev = repo[node].rev()
700701
parsed_refs[ref] = node
702marks.new_mark(rev, commit_mark)
703704
def parse_reset(parser):
705global parsed_refs
706707
ref = parser[1]
708parser.next()
709# ugh
710if parser.check('commit'):
711parse_commit(parser)
712return
713if not parser.check('from'):
714return
715from_mark = parser.get_mark()
716parser.next()
717718
node = parser.repo.changelog.node(mark_to_rev(from_mark))
719parsed_refs[ref] = node
720721
def parse_tag(parser):
722name = parser[1]
723parser.next()
724from_mark = parser.get_mark()
725parser.next()
726tagger = parser.get_author()
727parser.next()
728data = parser.get_data()
729parser.next()
730731
parsed_tags[name] = (tagger, data)
732733
def write_tag(repo, tag, node, msg, author):
734branch = repo[node].branch()
735tip = branch_tip(repo, branch)
736tip = repo[tip]
737738
def getfilectx(repo, memctx, f):
739try:
740fctx = tip.filectx(f)
741data = fctx.data()
742except error.ManifestLookupError:
743data = ""
744content = data + "%s %s\n" % (hghex(node), tag)
745return context.memfilectx(f, content, False, False, None)
746747
p1 = tip.hex()
748p2 = '\0' * 20
749if not author:
750author = (None, 0, 0)
751user, date, tz = author
752753
ctx = context.memctx(repo, (p1, p2), msg,
754['.hgtags'], getfilectx,
755user, (date, tz), {'branch' : branch})
756757
tmp = encoding.encoding
758encoding.encoding = 'utf-8'
759760
tagnode = repo.commitctx(ctx)
761762
encoding.encoding = tmp
763764
return tagnode
765766
def do_export(parser):
767global parsed_refs, bmarks, peer
768769
p_bmarks = []
770771
parser.next()
772773
for line in parser.each_block('done'):
774if parser.check('blob'):
775parse_blob(parser)
776elif parser.check('commit'):
777parse_commit(parser)
778elif parser.check('reset'):
779parse_reset(parser)
780elif parser.check('tag'):
781parse_tag(parser)
782elif parser.check('feature'):
783pass
784else:
785die('unhandled export command: %s' % line)
786787
for ref, node in parsed_refs.iteritems():
788if ref.startswith('refs/heads/branches'):
789branch = ref[len('refs/heads/branches/'):]
790if branch in branches and node in branches[branch]:
791# up to date
792continue
793print "ok %s" % ref
794elif ref.startswith('refs/heads/'):
795bmark = ref[len('refs/heads/'):]
796p_bmarks.append((bmark, node))
797continue
798elif ref.startswith('refs/tags/'):
799tag = ref[len('refs/tags/'):]
800author, msg = parsed_tags.get(tag, (None, None))
801if mode == 'git':
802if not msg:
803msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
804write_tag(parser.repo, tag, node, msg, author)
805else:
806fp = parser.repo.opener('localtags', 'a')
807fp.write('%s %s\n' % (hghex(node), tag))
808fp.close()
809print "ok %s" % ref
810else:
811# transport-helper/fast-export bugs
812continue
813814
if peer:
815parser.repo.push(peer, force=force_push)
816817
# handle bookmarks
818for bmark, node in p_bmarks:
819ref = 'refs/heads/' + bmark
820new = hghex(node)
821822
if bmark in bmarks:
823old = bmarks[bmark].hex()
824else:
825old = ''
826827
if bmark == 'master' and 'master' not in parser.repo._bookmarks:
828# fake bookmark
829pass
830elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
831# updated locally
832pass
833else:
834print "error %s" % ref
835continue
836837
if peer:
838rb = peer.listkeys('bookmarks')
839old = rb.get(bmark, '')
840if not peer.pushkey('bookmarks', bmark, old, new):
841print "error %s" % ref
842continue
843844
print "ok %s" % ref
845846
848
def fix_path(alias, repo, orig_url):
849url = urlparse.urlparse(orig_url, 'file')
850if url.scheme != 'file' or os.path.isabs(url.path):
851return
852abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
853cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
854subprocess.call(cmd)
855856
def main(args):
857global prefix, dirname, branches, bmarks
858global marks, blob_marks, parsed_refs
859global peer, mode, bad_mail, bad_name
860global track_branches, force_push, is_tmp
861global parsed_tags
862863
alias = args[1]
864url = args[2]
865peer = None
866867
hg_git_compat = False
868track_branches = True
869force_push = True
870871
try:
872if get_config('remote-hg.hg-git-compat') == 'true\n':
873hg_git_compat = True
874track_branches = False
875if get_config('remote-hg.track-branches') == 'false\n':
876track_branches = False
877if get_config('remote-hg.force-push') == 'false\n':
878force_push = False
879except subprocess.CalledProcessError:
880pass
881882
if hg_git_compat:
883mode = 'hg'
884bad_mail = 'none@none'
885bad_name = ''
886else:
887mode = 'git'
888bad_mail = 'unknown'
889bad_name = 'Unknown'
890891
if alias[4:] == url:
892is_tmp = True
893alias = util.sha1(alias).hexdigest()
894else:
895is_tmp = False
896897
gitdir = os.environ['GIT_DIR']
898dirname = os.path.join(gitdir, 'hg', alias)
899branches = {}
900bmarks = {}
901blob_marks = {}
902parsed_refs = {}
903marks = None
904parsed_tags = {}
905906
repo = get_repo(url, alias)
907prefix = 'refs/hg/%s' % alias
908909
if not is_tmp:
910fix_path(alias, peer or repo, url)
911912
if not os.path.exists(dirname):
913os.makedirs(dirname)
914915
marks_path = os.path.join(dirname, 'marks-hg')
916marks = Marks(marks_path)
917918
parser = Parser(repo)
919for line in parser:
920if parser.check('capabilities'):
921do_capabilities(parser)
922elif parser.check('list'):
923do_list(parser)
924elif parser.check('import'):
925do_import(parser)
926elif parser.check('export'):
927do_export(parser)
928else:
929die('unhandled command: %s' % line)
930sys.stdout.flush()
931932
def bye():
933if not marks:
934return
935if not is_tmp:
936marks.store()
937else:
938shutil.rmtree(dirname)
939940
atexit.register(bye)
941sys.exit(main(sys.argv))