##// END OF EJS Templates
fixed #545, exception during cloning of non-bare repositories
marcink -
r2764:c7a27b04 beta
parent child Browse files
Show More
@@ -1,201 +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
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, extras):
44 def __init__(self, repo_name, content_path, extras):
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.extras = extras
53 self.extras = extras
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.get('service')
70 git_command = request.GET.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 server_advert = '# service=%s' % git_command
82 server_advert = '# service=%s' % git_command
83 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
83 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
84 try:
84 try:
85 out = subprocessio.SubprocessIOChunker(
85 out = subprocessio.SubprocessIOChunker(
86 r'git %s --stateless-rpc --advertise-refs "%s"' % (
86 r'git %s --stateless-rpc --advertise-refs "%s"' % (
87 git_command[4:], self.content_path),
87 git_command[4:], self.content_path),
88 starting_values=[
88 starting_values=[
89 packet_len + server_advert + '0000'
89 packet_len + 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.charset = None
97 resp.charset = None
98 resp.app_iter = out
98 resp.app_iter = out
99 return resp
99 return resp
100
100
101 def backend(self, request, environ):
101 def backend(self, request, environ):
102 """
102 """
103 WSGI Response producer for HTTP POST Git Smart HTTP requests.
103 WSGI Response producer for HTTP POST Git Smart HTTP requests.
104 Reads commands and data from HTTP POST's body.
104 Reads commands and data from HTTP POST's body.
105 returns an iterator obj with contents of git command's
105 returns an iterator obj with contents of git command's
106 response to stdout
106 response to stdout
107 """
107 """
108 git_command = self._get_fixedpath(request.path_info)
108 git_command = self._get_fixedpath(request.path_info)
109 if git_command not in self.commands:
109 if git_command not in self.commands:
110 log.debug('command %s not allowed' % git_command)
110 log.debug('command %s not allowed' % git_command)
111 return exc.HTTPMethodNotAllowed()
111 return exc.HTTPMethodNotAllowed()
112
112
113 if 'CONTENT_LENGTH' in environ:
113 if 'CONTENT_LENGTH' in environ:
114 inputstream = FileWrapper(environ['wsgi.input'],
114 inputstream = FileWrapper(environ['wsgi.input'],
115 request.content_length)
115 request.content_length)
116 else:
116 else:
117 inputstream = environ['wsgi.input']
117 inputstream = environ['wsgi.input']
118
118
119 try:
119 try:
120 gitenv = os.environ
120 gitenv = os.environ
121 from rhodecode import CONFIG
121 from rhodecode import CONFIG
122 from rhodecode.lib.compat import json
122 from rhodecode.lib.compat import json
123 gitenv['RHODECODE_EXTRAS'] = json.dumps(self.extras)
123 gitenv['RHODECODE_EXTRAS'] = json.dumps(self.extras)
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.charset = None
152 resp.charset = None
153 resp.app_iter = out
153 resp.app_iter = out
154 return resp
154 return resp
155
155
156 def __call__(self, environ, start_response):
156 def __call__(self, environ, start_response):
157 request = Request(environ)
157 request = Request(environ)
158 _path = self._get_fixedpath(request.path_info)
158 _path = self._get_fixedpath(request.path_info)
159 if _path.startswith('info/refs'):
159 if _path.startswith('info/refs'):
160 app = self.inforefs
160 app = self.inforefs
161 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]:
162 app = self.backend
162 app = self.backend
163 try:
163 try:
164 resp = app(request, environ)
164 resp = app(request, environ)
165 except exc.HTTPException, e:
165 except exc.HTTPException, e:
166 resp = e
166 resp = e
167 log.exception(e)
167 log.exception(e)
168 except Exception, e:
168 except Exception, e:
169 log.exception(e)
169 log.exception(e)
170 resp = exc.HTTPInternalServerError()
170 resp = exc.HTTPInternalServerError()
171 return resp(environ, start_response)
171 return resp(environ, start_response)
172
172
173
173
174 class GitDirectory(object):
174 class GitDirectory(object):
175
175
176 def __init__(self, repo_root, repo_name, extras):
176 def __init__(self, repo_root, repo_name, extras):
177 repo_location = os.path.join(repo_root, repo_name)
177 repo_location = os.path.join(repo_root, repo_name)
178 if not os.path.isdir(repo_location):
178 if not os.path.isdir(repo_location):
179 raise OSError(repo_location)
179 raise OSError(repo_location)
180
180
181 self.content_path = repo_location
181 self.content_path = repo_location
182 self.repo_name = repo_name
182 self.repo_name = repo_name
183 self.repo_location = repo_location
183 self.repo_location = repo_location
184 self.extras = extras
184 self.extras = extras
185
185
186 def __call__(self, environ, start_response):
186 def __call__(self, environ, start_response):
187 content_path = self.content_path
187 content_path = self.content_path
188 try:
188 try:
189 app = GitRepository(self.repo_name, content_path, self.extras)
189 app = GitRepository(self.repo_name, content_path, self.extras)
190 except (AssertionError, OSError):
190 except (AssertionError, OSError):
191 if os.path.isdir(os.path.join(content_path, '.git')):
191 content_path = os.path.join(content_path, '.git')
192 app = GitRepository(self.repo_name,
192 if os.path.isdir(content_path):
193 os.path.join(content_path, '.git'),
193 app = GitRepository(self.repo_name, content_path, self.extras)
194 self.username)
195 else:
194 else:
196 return exc.HTTPNotFound()(environ, start_response)
195 return exc.HTTPNotFound()(environ, start_response)
197 return app(environ, start_response)
196 return app(environ, start_response)
198
197
199
198
200 def make_wsgi_app(repo_name, repo_root, extras):
199 def make_wsgi_app(repo_name, repo_root, extras):
201 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