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