remote-bzr: fix old organization destroy
[git/git.git] / contrib / remote-helpers / git-remote-bzr
CommitLineData
bee118ec
FC
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#
248663c4
FC
16# If you want to specify which branches you want track (per repo):
17# git config remote-bzr.branches 'trunk, devel, test'
18#
bee118ec
FC
19
20import sys
21
22import bzrlib
570e7ecd
FC
23if hasattr(bzrlib, "initialize"):
24 bzrlib.initialize()
bee118ec
FC
25
26import bzrlib.plugin
27bzrlib.plugin.load_plugins()
28
f0497716 29import bzrlib.generate_ids
073c3ffa 30import bzrlib.transport
fa7285dc 31import bzrlib.errors
4d74cd47 32import bzrlib.ui
95b0c608 33import bzrlib.urlutils
f0497716 34
bee118ec
FC
35import sys
36import os
37import json
38import re
f0497716 39import StringIO
d82c912c 40import atexit, shutil, hashlib, urlparse, subprocess
bee118ec
FC
41
42NAME_RE = re.compile('^([^<>]+)')
43AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
3f6e7c0a 44EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
f0497716 45RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
bee118ec
FC
46
47def die(msg, *args):
48 sys.stderr.write('ERROR: %s\n' % (msg % args))
49 sys.exit(1)
50
51def warn(msg, *args):
52 sys.stderr.write('WARNING: %s\n' % (msg % args))
53
54def gittz(tz):
55 return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
56
248663c4
FC
57def 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
bee118ec
FC
63class Marks:
64
65 def __init__(self, path):
66 self.path = path
67 self.tips = {}
68 self.marks = {}
f0497716 69 self.rev_marks = {}
bee118ec
FC
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
f0497716
FC
82 for rev, mark in self.marks.iteritems():
83 self.rev_marks[mark] = rev
84
bee118ec
FC
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
f0497716 97 def to_rev(self, mark):
08181121 98 return str(self.rev_marks[mark])
f0497716 99
bee118ec
FC
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):
75301a45 110 return rev in self.marks
bee118ec 111
f0497716
FC
112 def new_mark(self, rev, mark):
113 self.marks[rev] = mark
114 self.rev_marks[mark] = rev
115 self.last_mark = mark
116
bee118ec
FC
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
123class 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
f0497716
FC
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
bee118ec
FC
172def rev_to_mark(rev):
173 global marks
174 return marks.from_rev(rev)
175
f0497716
FC
176def mark_to_rev(mark):
177 global marks
178 return marks.to_rev(mark)
179
bee118ec
FC
180def 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:
3f6e7c0a 188 m = EMAIL_RE.match(user)
bee118ec 189 if m:
3f6e7c0a
FC
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'
bee118ec
FC
201
202 return '%s <%s>' % (name, mail)
203
204def get_filechanges(cur, prev):
205 modified = {}
206 removed = {}
207
208 changes = cur.changes_from(prev)
209
5ff4fc64
CS
210 def u(s):
211 return s.encode('utf-8')
212
bee118ec 213 for path, fid, kind in changes.added:
5ff4fc64 214 modified[u(path)] = fid
bee118ec 215 for path, fid, kind in changes.removed:
5ff4fc64 216 removed[u(path)] = None
bee118ec 217 for path, fid, kind, mod, _ in changes.modified:
5ff4fc64 218 modified[u(path)] = fid
bee118ec 219 for oldpath, newpath, fid, kind, mod, _ in changes.renamed:
5ff4fc64 220 removed[u(oldpath)] = None
82447e33
CS
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':
5ff4fc64 225 modified[u(newpath + '/' + path)] = fid
82447e33 226 else:
5ff4fc64 227 modified[u(newpath)] = fid
bee118ec
FC
228
229 return modified, removed
230
231def export_files(tree, files):
232 global marks, filenodes
233
234 final = []
235 for path, fid in files.iteritems():
bdeeb809
FC
236 kind = tree.kind(fid)
237
bee118ec
FC
238 h = tree.get_file_sha1(fid)
239
bdeeb809
FC
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
75301a45 250 # is the blob already exported?
bdeeb809
FC
251 if h in filenodes:
252 mark = filenodes[h]
5ff4fc64 253 final.append((mode, mark, path))
bdeeb809 254 continue
bee118ec 255
bee118ec 256 d = tree.get_file_text(fid)
bdeeb809
FC
257 elif kind == 'directory':
258 continue
259 else:
260 die("Unhandled kind '%s' for path '%s'" % (kind, path))
bee118ec 261
bdeeb809
FC
262 mark = marks.next_mark()
263 filenodes[h] = mark
bee118ec 264
bdeeb809
FC
265 print "blob"
266 print "mark :%u" % mark
267 print "data %d" % len(d)
268 print d
bee118ec 269
5ff4fc64 270 final.append((mode, mark, path))
bee118ec
FC
271
272 return final
273
95b0c608 274def export_branch(repo, name):
afad2005 275 global prefix
bee118ec
FC
276
277 ref = '%s/heads/%s' % (prefix, name)
278 tip = marks.get_tip(name)
279
d421c02b 280 branch = bzrlib.branch.Branch.open(branches[name])
bee118ec 281 repo = branch.repository
5df4fad3
FC
282
283 branch.lock_read()
bee118ec 284 revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
435f39a3
FC
285 try:
286 tip_revno = branch.revision_id_to_revno(tip)
287 last_revno, _ = branch.last_revision_info()
288 total = last_revno - tip_revno
289 except bzrlib.errors.NoSuchRevision:
290 tip_revno = 0
291 total = 0
bee118ec 292
38cecbdf 293 for revid, _, seq, _ in revs:
bee118ec 294
38cecbdf
FC
295 if marks.is_marked(revid):
296 continue
bee118ec
FC
297
298 rev = repo.get_revision(revid)
a3976999 299 revno = seq[0]
bee118ec
FC
300
301 parents = rev.parent_ids
302 time = rev.timestamp
303 tz = rev.timezone
304 committer = rev.committer.encode('utf-8')
305 committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
9d9d698c
DE
306 authors = rev.get_apparent_authors()
307 if authors:
308 author = authors[0].encode('utf-8')
309 author = "%s %u %s" % (fixup_user(author), time, gittz(tz))
310 else:
311 author = committer
bee118ec
FC
312 msg = rev.message.encode('utf-8')
313
314 msg += '\n'
315
316 if len(parents) == 0:
317 parent = bzrlib.revision.NULL_REVISION
318 else:
319 parent = parents[0]
320
321 cur_tree = repo.revision_tree(revid)
322 prev = repo.revision_tree(parent)
323 modified, removed = get_filechanges(cur_tree, prev)
324
325 modified_final = export_files(cur_tree, modified)
326
327 if len(parents) == 0:
328 print 'reset %s' % ref
329
330 print "commit %s" % ref
331 print "mark :%d" % (marks.get_mark(revid))
332 print "author %s" % (author)
333 print "committer %s" % (committer)
334 print "data %d" % (len(msg))
335 print msg
336
337 for i, p in enumerate(parents):
338 try:
339 m = rev_to_mark(p)
340 except KeyError:
341 # ghost?
342 continue
343 if i == 0:
344 print "from :%s" % m
345 else:
346 print "merge :%s" % m
347
bc51f7c3
CS
348 for f in removed:
349 print "D %s" % (f,)
bee118ec
FC
350 for f in modified_final:
351 print "M %s :%u %s" % f
bee118ec
FC
352 print
353
a3976999
FC
354 if len(seq) > 1:
355 # let's skip branch revisions from the progress report
356 continue
357
358 progress = (revno - tip_revno)
359 if (progress % 100 == 0):
435f39a3
FC
360 if total:
361 print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total)
362 else:
363 print "progress revision %d '%s' (%d)" % (revno, name, progress)
bee118ec 364
5df4fad3 365 branch.unlock()
bee118ec
FC
366
367 revid = branch.last_revision()
368
369 # make sure the ref is updated
370 print "reset %s" % ref
371 print "from :%u" % rev_to_mark(revid)
372 print
373
374 marks.set_tip(name, revid)
375
376def export_tag(repo, name):
afad2005
FC
377 global tags, prefix
378
379 ref = '%s/tags/%s' % (prefix, name)
380 print "reset %s" % ref
fa7285dc
FC
381 print "from :%u" % rev_to_mark(tags[name])
382 print
bee118ec
FC
383
384def do_import(parser):
385 global dirname
386
95b0c608 387 repo = parser.repo
bee118ec
FC
388 path = os.path.join(dirname, 'marks-git')
389
390 print "feature done"
391 if os.path.exists(path):
392 print "feature import-marks=%s" % path
393 print "feature export-marks=%s" % path
38e7167e 394 print "feature force"
bee118ec
FC
395 sys.stdout.flush()
396
397 while parser.check('import'):
398 ref = parser[1]
399 if ref.startswith('refs/heads/'):
400 name = ref[len('refs/heads/'):]
95b0c608 401 export_branch(repo, name)
bee118ec
FC
402 if ref.startswith('refs/tags/'):
403 name = ref[len('refs/tags/'):]
95b0c608 404 export_tag(repo, name)
bee118ec
FC
405 parser.next()
406
407 print 'done'
408
409 sys.stdout.flush()
410
f0497716
FC
411def parse_blob(parser):
412 global blob_marks
413
414 parser.next()
415 mark = parser.get_mark()
416 parser.next()
417 data = parser.get_data()
418 blob_marks[mark] = data
419 parser.next()
420
421class CustomTree():
422
5df4fad3 423 def __init__(self, branch, revid, parents, files):
f0497716
FC
424 global files_cache
425
7edea5c9 426 self.updates = {}
5df4fad3 427 self.branch = branch
f0497716
FC
428
429 def copy_tree(revid):
430 files = files_cache[revid] = {}
5df4fad3
FC
431 branch.lock_read()
432 tree = branch.repository.revision_tree(revid)
f0497716
FC
433 try:
434 for path, entry in tree.iter_entries_by_dir():
f38dfc4c 435 files[path] = [entry.file_id, None]
f0497716 436 finally:
5df4fad3 437 branch.unlock()
f0497716
FC
438 return files
439
440 if len(parents) == 0:
441 self.base_id = bzrlib.revision.NULL_REVISION
442 self.base_files = {}
443 else:
444 self.base_id = parents[0]
445 self.base_files = files_cache.get(self.base_id, None)
446 if not self.base_files:
447 self.base_files = copy_tree(self.base_id)
448
449 self.files = files_cache[revid] = self.base_files.copy()
f38dfc4c
FC
450 self.rev_files = {}
451
452 for path, data in self.files.iteritems():
453 fid, mark = data
454 self.rev_files[fid] = [path, mark]
f0497716 455
7edea5c9 456 for path, f in files.iteritems():
f38dfc4c 457 fid, mark = self.files.get(path, [None, None])
7edea5c9
FC
458 if not fid:
459 fid = bzrlib.generate_ids.gen_file_id(path)
460 f['path'] = path
f38dfc4c 461 self.rev_files[fid] = [path, mark]
7edea5c9
FC
462 self.updates[fid] = f
463
f0497716
FC
464 def last_revision(self):
465 return self.base_id
466
467 def iter_changes(self):
468 changes = []
469
470 def get_parent(dirname, basename):
f38dfc4c 471 parent_fid, mark = self.base_files.get(dirname, [None, None])
f0497716
FC
472 if parent_fid:
473 return parent_fid
f38dfc4c 474 parent_fid, mark = self.files.get(dirname, [None, None])
f0497716
FC
475 if parent_fid:
476 return parent_fid
477 if basename == '':
478 return None
7edea5c9 479 fid = bzrlib.generate_ids.gen_file_id(path)
c80f4c77 480 add_entry(fid, dirname, 'directory')
7edea5c9 481 return fid
f0497716 482
7edea5c9 483 def add_entry(fid, path, kind, mode = None):
f0497716
FC
484 dirname, basename = os.path.split(path)
485 parent_fid = get_parent(dirname, basename)
7edea5c9
FC
486
487 executable = False
488 if mode == '100755':
489 executable = True
490 elif mode == '120000':
491 kind = 'symlink'
492
f0497716
FC
493 change = (fid,
494 (None, path),
495 True,
496 (False, True),
497 (None, parent_fid),
498 (None, basename),
499 (None, kind),
7edea5c9 500 (None, executable))
f38dfc4c 501 self.files[path] = [change[0], None]
f0497716 502 changes.append(change)
f0497716 503
7edea5c9 504 def update_entry(fid, path, kind, mode = None):
f0497716 505 dirname, basename = os.path.split(path)
f0497716 506 parent_fid = get_parent(dirname, basename)
7edea5c9
FC
507
508 executable = False
509 if mode == '100755':
510 executable = True
511 elif mode == '120000':
512 kind = 'symlink'
513
f0497716
FC
514 change = (fid,
515 (path, path),
516 True,
517 (True, True),
518 (None, parent_fid),
519 (None, basename),
520 (None, kind),
7edea5c9 521 (None, executable))
f38dfc4c 522 self.files[path] = [change[0], None]
f0497716 523 changes.append(change)
f0497716 524
7edea5c9 525 def remove_entry(fid, path, kind):
f0497716 526 dirname, basename = os.path.split(path)
f0497716
FC
527 parent_fid = get_parent(dirname, basename)
528 change = (fid,
529 (path, None),
530 True,
531 (True, False),
532 (parent_fid, None),
533 (None, None),
534 (None, None),
535 (None, None))
536 del self.files[path]
537 changes.append(change)
f0497716 538
7edea5c9
FC
539 for fid, f in self.updates.iteritems():
540 path = f['path']
541
f0497716 542 if 'deleted' in f:
7edea5c9
FC
543 remove_entry(fid, path, 'file')
544 continue
545
546 if path in self.base_files:
547 update_entry(fid, path, 'file', f['mode'])
f0497716 548 else:
7edea5c9 549 add_entry(fid, path, 'file', f['mode'])
f0497716 550
f38dfc4c
FC
551 self.files[path][1] = f['mark']
552 self.rev_files[fid][1] = f['mark']
553
f0497716
FC
554 return changes
555
b25df87f 556 def get_content(self, file_id):
f38dfc4c 557 path, mark = self.rev_files[file_id]
b25df87f
FC
558 if mark:
559 return blob_marks[mark]
560
561 # last resort
562 tree = self.branch.repository.revision_tree(self.base_id)
563 return tree.get_file_text(file_id)
564
565 def get_file_with_stat(self, file_id, path=None):
566 content = self.get_content(file_id)
567 return (StringIO.StringIO(content), None)
7edea5c9
FC
568
569 def get_symlink_target(self, file_id):
b25df87f 570 return self.get_content(file_id)
f0497716 571
f38dfc4c
FC
572 def id2path(self, file_id):
573 path, mark = self.rev_files[file_id]
574 return path
575
6ff8d4e7
FC
576def c_style_unescape(string):
577 if string[0] == string[-1] == '"':
578 return string.decode('string-escape')[1:-1]
579 return string
580
f0497716 581def parse_commit(parser):
75301a45 582 global marks, blob_marks, parsed_refs
f0497716
FC
583 global mode
584
585 parents = []
586
587 ref = parser[1]
588 parser.next()
589
95b0c608
FC
590 if ref.startswith('refs/heads/'):
591 name = ref[len('refs/heads/'):]
d421c02b 592 branch = bzrlib.branch.Branch.open(branches[name])
95b0c608
FC
593 else:
594 die('unknown ref')
f0497716
FC
595
596 commit_mark = parser.get_mark()
597 parser.next()
598 author = parser.get_author()
599 parser.next()
600 committer = parser.get_author()
601 parser.next()
602 data = parser.get_data()
603 parser.next()
604 if parser.check('from'):
605 parents.append(parser.get_mark())
606 parser.next()
607 while parser.check('merge'):
608 parents.append(parser.get_mark())
609 parser.next()
610
877ee9cc
FC
611 # fast-export adds an extra newline
612 if data[-1] == '\n':
613 data = data[:-1]
614
f0497716
FC
615 files = {}
616
617 for line in parser:
618 if parser.check('M'):
619 t, m, mark_ref, path = line.split(' ', 3)
620 mark = int(mark_ref[1:])
18166208 621 f = { 'mode' : m, 'mark' : mark }
f0497716
FC
622 elif parser.check('D'):
623 t, path = line.split(' ')
624 f = { 'deleted' : True }
625 else:
626 die('Unknown file command: %s' % line)
6ff8d4e7 627 path = c_style_unescape(path).decode('utf-8')
f0497716
FC
628 files[path] = f
629
f0497716 630 committer, date, tz = committer
08181121 631 parents = [mark_to_rev(p) for p in parents]
f0497716
FC
632 revid = bzrlib.generate_ids.gen_revision_id(committer, date)
633 props = {}
5df4fad3 634 props['branch-nick'] = branch.nick
f0497716 635
5df4fad3 636 mtree = CustomTree(branch, revid, parents, files)
f0497716
FC
637 changes = mtree.iter_changes()
638
5df4fad3 639 branch.lock_write()
f0497716 640 try:
5df4fad3 641 builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid)
f0497716
FC
642 try:
643 list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
644 builder.finish_inventory()
645 builder.commit(data.decode('utf-8', 'replace'))
646 except Exception, e:
647 builder.abort()
648 raise
649 finally:
5df4fad3 650 branch.unlock()
f0497716
FC
651
652 parsed_refs[ref] = revid
653 marks.new_mark(revid, commit_mark)
654
655def parse_reset(parser):
656 global parsed_refs
657
658 ref = parser[1]
659 parser.next()
660
f0497716
FC
661 # ugh
662 if parser.check('commit'):
663 parse_commit(parser)
664 return
665 if not parser.check('from'):
666 return
667 from_mark = parser.get_mark()
668 parser.next()
669
670 parsed_refs[ref] = mark_to_rev(from_mark)
671
672def do_export(parser):
95b0c608 673 global parsed_refs, dirname
f0497716
FC
674
675 parser.next()
676
677 for line in parser.each_block('done'):
678 if parser.check('blob'):
679 parse_blob(parser)
680 elif parser.check('commit'):
681 parse_commit(parser)
682 elif parser.check('reset'):
683 parse_reset(parser)
684 elif parser.check('tag'):
685 pass
686 elif parser.check('feature'):
687 pass
688 else:
689 die('unhandled export command: %s' % line)
690
f0497716 691 for ref, revid in parsed_refs.iteritems():
4c008199
FC
692 if ref.startswith('refs/heads/'):
693 name = ref[len('refs/heads/'):]
694 branch = bzrlib.branch.Branch.open(branches[name])
695 branch.generate_revision_history(revid, marks.get_tip(name))
715d64fe 696
4c008199
FC
697 if name in peers:
698 peer = bzrlib.branch.Branch.open(peers[name])
699 try:
700 peer.bzrdir.push_branch(branch, revision_id=revid)
701 except bzrlib.errors.DivergedBranches:
702 print "error %s non-fast forward" % ref
703 continue
95b0c608 704
4c008199
FC
705 try:
706 wt = branch.bzrdir.open_workingtree()
707 wt.update()
708 except bzrlib.errors.NoWorkingTree:
709 pass
710 elif ref.startswith('refs/tags/'):
711 # TODO: implement tag push
712 print "error %s pushing tags not supported" % ref
713 continue
714 else:
715 # transport-helper/fast-export bugs
716 continue
715d64fe 717
f0497716 718 print "ok %s" % ref
75301a45 719
f0497716
FC
720 print
721
bee118ec 722def do_capabilities(parser):
f0497716
FC
723 global dirname
724
bee118ec 725 print "import"
f0497716 726 print "export"
bee118ec 727 print "refspec refs/heads/*:%s/heads/*" % prefix
afad2005 728 print "refspec refs/tags/*:%s/tags/*" % prefix
f0497716
FC
729
730 path = os.path.join(dirname, 'marks-git')
731
732 if os.path.exists(path):
733 print "*import-marks %s" % path
734 print "*export-marks %s" % path
735
bee118ec
FC
736 print
737
fa7285dc
FC
738def ref_is_valid(name):
739 return not True in [c in name for c in '~^: \\']
740
bee118ec
FC
741def do_list(parser):
742 global tags
8954441a 743
95b0c608
FC
744 master_branch = None
745
746 for name in branches:
747 if not master_branch:
748 master_branch = name
749 print "? refs/heads/%s" % name
750
d421c02b 751 branch = bzrlib.branch.Branch.open(branches[master_branch])
fa7285dc
FC
752 branch.lock_read()
753 for tag, revid in branch.tags.get_tag_dict().items():
754 try:
755 branch.revision_id_to_dotted_revno(revid)
756 except bzrlib.errors.NoSuchRevision:
757 continue
758 if not ref_is_valid(tag):
8954441a 759 continue
bee118ec
FC
760 print "? refs/tags/%s" % tag
761 tags[tag] = revid
fa7285dc 762 branch.unlock()
95b0c608
FC
763
764 print "@refs/heads/%s HEAD" % master_branch
bee118ec
FC
765 print
766
95b0c608
FC
767def get_remote_branch(origin, remote_branch, name):
768 global dirname, peers
769
770 branch_path = os.path.join(dirname, 'clone', name)
771 if os.path.exists(branch_path):
772 # pull
773 d = bzrlib.bzrdir.BzrDir.open(branch_path)
774 branch = d.open_branch()
775 try:
776 branch.pull(remote_branch, [], None, False)
777 except bzrlib.errors.DivergedBranches:
778 # use remote branch for now
779 return remote_branch
780 else:
781 # clone
782 d = origin.sprout(branch_path, None,
783 hardlink=True, create_tree_if_local=False,
784 force_new_repo=False,
785 source_branch=remote_branch)
786 branch = d.open_branch()
787
788 return branch
789
248663c4 790def find_branches(repo, wanted):
850dd25c
FC
791 transport = repo.user_transport
792
793 for fn in transport.iter_files_recursive():
794 if not fn.endswith('.bzr/branch-format'):
795 continue
796
797 name = subdir = fn[:-len('/.bzr/branch-format')]
798 name = name if name != '' else 'master'
799 name = name.replace('/', '+')
800
248663c4
FC
801 if wanted and not name in wanted:
802 continue
803
850dd25c
FC
804 try:
805 cur = transport.clone(subdir)
806 branch = bzrlib.branch.Branch.open_from_transport(cur)
807 except bzrlib.errors.NotBranchError:
808 continue
809 else:
810 yield name, branch
811
bee118ec 812def get_repo(url, alias):
95b0c608 813 global dirname, peer, branches
dc2177c2 814
95b0c608 815 normal_url = bzrlib.urlutils.normalize_url(url)
570e7ecd 816 origin = bzrlib.bzrdir.BzrDir.open(url)
95b0c608
FC
817 is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
818
c95c35f4
FC
819 shared_path = os.path.join(gitdir, 'bzr')
820 try:
821 shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path)
822 except bzrlib.errors.NotBranchError:
823 shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path)
824 try:
825 shared_repo = shared_dir.open_repository()
826 except bzrlib.errors.NoRepositoryPresent:
827 shared_repo = shared_dir.create_repository(shared=True)
828
829 if not is_local:
830 clone_path = os.path.join(dirname, 'clone')
831 if not os.path.exists(clone_path):
832 os.mkdir(clone_path)
6a3ac18b
FC
833 else:
834 # check and remove old organization
835 try:
836 bdir = bzrlib.bzrdir.BzrDir.open(clone_path)
837 bdir.destroy_repository()
838 except bzrlib.errors.NotBranchError:
839 pass
a70ae587
SBM
840 except bzrlib.errors.NoRepositoryPresent:
841 pass
95b0c608
FC
842
843 try:
844 repo = origin.open_repository()
4f4e7e9b
FC
845 if not repo.user_transport.listable():
846 # this repository is not usable for us
847 raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
95b0c608
FC
848 except bzrlib.errors.NoRepositoryPresent:
849 # branch
850
851 name = 'master'
d421c02b 852 remote_branch = origin.open_branch()
95b0c608
FC
853
854 if not is_local:
d421c02b
FC
855 peers[name] = remote_branch.base
856 branch = get_remote_branch(origin, remote_branch, name)
073c3ffa 857 else:
d421c02b
FC
858 branch = remote_branch
859
860 branches[name] = branch.base
95b0c608
FC
861
862 return branch.repository
dc2177c2 863 else:
95b0c608 864 # repository
dc2177c2 865
248663c4
FC
866 wanted = get_config('remote-bzr.branches').rstrip().split(', ')
867 # stupid python
868 wanted = [e for e in wanted if e]
869
d421c02b 870 for name, remote_branch in find_branches(repo, wanted):
95b0c608
FC
871
872 if not is_local:
d421c02b
FC
873 peers[name] = remote_branch.base
874 branch = get_remote_branch(origin, remote_branch, name)
95b0c608 875 else:
d421c02b
FC
876 branch = remote_branch
877
878 branches[name] = branch.base
95b0c608
FC
879
880 return repo
bee118ec 881
d82c912c
FC
882def fix_path(alias, orig_url):
883 url = urlparse.urlparse(orig_url, 'file')
884 if url.scheme != 'file' or os.path.isabs(url.path):
885 return
886 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
887 cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
888 subprocess.call(cmd)
889
bee118ec 890def main(args):
c95c35f4 891 global marks, prefix, gitdir, dirname
bee118ec 892 global tags, filenodes
f0497716
FC
893 global blob_marks
894 global parsed_refs
895 global files_cache
aa938456 896 global is_tmp
95b0c608 897 global branches, peers
bee118ec
FC
898
899 alias = args[1]
900 url = args[2]
901
bee118ec
FC
902 tags = {}
903 filenodes = {}
f0497716
FC
904 blob_marks = {}
905 parsed_refs = {}
906 files_cache = {}
d6bb9136 907 marks = None
95b0c608
FC
908 branches = {}
909 peers = {}
bee118ec 910
aa938456
FC
911 if alias[5:] == url:
912 is_tmp = True
913 alias = hashlib.sha1(alias).hexdigest()
914 else:
915 is_tmp = False
916
917 prefix = 'refs/bzr/%s' % alias
bee118ec
FC
918 gitdir = os.environ['GIT_DIR']
919 dirname = os.path.join(gitdir, 'bzr', alias)
920
d82c912c
FC
921 if not is_tmp:
922 fix_path(alias, url)
923
bee118ec
FC
924 if not os.path.exists(dirname):
925 os.makedirs(dirname)
926
4d74cd47
FC
927 bzrlib.ui.ui_factory.be_quiet(True)
928
bee118ec
FC
929 repo = get_repo(url, alias)
930
931 marks_path = os.path.join(dirname, 'marks-int')
932 marks = Marks(marks_path)
933
934 parser = Parser(repo)
935 for line in parser:
936 if parser.check('capabilities'):
937 do_capabilities(parser)
938 elif parser.check('list'):
939 do_list(parser)
940 elif parser.check('import'):
941 do_import(parser)
f0497716
FC
942 elif parser.check('export'):
943 do_export(parser)
bee118ec
FC
944 else:
945 die('unhandled command: %s' % line)
946 sys.stdout.flush()
947
d6bb9136
FC
948def bye():
949 if not marks:
950 return
aa938456
FC
951 if not is_tmp:
952 marks.store()
953 else:
954 shutil.rmtree(dirname)
bee118ec 955
d6bb9136 956atexit.register(bye)
bee118ec 957sys.exit(main(sys.argv))