summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commitcf31fe9b4fb650b27e19f5d7ee7297e383660caf (patch)
treed04ca6a45d579dca5e5469606c48c405aee68f4b
downloadgit-repo-cf31fe9b4fb650b27e19f5d7ee7297e383660caf.tar.gz
Initial Contributionv1.0
-rw-r--r--.gitignore1
-rw-r--r--COPYING202
-rw-r--r--Makefile29
-rw-r--r--codereview/__init__.py1
-rw-r--r--codereview/need_retry_pb2.py32
-rwxr-xr-xcodereview/proto_client.py349
-rw-r--r--codereview/review_pb2.py48
-rw-r--r--codereview/upload_bundle_pb2.py190
-rw-r--r--color.py154
-rw-r--r--command.py116
-rw-r--r--editor.py85
-rw-r--r--error.py66
-rw-r--r--froofle/__init__.py0
-rw-r--r--froofle/protobuf/__init__.py0
-rw-r--r--froofle/protobuf/descriptor.py433
-rw-r--r--froofle/protobuf/descriptor_pb2.py950
-rw-r--r--froofle/protobuf/internal/__init__.py0
-rw-r--r--froofle/protobuf/internal/decoder.py209
-rw-r--r--froofle/protobuf/internal/encoder.py206
-rw-r--r--froofle/protobuf/internal/input_stream.py326
-rw-r--r--froofle/protobuf/internal/message_listener.py69
-rw-r--r--froofle/protobuf/internal/output_stream.py125
-rw-r--r--froofle/protobuf/internal/type_checkers.py268
-rw-r--r--froofle/protobuf/internal/wire_format.py236
-rw-r--r--froofle/protobuf/message.py246
-rw-r--r--froofle/protobuf/reflection.py1653
-rw-r--r--froofle/protobuf/service.py208
-rw-r--r--froofle/protobuf/service_reflection.py289
-rw-r--r--froofle/protobuf/text_format.py125
-rwxr-xr-xgerrit_upload.py156
-rw-r--r--git_command.py164
-rw-r--r--git_config.py344
-rw-r--r--import_ext.py422
-rw-r--r--import_tar.py206
-rw-r--r--import_zip.py345
-rwxr-xr-xmain.py198
-rw-r--r--manifest.py338
-rwxr-xr-xpager.py84
-rw-r--r--project.py1058
-rw-r--r--remote.py21
-rwxr-xr-xrepo587
-rw-r--r--subcmds/__init__.py49
-rw-r--r--subcmds/compute_snapshot_check.py169
-rw-r--r--subcmds/diff.py27
-rw-r--r--subcmds/forall.py82
-rw-r--r--subcmds/help.py147
-rw-r--r--subcmds/init.py193
-rw-r--r--subcmds/prune.py59
-rw-r--r--subcmds/stage.py108
-rw-r--r--subcmds/start.py51
-rw-r--r--subcmds/status.py27
-rw-r--r--subcmds/sync.py150
-rw-r--r--subcmds/upload.py180
53 files changed, 11781 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..0d20b648
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
*.pyc
diff --git a/COPYING b/COPYING
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,202 @@
1
2 Apache License
3 Version 2.0, January 2004
4 http://www.apache.org/licenses/
5
6 TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
8 1. Definitions.
9
10 "License" shall mean the terms and conditions for use, reproduction,
11 and distribution as defined by Sections 1 through 9 of this document.
12
13 "Licensor" shall mean the copyright owner or entity authorized by
14 the copyright owner that is granting the License.
15
16 "Legal Entity" shall mean the union of the acting entity and all
17 other entities that control, are controlled by, or are under common
18 control with that entity. For the purposes of this definition,
19 "control" means (i) the power, direct or indirect, to cause the
20 direction or management of such entity, whether by contract or
21 otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 outstanding shares, or (iii) beneficial ownership of such entity.
23
24 "You" (or "Your") shall mean an individual or Legal Entity
25 exercising permissions granted by this License.
26
27 "Source" form shall mean the preferred form for making modifications,
28 including but not limited to software source code, documentation
29 source, and configuration files.
30
31 "Object" form shall mean any form resulting from mechanical
32 transformation or translation of a Source form, including but
33 not limited to compiled object code, generated documentation,
34 and conversions to other media types.
35
36 "Work" shall mean the work of authorship, whether in Source or
37 Object form, made available under the License, as indicated by a
38 copyright notice that is included in or attached to the work
39 (an example is provided in the Appendix below).
40
41 "Derivative Works" shall mean any work, whether in Source or Object
42 form, that is based on (or derived from) the Work and for which the
43 editorial revisions, annotations, elaborations, or other modifications
44 represent, as a whole, an original work of authorship. For the purposes
45 of this License, Derivative Works shall not include works that remain
46 separable from, or merely link (or bind by name) to the interfaces of,
47 the Work and Derivative Works thereof.
48
49 "Contribution" shall mean any work of authorship, including
50 the original version of the Work and any modifications or additions
51 to that Work or Derivative Works thereof, that is intentionally
52 submitted to Licensor for inclusion in the Work by the copyright owner
53 or by an individual or Legal Entity authorized to submit on behalf of
54 the copyright owner. For the purposes of this definition, "submitted"
55 means any form of electronic, verbal, or written communication sent
56 to the Licensor or its representatives, including but not limited to
57 communication on electronic mailing lists, source code control systems,
58 and issue tracking systems that are managed by, or on behalf of, the
59 Licensor for the purpose of discussing and improving the Work, but
60 excluding communication that is conspicuously marked or otherwise
61 designated in writing by the copyright owner as "Not a Contribution."
62
63 "Contributor" shall mean Licensor and any individual or Legal Entity
64 on behalf of whom a Contribution has been received by Licensor and
65 subsequently incorporated within the Work.
66
67 2. Grant of Copyright License. Subject to the terms and conditions of
68 this License, each Contributor hereby grants to You a perpetual,
69 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 copyright license to reproduce, prepare Derivative Works of,
71 publicly display, publicly perform, sublicense, and distribute the
72 Work and such Derivative Works in Source or Object form.
73
74 3. Grant of Patent License. Subject to the terms and conditions of
75 this License, each Contributor hereby grants to You a perpetual,
76 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 (except as stated in this section) patent license to make, have made,
78 use, offer to sell, sell, import, and otherwise transfer the Work,
79 where such license applies only to those patent claims licensable
80 by such Contributor that are necessarily infringed by their
81 Contribution(s) alone or by combination of their Contribution(s)
82 with the Work to which such Contribution(s) was submitted. If You
83 institute patent litigation against any entity (including a
84 cross-claim or counterclaim in a lawsuit) alleging that the Work
85 or a Contribution incorporated within the Work constitutes direct
86 or contributory patent infringement, then any patent licenses
87 granted to You under this License for that Work shall terminate
88 as of the date such litigation is filed.
89
90 4. Redistribution. You may reproduce and distribute copies of the
91 Work or Derivative Works thereof in any medium, with or without
92 modifications, and in Source or Object form, provided that You
93 meet the following conditions:
94
95 (a) You must give any other recipients of the Work or
96 Derivative Works a copy of this License; and
97
98 (b) You must cause any modified files to carry prominent notices
99 stating that You changed the files; and
100
101 (c) You must retain, in the Source form of any Derivative Works
102 that You distribute, all copyright, patent, trademark, and
103 attribution notices from the Source form of the Work,
104 excluding those notices that do not pertain to any part of
105 the Derivative Works; and
106
107 (d) If the Work includes a "NOTICE" text file as part of its
108 distribution, then any Derivative Works that You distribute must
109 include a readable copy of the attribution notices contained
110 within such NOTICE file, excluding those notices that do not
111 pertain to any part of the Derivative Works, in at least one
112 of the following places: within a NOTICE text file distributed
113 as part of the Derivative Works; within the Source form or
114 documentation, if provided along with the Derivative Works; or,
115 within a display generated by the Derivative Works, if and
116 wherever such third-party notices normally appear. The contents
117 of the NOTICE file are for informational purposes only and
118 do not modify the License. You may add Your own attribution
119 notices within Derivative Works that You distribute, alongside
120 or as an addendum to the NOTICE text from the Work, provided
121 that such additional attribution notices cannot be construed
122 as modifying the License.
123
124 You may add Your own copyright statement to Your modifications and
125 may provide additional or different license terms and conditions
126 for use, reproduction, or distribution of Your modifications, or
127 for any such Derivative Works as a whole, provided Your use,
128 reproduction, and distribution of the Work otherwise complies with
129 the conditions stated in this License.
130
131 5. Submission of Contributions. Unless You explicitly state otherwise,
132 any Contribution intentionally submitted for inclusion in the Work
133 by You to the Licensor shall be under the terms and conditions of
134 this License, without any additional terms or conditions.
135 Notwithstanding the above, nothing herein shall supersede or modify
136 the terms of any separate license agreement you may have executed
137 with Licensor regarding such Contributions.
138
139 6. Trademarks. This License does not grant permission to use the trade
140 names, trademarks, service marks, or product names of the Licensor,
141 except as required for reasonable and customary use in describing the
142 origin of the Work and reproducing the content of the NOTICE file.
143
144 7. Disclaimer of Warranty. Unless required by applicable law or
145 agreed to in writing, Licensor provides the Work (and each
146 Contributor provides its Contributions) on an "AS IS" BASIS,
147 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 implied, including, without limitation, any warranties or conditions
149 of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 PARTICULAR PURPOSE. You are solely responsible for determining the
151 appropriateness of using or redistributing the Work and assume any
152 risks associated with Your exercise of permissions under this License.
153
154 8. Limitation of Liability. In no event and under no legal theory,
155 whether in tort (including negligence), contract, or otherwise,
156 unless required by applicable law (such as deliberate and grossly
157 negligent acts) or agreed to in writing, shall any Contributor be
158 liable to You for damages, including any direct, indirect, special,
159 incidental, or consequential damages of any character arising as a
160 result of this License or out of the use or inability to use the
161 Work (including but not limited to damages for loss of goodwill,
162 work stoppage, computer failure or malfunction, or any and all
163 other commercial damages or losses), even if such Contributor
164 has been advised of the possibility of such damages.
165
166 9. Accepting Warranty or Additional Liability. While redistributing
167 the Work or Derivative Works thereof, You may choose to offer,
168 and charge a fee for, acceptance of support, warranty, indemnity,
169 or other liability obligations and/or rights consistent with this
170 License. However, in accepting such obligations, You may act only
171 on Your own behalf and on Your sole responsibility, not on behalf
172 of any other Contributor, and only if You agree to indemnify,
173 defend, and hold each Contributor harmless for any liability
174 incurred by, or claims asserted against, such Contributor by reason
175 of your accepting any such warranty or additional liability.
176
177 END OF TERMS AND CONDITIONS
178
179 APPENDIX: How to apply the Apache License to your work.
180
181 To apply the Apache License to your work, attach the following
182 boilerplate notice, with the fields enclosed by brackets "[]"
183 replaced with your own identifying information. (Don't include
184 the brackets!) The text should be enclosed in the appropriate
185 comment syntax for the file format. We also recommend that a
186 file or class name and description of purpose be included on the
187 same "printed page" as the copyright notice for easier
188 identification within third-party archives.
189
190 Copyright [yyyy] [name of copyright owner]
191
192 Licensed under the Apache License, Version 2.0 (the "License");
193 you may not use this file except in compliance with the License.
194 You may obtain a copy of the License at
195
196 http://www.apache.org/licenses/LICENSE-2.0
197
198 Unless required by applicable law or agreed to in writing, software
199 distributed under the License is distributed on an "AS IS" BASIS,
200 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 See the License for the specific language governing permissions and
202 limitations under the License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..0184e08a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,29 @@
1#
2# Copyright 2008 Google Inc.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16GERRIT_SRC=../gerrit
17GERRIT_MODULES=codereview froofle
18
19all:
20
21clean:
22 find . -name \*.pyc -type f | xargs rm -f
23
24update-pyclient:
25 $(MAKE) -C $(GERRIT_SRC) release-pyclient
26 rm -rf $(GERRIT_MODULES)
27 (cd $(GERRIT_SRC)/release/pyclient && \
28 find . -type f \
29 | cpio -pd $(abspath .))
diff --git a/codereview/__init__.py b/codereview/__init__.py
new file mode 100644
index 00000000..e47bc94e
--- /dev/null
+++ b/codereview/__init__.py
@@ -0,0 +1 @@
__version__ = 'v1.0'
diff --git a/codereview/need_retry_pb2.py b/codereview/need_retry_pb2.py
new file mode 100644
index 00000000..3fab2d43
--- /dev/null
+++ b/codereview/need_retry_pb2.py
@@ -0,0 +1,32 @@
1#!/usr/bin/python2.4
2# Generated by the protocol buffer compiler. DO NOT EDIT!
3
4from froofle.protobuf import descriptor
5from froofle.protobuf import message
6from froofle.protobuf import reflection
7from froofle.protobuf import service
8from froofle.protobuf import service_reflection
9from froofle.protobuf import descriptor_pb2
10
11
12
13_RETRYREQUESTLATERRESPONSE = descriptor.Descriptor(
14 name='RetryRequestLaterResponse',
15 full_name='codereview.RetryRequestLaterResponse',
16 filename='need_retry.proto',
17 containing_type=None,
18 fields=[
19 ],
20 extensions=[
21 ],
22 nested_types=[], # TODO(robinson): Implement.
23 enum_types=[
24 ],
25 options=None)
26
27
28
29class RetryRequestLaterResponse(message.Message):
30 __metaclass__ = reflection.GeneratedProtocolMessageType
31 DESCRIPTOR = _RETRYREQUESTLATERRESPONSE
32
diff --git a/codereview/proto_client.py b/codereview/proto_client.py
new file mode 100755
index 00000000..e11beff0
--- /dev/null
+++ b/codereview/proto_client.py
@@ -0,0 +1,349 @@
1# Copyright 2007, 2008 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import base64
16import cookielib
17import getpass
18import logging
19import md5
20import os
21import random
22import socket
23import time
24import urllib
25import urllib2
26import urlparse
27
28from froofle.protobuf.service import RpcChannel
29from froofle.protobuf.service import RpcController
30from need_retry_pb2 import RetryRequestLaterResponse;
31
32class ClientLoginError(urllib2.HTTPError):
33 """Raised to indicate an error authenticating with ClientLogin."""
34
35 def __init__(self, url, code, msg, headers, args):
36 urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
37 self.args = args
38 self.reason = args["Error"]
39
40
41class Proxy(object):
42 class _ResultHolder(object):
43 def __call__(self, result):
44 self._result = result
45
46 class _RemoteController(RpcController):
47 def Reset(self):
48 pass
49
50 def Failed(self):
51 pass
52
53 def ErrorText(self):
54 pass
55
56 def StartCancel(self):
57 pass
58
59 def SetFailed(self, reason):
60 raise RuntimeError, reason
61
62 def IsCancelled(self):
63 pass
64
65 def NotifyOnCancel(self, callback):
66 pass
67
68 def __init__(self, stub):
69 self._stub = stub
70
71 def __getattr__(self, key):
72 method = getattr(self._stub, key)
73
74 def call(request):
75 done = self._ResultHolder()
76 method(self._RemoteController(), request, done)
77 return done._result
78
79 return call
80
81
82class HttpRpc(RpcChannel):
83 """Simple protobuf over HTTP POST implementation."""
84
85 def __init__(self, host, auth_function,
86 host_override=None,
87 extra_headers={},
88 cookie_file=None):
89 """Creates a new HttpRpc.
90
91 Args:
92 host: The host to send requests to.
93 auth_function: A function that takes no arguments and returns an
94 (email, password) tuple when called. Will be called if authentication
95 is required.
96 host_override: The host header to send to the server (defaults to host).
97 extra_headers: A dict of extra headers to append to every request.
98 cookie_file: If not None, name of the file in ~/ to save the
99 cookie jar into. Applications are encouraged to set this to
100 '.$appname_cookies' or some otherwise unique name.
101 """
102 self.host = host.lower()
103 self.host_override = host_override
104 self.auth_function = auth_function
105 self.authenticated = False
106 self.extra_headers = extra_headers
107 self.xsrf_token = None
108 if cookie_file is None:
109 self.cookie_file = None
110 else:
111 self.cookie_file = os.path.expanduser("~/%s" % cookie_file)
112 self.opener = self._GetOpener()
113 if self.host_override:
114 logging.info("Server: %s; Host: %s", self.host, self.host_override)
115 else:
116 logging.info("Server: %s", self.host)
117
118 def CallMethod(self, method, controller, request, response_type, done):
119 pat = "application/x-google-protobuf; name=%s"
120
121 url = "/proto/%s/%s" % (method.containing_service.name, method.name)
122 reqbin = request.SerializeToString()
123 reqtyp = pat % request.DESCRIPTOR.full_name
124 reqmd5 = base64.b64encode(md5.new(reqbin).digest())
125
126 start = time.time()
127 while True:
128 t, b = self._Send(url, reqbin, reqtyp, reqmd5)
129 if t == (pat % RetryRequestLaterResponse.DESCRIPTOR.full_name):
130 if time.time() >= (start + 1800):
131 controller.SetFailed("timeout")
132 return
133 s = random.uniform(0.250, 2.000)
134 print "Busy, retrying in %.3f seconds ..." % s
135 time.sleep(s)
136 continue
137
138 if t == (pat % response_type.DESCRIPTOR.full_name):
139 response = response_type()
140 response.ParseFromString(b)
141 done(response)
142 else:
143 controller.SetFailed("Unexpected %s response" % t)
144 break
145
146 def _CreateRequest(self, url, data=None):
147 """Creates a new urllib request."""
148 logging.debug("Creating request for: '%s' with payload:\n%s", url, data)
149 req = urllib2.Request(url, data=data)
150 if self.host_override:
151 req.add_header("Host", self.host_override)
152 for key, value in self.extra_headers.iteritems():
153 req.add_header(key, value)
154 return req
155
156 def _GetAuthToken(self, email, password):
157 """Uses ClientLogin to authenticate the user, returning an auth token.
158
159 Args:
160 email: The user's email address
161 password: The user's password
162
163 Raises:
164 ClientLoginError: If there was an error authenticating with ClientLogin.
165 HTTPError: If there was some other form of HTTP error.
166
167 Returns:
168 The authentication token returned by ClientLogin.
169 """
170 req = self._CreateRequest(
171 url="https://www.google.com/accounts/ClientLogin",
172 data=urllib.urlencode({
173 "Email": email,
174 "Passwd": password,
175 "service": "ah",
176 "source": "gerrit-codereview-client",
177 "accountType": "HOSTED_OR_GOOGLE",
178 })
179 )
180 try:
181 response = self.opener.open(req)
182 response_body = response.read()
183 response_dict = dict(x.split("=")
184 for x in response_body.split("\n") if x)
185 return response_dict["Auth"]
186 except urllib2.HTTPError, e:
187 if e.code == 403:
188 body = e.read()
189 response_dict = dict(x.split("=", 1) for x in body.split("\n") if x)
190 raise ClientLoginError(req.get_full_url(), e.code, e.msg,
191 e.headers, response_dict)
192 else:
193 raise
194
195 def _GetAuthCookie(self, auth_token):
196 """Fetches authentication cookies for an authentication token.
197
198 Args:
199 auth_token: The authentication token returned by ClientLogin.
200
201 Raises:
202 HTTPError: If there was an error fetching the authentication cookies.
203 """
204 # This is a dummy value to allow us to identify when we're successful.
205 continue_location = "http://localhost/"
206 args = {"continue": continue_location, "auth": auth_token}
207 req = self._CreateRequest("http://%s/_ah/login?%s" %
208 (self.host, urllib.urlencode(args)))
209 try:
210 response = self.opener.open(req)
211 except urllib2.HTTPError, e:
212 response = e
213 if (response.code != 302 or
214 response.info()["location"] != continue_location):
215 raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg,
216 response.headers, response.fp)
217 self.authenticated = True
218
219 def _GetXsrfToken(self):
220 """Fetches /proto/_token for use in X-XSRF-Token HTTP header.
221
222 Raises:
223 HTTPError: If there was an error fetching a new token.
224 """
225 tries = 0
226 while True:
227 url = "http://%s/proto/_token" % self.host
228 req = self._CreateRequest(url)
229 try:
230 response = self.opener.open(req)
231 self.xsrf_token = response.read()
232 return
233 except urllib2.HTTPError, e:
234 if tries > 3:
235 raise
236 elif e.code == 401:
237 self._Authenticate()
238 else:
239 raise
240
241 def _Authenticate(self):
242 """Authenticates the user.
243
244 The authentication process works as follows:
245 1) We get a username and password from the user
246 2) We use ClientLogin to obtain an AUTH token for the user
247 (see http://code.google.com/apis/accounts/AuthForInstalledApps.html).
248 3) We pass the auth token to /_ah/login on the server to obtain an
249 authentication cookie. If login was successful, it tries to redirect
250 us to the URL we provided.
251
252 If we attempt to access the upload API without first obtaining an
253 authentication cookie, it returns a 401 response and directs us to
254 authenticate ourselves with ClientLogin.
255 """
256 for i in range(3):
257 credentials = self.auth_function()
258 auth_token = self._GetAuthToken(credentials[0], credentials[1])
259 self._GetAuthCookie(auth_token)
260 if self.cookie_file is not None:
261 self.cookie_jar.save()
262 return
263
264 def _Send(self, request_path, payload, content_type, content_md5):
265 """Sends an RPC and returns the response.
266
267 Args:
268 request_path: The path to send the request to, eg /api/appversion/create.
269 payload: The body of the request, or None to send an empty request.
270 content_type: The Content-Type header to use.
271 content_md5: The Content-MD5 header to use.
272
273 Returns:
274 The content type, as a string.
275 The response body, as a string.
276 """
277 if not self.authenticated:
278 self._Authenticate()
279 if not self.xsrf_token:
280 self._GetXsrfToken()
281
282 old_timeout = socket.getdefaulttimeout()
283 socket.setdefaulttimeout(None)
284 try:
285 tries = 0
286 while True:
287 tries += 1
288 url = "http://%s%s" % (self.host, request_path)
289 req = self._CreateRequest(url=url, data=payload)
290 req.add_header("Content-Type", content_type)
291 req.add_header("Content-MD5", content_md5)
292 req.add_header("X-XSRF-Token", self.xsrf_token)
293 try:
294 f = self.opener.open(req)
295 hdr = f.info()
296 type = hdr.getheader('Content-Type',
297 'application/octet-stream')
298 response = f.read()
299 f.close()
300 return type, response
301 except urllib2.HTTPError, e:
302 if tries > 3:
303 raise
304 elif e.code == 401:
305 self._Authenticate()
306 elif e.code == 403:
307 if not hasattr(e, 'read'):
308 e.read = lambda self: ''
309 raise RuntimeError, '403\nxsrf: %s\n%s' \
310 % (self.xsrf_token, e.read())
311 else:
312 raise
313 finally:
314 socket.setdefaulttimeout(old_timeout)
315
316 def _GetOpener(self):
317 """Returns an OpenerDirector that supports cookies and ignores redirects.
318
319 Returns:
320 A urllib2.OpenerDirector object.
321 """
322 opener = urllib2.OpenerDirector()
323 opener.add_handler(urllib2.ProxyHandler())
324 opener.add_handler(urllib2.UnknownHandler())
325 opener.add_handler(urllib2.HTTPHandler())
326 opener.add_handler(urllib2.HTTPDefaultErrorHandler())
327 opener.add_handler(urllib2.HTTPSHandler())
328 opener.add_handler(urllib2.HTTPErrorProcessor())
329 if self.cookie_file is not None:
330 self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file)
331 if os.path.exists(self.cookie_file):
332 try:
333 self.cookie_jar.load()
334 self.authenticated = True
335 except (cookielib.LoadError, IOError):
336 # Failed to load cookies - just ignore them.
337 pass
338 else:
339 # Create an empty cookie file with mode 600
340 fd = os.open(self.cookie_file, os.O_CREAT, 0600)
341 os.close(fd)
342 # Always chmod the cookie file
343 os.chmod(self.cookie_file, 0600)
344 else:
345 # Don't save cookies across runs of update.py.
346 self.cookie_jar = cookielib.CookieJar()
347 opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
348 return opener
349
diff --git a/codereview/review_pb2.py b/codereview/review_pb2.py
new file mode 100644
index 00000000..0896feba
--- /dev/null
+++ b/codereview/review_pb2.py
@@ -0,0 +1,48 @@
1#!/usr/bin/python2.4
2# Generated by the protocol buffer compiler. DO NOT EDIT!
3
4from froofle.protobuf import descriptor
5from froofle.protobuf import message
6from froofle.protobuf import reflection
7from froofle.protobuf import service
8from froofle.protobuf import service_reflection
9from froofle.protobuf import descriptor_pb2
10
11
12import upload_bundle_pb2
13
14
15
16_REVIEWSERVICE = descriptor.ServiceDescriptor(
17 name='ReviewService',
18 full_name='codereview.ReviewService',
19 index=0,
20 options=None,
21 methods=[
22 descriptor.MethodDescriptor(
23 name='UploadBundle',
24 full_name='codereview.ReviewService.UploadBundle',
25 index=0,
26 containing_service=None,
27 input_type=upload_bundle_pb2._UPLOADBUNDLEREQUEST,
28 output_type=upload_bundle_pb2._UPLOADBUNDLERESPONSE,
29 options=None,
30 ),
31 descriptor.MethodDescriptor(
32 name='ContinueBundle',
33 full_name='codereview.ReviewService.ContinueBundle',
34 index=1,
35 containing_service=None,
36 input_type=upload_bundle_pb2._UPLOADBUNDLECONTINUE,
37 output_type=upload_bundle_pb2._UPLOADBUNDLERESPONSE,
38 options=None,
39 ),
40])
41
42class ReviewService(service.Service):
43 __metaclass__ = service_reflection.GeneratedServiceType
44 DESCRIPTOR = _REVIEWSERVICE
45class ReviewService_Stub(ReviewService):
46 __metaclass__ = service_reflection.GeneratedServiceStubType
47 DESCRIPTOR = _REVIEWSERVICE
48
diff --git a/codereview/upload_bundle_pb2.py b/codereview/upload_bundle_pb2.py
new file mode 100644
index 00000000..48c36512
--- /dev/null
+++ b/codereview/upload_bundle_pb2.py
@@ -0,0 +1,190 @@
1#!/usr/bin/python2.4
2# Generated by the protocol buffer compiler. DO NOT EDIT!
3
4from froofle.protobuf import descriptor
5from froofle.protobuf import message
6from froofle.protobuf import reflection
7from froofle.protobuf import service
8from froofle.protobuf import service_reflection
9from froofle.protobuf import descriptor_pb2
10
11
12_UPLOADBUNDLERESPONSE_CODETYPE = descriptor.EnumDescriptor(
13 name='CodeType',
14 full_name='codereview.UploadBundleResponse.CodeType',
15 filename='CodeType',
16 values=[
17 descriptor.EnumValueDescriptor(
18 name='RECEIVED', index=0, number=1,
19 options=None,
20 type=None),
21 descriptor.EnumValueDescriptor(
22 name='CONTINUE', index=1, number=4,
23 options=None,
24 type=None),
25 descriptor.EnumValueDescriptor(
26 name='UNAUTHORIZED_USER', index=2, number=7,
27 options=None,
28 type=None),
29 descriptor.EnumValueDescriptor(
30 name='UNKNOWN_PROJECT', index=3, number=2,
31 options=None,
32 type=None),
33 descriptor.EnumValueDescriptor(
34 name='UNKNOWN_BRANCH', index=4, number=3,
35 options=None,
36 type=None),
37 descriptor.EnumValueDescriptor(
38 name='UNKNOWN_BUNDLE', index=5, number=5,
39 options=None,
40 type=None),
41 descriptor.EnumValueDescriptor(
42 name='NOT_BUNDLE_OWNER', index=6, number=6,
43 options=None,
44 type=None),
45 descriptor.EnumValueDescriptor(
46 name='BUNDLE_CLOSED', index=7, number=8,
47 options=None,
48 type=None),
49 ],
50 options=None,
51)
52
53
54_UPLOADBUNDLEREQUEST = descriptor.Descriptor(
55 name='UploadBundleRequest',
56 full_name='codereview.UploadBundleRequest',
57 filename='upload_bundle.proto',
58 containing_type=None,
59 fields=[
60 descriptor.FieldDescriptor(
61 name='dest_project', full_name='codereview.UploadBundleRequest.dest_project', index=0,
62 number=10, type=9, cpp_type=9, label=2,
63 default_value=unicode("", "utf-8"),
64 message_type=None, enum_type=None, containing_type=None,
65 is_extension=False, extension_scope=None,
66 options=None),
67 descriptor.FieldDescriptor(
68 name='dest_branch', full_name='codereview.UploadBundleRequest.dest_branch', index=1,
69 number=11, type=9, cpp_type=9, label=2,
70 default_value=unicode("", "utf-8"),
71 message_type=None, enum_type=None, containing_type=None,
72 is_extension=False, extension_scope=None,
73 options=None),
74 descriptor.FieldDescriptor(
75 name='partial_upload', full_name='codereview.UploadBundleRequest.partial_upload', index=2,
76 number=12, type=8, cpp_type=7, label=2,
77 default_value=False,
78 message_type=None, enum_type=None, containing_type=None,
79 is_extension=False, extension_scope=None,
80 options=None),
81 descriptor.FieldDescriptor(
82 name='bundle_data', full_name='codereview.UploadBundleRequest.bundle_data', index=3,
83 number=13, type=12, cpp_type=9, label=2,
84 default_value="",
85 message_type=None, enum_type=None, containing_type=None,
86 is_extension=False, extension_scope=None,
87 options=None),
88 descriptor.FieldDescriptor(
89 name='contained_object', full_name='codereview.UploadBundleRequest.contained_object', index=4,
90 number=1, type=9, cpp_type=9, label=3,
91 default_value=[],
92 message_type=None, enum_type=None, containing_type=None,
93 is_extension=False, extension_scope=None,
94 options=None),
95 ],
96 extensions=[
97 ],
98 nested_types=[], # TODO(robinson): Implement.
99 enum_types=[
100 ],
101 options=None)
102
103
104_UPLOADBUNDLERESPONSE = descriptor.Descriptor(
105 name='UploadBundleResponse',
106 full_name='codereview.UploadBundleResponse',
107 filename='upload_bundle.proto',
108 containing_type=None,
109 fields=[
110 descriptor.FieldDescriptor(
111 name='status_code', full_name='codereview.UploadBundleResponse.status_code', index=0,
112 number=10, type=14, cpp_type=8, label=2,
113 default_value=1,
114 message_type=None, enum_type=None, containing_type=None,
115 is_extension=False, extension_scope=None,
116 options=None),
117 descriptor.FieldDescriptor(
118 name='bundle_id', full_name='codereview.UploadBundleResponse.bundle_id', index=1,
119 number=11, type=9, cpp_type=9, label=1,
120 default_value=unicode("", "utf-8"),
121 message_type=None, enum_type=None, containing_type=None,
122 is_extension=False, extension_scope=None,
123 options=None),
124 ],
125 extensions=[
126 ],
127 nested_types=[], # TODO(robinson): Implement.
128 enum_types=[
129 _UPLOADBUNDLERESPONSE_CODETYPE,
130 ],
131 options=None)
132
133
134_UPLOADBUNDLECONTINUE = descriptor.Descriptor(
135 name='UploadBundleContinue',
136 full_name='codereview.UploadBundleContinue',
137 filename='upload_bundle.proto',
138 containing_type=None,
139 fields=[
140 descriptor.FieldDescriptor(
141 name='bundle_id', full_name='codereview.UploadBundleContinue.bundle_id', index=0,
142 number=10, type=9, cpp_type=9, label=2,
143 default_value=unicode("", "utf-8"),
144 message_type=None, enum_type=None, containing_type=None,
145 is_extension=False, extension_scope=None,
146 options=None),
147 descriptor.FieldDescriptor(
148 name='segment_id', full_name='codereview.UploadBundleContinue.segment_id', index=1,
149 number=11, type=5, cpp_type=1, label=2,
150 default_value=0,
151 message_type=None, enum_type=None, containing_type=None,
152 is_extension=False, extension_scope=None,
153 options=None),
154 descriptor.FieldDescriptor(
155 name='partial_upload', full_name='codereview.UploadBundleContinue.partial_upload', index=2,
156 number=12, type=8, cpp_type=7, label=2,
157 default_value=False,
158 message_type=None, enum_type=None, containing_type=None,
159 is_extension=False, extension_scope=None,
160 options=None),
161 descriptor.FieldDescriptor(
162 name='bundle_data', full_name='codereview.UploadBundleContinue.bundle_data', index=3,
163 number=13, type=12, cpp_type=9, label=1,
164 default_value="",
165 message_type=None, enum_type=None, containing_type=None,
166 is_extension=False, extension_scope=None,
167 options=None),
168 ],
169 extensions=[
170 ],
171 nested_types=[], # TODO(robinson): Implement.
172 enum_types=[
173 ],
174 options=None)
175
176
177_UPLOADBUNDLERESPONSE.fields_by_name['status_code'].enum_type = _UPLOADBUNDLERESPONSE_CODETYPE
178
179class UploadBundleRequest(message.Message):
180 __metaclass__ = reflection.GeneratedProtocolMessageType
181 DESCRIPTOR = _UPLOADBUNDLEREQUEST
182
183class UploadBundleResponse(message.Message):
184 __metaclass__ = reflection.GeneratedProtocolMessageType
185 DESCRIPTOR = _UPLOADBUNDLERESPONSE
186
187class UploadBundleContinue(message.Message):
188 __metaclass__ = reflection.GeneratedProtocolMessageType
189 DESCRIPTOR = _UPLOADBUNDLECONTINUE
190
diff --git a/color.py b/color.py
new file mode 100644
index 00000000..b3a558cd
--- /dev/null
+++ b/color.py
@@ -0,0 +1,154 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import sys
18
19import pager
20from git_config import GitConfig
21
22COLORS = {None :-1,
23 'normal' :-1,
24 'black' : 0,
25 'red' : 1,
26 'green' : 2,
27 'yellow' : 3,
28 'blue' : 4,
29 'magenta': 5,
30 'cyan' : 6,
31 'white' : 7}
32
33ATTRS = {None :-1,
34 'bold' : 1,
35 'dim' : 2,
36 'ul' : 4,
37 'blink' : 5,
38 'reverse': 7}
39
40RESET = "\033[m"
41
42def is_color(s): return s in COLORS
43def is_attr(s): return s in ATTRS
44
45def _Color(fg = None, bg = None, attr = None):
46 fg = COLORS[fg]
47 bg = COLORS[bg]
48 attr = ATTRS[attr]
49
50 if attr >= 0 or fg >= 0 or bg >= 0:
51 need_sep = False
52 code = "\033["
53
54 if attr >= 0:
55 code += chr(ord('0') + attr)
56 need_sep = True
57
58 if fg >= 0:
59 if need_sep:
60 code += ';'
61 need_sep = True
62
63 if fg < 8:
64 code += '3%c' % (ord('0') + fg)
65 else:
66 code += '38;5;%d' % fg
67
68 if bg >= 0:
69 if need_sep:
70 code += ';'
71 need_sep = True
72
73 if bg < 8:
74 code += '4%c' % (ord('0') + bg)
75 else:
76 code += '48;5;%d' % bg
77 code += 'm'
78 else:
79 code = ''
80 return code
81
82
83class Coloring(object):
84 def __init__(self, config, type):
85 self._section = 'color.%s' % type
86 self._config = config
87 self._out = sys.stdout
88
89 on = self._config.GetString(self._section)
90 if on is None:
91 on = self._config.GetString('color.ui')
92
93 if on == 'auto':
94 if pager.active or os.isatty(1):
95 self._on = True
96 else:
97 self._on = False
98 elif on in ('true', 'always'):
99 self._on = True
100 else:
101 self._on = False
102
103 @property
104 def is_on(self):
105 return self._on
106
107 def write(self, fmt, *args):
108 self._out.write(fmt % args)
109
110 def nl(self):
111 self._out.write('\n')
112
113 def printer(self, opt=None, fg=None, bg=None, attr=None):
114 s = self
115 c = self.colorer(opt, fg, bg, attr)
116 def f(fmt, *args):
117 s._out.write(c(fmt, *args))
118 return f
119
120 def colorer(self, opt=None, fg=None, bg=None, attr=None):
121 if self._on:
122 c = self._parse(opt, fg, bg, attr)
123 def f(fmt, *args):
124 str = fmt % args
125 return ''.join([c, str, RESET])
126 return f
127 else:
128 def f(fmt, *args):
129 return fmt % args
130 return f
131
132 def _parse(self, opt, fg, bg, attr):
133 if not opt:
134 return _Color(fg, bg, attr)
135
136 v = self._config.GetString('%s.%s' % (self._section, opt))
137 if v is None:
138 return _Color(fg, bg, attr)
139
140 v = v.trim().lowercase()
141 if v == "reset":
142 return RESET
143 elif v == '':
144 return _Color(fg, bg, attr)
145
146 have_fg = False
147 for a in v.split(' '):
148 if is_color(a):
149 if have_fg: bg = a
150 else: fg = a
151 elif is_attr(a):
152 attr = a
153
154 return _Color(fg, bg, attr)
diff --git a/command.py b/command.py
new file mode 100644
index 00000000..516c2d9d
--- /dev/null
+++ b/command.py
@@ -0,0 +1,116 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import optparse
18import sys
19
20from error import NoSuchProjectError
21
22class Command(object):
23 """Base class for any command line action in repo.
24 """
25
26 common = False
27 manifest = None
28 _optparse = None
29
30 @property
31 def OptionParser(self):
32 if self._optparse is None:
33 try:
34 me = 'repo %s' % self.NAME
35 usage = self.helpUsage.strip().replace('%prog', me)
36 except AttributeError:
37 usage = 'repo %s' % self.NAME
38 self._optparse = optparse.OptionParser(usage = usage)
39 self._Options(self._optparse)
40 return self._optparse
41
42 def _Options(self, p):
43 """Initialize the option parser.
44 """
45
46 def Usage(self):
47 """Display usage and terminate.
48 """
49 self.OptionParser.print_usage()
50 sys.exit(1)
51
52 def Execute(self, opt, args):
53 """Perform the action, after option parsing is complete.
54 """
55 raise NotImplementedError
56
57 def GetProjects(self, args, missing_ok=False):
58 """A list of projects that match the arguments.
59 """
60 all = self.manifest.projects
61 result = []
62
63 if not args:
64 for project in all.values():
65 if missing_ok or project.Exists:
66 result.append(project)
67 else:
68 by_path = None
69
70 for arg in args:
71 project = all.get(arg)
72
73 if not project:
74 path = os.path.abspath(arg)
75
76 if not by_path:
77 by_path = dict()
78 for p in all.values():
79 by_path[p.worktree] = p
80
81 if os.path.exists(path):
82 while path \
83 and path != '/' \
84 and path != self.manifest.topdir:
85 try:
86 project = by_path[path]
87 break
88 except KeyError:
89 path = os.path.dirname(path)
90 else:
91 try:
92 project = by_path[path]
93 except KeyError:
94 pass
95
96 if not project:
97 raise NoSuchProjectError(arg)
98 if not missing_ok and not project.Exists:
99 raise NoSuchProjectError(arg)
100
101 result.append(project)
102
103 def _getpath(x):
104 return x.relpath
105 result.sort(key=_getpath)
106 return result
107
108class InteractiveCommand(Command):
109 """Command which requires user interaction on the tty and
110 must not run within a pager, even if the user asks to.
111 """
112
113class PagedCommand(Command):
114 """Command which defaults to output in a pager, as its
115 display tends to be larger than one screen full.
116 """
diff --git a/editor.py b/editor.py
new file mode 100644
index 00000000..4f22257f
--- /dev/null
+++ b/editor.py
@@ -0,0 +1,85 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import sys
18import subprocess
19import tempfile
20
21from error import EditorError
22
23class Editor(object):
24 """Manages the user's preferred text editor."""
25
26 _editor = None
27 globalConfig = None
28
29 @classmethod
30 def _GetEditor(cls):
31 if cls._editor is None:
32 cls._editor = cls._SelectEditor()
33 return cls._editor
34
35 @classmethod
36 def _SelectEditor(cls):
37 e = os.getenv('GIT_EDITOR')
38 if e:
39 return e
40
41 e = cls.globalConfig.GetString('core.editor')
42 if e:
43 return e
44
45 e = os.getenv('VISUAL')
46 if e:
47 return e
48
49 e = os.getenv('EDITOR')
50 if e:
51 return e
52
53 if os.getenv('TERM') == 'dumb':
54 print >>sys.stderr,\
55"""No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR.
56Tried to fall back to vi but terminal is dumb. Please configure at
57least one of these before using this command."""
58 sys.exit(1)
59
60 return 'vi'
61
62 @classmethod
63 def EditString(cls, data):
64 """Opens an editor to edit the given content.
65
66 Args:
67 data : the text to edit
68
69 Returns:
70 new value of edited text; None if editing did not succeed
71 """
72 editor = cls._GetEditor()
73 fd, path = tempfile.mkstemp()
74 try:
75 os.write(fd, data)
76 os.close(fd)
77 fd = None
78
79 if subprocess.Popen([editor, path]).wait() != 0:
80 raise EditorError()
81 return open(path).read()
82 finally:
83 if fd:
84 os.close(fd)
85 os.remove(path)
diff --git a/error.py b/error.py
new file mode 100644
index 00000000..e3cf41c1
--- /dev/null
+++ b/error.py
@@ -0,0 +1,66 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16class ManifestParseError(Exception):
17 """Failed to parse the manifest file.
18 """
19
20class EditorError(Exception):
21 """Unspecified error from the user's text editor.
22 """
23
24class GitError(Exception):
25 """Unspecified internal error from git.
26 """
27 def __init__(self, command):
28 self.command = command
29
30 def __str__(self):
31 return self.command
32
33class ImportError(Exception):
34 """An import from a non-Git format cannot be performed.
35 """
36 def __init__(self, reason):
37 self.reason = reason
38
39 def __str__(self):
40 return self.reason
41
42class UploadError(Exception):
43 """A bundle upload to Gerrit did not succeed.
44 """
45 def __init__(self, reason):
46 self.reason = reason
47
48 def __str__(self):
49 return self.reason
50
51class NoSuchProjectError(Exception):
52 """A specified project does not exist in the work tree.
53 """
54 def __init__(self, name=None):
55 self.name = name
56
57 def __str__(self):
58 if self.Name is None:
59 return 'in current directory'
60 return self.name
61
62class RepoChangedException(Exception):
63 """Thrown if 'repo sync' results in repo updating its internal
64 repo or manifest repositories. In this special case we must
65 use exec to re-execute repo with the new code and manifest.
66 """
diff --git a/froofle/__init__.py b/froofle/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/froofle/__init__.py
diff --git a/froofle/protobuf/__init__.py b/froofle/protobuf/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/froofle/protobuf/__init__.py
diff --git a/froofle/protobuf/descriptor.py b/froofle/protobuf/descriptor.py
new file mode 100644
index 00000000..e74cf25e
--- /dev/null
+++ b/froofle/protobuf/descriptor.py
@@ -0,0 +1,433 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31# TODO(robinson): We probably need to provide deep-copy methods for
32# descriptor types. When a FieldDescriptor is passed into
33# Descriptor.__init__(), we should make a deep copy and then set
34# containing_type on it. Alternatively, we could just get
35# rid of containing_type (iit's not needed for reflection.py, at least).
36#
37# TODO(robinson): Print method?
38#
39# TODO(robinson): Useful __repr__?
40
41"""Descriptors essentially contain exactly the information found in a .proto
42file, in types that make this information accessible in Python.
43"""
44
45__author__ = 'robinson@google.com (Will Robinson)'
46
47class DescriptorBase(object):
48
49 """Descriptors base class.
50
51 This class is the base of all descriptor classes. It provides common options
52 related functionaility.
53 """
54
55 def __init__(self, options, options_class_name):
56 """Initialize the descriptor given its options message and the name of the
57 class of the options message. The name of the class is required in case
58 the options message is None and has to be created.
59 """
60 self._options = options
61 self._options_class_name = options_class_name
62
63 def GetOptions(self):
64 """Retrieves descriptor options.
65
66 This method returns the options set or creates the default options for the
67 descriptor.
68 """
69 if self._options:
70 return self._options
71 from froofle.protobuf import descriptor_pb2
72 try:
73 options_class = getattr(descriptor_pb2, self._options_class_name)
74 except AttributeError:
75 raise RuntimeError('Unknown options class name %s!' %
76 (self._options_class_name))
77 self._options = options_class()
78 return self._options
79
80
81class Descriptor(DescriptorBase):
82
83 """Descriptor for a protocol message type.
84
85 A Descriptor instance has the following attributes:
86
87 name: (str) Name of this protocol message type.
88 full_name: (str) Fully-qualified name of this protocol message type,
89 which will include protocol "package" name and the name of any
90 enclosing types.
91
92 filename: (str) Name of the .proto file containing this message.
93
94 containing_type: (Descriptor) Reference to the descriptor of the
95 type containing us, or None if we have no containing type.
96
97 fields: (list of FieldDescriptors) Field descriptors for all
98 fields in this type.
99 fields_by_number: (dict int -> FieldDescriptor) Same FieldDescriptor
100 objects as in |fields|, but indexed by "number" attribute in each
101 FieldDescriptor.
102 fields_by_name: (dict str -> FieldDescriptor) Same FieldDescriptor
103 objects as in |fields|, but indexed by "name" attribute in each
104 FieldDescriptor.
105
106 nested_types: (list of Descriptors) Descriptor references
107 for all protocol message types nested within this one.
108 nested_types_by_name: (dict str -> Descriptor) Same Descriptor
109 objects as in |nested_types|, but indexed by "name" attribute
110 in each Descriptor.
111
112 enum_types: (list of EnumDescriptors) EnumDescriptor references
113 for all enums contained within this type.
114 enum_types_by_name: (dict str ->EnumDescriptor) Same EnumDescriptor
115 objects as in |enum_types|, but indexed by "name" attribute
116 in each EnumDescriptor.
117 enum_values_by_name: (dict str -> EnumValueDescriptor) Dict mapping
118 from enum value name to EnumValueDescriptor for that value.
119
120 extensions: (list of FieldDescriptor) All extensions defined directly
121 within this message type (NOT within a nested type).
122 extensions_by_name: (dict, string -> FieldDescriptor) Same FieldDescriptor
123 objects as |extensions|, but indexed by "name" attribute of each
124 FieldDescriptor.
125
126 options: (descriptor_pb2.MessageOptions) Protocol message options or None
127 to use default message options.
128 """
129
130 def __init__(self, name, full_name, filename, containing_type,
131 fields, nested_types, enum_types, extensions, options=None):
132 """Arguments to __init__() are as described in the description
133 of Descriptor fields above.
134 """
135 super(Descriptor, self).__init__(options, 'MessageOptions')
136 self.name = name
137 self.full_name = full_name
138 self.filename = filename
139 self.containing_type = containing_type
140
141 # We have fields in addition to fields_by_name and fields_by_number,
142 # so that:
143 # 1. Clients can index fields by "order in which they're listed."
144 # 2. Clients can easily iterate over all fields with the terse
145 # syntax: for f in descriptor.fields: ...
146 self.fields = fields
147 for field in self.fields:
148 field.containing_type = self
149 self.fields_by_number = dict((f.number, f) for f in fields)
150 self.fields_by_name = dict((f.name, f) for f in fields)
151
152 self.nested_types = nested_types
153 self.nested_types_by_name = dict((t.name, t) for t in nested_types)
154
155 self.enum_types = enum_types
156 for enum_type in self.enum_types:
157 enum_type.containing_type = self
158 self.enum_types_by_name = dict((t.name, t) for t in enum_types)
159 self.enum_values_by_name = dict(
160 (v.name, v) for t in enum_types for v in t.values)
161
162 self.extensions = extensions
163 for extension in self.extensions:
164 extension.extension_scope = self
165 self.extensions_by_name = dict((f.name, f) for f in extensions)
166
167
168# TODO(robinson): We should have aggressive checking here,
169# for example:
170# * If you specify a repeated field, you should not be allowed
171# to specify a default value.
172# * [Other examples here as needed].
173#
174# TODO(robinson): for this and other *Descriptor classes, we
175# might also want to lock things down aggressively (e.g.,
176# prevent clients from setting the attributes). Having
177# stronger invariants here in general will reduce the number
178# of runtime checks we must do in reflection.py...
179class FieldDescriptor(DescriptorBase):
180
181 """Descriptor for a single field in a .proto file.
182
183 A FieldDescriptor instance has the following attriubtes:
184
185 name: (str) Name of this field, exactly as it appears in .proto.
186 full_name: (str) Name of this field, including containing scope. This is
187 particularly relevant for extensions.
188 index: (int) Dense, 0-indexed index giving the order that this
189 field textually appears within its message in the .proto file.
190 number: (int) Tag number declared for this field in the .proto file.
191
192 type: (One of the TYPE_* constants below) Declared type.
193 cpp_type: (One of the CPPTYPE_* constants below) C++ type used to
194 represent this field.
195
196 label: (One of the LABEL_* constants below) Tells whether this
197 field is optional, required, or repeated.
198 default_value: (Varies) Default value of this field. Only
199 meaningful for non-repeated scalar fields. Repeated fields
200 should always set this to [], and non-repeated composite
201 fields should always set this to None.
202
203 containing_type: (Descriptor) Descriptor of the protocol message
204 type that contains this field. Set by the Descriptor constructor
205 if we're passed into one.
206 Somewhat confusingly, for extension fields, this is the
207 descriptor of the EXTENDED message, not the descriptor
208 of the message containing this field. (See is_extension and
209 extension_scope below).
210 message_type: (Descriptor) If a composite field, a descriptor
211 of the message type contained in this field. Otherwise, this is None.
212 enum_type: (EnumDescriptor) If this field contains an enum, a
213 descriptor of that enum. Otherwise, this is None.
214
215 is_extension: True iff this describes an extension field.
216 extension_scope: (Descriptor) Only meaningful if is_extension is True.
217 Gives the message that immediately contains this extension field.
218 Will be None iff we're a top-level (file-level) extension field.
219
220 options: (descriptor_pb2.FieldOptions) Protocol message field options or
221 None to use default field options.
222 """
223
224 # Must be consistent with C++ FieldDescriptor::Type enum in
225 # descriptor.h.
226 #
227 # TODO(robinson): Find a way to eliminate this repetition.
228 TYPE_DOUBLE = 1
229 TYPE_FLOAT = 2
230 TYPE_INT64 = 3
231 TYPE_UINT64 = 4
232 TYPE_INT32 = 5
233 TYPE_FIXED64 = 6
234 TYPE_FIXED32 = 7
235 TYPE_BOOL = 8
236 TYPE_STRING = 9
237 TYPE_GROUP = 10
238 TYPE_MESSAGE = 11
239 TYPE_BYTES = 12
240 TYPE_UINT32 = 13
241 TYPE_ENUM = 14
242 TYPE_SFIXED32 = 15
243 TYPE_SFIXED64 = 16
244 TYPE_SINT32 = 17
245 TYPE_SINT64 = 18
246 MAX_TYPE = 18
247
248 # Must be consistent with C++ FieldDescriptor::CppType enum in
249 # descriptor.h.
250 #
251 # TODO(robinson): Find a way to eliminate this repetition.
252 CPPTYPE_INT32 = 1
253 CPPTYPE_INT64 = 2
254 CPPTYPE_UINT32 = 3
255 CPPTYPE_UINT64 = 4
256 CPPTYPE_DOUBLE = 5
257 CPPTYPE_FLOAT = 6
258 CPPTYPE_BOOL = 7
259 CPPTYPE_ENUM = 8
260 CPPTYPE_STRING = 9
261 CPPTYPE_MESSAGE = 10
262 MAX_CPPTYPE = 10
263
264 # Must be consistent with C++ FieldDescriptor::Label enum in
265 # descriptor.h.
266 #
267 # TODO(robinson): Find a way to eliminate this repetition.
268 LABEL_OPTIONAL = 1
269 LABEL_REQUIRED = 2
270 LABEL_REPEATED = 3
271 MAX_LABEL = 3
272
273 def __init__(self, name, full_name, index, number, type, cpp_type, label,
274 default_value, message_type, enum_type, containing_type,
275 is_extension, extension_scope, options=None):
276 """The arguments are as described in the description of FieldDescriptor
277 attributes above.
278
279 Note that containing_type may be None, and may be set later if necessary
280 (to deal with circular references between message types, for example).
281 Likewise for extension_scope.
282 """
283 super(FieldDescriptor, self).__init__(options, 'FieldOptions')
284 self.name = name
285 self.full_name = full_name
286 self.index = index
287 self.number = number
288 self.type = type
289 self.cpp_type = cpp_type
290 self.label = label
291 self.default_value = default_value
292 self.containing_type = containing_type
293 self.message_type = message_type
294 self.enum_type = enum_type
295 self.is_extension = is_extension
296 self.extension_scope = extension_scope
297
298
299class EnumDescriptor(DescriptorBase):
300
301 """Descriptor for an enum defined in a .proto file.
302
303 An EnumDescriptor instance has the following attributes:
304
305 name: (str) Name of the enum type.
306 full_name: (str) Full name of the type, including package name
307 and any enclosing type(s).
308 filename: (str) Name of the .proto file in which this appears.
309
310 values: (list of EnumValueDescriptors) List of the values
311 in this enum.
312 values_by_name: (dict str -> EnumValueDescriptor) Same as |values|,
313 but indexed by the "name" field of each EnumValueDescriptor.
314 values_by_number: (dict int -> EnumValueDescriptor) Same as |values|,
315 but indexed by the "number" field of each EnumValueDescriptor.
316 containing_type: (Descriptor) Descriptor of the immediate containing
317 type of this enum, or None if this is an enum defined at the
318 top level in a .proto file. Set by Descriptor's constructor
319 if we're passed into one.
320 options: (descriptor_pb2.EnumOptions) Enum options message or
321 None to use default enum options.
322 """
323
324 def __init__(self, name, full_name, filename, values,
325 containing_type=None, options=None):
326 """Arguments are as described in the attribute description above."""
327 super(EnumDescriptor, self).__init__(options, 'EnumOptions')
328 self.name = name
329 self.full_name = full_name
330 self.filename = filename
331 self.values = values
332 for value in self.values:
333 value.type = self
334 self.values_by_name = dict((v.name, v) for v in values)
335 self.values_by_number = dict((v.number, v) for v in values)
336 self.containing_type = containing_type
337
338
339class EnumValueDescriptor(DescriptorBase):
340
341 """Descriptor for a single value within an enum.
342
343 name: (str) Name of this value.
344 index: (int) Dense, 0-indexed index giving the order that this
345 value appears textually within its enum in the .proto file.
346 number: (int) Actual number assigned to this enum value.
347 type: (EnumDescriptor) EnumDescriptor to which this value
348 belongs. Set by EnumDescriptor's constructor if we're
349 passed into one.
350 options: (descriptor_pb2.EnumValueOptions) Enum value options message or
351 None to use default enum value options options.
352 """
353
354 def __init__(self, name, index, number, type=None, options=None):
355 """Arguments are as described in the attribute description above."""
356 super(EnumValueDescriptor, self).__init__(options, 'EnumValueOptions')
357 self.name = name
358 self.index = index
359 self.number = number
360 self.type = type
361
362
363class ServiceDescriptor(DescriptorBase):
364
365 """Descriptor for a service.
366
367 name: (str) Name of the service.
368 full_name: (str) Full name of the service, including package name.
369 index: (int) 0-indexed index giving the order that this services
370 definition appears withing the .proto file.
371 methods: (list of MethodDescriptor) List of methods provided by this
372 service.
373 options: (descriptor_pb2.ServiceOptions) Service options message or
374 None to use default service options.
375 """
376
377 def __init__(self, name, full_name, index, methods, options=None):
378 super(ServiceDescriptor, self).__init__(options, 'ServiceOptions')
379 self.name = name
380 self.full_name = full_name
381 self.index = index
382 self.methods = methods
383 # Set the containing service for each method in this service.
384 for method in self.methods:
385 method.containing_service = self
386
387 def FindMethodByName(self, name):
388 """Searches for the specified method, and returns its descriptor."""
389 for method in self.methods:
390 if name == method.name:
391 return method
392 return None
393
394
395class MethodDescriptor(DescriptorBase):
396
397 """Descriptor for a method in a service.
398
399 name: (str) Name of the method within the service.
400 full_name: (str) Full name of method.
401 index: (int) 0-indexed index of the method inside the service.
402 containing_service: (ServiceDescriptor) The service that contains this
403 method.
404 input_type: The descriptor of the message that this method accepts.
405 output_type: The descriptor of the message that this method returns.
406 options: (descriptor_pb2.MethodOptions) Method options message or
407 None to use default method options.
408 """
409
410 def __init__(self, name, full_name, index, containing_service,
411 input_type, output_type, options=None):
412 """The arguments are as described in the description of MethodDescriptor
413 attributes above.
414
415 Note that containing_service may be None, and may be set later if necessary.
416 """
417 super(MethodDescriptor, self).__init__(options, 'MethodOptions')
418 self.name = name
419 self.full_name = full_name
420 self.index = index
421 self.containing_service = containing_service
422 self.input_type = input_type
423 self.output_type = output_type
424
425
426def _ParseOptions(message, string):
427 """Parses serialized options.
428
429 This helper function is used to parse serialized options in generated
430 proto2 files. It must not be used outside proto2.
431 """
432 message.ParseFromString(string)
433 return message;
diff --git a/froofle/protobuf/descriptor_pb2.py b/froofle/protobuf/descriptor_pb2.py
new file mode 100644
index 00000000..16873834
--- /dev/null
+++ b/froofle/protobuf/descriptor_pb2.py
@@ -0,0 +1,950 @@
1#!/usr/bin/python2.4
2# Generated by the protocol buffer compiler. DO NOT EDIT!
3
4from froofle.protobuf import descriptor
5from froofle.protobuf import message
6from froofle.protobuf import reflection
7from froofle.protobuf import service
8from froofle.protobuf import service_reflection
9
10
11_FIELDDESCRIPTORPROTO_TYPE = descriptor.EnumDescriptor(
12 name='Type',
13 full_name='froofle.protobuf.FieldDescriptorProto.Type',
14 filename='Type',
15 values=[
16 descriptor.EnumValueDescriptor(
17 name='TYPE_DOUBLE', index=0, number=1,
18 options=None,
19 type=None),
20 descriptor.EnumValueDescriptor(
21 name='TYPE_FLOAT', index=1, number=2,
22 options=None,
23 type=None),
24 descriptor.EnumValueDescriptor(
25 name='TYPE_INT64', index=2, number=3,
26 options=None,
27 type=None),
28 descriptor.EnumValueDescriptor(
29 name='TYPE_UINT64', index=3, number=4,
30 options=None,
31 type=None),
32 descriptor.EnumValueDescriptor(
33 name='TYPE_INT32', index=4, number=5,
34 options=None,
35 type=None),
36 descriptor.EnumValueDescriptor(
37 name='TYPE_FIXED64', index=5, number=6,
38 options=None,
39 type=None),
40 descriptor.EnumValueDescriptor(
41 name='TYPE_FIXED32', index=6, number=7,
42 options=None,
43 type=None),
44 descriptor.EnumValueDescriptor(
45 name='TYPE_BOOL', index=7, number=8,
46 options=None,
47 type=None),
48 descriptor.EnumValueDescriptor(
49 name='TYPE_STRING', index=8, number=9,
50 options=None,
51 type=None),
52 descriptor.EnumValueDescriptor(
53 name='TYPE_GROUP', index=9, number=10,
54 options=None,
55 type=None),
56 descriptor.EnumValueDescriptor(
57 name='TYPE_MESSAGE', index=10, number=11,
58 options=None,
59 type=None),
60 descriptor.EnumValueDescriptor(
61 name='TYPE_BYTES', index=11, number=12,
62 options=None,
63 type=None),
64 descriptor.EnumValueDescriptor(
65 name='TYPE_UINT32', index=12, number=13,
66 options=None,
67 type=None),
68 descriptor.EnumValueDescriptor(
69 name='TYPE_ENUM', index=13, number=14,
70 options=None,
71 type=None),
72 descriptor.EnumValueDescriptor(
73 name='TYPE_SFIXED32', index=14, number=15,
74 options=None,
75 type=None),
76 descriptor.EnumValueDescriptor(
77 name='TYPE_SFIXED64', index=15, number=16,
78 options=None,
79 type=None),
80 descriptor.EnumValueDescriptor(
81 name='TYPE_SINT32', index=16, number=17,
82 options=None,
83 type=None),
84 descriptor.EnumValueDescriptor(
85 name='TYPE_SINT64', index=17, number=18,
86 options=None,
87 type=None),
88 ],
89 options=None,
90)
91
92_FIELDDESCRIPTORPROTO_LABEL = descriptor.EnumDescriptor(
93 name='Label',
94 full_name='froofle.protobuf.FieldDescriptorProto.Label',
95 filename='Label',
96 values=[
97 descriptor.EnumValueDescriptor(
98 name='LABEL_OPTIONAL', index=0, number=1,
99 options=None,
100 type=None),
101 descriptor.EnumValueDescriptor(
102 name='LABEL_REQUIRED', index=1, number=2,
103 options=None,
104 type=None),
105 descriptor.EnumValueDescriptor(
106 name='LABEL_REPEATED', index=2, number=3,
107 options=None,
108 type=None),
109 ],
110 options=None,
111)
112
113_FILEOPTIONS_OPTIMIZEMODE = descriptor.EnumDescriptor(
114 name='OptimizeMode',
115 full_name='froofle.protobuf.FileOptions.OptimizeMode',
116 filename='OptimizeMode',
117 values=[
118 descriptor.EnumValueDescriptor(
119 name='SPEED', index=0, number=1,
120 options=None,
121 type=None),
122 descriptor.EnumValueDescriptor(
123 name='CODE_SIZE', index=1, number=2,
124 options=None,
125 type=None),
126 ],
127 options=None,
128)
129
130_FIELDOPTIONS_CTYPE = descriptor.EnumDescriptor(
131 name='CType',
132 full_name='froofle.protobuf.FieldOptions.CType',
133 filename='CType',
134 values=[
135 descriptor.EnumValueDescriptor(
136 name='CORD', index=0, number=1,
137 options=None,
138 type=None),
139 descriptor.EnumValueDescriptor(
140 name='STRING_PIECE', index=1, number=2,
141 options=None,
142 type=None),
143 ],
144 options=None,
145)
146
147
148_FILEDESCRIPTORSET = descriptor.Descriptor(
149 name='FileDescriptorSet',
150 full_name='froofle.protobuf.FileDescriptorSet',
151 filename='froofle/protobuf/descriptor.proto',
152 containing_type=None,
153 fields=[
154 descriptor.FieldDescriptor(
155 name='file', full_name='froofle.protobuf.FileDescriptorSet.file', index=0,
156 number=1, type=11, cpp_type=10, label=3,
157 default_value=[],
158 message_type=None, enum_type=None, containing_type=None,
159 is_extension=False, extension_scope=None,
160 options=None),
161 ],
162 extensions=[
163 ],
164 nested_types=[], # TODO(robinson): Implement.
165 enum_types=[
166 ],
167 options=None)
168
169
170_FILEDESCRIPTORPROTO = descriptor.Descriptor(
171 name='FileDescriptorProto',
172 full_name='froofle.protobuf.FileDescriptorProto',
173 filename='froofle/protobuf/descriptor.proto',
174 containing_type=None,
175 fields=[
176 descriptor.FieldDescriptor(
177 name='name', full_name='froofle.protobuf.FileDescriptorProto.name', index=0,
178 number=1, type=9, cpp_type=9, label=1,
179 default_value=unicode("", "utf-8"),
180 message_type=None, enum_type=None, containing_type=None,
181 is_extension=False, extension_scope=None,
182 options=None),
183 descriptor.FieldDescriptor(
184 name='package', full_name='froofle.protobuf.FileDescriptorProto.package', index=1,
185 number=2, type=9, cpp_type=9, label=1,
186 default_value=unicode("", "utf-8"),
187 message_type=None, enum_type=None, containing_type=None,
188 is_extension=False, extension_scope=None,
189 options=None),
190 descriptor.FieldDescriptor(
191 name='dependency', full_name='froofle.protobuf.FileDescriptorProto.dependency', index=2,
192 number=3, type=9, cpp_type=9, label=3,
193 default_value=[],
194 message_type=None, enum_type=None, containing_type=None,
195 is_extension=False, extension_scope=None,
196 options=None),
197 descriptor.FieldDescriptor(
198 name='message_type', full_name='froofle.protobuf.FileDescriptorProto.message_type', index=3,
199 number=4, type=11, cpp_type=10, label=3,
200 default_value=[],
201 message_type=None, enum_type=None, containing_type=None,
202 is_extension=False, extension_scope=None,
203 options=None),
204 descriptor.FieldDescriptor(
205 name='enum_type', full_name='froofle.protobuf.FileDescriptorProto.enum_type', index=4,
206 number=5, type=11, cpp_type=10, label=3,
207 default_value=[],
208 message_type=None, enum_type=None, containing_type=None,
209 is_extension=False, extension_scope=None,
210 options=None),
211 descriptor.FieldDescriptor(
212 name='service', full_name='froofle.protobuf.FileDescriptorProto.service', index=5,
213 number=6, type=11, cpp_type=10, label=3,
214 default_value=[],
215 message_type=None, enum_type=None, containing_type=None,
216 is_extension=False, extension_scope=None,
217 options=None),
218 descriptor.FieldDescriptor(
219 name='extension', full_name='froofle.protobuf.FileDescriptorProto.extension', index=6,
220 number=7, type=11, cpp_type=10, label=3,
221 default_value=[],
222 message_type=None, enum_type=None, containing_type=None,
223 is_extension=False, extension_scope=None,
224 options=None),
225 descriptor.FieldDescriptor(
226 name='options', full_name='froofle.protobuf.FileDescriptorProto.options', index=7,
227 number=8, type=11, cpp_type=10, label=1,
228 default_value=None,
229 message_type=None, enum_type=None, containing_type=None,
230 is_extension=False, extension_scope=None,
231 options=None),
232 ],
233 extensions=[
234 ],
235 nested_types=[], # TODO(robinson): Implement.
236 enum_types=[
237 ],
238 options=None)
239
240
241_DESCRIPTORPROTO_EXTENSIONRANGE = descriptor.Descriptor(
242 name='ExtensionRange',
243 full_name='froofle.protobuf.DescriptorProto.ExtensionRange',
244 filename='froofle/protobuf/descriptor.proto',
245 containing_type=None,
246 fields=[
247 descriptor.FieldDescriptor(
248 name='start', full_name='froofle.protobuf.DescriptorProto.ExtensionRange.start', index=0,
249 number=1, type=5, cpp_type=1, label=1,
250 default_value=0,
251 message_type=None, enum_type=None, containing_type=None,
252 is_extension=False, extension_scope=None,
253 options=None),
254 descriptor.FieldDescriptor(
255 name='end', full_name='froofle.protobuf.DescriptorProto.ExtensionRange.end', index=1,
256 number=2, type=5, cpp_type=1, label=1,
257 default_value=0,
258 message_type=None, enum_type=None, containing_type=None,
259 is_extension=False, extension_scope=None,
260 options=None),
261 ],
262 extensions=[
263 ],
264 nested_types=[], # TODO(robinson): Implement.
265 enum_types=[
266 ],
267 options=None)
268
269_DESCRIPTORPROTO = descriptor.Descriptor(
270 name='DescriptorProto',
271 full_name='froofle.protobuf.DescriptorProto',
272 filename='froofle/protobuf/descriptor.proto',
273 containing_type=None,
274 fields=[
275 descriptor.FieldDescriptor(
276 name='name', full_name='froofle.protobuf.DescriptorProto.name', index=0,
277 number=1, type=9, cpp_type=9, label=1,
278 default_value=unicode("", "utf-8"),
279 message_type=None, enum_type=None, containing_type=None,
280 is_extension=False, extension_scope=None,
281 options=None),
282 descriptor.FieldDescriptor(
283 name='field', full_name='froofle.protobuf.DescriptorProto.field', index=1,
284 number=2, type=11, cpp_type=10, label=3,
285 default_value=[],
286 message_type=None, enum_type=None, containing_type=None,
287 is_extension=False, extension_scope=None,
288 options=None),
289 descriptor.FieldDescriptor(
290 name='extension', full_name='froofle.protobuf.DescriptorProto.extension', index=2,
291 number=6, type=11, cpp_type=10, label=3,
292 default_value=[],
293 message_type=None, enum_type=None, containing_type=None,
294 is_extension=False, extension_scope=None,
295 options=None),
296 descriptor.FieldDescriptor(
297 name='nested_type', full_name='froofle.protobuf.DescriptorProto.nested_type', index=3,
298 number=3, type=11, cpp_type=10, label=3,
299 default_value=[],
300 message_type=None, enum_type=None, containing_type=None,
301 is_extension=False, extension_scope=None,
302 options=None),
303 descriptor.FieldDescriptor(
304 name='enum_type', full_name='froofle.protobuf.DescriptorProto.enum_type', index=4,
305 number=4, type=11, cpp_type=10, label=3,
306 default_value=[],
307 message_type=None, enum_type=None, containing_type=None,
308 is_extension=False, extension_scope=None,
309 options=None),
310 descriptor.FieldDescriptor(
311 name='extension_range', full_name='froofle.protobuf.DescriptorProto.extension_range', index=5,
312 number=5, type=11, cpp_type=10, label=3,
313 default_value=[],
314 message_type=None, enum_type=None, containing_type=None,
315 is_extension=False, extension_scope=None,
316 options=None),
317 descriptor.FieldDescriptor(
318 name='options', full_name='froofle.protobuf.DescriptorProto.options', index=6,
319 number=7, type=11, cpp_type=10, label=1,
320 default_value=None,
321 message_type=None, enum_type=None, containing_type=None,
322 is_extension=False, extension_scope=None,
323 options=None),
324 ],
325 extensions=[
326 ],
327 nested_types=[], # TODO(robinson): Implement.
328 enum_types=[
329 ],
330 options=None)
331
332
333_FIELDDESCRIPTORPROTO = descriptor.Descriptor(
334 name='FieldDescriptorProto',
335 full_name='froofle.protobuf.FieldDescriptorProto',
336 filename='froofle/protobuf/descriptor.proto',
337 containing_type=None,
338 fields=[
339 descriptor.FieldDescriptor(
340 name='name', full_name='froofle.protobuf.FieldDescriptorProto.name', index=0,
341 number=1, type=9, cpp_type=9, label=1,
342 default_value=unicode("", "utf-8"),
343 message_type=None, enum_type=None, containing_type=None,
344 is_extension=False, extension_scope=None,
345 options=None),
346 descriptor.FieldDescriptor(
347 name='number', full_name='froofle.protobuf.FieldDescriptorProto.number', index=1,
348 number=3, type=5, cpp_type=1, label=1,
349 default_value=0,
350 message_type=None, enum_type=None, containing_type=None,
351 is_extension=False, extension_scope=None,
352 options=None),
353 descriptor.FieldDescriptor(
354 name='label', full_name='froofle.protobuf.FieldDescriptorProto.label', index=2,
355 number=4, type=14, cpp_type=8, label=1,
356 default_value=1,
357 message_type=None, enum_type=None, containing_type=None,
358 is_extension=False, extension_scope=None,
359 options=None),
360 descriptor.FieldDescriptor(
361 name='type', full_name='froofle.protobuf.FieldDescriptorProto.type', index=3,
362 number=5, type=14, cpp_type=8, label=1,
363 default_value=1,
364 message_type=None, enum_type=None, containing_type=None,
365 is_extension=False, extension_scope=None,
366 options=None),
367 descriptor.FieldDescriptor(
368 name='type_name', full_name='froofle.protobuf.FieldDescriptorProto.type_name', index=4,
369 number=6, type=9, cpp_type=9, label=1,
370 default_value=unicode("", "utf-8"),
371 message_type=None, enum_type=None, containing_type=None,
372 is_extension=False, extension_scope=None,
373 options=None),
374 descriptor.FieldDescriptor(
375 name='extendee', full_name='froofle.protobuf.FieldDescriptorProto.extendee', index=5,
376 number=2, type=9, cpp_type=9, label=1,
377 default_value=unicode("", "utf-8"),
378 message_type=None, enum_type=None, containing_type=None,
379 is_extension=False, extension_scope=None,
380 options=None),
381 descriptor.FieldDescriptor(
382 name='default_value', full_name='froofle.protobuf.FieldDescriptorProto.default_value', index=6,
383 number=7, type=9, cpp_type=9, label=1,
384 default_value=unicode("", "utf-8"),
385 message_type=None, enum_type=None, containing_type=None,
386 is_extension=False, extension_scope=None,
387 options=None),
388 descriptor.FieldDescriptor(
389 name='options', full_name='froofle.protobuf.FieldDescriptorProto.options', index=7,
390 number=8, type=11, cpp_type=10, label=1,
391 default_value=None,
392 message_type=None, enum_type=None, containing_type=None,
393 is_extension=False, extension_scope=None,
394 options=None),
395 ],
396 extensions=[
397 ],
398 nested_types=[], # TODO(robinson): Implement.
399 enum_types=[
400 _FIELDDESCRIPTORPROTO_TYPE,
401 _FIELDDESCRIPTORPROTO_LABEL,
402 ],
403 options=None)
404
405
406_ENUMDESCRIPTORPROTO = descriptor.Descriptor(
407 name='EnumDescriptorProto',
408 full_name='froofle.protobuf.EnumDescriptorProto',
409 filename='froofle/protobuf/descriptor.proto',
410 containing_type=None,
411 fields=[
412 descriptor.FieldDescriptor(
413 name='name', full_name='froofle.protobuf.EnumDescriptorProto.name', index=0,
414 number=1, type=9, cpp_type=9, label=1,
415 default_value=unicode("", "utf-8"),
416 message_type=None, enum_type=None, containing_type=None,
417 is_extension=False, extension_scope=None,
418 options=None),
419 descriptor.FieldDescriptor(
420 name='value', full_name='froofle.protobuf.EnumDescriptorProto.value', index=1,
421 number=2, type=11, cpp_type=10, label=3,
422 default_value=[],
423 message_type=None, enum_type=None, containing_type=None,
424 is_extension=False, extension_scope=None,
425 options=None),
426 descriptor.FieldDescriptor(
427 name='options', full_name='froofle.protobuf.EnumDescriptorProto.options', index=2,
428 number=3, type=11, cpp_type=10, label=1,
429 default_value=None,
430 message_type=None, enum_type=None, containing_type=None,
431 is_extension=False, extension_scope=None,
432 options=None),
433 ],
434 extensions=[
435 ],
436 nested_types=[], # TODO(robinson): Implement.
437 enum_types=[
438 ],
439 options=None)
440
441
442_ENUMVALUEDESCRIPTORPROTO = descriptor.Descriptor(
443 name='EnumValueDescriptorProto',
444 full_name='froofle.protobuf.EnumValueDescriptorProto',
445 filename='froofle/protobuf/descriptor.proto',
446 containing_type=None,
447 fields=[
448 descriptor.FieldDescriptor(
449 name='name', full_name='froofle.protobuf.EnumValueDescriptorProto.name', index=0,
450 number=1, type=9, cpp_type=9, label=1,
451 default_value=unicode("", "utf-8"),
452 message_type=None, enum_type=None, containing_type=None,
453 is_extension=False, extension_scope=None,
454 options=None),
455 descriptor.FieldDescriptor(
456 name='number', full_name='froofle.protobuf.EnumValueDescriptorProto.number', index=1,
457 number=2, type=5, cpp_type=1, label=1,
458 default_value=0,
459 message_type=None, enum_type=None, containing_type=None,
460 is_extension=False, extension_scope=None,
461 options=None),
462 descriptor.FieldDescriptor(
463 name='options', full_name='froofle.protobuf.EnumValueDescriptorProto.options', index=2,
464 number=3, type=11, cpp_type=10, label=1,
465 default_value=None,
466 message_type=None, enum_type=None, containing_type=None,
467 is_extension=False, extension_scope=None,
468 options=None),
469 ],
470 extensions=[
471 ],
472 nested_types=[], # TODO(robinson): Implement.
473 enum_types=[
474 ],
475 options=None)
476
477
478_SERVICEDESCRIPTORPROTO = descriptor.Descriptor(
479 name='ServiceDescriptorProto',
480 full_name='froofle.protobuf.ServiceDescriptorProto',
481 filename='froofle/protobuf/descriptor.proto',
482 containing_type=None,
483 fields=[
484 descriptor.FieldDescriptor(
485 name='name', full_name='froofle.protobuf.ServiceDescriptorProto.name', index=0,
486 number=1, type=9, cpp_type=9, label=1,
487 default_value=unicode("", "utf-8"),
488 message_type=None, enum_type=None, containing_type=None,
489 is_extension=False, extension_scope=None,
490 options=None),
491 descriptor.FieldDescriptor(
492 name='method', full_name='froofle.protobuf.ServiceDescriptorProto.method', index=1,
493 number=2, type=11, cpp_type=10, label=3,
494 default_value=[],
495 message_type=None, enum_type=None, containing_type=None,
496 is_extension=False, extension_scope=None,
497 options=None),
498 descriptor.FieldDescriptor(
499 name='options', full_name='froofle.protobuf.ServiceDescriptorProto.options', index=2,
500 number=3, type=11, cpp_type=10, label=1,
501 default_value=None,
502 message_type=None, enum_type=None, containing_type=None,
503 is_extension=False, extension_scope=None,
504 options=None),
505 ],
506 extensions=[
507 ],
508 nested_types=[], # TODO(robinson): Implement.
509 enum_types=[
510 ],
511 options=None)
512
513
514_METHODDESCRIPTORPROTO = descriptor.Descriptor(
515 name='MethodDescriptorProto',
516 full_name='froofle.protobuf.MethodDescriptorProto',
517 filename='froofle/protobuf/descriptor.proto',
518 containing_type=None,
519 fields=[
520 descriptor.FieldDescriptor(
521 name='name', full_name='froofle.protobuf.MethodDescriptorProto.name', index=0,
522 number=1, type=9, cpp_type=9, label=1,
523 default_value=unicode("", "utf-8"),
524 message_type=None, enum_type=None, containing_type=None,
525 is_extension=False, extension_scope=None,
526 options=None),
527 descriptor.FieldDescriptor(
528 name='input_type', full_name='froofle.protobuf.MethodDescriptorProto.input_type', index=1,
529 number=2, type=9, cpp_type=9, label=1,
530 default_value=unicode("", "utf-8"),
531 message_type=None, enum_type=None, containing_type=None,
532 is_extension=False, extension_scope=None,
533 options=None),
534 descriptor.FieldDescriptor(
535 name='output_type', full_name='froofle.protobuf.MethodDescriptorProto.output_type', index=2,
536 number=3, type=9, cpp_type=9, label=1,
537 default_value=unicode("", "utf-8"),
538 message_type=None, enum_type=None, containing_type=None,
539 is_extension=False, extension_scope=None,
540 options=None),
541 descriptor.FieldDescriptor(
542 name='options', full_name='froofle.protobuf.MethodDescriptorProto.options', index=3,
543 number=4, type=11, cpp_type=10, label=1,
544 default_value=None,
545 message_type=None, enum_type=None, containing_type=None,
546 is_extension=False, extension_scope=None,
547 options=None),
548 ],
549 extensions=[
550 ],
551 nested_types=[], # TODO(robinson): Implement.
552 enum_types=[
553 ],
554 options=None)
555
556
557_FILEOPTIONS = descriptor.Descriptor(
558 name='FileOptions',
559 full_name='froofle.protobuf.FileOptions',
560 filename='froofle/protobuf/descriptor.proto',
561 containing_type=None,
562 fields=[
563 descriptor.FieldDescriptor(
564 name='java_package', full_name='froofle.protobuf.FileOptions.java_package', index=0,
565 number=1, type=9, cpp_type=9, label=1,
566 default_value=unicode("", "utf-8"),
567 message_type=None, enum_type=None, containing_type=None,
568 is_extension=False, extension_scope=None,
569 options=None),
570 descriptor.FieldDescriptor(
571 name='java_outer_classname', full_name='froofle.protobuf.FileOptions.java_outer_classname', index=1,
572 number=8, type=9, cpp_type=9, label=1,
573 default_value=unicode("", "utf-8"),
574 message_type=None, enum_type=None, containing_type=None,
575 is_extension=False, extension_scope=None,
576 options=None),
577 descriptor.FieldDescriptor(
578 name='java_multiple_files', full_name='froofle.protobuf.FileOptions.java_multiple_files', index=2,
579 number=10, type=8, cpp_type=7, label=1,
580 default_value=False,
581 message_type=None, enum_type=None, containing_type=None,
582 is_extension=False, extension_scope=None,
583 options=None),
584 descriptor.FieldDescriptor(
585 name='optimize_for', full_name='froofle.protobuf.FileOptions.optimize_for', index=3,
586 number=9, type=14, cpp_type=8, label=1,
587 default_value=2,
588 message_type=None, enum_type=None, containing_type=None,
589 is_extension=False, extension_scope=None,
590 options=None),
591 descriptor.FieldDescriptor(
592 name='uninterpreted_option', full_name='froofle.protobuf.FileOptions.uninterpreted_option', index=4,
593 number=999, type=11, cpp_type=10, label=3,
594 default_value=[],
595 message_type=None, enum_type=None, containing_type=None,
596 is_extension=False, extension_scope=None,
597 options=None),
598 ],
599 extensions=[
600 ],
601 nested_types=[], # TODO(robinson): Implement.
602 enum_types=[
603 _FILEOPTIONS_OPTIMIZEMODE,
604 ],
605 options=None)
606
607
608_MESSAGEOPTIONS = descriptor.Descriptor(
609 name='MessageOptions',
610 full_name='froofle.protobuf.MessageOptions',
611 filename='froofle/protobuf/descriptor.proto',
612 containing_type=None,
613 fields=[
614 descriptor.FieldDescriptor(
615 name='message_set_wire_format', full_name='froofle.protobuf.MessageOptions.message_set_wire_format', index=0,
616 number=1, type=8, cpp_type=7, label=1,
617 default_value=False,
618 message_type=None, enum_type=None, containing_type=None,
619 is_extension=False, extension_scope=None,
620 options=None),
621 descriptor.FieldDescriptor(
622 name='uninterpreted_option', full_name='froofle.protobuf.MessageOptions.uninterpreted_option', index=1,
623 number=999, type=11, cpp_type=10, label=3,
624 default_value=[],
625 message_type=None, enum_type=None, containing_type=None,
626 is_extension=False, extension_scope=None,
627 options=None),
628 ],
629 extensions=[
630 ],
631 nested_types=[], # TODO(robinson): Implement.
632 enum_types=[
633 ],
634 options=None)
635
636
637_FIELDOPTIONS = descriptor.Descriptor(
638 name='FieldOptions',
639 full_name='froofle.protobuf.FieldOptions',
640 filename='froofle/protobuf/descriptor.proto',
641 containing_type=None,
642 fields=[
643 descriptor.FieldDescriptor(
644 name='ctype', full_name='froofle.protobuf.FieldOptions.ctype', index=0,
645 number=1, type=14, cpp_type=8, label=1,
646 default_value=1,
647 message_type=None, enum_type=None, containing_type=None,
648 is_extension=False, extension_scope=None,
649 options=None),
650 descriptor.FieldDescriptor(
651 name='experimental_map_key', full_name='froofle.protobuf.FieldOptions.experimental_map_key', index=1,
652 number=9, type=9, cpp_type=9, label=1,
653 default_value=unicode("", "utf-8"),
654 message_type=None, enum_type=None, containing_type=None,
655 is_extension=False, extension_scope=None,
656 options=None),
657 descriptor.FieldDescriptor(
658 name='uninterpreted_option', full_name='froofle.protobuf.FieldOptions.uninterpreted_option', index=2,
659 number=999, type=11, cpp_type=10, label=3,
660 default_value=[],
661 message_type=None, enum_type=None, containing_type=None,
662 is_extension=False, extension_scope=None,
663 options=None),
664 ],
665 extensions=[
666 ],
667 nested_types=[], # TODO(robinson): Implement.
668 enum_types=[
669 _FIELDOPTIONS_CTYPE,
670 ],
671 options=None)
672
673
674_ENUMOPTIONS = descriptor.Descriptor(
675 name='EnumOptions',
676 full_name='froofle.protobuf.EnumOptions',
677 filename='froofle/protobuf/descriptor.proto',
678 containing_type=None,
679 fields=[
680 descriptor.FieldDescriptor(
681 name='uninterpreted_option', full_name='froofle.protobuf.EnumOptions.uninterpreted_option', index=0,
682 number=999, type=11, cpp_type=10, label=3,
683 default_value=[],
684 message_type=None, enum_type=None, containing_type=None,
685 is_extension=False, extension_scope=None,
686 options=None),
687 ],
688 extensions=[
689 ],
690 nested_types=[], # TODO(robinson): Implement.
691 enum_types=[
692 ],
693 options=None)
694
695
696_ENUMVALUEOPTIONS = descriptor.Descriptor(
697 name='EnumValueOptions',
698 full_name='froofle.protobuf.EnumValueOptions',
699 filename='froofle/protobuf/descriptor.proto',
700 containing_type=None,
701 fields=[
702 descriptor.FieldDescriptor(
703 name='uninterpreted_option', full_name='froofle.protobuf.EnumValueOptions.uninterpreted_option', index=0,
704 number=999, type=11, cpp_type=10, label=3,
705 default_value=[],
706 message_type=None, enum_type=None, containing_type=None,
707 is_extension=False, extension_scope=None,
708 options=None),
709 ],
710 extensions=[
711 ],
712 nested_types=[], # TODO(robinson): Implement.
713 enum_types=[
714 ],
715 options=None)
716
717
718_SERVICEOPTIONS = descriptor.Descriptor(
719 name='ServiceOptions',
720 full_name='froofle.protobuf.ServiceOptions',
721 filename='froofle/protobuf/descriptor.proto',
722 containing_type=None,
723 fields=[
724 descriptor.FieldDescriptor(
725 name='uninterpreted_option', full_name='froofle.protobuf.ServiceOptions.uninterpreted_option', index=0,
726 number=999, type=11, cpp_type=10, label=3,
727 default_value=[],
728 message_type=None, enum_type=None, containing_type=None,
729 is_extension=False, extension_scope=None,
730 options=None),
731 ],
732 extensions=[
733 ],
734 nested_types=[], # TODO(robinson): Implement.
735 enum_types=[
736 ],
737 options=None)
738
739
740_METHODOPTIONS = descriptor.Descriptor(
741 name='MethodOptions',
742 full_name='froofle.protobuf.MethodOptions',
743 filename='froofle/protobuf/descriptor.proto',
744 containing_type=None,
745 fields=[
746 descriptor.FieldDescriptor(
747 name='uninterpreted_option', full_name='froofle.protobuf.MethodOptions.uninterpreted_option', index=0,
748 number=999, type=11, cpp_type=10, label=3,
749 default_value=[],
750 message_type=None, enum_type=None, containing_type=None,
751 is_extension=False, extension_scope=None,
752 options=None),
753 ],
754 extensions=[
755 ],
756 nested_types=[], # TODO(robinson): Implement.
757 enum_types=[
758 ],
759 options=None)
760
761
762_UNINTERPRETEDOPTION_NAMEPART = descriptor.Descriptor(
763 name='NamePart',
764 full_name='froofle.protobuf.UninterpretedOption.NamePart',
765 filename='froofle/protobuf/descriptor.proto',
766 containing_type=None,
767 fields=[
768 descriptor.FieldDescriptor(
769 name='name_part', full_name='froofle.protobuf.UninterpretedOption.NamePart.name_part', index=0,
770 number=1, type=9, cpp_type=9, label=2,
771 default_value=unicode("", "utf-8"),
772 message_type=None, enum_type=None, containing_type=None,
773 is_extension=False, extension_scope=None,
774 options=None),
775 descriptor.FieldDescriptor(
776 name='is_extension', full_name='froofle.protobuf.UninterpretedOption.NamePart.is_extension', index=1,
777 number=2, type=8, cpp_type=7, label=2,
778 default_value=False,
779 message_type=None, enum_type=None, containing_type=None,
780 is_extension=False, extension_scope=None,
781 options=None),
782 ],
783 extensions=[
784 ],
785 nested_types=[], # TODO(robinson): Implement.
786 enum_types=[
787 ],
788 options=None)
789
790_UNINTERPRETEDOPTION = descriptor.Descriptor(
791 name='UninterpretedOption',
792 full_name='froofle.protobuf.UninterpretedOption',
793 filename='froofle/protobuf/descriptor.proto',
794 containing_type=None,
795 fields=[
796 descriptor.FieldDescriptor(
797 name='name', full_name='froofle.protobuf.UninterpretedOption.name', index=0,
798 number=2, type=11, cpp_type=10, label=3,
799 default_value=[],
800 message_type=None, enum_type=None, containing_type=None,
801 is_extension=False, extension_scope=None,
802 options=None),
803 descriptor.FieldDescriptor(
804 name='identifier_value', full_name='froofle.protobuf.UninterpretedOption.identifier_value', index=1,
805 number=3, type=9, cpp_type=9, label=1,
806 default_value=unicode("", "utf-8"),
807 message_type=None, enum_type=None, containing_type=None,
808 is_extension=False, extension_scope=None,
809 options=None),
810 descriptor.FieldDescriptor(
811 name='positive_int_value', full_name='froofle.protobuf.UninterpretedOption.positive_int_value', index=2,
812 number=4, type=4, cpp_type=4, label=1,
813 default_value=0,
814 message_type=None, enum_type=None, containing_type=None,
815 is_extension=False, extension_scope=None,
816 options=None),
817 descriptor.FieldDescriptor(
818 name='negative_int_value', full_name='froofle.protobuf.UninterpretedOption.negative_int_value', index=3,
819 number=5, type=3, cpp_type=2, label=1,
820 default_value=0,
821 message_type=None, enum_type=None, containing_type=None,
822 is_extension=False, extension_scope=None,
823 options=None),
824 descriptor.FieldDescriptor(
825 name='double_value', full_name='froofle.protobuf.UninterpretedOption.double_value', index=4,
826 number=6, type=1, cpp_type=5, label=1,
827 default_value=0,
828 message_type=None, enum_type=None, containing_type=None,
829 is_extension=False, extension_scope=None,
830 options=None),
831 descriptor.FieldDescriptor(
832 name='string_value', full_name='froofle.protobuf.UninterpretedOption.string_value', index=5,
833 number=7, type=12, cpp_type=9, label=1,
834 default_value="",
835 message_type=None, enum_type=None, containing_type=None,
836 is_extension=False, extension_scope=None,
837 options=None),
838 ],
839 extensions=[
840 ],
841 nested_types=[], # TODO(robinson): Implement.
842 enum_types=[
843 ],
844 options=None)
845
846
847_FILEDESCRIPTORSET.fields_by_name['file'].message_type = _FILEDESCRIPTORPROTO
848_FILEDESCRIPTORPROTO.fields_by_name['message_type'].message_type = _DESCRIPTORPROTO
849_FILEDESCRIPTORPROTO.fields_by_name['enum_type'].message_type = _ENUMDESCRIPTORPROTO
850_FILEDESCRIPTORPROTO.fields_by_name['service'].message_type = _SERVICEDESCRIPTORPROTO
851_FILEDESCRIPTORPROTO.fields_by_name['extension'].message_type = _FIELDDESCRIPTORPROTO
852_FILEDESCRIPTORPROTO.fields_by_name['options'].message_type = _FILEOPTIONS
853_DESCRIPTORPROTO.fields_by_name['field'].message_type = _FIELDDESCRIPTORPROTO
854_DESCRIPTORPROTO.fields_by_name['extension'].message_type = _FIELDDESCRIPTORPROTO
855_DESCRIPTORPROTO.fields_by_name['nested_type'].message_type = _DESCRIPTORPROTO
856_DESCRIPTORPROTO.fields_by_name['enum_type'].message_type = _ENUMDESCRIPTORPROTO
857_DESCRIPTORPROTO.fields_by_name['extension_range'].message_type = _DESCRIPTORPROTO_EXTENSIONRANGE
858_DESCRIPTORPROTO.fields_by_name['options'].message_type = _MESSAGEOPTIONS
859_FIELDDESCRIPTORPROTO.fields_by_name['label'].enum_type = _FIELDDESCRIPTORPROTO_LABEL
860_FIELDDESCRIPTORPROTO.fields_by_name['type'].enum_type = _FIELDDESCRIPTORPROTO_TYPE
861_FIELDDESCRIPTORPROTO.fields_by_name['options'].message_type = _FIELDOPTIONS
862_ENUMDESCRIPTORPROTO.fields_by_name['value'].message_type = _ENUMVALUEDESCRIPTORPROTO
863_ENUMDESCRIPTORPROTO.fields_by_name['options'].message_type = _ENUMOPTIONS
864_ENUMVALUEDESCRIPTORPROTO.fields_by_name['options'].message_type = _ENUMVALUEOPTIONS
865_SERVICEDESCRIPTORPROTO.fields_by_name['method'].message_type = _METHODDESCRIPTORPROTO
866_SERVICEDESCRIPTORPROTO.fields_by_name['options'].message_type = _SERVICEOPTIONS
867_METHODDESCRIPTORPROTO.fields_by_name['options'].message_type = _METHODOPTIONS
868_FILEOPTIONS.fields_by_name['optimize_for'].enum_type = _FILEOPTIONS_OPTIMIZEMODE
869_FILEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION
870_MESSAGEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION
871_FIELDOPTIONS.fields_by_name['ctype'].enum_type = _FIELDOPTIONS_CTYPE
872_FIELDOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION
873_ENUMOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION
874_ENUMVALUEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION
875_SERVICEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION
876_METHODOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION
877_UNINTERPRETEDOPTION.fields_by_name['name'].message_type = _UNINTERPRETEDOPTION_NAMEPART
878
879class FileDescriptorSet(message.Message):
880 __metaclass__ = reflection.GeneratedProtocolMessageType
881 DESCRIPTOR = _FILEDESCRIPTORSET
882
883class FileDescriptorProto(message.Message):
884 __metaclass__ = reflection.GeneratedProtocolMessageType
885 DESCRIPTOR = _FILEDESCRIPTORPROTO
886
887class DescriptorProto(message.Message):
888 __metaclass__ = reflection.GeneratedProtocolMessageType
889
890 class ExtensionRange(message.Message):
891 __metaclass__ = reflection.GeneratedProtocolMessageType
892 DESCRIPTOR = _DESCRIPTORPROTO_EXTENSIONRANGE
893 DESCRIPTOR = _DESCRIPTORPROTO
894
895class FieldDescriptorProto(message.Message):
896 __metaclass__ = reflection.GeneratedProtocolMessageType
897 DESCRIPTOR = _FIELDDESCRIPTORPROTO
898
899class EnumDescriptorProto(message.Message):
900 __metaclass__ = reflection.GeneratedProtocolMessageType
901 DESCRIPTOR = _ENUMDESCRIPTORPROTO
902
903class EnumValueDescriptorProto(message.Message):
904 __metaclass__ = reflection.GeneratedProtocolMessageType
905 DESCRIPTOR = _ENUMVALUEDESCRIPTORPROTO
906
907class ServiceDescriptorProto(message.Message):
908 __metaclass__ = reflection.GeneratedProtocolMessageType
909 DESCRIPTOR = _SERVICEDESCRIPTORPROTO
910
911class MethodDescriptorProto(message.Message):
912 __metaclass__ = reflection.GeneratedProtocolMessageType
913 DESCRIPTOR = _METHODDESCRIPTORPROTO
914
915class FileOptions(message.Message):
916 __metaclass__ = reflection.GeneratedProtocolMessageType
917 DESCRIPTOR = _FILEOPTIONS
918
919class MessageOptions(message.Message):
920 __metaclass__ = reflection.GeneratedProtocolMessageType
921 DESCRIPTOR = _MESSAGEOPTIONS
922
923class FieldOptions(message.Message):
924 __metaclass__ = reflection.GeneratedProtocolMessageType
925 DESCRIPTOR = _FIELDOPTIONS
926
927class EnumOptions(message.Message):
928 __metaclass__ = reflection.GeneratedProtocolMessageType
929 DESCRIPTOR = _ENUMOPTIONS
930
931class EnumValueOptions(message.Message):
932 __metaclass__ = reflection.GeneratedProtocolMessageType
933 DESCRIPTOR = _ENUMVALUEOPTIONS
934
935class ServiceOptions(message.Message):
936 __metaclass__ = reflection.GeneratedProtocolMessageType
937 DESCRIPTOR = _SERVICEOPTIONS
938
939class MethodOptions(message.Message):
940 __metaclass__ = reflection.GeneratedProtocolMessageType
941 DESCRIPTOR = _METHODOPTIONS
942
943class UninterpretedOption(message.Message):
944 __metaclass__ = reflection.GeneratedProtocolMessageType
945
946 class NamePart(message.Message):
947 __metaclass__ = reflection.GeneratedProtocolMessageType
948 DESCRIPTOR = _UNINTERPRETEDOPTION_NAMEPART
949 DESCRIPTOR = _UNINTERPRETEDOPTION
950
diff --git a/froofle/protobuf/internal/__init__.py b/froofle/protobuf/internal/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/froofle/protobuf/internal/__init__.py
diff --git a/froofle/protobuf/internal/decoder.py b/froofle/protobuf/internal/decoder.py
new file mode 100644
index 00000000..2dd4c96e
--- /dev/null
+++ b/froofle/protobuf/internal/decoder.py
@@ -0,0 +1,209 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Class for decoding protocol buffer primitives.
32
33Contains the logic for decoding every logical protocol field type
34from one of the 5 physical wire types.
35"""
36
37__author__ = 'robinson@google.com (Will Robinson)'
38
39import struct
40from froofle.protobuf import message
41from froofle.protobuf.internal import input_stream
42from froofle.protobuf.internal import wire_format
43
44
45
46# Note that much of this code is ported from //net/proto/ProtocolBuffer, and
47# that the interface is strongly inspired by WireFormat from the C++ proto2
48# implementation.
49
50
51class Decoder(object):
52
53 """Decodes logical protocol buffer fields from the wire."""
54
55 def __init__(self, s):
56 """Initializes the decoder to read from s.
57
58 Args:
59 s: An immutable sequence of bytes, which must be accessible
60 via the Python buffer() primitive (i.e., buffer(s)).
61 """
62 self._stream = input_stream.InputStream(s)
63
64 def EndOfStream(self):
65 """Returns true iff we've reached the end of the bytes we're reading."""
66 return self._stream.EndOfStream()
67
68 def Position(self):
69 """Returns the 0-indexed position in |s|."""
70 return self._stream.Position()
71
72 def ReadFieldNumberAndWireType(self):
73 """Reads a tag from the wire. Returns a (field_number, wire_type) pair."""
74 tag_and_type = self.ReadUInt32()
75 return wire_format.UnpackTag(tag_and_type)
76
77 def SkipBytes(self, bytes):
78 """Skips the specified number of bytes on the wire."""
79 self._stream.SkipBytes(bytes)
80
81 # Note that the Read*() methods below are not exactly symmetrical with the
82 # corresponding Encoder.Append*() methods. Those Encoder methods first
83 # encode a tag, but the Read*() methods below assume that the tag has already
84 # been read, and that the client wishes to read a field of the specified type
85 # starting at the current position.
86
87 def ReadInt32(self):
88 """Reads and returns a signed, varint-encoded, 32-bit integer."""
89 return self._stream.ReadVarint32()
90
91 def ReadInt64(self):
92 """Reads and returns a signed, varint-encoded, 64-bit integer."""
93 return self._stream.ReadVarint64()
94
95 def ReadUInt32(self):
96 """Reads and returns an signed, varint-encoded, 32-bit integer."""
97 return self._stream.ReadVarUInt32()
98
99 def ReadUInt64(self):
100 """Reads and returns an signed, varint-encoded,64-bit integer."""
101 return self._stream.ReadVarUInt64()
102
103 def ReadSInt32(self):
104 """Reads and returns a signed, zigzag-encoded, varint-encoded,
105 32-bit integer."""
106 return wire_format.ZigZagDecode(self._stream.ReadVarUInt32())
107
108 def ReadSInt64(self):
109 """Reads and returns a signed, zigzag-encoded, varint-encoded,
110 64-bit integer."""
111 return wire_format.ZigZagDecode(self._stream.ReadVarUInt64())
112
113 def ReadFixed32(self):
114 """Reads and returns an unsigned, fixed-width, 32-bit integer."""
115 return self._stream.ReadLittleEndian32()
116
117 def ReadFixed64(self):
118 """Reads and returns an unsigned, fixed-width, 64-bit integer."""
119 return self._stream.ReadLittleEndian64()
120
121 def ReadSFixed32(self):
122 """Reads and returns a signed, fixed-width, 32-bit integer."""
123 value = self._stream.ReadLittleEndian32()
124 if value >= (1 << 31):
125 value -= (1 << 32)
126 return value
127
128 def ReadSFixed64(self):
129 """Reads and returns a signed, fixed-width, 64-bit integer."""
130 value = self._stream.ReadLittleEndian64()
131 if value >= (1 << 63):
132 value -= (1 << 64)
133 return value
134
135 def ReadFloat(self):
136 """Reads and returns a 4-byte floating-point number."""
137 serialized = self._stream.ReadBytes(4)
138 return struct.unpack('f', serialized)[0]
139
140 def ReadDouble(self):
141 """Reads and returns an 8-byte floating-point number."""
142 serialized = self._stream.ReadBytes(8)
143 return struct.unpack('d', serialized)[0]
144
145 def ReadBool(self):
146 """Reads and returns a bool."""
147 i = self._stream.ReadVarUInt32()
148 return bool(i)
149
150 def ReadEnum(self):
151 """Reads and returns an enum value."""
152 return self._stream.ReadVarUInt32()
153
154 def ReadString(self):
155 """Reads and returns a length-delimited string."""
156 bytes = self.ReadBytes()
157 return unicode(bytes, 'utf-8')
158
159 def ReadBytes(self):
160 """Reads and returns a length-delimited byte sequence."""
161 length = self._stream.ReadVarUInt32()
162 return self._stream.ReadBytes(length)
163
164 def ReadMessageInto(self, msg):
165 """Calls msg.MergeFromString() to merge
166 length-delimited serialized message data into |msg|.
167
168 REQUIRES: The decoder must be positioned at the serialized "length"
169 prefix to a length-delmiited serialized message.
170
171 POSTCONDITION: The decoder is positioned just after the
172 serialized message, and we have merged those serialized
173 contents into |msg|.
174 """
175 length = self._stream.ReadVarUInt32()
176 sub_buffer = self._stream.GetSubBuffer(length)
177 num_bytes_used = msg.MergeFromString(sub_buffer)
178 if num_bytes_used != length:
179 raise message.DecodeError(
180 'Submessage told to deserialize from %d-byte encoding, '
181 'but used only %d bytes' % (length, num_bytes_used))
182 self._stream.SkipBytes(num_bytes_used)
183
184 def ReadGroupInto(self, expected_field_number, group):
185 """Calls group.MergeFromString() to merge
186 END_GROUP-delimited serialized message data into |group|.
187 We'll raise an exception if we don't find an END_GROUP
188 tag immediately after the serialized message contents.
189
190 REQUIRES: The decoder is positioned just after the START_GROUP
191 tag for this group.
192
193 POSTCONDITION: The decoder is positioned just after the
194 END_GROUP tag for this group, and we have merged
195 the contents of the group into |group|.
196 """
197 sub_buffer = self._stream.GetSubBuffer() # No a priori length limit.
198 num_bytes_used = group.MergeFromString(sub_buffer)
199 if num_bytes_used < 0:
200 raise message.DecodeError('Group message reported negative bytes read.')
201 self._stream.SkipBytes(num_bytes_used)
202 field_number, field_type = self.ReadFieldNumberAndWireType()
203 if field_type != wire_format.WIRETYPE_END_GROUP:
204 raise message.DecodeError('Group message did not end with an END_GROUP.')
205 if field_number != expected_field_number:
206 raise message.DecodeError('END_GROUP tag had field '
207 'number %d, was expecting field number %d' % (
208 field_number, expected_field_number))
209 # We're now positioned just after the END_GROUP tag. Perfect.
diff --git a/froofle/protobuf/internal/encoder.py b/froofle/protobuf/internal/encoder.py
new file mode 100644
index 00000000..8b924b3e
--- /dev/null
+++ b/froofle/protobuf/internal/encoder.py
@@ -0,0 +1,206 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Class for encoding protocol message primitives.
32
33Contains the logic for encoding every logical protocol field type
34into one of the 5 physical wire types.
35"""
36
37__author__ = 'robinson@google.com (Will Robinson)'
38
39import struct
40from froofle.protobuf import message
41from froofle.protobuf.internal import wire_format
42from froofle.protobuf.internal import output_stream
43
44
45# Note that much of this code is ported from //net/proto/ProtocolBuffer, and
46# that the interface is strongly inspired by WireFormat from the C++ proto2
47# implementation.
48
49
50class Encoder(object):
51
52 """Encodes logical protocol buffer fields to the wire format."""
53
54 def __init__(self):
55 self._stream = output_stream.OutputStream()
56
57 def ToString(self):
58 """Returns all values encoded in this object as a string."""
59 return self._stream.ToString()
60
61 # All the Append*() methods below first append a tag+type pair to the buffer
62 # before appending the specified value.
63
64 def AppendInt32(self, field_number, value):
65 """Appends a 32-bit integer to our buffer, varint-encoded."""
66 self._AppendTag(field_number, wire_format.WIRETYPE_VARINT)
67 self._stream.AppendVarint32(value)
68
69 def AppendInt64(self, field_number, value):
70 """Appends a 64-bit integer to our buffer, varint-encoded."""
71 self._AppendTag(field_number, wire_format.WIRETYPE_VARINT)
72 self._stream.AppendVarint64(value)
73
74 def AppendUInt32(self, field_number, unsigned_value):
75 """Appends an unsigned 32-bit integer to our buffer, varint-encoded."""
76 self._AppendTag(field_number, wire_format.WIRETYPE_VARINT)
77 self._stream.AppendVarUInt32(unsigned_value)
78
79 def AppendUInt64(self, field_number, unsigned_value):
80 """Appends an unsigned 64-bit integer to our buffer, varint-encoded."""
81 self._AppendTag(field_number, wire_format.WIRETYPE_VARINT)
82 self._stream.AppendVarUInt64(unsigned_value)
83
84 def AppendSInt32(self, field_number, value):
85 """Appends a 32-bit integer to our buffer, zigzag-encoded and then
86 varint-encoded.
87 """
88 self._AppendTag(field_number, wire_format.WIRETYPE_VARINT)
89 zigzag_value = wire_format.ZigZagEncode(value)
90 self._stream.AppendVarUInt32(zigzag_value)
91
92 def AppendSInt64(self, field_number, value):
93 """Appends a 64-bit integer to our buffer, zigzag-encoded and then
94 varint-encoded.
95 """
96 self._AppendTag(field_number, wire_format.WIRETYPE_VARINT)
97 zigzag_value = wire_format.ZigZagEncode(value)
98 self._stream.AppendVarUInt64(zigzag_value)
99
100 def AppendFixed32(self, field_number, unsigned_value):
101 """Appends an unsigned 32-bit integer to our buffer, in little-endian
102 byte-order.
103 """
104 self._AppendTag(field_number, wire_format.WIRETYPE_FIXED32)
105 self._stream.AppendLittleEndian32(unsigned_value)
106
107 def AppendFixed64(self, field_number, unsigned_value):
108 """Appends an unsigned 64-bit integer to our buffer, in little-endian
109 byte-order.
110 """
111 self._AppendTag(field_number, wire_format.WIRETYPE_FIXED64)
112 self._stream.AppendLittleEndian64(unsigned_value)
113
114 def AppendSFixed32(self, field_number, value):
115 """Appends a signed 32-bit integer to our buffer, in little-endian
116 byte-order.
117 """
118 sign = (value & 0x80000000) and -1 or 0
119 if value >> 32 != sign:
120 raise message.EncodeError('SFixed32 out of range: %d' % value)
121 self._AppendTag(field_number, wire_format.WIRETYPE_FIXED32)
122 self._stream.AppendLittleEndian32(value & 0xffffffff)
123
124 def AppendSFixed64(self, field_number, value):
125 """Appends a signed 64-bit integer to our buffer, in little-endian
126 byte-order.
127 """
128 sign = (value & 0x8000000000000000) and -1 or 0
129 if value >> 64 != sign:
130 raise message.EncodeError('SFixed64 out of range: %d' % value)
131 self._AppendTag(field_number, wire_format.WIRETYPE_FIXED64)
132 self._stream.AppendLittleEndian64(value & 0xffffffffffffffff)
133
134 def AppendFloat(self, field_number, value):
135 """Appends a floating-point number to our buffer."""
136 self._AppendTag(field_number, wire_format.WIRETYPE_FIXED32)
137 self._stream.AppendRawBytes(struct.pack('f', value))
138
139 def AppendDouble(self, field_number, value):
140 """Appends a double-precision floating-point number to our buffer."""
141 self._AppendTag(field_number, wire_format.WIRETYPE_FIXED64)
142 self._stream.AppendRawBytes(struct.pack('d', value))
143
144 def AppendBool(self, field_number, value):
145 """Appends a boolean to our buffer."""
146 self.AppendInt32(field_number, value)
147
148 def AppendEnum(self, field_number, value):
149 """Appends an enum value to our buffer."""
150 self.AppendInt32(field_number, value)
151
152 def AppendString(self, field_number, value):
153 """Appends a length-prefixed unicode string, encoded as UTF-8 to our buffer,
154 with the length varint-encoded.
155 """
156 self.AppendBytes(field_number, value.encode('utf-8'))
157
158 def AppendBytes(self, field_number, value):
159 """Appends a length-prefixed sequence of bytes to our buffer, with the
160 length varint-encoded.
161 """
162 self._AppendTag(field_number, wire_format.WIRETYPE_LENGTH_DELIMITED)
163 self._stream.AppendVarUInt32(len(value))
164 self._stream.AppendRawBytes(value)
165
166 # TODO(robinson): For AppendGroup() and AppendMessage(), we'd really like to
167 # avoid the extra string copy here. We can do so if we widen the Message
168 # interface to be able to serialize to a stream in addition to a string. The
169 # challenge when thinking ahead to the Python/C API implementation of Message
170 # is finding a stream-like Python thing to which we can write raw bytes
171 # from C. I'm not sure such a thing exists(?). (array.array is pretty much
172 # what we want, but it's not directly exposed in the Python/C API).
173
174 def AppendGroup(self, field_number, group):
175 """Appends a group to our buffer.
176 """
177 self._AppendTag(field_number, wire_format.WIRETYPE_START_GROUP)
178 self._stream.AppendRawBytes(group.SerializeToString())
179 self._AppendTag(field_number, wire_format.WIRETYPE_END_GROUP)
180
181 def AppendMessage(self, field_number, msg):
182 """Appends a nested message to our buffer.
183 """
184 self._AppendTag(field_number, wire_format.WIRETYPE_LENGTH_DELIMITED)
185 self._stream.AppendVarUInt32(msg.ByteSize())
186 self._stream.AppendRawBytes(msg.SerializeToString())
187
188 def AppendMessageSetItem(self, field_number, msg):
189 """Appends an item using the message set wire format.
190
191 The message set message looks like this:
192 message MessageSet {
193 repeated group Item = 1 {
194 required int32 type_id = 2;
195 required string message = 3;
196 }
197 }
198 """
199 self._AppendTag(1, wire_format.WIRETYPE_START_GROUP)
200 self.AppendInt32(2, field_number)
201 self.AppendMessage(3, msg)
202 self._AppendTag(1, wire_format.WIRETYPE_END_GROUP)
203
204 def _AppendTag(self, field_number, wire_type):
205 """Appends a tag containing field number and wire type information."""
206 self._stream.AppendVarUInt32(wire_format.PackTag(field_number, wire_type))
diff --git a/froofle/protobuf/internal/input_stream.py b/froofle/protobuf/internal/input_stream.py
new file mode 100644
index 00000000..26a26dcf
--- /dev/null
+++ b/froofle/protobuf/internal/input_stream.py
@@ -0,0 +1,326 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""InputStream is the primitive interface for reading bits from the wire.
32
33All protocol buffer deserialization can be expressed in terms of
34the InputStream primitives provided here.
35"""
36
37__author__ = 'robinson@google.com (Will Robinson)'
38
39import struct
40from array import array
41from froofle.protobuf import message
42from froofle.protobuf.internal import wire_format
43
44
45# Note that much of this code is ported from //net/proto/ProtocolBuffer, and
46# that the interface is strongly inspired by CodedInputStream from the C++
47# proto2 implementation.
48
49
50class InputStreamBuffer(object):
51
52 """Contains all logic for reading bits, and dealing with stream position.
53
54 If an InputStream method ever raises an exception, the stream is left
55 in an indeterminate state and is not safe for further use.
56 """
57
58 def __init__(self, s):
59 # What we really want is something like array('B', s), where elements we
60 # read from the array are already given to us as one-byte integers. BUT
61 # using array() instead of buffer() would force full string copies to result
62 # from each GetSubBuffer() call.
63 #
64 # So, if the N serialized bytes of a single protocol buffer object are
65 # split evenly between 2 child messages, and so on recursively, using
66 # array('B', s) instead of buffer() would incur an additional N*logN bytes
67 # copied during deserialization.
68 #
69 # The higher constant overhead of having to ord() for every byte we read
70 # from the buffer in _ReadVarintHelper() could definitely lead to worse
71 # performance in many real-world scenarios, even if the asymptotic
72 # complexity is better. However, our real answer is that the mythical
73 # Python/C extension module output mode for the protocol compiler will
74 # be blazing-fast and will eliminate most use of this class anyway.
75 self._buffer = buffer(s)
76 self._pos = 0
77
78 def EndOfStream(self):
79 """Returns true iff we're at the end of the stream.
80 If this returns true, then a call to any other InputStream method
81 will raise an exception.
82 """
83 return self._pos >= len(self._buffer)
84
85 def Position(self):
86 """Returns the current position in the stream, or equivalently, the
87 number of bytes read so far.
88 """
89 return self._pos
90
91 def GetSubBuffer(self, size=None):
92 """Returns a sequence-like object that represents a portion of our
93 underlying sequence.
94
95 Position 0 in the returned object corresponds to self.Position()
96 in this stream.
97
98 If size is specified, then the returned object ends after the
99 next "size" bytes in this stream. If size is not specified,
100 then the returned object ends at the end of this stream.
101
102 We guarantee that the returned object R supports the Python buffer
103 interface (and thus that the call buffer(R) will work).
104
105 Note that the returned buffer is read-only.
106
107 The intended use for this method is for nested-message and nested-group
108 deserialization, where we want to make a recursive MergeFromString()
109 call on the portion of the original sequence that contains the serialized
110 nested message. (And we'd like to do so without making unnecessary string
111 copies).
112
113 REQUIRES: size is nonnegative.
114 """
115 # Note that buffer() doesn't perform any actual string copy.
116 if size is None:
117 return buffer(self._buffer, self._pos)
118 else:
119 if size < 0:
120 raise message.DecodeError('Negative size %d' % size)
121 return buffer(self._buffer, self._pos, size)
122
123 def SkipBytes(self, num_bytes):
124 """Skip num_bytes bytes ahead, or go to the end of the stream, whichever
125 comes first.
126
127 REQUIRES: num_bytes is nonnegative.
128 """
129 if num_bytes < 0:
130 raise message.DecodeError('Negative num_bytes %d' % num_bytes)
131 self._pos += num_bytes
132 self._pos = min(self._pos, len(self._buffer))
133
134 def ReadBytes(self, size):
135 """Reads up to 'size' bytes from the stream, stopping early
136 only if we reach the end of the stream. Returns the bytes read
137 as a string.
138 """
139 if size < 0:
140 raise message.DecodeError('Negative size %d' % size)
141 s = (self._buffer[self._pos : self._pos + size])
142 self._pos += len(s) # Only advance by the number of bytes actually read.
143 return s
144
145 def ReadLittleEndian32(self):
146 """Interprets the next 4 bytes of the stream as a little-endian
147 encoded, unsiged 32-bit integer, and returns that integer.
148 """
149 try:
150 i = struct.unpack(wire_format.FORMAT_UINT32_LITTLE_ENDIAN,
151 self._buffer[self._pos : self._pos + 4])
152 self._pos += 4
153 return i[0] # unpack() result is a 1-element tuple.
154 except struct.error, e:
155 raise message.DecodeError(e)
156
157 def ReadLittleEndian64(self):
158 """Interprets the next 8 bytes of the stream as a little-endian
159 encoded, unsiged 64-bit integer, and returns that integer.
160 """
161 try:
162 i = struct.unpack(wire_format.FORMAT_UINT64_LITTLE_ENDIAN,
163 self._buffer[self._pos : self._pos + 8])
164 self._pos += 8
165 return i[0] # unpack() result is a 1-element tuple.
166 except struct.error, e:
167 raise message.DecodeError(e)
168
169 def ReadVarint32(self):
170 """Reads a varint from the stream, interprets this varint
171 as a signed, 32-bit integer, and returns the integer.
172 """
173 i = self.ReadVarint64()
174 if not wire_format.INT32_MIN <= i <= wire_format.INT32_MAX:
175 raise message.DecodeError('Value out of range for int32: %d' % i)
176 return int(i)
177
178 def ReadVarUInt32(self):
179 """Reads a varint from the stream, interprets this varint
180 as an unsigned, 32-bit integer, and returns the integer.
181 """
182 i = self.ReadVarUInt64()
183 if i > wire_format.UINT32_MAX:
184 raise message.DecodeError('Value out of range for uint32: %d' % i)
185 return i
186
187 def ReadVarint64(self):
188 """Reads a varint from the stream, interprets this varint
189 as a signed, 64-bit integer, and returns the integer.
190 """
191 i = self.ReadVarUInt64()
192 if i > wire_format.INT64_MAX:
193 i -= (1 << 64)
194 return i
195
196 def ReadVarUInt64(self):
197 """Reads a varint from the stream, interprets this varint
198 as an unsigned, 64-bit integer, and returns the integer.
199 """
200 i = self._ReadVarintHelper()
201 if not 0 <= i <= wire_format.UINT64_MAX:
202 raise message.DecodeError('Value out of range for uint64: %d' % i)
203 return i
204
205 def _ReadVarintHelper(self):
206 """Helper for the various varint-reading methods above.
207 Reads an unsigned, varint-encoded integer from the stream and
208 returns this integer.
209
210 Does no bounds checking except to ensure that we read at most as many bytes
211 as could possibly be present in a varint-encoded 64-bit number.
212 """
213 result = 0
214 shift = 0
215 while 1:
216 if shift >= 64:
217 raise message.DecodeError('Too many bytes when decoding varint.')
218 try:
219 b = ord(self._buffer[self._pos])
220 except IndexError:
221 raise message.DecodeError('Truncated varint.')
222 self._pos += 1
223 result |= ((b & 0x7f) << shift)
224 shift += 7
225 if not (b & 0x80):
226 return result
227
228class InputStreamArray(object):
229 def __init__(self, s):
230 self._buffer = array('B', s)
231 self._pos = 0
232
233 def EndOfStream(self):
234 return self._pos >= len(self._buffer)
235
236 def Position(self):
237 return self._pos
238
239 def GetSubBuffer(self, size=None):
240 if size is None:
241 return self._buffer[self._pos : ].tostring()
242 else:
243 if size < 0:
244 raise message.DecodeError('Negative size %d' % size)
245 return self._buffer[self._pos : self._pos + size].tostring()
246
247 def SkipBytes(self, num_bytes):
248 if num_bytes < 0:
249 raise message.DecodeError('Negative num_bytes %d' % num_bytes)
250 self._pos += num_bytes
251 self._pos = min(self._pos, len(self._buffer))
252
253 def ReadBytes(self, size):
254 if size < 0:
255 raise message.DecodeError('Negative size %d' % size)
256 s = self._buffer[self._pos : self._pos + size].tostring()
257 self._pos += len(s) # Only advance by the number of bytes actually read.
258 return s
259
260 def ReadLittleEndian32(self):
261 try:
262 i = struct.unpack(wire_format.FORMAT_UINT32_LITTLE_ENDIAN,
263 self._buffer[self._pos : self._pos + 4])
264 self._pos += 4
265 return i[0] # unpack() result is a 1-element tuple.
266 except struct.error, e:
267 raise message.DecodeError(e)
268
269 def ReadLittleEndian64(self):
270 try:
271 i = struct.unpack(wire_format.FORMAT_UINT64_LITTLE_ENDIAN,
272 self._buffer[self._pos : self._pos + 8])
273 self._pos += 8
274 return i[0] # unpack() result is a 1-element tuple.
275 except struct.error, e:
276 raise message.DecodeError(e)
277
278 def ReadVarint32(self):
279 i = self.ReadVarint64()
280 if not wire_format.INT32_MIN <= i <= wire_format.INT32_MAX:
281 raise message.DecodeError('Value out of range for int32: %d' % i)
282 return int(i)
283
284 def ReadVarUInt32(self):
285 i = self.ReadVarUInt64()
286 if i > wire_format.UINT32_MAX:
287 raise message.DecodeError('Value out of range for uint32: %d' % i)
288 return i
289
290 def ReadVarint64(self):
291 i = self.ReadVarUInt64()
292 if i > wire_format.INT64_MAX:
293 i -= (1 << 64)
294 return i
295
296 def ReadVarUInt64(self):
297 i = self._ReadVarintHelper()
298 if not 0 <= i <= wire_format.UINT64_MAX:
299 raise message.DecodeError('Value out of range for uint64: %d' % i)
300 return i
301
302 def _ReadVarintHelper(self):
303 result = 0
304 shift = 0
305 while 1:
306 if shift >= 64:
307 raise message.DecodeError('Too many bytes when decoding varint.')
308 try:
309 b = self._buffer[self._pos]
310 except IndexError:
311 raise message.DecodeError('Truncated varint.')
312 self._pos += 1
313 result |= ((b & 0x7f) << shift)
314 shift += 7
315 if not (b & 0x80):
316 return result
317
318try:
319 buffer("")
320 InputStream = InputStreamBuffer
321except NotImplementedError:
322 # Google App Engine: dev_appserver.py
323 InputStream = InputStreamArray
324except RuntimeError:
325 # Google App Engine: production
326 InputStream = InputStreamArray
diff --git a/froofle/protobuf/internal/message_listener.py b/froofle/protobuf/internal/message_listener.py
new file mode 100644
index 00000000..43978952
--- /dev/null
+++ b/froofle/protobuf/internal/message_listener.py
@@ -0,0 +1,69 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Defines a listener interface for observing certain
32state transitions on Message objects.
33
34Also defines a null implementation of this interface.
35"""
36
37__author__ = 'robinson@google.com (Will Robinson)'
38
39
40class MessageListener(object):
41
42 """Listens for transitions to nonempty and for invalidations of cached
43 byte sizes. Meant to be registered via Message._SetListener().
44 """
45
46 def TransitionToNonempty(self):
47 """Called the *first* time that this message becomes nonempty.
48 Implementations are free (but not required) to call this method multiple
49 times after the message has become nonempty.
50 """
51 raise NotImplementedError
52
53 def ByteSizeDirty(self):
54 """Called *every* time the cached byte size value
55 for this object is invalidated (transitions from being
56 "clean" to "dirty").
57 """
58 raise NotImplementedError
59
60
61class NullMessageListener(object):
62
63 """No-op MessageListener implementation."""
64
65 def TransitionToNonempty(self):
66 pass
67
68 def ByteSizeDirty(self):
69 pass
diff --git a/froofle/protobuf/internal/output_stream.py b/froofle/protobuf/internal/output_stream.py
new file mode 100644
index 00000000..f62cd1c4
--- /dev/null
+++ b/froofle/protobuf/internal/output_stream.py
@@ -0,0 +1,125 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""OutputStream is the primitive interface for sticking bits on the wire.
32
33All protocol buffer serialization can be expressed in terms of
34the OutputStream primitives provided here.
35"""
36
37__author__ = 'robinson@google.com (Will Robinson)'
38
39import array
40import struct
41from froofle.protobuf import message
42from froofle.protobuf.internal import wire_format
43
44
45
46# Note that much of this code is ported from //net/proto/ProtocolBuffer, and
47# that the interface is strongly inspired by CodedOutputStream from the C++
48# proto2 implementation.
49
50
51class OutputStream(object):
52
53 """Contains all logic for writing bits, and ToString() to get the result."""
54
55 def __init__(self):
56 self._buffer = array.array('B')
57
58 def AppendRawBytes(self, raw_bytes):
59 """Appends raw_bytes to our internal buffer."""
60 self._buffer.fromstring(raw_bytes)
61
62 def AppendLittleEndian32(self, unsigned_value):
63 """Appends an unsigned 32-bit integer to the internal buffer,
64 in little-endian byte order.
65 """
66 if not 0 <= unsigned_value <= wire_format.UINT32_MAX:
67 raise message.EncodeError(
68 'Unsigned 32-bit out of range: %d' % unsigned_value)
69 self._buffer.fromstring(struct.pack(
70 wire_format.FORMAT_UINT32_LITTLE_ENDIAN, unsigned_value))
71
72 def AppendLittleEndian64(self, unsigned_value):
73 """Appends an unsigned 64-bit integer to the internal buffer,
74 in little-endian byte order.
75 """
76 if not 0 <= unsigned_value <= wire_format.UINT64_MAX:
77 raise message.EncodeError(
78 'Unsigned 64-bit out of range: %d' % unsigned_value)
79 self._buffer.fromstring(struct.pack(
80 wire_format.FORMAT_UINT64_LITTLE_ENDIAN, unsigned_value))
81
82 def AppendVarint32(self, value):
83 """Appends a signed 32-bit integer to the internal buffer,
84 encoded as a varint. (Note that a negative varint32 will
85 always require 10 bytes of space.)
86 """
87 if not wire_format.INT32_MIN <= value <= wire_format.INT32_MAX:
88 raise message.EncodeError('Value out of range: %d' % value)
89 self.AppendVarint64(value)
90
91 def AppendVarUInt32(self, value):
92 """Appends an unsigned 32-bit integer to the internal buffer,
93 encoded as a varint.
94 """
95 if not 0 <= value <= wire_format.UINT32_MAX:
96 raise message.EncodeError('Value out of range: %d' % value)
97 self.AppendVarUInt64(value)
98
99 def AppendVarint64(self, value):
100 """Appends a signed 64-bit integer to the internal buffer,
101 encoded as a varint.
102 """
103 if not wire_format.INT64_MIN <= value <= wire_format.INT64_MAX:
104 raise message.EncodeError('Value out of range: %d' % value)
105 if value < 0:
106 value += (1 << 64)
107 self.AppendVarUInt64(value)
108
109 def AppendVarUInt64(self, unsigned_value):
110 """Appends an unsigned 64-bit integer to the internal buffer,
111 encoded as a varint.
112 """
113 if not 0 <= unsigned_value <= wire_format.UINT64_MAX:
114 raise message.EncodeError('Value out of range: %d' % unsigned_value)
115 while True:
116 bits = unsigned_value & 0x7f
117 unsigned_value >>= 7
118 if not unsigned_value:
119 self._buffer.append(bits)
120 break
121 self._buffer.append(0x80|bits)
122
123 def ToString(self):
124 """Returns a string containing the bytes in our internal buffer."""
125 return self._buffer.tostring()
diff --git a/froofle/protobuf/internal/type_checkers.py b/froofle/protobuf/internal/type_checkers.py
new file mode 100644
index 00000000..aaf7a844
--- /dev/null
+++ b/froofle/protobuf/internal/type_checkers.py
@@ -0,0 +1,268 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Provides type checking routines.
32
33This module defines type checking utilities in the forms of dictionaries:
34
35VALUE_CHECKERS: A dictionary of field types and a value validation object.
36TYPE_TO_BYTE_SIZE_FN: A dictionary with field types and a size computing
37 function.
38TYPE_TO_SERIALIZE_METHOD: A dictionary with field types and serialization
39 function.
40FIELD_TYPE_TO_WIRE_TYPE: A dictionary with field typed and their
41 coresponding wire types.
42TYPE_TO_DESERIALIZE_METHOD: A dictionary with field types and deserialization
43 function.
44"""
45
46__author__ = 'robinson@google.com (Will Robinson)'
47
48from froofle.protobuf.internal import decoder
49from froofle.protobuf.internal import encoder
50from froofle.protobuf.internal import wire_format
51from froofle.protobuf import descriptor
52
53_FieldDescriptor = descriptor.FieldDescriptor
54
55
56def GetTypeChecker(cpp_type, field_type):
57 """Returns a type checker for a message field of the specified types.
58
59 Args:
60 cpp_type: C++ type of the field (see descriptor.py).
61 field_type: Protocol message field type (see descriptor.py).
62
63 Returns:
64 An instance of TypeChecker which can be used to verify the types
65 of values assigned to a field of the specified type.
66 """
67 if (cpp_type == _FieldDescriptor.CPPTYPE_STRING and
68 field_type == _FieldDescriptor.TYPE_STRING):
69 return UnicodeValueChecker()
70 return _VALUE_CHECKERS[cpp_type]
71
72
73# None of the typecheckers below make any attempt to guard against people
74# subclassing builtin types and doing weird things. We're not trying to
75# protect against malicious clients here, just people accidentally shooting
76# themselves in the foot in obvious ways.
77
78class TypeChecker(object):
79
80 """Type checker used to catch type errors as early as possible
81 when the client is setting scalar fields in protocol messages.
82 """
83
84 def __init__(self, *acceptable_types):
85 self._acceptable_types = acceptable_types
86
87 def CheckValue(self, proposed_value):
88 if not isinstance(proposed_value, self._acceptable_types):
89 message = ('%.1024r has type %s, but expected one of: %s' %
90 (proposed_value, type(proposed_value), self._acceptable_types))
91 raise TypeError(message)
92
93
94# IntValueChecker and its subclasses perform integer type-checks
95# and bounds-checks.
96class IntValueChecker(object):
97
98 """Checker used for integer fields. Performs type-check and range check."""
99
100 def CheckValue(self, proposed_value):
101 if not isinstance(proposed_value, (int, long)):
102 message = ('%.1024r has type %s, but expected one of: %s' %
103 (proposed_value, type(proposed_value), (int, long)))
104 raise TypeError(message)
105 if not self._MIN <= proposed_value <= self._MAX:
106 raise ValueError('Value out of range: %d' % proposed_value)
107
108
109class UnicodeValueChecker(object):
110
111 """Checker used for string fields."""
112
113 def CheckValue(self, proposed_value):
114 if not isinstance(proposed_value, (str, unicode)):
115 message = ('%.1024r has type %s, but expected one of: %s' %
116 (proposed_value, type(proposed_value), (str, unicode)))
117 raise TypeError(message)
118
119 # If the value is of type 'str' make sure that it is in 7-bit ASCII
120 # encoding.
121 if isinstance(proposed_value, str):
122 try:
123 unicode(proposed_value, 'ascii')
124 except UnicodeDecodeError:
125 raise ValueError('%.1024r isn\'t in 7-bit ASCII encoding.'
126 % (proposed_value))
127
128
129class Int32ValueChecker(IntValueChecker):
130 # We're sure to use ints instead of longs here since comparison may be more
131 # efficient.
132 _MIN = -2147483648
133 _MAX = 2147483647
134
135
136class Uint32ValueChecker(IntValueChecker):
137 _MIN = 0
138 _MAX = (1 << 32) - 1
139
140
141class Int64ValueChecker(IntValueChecker):
142 _MIN = -(1 << 63)
143 _MAX = (1 << 63) - 1
144
145
146class Uint64ValueChecker(IntValueChecker):
147 _MIN = 0
148 _MAX = (1 << 64) - 1
149
150
151# Type-checkers for all scalar CPPTYPEs.
152_VALUE_CHECKERS = {
153 _FieldDescriptor.CPPTYPE_INT32: Int32ValueChecker(),
154 _FieldDescriptor.CPPTYPE_INT64: Int64ValueChecker(),
155 _FieldDescriptor.CPPTYPE_UINT32: Uint32ValueChecker(),
156 _FieldDescriptor.CPPTYPE_UINT64: Uint64ValueChecker(),
157 _FieldDescriptor.CPPTYPE_DOUBLE: TypeChecker(
158 float, int, long),
159 _FieldDescriptor.CPPTYPE_FLOAT: TypeChecker(
160 float, int, long),
161 _FieldDescriptor.CPPTYPE_BOOL: TypeChecker(bool, int),
162 _FieldDescriptor.CPPTYPE_ENUM: Int32ValueChecker(),
163 _FieldDescriptor.CPPTYPE_STRING: TypeChecker(str),
164 }
165
166
167# Map from field type to a function F, such that F(field_num, value)
168# gives the total byte size for a value of the given type. This
169# byte size includes tag information and any other additional space
170# associated with serializing "value".
171TYPE_TO_BYTE_SIZE_FN = {
172 _FieldDescriptor.TYPE_DOUBLE: wire_format.DoubleByteSize,
173 _FieldDescriptor.TYPE_FLOAT: wire_format.FloatByteSize,
174 _FieldDescriptor.TYPE_INT64: wire_format.Int64ByteSize,
175 _FieldDescriptor.TYPE_UINT64: wire_format.UInt64ByteSize,
176 _FieldDescriptor.TYPE_INT32: wire_format.Int32ByteSize,
177 _FieldDescriptor.TYPE_FIXED64: wire_format.Fixed64ByteSize,
178 _FieldDescriptor.TYPE_FIXED32: wire_format.Fixed32ByteSize,
179 _FieldDescriptor.TYPE_BOOL: wire_format.BoolByteSize,
180 _FieldDescriptor.TYPE_STRING: wire_format.StringByteSize,
181 _FieldDescriptor.TYPE_GROUP: wire_format.GroupByteSize,
182 _FieldDescriptor.TYPE_MESSAGE: wire_format.MessageByteSize,
183 _FieldDescriptor.TYPE_BYTES: wire_format.BytesByteSize,
184 _FieldDescriptor.TYPE_UINT32: wire_format.UInt32ByteSize,
185 _FieldDescriptor.TYPE_ENUM: wire_format.EnumByteSize,
186 _FieldDescriptor.TYPE_SFIXED32: wire_format.SFixed32ByteSize,
187 _FieldDescriptor.TYPE_SFIXED64: wire_format.SFixed64ByteSize,
188 _FieldDescriptor.TYPE_SINT32: wire_format.SInt32ByteSize,
189 _FieldDescriptor.TYPE_SINT64: wire_format.SInt64ByteSize
190 }
191
192
193# Maps from field type to an unbound Encoder method F, such that
194# F(encoder, field_number, value) will append the serialization
195# of a value of this type to the encoder.
196_Encoder = encoder.Encoder
197TYPE_TO_SERIALIZE_METHOD = {
198 _FieldDescriptor.TYPE_DOUBLE: _Encoder.AppendDouble,
199 _FieldDescriptor.TYPE_FLOAT: _Encoder.AppendFloat,
200 _FieldDescriptor.TYPE_INT64: _Encoder.AppendInt64,
201 _FieldDescriptor.TYPE_UINT64: _Encoder.AppendUInt64,
202 _FieldDescriptor.TYPE_INT32: _Encoder.AppendInt32,
203 _FieldDescriptor.TYPE_FIXED64: _Encoder.AppendFixed64,
204 _FieldDescriptor.TYPE_FIXED32: _Encoder.AppendFixed32,
205 _FieldDescriptor.TYPE_BOOL: _Encoder.AppendBool,
206 _FieldDescriptor.TYPE_STRING: _Encoder.AppendString,
207 _FieldDescriptor.TYPE_GROUP: _Encoder.AppendGroup,
208 _FieldDescriptor.TYPE_MESSAGE: _Encoder.AppendMessage,
209 _FieldDescriptor.TYPE_BYTES: _Encoder.AppendBytes,
210 _FieldDescriptor.TYPE_UINT32: _Encoder.AppendUInt32,
211 _FieldDescriptor.TYPE_ENUM: _Encoder.AppendEnum,
212 _FieldDescriptor.TYPE_SFIXED32: _Encoder.AppendSFixed32,
213 _FieldDescriptor.TYPE_SFIXED64: _Encoder.AppendSFixed64,
214 _FieldDescriptor.TYPE_SINT32: _Encoder.AppendSInt32,
215 _FieldDescriptor.TYPE_SINT64: _Encoder.AppendSInt64,
216 }
217
218
219# Maps from field type to expected wiretype.
220FIELD_TYPE_TO_WIRE_TYPE = {
221 _FieldDescriptor.TYPE_DOUBLE: wire_format.WIRETYPE_FIXED64,
222 _FieldDescriptor.TYPE_FLOAT: wire_format.WIRETYPE_FIXED32,
223 _FieldDescriptor.TYPE_INT64: wire_format.WIRETYPE_VARINT,
224 _FieldDescriptor.TYPE_UINT64: wire_format.WIRETYPE_VARINT,
225 _FieldDescriptor.TYPE_INT32: wire_format.WIRETYPE_VARINT,
226 _FieldDescriptor.TYPE_FIXED64: wire_format.WIRETYPE_FIXED64,
227 _FieldDescriptor.TYPE_FIXED32: wire_format.WIRETYPE_FIXED32,
228 _FieldDescriptor.TYPE_BOOL: wire_format.WIRETYPE_VARINT,
229 _FieldDescriptor.TYPE_STRING:
230 wire_format.WIRETYPE_LENGTH_DELIMITED,
231 _FieldDescriptor.TYPE_GROUP: wire_format.WIRETYPE_START_GROUP,
232 _FieldDescriptor.TYPE_MESSAGE:
233 wire_format.WIRETYPE_LENGTH_DELIMITED,
234 _FieldDescriptor.TYPE_BYTES:
235 wire_format.WIRETYPE_LENGTH_DELIMITED,
236 _FieldDescriptor.TYPE_UINT32: wire_format.WIRETYPE_VARINT,
237 _FieldDescriptor.TYPE_ENUM: wire_format.WIRETYPE_VARINT,
238 _FieldDescriptor.TYPE_SFIXED32: wire_format.WIRETYPE_FIXED32,
239 _FieldDescriptor.TYPE_SFIXED64: wire_format.WIRETYPE_FIXED64,
240 _FieldDescriptor.TYPE_SINT32: wire_format.WIRETYPE_VARINT,
241 _FieldDescriptor.TYPE_SINT64: wire_format.WIRETYPE_VARINT,
242 }
243
244
245# Maps from field type to an unbound Decoder method F,
246# such that F(decoder) will read a field of the requested type.
247#
248# Note that Message and Group are intentionally missing here.
249# They're handled by _RecursivelyMerge().
250_Decoder = decoder.Decoder
251TYPE_TO_DESERIALIZE_METHOD = {
252 _FieldDescriptor.TYPE_DOUBLE: _Decoder.ReadDouble,
253 _FieldDescriptor.TYPE_FLOAT: _Decoder.ReadFloat,
254 _FieldDescriptor.TYPE_INT64: _Decoder.ReadInt64,
255 _FieldDescriptor.TYPE_UINT64: _Decoder.ReadUInt64,
256 _FieldDescriptor.TYPE_INT32: _Decoder.ReadInt32,
257 _FieldDescriptor.TYPE_FIXED64: _Decoder.ReadFixed64,
258 _FieldDescriptor.TYPE_FIXED32: _Decoder.ReadFixed32,
259 _FieldDescriptor.TYPE_BOOL: _Decoder.ReadBool,
260 _FieldDescriptor.TYPE_STRING: _Decoder.ReadString,
261 _FieldDescriptor.TYPE_BYTES: _Decoder.ReadBytes,
262 _FieldDescriptor.TYPE_UINT32: _Decoder.ReadUInt32,
263 _FieldDescriptor.TYPE_ENUM: _Decoder.ReadEnum,
264 _FieldDescriptor.TYPE_SFIXED32: _Decoder.ReadSFixed32,
265 _FieldDescriptor.TYPE_SFIXED64: _Decoder.ReadSFixed64,
266 _FieldDescriptor.TYPE_SINT32: _Decoder.ReadSInt32,
267 _FieldDescriptor.TYPE_SINT64: _Decoder.ReadSInt64,
268 }
diff --git a/froofle/protobuf/internal/wire_format.py b/froofle/protobuf/internal/wire_format.py
new file mode 100644
index 00000000..4d823c8d
--- /dev/null
+++ b/froofle/protobuf/internal/wire_format.py
@@ -0,0 +1,236 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Constants and static functions to support protocol buffer wire format."""
32
33__author__ = 'robinson@google.com (Will Robinson)'
34
35import struct
36from froofle.protobuf import message
37
38
39TAG_TYPE_BITS = 3 # Number of bits used to hold type info in a proto tag.
40_TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1 # 0x7
41
42# These numbers identify the wire type of a protocol buffer value.
43# We use the least-significant TAG_TYPE_BITS bits of the varint-encoded
44# tag-and-type to store one of these WIRETYPE_* constants.
45# These values must match WireType enum in //net/proto2/public/wire_format.h.
46WIRETYPE_VARINT = 0
47WIRETYPE_FIXED64 = 1
48WIRETYPE_LENGTH_DELIMITED = 2
49WIRETYPE_START_GROUP = 3
50WIRETYPE_END_GROUP = 4
51WIRETYPE_FIXED32 = 5
52_WIRETYPE_MAX = 5
53
54
55# Bounds for various integer types.
56INT32_MAX = int((1 << 31) - 1)
57INT32_MIN = int(-(1 << 31))
58UINT32_MAX = (1 << 32) - 1
59
60INT64_MAX = (1 << 63) - 1
61INT64_MIN = -(1 << 63)
62UINT64_MAX = (1 << 64) - 1
63
64# "struct" format strings that will encode/decode the specified formats.
65FORMAT_UINT32_LITTLE_ENDIAN = '<I'
66FORMAT_UINT64_LITTLE_ENDIAN = '<Q'
67
68
69# We'll have to provide alternate implementations of AppendLittleEndian*() on
70# any architectures where these checks fail.
71if struct.calcsize(FORMAT_UINT32_LITTLE_ENDIAN) != 4:
72 raise AssertionError('Format "I" is not a 32-bit number.')
73if struct.calcsize(FORMAT_UINT64_LITTLE_ENDIAN) != 8:
74 raise AssertionError('Format "Q" is not a 64-bit number.')
75
76
77def PackTag(field_number, wire_type):
78 """Returns an unsigned 32-bit integer that encodes the field number and
79 wire type information in standard protocol message wire format.
80
81 Args:
82 field_number: Expected to be an integer in the range [1, 1 << 29)
83 wire_type: One of the WIRETYPE_* constants.
84 """
85 if not 0 <= wire_type <= _WIRETYPE_MAX:
86 raise message.EncodeError('Unknown wire type: %d' % wire_type)
87 return (field_number << TAG_TYPE_BITS) | wire_type
88
89
90def UnpackTag(tag):
91 """The inverse of PackTag(). Given an unsigned 32-bit number,
92 returns a (field_number, wire_type) tuple.
93 """
94 return (tag >> TAG_TYPE_BITS), (tag & _TAG_TYPE_MASK)
95
96
97def ZigZagEncode(value):
98 """ZigZag Transform: Encodes signed integers so that they can be
99 effectively used with varint encoding. See wire_format.h for
100 more details.
101 """
102 if value >= 0:
103 return value << 1
104 return (value << 1) ^ (~0)
105
106
107def ZigZagDecode(value):
108 """Inverse of ZigZagEncode()."""
109 if not value & 0x1:
110 return value >> 1
111 return (value >> 1) ^ (~0)
112
113
114
115# The *ByteSize() functions below return the number of bytes required to
116# serialize "field number + type" information and then serialize the value.
117
118
119def Int32ByteSize(field_number, int32):
120 return Int64ByteSize(field_number, int32)
121
122
123def Int64ByteSize(field_number, int64):
124 # Have to convert to uint before calling UInt64ByteSize().
125 return UInt64ByteSize(field_number, 0xffffffffffffffff & int64)
126
127
128def UInt32ByteSize(field_number, uint32):
129 return UInt64ByteSize(field_number, uint32)
130
131
132def UInt64ByteSize(field_number, uint64):
133 return _TagByteSize(field_number) + _VarUInt64ByteSizeNoTag(uint64)
134
135
136def SInt32ByteSize(field_number, int32):
137 return UInt32ByteSize(field_number, ZigZagEncode(int32))
138
139
140def SInt64ByteSize(field_number, int64):
141 return UInt64ByteSize(field_number, ZigZagEncode(int64))
142
143
144def Fixed32ByteSize(field_number, fixed32):
145 return _TagByteSize(field_number) + 4
146
147
148def Fixed64ByteSize(field_number, fixed64):
149 return _TagByteSize(field_number) + 8
150
151
152def SFixed32ByteSize(field_number, sfixed32):
153 return _TagByteSize(field_number) + 4
154
155
156def SFixed64ByteSize(field_number, sfixed64):
157 return _TagByteSize(field_number) + 8
158
159
160def FloatByteSize(field_number, flt):
161 return _TagByteSize(field_number) + 4
162
163
164def DoubleByteSize(field_number, double):
165 return _TagByteSize(field_number) + 8
166
167
168def BoolByteSize(field_number, b):
169 return _TagByteSize(field_number) + 1
170
171
172def EnumByteSize(field_number, enum):
173 return UInt32ByteSize(field_number, enum)
174
175
176def StringByteSize(field_number, string):
177 return BytesByteSize(field_number, string.encode('utf-8'))
178
179
180def BytesByteSize(field_number, b):
181 return (_TagByteSize(field_number)
182 + _VarUInt64ByteSizeNoTag(len(b))
183 + len(b))
184
185
186def GroupByteSize(field_number, message):
187 return (2 * _TagByteSize(field_number) # START and END group.
188 + message.ByteSize())
189
190
191def MessageByteSize(field_number, message):
192 return (_TagByteSize(field_number)
193 + _VarUInt64ByteSizeNoTag(message.ByteSize())
194 + message.ByteSize())
195
196
197def MessageSetItemByteSize(field_number, msg):
198 # First compute the sizes of the tags.
199 # There are 2 tags for the beginning and ending of the repeated group, that
200 # is field number 1, one with field number 2 (type_id) and one with field
201 # number 3 (message).
202 total_size = (2 * _TagByteSize(1) + _TagByteSize(2) + _TagByteSize(3))
203
204 # Add the number of bytes for type_id.
205 total_size += _VarUInt64ByteSizeNoTag(field_number)
206
207 message_size = msg.ByteSize()
208
209 # The number of bytes for encoding the length of the message.
210 total_size += _VarUInt64ByteSizeNoTag(message_size)
211
212 # The size of the message.
213 total_size += message_size
214 return total_size
215
216
217# Private helper functions for the *ByteSize() functions above.
218
219
220def _TagByteSize(field_number):
221 """Returns the bytes required to serialize a tag with this field number."""
222 # Just pass in type 0, since the type won't affect the tag+type size.
223 return _VarUInt64ByteSizeNoTag(PackTag(field_number, 0))
224
225
226def _VarUInt64ByteSizeNoTag(uint64):
227 """Returns the bytes required to serialize a single varint.
228 uint64 must be unsigned.
229 """
230 if uint64 > UINT64_MAX:
231 raise message.EncodeError('Value out of range: %d' % uint64)
232 bytes = 1
233 while uint64 > 0x7f:
234 bytes += 1
235 uint64 >>= 7
236 return bytes
diff --git a/froofle/protobuf/message.py b/froofle/protobuf/message.py
new file mode 100644
index 00000000..ed714853
--- /dev/null
+++ b/froofle/protobuf/message.py
@@ -0,0 +1,246 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31# TODO(robinson): We should just make these methods all "pure-virtual" and move
32# all implementation out, into reflection.py for now.
33
34
35"""Contains an abstract base class for protocol messages."""
36
37__author__ = 'robinson@google.com (Will Robinson)'
38
39from froofle.protobuf import text_format
40
41class Error(Exception): pass
42class DecodeError(Error): pass
43class EncodeError(Error): pass
44
45
46class Message(object):
47
48 """Abstract base class for protocol messages.
49
50 Protocol message classes are almost always generated by the protocol
51 compiler. These generated types subclass Message and implement the methods
52 shown below.
53
54 TODO(robinson): Link to an HTML document here.
55
56 TODO(robinson): Document that instances of this class will also
57 have an Extensions attribute with __getitem__ and __setitem__.
58 Again, not sure how to best convey this.
59
60 TODO(robinson): Document that the class must also have a static
61 RegisterExtension(extension_field) method.
62 Not sure how to best express at this point.
63 """
64
65 # TODO(robinson): Document these fields and methods.
66
67 __slots__ = []
68
69 DESCRIPTOR = None
70
71 def __eq__(self, other_msg):
72 raise NotImplementedError
73
74 def __ne__(self, other_msg):
75 # Can't just say self != other_msg, since that would infinitely recurse. :)
76 return not self == other_msg
77
78 def __str__(self):
79 return text_format.MessageToString(self)
80
81 def MergeFrom(self, other_msg):
82 """Merges the contents of the specified message into current message.
83
84 This method merges the contents of the specified message into the current
85 message. Singular fields that are set in the specified message overwrite
86 the corresponding fields in the current message. Repeated fields are
87 appended. Singular sub-messages and groups are recursively merged.
88
89 Args:
90 other_msg: Message to merge into the current message.
91 """
92 raise NotImplementedError
93
94 def CopyFrom(self, other_msg):
95 """Copies the content of the specified message into the current message.
96
97 The method clears the current message and then merges the specified
98 message using MergeFrom.
99
100 Args:
101 other_msg: Message to copy into the current one.
102 """
103 if self == other_msg:
104 return
105 self.Clear()
106 self.MergeFrom(other_msg)
107
108 def Clear(self):
109 """Clears all data that was set in the message."""
110 raise NotImplementedError
111
112 def IsInitialized(self):
113 """Checks if the message is initialized.
114
115 Returns:
116 The method returns True if the message is initialized (i.e. all of its
117 required fields are set).
118 """
119 raise NotImplementedError
120
121 # TODO(robinson): MergeFromString() should probably return None and be
122 # implemented in terms of a helper that returns the # of bytes read. Our
123 # deserialization routines would use the helper when recursively
124 # deserializing, but the end user would almost always just want the no-return
125 # MergeFromString().
126
127 def MergeFromString(self, serialized):
128 """Merges serialized protocol buffer data into this message.
129
130 When we find a field in |serialized| that is already present
131 in this message:
132 - If it's a "repeated" field, we append to the end of our list.
133 - Else, if it's a scalar, we overwrite our field.
134 - Else, (it's a nonrepeated composite), we recursively merge
135 into the existing composite.
136
137 TODO(robinson): Document handling of unknown fields.
138
139 Args:
140 serialized: Any object that allows us to call buffer(serialized)
141 to access a string of bytes using the buffer interface.
142
143 TODO(robinson): When we switch to a helper, this will return None.
144
145 Returns:
146 The number of bytes read from |serialized|.
147 For non-group messages, this will always be len(serialized),
148 but for messages which are actually groups, this will
149 generally be less than len(serialized), since we must
150 stop when we reach an END_GROUP tag. Note that if
151 we *do* stop because of an END_GROUP tag, the number
152 of bytes returned does not include the bytes
153 for the END_GROUP tag information.
154 """
155 raise NotImplementedError
156
157 def ParseFromString(self, serialized):
158 """Like MergeFromString(), except we clear the object first."""
159 self.Clear()
160 self.MergeFromString(serialized)
161
162 def SerializeToString(self):
163 """Serializes the protocol message to a binary string.
164
165 Returns:
166 A binary string representation of the message if all of the required
167 fields in the message are set (i.e. the message is initialized).
168
169 Raises:
170 message.EncodeError if the message isn't initialized.
171 """
172 raise NotImplementedError
173
174 def SerializePartialToString(self):
175 """Serializes the protocol message to a binary string.
176
177 This method is similar to SerializeToString but doesn't check if the
178 message is initialized.
179
180 Returns:
181 A string representation of the partial message.
182 """
183 raise NotImplementedError
184
185 # TODO(robinson): Decide whether we like these better
186 # than auto-generated has_foo() and clear_foo() methods
187 # on the instances themselves. This way is less consistent
188 # with C++, but it makes reflection-type access easier and
189 # reduces the number of magically autogenerated things.
190 #
191 # TODO(robinson): Be sure to document (and test) exactly
192 # which field names are accepted here. Are we case-sensitive?
193 # What do we do with fields that share names with Python keywords
194 # like 'lambda' and 'yield'?
195 #
196 # nnorwitz says:
197 # """
198 # Typically (in python), an underscore is appended to names that are
199 # keywords. So they would become lambda_ or yield_.
200 # """
201 def ListFields(self, field_name):
202 """Returns a list of (FieldDescriptor, value) tuples for all
203 fields in the message which are not empty. A singular field is non-empty
204 if HasField() would return true, and a repeated field is non-empty if
205 it contains at least one element. The fields are ordered by field
206 number"""
207 raise NotImplementedError
208
209 def HasField(self, field_name):
210 raise NotImplementedError
211
212 def ClearField(self, field_name):
213 raise NotImplementedError
214
215 def HasExtension(self, extension_handle):
216 raise NotImplementedError
217
218 def ClearExtension(self, extension_handle):
219 raise NotImplementedError
220
221 def ByteSize(self):
222 """Returns the serialized size of this message.
223 Recursively calls ByteSize() on all contained messages.
224 """
225 raise NotImplementedError
226
227 def _SetListener(self, message_listener):
228 """Internal method used by the protocol message implementation.
229 Clients should not call this directly.
230
231 Sets a listener that this message will call on certain state transitions.
232
233 The purpose of this method is to register back-edges from children to
234 parents at runtime, for the purpose of setting "has" bits and
235 byte-size-dirty bits in the parent and ancestor objects whenever a child or
236 descendant object is modified.
237
238 If the client wants to disconnect this Message from the object tree, she
239 explicitly sets callback to None.
240
241 If message_listener is None, unregisters any existing listener. Otherwise,
242 message_listener must implement the MessageListener interface in
243 internal/message_listener.py, and we discard any listener registered
244 via a previous _SetListener() call.
245 """
246 raise NotImplementedError
diff --git a/froofle/protobuf/reflection.py b/froofle/protobuf/reflection.py
new file mode 100644
index 00000000..e2abff04
--- /dev/null
+++ b/froofle/protobuf/reflection.py
@@ -0,0 +1,1653 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31# This code is meant to work on Python 2.4 and above only.
32#
33# TODO(robinson): Helpers for verbose, common checks like seeing if a
34# descriptor's cpp_type is CPPTYPE_MESSAGE.
35
36"""Contains a metaclass and helper functions used to create
37protocol message classes from Descriptor objects at runtime.
38
39Recall that a metaclass is the "type" of a class.
40(A class is to a metaclass what an instance is to a class.)
41
42In this case, we use the GeneratedProtocolMessageType metaclass
43to inject all the useful functionality into the classes
44output by the protocol compiler at compile-time.
45
46The upshot of all this is that the real implementation
47details for ALL pure-Python protocol buffers are *here in
48this file*.
49"""
50
51__author__ = 'robinson@google.com (Will Robinson)'
52
53import heapq
54import threading
55import weakref
56# We use "as" to avoid name collisions with variables.
57from froofle.protobuf.internal import decoder
58from froofle.protobuf.internal import encoder
59from froofle.protobuf.internal import message_listener as message_listener_mod
60from froofle.protobuf.internal import type_checkers
61from froofle.protobuf.internal import wire_format
62from froofle.protobuf import descriptor as descriptor_mod
63from froofle.protobuf import message as message_mod
64
65_FieldDescriptor = descriptor_mod.FieldDescriptor
66
67
68class GeneratedProtocolMessageType(type):
69
70 """Metaclass for protocol message classes created at runtime from Descriptors.
71
72 We add implementations for all methods described in the Message class. We
73 also create properties to allow getting/setting all fields in the protocol
74 message. Finally, we create slots to prevent users from accidentally
75 "setting" nonexistent fields in the protocol message, which then wouldn't get
76 serialized / deserialized properly.
77
78 The protocol compiler currently uses this metaclass to create protocol
79 message classes at runtime. Clients can also manually create their own
80 classes at runtime, as in this example:
81
82 mydescriptor = Descriptor(.....)
83 class MyProtoClass(Message):
84 __metaclass__ = GeneratedProtocolMessageType
85 DESCRIPTOR = mydescriptor
86 myproto_instance = MyProtoClass()
87 myproto.foo_field = 23
88 ...
89 """
90
91 # Must be consistent with the protocol-compiler code in
92 # proto2/compiler/internal/generator.*.
93 _DESCRIPTOR_KEY = 'DESCRIPTOR'
94
95 def __new__(cls, name, bases, dictionary):
96 """Custom allocation for runtime-generated class types.
97
98 We override __new__ because this is apparently the only place
99 where we can meaningfully set __slots__ on the class we're creating(?).
100 (The interplay between metaclasses and slots is not very well-documented).
101
102 Args:
103 name: Name of the class (ignored, but required by the
104 metaclass protocol).
105 bases: Base classes of the class we're constructing.
106 (Should be message.Message). We ignore this field, but
107 it's required by the metaclass protocol
108 dictionary: The class dictionary of the class we're
109 constructing. dictionary[_DESCRIPTOR_KEY] must contain
110 a Descriptor object describing this protocol message
111 type.
112
113 Returns:
114 Newly-allocated class.
115 """
116 descriptor = dictionary[GeneratedProtocolMessageType._DESCRIPTOR_KEY]
117 _AddSlots(descriptor, dictionary)
118 _AddClassAttributesForNestedExtensions(descriptor, dictionary)
119 superclass = super(GeneratedProtocolMessageType, cls)
120 return superclass.__new__(cls, name, bases, dictionary)
121
122 def __init__(cls, name, bases, dictionary):
123 """Here we perform the majority of our work on the class.
124 We add enum getters, an __init__ method, implementations
125 of all Message methods, and properties for all fields
126 in the protocol type.
127
128 Args:
129 name: Name of the class (ignored, but required by the
130 metaclass protocol).
131 bases: Base classes of the class we're constructing.
132 (Should be message.Message). We ignore this field, but
133 it's required by the metaclass protocol
134 dictionary: The class dictionary of the class we're
135 constructing. dictionary[_DESCRIPTOR_KEY] must contain
136 a Descriptor object describing this protocol message
137 type.
138 """
139 descriptor = dictionary[GeneratedProtocolMessageType._DESCRIPTOR_KEY]
140 # We act as a "friend" class of the descriptor, setting
141 # its _concrete_class attribute the first time we use a
142 # given descriptor to initialize a concrete protocol message
143 # class.
144 concrete_class_attr_name = '_concrete_class'
145 if not hasattr(descriptor, concrete_class_attr_name):
146 setattr(descriptor, concrete_class_attr_name, cls)
147 cls._known_extensions = []
148 _AddEnumValues(descriptor, cls)
149 _AddInitMethod(descriptor, cls)
150 _AddPropertiesForFields(descriptor, cls)
151 _AddStaticMethods(cls)
152 _AddMessageMethods(descriptor, cls)
153 _AddPrivateHelperMethods(cls)
154 superclass = super(GeneratedProtocolMessageType, cls)
155 superclass.__init__(cls, name, bases, dictionary)
156
157
158# Stateless helpers for GeneratedProtocolMessageType below.
159# Outside clients should not access these directly.
160#
161# I opted not to make any of these methods on the metaclass, to make it more
162# clear that I'm not really using any state there and to keep clients from
163# thinking that they have direct access to these construction helpers.
164
165
166def _PropertyName(proto_field_name):
167 """Returns the name of the public property attribute which
168 clients can use to get and (in some cases) set the value
169 of a protocol message field.
170
171 Args:
172 proto_field_name: The protocol message field name, exactly
173 as it appears (or would appear) in a .proto file.
174 """
175 # TODO(robinson): Escape Python keywords (e.g., yield), and test this support.
176 # nnorwitz makes my day by writing:
177 # """
178 # FYI. See the keyword module in the stdlib. This could be as simple as:
179 #
180 # if keyword.iskeyword(proto_field_name):
181 # return proto_field_name + "_"
182 # return proto_field_name
183 # """
184 return proto_field_name
185
186
187def _ValueFieldName(proto_field_name):
188 """Returns the name of the (internal) instance attribute which objects
189 should use to store the current value for a given protocol message field.
190
191 Args:
192 proto_field_name: The protocol message field name, exactly
193 as it appears (or would appear) in a .proto file.
194 """
195 return '_value_' + proto_field_name
196
197
198def _HasFieldName(proto_field_name):
199 """Returns the name of the (internal) instance attribute which
200 objects should use to store a boolean telling whether this field
201 is explicitly set or not.
202
203 Args:
204 proto_field_name: The protocol message field name, exactly
205 as it appears (or would appear) in a .proto file.
206 """
207 return '_has_' + proto_field_name
208
209
210def _AddSlots(message_descriptor, dictionary):
211 """Adds a __slots__ entry to dictionary, containing the names of all valid
212 attributes for this message type.
213
214 Args:
215 message_descriptor: A Descriptor instance describing this message type.
216 dictionary: Class dictionary to which we'll add a '__slots__' entry.
217 """
218 field_names = [_ValueFieldName(f.name) for f in message_descriptor.fields]
219 field_names.extend(_HasFieldName(f.name) for f in message_descriptor.fields
220 if f.label != _FieldDescriptor.LABEL_REPEATED)
221 field_names.extend(('Extensions',
222 '_cached_byte_size',
223 '_cached_byte_size_dirty',
224 '_called_transition_to_nonempty',
225 '_listener',
226 '_lock', '__weakref__'))
227 dictionary['__slots__'] = field_names
228
229
230def _AddClassAttributesForNestedExtensions(descriptor, dictionary):
231 extension_dict = descriptor.extensions_by_name
232 for extension_name, extension_field in extension_dict.iteritems():
233 assert extension_name not in dictionary
234 dictionary[extension_name] = extension_field
235
236
237def _AddEnumValues(descriptor, cls):
238 """Sets class-level attributes for all enum fields defined in this message.
239
240 Args:
241 descriptor: Descriptor object for this message type.
242 cls: Class we're constructing for this message type.
243 """
244 for enum_type in descriptor.enum_types:
245 for enum_value in enum_type.values:
246 setattr(cls, enum_value.name, enum_value.number)
247
248
249def _DefaultValueForField(message, field):
250 """Returns a default value for a field.
251
252 Args:
253 message: Message instance containing this field, or a weakref proxy
254 of same.
255 field: FieldDescriptor object for this field.
256
257 Returns: A default value for this field. May refer back to |message|
258 via a weak reference.
259 """
260 # TODO(robinson): Only the repeated fields need a reference to 'message' (so
261 # that they can set the 'has' bit on the containing Message when someone
262 # append()s a value). We could special-case this, and avoid an extra
263 # function call on __init__() and Clear() for non-repeated fields.
264
265 # TODO(robinson): Find a better place for the default value assertion in this
266 # function. No need to repeat them every time the client calls Clear('foo').
267 # (We should probably just assert these things once and as early as possible,
268 # by tightening checking in the descriptor classes.)
269 if field.label == _FieldDescriptor.LABEL_REPEATED:
270 if field.default_value != []:
271 raise ValueError('Repeated field default value not empty list: %s' % (
272 field.default_value))
273 listener = _Listener(message, None)
274 if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
275 # We can't look at _concrete_class yet since it might not have
276 # been set. (Depends on order in which we initialize the classes).
277 return _RepeatedCompositeFieldContainer(listener, field.message_type)
278 else:
279 return _RepeatedScalarFieldContainer(
280 listener, type_checkers.GetTypeChecker(field.cpp_type, field.type))
281
282 if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
283 assert field.default_value is None
284
285 return field.default_value
286
287
288def _AddInitMethod(message_descriptor, cls):
289 """Adds an __init__ method to cls."""
290 fields = message_descriptor.fields
291 def init(self):
292 self._cached_byte_size = 0
293 self._cached_byte_size_dirty = False
294 self._listener = message_listener_mod.NullMessageListener()
295 self._called_transition_to_nonempty = False
296 # TODO(robinson): We should only create a lock if we really need one
297 # in this class.
298 self._lock = threading.Lock()
299 for field in fields:
300 default_value = _DefaultValueForField(self, field)
301 python_field_name = _ValueFieldName(field.name)
302 setattr(self, python_field_name, default_value)
303 if field.label != _FieldDescriptor.LABEL_REPEATED:
304 setattr(self, _HasFieldName(field.name), False)
305 self.Extensions = _ExtensionDict(self, cls._known_extensions)
306
307 init.__module__ = None
308 init.__doc__ = None
309 cls.__init__ = init
310
311
312def _AddPropertiesForFields(descriptor, cls):
313 """Adds properties for all fields in this protocol message type."""
314 for field in descriptor.fields:
315 _AddPropertiesForField(field, cls)
316
317
318def _AddPropertiesForField(field, cls):
319 """Adds a public property for a protocol message field.
320 Clients can use this property to get and (in the case
321 of non-repeated scalar fields) directly set the value
322 of a protocol message field.
323
324 Args:
325 field: A FieldDescriptor for this field.
326 cls: The class we're constructing.
327 """
328 # Catch it if we add other types that we should
329 # handle specially here.
330 assert _FieldDescriptor.MAX_CPPTYPE == 10
331
332 if field.label == _FieldDescriptor.LABEL_REPEATED:
333 _AddPropertiesForRepeatedField(field, cls)
334 elif field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
335 _AddPropertiesForNonRepeatedCompositeField(field, cls)
336 else:
337 _AddPropertiesForNonRepeatedScalarField(field, cls)
338
339
340def _AddPropertiesForRepeatedField(field, cls):
341 """Adds a public property for a "repeated" protocol message field. Clients
342 can use this property to get the value of the field, which will be either a
343 _RepeatedScalarFieldContainer or _RepeatedCompositeFieldContainer (see
344 below).
345
346 Note that when clients add values to these containers, we perform
347 type-checking in the case of repeated scalar fields, and we also set any
348 necessary "has" bits as a side-effect.
349
350 Args:
351 field: A FieldDescriptor for this field.
352 cls: The class we're constructing.
353 """
354 proto_field_name = field.name
355 python_field_name = _ValueFieldName(proto_field_name)
356 property_name = _PropertyName(proto_field_name)
357
358 def getter(self):
359 return getattr(self, python_field_name)
360 getter.__module__ = None
361 getter.__doc__ = 'Getter for %s.' % proto_field_name
362
363 # We define a setter just so we can throw an exception with a more
364 # helpful error message.
365 def setter(self, new_value):
366 raise AttributeError('Assignment not allowed to repeated field '
367 '"%s" in protocol message object.' % proto_field_name)
368
369 doc = 'Magic attribute generated for "%s" proto field.' % proto_field_name
370 setattr(cls, property_name, property(getter, setter, doc=doc))
371
372
373def _AddPropertiesForNonRepeatedScalarField(field, cls):
374 """Adds a public property for a nonrepeated, scalar protocol message field.
375 Clients can use this property to get and directly set the value of the field.
376 Note that when the client sets the value of a field by using this property,
377 all necessary "has" bits are set as a side-effect, and we also perform
378 type-checking.
379
380 Args:
381 field: A FieldDescriptor for this field.
382 cls: The class we're constructing.
383 """
384 proto_field_name = field.name
385 python_field_name = _ValueFieldName(proto_field_name)
386 has_field_name = _HasFieldName(proto_field_name)
387 property_name = _PropertyName(proto_field_name)
388 type_checker = type_checkers.GetTypeChecker(field.cpp_type, field.type)
389
390 def getter(self):
391 return getattr(self, python_field_name)
392 getter.__module__ = None
393 getter.__doc__ = 'Getter for %s.' % proto_field_name
394 def setter(self, new_value):
395 type_checker.CheckValue(new_value)
396 setattr(self, has_field_name, True)
397 self._MarkByteSizeDirty()
398 self._MaybeCallTransitionToNonemptyCallback()
399 setattr(self, python_field_name, new_value)
400 setter.__module__ = None
401 setter.__doc__ = 'Setter for %s.' % proto_field_name
402
403 # Add a property to encapsulate the getter/setter.
404 doc = 'Magic attribute generated for "%s" proto field.' % proto_field_name
405 setattr(cls, property_name, property(getter, setter, doc=doc))
406
407
408def _AddPropertiesForNonRepeatedCompositeField(field, cls):
409 """Adds a public property for a nonrepeated, composite protocol message field.
410 A composite field is a "group" or "message" field.
411
412 Clients can use this property to get the value of the field, but cannot
413 assign to the property directly.
414
415 Args:
416 field: A FieldDescriptor for this field.
417 cls: The class we're constructing.
418 """
419 # TODO(robinson): Remove duplication with similar method
420 # for non-repeated scalars.
421 proto_field_name = field.name
422 python_field_name = _ValueFieldName(proto_field_name)
423 has_field_name = _HasFieldName(proto_field_name)
424 property_name = _PropertyName(proto_field_name)
425 message_type = field.message_type
426
427 def getter(self):
428 # TODO(robinson): Appropriately scary note about double-checked locking.
429 field_value = getattr(self, python_field_name)
430 if field_value is None:
431 self._lock.acquire()
432 try:
433 field_value = getattr(self, python_field_name)
434 if field_value is None:
435 field_class = message_type._concrete_class
436 field_value = field_class()
437 field_value._SetListener(_Listener(self, has_field_name))
438 setattr(self, python_field_name, field_value)
439 finally:
440 self._lock.release()
441 return field_value
442 getter.__module__ = None
443 getter.__doc__ = 'Getter for %s.' % proto_field_name
444
445 # We define a setter just so we can throw an exception with a more
446 # helpful error message.
447 def setter(self, new_value):
448 raise AttributeError('Assignment not allowed to composite field '
449 '"%s" in protocol message object.' % proto_field_name)
450
451 # Add a property to encapsulate the getter.
452 doc = 'Magic attribute generated for "%s" proto field.' % proto_field_name
453 setattr(cls, property_name, property(getter, setter, doc=doc))
454
455
456def _AddStaticMethods(cls):
457 # TODO(robinson): This probably needs to be thread-safe(?)
458 def RegisterExtension(extension_handle):
459 extension_handle.containing_type = cls.DESCRIPTOR
460 cls._known_extensions.append(extension_handle)
461 cls.RegisterExtension = staticmethod(RegisterExtension)
462
463
464def _AddListFieldsMethod(message_descriptor, cls):
465 """Helper for _AddMessageMethods()."""
466
467 # Ensure that we always list in ascending field-number order.
468 # For non-extension fields, we can do the sort once, here, at import-time.
469 # For extensions, we sort on each ListFields() call, though
470 # we could do better if we have to.
471 fields = sorted(message_descriptor.fields, key=lambda f: f.number)
472 has_field_names = (_HasFieldName(f.name) for f in fields)
473 value_field_names = (_ValueFieldName(f.name) for f in fields)
474 triplets = zip(has_field_names, value_field_names, fields)
475
476 def ListFields(self):
477 # We need to list all extension and non-extension fields
478 # together, in sorted order by field number.
479
480 # Step 0: Get an iterator over all "set" non-extension fields,
481 # sorted by field number.
482 # This iterator yields (field_number, field_descriptor, value) tuples.
483 def SortedSetFieldsIter():
484 # Note that triplets is already sorted by field number.
485 for has_field_name, value_field_name, field_descriptor in triplets:
486 if field_descriptor.label == _FieldDescriptor.LABEL_REPEATED:
487 value = getattr(self, _ValueFieldName(field_descriptor.name))
488 if len(value) > 0:
489 yield (field_descriptor.number, field_descriptor, value)
490 elif getattr(self, _HasFieldName(field_descriptor.name)):
491 value = getattr(self, _ValueFieldName(field_descriptor.name))
492 yield (field_descriptor.number, field_descriptor, value)
493 sorted_fields = SortedSetFieldsIter()
494
495 # Step 1: Get an iterator over all "set" extension fields,
496 # sorted by field number.
497 # This iterator ALSO yields (field_number, field_descriptor, value) tuples.
498 # TODO(robinson): It's not necessary to repeat this with each
499 # serialization call. We can do better.
500 sorted_extension_fields = sorted(
501 [(f.number, f, v) for f, v in self.Extensions._ListSetExtensions()])
502
503 # Step 2: Create a composite iterator that merges the extension-
504 # and non-extension fields, and that still yields fields in
505 # sorted order.
506 all_set_fields = _ImergeSorted(sorted_fields, sorted_extension_fields)
507
508 # Step 3: Strip off the field numbers and return.
509 return [field[1:] for field in all_set_fields]
510
511 cls.ListFields = ListFields
512
513def _AddHasFieldMethod(cls):
514 """Helper for _AddMessageMethods()."""
515 def HasField(self, field_name):
516 try:
517 return getattr(self, _HasFieldName(field_name))
518 except AttributeError:
519 raise ValueError('Protocol message has no "%s" field.' % field_name)
520 cls.HasField = HasField
521
522
523def _AddClearFieldMethod(cls):
524 """Helper for _AddMessageMethods()."""
525 def ClearField(self, field_name):
526 try:
527 field = self.DESCRIPTOR.fields_by_name[field_name]
528 except KeyError:
529 raise ValueError('Protocol message has no "%s" field.' % field_name)
530 proto_field_name = field.name
531 python_field_name = _ValueFieldName(proto_field_name)
532 has_field_name = _HasFieldName(proto_field_name)
533 default_value = _DefaultValueForField(self, field)
534 if field.label == _FieldDescriptor.LABEL_REPEATED:
535 self._MarkByteSizeDirty()
536 else:
537 if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
538 old_field_value = getattr(self, python_field_name)
539 if old_field_value is not None:
540 # Snip the old object out of the object tree.
541 old_field_value._SetListener(None)
542 if getattr(self, has_field_name):
543 setattr(self, has_field_name, False)
544 # Set dirty bit on ourself and parents only if
545 # we're actually changing state.
546 self._MarkByteSizeDirty()
547 setattr(self, python_field_name, default_value)
548 cls.ClearField = ClearField
549
550
551def _AddClearExtensionMethod(cls):
552 """Helper for _AddMessageMethods()."""
553 def ClearExtension(self, extension_handle):
554 self.Extensions._ClearExtension(extension_handle)
555 cls.ClearExtension = ClearExtension
556
557
558def _AddClearMethod(cls):
559 """Helper for _AddMessageMethods()."""
560 def Clear(self):
561 # Clear fields.
562 fields = self.DESCRIPTOR.fields
563 for field in fields:
564 self.ClearField(field.name)
565 # Clear extensions.
566 extensions = self.Extensions._ListSetExtensions()
567 for extension in extensions:
568 self.ClearExtension(extension[0])
569 cls.Clear = Clear
570
571
572def _AddHasExtensionMethod(cls):
573 """Helper for _AddMessageMethods()."""
574 def HasExtension(self, extension_handle):
575 return self.Extensions._HasExtension(extension_handle)
576 cls.HasExtension = HasExtension
577
578
579def _AddEqualsMethod(message_descriptor, cls):
580 """Helper for _AddMessageMethods()."""
581 def __eq__(self, other):
582 if self is other:
583 return True
584
585 # Compare all fields contained directly in this message.
586 for field_descriptor in message_descriptor.fields:
587 label = field_descriptor.label
588 property_name = _PropertyName(field_descriptor.name)
589 # Non-repeated field equality requires matching "has" bits as well
590 # as having an equal value.
591 if label != _FieldDescriptor.LABEL_REPEATED:
592 self_has = self.HasField(property_name)
593 other_has = other.HasField(property_name)
594 if self_has != other_has:
595 return False
596 if not self_has:
597 # If the "has" bit for this field is False, we must stop here.
598 # Otherwise we will recurse forever on recursively-defined protos.
599 continue
600 if getattr(self, property_name) != getattr(other, property_name):
601 return False
602
603 # Compare the extensions present in both messages.
604 return self.Extensions == other.Extensions
605 cls.__eq__ = __eq__
606
607
608def _AddSetListenerMethod(cls):
609 """Helper for _AddMessageMethods()."""
610 def SetListener(self, listener):
611 if listener is None:
612 self._listener = message_listener_mod.NullMessageListener()
613 else:
614 self._listener = listener
615 cls._SetListener = SetListener
616
617
618def _BytesForNonRepeatedElement(value, field_number, field_type):
619 """Returns the number of bytes needed to serialize a non-repeated element.
620 The returned byte count includes space for tag information and any
621 other additional space associated with serializing value.
622
623 Args:
624 value: Value we're serializing.
625 field_number: Field number of this value. (Since the field number
626 is stored as part of a varint-encoded tag, this has an impact
627 on the total bytes required to serialize the value).
628 field_type: The type of the field. One of the TYPE_* constants
629 within FieldDescriptor.
630 """
631 try:
632 fn = type_checkers.TYPE_TO_BYTE_SIZE_FN[field_type]
633 return fn(field_number, value)
634 except KeyError:
635 raise message_mod.EncodeError('Unrecognized field type: %d' % field_type)
636
637
638def _AddByteSizeMethod(message_descriptor, cls):
639 """Helper for _AddMessageMethods()."""
640
641 def BytesForField(message, field, value):
642 """Returns the number of bytes required to serialize a single field
643 in message. The field may be repeated or not, composite or not.
644
645 Args:
646 message: The Message instance containing a field of the given type.
647 field: A FieldDescriptor describing the field of interest.
648 value: The value whose byte size we're interested in.
649
650 Returns: The number of bytes required to serialize the current value
651 of "field" in "message", including space for tags and any other
652 necessary information.
653 """
654
655 if _MessageSetField(field):
656 return wire_format.MessageSetItemByteSize(field.number, value)
657
658 field_number, field_type = field.number, field.type
659
660 # Repeated fields.
661 if field.label == _FieldDescriptor.LABEL_REPEATED:
662 elements = value
663 else:
664 elements = [value]
665
666 size = sum(_BytesForNonRepeatedElement(element, field_number, field_type)
667 for element in elements)
668 return size
669
670 fields = message_descriptor.fields
671 has_field_names = (_HasFieldName(f.name) for f in fields)
672 zipped = zip(has_field_names, fields)
673
674 def ByteSize(self):
675 if not self._cached_byte_size_dirty:
676 return self._cached_byte_size
677
678 size = 0
679 # Hardcoded fields first.
680 for has_field_name, field in zipped:
681 if (field.label == _FieldDescriptor.LABEL_REPEATED
682 or getattr(self, has_field_name)):
683 value = getattr(self, _ValueFieldName(field.name))
684 size += BytesForField(self, field, value)
685 # Extensions next.
686 for field, value in self.Extensions._ListSetExtensions():
687 size += BytesForField(self, field, value)
688
689 self._cached_byte_size = size
690 self._cached_byte_size_dirty = False
691 return size
692 cls.ByteSize = ByteSize
693
694
695def _MessageSetField(field_descriptor):
696 """Checks if a field should be serialized using the message set wire format.
697
698 Args:
699 field_descriptor: Descriptor of the field.
700
701 Returns:
702 True if the field should be serialized using the message set wire format,
703 false otherwise.
704 """
705 return (field_descriptor.is_extension and
706 field_descriptor.label != _FieldDescriptor.LABEL_REPEATED and
707 field_descriptor.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
708 field_descriptor.containing_type.GetOptions().message_set_wire_format)
709
710
711def _SerializeValueToEncoder(value, field_number, field_descriptor, encoder):
712 """Appends the serialization of a single value to encoder.
713
714 Args:
715 value: Value to serialize.
716 field_number: Field number of this value.
717 field_descriptor: Descriptor of the field to serialize.
718 encoder: encoder.Encoder object to which we should serialize this value.
719 """
720 if _MessageSetField(field_descriptor):
721 encoder.AppendMessageSetItem(field_number, value)
722 return
723
724 try:
725 method = type_checkers.TYPE_TO_SERIALIZE_METHOD[field_descriptor.type]
726 method(encoder, field_number, value)
727 except KeyError:
728 raise message_mod.EncodeError('Unrecognized field type: %d' %
729 field_descriptor.type)
730
731
732def _ImergeSorted(*streams):
733 """Merges N sorted iterators into a single sorted iterator.
734 Each element in streams must be an iterable that yields
735 its elements in sorted order, and the elements contained
736 in each stream must all be comparable.
737
738 There may be repeated elements in the component streams or
739 across the streams; the repeated elements will all be repeated
740 in the merged iterator as well.
741
742 I believe that the heapq module at HEAD in the Python
743 sources has a method like this, but for now we roll our own.
744 """
745 iters = [iter(stream) for stream in streams]
746 heap = []
747 for index, it in enumerate(iters):
748 try:
749 heap.append((it.next(), index))
750 except StopIteration:
751 pass
752 heapq.heapify(heap)
753
754 while heap:
755 smallest_value, idx = heap[0]
756 yield smallest_value
757 try:
758 next_element = iters[idx].next()
759 heapq.heapreplace(heap, (next_element, idx))
760 except StopIteration:
761 heapq.heappop(heap)
762
763
764def _AddSerializeToStringMethod(message_descriptor, cls):
765 """Helper for _AddMessageMethods()."""
766
767 def SerializeToString(self):
768 # Check if the message has all of its required fields set.
769 errors = []
770 if not _InternalIsInitialized(self, errors):
771 raise message_mod.EncodeError('\n'.join(errors))
772 return self.SerializePartialToString()
773 cls.SerializeToString = SerializeToString
774
775
776def _AddSerializePartialToStringMethod(message_descriptor, cls):
777 """Helper for _AddMessageMethods()."""
778 Encoder = encoder.Encoder
779
780 def SerializePartialToString(self):
781 encoder = Encoder()
782 # We need to serialize all extension and non-extension fields
783 # together, in sorted order by field number.
784 for field_descriptor, field_value in self.ListFields():
785 if field_descriptor.label == _FieldDescriptor.LABEL_REPEATED:
786 repeated_value = field_value
787 else:
788 repeated_value = [field_value]
789 for element in repeated_value:
790 _SerializeValueToEncoder(element, field_descriptor.number,
791 field_descriptor, encoder)
792 return encoder.ToString()
793 cls.SerializePartialToString = SerializePartialToString
794
795
796def _WireTypeForFieldType(field_type):
797 """Given a field type, returns the expected wire type."""
798 try:
799 return type_checkers.FIELD_TYPE_TO_WIRE_TYPE[field_type]
800 except KeyError:
801 raise message_mod.DecodeError('Unknown field type: %d' % field_type)
802
803
804def _RecursivelyMerge(field_number, field_type, decoder, message):
805 """Decodes a message from decoder into message.
806 message is either a group or a nested message within some containing
807 protocol message. If it's a group, we use the group protocol to
808 deserialize, and if it's a nested message, we use the nested-message
809 protocol.
810
811 Args:
812 field_number: The field number of message in its enclosing protocol buffer.
813 field_type: The field type of message. Must be either TYPE_MESSAGE
814 or TYPE_GROUP.
815 decoder: Decoder to read from.
816 message: Message to deserialize into.
817 """
818 if field_type == _FieldDescriptor.TYPE_MESSAGE:
819 decoder.ReadMessageInto(message)
820 elif field_type == _FieldDescriptor.TYPE_GROUP:
821 decoder.ReadGroupInto(field_number, message)
822 else:
823 raise message_mod.DecodeError('Unexpected field type: %d' % field_type)
824
825
826def _DeserializeScalarFromDecoder(field_type, decoder):
827 """Deserializes a scalar of the requested type from decoder. field_type must
828 be a scalar (non-group, non-message) FieldDescriptor.FIELD_* constant.
829 """
830 try:
831 method = type_checkers.TYPE_TO_DESERIALIZE_METHOD[field_type]
832 return method(decoder)
833 except KeyError:
834 raise message_mod.DecodeError('Unrecognized field type: %d' % field_type)
835
836
837def _SkipField(field_number, wire_type, decoder):
838 """Skips a field with the specified wire type.
839
840 Args:
841 field_number: Tag number of the field to skip.
842 wire_type: Wire type of the field to skip.
843 decoder: Decoder used to deserialize the messsage. It must be positioned
844 just after reading the the tag and wire type of the field.
845 """
846 if wire_type == wire_format.WIRETYPE_VARINT:
847 decoder.ReadUInt64()
848 elif wire_type == wire_format.WIRETYPE_FIXED64:
849 decoder.ReadFixed64()
850 elif wire_type == wire_format.WIRETYPE_LENGTH_DELIMITED:
851 decoder.SkipBytes(decoder.ReadInt32())
852 elif wire_type == wire_format.WIRETYPE_START_GROUP:
853 _SkipGroup(field_number, decoder)
854 elif wire_type == wire_format.WIRETYPE_END_GROUP:
855 pass
856 elif wire_type == wire_format.WIRETYPE_FIXED32:
857 decoder.ReadFixed32()
858 else:
859 raise message_mod.DecodeError('Unexpected wire type: %d' % wire_type)
860
861
862def _SkipGroup(group_number, decoder):
863 """Skips a nested group from the decoder.
864
865 Args:
866 group_number: Tag number of the group to skip.
867 decoder: Decoder used to deserialize the message. It must be positioned
868 exactly at the beginning of the message that should be skipped.
869 """
870 while True:
871 field_number, wire_type = decoder.ReadFieldNumberAndWireType()
872 if (wire_type == wire_format.WIRETYPE_END_GROUP and
873 field_number == group_number):
874 return
875 _SkipField(field_number, wire_type, decoder)
876
877
878def _DeserializeMessageSetItem(message, decoder):
879 """Deserializes a message using the message set wire format.
880
881 Args:
882 message: Message to be parsed to.
883 decoder: The decoder to be used to deserialize encoded data. Note that the
884 decoder should be positioned just after reading the START_GROUP tag that
885 began the messageset item.
886 """
887 field_number, wire_type = decoder.ReadFieldNumberAndWireType()
888 if wire_type != wire_format.WIRETYPE_VARINT or field_number != 2:
889 raise message_mod.DecodeError(
890 'Incorrect message set wire format. '
891 'wire_type: %d, field_number: %d' % (wire_type, field_number))
892
893 type_id = decoder.ReadInt32()
894 field_number, wire_type = decoder.ReadFieldNumberAndWireType()
895 if wire_type != wire_format.WIRETYPE_LENGTH_DELIMITED or field_number != 3:
896 raise message_mod.DecodeError(
897 'Incorrect message set wire format. '
898 'wire_type: %d, field_number: %d' % (wire_type, field_number))
899
900 extension_dict = message.Extensions
901 extensions_by_number = extension_dict._AllExtensionsByNumber()
902 if type_id not in extensions_by_number:
903 _SkipField(field_number, wire_type, decoder)
904 return
905
906 field_descriptor = extensions_by_number[type_id]
907 value = extension_dict[field_descriptor]
908 decoder.ReadMessageInto(value)
909 # Read the END_GROUP tag.
910 field_number, wire_type = decoder.ReadFieldNumberAndWireType()
911 if wire_type != wire_format.WIRETYPE_END_GROUP or field_number != 1:
912 raise message_mod.DecodeError(
913 'Incorrect message set wire format. '
914 'wire_type: %d, field_number: %d' % (wire_type, field_number))
915
916
917def _DeserializeOneEntity(message_descriptor, message, decoder):
918 """Deserializes the next wire entity from decoder into message.
919 The next wire entity is either a scalar or a nested message,
920 and may also be an element in a repeated field (the wire encoding
921 is the same).
922
923 Args:
924 message_descriptor: A Descriptor instance describing all fields
925 in message.
926 message: The Message instance into which we're decoding our fields.
927 decoder: The Decoder we're using to deserialize encoded data.
928
929 Returns: The number of bytes read from decoder during this method.
930 """
931 initial_position = decoder.Position()
932 field_number, wire_type = decoder.ReadFieldNumberAndWireType()
933 extension_dict = message.Extensions
934 extensions_by_number = extension_dict._AllExtensionsByNumber()
935 if field_number in message_descriptor.fields_by_number:
936 # Non-extension field.
937 field_descriptor = message_descriptor.fields_by_number[field_number]
938 value = getattr(message, _PropertyName(field_descriptor.name))
939 def nonextension_setter_fn(scalar):
940 setattr(message, _PropertyName(field_descriptor.name), scalar)
941 scalar_setter_fn = nonextension_setter_fn
942 elif field_number in extensions_by_number:
943 # Extension field.
944 field_descriptor = extensions_by_number[field_number]
945 value = extension_dict[field_descriptor]
946 def extension_setter_fn(scalar):
947 extension_dict[field_descriptor] = scalar
948 scalar_setter_fn = extension_setter_fn
949 elif wire_type == wire_format.WIRETYPE_END_GROUP:
950 # We assume we're being parsed as the group that's ended.
951 return 0
952 elif (wire_type == wire_format.WIRETYPE_START_GROUP and
953 field_number == 1 and
954 message_descriptor.GetOptions().message_set_wire_format):
955 # A Message Set item.
956 _DeserializeMessageSetItem(message, decoder)
957 return decoder.Position() - initial_position
958 else:
959 _SkipField(field_number, wire_type, decoder)
960 return decoder.Position() - initial_position
961
962 # If we reach this point, we've identified the field as either
963 # hardcoded or extension, and set |field_descriptor|, |scalar_setter_fn|,
964 # and |value| appropriately. Now actually deserialize the thing.
965 #
966 # field_descriptor: Describes the field we're deserializing.
967 # value: The value currently stored in the field to deserialize.
968 # Used only if the field is composite and/or repeated.
969 # scalar_setter_fn: A function F such that F(scalar) will
970 # set a nonrepeated scalar value for this field. Used only
971 # if this field is a nonrepeated scalar.
972
973 field_number = field_descriptor.number
974 field_type = field_descriptor.type
975 expected_wire_type = _WireTypeForFieldType(field_type)
976 if wire_type != expected_wire_type:
977 # Need to fill in uninterpreted_bytes. Work for the next CL.
978 raise RuntimeError('TODO(robinson): Wiretype mismatches not handled.')
979
980 property_name = _PropertyName(field_descriptor.name)
981 label = field_descriptor.label
982 cpp_type = field_descriptor.cpp_type
983
984 # Nonrepeated scalar. Just set the field directly.
985 if (label != _FieldDescriptor.LABEL_REPEATED
986 and cpp_type != _FieldDescriptor.CPPTYPE_MESSAGE):
987 scalar_setter_fn(_DeserializeScalarFromDecoder(field_type, decoder))
988 return decoder.Position() - initial_position
989
990 # Nonrepeated composite. Recursively deserialize.
991 if label != _FieldDescriptor.LABEL_REPEATED:
992 composite = value
993 _RecursivelyMerge(field_number, field_type, decoder, composite)
994 return decoder.Position() - initial_position
995
996 # Now we know we're dealing with a repeated field of some kind.
997 element_list = value
998
999 if cpp_type != _FieldDescriptor.CPPTYPE_MESSAGE:
1000 # Repeated scalar.
1001 element_list.append(_DeserializeScalarFromDecoder(field_type, decoder))
1002 return decoder.Position() - initial_position
1003 else:
1004 # Repeated composite.
1005 composite = element_list.add()
1006 _RecursivelyMerge(field_number, field_type, decoder, composite)
1007 return decoder.Position() - initial_position
1008
1009
1010def _FieldOrExtensionValues(message, field_or_extension):
1011 """Retrieves the list of values for the specified field or extension.
1012
1013 The target field or extension can be optional, required or repeated, but it
1014 must have value(s) set. The assumption is that the target field or extension
1015 is set (e.g. _HasFieldOrExtension holds true).
1016
1017 Args:
1018 message: Message which contains the target field or extension.
1019 field_or_extension: Field or extension for which the list of values is
1020 required. Must be an instance of FieldDescriptor.
1021
1022 Returns:
1023 A list of values for the specified field or extension. This list will only
1024 contain a single element if the field is non-repeated.
1025 """
1026 if field_or_extension.is_extension:
1027 value = message.Extensions[field_or_extension]
1028 else:
1029 value = getattr(message, _ValueFieldName(field_or_extension.name))
1030 if field_or_extension.label != _FieldDescriptor.LABEL_REPEATED:
1031 return [value]
1032 else:
1033 # In this case value is a list or repeated values.
1034 return value
1035
1036
1037def _HasFieldOrExtension(message, field_or_extension):
1038 """Checks if a message has the specified field or extension set.
1039
1040 The field or extension specified can be optional, required or repeated. If
1041 it is repeated, this function returns True. Otherwise it checks the has bit
1042 of the field or extension.
1043
1044 Args:
1045 message: Message which contains the target field or extension.
1046 field_or_extension: Field or extension to check. This must be a
1047 FieldDescriptor instance.
1048
1049 Returns:
1050 True if the message has a value set for the specified field or extension,
1051 or if the field or extension is repeated.
1052 """
1053 if field_or_extension.label == _FieldDescriptor.LABEL_REPEATED:
1054 return True
1055 if field_or_extension.is_extension:
1056 return message.HasExtension(field_or_extension)
1057 else:
1058 return message.HasField(field_or_extension.name)
1059
1060
1061def _IsFieldOrExtensionInitialized(message, field, errors=None):
1062 """Checks if a message field or extension is initialized.
1063
1064 Args:
1065 message: The message which contains the field or extension.
1066 field: Field or extension to check. This must be a FieldDescriptor instance.
1067 errors: Errors will be appended to it, if set to a meaningful value.
1068
1069 Returns:
1070 True if the field/extension can be considered initialized.
1071 """
1072 # If the field is required and is not set, it isn't initialized.
1073 if field.label == _FieldDescriptor.LABEL_REQUIRED:
1074 if not _HasFieldOrExtension(message, field):
1075 if errors is not None:
1076 errors.append('Required field %s is not set.' % field.full_name)
1077 return False
1078
1079 # If the field is optional and is not set, or if it
1080 # isn't a submessage then the field is initialized.
1081 if field.label == _FieldDescriptor.LABEL_OPTIONAL:
1082 if not _HasFieldOrExtension(message, field):
1083 return True
1084 if field.cpp_type != _FieldDescriptor.CPPTYPE_MESSAGE:
1085 return True
1086
1087 # The field is set and is either a single or a repeated submessage.
1088 messages = _FieldOrExtensionValues(message, field)
1089 # If all submessages in this field are initialized, the field is
1090 # considered initialized.
1091 for message in messages:
1092 if not _InternalIsInitialized(message, errors):
1093 return False
1094 return True
1095
1096
1097def _InternalIsInitialized(message, errors=None):
1098 """Checks if all required fields of a message are set.
1099
1100 Args:
1101 message: The message to check.
1102 errors: If set, initialization errors will be appended to it.
1103
1104 Returns:
1105 True iff the specified message has all required fields set.
1106 """
1107 fields_and_extensions = []
1108 fields_and_extensions.extend(message.DESCRIPTOR.fields)
1109 fields_and_extensions.extend(
1110 [extension[0] for extension in message.Extensions._ListSetExtensions()])
1111 for field_or_extension in fields_and_extensions:
1112 if not _IsFieldOrExtensionInitialized(message, field_or_extension, errors):
1113 return False
1114 return True
1115
1116
1117def _AddMergeFromStringMethod(message_descriptor, cls):
1118 """Helper for _AddMessageMethods()."""
1119 Decoder = decoder.Decoder
1120 def MergeFromString(self, serialized):
1121 decoder = Decoder(serialized)
1122 byte_count = 0
1123 while not decoder.EndOfStream():
1124 bytes_read = _DeserializeOneEntity(message_descriptor, self, decoder)
1125 if not bytes_read:
1126 break
1127 byte_count += bytes_read
1128 return byte_count
1129 cls.MergeFromString = MergeFromString
1130
1131
1132def _AddIsInitializedMethod(cls):
1133 """Adds the IsInitialized method to the protocol message class."""
1134 cls.IsInitialized = _InternalIsInitialized
1135
1136
1137def _MergeFieldOrExtension(destination_msg, field, value):
1138 """Merges a specified message field into another message."""
1139 property_name = _PropertyName(field.name)
1140 is_extension = field.is_extension
1141
1142 if not is_extension:
1143 destination = getattr(destination_msg, property_name)
1144 elif (field.label == _FieldDescriptor.LABEL_REPEATED or
1145 field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE):
1146 destination = destination_msg.Extensions[field]
1147
1148 # Case 1 - a composite field.
1149 if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
1150 if field.label == _FieldDescriptor.LABEL_REPEATED:
1151 for v in value:
1152 destination.add().MergeFrom(v)
1153 else:
1154 destination.MergeFrom(value)
1155 return
1156
1157 # Case 2 - a repeated field.
1158 if field.label == _FieldDescriptor.LABEL_REPEATED:
1159 for v in value:
1160 destination.append(v)
1161 return
1162
1163 # Case 3 - a singular field.
1164 if is_extension:
1165 destination_msg.Extensions[field] = value
1166 else:
1167 setattr(destination_msg, property_name, value)
1168
1169
1170def _AddMergeFromMethod(cls):
1171 def MergeFrom(self, msg):
1172 assert msg is not self
1173 for field in msg.ListFields():
1174 _MergeFieldOrExtension(self, field[0], field[1])
1175 cls.MergeFrom = MergeFrom
1176
1177
1178def _AddMessageMethods(message_descriptor, cls):
1179 """Adds implementations of all Message methods to cls."""
1180 _AddListFieldsMethod(message_descriptor, cls)
1181 _AddHasFieldMethod(cls)
1182 _AddClearFieldMethod(cls)
1183 _AddClearExtensionMethod(cls)
1184 _AddClearMethod(cls)
1185 _AddHasExtensionMethod(cls)
1186 _AddEqualsMethod(message_descriptor, cls)
1187 _AddSetListenerMethod(cls)
1188 _AddByteSizeMethod(message_descriptor, cls)
1189 _AddSerializeToStringMethod(message_descriptor, cls)
1190 _AddSerializePartialToStringMethod(message_descriptor, cls)
1191 _AddMergeFromStringMethod(message_descriptor, cls)
1192 _AddIsInitializedMethod(cls)
1193 _AddMergeFromMethod(cls)
1194
1195
1196def _AddPrivateHelperMethods(cls):
1197 """Adds implementation of private helper methods to cls."""
1198
1199 def MaybeCallTransitionToNonemptyCallback(self):
1200 """Calls self._listener.TransitionToNonempty() the first time this
1201 method is called. On all subsequent calls, this is a no-op.
1202 """
1203 if not self._called_transition_to_nonempty:
1204 self._listener.TransitionToNonempty()
1205 self._called_transition_to_nonempty = True
1206 cls._MaybeCallTransitionToNonemptyCallback = (
1207 MaybeCallTransitionToNonemptyCallback)
1208
1209 def MarkByteSizeDirty(self):
1210 """Sets the _cached_byte_size_dirty bit to true,
1211 and propagates this to our listener iff this was a state change.
1212 """
1213 if not self._cached_byte_size_dirty:
1214 self._cached_byte_size_dirty = True
1215 self._listener.ByteSizeDirty()
1216 cls._MarkByteSizeDirty = MarkByteSizeDirty
1217
1218
1219class _Listener(object):
1220
1221 """MessageListener implementation that a parent message registers with its
1222 child message.
1223
1224 In order to support semantics like:
1225
1226 foo.bar.baz = 23
1227 assert foo.HasField('bar')
1228
1229 ...child objects must have back references to their parents.
1230 This helper class is at the heart of this support.
1231 """
1232
1233 def __init__(self, parent_message, has_field_name):
1234 """Args:
1235 parent_message: The message whose _MaybeCallTransitionToNonemptyCallback()
1236 and _MarkByteSizeDirty() methods we should call when we receive
1237 TransitionToNonempty() and ByteSizeDirty() messages.
1238 has_field_name: The name of the "has" field that we should set in
1239 the parent message when we receive a TransitionToNonempty message,
1240 or None if there's no "has" field to set. (This will be the case
1241 for child objects in "repeated" fields).
1242 """
1243 # This listener establishes a back reference from a child (contained) object
1244 # to its parent (containing) object. We make this a weak reference to avoid
1245 # creating cyclic garbage when the client finishes with the 'parent' object
1246 # in the tree.
1247 if isinstance(parent_message, weakref.ProxyType):
1248 self._parent_message_weakref = parent_message
1249 else:
1250 self._parent_message_weakref = weakref.proxy(parent_message)
1251 self._has_field_name = has_field_name
1252
1253 def TransitionToNonempty(self):
1254 try:
1255 if self._has_field_name is not None:
1256 setattr(self._parent_message_weakref, self._has_field_name, True)
1257 # Propagate the signal to our parents iff this is the first field set.
1258 self._parent_message_weakref._MaybeCallTransitionToNonemptyCallback()
1259 except ReferenceError:
1260 # We can get here if a client has kept a reference to a child object,
1261 # and is now setting a field on it, but the child's parent has been
1262 # garbage-collected. This is not an error.
1263 pass
1264
1265 def ByteSizeDirty(self):
1266 try:
1267 self._parent_message_weakref._MarkByteSizeDirty()
1268 except ReferenceError:
1269 # Same as above.
1270 pass
1271
1272
1273# TODO(robinson): Move elsewhere?
1274# TODO(robinson): Provide a clear() method here in addition to ClearField()?
1275class _RepeatedScalarFieldContainer(object):
1276
1277 """Simple, type-checked, list-like container for holding repeated scalars."""
1278
1279 # Minimizes memory usage and disallows assignment to other attributes.
1280 __slots__ = ['_message_listener', '_type_checker', '_values']
1281
1282 def __init__(self, message_listener, type_checker):
1283 """
1284 Args:
1285 message_listener: A MessageListener implementation.
1286 The _RepeatedScalarFieldContaininer will call this object's
1287 TransitionToNonempty() method when it transitions from being empty to
1288 being nonempty.
1289 type_checker: A _ValueChecker instance to run on elements inserted
1290 into this container.
1291 """
1292 self._message_listener = message_listener
1293 self._type_checker = type_checker
1294 self._values = []
1295
1296 def append(self, elem):
1297 self._type_checker.CheckValue(elem)
1298 self._values.append(elem)
1299 self._message_listener.ByteSizeDirty()
1300 if len(self._values) == 1:
1301 self._message_listener.TransitionToNonempty()
1302
1303 def remove(self, elem):
1304 self._values.remove(elem)
1305 self._message_listener.ByteSizeDirty()
1306
1307 # List-like __getitem__() support also makes us iterable (via "iter(foo)"
1308 # or implicitly via "for i in mylist:") for free.
1309 def __getitem__(self, key):
1310 return self._values[key]
1311
1312 def __setitem__(self, key, value):
1313 # No need to call TransitionToNonempty(), since if we're able to
1314 # set the element at this index, we were already nonempty before
1315 # this method was called.
1316 self._message_listener.ByteSizeDirty()
1317 self._type_checker.CheckValue(value)
1318 self._values[key] = value
1319
1320 def __len__(self):
1321 return len(self._values)
1322
1323 def __eq__(self, other):
1324 if self is other:
1325 return True
1326 # Special case for the same type which should be common and fast.
1327 if isinstance(other, self.__class__):
1328 return other._values == self._values
1329 # We are presumably comparing against some other sequence type.
1330 return other == self._values
1331
1332 def __ne__(self, other):
1333 # Can't use != here since it would infinitely recurse.
1334 return not self == other
1335
1336
1337# TODO(robinson): Move elsewhere?
1338# TODO(robinson): Provide a clear() method here in addition to ClearField()?
1339# TODO(robinson): Unify common functionality with
1340# _RepeatedScalarFieldContaininer?
1341class _RepeatedCompositeFieldContainer(object):
1342
1343 """Simple, list-like container for holding repeated composite fields."""
1344
1345 # Minimizes memory usage and disallows assignment to other attributes.
1346 __slots__ = ['_values', '_message_descriptor', '_message_listener']
1347
1348 def __init__(self, message_listener, message_descriptor):
1349 """Note that we pass in a descriptor instead of the generated directly,
1350 since at the time we construct a _RepeatedCompositeFieldContainer we
1351 haven't yet necessarily initialized the type that will be contained in the
1352 container.
1353
1354 Args:
1355 message_listener: A MessageListener implementation.
1356 The _RepeatedCompositeFieldContainer will call this object's
1357 TransitionToNonempty() method when it transitions from being empty to
1358 being nonempty.
1359 message_descriptor: A Descriptor instance describing the protocol type
1360 that should be present in this container. We'll use the
1361 _concrete_class field of this descriptor when the client calls add().
1362 """
1363 self._message_listener = message_listener
1364 self._message_descriptor = message_descriptor
1365 self._values = []
1366
1367 def add(self):
1368 new_element = self._message_descriptor._concrete_class()
1369 new_element._SetListener(self._message_listener)
1370 self._values.append(new_element)
1371 self._message_listener.ByteSizeDirty()
1372 self._message_listener.TransitionToNonempty()
1373 return new_element
1374
1375 def __delitem__(self, key):
1376 self._message_listener.ByteSizeDirty()
1377 del self._values[key]
1378
1379 # List-like __getitem__() support also makes us iterable (via "iter(foo)"
1380 # or implicitly via "for i in mylist:") for free.
1381 def __getitem__(self, key):
1382 return self._values[key]
1383
1384 def __len__(self):
1385 return len(self._values)
1386
1387 def __eq__(self, other):
1388 if self is other:
1389 return True
1390 if not isinstance(other, self.__class__):
1391 raise TypeError('Can only compare repeated composite fields against '
1392 'other repeated composite fields.')
1393 return self._values == other._values
1394
1395 def __ne__(self, other):
1396 # Can't use != here since it would infinitely recurse.
1397 return not self == other
1398
1399 # TODO(robinson): Implement, document, and test slicing support.
1400
1401
1402# TODO(robinson): Move elsewhere? This file is getting pretty ridiculous...
1403# TODO(robinson): Unify error handling of "unknown extension" crap.
1404# TODO(robinson): There's so much similarity between the way that
1405# extensions behave and the way that normal fields behave that it would
1406# be really nice to unify more code. It's not immediately obvious
1407# how to do this, though, and I'd rather get the full functionality
1408# implemented (and, crucially, get all the tests and specs fleshed out
1409# and passing), and then come back to this thorny unification problem.
1410# TODO(robinson): Support iteritems()-style iteration over all
1411# extensions with the "has" bits turned on?
1412class _ExtensionDict(object):
1413
1414 """Dict-like container for supporting an indexable "Extensions"
1415 field on proto instances.
1416
1417 Note that in all cases we expect extension handles to be
1418 FieldDescriptors.
1419 """
1420
1421 class _ExtensionListener(object):
1422
1423 """Adapts an _ExtensionDict to behave as a MessageListener."""
1424
1425 def __init__(self, extension_dict, handle_id):
1426 self._extension_dict = extension_dict
1427 self._handle_id = handle_id
1428
1429 def TransitionToNonempty(self):
1430 self._extension_dict._SubmessageTransitionedToNonempty(self._handle_id)
1431
1432 def ByteSizeDirty(self):
1433 self._extension_dict._SubmessageByteSizeBecameDirty()
1434
1435 # TODO(robinson): Somewhere, we need to blow up if people
1436 # try to register two extensions with the same field number.
1437 # (And we need a test for this of course).
1438
1439 def __init__(self, extended_message, known_extensions):
1440 """extended_message: Message instance for which we are the Extensions dict.
1441 known_extensions: Iterable of known extension handles.
1442 These must be FieldDescriptors.
1443 """
1444 # We keep a weak reference to extended_message, since
1445 # it has a reference to this instance in turn.
1446 self._extended_message = weakref.proxy(extended_message)
1447 # We make a deep copy of known_extensions to avoid any
1448 # thread-safety concerns, since the argument passed in
1449 # is the global (class-level) dict of known extensions for
1450 # this type of message, which could be modified at any time
1451 # via a RegisterExtension() call.
1452 #
1453 # This dict maps from handle id to handle (a FieldDescriptor).
1454 #
1455 # XXX
1456 # TODO(robinson): This isn't good enough. The client could
1457 # instantiate an object in module A, then afterward import
1458 # module B and pass the instance to B.Foo(). If B imports
1459 # an extender of this proto and then tries to use it, B
1460 # will get a KeyError, even though the extension *is* registered
1461 # at the time of use.
1462 # XXX
1463 self._known_extensions = dict((id(e), e) for e in known_extensions)
1464 # Read lock around self._values, which may be modified by multiple
1465 # concurrent readers in the conceptually "const" __getitem__ method.
1466 # So, we grab this lock in every "read-only" method to ensure
1467 # that concurrent read access is safe without external locking.
1468 self._lock = threading.Lock()
1469 # Maps from extension handle ID to current value of that extension.
1470 self._values = {}
1471 # Maps from extension handle ID to a boolean "has" bit, but only
1472 # for non-repeated extension fields.
1473 keys = (id for id, extension in self._known_extensions.iteritems()
1474 if extension.label != _FieldDescriptor.LABEL_REPEATED)
1475 self._has_bits = dict.fromkeys(keys, False)
1476
1477 def __getitem__(self, extension_handle):
1478 """Returns the current value of the given extension handle."""
1479 # We don't care as much about keeping critical sections short in the
1480 # extension support, since it's presumably much less of a common case.
1481 self._lock.acquire()
1482 try:
1483 handle_id = id(extension_handle)
1484 if handle_id not in self._known_extensions:
1485 raise KeyError('Extension not known to this class')
1486 if handle_id not in self._values:
1487 self._AddMissingHandle(extension_handle, handle_id)
1488 return self._values[handle_id]
1489 finally:
1490 self._lock.release()
1491
1492 def __eq__(self, other):
1493 # We have to grab read locks since we're accessing _values
1494 # in a "const" method. See the comment in the constructor.
1495 if self is other:
1496 return True
1497 self._lock.acquire()
1498 try:
1499 other._lock.acquire()
1500 try:
1501 if self._has_bits != other._has_bits:
1502 return False
1503 # If there's a "has" bit, then only compare values where it is true.
1504 for k, v in self._values.iteritems():
1505 if self._has_bits.get(k, False) and v != other._values[k]:
1506 return False
1507 return True
1508 finally:
1509 other._lock.release()
1510 finally:
1511 self._lock.release()
1512
1513 def __ne__(self, other):
1514 return not self == other
1515
1516 # Note that this is only meaningful for non-repeated, scalar extension
1517 # fields. Note also that we may have to call
1518 # MaybeCallTransitionToNonemptyCallback() when we do successfully set a field
1519 # this way, to set any necssary "has" bits in the ancestors of the extended
1520 # message.
1521 def __setitem__(self, extension_handle, value):
1522 """If extension_handle specifies a non-repeated, scalar extension
1523 field, sets the value of that field.
1524 """
1525 handle_id = id(extension_handle)
1526 if handle_id not in self._known_extensions:
1527 raise KeyError('Extension not known to this class')
1528 field = extension_handle # Just shorten the name.
1529 if (field.label == _FieldDescriptor.LABEL_OPTIONAL
1530 and field.cpp_type != _FieldDescriptor.CPPTYPE_MESSAGE):
1531 # It's slightly wasteful to lookup the type checker each time,
1532 # but we expect this to be a vanishingly uncommon case anyway.
1533 type_checker = type_checkers.GetTypeChecker(field.cpp_type, field.type)
1534 type_checker.CheckValue(value)
1535 self._values[handle_id] = value
1536 self._has_bits[handle_id] = True
1537 self._extended_message._MarkByteSizeDirty()
1538 self._extended_message._MaybeCallTransitionToNonemptyCallback()
1539 else:
1540 raise TypeError('Extension is repeated and/or a composite type.')
1541
1542 def _AddMissingHandle(self, extension_handle, handle_id):
1543 """Helper internal to ExtensionDict."""
1544 # Special handling for non-repeated message extensions, which (like
1545 # normal fields of this kind) are initialized lazily.
1546 # REQUIRES: _lock already held.
1547 cpp_type = extension_handle.cpp_type
1548 label = extension_handle.label
1549 if (cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE
1550 and label != _FieldDescriptor.LABEL_REPEATED):
1551 self._AddMissingNonRepeatedCompositeHandle(extension_handle, handle_id)
1552 else:
1553 self._values[handle_id] = _DefaultValueForField(
1554 self._extended_message, extension_handle)
1555
1556 def _AddMissingNonRepeatedCompositeHandle(self, extension_handle, handle_id):
1557 """Helper internal to ExtensionDict."""
1558 # REQUIRES: _lock already held.
1559 value = extension_handle.message_type._concrete_class()
1560 value._SetListener(_ExtensionDict._ExtensionListener(self, handle_id))
1561 self._values[handle_id] = value
1562
1563 def _SubmessageTransitionedToNonempty(self, handle_id):
1564 """Called when a submessage with a given handle id first transitions to
1565 being nonempty. Called by _ExtensionListener.
1566 """
1567 assert handle_id in self._has_bits
1568 self._has_bits[handle_id] = True
1569 self._extended_message._MaybeCallTransitionToNonemptyCallback()
1570
1571 def _SubmessageByteSizeBecameDirty(self):
1572 """Called whenever a submessage's cached byte size becomes invalid
1573 (goes from being "clean" to being "dirty"). Called by _ExtensionListener.
1574 """
1575 self._extended_message._MarkByteSizeDirty()
1576
1577 # We may wish to widen the public interface of Message.Extensions
1578 # to expose some of this private functionality in the future.
1579 # For now, we make all this functionality module-private and just
1580 # implement what we need for serialization/deserialization,
1581 # HasField()/ClearField(), etc.
1582
1583 def _HasExtension(self, extension_handle):
1584 """Method for internal use by this module.
1585 Returns true iff we "have" this extension in the sense of the
1586 "has" bit being set.
1587 """
1588 handle_id = id(extension_handle)
1589 # Note that this is different from the other checks.
1590 if handle_id not in self._has_bits:
1591 raise KeyError('Extension not known to this class, or is repeated field.')
1592 return self._has_bits[handle_id]
1593
1594 # Intentionally pretty similar to ClearField() above.
1595 def _ClearExtension(self, extension_handle):
1596 """Method for internal use by this module.
1597 Clears the specified extension, unsetting its "has" bit.
1598 """
1599 handle_id = id(extension_handle)
1600 if handle_id not in self._known_extensions:
1601 raise KeyError('Extension not known to this class')
1602 default_value = _DefaultValueForField(self._extended_message,
1603 extension_handle)
1604 if extension_handle.label == _FieldDescriptor.LABEL_REPEATED:
1605 self._extended_message._MarkByteSizeDirty()
1606 else:
1607 cpp_type = extension_handle.cpp_type
1608 if cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
1609 if handle_id in self._values:
1610 # Future modifications to this object shouldn't set any
1611 # "has" bits here.
1612 self._values[handle_id]._SetListener(None)
1613 if self._has_bits[handle_id]:
1614 self._has_bits[handle_id] = False
1615 self._extended_message._MarkByteSizeDirty()
1616 if handle_id in self._values:
1617 del self._values[handle_id]
1618
1619 def _ListSetExtensions(self):
1620 """Method for internal use by this module.
1621
1622 Returns an sequence of all extensions that are currently "set"
1623 in this extension dict. A "set" extension is a repeated extension,
1624 or a non-repeated extension with its "has" bit set.
1625
1626 The returned sequence contains (field_descriptor, value) pairs,
1627 where value is the current value of the extension with the given
1628 field descriptor.
1629
1630 The sequence values are in arbitrary order.
1631 """
1632 self._lock.acquire() # Read-only methods must lock around self._values.
1633 try:
1634 set_extensions = []
1635 for handle_id, value in self._values.iteritems():
1636 handle = self._known_extensions[handle_id]
1637 if (handle.label == _FieldDescriptor.LABEL_REPEATED
1638 or self._has_bits[handle_id]):
1639 set_extensions.append((handle, value))
1640 return set_extensions
1641 finally:
1642 self._lock.release()
1643
1644 def _AllExtensionsByNumber(self):
1645 """Method for internal use by this module.
1646
1647 Returns: A dict mapping field_number to (handle, field_descriptor),
1648 for *all* registered extensions for this dict.
1649 """
1650 # TODO(robinson): Precompute and store this away. Note that we'll have to
1651 # be careful when we move away from having _known_extensions as a
1652 # deep-copied member of this object.
1653 return dict((f.number, f) for f in self._known_extensions.itervalues())
diff --git a/froofle/protobuf/service.py b/froofle/protobuf/service.py
new file mode 100644
index 00000000..3989216a
--- /dev/null
+++ b/froofle/protobuf/service.py
@@ -0,0 +1,208 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Declares the RPC service interfaces.
32
33This module declares the abstract interfaces underlying proto2 RPC
34services. These are intented to be independent of any particular RPC
35implementation, so that proto2 services can be used on top of a variety
36of implementations.
37"""
38
39__author__ = 'petar@google.com (Petar Petrov)'
40
41
42class Service(object):
43
44 """Abstract base interface for protocol-buffer-based RPC services.
45
46 Services themselves are abstract classes (implemented either by servers or as
47 stubs), but they subclass this base interface. The methods of this
48 interface can be used to call the methods of the service without knowing
49 its exact type at compile time (analogous to the Message interface).
50 """
51
52 def GetDescriptor(self):
53 """Retrieves this service's descriptor."""
54 raise NotImplementedError
55
56 def CallMethod(self, method_descriptor, rpc_controller,
57 request, done):
58 """Calls a method of the service specified by method_descriptor.
59
60 Preconditions:
61 * method_descriptor.service == GetDescriptor
62 * request is of the exact same classes as returned by
63 GetRequestClass(method).
64 * After the call has started, the request must not be modified.
65 * "rpc_controller" is of the correct type for the RPC implementation being
66 used by this Service. For stubs, the "correct type" depends on the
67 RpcChannel which the stub is using.
68
69 Postconditions:
70 * "done" will be called when the method is complete. This may be
71 before CallMethod() returns or it may be at some point in the future.
72 """
73 raise NotImplementedError
74
75 def GetRequestClass(self, method_descriptor):
76 """Returns the class of the request message for the specified method.
77
78 CallMethod() requires that the request is of a particular subclass of
79 Message. GetRequestClass() gets the default instance of this required
80 type.
81
82 Example:
83 method = service.GetDescriptor().FindMethodByName("Foo")
84 request = stub.GetRequestClass(method)()
85 request.ParseFromString(input)
86 service.CallMethod(method, request, callback)
87 """
88 raise NotImplementedError
89
90 def GetResponseClass(self, method_descriptor):
91 """Returns the class of the response message for the specified method.
92
93 This method isn't really needed, as the RpcChannel's CallMethod constructs
94 the response protocol message. It's provided anyway in case it is useful
95 for the caller to know the response type in advance.
96 """
97 raise NotImplementedError
98
99
100class RpcController(object):
101
102 """An RpcController mediates a single method call.
103
104 The primary purpose of the controller is to provide a way to manipulate
105 settings specific to the RPC implementation and to find out about RPC-level
106 errors. The methods provided by the RpcController interface are intended
107 to be a "least common denominator" set of features which we expect all
108 implementations to support. Specific implementations may provide more
109 advanced features (e.g. deadline propagation).
110 """
111
112 # Client-side methods below
113
114 def Reset(self):
115 """Resets the RpcController to its initial state.
116
117 After the RpcController has been reset, it may be reused in
118 a new call. Must not be called while an RPC is in progress.
119 """
120 raise NotImplementedError
121
122 def Failed(self):
123 """Returns true if the call failed.
124
125 After a call has finished, returns true if the call failed. The possible
126 reasons for failure depend on the RPC implementation. Failed() must not
127 be called before a call has finished. If Failed() returns true, the
128 contents of the response message are undefined.
129 """
130 raise NotImplementedError
131
132 def ErrorText(self):
133 """If Failed is true, returns a human-readable description of the error."""
134 raise NotImplementedError
135
136 def StartCancel(self):
137 """Initiate cancellation.
138
139 Advises the RPC system that the caller desires that the RPC call be
140 canceled. The RPC system may cancel it immediately, may wait awhile and
141 then cancel it, or may not even cancel the call at all. If the call is
142 canceled, the "done" callback will still be called and the RpcController
143 will indicate that the call failed at that time.
144 """
145 raise NotImplementedError
146
147 # Server-side methods below
148
149 def SetFailed(self, reason):
150 """Sets a failure reason.
151
152 Causes Failed() to return true on the client side. "reason" will be
153 incorporated into the message returned by ErrorText(). If you find
154 you need to return machine-readable information about failures, you
155 should incorporate it into your response protocol buffer and should
156 NOT call SetFailed().
157 """
158 raise NotImplementedError
159
160 def IsCanceled(self):
161 """Checks if the client cancelled the RPC.
162
163 If true, indicates that the client canceled the RPC, so the server may
164 as well give up on replying to it. The server should still call the
165 final "done" callback.
166 """
167 raise NotImplementedError
168
169 def NotifyOnCancel(self, callback):
170 """Sets a callback to invoke on cancel.
171
172 Asks that the given callback be called when the RPC is canceled. The
173 callback will always be called exactly once. If the RPC completes without
174 being canceled, the callback will be called after completion. If the RPC
175 has already been canceled when NotifyOnCancel() is called, the callback
176 will be called immediately.
177
178 NotifyOnCancel() must be called no more than once per request.
179 """
180 raise NotImplementedError
181
182
183class RpcChannel(object):
184
185 """Abstract interface for an RPC channel.
186
187 An RpcChannel represents a communication line to a service which can be used
188 to call that service's methods. The service may be running on another
189 machine. Normally, you should not use an RpcChannel directly, but instead
190 construct a stub {@link Service} wrapping it. Example:
191
192 Example:
193 RpcChannel channel = rpcImpl.Channel("remotehost.example.com:1234")
194 RpcController controller = rpcImpl.Controller()
195 MyService service = MyService_Stub(channel)
196 service.MyMethod(controller, request, callback)
197 """
198
199 def CallMethod(self, method_descriptor, rpc_controller,
200 request, response_class, done):
201 """Calls the method identified by the descriptor.
202
203 Call the given method of the remote service. The signature of this
204 procedure looks the same as Service.CallMethod(), but the requirements
205 are less strict in one important way: the request object doesn't have to
206 be of any specific class as long as its descriptor is method.input_type.
207 """
208 raise NotImplementedError
diff --git a/froofle/protobuf/service_reflection.py b/froofle/protobuf/service_reflection.py
new file mode 100644
index 00000000..bdd6bad5
--- /dev/null
+++ b/froofle/protobuf/service_reflection.py
@@ -0,0 +1,289 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Contains metaclasses used to create protocol service and service stub
32classes from ServiceDescriptor objects at runtime.
33
34The GeneratedServiceType and GeneratedServiceStubType metaclasses are used to
35inject all useful functionality into the classes output by the protocol
36compiler at compile-time.
37"""
38
39__author__ = 'petar@google.com (Petar Petrov)'
40
41
42class GeneratedServiceType(type):
43
44 """Metaclass for service classes created at runtime from ServiceDescriptors.
45
46 Implementations for all methods described in the Service class are added here
47 by this class. We also create properties to allow getting/setting all fields
48 in the protocol message.
49
50 The protocol compiler currently uses this metaclass to create protocol service
51 classes at runtime. Clients can also manually create their own classes at
52 runtime, as in this example:
53
54 mydescriptor = ServiceDescriptor(.....)
55 class MyProtoService(service.Service):
56 __metaclass__ = GeneratedServiceType
57 DESCRIPTOR = mydescriptor
58 myservice_instance = MyProtoService()
59 ...
60 """
61
62 _DESCRIPTOR_KEY = 'DESCRIPTOR'
63
64 def __init__(cls, name, bases, dictionary):
65 """Creates a message service class.
66
67 Args:
68 name: Name of the class (ignored, but required by the metaclass
69 protocol).
70 bases: Base classes of the class being constructed.
71 dictionary: The class dictionary of the class being constructed.
72 dictionary[_DESCRIPTOR_KEY] must contain a ServiceDescriptor object
73 describing this protocol service type.
74 """
75 # Don't do anything if this class doesn't have a descriptor. This happens
76 # when a service class is subclassed.
77 if GeneratedServiceType._DESCRIPTOR_KEY not in dictionary:
78 return
79 descriptor = dictionary[GeneratedServiceType._DESCRIPTOR_KEY]
80 service_builder = _ServiceBuilder(descriptor)
81 service_builder.BuildService(cls)
82
83
84class GeneratedServiceStubType(GeneratedServiceType):
85
86 """Metaclass for service stubs created at runtime from ServiceDescriptors.
87
88 This class has similar responsibilities as GeneratedServiceType, except that
89 it creates the service stub classes.
90 """
91
92 _DESCRIPTOR_KEY = 'DESCRIPTOR'
93
94 def __init__(cls, name, bases, dictionary):
95 """Creates a message service stub class.
96
97 Args:
98 name: Name of the class (ignored, here).
99 bases: Base classes of the class being constructed.
100 dictionary: The class dictionary of the class being constructed.
101 dictionary[_DESCRIPTOR_KEY] must contain a ServiceDescriptor object
102 describing this protocol service type.
103 """
104 super(GeneratedServiceStubType, cls).__init__(name, bases, dictionary)
105 # Don't do anything if this class doesn't have a descriptor. This happens
106 # when a service stub is subclassed.
107 if GeneratedServiceStubType._DESCRIPTOR_KEY not in dictionary:
108 return
109 descriptor = dictionary[GeneratedServiceStubType._DESCRIPTOR_KEY]
110 service_stub_builder = _ServiceStubBuilder(descriptor)
111 service_stub_builder.BuildServiceStub(cls)
112
113
114class _ServiceBuilder(object):
115
116 """This class constructs a protocol service class using a service descriptor.
117
118 Given a service descriptor, this class constructs a class that represents
119 the specified service descriptor. One service builder instance constructs
120 exactly one service class. That means all instances of that class share the
121 same builder.
122 """
123
124 def __init__(self, service_descriptor):
125 """Initializes an instance of the service class builder.
126
127 Args:
128 service_descriptor: ServiceDescriptor to use when constructing the
129 service class.
130 """
131 self.descriptor = service_descriptor
132
133 def BuildService(self, cls):
134 """Constructs the service class.
135
136 Args:
137 cls: The class that will be constructed.
138 """
139
140 # CallMethod needs to operate with an instance of the Service class. This
141 # internal wrapper function exists only to be able to pass the service
142 # instance to the method that does the real CallMethod work.
143 def _WrapCallMethod(srvc, method_descriptor,
144 rpc_controller, request, callback):
145 self._CallMethod(srvc, method_descriptor,
146 rpc_controller, request, callback)
147 self.cls = cls
148 cls.CallMethod = _WrapCallMethod
149 cls.GetDescriptor = self._GetDescriptor
150 cls.GetRequestClass = self._GetRequestClass
151 cls.GetResponseClass = self._GetResponseClass
152 for method in self.descriptor.methods:
153 setattr(cls, method.name, self._GenerateNonImplementedMethod(method))
154
155 def _GetDescriptor(self):
156 """Retrieves the service descriptor.
157
158 Returns:
159 The descriptor of the service (of type ServiceDescriptor).
160 """
161 return self.descriptor
162
163 def _CallMethod(self, srvc, method_descriptor,
164 rpc_controller, request, callback):
165 """Calls the method described by a given method descriptor.
166
167 Args:
168 srvc: Instance of the service for which this method is called.
169 method_descriptor: Descriptor that represent the method to call.
170 rpc_controller: RPC controller to use for this method's execution.
171 request: Request protocol message.
172 callback: A callback to invoke after the method has completed.
173 """
174 if method_descriptor.containing_service != self.descriptor:
175 raise RuntimeError(
176 'CallMethod() given method descriptor for wrong service type.')
177 method = getattr(srvc, method_descriptor.name)
178 method(rpc_controller, request, callback)
179
180 def _GetRequestClass(self, method_descriptor):
181 """Returns the class of the request protocol message.
182
183 Args:
184 method_descriptor: Descriptor of the method for which to return the
185 request protocol message class.
186
187 Returns:
188 A class that represents the input protocol message of the specified
189 method.
190 """
191 if method_descriptor.containing_service != self.descriptor:
192 raise RuntimeError(
193 'GetRequestClass() given method descriptor for wrong service type.')
194 return method_descriptor.input_type._concrete_class
195
196 def _GetResponseClass(self, method_descriptor):
197 """Returns the class of the response protocol message.
198
199 Args:
200 method_descriptor: Descriptor of the method for which to return the
201 response protocol message class.
202
203 Returns:
204 A class that represents the output protocol message of the specified
205 method.
206 """
207 if method_descriptor.containing_service != self.descriptor:
208 raise RuntimeError(
209 'GetResponseClass() given method descriptor for wrong service type.')
210 return method_descriptor.output_type._concrete_class
211
212 def _GenerateNonImplementedMethod(self, method):
213 """Generates and returns a method that can be set for a service methods.
214
215 Args:
216 method: Descriptor of the service method for which a method is to be
217 generated.
218
219 Returns:
220 A method that can be added to the service class.
221 """
222 return lambda inst, rpc_controller, request, callback: (
223 self._NonImplementedMethod(method.name, rpc_controller, callback))
224
225 def _NonImplementedMethod(self, method_name, rpc_controller, callback):
226 """The body of all methods in the generated service class.
227
228 Args:
229 method_name: Name of the method being executed.
230 rpc_controller: RPC controller used to execute this method.
231 callback: A callback which will be invoked when the method finishes.
232 """
233 rpc_controller.SetFailed('Method %s not implemented.' % method_name)
234 callback(None)
235
236
237class _ServiceStubBuilder(object):
238
239 """Constructs a protocol service stub class using a service descriptor.
240
241 Given a service descriptor, this class constructs a suitable stub class.
242 A stub is just a type-safe wrapper around an RpcChannel which emulates a
243 local implementation of the service.
244
245 One service stub builder instance constructs exactly one class. It means all
246 instances of that class share the same service stub builder.
247 """
248
249 def __init__(self, service_descriptor):
250 """Initializes an instance of the service stub class builder.
251
252 Args:
253 service_descriptor: ServiceDescriptor to use when constructing the
254 stub class.
255 """
256 self.descriptor = service_descriptor
257
258 def BuildServiceStub(self, cls):
259 """Constructs the stub class.
260
261 Args:
262 cls: The class that will be constructed.
263 """
264
265 def _ServiceStubInit(stub, rpc_channel):
266 stub.rpc_channel = rpc_channel
267 self.cls = cls
268 cls.__init__ = _ServiceStubInit
269 for method in self.descriptor.methods:
270 setattr(cls, method.name, self._GenerateStubMethod(method))
271
272 def _GenerateStubMethod(self, method):
273 return lambda inst, rpc_controller, request, callback: self._StubMethod(
274 inst, method, rpc_controller, request, callback)
275
276 def _StubMethod(self, stub, method_descriptor,
277 rpc_controller, request, callback):
278 """The body of all service methods in the generated stub class.
279
280 Args:
281 stub: Stub instance.
282 method_descriptor: Descriptor of the invoked method.
283 rpc_controller: Rpc controller to execute the method.
284 request: Request protocol message.
285 callback: A callback to execute when the method finishes.
286 """
287 stub.rpc_channel.CallMethod(
288 method_descriptor, rpc_controller, request,
289 method_descriptor.output_type._concrete_class, callback)
diff --git a/froofle/protobuf/text_format.py b/froofle/protobuf/text_format.py
new file mode 100644
index 00000000..1c4cadfc
--- /dev/null
+++ b/froofle/protobuf/text_format.py
@@ -0,0 +1,125 @@
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Contains routines for printing protocol messages in text format."""
32
33__author__ = 'kenton@google.com (Kenton Varda)'
34
35import cStringIO
36
37from froofle.protobuf import descriptor
38
39__all__ = [ 'MessageToString', 'PrintMessage', 'PrintField', 'PrintFieldValue' ]
40
41def MessageToString(message):
42 out = cStringIO.StringIO()
43 PrintMessage(message, out)
44 result = out.getvalue()
45 out.close()
46 return result
47
48def PrintMessage(message, out, indent = 0):
49 for field, value in message.ListFields():
50 if field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
51 for element in value:
52 PrintField(field, element, out, indent)
53 else:
54 PrintField(field, value, out, indent)
55
56def PrintField(field, value, out, indent = 0):
57 """Print a single field name/value pair. For repeated fields, the value
58 should be a single element."""
59
60 out.write(' ' * indent);
61 if field.is_extension:
62 out.write('[')
63 if (field.containing_type.GetOptions().message_set_wire_format and
64 field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and
65 field.message_type == field.extension_scope and
66 field.label == descriptor.FieldDescriptor.LABEL_OPTIONAL):
67 out.write(field.message_type.full_name)
68 else:
69 out.write(field.full_name)
70 out.write(']')
71 elif field.type == descriptor.FieldDescriptor.TYPE_GROUP:
72 # For groups, use the capitalized name.
73 out.write(field.message_type.name)
74 else:
75 out.write(field.name)
76
77 if field.cpp_type != descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
78 # The colon is optional in this case, but our cross-language golden files
79 # don't include it.
80 out.write(': ')
81
82 PrintFieldValue(field, value, out, indent)
83 out.write('\n')
84
85def PrintFieldValue(field, value, out, indent = 0):
86 """Print a single field value (not including name). For repeated fields,
87 the value should be a single element."""
88
89 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
90 out.write(' {\n')
91 PrintMessage(value, out, indent + 2)
92 out.write(' ' * indent + '}')
93 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM:
94 out.write(field.enum_type.values_by_number[value].name)
95 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
96 out.write('\"')
97 out.write(_CEscape(value))
98 out.write('\"')
99 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL:
100 if value:
101 out.write("true")
102 else:
103 out.write("false")
104 else:
105 out.write(str(value))
106
107# text.encode('string_escape') does not seem to satisfy our needs as it
108# encodes unprintable characters using two-digit hex escapes whereas our
109# C++ unescaping function allows hex escapes to be any length. So,
110# "\0011".encode('string_escape') ends up being "\\x011", which will be
111# decoded in C++ as a single-character string with char code 0x11.
112def _CEscape(text):
113 def escape(c):
114 o = ord(c)
115 if o == 10: return r"\n" # optional escape
116 if o == 13: return r"\r" # optional escape
117 if o == 9: return r"\t" # optional escape
118 if o == 39: return r"\'" # optional escape
119
120 if o == 34: return r'\"' # necessary escape
121 if o == 92: return r"\\" # necessary escape
122
123 if o >= 127 or o < 32: return "\\%03o" % o # necessary escapes
124 return c
125 return "".join([escape(c) for c in text])
diff --git a/gerrit_upload.py b/gerrit_upload.py
new file mode 100755
index 00000000..a49fb232
--- /dev/null
+++ b/gerrit_upload.py
@@ -0,0 +1,156 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import getpass
17import os
18import subprocess
19import sys
20from tempfile import mkstemp
21
22from codereview.proto_client import HttpRpc, Proxy
23from codereview.review_pb2 import ReviewService_Stub
24from codereview.upload_bundle_pb2 import *
25from git_command import GitCommand
26from error import UploadError
27
28try:
29 import readline
30except ImportError:
31 pass
32
33MAX_SEGMENT_SIZE = 1020 * 1024
34
35def _GetRpcServer(email, server, save_cookies):
36 """Returns an RpcServer.
37
38 Returns:
39 A new RpcServer, on which RPC calls can be made.
40 """
41
42 def GetUserCredentials():
43 """Prompts the user for a username and password."""
44 e = email
45 if e is None:
46 e = raw_input("Email: ").strip()
47 password = getpass.getpass("Password for %s: " % e)
48 return (e, password)
49
50 # If this is the dev_appserver, use fake authentication.
51 lc_server = server.lower()
52 if lc_server == "localhost" or lc_server.startswith("localhost:"):
53 if email is None:
54 email = "test@example.com"
55 server = HttpRpc(
56 server,
57 lambda: (email, "password"),
58 extra_headers={"Cookie":
59 'dev_appserver_login="%s:False"' % email})
60 # Don't try to talk to ClientLogin.
61 server.authenticated = True
62 return server
63
64 if save_cookies:
65 cookie_file = ".gerrit_cookies"
66 else:
67 cookie_file = None
68
69 return HttpRpc(server, GetUserCredentials,
70 cookie_file=cookie_file)
71
72def UploadBundle(project,
73 server,
74 email,
75 dest_project,
76 dest_branch,
77 src_branch,
78 bases,
79 save_cookies=True):
80
81 srv = _GetRpcServer(email, server, save_cookies)
82 review = Proxy(ReviewService_Stub(srv))
83 tmp_fd, tmp_bundle = mkstemp(".bundle", ".gpq")
84 os.close(tmp_fd)
85
86 srcid = project.bare_git.rev_parse(src_branch)
87 revlist = project._revlist(src_branch, *bases)
88
89 if srcid not in revlist:
90 # This can happen if src_branch is an annotated tag
91 #
92 revlist.append(srcid)
93 revlist_size = len(revlist) * 42
94
95 try:
96 cmd = ['bundle', 'create', tmp_bundle, src_branch]
97 cmd.extend(bases)
98 if GitCommand(project, cmd).Wait() != 0:
99 raise UploadError('cannot create bundle')
100 fd = open(tmp_bundle, "rb")
101
102 bundle_id = None
103 segment_id = 0
104 next_data = fd.read(MAX_SEGMENT_SIZE - revlist_size)
105
106 while True:
107 this_data = next_data
108 next_data = fd.read(MAX_SEGMENT_SIZE)
109 segment_id += 1
110
111 if bundle_id is None:
112 req = UploadBundleRequest()
113 req.dest_project = str(dest_project)
114 req.dest_branch = str(dest_branch)
115 for c in revlist:
116 req.contained_object.append(c)
117 else:
118 req = UploadBundleContinue()
119 req.bundle_id = bundle_id
120 req.segment_id = segment_id
121
122 req.bundle_data = this_data
123 if len(next_data) > 0:
124 req.partial_upload = True
125 else:
126 req.partial_upload = False
127
128 if bundle_id is None:
129 rsp = review.UploadBundle(req)
130 else:
131 rsp = review.ContinueBundle(req)
132
133 if rsp.status_code == UploadBundleResponse.CONTINUE:
134 bundle_id = rsp.bundle_id
135 elif rsp.status_code == UploadBundleResponse.RECEIVED:
136 bundle_id = rsp.bundle_id
137 return bundle_id
138 else:
139 if rsp.status_code == UploadBundleResponse.UNKNOWN_PROJECT:
140 reason = 'unknown project "%s"' % dest_project
141 elif rsp.status_code == UploadBundleResponse.UNKNOWN_BRANCH:
142 reason = 'unknown branch "%s"' % dest_branch
143 elif rsp.status_code == UploadBundleResponse.UNKNOWN_BUNDLE:
144 reason = 'unknown bundle'
145 elif rsp.status_code == UploadBundleResponse.NOT_BUNDLE_OWNER:
146 reason = 'not bundle owner'
147 elif rsp.status_code == UploadBundleResponse.BUNDLE_CLOSED:
148 reason = 'bundle closed'
149 elif rsp.status_code == UploadBundleResponse.UNAUTHORIZED_USER:
150 reason = ('Unauthorized user. Visit http://%s/hello to sign up.'
151 % server)
152 else:
153 reason = 'unknown error ' + str(rsp.status_code)
154 raise UploadError(reason)
155 finally:
156 os.unlink(tmp_bundle)
diff --git a/git_command.py b/git_command.py
new file mode 100644
index 00000000..a3bd9192
--- /dev/null
+++ b/git_command.py
@@ -0,0 +1,164 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import sys
18import subprocess
19from error import GitError
20
21GIT = 'git'
22MIN_GIT_VERSION = (1, 5, 4)
23GIT_DIR = 'GIT_DIR'
24REPO_TRACE = 'REPO_TRACE'
25
26LAST_GITDIR = None
27LAST_CWD = None
28try:
29 TRACE = os.environ[REPO_TRACE] == '1'
30except KeyError:
31 TRACE = False
32
33
34class _GitCall(object):
35 def version(self):
36 p = GitCommand(None, ['--version'], capture_stdout=True)
37 if p.Wait() == 0:
38 return p.stdout
39 return None
40
41 def __getattr__(self, name):
42 name = name.replace('_','-')
43 def fun(*cmdv):
44 command = [name]
45 command.extend(cmdv)
46 return GitCommand(None, command).Wait() == 0
47 return fun
48git = _GitCall()
49
50class GitCommand(object):
51 def __init__(self,
52 project,
53 cmdv,
54 bare = False,
55 provide_stdin = False,
56 capture_stdout = False,
57 capture_stderr = False,
58 disable_editor = False,
59 cwd = None,
60 gitdir = None):
61 env = dict(os.environ)
62
63 for e in [REPO_TRACE,
64 GIT_DIR,
65 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
66 'GIT_OBJECT_DIRECTORY',
67 'GIT_WORK_TREE',
68 'GIT_GRAFT_FILE',
69 'GIT_INDEX_FILE']:
70 if e in env:
71 del env[e]
72
73 if disable_editor:
74 env['GIT_EDITOR'] = ':'
75
76 if project:
77 if not cwd:
78 cwd = project.worktree
79 if not gitdir:
80 gitdir = project.gitdir
81
82 command = [GIT]
83 if bare:
84 if gitdir:
85 env[GIT_DIR] = gitdir
86 cwd = None
87 command.extend(cmdv)
88
89 if provide_stdin:
90 stdin = subprocess.PIPE
91 else:
92 stdin = None
93
94 if capture_stdout:
95 stdout = subprocess.PIPE
96 else:
97 stdout = None
98
99 if capture_stderr:
100 stderr = subprocess.PIPE
101 else:
102 stderr = None
103
104 if TRACE:
105 global LAST_CWD
106 global LAST_GITDIR
107
108 dbg = ''
109
110 if cwd and LAST_CWD != cwd:
111 if LAST_GITDIR or LAST_CWD:
112 dbg += '\n'
113 dbg += ': cd %s\n' % cwd
114 LAST_CWD = cwd
115
116 if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]:
117 if LAST_GITDIR or LAST_CWD:
118 dbg += '\n'
119 dbg += ': export GIT_DIR=%s\n' % env[GIT_DIR]
120 LAST_GITDIR = env[GIT_DIR]
121
122 dbg += ': '
123 dbg += ' '.join(command)
124 if stdin == subprocess.PIPE:
125 dbg += ' 0<|'
126 if stdout == subprocess.PIPE:
127 dbg += ' 1>|'
128 if stderr == subprocess.PIPE:
129 dbg += ' 2>|'
130 print >>sys.stderr, dbg
131
132 try:
133 p = subprocess.Popen(command,
134 cwd = cwd,
135 env = env,
136 stdin = stdin,
137 stdout = stdout,
138 stderr = stderr)
139 except Exception, e:
140 raise GitError('%s: %s' % (command[1], e))
141
142 self.process = p
143 self.stdin = p.stdin
144
145 def Wait(self):
146 p = self.process
147
148 if p.stdin:
149 p.stdin.close()
150 self.stdin = None
151
152 if p.stdout:
153 self.stdout = p.stdout.read()
154 p.stdout.close()
155 else:
156 p.stdout = None
157
158 if p.stderr:
159 self.stderr = p.stderr.read()
160 p.stderr.close()
161 else:
162 p.stderr = None
163
164 return self.process.wait()
diff --git a/git_config.py b/git_config.py
new file mode 100644
index 00000000..f6c5bd1e
--- /dev/null
+++ b/git_config.py
@@ -0,0 +1,344 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import re
18import sys
19from error import GitError
20from git_command import GitCommand
21
22R_HEADS = 'refs/heads/'
23R_TAGS = 'refs/tags/'
24ID_RE = re.compile('^[0-9a-f]{40}$')
25
26def IsId(rev):
27 return ID_RE.match(rev)
28
29
30class GitConfig(object):
31 @classmethod
32 def ForUser(cls):
33 return cls(file = os.path.expanduser('~/.gitconfig'))
34
35 @classmethod
36 def ForRepository(cls, gitdir, defaults=None):
37 return cls(file = os.path.join(gitdir, 'config'),
38 defaults = defaults)
39
40 def __init__(self, file, defaults=None):
41 self.file = file
42 self.defaults = defaults
43 self._cache_dict = None
44 self._remotes = {}
45 self._branches = {}
46
47 def Has(self, name, include_defaults = True):
48 """Return true if this configuration file has the key.
49 """
50 name = name.lower()
51 if name in self._cache:
52 return True
53 if include_defaults and self.defaults:
54 return self.defaults.Has(name, include_defaults = True)
55 return False
56
57 def GetBoolean(self, name):
58 """Returns a boolean from the configuration file.
59 None : The value was not defined, or is not a boolean.
60 True : The value was set to true or yes.
61 False: The value was set to false or no.
62 """
63 v = self.GetString(name)
64 if v is None:
65 return None
66 v = v.lower()
67 if v in ('true', 'yes'):
68 return True
69 if v in ('false', 'no'):
70 return False
71 return None
72
73 def GetString(self, name, all=False):
74 """Get the first value for a key, or None if it is not defined.
75
76 This configuration file is used first, if the key is not
77 defined or all = True then the defaults are also searched.
78 """
79 name = name.lower()
80
81 try:
82 v = self._cache[name]
83 except KeyError:
84 if self.defaults:
85 return self.defaults.GetString(name, all = all)
86 v = []
87
88 if not all:
89 if v:
90 return v[0]
91 return None
92
93 r = []
94 r.extend(v)
95 if self.defaults:
96 r.extend(self.defaults.GetString(name, all = True))
97 return r
98
99 def SetString(self, name, value):
100 """Set the value(s) for a key.
101 Only this configuration file is modified.
102
103 The supplied value should be either a string,
104 or a list of strings (to store multiple values).
105 """
106 name = name.lower()
107
108 try:
109 old = self._cache[name]
110 except KeyError:
111 old = []
112
113 if value is None:
114 if old:
115 del self._cache[name]
116 self._do('--unset-all', name)
117
118 elif isinstance(value, list):
119 if len(value) == 0:
120 self.SetString(name, None)
121
122 elif len(value) == 1:
123 self.SetString(name, value[0])
124
125 elif old != value:
126 self._cache[name] = list(value)
127 self._do('--replace-all', name, value[0])
128 for i in xrange(1, len(value)):
129 self._do('--add', name, value[i])
130
131 elif len(old) != 1 or old[0] != value:
132 self._cache[name] = [value]
133 self._do('--replace-all', name, value)
134
135 def GetRemote(self, name):
136 """Get the remote.$name.* configuration values as an object.
137 """
138 try:
139 r = self._remotes[name]
140 except KeyError:
141 r = Remote(self, name)
142 self._remotes[r.name] = r
143 return r
144
145 def GetBranch(self, name):
146 """Get the branch.$name.* configuration values as an object.
147 """
148 try:
149 b = self._branches[name]
150 except KeyError:
151 b = Branch(self, name)
152 self._branches[b.name] = b
153 return b
154
155 @property
156 def _cache(self):
157 if self._cache_dict is None:
158 self._cache_dict = self._Read()
159 return self._cache_dict
160
161 def _Read(self):
162 d = self._do('--null', '--list')
163 c = {}
164 while d:
165 lf = d.index('\n')
166 nul = d.index('\0', lf + 1)
167
168 key = d[0:lf]
169 val = d[lf + 1:nul]
170
171 if key in c:
172 c[key].append(val)
173 else:
174 c[key] = [val]
175
176 d = d[nul + 1:]
177 return c
178
179 def _do(self, *args):
180 command = ['config', '--file', self.file]
181 command.extend(args)
182
183 p = GitCommand(None,
184 command,
185 capture_stdout = True,
186 capture_stderr = True)
187 if p.Wait() == 0:
188 return p.stdout
189 else:
190 GitError('git config %s: %s' % (str(args), p.stderr))
191
192
193class RefSpec(object):
194 """A Git refspec line, split into its components:
195
196 forced: True if the line starts with '+'
197 src: Left side of the line
198 dst: Right side of the line
199 """
200
201 @classmethod
202 def FromString(cls, rs):
203 lhs, rhs = rs.split(':', 2)
204 if lhs.startswith('+'):
205 lhs = lhs[1:]
206 forced = True
207 else:
208 forced = False
209 return cls(forced, lhs, rhs)
210
211 def __init__(self, forced, lhs, rhs):
212 self.forced = forced
213 self.src = lhs
214 self.dst = rhs
215
216 def SourceMatches(self, rev):
217 if self.src:
218 if rev == self.src:
219 return True
220 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
221 return True
222 return False
223
224 def DestMatches(self, ref):
225 if self.dst:
226 if ref == self.dst:
227 return True
228 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
229 return True
230 return False
231
232 def MapSource(self, rev):
233 if self.src.endswith('/*'):
234 return self.dst[:-1] + rev[len(self.src) - 1:]
235 return self.dst
236
237 def __str__(self):
238 s = ''
239 if self.forced:
240 s += '+'
241 if self.src:
242 s += self.src
243 if self.dst:
244 s += ':'
245 s += self.dst
246 return s
247
248
249class Remote(object):
250 """Configuration options related to a remote.
251 """
252 def __init__(self, config, name):
253 self._config = config
254 self.name = name
255 self.url = self._Get('url')
256 self.review = self._Get('review')
257 self.fetch = map(lambda x: RefSpec.FromString(x),
258 self._Get('fetch', all=True))
259
260 def ToLocal(self, rev):
261 """Convert a remote revision string to something we have locally.
262 """
263 if IsId(rev):
264 return rev
265 if rev.startswith(R_TAGS):
266 return rev
267
268 if not rev.startswith('refs/'):
269 rev = R_HEADS + rev
270
271 for spec in self.fetch:
272 if spec.SourceMatches(rev):
273 return spec.MapSource(rev)
274 raise GitError('remote %s does not have %s' % (self.name, rev))
275
276 def WritesTo(self, ref):
277 """True if the remote stores to the tracking ref.
278 """
279 for spec in self.fetch:
280 if spec.DestMatches(ref):
281 return True
282 return False
283
284 def ResetFetch(self):
285 """Set the fetch refspec to its default value.
286 """
287 self.fetch = [RefSpec(True,
288 'refs/heads/*',
289 'refs/remotes/%s/*' % self.name)]
290
291 def Save(self):
292 """Save this remote to the configuration.
293 """
294 self._Set('url', self.url)
295 self._Set('review', self.review)
296 self._Set('fetch', map(lambda x: str(x), self.fetch))
297
298 def _Set(self, key, value):
299 key = 'remote.%s.%s' % (self.name, key)
300 return self._config.SetString(key, value)
301
302 def _Get(self, key, all=False):
303 key = 'remote.%s.%s' % (self.name, key)
304 return self._config.GetString(key, all = all)
305
306
307class Branch(object):
308 """Configuration options related to a single branch.
309 """
310 def __init__(self, config, name):
311 self._config = config
312 self.name = name
313 self.merge = self._Get('merge')
314
315 r = self._Get('remote')
316 if r:
317 self.remote = self._config.GetRemote(r)
318 else:
319 self.remote = None
320
321 @property
322 def LocalMerge(self):
323 """Convert the merge spec to a local name.
324 """
325 if self.remote and self.merge:
326 return self.remote.ToLocal(self.merge)
327 return None
328
329 def Save(self):
330 """Save this branch back into the configuration.
331 """
332 self._Set('merge', self.merge)
333 if self.remote:
334 self._Set('remote', self.remote.name)
335 else:
336 self._Set('remote', None)
337
338 def _Set(self, key, value):
339 key = 'branch.%s.%s' % (self.name, key)
340 return self._config.SetString(key, value)
341
342 def _Get(self, key, all=False):
343 key = 'branch.%s.%s' % (self.name, key)
344 return self._config.GetString(key, all = all)
diff --git a/import_ext.py b/import_ext.py
new file mode 100644
index 00000000..2a1ebf88
--- /dev/null
+++ b/import_ext.py
@@ -0,0 +1,422 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import random
18import stat
19import sys
20import urllib2
21import StringIO
22
23from error import GitError, ImportError
24from git_command import GitCommand
25
26class ImportExternal(object):
27 """Imports a single revision from a non-git data source.
28 Suitable for use to import a tar or zip based snapshot.
29 """
30 def __init__(self):
31 self._marks = 0
32 self._files = {}
33 self._tempref = 'refs/repo-external/import'
34
35 self._urls = []
36 self._remap = []
37 self.parent = None
38 self._user_name = 'Upstream'
39 self._user_email = 'upstream-import@none'
40 self._user_when = 1000000
41
42 self.commit = None
43
44 def Clone(self):
45 r = self.__class__()
46
47 r.project = self.project
48 for u in self._urls:
49 r._urls.append(u)
50 for p in self._remap:
51 r._remap.append(_PathMap(r, p._old, p._new))
52
53 return r
54
55 def SetProject(self, project):
56 self.project = project
57
58 def SetVersion(self, version):
59 self.version = version
60
61 def AddUrl(self, url):
62 self._urls.append(url)
63
64 def SetParent(self, commit_hash):
65 self.parent = commit_hash
66
67 def SetCommit(self, commit_hash):
68 self.commit = commit_hash
69
70 def RemapPath(self, old, new, replace_version=True):
71 self._remap.append(_PathMap(self, old, new))
72
73 @property
74 def TagName(self):
75 v = ''
76 for c in self.version:
77 if c >= '0' and c <= '9':
78 v += c
79 elif c >= 'A' and c <= 'Z':
80 v += c
81 elif c >= 'a' and c <= 'z':
82 v += c
83 elif c in ('-', '_', '.', '/', '+', '@'):
84 v += c
85 return 'upstream/%s' % v
86
87 @property
88 def PackageName(self):
89 n = self.project.name
90 if n.startswith('platform/'):
91 # This was not my finest moment...
92 #
93 n = n[len('platform/'):]
94 return n
95
96 def Import(self):
97 self._need_graft = False
98 if self.parent:
99 try:
100 self.project.bare_git.cat_file('-e', self.parent)
101 except GitError:
102 self._need_graft = True
103
104 gfi = GitCommand(self.project,
105 ['fast-import', '--force', '--quiet'],
106 bare = True,
107 provide_stdin = True)
108 try:
109 self._out = gfi.stdin
110
111 try:
112 self._UnpackFiles()
113 self._MakeCommit()
114 self._out.flush()
115 finally:
116 rc = gfi.Wait()
117 if rc != 0:
118 raise ImportError('fast-import failed')
119
120 if self._need_graft:
121 id = self._GraftCommit()
122 else:
123 id = self.project.bare_git.rev_parse('%s^0' % self._tempref)
124
125 if self.commit and self.commit != id:
126 raise ImportError('checksum mismatch: %s expected,'
127 ' %s imported' % (self.commit, id))
128
129 self._MakeTag(id)
130 return id
131 finally:
132 try:
133 self.project.bare_git.DeleteRef(self._tempref)
134 except GitError:
135 pass
136
137 def _PickUrl(self, failed):
138 u = map(lambda x: x.replace('%version%', self.version), self._urls)
139 for f in failed:
140 if f in u:
141 u.remove(f)
142 if len(u) == 0:
143 return None
144 return random.choice(u)
145
146 def _OpenUrl(self):
147 failed = {}
148 while True:
149 url = self._PickUrl(failed.keys())
150 if url is None:
151 why = 'Cannot download %s' % self.project.name
152
153 if failed:
154 why += ': one or more mirrors are down\n'
155 bad_urls = list(failed.keys())
156 bad_urls.sort()
157 for url in bad_urls:
158 why += ' %s: %s\n' % (url, failed[url])
159 else:
160 why += ': no mirror URLs'
161 raise ImportError(why)
162
163 print >>sys.stderr, "Getting %s ..." % url
164 try:
165 return urllib2.urlopen(url), url
166 except urllib2.HTTPError, e:
167 failed[url] = e.code
168 except urllib2.URLError, e:
169 failed[url] = e.reason[1]
170 except OSError, e:
171 failed[url] = e.strerror
172
173 def _UnpackFiles(self):
174 raise NotImplementedError
175
176 def _NextMark(self):
177 self._marks += 1
178 return self._marks
179
180 def _UnpackOneFile(self, mode, size, name, fd):
181 if stat.S_ISDIR(mode): # directory
182 return
183 else:
184 mode = self._CleanMode(mode, name)
185
186 old_name = name
187 name = self._CleanName(name)
188
189 if stat.S_ISLNK(mode) and self._remap:
190 # The link is relative to the old_name, and may need to
191 # be rewritten according to our remap rules if it goes
192 # up high enough in the tree structure.
193 #
194 dest = self._RewriteLink(fd.read(size), old_name, name)
195 fd = StringIO.StringIO(dest)
196 size = len(dest)
197
198 fi = _File(mode, name, self._NextMark())
199
200 self._out.write('blob\n')
201 self._out.write('mark :%d\n' % fi.mark)
202 self._out.write('data %d\n' % size)
203 while size > 0:
204 n = min(2048, size)
205 self._out.write(fd.read(n))
206 size -= n
207 self._out.write('\n')
208 self._files[fi.name] = fi
209
210 def _SetFileMode(self, name, mode):
211 if not stat.S_ISDIR(mode):
212 mode = self._CleanMode(mode, name)
213 name = self._CleanName(name)
214 try:
215 fi = self._files[name]
216 except KeyError:
217 raise ImportError('file %s was not unpacked' % name)
218 fi.mode = mode
219
220 def _RewriteLink(self, dest, relto_old, relto_new):
221 # Drop the last components of the symlink itself
222 # as the dest is relative to the directory its in.
223 #
224 relto_old = _TrimPath(relto_old)
225 relto_new = _TrimPath(relto_new)
226
227 # Resolve the link to be absolute from the top of
228 # the archive, so we can remap its destination.
229 #
230 while dest.find('/./') >= 0 or dest.find('//') >= 0:
231 dest = dest.replace('/./', '/')
232 dest = dest.replace('//', '/')
233
234 if dest.startswith('../') or dest.find('/../') > 0:
235 dest = _FoldPath('%s/%s' % (relto_old, dest))
236
237 for pm in self._remap:
238 if pm.Matches(dest):
239 dest = pm.Apply(dest)
240 break
241
242 dest, relto_new = _StripCommonPrefix(dest, relto_new)
243 while relto_new:
244 i = relto_new.find('/')
245 if i > 0:
246 relto_new = relto_new[i + 1:]
247 else:
248 relto_new = ''
249 dest = '../' + dest
250 return dest
251
252 def _CleanMode(self, mode, name):
253 if stat.S_ISREG(mode): # regular file
254 if (mode & 0111) == 0:
255 return 0644
256 else:
257 return 0755
258 elif stat.S_ISLNK(mode): # symlink
259 return stat.S_IFLNK
260 else:
261 raise ImportError('invalid mode %o in %s' % (mode, name))
262
263 def _CleanName(self, name):
264 old_name = name
265 for pm in self._remap:
266 if pm.Matches(name):
267 name = pm.Apply(name)
268 break
269 while name.startswith('/'):
270 name = name[1:]
271 if not name:
272 raise ImportError('path %s is empty after remap' % old_name)
273 if name.find('/./') >= 0 or name.find('/../') >= 0:
274 raise ImportError('path %s contains relative parts' % name)
275 return name
276
277 def _MakeCommit(self):
278 msg = '%s %s\n' % (self.PackageName, self.version)
279
280 self._out.write('commit %s\n' % self._tempref)
281 self._out.write('committer %s <%s> %d +0000\n' % (
282 self._user_name,
283 self._user_email,
284 self._user_when))
285 self._out.write('data %d\n' % len(msg))
286 self._out.write(msg)
287 self._out.write('\n')
288 if self.parent and not self._need_graft:
289 self._out.write('from %s^0\n' % self.parent)
290 self._out.write('deleteall\n')
291
292 for f in self._files.values():
293 self._out.write('M %o :%d %s\n' % (f.mode, f.mark, f.name))
294 self._out.write('\n')
295
296 def _GraftCommit(self):
297 raw = self.project.bare_git.cat_file('commit', self._tempref)
298 raw = raw.split("\n")
299 while raw[1].startswith('parent '):
300 del raw[1]
301 raw.insert(1, 'parent %s' % self.parent)
302 id = self._WriteObject('commit', "\n".join(raw))
303
304 graft_file = os.path.join(self.project.gitdir, 'info/grafts')
305 if os.path.exists(graft_file):
306 graft_list = open(graft_file, 'rb').read().split("\n")
307 if graft_list and graft_list[-1] == '':
308 del graft_list[-1]
309 else:
310 graft_list = []
311
312 exists = False
313 for line in graft_list:
314 if line == id:
315 exists = True
316 break
317
318 if not exists:
319 graft_list.append(id)
320 graft_list.append('')
321 fd = open(graft_file, 'wb')
322 fd.write("\n".join(graft_list))
323 fd.close()
324
325 return id
326
327 def _MakeTag(self, id):
328 name = self.TagName
329
330 raw = []
331 raw.append('object %s' % id)
332 raw.append('type commit')
333 raw.append('tag %s' % name)
334 raw.append('tagger %s <%s> %d +0000' % (
335 self._user_name,
336 self._user_email,
337 self._user_when))
338 raw.append('')
339 raw.append('%s %s\n' % (self.PackageName, self.version))
340
341 tagid = self._WriteObject('tag', "\n".join(raw))
342 self.project.bare_git.UpdateRef('refs/tags/%s' % name, tagid)
343
344 def _WriteObject(self, type, data):
345 wo = GitCommand(self.project,
346 ['hash-object', '-t', type, '-w', '--stdin'],
347 bare = True,
348 provide_stdin = True,
349 capture_stdout = True,
350 capture_stderr = True)
351 wo.stdin.write(data)
352 if wo.Wait() != 0:
353 raise GitError('cannot create %s from (%s)' % (type, data))
354 return wo.stdout[:-1]
355
356
357def _TrimPath(path):
358 i = path.rfind('/')
359 if i > 0:
360 path = path[0:i]
361 return ''
362
363def _StripCommonPrefix(a, b):
364 while True:
365 ai = a.find('/')
366 bi = b.find('/')
367 if ai > 0 and bi > 0 and a[0:ai] == b[0:bi]:
368 a = a[ai + 1:]
369 b = b[bi + 1:]
370 else:
371 break
372 return a, b
373
374def _FoldPath(path):
375 while True:
376 if path.startswith('../'):
377 return path
378
379 i = path.find('/../')
380 if i <= 0:
381 if path.startswith('/'):
382 return path[1:]
383 return path
384
385 lhs = path[0:i]
386 rhs = path[i + 4:]
387
388 i = lhs.rfind('/')
389 if i > 0:
390 path = lhs[0:i + 1] + rhs
391 else:
392 path = rhs
393
394class _File(object):
395 def __init__(self, mode, name, mark):
396 self.mode = mode
397 self.name = name
398 self.mark = mark
399
400
401class _PathMap(object):
402 def __init__(self, imp, old, new):
403 self._imp = imp
404 self._old = old
405 self._new = new
406
407 def _r(self, p):
408 return p.replace('%version%', self._imp.version)
409
410 @property
411 def old(self):
412 return self._r(self._old)
413
414 @property
415 def new(self):
416 return self._r(self._new)
417
418 def Matches(self, name):
419 return name.startswith(self.old)
420
421 def Apply(self, name):
422 return self.new + name[len(self.old):]
diff --git a/import_tar.py b/import_tar.py
new file mode 100644
index 00000000..d7ce14de
--- /dev/null
+++ b/import_tar.py
@@ -0,0 +1,206 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import bz2
17import stat
18import tarfile
19import zlib
20import StringIO
21
22from import_ext import ImportExternal
23from error import ImportError
24
25class ImportTar(ImportExternal):
26 """Streams a (optionally compressed) tar file from the network
27 directly into a Project's Git repository.
28 """
29 @classmethod
30 def CanAccept(cls, url):
31 """Can this importer read and unpack the data stored at url?
32 """
33 if url.endswith('.tar.gz') or url.endswith('.tgz'):
34 return True
35 if url.endswith('.tar.bz2'):
36 return True
37 if url.endswith('.tar'):
38 return True
39 return False
40
41 def _UnpackFiles(self):
42 url_fd, url = self._OpenUrl()
43 try:
44 if url.endswith('.tar.gz') or url.endswith('.tgz'):
45 tar_fd = _Gzip(url_fd)
46 elif url.endswith('.tar.bz2'):
47 tar_fd = _Bzip2(url_fd)
48 elif url.endswith('.tar'):
49 tar_fd = _Raw(url_fd)
50 else:
51 raise ImportError('non-tar file extension: %s' % url)
52
53 try:
54 tar = tarfile.TarFile(name = url,
55 mode = 'r',
56 fileobj = tar_fd)
57 try:
58 for entry in tar:
59 mode = entry.mode
60
61 if (mode & 0170000) == 0:
62 if entry.isdir():
63 mode |= stat.S_IFDIR
64 elif entry.isfile() or entry.islnk(): # hard links as files
65 mode |= stat.S_IFREG
66 elif entry.issym():
67 mode |= stat.S_IFLNK
68
69 if stat.S_ISLNK(mode): # symlink
70 data_fd = StringIO.StringIO(entry.linkname)
71 data_sz = len(entry.linkname)
72 elif stat.S_ISDIR(mode): # directory
73 data_fd = StringIO.StringIO('')
74 data_sz = 0
75 else:
76 data_fd = tar.extractfile(entry)
77 data_sz = entry.size
78
79 self._UnpackOneFile(mode, data_sz, entry.name, data_fd)
80 finally:
81 tar.close()
82 finally:
83 tar_fd.close()
84 finally:
85 url_fd.close()
86
87
88
89class _DecompressStream(object):
90 """file like object to decompress a tar stream
91 """
92 def __init__(self, fd):
93 self._fd = fd
94 self._pos = 0
95 self._buf = None
96
97 def tell(self):
98 return self._pos
99
100 def seek(self, offset):
101 d = offset - self._pos
102 if d > 0:
103 self.read(d)
104 elif d == 0:
105 pass
106 else:
107 raise NotImplementedError, 'seek backwards'
108
109 def close(self):
110 self._fd = None
111
112 def read(self, size = -1):
113 if not self._fd:
114 raise EOFError, 'Reached EOF'
115
116 r = []
117 try:
118 if size >= 0:
119 self._ReadChunk(r, size)
120 else:
121 while True:
122 self._ReadChunk(r, 2048)
123 except EOFError:
124 pass
125
126 if len(r) == 1:
127 r = r[0]
128 else:
129 r = ''.join(r)
130 self._pos += len(r)
131 return r
132
133 def _ReadChunk(self, r, size):
134 b = self._buf
135 try:
136 while size > 0:
137 if b is None or len(b) == 0:
138 b = self._Decompress(self._fd.read(2048))
139 continue
140
141 use = min(size, len(b))
142 r.append(b[:use])
143 b = b[use:]
144 size -= use
145 finally:
146 self._buf = b
147
148 def _Decompress(self, b):
149 raise NotImplementedError, '_Decompress'
150
151
152class _Raw(_DecompressStream):
153 """file like object for an uncompressed stream
154 """
155 def __init__(self, fd):
156 _DecompressStream.__init__(self, fd)
157
158 def _Decompress(self, b):
159 return b
160
161
162class _Bzip2(_DecompressStream):
163 """file like object to decompress a .bz2 stream
164 """
165 def __init__(self, fd):
166 _DecompressStream.__init__(self, fd)
167 self._bz = bz2.BZ2Decompressor()
168
169 def _Decompress(self, b):
170 return self._bz.decompress(b)
171
172
173_FHCRC, _FEXTRA, _FNAME, _FCOMMENT = 2, 4, 8, 16
174class _Gzip(_DecompressStream):
175 """file like object to decompress a .gz stream
176 """
177 def __init__(self, fd):
178 _DecompressStream.__init__(self, fd)
179 self._z = zlib.decompressobj(-zlib.MAX_WBITS)
180
181 magic = fd.read(2)
182 if magic != '\037\213':
183 raise IOError, 'Not a gzipped file'
184
185 method = ord(fd.read(1))
186 if method != 8:
187 raise IOError, 'Unknown compression method'
188
189 flag = ord(fd.read(1))
190 fd.read(6)
191
192 if flag & _FEXTRA:
193 xlen = ord(fd.read(1))
194 xlen += 256 * ord(fd.read(1))
195 fd.read(xlen)
196 if flag & _FNAME:
197 while fd.read(1) != '\0':
198 pass
199 if flag & _FCOMMENT:
200 while fd.read(1) != '\0':
201 pass
202 if flag & _FHCRC:
203 fd.read(2)
204
205 def _Decompress(self, b):
206 return self._z.decompress(b)
diff --git a/import_zip.py b/import_zip.py
new file mode 100644
index 00000000..08aff326
--- /dev/null
+++ b/import_zip.py
@@ -0,0 +1,345 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import stat
17import struct
18import zlib
19import cStringIO
20
21from import_ext import ImportExternal
22from error import ImportError
23
24class ImportZip(ImportExternal):
25 """Streams a zip file from the network directly into a Project's
26 Git repository.
27 """
28 @classmethod
29 def CanAccept(cls, url):
30 """Can this importer read and unpack the data stored at url?
31 """
32 if url.endswith('.zip') or url.endswith('.jar'):
33 return True
34 return False
35
36 def _UnpackFiles(self):
37 url_fd, url = self._OpenUrl()
38 try:
39 if not self.__class__.CanAccept(url):
40 raise ImportError('non-zip file extension: %s' % url)
41
42 zip = _ZipFile(url_fd)
43 for entry in zip.FileRecords():
44 data = zip.Open(entry).read()
45 sz = len(data)
46
47 if data and _SafeCRLF(data):
48 data = data.replace('\r\n', '\n')
49 sz = len(data)
50
51 fd = cStringIO.StringIO(data)
52 self._UnpackOneFile(entry.mode, sz, entry.name, fd)
53 zip.Close(entry)
54
55 for entry in zip.CentralDirectory():
56 self._SetFileMode(entry.name, entry.mode)
57
58 zip.CheckTail()
59 finally:
60 url_fd.close()
61
62
63def _SafeCRLF(data):
64 """Is it reasonably safe to perform a CRLF->LF conversion?
65
66 If the stream contains a NUL byte it is likely binary,
67 and thus a CRLF->LF conversion may damage the stream.
68
69 If the only NUL is in the last position of the stream,
70 but it otherwise can do a CRLF<->LF conversion we do
71 the CRLF conversion anyway. At least one source ZIP
72 file has this structure in its source code.
73
74 If every occurrance of a CR and LF is paired up as a
75 CRLF pair then the conversion is safely bi-directional.
76 s/\r\n/\n/g == s/\n/\r\\n/g can convert between them.
77 """
78 nul = data.find('\0')
79 if 0 <= nul and nul < (len(data) - 1):
80 return False
81
82 n_lf = 0
83 last = 0
84 while True:
85 lf = data.find('\n', last)
86 if lf < 0:
87 break
88 if lf == 0 or data[lf - 1] != '\r':
89 return False
90 last = lf + 1
91 n_lf += 1
92 return n_lf > 0
93
94class _ZipFile(object):
95 """Streaming iterator to parse a zip file on the fly.
96 """
97 def __init__(self, fd):
98 self._fd = _UngetStream(fd)
99
100 def FileRecords(self):
101 return _FileIter(self._fd)
102
103 def CentralDirectory(self):
104 return _CentIter(self._fd)
105
106 def CheckTail(self):
107 type_buf = self._fd.read(4)
108 type = struct.unpack('<I', type_buf)[0]
109 if type != 0x06054b50: # end of central directory
110 raise ImportError('zip record %x unsupported' % type)
111
112 def Open(self, entry):
113 if entry.is_compressed:
114 return _InflateStream(self._fd)
115 else:
116 if entry.has_trailer:
117 raise ImportError('unable to extract streamed zip')
118 return _FixedLengthStream(self._fd, entry.uncompressed_size)
119
120 def Close(self, entry):
121 if entry.has_trailer:
122 type = struct.unpack('<I', self._fd.read(4))[0]
123 if type == 0x08074b50:
124 # Not a formal type marker, but commonly seen in zips
125 # as the data descriptor signature.
126 #
127 struct.unpack('<3I', self._fd.read(12))
128 else:
129 # No signature for the data descriptor, so read the
130 # remaining fields out of the stream
131 #
132 self._fd.read(8)
133
134
135class _FileIter(object):
136 def __init__(self, fd):
137 self._fd = fd
138
139 def __iter__(self):
140 return self
141
142 def next(self):
143 fd = self._fd
144
145 type_buf = fd.read(4)
146 type = struct.unpack('<I', type_buf)[0]
147
148 if type != 0x04034b50: # local file header
149 fd.unread(type_buf)
150 raise StopIteration()
151
152 rec = _FileHeader(fd.read(26))
153 rec.name = fd.read(rec.name_len)
154 fd.read(rec.extra_len)
155
156 if rec.name.endswith('/'):
157 rec.name = rec.name[:-1]
158 rec.mode = stat.S_IFDIR | 0777
159 return rec
160
161
162class _FileHeader(object):
163 """Information about a single file in the archive.
164 0 version needed to extract 2 bytes
165 1 general purpose bit flag 2 bytes
166 2 compression method 2 bytes
167 3 last mod file time 2 bytes
168 4 last mod file date 2 bytes
169 5 crc-32 4 bytes
170 6 compressed size 4 bytes
171 7 uncompressed size 4 bytes
172 8 file name length 2 bytes
173 9 extra field length 2 bytes
174 """
175 def __init__(self, raw_bin):
176 rec = struct.unpack('<5H3I2H', raw_bin)
177
178 if rec[2] == 8:
179 self.is_compressed = True
180 elif rec[2] == 0:
181 self.is_compressed = False
182 else:
183 raise ImportError('unrecognized compression format')
184
185 if rec[1] & (1 << 3):
186 self.has_trailer = True
187 else:
188 self.has_trailer = False
189
190 self.compressed_size = rec[6]
191 self.uncompressed_size = rec[7]
192 self.name_len = rec[8]
193 self.extra_len = rec[9]
194 self.mode = stat.S_IFREG | 0644
195
196
197class _CentIter(object):
198 def __init__(self, fd):
199 self._fd = fd
200
201 def __iter__(self):
202 return self
203
204 def next(self):
205 fd = self._fd
206
207 type_buf = fd.read(4)
208 type = struct.unpack('<I', type_buf)[0]
209
210 if type != 0x02014b50: # central directory
211 fd.unread(type_buf)
212 raise StopIteration()
213
214 rec = _CentHeader(fd.read(42))
215 rec.name = fd.read(rec.name_len)
216 fd.read(rec.extra_len)
217 fd.read(rec.comment_len)
218
219 if rec.name.endswith('/'):
220 rec.name = rec.name[:-1]
221 rec.mode = stat.S_IFDIR | 0777
222 return rec
223
224
225class _CentHeader(object):
226 """Information about a single file in the archive.
227 0 version made by 2 bytes
228 1 version needed to extract 2 bytes
229 2 general purpose bit flag 2 bytes
230 3 compression method 2 bytes
231 4 last mod file time 2 bytes
232 5 last mod file date 2 bytes
233 6 crc-32 4 bytes
234 7 compressed size 4 bytes
235 8 uncompressed size 4 bytes
236 9 file name length 2 bytes
237 10 extra field length 2 bytes
238 11 file comment length 2 bytes
239 12 disk number start 2 bytes
240 13 internal file attributes 2 bytes
241 14 external file attributes 4 bytes
242 15 relative offset of local header 4 bytes
243 """
244 def __init__(self, raw_bin):
245 rec = struct.unpack('<6H3I5H2I', raw_bin)
246 self.name_len = rec[9]
247 self.extra_len = rec[10]
248 self.comment_len = rec[11]
249
250 if (rec[0] & 0xff00) == 0x0300: # UNIX
251 self.mode = rec[14] >> 16
252 else:
253 self.mode = stat.S_IFREG | 0644
254
255
256class _UngetStream(object):
257 """File like object to read and rewind a stream.
258 """
259 def __init__(self, fd):
260 self._fd = fd
261 self._buf = None
262
263 def read(self, size = -1):
264 r = []
265 try:
266 if size >= 0:
267 self._ReadChunk(r, size)
268 else:
269 while True:
270 self._ReadChunk(r, 2048)
271 except EOFError:
272 pass
273
274 if len(r) == 1:
275 return r[0]
276 return ''.join(r)
277
278 def unread(self, buf):
279 b = self._buf
280 if b is None or len(b) == 0:
281 self._buf = buf
282 else:
283 self._buf = buf + b
284
285 def _ReadChunk(self, r, size):
286 b = self._buf
287 try:
288 while size > 0:
289 if b is None or len(b) == 0:
290 b = self._Inflate(self._fd.read(2048))
291 if not b:
292 raise EOFError()
293 continue
294
295 use = min(size, len(b))
296 r.append(b[:use])
297 b = b[use:]
298 size -= use
299 finally:
300 self._buf = b
301
302 def _Inflate(self, b):
303 return b
304
305
306class _FixedLengthStream(_UngetStream):
307 """File like object to read a fixed length stream.
308 """
309 def __init__(self, fd, have):
310 _UngetStream.__init__(self, fd)
311 self._have = have
312
313 def _Inflate(self, b):
314 n = self._have
315 if n == 0:
316 self._fd.unread(b)
317 return None
318
319 if len(b) > n:
320 self._fd.unread(b[n:])
321 b = b[:n]
322 self._have -= len(b)
323 return b
324
325
326class _InflateStream(_UngetStream):
327 """Inflates the stream as it reads input.
328 """
329 def __init__(self, fd):
330 _UngetStream.__init__(self, fd)
331 self._z = zlib.decompressobj(-zlib.MAX_WBITS)
332
333 def _Inflate(self, b):
334 z = self._z
335 if not z:
336 self._fd.unread(b)
337 return None
338
339 b = z.decompress(b)
340 if z.unconsumed_tail != '':
341 self._fd.unread(z.unconsumed_tail)
342 elif z.unused_data != '':
343 self._fd.unread(z.unused_data)
344 self._z = None
345 return b
diff --git a/main.py b/main.py
new file mode 100755
index 00000000..56092990
--- /dev/null
+++ b/main.py
@@ -0,0 +1,198 @@
1#!/bin/sh
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17magic='--calling-python-from-/bin/sh--'
18"""exec" python2.4 -E "$0" "$@" """#$magic"
19if __name__ == '__main__':
20 import sys
21 if sys.argv[-1] == '#%s' % magic:
22 del sys.argv[-1]
23del magic
24
25import optparse
26import os
27import re
28import sys
29
30from command import InteractiveCommand, PagedCommand
31from error import NoSuchProjectError
32from error import RepoChangedException
33from manifest import Manifest
34from pager import RunPager
35
36from subcmds import all as all_commands
37
38global_options = optparse.OptionParser(
39 usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]"
40 )
41global_options.add_option('-p', '--paginate',
42 dest='pager', action='store_true',
43 help='display command output in the pager')
44global_options.add_option('--no-pager',
45 dest='no_pager', action='store_true',
46 help='disable the pager')
47
48class _Repo(object):
49 def __init__(self, repodir):
50 self.repodir = repodir
51 self.commands = all_commands
52
53 def _Run(self, argv):
54 name = None
55 glob = []
56
57 for i in xrange(0, len(argv)):
58 if not argv[i].startswith('-'):
59 name = argv[i]
60 if i > 0:
61 glob = argv[:i]
62 argv = argv[i + 1:]
63 break
64 if not name:
65 glob = argv
66 name = 'help'
67 argv = []
68 gopts, gargs = global_options.parse_args(glob)
69
70 try:
71 cmd = self.commands[name]
72 except KeyError:
73 print >>sys.stderr,\
74 "repo: '%s' is not a repo command. See 'repo help'."\
75 % name
76 sys.exit(1)
77
78 cmd.repodir = self.repodir
79 cmd.manifest = Manifest(cmd.repodir)
80
81 if not gopts.no_pager and not isinstance(cmd, InteractiveCommand):
82 config = cmd.manifest.globalConfig
83 if gopts.pager:
84 use_pager = True
85 else:
86 use_pager = config.GetBoolean('pager.%s' % name)
87 if use_pager is None:
88 use_pager = isinstance(cmd, PagedCommand)
89 if use_pager:
90 RunPager(config)
91
92 copts, cargs = cmd.OptionParser.parse_args(argv)
93 try:
94 cmd.Execute(copts, cargs)
95 except NoSuchProjectError, e:
96 if e.name:
97 print >>sys.stderr, 'error: project %s not found' % e.name
98 else:
99 print >>sys.stderr, 'error: no project in current directory'
100 sys.exit(1)
101
102def _MyWrapperPath():
103 return os.path.join(os.path.dirname(__file__), 'repo')
104
105def _CurrentWrapperVersion():
106 VERSION = None
107 pat = re.compile(r'^VERSION *=')
108 fd = open(_MyWrapperPath())
109 for line in fd:
110 if pat.match(line):
111 fd.close()
112 exec line
113 return VERSION
114 raise NameError, 'No VERSION in repo script'
115
116def _CheckWrapperVersion(ver, repo_path):
117 if not repo_path:
118 repo_path = '~/bin/repo'
119
120 if not ver:
121 print >>sys.stderr, 'no --wrapper-version argument'
122 sys.exit(1)
123
124 exp = _CurrentWrapperVersion()
125 ver = tuple(map(lambda x: int(x), ver.split('.')))
126 if len(ver) == 1:
127 ver = (0, ver[0])
128
129 if exp[0] > ver[0] or ver < (0, 4):
130 exp_str = '.'.join(map(lambda x: str(x), exp))
131 print >>sys.stderr, """
132!!! A new repo command (%5s) is available. !!!
133!!! You must upgrade before you can continue: !!!
134
135 cp %s %s
136""" % (exp_str, _MyWrapperPath(), repo_path)
137 sys.exit(1)
138
139 if exp > ver:
140 exp_str = '.'.join(map(lambda x: str(x), exp))
141 print >>sys.stderr, """
142... A new repo command (%5s) is available.
143... You should upgrade soon:
144
145 cp %s %s
146""" % (exp_str, _MyWrapperPath(), repo_path)
147
148def _CheckRepoDir(dir):
149 if not dir:
150 print >>sys.stderr, 'no --repo-dir argument'
151 sys.exit(1)
152
153def _PruneOptions(argv, opt):
154 i = 0
155 while i < len(argv):
156 a = argv[i]
157 if a == '--':
158 break
159 if a.startswith('--'):
160 eq = a.find('=')
161 if eq > 0:
162 a = a[0:eq]
163 if not opt.has_option(a):
164 del argv[i]
165 continue
166 i += 1
167
168def _Main(argv):
169 opt = optparse.OptionParser(usage="repo wrapperinfo -- ...")
170 opt.add_option("--repo-dir", dest="repodir",
171 help="path to .repo/")
172 opt.add_option("--wrapper-version", dest="wrapper_version",
173 help="version of the wrapper script")
174 opt.add_option("--wrapper-path", dest="wrapper_path",
175 help="location of the wrapper script")
176 _PruneOptions(argv, opt)
177 opt, argv = opt.parse_args(argv)
178
179 _CheckWrapperVersion(opt.wrapper_version, opt.wrapper_path)
180 _CheckRepoDir(opt.repodir)
181
182 repo = _Repo(opt.repodir)
183 try:
184 repo._Run(argv)
185 except KeyboardInterrupt:
186 sys.exit(1)
187 except RepoChangedException:
188 # If the repo or manifest changed, re-exec ourselves.
189 #
190 try:
191 os.execv(__file__, sys.argv)
192 except OSError, e:
193 print >>sys.stderr, 'fatal: cannot restart repo after upgrade'
194 print >>sys.stderr, 'fatal: %s' % e
195 sys.exit(128)
196
197if __name__ == '__main__':
198 _Main(sys.argv[1:])
diff --git a/manifest.py b/manifest.py
new file mode 100644
index 00000000..45b0f9a5
--- /dev/null
+++ b/manifest.py
@@ -0,0 +1,338 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import sys
18import xml.dom.minidom
19
20from editor import Editor
21from git_config import GitConfig, IsId
22from import_tar import ImportTar
23from import_zip import ImportZip
24from project import Project, MetaProject, R_TAGS
25from remote import Remote
26from error import ManifestParseError
27
28MANIFEST_FILE_NAME = 'manifest.xml'
29
30class _Default(object):
31 """Project defaults within the manifest."""
32
33 revision = None
34 remote = None
35
36
37class Manifest(object):
38 """manages the repo configuration file"""
39
40 def __init__(self, repodir):
41 self.repodir = os.path.abspath(repodir)
42 self.topdir = os.path.dirname(self.repodir)
43 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
44
45 self.globalConfig = GitConfig.ForUser()
46 Editor.globalConfig = self.globalConfig
47
48 self.repoProject = MetaProject(self, 'repo',
49 gitdir = os.path.join(repodir, 'repo/.git'),
50 worktree = os.path.join(repodir, 'repo'))
51
52 wt = os.path.join(repodir, 'manifests')
53 gd_new = os.path.join(repodir, 'manifests.git')
54 gd_old = os.path.join(wt, '.git')
55 if os.path.exists(gd_new) or not os.path.exists(gd_old):
56 gd = gd_new
57 else:
58 gd = gd_old
59 self.manifestProject = MetaProject(self, 'manifests',
60 gitdir = gd,
61 worktree = wt)
62
63 self._Unload()
64
65 def Link(self, name):
66 """Update the repo metadata to use a different manifest.
67 """
68 path = os.path.join(self.manifestProject.worktree, name)
69 if not os.path.isfile(path):
70 raise ManifestParseError('manifest %s not found' % name)
71
72 old = self.manifestFile
73 try:
74 self.manifestFile = path
75 self._Unload()
76 self._Load()
77 finally:
78 self.manifestFile = old
79
80 try:
81 if os.path.exists(self.manifestFile):
82 os.remove(self.manifestFile)
83 os.symlink('manifests/%s' % name, self.manifestFile)
84 except OSError, e:
85 raise ManifestParseError('cannot link manifest %s' % name)
86
87 @property
88 def projects(self):
89 self._Load()
90 return self._projects
91
92 @property
93 def remotes(self):
94 self._Load()
95 return self._remotes
96
97 @property
98 def default(self):
99 self._Load()
100 return self._default
101
102 def _Unload(self):
103 self._loaded = False
104 self._projects = {}
105 self._remotes = {}
106 self._default = None
107 self.branch = None
108
109 def _Load(self):
110 if not self._loaded:
111 self._ParseManifest()
112 self._loaded = True
113
114 def _ParseManifest(self):
115 root = xml.dom.minidom.parse(self.manifestFile)
116 if not root or not root.childNodes:
117 raise ManifestParseError, \
118 "no root node in %s" % \
119 self.manifestFile
120
121 config = root.childNodes[0]
122 if config.nodeName != 'manifest':
123 raise ManifestParseError, \
124 "no <manifest> in %s" % \
125 self.manifestFile
126
127 self.branch = config.getAttribute('branch')
128 if not self.branch:
129 self.branch = 'default'
130
131 for node in config.childNodes:
132 if node.nodeName == 'remote':
133 remote = self._ParseRemote(node)
134 if self._remotes.get(remote.name):
135 raise ManifestParseError, \
136 'duplicate remote %s in %s' % \
137 (remote.name, self.manifestFile)
138 self._remotes[remote.name] = remote
139
140 for node in config.childNodes:
141 if node.nodeName == 'default':
142 if self._default is not None:
143 raise ManifestParseError, \
144 'duplicate default in %s' % \
145 (self.manifestFile)
146 self._default = self._ParseDefault(node)
147 if self._default is None:
148 self._default = _Default()
149
150 for node in config.childNodes:
151 if node.nodeName == 'project':
152 project = self._ParseProject(node)
153 if self._projects.get(project.name):
154 raise ManifestParseError, \
155 'duplicate project %s in %s' % \
156 (project.name, self.manifestFile)
157 self._projects[project.name] = project
158
159 def _ParseRemote(self, node):
160 """
161 reads a <remote> element from the manifest file
162 """
163 name = self._reqatt(node, 'name')
164 fetch = self._reqatt(node, 'fetch')
165 review = node.getAttribute('review')
166
167 r = Remote(name=name,
168 fetch=fetch,
169 review=review)
170
171 for n in node.childNodes:
172 if n.nodeName == 'require':
173 r.requiredCommits.append(self._reqatt(n, 'commit'))
174
175 return r
176
177 def _ParseDefault(self, node):
178 """
179 reads a <default> element from the manifest file
180 """
181 d = _Default()
182 d.remote = self._get_remote(node)
183 d.revision = node.getAttribute('revision')
184 return d
185
186 def _ParseProject(self, node):
187 """
188 reads a <project> element from the manifest file
189 """
190 name = self._reqatt(node, 'name')
191
192 remote = self._get_remote(node)
193 if remote is None:
194 remote = self._default.remote
195 if remote is None:
196 raise ManifestParseError, \
197 "no remote for project %s within %s" % \
198 (name, self.manifestFile)
199
200 revision = node.getAttribute('revision')
201 if not revision:
202 revision = self._default.revision
203 if not revision:
204 raise ManifestParseError, \
205 "no revision for project %s within %s" % \
206 (name, self.manifestFile)
207
208 path = node.getAttribute('path')
209 if not path:
210 path = name
211 if path.startswith('/'):
212 raise ManifestParseError, \
213 "project %s path cannot be absolute in %s" % \
214 (name, self.manifestFile)
215
216 worktree = os.path.join(self.topdir, path)
217 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
218
219 project = Project(manifest = self,
220 name = name,
221 remote = remote,
222 gitdir = gitdir,
223 worktree = worktree,
224 relpath = path,
225 revision = revision)
226
227 for n in node.childNodes:
228 if n.nodeName == 'remote':
229 r = self._ParseRemote(n)
230 if project.extraRemotes.get(r.name) \
231 or project.remote.name == r.name:
232 raise ManifestParseError, \
233 'duplicate remote %s in project %s in %s' % \
234 (r.name, project.name, self.manifestFile)
235 project.extraRemotes[r.name] = r
236 elif n.nodeName == 'copyfile':
237 self._ParseCopyFile(project, n)
238
239 to_resolve = []
240 by_version = {}
241
242 for n in node.childNodes:
243 if n.nodeName == 'import':
244 self._ParseImport(project, n, to_resolve, by_version)
245
246 for pair in to_resolve:
247 sn, pr = pair
248 try:
249 sn.SetParent(by_version[pr].commit)
250 except KeyError:
251 raise ManifestParseError, \
252 'snapshot %s not in project %s in %s' % \
253 (pr, project.name, self.manifestFile)
254
255 return project
256
257 def _ParseImport(self, project, import_node, to_resolve, by_version):
258 first_url = None
259 for node in import_node.childNodes:
260 if node.nodeName == 'mirror':
261 first_url = self._reqatt(node, 'url')
262 break
263 if not first_url:
264 raise ManifestParseError, \
265 'mirror url required for project %s in %s' % \
266 (project.name, self.manifestFile)
267
268 imp = None
269 for cls in [ImportTar, ImportZip]:
270 if cls.CanAccept(first_url):
271 imp = cls()
272 break
273 if not imp:
274 raise ManifestParseError, \
275 'snapshot %s unsupported for project %s in %s' % \
276 (first_url, project.name, self.manifestFile)
277
278 imp.SetProject(project)
279
280 for node in import_node.childNodes:
281 if node.nodeName == 'remap':
282 old = node.getAttribute('strip')
283 new = node.getAttribute('insert')
284 imp.RemapPath(old, new)
285
286 elif node.nodeName == 'mirror':
287 imp.AddUrl(self._reqatt(node, 'url'))
288
289 for node in import_node.childNodes:
290 if node.nodeName == 'snapshot':
291 sn = imp.Clone()
292 sn.SetVersion(self._reqatt(node, 'version'))
293 sn.SetCommit(node.getAttribute('check'))
294
295 pr = node.getAttribute('prior')
296 if pr:
297 if IsId(pr):
298 sn.SetParent(pr)
299 else:
300 to_resolve.append((sn, pr))
301
302 rev = R_TAGS + sn.TagName
303
304 if rev in project.snapshots:
305 raise ManifestParseError, \
306 'duplicate snapshot %s for project %s in %s' % \
307 (sn.version, project.name, self.manifestFile)
308 project.snapshots[rev] = sn
309 by_version[sn.version] = sn
310
311 def _ParseCopyFile(self, project, node):
312 src = self._reqatt(node, 'src')
313 dest = self._reqatt(node, 'dest')
314 # src is project relative, and dest is relative to the top of the tree
315 project.AddCopyFile(src, os.path.join(self.topdir, dest))
316
317 def _get_remote(self, node):
318 name = node.getAttribute('remote')
319 if not name:
320 return None
321
322 v = self._remotes.get(name)
323 if not v:
324 raise ManifestParseError, \
325 "remote %s not defined in %s" % \
326 (name, self.manifestFile)
327 return v
328
329 def _reqatt(self, node, attname):
330 """
331 reads a required attribute from the node.
332 """
333 v = node.getAttribute(attname)
334 if not v:
335 raise ManifestParseError, \
336 "no %s in <%s> within %s" % \
337 (attname, node.nodeName, self.manifestFile)
338 return v
diff --git a/pager.py b/pager.py
new file mode 100755
index 00000000..320131cd
--- /dev/null
+++ b/pager.py
@@ -0,0 +1,84 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import select
18import sys
19
20active = False
21
22def RunPager(globalConfig):
23 global active
24
25 if not os.isatty(0):
26 return
27 pager = _SelectPager(globalConfig)
28 if pager == '' or pager == 'cat':
29 return
30
31 # This process turns into the pager; a child it forks will
32 # do the real processing and output back to the pager. This
33 # is necessary to keep the pager in control of the tty.
34 #
35 try:
36 r, w = os.pipe()
37 pid = os.fork()
38 if not pid:
39 os.dup2(w, 1)
40 os.dup2(w, 2)
41 os.close(r)
42 os.close(w)
43 active = True
44 return
45
46 os.dup2(r, 0)
47 os.close(r)
48 os.close(w)
49
50 _BecomePager(pager)
51 except Exception:
52 print >>sys.stderr, "fatal: cannot start pager '%s'" % pager
53 os.exit(255)
54
55def _SelectPager(globalConfig):
56 try:
57 return os.environ['GIT_PAGER']
58 except KeyError:
59 pass
60
61 pager = globalConfig.GetString('core.pager')
62 if pager:
63 return pager
64
65 try:
66 return os.environ['PAGER']
67 except KeyError:
68 pass
69
70 return 'less'
71
72def _BecomePager(pager):
73 # Delaying execution of the pager until we have output
74 # ready works around a long-standing bug in popularly
75 # available versions of 'less', a better 'more'.
76 #
77 a, b, c = select.select([0], [], [0])
78
79 os.environ['LESS'] = 'FRSX'
80
81 try:
82 os.execvp(pager, [pager])
83 except OSError, e:
84 os.execv('/bin/sh', ['sh', '-c', pager])
diff --git a/project.py b/project.py
new file mode 100644
index 00000000..7c0c58f9
--- /dev/null
+++ b/project.py
@@ -0,0 +1,1058 @@
1# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import filecmp
16import os
17import re
18import shutil
19import stat
20import sys
21import urllib2
22
23from color import Coloring
24from git_command import GitCommand
25from git_config import GitConfig, IsId
26from gerrit_upload import UploadBundle
27from error import GitError, ImportError, UploadError
28from remote import Remote
29from codereview import proto_client
30
31HEAD = 'HEAD'
32R_HEADS = 'refs/heads/'
33R_TAGS = 'refs/tags/'
34R_PUB = 'refs/published/'
35R_M = 'refs/remotes/m/'
36
37def _warn(fmt, *args):
38 msg = fmt % args
39 print >>sys.stderr, 'warn: %s' % msg
40
41def _info(fmt, *args):
42 msg = fmt % args
43 print >>sys.stderr, 'info: %s' % msg
44
45def not_rev(r):
46 return '^' + r
47
48class ReviewableBranch(object):
49 _commit_cache = None
50
51 def __init__(self, project, branch, base):
52 self.project = project
53 self.branch = branch
54 self.base = base
55
56 @property
57 def name(self):
58 return self.branch.name
59
60 @property
61 def commits(self):
62 if self._commit_cache is None:
63 self._commit_cache = self.project.bare_git.rev_list(
64 '--abbrev=8',
65 '--abbrev-commit',
66 '--pretty=oneline',
67 '--reverse',
68 '--date-order',
69 not_rev(self.base),
70 R_HEADS + self.name,
71 '--')
72 return self._commit_cache
73
74 @property
75 def date(self):
76 return self.project.bare_git.log(
77 '--pretty=format:%cd',
78 '-n', '1',
79 R_HEADS + self.name,
80 '--')
81
82 def UploadForReview(self):
83 self.project.UploadForReview(self.name)
84
85 @property
86 def tip_url(self):
87 me = self.project.GetBranch(self.name)
88 commit = self.project.bare_git.rev_parse(R_HEADS + self.name)
89 return 'http://%s/r/%s' % (me.remote.review, commit[0:12])
90
91
92class StatusColoring(Coloring):
93 def __init__(self, config):
94 Coloring.__init__(self, config, 'status')
95 self.project = self.printer('header', attr = 'bold')
96 self.branch = self.printer('header', attr = 'bold')
97 self.nobranch = self.printer('nobranch', fg = 'red')
98
99 self.added = self.printer('added', fg = 'green')
100 self.changed = self.printer('changed', fg = 'red')
101 self.untracked = self.printer('untracked', fg = 'red')
102
103
104class DiffColoring(Coloring):
105 def __init__(self, config):
106 Coloring.__init__(self, config, 'diff')
107 self.project = self.printer('header', attr = 'bold')
108
109
110class _CopyFile:
111 def __init__(self, src, dest):
112 self.src = src
113 self.dest = dest
114
115 def _Copy(self):
116 src = self.src
117 dest = self.dest
118 # copy file if it does not exist or is out of date
119 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
120 try:
121 # remove existing file first, since it might be read-only
122 if os.path.exists(dest):
123 os.remove(dest)
124 shutil.copy(src, dest)
125 # make the file read-only
126 mode = os.stat(dest)[stat.ST_MODE]
127 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
128 os.chmod(dest, mode)
129 except IOError:
130 print >>sys.stderr, \
131 'error: Cannot copy file %s to %s' \
132 % (src, dest)
133
134
135class Project(object):
136 def __init__(self,
137 manifest,
138 name,
139 remote,
140 gitdir,
141 worktree,
142 relpath,
143 revision):
144 self.manifest = manifest
145 self.name = name
146 self.remote = remote
147 self.gitdir = gitdir
148 self.worktree = worktree
149 self.relpath = relpath
150 self.revision = revision
151 self.snapshots = {}
152 self.extraRemotes = {}
153 self.copyfiles = []
154 self.config = GitConfig.ForRepository(
155 gitdir = self.gitdir,
156 defaults = self.manifest.globalConfig)
157
158 self.work_git = self._GitGetByExec(self, bare=False)
159 self.bare_git = self._GitGetByExec(self, bare=True)
160
161 @property
162 def Exists(self):
163 return os.path.isdir(self.gitdir)
164
165 @property
166 def CurrentBranch(self):
167 """Obtain the name of the currently checked out branch.
168 The branch name omits the 'refs/heads/' prefix.
169 None is returned if the project is on a detached HEAD.
170 """
171 try:
172 b = self.work_git.GetHead()
173 except GitError:
174 return None
175 if b.startswith(R_HEADS):
176 return b[len(R_HEADS):]
177 return None
178
179 def IsDirty(self, consider_untracked=True):
180 """Is the working directory modified in some way?
181 """
182 self.work_git.update_index('-q',
183 '--unmerged',
184 '--ignore-missing',
185 '--refresh')
186 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
187 return True
188 if self.work_git.DiffZ('diff-files'):
189 return True
190 if consider_untracked and self.work_git.LsOthers():
191 return True
192 return False
193
194 _userident_name = None
195 _userident_email = None
196
197 @property
198 def UserName(self):
199 """Obtain the user's personal name.
200 """
201 if self._userident_name is None:
202 self._LoadUserIdentity()
203 return self._userident_name
204
205 @property
206 def UserEmail(self):
207 """Obtain the user's email address. This is very likely
208 to be their Gerrit login.
209 """
210 if self._userident_email is None:
211 self._LoadUserIdentity()
212 return self._userident_email
213
214 def _LoadUserIdentity(self):
215 u = self.bare_git.var('GIT_COMMITTER_IDENT')
216 m = re.compile("^(.*) <([^>]*)> ").match(u)
217 if m:
218 self._userident_name = m.group(1)
219 self._userident_email = m.group(2)
220 else:
221 self._userident_name = ''
222 self._userident_email = ''
223
224 def GetRemote(self, name):
225 """Get the configuration for a single remote.
226 """
227 return self.config.GetRemote(name)
228
229 def GetBranch(self, name):
230 """Get the configuration for a single branch.
231 """
232 return self.config.GetBranch(name)
233
234
235## Status Display ##
236
237 def PrintWorkTreeStatus(self):
238 """Prints the status of the repository to stdout.
239 """
240 if not os.path.isdir(self.worktree):
241 print ''
242 print 'project %s/' % self.relpath
243 print ' missing (run "repo sync")'
244 return
245
246 self.work_git.update_index('-q',
247 '--unmerged',
248 '--ignore-missing',
249 '--refresh')
250 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
251 df = self.work_git.DiffZ('diff-files')
252 do = self.work_git.LsOthers()
253 if not di and not df and not do:
254 return
255
256 out = StatusColoring(self.config)
257 out.project('project %-40s', self.relpath + '/')
258
259 branch = self.CurrentBranch
260 if branch is None:
261 out.nobranch('(*** NO BRANCH ***)')
262 else:
263 out.branch('branch %s', branch)
264 out.nl()
265
266 paths = list()
267 paths.extend(di.keys())
268 paths.extend(df.keys())
269 paths.extend(do)
270
271 paths = list(set(paths))
272 paths.sort()
273
274 for p in paths:
275 try: i = di[p]
276 except KeyError: i = None
277
278 try: f = df[p]
279 except KeyError: f = None
280
281 if i: i_status = i.status.upper()
282 else: i_status = '-'
283
284 if f: f_status = f.status.lower()
285 else: f_status = '-'
286
287 if i and i.src_path:
288 line = ' %s%s\t%s => (%s%%)' % (i_status, f_status,
289 i.src_path, p, i.level)
290 else:
291 line = ' %s%s\t%s' % (i_status, f_status, p)
292
293 if i and not f:
294 out.added('%s', line)
295 elif (i and f) or (not i and f):
296 out.changed('%s', line)
297 elif not i and not f:
298 out.untracked('%s', line)
299 else:
300 out.write('%s', line)
301 out.nl()
302
303 def PrintWorkTreeDiff(self):
304 """Prints the status of the repository to stdout.
305 """
306 out = DiffColoring(self.config)
307 cmd = ['diff']
308 if out.is_on:
309 cmd.append('--color')
310 cmd.append(HEAD)
311 cmd.append('--')
312 p = GitCommand(self,
313 cmd,
314 capture_stdout = True,
315 capture_stderr = True)
316 has_diff = False
317 for line in p.process.stdout:
318 if not has_diff:
319 out.nl()
320 out.project('project %s/' % self.relpath)
321 out.nl()
322 has_diff = True
323 print line[:-1]
324 p.Wait()
325
326
327## Publish / Upload ##
328
329 def WasPublished(self, branch):
330 """Was the branch published (uploaded) for code review?
331 If so, returns the SHA-1 hash of the last published
332 state for the branch.
333 """
334 try:
335 return self.bare_git.rev_parse(R_PUB + branch)
336 except GitError:
337 return None
338
339 def CleanPublishedCache(self):
340 """Prunes any stale published refs.
341 """
342 heads = set()
343 canrm = {}
344 for name, id in self._allrefs.iteritems():
345 if name.startswith(R_HEADS):
346 heads.add(name)
347 elif name.startswith(R_PUB):
348 canrm[name] = id
349
350 for name, id in canrm.iteritems():
351 n = name[len(R_PUB):]
352 if R_HEADS + n not in heads:
353 self.bare_git.DeleteRef(name, id)
354
355 def GetUploadableBranches(self):
356 """List any branches which can be uploaded for review.
357 """
358 heads = {}
359 pubed = {}
360
361 for name, id in self._allrefs.iteritems():
362 if name.startswith(R_HEADS):
363 heads[name[len(R_HEADS):]] = id
364 elif name.startswith(R_PUB):
365 pubed[name[len(R_PUB):]] = id
366
367 ready = []
368 for branch, id in heads.iteritems():
369 if branch in pubed and pubed[branch] == id:
370 continue
371
372 branch = self.GetBranch(branch)
373 base = branch.LocalMerge
374 if branch.LocalMerge:
375 rb = ReviewableBranch(self, branch, base)
376 if rb.commits:
377 ready.append(rb)
378 return ready
379
380 def UploadForReview(self, branch=None):
381 """Uploads the named branch for code review.
382 """
383 if branch is None:
384 branch = self.CurrentBranch
385 if branch is None:
386 raise GitError('not currently on a branch')
387
388 branch = self.GetBranch(branch)
389 if not branch.LocalMerge:
390 raise GitError('branch %s does not track a remote' % branch.name)
391 if not branch.remote.review:
392 raise GitError('remote %s has no review url' % branch.remote.name)
393
394 dest_branch = branch.merge
395 if not dest_branch.startswith(R_HEADS):
396 dest_branch = R_HEADS + dest_branch
397
398 base_list = []
399 for name, id in self._allrefs.iteritems():
400 if branch.remote.WritesTo(name):
401 base_list.append(not_rev(name))
402 if not base_list:
403 raise GitError('no base refs, cannot upload %s' % branch.name)
404
405 print >>sys.stderr, ''
406 _info("Uploading %s to %s:", branch.name, self.name)
407 try:
408 UploadBundle(project = self,
409 server = branch.remote.review,
410 email = self.UserEmail,
411 dest_project = self.name,
412 dest_branch = dest_branch,
413 src_branch = R_HEADS + branch.name,
414 bases = base_list)
415 except proto_client.ClientLoginError:
416 raise UploadError('Login failure')
417 except urllib2.HTTPError, e:
418 raise UploadError('HTTP error %d' % e.code)
419
420 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
421 self.bare_git.UpdateRef(R_PUB + branch.name,
422 R_HEADS + branch.name,
423 message = msg)
424
425
426## Sync ##
427
428 def Sync_NetworkHalf(self):
429 """Perform only the network IO portion of the sync process.
430 Local working directory/branch state is not affected.
431 """
432 if not self.Exists:
433 print >>sys.stderr
434 print >>sys.stderr, 'Initializing project %s ...' % self.name
435 self._InitGitDir()
436 self._InitRemote()
437 for r in self.extraRemotes.values():
438 if not self._RemoteFetch(r.name):
439 return False
440 if not self._SnapshotDownload():
441 return False
442 if not self._RemoteFetch():
443 return False
444 self._InitMRef()
445 return True
446
447 def _CopyFiles(self):
448 for file in self.copyfiles:
449 file._Copy()
450
451 def Sync_LocalHalf(self):
452 """Perform only the local IO portion of the sync process.
453 Network access is not required.
454
455 Return:
456 True: the sync was successful
457 False: the sync requires user input
458 """
459 self._InitWorkTree()
460 self.CleanPublishedCache()
461
462 rem = self.GetRemote(self.remote.name)
463 rev = rem.ToLocal(self.revision)
464 branch = self.CurrentBranch
465
466 if branch is None:
467 # Currently on a detached HEAD. The user is assumed to
468 # not have any local modifications worth worrying about.
469 #
470 lost = self._revlist(not_rev(rev), HEAD)
471 if lost:
472 _info("[%s] Discarding %d commits", self.name, len(lost))
473 try:
474 self._Checkout(rev, quiet=True)
475 except GitError:
476 return False
477 self._CopyFiles()
478 return True
479
480 branch = self.GetBranch(branch)
481 merge = branch.LocalMerge
482
483 if not merge:
484 # The current branch has no tracking configuration.
485 # Jump off it to a deatched HEAD.
486 #
487 _info("[%s] Leaving %s"
488 " (does not track any upstream)",
489 self.name,
490 branch.name)
491 try:
492 self._Checkout(rev, quiet=True)
493 except GitError:
494 return False
495 self._CopyFiles()
496 return True
497
498 upstream_gain = self._revlist(not_rev(HEAD), rev)
499 pub = self.WasPublished(branch.name)
500 if pub:
501 not_merged = self._revlist(not_rev(rev), pub)
502 if not_merged:
503 if upstream_gain:
504 # The user has published this branch and some of those
505 # commits are not yet merged upstream. We do not want
506 # to rewrite the published commits so we punt.
507 #
508 _info("[%s] Branch %s is published,"
509 " but is now %d commits behind.",
510 self.name, branch.name, len(upstream_gain))
511 _info("[%s] Consider merging or rebasing the"
512 " unpublished commits.", self.name)
513 return True
514
515 if merge == rev:
516 try:
517 old_merge = self.bare_git.rev_parse('%s@{1}' % merge)
518 except GitError:
519 old_merge = merge
520 else:
521 # The upstream switched on us. Time to cross our fingers
522 # and pray that the old upstream also wasn't in the habit
523 # of rebasing itself.
524 #
525 _info("[%s] Manifest switched from %s to %s",
526 self.name, merge, rev)
527 old_merge = merge
528
529 if rev == old_merge:
530 upstream_lost = []
531 else:
532 upstream_lost = self._revlist(not_rev(rev), old_merge)
533
534 if not upstream_lost and not upstream_gain:
535 # Trivially no changes caused by the upstream.
536 #
537 return True
538
539 if self.IsDirty(consider_untracked=False):
540 _warn('[%s] commit (or discard) uncommitted changes'
541 ' before sync', self.name)
542 return False
543
544 if upstream_lost:
545 # Upstream rebased. Not everything in HEAD
546 # may have been caused by the user.
547 #
548 _info("[%s] Discarding %d commits removed from upstream",
549 self.name, len(upstream_lost))
550
551 branch.remote = rem
552 branch.merge = self.revision
553 branch.Save()
554
555 my_changes = self._revlist(not_rev(old_merge), HEAD)
556 if my_changes:
557 try:
558 self._Rebase(upstream = old_merge, onto = rev)
559 except GitError:
560 return False
561 elif upstream_lost:
562 try:
563 self._ResetHard(rev)
564 except GitError:
565 return False
566 else:
567 try:
568 self._FastForward(rev)
569 except GitError:
570 return False
571
572 self._CopyFiles()
573 return True
574
575 def _SnapshotDownload(self):
576 if self.snapshots:
577 have = set(self._allrefs.keys())
578 need = []
579
580 for tag, sn in self.snapshots.iteritems():
581 if tag not in have:
582 need.append(sn)
583
584 if need:
585 print >>sys.stderr, """
586 *** Downloading source(s) from a mirror site. ***
587 *** If the network hangs, kill and restart repo. ***
588"""
589 for sn in need:
590 try:
591 sn.Import()
592 except ImportError, e:
593 print >>sys.stderr, \
594 'error: Cannot import %s: %s' \
595 % (self.name, e)
596 return False
597 cmd = ['repack', '-a', '-d', '-f', '-l']
598 if GitCommand(self, cmd, bare = True).Wait() != 0:
599 return False
600 return True
601
602 def AddCopyFile(self, src, dest):
603 # dest should already be an absolute path, but src is project relative
604 # make src an absolute path
605 src = os.path.join(self.worktree, src)
606 self.copyfiles.append(_CopyFile(src, dest))
607
608
609## Branch Management ##
610
611 def StartBranch(self, name):
612 """Create a new branch off the manifest's revision.
613 """
614 branch = self.GetBranch(name)
615 branch.remote = self.GetRemote(self.remote.name)
616 branch.merge = self.revision
617
618 rev = branch.LocalMerge
619 cmd = ['checkout', '-b', branch.name, rev]
620 if GitCommand(self, cmd).Wait() == 0:
621 branch.Save()
622 else:
623 raise GitError('%s checkout %s ' % (self.name, rev))
624
625 def PruneHeads(self):
626 """Prune any topic branches already merged into upstream.
627 """
628 cb = self.CurrentBranch
629 kill = []
630 for name in self._allrefs.keys():
631 if name.startswith(R_HEADS):
632 name = name[len(R_HEADS):]
633 if cb is None or name != cb:
634 kill.append(name)
635
636 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
637 if cb is not None \
638 and not self._revlist(HEAD + '...' + rev) \
639 and not self.IsDirty(consider_untracked = False):
640 self.work_git.DetachHead(HEAD)
641 kill.append(cb)
642
643 deleted = set()
644 if kill:
645 try:
646 old = self.bare_git.GetHead()
647 except GitError:
648 old = 'refs/heads/please_never_use_this_as_a_branch_name'
649
650 rm_re = re.compile(r"^Deleted branch (.*)\.$")
651 try:
652 self.bare_git.DetachHead(rev)
653
654 b = ['branch', '-d']
655 b.extend(kill)
656 b = GitCommand(self, b, bare=True,
657 capture_stdout=True,
658 capture_stderr=True)
659 b.Wait()
660 finally:
661 self.bare_git.SetHead(old)
662
663 for line in b.stdout.split("\n"):
664 m = rm_re.match(line)
665 if m:
666 deleted.add(m.group(1))
667
668 if deleted:
669 self.CleanPublishedCache()
670
671 if cb and cb not in kill:
672 kill.append(cb)
673 kill.sort()
674
675 kept = []
676 for branch in kill:
677 if branch not in deleted:
678 branch = self.GetBranch(branch)
679 base = branch.LocalMerge
680 if not base:
681 base = rev
682 kept.append(ReviewableBranch(self, branch, base))
683 return kept
684
685
686## Direct Git Commands ##
687
688 def _RemoteFetch(self, name=None):
689 if not name:
690 name = self.remote.name
691
692 hide_errors = False
693 if self.extraRemotes or self.snapshots:
694 hide_errors = True
695
696 proc = GitCommand(self,
697 ['fetch', name],
698 bare = True,
699 capture_stderr = hide_errors)
700 if hide_errors:
701 err = proc.process.stderr.fileno()
702 buf = ''
703 while True:
704 b = os.read(err, 256)
705 if b:
706 buf += b
707 while buf:
708 r = buf.find('remote: error: unable to find ')
709 if r >= 0:
710 lf = buf.find('\n')
711 if lf < 0:
712 break
713 buf = buf[lf + 1:]
714 continue
715
716 cr = buf.find('\r')
717 if cr < 0:
718 break
719 os.write(2, buf[0:cr + 1])
720 buf = buf[cr + 1:]
721 if not b:
722 if buf:
723 os.write(2, buf)
724 break
725 return proc.Wait() == 0
726
727 def _Checkout(self, rev, quiet=False):
728 cmd = ['checkout']
729 if quiet:
730 cmd.append('-q')
731 cmd.append(rev)
732 cmd.append('--')
733 if GitCommand(self, cmd).Wait() != 0:
734 if self._allrefs:
735 raise GitError('%s checkout %s ' % (self.name, rev))
736
737 def _ResetHard(self, rev, quiet=True):
738 cmd = ['reset', '--hard']
739 if quiet:
740 cmd.append('-q')
741 cmd.append(rev)
742 if GitCommand(self, cmd).Wait() != 0:
743 raise GitError('%s reset --hard %s ' % (self.name, rev))
744
745 def _Rebase(self, upstream, onto = None):
746 cmd = ['rebase', '-i']
747 if onto is not None:
748 cmd.extend(['--onto', onto])
749 cmd.append(upstream)
750 if GitCommand(self, cmd, disable_editor=True).Wait() != 0:
751 raise GitError('%s rebase %s ' % (self.name, upstream))
752
753 def _FastForward(self, head):
754 cmd = ['merge', head]
755 if GitCommand(self, cmd).Wait() != 0:
756 raise GitError('%s merge %s ' % (self.name, head))
757
758 def _InitGitDir(self):
759 if not os.path.exists(self.gitdir):
760 os.makedirs(self.gitdir)
761 self.bare_git.init()
762 self.config.SetString('core.bare', None)
763
764 hooks = self._gitdir_path('hooks')
765 for old_hook in os.listdir(hooks):
766 os.remove(os.path.join(hooks, old_hook))
767
768 # TODO(sop) install custom repo hooks
769
770 m = self.manifest.manifestProject.config
771 for key in ['user.name', 'user.email']:
772 if m.Has(key, include_defaults = False):
773 self.config.SetString(key, m.GetString(key))
774
775 def _InitRemote(self):
776 if self.remote.fetchUrl:
777 remote = self.GetRemote(self.remote.name)
778
779 url = self.remote.fetchUrl
780 while url.endswith('/'):
781 url = url[:-1]
782 url += '/%s.git' % self.name
783 remote.url = url
784 remote.review = self.remote.reviewUrl
785
786 remote.ResetFetch()
787 remote.Save()
788
789 for r in self.extraRemotes.values():
790 remote = self.GetRemote(r.name)
791 remote.url = r.fetchUrl
792 remote.review = r.reviewUrl
793 remote.ResetFetch()
794 remote.Save()
795
796 def _InitMRef(self):
797 if self.manifest.branch:
798 msg = 'manifest set to %s' % self.revision
799 ref = R_M + self.manifest.branch
800
801 if IsId(self.revision):
802 dst = self.revision + '^0',
803 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
804 else:
805 remote = self.GetRemote(self.remote.name)
806 dst = remote.ToLocal(self.revision)
807 self.bare_git.symbolic_ref('-m', msg, ref, dst)
808
809 def _InitWorkTree(self):
810 dotgit = os.path.join(self.worktree, '.git')
811 if not os.path.exists(dotgit):
812 os.makedirs(dotgit)
813
814 topdir = os.path.commonprefix([self.gitdir, dotgit])
815 if topdir.endswith('/'):
816 topdir = topdir[:-1]
817 else:
818 topdir = os.path.dirname(topdir)
819
820 tmpdir = dotgit
821 relgit = ''
822 while topdir != tmpdir:
823 relgit += '../'
824 tmpdir = os.path.dirname(tmpdir)
825 relgit += self.gitdir[len(topdir) + 1:]
826
827 for name in ['config',
828 'description',
829 'hooks',
830 'info',
831 'logs',
832 'objects',
833 'packed-refs',
834 'refs',
835 'rr-cache',
836 'svn']:
837 os.symlink(os.path.join(relgit, name),
838 os.path.join(dotgit, name))
839
840 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
841 rev = self.bare_git.rev_parse('%s^0' % rev)
842
843 f = open(os.path.join(dotgit, HEAD), 'wb')
844 f.write("%s\n" % rev)
845 f.close()
846
847 cmd = ['read-tree', '--reset', '-u']
848 cmd.append('-v')
849 cmd.append('HEAD')
850 if GitCommand(self, cmd).Wait() != 0:
851 raise GitError("cannot initialize work tree")
852
853 def _gitdir_path(self, path):
854 return os.path.join(self.gitdir, path)
855
856 def _revlist(self, *args):
857 cmd = []
858 cmd.extend(args)
859 cmd.append('--')
860 return self.work_git.rev_list(*args)
861
862 @property
863 def _allrefs(self):
864 return self.bare_git.ListRefs()
865
866 class _GitGetByExec(object):
867 def __init__(self, project, bare):
868 self._project = project
869 self._bare = bare
870
871 def ListRefs(self, *args):
872 cmdv = ['for-each-ref', '--format=%(objectname) %(refname)']
873 cmdv.extend(args)
874 p = GitCommand(self._project,
875 cmdv,
876 bare = self._bare,
877 capture_stdout = True,
878 capture_stderr = True)
879 r = {}
880 for line in p.process.stdout:
881 id, name = line[:-1].split(' ', 2)
882 r[name] = id
883 if p.Wait() != 0:
884 raise GitError('%s for-each-ref %s: %s' % (
885 self._project.name,
886 str(args),
887 p.stderr))
888 return r
889
890 def LsOthers(self):
891 p = GitCommand(self._project,
892 ['ls-files',
893 '-z',
894 '--others',
895 '--exclude-standard'],
896 bare = False,
897 capture_stdout = True,
898 capture_stderr = True)
899 if p.Wait() == 0:
900 out = p.stdout
901 if out:
902 return out[:-1].split("\0")
903 return []
904
905 def DiffZ(self, name, *args):
906 cmd = [name]
907 cmd.append('-z')
908 cmd.extend(args)
909 p = GitCommand(self._project,
910 cmd,
911 bare = False,
912 capture_stdout = True,
913 capture_stderr = True)
914 try:
915 out = p.process.stdout.read()
916 r = {}
917 if out:
918 out = iter(out[:-1].split('\0'))
919 while out:
920 info = out.next()
921 path = out.next()
922
923 class _Info(object):
924 def __init__(self, path, omode, nmode, oid, nid, state):
925 self.path = path
926 self.src_path = None
927 self.old_mode = omode
928 self.new_mode = nmode
929 self.old_id = oid
930 self.new_id = nid
931
932 if len(state) == 1:
933 self.status = state
934 self.level = None
935 else:
936 self.status = state[:1]
937 self.level = state[1:]
938 while self.level.startswith('0'):
939 self.level = self.level[1:]
940
941 info = info[1:].split(' ')
942 info =_Info(path, *info)
943 if info.status in ('R', 'C'):
944 info.src_path = info.path
945 info.path = out.next()
946 r[info.path] = info
947 return r
948 finally:
949 p.Wait()
950
951 def GetHead(self):
952 return self.symbolic_ref(HEAD)
953
954 def SetHead(self, ref, message=None):
955 cmdv = []
956 if message is not None:
957 cmdv.extend(['-m', message])
958 cmdv.append(HEAD)
959 cmdv.append(ref)
960 self.symbolic_ref(*cmdv)
961
962 def DetachHead(self, new, message=None):
963 cmdv = ['--no-deref']
964 if message is not None:
965 cmdv.extend(['-m', message])
966 cmdv.append(HEAD)
967 cmdv.append(new)
968 self.update_ref(*cmdv)
969
970 def UpdateRef(self, name, new, old=None,
971 message=None,
972 detach=False):
973 cmdv = []
974 if message is not None:
975 cmdv.extend(['-m', message])
976 if detach:
977 cmdv.append('--no-deref')
978 cmdv.append(name)
979 cmdv.append(new)
980 if old is not None:
981 cmdv.append(old)
982 self.update_ref(*cmdv)
983
984 def DeleteRef(self, name, old=None):
985 if not old:
986 old = self.rev_parse(name)
987 self.update_ref('-d', name, old)
988
989 def rev_list(self, *args):
990 cmdv = ['rev-list']
991 cmdv.extend(args)
992 p = GitCommand(self._project,
993 cmdv,
994 bare = self._bare,
995 capture_stdout = True,
996 capture_stderr = True)
997 r = []
998 for line in p.process.stdout:
999 r.append(line[:-1])
1000 if p.Wait() != 0:
1001 raise GitError('%s rev-list %s: %s' % (
1002 self._project.name,
1003 str(args),
1004 p.stderr))
1005 return r
1006
1007 def __getattr__(self, name):
1008 name = name.replace('_', '-')
1009 def runner(*args):
1010 cmdv = [name]
1011 cmdv.extend(args)
1012 p = GitCommand(self._project,
1013 cmdv,
1014 bare = self._bare,
1015 capture_stdout = True,
1016 capture_stderr = True)
1017 if p.Wait() != 0:
1018 raise GitError('%s %s: %s' % (
1019 self._project.name,
1020 name,
1021 p.stderr))
1022 r = p.stdout
1023 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1024 return r[:-1]
1025 return r
1026 return runner
1027
1028
1029class MetaProject(Project):
1030 """A special project housed under .repo.
1031 """
1032 def __init__(self, manifest, name, gitdir, worktree):
1033 repodir = manifest.repodir
1034 Project.__init__(self,
1035 manifest = manifest,
1036 name = name,
1037 gitdir = gitdir,
1038 worktree = worktree,
1039 remote = Remote('origin'),
1040 relpath = '.repo/%s' % name,
1041 revision = 'refs/heads/master')
1042
1043 def PreSync(self):
1044 if self.Exists:
1045 cb = self.CurrentBranch
1046 if cb:
1047 base = self.GetBranch(cb).merge
1048 if base:
1049 self.revision = base
1050
1051 @property
1052 def HasChanges(self):
1053 """Has the remote received new commits not yet checked out?
1054 """
1055 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
1056 if self._revlist(not_rev(HEAD), rev):
1057 return True
1058 return False
diff --git a/remote.py b/remote.py
new file mode 100644
index 00000000..27a8f7a7
--- /dev/null
+++ b/remote.py
@@ -0,0 +1,21 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16class Remote(object):
17 def __init__(self, name, fetch=None, review=None):
18 self.name = name
19 self.fetchUrl = fetch
20 self.reviewUrl = review
21 self.requiredCommits = []
diff --git a/repo b/repo
new file mode 100755
index 00000000..d5f69fb2
--- /dev/null
+++ b/repo
@@ -0,0 +1,587 @@
1#!/bin/sh
2
3## repo default configuration
4##
5REPO_URL='git://android.kernel.org/tools/repo.git'
6REPO_REV='stable'
7
8# Copyright (C) 2008 Google Inc.
9#
10# Licensed under the Apache License, Version 2.0 (the "License");
11# you may not use this file except in compliance with the License.
12# You may obtain a copy of the License at
13#
14# http://www.apache.org/licenses/LICENSE-2.0
15#
16# Unless required by applicable law or agreed to in writing, software
17# distributed under the License is distributed on an "AS IS" BASIS,
18# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19# See the License for the specific language governing permissions and
20# limitations under the License.
21
22magic='--calling-python-from-/bin/sh--'
23"""exec" python2.4 -E "$0" "$@" """#$magic"
24if __name__ == '__main__':
25 import sys
26 if sys.argv[-1] == '#%s' % magic:
27 del sys.argv[-1]
28del magic
29
30# increment this whenever we make important changes to this script
31VERSION = (1, 4)
32
33# increment this if the MAINTAINER_KEYS block is modified
34KEYRING_VERSION = (1,0)
35MAINTAINER_KEYS = """
36
37 Repo Maintainer <repo@android.kernel.org>
38-----BEGIN PGP PUBLIC KEY BLOCK-----
39Version: GnuPG v1.4.2.2 (GNU/Linux)
40
41mQGiBEj3ugERBACrLJh/ZPyVSKeClMuznFIrsQ+hpNnmJGw1a9GXKYKk8qHPhAZf
42WKtrBqAVMNRLhL85oSlekRz98u41H5si5zcuv+IXJDF5MJYcB8f22wAy15lUqPWi
43VCkk1l8qqLiuW0fo+ZkPY5qOgrvc0HW1SmdH649uNwqCbcKb6CxaTxzhOwCgj3AP
44xI1WfzLqdJjsm1Nq98L0cLcD/iNsILCuw44PRds3J75YP0pze7YF/6WFMB6QSFGu
45aUX1FsTTztKNXGms8i5b2l1B8JaLRWq/jOnZzyl1zrUJhkc0JgyZW5oNLGyWGhKD
46Fxp5YpHuIuMImopWEMFIRQNrvlg+YVK8t3FpdI1RY0LYqha8pPzANhEYgSfoVzOb
47fbfbA/4ioOrxy8ifSoga7ITyZMA+XbW8bx33WXutO9N7SPKS/AK2JpasSEVLZcON
48ae5hvAEGVXKxVPDjJBmIc2cOe7kOKSi3OxLzBqrjS2rnjiP4o0ekhZIe4+ocwVOg
49e0PLlH5avCqihGRhpoqDRsmpzSHzJIxtoeb+GgGEX8KkUsVAhbQpUmVwbyBNYWlu
50dGFpbmVyIDxyZXBvQGFuZHJvaWQua2VybmVsLm9yZz6IYAQTEQIAIAUCSPe6AQIb
51AwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEBZTDV6SD1xl1GEAn0x/OKQpy7qI
526G73NJviU0IUMtftAKCFMUhGb/0bZvQ8Rm3QCUpWHyEIu7kEDQRI97ogEBAA2wI6
535fs9y/rMwD6dkD/vK9v4C9mOn1IL5JCPYMJBVSci+9ED4ChzYvfq7wOcj9qIvaE0
54GwCt2ar7Q56me5J+byhSb32Rqsw/r3Vo5cZMH80N4cjesGuSXOGyEWTe4HYoxnHv
55gF4EKI2LK7xfTUcxMtlyn52sUpkfKsCpUhFvdmbAiJE+jCkQZr1Z8u2KphV79Ou+
56P1N5IXY/XWOlq48Qf4MWCYlJFrB07xjUjLKMPDNDnm58L5byDrP/eHysKexpbakL
57xCmYyfT6DV1SWLblpd2hie0sL3YejdtuBMYMS2rI7Yxb8kGuqkz+9l1qhwJtei94
585MaretDy/d/JH/pRYkRf7L+ke7dpzrP+aJmcz9P1e6gq4NJsWejaALVASBiioqNf
59QmtqSVzF1wkR5avZkFHuYvj6V/t1RrOZTXxkSk18KFMJRBZrdHFCWbc5qrVxUB6e
60N5pja0NFIUCigLBV1c6I2DwiuboMNh18VtJJh+nwWeez/RueN4ig59gRTtkcc0PR
6135tX2DR8+xCCFVW/NcJ4PSePYzCuuLvp1vEDHnj41R52Fz51hgddT4rBsp0nL+5I
62socSOIIezw8T9vVzMY4ArCKFAVu2IVyBcahTfBS8q5EM63mONU6UVJEozfGljiMw
63xuQ7JwKcw0AUEKTKG7aBgBaTAgT8TOevpvlw91cAAwUP/jRkyVi/0WAb0qlEaq/S
64ouWxX1faR+vU3b+Y2/DGjtXQMzG0qpetaTHC/AxxHpgt/dCkWI6ljYDnxgPLwG0a
65Oasm94BjZc6vZwf1opFZUKsjOAAxRxNZyjUJKe4UZVuMTk6zo27Nt3LMnc0FO47v
66FcOjRyquvgNOS818irVHUf12waDx8gszKxQTTtFxU5/ePB2jZmhP6oXSe4K/LG5T
67+WBRPDrHiGPhCzJRzm9BP0lTnGCAj3o9W90STZa65RK7IaYpC8TB35JTBEbrrNCp
68w6lzd74LnNEp5eMlKDnXzUAgAH0yzCQeMl7t33QCdYx2hRs2wtTQSjGfAiNmj/WW
69Vl5Jn+2jCDnRLenKHwVRFsBX2e0BiRWt/i9Y8fjorLCXVj4z+7yW6DawdLkJorEo
70p3v5ILwfC7hVx4jHSnOgZ65L9s8EQdVr1ckN9243yta7rNgwfcqb60ILMFF1BRk/
710V7wCL+68UwwiQDvyMOQuqkysKLSDCLb7BFcyA7j6KG+5hpsREstFX2wK1yKeraz
725xGrFy8tfAaeBMIQ17gvFSp/suc9DYO0ICK2BISzq+F+ZiAKsjMYOBNdH/h0zobQ
73HTHs37+/QLMomGEGKZMWi0dShU2J5mNRQu3Hhxl3hHDVbt5CeJBb26aQcQrFz69W
74zE3GNvmJosh6leayjtI9P2A6iEkEGBECAAkFAkj3uiACGwwACgkQFlMNXpIPXGWp
75TACbBS+Up3RpfYVfd63c1cDdlru13pQAn3NQy/SN858MkxN+zym86UBgOad2
76=CMiZ
77-----END PGP PUBLIC KEY BLOCK-----
78"""
79
80GIT = 'git' # our git command
81MIN_GIT_VERSION = (1, 5, 4) # minimum supported git version
82repodir = '.repo' # name of repo's private directory
83S_repo = 'repo' # special repo reposiory
84S_manifests = 'manifests' # special manifest repository
85REPO_MAIN = S_repo + '/main.py' # main script
86
87
88import optparse
89import os
90import re
91import readline
92import subprocess
93import sys
94
95home_dot_repo = os.path.expanduser('~/.repoconfig')
96gpg_dir = os.path.join(home_dot_repo, 'gnupg')
97
98extra_args = []
99init_optparse = optparse.OptionParser(usage="repo init -u url [options]")
100
101# Logging
102group = init_optparse.add_option_group('Logging options')
103group.add_option('-q', '--quiet',
104 dest="quiet", action="store_true", default=False,
105 help="be quiet")
106
107# Manifest
108group = init_optparse.add_option_group('Manifest options')
109group.add_option('-u', '--manifest-url',
110 dest='manifest_url',
111 help='manifest repository location', metavar='URL')
112group.add_option('-b', '--manifest-branch',
113 dest='manifest_branch',
114 help='manifest branch or revision', metavar='REVISION')
115group.add_option('-m', '--manifest-name',
116 dest='manifest_name',
117 help='initial manifest file', metavar='NAME.xml')
118
119# Tool
120group = init_optparse.add_option_group('Version options')
121group.add_option('--repo-url',
122 dest='repo_url',
123 help='repo repository location', metavar='URL')
124group.add_option('--repo-branch',
125 dest='repo_branch',
126 help='repo branch or revision', metavar='REVISION')
127group.add_option('--no-repo-verify',
128 dest='no_repo_verify', action='store_true',
129 help='do not verify repo source code')
130
131
132class CloneFailure(Exception):
133 """Indicate the remote clone of repo itself failed.
134 """
135
136
137def _Init(args):
138 """Installs repo by cloning it over the network.
139 """
140 opt, args = init_optparse.parse_args(args)
141 if args or not opt.manifest_url:
142 init_optparse.print_usage()
143 sys.exit(1)
144
145 url = opt.repo_url
146 if not url:
147 url = REPO_URL
148 extra_args.append('--repo-url=%s' % url)
149
150 branch = opt.repo_branch
151 if not branch:
152 branch = REPO_REV
153 extra_args.append('--repo-branch=%s' % branch)
154
155 if branch.startswith('refs/heads/'):
156 branch = branch[len('refs/heads/'):]
157 if branch.startswith('refs/'):
158 print >>sys.stderr, "fatal: invalid branch name '%s'" % branch
159 raise CloneFailure()
160
161 if not os.path.isdir(repodir):
162 try:
163 os.mkdir(repodir)
164 except OSError, e:
165 print >>sys.stderr, \
166 'fatal: cannot make %s directory: %s' % (
167 repodir, e.strerror)
168 # Don't faise CloneFailure; that would delete the
169 # name. Instead exit immediately.
170 #
171 sys.exit(1)
172
173 _CheckGitVersion()
174 try:
175 if _NeedSetupGnuPG():
176 can_verify = _SetupGnuPG(opt.quiet)
177 else:
178 can_verify = True
179
180 if not opt.quiet:
181 print >>sys.stderr, 'Getting repo ...'
182 print >>sys.stderr, ' from %s' % url
183
184 dst = os.path.abspath(os.path.join(repodir, S_repo))
185 _Clone(url, dst, opt.quiet)
186
187 if can_verify and not opt.no_repo_verify:
188 rev = _Verify(dst, branch, opt.quiet)
189 else:
190 rev = 'refs/remotes/origin/%s^0' % branch
191
192 _Checkout(dst, branch, rev, opt.quiet)
193 except CloneFailure:
194 if opt.quiet:
195 print >>sys.stderr, \
196 'fatal: repo init failed; run without --quiet to see why'
197 raise
198
199
200def _CheckGitVersion():
201 cmd = [GIT, '--version']
202 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
203 ver_str = proc.stdout.read().strip()
204 proc.stdout.close()
205
206 if not ver_str.startswith('git version '):
207 print >>sys.stderr, 'error: "%s" unsupported' % ver_str
208 raise CloneFailure()
209
210 ver_str = ver_str[len('git version '):].strip()
211 ver_act = tuple(map(lambda x: int(x), ver_str.split('.')[0:3]))
212 if ver_act < MIN_GIT_VERSION:
213 need = '.'.join(map(lambda x: str(x), MIN_GIT_VERSION))
214 print >>sys.stderr, 'fatal: git %s or later required' % need
215 raise CloneFailure()
216
217
218def _NeedSetupGnuPG():
219 if not os.path.isdir(home_dot_repo):
220 return True
221
222 kv = os.path.join(home_dot_repo, 'keyring-version')
223 if not os.path.exists(kv):
224 return True
225
226 kv = open(kv).read()
227 if not kv:
228 return True
229
230 kv = tuple(map(lambda x: int(x), kv.split('.')))
231 if kv < KEYRING_VERSION:
232 return True
233 return False
234
235
236def _SetupGnuPG(quiet):
237 if not os.path.isdir(home_dot_repo):
238 try:
239 os.mkdir(home_dot_repo)
240 except OSError, e:
241 print >>sys.stderr, \
242 'fatal: cannot make %s directory: %s' % (
243 home_dot_repo, e.strerror)
244 sys.exit(1)
245
246 if not os.path.isdir(gpg_dir):
247 try:
248 os.mkdir(gpg_dir, 0700)
249 except OSError, e:
250 print >>sys.stderr, \
251 'fatal: cannot make %s directory: %s' % (
252 gpg_dir, e.strerror)
253 sys.exit(1)
254
255 env = dict(os.environ)
256 env['GNUPGHOME'] = gpg_dir
257
258 cmd = ['gpg', '--import']
259 try:
260 proc = subprocess.Popen(cmd,
261 env = env,
262 stdin = subprocess.PIPE)
263 except OSError, e:
264 if not quiet:
265 print >>sys.stderr, 'warning: gpg (GnuPG) is not available.'
266 print >>sys.stderr, 'warning: Installing it is strongly encouraged.'
267 print >>sys.stderr
268 return False
269
270 proc.stdin.write(MAINTAINER_KEYS)
271 proc.stdin.close()
272
273 if proc.wait() != 0:
274 print >>sys.stderr, 'fatal: registering repo maintainer keys failed'
275 sys.exit(1)
276 print
277
278 fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
279 fd.write('.'.join(map(lambda x: str(x), KEYRING_VERSION)) + '\n')
280 fd.close()
281 return True
282
283
284def _SetConfig(local, name, value):
285 """Set a git configuration option to the specified value.
286 """
287 cmd = [GIT, 'config', name, value]
288 if subprocess.Popen(cmd, cwd = local).wait() != 0:
289 raise CloneFailure()
290
291
292def _Fetch(local, quiet, *args):
293 cmd = [GIT, 'fetch']
294 if quiet:
295 cmd.append('--quiet')
296 err = subprocess.PIPE
297 else:
298 err = None
299 cmd.extend(args)
300 cmd.append('origin')
301
302 proc = subprocess.Popen(cmd, cwd = local, stderr = err)
303 if err:
304 proc.stderr.read()
305 proc.stderr.close()
306 if proc.wait() != 0:
307 raise CloneFailure()
308
309
310def _Clone(url, local, quiet):
311 """Clones a git repository to a new subdirectory of repodir
312 """
313 try:
314 os.mkdir(local)
315 except OSError, e:
316 print >>sys.stderr, \
317 'fatal: cannot make %s directory: %s' \
318 % (local, e.strerror)
319 raise CloneFailure()
320
321 cmd = [GIT, 'init', '--quiet']
322 try:
323 proc = subprocess.Popen(cmd, cwd = local)
324 except OSError, e:
325 print >>sys.stderr
326 print >>sys.stderr, "fatal: '%s' is not available" % GIT
327 print >>sys.stderr, 'fatal: %s' % e
328 print >>sys.stderr
329 print >>sys.stderr, 'Please make sure %s is installed'\
330 ' and in your path.' % GIT
331 raise CloneFailure()
332 if proc.wait() != 0:
333 print >>sys.stderr, 'fatal: could not create %s' % local
334 raise CloneFailure()
335
336 _SetConfig(local, 'remote.origin.url', url)
337 _SetConfig(local, 'remote.origin.fetch',
338 '+refs/heads/*:refs/remotes/origin/*')
339 _Fetch(local, quiet)
340 _Fetch(local, quiet, '--tags')
341
342
343def _Verify(cwd, branch, quiet):
344 """Verify the branch has been signed by a tag.
345 """
346 cmd = [GIT, 'describe', 'origin/%s' % branch]
347 proc = subprocess.Popen(cmd,
348 stdout=subprocess.PIPE,
349 stderr=subprocess.PIPE,
350 cwd = cwd)
351 cur = proc.stdout.read().strip()
352 proc.stdout.close()
353
354 proc.stderr.read()
355 proc.stderr.close()
356
357 if proc.wait() != 0 or not cur:
358 print >>sys.stderr
359 print >>sys.stderr,\
360 "fatal: branch '%s' has not been signed" \
361 % branch
362 raise CloneFailure()
363
364 m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
365 if m:
366 cur = m.group(1)
367 if not quiet:
368 print >>sys.stderr
369 print >>sys.stderr, \
370 "info: Ignoring branch '%s'; using tagged release '%s'" \
371 % (branch, cur)
372 print >>sys.stderr
373
374 env = dict(os.environ)
375 env['GNUPGHOME'] = gpg_dir
376
377 cmd = [GIT, 'tag', '-v', cur]
378 proc = subprocess.Popen(cmd,
379 stdout = subprocess.PIPE,
380 stderr = subprocess.PIPE,
381 cwd = cwd,
382 env = env)
383 out = proc.stdout.read()
384 proc.stdout.close()
385
386 err = proc.stderr.read()
387 proc.stderr.close()
388
389 if proc.wait() != 0:
390 print >>sys.stderr
391 print >>sys.stderr, out
392 print >>sys.stderr, err
393 print >>sys.stderr
394 raise CloneFailure()
395 return '%s^0' % cur
396
397
398def _Checkout(cwd, branch, rev, quiet):
399 """Checkout an upstream branch into the repository and track it.
400 """
401 cmd = [GIT, 'update-ref', 'refs/heads/default', rev]
402 if subprocess.Popen(cmd, cwd = cwd).wait() != 0:
403 raise CloneFailure()
404
405 _SetConfig(cwd, 'branch.default.remote', 'origin')
406 _SetConfig(cwd, 'branch.default.merge', 'refs/heads/%s' % branch)
407
408 cmd = [GIT, 'symbolic-ref', 'HEAD', 'refs/heads/default']
409 if subprocess.Popen(cmd, cwd = cwd).wait() != 0:
410 raise CloneFailure()
411
412 cmd = [GIT, 'read-tree', '--reset', '-u']
413 if not quiet:
414 cmd.append('-v')
415 cmd.append('HEAD')
416 if subprocess.Popen(cmd, cwd = cwd).wait() != 0:
417 raise CloneFailure()
418
419
420def _FindRepo():
421 """Look for a repo installation, starting at the current directory.
422 """
423 dir = os.getcwd()
424 repo = None
425
426 while dir != '/' and not repo:
427 repo = os.path.join(dir, repodir, REPO_MAIN)
428 if not os.path.isfile(repo):
429 repo = None
430 dir = os.path.dirname(dir)
431 return (repo, os.path.join(dir, repodir))
432
433
434class _Options:
435 help = False
436
437
438def _ParseArguments(args):
439 cmd = None
440 opt = _Options()
441 arg = []
442
443 for i in xrange(0, len(args)):
444 a = args[i]
445 if a == '-h' or a == '--help':
446 opt.help = True
447
448 elif not a.startswith('-'):
449 cmd = a
450 arg = args[i + 1:]
451 break
452 return cmd, opt, arg
453
454
455def _Usage():
456 print >>sys.stderr,\
457"""usage: repo COMMAND [ARGS]
458
459repo is not yet installed. Use "repo init" to install it here.
460
461The most commonly used repo commands are:
462
463 init Install repo in the current working directory
464 help Display detailed help on a command
465
466For access to the full online help, install repo ("repo init").
467"""
468 sys.exit(1)
469
470
471def _Help(args):
472 if args:
473 if args[0] == 'init':
474 init_optparse.print_help()
475 else:
476 print >>sys.stderr,\
477 "error: '%s' is not a bootstrap command.\n"\
478 ' For access to online help, install repo ("repo init").'\
479 % args[0]
480 else:
481 _Usage()
482 sys.exit(1)
483
484
485def _NotInstalled():
486 print >>sys.stderr,\
487'error: repo is not installed. Use "repo init" to install it here.'
488 sys.exit(1)
489
490
491def _NoCommands(cmd):
492 print >>sys.stderr,\
493"""error: command '%s' requires repo to be installed first.
494 Use "repo init" to install it here.""" % cmd
495 sys.exit(1)
496
497
498def _RunSelf(wrapper_path):
499 my_dir = os.path.dirname(wrapper_path)
500 my_main = os.path.join(my_dir, 'main.py')
501 my_git = os.path.join(my_dir, '.git')
502
503 if os.path.isfile(my_main) and os.path.isdir(my_git):
504 for name in ['manifest.py',
505 'project.py',
506 'subcmds']:
507 if not os.path.exists(os.path.join(my_dir, name)):
508 return None, None
509 return my_main, my_git
510 return None, None
511
512
513def _SetDefaultsTo(gitdir):
514 global REPO_URL
515 global REPO_REV
516
517 REPO_URL = gitdir
518 proc = subprocess.Popen([GIT,
519 '--git-dir=%s' % gitdir,
520 'symbolic-ref',
521 'HEAD'],
522 stdout = subprocess.PIPE,
523 stderr = subprocess.PIPE)
524 REPO_REV = proc.stdout.read().strip()
525 proc.stdout.close()
526
527 proc.stderr.read()
528 proc.stderr.close()
529
530 if proc.wait() != 0:
531 print >>sys.stderr, 'fatal: %s has no current branch' % gitdir
532 sys.exit(1)
533
534
535def main(orig_args):
536 main, dir = _FindRepo()
537 cmd, opt, args = _ParseArguments(orig_args)
538
539 wrapper_path = os.path.abspath(__file__)
540 my_main, my_git = _RunSelf(wrapper_path)
541
542 if not main:
543 if opt.help:
544 _Usage()
545 if cmd == 'help':
546 _Help(args)
547 if not cmd:
548 _NotInstalled()
549 if cmd == 'init':
550 if my_git:
551 _SetDefaultsTo(my_git)
552 try:
553 _Init(args)
554 except CloneFailure:
555 for root, dirs, files in os.walk(repodir, topdown=False):
556 for name in files:
557 os.remove(os.path.join(root, name))
558 for name in dirs:
559 os.rmdir(os.path.join(root, name))
560 os.rmdir(repodir)
561 sys.exit(1)
562 main, dir = _FindRepo()
563 else:
564 _NoCommands(cmd)
565
566 if my_main:
567 main = my_main
568
569 ver_str = '.'.join(map(lambda x: str(x), VERSION))
570 me = [main,
571 '--repo-dir=%s' % dir,
572 '--wrapper-version=%s' % ver_str,
573 '--wrapper-path=%s' % wrapper_path,
574 '--']
575 me.extend(orig_args)
576 me.extend(extra_args)
577 try:
578 os.execv(main, me)
579 except OSError, e:
580 print >>sys.stderr, "fatal: unable to start %s" % main
581 print >>sys.stderr, "fatal: %s" % e
582 sys.exit(148)
583
584
585if __name__ == '__main__':
586 main(sys.argv[1:])
587
diff --git a/subcmds/__init__.py b/subcmds/__init__.py
new file mode 100644
index 00000000..a2286e78
--- /dev/null
+++ b/subcmds/__init__.py
@@ -0,0 +1,49 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17
18all = {}
19
20my_dir = os.path.dirname(__file__)
21for py in os.listdir(my_dir):
22 if py == '__init__.py':
23 continue
24
25 if py.endswith('.py'):
26 name = py[:-3]
27
28 clsn = name.capitalize()
29 while clsn.find('_') > 0:
30 h = clsn.index('_')
31 clsn = clsn[0:h] + clsn[h + 1:].capitalize()
32
33 mod = __import__(__name__,
34 globals(),
35 locals(),
36 ['%s' % name])
37 mod = getattr(mod, name)
38 try:
39 cmd = getattr(mod, clsn)()
40 except AttributeError:
41 raise SyntaxError, '%s/%s does not define class %s' % (
42 __name__, py, clsn)
43
44 name = name.replace('_', '-')
45 cmd.NAME = name
46 all[name] = cmd
47
48if 'help' in all:
49 all['help'].commands = all
diff --git a/subcmds/compute_snapshot_check.py b/subcmds/compute_snapshot_check.py
new file mode 100644
index 00000000..82db359a
--- /dev/null
+++ b/subcmds/compute_snapshot_check.py
@@ -0,0 +1,169 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import sys
18import tempfile
19
20from command import Command
21from error import GitError, NoSuchProjectError
22from git_config import IsId
23from import_tar import ImportTar
24from import_zip import ImportZip
25from project import Project
26from remote import Remote
27
28def _ToCommit(project, rev):
29 return project.bare_git.rev_parse('--verify', '%s^0' % rev)
30
31def _Missing(project, rev):
32 return project._revlist('--objects', rev, '--not', '--all')
33
34
35class ComputeSnapshotCheck(Command):
36 common = False
37 helpSummary = "Compute the check value for a new snapshot"
38 helpUsage = """
39%prog -p NAME -v VERSION -s FILE [options]
40"""
41 helpDescription = """
42%prog computes and then displays the proper check value for a
43snapshot, so it can be pasted into the manifest file for a project.
44"""
45
46 def _Options(self, p):
47 g = p.add_option_group('Snapshot description options')
48 g.add_option('-p', '--project',
49 dest='project', metavar='NAME',
50 help='destination project name')
51 g.add_option('-v', '--version',
52 dest='version', metavar='VERSION',
53 help='upstream version/revision identifier')
54 g.add_option('-s', '--snapshot',
55 dest='snapshot', metavar='PATH',
56 help='local tarball path')
57 g.add_option('--new-project',
58 dest='new_project', action='store_true',
59 help='destinition is a new project')
60 g.add_option('--keep',
61 dest='keep_git', action='store_true',
62 help='keep the temporary git repository')
63
64 g = p.add_option_group('Base revision grafting options')
65 g.add_option('--prior',
66 dest='prior', metavar='COMMIT',
67 help='prior revision checksum')
68
69 g = p.add_option_group('Path mangling options')
70 g.add_option('--strip-prefix',
71 dest='strip_prefix', metavar='PREFIX',
72 help='remove prefix from all paths on import')
73 g.add_option('--insert-prefix',
74 dest='insert_prefix', metavar='PREFIX',
75 help='insert prefix before all paths on import')
76
77
78 def _Compute(self, opt):
79 try:
80 real_project = self.GetProjects([opt.project])[0]
81 except NoSuchProjectError:
82 if opt.new_project:
83 print >>sys.stderr, \
84 "warning: project '%s' does not exist" % opt.project
85 else:
86 raise NoSuchProjectError(opt.project)
87
88 self._tmpdir = tempfile.mkdtemp()
89 project = Project(manifest = self.manifest,
90 name = opt.project,
91 remote = Remote('origin'),
92 gitdir = os.path.join(self._tmpdir, '.git'),
93 worktree = self._tmpdir,
94 relpath = opt.project,
95 revision = 'refs/heads/master')
96 project._InitGitDir()
97
98 url = 'file://%s' % os.path.abspath(opt.snapshot)
99
100 imp = None
101 for cls in [ImportTar, ImportZip]:
102 if cls.CanAccept(url):
103 imp = cls()
104 break
105 if not imp:
106 print >>sys.stderr, 'error: %s unsupported' % opt.snapshot
107 sys.exit(1)
108
109 imp.SetProject(project)
110 imp.SetVersion(opt.version)
111 imp.AddUrl(url)
112
113 if opt.prior:
114 if opt.new_project:
115 if not IsId(opt.prior):
116 print >>sys.stderr, 'error: --prior=%s not valid' % opt.prior
117 sys.exit(1)
118 else:
119 try:
120 opt.prior = _ToCommit(real_project, opt.prior)
121 missing = _Missing(real_project, opt.prior)
122 except GitError, e:
123 print >>sys.stderr,\
124 'error: --prior=%s not valid\n%s' \
125 % (opt.prior, e)
126 sys.exit(1)
127 if missing:
128 print >>sys.stderr,\
129 'error: --prior=%s is valid, but is not reachable' \
130 % opt.prior
131 sys.exit(1)
132 imp.SetParent(opt.prior)
133
134 src = opt.strip_prefix
135 dst = opt.insert_prefix
136 if src or dst:
137 if src is None:
138 src = ''
139 if dst is None:
140 dst = ''
141 imp.RemapPath(src, dst)
142 commitId = imp.Import()
143
144 print >>sys.stderr,"%s\t%s" % (commitId, imp.version)
145 return project
146
147 def Execute(self, opt, args):
148 if args \
149 or not opt.project \
150 or not opt.version \
151 or not opt.snapshot:
152 self.Usage()
153
154 success = False
155 project = None
156 try:
157 self._tmpdir = None
158 project = self._Compute(opt)
159 finally:
160 if project and opt.keep_git:
161 print 'GIT_DIR = %s' % (project.gitdir)
162 elif self._tmpdir:
163 for root, dirs, files in os.walk(self._tmpdir, topdown=False):
164 for name in files:
165 os.remove(os.path.join(root, name))
166 for name in dirs:
167 os.rmdir(os.path.join(root, name))
168 os.rmdir(self._tmpdir)
169
diff --git a/subcmds/diff.py b/subcmds/diff.py
new file mode 100644
index 00000000..e0247140
--- /dev/null
+++ b/subcmds/diff.py
@@ -0,0 +1,27 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from command import PagedCommand
17
18class Diff(PagedCommand):
19 common = True
20 helpSummary = "Show changes between commit and working tree"
21 helpUsage = """
22%prog [<project>...]
23"""
24
25 def Execute(self, opt, args):
26 for project in self.GetProjects(args):
27 project.PrintWorkTreeDiff()
diff --git a/subcmds/forall.py b/subcmds/forall.py
new file mode 100644
index 00000000..b22e22a1
--- /dev/null
+++ b/subcmds/forall.py
@@ -0,0 +1,82 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import re
17import os
18import sys
19import subprocess
20from command import Command
21
22class Forall(Command):
23 common = False
24 helpSummary = "Run a shell command in each project"
25 helpUsage = """
26%prog [<project>...] -c <command> [<arg>...]
27"""
28 helpDescription = """
29Executes the same shell command in each project.
30
31Environment
32-----------
33pwd is the project's working directory.
34
35REPO_PROJECT is set to the unique name of the project.
36
37shell positional arguments ($1, $2, .., $#) are set to any arguments
38following <command>.
39
40stdin, stdout, stderr are inherited from the terminal and are
41not redirected.
42"""
43
44 def _Options(self, p):
45 def cmd(option, opt_str, value, parser):
46 setattr(parser.values, option.dest, list(parser.rargs))
47 while parser.rargs:
48 del parser.rargs[0]
49 p.add_option('-c', '--command',
50 help='Command (and arguments) to execute',
51 dest='command',
52 action='callback',
53 callback=cmd)
54
55 def Execute(self, opt, args):
56 if not opt.command:
57 self.Usage()
58
59 cmd = [opt.command[0]]
60
61 shell = True
62 if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]):
63 shell = False
64
65 if shell:
66 cmd.append(cmd[0])
67 cmd.extend(opt.command[1:])
68
69 rc = 0
70 for project in self.GetProjects(args):
71 env = dict(os.environ.iteritems())
72 env['REPO_PROJECT'] = project.name
73
74 p = subprocess.Popen(cmd,
75 cwd = project.worktree,
76 shell = shell,
77 env = env)
78 r = p.wait()
79 if r != 0 and r != rc:
80 rc = r
81 if rc != 0:
82 sys.exit(rc)
diff --git a/subcmds/help.py b/subcmds/help.py
new file mode 100644
index 00000000..6e0238a0
--- /dev/null
+++ b/subcmds/help.py
@@ -0,0 +1,147 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys
17from formatter import AbstractFormatter, DumbWriter
18
19from color import Coloring
20from command import PagedCommand
21
22class Help(PagedCommand):
23 common = False
24 helpSummary = "Display detailed help on a command"
25 helpUsage = """
26%prog [--all|command]
27"""
28 helpDescription = """
29Displays detailed usage information about a command.
30"""
31
32 def _PrintAllCommands(self):
33 print 'usage: repo COMMAND [ARGS]'
34 print """
35The complete list of recognized repo commands are:
36"""
37 commandNames = self.commands.keys()
38 commandNames.sort()
39
40 maxlen = 0
41 for name in commandNames:
42 maxlen = max(maxlen, len(name))
43 fmt = ' %%-%ds %%s' % maxlen
44
45 for name in commandNames:
46 command = self.commands[name]
47 try:
48 summary = command.helpSummary.strip()
49 except AttributeError:
50 summary = ''
51 print fmt % (name, summary)
52 print """
53See 'repo help <command>' for more information on a specific command.
54"""
55
56 def _PrintCommonCommands(self):
57 print 'usage: repo COMMAND [ARGS]'
58 print """
59The most commonly used repo commands are:
60"""
61 commandNames = [name
62 for name in self.commands.keys()
63 if self.commands[name].common]
64 commandNames.sort()
65
66 maxlen = 0
67 for name in commandNames:
68 maxlen = max(maxlen, len(name))
69 fmt = ' %%-%ds %%s' % maxlen
70
71 for name in commandNames:
72 command = self.commands[name]
73 try:
74 summary = command.helpSummary.strip()
75 except AttributeError:
76 summary = ''
77 print fmt % (name, summary)
78 print """
79See 'repo help <command>' for more information on a specific command.
80"""
81
82 def _PrintCommandHelp(self, cmd):
83 class _Out(Coloring):
84 def __init__(self, gc):
85 Coloring.__init__(self, gc, 'help')
86 self.heading = self.printer('heading', attr='bold')
87
88 self.wrap = AbstractFormatter(DumbWriter())
89
90 def _PrintSection(self, heading, bodyAttr):
91 try:
92 body = getattr(cmd, bodyAttr)
93 except AttributeError:
94 return
95
96 self.nl()
97
98 self.heading('%s', heading)
99 self.nl()
100
101 self.heading('%s', ''.ljust(len(heading), '-'))
102 self.nl()
103
104 me = 'repo %s' % cmd.NAME
105 body = body.strip()
106 body = body.replace('%prog', me)
107
108 for para in body.split("\n\n"):
109 if para.startswith(' '):
110 self.write('%s', para)
111 self.nl()
112 self.nl()
113 else:
114 self.wrap.add_flowing_data(para)
115 self.wrap.end_paragraph(1)
116 self.wrap.end_paragraph(0)
117
118 out = _Out(self.manifest.globalConfig)
119 cmd.OptionParser.print_help()
120 out._PrintSection('Summary', 'helpSummary')
121 out._PrintSection('Description', 'helpDescription')
122
123 def _Options(self, p):
124 p.add_option('-a', '--all',
125 dest='show_all', action='store_true',
126 help='show the complete list of commands')
127
128 def Execute(self, opt, args):
129 if len(args) == 0:
130 if opt.show_all:
131 self._PrintAllCommands()
132 else:
133 self._PrintCommonCommands()
134
135 elif len(args) == 1:
136 name = args[0]
137
138 try:
139 cmd = self.commands[name]
140 except KeyError:
141 print >>sys.stderr, "repo: '%s' is not a repo command." % name
142 sys.exit(1)
143
144 self._PrintCommandHelp(cmd)
145
146 else:
147 self._PrintCommandHelp(self)
diff --git a/subcmds/init.py b/subcmds/init.py
new file mode 100644
index 00000000..03f358d1
--- /dev/null
+++ b/subcmds/init.py
@@ -0,0 +1,193 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import sys
18
19from color import Coloring
20from command import InteractiveCommand
21from error import ManifestParseError
22from remote import Remote
23from git_command import git, MIN_GIT_VERSION
24
25class Init(InteractiveCommand):
26 common = True
27 helpSummary = "Initialize repo in the current directory"
28 helpUsage = """
29%prog [options]
30"""
31 helpDescription = """
32The '%prog' command is run once to install and initialize repo.
33The latest repo source code and manifest collection is downloaded
34from the server and is installed in the .repo/ directory in the
35current working directory.
36
37The optional <manifest> argument can be used to specify an alternate
38manifest to be used. If no manifest is specified, the manifest
39default.xml will be used.
40"""
41
42 def _Options(self, p):
43 # Logging
44 g = p.add_option_group('Logging options')
45 g.add_option('-q', '--quiet',
46 dest="quiet", action="store_true", default=False,
47 help="be quiet")
48
49 # Manifest
50 g = p.add_option_group('Manifest options')
51 g.add_option('-u', '--manifest-url',
52 dest='manifest_url',
53 help='manifest repository location', metavar='URL')
54 g.add_option('-b', '--manifest-branch',
55 dest='manifest_branch',
56 help='manifest branch or revision', metavar='REVISION')
57 g.add_option('-m', '--manifest-name',
58 dest='manifest_name', default='default.xml',
59 help='initial manifest file', metavar='NAME.xml')
60
61 # Tool
62 g = p.add_option_group('Version options')
63 g.add_option('--repo-url',
64 dest='repo_url',
65 help='repo repository location', metavar='URL')
66 g.add_option('--repo-branch',
67 dest='repo_branch',
68 help='repo branch or revision', metavar='REVISION')
69 g.add_option('--no-repo-verify',
70 dest='no_repo_verify', action='store_true',
71 help='do not verify repo source code')
72
73 def _CheckGitVersion(self):
74 ver_str = git.version()
75 if not ver_str.startswith('git version '):
76 print >>sys.stderr, 'error: "%s" unsupported' % ver_str
77 sys.exit(1)
78
79 ver_str = ver_str[len('git version '):].strip()
80 ver_act = tuple(map(lambda x: int(x), ver_str.split('.')[0:3]))
81 if ver_act < MIN_GIT_VERSION:
82 need = '.'.join(map(lambda x: str(x), MIN_GIT_VERSION))
83 print >>sys.stderr, 'fatal: git %s or later required' % need
84 sys.exit(1)
85
86 def _SyncManifest(self, opt):
87 m = self.manifest.manifestProject
88
89 if not m.Exists:
90 if not opt.manifest_url:
91 print >>sys.stderr, 'fatal: manifest url (-u) is required.'
92 sys.exit(1)
93
94 if not opt.quiet:
95 print >>sys.stderr, 'Getting manifest ...'
96 print >>sys.stderr, ' from %s' % opt.manifest_url
97 m._InitGitDir()
98
99 if opt.manifest_branch:
100 m.revision = opt.manifest_branch
101 else:
102 m.revision = 'refs/heads/master'
103 else:
104 if opt.manifest_branch:
105 m.revision = opt.manifest_branch
106 else:
107 m.PreSync()
108
109 if opt.manifest_url:
110 r = m.GetRemote(m.remote.name)
111 r.url = opt.manifest_url
112 r.ResetFetch()
113 r.Save()
114
115 m.Sync_NetworkHalf()
116 m.Sync_LocalHalf()
117 m.StartBranch('default')
118
119 def _LinkManifest(self, name):
120 if not name:
121 print >>sys.stderr, 'fatal: manifest name (-m) is required.'
122 sys.exit(1)
123
124 try:
125 self.manifest.Link(name)
126 except ManifestParseError, e:
127 print >>sys.stderr, "fatal: manifest '%s' not available" % name
128 print >>sys.stderr, 'fatal: %s' % str(e)
129 sys.exit(1)
130
131 def _PromptKey(self, prompt, key, value):
132 mp = self.manifest.manifestProject
133
134 sys.stdout.write('%-10s [%s]: ' % (prompt, value))
135 a = sys.stdin.readline().strip()
136 if a != '' and a != value:
137 mp.config.SetString(key, a)
138
139 def _ConfigureUser(self):
140 mp = self.manifest.manifestProject
141
142 print ''
143 self._PromptKey('Your Name', 'user.name', mp.UserName)
144 self._PromptKey('Your Email', 'user.email', mp.UserEmail)
145
146 def _HasColorSet(self, gc):
147 for n in ['ui', 'diff', 'status']:
148 if gc.Has('color.%s' % n):
149 return True
150 return False
151
152 def _ConfigureColor(self):
153 gc = self.manifest.globalConfig
154 if self._HasColorSet(gc):
155 return
156
157 class _Test(Coloring):
158 def __init__(self):
159 Coloring.__init__(self, gc, 'test color display')
160 self._on = True
161 out = _Test()
162
163 print ''
164 print "Testing colorized output (for 'repo diff', 'repo status'):"
165
166 for c in ['black','red','green','yellow','blue','magenta','cyan']:
167 out.write(' ')
168 out.printer(fg=c)(' %-6s ', c)
169 out.write(' ')
170 out.printer(fg='white', bg='black')(' %s ' % 'white')
171 out.nl()
172
173 for c in ['bold','dim','ul','reverse']:
174 out.write(' ')
175 out.printer(fg='black', attr=c)(' %-6s ', c)
176 out.nl()
177
178 sys.stdout.write('Enable color display in this user account (y/n)? ')
179 a = sys.stdin.readline().strip().lower()
180 if a in ('y', 'yes', 't', 'true', 'on'):
181 gc.SetString('color.ui', 'auto')
182
183 def Execute(self, opt, args):
184 self._CheckGitVersion()
185 self._SyncManifest(opt)
186 self._LinkManifest(opt.manifest_name)
187
188 if os.isatty(0) and os.isatty(1):
189 self._ConfigureUser()
190 self._ConfigureColor()
191
192 print ''
193 print 'repo initialized in %s' % self.manifest.topdir
diff --git a/subcmds/prune.py b/subcmds/prune.py
new file mode 100644
index 00000000..f412bd48
--- /dev/null
+++ b/subcmds/prune.py
@@ -0,0 +1,59 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from color import Coloring
17from command import PagedCommand
18
19class Prune(PagedCommand):
20 common = True
21 helpSummary = "Prune (delete) already merged topics"
22 helpUsage = """
23%prog [<project>...]
24"""
25
26 def Execute(self, opt, args):
27 all = []
28 for project in self.GetProjects(args):
29 all.extend(project.PruneHeads())
30
31 if not all:
32 return
33
34 class Report(Coloring):
35 def __init__(self, config):
36 Coloring.__init__(self, config, 'status')
37 self.project = self.printer('header', attr='bold')
38
39 out = Report(all[0].project.config)
40 out.project('Pending Branches')
41 out.nl()
42
43 project = None
44
45 for branch in all:
46 if project != branch.project:
47 project = branch.project
48 out.nl()
49 out.project('project %s/' % project.relpath)
50 out.nl()
51
52 commits = branch.commits
53 date = branch.date
54 print '%s %-33s (%2d commit%s, %s)' % (
55 branch.name == project.CurrentBranch and '*' or ' ',
56 branch.name,
57 len(commits),
58 len(commits) != 1 and 's' or ' ',
59 date)
diff --git a/subcmds/stage.py b/subcmds/stage.py
new file mode 100644
index 00000000..c451cd6d
--- /dev/null
+++ b/subcmds/stage.py
@@ -0,0 +1,108 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys
17
18from color import Coloring
19from command import InteractiveCommand
20from git_command import GitCommand
21
22class _ProjectList(Coloring):
23 def __init__(self, gc):
24 Coloring.__init__(self, gc, 'interactive')
25 self.prompt = self.printer('prompt', fg='blue', attr='bold')
26 self.header = self.printer('header', attr='bold')
27 self.help = self.printer('help', fg='red', attr='bold')
28
29class Stage(InteractiveCommand):
30 common = True
31 helpSummary = "Stage file(s) for commit"
32 helpUsage = """
33%prog -i [<project>...]
34"""
35 helpDescription = """
36The '%prog' command stages files to prepare the next commit.
37"""
38
39 def _Options(self, p):
40 p.add_option('-i', '--interactive',
41 dest='interactive', action='store_true',
42 help='use interactive staging')
43
44 def Execute(self, opt, args):
45 if opt.interactive:
46 self._Interactive(opt, args)
47 else:
48 self.Usage()
49
50 def _Interactive(self, opt, args):
51 all = filter(lambda x: x.IsDirty(), self.GetProjects(args))
52 if not all:
53 print >>sys.stderr,'no projects have uncommitted modifications'
54 return
55
56 out = _ProjectList(self.manifest.manifestProject.config)
57 while True:
58 out.header(' %-20s %s', 'project', 'path')
59 out.nl()
60
61 for i in xrange(0, len(all)):
62 p = all[i]
63 out.write('%3d: %-20s %s', i + 1, p.name, p.relpath + '/')
64 out.nl()
65 out.nl()
66
67 out.write('%3d: (', 0)
68 out.prompt('q')
69 out.write('uit)')
70 out.nl()
71
72 out.prompt('project> ')
73 try:
74 a = sys.stdin.readline()
75 except KeyboardInterrupt:
76 out.nl()
77 break
78 if a == '':
79 out.nl()
80 break
81
82 a = a.strip()
83 if a.lower() in ('q', 'quit', 'exit'):
84 break
85 if not a:
86 continue
87
88 try:
89 a_index = int(a)
90 except ValueError:
91 a_index = None
92
93 if a_index is not None:
94 if a_index == 0:
95 break
96 if 0 < a_index and a_index <= len(all):
97 _AddI(all[a_index - 1])
98 continue
99
100 p = filter(lambda x: x.name == a or x.relpath == a, all)
101 if len(p) == 1:
102 _AddI(p[0])
103 continue
104 print 'Bye.'
105
106def _AddI(project):
107 p = GitCommand(project, ['add', '--interactive'], bare=False)
108 p.Wait()
diff --git a/subcmds/start.py b/subcmds/start.py
new file mode 100644
index 00000000..4eb3e476
--- /dev/null
+++ b/subcmds/start.py
@@ -0,0 +1,51 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys
17from command import Command
18from git_command import git
19
20class Start(Command):
21 common = True
22 helpSummary = "Start a new branch for development"
23 helpUsage = """
24%prog <newbranchname> [<project>...]
25
26This subcommand starts a new branch of development that is automatically
27pulled from a remote branch.
28
29It is equivalent to the following git commands:
30
31"git branch --track <newbranchname> m/<codeline>",
32or
33"git checkout --track -b <newbranchname> m/<codeline>".
34
35All three forms set up the config entries that repo bases some of its
36processing on. Use %prog or git branch or checkout with --track to ensure
37the configuration data is set up properly.
38
39"""
40
41 def Execute(self, opt, args):
42 if not args:
43 self.Usage()
44
45 nb = args[0]
46 if not git.check_ref_format('heads/%s' % nb):
47 print >>sys.stderr, "error: '%s' is not a valid name" % nb
48 sys.exit(1)
49
50 for project in self.GetProjects(args[1:]):
51 project.StartBranch(nb)
diff --git a/subcmds/status.py b/subcmds/status.py
new file mode 100644
index 00000000..1615b423
--- /dev/null
+++ b/subcmds/status.py
@@ -0,0 +1,27 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from command import PagedCommand
17
18class Status(PagedCommand):
19 common = True
20 helpSummary = "Show the working tree status"
21 helpUsage = """
22%prog [<project>...]
23"""
24
25 def Execute(self, opt, args):
26 for project in self.GetProjects(args):
27 project.PrintWorkTreeStatus()
diff --git a/subcmds/sync.py b/subcmds/sync.py
new file mode 100644
index 00000000..3eb44edf
--- /dev/null
+++ b/subcmds/sync.py
@@ -0,0 +1,150 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import re
18import subprocess
19import sys
20
21from git_command import GIT
22from command import Command
23from error import RepoChangedException, GitError
24from project import R_HEADS
25
26class Sync(Command):
27 common = True
28 helpSummary = "Update working tree to the latest revision"
29 helpUsage = """
30%prog [<project>...]
31"""
32 helpDescription = """
33The '%prog' command synchronizes local project directories
34with the remote repositories specified in the manifest. If a local
35project does not yet exist, it will clone a new local directory from
36the remote repository and set up tracking branches as specified in
37the manifest. If the local project already exists, '%prog'
38will update the remote branches and rebase any new local changes
39on top of the new remote changes.
40
41'%prog' will synchronize all projects listed at the command
42line. Projects can be specified either by name, or by a relative
43or absolute path to the project's local directory. If no projects
44are specified, '%prog' will synchronize all projects listed in
45the manifest.
46"""
47
48 def _Options(self, p):
49 p.add_option('--no-repo-verify',
50 dest='no_repo_verify', action='store_true',
51 help='do not verify repo source code')
52
53 def _Fetch(self, *projects):
54 fetched = set()
55 for project in projects:
56 if project.Sync_NetworkHalf():
57 fetched.add(project.gitdir)
58 else:
59 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
60 sys.exit(1)
61 return fetched
62
63 def Execute(self, opt, args):
64 rp = self.manifest.repoProject
65 rp.PreSync()
66
67 mp = self.manifest.manifestProject
68 mp.PreSync()
69
70 all = self.GetProjects(args, missing_ok=True)
71 fetched = self._Fetch(rp, mp, *all)
72
73 if rp.HasChanges:
74 print >>sys.stderr, 'info: A new version of repo is available'
75 print >>sys.stderr, ''
76 if opt.no_repo_verify or _VerifyTag(rp):
77 if not rp.Sync_LocalHalf():
78 sys.exit(1)
79 print >>sys.stderr, 'info: Restarting repo with latest version'
80 raise RepoChangedException()
81 else:
82 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
83
84 if mp.HasChanges:
85 if not mp.Sync_LocalHalf():
86 sys.exit(1)
87
88 self.manifest._Unload()
89 all = self.GetProjects(args, missing_ok=True)
90 missing = []
91 for project in all:
92 if project.gitdir not in fetched:
93 missing.append(project)
94 self._Fetch(*missing)
95
96 for project in all:
97 if not project.Sync_LocalHalf():
98 sys.exit(1)
99
100
101def _VerifyTag(project):
102 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
103 if not os.path.exists(gpg_dir):
104 print >>sys.stderr,\
105"""warning: GnuPG was not available during last "repo init"
106warning: Cannot automatically authenticate repo."""
107 return True
108
109 remote = project.GetRemote(project.remote.name)
110 ref = remote.ToLocal(project.revision)
111
112 try:
113 cur = project.bare_git.describe(ref)
114 except GitError:
115 cur = None
116
117 if not cur \
118 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
119 rev = project.revision
120 if rev.startswith(R_HEADS):
121 rev = rev[len(R_HEADS):]
122
123 print >>sys.stderr
124 print >>sys.stderr,\
125 "warning: project '%s' branch '%s' is not signed" \
126 % (project.name, rev)
127 return False
128
129 env = dict(os.environ)
130 env['GIT_DIR'] = project.gitdir
131 env['GNUPGHOME'] = gpg_dir
132
133 cmd = [GIT, 'tag', '-v', cur]
134 proc = subprocess.Popen(cmd,
135 stdout = subprocess.PIPE,
136 stderr = subprocess.PIPE,
137 env = env)
138 out = proc.stdout.read()
139 proc.stdout.close()
140
141 err = proc.stderr.read()
142 proc.stderr.close()
143
144 if proc.wait() != 0:
145 print >>sys.stderr
146 print >>sys.stderr, out
147 print >>sys.stderr, err
148 print >>sys.stderr
149 return False
150 return True
diff --git a/subcmds/upload.py b/subcmds/upload.py
new file mode 100644
index 00000000..ad05050e
--- /dev/null
+++ b/subcmds/upload.py
@@ -0,0 +1,180 @@
1#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import re
17import sys
18
19from command import InteractiveCommand
20from editor import Editor
21from error import UploadError
22
23def _die(fmt, *args):
24 msg = fmt % args
25 print >>sys.stderr, 'error: %s' % msg
26 sys.exit(1)
27
28class Upload(InteractiveCommand):
29 common = True
30 helpSummary = "Upload changes for code review"
31 helpUsage="""
32%prog [<project>]...
33"""
34 helpDescription = """
35The '%prog' command is used to send changes to the Gerrit code
36review system. It searches for changes in local projects that do
37not yet exist in the corresponding remote repository. If multiple
38changes are found, '%prog' opens an editor to allow the
39user to choose which change to upload. After a successful upload,
40repo prints the URL for the change in the Gerrit code review system.
41
42'%prog' searches for uploadable changes in all projects listed
43at the command line. Projects can be specified either by name, or
44by a relative or absolute path to the project's local directory. If
45no projects are specified, '%prog' will search for uploadable
46changes in all projects listed in the manifest.
47"""
48
49 def _SingleBranch(self, branch):
50 project = branch.project
51 name = branch.name
52 date = branch.date
53 list = branch.commits
54
55 print 'Upload project %s/:' % project.relpath
56 print ' branch %s (%2d commit%s, %s):' % (
57 name,
58 len(list),
59 len(list) != 1 and 's' or '',
60 date)
61 for commit in list:
62 print ' %s' % commit
63
64 sys.stdout.write('(y/n)? ')
65 answer = sys.stdin.readline().strip()
66 if answer in ('y', 'Y', 'yes', '1', 'true', 't'):
67 self._UploadAndReport([branch])
68 else:
69 _die("upload aborted by user")
70
71 def _MultipleBranches(self, pending):
72 projects = {}
73 branches = {}
74
75 script = []
76 script.append('# Uncomment the branches to upload:')
77 for project, avail in pending:
78 script.append('#')
79 script.append('# project %s/:' % project.relpath)
80
81 b = {}
82 for branch in avail:
83 name = branch.name
84 date = branch.date
85 list = branch.commits
86
87 if b:
88 script.append('#')
89 script.append('# branch %s (%2d commit%s, %s):' % (
90 name,
91 len(list),
92 len(list) != 1 and 's' or '',
93 date))
94 for commit in list:
95 script.append('# %s' % commit)
96 b[name] = branch
97
98 projects[project.relpath] = project
99 branches[project.name] = b
100 script.append('')
101
102 script = Editor.EditString("\n".join(script)).split("\n")
103
104 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
105 branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
106
107 project = None
108 todo = []
109
110 for line in script:
111 m = project_re.match(line)
112 if m:
113 name = m.group(1)
114 project = projects.get(name)
115 if not project:
116 _die('project %s not available for upload', name)
117 continue
118
119 m = branch_re.match(line)
120 if m:
121 name = m.group(1)
122 if not project:
123 _die('project for branch %s not in script', name)
124 branch = branches[project.name].get(name)
125 if not branch:
126 _die('branch %s not in %s', name, project.relpath)
127 todo.append(branch)
128 if not todo:
129 _die("nothing uncommented for upload")
130 self._UploadAndReport(todo)
131
132 def _UploadAndReport(self, todo):
133 have_errors = False
134 for branch in todo:
135 try:
136 branch.UploadForReview()
137 branch.uploaded = True
138 except UploadError, e:
139 branch.error = e
140 branch.uploaded = False
141 have_errors = True
142
143 print >>sys.stderr, ''
144 print >>sys.stderr, '--------------------------------------------'
145
146 if have_errors:
147 for branch in todo:
148 if not branch.uploaded:
149 print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
150 branch.project.relpath + '/', \
151 branch.name, \
152 branch.error)
153 print >>sys.stderr, ''
154
155 for branch in todo:
156 if branch.uploaded:
157 print >>sys.stderr, '[OK ] %-15s %s' % (
158 branch.project.relpath + '/',
159 branch.name)
160 print >>sys.stderr, '%s' % branch.tip_url
161 print >>sys.stderr, ''
162
163 if have_errors:
164 sys.exit(1)
165
166 def Execute(self, opt, args):
167 project_list = self.GetProjects(args)
168 pending = []
169
170 for project in project_list:
171 avail = project.GetUploadableBranches()
172 if avail:
173 pending.append((project, avail))
174
175 if not pending:
176 print >>sys.stdout, "no branches ready for upload"
177 elif len(pending) == 1 and len(pending[0][1]) == 1:
178 self._SingleBranch(pending[0][1][0])
179 else:
180 self._MultipleBranches(pending)