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