##// END OF EJS Templates
changed scope of calling EXTENSIONS from rhodecode for githooks to be able to execute them
marcink -
r2406:7be31af5 beta
parent child Browse files
Show More
@@ -1,256 +1,257 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.hooks
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Hooks runned by rhodecode
7 7
8 8 :created_on: Aug 6, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import sys
27 27 import binascii
28 28 from inspect import isfunction
29 29
30 30 from mercurial.scmutil import revrange
31 31 from mercurial.node import nullrev
32 32
33 from rhodecode import EXTENSIONS
34 33 from rhodecode.lib import helpers as h
35 34 from rhodecode.lib.utils import action_logger
36 35 from rhodecode.lib.vcs.backends.base import EmptyChangeset
37 36
38 37
39 38 def _get_scm_size(alias, root_path):
40 39
41 40 if not alias.startswith('.'):
42 41 alias += '.'
43 42
44 43 size_scm, size_root = 0, 0
45 44 for path, dirs, files in os.walk(root_path):
46 45 if path.find(alias) != -1:
47 46 for f in files:
48 47 try:
49 48 size_scm += os.path.getsize(os.path.join(path, f))
50 49 except OSError:
51 50 pass
52 51 else:
53 52 for f in files:
54 53 try:
55 54 size_root += os.path.getsize(os.path.join(path, f))
56 55 except OSError:
57 56 pass
58 57
59 58 size_scm_f = h.format_byte_size(size_scm)
60 59 size_root_f = h.format_byte_size(size_root)
61 60 size_total_f = h.format_byte_size(size_root + size_scm)
62 61
63 62 return size_scm_f, size_root_f, size_total_f
64 63
65 64
66 65 def repo_size(ui, repo, hooktype=None, **kwargs):
67 66 """
68 67 Presents size of repository after push
69 68
70 69 :param ui:
71 70 :param repo:
72 71 :param hooktype:
73 72 """
74 73
75 74 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
76 75
77 76 last_cs = repo[len(repo) - 1]
78 77
79 78 msg = ('Repository size .hg:%s repo:%s total:%s\n'
80 79 'Last revision is now r%s:%s\n') % (
81 80 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
82 81 )
83 82
84 83 sys.stdout.write(msg)
85 84
86 85
87 86 def log_pull_action(ui, repo, **kwargs):
88 87 """
89 88 Logs user last pull action
90 89
91 90 :param ui:
92 91 :param repo:
93 92 """
94 93
95 94 extras = dict(repo.ui.configitems('rhodecode_extras'))
96 95 username = extras['username']
97 96 repository = extras['repository']
98 97 scm = extras['scm']
99 98 action = 'pull'
100 99
101 100 action_logger(username, action, repository, extras['ip'], commit=True)
102 101 # extension hook call
102 from rhodecode import EXTENSIONS
103 103 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
104 104
105 105 if isfunction(callback):
106 106 kw = {}
107 107 kw.update(extras)
108 108 callback(**kw)
109 109 return 0
110 110
111 111
112 112 def log_push_action(ui, repo, **kwargs):
113 113 """
114 114 Maps user last push action to new changeset id, from mercurial
115 115
116 116 :param ui:
117 117 :param repo: repo object containing the `ui` object
118 118 """
119 119
120 120 extras = dict(repo.ui.configitems('rhodecode_extras'))
121 121 username = extras['username']
122 122 repository = extras['repository']
123 123 action = extras['action'] + ':%s'
124 124 scm = extras['scm']
125 125
126 126 if scm == 'hg':
127 127 node = kwargs['node']
128 128
129 129 def get_revs(repo, rev_opt):
130 130 if rev_opt:
131 131 revs = revrange(repo, rev_opt)
132 132
133 133 if len(revs) == 0:
134 134 return (nullrev, nullrev)
135 135 return (max(revs), min(revs))
136 136 else:
137 137 return (len(repo) - 1, 0)
138 138
139 139 stop, start = get_revs(repo, [node + ':'])
140 140 h = binascii.hexlify
141 141 revs = (h(repo[r].node()) for r in xrange(start, stop + 1))
142 142 elif scm == 'git':
143 143 revs = kwargs.get('_git_revs', [])
144 144 if '_git_revs' in kwargs:
145 145 kwargs.pop('_git_revs')
146 146
147 147 action = action % ','.join(revs)
148 148
149 149 action_logger(username, action, repository, extras['ip'], commit=True)
150 150
151 151 # extension hook call
152 from rhodecode import EXTENSIONS
152 153 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
153 154 if isfunction(callback):
154 155 kw = {'pushed_revs': revs}
155 156 kw.update(extras)
156 157 callback(**kw)
157 158 return 0
158 159
159 160
160 161 def log_create_repository(repository_dict, created_by, **kwargs):
161 162 """
162 163 Post create repository Hook. This is a dummy function for admins to re-use
163 164 if needed. It's taken from rhodecode-extensions module and executed
164 165 if present
165 166
166 167 :param repository: dict dump of repository object
167 168 :param created_by: username who created repository
168 169 :param created_date: date of creation
169 170
170 171 available keys of repository_dict:
171 172
172 173 'repo_type',
173 174 'description',
174 175 'private',
175 176 'created_on',
176 177 'enable_downloads',
177 178 'repo_id',
178 179 'user_id',
179 180 'enable_statistics',
180 181 'clone_uri',
181 182 'fork_id',
182 183 'group_id',
183 184 'repo_name'
184 185
185 186 """
186
187 from rhodecode import EXTENSIONS
187 188 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
188 189 if isfunction(callback):
189 190 kw = {}
190 191 kw.update(repository_dict)
191 192 kw.update({'created_by': created_by})
192 193 kw.update(kwargs)
193 194 return callback(**kw)
194 195
195 196 return 0
196 197
197 198
198 199 def handle_git_post_receive(repo_path, revs, env):
199 200 """
200 201 A really hacky method that is runned by git pre-receive hook and logs
201 202 an push action together with pushed revisions. It's runned by subprocess
202 203 thus needs all info to be able to create a temp pylons enviroment, connect
203 204 to database and run the logging code. Hacky as sh**t but works. ps.
204 205 GIT SUCKS
205 206
206 207 :param repo_path:
207 208 :type repo_path:
208 209 :param revs:
209 210 :type revs:
210 211 :param env:
211 212 :type env:
212 213 """
213 214 from paste.deploy import appconfig
214 215 from sqlalchemy import engine_from_config
215 216 from rhodecode.config.environment import load_environment
216 217 from rhodecode.model import init_model
217 218 from rhodecode.model.db import RhodeCodeUi
218 219 from rhodecode.lib.utils import make_ui
219 220 from rhodecode.model.db import Repository
220 221
221 222 path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE'])
222 223 conf = appconfig('config:%s' % ini_name, relative_to=path)
223 224 load_environment(conf.global_conf, conf.local_conf)
224 225
225 226 engine = engine_from_config(conf, 'sqlalchemy.db1.')
226 227 init_model(engine)
227 228
228 229 baseui = make_ui('db')
229 230 repo = Repository.get_by_full_path(repo_path)
230 231
231 232 _hooks = dict(baseui.configitems('hooks')) or {}
232 233 # if push hook is enabled via web interface
233 234 if _hooks.get(RhodeCodeUi.HOOK_PUSH):
234 235
235 236 extras = {
236 237 'username': env['RHODECODE_USER'],
237 238 'repository': repo.repo_name,
238 239 'scm': 'git',
239 240 'action': 'push',
240 241 'ip': env['RHODECODE_CONFIG_IP'],
241 242 }
242 243 for k, v in extras.items():
243 244 baseui.setconfig('rhodecode_extras', k, v)
244 245 repo = repo.scm_instance
245 246 repo.ui = baseui
246 247 old_rev, new_rev, ref = revs
247 248 if old_rev == EmptyChangeset().raw_id:
248 249 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
249 250 heads = repo.run_git_command(cmd)[0]
250 251 heads = heads.replace(ref, '')
251 252 cmd = 'log ' + new_rev + ' --reverse --pretty=format:"%H" --not ' + heads
252 253 else:
253 254 cmd = 'log ' + old_rev + '..' + new_rev + ' --reverse --pretty=format:"%H"'
254 255 git_revs = repo.run_git_command(cmd)[0].splitlines()
255 256
256 257 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,198 +1,199 b''
1 1 import os
2 2 import socket
3 3 import logging
4 4 import subprocess
5 5
6 6 from webob import Request, Response, exc
7 7
8 8 from rhodecode.lib import subprocessio
9 9
10 10 log = logging.getLogger(__name__)
11 11
12 12
13 13 class FileWrapper(object):
14 14
15 15 def __init__(self, fd, content_length):
16 16 self.fd = fd
17 17 self.content_length = content_length
18 18 self.remain = content_length
19 19
20 20 def read(self, size):
21 21 if size <= self.remain:
22 22 try:
23 23 data = self.fd.read(size)
24 24 except socket.error:
25 25 raise IOError(self)
26 26 self.remain -= size
27 27 elif self.remain:
28 28 data = self.fd.read(self.remain)
29 29 self.remain = 0
30 30 else:
31 31 data = None
32 32 return data
33 33
34 34 def __repr__(self):
35 35 return '<FileWrapper %s len: %s, read: %s>' % (
36 36 self.fd, self.content_length, self.content_length - self.remain
37 37 )
38 38
39 39
40 40 class GitRepository(object):
41 41 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
42 42 commands = ['git-upload-pack', 'git-receive-pack']
43 43
44 44 def __init__(self, repo_name, content_path, username):
45 45 files = set([f.lower() for f in os.listdir(content_path)])
46 46 if not (self.git_folder_signature.intersection(files)
47 47 == self.git_folder_signature):
48 48 raise OSError('%s missing git signature' % content_path)
49 49 self.content_path = content_path
50 50 self.valid_accepts = ['application/x-%s-result' %
51 51 c for c in self.commands]
52 52 self.repo_name = repo_name
53 53 self.username = username
54 54
55 55 def _get_fixedpath(self, path):
56 56 """
57 57 Small fix for repo_path
58 58
59 59 :param path:
60 60 :type path:
61 61 """
62 62 return path.split(self.repo_name, 1)[-1].strip('/')
63 63
64 64 def inforefs(self, request, environ):
65 65 """
66 66 WSGI Response producer for HTTP GET Git Smart
67 67 HTTP /info/refs request.
68 68 """
69 69
70 70 git_command = request.GET['service']
71 71 if git_command not in self.commands:
72 72 log.debug('command %s not allowed' % git_command)
73 73 return exc.HTTPMethodNotAllowed()
74 74
75 75 # note to self:
76 76 # please, resist the urge to add '\n' to git capture and increment
77 77 # line count by 1.
78 78 # The code in Git client not only does NOT need '\n', but actually
79 79 # blows up if you sprinkle "flush" (0000) as "0001\n".
80 80 # It reads binary, per number of bytes specified.
81 81 # if you do add '\n' as part of data, count it.
82 82 smart_server_advert = '# service=%s' % git_command
83 83 try:
84 84 out = subprocessio.SubprocessIOChunker(
85 85 r'git %s --stateless-rpc --advertise-refs "%s"' % (
86 86 git_command[4:], self.content_path),
87 87 starting_values=[
88 88 str(hex(len(smart_server_advert) + 4)[2:]
89 89 .rjust(4, '0') + smart_server_advert + '0000')
90 90 ]
91 91 )
92 92 except EnvironmentError, e:
93 93 log.exception(e)
94 94 raise exc.HTTPExpectationFailed()
95 95 resp = Response()
96 96 resp.content_type = 'application/x-%s-advertisement' % str(git_command)
97 97 resp.app_iter = out
98 98 return resp
99 99
100 100 def backend(self, request, environ):
101 101 """
102 102 WSGI Response producer for HTTP POST Git Smart HTTP requests.
103 103 Reads commands and data from HTTP POST's body.
104 104 returns an iterator obj with contents of git command's
105 105 response to stdout
106 106 """
107 107 git_command = self._get_fixedpath(request.path_info)
108 108 if git_command not in self.commands:
109 109 log.debug('command %s not allowed' % git_command)
110 110 return exc.HTTPMethodNotAllowed()
111 111
112 112 if 'CONTENT_LENGTH' in environ:
113 113 inputstream = FileWrapper(environ['wsgi.input'],
114 114 request.content_length)
115 115 else:
116 116 inputstream = environ['wsgi.input']
117 117
118 118 try:
119 119 gitenv = os.environ
120 120 from rhodecode import CONFIG
121 121 from rhodecode.lib.base import _get_ip_addr
122 122 gitenv['RHODECODE_USER'] = self.username
123 123 gitenv['RHODECODE_CONFIG_IP'] = _get_ip_addr(environ)
124 124 # forget all configs
125 125 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
126 126 # we need current .ini file used to later initialize rhodecode
127 127 # env and connect to db
128 128 gitenv['RHODECODE_CONFIG_FILE'] = CONFIG['__file__']
129 129 opts = dict(
130 env=gitenv
130 env=gitenv,
131 cwd=os.getcwd()
131 132 )
132 133 out = subprocessio.SubprocessIOChunker(
133 134 r'git %s --stateless-rpc "%s"' % (git_command[4:],
134 135 self.content_path),
135 136 inputstream=inputstream,
136 137 **opts
137 138 )
138 139 except EnvironmentError, e:
139 140 log.exception(e)
140 141 raise exc.HTTPExpectationFailed()
141 142
142 143 if git_command in [u'git-receive-pack']:
143 144 # updating refs manually after each push.
144 145 # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
145 146 subprocess.call(u'git --git-dir "%s" '
146 147 'update-server-info' % self.content_path,
147 148 shell=True)
148 149
149 150 resp = Response()
150 151 resp.content_type = 'application/x-%s-result' % git_command.encode('utf8')
151 152 resp.app_iter = out
152 153 return resp
153 154
154 155 def __call__(self, environ, start_response):
155 156 request = Request(environ)
156 157 _path = self._get_fixedpath(request.path_info)
157 158 if _path.startswith('info/refs'):
158 159 app = self.inforefs
159 160 elif [a for a in self.valid_accepts if a in request.accept]:
160 161 app = self.backend
161 162 try:
162 163 resp = app(request, environ)
163 164 except exc.HTTPException, e:
164 165 resp = e
165 166 log.exception(e)
166 167 except Exception, e:
167 168 log.exception(e)
168 169 resp = exc.HTTPInternalServerError()
169 170 return resp(environ, start_response)
170 171
171 172
172 173 class GitDirectory(object):
173 174
174 175 def __init__(self, repo_root, repo_name, username):
175 176 repo_location = os.path.join(repo_root, repo_name)
176 177 if not os.path.isdir(repo_location):
177 178 raise OSError(repo_location)
178 179
179 180 self.content_path = repo_location
180 181 self.repo_name = repo_name
181 182 self.repo_location = repo_location
182 183 self.username = username
183 184
184 185 def __call__(self, environ, start_response):
185 186 content_path = self.content_path
186 187 try:
187 188 app = GitRepository(self.repo_name, content_path, self.username)
188 189 except (AssertionError, OSError):
189 190 if os.path.isdir(os.path.join(content_path, '.git')):
190 191 app = GitRepository(self.repo_name,
191 192 os.path.join(content_path, '.git'))
192 193 else:
193 194 return exc.HTTPNotFound()(environ, start_response, self.username)
194 195 return app(environ, start_response)
195 196
196 197
197 198 def make_wsgi_app(repo_name, repo_root, username):
198 199 return GitDirectory(repo_root, repo_name, username)
General Comments 0
You need to be logged in to leave comments. Login now