##// END OF EJS Templates
wireproto: refactor list of nodeid encoding / decoding
Benoit Boissinot -
r11597:9141d2c9 default
parent child Browse files
Show More
@@ -1,283 +1,290 b''
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import urllib, tempfile, os
8 import urllib, tempfile, os
9 from i18n import _
9 from i18n import _
10 from node import bin, hex
10 from node import bin, hex
11 import changegroup as changegroupmod
11 import changegroup as changegroupmod
12 import streamclone, repo, error, encoding, util
12 import streamclone, repo, error, encoding, util
13 import pushkey as pushkey_
13 import pushkey as pushkey_
14
14
15 # list of nodes encoding / decoding
16
17 def decodelist(l, sep=' '):
18 return map(bin, l.split(sep))
19
20 def encodelist(l, sep=' '):
21 return sep.join(map(hex, l))
22
15 # client side
23 # client side
16
24
17 class wirerepository(repo.repository):
25 class wirerepository(repo.repository):
18 def lookup(self, key):
26 def lookup(self, key):
19 self.requirecap('lookup', _('look up remote revision'))
27 self.requirecap('lookup', _('look up remote revision'))
20 d = self._call("lookup", key=key)
28 d = self._call("lookup", key=key)
21 success, data = d[:-1].split(" ", 1)
29 success, data = d[:-1].split(" ", 1)
22 if int(success):
30 if int(success):
23 return bin(data)
31 return bin(data)
24 self._abort(error.RepoError(data))
32 self._abort(error.RepoError(data))
25
33
26 def heads(self):
34 def heads(self):
27 d = self._call("heads")
35 d = self._call("heads")
28 try:
36 try:
29 return map(bin, d[:-1].split(" "))
37 return decodelist(d[:-1])
30 except:
38 except:
31 self.abort(error.ResponseError(_("unexpected response:"), d))
39 self.abort(error.ResponseError(_("unexpected response:"), d))
32
40
33 def branchmap(self):
41 def branchmap(self):
34 d = self._call("branchmap")
42 d = self._call("branchmap")
35 try:
43 try:
36 branchmap = {}
44 branchmap = {}
37 for branchpart in d.splitlines():
45 for branchpart in d.splitlines():
38 branchheads = branchpart.split(' ')
46 branchname, branchheads = branchpart.split(' ', 1)
39 branchname = urllib.unquote(branchheads[0])
47 branchname = urllib.unquote(branchname)
40 # Earlier servers (1.3.x) send branch names in (their) local
48 # Earlier servers (1.3.x) send branch names in (their) local
41 # charset. The best we can do is assume it's identical to our
49 # charset. The best we can do is assume it's identical to our
42 # own local charset, in case it's not utf-8.
50 # own local charset, in case it's not utf-8.
43 try:
51 try:
44 branchname.decode('utf-8')
52 branchname.decode('utf-8')
45 except UnicodeDecodeError:
53 except UnicodeDecodeError:
46 branchname = encoding.fromlocal(branchname)
54 branchname = encoding.fromlocal(branchname)
47 branchheads = [bin(x) for x in branchheads[1:]]
55 branchheads = decodelist(branchheads)
48 branchmap[branchname] = branchheads
56 branchmap[branchname] = branchheads
49 return branchmap
57 return branchmap
50 except TypeError:
58 except TypeError:
51 self._abort(error.ResponseError(_("unexpected response:"), d))
59 self._abort(error.ResponseError(_("unexpected response:"), d))
52
60
53 def branches(self, nodes):
61 def branches(self, nodes):
54 n = " ".join(map(hex, nodes))
62 n = encodelist(nodes)
55 d = self._call("branches", nodes=n)
63 d = self._call("branches", nodes=n)
56 try:
64 try:
57 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
65 br = [tuple(decodelist(b)) for b in d.splitlines()]
58 return br
66 return br
59 except:
67 except:
60 self._abort(error.ResponseError(_("unexpected response:"), d))
68 self._abort(error.ResponseError(_("unexpected response:"), d))
61
69
62 def between(self, pairs):
70 def between(self, pairs):
63 batch = 8 # avoid giant requests
71 batch = 8 # avoid giant requests
64 r = []
72 r = []
65 for i in xrange(0, len(pairs), batch):
73 for i in xrange(0, len(pairs), batch):
66 n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
74 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
67 d = self._call("between", pairs=n)
75 d = self._call("between", pairs=n)
68 try:
76 try:
69 r += [l and map(bin, l.split(" ")) or []
77 r.extend(l and decodelist(l) or [] for l in d.splitlines())
70 for l in d.splitlines()]
71 except:
78 except:
72 self._abort(error.ResponseError(_("unexpected response:"), d))
79 self._abort(error.ResponseError(_("unexpected response:"), d))
73 return r
80 return r
74
81
75 def pushkey(self, namespace, key, old, new):
82 def pushkey(self, namespace, key, old, new):
76 if not self.capable('pushkey'):
83 if not self.capable('pushkey'):
77 return False
84 return False
78 d = self._call("pushkey",
85 d = self._call("pushkey",
79 namespace=namespace, key=key, old=old, new=new)
86 namespace=namespace, key=key, old=old, new=new)
80 return bool(int(d))
87 return bool(int(d))
81
88
82 def listkeys(self, namespace):
89 def listkeys(self, namespace):
83 if not self.capable('pushkey'):
90 if not self.capable('pushkey'):
84 return {}
91 return {}
85 d = self._call("listkeys", namespace=namespace)
92 d = self._call("listkeys", namespace=namespace)
86 r = {}
93 r = {}
87 for l in d.splitlines():
94 for l in d.splitlines():
88 k, v = l.split('\t')
95 k, v = l.split('\t')
89 r[k.decode('string-escape')] = v.decode('string-escape')
96 r[k.decode('string-escape')] = v.decode('string-escape')
90 return r
97 return r
91
98
92 def stream_out(self):
99 def stream_out(self):
93 return self._callstream('stream_out')
100 return self._callstream('stream_out')
94
101
95 def changegroup(self, nodes, kind):
102 def changegroup(self, nodes, kind):
96 n = " ".join(map(hex, nodes))
103 n = encodelist(nodes)
97 f = self._callstream("changegroup", roots=n)
104 f = self._callstream("changegroup", roots=n)
98 return self._decompress(f)
105 return self._decompress(f)
99
106
100 def changegroupsubset(self, bases, heads, kind):
107 def changegroupsubset(self, bases, heads, kind):
101 self.requirecap('changegroupsubset', _('look up remote changes'))
108 self.requirecap('changegroupsubset', _('look up remote changes'))
102 bases = " ".join(map(hex, bases))
109 bases = encodelist(bases)
103 heads = " ".join(map(hex, heads))
110 heads = encodelist(heads)
104 return self._decompress(self._callstream("changegroupsubset",
111 return self._decompress(self._callstream("changegroupsubset",
105 bases=bases, heads=heads))
112 bases=bases, heads=heads))
106
113
107 def unbundle(self, cg, heads, source):
114 def unbundle(self, cg, heads, source):
108 '''Send cg (a readable file-like object representing the
115 '''Send cg (a readable file-like object representing the
109 changegroup to push, typically a chunkbuffer object) to the
116 changegroup to push, typically a chunkbuffer object) to the
110 remote server as a bundle. Return an integer indicating the
117 remote server as a bundle. Return an integer indicating the
111 result of the push (see localrepository.addchangegroup()).'''
118 result of the push (see localrepository.addchangegroup()).'''
112
119
113 ret, output = self._callpush("unbundle", cg, heads=' '.join(map(hex, heads)))
120 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
114 if ret == "":
121 if ret == "":
115 raise error.ResponseError(
122 raise error.ResponseError(
116 _('push failed:'), output)
123 _('push failed:'), output)
117 try:
124 try:
118 ret = int(ret)
125 ret = int(ret)
119 except ValueError, err:
126 except ValueError, err:
120 raise error.ResponseError(
127 raise error.ResponseError(
121 _('push failed (unexpected response):'), ret)
128 _('push failed (unexpected response):'), ret)
122
129
123 for l in output.splitlines(True):
130 for l in output.splitlines(True):
124 self.ui.status(_('remote: '), l)
131 self.ui.status(_('remote: '), l)
125 return ret
132 return ret
126
133
127 # server side
134 # server side
128
135
129 def dispatch(repo, proto, command):
136 def dispatch(repo, proto, command):
130 if command not in commands:
137 if command not in commands:
131 return False
138 return False
132 func, spec = commands[command]
139 func, spec = commands[command]
133 args = proto.getargs(spec)
140 args = proto.getargs(spec)
134 r = func(repo, proto, *args)
141 r = func(repo, proto, *args)
135 if r != None:
142 if r != None:
136 proto.respond(r)
143 proto.respond(r)
137 return True
144 return True
138
145
139 def between(repo, proto, pairs):
146 def between(repo, proto, pairs):
140 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
147 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
141 r = []
148 r = []
142 for b in repo.between(pairs):
149 for b in repo.between(pairs):
143 r.append(" ".join(map(hex, b)) + "\n")
150 r.append(encodelist(b) + "\n")
144 return "".join(r)
151 return "".join(r)
145
152
146 def branchmap(repo, proto):
153 def branchmap(repo, proto):
147 branchmap = repo.branchmap()
154 branchmap = repo.branchmap()
148 heads = []
155 heads = []
149 for branch, nodes in branchmap.iteritems():
156 for branch, nodes in branchmap.iteritems():
150 branchname = urllib.quote(branch)
157 branchname = urllib.quote(branch)
151 branchnodes = [hex(node) for node in nodes]
158 branchnodes = encodelist(nodes)
152 heads.append('%s %s' % (branchname, ' '.join(branchnodes)))
159 heads.append('%s %s' % (branchname, branchnodes))
153 return '\n'.join(heads)
160 return '\n'.join(heads)
154
161
155 def branches(repo, proto, nodes):
162 def branches(repo, proto, nodes):
156 nodes = map(bin, nodes.split(" "))
163 nodes = decodelist(nodes)
157 r = []
164 r = []
158 for b in repo.branches(nodes):
165 for b in repo.branches(nodes):
159 r.append(" ".join(map(hex, b)) + "\n")
166 r.append(encodelist(b) + "\n")
160 return "".join(r)
167 return "".join(r)
161
168
162 def capabilities(repo, proto):
169 def capabilities(repo, proto):
163 caps = 'lookup changegroupsubset branchmap pushkey'.split()
170 caps = 'lookup changegroupsubset branchmap pushkey'.split()
164 if streamclone.allowed(repo.ui):
171 if streamclone.allowed(repo.ui):
165 caps.append('stream=%d' % repo.changelog.version)
172 caps.append('stream=%d' % repo.changelog.version)
166 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
173 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
167 return ' '.join(caps)
174 return ' '.join(caps)
168
175
169 def changegroup(repo, proto, roots):
176 def changegroup(repo, proto, roots):
170 nodes = map(bin, roots.split(" "))
177 nodes = decodelist(roots)
171 cg = repo.changegroup(nodes, 'serve')
178 cg = repo.changegroup(nodes, 'serve')
172 proto.sendchangegroup(cg)
179 proto.sendchangegroup(cg)
173
180
174 def changegroupsubset(repo, proto, bases, heads):
181 def changegroupsubset(repo, proto, bases, heads):
175 bases = [bin(n) for n in bases.split(' ')]
182 bases = decodelist(bases)
176 heads = [bin(n) for n in heads.split(' ')]
183 heads = decodelist(heads)
177 cg = repo.changegroupsubset(bases, heads, 'serve')
184 cg = repo.changegroupsubset(bases, heads, 'serve')
178 proto.sendchangegroup(cg)
185 proto.sendchangegroup(cg)
179
186
180 def heads(repo, proto):
187 def heads(repo, proto):
181 h = repo.heads()
188 h = repo.heads()
182 return " ".join(map(hex, h)) + "\n"
189 return encodelist(h) + "\n"
183
190
184 def hello(repo, proto):
191 def hello(repo, proto):
185 '''the hello command returns a set of lines describing various
192 '''the hello command returns a set of lines describing various
186 interesting things about the server, in an RFC822-like format.
193 interesting things about the server, in an RFC822-like format.
187 Currently the only one defined is "capabilities", which
194 Currently the only one defined is "capabilities", which
188 consists of a line in the form:
195 consists of a line in the form:
189
196
190 capabilities: space separated list of tokens
197 capabilities: space separated list of tokens
191 '''
198 '''
192 return "capabilities: %s\n" % (capabilities(repo, proto))
199 return "capabilities: %s\n" % (capabilities(repo, proto))
193
200
194 def listkeys(repo, proto, namespace):
201 def listkeys(repo, proto, namespace):
195 d = pushkey_.list(repo, namespace).items()
202 d = pushkey_.list(repo, namespace).items()
196 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
203 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
197 v.encode('string-escape')) for k, v in d])
204 v.encode('string-escape')) for k, v in d])
198 return t
205 return t
199
206
200 def lookup(repo, proto, key):
207 def lookup(repo, proto, key):
201 try:
208 try:
202 r = hex(repo.lookup(key))
209 r = hex(repo.lookup(key))
203 success = 1
210 success = 1
204 except Exception, inst:
211 except Exception, inst:
205 r = str(inst)
212 r = str(inst)
206 success = 0
213 success = 0
207 return "%s %s\n" % (success, r)
214 return "%s %s\n" % (success, r)
208
215
209 def pushkey(repo, proto, namespace, key, old, new):
216 def pushkey(repo, proto, namespace, key, old, new):
210 r = pushkey_.push(repo, namespace, key, old, new)
217 r = pushkey_.push(repo, namespace, key, old, new)
211 return '%s\n' % int(r)
218 return '%s\n' % int(r)
212
219
213 def stream(repo, proto):
220 def stream(repo, proto):
214 try:
221 try:
215 proto.sendstream(streamclone.stream_out(repo))
222 proto.sendstream(streamclone.stream_out(repo))
216 except streamclone.StreamException, inst:
223 except streamclone.StreamException, inst:
217 return str(inst)
224 return str(inst)
218
225
219 def unbundle(repo, proto, heads):
226 def unbundle(repo, proto, heads):
220 their_heads = heads.split()
227 their_heads = decodelist(heads)
221
228
222 def check_heads():
229 def check_heads():
223 heads = map(hex, repo.heads())
230 heads = repo.heads()
224 return their_heads == [hex('force')] or their_heads == heads
231 return their_heads == ['force'] or their_heads == heads
225
232
226 # fail early if possible
233 # fail early if possible
227 if not check_heads():
234 if not check_heads():
228 repo.respond(_('unsynced changes'))
235 repo.respond(_('unsynced changes'))
229 return
236 return
230
237
231 # write bundle data to temporary file because it can be big
238 # write bundle data to temporary file because it can be big
232 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
239 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
233 fp = os.fdopen(fd, 'wb+')
240 fp = os.fdopen(fd, 'wb+')
234 r = 0
241 r = 0
235 proto.redirect()
242 proto.redirect()
236 try:
243 try:
237 proto.getfile(fp)
244 proto.getfile(fp)
238 lock = repo.lock()
245 lock = repo.lock()
239 try:
246 try:
240 if not check_heads():
247 if not check_heads():
241 # someone else committed/pushed/unbundled while we
248 # someone else committed/pushed/unbundled while we
242 # were transferring data
249 # were transferring data
243 proto.respond(_('unsynced changes'))
250 proto.respond(_('unsynced changes'))
244 return
251 return
245
252
246 # push can proceed
253 # push can proceed
247 fp.seek(0)
254 fp.seek(0)
248 header = fp.read(6)
255 header = fp.read(6)
249 if header.startswith('HG'):
256 if header.startswith('HG'):
250 if not header.startswith('HG10'):
257 if not header.startswith('HG10'):
251 raise ValueError('unknown bundle version')
258 raise ValueError('unknown bundle version')
252 elif header not in changegroupmod.bundletypes:
259 elif header not in changegroupmod.bundletypes:
253 raise ValueError('unknown bundle compression type')
260 raise ValueError('unknown bundle compression type')
254 gen = changegroupmod.unbundle(header, fp)
261 gen = changegroupmod.unbundle(header, fp)
255
262
256 try:
263 try:
257 r = repo.addchangegroup(gen, 'serve', proto._client(),
264 r = repo.addchangegroup(gen, 'serve', proto._client(),
258 lock=lock)
265 lock=lock)
259 except util.Abort, inst:
266 except util.Abort, inst:
260 sys.stderr.write("abort: %s\n" % inst)
267 sys.stderr.write("abort: %s\n" % inst)
261 finally:
268 finally:
262 lock.release()
269 lock.release()
263 proto.respondpush(r)
270 proto.respondpush(r)
264
271
265 finally:
272 finally:
266 fp.close()
273 fp.close()
267 os.unlink(tempname)
274 os.unlink(tempname)
268
275
269 commands = {
276 commands = {
270 'between': (between, 'pairs'),
277 'between': (between, 'pairs'),
271 'branchmap': (branchmap, ''),
278 'branchmap': (branchmap, ''),
272 'branches': (branches, 'nodes'),
279 'branches': (branches, 'nodes'),
273 'capabilities': (capabilities, ''),
280 'capabilities': (capabilities, ''),
274 'changegroup': (changegroup, 'roots'),
281 'changegroup': (changegroup, 'roots'),
275 'changegroupsubset': (changegroupsubset, 'bases heads'),
282 'changegroupsubset': (changegroupsubset, 'bases heads'),
276 'heads': (heads, ''),
283 'heads': (heads, ''),
277 'hello': (hello, ''),
284 'hello': (hello, ''),
278 'listkeys': (listkeys, 'namespace'),
285 'listkeys': (listkeys, 'namespace'),
279 'lookup': (lookup, 'key'),
286 'lookup': (lookup, 'key'),
280 'pushkey': (pushkey, 'namespace key old new'),
287 'pushkey': (pushkey, 'namespace key old new'),
281 'stream_out': (stream, ''),
288 'stream_out': (stream, ''),
282 'unbundle': (unbundle, 'heads'),
289 'unbundle': (unbundle, 'heads'),
283 }
290 }
General Comments 0
You need to be logged in to leave comments. Login now