remote-bzr: add support for pushing
Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Felipe Contreras committed
Nov 11, 2012 at 15:19 UTC
f04977168f520a50b51794c17b443e256a61b4ec
1 file changed
+295
contrib/remote-helpers/git-remote-bzr
+295
@@ -22,13 +22,17 @@ bzrlib.initialize()
22
import bzrlib.plugin
23
bzrlib.plugin.load_plugins()
24
25
+import bzrlib.generate_ids
26
+
27
import sys
28
import os
29
import json
30
import re
31
+import StringIO
32
33
NAME_RE = re.compile('^([^<>]+)')
34
AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
35
+RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
36
37
def die(msg, *args):
38
sys.stderr.write('ERROR: %s\n' % (msg % args))
@@ -46,6 +50,7 @@ class Marks:
50
self.path = path
51
self.tips = {}
52
self.marks = {}
53
+ self.rev_marks = {}
54
self.last_mark = 0
55
self.load()
56
@@ -58,6 +63,9 @@ class Marks:
63
self.marks = tmp['marks']
64
self.last_mark = tmp['last-mark']
65
66
+ for rev, mark in self.marks.iteritems():
67
+ self.rev_marks[mark] = rev
68
+
69
def dict(self):
70
return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
71
@@ -70,6 +78,9 @@ class Marks:
78
def from_rev(self, rev):
79
return self.marks[rev]
80
81
+ def to_rev(self, mark):
82
+ return self.rev_marks[mark]
83
+
84
def next_mark(self):
85
self.last_mark += 1
86
return self.last_mark
@@ -82,6 +93,11 @@ class Marks:
93
def is_marked(self, rev):
94
return self.marks.has_key(rev)
95
96
+ def new_mark(self, rev, mark):
97
+ self.marks[rev] = mark
98
+ self.rev_marks[mark] = rev
99
+ self.last_mark = mark
100
+
101
def get_tip(self, branch):
102
return self.tips.get(branch, None)
103
@@ -116,10 +132,35 @@ class Parser:
132
if self.line == 'done':
133
self.line = None
134
135
+ def get_mark(self):
136
+ i = self.line.index(':') + 1
137
+ return int(self.line[i:])
138
+
139
+ def get_data(self):
140
+ if not self.check('data'):
141
+ return None
142
+ i = self.line.index(' ') + 1
143
+ size = int(self.line[i:])
144
+ return sys.stdin.read(size)
145
+
146
+ def get_author(self):
147
+ m = RAW_AUTHOR_RE.match(self.line)
148
+ if not m:
149
+ return None
150
+ _, name, email, date, tz = m.groups()
151
+ committer = '%s <%s>' % (name, email)
152
+ tz = int(tz)
153
+ tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
154
+ return (committer, int(date), tz)
155
+
156
def rev_to_mark(rev):
157
global marks
158
return marks.from_rev(rev)
159
160
+def mark_to_rev(mark):
161
+ global marks
162
+ return marks.to_rev(mark)
163
+
164
def fixup_user(user):
165
name = mail = None
166
user = user.replace('"', '')
@@ -295,9 +336,255 @@ def do_import(parser):
336
337
sys.stdout.flush()
338
339
+def parse_blob(parser):
340
+ global blob_marks
341
+
342
+ parser.next()
343
+ mark = parser.get_mark()
344
+ parser.next()
345
+ data = parser.get_data()
346
+ blob_marks[mark] = data
347
+ parser.next()
348
+
349
+class CustomTree():
350
+
351
+ def __init__(self, repo, revid, parents, files):
352
+ global files_cache
353
+
354
+ self.repo = repo
355
+ self.revid = revid
356
+ self.parents = parents
357
+ self.updates = files
358
+
359
+ def copy_tree(revid):
360
+ files = files_cache[revid] = {}
361
+ tree = repo.repository.revision_tree(revid)
362
+ repo.lock_read()
363
+ try:
364
+ for path, entry in tree.iter_entries_by_dir():
365
+ files[path] = entry.file_id
366
+ finally:
367
+ repo.unlock()
368
+ return files
369
+
370
+ if len(parents) == 0:
371
+ self.base_id = bzrlib.revision.NULL_REVISION
372
+ self.base_files = {}
373
+ else:
374
+ self.base_id = parents[0]
375
+ self.base_files = files_cache.get(self.base_id, None)
376
+ if not self.base_files:
377
+ self.base_files = copy_tree(self.base_id)
378
+
379
+ self.files = files_cache[revid] = self.base_files.copy()
380
+
381
+ def last_revision(self):
382
+ return self.base_id
383
+
384
+ def iter_changes(self):
385
+ changes = []
386
+
387
+ def get_parent(dirname, basename):
388
+ parent_fid = self.base_files.get(dirname, None)
389
+ if parent_fid:
390
+ return parent_fid
391
+ parent_fid = self.files.get(dirname, None)
392
+ if parent_fid:
393
+ return parent_fid
394
+ if basename == '':
395
+ return None
396
+ d = add_entry(dirname, 'directory')
397
+ return d[0]
398
+
399
+ def add_entry(path, kind):
400
+ dirname, basename = os.path.split(path)
401
+ parent_fid = get_parent(dirname, basename)
402
+ fid = bzrlib.generate_ids.gen_file_id(path)
403
+ change = (fid,
404
+ (None, path),
405
+ True,
406
+ (False, True),
407
+ (None, parent_fid),
408
+ (None, basename),
409
+ (None, kind),
410
+ (None, False))
411
+ self.files[path] = change[0]
412
+ changes.append(change)
413
+ return change
414
+
415
+ def update_entry(path, kind):
416
+ dirname, basename = os.path.split(path)
417
+ fid = self.base_files[path]
418
+ parent_fid = get_parent(dirname, basename)
419
+ change = (fid,
420
+ (path, path),
421
+ True,
422
+ (True, True),
423
+ (None, parent_fid),
424
+ (None, basename),
425
+ (None, kind),
426
+ (None, False))
427
+ self.files[path] = change[0]
428
+ changes.append(change)
429
+ return change
430
+
431
+ def remove_entry(path, kind):
432
+ dirname, basename = os.path.split(path)
433
+ fid = self.base_files[path]
434
+ parent_fid = get_parent(dirname, basename)
435
+ change = (fid,
436
+ (path, None),
437
+ True,
438
+ (True, False),
439
+ (parent_fid, None),
440
+ (None, None),
441
+ (None, None),
442
+ (None, None))
443
+ del self.files[path]
444
+ changes.append(change)
445
+ return change
446
+
447
+ for path, f in self.updates.iteritems():
448
+ if 'deleted' in f:
449
+ remove_entry(path, 'file')
450
+ elif path in self.base_files:
451
+ update_entry(path, 'file')
452
+ else:
453
+ add_entry(path, 'file')
454
+
455
+ return changes
456
+
457
+ def get_file_with_stat(self, file_id, path=None):
458
+ return (StringIO.StringIO(self.updates[path]['data']), None)
459
+
460
+def parse_commit(parser):
461
+ global marks, blob_marks, bmarks, parsed_refs
462
+ global mode
463
+
464
+ parents = []
465
+
466
+ ref = parser[1]
467
+ parser.next()
468
+
469
+ if ref != 'refs/heads/master':
470
+ die("bzr doesn't support multiple branches; use 'master'")
471
+
472
+ commit_mark = parser.get_mark()
473
+ parser.next()
474
+ author = parser.get_author()
475
+ parser.next()
476
+ committer = parser.get_author()
477
+ parser.next()
478
+ data = parser.get_data()
479
+ parser.next()
480
+ if parser.check('from'):
481
+ parents.append(parser.get_mark())
482
+ parser.next()
483
+ while parser.check('merge'):
484
+ parents.append(parser.get_mark())
485
+ parser.next()
486
+
487
+ files = {}
488
+
489
+ for line in parser:
490
+ if parser.check('M'):
491
+ t, m, mark_ref, path = line.split(' ', 3)
492
+ mark = int(mark_ref[1:])
493
+ f = { 'mode' : m, 'data' : blob_marks[mark] }
494
+ elif parser.check('D'):
495
+ t, path = line.split(' ')
496
+ f = { 'deleted' : True }
497
+ else:
498
+ die('Unknown file command: %s' % line)
499
+ files[path] = f
500
+
501
+ repo = parser.repo
502
+
503
+ committer, date, tz = committer
504
+ parents = [str(mark_to_rev(p)) for p in parents]
505
+ revid = bzrlib.generate_ids.gen_revision_id(committer, date)
506
+ props = {}
507
+ props['branch-nick'] = repo.nick
508
+
509
+ mtree = CustomTree(repo, revid, parents, files)
510
+ changes = mtree.iter_changes()
511
+
512
+ repo.lock_write()
513
+ try:
514
+ builder = repo.get_commit_builder(parents, None, date, tz, committer, props, revid, False)
515
+ try:
516
+ list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
517
+ builder.finish_inventory()
518
+ builder.commit(data.decode('utf-8', 'replace'))
519
+ except Exception, e:
520
+ builder.abort()
521
+ raise
522
+ finally:
523
+ repo.unlock()
524
+
525
+ parsed_refs[ref] = revid
526
+ marks.new_mark(revid, commit_mark)
527
+
528
+def parse_reset(parser):
529
+ global parsed_refs
530
+
531
+ ref = parser[1]
532
+ parser.next()
533
+
534
+ if ref != 'refs/heads/master':
535
+ die("bzr doesn't support multiple branches; use 'master'")
536
+
537
+ # ugh
538
+ if parser.check('commit'):
539
+ parse_commit(parser)
540
+ return
541
+ if not parser.check('from'):
542
+ return
543
+ from_mark = parser.get_mark()
544
+ parser.next()
545
+
546
+ parsed_refs[ref] = mark_to_rev(from_mark)
547
+
548
+def do_export(parser):
549
+ global parsed_refs, dirname
550
+
551
+ parser.next()
552
+
553
+ for line in parser.each_block('done'):
554
+ if parser.check('blob'):
555
+ parse_blob(parser)
556
+ elif parser.check('commit'):
557
+ parse_commit(parser)
558
+ elif parser.check('reset'):
559
+ parse_reset(parser)
560
+ elif parser.check('tag'):
561
+ pass
562
+ elif parser.check('feature'):
563
+ pass
564
+ else:
565
+ die('unhandled export command: %s' % line)
566
+
567
+ repo = parser.repo
568
+
569
+ for ref, revid in parsed_refs.iteritems():
570
+ if ref == 'refs/heads/master':
571
+ repo.generate_revision_history(revid, marks.get_tip('master'))
572
+ print "ok %s" % ref
573
+ print
574
+
575
def do_capabilities(parser):
576
+ global dirname
577
+
578
print "import"
579
+ print "export"
580
print "refspec refs/heads/*:%s/heads/*" % prefix
581
+
582
+ path = os.path.join(dirname, 'marks-git')
583
+
584
+ if os.path.exists(path):
585
+ print "*import-marks %s" % path
586
+ print "*export-marks %s" % path
587
+
588
print
589
590
def do_list(parser):
@@ -316,6 +603,9 @@ def get_repo(url, alias):
603
def main(args):
604
global marks, prefix, dirname
605
global tags, filenodes
606
+ global blob_marks
607
+ global parsed_refs
608
+ global files_cache
609
610
alias = args[1]
611
url = args[2]
@@ -323,6 +613,9 @@ def main(args):
613
prefix = 'refs/bzr/%s' % alias
614
tags = {}
615
filenodes = {}
616
+ blob_marks = {}
617
+ parsed_refs = {}
618
+ files_cache = {}
619
620
gitdir = os.environ['GIT_DIR']
621
dirname = os.path.join(gitdir, 'bzr', alias)
@@ -343,6 +636,8 @@ def main(args):
636
do_list(parser)
637
elif parser.check('import'):
638
do_import(parser)
639
+ elif parser.check('export'):
640
+ do_export(parser)
641
else:
642
die('unhandled command: %s' % line)
643
sys.stdout.flush()