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