Add new remote-bzr transport helper
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
bee118ec048911cfacdd65ad1ea2bdd6e25a6254
1 file changed
+352
contrib/remote-helpers/git-remote-bzr
new
+352
@@ -0,0 +1,352 @@
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
+
17
+import sys
18
+
19
+import bzrlib
20
+bzrlib.initialize()
21
+
22
+import bzrlib.plugin
23
+bzrlib.plugin.load_plugins()
24
+
25
+import sys
26
+import os
27
+import json
28
+import re
29
+
30
+NAME_RE = re.compile('^([^<>]+)')
31
+AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
32
+
33
+def die(msg, *args):
34
+ sys.stderr.write('ERROR: %s\n' % (msg % args))
35
+ sys.exit(1)
36
+
37
+def warn(msg, *args):
38
+ sys.stderr.write('WARNING: %s\n' % (msg % args))
39
+
40
+def gittz(tz):
41
+ return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
42
+
43
+class Marks:
44
+
45
+ def __init__(self, path):
46
+ self.path = path
47
+ self.tips = {}
48
+ self.marks = {}
49
+ self.last_mark = 0
50
+ self.load()
51
+
52
+ def load(self):
53
+ if not os.path.exists(self.path):
54
+ return
55
+
56
+ tmp = json.load(open(self.path))
57
+ self.tips = tmp['tips']
58
+ self.marks = tmp['marks']
59
+ self.last_mark = tmp['last-mark']
60
+
61
+ def dict(self):
62
+ return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
63
+
64
+ def store(self):
65
+ json.dump(self.dict(), open(self.path, 'w'))
66
+
67
+ def __str__(self):
68
+ return str(self.dict())
69
+
70
+ def from_rev(self, rev):
71
+ return self.marks[rev]
72
+
73
+ def next_mark(self):
74
+ self.last_mark += 1
75
+ return self.last_mark
76
+
77
+ def get_mark(self, rev):
78
+ self.last_mark += 1
79
+ self.marks[rev] = self.last_mark
80
+ return self.last_mark
81
+
82
+ def is_marked(self, rev):
83
+ return self.marks.has_key(rev)
84
+
85
+ def get_tip(self, branch):
86
+ return self.tips.get(branch, None)
87
+
88
+ def set_tip(self, branch, tip):
89
+ self.tips[branch] = tip
90
+
91
+class Parser:
92
+
93
+ def __init__(self, repo):
94
+ self.repo = repo
95
+ self.line = self.get_line()
96
+
97
+ def get_line(self):
98
+ return sys.stdin.readline().strip()
99
+
100
+ def __getitem__(self, i):
101
+ return self.line.split()[i]
102
+
103
+ def check(self, word):
104
+ return self.line.startswith(word)
105
+
106
+ def each_block(self, separator):
107
+ while self.line != separator:
108
+ yield self.line
109
+ self.line = self.get_line()
110
+
111
+ def __iter__(self):
112
+ return self.each_block('')
113
+
114
+ def next(self):
115
+ self.line = self.get_line()
116
+ if self.line == 'done':
117
+ self.line = None
118
+
119
+def rev_to_mark(rev):
120
+ global marks
121
+ return marks.from_rev(rev)
122
+
123
+def fixup_user(user):
124
+ name = mail = None
125
+ user = user.replace('"', '')
126
+ m = AUTHOR_RE.match(user)
127
+ if m:
128
+ name = m.group(1)
129
+ mail = m.group(2).strip()
130
+ else:
131
+ m = NAME_RE.match(user)
132
+ if m:
133
+ name = m.group(1).strip()
134
+
135
+ return '%s <%s>' % (name, mail)
136
+
137
+def get_filechanges(cur, prev):
138
+ modified = {}
139
+ removed = {}
140
+
141
+ changes = cur.changes_from(prev)
142
+
143
+ for path, fid, kind in changes.added:
144
+ modified[path] = fid
145
+ for path, fid, kind in changes.removed:
146
+ removed[path] = None
147
+ for path, fid, kind, mod, _ in changes.modified:
148
+ modified[path] = fid
149
+ for oldpath, newpath, fid, kind, mod, _ in changes.renamed:
150
+ removed[oldpath] = None
151
+ modified[newpath] = fid
152
+
153
+ return modified, removed
154
+
155
+def export_files(tree, files):
156
+ global marks, filenodes
157
+
158
+ final = []
159
+ for path, fid in files.iteritems():
160
+ h = tree.get_file_sha1(fid)
161
+
162
+ mode = '100644'
163
+
164
+ # is the blob already exported?
165
+ if h in filenodes:
166
+ mark = filenodes[h]
167
+ else:
168
+ d = tree.get_file_text(fid)
169
+
170
+ mark = marks.next_mark()
171
+ filenodes[h] = mark
172
+
173
+ print "blob"
174
+ print "mark :%u" % mark
175
+ print "data %d" % len(d)
176
+ print d
177
+
178
+ final.append((mode, mark, path))
179
+
180
+ return final
181
+
182
+def export_branch(branch, name):
183
+ global prefix, dirname
184
+
185
+ ref = '%s/heads/%s' % (prefix, name)
186
+ tip = marks.get_tip(name)
187
+
188
+ repo = branch.repository
189
+ repo.lock_read()
190
+ revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
191
+ count = 0
192
+
193
+ revs = [revid for revid, _, _, _ in revs if not marks.is_marked(revid)]
194
+
195
+ for revid in revs:
196
+
197
+ rev = repo.get_revision(revid)
198
+
199
+ parents = rev.parent_ids
200
+ time = rev.timestamp
201
+ tz = rev.timezone
202
+ committer = rev.committer.encode('utf-8')
203
+ committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
204
+ author = committer
205
+ msg = rev.message.encode('utf-8')
206
+
207
+ msg += '\n'
208
+
209
+ if len(parents) == 0:
210
+ parent = bzrlib.revision.NULL_REVISION
211
+ else:
212
+ parent = parents[0]
213
+
214
+ cur_tree = repo.revision_tree(revid)
215
+ prev = repo.revision_tree(parent)
216
+ modified, removed = get_filechanges(cur_tree, prev)
217
+
218
+ modified_final = export_files(cur_tree, modified)
219
+
220
+ if len(parents) == 0:
221
+ print 'reset %s' % ref
222
+
223
+ print "commit %s" % ref
224
+ print "mark :%d" % (marks.get_mark(revid))
225
+ print "author %s" % (author)
226
+ print "committer %s" % (committer)
227
+ print "data %d" % (len(msg))
228
+ print msg
229
+
230
+ for i, p in enumerate(parents):
231
+ try:
232
+ m = rev_to_mark(p)
233
+ except KeyError:
234
+ # ghost?
235
+ continue
236
+ if i == 0:
237
+ print "from :%s" % m
238
+ else:
239
+ print "merge :%s" % m
240
+
241
+ for f in modified_final:
242
+ print "M %s :%u %s" % f
243
+ for f in removed:
244
+ print "D %s" % (f)
245
+ print
246
+
247
+ count += 1
248
+ if (count % 100 == 0):
249
+ print "progress revision %s (%d/%d)" % (revid, count, len(revs))
250
+ print "#############################################################"
251
+
252
+ repo.unlock()
253
+
254
+ revid = branch.last_revision()
255
+
256
+ # make sure the ref is updated
257
+ print "reset %s" % ref
258
+ print "from :%u" % rev_to_mark(revid)
259
+ print
260
+
261
+ marks.set_tip(name, revid)
262
+
263
+def export_tag(repo, name):
264
+ global tags
265
+ try:
266
+ print "reset refs/tags/%s" % name
267
+ print "from :%u" % rev_to_mark(tags[name])
268
+ print
269
+ except KeyError:
270
+ warn("TODO: fetch tag '%s'" % name)
271
+
272
+def do_import(parser):
273
+ global dirname
274
+
275
+ branch = parser.repo
276
+ path = os.path.join(dirname, 'marks-git')
277
+
278
+ print "feature done"
279
+ if os.path.exists(path):
280
+ print "feature import-marks=%s" % path
281
+ print "feature export-marks=%s" % path
282
+ sys.stdout.flush()
283
+
284
+ while parser.check('import'):
285
+ ref = parser[1]
286
+ if ref.startswith('refs/heads/'):
287
+ name = ref[len('refs/heads/'):]
288
+ export_branch(branch, name)
289
+ if ref.startswith('refs/tags/'):
290
+ name = ref[len('refs/tags/'):]
291
+ export_tag(branch, name)
292
+ parser.next()
293
+
294
+ print 'done'
295
+
296
+ sys.stdout.flush()
297
+
298
+def do_capabilities(parser):
299
+ print "import"
300
+ print "refspec refs/heads/*:%s/heads/*" % prefix
301
+ print
302
+
303
+def do_list(parser):
304
+ global tags
305
+ print "? refs/heads/%s" % 'master'
306
+ for tag, revid in parser.repo.tags.get_tag_dict().items():
307
+ print "? refs/tags/%s" % tag
308
+ tags[tag] = revid
309
+ print "@refs/heads/%s HEAD" % 'master'
310
+ print
311
+
312
+def get_repo(url, alias):
313
+ origin = bzrlib.controldir.ControlDir.open(url)
314
+ return origin.open_branch()
315
+
316
+def main(args):
317
+ global marks, prefix, dirname
318
+ global tags, filenodes
319
+
320
+ alias = args[1]
321
+ url = args[2]
322
+
323
+ prefix = 'refs/bzr/%s' % alias
324
+ tags = {}
325
+ filenodes = {}
326
+
327
+ gitdir = os.environ['GIT_DIR']
328
+ dirname = os.path.join(gitdir, 'bzr', alias)
329
+
330
+ if not os.path.exists(dirname):
331
+ os.makedirs(dirname)
332
+
333
+ repo = get_repo(url, alias)
334
+
335
+ marks_path = os.path.join(dirname, 'marks-int')
336
+ marks = Marks(marks_path)
337
+
338
+ parser = Parser(repo)
339
+ for line in parser:
340
+ if parser.check('capabilities'):
341
+ do_capabilities(parser)
342
+ elif parser.check('list'):
343
+ do_list(parser)
344
+ elif parser.check('import'):
345
+ do_import(parser)
346
+ else:
347
+ die('unhandled command: %s' % line)
348
+ sys.stdout.flush()
349
+
350
+ marks.store()
351
+
352
+sys.exit(main(sys.argv))