##// END OF EJS Templates
node-history: small fixes and added a investigation note
super-admin -
r1151:f738f913 default
parent child Browse files
Show More
@@ -1,1199 +1,1201 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17 import binascii
18 18 import io
19 19 import logging
20 20 import stat
21 21 import sys
22 22 import urllib.request
23 23 import urllib.parse
24 24 import hashlib
25 25
26 26 from hgext import largefiles, rebase, purge
27 27
28 28 from mercurial import commands
29 29 from mercurial import unionrepo
30 30 from mercurial import verify
31 31 from mercurial import repair
32 32 from mercurial.error import AmbiguousPrefixLookupError
33 33
34 34 import vcsserver
35 35 from vcsserver import exceptions
36 36 from vcsserver.base import (
37 37 RepoFactory,
38 38 obfuscate_qs,
39 39 raise_from_original,
40 40 store_archive_in_cache,
41 41 ArchiveNode,
42 42 BytesEnvelope,
43 43 BinaryEnvelope,
44 44 )
45 45 from vcsserver.hgcompat import (
46 46 archival,
47 47 bin,
48 48 clone,
49 49 config as hgconfig,
50 50 diffopts,
51 51 hex,
52 52 get_ctx,
53 53 hg_url as url_parser,
54 54 httpbasicauthhandler,
55 55 httpdigestauthhandler,
56 56 makepeer,
57 57 instance,
58 58 match,
59 59 memctx,
60 60 exchange,
61 61 memfilectx,
62 62 nullrev,
63 63 hg_merge,
64 64 patch,
65 65 peer,
66 66 revrange,
67 67 ui,
68 68 hg_tag,
69 69 Abort,
70 70 LookupError,
71 71 RepoError,
72 72 RepoLookupError,
73 73 InterventionRequired,
74 74 RequirementError,
75 75 alwaysmatcher,
76 76 patternmatcher,
77 77 hgutil,
78 78 hgext_strip,
79 79 )
80 80 from vcsserver.str_utils import ascii_bytes, ascii_str, safe_str, safe_bytes
81 81 from vcsserver.vcs_base import RemoteBase
82 82 from vcsserver.config import hooks as hooks_config
83 83 from vcsserver.lib.exc_tracking import format_exc
84 84
85 85 log = logging.getLogger(__name__)
86 86
87 87
88 88 def make_ui_from_config(repo_config):
89 89
90 90 class LoggingUI(ui.ui):
91 91
92 92 def status(self, *msg, **opts):
93 93 str_msg = map(safe_str, msg)
94 94 log.info(' '.join(str_msg).rstrip('\n'))
95 95 #super(LoggingUI, self).status(*msg, **opts)
96 96
97 97 def warn(self, *msg, **opts):
98 98 str_msg = map(safe_str, msg)
99 99 log.warning('ui_logger:'+' '.join(str_msg).rstrip('\n'))
100 100 #super(LoggingUI, self).warn(*msg, **opts)
101 101
102 102 def error(self, *msg, **opts):
103 103 str_msg = map(safe_str, msg)
104 104 log.error('ui_logger:'+' '.join(str_msg).rstrip('\n'))
105 105 #super(LoggingUI, self).error(*msg, **opts)
106 106
107 107 def note(self, *msg, **opts):
108 108 str_msg = map(safe_str, msg)
109 109 log.info('ui_logger:'+' '.join(str_msg).rstrip('\n'))
110 110 #super(LoggingUI, self).note(*msg, **opts)
111 111
112 112 def debug(self, *msg, **opts):
113 113 str_msg = map(safe_str, msg)
114 114 log.debug('ui_logger:'+' '.join(str_msg).rstrip('\n'))
115 115 #super(LoggingUI, self).debug(*msg, **opts)
116 116
117 117 baseui = LoggingUI()
118 118
119 119 # clean the baseui object
120 120 baseui._ocfg = hgconfig.config()
121 121 baseui._ucfg = hgconfig.config()
122 122 baseui._tcfg = hgconfig.config()
123 123
124 124 for section, option, value in repo_config:
125 125 baseui.setconfig(ascii_bytes(section), ascii_bytes(option), ascii_bytes(value))
126 126
127 127 # make our hgweb quiet so it doesn't print output
128 128 baseui.setconfig(b'ui', b'quiet', b'true')
129 129
130 130 baseui.setconfig(b'ui', b'paginate', b'never')
131 131 # for better Error reporting of Mercurial
132 132 baseui.setconfig(b'ui', b'message-output', b'stderr')
133 133
134 134 # force mercurial to only use 1 thread, otherwise it may try to set a
135 135 # signal in a non-main thread, thus generating a ValueError.
136 136 baseui.setconfig(b'worker', b'numcpus', 1)
137 137
138 138 # If there is no config for the largefiles extension, we explicitly disable
139 139 # it here. This overrides settings from repositories hgrc file. Recent
140 140 # mercurial versions enable largefiles in hgrc on clone from largefile
141 141 # repo.
142 142 if not baseui.hasconfig(b'extensions', b'largefiles'):
143 143 log.debug('Explicitly disable largefiles extension for repo.')
144 144 baseui.setconfig(b'extensions', b'largefiles', b'!')
145 145
146 146 return baseui
147 147
148 148
149 149 def reraise_safe_exceptions(func):
150 150 """Decorator for converting mercurial exceptions to something neutral."""
151 151
152 152 def wrapper(*args, **kwargs):
153 153 try:
154 154 return func(*args, **kwargs)
155 155 except (Abort, InterventionRequired) as e:
156 156 raise_from_original(exceptions.AbortException(e), e)
157 157 except RepoLookupError as e:
158 158 raise_from_original(exceptions.LookupException(e), e)
159 159 except RequirementError as e:
160 160 raise_from_original(exceptions.RequirementException(e), e)
161 161 except RepoError as e:
162 162 raise_from_original(exceptions.VcsException(e), e)
163 163 except LookupError as e:
164 164 raise_from_original(exceptions.LookupException(e), e)
165 165 except Exception as e:
166 166 if not hasattr(e, '_vcs_kind'):
167 167 log.exception("Unhandled exception in hg remote call")
168 168 raise_from_original(exceptions.UnhandledException(e), e)
169 169
170 170 raise
171 171 return wrapper
172 172
173 173
174 174 class MercurialFactory(RepoFactory):
175 175 repo_type = 'hg'
176 176
177 177 def _create_config(self, config, hooks=True):
178 178 if not hooks:
179 179
180 180 hooks_to_clean = {
181 181
182 182 hooks_config.HOOK_REPO_SIZE,
183 183 hooks_config.HOOK_PRE_PULL,
184 184 hooks_config.HOOK_PULL,
185 185
186 186 hooks_config.HOOK_PRE_PUSH,
187 187 # TODO: what about PRETXT, this was disabled in pre 5.0.0
188 188 hooks_config.HOOK_PRETX_PUSH,
189 189
190 190 }
191 191 new_config = []
192 192 for section, option, value in config:
193 193 if section == 'hooks' and option in hooks_to_clean:
194 194 continue
195 195 new_config.append((section, option, value))
196 196 config = new_config
197 197
198 198 baseui = make_ui_from_config(config)
199 199 return baseui
200 200
201 201 def _create_repo(self, wire, create):
202 202 baseui = self._create_config(wire["config"])
203 203 repo = instance(baseui, safe_bytes(wire["path"]), create)
204 204 log.debug('repository created: got HG object: %s', repo)
205 205 return repo
206 206
207 207 def repo(self, wire, create=False):
208 208 """
209 209 Get a repository instance for the given path.
210 210 """
211 211 return self._create_repo(wire, create)
212 212
213 213
214 214 def patch_ui_message_output(baseui):
215 215 baseui.setconfig(b'ui', b'quiet', b'false')
216 216 output = io.BytesIO()
217 217
218 218 def write(data, **unused_kwargs):
219 219 output.write(data)
220 220
221 221 baseui.status = write
222 222 baseui.write = write
223 223 baseui.warn = write
224 224 baseui.debug = write
225 225
226 226 return baseui, output
227 227
228 228
229 229 def get_obfuscated_url(url_obj):
230 230 url_obj.passwd = b'*****' if url_obj.passwd else url_obj.passwd
231 231 url_obj.query = obfuscate_qs(url_obj.query)
232 232 obfuscated_uri = str(url_obj)
233 233 return obfuscated_uri
234 234
235 235
236 236 def normalize_url_for_hg(url: str):
237 237 _proto = None
238 238
239 239 if '+' in url[:url.find('://')]:
240 240 _proto = url[0:url.find('+')]
241 241 url = url[url.find('+') + 1:]
242 242 return url, _proto
243 243
244 244
245 245 class HgRemote(RemoteBase):
246 246
247 247 def __init__(self, factory):
248 248 self._factory = factory
249 249 self._bulk_methods = {
250 250 "affected_files": self.ctx_files,
251 251 "author": self.ctx_user,
252 252 "branch": self.ctx_branch,
253 253 "children": self.ctx_children,
254 254 "date": self.ctx_date,
255 255 "message": self.ctx_description,
256 256 "parents": self.ctx_parents,
257 257 "status": self.ctx_status,
258 258 "obsolete": self.ctx_obsolete,
259 259 "phase": self.ctx_phase,
260 260 "hidden": self.ctx_hidden,
261 261 "_file_paths": self.ctx_list,
262 262 }
263 263 self._bulk_file_methods = {
264 264 "size": self.fctx_size,
265 265 "data": self.fctx_node_data,
266 266 "flags": self.fctx_flags,
267 267 "is_binary": self.is_binary,
268 268 "md5": self.md5_hash,
269 269 }
270 270
271 271 def _get_ctx(self, repo, ref):
272 272 return get_ctx(repo, ref)
273 273
274 274 @reraise_safe_exceptions
275 275 def discover_hg_version(self):
276 276 from mercurial import util
277 277 return safe_str(util.version())
278 278
279 279 @reraise_safe_exceptions
280 280 def is_empty(self, wire):
281 281 repo = self._factory.repo(wire)
282 282
283 283 try:
284 284 return len(repo) == 0
285 285 except Exception:
286 286 log.exception("failed to read object_store")
287 287 return False
288 288
289 289 @reraise_safe_exceptions
290 290 def bookmarks(self, wire):
291 291 cache_on, context_uid, repo_id = self._cache_on(wire)
292 292 region = self._region(wire)
293 293
294 294 @region.conditional_cache_on_arguments(condition=cache_on)
295 295 def _bookmarks(_context_uid, _repo_id):
296 296 repo = self._factory.repo(wire)
297 297 return {safe_str(name): ascii_str(hex(sha)) for name, sha in repo._bookmarks.items()}
298 298
299 299 return _bookmarks(context_uid, repo_id)
300 300
301 301 @reraise_safe_exceptions
302 302 def branches(self, wire, normal, closed):
303 303 cache_on, context_uid, repo_id = self._cache_on(wire)
304 304 region = self._region(wire)
305 305
306 306 @region.conditional_cache_on_arguments(condition=cache_on)
307 307 def _branches(_context_uid, _repo_id, _normal, _closed):
308 308 repo = self._factory.repo(wire)
309 309 iter_branches = repo.branchmap().iterbranches()
310 310 bt = {}
311 311 for branch_name, _heads, tip_node, is_closed in iter_branches:
312 312 if normal and not is_closed:
313 313 bt[safe_str(branch_name)] = ascii_str(hex(tip_node))
314 314 if closed and is_closed:
315 315 bt[safe_str(branch_name)] = ascii_str(hex(tip_node))
316 316
317 317 return bt
318 318
319 319 return _branches(context_uid, repo_id, normal, closed)
320 320
321 321 @reraise_safe_exceptions
322 322 def bulk_request(self, wire, commit_id, pre_load):
323 323 cache_on, context_uid, repo_id = self._cache_on(wire)
324 324 region = self._region(wire)
325 325
326 326 @region.conditional_cache_on_arguments(condition=cache_on)
327 327 def _bulk_request(_repo_id, _commit_id, _pre_load):
328 328 result = {}
329 329 for attr in pre_load:
330 330 try:
331 331 method = self._bulk_methods[attr]
332 332 wire.update({'cache': False}) # disable cache for bulk calls so we don't double cache
333 333 result[attr] = method(wire, commit_id)
334 334 except KeyError as e:
335 335 raise exceptions.VcsException(e)(
336 336 'Unknown bulk attribute: "%s"' % attr)
337 337 return result
338 338
339 339 return _bulk_request(repo_id, commit_id, sorted(pre_load))
340 340
341 341 @reraise_safe_exceptions
342 342 def ctx_branch(self, wire, commit_id):
343 343 cache_on, context_uid, repo_id = self._cache_on(wire)
344 344 region = self._region(wire)
345 345
346 346 @region.conditional_cache_on_arguments(condition=cache_on)
347 347 def _ctx_branch(_repo_id, _commit_id):
348 348 repo = self._factory.repo(wire)
349 349 ctx = self._get_ctx(repo, commit_id)
350 350 return ctx.branch()
351 351 return _ctx_branch(repo_id, commit_id)
352 352
353 353 @reraise_safe_exceptions
354 354 def ctx_date(self, wire, commit_id):
355 355 cache_on, context_uid, repo_id = self._cache_on(wire)
356 356 region = self._region(wire)
357 357
358 358 @region.conditional_cache_on_arguments(condition=cache_on)
359 359 def _ctx_date(_repo_id, _commit_id):
360 360 repo = self._factory.repo(wire)
361 361 ctx = self._get_ctx(repo, commit_id)
362 362 return ctx.date()
363 363 return _ctx_date(repo_id, commit_id)
364 364
365 365 @reraise_safe_exceptions
366 366 def ctx_description(self, wire, revision):
367 367 repo = self._factory.repo(wire)
368 368 ctx = self._get_ctx(repo, revision)
369 369 return ctx.description()
370 370
371 371 @reraise_safe_exceptions
372 372 def ctx_files(self, wire, commit_id):
373 373 cache_on, context_uid, repo_id = self._cache_on(wire)
374 374 region = self._region(wire)
375 375
376 376 @region.conditional_cache_on_arguments(condition=cache_on)
377 377 def _ctx_files(_repo_id, _commit_id):
378 378 repo = self._factory.repo(wire)
379 379 ctx = self._get_ctx(repo, commit_id)
380 380 return ctx.files()
381 381
382 382 return _ctx_files(repo_id, commit_id)
383 383
384 384 @reraise_safe_exceptions
385 385 def ctx_list(self, path, revision):
386 386 repo = self._factory.repo(path)
387 387 ctx = self._get_ctx(repo, revision)
388 388 return list(ctx)
389 389
390 390 @reraise_safe_exceptions
391 391 def ctx_parents(self, wire, commit_id):
392 392 cache_on, context_uid, repo_id = self._cache_on(wire)
393 393 region = self._region(wire)
394 394
395 395 @region.conditional_cache_on_arguments(condition=cache_on)
396 396 def _ctx_parents(_repo_id, _commit_id):
397 397 repo = self._factory.repo(wire)
398 398 ctx = self._get_ctx(repo, commit_id)
399 399 return [parent.hex() for parent in ctx.parents()
400 400 if not (parent.hidden() or parent.obsolete())]
401 401
402 402 return _ctx_parents(repo_id, commit_id)
403 403
404 404 @reraise_safe_exceptions
405 405 def ctx_children(self, wire, commit_id):
406 406 cache_on, context_uid, repo_id = self._cache_on(wire)
407 407 region = self._region(wire)
408 408
409 409 @region.conditional_cache_on_arguments(condition=cache_on)
410 410 def _ctx_children(_repo_id, _commit_id):
411 411 repo = self._factory.repo(wire)
412 412 ctx = self._get_ctx(repo, commit_id)
413 413 return [child.hex() for child in ctx.children()
414 414 if not (child.hidden() or child.obsolete())]
415 415
416 416 return _ctx_children(repo_id, commit_id)
417 417
418 418 @reraise_safe_exceptions
419 419 def ctx_phase(self, wire, commit_id):
420 420 cache_on, context_uid, repo_id = self._cache_on(wire)
421 421 region = self._region(wire)
422 422
423 423 @region.conditional_cache_on_arguments(condition=cache_on)
424 424 def _ctx_phase(_context_uid, _repo_id, _commit_id):
425 425 repo = self._factory.repo(wire)
426 426 ctx = self._get_ctx(repo, commit_id)
427 427 # public=0, draft=1, secret=3
428 428 return ctx.phase()
429 429 return _ctx_phase(context_uid, repo_id, commit_id)
430 430
431 431 @reraise_safe_exceptions
432 432 def ctx_obsolete(self, wire, commit_id):
433 433 cache_on, context_uid, repo_id = self._cache_on(wire)
434 434 region = self._region(wire)
435 435
436 436 @region.conditional_cache_on_arguments(condition=cache_on)
437 437 def _ctx_obsolete(_context_uid, _repo_id, _commit_id):
438 438 repo = self._factory.repo(wire)
439 439 ctx = self._get_ctx(repo, commit_id)
440 440 return ctx.obsolete()
441 441 return _ctx_obsolete(context_uid, repo_id, commit_id)
442 442
443 443 @reraise_safe_exceptions
444 444 def ctx_hidden(self, wire, commit_id):
445 445 cache_on, context_uid, repo_id = self._cache_on(wire)
446 446 region = self._region(wire)
447 447
448 448 @region.conditional_cache_on_arguments(condition=cache_on)
449 449 def _ctx_hidden(_context_uid, _repo_id, _commit_id):
450 450 repo = self._factory.repo(wire)
451 451 ctx = self._get_ctx(repo, commit_id)
452 452 return ctx.hidden()
453 453 return _ctx_hidden(context_uid, repo_id, commit_id)
454 454
455 455 @reraise_safe_exceptions
456 456 def ctx_substate(self, wire, revision):
457 457 repo = self._factory.repo(wire)
458 458 ctx = self._get_ctx(repo, revision)
459 459 return ctx.substate
460 460
461 461 @reraise_safe_exceptions
462 462 def ctx_status(self, wire, revision):
463 463 repo = self._factory.repo(wire)
464 464 ctx = self._get_ctx(repo, revision)
465 465 status = repo[ctx.p1().node()].status(other=ctx.node())
466 466 # object of status (odd, custom named tuple in mercurial) is not
467 467 # correctly serializable, we make it a list, as the underling
468 468 # API expects this to be a list
469 469 return list(status)
470 470
471 471 @reraise_safe_exceptions
472 472 def ctx_user(self, wire, revision):
473 473 repo = self._factory.repo(wire)
474 474 ctx = self._get_ctx(repo, revision)
475 475 return ctx.user()
476 476
477 477 @reraise_safe_exceptions
478 478 def check_url(self, url, config):
479 479 url, _proto = normalize_url_for_hg(url)
480 480 url_obj = url_parser(safe_bytes(url))
481 481
482 482 test_uri = safe_str(url_obj.authinfo()[0])
483 483 authinfo = url_obj.authinfo()[1]
484 484 obfuscated_uri = get_obfuscated_url(url_obj)
485 485 log.info("Checking URL for remote cloning/import: %s", obfuscated_uri)
486 486
487 487 handlers = []
488 488 if authinfo:
489 489 # create a password manager
490 490 passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
491 491 passmgr.add_password(*authinfo)
492 492
493 493 handlers.extend((httpbasicauthhandler(passmgr),
494 494 httpdigestauthhandler(passmgr)))
495 495
496 496 o = urllib.request.build_opener(*handlers)
497 497 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
498 498 ('Accept', 'application/mercurial-0.1')]
499 499
500 500 q = {"cmd": 'between'}
501 501 q.update({'pairs': "{}-{}".format('0' * 40, '0' * 40)})
502 502 qs = '?%s' % urllib.parse.urlencode(q)
503 503 cu = f"{test_uri}{qs}"
504 504 req = urllib.request.Request(cu, None, {})
505 505
506 506 try:
507 507 log.debug("Trying to open URL %s", obfuscated_uri)
508 508 resp = o.open(req)
509 509 if resp.code != 200:
510 510 raise exceptions.URLError()('Return Code is not 200')
511 511 except Exception as e:
512 512 log.warning("URL cannot be opened: %s", obfuscated_uri, exc_info=True)
513 513 # means it cannot be cloned
514 514 raise exceptions.URLError(e)(f"[{obfuscated_uri}] org_exc: {e}")
515 515
516 516 # now check if it's a proper hg repo, but don't do it for svn
517 517 try:
518 518 if _proto == 'svn':
519 519 pass
520 520 else:
521 521 # check for pure hg repos
522 522 log.debug(
523 523 "Verifying if URL is a Mercurial repository: %s", obfuscated_uri)
524 524 ui = make_ui_from_config(config)
525 525 peer_checker = makepeer(ui, safe_bytes(url))
526 526 peer_checker.lookup(b'tip')
527 527 except Exception as e:
528 528 log.warning("URL is not a valid Mercurial repository: %s",
529 529 obfuscated_uri)
530 530 raise exceptions.URLError(e)(
531 531 "url [%s] does not look like an hg repo org_exc: %s"
532 532 % (obfuscated_uri, e))
533 533
534 534 log.info("URL is a valid Mercurial repository: %s", obfuscated_uri)
535 535 return True
536 536
537 537 @reraise_safe_exceptions
538 538 def diff(self, wire, commit_id_1, commit_id_2, file_filter, opt_git, opt_ignorews, context):
539 539 repo = self._factory.repo(wire)
540 540
541 541 if file_filter:
542 542 # unpack the file-filter
543 543 repo_path, node_path = file_filter
544 544 match_filter = match(safe_bytes(repo_path), b'', [safe_bytes(node_path)])
545 545 else:
546 546 match_filter = file_filter
547 547 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context, showfunc=1)
548 548
549 549 try:
550 550 diff_iter = patch.diff(
551 551 repo, node1=commit_id_1, node2=commit_id_2, match=match_filter, opts=opts)
552 552 return BytesEnvelope(b"".join(diff_iter))
553 553 except RepoLookupError as e:
554 554 raise exceptions.LookupException(e)()
555 555
556 556 @reraise_safe_exceptions
557 557 def node_history(self, wire, revision, path, limit):
558 558 cache_on, context_uid, repo_id = self._cache_on(wire)
559 559 region = self._region(wire)
560 560
561 561 @region.conditional_cache_on_arguments(condition=cache_on)
562 562 def _node_history(_context_uid, _repo_id, _revision, _path, _limit):
563 563 repo = self._factory.repo(wire)
564 564
565 565 ctx = self._get_ctx(repo, revision)
566 566 fctx = ctx.filectx(safe_bytes(path))
567 567
568 568 def history_iter():
569 569 limit_rev = fctx.rev()
570 for obj in reversed(list(fctx.filelog())):
571 obj = fctx.filectx(obj)
572 ctx = obj.changectx()
573 if ctx.hidden() or ctx.obsolete():
570 for fctx_candidate in reversed(list(fctx.filelog())):
571 f_obj = fctx.filectx(fctx_candidate)
572
573 # NOTE: This can be problematic...we can hide ONLY history node resulting in empty history
574 _ctx = obj.changectx()
575 if _ctx.hidden() or _ctx.obsolete():
574 576 continue
575 577
576 if limit_rev >= obj.rev():
577 yield obj
578 if limit_rev >= f_obj.rev():
579 yield f_obj
578 580
579 581 history = []
580 582 for cnt, obj in enumerate(history_iter()):
581 583 if limit and cnt >= limit:
582 584 break
583 585 history.append(hex(obj.node()))
584 586
585 587 return [x for x in history]
586 588 return _node_history(context_uid, repo_id, revision, path, limit)
587 589
588 590 @reraise_safe_exceptions
589 def node_history_untill(self, wire, revision, path, limit):
591 def node_history_until(self, wire, revision, path, limit):
590 592 cache_on, context_uid, repo_id = self._cache_on(wire)
591 593 region = self._region(wire)
592 594
593 595 @region.conditional_cache_on_arguments(condition=cache_on)
594 596 def _node_history_until(_context_uid, _repo_id):
595 597 repo = self._factory.repo(wire)
596 598 ctx = self._get_ctx(repo, revision)
597 599 fctx = ctx.filectx(safe_bytes(path))
598 600
599 601 file_log = list(fctx.filelog())
600 602 if limit:
601 603 # Limit to the last n items
602 604 file_log = file_log[-limit:]
603 605
604 606 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
605 607 return _node_history_until(context_uid, repo_id, revision, path, limit)
606 608
607 609 @reraise_safe_exceptions
608 610 def bulk_file_request(self, wire, commit_id, path, pre_load):
609 611 cache_on, context_uid, repo_id = self._cache_on(wire)
610 612 region = self._region(wire)
611 613
612 614 @region.conditional_cache_on_arguments(condition=cache_on)
613 615 def _bulk_file_request(_repo_id, _commit_id, _path, _pre_load):
614 616 result = {}
615 617 for attr in pre_load:
616 618 try:
617 619 method = self._bulk_file_methods[attr]
618 620 wire.update({'cache': False}) # disable cache for bulk calls so we don't double cache
619 621 result[attr] = method(wire, _commit_id, _path)
620 622 except KeyError as e:
621 623 raise exceptions.VcsException(e)(f'Unknown bulk attribute: "{attr}"')
622 624 return result
623 625
624 626 return BinaryEnvelope(_bulk_file_request(repo_id, commit_id, path, sorted(pre_load)))
625 627
626 628 @reraise_safe_exceptions
627 629 def fctx_annotate(self, wire, revision, path):
628 630 repo = self._factory.repo(wire)
629 631 ctx = self._get_ctx(repo, revision)
630 632 fctx = ctx.filectx(safe_bytes(path))
631 633
632 634 result = []
633 635 for i, annotate_obj in enumerate(fctx.annotate(), 1):
634 636 ln_no = i
635 637 sha = hex(annotate_obj.fctx.node())
636 638 content = annotate_obj.text
637 639 result.append((ln_no, ascii_str(sha), content))
638 640 return BinaryEnvelope(result)
639 641
640 642 @reraise_safe_exceptions
641 643 def fctx_node_data(self, wire, revision, path):
642 644 repo = self._factory.repo(wire)
643 645 ctx = self._get_ctx(repo, revision)
644 646 fctx = ctx.filectx(safe_bytes(path))
645 647 return BytesEnvelope(fctx.data())
646 648
647 649 @reraise_safe_exceptions
648 650 def fctx_flags(self, wire, commit_id, path):
649 651 cache_on, context_uid, repo_id = self._cache_on(wire)
650 652 region = self._region(wire)
651 653
652 654 @region.conditional_cache_on_arguments(condition=cache_on)
653 655 def _fctx_flags(_repo_id, _commit_id, _path):
654 656 repo = self._factory.repo(wire)
655 657 ctx = self._get_ctx(repo, commit_id)
656 658 fctx = ctx.filectx(safe_bytes(path))
657 659 return fctx.flags()
658 660
659 661 return _fctx_flags(repo_id, commit_id, path)
660 662
661 663 @reraise_safe_exceptions
662 664 def fctx_size(self, wire, commit_id, path):
663 665 cache_on, context_uid, repo_id = self._cache_on(wire)
664 666 region = self._region(wire)
665 667
666 668 @region.conditional_cache_on_arguments(condition=cache_on)
667 669 def _fctx_size(_repo_id, _revision, _path):
668 670 repo = self._factory.repo(wire)
669 671 ctx = self._get_ctx(repo, commit_id)
670 672 fctx = ctx.filectx(safe_bytes(path))
671 673 return fctx.size()
672 674 return _fctx_size(repo_id, commit_id, path)
673 675
674 676 @reraise_safe_exceptions
675 677 def get_all_commit_ids(self, wire, name):
676 678 cache_on, context_uid, repo_id = self._cache_on(wire)
677 679 region = self._region(wire)
678 680
679 681 @region.conditional_cache_on_arguments(condition=cache_on)
680 682 def _get_all_commit_ids(_context_uid, _repo_id, _name):
681 683 repo = self._factory.repo(wire)
682 684 revs = [ascii_str(repo[x].hex()) for x in repo.filtered(b'visible').changelog.revs()]
683 685 return revs
684 686 return _get_all_commit_ids(context_uid, repo_id, name)
685 687
686 688 @reraise_safe_exceptions
687 689 def get_config_value(self, wire, section, name, untrusted=False):
688 690 repo = self._factory.repo(wire)
689 691 return repo.ui.config(ascii_bytes(section), ascii_bytes(name), untrusted=untrusted)
690 692
691 693 @reraise_safe_exceptions
692 694 def is_large_file(self, wire, commit_id, path):
693 695 cache_on, context_uid, repo_id = self._cache_on(wire)
694 696 region = self._region(wire)
695 697
696 698 @region.conditional_cache_on_arguments(condition=cache_on)
697 699 def _is_large_file(_context_uid, _repo_id, _commit_id, _path):
698 700 return largefiles.lfutil.isstandin(safe_bytes(path))
699 701
700 702 return _is_large_file(context_uid, repo_id, commit_id, path)
701 703
702 704 @reraise_safe_exceptions
703 705 def is_binary(self, wire, revision, path):
704 706 cache_on, context_uid, repo_id = self._cache_on(wire)
705 707 region = self._region(wire)
706 708
707 709 @region.conditional_cache_on_arguments(condition=cache_on)
708 710 def _is_binary(_repo_id, _sha, _path):
709 711 repo = self._factory.repo(wire)
710 712 ctx = self._get_ctx(repo, revision)
711 713 fctx = ctx.filectx(safe_bytes(path))
712 714 return fctx.isbinary()
713 715
714 716 return _is_binary(repo_id, revision, path)
715 717
716 718 @reraise_safe_exceptions
717 719 def md5_hash(self, wire, revision, path):
718 720 cache_on, context_uid, repo_id = self._cache_on(wire)
719 721 region = self._region(wire)
720 722
721 723 @region.conditional_cache_on_arguments(condition=cache_on)
722 724 def _md5_hash(_repo_id, _sha, _path):
723 725 repo = self._factory.repo(wire)
724 726 ctx = self._get_ctx(repo, revision)
725 727 fctx = ctx.filectx(safe_bytes(path))
726 728 return hashlib.md5(fctx.data()).hexdigest()
727 729
728 730 return _md5_hash(repo_id, revision, path)
729 731
730 732 @reraise_safe_exceptions
731 733 def in_largefiles_store(self, wire, sha):
732 734 repo = self._factory.repo(wire)
733 735 return largefiles.lfutil.instore(repo, sha)
734 736
735 737 @reraise_safe_exceptions
736 738 def in_user_cache(self, wire, sha):
737 739 repo = self._factory.repo(wire)
738 740 return largefiles.lfutil.inusercache(repo.ui, sha)
739 741
740 742 @reraise_safe_exceptions
741 743 def store_path(self, wire, sha):
742 744 repo = self._factory.repo(wire)
743 745 return largefiles.lfutil.storepath(repo, sha)
744 746
745 747 @reraise_safe_exceptions
746 748 def link(self, wire, sha, path):
747 749 repo = self._factory.repo(wire)
748 750 largefiles.lfutil.link(
749 751 largefiles.lfutil.usercachepath(repo.ui, sha), path)
750 752
751 753 @reraise_safe_exceptions
752 754 def localrepository(self, wire, create=False):
753 755 self._factory.repo(wire, create=create)
754 756
755 757 @reraise_safe_exceptions
756 758 def lookup(self, wire, revision, both):
757 759 cache_on, context_uid, repo_id = self._cache_on(wire)
758 760 region = self._region(wire)
759 761
760 762 @region.conditional_cache_on_arguments(condition=cache_on)
761 763 def _lookup(_context_uid, _repo_id, _revision, _both):
762 764 repo = self._factory.repo(wire)
763 765 rev = _revision
764 766 if isinstance(rev, int):
765 767 # NOTE(marcink):
766 768 # since Mercurial doesn't support negative indexes properly
767 769 # we need to shift accordingly by one to get proper index, e.g
768 770 # repo[-1] => repo[-2]
769 771 # repo[0] => repo[-1]
770 772 if rev <= 0:
771 773 rev = rev + -1
772 774 try:
773 775 ctx = self._get_ctx(repo, rev)
774 776 except AmbiguousPrefixLookupError:
775 777 e = RepoLookupError(rev)
776 778 e._org_exc_tb = format_exc(sys.exc_info())
777 779 raise exceptions.LookupException(e)(rev)
778 780 except (TypeError, RepoLookupError, binascii.Error) as e:
779 781 e._org_exc_tb = format_exc(sys.exc_info())
780 782 raise exceptions.LookupException(e)(rev)
781 783 except LookupError as e:
782 784 e._org_exc_tb = format_exc(sys.exc_info())
783 785 raise exceptions.LookupException(e)(e.name)
784 786
785 787 if not both:
786 788 return ctx.hex()
787 789
788 790 ctx = repo[ctx.hex()]
789 791 return ctx.hex(), ctx.rev()
790 792
791 793 return _lookup(context_uid, repo_id, revision, both)
792 794
793 795 @reraise_safe_exceptions
794 796 def sync_push(self, wire, url):
795 797 if not self.check_url(url, wire['config']):
796 798 return
797 799
798 800 repo = self._factory.repo(wire)
799 801
800 802 # Disable any prompts for this repo
801 803 repo.ui.setconfig(b'ui', b'interactive', b'off', b'-y')
802 804
803 805 bookmarks = list(dict(repo._bookmarks).keys())
804 806 remote = peer(repo, {}, safe_bytes(url))
805 807 # Disable any prompts for this remote
806 808 remote.ui.setconfig(b'ui', b'interactive', b'off', b'-y')
807 809
808 810 return exchange.push(
809 811 repo, remote, newbranch=True, bookmarks=bookmarks).cgresult
810 812
811 813 @reraise_safe_exceptions
812 814 def revision(self, wire, rev):
813 815 repo = self._factory.repo(wire)
814 816 ctx = self._get_ctx(repo, rev)
815 817 return ctx.rev()
816 818
817 819 @reraise_safe_exceptions
818 820 def rev_range(self, wire, commit_filter):
819 821 cache_on, context_uid, repo_id = self._cache_on(wire)
820 822 region = self._region(wire)
821 823
822 824 @region.conditional_cache_on_arguments(condition=cache_on)
823 825 def _rev_range(_context_uid, _repo_id, _filter):
824 826 repo = self._factory.repo(wire)
825 827 revisions = [
826 828 ascii_str(repo[rev].hex())
827 829 for rev in revrange(repo, list(map(ascii_bytes, commit_filter)))
828 830 ]
829 831 return revisions
830 832
831 833 return _rev_range(context_uid, repo_id, sorted(commit_filter))
832 834
833 835 @reraise_safe_exceptions
834 836 def rev_range_hash(self, wire, node):
835 837 repo = self._factory.repo(wire)
836 838
837 839 def get_revs(repo, rev_opt):
838 840 if rev_opt:
839 841 revs = revrange(repo, rev_opt)
840 842 if len(revs) == 0:
841 843 return (nullrev, nullrev)
842 844 return max(revs), min(revs)
843 845 else:
844 846 return len(repo) - 1, 0
845 847
846 848 stop, start = get_revs(repo, [node + ':'])
847 849 revs = [ascii_str(repo[r].hex()) for r in range(start, stop + 1)]
848 850 return revs
849 851
850 852 @reraise_safe_exceptions
851 853 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
852 854 org_path = safe_bytes(wire["path"])
853 855 other_path = safe_bytes(kwargs.pop('other_path', ''))
854 856
855 857 # case when we want to compare two independent repositories
856 858 if other_path and other_path != wire["path"]:
857 859 baseui = self._factory._create_config(wire["config"])
858 860 repo = unionrepo.makeunionrepository(baseui, other_path, org_path)
859 861 else:
860 862 repo = self._factory.repo(wire)
861 863 return list(repo.revs(rev_spec, *args))
862 864
863 865 @reraise_safe_exceptions
864 866 def verify(self, wire,):
865 867 repo = self._factory.repo(wire)
866 868 baseui = self._factory._create_config(wire['config'])
867 869
868 870 baseui, output = patch_ui_message_output(baseui)
869 871
870 872 repo.ui = baseui
871 873 verify.verify(repo)
872 874 return output.getvalue()
873 875
874 876 @reraise_safe_exceptions
875 877 def hg_update_cache(self, wire,):
876 878 repo = self._factory.repo(wire)
877 879 baseui = self._factory._create_config(wire['config'])
878 880 baseui, output = patch_ui_message_output(baseui)
879 881
880 882 repo.ui = baseui
881 883 with repo.wlock(), repo.lock():
882 884 repo.updatecaches(full=True)
883 885
884 886 return output.getvalue()
885 887
886 888 @reraise_safe_exceptions
887 889 def hg_rebuild_fn_cache(self, wire,):
888 890 repo = self._factory.repo(wire)
889 891 baseui = self._factory._create_config(wire['config'])
890 892 baseui, output = patch_ui_message_output(baseui)
891 893
892 894 repo.ui = baseui
893 895
894 896 repair.rebuildfncache(baseui, repo)
895 897
896 898 return output.getvalue()
897 899
898 900 @reraise_safe_exceptions
899 901 def tags(self, wire):
900 902 cache_on, context_uid, repo_id = self._cache_on(wire)
901 903 region = self._region(wire)
902 904
903 905 @region.conditional_cache_on_arguments(condition=cache_on)
904 906 def _tags(_context_uid, _repo_id):
905 907 repo = self._factory.repo(wire)
906 908 return {safe_str(name): ascii_str(hex(sha)) for name, sha in repo.tags().items()}
907 909
908 910 return _tags(context_uid, repo_id)
909 911
910 912 @reraise_safe_exceptions
911 913 def update(self, wire, node='', clean=False):
912 914 repo = self._factory.repo(wire)
913 915 baseui = self._factory._create_config(wire['config'])
914 916 node = safe_bytes(node)
915 917
916 918 commands.update(baseui, repo, node=node, clean=clean)
917 919
918 920 @reraise_safe_exceptions
919 921 def identify(self, wire):
920 922 repo = self._factory.repo(wire)
921 923 baseui = self._factory._create_config(wire['config'])
922 924 output = io.BytesIO()
923 925 baseui.write = output.write
924 926 # This is required to get a full node id
925 927 baseui.debugflag = True
926 928 commands.identify(baseui, repo, id=True)
927 929
928 930 return output.getvalue()
929 931
930 932 @reraise_safe_exceptions
931 933 def heads(self, wire, branch=None):
932 934 repo = self._factory.repo(wire)
933 935 baseui = self._factory._create_config(wire['config'])
934 936 output = io.BytesIO()
935 937
936 938 def write(data, **unused_kwargs):
937 939 output.write(data)
938 940
939 941 baseui.write = write
940 942 if branch:
941 943 args = [safe_bytes(branch)]
942 944 else:
943 945 args = []
944 946 commands.heads(baseui, repo, template=b'{node} ', *args)
945 947
946 948 return output.getvalue()
947 949
948 950 @reraise_safe_exceptions
949 951 def ancestor(self, wire, revision1, revision2):
950 952 repo = self._factory.repo(wire)
951 953 changelog = repo.changelog
952 954 lookup = repo.lookup
953 955 a = changelog.ancestor(lookup(safe_bytes(revision1)), lookup(safe_bytes(revision2)))
954 956 return hex(a)
955 957
956 958 @reraise_safe_exceptions
957 959 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
958 960 baseui = self._factory._create_config(wire["config"], hooks=hooks)
959 961 clone(baseui, safe_bytes(source), safe_bytes(dest), noupdate=not update_after_clone)
960 962
961 963 @reraise_safe_exceptions
962 964 def commitctx(self, wire, message, parents, commit_time, commit_timezone, user, files, extra, removed, updated):
963 965
964 966 repo = self._factory.repo(wire)
965 967 baseui = self._factory._create_config(wire['config'])
966 968 publishing = baseui.configbool(b'phases', b'publish')
967 969
968 970 def _filectxfn(_repo, ctx, path: bytes):
969 971 """
970 972 Marks given path as added/changed/removed in a given _repo. This is
971 973 for internal mercurial commit function.
972 974 """
973 975
974 976 # check if this path is removed
975 977 if safe_str(path) in removed:
976 978 # returning None is a way to mark node for removal
977 979 return None
978 980
979 981 # check if this path is added
980 982 for node in updated:
981 983 if safe_bytes(node['path']) == path:
982 984 return memfilectx(
983 985 _repo,
984 986 changectx=ctx,
985 987 path=safe_bytes(node['path']),
986 988 data=safe_bytes(node['content']),
987 989 islink=False,
988 990 isexec=bool(node['mode'] & stat.S_IXUSR),
989 991 copysource=False)
990 992 abort_exc = exceptions.AbortException()
991 993 raise abort_exc(f"Given path haven't been marked as added, changed or removed ({path})")
992 994
993 995 if publishing:
994 996 new_commit_phase = b'public'
995 997 else:
996 998 new_commit_phase = b'draft'
997 999 with repo.ui.configoverride({(b'phases', b'new-commit'): new_commit_phase}):
998 1000 kwargs = {safe_bytes(k): safe_bytes(v) for k, v in extra.items()}
999 1001 commit_ctx = memctx(
1000 1002 repo=repo,
1001 1003 parents=parents,
1002 1004 text=safe_bytes(message),
1003 1005 files=[safe_bytes(x) for x in files],
1004 1006 filectxfn=_filectxfn,
1005 1007 user=safe_bytes(user),
1006 1008 date=(commit_time, commit_timezone),
1007 1009 extra=kwargs)
1008 1010
1009 1011 n = repo.commitctx(commit_ctx)
1010 1012 new_id = hex(n)
1011 1013
1012 1014 return new_id
1013 1015
1014 1016 @reraise_safe_exceptions
1015 1017 def pull(self, wire, url, commit_ids=None):
1016 1018 repo = self._factory.repo(wire)
1017 1019 # Disable any prompts for this repo
1018 1020 repo.ui.setconfig(b'ui', b'interactive', b'off', b'-y')
1019 1021
1020 1022 remote = peer(repo, {}, safe_bytes(url))
1021 1023 # Disable any prompts for this remote
1022 1024 remote.ui.setconfig(b'ui', b'interactive', b'off', b'-y')
1023 1025
1024 1026 if commit_ids:
1025 1027 commit_ids = [bin(commit_id) for commit_id in commit_ids]
1026 1028
1027 1029 return exchange.pull(
1028 1030 repo, remote, heads=commit_ids, force=None).cgresult
1029 1031
1030 1032 @reraise_safe_exceptions
1031 1033 def pull_cmd(self, wire, source, bookmark='', branch='', revision='', hooks=True):
1032 1034 repo = self._factory.repo(wire)
1033 1035 baseui = self._factory._create_config(wire['config'], hooks=hooks)
1034 1036
1035 1037 source = safe_bytes(source)
1036 1038
1037 1039 # Mercurial internally has a lot of logic that checks ONLY if
1038 1040 # option is defined, we just pass those if they are defined then
1039 1041 opts = {}
1040 1042
1041 1043 if bookmark:
1042 1044 opts['bookmark'] = [safe_bytes(x) for x in bookmark] \
1043 1045 if isinstance(bookmark, list) else safe_bytes(bookmark)
1044 1046
1045 1047 if branch:
1046 1048 opts['branch'] = [safe_bytes(x) for x in branch] \
1047 1049 if isinstance(branch, list) else safe_bytes(branch)
1048 1050
1049 1051 if revision:
1050 1052 opts['rev'] = [safe_bytes(x) for x in revision] \
1051 1053 if isinstance(revision, list) else safe_bytes(revision)
1052 1054
1053 1055 commands.pull(baseui, repo, source, **opts)
1054 1056
1055 1057 @reraise_safe_exceptions
1056 1058 def push(self, wire, revisions, dest_path, hooks: bool = True, push_branches: bool = False):
1057 1059 repo = self._factory.repo(wire)
1058 1060 baseui = self._factory._create_config(wire['config'], hooks=hooks)
1059 1061
1060 1062 revisions = [safe_bytes(x) for x in revisions] \
1061 1063 if isinstance(revisions, list) else safe_bytes(revisions)
1062 1064
1063 1065 commands.push(baseui, repo, safe_bytes(dest_path),
1064 1066 rev=revisions,
1065 1067 new_branch=push_branches)
1066 1068
1067 1069 @reraise_safe_exceptions
1068 1070 def strip(self, wire, revision, update, backup):
1069 1071 repo = self._factory.repo(wire)
1070 1072 ctx = self._get_ctx(repo, revision)
1071 1073 hgext_strip.strip(
1072 1074 repo.baseui, repo, ctx.node(), update=update, backup=backup)
1073 1075
1074 1076 @reraise_safe_exceptions
1075 1077 def get_unresolved_files(self, wire):
1076 1078 repo = self._factory.repo(wire)
1077 1079
1078 1080 log.debug('Calculating unresolved files for repo: %s', repo)
1079 1081 output = io.BytesIO()
1080 1082
1081 1083 def write(data, **unused_kwargs):
1082 1084 output.write(data)
1083 1085
1084 1086 baseui = self._factory._create_config(wire['config'])
1085 1087 baseui.write = write
1086 1088
1087 1089 commands.resolve(baseui, repo, list=True)
1088 1090 unresolved = output.getvalue().splitlines(0)
1089 1091 return unresolved
1090 1092
1091 1093 @reraise_safe_exceptions
1092 1094 def merge(self, wire, revision):
1093 1095 repo = self._factory.repo(wire)
1094 1096 baseui = self._factory._create_config(wire['config'])
1095 1097 repo.ui.setconfig(b'ui', b'merge', b'internal:dump')
1096 1098
1097 1099 # In case of sub repositories are used mercurial prompts the user in
1098 1100 # case of merge conflicts or different sub repository sources. By
1099 1101 # setting the interactive flag to `False` mercurial doesn't prompt the
1100 1102 # used but instead uses a default value.
1101 1103 repo.ui.setconfig(b'ui', b'interactive', False)
1102 1104 commands.merge(baseui, repo, rev=safe_bytes(revision))
1103 1105
1104 1106 @reraise_safe_exceptions
1105 1107 def merge_state(self, wire):
1106 1108 repo = self._factory.repo(wire)
1107 1109 repo.ui.setconfig(b'ui', b'merge', b'internal:dump')
1108 1110
1109 1111 # In case of sub repositories are used mercurial prompts the user in
1110 1112 # case of merge conflicts or different sub repository sources. By
1111 1113 # setting the interactive flag to `False` mercurial doesn't prompt the
1112 1114 # used but instead uses a default value.
1113 1115 repo.ui.setconfig(b'ui', b'interactive', False)
1114 1116 ms = hg_merge.mergestate(repo)
1115 1117 return [x for x in ms.unresolved()]
1116 1118
1117 1119 @reraise_safe_exceptions
1118 1120 def commit(self, wire, message, username, close_branch=False):
1119 1121 repo = self._factory.repo(wire)
1120 1122 baseui = self._factory._create_config(wire['config'])
1121 1123 repo.ui.setconfig(b'ui', b'username', safe_bytes(username))
1122 1124 commands.commit(baseui, repo, message=safe_bytes(message), close_branch=close_branch)
1123 1125
1124 1126 @reraise_safe_exceptions
1125 1127 def rebase(self, wire, source='', dest='', abort=False):
1126 1128 repo = self._factory.repo(wire)
1127 1129 baseui = self._factory._create_config(wire['config'])
1128 1130 repo.ui.setconfig(b'ui', b'merge', b'internal:dump')
1129 1131 # In case of sub repositories are used mercurial prompts the user in
1130 1132 # case of merge conflicts or different sub repository sources. By
1131 1133 # setting the interactive flag to `False` mercurial doesn't prompt the
1132 1134 # used but instead uses a default value.
1133 1135 repo.ui.setconfig(b'ui', b'interactive', False)
1134 1136
1135 1137 rebase.rebase(baseui, repo, base=safe_bytes(source or ''), dest=safe_bytes(dest or ''),
1136 1138 abort=abort, keep=not abort)
1137 1139
1138 1140 @reraise_safe_exceptions
1139 1141 def tag(self, wire, name, revision, message, local, user, tag_time, tag_timezone):
1140 1142 repo = self._factory.repo(wire)
1141 1143 ctx = self._get_ctx(repo, revision)
1142 1144 node = ctx.node()
1143 1145
1144 1146 date = (tag_time, tag_timezone)
1145 1147 try:
1146 1148 hg_tag.tag(repo, safe_bytes(name), node, safe_bytes(message), local, safe_bytes(user), date)
1147 1149 except Abort as e:
1148 1150 log.exception("Tag operation aborted")
1149 1151 # Exception can contain unicode which we convert
1150 1152 raise exceptions.AbortException(e)(repr(e))
1151 1153
1152 1154 @reraise_safe_exceptions
1153 1155 def bookmark(self, wire, bookmark, revision=''):
1154 1156 repo = self._factory.repo(wire)
1155 1157 baseui = self._factory._create_config(wire['config'])
1156 1158 revision = revision or ''
1157 1159 commands.bookmark(baseui, repo, safe_bytes(bookmark), rev=safe_bytes(revision), force=True)
1158 1160
1159 1161 @reraise_safe_exceptions
1160 1162 def install_hooks(self, wire, force=False):
1161 1163 # we don't need any special hooks for Mercurial
1162 1164 pass
1163 1165
1164 1166 @reraise_safe_exceptions
1165 1167 def get_hooks_info(self, wire):
1166 1168 return {
1167 1169 'pre_version': vcsserver.__version__,
1168 1170 'post_version': vcsserver.__version__,
1169 1171 }
1170 1172
1171 1173 @reraise_safe_exceptions
1172 1174 def set_head_ref(self, wire, head_name):
1173 1175 pass
1174 1176
1175 1177 @reraise_safe_exceptions
1176 1178 def archive_repo(self, wire, archive_name_key, kind, mtime, archive_at_path,
1177 1179 archive_dir_name, commit_id, cache_config):
1178 1180
1179 1181 def file_walker(_commit_id, path):
1180 1182 repo = self._factory.repo(wire)
1181 1183 ctx = repo[_commit_id]
1182 1184 is_root = path in ['', '/']
1183 1185 if is_root:
1184 1186 matcher = alwaysmatcher(badfn=None)
1185 1187 else:
1186 1188 matcher = patternmatcher('', [(b'glob', safe_bytes(path)+b'/**', b'')], badfn=None)
1187 1189 file_iter = ctx.manifest().walk(matcher)
1188 1190
1189 1191 for fn in file_iter:
1190 1192 file_path = fn
1191 1193 flags = ctx.flags(fn)
1192 1194 mode = b'x' in flags and 0o755 or 0o644
1193 1195 is_link = b'l' in flags
1194 1196
1195 1197 yield ArchiveNode(file_path, mode, is_link, ctx[fn].data)
1196 1198
1197 1199 return store_archive_in_cache(
1198 1200 file_walker, archive_name_key, kind, mtime, archive_at_path, archive_dir_name, commit_id, cache_config=cache_config)
1199 1201
General Comments 0
You need to be logged in to leave comments. Login now