remote-bzr: avoid bad refs
[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')
a3976999
FC
285 tip_revno = branch.revision_id_to_revno(tip)
286 last_revno, _ = branch.last_revision_info()
287 total = last_revno - tip_revno
bee118ec 288
38cecbdf 289 for revid, _, seq, _ in revs:
bee118ec 290
38cecbdf
FC
291 if marks.is_marked(revid):
292 continue
bee118ec
FC
293
294 rev = repo.get_revision(revid)
a3976999 295 revno = seq[0]
bee118ec
FC
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))
9d9d698c
DE
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
bee118ec
FC
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
bc51f7c3
CS
344 for f in removed:
345 print "D %s" % (f,)
bee118ec
FC
346 for f in modified_final:
347 print "M %s :%u %s" % f
bee118ec
FC
348 print
349
a3976999
FC
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)
bee118ec 357
5df4fad3 358 branch.unlock()
bee118ec
FC
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
369def export_tag(repo, name):
afad2005
FC
370 global tags, prefix
371
372 ref = '%s/tags/%s' % (prefix, name)
373 print "reset %s" % ref
fa7285dc
FC
374 print "from :%u" % rev_to_mark(tags[name])
375 print
bee118ec
FC
376
377def do_import(parser):
378 global dirname
379
95b0c608 380 repo = parser.repo
bee118ec
FC
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
38e7167e 387 print "feature force"
bee118ec
FC
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/'):]
95b0c608 394 export_branch(repo, name)
bee118ec
FC
395 if ref.startswith('refs/tags/'):
396 name = ref[len('refs/tags/'):]
95b0c608 397 export_tag(repo, name)
bee118ec
FC
398 parser.next()
399
400 print 'done'
401
402 sys.stdout.flush()
403
f0497716
FC
404def 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
414class CustomTree():
415
5df4fad3 416 def __init__(self, branch, revid, parents, files):
f0497716
FC
417 global files_cache
418
7edea5c9 419 self.updates = {}
5df4fad3 420 self.branch = branch
f0497716
FC
421
422 def copy_tree(revid):
423 files = files_cache[revid] = {}
5df4fad3
FC
424 branch.lock_read()
425 tree = branch.repository.revision_tree(revid)
f0497716
FC
426 try:
427 for path, entry in tree.iter_entries_by_dir():
f38dfc4c 428 files[path] = [entry.file_id, None]
f0497716 429 finally:
5df4fad3 430 branch.unlock()
f0497716
FC
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()
f38dfc4c
FC
443 self.rev_files = {}
444
445 for path, data in self.files.iteritems():
446 fid, mark = data
447 self.rev_files[fid] = [path, mark]
f0497716 448
7edea5c9 449 for path, f in files.iteritems():
f38dfc4c 450 fid, mark = self.files.get(path, [None, None])
7edea5c9
FC
451 if not fid:
452 fid = bzrlib.generate_ids.gen_file_id(path)
453 f['path'] = path
f38dfc4c 454 self.rev_files[fid] = [path, mark]
7edea5c9
FC
455 self.updates[fid] = f
456
f0497716
FC
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):
f38dfc4c 464 parent_fid, mark = self.base_files.get(dirname, [None, None])
f0497716
FC
465 if parent_fid:
466 return parent_fid
f38dfc4c 467 parent_fid, mark = self.files.get(dirname, [None, None])
f0497716
FC
468 if parent_fid:
469 return parent_fid
470 if basename == '':
471 return None
7edea5c9 472 fid = bzrlib.generate_ids.gen_file_id(path)
c80f4c77 473 add_entry(fid, dirname, 'directory')
7edea5c9 474 return fid
f0497716 475
7edea5c9 476 def add_entry(fid, path, kind, mode = None):
f0497716
FC
477 dirname, basename = os.path.split(path)
478 parent_fid = get_parent(dirname, basename)
7edea5c9
FC
479
480 executable = False
481 if mode == '100755':
482 executable = True
483 elif mode == '120000':
484 kind = 'symlink'
485
f0497716
FC
486 change = (fid,
487 (None, path),
488 True,
489 (False, True),
490 (None, parent_fid),
491 (None, basename),
492 (None, kind),
7edea5c9 493 (None, executable))
f38dfc4c 494 self.files[path] = [change[0], None]
f0497716 495 changes.append(change)
f0497716 496
7edea5c9 497 def update_entry(fid, path, kind, mode = None):
f0497716 498 dirname, basename = os.path.split(path)
f0497716 499 parent_fid = get_parent(dirname, basename)
7edea5c9
FC
500
501 executable = False
502 if mode == '100755':
503 executable = True
504 elif mode == '120000':
505 kind = 'symlink'
506
f0497716
FC
507 change = (fid,
508 (path, path),
509 True,
510 (True, True),
511 (None, parent_fid),
512 (None, basename),
513 (None, kind),
7edea5c9 514 (None, executable))
f38dfc4c 515 self.files[path] = [change[0], None]
f0497716 516 changes.append(change)
f0497716 517
7edea5c9 518 def remove_entry(fid, path, kind):
f0497716 519 dirname, basename = os.path.split(path)
f0497716
FC
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)
f0497716 531
7edea5c9
FC
532 for fid, f in self.updates.iteritems():
533 path = f['path']
534
f0497716 535 if 'deleted' in f:
7edea5c9
FC
536 remove_entry(fid, path, 'file')
537 continue
538
539 if path in self.base_files:
540 update_entry(fid, path, 'file', f['mode'])
f0497716 541 else:
7edea5c9 542 add_entry(fid, path, 'file', f['mode'])
f0497716 543
f38dfc4c
FC
544 self.files[path][1] = f['mark']
545 self.rev_files[fid][1] = f['mark']
546
f0497716
FC
547 return changes
548
b25df87f 549 def get_content(self, file_id):
f38dfc4c 550 path, mark = self.rev_files[file_id]
b25df87f
FC
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)
7edea5c9
FC
561
562 def get_symlink_target(self, file_id):
b25df87f 563 return self.get_content(file_id)
f0497716 564
f38dfc4c
FC
565 def id2path(self, file_id):
566 path, mark = self.rev_files[file_id]
567 return path
568
6ff8d4e7
FC
569def c_style_unescape(string):
570 if string[0] == string[-1] == '"':
571 return string.decode('string-escape')[1:-1]
572 return string
573
f0497716 574def parse_commit(parser):
75301a45 575 global marks, blob_marks, parsed_refs
f0497716
FC
576 global mode
577
578 parents = []
579
580 ref = parser[1]
581 parser.next()
582
95b0c608
FC
583 if ref.startswith('refs/heads/'):
584 name = ref[len('refs/heads/'):]
d421c02b 585 branch = bzrlib.branch.Branch.open(branches[name])
95b0c608
FC
586 else:
587 die('unknown ref')
f0497716
FC
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
877ee9cc
FC
604 # fast-export adds an extra newline
605 if data[-1] == '\n':
606 data = data[:-1]
607
f0497716
FC
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:])
18166208 614 f = { 'mode' : m, 'mark' : mark }
f0497716
FC
615 elif parser.check('D'):
616 t, path = line.split(' ')
617 f = { 'deleted' : True }
618 else:
619 die('Unknown file command: %s' % line)
6ff8d4e7 620 path = c_style_unescape(path).decode('utf-8')
f0497716
FC
621 files[path] = f
622
f0497716 623 committer, date, tz = committer
08181121 624 parents = [mark_to_rev(p) for p in parents]
f0497716
FC
625 revid = bzrlib.generate_ids.gen_revision_id(committer, date)
626 props = {}
5df4fad3 627 props['branch-nick'] = branch.nick
f0497716 628
5df4fad3 629 mtree = CustomTree(branch, revid, parents, files)
f0497716
FC
630 changes = mtree.iter_changes()
631
5df4fad3 632 branch.lock_write()
f0497716 633 try:
5df4fad3 634 builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid)
f0497716
FC
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:
5df4fad3 643 branch.unlock()
f0497716
FC
644
645 parsed_refs[ref] = revid
646 marks.new_mark(revid, commit_mark)
647
648def parse_reset(parser):
649 global parsed_refs
650
651 ref = parser[1]
652 parser.next()
653
f0497716
FC
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
665def do_export(parser):
95b0c608 666 global parsed_refs, dirname
f0497716
FC
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
f0497716 684 for ref, revid in parsed_refs.iteritems():
4c008199
FC
685 if ref.startswith('refs/heads/'):
686 name = ref[len('refs/heads/'):]
687 branch = bzrlib.branch.Branch.open(branches[name])
688 branch.generate_revision_history(revid, marks.get_tip(name))
715d64fe 689
4c008199
FC
690 if name in peers:
691 peer = bzrlib.branch.Branch.open(peers[name])
692 try:
693 peer.bzrdir.push_branch(branch, revision_id=revid)
694 except bzrlib.errors.DivergedBranches:
695 print "error %s non-fast forward" % ref
696 continue
95b0c608 697
4c008199
FC
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
715d64fe 710
f0497716 711 print "ok %s" % ref
75301a45 712
f0497716
FC
713 print
714
bee118ec 715def do_capabilities(parser):
f0497716
FC
716 global dirname
717
bee118ec 718 print "import"
f0497716 719 print "export"
bee118ec 720 print "refspec refs/heads/*:%s/heads/*" % prefix
afad2005 721 print "refspec refs/tags/*:%s/tags/*" % prefix
f0497716
FC
722
723 path = os.path.join(dirname, 'marks-git')
724
725 if os.path.exists(path):
726 print "*import-marks %s" % path
727 print "*export-marks %s" % path
728
bee118ec
FC
729 print
730
fa7285dc
FC
731def ref_is_valid(name):
732 return not True in [c in name for c in '~^: \\']
733
bee118ec
FC
734def do_list(parser):
735 global tags
8954441a 736
95b0c608
FC
737 master_branch = None
738
739 for name in branches:
740 if not master_branch:
741 master_branch = name
742 print "? refs/heads/%s" % name
743
d421c02b 744 branch = bzrlib.branch.Branch.open(branches[master_branch])
fa7285dc
FC
745 branch.lock_read()
746 for tag, revid in branch.tags.get_tag_dict().items():
747 try:
748 branch.revision_id_to_dotted_revno(revid)
749 except bzrlib.errors.NoSuchRevision:
750 continue
751 if not ref_is_valid(tag):
8954441a 752 continue
bee118ec
FC
753 print "? refs/tags/%s" % tag
754 tags[tag] = revid
fa7285dc 755 branch.unlock()
95b0c608
FC
756
757 print "@refs/heads/%s HEAD" % master_branch
bee118ec
FC
758 print
759
95b0c608
FC
760def get_remote_branch(origin, remote_branch, name):
761 global dirname, peers
762
763 branch_path = os.path.join(dirname, 'clone', name)
764 if os.path.exists(branch_path):
765 # pull
766 d = bzrlib.bzrdir.BzrDir.open(branch_path)
767 branch = d.open_branch()
768 try:
769 branch.pull(remote_branch, [], None, False)
770 except bzrlib.errors.DivergedBranches:
771 # use remote branch for now
772 return remote_branch
773 else:
774 # clone
775 d = origin.sprout(branch_path, None,
776 hardlink=True, create_tree_if_local=False,
777 force_new_repo=False,
778 source_branch=remote_branch)
779 branch = d.open_branch()
780
781 return branch
782
248663c4 783def find_branches(repo, wanted):
850dd25c
FC
784 transport = repo.user_transport
785
786 for fn in transport.iter_files_recursive():
787 if not fn.endswith('.bzr/branch-format'):
788 continue
789
790 name = subdir = fn[:-len('/.bzr/branch-format')]
791 name = name if name != '' else 'master'
792 name = name.replace('/', '+')
793
248663c4
FC
794 if wanted and not name in wanted:
795 continue
796
850dd25c
FC
797 try:
798 cur = transport.clone(subdir)
799 branch = bzrlib.branch.Branch.open_from_transport(cur)
800 except bzrlib.errors.NotBranchError:
801 continue
802 else:
803 yield name, branch
804
bee118ec 805def get_repo(url, alias):
95b0c608 806 global dirname, peer, branches
dc2177c2 807
95b0c608 808 normal_url = bzrlib.urlutils.normalize_url(url)
570e7ecd 809 origin = bzrlib.bzrdir.BzrDir.open(url)
95b0c608
FC
810 is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
811
c95c35f4
FC
812 shared_path = os.path.join(gitdir, 'bzr')
813 try:
814 shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path)
815 except bzrlib.errors.NotBranchError:
816 shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path)
817 try:
818 shared_repo = shared_dir.open_repository()
819 except bzrlib.errors.NoRepositoryPresent:
820 shared_repo = shared_dir.create_repository(shared=True)
821
822 if not is_local:
823 clone_path = os.path.join(dirname, 'clone')
824 if not os.path.exists(clone_path):
825 os.mkdir(clone_path)
95b0c608
FC
826
827 try:
828 repo = origin.open_repository()
829 except bzrlib.errors.NoRepositoryPresent:
830 # branch
831
832 name = 'master'
d421c02b 833 remote_branch = origin.open_branch()
95b0c608
FC
834
835 if not is_local:
d421c02b
FC
836 peers[name] = remote_branch.base
837 branch = get_remote_branch(origin, remote_branch, name)
073c3ffa 838 else:
d421c02b
FC
839 branch = remote_branch
840
841 branches[name] = branch.base
95b0c608
FC
842
843 return branch.repository
dc2177c2 844 else:
95b0c608 845 # repository
dc2177c2 846
248663c4
FC
847 wanted = get_config('remote-bzr.branches').rstrip().split(', ')
848 # stupid python
849 wanted = [e for e in wanted if e]
850
d421c02b 851 for name, remote_branch in find_branches(repo, wanted):
95b0c608
FC
852
853 if not is_local:
d421c02b
FC
854 peers[name] = remote_branch.base
855 branch = get_remote_branch(origin, remote_branch, name)
95b0c608 856 else:
d421c02b
FC
857 branch = remote_branch
858
859 branches[name] = branch.base
95b0c608
FC
860
861 return repo
bee118ec 862
d82c912c
FC
863def fix_path(alias, orig_url):
864 url = urlparse.urlparse(orig_url, 'file')
865 if url.scheme != 'file' or os.path.isabs(url.path):
866 return
867 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
868 cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
869 subprocess.call(cmd)
870
bee118ec 871def main(args):
c95c35f4 872 global marks, prefix, gitdir, dirname
bee118ec 873 global tags, filenodes
f0497716
FC
874 global blob_marks
875 global parsed_refs
876 global files_cache
aa938456 877 global is_tmp
95b0c608 878 global branches, peers
bee118ec
FC
879
880 alias = args[1]
881 url = args[2]
882
bee118ec
FC
883 tags = {}
884 filenodes = {}
f0497716
FC
885 blob_marks = {}
886 parsed_refs = {}
887 files_cache = {}
d6bb9136 888 marks = None
95b0c608
FC
889 branches = {}
890 peers = {}
bee118ec 891
aa938456
FC
892 if alias[5:] == url:
893 is_tmp = True
894 alias = hashlib.sha1(alias).hexdigest()
895 else:
896 is_tmp = False
897
898 prefix = 'refs/bzr/%s' % alias
bee118ec
FC
899 gitdir = os.environ['GIT_DIR']
900 dirname = os.path.join(gitdir, 'bzr', alias)
901
d82c912c
FC
902 if not is_tmp:
903 fix_path(alias, url)
904
bee118ec
FC
905 if not os.path.exists(dirname):
906 os.makedirs(dirname)
907
4d74cd47
FC
908 bzrlib.ui.ui_factory.be_quiet(True)
909
bee118ec
FC
910 repo = get_repo(url, alias)
911
912 marks_path = os.path.join(dirname, 'marks-int')
913 marks = Marks(marks_path)
914
915 parser = Parser(repo)
916 for line in parser:
917 if parser.check('capabilities'):
918 do_capabilities(parser)
919 elif parser.check('list'):
920 do_list(parser)
921 elif parser.check('import'):
922 do_import(parser)
f0497716
FC
923 elif parser.check('export'):
924 do_export(parser)
bee118ec
FC
925 else:
926 die('unhandled command: %s' % line)
927 sys.stdout.flush()
928
d6bb9136
FC
929def bye():
930 if not marks:
931 return
aa938456
FC
932 if not is_tmp:
933 marks.store()
934 else:
935 shutil.rmtree(dirname)
bee118ec 936
d6bb9136 937atexit.register(bye)
bee118ec 938sys.exit(main(sys.argv))