##// END OF EJS Templates
git: use new sync_fetch that uses git subcommand instead of fragile dulwich code.
marcink -
r549:ed3e9024 default
parent child Browse files
Show More
@@ -1,675 +1,711 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17 import collections
18 import logging
18 import logging
19 import os
19 import os
20 import posixpath as vcspath
20 import posixpath as vcspath
21 import re
21 import re
22 import stat
22 import stat
23 import traceback
23 import traceback
24 import urllib
24 import urllib
25 import urllib2
25 import urllib2
26 from functools import wraps
26 from functools import wraps
27
27
28 from dulwich import index, objects
28 from dulwich import index, objects
29 from dulwich.client import HttpGitClient, LocalGitClient
29 from dulwich.client import HttpGitClient, LocalGitClient
30 from dulwich.errors import (
30 from dulwich.errors import (
31 NotGitRepository, ChecksumMismatch, WrongObjectException,
31 NotGitRepository, ChecksumMismatch, WrongObjectException,
32 MissingCommitError, ObjectMissing, HangupException,
32 MissingCommitError, ObjectMissing, HangupException,
33 UnexpectedCommandError)
33 UnexpectedCommandError)
34 from dulwich.repo import Repo as DulwichRepo, Tag
34 from dulwich.repo import Repo as DulwichRepo, Tag
35 from dulwich.server import update_server_info
35 from dulwich.server import update_server_info
36
36
37 from vcsserver import exceptions, settings, subprocessio
37 from vcsserver import exceptions, settings, subprocessio
38 from vcsserver.utils import safe_str
38 from vcsserver.utils import safe_str
39 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
39 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
40 from vcsserver.hgcompat import (
40 from vcsserver.hgcompat import (
41 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
41 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
42 from vcsserver.git_lfs.lib import LFSOidStore
42 from vcsserver.git_lfs.lib import LFSOidStore
43
43
44 DIR_STAT = stat.S_IFDIR
44 DIR_STAT = stat.S_IFDIR
45 FILE_MODE = stat.S_IFMT
45 FILE_MODE = stat.S_IFMT
46 GIT_LINK = objects.S_IFGITLINK
46 GIT_LINK = objects.S_IFGITLINK
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 def reraise_safe_exceptions(func):
51 def reraise_safe_exceptions(func):
52 """Converts Dulwich exceptions to something neutral."""
52 """Converts Dulwich exceptions to something neutral."""
53 @wraps(func)
53 @wraps(func)
54 def wrapper(*args, **kwargs):
54 def wrapper(*args, **kwargs):
55 try:
55 try:
56 return func(*args, **kwargs)
56 return func(*args, **kwargs)
57 except (ChecksumMismatch, WrongObjectException, MissingCommitError,
57 except (ChecksumMismatch, WrongObjectException, MissingCommitError,
58 ObjectMissing) as e:
58 ObjectMissing) as e:
59 raise exceptions.LookupException(e)(e.message)
59 raise exceptions.LookupException(e)(e.message)
60 except (HangupException, UnexpectedCommandError) as e:
60 except (HangupException, UnexpectedCommandError) as e:
61 raise exceptions.VcsException(e)(e.message)
61 raise exceptions.VcsException(e)(e.message)
62 except Exception as e:
62 except Exception as e:
63 # NOTE(marcink): becuase of how dulwich handles some exceptions
63 # NOTE(marcink): becuase of how dulwich handles some exceptions
64 # (KeyError on empty repos), we cannot track this and catch all
64 # (KeyError on empty repos), we cannot track this and catch all
65 # exceptions, it's an exceptions from other handlers
65 # exceptions, it's an exceptions from other handlers
66 #if not hasattr(e, '_vcs_kind'):
66 #if not hasattr(e, '_vcs_kind'):
67 #log.exception("Unhandled exception in git remote call")
67 #log.exception("Unhandled exception in git remote call")
68 #raise_from_original(exceptions.UnhandledException)
68 #raise_from_original(exceptions.UnhandledException)
69 raise
69 raise
70 return wrapper
70 return wrapper
71
71
72
72
73 class Repo(DulwichRepo):
73 class Repo(DulwichRepo):
74 """
74 """
75 A wrapper for dulwich Repo class.
75 A wrapper for dulwich Repo class.
76
76
77 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
77 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
78 "Too many open files" error. We need to close all opened file descriptors
78 "Too many open files" error. We need to close all opened file descriptors
79 once the repo object is destroyed.
79 once the repo object is destroyed.
80
80
81 TODO: mikhail: please check if we need this wrapper after updating dulwich
81 TODO: mikhail: please check if we need this wrapper after updating dulwich
82 to 0.12.0 +
82 to 0.12.0 +
83 """
83 """
84 def __del__(self):
84 def __del__(self):
85 if hasattr(self, 'object_store'):
85 if hasattr(self, 'object_store'):
86 self.close()
86 self.close()
87
87
88
88
89 class GitFactory(RepoFactory):
89 class GitFactory(RepoFactory):
90 repo_type = 'git'
90 repo_type = 'git'
91
91
92 def _create_repo(self, wire, create):
92 def _create_repo(self, wire, create):
93 repo_path = str_to_dulwich(wire['path'])
93 repo_path = str_to_dulwich(wire['path'])
94 return Repo(repo_path)
94 return Repo(repo_path)
95
95
96
96
97 class GitRemote(object):
97 class GitRemote(object):
98
98
99 def __init__(self, factory):
99 def __init__(self, factory):
100 self._factory = factory
100 self._factory = factory
101
101
102 self._bulk_methods = {
102 self._bulk_methods = {
103 "author": self.commit_attribute,
103 "author": self.commit_attribute,
104 "date": self.get_object_attrs,
104 "date": self.get_object_attrs,
105 "message": self.commit_attribute,
105 "message": self.commit_attribute,
106 "parents": self.commit_attribute,
106 "parents": self.commit_attribute,
107 "_commit": self.revision,
107 "_commit": self.revision,
108 }
108 }
109
109
110 def _wire_to_config(self, wire):
110 def _wire_to_config(self, wire):
111 if 'config' in wire:
111 if 'config' in wire:
112 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
112 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
113 return {}
113 return {}
114
114
115 def _assign_ref(self, wire, ref, commit_id):
115 def _assign_ref(self, wire, ref, commit_id):
116 repo = self._factory.repo(wire)
116 repo = self._factory.repo(wire)
117 repo[ref] = commit_id
117 repo[ref] = commit_id
118
118
119 @reraise_safe_exceptions
119 @reraise_safe_exceptions
120 def add_object(self, wire, content):
120 def add_object(self, wire, content):
121 repo = self._factory.repo(wire)
121 repo = self._factory.repo(wire)
122 blob = objects.Blob()
122 blob = objects.Blob()
123 blob.set_raw_string(content)
123 blob.set_raw_string(content)
124 repo.object_store.add_object(blob)
124 repo.object_store.add_object(blob)
125 return blob.id
125 return blob.id
126
126
127 @reraise_safe_exceptions
127 @reraise_safe_exceptions
128 def assert_correct_path(self, wire):
128 def assert_correct_path(self, wire):
129 path = wire.get('path')
129 path = wire.get('path')
130 try:
130 try:
131 self._factory.repo(wire)
131 self._factory.repo(wire)
132 except NotGitRepository as e:
132 except NotGitRepository as e:
133 tb = traceback.format_exc()
133 tb = traceback.format_exc()
134 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
134 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
135 return False
135 return False
136
136
137 return True
137 return True
138
138
139 @reraise_safe_exceptions
139 @reraise_safe_exceptions
140 def bare(self, wire):
140 def bare(self, wire):
141 repo = self._factory.repo(wire)
141 repo = self._factory.repo(wire)
142 return repo.bare
142 return repo.bare
143
143
144 @reraise_safe_exceptions
144 @reraise_safe_exceptions
145 def blob_as_pretty_string(self, wire, sha):
145 def blob_as_pretty_string(self, wire, sha):
146 repo = self._factory.repo(wire)
146 repo = self._factory.repo(wire)
147 return repo[sha].as_pretty_string()
147 return repo[sha].as_pretty_string()
148
148
149 @reraise_safe_exceptions
149 @reraise_safe_exceptions
150 def blob_raw_length(self, wire, sha):
150 def blob_raw_length(self, wire, sha):
151 repo = self._factory.repo(wire)
151 repo = self._factory.repo(wire)
152 blob = repo[sha]
152 blob = repo[sha]
153 return blob.raw_length()
153 return blob.raw_length()
154
154
155 def _parse_lfs_pointer(self, raw_content):
155 def _parse_lfs_pointer(self, raw_content):
156
156
157 spec_string = 'version https://git-lfs.github.com/spec'
157 spec_string = 'version https://git-lfs.github.com/spec'
158 if raw_content and raw_content.startswith(spec_string):
158 if raw_content and raw_content.startswith(spec_string):
159 pattern = re.compile(r"""
159 pattern = re.compile(r"""
160 (?:\n)?
160 (?:\n)?
161 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
161 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
162 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
162 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
163 ^size[ ](?P<oid_size>[0-9]+)\n
163 ^size[ ](?P<oid_size>[0-9]+)\n
164 (?:\n)?
164 (?:\n)?
165 """, re.VERBOSE | re.MULTILINE)
165 """, re.VERBOSE | re.MULTILINE)
166 match = pattern.match(raw_content)
166 match = pattern.match(raw_content)
167 if match:
167 if match:
168 return match.groupdict()
168 return match.groupdict()
169
169
170 return {}
170 return {}
171
171
172 @reraise_safe_exceptions
172 @reraise_safe_exceptions
173 def is_large_file(self, wire, sha):
173 def is_large_file(self, wire, sha):
174 repo = self._factory.repo(wire)
174 repo = self._factory.repo(wire)
175 blob = repo[sha]
175 blob = repo[sha]
176 return self._parse_lfs_pointer(blob.as_raw_string())
176 return self._parse_lfs_pointer(blob.as_raw_string())
177
177
178 @reraise_safe_exceptions
178 @reraise_safe_exceptions
179 def in_largefiles_store(self, wire, oid):
179 def in_largefiles_store(self, wire, oid):
180 repo = self._factory.repo(wire)
180 repo = self._factory.repo(wire)
181 conf = self._wire_to_config(wire)
181 conf = self._wire_to_config(wire)
182
182
183 store_location = conf.get('vcs_git_lfs_store_location')
183 store_location = conf.get('vcs_git_lfs_store_location')
184 if store_location:
184 if store_location:
185 repo_name = repo.path
185 repo_name = repo.path
186 store = LFSOidStore(
186 store = LFSOidStore(
187 oid=oid, repo=repo_name, store_location=store_location)
187 oid=oid, repo=repo_name, store_location=store_location)
188 return store.has_oid()
188 return store.has_oid()
189
189
190 return False
190 return False
191
191
192 @reraise_safe_exceptions
192 @reraise_safe_exceptions
193 def store_path(self, wire, oid):
193 def store_path(self, wire, oid):
194 repo = self._factory.repo(wire)
194 repo = self._factory.repo(wire)
195 conf = self._wire_to_config(wire)
195 conf = self._wire_to_config(wire)
196
196
197 store_location = conf.get('vcs_git_lfs_store_location')
197 store_location = conf.get('vcs_git_lfs_store_location')
198 if store_location:
198 if store_location:
199 repo_name = repo.path
199 repo_name = repo.path
200 store = LFSOidStore(
200 store = LFSOidStore(
201 oid=oid, repo=repo_name, store_location=store_location)
201 oid=oid, repo=repo_name, store_location=store_location)
202 return store.oid_path
202 return store.oid_path
203 raise ValueError('Unable to fetch oid with path {}'.format(oid))
203 raise ValueError('Unable to fetch oid with path {}'.format(oid))
204
204
205 @reraise_safe_exceptions
205 @reraise_safe_exceptions
206 def bulk_request(self, wire, rev, pre_load):
206 def bulk_request(self, wire, rev, pre_load):
207 result = {}
207 result = {}
208 for attr in pre_load:
208 for attr in pre_load:
209 try:
209 try:
210 method = self._bulk_methods[attr]
210 method = self._bulk_methods[attr]
211 args = [wire, rev]
211 args = [wire, rev]
212 if attr == "date":
212 if attr == "date":
213 args.extend(["commit_time", "commit_timezone"])
213 args.extend(["commit_time", "commit_timezone"])
214 elif attr in ["author", "message", "parents"]:
214 elif attr in ["author", "message", "parents"]:
215 args.append(attr)
215 args.append(attr)
216 result[attr] = method(*args)
216 result[attr] = method(*args)
217 except KeyError as e:
217 except KeyError as e:
218 raise exceptions.VcsException(e)(
218 raise exceptions.VcsException(e)(
219 "Unknown bulk attribute: %s" % attr)
219 "Unknown bulk attribute: %s" % attr)
220 return result
220 return result
221
221
222 def _build_opener(self, url):
222 def _build_opener(self, url):
223 handlers = []
223 handlers = []
224 url_obj = url_parser(url)
224 url_obj = url_parser(url)
225 _, authinfo = url_obj.authinfo()
225 _, authinfo = url_obj.authinfo()
226
226
227 if authinfo:
227 if authinfo:
228 # create a password manager
228 # create a password manager
229 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
229 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
230 passmgr.add_password(*authinfo)
230 passmgr.add_password(*authinfo)
231
231
232 handlers.extend((httpbasicauthhandler(passmgr),
232 handlers.extend((httpbasicauthhandler(passmgr),
233 httpdigestauthhandler(passmgr)))
233 httpdigestauthhandler(passmgr)))
234
234
235 return urllib2.build_opener(*handlers)
235 return urllib2.build_opener(*handlers)
236
236
237 @reraise_safe_exceptions
237 @reraise_safe_exceptions
238 def check_url(self, url, config):
238 def check_url(self, url, config):
239 url_obj = url_parser(url)
239 url_obj = url_parser(url)
240 test_uri, _ = url_obj.authinfo()
240 test_uri, _ = url_obj.authinfo()
241 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
241 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
242 url_obj.query = obfuscate_qs(url_obj.query)
242 url_obj.query = obfuscate_qs(url_obj.query)
243 cleaned_uri = str(url_obj)
243 cleaned_uri = str(url_obj)
244 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
244 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
245
245
246 if not test_uri.endswith('info/refs'):
246 if not test_uri.endswith('info/refs'):
247 test_uri = test_uri.rstrip('/') + '/info/refs'
247 test_uri = test_uri.rstrip('/') + '/info/refs'
248
248
249 o = self._build_opener(url)
249 o = self._build_opener(url)
250 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
250 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
251
251
252 q = {"service": 'git-upload-pack'}
252 q = {"service": 'git-upload-pack'}
253 qs = '?%s' % urllib.urlencode(q)
253 qs = '?%s' % urllib.urlencode(q)
254 cu = "%s%s" % (test_uri, qs)
254 cu = "%s%s" % (test_uri, qs)
255 req = urllib2.Request(cu, None, {})
255 req = urllib2.Request(cu, None, {})
256
256
257 try:
257 try:
258 log.debug("Trying to open URL %s", cleaned_uri)
258 log.debug("Trying to open URL %s", cleaned_uri)
259 resp = o.open(req)
259 resp = o.open(req)
260 if resp.code != 200:
260 if resp.code != 200:
261 raise exceptions.URLError()('Return Code is not 200')
261 raise exceptions.URLError()('Return Code is not 200')
262 except Exception as e:
262 except Exception as e:
263 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
263 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
264 # means it cannot be cloned
264 # means it cannot be cloned
265 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
265 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
266
266
267 # now detect if it's proper git repo
267 # now detect if it's proper git repo
268 gitdata = resp.read()
268 gitdata = resp.read()
269 if 'service=git-upload-pack' in gitdata:
269 if 'service=git-upload-pack' in gitdata:
270 pass
270 pass
271 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
271 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
272 # old style git can return some other format !
272 # old style git can return some other format !
273 pass
273 pass
274 else:
274 else:
275 raise exceptions.URLError()(
275 raise exceptions.URLError()(
276 "url [%s] does not look like an git" % (cleaned_uri,))
276 "url [%s] does not look like an git" % (cleaned_uri,))
277
277
278 return True
278 return True
279
279
280 @reraise_safe_exceptions
280 @reraise_safe_exceptions
281 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
281 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
282 remote_refs = self.fetch(wire, url, apply_refs=False)
282 remote_refs = self.fetch(wire, url, apply_refs=False)
283 repo = self._factory.repo(wire)
283 repo = self._factory.repo(wire)
284 if isinstance(valid_refs, list):
284 if isinstance(valid_refs, list):
285 valid_refs = tuple(valid_refs)
285 valid_refs = tuple(valid_refs)
286
286
287 for k in remote_refs:
287 for k in remote_refs:
288 # only parse heads/tags and skip so called deferred tags
288 # only parse heads/tags and skip so called deferred tags
289 if k.startswith(valid_refs) and not k.endswith(deferred):
289 if k.startswith(valid_refs) and not k.endswith(deferred):
290 repo[k] = remote_refs[k]
290 repo[k] = remote_refs[k]
291
291
292 if update_after_clone:
292 if update_after_clone:
293 # we want to checkout HEAD
293 # we want to checkout HEAD
294 repo["HEAD"] = remote_refs["HEAD"]
294 repo["HEAD"] = remote_refs["HEAD"]
295 index.build_index_from_tree(repo.path, repo.index_path(),
295 index.build_index_from_tree(repo.path, repo.index_path(),
296 repo.object_store, repo["HEAD"].tree)
296 repo.object_store, repo["HEAD"].tree)
297
297
298 # TODO: this is quite complex, check if that can be simplified
298 # TODO: this is quite complex, check if that can be simplified
299 @reraise_safe_exceptions
299 @reraise_safe_exceptions
300 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
300 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
301 repo = self._factory.repo(wire)
301 repo = self._factory.repo(wire)
302 object_store = repo.object_store
302 object_store = repo.object_store
303
303
304 # Create tree and populates it with blobs
304 # Create tree and populates it with blobs
305 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
305 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
306
306
307 for node in updated:
307 for node in updated:
308 # Compute subdirs if needed
308 # Compute subdirs if needed
309 dirpath, nodename = vcspath.split(node['path'])
309 dirpath, nodename = vcspath.split(node['path'])
310 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
310 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
311 parent = commit_tree
311 parent = commit_tree
312 ancestors = [('', parent)]
312 ancestors = [('', parent)]
313
313
314 # Tries to dig for the deepest existing tree
314 # Tries to dig for the deepest existing tree
315 while dirnames:
315 while dirnames:
316 curdir = dirnames.pop(0)
316 curdir = dirnames.pop(0)
317 try:
317 try:
318 dir_id = parent[curdir][1]
318 dir_id = parent[curdir][1]
319 except KeyError:
319 except KeyError:
320 # put curdir back into dirnames and stops
320 # put curdir back into dirnames and stops
321 dirnames.insert(0, curdir)
321 dirnames.insert(0, curdir)
322 break
322 break
323 else:
323 else:
324 # If found, updates parent
324 # If found, updates parent
325 parent = repo[dir_id]
325 parent = repo[dir_id]
326 ancestors.append((curdir, parent))
326 ancestors.append((curdir, parent))
327 # Now parent is deepest existing tree and we need to create
327 # Now parent is deepest existing tree and we need to create
328 # subtrees for dirnames (in reverse order)
328 # subtrees for dirnames (in reverse order)
329 # [this only applies for nodes from added]
329 # [this only applies for nodes from added]
330 new_trees = []
330 new_trees = []
331
331
332 blob = objects.Blob.from_string(node['content'])
332 blob = objects.Blob.from_string(node['content'])
333
333
334 if dirnames:
334 if dirnames:
335 # If there are trees which should be created we need to build
335 # If there are trees which should be created we need to build
336 # them now (in reverse order)
336 # them now (in reverse order)
337 reversed_dirnames = list(reversed(dirnames))
337 reversed_dirnames = list(reversed(dirnames))
338 curtree = objects.Tree()
338 curtree = objects.Tree()
339 curtree[node['node_path']] = node['mode'], blob.id
339 curtree[node['node_path']] = node['mode'], blob.id
340 new_trees.append(curtree)
340 new_trees.append(curtree)
341 for dirname in reversed_dirnames[:-1]:
341 for dirname in reversed_dirnames[:-1]:
342 newtree = objects.Tree()
342 newtree = objects.Tree()
343 newtree[dirname] = (DIR_STAT, curtree.id)
343 newtree[dirname] = (DIR_STAT, curtree.id)
344 new_trees.append(newtree)
344 new_trees.append(newtree)
345 curtree = newtree
345 curtree = newtree
346 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
346 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
347 else:
347 else:
348 parent.add(
348 parent.add(
349 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
349 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
350
350
351 new_trees.append(parent)
351 new_trees.append(parent)
352 # Update ancestors
352 # Update ancestors
353 reversed_ancestors = reversed(
353 reversed_ancestors = reversed(
354 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
354 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
355 for parent, tree, path in reversed_ancestors:
355 for parent, tree, path in reversed_ancestors:
356 parent[path] = (DIR_STAT, tree.id)
356 parent[path] = (DIR_STAT, tree.id)
357 object_store.add_object(tree)
357 object_store.add_object(tree)
358
358
359 object_store.add_object(blob)
359 object_store.add_object(blob)
360 for tree in new_trees:
360 for tree in new_trees:
361 object_store.add_object(tree)
361 object_store.add_object(tree)
362
362
363 for node_path in removed:
363 for node_path in removed:
364 paths = node_path.split('/')
364 paths = node_path.split('/')
365 tree = commit_tree
365 tree = commit_tree
366 trees = [tree]
366 trees = [tree]
367 # Traverse deep into the forest...
367 # Traverse deep into the forest...
368 for path in paths:
368 for path in paths:
369 try:
369 try:
370 obj = repo[tree[path][1]]
370 obj = repo[tree[path][1]]
371 if isinstance(obj, objects.Tree):
371 if isinstance(obj, objects.Tree):
372 trees.append(obj)
372 trees.append(obj)
373 tree = obj
373 tree = obj
374 except KeyError:
374 except KeyError:
375 break
375 break
376 # Cut down the blob and all rotten trees on the way back...
376 # Cut down the blob and all rotten trees on the way back...
377 for path, tree in reversed(zip(paths, trees)):
377 for path, tree in reversed(zip(paths, trees)):
378 del tree[path]
378 del tree[path]
379 if tree:
379 if tree:
380 # This tree still has elements - don't remove it or any
380 # This tree still has elements - don't remove it or any
381 # of it's parents
381 # of it's parents
382 break
382 break
383
383
384 object_store.add_object(commit_tree)
384 object_store.add_object(commit_tree)
385
385
386 # Create commit
386 # Create commit
387 commit = objects.Commit()
387 commit = objects.Commit()
388 commit.tree = commit_tree.id
388 commit.tree = commit_tree.id
389 for k, v in commit_data.iteritems():
389 for k, v in commit_data.iteritems():
390 setattr(commit, k, v)
390 setattr(commit, k, v)
391 object_store.add_object(commit)
391 object_store.add_object(commit)
392
392
393 ref = 'refs/heads/%s' % branch
393 ref = 'refs/heads/%s' % branch
394 repo.refs[ref] = commit.id
394 repo.refs[ref] = commit.id
395
395
396 return commit.id
396 return commit.id
397
397
398 @reraise_safe_exceptions
398 @reraise_safe_exceptions
399 def fetch(self, wire, url, apply_refs=True, refs=None):
399 def fetch(self, wire, url, apply_refs=True, refs=None):
400 if url != 'default' and '://' not in url:
400 if url != 'default' and '://' not in url:
401 client = LocalGitClient(url)
401 client = LocalGitClient(url)
402 else:
402 else:
403 url_obj = url_parser(url)
403 url_obj = url_parser(url)
404 o = self._build_opener(url)
404 o = self._build_opener(url)
405 url, _ = url_obj.authinfo()
405 url, _ = url_obj.authinfo()
406 client = HttpGitClient(base_url=url, opener=o)
406 client = HttpGitClient(base_url=url, opener=o)
407 repo = self._factory.repo(wire)
407 repo = self._factory.repo(wire)
408
408
409 determine_wants = repo.object_store.determine_wants_all
409 determine_wants = repo.object_store.determine_wants_all
410 if refs:
410 if refs:
411 def determine_wants_requested(references):
411 def determine_wants_requested(references):
412 return [references[r] for r in references if r in refs]
412 return [references[r] for r in references if r in refs]
413 determine_wants = determine_wants_requested
413 determine_wants = determine_wants_requested
414
414
415 try:
415 try:
416 remote_refs = client.fetch(
416 remote_refs = client.fetch(
417 path=url, target=repo, determine_wants=determine_wants)
417 path=url, target=repo, determine_wants=determine_wants)
418 except NotGitRepository as e:
418 except NotGitRepository as e:
419 log.warning(
419 log.warning(
420 'Trying to fetch from "%s" failed, not a Git repository.', url)
420 'Trying to fetch from "%s" failed, not a Git repository.', url)
421 # Exception can contain unicode which we convert
421 # Exception can contain unicode which we convert
422 raise exceptions.AbortException(e)(repr(e))
422 raise exceptions.AbortException(e)(repr(e))
423
423
424 # mikhail: client.fetch() returns all the remote refs, but fetches only
424 # mikhail: client.fetch() returns all the remote refs, but fetches only
425 # refs filtered by `determine_wants` function. We need to filter result
425 # refs filtered by `determine_wants` function. We need to filter result
426 # as well
426 # as well
427 if refs:
427 if refs:
428 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
428 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
429
429
430 if apply_refs:
430 if apply_refs:
431 # TODO: johbo: Needs proper test coverage with a git repository
431 # TODO: johbo: Needs proper test coverage with a git repository
432 # that contains a tag object, so that we would end up with
432 # that contains a tag object, so that we would end up with
433 # a peeled ref at this point.
433 # a peeled ref at this point.
434 PEELED_REF_MARKER = '^{}'
434 PEELED_REF_MARKER = '^{}'
435 for k in remote_refs:
435 for k in remote_refs:
436 if k.endswith(PEELED_REF_MARKER):
436 if k.endswith(PEELED_REF_MARKER):
437 log.info("Skipping peeled reference %s", k)
437 log.info("Skipping peeled reference %s", k)
438 continue
438 continue
439 repo[k] = remote_refs[k]
439 repo[k] = remote_refs[k]
440
440
441 if refs:
441 if refs:
442 # mikhail: explicitly set the head to the last ref.
442 # mikhail: explicitly set the head to the last ref.
443 repo['HEAD'] = remote_refs[refs[-1]]
443 repo['HEAD'] = remote_refs[refs[-1]]
444
444
445 # TODO: mikhail: should we return remote_refs here to be
445 # TODO: mikhail: should we return remote_refs here to be
446 # consistent?
446 # consistent?
447 else:
447 else:
448 return remote_refs
448 return remote_refs
449
449
450 @reraise_safe_exceptions
450 @reraise_safe_exceptions
451 def sync_fetch(self, wire, url, refs=None):
452 repo = self._factory.repo(wire)
453 if refs and not isinstance(refs, (list, tuple)):
454 refs = [refs]
455
456 # get remote heads
457 output, __ = self.run_git_command(
458 wire, ['ls-remote', url], fail_on_stderr=False,
459 _copts=['-c', 'core.askpass=""'],
460 extra_env={'GIT_TERMINAL_PROMPT': '0'})
461
462 remote_refs = collections.OrderedDict()
463 fetch_refs = []
464 for ref_line in output.splitlines():
465 sha, ref = ref_line.split('\t')
466 sha = sha.strip()
467 remote_refs[ref] = sha
468
469 if refs and sha in refs:
470 # we filter fetch using our specified refs
471 fetch_refs.append('{}:{}'.format(ref, ref))
472 elif not refs:
473 fetch_refs.append('{}:{}'.format(ref, ref))
474
475 fetch_refs.append('{}:{}'.format(ref, ref))
476
477 _out, _err = self.run_git_command(
478 wire, ['fetch', url, '--'] + fetch_refs, fail_on_stderr=False,
479 _copts=['-c', 'core.askpass=""'],
480 extra_env={'GIT_TERMINAL_PROMPT': '0'})
481
482 return remote_refs
483
484 @reraise_safe_exceptions
451 def sync_push(self, wire, url, refs=None):
485 def sync_push(self, wire, url, refs=None):
452 if self.check_url(url, wire):
486 if not self.check_url(url, wire):
487 return
488
453 repo = self._factory.repo(wire)
489 repo = self._factory.repo(wire)
454 self.run_git_command(
490 self.run_git_command(
455 wire, ['push', url, '--mirror'], fail_on_stderr=False,
491 wire, ['push', url, '--mirror'], fail_on_stderr=False,
456 _copts=['-c', 'core.askpass=""'],
492 _copts=['-c', 'core.askpass=""'],
457 extra_env={'GIT_TERMINAL_PROMPT': '0'})
493 extra_env={'GIT_TERMINAL_PROMPT': '0'})
458
494
459 @reraise_safe_exceptions
495 @reraise_safe_exceptions
460 def get_remote_refs(self, wire, url):
496 def get_remote_refs(self, wire, url):
461 repo = Repo(url)
497 repo = Repo(url)
462 return repo.get_refs()
498 return repo.get_refs()
463
499
464 @reraise_safe_exceptions
500 @reraise_safe_exceptions
465 def get_description(self, wire):
501 def get_description(self, wire):
466 repo = self._factory.repo(wire)
502 repo = self._factory.repo(wire)
467 return repo.get_description()
503 return repo.get_description()
468
504
469 @reraise_safe_exceptions
505 @reraise_safe_exceptions
470 def get_file_history(self, wire, file_path, commit_id, limit):
506 def get_file_history(self, wire, file_path, commit_id, limit):
471 repo = self._factory.repo(wire)
507 repo = self._factory.repo(wire)
472 include = [commit_id]
508 include = [commit_id]
473 paths = [file_path]
509 paths = [file_path]
474
510
475 walker = repo.get_walker(include, paths=paths, max_entries=limit)
511 walker = repo.get_walker(include, paths=paths, max_entries=limit)
476 return [x.commit.id for x in walker]
512 return [x.commit.id for x in walker]
477
513
478 @reraise_safe_exceptions
514 @reraise_safe_exceptions
479 def get_missing_revs(self, wire, rev1, rev2, path2):
515 def get_missing_revs(self, wire, rev1, rev2, path2):
480 repo = self._factory.repo(wire)
516 repo = self._factory.repo(wire)
481 LocalGitClient(thin_packs=False).fetch(path2, repo)
517 LocalGitClient(thin_packs=False).fetch(path2, repo)
482
518
483 wire_remote = wire.copy()
519 wire_remote = wire.copy()
484 wire_remote['path'] = path2
520 wire_remote['path'] = path2
485 repo_remote = self._factory.repo(wire_remote)
521 repo_remote = self._factory.repo(wire_remote)
486 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
522 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
487
523
488 revs = [
524 revs = [
489 x.commit.id
525 x.commit.id
490 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
526 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
491 return revs
527 return revs
492
528
493 @reraise_safe_exceptions
529 @reraise_safe_exceptions
494 def get_object(self, wire, sha):
530 def get_object(self, wire, sha):
495 repo = self._factory.repo(wire)
531 repo = self._factory.repo(wire)
496 obj = repo.get_object(sha)
532 obj = repo.get_object(sha)
497 commit_id = obj.id
533 commit_id = obj.id
498
534
499 if isinstance(obj, Tag):
535 if isinstance(obj, Tag):
500 commit_id = obj.object[1]
536 commit_id = obj.object[1]
501
537
502 return {
538 return {
503 'id': obj.id,
539 'id': obj.id,
504 'type': obj.type_name,
540 'type': obj.type_name,
505 'commit_id': commit_id
541 'commit_id': commit_id
506 }
542 }
507
543
508 @reraise_safe_exceptions
544 @reraise_safe_exceptions
509 def get_object_attrs(self, wire, sha, *attrs):
545 def get_object_attrs(self, wire, sha, *attrs):
510 repo = self._factory.repo(wire)
546 repo = self._factory.repo(wire)
511 obj = repo.get_object(sha)
547 obj = repo.get_object(sha)
512 return list(getattr(obj, a) for a in attrs)
548 return list(getattr(obj, a) for a in attrs)
513
549
514 @reraise_safe_exceptions
550 @reraise_safe_exceptions
515 def get_refs(self, wire):
551 def get_refs(self, wire):
516 repo = self._factory.repo(wire)
552 repo = self._factory.repo(wire)
517 result = {}
553 result = {}
518 for ref, sha in repo.refs.as_dict().items():
554 for ref, sha in repo.refs.as_dict().items():
519 peeled_sha = repo.get_peeled(ref)
555 peeled_sha = repo.get_peeled(ref)
520 result[ref] = peeled_sha
556 result[ref] = peeled_sha
521 return result
557 return result
522
558
523 @reraise_safe_exceptions
559 @reraise_safe_exceptions
524 def get_refs_path(self, wire):
560 def get_refs_path(self, wire):
525 repo = self._factory.repo(wire)
561 repo = self._factory.repo(wire)
526 return repo.refs.path
562 return repo.refs.path
527
563
528 @reraise_safe_exceptions
564 @reraise_safe_exceptions
529 def head(self, wire, show_exc=True):
565 def head(self, wire, show_exc=True):
530 repo = self._factory.repo(wire)
566 repo = self._factory.repo(wire)
531 try:
567 try:
532 return repo.head()
568 return repo.head()
533 except Exception:
569 except Exception:
534 if show_exc:
570 if show_exc:
535 raise
571 raise
536
572
537 @reraise_safe_exceptions
573 @reraise_safe_exceptions
538 def init(self, wire):
574 def init(self, wire):
539 repo_path = str_to_dulwich(wire['path'])
575 repo_path = str_to_dulwich(wire['path'])
540 self.repo = Repo.init(repo_path)
576 self.repo = Repo.init(repo_path)
541
577
542 @reraise_safe_exceptions
578 @reraise_safe_exceptions
543 def init_bare(self, wire):
579 def init_bare(self, wire):
544 repo_path = str_to_dulwich(wire['path'])
580 repo_path = str_to_dulwich(wire['path'])
545 self.repo = Repo.init_bare(repo_path)
581 self.repo = Repo.init_bare(repo_path)
546
582
547 @reraise_safe_exceptions
583 @reraise_safe_exceptions
548 def revision(self, wire, rev):
584 def revision(self, wire, rev):
549 repo = self._factory.repo(wire)
585 repo = self._factory.repo(wire)
550 obj = repo[rev]
586 obj = repo[rev]
551 obj_data = {
587 obj_data = {
552 'id': obj.id,
588 'id': obj.id,
553 }
589 }
554 try:
590 try:
555 obj_data['tree'] = obj.tree
591 obj_data['tree'] = obj.tree
556 except AttributeError:
592 except AttributeError:
557 pass
593 pass
558 return obj_data
594 return obj_data
559
595
560 @reraise_safe_exceptions
596 @reraise_safe_exceptions
561 def commit_attribute(self, wire, rev, attr):
597 def commit_attribute(self, wire, rev, attr):
562 repo = self._factory.repo(wire)
598 repo = self._factory.repo(wire)
563 obj = repo[rev]
599 obj = repo[rev]
564 return getattr(obj, attr)
600 return getattr(obj, attr)
565
601
566 @reraise_safe_exceptions
602 @reraise_safe_exceptions
567 def set_refs(self, wire, key, value):
603 def set_refs(self, wire, key, value):
568 repo = self._factory.repo(wire)
604 repo = self._factory.repo(wire)
569 repo.refs[key] = value
605 repo.refs[key] = value
570
606
571 @reraise_safe_exceptions
607 @reraise_safe_exceptions
572 def remove_ref(self, wire, key):
608 def remove_ref(self, wire, key):
573 repo = self._factory.repo(wire)
609 repo = self._factory.repo(wire)
574 del repo.refs[key]
610 del repo.refs[key]
575
611
576 @reraise_safe_exceptions
612 @reraise_safe_exceptions
577 def tree_changes(self, wire, source_id, target_id):
613 def tree_changes(self, wire, source_id, target_id):
578 repo = self._factory.repo(wire)
614 repo = self._factory.repo(wire)
579 source = repo[source_id].tree if source_id else None
615 source = repo[source_id].tree if source_id else None
580 target = repo[target_id].tree
616 target = repo[target_id].tree
581 result = repo.object_store.tree_changes(source, target)
617 result = repo.object_store.tree_changes(source, target)
582 return list(result)
618 return list(result)
583
619
584 @reraise_safe_exceptions
620 @reraise_safe_exceptions
585 def tree_items(self, wire, tree_id):
621 def tree_items(self, wire, tree_id):
586 repo = self._factory.repo(wire)
622 repo = self._factory.repo(wire)
587 tree = repo[tree_id]
623 tree = repo[tree_id]
588
624
589 result = []
625 result = []
590 for item in tree.iteritems():
626 for item in tree.iteritems():
591 item_sha = item.sha
627 item_sha = item.sha
592 item_mode = item.mode
628 item_mode = item.mode
593
629
594 if FILE_MODE(item_mode) == GIT_LINK:
630 if FILE_MODE(item_mode) == GIT_LINK:
595 item_type = "link"
631 item_type = "link"
596 else:
632 else:
597 item_type = repo[item_sha].type_name
633 item_type = repo[item_sha].type_name
598
634
599 result.append((item.path, item_mode, item_sha, item_type))
635 result.append((item.path, item_mode, item_sha, item_type))
600 return result
636 return result
601
637
602 @reraise_safe_exceptions
638 @reraise_safe_exceptions
603 def update_server_info(self, wire):
639 def update_server_info(self, wire):
604 repo = self._factory.repo(wire)
640 repo = self._factory.repo(wire)
605 update_server_info(repo)
641 update_server_info(repo)
606
642
607 @reraise_safe_exceptions
643 @reraise_safe_exceptions
608 def discover_git_version(self):
644 def discover_git_version(self):
609 stdout, _ = self.run_git_command(
645 stdout, _ = self.run_git_command(
610 {}, ['--version'], _bare=True, _safe=True)
646 {}, ['--version'], _bare=True, _safe=True)
611 prefix = 'git version'
647 prefix = 'git version'
612 if stdout.startswith(prefix):
648 if stdout.startswith(prefix):
613 stdout = stdout[len(prefix):]
649 stdout = stdout[len(prefix):]
614 return stdout.strip()
650 return stdout.strip()
615
651
616 @reraise_safe_exceptions
652 @reraise_safe_exceptions
617 def run_git_command(self, wire, cmd, **opts):
653 def run_git_command(self, wire, cmd, **opts):
618 path = wire.get('path', None)
654 path = wire.get('path', None)
619
655
620 if path and os.path.isdir(path):
656 if path and os.path.isdir(path):
621 opts['cwd'] = path
657 opts['cwd'] = path
622
658
623 if '_bare' in opts:
659 if '_bare' in opts:
624 _copts = []
660 _copts = []
625 del opts['_bare']
661 del opts['_bare']
626 else:
662 else:
627 _copts = ['-c', 'core.quotepath=false', ]
663 _copts = ['-c', 'core.quotepath=false', ]
628 safe_call = False
664 safe_call = False
629 if '_safe' in opts:
665 if '_safe' in opts:
630 # no exc on failure
666 # no exc on failure
631 del opts['_safe']
667 del opts['_safe']
632 safe_call = True
668 safe_call = True
633
669
634 if '_copts' in opts:
670 if '_copts' in opts:
635 _copts.extend(opts['_copts'] or [])
671 _copts.extend(opts['_copts'] or [])
636 del opts['_copts']
672 del opts['_copts']
637
673
638 gitenv = os.environ.copy()
674 gitenv = os.environ.copy()
639 gitenv.update(opts.pop('extra_env', {}))
675 gitenv.update(opts.pop('extra_env', {}))
640 # need to clean fix GIT_DIR !
676 # need to clean fix GIT_DIR !
641 if 'GIT_DIR' in gitenv:
677 if 'GIT_DIR' in gitenv:
642 del gitenv['GIT_DIR']
678 del gitenv['GIT_DIR']
643 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
679 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
644 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
680 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
645
681
646 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
682 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
647
683
648 try:
684 try:
649 _opts = {'env': gitenv, 'shell': False}
685 _opts = {'env': gitenv, 'shell': False}
650 _opts.update(opts)
686 _opts.update(opts)
651 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
687 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
652
688
653 return ''.join(p), ''.join(p.error)
689 return ''.join(p), ''.join(p.error)
654 except (EnvironmentError, OSError) as err:
690 except (EnvironmentError, OSError) as err:
655 cmd = ' '.join(cmd) # human friendly CMD
691 cmd = ' '.join(cmd) # human friendly CMD
656 tb_err = ("Couldn't run git command (%s).\n"
692 tb_err = ("Couldn't run git command (%s).\n"
657 "Original error was:%s\n" % (cmd, err))
693 "Original error was:%s\n" % (cmd, err))
658 log.exception(tb_err)
694 log.exception(tb_err)
659 if safe_call:
695 if safe_call:
660 return '', err
696 return '', err
661 else:
697 else:
662 raise exceptions.VcsException()(tb_err)
698 raise exceptions.VcsException()(tb_err)
663
699
664 @reraise_safe_exceptions
700 @reraise_safe_exceptions
665 def install_hooks(self, wire, force=False):
701 def install_hooks(self, wire, force=False):
666 from vcsserver.hook_utils import install_git_hooks
702 from vcsserver.hook_utils import install_git_hooks
667 repo = self._factory.repo(wire)
703 repo = self._factory.repo(wire)
668 return install_git_hooks(repo.path, repo.bare, force_create=force)
704 return install_git_hooks(repo.path, repo.bare, force_create=force)
669
705
670
706
671 def str_to_dulwich(value):
707 def str_to_dulwich(value):
672 """
708 """
673 Dulwich 0.10.1a requires `unicode` objects to be passed in.
709 Dulwich 0.10.1a requires `unicode` objects to be passed in.
674 """
710 """
675 return value.decode(settings.WIRE_ENCODING)
711 return value.decode(settings.WIRE_ENCODING)
@@ -1,793 +1,795 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import io
18 import io
19 import logging
19 import logging
20 import stat
20 import stat
21 import urllib
21 import urllib
22 import urllib2
22 import urllib2
23
23
24 from hgext import largefiles, rebase
24 from hgext import largefiles, rebase
25 from hgext.strip import strip as hgext_strip
25 from hgext.strip import strip as hgext_strip
26 from mercurial import commands
26 from mercurial import commands
27 from mercurial import unionrepo
27 from mercurial import unionrepo
28 from mercurial import verify
28 from mercurial import verify
29
29
30 from vcsserver import exceptions
30 from vcsserver import exceptions
31 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
31 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
32 from vcsserver.hgcompat import (
32 from vcsserver.hgcompat import (
33 archival, bin, clone, config as hgconfig, diffopts, hex,
33 archival, bin, clone, config as hgconfig, diffopts, hex,
34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
35 makepeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
35 makepeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
36 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
36 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
37 RepoLookupError, InterventionRequired, RequirementError)
37 RepoLookupError, InterventionRequired, RequirementError)
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 def make_ui_from_config(repo_config):
42 def make_ui_from_config(repo_config):
43 baseui = ui.ui()
43 baseui = ui.ui()
44
44
45 # clean the baseui object
45 # clean the baseui object
46 baseui._ocfg = hgconfig.config()
46 baseui._ocfg = hgconfig.config()
47 baseui._ucfg = hgconfig.config()
47 baseui._ucfg = hgconfig.config()
48 baseui._tcfg = hgconfig.config()
48 baseui._tcfg = hgconfig.config()
49
49
50 for section, option, value in repo_config:
50 for section, option, value in repo_config:
51 baseui.setconfig(section, option, value)
51 baseui.setconfig(section, option, value)
52
52
53 # make our hgweb quiet so it doesn't print output
53 # make our hgweb quiet so it doesn't print output
54 baseui.setconfig('ui', 'quiet', 'true')
54 baseui.setconfig('ui', 'quiet', 'true')
55
55
56 baseui.setconfig('ui', 'paginate', 'never')
56 baseui.setconfig('ui', 'paginate', 'never')
57 # force mercurial to only use 1 thread, otherwise it may try to set a
57 # force mercurial to only use 1 thread, otherwise it may try to set a
58 # signal in a non-main thread, thus generating a ValueError.
58 # signal in a non-main thread, thus generating a ValueError.
59 baseui.setconfig('worker', 'numcpus', 1)
59 baseui.setconfig('worker', 'numcpus', 1)
60
60
61 # If there is no config for the largefiles extension, we explicitly disable
61 # If there is no config for the largefiles extension, we explicitly disable
62 # it here. This overrides settings from repositories hgrc file. Recent
62 # it here. This overrides settings from repositories hgrc file. Recent
63 # mercurial versions enable largefiles in hgrc on clone from largefile
63 # mercurial versions enable largefiles in hgrc on clone from largefile
64 # repo.
64 # repo.
65 if not baseui.hasconfig('extensions', 'largefiles'):
65 if not baseui.hasconfig('extensions', 'largefiles'):
66 log.debug('Explicitly disable largefiles extension for repo.')
66 log.debug('Explicitly disable largefiles extension for repo.')
67 baseui.setconfig('extensions', 'largefiles', '!')
67 baseui.setconfig('extensions', 'largefiles', '!')
68
68
69 return baseui
69 return baseui
70
70
71
71
72 def reraise_safe_exceptions(func):
72 def reraise_safe_exceptions(func):
73 """Decorator for converting mercurial exceptions to something neutral."""
73 """Decorator for converting mercurial exceptions to something neutral."""
74 def wrapper(*args, **kwargs):
74 def wrapper(*args, **kwargs):
75 try:
75 try:
76 return func(*args, **kwargs)
76 return func(*args, **kwargs)
77 except (Abort, InterventionRequired) as e:
77 except (Abort, InterventionRequired) as e:
78 raise_from_original(exceptions.AbortException(e))
78 raise_from_original(exceptions.AbortException(e))
79 except RepoLookupError as e:
79 except RepoLookupError as e:
80 raise_from_original(exceptions.LookupException(e))
80 raise_from_original(exceptions.LookupException(e))
81 except RequirementError as e:
81 except RequirementError as e:
82 raise_from_original(exceptions.RequirementException(e))
82 raise_from_original(exceptions.RequirementException(e))
83 except RepoError as e:
83 except RepoError as e:
84 raise_from_original(exceptions.VcsException(e))
84 raise_from_original(exceptions.VcsException(e))
85 except LookupError as e:
85 except LookupError as e:
86 raise_from_original(exceptions.LookupException(e))
86 raise_from_original(exceptions.LookupException(e))
87 except Exception as e:
87 except Exception as e:
88 if not hasattr(e, '_vcs_kind'):
88 if not hasattr(e, '_vcs_kind'):
89 log.exception("Unhandled exception in hg remote call")
89 log.exception("Unhandled exception in hg remote call")
90 raise_from_original(exceptions.UnhandledException(e))
90 raise_from_original(exceptions.UnhandledException(e))
91
91
92 raise
92 raise
93 return wrapper
93 return wrapper
94
94
95
95
96 class MercurialFactory(RepoFactory):
96 class MercurialFactory(RepoFactory):
97 repo_type = 'hg'
97 repo_type = 'hg'
98
98
99 def _create_config(self, config, hooks=True):
99 def _create_config(self, config, hooks=True):
100 if not hooks:
100 if not hooks:
101 hooks_to_clean = frozenset((
101 hooks_to_clean = frozenset((
102 'changegroup.repo_size', 'preoutgoing.pre_pull',
102 'changegroup.repo_size', 'preoutgoing.pre_pull',
103 'outgoing.pull_logger', 'prechangegroup.pre_push'))
103 'outgoing.pull_logger', 'prechangegroup.pre_push'))
104 new_config = []
104 new_config = []
105 for section, option, value in config:
105 for section, option, value in config:
106 if section == 'hooks' and option in hooks_to_clean:
106 if section == 'hooks' and option in hooks_to_clean:
107 continue
107 continue
108 new_config.append((section, option, value))
108 new_config.append((section, option, value))
109 config = new_config
109 config = new_config
110
110
111 baseui = make_ui_from_config(config)
111 baseui = make_ui_from_config(config)
112 return baseui
112 return baseui
113
113
114 def _create_repo(self, wire, create):
114 def _create_repo(self, wire, create):
115 baseui = self._create_config(wire["config"])
115 baseui = self._create_config(wire["config"])
116 return localrepository(baseui, wire["path"], create)
116 return localrepository(baseui, wire["path"], create)
117
117
118
118
119 class HgRemote(object):
119 class HgRemote(object):
120
120
121 def __init__(self, factory):
121 def __init__(self, factory):
122 self._factory = factory
122 self._factory = factory
123
123
124 self._bulk_methods = {
124 self._bulk_methods = {
125 "affected_files": self.ctx_files,
125 "affected_files": self.ctx_files,
126 "author": self.ctx_user,
126 "author": self.ctx_user,
127 "branch": self.ctx_branch,
127 "branch": self.ctx_branch,
128 "children": self.ctx_children,
128 "children": self.ctx_children,
129 "date": self.ctx_date,
129 "date": self.ctx_date,
130 "message": self.ctx_description,
130 "message": self.ctx_description,
131 "parents": self.ctx_parents,
131 "parents": self.ctx_parents,
132 "status": self.ctx_status,
132 "status": self.ctx_status,
133 "obsolete": self.ctx_obsolete,
133 "obsolete": self.ctx_obsolete,
134 "phase": self.ctx_phase,
134 "phase": self.ctx_phase,
135 "hidden": self.ctx_hidden,
135 "hidden": self.ctx_hidden,
136 "_file_paths": self.ctx_list,
136 "_file_paths": self.ctx_list,
137 }
137 }
138
138
139 @reraise_safe_exceptions
139 @reraise_safe_exceptions
140 def discover_hg_version(self):
140 def discover_hg_version(self):
141 from mercurial import util
141 from mercurial import util
142 return util.version()
142 return util.version()
143
143
144 @reraise_safe_exceptions
144 @reraise_safe_exceptions
145 def archive_repo(self, archive_path, mtime, file_info, kind):
145 def archive_repo(self, archive_path, mtime, file_info, kind):
146 if kind == "tgz":
146 if kind == "tgz":
147 archiver = archival.tarit(archive_path, mtime, "gz")
147 archiver = archival.tarit(archive_path, mtime, "gz")
148 elif kind == "tbz2":
148 elif kind == "tbz2":
149 archiver = archival.tarit(archive_path, mtime, "bz2")
149 archiver = archival.tarit(archive_path, mtime, "bz2")
150 elif kind == 'zip':
150 elif kind == 'zip':
151 archiver = archival.zipit(archive_path, mtime)
151 archiver = archival.zipit(archive_path, mtime)
152 else:
152 else:
153 raise exceptions.ArchiveException()(
153 raise exceptions.ArchiveException()(
154 'Remote does not support: "%s".' % kind)
154 'Remote does not support: "%s".' % kind)
155
155
156 for f_path, f_mode, f_is_link, f_content in file_info:
156 for f_path, f_mode, f_is_link, f_content in file_info:
157 archiver.addfile(f_path, f_mode, f_is_link, f_content)
157 archiver.addfile(f_path, f_mode, f_is_link, f_content)
158 archiver.done()
158 archiver.done()
159
159
160 @reraise_safe_exceptions
160 @reraise_safe_exceptions
161 def bookmarks(self, wire):
161 def bookmarks(self, wire):
162 repo = self._factory.repo(wire)
162 repo = self._factory.repo(wire)
163 return dict(repo._bookmarks)
163 return dict(repo._bookmarks)
164
164
165 @reraise_safe_exceptions
165 @reraise_safe_exceptions
166 def branches(self, wire, normal, closed):
166 def branches(self, wire, normal, closed):
167 repo = self._factory.repo(wire)
167 repo = self._factory.repo(wire)
168 iter_branches = repo.branchmap().iterbranches()
168 iter_branches = repo.branchmap().iterbranches()
169 bt = {}
169 bt = {}
170 for branch_name, _heads, tip, is_closed in iter_branches:
170 for branch_name, _heads, tip, is_closed in iter_branches:
171 if normal and not is_closed:
171 if normal and not is_closed:
172 bt[branch_name] = tip
172 bt[branch_name] = tip
173 if closed and is_closed:
173 if closed and is_closed:
174 bt[branch_name] = tip
174 bt[branch_name] = tip
175
175
176 return bt
176 return bt
177
177
178 @reraise_safe_exceptions
178 @reraise_safe_exceptions
179 def bulk_request(self, wire, rev, pre_load):
179 def bulk_request(self, wire, rev, pre_load):
180 result = {}
180 result = {}
181 for attr in pre_load:
181 for attr in pre_load:
182 try:
182 try:
183 method = self._bulk_methods[attr]
183 method = self._bulk_methods[attr]
184 result[attr] = method(wire, rev)
184 result[attr] = method(wire, rev)
185 except KeyError as e:
185 except KeyError as e:
186 raise exceptions.VcsException(e)(
186 raise exceptions.VcsException(e)(
187 'Unknown bulk attribute: "%s"' % attr)
187 'Unknown bulk attribute: "%s"' % attr)
188 return result
188 return result
189
189
190 @reraise_safe_exceptions
190 @reraise_safe_exceptions
191 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
191 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
192 baseui = self._factory._create_config(wire["config"], hooks=hooks)
192 baseui = self._factory._create_config(wire["config"], hooks=hooks)
193 clone(baseui, source, dest, noupdate=not update_after_clone)
193 clone(baseui, source, dest, noupdate=not update_after_clone)
194
194
195 @reraise_safe_exceptions
195 @reraise_safe_exceptions
196 def commitctx(
196 def commitctx(
197 self, wire, message, parents, commit_time, commit_timezone,
197 self, wire, message, parents, commit_time, commit_timezone,
198 user, files, extra, removed, updated):
198 user, files, extra, removed, updated):
199
199
200 def _filectxfn(_repo, memctx, path):
200 def _filectxfn(_repo, memctx, path):
201 """
201 """
202 Marks given path as added/changed/removed in a given _repo. This is
202 Marks given path as added/changed/removed in a given _repo. This is
203 for internal mercurial commit function.
203 for internal mercurial commit function.
204 """
204 """
205
205
206 # check if this path is removed
206 # check if this path is removed
207 if path in removed:
207 if path in removed:
208 # returning None is a way to mark node for removal
208 # returning None is a way to mark node for removal
209 return None
209 return None
210
210
211 # check if this path is added
211 # check if this path is added
212 for node in updated:
212 for node in updated:
213 if node['path'] == path:
213 if node['path'] == path:
214 return memfilectx(
214 return memfilectx(
215 _repo,
215 _repo,
216 changectx=memctx,
216 changectx=memctx,
217 path=node['path'],
217 path=node['path'],
218 data=node['content'],
218 data=node['content'],
219 islink=False,
219 islink=False,
220 isexec=bool(node['mode'] & stat.S_IXUSR),
220 isexec=bool(node['mode'] & stat.S_IXUSR),
221 copied=False)
221 copied=False)
222
222
223 raise exceptions.AbortException()(
223 raise exceptions.AbortException()(
224 "Given path haven't been marked as added, "
224 "Given path haven't been marked as added, "
225 "changed or removed (%s)" % path)
225 "changed or removed (%s)" % path)
226
226
227 repo = self._factory.repo(wire)
227 repo = self._factory.repo(wire)
228
228
229 commit_ctx = memctx(
229 commit_ctx = memctx(
230 repo=repo,
230 repo=repo,
231 parents=parents,
231 parents=parents,
232 text=message,
232 text=message,
233 files=files,
233 files=files,
234 filectxfn=_filectxfn,
234 filectxfn=_filectxfn,
235 user=user,
235 user=user,
236 date=(commit_time, commit_timezone),
236 date=(commit_time, commit_timezone),
237 extra=extra)
237 extra=extra)
238
238
239 n = repo.commitctx(commit_ctx)
239 n = repo.commitctx(commit_ctx)
240 new_id = hex(n)
240 new_id = hex(n)
241
241
242 return new_id
242 return new_id
243
243
244 @reraise_safe_exceptions
244 @reraise_safe_exceptions
245 def ctx_branch(self, wire, revision):
245 def ctx_branch(self, wire, revision):
246 repo = self._factory.repo(wire)
246 repo = self._factory.repo(wire)
247 ctx = repo[revision]
247 ctx = repo[revision]
248 return ctx.branch()
248 return ctx.branch()
249
249
250 @reraise_safe_exceptions
250 @reraise_safe_exceptions
251 def ctx_children(self, wire, revision):
251 def ctx_children(self, wire, revision):
252 repo = self._factory.repo(wire)
252 repo = self._factory.repo(wire)
253 ctx = repo[revision]
253 ctx = repo[revision]
254 return [child.rev() for child in ctx.children()]
254 return [child.rev() for child in ctx.children()]
255
255
256 @reraise_safe_exceptions
256 @reraise_safe_exceptions
257 def ctx_date(self, wire, revision):
257 def ctx_date(self, wire, revision):
258 repo = self._factory.repo(wire)
258 repo = self._factory.repo(wire)
259 ctx = repo[revision]
259 ctx = repo[revision]
260 return ctx.date()
260 return ctx.date()
261
261
262 @reraise_safe_exceptions
262 @reraise_safe_exceptions
263 def ctx_description(self, wire, revision):
263 def ctx_description(self, wire, revision):
264 repo = self._factory.repo(wire)
264 repo = self._factory.repo(wire)
265 ctx = repo[revision]
265 ctx = repo[revision]
266 return ctx.description()
266 return ctx.description()
267
267
268 @reraise_safe_exceptions
268 @reraise_safe_exceptions
269 def ctx_diff(
269 def ctx_diff(
270 self, wire, revision, git=True, ignore_whitespace=True, context=3):
270 self, wire, revision, git=True, ignore_whitespace=True, context=3):
271 repo = self._factory.repo(wire)
271 repo = self._factory.repo(wire)
272 ctx = repo[revision]
272 ctx = repo[revision]
273 result = ctx.diff(
273 result = ctx.diff(
274 git=git, ignore_whitespace=ignore_whitespace, context=context)
274 git=git, ignore_whitespace=ignore_whitespace, context=context)
275 return list(result)
275 return list(result)
276
276
277 @reraise_safe_exceptions
277 @reraise_safe_exceptions
278 def ctx_files(self, wire, revision):
278 def ctx_files(self, wire, revision):
279 repo = self._factory.repo(wire)
279 repo = self._factory.repo(wire)
280 ctx = repo[revision]
280 ctx = repo[revision]
281 return ctx.files()
281 return ctx.files()
282
282
283 @reraise_safe_exceptions
283 @reraise_safe_exceptions
284 def ctx_list(self, path, revision):
284 def ctx_list(self, path, revision):
285 repo = self._factory.repo(path)
285 repo = self._factory.repo(path)
286 ctx = repo[revision]
286 ctx = repo[revision]
287 return list(ctx)
287 return list(ctx)
288
288
289 @reraise_safe_exceptions
289 @reraise_safe_exceptions
290 def ctx_parents(self, wire, revision):
290 def ctx_parents(self, wire, revision):
291 repo = self._factory.repo(wire)
291 repo = self._factory.repo(wire)
292 ctx = repo[revision]
292 ctx = repo[revision]
293 return [parent.rev() for parent in ctx.parents()]
293 return [parent.rev() for parent in ctx.parents()]
294
294
295 @reraise_safe_exceptions
295 @reraise_safe_exceptions
296 def ctx_phase(self, wire, revision):
296 def ctx_phase(self, wire, revision):
297 repo = self._factory.repo(wire)
297 repo = self._factory.repo(wire)
298 ctx = repo[revision]
298 ctx = repo[revision]
299 # public=0, draft=1, secret=3
299 # public=0, draft=1, secret=3
300 return ctx.phase()
300 return ctx.phase()
301
301
302 @reraise_safe_exceptions
302 @reraise_safe_exceptions
303 def ctx_obsolete(self, wire, revision):
303 def ctx_obsolete(self, wire, revision):
304 repo = self._factory.repo(wire)
304 repo = self._factory.repo(wire)
305 ctx = repo[revision]
305 ctx = repo[revision]
306 return ctx.obsolete()
306 return ctx.obsolete()
307
307
308 @reraise_safe_exceptions
308 @reraise_safe_exceptions
309 def ctx_hidden(self, wire, revision):
309 def ctx_hidden(self, wire, revision):
310 repo = self._factory.repo(wire)
310 repo = self._factory.repo(wire)
311 ctx = repo[revision]
311 ctx = repo[revision]
312 return ctx.hidden()
312 return ctx.hidden()
313
313
314 @reraise_safe_exceptions
314 @reraise_safe_exceptions
315 def ctx_substate(self, wire, revision):
315 def ctx_substate(self, wire, revision):
316 repo = self._factory.repo(wire)
316 repo = self._factory.repo(wire)
317 ctx = repo[revision]
317 ctx = repo[revision]
318 return ctx.substate
318 return ctx.substate
319
319
320 @reraise_safe_exceptions
320 @reraise_safe_exceptions
321 def ctx_status(self, wire, revision):
321 def ctx_status(self, wire, revision):
322 repo = self._factory.repo(wire)
322 repo = self._factory.repo(wire)
323 ctx = repo[revision]
323 ctx = repo[revision]
324 status = repo[ctx.p1().node()].status(other=ctx.node())
324 status = repo[ctx.p1().node()].status(other=ctx.node())
325 # object of status (odd, custom named tuple in mercurial) is not
325 # object of status (odd, custom named tuple in mercurial) is not
326 # correctly serializable, we make it a list, as the underling
326 # correctly serializable, we make it a list, as the underling
327 # API expects this to be a list
327 # API expects this to be a list
328 return list(status)
328 return list(status)
329
329
330 @reraise_safe_exceptions
330 @reraise_safe_exceptions
331 def ctx_user(self, wire, revision):
331 def ctx_user(self, wire, revision):
332 repo = self._factory.repo(wire)
332 repo = self._factory.repo(wire)
333 ctx = repo[revision]
333 ctx = repo[revision]
334 return ctx.user()
334 return ctx.user()
335
335
336 @reraise_safe_exceptions
336 @reraise_safe_exceptions
337 def check_url(self, url, config):
337 def check_url(self, url, config):
338 _proto = None
338 _proto = None
339 if '+' in url[:url.find('://')]:
339 if '+' in url[:url.find('://')]:
340 _proto = url[0:url.find('+')]
340 _proto = url[0:url.find('+')]
341 url = url[url.find('+') + 1:]
341 url = url[url.find('+') + 1:]
342 handlers = []
342 handlers = []
343 url_obj = url_parser(url)
343 url_obj = url_parser(url)
344 test_uri, authinfo = url_obj.authinfo()
344 test_uri, authinfo = url_obj.authinfo()
345 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
345 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
346 url_obj.query = obfuscate_qs(url_obj.query)
346 url_obj.query = obfuscate_qs(url_obj.query)
347
347
348 cleaned_uri = str(url_obj)
348 cleaned_uri = str(url_obj)
349 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
349 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
350
350
351 if authinfo:
351 if authinfo:
352 # create a password manager
352 # create a password manager
353 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
353 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
354 passmgr.add_password(*authinfo)
354 passmgr.add_password(*authinfo)
355
355
356 handlers.extend((httpbasicauthhandler(passmgr),
356 handlers.extend((httpbasicauthhandler(passmgr),
357 httpdigestauthhandler(passmgr)))
357 httpdigestauthhandler(passmgr)))
358
358
359 o = urllib2.build_opener(*handlers)
359 o = urllib2.build_opener(*handlers)
360 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
360 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
361 ('Accept', 'application/mercurial-0.1')]
361 ('Accept', 'application/mercurial-0.1')]
362
362
363 q = {"cmd": 'between'}
363 q = {"cmd": 'between'}
364 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
364 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
365 qs = '?%s' % urllib.urlencode(q)
365 qs = '?%s' % urllib.urlencode(q)
366 cu = "%s%s" % (test_uri, qs)
366 cu = "%s%s" % (test_uri, qs)
367 req = urllib2.Request(cu, None, {})
367 req = urllib2.Request(cu, None, {})
368
368
369 try:
369 try:
370 log.debug("Trying to open URL %s", cleaned_uri)
370 log.debug("Trying to open URL %s", cleaned_uri)
371 resp = o.open(req)
371 resp = o.open(req)
372 if resp.code != 200:
372 if resp.code != 200:
373 raise exceptions.URLError()('Return Code is not 200')
373 raise exceptions.URLError()('Return Code is not 200')
374 except Exception as e:
374 except Exception as e:
375 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
375 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
376 # means it cannot be cloned
376 # means it cannot be cloned
377 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
377 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
378
378
379 # now check if it's a proper hg repo, but don't do it for svn
379 # now check if it's a proper hg repo, but don't do it for svn
380 try:
380 try:
381 if _proto == 'svn':
381 if _proto == 'svn':
382 pass
382 pass
383 else:
383 else:
384 # check for pure hg repos
384 # check for pure hg repos
385 log.debug(
385 log.debug(
386 "Verifying if URL is a Mercurial repository: %s",
386 "Verifying if URL is a Mercurial repository: %s",
387 cleaned_uri)
387 cleaned_uri)
388 ui = make_ui_from_config(config)
388 ui = make_ui_from_config(config)
389 peer_checker = makepeer(ui, url)
389 peer_checker = makepeer(ui, url)
390 peer_checker.lookup('tip')
390 peer_checker.lookup('tip')
391 except Exception as e:
391 except Exception as e:
392 log.warning("URL is not a valid Mercurial repository: %s",
392 log.warning("URL is not a valid Mercurial repository: %s",
393 cleaned_uri)
393 cleaned_uri)
394 raise exceptions.URLError(e)(
394 raise exceptions.URLError(e)(
395 "url [%s] does not look like an hg repo org_exc: %s"
395 "url [%s] does not look like an hg repo org_exc: %s"
396 % (cleaned_uri, e))
396 % (cleaned_uri, e))
397
397
398 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
398 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
399 return True
399 return True
400
400
401 @reraise_safe_exceptions
401 @reraise_safe_exceptions
402 def diff(
402 def diff(
403 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
403 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
404 context):
404 context):
405 repo = self._factory.repo(wire)
405 repo = self._factory.repo(wire)
406
406
407 if file_filter:
407 if file_filter:
408 match_filter = match(file_filter[0], '', [file_filter[1]])
408 match_filter = match(file_filter[0], '', [file_filter[1]])
409 else:
409 else:
410 match_filter = file_filter
410 match_filter = file_filter
411 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
411 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
412
412
413 try:
413 try:
414 return "".join(patch.diff(
414 return "".join(patch.diff(
415 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
415 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
416 except RepoLookupError as e:
416 except RepoLookupError as e:
417 raise exceptions.LookupException(e)()
417 raise exceptions.LookupException(e)()
418
418
419 @reraise_safe_exceptions
419 @reraise_safe_exceptions
420 def file_history(self, wire, revision, path, limit):
420 def file_history(self, wire, revision, path, limit):
421 repo = self._factory.repo(wire)
421 repo = self._factory.repo(wire)
422
422
423 ctx = repo[revision]
423 ctx = repo[revision]
424 fctx = ctx.filectx(path)
424 fctx = ctx.filectx(path)
425
425
426 def history_iter():
426 def history_iter():
427 limit_rev = fctx.rev()
427 limit_rev = fctx.rev()
428 for obj in reversed(list(fctx.filelog())):
428 for obj in reversed(list(fctx.filelog())):
429 obj = fctx.filectx(obj)
429 obj = fctx.filectx(obj)
430 if limit_rev >= obj.rev():
430 if limit_rev >= obj.rev():
431 yield obj
431 yield obj
432
432
433 history = []
433 history = []
434 for cnt, obj in enumerate(history_iter()):
434 for cnt, obj in enumerate(history_iter()):
435 if limit and cnt >= limit:
435 if limit and cnt >= limit:
436 break
436 break
437 history.append(hex(obj.node()))
437 history.append(hex(obj.node()))
438
438
439 return [x for x in history]
439 return [x for x in history]
440
440
441 @reraise_safe_exceptions
441 @reraise_safe_exceptions
442 def file_history_untill(self, wire, revision, path, limit):
442 def file_history_untill(self, wire, revision, path, limit):
443 repo = self._factory.repo(wire)
443 repo = self._factory.repo(wire)
444 ctx = repo[revision]
444 ctx = repo[revision]
445 fctx = ctx.filectx(path)
445 fctx = ctx.filectx(path)
446
446
447 file_log = list(fctx.filelog())
447 file_log = list(fctx.filelog())
448 if limit:
448 if limit:
449 # Limit to the last n items
449 # Limit to the last n items
450 file_log = file_log[-limit:]
450 file_log = file_log[-limit:]
451
451
452 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
452 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
453
453
454 @reraise_safe_exceptions
454 @reraise_safe_exceptions
455 def fctx_annotate(self, wire, revision, path):
455 def fctx_annotate(self, wire, revision, path):
456 repo = self._factory.repo(wire)
456 repo = self._factory.repo(wire)
457 ctx = repo[revision]
457 ctx = repo[revision]
458 fctx = ctx.filectx(path)
458 fctx = ctx.filectx(path)
459
459
460 result = []
460 result = []
461 for i, annotate_obj in enumerate(fctx.annotate(), 1):
461 for i, annotate_obj in enumerate(fctx.annotate(), 1):
462 ln_no = i
462 ln_no = i
463 sha = hex(annotate_obj.fctx.node())
463 sha = hex(annotate_obj.fctx.node())
464 content = annotate_obj.text
464 content = annotate_obj.text
465 result.append((ln_no, sha, content))
465 result.append((ln_no, sha, content))
466 return result
466 return result
467
467
468 @reraise_safe_exceptions
468 @reraise_safe_exceptions
469 def fctx_data(self, wire, revision, path):
469 def fctx_data(self, wire, revision, path):
470 repo = self._factory.repo(wire)
470 repo = self._factory.repo(wire)
471 ctx = repo[revision]
471 ctx = repo[revision]
472 fctx = ctx.filectx(path)
472 fctx = ctx.filectx(path)
473 return fctx.data()
473 return fctx.data()
474
474
475 @reraise_safe_exceptions
475 @reraise_safe_exceptions
476 def fctx_flags(self, wire, revision, path):
476 def fctx_flags(self, wire, revision, path):
477 repo = self._factory.repo(wire)
477 repo = self._factory.repo(wire)
478 ctx = repo[revision]
478 ctx = repo[revision]
479 fctx = ctx.filectx(path)
479 fctx = ctx.filectx(path)
480 return fctx.flags()
480 return fctx.flags()
481
481
482 @reraise_safe_exceptions
482 @reraise_safe_exceptions
483 def fctx_size(self, wire, revision, path):
483 def fctx_size(self, wire, revision, path):
484 repo = self._factory.repo(wire)
484 repo = self._factory.repo(wire)
485 ctx = repo[revision]
485 ctx = repo[revision]
486 fctx = ctx.filectx(path)
486 fctx = ctx.filectx(path)
487 return fctx.size()
487 return fctx.size()
488
488
489 @reraise_safe_exceptions
489 @reraise_safe_exceptions
490 def get_all_commit_ids(self, wire, name):
490 def get_all_commit_ids(self, wire, name):
491 repo = self._factory.repo(wire)
491 repo = self._factory.repo(wire)
492 revs = repo.filtered(name).changelog.index
492 revs = repo.filtered(name).changelog.index
493 return map(lambda x: hex(x[7]), revs)[:-1]
493 return map(lambda x: hex(x[7]), revs)[:-1]
494
494
495 @reraise_safe_exceptions
495 @reraise_safe_exceptions
496 def get_config_value(self, wire, section, name, untrusted=False):
496 def get_config_value(self, wire, section, name, untrusted=False):
497 repo = self._factory.repo(wire)
497 repo = self._factory.repo(wire)
498 return repo.ui.config(section, name, untrusted=untrusted)
498 return repo.ui.config(section, name, untrusted=untrusted)
499
499
500 @reraise_safe_exceptions
500 @reraise_safe_exceptions
501 def get_config_bool(self, wire, section, name, untrusted=False):
501 def get_config_bool(self, wire, section, name, untrusted=False):
502 repo = self._factory.repo(wire)
502 repo = self._factory.repo(wire)
503 return repo.ui.configbool(section, name, untrusted=untrusted)
503 return repo.ui.configbool(section, name, untrusted=untrusted)
504
504
505 @reraise_safe_exceptions
505 @reraise_safe_exceptions
506 def get_config_list(self, wire, section, name, untrusted=False):
506 def get_config_list(self, wire, section, name, untrusted=False):
507 repo = self._factory.repo(wire)
507 repo = self._factory.repo(wire)
508 return repo.ui.configlist(section, name, untrusted=untrusted)
508 return repo.ui.configlist(section, name, untrusted=untrusted)
509
509
510 @reraise_safe_exceptions
510 @reraise_safe_exceptions
511 def is_large_file(self, wire, path):
511 def is_large_file(self, wire, path):
512 return largefiles.lfutil.isstandin(path)
512 return largefiles.lfutil.isstandin(path)
513
513
514 @reraise_safe_exceptions
514 @reraise_safe_exceptions
515 def in_largefiles_store(self, wire, sha):
515 def in_largefiles_store(self, wire, sha):
516 repo = self._factory.repo(wire)
516 repo = self._factory.repo(wire)
517 return largefiles.lfutil.instore(repo, sha)
517 return largefiles.lfutil.instore(repo, sha)
518
518
519 @reraise_safe_exceptions
519 @reraise_safe_exceptions
520 def in_user_cache(self, wire, sha):
520 def in_user_cache(self, wire, sha):
521 repo = self._factory.repo(wire)
521 repo = self._factory.repo(wire)
522 return largefiles.lfutil.inusercache(repo.ui, sha)
522 return largefiles.lfutil.inusercache(repo.ui, sha)
523
523
524 @reraise_safe_exceptions
524 @reraise_safe_exceptions
525 def store_path(self, wire, sha):
525 def store_path(self, wire, sha):
526 repo = self._factory.repo(wire)
526 repo = self._factory.repo(wire)
527 return largefiles.lfutil.storepath(repo, sha)
527 return largefiles.lfutil.storepath(repo, sha)
528
528
529 @reraise_safe_exceptions
529 @reraise_safe_exceptions
530 def link(self, wire, sha, path):
530 def link(self, wire, sha, path):
531 repo = self._factory.repo(wire)
531 repo = self._factory.repo(wire)
532 largefiles.lfutil.link(
532 largefiles.lfutil.link(
533 largefiles.lfutil.usercachepath(repo.ui, sha), path)
533 largefiles.lfutil.usercachepath(repo.ui, sha), path)
534
534
535 @reraise_safe_exceptions
535 @reraise_safe_exceptions
536 def localrepository(self, wire, create=False):
536 def localrepository(self, wire, create=False):
537 self._factory.repo(wire, create=create)
537 self._factory.repo(wire, create=create)
538
538
539 @reraise_safe_exceptions
539 @reraise_safe_exceptions
540 def lookup(self, wire, revision, both):
540 def lookup(self, wire, revision, both):
541
541
542 repo = self._factory.repo(wire)
542 repo = self._factory.repo(wire)
543
543
544 if isinstance(revision, int):
544 if isinstance(revision, int):
545 # NOTE(marcink):
545 # NOTE(marcink):
546 # since Mercurial doesn't support indexes properly
546 # since Mercurial doesn't support indexes properly
547 # we need to shift accordingly by one to get proper index, e.g
547 # we need to shift accordingly by one to get proper index, e.g
548 # repo[-1] => repo[-2]
548 # repo[-1] => repo[-2]
549 # repo[0] => repo[-1]
549 # repo[0] => repo[-1]
550 # repo[1] => repo[2] we also never call repo[0] because
550 # repo[1] => repo[2] we also never call repo[0] because
551 # it's actually second commit
551 # it's actually second commit
552 if revision <= 0:
552 if revision <= 0:
553 revision = revision + -1
553 revision = revision + -1
554 else:
554 else:
555 revision = revision + 1
555 revision = revision + 1
556
556
557 try:
557 try:
558 ctx = repo[revision]
558 ctx = repo[revision]
559 except RepoLookupError as e:
559 except RepoLookupError as e:
560 raise exceptions.LookupException(e)(revision)
560 raise exceptions.LookupException(e)(revision)
561 except LookupError as e:
561 except LookupError as e:
562 raise exceptions.LookupException(e)(e.name)
562 raise exceptions.LookupException(e)(e.name)
563
563
564 if not both:
564 if not both:
565 return ctx.hex()
565 return ctx.hex()
566
566
567 ctx = repo[ctx.hex()]
567 ctx = repo[ctx.hex()]
568 return ctx.hex(), ctx.rev()
568 return ctx.hex(), ctx.rev()
569
569
570 @reraise_safe_exceptions
570 @reraise_safe_exceptions
571 def pull(self, wire, url, commit_ids=None):
571 def pull(self, wire, url, commit_ids=None):
572 repo = self._factory.repo(wire)
572 repo = self._factory.repo(wire)
573 # Disable any prompts for this repo
573 # Disable any prompts for this repo
574 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
574 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
575
575
576 remote = peer(repo, {}, url)
576 remote = peer(repo, {}, url)
577 # Disable any prompts for this remote
577 # Disable any prompts for this remote
578 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
578 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
579
579
580 if commit_ids:
580 if commit_ids:
581 commit_ids = [bin(commit_id) for commit_id in commit_ids]
581 commit_ids = [bin(commit_id) for commit_id in commit_ids]
582
582
583 return exchange.pull(
583 return exchange.pull(
584 repo, remote, heads=commit_ids, force=None).cgresult
584 repo, remote, heads=commit_ids, force=None).cgresult
585
585
586 @reraise_safe_exceptions
586 @reraise_safe_exceptions
587 def sync_push(self, wire, url):
587 def sync_push(self, wire, url):
588 if self.check_url(url, wire['config']):
588 if not self.check_url(url, wire['config']):
589 return
590
589 repo = self._factory.repo(wire)
591 repo = self._factory.repo(wire)
590
592
591 # Disable any prompts for this repo
593 # Disable any prompts for this repo
592 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
594 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
593
595
594 bookmarks = dict(repo._bookmarks).keys()
596 bookmarks = dict(repo._bookmarks).keys()
595 remote = peer(repo, {}, url)
597 remote = peer(repo, {}, url)
596 # Disable any prompts for this remote
598 # Disable any prompts for this remote
597 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
599 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
598
600
599 return exchange.push(
601 return exchange.push(
600 repo, remote, newbranch=True, bookmarks=bookmarks).cgresult
602 repo, remote, newbranch=True, bookmarks=bookmarks).cgresult
601
603
602 @reraise_safe_exceptions
604 @reraise_safe_exceptions
603 def revision(self, wire, rev):
605 def revision(self, wire, rev):
604 repo = self._factory.repo(wire)
606 repo = self._factory.repo(wire)
605 ctx = repo[rev]
607 ctx = repo[rev]
606 return ctx.rev()
608 return ctx.rev()
607
609
608 @reraise_safe_exceptions
610 @reraise_safe_exceptions
609 def rev_range(self, wire, filter):
611 def rev_range(self, wire, filter):
610 repo = self._factory.repo(wire)
612 repo = self._factory.repo(wire)
611 revisions = [rev for rev in revrange(repo, filter)]
613 revisions = [rev for rev in revrange(repo, filter)]
612 return revisions
614 return revisions
613
615
614 @reraise_safe_exceptions
616 @reraise_safe_exceptions
615 def rev_range_hash(self, wire, node):
617 def rev_range_hash(self, wire, node):
616 repo = self._factory.repo(wire)
618 repo = self._factory.repo(wire)
617
619
618 def get_revs(repo, rev_opt):
620 def get_revs(repo, rev_opt):
619 if rev_opt:
621 if rev_opt:
620 revs = revrange(repo, rev_opt)
622 revs = revrange(repo, rev_opt)
621 if len(revs) == 0:
623 if len(revs) == 0:
622 return (nullrev, nullrev)
624 return (nullrev, nullrev)
623 return max(revs), min(revs)
625 return max(revs), min(revs)
624 else:
626 else:
625 return len(repo) - 1, 0
627 return len(repo) - 1, 0
626
628
627 stop, start = get_revs(repo, [node + ':'])
629 stop, start = get_revs(repo, [node + ':'])
628 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
630 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
629 return revs
631 return revs
630
632
631 @reraise_safe_exceptions
633 @reraise_safe_exceptions
632 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
634 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
633 other_path = kwargs.pop('other_path', None)
635 other_path = kwargs.pop('other_path', None)
634
636
635 # case when we want to compare two independent repositories
637 # case when we want to compare two independent repositories
636 if other_path and other_path != wire["path"]:
638 if other_path and other_path != wire["path"]:
637 baseui = self._factory._create_config(wire["config"])
639 baseui = self._factory._create_config(wire["config"])
638 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
640 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
639 else:
641 else:
640 repo = self._factory.repo(wire)
642 repo = self._factory.repo(wire)
641 return list(repo.revs(rev_spec, *args))
643 return list(repo.revs(rev_spec, *args))
642
644
643 @reraise_safe_exceptions
645 @reraise_safe_exceptions
644 def strip(self, wire, revision, update, backup):
646 def strip(self, wire, revision, update, backup):
645 repo = self._factory.repo(wire)
647 repo = self._factory.repo(wire)
646 ctx = repo[revision]
648 ctx = repo[revision]
647 hgext_strip(
649 hgext_strip(
648 repo.baseui, repo, ctx.node(), update=update, backup=backup)
650 repo.baseui, repo, ctx.node(), update=update, backup=backup)
649
651
650 @reraise_safe_exceptions
652 @reraise_safe_exceptions
651 def verify(self, wire,):
653 def verify(self, wire,):
652 repo = self._factory.repo(wire)
654 repo = self._factory.repo(wire)
653 baseui = self._factory._create_config(wire['config'])
655 baseui = self._factory._create_config(wire['config'])
654 baseui.setconfig('ui', 'quiet', 'false')
656 baseui.setconfig('ui', 'quiet', 'false')
655 output = io.BytesIO()
657 output = io.BytesIO()
656
658
657 def write(data, **unused_kwargs):
659 def write(data, **unused_kwargs):
658 output.write(data)
660 output.write(data)
659 baseui.write = write
661 baseui.write = write
660
662
661 repo.ui = baseui
663 repo.ui = baseui
662 verify.verify(repo)
664 verify.verify(repo)
663 return output.getvalue()
665 return output.getvalue()
664
666
665 @reraise_safe_exceptions
667 @reraise_safe_exceptions
666 def tag(self, wire, name, revision, message, local, user,
668 def tag(self, wire, name, revision, message, local, user,
667 tag_time, tag_timezone):
669 tag_time, tag_timezone):
668 repo = self._factory.repo(wire)
670 repo = self._factory.repo(wire)
669 ctx = repo[revision]
671 ctx = repo[revision]
670 node = ctx.node()
672 node = ctx.node()
671
673
672 date = (tag_time, tag_timezone)
674 date = (tag_time, tag_timezone)
673 try:
675 try:
674 hg_tag.tag(repo, name, node, message, local, user, date)
676 hg_tag.tag(repo, name, node, message, local, user, date)
675 except Abort as e:
677 except Abort as e:
676 log.exception("Tag operation aborted")
678 log.exception("Tag operation aborted")
677 # Exception can contain unicode which we convert
679 # Exception can contain unicode which we convert
678 raise exceptions.AbortException(e)(repr(e))
680 raise exceptions.AbortException(e)(repr(e))
679
681
680 @reraise_safe_exceptions
682 @reraise_safe_exceptions
681 def tags(self, wire):
683 def tags(self, wire):
682 repo = self._factory.repo(wire)
684 repo = self._factory.repo(wire)
683 return repo.tags()
685 return repo.tags()
684
686
685 @reraise_safe_exceptions
687 @reraise_safe_exceptions
686 def update(self, wire, node=None, clean=False):
688 def update(self, wire, node=None, clean=False):
687 repo = self._factory.repo(wire)
689 repo = self._factory.repo(wire)
688 baseui = self._factory._create_config(wire['config'])
690 baseui = self._factory._create_config(wire['config'])
689 commands.update(baseui, repo, node=node, clean=clean)
691 commands.update(baseui, repo, node=node, clean=clean)
690
692
691 @reraise_safe_exceptions
693 @reraise_safe_exceptions
692 def identify(self, wire):
694 def identify(self, wire):
693 repo = self._factory.repo(wire)
695 repo = self._factory.repo(wire)
694 baseui = self._factory._create_config(wire['config'])
696 baseui = self._factory._create_config(wire['config'])
695 output = io.BytesIO()
697 output = io.BytesIO()
696 baseui.write = output.write
698 baseui.write = output.write
697 # This is required to get a full node id
699 # This is required to get a full node id
698 baseui.debugflag = True
700 baseui.debugflag = True
699 commands.identify(baseui, repo, id=True)
701 commands.identify(baseui, repo, id=True)
700
702
701 return output.getvalue()
703 return output.getvalue()
702
704
703 @reraise_safe_exceptions
705 @reraise_safe_exceptions
704 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
706 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
705 hooks=True):
707 hooks=True):
706 repo = self._factory.repo(wire)
708 repo = self._factory.repo(wire)
707 baseui = self._factory._create_config(wire['config'], hooks=hooks)
709 baseui = self._factory._create_config(wire['config'], hooks=hooks)
708
710
709 # Mercurial internally has a lot of logic that checks ONLY if
711 # Mercurial internally has a lot of logic that checks ONLY if
710 # option is defined, we just pass those if they are defined then
712 # option is defined, we just pass those if they are defined then
711 opts = {}
713 opts = {}
712 if bookmark:
714 if bookmark:
713 opts['bookmark'] = bookmark
715 opts['bookmark'] = bookmark
714 if branch:
716 if branch:
715 opts['branch'] = branch
717 opts['branch'] = branch
716 if revision:
718 if revision:
717 opts['rev'] = revision
719 opts['rev'] = revision
718
720
719 commands.pull(baseui, repo, source, **opts)
721 commands.pull(baseui, repo, source, **opts)
720
722
721 @reraise_safe_exceptions
723 @reraise_safe_exceptions
722 def heads(self, wire, branch=None):
724 def heads(self, wire, branch=None):
723 repo = self._factory.repo(wire)
725 repo = self._factory.repo(wire)
724 baseui = self._factory._create_config(wire['config'])
726 baseui = self._factory._create_config(wire['config'])
725 output = io.BytesIO()
727 output = io.BytesIO()
726
728
727 def write(data, **unused_kwargs):
729 def write(data, **unused_kwargs):
728 output.write(data)
730 output.write(data)
729
731
730 baseui.write = write
732 baseui.write = write
731 if branch:
733 if branch:
732 args = [branch]
734 args = [branch]
733 else:
735 else:
734 args = []
736 args = []
735 commands.heads(baseui, repo, template='{node} ', *args)
737 commands.heads(baseui, repo, template='{node} ', *args)
736
738
737 return output.getvalue()
739 return output.getvalue()
738
740
739 @reraise_safe_exceptions
741 @reraise_safe_exceptions
740 def ancestor(self, wire, revision1, revision2):
742 def ancestor(self, wire, revision1, revision2):
741 repo = self._factory.repo(wire)
743 repo = self._factory.repo(wire)
742 changelog = repo.changelog
744 changelog = repo.changelog
743 lookup = repo.lookup
745 lookup = repo.lookup
744 a = changelog.ancestor(lookup(revision1), lookup(revision2))
746 a = changelog.ancestor(lookup(revision1), lookup(revision2))
745 return hex(a)
747 return hex(a)
746
748
747 @reraise_safe_exceptions
749 @reraise_safe_exceptions
748 def push(self, wire, revisions, dest_path, hooks=True,
750 def push(self, wire, revisions, dest_path, hooks=True,
749 push_branches=False):
751 push_branches=False):
750 repo = self._factory.repo(wire)
752 repo = self._factory.repo(wire)
751 baseui = self._factory._create_config(wire['config'], hooks=hooks)
753 baseui = self._factory._create_config(wire['config'], hooks=hooks)
752 commands.push(baseui, repo, dest=dest_path, rev=revisions,
754 commands.push(baseui, repo, dest=dest_path, rev=revisions,
753 new_branch=push_branches)
755 new_branch=push_branches)
754
756
755 @reraise_safe_exceptions
757 @reraise_safe_exceptions
756 def merge(self, wire, revision):
758 def merge(self, wire, revision):
757 repo = self._factory.repo(wire)
759 repo = self._factory.repo(wire)
758 baseui = self._factory._create_config(wire['config'])
760 baseui = self._factory._create_config(wire['config'])
759 repo.ui.setconfig('ui', 'merge', 'internal:dump')
761 repo.ui.setconfig('ui', 'merge', 'internal:dump')
760
762
761 # In case of sub repositories are used mercurial prompts the user in
763 # In case of sub repositories are used mercurial prompts the user in
762 # case of merge conflicts or different sub repository sources. By
764 # case of merge conflicts or different sub repository sources. By
763 # setting the interactive flag to `False` mercurial doesn't prompt the
765 # setting the interactive flag to `False` mercurial doesn't prompt the
764 # used but instead uses a default value.
766 # used but instead uses a default value.
765 repo.ui.setconfig('ui', 'interactive', False)
767 repo.ui.setconfig('ui', 'interactive', False)
766
768
767 commands.merge(baseui, repo, rev=revision)
769 commands.merge(baseui, repo, rev=revision)
768
770
769 @reraise_safe_exceptions
771 @reraise_safe_exceptions
770 def commit(self, wire, message, username, close_branch=False):
772 def commit(self, wire, message, username, close_branch=False):
771 repo = self._factory.repo(wire)
773 repo = self._factory.repo(wire)
772 baseui = self._factory._create_config(wire['config'])
774 baseui = self._factory._create_config(wire['config'])
773 repo.ui.setconfig('ui', 'username', username)
775 repo.ui.setconfig('ui', 'username', username)
774 commands.commit(baseui, repo, message=message, close_branch=close_branch)
776 commands.commit(baseui, repo, message=message, close_branch=close_branch)
775
777
776 @reraise_safe_exceptions
778 @reraise_safe_exceptions
777 def rebase(self, wire, source=None, dest=None, abort=False):
779 def rebase(self, wire, source=None, dest=None, abort=False):
778 repo = self._factory.repo(wire)
780 repo = self._factory.repo(wire)
779 baseui = self._factory._create_config(wire['config'])
781 baseui = self._factory._create_config(wire['config'])
780 repo.ui.setconfig('ui', 'merge', 'internal:dump')
782 repo.ui.setconfig('ui', 'merge', 'internal:dump')
781 rebase.rebase(
783 rebase.rebase(
782 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
784 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
783
785
784 @reraise_safe_exceptions
786 @reraise_safe_exceptions
785 def bookmark(self, wire, bookmark, revision=None):
787 def bookmark(self, wire, bookmark, revision=None):
786 repo = self._factory.repo(wire)
788 repo = self._factory.repo(wire)
787 baseui = self._factory._create_config(wire['config'])
789 baseui = self._factory._create_config(wire['config'])
788 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
790 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
789
791
790 @reraise_safe_exceptions
792 @reraise_safe_exceptions
791 def install_hooks(self, wire, force=False):
793 def install_hooks(self, wire, force=False):
792 # we don't need any special hooks for Mercurial
794 # we don't need any special hooks for Mercurial
793 pass
795 pass
General Comments 0
You need to be logged in to leave comments. Login now