##// END OF EJS Templates
Mercurial: fix stream ehxaustion problem....
marcink -
r249:8784eaf0 default
parent child Browse files
Show More
@@ -1,209 +1,229 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-2017 RodeCode GmbH
2 # Copyright (C) 2014-2017 RodeCode 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
21
21 import mercurial
22 import mercurial
22 import mercurial.error
23 import mercurial.error
23 import mercurial.hgweb.common
24 import mercurial.hgweb.common
24 import mercurial.hgweb.hgweb_mod
25 import mercurial.hgweb.hgweb_mod
25 import mercurial.hgweb.protocol
26 import mercurial.hgweb.protocol
26 import webob.exc
27 import webob.exc
27
28
28 from vcsserver import pygrack, exceptions, settings, git_lfs
29 from vcsserver import pygrack, exceptions, settings, git_lfs
29
30
30
31
31 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
32
33
33
34
34 # propagated from mercurial documentation
35 # propagated from mercurial documentation
35 HG_UI_SECTIONS = [
36 HG_UI_SECTIONS = [
36 'alias', 'auth', 'decode/encode', 'defaults', 'diff', 'email', 'extensions',
37 'alias', 'auth', 'decode/encode', 'defaults', 'diff', 'email', 'extensions',
37 'format', 'merge-patterns', 'merge-tools', 'hooks', 'http_proxy', 'smtp',
38 'format', 'merge-patterns', 'merge-tools', 'hooks', 'http_proxy', 'smtp',
38 'patch', 'paths', 'profiling', 'server', 'trusted', 'ui', 'web',
39 'patch', 'paths', 'profiling', 'server', 'trusted', 'ui', 'web',
39 ]
40 ]
40
41
41
42
42 class HgWeb(mercurial.hgweb.hgweb_mod.hgweb):
43 class HgWeb(mercurial.hgweb.hgweb_mod.hgweb):
43 """Extension of hgweb that simplifies some functions."""
44 """Extension of hgweb that simplifies some functions."""
44
45
45 def _get_view(self, repo):
46 def _get_view(self, repo):
46 """Views are not supported."""
47 """Views are not supported."""
47 return repo
48 return repo
48
49
49 def loadsubweb(self):
50 def loadsubweb(self):
50 """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."""
51 return None
52 return None
52
53
53 def run(self):
54 def run(self):
54 """Unused function so raise an exception if accidentally called."""
55 """Unused function so raise an exception if accidentally called."""
55 raise NotImplementedError
56 raise NotImplementedError
56
57
57 def templater(self, req):
58 def templater(self, req):
58 """Function used in an unreachable code path.
59 """Function used in an unreachable code path.
59
60
60 This code is unreachable because we guarantee that the HTTP request,
61 This code is unreachable because we guarantee that the HTTP request,
61 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
62 never going to get a user-visible url.
63 never going to get a user-visible url.
63 """
64 """
64 raise NotImplementedError
65 raise NotImplementedError
65
66
66 def archivelist(self, nodeid):
67 def archivelist(self, nodeid):
67 """Unused function so raise an exception if accidentally called."""
68 """Unused function so raise an exception if accidentally called."""
68 raise NotImplementedError
69 raise NotImplementedError
69
70
70 def run_wsgi(self, req):
71 def __call__(self, environ, start_response):
71 """Check the request has a valid command, failing fast otherwise."""
72 """Run the WSGI application.
73
74 This may be called by multiple threads.
75 """
76 req = mercurial.hgweb.request.wsgirequest(environ, start_response)
77 gen = self.run_wsgi(req)
78
79 first_chunk = None
80
81 try:
82 data = gen.next()
83 def first_chunk(): yield data
84 except StopIteration:
85 pass
86
87 if first_chunk:
88 return itertools.chain(first_chunk(), gen)
89 return gen
90
91 def _runwsgi(self, req, repo):
72 cmd = req.form.get('cmd', [''])[0]
92 cmd = req.form.get('cmd', [''])[0]
73 if not mercurial.hgweb.protocol.iscmd(cmd):
93 if not mercurial.hgweb.protocol.iscmd(cmd):
74 req.respond(
94 req.respond(
75 mercurial.hgweb.common.ErrorResponse(
95 mercurial.hgweb.common.ErrorResponse(
76 mercurial.hgweb.common.HTTP_BAD_REQUEST),
96 mercurial.hgweb.common.HTTP_BAD_REQUEST),
77 mercurial.hgweb.protocol.HGTYPE
97 mercurial.hgweb.protocol.HGTYPE
78 )
98 )
79 return ['']
99 return ['']
80
100
81 return super(HgWeb, self).run_wsgi(req)
101 return super(HgWeb, self)._runwsgi(req, repo)
82
102
83
103
84 def make_hg_ui_from_config(repo_config):
104 def make_hg_ui_from_config(repo_config):
85 baseui = mercurial.ui.ui()
105 baseui = mercurial.ui.ui()
86
106
87 # clean the baseui object
107 # clean the baseui object
88 baseui._ocfg = mercurial.config.config()
108 baseui._ocfg = mercurial.config.config()
89 baseui._ucfg = mercurial.config.config()
109 baseui._ucfg = mercurial.config.config()
90 baseui._tcfg = mercurial.config.config()
110 baseui._tcfg = mercurial.config.config()
91
111
92 for section, option, value in repo_config:
112 for section, option, value in repo_config:
93 baseui.setconfig(section, option, value)
113 baseui.setconfig(section, option, value)
94
114
95 # make our hgweb quiet so it doesn't print output
115 # make our hgweb quiet so it doesn't print output
96 baseui.setconfig('ui', 'quiet', 'true')
116 baseui.setconfig('ui', 'quiet', 'true')
97
117
98 return baseui
118 return baseui
99
119
100
120
101 def update_hg_ui_from_hgrc(baseui, repo_path):
121 def update_hg_ui_from_hgrc(baseui, repo_path):
102 path = os.path.join(repo_path, '.hg', 'hgrc')
122 path = os.path.join(repo_path, '.hg', 'hgrc')
103
123
104 if not os.path.isfile(path):
124 if not os.path.isfile(path):
105 log.debug('hgrc file is not present at %s, skipping...', path)
125 log.debug('hgrc file is not present at %s, skipping...', path)
106 return
126 return
107 log.debug('reading hgrc from %s', path)
127 log.debug('reading hgrc from %s', path)
108 cfg = mercurial.config.config()
128 cfg = mercurial.config.config()
109 cfg.read(path)
129 cfg.read(path)
110 for section in HG_UI_SECTIONS:
130 for section in HG_UI_SECTIONS:
111 for k, v in cfg.items(section):
131 for k, v in cfg.items(section):
112 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
132 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
113 baseui.setconfig(section, k, v)
133 baseui.setconfig(section, k, v)
114
134
115
135
116 def create_hg_wsgi_app(repo_path, repo_name, config):
136 def create_hg_wsgi_app(repo_path, repo_name, config):
117 """
137 """
118 Prepares a WSGI application to handle Mercurial requests.
138 Prepares a WSGI application to handle Mercurial requests.
119
139
120 :param config: is a list of 3-item tuples representing a ConfigObject
140 :param config: is a list of 3-item tuples representing a ConfigObject
121 (it is the serialized version of the config object).
141 (it is the serialized version of the config object).
122 """
142 """
123 log.debug("Creating Mercurial WSGI application")
143 log.debug("Creating Mercurial WSGI application")
124
144
125 baseui = make_hg_ui_from_config(config)
145 baseui = make_hg_ui_from_config(config)
126 update_hg_ui_from_hgrc(baseui, repo_path)
146 update_hg_ui_from_hgrc(baseui, repo_path)
127
147
128 try:
148 try:
129 return HgWeb(repo_path, name=repo_name, baseui=baseui)
149 return HgWeb(repo_path, name=repo_name, baseui=baseui)
130 except mercurial.error.RequirementError as exc:
150 except mercurial.error.RequirementError as exc:
131 raise exceptions.RequirementException(exc)
151 raise exceptions.RequirementException(exc)
132
152
133
153
134 class GitHandler(object):
154 class GitHandler(object):
135 """
155 """
136 Handler for Git operations like push/pull etc
156 Handler for Git operations like push/pull etc
137 """
157 """
138 def __init__(self, repo_location, repo_name, git_path, update_server_info,
158 def __init__(self, repo_location, repo_name, git_path, update_server_info,
139 extras):
159 extras):
140 if not os.path.isdir(repo_location):
160 if not os.path.isdir(repo_location):
141 raise OSError(repo_location)
161 raise OSError(repo_location)
142 self.content_path = repo_location
162 self.content_path = repo_location
143 self.repo_name = repo_name
163 self.repo_name = repo_name
144 self.repo_location = repo_location
164 self.repo_location = repo_location
145 self.extras = extras
165 self.extras = extras
146 self.git_path = git_path
166 self.git_path = git_path
147 self.update_server_info = update_server_info
167 self.update_server_info = update_server_info
148
168
149 def __call__(self, environ, start_response):
169 def __call__(self, environ, start_response):
150 app = webob.exc.HTTPNotFound()
170 app = webob.exc.HTTPNotFound()
151 candidate_paths = (
171 candidate_paths = (
152 self.content_path, os.path.join(self.content_path, '.git'))
172 self.content_path, os.path.join(self.content_path, '.git'))
153
173
154 for content_path in candidate_paths:
174 for content_path in candidate_paths:
155 try:
175 try:
156 app = pygrack.GitRepository(
176 app = pygrack.GitRepository(
157 self.repo_name, content_path, self.git_path,
177 self.repo_name, content_path, self.git_path,
158 self.update_server_info, self.extras)
178 self.update_server_info, self.extras)
159 break
179 break
160 except OSError:
180 except OSError:
161 continue
181 continue
162
182
163 return app(environ, start_response)
183 return app(environ, start_response)
164
184
165
185
166 def create_git_wsgi_app(repo_path, repo_name, config):
186 def create_git_wsgi_app(repo_path, repo_name, config):
167 """
187 """
168 Creates a WSGI application to handle Git requests.
188 Creates a WSGI application to handle Git requests.
169
189
170 :param config: is a dictionary holding the extras.
190 :param config: is a dictionary holding the extras.
171 """
191 """
172 git_path = settings.GIT_EXECUTABLE
192 git_path = settings.GIT_EXECUTABLE
173 update_server_info = config.pop('git_update_server_info')
193 update_server_info = config.pop('git_update_server_info')
174 app = GitHandler(
194 app = GitHandler(
175 repo_path, repo_name, git_path, update_server_info, config)
195 repo_path, repo_name, git_path, update_server_info, config)
176
196
177 return app
197 return app
178
198
179
199
180 class GitLFSHandler(object):
200 class GitLFSHandler(object):
181 """
201 """
182 Handler for Git LFS operations
202 Handler for Git LFS operations
183 """
203 """
184
204
185 def __init__(self, repo_location, repo_name, git_path, update_server_info,
205 def __init__(self, repo_location, repo_name, git_path, update_server_info,
186 extras):
206 extras):
187 if not os.path.isdir(repo_location):
207 if not os.path.isdir(repo_location):
188 raise OSError(repo_location)
208 raise OSError(repo_location)
189 self.content_path = repo_location
209 self.content_path = repo_location
190 self.repo_name = repo_name
210 self.repo_name = repo_name
191 self.repo_location = repo_location
211 self.repo_location = repo_location
192 self.extras = extras
212 self.extras = extras
193 self.git_path = git_path
213 self.git_path = git_path
194 self.update_server_info = update_server_info
214 self.update_server_info = update_server_info
195
215
196 def get_app(self, git_lfs_enabled, git_lfs_store_path):
216 def get_app(self, git_lfs_enabled, git_lfs_store_path):
197 app = git_lfs.create_app(git_lfs_enabled, git_lfs_store_path)
217 app = git_lfs.create_app(git_lfs_enabled, git_lfs_store_path)
198 return app
218 return app
199
219
200
220
201 def create_git_lfs_wsgi_app(repo_path, repo_name, config):
221 def create_git_lfs_wsgi_app(repo_path, repo_name, config):
202 git_path = settings.GIT_EXECUTABLE
222 git_path = settings.GIT_EXECUTABLE
203 update_server_info = config.pop('git_update_server_info')
223 update_server_info = config.pop('git_update_server_info')
204 git_lfs_enabled = config.pop('git_lfs_enabled')
224 git_lfs_enabled = config.pop('git_lfs_enabled')
205 git_lfs_store_path = config.pop('git_lfs_store_path')
225 git_lfs_store_path = config.pop('git_lfs_store_path')
206 app = GitLFSHandler(
226 app = GitLFSHandler(
207 repo_path, repo_name, git_path, update_server_info, config)
227 repo_path, repo_name, git_path, update_server_info, config)
208
228
209 return app.get_app(git_lfs_enabled, git_lfs_store_path)
229 return app.get_app(git_lfs_enabled, git_lfs_store_path)
General Comments 0
You need to be logged in to leave comments. Login now