##// END OF EJS Templates
more logging in pygrack
marcink -
r2875:f68522e3 beta
parent child Browse files
Show More
@@ -1,197 +1,200 b''
1 import os
1 import os
2 import socket
2 import socket
3 import logging
3 import logging
4 import subprocess
4 import subprocess
5 import traceback
5
6
6 from webob import Request, Response, exc
7 from webob import Request, Response, exc
7
8
8 from rhodecode.lib import subprocessio
9 from rhodecode.lib import subprocessio
9
10
10 log = logging.getLogger(__name__)
11 log = logging.getLogger(__name__)
11
12
12
13
13 class FileWrapper(object):
14 class FileWrapper(object):
14
15
15 def __init__(self, fd, content_length):
16 def __init__(self, fd, content_length):
16 self.fd = fd
17 self.fd = fd
17 self.content_length = content_length
18 self.content_length = content_length
18 self.remain = content_length
19 self.remain = content_length
19
20
20 def read(self, size):
21 def read(self, size):
21 if size <= self.remain:
22 if size <= self.remain:
22 try:
23 try:
23 data = self.fd.read(size)
24 data = self.fd.read(size)
24 except socket.error:
25 except socket.error:
25 raise IOError(self)
26 raise IOError(self)
26 self.remain -= size
27 self.remain -= size
27 elif self.remain:
28 elif self.remain:
28 data = self.fd.read(self.remain)
29 data = self.fd.read(self.remain)
29 self.remain = 0
30 self.remain = 0
30 else:
31 else:
31 data = None
32 data = None
32 return data
33 return data
33
34
34 def __repr__(self):
35 def __repr__(self):
35 return '<FileWrapper %s len: %s, read: %s>' % (
36 return '<FileWrapper %s len: %s, read: %s>' % (
36 self.fd, self.content_length, self.content_length - self.remain
37 self.fd, self.content_length, self.content_length - self.remain
37 )
38 )
38
39
39
40
40 class GitRepository(object):
41 class GitRepository(object):
41 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
42 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
42 commands = ['git-upload-pack', 'git-receive-pack']
43 commands = ['git-upload-pack', 'git-receive-pack']
43
44
44 def __init__(self, repo_name, content_path, extras):
45 def __init__(self, repo_name, content_path, extras):
45 files = set([f.lower() for f in os.listdir(content_path)])
46 files = set([f.lower() for f in os.listdir(content_path)])
46 if not (self.git_folder_signature.intersection(files)
47 if not (self.git_folder_signature.intersection(files)
47 == self.git_folder_signature):
48 == self.git_folder_signature):
48 raise OSError('%s missing git signature' % content_path)
49 raise OSError('%s missing git signature' % content_path)
49 self.content_path = content_path
50 self.content_path = content_path
50 self.valid_accepts = ['application/x-%s-result' %
51 self.valid_accepts = ['application/x-%s-result' %
51 c for c in self.commands]
52 c for c in self.commands]
52 self.repo_name = repo_name
53 self.repo_name = repo_name
53 self.extras = extras
54 self.extras = extras
54
55
55 def _get_fixedpath(self, path):
56 def _get_fixedpath(self, path):
56 """
57 """
57 Small fix for repo_path
58 Small fix for repo_path
58
59
59 :param path:
60 :param path:
60 :type path:
61 :type path:
61 """
62 """
62 return path.split(self.repo_name, 1)[-1].strip('/')
63 return path.split(self.repo_name, 1)[-1].strip('/')
63
64
64 def inforefs(self, request, environ):
65 def inforefs(self, request, environ):
65 """
66 """
66 WSGI Response producer for HTTP GET Git Smart
67 WSGI Response producer for HTTP GET Git Smart
67 HTTP /info/refs request.
68 HTTP /info/refs request.
68 """
69 """
69
70
70 git_command = request.GET.get('service')
71 git_command = request.GET.get('service')
71 if git_command not in self.commands:
72 if git_command not in self.commands:
72 log.debug('command %s not allowed' % git_command)
73 log.debug('command %s not allowed' % git_command)
73 return exc.HTTPMethodNotAllowed()
74 return exc.HTTPMethodNotAllowed()
74
75
75 # note to self:
76 # note to self:
76 # please, resist the urge to add '\n' to git capture and increment
77 # please, resist the urge to add '\n' to git capture and increment
77 # line count by 1.
78 # line count by 1.
78 # The code in Git client not only does NOT need '\n', but actually
79 # The code in Git client not only does NOT need '\n', but actually
79 # blows up if you sprinkle "flush" (0000) as "0001\n".
80 # blows up if you sprinkle "flush" (0000) as "0001\n".
80 # It reads binary, per number of bytes specified.
81 # It reads binary, per number of bytes specified.
81 # if you do add '\n' as part of data, count it.
82 # if you do add '\n' as part of data, count it.
82 server_advert = '# service=%s' % git_command
83 server_advert = '# service=%s' % git_command
83 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
84 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
84 try:
85 try:
85 out = subprocessio.SubprocessIOChunker(
86 out = subprocessio.SubprocessIOChunker(
86 r'git %s --stateless-rpc --advertise-refs "%s"' % (
87 r'git %s --stateless-rpc --advertise-refs "%s"' % (
87 git_command[4:], self.content_path),
88 git_command[4:], self.content_path),
88 starting_values=[
89 starting_values=[
89 packet_len + server_advert + '0000'
90 packet_len + server_advert + '0000'
90 ]
91 ]
91 )
92 )
92 except EnvironmentError, e:
93 except EnvironmentError, e:
93 log.exception(e)
94 log.error(traceback.format_exc())
94 raise exc.HTTPExpectationFailed()
95 raise exc.HTTPExpectationFailed()
95 resp = Response()
96 resp = Response()
96 resp.content_type = 'application/x-%s-advertisement' % str(git_command)
97 resp.content_type = 'application/x-%s-advertisement' % str(git_command)
97 resp.charset = None
98 resp.charset = None
98 resp.app_iter = out
99 resp.app_iter = out
99 return resp
100 return resp
100
101
101 def backend(self, request, environ):
102 def backend(self, request, environ):
102 """
103 """
103 WSGI Response producer for HTTP POST Git Smart HTTP requests.
104 WSGI Response producer for HTTP POST Git Smart HTTP requests.
104 Reads commands and data from HTTP POST's body.
105 Reads commands and data from HTTP POST's body.
105 returns an iterator obj with contents of git command's
106 returns an iterator obj with contents of git command's
106 response to stdout
107 response to stdout
107 """
108 """
108 git_command = self._get_fixedpath(request.path_info)
109 git_command = self._get_fixedpath(request.path_info)
109 if git_command not in self.commands:
110 if git_command not in self.commands:
110 log.debug('command %s not allowed' % git_command)
111 log.debug('command %s not allowed' % git_command)
111 return exc.HTTPMethodNotAllowed()
112 return exc.HTTPMethodNotAllowed()
112
113
113 if 'CONTENT_LENGTH' in environ:
114 if 'CONTENT_LENGTH' in environ:
114 inputstream = FileWrapper(environ['wsgi.input'],
115 inputstream = FileWrapper(environ['wsgi.input'],
115 request.content_length)
116 request.content_length)
116 else:
117 else:
117 inputstream = environ['wsgi.input']
118 inputstream = environ['wsgi.input']
118
119
119 try:
120 try:
120 gitenv = os.environ
121 gitenv = os.environ
121 from rhodecode.lib.compat import json
122 from rhodecode.lib.compat import json
122 gitenv['RHODECODE_EXTRAS'] = json.dumps(self.extras)
123 gitenv['RHODECODE_EXTRAS'] = json.dumps(self.extras)
123 # forget all configs
124 # forget all configs
124 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
125 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
125 opts = dict(
126 opts = dict(
126 env=gitenv,
127 env=gitenv,
127 cwd=os.getcwd()
128 cwd=os.getcwd()
128 )
129 )
129
130 cmd = r'git %s --stateless-rpc "%s"' % (git_command[4:],
131 self.content_path),
132 log.debug('handling cmd %s' % cmd)
130 out = subprocessio.SubprocessIOChunker(
133 out = subprocessio.SubprocessIOChunker(
131 r'git %s --stateless-rpc "%s"' % (git_command[4:],
134 cmd,
132 self.content_path),
133 inputstream=inputstream,
135 inputstream=inputstream,
134 **opts
136 **opts
135 )
137 )
136 except EnvironmentError, e:
138 except EnvironmentError, e:
137 log.exception(e)
139 log.error(traceback.format_exc())
138 raise exc.HTTPExpectationFailed()
140 raise exc.HTTPExpectationFailed()
139
141
140 if git_command in [u'git-receive-pack']:
142 if git_command in [u'git-receive-pack']:
141 # updating refs manually after each push.
143 # updating refs manually after each push.
142 # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
144 # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
143 subprocess.call(u'git --git-dir "%s" '
145 cmd = (u'git --git-dir "%s" '
144 'update-server-info' % self.content_path,
146 'update-server-info' % self.content_path)
145 shell=True)
147 log.debug('handling cmd %s' % cmd)
148 subprocess.call(cmd, shell=True)
146
149
147 resp = Response()
150 resp = Response()
148 resp.content_type = 'application/x-%s-result' % git_command.encode('utf8')
151 resp.content_type = 'application/x-%s-result' % git_command.encode('utf8')
149 resp.charset = None
152 resp.charset = None
150 resp.app_iter = out
153 resp.app_iter = out
151 return resp
154 return resp
152
155
153 def __call__(self, environ, start_response):
156 def __call__(self, environ, start_response):
154 request = Request(environ)
157 request = Request(environ)
155 _path = self._get_fixedpath(request.path_info)
158 _path = self._get_fixedpath(request.path_info)
156 if _path.startswith('info/refs'):
159 if _path.startswith('info/refs'):
157 app = self.inforefs
160 app = self.inforefs
158 elif [a for a in self.valid_accepts if a in request.accept]:
161 elif [a for a in self.valid_accepts if a in request.accept]:
159 app = self.backend
162 app = self.backend
160 try:
163 try:
161 resp = app(request, environ)
164 resp = app(request, environ)
162 except exc.HTTPException, e:
165 except exc.HTTPException, e:
163 resp = e
166 resp = e
164 log.exception(e)
167 log.error(traceback.format_exc())
165 except Exception, e:
168 except Exception, e:
166 log.exception(e)
169 log.error(traceback.format_exc())
167 resp = exc.HTTPInternalServerError()
170 resp = exc.HTTPInternalServerError()
168 return resp(environ, start_response)
171 return resp(environ, start_response)
169
172
170
173
171 class GitDirectory(object):
174 class GitDirectory(object):
172
175
173 def __init__(self, repo_root, repo_name, extras):
176 def __init__(self, repo_root, repo_name, extras):
174 repo_location = os.path.join(repo_root, repo_name)
177 repo_location = os.path.join(repo_root, repo_name)
175 if not os.path.isdir(repo_location):
178 if not os.path.isdir(repo_location):
176 raise OSError(repo_location)
179 raise OSError(repo_location)
177
180
178 self.content_path = repo_location
181 self.content_path = repo_location
179 self.repo_name = repo_name
182 self.repo_name = repo_name
180 self.repo_location = repo_location
183 self.repo_location = repo_location
181 self.extras = extras
184 self.extras = extras
182
185
183 def __call__(self, environ, start_response):
186 def __call__(self, environ, start_response):
184 content_path = self.content_path
187 content_path = self.content_path
185 try:
188 try:
186 app = GitRepository(self.repo_name, content_path, self.extras)
189 app = GitRepository(self.repo_name, content_path, self.extras)
187 except (AssertionError, OSError):
190 except (AssertionError, OSError):
188 content_path = os.path.join(content_path, '.git')
191 content_path = os.path.join(content_path, '.git')
189 if os.path.isdir(content_path):
192 if os.path.isdir(content_path):
190 app = GitRepository(self.repo_name, content_path, self.extras)
193 app = GitRepository(self.repo_name, content_path, self.extras)
191 else:
194 else:
192 return exc.HTTPNotFound()(environ, start_response)
195 return exc.HTTPNotFound()(environ, start_response)
193 return app(environ, start_response)
196 return app(environ, start_response)
194
197
195
198
196 def make_wsgi_app(repo_name, repo_root, extras):
199 def make_wsgi_app(repo_name, repo_root, extras):
197 return GitDirectory(repo_root, repo_name, extras)
200 return GitDirectory(repo_root, repo_name, extras)
General Comments 0
You need to be logged in to leave comments. Login now