##// END OF EJS Templates
scripts: introduce source_format.py to fix up the module name in file headers
Mads Kiilerich -
r8549:f8971422 default
parent child Browse files
Show More
@@ -0,0 +1,24 b''
1 #!/usr/bin/env python3
2
3 # hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/source_format.py
4
5 import re
6 import sys
7
8
9 filenames = sys.argv[1:]
10
11 for fn in filenames:
12 with open(fn) as f:
13 org_content = f.read()
14
15 mod_name = fn[:-3] if fn.endswith('.py') else fn
16 mod_name = mod_name[:-9] if mod_name.endswith('/__init__') else mod_name
17 mod_name = mod_name.replace('/', '.')
18 def f(m):
19 return '"""\n%s\n%s\n' % (mod_name, '~' * len(mod_name))
20 new_content = re.sub(r'^"""\n(kallithea\..*\n)(~+\n)?', f, org_content, count=1, flags=re.MULTILINE)
21
22 if new_content != org_content:
23 with open(fn, 'w') as f:
24 f.write(new_content)
@@ -1,191 +1,191 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.bin.vcs_hooks
15 kallithea.bin.vcs_hooks
16 ~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Entry points for Kallithea hooking into Mercurial and Git.
18 Entry points for Kallithea hooking into Mercurial and Git.
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Aug 6, 2010
22 :created_on: Aug 6, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import os
28 import os
29 import sys
29 import sys
30
30
31 import mercurial.scmutil
31 import mercurial.scmutil
32 import paste.deploy
32 import paste.deploy
33
33
34 import kallithea
34 import kallithea
35 import kallithea.config.application
35 import kallithea.config.application
36 from kallithea.lib import hooks, webutils
36 from kallithea.lib import hooks, webutils
37 from kallithea.lib.utils2 import HookEnvironmentError, ascii_str, get_hook_environment, safe_bytes, safe_str
37 from kallithea.lib.utils2 import HookEnvironmentError, ascii_str, get_hook_environment, safe_bytes, safe_str
38 from kallithea.lib.vcs.backends.base import EmptyChangeset
38 from kallithea.lib.vcs.backends.base import EmptyChangeset
39 from kallithea.lib.vcs.utils.helpers import get_scm_size
39 from kallithea.lib.vcs.utils.helpers import get_scm_size
40 from kallithea.model import db
40 from kallithea.model import db
41
41
42
42
43 def repo_size(ui, repo, hooktype=None, **kwargs):
43 def repo_size(ui, repo, hooktype=None, **kwargs):
44 """Show size of Mercurial repository.
44 """Show size of Mercurial repository.
45
45
46 Called as Mercurial hook changegroup.repo_size after push.
46 Called as Mercurial hook changegroup.repo_size after push.
47 """
47 """
48 size_hg, size_root = get_scm_size('.hg', safe_str(repo.root))
48 size_hg, size_root = get_scm_size('.hg', safe_str(repo.root))
49
49
50 last_cs = repo[len(repo) - 1]
50 last_cs = repo[len(repo) - 1]
51
51
52 msg = ('Repository size .hg: %s Checkout: %s Total: %s\n'
52 msg = ('Repository size .hg: %s Checkout: %s Total: %s\n'
53 'Last revision is now r%s:%s\n') % (
53 'Last revision is now r%s:%s\n') % (
54 webutils.format_byte_size(size_hg),
54 webutils.format_byte_size(size_hg),
55 webutils.format_byte_size(size_root),
55 webutils.format_byte_size(size_root),
56 webutils.format_byte_size(size_hg + size_root),
56 webutils.format_byte_size(size_hg + size_root),
57 last_cs.rev(),
57 last_cs.rev(),
58 ascii_str(last_cs.hex())[:12],
58 ascii_str(last_cs.hex())[:12],
59 )
59 )
60 ui.status(safe_bytes(msg))
60 ui.status(safe_bytes(msg))
61
61
62
62
63 def pull_action(ui, repo, **kwargs):
63 def pull_action(ui, repo, **kwargs):
64 """Logs user pull action
64 """Logs user pull action
65
65
66 Called as Mercurial hook outgoing.kallithea_pull_action.
66 Called as Mercurial hook outgoing.kallithea_pull_action.
67 """
67 """
68 hooks.log_pull_action()
68 hooks.log_pull_action()
69
69
70
70
71 def push_action(ui, repo, node, node_last, **kwargs):
71 def push_action(ui, repo, node, node_last, **kwargs):
72 """
72 """
73 Register that changes have been added to the repo - log the action *and* invalidate caches.
73 Register that changes have been added to the repo - log the action *and* invalidate caches.
74 Note: This hook is not only logging, but also the side effect invalidating
74 Note: This hook is not only logging, but also the side effect invalidating
75 caches! The function should perhaps be renamed.
75 caches! The function should perhaps be renamed.
76
76
77 Called as Mercurial hook changegroup.kallithea_push_action .
77 Called as Mercurial hook changegroup.kallithea_push_action .
78
78
79 The pushed changesets is given by the revset 'node:node_last'.
79 The pushed changesets is given by the revset 'node:node_last'.
80 """
80 """
81 revs = [ascii_str(repo[r].hex()) for r in mercurial.scmutil.revrange(repo, [b'%s:%s' % (node, node_last)])]
81 revs = [ascii_str(repo[r].hex()) for r in mercurial.scmutil.revrange(repo, [b'%s:%s' % (node, node_last)])]
82 hooks.process_pushed_raw_ids(revs)
82 hooks.process_pushed_raw_ids(revs)
83
83
84
84
85 def _git_hook_environment(repo_path):
85 def _git_hook_environment(repo_path):
86 """
86 """
87 Create a light-weight environment for stand-alone scripts and return an UI and the
87 Create a light-weight environment for stand-alone scripts and return an UI and the
88 db repository.
88 db repository.
89
89
90 Git hooks are executed as subprocess of Git while Kallithea is waiting, and
90 Git hooks are executed as subprocess of Git while Kallithea is waiting, and
91 they thus need enough info to be able to create an app environment and
91 they thus need enough info to be able to create an app environment and
92 connect to the database.
92 connect to the database.
93 """
93 """
94 extras = get_hook_environment()
94 extras = get_hook_environment()
95
95
96 path_to_ini_file = extras['config']
96 path_to_ini_file = extras['config']
97 config = paste.deploy.appconfig('config:' + path_to_ini_file)
97 config = paste.deploy.appconfig('config:' + path_to_ini_file)
98 #logging.config.fileConfig(ini_file_path) # Note: we are in a different process - don't use configured logging
98 #logging.config.fileConfig(ini_file_path) # Note: we are in a different process - don't use configured logging
99 kallithea.config.application.make_app(config.global_conf, **config.local_conf)
99 kallithea.config.application.make_app(config.global_conf, **config.local_conf)
100
100
101 # fix if it's not a bare repo
101 # fix if it's not a bare repo
102 if repo_path.endswith(os.sep + '.git'):
102 if repo_path.endswith(os.sep + '.git'):
103 repo_path = repo_path[:-5]
103 repo_path = repo_path[:-5]
104
104
105 repo = db.Repository.get_by_full_path(repo_path)
105 repo = db.Repository.get_by_full_path(repo_path)
106 if not repo:
106 if not repo:
107 raise OSError('Repository %s not found in database' % repo_path)
107 raise OSError('Repository %s not found in database' % repo_path)
108
108
109 return repo
109 return repo
110
110
111
111
112 def pre_receive(repo_path, git_stdin_lines):
112 def pre_receive(repo_path, git_stdin_lines):
113 """Called from Git pre-receive hook.
113 """Called from Git pre-receive hook.
114 The returned value is used as hook exit code and must be 0.
114 The returned value is used as hook exit code and must be 0.
115 """
115 """
116 # Currently unused. TODO: remove?
116 # Currently unused. TODO: remove?
117 return 0
117 return 0
118
118
119
119
120 def post_receive(repo_path, git_stdin_lines):
120 def post_receive(repo_path, git_stdin_lines):
121 """Called from Git post-receive hook.
121 """Called from Git post-receive hook.
122 The returned value is used as hook exit code and must be 0.
122 The returned value is used as hook exit code and must be 0.
123 """
123 """
124 try:
124 try:
125 repo = _git_hook_environment(repo_path)
125 repo = _git_hook_environment(repo_path)
126 except HookEnvironmentError as e:
126 except HookEnvironmentError as e:
127 sys.stderr.write("Skipping Kallithea Git post-receive hook %r.\nGit was apparently not invoked by Kallithea: %s\n" % (sys.argv[0], e))
127 sys.stderr.write("Skipping Kallithea Git post-receive hook %r.\nGit was apparently not invoked by Kallithea: %s\n" % (sys.argv[0], e))
128 return 0
128 return 0
129
129
130 # the post push hook should never use the cached instance
130 # the post push hook should never use the cached instance
131 scm_repo = repo.scm_instance_no_cache()
131 scm_repo = repo.scm_instance_no_cache()
132
132
133 rev_data = []
133 rev_data = []
134 for l in git_stdin_lines:
134 for l in git_stdin_lines:
135 old_rev, new_rev, ref = l.strip().split(' ')
135 old_rev, new_rev, ref = l.strip().split(' ')
136 _ref_data = ref.split('/')
136 _ref_data = ref.split('/')
137 if _ref_data[1] in ['tags', 'heads']:
137 if _ref_data[1] in ['tags', 'heads']:
138 rev_data.append({'old_rev': old_rev,
138 rev_data.append({'old_rev': old_rev,
139 'new_rev': new_rev,
139 'new_rev': new_rev,
140 'ref': ref,
140 'ref': ref,
141 'type': _ref_data[1],
141 'type': _ref_data[1],
142 'name': '/'.join(_ref_data[2:])})
142 'name': '/'.join(_ref_data[2:])})
143
143
144 git_revs = []
144 git_revs = []
145 for push_ref in rev_data:
145 for push_ref in rev_data:
146 _type = push_ref['type']
146 _type = push_ref['type']
147 if _type == 'heads':
147 if _type == 'heads':
148 if push_ref['old_rev'] == EmptyChangeset().raw_id:
148 if push_ref['old_rev'] == EmptyChangeset().raw_id:
149 # update the symbolic ref if we push new repo
149 # update the symbolic ref if we push new repo
150 if scm_repo.is_empty():
150 if scm_repo.is_empty():
151 scm_repo._repo.refs.set_symbolic_ref(
151 scm_repo._repo.refs.set_symbolic_ref(
152 b'HEAD',
152 b'HEAD',
153 b'refs/heads/%s' % safe_bytes(push_ref['name']))
153 b'refs/heads/%s' % safe_bytes(push_ref['name']))
154
154
155 # build exclude list without the ref
155 # build exclude list without the ref
156 cmd = ['for-each-ref', '--format=%(refname)', 'refs/heads/*']
156 cmd = ['for-each-ref', '--format=%(refname)', 'refs/heads/*']
157 stdout = scm_repo.run_git_command(cmd)
157 stdout = scm_repo.run_git_command(cmd)
158 ref = push_ref['ref']
158 ref = push_ref['ref']
159 heads = [head for head in stdout.splitlines() if head != ref]
159 heads = [head for head in stdout.splitlines() if head != ref]
160 # now list the git revs while excluding from the list
160 # now list the git revs while excluding from the list
161 cmd = ['log', push_ref['new_rev'], '--reverse', '--pretty=format:%H']
161 cmd = ['log', push_ref['new_rev'], '--reverse', '--pretty=format:%H']
162 cmd.append('--not')
162 cmd.append('--not')
163 cmd.extend(heads) # empty list is ok
163 cmd.extend(heads) # empty list is ok
164 stdout = scm_repo.run_git_command(cmd)
164 stdout = scm_repo.run_git_command(cmd)
165 git_revs += stdout.splitlines()
165 git_revs += stdout.splitlines()
166
166
167 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
167 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
168 # delete branch case
168 # delete branch case
169 git_revs += ['delete_branch=>%s' % push_ref['name']]
169 git_revs += ['delete_branch=>%s' % push_ref['name']]
170 else:
170 else:
171 cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref,
171 cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref,
172 '--reverse', '--pretty=format:%H']
172 '--reverse', '--pretty=format:%H']
173 stdout = scm_repo.run_git_command(cmd)
173 stdout = scm_repo.run_git_command(cmd)
174 git_revs += stdout.splitlines()
174 git_revs += stdout.splitlines()
175
175
176 elif _type == 'tags':
176 elif _type == 'tags':
177 git_revs += ['tag=>%s' % push_ref['name']]
177 git_revs += ['tag=>%s' % push_ref['name']]
178
178
179 hooks.process_pushed_raw_ids(git_revs)
179 hooks.process_pushed_raw_ids(git_revs)
180
180
181 return 0
181 return 0
182
182
183
183
184 # Almost exactly like Mercurial contrib/hg-ssh:
184 # Almost exactly like Mercurial contrib/hg-ssh:
185 def rejectpush(ui, **kwargs):
185 def rejectpush(ui, **kwargs):
186 """Mercurial hook to be installed as pretxnopen and prepushkey for read-only repos.
186 """Mercurial hook to be installed as pretxnopen and prepushkey for read-only repos.
187 Return value 1 will make the hook fail and reject the push.
187 Return value 1 will make the hook fail and reject the push.
188 """
188 """
189 ex = get_hook_environment()
189 ex = get_hook_environment()
190 ui.warn(safe_bytes("Push access to %r denied\n" % ex.repository))
190 ui.warn(safe_bytes("Push access to %r denied\n" % ex.repository))
191 return 1
191 return 1
@@ -1,73 +1,73 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.middleware.https_fixup
15 kallithea.config.middleware.https_fixup
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 middleware to handle https correctly
18 middleware to handle https correctly
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: May 23, 2010
22 :created_on: May 23, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 from kallithea.lib.utils2 import asbool
29 from kallithea.lib.utils2 import asbool
30
30
31
31
32 class HttpsFixup(object):
32 class HttpsFixup(object):
33
33
34 def __init__(self, app, config):
34 def __init__(self, app, config):
35 self.application = app
35 self.application = app
36 self.config = config
36 self.config = config
37
37
38 def __call__(self, environ, start_response):
38 def __call__(self, environ, start_response):
39 self.__fixup(environ)
39 self.__fixup(environ)
40 debug = asbool(self.config.get('debug'))
40 debug = asbool(self.config.get('debug'))
41 is_ssl = environ['wsgi.url_scheme'] == 'https'
41 is_ssl = environ['wsgi.url_scheme'] == 'https'
42
42
43 def custom_start_response(status, headers, exc_info=None):
43 def custom_start_response(status, headers, exc_info=None):
44 if is_ssl and asbool(self.config.get('use_htsts')) and not debug:
44 if is_ssl and asbool(self.config.get('use_htsts')) and not debug:
45 headers.append(('Strict-Transport-Security',
45 headers.append(('Strict-Transport-Security',
46 'max-age=8640000; includeSubDomains'))
46 'max-age=8640000; includeSubDomains'))
47 return start_response(status, headers, exc_info)
47 return start_response(status, headers, exc_info)
48
48
49 return self.application(environ, custom_start_response)
49 return self.application(environ, custom_start_response)
50
50
51 def __fixup(self, environ):
51 def __fixup(self, environ):
52 """
52 """
53 Function to fixup the environ as needed. In order to use this
53 Function to fixup the environ as needed. In order to use this
54 middleware you should set this header inside your
54 middleware you should set this header inside your
55 proxy ie. nginx, apache etc.
55 proxy ie. nginx, apache etc.
56 """
56 """
57 # DETECT PROTOCOL !
57 # DETECT PROTOCOL !
58 if 'HTTP_X_URL_SCHEME' in environ:
58 if 'HTTP_X_URL_SCHEME' in environ:
59 proto = environ.get('HTTP_X_URL_SCHEME')
59 proto = environ.get('HTTP_X_URL_SCHEME')
60 elif 'HTTP_X_FORWARDED_SCHEME' in environ:
60 elif 'HTTP_X_FORWARDED_SCHEME' in environ:
61 proto = environ.get('HTTP_X_FORWARDED_SCHEME')
61 proto = environ.get('HTTP_X_FORWARDED_SCHEME')
62 elif 'HTTP_X_FORWARDED_PROTO' in environ:
62 elif 'HTTP_X_FORWARDED_PROTO' in environ:
63 proto = environ.get('HTTP_X_FORWARDED_PROTO')
63 proto = environ.get('HTTP_X_FORWARDED_PROTO')
64 else:
64 else:
65 proto = 'http'
65 proto = 'http'
66 org_proto = proto
66 org_proto = proto
67
67
68 # if we have force, just override
68 # if we have force, just override
69 if asbool(self.config.get('force_https')):
69 if asbool(self.config.get('force_https')):
70 proto = 'https'
70 proto = 'https'
71
71
72 environ['wsgi.url_scheme'] = proto
72 environ['wsgi.url_scheme'] = proto
73 environ['wsgi._org_proto'] = org_proto
73 environ['wsgi._org_proto'] = org_proto
@@ -1,41 +1,41 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.middleware.permanent_repo_url
15 kallithea.config.middleware.permanent_repo_url
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 middleware to handle permanent repo URLs, replacing PATH_INFO '/_123/yada' with
18 middleware to handle permanent repo URLs, replacing PATH_INFO '/_123/yada' with
19 '/name/of/repo/yada' after looking 123 up in the database.
19 '/name/of/repo/yada' after looking 123 up in the database.
20 """
20 """
21
21
22
22
23 from kallithea.lib.utils import fix_repo_id_name
23 from kallithea.lib.utils import fix_repo_id_name
24 from kallithea.lib.utils2 import safe_bytes, safe_str
24 from kallithea.lib.utils2 import safe_bytes, safe_str
25
25
26
26
27 class PermanentRepoUrl(object):
27 class PermanentRepoUrl(object):
28
28
29 def __init__(self, app, config):
29 def __init__(self, app, config):
30 self.application = app
30 self.application = app
31 self.config = config
31 self.config = config
32
32
33 def __call__(self, environ, start_response):
33 def __call__(self, environ, start_response):
34 # Extract path_info as get_path_info does, but do it explicitly because
34 # Extract path_info as get_path_info does, but do it explicitly because
35 # we also have to do the reverse operation when patching it back in
35 # we also have to do the reverse operation when patching it back in
36 path_info = safe_str(environ['PATH_INFO'].encode('latin1'))
36 path_info = safe_str(environ['PATH_INFO'].encode('latin1'))
37 if path_info.startswith('/'): # it must
37 if path_info.startswith('/'): # it must
38 path_info = '/' + fix_repo_id_name(path_info[1:])
38 path_info = '/' + fix_repo_id_name(path_info[1:])
39 environ['PATH_INFO'] = safe_bytes(path_info).decode('latin1')
39 environ['PATH_INFO'] = safe_bytes(path_info).decode('latin1')
40
40
41 return self.application(environ, start_response)
41 return self.application(environ, start_response)
@@ -1,227 +1,227 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.middleware.pygrack
15 kallithea.config.middleware.pygrack
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Python implementation of git-http-backend's Smart HTTP protocol
18 Python implementation of git-http-backend's Smart HTTP protocol
19
19
20 Based on original code from git_http_backend.py project.
20 Based on original code from git_http_backend.py project.
21
21
22 Copyright (c) 2010 Daniel Dotsenko <dotsa@hotmail.com>
22 Copyright (c) 2010 Daniel Dotsenko <dotsa@hotmail.com>
23 Copyright (c) 2012 Marcin Kuzminski <marcin@python-works.com>
23 Copyright (c) 2012 Marcin Kuzminski <marcin@python-works.com>
24
24
25 This file was forked by the Kallithea project in July 2014.
25 This file was forked by the Kallithea project in July 2014.
26 """
26 """
27
27
28 import logging
28 import logging
29 import os
29 import os
30 import socket
30 import socket
31 import traceback
31 import traceback
32
32
33 from dulwich.server import update_server_info
33 from dulwich.server import update_server_info
34 from dulwich.web import GunzipFilter, LimitedInputFilter
34 from dulwich.web import GunzipFilter, LimitedInputFilter
35 from webob import Request, Response, exc
35 from webob import Request, Response, exc
36
36
37 import kallithea
37 import kallithea
38 from kallithea.lib.utils2 import ascii_bytes
38 from kallithea.lib.utils2 import ascii_bytes
39 from kallithea.lib.vcs import get_repo, subprocessio
39 from kallithea.lib.vcs import get_repo, subprocessio
40
40
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class FileWrapper(object):
45 class FileWrapper(object):
46
46
47 def __init__(self, fd, content_length):
47 def __init__(self, fd, content_length):
48 self.fd = fd
48 self.fd = fd
49 self.content_length = content_length
49 self.content_length = content_length
50 self.remain = content_length
50 self.remain = content_length
51
51
52 def read(self, size):
52 def read(self, size):
53 if size <= self.remain:
53 if size <= self.remain:
54 try:
54 try:
55 data = self.fd.read(size)
55 data = self.fd.read(size)
56 except socket.error:
56 except socket.error:
57 raise IOError(self)
57 raise IOError(self)
58 self.remain -= size
58 self.remain -= size
59 elif self.remain:
59 elif self.remain:
60 data = self.fd.read(self.remain)
60 data = self.fd.read(self.remain)
61 self.remain = 0
61 self.remain = 0
62 else:
62 else:
63 data = None
63 data = None
64 return data
64 return data
65
65
66 def __repr__(self):
66 def __repr__(self):
67 return '<FileWrapper %s len: %s, read: %s>' % (
67 return '<FileWrapper %s len: %s, read: %s>' % (
68 self.fd, self.content_length, self.content_length - self.remain
68 self.fd, self.content_length, self.content_length - self.remain
69 )
69 )
70
70
71
71
72 class GitRepository(object):
72 class GitRepository(object):
73 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
73 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
74 commands = ['git-upload-pack', 'git-receive-pack']
74 commands = ['git-upload-pack', 'git-receive-pack']
75
75
76 def __init__(self, repo_name, content_path):
76 def __init__(self, repo_name, content_path):
77 files = set([f.lower() for f in os.listdir(content_path)])
77 files = set([f.lower() for f in os.listdir(content_path)])
78 if not (self.git_folder_signature.intersection(files)
78 if not (self.git_folder_signature.intersection(files)
79 == self.git_folder_signature):
79 == self.git_folder_signature):
80 raise OSError('%s missing git signature' % content_path)
80 raise OSError('%s missing git signature' % content_path)
81 self.content_path = content_path
81 self.content_path = content_path
82 self.valid_accepts = ['application/x-%s-result' %
82 self.valid_accepts = ['application/x-%s-result' %
83 c for c in self.commands]
83 c for c in self.commands]
84 self.repo_name = repo_name
84 self.repo_name = repo_name
85
85
86 def _get_fixedpath(self, path):
86 def _get_fixedpath(self, path):
87 """
87 """
88 Small fix for repo_path
88 Small fix for repo_path
89
89
90 :param path:
90 :param path:
91 """
91 """
92 assert path.startswith('/' + self.repo_name + '/')
92 assert path.startswith('/' + self.repo_name + '/')
93 return path[len(self.repo_name) + 2:].strip('/')
93 return path[len(self.repo_name) + 2:].strip('/')
94
94
95 def inforefs(self, req, environ):
95 def inforefs(self, req, environ):
96 """
96 """
97 WSGI Response producer for HTTP GET Git Smart
97 WSGI Response producer for HTTP GET Git Smart
98 HTTP /info/refs request.
98 HTTP /info/refs request.
99 """
99 """
100
100
101 git_command = req.GET.get('service')
101 git_command = req.GET.get('service')
102 if git_command not in self.commands:
102 if git_command not in self.commands:
103 log.debug('command %s not allowed', git_command)
103 log.debug('command %s not allowed', git_command)
104 return exc.HTTPMethodNotAllowed()
104 return exc.HTTPMethodNotAllowed()
105
105
106 # From Documentation/technical/http-protocol.txt shipped with Git:
106 # From Documentation/technical/http-protocol.txt shipped with Git:
107 #
107 #
108 # Clients MUST verify the first pkt-line is `# service=$servicename`.
108 # Clients MUST verify the first pkt-line is `# service=$servicename`.
109 # Servers MUST set $servicename to be the request parameter value.
109 # Servers MUST set $servicename to be the request parameter value.
110 # Servers SHOULD include an LF at the end of this line.
110 # Servers SHOULD include an LF at the end of this line.
111 # Clients MUST ignore an LF at the end of the line.
111 # Clients MUST ignore an LF at the end of the line.
112 #
112 #
113 # smart_reply = PKT-LINE("# service=$servicename" LF)
113 # smart_reply = PKT-LINE("# service=$servicename" LF)
114 # ref_list
114 # ref_list
115 # "0000"
115 # "0000"
116 server_advert = '# service=%s\n' % git_command
116 server_advert = '# service=%s\n' % git_command
117 packet_len = hex(len(server_advert) + 4)[2:].rjust(4, '0').lower()
117 packet_len = hex(len(server_advert) + 4)[2:].rjust(4, '0').lower()
118 _git_path = kallithea.CONFIG.get('git_path', 'git')
118 _git_path = kallithea.CONFIG.get('git_path', 'git')
119 cmd = [_git_path, git_command[4:],
119 cmd = [_git_path, git_command[4:],
120 '--stateless-rpc', '--advertise-refs', self.content_path]
120 '--stateless-rpc', '--advertise-refs', self.content_path]
121 log.debug('handling cmd %s', cmd)
121 log.debug('handling cmd %s', cmd)
122 try:
122 try:
123 out = subprocessio.SubprocessIOChunker(cmd,
123 out = subprocessio.SubprocessIOChunker(cmd,
124 starting_values=[ascii_bytes(packet_len + server_advert + '0000')]
124 starting_values=[ascii_bytes(packet_len + server_advert + '0000')]
125 )
125 )
126 except EnvironmentError as e:
126 except EnvironmentError as e:
127 log.error(traceback.format_exc())
127 log.error(traceback.format_exc())
128 raise exc.HTTPExpectationFailed()
128 raise exc.HTTPExpectationFailed()
129 resp = Response()
129 resp = Response()
130 resp.content_type = 'application/x-%s-advertisement' % git_command
130 resp.content_type = 'application/x-%s-advertisement' % git_command
131 resp.charset = None
131 resp.charset = None
132 resp.app_iter = out
132 resp.app_iter = out
133 return resp
133 return resp
134
134
135 def backend(self, req, environ):
135 def backend(self, req, environ):
136 """
136 """
137 WSGI Response producer for HTTP POST Git Smart HTTP requests.
137 WSGI Response producer for HTTP POST Git Smart HTTP requests.
138 Reads commands and data from HTTP POST's body.
138 Reads commands and data from HTTP POST's body.
139 returns an iterator obj with contents of git command's
139 returns an iterator obj with contents of git command's
140 response to stdout
140 response to stdout
141 """
141 """
142 _git_path = kallithea.CONFIG.get('git_path', 'git')
142 _git_path = kallithea.CONFIG.get('git_path', 'git')
143 git_command = self._get_fixedpath(req.path_info)
143 git_command = self._get_fixedpath(req.path_info)
144 if git_command not in self.commands:
144 if git_command not in self.commands:
145 log.debug('command %s not allowed', git_command)
145 log.debug('command %s not allowed', git_command)
146 return exc.HTTPMethodNotAllowed()
146 return exc.HTTPMethodNotAllowed()
147
147
148 if 'CONTENT_LENGTH' in environ:
148 if 'CONTENT_LENGTH' in environ:
149 inputstream = FileWrapper(environ['wsgi.input'],
149 inputstream = FileWrapper(environ['wsgi.input'],
150 req.content_length)
150 req.content_length)
151 else:
151 else:
152 inputstream = environ['wsgi.input']
152 inputstream = environ['wsgi.input']
153
153
154 gitenv = dict(os.environ)
154 gitenv = dict(os.environ)
155 # forget all configs
155 # forget all configs
156 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
156 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
157 cmd = [_git_path, git_command[4:], '--stateless-rpc', self.content_path]
157 cmd = [_git_path, git_command[4:], '--stateless-rpc', self.content_path]
158 log.debug('handling cmd %s', cmd)
158 log.debug('handling cmd %s', cmd)
159 try:
159 try:
160 out = subprocessio.SubprocessIOChunker(
160 out = subprocessio.SubprocessIOChunker(
161 cmd,
161 cmd,
162 inputstream=inputstream,
162 inputstream=inputstream,
163 env=gitenv,
163 env=gitenv,
164 cwd=self.content_path,
164 cwd=self.content_path,
165 )
165 )
166 except EnvironmentError as e:
166 except EnvironmentError as e:
167 log.error(traceback.format_exc())
167 log.error(traceback.format_exc())
168 raise exc.HTTPExpectationFailed()
168 raise exc.HTTPExpectationFailed()
169
169
170 if git_command in ['git-receive-pack']:
170 if git_command in ['git-receive-pack']:
171 # updating refs manually after each push.
171 # updating refs manually after each push.
172 # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
172 # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
173 repo = get_repo(self.content_path)
173 repo = get_repo(self.content_path)
174 if repo:
174 if repo:
175 update_server_info(repo._repo)
175 update_server_info(repo._repo)
176
176
177 resp = Response()
177 resp = Response()
178 resp.content_type = 'application/x-%s-result' % git_command
178 resp.content_type = 'application/x-%s-result' % git_command
179 resp.charset = None
179 resp.charset = None
180 resp.app_iter = out
180 resp.app_iter = out
181 return resp
181 return resp
182
182
183 def __call__(self, environ, start_response):
183 def __call__(self, environ, start_response):
184 req = Request(environ)
184 req = Request(environ)
185 _path = self._get_fixedpath(req.path_info)
185 _path = self._get_fixedpath(req.path_info)
186 if _path.startswith('info/refs'):
186 if _path.startswith('info/refs'):
187 app = self.inforefs
187 app = self.inforefs
188 elif req.accept.acceptable_offers(self.valid_accepts):
188 elif req.accept.acceptable_offers(self.valid_accepts):
189 app = self.backend
189 app = self.backend
190 try:
190 try:
191 resp = app(req, environ)
191 resp = app(req, environ)
192 except exc.HTTPException as e:
192 except exc.HTTPException as e:
193 resp = e
193 resp = e
194 log.error(traceback.format_exc())
194 log.error(traceback.format_exc())
195 except Exception as e:
195 except Exception as e:
196 log.error(traceback.format_exc())
196 log.error(traceback.format_exc())
197 resp = exc.HTTPInternalServerError()
197 resp = exc.HTTPInternalServerError()
198 return resp(environ, start_response)
198 return resp(environ, start_response)
199
199
200
200
201 class GitDirectory(object):
201 class GitDirectory(object):
202
202
203 def __init__(self, repo_root, repo_name):
203 def __init__(self, repo_root, repo_name):
204 repo_location = os.path.join(repo_root, repo_name)
204 repo_location = os.path.join(repo_root, repo_name)
205 if not os.path.isdir(repo_location):
205 if not os.path.isdir(repo_location):
206 raise OSError(repo_location)
206 raise OSError(repo_location)
207
207
208 self.content_path = repo_location
208 self.content_path = repo_location
209 self.repo_name = repo_name
209 self.repo_name = repo_name
210 self.repo_location = repo_location
210 self.repo_location = repo_location
211
211
212 def __call__(self, environ, start_response):
212 def __call__(self, environ, start_response):
213 content_path = self.content_path
213 content_path = self.content_path
214 try:
214 try:
215 app = GitRepository(self.repo_name, content_path)
215 app = GitRepository(self.repo_name, content_path)
216 except (AssertionError, OSError):
216 except (AssertionError, OSError):
217 content_path = os.path.join(content_path, '.git')
217 content_path = os.path.join(content_path, '.git')
218 if os.path.isdir(content_path):
218 if os.path.isdir(content_path):
219 app = GitRepository(self.repo_name, content_path)
219 app = GitRepository(self.repo_name, content_path)
220 else:
220 else:
221 return exc.HTTPNotFound()(environ, start_response)
221 return exc.HTTPNotFound()(environ, start_response)
222 return app(environ, start_response)
222 return app(environ, start_response)
223
223
224
224
225 def make_wsgi_app(repo_name, repo_root):
225 def make_wsgi_app(repo_name, repo_root):
226 app = GitDirectory(repo_root, repo_name)
226 app = GitDirectory(repo_root, repo_name)
227 return GunzipFilter(LimitedInputFilter(app))
227 return GunzipFilter(LimitedInputFilter(app))
@@ -1,93 +1,93 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.middleware.simplegit
15 kallithea.config.middleware.simplegit
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 SimpleGit middleware for handling Git protocol requests (push/clone etc.)
18 SimpleGit middleware for handling Git protocol requests (push/clone etc.)
19 It's implemented with basic auth function
19 It's implemented with basic auth function
20
20
21 This file was forked by the Kallithea project in July 2014.
21 This file was forked by the Kallithea project in July 2014.
22 Original author and date, and relevant copyright and licensing information is below:
22 Original author and date, and relevant copyright and licensing information is below:
23 :created_on: Apr 28, 2010
23 :created_on: Apr 28, 2010
24 :author: marcink
24 :author: marcink
25 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :copyright: (c) 2013 RhodeCode GmbH, and others.
26 :license: GPLv3, see LICENSE.md for more details.
26 :license: GPLv3, see LICENSE.md for more details.
27
27
28 """
28 """
29
29
30
30
31 import logging
31 import logging
32 import re
32 import re
33
33
34 from kallithea.config.middleware.pygrack import make_wsgi_app
34 from kallithea.config.middleware.pygrack import make_wsgi_app
35 from kallithea.lib import hooks
35 from kallithea.lib import hooks
36 from kallithea.lib.base import BaseVCSController, get_path_info
36 from kallithea.lib.base import BaseVCSController, get_path_info
37
37
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)$')
42 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)$')
43
43
44
44
45 cmd_mapping = {
45 cmd_mapping = {
46 'git-receive-pack': 'push',
46 'git-receive-pack': 'push',
47 'git-upload-pack': 'pull',
47 'git-upload-pack': 'pull',
48 }
48 }
49
49
50
50
51 class SimpleGit(BaseVCSController):
51 class SimpleGit(BaseVCSController):
52
52
53 scm_alias = 'git'
53 scm_alias = 'git'
54
54
55 @classmethod
55 @classmethod
56 def parse_request(cls, environ):
56 def parse_request(cls, environ):
57 path_info = get_path_info(environ)
57 path_info = get_path_info(environ)
58 m = GIT_PROTO_PAT.match(path_info)
58 m = GIT_PROTO_PAT.match(path_info)
59 if m is None:
59 if m is None:
60 return None
60 return None
61
61
62 class parsed_request(object):
62 class parsed_request(object):
63 # See https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols#_the_smart_protocol
63 # See https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols#_the_smart_protocol
64 repo_name = m.group(1).rstrip('/')
64 repo_name = m.group(1).rstrip('/')
65 cmd = m.group(2)
65 cmd = m.group(2)
66
66
67 query_string = environ['QUERY_STRING']
67 query_string = environ['QUERY_STRING']
68 if cmd == 'info/refs' and query_string.startswith('service='):
68 if cmd == 'info/refs' and query_string.startswith('service='):
69 service = query_string.split('=', 1)[1]
69 service = query_string.split('=', 1)[1]
70 action = cmd_mapping.get(service)
70 action = cmd_mapping.get(service)
71 else:
71 else:
72 service = None
72 service = None
73 action = cmd_mapping.get(cmd)
73 action = cmd_mapping.get(cmd)
74
74
75 return parsed_request
75 return parsed_request
76
76
77 def _make_app(self, parsed_request):
77 def _make_app(self, parsed_request):
78 """
78 """
79 Return a pygrack wsgi application.
79 Return a pygrack wsgi application.
80 """
80 """
81 pygrack_app = make_wsgi_app(parsed_request.repo_name, self.basepath)
81 pygrack_app = make_wsgi_app(parsed_request.repo_name, self.basepath)
82
82
83 def wrapper_app(environ, start_response):
83 def wrapper_app(environ, start_response):
84 if (parsed_request.cmd == 'info/refs' and
84 if (parsed_request.cmd == 'info/refs' and
85 parsed_request.service == 'git-upload-pack'
85 parsed_request.service == 'git-upload-pack'
86 ):
86 ):
87 # Run hooks like Mercurial outgoing.kallithea_pull_action does
87 # Run hooks like Mercurial outgoing.kallithea_pull_action does
88 hooks.log_pull_action()
88 hooks.log_pull_action()
89 # Note: push hooks are handled by post-receive hook
89 # Note: push hooks are handled by post-receive hook
90
90
91 return pygrack_app(environ, start_response)
91 return pygrack_app(environ, start_response)
92
92
93 return wrapper_app
93 return wrapper_app
@@ -1,149 +1,149 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.middleware.simplehg
15 kallithea.config.middleware.simplehg
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 SimpleHg middleware for handling Mercurial protocol requests (push/clone etc.).
18 SimpleHg middleware for handling Mercurial protocol requests (push/clone etc.).
19 It's implemented with basic auth function
19 It's implemented with basic auth function
20
20
21 This file was forked by the Kallithea project in July 2014.
21 This file was forked by the Kallithea project in July 2014.
22 Original author and date, and relevant copyright and licensing information is below:
22 Original author and date, and relevant copyright and licensing information is below:
23 :created_on: Apr 28, 2010
23 :created_on: Apr 28, 2010
24 :author: marcink
24 :author: marcink
25 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :copyright: (c) 2013 RhodeCode GmbH, and others.
26 :license: GPLv3, see LICENSE.md for more details.
26 :license: GPLv3, see LICENSE.md for more details.
27
27
28 """
28 """
29
29
30
30
31 import logging
31 import logging
32 import os
32 import os
33 import urllib.parse
33 import urllib.parse
34
34
35 import mercurial.hgweb
35 import mercurial.hgweb
36
36
37 from kallithea.lib.base import BaseVCSController, get_path_info
37 from kallithea.lib.base import BaseVCSController, get_path_info
38 from kallithea.lib.utils import make_ui
38 from kallithea.lib.utils import make_ui
39 from kallithea.lib.utils2 import safe_bytes
39 from kallithea.lib.utils2 import safe_bytes
40
40
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 def get_header_hgarg(environ):
45 def get_header_hgarg(environ):
46 """Decode the special Mercurial encoding of big requests over multiple headers.
46 """Decode the special Mercurial encoding of big requests over multiple headers.
47 >>> get_header_hgarg({})
47 >>> get_header_hgarg({})
48 ''
48 ''
49 >>> get_header_hgarg({'HTTP_X_HGARG_0': ' ', 'HTTP_X_HGARG_1': 'a','HTTP_X_HGARG_2': '','HTTP_X_HGARG_3': 'b+c %20'})
49 >>> get_header_hgarg({'HTTP_X_HGARG_0': ' ', 'HTTP_X_HGARG_1': 'a','HTTP_X_HGARG_2': '','HTTP_X_HGARG_3': 'b+c %20'})
50 'ab+c %20'
50 'ab+c %20'
51 """
51 """
52 chunks = []
52 chunks = []
53 i = 1
53 i = 1
54 while True:
54 while True:
55 v = environ.get('HTTP_X_HGARG_%d' % i)
55 v = environ.get('HTTP_X_HGARG_%d' % i)
56 if v is None:
56 if v is None:
57 break
57 break
58 chunks.append(v)
58 chunks.append(v)
59 i += 1
59 i += 1
60 return ''.join(chunks)
60 return ''.join(chunks)
61
61
62
62
63 cmd_mapping = {
63 cmd_mapping = {
64 # 'batch' is not in this list - it is handled explicitly
64 # 'batch' is not in this list - it is handled explicitly
65 'between': 'pull',
65 'between': 'pull',
66 'branches': 'pull',
66 'branches': 'pull',
67 'branchmap': 'pull',
67 'branchmap': 'pull',
68 'capabilities': 'pull',
68 'capabilities': 'pull',
69 'changegroup': 'pull',
69 'changegroup': 'pull',
70 'changegroupsubset': 'pull',
70 'changegroupsubset': 'pull',
71 'changesetdata': 'pull',
71 'changesetdata': 'pull',
72 'clonebundles': 'pull',
72 'clonebundles': 'pull',
73 'debugwireargs': 'pull',
73 'debugwireargs': 'pull',
74 'filedata': 'pull',
74 'filedata': 'pull',
75 'getbundle': 'pull',
75 'getbundle': 'pull',
76 'getlfile': 'pull',
76 'getlfile': 'pull',
77 'heads': 'pull',
77 'heads': 'pull',
78 'hello': 'pull',
78 'hello': 'pull',
79 'known': 'pull',
79 'known': 'pull',
80 'lheads': 'pull',
80 'lheads': 'pull',
81 'listkeys': 'pull',
81 'listkeys': 'pull',
82 'lookup': 'pull',
82 'lookup': 'pull',
83 'manifestdata': 'pull',
83 'manifestdata': 'pull',
84 'narrow_widen': 'pull',
84 'narrow_widen': 'pull',
85 'protocaps': 'pull',
85 'protocaps': 'pull',
86 'statlfile': 'pull',
86 'statlfile': 'pull',
87 'stream_out': 'pull',
87 'stream_out': 'pull',
88 'pushkey': 'push',
88 'pushkey': 'push',
89 'putlfile': 'push',
89 'putlfile': 'push',
90 'unbundle': 'push',
90 'unbundle': 'push',
91 }
91 }
92
92
93
93
94 class SimpleHg(BaseVCSController):
94 class SimpleHg(BaseVCSController):
95
95
96 scm_alias = 'hg'
96 scm_alias = 'hg'
97
97
98 @classmethod
98 @classmethod
99 def parse_request(cls, environ):
99 def parse_request(cls, environ):
100 http_accept = environ.get('HTTP_ACCEPT', '')
100 http_accept = environ.get('HTTP_ACCEPT', '')
101 if not http_accept.startswith('application/mercurial'):
101 if not http_accept.startswith('application/mercurial'):
102 return None
102 return None
103 path_info = get_path_info(environ)
103 path_info = get_path_info(environ)
104 if not path_info.startswith('/'): # it must!
104 if not path_info.startswith('/'): # it must!
105 return None
105 return None
106
106
107 class parsed_request(object):
107 class parsed_request(object):
108 repo_name = path_info[1:].rstrip('/')
108 repo_name = path_info[1:].rstrip('/')
109
109
110 query_string = environ['QUERY_STRING']
110 query_string = environ['QUERY_STRING']
111
111
112 action = None
112 action = None
113 for qry in query_string.split('&'):
113 for qry in query_string.split('&'):
114 parts = qry.split('=', 1)
114 parts = qry.split('=', 1)
115 if len(parts) == 2 and parts[0] == 'cmd':
115 if len(parts) == 2 and parts[0] == 'cmd':
116 cmd = parts[1]
116 cmd = parts[1]
117 if cmd == 'batch':
117 if cmd == 'batch':
118 hgarg = get_header_hgarg(environ)
118 hgarg = get_header_hgarg(environ)
119 if not hgarg.startswith('cmds='):
119 if not hgarg.startswith('cmds='):
120 action = 'push' # paranoid and safe
120 action = 'push' # paranoid and safe
121 break
121 break
122 action = 'pull'
122 action = 'pull'
123 for cmd_arg in hgarg[5:].split(';'):
123 for cmd_arg in hgarg[5:].split(';'):
124 cmd, _args = urllib.parse.unquote_plus(cmd_arg).split(' ', 1)
124 cmd, _args = urllib.parse.unquote_plus(cmd_arg).split(' ', 1)
125 op = cmd_mapping.get(cmd, 'push')
125 op = cmd_mapping.get(cmd, 'push')
126 if op != 'pull':
126 if op != 'pull':
127 assert op == 'push'
127 assert op == 'push'
128 action = 'push'
128 action = 'push'
129 break
129 break
130 else:
130 else:
131 action = cmd_mapping.get(cmd, 'push')
131 action = cmd_mapping.get(cmd, 'push')
132 break # only process one cmd
132 break # only process one cmd
133
133
134 return parsed_request
134 return parsed_request
135
135
136 def _make_app(self, parsed_request):
136 def _make_app(self, parsed_request):
137 """
137 """
138 Make an hgweb wsgi application.
138 Make an hgweb wsgi application.
139 """
139 """
140 repo_name = parsed_request.repo_name
140 repo_name = parsed_request.repo_name
141 repo_path = os.path.join(self.basepath, repo_name)
141 repo_path = os.path.join(self.basepath, repo_name)
142 baseui = make_ui(repo_path=repo_path)
142 baseui = make_ui(repo_path=repo_path)
143 hgweb_app = mercurial.hgweb.hgweb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
143 hgweb_app = mercurial.hgweb.hgweb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
144
144
145 def wrapper_app(environ, start_response):
145 def wrapper_app(environ, start_response):
146 environ['REPO_NAME'] = repo_name # used by mercurial.hgweb.hgweb
146 environ['REPO_NAME'] = repo_name # used by mercurial.hgweb.hgweb
147 return hgweb_app(environ, start_response)
147 return hgweb_app(environ, start_response)
148
148
149 return wrapper_app
149 return wrapper_app
@@ -1,102 +1,102 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.middleware.wrapper
15 kallithea.config.middleware.wrapper
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Wrap app to measure request and response time ... all the way to the response
18 Wrap app to measure request and response time ... all the way to the response
19 WSGI iterator has been closed.
19 WSGI iterator has been closed.
20
20
21 This file was forked by the Kallithea project in July 2014.
21 This file was forked by the Kallithea project in July 2014.
22 Original author and date, and relevant copyright and licensing information is below:
22 Original author and date, and relevant copyright and licensing information is below:
23 :created_on: May 23, 2013
23 :created_on: May 23, 2013
24 :author: marcink
24 :author: marcink
25 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :copyright: (c) 2013 RhodeCode GmbH, and others.
26 :license: GPLv3, see LICENSE.md for more details.
26 :license: GPLv3, see LICENSE.md for more details.
27 """
27 """
28
28
29 import logging
29 import logging
30 import time
30 import time
31
31
32 from kallithea.lib.base import _get_ip_addr, get_path_info
32 from kallithea.lib.base import _get_ip_addr, get_path_info
33
33
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class Meter:
38 class Meter:
39
39
40 def __init__(self, start_response):
40 def __init__(self, start_response):
41 self._start_response = start_response
41 self._start_response = start_response
42 self._start = time.time()
42 self._start = time.time()
43 self.status = None
43 self.status = None
44 self._size = 0
44 self._size = 0
45
45
46 def duration(self):
46 def duration(self):
47 return time.time() - self._start
47 return time.time() - self._start
48
48
49 def start_response(self, status, response_headers, exc_info=None):
49 def start_response(self, status, response_headers, exc_info=None):
50 self.status = status
50 self.status = status
51 write = self._start_response(status, response_headers, exc_info)
51 write = self._start_response(status, response_headers, exc_info)
52 def metered_write(s):
52 def metered_write(s):
53 self.measure(s)
53 self.measure(s)
54 write(s)
54 write(s)
55 return metered_write
55 return metered_write
56
56
57 def measure(self, chunk):
57 def measure(self, chunk):
58 self._size += len(chunk)
58 self._size += len(chunk)
59
59
60 def size(self):
60 def size(self):
61 return self._size
61 return self._size
62
62
63
63
64 class ResultIter:
64 class ResultIter:
65
65
66 def __init__(self, result, meter, description):
66 def __init__(self, result, meter, description):
67 self._result_close = getattr(result, 'close', None) or (lambda: None)
67 self._result_close = getattr(result, 'close', None) or (lambda: None)
68 self._next = iter(result).__next__
68 self._next = iter(result).__next__
69 self._meter = meter
69 self._meter = meter
70 self._description = description
70 self._description = description
71
71
72 def __iter__(self):
72 def __iter__(self):
73 return self
73 return self
74
74
75 def __next__(self):
75 def __next__(self):
76 chunk = self._next()
76 chunk = self._next()
77 self._meter.measure(chunk)
77 self._meter.measure(chunk)
78 return chunk
78 return chunk
79
79
80 def close(self):
80 def close(self):
81 self._result_close()
81 self._result_close()
82 log.info("%s responded %r after %.3fs with %s bytes", self._description, self._meter.status, self._meter.duration(), self._meter.size())
82 log.info("%s responded %r after %.3fs with %s bytes", self._description, self._meter.status, self._meter.duration(), self._meter.size())
83
83
84
84
85 class RequestWrapper(object):
85 class RequestWrapper(object):
86
86
87 def __init__(self, app, config):
87 def __init__(self, app, config):
88 self.application = app
88 self.application = app
89 self.config = config
89 self.config = config
90
90
91 def __call__(self, environ, start_response):
91 def __call__(self, environ, start_response):
92 meter = Meter(start_response)
92 meter = Meter(start_response)
93 description = "Request from %s for %s" % (
93 description = "Request from %s for %s" % (
94 _get_ip_addr(environ),
94 _get_ip_addr(environ),
95 get_path_info(environ),
95 get_path_info(environ),
96 )
96 )
97 log.info("%s received", description)
97 log.info("%s received", description)
98 try:
98 try:
99 result = self.application(environ, meter.start_response)
99 result = self.application(environ, meter.start_response)
100 finally:
100 finally:
101 log.info("%s responding %r after %.3fs", description, meter.status, meter.duration())
101 log.info("%s responding %r after %.3fs", description, meter.status, meter.duration())
102 return ResultIter(result, meter, description)
102 return ResultIter(result, meter, description)
@@ -1,69 +1,69 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.conf
15 kallithea.lib.conf
16 ~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~
17
17
18 Various config settings for Kallithea
18 Various config settings for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Mar 7, 2012
22 :created_on: Mar 7, 2012
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 from kallithea.lib import pygmentsutils
28 from kallithea.lib import pygmentsutils
29
29
30
30
31 # language map is also used by whoosh indexer, which for those specified
31 # language map is also used by whoosh indexer, which for those specified
32 # extensions will index it's content
32 # extensions will index it's content
33 LANGUAGES_EXTENSIONS_MAP = pygmentsutils.get_extension_descriptions()
33 LANGUAGES_EXTENSIONS_MAP = pygmentsutils.get_extension_descriptions()
34
34
35 # Whoosh index targets
35 # Whoosh index targets
36
36
37 # Extensions we want to index content of using whoosh
37 # Extensions we want to index content of using whoosh
38 INDEX_EXTENSIONS = list(LANGUAGES_EXTENSIONS_MAP)
38 INDEX_EXTENSIONS = list(LANGUAGES_EXTENSIONS_MAP)
39
39
40 # Filenames we want to index content of using whoosh
40 # Filenames we want to index content of using whoosh
41 INDEX_FILENAMES = pygmentsutils.get_index_filenames()
41 INDEX_FILENAMES = pygmentsutils.get_index_filenames()
42
42
43 # list of readme files to search in file tree and display in summary
43 # list of readme files to search in file tree and display in summary
44 # attached weights defines the search order lower is first
44 # attached weights defines the search order lower is first
45 ALL_READMES = [
45 ALL_READMES = [
46 ('readme', 0), ('README', 0), ('Readme', 0),
46 ('readme', 0), ('README', 0), ('Readme', 0),
47 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
47 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
48 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
48 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
49 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
49 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
50 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
50 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
51 ]
51 ]
52
52
53 # extension together with weights to search lower is first
53 # extension together with weights to search lower is first
54 RST_EXTS = [
54 RST_EXTS = [
55 ('', 0), ('.rst', 1), ('.rest', 1),
55 ('', 0), ('.rst', 1), ('.rest', 1),
56 ('.RST', 2), ('.REST', 2),
56 ('.RST', 2), ('.REST', 2),
57 ('.txt', 3), ('.TXT', 3)
57 ('.txt', 3), ('.TXT', 3)
58 ]
58 ]
59
59
60 MARKDOWN_EXTS = [
60 MARKDOWN_EXTS = [
61 ('.md', 1), ('.MD', 1),
61 ('.md', 1), ('.MD', 1),
62 ('.mkdn', 2), ('.MKDN', 2),
62 ('.mkdn', 2), ('.MKDN', 2),
63 ('.mdown', 3), ('.MDOWN', 3),
63 ('.mdown', 3), ('.MDOWN', 3),
64 ('.markdown', 4), ('.MARKDOWN', 4)
64 ('.markdown', 4), ('.MARKDOWN', 4)
65 ]
65 ]
66
66
67 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
67 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
68
68
69 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
69 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
@@ -1,258 +1,258 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.webutils
15 kallithea.lib.webutils
16 ~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Helper functions that may rely on the current WSGI request, exposed in the TG2
18 Helper functions that may rely on the current WSGI request, exposed in the TG2
19 thread-local "global" variables. It should have few dependencies so it can be
19 thread-local "global" variables. It should have few dependencies so it can be
20 imported anywhere - just like the global variables can be used everywhere.
20 imported anywhere - just like the global variables can be used everywhere.
21 """
21 """
22
22
23 import logging
23 import logging
24 import random
24 import random
25
25
26 from tg import request, session
26 from tg import request, session
27 from webhelpers2.html import HTML, escape, literal
27 from webhelpers2.html import HTML, escape, literal
28 from webhelpers2.html.tags import NotGiven, Option, Options, _input
28 from webhelpers2.html.tags import NotGiven, Option, Options, _input
29 from webhelpers2.html.tags import _make_safe_id_component as safeid
29 from webhelpers2.html.tags import _make_safe_id_component as safeid
30 from webhelpers2.html.tags import checkbox, end_form
30 from webhelpers2.html.tags import checkbox, end_form
31 from webhelpers2.html.tags import form as insecure_form
31 from webhelpers2.html.tags import form as insecure_form
32 from webhelpers2.html.tags import hidden, link_to, password, radio
32 from webhelpers2.html.tags import hidden, link_to, password, radio
33 from webhelpers2.html.tags import select as webhelpers2_select
33 from webhelpers2.html.tags import select as webhelpers2_select
34 from webhelpers2.html.tags import submit, text, textarea
34 from webhelpers2.html.tags import submit, text, textarea
35 from webhelpers2.number import format_byte_size
35 from webhelpers2.number import format_byte_size
36 from webhelpers2.text import chop_at, truncate, wrap_paragraphs
36 from webhelpers2.text import chop_at, truncate, wrap_paragraphs
37
37
38 import kallithea
38 import kallithea
39
39
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 # mute pyflakes "imported but unused"
44 # mute pyflakes "imported but unused"
45 assert Option
45 assert Option
46 assert checkbox
46 assert checkbox
47 assert chop_at
47 assert chop_at
48 assert end_form
48 assert end_form
49 assert escape
49 assert escape
50 assert format_byte_size
50 assert format_byte_size
51 assert link_to
51 assert link_to
52 assert literal
52 assert literal
53 assert password
53 assert password
54 assert radio
54 assert radio
55 assert safeid
55 assert safeid
56 assert submit
56 assert submit
57 assert text
57 assert text
58 assert textarea
58 assert textarea
59 assert truncate
59 assert truncate
60 assert wrap_paragraphs
60 assert wrap_paragraphs
61
61
62
62
63 #
63 #
64 # General Kallithea URL handling
64 # General Kallithea URL handling
65 #
65 #
66
66
67 class UrlGenerator(object):
67 class UrlGenerator(object):
68 """Emulate pylons.url in providing a wrapper around routes.url
68 """Emulate pylons.url in providing a wrapper around routes.url
69
69
70 This code was added during migration from Pylons to Turbogears2. Pylons
70 This code was added during migration from Pylons to Turbogears2. Pylons
71 already provided a wrapper like this, but Turbogears2 does not.
71 already provided a wrapper like this, but Turbogears2 does not.
72
72
73 When the routing of Kallithea is changed to use less Routes and more
73 When the routing of Kallithea is changed to use less Routes and more
74 Turbogears2-style routing, this class may disappear or change.
74 Turbogears2-style routing, this class may disappear or change.
75
75
76 url() (the __call__ method) returns the URL based on a route name and
76 url() (the __call__ method) returns the URL based on a route name and
77 arguments.
77 arguments.
78 url.current() returns the URL of the current page with arguments applied.
78 url.current() returns the URL of the current page with arguments applied.
79
79
80 Refer to documentation of Routes for details:
80 Refer to documentation of Routes for details:
81 https://routes.readthedocs.io/en/latest/generating.html#generation
81 https://routes.readthedocs.io/en/latest/generating.html#generation
82 """
82 """
83 def __call__(self, *args, **kwargs):
83 def __call__(self, *args, **kwargs):
84 return request.environ['routes.url'](*args, **kwargs)
84 return request.environ['routes.url'](*args, **kwargs)
85
85
86 def current(self, *args, **kwargs):
86 def current(self, *args, **kwargs):
87 return request.environ['routes.url'].current(*args, **kwargs)
87 return request.environ['routes.url'].current(*args, **kwargs)
88
88
89
89
90 url = UrlGenerator()
90 url = UrlGenerator()
91
91
92
92
93 def canonical_url(*args, **kargs):
93 def canonical_url(*args, **kargs):
94 '''Like url(x, qualified=True), but returns url that not only is qualified
94 '''Like url(x, qualified=True), but returns url that not only is qualified
95 but also canonical, as configured in canonical_url'''
95 but also canonical, as configured in canonical_url'''
96 try:
96 try:
97 parts = kallithea.CONFIG.get('canonical_url', '').split('://', 1)
97 parts = kallithea.CONFIG.get('canonical_url', '').split('://', 1)
98 kargs['host'] = parts[1]
98 kargs['host'] = parts[1]
99 kargs['protocol'] = parts[0]
99 kargs['protocol'] = parts[0]
100 except IndexError:
100 except IndexError:
101 kargs['qualified'] = True
101 kargs['qualified'] = True
102 return url(*args, **kargs)
102 return url(*args, **kargs)
103
103
104
104
105 def canonical_hostname():
105 def canonical_hostname():
106 '''Return canonical hostname of system'''
106 '''Return canonical hostname of system'''
107 try:
107 try:
108 parts = kallithea.CONFIG.get('canonical_url', '').split('://', 1)
108 parts = kallithea.CONFIG.get('canonical_url', '').split('://', 1)
109 return parts[1].split('/', 1)[0]
109 return parts[1].split('/', 1)[0]
110 except IndexError:
110 except IndexError:
111 parts = url('home', qualified=True).split('://', 1)
111 parts = url('home', qualified=True).split('://', 1)
112 return parts[1].split('/', 1)[0]
112 return parts[1].split('/', 1)[0]
113
113
114
114
115 #
115 #
116 # Custom Webhelpers2 stuff
116 # Custom Webhelpers2 stuff
117 #
117 #
118
118
119 def html_escape(s):
119 def html_escape(s):
120 """Return string with all html escaped.
120 """Return string with all html escaped.
121 This is also safe for javascript in html but not necessarily correct.
121 This is also safe for javascript in html but not necessarily correct.
122 """
122 """
123 return (s
123 return (s
124 .replace('&', '&amp;')
124 .replace('&', '&amp;')
125 .replace(">", "&gt;")
125 .replace(">", "&gt;")
126 .replace("<", "&lt;")
126 .replace("<", "&lt;")
127 .replace('"', "&quot;")
127 .replace('"', "&quot;")
128 .replace("'", "&apos;") # Note: this is HTML5 not HTML4 and might not work in mails
128 .replace("'", "&apos;") # Note: this is HTML5 not HTML4 and might not work in mails
129 )
129 )
130
130
131
131
132 def reset(name, value, id=NotGiven, **attrs):
132 def reset(name, value, id=NotGiven, **attrs):
133 """Create a reset button, similar to webhelpers2.html.tags.submit ."""
133 """Create a reset button, similar to webhelpers2.html.tags.submit ."""
134 return _input("reset", name, value, id, attrs)
134 return _input("reset", name, value, id, attrs)
135
135
136
136
137 def select(name, selected_values, options, id=NotGiven, **attrs):
137 def select(name, selected_values, options, id=NotGiven, **attrs):
138 """Convenient wrapper of webhelpers2 to let it accept options as a tuple list"""
138 """Convenient wrapper of webhelpers2 to let it accept options as a tuple list"""
139 if isinstance(options, list):
139 if isinstance(options, list):
140 option_list = options
140 option_list = options
141 # Handle old value,label lists ... where value also can be value,label lists
141 # Handle old value,label lists ... where value also can be value,label lists
142 options = Options()
142 options = Options()
143 for x in option_list:
143 for x in option_list:
144 if isinstance(x, tuple) and len(x) == 2:
144 if isinstance(x, tuple) and len(x) == 2:
145 value, label = x
145 value, label = x
146 elif isinstance(x, str):
146 elif isinstance(x, str):
147 value = label = x
147 value = label = x
148 else:
148 else:
149 log.error('invalid select option %r', x)
149 log.error('invalid select option %r', x)
150 raise
150 raise
151 if isinstance(value, list):
151 if isinstance(value, list):
152 og = options.add_optgroup(label)
152 og = options.add_optgroup(label)
153 for x in value:
153 for x in value:
154 if isinstance(x, tuple) and len(x) == 2:
154 if isinstance(x, tuple) and len(x) == 2:
155 group_value, group_label = x
155 group_value, group_label = x
156 elif isinstance(x, str):
156 elif isinstance(x, str):
157 group_value = group_label = x
157 group_value = group_label = x
158 else:
158 else:
159 log.error('invalid select option %r', x)
159 log.error('invalid select option %r', x)
160 raise
160 raise
161 og.add_option(group_label, group_value)
161 og.add_option(group_label, group_value)
162 else:
162 else:
163 options.add_option(label, value)
163 options.add_option(label, value)
164 return webhelpers2_select(name, selected_values, options, id=id, **attrs)
164 return webhelpers2_select(name, selected_values, options, id=id, **attrs)
165
165
166
166
167 session_csrf_secret_name = "_session_csrf_secret_token"
167 session_csrf_secret_name = "_session_csrf_secret_token"
168
168
169 def session_csrf_secret_token():
169 def session_csrf_secret_token():
170 """Return (and create) the current session's CSRF protection token."""
170 """Return (and create) the current session's CSRF protection token."""
171 if not session_csrf_secret_name in session:
171 if not session_csrf_secret_name in session:
172 session[session_csrf_secret_name] = str(random.getrandbits(128))
172 session[session_csrf_secret_name] = str(random.getrandbits(128))
173 session.save()
173 session.save()
174 return session[session_csrf_secret_name]
174 return session[session_csrf_secret_name]
175
175
176 def form(url, method="post", **attrs):
176 def form(url, method="post", **attrs):
177 """Like webhelpers.html.tags.form , but automatically adding
177 """Like webhelpers.html.tags.form , but automatically adding
178 session_csrf_secret_token for POST. The secret is thus never leaked in GET
178 session_csrf_secret_token for POST. The secret is thus never leaked in GET
179 URLs.
179 URLs.
180 """
180 """
181 form = insecure_form(url, method, **attrs)
181 form = insecure_form(url, method, **attrs)
182 if method.lower() == 'get':
182 if method.lower() == 'get':
183 return form
183 return form
184 return form + HTML.div(hidden(session_csrf_secret_name, session_csrf_secret_token()), style="display: none;")
184 return form + HTML.div(hidden(session_csrf_secret_name, session_csrf_secret_token()), style="display: none;")
185
185
186
186
187 #
187 #
188 # Flash messages, stored in cookie
188 # Flash messages, stored in cookie
189 #
189 #
190
190
191 class _Message(object):
191 class _Message(object):
192 """A message returned by ``pop_flash_messages()``.
192 """A message returned by ``pop_flash_messages()``.
193
193
194 Converting the message to a string returns the message text. Instances
194 Converting the message to a string returns the message text. Instances
195 also have the following attributes:
195 also have the following attributes:
196
196
197 * ``category``: the category specified when the message was created.
197 * ``category``: the category specified when the message was created.
198 * ``message``: the html-safe message text.
198 * ``message``: the html-safe message text.
199 """
199 """
200
200
201 def __init__(self, category, message):
201 def __init__(self, category, message):
202 self.category = category
202 self.category = category
203 self.message = message
203 self.message = message
204
204
205
205
206 def _session_flash_messages(append=None, clear=False):
206 def _session_flash_messages(append=None, clear=False):
207 """Manage a message queue in tg.session: return the current message queue
207 """Manage a message queue in tg.session: return the current message queue
208 after appending the given message, and possibly clearing the queue."""
208 after appending the given message, and possibly clearing the queue."""
209 key = 'flash'
209 key = 'flash'
210 if key in session:
210 if key in session:
211 flash_messages = session[key]
211 flash_messages = session[key]
212 else:
212 else:
213 if append is None: # common fast path - also used for clearing empty queue
213 if append is None: # common fast path - also used for clearing empty queue
214 return [] # don't bother saving
214 return [] # don't bother saving
215 flash_messages = []
215 flash_messages = []
216 session[key] = flash_messages
216 session[key] = flash_messages
217 if append is not None and append not in flash_messages:
217 if append is not None and append not in flash_messages:
218 flash_messages.append(append)
218 flash_messages.append(append)
219 if clear:
219 if clear:
220 session.pop(key, None)
220 session.pop(key, None)
221 session.save()
221 session.save()
222 return flash_messages
222 return flash_messages
223
223
224
224
225 def flash(message, category, logf=None):
225 def flash(message, category, logf=None):
226 """
226 """
227 Show a message to the user _and_ log it through the specified function
227 Show a message to the user _and_ log it through the specified function
228
228
229 category: notice (default), warning, error, success
229 category: notice (default), warning, error, success
230 logf: a custom log function - such as log.debug
230 logf: a custom log function - such as log.debug
231
231
232 logf defaults to log.info, unless category equals 'success', in which
232 logf defaults to log.info, unless category equals 'success', in which
233 case logf defaults to log.debug.
233 case logf defaults to log.debug.
234 """
234 """
235 assert category in ('error', 'success', 'warning'), category
235 assert category in ('error', 'success', 'warning'), category
236 if hasattr(message, '__html__'):
236 if hasattr(message, '__html__'):
237 # render to HTML for storing in cookie
237 # render to HTML for storing in cookie
238 safe_message = str(message)
238 safe_message = str(message)
239 else:
239 else:
240 # Apply str - the message might be an exception with __str__
240 # Apply str - the message might be an exception with __str__
241 # Escape, so we can trust the result without further escaping, without any risk of injection
241 # Escape, so we can trust the result without further escaping, without any risk of injection
242 safe_message = html_escape(str(message))
242 safe_message = html_escape(str(message))
243 if logf is None:
243 if logf is None:
244 logf = log.info
244 logf = log.info
245 if category == 'success':
245 if category == 'success':
246 logf = log.debug
246 logf = log.debug
247
247
248 logf('Flash %s: %s', category, safe_message)
248 logf('Flash %s: %s', category, safe_message)
249
249
250 _session_flash_messages(append=(category, safe_message))
250 _session_flash_messages(append=(category, safe_message))
251
251
252
252
253 def pop_flash_messages():
253 def pop_flash_messages():
254 """Return all accumulated messages and delete them from the session.
254 """Return all accumulated messages and delete them from the session.
255
255
256 The return value is a list of ``Message`` objects.
256 The return value is a list of ``Message`` objects.
257 """
257 """
258 return [_Message(category, message) for category, message in _session_flash_messages(clear=True)]
258 return [_Message(category, message) for category, message in _session_flash_messages(clear=True)]
@@ -1,90 +1,90 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.db
15 kallithea.model.userlog
16 ~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Database Models for Kallithea
18 Database Models for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 08, 2010
22 :created_on: Apr 08, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import datetime
29 import datetime
30 import logging
30 import logging
31
31
32 from kallithea.lib.utils2 import get_current_authuser
32 from kallithea.lib.utils2 import get_current_authuser
33 from kallithea.model import db, meta
33 from kallithea.model import db, meta
34
34
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 def action_logger(user, action, repo, ipaddr='', commit=False):
39 def action_logger(user, action, repo, ipaddr='', commit=False):
40 """
40 """
41 Action logger for various actions made by users
41 Action logger for various actions made by users
42
42
43 :param user: user that made this action, can be a unique username string or
43 :param user: user that made this action, can be a unique username string or
44 object containing user_id attribute
44 object containing user_id attribute
45 :param action: action to log, should be on of predefined unique actions for
45 :param action: action to log, should be on of predefined unique actions for
46 easy translations
46 easy translations
47 :param repo: string name of repository or object containing repo_id,
47 :param repo: string name of repository or object containing repo_id,
48 that action was made on
48 that action was made on
49 :param ipaddr: optional IP address from what the action was made
49 :param ipaddr: optional IP address from what the action was made
50
50
51 """
51 """
52
52
53 # if we don't get explicit IP address try to get one from registered user
53 # if we don't get explicit IP address try to get one from registered user
54 # in tmpl context var
54 # in tmpl context var
55 if not ipaddr:
55 if not ipaddr:
56 ipaddr = getattr(get_current_authuser(), 'ip_addr', '')
56 ipaddr = getattr(get_current_authuser(), 'ip_addr', '')
57
57
58 if getattr(user, 'user_id', None):
58 if getattr(user, 'user_id', None):
59 user_obj = db.User.get(user.user_id)
59 user_obj = db.User.get(user.user_id)
60 elif isinstance(user, str):
60 elif isinstance(user, str):
61 user_obj = db.User.get_by_username(user)
61 user_obj = db.User.get_by_username(user)
62 else:
62 else:
63 raise Exception('You have to provide a user object or a username')
63 raise Exception('You have to provide a user object or a username')
64
64
65 if getattr(repo, 'repo_id', None):
65 if getattr(repo, 'repo_id', None):
66 repo_obj = db.Repository.get(repo.repo_id)
66 repo_obj = db.Repository.get(repo.repo_id)
67 repo_name = repo_obj.repo_name
67 repo_name = repo_obj.repo_name
68 elif isinstance(repo, str):
68 elif isinstance(repo, str):
69 repo_name = repo.lstrip('/')
69 repo_name = repo.lstrip('/')
70 repo_obj = db.Repository.get_by_repo_name(repo_name)
70 repo_obj = db.Repository.get_by_repo_name(repo_name)
71 else:
71 else:
72 repo_obj = None
72 repo_obj = None
73 repo_name = ''
73 repo_name = ''
74
74
75 user_log = db.UserLog()
75 user_log = db.UserLog()
76 user_log.user_id = user_obj.user_id
76 user_log.user_id = user_obj.user_id
77 user_log.username = user_obj.username
77 user_log.username = user_obj.username
78 user_log.action = action
78 user_log.action = action
79
79
80 user_log.repository = repo_obj
80 user_log.repository = repo_obj
81 user_log.repository_name = repo_name
81 user_log.repository_name = repo_name
82
82
83 user_log.action_date = datetime.datetime.now()
83 user_log.action_date = datetime.datetime.now()
84 user_log.user_ip = ipaddr
84 user_log.user_ip = ipaddr
85 meta.Session().add(user_log)
85 meta.Session().add(user_log)
86
86
87 log.info('Logging action:%s on %s by user:%s ip:%s',
87 log.info('Logging action:%s on %s by user:%s ip:%s',
88 action, repo, user_obj, ipaddr)
88 action, repo, user_obj, ipaddr)
89 if commit:
89 if commit:
90 meta.Session().commit()
90 meta.Session().commit()
@@ -1,13 +1,14 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 # Convenience script for running various idempotent source code cleanup scripts
3 # Convenience script for running various idempotent source code cleanup scripts
4
4
5 set -e
5 set -e
6 set -x
6 set -x
7
7
8 scripts/docs-headings.py
8 scripts/docs-headings.py
9 scripts/generate-ini.py
9 scripts/generate-ini.py
10 scripts/whitespacecleanup.sh
10 scripts/whitespacecleanup.sh
11 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/source_format.py
11
12
12 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/pyflakes
13 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/pyflakes
13 echo "no blocking problems found by $0"
14 echo "no blocking problems found by $0"
General Comments 0
You need to be logged in to leave comments. Login now