##// END OF EJS Templates
hg: added compat for fetching revision using new hg 4.9 code
marcink -
r660:f4b31a7a default
parent child Browse files
Show More
@@ -1,809 +1,807 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-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 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 import traceback
23 import traceback
24
24
25 from hgext import largefiles, rebase
25 from hgext import largefiles, rebase
26 from hgext.strip import strip as hgext_strip
26 from hgext.strip import strip as hgext_strip
27 from mercurial import commands
27 from mercurial import commands
28 from mercurial import unionrepo
28 from mercurial import unionrepo
29 from mercurial import verify
29 from mercurial import verify
30
30
31 import vcsserver
31 import vcsserver
32 from vcsserver import exceptions
32 from vcsserver import exceptions
33 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
33 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
34 from vcsserver.hgcompat import (
34 from vcsserver.hgcompat import (
35 archival, bin, clone, config as hgconfig, diffopts, hex, revsymbol,
35 archival, bin, clone, config as hgconfig, diffopts, hex, get_ctx,
36 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
36 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
37 makepeer, instance, match, memctx, exchange, memfilectx, nullrev,
37 makepeer, instance, match, memctx, exchange, memfilectx, nullrev,
38 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
38 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
39 RepoLookupError, ProgrammingError, InterventionRequired, RequirementError)
39 RepoLookupError, InterventionRequired, RequirementError)
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 def make_ui_from_config(repo_config):
44 def make_ui_from_config(repo_config):
45 baseui = ui.ui()
45 baseui = ui.ui()
46
46
47 # clean the baseui object
47 # clean the baseui object
48 baseui._ocfg = hgconfig.config()
48 baseui._ocfg = hgconfig.config()
49 baseui._ucfg = hgconfig.config()
49 baseui._ucfg = hgconfig.config()
50 baseui._tcfg = hgconfig.config()
50 baseui._tcfg = hgconfig.config()
51
51
52 for section, option, value in repo_config:
52 for section, option, value in repo_config:
53 baseui.setconfig(section, option, value)
53 baseui.setconfig(section, option, value)
54
54
55 # make our hgweb quiet so it doesn't print output
55 # make our hgweb quiet so it doesn't print output
56 baseui.setconfig('ui', 'quiet', 'true')
56 baseui.setconfig('ui', 'quiet', 'true')
57
57
58 baseui.setconfig('ui', 'paginate', 'never')
58 baseui.setconfig('ui', 'paginate', 'never')
59 # for better Error reporting of Mercurial
59 # for better Error reporting of Mercurial
60 baseui.setconfig('ui', 'message-output', 'stderr')
60 baseui.setconfig('ui', 'message-output', 'stderr')
61
61
62 # force mercurial to only use 1 thread, otherwise it may try to set a
62 # force mercurial to only use 1 thread, otherwise it may try to set a
63 # signal in a non-main thread, thus generating a ValueError.
63 # signal in a non-main thread, thus generating a ValueError.
64 baseui.setconfig('worker', 'numcpus', 1)
64 baseui.setconfig('worker', 'numcpus', 1)
65
65
66 # If there is no config for the largefiles extension, we explicitly disable
66 # If there is no config for the largefiles extension, we explicitly disable
67 # it here. This overrides settings from repositories hgrc file. Recent
67 # it here. This overrides settings from repositories hgrc file. Recent
68 # mercurial versions enable largefiles in hgrc on clone from largefile
68 # mercurial versions enable largefiles in hgrc on clone from largefile
69 # repo.
69 # repo.
70 if not baseui.hasconfig('extensions', 'largefiles'):
70 if not baseui.hasconfig('extensions', 'largefiles'):
71 log.debug('Explicitly disable largefiles extension for repo.')
71 log.debug('Explicitly disable largefiles extension for repo.')
72 baseui.setconfig('extensions', 'largefiles', '!')
72 baseui.setconfig('extensions', 'largefiles', '!')
73
73
74 return baseui
74 return baseui
75
75
76
76
77 def reraise_safe_exceptions(func):
77 def reraise_safe_exceptions(func):
78 """Decorator for converting mercurial exceptions to something neutral."""
78 """Decorator for converting mercurial exceptions to something neutral."""
79 def wrapper(*args, **kwargs):
79 def wrapper(*args, **kwargs):
80 try:
80 try:
81 return func(*args, **kwargs)
81 return func(*args, **kwargs)
82 except (Abort, InterventionRequired) as e:
82 except (Abort, InterventionRequired) as e:
83 raise_from_original(exceptions.AbortException(e))
83 raise_from_original(exceptions.AbortException(e))
84 except RepoLookupError as e:
84 except RepoLookupError as e:
85 raise_from_original(exceptions.LookupException(e))
85 raise_from_original(exceptions.LookupException(e))
86 except RequirementError as e:
86 except RequirementError as e:
87 raise_from_original(exceptions.RequirementException(e))
87 raise_from_original(exceptions.RequirementException(e))
88 except RepoError as e:
88 except RepoError as e:
89 raise_from_original(exceptions.VcsException(e))
89 raise_from_original(exceptions.VcsException(e))
90 except LookupError as e:
90 except LookupError as e:
91 raise_from_original(exceptions.LookupException(e))
91 raise_from_original(exceptions.LookupException(e))
92 except Exception as e:
92 except Exception as e:
93 if not hasattr(e, '_vcs_kind'):
93 if not hasattr(e, '_vcs_kind'):
94 log.exception("Unhandled exception in hg remote call")
94 log.exception("Unhandled exception in hg remote call")
95 raise_from_original(exceptions.UnhandledException(e))
95 raise_from_original(exceptions.UnhandledException(e))
96
96
97 raise
97 raise
98 return wrapper
98 return wrapper
99
99
100
100
101 class MercurialFactory(RepoFactory):
101 class MercurialFactory(RepoFactory):
102 repo_type = 'hg'
102 repo_type = 'hg'
103
103
104 def _create_config(self, config, hooks=True):
104 def _create_config(self, config, hooks=True):
105 if not hooks:
105 if not hooks:
106 hooks_to_clean = frozenset((
106 hooks_to_clean = frozenset((
107 'changegroup.repo_size', 'preoutgoing.pre_pull',
107 'changegroup.repo_size', 'preoutgoing.pre_pull',
108 'outgoing.pull_logger', 'prechangegroup.pre_push'))
108 'outgoing.pull_logger', 'prechangegroup.pre_push'))
109 new_config = []
109 new_config = []
110 for section, option, value in config:
110 for section, option, value in config:
111 if section == 'hooks' and option in hooks_to_clean:
111 if section == 'hooks' and option in hooks_to_clean:
112 continue
112 continue
113 new_config.append((section, option, value))
113 new_config.append((section, option, value))
114 config = new_config
114 config = new_config
115
115
116 baseui = make_ui_from_config(config)
116 baseui = make_ui_from_config(config)
117 return baseui
117 return baseui
118
118
119 def _create_repo(self, wire, create):
119 def _create_repo(self, wire, create):
120 baseui = self._create_config(wire["config"])
120 baseui = self._create_config(wire["config"])
121 return instance(baseui, wire["path"], create)
121 return instance(baseui, wire["path"], create)
122
122
123
123
124 class HgRemote(object):
124 class HgRemote(object):
125
125
126 def __init__(self, factory):
126 def __init__(self, factory):
127 self._factory = factory
127 self._factory = factory
128
128
129 self._bulk_methods = {
129 self._bulk_methods = {
130 "affected_files": self.ctx_files,
130 "affected_files": self.ctx_files,
131 "author": self.ctx_user,
131 "author": self.ctx_user,
132 "branch": self.ctx_branch,
132 "branch": self.ctx_branch,
133 "children": self.ctx_children,
133 "children": self.ctx_children,
134 "date": self.ctx_date,
134 "date": self.ctx_date,
135 "message": self.ctx_description,
135 "message": self.ctx_description,
136 "parents": self.ctx_parents,
136 "parents": self.ctx_parents,
137 "status": self.ctx_status,
137 "status": self.ctx_status,
138 "obsolete": self.ctx_obsolete,
138 "obsolete": self.ctx_obsolete,
139 "phase": self.ctx_phase,
139 "phase": self.ctx_phase,
140 "hidden": self.ctx_hidden,
140 "hidden": self.ctx_hidden,
141 "_file_paths": self.ctx_list,
141 "_file_paths": self.ctx_list,
142 }
142 }
143
143
144 def _get_ctx(self, repo, ref):
145 return get_ctx(repo, ref)
146
144 @reraise_safe_exceptions
147 @reraise_safe_exceptions
145 def discover_hg_version(self):
148 def discover_hg_version(self):
146 from mercurial import util
149 from mercurial import util
147 return util.version()
150 return util.version()
148
151
149 @reraise_safe_exceptions
152 @reraise_safe_exceptions
150 def archive_repo(self, archive_path, mtime, file_info, kind):
153 def archive_repo(self, archive_path, mtime, file_info, kind):
151 if kind == "tgz":
154 if kind == "tgz":
152 archiver = archival.tarit(archive_path, mtime, "gz")
155 archiver = archival.tarit(archive_path, mtime, "gz")
153 elif kind == "tbz2":
156 elif kind == "tbz2":
154 archiver = archival.tarit(archive_path, mtime, "bz2")
157 archiver = archival.tarit(archive_path, mtime, "bz2")
155 elif kind == 'zip':
158 elif kind == 'zip':
156 archiver = archival.zipit(archive_path, mtime)
159 archiver = archival.zipit(archive_path, mtime)
157 else:
160 else:
158 raise exceptions.ArchiveException()(
161 raise exceptions.ArchiveException()(
159 'Remote does not support: "%s".' % kind)
162 'Remote does not support: "%s".' % kind)
160
163
161 for f_path, f_mode, f_is_link, f_content in file_info:
164 for f_path, f_mode, f_is_link, f_content in file_info:
162 archiver.addfile(f_path, f_mode, f_is_link, f_content)
165 archiver.addfile(f_path, f_mode, f_is_link, f_content)
163 archiver.done()
166 archiver.done()
164
167
165 @reraise_safe_exceptions
168 @reraise_safe_exceptions
166 def bookmarks(self, wire):
169 def bookmarks(self, wire):
167 repo = self._factory.repo(wire)
170 repo = self._factory.repo(wire)
168 return dict(repo._bookmarks)
171 return dict(repo._bookmarks)
169
172
170 @reraise_safe_exceptions
173 @reraise_safe_exceptions
171 def branches(self, wire, normal, closed):
174 def branches(self, wire, normal, closed):
172 repo = self._factory.repo(wire)
175 repo = self._factory.repo(wire)
173 iter_branches = repo.branchmap().iterbranches()
176 iter_branches = repo.branchmap().iterbranches()
174 bt = {}
177 bt = {}
175 for branch_name, _heads, tip, is_closed in iter_branches:
178 for branch_name, _heads, tip, is_closed in iter_branches:
176 if normal and not is_closed:
179 if normal and not is_closed:
177 bt[branch_name] = tip
180 bt[branch_name] = tip
178 if closed and is_closed:
181 if closed and is_closed:
179 bt[branch_name] = tip
182 bt[branch_name] = tip
180
183
181 return bt
184 return bt
182
185
183 @reraise_safe_exceptions
186 @reraise_safe_exceptions
184 def bulk_request(self, wire, rev, pre_load):
187 def bulk_request(self, wire, rev, pre_load):
185 result = {}
188 result = {}
186 for attr in pre_load:
189 for attr in pre_load:
187 try:
190 try:
188 method = self._bulk_methods[attr]
191 method = self._bulk_methods[attr]
189 result[attr] = method(wire, rev)
192 result[attr] = method(wire, rev)
190 except KeyError as e:
193 except KeyError as e:
191 raise exceptions.VcsException(e)(
194 raise exceptions.VcsException(e)(
192 'Unknown bulk attribute: "%s"' % attr)
195 'Unknown bulk attribute: "%s"' % attr)
193 return result
196 return result
194
197
195 @reraise_safe_exceptions
198 @reraise_safe_exceptions
196 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
199 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
197 baseui = self._factory._create_config(wire["config"], hooks=hooks)
200 baseui = self._factory._create_config(wire["config"], hooks=hooks)
198 clone(baseui, source, dest, noupdate=not update_after_clone)
201 clone(baseui, source, dest, noupdate=not update_after_clone)
199
202
200 @reraise_safe_exceptions
203 @reraise_safe_exceptions
201 def commitctx(
204 def commitctx(
202 self, wire, message, parents, commit_time, commit_timezone,
205 self, wire, message, parents, commit_time, commit_timezone,
203 user, files, extra, removed, updated):
206 user, files, extra, removed, updated):
204
207
205 repo = self._factory.repo(wire)
208 repo = self._factory.repo(wire)
206 baseui = self._factory._create_config(wire['config'])
209 baseui = self._factory._create_config(wire['config'])
207 publishing = baseui.configbool('phases', 'publish')
210 publishing = baseui.configbool('phases', 'publish')
208 if publishing:
211 if publishing:
209 new_commit = 'public'
212 new_commit = 'public'
210 else:
213 else:
211 new_commit = 'draft'
214 new_commit = 'draft'
212
215
213 def _filectxfn(_repo, ctx, path):
216 def _filectxfn(_repo, ctx, path):
214 """
217 """
215 Marks given path as added/changed/removed in a given _repo. This is
218 Marks given path as added/changed/removed in a given _repo. This is
216 for internal mercurial commit function.
219 for internal mercurial commit function.
217 """
220 """
218
221
219 # check if this path is removed
222 # check if this path is removed
220 if path in removed:
223 if path in removed:
221 # returning None is a way to mark node for removal
224 # returning None is a way to mark node for removal
222 return None
225 return None
223
226
224 # check if this path is added
227 # check if this path is added
225 for node in updated:
228 for node in updated:
226 if node['path'] == path:
229 if node['path'] == path:
227 return memfilectx(
230 return memfilectx(
228 _repo,
231 _repo,
229 changectx=ctx,
232 changectx=ctx,
230 path=node['path'],
233 path=node['path'],
231 data=node['content'],
234 data=node['content'],
232 islink=False,
235 islink=False,
233 isexec=bool(node['mode'] & stat.S_IXUSR),
236 isexec=bool(node['mode'] & stat.S_IXUSR),
234 copied=False)
237 copied=False)
235
238
236 raise exceptions.AbortException()(
239 raise exceptions.AbortException()(
237 "Given path haven't been marked as added, "
240 "Given path haven't been marked as added, "
238 "changed or removed (%s)" % path)
241 "changed or removed (%s)" % path)
239
242
240 with repo.ui.configoverride({('phases', 'new-commit'): new_commit}):
243 with repo.ui.configoverride({('phases', 'new-commit'): new_commit}):
241
244
242 commit_ctx = memctx(
245 commit_ctx = memctx(
243 repo=repo,
246 repo=repo,
244 parents=parents,
247 parents=parents,
245 text=message,
248 text=message,
246 files=files,
249 files=files,
247 filectxfn=_filectxfn,
250 filectxfn=_filectxfn,
248 user=user,
251 user=user,
249 date=(commit_time, commit_timezone),
252 date=(commit_time, commit_timezone),
250 extra=extra)
253 extra=extra)
251
254
252 n = repo.commitctx(commit_ctx)
255 n = repo.commitctx(commit_ctx)
253 new_id = hex(n)
256 new_id = hex(n)
254
257
255 return new_id
258 return new_id
256
259
257 @reraise_safe_exceptions
260 @reraise_safe_exceptions
258 def ctx_branch(self, wire, revision):
261 def ctx_branch(self, wire, revision):
259 repo = self._factory.repo(wire)
262 repo = self._factory.repo(wire)
260 ctx = repo[revision]
263 ctx = self._get_ctx(repo, revision)
261 return ctx.branch()
264 return ctx.branch()
262
265
263 @reraise_safe_exceptions
266 @reraise_safe_exceptions
264 def ctx_children(self, wire, revision):
267 def ctx_children(self, wire, revision):
265 repo = self._factory.repo(wire)
268 repo = self._factory.repo(wire)
266 ctx = repo[revision]
269 ctx = self._get_ctx(repo, revision)
267 return [child.rev() for child in ctx.children()]
270 return [child.rev() for child in ctx.children()]
268
271
269 @reraise_safe_exceptions
272 @reraise_safe_exceptions
270 def ctx_date(self, wire, revision):
273 def ctx_date(self, wire, revision):
271 repo = self._factory.repo(wire)
274 repo = self._factory.repo(wire)
272 ctx = repo[revision]
275 ctx = self._get_ctx(repo, revision)
273 return ctx.date()
276 return ctx.date()
274
277
275 @reraise_safe_exceptions
278 @reraise_safe_exceptions
276 def ctx_description(self, wire, revision):
279 def ctx_description(self, wire, revision):
277 repo = self._factory.repo(wire)
280 repo = self._factory.repo(wire)
278 ctx = repo[revision]
281 ctx = self._get_ctx(repo, revision)
279 return ctx.description()
282 return ctx.description()
280
283
281 @reraise_safe_exceptions
284 @reraise_safe_exceptions
282 def ctx_files(self, wire, revision):
285 def ctx_files(self, wire, revision):
283 repo = self._factory.repo(wire)
286 repo = self._factory.repo(wire)
284 ctx = repo[revision]
287 ctx = self._get_ctx(repo, revision)
285 return ctx.files()
288 return ctx.files()
286
289
287 @reraise_safe_exceptions
290 @reraise_safe_exceptions
288 def ctx_list(self, path, revision):
291 def ctx_list(self, path, revision):
289 repo = self._factory.repo(path)
292 repo = self._factory.repo(path)
290 ctx = repo[revision]
293 ctx = self._get_ctx(repo, revision)
291 return list(ctx)
294 return list(ctx)
292
295
293 @reraise_safe_exceptions
296 @reraise_safe_exceptions
294 def ctx_parents(self, wire, revision):
297 def ctx_parents(self, wire, revision):
295 repo = self._factory.repo(wire)
298 repo = self._factory.repo(wire)
296 ctx = repo[revision]
299 ctx = self._get_ctx(repo, revision)
297 return [parent.rev() for parent in ctx.parents()]
300 return [parent.rev() for parent in ctx.parents()]
298
301
299 @reraise_safe_exceptions
302 @reraise_safe_exceptions
300 def ctx_phase(self, wire, revision):
303 def ctx_phase(self, wire, revision):
301 repo = self._factory.repo(wire)
304 repo = self._factory.repo(wire)
302 ctx = repo[revision]
305 ctx = self._get_ctx(repo, revision)
303 # public=0, draft=1, secret=3
306 # public=0, draft=1, secret=3
304 return ctx.phase()
307 return ctx.phase()
305
308
306 @reraise_safe_exceptions
309 @reraise_safe_exceptions
307 def ctx_obsolete(self, wire, revision):
310 def ctx_obsolete(self, wire, revision):
308 repo = self._factory.repo(wire)
311 repo = self._factory.repo(wire)
309 ctx = repo[revision]
312 ctx = self._get_ctx(repo, revision)
310 return ctx.obsolete()
313 return ctx.obsolete()
311
314
312 @reraise_safe_exceptions
315 @reraise_safe_exceptions
313 def ctx_hidden(self, wire, revision):
316 def ctx_hidden(self, wire, revision):
314 repo = self._factory.repo(wire)
317 repo = self._factory.repo(wire)
315 ctx = repo[revision]
318 ctx = self._get_ctx(repo, revision)
316 return ctx.hidden()
319 return ctx.hidden()
317
320
318 @reraise_safe_exceptions
321 @reraise_safe_exceptions
319 def ctx_substate(self, wire, revision):
322 def ctx_substate(self, wire, revision):
320 repo = self._factory.repo(wire)
323 repo = self._factory.repo(wire)
321 ctx = repo[revision]
324 ctx = self._get_ctx(repo, revision)
322 return ctx.substate
325 return ctx.substate
323
326
324 @reraise_safe_exceptions
327 @reraise_safe_exceptions
325 def ctx_status(self, wire, revision):
328 def ctx_status(self, wire, revision):
326 repo = self._factory.repo(wire)
329 repo = self._factory.repo(wire)
327 ctx = repo[revision]
330 ctx = self._get_ctx(repo, revision)
328 status = repo[ctx.p1().node()].status(other=ctx.node())
331 status = repo[ctx.p1().node()].status(other=ctx.node())
329 # object of status (odd, custom named tuple in mercurial) is not
332 # object of status (odd, custom named tuple in mercurial) is not
330 # correctly serializable, we make it a list, as the underling
333 # correctly serializable, we make it a list, as the underling
331 # API expects this to be a list
334 # API expects this to be a list
332 return list(status)
335 return list(status)
333
336
334 @reraise_safe_exceptions
337 @reraise_safe_exceptions
335 def ctx_user(self, wire, revision):
338 def ctx_user(self, wire, revision):
336 repo = self._factory.repo(wire)
339 repo = self._factory.repo(wire)
337 ctx = repo[revision]
340 ctx = self._get_ctx(repo, revision)
338 return ctx.user()
341 return ctx.user()
339
342
340 @reraise_safe_exceptions
343 @reraise_safe_exceptions
341 def check_url(self, url, config):
344 def check_url(self, url, config):
342 _proto = None
345 _proto = None
343 if '+' in url[:url.find('://')]:
346 if '+' in url[:url.find('://')]:
344 _proto = url[0:url.find('+')]
347 _proto = url[0:url.find('+')]
345 url = url[url.find('+') + 1:]
348 url = url[url.find('+') + 1:]
346 handlers = []
349 handlers = []
347 url_obj = url_parser(url)
350 url_obj = url_parser(url)
348 test_uri, authinfo = url_obj.authinfo()
351 test_uri, authinfo = url_obj.authinfo()
349 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
352 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
350 url_obj.query = obfuscate_qs(url_obj.query)
353 url_obj.query = obfuscate_qs(url_obj.query)
351
354
352 cleaned_uri = str(url_obj)
355 cleaned_uri = str(url_obj)
353 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
356 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
354
357
355 if authinfo:
358 if authinfo:
356 # create a password manager
359 # create a password manager
357 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
360 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
358 passmgr.add_password(*authinfo)
361 passmgr.add_password(*authinfo)
359
362
360 handlers.extend((httpbasicauthhandler(passmgr),
363 handlers.extend((httpbasicauthhandler(passmgr),
361 httpdigestauthhandler(passmgr)))
364 httpdigestauthhandler(passmgr)))
362
365
363 o = urllib2.build_opener(*handlers)
366 o = urllib2.build_opener(*handlers)
364 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
367 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
365 ('Accept', 'application/mercurial-0.1')]
368 ('Accept', 'application/mercurial-0.1')]
366
369
367 q = {"cmd": 'between'}
370 q = {"cmd": 'between'}
368 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
371 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
369 qs = '?%s' % urllib.urlencode(q)
372 qs = '?%s' % urllib.urlencode(q)
370 cu = "%s%s" % (test_uri, qs)
373 cu = "%s%s" % (test_uri, qs)
371 req = urllib2.Request(cu, None, {})
374 req = urllib2.Request(cu, None, {})
372
375
373 try:
376 try:
374 log.debug("Trying to open URL %s", cleaned_uri)
377 log.debug("Trying to open URL %s", cleaned_uri)
375 resp = o.open(req)
378 resp = o.open(req)
376 if resp.code != 200:
379 if resp.code != 200:
377 raise exceptions.URLError()('Return Code is not 200')
380 raise exceptions.URLError()('Return Code is not 200')
378 except Exception as e:
381 except Exception as e:
379 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
382 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
380 # means it cannot be cloned
383 # means it cannot be cloned
381 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
384 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
382
385
383 # now check if it's a proper hg repo, but don't do it for svn
386 # now check if it's a proper hg repo, but don't do it for svn
384 try:
387 try:
385 if _proto == 'svn':
388 if _proto == 'svn':
386 pass
389 pass
387 else:
390 else:
388 # check for pure hg repos
391 # check for pure hg repos
389 log.debug(
392 log.debug(
390 "Verifying if URL is a Mercurial repository: %s",
393 "Verifying if URL is a Mercurial repository: %s",
391 cleaned_uri)
394 cleaned_uri)
392 ui = make_ui_from_config(config)
395 ui = make_ui_from_config(config)
393 peer_checker = makepeer(ui, url)
396 peer_checker = makepeer(ui, url)
394 peer_checker.lookup('tip')
397 peer_checker.lookup('tip')
395 except Exception as e:
398 except Exception as e:
396 log.warning("URL is not a valid Mercurial repository: %s",
399 log.warning("URL is not a valid Mercurial repository: %s",
397 cleaned_uri)
400 cleaned_uri)
398 raise exceptions.URLError(e)(
401 raise exceptions.URLError(e)(
399 "url [%s] does not look like an hg repo org_exc: %s"
402 "url [%s] does not look like an hg repo org_exc: %s"
400 % (cleaned_uri, e))
403 % (cleaned_uri, e))
401
404
402 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
405 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
403 return True
406 return True
404
407
405 @reraise_safe_exceptions
408 @reraise_safe_exceptions
406 def diff(
409 def diff(
407 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
410 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
408 context):
411 context):
409 repo = self._factory.repo(wire)
412 repo = self._factory.repo(wire)
410
413
411 if file_filter:
414 if file_filter:
412 match_filter = match(file_filter[0], '', [file_filter[1]])
415 match_filter = match(file_filter[0], '', [file_filter[1]])
413 else:
416 else:
414 match_filter = file_filter
417 match_filter = file_filter
415 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
418 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
416
419
417 try:
420 try:
418 return "".join(patch.diff(
421 return "".join(patch.diff(
419 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
422 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
420 except RepoLookupError as e:
423 except RepoLookupError as e:
421 raise exceptions.LookupException(e)()
424 raise exceptions.LookupException(e)()
422
425
423 @reraise_safe_exceptions
426 @reraise_safe_exceptions
424 def node_history(self, wire, revision, path, limit):
427 def node_history(self, wire, revision, path, limit):
425 repo = self._factory.repo(wire)
428 repo = self._factory.repo(wire)
426
429
427 ctx = repo[revision]
430 ctx = self._get_ctx(repo, revision)
428 fctx = ctx.filectx(path)
431 fctx = ctx.filectx(path)
429
432
430 def history_iter():
433 def history_iter():
431 limit_rev = fctx.rev()
434 limit_rev = fctx.rev()
432 for obj in reversed(list(fctx.filelog())):
435 for obj in reversed(list(fctx.filelog())):
433 obj = fctx.filectx(obj)
436 obj = fctx.filectx(obj)
434 if limit_rev >= obj.rev():
437 if limit_rev >= obj.rev():
435 yield obj
438 yield obj
436
439
437 history = []
440 history = []
438 for cnt, obj in enumerate(history_iter()):
441 for cnt, obj in enumerate(history_iter()):
439 if limit and cnt >= limit:
442 if limit and cnt >= limit:
440 break
443 break
441 history.append(hex(obj.node()))
444 history.append(hex(obj.node()))
442
445
443 return [x for x in history]
446 return [x for x in history]
444
447
445 @reraise_safe_exceptions
448 @reraise_safe_exceptions
446 def node_history_untill(self, wire, revision, path, limit):
449 def node_history_untill(self, wire, revision, path, limit):
447 repo = self._factory.repo(wire)
450 repo = self._factory.repo(wire)
448 ctx = repo[revision]
451 ctx = self._get_ctx(repo, revision)
449 fctx = ctx.filectx(path)
452 fctx = ctx.filectx(path)
450
453
451 file_log = list(fctx.filelog())
454 file_log = list(fctx.filelog())
452 if limit:
455 if limit:
453 # Limit to the last n items
456 # Limit to the last n items
454 file_log = file_log[-limit:]
457 file_log = file_log[-limit:]
455
458
456 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
459 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
457
460
458 @reraise_safe_exceptions
461 @reraise_safe_exceptions
459 def fctx_annotate(self, wire, revision, path):
462 def fctx_annotate(self, wire, revision, path):
460 repo = self._factory.repo(wire)
463 repo = self._factory.repo(wire)
461 ctx = repo[revision]
464 ctx = self._get_ctx(repo, revision)
462 fctx = ctx.filectx(path)
465 fctx = ctx.filectx(path)
463
466
464 result = []
467 result = []
465 for i, annotate_obj in enumerate(fctx.annotate(), 1):
468 for i, annotate_obj in enumerate(fctx.annotate(), 1):
466 ln_no = i
469 ln_no = i
467 sha = hex(annotate_obj.fctx.node())
470 sha = hex(annotate_obj.fctx.node())
468 content = annotate_obj.text
471 content = annotate_obj.text
469 result.append((ln_no, sha, content))
472 result.append((ln_no, sha, content))
470 return result
473 return result
471
474
472 @reraise_safe_exceptions
475 @reraise_safe_exceptions
473 def fctx_data(self, wire, revision, path):
476 def fctx_data(self, wire, revision, path):
474 repo = self._factory.repo(wire)
477 repo = self._factory.repo(wire)
475 ctx = repo[revision]
478 ctx = self._get_ctx(repo, revision)
476 fctx = ctx.filectx(path)
479 fctx = ctx.filectx(path)
477 return fctx.data()
480 return fctx.data()
478
481
479 @reraise_safe_exceptions
482 @reraise_safe_exceptions
480 def fctx_flags(self, wire, revision, path):
483 def fctx_flags(self, wire, revision, path):
481 repo = self._factory.repo(wire)
484 repo = self._factory.repo(wire)
482 ctx = repo[revision]
485 ctx = self._get_ctx(repo, revision)
483 fctx = ctx.filectx(path)
486 fctx = ctx.filectx(path)
484 return fctx.flags()
487 return fctx.flags()
485
488
486 @reraise_safe_exceptions
489 @reraise_safe_exceptions
487 def fctx_size(self, wire, revision, path):
490 def fctx_size(self, wire, revision, path):
488 repo = self._factory.repo(wire)
491 repo = self._factory.repo(wire)
489 ctx = repo[revision]
492 ctx = self._get_ctx(repo, revision)
490 fctx = ctx.filectx(path)
493 fctx = ctx.filectx(path)
491 return fctx.size()
494 return fctx.size()
492
495
493 @reraise_safe_exceptions
496 @reraise_safe_exceptions
494 def get_all_commit_ids(self, wire, name):
497 def get_all_commit_ids(self, wire, name):
495 repo = self._factory.repo(wire)
498 repo = self._factory.repo(wire)
496 repo = repo.filtered(name)
499 repo = repo.filtered(name)
497 revs = map(lambda x: hex(x[7]), repo.changelog.index)
500 revs = map(lambda x: hex(x[7]), repo.changelog.index)
498 return revs
501 return revs
499
502
500 @reraise_safe_exceptions
503 @reraise_safe_exceptions
501 def get_config_value(self, wire, section, name, untrusted=False):
504 def get_config_value(self, wire, section, name, untrusted=False):
502 repo = self._factory.repo(wire)
505 repo = self._factory.repo(wire)
503 return repo.ui.config(section, name, untrusted=untrusted)
506 return repo.ui.config(section, name, untrusted=untrusted)
504
507
505 @reraise_safe_exceptions
508 @reraise_safe_exceptions
506 def get_config_bool(self, wire, section, name, untrusted=False):
509 def get_config_bool(self, wire, section, name, untrusted=False):
507 repo = self._factory.repo(wire)
510 repo = self._factory.repo(wire)
508 return repo.ui.configbool(section, name, untrusted=untrusted)
511 return repo.ui.configbool(section, name, untrusted=untrusted)
509
512
510 @reraise_safe_exceptions
513 @reraise_safe_exceptions
511 def get_config_list(self, wire, section, name, untrusted=False):
514 def get_config_list(self, wire, section, name, untrusted=False):
512 repo = self._factory.repo(wire)
515 repo = self._factory.repo(wire)
513 return repo.ui.configlist(section, name, untrusted=untrusted)
516 return repo.ui.configlist(section, name, untrusted=untrusted)
514
517
515 @reraise_safe_exceptions
518 @reraise_safe_exceptions
516 def is_large_file(self, wire, path):
519 def is_large_file(self, wire, path):
517 return largefiles.lfutil.isstandin(path)
520 return largefiles.lfutil.isstandin(path)
518
521
519 @reraise_safe_exceptions
522 @reraise_safe_exceptions
520 def in_largefiles_store(self, wire, sha):
523 def in_largefiles_store(self, wire, sha):
521 repo = self._factory.repo(wire)
524 repo = self._factory.repo(wire)
522 return largefiles.lfutil.instore(repo, sha)
525 return largefiles.lfutil.instore(repo, sha)
523
526
524 @reraise_safe_exceptions
527 @reraise_safe_exceptions
525 def in_user_cache(self, wire, sha):
528 def in_user_cache(self, wire, sha):
526 repo = self._factory.repo(wire)
529 repo = self._factory.repo(wire)
527 return largefiles.lfutil.inusercache(repo.ui, sha)
530 return largefiles.lfutil.inusercache(repo.ui, sha)
528
531
529 @reraise_safe_exceptions
532 @reraise_safe_exceptions
530 def store_path(self, wire, sha):
533 def store_path(self, wire, sha):
531 repo = self._factory.repo(wire)
534 repo = self._factory.repo(wire)
532 return largefiles.lfutil.storepath(repo, sha)
535 return largefiles.lfutil.storepath(repo, sha)
533
536
534 @reraise_safe_exceptions
537 @reraise_safe_exceptions
535 def link(self, wire, sha, path):
538 def link(self, wire, sha, path):
536 repo = self._factory.repo(wire)
539 repo = self._factory.repo(wire)
537 largefiles.lfutil.link(
540 largefiles.lfutil.link(
538 largefiles.lfutil.usercachepath(repo.ui, sha), path)
541 largefiles.lfutil.usercachepath(repo.ui, sha), path)
539
542
540 @reraise_safe_exceptions
543 @reraise_safe_exceptions
541 def localrepository(self, wire, create=False):
544 def localrepository(self, wire, create=False):
542 self._factory.repo(wire, create=create)
545 self._factory.repo(wire, create=create)
543
546
544 @reraise_safe_exceptions
547 @reraise_safe_exceptions
545 def lookup(self, wire, revision, both):
548 def lookup(self, wire, revision, both):
546
549
547 repo = self._factory.repo(wire)
550 repo = self._factory.repo(wire)
548
551
549 if isinstance(revision, int):
552 if isinstance(revision, int):
550 # NOTE(marcink):
553 # NOTE(marcink):
551 # since Mercurial doesn't support negative indexes properly
554 # since Mercurial doesn't support negative indexes properly
552 # we need to shift accordingly by one to get proper index, e.g
555 # we need to shift accordingly by one to get proper index, e.g
553 # repo[-1] => repo[-2]
556 # repo[-1] => repo[-2]
554 # repo[0] => repo[-1]
557 # repo[0] => repo[-1]
555 if revision <= 0:
558 if revision <= 0:
556 revision = revision + -1
559 revision = revision + -1
557 try:
560 try:
558 try:
561 ctx = self._get_ctx(repo, revision)
559 ctx = repo[revision]
560 except ProgrammingError:
561 # we're unable to find the rev using a regular lookup, we fallback
562 # to slower, but backward compat revsymbol usage
563 ctx = revsymbol(repo, revision)
564 except (TypeError, RepoLookupError) as e:
562 except (TypeError, RepoLookupError) as e:
565 e._org_exc_tb = traceback.format_exc()
563 e._org_exc_tb = traceback.format_exc()
566 raise exceptions.LookupException(e)(revision)
564 raise exceptions.LookupException(e)(revision)
567 except LookupError as e:
565 except LookupError as e:
568 e._org_exc_tb = traceback.format_exc()
566 e._org_exc_tb = traceback.format_exc()
569 raise exceptions.LookupException(e)(e.name)
567 raise exceptions.LookupException(e)(e.name)
570
568
571 if not both:
569 if not both:
572 return ctx.hex()
570 return ctx.hex()
573
571
574 ctx = repo[ctx.hex()]
572 ctx = repo[ctx.hex()]
575 return ctx.hex(), ctx.rev()
573 return ctx.hex(), ctx.rev()
576
574
577 @reraise_safe_exceptions
575 @reraise_safe_exceptions
578 def pull(self, wire, url, commit_ids=None):
576 def pull(self, wire, url, commit_ids=None):
579 repo = self._factory.repo(wire)
577 repo = self._factory.repo(wire)
580 # Disable any prompts for this repo
578 # Disable any prompts for this repo
581 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
579 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
582
580
583 remote = peer(repo, {}, url)
581 remote = peer(repo, {}, url)
584 # Disable any prompts for this remote
582 # Disable any prompts for this remote
585 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
583 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
586
584
587 if commit_ids:
585 if commit_ids:
588 commit_ids = [bin(commit_id) for commit_id in commit_ids]
586 commit_ids = [bin(commit_id) for commit_id in commit_ids]
589
587
590 return exchange.pull(
588 return exchange.pull(
591 repo, remote, heads=commit_ids, force=None).cgresult
589 repo, remote, heads=commit_ids, force=None).cgresult
592
590
593 @reraise_safe_exceptions
591 @reraise_safe_exceptions
594 def sync_push(self, wire, url):
592 def sync_push(self, wire, url):
595 if not self.check_url(url, wire['config']):
593 if not self.check_url(url, wire['config']):
596 return
594 return
597
595
598 repo = self._factory.repo(wire)
596 repo = self._factory.repo(wire)
599
597
600 # Disable any prompts for this repo
598 # Disable any prompts for this repo
601 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
599 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
602
600
603 bookmarks = dict(repo._bookmarks).keys()
601 bookmarks = dict(repo._bookmarks).keys()
604 remote = peer(repo, {}, url)
602 remote = peer(repo, {}, url)
605 # Disable any prompts for this remote
603 # Disable any prompts for this remote
606 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
604 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
607
605
608 return exchange.push(
606 return exchange.push(
609 repo, remote, newbranch=True, bookmarks=bookmarks).cgresult
607 repo, remote, newbranch=True, bookmarks=bookmarks).cgresult
610
608
611 @reraise_safe_exceptions
609 @reraise_safe_exceptions
612 def revision(self, wire, rev):
610 def revision(self, wire, rev):
613 repo = self._factory.repo(wire)
611 repo = self._factory.repo(wire)
614 ctx = repo[rev]
612 ctx = self._get_ctx(repo, rev)
615 return ctx.rev()
613 return ctx.rev()
616
614
617 @reraise_safe_exceptions
615 @reraise_safe_exceptions
618 def rev_range(self, wire, filter):
616 def rev_range(self, wire, filter):
619 repo = self._factory.repo(wire)
617 repo = self._factory.repo(wire)
620 revisions = [rev for rev in revrange(repo, filter)]
618 revisions = [rev for rev in revrange(repo, filter)]
621 return revisions
619 return revisions
622
620
623 @reraise_safe_exceptions
621 @reraise_safe_exceptions
624 def rev_range_hash(self, wire, node):
622 def rev_range_hash(self, wire, node):
625 repo = self._factory.repo(wire)
623 repo = self._factory.repo(wire)
626
624
627 def get_revs(repo, rev_opt):
625 def get_revs(repo, rev_opt):
628 if rev_opt:
626 if rev_opt:
629 revs = revrange(repo, rev_opt)
627 revs = revrange(repo, rev_opt)
630 if len(revs) == 0:
628 if len(revs) == 0:
631 return (nullrev, nullrev)
629 return (nullrev, nullrev)
632 return max(revs), min(revs)
630 return max(revs), min(revs)
633 else:
631 else:
634 return len(repo) - 1, 0
632 return len(repo) - 1, 0
635
633
636 stop, start = get_revs(repo, [node + ':'])
634 stop, start = get_revs(repo, [node + ':'])
637 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
635 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
638 return revs
636 return revs
639
637
640 @reraise_safe_exceptions
638 @reraise_safe_exceptions
641 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
639 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
642 other_path = kwargs.pop('other_path', None)
640 other_path = kwargs.pop('other_path', None)
643
641
644 # case when we want to compare two independent repositories
642 # case when we want to compare two independent repositories
645 if other_path and other_path != wire["path"]:
643 if other_path and other_path != wire["path"]:
646 baseui = self._factory._create_config(wire["config"])
644 baseui = self._factory._create_config(wire["config"])
647 repo = unionrepo.makeunionrepository(baseui, other_path, wire["path"])
645 repo = unionrepo.makeunionrepository(baseui, other_path, wire["path"])
648 else:
646 else:
649 repo = self._factory.repo(wire)
647 repo = self._factory.repo(wire)
650 return list(repo.revs(rev_spec, *args))
648 return list(repo.revs(rev_spec, *args))
651
649
652 @reraise_safe_exceptions
650 @reraise_safe_exceptions
653 def strip(self, wire, revision, update, backup):
651 def strip(self, wire, revision, update, backup):
654 repo = self._factory.repo(wire)
652 repo = self._factory.repo(wire)
655 ctx = repo[revision]
653 ctx = self._get_ctx(repo, revision)
656 hgext_strip(
654 hgext_strip(
657 repo.baseui, repo, ctx.node(), update=update, backup=backup)
655 repo.baseui, repo, ctx.node(), update=update, backup=backup)
658
656
659 @reraise_safe_exceptions
657 @reraise_safe_exceptions
660 def verify(self, wire,):
658 def verify(self, wire,):
661 repo = self._factory.repo(wire)
659 repo = self._factory.repo(wire)
662 baseui = self._factory._create_config(wire['config'])
660 baseui = self._factory._create_config(wire['config'])
663 baseui.setconfig('ui', 'quiet', 'false')
661 baseui.setconfig('ui', 'quiet', 'false')
664 output = io.BytesIO()
662 output = io.BytesIO()
665
663
666 def write(data, **unused_kwargs):
664 def write(data, **unused_kwargs):
667 output.write(data)
665 output.write(data)
668 baseui.write = write
666 baseui.write = write
669
667
670 repo.ui = baseui
668 repo.ui = baseui
671 verify.verify(repo)
669 verify.verify(repo)
672 return output.getvalue()
670 return output.getvalue()
673
671
674 @reraise_safe_exceptions
672 @reraise_safe_exceptions
675 def tag(self, wire, name, revision, message, local, user,
673 def tag(self, wire, name, revision, message, local, user,
676 tag_time, tag_timezone):
674 tag_time, tag_timezone):
677 repo = self._factory.repo(wire)
675 repo = self._factory.repo(wire)
678 ctx = repo[revision]
676 ctx = self._get_ctx(repo, revision)
679 node = ctx.node()
677 node = ctx.node()
680
678
681 date = (tag_time, tag_timezone)
679 date = (tag_time, tag_timezone)
682 try:
680 try:
683 hg_tag.tag(repo, name, node, message, local, user, date)
681 hg_tag.tag(repo, name, node, message, local, user, date)
684 except Abort as e:
682 except Abort as e:
685 log.exception("Tag operation aborted")
683 log.exception("Tag operation aborted")
686 # Exception can contain unicode which we convert
684 # Exception can contain unicode which we convert
687 raise exceptions.AbortException(e)(repr(e))
685 raise exceptions.AbortException(e)(repr(e))
688
686
689 @reraise_safe_exceptions
687 @reraise_safe_exceptions
690 def tags(self, wire):
688 def tags(self, wire):
691 repo = self._factory.repo(wire)
689 repo = self._factory.repo(wire)
692 return repo.tags()
690 return repo.tags()
693
691
694 @reraise_safe_exceptions
692 @reraise_safe_exceptions
695 def update(self, wire, node=None, clean=False):
693 def update(self, wire, node=None, clean=False):
696 repo = self._factory.repo(wire)
694 repo = self._factory.repo(wire)
697 baseui = self._factory._create_config(wire['config'])
695 baseui = self._factory._create_config(wire['config'])
698 commands.update(baseui, repo, node=node, clean=clean)
696 commands.update(baseui, repo, node=node, clean=clean)
699
697
700 @reraise_safe_exceptions
698 @reraise_safe_exceptions
701 def identify(self, wire):
699 def identify(self, wire):
702 repo = self._factory.repo(wire)
700 repo = self._factory.repo(wire)
703 baseui = self._factory._create_config(wire['config'])
701 baseui = self._factory._create_config(wire['config'])
704 output = io.BytesIO()
702 output = io.BytesIO()
705 baseui.write = output.write
703 baseui.write = output.write
706 # This is required to get a full node id
704 # This is required to get a full node id
707 baseui.debugflag = True
705 baseui.debugflag = True
708 commands.identify(baseui, repo, id=True)
706 commands.identify(baseui, repo, id=True)
709
707
710 return output.getvalue()
708 return output.getvalue()
711
709
712 @reraise_safe_exceptions
710 @reraise_safe_exceptions
713 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
711 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
714 hooks=True):
712 hooks=True):
715 repo = self._factory.repo(wire)
713 repo = self._factory.repo(wire)
716 baseui = self._factory._create_config(wire['config'], hooks=hooks)
714 baseui = self._factory._create_config(wire['config'], hooks=hooks)
717
715
718 # Mercurial internally has a lot of logic that checks ONLY if
716 # Mercurial internally has a lot of logic that checks ONLY if
719 # option is defined, we just pass those if they are defined then
717 # option is defined, we just pass those if they are defined then
720 opts = {}
718 opts = {}
721 if bookmark:
719 if bookmark:
722 opts['bookmark'] = bookmark
720 opts['bookmark'] = bookmark
723 if branch:
721 if branch:
724 opts['branch'] = branch
722 opts['branch'] = branch
725 if revision:
723 if revision:
726 opts['rev'] = revision
724 opts['rev'] = revision
727
725
728 commands.pull(baseui, repo, source, **opts)
726 commands.pull(baseui, repo, source, **opts)
729
727
730 @reraise_safe_exceptions
728 @reraise_safe_exceptions
731 def heads(self, wire, branch=None):
729 def heads(self, wire, branch=None):
732 repo = self._factory.repo(wire)
730 repo = self._factory.repo(wire)
733 baseui = self._factory._create_config(wire['config'])
731 baseui = self._factory._create_config(wire['config'])
734 output = io.BytesIO()
732 output = io.BytesIO()
735
733
736 def write(data, **unused_kwargs):
734 def write(data, **unused_kwargs):
737 output.write(data)
735 output.write(data)
738
736
739 baseui.write = write
737 baseui.write = write
740 if branch:
738 if branch:
741 args = [branch]
739 args = [branch]
742 else:
740 else:
743 args = []
741 args = []
744 commands.heads(baseui, repo, template='{node} ', *args)
742 commands.heads(baseui, repo, template='{node} ', *args)
745
743
746 return output.getvalue()
744 return output.getvalue()
747
745
748 @reraise_safe_exceptions
746 @reraise_safe_exceptions
749 def ancestor(self, wire, revision1, revision2):
747 def ancestor(self, wire, revision1, revision2):
750 repo = self._factory.repo(wire)
748 repo = self._factory.repo(wire)
751 changelog = repo.changelog
749 changelog = repo.changelog
752 lookup = repo.lookup
750 lookup = repo.lookup
753 a = changelog.ancestor(lookup(revision1), lookup(revision2))
751 a = changelog.ancestor(lookup(revision1), lookup(revision2))
754 return hex(a)
752 return hex(a)
755
753
756 @reraise_safe_exceptions
754 @reraise_safe_exceptions
757 def push(self, wire, revisions, dest_path, hooks=True,
755 def push(self, wire, revisions, dest_path, hooks=True,
758 push_branches=False):
756 push_branches=False):
759 repo = self._factory.repo(wire)
757 repo = self._factory.repo(wire)
760 baseui = self._factory._create_config(wire['config'], hooks=hooks)
758 baseui = self._factory._create_config(wire['config'], hooks=hooks)
761 commands.push(baseui, repo, dest=dest_path, rev=revisions,
759 commands.push(baseui, repo, dest=dest_path, rev=revisions,
762 new_branch=push_branches)
760 new_branch=push_branches)
763
761
764 @reraise_safe_exceptions
762 @reraise_safe_exceptions
765 def merge(self, wire, revision):
763 def merge(self, wire, revision):
766 repo = self._factory.repo(wire)
764 repo = self._factory.repo(wire)
767 baseui = self._factory._create_config(wire['config'])
765 baseui = self._factory._create_config(wire['config'])
768 repo.ui.setconfig('ui', 'merge', 'internal:dump')
766 repo.ui.setconfig('ui', 'merge', 'internal:dump')
769
767
770 # In case of sub repositories are used mercurial prompts the user in
768 # In case of sub repositories are used mercurial prompts the user in
771 # case of merge conflicts or different sub repository sources. By
769 # case of merge conflicts or different sub repository sources. By
772 # setting the interactive flag to `False` mercurial doesn't prompt the
770 # setting the interactive flag to `False` mercurial doesn't prompt the
773 # used but instead uses a default value.
771 # used but instead uses a default value.
774 repo.ui.setconfig('ui', 'interactive', False)
772 repo.ui.setconfig('ui', 'interactive', False)
775
773
776 commands.merge(baseui, repo, rev=revision)
774 commands.merge(baseui, repo, rev=revision)
777
775
778 @reraise_safe_exceptions
776 @reraise_safe_exceptions
779 def commit(self, wire, message, username, close_branch=False):
777 def commit(self, wire, message, username, close_branch=False):
780 repo = self._factory.repo(wire)
778 repo = self._factory.repo(wire)
781 baseui = self._factory._create_config(wire['config'])
779 baseui = self._factory._create_config(wire['config'])
782 repo.ui.setconfig('ui', 'username', username)
780 repo.ui.setconfig('ui', 'username', username)
783 commands.commit(baseui, repo, message=message, close_branch=close_branch)
781 commands.commit(baseui, repo, message=message, close_branch=close_branch)
784
782
785 @reraise_safe_exceptions
783 @reraise_safe_exceptions
786 def rebase(self, wire, source=None, dest=None, abort=False):
784 def rebase(self, wire, source=None, dest=None, abort=False):
787 repo = self._factory.repo(wire)
785 repo = self._factory.repo(wire)
788 baseui = self._factory._create_config(wire['config'])
786 baseui = self._factory._create_config(wire['config'])
789 repo.ui.setconfig('ui', 'merge', 'internal:dump')
787 repo.ui.setconfig('ui', 'merge', 'internal:dump')
790 rebase.rebase(
788 rebase.rebase(
791 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
789 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
792
790
793 @reraise_safe_exceptions
791 @reraise_safe_exceptions
794 def bookmark(self, wire, bookmark, revision=None):
792 def bookmark(self, wire, bookmark, revision=None):
795 repo = self._factory.repo(wire)
793 repo = self._factory.repo(wire)
796 baseui = self._factory._create_config(wire['config'])
794 baseui = self._factory._create_config(wire['config'])
797 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
795 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
798
796
799 @reraise_safe_exceptions
797 @reraise_safe_exceptions
800 def install_hooks(self, wire, force=False):
798 def install_hooks(self, wire, force=False):
801 # we don't need any special hooks for Mercurial
799 # we don't need any special hooks for Mercurial
802 pass
800 pass
803
801
804 @reraise_safe_exceptions
802 @reraise_safe_exceptions
805 def get_hooks_info(self, wire):
803 def get_hooks_info(self, wire):
806 return {
804 return {
807 'pre_version': vcsserver.__version__,
805 'pre_version': vcsserver.__version__,
808 'post_version': vcsserver.__version__,
806 'post_version': vcsserver.__version__,
809 }
807 }
@@ -1,63 +1,74 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-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 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 """
18 """
19 Mercurial libs compatibility
19 Mercurial libs compatibility
20 """
20 """
21
21
22 import mercurial
22 import mercurial
23 from mercurial import demandimport
23 from mercurial import demandimport
24 # patch demandimport, due to bug in mercurial when it always triggers
24 # patch demandimport, due to bug in mercurial when it always triggers
25 # demandimport.enable()
25 # demandimport.enable()
26 demandimport.enable = lambda *args, **kwargs: 1
26 demandimport.enable = lambda *args, **kwargs: 1
27
27
28 from mercurial import ui
28 from mercurial import ui
29 from mercurial import patch
29 from mercurial import patch
30 from mercurial import config
30 from mercurial import config
31 from mercurial import extensions
31 from mercurial import extensions
32 from mercurial import scmutil
32 from mercurial import scmutil
33 from mercurial import archival
33 from mercurial import archival
34 from mercurial import discovery
34 from mercurial import discovery
35 from mercurial import unionrepo
35 from mercurial import unionrepo
36 from mercurial import localrepo
36 from mercurial import localrepo
37 from mercurial import merge as hg_merge
37 from mercurial import merge as hg_merge
38 from mercurial import subrepo
38 from mercurial import subrepo
39 from mercurial import tags as hg_tag
39 from mercurial import tags as hg_tag
40
40
41 from mercurial.commands import clone, nullid, pull
41 from mercurial.commands import clone, nullid, pull
42 from mercurial.context import memctx, memfilectx
42 from mercurial.context import memctx, memfilectx
43 from mercurial.error import (
43 from mercurial.error import (
44 LookupError, RepoError, RepoLookupError, Abort, InterventionRequired,
44 LookupError, RepoError, RepoLookupError, Abort, InterventionRequired,
45 RequirementError, ProgrammingError)
45 RequirementError, ProgrammingError)
46 from mercurial.hgweb import hgweb_mod
46 from mercurial.hgweb import hgweb_mod
47 from mercurial.localrepo import instance
47 from mercurial.localrepo import instance
48 from mercurial.match import match
48 from mercurial.match import match
49 from mercurial.mdiff import diffopts
49 from mercurial.mdiff import diffopts
50 from mercurial.node import bin, hex
50 from mercurial.node import bin, hex
51 from mercurial.encoding import tolocal
51 from mercurial.encoding import tolocal
52 from mercurial.discovery import findcommonoutgoing
52 from mercurial.discovery import findcommonoutgoing
53 from mercurial.hg import peer
53 from mercurial.hg import peer
54 from mercurial.httppeer import makepeer
54 from mercurial.httppeer import makepeer
55 from mercurial.util import url as hg_url
55 from mercurial.util import url as hg_url
56 from mercurial.scmutil import revrange, revsymbol
56 from mercurial.scmutil import revrange, revsymbol
57 from mercurial.node import nullrev
57 from mercurial.node import nullrev
58 from mercurial import exchange
58 from mercurial import exchange
59 from hgext import largefiles
59 from hgext import largefiles
60
60
61 # those authnadlers are patched for python 2.6.5 bug an
61 # those authnadlers are patched for python 2.6.5 bug an
62 # infinit looping when given invalid resources
62 # infinit looping when given invalid resources
63 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
63 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
64
65
66 def get_ctx(repo, ref):
67 try:
68 ctx = repo[ref]
69 except ProgrammingError:
70 # we're unable to find the rev using a regular lookup, we fallback
71 # to slower, but backward compat revsymbol usage
72 ctx = revsymbol(repo, ref)
73
74 return ctx
@@ -1,710 +1,711 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # RhodeCode VCSServer provides access to different vcs backends via network.
3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 # Copyright (C) 2014-2019 RhodeCode GmbH
4 # Copyright (C) 2014-2019 RhodeCode GmbH
5 #
5 #
6 # This program is free software; you can redistribute it and/or modify
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
9 # (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
20 import io
20 import io
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import importlib
25 import importlib
26 import base64
26 import base64
27
27
28 from httplib import HTTPConnection
28 from httplib import HTTPConnection
29
29
30
30
31 import mercurial.scmutil
31 import mercurial.scmutil
32 import mercurial.node
32 import mercurial.node
33 import simplejson as json
33 import simplejson as json
34
34
35 from vcsserver import exceptions, subprocessio, settings
35 from vcsserver import exceptions, subprocessio, settings
36 from vcsserver.hgcompat import get_ctx
36
37
37 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
38
39
39
40
40 class HooksHttpClient(object):
41 class HooksHttpClient(object):
41 connection = None
42 connection = None
42
43
43 def __init__(self, hooks_uri):
44 def __init__(self, hooks_uri):
44 self.hooks_uri = hooks_uri
45 self.hooks_uri = hooks_uri
45
46
46 def __call__(self, method, extras):
47 def __call__(self, method, extras):
47 connection = HTTPConnection(self.hooks_uri)
48 connection = HTTPConnection(self.hooks_uri)
48 body = self._serialize(method, extras)
49 body = self._serialize(method, extras)
49 try:
50 try:
50 connection.request('POST', '/', body)
51 connection.request('POST', '/', body)
51 except Exception:
52 except Exception:
52 log.error('Connection failed on %s', connection)
53 log.error('Connection failed on %s', connection)
53 raise
54 raise
54 response = connection.getresponse()
55 response = connection.getresponse()
55
56
56 response_data = response.read()
57 response_data = response.read()
57
58
58 try:
59 try:
59 return json.loads(response_data)
60 return json.loads(response_data)
60 except Exception:
61 except Exception:
61 log.exception('Failed to decode hook response json data. '
62 log.exception('Failed to decode hook response json data. '
62 'response_code:%s, raw_data:%s',
63 'response_code:%s, raw_data:%s',
63 response.status, response_data)
64 response.status, response_data)
64 raise
65 raise
65
66
66 def _serialize(self, hook_name, extras):
67 def _serialize(self, hook_name, extras):
67 data = {
68 data = {
68 'method': hook_name,
69 'method': hook_name,
69 'extras': extras
70 'extras': extras
70 }
71 }
71 return json.dumps(data)
72 return json.dumps(data)
72
73
73
74
74 class HooksDummyClient(object):
75 class HooksDummyClient(object):
75 def __init__(self, hooks_module):
76 def __init__(self, hooks_module):
76 self._hooks_module = importlib.import_module(hooks_module)
77 self._hooks_module = importlib.import_module(hooks_module)
77
78
78 def __call__(self, hook_name, extras):
79 def __call__(self, hook_name, extras):
79 with self._hooks_module.Hooks() as hooks:
80 with self._hooks_module.Hooks() as hooks:
80 return getattr(hooks, hook_name)(extras)
81 return getattr(hooks, hook_name)(extras)
81
82
82
83
83 class RemoteMessageWriter(object):
84 class RemoteMessageWriter(object):
84 """Writer base class."""
85 """Writer base class."""
85 def write(self, message):
86 def write(self, message):
86 raise NotImplementedError()
87 raise NotImplementedError()
87
88
88
89
89 class HgMessageWriter(RemoteMessageWriter):
90 class HgMessageWriter(RemoteMessageWriter):
90 """Writer that knows how to send messages to mercurial clients."""
91 """Writer that knows how to send messages to mercurial clients."""
91
92
92 def __init__(self, ui):
93 def __init__(self, ui):
93 self.ui = ui
94 self.ui = ui
94
95
95 def write(self, message):
96 def write(self, message):
96 # TODO: Check why the quiet flag is set by default.
97 # TODO: Check why the quiet flag is set by default.
97 old = self.ui.quiet
98 old = self.ui.quiet
98 self.ui.quiet = False
99 self.ui.quiet = False
99 self.ui.status(message.encode('utf-8'))
100 self.ui.status(message.encode('utf-8'))
100 self.ui.quiet = old
101 self.ui.quiet = old
101
102
102
103
103 class GitMessageWriter(RemoteMessageWriter):
104 class GitMessageWriter(RemoteMessageWriter):
104 """Writer that knows how to send messages to git clients."""
105 """Writer that knows how to send messages to git clients."""
105
106
106 def __init__(self, stdout=None):
107 def __init__(self, stdout=None):
107 self.stdout = stdout or sys.stdout
108 self.stdout = stdout or sys.stdout
108
109
109 def write(self, message):
110 def write(self, message):
110 self.stdout.write(message.encode('utf-8'))
111 self.stdout.write(message.encode('utf-8'))
111
112
112
113
113 class SvnMessageWriter(RemoteMessageWriter):
114 class SvnMessageWriter(RemoteMessageWriter):
114 """Writer that knows how to send messages to svn clients."""
115 """Writer that knows how to send messages to svn clients."""
115
116
116 def __init__(self, stderr=None):
117 def __init__(self, stderr=None):
117 # SVN needs data sent to stderr for back-to-client messaging
118 # SVN needs data sent to stderr for back-to-client messaging
118 self.stderr = stderr or sys.stderr
119 self.stderr = stderr or sys.stderr
119
120
120 def write(self, message):
121 def write(self, message):
121 self.stderr.write(message.encode('utf-8'))
122 self.stderr.write(message.encode('utf-8'))
122
123
123
124
124 def _handle_exception(result):
125 def _handle_exception(result):
125 exception_class = result.get('exception')
126 exception_class = result.get('exception')
126 exception_traceback = result.get('exception_traceback')
127 exception_traceback = result.get('exception_traceback')
127
128
128 if exception_traceback:
129 if exception_traceback:
129 log.error('Got traceback from remote call:%s', exception_traceback)
130 log.error('Got traceback from remote call:%s', exception_traceback)
130
131
131 if exception_class == 'HTTPLockedRC':
132 if exception_class == 'HTTPLockedRC':
132 raise exceptions.RepositoryLockedException()(*result['exception_args'])
133 raise exceptions.RepositoryLockedException()(*result['exception_args'])
133 elif exception_class == 'HTTPBranchProtected':
134 elif exception_class == 'HTTPBranchProtected':
134 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
135 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
135 elif exception_class == 'RepositoryError':
136 elif exception_class == 'RepositoryError':
136 raise exceptions.VcsException()(*result['exception_args'])
137 raise exceptions.VcsException()(*result['exception_args'])
137 elif exception_class:
138 elif exception_class:
138 raise Exception('Got remote exception "%s" with args "%s"' %
139 raise Exception('Got remote exception "%s" with args "%s"' %
139 (exception_class, result['exception_args']))
140 (exception_class, result['exception_args']))
140
141
141
142
142 def _get_hooks_client(extras):
143 def _get_hooks_client(extras):
143 if 'hooks_uri' in extras:
144 if 'hooks_uri' in extras:
144 protocol = extras.get('hooks_protocol')
145 protocol = extras.get('hooks_protocol')
145 return HooksHttpClient(extras['hooks_uri'])
146 return HooksHttpClient(extras['hooks_uri'])
146 else:
147 else:
147 return HooksDummyClient(extras['hooks_module'])
148 return HooksDummyClient(extras['hooks_module'])
148
149
149
150
150 def _call_hook(hook_name, extras, writer):
151 def _call_hook(hook_name, extras, writer):
151 hooks_client = _get_hooks_client(extras)
152 hooks_client = _get_hooks_client(extras)
152 log.debug('Hooks, using client:%s', hooks_client)
153 log.debug('Hooks, using client:%s', hooks_client)
153 result = hooks_client(hook_name, extras)
154 result = hooks_client(hook_name, extras)
154 log.debug('Hooks got result: %s', result)
155 log.debug('Hooks got result: %s', result)
155
156
156 _handle_exception(result)
157 _handle_exception(result)
157 writer.write(result['output'])
158 writer.write(result['output'])
158
159
159 return result['status']
160 return result['status']
160
161
161
162
162 def _extras_from_ui(ui):
163 def _extras_from_ui(ui):
163 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
164 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
164 if not hook_data:
165 if not hook_data:
165 # maybe it's inside environ ?
166 # maybe it's inside environ ?
166 env_hook_data = os.environ.get('RC_SCM_DATA')
167 env_hook_data = os.environ.get('RC_SCM_DATA')
167 if env_hook_data:
168 if env_hook_data:
168 hook_data = env_hook_data
169 hook_data = env_hook_data
169
170
170 extras = {}
171 extras = {}
171 if hook_data:
172 if hook_data:
172 extras = json.loads(hook_data)
173 extras = json.loads(hook_data)
173 return extras
174 return extras
174
175
175
176
176 def _rev_range_hash(repo, node, check_heads=False):
177 def _rev_range_hash(repo, node, check_heads=False):
177
178
178 commits = []
179 commits = []
179 revs = []
180 revs = []
180 start = repo[node].rev()
181 start = get_ctx(repo, node).rev()
181 end = len(repo)
182 end = len(repo)
182 for rev in range(start, end):
183 for rev in range(start, end):
183 revs.append(rev)
184 revs.append(rev)
184 ctx = repo[rev]
185 ctx = get_ctx(repo, rev)
185 commit_id = mercurial.node.hex(ctx.node())
186 commit_id = mercurial.node.hex(ctx.node())
186 branch = ctx.branch()
187 branch = ctx.branch()
187 commits.append((commit_id, branch))
188 commits.append((commit_id, branch))
188
189
189 parent_heads = []
190 parent_heads = []
190 if check_heads:
191 if check_heads:
191 parent_heads = _check_heads(repo, start, end, revs)
192 parent_heads = _check_heads(repo, start, end, revs)
192 return commits, parent_heads
193 return commits, parent_heads
193
194
194
195
195 def _check_heads(repo, start, end, commits):
196 def _check_heads(repo, start, end, commits):
196 changelog = repo.changelog
197 changelog = repo.changelog
197 parents = set()
198 parents = set()
198
199
199 for new_rev in commits:
200 for new_rev in commits:
200 for p in changelog.parentrevs(new_rev):
201 for p in changelog.parentrevs(new_rev):
201 if p == mercurial.node.nullrev:
202 if p == mercurial.node.nullrev:
202 continue
203 continue
203 if p < start:
204 if p < start:
204 parents.add(p)
205 parents.add(p)
205
206
206 for p in parents:
207 for p in parents:
207 branch = repo[p].branch()
208 branch = get_ctx(repo, p).branch()
208 # The heads descending from that parent, on the same branch
209 # The heads descending from that parent, on the same branch
209 parent_heads = set([p])
210 parent_heads = set([p])
210 reachable = set([p])
211 reachable = set([p])
211 for x in xrange(p + 1, end):
212 for x in xrange(p + 1, end):
212 if repo[x].branch() != branch:
213 if get_ctx(repo, x).branch() != branch:
213 continue
214 continue
214 for pp in changelog.parentrevs(x):
215 for pp in changelog.parentrevs(x):
215 if pp in reachable:
216 if pp in reachable:
216 reachable.add(x)
217 reachable.add(x)
217 parent_heads.discard(pp)
218 parent_heads.discard(pp)
218 parent_heads.add(x)
219 parent_heads.add(x)
219 # More than one head? Suggest merging
220 # More than one head? Suggest merging
220 if len(parent_heads) > 1:
221 if len(parent_heads) > 1:
221 return list(parent_heads)
222 return list(parent_heads)
222
223
223 return []
224 return []
224
225
225
226
226 def _get_git_env():
227 def _get_git_env():
227 env = {}
228 env = {}
228 for k, v in os.environ.items():
229 for k, v in os.environ.items():
229 if k.startswith('GIT'):
230 if k.startswith('GIT'):
230 env[k] = v
231 env[k] = v
231
232
232 # serialized version
233 # serialized version
233 return [(k, v) for k, v in env.items()]
234 return [(k, v) for k, v in env.items()]
234
235
235
236
236 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
237 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
237 env = {}
238 env = {}
238 for k, v in os.environ.items():
239 for k, v in os.environ.items():
239 if k.startswith('HG'):
240 if k.startswith('HG'):
240 env[k] = v
241 env[k] = v
241
242
242 env['HG_NODE'] = old_rev
243 env['HG_NODE'] = old_rev
243 env['HG_NODE_LAST'] = new_rev
244 env['HG_NODE_LAST'] = new_rev
244 env['HG_TXNID'] = txnid
245 env['HG_TXNID'] = txnid
245 env['HG_PENDING'] = repo_path
246 env['HG_PENDING'] = repo_path
246
247
247 return [(k, v) for k, v in env.items()]
248 return [(k, v) for k, v in env.items()]
248
249
249
250
250 def repo_size(ui, repo, **kwargs):
251 def repo_size(ui, repo, **kwargs):
251 extras = _extras_from_ui(ui)
252 extras = _extras_from_ui(ui)
252 return _call_hook('repo_size', extras, HgMessageWriter(ui))
253 return _call_hook('repo_size', extras, HgMessageWriter(ui))
253
254
254
255
255 def pre_pull(ui, repo, **kwargs):
256 def pre_pull(ui, repo, **kwargs):
256 extras = _extras_from_ui(ui)
257 extras = _extras_from_ui(ui)
257 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
258 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
258
259
259
260
260 def pre_pull_ssh(ui, repo, **kwargs):
261 def pre_pull_ssh(ui, repo, **kwargs):
261 extras = _extras_from_ui(ui)
262 extras = _extras_from_ui(ui)
262 if extras and extras.get('SSH'):
263 if extras and extras.get('SSH'):
263 return pre_pull(ui, repo, **kwargs)
264 return pre_pull(ui, repo, **kwargs)
264 return 0
265 return 0
265
266
266
267
267 def post_pull(ui, repo, **kwargs):
268 def post_pull(ui, repo, **kwargs):
268 extras = _extras_from_ui(ui)
269 extras = _extras_from_ui(ui)
269 return _call_hook('post_pull', extras, HgMessageWriter(ui))
270 return _call_hook('post_pull', extras, HgMessageWriter(ui))
270
271
271
272
272 def post_pull_ssh(ui, repo, **kwargs):
273 def post_pull_ssh(ui, repo, **kwargs):
273 extras = _extras_from_ui(ui)
274 extras = _extras_from_ui(ui)
274 if extras and extras.get('SSH'):
275 if extras and extras.get('SSH'):
275 return post_pull(ui, repo, **kwargs)
276 return post_pull(ui, repo, **kwargs)
276 return 0
277 return 0
277
278
278
279
279 def pre_push(ui, repo, node=None, **kwargs):
280 def pre_push(ui, repo, node=None, **kwargs):
280 """
281 """
281 Mercurial pre_push hook
282 Mercurial pre_push hook
282 """
283 """
283 extras = _extras_from_ui(ui)
284 extras = _extras_from_ui(ui)
284 detect_force_push = extras.get('detect_force_push')
285 detect_force_push = extras.get('detect_force_push')
285
286
286 rev_data = []
287 rev_data = []
287 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
288 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
288 branches = collections.defaultdict(list)
289 branches = collections.defaultdict(list)
289 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
290 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
290 for commit_id, branch in commits:
291 for commit_id, branch in commits:
291 branches[branch].append(commit_id)
292 branches[branch].append(commit_id)
292
293
293 for branch, commits in branches.items():
294 for branch, commits in branches.items():
294 old_rev = kwargs.get('node_last') or commits[0]
295 old_rev = kwargs.get('node_last') or commits[0]
295 rev_data.append({
296 rev_data.append({
296 'total_commits': len(commits),
297 'total_commits': len(commits),
297 'old_rev': old_rev,
298 'old_rev': old_rev,
298 'new_rev': commits[-1],
299 'new_rev': commits[-1],
299 'ref': '',
300 'ref': '',
300 'type': 'branch',
301 'type': 'branch',
301 'name': branch,
302 'name': branch,
302 })
303 })
303
304
304 for push_ref in rev_data:
305 for push_ref in rev_data:
305 push_ref['multiple_heads'] = _heads
306 push_ref['multiple_heads'] = _heads
306
307
307 repo_path = os.path.join(
308 repo_path = os.path.join(
308 extras.get('repo_store', ''), extras.get('repository', ''))
309 extras.get('repo_store', ''), extras.get('repository', ''))
309 push_ref['hg_env'] = _get_hg_env(
310 push_ref['hg_env'] = _get_hg_env(
310 old_rev=push_ref['old_rev'],
311 old_rev=push_ref['old_rev'],
311 new_rev=push_ref['new_rev'], txnid=kwargs.get('txnid'),
312 new_rev=push_ref['new_rev'], txnid=kwargs.get('txnid'),
312 repo_path=repo_path)
313 repo_path=repo_path)
313
314
314 extras['hook_type'] = kwargs.get('hooktype', 'pre_push')
315 extras['hook_type'] = kwargs.get('hooktype', 'pre_push')
315 extras['commit_ids'] = rev_data
316 extras['commit_ids'] = rev_data
316
317
317 return _call_hook('pre_push', extras, HgMessageWriter(ui))
318 return _call_hook('pre_push', extras, HgMessageWriter(ui))
318
319
319
320
320 def pre_push_ssh(ui, repo, node=None, **kwargs):
321 def pre_push_ssh(ui, repo, node=None, **kwargs):
321 extras = _extras_from_ui(ui)
322 extras = _extras_from_ui(ui)
322 if extras.get('SSH'):
323 if extras.get('SSH'):
323 return pre_push(ui, repo, node, **kwargs)
324 return pre_push(ui, repo, node, **kwargs)
324
325
325 return 0
326 return 0
326
327
327
328
328 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
329 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
329 """
330 """
330 Mercurial pre_push hook for SSH
331 Mercurial pre_push hook for SSH
331 """
332 """
332 extras = _extras_from_ui(ui)
333 extras = _extras_from_ui(ui)
333 if extras.get('SSH'):
334 if extras.get('SSH'):
334 permission = extras['SSH_PERMISSIONS']
335 permission = extras['SSH_PERMISSIONS']
335
336
336 if 'repository.write' == permission or 'repository.admin' == permission:
337 if 'repository.write' == permission or 'repository.admin' == permission:
337 return 0
338 return 0
338
339
339 # non-zero ret code
340 # non-zero ret code
340 return 1
341 return 1
341
342
342 return 0
343 return 0
343
344
344
345
345 def post_push(ui, repo, node, **kwargs):
346 def post_push(ui, repo, node, **kwargs):
346 """
347 """
347 Mercurial post_push hook
348 Mercurial post_push hook
348 """
349 """
349 extras = _extras_from_ui(ui)
350 extras = _extras_from_ui(ui)
350
351
351 commit_ids = []
352 commit_ids = []
352 branches = []
353 branches = []
353 bookmarks = []
354 bookmarks = []
354 tags = []
355 tags = []
355
356
356 commits, _heads = _rev_range_hash(repo, node)
357 commits, _heads = _rev_range_hash(repo, node)
357 for commit_id, branch in commits:
358 for commit_id, branch in commits:
358 commit_ids.append(commit_id)
359 commit_ids.append(commit_id)
359 if branch not in branches:
360 if branch not in branches:
360 branches.append(branch)
361 branches.append(branch)
361
362
362 if hasattr(ui, '_rc_pushkey_branches'):
363 if hasattr(ui, '_rc_pushkey_branches'):
363 bookmarks = ui._rc_pushkey_branches
364 bookmarks = ui._rc_pushkey_branches
364
365
365 extras['hook_type'] = kwargs.get('hooktype', 'post_push')
366 extras['hook_type'] = kwargs.get('hooktype', 'post_push')
366 extras['commit_ids'] = commit_ids
367 extras['commit_ids'] = commit_ids
367 extras['new_refs'] = {
368 extras['new_refs'] = {
368 'branches': branches,
369 'branches': branches,
369 'bookmarks': bookmarks,
370 'bookmarks': bookmarks,
370 'tags': tags
371 'tags': tags
371 }
372 }
372
373
373 return _call_hook('post_push', extras, HgMessageWriter(ui))
374 return _call_hook('post_push', extras, HgMessageWriter(ui))
374
375
375
376
376 def post_push_ssh(ui, repo, node, **kwargs):
377 def post_push_ssh(ui, repo, node, **kwargs):
377 """
378 """
378 Mercurial post_push hook for SSH
379 Mercurial post_push hook for SSH
379 """
380 """
380 if _extras_from_ui(ui).get('SSH'):
381 if _extras_from_ui(ui).get('SSH'):
381 return post_push(ui, repo, node, **kwargs)
382 return post_push(ui, repo, node, **kwargs)
382 return 0
383 return 0
383
384
384
385
385 def key_push(ui, repo, **kwargs):
386 def key_push(ui, repo, **kwargs):
386 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
387 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
387 # store new bookmarks in our UI object propagated later to post_push
388 # store new bookmarks in our UI object propagated later to post_push
388 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
389 ui._rc_pushkey_branches = get_ctx(repo, kwargs['key']).bookmarks()
389 return
390 return
390
391
391
392
392 # backward compat
393 # backward compat
393 log_pull_action = post_pull
394 log_pull_action = post_pull
394
395
395 # backward compat
396 # backward compat
396 log_push_action = post_push
397 log_push_action = post_push
397
398
398
399
399 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
400 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
400 """
401 """
401 Old hook name: keep here for backward compatibility.
402 Old hook name: keep here for backward compatibility.
402
403
403 This is only required when the installed git hooks are not upgraded.
404 This is only required when the installed git hooks are not upgraded.
404 """
405 """
405 pass
406 pass
406
407
407
408
408 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
409 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
409 """
410 """
410 Old hook name: keep here for backward compatibility.
411 Old hook name: keep here for backward compatibility.
411
412
412 This is only required when the installed git hooks are not upgraded.
413 This is only required when the installed git hooks are not upgraded.
413 """
414 """
414 pass
415 pass
415
416
416
417
417 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
418 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
418
419
419
420
420 def git_pre_pull(extras):
421 def git_pre_pull(extras):
421 """
422 """
422 Pre pull hook.
423 Pre pull hook.
423
424
424 :param extras: dictionary containing the keys defined in simplevcs
425 :param extras: dictionary containing the keys defined in simplevcs
425 :type extras: dict
426 :type extras: dict
426
427
427 :return: status code of the hook. 0 for success.
428 :return: status code of the hook. 0 for success.
428 :rtype: int
429 :rtype: int
429 """
430 """
430 if 'pull' not in extras['hooks']:
431 if 'pull' not in extras['hooks']:
431 return HookResponse(0, '')
432 return HookResponse(0, '')
432
433
433 stdout = io.BytesIO()
434 stdout = io.BytesIO()
434 try:
435 try:
435 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
436 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
436 except Exception as error:
437 except Exception as error:
437 status = 128
438 status = 128
438 stdout.write('ERROR: %s\n' % str(error))
439 stdout.write('ERROR: %s\n' % str(error))
439
440
440 return HookResponse(status, stdout.getvalue())
441 return HookResponse(status, stdout.getvalue())
441
442
442
443
443 def git_post_pull(extras):
444 def git_post_pull(extras):
444 """
445 """
445 Post pull hook.
446 Post pull hook.
446
447
447 :param extras: dictionary containing the keys defined in simplevcs
448 :param extras: dictionary containing the keys defined in simplevcs
448 :type extras: dict
449 :type extras: dict
449
450
450 :return: status code of the hook. 0 for success.
451 :return: status code of the hook. 0 for success.
451 :rtype: int
452 :rtype: int
452 """
453 """
453 if 'pull' not in extras['hooks']:
454 if 'pull' not in extras['hooks']:
454 return HookResponse(0, '')
455 return HookResponse(0, '')
455
456
456 stdout = io.BytesIO()
457 stdout = io.BytesIO()
457 try:
458 try:
458 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
459 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
459 except Exception as error:
460 except Exception as error:
460 status = 128
461 status = 128
461 stdout.write('ERROR: %s\n' % error)
462 stdout.write('ERROR: %s\n' % error)
462
463
463 return HookResponse(status, stdout.getvalue())
464 return HookResponse(status, stdout.getvalue())
464
465
465
466
466 def _parse_git_ref_lines(revision_lines):
467 def _parse_git_ref_lines(revision_lines):
467 rev_data = []
468 rev_data = []
468 for revision_line in revision_lines or []:
469 for revision_line in revision_lines or []:
469 old_rev, new_rev, ref = revision_line.strip().split(' ')
470 old_rev, new_rev, ref = revision_line.strip().split(' ')
470 ref_data = ref.split('/', 2)
471 ref_data = ref.split('/', 2)
471 if ref_data[1] in ('tags', 'heads'):
472 if ref_data[1] in ('tags', 'heads'):
472 rev_data.append({
473 rev_data.append({
473 # NOTE(marcink):
474 # NOTE(marcink):
474 # we're unable to tell total_commits for git at this point
475 # we're unable to tell total_commits for git at this point
475 # but we set the variable for consistency with GIT
476 # but we set the variable for consistency with GIT
476 'total_commits': -1,
477 'total_commits': -1,
477 'old_rev': old_rev,
478 'old_rev': old_rev,
478 'new_rev': new_rev,
479 'new_rev': new_rev,
479 'ref': ref,
480 'ref': ref,
480 'type': ref_data[1],
481 'type': ref_data[1],
481 'name': ref_data[2],
482 'name': ref_data[2],
482 })
483 })
483 return rev_data
484 return rev_data
484
485
485
486
486 def git_pre_receive(unused_repo_path, revision_lines, env):
487 def git_pre_receive(unused_repo_path, revision_lines, env):
487 """
488 """
488 Pre push hook.
489 Pre push hook.
489
490
490 :param extras: dictionary containing the keys defined in simplevcs
491 :param extras: dictionary containing the keys defined in simplevcs
491 :type extras: dict
492 :type extras: dict
492
493
493 :return: status code of the hook. 0 for success.
494 :return: status code of the hook. 0 for success.
494 :rtype: int
495 :rtype: int
495 """
496 """
496 extras = json.loads(env['RC_SCM_DATA'])
497 extras = json.loads(env['RC_SCM_DATA'])
497 rev_data = _parse_git_ref_lines(revision_lines)
498 rev_data = _parse_git_ref_lines(revision_lines)
498 if 'push' not in extras['hooks']:
499 if 'push' not in extras['hooks']:
499 return 0
500 return 0
500 empty_commit_id = '0' * 40
501 empty_commit_id = '0' * 40
501
502
502 detect_force_push = extras.get('detect_force_push')
503 detect_force_push = extras.get('detect_force_push')
503
504
504 for push_ref in rev_data:
505 for push_ref in rev_data:
505 # store our git-env which holds the temp store
506 # store our git-env which holds the temp store
506 push_ref['git_env'] = _get_git_env()
507 push_ref['git_env'] = _get_git_env()
507 push_ref['pruned_sha'] = ''
508 push_ref['pruned_sha'] = ''
508 if not detect_force_push:
509 if not detect_force_push:
509 # don't check for forced-push when we don't need to
510 # don't check for forced-push when we don't need to
510 continue
511 continue
511
512
512 type_ = push_ref['type']
513 type_ = push_ref['type']
513 new_branch = push_ref['old_rev'] == empty_commit_id
514 new_branch = push_ref['old_rev'] == empty_commit_id
514 delete_branch = push_ref['new_rev'] == empty_commit_id
515 delete_branch = push_ref['new_rev'] == empty_commit_id
515 if type_ == 'heads' and not (new_branch or delete_branch):
516 if type_ == 'heads' and not (new_branch or delete_branch):
516 old_rev = push_ref['old_rev']
517 old_rev = push_ref['old_rev']
517 new_rev = push_ref['new_rev']
518 new_rev = push_ref['new_rev']
518 cmd = [settings.GIT_EXECUTABLE, 'rev-list', old_rev, '^{}'.format(new_rev)]
519 cmd = [settings.GIT_EXECUTABLE, 'rev-list', old_rev, '^{}'.format(new_rev)]
519 stdout, stderr = subprocessio.run_command(
520 stdout, stderr = subprocessio.run_command(
520 cmd, env=os.environ.copy())
521 cmd, env=os.environ.copy())
521 # means we're having some non-reachable objects, this forced push was used
522 # means we're having some non-reachable objects, this forced push was used
522 if stdout:
523 if stdout:
523 push_ref['pruned_sha'] = stdout.splitlines()
524 push_ref['pruned_sha'] = stdout.splitlines()
524
525
525 extras['hook_type'] = 'pre_receive'
526 extras['hook_type'] = 'pre_receive'
526 extras['commit_ids'] = rev_data
527 extras['commit_ids'] = rev_data
527 return _call_hook('pre_push', extras, GitMessageWriter())
528 return _call_hook('pre_push', extras, GitMessageWriter())
528
529
529
530
530 def git_post_receive(unused_repo_path, revision_lines, env):
531 def git_post_receive(unused_repo_path, revision_lines, env):
531 """
532 """
532 Post push hook.
533 Post push hook.
533
534
534 :param extras: dictionary containing the keys defined in simplevcs
535 :param extras: dictionary containing the keys defined in simplevcs
535 :type extras: dict
536 :type extras: dict
536
537
537 :return: status code of the hook. 0 for success.
538 :return: status code of the hook. 0 for success.
538 :rtype: int
539 :rtype: int
539 """
540 """
540 extras = json.loads(env['RC_SCM_DATA'])
541 extras = json.loads(env['RC_SCM_DATA'])
541 if 'push' not in extras['hooks']:
542 if 'push' not in extras['hooks']:
542 return 0
543 return 0
543
544
544 rev_data = _parse_git_ref_lines(revision_lines)
545 rev_data = _parse_git_ref_lines(revision_lines)
545
546
546 git_revs = []
547 git_revs = []
547
548
548 # N.B.(skreft): it is ok to just call git, as git before calling a
549 # N.B.(skreft): it is ok to just call git, as git before calling a
549 # subcommand sets the PATH environment variable so that it point to the
550 # subcommand sets the PATH environment variable so that it point to the
550 # correct version of the git executable.
551 # correct version of the git executable.
551 empty_commit_id = '0' * 40
552 empty_commit_id = '0' * 40
552 branches = []
553 branches = []
553 tags = []
554 tags = []
554 for push_ref in rev_data:
555 for push_ref in rev_data:
555 type_ = push_ref['type']
556 type_ = push_ref['type']
556
557
557 if type_ == 'heads':
558 if type_ == 'heads':
558 if push_ref['old_rev'] == empty_commit_id:
559 if push_ref['old_rev'] == empty_commit_id:
559 # starting new branch case
560 # starting new branch case
560 if push_ref['name'] not in branches:
561 if push_ref['name'] not in branches:
561 branches.append(push_ref['name'])
562 branches.append(push_ref['name'])
562
563
563 # Fix up head revision if needed
564 # Fix up head revision if needed
564 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
565 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
565 try:
566 try:
566 subprocessio.run_command(cmd, env=os.environ.copy())
567 subprocessio.run_command(cmd, env=os.environ.copy())
567 except Exception:
568 except Exception:
568 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
569 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
569 'refs/heads/%s' % push_ref['name']]
570 'refs/heads/%s' % push_ref['name']]
570 print("Setting default branch to %s" % push_ref['name'])
571 print("Setting default branch to %s" % push_ref['name'])
571 subprocessio.run_command(cmd, env=os.environ.copy())
572 subprocessio.run_command(cmd, env=os.environ.copy())
572
573
573 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
574 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
574 '--format=%(refname)', 'refs/heads/*']
575 '--format=%(refname)', 'refs/heads/*']
575 stdout, stderr = subprocessio.run_command(
576 stdout, stderr = subprocessio.run_command(
576 cmd, env=os.environ.copy())
577 cmd, env=os.environ.copy())
577 heads = stdout
578 heads = stdout
578 heads = heads.replace(push_ref['ref'], '')
579 heads = heads.replace(push_ref['ref'], '')
579 heads = ' '.join(head for head
580 heads = ' '.join(head for head
580 in heads.splitlines() if head) or '.'
581 in heads.splitlines() if head) or '.'
581 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
582 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
582 '--pretty=format:%H', '--', push_ref['new_rev'],
583 '--pretty=format:%H', '--', push_ref['new_rev'],
583 '--not', heads]
584 '--not', heads]
584 stdout, stderr = subprocessio.run_command(
585 stdout, stderr = subprocessio.run_command(
585 cmd, env=os.environ.copy())
586 cmd, env=os.environ.copy())
586 git_revs.extend(stdout.splitlines())
587 git_revs.extend(stdout.splitlines())
587 elif push_ref['new_rev'] == empty_commit_id:
588 elif push_ref['new_rev'] == empty_commit_id:
588 # delete branch case
589 # delete branch case
589 git_revs.append('delete_branch=>%s' % push_ref['name'])
590 git_revs.append('delete_branch=>%s' % push_ref['name'])
590 else:
591 else:
591 if push_ref['name'] not in branches:
592 if push_ref['name'] not in branches:
592 branches.append(push_ref['name'])
593 branches.append(push_ref['name'])
593
594
594 cmd = [settings.GIT_EXECUTABLE, 'log',
595 cmd = [settings.GIT_EXECUTABLE, 'log',
595 '{old_rev}..{new_rev}'.format(**push_ref),
596 '{old_rev}..{new_rev}'.format(**push_ref),
596 '--reverse', '--pretty=format:%H']
597 '--reverse', '--pretty=format:%H']
597 stdout, stderr = subprocessio.run_command(
598 stdout, stderr = subprocessio.run_command(
598 cmd, env=os.environ.copy())
599 cmd, env=os.environ.copy())
599 git_revs.extend(stdout.splitlines())
600 git_revs.extend(stdout.splitlines())
600 elif type_ == 'tags':
601 elif type_ == 'tags':
601 if push_ref['name'] not in tags:
602 if push_ref['name'] not in tags:
602 tags.append(push_ref['name'])
603 tags.append(push_ref['name'])
603 git_revs.append('tag=>%s' % push_ref['name'])
604 git_revs.append('tag=>%s' % push_ref['name'])
604
605
605 extras['hook_type'] = 'post_receive'
606 extras['hook_type'] = 'post_receive'
606 extras['commit_ids'] = git_revs
607 extras['commit_ids'] = git_revs
607 extras['new_refs'] = {
608 extras['new_refs'] = {
608 'branches': branches,
609 'branches': branches,
609 'bookmarks': [],
610 'bookmarks': [],
610 'tags': tags,
611 'tags': tags,
611 }
612 }
612
613
613 if 'repo_size' in extras['hooks']:
614 if 'repo_size' in extras['hooks']:
614 try:
615 try:
615 _call_hook('repo_size', extras, GitMessageWriter())
616 _call_hook('repo_size', extras, GitMessageWriter())
616 except:
617 except:
617 pass
618 pass
618
619
619 return _call_hook('post_push', extras, GitMessageWriter())
620 return _call_hook('post_push', extras, GitMessageWriter())
620
621
621
622
622 def _get_extras_from_txn_id(path, txn_id):
623 def _get_extras_from_txn_id(path, txn_id):
623 extras = {}
624 extras = {}
624 try:
625 try:
625 cmd = ['svnlook', 'pget',
626 cmd = ['svnlook', 'pget',
626 '-t', txn_id,
627 '-t', txn_id,
627 '--revprop', path, 'rc-scm-extras']
628 '--revprop', path, 'rc-scm-extras']
628 stdout, stderr = subprocessio.run_command(
629 stdout, stderr = subprocessio.run_command(
629 cmd, env=os.environ.copy())
630 cmd, env=os.environ.copy())
630 extras = json.loads(base64.urlsafe_b64decode(stdout))
631 extras = json.loads(base64.urlsafe_b64decode(stdout))
631 except Exception:
632 except Exception:
632 log.exception('Failed to extract extras info from txn_id')
633 log.exception('Failed to extract extras info from txn_id')
633
634
634 return extras
635 return extras
635
636
636
637
637 def _get_extras_from_commit_id(commit_id, path):
638 def _get_extras_from_commit_id(commit_id, path):
638 extras = {}
639 extras = {}
639 try:
640 try:
640 cmd = ['svnlook', 'pget',
641 cmd = ['svnlook', 'pget',
641 '-r', commit_id,
642 '-r', commit_id,
642 '--revprop', path, 'rc-scm-extras']
643 '--revprop', path, 'rc-scm-extras']
643 stdout, stderr = subprocessio.run_command(
644 stdout, stderr = subprocessio.run_command(
644 cmd, env=os.environ.copy())
645 cmd, env=os.environ.copy())
645 extras = json.loads(base64.urlsafe_b64decode(stdout))
646 extras = json.loads(base64.urlsafe_b64decode(stdout))
646 except Exception:
647 except Exception:
647 log.exception('Failed to extract extras info from commit_id')
648 log.exception('Failed to extract extras info from commit_id')
648
649
649 return extras
650 return extras
650
651
651
652
652 def svn_pre_commit(repo_path, commit_data, env):
653 def svn_pre_commit(repo_path, commit_data, env):
653 path, txn_id = commit_data
654 path, txn_id = commit_data
654 branches = []
655 branches = []
655 tags = []
656 tags = []
656
657
657 if env.get('RC_SCM_DATA'):
658 if env.get('RC_SCM_DATA'):
658 extras = json.loads(env['RC_SCM_DATA'])
659 extras = json.loads(env['RC_SCM_DATA'])
659 else:
660 else:
660 # fallback method to read from TXN-ID stored data
661 # fallback method to read from TXN-ID stored data
661 extras = _get_extras_from_txn_id(path, txn_id)
662 extras = _get_extras_from_txn_id(path, txn_id)
662 if not extras:
663 if not extras:
663 return 0
664 return 0
664
665
665 extras['hook_type'] = 'pre_commit'
666 extras['hook_type'] = 'pre_commit'
666 extras['commit_ids'] = []
667 extras['commit_ids'] = []
667 extras['txn_id'] = txn_id
668 extras['txn_id'] = txn_id
668 extras['new_refs'] = {
669 extras['new_refs'] = {
669 'total_commits': 1,
670 'total_commits': 1,
670 'branches': branches,
671 'branches': branches,
671 'bookmarks': [],
672 'bookmarks': [],
672 'tags': tags,
673 'tags': tags,
673 }
674 }
674
675
675 return _call_hook('pre_push', extras, SvnMessageWriter())
676 return _call_hook('pre_push', extras, SvnMessageWriter())
676
677
677
678
678 def svn_post_commit(repo_path, commit_data, env):
679 def svn_post_commit(repo_path, commit_data, env):
679 """
680 """
680 commit_data is path, rev, txn_id
681 commit_data is path, rev, txn_id
681 """
682 """
682 path, commit_id, txn_id = commit_data
683 path, commit_id, txn_id = commit_data
683 branches = []
684 branches = []
684 tags = []
685 tags = []
685
686
686 if env.get('RC_SCM_DATA'):
687 if env.get('RC_SCM_DATA'):
687 extras = json.loads(env['RC_SCM_DATA'])
688 extras = json.loads(env['RC_SCM_DATA'])
688 else:
689 else:
689 # fallback method to read from TXN-ID stored data
690 # fallback method to read from TXN-ID stored data
690 extras = _get_extras_from_commit_id(commit_id, path)
691 extras = _get_extras_from_commit_id(commit_id, path)
691 if not extras:
692 if not extras:
692 return 0
693 return 0
693
694
694 extras['hook_type'] = 'post_commit'
695 extras['hook_type'] = 'post_commit'
695 extras['commit_ids'] = [commit_id]
696 extras['commit_ids'] = [commit_id]
696 extras['txn_id'] = txn_id
697 extras['txn_id'] = txn_id
697 extras['new_refs'] = {
698 extras['new_refs'] = {
698 'branches': branches,
699 'branches': branches,
699 'bookmarks': [],
700 'bookmarks': [],
700 'tags': tags,
701 'tags': tags,
701 'total_commits': 1,
702 'total_commits': 1,
702 }
703 }
703
704
704 if 'repo_size' in extras['hooks']:
705 if 'repo_size' in extras['hooks']:
705 try:
706 try:
706 _call_hook('repo_size', extras, SvnMessageWriter())
707 _call_hook('repo_size', extras, SvnMessageWriter())
707 except Exception:
708 except Exception:
708 pass
709 pass
709
710
710 return _call_hook('post_push', extras, SvnMessageWriter())
711 return _call_hook('post_push', extras, SvnMessageWriter())
General Comments 0
You need to be logged in to leave comments. Login now