contrib: remote-helpers: add move warnings (v2.0)
[git.git] / contrib / remote-helpers / git-remote-bzr
blobbe4b9a34edc68b7112c925e6023d61805fffb91e
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 sys.stderr.write('WARNING: git-remote-bzr is now maintained independently.\n')
47 sys.stderr.write('WARNING: For more information visit https://github.com/felipec/git-remote-bzr\n')
49 NAME_RE = re.compile('^([^<>]+)')
50 AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
51 EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
52 RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
54 def die(msg, *args):
55 sys.stderr.write('ERROR: %s\n' % (msg % args))
56 sys.exit(1)
58 def warn(msg, *args):
59 sys.stderr.write('WARNING: %s\n' % (msg % args))
61 def gittz(tz):
62 return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
64 def get_config(config):
65 cmd = ['git', 'config', '--get', config]
66 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
67 output, _ = process.communicate()
68 return output
70 class Marks:
72 def __init__(self, path):
73 self.path = path
74 self.tips = {}
75 self.marks = {}
76 self.rev_marks = {}
77 self.last_mark = 0
78 self.load()
80 def load(self):
81 if not os.path.exists(self.path):
82 return
84 tmp = json.load(open(self.path))
85 self.tips = tmp['tips']
86 self.marks = tmp['marks']
87 self.last_mark = tmp['last-mark']
89 for rev, mark in self.marks.iteritems():
90 self.rev_marks[mark] = rev
92 def dict(self):
93 return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
95 def store(self):
96 json.dump(self.dict(), open(self.path, 'w'))
98 def __str__(self):
99 return str(self.dict())
101 def from_rev(self, rev):
102 return self.marks[rev]
104 def to_rev(self, mark):
105 return str(self.rev_marks[mark])
107 def next_mark(self):
108 self.last_mark += 1
109 return self.last_mark
111 def get_mark(self, rev):
112 self.last_mark += 1
113 self.marks[rev] = self.last_mark
114 return self.last_mark
116 def is_marked(self, rev):
117 return rev in self.marks
119 def new_mark(self, rev, mark):
120 self.marks[rev] = mark
121 self.rev_marks[mark] = rev
122 self.last_mark = mark
124 def get_tip(self, branch):
125 try:
126 return str(self.tips[branch])
127 except KeyError:
128 return None
130 def set_tip(self, branch, tip):
131 self.tips[branch] = tip
133 class Parser:
135 def __init__(self, repo):
136 self.repo = repo
137 self.line = self.get_line()
139 def get_line(self):
140 return sys.stdin.readline().strip()
142 def __getitem__(self, i):
143 return self.line.split()[i]
145 def check(self, word):
146 return self.line.startswith(word)
148 def each_block(self, separator):
149 while self.line != separator:
150 yield self.line
151 self.line = self.get_line()
153 def __iter__(self):
154 return self.each_block('')
156 def next(self):
157 self.line = self.get_line()
158 if self.line == 'done':
159 self.line = None
161 def get_mark(self):
162 i = self.line.index(':') + 1
163 return int(self.line[i:])
165 def get_data(self):
166 if not self.check('data'):
167 return None
168 i = self.line.index(' ') + 1
169 size = int(self.line[i:])
170 return sys.stdin.read(size)
172 def get_author(self):
173 m = RAW_AUTHOR_RE.match(self.line)
174 if not m:
175 return None
176 _, name, email, date, tz = m.groups()
177 name = name.decode('utf-8')
178 committer = '%s <%s>' % (name, email)
179 tz = int(tz)
180 tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
181 return (committer, int(date), tz)
183 def rev_to_mark(rev):
184 return marks.from_rev(rev)
186 def mark_to_rev(mark):
187 return marks.to_rev(mark)
189 def fixup_user(user):
190 name = mail = None
191 user = user.replace('"', '')
192 m = AUTHOR_RE.match(user)
193 if m:
194 name = m.group(1)
195 mail = m.group(2).strip()
196 else:
197 m = EMAIL_RE.match(user)
198 if m:
199 mail = m.group(1)
200 else:
201 m = NAME_RE.match(user)
202 if m:
203 name = m.group(1).strip()
205 if not name:
206 name = 'unknown'
207 if not mail:
208 mail = 'Unknown'
210 return '%s <%s>' % (name, mail)
212 def get_filechanges(cur, prev):
213 modified = {}
214 removed = {}
216 changes = cur.changes_from(prev)
218 def u(s):
219 return s.encode('utf-8')
221 for path, fid, kind in changes.added:
222 modified[u(path)] = fid
223 for path, fid, kind in changes.removed:
224 removed[u(path)] = None
225 for path, fid, kind, mod, _ in changes.modified:
226 modified[u(path)] = fid
227 for oldpath, newpath, fid, kind, mod, _ in changes.renamed:
228 removed[u(oldpath)] = None
229 if kind == 'directory':
230 lst = cur.list_files(from_dir=newpath, recursive=True)
231 for path, file_class, kind, fid, entry in lst:
232 if kind != 'directory':
233 modified[u(newpath + '/' + path)] = fid
234 else:
235 modified[u(newpath)] = fid
237 return modified, removed
239 def export_files(tree, files):
240 final = []
241 for path, fid in files.iteritems():
242 kind = tree.kind(fid)
244 h = tree.get_file_sha1(fid)
246 if kind == 'symlink':
247 d = tree.get_symlink_target(fid)
248 mode = '120000'
249 elif kind == 'file':
251 if tree.is_executable(fid):
252 mode = '100755'
253 else:
254 mode = '100644'
256 # is the blob already exported?
257 if h in filenodes:
258 mark = filenodes[h]
259 final.append((mode, mark, path))
260 continue
262 d = tree.get_file_text(fid)
263 elif kind == 'directory':
264 continue
265 else:
266 die("Unhandled kind '%s' for path '%s'" % (kind, path))
268 mark = marks.next_mark()
269 filenodes[h] = mark
271 print "blob"
272 print "mark :%u" % mark
273 print "data %d" % len(d)
274 print d
276 final.append((mode, mark, path))
278 return final
280 def export_branch(repo, name):
281 ref = '%s/heads/%s' % (prefix, name)
282 tip = marks.get_tip(name)
284 branch = get_remote_branch(name)
285 repo = branch.repository
287 branch.lock_read()
288 revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
289 try:
290 tip_revno = branch.revision_id_to_revno(tip)
291 last_revno, _ = branch.last_revision_info()
292 total = last_revno - tip_revno
293 except bzrlib.errors.NoSuchRevision:
294 tip_revno = 0
295 total = 0
297 for revid, _, seq, _ in revs:
299 if marks.is_marked(revid):
300 continue
302 rev = repo.get_revision(revid)
303 revno = seq[0]
305 parents = rev.parent_ids
306 time = rev.timestamp
307 tz = rev.timezone
308 committer = rev.committer.encode('utf-8')
309 committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
310 authors = rev.get_apparent_authors()
311 if authors:
312 author = authors[0].encode('utf-8')
313 author = "%s %u %s" % (fixup_user(author), time, gittz(tz))
314 else:
315 author = committer
316 msg = rev.message.encode('utf-8')
318 msg += '\n'
320 if len(parents) == 0:
321 parent = bzrlib.revision.NULL_REVISION
322 else:
323 parent = parents[0]
325 cur_tree = repo.revision_tree(revid)
326 prev = repo.revision_tree(parent)
327 modified, removed = get_filechanges(cur_tree, prev)
329 modified_final = export_files(cur_tree, modified)
331 if len(parents) == 0:
332 print 'reset %s' % ref
334 print "commit %s" % ref
335 print "mark :%d" % (marks.get_mark(revid))
336 print "author %s" % (author)
337 print "committer %s" % (committer)
338 print "data %d" % (len(msg))
339 print msg
341 for i, p in enumerate(parents):
342 try:
343 m = rev_to_mark(p)
344 except KeyError:
345 # ghost?
346 continue
347 if i == 0:
348 print "from :%s" % m
349 else:
350 print "merge :%s" % m
352 for f in removed:
353 print "D %s" % (f,)
354 for f in modified_final:
355 print "M %s :%u %s" % f
356 print
358 if len(seq) > 1:
359 # let's skip branch revisions from the progress report
360 continue
362 progress = (revno - tip_revno)
363 if (progress % 100 == 0):
364 if total:
365 print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total)
366 else:
367 print "progress revision %d '%s' (%d)" % (revno, name, progress)
369 branch.unlock()
371 revid = branch.last_revision()
373 # make sure the ref is updated
374 print "reset %s" % ref
375 print "from :%u" % rev_to_mark(revid)
376 print
378 marks.set_tip(name, revid)
380 def export_tag(repo, name):
381 ref = '%s/tags/%s' % (prefix, name)
382 print "reset %s" % ref
383 print "from :%u" % rev_to_mark(tags[name])
384 print
386 def do_import(parser):
387 repo = parser.repo
388 path = os.path.join(dirname, 'marks-git')
390 print "feature done"
391 if os.path.exists(path):
392 print "feature import-marks=%s" % path
393 print "feature export-marks=%s" % path
394 print "feature force"
395 sys.stdout.flush()
397 while parser.check('import'):
398 ref = parser[1]
399 if ref.startswith('refs/heads/'):
400 name = ref[len('refs/heads/'):]
401 export_branch(repo, name)
402 if ref.startswith('refs/tags/'):
403 name = ref[len('refs/tags/'):]
404 export_tag(repo, name)
405 parser.next()
407 print 'done'
409 sys.stdout.flush()
411 def parse_blob(parser):
412 parser.next()
413 mark = parser.get_mark()
414 parser.next()
415 data = parser.get_data()
416 blob_marks[mark] = data
417 parser.next()
419 class CustomTree():
421 def __init__(self, branch, revid, parents, files):
422 self.updates = {}
423 self.branch = branch
425 def copy_tree(revid):
426 files = files_cache[revid] = {}
427 branch.lock_read()
428 tree = branch.repository.revision_tree(revid)
429 try:
430 for path, entry in tree.iter_entries_by_dir():
431 files[path] = [entry.file_id, None]
432 finally:
433 branch.unlock()
434 return files
436 if len(parents) == 0:
437 self.base_id = bzrlib.revision.NULL_REVISION
438 self.base_files = {}
439 else:
440 self.base_id = parents[0]
441 self.base_files = files_cache.get(self.base_id, None)
442 if not self.base_files:
443 self.base_files = copy_tree(self.base_id)
445 self.files = files_cache[revid] = self.base_files.copy()
446 self.rev_files = {}
448 for path, data in self.files.iteritems():
449 fid, mark = data
450 self.rev_files[fid] = [path, mark]
452 for path, f in files.iteritems():
453 fid, mark = self.files.get(path, [None, None])
454 if not fid:
455 fid = bzrlib.generate_ids.gen_file_id(path)
456 f['path'] = path
457 self.rev_files[fid] = [path, mark]
458 self.updates[fid] = f
460 def last_revision(self):
461 return self.base_id
463 def iter_changes(self):
464 changes = []
466 def get_parent(dirname, basename):
467 parent_fid, mark = self.base_files.get(dirname, [None, None])
468 if parent_fid:
469 return parent_fid
470 parent_fid, mark = self.files.get(dirname, [None, None])
471 if parent_fid:
472 return parent_fid
473 if basename == '':
474 return None
475 fid = bzrlib.generate_ids.gen_file_id(path)
476 add_entry(fid, dirname, 'directory')
477 return fid
479 def add_entry(fid, path, kind, mode=None):
480 dirname, basename = os.path.split(path)
481 parent_fid = get_parent(dirname, basename)
483 executable = False
484 if mode == '100755':
485 executable = True
486 elif mode == '120000':
487 kind = 'symlink'
489 change = (fid,
490 (None, path),
491 True,
492 (False, True),
493 (None, parent_fid),
494 (None, basename),
495 (None, kind),
496 (None, executable))
497 self.files[path] = [change[0], None]
498 changes.append(change)
500 def update_entry(fid, path, kind, mode=None):
501 dirname, basename = os.path.split(path)
502 parent_fid = get_parent(dirname, basename)
504 executable = False
505 if mode == '100755':
506 executable = True
507 elif mode == '120000':
508 kind = 'symlink'
510 change = (fid,
511 (path, path),
512 True,
513 (True, True),
514 (None, parent_fid),
515 (None, basename),
516 (None, kind),
517 (None, executable))
518 self.files[path] = [change[0], None]
519 changes.append(change)
521 def remove_entry(fid, path, kind):
522 dirname, basename = os.path.split(path)
523 parent_fid = get_parent(dirname, basename)
524 change = (fid,
525 (path, None),
526 True,
527 (True, False),
528 (parent_fid, None),
529 (None, None),
530 (None, None),
531 (None, None))
532 del self.files[path]
533 changes.append(change)
535 for fid, f in self.updates.iteritems():
536 path = f['path']
538 if 'deleted' in f:
539 remove_entry(fid, path, 'file')
540 continue
542 if path in self.base_files:
543 update_entry(fid, path, 'file', f['mode'])
544 else:
545 add_entry(fid, path, 'file', f['mode'])
547 self.files[path][1] = f['mark']
548 self.rev_files[fid][1] = f['mark']
550 return changes
552 def get_content(self, file_id):
553 path, mark = self.rev_files[file_id]
554 if mark:
555 return blob_marks[mark]
557 # last resort
558 tree = self.branch.repository.revision_tree(self.base_id)
559 return tree.get_file_text(file_id)
561 def get_file_with_stat(self, file_id, path=None):
562 content = self.get_content(file_id)
563 return (StringIO.StringIO(content), None)
565 def get_symlink_target(self, file_id):
566 return self.get_content(file_id)
568 def id2path(self, file_id):
569 path, mark = self.rev_files[file_id]
570 return path
572 def c_style_unescape(string):
573 if string[0] == string[-1] == '"':
574 return string.decode('string-escape')[1:-1]
575 return string
577 def parse_commit(parser):
578 parents = []
580 ref = parser[1]
581 parser.next()
583 if ref.startswith('refs/heads/'):
584 name = ref[len('refs/heads/'):]
585 branch = get_remote_branch(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(' ', 1)
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 author, _, _ = author
625 parents = [mark_to_rev(p) for p in parents]
626 revid = bzrlib.generate_ids.gen_revision_id(committer, date)
627 props = {}
628 props['branch-nick'] = branch.nick
629 props['authors'] = author
631 mtree = CustomTree(branch, revid, parents, files)
632 changes = mtree.iter_changes()
634 branch.lock_write()
635 try:
636 builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid)
637 try:
638 list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
639 builder.finish_inventory()
640 builder.commit(data.decode('utf-8', 'replace'))
641 except Exception, e:
642 builder.abort()
643 raise
644 finally:
645 branch.unlock()
647 parsed_refs[ref] = revid
648 marks.new_mark(revid, commit_mark)
650 def parse_reset(parser):
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 parser.next()
668 for line in parser.each_block('done'):
669 if parser.check('blob'):
670 parse_blob(parser)
671 elif parser.check('commit'):
672 parse_commit(parser)
673 elif parser.check('reset'):
674 parse_reset(parser)
675 elif parser.check('tag'):
676 pass
677 elif parser.check('feature'):
678 pass
679 else:
680 die('unhandled export command: %s' % line)
682 for ref, revid in parsed_refs.iteritems():
683 if ref.startswith('refs/heads/'):
684 name = ref[len('refs/heads/'):]
685 branch = get_remote_branch(name)
686 branch.generate_revision_history(revid, marks.get_tip(name))
688 if name in peers:
689 peer = bzrlib.branch.Branch.open(peers[name],
690 possible_transports=transports)
691 try:
692 peer.bzrdir.push_branch(branch, revision_id=revid,
693 overwrite=force)
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 print "import"
717 print "export"
718 print "refspec refs/heads/*:%s/heads/*" % prefix
719 print "refspec refs/tags/*:%s/tags/*" % prefix
721 path = os.path.join(dirname, 'marks-git')
723 if os.path.exists(path):
724 print "*import-marks %s" % path
725 print "*export-marks %s" % path
727 print "option"
728 print
730 class InvalidOptionValue(Exception):
731 pass
733 def get_bool_option(val):
734 if val == 'true':
735 return True
736 elif val == 'false':
737 return False
738 else:
739 raise InvalidOptionValue()
741 def do_option(parser):
742 global force
743 opt, val = parser[1:3]
744 try:
745 if opt == 'force':
746 force = get_bool_option(val)
747 print 'ok'
748 else:
749 print 'unsupported'
750 except InvalidOptionValue:
751 print "error '%s' is not a valid value for option '%s'" % (val, opt)
753 def ref_is_valid(name):
754 return not True in [c in name for c in '~^: \\']
756 def do_list(parser):
757 master_branch = None
759 for name in branches:
760 if not master_branch:
761 master_branch = name
762 print "? refs/heads/%s" % name
764 branch = get_remote_branch(master_branch)
765 branch.lock_read()
766 for tag, revid in branch.tags.get_tag_dict().items():
767 try:
768 branch.revision_id_to_dotted_revno(revid)
769 except bzrlib.errors.NoSuchRevision:
770 continue
771 if not ref_is_valid(tag):
772 continue
773 print "? refs/tags/%s" % tag
774 tags[tag] = revid
775 branch.unlock()
777 print "@refs/heads/%s HEAD" % master_branch
778 print
780 def clone(path, remote_branch):
781 try:
782 bdir = bzrlib.bzrdir.BzrDir.create(path, possible_transports=transports)
783 except bzrlib.errors.AlreadyControlDirError:
784 bdir = bzrlib.bzrdir.BzrDir.open(path, possible_transports=transports)
785 repo = bdir.find_repository()
786 repo.fetch(remote_branch.repository)
787 return remote_branch.sprout(bdir, repository=repo)
789 def get_remote_branch(name):
790 remote_branch = bzrlib.branch.Branch.open(branches[name],
791 possible_transports=transports)
792 if isinstance(remote_branch.bzrdir.root_transport, bzrlib.transport.local.LocalTransport):
793 return remote_branch
795 branch_path = os.path.join(dirname, 'clone', name)
797 try:
798 branch = bzrlib.branch.Branch.open(branch_path,
799 possible_transports=transports)
800 except bzrlib.errors.NotBranchError:
801 # clone
802 branch = clone(branch_path, remote_branch)
803 else:
804 # pull
805 try:
806 branch.pull(remote_branch, overwrite=True)
807 except bzrlib.errors.DivergedBranches:
808 # use remote branch for now
809 return remote_branch
811 return branch
813 def find_branches(repo):
814 transport = repo.bzrdir.root_transport
816 for fn in transport.iter_files_recursive():
817 if not fn.endswith('.bzr/branch-format'):
818 continue
820 name = subdir = fn[:-len('/.bzr/branch-format')]
821 name = name if name != '' else 'master'
822 name = name.replace('/', '+')
824 try:
825 cur = transport.clone(subdir)
826 branch = bzrlib.branch.Branch.open_from_transport(cur)
827 except bzrlib.errors.NotBranchError:
828 continue
829 else:
830 yield name, branch.base
832 def get_repo(url, alias):
833 normal_url = bzrlib.urlutils.normalize_url(url)
834 origin = bzrlib.bzrdir.BzrDir.open(url, possible_transports=transports)
835 is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
837 shared_path = os.path.join(gitdir, 'bzr')
838 try:
839 shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path,
840 possible_transports=transports)
841 except bzrlib.errors.NotBranchError:
842 shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path,
843 possible_transports=transports)
844 try:
845 shared_repo = shared_dir.open_repository()
846 except bzrlib.errors.NoRepositoryPresent:
847 shared_repo = shared_dir.create_repository(shared=True)
849 if not is_local:
850 clone_path = os.path.join(dirname, 'clone')
851 if not os.path.exists(clone_path):
852 os.mkdir(clone_path)
853 else:
854 # check and remove old organization
855 try:
856 bdir = bzrlib.bzrdir.BzrDir.open(clone_path,
857 possible_transports=transports)
858 bdir.destroy_repository()
859 except bzrlib.errors.NotBranchError:
860 pass
861 except bzrlib.errors.NoRepositoryPresent:
862 pass
864 wanted = get_config('remote.%s.bzr-branches' % alias).rstrip().split(', ')
865 # stupid python
866 wanted = [e for e in wanted if e]
867 if not wanted:
868 wanted = get_config('remote-bzr.branches').rstrip().split(', ')
869 # stupid python
870 wanted = [e for e in wanted if e]
872 if not wanted:
873 try:
874 repo = origin.open_repository()
875 if not repo.bzrdir.root_transport.listable():
876 # this repository is not usable for us
877 raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
878 except bzrlib.errors.NoRepositoryPresent:
879 wanted = ['master']
881 if wanted:
882 def list_wanted(url, wanted):
883 for name in wanted:
884 subdir = name if name != 'master' else ''
885 yield name, bzrlib.urlutils.join(url, subdir)
887 branch_list = list_wanted(url, wanted)
888 else:
889 branch_list = find_branches(repo)
891 for name, url in branch_list:
892 if not is_local:
893 peers[name] = url
894 branches[name] = url
896 return origin
898 def fix_path(alias, orig_url):
899 url = urlparse.urlparse(orig_url, 'file')
900 if url.scheme != 'file' or os.path.isabs(url.path):
901 return
902 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
903 cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
904 subprocess.call(cmd)
906 def main(args):
907 global marks, prefix, gitdir, dirname
908 global tags, filenodes
909 global blob_marks
910 global parsed_refs
911 global files_cache
912 global is_tmp
913 global branches, peers
914 global transports
915 global force
917 marks = None
918 is_tmp = False
919 gitdir = os.environ.get('GIT_DIR', None)
921 if len(args) < 3:
922 die('Not enough arguments.')
924 if not gitdir:
925 die('GIT_DIR not set')
927 alias = args[1]
928 url = args[2]
930 tags = {}
931 filenodes = {}
932 blob_marks = {}
933 parsed_refs = {}
934 files_cache = {}
935 branches = {}
936 peers = {}
937 transports = []
938 force = False
940 if alias[5:] == url:
941 is_tmp = True
942 alias = hashlib.sha1(alias).hexdigest()
944 prefix = 'refs/bzr/%s' % alias
945 dirname = os.path.join(gitdir, 'bzr', alias)
947 if not is_tmp:
948 fix_path(alias, url)
950 if not os.path.exists(dirname):
951 os.makedirs(dirname)
953 if hasattr(bzrlib.ui.ui_factory, 'be_quiet'):
954 bzrlib.ui.ui_factory.be_quiet(True)
956 repo = get_repo(url, alias)
958 marks_path = os.path.join(dirname, 'marks-int')
959 marks = Marks(marks_path)
961 parser = Parser(repo)
962 for line in parser:
963 if parser.check('capabilities'):
964 do_capabilities(parser)
965 elif parser.check('list'):
966 do_list(parser)
967 elif parser.check('import'):
968 do_import(parser)
969 elif parser.check('export'):
970 do_export(parser)
971 elif parser.check('option'):
972 do_option(parser)
973 else:
974 die('unhandled command: %s' % line)
975 sys.stdout.flush()
977 def bye():
978 if not marks:
979 return
980 if not is_tmp:
981 marks.store()
982 else:
983 shutil.rmtree(dirname)
985 atexit.register(bye)
986 sys.exit(main(sys.argv))