Update draft release notes to 2.0
[git.git] / contrib / remote-helpers / git-remote-bzr
blob5f4b2e3e160f60955e25f8b8fedf8c122dd5c73c
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 to track (per repo):
17 # % git config remote.origin.bzr-branches 'trunk, devel, test'
19 # Where 'origin' is the name of the repository you want to specify the
20 # branches.
23 import sys
25 import bzrlib
26 if hasattr(bzrlib, "initialize"):
27 bzrlib.initialize()
29 import bzrlib.plugin
30 bzrlib.plugin.load_plugins()
32 import bzrlib.generate_ids
33 import bzrlib.transport
34 import bzrlib.errors
35 import bzrlib.ui
36 import bzrlib.urlutils
37 import bzrlib.branch
39 import sys
40 import os
41 import json
42 import re
43 import StringIO
44 import atexit, shutil, hashlib, urlparse, subprocess
46 NAME_RE = re.compile('^([^<>]+)')
47 AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
48 EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
49 RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
51 def die(msg, *args):
52 sys.stderr.write('ERROR: %s\n' % (msg % args))
53 sys.exit(1)
55 def warn(msg, *args):
56 sys.stderr.write('WARNING: %s\n' % (msg % args))
58 def gittz(tz):
59 return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
61 def get_config(config):
62 cmd = ['git', 'config', '--get', config]
63 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
64 output, _ = process.communicate()
65 return output
67 class Marks:
69 def __init__(self, path):
70 self.path = path
71 self.tips = {}
72 self.marks = {}
73 self.rev_marks = {}
74 self.last_mark = 0
75 self.load()
77 def load(self):
78 if not os.path.exists(self.path):
79 return
81 tmp = json.load(open(self.path))
82 self.tips = tmp['tips']
83 self.marks = tmp['marks']
84 self.last_mark = tmp['last-mark']
86 for rev, mark in self.marks.iteritems():
87 self.rev_marks[mark] = rev
89 def dict(self):
90 return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
92 def store(self):
93 json.dump(self.dict(), open(self.path, 'w'))
95 def __str__(self):
96 return str(self.dict())
98 def from_rev(self, rev):
99 return self.marks[rev]
101 def to_rev(self, mark):
102 return str(self.rev_marks[mark])
104 def next_mark(self):
105 self.last_mark += 1
106 return self.last_mark
108 def get_mark(self, rev):
109 self.last_mark += 1
110 self.marks[rev] = self.last_mark
111 return self.last_mark
113 def is_marked(self, rev):
114 return rev in self.marks
116 def new_mark(self, rev, mark):
117 self.marks[rev] = mark
118 self.rev_marks[mark] = rev
119 self.last_mark = mark
121 def get_tip(self, branch):
122 try:
123 return str(self.tips[branch])
124 except KeyError:
125 return None
127 def set_tip(self, branch, tip):
128 self.tips[branch] = tip
130 class Parser:
132 def __init__(self, repo):
133 self.repo = repo
134 self.line = self.get_line()
136 def get_line(self):
137 return sys.stdin.readline().strip()
139 def __getitem__(self, i):
140 return self.line.split()[i]
142 def check(self, word):
143 return self.line.startswith(word)
145 def each_block(self, separator):
146 while self.line != separator:
147 yield self.line
148 self.line = self.get_line()
150 def __iter__(self):
151 return self.each_block('')
153 def next(self):
154 self.line = self.get_line()
155 if self.line == 'done':
156 self.line = None
158 def get_mark(self):
159 i = self.line.index(':') + 1
160 return int(self.line[i:])
162 def get_data(self):
163 if not self.check('data'):
164 return None
165 i = self.line.index(' ') + 1
166 size = int(self.line[i:])
167 return sys.stdin.read(size)
169 def get_author(self):
170 m = RAW_AUTHOR_RE.match(self.line)
171 if not m:
172 return None
173 _, name, email, date, tz = m.groups()
174 name = name.decode('utf-8')
175 committer = '%s <%s>' % (name, email)
176 tz = int(tz)
177 tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
178 return (committer, int(date), tz)
180 def rev_to_mark(rev):
181 return marks.from_rev(rev)
183 def mark_to_rev(mark):
184 return marks.to_rev(mark)
186 def fixup_user(user):
187 name = mail = None
188 user = user.replace('"', '')
189 m = AUTHOR_RE.match(user)
190 if m:
191 name = m.group(1)
192 mail = m.group(2).strip()
193 else:
194 m = EMAIL_RE.match(user)
195 if m:
196 mail = m.group(1)
197 else:
198 m = NAME_RE.match(user)
199 if m:
200 name = m.group(1).strip()
202 if not name:
203 name = 'unknown'
204 if not mail:
205 mail = 'Unknown'
207 return '%s <%s>' % (name, mail)
209 def get_filechanges(cur, prev):
210 modified = {}
211 removed = {}
213 changes = cur.changes_from(prev)
215 def u(s):
216 return s.encode('utf-8')
218 for path, fid, kind in changes.added:
219 modified[u(path)] = fid
220 for path, fid, kind in changes.removed:
221 removed[u(path)] = None
222 for path, fid, kind, mod, _ in changes.modified:
223 modified[u(path)] = fid
224 for oldpath, newpath, fid, kind, mod, _ in changes.renamed:
225 removed[u(oldpath)] = None
226 if kind == 'directory':
227 lst = cur.list_files(from_dir=newpath, recursive=True)
228 for path, file_class, kind, fid, entry in lst:
229 if kind != 'directory':
230 modified[u(newpath + '/' + path)] = fid
231 else:
232 modified[u(newpath)] = fid
234 return modified, removed
236 def export_files(tree, files):
237 final = []
238 for path, fid in files.iteritems():
239 kind = tree.kind(fid)
241 h = tree.get_file_sha1(fid)
243 if kind == 'symlink':
244 d = tree.get_symlink_target(fid)
245 mode = '120000'
246 elif kind == 'file':
248 if tree.is_executable(fid):
249 mode = '100755'
250 else:
251 mode = '100644'
253 # is the blob already exported?
254 if h in filenodes:
255 mark = filenodes[h]
256 final.append((mode, mark, path))
257 continue
259 d = tree.get_file_text(fid)
260 elif kind == 'directory':
261 continue
262 else:
263 die("Unhandled kind '%s' for path '%s'" % (kind, path))
265 mark = marks.next_mark()
266 filenodes[h] = mark
268 print "blob"
269 print "mark :%u" % mark
270 print "data %d" % len(d)
271 print d
273 final.append((mode, mark, path))
275 return final
277 def export_branch(repo, name):
278 ref = '%s/heads/%s' % (prefix, name)
279 tip = marks.get_tip(name)
281 branch = get_remote_branch(name)
282 repo = branch.repository
284 branch.lock_read()
285 revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
286 try:
287 tip_revno = branch.revision_id_to_revno(tip)
288 last_revno, _ = branch.last_revision_info()
289 total = last_revno - tip_revno
290 except bzrlib.errors.NoSuchRevision:
291 tip_revno = 0
292 total = 0
294 for revid, _, seq, _ in revs:
296 if marks.is_marked(revid):
297 continue
299 rev = repo.get_revision(revid)
300 revno = seq[0]
302 parents = rev.parent_ids
303 time = rev.timestamp
304 tz = rev.timezone
305 committer = rev.committer.encode('utf-8')
306 committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
307 authors = rev.get_apparent_authors()
308 if authors:
309 author = authors[0].encode('utf-8')
310 author = "%s %u %s" % (fixup_user(author), time, gittz(tz))
311 else:
312 author = committer
313 msg = rev.message.encode('utf-8')
315 msg += '\n'
317 if len(parents) == 0:
318 parent = bzrlib.revision.NULL_REVISION
319 else:
320 parent = parents[0]
322 cur_tree = repo.revision_tree(revid)
323 prev = repo.revision_tree(parent)
324 modified, removed = get_filechanges(cur_tree, prev)
326 modified_final = export_files(cur_tree, modified)
328 if len(parents) == 0:
329 print 'reset %s' % ref
331 print "commit %s" % ref
332 print "mark :%d" % (marks.get_mark(revid))
333 print "author %s" % (author)
334 print "committer %s" % (committer)
335 print "data %d" % (len(msg))
336 print msg
338 for i, p in enumerate(parents):
339 try:
340 m = rev_to_mark(p)
341 except KeyError:
342 # ghost?
343 continue
344 if i == 0:
345 print "from :%s" % m
346 else:
347 print "merge :%s" % m
349 for f in removed:
350 print "D %s" % (f,)
351 for f in modified_final:
352 print "M %s :%u %s" % f
353 print
355 if len(seq) > 1:
356 # let's skip branch revisions from the progress report
357 continue
359 progress = (revno - tip_revno)
360 if (progress % 100 == 0):
361 if total:
362 print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total)
363 else:
364 print "progress revision %d '%s' (%d)" % (revno, name, progress)
366 branch.unlock()
368 revid = branch.last_revision()
370 # make sure the ref is updated
371 print "reset %s" % ref
372 print "from :%u" % rev_to_mark(revid)
373 print
375 marks.set_tip(name, revid)
377 def export_tag(repo, name):
378 ref = '%s/tags/%s' % (prefix, name)
379 print "reset %s" % ref
380 print "from :%u" % rev_to_mark(tags[name])
381 print
383 def do_import(parser):
384 repo = parser.repo
385 path = os.path.join(dirname, 'marks-git')
387 print "feature done"
388 if os.path.exists(path):
389 print "feature import-marks=%s" % path
390 print "feature export-marks=%s" % path
391 print "feature force"
392 sys.stdout.flush()
394 while parser.check('import'):
395 ref = parser[1]
396 if ref.startswith('refs/heads/'):
397 name = ref[len('refs/heads/'):]
398 export_branch(repo, name)
399 if ref.startswith('refs/tags/'):
400 name = ref[len('refs/tags/'):]
401 export_tag(repo, name)
402 parser.next()
404 print 'done'
406 sys.stdout.flush()
408 def parse_blob(parser):
409 parser.next()
410 mark = parser.get_mark()
411 parser.next()
412 data = parser.get_data()
413 blob_marks[mark] = data
414 parser.next()
416 class CustomTree():
418 def __init__(self, branch, revid, parents, files):
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 parents = []
577 ref = parser[1]
578 parser.next()
580 if ref.startswith('refs/heads/'):
581 name = ref[len('refs/heads/'):]
582 branch = get_remote_branch(name)
583 else:
584 die('unknown ref')
586 commit_mark = parser.get_mark()
587 parser.next()
588 author = parser.get_author()
589 parser.next()
590 committer = parser.get_author()
591 parser.next()
592 data = parser.get_data()
593 parser.next()
594 if parser.check('from'):
595 parents.append(parser.get_mark())
596 parser.next()
597 while parser.check('merge'):
598 parents.append(parser.get_mark())
599 parser.next()
601 # fast-export adds an extra newline
602 if data[-1] == '\n':
603 data = data[:-1]
605 files = {}
607 for line in parser:
608 if parser.check('M'):
609 t, m, mark_ref, path = line.split(' ', 3)
610 mark = int(mark_ref[1:])
611 f = { 'mode' : m, 'mark' : mark }
612 elif parser.check('D'):
613 t, path = line.split(' ', 1)
614 f = { 'deleted' : True }
615 else:
616 die('Unknown file command: %s' % line)
617 path = c_style_unescape(path).decode('utf-8')
618 files[path] = f
620 committer, date, tz = committer
621 parents = [mark_to_rev(p) for p in parents]
622 revid = bzrlib.generate_ids.gen_revision_id(committer, date)
623 props = {}
624 props['branch-nick'] = branch.nick
626 mtree = CustomTree(branch, revid, parents, files)
627 changes = mtree.iter_changes()
629 branch.lock_write()
630 try:
631 builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid)
632 try:
633 list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
634 builder.finish_inventory()
635 builder.commit(data.decode('utf-8', 'replace'))
636 except Exception, e:
637 builder.abort()
638 raise
639 finally:
640 branch.unlock()
642 parsed_refs[ref] = revid
643 marks.new_mark(revid, commit_mark)
645 def parse_reset(parser):
646 ref = parser[1]
647 parser.next()
649 # ugh
650 if parser.check('commit'):
651 parse_commit(parser)
652 return
653 if not parser.check('from'):
654 return
655 from_mark = parser.get_mark()
656 parser.next()
658 parsed_refs[ref] = mark_to_rev(from_mark)
660 def do_export(parser):
661 parser.next()
663 for line in parser.each_block('done'):
664 if parser.check('blob'):
665 parse_blob(parser)
666 elif parser.check('commit'):
667 parse_commit(parser)
668 elif parser.check('reset'):
669 parse_reset(parser)
670 elif parser.check('tag'):
671 pass
672 elif parser.check('feature'):
673 pass
674 else:
675 die('unhandled export command: %s' % line)
677 for ref, revid in parsed_refs.iteritems():
678 if ref.startswith('refs/heads/'):
679 name = ref[len('refs/heads/'):]
680 branch = get_remote_branch(name)
681 branch.generate_revision_history(revid, marks.get_tip(name))
683 if name in peers:
684 peer = bzrlib.branch.Branch.open(peers[name],
685 possible_transports=transports)
686 try:
687 peer.bzrdir.push_branch(branch, revision_id=revid,
688 overwrite=force)
689 except bzrlib.errors.DivergedBranches:
690 print "error %s non-fast forward" % ref
691 continue
693 try:
694 wt = branch.bzrdir.open_workingtree()
695 wt.update()
696 except bzrlib.errors.NoWorkingTree:
697 pass
698 elif ref.startswith('refs/tags/'):
699 # TODO: implement tag push
700 print "error %s pushing tags not supported" % ref
701 continue
702 else:
703 # transport-helper/fast-export bugs
704 continue
706 print "ok %s" % ref
708 print
710 def do_capabilities(parser):
711 print "import"
712 print "export"
713 print "refspec refs/heads/*:%s/heads/*" % prefix
714 print "refspec refs/tags/*:%s/tags/*" % prefix
716 path = os.path.join(dirname, 'marks-git')
718 if os.path.exists(path):
719 print "*import-marks %s" % path
720 print "*export-marks %s" % path
722 print "option"
723 print
725 class InvalidOptionValue(Exception):
726 pass
728 def get_bool_option(val):
729 if val == 'true':
730 return True
731 elif val == 'false':
732 return False
733 else:
734 raise InvalidOptionValue()
736 def do_option(parser):
737 global force
738 opt, val = parser[1:3]
739 try:
740 if opt == 'force':
741 force = get_bool_option(val)
742 print 'ok'
743 else:
744 print 'unsupported'
745 except InvalidOptionValue:
746 print "error '%s' is not a valid value for option '%s'" % (val, opt)
748 def ref_is_valid(name):
749 return not True in [c in name for c in '~^: \\']
751 def do_list(parser):
752 master_branch = None
754 for name in branches:
755 if not master_branch:
756 master_branch = name
757 print "? refs/heads/%s" % name
759 branch = get_remote_branch(master_branch)
760 branch.lock_read()
761 for tag, revid in branch.tags.get_tag_dict().items():
762 try:
763 branch.revision_id_to_dotted_revno(revid)
764 except bzrlib.errors.NoSuchRevision:
765 continue
766 if not ref_is_valid(tag):
767 continue
768 print "? refs/tags/%s" % tag
769 tags[tag] = revid
770 branch.unlock()
772 print "@refs/heads/%s HEAD" % master_branch
773 print
775 def clone(path, remote_branch):
776 try:
777 bdir = bzrlib.bzrdir.BzrDir.create(path, possible_transports=transports)
778 except bzrlib.errors.AlreadyControlDirError:
779 bdir = bzrlib.bzrdir.BzrDir.open(path, possible_transports=transports)
780 repo = bdir.find_repository()
781 repo.fetch(remote_branch.repository)
782 return remote_branch.sprout(bdir, repository=repo)
784 def get_remote_branch(name):
785 remote_branch = bzrlib.branch.Branch.open(branches[name],
786 possible_transports=transports)
787 if isinstance(remote_branch.user_transport, bzrlib.transport.local.LocalTransport):
788 return remote_branch
790 branch_path = os.path.join(dirname, 'clone', name)
792 try:
793 branch = bzrlib.branch.Branch.open(branch_path,
794 possible_transports=transports)
795 except bzrlib.errors.NotBranchError:
796 # clone
797 branch = clone(branch_path, remote_branch)
798 else:
799 # pull
800 try:
801 branch.pull(remote_branch, overwrite=True)
802 except bzrlib.errors.DivergedBranches:
803 # use remote branch for now
804 return remote_branch
806 return branch
808 def find_branches(repo):
809 transport = repo.bzrdir.root_transport
811 for fn in transport.iter_files_recursive():
812 if not fn.endswith('.bzr/branch-format'):
813 continue
815 name = subdir = fn[:-len('/.bzr/branch-format')]
816 name = name if name != '' else 'master'
817 name = name.replace('/', '+')
819 try:
820 cur = transport.clone(subdir)
821 branch = bzrlib.branch.Branch.open_from_transport(cur)
822 except bzrlib.errors.NotBranchError:
823 continue
824 else:
825 yield name, branch.base
827 def get_repo(url, alias):
828 normal_url = bzrlib.urlutils.normalize_url(url)
829 origin = bzrlib.bzrdir.BzrDir.open(url, possible_transports=transports)
830 is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
832 shared_path = os.path.join(gitdir, 'bzr')
833 try:
834 shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path,
835 possible_transports=transports)
836 except bzrlib.errors.NotBranchError:
837 shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path,
838 possible_transports=transports)
839 try:
840 shared_repo = shared_dir.open_repository()
841 except bzrlib.errors.NoRepositoryPresent:
842 shared_repo = shared_dir.create_repository(shared=True)
844 if not is_local:
845 clone_path = os.path.join(dirname, 'clone')
846 if not os.path.exists(clone_path):
847 os.mkdir(clone_path)
848 else:
849 # check and remove old organization
850 try:
851 bdir = bzrlib.bzrdir.BzrDir.open(clone_path,
852 possible_transports=transports)
853 bdir.destroy_repository()
854 except bzrlib.errors.NotBranchError:
855 pass
856 except bzrlib.errors.NoRepositoryPresent:
857 pass
859 wanted = get_config('remote.%s.bzr-branches' % alias).rstrip().split(', ')
860 # stupid python
861 wanted = [e for e in wanted if e]
862 if not wanted:
863 wanted = get_config('remote-bzr.branches').rstrip().split(', ')
864 # stupid python
865 wanted = [e for e in wanted if e]
867 if not wanted:
868 try:
869 repo = origin.open_repository()
870 if not repo.user_transport.listable():
871 # this repository is not usable for us
872 raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
873 except bzrlib.errors.NoRepositoryPresent:
874 wanted = ['master']
876 if wanted:
877 def list_wanted(url, wanted):
878 for name in wanted:
879 subdir = name if name != 'master' else ''
880 yield name, bzrlib.urlutils.join(url, subdir)
882 branch_list = list_wanted(url, wanted)
883 else:
884 branch_list = find_branches(repo)
886 for name, url in branch_list:
887 if not is_local:
888 peers[name] = url
889 branches[name] = url
891 return origin
893 def fix_path(alias, orig_url):
894 url = urlparse.urlparse(orig_url, 'file')
895 if url.scheme != 'file' or os.path.isabs(url.path):
896 return
897 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
898 cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
899 subprocess.call(cmd)
901 def main(args):
902 global marks, prefix, gitdir, dirname
903 global tags, filenodes
904 global blob_marks
905 global parsed_refs
906 global files_cache
907 global is_tmp
908 global branches, peers
909 global transports
910 global force
912 marks = None
913 is_tmp = False
914 gitdir = os.environ.get('GIT_DIR', None)
916 if len(args) < 3:
917 die('Not enough arguments.')
919 if not gitdir:
920 die('GIT_DIR not set')
922 alias = args[1]
923 url = args[2]
925 tags = {}
926 filenodes = {}
927 blob_marks = {}
928 parsed_refs = {}
929 files_cache = {}
930 branches = {}
931 peers = {}
932 transports = []
933 force = False
935 if alias[5:] == url:
936 is_tmp = True
937 alias = hashlib.sha1(alias).hexdigest()
939 prefix = 'refs/bzr/%s' % alias
940 dirname = os.path.join(gitdir, 'bzr', alias)
942 if not is_tmp:
943 fix_path(alias, url)
945 if not os.path.exists(dirname):
946 os.makedirs(dirname)
948 if hasattr(bzrlib.ui.ui_factory, 'be_quiet'):
949 bzrlib.ui.ui_factory.be_quiet(True)
951 repo = get_repo(url, alias)
953 marks_path = os.path.join(dirname, 'marks-int')
954 marks = Marks(marks_path)
956 parser = Parser(repo)
957 for line in parser:
958 if parser.check('capabilities'):
959 do_capabilities(parser)
960 elif parser.check('list'):
961 do_list(parser)
962 elif parser.check('import'):
963 do_import(parser)
964 elif parser.check('export'):
965 do_export(parser)
966 elif parser.check('option'):
967 do_option(parser)
968 else:
969 die('unhandled command: %s' % line)
970 sys.stdout.flush()
972 def bye():
973 if not marks:
974 return
975 if not is_tmp:
976 marks.store()
977 else:
978 shutil.rmtree(dirname)
980 atexit.register(bye)
981 sys.exit(main(sys.argv))