##// END OF EJS Templates
fix(mercurial): dropped support for depreacted hgsubversion fixes RCCE-12
super-admin -
r1190:cb9d8354 default
parent child Browse files
Show More
@@ -1,242 +1,255 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2023 RhodeCode GmbH
2 # Copyright (C) 2014-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
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
18 import os
18 import os
19 import logging
19 import logging
20 import itertools
20 import itertools
21
21
22 import mercurial
22 import mercurial
23 import mercurial.error
23 import mercurial.error
24 import mercurial.wireprotoserver
24 import mercurial.wireprotoserver
25 import mercurial.hgweb.common
25 import mercurial.hgweb.common
26 import mercurial.hgweb.hgweb_mod
26 import mercurial.hgweb.hgweb_mod
27 import webob.exc
27 import webob.exc
28
28
29 from vcsserver import pygrack, exceptions, settings, git_lfs
29 from vcsserver import pygrack, exceptions, settings, git_lfs
30 from vcsserver.str_utils import ascii_bytes, safe_bytes
30 from vcsserver.str_utils import ascii_bytes, safe_bytes
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 # propagated from mercurial documentation
35 # propagated from mercurial documentation
36 HG_UI_SECTIONS = [
36 HG_UI_SECTIONS = [
37 'alias', 'auth', 'decode/encode', 'defaults', 'diff', 'email', 'extensions',
37 'alias', 'auth', 'decode/encode', 'defaults', 'diff', 'email', 'extensions',
38 'format', 'merge-patterns', 'merge-tools', 'hooks', 'http_proxy', 'smtp',
38 'format', 'merge-patterns', 'merge-tools', 'hooks', 'http_proxy', 'smtp',
39 'patch', 'paths', 'profiling', 'server', 'trusted', 'ui', 'web',
39 'patch', 'paths', 'profiling', 'server', 'trusted', 'ui', 'web',
40 ]
40 ]
41
41
42
42
43 class HgWeb(mercurial.hgweb.hgweb_mod.hgweb):
43 class HgWeb(mercurial.hgweb.hgweb_mod.hgweb):
44 """Extension of hgweb that simplifies some functions."""
44 """Extension of hgweb that simplifies some functions."""
45
45
46 def _get_view(self, repo):
46 def _get_view(self, repo):
47 """Views are not supported."""
47 """Views are not supported."""
48 return repo
48 return repo
49
49
50 def loadsubweb(self):
50 def loadsubweb(self):
51 """The result is only used in the templater method which is not used."""
51 """The result is only used in the templater method which is not used."""
52 return None
52 return None
53
53
54 def run(self):
54 def run(self):
55 """Unused function so raise an exception if accidentally called."""
55 """Unused function so raise an exception if accidentally called."""
56 raise NotImplementedError
56 raise NotImplementedError
57
57
58 def templater(self, req):
58 def templater(self, req):
59 """Function used in an unreachable code path.
59 """Function used in an unreachable code path.
60
60
61 This code is unreachable because we guarantee that the HTTP request,
61 This code is unreachable because we guarantee that the HTTP request,
62 corresponds to a Mercurial command. See the is_hg method. So, we are
62 corresponds to a Mercurial command. See the is_hg method. So, we are
63 never going to get a user-visible url.
63 never going to get a user-visible url.
64 """
64 """
65 raise NotImplementedError
65 raise NotImplementedError
66
66
67 def archivelist(self, nodeid):
67 def archivelist(self, nodeid):
68 """Unused function so raise an exception if accidentally called."""
68 """Unused function so raise an exception if accidentally called."""
69 raise NotImplementedError
69 raise NotImplementedError
70
70
71 def __call__(self, environ, start_response):
71 def __call__(self, environ, start_response):
72 """Run the WSGI application.
72 """Run the WSGI application.
73
73
74 This may be called by multiple threads.
74 This may be called by multiple threads.
75 """
75 """
76 from mercurial.hgweb import request as requestmod
76 from mercurial.hgweb import request as requestmod
77 req = requestmod.parserequestfromenv(environ)
77 req = requestmod.parserequestfromenv(environ)
78 res = requestmod.wsgiresponse(req, start_response)
78 res = requestmod.wsgiresponse(req, start_response)
79 gen = self.run_wsgi(req, res)
79 gen = self.run_wsgi(req, res)
80
80
81 first_chunk = None
81 first_chunk = None
82
82
83 try:
83 try:
84 data = next(gen)
84 data = next(gen)
85
85
86 def first_chunk():
86 def first_chunk():
87 yield data
87 yield data
88 except StopIteration:
88 except StopIteration:
89 pass
89 pass
90
90
91 if first_chunk:
91 if first_chunk:
92 return itertools.chain(first_chunk(), gen)
92 return itertools.chain(first_chunk(), gen)
93 return gen
93 return gen
94
94
95 def _runwsgi(self, req, res, repo):
95 def _runwsgi(self, req, res, repo):
96
96
97 cmd = req.qsparams.get(b'cmd', '')
97 cmd = req.qsparams.get(b'cmd', '')
98 if not mercurial.wireprotoserver.iscmd(cmd):
98 if not mercurial.wireprotoserver.iscmd(cmd):
99 # NOTE(marcink): for unsupported commands, we return bad request
99 # NOTE(marcink): for unsupported commands, we return bad request
100 # internally from HG
100 # internally from HG
101 log.warning('cmd: `%s` is not supported by the mercurial wireprotocol v1', cmd)
101 log.warning('cmd: `%s` is not supported by the mercurial wireprotocol v1', cmd)
102 from mercurial.hgweb.common import statusmessage
102 from mercurial.hgweb.common import statusmessage
103 res.status = statusmessage(mercurial.hgweb.common.HTTP_BAD_REQUEST)
103 res.status = statusmessage(mercurial.hgweb.common.HTTP_BAD_REQUEST)
104 res.setbodybytes(b'')
104 res.setbodybytes(b'')
105 return res.sendresponse()
105 return res.sendresponse()
106
106
107 return super()._runwsgi(req, res, repo)
107 return super()._runwsgi(req, res, repo)
108
108
109
109
110 def sanitize_hg_ui(baseui):
111 # NOTE(marcink): since python3 hgsubversion is deprecated.
112 # From old installations we might still have this set enabled
113 # we explicitly remove this now here to make sure it wont propagate further
114
115 if baseui.config(b'extensions', b'hgsubversion') is not None:
116 for cfg in (baseui._ocfg, baseui._tcfg, baseui._ucfg):
117 if b'extensions' in cfg:
118 if b'hgsubversion' in cfg[b'extensions']:
119 del cfg[b'extensions'][b'hgsubversion']
120
121
110 def make_hg_ui_from_config(repo_config):
122 def make_hg_ui_from_config(repo_config):
111 baseui = mercurial.ui.ui()
123 baseui = mercurial.ui.ui()
112
124
113 # clean the baseui object
125 # clean the baseui object
114 baseui._ocfg = mercurial.config.config()
126 baseui._ocfg = mercurial.config.config()
115 baseui._ucfg = mercurial.config.config()
127 baseui._ucfg = mercurial.config.config()
116 baseui._tcfg = mercurial.config.config()
128 baseui._tcfg = mercurial.config.config()
117
129
118 for section, option, value in repo_config:
130 for section, option, value in repo_config:
119 baseui.setconfig(
131 baseui.setconfig(
120 ascii_bytes(section, allow_bytes=True),
132 ascii_bytes(section, allow_bytes=True),
121 ascii_bytes(option, allow_bytes=True),
133 ascii_bytes(option, allow_bytes=True),
122 ascii_bytes(value, allow_bytes=True))
134 ascii_bytes(value, allow_bytes=True))
123
135
124 # make our hgweb quiet so it doesn't print output
136 # make our hgweb quiet so it doesn't print output
125 baseui.setconfig(b'ui', b'quiet', b'true')
137 baseui.setconfig(b'ui', b'quiet', b'true')
126
138
127 return baseui
139 return baseui
128
140
129
141
130 def update_hg_ui_from_hgrc(baseui, repo_path):
142 def update_hg_ui_from_hgrc(baseui, repo_path):
131 path = os.path.join(repo_path, '.hg', 'hgrc')
143 path = os.path.join(repo_path, '.hg', 'hgrc')
132
144
133 if not os.path.isfile(path):
145 if not os.path.isfile(path):
134 log.debug('hgrc file is not present at %s, skipping...', path)
146 log.debug('hgrc file is not present at %s, skipping...', path)
135 return
147 return
136 log.debug('reading hgrc from %s', path)
148 log.debug('reading hgrc from %s', path)
137 cfg = mercurial.config.config()
149 cfg = mercurial.config.config()
138 cfg.read(ascii_bytes(path))
150 cfg.read(ascii_bytes(path))
139 for section in HG_UI_SECTIONS:
151 for section in HG_UI_SECTIONS:
140 for k, v in cfg.items(section):
152 for k, v in cfg.items(section):
141 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
153 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
142 baseui.setconfig(
154 baseui.setconfig(
143 ascii_bytes(section, allow_bytes=True),
155 ascii_bytes(section, allow_bytes=True),
144 ascii_bytes(k, allow_bytes=True),
156 ascii_bytes(k, allow_bytes=True),
145 ascii_bytes(v, allow_bytes=True))
157 ascii_bytes(v, allow_bytes=True))
146
158
147
159
148 def create_hg_wsgi_app(repo_path, repo_name, config):
160 def create_hg_wsgi_app(repo_path, repo_name, config):
149 """
161 """
150 Prepares a WSGI application to handle Mercurial requests.
162 Prepares a WSGI application to handle Mercurial requests.
151
163
152 :param config: is a list of 3-item tuples representing a ConfigObject
164 :param config: is a list of 3-item tuples representing a ConfigObject
153 (it is the serialized version of the config object).
165 (it is the serialized version of the config object).
154 """
166 """
155 log.debug("Creating Mercurial WSGI application")
167 log.debug("Creating Mercurial WSGI application")
156
168
157 baseui = make_hg_ui_from_config(config)
169 baseui = make_hg_ui_from_config(config)
158 update_hg_ui_from_hgrc(baseui, repo_path)
170 update_hg_ui_from_hgrc(baseui, repo_path)
171 sanitize_hg_ui(baseui)
159
172
160 try:
173 try:
161 return HgWeb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
174 return HgWeb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
162 except mercurial.error.RequirementError as e:
175 except mercurial.error.RequirementError as e:
163 raise exceptions.RequirementException(e)(e)
176 raise exceptions.RequirementException(e)(e)
164
177
165
178
166 class GitHandler:
179 class GitHandler:
167 """
180 """
168 Handler for Git operations like push/pull etc
181 Handler for Git operations like push/pull etc
169 """
182 """
170 def __init__(self, repo_location, repo_name, git_path, update_server_info,
183 def __init__(self, repo_location, repo_name, git_path, update_server_info,
171 extras):
184 extras):
172 if not os.path.isdir(repo_location):
185 if not os.path.isdir(repo_location):
173 raise OSError(repo_location)
186 raise OSError(repo_location)
174 self.content_path = repo_location
187 self.content_path = repo_location
175 self.repo_name = repo_name
188 self.repo_name = repo_name
176 self.repo_location = repo_location
189 self.repo_location = repo_location
177 self.extras = extras
190 self.extras = extras
178 self.git_path = git_path
191 self.git_path = git_path
179 self.update_server_info = update_server_info
192 self.update_server_info = update_server_info
180
193
181 def __call__(self, environ, start_response):
194 def __call__(self, environ, start_response):
182 app = webob.exc.HTTPNotFound()
195 app = webob.exc.HTTPNotFound()
183 candidate_paths = (
196 candidate_paths = (
184 self.content_path, os.path.join(self.content_path, '.git'))
197 self.content_path, os.path.join(self.content_path, '.git'))
185
198
186 for content_path in candidate_paths:
199 for content_path in candidate_paths:
187 try:
200 try:
188 app = pygrack.GitRepository(
201 app = pygrack.GitRepository(
189 self.repo_name, content_path, self.git_path,
202 self.repo_name, content_path, self.git_path,
190 self.update_server_info, self.extras)
203 self.update_server_info, self.extras)
191 break
204 break
192 except OSError:
205 except OSError:
193 continue
206 continue
194
207
195 return app(environ, start_response)
208 return app(environ, start_response)
196
209
197
210
198 def create_git_wsgi_app(repo_path, repo_name, config):
211 def create_git_wsgi_app(repo_path, repo_name, config):
199 """
212 """
200 Creates a WSGI application to handle Git requests.
213 Creates a WSGI application to handle Git requests.
201
214
202 :param config: is a dictionary holding the extras.
215 :param config: is a dictionary holding the extras.
203 """
216 """
204 git_path = settings.GIT_EXECUTABLE
217 git_path = settings.GIT_EXECUTABLE
205 update_server_info = config.pop('git_update_server_info')
218 update_server_info = config.pop('git_update_server_info')
206 app = GitHandler(
219 app = GitHandler(
207 repo_path, repo_name, git_path, update_server_info, config)
220 repo_path, repo_name, git_path, update_server_info, config)
208
221
209 return app
222 return app
210
223
211
224
212 class GitLFSHandler:
225 class GitLFSHandler:
213 """
226 """
214 Handler for Git LFS operations
227 Handler for Git LFS operations
215 """
228 """
216
229
217 def __init__(self, repo_location, repo_name, git_path, update_server_info,
230 def __init__(self, repo_location, repo_name, git_path, update_server_info,
218 extras):
231 extras):
219 if not os.path.isdir(repo_location):
232 if not os.path.isdir(repo_location):
220 raise OSError(repo_location)
233 raise OSError(repo_location)
221 self.content_path = repo_location
234 self.content_path = repo_location
222 self.repo_name = repo_name
235 self.repo_name = repo_name
223 self.repo_location = repo_location
236 self.repo_location = repo_location
224 self.extras = extras
237 self.extras = extras
225 self.git_path = git_path
238 self.git_path = git_path
226 self.update_server_info = update_server_info
239 self.update_server_info = update_server_info
227
240
228 def get_app(self, git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme):
241 def get_app(self, git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme):
229 app = git_lfs.create_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
242 app = git_lfs.create_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
230 return app
243 return app
231
244
232
245
233 def create_git_lfs_wsgi_app(repo_path, repo_name, config):
246 def create_git_lfs_wsgi_app(repo_path, repo_name, config):
234 git_path = settings.GIT_EXECUTABLE
247 git_path = settings.GIT_EXECUTABLE
235 update_server_info = config.pop(b'git_update_server_info')
248 update_server_info = config.pop(b'git_update_server_info')
236 git_lfs_enabled = config.pop(b'git_lfs_enabled')
249 git_lfs_enabled = config.pop(b'git_lfs_enabled')
237 git_lfs_store_path = config.pop(b'git_lfs_store_path')
250 git_lfs_store_path = config.pop(b'git_lfs_store_path')
238 git_lfs_http_scheme = config.pop(b'git_lfs_http_scheme', 'http')
251 git_lfs_http_scheme = config.pop(b'git_lfs_http_scheme', 'http')
239 app = GitLFSHandler(
252 app = GitLFSHandler(
240 repo_path, repo_name, git_path, update_server_info, config)
253 repo_path, repo_name, git_path, update_server_info, config)
241
254
242 return app.get_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
255 return app.get_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
General Comments 0
You need to be logged in to leave comments. Login now