##// END OF EJS Templates
protocol: move basic ssh client commands to wirerepository
Matt Mackall -
r11586:ddaaaa23 default
parent child Browse files
Show More
@@ -1,293 +1,230 b''
1 # sshrepo.py - ssh repository proxy class for mercurial
1 # sshrepo.py - ssh repository proxy class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 from node import bin, hex
8 from node import bin, hex
9 from i18n import _
9 from i18n import _
10 import repo, util, error, encoding
10 import repo, util, error, encoding, wireproto
11 import re, urllib
11 import re, urllib
12
12
13 class remotelock(object):
13 class remotelock(object):
14 def __init__(self, repo):
14 def __init__(self, repo):
15 self.repo = repo
15 self.repo = repo
16 def release(self):
16 def release(self):
17 self.repo.unlock()
17 self.repo.unlock()
18 self.repo = None
18 self.repo = None
19 def __del__(self):
19 def __del__(self):
20 if self.repo:
20 if self.repo:
21 self.release()
21 self.release()
22
22
23 class sshrepository(repo.repository):
23 class sshrepository(wireproto.wirerepository):
24 def __init__(self, ui, path, create=0):
24 def __init__(self, ui, path, create=0):
25 self._url = path
25 self._url = path
26 self.ui = ui
26 self.ui = ui
27
27
28 m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
28 m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
29 if not m:
29 if not m:
30 self.abort(error.RepoError(_("couldn't parse location %s") % path))
30 self.abort(error.RepoError(_("couldn't parse location %s") % path))
31
31
32 self.user = m.group(2)
32 self.user = m.group(2)
33 self.host = m.group(3)
33 self.host = m.group(3)
34 self.port = m.group(5)
34 self.port = m.group(5)
35 self.path = m.group(7) or "."
35 self.path = m.group(7) or "."
36
36
37 sshcmd = self.ui.config("ui", "ssh", "ssh")
37 sshcmd = self.ui.config("ui", "ssh", "ssh")
38 remotecmd = self.ui.config("ui", "remotecmd", "hg")
38 remotecmd = self.ui.config("ui", "remotecmd", "hg")
39
39
40 args = util.sshargs(sshcmd, self.host, self.user, self.port)
40 args = util.sshargs(sshcmd, self.host, self.user, self.port)
41
41
42 if create:
42 if create:
43 cmd = '%s %s "%s init %s"'
43 cmd = '%s %s "%s init %s"'
44 cmd = cmd % (sshcmd, args, remotecmd, self.path)
44 cmd = cmd % (sshcmd, args, remotecmd, self.path)
45
45
46 ui.note(_('running %s\n') % cmd)
46 ui.note(_('running %s\n') % cmd)
47 res = util.system(cmd)
47 res = util.system(cmd)
48 if res != 0:
48 if res != 0:
49 self.abort(error.RepoError(_("could not create remote repo")))
49 self.abort(error.RepoError(_("could not create remote repo")))
50
50
51 self.validate_repo(ui, sshcmd, args, remotecmd)
51 self.validate_repo(ui, sshcmd, args, remotecmd)
52
52
53 def url(self):
53 def url(self):
54 return self._url
54 return self._url
55
55
56 def validate_repo(self, ui, sshcmd, args, remotecmd):
56 def validate_repo(self, ui, sshcmd, args, remotecmd):
57 # cleanup up previous run
57 # cleanup up previous run
58 self.cleanup()
58 self.cleanup()
59
59
60 cmd = '%s %s "%s -R %s serve --stdio"'
60 cmd = '%s %s "%s -R %s serve --stdio"'
61 cmd = cmd % (sshcmd, args, remotecmd, self.path)
61 cmd = cmd % (sshcmd, args, remotecmd, self.path)
62
62
63 cmd = util.quotecommand(cmd)
63 cmd = util.quotecommand(cmd)
64 ui.note(_('running %s\n') % cmd)
64 ui.note(_('running %s\n') % cmd)
65 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
65 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
66
66
67 # skip any noise generated by remote shell
67 # skip any noise generated by remote shell
68 self.do_cmd("hello")
68 self.do_cmd("hello")
69 r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
69 r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
70 lines = ["", "dummy"]
70 lines = ["", "dummy"]
71 max_noise = 500
71 max_noise = 500
72 while lines[-1] and max_noise:
72 while lines[-1] and max_noise:
73 l = r.readline()
73 l = r.readline()
74 self.readerr()
74 self.readerr()
75 if lines[-1] == "1\n" and l == "\n":
75 if lines[-1] == "1\n" and l == "\n":
76 break
76 break
77 if l:
77 if l:
78 ui.debug("remote: ", l)
78 ui.debug("remote: ", l)
79 lines.append(l)
79 lines.append(l)
80 max_noise -= 1
80 max_noise -= 1
81 else:
81 else:
82 self.abort(error.RepoError(_("no suitable response from remote hg")))
82 self.abort(error.RepoError(_("no suitable response from remote hg")))
83
83
84 self.capabilities = set()
84 self.capabilities = set()
85 for l in reversed(lines):
85 for l in reversed(lines):
86 if l.startswith("capabilities:"):
86 if l.startswith("capabilities:"):
87 self.capabilities.update(l[:-1].split(":")[1].split())
87 self.capabilities.update(l[:-1].split(":")[1].split())
88 break
88 break
89
89
90 def readerr(self):
90 def readerr(self):
91 while 1:
91 while 1:
92 size = util.fstat(self.pipee).st_size
92 size = util.fstat(self.pipee).st_size
93 if size == 0:
93 if size == 0:
94 break
94 break
95 l = self.pipee.readline()
95 l = self.pipee.readline()
96 if not l:
96 if not l:
97 break
97 break
98 self.ui.status(_("remote: "), l)
98 self.ui.status(_("remote: "), l)
99
99
100 def abort(self, exception):
100 def abort(self, exception):
101 self.cleanup()
101 self.cleanup()
102 raise exception
102 raise exception
103
103
104 def _abort(self, exception):
105 self.cleanup()
106 raise exception
107
104 def cleanup(self):
108 def cleanup(self):
105 try:
109 try:
106 self.pipeo.close()
110 self.pipeo.close()
107 self.pipei.close()
111 self.pipei.close()
108 # read the error descriptor until EOF
112 # read the error descriptor until EOF
109 for l in self.pipee:
113 for l in self.pipee:
110 self.ui.status(_("remote: "), l)
114 self.ui.status(_("remote: "), l)
111 self.pipee.close()
115 self.pipee.close()
112 except:
116 except:
113 pass
117 pass
114
118
115 __del__ = cleanup
119 __del__ = cleanup
116
120
117 def do_cmd(self, cmd, **args):
121 def do_cmd(self, cmd, **args):
118 self.ui.debug("sending %s command\n" % cmd)
122 self.ui.debug("sending %s command\n" % cmd)
119 self.pipeo.write("%s\n" % cmd)
123 self.pipeo.write("%s\n" % cmd)
120 for k, v in sorted(args.iteritems()):
124 for k, v in sorted(args.iteritems()):
121 self.pipeo.write("%s %d\n" % (k, len(v)))
125 self.pipeo.write("%s %d\n" % (k, len(v)))
122 self.pipeo.write(v)
126 self.pipeo.write(v)
123 self.pipeo.flush()
127 self.pipeo.flush()
124
128
125 return self.pipei
129 return self.pipei
126
130
127 def call(self, cmd, **args):
131 def call(self, cmd, **args):
128 self.do_cmd(cmd, **args)
132 self.do_cmd(cmd, **args)
129 return self._recv()
133 return self._recv()
130
134
135 def _call(self, cmd, **args):
136 self.do_cmd(cmd, **args)
137 return self._recv()
138
131 def _recv(self):
139 def _recv(self):
132 l = self.pipei.readline()
140 l = self.pipei.readline()
133 self.readerr()
141 self.readerr()
134 try:
142 try:
135 l = int(l)
143 l = int(l)
136 except:
144 except:
137 self.abort(error.ResponseError(_("unexpected response:"), l))
145 self.abort(error.ResponseError(_("unexpected response:"), l))
138 return self.pipei.read(l)
146 return self.pipei.read(l)
139
147
140 def _send(self, data, flush=False):
148 def _send(self, data, flush=False):
141 self.pipeo.write("%d\n" % len(data))
149 self.pipeo.write("%d\n" % len(data))
142 if data:
150 if data:
143 self.pipeo.write(data)
151 self.pipeo.write(data)
144 if flush:
152 if flush:
145 self.pipeo.flush()
153 self.pipeo.flush()
146 self.readerr()
154 self.readerr()
147
155
148 def lock(self):
156 def lock(self):
149 self.call("lock")
157 self.call("lock")
150 return remotelock(self)
158 return remotelock(self)
151
159
152 def unlock(self):
160 def unlock(self):
153 self.call("unlock")
161 self.call("unlock")
154
162
155 def lookup(self, key):
156 self.requirecap('lookup', _('look up remote revision'))
157 d = self.call("lookup", key=key)
158 success, data = d[:-1].split(" ", 1)
159 if int(success):
160 return bin(data)
161 else:
162 self.abort(error.RepoError(data))
163
164 def heads(self):
165 d = self.call("heads")
166 try:
167 return map(bin, d[:-1].split(" "))
168 except:
169 self.abort(error.ResponseError(_("unexpected response:"), d))
170
171 def branchmap(self):
172 d = self.call("branchmap")
173 try:
174 branchmap = {}
175 for branchpart in d.splitlines():
176 branchheads = branchpart.split(' ')
177 branchname = urllib.unquote(branchheads[0])
178 # Earlier servers (1.3.x) send branch names in (their) local
179 # charset. The best we can do is assume it's identical to our
180 # own local charset, in case it's not utf-8.
181 try:
182 branchname.decode('utf-8')
183 except UnicodeDecodeError:
184 branchname = encoding.fromlocal(branchname)
185 branchheads = [bin(x) for x in branchheads[1:]]
186 branchmap[branchname] = branchheads
187 return branchmap
188 except:
189 raise error.ResponseError(_("unexpected response:"), d)
190
191 def branches(self, nodes):
192 n = " ".join(map(hex, nodes))
193 d = self.call("branches", nodes=n)
194 try:
195 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
196 return br
197 except:
198 self.abort(error.ResponseError(_("unexpected response:"), d))
199
200 def between(self, pairs):
201 n = " ".join(["-".join(map(hex, p)) for p in pairs])
202 d = self.call("between", pairs=n)
203 try:
204 p = [l and map(bin, l.split(" ")) or [] for l in d.splitlines()]
205 return p
206 except:
207 self.abort(error.ResponseError(_("unexpected response:"), d))
208
209 def changegroup(self, nodes, kind):
163 def changegroup(self, nodes, kind):
210 n = " ".join(map(hex, nodes))
164 n = " ".join(map(hex, nodes))
211 return self.do_cmd("changegroup", roots=n)
165 return self.do_cmd("changegroup", roots=n)
212
166
213 def changegroupsubset(self, bases, heads, kind):
167 def changegroupsubset(self, bases, heads, kind):
214 self.requirecap('changegroupsubset', _('look up remote changes'))
168 self.requirecap('changegroupsubset', _('look up remote changes'))
215 bases = " ".join(map(hex, bases))
169 bases = " ".join(map(hex, bases))
216 heads = " ".join(map(hex, heads))
170 heads = " ".join(map(hex, heads))
217 return self.do_cmd("changegroupsubset", bases=bases, heads=heads)
171 return self.do_cmd("changegroupsubset", bases=bases, heads=heads)
218
172
219 def unbundle(self, cg, heads, source):
173 def unbundle(self, cg, heads, source):
220 '''Send cg (a readable file-like object representing the
174 '''Send cg (a readable file-like object representing the
221 changegroup to push, typically a chunkbuffer object) to the
175 changegroup to push, typically a chunkbuffer object) to the
222 remote server as a bundle. Return an integer indicating the
176 remote server as a bundle. Return an integer indicating the
223 result of the push (see localrepository.addchangegroup()).'''
177 result of the push (see localrepository.addchangegroup()).'''
224 d = self.call("unbundle", heads=' '.join(map(hex, heads)))
178 d = self.call("unbundle", heads=' '.join(map(hex, heads)))
225 if d:
179 if d:
226 # remote may send "unsynced changes"
180 # remote may send "unsynced changes"
227 self.abort(error.RepoError(_("push refused: %s") % d))
181 self.abort(error.RepoError(_("push refused: %s") % d))
228
182
229 while 1:
183 while 1:
230 d = cg.read(4096)
184 d = cg.read(4096)
231 if not d:
185 if not d:
232 break
186 break
233 self._send(d)
187 self._send(d)
234
188
235 self._send("", flush=True)
189 self._send("", flush=True)
236
190
237 r = self._recv()
191 r = self._recv()
238 if r:
192 if r:
239 # remote may send "unsynced changes"
193 # remote may send "unsynced changes"
240 self.abort(error.RepoError(_("push failed: %s") % r))
194 self.abort(error.RepoError(_("push failed: %s") % r))
241
195
242 r = self._recv()
196 r = self._recv()
243 try:
197 try:
244 return int(r)
198 return int(r)
245 except:
199 except:
246 self.abort(error.ResponseError(_("unexpected response:"), r))
200 self.abort(error.ResponseError(_("unexpected response:"), r))
247
201
248 def addchangegroup(self, cg, source, url):
202 def addchangegroup(self, cg, source, url):
249 '''Send a changegroup to the remote server. Return an integer
203 '''Send a changegroup to the remote server. Return an integer
250 similar to unbundle(). DEPRECATED, since it requires locking the
204 similar to unbundle(). DEPRECATED, since it requires locking the
251 remote.'''
205 remote.'''
252 d = self.call("addchangegroup")
206 d = self.call("addchangegroup")
253 if d:
207 if d:
254 self.abort(error.RepoError(_("push refused: %s") % d))
208 self.abort(error.RepoError(_("push refused: %s") % d))
255 while 1:
209 while 1:
256 d = cg.read(4096)
210 d = cg.read(4096)
257 if not d:
211 if not d:
258 break
212 break
259 self.pipeo.write(d)
213 self.pipeo.write(d)
260 self.readerr()
214 self.readerr()
261
215
262 self.pipeo.flush()
216 self.pipeo.flush()
263
217
264 self.readerr()
218 self.readerr()
265 r = self._recv()
219 r = self._recv()
266 if not r:
220 if not r:
267 return 1
221 return 1
268 try:
222 try:
269 return int(r)
223 return int(r)
270 except:
224 except:
271 self.abort(error.ResponseError(_("unexpected response:"), r))
225 self.abort(error.ResponseError(_("unexpected response:"), r))
272
226
273 def stream_out(self):
227 def stream_out(self):
274 return self.do_cmd('stream_out')
228 return self.do_cmd('stream_out')
275
229
276 def pushkey(self, namespace, key, old, new):
277 if not self.capable('pushkey'):
278 return False
279 d = self.call("pushkey",
280 namespace=namespace, key=key, old=old, new=new)
281 return bool(int(d))
282
283 def listkeys(self, namespace):
284 if not self.capable('pushkey'):
285 return {}
286 d = self.call("listkeys", namespace=namespace)
287 r = {}
288 for l in d.splitlines():
289 k, v = l.split('\t')
290 r[k.decode('string-escape')] = v.decode('string-escape')
291 return r
292
293 instance = sshrepository
230 instance = sshrepository
@@ -1,97 +1,173 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 from i18n import _
8 from i18n import _
9 from node import bin, hex
9 from node import bin, hex
10 import urllib, streamclone
10 import urllib
11 import streamclone, repo, error, encoding
11 import pushkey as pushkey_
12 import pushkey as pushkey_
12
13
14 # client side
15
16 class wirerepository(repo.repository):
17 def lookup(self, key):
18 self.requirecap('lookup', _('look up remote revision'))
19 d = self._call("lookup", key=key)
20 success, data = d[:-1].split(" ", 1)
21 if int(success):
22 return bin(data)
23 self._abort(error.RepoError(data))
24
25 def heads(self):
26 d = self._call("heads")
27 try:
28 return map(bin, d[:-1].split(" "))
29 except:
30 self.abort(error.ResponseError(_("unexpected response:"), d))
31
32 def branchmap(self):
33 d = self._call("branchmap")
34 try:
35 branchmap = {}
36 for branchpart in d.splitlines():
37 branchheads = branchpart.split(' ')
38 branchname = urllib.unquote(branchheads[0])
39 # Earlier servers (1.3.x) send branch names in (their) local
40 # charset. The best we can do is assume it's identical to our
41 # own local charset, in case it's not utf-8.
42 try:
43 branchname.decode('utf-8')
44 except UnicodeDecodeError:
45 branchname = encoding.fromlocal(branchname)
46 branchheads = [bin(x) for x in branchheads[1:]]
47 branchmap[branchname] = branchheads
48 return branchmap
49 except TypeError:
50 self._abort(error.ResponseError(_("unexpected response:"), d))
51
52 def branches(self, nodes):
53 n = " ".join(map(hex, nodes))
54 d = self._call("branches", nodes=n)
55 try:
56 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
57 return br
58 except:
59 self._abort(error.ResponseError(_("unexpected response:"), d))
60
61 def between(self, pairs):
62 n = " ".join(["-".join(map(hex, p)) for p in pairs])
63 d = self._call("between", pairs=n)
64 try:
65 p = [l and map(bin, l.split(" ")) or [] for l in d.splitlines()]
66 return p
67 except:
68 self._abort(error.ResponseError(_("unexpected response:"), d))
69
70 def pushkey(self, namespace, key, old, new):
71 if not self.capable('pushkey'):
72 return False
73 d = self._call("pushkey",
74 namespace=namespace, key=key, old=old, new=new)
75 return bool(int(d))
76
77 def listkeys(self, namespace):
78 if not self.capable('pushkey'):
79 return {}
80 d = self._call("listkeys", namespace=namespace)
81 r = {}
82 for l in d.splitlines():
83 k, v = l.split('\t')
84 r[k.decode('string-escape')] = v.decode('string-escape')
85 return r
86
87 # server side
88
13 def dispatch(repo, proto, command):
89 def dispatch(repo, proto, command):
14 if command not in commands:
90 if command not in commands:
15 return False
91 return False
16 func, spec = commands[command]
92 func, spec = commands[command]
17 args = proto.getargs(spec)
93 args = proto.getargs(spec)
18 r = func(repo, proto, *args)
94 r = func(repo, proto, *args)
19 if r != None:
95 if r != None:
20 proto.respond(r)
96 proto.respond(r)
21 return True
97 return True
22
98
23 def between(repo, proto, pairs):
99 def between(repo, proto, pairs):
24 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
100 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
25 r = []
101 r = []
26 for b in repo.between(pairs):
102 for b in repo.between(pairs):
27 r.append(" ".join(map(hex, b)) + "\n")
103 r.append(" ".join(map(hex, b)) + "\n")
28 return "".join(r)
104 return "".join(r)
29
105
30 def branchmap(repo, proto):
106 def branchmap(repo, proto):
31 branchmap = repo.branchmap()
107 branchmap = repo.branchmap()
32 heads = []
108 heads = []
33 for branch, nodes in branchmap.iteritems():
109 for branch, nodes in branchmap.iteritems():
34 branchname = urllib.quote(branch)
110 branchname = urllib.quote(branch)
35 branchnodes = [hex(node) for node in nodes]
111 branchnodes = [hex(node) for node in nodes]
36 heads.append('%s %s' % (branchname, ' '.join(branchnodes)))
112 heads.append('%s %s' % (branchname, ' '.join(branchnodes)))
37 return '\n'.join(heads)
113 return '\n'.join(heads)
38
114
39 def branches(repo, proto, nodes):
115 def branches(repo, proto, nodes):
40 nodes = map(bin, nodes.split(" "))
116 nodes = map(bin, nodes.split(" "))
41 r = []
117 r = []
42 for b in repo.branches(nodes):
118 for b in repo.branches(nodes):
43 r.append(" ".join(map(hex, b)) + "\n")
119 r.append(" ".join(map(hex, b)) + "\n")
44 return "".join(r)
120 return "".join(r)
45
121
46 def changegroup(repo, proto, roots):
122 def changegroup(repo, proto, roots):
47 nodes = map(bin, roots.split(" "))
123 nodes = map(bin, roots.split(" "))
48 cg = repo.changegroup(nodes, 'serve')
124 cg = repo.changegroup(nodes, 'serve')
49 proto.sendchangegroup(cg)
125 proto.sendchangegroup(cg)
50
126
51 def changegroupsubset(repo, proto, bases, heads):
127 def changegroupsubset(repo, proto, bases, heads):
52 bases = [bin(n) for n in bases.split(' ')]
128 bases = [bin(n) for n in bases.split(' ')]
53 heads = [bin(n) for n in heads.split(' ')]
129 heads = [bin(n) for n in heads.split(' ')]
54 cg = repo.changegroupsubset(bases, heads, 'serve')
130 cg = repo.changegroupsubset(bases, heads, 'serve')
55 proto.sendchangegroup(cg)
131 proto.sendchangegroup(cg)
56
132
57 def heads(repo, proto):
133 def heads(repo, proto):
58 h = repo.heads()
134 h = repo.heads()
59 return " ".join(map(hex, h)) + "\n"
135 return " ".join(map(hex, h)) + "\n"
60
136
61 def listkeys(repo, proto, namespace):
137 def listkeys(repo, proto, namespace):
62 d = pushkey_.list(repo, namespace).items()
138 d = pushkey_.list(repo, namespace).items()
63 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
139 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
64 v.encode('string-escape')) for k, v in d])
140 v.encode('string-escape')) for k, v in d])
65 return t
141 return t
66
142
67 def lookup(repo, proto, key):
143 def lookup(repo, proto, key):
68 try:
144 try:
69 r = hex(repo.lookup(key))
145 r = hex(repo.lookup(key))
70 success = 1
146 success = 1
71 except Exception, inst:
147 except Exception, inst:
72 r = str(inst)
148 r = str(inst)
73 success = 0
149 success = 0
74 return "%s %s\n" % (success, r)
150 return "%s %s\n" % (success, r)
75
151
76 def pushkey(repo, proto, namespace, key, old, new):
152 def pushkey(repo, proto, namespace, key, old, new):
77 r = pushkey_.push(repo, namespace, key, old, new)
153 r = pushkey_.push(repo, namespace, key, old, new)
78 return '%s\n' % int(r)
154 return '%s\n' % int(r)
79
155
80 def stream(repo, proto):
156 def stream(repo, proto):
81 try:
157 try:
82 proto.sendstream(streamclone.stream_out(repo))
158 proto.sendstream(streamclone.stream_out(repo))
83 except streamclone.StreamException, inst:
159 except streamclone.StreamException, inst:
84 return str(inst)
160 return str(inst)
85
161
86 commands = {
162 commands = {
87 'between': (between, 'pairs'),
163 'between': (between, 'pairs'),
88 'branchmap': (branchmap, ''),
164 'branchmap': (branchmap, ''),
89 'branches': (branches, 'nodes'),
165 'branches': (branches, 'nodes'),
90 'changegroup': (changegroup, 'roots'),
166 'changegroup': (changegroup, 'roots'),
91 'changegroupsubset': (changegroupsubset, 'bases heads'),
167 'changegroupsubset': (changegroupsubset, 'bases heads'),
92 'heads': (heads, ''),
168 'heads': (heads, ''),
93 'listkeys': (listkeys, 'namespace'),
169 'listkeys': (listkeys, 'namespace'),
94 'lookup': (lookup, 'key'),
170 'lookup': (lookup, 'key'),
95 'pushkey': (pushkey, 'namespace key old new'),
171 'pushkey': (pushkey, 'namespace key old new'),
96 'stream_out': (stream, ''),
172 'stream_out': (stream, ''),
97 }
173 }
General Comments 0
You need to be logged in to leave comments. Login now