##// END OF EJS Templates
chore(release): merged default into stable branch
super-admin -
r1301:433d8917 merge v5.3.0 stable
parent child Browse files
Show More
@@ -1,5 +1,5 b''
1 [bumpversion]
1 [bumpversion]
2 current_version = 5.2.1
2 current_version = 5.3.0
3 message = release: Bump version {current_version} to {new_version}
3 message = release: Bump version {current_version} to {new_version}
4
4
5 [bumpversion:file:vcsserver/VERSION]
5 [bumpversion:file:vcsserver/VERSION]
@@ -56,7 +56,7 b' pyramid==2.0.2'
56 venusian==3.0.0
56 venusian==3.0.0
57 webob==1.8.7
57 webob==1.8.7
58 zope.deprecation==5.0.0
58 zope.deprecation==5.0.0
59 zope.interface==6.4.post2
59 zope.interface==7.0.3
60 redis==5.1.0
60 redis==5.1.0
61 async-timeout==4.0.3
61 async-timeout==4.0.3
62 repoze.lru==0.7
62 repoze.lru==0.7
@@ -1,1 +1,1 b''
1 5.2.1 No newline at end of file
1 5.3.0
@@ -14,7 +14,7 b''
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17 import hashlib
18 import re
18 import re
19 import logging
19 import logging
20
20
@@ -60,6 +60,7 b' class AuthHeaderRequired:'
60 request = fargs[1]
60 request = fargs[1]
61 auth = request.authorization
61 auth = request.authorization
62 if not auth:
62 if not auth:
63 log.debug('No auth header found, returning 403')
63 return write_response_error(HTTPForbidden)
64 return write_response_error(HTTPForbidden)
64 return func(*fargs[1:], **fkwargs)
65 return func(*fargs[1:], **fkwargs)
65
66
@@ -159,8 +160,14 b' def lfs_objects_oid_upload(request):'
159 engine = store.get_engine(mode='wb')
160 engine = store.get_engine(mode='wb')
160 log.debug('LFS: starting chunked write of LFS oid: %s to storage', oid)
161 log.debug('LFS: starting chunked write of LFS oid: %s to storage', oid)
161
162
163 # validate if OID is not by any chance already in the store
164 if store.has_oid():
165 log.debug('LFS: oid %s exists in store', oid)
166 return {'upload': 'ok', 'state': 'in-store'}
167
162 body = request.environ['wsgi.input']
168 body = request.environ['wsgi.input']
163
169
170 digest = hashlib.sha256()
164 with engine as f:
171 with engine as f:
165 blksize = 64 * 1024 # 64kb
172 blksize = 64 * 1024 # 64kb
166 while True:
173 while True:
@@ -174,10 +181,17 b' def lfs_objects_oid_upload(request):'
174
181
175 if not chunk:
182 if not chunk:
176 break
183 break
184 f.write(chunk)
185 digest.update(chunk)
177
186
178 f.write(chunk)
187 hex_digest = digest.hexdigest()
188 digest_check = hex_digest == oid
189 if not digest_check:
190 engine.cleanup() # trigger cleanup so we don't save mismatch OID into the store
191 return write_response_error(
192 HTTPBadRequest, f'oid {oid} does not match expected sha {hex_digest}')
179
193
180 return {'upload': 'ok'}
194 return {'upload': 'ok', 'state': 'written'}
181
195
182
196
183 def lfs_objects_oid_download(request):
197 def lfs_objects_oid_download(request):
@@ -222,12 +236,10 b' def lfs_objects_verify(request):'
222
236
223 store_size = store.size_oid()
237 store_size = store.size_oid()
224 if store_size != size:
238 if store_size != size:
225 msg = 'requested file size mismatch store size:{} requested:{}'.format(
239 msg = f'requested file size mismatch store size:{store_size} requested:{size}'
226 store_size, size)
240 return write_response_error(HTTPUnprocessableEntity, msg)
227 return write_response_error(
228 HTTPUnprocessableEntity, msg)
229
241
230 return {'message': {'size': 'ok', 'in_store': 'ok'}}
242 return {'message': {'size': store_size, 'oid': oid}}
231
243
232
244
233 def lfs_objects_lock(request):
245 def lfs_objects_lock(request):
@@ -133,12 +133,16 b' class LFSOidStore:'
133 """
133 """
134
134
135 class StoreEngine:
135 class StoreEngine:
136 _cleanup = None
136 def __init__(self, mode, store_path, oid_path, tmp_oid_path):
137 def __init__(self, mode, store_path, oid_path, tmp_oid_path):
137 self.mode = mode
138 self.mode = mode
138 self.store_path = store_path
139 self.store_path = store_path
139 self.oid_path = oid_path
140 self.oid_path = oid_path
140 self.tmp_oid_path = tmp_oid_path
141 self.tmp_oid_path = tmp_oid_path
141
142
143 def cleanup(self):
144 self._cleanup = True
145
142 def __enter__(self):
146 def __enter__(self):
143 if not os.path.isdir(self.store_path):
147 if not os.path.isdir(self.store_path):
144 os.makedirs(self.store_path)
148 os.makedirs(self.store_path)
@@ -149,9 +153,13 b' class LFSOidStore:'
149 return fd
153 return fd
150
154
151 def __exit__(self, exc_type, exc_value, traceback):
155 def __exit__(self, exc_type, exc_value, traceback):
156 self.fd.close()
157
158 if self._cleanup is None:
152 # close tmp file, and rename to final destination
159 # close tmp file, and rename to final destination
153 self.fd.close()
154 shutil.move(self.tmp_oid_path, self.oid_path)
160 shutil.move(self.tmp_oid_path, self.oid_path)
161 else:
162 os.remove(self.tmp_oid_path)
155
163
156 return StoreEngine(
164 return StoreEngine(
157 mode, self.store_path, self.oid_path, self.tmp_oid_path)
165 mode, self.store_path, self.oid_path, self.tmp_oid_path)
@@ -100,22 +100,27 b' class TestLFSApplication:'
100
100
101 def test_app_batch_api_download_missing_object(
101 def test_app_batch_api_download_missing_object(
102 self, git_lfs_app, http_auth):
102 self, git_lfs_app, http_auth):
103 params = {'operation': 'download',
103 params = {
104 'objects': [{'oid': '123', 'size': '1024'}]}
104 'operation': 'download',
105 'objects': [{'oid': '123', 'size': '1024'}]
106 }
105 response = git_lfs_app.post_json(
107 response = git_lfs_app.post_json(
106 '/repo/info/lfs/objects/batch', params=params,
108 '/repo/info/lfs/objects/batch', params=params,
107 extra_environ=http_auth)
109 extra_environ=http_auth)
108
110
109 expected_objects = [
111 expected_objects = [
110 {'authenticated': True,
112 {
111 'errors': {'error': {
112 'code': 404,
113 'message': 'object: 123 does not exist in store'}},
114 'oid': '123',
113 'oid': '123',
115 'size': '1024'}
114 'size': '1024',
115 'authenticated': True,
116 'errors': {'error': {'code': 404, 'message': 'object: 123 does not exist in store'}},
117 }
116 ]
118 ]
119
117 assert json.loads(response.text) == {
120 assert json.loads(response.text) == {
118 'objects': expected_objects, 'transfer': 'basic'}
121 'objects': expected_objects,
122 'transfer': 'basic'
123 }
119
124
120 def test_app_batch_api_download(self, git_lfs_app, http_auth):
125 def test_app_batch_api_download(self, git_lfs_app, http_auth):
121 oid = '456'
126 oid = '456'
@@ -142,7 +147,9 b' class TestLFSApplication:'
142 'size': '1024'}
147 'size': '1024'}
143 ]
148 ]
144 assert json.loads(response.text) == {
149 assert json.loads(response.text) == {
145 'objects': expected_objects, 'transfer': 'basic'}
150 'objects': expected_objects,
151 'transfer': 'basic'
152 }
146
153
147 def test_app_batch_api_upload(self, git_lfs_app, http_auth):
154 def test_app_batch_api_upload(self, git_lfs_app, http_auth):
148 params = {'operation': 'upload',
155 params = {'operation': 'upload',
@@ -151,21 +158,31 b' class TestLFSApplication:'
151 '/repo/info/lfs/objects/batch', params=params,
158 '/repo/info/lfs/objects/batch', params=params,
152 extra_environ=http_auth)
159 extra_environ=http_auth)
153 expected_objects = [
160 expected_objects = [
154 {'authenticated': True,
161 {
162 'authenticated': True,
155 'actions': {
163 'actions': {
156 'upload': {
164 'upload': {
157 'header': {'Authorization': 'Basic XXXXX',
165 'header': {
158 'Transfer-Encoding': 'chunked'},
166 'Authorization': 'Basic XXXXX',
159 'href': 'http://localhost/repo/info/lfs/objects/123'},
167 'Transfer-Encoding': 'chunked'
168 },
169 'href': 'http://localhost/repo/info/lfs/objects/123'
170 },
160 'verify': {
171 'verify': {
161 'header': {'Authorization': 'Basic XXXXX'},
172 'header': {
162 'href': 'http://localhost/repo/info/lfs/verify'}
173 'Authorization': 'Basic XXXXX'
174 },
175 'href': 'http://localhost/repo/info/lfs/verify'
176 }
163 },
177 },
164 'oid': '123',
178 'oid': '123',
165 'size': '1024'}
179 'size': '1024'
180 }
166 ]
181 ]
167 assert json.loads(response.text) == {
182 assert json.loads(response.text) == {
168 'objects': expected_objects, 'transfer': 'basic'}
183 'objects': expected_objects,
184 'transfer': 'basic'
185 }
169
186
170 def test_app_batch_api_upload_for_https(self, git_lfs_https_app, http_auth):
187 def test_app_batch_api_upload_for_https(self, git_lfs_https_app, http_auth):
171 params = {'operation': 'upload',
188 params = {'operation': 'upload',
@@ -206,7 +223,8 b' class TestLFSApplication:'
206 status=404)
223 status=404)
207
224
208 assert json.loads(response.text) == {
225 assert json.loads(response.text) == {
209 'message': 'oid `missing` does not exists in store'}
226 'message': 'oid `missing` does not exists in store'
227 }
210
228
211 def test_app_verify_api_size_mismatch(self, git_lfs_app):
229 def test_app_verify_api_size_mismatch(self, git_lfs_app):
212 oid = 'existing'
230 oid = 'existing'
@@ -221,8 +239,8 b' class TestLFSApplication:'
221 '/repo/info/lfs/verify', params=params, status=422)
239 '/repo/info/lfs/verify', params=params, status=422)
222
240
223 assert json.loads(response.text) == {
241 assert json.loads(response.text) == {
224 'message': 'requested file size mismatch '
242 'message': 'requested file size mismatch store size:11 requested:1024'
225 'store size:11 requested:1024'}
243 }
226
244
227 def test_app_verify_api(self, git_lfs_app):
245 def test_app_verify_api(self, git_lfs_app):
228 oid = 'existing'
246 oid = 'existing'
@@ -237,13 +255,13 b' class TestLFSApplication:'
237 '/repo/info/lfs/verify', params=params)
255 '/repo/info/lfs/verify', params=params)
238
256
239 assert json.loads(response.text) == {
257 assert json.loads(response.text) == {
240 'message': {'size': 'ok', 'in_store': 'ok'}}
258 'message': {'size': 11, 'oid': oid}
259 }
241
260
242 def test_app_download_api_oid_not_existing(self, git_lfs_app):
261 def test_app_download_api_oid_not_existing(self, git_lfs_app):
243 oid = 'missing'
262 oid = 'missing'
244
263
245 response = git_lfs_app.get(
264 response = git_lfs_app.get(f'/repo/info/lfs/objects/{oid}', status=404)
246 '/repo/info/lfs/objects/{oid}'.format(oid=oid), status=404)
247
265
248 assert json.loads(response.text) == {
266 assert json.loads(response.text) == {
249 'message': 'requested file with oid `missing` not found in store'}
267 'message': 'requested file with oid `missing` not found in store'}
@@ -256,19 +274,37 b' class TestLFSApplication:'
256 with open(oid_path, 'wb') as f:
274 with open(oid_path, 'wb') as f:
257 f.write(safe_bytes('OID_CONTENT'))
275 f.write(safe_bytes('OID_CONTENT'))
258
276
259 response = git_lfs_app.get(
277 response = git_lfs_app.get(f'/repo/info/lfs/objects/{oid}')
260 '/repo/info/lfs/objects/{oid}'.format(oid=oid))
261 assert response
278 assert response
262
279
263 def test_app_upload(self, git_lfs_app):
280 def test_app_upload(self, git_lfs_app):
264 oid = 'uploaded'
281 oid = '65f23e22a9bfedda96929b3cfcb8b6d2fdd34a2e877ddb81f45d79ab05710e12'
265
282
266 response = git_lfs_app.put(
283 response = git_lfs_app.put(
267 '/repo/info/lfs/objects/{oid}'.format(oid=oid), params='CONTENT')
284 f'/repo/info/lfs/objects/{oid}', params='CONTENT')
268
285
269 assert json.loads(response.text) == {'upload': 'ok'}
286 assert json.loads(response.text) == {'upload': 'ok', 'state': 'written'}
270
287
271 # verify that we actually wrote that OID
288 # verify that we actually wrote that OID
272 oid_path = LFSOidStore(oid=oid, repo=None, store_location=git_lfs_app._store).oid_path
289 oid_path = LFSOidStore(oid=oid, repo=None, store_location=git_lfs_app._store).oid_path
273 assert os.path.isfile(oid_path)
290 assert os.path.isfile(oid_path)
274 assert 'CONTENT' == open(oid_path).read()
291 assert 'CONTENT' == open(oid_path).read()
292
293 response = git_lfs_app.put(
294 f'/repo/info/lfs/objects/{oid}', params='CONTENT')
295
296 assert json.loads(response.text) == {'upload': 'ok', 'state': 'in-store'}
297
298
299 def test_app_upload_wrong_sha(self, git_lfs_app):
300 oid = 'i-am-a-wrong-sha'
301
302 response = git_lfs_app.put(f'/repo/info/lfs/objects/{oid}', params='CONTENT', status=400)
303
304 assert json.loads(response.text) == {
305 'message': 'oid i-am-a-wrong-sha does not match expected sha '
306 '65f23e22a9bfedda96929b3cfcb8b6d2fdd34a2e877ddb81f45d79ab05710e12'}
307
308 # check this OID wasn't written to store
309 response = git_lfs_app.get(f'/repo/info/lfs/objects/{oid}', status=404)
310 assert json.loads(response.text) == {'message': 'requested file with oid `i-am-a-wrong-sha` not found in store'}
@@ -24,7 +24,7 b' from vcsserver.git_lfs.lib import OidHan'
24 @pytest.fixture()
24 @pytest.fixture()
25 def lfs_store(tmpdir):
25 def lfs_store(tmpdir):
26 repo = 'test'
26 repo = 'test'
27 oid = '123456789'
27 oid = '65f23e22a9bfedda96929b3cfcb8b6d2fdd34a2e877ddb81f45d79ab05710e12'
28 store = LFSOidStore(oid=oid, repo=repo, store_location=str(tmpdir))
28 store = LFSOidStore(oid=oid, repo=repo, store_location=str(tmpdir))
29 return store
29 return store
30
30
@@ -63,7 +63,8 b' class TestOidHandler:'
63 assert response is None
63 assert response is None
64 assert has_errors['error'] == {
64 assert has_errors['error'] == {
65 'code': 404,
65 'code': 404,
66 'message': 'object: 123456789 does not exist in store'}
66 'message': 'object: 65f23e22a9bfedda96929b3cfcb8b6d2fdd34a2e877ddb81f45d79ab05710e12 does not exist in store'
67 }
67
68
68 def test_download_oid(self, oid_handler):
69 def test_download_oid(self, oid_handler):
69 store = oid_handler.get_store()
70 store = oid_handler.get_store()
@@ -107,7 +107,9 b' class HooksCeleryClient:'
107 inquired_task = self.celery_app.signature(
107 inquired_task = self.celery_app.signature(
108 f'rhodecode.lib.celerylib.tasks.{method}'
108 f'rhodecode.lib.celerylib.tasks.{method}'
109 )
109 )
110 return inquired_task.delay(extras).get(timeout=self.TASK_TIMEOUT)
110 result = inquired_task.delay(extras).get(timeout=self.TASK_TIMEOUT)
111
112 return result
111
113
112
114
113 class HooksShadowRepoClient:
115 class HooksShadowRepoClient:
@@ -157,9 +159,11 b' class SvnMessageWriter(RemoteMessageWrit'
157 self.stderr.write(message)
159 self.stderr.write(message)
158
160
159
161
160 def _handle_exception(result):
162 def _maybe_handle_exception(result):
161 exception_class = result.get('exception')
163 exception_class = result.get('exception')
162 exception_traceback = result.get('exception_traceback')
164 exception_traceback = result.get('exception_traceback')
165 if not (exception_class and exception_traceback):
166 return
163 log.debug('Handling hook-call exception: %s', exception_class)
167 log.debug('Handling hook-call exception: %s', exception_class)
164
168
165 if exception_traceback:
169 if exception_traceback:
@@ -200,7 +204,7 b' def _call_hook(hook_name, extras, writer'
200 log.debug('Hooks, using client:%s', hooks_client)
204 log.debug('Hooks, using client:%s', hooks_client)
201 result = hooks_client(hook_name, extras)
205 result = hooks_client(hook_name, extras)
202 log.debug('Hooks got result: %s', result)
206 log.debug('Hooks got result: %s', result)
203 _handle_exception(result)
207 _maybe_handle_exception(result)
204 writer.write(result['output'])
208 writer.write(result['output'])
205
209
206 return result['status']
210 return result['status']
General Comments 0
You need to be logged in to leave comments. Login now