remote-bzr: delay peer branch usage
[git/git.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))