Merge branch 'jk/commit-dates-parsing-fix' into maint
[git] / contrib / remote-helpers / git-remote-bzr
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2012 Felipe Contreras
4 #
5
6 #
7 # Just copy to your ~/bin, or anywhere in your $PATH.
8 # Then you can clone with:
9 # % git clone bzr::/path/to/bzr/repo/or/url
10 #
11 # For example:
12 # % git clone bzr::$HOME/myrepo
13 # or
14 # % git clone bzr::lp:myrepo
15 #
16 # If you want to specify which branches you want to track (per repo):
17 # % git config remote.origin.bzr-branches 'trunk, devel, test'
18 #
19 # Where 'origin' is the name of the repository you want to specify the
20 # branches.
21 #
22
23 import sys
24
25 import bzrlib
26 if hasattr(bzrlib, "initialize"):
27     bzrlib.initialize()
28
29 import bzrlib.plugin
30 bzrlib.plugin.load_plugins()
31
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
38
39 import sys
40 import os
41 import json
42 import re
43 import StringIO
44 import atexit, shutil, hashlib, urlparse, subprocess
45
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+)')
50
51 def die(msg, *args):
52     sys.stderr.write('ERROR: %s\n' % (msg % args))
53     sys.exit(1)
54
55 def warn(msg, *args):
56     sys.stderr.write('WARNING: %s\n' % (msg % args))
57
58 def gittz(tz):
59     return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
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
66
67 class Marks:
68
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()
76
77     def load(self):
78         if not os.path.exists(self.path):
79             return
80
81         tmp = json.load(open(self.path))
82         self.tips = tmp['tips']
83         self.marks = tmp['marks']
84         self.last_mark = tmp['last-mark']
85
86         for rev, mark in self.marks.iteritems():
87             self.rev_marks[mark] = rev
88
89     def dict(self):
90         return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
91
92     def store(self):
93         json.dump(self.dict(), open(self.path, 'w'))
94
95     def __str__(self):
96         return str(self.dict())
97
98     def from_rev(self, rev):
99         return self.marks[rev]
100
101     def to_rev(self, mark):
102         return str(self.rev_marks[mark])
103
104     def next_mark(self):
105         self.last_mark += 1
106         return self.last_mark
107
108     def get_mark(self, rev):
109         self.last_mark += 1
110         self.marks[rev] = self.last_mark
111         return self.last_mark
112
113     def is_marked(self, rev):
114         return rev in self.marks
115
116     def new_mark(self, rev, mark):
117         self.marks[rev] = mark
118         self.rev_marks[mark] = rev
119         self.last_mark = mark
120
121     def get_tip(self, branch):
122         try:
123             return str(self.tips[branch])
124         except KeyError:
125             return None
126
127     def set_tip(self, branch, tip):
128         self.tips[branch] = tip
129
130 class Parser:
131
132     def __init__(self, repo):
133         self.repo = repo
134         self.line = self.get_line()
135
136     def get_line(self):
137         return sys.stdin.readline().strip()
138
139     def __getitem__(self, i):
140         return self.line.split()[i]
141
142     def check(self, word):
143         return self.line.startswith(word)
144
145     def each_block(self, separator):
146         while self.line != separator:
147             yield self.line
148             self.line = self.get_line()
149
150     def __iter__(self):
151         return self.each_block('')
152
153     def next(self):
154         self.line = self.get_line()
155         if self.line == 'done':
156             self.line = None
157
158     def get_mark(self):
159         i = self.line.index(':') + 1
160         return int(self.line[i:])
161
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)
168
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)
179
180 def rev_to_mark(rev):
181     return marks.from_rev(rev)
182
183 def mark_to_rev(mark):
184     return marks.to_rev(mark)
185
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()
201
202     if not name:
203         name = 'unknown'
204     if not mail:
205         mail = 'Unknown'
206
207     return '%s <%s>' % (name, mail)
208
209 def get_filechanges(cur, prev):
210     modified = {}
211     removed = {}
212
213     changes = cur.changes_from(prev)
214
215     def u(s):
216         return s.encode('utf-8')
217
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
233
234     return modified, removed
235
236 def export_files(tree, files):
237     final = []
238     for path, fid in files.iteritems():
239         kind = tree.kind(fid)
240
241         h = tree.get_file_sha1(fid)
242
243         if kind == 'symlink':
244             d = tree.get_symlink_target(fid)
245             mode = '120000'
246         elif kind == 'file':
247
248             if tree.is_executable(fid):
249                 mode = '100755'
250             else:
251                 mode = '100644'
252
253             # is the blob already exported?
254             if h in filenodes:
255                 mark = filenodes[h]
256                 final.append((mode, mark, path))
257                 continue
258
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))
264
265         mark = marks.next_mark()
266         filenodes[h] = mark
267
268         print "blob"
269         print "mark :%u" % mark
270         print "data %d" % len(d)
271         print d
272
273         final.append((mode, mark, path))
274
275     return final
276
277 def export_branch(repo, name):
278     ref = '%s/heads/%s' % (prefix, name)
279     tip = marks.get_tip(name)
280
281     branch = get_remote_branch(name)
282     repo = branch.repository
283
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
293
294     for revid, _, seq, _ in revs:
295
296         if marks.is_marked(revid):
297             continue
298
299         rev = repo.get_revision(revid)
300         revno = seq[0]
301
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')
314
315         msg += '\n'
316
317         if len(parents) == 0:
318             parent = bzrlib.revision.NULL_REVISION
319         else:
320             parent = parents[0]
321
322         cur_tree = repo.revision_tree(revid)
323         prev = repo.revision_tree(parent)
324         modified, removed = get_filechanges(cur_tree, prev)
325
326         modified_final = export_files(cur_tree, modified)
327
328         if len(parents) == 0:
329             print 'reset %s' % ref
330
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
337
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
348
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
354
355         if len(seq) > 1:
356             # let's skip branch revisions from the progress report
357             continue
358
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)
365
366     branch.unlock()
367
368     revid = branch.last_revision()
369
370     # make sure the ref is updated
371     print "reset %s" % ref
372     print "from :%u" % rev_to_mark(revid)
373     print
374
375     marks.set_tip(name, revid)
376
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
382
383 def do_import(parser):
384     repo = parser.repo
385     path = os.path.join(dirname, 'marks-git')
386
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()
393
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()
403
404     print 'done'
405
406     sys.stdout.flush()
407
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()
415
416 class CustomTree():
417
418     def __init__(self, branch, revid, parents, files):
419         self.updates = {}
420         self.branch = branch
421
422         def copy_tree(revid):
423             files = files_cache[revid] = {}
424             branch.lock_read()
425             tree = branch.repository.revision_tree(revid)
426             try:
427                 for path, entry in tree.iter_entries_by_dir():
428                     files[path] = [entry.file_id, None]
429             finally:
430                 branch.unlock()
431             return files
432
433         if len(parents) == 0:
434             self.base_id = bzrlib.revision.NULL_REVISION
435             self.base_files = {}
436         else:
437             self.base_id = parents[0]
438             self.base_files = files_cache.get(self.base_id, None)
439             if not self.base_files:
440                 self.base_files = copy_tree(self.base_id)
441
442         self.files = files_cache[revid] = self.base_files.copy()
443         self.rev_files = {}
444
445         for path, data in self.files.iteritems():
446             fid, mark = data
447             self.rev_files[fid] = [path, mark]
448
449         for path, f in files.iteritems():
450             fid, mark = self.files.get(path, [None, None])
451             if not fid:
452                 fid = bzrlib.generate_ids.gen_file_id(path)
453             f['path'] = path
454             self.rev_files[fid] = [path, mark]
455             self.updates[fid] = f
456
457     def last_revision(self):
458         return self.base_id
459
460     def iter_changes(self):
461         changes = []
462
463         def get_parent(dirname, basename):
464             parent_fid, mark = self.base_files.get(dirname, [None, None])
465             if parent_fid:
466                 return parent_fid
467             parent_fid, mark = self.files.get(dirname, [None, None])
468             if parent_fid:
469                 return parent_fid
470             if basename == '':
471                 return None
472             fid = bzrlib.generate_ids.gen_file_id(path)
473             add_entry(fid, dirname, 'directory')
474             return fid
475
476         def add_entry(fid, path, kind, mode=None):
477             dirname, basename = os.path.split(path)
478             parent_fid = get_parent(dirname, basename)
479
480             executable = False
481             if mode == '100755':
482                 executable = True
483             elif mode == '120000':
484                 kind = 'symlink'
485
486             change = (fid,
487                     (None, path),
488                     True,
489                     (False, True),
490                     (None, parent_fid),
491                     (None, basename),
492                     (None, kind),
493                     (None, executable))
494             self.files[path] = [change[0], None]
495             changes.append(change)
496
497         def update_entry(fid, path, kind, mode=None):
498             dirname, basename = os.path.split(path)
499             parent_fid = get_parent(dirname, basename)
500
501             executable = False
502             if mode == '100755':
503                 executable = True
504             elif mode == '120000':
505                 kind = 'symlink'
506
507             change = (fid,
508                     (path, path),
509                     True,
510                     (True, True),
511                     (None, parent_fid),
512                     (None, basename),
513                     (None, kind),
514                     (None, executable))
515             self.files[path] = [change[0], None]
516             changes.append(change)
517
518         def remove_entry(fid, path, kind):
519             dirname, basename = os.path.split(path)
520             parent_fid = get_parent(dirname, basename)
521             change = (fid,
522                     (path, None),
523                     True,
524                     (True, False),
525                     (parent_fid, None),
526                     (None, None),
527                     (None, None),
528                     (None, None))
529             del self.files[path]
530             changes.append(change)
531
532         for fid, f in self.updates.iteritems():
533             path = f['path']
534
535             if 'deleted' in f:
536                 remove_entry(fid, path, 'file')
537                 continue
538
539             if path in self.base_files:
540                 update_entry(fid, path, 'file', f['mode'])
541             else:
542                 add_entry(fid, path, 'file', f['mode'])
543
544             self.files[path][1] = f['mark']
545             self.rev_files[fid][1] = f['mark']
546
547         return changes
548
549     def get_content(self, file_id):
550         path, mark = self.rev_files[file_id]
551         if mark:
552             return blob_marks[mark]
553
554         # last resort
555         tree = self.branch.repository.revision_tree(self.base_id)
556         return tree.get_file_text(file_id)
557
558     def get_file_with_stat(self, file_id, path=None):
559         content = self.get_content(file_id)
560         return (StringIO.StringIO(content), None)
561
562     def get_symlink_target(self, file_id):
563         return self.get_content(file_id)
564
565     def id2path(self, file_id):
566         path, mark = self.rev_files[file_id]
567         return path
568
569 def c_style_unescape(string):
570     if string[0] == string[-1] == '"':
571         return string.decode('string-escape')[1:-1]
572     return string
573
574 def parse_commit(parser):
575     parents = []
576
577     ref = parser[1]
578     parser.next()
579
580     if ref.startswith('refs/heads/'):
581         name = ref[len('refs/heads/'):]
582         branch = get_remote_branch(name)
583     else:
584         die('unknown ref')
585
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()
600
601     # fast-export adds an extra newline
602     if data[-1] == '\n':
603         data = data[:-1]
604
605     files = {}
606
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
619
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
625
626     mtree = CustomTree(branch, revid, parents, files)
627     changes = mtree.iter_changes()
628
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()
641
642     parsed_refs[ref] = revid
643     marks.new_mark(revid, commit_mark)
644
645 def parse_reset(parser):
646     ref = parser[1]
647     parser.next()
648
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()
657
658     parsed_refs[ref] = mark_to_rev(from_mark)
659
660 def do_export(parser):
661     parser.next()
662
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)
676
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))
682
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                 except bzrlib.errors.DivergedBranches:
689                     print "error %s non-fast forward" % ref
690                     continue
691
692             try:
693                 wt = branch.bzrdir.open_workingtree()
694                 wt.update()
695             except bzrlib.errors.NoWorkingTree:
696                 pass
697         elif ref.startswith('refs/tags/'):
698             # TODO: implement tag push
699             print "error %s pushing tags not supported" % ref
700             continue
701         else:
702             # transport-helper/fast-export bugs
703             continue
704
705         print "ok %s" % ref
706
707     print
708
709 def do_capabilities(parser):
710     print "import"
711     print "export"
712     print "refspec refs/heads/*:%s/heads/*" % prefix
713     print "refspec refs/tags/*:%s/tags/*" % prefix
714
715     path = os.path.join(dirname, 'marks-git')
716
717     if os.path.exists(path):
718         print "*import-marks %s" % path
719     print "*export-marks %s" % path
720
721     print
722
723 def ref_is_valid(name):
724     return not True in [c in name for c in '~^: \\']
725
726 def do_list(parser):
727     master_branch = None
728
729     for name in branches:
730         if not master_branch:
731             master_branch = name
732         print "? refs/heads/%s" % name
733
734     branch = get_remote_branch(master_branch)
735     branch.lock_read()
736     for tag, revid in branch.tags.get_tag_dict().items():
737         try:
738             branch.revision_id_to_dotted_revno(revid)
739         except bzrlib.errors.NoSuchRevision:
740             continue
741         if not ref_is_valid(tag):
742             continue
743         print "? refs/tags/%s" % tag
744         tags[tag] = revid
745     branch.unlock()
746
747     print "@refs/heads/%s HEAD" % master_branch
748     print
749
750 def clone(path, remote_branch):
751     try:
752         bdir = bzrlib.bzrdir.BzrDir.create(path, possible_transports=transports)
753     except bzrlib.errors.AlreadyControlDirError:
754         bdir = bzrlib.bzrdir.BzrDir.open(path, possible_transports=transports)
755     repo = bdir.find_repository()
756     repo.fetch(remote_branch.repository)
757     return remote_branch.sprout(bdir, repository=repo)
758
759 def get_remote_branch(name):
760     remote_branch = bzrlib.branch.Branch.open(branches[name],
761                                               possible_transports=transports)
762     if isinstance(remote_branch.user_transport, bzrlib.transport.local.LocalTransport):
763         return remote_branch
764
765     branch_path = os.path.join(dirname, 'clone', name)
766
767     try:
768         branch = bzrlib.branch.Branch.open(branch_path,
769                                            possible_transports=transports)
770     except bzrlib.errors.NotBranchError:
771         # clone
772         branch = clone(branch_path, remote_branch)
773     else:
774         # pull
775         try:
776             branch.pull(remote_branch, overwrite=True)
777         except bzrlib.errors.DivergedBranches:
778             # use remote branch for now
779             return remote_branch
780
781     return branch
782
783 def find_branches(repo):
784     transport = repo.bzrdir.root_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
794         try:
795             cur = transport.clone(subdir)
796             branch = bzrlib.branch.Branch.open_from_transport(cur)
797         except bzrlib.errors.NotBranchError:
798             continue
799         else:
800             yield name, branch.base
801
802 def get_repo(url, alias):
803     normal_url = bzrlib.urlutils.normalize_url(url)
804     origin = bzrlib.bzrdir.BzrDir.open(url, possible_transports=transports)
805     is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
806
807     shared_path = os.path.join(gitdir, 'bzr')
808     try:
809         shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path,
810                                                possible_transports=transports)
811     except bzrlib.errors.NotBranchError:
812         shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path,
813                                                  possible_transports=transports)
814     try:
815         shared_repo = shared_dir.open_repository()
816     except bzrlib.errors.NoRepositoryPresent:
817         shared_repo = shared_dir.create_repository(shared=True)
818
819     if not is_local:
820         clone_path = os.path.join(dirname, 'clone')
821         if not os.path.exists(clone_path):
822             os.mkdir(clone_path)
823         else:
824             # check and remove old organization
825             try:
826                 bdir = bzrlib.bzrdir.BzrDir.open(clone_path,
827                                                  possible_transports=transports)
828                 bdir.destroy_repository()
829             except bzrlib.errors.NotBranchError:
830                 pass
831             except bzrlib.errors.NoRepositoryPresent:
832                 pass
833
834     wanted = get_config('remote.%s.bzr-branches' % alias).rstrip().split(', ')
835     # stupid python
836     wanted = [e for e in wanted if e]
837     if not wanted:
838         wanted = get_config('remote-bzr.branches').rstrip().split(', ')
839         # stupid python
840         wanted = [e for e in wanted if e]
841
842     if not wanted:
843         try:
844             repo = origin.open_repository()
845             if not repo.user_transport.listable():
846                 # this repository is not usable for us
847                 raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
848         except bzrlib.errors.NoRepositoryPresent:
849             wanted = ['master']
850
851     if wanted:
852         def list_wanted(url, wanted):
853             for name in wanted:
854                 subdir = name if name != 'master' else ''
855                 yield name, bzrlib.urlutils.join(url, subdir)
856
857         branch_list = list_wanted(url, wanted)
858     else:
859         branch_list = find_branches(repo)
860
861     for name, url in branch_list:
862         if not is_local:
863             peers[name] = url
864         branches[name] = url
865
866     return origin
867
868 def fix_path(alias, orig_url):
869     url = urlparse.urlparse(orig_url, 'file')
870     if url.scheme != 'file' or os.path.isabs(url.path):
871         return
872     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
873     cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url]
874     subprocess.call(cmd)
875
876 def main(args):
877     global marks, prefix, gitdir, dirname
878     global tags, filenodes
879     global blob_marks
880     global parsed_refs
881     global files_cache
882     global is_tmp
883     global branches, peers
884     global transports
885
886     marks = None
887     is_tmp = False
888     gitdir = os.environ.get('GIT_DIR', None)
889
890     if len(args) < 3:
891         die('Not enough arguments.')
892
893     if not gitdir:
894         die('GIT_DIR not set')
895
896     alias = args[1]
897     url = args[2]
898
899     tags = {}
900     filenodes = {}
901     blob_marks = {}
902     parsed_refs = {}
903     files_cache = {}
904     branches = {}
905     peers = {}
906     transports = []
907
908     if alias[5:] == url:
909         is_tmp = True
910         alias = hashlib.sha1(alias).hexdigest()
911
912     prefix = 'refs/bzr/%s' % alias
913     dirname = os.path.join(gitdir, 'bzr', alias)
914
915     if not is_tmp:
916         fix_path(alias, url)
917
918     if not os.path.exists(dirname):
919         os.makedirs(dirname)
920
921     if hasattr(bzrlib.ui.ui_factory, 'be_quiet'):
922         bzrlib.ui.ui_factory.be_quiet(True)
923
924     repo = get_repo(url, alias)
925
926     marks_path = os.path.join(dirname, 'marks-int')
927     marks = Marks(marks_path)
928
929     parser = Parser(repo)
930     for line in parser:
931         if parser.check('capabilities'):
932             do_capabilities(parser)
933         elif parser.check('list'):
934             do_list(parser)
935         elif parser.check('import'):
936             do_import(parser)
937         elif parser.check('export'):
938             do_export(parser)
939         else:
940             die('unhandled command: %s' % line)
941         sys.stdout.flush()
942
943 def bye():
944     if not marks:
945         return
946     if not is_tmp:
947         marks.store()
948     else:
949         shutil.rmtree(dirname)
950
951 atexit.register(bye)
952 sys.exit(main(sys.argv))