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