##// END OF EJS Templates
sshpeer: enable+fix warning about sshpeers not being closed explicitly...
Valentin Gatien-Baron -
r47804:a4c19a16 default
parent child Browse files
Show More
@@ -1,1376 +1,1382 b''
1 1 # Infinite push
2 2 #
3 3 # Copyright 2016 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """ store some pushes in a remote blob store on the server (EXPERIMENTAL)
8 8
9 9 IMPORTANT: if you use this extension, please contact
10 10 mercurial-devel@mercurial-scm.org ASAP. This extension is believed to
11 11 be unused and barring learning of users of this functionality, we will
12 12 delete this code at the end of 2020.
13 13
14 14 [infinitepush]
15 15 # Server-side and client-side option. Pattern of the infinitepush bookmark
16 16 branchpattern = PATTERN
17 17
18 18 # Server or client
19 19 server = False
20 20
21 21 # Server-side option. Possible values: 'disk' or 'sql'. Fails if not set
22 22 indextype = disk
23 23
24 24 # Server-side option. Used only if indextype=sql.
25 25 # Format: 'IP:PORT:DB_NAME:USER:PASSWORD'
26 26 sqlhost = IP:PORT:DB_NAME:USER:PASSWORD
27 27
28 28 # Server-side option. Used only if indextype=disk.
29 29 # Filesystem path to the index store
30 30 indexpath = PATH
31 31
32 32 # Server-side option. Possible values: 'disk' or 'external'
33 33 # Fails if not set
34 34 storetype = disk
35 35
36 36 # Server-side option.
37 37 # Path to the binary that will save bundle to the bundlestore
38 38 # Formatted cmd line will be passed to it (see `put_args`)
39 39 put_binary = put
40 40
41 41 # Serser-side option. Used only if storetype=external.
42 42 # Format cmd-line string for put binary. Placeholder: {filename}
43 43 put_args = {filename}
44 44
45 45 # Server-side option.
46 46 # Path to the binary that get bundle from the bundlestore.
47 47 # Formatted cmd line will be passed to it (see `get_args`)
48 48 get_binary = get
49 49
50 50 # Serser-side option. Used only if storetype=external.
51 51 # Format cmd-line string for get binary. Placeholders: {filename} {handle}
52 52 get_args = {filename} {handle}
53 53
54 54 # Server-side option
55 55 logfile = FIlE
56 56
57 57 # Server-side option
58 58 loglevel = DEBUG
59 59
60 60 # Server-side option. Used only if indextype=sql.
61 61 # Sets mysql wait_timeout option.
62 62 waittimeout = 300
63 63
64 64 # Server-side option. Used only if indextype=sql.
65 65 # Sets mysql innodb_lock_wait_timeout option.
66 66 locktimeout = 120
67 67
68 68 # Server-side option. Used only if indextype=sql.
69 69 # Name of the repository
70 70 reponame = ''
71 71
72 72 # Client-side option. Used by --list-remote option. List of remote scratch
73 73 # patterns to list if no patterns are specified.
74 74 defaultremotepatterns = ['*']
75 75
76 76 # Instructs infinitepush to forward all received bundle2 parts to the
77 77 # bundle for storage. Defaults to False.
78 78 storeallparts = True
79 79
80 80 # routes each incoming push to the bundlestore. defaults to False
81 81 pushtobundlestore = True
82 82
83 83 [remotenames]
84 84 # Client-side option
85 85 # This option should be set only if remotenames extension is enabled.
86 86 # Whether remote bookmarks are tracked by remotenames extension.
87 87 bookmarks = True
88 88 """
89 89
90 90 from __future__ import absolute_import
91 91
92 92 import collections
93 93 import contextlib
94 94 import errno
95 95 import functools
96 96 import logging
97 97 import os
98 98 import random
99 99 import re
100 100 import socket
101 101 import subprocess
102 102 import time
103 103
104 104 from mercurial.node import (
105 105 bin,
106 106 hex,
107 107 )
108 108
109 109 from mercurial.i18n import _
110 110
111 111 from mercurial.pycompat import (
112 112 getattr,
113 113 open,
114 114 )
115 115
116 116 from mercurial.utils import (
117 117 procutil,
118 118 stringutil,
119 119 )
120 120
121 121 from mercurial import (
122 122 bundle2,
123 123 changegroup,
124 124 commands,
125 125 discovery,
126 126 encoding,
127 127 error,
128 128 exchange,
129 129 extensions,
130 130 hg,
131 131 localrepo,
132 132 phases,
133 133 pushkey,
134 134 pycompat,
135 135 registrar,
136 136 util,
137 137 wireprototypes,
138 138 wireprotov1peer,
139 139 wireprotov1server,
140 140 )
141 141
142 142 from . import (
143 143 bundleparts,
144 144 common,
145 145 )
146 146
147 147 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
148 148 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
149 149 # be specifying the version(s) of Mercurial they are tested with, or
150 150 # leave the attribute unspecified.
151 151 testedwith = b'ships-with-hg-core'
152 152
153 153 configtable = {}
154 154 configitem = registrar.configitem(configtable)
155 155
156 156 configitem(
157 157 b'infinitepush',
158 158 b'server',
159 159 default=False,
160 160 )
161 161 configitem(
162 162 b'infinitepush',
163 163 b'storetype',
164 164 default=b'',
165 165 )
166 166 configitem(
167 167 b'infinitepush',
168 168 b'indextype',
169 169 default=b'',
170 170 )
171 171 configitem(
172 172 b'infinitepush',
173 173 b'indexpath',
174 174 default=b'',
175 175 )
176 176 configitem(
177 177 b'infinitepush',
178 178 b'storeallparts',
179 179 default=False,
180 180 )
181 181 configitem(
182 182 b'infinitepush',
183 183 b'reponame',
184 184 default=b'',
185 185 )
186 186 configitem(
187 187 b'scratchbranch',
188 188 b'storepath',
189 189 default=b'',
190 190 )
191 191 configitem(
192 192 b'infinitepush',
193 193 b'branchpattern',
194 194 default=b'',
195 195 )
196 196 configitem(
197 197 b'infinitepush',
198 198 b'pushtobundlestore',
199 199 default=False,
200 200 )
201 201 configitem(
202 202 b'experimental',
203 203 b'server-bundlestore-bookmark',
204 204 default=b'',
205 205 )
206 206 configitem(
207 207 b'experimental',
208 208 b'infinitepush-scratchpush',
209 209 default=False,
210 210 )
211 211
212 212 experimental = b'experimental'
213 213 configbookmark = b'server-bundlestore-bookmark'
214 214 configscratchpush = b'infinitepush-scratchpush'
215 215
216 216 scratchbranchparttype = bundleparts.scratchbranchparttype
217 217 revsetpredicate = registrar.revsetpredicate()
218 218 templatekeyword = registrar.templatekeyword()
219 219 _scratchbranchmatcher = lambda x: False
220 220 _maybehash = re.compile('^[a-f0-9]+$').search
221 221
222 222
223 223 def _buildexternalbundlestore(ui):
224 224 put_args = ui.configlist(b'infinitepush', b'put_args', [])
225 225 put_binary = ui.config(b'infinitepush', b'put_binary')
226 226 if not put_binary:
227 227 raise error.Abort(b'put binary is not specified')
228 228 get_args = ui.configlist(b'infinitepush', b'get_args', [])
229 229 get_binary = ui.config(b'infinitepush', b'get_binary')
230 230 if not get_binary:
231 231 raise error.Abort(b'get binary is not specified')
232 232 from . import store
233 233
234 234 return store.externalbundlestore(put_binary, put_args, get_binary, get_args)
235 235
236 236
237 237 def _buildsqlindex(ui):
238 238 sqlhost = ui.config(b'infinitepush', b'sqlhost')
239 239 if not sqlhost:
240 240 raise error.Abort(_(b'please set infinitepush.sqlhost'))
241 241 host, port, db, user, password = sqlhost.split(b':')
242 242 reponame = ui.config(b'infinitepush', b'reponame')
243 243 if not reponame:
244 244 raise error.Abort(_(b'please set infinitepush.reponame'))
245 245
246 246 logfile = ui.config(b'infinitepush', b'logfile', b'')
247 247 waittimeout = ui.configint(b'infinitepush', b'waittimeout', 300)
248 248 locktimeout = ui.configint(b'infinitepush', b'locktimeout', 120)
249 249 from . import sqlindexapi
250 250
251 251 return sqlindexapi.sqlindexapi(
252 252 reponame,
253 253 host,
254 254 port,
255 255 db,
256 256 user,
257 257 password,
258 258 logfile,
259 259 _getloglevel(ui),
260 260 waittimeout=waittimeout,
261 261 locktimeout=locktimeout,
262 262 )
263 263
264 264
265 265 def _getloglevel(ui):
266 266 loglevel = ui.config(b'infinitepush', b'loglevel', b'DEBUG')
267 267 numeric_loglevel = getattr(logging, loglevel.upper(), None)
268 268 if not isinstance(numeric_loglevel, int):
269 269 raise error.Abort(_(b'invalid log level %s') % loglevel)
270 270 return numeric_loglevel
271 271
272 272
273 273 def _tryhoist(ui, remotebookmark):
274 274 """returns a bookmarks with hoisted part removed
275 275
276 276 Remotenames extension has a 'hoist' config that allows to use remote
277 277 bookmarks without specifying remote path. For example, 'hg update master'
278 278 works as well as 'hg update remote/master'. We want to allow the same in
279 279 infinitepush.
280 280 """
281 281
282 282 if common.isremotebooksenabled(ui):
283 283 hoist = ui.config(b'remotenames', b'hoistedpeer') + b'/'
284 284 if remotebookmark.startswith(hoist):
285 285 return remotebookmark[len(hoist) :]
286 286 return remotebookmark
287 287
288 288
289 289 class bundlestore(object):
290 290 def __init__(self, repo):
291 291 self._repo = repo
292 292 storetype = self._repo.ui.config(b'infinitepush', b'storetype')
293 293 if storetype == b'disk':
294 294 from . import store
295 295
296 296 self.store = store.filebundlestore(self._repo.ui, self._repo)
297 297 elif storetype == b'external':
298 298 self.store = _buildexternalbundlestore(self._repo.ui)
299 299 else:
300 300 raise error.Abort(
301 301 _(b'unknown infinitepush store type specified %s') % storetype
302 302 )
303 303
304 304 indextype = self._repo.ui.config(b'infinitepush', b'indextype')
305 305 if indextype == b'disk':
306 306 from . import fileindexapi
307 307
308 308 self.index = fileindexapi.fileindexapi(self._repo)
309 309 elif indextype == b'sql':
310 310 self.index = _buildsqlindex(self._repo.ui)
311 311 else:
312 312 raise error.Abort(
313 313 _(b'unknown infinitepush index type specified %s') % indextype
314 314 )
315 315
316 316
317 317 def _isserver(ui):
318 318 return ui.configbool(b'infinitepush', b'server')
319 319
320 320
321 321 def reposetup(ui, repo):
322 322 if _isserver(ui) and repo.local():
323 323 repo.bundlestore = bundlestore(repo)
324 324
325 325
326 326 def extsetup(ui):
327 327 commonsetup(ui)
328 328 if _isserver(ui):
329 329 serverextsetup(ui)
330 330 else:
331 331 clientextsetup(ui)
332 332
333 333
334 334 def commonsetup(ui):
335 335 wireprotov1server.commands[b'listkeyspatterns'] = (
336 336 wireprotolistkeyspatterns,
337 337 b'namespace patterns',
338 338 )
339 339 scratchbranchpat = ui.config(b'infinitepush', b'branchpattern')
340 340 if scratchbranchpat:
341 341 global _scratchbranchmatcher
342 342 kind, pat, _scratchbranchmatcher = stringutil.stringmatcher(
343 343 scratchbranchpat
344 344 )
345 345
346 346
347 347 def serverextsetup(ui):
348 348 origpushkeyhandler = bundle2.parthandlermapping[b'pushkey']
349 349
350 350 def newpushkeyhandler(*args, **kwargs):
351 351 bundle2pushkey(origpushkeyhandler, *args, **kwargs)
352 352
353 353 newpushkeyhandler.params = origpushkeyhandler.params
354 354 bundle2.parthandlermapping[b'pushkey'] = newpushkeyhandler
355 355
356 356 orighandlephasehandler = bundle2.parthandlermapping[b'phase-heads']
357 357 newphaseheadshandler = lambda *args, **kwargs: bundle2handlephases(
358 358 orighandlephasehandler, *args, **kwargs
359 359 )
360 360 newphaseheadshandler.params = orighandlephasehandler.params
361 361 bundle2.parthandlermapping[b'phase-heads'] = newphaseheadshandler
362 362
363 363 extensions.wrapfunction(
364 364 localrepo.localrepository, b'listkeys', localrepolistkeys
365 365 )
366 366 wireprotov1server.commands[b'lookup'] = (
367 367 _lookupwrap(wireprotov1server.commands[b'lookup'][0]),
368 368 b'key',
369 369 )
370 370 extensions.wrapfunction(exchange, b'getbundlechunks', getbundlechunks)
371 371
372 372 extensions.wrapfunction(bundle2, b'processparts', processparts)
373 373
374 374
375 375 def clientextsetup(ui):
376 376 entry = extensions.wrapcommand(commands.table, b'push', _push)
377 377
378 378 entry[1].append(
379 379 (
380 380 b'',
381 381 b'bundle-store',
382 382 None,
383 383 _(b'force push to go to bundle store (EXPERIMENTAL)'),
384 384 )
385 385 )
386 386
387 387 extensions.wrapcommand(commands.table, b'pull', _pull)
388 388
389 389 extensions.wrapfunction(discovery, b'checkheads', _checkheads)
390 390
391 391 wireprotov1peer.wirepeer.listkeyspatterns = listkeyspatterns
392 392
393 393 partorder = exchange.b2partsgenorder
394 394 index = partorder.index(b'changeset')
395 395 partorder.insert(
396 396 index, partorder.pop(partorder.index(scratchbranchparttype))
397 397 )
398 398
399 399
400 400 def _checkheads(orig, pushop):
401 401 if pushop.ui.configbool(experimental, configscratchpush, False):
402 402 return
403 403 return orig(pushop)
404 404
405 405
406 406 def wireprotolistkeyspatterns(repo, proto, namespace, patterns):
407 407 patterns = wireprototypes.decodelist(patterns)
408 408 d = pycompat.iteritems(repo.listkeys(encoding.tolocal(namespace), patterns))
409 409 return pushkey.encodekeys(d)
410 410
411 411
412 412 def localrepolistkeys(orig, self, namespace, patterns=None):
413 413 if namespace == b'bookmarks' and patterns:
414 414 index = self.bundlestore.index
415 415 results = {}
416 416 bookmarks = orig(self, namespace)
417 417 for pattern in patterns:
418 418 results.update(index.getbookmarks(pattern))
419 419 if pattern.endswith(b'*'):
420 420 pattern = b're:^' + pattern[:-1] + b'.*'
421 421 kind, pat, matcher = stringutil.stringmatcher(pattern)
422 422 for bookmark, node in pycompat.iteritems(bookmarks):
423 423 if matcher(bookmark):
424 424 results[bookmark] = node
425 425 return results
426 426 else:
427 427 return orig(self, namespace)
428 428
429 429
430 430 @wireprotov1peer.batchable
431 431 def listkeyspatterns(self, namespace, patterns):
432 432 if not self.capable(b'pushkey'):
433 433 yield {}, None
434 434 f = wireprotov1peer.future()
435 435 self.ui.debug(b'preparing listkeys for "%s"\n' % namespace)
436 436 yield {
437 437 b'namespace': encoding.fromlocal(namespace),
438 438 b'patterns': wireprototypes.encodelist(patterns),
439 439 }, f
440 440 d = f.value
441 441 self.ui.debug(
442 442 b'received listkey for "%s": %i bytes\n' % (namespace, len(d))
443 443 )
444 444 yield pushkey.decodekeys(d)
445 445
446 446
447 447 def _readbundlerevs(bundlerepo):
448 448 return list(bundlerepo.revs(b'bundle()'))
449 449
450 450
451 451 def _includefilelogstobundle(bundlecaps, bundlerepo, bundlerevs, ui):
452 452 """Tells remotefilelog to include all changed files to the changegroup
453 453
454 454 By default remotefilelog doesn't include file content to the changegroup.
455 455 But we need to include it if we are fetching from bundlestore.
456 456 """
457 457 changedfiles = set()
458 458 cl = bundlerepo.changelog
459 459 for r in bundlerevs:
460 460 # [3] means changed files
461 461 changedfiles.update(cl.read(r)[3])
462 462 if not changedfiles:
463 463 return bundlecaps
464 464
465 465 changedfiles = b'\0'.join(changedfiles)
466 466 newcaps = []
467 467 appended = False
468 468 for cap in bundlecaps or []:
469 469 if cap.startswith(b'excludepattern='):
470 470 newcaps.append(b'\0'.join((cap, changedfiles)))
471 471 appended = True
472 472 else:
473 473 newcaps.append(cap)
474 474 if not appended:
475 475 # Not found excludepattern cap. Just append it
476 476 newcaps.append(b'excludepattern=' + changedfiles)
477 477
478 478 return newcaps
479 479
480 480
481 481 def _rebundle(bundlerepo, bundleroots, unknownhead):
482 482 """
483 483 Bundle may include more revision then user requested. For example,
484 484 if user asks for revision but bundle also consists its descendants.
485 485 This function will filter out all revision that user is not requested.
486 486 """
487 487 parts = []
488 488
489 489 version = b'02'
490 490 outgoing = discovery.outgoing(
491 491 bundlerepo, commonheads=bundleroots, ancestorsof=[unknownhead]
492 492 )
493 493 cgstream = changegroup.makestream(bundlerepo, outgoing, version, b'pull')
494 494 cgstream = util.chunkbuffer(cgstream).read()
495 495 cgpart = bundle2.bundlepart(b'changegroup', data=cgstream)
496 496 cgpart.addparam(b'version', version)
497 497 parts.append(cgpart)
498 498
499 499 return parts
500 500
501 501
502 502 def _getbundleroots(oldrepo, bundlerepo, bundlerevs):
503 503 cl = bundlerepo.changelog
504 504 bundleroots = []
505 505 for rev in bundlerevs:
506 506 node = cl.node(rev)
507 507 parents = cl.parents(node)
508 508 for parent in parents:
509 509 # include all revs that exist in the main repo
510 510 # to make sure that bundle may apply client-side
511 511 if parent in oldrepo:
512 512 bundleroots.append(parent)
513 513 return bundleroots
514 514
515 515
516 516 def _needsrebundling(head, bundlerepo):
517 517 bundleheads = list(bundlerepo.revs(b'heads(bundle())'))
518 518 return not (
519 519 len(bundleheads) == 1 and bundlerepo[bundleheads[0]].node() == head
520 520 )
521 521
522 522
523 523 def _generateoutputparts(head, bundlerepo, bundleroots, bundlefile):
524 524 """generates bundle that will be send to the user
525 525
526 526 returns tuple with raw bundle string and bundle type
527 527 """
528 528 parts = []
529 529 if not _needsrebundling(head, bundlerepo):
530 530 with util.posixfile(bundlefile, b"rb") as f:
531 531 unbundler = exchange.readbundle(bundlerepo.ui, f, bundlefile)
532 532 if isinstance(unbundler, changegroup.cg1unpacker):
533 533 part = bundle2.bundlepart(
534 534 b'changegroup', data=unbundler._stream.read()
535 535 )
536 536 part.addparam(b'version', b'01')
537 537 parts.append(part)
538 538 elif isinstance(unbundler, bundle2.unbundle20):
539 539 haschangegroup = False
540 540 for part in unbundler.iterparts():
541 541 if part.type == b'changegroup':
542 542 haschangegroup = True
543 543 newpart = bundle2.bundlepart(part.type, data=part.read())
544 544 for key, value in pycompat.iteritems(part.params):
545 545 newpart.addparam(key, value)
546 546 parts.append(newpart)
547 547
548 548 if not haschangegroup:
549 549 raise error.Abort(
550 550 b'unexpected bundle without changegroup part, '
551 551 + b'head: %s' % hex(head),
552 552 hint=b'report to administrator',
553 553 )
554 554 else:
555 555 raise error.Abort(b'unknown bundle type')
556 556 else:
557 557 parts = _rebundle(bundlerepo, bundleroots, head)
558 558
559 559 return parts
560 560
561 561
562 562 def getbundlechunks(orig, repo, source, heads=None, bundlecaps=None, **kwargs):
563 563 heads = heads or []
564 564 # newheads are parents of roots of scratch bundles that were requested
565 565 newphases = {}
566 566 scratchbundles = []
567 567 newheads = []
568 568 scratchheads = []
569 569 nodestobundle = {}
570 570 allbundlestocleanup = []
571 571 try:
572 572 for head in heads:
573 573 if not repo.changelog.index.has_node(head):
574 574 if head not in nodestobundle:
575 575 newbundlefile = common.downloadbundle(repo, head)
576 576 bundlepath = b"bundle:%s+%s" % (repo.root, newbundlefile)
577 577 bundlerepo = hg.repository(repo.ui, bundlepath)
578 578
579 579 allbundlestocleanup.append((bundlerepo, newbundlefile))
580 580 bundlerevs = set(_readbundlerevs(bundlerepo))
581 581 bundlecaps = _includefilelogstobundle(
582 582 bundlecaps, bundlerepo, bundlerevs, repo.ui
583 583 )
584 584 cl = bundlerepo.changelog
585 585 bundleroots = _getbundleroots(repo, bundlerepo, bundlerevs)
586 586 for rev in bundlerevs:
587 587 node = cl.node(rev)
588 588 newphases[hex(node)] = str(phases.draft)
589 589 nodestobundle[node] = (
590 590 bundlerepo,
591 591 bundleroots,
592 592 newbundlefile,
593 593 )
594 594
595 595 scratchbundles.append(
596 596 _generateoutputparts(head, *nodestobundle[head])
597 597 )
598 598 newheads.extend(bundleroots)
599 599 scratchheads.append(head)
600 600 finally:
601 601 for bundlerepo, bundlefile in allbundlestocleanup:
602 602 bundlerepo.close()
603 603 try:
604 604 os.unlink(bundlefile)
605 605 except (IOError, OSError):
606 606 # if we can't cleanup the file then just ignore the error,
607 607 # no need to fail
608 608 pass
609 609
610 610 pullfrombundlestore = bool(scratchbundles)
611 611 wrappedchangegrouppart = False
612 612 wrappedlistkeys = False
613 613 oldchangegrouppart = exchange.getbundle2partsmapping[b'changegroup']
614 614 try:
615 615
616 616 def _changegrouppart(bundler, *args, **kwargs):
617 617 # Order is important here. First add non-scratch part
618 618 # and only then add parts with scratch bundles because
619 619 # non-scratch part contains parents of roots of scratch bundles.
620 620 result = oldchangegrouppart(bundler, *args, **kwargs)
621 621 for bundle in scratchbundles:
622 622 for part in bundle:
623 623 bundler.addpart(part)
624 624 return result
625 625
626 626 exchange.getbundle2partsmapping[b'changegroup'] = _changegrouppart
627 627 wrappedchangegrouppart = True
628 628
629 629 def _listkeys(orig, self, namespace):
630 630 origvalues = orig(self, namespace)
631 631 if namespace == b'phases' and pullfrombundlestore:
632 632 if origvalues.get(b'publishing') == b'True':
633 633 # Make repo non-publishing to preserve draft phase
634 634 del origvalues[b'publishing']
635 635 origvalues.update(newphases)
636 636 return origvalues
637 637
638 638 extensions.wrapfunction(
639 639 localrepo.localrepository, b'listkeys', _listkeys
640 640 )
641 641 wrappedlistkeys = True
642 642 heads = list((set(newheads) | set(heads)) - set(scratchheads))
643 643 result = orig(
644 644 repo, source, heads=heads, bundlecaps=bundlecaps, **kwargs
645 645 )
646 646 finally:
647 647 if wrappedchangegrouppart:
648 648 exchange.getbundle2partsmapping[b'changegroup'] = oldchangegrouppart
649 649 if wrappedlistkeys:
650 650 extensions.unwrapfunction(
651 651 localrepo.localrepository, b'listkeys', _listkeys
652 652 )
653 653 return result
654 654
655 655
656 656 def _lookupwrap(orig):
657 657 def _lookup(repo, proto, key):
658 658 localkey = encoding.tolocal(key)
659 659
660 660 if isinstance(localkey, str) and _scratchbranchmatcher(localkey):
661 661 scratchnode = repo.bundlestore.index.getnode(localkey)
662 662 if scratchnode:
663 663 return b"%d %s\n" % (1, scratchnode)
664 664 else:
665 665 return b"%d %s\n" % (
666 666 0,
667 667 b'scratch branch %s not found' % localkey,
668 668 )
669 669 else:
670 670 try:
671 671 r = hex(repo.lookup(localkey))
672 672 return b"%d %s\n" % (1, r)
673 673 except Exception as inst:
674 674 if repo.bundlestore.index.getbundle(localkey):
675 675 return b"%d %s\n" % (1, localkey)
676 676 else:
677 677 r = stringutil.forcebytestr(inst)
678 678 return b"%d %s\n" % (0, r)
679 679
680 680 return _lookup
681 681
682 682
683 683 def _pull(orig, ui, repo, source=b"default", **opts):
684 684 opts = pycompat.byteskwargs(opts)
685 685 # Copy paste from `pull` command
686 686 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
687 687
688 688 scratchbookmarks = {}
689 689 unfi = repo.unfiltered()
690 690 unknownnodes = []
691 691 for rev in opts.get(b'rev', []):
692 692 if rev not in unfi:
693 693 unknownnodes.append(rev)
694 694 if opts.get(b'bookmark'):
695 695 bookmarks = []
696 696 revs = opts.get(b'rev') or []
697 697 for bookmark in opts.get(b'bookmark'):
698 698 if _scratchbranchmatcher(bookmark):
699 699 # rev is not known yet
700 700 # it will be fetched with listkeyspatterns next
701 701 scratchbookmarks[bookmark] = b'REVTOFETCH'
702 702 else:
703 703 bookmarks.append(bookmark)
704 704
705 705 if scratchbookmarks:
706 706 other = hg.peer(repo, opts, source)
707 fetchedbookmarks = other.listkeyspatterns(
708 b'bookmarks', patterns=scratchbookmarks
709 )
710 for bookmark in scratchbookmarks:
711 if bookmark not in fetchedbookmarks:
712 raise error.Abort(
713 b'remote bookmark %s not found!' % bookmark
714 )
715 scratchbookmarks[bookmark] = fetchedbookmarks[bookmark]
716 revs.append(fetchedbookmarks[bookmark])
707 try:
708 fetchedbookmarks = other.listkeyspatterns(
709 b'bookmarks', patterns=scratchbookmarks
710 )
711 for bookmark in scratchbookmarks:
712 if bookmark not in fetchedbookmarks:
713 raise error.Abort(
714 b'remote bookmark %s not found!' % bookmark
715 )
716 scratchbookmarks[bookmark] = fetchedbookmarks[bookmark]
717 revs.append(fetchedbookmarks[bookmark])
718 finally:
719 other.close()
717 720 opts[b'bookmark'] = bookmarks
718 721 opts[b'rev'] = revs
719 722
720 723 if scratchbookmarks or unknownnodes:
721 724 # Set anyincoming to True
722 725 extensions.wrapfunction(
723 726 discovery, b'findcommonincoming', _findcommonincoming
724 727 )
725 728 try:
726 729 # Remote scratch bookmarks will be deleted because remotenames doesn't
727 730 # know about them. Let's save it before pull and restore after
728 731 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, source)
729 732 result = orig(ui, repo, source, **pycompat.strkwargs(opts))
730 733 # TODO(stash): race condition is possible
731 734 # if scratch bookmarks was updated right after orig.
732 735 # But that's unlikely and shouldn't be harmful.
733 736 if common.isremotebooksenabled(ui):
734 737 remotescratchbookmarks.update(scratchbookmarks)
735 738 _saveremotebookmarks(repo, remotescratchbookmarks, source)
736 739 else:
737 740 _savelocalbookmarks(repo, scratchbookmarks)
738 741 return result
739 742 finally:
740 743 if scratchbookmarks:
741 744 extensions.unwrapfunction(discovery, b'findcommonincoming')
742 745
743 746
744 747 def _readscratchremotebookmarks(ui, repo, other):
745 748 if common.isremotebooksenabled(ui):
746 749 remotenamesext = extensions.find(b'remotenames')
747 750 remotepath = remotenamesext.activepath(repo.ui, other)
748 751 result = {}
749 752 # Let's refresh remotenames to make sure we have it up to date
750 753 # Seems that `repo.names['remotebookmarks']` may return stale bookmarks
751 754 # and it results in deleting scratch bookmarks. Our best guess how to
752 755 # fix it is to use `clearnames()`
753 756 repo._remotenames.clearnames()
754 757 for remotebookmark in repo.names[b'remotebookmarks'].listnames(repo):
755 758 path, bookname = remotenamesext.splitremotename(remotebookmark)
756 759 if path == remotepath and _scratchbranchmatcher(bookname):
757 760 nodes = repo.names[b'remotebookmarks'].nodes(
758 761 repo, remotebookmark
759 762 )
760 763 if nodes:
761 764 result[bookname] = hex(nodes[0])
762 765 return result
763 766 else:
764 767 return {}
765 768
766 769
767 770 def _saveremotebookmarks(repo, newbookmarks, remote):
768 771 remotenamesext = extensions.find(b'remotenames')
769 772 remotepath = remotenamesext.activepath(repo.ui, remote)
770 773 branches = collections.defaultdict(list)
771 774 bookmarks = {}
772 775 remotenames = remotenamesext.readremotenames(repo)
773 776 for hexnode, nametype, remote, rname in remotenames:
774 777 if remote != remotepath:
775 778 continue
776 779 if nametype == b'bookmarks':
777 780 if rname in newbookmarks:
778 781 # It's possible if we have a normal bookmark that matches
779 782 # scratch branch pattern. In this case just use the current
780 783 # bookmark node
781 784 del newbookmarks[rname]
782 785 bookmarks[rname] = hexnode
783 786 elif nametype == b'branches':
784 787 # saveremotenames expects 20 byte binary nodes for branches
785 788 branches[rname].append(bin(hexnode))
786 789
787 790 for bookmark, hexnode in pycompat.iteritems(newbookmarks):
788 791 bookmarks[bookmark] = hexnode
789 792 remotenamesext.saveremotenames(repo, remotepath, branches, bookmarks)
790 793
791 794
792 795 def _savelocalbookmarks(repo, bookmarks):
793 796 if not bookmarks:
794 797 return
795 798 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
796 799 changes = []
797 800 for scratchbook, node in pycompat.iteritems(bookmarks):
798 801 changectx = repo[node]
799 802 changes.append((scratchbook, changectx.node()))
800 803 repo._bookmarks.applychanges(repo, tr, changes)
801 804
802 805
803 806 def _findcommonincoming(orig, *args, **kwargs):
804 807 common, inc, remoteheads = orig(*args, **kwargs)
805 808 return common, True, remoteheads
806 809
807 810
808 811 def _push(orig, ui, repo, dest=None, *args, **opts):
809 812 opts = pycompat.byteskwargs(opts)
810 813 bookmark = opts.get(b'bookmark')
811 814 # we only support pushing one infinitepush bookmark at once
812 815 if len(bookmark) == 1:
813 816 bookmark = bookmark[0]
814 817 else:
815 818 bookmark = b''
816 819
817 820 oldphasemove = None
818 821 overrides = {(experimental, configbookmark): bookmark}
819 822
820 823 with ui.configoverride(overrides, b'infinitepush'):
821 824 scratchpush = opts.get(b'bundle_store')
822 825 if _scratchbranchmatcher(bookmark):
823 826 scratchpush = True
824 827 # bundle2 can be sent back after push (for example, bundle2
825 828 # containing `pushkey` part to update bookmarks)
826 829 ui.setconfig(experimental, b'bundle2.pushback', True)
827 830
828 831 if scratchpush:
829 832 # this is an infinitepush, we don't want the bookmark to be applied
830 833 # rather that should be stored in the bundlestore
831 834 opts[b'bookmark'] = []
832 835 ui.setconfig(experimental, configscratchpush, True)
833 836 oldphasemove = extensions.wrapfunction(
834 837 exchange, b'_localphasemove', _phasemove
835 838 )
836 839 # Copy-paste from `push` command
837 840 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
838 841 if not path:
839 842 raise error.Abort(
840 843 _(b'default repository not configured!'),
841 844 hint=_(b"see 'hg help config.paths'"),
842 845 )
843 846 destpath = path.pushloc or path.loc
844 847 # Remote scratch bookmarks will be deleted because remotenames doesn't
845 848 # know about them. Let's save it before push and restore after
846 849 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, destpath)
847 850 result = orig(ui, repo, dest, *args, **pycompat.strkwargs(opts))
848 851 if common.isremotebooksenabled(ui):
849 852 if bookmark and scratchpush:
850 853 other = hg.peer(repo, opts, destpath)
851 fetchedbookmarks = other.listkeyspatterns(
852 b'bookmarks', patterns=[bookmark]
853 )
854 remotescratchbookmarks.update(fetchedbookmarks)
854 try:
855 fetchedbookmarks = other.listkeyspatterns(
856 b'bookmarks', patterns=[bookmark]
857 )
858 remotescratchbookmarks.update(fetchedbookmarks)
859 finally:
860 other.close()
855 861 _saveremotebookmarks(repo, remotescratchbookmarks, destpath)
856 862 if oldphasemove:
857 863 exchange._localphasemove = oldphasemove
858 864 return result
859 865
860 866
861 867 def _deleteinfinitepushbookmarks(ui, repo, path, names):
862 868 """Prune remote names by removing the bookmarks we don't want anymore,
863 869 then writing the result back to disk
864 870 """
865 871 remotenamesext = extensions.find(b'remotenames')
866 872
867 873 # remotename format is:
868 874 # (node, nametype ("branches" or "bookmarks"), remote, name)
869 875 nametype_idx = 1
870 876 remote_idx = 2
871 877 name_idx = 3
872 878 remotenames = [
873 879 remotename
874 880 for remotename in remotenamesext.readremotenames(repo)
875 881 if remotename[remote_idx] == path
876 882 ]
877 883 remote_bm_names = [
878 884 remotename[name_idx]
879 885 for remotename in remotenames
880 886 if remotename[nametype_idx] == b"bookmarks"
881 887 ]
882 888
883 889 for name in names:
884 890 if name not in remote_bm_names:
885 891 raise error.Abort(
886 892 _(
887 893 b"infinitepush bookmark '{}' does not exist "
888 894 b"in path '{}'"
889 895 ).format(name, path)
890 896 )
891 897
892 898 bookmarks = {}
893 899 branches = collections.defaultdict(list)
894 900 for node, nametype, remote, name in remotenames:
895 901 if nametype == b"bookmarks" and name not in names:
896 902 bookmarks[name] = node
897 903 elif nametype == b"branches":
898 904 # saveremotenames wants binary nodes for branches
899 905 branches[name].append(bin(node))
900 906
901 907 remotenamesext.saveremotenames(repo, path, branches, bookmarks)
902 908
903 909
904 910 def _phasemove(orig, pushop, nodes, phase=phases.public):
905 911 """prevent commits from being marked public
906 912
907 913 Since these are going to a scratch branch, they aren't really being
908 914 published."""
909 915
910 916 if phase != phases.public:
911 917 orig(pushop, nodes, phase)
912 918
913 919
914 920 @exchange.b2partsgenerator(scratchbranchparttype)
915 921 def partgen(pushop, bundler):
916 922 bookmark = pushop.ui.config(experimental, configbookmark)
917 923 scratchpush = pushop.ui.configbool(experimental, configscratchpush)
918 924 if b'changesets' in pushop.stepsdone or not scratchpush:
919 925 return
920 926
921 927 if scratchbranchparttype not in bundle2.bundle2caps(pushop.remote):
922 928 return
923 929
924 930 pushop.stepsdone.add(b'changesets')
925 931 if not pushop.outgoing.missing:
926 932 pushop.ui.status(_(b'no changes found\n'))
927 933 pushop.cgresult = 0
928 934 return
929 935
930 936 # This parameter tells the server that the following bundle is an
931 937 # infinitepush. This let's it switch the part processing to our infinitepush
932 938 # code path.
933 939 bundler.addparam(b"infinitepush", b"True")
934 940
935 941 scratchparts = bundleparts.getscratchbranchparts(
936 942 pushop.repo, pushop.remote, pushop.outgoing, pushop.ui, bookmark
937 943 )
938 944
939 945 for scratchpart in scratchparts:
940 946 bundler.addpart(scratchpart)
941 947
942 948 def handlereply(op):
943 949 # server either succeeds or aborts; no code to read
944 950 pushop.cgresult = 1
945 951
946 952 return handlereply
947 953
948 954
949 955 bundle2.capabilities[bundleparts.scratchbranchparttype] = ()
950 956
951 957
952 958 def _getrevs(bundle, oldnode, force, bookmark):
953 959 b'extracts and validates the revs to be imported'
954 960 revs = [bundle[r] for r in bundle.revs(b'sort(bundle())')]
955 961
956 962 # new bookmark
957 963 if oldnode is None:
958 964 return revs
959 965
960 966 # Fast forward update
961 967 if oldnode in bundle and list(bundle.set(b'bundle() & %s::', oldnode)):
962 968 return revs
963 969
964 970 return revs
965 971
966 972
967 973 @contextlib.contextmanager
968 974 def logservicecall(logger, service, **kwargs):
969 975 start = time.time()
970 976 logger(service, eventtype=b'start', **kwargs)
971 977 try:
972 978 yield
973 979 logger(
974 980 service,
975 981 eventtype=b'success',
976 982 elapsedms=(time.time() - start) * 1000,
977 983 **kwargs
978 984 )
979 985 except Exception as e:
980 986 logger(
981 987 service,
982 988 eventtype=b'failure',
983 989 elapsedms=(time.time() - start) * 1000,
984 990 errormsg=stringutil.forcebytestr(e),
985 991 **kwargs
986 992 )
987 993 raise
988 994
989 995
990 996 def _getorcreateinfinitepushlogger(op):
991 997 logger = op.records[b'infinitepushlogger']
992 998 if not logger:
993 999 ui = op.repo.ui
994 1000 try:
995 1001 username = procutil.getuser()
996 1002 except Exception:
997 1003 username = b'unknown'
998 1004 # Generate random request id to be able to find all logged entries
999 1005 # for the same request. Since requestid is pseudo-generated it may
1000 1006 # not be unique, but we assume that (hostname, username, requestid)
1001 1007 # is unique.
1002 1008 random.seed()
1003 1009 requestid = random.randint(0, 2000000000)
1004 1010 hostname = socket.gethostname()
1005 1011 logger = functools.partial(
1006 1012 ui.log,
1007 1013 b'infinitepush',
1008 1014 user=username,
1009 1015 requestid=requestid,
1010 1016 hostname=hostname,
1011 1017 reponame=ui.config(b'infinitepush', b'reponame'),
1012 1018 )
1013 1019 op.records.add(b'infinitepushlogger', logger)
1014 1020 else:
1015 1021 logger = logger[0]
1016 1022 return logger
1017 1023
1018 1024
1019 1025 def storetobundlestore(orig, repo, op, unbundler):
1020 1026 """stores the incoming bundle coming from push command to the bundlestore
1021 1027 instead of applying on the revlogs"""
1022 1028
1023 1029 repo.ui.status(_(b"storing changesets on the bundlestore\n"))
1024 1030 bundler = bundle2.bundle20(repo.ui)
1025 1031
1026 1032 # processing each part and storing it in bundler
1027 1033 with bundle2.partiterator(repo, op, unbundler) as parts:
1028 1034 for part in parts:
1029 1035 bundlepart = None
1030 1036 if part.type == b'replycaps':
1031 1037 # This configures the current operation to allow reply parts.
1032 1038 bundle2._processpart(op, part)
1033 1039 else:
1034 1040 bundlepart = bundle2.bundlepart(part.type, data=part.read())
1035 1041 for key, value in pycompat.iteritems(part.params):
1036 1042 bundlepart.addparam(key, value)
1037 1043
1038 1044 # Certain parts require a response
1039 1045 if part.type in (b'pushkey', b'changegroup'):
1040 1046 if op.reply is not None:
1041 1047 rpart = op.reply.newpart(b'reply:%s' % part.type)
1042 1048 rpart.addparam(
1043 1049 b'in-reply-to', b'%d' % part.id, mandatory=False
1044 1050 )
1045 1051 rpart.addparam(b'return', b'1', mandatory=False)
1046 1052
1047 1053 op.records.add(
1048 1054 part.type,
1049 1055 {
1050 1056 b'return': 1,
1051 1057 },
1052 1058 )
1053 1059 if bundlepart:
1054 1060 bundler.addpart(bundlepart)
1055 1061
1056 1062 # storing the bundle in the bundlestore
1057 1063 buf = util.chunkbuffer(bundler.getchunks())
1058 1064 fd, bundlefile = pycompat.mkstemp()
1059 1065 try:
1060 1066 try:
1061 1067 fp = os.fdopen(fd, 'wb')
1062 1068 fp.write(buf.read())
1063 1069 finally:
1064 1070 fp.close()
1065 1071 storebundle(op, {}, bundlefile)
1066 1072 finally:
1067 1073 try:
1068 1074 os.unlink(bundlefile)
1069 1075 except Exception:
1070 1076 # we would rather see the original exception
1071 1077 pass
1072 1078
1073 1079
1074 1080 def processparts(orig, repo, op, unbundler):
1075 1081
1076 1082 # make sure we don't wrap processparts in case of `hg unbundle`
1077 1083 if op.source == b'unbundle':
1078 1084 return orig(repo, op, unbundler)
1079 1085
1080 1086 # this server routes each push to bundle store
1081 1087 if repo.ui.configbool(b'infinitepush', b'pushtobundlestore'):
1082 1088 return storetobundlestore(orig, repo, op, unbundler)
1083 1089
1084 1090 if unbundler.params.get(b'infinitepush') != b'True':
1085 1091 return orig(repo, op, unbundler)
1086 1092
1087 1093 handleallparts = repo.ui.configbool(b'infinitepush', b'storeallparts')
1088 1094
1089 1095 bundler = bundle2.bundle20(repo.ui)
1090 1096 cgparams = None
1091 1097 with bundle2.partiterator(repo, op, unbundler) as parts:
1092 1098 for part in parts:
1093 1099 bundlepart = None
1094 1100 if part.type == b'replycaps':
1095 1101 # This configures the current operation to allow reply parts.
1096 1102 bundle2._processpart(op, part)
1097 1103 elif part.type == bundleparts.scratchbranchparttype:
1098 1104 # Scratch branch parts need to be converted to normal
1099 1105 # changegroup parts, and the extra parameters stored for later
1100 1106 # when we upload to the store. Eventually those parameters will
1101 1107 # be put on the actual bundle instead of this part, then we can
1102 1108 # send a vanilla changegroup instead of the scratchbranch part.
1103 1109 cgversion = part.params.get(b'cgversion', b'01')
1104 1110 bundlepart = bundle2.bundlepart(
1105 1111 b'changegroup', data=part.read()
1106 1112 )
1107 1113 bundlepart.addparam(b'version', cgversion)
1108 1114 cgparams = part.params
1109 1115
1110 1116 # If we're not dumping all parts into the new bundle, we need to
1111 1117 # alert the future pushkey and phase-heads handler to skip
1112 1118 # the part.
1113 1119 if not handleallparts:
1114 1120 op.records.add(
1115 1121 scratchbranchparttype + b'_skippushkey', True
1116 1122 )
1117 1123 op.records.add(
1118 1124 scratchbranchparttype + b'_skipphaseheads', True
1119 1125 )
1120 1126 else:
1121 1127 if handleallparts:
1122 1128 # Ideally we would not process any parts, and instead just
1123 1129 # forward them to the bundle for storage, but since this
1124 1130 # differs from previous behavior, we need to put it behind a
1125 1131 # config flag for incremental rollout.
1126 1132 bundlepart = bundle2.bundlepart(part.type, data=part.read())
1127 1133 for key, value in pycompat.iteritems(part.params):
1128 1134 bundlepart.addparam(key, value)
1129 1135
1130 1136 # Certain parts require a response
1131 1137 if part.type == b'pushkey':
1132 1138 if op.reply is not None:
1133 1139 rpart = op.reply.newpart(b'reply:pushkey')
1134 1140 rpart.addparam(
1135 1141 b'in-reply-to', str(part.id), mandatory=False
1136 1142 )
1137 1143 rpart.addparam(b'return', b'1', mandatory=False)
1138 1144 else:
1139 1145 bundle2._processpart(op, part)
1140 1146
1141 1147 if handleallparts:
1142 1148 op.records.add(
1143 1149 part.type,
1144 1150 {
1145 1151 b'return': 1,
1146 1152 },
1147 1153 )
1148 1154 if bundlepart:
1149 1155 bundler.addpart(bundlepart)
1150 1156
1151 1157 # If commits were sent, store them
1152 1158 if cgparams:
1153 1159 buf = util.chunkbuffer(bundler.getchunks())
1154 1160 fd, bundlefile = pycompat.mkstemp()
1155 1161 try:
1156 1162 try:
1157 1163 fp = os.fdopen(fd, 'wb')
1158 1164 fp.write(buf.read())
1159 1165 finally:
1160 1166 fp.close()
1161 1167 storebundle(op, cgparams, bundlefile)
1162 1168 finally:
1163 1169 try:
1164 1170 os.unlink(bundlefile)
1165 1171 except Exception:
1166 1172 # we would rather see the original exception
1167 1173 pass
1168 1174
1169 1175
1170 1176 def storebundle(op, params, bundlefile):
1171 1177 log = _getorcreateinfinitepushlogger(op)
1172 1178 parthandlerstart = time.time()
1173 1179 log(scratchbranchparttype, eventtype=b'start')
1174 1180 index = op.repo.bundlestore.index
1175 1181 store = op.repo.bundlestore.store
1176 1182 op.records.add(scratchbranchparttype + b'_skippushkey', True)
1177 1183
1178 1184 bundle = None
1179 1185 try: # guards bundle
1180 1186 bundlepath = b"bundle:%s+%s" % (op.repo.root, bundlefile)
1181 1187 bundle = hg.repository(op.repo.ui, bundlepath)
1182 1188
1183 1189 bookmark = params.get(b'bookmark')
1184 1190 bookprevnode = params.get(b'bookprevnode', b'')
1185 1191 force = params.get(b'force')
1186 1192
1187 1193 if bookmark:
1188 1194 oldnode = index.getnode(bookmark)
1189 1195 else:
1190 1196 oldnode = None
1191 1197 bundleheads = bundle.revs(b'heads(bundle())')
1192 1198 if bookmark and len(bundleheads) > 1:
1193 1199 raise error.Abort(
1194 1200 _(b'cannot push more than one head to a scratch branch')
1195 1201 )
1196 1202
1197 1203 revs = _getrevs(bundle, oldnode, force, bookmark)
1198 1204
1199 1205 # Notify the user of what is being pushed
1200 1206 plural = b's' if len(revs) > 1 else b''
1201 1207 op.repo.ui.warn(_(b"pushing %d commit%s:\n") % (len(revs), plural))
1202 1208 maxoutput = 10
1203 1209 for i in range(0, min(len(revs), maxoutput)):
1204 1210 firstline = bundle[revs[i]].description().split(b'\n')[0][:50]
1205 1211 op.repo.ui.warn(b" %s %s\n" % (revs[i], firstline))
1206 1212
1207 1213 if len(revs) > maxoutput + 1:
1208 1214 op.repo.ui.warn(b" ...\n")
1209 1215 firstline = bundle[revs[-1]].description().split(b'\n')[0][:50]
1210 1216 op.repo.ui.warn(b" %s %s\n" % (revs[-1], firstline))
1211 1217
1212 1218 nodesctx = [bundle[rev] for rev in revs]
1213 1219 inindex = lambda rev: bool(index.getbundle(bundle[rev].hex()))
1214 1220 if bundleheads:
1215 1221 newheadscount = sum(not inindex(rev) for rev in bundleheads)
1216 1222 else:
1217 1223 newheadscount = 0
1218 1224 # If there's a bookmark specified, there should be only one head,
1219 1225 # so we choose the last node, which will be that head.
1220 1226 # If a bug or malicious client allows there to be a bookmark
1221 1227 # with multiple heads, we will place the bookmark on the last head.
1222 1228 bookmarknode = nodesctx[-1].hex() if nodesctx else None
1223 1229 key = None
1224 1230 if newheadscount:
1225 1231 with open(bundlefile, b'rb') as f:
1226 1232 bundledata = f.read()
1227 1233 with logservicecall(
1228 1234 log, b'bundlestore', bundlesize=len(bundledata)
1229 1235 ):
1230 1236 bundlesizelimit = 100 * 1024 * 1024 # 100 MB
1231 1237 if len(bundledata) > bundlesizelimit:
1232 1238 error_msg = (
1233 1239 b'bundle is too big: %d bytes. '
1234 1240 + b'max allowed size is 100 MB'
1235 1241 )
1236 1242 raise error.Abort(error_msg % (len(bundledata),))
1237 1243 key = store.write(bundledata)
1238 1244
1239 1245 with logservicecall(log, b'index', newheadscount=newheadscount), index:
1240 1246 if key:
1241 1247 index.addbundle(key, nodesctx)
1242 1248 if bookmark:
1243 1249 index.addbookmark(bookmark, bookmarknode)
1244 1250 _maybeaddpushbackpart(
1245 1251 op, bookmark, bookmarknode, bookprevnode, params
1246 1252 )
1247 1253 log(
1248 1254 scratchbranchparttype,
1249 1255 eventtype=b'success',
1250 1256 elapsedms=(time.time() - parthandlerstart) * 1000,
1251 1257 )
1252 1258
1253 1259 except Exception as e:
1254 1260 log(
1255 1261 scratchbranchparttype,
1256 1262 eventtype=b'failure',
1257 1263 elapsedms=(time.time() - parthandlerstart) * 1000,
1258 1264 errormsg=stringutil.forcebytestr(e),
1259 1265 )
1260 1266 raise
1261 1267 finally:
1262 1268 if bundle:
1263 1269 bundle.close()
1264 1270
1265 1271
1266 1272 @bundle2.parthandler(
1267 1273 scratchbranchparttype,
1268 1274 (
1269 1275 b'bookmark',
1270 1276 b'bookprevnode',
1271 1277 b'force',
1272 1278 b'pushbackbookmarks',
1273 1279 b'cgversion',
1274 1280 ),
1275 1281 )
1276 1282 def bundle2scratchbranch(op, part):
1277 1283 '''unbundle a bundle2 part containing a changegroup to store'''
1278 1284
1279 1285 bundler = bundle2.bundle20(op.repo.ui)
1280 1286 cgversion = part.params.get(b'cgversion', b'01')
1281 1287 cgpart = bundle2.bundlepart(b'changegroup', data=part.read())
1282 1288 cgpart.addparam(b'version', cgversion)
1283 1289 bundler.addpart(cgpart)
1284 1290 buf = util.chunkbuffer(bundler.getchunks())
1285 1291
1286 1292 fd, bundlefile = pycompat.mkstemp()
1287 1293 try:
1288 1294 try:
1289 1295 fp = os.fdopen(fd, 'wb')
1290 1296 fp.write(buf.read())
1291 1297 finally:
1292 1298 fp.close()
1293 1299 storebundle(op, part.params, bundlefile)
1294 1300 finally:
1295 1301 try:
1296 1302 os.unlink(bundlefile)
1297 1303 except OSError as e:
1298 1304 if e.errno != errno.ENOENT:
1299 1305 raise
1300 1306
1301 1307 return 1
1302 1308
1303 1309
1304 1310 def _maybeaddpushbackpart(op, bookmark, newnode, oldnode, params):
1305 1311 if params.get(b'pushbackbookmarks'):
1306 1312 if op.reply and b'pushback' in op.reply.capabilities:
1307 1313 params = {
1308 1314 b'namespace': b'bookmarks',
1309 1315 b'key': bookmark,
1310 1316 b'new': newnode,
1311 1317 b'old': oldnode,
1312 1318 }
1313 1319 op.reply.newpart(
1314 1320 b'pushkey', mandatoryparams=pycompat.iteritems(params)
1315 1321 )
1316 1322
1317 1323
1318 1324 def bundle2pushkey(orig, op, part):
1319 1325 """Wrapper of bundle2.handlepushkey()
1320 1326
1321 1327 The only goal is to skip calling the original function if flag is set.
1322 1328 It's set if infinitepush push is happening.
1323 1329 """
1324 1330 if op.records[scratchbranchparttype + b'_skippushkey']:
1325 1331 if op.reply is not None:
1326 1332 rpart = op.reply.newpart(b'reply:pushkey')
1327 1333 rpart.addparam(b'in-reply-to', str(part.id), mandatory=False)
1328 1334 rpart.addparam(b'return', b'1', mandatory=False)
1329 1335 return 1
1330 1336
1331 1337 return orig(op, part)
1332 1338
1333 1339
1334 1340 def bundle2handlephases(orig, op, part):
1335 1341 """Wrapper of bundle2.handlephases()
1336 1342
1337 1343 The only goal is to skip calling the original function if flag is set.
1338 1344 It's set if infinitepush push is happening.
1339 1345 """
1340 1346
1341 1347 if op.records[scratchbranchparttype + b'_skipphaseheads']:
1342 1348 return
1343 1349
1344 1350 return orig(op, part)
1345 1351
1346 1352
1347 1353 def _asyncsavemetadata(root, nodes):
1348 1354 """starts a separate process that fills metadata for the nodes
1349 1355
1350 1356 This function creates a separate process and doesn't wait for it's
1351 1357 completion. This was done to avoid slowing down pushes
1352 1358 """
1353 1359
1354 1360 maxnodes = 50
1355 1361 if len(nodes) > maxnodes:
1356 1362 return
1357 1363 nodesargs = []
1358 1364 for node in nodes:
1359 1365 nodesargs.append(b'--node')
1360 1366 nodesargs.append(node)
1361 1367 with open(os.devnull, b'w+b') as devnull:
1362 1368 cmdline = [
1363 1369 util.hgexecutable(),
1364 1370 b'debugfillinfinitepushmetadata',
1365 1371 b'-R',
1366 1372 root,
1367 1373 ] + nodesargs
1368 1374 # Process will run in background. We don't care about the return code
1369 1375 subprocess.Popen(
1370 1376 pycompat.rapply(procutil.tonativestr, cmdline),
1371 1377 close_fds=True,
1372 1378 shell=False,
1373 1379 stdin=devnull,
1374 1380 stdout=devnull,
1375 1381 stderr=devnull,
1376 1382 )
@@ -1,671 +1,677 b''
1 1 # narrowcommands.py - command modifications for narrowhg extension
2 2 #
3 3 # Copyright 2017 Google, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 from __future__ import absolute_import
8 8
9 9 import itertools
10 10 import os
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial.node import (
14 14 hex,
15 15 nullid,
16 16 short,
17 17 )
18 18 from mercurial import (
19 19 bundle2,
20 20 cmdutil,
21 21 commands,
22 22 discovery,
23 23 encoding,
24 24 error,
25 25 exchange,
26 26 extensions,
27 27 hg,
28 28 narrowspec,
29 29 pathutil,
30 30 pycompat,
31 31 registrar,
32 32 repair,
33 33 repoview,
34 34 requirements,
35 35 sparse,
36 36 util,
37 37 wireprototypes,
38 38 )
39 39
40 40 table = {}
41 41 command = registrar.command(table)
42 42
43 43
44 44 def setup():
45 45 """Wraps user-facing mercurial commands with narrow-aware versions."""
46 46
47 47 entry = extensions.wrapcommand(commands.table, b'clone', clonenarrowcmd)
48 48 entry[1].append(
49 49 (b'', b'narrow', None, _(b"create a narrow clone of select files"))
50 50 )
51 51 entry[1].append(
52 52 (
53 53 b'',
54 54 b'depth',
55 55 b'',
56 56 _(b"limit the history fetched by distance from heads"),
57 57 )
58 58 )
59 59 entry[1].append((b'', b'narrowspec', b'', _(b"read narrowspecs from file")))
60 60 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
61 61 if b'sparse' not in extensions.enabled():
62 62 entry[1].append(
63 63 (b'', b'include', [], _(b"specifically fetch this file/directory"))
64 64 )
65 65 entry[1].append(
66 66 (
67 67 b'',
68 68 b'exclude',
69 69 [],
70 70 _(b"do not fetch this file/directory, even if included"),
71 71 )
72 72 )
73 73
74 74 entry = extensions.wrapcommand(commands.table, b'pull', pullnarrowcmd)
75 75 entry[1].append(
76 76 (
77 77 b'',
78 78 b'depth',
79 79 b'',
80 80 _(b"limit the history fetched by distance from heads"),
81 81 )
82 82 )
83 83
84 84 extensions.wrapcommand(commands.table, b'archive', archivenarrowcmd)
85 85
86 86
87 87 def clonenarrowcmd(orig, ui, repo, *args, **opts):
88 88 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
89 89 opts = pycompat.byteskwargs(opts)
90 90 wrappedextraprepare = util.nullcontextmanager()
91 91 narrowspecfile = opts[b'narrowspec']
92 92
93 93 if narrowspecfile:
94 94 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
95 95 ui.status(_(b"reading narrowspec from '%s'\n") % filepath)
96 96 try:
97 97 fdata = util.readfile(filepath)
98 98 except IOError as inst:
99 99 raise error.Abort(
100 100 _(b"cannot read narrowspecs from '%s': %s")
101 101 % (filepath, encoding.strtolocal(inst.strerror))
102 102 )
103 103
104 104 includes, excludes, profiles = sparse.parseconfig(ui, fdata, b'narrow')
105 105 if profiles:
106 106 raise error.ConfigError(
107 107 _(
108 108 b"cannot specify other files using '%include' in"
109 109 b" narrowspec"
110 110 )
111 111 )
112 112
113 113 narrowspec.validatepatterns(includes)
114 114 narrowspec.validatepatterns(excludes)
115 115
116 116 # narrowspec is passed so we should assume that user wants narrow clone
117 117 opts[b'narrow'] = True
118 118 opts[b'include'].extend(includes)
119 119 opts[b'exclude'].extend(excludes)
120 120
121 121 if opts[b'narrow']:
122 122
123 123 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
124 124 orig(pullop, kwargs)
125 125
126 126 if opts.get(b'depth'):
127 127 kwargs[b'depth'] = opts[b'depth']
128 128
129 129 wrappedextraprepare = extensions.wrappedfunction(
130 130 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
131 131 )
132 132
133 133 with wrappedextraprepare:
134 134 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
135 135
136 136
137 137 def pullnarrowcmd(orig, ui, repo, *args, **opts):
138 138 """Wraps pull command to allow modifying narrow spec."""
139 139 wrappedextraprepare = util.nullcontextmanager()
140 140 if requirements.NARROW_REQUIREMENT in repo.requirements:
141 141
142 142 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
143 143 orig(pullop, kwargs)
144 144 if opts.get('depth'):
145 145 kwargs[b'depth'] = opts['depth']
146 146
147 147 wrappedextraprepare = extensions.wrappedfunction(
148 148 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
149 149 )
150 150
151 151 with wrappedextraprepare:
152 152 return orig(ui, repo, *args, **opts)
153 153
154 154
155 155 def archivenarrowcmd(orig, ui, repo, *args, **opts):
156 156 """Wraps archive command to narrow the default includes."""
157 157 if requirements.NARROW_REQUIREMENT in repo.requirements:
158 158 repo_includes, repo_excludes = repo.narrowpats
159 159 includes = set(opts.get('include', []))
160 160 excludes = set(opts.get('exclude', []))
161 161 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
162 162 includes, excludes, repo_includes, repo_excludes
163 163 )
164 164 if includes:
165 165 opts['include'] = includes
166 166 if excludes:
167 167 opts['exclude'] = excludes
168 168 return orig(ui, repo, *args, **opts)
169 169
170 170
171 171 def pullbundle2extraprepare(orig, pullop, kwargs):
172 172 repo = pullop.repo
173 173 if requirements.NARROW_REQUIREMENT not in repo.requirements:
174 174 return orig(pullop, kwargs)
175 175
176 176 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
177 177 raise error.Abort(_(b"server does not support narrow clones"))
178 178 orig(pullop, kwargs)
179 179 kwargs[b'narrow'] = True
180 180 include, exclude = repo.narrowpats
181 181 kwargs[b'oldincludepats'] = include
182 182 kwargs[b'oldexcludepats'] = exclude
183 183 if include:
184 184 kwargs[b'includepats'] = include
185 185 if exclude:
186 186 kwargs[b'excludepats'] = exclude
187 187 # calculate known nodes only in ellipses cases because in non-ellipses cases
188 188 # we have all the nodes
189 189 if wireprototypes.ELLIPSESCAP1 in pullop.remote.capabilities():
190 190 kwargs[b'known'] = [
191 191 hex(ctx.node())
192 192 for ctx in repo.set(b'::%ln', pullop.common)
193 193 if ctx.node() != nullid
194 194 ]
195 195 if not kwargs[b'known']:
196 196 # Mercurial serializes an empty list as '' and deserializes it as
197 197 # [''], so delete it instead to avoid handling the empty string on
198 198 # the server.
199 199 del kwargs[b'known']
200 200
201 201
202 202 extensions.wrapfunction(
203 203 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare
204 204 )
205 205
206 206
207 207 def _narrow(
208 208 ui,
209 209 repo,
210 210 remote,
211 211 commoninc,
212 212 oldincludes,
213 213 oldexcludes,
214 214 newincludes,
215 215 newexcludes,
216 216 force,
217 217 backup,
218 218 ):
219 219 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
220 220 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
221 221
222 222 # This is essentially doing "hg outgoing" to find all local-only
223 223 # commits. We will then check that the local-only commits don't
224 224 # have any changes to files that will be untracked.
225 225 unfi = repo.unfiltered()
226 226 outgoing = discovery.findcommonoutgoing(unfi, remote, commoninc=commoninc)
227 227 ui.status(_(b'looking for local changes to affected paths\n'))
228 228 localnodes = []
229 229 for n in itertools.chain(outgoing.missing, outgoing.excluded):
230 230 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
231 231 localnodes.append(n)
232 232 revstostrip = unfi.revs(b'descendants(%ln)', localnodes)
233 233 hiddenrevs = repoview.filterrevs(repo, b'visible')
234 234 visibletostrip = list(
235 235 repo.changelog.node(r) for r in (revstostrip - hiddenrevs)
236 236 )
237 237 if visibletostrip:
238 238 ui.status(
239 239 _(
240 240 b'The following changeset(s) or their ancestors have '
241 241 b'local changes not on the remote:\n'
242 242 )
243 243 )
244 244 maxnodes = 10
245 245 if ui.verbose or len(visibletostrip) <= maxnodes:
246 246 for n in visibletostrip:
247 247 ui.status(b'%s\n' % short(n))
248 248 else:
249 249 for n in visibletostrip[:maxnodes]:
250 250 ui.status(b'%s\n' % short(n))
251 251 ui.status(
252 252 _(b'...and %d more, use --verbose to list all\n')
253 253 % (len(visibletostrip) - maxnodes)
254 254 )
255 255 if not force:
256 256 raise error.StateError(
257 257 _(b'local changes found'),
258 258 hint=_(b'use --force-delete-local-changes to ignore'),
259 259 )
260 260
261 261 with ui.uninterruptible():
262 262 if revstostrip:
263 263 tostrip = [unfi.changelog.node(r) for r in revstostrip]
264 264 if repo[b'.'].node() in tostrip:
265 265 # stripping working copy, so move to a different commit first
266 266 urev = max(
267 267 repo.revs(
268 268 b'(::%n) - %ln + null',
269 269 repo[b'.'].node(),
270 270 visibletostrip,
271 271 )
272 272 )
273 273 hg.clean(repo, urev)
274 274 overrides = {(b'devel', b'strip-obsmarkers'): False}
275 275 with ui.configoverride(overrides, b'narrow'):
276 276 repair.strip(ui, unfi, tostrip, topic=b'narrow', backup=backup)
277 277
278 278 todelete = []
279 279 for f, f2, size in repo.store.datafiles():
280 280 if f.startswith(b'data/'):
281 281 file = f[5:-2]
282 282 if not newmatch(file):
283 283 todelete.append(f)
284 284 elif f.startswith(b'meta/'):
285 285 dir = f[5:-13]
286 286 dirs = sorted(pathutil.dirs({dir})) + [dir]
287 287 include = True
288 288 for d in dirs:
289 289 visit = newmatch.visitdir(d)
290 290 if not visit:
291 291 include = False
292 292 break
293 293 if visit == b'all':
294 294 break
295 295 if not include:
296 296 todelete.append(f)
297 297
298 298 repo.destroying()
299 299
300 300 with repo.transaction(b'narrowing'):
301 301 # Update narrowspec before removing revlogs, so repo won't be
302 302 # corrupt in case of crash
303 303 repo.setnarrowpats(newincludes, newexcludes)
304 304
305 305 for f in todelete:
306 306 ui.status(_(b'deleting %s\n') % f)
307 307 util.unlinkpath(repo.svfs.join(f))
308 308 repo.store.markremoved(f)
309 309
310 310 narrowspec.updateworkingcopy(repo, assumeclean=True)
311 311 narrowspec.copytoworkingcopy(repo)
312 312
313 313 repo.destroyed()
314 314
315 315
316 316 def _widen(
317 317 ui,
318 318 repo,
319 319 remote,
320 320 commoninc,
321 321 oldincludes,
322 322 oldexcludes,
323 323 newincludes,
324 324 newexcludes,
325 325 ):
326 326 # for now we assume that if a server has ellipses enabled, we will be
327 327 # exchanging ellipses nodes. In future we should add ellipses as a client
328 328 # side requirement (maybe) to distinguish a client is shallow or not and
329 329 # then send that information to server whether we want ellipses or not.
330 330 # Theoretically a non-ellipses repo should be able to use narrow
331 331 # functionality from an ellipses enabled server
332 332 remotecap = remote.capabilities()
333 333 ellipsesremote = any(
334 334 cap in remotecap for cap in wireprototypes.SUPPORTED_ELLIPSESCAP
335 335 )
336 336
337 337 # check whether we are talking to a server which supports old version of
338 338 # ellipses capabilities
339 339 isoldellipses = (
340 340 ellipsesremote
341 341 and wireprototypes.ELLIPSESCAP1 in remotecap
342 342 and wireprototypes.ELLIPSESCAP not in remotecap
343 343 )
344 344
345 345 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
346 346 orig(pullop, kwargs)
347 347 # The old{in,ex}cludepats have already been set by orig()
348 348 kwargs[b'includepats'] = newincludes
349 349 kwargs[b'excludepats'] = newexcludes
350 350
351 351 wrappedextraprepare = extensions.wrappedfunction(
352 352 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
353 353 )
354 354
355 355 # define a function that narrowbundle2 can call after creating the
356 356 # backup bundle, but before applying the bundle from the server
357 357 def setnewnarrowpats():
358 358 repo.setnarrowpats(newincludes, newexcludes)
359 359
360 360 repo.setnewnarrowpats = setnewnarrowpats
361 361 # silence the devel-warning of applying an empty changegroup
362 362 overrides = {(b'devel', b'all-warnings'): False}
363 363
364 364 common = commoninc[0]
365 365 with ui.uninterruptible():
366 366 if ellipsesremote:
367 367 ds = repo.dirstate
368 368 p1, p2 = ds.p1(), ds.p2()
369 369 with ds.parentchange():
370 370 ds.setparents(nullid, nullid)
371 371 if isoldellipses:
372 372 with wrappedextraprepare:
373 373 exchange.pull(repo, remote, heads=common)
374 374 else:
375 375 known = []
376 376 if ellipsesremote:
377 377 known = [
378 378 ctx.node()
379 379 for ctx in repo.set(b'::%ln', common)
380 380 if ctx.node() != nullid
381 381 ]
382 382 with remote.commandexecutor() as e:
383 383 bundle = e.callcommand(
384 384 b'narrow_widen',
385 385 {
386 386 b'oldincludes': oldincludes,
387 387 b'oldexcludes': oldexcludes,
388 388 b'newincludes': newincludes,
389 389 b'newexcludes': newexcludes,
390 390 b'cgversion': b'03',
391 391 b'commonheads': common,
392 392 b'known': known,
393 393 b'ellipses': ellipsesremote,
394 394 },
395 395 ).result()
396 396
397 397 trmanager = exchange.transactionmanager(
398 398 repo, b'widen', remote.url()
399 399 )
400 400 with trmanager, repo.ui.configoverride(overrides, b'widen'):
401 401 op = bundle2.bundleoperation(
402 402 repo, trmanager.transaction, source=b'widen'
403 403 )
404 404 # TODO: we should catch error.Abort here
405 405 bundle2.processbundle(repo, bundle, op=op)
406 406
407 407 if ellipsesremote:
408 408 with ds.parentchange():
409 409 ds.setparents(p1, p2)
410 410
411 411 with repo.transaction(b'widening'):
412 412 repo.setnewnarrowpats()
413 413 narrowspec.updateworkingcopy(repo)
414 414 narrowspec.copytoworkingcopy(repo)
415 415
416 416
417 417 # TODO(rdamazio): Make new matcher format and update description
418 418 @command(
419 419 b'tracked',
420 420 [
421 421 (b'', b'addinclude', [], _(b'new paths to include')),
422 422 (b'', b'removeinclude', [], _(b'old paths to no longer include')),
423 423 (
424 424 b'',
425 425 b'auto-remove-includes',
426 426 False,
427 427 _(b'automatically choose unused includes to remove'),
428 428 ),
429 429 (b'', b'addexclude', [], _(b'new paths to exclude')),
430 430 (b'', b'import-rules', b'', _(b'import narrowspecs from a file')),
431 431 (b'', b'removeexclude', [], _(b'old paths to no longer exclude')),
432 432 (
433 433 b'',
434 434 b'clear',
435 435 False,
436 436 _(b'whether to replace the existing narrowspec'),
437 437 ),
438 438 (
439 439 b'',
440 440 b'force-delete-local-changes',
441 441 False,
442 442 _(b'forces deletion of local changes when narrowing'),
443 443 ),
444 444 (
445 445 b'',
446 446 b'backup',
447 447 True,
448 448 _(b'back up local changes when narrowing'),
449 449 ),
450 450 (
451 451 b'',
452 452 b'update-working-copy',
453 453 False,
454 454 _(b'update working copy when the store has changed'),
455 455 ),
456 456 ]
457 457 + commands.remoteopts,
458 458 _(b'[OPTIONS]... [REMOTE]'),
459 459 inferrepo=True,
460 460 helpcategory=command.CATEGORY_MAINTENANCE,
461 461 )
462 462 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
463 463 """show or change the current narrowspec
464 464
465 465 With no argument, shows the current narrowspec entries, one per line. Each
466 466 line will be prefixed with 'I' or 'X' for included or excluded patterns,
467 467 respectively.
468 468
469 469 The narrowspec is comprised of expressions to match remote files and/or
470 470 directories that should be pulled into your client.
471 471 The narrowspec has *include* and *exclude* expressions, with excludes always
472 472 trumping includes: that is, if a file matches an exclude expression, it will
473 473 be excluded even if it also matches an include expression.
474 474 Excluding files that were never included has no effect.
475 475
476 476 Each included or excluded entry is in the format described by
477 477 'hg help patterns'.
478 478
479 479 The options allow you to add or remove included and excluded expressions.
480 480
481 481 If --clear is specified, then all previous includes and excludes are DROPPED
482 482 and replaced by the new ones specified to --addinclude and --addexclude.
483 483 If --clear is specified without any further options, the narrowspec will be
484 484 empty and will not match any files.
485 485
486 486 If --auto-remove-includes is specified, then those includes that don't match
487 487 any files modified by currently visible local commits (those not shared by
488 488 the remote) will be added to the set of explicitly specified includes to
489 489 remove.
490 490
491 491 --import-rules accepts a path to a file containing rules, allowing you to
492 492 add --addinclude, --addexclude rules in bulk. Like the other include and
493 493 exclude switches, the changes are applied immediately.
494 494 """
495 495 opts = pycompat.byteskwargs(opts)
496 496 if requirements.NARROW_REQUIREMENT not in repo.requirements:
497 497 raise error.InputError(
498 498 _(
499 499 b'the tracked command is only supported on '
500 500 b'repositories cloned with --narrow'
501 501 )
502 502 )
503 503
504 504 # Before supporting, decide whether it "hg tracked --clear" should mean
505 505 # tracking no paths or all paths.
506 506 if opts[b'clear']:
507 507 raise error.InputError(_(b'the --clear option is not yet supported'))
508 508
509 509 # import rules from a file
510 510 newrules = opts.get(b'import_rules')
511 511 if newrules:
512 512 try:
513 513 filepath = os.path.join(encoding.getcwd(), newrules)
514 514 fdata = util.readfile(filepath)
515 515 except IOError as inst:
516 516 raise error.StorageError(
517 517 _(b"cannot read narrowspecs from '%s': %s")
518 518 % (filepath, encoding.strtolocal(inst.strerror))
519 519 )
520 520 includepats, excludepats, profiles = sparse.parseconfig(
521 521 ui, fdata, b'narrow'
522 522 )
523 523 if profiles:
524 524 raise error.InputError(
525 525 _(
526 526 b"including other spec files using '%include' "
527 527 b"is not supported in narrowspec"
528 528 )
529 529 )
530 530 opts[b'addinclude'].extend(includepats)
531 531 opts[b'addexclude'].extend(excludepats)
532 532
533 533 addedincludes = narrowspec.parsepatterns(opts[b'addinclude'])
534 534 removedincludes = narrowspec.parsepatterns(opts[b'removeinclude'])
535 535 addedexcludes = narrowspec.parsepatterns(opts[b'addexclude'])
536 536 removedexcludes = narrowspec.parsepatterns(opts[b'removeexclude'])
537 537 autoremoveincludes = opts[b'auto_remove_includes']
538 538
539 539 update_working_copy = opts[b'update_working_copy']
540 540 only_show = not (
541 541 addedincludes
542 542 or removedincludes
543 543 or addedexcludes
544 544 or removedexcludes
545 545 or newrules
546 546 or autoremoveincludes
547 547 or update_working_copy
548 548 )
549 549
550 550 oldincludes, oldexcludes = repo.narrowpats
551 551
552 552 # filter the user passed additions and deletions into actual additions and
553 553 # deletions of excludes and includes
554 554 addedincludes -= oldincludes
555 555 removedincludes &= oldincludes
556 556 addedexcludes -= oldexcludes
557 557 removedexcludes &= oldexcludes
558 558
559 559 widening = addedincludes or removedexcludes
560 560 narrowing = removedincludes or addedexcludes
561 561
562 562 # Only print the current narrowspec.
563 563 if only_show:
564 564 ui.pager(b'tracked')
565 565 fm = ui.formatter(b'narrow', opts)
566 566 for i in sorted(oldincludes):
567 567 fm.startitem()
568 568 fm.write(b'status', b'%s ', b'I', label=b'narrow.included')
569 569 fm.write(b'pat', b'%s\n', i, label=b'narrow.included')
570 570 for i in sorted(oldexcludes):
571 571 fm.startitem()
572 572 fm.write(b'status', b'%s ', b'X', label=b'narrow.excluded')
573 573 fm.write(b'pat', b'%s\n', i, label=b'narrow.excluded')
574 574 fm.end()
575 575 return 0
576 576
577 577 if update_working_copy:
578 578 with repo.wlock(), repo.lock(), repo.transaction(b'narrow-wc'):
579 579 narrowspec.updateworkingcopy(repo)
580 580 narrowspec.copytoworkingcopy(repo)
581 581 return 0
582 582
583 583 if not (widening or narrowing or autoremoveincludes):
584 584 ui.status(_(b"nothing to widen or narrow\n"))
585 585 return 0
586 586
587 587 with repo.wlock(), repo.lock():
588 588 cmdutil.bailifchanged(repo)
589 589
590 590 # Find the revisions we have in common with the remote. These will
591 591 # be used for finding local-only changes for narrowing. They will
592 592 # also define the set of revisions to update for widening.
593 593 remotepath = ui.expandpath(remotepath or b'default')
594 594 url, branches = hg.parseurl(remotepath)
595 595 ui.status(_(b'comparing with %s\n') % util.hidepassword(url))
596 596 remote = hg.peer(repo, opts, url)
597 597
598 # check narrow support before doing anything if widening needs to be
599 # performed. In future we should also abort if client is ellipses and
600 # server does not support ellipses
601 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
602 raise error.Abort(_(b"server does not support narrow clones"))
598 try:
599 # check narrow support before doing anything if widening needs to be
600 # performed. In future we should also abort if client is ellipses and
601 # server does not support ellipses
602 if (
603 widening
604 and wireprototypes.NARROWCAP not in remote.capabilities()
605 ):
606 raise error.Abort(_(b"server does not support narrow clones"))
603 607
604 commoninc = discovery.findcommonincoming(repo, remote)
608 commoninc = discovery.findcommonincoming(repo, remote)
605 609
606 if autoremoveincludes:
607 outgoing = discovery.findcommonoutgoing(
608 repo, remote, commoninc=commoninc
609 )
610 ui.status(_(b'looking for unused includes to remove\n'))
611 localfiles = set()
612 for n in itertools.chain(outgoing.missing, outgoing.excluded):
613 localfiles.update(repo[n].files())
614 suggestedremovals = []
615 for include in sorted(oldincludes):
616 match = narrowspec.match(repo.root, [include], oldexcludes)
617 if not any(match(f) for f in localfiles):
618 suggestedremovals.append(include)
619 if suggestedremovals:
620 for s in suggestedremovals:
621 ui.status(b'%s\n' % s)
622 if (
623 ui.promptchoice(
624 _(
625 b'remove these unused includes (yn)?'
626 b'$$ &Yes $$ &No'
610 if autoremoveincludes:
611 outgoing = discovery.findcommonoutgoing(
612 repo, remote, commoninc=commoninc
613 )
614 ui.status(_(b'looking for unused includes to remove\n'))
615 localfiles = set()
616 for n in itertools.chain(outgoing.missing, outgoing.excluded):
617 localfiles.update(repo[n].files())
618 suggestedremovals = []
619 for include in sorted(oldincludes):
620 match = narrowspec.match(repo.root, [include], oldexcludes)
621 if not any(match(f) for f in localfiles):
622 suggestedremovals.append(include)
623 if suggestedremovals:
624 for s in suggestedremovals:
625 ui.status(b'%s\n' % s)
626 if (
627 ui.promptchoice(
628 _(
629 b'remove these unused includes (yn)?'
630 b'$$ &Yes $$ &No'
631 )
627 632 )
628 )
629 == 0
630 ):
631 removedincludes.update(suggestedremovals)
632 narrowing = True
633 else:
634 ui.status(_(b'found no unused includes\n'))
633 == 0
634 ):
635 removedincludes.update(suggestedremovals)
636 narrowing = True
637 else:
638 ui.status(_(b'found no unused includes\n'))
635 639
636 if narrowing:
637 newincludes = oldincludes - removedincludes
638 newexcludes = oldexcludes | addedexcludes
639 _narrow(
640 ui,
641 repo,
642 remote,
643 commoninc,
644 oldincludes,
645 oldexcludes,
646 newincludes,
647 newexcludes,
648 opts[b'force_delete_local_changes'],
649 opts[b'backup'],
650 )
651 # _narrow() updated the narrowspec and _widen() below needs to
652 # use the updated values as its base (otherwise removed includes
653 # and addedexcludes will be lost in the resulting narrowspec)
654 oldincludes = newincludes
655 oldexcludes = newexcludes
640 if narrowing:
641 newincludes = oldincludes - removedincludes
642 newexcludes = oldexcludes | addedexcludes
643 _narrow(
644 ui,
645 repo,
646 remote,
647 commoninc,
648 oldincludes,
649 oldexcludes,
650 newincludes,
651 newexcludes,
652 opts[b'force_delete_local_changes'],
653 opts[b'backup'],
654 )
655 # _narrow() updated the narrowspec and _widen() below needs to
656 # use the updated values as its base (otherwise removed includes
657 # and addedexcludes will be lost in the resulting narrowspec)
658 oldincludes = newincludes
659 oldexcludes = newexcludes
656 660
657 if widening:
658 newincludes = oldincludes | addedincludes
659 newexcludes = oldexcludes - removedexcludes
660 _widen(
661 ui,
662 repo,
663 remote,
664 commoninc,
665 oldincludes,
666 oldexcludes,
667 newincludes,
668 newexcludes,
669 )
661 if widening:
662 newincludes = oldincludes | addedincludes
663 newexcludes = oldexcludes - removedexcludes
664 _widen(
665 ui,
666 repo,
667 remote,
668 commoninc,
669 oldincludes,
670 oldexcludes,
671 newincludes,
672 newexcludes,
673 )
674 finally:
675 remote.close()
670 676
671 677 return 0
@@ -1,7855 +1,7872 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import re
13 13 import sys
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullid,
19 19 nullrev,
20 20 short,
21 21 wdirhex,
22 22 wdirrev,
23 23 )
24 24 from .pycompat import open
25 25 from . import (
26 26 archival,
27 27 bookmarks,
28 28 bundle2,
29 29 bundlecaches,
30 30 changegroup,
31 31 cmdutil,
32 32 copies,
33 33 debugcommands as debugcommandsmod,
34 34 destutil,
35 35 dirstateguard,
36 36 discovery,
37 37 encoding,
38 38 error,
39 39 exchange,
40 40 extensions,
41 41 filemerge,
42 42 formatter,
43 43 graphmod,
44 44 grep as grepmod,
45 45 hbisect,
46 46 help,
47 47 hg,
48 48 logcmdutil,
49 49 merge as mergemod,
50 50 mergestate as mergestatemod,
51 51 narrowspec,
52 52 obsolete,
53 53 obsutil,
54 54 patch,
55 55 phases,
56 56 pycompat,
57 57 rcutil,
58 58 registrar,
59 59 requirements,
60 60 revsetlang,
61 61 rewriteutil,
62 62 scmutil,
63 63 server,
64 64 shelve as shelvemod,
65 65 state as statemod,
66 66 streamclone,
67 67 tags as tagsmod,
68 68 ui as uimod,
69 69 util,
70 70 verify as verifymod,
71 71 vfs as vfsmod,
72 72 wireprotoserver,
73 73 )
74 74 from .utils import (
75 75 dateutil,
76 76 stringutil,
77 77 )
78 78
79 79 table = {}
80 80 table.update(debugcommandsmod.command._table)
81 81
82 82 command = registrar.command(table)
83 83 INTENT_READONLY = registrar.INTENT_READONLY
84 84
85 85 # common command options
86 86
87 87 globalopts = [
88 88 (
89 89 b'R',
90 90 b'repository',
91 91 b'',
92 92 _(b'repository root directory or name of overlay bundle file'),
93 93 _(b'REPO'),
94 94 ),
95 95 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
96 96 (
97 97 b'y',
98 98 b'noninteractive',
99 99 None,
100 100 _(
101 101 b'do not prompt, automatically pick the first choice for all prompts'
102 102 ),
103 103 ),
104 104 (b'q', b'quiet', None, _(b'suppress output')),
105 105 (b'v', b'verbose', None, _(b'enable additional output')),
106 106 (
107 107 b'',
108 108 b'color',
109 109 b'',
110 110 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
111 111 # and should not be translated
112 112 _(b"when to colorize (boolean, always, auto, never, or debug)"),
113 113 _(b'TYPE'),
114 114 ),
115 115 (
116 116 b'',
117 117 b'config',
118 118 [],
119 119 _(b'set/override config option (use \'section.name=value\')'),
120 120 _(b'CONFIG'),
121 121 ),
122 122 (b'', b'debug', None, _(b'enable debugging output')),
123 123 (b'', b'debugger', None, _(b'start debugger')),
124 124 (
125 125 b'',
126 126 b'encoding',
127 127 encoding.encoding,
128 128 _(b'set the charset encoding'),
129 129 _(b'ENCODE'),
130 130 ),
131 131 (
132 132 b'',
133 133 b'encodingmode',
134 134 encoding.encodingmode,
135 135 _(b'set the charset encoding mode'),
136 136 _(b'MODE'),
137 137 ),
138 138 (b'', b'traceback', None, _(b'always print a traceback on exception')),
139 139 (b'', b'time', None, _(b'time how long the command takes')),
140 140 (b'', b'profile', None, _(b'print command execution profile')),
141 141 (b'', b'version', None, _(b'output version information and exit')),
142 142 (b'h', b'help', None, _(b'display help and exit')),
143 143 (b'', b'hidden', False, _(b'consider hidden changesets')),
144 144 (
145 145 b'',
146 146 b'pager',
147 147 b'auto',
148 148 _(b"when to paginate (boolean, always, auto, or never)"),
149 149 _(b'TYPE'),
150 150 ),
151 151 ]
152 152
153 153 dryrunopts = cmdutil.dryrunopts
154 154 remoteopts = cmdutil.remoteopts
155 155 walkopts = cmdutil.walkopts
156 156 commitopts = cmdutil.commitopts
157 157 commitopts2 = cmdutil.commitopts2
158 158 commitopts3 = cmdutil.commitopts3
159 159 formatteropts = cmdutil.formatteropts
160 160 templateopts = cmdutil.templateopts
161 161 logopts = cmdutil.logopts
162 162 diffopts = cmdutil.diffopts
163 163 diffwsopts = cmdutil.diffwsopts
164 164 diffopts2 = cmdutil.diffopts2
165 165 mergetoolopts = cmdutil.mergetoolopts
166 166 similarityopts = cmdutil.similarityopts
167 167 subrepoopts = cmdutil.subrepoopts
168 168 debugrevlogopts = cmdutil.debugrevlogopts
169 169
170 170 # Commands start here, listed alphabetically
171 171
172 172
173 173 @command(
174 174 b'abort',
175 175 dryrunopts,
176 176 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
177 177 helpbasic=True,
178 178 )
179 179 def abort(ui, repo, **opts):
180 180 """abort an unfinished operation (EXPERIMENTAL)
181 181
182 182 Aborts a multistep operation like graft, histedit, rebase, merge,
183 183 and unshelve if they are in an unfinished state.
184 184
185 185 use --dry-run/-n to dry run the command.
186 186 """
187 187 dryrun = opts.get('dry_run')
188 188 abortstate = cmdutil.getunfinishedstate(repo)
189 189 if not abortstate:
190 190 raise error.StateError(_(b'no operation in progress'))
191 191 if not abortstate.abortfunc:
192 192 raise error.InputError(
193 193 (
194 194 _(b"%s in progress but does not support 'hg abort'")
195 195 % (abortstate._opname)
196 196 ),
197 197 hint=abortstate.hint(),
198 198 )
199 199 if dryrun:
200 200 ui.status(
201 201 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
202 202 )
203 203 return
204 204 return abortstate.abortfunc(ui, repo)
205 205
206 206
207 207 @command(
208 208 b'add',
209 209 walkopts + subrepoopts + dryrunopts,
210 210 _(b'[OPTION]... [FILE]...'),
211 211 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
212 212 helpbasic=True,
213 213 inferrepo=True,
214 214 )
215 215 def add(ui, repo, *pats, **opts):
216 216 """add the specified files on the next commit
217 217
218 218 Schedule files to be version controlled and added to the
219 219 repository.
220 220
221 221 The files will be added to the repository at the next commit. To
222 222 undo an add before that, see :hg:`forget`.
223 223
224 224 If no names are given, add all files to the repository (except
225 225 files matching ``.hgignore``).
226 226
227 227 .. container:: verbose
228 228
229 229 Examples:
230 230
231 231 - New (unknown) files are added
232 232 automatically by :hg:`add`::
233 233
234 234 $ ls
235 235 foo.c
236 236 $ hg status
237 237 ? foo.c
238 238 $ hg add
239 239 adding foo.c
240 240 $ hg status
241 241 A foo.c
242 242
243 243 - Specific files to be added can be specified::
244 244
245 245 $ ls
246 246 bar.c foo.c
247 247 $ hg status
248 248 ? bar.c
249 249 ? foo.c
250 250 $ hg add bar.c
251 251 $ hg status
252 252 A bar.c
253 253 ? foo.c
254 254
255 255 Returns 0 if all files are successfully added.
256 256 """
257 257
258 258 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
259 259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
260 260 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
261 261 return rejected and 1 or 0
262 262
263 263
264 264 @command(
265 265 b'addremove',
266 266 similarityopts + subrepoopts + walkopts + dryrunopts,
267 267 _(b'[OPTION]... [FILE]...'),
268 268 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
269 269 inferrepo=True,
270 270 )
271 271 def addremove(ui, repo, *pats, **opts):
272 272 """add all new files, delete all missing files
273 273
274 274 Add all new files and remove all missing files from the
275 275 repository.
276 276
277 277 Unless names are given, new files are ignored if they match any of
278 278 the patterns in ``.hgignore``. As with add, these changes take
279 279 effect at the next commit.
280 280
281 281 Use the -s/--similarity option to detect renamed files. This
282 282 option takes a percentage between 0 (disabled) and 100 (files must
283 283 be identical) as its parameter. With a parameter greater than 0,
284 284 this compares every removed file with every added file and records
285 285 those similar enough as renames. Detecting renamed files this way
286 286 can be expensive. After using this option, :hg:`status -C` can be
287 287 used to check which files were identified as moved or renamed. If
288 288 not specified, -s/--similarity defaults to 100 and only renames of
289 289 identical files are detected.
290 290
291 291 .. container:: verbose
292 292
293 293 Examples:
294 294
295 295 - A number of files (bar.c and foo.c) are new,
296 296 while foobar.c has been removed (without using :hg:`remove`)
297 297 from the repository::
298 298
299 299 $ ls
300 300 bar.c foo.c
301 301 $ hg status
302 302 ! foobar.c
303 303 ? bar.c
304 304 ? foo.c
305 305 $ hg addremove
306 306 adding bar.c
307 307 adding foo.c
308 308 removing foobar.c
309 309 $ hg status
310 310 A bar.c
311 311 A foo.c
312 312 R foobar.c
313 313
314 314 - A file foobar.c was moved to foo.c without using :hg:`rename`.
315 315 Afterwards, it was edited slightly::
316 316
317 317 $ ls
318 318 foo.c
319 319 $ hg status
320 320 ! foobar.c
321 321 ? foo.c
322 322 $ hg addremove --similarity 90
323 323 removing foobar.c
324 324 adding foo.c
325 325 recording removal of foobar.c as rename to foo.c (94% similar)
326 326 $ hg status -C
327 327 A foo.c
328 328 foobar.c
329 329 R foobar.c
330 330
331 331 Returns 0 if all files are successfully added.
332 332 """
333 333 opts = pycompat.byteskwargs(opts)
334 334 if not opts.get(b'similarity'):
335 335 opts[b'similarity'] = b'100'
336 336 matcher = scmutil.match(repo[None], pats, opts)
337 337 relative = scmutil.anypats(pats, opts)
338 338 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
339 339 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
340 340
341 341
342 342 @command(
343 343 b'annotate|blame',
344 344 [
345 345 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
346 346 (
347 347 b'',
348 348 b'follow',
349 349 None,
350 350 _(b'follow copies/renames and list the filename (DEPRECATED)'),
351 351 ),
352 352 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
353 353 (b'a', b'text', None, _(b'treat all files as text')),
354 354 (b'u', b'user', None, _(b'list the author (long with -v)')),
355 355 (b'f', b'file', None, _(b'list the filename')),
356 356 (b'd', b'date', None, _(b'list the date (short with -q)')),
357 357 (b'n', b'number', None, _(b'list the revision number (default)')),
358 358 (b'c', b'changeset', None, _(b'list the changeset')),
359 359 (
360 360 b'l',
361 361 b'line-number',
362 362 None,
363 363 _(b'show line number at the first appearance'),
364 364 ),
365 365 (
366 366 b'',
367 367 b'skip',
368 368 [],
369 369 _(b'revset to not display (EXPERIMENTAL)'),
370 370 _(b'REV'),
371 371 ),
372 372 ]
373 373 + diffwsopts
374 374 + walkopts
375 375 + formatteropts,
376 376 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
377 377 helpcategory=command.CATEGORY_FILE_CONTENTS,
378 378 helpbasic=True,
379 379 inferrepo=True,
380 380 )
381 381 def annotate(ui, repo, *pats, **opts):
382 382 """show changeset information by line for each file
383 383
384 384 List changes in files, showing the revision id responsible for
385 385 each line.
386 386
387 387 This command is useful for discovering when a change was made and
388 388 by whom.
389 389
390 390 If you include --file, --user, or --date, the revision number is
391 391 suppressed unless you also include --number.
392 392
393 393 Without the -a/--text option, annotate will avoid processing files
394 394 it detects as binary. With -a, annotate will annotate the file
395 395 anyway, although the results will probably be neither useful
396 396 nor desirable.
397 397
398 398 .. container:: verbose
399 399
400 400 Template:
401 401
402 402 The following keywords are supported in addition to the common template
403 403 keywords and functions. See also :hg:`help templates`.
404 404
405 405 :lines: List of lines with annotation data.
406 406 :path: String. Repository-absolute path of the specified file.
407 407
408 408 And each entry of ``{lines}`` provides the following sub-keywords in
409 409 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
410 410
411 411 :line: String. Line content.
412 412 :lineno: Integer. Line number at that revision.
413 413 :path: String. Repository-absolute path of the file at that revision.
414 414
415 415 See :hg:`help templates.operators` for the list expansion syntax.
416 416
417 417 Returns 0 on success.
418 418 """
419 419 opts = pycompat.byteskwargs(opts)
420 420 if not pats:
421 421 raise error.InputError(
422 422 _(b'at least one filename or pattern is required')
423 423 )
424 424
425 425 if opts.get(b'follow'):
426 426 # --follow is deprecated and now just an alias for -f/--file
427 427 # to mimic the behavior of Mercurial before version 1.5
428 428 opts[b'file'] = True
429 429
430 430 if (
431 431 not opts.get(b'user')
432 432 and not opts.get(b'changeset')
433 433 and not opts.get(b'date')
434 434 and not opts.get(b'file')
435 435 ):
436 436 opts[b'number'] = True
437 437
438 438 linenumber = opts.get(b'line_number') is not None
439 439 if (
440 440 linenumber
441 441 and (not opts.get(b'changeset'))
442 442 and (not opts.get(b'number'))
443 443 ):
444 444 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
445 445
446 446 rev = opts.get(b'rev')
447 447 if rev:
448 448 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
449 449 ctx = scmutil.revsingle(repo, rev)
450 450
451 451 ui.pager(b'annotate')
452 452 rootfm = ui.formatter(b'annotate', opts)
453 453 if ui.debugflag:
454 454 shorthex = pycompat.identity
455 455 else:
456 456
457 457 def shorthex(h):
458 458 return h[:12]
459 459
460 460 if ui.quiet:
461 461 datefunc = dateutil.shortdate
462 462 else:
463 463 datefunc = dateutil.datestr
464 464 if ctx.rev() is None:
465 465 if opts.get(b'changeset'):
466 466 # omit "+" suffix which is appended to node hex
467 467 def formatrev(rev):
468 468 if rev == wdirrev:
469 469 return b'%d' % ctx.p1().rev()
470 470 else:
471 471 return b'%d' % rev
472 472
473 473 else:
474 474
475 475 def formatrev(rev):
476 476 if rev == wdirrev:
477 477 return b'%d+' % ctx.p1().rev()
478 478 else:
479 479 return b'%d ' % rev
480 480
481 481 def formathex(h):
482 482 if h == wdirhex:
483 483 return b'%s+' % shorthex(hex(ctx.p1().node()))
484 484 else:
485 485 return b'%s ' % shorthex(h)
486 486
487 487 else:
488 488 formatrev = b'%d'.__mod__
489 489 formathex = shorthex
490 490
491 491 opmap = [
492 492 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
493 493 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
494 494 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
495 495 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
496 496 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
497 497 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
498 498 ]
499 499 opnamemap = {
500 500 b'rev': b'number',
501 501 b'node': b'changeset',
502 502 b'path': b'file',
503 503 b'lineno': b'line_number',
504 504 }
505 505
506 506 if rootfm.isplain():
507 507
508 508 def makefunc(get, fmt):
509 509 return lambda x: fmt(get(x))
510 510
511 511 else:
512 512
513 513 def makefunc(get, fmt):
514 514 return get
515 515
516 516 datahint = rootfm.datahint()
517 517 funcmap = [
518 518 (makefunc(get, fmt), sep)
519 519 for fn, sep, get, fmt in opmap
520 520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
521 521 ]
522 522 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
523 523 fields = b' '.join(
524 524 fn
525 525 for fn, sep, get, fmt in opmap
526 526 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
527 527 )
528 528
529 529 def bad(x, y):
530 530 raise error.Abort(b"%s: %s" % (x, y))
531 531
532 532 m = scmutil.match(ctx, pats, opts, badfn=bad)
533 533
534 534 follow = not opts.get(b'no_follow')
535 535 diffopts = patch.difffeatureopts(
536 536 ui, opts, section=b'annotate', whitespace=True
537 537 )
538 538 skiprevs = opts.get(b'skip')
539 539 if skiprevs:
540 540 skiprevs = scmutil.revrange(repo, skiprevs)
541 541
542 542 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
543 543 for abs in ctx.walk(m):
544 544 fctx = ctx[abs]
545 545 rootfm.startitem()
546 546 rootfm.data(path=abs)
547 547 if not opts.get(b'text') and fctx.isbinary():
548 548 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
549 549 continue
550 550
551 551 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
552 552 lines = fctx.annotate(
553 553 follow=follow, skiprevs=skiprevs, diffopts=diffopts
554 554 )
555 555 if not lines:
556 556 fm.end()
557 557 continue
558 558 formats = []
559 559 pieces = []
560 560
561 561 for f, sep in funcmap:
562 562 l = [f(n) for n in lines]
563 563 if fm.isplain():
564 564 sizes = [encoding.colwidth(x) for x in l]
565 565 ml = max(sizes)
566 566 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
567 567 else:
568 568 formats.append([b'%s'] * len(l))
569 569 pieces.append(l)
570 570
571 571 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
572 572 fm.startitem()
573 573 fm.context(fctx=n.fctx)
574 574 fm.write(fields, b"".join(f), *p)
575 575 if n.skip:
576 576 fmt = b"* %s"
577 577 else:
578 578 fmt = b": %s"
579 579 fm.write(b'line', fmt, n.text)
580 580
581 581 if not lines[-1].text.endswith(b'\n'):
582 582 fm.plain(b'\n')
583 583 fm.end()
584 584
585 585 rootfm.end()
586 586
587 587
588 588 @command(
589 589 b'archive',
590 590 [
591 591 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
592 592 (
593 593 b'p',
594 594 b'prefix',
595 595 b'',
596 596 _(b'directory prefix for files in archive'),
597 597 _(b'PREFIX'),
598 598 ),
599 599 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
600 600 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
601 601 ]
602 602 + subrepoopts
603 603 + walkopts,
604 604 _(b'[OPTION]... DEST'),
605 605 helpcategory=command.CATEGORY_IMPORT_EXPORT,
606 606 )
607 607 def archive(ui, repo, dest, **opts):
608 608 """create an unversioned archive of a repository revision
609 609
610 610 By default, the revision used is the parent of the working
611 611 directory; use -r/--rev to specify a different revision.
612 612
613 613 The archive type is automatically detected based on file
614 614 extension (to override, use -t/--type).
615 615
616 616 .. container:: verbose
617 617
618 618 Examples:
619 619
620 620 - create a zip file containing the 1.0 release::
621 621
622 622 hg archive -r 1.0 project-1.0.zip
623 623
624 624 - create a tarball excluding .hg files::
625 625
626 626 hg archive project.tar.gz -X ".hg*"
627 627
628 628 Valid types are:
629 629
630 630 :``files``: a directory full of files (default)
631 631 :``tar``: tar archive, uncompressed
632 632 :``tbz2``: tar archive, compressed using bzip2
633 633 :``tgz``: tar archive, compressed using gzip
634 634 :``txz``: tar archive, compressed using lzma (only in Python 3)
635 635 :``uzip``: zip archive, uncompressed
636 636 :``zip``: zip archive, compressed using deflate
637 637
638 638 The exact name of the destination archive or directory is given
639 639 using a format string; see :hg:`help export` for details.
640 640
641 641 Each member added to an archive file has a directory prefix
642 642 prepended. Use -p/--prefix to specify a format string for the
643 643 prefix. The default is the basename of the archive, with suffixes
644 644 removed.
645 645
646 646 Returns 0 on success.
647 647 """
648 648
649 649 opts = pycompat.byteskwargs(opts)
650 650 rev = opts.get(b'rev')
651 651 if rev:
652 652 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
653 653 ctx = scmutil.revsingle(repo, rev)
654 654 if not ctx:
655 655 raise error.InputError(
656 656 _(b'no working directory: please specify a revision')
657 657 )
658 658 node = ctx.node()
659 659 dest = cmdutil.makefilename(ctx, dest)
660 660 if os.path.realpath(dest) == repo.root:
661 661 raise error.InputError(_(b'repository root cannot be destination'))
662 662
663 663 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
664 664 prefix = opts.get(b'prefix')
665 665
666 666 if dest == b'-':
667 667 if kind == b'files':
668 668 raise error.InputError(_(b'cannot archive plain files to stdout'))
669 669 dest = cmdutil.makefileobj(ctx, dest)
670 670 if not prefix:
671 671 prefix = os.path.basename(repo.root) + b'-%h'
672 672
673 673 prefix = cmdutil.makefilename(ctx, prefix)
674 674 match = scmutil.match(ctx, [], opts)
675 675 archival.archive(
676 676 repo,
677 677 dest,
678 678 node,
679 679 kind,
680 680 not opts.get(b'no_decode'),
681 681 match,
682 682 prefix,
683 683 subrepos=opts.get(b'subrepos'),
684 684 )
685 685
686 686
687 687 @command(
688 688 b'backout',
689 689 [
690 690 (
691 691 b'',
692 692 b'merge',
693 693 None,
694 694 _(b'merge with old dirstate parent after backout'),
695 695 ),
696 696 (
697 697 b'',
698 698 b'commit',
699 699 None,
700 700 _(b'commit if no conflicts were encountered (DEPRECATED)'),
701 701 ),
702 702 (b'', b'no-commit', None, _(b'do not commit')),
703 703 (
704 704 b'',
705 705 b'parent',
706 706 b'',
707 707 _(b'parent to choose when backing out merge (DEPRECATED)'),
708 708 _(b'REV'),
709 709 ),
710 710 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
711 711 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
712 712 ]
713 713 + mergetoolopts
714 714 + walkopts
715 715 + commitopts
716 716 + commitopts2,
717 717 _(b'[OPTION]... [-r] REV'),
718 718 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
719 719 )
720 720 def backout(ui, repo, node=None, rev=None, **opts):
721 721 """reverse effect of earlier changeset
722 722
723 723 Prepare a new changeset with the effect of REV undone in the
724 724 current working directory. If no conflicts were encountered,
725 725 it will be committed immediately.
726 726
727 727 If REV is the parent of the working directory, then this new changeset
728 728 is committed automatically (unless --no-commit is specified).
729 729
730 730 .. note::
731 731
732 732 :hg:`backout` cannot be used to fix either an unwanted or
733 733 incorrect merge.
734 734
735 735 .. container:: verbose
736 736
737 737 Examples:
738 738
739 739 - Reverse the effect of the parent of the working directory.
740 740 This backout will be committed immediately::
741 741
742 742 hg backout -r .
743 743
744 744 - Reverse the effect of previous bad revision 23::
745 745
746 746 hg backout -r 23
747 747
748 748 - Reverse the effect of previous bad revision 23 and
749 749 leave changes uncommitted::
750 750
751 751 hg backout -r 23 --no-commit
752 752 hg commit -m "Backout revision 23"
753 753
754 754 By default, the pending changeset will have one parent,
755 755 maintaining a linear history. With --merge, the pending
756 756 changeset will instead have two parents: the old parent of the
757 757 working directory and a new child of REV that simply undoes REV.
758 758
759 759 Before version 1.7, the behavior without --merge was equivalent
760 760 to specifying --merge followed by :hg:`update --clean .` to
761 761 cancel the merge and leave the child of REV as a head to be
762 762 merged separately.
763 763
764 764 See :hg:`help dates` for a list of formats valid for -d/--date.
765 765
766 766 See :hg:`help revert` for a way to restore files to the state
767 767 of another revision.
768 768
769 769 Returns 0 on success, 1 if nothing to backout or there are unresolved
770 770 files.
771 771 """
772 772 with repo.wlock(), repo.lock():
773 773 return _dobackout(ui, repo, node, rev, **opts)
774 774
775 775
776 776 def _dobackout(ui, repo, node=None, rev=None, **opts):
777 777 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
778 778 opts = pycompat.byteskwargs(opts)
779 779
780 780 if rev and node:
781 781 raise error.InputError(_(b"please specify just one revision"))
782 782
783 783 if not rev:
784 784 rev = node
785 785
786 786 if not rev:
787 787 raise error.InputError(_(b"please specify a revision to backout"))
788 788
789 789 date = opts.get(b'date')
790 790 if date:
791 791 opts[b'date'] = dateutil.parsedate(date)
792 792
793 793 cmdutil.checkunfinished(repo)
794 794 cmdutil.bailifchanged(repo)
795 795 ctx = scmutil.revsingle(repo, rev)
796 796 node = ctx.node()
797 797
798 798 op1, op2 = repo.dirstate.parents()
799 799 if not repo.changelog.isancestor(node, op1):
800 800 raise error.InputError(
801 801 _(b'cannot backout change that is not an ancestor')
802 802 )
803 803
804 804 p1, p2 = repo.changelog.parents(node)
805 805 if p1 == nullid:
806 806 raise error.InputError(_(b'cannot backout a change with no parents'))
807 807 if p2 != nullid:
808 808 if not opts.get(b'parent'):
809 809 raise error.InputError(_(b'cannot backout a merge changeset'))
810 810 p = repo.lookup(opts[b'parent'])
811 811 if p not in (p1, p2):
812 812 raise error.InputError(
813 813 _(b'%s is not a parent of %s') % (short(p), short(node))
814 814 )
815 815 parent = p
816 816 else:
817 817 if opts.get(b'parent'):
818 818 raise error.InputError(
819 819 _(b'cannot use --parent on non-merge changeset')
820 820 )
821 821 parent = p1
822 822
823 823 # the backout should appear on the same branch
824 824 branch = repo.dirstate.branch()
825 825 bheads = repo.branchheads(branch)
826 826 rctx = scmutil.revsingle(repo, hex(parent))
827 827 if not opts.get(b'merge') and op1 != node:
828 828 with dirstateguard.dirstateguard(repo, b'backout'):
829 829 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
830 830 with ui.configoverride(overrides, b'backout'):
831 831 stats = mergemod.back_out(ctx, parent=repo[parent])
832 832 repo.setparents(op1, op2)
833 833 hg._showstats(repo, stats)
834 834 if stats.unresolvedcount:
835 835 repo.ui.status(
836 836 _(b"use 'hg resolve' to retry unresolved file merges\n")
837 837 )
838 838 return 1
839 839 else:
840 840 hg.clean(repo, node, show_stats=False)
841 841 repo.dirstate.setbranch(branch)
842 842 cmdutil.revert(ui, repo, rctx)
843 843
844 844 if opts.get(b'no_commit'):
845 845 msg = _(b"changeset %s backed out, don't forget to commit.\n")
846 846 ui.status(msg % short(node))
847 847 return 0
848 848
849 849 def commitfunc(ui, repo, message, match, opts):
850 850 editform = b'backout'
851 851 e = cmdutil.getcommiteditor(
852 852 editform=editform, **pycompat.strkwargs(opts)
853 853 )
854 854 if not message:
855 855 # we don't translate commit messages
856 856 message = b"Backed out changeset %s" % short(node)
857 857 e = cmdutil.getcommiteditor(edit=True, editform=editform)
858 858 return repo.commit(
859 859 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
860 860 )
861 861
862 862 # save to detect changes
863 863 tip = repo.changelog.tip()
864 864
865 865 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
866 866 if not newnode:
867 867 ui.status(_(b"nothing changed\n"))
868 868 return 1
869 869 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
870 870
871 871 def nice(node):
872 872 return b'%d:%s' % (repo.changelog.rev(node), short(node))
873 873
874 874 ui.status(
875 875 _(b'changeset %s backs out changeset %s\n')
876 876 % (nice(newnode), nice(node))
877 877 )
878 878 if opts.get(b'merge') and op1 != node:
879 879 hg.clean(repo, op1, show_stats=False)
880 880 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
881 881 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
882 882 with ui.configoverride(overrides, b'backout'):
883 883 return hg.merge(repo[b'tip'])
884 884 return 0
885 885
886 886
887 887 @command(
888 888 b'bisect',
889 889 [
890 890 (b'r', b'reset', False, _(b'reset bisect state')),
891 891 (b'g', b'good', False, _(b'mark changeset good')),
892 892 (b'b', b'bad', False, _(b'mark changeset bad')),
893 893 (b's', b'skip', False, _(b'skip testing changeset')),
894 894 (b'e', b'extend', False, _(b'extend the bisect range')),
895 895 (
896 896 b'c',
897 897 b'command',
898 898 b'',
899 899 _(b'use command to check changeset state'),
900 900 _(b'CMD'),
901 901 ),
902 902 (b'U', b'noupdate', False, _(b'do not update to target')),
903 903 ],
904 904 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
905 905 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
906 906 )
907 907 def bisect(
908 908 ui,
909 909 repo,
910 910 positional_1=None,
911 911 positional_2=None,
912 912 command=None,
913 913 reset=None,
914 914 good=None,
915 915 bad=None,
916 916 skip=None,
917 917 extend=None,
918 918 noupdate=None,
919 919 ):
920 920 """subdivision search of changesets
921 921
922 922 This command helps to find changesets which introduce problems. To
923 923 use, mark the earliest changeset you know exhibits the problem as
924 924 bad, then mark the latest changeset which is free from the problem
925 925 as good. Bisect will update your working directory to a revision
926 926 for testing (unless the -U/--noupdate option is specified). Once
927 927 you have performed tests, mark the working directory as good or
928 928 bad, and bisect will either update to another candidate changeset
929 929 or announce that it has found the bad revision.
930 930
931 931 As a shortcut, you can also use the revision argument to mark a
932 932 revision as good or bad without checking it out first.
933 933
934 934 If you supply a command, it will be used for automatic bisection.
935 935 The environment variable HG_NODE will contain the ID of the
936 936 changeset being tested. The exit status of the command will be
937 937 used to mark revisions as good or bad: status 0 means good, 125
938 938 means to skip the revision, 127 (command not found) will abort the
939 939 bisection, and any other non-zero exit status means the revision
940 940 is bad.
941 941
942 942 .. container:: verbose
943 943
944 944 Some examples:
945 945
946 946 - start a bisection with known bad revision 34, and good revision 12::
947 947
948 948 hg bisect --bad 34
949 949 hg bisect --good 12
950 950
951 951 - advance the current bisection by marking current revision as good or
952 952 bad::
953 953
954 954 hg bisect --good
955 955 hg bisect --bad
956 956
957 957 - mark the current revision, or a known revision, to be skipped (e.g. if
958 958 that revision is not usable because of another issue)::
959 959
960 960 hg bisect --skip
961 961 hg bisect --skip 23
962 962
963 963 - skip all revisions that do not touch directories ``foo`` or ``bar``::
964 964
965 965 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
966 966
967 967 - forget the current bisection::
968 968
969 969 hg bisect --reset
970 970
971 971 - use 'make && make tests' to automatically find the first broken
972 972 revision::
973 973
974 974 hg bisect --reset
975 975 hg bisect --bad 34
976 976 hg bisect --good 12
977 977 hg bisect --command "make && make tests"
978 978
979 979 - see all changesets whose states are already known in the current
980 980 bisection::
981 981
982 982 hg log -r "bisect(pruned)"
983 983
984 984 - see the changeset currently being bisected (especially useful
985 985 if running with -U/--noupdate)::
986 986
987 987 hg log -r "bisect(current)"
988 988
989 989 - see all changesets that took part in the current bisection::
990 990
991 991 hg log -r "bisect(range)"
992 992
993 993 - you can even get a nice graph::
994 994
995 995 hg log --graph -r "bisect(range)"
996 996
997 997 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
998 998
999 999 Returns 0 on success.
1000 1000 """
1001 1001 rev = []
1002 1002 # backward compatibility
1003 1003 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1004 1004 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1005 1005 cmd = positional_1
1006 1006 rev.append(positional_2)
1007 1007 if cmd == b"good":
1008 1008 good = True
1009 1009 elif cmd == b"bad":
1010 1010 bad = True
1011 1011 else:
1012 1012 reset = True
1013 1013 elif positional_2:
1014 1014 raise error.InputError(_(b'incompatible arguments'))
1015 1015 elif positional_1 is not None:
1016 1016 rev.append(positional_1)
1017 1017
1018 1018 incompatibles = {
1019 1019 b'--bad': bad,
1020 1020 b'--command': bool(command),
1021 1021 b'--extend': extend,
1022 1022 b'--good': good,
1023 1023 b'--reset': reset,
1024 1024 b'--skip': skip,
1025 1025 }
1026 1026
1027 1027 enabled = [x for x in incompatibles if incompatibles[x]]
1028 1028
1029 1029 if len(enabled) > 1:
1030 1030 raise error.InputError(
1031 1031 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1032 1032 )
1033 1033
1034 1034 if reset:
1035 1035 hbisect.resetstate(repo)
1036 1036 return
1037 1037
1038 1038 state = hbisect.load_state(repo)
1039 1039
1040 1040 if rev:
1041 1041 nodes = [repo[i].node() for i in scmutil.revrange(repo, rev)]
1042 1042 else:
1043 1043 nodes = [repo.lookup(b'.')]
1044 1044
1045 1045 # update state
1046 1046 if good or bad or skip:
1047 1047 if good:
1048 1048 state[b'good'] += nodes
1049 1049 elif bad:
1050 1050 state[b'bad'] += nodes
1051 1051 elif skip:
1052 1052 state[b'skip'] += nodes
1053 1053 hbisect.save_state(repo, state)
1054 1054 if not (state[b'good'] and state[b'bad']):
1055 1055 return
1056 1056
1057 1057 def mayupdate(repo, node, show_stats=True):
1058 1058 """common used update sequence"""
1059 1059 if noupdate:
1060 1060 return
1061 1061 cmdutil.checkunfinished(repo)
1062 1062 cmdutil.bailifchanged(repo)
1063 1063 return hg.clean(repo, node, show_stats=show_stats)
1064 1064
1065 1065 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1066 1066
1067 1067 if command:
1068 1068 changesets = 1
1069 1069 if noupdate:
1070 1070 try:
1071 1071 node = state[b'current'][0]
1072 1072 except LookupError:
1073 1073 raise error.StateError(
1074 1074 _(
1075 1075 b'current bisect revision is unknown - '
1076 1076 b'start a new bisect to fix'
1077 1077 )
1078 1078 )
1079 1079 else:
1080 1080 node, p2 = repo.dirstate.parents()
1081 1081 if p2 != nullid:
1082 1082 raise error.StateError(_(b'current bisect revision is a merge'))
1083 1083 if rev:
1084 1084 if not nodes:
1085 1085 raise error.Abort(_(b'empty revision set'))
1086 1086 node = repo[nodes.last()].node()
1087 1087 with hbisect.restore_state(repo, state, node):
1088 1088 while changesets:
1089 1089 # update state
1090 1090 state[b'current'] = [node]
1091 1091 hbisect.save_state(repo, state)
1092 1092 status = ui.system(
1093 1093 command,
1094 1094 environ={b'HG_NODE': hex(node)},
1095 1095 blockedtag=b'bisect_check',
1096 1096 )
1097 1097 if status == 125:
1098 1098 transition = b"skip"
1099 1099 elif status == 0:
1100 1100 transition = b"good"
1101 1101 # status < 0 means process was killed
1102 1102 elif status == 127:
1103 1103 raise error.Abort(_(b"failed to execute %s") % command)
1104 1104 elif status < 0:
1105 1105 raise error.Abort(_(b"%s killed") % command)
1106 1106 else:
1107 1107 transition = b"bad"
1108 1108 state[transition].append(node)
1109 1109 ctx = repo[node]
1110 1110 ui.status(
1111 1111 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1112 1112 )
1113 1113 hbisect.checkstate(state)
1114 1114 # bisect
1115 1115 nodes, changesets, bgood = hbisect.bisect(repo, state)
1116 1116 # update to next check
1117 1117 node = nodes[0]
1118 1118 mayupdate(repo, node, show_stats=False)
1119 1119 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1120 1120 return
1121 1121
1122 1122 hbisect.checkstate(state)
1123 1123
1124 1124 # actually bisect
1125 1125 nodes, changesets, good = hbisect.bisect(repo, state)
1126 1126 if extend:
1127 1127 if not changesets:
1128 1128 extendnode = hbisect.extendrange(repo, state, nodes, good)
1129 1129 if extendnode is not None:
1130 1130 ui.write(
1131 1131 _(b"Extending search to changeset %d:%s\n")
1132 1132 % (extendnode.rev(), extendnode)
1133 1133 )
1134 1134 state[b'current'] = [extendnode.node()]
1135 1135 hbisect.save_state(repo, state)
1136 1136 return mayupdate(repo, extendnode.node())
1137 1137 raise error.StateError(_(b"nothing to extend"))
1138 1138
1139 1139 if changesets == 0:
1140 1140 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1141 1141 else:
1142 1142 assert len(nodes) == 1 # only a single node can be tested next
1143 1143 node = nodes[0]
1144 1144 # compute the approximate number of remaining tests
1145 1145 tests, size = 0, 2
1146 1146 while size <= changesets:
1147 1147 tests, size = tests + 1, size * 2
1148 1148 rev = repo.changelog.rev(node)
1149 1149 ui.write(
1150 1150 _(
1151 1151 b"Testing changeset %d:%s "
1152 1152 b"(%d changesets remaining, ~%d tests)\n"
1153 1153 )
1154 1154 % (rev, short(node), changesets, tests)
1155 1155 )
1156 1156 state[b'current'] = [node]
1157 1157 hbisect.save_state(repo, state)
1158 1158 return mayupdate(repo, node)
1159 1159
1160 1160
1161 1161 @command(
1162 1162 b'bookmarks|bookmark',
1163 1163 [
1164 1164 (b'f', b'force', False, _(b'force')),
1165 1165 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1166 1166 (b'd', b'delete', False, _(b'delete a given bookmark')),
1167 1167 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1168 1168 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1169 1169 (b'l', b'list', False, _(b'list existing bookmarks')),
1170 1170 ]
1171 1171 + formatteropts,
1172 1172 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1173 1173 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1174 1174 )
1175 1175 def bookmark(ui, repo, *names, **opts):
1176 1176 """create a new bookmark or list existing bookmarks
1177 1177
1178 1178 Bookmarks are labels on changesets to help track lines of development.
1179 1179 Bookmarks are unversioned and can be moved, renamed and deleted.
1180 1180 Deleting or moving a bookmark has no effect on the associated changesets.
1181 1181
1182 1182 Creating or updating to a bookmark causes it to be marked as 'active'.
1183 1183 The active bookmark is indicated with a '*'.
1184 1184 When a commit is made, the active bookmark will advance to the new commit.
1185 1185 A plain :hg:`update` will also advance an active bookmark, if possible.
1186 1186 Updating away from a bookmark will cause it to be deactivated.
1187 1187
1188 1188 Bookmarks can be pushed and pulled between repositories (see
1189 1189 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1190 1190 diverged, a new 'divergent bookmark' of the form 'name@path' will
1191 1191 be created. Using :hg:`merge` will resolve the divergence.
1192 1192
1193 1193 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1194 1194 the active bookmark's name.
1195 1195
1196 1196 A bookmark named '@' has the special property that :hg:`clone` will
1197 1197 check it out by default if it exists.
1198 1198
1199 1199 .. container:: verbose
1200 1200
1201 1201 Template:
1202 1202
1203 1203 The following keywords are supported in addition to the common template
1204 1204 keywords and functions such as ``{bookmark}``. See also
1205 1205 :hg:`help templates`.
1206 1206
1207 1207 :active: Boolean. True if the bookmark is active.
1208 1208
1209 1209 Examples:
1210 1210
1211 1211 - create an active bookmark for a new line of development::
1212 1212
1213 1213 hg book new-feature
1214 1214
1215 1215 - create an inactive bookmark as a place marker::
1216 1216
1217 1217 hg book -i reviewed
1218 1218
1219 1219 - create an inactive bookmark on another changeset::
1220 1220
1221 1221 hg book -r .^ tested
1222 1222
1223 1223 - rename bookmark turkey to dinner::
1224 1224
1225 1225 hg book -m turkey dinner
1226 1226
1227 1227 - move the '@' bookmark from another branch::
1228 1228
1229 1229 hg book -f @
1230 1230
1231 1231 - print only the active bookmark name::
1232 1232
1233 1233 hg book -ql .
1234 1234 """
1235 1235 opts = pycompat.byteskwargs(opts)
1236 1236 force = opts.get(b'force')
1237 1237 rev = opts.get(b'rev')
1238 1238 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1239 1239
1240 1240 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1241 1241 if action:
1242 1242 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1243 1243 elif names or rev:
1244 1244 action = b'add'
1245 1245 elif inactive:
1246 1246 action = b'inactive' # meaning deactivate
1247 1247 else:
1248 1248 action = b'list'
1249 1249
1250 1250 cmdutil.check_incompatible_arguments(
1251 1251 opts, b'inactive', [b'delete', b'list']
1252 1252 )
1253 1253 if not names and action in {b'add', b'delete'}:
1254 1254 raise error.InputError(_(b"bookmark name required"))
1255 1255
1256 1256 if action in {b'add', b'delete', b'rename', b'inactive'}:
1257 1257 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1258 1258 if action == b'delete':
1259 1259 names = pycompat.maplist(repo._bookmarks.expandname, names)
1260 1260 bookmarks.delete(repo, tr, names)
1261 1261 elif action == b'rename':
1262 1262 if not names:
1263 1263 raise error.InputError(_(b"new bookmark name required"))
1264 1264 elif len(names) > 1:
1265 1265 raise error.InputError(
1266 1266 _(b"only one new bookmark name allowed")
1267 1267 )
1268 1268 oldname = repo._bookmarks.expandname(opts[b'rename'])
1269 1269 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1270 1270 elif action == b'add':
1271 1271 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1272 1272 elif action == b'inactive':
1273 1273 if len(repo._bookmarks) == 0:
1274 1274 ui.status(_(b"no bookmarks set\n"))
1275 1275 elif not repo._activebookmark:
1276 1276 ui.status(_(b"no active bookmark\n"))
1277 1277 else:
1278 1278 bookmarks.deactivate(repo)
1279 1279 elif action == b'list':
1280 1280 names = pycompat.maplist(repo._bookmarks.expandname, names)
1281 1281 with ui.formatter(b'bookmarks', opts) as fm:
1282 1282 bookmarks.printbookmarks(ui, repo, fm, names)
1283 1283 else:
1284 1284 raise error.ProgrammingError(b'invalid action: %s' % action)
1285 1285
1286 1286
1287 1287 @command(
1288 1288 b'branch',
1289 1289 [
1290 1290 (
1291 1291 b'f',
1292 1292 b'force',
1293 1293 None,
1294 1294 _(b'set branch name even if it shadows an existing branch'),
1295 1295 ),
1296 1296 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1297 1297 (
1298 1298 b'r',
1299 1299 b'rev',
1300 1300 [],
1301 1301 _(b'change branches of the given revs (EXPERIMENTAL)'),
1302 1302 ),
1303 1303 ],
1304 1304 _(b'[-fC] [NAME]'),
1305 1305 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1306 1306 )
1307 1307 def branch(ui, repo, label=None, **opts):
1308 1308 """set or show the current branch name
1309 1309
1310 1310 .. note::
1311 1311
1312 1312 Branch names are permanent and global. Use :hg:`bookmark` to create a
1313 1313 light-weight bookmark instead. See :hg:`help glossary` for more
1314 1314 information about named branches and bookmarks.
1315 1315
1316 1316 With no argument, show the current branch name. With one argument,
1317 1317 set the working directory branch name (the branch will not exist
1318 1318 in the repository until the next commit). Standard practice
1319 1319 recommends that primary development take place on the 'default'
1320 1320 branch.
1321 1321
1322 1322 Unless -f/--force is specified, branch will not let you set a
1323 1323 branch name that already exists.
1324 1324
1325 1325 Use -C/--clean to reset the working directory branch to that of
1326 1326 the parent of the working directory, negating a previous branch
1327 1327 change.
1328 1328
1329 1329 Use the command :hg:`update` to switch to an existing branch. Use
1330 1330 :hg:`commit --close-branch` to mark this branch head as closed.
1331 1331 When all heads of a branch are closed, the branch will be
1332 1332 considered closed.
1333 1333
1334 1334 Returns 0 on success.
1335 1335 """
1336 1336 opts = pycompat.byteskwargs(opts)
1337 1337 revs = opts.get(b'rev')
1338 1338 if label:
1339 1339 label = label.strip()
1340 1340
1341 1341 if not opts.get(b'clean') and not label:
1342 1342 if revs:
1343 1343 raise error.InputError(
1344 1344 _(b"no branch name specified for the revisions")
1345 1345 )
1346 1346 ui.write(b"%s\n" % repo.dirstate.branch())
1347 1347 return
1348 1348
1349 1349 with repo.wlock():
1350 1350 if opts.get(b'clean'):
1351 1351 label = repo[b'.'].branch()
1352 1352 repo.dirstate.setbranch(label)
1353 1353 ui.status(_(b'reset working directory to branch %s\n') % label)
1354 1354 elif label:
1355 1355
1356 1356 scmutil.checknewlabel(repo, label, b'branch')
1357 1357 if revs:
1358 1358 return cmdutil.changebranch(ui, repo, revs, label, opts)
1359 1359
1360 1360 if not opts.get(b'force') and label in repo.branchmap():
1361 1361 if label not in [p.branch() for p in repo[None].parents()]:
1362 1362 raise error.InputError(
1363 1363 _(b'a branch of the same name already exists'),
1364 1364 # i18n: "it" refers to an existing branch
1365 1365 hint=_(b"use 'hg update' to switch to it"),
1366 1366 )
1367 1367
1368 1368 repo.dirstate.setbranch(label)
1369 1369 ui.status(_(b'marked working directory as branch %s\n') % label)
1370 1370
1371 1371 # find any open named branches aside from default
1372 1372 for n, h, t, c in repo.branchmap().iterbranches():
1373 1373 if n != b"default" and not c:
1374 1374 return 0
1375 1375 ui.status(
1376 1376 _(
1377 1377 b'(branches are permanent and global, '
1378 1378 b'did you want a bookmark?)\n'
1379 1379 )
1380 1380 )
1381 1381
1382 1382
1383 1383 @command(
1384 1384 b'branches',
1385 1385 [
1386 1386 (
1387 1387 b'a',
1388 1388 b'active',
1389 1389 False,
1390 1390 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1391 1391 ),
1392 1392 (b'c', b'closed', False, _(b'show normal and closed branches')),
1393 1393 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1394 1394 ]
1395 1395 + formatteropts,
1396 1396 _(b'[-c]'),
1397 1397 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1398 1398 intents={INTENT_READONLY},
1399 1399 )
1400 1400 def branches(ui, repo, active=False, closed=False, **opts):
1401 1401 """list repository named branches
1402 1402
1403 1403 List the repository's named branches, indicating which ones are
1404 1404 inactive. If -c/--closed is specified, also list branches which have
1405 1405 been marked closed (see :hg:`commit --close-branch`).
1406 1406
1407 1407 Use the command :hg:`update` to switch to an existing branch.
1408 1408
1409 1409 .. container:: verbose
1410 1410
1411 1411 Template:
1412 1412
1413 1413 The following keywords are supported in addition to the common template
1414 1414 keywords and functions such as ``{branch}``. See also
1415 1415 :hg:`help templates`.
1416 1416
1417 1417 :active: Boolean. True if the branch is active.
1418 1418 :closed: Boolean. True if the branch is closed.
1419 1419 :current: Boolean. True if it is the current branch.
1420 1420
1421 1421 Returns 0.
1422 1422 """
1423 1423
1424 1424 opts = pycompat.byteskwargs(opts)
1425 1425 revs = opts.get(b'rev')
1426 1426 selectedbranches = None
1427 1427 if revs:
1428 1428 revs = scmutil.revrange(repo, revs)
1429 1429 getbi = repo.revbranchcache().branchinfo
1430 1430 selectedbranches = {getbi(r)[0] for r in revs}
1431 1431
1432 1432 ui.pager(b'branches')
1433 1433 fm = ui.formatter(b'branches', opts)
1434 1434 hexfunc = fm.hexfunc
1435 1435
1436 1436 allheads = set(repo.heads())
1437 1437 branches = []
1438 1438 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1439 1439 if selectedbranches is not None and tag not in selectedbranches:
1440 1440 continue
1441 1441 isactive = False
1442 1442 if not isclosed:
1443 1443 openheads = set(repo.branchmap().iteropen(heads))
1444 1444 isactive = bool(openheads & allheads)
1445 1445 branches.append((tag, repo[tip], isactive, not isclosed))
1446 1446 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1447 1447
1448 1448 for tag, ctx, isactive, isopen in branches:
1449 1449 if active and not isactive:
1450 1450 continue
1451 1451 if isactive:
1452 1452 label = b'branches.active'
1453 1453 notice = b''
1454 1454 elif not isopen:
1455 1455 if not closed:
1456 1456 continue
1457 1457 label = b'branches.closed'
1458 1458 notice = _(b' (closed)')
1459 1459 else:
1460 1460 label = b'branches.inactive'
1461 1461 notice = _(b' (inactive)')
1462 1462 current = tag == repo.dirstate.branch()
1463 1463 if current:
1464 1464 label = b'branches.current'
1465 1465
1466 1466 fm.startitem()
1467 1467 fm.write(b'branch', b'%s', tag, label=label)
1468 1468 rev = ctx.rev()
1469 1469 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1470 1470 fmt = b' ' * padsize + b' %d:%s'
1471 1471 fm.condwrite(
1472 1472 not ui.quiet,
1473 1473 b'rev node',
1474 1474 fmt,
1475 1475 rev,
1476 1476 hexfunc(ctx.node()),
1477 1477 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1478 1478 )
1479 1479 fm.context(ctx=ctx)
1480 1480 fm.data(active=isactive, closed=not isopen, current=current)
1481 1481 if not ui.quiet:
1482 1482 fm.plain(notice)
1483 1483 fm.plain(b'\n')
1484 1484 fm.end()
1485 1485
1486 1486
1487 1487 @command(
1488 1488 b'bundle',
1489 1489 [
1490 1490 (
1491 1491 b'f',
1492 1492 b'force',
1493 1493 None,
1494 1494 _(b'run even when the destination is unrelated'),
1495 1495 ),
1496 1496 (
1497 1497 b'r',
1498 1498 b'rev',
1499 1499 [],
1500 1500 _(b'a changeset intended to be added to the destination'),
1501 1501 _(b'REV'),
1502 1502 ),
1503 1503 (
1504 1504 b'b',
1505 1505 b'branch',
1506 1506 [],
1507 1507 _(b'a specific branch you would like to bundle'),
1508 1508 _(b'BRANCH'),
1509 1509 ),
1510 1510 (
1511 1511 b'',
1512 1512 b'base',
1513 1513 [],
1514 1514 _(b'a base changeset assumed to be available at the destination'),
1515 1515 _(b'REV'),
1516 1516 ),
1517 1517 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1518 1518 (
1519 1519 b't',
1520 1520 b'type',
1521 1521 b'bzip2',
1522 1522 _(b'bundle compression type to use'),
1523 1523 _(b'TYPE'),
1524 1524 ),
1525 1525 ]
1526 1526 + remoteopts,
1527 1527 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1528 1528 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1529 1529 )
1530 1530 def bundle(ui, repo, fname, dest=None, **opts):
1531 1531 """create a bundle file
1532 1532
1533 1533 Generate a bundle file containing data to be transferred to another
1534 1534 repository.
1535 1535
1536 1536 To create a bundle containing all changesets, use -a/--all
1537 1537 (or --base null). Otherwise, hg assumes the destination will have
1538 1538 all the nodes you specify with --base parameters. Otherwise, hg
1539 1539 will assume the repository has all the nodes in destination, or
1540 1540 default-push/default if no destination is specified, where destination
1541 1541 is the repository you provide through DEST option.
1542 1542
1543 1543 You can change bundle format with the -t/--type option. See
1544 1544 :hg:`help bundlespec` for documentation on this format. By default,
1545 1545 the most appropriate format is used and compression defaults to
1546 1546 bzip2.
1547 1547
1548 1548 The bundle file can then be transferred using conventional means
1549 1549 and applied to another repository with the unbundle or pull
1550 1550 command. This is useful when direct push and pull are not
1551 1551 available or when exporting an entire repository is undesirable.
1552 1552
1553 1553 Applying bundles preserves all changeset contents including
1554 1554 permissions, copy/rename information, and revision history.
1555 1555
1556 1556 Returns 0 on success, 1 if no changes found.
1557 1557 """
1558 1558 opts = pycompat.byteskwargs(opts)
1559 1559 revs = None
1560 1560 if b'rev' in opts:
1561 1561 revstrings = opts[b'rev']
1562 1562 revs = scmutil.revrange(repo, revstrings)
1563 1563 if revstrings and not revs:
1564 1564 raise error.InputError(_(b'no commits to bundle'))
1565 1565
1566 1566 bundletype = opts.get(b'type', b'bzip2').lower()
1567 1567 try:
1568 1568 bundlespec = bundlecaches.parsebundlespec(
1569 1569 repo, bundletype, strict=False
1570 1570 )
1571 1571 except error.UnsupportedBundleSpecification as e:
1572 1572 raise error.InputError(
1573 1573 pycompat.bytestr(e),
1574 1574 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1575 1575 )
1576 1576 cgversion = bundlespec.contentopts[b"cg.version"]
1577 1577
1578 1578 # Packed bundles are a pseudo bundle format for now.
1579 1579 if cgversion == b's1':
1580 1580 raise error.InputError(
1581 1581 _(b'packed bundles cannot be produced by "hg bundle"'),
1582 1582 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1583 1583 )
1584 1584
1585 1585 if opts.get(b'all'):
1586 1586 if dest:
1587 1587 raise error.InputError(
1588 1588 _(b"--all is incompatible with specifying a destination")
1589 1589 )
1590 1590 if opts.get(b'base'):
1591 1591 ui.warn(_(b"ignoring --base because --all was specified\n"))
1592 1592 base = [nullrev]
1593 1593 else:
1594 1594 base = scmutil.revrange(repo, opts.get(b'base'))
1595 1595 if cgversion not in changegroup.supportedoutgoingversions(repo):
1596 1596 raise error.Abort(
1597 1597 _(b"repository does not support bundle version %s") % cgversion
1598 1598 )
1599 1599
1600 1600 if base:
1601 1601 if dest:
1602 1602 raise error.InputError(
1603 1603 _(b"--base is incompatible with specifying a destination")
1604 1604 )
1605 1605 common = [repo[rev].node() for rev in base]
1606 1606 heads = [repo[r].node() for r in revs] if revs else None
1607 1607 outgoing = discovery.outgoing(repo, common, heads)
1608 1608 else:
1609 1609 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1610 1610 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1611 1611 other = hg.peer(repo, opts, dest)
1612 1612 revs = [repo[r].hex() for r in revs]
1613 1613 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1614 1614 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1615 1615 outgoing = discovery.findcommonoutgoing(
1616 1616 repo,
1617 1617 other,
1618 1618 onlyheads=heads,
1619 1619 force=opts.get(b'force'),
1620 1620 portable=True,
1621 1621 )
1622 1622
1623 1623 if not outgoing.missing:
1624 1624 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1625 1625 return 1
1626 1626
1627 1627 if cgversion == b'01': # bundle1
1628 1628 bversion = b'HG10' + bundlespec.wirecompression
1629 1629 bcompression = None
1630 1630 elif cgversion in (b'02', b'03'):
1631 1631 bversion = b'HG20'
1632 1632 bcompression = bundlespec.wirecompression
1633 1633 else:
1634 1634 raise error.ProgrammingError(
1635 1635 b'bundle: unexpected changegroup version %s' % cgversion
1636 1636 )
1637 1637
1638 1638 # TODO compression options should be derived from bundlespec parsing.
1639 1639 # This is a temporary hack to allow adjusting bundle compression
1640 1640 # level without a) formalizing the bundlespec changes to declare it
1641 1641 # b) introducing a command flag.
1642 1642 compopts = {}
1643 1643 complevel = ui.configint(
1644 1644 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1645 1645 )
1646 1646 if complevel is None:
1647 1647 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1648 1648 if complevel is not None:
1649 1649 compopts[b'level'] = complevel
1650 1650
1651 1651 # Bundling of obsmarker and phases is optional as not all clients
1652 1652 # support the necessary features.
1653 1653 cfg = ui.configbool
1654 1654 contentopts = {
1655 1655 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1656 1656 b'obsolescence-mandatory': cfg(
1657 1657 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1658 1658 ),
1659 1659 b'phases': cfg(b'experimental', b'bundle-phases'),
1660 1660 }
1661 1661 bundlespec.contentopts.update(contentopts)
1662 1662
1663 1663 bundle2.writenewbundle(
1664 1664 ui,
1665 1665 repo,
1666 1666 b'bundle',
1667 1667 fname,
1668 1668 bversion,
1669 1669 outgoing,
1670 1670 bundlespec.contentopts,
1671 1671 compression=bcompression,
1672 1672 compopts=compopts,
1673 1673 )
1674 1674
1675 1675
1676 1676 @command(
1677 1677 b'cat',
1678 1678 [
1679 1679 (
1680 1680 b'o',
1681 1681 b'output',
1682 1682 b'',
1683 1683 _(b'print output to file with formatted name'),
1684 1684 _(b'FORMAT'),
1685 1685 ),
1686 1686 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1687 1687 (b'', b'decode', None, _(b'apply any matching decode filter')),
1688 1688 ]
1689 1689 + walkopts
1690 1690 + formatteropts,
1691 1691 _(b'[OPTION]... FILE...'),
1692 1692 helpcategory=command.CATEGORY_FILE_CONTENTS,
1693 1693 inferrepo=True,
1694 1694 intents={INTENT_READONLY},
1695 1695 )
1696 1696 def cat(ui, repo, file1, *pats, **opts):
1697 1697 """output the current or given revision of files
1698 1698
1699 1699 Print the specified files as they were at the given revision. If
1700 1700 no revision is given, the parent of the working directory is used.
1701 1701
1702 1702 Output may be to a file, in which case the name of the file is
1703 1703 given using a template string. See :hg:`help templates`. In addition
1704 1704 to the common template keywords, the following formatting rules are
1705 1705 supported:
1706 1706
1707 1707 :``%%``: literal "%" character
1708 1708 :``%s``: basename of file being printed
1709 1709 :``%d``: dirname of file being printed, or '.' if in repository root
1710 1710 :``%p``: root-relative path name of file being printed
1711 1711 :``%H``: changeset hash (40 hexadecimal digits)
1712 1712 :``%R``: changeset revision number
1713 1713 :``%h``: short-form changeset hash (12 hexadecimal digits)
1714 1714 :``%r``: zero-padded changeset revision number
1715 1715 :``%b``: basename of the exporting repository
1716 1716 :``\\``: literal "\\" character
1717 1717
1718 1718 .. container:: verbose
1719 1719
1720 1720 Template:
1721 1721
1722 1722 The following keywords are supported in addition to the common template
1723 1723 keywords and functions. See also :hg:`help templates`.
1724 1724
1725 1725 :data: String. File content.
1726 1726 :path: String. Repository-absolute path of the file.
1727 1727
1728 1728 Returns 0 on success.
1729 1729 """
1730 1730 opts = pycompat.byteskwargs(opts)
1731 1731 rev = opts.get(b'rev')
1732 1732 if rev:
1733 1733 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1734 1734 ctx = scmutil.revsingle(repo, rev)
1735 1735 m = scmutil.match(ctx, (file1,) + pats, opts)
1736 1736 fntemplate = opts.pop(b'output', b'')
1737 1737 if cmdutil.isstdiofilename(fntemplate):
1738 1738 fntemplate = b''
1739 1739
1740 1740 if fntemplate:
1741 1741 fm = formatter.nullformatter(ui, b'cat', opts)
1742 1742 else:
1743 1743 ui.pager(b'cat')
1744 1744 fm = ui.formatter(b'cat', opts)
1745 1745 with fm:
1746 1746 return cmdutil.cat(
1747 1747 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1748 1748 )
1749 1749
1750 1750
1751 1751 @command(
1752 1752 b'clone',
1753 1753 [
1754 1754 (
1755 1755 b'U',
1756 1756 b'noupdate',
1757 1757 None,
1758 1758 _(
1759 1759 b'the clone will include an empty working '
1760 1760 b'directory (only a repository)'
1761 1761 ),
1762 1762 ),
1763 1763 (
1764 1764 b'u',
1765 1765 b'updaterev',
1766 1766 b'',
1767 1767 _(b'revision, tag, or branch to check out'),
1768 1768 _(b'REV'),
1769 1769 ),
1770 1770 (
1771 1771 b'r',
1772 1772 b'rev',
1773 1773 [],
1774 1774 _(
1775 1775 b'do not clone everything, but include this changeset'
1776 1776 b' and its ancestors'
1777 1777 ),
1778 1778 _(b'REV'),
1779 1779 ),
1780 1780 (
1781 1781 b'b',
1782 1782 b'branch',
1783 1783 [],
1784 1784 _(
1785 1785 b'do not clone everything, but include this branch\'s'
1786 1786 b' changesets and their ancestors'
1787 1787 ),
1788 1788 _(b'BRANCH'),
1789 1789 ),
1790 1790 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1791 1791 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1792 1792 (b'', b'stream', None, _(b'clone with minimal data processing')),
1793 1793 ]
1794 1794 + remoteopts,
1795 1795 _(b'[OPTION]... SOURCE [DEST]'),
1796 1796 helpcategory=command.CATEGORY_REPO_CREATION,
1797 1797 helpbasic=True,
1798 1798 norepo=True,
1799 1799 )
1800 1800 def clone(ui, source, dest=None, **opts):
1801 1801 """make a copy of an existing repository
1802 1802
1803 1803 Create a copy of an existing repository in a new directory.
1804 1804
1805 1805 If no destination directory name is specified, it defaults to the
1806 1806 basename of the source.
1807 1807
1808 1808 The location of the source is added to the new repository's
1809 1809 ``.hg/hgrc`` file, as the default to be used for future pulls.
1810 1810
1811 1811 Only local paths and ``ssh://`` URLs are supported as
1812 1812 destinations. For ``ssh://`` destinations, no working directory or
1813 1813 ``.hg/hgrc`` will be created on the remote side.
1814 1814
1815 1815 If the source repository has a bookmark called '@' set, that
1816 1816 revision will be checked out in the new repository by default.
1817 1817
1818 1818 To check out a particular version, use -u/--update, or
1819 1819 -U/--noupdate to create a clone with no working directory.
1820 1820
1821 1821 To pull only a subset of changesets, specify one or more revisions
1822 1822 identifiers with -r/--rev or branches with -b/--branch. The
1823 1823 resulting clone will contain only the specified changesets and
1824 1824 their ancestors. These options (or 'clone src#rev dest') imply
1825 1825 --pull, even for local source repositories.
1826 1826
1827 1827 In normal clone mode, the remote normalizes repository data into a common
1828 1828 exchange format and the receiving end translates this data into its local
1829 1829 storage format. --stream activates a different clone mode that essentially
1830 1830 copies repository files from the remote with minimal data processing. This
1831 1831 significantly reduces the CPU cost of a clone both remotely and locally.
1832 1832 However, it often increases the transferred data size by 30-40%. This can
1833 1833 result in substantially faster clones where I/O throughput is plentiful,
1834 1834 especially for larger repositories. A side-effect of --stream clones is
1835 1835 that storage settings and requirements on the remote are applied locally:
1836 1836 a modern client may inherit legacy or inefficient storage used by the
1837 1837 remote or a legacy Mercurial client may not be able to clone from a
1838 1838 modern Mercurial remote.
1839 1839
1840 1840 .. note::
1841 1841
1842 1842 Specifying a tag will include the tagged changeset but not the
1843 1843 changeset containing the tag.
1844 1844
1845 1845 .. container:: verbose
1846 1846
1847 1847 For efficiency, hardlinks are used for cloning whenever the
1848 1848 source and destination are on the same filesystem (note this
1849 1849 applies only to the repository data, not to the working
1850 1850 directory). Some filesystems, such as AFS, implement hardlinking
1851 1851 incorrectly, but do not report errors. In these cases, use the
1852 1852 --pull option to avoid hardlinking.
1853 1853
1854 1854 Mercurial will update the working directory to the first applicable
1855 1855 revision from this list:
1856 1856
1857 1857 a) null if -U or the source repository has no changesets
1858 1858 b) if -u . and the source repository is local, the first parent of
1859 1859 the source repository's working directory
1860 1860 c) the changeset specified with -u (if a branch name, this means the
1861 1861 latest head of that branch)
1862 1862 d) the changeset specified with -r
1863 1863 e) the tipmost head specified with -b
1864 1864 f) the tipmost head specified with the url#branch source syntax
1865 1865 g) the revision marked with the '@' bookmark, if present
1866 1866 h) the tipmost head of the default branch
1867 1867 i) tip
1868 1868
1869 1869 When cloning from servers that support it, Mercurial may fetch
1870 1870 pre-generated data from a server-advertised URL or inline from the
1871 1871 same stream. When this is done, hooks operating on incoming changesets
1872 1872 and changegroups may fire more than once, once for each pre-generated
1873 1873 bundle and as well as for any additional remaining data. In addition,
1874 1874 if an error occurs, the repository may be rolled back to a partial
1875 1875 clone. This behavior may change in future releases.
1876 1876 See :hg:`help -e clonebundles` for more.
1877 1877
1878 1878 Examples:
1879 1879
1880 1880 - clone a remote repository to a new directory named hg/::
1881 1881
1882 1882 hg clone https://www.mercurial-scm.org/repo/hg/
1883 1883
1884 1884 - create a lightweight local clone::
1885 1885
1886 1886 hg clone project/ project-feature/
1887 1887
1888 1888 - clone from an absolute path on an ssh server (note double-slash)::
1889 1889
1890 1890 hg clone ssh://user@server//home/projects/alpha/
1891 1891
1892 1892 - do a streaming clone while checking out a specified version::
1893 1893
1894 1894 hg clone --stream http://server/repo -u 1.5
1895 1895
1896 1896 - create a repository without changesets after a particular revision::
1897 1897
1898 1898 hg clone -r 04e544 experimental/ good/
1899 1899
1900 1900 - clone (and track) a particular named branch::
1901 1901
1902 1902 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1903 1903
1904 1904 See :hg:`help urls` for details on specifying URLs.
1905 1905
1906 1906 Returns 0 on success.
1907 1907 """
1908 1908 opts = pycompat.byteskwargs(opts)
1909 1909 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1910 1910
1911 1911 # --include/--exclude can come from narrow or sparse.
1912 1912 includepats, excludepats = None, None
1913 1913
1914 1914 # hg.clone() differentiates between None and an empty set. So make sure
1915 1915 # patterns are sets if narrow is requested without patterns.
1916 1916 if opts.get(b'narrow'):
1917 1917 includepats = set()
1918 1918 excludepats = set()
1919 1919
1920 1920 if opts.get(b'include'):
1921 1921 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1922 1922 if opts.get(b'exclude'):
1923 1923 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1924 1924
1925 1925 r = hg.clone(
1926 1926 ui,
1927 1927 opts,
1928 1928 source,
1929 1929 dest,
1930 1930 pull=opts.get(b'pull'),
1931 1931 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1932 1932 revs=opts.get(b'rev'),
1933 1933 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1934 1934 branch=opts.get(b'branch'),
1935 1935 shareopts=opts.get(b'shareopts'),
1936 1936 storeincludepats=includepats,
1937 1937 storeexcludepats=excludepats,
1938 1938 depth=opts.get(b'depth') or None,
1939 1939 )
1940 1940
1941 1941 return r is None
1942 1942
1943 1943
1944 1944 @command(
1945 1945 b'commit|ci',
1946 1946 [
1947 1947 (
1948 1948 b'A',
1949 1949 b'addremove',
1950 1950 None,
1951 1951 _(b'mark new/missing files as added/removed before committing'),
1952 1952 ),
1953 1953 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1954 1954 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1955 1955 (b's', b'secret', None, _(b'use the secret phase for committing')),
1956 1956 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1957 1957 (
1958 1958 b'',
1959 1959 b'force-close-branch',
1960 1960 None,
1961 1961 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1962 1962 ),
1963 1963 (b'i', b'interactive', None, _(b'use interactive mode')),
1964 1964 ]
1965 1965 + walkopts
1966 1966 + commitopts
1967 1967 + commitopts2
1968 1968 + subrepoopts,
1969 1969 _(b'[OPTION]... [FILE]...'),
1970 1970 helpcategory=command.CATEGORY_COMMITTING,
1971 1971 helpbasic=True,
1972 1972 inferrepo=True,
1973 1973 )
1974 1974 def commit(ui, repo, *pats, **opts):
1975 1975 """commit the specified files or all outstanding changes
1976 1976
1977 1977 Commit changes to the given files into the repository. Unlike a
1978 1978 centralized SCM, this operation is a local operation. See
1979 1979 :hg:`push` for a way to actively distribute your changes.
1980 1980
1981 1981 If a list of files is omitted, all changes reported by :hg:`status`
1982 1982 will be committed.
1983 1983
1984 1984 If you are committing the result of a merge, do not provide any
1985 1985 filenames or -I/-X filters.
1986 1986
1987 1987 If no commit message is specified, Mercurial starts your
1988 1988 configured editor where you can enter a message. In case your
1989 1989 commit fails, you will find a backup of your message in
1990 1990 ``.hg/last-message.txt``.
1991 1991
1992 1992 The --close-branch flag can be used to mark the current branch
1993 1993 head closed. When all heads of a branch are closed, the branch
1994 1994 will be considered closed and no longer listed.
1995 1995
1996 1996 The --amend flag can be used to amend the parent of the
1997 1997 working directory with a new commit that contains the changes
1998 1998 in the parent in addition to those currently reported by :hg:`status`,
1999 1999 if there are any. The old commit is stored in a backup bundle in
2000 2000 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2001 2001 on how to restore it).
2002 2002
2003 2003 Message, user and date are taken from the amended commit unless
2004 2004 specified. When a message isn't specified on the command line,
2005 2005 the editor will open with the message of the amended commit.
2006 2006
2007 2007 It is not possible to amend public changesets (see :hg:`help phases`)
2008 2008 or changesets that have children.
2009 2009
2010 2010 See :hg:`help dates` for a list of formats valid for -d/--date.
2011 2011
2012 2012 Returns 0 on success, 1 if nothing changed.
2013 2013
2014 2014 .. container:: verbose
2015 2015
2016 2016 Examples:
2017 2017
2018 2018 - commit all files ending in .py::
2019 2019
2020 2020 hg commit --include "set:**.py"
2021 2021
2022 2022 - commit all non-binary files::
2023 2023
2024 2024 hg commit --exclude "set:binary()"
2025 2025
2026 2026 - amend the current commit and set the date to now::
2027 2027
2028 2028 hg commit --amend --date now
2029 2029 """
2030 2030 with repo.wlock(), repo.lock():
2031 2031 return _docommit(ui, repo, *pats, **opts)
2032 2032
2033 2033
2034 2034 def _docommit(ui, repo, *pats, **opts):
2035 2035 if opts.get('interactive'):
2036 2036 opts.pop('interactive')
2037 2037 ret = cmdutil.dorecord(
2038 2038 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2039 2039 )
2040 2040 # ret can be 0 (no changes to record) or the value returned by
2041 2041 # commit(), 1 if nothing changed or None on success.
2042 2042 return 1 if ret == 0 else ret
2043 2043
2044 2044 opts = pycompat.byteskwargs(opts)
2045 2045 if opts.get(b'subrepos'):
2046 2046 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'amend'])
2047 2047 # Let --subrepos on the command line override config setting.
2048 2048 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2049 2049
2050 2050 cmdutil.checkunfinished(repo, commit=True)
2051 2051
2052 2052 branch = repo[None].branch()
2053 2053 bheads = repo.branchheads(branch)
2054 2054 tip = repo.changelog.tip()
2055 2055
2056 2056 extra = {}
2057 2057 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2058 2058 extra[b'close'] = b'1'
2059 2059
2060 2060 if repo[b'.'].closesbranch():
2061 2061 raise error.InputError(
2062 2062 _(b'current revision is already a branch closing head')
2063 2063 )
2064 2064 elif not bheads:
2065 2065 raise error.InputError(
2066 2066 _(b'branch "%s" has no heads to close') % branch
2067 2067 )
2068 2068 elif (
2069 2069 branch == repo[b'.'].branch()
2070 2070 and repo[b'.'].node() not in bheads
2071 2071 and not opts.get(b'force_close_branch')
2072 2072 ):
2073 2073 hint = _(
2074 2074 b'use --force-close-branch to close branch from a non-head'
2075 2075 b' changeset'
2076 2076 )
2077 2077 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2078 2078 elif opts.get(b'amend'):
2079 2079 if (
2080 2080 repo[b'.'].p1().branch() != branch
2081 2081 and repo[b'.'].p2().branch() != branch
2082 2082 ):
2083 2083 raise error.InputError(_(b'can only close branch heads'))
2084 2084
2085 2085 if opts.get(b'amend'):
2086 2086 if ui.configbool(b'ui', b'commitsubrepos'):
2087 2087 raise error.InputError(
2088 2088 _(b'cannot amend with ui.commitsubrepos enabled')
2089 2089 )
2090 2090
2091 2091 old = repo[b'.']
2092 2092 rewriteutil.precheck(repo, [old.rev()], b'amend')
2093 2093
2094 2094 # Currently histedit gets confused if an amend happens while histedit
2095 2095 # is in progress. Since we have a checkunfinished command, we are
2096 2096 # temporarily honoring it.
2097 2097 #
2098 2098 # Note: eventually this guard will be removed. Please do not expect
2099 2099 # this behavior to remain.
2100 2100 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2101 2101 cmdutil.checkunfinished(repo)
2102 2102
2103 2103 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2104 2104 if node == old.node():
2105 2105 ui.status(_(b"nothing changed\n"))
2106 2106 return 1
2107 2107 else:
2108 2108
2109 2109 def commitfunc(ui, repo, message, match, opts):
2110 2110 overrides = {}
2111 2111 if opts.get(b'secret'):
2112 2112 overrides[(b'phases', b'new-commit')] = b'secret'
2113 2113
2114 2114 baseui = repo.baseui
2115 2115 with baseui.configoverride(overrides, b'commit'):
2116 2116 with ui.configoverride(overrides, b'commit'):
2117 2117 editform = cmdutil.mergeeditform(
2118 2118 repo[None], b'commit.normal'
2119 2119 )
2120 2120 editor = cmdutil.getcommiteditor(
2121 2121 editform=editform, **pycompat.strkwargs(opts)
2122 2122 )
2123 2123 return repo.commit(
2124 2124 message,
2125 2125 opts.get(b'user'),
2126 2126 opts.get(b'date'),
2127 2127 match,
2128 2128 editor=editor,
2129 2129 extra=extra,
2130 2130 )
2131 2131
2132 2132 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2133 2133
2134 2134 if not node:
2135 2135 stat = cmdutil.postcommitstatus(repo, pats, opts)
2136 2136 if stat.deleted:
2137 2137 ui.status(
2138 2138 _(
2139 2139 b"nothing changed (%d missing files, see "
2140 2140 b"'hg status')\n"
2141 2141 )
2142 2142 % len(stat.deleted)
2143 2143 )
2144 2144 else:
2145 2145 ui.status(_(b"nothing changed\n"))
2146 2146 return 1
2147 2147
2148 2148 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2149 2149
2150 2150 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2151 2151 status(
2152 2152 ui,
2153 2153 repo,
2154 2154 modified=True,
2155 2155 added=True,
2156 2156 removed=True,
2157 2157 deleted=True,
2158 2158 unknown=True,
2159 2159 subrepos=opts.get(b'subrepos'),
2160 2160 )
2161 2161
2162 2162
2163 2163 @command(
2164 2164 b'config|showconfig|debugconfig',
2165 2165 [
2166 2166 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2167 2167 (b'e', b'edit', None, _(b'edit user config')),
2168 2168 (b'l', b'local', None, _(b'edit repository config')),
2169 2169 (
2170 2170 b'',
2171 2171 b'shared',
2172 2172 None,
2173 2173 _(b'edit shared source repository config (EXPERIMENTAL)'),
2174 2174 ),
2175 2175 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2176 2176 (b'g', b'global', None, _(b'edit global config')),
2177 2177 ]
2178 2178 + formatteropts,
2179 2179 _(b'[-u] [NAME]...'),
2180 2180 helpcategory=command.CATEGORY_HELP,
2181 2181 optionalrepo=True,
2182 2182 intents={INTENT_READONLY},
2183 2183 )
2184 2184 def config(ui, repo, *values, **opts):
2185 2185 """show combined config settings from all hgrc files
2186 2186
2187 2187 With no arguments, print names and values of all config items.
2188 2188
2189 2189 With one argument of the form section.name, print just the value
2190 2190 of that config item.
2191 2191
2192 2192 With multiple arguments, print names and values of all config
2193 2193 items with matching section names or section.names.
2194 2194
2195 2195 With --edit, start an editor on the user-level config file. With
2196 2196 --global, edit the system-wide config file. With --local, edit the
2197 2197 repository-level config file.
2198 2198
2199 2199 With --debug, the source (filename and line number) is printed
2200 2200 for each config item.
2201 2201
2202 2202 See :hg:`help config` for more information about config files.
2203 2203
2204 2204 .. container:: verbose
2205 2205
2206 2206 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2207 2207 This file is not shared across shares when in share-safe mode.
2208 2208
2209 2209 Template:
2210 2210
2211 2211 The following keywords are supported. See also :hg:`help templates`.
2212 2212
2213 2213 :name: String. Config name.
2214 2214 :source: String. Filename and line number where the item is defined.
2215 2215 :value: String. Config value.
2216 2216
2217 2217 The --shared flag can be used to edit the config file of shared source
2218 2218 repository. It only works when you have shared using the experimental
2219 2219 share safe feature.
2220 2220
2221 2221 Returns 0 on success, 1 if NAME does not exist.
2222 2222
2223 2223 """
2224 2224
2225 2225 opts = pycompat.byteskwargs(opts)
2226 2226 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2227 2227 if any(opts.get(o) for o in editopts):
2228 2228 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2229 2229 if opts.get(b'local'):
2230 2230 if not repo:
2231 2231 raise error.InputError(
2232 2232 _(b"can't use --local outside a repository")
2233 2233 )
2234 2234 paths = [repo.vfs.join(b'hgrc')]
2235 2235 elif opts.get(b'global'):
2236 2236 paths = rcutil.systemrcpath()
2237 2237 elif opts.get(b'shared'):
2238 2238 if not repo.shared():
2239 2239 raise error.InputError(
2240 2240 _(b"repository is not shared; can't use --shared")
2241 2241 )
2242 2242 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2243 2243 raise error.InputError(
2244 2244 _(
2245 2245 b"share safe feature not enabled; "
2246 2246 b"unable to edit shared source repository config"
2247 2247 )
2248 2248 )
2249 2249 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2250 2250 elif opts.get(b'non_shared'):
2251 2251 paths = [repo.vfs.join(b'hgrc-not-shared')]
2252 2252 else:
2253 2253 paths = rcutil.userrcpath()
2254 2254
2255 2255 for f in paths:
2256 2256 if os.path.exists(f):
2257 2257 break
2258 2258 else:
2259 2259 if opts.get(b'global'):
2260 2260 samplehgrc = uimod.samplehgrcs[b'global']
2261 2261 elif opts.get(b'local'):
2262 2262 samplehgrc = uimod.samplehgrcs[b'local']
2263 2263 else:
2264 2264 samplehgrc = uimod.samplehgrcs[b'user']
2265 2265
2266 2266 f = paths[0]
2267 2267 fp = open(f, b"wb")
2268 2268 fp.write(util.tonativeeol(samplehgrc))
2269 2269 fp.close()
2270 2270
2271 2271 editor = ui.geteditor()
2272 2272 ui.system(
2273 2273 b"%s \"%s\"" % (editor, f),
2274 2274 onerr=error.InputError,
2275 2275 errprefix=_(b"edit failed"),
2276 2276 blockedtag=b'config_edit',
2277 2277 )
2278 2278 return
2279 2279 ui.pager(b'config')
2280 2280 fm = ui.formatter(b'config', opts)
2281 2281 for t, f in rcutil.rccomponents():
2282 2282 if t == b'path':
2283 2283 ui.debug(b'read config from: %s\n' % f)
2284 2284 elif t == b'resource':
2285 2285 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2286 2286 elif t == b'items':
2287 2287 # Don't print anything for 'items'.
2288 2288 pass
2289 2289 else:
2290 2290 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2291 2291 untrusted = bool(opts.get(b'untrusted'))
2292 2292
2293 2293 selsections = selentries = []
2294 2294 if values:
2295 2295 selsections = [v for v in values if b'.' not in v]
2296 2296 selentries = [v for v in values if b'.' in v]
2297 2297 uniquesel = len(selentries) == 1 and not selsections
2298 2298 selsections = set(selsections)
2299 2299 selentries = set(selentries)
2300 2300
2301 2301 matched = False
2302 2302 for section, name, value in ui.walkconfig(untrusted=untrusted):
2303 2303 source = ui.configsource(section, name, untrusted)
2304 2304 value = pycompat.bytestr(value)
2305 2305 defaultvalue = ui.configdefault(section, name)
2306 2306 if fm.isplain():
2307 2307 source = source or b'none'
2308 2308 value = value.replace(b'\n', b'\\n')
2309 2309 entryname = section + b'.' + name
2310 2310 if values and not (section in selsections or entryname in selentries):
2311 2311 continue
2312 2312 fm.startitem()
2313 2313 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2314 2314 if uniquesel:
2315 2315 fm.data(name=entryname)
2316 2316 fm.write(b'value', b'%s\n', value)
2317 2317 else:
2318 2318 fm.write(b'name value', b'%s=%s\n', entryname, value)
2319 2319 if formatter.isprintable(defaultvalue):
2320 2320 fm.data(defaultvalue=defaultvalue)
2321 2321 elif isinstance(defaultvalue, list) and all(
2322 2322 formatter.isprintable(e) for e in defaultvalue
2323 2323 ):
2324 2324 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2325 2325 # TODO: no idea how to process unsupported defaultvalue types
2326 2326 matched = True
2327 2327 fm.end()
2328 2328 if matched:
2329 2329 return 0
2330 2330 return 1
2331 2331
2332 2332
2333 2333 @command(
2334 2334 b'continue',
2335 2335 dryrunopts,
2336 2336 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2337 2337 helpbasic=True,
2338 2338 )
2339 2339 def continuecmd(ui, repo, **opts):
2340 2340 """resumes an interrupted operation (EXPERIMENTAL)
2341 2341
2342 2342 Finishes a multistep operation like graft, histedit, rebase, merge,
2343 2343 and unshelve if they are in an interrupted state.
2344 2344
2345 2345 use --dry-run/-n to dry run the command.
2346 2346 """
2347 2347 dryrun = opts.get('dry_run')
2348 2348 contstate = cmdutil.getunfinishedstate(repo)
2349 2349 if not contstate:
2350 2350 raise error.StateError(_(b'no operation in progress'))
2351 2351 if not contstate.continuefunc:
2352 2352 raise error.StateError(
2353 2353 (
2354 2354 _(b"%s in progress but does not support 'hg continue'")
2355 2355 % (contstate._opname)
2356 2356 ),
2357 2357 hint=contstate.continuemsg(),
2358 2358 )
2359 2359 if dryrun:
2360 2360 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2361 2361 return
2362 2362 return contstate.continuefunc(ui, repo)
2363 2363
2364 2364
2365 2365 @command(
2366 2366 b'copy|cp',
2367 2367 [
2368 2368 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2369 2369 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2370 2370 (
2371 2371 b'',
2372 2372 b'at-rev',
2373 2373 b'',
2374 2374 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2375 2375 _(b'REV'),
2376 2376 ),
2377 2377 (
2378 2378 b'f',
2379 2379 b'force',
2380 2380 None,
2381 2381 _(b'forcibly copy over an existing managed file'),
2382 2382 ),
2383 2383 ]
2384 2384 + walkopts
2385 2385 + dryrunopts,
2386 2386 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2387 2387 helpcategory=command.CATEGORY_FILE_CONTENTS,
2388 2388 )
2389 2389 def copy(ui, repo, *pats, **opts):
2390 2390 """mark files as copied for the next commit
2391 2391
2392 2392 Mark dest as having copies of source files. If dest is a
2393 2393 directory, copies are put in that directory. If dest is a file,
2394 2394 the source must be a single file.
2395 2395
2396 2396 By default, this command copies the contents of files as they
2397 2397 exist in the working directory. If invoked with -A/--after, the
2398 2398 operation is recorded, but no copying is performed.
2399 2399
2400 2400 To undo marking a destination file as copied, use --forget. With that
2401 2401 option, all given (positional) arguments are unmarked as copies. The
2402 2402 destination file(s) will be left in place (still tracked).
2403 2403
2404 2404 This command takes effect with the next commit by default.
2405 2405
2406 2406 Returns 0 on success, 1 if errors are encountered.
2407 2407 """
2408 2408 opts = pycompat.byteskwargs(opts)
2409 2409 with repo.wlock():
2410 2410 return cmdutil.copy(ui, repo, pats, opts)
2411 2411
2412 2412
2413 2413 @command(
2414 2414 b'debugcommands',
2415 2415 [],
2416 2416 _(b'[COMMAND]'),
2417 2417 helpcategory=command.CATEGORY_HELP,
2418 2418 norepo=True,
2419 2419 )
2420 2420 def debugcommands(ui, cmd=b'', *args):
2421 2421 """list all available commands and options"""
2422 2422 for cmd, vals in sorted(pycompat.iteritems(table)):
2423 2423 cmd = cmd.split(b'|')[0]
2424 2424 opts = b', '.join([i[1] for i in vals[1]])
2425 2425 ui.write(b'%s: %s\n' % (cmd, opts))
2426 2426
2427 2427
2428 2428 @command(
2429 2429 b'debugcomplete',
2430 2430 [(b'o', b'options', None, _(b'show the command options'))],
2431 2431 _(b'[-o] CMD'),
2432 2432 helpcategory=command.CATEGORY_HELP,
2433 2433 norepo=True,
2434 2434 )
2435 2435 def debugcomplete(ui, cmd=b'', **opts):
2436 2436 """returns the completion list associated with the given command"""
2437 2437
2438 2438 if opts.get('options'):
2439 2439 options = []
2440 2440 otables = [globalopts]
2441 2441 if cmd:
2442 2442 aliases, entry = cmdutil.findcmd(cmd, table, False)
2443 2443 otables.append(entry[1])
2444 2444 for t in otables:
2445 2445 for o in t:
2446 2446 if b"(DEPRECATED)" in o[3]:
2447 2447 continue
2448 2448 if o[0]:
2449 2449 options.append(b'-%s' % o[0])
2450 2450 options.append(b'--%s' % o[1])
2451 2451 ui.write(b"%s\n" % b"\n".join(options))
2452 2452 return
2453 2453
2454 2454 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2455 2455 if ui.verbose:
2456 2456 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2457 2457 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2458 2458
2459 2459
2460 2460 @command(
2461 2461 b'diff',
2462 2462 [
2463 2463 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2464 2464 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2465 2465 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2466 2466 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2467 2467 ]
2468 2468 + diffopts
2469 2469 + diffopts2
2470 2470 + walkopts
2471 2471 + subrepoopts,
2472 2472 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2473 2473 helpcategory=command.CATEGORY_FILE_CONTENTS,
2474 2474 helpbasic=True,
2475 2475 inferrepo=True,
2476 2476 intents={INTENT_READONLY},
2477 2477 )
2478 2478 def diff(ui, repo, *pats, **opts):
2479 2479 """diff repository (or selected files)
2480 2480
2481 2481 Show differences between revisions for the specified files.
2482 2482
2483 2483 Differences between files are shown using the unified diff format.
2484 2484
2485 2485 .. note::
2486 2486
2487 2487 :hg:`diff` may generate unexpected results for merges, as it will
2488 2488 default to comparing against the working directory's first
2489 2489 parent changeset if no revisions are specified.
2490 2490
2491 2491 By default, the working directory files are compared to its first parent. To
2492 2492 see the differences from another revision, use --from. To see the difference
2493 2493 to another revision, use --to. For example, :hg:`diff --from .^` will show
2494 2494 the differences from the working copy's grandparent to the working copy,
2495 2495 :hg:`diff --to .` will show the diff from the working copy to its parent
2496 2496 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2497 2497 show the diff between those two revisions.
2498 2498
2499 2499 Alternatively you can specify -c/--change with a revision to see the changes
2500 2500 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2501 2501 equivalent to :hg:`diff --from 42^ --to 42`)
2502 2502
2503 2503 Without the -a/--text option, diff will avoid generating diffs of
2504 2504 files it detects as binary. With -a, diff will generate a diff
2505 2505 anyway, probably with undesirable results.
2506 2506
2507 2507 Use the -g/--git option to generate diffs in the git extended diff
2508 2508 format. For more information, read :hg:`help diffs`.
2509 2509
2510 2510 .. container:: verbose
2511 2511
2512 2512 Examples:
2513 2513
2514 2514 - compare a file in the current working directory to its parent::
2515 2515
2516 2516 hg diff foo.c
2517 2517
2518 2518 - compare two historical versions of a directory, with rename info::
2519 2519
2520 2520 hg diff --git --from 1.0 --to 1.2 lib/
2521 2521
2522 2522 - get change stats relative to the last change on some date::
2523 2523
2524 2524 hg diff --stat --from "date('may 2')"
2525 2525
2526 2526 - diff all newly-added files that contain a keyword::
2527 2527
2528 2528 hg diff "set:added() and grep(GNU)"
2529 2529
2530 2530 - compare a revision and its parents::
2531 2531
2532 2532 hg diff -c 9353 # compare against first parent
2533 2533 hg diff --from 9353^ --to 9353 # same using revset syntax
2534 2534 hg diff --from 9353^2 --to 9353 # compare against the second parent
2535 2535
2536 2536 Returns 0 on success.
2537 2537 """
2538 2538
2539 2539 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2540 2540 opts = pycompat.byteskwargs(opts)
2541 2541 revs = opts.get(b'rev')
2542 2542 change = opts.get(b'change')
2543 2543 from_rev = opts.get(b'from')
2544 2544 to_rev = opts.get(b'to')
2545 2545 stat = opts.get(b'stat')
2546 2546 reverse = opts.get(b'reverse')
2547 2547
2548 2548 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2549 2549 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2550 2550 if change:
2551 2551 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2552 2552 ctx2 = scmutil.revsingle(repo, change, None)
2553 2553 ctx1 = logcmdutil.diff_parent(ctx2)
2554 2554 elif from_rev or to_rev:
2555 2555 repo = scmutil.unhidehashlikerevs(
2556 2556 repo, [from_rev] + [to_rev], b'nowarn'
2557 2557 )
2558 2558 ctx1 = scmutil.revsingle(repo, from_rev, None)
2559 2559 ctx2 = scmutil.revsingle(repo, to_rev, None)
2560 2560 else:
2561 2561 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2562 2562 ctx1, ctx2 = scmutil.revpair(repo, revs)
2563 2563
2564 2564 if reverse:
2565 2565 ctxleft = ctx2
2566 2566 ctxright = ctx1
2567 2567 else:
2568 2568 ctxleft = ctx1
2569 2569 ctxright = ctx2
2570 2570
2571 2571 diffopts = patch.diffallopts(ui, opts)
2572 2572 m = scmutil.match(ctx2, pats, opts)
2573 2573 m = repo.narrowmatch(m)
2574 2574 ui.pager(b'diff')
2575 2575 logcmdutil.diffordiffstat(
2576 2576 ui,
2577 2577 repo,
2578 2578 diffopts,
2579 2579 ctxleft,
2580 2580 ctxright,
2581 2581 m,
2582 2582 stat=stat,
2583 2583 listsubrepos=opts.get(b'subrepos'),
2584 2584 root=opts.get(b'root'),
2585 2585 )
2586 2586
2587 2587
2588 2588 @command(
2589 2589 b'export',
2590 2590 [
2591 2591 (
2592 2592 b'B',
2593 2593 b'bookmark',
2594 2594 b'',
2595 2595 _(b'export changes only reachable by given bookmark'),
2596 2596 _(b'BOOKMARK'),
2597 2597 ),
2598 2598 (
2599 2599 b'o',
2600 2600 b'output',
2601 2601 b'',
2602 2602 _(b'print output to file with formatted name'),
2603 2603 _(b'FORMAT'),
2604 2604 ),
2605 2605 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2606 2606 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2607 2607 ]
2608 2608 + diffopts
2609 2609 + formatteropts,
2610 2610 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2611 2611 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2612 2612 helpbasic=True,
2613 2613 intents={INTENT_READONLY},
2614 2614 )
2615 2615 def export(ui, repo, *changesets, **opts):
2616 2616 """dump the header and diffs for one or more changesets
2617 2617
2618 2618 Print the changeset header and diffs for one or more revisions.
2619 2619 If no revision is given, the parent of the working directory is used.
2620 2620
2621 2621 The information shown in the changeset header is: author, date,
2622 2622 branch name (if non-default), changeset hash, parent(s) and commit
2623 2623 comment.
2624 2624
2625 2625 .. note::
2626 2626
2627 2627 :hg:`export` may generate unexpected diff output for merge
2628 2628 changesets, as it will compare the merge changeset against its
2629 2629 first parent only.
2630 2630
2631 2631 Output may be to a file, in which case the name of the file is
2632 2632 given using a template string. See :hg:`help templates`. In addition
2633 2633 to the common template keywords, the following formatting rules are
2634 2634 supported:
2635 2635
2636 2636 :``%%``: literal "%" character
2637 2637 :``%H``: changeset hash (40 hexadecimal digits)
2638 2638 :``%N``: number of patches being generated
2639 2639 :``%R``: changeset revision number
2640 2640 :``%b``: basename of the exporting repository
2641 2641 :``%h``: short-form changeset hash (12 hexadecimal digits)
2642 2642 :``%m``: first line of the commit message (only alphanumeric characters)
2643 2643 :``%n``: zero-padded sequence number, starting at 1
2644 2644 :``%r``: zero-padded changeset revision number
2645 2645 :``\\``: literal "\\" character
2646 2646
2647 2647 Without the -a/--text option, export will avoid generating diffs
2648 2648 of files it detects as binary. With -a, export will generate a
2649 2649 diff anyway, probably with undesirable results.
2650 2650
2651 2651 With -B/--bookmark changesets reachable by the given bookmark are
2652 2652 selected.
2653 2653
2654 2654 Use the -g/--git option to generate diffs in the git extended diff
2655 2655 format. See :hg:`help diffs` for more information.
2656 2656
2657 2657 With the --switch-parent option, the diff will be against the
2658 2658 second parent. It can be useful to review a merge.
2659 2659
2660 2660 .. container:: verbose
2661 2661
2662 2662 Template:
2663 2663
2664 2664 The following keywords are supported in addition to the common template
2665 2665 keywords and functions. See also :hg:`help templates`.
2666 2666
2667 2667 :diff: String. Diff content.
2668 2668 :parents: List of strings. Parent nodes of the changeset.
2669 2669
2670 2670 Examples:
2671 2671
2672 2672 - use export and import to transplant a bugfix to the current
2673 2673 branch::
2674 2674
2675 2675 hg export -r 9353 | hg import -
2676 2676
2677 2677 - export all the changesets between two revisions to a file with
2678 2678 rename information::
2679 2679
2680 2680 hg export --git -r 123:150 > changes.txt
2681 2681
2682 2682 - split outgoing changes into a series of patches with
2683 2683 descriptive names::
2684 2684
2685 2685 hg export -r "outgoing()" -o "%n-%m.patch"
2686 2686
2687 2687 Returns 0 on success.
2688 2688 """
2689 2689 opts = pycompat.byteskwargs(opts)
2690 2690 bookmark = opts.get(b'bookmark')
2691 2691 changesets += tuple(opts.get(b'rev', []))
2692 2692
2693 2693 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2694 2694
2695 2695 if bookmark:
2696 2696 if bookmark not in repo._bookmarks:
2697 2697 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2698 2698
2699 2699 revs = scmutil.bookmarkrevs(repo, bookmark)
2700 2700 else:
2701 2701 if not changesets:
2702 2702 changesets = [b'.']
2703 2703
2704 2704 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2705 2705 revs = scmutil.revrange(repo, changesets)
2706 2706
2707 2707 if not revs:
2708 2708 raise error.InputError(_(b"export requires at least one changeset"))
2709 2709 if len(revs) > 1:
2710 2710 ui.note(_(b'exporting patches:\n'))
2711 2711 else:
2712 2712 ui.note(_(b'exporting patch:\n'))
2713 2713
2714 2714 fntemplate = opts.get(b'output')
2715 2715 if cmdutil.isstdiofilename(fntemplate):
2716 2716 fntemplate = b''
2717 2717
2718 2718 if fntemplate:
2719 2719 fm = formatter.nullformatter(ui, b'export', opts)
2720 2720 else:
2721 2721 ui.pager(b'export')
2722 2722 fm = ui.formatter(b'export', opts)
2723 2723 with fm:
2724 2724 cmdutil.export(
2725 2725 repo,
2726 2726 revs,
2727 2727 fm,
2728 2728 fntemplate=fntemplate,
2729 2729 switch_parent=opts.get(b'switch_parent'),
2730 2730 opts=patch.diffallopts(ui, opts),
2731 2731 )
2732 2732
2733 2733
2734 2734 @command(
2735 2735 b'files',
2736 2736 [
2737 2737 (
2738 2738 b'r',
2739 2739 b'rev',
2740 2740 b'',
2741 2741 _(b'search the repository as it is in REV'),
2742 2742 _(b'REV'),
2743 2743 ),
2744 2744 (
2745 2745 b'0',
2746 2746 b'print0',
2747 2747 None,
2748 2748 _(b'end filenames with NUL, for use with xargs'),
2749 2749 ),
2750 2750 ]
2751 2751 + walkopts
2752 2752 + formatteropts
2753 2753 + subrepoopts,
2754 2754 _(b'[OPTION]... [FILE]...'),
2755 2755 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2756 2756 intents={INTENT_READONLY},
2757 2757 )
2758 2758 def files(ui, repo, *pats, **opts):
2759 2759 """list tracked files
2760 2760
2761 2761 Print files under Mercurial control in the working directory or
2762 2762 specified revision for given files (excluding removed files).
2763 2763 Files can be specified as filenames or filesets.
2764 2764
2765 2765 If no files are given to match, this command prints the names
2766 2766 of all files under Mercurial control.
2767 2767
2768 2768 .. container:: verbose
2769 2769
2770 2770 Template:
2771 2771
2772 2772 The following keywords are supported in addition to the common template
2773 2773 keywords and functions. See also :hg:`help templates`.
2774 2774
2775 2775 :flags: String. Character denoting file's symlink and executable bits.
2776 2776 :path: String. Repository-absolute path of the file.
2777 2777 :size: Integer. Size of the file in bytes.
2778 2778
2779 2779 Examples:
2780 2780
2781 2781 - list all files under the current directory::
2782 2782
2783 2783 hg files .
2784 2784
2785 2785 - shows sizes and flags for current revision::
2786 2786
2787 2787 hg files -vr .
2788 2788
2789 2789 - list all files named README::
2790 2790
2791 2791 hg files -I "**/README"
2792 2792
2793 2793 - list all binary files::
2794 2794
2795 2795 hg files "set:binary()"
2796 2796
2797 2797 - find files containing a regular expression::
2798 2798
2799 2799 hg files "set:grep('bob')"
2800 2800
2801 2801 - search tracked file contents with xargs and grep::
2802 2802
2803 2803 hg files -0 | xargs -0 grep foo
2804 2804
2805 2805 See :hg:`help patterns` and :hg:`help filesets` for more information
2806 2806 on specifying file patterns.
2807 2807
2808 2808 Returns 0 if a match is found, 1 otherwise.
2809 2809
2810 2810 """
2811 2811
2812 2812 opts = pycompat.byteskwargs(opts)
2813 2813 rev = opts.get(b'rev')
2814 2814 if rev:
2815 2815 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2816 2816 ctx = scmutil.revsingle(repo, rev, None)
2817 2817
2818 2818 end = b'\n'
2819 2819 if opts.get(b'print0'):
2820 2820 end = b'\0'
2821 2821 fmt = b'%s' + end
2822 2822
2823 2823 m = scmutil.match(ctx, pats, opts)
2824 2824 ui.pager(b'files')
2825 2825 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2826 2826 with ui.formatter(b'files', opts) as fm:
2827 2827 return cmdutil.files(
2828 2828 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2829 2829 )
2830 2830
2831 2831
2832 2832 @command(
2833 2833 b'forget',
2834 2834 [
2835 2835 (b'i', b'interactive', None, _(b'use interactive mode')),
2836 2836 ]
2837 2837 + walkopts
2838 2838 + dryrunopts,
2839 2839 _(b'[OPTION]... FILE...'),
2840 2840 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2841 2841 helpbasic=True,
2842 2842 inferrepo=True,
2843 2843 )
2844 2844 def forget(ui, repo, *pats, **opts):
2845 2845 """forget the specified files on the next commit
2846 2846
2847 2847 Mark the specified files so they will no longer be tracked
2848 2848 after the next commit.
2849 2849
2850 2850 This only removes files from the current branch, not from the
2851 2851 entire project history, and it does not delete them from the
2852 2852 working directory.
2853 2853
2854 2854 To delete the file from the working directory, see :hg:`remove`.
2855 2855
2856 2856 To undo a forget before the next commit, see :hg:`add`.
2857 2857
2858 2858 .. container:: verbose
2859 2859
2860 2860 Examples:
2861 2861
2862 2862 - forget newly-added binary files::
2863 2863
2864 2864 hg forget "set:added() and binary()"
2865 2865
2866 2866 - forget files that would be excluded by .hgignore::
2867 2867
2868 2868 hg forget "set:hgignore()"
2869 2869
2870 2870 Returns 0 on success.
2871 2871 """
2872 2872
2873 2873 opts = pycompat.byteskwargs(opts)
2874 2874 if not pats:
2875 2875 raise error.InputError(_(b'no files specified'))
2876 2876
2877 2877 m = scmutil.match(repo[None], pats, opts)
2878 2878 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2879 2879 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2880 2880 rejected = cmdutil.forget(
2881 2881 ui,
2882 2882 repo,
2883 2883 m,
2884 2884 prefix=b"",
2885 2885 uipathfn=uipathfn,
2886 2886 explicitonly=False,
2887 2887 dryrun=dryrun,
2888 2888 interactive=interactive,
2889 2889 )[0]
2890 2890 return rejected and 1 or 0
2891 2891
2892 2892
2893 2893 @command(
2894 2894 b'graft',
2895 2895 [
2896 2896 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2897 2897 (
2898 2898 b'',
2899 2899 b'base',
2900 2900 b'',
2901 2901 _(b'base revision when doing the graft merge (ADVANCED)'),
2902 2902 _(b'REV'),
2903 2903 ),
2904 2904 (b'c', b'continue', False, _(b'resume interrupted graft')),
2905 2905 (b'', b'stop', False, _(b'stop interrupted graft')),
2906 2906 (b'', b'abort', False, _(b'abort interrupted graft')),
2907 2907 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2908 2908 (b'', b'log', None, _(b'append graft info to log message')),
2909 2909 (
2910 2910 b'',
2911 2911 b'no-commit',
2912 2912 None,
2913 2913 _(b"don't commit, just apply the changes in working directory"),
2914 2914 ),
2915 2915 (b'f', b'force', False, _(b'force graft')),
2916 2916 (
2917 2917 b'D',
2918 2918 b'currentdate',
2919 2919 False,
2920 2920 _(b'record the current date as commit date'),
2921 2921 ),
2922 2922 (
2923 2923 b'U',
2924 2924 b'currentuser',
2925 2925 False,
2926 2926 _(b'record the current user as committer'),
2927 2927 ),
2928 2928 ]
2929 2929 + commitopts2
2930 2930 + mergetoolopts
2931 2931 + dryrunopts,
2932 2932 _(b'[OPTION]... [-r REV]... REV...'),
2933 2933 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2934 2934 )
2935 2935 def graft(ui, repo, *revs, **opts):
2936 2936 """copy changes from other branches onto the current branch
2937 2937
2938 2938 This command uses Mercurial's merge logic to copy individual
2939 2939 changes from other branches without merging branches in the
2940 2940 history graph. This is sometimes known as 'backporting' or
2941 2941 'cherry-picking'. By default, graft will copy user, date, and
2942 2942 description from the source changesets.
2943 2943
2944 2944 Changesets that are ancestors of the current revision, that have
2945 2945 already been grafted, or that are merges will be skipped.
2946 2946
2947 2947 If --log is specified, log messages will have a comment appended
2948 2948 of the form::
2949 2949
2950 2950 (grafted from CHANGESETHASH)
2951 2951
2952 2952 If --force is specified, revisions will be grafted even if they
2953 2953 are already ancestors of, or have been grafted to, the destination.
2954 2954 This is useful when the revisions have since been backed out.
2955 2955
2956 2956 If a graft merge results in conflicts, the graft process is
2957 2957 interrupted so that the current merge can be manually resolved.
2958 2958 Once all conflicts are addressed, the graft process can be
2959 2959 continued with the -c/--continue option.
2960 2960
2961 2961 The -c/--continue option reapplies all the earlier options.
2962 2962
2963 2963 .. container:: verbose
2964 2964
2965 2965 The --base option exposes more of how graft internally uses merge with a
2966 2966 custom base revision. --base can be used to specify another ancestor than
2967 2967 the first and only parent.
2968 2968
2969 2969 The command::
2970 2970
2971 2971 hg graft -r 345 --base 234
2972 2972
2973 2973 is thus pretty much the same as::
2974 2974
2975 2975 hg diff --from 234 --to 345 | hg import
2976 2976
2977 2977 but using merge to resolve conflicts and track moved files.
2978 2978
2979 2979 The result of a merge can thus be backported as a single commit by
2980 2980 specifying one of the merge parents as base, and thus effectively
2981 2981 grafting the changes from the other side.
2982 2982
2983 2983 It is also possible to collapse multiple changesets and clean up history
2984 2984 by specifying another ancestor as base, much like rebase --collapse
2985 2985 --keep.
2986 2986
2987 2987 The commit message can be tweaked after the fact using commit --amend .
2988 2988
2989 2989 For using non-ancestors as the base to backout changes, see the backout
2990 2990 command and the hidden --parent option.
2991 2991
2992 2992 .. container:: verbose
2993 2993
2994 2994 Examples:
2995 2995
2996 2996 - copy a single change to the stable branch and edit its description::
2997 2997
2998 2998 hg update stable
2999 2999 hg graft --edit 9393
3000 3000
3001 3001 - graft a range of changesets with one exception, updating dates::
3002 3002
3003 3003 hg graft -D "2085::2093 and not 2091"
3004 3004
3005 3005 - continue a graft after resolving conflicts::
3006 3006
3007 3007 hg graft -c
3008 3008
3009 3009 - show the source of a grafted changeset::
3010 3010
3011 3011 hg log --debug -r .
3012 3012
3013 3013 - show revisions sorted by date::
3014 3014
3015 3015 hg log -r "sort(all(), date)"
3016 3016
3017 3017 - backport the result of a merge as a single commit::
3018 3018
3019 3019 hg graft -r 123 --base 123^
3020 3020
3021 3021 - land a feature branch as one changeset::
3022 3022
3023 3023 hg up -cr default
3024 3024 hg graft -r featureX --base "ancestor('featureX', 'default')"
3025 3025
3026 3026 See :hg:`help revisions` for more about specifying revisions.
3027 3027
3028 3028 Returns 0 on successful completion, 1 if there are unresolved files.
3029 3029 """
3030 3030 with repo.wlock():
3031 3031 return _dograft(ui, repo, *revs, **opts)
3032 3032
3033 3033
3034 3034 def _dograft(ui, repo, *revs, **opts):
3035 3035 opts = pycompat.byteskwargs(opts)
3036 3036 if revs and opts.get(b'rev'):
3037 3037 ui.warn(
3038 3038 _(
3039 3039 b'warning: inconsistent use of --rev might give unexpected '
3040 3040 b'revision ordering!\n'
3041 3041 )
3042 3042 )
3043 3043
3044 3044 revs = list(revs)
3045 3045 revs.extend(opts.get(b'rev'))
3046 3046 # a dict of data to be stored in state file
3047 3047 statedata = {}
3048 3048 # list of new nodes created by ongoing graft
3049 3049 statedata[b'newnodes'] = []
3050 3050
3051 3051 cmdutil.resolvecommitoptions(ui, opts)
3052 3052
3053 3053 editor = cmdutil.getcommiteditor(
3054 3054 editform=b'graft', **pycompat.strkwargs(opts)
3055 3055 )
3056 3056
3057 3057 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3058 3058
3059 3059 cont = False
3060 3060 if opts.get(b'no_commit'):
3061 3061 cmdutil.check_incompatible_arguments(
3062 3062 opts,
3063 3063 b'no_commit',
3064 3064 [b'edit', b'currentuser', b'currentdate', b'log'],
3065 3065 )
3066 3066
3067 3067 graftstate = statemod.cmdstate(repo, b'graftstate')
3068 3068
3069 3069 if opts.get(b'stop'):
3070 3070 cmdutil.check_incompatible_arguments(
3071 3071 opts,
3072 3072 b'stop',
3073 3073 [
3074 3074 b'edit',
3075 3075 b'log',
3076 3076 b'user',
3077 3077 b'date',
3078 3078 b'currentdate',
3079 3079 b'currentuser',
3080 3080 b'rev',
3081 3081 ],
3082 3082 )
3083 3083 return _stopgraft(ui, repo, graftstate)
3084 3084 elif opts.get(b'abort'):
3085 3085 cmdutil.check_incompatible_arguments(
3086 3086 opts,
3087 3087 b'abort',
3088 3088 [
3089 3089 b'edit',
3090 3090 b'log',
3091 3091 b'user',
3092 3092 b'date',
3093 3093 b'currentdate',
3094 3094 b'currentuser',
3095 3095 b'rev',
3096 3096 ],
3097 3097 )
3098 3098 return cmdutil.abortgraft(ui, repo, graftstate)
3099 3099 elif opts.get(b'continue'):
3100 3100 cont = True
3101 3101 if revs:
3102 3102 raise error.InputError(_(b"can't specify --continue and revisions"))
3103 3103 # read in unfinished revisions
3104 3104 if graftstate.exists():
3105 3105 statedata = cmdutil.readgraftstate(repo, graftstate)
3106 3106 if statedata.get(b'date'):
3107 3107 opts[b'date'] = statedata[b'date']
3108 3108 if statedata.get(b'user'):
3109 3109 opts[b'user'] = statedata[b'user']
3110 3110 if statedata.get(b'log'):
3111 3111 opts[b'log'] = True
3112 3112 if statedata.get(b'no_commit'):
3113 3113 opts[b'no_commit'] = statedata.get(b'no_commit')
3114 3114 if statedata.get(b'base'):
3115 3115 opts[b'base'] = statedata.get(b'base')
3116 3116 nodes = statedata[b'nodes']
3117 3117 revs = [repo[node].rev() for node in nodes]
3118 3118 else:
3119 3119 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3120 3120 else:
3121 3121 if not revs:
3122 3122 raise error.InputError(_(b'no revisions specified'))
3123 3123 cmdutil.checkunfinished(repo)
3124 3124 cmdutil.bailifchanged(repo)
3125 3125 revs = scmutil.revrange(repo, revs)
3126 3126
3127 3127 skipped = set()
3128 3128 basectx = None
3129 3129 if opts.get(b'base'):
3130 3130 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3131 3131 if basectx is None:
3132 3132 # check for merges
3133 3133 for rev in repo.revs(b'%ld and merge()', revs):
3134 3134 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3135 3135 skipped.add(rev)
3136 3136 revs = [r for r in revs if r not in skipped]
3137 3137 if not revs:
3138 3138 return -1
3139 3139 if basectx is not None and len(revs) != 1:
3140 3140 raise error.InputError(_(b'only one revision allowed with --base '))
3141 3141
3142 3142 # Don't check in the --continue case, in effect retaining --force across
3143 3143 # --continues. That's because without --force, any revisions we decided to
3144 3144 # skip would have been filtered out here, so they wouldn't have made their
3145 3145 # way to the graftstate. With --force, any revisions we would have otherwise
3146 3146 # skipped would not have been filtered out, and if they hadn't been applied
3147 3147 # already, they'd have been in the graftstate.
3148 3148 if not (cont or opts.get(b'force')) and basectx is None:
3149 3149 # check for ancestors of dest branch
3150 3150 ancestors = repo.revs(b'%ld & (::.)', revs)
3151 3151 for rev in ancestors:
3152 3152 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3153 3153
3154 3154 revs = [r for r in revs if r not in ancestors]
3155 3155
3156 3156 if not revs:
3157 3157 return -1
3158 3158
3159 3159 # analyze revs for earlier grafts
3160 3160 ids = {}
3161 3161 for ctx in repo.set(b"%ld", revs):
3162 3162 ids[ctx.hex()] = ctx.rev()
3163 3163 n = ctx.extra().get(b'source')
3164 3164 if n:
3165 3165 ids[n] = ctx.rev()
3166 3166
3167 3167 # check ancestors for earlier grafts
3168 3168 ui.debug(b'scanning for duplicate grafts\n')
3169 3169
3170 3170 # The only changesets we can be sure doesn't contain grafts of any
3171 3171 # revs, are the ones that are common ancestors of *all* revs:
3172 3172 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3173 3173 ctx = repo[rev]
3174 3174 n = ctx.extra().get(b'source')
3175 3175 if n in ids:
3176 3176 try:
3177 3177 r = repo[n].rev()
3178 3178 except error.RepoLookupError:
3179 3179 r = None
3180 3180 if r in revs:
3181 3181 ui.warn(
3182 3182 _(
3183 3183 b'skipping revision %d:%s '
3184 3184 b'(already grafted to %d:%s)\n'
3185 3185 )
3186 3186 % (r, repo[r], rev, ctx)
3187 3187 )
3188 3188 revs.remove(r)
3189 3189 elif ids[n] in revs:
3190 3190 if r is None:
3191 3191 ui.warn(
3192 3192 _(
3193 3193 b'skipping already grafted revision %d:%s '
3194 3194 b'(%d:%s also has unknown origin %s)\n'
3195 3195 )
3196 3196 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3197 3197 )
3198 3198 else:
3199 3199 ui.warn(
3200 3200 _(
3201 3201 b'skipping already grafted revision %d:%s '
3202 3202 b'(%d:%s also has origin %d:%s)\n'
3203 3203 )
3204 3204 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3205 3205 )
3206 3206 revs.remove(ids[n])
3207 3207 elif ctx.hex() in ids:
3208 3208 r = ids[ctx.hex()]
3209 3209 if r in revs:
3210 3210 ui.warn(
3211 3211 _(
3212 3212 b'skipping already grafted revision %d:%s '
3213 3213 b'(was grafted from %d:%s)\n'
3214 3214 )
3215 3215 % (r, repo[r], rev, ctx)
3216 3216 )
3217 3217 revs.remove(r)
3218 3218 if not revs:
3219 3219 return -1
3220 3220
3221 3221 if opts.get(b'no_commit'):
3222 3222 statedata[b'no_commit'] = True
3223 3223 if opts.get(b'base'):
3224 3224 statedata[b'base'] = opts[b'base']
3225 3225 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3226 3226 desc = b'%d:%s "%s"' % (
3227 3227 ctx.rev(),
3228 3228 ctx,
3229 3229 ctx.description().split(b'\n', 1)[0],
3230 3230 )
3231 3231 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3232 3232 if names:
3233 3233 desc += b' (%s)' % b' '.join(names)
3234 3234 ui.status(_(b'grafting %s\n') % desc)
3235 3235 if opts.get(b'dry_run'):
3236 3236 continue
3237 3237
3238 3238 source = ctx.extra().get(b'source')
3239 3239 extra = {}
3240 3240 if source:
3241 3241 extra[b'source'] = source
3242 3242 extra[b'intermediate-source'] = ctx.hex()
3243 3243 else:
3244 3244 extra[b'source'] = ctx.hex()
3245 3245 user = ctx.user()
3246 3246 if opts.get(b'user'):
3247 3247 user = opts[b'user']
3248 3248 statedata[b'user'] = user
3249 3249 date = ctx.date()
3250 3250 if opts.get(b'date'):
3251 3251 date = opts[b'date']
3252 3252 statedata[b'date'] = date
3253 3253 message = ctx.description()
3254 3254 if opts.get(b'log'):
3255 3255 message += b'\n(grafted from %s)' % ctx.hex()
3256 3256 statedata[b'log'] = True
3257 3257
3258 3258 # we don't merge the first commit when continuing
3259 3259 if not cont:
3260 3260 # perform the graft merge with p1(rev) as 'ancestor'
3261 3261 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3262 3262 base = ctx.p1() if basectx is None else basectx
3263 3263 with ui.configoverride(overrides, b'graft'):
3264 3264 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3265 3265 # report any conflicts
3266 3266 if stats.unresolvedcount > 0:
3267 3267 # write out state for --continue
3268 3268 nodes = [repo[rev].hex() for rev in revs[pos:]]
3269 3269 statedata[b'nodes'] = nodes
3270 3270 stateversion = 1
3271 3271 graftstate.save(stateversion, statedata)
3272 3272 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3273 3273 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3274 3274 return 1
3275 3275 else:
3276 3276 cont = False
3277 3277
3278 3278 # commit if --no-commit is false
3279 3279 if not opts.get(b'no_commit'):
3280 3280 node = repo.commit(
3281 3281 text=message, user=user, date=date, extra=extra, editor=editor
3282 3282 )
3283 3283 if node is None:
3284 3284 ui.warn(
3285 3285 _(b'note: graft of %d:%s created no changes to commit\n')
3286 3286 % (ctx.rev(), ctx)
3287 3287 )
3288 3288 # checking that newnodes exist because old state files won't have it
3289 3289 elif statedata.get(b'newnodes') is not None:
3290 3290 statedata[b'newnodes'].append(node)
3291 3291
3292 3292 # remove state when we complete successfully
3293 3293 if not opts.get(b'dry_run'):
3294 3294 graftstate.delete()
3295 3295
3296 3296 return 0
3297 3297
3298 3298
3299 3299 def _stopgraft(ui, repo, graftstate):
3300 3300 """stop the interrupted graft"""
3301 3301 if not graftstate.exists():
3302 3302 raise error.StateError(_(b"no interrupted graft found"))
3303 3303 pctx = repo[b'.']
3304 3304 mergemod.clean_update(pctx)
3305 3305 graftstate.delete()
3306 3306 ui.status(_(b"stopped the interrupted graft\n"))
3307 3307 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3308 3308 return 0
3309 3309
3310 3310
3311 3311 statemod.addunfinished(
3312 3312 b'graft',
3313 3313 fname=b'graftstate',
3314 3314 clearable=True,
3315 3315 stopflag=True,
3316 3316 continueflag=True,
3317 3317 abortfunc=cmdutil.hgabortgraft,
3318 3318 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3319 3319 )
3320 3320
3321 3321
3322 3322 @command(
3323 3323 b'grep',
3324 3324 [
3325 3325 (b'0', b'print0', None, _(b'end fields with NUL')),
3326 3326 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3327 3327 (
3328 3328 b'',
3329 3329 b'diff',
3330 3330 None,
3331 3331 _(
3332 3332 b'search revision differences for when the pattern was added '
3333 3333 b'or removed'
3334 3334 ),
3335 3335 ),
3336 3336 (b'a', b'text', None, _(b'treat all files as text')),
3337 3337 (
3338 3338 b'f',
3339 3339 b'follow',
3340 3340 None,
3341 3341 _(
3342 3342 b'follow changeset history,'
3343 3343 b' or file history across copies and renames'
3344 3344 ),
3345 3345 ),
3346 3346 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3347 3347 (
3348 3348 b'l',
3349 3349 b'files-with-matches',
3350 3350 None,
3351 3351 _(b'print only filenames and revisions that match'),
3352 3352 ),
3353 3353 (b'n', b'line-number', None, _(b'print matching line numbers')),
3354 3354 (
3355 3355 b'r',
3356 3356 b'rev',
3357 3357 [],
3358 3358 _(b'search files changed within revision range'),
3359 3359 _(b'REV'),
3360 3360 ),
3361 3361 (
3362 3362 b'',
3363 3363 b'all-files',
3364 3364 None,
3365 3365 _(
3366 3366 b'include all files in the changeset while grepping (DEPRECATED)'
3367 3367 ),
3368 3368 ),
3369 3369 (b'u', b'user', None, _(b'list the author (long with -v)')),
3370 3370 (b'd', b'date', None, _(b'list the date (short with -q)')),
3371 3371 ]
3372 3372 + formatteropts
3373 3373 + walkopts,
3374 3374 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3375 3375 helpcategory=command.CATEGORY_FILE_CONTENTS,
3376 3376 inferrepo=True,
3377 3377 intents={INTENT_READONLY},
3378 3378 )
3379 3379 def grep(ui, repo, pattern, *pats, **opts):
3380 3380 """search for a pattern in specified files
3381 3381
3382 3382 Search the working directory or revision history for a regular
3383 3383 expression in the specified files for the entire repository.
3384 3384
3385 3385 By default, grep searches the repository files in the working
3386 3386 directory and prints the files where it finds a match. To specify
3387 3387 historical revisions instead of the working directory, use the
3388 3388 --rev flag.
3389 3389
3390 3390 To search instead historical revision differences that contains a
3391 3391 change in match status ("-" for a match that becomes a non-match,
3392 3392 or "+" for a non-match that becomes a match), use the --diff flag.
3393 3393
3394 3394 PATTERN can be any Python (roughly Perl-compatible) regular
3395 3395 expression.
3396 3396
3397 3397 If no FILEs are specified and the --rev flag isn't supplied, all
3398 3398 files in the working directory are searched. When using the --rev
3399 3399 flag and specifying FILEs, use the --follow argument to also
3400 3400 follow the specified FILEs across renames and copies.
3401 3401
3402 3402 .. container:: verbose
3403 3403
3404 3404 Template:
3405 3405
3406 3406 The following keywords are supported in addition to the common template
3407 3407 keywords and functions. See also :hg:`help templates`.
3408 3408
3409 3409 :change: String. Character denoting insertion ``+`` or removal ``-``.
3410 3410 Available if ``--diff`` is specified.
3411 3411 :lineno: Integer. Line number of the match.
3412 3412 :path: String. Repository-absolute path of the file.
3413 3413 :texts: List of text chunks.
3414 3414
3415 3415 And each entry of ``{texts}`` provides the following sub-keywords.
3416 3416
3417 3417 :matched: Boolean. True if the chunk matches the specified pattern.
3418 3418 :text: String. Chunk content.
3419 3419
3420 3420 See :hg:`help templates.operators` for the list expansion syntax.
3421 3421
3422 3422 Returns 0 if a match is found, 1 otherwise.
3423 3423
3424 3424 """
3425 3425 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3426 3426 opts = pycompat.byteskwargs(opts)
3427 3427 diff = opts.get(b'all') or opts.get(b'diff')
3428 3428 follow = opts.get(b'follow')
3429 3429 if opts.get(b'all_files') is None and not diff:
3430 3430 opts[b'all_files'] = True
3431 3431 plaingrep = (
3432 3432 opts.get(b'all_files')
3433 3433 and not opts.get(b'rev')
3434 3434 and not opts.get(b'follow')
3435 3435 )
3436 3436 all_files = opts.get(b'all_files')
3437 3437 if plaingrep:
3438 3438 opts[b'rev'] = [b'wdir()']
3439 3439
3440 3440 reflags = re.M
3441 3441 if opts.get(b'ignore_case'):
3442 3442 reflags |= re.I
3443 3443 try:
3444 3444 regexp = util.re.compile(pattern, reflags)
3445 3445 except re.error as inst:
3446 3446 ui.warn(
3447 3447 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3448 3448 )
3449 3449 return 1
3450 3450 sep, eol = b':', b'\n'
3451 3451 if opts.get(b'print0'):
3452 3452 sep = eol = b'\0'
3453 3453
3454 3454 searcher = grepmod.grepsearcher(
3455 3455 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3456 3456 )
3457 3457
3458 3458 getfile = searcher._getfile
3459 3459
3460 3460 uipathfn = scmutil.getuipathfn(repo)
3461 3461
3462 3462 def display(fm, fn, ctx, pstates, states):
3463 3463 rev = scmutil.intrev(ctx)
3464 3464 if fm.isplain():
3465 3465 formatuser = ui.shortuser
3466 3466 else:
3467 3467 formatuser = pycompat.bytestr
3468 3468 if ui.quiet:
3469 3469 datefmt = b'%Y-%m-%d'
3470 3470 else:
3471 3471 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3472 3472 found = False
3473 3473
3474 3474 @util.cachefunc
3475 3475 def binary():
3476 3476 flog = getfile(fn)
3477 3477 try:
3478 3478 return stringutil.binary(flog.read(ctx.filenode(fn)))
3479 3479 except error.WdirUnsupported:
3480 3480 return ctx[fn].isbinary()
3481 3481
3482 3482 fieldnamemap = {b'linenumber': b'lineno'}
3483 3483 if diff:
3484 3484 iter = grepmod.difflinestates(pstates, states)
3485 3485 else:
3486 3486 iter = [(b'', l) for l in states]
3487 3487 for change, l in iter:
3488 3488 fm.startitem()
3489 3489 fm.context(ctx=ctx)
3490 3490 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3491 3491 fm.plain(uipathfn(fn), label=b'grep.filename')
3492 3492
3493 3493 cols = [
3494 3494 (b'rev', b'%d', rev, not plaingrep, b''),
3495 3495 (
3496 3496 b'linenumber',
3497 3497 b'%d',
3498 3498 l.linenum,
3499 3499 opts.get(b'line_number'),
3500 3500 b'',
3501 3501 ),
3502 3502 ]
3503 3503 if diff:
3504 3504 cols.append(
3505 3505 (
3506 3506 b'change',
3507 3507 b'%s',
3508 3508 change,
3509 3509 True,
3510 3510 b'grep.inserted '
3511 3511 if change == b'+'
3512 3512 else b'grep.deleted ',
3513 3513 )
3514 3514 )
3515 3515 cols.extend(
3516 3516 [
3517 3517 (
3518 3518 b'user',
3519 3519 b'%s',
3520 3520 formatuser(ctx.user()),
3521 3521 opts.get(b'user'),
3522 3522 b'',
3523 3523 ),
3524 3524 (
3525 3525 b'date',
3526 3526 b'%s',
3527 3527 fm.formatdate(ctx.date(), datefmt),
3528 3528 opts.get(b'date'),
3529 3529 b'',
3530 3530 ),
3531 3531 ]
3532 3532 )
3533 3533 for name, fmt, data, cond, extra_label in cols:
3534 3534 if cond:
3535 3535 fm.plain(sep, label=b'grep.sep')
3536 3536 field = fieldnamemap.get(name, name)
3537 3537 label = extra_label + (b'grep.%s' % name)
3538 3538 fm.condwrite(cond, field, fmt, data, label=label)
3539 3539 if not opts.get(b'files_with_matches'):
3540 3540 fm.plain(sep, label=b'grep.sep')
3541 3541 if not opts.get(b'text') and binary():
3542 3542 fm.plain(_(b" Binary file matches"))
3543 3543 else:
3544 3544 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3545 3545 fm.plain(eol)
3546 3546 found = True
3547 3547 if opts.get(b'files_with_matches'):
3548 3548 break
3549 3549 return found
3550 3550
3551 3551 def displaymatches(fm, l):
3552 3552 p = 0
3553 3553 for s, e in l.findpos(regexp):
3554 3554 if p < s:
3555 3555 fm.startitem()
3556 3556 fm.write(b'text', b'%s', l.line[p:s])
3557 3557 fm.data(matched=False)
3558 3558 fm.startitem()
3559 3559 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3560 3560 fm.data(matched=True)
3561 3561 p = e
3562 3562 if p < len(l.line):
3563 3563 fm.startitem()
3564 3564 fm.write(b'text', b'%s', l.line[p:])
3565 3565 fm.data(matched=False)
3566 3566 fm.end()
3567 3567
3568 3568 found = False
3569 3569
3570 3570 wopts = logcmdutil.walkopts(
3571 3571 pats=pats,
3572 3572 opts=opts,
3573 3573 revspec=opts[b'rev'],
3574 3574 include_pats=opts[b'include'],
3575 3575 exclude_pats=opts[b'exclude'],
3576 3576 follow=follow,
3577 3577 force_changelog_traversal=all_files,
3578 3578 filter_revisions_by_pats=not all_files,
3579 3579 )
3580 3580 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3581 3581
3582 3582 ui.pager(b'grep')
3583 3583 fm = ui.formatter(b'grep', opts)
3584 3584 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3585 3585 r = display(fm, fn, ctx, pstates, states)
3586 3586 found = found or r
3587 3587 if r and not diff and not all_files:
3588 3588 searcher.skipfile(fn, ctx.rev())
3589 3589 fm.end()
3590 3590
3591 3591 return not found
3592 3592
3593 3593
3594 3594 @command(
3595 3595 b'heads',
3596 3596 [
3597 3597 (
3598 3598 b'r',
3599 3599 b'rev',
3600 3600 b'',
3601 3601 _(b'show only heads which are descendants of STARTREV'),
3602 3602 _(b'STARTREV'),
3603 3603 ),
3604 3604 (b't', b'topo', False, _(b'show topological heads only')),
3605 3605 (
3606 3606 b'a',
3607 3607 b'active',
3608 3608 False,
3609 3609 _(b'show active branchheads only (DEPRECATED)'),
3610 3610 ),
3611 3611 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3612 3612 ]
3613 3613 + templateopts,
3614 3614 _(b'[-ct] [-r STARTREV] [REV]...'),
3615 3615 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3616 3616 intents={INTENT_READONLY},
3617 3617 )
3618 3618 def heads(ui, repo, *branchrevs, **opts):
3619 3619 """show branch heads
3620 3620
3621 3621 With no arguments, show all open branch heads in the repository.
3622 3622 Branch heads are changesets that have no descendants on the
3623 3623 same branch. They are where development generally takes place and
3624 3624 are the usual targets for update and merge operations.
3625 3625
3626 3626 If one or more REVs are given, only open branch heads on the
3627 3627 branches associated with the specified changesets are shown. This
3628 3628 means that you can use :hg:`heads .` to see the heads on the
3629 3629 currently checked-out branch.
3630 3630
3631 3631 If -c/--closed is specified, also show branch heads marked closed
3632 3632 (see :hg:`commit --close-branch`).
3633 3633
3634 3634 If STARTREV is specified, only those heads that are descendants of
3635 3635 STARTREV will be displayed.
3636 3636
3637 3637 If -t/--topo is specified, named branch mechanics will be ignored and only
3638 3638 topological heads (changesets with no children) will be shown.
3639 3639
3640 3640 Returns 0 if matching heads are found, 1 if not.
3641 3641 """
3642 3642
3643 3643 opts = pycompat.byteskwargs(opts)
3644 3644 start = None
3645 3645 rev = opts.get(b'rev')
3646 3646 if rev:
3647 3647 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3648 3648 start = scmutil.revsingle(repo, rev, None).node()
3649 3649
3650 3650 if opts.get(b'topo'):
3651 3651 heads = [repo[h] for h in repo.heads(start)]
3652 3652 else:
3653 3653 heads = []
3654 3654 for branch in repo.branchmap():
3655 3655 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3656 3656 heads = [repo[h] for h in heads]
3657 3657
3658 3658 if branchrevs:
3659 3659 branches = {
3660 3660 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3661 3661 }
3662 3662 heads = [h for h in heads if h.branch() in branches]
3663 3663
3664 3664 if opts.get(b'active') and branchrevs:
3665 3665 dagheads = repo.heads(start)
3666 3666 heads = [h for h in heads if h.node() in dagheads]
3667 3667
3668 3668 if branchrevs:
3669 3669 haveheads = {h.branch() for h in heads}
3670 3670 if branches - haveheads:
3671 3671 headless = b', '.join(b for b in branches - haveheads)
3672 3672 msg = _(b'no open branch heads found on branches %s')
3673 3673 if opts.get(b'rev'):
3674 3674 msg += _(b' (started at %s)') % opts[b'rev']
3675 3675 ui.warn((msg + b'\n') % headless)
3676 3676
3677 3677 if not heads:
3678 3678 return 1
3679 3679
3680 3680 ui.pager(b'heads')
3681 3681 heads = sorted(heads, key=lambda x: -(x.rev()))
3682 3682 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3683 3683 for ctx in heads:
3684 3684 displayer.show(ctx)
3685 3685 displayer.close()
3686 3686
3687 3687
3688 3688 @command(
3689 3689 b'help',
3690 3690 [
3691 3691 (b'e', b'extension', None, _(b'show only help for extensions')),
3692 3692 (b'c', b'command', None, _(b'show only help for commands')),
3693 3693 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3694 3694 (
3695 3695 b's',
3696 3696 b'system',
3697 3697 [],
3698 3698 _(b'show help for specific platform(s)'),
3699 3699 _(b'PLATFORM'),
3700 3700 ),
3701 3701 ],
3702 3702 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3703 3703 helpcategory=command.CATEGORY_HELP,
3704 3704 norepo=True,
3705 3705 intents={INTENT_READONLY},
3706 3706 )
3707 3707 def help_(ui, name=None, **opts):
3708 3708 """show help for a given topic or a help overview
3709 3709
3710 3710 With no arguments, print a list of commands with short help messages.
3711 3711
3712 3712 Given a topic, extension, or command name, print help for that
3713 3713 topic.
3714 3714
3715 3715 Returns 0 if successful.
3716 3716 """
3717 3717
3718 3718 keep = opts.get('system') or []
3719 3719 if len(keep) == 0:
3720 3720 if pycompat.sysplatform.startswith(b'win'):
3721 3721 keep.append(b'windows')
3722 3722 elif pycompat.sysplatform == b'OpenVMS':
3723 3723 keep.append(b'vms')
3724 3724 elif pycompat.sysplatform == b'plan9':
3725 3725 keep.append(b'plan9')
3726 3726 else:
3727 3727 keep.append(b'unix')
3728 3728 keep.append(pycompat.sysplatform.lower())
3729 3729 if ui.verbose:
3730 3730 keep.append(b'verbose')
3731 3731
3732 3732 commands = sys.modules[__name__]
3733 3733 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3734 3734 ui.pager(b'help')
3735 3735 ui.write(formatted)
3736 3736
3737 3737
3738 3738 @command(
3739 3739 b'identify|id',
3740 3740 [
3741 3741 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3742 3742 (b'n', b'num', None, _(b'show local revision number')),
3743 3743 (b'i', b'id', None, _(b'show global revision id')),
3744 3744 (b'b', b'branch', None, _(b'show branch')),
3745 3745 (b't', b'tags', None, _(b'show tags')),
3746 3746 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3747 3747 ]
3748 3748 + remoteopts
3749 3749 + formatteropts,
3750 3750 _(b'[-nibtB] [-r REV] [SOURCE]'),
3751 3751 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3752 3752 optionalrepo=True,
3753 3753 intents={INTENT_READONLY},
3754 3754 )
3755 3755 def identify(
3756 3756 ui,
3757 3757 repo,
3758 3758 source=None,
3759 3759 rev=None,
3760 3760 num=None,
3761 3761 id=None,
3762 3762 branch=None,
3763 3763 tags=None,
3764 3764 bookmarks=None,
3765 3765 **opts
3766 3766 ):
3767 3767 """identify the working directory or specified revision
3768 3768
3769 3769 Print a summary identifying the repository state at REV using one or
3770 3770 two parent hash identifiers, followed by a "+" if the working
3771 3771 directory has uncommitted changes, the branch name (if not default),
3772 3772 a list of tags, and a list of bookmarks.
3773 3773
3774 3774 When REV is not given, print a summary of the current state of the
3775 3775 repository including the working directory. Specify -r. to get information
3776 3776 of the working directory parent without scanning uncommitted changes.
3777 3777
3778 3778 Specifying a path to a repository root or Mercurial bundle will
3779 3779 cause lookup to operate on that repository/bundle.
3780 3780
3781 3781 .. container:: verbose
3782 3782
3783 3783 Template:
3784 3784
3785 3785 The following keywords are supported in addition to the common template
3786 3786 keywords and functions. See also :hg:`help templates`.
3787 3787
3788 3788 :dirty: String. Character ``+`` denoting if the working directory has
3789 3789 uncommitted changes.
3790 3790 :id: String. One or two nodes, optionally followed by ``+``.
3791 3791 :parents: List of strings. Parent nodes of the changeset.
3792 3792
3793 3793 Examples:
3794 3794
3795 3795 - generate a build identifier for the working directory::
3796 3796
3797 3797 hg id --id > build-id.dat
3798 3798
3799 3799 - find the revision corresponding to a tag::
3800 3800
3801 3801 hg id -n -r 1.3
3802 3802
3803 3803 - check the most recent revision of a remote repository::
3804 3804
3805 3805 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3806 3806
3807 3807 See :hg:`log` for generating more information about specific revisions,
3808 3808 including full hash identifiers.
3809 3809
3810 3810 Returns 0 if successful.
3811 3811 """
3812 3812
3813 3813 opts = pycompat.byteskwargs(opts)
3814 3814 if not repo and not source:
3815 3815 raise error.InputError(
3816 3816 _(b"there is no Mercurial repository here (.hg not found)")
3817 3817 )
3818 3818
3819 3819 default = not (num or id or branch or tags or bookmarks)
3820 3820 output = []
3821 3821 revs = []
3822 3822
3823 if source:
3824 source, branches = hg.parseurl(ui.expandpath(source))
3825 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3826 repo = peer.local()
3827 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3828
3829 fm = ui.formatter(b'identify', opts)
3830 fm.startitem()
3831
3832 if not repo:
3833 if num or branch or tags:
3834 raise error.InputError(
3835 _(b"can't query remote revision number, branch, or tags")
3836 )
3837 if not rev and revs:
3838 rev = revs[0]
3839 if not rev:
3840 rev = b"tip"
3841
3842 remoterev = peer.lookup(rev)
3843 hexrev = fm.hexfunc(remoterev)
3844 if default or id:
3845 output = [hexrev]
3846 fm.data(id=hexrev)
3847
3848 @util.cachefunc
3849 def getbms():
3850 bms = []
3851
3852 if b'bookmarks' in peer.listkeys(b'namespaces'):
3853 hexremoterev = hex(remoterev)
3854 bms = [
3855 bm
3856 for bm, bmr in pycompat.iteritems(
3857 peer.listkeys(b'bookmarks')
3823 peer = None
3824 try:
3825 if source:
3826 source, branches = hg.parseurl(ui.expandpath(source))
3827 # only pass ui when no repo
3828 peer = hg.peer(repo or ui, opts, source)
3829 repo = peer.local()
3830 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3831
3832 fm = ui.formatter(b'identify', opts)
3833 fm.startitem()
3834
3835 if not repo:
3836 if num or branch or tags:
3837 raise error.InputError(
3838 _(b"can't query remote revision number, branch, or tags")
3839 )
3840 if not rev and revs:
3841 rev = revs[0]
3842 if not rev:
3843 rev = b"tip"
3844
3845 remoterev = peer.lookup(rev)
3846 hexrev = fm.hexfunc(remoterev)
3847 if default or id:
3848 output = [hexrev]
3849 fm.data(id=hexrev)
3850
3851 @util.cachefunc
3852 def getbms():
3853 bms = []
3854
3855 if b'bookmarks' in peer.listkeys(b'namespaces'):
3856 hexremoterev = hex(remoterev)
3857 bms = [
3858 bm
3859 for bm, bmr in pycompat.iteritems(
3860 peer.listkeys(b'bookmarks')
3861 )
3862 if bmr == hexremoterev
3863 ]
3864
3865 return sorted(bms)
3866
3867 if fm.isplain():
3868 if bookmarks:
3869 output.extend(getbms())
3870 elif default and not ui.quiet:
3871 # multiple bookmarks for a single parent separated by '/'
3872 bm = b'/'.join(getbms())
3873 if bm:
3874 output.append(bm)
3875 else:
3876 fm.data(node=hex(remoterev))
3877 if bookmarks or b'bookmarks' in fm.datahint():
3878 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3879 else:
3880 if rev:
3881 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3882 ctx = scmutil.revsingle(repo, rev, None)
3883
3884 if ctx.rev() is None:
3885 ctx = repo[None]
3886 parents = ctx.parents()
3887 taglist = []
3888 for p in parents:
3889 taglist.extend(p.tags())
3890
3891 dirty = b""
3892 if ctx.dirty(missing=True, merge=False, branch=False):
3893 dirty = b'+'
3894 fm.data(dirty=dirty)
3895
3896 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3897 if default or id:
3898 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3899 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3900
3901 if num:
3902 numoutput = [b"%d" % p.rev() for p in parents]
3903 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3904
3905 fm.data(
3906 parents=fm.formatlist(
3907 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3858 3908 )
3859 if bmr == hexremoterev
3860 ]
3861
3862 return sorted(bms)
3863
3864 if fm.isplain():
3865 if bookmarks:
3866 output.extend(getbms())
3867 elif default and not ui.quiet:
3909 )
3910 else:
3911 hexoutput = fm.hexfunc(ctx.node())
3912 if default or id:
3913 output = [hexoutput]
3914 fm.data(id=hexoutput)
3915
3916 if num:
3917 output.append(pycompat.bytestr(ctx.rev()))
3918 taglist = ctx.tags()
3919
3920 if default and not ui.quiet:
3921 b = ctx.branch()
3922 if b != b'default':
3923 output.append(b"(%s)" % b)
3924
3925 # multiple tags for a single parent separated by '/'
3926 t = b'/'.join(taglist)
3927 if t:
3928 output.append(t)
3929
3868 3930 # multiple bookmarks for a single parent separated by '/'
3869 bm = b'/'.join(getbms())
3931 bm = b'/'.join(ctx.bookmarks())
3870 3932 if bm:
3871 3933 output.append(bm)
3872 else:
3873 fm.data(node=hex(remoterev))
3874 if bookmarks or b'bookmarks' in fm.datahint():
3875 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3876 else:
3877 if rev:
3878 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3879 ctx = scmutil.revsingle(repo, rev, None)
3880
3881 if ctx.rev() is None:
3882 ctx = repo[None]
3883 parents = ctx.parents()
3884 taglist = []
3885 for p in parents:
3886 taglist.extend(p.tags())
3887
3888 dirty = b""
3889 if ctx.dirty(missing=True, merge=False, branch=False):
3890 dirty = b'+'
3891 fm.data(dirty=dirty)
3892
3893 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3894 if default or id:
3895 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3896 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3897
3898 if num:
3899 numoutput = [b"%d" % p.rev() for p in parents]
3900 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3901
3902 fm.data(
3903 parents=fm.formatlist(
3904 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3905 )
3906 )
3907 else:
3908 hexoutput = fm.hexfunc(ctx.node())
3909 if default or id:
3910 output = [hexoutput]
3911 fm.data(id=hexoutput)
3912
3913 if num:
3914 output.append(pycompat.bytestr(ctx.rev()))
3915 taglist = ctx.tags()
3916
3917 if default and not ui.quiet:
3918 b = ctx.branch()
3919 if b != b'default':
3920 output.append(b"(%s)" % b)
3921
3922 # multiple tags for a single parent separated by '/'
3923 t = b'/'.join(taglist)
3924 if t:
3925 output.append(t)
3926
3927 # multiple bookmarks for a single parent separated by '/'
3928 bm = b'/'.join(ctx.bookmarks())
3929 if bm:
3930 output.append(bm)
3931 else:
3932 if branch:
3933 output.append(ctx.branch())
3934
3935 if tags:
3936 output.extend(taglist)
3937
3938 if bookmarks:
3939 output.extend(ctx.bookmarks())
3940
3941 fm.data(node=ctx.hex())
3942 fm.data(branch=ctx.branch())
3943 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
3944 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
3945 fm.context(ctx=ctx)
3946
3947 fm.plain(b"%s\n" % b' '.join(output))
3948 fm.end()
3934 else:
3935 if branch:
3936 output.append(ctx.branch())
3937
3938 if tags:
3939 output.extend(taglist)
3940
3941 if bookmarks:
3942 output.extend(ctx.bookmarks())
3943
3944 fm.data(node=ctx.hex())
3945 fm.data(branch=ctx.branch())
3946 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
3947 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
3948 fm.context(ctx=ctx)
3949
3950 fm.plain(b"%s\n" % b' '.join(output))
3951 fm.end()
3952 finally:
3953 if peer:
3954 peer.close()
3949 3955
3950 3956
3951 3957 @command(
3952 3958 b'import|patch',
3953 3959 [
3954 3960 (
3955 3961 b'p',
3956 3962 b'strip',
3957 3963 1,
3958 3964 _(
3959 3965 b'directory strip option for patch. This has the same '
3960 3966 b'meaning as the corresponding patch option'
3961 3967 ),
3962 3968 _(b'NUM'),
3963 3969 ),
3964 3970 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
3965 3971 (b'', b'secret', None, _(b'use the secret phase for committing')),
3966 3972 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
3967 3973 (
3968 3974 b'f',
3969 3975 b'force',
3970 3976 None,
3971 3977 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
3972 3978 ),
3973 3979 (
3974 3980 b'',
3975 3981 b'no-commit',
3976 3982 None,
3977 3983 _(b"don't commit, just update the working directory"),
3978 3984 ),
3979 3985 (
3980 3986 b'',
3981 3987 b'bypass',
3982 3988 None,
3983 3989 _(b"apply patch without touching the working directory"),
3984 3990 ),
3985 3991 (b'', b'partial', None, _(b'commit even if some hunks fail')),
3986 3992 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
3987 3993 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
3988 3994 (
3989 3995 b'',
3990 3996 b'import-branch',
3991 3997 None,
3992 3998 _(b'use any branch information in patch (implied by --exact)'),
3993 3999 ),
3994 4000 ]
3995 4001 + commitopts
3996 4002 + commitopts2
3997 4003 + similarityopts,
3998 4004 _(b'[OPTION]... PATCH...'),
3999 4005 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4000 4006 )
4001 4007 def import_(ui, repo, patch1=None, *patches, **opts):
4002 4008 """import an ordered set of patches
4003 4009
4004 4010 Import a list of patches and commit them individually (unless
4005 4011 --no-commit is specified).
4006 4012
4007 4013 To read a patch from standard input (stdin), use "-" as the patch
4008 4014 name. If a URL is specified, the patch will be downloaded from
4009 4015 there.
4010 4016
4011 4017 Import first applies changes to the working directory (unless
4012 4018 --bypass is specified), import will abort if there are outstanding
4013 4019 changes.
4014 4020
4015 4021 Use --bypass to apply and commit patches directly to the
4016 4022 repository, without affecting the working directory. Without
4017 4023 --exact, patches will be applied on top of the working directory
4018 4024 parent revision.
4019 4025
4020 4026 You can import a patch straight from a mail message. Even patches
4021 4027 as attachments work (to use the body part, it must have type
4022 4028 text/plain or text/x-patch). From and Subject headers of email
4023 4029 message are used as default committer and commit message. All
4024 4030 text/plain body parts before first diff are added to the commit
4025 4031 message.
4026 4032
4027 4033 If the imported patch was generated by :hg:`export`, user and
4028 4034 description from patch override values from message headers and
4029 4035 body. Values given on command line with -m/--message and -u/--user
4030 4036 override these.
4031 4037
4032 4038 If --exact is specified, import will set the working directory to
4033 4039 the parent of each patch before applying it, and will abort if the
4034 4040 resulting changeset has a different ID than the one recorded in
4035 4041 the patch. This will guard against various ways that portable
4036 4042 patch formats and mail systems might fail to transfer Mercurial
4037 4043 data or metadata. See :hg:`bundle` for lossless transmission.
4038 4044
4039 4045 Use --partial to ensure a changeset will be created from the patch
4040 4046 even if some hunks fail to apply. Hunks that fail to apply will be
4041 4047 written to a <target-file>.rej file. Conflicts can then be resolved
4042 4048 by hand before :hg:`commit --amend` is run to update the created
4043 4049 changeset. This flag exists to let people import patches that
4044 4050 partially apply without losing the associated metadata (author,
4045 4051 date, description, ...).
4046 4052
4047 4053 .. note::
4048 4054
4049 4055 When no hunks apply cleanly, :hg:`import --partial` will create
4050 4056 an empty changeset, importing only the patch metadata.
4051 4057
4052 4058 With -s/--similarity, hg will attempt to discover renames and
4053 4059 copies in the patch in the same way as :hg:`addremove`.
4054 4060
4055 4061 It is possible to use external patch programs to perform the patch
4056 4062 by setting the ``ui.patch`` configuration option. For the default
4057 4063 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4058 4064 See :hg:`help config` for more information about configuration
4059 4065 files and how to use these options.
4060 4066
4061 4067 See :hg:`help dates` for a list of formats valid for -d/--date.
4062 4068
4063 4069 .. container:: verbose
4064 4070
4065 4071 Examples:
4066 4072
4067 4073 - import a traditional patch from a website and detect renames::
4068 4074
4069 4075 hg import -s 80 http://example.com/bugfix.patch
4070 4076
4071 4077 - import a changeset from an hgweb server::
4072 4078
4073 4079 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4074 4080
4075 4081 - import all the patches in an Unix-style mbox::
4076 4082
4077 4083 hg import incoming-patches.mbox
4078 4084
4079 4085 - import patches from stdin::
4080 4086
4081 4087 hg import -
4082 4088
4083 4089 - attempt to exactly restore an exported changeset (not always
4084 4090 possible)::
4085 4091
4086 4092 hg import --exact proposed-fix.patch
4087 4093
4088 4094 - use an external tool to apply a patch which is too fuzzy for
4089 4095 the default internal tool.
4090 4096
4091 4097 hg import --config ui.patch="patch --merge" fuzzy.patch
4092 4098
4093 4099 - change the default fuzzing from 2 to a less strict 7
4094 4100
4095 4101 hg import --config ui.fuzz=7 fuzz.patch
4096 4102
4097 4103 Returns 0 on success, 1 on partial success (see --partial).
4098 4104 """
4099 4105
4100 4106 cmdutil.check_incompatible_arguments(
4101 4107 opts, 'no_commit', ['bypass', 'secret']
4102 4108 )
4103 4109 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4104 4110 opts = pycompat.byteskwargs(opts)
4105 4111 if not patch1:
4106 4112 raise error.InputError(_(b'need at least one patch to import'))
4107 4113
4108 4114 patches = (patch1,) + patches
4109 4115
4110 4116 date = opts.get(b'date')
4111 4117 if date:
4112 4118 opts[b'date'] = dateutil.parsedate(date)
4113 4119
4114 4120 exact = opts.get(b'exact')
4115 4121 update = not opts.get(b'bypass')
4116 4122 try:
4117 4123 sim = float(opts.get(b'similarity') or 0)
4118 4124 except ValueError:
4119 4125 raise error.InputError(_(b'similarity must be a number'))
4120 4126 if sim < 0 or sim > 100:
4121 4127 raise error.InputError(_(b'similarity must be between 0 and 100'))
4122 4128 if sim and not update:
4123 4129 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4124 4130
4125 4131 base = opts[b"base"]
4126 4132 msgs = []
4127 4133 ret = 0
4128 4134
4129 4135 with repo.wlock():
4130 4136 if update:
4131 4137 cmdutil.checkunfinished(repo)
4132 4138 if exact or not opts.get(b'force'):
4133 4139 cmdutil.bailifchanged(repo)
4134 4140
4135 4141 if not opts.get(b'no_commit'):
4136 4142 lock = repo.lock
4137 4143 tr = lambda: repo.transaction(b'import')
4138 4144 dsguard = util.nullcontextmanager
4139 4145 else:
4140 4146 lock = util.nullcontextmanager
4141 4147 tr = util.nullcontextmanager
4142 4148 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4143 4149 with lock(), tr(), dsguard():
4144 4150 parents = repo[None].parents()
4145 4151 for patchurl in patches:
4146 4152 if patchurl == b'-':
4147 4153 ui.status(_(b'applying patch from stdin\n'))
4148 4154 patchfile = ui.fin
4149 4155 patchurl = b'stdin' # for error message
4150 4156 else:
4151 4157 patchurl = os.path.join(base, patchurl)
4152 4158 ui.status(_(b'applying %s\n') % patchurl)
4153 4159 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4154 4160
4155 4161 haspatch = False
4156 4162 for hunk in patch.split(patchfile):
4157 4163 with patch.extract(ui, hunk) as patchdata:
4158 4164 msg, node, rej = cmdutil.tryimportone(
4159 4165 ui, repo, patchdata, parents, opts, msgs, hg.clean
4160 4166 )
4161 4167 if msg:
4162 4168 haspatch = True
4163 4169 ui.note(msg + b'\n')
4164 4170 if update or exact:
4165 4171 parents = repo[None].parents()
4166 4172 else:
4167 4173 parents = [repo[node]]
4168 4174 if rej:
4169 4175 ui.write_err(_(b"patch applied partially\n"))
4170 4176 ui.write_err(
4171 4177 _(
4172 4178 b"(fix the .rej files and run "
4173 4179 b"`hg commit --amend`)\n"
4174 4180 )
4175 4181 )
4176 4182 ret = 1
4177 4183 break
4178 4184
4179 4185 if not haspatch:
4180 4186 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4181 4187
4182 4188 if msgs:
4183 4189 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4184 4190 return ret
4185 4191
4186 4192
4187 4193 @command(
4188 4194 b'incoming|in',
4189 4195 [
4190 4196 (
4191 4197 b'f',
4192 4198 b'force',
4193 4199 None,
4194 4200 _(b'run even if remote repository is unrelated'),
4195 4201 ),
4196 4202 (b'n', b'newest-first', None, _(b'show newest record first')),
4197 4203 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4198 4204 (
4199 4205 b'r',
4200 4206 b'rev',
4201 4207 [],
4202 4208 _(b'a remote changeset intended to be added'),
4203 4209 _(b'REV'),
4204 4210 ),
4205 4211 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4206 4212 (
4207 4213 b'b',
4208 4214 b'branch',
4209 4215 [],
4210 4216 _(b'a specific branch you would like to pull'),
4211 4217 _(b'BRANCH'),
4212 4218 ),
4213 4219 ]
4214 4220 + logopts
4215 4221 + remoteopts
4216 4222 + subrepoopts,
4217 4223 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4218 4224 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4219 4225 )
4220 4226 def incoming(ui, repo, source=b"default", **opts):
4221 4227 """show new changesets found in source
4222 4228
4223 4229 Show new changesets found in the specified path/URL or the default
4224 4230 pull location. These are the changesets that would have been pulled
4225 4231 by :hg:`pull` at the time you issued this command.
4226 4232
4227 4233 See pull for valid source format details.
4228 4234
4229 4235 .. container:: verbose
4230 4236
4231 4237 With -B/--bookmarks, the result of bookmark comparison between
4232 4238 local and remote repositories is displayed. With -v/--verbose,
4233 4239 status is also displayed for each bookmark like below::
4234 4240
4235 4241 BM1 01234567890a added
4236 4242 BM2 1234567890ab advanced
4237 4243 BM3 234567890abc diverged
4238 4244 BM4 34567890abcd changed
4239 4245
4240 4246 The action taken locally when pulling depends on the
4241 4247 status of each bookmark:
4242 4248
4243 4249 :``added``: pull will create it
4244 4250 :``advanced``: pull will update it
4245 4251 :``diverged``: pull will create a divergent bookmark
4246 4252 :``changed``: result depends on remote changesets
4247 4253
4248 4254 From the point of view of pulling behavior, bookmark
4249 4255 existing only in the remote repository are treated as ``added``,
4250 4256 even if it is in fact locally deleted.
4251 4257
4252 4258 .. container:: verbose
4253 4259
4254 4260 For remote repository, using --bundle avoids downloading the
4255 4261 changesets twice if the incoming is followed by a pull.
4256 4262
4257 4263 Examples:
4258 4264
4259 4265 - show incoming changes with patches and full description::
4260 4266
4261 4267 hg incoming -vp
4262 4268
4263 4269 - show incoming changes excluding merges, store a bundle::
4264 4270
4265 4271 hg in -vpM --bundle incoming.hg
4266 4272 hg pull incoming.hg
4267 4273
4268 4274 - briefly list changes inside a bundle::
4269 4275
4270 4276 hg in changes.hg -T "{desc|firstline}\\n"
4271 4277
4272 4278 Returns 0 if there are incoming changes, 1 otherwise.
4273 4279 """
4274 4280 opts = pycompat.byteskwargs(opts)
4275 4281 if opts.get(b'graph'):
4276 4282 logcmdutil.checkunsupportedgraphflags([], opts)
4277 4283
4278 4284 def display(other, chlist, displayer):
4279 4285 revdag = logcmdutil.graphrevs(other, chlist, opts)
4280 4286 logcmdutil.displaygraph(
4281 4287 ui, repo, revdag, displayer, graphmod.asciiedges
4282 4288 )
4283 4289
4284 4290 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4285 4291 return 0
4286 4292
4287 4293 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4288 4294
4289 4295 if opts.get(b'bookmarks'):
4290 4296 source, branches = hg.parseurl(
4291 4297 ui.expandpath(source), opts.get(b'branch')
4292 4298 )
4293 4299 other = hg.peer(repo, opts, source)
4294 if b'bookmarks' not in other.listkeys(b'namespaces'):
4295 ui.warn(_(b"remote doesn't support bookmarks\n"))
4296 return 0
4297 ui.pager(b'incoming')
4298 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4299 return bookmarks.incoming(ui, repo, other)
4300 try:
4301 if b'bookmarks' not in other.listkeys(b'namespaces'):
4302 ui.warn(_(b"remote doesn't support bookmarks\n"))
4303 return 0
4304 ui.pager(b'incoming')
4305 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4306 return bookmarks.incoming(ui, repo, other)
4307 finally:
4308 other.close()
4300 4309
4301 4310 repo._subtoppath = ui.expandpath(source)
4302 4311 try:
4303 4312 return hg.incoming(ui, repo, source, opts)
4304 4313 finally:
4305 4314 del repo._subtoppath
4306 4315
4307 4316
4308 4317 @command(
4309 4318 b'init',
4310 4319 remoteopts,
4311 4320 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4312 4321 helpcategory=command.CATEGORY_REPO_CREATION,
4313 4322 helpbasic=True,
4314 4323 norepo=True,
4315 4324 )
4316 4325 def init(ui, dest=b".", **opts):
4317 4326 """create a new repository in the given directory
4318 4327
4319 4328 Initialize a new repository in the given directory. If the given
4320 4329 directory does not exist, it will be created.
4321 4330
4322 4331 If no directory is given, the current directory is used.
4323 4332
4324 4333 It is possible to specify an ``ssh://`` URL as the destination.
4325 4334 See :hg:`help urls` for more information.
4326 4335
4327 4336 Returns 0 on success.
4328 4337 """
4329 4338 opts = pycompat.byteskwargs(opts)
4330 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4339 peer = hg.peer(ui, opts, ui.expandpath(dest), create=True)
4340 peer.close()
4331 4341
4332 4342
4333 4343 @command(
4334 4344 b'locate',
4335 4345 [
4336 4346 (
4337 4347 b'r',
4338 4348 b'rev',
4339 4349 b'',
4340 4350 _(b'search the repository as it is in REV'),
4341 4351 _(b'REV'),
4342 4352 ),
4343 4353 (
4344 4354 b'0',
4345 4355 b'print0',
4346 4356 None,
4347 4357 _(b'end filenames with NUL, for use with xargs'),
4348 4358 ),
4349 4359 (
4350 4360 b'f',
4351 4361 b'fullpath',
4352 4362 None,
4353 4363 _(b'print complete paths from the filesystem root'),
4354 4364 ),
4355 4365 ]
4356 4366 + walkopts,
4357 4367 _(b'[OPTION]... [PATTERN]...'),
4358 4368 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4359 4369 )
4360 4370 def locate(ui, repo, *pats, **opts):
4361 4371 """locate files matching specific patterns (DEPRECATED)
4362 4372
4363 4373 Print files under Mercurial control in the working directory whose
4364 4374 names match the given patterns.
4365 4375
4366 4376 By default, this command searches all directories in the working
4367 4377 directory. To search just the current directory and its
4368 4378 subdirectories, use "--include .".
4369 4379
4370 4380 If no patterns are given to match, this command prints the names
4371 4381 of all files under Mercurial control in the working directory.
4372 4382
4373 4383 If you want to feed the output of this command into the "xargs"
4374 4384 command, use the -0 option to both this command and "xargs". This
4375 4385 will avoid the problem of "xargs" treating single filenames that
4376 4386 contain whitespace as multiple filenames.
4377 4387
4378 4388 See :hg:`help files` for a more versatile command.
4379 4389
4380 4390 Returns 0 if a match is found, 1 otherwise.
4381 4391 """
4382 4392 opts = pycompat.byteskwargs(opts)
4383 4393 if opts.get(b'print0'):
4384 4394 end = b'\0'
4385 4395 else:
4386 4396 end = b'\n'
4387 4397 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4388 4398
4389 4399 ret = 1
4390 4400 m = scmutil.match(
4391 4401 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4392 4402 )
4393 4403
4394 4404 ui.pager(b'locate')
4395 4405 if ctx.rev() is None:
4396 4406 # When run on the working copy, "locate" includes removed files, so
4397 4407 # we get the list of files from the dirstate.
4398 4408 filesgen = sorted(repo.dirstate.matches(m))
4399 4409 else:
4400 4410 filesgen = ctx.matches(m)
4401 4411 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4402 4412 for abs in filesgen:
4403 4413 if opts.get(b'fullpath'):
4404 4414 ui.write(repo.wjoin(abs), end)
4405 4415 else:
4406 4416 ui.write(uipathfn(abs), end)
4407 4417 ret = 0
4408 4418
4409 4419 return ret
4410 4420
4411 4421
4412 4422 @command(
4413 4423 b'log|history',
4414 4424 [
4415 4425 (
4416 4426 b'f',
4417 4427 b'follow',
4418 4428 None,
4419 4429 _(
4420 4430 b'follow changeset history, or file history across copies and renames'
4421 4431 ),
4422 4432 ),
4423 4433 (
4424 4434 b'',
4425 4435 b'follow-first',
4426 4436 None,
4427 4437 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4428 4438 ),
4429 4439 (
4430 4440 b'd',
4431 4441 b'date',
4432 4442 b'',
4433 4443 _(b'show revisions matching date spec'),
4434 4444 _(b'DATE'),
4435 4445 ),
4436 4446 (b'C', b'copies', None, _(b'show copied files')),
4437 4447 (
4438 4448 b'k',
4439 4449 b'keyword',
4440 4450 [],
4441 4451 _(b'do case-insensitive search for a given text'),
4442 4452 _(b'TEXT'),
4443 4453 ),
4444 4454 (
4445 4455 b'r',
4446 4456 b'rev',
4447 4457 [],
4448 4458 _(b'revisions to select or follow from'),
4449 4459 _(b'REV'),
4450 4460 ),
4451 4461 (
4452 4462 b'L',
4453 4463 b'line-range',
4454 4464 [],
4455 4465 _(b'follow line range of specified file (EXPERIMENTAL)'),
4456 4466 _(b'FILE,RANGE'),
4457 4467 ),
4458 4468 (
4459 4469 b'',
4460 4470 b'removed',
4461 4471 None,
4462 4472 _(b'include revisions where files were removed'),
4463 4473 ),
4464 4474 (
4465 4475 b'm',
4466 4476 b'only-merges',
4467 4477 None,
4468 4478 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4469 4479 ),
4470 4480 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4471 4481 (
4472 4482 b'',
4473 4483 b'only-branch',
4474 4484 [],
4475 4485 _(
4476 4486 b'show only changesets within the given named branch (DEPRECATED)'
4477 4487 ),
4478 4488 _(b'BRANCH'),
4479 4489 ),
4480 4490 (
4481 4491 b'b',
4482 4492 b'branch',
4483 4493 [],
4484 4494 _(b'show changesets within the given named branch'),
4485 4495 _(b'BRANCH'),
4486 4496 ),
4487 4497 (
4488 4498 b'B',
4489 4499 b'bookmark',
4490 4500 [],
4491 4501 _(b"show changesets within the given bookmark"),
4492 4502 _(b'BOOKMARK'),
4493 4503 ),
4494 4504 (
4495 4505 b'P',
4496 4506 b'prune',
4497 4507 [],
4498 4508 _(b'do not display revision or any of its ancestors'),
4499 4509 _(b'REV'),
4500 4510 ),
4501 4511 ]
4502 4512 + logopts
4503 4513 + walkopts,
4504 4514 _(b'[OPTION]... [FILE]'),
4505 4515 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4506 4516 helpbasic=True,
4507 4517 inferrepo=True,
4508 4518 intents={INTENT_READONLY},
4509 4519 )
4510 4520 def log(ui, repo, *pats, **opts):
4511 4521 """show revision history of entire repository or files
4512 4522
4513 4523 Print the revision history of the specified files or the entire
4514 4524 project.
4515 4525
4516 4526 If no revision range is specified, the default is ``tip:0`` unless
4517 4527 --follow is set.
4518 4528
4519 4529 File history is shown without following rename or copy history of
4520 4530 files. Use -f/--follow with a filename to follow history across
4521 4531 renames and copies. --follow without a filename will only show
4522 4532 ancestors of the starting revisions. The starting revisions can be
4523 4533 specified by -r/--rev, which default to the working directory parent.
4524 4534
4525 4535 By default this command prints revision number and changeset id,
4526 4536 tags, non-trivial parents, user, date and time, and a summary for
4527 4537 each commit. When the -v/--verbose switch is used, the list of
4528 4538 changed files and full commit message are shown.
4529 4539
4530 4540 With --graph the revisions are shown as an ASCII art DAG with the most
4531 4541 recent changeset at the top.
4532 4542 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4533 4543 involved in an unresolved merge conflict, '_' closes a branch,
4534 4544 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4535 4545 changeset from the lines below is a parent of the 'o' merge on the same
4536 4546 line.
4537 4547 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4538 4548 of a '|' indicates one or more revisions in a path are omitted.
4539 4549
4540 4550 .. container:: verbose
4541 4551
4542 4552 Use -L/--line-range FILE,M:N options to follow the history of lines
4543 4553 from M to N in FILE. With -p/--patch only diff hunks affecting
4544 4554 specified line range will be shown. This option requires --follow;
4545 4555 it can be specified multiple times. Currently, this option is not
4546 4556 compatible with --graph. This option is experimental.
4547 4557
4548 4558 .. note::
4549 4559
4550 4560 :hg:`log --patch` may generate unexpected diff output for merge
4551 4561 changesets, as it will only compare the merge changeset against
4552 4562 its first parent. Also, only files different from BOTH parents
4553 4563 will appear in files:.
4554 4564
4555 4565 .. note::
4556 4566
4557 4567 For performance reasons, :hg:`log FILE` may omit duplicate changes
4558 4568 made on branches and will not show removals or mode changes. To
4559 4569 see all such changes, use the --removed switch.
4560 4570
4561 4571 .. container:: verbose
4562 4572
4563 4573 .. note::
4564 4574
4565 4575 The history resulting from -L/--line-range options depends on diff
4566 4576 options; for instance if white-spaces are ignored, respective changes
4567 4577 with only white-spaces in specified line range will not be listed.
4568 4578
4569 4579 .. container:: verbose
4570 4580
4571 4581 Some examples:
4572 4582
4573 4583 - changesets with full descriptions and file lists::
4574 4584
4575 4585 hg log -v
4576 4586
4577 4587 - changesets ancestral to the working directory::
4578 4588
4579 4589 hg log -f
4580 4590
4581 4591 - last 10 commits on the current branch::
4582 4592
4583 4593 hg log -l 10 -b .
4584 4594
4585 4595 - changesets showing all modifications of a file, including removals::
4586 4596
4587 4597 hg log --removed file.c
4588 4598
4589 4599 - all changesets that touch a directory, with diffs, excluding merges::
4590 4600
4591 4601 hg log -Mp lib/
4592 4602
4593 4603 - all revision numbers that match a keyword::
4594 4604
4595 4605 hg log -k bug --template "{rev}\\n"
4596 4606
4597 4607 - the full hash identifier of the working directory parent::
4598 4608
4599 4609 hg log -r . --template "{node}\\n"
4600 4610
4601 4611 - list available log templates::
4602 4612
4603 4613 hg log -T list
4604 4614
4605 4615 - check if a given changeset is included in a tagged release::
4606 4616
4607 4617 hg log -r "a21ccf and ancestor(1.9)"
4608 4618
4609 4619 - find all changesets by some user in a date range::
4610 4620
4611 4621 hg log -k alice -d "may 2008 to jul 2008"
4612 4622
4613 4623 - summary of all changesets after the last tag::
4614 4624
4615 4625 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4616 4626
4617 4627 - changesets touching lines 13 to 23 for file.c::
4618 4628
4619 4629 hg log -L file.c,13:23
4620 4630
4621 4631 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4622 4632 main.c with patch::
4623 4633
4624 4634 hg log -L file.c,13:23 -L main.c,2:6 -p
4625 4635
4626 4636 See :hg:`help dates` for a list of formats valid for -d/--date.
4627 4637
4628 4638 See :hg:`help revisions` for more about specifying and ordering
4629 4639 revisions.
4630 4640
4631 4641 See :hg:`help templates` for more about pre-packaged styles and
4632 4642 specifying custom templates. The default template used by the log
4633 4643 command can be customized via the ``command-templates.log`` configuration
4634 4644 setting.
4635 4645
4636 4646 Returns 0 on success.
4637 4647
4638 4648 """
4639 4649 opts = pycompat.byteskwargs(opts)
4640 4650 linerange = opts.get(b'line_range')
4641 4651
4642 4652 if linerange and not opts.get(b'follow'):
4643 4653 raise error.InputError(_(b'--line-range requires --follow'))
4644 4654
4645 4655 if linerange and pats:
4646 4656 # TODO: take pats as patterns with no line-range filter
4647 4657 raise error.InputError(
4648 4658 _(b'FILE arguments are not compatible with --line-range option')
4649 4659 )
4650 4660
4651 4661 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4652 4662 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4653 4663 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4654 4664 if linerange:
4655 4665 # TODO: should follow file history from logcmdutil._initialrevs(),
4656 4666 # then filter the result by logcmdutil._makerevset() and --limit
4657 4667 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4658 4668
4659 4669 getcopies = None
4660 4670 if opts.get(b'copies'):
4661 4671 endrev = None
4662 4672 if revs:
4663 4673 endrev = revs.max() + 1
4664 4674 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4665 4675
4666 4676 ui.pager(b'log')
4667 4677 displayer = logcmdutil.changesetdisplayer(
4668 4678 ui, repo, opts, differ, buffered=True
4669 4679 )
4670 4680 if opts.get(b'graph'):
4671 4681 displayfn = logcmdutil.displaygraphrevs
4672 4682 else:
4673 4683 displayfn = logcmdutil.displayrevs
4674 4684 displayfn(ui, repo, revs, displayer, getcopies)
4675 4685
4676 4686
4677 4687 @command(
4678 4688 b'manifest',
4679 4689 [
4680 4690 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4681 4691 (b'', b'all', False, _(b"list files from all revisions")),
4682 4692 ]
4683 4693 + formatteropts,
4684 4694 _(b'[-r REV]'),
4685 4695 helpcategory=command.CATEGORY_MAINTENANCE,
4686 4696 intents={INTENT_READONLY},
4687 4697 )
4688 4698 def manifest(ui, repo, node=None, rev=None, **opts):
4689 4699 """output the current or given revision of the project manifest
4690 4700
4691 4701 Print a list of version controlled files for the given revision.
4692 4702 If no revision is given, the first parent of the working directory
4693 4703 is used, or the null revision if no revision is checked out.
4694 4704
4695 4705 With -v, print file permissions, symlink and executable bits.
4696 4706 With --debug, print file revision hashes.
4697 4707
4698 4708 If option --all is specified, the list of all files from all revisions
4699 4709 is printed. This includes deleted and renamed files.
4700 4710
4701 4711 Returns 0 on success.
4702 4712 """
4703 4713 opts = pycompat.byteskwargs(opts)
4704 4714 fm = ui.formatter(b'manifest', opts)
4705 4715
4706 4716 if opts.get(b'all'):
4707 4717 if rev or node:
4708 4718 raise error.InputError(_(b"can't specify a revision with --all"))
4709 4719
4710 4720 res = set()
4711 4721 for rev in repo:
4712 4722 ctx = repo[rev]
4713 4723 res |= set(ctx.files())
4714 4724
4715 4725 ui.pager(b'manifest')
4716 4726 for f in sorted(res):
4717 4727 fm.startitem()
4718 4728 fm.write(b"path", b'%s\n', f)
4719 4729 fm.end()
4720 4730 return
4721 4731
4722 4732 if rev and node:
4723 4733 raise error.InputError(_(b"please specify just one revision"))
4724 4734
4725 4735 if not node:
4726 4736 node = rev
4727 4737
4728 4738 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4729 4739 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4730 4740 if node:
4731 4741 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4732 4742 ctx = scmutil.revsingle(repo, node)
4733 4743 mf = ctx.manifest()
4734 4744 ui.pager(b'manifest')
4735 4745 for f in ctx:
4736 4746 fm.startitem()
4737 4747 fm.context(ctx=ctx)
4738 4748 fl = ctx[f].flags()
4739 4749 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4740 4750 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4741 4751 fm.write(b'path', b'%s\n', f)
4742 4752 fm.end()
4743 4753
4744 4754
4745 4755 @command(
4746 4756 b'merge',
4747 4757 [
4748 4758 (
4749 4759 b'f',
4750 4760 b'force',
4751 4761 None,
4752 4762 _(b'force a merge including outstanding changes (DEPRECATED)'),
4753 4763 ),
4754 4764 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4755 4765 (
4756 4766 b'P',
4757 4767 b'preview',
4758 4768 None,
4759 4769 _(b'review revisions to merge (no merge is performed)'),
4760 4770 ),
4761 4771 (b'', b'abort', None, _(b'abort the ongoing merge')),
4762 4772 ]
4763 4773 + mergetoolopts,
4764 4774 _(b'[-P] [[-r] REV]'),
4765 4775 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4766 4776 helpbasic=True,
4767 4777 )
4768 4778 def merge(ui, repo, node=None, **opts):
4769 4779 """merge another revision into working directory
4770 4780
4771 4781 The current working directory is updated with all changes made in
4772 4782 the requested revision since the last common predecessor revision.
4773 4783
4774 4784 Files that changed between either parent are marked as changed for
4775 4785 the next commit and a commit must be performed before any further
4776 4786 updates to the repository are allowed. The next commit will have
4777 4787 two parents.
4778 4788
4779 4789 ``--tool`` can be used to specify the merge tool used for file
4780 4790 merges. It overrides the HGMERGE environment variable and your
4781 4791 configuration files. See :hg:`help merge-tools` for options.
4782 4792
4783 4793 If no revision is specified, the working directory's parent is a
4784 4794 head revision, and the current branch contains exactly one other
4785 4795 head, the other head is merged with by default. Otherwise, an
4786 4796 explicit revision with which to merge must be provided.
4787 4797
4788 4798 See :hg:`help resolve` for information on handling file conflicts.
4789 4799
4790 4800 To undo an uncommitted merge, use :hg:`merge --abort` which
4791 4801 will check out a clean copy of the original merge parent, losing
4792 4802 all changes.
4793 4803
4794 4804 Returns 0 on success, 1 if there are unresolved files.
4795 4805 """
4796 4806
4797 4807 opts = pycompat.byteskwargs(opts)
4798 4808 abort = opts.get(b'abort')
4799 4809 if abort and repo.dirstate.p2() == nullid:
4800 4810 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4801 4811 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4802 4812 if abort:
4803 4813 state = cmdutil.getunfinishedstate(repo)
4804 4814 if state and state._opname != b'merge':
4805 4815 raise error.StateError(
4806 4816 _(b'cannot abort merge with %s in progress') % (state._opname),
4807 4817 hint=state.hint(),
4808 4818 )
4809 4819 if node:
4810 4820 raise error.InputError(_(b"cannot specify a node with --abort"))
4811 4821 return hg.abortmerge(repo.ui, repo)
4812 4822
4813 4823 if opts.get(b'rev') and node:
4814 4824 raise error.InputError(_(b"please specify just one revision"))
4815 4825 if not node:
4816 4826 node = opts.get(b'rev')
4817 4827
4818 4828 if node:
4819 4829 ctx = scmutil.revsingle(repo, node)
4820 4830 else:
4821 4831 if ui.configbool(b'commands', b'merge.require-rev'):
4822 4832 raise error.InputError(
4823 4833 _(
4824 4834 b'configuration requires specifying revision to merge '
4825 4835 b'with'
4826 4836 )
4827 4837 )
4828 4838 ctx = repo[destutil.destmerge(repo)]
4829 4839
4830 4840 if ctx.node() is None:
4831 4841 raise error.InputError(
4832 4842 _(b'merging with the working copy has no effect')
4833 4843 )
4834 4844
4835 4845 if opts.get(b'preview'):
4836 4846 # find nodes that are ancestors of p2 but not of p1
4837 4847 p1 = repo[b'.'].node()
4838 4848 p2 = ctx.node()
4839 4849 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4840 4850
4841 4851 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4842 4852 for node in nodes:
4843 4853 displayer.show(repo[node])
4844 4854 displayer.close()
4845 4855 return 0
4846 4856
4847 4857 # ui.forcemerge is an internal variable, do not document
4848 4858 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4849 4859 with ui.configoverride(overrides, b'merge'):
4850 4860 force = opts.get(b'force')
4851 4861 labels = [b'working copy', b'merge rev']
4852 4862 return hg.merge(ctx, force=force, labels=labels)
4853 4863
4854 4864
4855 4865 statemod.addunfinished(
4856 4866 b'merge',
4857 4867 fname=None,
4858 4868 clearable=True,
4859 4869 allowcommit=True,
4860 4870 cmdmsg=_(b'outstanding uncommitted merge'),
4861 4871 abortfunc=hg.abortmerge,
4862 4872 statushint=_(
4863 4873 b'To continue: hg commit\nTo abort: hg merge --abort'
4864 4874 ),
4865 4875 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4866 4876 )
4867 4877
4868 4878
4869 4879 @command(
4870 4880 b'outgoing|out',
4871 4881 [
4872 4882 (
4873 4883 b'f',
4874 4884 b'force',
4875 4885 None,
4876 4886 _(b'run even when the destination is unrelated'),
4877 4887 ),
4878 4888 (
4879 4889 b'r',
4880 4890 b'rev',
4881 4891 [],
4882 4892 _(b'a changeset intended to be included in the destination'),
4883 4893 _(b'REV'),
4884 4894 ),
4885 4895 (b'n', b'newest-first', None, _(b'show newest record first')),
4886 4896 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4887 4897 (
4888 4898 b'b',
4889 4899 b'branch',
4890 4900 [],
4891 4901 _(b'a specific branch you would like to push'),
4892 4902 _(b'BRANCH'),
4893 4903 ),
4894 4904 ]
4895 4905 + logopts
4896 4906 + remoteopts
4897 4907 + subrepoopts,
4898 4908 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4899 4909 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4900 4910 )
4901 4911 def outgoing(ui, repo, dest=None, **opts):
4902 4912 """show changesets not found in the destination
4903 4913
4904 4914 Show changesets not found in the specified destination repository
4905 4915 or the default push location. These are the changesets that would
4906 4916 be pushed if a push was requested.
4907 4917
4908 4918 See pull for details of valid destination formats.
4909 4919
4910 4920 .. container:: verbose
4911 4921
4912 4922 With -B/--bookmarks, the result of bookmark comparison between
4913 4923 local and remote repositories is displayed. With -v/--verbose,
4914 4924 status is also displayed for each bookmark like below::
4915 4925
4916 4926 BM1 01234567890a added
4917 4927 BM2 deleted
4918 4928 BM3 234567890abc advanced
4919 4929 BM4 34567890abcd diverged
4920 4930 BM5 4567890abcde changed
4921 4931
4922 4932 The action taken when pushing depends on the
4923 4933 status of each bookmark:
4924 4934
4925 4935 :``added``: push with ``-B`` will create it
4926 4936 :``deleted``: push with ``-B`` will delete it
4927 4937 :``advanced``: push will update it
4928 4938 :``diverged``: push with ``-B`` will update it
4929 4939 :``changed``: push with ``-B`` will update it
4930 4940
4931 4941 From the point of view of pushing behavior, bookmarks
4932 4942 existing only in the remote repository are treated as
4933 4943 ``deleted``, even if it is in fact added remotely.
4934 4944
4935 4945 Returns 0 if there are outgoing changes, 1 otherwise.
4936 4946 """
4937 4947 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4938 4948 # style URLs, so don't overwrite dest.
4939 4949 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4940 4950 if not path:
4941 4951 raise error.ConfigError(
4942 4952 _(b'default repository not configured!'),
4943 4953 hint=_(b"see 'hg help config.paths'"),
4944 4954 )
4945 4955
4946 4956 opts = pycompat.byteskwargs(opts)
4947 4957 if opts.get(b'graph'):
4948 4958 logcmdutil.checkunsupportedgraphflags([], opts)
4949 4959 o, other = hg._outgoing(ui, repo, dest, opts)
4950 4960 if not o:
4951 4961 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4952 4962 return
4953 4963
4954 4964 revdag = logcmdutil.graphrevs(repo, o, opts)
4955 4965 ui.pager(b'outgoing')
4956 4966 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4957 4967 logcmdutil.displaygraph(
4958 4968 ui, repo, revdag, displayer, graphmod.asciiedges
4959 4969 )
4960 4970 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4961 4971 return 0
4962 4972
4963 4973 if opts.get(b'bookmarks'):
4964 4974 dest = path.pushloc or path.loc
4965 4975 other = hg.peer(repo, opts, dest)
4966 if b'bookmarks' not in other.listkeys(b'namespaces'):
4967 ui.warn(_(b"remote doesn't support bookmarks\n"))
4968 return 0
4969 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
4970 ui.pager(b'outgoing')
4971 return bookmarks.outgoing(ui, repo, other)
4976 try:
4977 if b'bookmarks' not in other.listkeys(b'namespaces'):
4978 ui.warn(_(b"remote doesn't support bookmarks\n"))
4979 return 0
4980 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
4981 ui.pager(b'outgoing')
4982 return bookmarks.outgoing(ui, repo, other)
4983 finally:
4984 other.close()
4972 4985
4973 4986 repo._subtoppath = path.pushloc or path.loc
4974 4987 try:
4975 4988 return hg.outgoing(ui, repo, dest, opts)
4976 4989 finally:
4977 4990 del repo._subtoppath
4978 4991
4979 4992
4980 4993 @command(
4981 4994 b'parents',
4982 4995 [
4983 4996 (
4984 4997 b'r',
4985 4998 b'rev',
4986 4999 b'',
4987 5000 _(b'show parents of the specified revision'),
4988 5001 _(b'REV'),
4989 5002 ),
4990 5003 ]
4991 5004 + templateopts,
4992 5005 _(b'[-r REV] [FILE]'),
4993 5006 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4994 5007 inferrepo=True,
4995 5008 )
4996 5009 def parents(ui, repo, file_=None, **opts):
4997 5010 """show the parents of the working directory or revision (DEPRECATED)
4998 5011
4999 5012 Print the working directory's parent revisions. If a revision is
5000 5013 given via -r/--rev, the parent of that revision will be printed.
5001 5014 If a file argument is given, the revision in which the file was
5002 5015 last changed (before the working directory revision or the
5003 5016 argument to --rev if given) is printed.
5004 5017
5005 5018 This command is equivalent to::
5006 5019
5007 5020 hg log -r "p1()+p2()" or
5008 5021 hg log -r "p1(REV)+p2(REV)" or
5009 5022 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5010 5023 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5011 5024
5012 5025 See :hg:`summary` and :hg:`help revsets` for related information.
5013 5026
5014 5027 Returns 0 on success.
5015 5028 """
5016 5029
5017 5030 opts = pycompat.byteskwargs(opts)
5018 5031 rev = opts.get(b'rev')
5019 5032 if rev:
5020 5033 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5021 5034 ctx = scmutil.revsingle(repo, rev, None)
5022 5035
5023 5036 if file_:
5024 5037 m = scmutil.match(ctx, (file_,), opts)
5025 5038 if m.anypats() or len(m.files()) != 1:
5026 5039 raise error.InputError(_(b'can only specify an explicit filename'))
5027 5040 file_ = m.files()[0]
5028 5041 filenodes = []
5029 5042 for cp in ctx.parents():
5030 5043 if not cp:
5031 5044 continue
5032 5045 try:
5033 5046 filenodes.append(cp.filenode(file_))
5034 5047 except error.LookupError:
5035 5048 pass
5036 5049 if not filenodes:
5037 5050 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5038 5051 p = []
5039 5052 for fn in filenodes:
5040 5053 fctx = repo.filectx(file_, fileid=fn)
5041 5054 p.append(fctx.node())
5042 5055 else:
5043 5056 p = [cp.node() for cp in ctx.parents()]
5044 5057
5045 5058 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5046 5059 for n in p:
5047 5060 if n != nullid:
5048 5061 displayer.show(repo[n])
5049 5062 displayer.close()
5050 5063
5051 5064
5052 5065 @command(
5053 5066 b'paths',
5054 5067 formatteropts,
5055 5068 _(b'[NAME]'),
5056 5069 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5057 5070 optionalrepo=True,
5058 5071 intents={INTENT_READONLY},
5059 5072 )
5060 5073 def paths(ui, repo, search=None, **opts):
5061 5074 """show aliases for remote repositories
5062 5075
5063 5076 Show definition of symbolic path name NAME. If no name is given,
5064 5077 show definition of all available names.
5065 5078
5066 5079 Option -q/--quiet suppresses all output when searching for NAME
5067 5080 and shows only the path names when listing all definitions.
5068 5081
5069 5082 Path names are defined in the [paths] section of your
5070 5083 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5071 5084 repository, ``.hg/hgrc`` is used, too.
5072 5085
5073 5086 The path names ``default`` and ``default-push`` have a special
5074 5087 meaning. When performing a push or pull operation, they are used
5075 5088 as fallbacks if no location is specified on the command-line.
5076 5089 When ``default-push`` is set, it will be used for push and
5077 5090 ``default`` will be used for pull; otherwise ``default`` is used
5078 5091 as the fallback for both. When cloning a repository, the clone
5079 5092 source is written as ``default`` in ``.hg/hgrc``.
5080 5093
5081 5094 .. note::
5082 5095
5083 5096 ``default`` and ``default-push`` apply to all inbound (e.g.
5084 5097 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5085 5098 and :hg:`bundle`) operations.
5086 5099
5087 5100 See :hg:`help urls` for more information.
5088 5101
5089 5102 .. container:: verbose
5090 5103
5091 5104 Template:
5092 5105
5093 5106 The following keywords are supported. See also :hg:`help templates`.
5094 5107
5095 5108 :name: String. Symbolic name of the path alias.
5096 5109 :pushurl: String. URL for push operations.
5097 5110 :url: String. URL or directory path for the other operations.
5098 5111
5099 5112 Returns 0 on success.
5100 5113 """
5101 5114
5102 5115 opts = pycompat.byteskwargs(opts)
5103 5116 ui.pager(b'paths')
5104 5117 if search:
5105 5118 pathitems = [
5106 5119 (name, path)
5107 5120 for name, path in pycompat.iteritems(ui.paths)
5108 5121 if name == search
5109 5122 ]
5110 5123 else:
5111 5124 pathitems = sorted(pycompat.iteritems(ui.paths))
5112 5125
5113 5126 fm = ui.formatter(b'paths', opts)
5114 5127 if fm.isplain():
5115 5128 hidepassword = util.hidepassword
5116 5129 else:
5117 5130 hidepassword = bytes
5118 5131 if ui.quiet:
5119 5132 namefmt = b'%s\n'
5120 5133 else:
5121 5134 namefmt = b'%s = '
5122 5135 showsubopts = not search and not ui.quiet
5123 5136
5124 5137 for name, path in pathitems:
5125 5138 fm.startitem()
5126 5139 fm.condwrite(not search, b'name', namefmt, name)
5127 5140 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5128 5141 for subopt, value in sorted(path.suboptions.items()):
5129 5142 assert subopt not in (b'name', b'url')
5130 5143 if showsubopts:
5131 5144 fm.plain(b'%s:%s = ' % (name, subopt))
5132 5145 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5133 5146
5134 5147 fm.end()
5135 5148
5136 5149 if search and not pathitems:
5137 5150 if not ui.quiet:
5138 5151 ui.warn(_(b"not found!\n"))
5139 5152 return 1
5140 5153 else:
5141 5154 return 0
5142 5155
5143 5156
5144 5157 @command(
5145 5158 b'phase',
5146 5159 [
5147 5160 (b'p', b'public', False, _(b'set changeset phase to public')),
5148 5161 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5149 5162 (b's', b'secret', False, _(b'set changeset phase to secret')),
5150 5163 (b'f', b'force', False, _(b'allow to move boundary backward')),
5151 5164 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5152 5165 ],
5153 5166 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5154 5167 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5155 5168 )
5156 5169 def phase(ui, repo, *revs, **opts):
5157 5170 """set or show the current phase name
5158 5171
5159 5172 With no argument, show the phase name of the current revision(s).
5160 5173
5161 5174 With one of -p/--public, -d/--draft or -s/--secret, change the
5162 5175 phase value of the specified revisions.
5163 5176
5164 5177 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5165 5178 lower phase to a higher phase. Phases are ordered as follows::
5166 5179
5167 5180 public < draft < secret
5168 5181
5169 5182 Returns 0 on success, 1 if some phases could not be changed.
5170 5183
5171 5184 (For more information about the phases concept, see :hg:`help phases`.)
5172 5185 """
5173 5186 opts = pycompat.byteskwargs(opts)
5174 5187 # search for a unique phase argument
5175 5188 targetphase = None
5176 5189 for idx, name in enumerate(phases.cmdphasenames):
5177 5190 if opts[name]:
5178 5191 if targetphase is not None:
5179 5192 raise error.InputError(_(b'only one phase can be specified'))
5180 5193 targetphase = idx
5181 5194
5182 5195 # look for specified revision
5183 5196 revs = list(revs)
5184 5197 revs.extend(opts[b'rev'])
5185 5198 if not revs:
5186 5199 # display both parents as the second parent phase can influence
5187 5200 # the phase of a merge commit
5188 5201 revs = [c.rev() for c in repo[None].parents()]
5189 5202
5190 5203 revs = scmutil.revrange(repo, revs)
5191 5204
5192 5205 ret = 0
5193 5206 if targetphase is None:
5194 5207 # display
5195 5208 for r in revs:
5196 5209 ctx = repo[r]
5197 5210 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5198 5211 else:
5199 5212 with repo.lock(), repo.transaction(b"phase") as tr:
5200 5213 # set phase
5201 5214 if not revs:
5202 5215 raise error.InputError(_(b'empty revision set'))
5203 5216 nodes = [repo[r].node() for r in revs]
5204 5217 # moving revision from public to draft may hide them
5205 5218 # We have to check result on an unfiltered repository
5206 5219 unfi = repo.unfiltered()
5207 5220 getphase = unfi._phasecache.phase
5208 5221 olddata = [getphase(unfi, r) for r in unfi]
5209 5222 phases.advanceboundary(repo, tr, targetphase, nodes)
5210 5223 if opts[b'force']:
5211 5224 phases.retractboundary(repo, tr, targetphase, nodes)
5212 5225 getphase = unfi._phasecache.phase
5213 5226 newdata = [getphase(unfi, r) for r in unfi]
5214 5227 changes = sum(newdata[r] != olddata[r] for r in unfi)
5215 5228 cl = unfi.changelog
5216 5229 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5217 5230 if rejected:
5218 5231 ui.warn(
5219 5232 _(
5220 5233 b'cannot move %i changesets to a higher '
5221 5234 b'phase, use --force\n'
5222 5235 )
5223 5236 % len(rejected)
5224 5237 )
5225 5238 ret = 1
5226 5239 if changes:
5227 5240 msg = _(b'phase changed for %i changesets\n') % changes
5228 5241 if ret:
5229 5242 ui.status(msg)
5230 5243 else:
5231 5244 ui.note(msg)
5232 5245 else:
5233 5246 ui.warn(_(b'no phases changed\n'))
5234 5247 return ret
5235 5248
5236 5249
5237 5250 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5238 5251 """Run after a changegroup has been added via pull/unbundle
5239 5252
5240 5253 This takes arguments below:
5241 5254
5242 5255 :modheads: change of heads by pull/unbundle
5243 5256 :optupdate: updating working directory is needed or not
5244 5257 :checkout: update destination revision (or None to default destination)
5245 5258 :brev: a name, which might be a bookmark to be activated after updating
5246 5259 """
5247 5260 if modheads == 0:
5248 5261 return
5249 5262 if optupdate:
5250 5263 try:
5251 5264 return hg.updatetotally(ui, repo, checkout, brev)
5252 5265 except error.UpdateAbort as inst:
5253 5266 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5254 5267 hint = inst.hint
5255 5268 raise error.UpdateAbort(msg, hint=hint)
5256 5269 if modheads is not None and modheads > 1:
5257 5270 currentbranchheads = len(repo.branchheads())
5258 5271 if currentbranchheads == modheads:
5259 5272 ui.status(
5260 5273 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5261 5274 )
5262 5275 elif currentbranchheads > 1:
5263 5276 ui.status(
5264 5277 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5265 5278 )
5266 5279 else:
5267 5280 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5268 5281 elif not ui.configbool(b'commands', b'update.requiredest'):
5269 5282 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5270 5283
5271 5284
5272 5285 @command(
5273 5286 b'pull',
5274 5287 [
5275 5288 (
5276 5289 b'u',
5277 5290 b'update',
5278 5291 None,
5279 5292 _(b'update to new branch head if new descendants were pulled'),
5280 5293 ),
5281 5294 (
5282 5295 b'f',
5283 5296 b'force',
5284 5297 None,
5285 5298 _(b'run even when remote repository is unrelated'),
5286 5299 ),
5287 5300 (
5288 5301 b'',
5289 5302 b'confirm',
5290 5303 None,
5291 5304 _(b'confirm pull before applying changes'),
5292 5305 ),
5293 5306 (
5294 5307 b'r',
5295 5308 b'rev',
5296 5309 [],
5297 5310 _(b'a remote changeset intended to be added'),
5298 5311 _(b'REV'),
5299 5312 ),
5300 5313 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5301 5314 (
5302 5315 b'b',
5303 5316 b'branch',
5304 5317 [],
5305 5318 _(b'a specific branch you would like to pull'),
5306 5319 _(b'BRANCH'),
5307 5320 ),
5308 5321 ]
5309 5322 + remoteopts,
5310 5323 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5311 5324 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5312 5325 helpbasic=True,
5313 5326 )
5314 5327 def pull(ui, repo, source=b"default", **opts):
5315 5328 """pull changes from the specified source
5316 5329
5317 5330 Pull changes from a remote repository to a local one.
5318 5331
5319 5332 This finds all changes from the repository at the specified path
5320 5333 or URL and adds them to a local repository (the current one unless
5321 5334 -R is specified). By default, this does not update the copy of the
5322 5335 project in the working directory.
5323 5336
5324 5337 When cloning from servers that support it, Mercurial may fetch
5325 5338 pre-generated data. When this is done, hooks operating on incoming
5326 5339 changesets and changegroups may fire more than once, once for each
5327 5340 pre-generated bundle and as well as for any additional remaining
5328 5341 data. See :hg:`help -e clonebundles` for more.
5329 5342
5330 5343 Use :hg:`incoming` if you want to see what would have been added
5331 5344 by a pull at the time you issued this command. If you then decide
5332 5345 to add those changes to the repository, you should use :hg:`pull
5333 5346 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5334 5347
5335 5348 If SOURCE is omitted, the 'default' path will be used.
5336 5349 See :hg:`help urls` for more information.
5337 5350
5338 5351 Specifying bookmark as ``.`` is equivalent to specifying the active
5339 5352 bookmark's name.
5340 5353
5341 5354 Returns 0 on success, 1 if an update had unresolved files.
5342 5355 """
5343 5356
5344 5357 opts = pycompat.byteskwargs(opts)
5345 5358 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5346 5359 b'update'
5347 5360 ):
5348 5361 msg = _(b'update destination required by configuration')
5349 5362 hint = _(b'use hg pull followed by hg update DEST')
5350 5363 raise error.InputError(msg, hint=hint)
5351 5364
5352 5365 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5353 5366 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5354 5367 ui.flush()
5355 5368 other = hg.peer(repo, opts, source)
5356 5369 try:
5357 5370 revs, checkout = hg.addbranchrevs(
5358 5371 repo, other, branches, opts.get(b'rev')
5359 5372 )
5360 5373
5361 5374 pullopargs = {}
5362 5375
5363 5376 nodes = None
5364 5377 if opts.get(b'bookmark') or revs:
5365 5378 # The list of bookmark used here is the same used to actually update
5366 5379 # the bookmark names, to avoid the race from issue 4689 and we do
5367 5380 # all lookup and bookmark queries in one go so they see the same
5368 5381 # version of the server state (issue 4700).
5369 5382 nodes = []
5370 5383 fnodes = []
5371 5384 revs = revs or []
5372 5385 if revs and not other.capable(b'lookup'):
5373 5386 err = _(
5374 5387 b"other repository doesn't support revision lookup, "
5375 5388 b"so a rev cannot be specified."
5376 5389 )
5377 5390 raise error.Abort(err)
5378 5391 with other.commandexecutor() as e:
5379 5392 fremotebookmarks = e.callcommand(
5380 5393 b'listkeys', {b'namespace': b'bookmarks'}
5381 5394 )
5382 5395 for r in revs:
5383 5396 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5384 5397 remotebookmarks = fremotebookmarks.result()
5385 5398 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5386 5399 pullopargs[b'remotebookmarks'] = remotebookmarks
5387 5400 for b in opts.get(b'bookmark', []):
5388 5401 b = repo._bookmarks.expandname(b)
5389 5402 if b not in remotebookmarks:
5390 5403 raise error.InputError(
5391 5404 _(b'remote bookmark %s not found!') % b
5392 5405 )
5393 5406 nodes.append(remotebookmarks[b])
5394 5407 for i, rev in enumerate(revs):
5395 5408 node = fnodes[i].result()
5396 5409 nodes.append(node)
5397 5410 if rev == checkout:
5398 5411 checkout = node
5399 5412
5400 5413 wlock = util.nullcontextmanager()
5401 5414 if opts.get(b'update'):
5402 5415 wlock = repo.wlock()
5403 5416 with wlock:
5404 5417 pullopargs.update(opts.get(b'opargs', {}))
5405 5418 modheads = exchange.pull(
5406 5419 repo,
5407 5420 other,
5408 5421 heads=nodes,
5409 5422 force=opts.get(b'force'),
5410 5423 bookmarks=opts.get(b'bookmark', ()),
5411 5424 opargs=pullopargs,
5412 5425 confirm=opts.get(b'confirm'),
5413 5426 ).cgresult
5414 5427
5415 5428 # brev is a name, which might be a bookmark to be activated at
5416 5429 # the end of the update. In other words, it is an explicit
5417 5430 # destination of the update
5418 5431 brev = None
5419 5432
5420 5433 if checkout:
5421 5434 checkout = repo.unfiltered().changelog.rev(checkout)
5422 5435
5423 5436 # order below depends on implementation of
5424 5437 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5425 5438 # because 'checkout' is determined without it.
5426 5439 if opts.get(b'rev'):
5427 5440 brev = opts[b'rev'][0]
5428 5441 elif opts.get(b'branch'):
5429 5442 brev = opts[b'branch'][0]
5430 5443 else:
5431 5444 brev = branches[0]
5432 5445 repo._subtoppath = source
5433 5446 try:
5434 5447 ret = postincoming(
5435 5448 ui, repo, modheads, opts.get(b'update'), checkout, brev
5436 5449 )
5437 5450 except error.FilteredRepoLookupError as exc:
5438 5451 msg = _(b'cannot update to target: %s') % exc.args[0]
5439 5452 exc.args = (msg,) + exc.args[1:]
5440 5453 raise
5441 5454 finally:
5442 5455 del repo._subtoppath
5443 5456
5444 5457 finally:
5445 5458 other.close()
5446 5459 return ret
5447 5460
5448 5461
5449 5462 @command(
5450 5463 b'purge|clean',
5451 5464 [
5452 5465 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5453 5466 (b'', b'all', None, _(b'purge ignored files too')),
5454 5467 (b'i', b'ignored', None, _(b'purge only ignored files')),
5455 5468 (b'', b'dirs', None, _(b'purge empty directories')),
5456 5469 (b'', b'files', None, _(b'purge files')),
5457 5470 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5458 5471 (
5459 5472 b'0',
5460 5473 b'print0',
5461 5474 None,
5462 5475 _(
5463 5476 b'end filenames with NUL, for use with xargs'
5464 5477 b' (implies -p/--print)'
5465 5478 ),
5466 5479 ),
5467 5480 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5468 5481 ]
5469 5482 + cmdutil.walkopts,
5470 5483 _(b'hg purge [OPTION]... [DIR]...'),
5471 5484 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5472 5485 )
5473 5486 def purge(ui, repo, *dirs, **opts):
5474 5487 """removes files not tracked by Mercurial
5475 5488
5476 5489 Delete files not known to Mercurial. This is useful to test local
5477 5490 and uncommitted changes in an otherwise-clean source tree.
5478 5491
5479 5492 This means that purge will delete the following by default:
5480 5493
5481 5494 - Unknown files: files marked with "?" by :hg:`status`
5482 5495 - Empty directories: in fact Mercurial ignores directories unless
5483 5496 they contain files under source control management
5484 5497
5485 5498 But it will leave untouched:
5486 5499
5487 5500 - Modified and unmodified tracked files
5488 5501 - Ignored files (unless -i or --all is specified)
5489 5502 - New files added to the repository (with :hg:`add`)
5490 5503
5491 5504 The --files and --dirs options can be used to direct purge to delete
5492 5505 only files, only directories, or both. If neither option is given,
5493 5506 both will be deleted.
5494 5507
5495 5508 If directories are given on the command line, only files in these
5496 5509 directories are considered.
5497 5510
5498 5511 Be careful with purge, as you could irreversibly delete some files
5499 5512 you forgot to add to the repository. If you only want to print the
5500 5513 list of files that this program would delete, use the --print
5501 5514 option.
5502 5515 """
5503 5516 opts = pycompat.byteskwargs(opts)
5504 5517 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5505 5518
5506 5519 act = not opts.get(b'print')
5507 5520 eol = b'\n'
5508 5521 if opts.get(b'print0'):
5509 5522 eol = b'\0'
5510 5523 act = False # --print0 implies --print
5511 5524 if opts.get(b'all', False):
5512 5525 ignored = True
5513 5526 unknown = True
5514 5527 else:
5515 5528 ignored = opts.get(b'ignored', False)
5516 5529 unknown = not ignored
5517 5530
5518 5531 removefiles = opts.get(b'files')
5519 5532 removedirs = opts.get(b'dirs')
5520 5533 confirm = opts.get(b'confirm')
5521 5534 if confirm is None:
5522 5535 try:
5523 5536 extensions.find(b'purge')
5524 5537 confirm = False
5525 5538 except KeyError:
5526 5539 confirm = True
5527 5540
5528 5541 if not removefiles and not removedirs:
5529 5542 removefiles = True
5530 5543 removedirs = True
5531 5544
5532 5545 match = scmutil.match(repo[None], dirs, opts)
5533 5546
5534 5547 paths = mergemod.purge(
5535 5548 repo,
5536 5549 match,
5537 5550 unknown=unknown,
5538 5551 ignored=ignored,
5539 5552 removeemptydirs=removedirs,
5540 5553 removefiles=removefiles,
5541 5554 abortonerror=opts.get(b'abort_on_err'),
5542 5555 noop=not act,
5543 5556 confirm=confirm,
5544 5557 )
5545 5558
5546 5559 for path in paths:
5547 5560 if not act:
5548 5561 ui.write(b'%s%s' % (path, eol))
5549 5562
5550 5563
5551 5564 @command(
5552 5565 b'push',
5553 5566 [
5554 5567 (b'f', b'force', None, _(b'force push')),
5555 5568 (
5556 5569 b'r',
5557 5570 b'rev',
5558 5571 [],
5559 5572 _(b'a changeset intended to be included in the destination'),
5560 5573 _(b'REV'),
5561 5574 ),
5562 5575 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5563 5576 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5564 5577 (
5565 5578 b'b',
5566 5579 b'branch',
5567 5580 [],
5568 5581 _(b'a specific branch you would like to push'),
5569 5582 _(b'BRANCH'),
5570 5583 ),
5571 5584 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5572 5585 (
5573 5586 b'',
5574 5587 b'pushvars',
5575 5588 [],
5576 5589 _(b'variables that can be sent to server (ADVANCED)'),
5577 5590 ),
5578 5591 (
5579 5592 b'',
5580 5593 b'publish',
5581 5594 False,
5582 5595 _(b'push the changeset as public (EXPERIMENTAL)'),
5583 5596 ),
5584 5597 ]
5585 5598 + remoteopts,
5586 5599 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5587 5600 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5588 5601 helpbasic=True,
5589 5602 )
5590 5603 def push(ui, repo, dest=None, **opts):
5591 5604 """push changes to the specified destination
5592 5605
5593 5606 Push changesets from the local repository to the specified
5594 5607 destination.
5595 5608
5596 5609 This operation is symmetrical to pull: it is identical to a pull
5597 5610 in the destination repository from the current one.
5598 5611
5599 5612 By default, push will not allow creation of new heads at the
5600 5613 destination, since multiple heads would make it unclear which head
5601 5614 to use. In this situation, it is recommended to pull and merge
5602 5615 before pushing.
5603 5616
5604 5617 Use --new-branch if you want to allow push to create a new named
5605 5618 branch that is not present at the destination. This allows you to
5606 5619 only create a new branch without forcing other changes.
5607 5620
5608 5621 .. note::
5609 5622
5610 5623 Extra care should be taken with the -f/--force option,
5611 5624 which will push all new heads on all branches, an action which will
5612 5625 almost always cause confusion for collaborators.
5613 5626
5614 5627 If -r/--rev is used, the specified revision and all its ancestors
5615 5628 will be pushed to the remote repository.
5616 5629
5617 5630 If -B/--bookmark is used, the specified bookmarked revision, its
5618 5631 ancestors, and the bookmark will be pushed to the remote
5619 5632 repository. Specifying ``.`` is equivalent to specifying the active
5620 5633 bookmark's name. Use the --all-bookmarks option for pushing all
5621 5634 current bookmarks.
5622 5635
5623 5636 Please see :hg:`help urls` for important details about ``ssh://``
5624 5637 URLs. If DESTINATION is omitted, a default path will be used.
5625 5638
5626 5639 .. container:: verbose
5627 5640
5628 5641 The --pushvars option sends strings to the server that become
5629 5642 environment variables prepended with ``HG_USERVAR_``. For example,
5630 5643 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5631 5644 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5632 5645
5633 5646 pushvars can provide for user-overridable hooks as well as set debug
5634 5647 levels. One example is having a hook that blocks commits containing
5635 5648 conflict markers, but enables the user to override the hook if the file
5636 5649 is using conflict markers for testing purposes or the file format has
5637 5650 strings that look like conflict markers.
5638 5651
5639 5652 By default, servers will ignore `--pushvars`. To enable it add the
5640 5653 following to your configuration file::
5641 5654
5642 5655 [push]
5643 5656 pushvars.server = true
5644 5657
5645 5658 Returns 0 if push was successful, 1 if nothing to push.
5646 5659 """
5647 5660
5648 5661 opts = pycompat.byteskwargs(opts)
5649 5662
5650 5663 if opts.get(b'all_bookmarks'):
5651 5664 cmdutil.check_incompatible_arguments(
5652 5665 opts,
5653 5666 b'all_bookmarks',
5654 5667 [b'bookmark', b'rev'],
5655 5668 )
5656 5669 opts[b'bookmark'] = list(repo._bookmarks)
5657 5670
5658 5671 if opts.get(b'bookmark'):
5659 5672 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5660 5673 for b in opts[b'bookmark']:
5661 5674 # translate -B options to -r so changesets get pushed
5662 5675 b = repo._bookmarks.expandname(b)
5663 5676 if b in repo._bookmarks:
5664 5677 opts.setdefault(b'rev', []).append(b)
5665 5678 else:
5666 5679 # if we try to push a deleted bookmark, translate it to null
5667 5680 # this lets simultaneous -r, -b options continue working
5668 5681 opts.setdefault(b'rev', []).append(b"null")
5669 5682
5670 5683 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5671 5684 if not path:
5672 5685 raise error.ConfigError(
5673 5686 _(b'default repository not configured!'),
5674 5687 hint=_(b"see 'hg help config.paths'"),
5675 5688 )
5676 5689 dest = path.pushloc or path.loc
5677 5690 branches = (path.branch, opts.get(b'branch') or [])
5678 5691 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5679 5692 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5680 5693 other = hg.peer(repo, opts, dest)
5681 5694
5682 if revs:
5683 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5684 if not revs:
5695 try:
5696 if revs:
5697 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5698 if not revs:
5699 raise error.InputError(
5700 _(b"specified revisions evaluate to an empty set"),
5701 hint=_(b"use different revision arguments"),
5702 )
5703 elif path.pushrev:
5704 # It doesn't make any sense to specify ancestor revisions. So limit
5705 # to DAG heads to make discovery simpler.
5706 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5707 revs = scmutil.revrange(repo, [expr])
5708 revs = [repo[rev].node() for rev in revs]
5709 if not revs:
5710 raise error.InputError(
5711 _(b'default push revset for path evaluates to an empty set')
5712 )
5713 elif ui.configbool(b'commands', b'push.require-revs'):
5685 5714 raise error.InputError(
5686 _(b"specified revisions evaluate to an empty set"),
5687 hint=_(b"use different revision arguments"),
5715 _(b'no revisions specified to push'),
5716 hint=_(b'did you mean "hg push -r ."?'),
5688 5717 )
5689 elif path.pushrev:
5690 # It doesn't make any sense to specify ancestor revisions. So limit
5691 # to DAG heads to make discovery simpler.
5692 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5693 revs = scmutil.revrange(repo, [expr])
5694 revs = [repo[rev].node() for rev in revs]
5695 if not revs:
5696 raise error.InputError(
5697 _(b'default push revset for path evaluates to an empty set')
5698 )
5699 elif ui.configbool(b'commands', b'push.require-revs'):
5700 raise error.InputError(
5701 _(b'no revisions specified to push'),
5702 hint=_(b'did you mean "hg push -r ."?'),
5718
5719 repo._subtoppath = dest
5720 try:
5721 # push subrepos depth-first for coherent ordering
5722 c = repo[b'.']
5723 subs = c.substate # only repos that are committed
5724 for s in sorted(subs):
5725 result = c.sub(s).push(opts)
5726 if result == 0:
5727 return not result
5728 finally:
5729 del repo._subtoppath
5730
5731 opargs = dict(
5732 opts.get(b'opargs', {})
5733 ) # copy opargs since we may mutate it
5734 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5735
5736 pushop = exchange.push(
5737 repo,
5738 other,
5739 opts.get(b'force'),
5740 revs=revs,
5741 newbranch=opts.get(b'new_branch'),
5742 bookmarks=opts.get(b'bookmark', ()),
5743 publish=opts.get(b'publish'),
5744 opargs=opargs,
5703 5745 )
5704 5746
5705 repo._subtoppath = dest
5706 try:
5707 # push subrepos depth-first for coherent ordering
5708 c = repo[b'.']
5709 subs = c.substate # only repos that are committed
5710 for s in sorted(subs):
5711 result = c.sub(s).push(opts)
5712 if result == 0:
5713 return not result
5747 result = not pushop.cgresult
5748
5749 if pushop.bkresult is not None:
5750 if pushop.bkresult == 2:
5751 result = 2
5752 elif not result and pushop.bkresult:
5753 result = 2
5714 5754 finally:
5715 del repo._subtoppath
5716
5717 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5718 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5719
5720 pushop = exchange.push(
5721 repo,
5722 other,
5723 opts.get(b'force'),
5724 revs=revs,
5725 newbranch=opts.get(b'new_branch'),
5726 bookmarks=opts.get(b'bookmark', ()),
5727 publish=opts.get(b'publish'),
5728 opargs=opargs,
5729 )
5730
5731 result = not pushop.cgresult
5732
5733 if pushop.bkresult is not None:
5734 if pushop.bkresult == 2:
5735 result = 2
5736 elif not result and pushop.bkresult:
5737 result = 2
5738
5755 other.close()
5739 5756 return result
5740 5757
5741 5758
5742 5759 @command(
5743 5760 b'recover',
5744 5761 [
5745 5762 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5746 5763 ],
5747 5764 helpcategory=command.CATEGORY_MAINTENANCE,
5748 5765 )
5749 5766 def recover(ui, repo, **opts):
5750 5767 """roll back an interrupted transaction
5751 5768
5752 5769 Recover from an interrupted commit or pull.
5753 5770
5754 5771 This command tries to fix the repository status after an
5755 5772 interrupted operation. It should only be necessary when Mercurial
5756 5773 suggests it.
5757 5774
5758 5775 Returns 0 if successful, 1 if nothing to recover or verify fails.
5759 5776 """
5760 5777 ret = repo.recover()
5761 5778 if ret:
5762 5779 if opts['verify']:
5763 5780 return hg.verify(repo)
5764 5781 else:
5765 5782 msg = _(
5766 5783 b"(verify step skipped, run `hg verify` to check your "
5767 5784 b"repository content)\n"
5768 5785 )
5769 5786 ui.warn(msg)
5770 5787 return 0
5771 5788 return 1
5772 5789
5773 5790
5774 5791 @command(
5775 5792 b'remove|rm',
5776 5793 [
5777 5794 (b'A', b'after', None, _(b'record delete for missing files')),
5778 5795 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5779 5796 ]
5780 5797 + subrepoopts
5781 5798 + walkopts
5782 5799 + dryrunopts,
5783 5800 _(b'[OPTION]... FILE...'),
5784 5801 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5785 5802 helpbasic=True,
5786 5803 inferrepo=True,
5787 5804 )
5788 5805 def remove(ui, repo, *pats, **opts):
5789 5806 """remove the specified files on the next commit
5790 5807
5791 5808 Schedule the indicated files for removal from the current branch.
5792 5809
5793 5810 This command schedules the files to be removed at the next commit.
5794 5811 To undo a remove before that, see :hg:`revert`. To undo added
5795 5812 files, see :hg:`forget`.
5796 5813
5797 5814 .. container:: verbose
5798 5815
5799 5816 -A/--after can be used to remove only files that have already
5800 5817 been deleted, -f/--force can be used to force deletion, and -Af
5801 5818 can be used to remove files from the next revision without
5802 5819 deleting them from the working directory.
5803 5820
5804 5821 The following table details the behavior of remove for different
5805 5822 file states (columns) and option combinations (rows). The file
5806 5823 states are Added [A], Clean [C], Modified [M] and Missing [!]
5807 5824 (as reported by :hg:`status`). The actions are Warn, Remove
5808 5825 (from branch) and Delete (from disk):
5809 5826
5810 5827 ========= == == == ==
5811 5828 opt/state A C M !
5812 5829 ========= == == == ==
5813 5830 none W RD W R
5814 5831 -f R RD RD R
5815 5832 -A W W W R
5816 5833 -Af R R R R
5817 5834 ========= == == == ==
5818 5835
5819 5836 .. note::
5820 5837
5821 5838 :hg:`remove` never deletes files in Added [A] state from the
5822 5839 working directory, not even if ``--force`` is specified.
5823 5840
5824 5841 Returns 0 on success, 1 if any warnings encountered.
5825 5842 """
5826 5843
5827 5844 opts = pycompat.byteskwargs(opts)
5828 5845 after, force = opts.get(b'after'), opts.get(b'force')
5829 5846 dryrun = opts.get(b'dry_run')
5830 5847 if not pats and not after:
5831 5848 raise error.InputError(_(b'no files specified'))
5832 5849
5833 5850 m = scmutil.match(repo[None], pats, opts)
5834 5851 subrepos = opts.get(b'subrepos')
5835 5852 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5836 5853 return cmdutil.remove(
5837 5854 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5838 5855 )
5839 5856
5840 5857
5841 5858 @command(
5842 5859 b'rename|move|mv',
5843 5860 [
5844 5861 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5845 5862 (
5846 5863 b'',
5847 5864 b'at-rev',
5848 5865 b'',
5849 5866 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5850 5867 _(b'REV'),
5851 5868 ),
5852 5869 (
5853 5870 b'f',
5854 5871 b'force',
5855 5872 None,
5856 5873 _(b'forcibly move over an existing managed file'),
5857 5874 ),
5858 5875 ]
5859 5876 + walkopts
5860 5877 + dryrunopts,
5861 5878 _(b'[OPTION]... SOURCE... DEST'),
5862 5879 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5863 5880 )
5864 5881 def rename(ui, repo, *pats, **opts):
5865 5882 """rename files; equivalent of copy + remove
5866 5883
5867 5884 Mark dest as copies of sources; mark sources for deletion. If dest
5868 5885 is a directory, copies are put in that directory. If dest is a
5869 5886 file, there can only be one source.
5870 5887
5871 5888 By default, this command copies the contents of files as they
5872 5889 exist in the working directory. If invoked with -A/--after, the
5873 5890 operation is recorded, but no copying is performed.
5874 5891
5875 5892 This command takes effect at the next commit. To undo a rename
5876 5893 before that, see :hg:`revert`.
5877 5894
5878 5895 Returns 0 on success, 1 if errors are encountered.
5879 5896 """
5880 5897 opts = pycompat.byteskwargs(opts)
5881 5898 with repo.wlock():
5882 5899 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5883 5900
5884 5901
5885 5902 @command(
5886 5903 b'resolve',
5887 5904 [
5888 5905 (b'a', b'all', None, _(b'select all unresolved files')),
5889 5906 (b'l', b'list', None, _(b'list state of files needing merge')),
5890 5907 (b'm', b'mark', None, _(b'mark files as resolved')),
5891 5908 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5892 5909 (b'n', b'no-status', None, _(b'hide status prefix')),
5893 5910 (b'', b're-merge', None, _(b're-merge files')),
5894 5911 ]
5895 5912 + mergetoolopts
5896 5913 + walkopts
5897 5914 + formatteropts,
5898 5915 _(b'[OPTION]... [FILE]...'),
5899 5916 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5900 5917 inferrepo=True,
5901 5918 )
5902 5919 def resolve(ui, repo, *pats, **opts):
5903 5920 """redo merges or set/view the merge status of files
5904 5921
5905 5922 Merges with unresolved conflicts are often the result of
5906 5923 non-interactive merging using the ``internal:merge`` configuration
5907 5924 setting, or a command-line merge tool like ``diff3``. The resolve
5908 5925 command is used to manage the files involved in a merge, after
5909 5926 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5910 5927 working directory must have two parents). See :hg:`help
5911 5928 merge-tools` for information on configuring merge tools.
5912 5929
5913 5930 The resolve command can be used in the following ways:
5914 5931
5915 5932 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5916 5933 the specified files, discarding any previous merge attempts. Re-merging
5917 5934 is not performed for files already marked as resolved. Use ``--all/-a``
5918 5935 to select all unresolved files. ``--tool`` can be used to specify
5919 5936 the merge tool used for the given files. It overrides the HGMERGE
5920 5937 environment variable and your configuration files. Previous file
5921 5938 contents are saved with a ``.orig`` suffix.
5922 5939
5923 5940 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5924 5941 (e.g. after having manually fixed-up the files). The default is
5925 5942 to mark all unresolved files.
5926 5943
5927 5944 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5928 5945 default is to mark all resolved files.
5929 5946
5930 5947 - :hg:`resolve -l`: list files which had or still have conflicts.
5931 5948 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5932 5949 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5933 5950 the list. See :hg:`help filesets` for details.
5934 5951
5935 5952 .. note::
5936 5953
5937 5954 Mercurial will not let you commit files with unresolved merge
5938 5955 conflicts. You must use :hg:`resolve -m ...` before you can
5939 5956 commit after a conflicting merge.
5940 5957
5941 5958 .. container:: verbose
5942 5959
5943 5960 Template:
5944 5961
5945 5962 The following keywords are supported in addition to the common template
5946 5963 keywords and functions. See also :hg:`help templates`.
5947 5964
5948 5965 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5949 5966 :path: String. Repository-absolute path of the file.
5950 5967
5951 5968 Returns 0 on success, 1 if any files fail a resolve attempt.
5952 5969 """
5953 5970
5954 5971 opts = pycompat.byteskwargs(opts)
5955 5972 confirm = ui.configbool(b'commands', b'resolve.confirm')
5956 5973 flaglist = b'all mark unmark list no_status re_merge'.split()
5957 5974 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5958 5975
5959 5976 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5960 5977 if actioncount > 1:
5961 5978 raise error.InputError(_(b"too many actions specified"))
5962 5979 elif actioncount == 0 and ui.configbool(
5963 5980 b'commands', b'resolve.explicit-re-merge'
5964 5981 ):
5965 5982 hint = _(b'use --mark, --unmark, --list or --re-merge')
5966 5983 raise error.InputError(_(b'no action specified'), hint=hint)
5967 5984 if pats and all:
5968 5985 raise error.InputError(_(b"can't specify --all and patterns"))
5969 5986 if not (all or pats or show or mark or unmark):
5970 5987 raise error.InputError(
5971 5988 _(b'no files or directories specified'),
5972 5989 hint=b'use --all to re-merge all unresolved files',
5973 5990 )
5974 5991
5975 5992 if confirm:
5976 5993 if all:
5977 5994 if ui.promptchoice(
5978 5995 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5979 5996 ):
5980 5997 raise error.CanceledError(_(b'user quit'))
5981 5998 if mark and not pats:
5982 5999 if ui.promptchoice(
5983 6000 _(
5984 6001 b'mark all unresolved files as resolved (yn)?'
5985 6002 b'$$ &Yes $$ &No'
5986 6003 )
5987 6004 ):
5988 6005 raise error.CanceledError(_(b'user quit'))
5989 6006 if unmark and not pats:
5990 6007 if ui.promptchoice(
5991 6008 _(
5992 6009 b'mark all resolved files as unresolved (yn)?'
5993 6010 b'$$ &Yes $$ &No'
5994 6011 )
5995 6012 ):
5996 6013 raise error.CanceledError(_(b'user quit'))
5997 6014
5998 6015 uipathfn = scmutil.getuipathfn(repo)
5999 6016
6000 6017 if show:
6001 6018 ui.pager(b'resolve')
6002 6019 fm = ui.formatter(b'resolve', opts)
6003 6020 ms = mergestatemod.mergestate.read(repo)
6004 6021 wctx = repo[None]
6005 6022 m = scmutil.match(wctx, pats, opts)
6006 6023
6007 6024 # Labels and keys based on merge state. Unresolved path conflicts show
6008 6025 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6009 6026 # resolved conflicts.
6010 6027 mergestateinfo = {
6011 6028 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6012 6029 b'resolve.unresolved',
6013 6030 b'U',
6014 6031 ),
6015 6032 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6016 6033 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6017 6034 b'resolve.unresolved',
6018 6035 b'P',
6019 6036 ),
6020 6037 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6021 6038 b'resolve.resolved',
6022 6039 b'R',
6023 6040 ),
6024 6041 }
6025 6042
6026 6043 for f in ms:
6027 6044 if not m(f):
6028 6045 continue
6029 6046
6030 6047 label, key = mergestateinfo[ms[f]]
6031 6048 fm.startitem()
6032 6049 fm.context(ctx=wctx)
6033 6050 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6034 6051 fm.data(path=f)
6035 6052 fm.plain(b'%s\n' % uipathfn(f), label=label)
6036 6053 fm.end()
6037 6054 return 0
6038 6055
6039 6056 with repo.wlock():
6040 6057 ms = mergestatemod.mergestate.read(repo)
6041 6058
6042 6059 if not (ms.active() or repo.dirstate.p2() != nullid):
6043 6060 raise error.StateError(
6044 6061 _(b'resolve command not applicable when not merging')
6045 6062 )
6046 6063
6047 6064 wctx = repo[None]
6048 6065 m = scmutil.match(wctx, pats, opts)
6049 6066 ret = 0
6050 6067 didwork = False
6051 6068
6052 6069 tocomplete = []
6053 6070 hasconflictmarkers = []
6054 6071 if mark:
6055 6072 markcheck = ui.config(b'commands', b'resolve.mark-check')
6056 6073 if markcheck not in [b'warn', b'abort']:
6057 6074 # Treat all invalid / unrecognized values as 'none'.
6058 6075 markcheck = False
6059 6076 for f in ms:
6060 6077 if not m(f):
6061 6078 continue
6062 6079
6063 6080 didwork = True
6064 6081
6065 6082 # path conflicts must be resolved manually
6066 6083 if ms[f] in (
6067 6084 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6068 6085 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6069 6086 ):
6070 6087 if mark:
6071 6088 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6072 6089 elif unmark:
6073 6090 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6074 6091 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6075 6092 ui.warn(
6076 6093 _(b'%s: path conflict must be resolved manually\n')
6077 6094 % uipathfn(f)
6078 6095 )
6079 6096 continue
6080 6097
6081 6098 if mark:
6082 6099 if markcheck:
6083 6100 fdata = repo.wvfs.tryread(f)
6084 6101 if (
6085 6102 filemerge.hasconflictmarkers(fdata)
6086 6103 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6087 6104 ):
6088 6105 hasconflictmarkers.append(f)
6089 6106 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6090 6107 elif unmark:
6091 6108 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6092 6109 else:
6093 6110 # backup pre-resolve (merge uses .orig for its own purposes)
6094 6111 a = repo.wjoin(f)
6095 6112 try:
6096 6113 util.copyfile(a, a + b".resolve")
6097 6114 except (IOError, OSError) as inst:
6098 6115 if inst.errno != errno.ENOENT:
6099 6116 raise
6100 6117
6101 6118 try:
6102 6119 # preresolve file
6103 6120 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6104 6121 with ui.configoverride(overrides, b'resolve'):
6105 6122 complete, r = ms.preresolve(f, wctx)
6106 6123 if not complete:
6107 6124 tocomplete.append(f)
6108 6125 elif r:
6109 6126 ret = 1
6110 6127 finally:
6111 6128 ms.commit()
6112 6129
6113 6130 # replace filemerge's .orig file with our resolve file, but only
6114 6131 # for merges that are complete
6115 6132 if complete:
6116 6133 try:
6117 6134 util.rename(
6118 6135 a + b".resolve", scmutil.backuppath(ui, repo, f)
6119 6136 )
6120 6137 except OSError as inst:
6121 6138 if inst.errno != errno.ENOENT:
6122 6139 raise
6123 6140
6124 6141 if hasconflictmarkers:
6125 6142 ui.warn(
6126 6143 _(
6127 6144 b'warning: the following files still have conflict '
6128 6145 b'markers:\n'
6129 6146 )
6130 6147 + b''.join(
6131 6148 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6132 6149 )
6133 6150 )
6134 6151 if markcheck == b'abort' and not all and not pats:
6135 6152 raise error.StateError(
6136 6153 _(b'conflict markers detected'),
6137 6154 hint=_(b'use --all to mark anyway'),
6138 6155 )
6139 6156
6140 6157 for f in tocomplete:
6141 6158 try:
6142 6159 # resolve file
6143 6160 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6144 6161 with ui.configoverride(overrides, b'resolve'):
6145 6162 r = ms.resolve(f, wctx)
6146 6163 if r:
6147 6164 ret = 1
6148 6165 finally:
6149 6166 ms.commit()
6150 6167
6151 6168 # replace filemerge's .orig file with our resolve file
6152 6169 a = repo.wjoin(f)
6153 6170 try:
6154 6171 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6155 6172 except OSError as inst:
6156 6173 if inst.errno != errno.ENOENT:
6157 6174 raise
6158 6175
6159 6176 ms.commit()
6160 6177 branchmerge = repo.dirstate.p2() != nullid
6161 6178 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6162 6179
6163 6180 if not didwork and pats:
6164 6181 hint = None
6165 6182 if not any([p for p in pats if p.find(b':') >= 0]):
6166 6183 pats = [b'path:%s' % p for p in pats]
6167 6184 m = scmutil.match(wctx, pats, opts)
6168 6185 for f in ms:
6169 6186 if not m(f):
6170 6187 continue
6171 6188
6172 6189 def flag(o):
6173 6190 if o == b're_merge':
6174 6191 return b'--re-merge '
6175 6192 return b'-%s ' % o[0:1]
6176 6193
6177 6194 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6178 6195 hint = _(b"(try: hg resolve %s%s)\n") % (
6179 6196 flags,
6180 6197 b' '.join(pats),
6181 6198 )
6182 6199 break
6183 6200 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6184 6201 if hint:
6185 6202 ui.warn(hint)
6186 6203
6187 6204 unresolvedf = ms.unresolvedcount()
6188 6205 if not unresolvedf:
6189 6206 ui.status(_(b'(no more unresolved files)\n'))
6190 6207 cmdutil.checkafterresolved(repo)
6191 6208
6192 6209 return ret
6193 6210
6194 6211
6195 6212 @command(
6196 6213 b'revert',
6197 6214 [
6198 6215 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6199 6216 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6200 6217 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6201 6218 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6202 6219 (b'i', b'interactive', None, _(b'interactively select the changes')),
6203 6220 ]
6204 6221 + walkopts
6205 6222 + dryrunopts,
6206 6223 _(b'[OPTION]... [-r REV] [NAME]...'),
6207 6224 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6208 6225 )
6209 6226 def revert(ui, repo, *pats, **opts):
6210 6227 """restore files to their checkout state
6211 6228
6212 6229 .. note::
6213 6230
6214 6231 To check out earlier revisions, you should use :hg:`update REV`.
6215 6232 To cancel an uncommitted merge (and lose your changes),
6216 6233 use :hg:`merge --abort`.
6217 6234
6218 6235 With no revision specified, revert the specified files or directories
6219 6236 to the contents they had in the parent of the working directory.
6220 6237 This restores the contents of files to an unmodified
6221 6238 state and unschedules adds, removes, copies, and renames. If the
6222 6239 working directory has two parents, you must explicitly specify a
6223 6240 revision.
6224 6241
6225 6242 Using the -r/--rev or -d/--date options, revert the given files or
6226 6243 directories to their states as of a specific revision. Because
6227 6244 revert does not change the working directory parents, this will
6228 6245 cause these files to appear modified. This can be helpful to "back
6229 6246 out" some or all of an earlier change. See :hg:`backout` for a
6230 6247 related method.
6231 6248
6232 6249 Modified files are saved with a .orig suffix before reverting.
6233 6250 To disable these backups, use --no-backup. It is possible to store
6234 6251 the backup files in a custom directory relative to the root of the
6235 6252 repository by setting the ``ui.origbackuppath`` configuration
6236 6253 option.
6237 6254
6238 6255 See :hg:`help dates` for a list of formats valid for -d/--date.
6239 6256
6240 6257 See :hg:`help backout` for a way to reverse the effect of an
6241 6258 earlier changeset.
6242 6259
6243 6260 Returns 0 on success.
6244 6261 """
6245 6262
6246 6263 opts = pycompat.byteskwargs(opts)
6247 6264 if opts.get(b"date"):
6248 6265 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6249 6266 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6250 6267
6251 6268 parent, p2 = repo.dirstate.parents()
6252 6269 if not opts.get(b'rev') and p2 != nullid:
6253 6270 # revert after merge is a trap for new users (issue2915)
6254 6271 raise error.InputError(
6255 6272 _(b'uncommitted merge with no revision specified'),
6256 6273 hint=_(b"use 'hg update' or see 'hg help revert'"),
6257 6274 )
6258 6275
6259 6276 rev = opts.get(b'rev')
6260 6277 if rev:
6261 6278 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6262 6279 ctx = scmutil.revsingle(repo, rev)
6263 6280
6264 6281 if not (
6265 6282 pats
6266 6283 or opts.get(b'include')
6267 6284 or opts.get(b'exclude')
6268 6285 or opts.get(b'all')
6269 6286 or opts.get(b'interactive')
6270 6287 ):
6271 6288 msg = _(b"no files or directories specified")
6272 6289 if p2 != nullid:
6273 6290 hint = _(
6274 6291 b"uncommitted merge, use --all to discard all changes,"
6275 6292 b" or 'hg update -C .' to abort the merge"
6276 6293 )
6277 6294 raise error.InputError(msg, hint=hint)
6278 6295 dirty = any(repo.status())
6279 6296 node = ctx.node()
6280 6297 if node != parent:
6281 6298 if dirty:
6282 6299 hint = (
6283 6300 _(
6284 6301 b"uncommitted changes, use --all to discard all"
6285 6302 b" changes, or 'hg update %d' to update"
6286 6303 )
6287 6304 % ctx.rev()
6288 6305 )
6289 6306 else:
6290 6307 hint = (
6291 6308 _(
6292 6309 b"use --all to revert all files,"
6293 6310 b" or 'hg update %d' to update"
6294 6311 )
6295 6312 % ctx.rev()
6296 6313 )
6297 6314 elif dirty:
6298 6315 hint = _(b"uncommitted changes, use --all to discard all changes")
6299 6316 else:
6300 6317 hint = _(b"use --all to revert all files")
6301 6318 raise error.InputError(msg, hint=hint)
6302 6319
6303 6320 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6304 6321
6305 6322
6306 6323 @command(
6307 6324 b'rollback',
6308 6325 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6309 6326 helpcategory=command.CATEGORY_MAINTENANCE,
6310 6327 )
6311 6328 def rollback(ui, repo, **opts):
6312 6329 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6313 6330
6314 6331 Please use :hg:`commit --amend` instead of rollback to correct
6315 6332 mistakes in the last commit.
6316 6333
6317 6334 This command should be used with care. There is only one level of
6318 6335 rollback, and there is no way to undo a rollback. It will also
6319 6336 restore the dirstate at the time of the last transaction, losing
6320 6337 any dirstate changes since that time. This command does not alter
6321 6338 the working directory.
6322 6339
6323 6340 Transactions are used to encapsulate the effects of all commands
6324 6341 that create new changesets or propagate existing changesets into a
6325 6342 repository.
6326 6343
6327 6344 .. container:: verbose
6328 6345
6329 6346 For example, the following commands are transactional, and their
6330 6347 effects can be rolled back:
6331 6348
6332 6349 - commit
6333 6350 - import
6334 6351 - pull
6335 6352 - push (with this repository as the destination)
6336 6353 - unbundle
6337 6354
6338 6355 To avoid permanent data loss, rollback will refuse to rollback a
6339 6356 commit transaction if it isn't checked out. Use --force to
6340 6357 override this protection.
6341 6358
6342 6359 The rollback command can be entirely disabled by setting the
6343 6360 ``ui.rollback`` configuration setting to false. If you're here
6344 6361 because you want to use rollback and it's disabled, you can
6345 6362 re-enable the command by setting ``ui.rollback`` to true.
6346 6363
6347 6364 This command is not intended for use on public repositories. Once
6348 6365 changes are visible for pull by other users, rolling a transaction
6349 6366 back locally is ineffective (someone else may already have pulled
6350 6367 the changes). Furthermore, a race is possible with readers of the
6351 6368 repository; for example an in-progress pull from the repository
6352 6369 may fail if a rollback is performed.
6353 6370
6354 6371 Returns 0 on success, 1 if no rollback data is available.
6355 6372 """
6356 6373 if not ui.configbool(b'ui', b'rollback'):
6357 6374 raise error.Abort(
6358 6375 _(b'rollback is disabled because it is unsafe'),
6359 6376 hint=b'see `hg help -v rollback` for information',
6360 6377 )
6361 6378 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6362 6379
6363 6380
6364 6381 @command(
6365 6382 b'root',
6366 6383 [] + formatteropts,
6367 6384 intents={INTENT_READONLY},
6368 6385 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6369 6386 )
6370 6387 def root(ui, repo, **opts):
6371 6388 """print the root (top) of the current working directory
6372 6389
6373 6390 Print the root directory of the current repository.
6374 6391
6375 6392 .. container:: verbose
6376 6393
6377 6394 Template:
6378 6395
6379 6396 The following keywords are supported in addition to the common template
6380 6397 keywords and functions. See also :hg:`help templates`.
6381 6398
6382 6399 :hgpath: String. Path to the .hg directory.
6383 6400 :storepath: String. Path to the directory holding versioned data.
6384 6401
6385 6402 Returns 0 on success.
6386 6403 """
6387 6404 opts = pycompat.byteskwargs(opts)
6388 6405 with ui.formatter(b'root', opts) as fm:
6389 6406 fm.startitem()
6390 6407 fm.write(b'reporoot', b'%s\n', repo.root)
6391 6408 fm.data(hgpath=repo.path, storepath=repo.spath)
6392 6409
6393 6410
6394 6411 @command(
6395 6412 b'serve',
6396 6413 [
6397 6414 (
6398 6415 b'A',
6399 6416 b'accesslog',
6400 6417 b'',
6401 6418 _(b'name of access log file to write to'),
6402 6419 _(b'FILE'),
6403 6420 ),
6404 6421 (b'd', b'daemon', None, _(b'run server in background')),
6405 6422 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6406 6423 (
6407 6424 b'E',
6408 6425 b'errorlog',
6409 6426 b'',
6410 6427 _(b'name of error log file to write to'),
6411 6428 _(b'FILE'),
6412 6429 ),
6413 6430 # use string type, then we can check if something was passed
6414 6431 (
6415 6432 b'p',
6416 6433 b'port',
6417 6434 b'',
6418 6435 _(b'port to listen on (default: 8000)'),
6419 6436 _(b'PORT'),
6420 6437 ),
6421 6438 (
6422 6439 b'a',
6423 6440 b'address',
6424 6441 b'',
6425 6442 _(b'address to listen on (default: all interfaces)'),
6426 6443 _(b'ADDR'),
6427 6444 ),
6428 6445 (
6429 6446 b'',
6430 6447 b'prefix',
6431 6448 b'',
6432 6449 _(b'prefix path to serve from (default: server root)'),
6433 6450 _(b'PREFIX'),
6434 6451 ),
6435 6452 (
6436 6453 b'n',
6437 6454 b'name',
6438 6455 b'',
6439 6456 _(b'name to show in web pages (default: working directory)'),
6440 6457 _(b'NAME'),
6441 6458 ),
6442 6459 (
6443 6460 b'',
6444 6461 b'web-conf',
6445 6462 b'',
6446 6463 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6447 6464 _(b'FILE'),
6448 6465 ),
6449 6466 (
6450 6467 b'',
6451 6468 b'webdir-conf',
6452 6469 b'',
6453 6470 _(b'name of the hgweb config file (DEPRECATED)'),
6454 6471 _(b'FILE'),
6455 6472 ),
6456 6473 (
6457 6474 b'',
6458 6475 b'pid-file',
6459 6476 b'',
6460 6477 _(b'name of file to write process ID to'),
6461 6478 _(b'FILE'),
6462 6479 ),
6463 6480 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6464 6481 (
6465 6482 b'',
6466 6483 b'cmdserver',
6467 6484 b'',
6468 6485 _(b'for remote clients (ADVANCED)'),
6469 6486 _(b'MODE'),
6470 6487 ),
6471 6488 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6472 6489 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6473 6490 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6474 6491 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6475 6492 (b'', b'print-url', None, _(b'start and print only the URL')),
6476 6493 ]
6477 6494 + subrepoopts,
6478 6495 _(b'[OPTION]...'),
6479 6496 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6480 6497 helpbasic=True,
6481 6498 optionalrepo=True,
6482 6499 )
6483 6500 def serve(ui, repo, **opts):
6484 6501 """start stand-alone webserver
6485 6502
6486 6503 Start a local HTTP repository browser and pull server. You can use
6487 6504 this for ad-hoc sharing and browsing of repositories. It is
6488 6505 recommended to use a real web server to serve a repository for
6489 6506 longer periods of time.
6490 6507
6491 6508 Please note that the server does not implement access control.
6492 6509 This means that, by default, anybody can read from the server and
6493 6510 nobody can write to it by default. Set the ``web.allow-push``
6494 6511 option to ``*`` to allow everybody to push to the server. You
6495 6512 should use a real web server if you need to authenticate users.
6496 6513
6497 6514 By default, the server logs accesses to stdout and errors to
6498 6515 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6499 6516 files.
6500 6517
6501 6518 To have the server choose a free port number to listen on, specify
6502 6519 a port number of 0; in this case, the server will print the port
6503 6520 number it uses.
6504 6521
6505 6522 Returns 0 on success.
6506 6523 """
6507 6524
6508 6525 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6509 6526 opts = pycompat.byteskwargs(opts)
6510 6527 if opts[b"print_url"] and ui.verbose:
6511 6528 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6512 6529
6513 6530 if opts[b"stdio"]:
6514 6531 if repo is None:
6515 6532 raise error.RepoError(
6516 6533 _(b"there is no Mercurial repository here (.hg not found)")
6517 6534 )
6518 6535 s = wireprotoserver.sshserver(ui, repo)
6519 6536 s.serve_forever()
6520 6537 return
6521 6538
6522 6539 service = server.createservice(ui, repo, opts)
6523 6540 return server.runservice(opts, initfn=service.init, runfn=service.run)
6524 6541
6525 6542
6526 6543 @command(
6527 6544 b'shelve',
6528 6545 [
6529 6546 (
6530 6547 b'A',
6531 6548 b'addremove',
6532 6549 None,
6533 6550 _(b'mark new/missing files as added/removed before shelving'),
6534 6551 ),
6535 6552 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6536 6553 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6537 6554 (
6538 6555 b'',
6539 6556 b'date',
6540 6557 b'',
6541 6558 _(b'shelve with the specified commit date'),
6542 6559 _(b'DATE'),
6543 6560 ),
6544 6561 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6545 6562 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6546 6563 (
6547 6564 b'k',
6548 6565 b'keep',
6549 6566 False,
6550 6567 _(b'shelve, but keep changes in the working directory'),
6551 6568 ),
6552 6569 (b'l', b'list', None, _(b'list current shelves')),
6553 6570 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6554 6571 (
6555 6572 b'n',
6556 6573 b'name',
6557 6574 b'',
6558 6575 _(b'use the given name for the shelved commit'),
6559 6576 _(b'NAME'),
6560 6577 ),
6561 6578 (
6562 6579 b'p',
6563 6580 b'patch',
6564 6581 None,
6565 6582 _(
6566 6583 b'output patches for changes (provide the names of the shelved '
6567 6584 b'changes as positional arguments)'
6568 6585 ),
6569 6586 ),
6570 6587 (b'i', b'interactive', None, _(b'interactive mode')),
6571 6588 (
6572 6589 b'',
6573 6590 b'stat',
6574 6591 None,
6575 6592 _(
6576 6593 b'output diffstat-style summary of changes (provide the names of '
6577 6594 b'the shelved changes as positional arguments)'
6578 6595 ),
6579 6596 ),
6580 6597 ]
6581 6598 + cmdutil.walkopts,
6582 6599 _(b'hg shelve [OPTION]... [FILE]...'),
6583 6600 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6584 6601 )
6585 6602 def shelve(ui, repo, *pats, **opts):
6586 6603 """save and set aside changes from the working directory
6587 6604
6588 6605 Shelving takes files that "hg status" reports as not clean, saves
6589 6606 the modifications to a bundle (a shelved change), and reverts the
6590 6607 files so that their state in the working directory becomes clean.
6591 6608
6592 6609 To restore these changes to the working directory, using "hg
6593 6610 unshelve"; this will work even if you switch to a different
6594 6611 commit.
6595 6612
6596 6613 When no files are specified, "hg shelve" saves all not-clean
6597 6614 files. If specific files or directories are named, only changes to
6598 6615 those files are shelved.
6599 6616
6600 6617 In bare shelve (when no files are specified, without interactive,
6601 6618 include and exclude option), shelving remembers information if the
6602 6619 working directory was on newly created branch, in other words working
6603 6620 directory was on different branch than its first parent. In this
6604 6621 situation unshelving restores branch information to the working directory.
6605 6622
6606 6623 Each shelved change has a name that makes it easier to find later.
6607 6624 The name of a shelved change defaults to being based on the active
6608 6625 bookmark, or if there is no active bookmark, the current named
6609 6626 branch. To specify a different name, use ``--name``.
6610 6627
6611 6628 To see a list of existing shelved changes, use the ``--list``
6612 6629 option. For each shelved change, this will print its name, age,
6613 6630 and description; use ``--patch`` or ``--stat`` for more details.
6614 6631
6615 6632 To delete specific shelved changes, use ``--delete``. To delete
6616 6633 all shelved changes, use ``--cleanup``.
6617 6634 """
6618 6635 opts = pycompat.byteskwargs(opts)
6619 6636 allowables = [
6620 6637 (b'addremove', {b'create'}), # 'create' is pseudo action
6621 6638 (b'unknown', {b'create'}),
6622 6639 (b'cleanup', {b'cleanup'}),
6623 6640 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6624 6641 (b'delete', {b'delete'}),
6625 6642 (b'edit', {b'create'}),
6626 6643 (b'keep', {b'create'}),
6627 6644 (b'list', {b'list'}),
6628 6645 (b'message', {b'create'}),
6629 6646 (b'name', {b'create'}),
6630 6647 (b'patch', {b'patch', b'list'}),
6631 6648 (b'stat', {b'stat', b'list'}),
6632 6649 ]
6633 6650
6634 6651 def checkopt(opt):
6635 6652 if opts.get(opt):
6636 6653 for i, allowable in allowables:
6637 6654 if opts[i] and opt not in allowable:
6638 6655 raise error.InputError(
6639 6656 _(
6640 6657 b"options '--%s' and '--%s' may not be "
6641 6658 b"used together"
6642 6659 )
6643 6660 % (opt, i)
6644 6661 )
6645 6662 return True
6646 6663
6647 6664 if checkopt(b'cleanup'):
6648 6665 if pats:
6649 6666 raise error.InputError(
6650 6667 _(b"cannot specify names when using '--cleanup'")
6651 6668 )
6652 6669 return shelvemod.cleanupcmd(ui, repo)
6653 6670 elif checkopt(b'delete'):
6654 6671 return shelvemod.deletecmd(ui, repo, pats)
6655 6672 elif checkopt(b'list'):
6656 6673 return shelvemod.listcmd(ui, repo, pats, opts)
6657 6674 elif checkopt(b'patch') or checkopt(b'stat'):
6658 6675 return shelvemod.patchcmds(ui, repo, pats, opts)
6659 6676 else:
6660 6677 return shelvemod.createcmd(ui, repo, pats, opts)
6661 6678
6662 6679
6663 6680 _NOTTERSE = b'nothing'
6664 6681
6665 6682
6666 6683 @command(
6667 6684 b'status|st',
6668 6685 [
6669 6686 (b'A', b'all', None, _(b'show status of all files')),
6670 6687 (b'm', b'modified', None, _(b'show only modified files')),
6671 6688 (b'a', b'added', None, _(b'show only added files')),
6672 6689 (b'r', b'removed', None, _(b'show only removed files')),
6673 6690 (b'd', b'deleted', None, _(b'show only missing files')),
6674 6691 (b'c', b'clean', None, _(b'show only files without changes')),
6675 6692 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6676 6693 (b'i', b'ignored', None, _(b'show only ignored files')),
6677 6694 (b'n', b'no-status', None, _(b'hide status prefix')),
6678 6695 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6679 6696 (
6680 6697 b'C',
6681 6698 b'copies',
6682 6699 None,
6683 6700 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6684 6701 ),
6685 6702 (
6686 6703 b'0',
6687 6704 b'print0',
6688 6705 None,
6689 6706 _(b'end filenames with NUL, for use with xargs'),
6690 6707 ),
6691 6708 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6692 6709 (
6693 6710 b'',
6694 6711 b'change',
6695 6712 b'',
6696 6713 _(b'list the changed files of a revision'),
6697 6714 _(b'REV'),
6698 6715 ),
6699 6716 ]
6700 6717 + walkopts
6701 6718 + subrepoopts
6702 6719 + formatteropts,
6703 6720 _(b'[OPTION]... [FILE]...'),
6704 6721 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6705 6722 helpbasic=True,
6706 6723 inferrepo=True,
6707 6724 intents={INTENT_READONLY},
6708 6725 )
6709 6726 def status(ui, repo, *pats, **opts):
6710 6727 """show changed files in the working directory
6711 6728
6712 6729 Show status of files in the repository. If names are given, only
6713 6730 files that match are shown. Files that are clean or ignored or
6714 6731 the source of a copy/move operation, are not listed unless
6715 6732 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6716 6733 Unless options described with "show only ..." are given, the
6717 6734 options -mardu are used.
6718 6735
6719 6736 Option -q/--quiet hides untracked (unknown and ignored) files
6720 6737 unless explicitly requested with -u/--unknown or -i/--ignored.
6721 6738
6722 6739 .. note::
6723 6740
6724 6741 :hg:`status` may appear to disagree with diff if permissions have
6725 6742 changed or a merge has occurred. The standard diff format does
6726 6743 not report permission changes and diff only reports changes
6727 6744 relative to one merge parent.
6728 6745
6729 6746 If one revision is given, it is used as the base revision.
6730 6747 If two revisions are given, the differences between them are
6731 6748 shown. The --change option can also be used as a shortcut to list
6732 6749 the changed files of a revision from its first parent.
6733 6750
6734 6751 The codes used to show the status of files are::
6735 6752
6736 6753 M = modified
6737 6754 A = added
6738 6755 R = removed
6739 6756 C = clean
6740 6757 ! = missing (deleted by non-hg command, but still tracked)
6741 6758 ? = not tracked
6742 6759 I = ignored
6743 6760 = origin of the previous file (with --copies)
6744 6761
6745 6762 .. container:: verbose
6746 6763
6747 6764 The -t/--terse option abbreviates the output by showing only the directory
6748 6765 name if all the files in it share the same status. The option takes an
6749 6766 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6750 6767 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6751 6768 for 'ignored' and 'c' for clean.
6752 6769
6753 6770 It abbreviates only those statuses which are passed. Note that clean and
6754 6771 ignored files are not displayed with '--terse ic' unless the -c/--clean
6755 6772 and -i/--ignored options are also used.
6756 6773
6757 6774 The -v/--verbose option shows information when the repository is in an
6758 6775 unfinished merge, shelve, rebase state etc. You can have this behavior
6759 6776 turned on by default by enabling the ``commands.status.verbose`` option.
6760 6777
6761 6778 You can skip displaying some of these states by setting
6762 6779 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6763 6780 'histedit', 'merge', 'rebase', or 'unshelve'.
6764 6781
6765 6782 Template:
6766 6783
6767 6784 The following keywords are supported in addition to the common template
6768 6785 keywords and functions. See also :hg:`help templates`.
6769 6786
6770 6787 :path: String. Repository-absolute path of the file.
6771 6788 :source: String. Repository-absolute path of the file originated from.
6772 6789 Available if ``--copies`` is specified.
6773 6790 :status: String. Character denoting file's status.
6774 6791
6775 6792 Examples:
6776 6793
6777 6794 - show changes in the working directory relative to a
6778 6795 changeset::
6779 6796
6780 6797 hg status --rev 9353
6781 6798
6782 6799 - show changes in the working directory relative to the
6783 6800 current directory (see :hg:`help patterns` for more information)::
6784 6801
6785 6802 hg status re:
6786 6803
6787 6804 - show all changes including copies in an existing changeset::
6788 6805
6789 6806 hg status --copies --change 9353
6790 6807
6791 6808 - get a NUL separated list of added files, suitable for xargs::
6792 6809
6793 6810 hg status -an0
6794 6811
6795 6812 - show more information about the repository status, abbreviating
6796 6813 added, removed, modified, deleted, and untracked paths::
6797 6814
6798 6815 hg status -v -t mardu
6799 6816
6800 6817 Returns 0 on success.
6801 6818
6802 6819 """
6803 6820
6804 6821 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6805 6822 opts = pycompat.byteskwargs(opts)
6806 6823 revs = opts.get(b'rev')
6807 6824 change = opts.get(b'change')
6808 6825 terse = opts.get(b'terse')
6809 6826 if terse is _NOTTERSE:
6810 6827 if revs:
6811 6828 terse = b''
6812 6829 else:
6813 6830 terse = ui.config(b'commands', b'status.terse')
6814 6831
6815 6832 if revs and terse:
6816 6833 msg = _(b'cannot use --terse with --rev')
6817 6834 raise error.InputError(msg)
6818 6835 elif change:
6819 6836 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6820 6837 ctx2 = scmutil.revsingle(repo, change, None)
6821 6838 ctx1 = ctx2.p1()
6822 6839 else:
6823 6840 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6824 6841 ctx1, ctx2 = scmutil.revpair(repo, revs)
6825 6842
6826 6843 forcerelativevalue = None
6827 6844 if ui.hasconfig(b'commands', b'status.relative'):
6828 6845 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6829 6846 uipathfn = scmutil.getuipathfn(
6830 6847 repo,
6831 6848 legacyrelativevalue=bool(pats),
6832 6849 forcerelativevalue=forcerelativevalue,
6833 6850 )
6834 6851
6835 6852 if opts.get(b'print0'):
6836 6853 end = b'\0'
6837 6854 else:
6838 6855 end = b'\n'
6839 6856 states = b'modified added removed deleted unknown ignored clean'.split()
6840 6857 show = [k for k in states if opts.get(k)]
6841 6858 if opts.get(b'all'):
6842 6859 show += ui.quiet and (states[:4] + [b'clean']) or states
6843 6860
6844 6861 if not show:
6845 6862 if ui.quiet:
6846 6863 show = states[:4]
6847 6864 else:
6848 6865 show = states[:5]
6849 6866
6850 6867 m = scmutil.match(ctx2, pats, opts)
6851 6868 if terse:
6852 6869 # we need to compute clean and unknown to terse
6853 6870 stat = repo.status(
6854 6871 ctx1.node(),
6855 6872 ctx2.node(),
6856 6873 m,
6857 6874 b'ignored' in show or b'i' in terse,
6858 6875 clean=True,
6859 6876 unknown=True,
6860 6877 listsubrepos=opts.get(b'subrepos'),
6861 6878 )
6862 6879
6863 6880 stat = cmdutil.tersedir(stat, terse)
6864 6881 else:
6865 6882 stat = repo.status(
6866 6883 ctx1.node(),
6867 6884 ctx2.node(),
6868 6885 m,
6869 6886 b'ignored' in show,
6870 6887 b'clean' in show,
6871 6888 b'unknown' in show,
6872 6889 opts.get(b'subrepos'),
6873 6890 )
6874 6891
6875 6892 changestates = zip(
6876 6893 states,
6877 6894 pycompat.iterbytestr(b'MAR!?IC'),
6878 6895 [getattr(stat, s.decode('utf8')) for s in states],
6879 6896 )
6880 6897
6881 6898 copy = {}
6882 6899 if (
6883 6900 opts.get(b'all')
6884 6901 or opts.get(b'copies')
6885 6902 or ui.configbool(b'ui', b'statuscopies')
6886 6903 ) and not opts.get(b'no_status'):
6887 6904 copy = copies.pathcopies(ctx1, ctx2, m)
6888 6905
6889 6906 morestatus = None
6890 6907 if (
6891 6908 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6892 6909 and not ui.plain()
6893 6910 and not opts.get(b'print0')
6894 6911 ):
6895 6912 morestatus = cmdutil.readmorestatus(repo)
6896 6913
6897 6914 ui.pager(b'status')
6898 6915 fm = ui.formatter(b'status', opts)
6899 6916 fmt = b'%s' + end
6900 6917 showchar = not opts.get(b'no_status')
6901 6918
6902 6919 for state, char, files in changestates:
6903 6920 if state in show:
6904 6921 label = b'status.' + state
6905 6922 for f in files:
6906 6923 fm.startitem()
6907 6924 fm.context(ctx=ctx2)
6908 6925 fm.data(itemtype=b'file', path=f)
6909 6926 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6910 6927 fm.plain(fmt % uipathfn(f), label=label)
6911 6928 if f in copy:
6912 6929 fm.data(source=copy[f])
6913 6930 fm.plain(
6914 6931 (b' %s' + end) % uipathfn(copy[f]),
6915 6932 label=b'status.copied',
6916 6933 )
6917 6934 if morestatus:
6918 6935 morestatus.formatfile(f, fm)
6919 6936
6920 6937 if morestatus:
6921 6938 morestatus.formatfooter(fm)
6922 6939 fm.end()
6923 6940
6924 6941
6925 6942 @command(
6926 6943 b'summary|sum',
6927 6944 [(b'', b'remote', None, _(b'check for push and pull'))],
6928 6945 b'[--remote]',
6929 6946 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6930 6947 helpbasic=True,
6931 6948 intents={INTENT_READONLY},
6932 6949 )
6933 6950 def summary(ui, repo, **opts):
6934 6951 """summarize working directory state
6935 6952
6936 6953 This generates a brief summary of the working directory state,
6937 6954 including parents, branch, commit status, phase and available updates.
6938 6955
6939 6956 With the --remote option, this will check the default paths for
6940 6957 incoming and outgoing changes. This can be time-consuming.
6941 6958
6942 6959 Returns 0 on success.
6943 6960 """
6944 6961
6945 6962 opts = pycompat.byteskwargs(opts)
6946 6963 ui.pager(b'summary')
6947 6964 ctx = repo[None]
6948 6965 parents = ctx.parents()
6949 6966 pnode = parents[0].node()
6950 6967 marks = []
6951 6968
6952 6969 try:
6953 6970 ms = mergestatemod.mergestate.read(repo)
6954 6971 except error.UnsupportedMergeRecords as e:
6955 6972 s = b' '.join(e.recordtypes)
6956 6973 ui.warn(
6957 6974 _(b'warning: merge state has unsupported record types: %s\n') % s
6958 6975 )
6959 6976 unresolved = []
6960 6977 else:
6961 6978 unresolved = list(ms.unresolved())
6962 6979
6963 6980 for p in parents:
6964 6981 # label with log.changeset (instead of log.parent) since this
6965 6982 # shows a working directory parent *changeset*:
6966 6983 # i18n: column positioning for "hg summary"
6967 6984 ui.write(
6968 6985 _(b'parent: %d:%s ') % (p.rev(), p),
6969 6986 label=logcmdutil.changesetlabels(p),
6970 6987 )
6971 6988 ui.write(b' '.join(p.tags()), label=b'log.tag')
6972 6989 if p.bookmarks():
6973 6990 marks.extend(p.bookmarks())
6974 6991 if p.rev() == -1:
6975 6992 if not len(repo):
6976 6993 ui.write(_(b' (empty repository)'))
6977 6994 else:
6978 6995 ui.write(_(b' (no revision checked out)'))
6979 6996 if p.obsolete():
6980 6997 ui.write(_(b' (obsolete)'))
6981 6998 if p.isunstable():
6982 6999 instabilities = (
6983 7000 ui.label(instability, b'trouble.%s' % instability)
6984 7001 for instability in p.instabilities()
6985 7002 )
6986 7003 ui.write(b' (' + b', '.join(instabilities) + b')')
6987 7004 ui.write(b'\n')
6988 7005 if p.description():
6989 7006 ui.status(
6990 7007 b' ' + p.description().splitlines()[0].strip() + b'\n',
6991 7008 label=b'log.summary',
6992 7009 )
6993 7010
6994 7011 branch = ctx.branch()
6995 7012 bheads = repo.branchheads(branch)
6996 7013 # i18n: column positioning for "hg summary"
6997 7014 m = _(b'branch: %s\n') % branch
6998 7015 if branch != b'default':
6999 7016 ui.write(m, label=b'log.branch')
7000 7017 else:
7001 7018 ui.status(m, label=b'log.branch')
7002 7019
7003 7020 if marks:
7004 7021 active = repo._activebookmark
7005 7022 # i18n: column positioning for "hg summary"
7006 7023 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7007 7024 if active is not None:
7008 7025 if active in marks:
7009 7026 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7010 7027 marks.remove(active)
7011 7028 else:
7012 7029 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7013 7030 for m in marks:
7014 7031 ui.write(b' ' + m, label=b'log.bookmark')
7015 7032 ui.write(b'\n', label=b'log.bookmark')
7016 7033
7017 7034 status = repo.status(unknown=True)
7018 7035
7019 7036 c = repo.dirstate.copies()
7020 7037 copied, renamed = [], []
7021 7038 for d, s in pycompat.iteritems(c):
7022 7039 if s in status.removed:
7023 7040 status.removed.remove(s)
7024 7041 renamed.append(d)
7025 7042 else:
7026 7043 copied.append(d)
7027 7044 if d in status.added:
7028 7045 status.added.remove(d)
7029 7046
7030 7047 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7031 7048
7032 7049 labels = [
7033 7050 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7034 7051 (ui.label(_(b'%d added'), b'status.added'), status.added),
7035 7052 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7036 7053 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7037 7054 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7038 7055 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7039 7056 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7040 7057 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7041 7058 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7042 7059 ]
7043 7060 t = []
7044 7061 for l, s in labels:
7045 7062 if s:
7046 7063 t.append(l % len(s))
7047 7064
7048 7065 t = b', '.join(t)
7049 7066 cleanworkdir = False
7050 7067
7051 7068 if repo.vfs.exists(b'graftstate'):
7052 7069 t += _(b' (graft in progress)')
7053 7070 if repo.vfs.exists(b'updatestate'):
7054 7071 t += _(b' (interrupted update)')
7055 7072 elif len(parents) > 1:
7056 7073 t += _(b' (merge)')
7057 7074 elif branch != parents[0].branch():
7058 7075 t += _(b' (new branch)')
7059 7076 elif parents[0].closesbranch() and pnode in repo.branchheads(
7060 7077 branch, closed=True
7061 7078 ):
7062 7079 t += _(b' (head closed)')
7063 7080 elif not (
7064 7081 status.modified
7065 7082 or status.added
7066 7083 or status.removed
7067 7084 or renamed
7068 7085 or copied
7069 7086 or subs
7070 7087 ):
7071 7088 t += _(b' (clean)')
7072 7089 cleanworkdir = True
7073 7090 elif pnode not in bheads:
7074 7091 t += _(b' (new branch head)')
7075 7092
7076 7093 if parents:
7077 7094 pendingphase = max(p.phase() for p in parents)
7078 7095 else:
7079 7096 pendingphase = phases.public
7080 7097
7081 7098 if pendingphase > phases.newcommitphase(ui):
7082 7099 t += b' (%s)' % phases.phasenames[pendingphase]
7083 7100
7084 7101 if cleanworkdir:
7085 7102 # i18n: column positioning for "hg summary"
7086 7103 ui.status(_(b'commit: %s\n') % t.strip())
7087 7104 else:
7088 7105 # i18n: column positioning for "hg summary"
7089 7106 ui.write(_(b'commit: %s\n') % t.strip())
7090 7107
7091 7108 # all ancestors of branch heads - all ancestors of parent = new csets
7092 7109 new = len(
7093 7110 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7094 7111 )
7095 7112
7096 7113 if new == 0:
7097 7114 # i18n: column positioning for "hg summary"
7098 7115 ui.status(_(b'update: (current)\n'))
7099 7116 elif pnode not in bheads:
7100 7117 # i18n: column positioning for "hg summary"
7101 7118 ui.write(_(b'update: %d new changesets (update)\n') % new)
7102 7119 else:
7103 7120 # i18n: column positioning for "hg summary"
7104 7121 ui.write(
7105 7122 _(b'update: %d new changesets, %d branch heads (merge)\n')
7106 7123 % (new, len(bheads))
7107 7124 )
7108 7125
7109 7126 t = []
7110 7127 draft = len(repo.revs(b'draft()'))
7111 7128 if draft:
7112 7129 t.append(_(b'%d draft') % draft)
7113 7130 secret = len(repo.revs(b'secret()'))
7114 7131 if secret:
7115 7132 t.append(_(b'%d secret') % secret)
7116 7133
7117 7134 if draft or secret:
7118 7135 ui.status(_(b'phases: %s\n') % b', '.join(t))
7119 7136
7120 7137 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7121 7138 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7122 7139 numtrouble = len(repo.revs(trouble + b"()"))
7123 7140 # We write all the possibilities to ease translation
7124 7141 troublemsg = {
7125 7142 b"orphan": _(b"orphan: %d changesets"),
7126 7143 b"contentdivergent": _(b"content-divergent: %d changesets"),
7127 7144 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7128 7145 }
7129 7146 if numtrouble > 0:
7130 7147 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7131 7148
7132 7149 cmdutil.summaryhooks(ui, repo)
7133 7150
7134 7151 if opts.get(b'remote'):
7135 7152 needsincoming, needsoutgoing = True, True
7136 7153 else:
7137 7154 needsincoming, needsoutgoing = False, False
7138 7155 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7139 7156 if i:
7140 7157 needsincoming = True
7141 7158 if o:
7142 7159 needsoutgoing = True
7143 7160 if not needsincoming and not needsoutgoing:
7144 7161 return
7145 7162
7146 7163 def getincoming():
7147 7164 source, branches = hg.parseurl(ui.expandpath(b'default'))
7148 7165 sbranch = branches[0]
7149 7166 try:
7150 7167 other = hg.peer(repo, {}, source)
7151 7168 except error.RepoError:
7152 7169 if opts.get(b'remote'):
7153 7170 raise
7154 7171 return source, sbranch, None, None, None
7155 7172 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7156 7173 if revs:
7157 7174 revs = [other.lookup(rev) for rev in revs]
7158 7175 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7159 7176 repo.ui.pushbuffer()
7160 7177 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7161 7178 repo.ui.popbuffer()
7162 7179 return source, sbranch, other, commoninc, commoninc[1]
7163 7180
7164 7181 if needsincoming:
7165 7182 source, sbranch, sother, commoninc, incoming = getincoming()
7166 7183 else:
7167 7184 source = sbranch = sother = commoninc = incoming = None
7168 7185
7169 7186 def getoutgoing():
7170 7187 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7171 7188 dbranch = branches[0]
7172 7189 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7173 7190 if source != dest:
7174 7191 try:
7175 7192 dother = hg.peer(repo, {}, dest)
7176 7193 except error.RepoError:
7177 7194 if opts.get(b'remote'):
7178 7195 raise
7179 7196 return dest, dbranch, None, None
7180 7197 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7181 7198 elif sother is None:
7182 7199 # there is no explicit destination peer, but source one is invalid
7183 7200 return dest, dbranch, None, None
7184 7201 else:
7185 7202 dother = sother
7186 7203 if source != dest or (sbranch is not None and sbranch != dbranch):
7187 7204 common = None
7188 7205 else:
7189 7206 common = commoninc
7190 7207 if revs:
7191 7208 revs = [repo.lookup(rev) for rev in revs]
7192 7209 repo.ui.pushbuffer()
7193 7210 outgoing = discovery.findcommonoutgoing(
7194 7211 repo, dother, onlyheads=revs, commoninc=common
7195 7212 )
7196 7213 repo.ui.popbuffer()
7197 7214 return dest, dbranch, dother, outgoing
7198 7215
7199 7216 if needsoutgoing:
7200 7217 dest, dbranch, dother, outgoing = getoutgoing()
7201 7218 else:
7202 7219 dest = dbranch = dother = outgoing = None
7203 7220
7204 7221 if opts.get(b'remote'):
7205 7222 t = []
7206 7223 if incoming:
7207 7224 t.append(_(b'1 or more incoming'))
7208 7225 o = outgoing.missing
7209 7226 if o:
7210 7227 t.append(_(b'%d outgoing') % len(o))
7211 7228 other = dother or sother
7212 7229 if b'bookmarks' in other.listkeys(b'namespaces'):
7213 7230 counts = bookmarks.summary(repo, other)
7214 7231 if counts[0] > 0:
7215 7232 t.append(_(b'%d incoming bookmarks') % counts[0])
7216 7233 if counts[1] > 0:
7217 7234 t.append(_(b'%d outgoing bookmarks') % counts[1])
7218 7235
7219 7236 if t:
7220 7237 # i18n: column positioning for "hg summary"
7221 7238 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7222 7239 else:
7223 7240 # i18n: column positioning for "hg summary"
7224 7241 ui.status(_(b'remote: (synced)\n'))
7225 7242
7226 7243 cmdutil.summaryremotehooks(
7227 7244 ui,
7228 7245 repo,
7229 7246 opts,
7230 7247 (
7231 7248 (source, sbranch, sother, commoninc),
7232 7249 (dest, dbranch, dother, outgoing),
7233 7250 ),
7234 7251 )
7235 7252
7236 7253
7237 7254 @command(
7238 7255 b'tag',
7239 7256 [
7240 7257 (b'f', b'force', None, _(b'force tag')),
7241 7258 (b'l', b'local', None, _(b'make the tag local')),
7242 7259 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7243 7260 (b'', b'remove', None, _(b'remove a tag')),
7244 7261 # -l/--local is already there, commitopts cannot be used
7245 7262 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7246 7263 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7247 7264 ]
7248 7265 + commitopts2,
7249 7266 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7250 7267 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7251 7268 )
7252 7269 def tag(ui, repo, name1, *names, **opts):
7253 7270 """add one or more tags for the current or given revision
7254 7271
7255 7272 Name a particular revision using <name>.
7256 7273
7257 7274 Tags are used to name particular revisions of the repository and are
7258 7275 very useful to compare different revisions, to go back to significant
7259 7276 earlier versions or to mark branch points as releases, etc. Changing
7260 7277 an existing tag is normally disallowed; use -f/--force to override.
7261 7278
7262 7279 If no revision is given, the parent of the working directory is
7263 7280 used.
7264 7281
7265 7282 To facilitate version control, distribution, and merging of tags,
7266 7283 they are stored as a file named ".hgtags" which is managed similarly
7267 7284 to other project files and can be hand-edited if necessary. This
7268 7285 also means that tagging creates a new commit. The file
7269 7286 ".hg/localtags" is used for local tags (not shared among
7270 7287 repositories).
7271 7288
7272 7289 Tag commits are usually made at the head of a branch. If the parent
7273 7290 of the working directory is not a branch head, :hg:`tag` aborts; use
7274 7291 -f/--force to force the tag commit to be based on a non-head
7275 7292 changeset.
7276 7293
7277 7294 See :hg:`help dates` for a list of formats valid for -d/--date.
7278 7295
7279 7296 Since tag names have priority over branch names during revision
7280 7297 lookup, using an existing branch name as a tag name is discouraged.
7281 7298
7282 7299 Returns 0 on success.
7283 7300 """
7284 7301 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7285 7302 opts = pycompat.byteskwargs(opts)
7286 7303 with repo.wlock(), repo.lock():
7287 7304 rev_ = b"."
7288 7305 names = [t.strip() for t in (name1,) + names]
7289 7306 if len(names) != len(set(names)):
7290 7307 raise error.InputError(_(b'tag names must be unique'))
7291 7308 for n in names:
7292 7309 scmutil.checknewlabel(repo, n, b'tag')
7293 7310 if not n:
7294 7311 raise error.InputError(
7295 7312 _(b'tag names cannot consist entirely of whitespace')
7296 7313 )
7297 7314 if opts.get(b'rev'):
7298 7315 rev_ = opts[b'rev']
7299 7316 message = opts.get(b'message')
7300 7317 if opts.get(b'remove'):
7301 7318 if opts.get(b'local'):
7302 7319 expectedtype = b'local'
7303 7320 else:
7304 7321 expectedtype = b'global'
7305 7322
7306 7323 for n in names:
7307 7324 if repo.tagtype(n) == b'global':
7308 7325 alltags = tagsmod.findglobaltags(ui, repo)
7309 7326 if alltags[n][0] == nullid:
7310 7327 raise error.InputError(
7311 7328 _(b"tag '%s' is already removed") % n
7312 7329 )
7313 7330 if not repo.tagtype(n):
7314 7331 raise error.InputError(_(b"tag '%s' does not exist") % n)
7315 7332 if repo.tagtype(n) != expectedtype:
7316 7333 if expectedtype == b'global':
7317 7334 raise error.InputError(
7318 7335 _(b"tag '%s' is not a global tag") % n
7319 7336 )
7320 7337 else:
7321 7338 raise error.InputError(
7322 7339 _(b"tag '%s' is not a local tag") % n
7323 7340 )
7324 7341 rev_ = b'null'
7325 7342 if not message:
7326 7343 # we don't translate commit messages
7327 7344 message = b'Removed tag %s' % b', '.join(names)
7328 7345 elif not opts.get(b'force'):
7329 7346 for n in names:
7330 7347 if n in repo.tags():
7331 7348 raise error.InputError(
7332 7349 _(b"tag '%s' already exists (use -f to force)") % n
7333 7350 )
7334 7351 if not opts.get(b'local'):
7335 7352 p1, p2 = repo.dirstate.parents()
7336 7353 if p2 != nullid:
7337 7354 raise error.StateError(_(b'uncommitted merge'))
7338 7355 bheads = repo.branchheads()
7339 7356 if not opts.get(b'force') and bheads and p1 not in bheads:
7340 7357 raise error.InputError(
7341 7358 _(
7342 7359 b'working directory is not at a branch head '
7343 7360 b'(use -f to force)'
7344 7361 )
7345 7362 )
7346 7363 node = scmutil.revsingle(repo, rev_).node()
7347 7364
7348 7365 if not message:
7349 7366 # we don't translate commit messages
7350 7367 message = b'Added tag %s for changeset %s' % (
7351 7368 b', '.join(names),
7352 7369 short(node),
7353 7370 )
7354 7371
7355 7372 date = opts.get(b'date')
7356 7373 if date:
7357 7374 date = dateutil.parsedate(date)
7358 7375
7359 7376 if opts.get(b'remove'):
7360 7377 editform = b'tag.remove'
7361 7378 else:
7362 7379 editform = b'tag.add'
7363 7380 editor = cmdutil.getcommiteditor(
7364 7381 editform=editform, **pycompat.strkwargs(opts)
7365 7382 )
7366 7383
7367 7384 # don't allow tagging the null rev
7368 7385 if (
7369 7386 not opts.get(b'remove')
7370 7387 and scmutil.revsingle(repo, rev_).rev() == nullrev
7371 7388 ):
7372 7389 raise error.InputError(_(b"cannot tag null revision"))
7373 7390
7374 7391 tagsmod.tag(
7375 7392 repo,
7376 7393 names,
7377 7394 node,
7378 7395 message,
7379 7396 opts.get(b'local'),
7380 7397 opts.get(b'user'),
7381 7398 date,
7382 7399 editor=editor,
7383 7400 )
7384 7401
7385 7402
7386 7403 @command(
7387 7404 b'tags',
7388 7405 formatteropts,
7389 7406 b'',
7390 7407 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7391 7408 intents={INTENT_READONLY},
7392 7409 )
7393 7410 def tags(ui, repo, **opts):
7394 7411 """list repository tags
7395 7412
7396 7413 This lists both regular and local tags. When the -v/--verbose
7397 7414 switch is used, a third column "local" is printed for local tags.
7398 7415 When the -q/--quiet switch is used, only the tag name is printed.
7399 7416
7400 7417 .. container:: verbose
7401 7418
7402 7419 Template:
7403 7420
7404 7421 The following keywords are supported in addition to the common template
7405 7422 keywords and functions such as ``{tag}``. See also
7406 7423 :hg:`help templates`.
7407 7424
7408 7425 :type: String. ``local`` for local tags.
7409 7426
7410 7427 Returns 0 on success.
7411 7428 """
7412 7429
7413 7430 opts = pycompat.byteskwargs(opts)
7414 7431 ui.pager(b'tags')
7415 7432 fm = ui.formatter(b'tags', opts)
7416 7433 hexfunc = fm.hexfunc
7417 7434
7418 7435 for t, n in reversed(repo.tagslist()):
7419 7436 hn = hexfunc(n)
7420 7437 label = b'tags.normal'
7421 7438 tagtype = repo.tagtype(t)
7422 7439 if not tagtype or tagtype == b'global':
7423 7440 tagtype = b''
7424 7441 else:
7425 7442 label = b'tags.' + tagtype
7426 7443
7427 7444 fm.startitem()
7428 7445 fm.context(repo=repo)
7429 7446 fm.write(b'tag', b'%s', t, label=label)
7430 7447 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7431 7448 fm.condwrite(
7432 7449 not ui.quiet,
7433 7450 b'rev node',
7434 7451 fmt,
7435 7452 repo.changelog.rev(n),
7436 7453 hn,
7437 7454 label=label,
7438 7455 )
7439 7456 fm.condwrite(
7440 7457 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7441 7458 )
7442 7459 fm.plain(b'\n')
7443 7460 fm.end()
7444 7461
7445 7462
7446 7463 @command(
7447 7464 b'tip',
7448 7465 [
7449 7466 (b'p', b'patch', None, _(b'show patch')),
7450 7467 (b'g', b'git', None, _(b'use git extended diff format')),
7451 7468 ]
7452 7469 + templateopts,
7453 7470 _(b'[-p] [-g]'),
7454 7471 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7455 7472 )
7456 7473 def tip(ui, repo, **opts):
7457 7474 """show the tip revision (DEPRECATED)
7458 7475
7459 7476 The tip revision (usually just called the tip) is the changeset
7460 7477 most recently added to the repository (and therefore the most
7461 7478 recently changed head).
7462 7479
7463 7480 If you have just made a commit, that commit will be the tip. If
7464 7481 you have just pulled changes from another repository, the tip of
7465 7482 that repository becomes the current tip. The "tip" tag is special
7466 7483 and cannot be renamed or assigned to a different changeset.
7467 7484
7468 7485 This command is deprecated, please use :hg:`heads` instead.
7469 7486
7470 7487 Returns 0 on success.
7471 7488 """
7472 7489 opts = pycompat.byteskwargs(opts)
7473 7490 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7474 7491 displayer.show(repo[b'tip'])
7475 7492 displayer.close()
7476 7493
7477 7494
7478 7495 @command(
7479 7496 b'unbundle',
7480 7497 [
7481 7498 (
7482 7499 b'u',
7483 7500 b'update',
7484 7501 None,
7485 7502 _(b'update to new branch head if changesets were unbundled'),
7486 7503 )
7487 7504 ],
7488 7505 _(b'[-u] FILE...'),
7489 7506 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7490 7507 )
7491 7508 def unbundle(ui, repo, fname1, *fnames, **opts):
7492 7509 """apply one or more bundle files
7493 7510
7494 7511 Apply one or more bundle files generated by :hg:`bundle`.
7495 7512
7496 7513 Returns 0 on success, 1 if an update has unresolved files.
7497 7514 """
7498 7515 fnames = (fname1,) + fnames
7499 7516
7500 7517 with repo.lock():
7501 7518 for fname in fnames:
7502 7519 f = hg.openpath(ui, fname)
7503 7520 gen = exchange.readbundle(ui, f, fname)
7504 7521 if isinstance(gen, streamclone.streamcloneapplier):
7505 7522 raise error.InputError(
7506 7523 _(
7507 7524 b'packed bundles cannot be applied with '
7508 7525 b'"hg unbundle"'
7509 7526 ),
7510 7527 hint=_(b'use "hg debugapplystreamclonebundle"'),
7511 7528 )
7512 7529 url = b'bundle:' + fname
7513 7530 try:
7514 7531 txnname = b'unbundle'
7515 7532 if not isinstance(gen, bundle2.unbundle20):
7516 7533 txnname = b'unbundle\n%s' % util.hidepassword(url)
7517 7534 with repo.transaction(txnname) as tr:
7518 7535 op = bundle2.applybundle(
7519 7536 repo, gen, tr, source=b'unbundle', url=url
7520 7537 )
7521 7538 except error.BundleUnknownFeatureError as exc:
7522 7539 raise error.Abort(
7523 7540 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7524 7541 hint=_(
7525 7542 b"see https://mercurial-scm.org/"
7526 7543 b"wiki/BundleFeature for more "
7527 7544 b"information"
7528 7545 ),
7529 7546 )
7530 7547 modheads = bundle2.combinechangegroupresults(op)
7531 7548
7532 7549 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7533 7550
7534 7551
7535 7552 @command(
7536 7553 b'unshelve',
7537 7554 [
7538 7555 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7539 7556 (
7540 7557 b'c',
7541 7558 b'continue',
7542 7559 None,
7543 7560 _(b'continue an incomplete unshelve operation'),
7544 7561 ),
7545 7562 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7546 7563 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7547 7564 (
7548 7565 b'n',
7549 7566 b'name',
7550 7567 b'',
7551 7568 _(b'restore shelved change with given name'),
7552 7569 _(b'NAME'),
7553 7570 ),
7554 7571 (b't', b'tool', b'', _(b'specify merge tool')),
7555 7572 (
7556 7573 b'',
7557 7574 b'date',
7558 7575 b'',
7559 7576 _(b'set date for temporary commits (DEPRECATED)'),
7560 7577 _(b'DATE'),
7561 7578 ),
7562 7579 ],
7563 7580 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7564 7581 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7565 7582 )
7566 7583 def unshelve(ui, repo, *shelved, **opts):
7567 7584 """restore a shelved change to the working directory
7568 7585
7569 7586 This command accepts an optional name of a shelved change to
7570 7587 restore. If none is given, the most recent shelved change is used.
7571 7588
7572 7589 If a shelved change is applied successfully, the bundle that
7573 7590 contains the shelved changes is moved to a backup location
7574 7591 (.hg/shelve-backup).
7575 7592
7576 7593 Since you can restore a shelved change on top of an arbitrary
7577 7594 commit, it is possible that unshelving will result in a conflict
7578 7595 between your changes and the commits you are unshelving onto. If
7579 7596 this occurs, you must resolve the conflict, then use
7580 7597 ``--continue`` to complete the unshelve operation. (The bundle
7581 7598 will not be moved until you successfully complete the unshelve.)
7582 7599
7583 7600 (Alternatively, you can use ``--abort`` to abandon an unshelve
7584 7601 that causes a conflict. This reverts the unshelved changes, and
7585 7602 leaves the bundle in place.)
7586 7603
7587 7604 If bare shelved change (without interactive, include and exclude
7588 7605 option) was done on newly created branch it would restore branch
7589 7606 information to the working directory.
7590 7607
7591 7608 After a successful unshelve, the shelved changes are stored in a
7592 7609 backup directory. Only the N most recent backups are kept. N
7593 7610 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7594 7611 configuration option.
7595 7612
7596 7613 .. container:: verbose
7597 7614
7598 7615 Timestamp in seconds is used to decide order of backups. More
7599 7616 than ``maxbackups`` backups are kept, if same timestamp
7600 7617 prevents from deciding exact order of them, for safety.
7601 7618
7602 7619 Selected changes can be unshelved with ``--interactive`` flag.
7603 7620 The working directory is updated with the selected changes, and
7604 7621 only the unselected changes remain shelved.
7605 7622 Note: The whole shelve is applied to working directory first before
7606 7623 running interactively. So, this will bring up all the conflicts between
7607 7624 working directory and the shelve, irrespective of which changes will be
7608 7625 unshelved.
7609 7626 """
7610 7627 with repo.wlock():
7611 7628 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7612 7629
7613 7630
7614 7631 statemod.addunfinished(
7615 7632 b'unshelve',
7616 7633 fname=b'shelvedstate',
7617 7634 continueflag=True,
7618 7635 abortfunc=shelvemod.hgabortunshelve,
7619 7636 continuefunc=shelvemod.hgcontinueunshelve,
7620 7637 cmdmsg=_(b'unshelve already in progress'),
7621 7638 )
7622 7639
7623 7640
7624 7641 @command(
7625 7642 b'update|up|checkout|co',
7626 7643 [
7627 7644 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7628 7645 (b'c', b'check', None, _(b'require clean working directory')),
7629 7646 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7630 7647 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7631 7648 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7632 7649 ]
7633 7650 + mergetoolopts,
7634 7651 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7635 7652 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7636 7653 helpbasic=True,
7637 7654 )
7638 7655 def update(ui, repo, node=None, **opts):
7639 7656 """update working directory (or switch revisions)
7640 7657
7641 7658 Update the repository's working directory to the specified
7642 7659 changeset. If no changeset is specified, update to the tip of the
7643 7660 current named branch and move the active bookmark (see :hg:`help
7644 7661 bookmarks`).
7645 7662
7646 7663 Update sets the working directory's parent revision to the specified
7647 7664 changeset (see :hg:`help parents`).
7648 7665
7649 7666 If the changeset is not a descendant or ancestor of the working
7650 7667 directory's parent and there are uncommitted changes, the update is
7651 7668 aborted. With the -c/--check option, the working directory is checked
7652 7669 for uncommitted changes; if none are found, the working directory is
7653 7670 updated to the specified changeset.
7654 7671
7655 7672 .. container:: verbose
7656 7673
7657 7674 The -C/--clean, -c/--check, and -m/--merge options control what
7658 7675 happens if the working directory contains uncommitted changes.
7659 7676 At most of one of them can be specified.
7660 7677
7661 7678 1. If no option is specified, and if
7662 7679 the requested changeset is an ancestor or descendant of
7663 7680 the working directory's parent, the uncommitted changes
7664 7681 are merged into the requested changeset and the merged
7665 7682 result is left uncommitted. If the requested changeset is
7666 7683 not an ancestor or descendant (that is, it is on another
7667 7684 branch), the update is aborted and the uncommitted changes
7668 7685 are preserved.
7669 7686
7670 7687 2. With the -m/--merge option, the update is allowed even if the
7671 7688 requested changeset is not an ancestor or descendant of
7672 7689 the working directory's parent.
7673 7690
7674 7691 3. With the -c/--check option, the update is aborted and the
7675 7692 uncommitted changes are preserved.
7676 7693
7677 7694 4. With the -C/--clean option, uncommitted changes are discarded and
7678 7695 the working directory is updated to the requested changeset.
7679 7696
7680 7697 To cancel an uncommitted merge (and lose your changes), use
7681 7698 :hg:`merge --abort`.
7682 7699
7683 7700 Use null as the changeset to remove the working directory (like
7684 7701 :hg:`clone -U`).
7685 7702
7686 7703 If you want to revert just one file to an older revision, use
7687 7704 :hg:`revert [-r REV] NAME`.
7688 7705
7689 7706 See :hg:`help dates` for a list of formats valid for -d/--date.
7690 7707
7691 7708 Returns 0 on success, 1 if there are unresolved files.
7692 7709 """
7693 7710 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7694 7711 rev = opts.get('rev')
7695 7712 date = opts.get('date')
7696 7713 clean = opts.get('clean')
7697 7714 check = opts.get('check')
7698 7715 merge = opts.get('merge')
7699 7716 if rev and node:
7700 7717 raise error.InputError(_(b"please specify just one revision"))
7701 7718
7702 7719 if ui.configbool(b'commands', b'update.requiredest'):
7703 7720 if not node and not rev and not date:
7704 7721 raise error.InputError(
7705 7722 _(b'you must specify a destination'),
7706 7723 hint=_(b'for example: hg update ".::"'),
7707 7724 )
7708 7725
7709 7726 if rev is None or rev == b'':
7710 7727 rev = node
7711 7728
7712 7729 if date and rev is not None:
7713 7730 raise error.InputError(_(b"you can't specify a revision and a date"))
7714 7731
7715 7732 updatecheck = None
7716 7733 if check:
7717 7734 updatecheck = b'abort'
7718 7735 elif merge:
7719 7736 updatecheck = b'none'
7720 7737
7721 7738 with repo.wlock():
7722 7739 cmdutil.clearunfinished(repo)
7723 7740 if date:
7724 7741 rev = cmdutil.finddate(ui, repo, date)
7725 7742
7726 7743 # if we defined a bookmark, we have to remember the original name
7727 7744 brev = rev
7728 7745 if rev:
7729 7746 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7730 7747 ctx = scmutil.revsingle(repo, rev, default=None)
7731 7748 rev = ctx.rev()
7732 7749 hidden = ctx.hidden()
7733 7750 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7734 7751 with ui.configoverride(overrides, b'update'):
7735 7752 ret = hg.updatetotally(
7736 7753 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7737 7754 )
7738 7755 if hidden:
7739 7756 ctxstr = ctx.hex()[:12]
7740 7757 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7741 7758
7742 7759 if ctx.obsolete():
7743 7760 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7744 7761 ui.warn(b"(%s)\n" % obsfatemsg)
7745 7762 return ret
7746 7763
7747 7764
7748 7765 @command(
7749 7766 b'verify',
7750 7767 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7751 7768 helpcategory=command.CATEGORY_MAINTENANCE,
7752 7769 )
7753 7770 def verify(ui, repo, **opts):
7754 7771 """verify the integrity of the repository
7755 7772
7756 7773 Verify the integrity of the current repository.
7757 7774
7758 7775 This will perform an extensive check of the repository's
7759 7776 integrity, validating the hashes and checksums of each entry in
7760 7777 the changelog, manifest, and tracked files, as well as the
7761 7778 integrity of their crosslinks and indices.
7762 7779
7763 7780 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7764 7781 for more information about recovery from corruption of the
7765 7782 repository.
7766 7783
7767 7784 Returns 0 on success, 1 if errors are encountered.
7768 7785 """
7769 7786 opts = pycompat.byteskwargs(opts)
7770 7787
7771 7788 level = None
7772 7789 if opts[b'full']:
7773 7790 level = verifymod.VERIFY_FULL
7774 7791 return hg.verify(repo, level)
7775 7792
7776 7793
7777 7794 @command(
7778 7795 b'version',
7779 7796 [] + formatteropts,
7780 7797 helpcategory=command.CATEGORY_HELP,
7781 7798 norepo=True,
7782 7799 intents={INTENT_READONLY},
7783 7800 )
7784 7801 def version_(ui, **opts):
7785 7802 """output version and copyright information
7786 7803
7787 7804 .. container:: verbose
7788 7805
7789 7806 Template:
7790 7807
7791 7808 The following keywords are supported. See also :hg:`help templates`.
7792 7809
7793 7810 :extensions: List of extensions.
7794 7811 :ver: String. Version number.
7795 7812
7796 7813 And each entry of ``{extensions}`` provides the following sub-keywords
7797 7814 in addition to ``{ver}``.
7798 7815
7799 7816 :bundled: Boolean. True if included in the release.
7800 7817 :name: String. Extension name.
7801 7818 """
7802 7819 opts = pycompat.byteskwargs(opts)
7803 7820 if ui.verbose:
7804 7821 ui.pager(b'version')
7805 7822 fm = ui.formatter(b"version", opts)
7806 7823 fm.startitem()
7807 7824 fm.write(
7808 7825 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7809 7826 )
7810 7827 license = _(
7811 7828 b"(see https://mercurial-scm.org for more information)\n"
7812 7829 b"\nCopyright (C) 2005-2021 Matt Mackall and others\n"
7813 7830 b"This is free software; see the source for copying conditions. "
7814 7831 b"There is NO\nwarranty; "
7815 7832 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7816 7833 )
7817 7834 if not ui.quiet:
7818 7835 fm.plain(license)
7819 7836
7820 7837 if ui.verbose:
7821 7838 fm.plain(_(b"\nEnabled extensions:\n\n"))
7822 7839 # format names and versions into columns
7823 7840 names = []
7824 7841 vers = []
7825 7842 isinternals = []
7826 7843 for name, module in sorted(extensions.extensions()):
7827 7844 names.append(name)
7828 7845 vers.append(extensions.moduleversion(module) or None)
7829 7846 isinternals.append(extensions.ismoduleinternal(module))
7830 7847 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7831 7848 if names:
7832 7849 namefmt = b" %%-%ds " % max(len(n) for n in names)
7833 7850 places = [_(b"external"), _(b"internal")]
7834 7851 for n, v, p in zip(names, vers, isinternals):
7835 7852 fn.startitem()
7836 7853 fn.condwrite(ui.verbose, b"name", namefmt, n)
7837 7854 if ui.verbose:
7838 7855 fn.plain(b"%s " % places[p])
7839 7856 fn.data(bundled=p)
7840 7857 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7841 7858 if ui.verbose:
7842 7859 fn.plain(b"\n")
7843 7860 fn.end()
7844 7861 fm.end()
7845 7862
7846 7863
7847 7864 def loadcmdtable(ui, name, cmdtable):
7848 7865 """Load command functions from specified cmdtable"""
7849 7866 overrides = [cmd for cmd in cmdtable if cmd in table]
7850 7867 if overrides:
7851 7868 ui.warn(
7852 7869 _(b"extension '%s' overrides commands: %s\n")
7853 7870 % (name, b" ".join(overrides))
7854 7871 )
7855 7872 table.update(cmdtable)
@@ -1,4758 +1,4773 b''
1 1 # debugcommands.py - command processing for debug* commands
2 2 #
3 3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import codecs
11 11 import collections
12 12 import difflib
13 13 import errno
14 14 import glob
15 15 import operator
16 16 import os
17 17 import platform
18 18 import random
19 19 import re
20 20 import socket
21 21 import ssl
22 22 import stat
23 23 import string
24 24 import subprocess
25 25 import sys
26 26 import time
27 27
28 28 from .i18n import _
29 29 from .node import (
30 30 bin,
31 31 hex,
32 32 nullid,
33 33 nullrev,
34 34 short,
35 35 )
36 36 from .pycompat import (
37 37 getattr,
38 38 open,
39 39 )
40 40 from . import (
41 41 bundle2,
42 42 bundlerepo,
43 43 changegroup,
44 44 cmdutil,
45 45 color,
46 46 context,
47 47 copies,
48 48 dagparser,
49 49 encoding,
50 50 error,
51 51 exchange,
52 52 extensions,
53 53 filemerge,
54 54 filesetlang,
55 55 formatter,
56 56 hg,
57 57 httppeer,
58 58 localrepo,
59 59 lock as lockmod,
60 60 logcmdutil,
61 61 mergestate as mergestatemod,
62 62 metadata,
63 63 obsolete,
64 64 obsutil,
65 65 pathutil,
66 66 phases,
67 67 policy,
68 68 pvec,
69 69 pycompat,
70 70 registrar,
71 71 repair,
72 72 repoview,
73 73 revlog,
74 74 revset,
75 75 revsetlang,
76 76 scmutil,
77 77 setdiscovery,
78 78 simplemerge,
79 79 sshpeer,
80 80 sslutil,
81 81 streamclone,
82 82 strip,
83 83 tags as tagsmod,
84 84 templater,
85 85 treediscovery,
86 86 upgrade,
87 87 url as urlmod,
88 88 util,
89 89 vfs as vfsmod,
90 90 wireprotoframing,
91 91 wireprotoserver,
92 92 wireprotov2peer,
93 93 )
94 94 from .utils import (
95 95 cborutil,
96 96 compression,
97 97 dateutil,
98 98 procutil,
99 99 stringutil,
100 100 )
101 101
102 102 from .revlogutils import (
103 103 deltas as deltautil,
104 104 nodemap,
105 105 sidedata,
106 106 )
107 107
108 108 release = lockmod.release
109 109
110 110 table = {}
111 111 table.update(strip.command._table)
112 112 command = registrar.command(table)
113 113
114 114
115 115 @command(b'debugancestor', [], _(b'[INDEX] REV1 REV2'), optionalrepo=True)
116 116 def debugancestor(ui, repo, *args):
117 117 """find the ancestor revision of two revisions in a given index"""
118 118 if len(args) == 3:
119 119 index, rev1, rev2 = args
120 120 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), index)
121 121 lookup = r.lookup
122 122 elif len(args) == 2:
123 123 if not repo:
124 124 raise error.Abort(
125 125 _(b'there is no Mercurial repository here (.hg not found)')
126 126 )
127 127 rev1, rev2 = args
128 128 r = repo.changelog
129 129 lookup = repo.lookup
130 130 else:
131 131 raise error.Abort(_(b'either two or three arguments required'))
132 132 a = r.ancestor(lookup(rev1), lookup(rev2))
133 133 ui.write(b'%d:%s\n' % (r.rev(a), hex(a)))
134 134
135 135
136 136 @command(b'debugantivirusrunning', [])
137 137 def debugantivirusrunning(ui, repo):
138 138 """attempt to trigger an antivirus scanner to see if one is active"""
139 139 with repo.cachevfs.open('eicar-test-file.com', b'wb') as f:
140 140 f.write(
141 141 util.b85decode(
142 142 # This is a base85-armored version of the EICAR test file. See
143 143 # https://en.wikipedia.org/wiki/EICAR_test_file for details.
144 144 b'ST#=}P$fV?P+K%yP+C|uG$>GBDK|qyDK~v2MM*<JQY}+dK~6+LQba95P'
145 145 b'E<)&Nm5l)EmTEQR4qnHOhq9iNGnJx'
146 146 )
147 147 )
148 148 # Give an AV engine time to scan the file.
149 149 time.sleep(2)
150 150 util.unlink(repo.cachevfs.join('eicar-test-file.com'))
151 151
152 152
153 153 @command(b'debugapplystreamclonebundle', [], b'FILE')
154 154 def debugapplystreamclonebundle(ui, repo, fname):
155 155 """apply a stream clone bundle file"""
156 156 f = hg.openpath(ui, fname)
157 157 gen = exchange.readbundle(ui, f, fname)
158 158 gen.apply(repo)
159 159
160 160
161 161 @command(
162 162 b'debugbuilddag',
163 163 [
164 164 (
165 165 b'm',
166 166 b'mergeable-file',
167 167 None,
168 168 _(b'add single file mergeable changes'),
169 169 ),
170 170 (
171 171 b'o',
172 172 b'overwritten-file',
173 173 None,
174 174 _(b'add single file all revs overwrite'),
175 175 ),
176 176 (b'n', b'new-file', None, _(b'add new file at each rev')),
177 177 ],
178 178 _(b'[OPTION]... [TEXT]'),
179 179 )
180 180 def debugbuilddag(
181 181 ui,
182 182 repo,
183 183 text=None,
184 184 mergeable_file=False,
185 185 overwritten_file=False,
186 186 new_file=False,
187 187 ):
188 188 """builds a repo with a given DAG from scratch in the current empty repo
189 189
190 190 The description of the DAG is read from stdin if not given on the
191 191 command line.
192 192
193 193 Elements:
194 194
195 195 - "+n" is a linear run of n nodes based on the current default parent
196 196 - "." is a single node based on the current default parent
197 197 - "$" resets the default parent to null (implied at the start);
198 198 otherwise the default parent is always the last node created
199 199 - "<p" sets the default parent to the backref p
200 200 - "*p" is a fork at parent p, which is a backref
201 201 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
202 202 - "/p2" is a merge of the preceding node and p2
203 203 - ":tag" defines a local tag for the preceding node
204 204 - "@branch" sets the named branch for subsequent nodes
205 205 - "#...\\n" is a comment up to the end of the line
206 206
207 207 Whitespace between the above elements is ignored.
208 208
209 209 A backref is either
210 210
211 211 - a number n, which references the node curr-n, where curr is the current
212 212 node, or
213 213 - the name of a local tag you placed earlier using ":tag", or
214 214 - empty to denote the default parent.
215 215
216 216 All string valued-elements are either strictly alphanumeric, or must
217 217 be enclosed in double quotes ("..."), with "\\" as escape character.
218 218 """
219 219
220 220 if text is None:
221 221 ui.status(_(b"reading DAG from stdin\n"))
222 222 text = ui.fin.read()
223 223
224 224 cl = repo.changelog
225 225 if len(cl) > 0:
226 226 raise error.Abort(_(b'repository is not empty'))
227 227
228 228 # determine number of revs in DAG
229 229 total = 0
230 230 for type, data in dagparser.parsedag(text):
231 231 if type == b'n':
232 232 total += 1
233 233
234 234 if mergeable_file:
235 235 linesperrev = 2
236 236 # make a file with k lines per rev
237 237 initialmergedlines = [
238 238 b'%d' % i for i in pycompat.xrange(0, total * linesperrev)
239 239 ]
240 240 initialmergedlines.append(b"")
241 241
242 242 tags = []
243 243 progress = ui.makeprogress(
244 244 _(b'building'), unit=_(b'revisions'), total=total
245 245 )
246 246 with progress, repo.wlock(), repo.lock(), repo.transaction(b"builddag"):
247 247 at = -1
248 248 atbranch = b'default'
249 249 nodeids = []
250 250 id = 0
251 251 progress.update(id)
252 252 for type, data in dagparser.parsedag(text):
253 253 if type == b'n':
254 254 ui.note((b'node %s\n' % pycompat.bytestr(data)))
255 255 id, ps = data
256 256
257 257 files = []
258 258 filecontent = {}
259 259
260 260 p2 = None
261 261 if mergeable_file:
262 262 fn = b"mf"
263 263 p1 = repo[ps[0]]
264 264 if len(ps) > 1:
265 265 p2 = repo[ps[1]]
266 266 pa = p1.ancestor(p2)
267 267 base, local, other = [
268 268 x[fn].data() for x in (pa, p1, p2)
269 269 ]
270 270 m3 = simplemerge.Merge3Text(base, local, other)
271 271 ml = [l.strip() for l in m3.merge_lines()]
272 272 ml.append(b"")
273 273 elif at > 0:
274 274 ml = p1[fn].data().split(b"\n")
275 275 else:
276 276 ml = initialmergedlines
277 277 ml[id * linesperrev] += b" r%i" % id
278 278 mergedtext = b"\n".join(ml)
279 279 files.append(fn)
280 280 filecontent[fn] = mergedtext
281 281
282 282 if overwritten_file:
283 283 fn = b"of"
284 284 files.append(fn)
285 285 filecontent[fn] = b"r%i\n" % id
286 286
287 287 if new_file:
288 288 fn = b"nf%i" % id
289 289 files.append(fn)
290 290 filecontent[fn] = b"r%i\n" % id
291 291 if len(ps) > 1:
292 292 if not p2:
293 293 p2 = repo[ps[1]]
294 294 for fn in p2:
295 295 if fn.startswith(b"nf"):
296 296 files.append(fn)
297 297 filecontent[fn] = p2[fn].data()
298 298
299 299 def fctxfn(repo, cx, path):
300 300 if path in filecontent:
301 301 return context.memfilectx(
302 302 repo, cx, path, filecontent[path]
303 303 )
304 304 return None
305 305
306 306 if len(ps) == 0 or ps[0] < 0:
307 307 pars = [None, None]
308 308 elif len(ps) == 1:
309 309 pars = [nodeids[ps[0]], None]
310 310 else:
311 311 pars = [nodeids[p] for p in ps]
312 312 cx = context.memctx(
313 313 repo,
314 314 pars,
315 315 b"r%i" % id,
316 316 files,
317 317 fctxfn,
318 318 date=(id, 0),
319 319 user=b"debugbuilddag",
320 320 extra={b'branch': atbranch},
321 321 )
322 322 nodeid = repo.commitctx(cx)
323 323 nodeids.append(nodeid)
324 324 at = id
325 325 elif type == b'l':
326 326 id, name = data
327 327 ui.note((b'tag %s\n' % name))
328 328 tags.append(b"%s %s\n" % (hex(repo.changelog.node(id)), name))
329 329 elif type == b'a':
330 330 ui.note((b'branch %s\n' % data))
331 331 atbranch = data
332 332 progress.update(id)
333 333
334 334 if tags:
335 335 repo.vfs.write(b"localtags", b"".join(tags))
336 336
337 337
338 338 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
339 339 indent_string = b' ' * indent
340 340 if all:
341 341 ui.writenoi18n(
342 342 b"%sformat: id, p1, p2, cset, delta base, len(delta)\n"
343 343 % indent_string
344 344 )
345 345
346 346 def showchunks(named):
347 347 ui.write(b"\n%s%s\n" % (indent_string, named))
348 348 for deltadata in gen.deltaiter():
349 349 node, p1, p2, cs, deltabase, delta, flags = deltadata
350 350 ui.write(
351 351 b"%s%s %s %s %s %s %d\n"
352 352 % (
353 353 indent_string,
354 354 hex(node),
355 355 hex(p1),
356 356 hex(p2),
357 357 hex(cs),
358 358 hex(deltabase),
359 359 len(delta),
360 360 )
361 361 )
362 362
363 363 gen.changelogheader()
364 364 showchunks(b"changelog")
365 365 gen.manifestheader()
366 366 showchunks(b"manifest")
367 367 for chunkdata in iter(gen.filelogheader, {}):
368 368 fname = chunkdata[b'filename']
369 369 showchunks(fname)
370 370 else:
371 371 if isinstance(gen, bundle2.unbundle20):
372 372 raise error.Abort(_(b'use debugbundle2 for this file'))
373 373 gen.changelogheader()
374 374 for deltadata in gen.deltaiter():
375 375 node, p1, p2, cs, deltabase, delta, flags = deltadata
376 376 ui.write(b"%s%s\n" % (indent_string, hex(node)))
377 377
378 378
379 379 def _debugobsmarkers(ui, part, indent=0, **opts):
380 380 """display version and markers contained in 'data'"""
381 381 opts = pycompat.byteskwargs(opts)
382 382 data = part.read()
383 383 indent_string = b' ' * indent
384 384 try:
385 385 version, markers = obsolete._readmarkers(data)
386 386 except error.UnknownVersion as exc:
387 387 msg = b"%sunsupported version: %s (%d bytes)\n"
388 388 msg %= indent_string, exc.version, len(data)
389 389 ui.write(msg)
390 390 else:
391 391 msg = b"%sversion: %d (%d bytes)\n"
392 392 msg %= indent_string, version, len(data)
393 393 ui.write(msg)
394 394 fm = ui.formatter(b'debugobsolete', opts)
395 395 for rawmarker in sorted(markers):
396 396 m = obsutil.marker(None, rawmarker)
397 397 fm.startitem()
398 398 fm.plain(indent_string)
399 399 cmdutil.showmarker(fm, m)
400 400 fm.end()
401 401
402 402
403 403 def _debugphaseheads(ui, data, indent=0):
404 404 """display version and markers contained in 'data'"""
405 405 indent_string = b' ' * indent
406 406 headsbyphase = phases.binarydecode(data)
407 407 for phase in phases.allphases:
408 408 for head in headsbyphase[phase]:
409 409 ui.write(indent_string)
410 410 ui.write(b'%s %s\n' % (hex(head), phases.phasenames[phase]))
411 411
412 412
413 413 def _quasirepr(thing):
414 414 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
415 415 return b'{%s}' % (
416 416 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing))
417 417 )
418 418 return pycompat.bytestr(repr(thing))
419 419
420 420
421 421 def _debugbundle2(ui, gen, all=None, **opts):
422 422 """lists the contents of a bundle2"""
423 423 if not isinstance(gen, bundle2.unbundle20):
424 424 raise error.Abort(_(b'not a bundle2 file'))
425 425 ui.write((b'Stream params: %s\n' % _quasirepr(gen.params)))
426 426 parttypes = opts.get('part_type', [])
427 427 for part in gen.iterparts():
428 428 if parttypes and part.type not in parttypes:
429 429 continue
430 430 msg = b'%s -- %s (mandatory: %r)\n'
431 431 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
432 432 if part.type == b'changegroup':
433 433 version = part.params.get(b'version', b'01')
434 434 cg = changegroup.getunbundler(version, part, b'UN')
435 435 if not ui.quiet:
436 436 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
437 437 if part.type == b'obsmarkers':
438 438 if not ui.quiet:
439 439 _debugobsmarkers(ui, part, indent=4, **opts)
440 440 if part.type == b'phase-heads':
441 441 if not ui.quiet:
442 442 _debugphaseheads(ui, part, indent=4)
443 443
444 444
445 445 @command(
446 446 b'debugbundle',
447 447 [
448 448 (b'a', b'all', None, _(b'show all details')),
449 449 (b'', b'part-type', [], _(b'show only the named part type')),
450 450 (b'', b'spec', None, _(b'print the bundlespec of the bundle')),
451 451 ],
452 452 _(b'FILE'),
453 453 norepo=True,
454 454 )
455 455 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
456 456 """lists the contents of a bundle"""
457 457 with hg.openpath(ui, bundlepath) as f:
458 458 if spec:
459 459 spec = exchange.getbundlespec(ui, f)
460 460 ui.write(b'%s\n' % spec)
461 461 return
462 462
463 463 gen = exchange.readbundle(ui, f, bundlepath)
464 464 if isinstance(gen, bundle2.unbundle20):
465 465 return _debugbundle2(ui, gen, all=all, **opts)
466 466 _debugchangegroup(ui, gen, all=all, **opts)
467 467
468 468
469 469 @command(b'debugcapabilities', [], _(b'PATH'), norepo=True)
470 470 def debugcapabilities(ui, path, **opts):
471 471 """lists the capabilities of a remote peer"""
472 472 opts = pycompat.byteskwargs(opts)
473 473 peer = hg.peer(ui, opts, path)
474 caps = peer.capabilities()
475 ui.writenoi18n(b'Main capabilities:\n')
476 for c in sorted(caps):
477 ui.write(b' %s\n' % c)
478 b2caps = bundle2.bundle2caps(peer)
479 if b2caps:
480 ui.writenoi18n(b'Bundle2 capabilities:\n')
481 for key, values in sorted(pycompat.iteritems(b2caps)):
482 ui.write(b' %s\n' % key)
483 for v in values:
484 ui.write(b' %s\n' % v)
474 try:
475 caps = peer.capabilities()
476 ui.writenoi18n(b'Main capabilities:\n')
477 for c in sorted(caps):
478 ui.write(b' %s\n' % c)
479 b2caps = bundle2.bundle2caps(peer)
480 if b2caps:
481 ui.writenoi18n(b'Bundle2 capabilities:\n')
482 for key, values in sorted(pycompat.iteritems(b2caps)):
483 ui.write(b' %s\n' % key)
484 for v in values:
485 ui.write(b' %s\n' % v)
486 finally:
487 peer.close()
485 488
486 489
487 490 @command(
488 491 b'debugchangedfiles',
489 492 [
490 493 (
491 494 b'',
492 495 b'compute',
493 496 False,
494 497 b"compute information instead of reading it from storage",
495 498 ),
496 499 ],
497 500 b'REV',
498 501 )
499 502 def debugchangedfiles(ui, repo, rev, **opts):
500 503 """list the stored files changes for a revision"""
501 504 ctx = scmutil.revsingle(repo, rev, None)
502 505 files = None
503 506
504 507 if opts['compute']:
505 508 files = metadata.compute_all_files_changes(ctx)
506 509 else:
507 510 sd = repo.changelog.sidedata(ctx.rev())
508 511 files_block = sd.get(sidedata.SD_FILES)
509 512 if files_block is not None:
510 513 files = metadata.decode_files_sidedata(sd)
511 514 if files is not None:
512 515 for f in sorted(files.touched):
513 516 if f in files.added:
514 517 action = b"added"
515 518 elif f in files.removed:
516 519 action = b"removed"
517 520 elif f in files.merged:
518 521 action = b"merged"
519 522 elif f in files.salvaged:
520 523 action = b"salvaged"
521 524 else:
522 525 action = b"touched"
523 526
524 527 copy_parent = b""
525 528 copy_source = b""
526 529 if f in files.copied_from_p1:
527 530 copy_parent = b"p1"
528 531 copy_source = files.copied_from_p1[f]
529 532 elif f in files.copied_from_p2:
530 533 copy_parent = b"p2"
531 534 copy_source = files.copied_from_p2[f]
532 535
533 536 data = (action, copy_parent, f, copy_source)
534 537 template = b"%-8s %2s: %s, %s;\n"
535 538 ui.write(template % data)
536 539
537 540
538 541 @command(b'debugcheckstate', [], b'')
539 542 def debugcheckstate(ui, repo):
540 543 """validate the correctness of the current dirstate"""
541 544 parent1, parent2 = repo.dirstate.parents()
542 545 m1 = repo[parent1].manifest()
543 546 m2 = repo[parent2].manifest()
544 547 errors = 0
545 548 for f in repo.dirstate:
546 549 state = repo.dirstate[f]
547 550 if state in b"nr" and f not in m1:
548 551 ui.warn(_(b"%s in state %s, but not in manifest1\n") % (f, state))
549 552 errors += 1
550 553 if state in b"a" and f in m1:
551 554 ui.warn(_(b"%s in state %s, but also in manifest1\n") % (f, state))
552 555 errors += 1
553 556 if state in b"m" and f not in m1 and f not in m2:
554 557 ui.warn(
555 558 _(b"%s in state %s, but not in either manifest\n") % (f, state)
556 559 )
557 560 errors += 1
558 561 for f in m1:
559 562 state = repo.dirstate[f]
560 563 if state not in b"nrm":
561 564 ui.warn(_(b"%s in manifest1, but listed as state %s") % (f, state))
562 565 errors += 1
563 566 if errors:
564 567 errstr = _(b".hg/dirstate inconsistent with current parent's manifest")
565 568 raise error.Abort(errstr)
566 569
567 570
568 571 @command(
569 572 b'debugcolor',
570 573 [(b'', b'style', None, _(b'show all configured styles'))],
571 574 b'hg debugcolor',
572 575 )
573 576 def debugcolor(ui, repo, **opts):
574 577 """show available color, effects or style"""
575 578 ui.writenoi18n(b'color mode: %s\n' % stringutil.pprint(ui._colormode))
576 579 if opts.get('style'):
577 580 return _debugdisplaystyle(ui)
578 581 else:
579 582 return _debugdisplaycolor(ui)
580 583
581 584
582 585 def _debugdisplaycolor(ui):
583 586 ui = ui.copy()
584 587 ui._styles.clear()
585 588 for effect in color._activeeffects(ui).keys():
586 589 ui._styles[effect] = effect
587 590 if ui._terminfoparams:
588 591 for k, v in ui.configitems(b'color'):
589 592 if k.startswith(b'color.'):
590 593 ui._styles[k] = k[6:]
591 594 elif k.startswith(b'terminfo.'):
592 595 ui._styles[k] = k[9:]
593 596 ui.write(_(b'available colors:\n'))
594 597 # sort label with a '_' after the other to group '_background' entry.
595 598 items = sorted(ui._styles.items(), key=lambda i: (b'_' in i[0], i[0], i[1]))
596 599 for colorname, label in items:
597 600 ui.write(b'%s\n' % colorname, label=label)
598 601
599 602
600 603 def _debugdisplaystyle(ui):
601 604 ui.write(_(b'available style:\n'))
602 605 if not ui._styles:
603 606 return
604 607 width = max(len(s) for s in ui._styles)
605 608 for label, effects in sorted(ui._styles.items()):
606 609 ui.write(b'%s' % label, label=label)
607 610 if effects:
608 611 # 50
609 612 ui.write(b': ')
610 613 ui.write(b' ' * (max(0, width - len(label))))
611 614 ui.write(b', '.join(ui.label(e, e) for e in effects.split()))
612 615 ui.write(b'\n')
613 616
614 617
615 618 @command(b'debugcreatestreamclonebundle', [], b'FILE')
616 619 def debugcreatestreamclonebundle(ui, repo, fname):
617 620 """create a stream clone bundle file
618 621
619 622 Stream bundles are special bundles that are essentially archives of
620 623 revlog files. They are commonly used for cloning very quickly.
621 624 """
622 625 # TODO we may want to turn this into an abort when this functionality
623 626 # is moved into `hg bundle`.
624 627 if phases.hassecret(repo):
625 628 ui.warn(
626 629 _(
627 630 b'(warning: stream clone bundle will contain secret '
628 631 b'revisions)\n'
629 632 )
630 633 )
631 634
632 635 requirements, gen = streamclone.generatebundlev1(repo)
633 636 changegroup.writechunks(ui, gen, fname)
634 637
635 638 ui.write(_(b'bundle requirements: %s\n') % b', '.join(sorted(requirements)))
636 639
637 640
638 641 @command(
639 642 b'debugdag',
640 643 [
641 644 (b't', b'tags', None, _(b'use tags as labels')),
642 645 (b'b', b'branches', None, _(b'annotate with branch names')),
643 646 (b'', b'dots', None, _(b'use dots for runs')),
644 647 (b's', b'spaces', None, _(b'separate elements by spaces')),
645 648 ],
646 649 _(b'[OPTION]... [FILE [REV]...]'),
647 650 optionalrepo=True,
648 651 )
649 652 def debugdag(ui, repo, file_=None, *revs, **opts):
650 653 """format the changelog or an index DAG as a concise textual description
651 654
652 655 If you pass a revlog index, the revlog's DAG is emitted. If you list
653 656 revision numbers, they get labeled in the output as rN.
654 657
655 658 Otherwise, the changelog DAG of the current repo is emitted.
656 659 """
657 660 spaces = opts.get('spaces')
658 661 dots = opts.get('dots')
659 662 if file_:
660 663 rlog = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), file_)
661 664 revs = {int(r) for r in revs}
662 665
663 666 def events():
664 667 for r in rlog:
665 668 yield b'n', (r, list(p for p in rlog.parentrevs(r) if p != -1))
666 669 if r in revs:
667 670 yield b'l', (r, b"r%i" % r)
668 671
669 672 elif repo:
670 673 cl = repo.changelog
671 674 tags = opts.get('tags')
672 675 branches = opts.get('branches')
673 676 if tags:
674 677 labels = {}
675 678 for l, n in repo.tags().items():
676 679 labels.setdefault(cl.rev(n), []).append(l)
677 680
678 681 def events():
679 682 b = b"default"
680 683 for r in cl:
681 684 if branches:
682 685 newb = cl.read(cl.node(r))[5][b'branch']
683 686 if newb != b:
684 687 yield b'a', newb
685 688 b = newb
686 689 yield b'n', (r, list(p for p in cl.parentrevs(r) if p != -1))
687 690 if tags:
688 691 ls = labels.get(r)
689 692 if ls:
690 693 for l in ls:
691 694 yield b'l', (r, l)
692 695
693 696 else:
694 697 raise error.Abort(_(b'need repo for changelog dag'))
695 698
696 699 for line in dagparser.dagtextlines(
697 700 events(),
698 701 addspaces=spaces,
699 702 wraplabels=True,
700 703 wrapannotations=True,
701 704 wrapnonlinear=dots,
702 705 usedots=dots,
703 706 maxlinewidth=70,
704 707 ):
705 708 ui.write(line)
706 709 ui.write(b"\n")
707 710
708 711
709 712 @command(b'debugdata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
710 713 def debugdata(ui, repo, file_, rev=None, **opts):
711 714 """dump the contents of a data file revision"""
712 715 opts = pycompat.byteskwargs(opts)
713 716 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
714 717 if rev is not None:
715 718 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
716 719 file_, rev = None, file_
717 720 elif rev is None:
718 721 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
719 722 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
720 723 try:
721 724 ui.write(r.rawdata(r.lookup(rev)))
722 725 except KeyError:
723 726 raise error.Abort(_(b'invalid revision identifier %s') % rev)
724 727
725 728
726 729 @command(
727 730 b'debugdate',
728 731 [(b'e', b'extended', None, _(b'try extended date formats'))],
729 732 _(b'[-e] DATE [RANGE]'),
730 733 norepo=True,
731 734 optionalrepo=True,
732 735 )
733 736 def debugdate(ui, date, range=None, **opts):
734 737 """parse and display a date"""
735 738 if opts["extended"]:
736 739 d = dateutil.parsedate(date, dateutil.extendeddateformats)
737 740 else:
738 741 d = dateutil.parsedate(date)
739 742 ui.writenoi18n(b"internal: %d %d\n" % d)
740 743 ui.writenoi18n(b"standard: %s\n" % dateutil.datestr(d))
741 744 if range:
742 745 m = dateutil.matchdate(range)
743 746 ui.writenoi18n(b"match: %s\n" % m(d[0]))
744 747
745 748
746 749 @command(
747 750 b'debugdeltachain',
748 751 cmdutil.debugrevlogopts + cmdutil.formatteropts,
749 752 _(b'-c|-m|FILE'),
750 753 optionalrepo=True,
751 754 )
752 755 def debugdeltachain(ui, repo, file_=None, **opts):
753 756 """dump information about delta chains in a revlog
754 757
755 758 Output can be templatized. Available template keywords are:
756 759
757 760 :``rev``: revision number
758 761 :``chainid``: delta chain identifier (numbered by unique base)
759 762 :``chainlen``: delta chain length to this revision
760 763 :``prevrev``: previous revision in delta chain
761 764 :``deltatype``: role of delta / how it was computed
762 765 :``compsize``: compressed size of revision
763 766 :``uncompsize``: uncompressed size of revision
764 767 :``chainsize``: total size of compressed revisions in chain
765 768 :``chainratio``: total chain size divided by uncompressed revision size
766 769 (new delta chains typically start at ratio 2.00)
767 770 :``lindist``: linear distance from base revision in delta chain to end
768 771 of this revision
769 772 :``extradist``: total size of revisions not part of this delta chain from
770 773 base of delta chain to end of this revision; a measurement
771 774 of how much extra data we need to read/seek across to read
772 775 the delta chain for this revision
773 776 :``extraratio``: extradist divided by chainsize; another representation of
774 777 how much unrelated data is needed to load this delta chain
775 778
776 779 If the repository is configured to use the sparse read, additional keywords
777 780 are available:
778 781
779 782 :``readsize``: total size of data read from the disk for a revision
780 783 (sum of the sizes of all the blocks)
781 784 :``largestblock``: size of the largest block of data read from the disk
782 785 :``readdensity``: density of useful bytes in the data read from the disk
783 786 :``srchunks``: in how many data hunks the whole revision would be read
784 787
785 788 The sparse read can be enabled with experimental.sparse-read = True
786 789 """
787 790 opts = pycompat.byteskwargs(opts)
788 791 r = cmdutil.openrevlog(repo, b'debugdeltachain', file_, opts)
789 792 index = r.index
790 793 start = r.start
791 794 length = r.length
792 795 generaldelta = r.version & revlog.FLAG_GENERALDELTA
793 796 withsparseread = getattr(r, '_withsparseread', False)
794 797
795 798 def revinfo(rev):
796 799 e = index[rev]
797 800 compsize = e[1]
798 801 uncompsize = e[2]
799 802 chainsize = 0
800 803
801 804 if generaldelta:
802 805 if e[3] == e[5]:
803 806 deltatype = b'p1'
804 807 elif e[3] == e[6]:
805 808 deltatype = b'p2'
806 809 elif e[3] == rev - 1:
807 810 deltatype = b'prev'
808 811 elif e[3] == rev:
809 812 deltatype = b'base'
810 813 else:
811 814 deltatype = b'other'
812 815 else:
813 816 if e[3] == rev:
814 817 deltatype = b'base'
815 818 else:
816 819 deltatype = b'prev'
817 820
818 821 chain = r._deltachain(rev)[0]
819 822 for iterrev in chain:
820 823 e = index[iterrev]
821 824 chainsize += e[1]
822 825
823 826 return compsize, uncompsize, deltatype, chain, chainsize
824 827
825 828 fm = ui.formatter(b'debugdeltachain', opts)
826 829
827 830 fm.plain(
828 831 b' rev chain# chainlen prev delta '
829 832 b'size rawsize chainsize ratio lindist extradist '
830 833 b'extraratio'
831 834 )
832 835 if withsparseread:
833 836 fm.plain(b' readsize largestblk rddensity srchunks')
834 837 fm.plain(b'\n')
835 838
836 839 chainbases = {}
837 840 for rev in r:
838 841 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
839 842 chainbase = chain[0]
840 843 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
841 844 basestart = start(chainbase)
842 845 revstart = start(rev)
843 846 lineardist = revstart + comp - basestart
844 847 extradist = lineardist - chainsize
845 848 try:
846 849 prevrev = chain[-2]
847 850 except IndexError:
848 851 prevrev = -1
849 852
850 853 if uncomp != 0:
851 854 chainratio = float(chainsize) / float(uncomp)
852 855 else:
853 856 chainratio = chainsize
854 857
855 858 if chainsize != 0:
856 859 extraratio = float(extradist) / float(chainsize)
857 860 else:
858 861 extraratio = extradist
859 862
860 863 fm.startitem()
861 864 fm.write(
862 865 b'rev chainid chainlen prevrev deltatype compsize '
863 866 b'uncompsize chainsize chainratio lindist extradist '
864 867 b'extraratio',
865 868 b'%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
866 869 rev,
867 870 chainid,
868 871 len(chain),
869 872 prevrev,
870 873 deltatype,
871 874 comp,
872 875 uncomp,
873 876 chainsize,
874 877 chainratio,
875 878 lineardist,
876 879 extradist,
877 880 extraratio,
878 881 rev=rev,
879 882 chainid=chainid,
880 883 chainlen=len(chain),
881 884 prevrev=prevrev,
882 885 deltatype=deltatype,
883 886 compsize=comp,
884 887 uncompsize=uncomp,
885 888 chainsize=chainsize,
886 889 chainratio=chainratio,
887 890 lindist=lineardist,
888 891 extradist=extradist,
889 892 extraratio=extraratio,
890 893 )
891 894 if withsparseread:
892 895 readsize = 0
893 896 largestblock = 0
894 897 srchunks = 0
895 898
896 899 for revschunk in deltautil.slicechunk(r, chain):
897 900 srchunks += 1
898 901 blkend = start(revschunk[-1]) + length(revschunk[-1])
899 902 blksize = blkend - start(revschunk[0])
900 903
901 904 readsize += blksize
902 905 if largestblock < blksize:
903 906 largestblock = blksize
904 907
905 908 if readsize:
906 909 readdensity = float(chainsize) / float(readsize)
907 910 else:
908 911 readdensity = 1
909 912
910 913 fm.write(
911 914 b'readsize largestblock readdensity srchunks',
912 915 b' %10d %10d %9.5f %8d',
913 916 readsize,
914 917 largestblock,
915 918 readdensity,
916 919 srchunks,
917 920 readsize=readsize,
918 921 largestblock=largestblock,
919 922 readdensity=readdensity,
920 923 srchunks=srchunks,
921 924 )
922 925
923 926 fm.plain(b'\n')
924 927
925 928 fm.end()
926 929
927 930
928 931 @command(
929 932 b'debugdirstate|debugstate',
930 933 [
931 934 (
932 935 b'',
933 936 b'nodates',
934 937 None,
935 938 _(b'do not display the saved mtime (DEPRECATED)'),
936 939 ),
937 940 (b'', b'dates', True, _(b'display the saved mtime')),
938 941 (b'', b'datesort', None, _(b'sort by saved mtime')),
939 942 ],
940 943 _(b'[OPTION]...'),
941 944 )
942 945 def debugstate(ui, repo, **opts):
943 946 """show the contents of the current dirstate"""
944 947
945 948 nodates = not opts['dates']
946 949 if opts.get('nodates') is not None:
947 950 nodates = True
948 951 datesort = opts.get('datesort')
949 952
950 953 if datesort:
951 954 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
952 955 else:
953 956 keyfunc = None # sort by filename
954 957 for file_, ent in sorted(pycompat.iteritems(repo.dirstate), key=keyfunc):
955 958 if ent[3] == -1:
956 959 timestr = b'unset '
957 960 elif nodates:
958 961 timestr = b'set '
959 962 else:
960 963 timestr = time.strftime(
961 964 "%Y-%m-%d %H:%M:%S ", time.localtime(ent[3])
962 965 )
963 966 timestr = encoding.strtolocal(timestr)
964 967 if ent[1] & 0o20000:
965 968 mode = b'lnk'
966 969 else:
967 970 mode = b'%3o' % (ent[1] & 0o777 & ~util.umask)
968 971 ui.write(b"%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
969 972 for f in repo.dirstate.copies():
970 973 ui.write(_(b"copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
971 974
972 975
973 976 @command(
974 977 b'debugdiscovery',
975 978 [
976 979 (b'', b'old', None, _(b'use old-style discovery')),
977 980 (
978 981 b'',
979 982 b'nonheads',
980 983 None,
981 984 _(b'use old-style discovery with non-heads included'),
982 985 ),
983 986 (b'', b'rev', [], b'restrict discovery to this set of revs'),
984 987 (b'', b'seed', b'12323', b'specify the random seed use for discovery'),
985 988 (
986 989 b'',
987 990 b'local-as-revs',
988 991 "",
989 992 'treat local has having these revisions only',
990 993 ),
991 994 (
992 995 b'',
993 996 b'remote-as-revs',
994 997 "",
995 998 'use local as remote, with only these these revisions',
996 999 ),
997 1000 ]
998 1001 + cmdutil.remoteopts,
999 1002 _(b'[--rev REV] [OTHER]'),
1000 1003 )
1001 1004 def debugdiscovery(ui, repo, remoteurl=b"default", **opts):
1002 1005 """runs the changeset discovery protocol in isolation
1003 1006
1004 1007 The local peer can be "replaced" by a subset of the local repository by
1005 1008 using the `--local-as-revs` flag. Int he same way, usual `remote` peer can
1006 1009 be "replaced" by a subset of the local repository using the
1007 1010 `--local-as-revs` flag. This is useful to efficiently debug pathological
1008 1011 discovery situation.
1009 1012 """
1010 1013 opts = pycompat.byteskwargs(opts)
1011 1014 unfi = repo.unfiltered()
1012 1015
1013 1016 # setup potential extra filtering
1014 1017 local_revs = opts[b"local_as_revs"]
1015 1018 remote_revs = opts[b"remote_as_revs"]
1016 1019
1017 1020 # make sure tests are repeatable
1018 1021 random.seed(int(opts[b'seed']))
1019 1022
1020 1023 if not remote_revs:
1021 1024
1022 1025 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
1023 1026 remote = hg.peer(repo, opts, remoteurl)
1024 1027 ui.status(_(b'comparing with %s\n') % util.hidepassword(remoteurl))
1025 1028 else:
1026 1029 branches = (None, [])
1027 1030 remote_filtered_revs = scmutil.revrange(
1028 1031 unfi, [b"not (::(%s))" % remote_revs]
1029 1032 )
1030 1033 remote_filtered_revs = frozenset(remote_filtered_revs)
1031 1034
1032 1035 def remote_func(x):
1033 1036 return remote_filtered_revs
1034 1037
1035 1038 repoview.filtertable[b'debug-discovery-remote-filter'] = remote_func
1036 1039
1037 1040 remote = repo.peer()
1038 1041 remote._repo = remote._repo.filtered(b'debug-discovery-remote-filter')
1039 1042
1040 1043 if local_revs:
1041 1044 local_filtered_revs = scmutil.revrange(
1042 1045 unfi, [b"not (::(%s))" % local_revs]
1043 1046 )
1044 1047 local_filtered_revs = frozenset(local_filtered_revs)
1045 1048
1046 1049 def local_func(x):
1047 1050 return local_filtered_revs
1048 1051
1049 1052 repoview.filtertable[b'debug-discovery-local-filter'] = local_func
1050 1053 repo = repo.filtered(b'debug-discovery-local-filter')
1051 1054
1052 1055 data = {}
1053 1056 if opts.get(b'old'):
1054 1057
1055 1058 def doit(pushedrevs, remoteheads, remote=remote):
1056 1059 if not util.safehasattr(remote, b'branches'):
1057 1060 # enable in-client legacy support
1058 1061 remote = localrepo.locallegacypeer(remote.local())
1059 1062 common, _in, hds = treediscovery.findcommonincoming(
1060 1063 repo, remote, force=True, audit=data
1061 1064 )
1062 1065 common = set(common)
1063 1066 if not opts.get(b'nonheads'):
1064 1067 ui.writenoi18n(
1065 1068 b"unpruned common: %s\n"
1066 1069 % b" ".join(sorted(short(n) for n in common))
1067 1070 )
1068 1071
1069 1072 clnode = repo.changelog.node
1070 1073 common = repo.revs(b'heads(::%ln)', common)
1071 1074 common = {clnode(r) for r in common}
1072 1075 return common, hds
1073 1076
1074 1077 else:
1075 1078
1076 1079 def doit(pushedrevs, remoteheads, remote=remote):
1077 1080 nodes = None
1078 1081 if pushedrevs:
1079 1082 revs = scmutil.revrange(repo, pushedrevs)
1080 1083 nodes = [repo[r].node() for r in revs]
1081 1084 common, any, hds = setdiscovery.findcommonheads(
1082 1085 ui, repo, remote, ancestorsof=nodes, audit=data
1083 1086 )
1084 1087 return common, hds
1085 1088
1086 1089 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
1087 1090 localrevs = opts[b'rev']
1088 1091 with util.timedcm('debug-discovery') as t:
1089 1092 common, hds = doit(localrevs, remoterevs)
1090 1093
1091 1094 # compute all statistics
1092 1095 heads_common = set(common)
1093 1096 heads_remote = set(hds)
1094 1097 heads_local = set(repo.heads())
1095 1098 # note: they cannot be a local or remote head that is in common and not
1096 1099 # itself a head of common.
1097 1100 heads_common_local = heads_common & heads_local
1098 1101 heads_common_remote = heads_common & heads_remote
1099 1102 heads_common_both = heads_common & heads_remote & heads_local
1100 1103
1101 1104 all = repo.revs(b'all()')
1102 1105 common = repo.revs(b'::%ln', common)
1103 1106 roots_common = repo.revs(b'roots(::%ld)', common)
1104 1107 missing = repo.revs(b'not ::%ld', common)
1105 1108 heads_missing = repo.revs(b'heads(%ld)', missing)
1106 1109 roots_missing = repo.revs(b'roots(%ld)', missing)
1107 1110 assert len(common) + len(missing) == len(all)
1108 1111
1109 1112 initial_undecided = repo.revs(
1110 1113 b'not (::%ln or %ln::)', heads_common_remote, heads_common_local
1111 1114 )
1112 1115 heads_initial_undecided = repo.revs(b'heads(%ld)', initial_undecided)
1113 1116 roots_initial_undecided = repo.revs(b'roots(%ld)', initial_undecided)
1114 1117 common_initial_undecided = initial_undecided & common
1115 1118 missing_initial_undecided = initial_undecided & missing
1116 1119
1117 1120 data[b'elapsed'] = t.elapsed
1118 1121 data[b'nb-common-heads'] = len(heads_common)
1119 1122 data[b'nb-common-heads-local'] = len(heads_common_local)
1120 1123 data[b'nb-common-heads-remote'] = len(heads_common_remote)
1121 1124 data[b'nb-common-heads-both'] = len(heads_common_both)
1122 1125 data[b'nb-common-roots'] = len(roots_common)
1123 1126 data[b'nb-head-local'] = len(heads_local)
1124 1127 data[b'nb-head-local-missing'] = len(heads_local) - len(heads_common_local)
1125 1128 data[b'nb-head-remote'] = len(heads_remote)
1126 1129 data[b'nb-head-remote-unknown'] = len(heads_remote) - len(
1127 1130 heads_common_remote
1128 1131 )
1129 1132 data[b'nb-revs'] = len(all)
1130 1133 data[b'nb-revs-common'] = len(common)
1131 1134 data[b'nb-revs-missing'] = len(missing)
1132 1135 data[b'nb-missing-heads'] = len(heads_missing)
1133 1136 data[b'nb-missing-roots'] = len(roots_missing)
1134 1137 data[b'nb-ini_und'] = len(initial_undecided)
1135 1138 data[b'nb-ini_und-heads'] = len(heads_initial_undecided)
1136 1139 data[b'nb-ini_und-roots'] = len(roots_initial_undecided)
1137 1140 data[b'nb-ini_und-common'] = len(common_initial_undecided)
1138 1141 data[b'nb-ini_und-missing'] = len(missing_initial_undecided)
1139 1142
1140 1143 # display discovery summary
1141 1144 ui.writenoi18n(b"elapsed time: %(elapsed)f seconds\n" % data)
1142 1145 ui.writenoi18n(b"round-trips: %(total-roundtrips)9d\n" % data)
1143 1146 ui.writenoi18n(b"heads summary:\n")
1144 1147 ui.writenoi18n(b" total common heads: %(nb-common-heads)9d\n" % data)
1145 1148 ui.writenoi18n(
1146 1149 b" also local heads: %(nb-common-heads-local)9d\n" % data
1147 1150 )
1148 1151 ui.writenoi18n(
1149 1152 b" also remote heads: %(nb-common-heads-remote)9d\n" % data
1150 1153 )
1151 1154 ui.writenoi18n(b" both: %(nb-common-heads-both)9d\n" % data)
1152 1155 ui.writenoi18n(b" local heads: %(nb-head-local)9d\n" % data)
1153 1156 ui.writenoi18n(
1154 1157 b" common: %(nb-common-heads-local)9d\n" % data
1155 1158 )
1156 1159 ui.writenoi18n(
1157 1160 b" missing: %(nb-head-local-missing)9d\n" % data
1158 1161 )
1159 1162 ui.writenoi18n(b" remote heads: %(nb-head-remote)9d\n" % data)
1160 1163 ui.writenoi18n(
1161 1164 b" common: %(nb-common-heads-remote)9d\n" % data
1162 1165 )
1163 1166 ui.writenoi18n(
1164 1167 b" unknown: %(nb-head-remote-unknown)9d\n" % data
1165 1168 )
1166 1169 ui.writenoi18n(b"local changesets: %(nb-revs)9d\n" % data)
1167 1170 ui.writenoi18n(b" common: %(nb-revs-common)9d\n" % data)
1168 1171 ui.writenoi18n(b" heads: %(nb-common-heads)9d\n" % data)
1169 1172 ui.writenoi18n(b" roots: %(nb-common-roots)9d\n" % data)
1170 1173 ui.writenoi18n(b" missing: %(nb-revs-missing)9d\n" % data)
1171 1174 ui.writenoi18n(b" heads: %(nb-missing-heads)9d\n" % data)
1172 1175 ui.writenoi18n(b" roots: %(nb-missing-roots)9d\n" % data)
1173 1176 ui.writenoi18n(b" first undecided set: %(nb-ini_und)9d\n" % data)
1174 1177 ui.writenoi18n(b" heads: %(nb-ini_und-heads)9d\n" % data)
1175 1178 ui.writenoi18n(b" roots: %(nb-ini_und-roots)9d\n" % data)
1176 1179 ui.writenoi18n(b" common: %(nb-ini_und-common)9d\n" % data)
1177 1180 ui.writenoi18n(b" missing: %(nb-ini_und-missing)9d\n" % data)
1178 1181
1179 1182 if ui.verbose:
1180 1183 ui.writenoi18n(
1181 1184 b"common heads: %s\n"
1182 1185 % b" ".join(sorted(short(n) for n in heads_common))
1183 1186 )
1184 1187
1185 1188
1186 1189 _chunksize = 4 << 10
1187 1190
1188 1191
1189 1192 @command(
1190 1193 b'debugdownload',
1191 1194 [
1192 1195 (b'o', b'output', b'', _(b'path')),
1193 1196 ],
1194 1197 optionalrepo=True,
1195 1198 )
1196 1199 def debugdownload(ui, repo, url, output=None, **opts):
1197 1200 """download a resource using Mercurial logic and config"""
1198 1201 fh = urlmod.open(ui, url, output)
1199 1202
1200 1203 dest = ui
1201 1204 if output:
1202 1205 dest = open(output, b"wb", _chunksize)
1203 1206 try:
1204 1207 data = fh.read(_chunksize)
1205 1208 while data:
1206 1209 dest.write(data)
1207 1210 data = fh.read(_chunksize)
1208 1211 finally:
1209 1212 if output:
1210 1213 dest.close()
1211 1214
1212 1215
1213 1216 @command(b'debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
1214 1217 def debugextensions(ui, repo, **opts):
1215 1218 '''show information about active extensions'''
1216 1219 opts = pycompat.byteskwargs(opts)
1217 1220 exts = extensions.extensions(ui)
1218 1221 hgver = util.version()
1219 1222 fm = ui.formatter(b'debugextensions', opts)
1220 1223 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
1221 1224 isinternal = extensions.ismoduleinternal(extmod)
1222 1225 extsource = None
1223 1226
1224 1227 if util.safehasattr(extmod, '__file__'):
1225 1228 extsource = pycompat.fsencode(extmod.__file__)
1226 1229 elif getattr(sys, 'oxidized', False):
1227 1230 extsource = pycompat.sysexecutable
1228 1231 if isinternal:
1229 1232 exttestedwith = [] # never expose magic string to users
1230 1233 else:
1231 1234 exttestedwith = getattr(extmod, 'testedwith', b'').split()
1232 1235 extbuglink = getattr(extmod, 'buglink', None)
1233 1236
1234 1237 fm.startitem()
1235 1238
1236 1239 if ui.quiet or ui.verbose:
1237 1240 fm.write(b'name', b'%s\n', extname)
1238 1241 else:
1239 1242 fm.write(b'name', b'%s', extname)
1240 1243 if isinternal or hgver in exttestedwith:
1241 1244 fm.plain(b'\n')
1242 1245 elif not exttestedwith:
1243 1246 fm.plain(_(b' (untested!)\n'))
1244 1247 else:
1245 1248 lasttestedversion = exttestedwith[-1]
1246 1249 fm.plain(b' (%s!)\n' % lasttestedversion)
1247 1250
1248 1251 fm.condwrite(
1249 1252 ui.verbose and extsource,
1250 1253 b'source',
1251 1254 _(b' location: %s\n'),
1252 1255 extsource or b"",
1253 1256 )
1254 1257
1255 1258 if ui.verbose:
1256 1259 fm.plain(_(b' bundled: %s\n') % [b'no', b'yes'][isinternal])
1257 1260 fm.data(bundled=isinternal)
1258 1261
1259 1262 fm.condwrite(
1260 1263 ui.verbose and exttestedwith,
1261 1264 b'testedwith',
1262 1265 _(b' tested with: %s\n'),
1263 1266 fm.formatlist(exttestedwith, name=b'ver'),
1264 1267 )
1265 1268
1266 1269 fm.condwrite(
1267 1270 ui.verbose and extbuglink,
1268 1271 b'buglink',
1269 1272 _(b' bug reporting: %s\n'),
1270 1273 extbuglink or b"",
1271 1274 )
1272 1275
1273 1276 fm.end()
1274 1277
1275 1278
1276 1279 @command(
1277 1280 b'debugfileset',
1278 1281 [
1279 1282 (
1280 1283 b'r',
1281 1284 b'rev',
1282 1285 b'',
1283 1286 _(b'apply the filespec on this revision'),
1284 1287 _(b'REV'),
1285 1288 ),
1286 1289 (
1287 1290 b'',
1288 1291 b'all-files',
1289 1292 False,
1290 1293 _(b'test files from all revisions and working directory'),
1291 1294 ),
1292 1295 (
1293 1296 b's',
1294 1297 b'show-matcher',
1295 1298 None,
1296 1299 _(b'print internal representation of matcher'),
1297 1300 ),
1298 1301 (
1299 1302 b'p',
1300 1303 b'show-stage',
1301 1304 [],
1302 1305 _(b'print parsed tree at the given stage'),
1303 1306 _(b'NAME'),
1304 1307 ),
1305 1308 ],
1306 1309 _(b'[-r REV] [--all-files] [OPTION]... FILESPEC'),
1307 1310 )
1308 1311 def debugfileset(ui, repo, expr, **opts):
1309 1312 '''parse and apply a fileset specification'''
1310 1313 from . import fileset
1311 1314
1312 1315 fileset.symbols # force import of fileset so we have predicates to optimize
1313 1316 opts = pycompat.byteskwargs(opts)
1314 1317 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
1315 1318
1316 1319 stages = [
1317 1320 (b'parsed', pycompat.identity),
1318 1321 (b'analyzed', filesetlang.analyze),
1319 1322 (b'optimized', filesetlang.optimize),
1320 1323 ]
1321 1324 stagenames = {n for n, f in stages}
1322 1325
1323 1326 showalways = set()
1324 1327 if ui.verbose and not opts[b'show_stage']:
1325 1328 # show parsed tree by --verbose (deprecated)
1326 1329 showalways.add(b'parsed')
1327 1330 if opts[b'show_stage'] == [b'all']:
1328 1331 showalways.update(stagenames)
1329 1332 else:
1330 1333 for n in opts[b'show_stage']:
1331 1334 if n not in stagenames:
1332 1335 raise error.Abort(_(b'invalid stage name: %s') % n)
1333 1336 showalways.update(opts[b'show_stage'])
1334 1337
1335 1338 tree = filesetlang.parse(expr)
1336 1339 for n, f in stages:
1337 1340 tree = f(tree)
1338 1341 if n in showalways:
1339 1342 if opts[b'show_stage'] or n != b'parsed':
1340 1343 ui.write(b"* %s:\n" % n)
1341 1344 ui.write(filesetlang.prettyformat(tree), b"\n")
1342 1345
1343 1346 files = set()
1344 1347 if opts[b'all_files']:
1345 1348 for r in repo:
1346 1349 c = repo[r]
1347 1350 files.update(c.files())
1348 1351 files.update(c.substate)
1349 1352 if opts[b'all_files'] or ctx.rev() is None:
1350 1353 wctx = repo[None]
1351 1354 files.update(
1352 1355 repo.dirstate.walk(
1353 1356 scmutil.matchall(repo),
1354 1357 subrepos=list(wctx.substate),
1355 1358 unknown=True,
1356 1359 ignored=True,
1357 1360 )
1358 1361 )
1359 1362 files.update(wctx.substate)
1360 1363 else:
1361 1364 files.update(ctx.files())
1362 1365 files.update(ctx.substate)
1363 1366
1364 1367 m = ctx.matchfileset(repo.getcwd(), expr)
1365 1368 if opts[b'show_matcher'] or (opts[b'show_matcher'] is None and ui.verbose):
1366 1369 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
1367 1370 for f in sorted(files):
1368 1371 if not m(f):
1369 1372 continue
1370 1373 ui.write(b"%s\n" % f)
1371 1374
1372 1375
1373 1376 @command(b'debugformat', [] + cmdutil.formatteropts)
1374 1377 def debugformat(ui, repo, **opts):
1375 1378 """display format information about the current repository
1376 1379
1377 1380 Use --verbose to get extra information about current config value and
1378 1381 Mercurial default."""
1379 1382 opts = pycompat.byteskwargs(opts)
1380 1383 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
1381 1384 maxvariantlength = max(len(b'format-variant'), maxvariantlength)
1382 1385
1383 1386 def makeformatname(name):
1384 1387 return b'%s:' + (b' ' * (maxvariantlength - len(name)))
1385 1388
1386 1389 fm = ui.formatter(b'debugformat', opts)
1387 1390 if fm.isplain():
1388 1391
1389 1392 def formatvalue(value):
1390 1393 if util.safehasattr(value, b'startswith'):
1391 1394 return value
1392 1395 if value:
1393 1396 return b'yes'
1394 1397 else:
1395 1398 return b'no'
1396 1399
1397 1400 else:
1398 1401 formatvalue = pycompat.identity
1399 1402
1400 1403 fm.plain(b'format-variant')
1401 1404 fm.plain(b' ' * (maxvariantlength - len(b'format-variant')))
1402 1405 fm.plain(b' repo')
1403 1406 if ui.verbose:
1404 1407 fm.plain(b' config default')
1405 1408 fm.plain(b'\n')
1406 1409 for fv in upgrade.allformatvariant:
1407 1410 fm.startitem()
1408 1411 repovalue = fv.fromrepo(repo)
1409 1412 configvalue = fv.fromconfig(repo)
1410 1413
1411 1414 if repovalue != configvalue:
1412 1415 namelabel = b'formatvariant.name.mismatchconfig'
1413 1416 repolabel = b'formatvariant.repo.mismatchconfig'
1414 1417 elif repovalue != fv.default:
1415 1418 namelabel = b'formatvariant.name.mismatchdefault'
1416 1419 repolabel = b'formatvariant.repo.mismatchdefault'
1417 1420 else:
1418 1421 namelabel = b'formatvariant.name.uptodate'
1419 1422 repolabel = b'formatvariant.repo.uptodate'
1420 1423
1421 1424 fm.write(b'name', makeformatname(fv.name), fv.name, label=namelabel)
1422 1425 fm.write(b'repo', b' %3s', formatvalue(repovalue), label=repolabel)
1423 1426 if fv.default != configvalue:
1424 1427 configlabel = b'formatvariant.config.special'
1425 1428 else:
1426 1429 configlabel = b'formatvariant.config.default'
1427 1430 fm.condwrite(
1428 1431 ui.verbose,
1429 1432 b'config',
1430 1433 b' %6s',
1431 1434 formatvalue(configvalue),
1432 1435 label=configlabel,
1433 1436 )
1434 1437 fm.condwrite(
1435 1438 ui.verbose,
1436 1439 b'default',
1437 1440 b' %7s',
1438 1441 formatvalue(fv.default),
1439 1442 label=b'formatvariant.default',
1440 1443 )
1441 1444 fm.plain(b'\n')
1442 1445 fm.end()
1443 1446
1444 1447
1445 1448 @command(b'debugfsinfo', [], _(b'[PATH]'), norepo=True)
1446 1449 def debugfsinfo(ui, path=b"."):
1447 1450 """show information detected about current filesystem"""
1448 1451 ui.writenoi18n(b'path: %s\n' % path)
1449 1452 ui.writenoi18n(
1450 1453 b'mounted on: %s\n' % (util.getfsmountpoint(path) or b'(unknown)')
1451 1454 )
1452 1455 ui.writenoi18n(b'exec: %s\n' % (util.checkexec(path) and b'yes' or b'no'))
1453 1456 ui.writenoi18n(b'fstype: %s\n' % (util.getfstype(path) or b'(unknown)'))
1454 1457 ui.writenoi18n(
1455 1458 b'symlink: %s\n' % (util.checklink(path) and b'yes' or b'no')
1456 1459 )
1457 1460 ui.writenoi18n(
1458 1461 b'hardlink: %s\n' % (util.checknlink(path) and b'yes' or b'no')
1459 1462 )
1460 1463 casesensitive = b'(unknown)'
1461 1464 try:
1462 1465 with pycompat.namedtempfile(prefix=b'.debugfsinfo', dir=path) as f:
1463 1466 casesensitive = util.fscasesensitive(f.name) and b'yes' or b'no'
1464 1467 except OSError:
1465 1468 pass
1466 1469 ui.writenoi18n(b'case-sensitive: %s\n' % casesensitive)
1467 1470
1468 1471
1469 1472 @command(
1470 1473 b'debuggetbundle',
1471 1474 [
1472 1475 (b'H', b'head', [], _(b'id of head node'), _(b'ID')),
1473 1476 (b'C', b'common', [], _(b'id of common node'), _(b'ID')),
1474 1477 (
1475 1478 b't',
1476 1479 b'type',
1477 1480 b'bzip2',
1478 1481 _(b'bundle compression type to use'),
1479 1482 _(b'TYPE'),
1480 1483 ),
1481 1484 ],
1482 1485 _(b'REPO FILE [-H|-C ID]...'),
1483 1486 norepo=True,
1484 1487 )
1485 1488 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1486 1489 """retrieves a bundle from a repo
1487 1490
1488 1491 Every ID must be a full-length hex node id string. Saves the bundle to the
1489 1492 given file.
1490 1493 """
1491 1494 opts = pycompat.byteskwargs(opts)
1492 1495 repo = hg.peer(ui, opts, repopath)
1493 1496 if not repo.capable(b'getbundle'):
1494 1497 raise error.Abort(b"getbundle() not supported by target repository")
1495 1498 args = {}
1496 1499 if common:
1497 1500 args['common'] = [bin(s) for s in common]
1498 1501 if head:
1499 1502 args['heads'] = [bin(s) for s in head]
1500 1503 # TODO: get desired bundlecaps from command line.
1501 1504 args['bundlecaps'] = None
1502 1505 bundle = repo.getbundle(b'debug', **args)
1503 1506
1504 1507 bundletype = opts.get(b'type', b'bzip2').lower()
1505 1508 btypes = {
1506 1509 b'none': b'HG10UN',
1507 1510 b'bzip2': b'HG10BZ',
1508 1511 b'gzip': b'HG10GZ',
1509 1512 b'bundle2': b'HG20',
1510 1513 }
1511 1514 bundletype = btypes.get(bundletype)
1512 1515 if bundletype not in bundle2.bundletypes:
1513 1516 raise error.Abort(_(b'unknown bundle type specified with --type'))
1514 1517 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1515 1518
1516 1519
1517 1520 @command(b'debugignore', [], b'[FILE]')
1518 1521 def debugignore(ui, repo, *files, **opts):
1519 1522 """display the combined ignore pattern and information about ignored files
1520 1523
1521 1524 With no argument display the combined ignore pattern.
1522 1525
1523 1526 Given space separated file names, shows if the given file is ignored and
1524 1527 if so, show the ignore rule (file and line number) that matched it.
1525 1528 """
1526 1529 ignore = repo.dirstate._ignore
1527 1530 if not files:
1528 1531 # Show all the patterns
1529 1532 ui.write(b"%s\n" % pycompat.byterepr(ignore))
1530 1533 else:
1531 1534 m = scmutil.match(repo[None], pats=files)
1532 1535 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1533 1536 for f in m.files():
1534 1537 nf = util.normpath(f)
1535 1538 ignored = None
1536 1539 ignoredata = None
1537 1540 if nf != b'.':
1538 1541 if ignore(nf):
1539 1542 ignored = nf
1540 1543 ignoredata = repo.dirstate._ignorefileandline(nf)
1541 1544 else:
1542 1545 for p in pathutil.finddirs(nf):
1543 1546 if ignore(p):
1544 1547 ignored = p
1545 1548 ignoredata = repo.dirstate._ignorefileandline(p)
1546 1549 break
1547 1550 if ignored:
1548 1551 if ignored == nf:
1549 1552 ui.write(_(b"%s is ignored\n") % uipathfn(f))
1550 1553 else:
1551 1554 ui.write(
1552 1555 _(
1553 1556 b"%s is ignored because of "
1554 1557 b"containing directory %s\n"
1555 1558 )
1556 1559 % (uipathfn(f), ignored)
1557 1560 )
1558 1561 ignorefile, lineno, line = ignoredata
1559 1562 ui.write(
1560 1563 _(b"(ignore rule in %s, line %d: '%s')\n")
1561 1564 % (ignorefile, lineno, line)
1562 1565 )
1563 1566 else:
1564 1567 ui.write(_(b"%s is not ignored\n") % uipathfn(f))
1565 1568
1566 1569
1567 1570 @command(
1568 1571 b'debugindex',
1569 1572 cmdutil.debugrevlogopts + cmdutil.formatteropts,
1570 1573 _(b'-c|-m|FILE'),
1571 1574 )
1572 1575 def debugindex(ui, repo, file_=None, **opts):
1573 1576 """dump index data for a storage primitive"""
1574 1577 opts = pycompat.byteskwargs(opts)
1575 1578 store = cmdutil.openstorage(repo, b'debugindex', file_, opts)
1576 1579
1577 1580 if ui.debugflag:
1578 1581 shortfn = hex
1579 1582 else:
1580 1583 shortfn = short
1581 1584
1582 1585 idlen = 12
1583 1586 for i in store:
1584 1587 idlen = len(shortfn(store.node(i)))
1585 1588 break
1586 1589
1587 1590 fm = ui.formatter(b'debugindex', opts)
1588 1591 fm.plain(
1589 1592 b' rev linkrev %s %s p2\n'
1590 1593 % (b'nodeid'.ljust(idlen), b'p1'.ljust(idlen))
1591 1594 )
1592 1595
1593 1596 for rev in store:
1594 1597 node = store.node(rev)
1595 1598 parents = store.parents(node)
1596 1599
1597 1600 fm.startitem()
1598 1601 fm.write(b'rev', b'%6d ', rev)
1599 1602 fm.write(b'linkrev', b'%7d ', store.linkrev(rev))
1600 1603 fm.write(b'node', b'%s ', shortfn(node))
1601 1604 fm.write(b'p1', b'%s ', shortfn(parents[0]))
1602 1605 fm.write(b'p2', b'%s', shortfn(parents[1]))
1603 1606 fm.plain(b'\n')
1604 1607
1605 1608 fm.end()
1606 1609
1607 1610
1608 1611 @command(
1609 1612 b'debugindexdot',
1610 1613 cmdutil.debugrevlogopts,
1611 1614 _(b'-c|-m|FILE'),
1612 1615 optionalrepo=True,
1613 1616 )
1614 1617 def debugindexdot(ui, repo, file_=None, **opts):
1615 1618 """dump an index DAG as a graphviz dot file"""
1616 1619 opts = pycompat.byteskwargs(opts)
1617 1620 r = cmdutil.openstorage(repo, b'debugindexdot', file_, opts)
1618 1621 ui.writenoi18n(b"digraph G {\n")
1619 1622 for i in r:
1620 1623 node = r.node(i)
1621 1624 pp = r.parents(node)
1622 1625 ui.write(b"\t%d -> %d\n" % (r.rev(pp[0]), i))
1623 1626 if pp[1] != nullid:
1624 1627 ui.write(b"\t%d -> %d\n" % (r.rev(pp[1]), i))
1625 1628 ui.write(b"}\n")
1626 1629
1627 1630
1628 1631 @command(b'debugindexstats', [])
1629 1632 def debugindexstats(ui, repo):
1630 1633 """show stats related to the changelog index"""
1631 1634 repo.changelog.shortest(nullid, 1)
1632 1635 index = repo.changelog.index
1633 1636 if not util.safehasattr(index, b'stats'):
1634 1637 raise error.Abort(_(b'debugindexstats only works with native code'))
1635 1638 for k, v in sorted(index.stats().items()):
1636 1639 ui.write(b'%s: %d\n' % (k, v))
1637 1640
1638 1641
1639 1642 @command(b'debuginstall', [] + cmdutil.formatteropts, b'', norepo=True)
1640 1643 def debuginstall(ui, **opts):
1641 1644 """test Mercurial installation
1642 1645
1643 1646 Returns 0 on success.
1644 1647 """
1645 1648 opts = pycompat.byteskwargs(opts)
1646 1649
1647 1650 problems = 0
1648 1651
1649 1652 fm = ui.formatter(b'debuginstall', opts)
1650 1653 fm.startitem()
1651 1654
1652 1655 # encoding might be unknown or wrong. don't translate these messages.
1653 1656 fm.write(b'encoding', b"checking encoding (%s)...\n", encoding.encoding)
1654 1657 err = None
1655 1658 try:
1656 1659 codecs.lookup(pycompat.sysstr(encoding.encoding))
1657 1660 except LookupError as inst:
1658 1661 err = stringutil.forcebytestr(inst)
1659 1662 problems += 1
1660 1663 fm.condwrite(
1661 1664 err,
1662 1665 b'encodingerror',
1663 1666 b" %s\n (check that your locale is properly set)\n",
1664 1667 err,
1665 1668 )
1666 1669
1667 1670 # Python
1668 1671 pythonlib = None
1669 1672 if util.safehasattr(os, '__file__'):
1670 1673 pythonlib = os.path.dirname(pycompat.fsencode(os.__file__))
1671 1674 elif getattr(sys, 'oxidized', False):
1672 1675 pythonlib = pycompat.sysexecutable
1673 1676
1674 1677 fm.write(
1675 1678 b'pythonexe',
1676 1679 _(b"checking Python executable (%s)\n"),
1677 1680 pycompat.sysexecutable or _(b"unknown"),
1678 1681 )
1679 1682 fm.write(
1680 1683 b'pythonimplementation',
1681 1684 _(b"checking Python implementation (%s)\n"),
1682 1685 pycompat.sysbytes(platform.python_implementation()),
1683 1686 )
1684 1687 fm.write(
1685 1688 b'pythonver',
1686 1689 _(b"checking Python version (%s)\n"),
1687 1690 (b"%d.%d.%d" % sys.version_info[:3]),
1688 1691 )
1689 1692 fm.write(
1690 1693 b'pythonlib',
1691 1694 _(b"checking Python lib (%s)...\n"),
1692 1695 pythonlib or _(b"unknown"),
1693 1696 )
1694 1697
1695 1698 try:
1696 1699 from . import rustext
1697 1700
1698 1701 rustext.__doc__ # trigger lazy import
1699 1702 except ImportError:
1700 1703 rustext = None
1701 1704
1702 1705 security = set(sslutil.supportedprotocols)
1703 1706 if sslutil.hassni:
1704 1707 security.add(b'sni')
1705 1708
1706 1709 fm.write(
1707 1710 b'pythonsecurity',
1708 1711 _(b"checking Python security support (%s)\n"),
1709 1712 fm.formatlist(sorted(security), name=b'protocol', fmt=b'%s', sep=b','),
1710 1713 )
1711 1714
1712 1715 # These are warnings, not errors. So don't increment problem count. This
1713 1716 # may change in the future.
1714 1717 if b'tls1.2' not in security:
1715 1718 fm.plain(
1716 1719 _(
1717 1720 b' TLS 1.2 not supported by Python install; '
1718 1721 b'network connections lack modern security\n'
1719 1722 )
1720 1723 )
1721 1724 if b'sni' not in security:
1722 1725 fm.plain(
1723 1726 _(
1724 1727 b' SNI not supported by Python install; may have '
1725 1728 b'connectivity issues with some servers\n'
1726 1729 )
1727 1730 )
1728 1731
1729 1732 fm.plain(
1730 1733 _(
1731 1734 b"checking Rust extensions (%s)\n"
1732 1735 % (b'missing' if rustext is None else b'installed')
1733 1736 ),
1734 1737 )
1735 1738
1736 1739 # TODO print CA cert info
1737 1740
1738 1741 # hg version
1739 1742 hgver = util.version()
1740 1743 fm.write(
1741 1744 b'hgver', _(b"checking Mercurial version (%s)\n"), hgver.split(b'+')[0]
1742 1745 )
1743 1746 fm.write(
1744 1747 b'hgverextra',
1745 1748 _(b"checking Mercurial custom build (%s)\n"),
1746 1749 b'+'.join(hgver.split(b'+')[1:]),
1747 1750 )
1748 1751
1749 1752 # compiled modules
1750 1753 hgmodules = None
1751 1754 if util.safehasattr(sys.modules[__name__], '__file__'):
1752 1755 hgmodules = os.path.dirname(pycompat.fsencode(__file__))
1753 1756 elif getattr(sys, 'oxidized', False):
1754 1757 hgmodules = pycompat.sysexecutable
1755 1758
1756 1759 fm.write(
1757 1760 b'hgmodulepolicy', _(b"checking module policy (%s)\n"), policy.policy
1758 1761 )
1759 1762 fm.write(
1760 1763 b'hgmodules',
1761 1764 _(b"checking installed modules (%s)...\n"),
1762 1765 hgmodules or _(b"unknown"),
1763 1766 )
1764 1767
1765 1768 rustandc = policy.policy in (b'rust+c', b'rust+c-allow')
1766 1769 rustext = rustandc # for now, that's the only case
1767 1770 cext = policy.policy in (b'c', b'allow') or rustandc
1768 1771 nopure = cext or rustext
1769 1772 if nopure:
1770 1773 err = None
1771 1774 try:
1772 1775 if cext:
1773 1776 from .cext import ( # pytype: disable=import-error
1774 1777 base85,
1775 1778 bdiff,
1776 1779 mpatch,
1777 1780 osutil,
1778 1781 )
1779 1782
1780 1783 # quiet pyflakes
1781 1784 dir(bdiff), dir(mpatch), dir(base85), dir(osutil)
1782 1785 if rustext:
1783 1786 from .rustext import ( # pytype: disable=import-error
1784 1787 ancestor,
1785 1788 dirstate,
1786 1789 )
1787 1790
1788 1791 dir(ancestor), dir(dirstate) # quiet pyflakes
1789 1792 except Exception as inst:
1790 1793 err = stringutil.forcebytestr(inst)
1791 1794 problems += 1
1792 1795 fm.condwrite(err, b'extensionserror', b" %s\n", err)
1793 1796
1794 1797 compengines = util.compengines._engines.values()
1795 1798 fm.write(
1796 1799 b'compengines',
1797 1800 _(b'checking registered compression engines (%s)\n'),
1798 1801 fm.formatlist(
1799 1802 sorted(e.name() for e in compengines),
1800 1803 name=b'compengine',
1801 1804 fmt=b'%s',
1802 1805 sep=b', ',
1803 1806 ),
1804 1807 )
1805 1808 fm.write(
1806 1809 b'compenginesavail',
1807 1810 _(b'checking available compression engines (%s)\n'),
1808 1811 fm.formatlist(
1809 1812 sorted(e.name() for e in compengines if e.available()),
1810 1813 name=b'compengine',
1811 1814 fmt=b'%s',
1812 1815 sep=b', ',
1813 1816 ),
1814 1817 )
1815 1818 wirecompengines = compression.compengines.supportedwireengines(
1816 1819 compression.SERVERROLE
1817 1820 )
1818 1821 fm.write(
1819 1822 b'compenginesserver',
1820 1823 _(
1821 1824 b'checking available compression engines '
1822 1825 b'for wire protocol (%s)\n'
1823 1826 ),
1824 1827 fm.formatlist(
1825 1828 [e.name() for e in wirecompengines if e.wireprotosupport()],
1826 1829 name=b'compengine',
1827 1830 fmt=b'%s',
1828 1831 sep=b', ',
1829 1832 ),
1830 1833 )
1831 1834 re2 = b'missing'
1832 1835 if util._re2:
1833 1836 re2 = b'available'
1834 1837 fm.plain(_(b'checking "re2" regexp engine (%s)\n') % re2)
1835 1838 fm.data(re2=bool(util._re2))
1836 1839
1837 1840 # templates
1838 1841 p = templater.templatedir()
1839 1842 fm.write(b'templatedirs', b'checking templates (%s)...\n', p or b'')
1840 1843 fm.condwrite(not p, b'', _(b" no template directories found\n"))
1841 1844 if p:
1842 1845 (m, fp) = templater.try_open_template(b"map-cmdline.default")
1843 1846 if m:
1844 1847 # template found, check if it is working
1845 1848 err = None
1846 1849 try:
1847 1850 templater.templater.frommapfile(m)
1848 1851 except Exception as inst:
1849 1852 err = stringutil.forcebytestr(inst)
1850 1853 p = None
1851 1854 fm.condwrite(err, b'defaulttemplateerror', b" %s\n", err)
1852 1855 else:
1853 1856 p = None
1854 1857 fm.condwrite(
1855 1858 p, b'defaulttemplate', _(b"checking default template (%s)\n"), m
1856 1859 )
1857 1860 fm.condwrite(
1858 1861 not m,
1859 1862 b'defaulttemplatenotfound',
1860 1863 _(b" template '%s' not found\n"),
1861 1864 b"default",
1862 1865 )
1863 1866 if not p:
1864 1867 problems += 1
1865 1868 fm.condwrite(
1866 1869 not p, b'', _(b" (templates seem to have been installed incorrectly)\n")
1867 1870 )
1868 1871
1869 1872 # editor
1870 1873 editor = ui.geteditor()
1871 1874 editor = util.expandpath(editor)
1872 1875 editorbin = procutil.shellsplit(editor)[0]
1873 1876 fm.write(b'editor', _(b"checking commit editor... (%s)\n"), editorbin)
1874 1877 cmdpath = procutil.findexe(editorbin)
1875 1878 fm.condwrite(
1876 1879 not cmdpath and editor == b'vi',
1877 1880 b'vinotfound',
1878 1881 _(
1879 1882 b" No commit editor set and can't find %s in PATH\n"
1880 1883 b" (specify a commit editor in your configuration"
1881 1884 b" file)\n"
1882 1885 ),
1883 1886 not cmdpath and editor == b'vi' and editorbin,
1884 1887 )
1885 1888 fm.condwrite(
1886 1889 not cmdpath and editor != b'vi',
1887 1890 b'editornotfound',
1888 1891 _(
1889 1892 b" Can't find editor '%s' in PATH\n"
1890 1893 b" (specify a commit editor in your configuration"
1891 1894 b" file)\n"
1892 1895 ),
1893 1896 not cmdpath and editorbin,
1894 1897 )
1895 1898 if not cmdpath and editor != b'vi':
1896 1899 problems += 1
1897 1900
1898 1901 # check username
1899 1902 username = None
1900 1903 err = None
1901 1904 try:
1902 1905 username = ui.username()
1903 1906 except error.Abort as e:
1904 1907 err = e.message
1905 1908 problems += 1
1906 1909
1907 1910 fm.condwrite(
1908 1911 username, b'username', _(b"checking username (%s)\n"), username
1909 1912 )
1910 1913 fm.condwrite(
1911 1914 err,
1912 1915 b'usernameerror',
1913 1916 _(
1914 1917 b"checking username...\n %s\n"
1915 1918 b" (specify a username in your configuration file)\n"
1916 1919 ),
1917 1920 err,
1918 1921 )
1919 1922
1920 1923 for name, mod in extensions.extensions():
1921 1924 handler = getattr(mod, 'debuginstall', None)
1922 1925 if handler is not None:
1923 1926 problems += handler(ui, fm)
1924 1927
1925 1928 fm.condwrite(not problems, b'', _(b"no problems detected\n"))
1926 1929 if not problems:
1927 1930 fm.data(problems=problems)
1928 1931 fm.condwrite(
1929 1932 problems,
1930 1933 b'problems',
1931 1934 _(b"%d problems detected, please check your install!\n"),
1932 1935 problems,
1933 1936 )
1934 1937 fm.end()
1935 1938
1936 1939 return problems
1937 1940
1938 1941
1939 1942 @command(b'debugknown', [], _(b'REPO ID...'), norepo=True)
1940 1943 def debugknown(ui, repopath, *ids, **opts):
1941 1944 """test whether node ids are known to a repo
1942 1945
1943 1946 Every ID must be a full-length hex node id string. Returns a list of 0s
1944 1947 and 1s indicating unknown/known.
1945 1948 """
1946 1949 opts = pycompat.byteskwargs(opts)
1947 1950 repo = hg.peer(ui, opts, repopath)
1948 1951 if not repo.capable(b'known'):
1949 1952 raise error.Abort(b"known() not supported by target repository")
1950 1953 flags = repo.known([bin(s) for s in ids])
1951 1954 ui.write(b"%s\n" % (b"".join([f and b"1" or b"0" for f in flags])))
1952 1955
1953 1956
1954 1957 @command(b'debuglabelcomplete', [], _(b'LABEL...'))
1955 1958 def debuglabelcomplete(ui, repo, *args):
1956 1959 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1957 1960 debugnamecomplete(ui, repo, *args)
1958 1961
1959 1962
1960 1963 @command(
1961 1964 b'debuglocks',
1962 1965 [
1963 1966 (b'L', b'force-free-lock', None, _(b'free the store lock (DANGEROUS)')),
1964 1967 (
1965 1968 b'W',
1966 1969 b'force-free-wlock',
1967 1970 None,
1968 1971 _(b'free the working state lock (DANGEROUS)'),
1969 1972 ),
1970 1973 (b's', b'set-lock', None, _(b'set the store lock until stopped')),
1971 1974 (
1972 1975 b'S',
1973 1976 b'set-wlock',
1974 1977 None,
1975 1978 _(b'set the working state lock until stopped'),
1976 1979 ),
1977 1980 ],
1978 1981 _(b'[OPTION]...'),
1979 1982 )
1980 1983 def debuglocks(ui, repo, **opts):
1981 1984 """show or modify state of locks
1982 1985
1983 1986 By default, this command will show which locks are held. This
1984 1987 includes the user and process holding the lock, the amount of time
1985 1988 the lock has been held, and the machine name where the process is
1986 1989 running if it's not local.
1987 1990
1988 1991 Locks protect the integrity of Mercurial's data, so should be
1989 1992 treated with care. System crashes or other interruptions may cause
1990 1993 locks to not be properly released, though Mercurial will usually
1991 1994 detect and remove such stale locks automatically.
1992 1995
1993 1996 However, detecting stale locks may not always be possible (for
1994 1997 instance, on a shared filesystem). Removing locks may also be
1995 1998 blocked by filesystem permissions.
1996 1999
1997 2000 Setting a lock will prevent other commands from changing the data.
1998 2001 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1999 2002 The set locks are removed when the command exits.
2000 2003
2001 2004 Returns 0 if no locks are held.
2002 2005
2003 2006 """
2004 2007
2005 2008 if opts.get('force_free_lock'):
2006 2009 repo.svfs.unlink(b'lock')
2007 2010 if opts.get('force_free_wlock'):
2008 2011 repo.vfs.unlink(b'wlock')
2009 2012 if opts.get('force_free_lock') or opts.get('force_free_wlock'):
2010 2013 return 0
2011 2014
2012 2015 locks = []
2013 2016 try:
2014 2017 if opts.get('set_wlock'):
2015 2018 try:
2016 2019 locks.append(repo.wlock(False))
2017 2020 except error.LockHeld:
2018 2021 raise error.Abort(_(b'wlock is already held'))
2019 2022 if opts.get('set_lock'):
2020 2023 try:
2021 2024 locks.append(repo.lock(False))
2022 2025 except error.LockHeld:
2023 2026 raise error.Abort(_(b'lock is already held'))
2024 2027 if len(locks):
2025 2028 ui.promptchoice(_(b"ready to release the lock (y)? $$ &Yes"))
2026 2029 return 0
2027 2030 finally:
2028 2031 release(*locks)
2029 2032
2030 2033 now = time.time()
2031 2034 held = 0
2032 2035
2033 2036 def report(vfs, name, method):
2034 2037 # this causes stale locks to get reaped for more accurate reporting
2035 2038 try:
2036 2039 l = method(False)
2037 2040 except error.LockHeld:
2038 2041 l = None
2039 2042
2040 2043 if l:
2041 2044 l.release()
2042 2045 else:
2043 2046 try:
2044 2047 st = vfs.lstat(name)
2045 2048 age = now - st[stat.ST_MTIME]
2046 2049 user = util.username(st.st_uid)
2047 2050 locker = vfs.readlock(name)
2048 2051 if b":" in locker:
2049 2052 host, pid = locker.split(b':')
2050 2053 if host == socket.gethostname():
2051 2054 locker = b'user %s, process %s' % (user or b'None', pid)
2052 2055 else:
2053 2056 locker = b'user %s, process %s, host %s' % (
2054 2057 user or b'None',
2055 2058 pid,
2056 2059 host,
2057 2060 )
2058 2061 ui.writenoi18n(b"%-6s %s (%ds)\n" % (name + b":", locker, age))
2059 2062 return 1
2060 2063 except OSError as e:
2061 2064 if e.errno != errno.ENOENT:
2062 2065 raise
2063 2066
2064 2067 ui.writenoi18n(b"%-6s free\n" % (name + b":"))
2065 2068 return 0
2066 2069
2067 2070 held += report(repo.svfs, b"lock", repo.lock)
2068 2071 held += report(repo.vfs, b"wlock", repo.wlock)
2069 2072
2070 2073 return held
2071 2074
2072 2075
2073 2076 @command(
2074 2077 b'debugmanifestfulltextcache',
2075 2078 [
2076 2079 (b'', b'clear', False, _(b'clear the cache')),
2077 2080 (
2078 2081 b'a',
2079 2082 b'add',
2080 2083 [],
2081 2084 _(b'add the given manifest nodes to the cache'),
2082 2085 _(b'NODE'),
2083 2086 ),
2084 2087 ],
2085 2088 b'',
2086 2089 )
2087 2090 def debugmanifestfulltextcache(ui, repo, add=(), **opts):
2088 2091 """show, clear or amend the contents of the manifest fulltext cache"""
2089 2092
2090 2093 def getcache():
2091 2094 r = repo.manifestlog.getstorage(b'')
2092 2095 try:
2093 2096 return r._fulltextcache
2094 2097 except AttributeError:
2095 2098 msg = _(
2096 2099 b"Current revlog implementation doesn't appear to have a "
2097 2100 b"manifest fulltext cache\n"
2098 2101 )
2099 2102 raise error.Abort(msg)
2100 2103
2101 2104 if opts.get('clear'):
2102 2105 with repo.wlock():
2103 2106 cache = getcache()
2104 2107 cache.clear(clear_persisted_data=True)
2105 2108 return
2106 2109
2107 2110 if add:
2108 2111 with repo.wlock():
2109 2112 m = repo.manifestlog
2110 2113 store = m.getstorage(b'')
2111 2114 for n in add:
2112 2115 try:
2113 2116 manifest = m[store.lookup(n)]
2114 2117 except error.LookupError as e:
2115 2118 raise error.Abort(e, hint=b"Check your manifest node id")
2116 2119 manifest.read() # stores revisision in cache too
2117 2120 return
2118 2121
2119 2122 cache = getcache()
2120 2123 if not len(cache):
2121 2124 ui.write(_(b'cache empty\n'))
2122 2125 else:
2123 2126 ui.write(
2124 2127 _(
2125 2128 b'cache contains %d manifest entries, in order of most to '
2126 2129 b'least recent:\n'
2127 2130 )
2128 2131 % (len(cache),)
2129 2132 )
2130 2133 totalsize = 0
2131 2134 for nodeid in cache:
2132 2135 # Use cache.get to not update the LRU order
2133 2136 data = cache.peek(nodeid)
2134 2137 size = len(data)
2135 2138 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
2136 2139 ui.write(
2137 2140 _(b'id: %s, size %s\n') % (hex(nodeid), util.bytecount(size))
2138 2141 )
2139 2142 ondisk = cache._opener.stat(b'manifestfulltextcache').st_size
2140 2143 ui.write(
2141 2144 _(b'total cache data size %s, on-disk %s\n')
2142 2145 % (util.bytecount(totalsize), util.bytecount(ondisk))
2143 2146 )
2144 2147
2145 2148
2146 2149 @command(b'debugmergestate', [] + cmdutil.templateopts, b'')
2147 2150 def debugmergestate(ui, repo, *args, **opts):
2148 2151 """print merge state
2149 2152
2150 2153 Use --verbose to print out information about whether v1 or v2 merge state
2151 2154 was chosen."""
2152 2155
2153 2156 if ui.verbose:
2154 2157 ms = mergestatemod.mergestate(repo)
2155 2158
2156 2159 # sort so that reasonable information is on top
2157 2160 v1records = ms._readrecordsv1()
2158 2161 v2records = ms._readrecordsv2()
2159 2162
2160 2163 if not v1records and not v2records:
2161 2164 pass
2162 2165 elif not v2records:
2163 2166 ui.writenoi18n(b'no version 2 merge state\n')
2164 2167 elif ms._v1v2match(v1records, v2records):
2165 2168 ui.writenoi18n(b'v1 and v2 states match: using v2\n')
2166 2169 else:
2167 2170 ui.writenoi18n(b'v1 and v2 states mismatch: using v1\n')
2168 2171
2169 2172 opts = pycompat.byteskwargs(opts)
2170 2173 if not opts[b'template']:
2171 2174 opts[b'template'] = (
2172 2175 b'{if(commits, "", "no merge state found\n")}'
2173 2176 b'{commits % "{name}{if(label, " ({label})")}: {node}\n"}'
2174 2177 b'{files % "file: {path} (state \\"{state}\\")\n'
2175 2178 b'{if(local_path, "'
2176 2179 b' local path: {local_path} (hash {local_key}, flags \\"{local_flags}\\")\n'
2177 2180 b' ancestor path: {ancestor_path} (node {ancestor_node})\n'
2178 2181 b' other path: {other_path} (node {other_node})\n'
2179 2182 b'")}'
2180 2183 b'{if(rename_side, "'
2181 2184 b' rename side: {rename_side}\n'
2182 2185 b' renamed path: {renamed_path}\n'
2183 2186 b'")}'
2184 2187 b'{extras % " extra: {key} = {value}\n"}'
2185 2188 b'"}'
2186 2189 b'{extras % "extra: {file} ({key} = {value})\n"}'
2187 2190 )
2188 2191
2189 2192 ms = mergestatemod.mergestate.read(repo)
2190 2193
2191 2194 fm = ui.formatter(b'debugmergestate', opts)
2192 2195 fm.startitem()
2193 2196
2194 2197 fm_commits = fm.nested(b'commits')
2195 2198 if ms.active():
2196 2199 for name, node, label_index in (
2197 2200 (b'local', ms.local, 0),
2198 2201 (b'other', ms.other, 1),
2199 2202 ):
2200 2203 fm_commits.startitem()
2201 2204 fm_commits.data(name=name)
2202 2205 fm_commits.data(node=hex(node))
2203 2206 if ms._labels and len(ms._labels) > label_index:
2204 2207 fm_commits.data(label=ms._labels[label_index])
2205 2208 fm_commits.end()
2206 2209
2207 2210 fm_files = fm.nested(b'files')
2208 2211 if ms.active():
2209 2212 for f in ms:
2210 2213 fm_files.startitem()
2211 2214 fm_files.data(path=f)
2212 2215 state = ms._state[f]
2213 2216 fm_files.data(state=state[0])
2214 2217 if state[0] in (
2215 2218 mergestatemod.MERGE_RECORD_UNRESOLVED,
2216 2219 mergestatemod.MERGE_RECORD_RESOLVED,
2217 2220 ):
2218 2221 fm_files.data(local_key=state[1])
2219 2222 fm_files.data(local_path=state[2])
2220 2223 fm_files.data(ancestor_path=state[3])
2221 2224 fm_files.data(ancestor_node=state[4])
2222 2225 fm_files.data(other_path=state[5])
2223 2226 fm_files.data(other_node=state[6])
2224 2227 fm_files.data(local_flags=state[7])
2225 2228 elif state[0] in (
2226 2229 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
2227 2230 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
2228 2231 ):
2229 2232 fm_files.data(renamed_path=state[1])
2230 2233 fm_files.data(rename_side=state[2])
2231 2234 fm_extras = fm_files.nested(b'extras')
2232 2235 for k, v in sorted(ms.extras(f).items()):
2233 2236 fm_extras.startitem()
2234 2237 fm_extras.data(key=k)
2235 2238 fm_extras.data(value=v)
2236 2239 fm_extras.end()
2237 2240
2238 2241 fm_files.end()
2239 2242
2240 2243 fm_extras = fm.nested(b'extras')
2241 2244 for f, d in sorted(pycompat.iteritems(ms.allextras())):
2242 2245 if f in ms:
2243 2246 # If file is in mergestate, we have already processed it's extras
2244 2247 continue
2245 2248 for k, v in pycompat.iteritems(d):
2246 2249 fm_extras.startitem()
2247 2250 fm_extras.data(file=f)
2248 2251 fm_extras.data(key=k)
2249 2252 fm_extras.data(value=v)
2250 2253 fm_extras.end()
2251 2254
2252 2255 fm.end()
2253 2256
2254 2257
2255 2258 @command(b'debugnamecomplete', [], _(b'NAME...'))
2256 2259 def debugnamecomplete(ui, repo, *args):
2257 2260 '''complete "names" - tags, open branch names, bookmark names'''
2258 2261
2259 2262 names = set()
2260 2263 # since we previously only listed open branches, we will handle that
2261 2264 # specially (after this for loop)
2262 2265 for name, ns in pycompat.iteritems(repo.names):
2263 2266 if name != b'branches':
2264 2267 names.update(ns.listnames(repo))
2265 2268 names.update(
2266 2269 tag
2267 2270 for (tag, heads, tip, closed) in repo.branchmap().iterbranches()
2268 2271 if not closed
2269 2272 )
2270 2273 completions = set()
2271 2274 if not args:
2272 2275 args = [b'']
2273 2276 for a in args:
2274 2277 completions.update(n for n in names if n.startswith(a))
2275 2278 ui.write(b'\n'.join(sorted(completions)))
2276 2279 ui.write(b'\n')
2277 2280
2278 2281
2279 2282 @command(
2280 2283 b'debugnodemap',
2281 2284 [
2282 2285 (
2283 2286 b'',
2284 2287 b'dump-new',
2285 2288 False,
2286 2289 _(b'write a (new) persistent binary nodemap on stdout'),
2287 2290 ),
2288 2291 (b'', b'dump-disk', False, _(b'dump on-disk data on stdout')),
2289 2292 (
2290 2293 b'',
2291 2294 b'check',
2292 2295 False,
2293 2296 _(b'check that the data on disk data are correct.'),
2294 2297 ),
2295 2298 (
2296 2299 b'',
2297 2300 b'metadata',
2298 2301 False,
2299 2302 _(b'display the on disk meta data for the nodemap'),
2300 2303 ),
2301 2304 ],
2302 2305 )
2303 2306 def debugnodemap(ui, repo, **opts):
2304 2307 """write and inspect on disk nodemap"""
2305 2308 if opts['dump_new']:
2306 2309 unfi = repo.unfiltered()
2307 2310 cl = unfi.changelog
2308 2311 if util.safehasattr(cl.index, "nodemap_data_all"):
2309 2312 data = cl.index.nodemap_data_all()
2310 2313 else:
2311 2314 data = nodemap.persistent_data(cl.index)
2312 2315 ui.write(data)
2313 2316 elif opts['dump_disk']:
2314 2317 unfi = repo.unfiltered()
2315 2318 cl = unfi.changelog
2316 2319 nm_data = nodemap.persisted_data(cl)
2317 2320 if nm_data is not None:
2318 2321 docket, data = nm_data
2319 2322 ui.write(data[:])
2320 2323 elif opts['check']:
2321 2324 unfi = repo.unfiltered()
2322 2325 cl = unfi.changelog
2323 2326 nm_data = nodemap.persisted_data(cl)
2324 2327 if nm_data is not None:
2325 2328 docket, data = nm_data
2326 2329 return nodemap.check_data(ui, cl.index, data)
2327 2330 elif opts['metadata']:
2328 2331 unfi = repo.unfiltered()
2329 2332 cl = unfi.changelog
2330 2333 nm_data = nodemap.persisted_data(cl)
2331 2334 if nm_data is not None:
2332 2335 docket, data = nm_data
2333 2336 ui.write((b"uid: %s\n") % docket.uid)
2334 2337 ui.write((b"tip-rev: %d\n") % docket.tip_rev)
2335 2338 ui.write((b"tip-node: %s\n") % hex(docket.tip_node))
2336 2339 ui.write((b"data-length: %d\n") % docket.data_length)
2337 2340 ui.write((b"data-unused: %d\n") % docket.data_unused)
2338 2341 unused_perc = docket.data_unused * 100.0 / docket.data_length
2339 2342 ui.write((b"data-unused: %2.3f%%\n") % unused_perc)
2340 2343
2341 2344
2342 2345 @command(
2343 2346 b'debugobsolete',
2344 2347 [
2345 2348 (b'', b'flags', 0, _(b'markers flag')),
2346 2349 (
2347 2350 b'',
2348 2351 b'record-parents',
2349 2352 False,
2350 2353 _(b'record parent information for the precursor'),
2351 2354 ),
2352 2355 (b'r', b'rev', [], _(b'display markers relevant to REV')),
2353 2356 (
2354 2357 b'',
2355 2358 b'exclusive',
2356 2359 False,
2357 2360 _(b'restrict display to markers only relevant to REV'),
2358 2361 ),
2359 2362 (b'', b'index', False, _(b'display index of the marker')),
2360 2363 (b'', b'delete', [], _(b'delete markers specified by indices')),
2361 2364 ]
2362 2365 + cmdutil.commitopts2
2363 2366 + cmdutil.formatteropts,
2364 2367 _(b'[OBSOLETED [REPLACEMENT ...]]'),
2365 2368 )
2366 2369 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2367 2370 """create arbitrary obsolete marker
2368 2371
2369 2372 With no arguments, displays the list of obsolescence markers."""
2370 2373
2371 2374 opts = pycompat.byteskwargs(opts)
2372 2375
2373 2376 def parsenodeid(s):
2374 2377 try:
2375 2378 # We do not use revsingle/revrange functions here to accept
2376 2379 # arbitrary node identifiers, possibly not present in the
2377 2380 # local repository.
2378 2381 n = bin(s)
2379 2382 if len(n) != len(nullid):
2380 2383 raise TypeError()
2381 2384 return n
2382 2385 except TypeError:
2383 2386 raise error.InputError(
2384 2387 b'changeset references must be full hexadecimal '
2385 2388 b'node identifiers'
2386 2389 )
2387 2390
2388 2391 if opts.get(b'delete'):
2389 2392 indices = []
2390 2393 for v in opts.get(b'delete'):
2391 2394 try:
2392 2395 indices.append(int(v))
2393 2396 except ValueError:
2394 2397 raise error.InputError(
2395 2398 _(b'invalid index value: %r') % v,
2396 2399 hint=_(b'use integers for indices'),
2397 2400 )
2398 2401
2399 2402 if repo.currenttransaction():
2400 2403 raise error.Abort(
2401 2404 _(b'cannot delete obsmarkers in the middle of transaction.')
2402 2405 )
2403 2406
2404 2407 with repo.lock():
2405 2408 n = repair.deleteobsmarkers(repo.obsstore, indices)
2406 2409 ui.write(_(b'deleted %i obsolescence markers\n') % n)
2407 2410
2408 2411 return
2409 2412
2410 2413 if precursor is not None:
2411 2414 if opts[b'rev']:
2412 2415 raise error.InputError(
2413 2416 b'cannot select revision when creating marker'
2414 2417 )
2415 2418 metadata = {}
2416 2419 metadata[b'user'] = encoding.fromlocal(opts[b'user'] or ui.username())
2417 2420 succs = tuple(parsenodeid(succ) for succ in successors)
2418 2421 l = repo.lock()
2419 2422 try:
2420 2423 tr = repo.transaction(b'debugobsolete')
2421 2424 try:
2422 2425 date = opts.get(b'date')
2423 2426 if date:
2424 2427 date = dateutil.parsedate(date)
2425 2428 else:
2426 2429 date = None
2427 2430 prec = parsenodeid(precursor)
2428 2431 parents = None
2429 2432 if opts[b'record_parents']:
2430 2433 if prec not in repo.unfiltered():
2431 2434 raise error.Abort(
2432 2435 b'cannot used --record-parents on '
2433 2436 b'unknown changesets'
2434 2437 )
2435 2438 parents = repo.unfiltered()[prec].parents()
2436 2439 parents = tuple(p.node() for p in parents)
2437 2440 repo.obsstore.create(
2438 2441 tr,
2439 2442 prec,
2440 2443 succs,
2441 2444 opts[b'flags'],
2442 2445 parents=parents,
2443 2446 date=date,
2444 2447 metadata=metadata,
2445 2448 ui=ui,
2446 2449 )
2447 2450 tr.close()
2448 2451 except ValueError as exc:
2449 2452 raise error.Abort(
2450 2453 _(b'bad obsmarker input: %s') % pycompat.bytestr(exc)
2451 2454 )
2452 2455 finally:
2453 2456 tr.release()
2454 2457 finally:
2455 2458 l.release()
2456 2459 else:
2457 2460 if opts[b'rev']:
2458 2461 revs = scmutil.revrange(repo, opts[b'rev'])
2459 2462 nodes = [repo[r].node() for r in revs]
2460 2463 markers = list(
2461 2464 obsutil.getmarkers(
2462 2465 repo, nodes=nodes, exclusive=opts[b'exclusive']
2463 2466 )
2464 2467 )
2465 2468 markers.sort(key=lambda x: x._data)
2466 2469 else:
2467 2470 markers = obsutil.getmarkers(repo)
2468 2471
2469 2472 markerstoiter = markers
2470 2473 isrelevant = lambda m: True
2471 2474 if opts.get(b'rev') and opts.get(b'index'):
2472 2475 markerstoiter = obsutil.getmarkers(repo)
2473 2476 markerset = set(markers)
2474 2477 isrelevant = lambda m: m in markerset
2475 2478
2476 2479 fm = ui.formatter(b'debugobsolete', opts)
2477 2480 for i, m in enumerate(markerstoiter):
2478 2481 if not isrelevant(m):
2479 2482 # marker can be irrelevant when we're iterating over a set
2480 2483 # of markers (markerstoiter) which is bigger than the set
2481 2484 # of markers we want to display (markers)
2482 2485 # this can happen if both --index and --rev options are
2483 2486 # provided and thus we need to iterate over all of the markers
2484 2487 # to get the correct indices, but only display the ones that
2485 2488 # are relevant to --rev value
2486 2489 continue
2487 2490 fm.startitem()
2488 2491 ind = i if opts.get(b'index') else None
2489 2492 cmdutil.showmarker(fm, m, index=ind)
2490 2493 fm.end()
2491 2494
2492 2495
2493 2496 @command(
2494 2497 b'debugp1copies',
2495 2498 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2496 2499 _(b'[-r REV]'),
2497 2500 )
2498 2501 def debugp1copies(ui, repo, **opts):
2499 2502 """dump copy information compared to p1"""
2500 2503
2501 2504 opts = pycompat.byteskwargs(opts)
2502 2505 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2503 2506 for dst, src in ctx.p1copies().items():
2504 2507 ui.write(b'%s -> %s\n' % (src, dst))
2505 2508
2506 2509
2507 2510 @command(
2508 2511 b'debugp2copies',
2509 2512 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2510 2513 _(b'[-r REV]'),
2511 2514 )
2512 2515 def debugp1copies(ui, repo, **opts):
2513 2516 """dump copy information compared to p2"""
2514 2517
2515 2518 opts = pycompat.byteskwargs(opts)
2516 2519 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2517 2520 for dst, src in ctx.p2copies().items():
2518 2521 ui.write(b'%s -> %s\n' % (src, dst))
2519 2522
2520 2523
2521 2524 @command(
2522 2525 b'debugpathcomplete',
2523 2526 [
2524 2527 (b'f', b'full', None, _(b'complete an entire path')),
2525 2528 (b'n', b'normal', None, _(b'show only normal files')),
2526 2529 (b'a', b'added', None, _(b'show only added files')),
2527 2530 (b'r', b'removed', None, _(b'show only removed files')),
2528 2531 ],
2529 2532 _(b'FILESPEC...'),
2530 2533 )
2531 2534 def debugpathcomplete(ui, repo, *specs, **opts):
2532 2535 """complete part or all of a tracked path
2533 2536
2534 2537 This command supports shells that offer path name completion. It
2535 2538 currently completes only files already known to the dirstate.
2536 2539
2537 2540 Completion extends only to the next path segment unless
2538 2541 --full is specified, in which case entire paths are used."""
2539 2542
2540 2543 def complete(path, acceptable):
2541 2544 dirstate = repo.dirstate
2542 2545 spec = os.path.normpath(os.path.join(encoding.getcwd(), path))
2543 2546 rootdir = repo.root + pycompat.ossep
2544 2547 if spec != repo.root and not spec.startswith(rootdir):
2545 2548 return [], []
2546 2549 if os.path.isdir(spec):
2547 2550 spec += b'/'
2548 2551 spec = spec[len(rootdir) :]
2549 2552 fixpaths = pycompat.ossep != b'/'
2550 2553 if fixpaths:
2551 2554 spec = spec.replace(pycompat.ossep, b'/')
2552 2555 speclen = len(spec)
2553 2556 fullpaths = opts['full']
2554 2557 files, dirs = set(), set()
2555 2558 adddir, addfile = dirs.add, files.add
2556 2559 for f, st in pycompat.iteritems(dirstate):
2557 2560 if f.startswith(spec) and st[0] in acceptable:
2558 2561 if fixpaths:
2559 2562 f = f.replace(b'/', pycompat.ossep)
2560 2563 if fullpaths:
2561 2564 addfile(f)
2562 2565 continue
2563 2566 s = f.find(pycompat.ossep, speclen)
2564 2567 if s >= 0:
2565 2568 adddir(f[:s])
2566 2569 else:
2567 2570 addfile(f)
2568 2571 return files, dirs
2569 2572
2570 2573 acceptable = b''
2571 2574 if opts['normal']:
2572 2575 acceptable += b'nm'
2573 2576 if opts['added']:
2574 2577 acceptable += b'a'
2575 2578 if opts['removed']:
2576 2579 acceptable += b'r'
2577 2580 cwd = repo.getcwd()
2578 2581 if not specs:
2579 2582 specs = [b'.']
2580 2583
2581 2584 files, dirs = set(), set()
2582 2585 for spec in specs:
2583 2586 f, d = complete(spec, acceptable or b'nmar')
2584 2587 files.update(f)
2585 2588 dirs.update(d)
2586 2589 files.update(dirs)
2587 2590 ui.write(b'\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2588 2591 ui.write(b'\n')
2589 2592
2590 2593
2591 2594 @command(
2592 2595 b'debugpathcopies',
2593 2596 cmdutil.walkopts,
2594 2597 b'hg debugpathcopies REV1 REV2 [FILE]',
2595 2598 inferrepo=True,
2596 2599 )
2597 2600 def debugpathcopies(ui, repo, rev1, rev2, *pats, **opts):
2598 2601 """show copies between two revisions"""
2599 2602 ctx1 = scmutil.revsingle(repo, rev1)
2600 2603 ctx2 = scmutil.revsingle(repo, rev2)
2601 2604 m = scmutil.match(ctx1, pats, opts)
2602 2605 for dst, src in sorted(copies.pathcopies(ctx1, ctx2, m).items()):
2603 2606 ui.write(b'%s -> %s\n' % (src, dst))
2604 2607
2605 2608
2606 2609 @command(b'debugpeer', [], _(b'PATH'), norepo=True)
2607 2610 def debugpeer(ui, path):
2608 2611 """establish a connection to a peer repository"""
2609 2612 # Always enable peer request logging. Requires --debug to display
2610 2613 # though.
2611 2614 overrides = {
2612 2615 (b'devel', b'debug.peer-request'): True,
2613 2616 }
2614 2617
2615 2618 with ui.configoverride(overrides):
2616 2619 peer = hg.peer(ui, {}, path)
2617 2620
2618 local = peer.local() is not None
2619 canpush = peer.canpush()
2620
2621 ui.write(_(b'url: %s\n') % peer.url())
2622 ui.write(_(b'local: %s\n') % (_(b'yes') if local else _(b'no')))
2623 ui.write(_(b'pushable: %s\n') % (_(b'yes') if canpush else _(b'no')))
2621 try:
2622 local = peer.local() is not None
2623 canpush = peer.canpush()
2624
2625 ui.write(_(b'url: %s\n') % peer.url())
2626 ui.write(_(b'local: %s\n') % (_(b'yes') if local else _(b'no')))
2627 ui.write(
2628 _(b'pushable: %s\n') % (_(b'yes') if canpush else _(b'no'))
2629 )
2630 finally:
2631 peer.close()
2624 2632
2625 2633
2626 2634 @command(
2627 2635 b'debugpickmergetool',
2628 2636 [
2629 2637 (b'r', b'rev', b'', _(b'check for files in this revision'), _(b'REV')),
2630 2638 (b'', b'changedelete', None, _(b'emulate merging change and delete')),
2631 2639 ]
2632 2640 + cmdutil.walkopts
2633 2641 + cmdutil.mergetoolopts,
2634 2642 _(b'[PATTERN]...'),
2635 2643 inferrepo=True,
2636 2644 )
2637 2645 def debugpickmergetool(ui, repo, *pats, **opts):
2638 2646 """examine which merge tool is chosen for specified file
2639 2647
2640 2648 As described in :hg:`help merge-tools`, Mercurial examines
2641 2649 configurations below in this order to decide which merge tool is
2642 2650 chosen for specified file.
2643 2651
2644 2652 1. ``--tool`` option
2645 2653 2. ``HGMERGE`` environment variable
2646 2654 3. configurations in ``merge-patterns`` section
2647 2655 4. configuration of ``ui.merge``
2648 2656 5. configurations in ``merge-tools`` section
2649 2657 6. ``hgmerge`` tool (for historical reason only)
2650 2658 7. default tool for fallback (``:merge`` or ``:prompt``)
2651 2659
2652 2660 This command writes out examination result in the style below::
2653 2661
2654 2662 FILE = MERGETOOL
2655 2663
2656 2664 By default, all files known in the first parent context of the
2657 2665 working directory are examined. Use file patterns and/or -I/-X
2658 2666 options to limit target files. -r/--rev is also useful to examine
2659 2667 files in another context without actual updating to it.
2660 2668
2661 2669 With --debug, this command shows warning messages while matching
2662 2670 against ``merge-patterns`` and so on, too. It is recommended to
2663 2671 use this option with explicit file patterns and/or -I/-X options,
2664 2672 because this option increases amount of output per file according
2665 2673 to configurations in hgrc.
2666 2674
2667 2675 With -v/--verbose, this command shows configurations below at
2668 2676 first (only if specified).
2669 2677
2670 2678 - ``--tool`` option
2671 2679 - ``HGMERGE`` environment variable
2672 2680 - configuration of ``ui.merge``
2673 2681
2674 2682 If merge tool is chosen before matching against
2675 2683 ``merge-patterns``, this command can't show any helpful
2676 2684 information, even with --debug. In such case, information above is
2677 2685 useful to know why a merge tool is chosen.
2678 2686 """
2679 2687 opts = pycompat.byteskwargs(opts)
2680 2688 overrides = {}
2681 2689 if opts[b'tool']:
2682 2690 overrides[(b'ui', b'forcemerge')] = opts[b'tool']
2683 2691 ui.notenoi18n(b'with --tool %r\n' % (pycompat.bytestr(opts[b'tool'])))
2684 2692
2685 2693 with ui.configoverride(overrides, b'debugmergepatterns'):
2686 2694 hgmerge = encoding.environ.get(b"HGMERGE")
2687 2695 if hgmerge is not None:
2688 2696 ui.notenoi18n(b'with HGMERGE=%r\n' % (pycompat.bytestr(hgmerge)))
2689 2697 uimerge = ui.config(b"ui", b"merge")
2690 2698 if uimerge:
2691 2699 ui.notenoi18n(b'with ui.merge=%r\n' % (pycompat.bytestr(uimerge)))
2692 2700
2693 2701 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2694 2702 m = scmutil.match(ctx, pats, opts)
2695 2703 changedelete = opts[b'changedelete']
2696 2704 for path in ctx.walk(m):
2697 2705 fctx = ctx[path]
2698 2706 try:
2699 2707 if not ui.debugflag:
2700 2708 ui.pushbuffer(error=True)
2701 2709 tool, toolpath = filemerge._picktool(
2702 2710 repo,
2703 2711 ui,
2704 2712 path,
2705 2713 fctx.isbinary(),
2706 2714 b'l' in fctx.flags(),
2707 2715 changedelete,
2708 2716 )
2709 2717 finally:
2710 2718 if not ui.debugflag:
2711 2719 ui.popbuffer()
2712 2720 ui.write(b'%s = %s\n' % (path, tool))
2713 2721
2714 2722
2715 2723 @command(b'debugpushkey', [], _(b'REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2716 2724 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2717 2725 """access the pushkey key/value protocol
2718 2726
2719 2727 With two args, list the keys in the given namespace.
2720 2728
2721 2729 With five args, set a key to new if it currently is set to old.
2722 2730 Reports success or failure.
2723 2731 """
2724 2732
2725 2733 target = hg.peer(ui, {}, repopath)
2726 if keyinfo:
2727 key, old, new = keyinfo
2728 with target.commandexecutor() as e:
2729 r = e.callcommand(
2730 b'pushkey',
2731 {
2732 b'namespace': namespace,
2733 b'key': key,
2734 b'old': old,
2735 b'new': new,
2736 },
2737 ).result()
2738
2739 ui.status(pycompat.bytestr(r) + b'\n')
2740 return not r
2741 else:
2742 for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))):
2743 ui.write(
2744 b"%s\t%s\n" % (stringutil.escapestr(k), stringutil.escapestr(v))
2745 )
2734 try:
2735 if keyinfo:
2736 key, old, new = keyinfo
2737 with target.commandexecutor() as e:
2738 r = e.callcommand(
2739 b'pushkey',
2740 {
2741 b'namespace': namespace,
2742 b'key': key,
2743 b'old': old,
2744 b'new': new,
2745 },
2746 ).result()
2747
2748 ui.status(pycompat.bytestr(r) + b'\n')
2749 return not r
2750 else:
2751 for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))):
2752 ui.write(
2753 b"%s\t%s\n"
2754 % (stringutil.escapestr(k), stringutil.escapestr(v))
2755 )
2756 finally:
2757 target.close()
2746 2758
2747 2759
2748 2760 @command(b'debugpvec', [], _(b'A B'))
2749 2761 def debugpvec(ui, repo, a, b=None):
2750 2762 ca = scmutil.revsingle(repo, a)
2751 2763 cb = scmutil.revsingle(repo, b)
2752 2764 pa = pvec.ctxpvec(ca)
2753 2765 pb = pvec.ctxpvec(cb)
2754 2766 if pa == pb:
2755 2767 rel = b"="
2756 2768 elif pa > pb:
2757 2769 rel = b">"
2758 2770 elif pa < pb:
2759 2771 rel = b"<"
2760 2772 elif pa | pb:
2761 2773 rel = b"|"
2762 2774 ui.write(_(b"a: %s\n") % pa)
2763 2775 ui.write(_(b"b: %s\n") % pb)
2764 2776 ui.write(_(b"depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2765 2777 ui.write(
2766 2778 _(b"delta: %d hdist: %d distance: %d relation: %s\n")
2767 2779 % (
2768 2780 abs(pa._depth - pb._depth),
2769 2781 pvec._hamming(pa._vec, pb._vec),
2770 2782 pa.distance(pb),
2771 2783 rel,
2772 2784 )
2773 2785 )
2774 2786
2775 2787
2776 2788 @command(
2777 2789 b'debugrebuilddirstate|debugrebuildstate',
2778 2790 [
2779 2791 (b'r', b'rev', b'', _(b'revision to rebuild to'), _(b'REV')),
2780 2792 (
2781 2793 b'',
2782 2794 b'minimal',
2783 2795 None,
2784 2796 _(
2785 2797 b'only rebuild files that are inconsistent with '
2786 2798 b'the working copy parent'
2787 2799 ),
2788 2800 ),
2789 2801 ],
2790 2802 _(b'[-r REV]'),
2791 2803 )
2792 2804 def debugrebuilddirstate(ui, repo, rev, **opts):
2793 2805 """rebuild the dirstate as it would look like for the given revision
2794 2806
2795 2807 If no revision is specified the first current parent will be used.
2796 2808
2797 2809 The dirstate will be set to the files of the given revision.
2798 2810 The actual working directory content or existing dirstate
2799 2811 information such as adds or removes is not considered.
2800 2812
2801 2813 ``minimal`` will only rebuild the dirstate status for files that claim to be
2802 2814 tracked but are not in the parent manifest, or that exist in the parent
2803 2815 manifest but are not in the dirstate. It will not change adds, removes, or
2804 2816 modified files that are in the working copy parent.
2805 2817
2806 2818 One use of this command is to make the next :hg:`status` invocation
2807 2819 check the actual file content.
2808 2820 """
2809 2821 ctx = scmutil.revsingle(repo, rev)
2810 2822 with repo.wlock():
2811 2823 dirstate = repo.dirstate
2812 2824 changedfiles = None
2813 2825 # See command doc for what minimal does.
2814 2826 if opts.get('minimal'):
2815 2827 manifestfiles = set(ctx.manifest().keys())
2816 2828 dirstatefiles = set(dirstate)
2817 2829 manifestonly = manifestfiles - dirstatefiles
2818 2830 dsonly = dirstatefiles - manifestfiles
2819 2831 dsnotadded = {f for f in dsonly if dirstate[f] != b'a'}
2820 2832 changedfiles = manifestonly | dsnotadded
2821 2833
2822 2834 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2823 2835
2824 2836
2825 2837 @command(b'debugrebuildfncache', [], b'')
2826 2838 def debugrebuildfncache(ui, repo):
2827 2839 """rebuild the fncache file"""
2828 2840 repair.rebuildfncache(ui, repo)
2829 2841
2830 2842
2831 2843 @command(
2832 2844 b'debugrename',
2833 2845 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2834 2846 _(b'[-r REV] [FILE]...'),
2835 2847 )
2836 2848 def debugrename(ui, repo, *pats, **opts):
2837 2849 """dump rename information"""
2838 2850
2839 2851 opts = pycompat.byteskwargs(opts)
2840 2852 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2841 2853 m = scmutil.match(ctx, pats, opts)
2842 2854 for abs in ctx.walk(m):
2843 2855 fctx = ctx[abs]
2844 2856 o = fctx.filelog().renamed(fctx.filenode())
2845 2857 rel = repo.pathto(abs)
2846 2858 if o:
2847 2859 ui.write(_(b"%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2848 2860 else:
2849 2861 ui.write(_(b"%s not renamed\n") % rel)
2850 2862
2851 2863
2852 2864 @command(b'debugrequires|debugrequirements', [], b'')
2853 2865 def debugrequirements(ui, repo):
2854 2866 """ print the current repo requirements """
2855 2867 for r in sorted(repo.requirements):
2856 2868 ui.write(b"%s\n" % r)
2857 2869
2858 2870
2859 2871 @command(
2860 2872 b'debugrevlog',
2861 2873 cmdutil.debugrevlogopts + [(b'd', b'dump', False, _(b'dump index data'))],
2862 2874 _(b'-c|-m|FILE'),
2863 2875 optionalrepo=True,
2864 2876 )
2865 2877 def debugrevlog(ui, repo, file_=None, **opts):
2866 2878 """show data and statistics about a revlog"""
2867 2879 opts = pycompat.byteskwargs(opts)
2868 2880 r = cmdutil.openrevlog(repo, b'debugrevlog', file_, opts)
2869 2881
2870 2882 if opts.get(b"dump"):
2871 2883 numrevs = len(r)
2872 2884 ui.write(
2873 2885 (
2874 2886 b"# rev p1rev p2rev start end deltastart base p1 p2"
2875 2887 b" rawsize totalsize compression heads chainlen\n"
2876 2888 )
2877 2889 )
2878 2890 ts = 0
2879 2891 heads = set()
2880 2892
2881 2893 for rev in pycompat.xrange(numrevs):
2882 2894 dbase = r.deltaparent(rev)
2883 2895 if dbase == -1:
2884 2896 dbase = rev
2885 2897 cbase = r.chainbase(rev)
2886 2898 clen = r.chainlen(rev)
2887 2899 p1, p2 = r.parentrevs(rev)
2888 2900 rs = r.rawsize(rev)
2889 2901 ts = ts + rs
2890 2902 heads -= set(r.parentrevs(rev))
2891 2903 heads.add(rev)
2892 2904 try:
2893 2905 compression = ts / r.end(rev)
2894 2906 except ZeroDivisionError:
2895 2907 compression = 0
2896 2908 ui.write(
2897 2909 b"%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2898 2910 b"%11d %5d %8d\n"
2899 2911 % (
2900 2912 rev,
2901 2913 p1,
2902 2914 p2,
2903 2915 r.start(rev),
2904 2916 r.end(rev),
2905 2917 r.start(dbase),
2906 2918 r.start(cbase),
2907 2919 r.start(p1),
2908 2920 r.start(p2),
2909 2921 rs,
2910 2922 ts,
2911 2923 compression,
2912 2924 len(heads),
2913 2925 clen,
2914 2926 )
2915 2927 )
2916 2928 return 0
2917 2929
2918 2930 v = r.version
2919 2931 format = v & 0xFFFF
2920 2932 flags = []
2921 2933 gdelta = False
2922 2934 if v & revlog.FLAG_INLINE_DATA:
2923 2935 flags.append(b'inline')
2924 2936 if v & revlog.FLAG_GENERALDELTA:
2925 2937 gdelta = True
2926 2938 flags.append(b'generaldelta')
2927 2939 if not flags:
2928 2940 flags = [b'(none)']
2929 2941
2930 2942 ### tracks merge vs single parent
2931 2943 nummerges = 0
2932 2944
2933 2945 ### tracks ways the "delta" are build
2934 2946 # nodelta
2935 2947 numempty = 0
2936 2948 numemptytext = 0
2937 2949 numemptydelta = 0
2938 2950 # full file content
2939 2951 numfull = 0
2940 2952 # intermediate snapshot against a prior snapshot
2941 2953 numsemi = 0
2942 2954 # snapshot count per depth
2943 2955 numsnapdepth = collections.defaultdict(lambda: 0)
2944 2956 # delta against previous revision
2945 2957 numprev = 0
2946 2958 # delta against first or second parent (not prev)
2947 2959 nump1 = 0
2948 2960 nump2 = 0
2949 2961 # delta against neither prev nor parents
2950 2962 numother = 0
2951 2963 # delta against prev that are also first or second parent
2952 2964 # (details of `numprev`)
2953 2965 nump1prev = 0
2954 2966 nump2prev = 0
2955 2967
2956 2968 # data about delta chain of each revs
2957 2969 chainlengths = []
2958 2970 chainbases = []
2959 2971 chainspans = []
2960 2972
2961 2973 # data about each revision
2962 2974 datasize = [None, 0, 0]
2963 2975 fullsize = [None, 0, 0]
2964 2976 semisize = [None, 0, 0]
2965 2977 # snapshot count per depth
2966 2978 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2967 2979 deltasize = [None, 0, 0]
2968 2980 chunktypecounts = {}
2969 2981 chunktypesizes = {}
2970 2982
2971 2983 def addsize(size, l):
2972 2984 if l[0] is None or size < l[0]:
2973 2985 l[0] = size
2974 2986 if size > l[1]:
2975 2987 l[1] = size
2976 2988 l[2] += size
2977 2989
2978 2990 numrevs = len(r)
2979 2991 for rev in pycompat.xrange(numrevs):
2980 2992 p1, p2 = r.parentrevs(rev)
2981 2993 delta = r.deltaparent(rev)
2982 2994 if format > 0:
2983 2995 addsize(r.rawsize(rev), datasize)
2984 2996 if p2 != nullrev:
2985 2997 nummerges += 1
2986 2998 size = r.length(rev)
2987 2999 if delta == nullrev:
2988 3000 chainlengths.append(0)
2989 3001 chainbases.append(r.start(rev))
2990 3002 chainspans.append(size)
2991 3003 if size == 0:
2992 3004 numempty += 1
2993 3005 numemptytext += 1
2994 3006 else:
2995 3007 numfull += 1
2996 3008 numsnapdepth[0] += 1
2997 3009 addsize(size, fullsize)
2998 3010 addsize(size, snapsizedepth[0])
2999 3011 else:
3000 3012 chainlengths.append(chainlengths[delta] + 1)
3001 3013 baseaddr = chainbases[delta]
3002 3014 revaddr = r.start(rev)
3003 3015 chainbases.append(baseaddr)
3004 3016 chainspans.append((revaddr - baseaddr) + size)
3005 3017 if size == 0:
3006 3018 numempty += 1
3007 3019 numemptydelta += 1
3008 3020 elif r.issnapshot(rev):
3009 3021 addsize(size, semisize)
3010 3022 numsemi += 1
3011 3023 depth = r.snapshotdepth(rev)
3012 3024 numsnapdepth[depth] += 1
3013 3025 addsize(size, snapsizedepth[depth])
3014 3026 else:
3015 3027 addsize(size, deltasize)
3016 3028 if delta == rev - 1:
3017 3029 numprev += 1
3018 3030 if delta == p1:
3019 3031 nump1prev += 1
3020 3032 elif delta == p2:
3021 3033 nump2prev += 1
3022 3034 elif delta == p1:
3023 3035 nump1 += 1
3024 3036 elif delta == p2:
3025 3037 nump2 += 1
3026 3038 elif delta != nullrev:
3027 3039 numother += 1
3028 3040
3029 3041 # Obtain data on the raw chunks in the revlog.
3030 3042 if util.safehasattr(r, b'_getsegmentforrevs'):
3031 3043 segment = r._getsegmentforrevs(rev, rev)[1]
3032 3044 else:
3033 3045 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
3034 3046 if segment:
3035 3047 chunktype = bytes(segment[0:1])
3036 3048 else:
3037 3049 chunktype = b'empty'
3038 3050
3039 3051 if chunktype not in chunktypecounts:
3040 3052 chunktypecounts[chunktype] = 0
3041 3053 chunktypesizes[chunktype] = 0
3042 3054
3043 3055 chunktypecounts[chunktype] += 1
3044 3056 chunktypesizes[chunktype] += size
3045 3057
3046 3058 # Adjust size min value for empty cases
3047 3059 for size in (datasize, fullsize, semisize, deltasize):
3048 3060 if size[0] is None:
3049 3061 size[0] = 0
3050 3062
3051 3063 numdeltas = numrevs - numfull - numempty - numsemi
3052 3064 numoprev = numprev - nump1prev - nump2prev
3053 3065 totalrawsize = datasize[2]
3054 3066 datasize[2] /= numrevs
3055 3067 fulltotal = fullsize[2]
3056 3068 if numfull == 0:
3057 3069 fullsize[2] = 0
3058 3070 else:
3059 3071 fullsize[2] /= numfull
3060 3072 semitotal = semisize[2]
3061 3073 snaptotal = {}
3062 3074 if numsemi > 0:
3063 3075 semisize[2] /= numsemi
3064 3076 for depth in snapsizedepth:
3065 3077 snaptotal[depth] = snapsizedepth[depth][2]
3066 3078 snapsizedepth[depth][2] /= numsnapdepth[depth]
3067 3079
3068 3080 deltatotal = deltasize[2]
3069 3081 if numdeltas > 0:
3070 3082 deltasize[2] /= numdeltas
3071 3083 totalsize = fulltotal + semitotal + deltatotal
3072 3084 avgchainlen = sum(chainlengths) / numrevs
3073 3085 maxchainlen = max(chainlengths)
3074 3086 maxchainspan = max(chainspans)
3075 3087 compratio = 1
3076 3088 if totalsize:
3077 3089 compratio = totalrawsize / totalsize
3078 3090
3079 3091 basedfmtstr = b'%%%dd\n'
3080 3092 basepcfmtstr = b'%%%dd %s(%%5.2f%%%%)\n'
3081 3093
3082 3094 def dfmtstr(max):
3083 3095 return basedfmtstr % len(str(max))
3084 3096
3085 3097 def pcfmtstr(max, padding=0):
3086 3098 return basepcfmtstr % (len(str(max)), b' ' * padding)
3087 3099
3088 3100 def pcfmt(value, total):
3089 3101 if total:
3090 3102 return (value, 100 * float(value) / total)
3091 3103 else:
3092 3104 return value, 100.0
3093 3105
3094 3106 ui.writenoi18n(b'format : %d\n' % format)
3095 3107 ui.writenoi18n(b'flags : %s\n' % b', '.join(flags))
3096 3108
3097 3109 ui.write(b'\n')
3098 3110 fmt = pcfmtstr(totalsize)
3099 3111 fmt2 = dfmtstr(totalsize)
3100 3112 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
3101 3113 ui.writenoi18n(b' merges : ' + fmt % pcfmt(nummerges, numrevs))
3102 3114 ui.writenoi18n(
3103 3115 b' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs)
3104 3116 )
3105 3117 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
3106 3118 ui.writenoi18n(b' empty : ' + fmt % pcfmt(numempty, numrevs))
3107 3119 ui.writenoi18n(
3108 3120 b' text : '
3109 3121 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta)
3110 3122 )
3111 3123 ui.writenoi18n(
3112 3124 b' delta : '
3113 3125 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta)
3114 3126 )
3115 3127 ui.writenoi18n(
3116 3128 b' snapshot : ' + fmt % pcfmt(numfull + numsemi, numrevs)
3117 3129 )
3118 3130 for depth in sorted(numsnapdepth):
3119 3131 ui.write(
3120 3132 (b' lvl-%-3d : ' % depth)
3121 3133 + fmt % pcfmt(numsnapdepth[depth], numrevs)
3122 3134 )
3123 3135 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
3124 3136 ui.writenoi18n(b'revision size : ' + fmt2 % totalsize)
3125 3137 ui.writenoi18n(
3126 3138 b' snapshot : ' + fmt % pcfmt(fulltotal + semitotal, totalsize)
3127 3139 )
3128 3140 for depth in sorted(numsnapdepth):
3129 3141 ui.write(
3130 3142 (b' lvl-%-3d : ' % depth)
3131 3143 + fmt % pcfmt(snaptotal[depth], totalsize)
3132 3144 )
3133 3145 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
3134 3146
3135 3147 def fmtchunktype(chunktype):
3136 3148 if chunktype == b'empty':
3137 3149 return b' %s : ' % chunktype
3138 3150 elif chunktype in pycompat.bytestr(string.ascii_letters):
3139 3151 return b' 0x%s (%s) : ' % (hex(chunktype), chunktype)
3140 3152 else:
3141 3153 return b' 0x%s : ' % hex(chunktype)
3142 3154
3143 3155 ui.write(b'\n')
3144 3156 ui.writenoi18n(b'chunks : ' + fmt2 % numrevs)
3145 3157 for chunktype in sorted(chunktypecounts):
3146 3158 ui.write(fmtchunktype(chunktype))
3147 3159 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
3148 3160 ui.writenoi18n(b'chunks size : ' + fmt2 % totalsize)
3149 3161 for chunktype in sorted(chunktypecounts):
3150 3162 ui.write(fmtchunktype(chunktype))
3151 3163 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
3152 3164
3153 3165 ui.write(b'\n')
3154 3166 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
3155 3167 ui.writenoi18n(b'avg chain length : ' + fmt % avgchainlen)
3156 3168 ui.writenoi18n(b'max chain length : ' + fmt % maxchainlen)
3157 3169 ui.writenoi18n(b'max chain reach : ' + fmt % maxchainspan)
3158 3170 ui.writenoi18n(b'compression ratio : ' + fmt % compratio)
3159 3171
3160 3172 if format > 0:
3161 3173 ui.write(b'\n')
3162 3174 ui.writenoi18n(
3163 3175 b'uncompressed data size (min/max/avg) : %d / %d / %d\n'
3164 3176 % tuple(datasize)
3165 3177 )
3166 3178 ui.writenoi18n(
3167 3179 b'full revision size (min/max/avg) : %d / %d / %d\n'
3168 3180 % tuple(fullsize)
3169 3181 )
3170 3182 ui.writenoi18n(
3171 3183 b'inter-snapshot size (min/max/avg) : %d / %d / %d\n'
3172 3184 % tuple(semisize)
3173 3185 )
3174 3186 for depth in sorted(snapsizedepth):
3175 3187 if depth == 0:
3176 3188 continue
3177 3189 ui.writenoi18n(
3178 3190 b' level-%-3d (min/max/avg) : %d / %d / %d\n'
3179 3191 % ((depth,) + tuple(snapsizedepth[depth]))
3180 3192 )
3181 3193 ui.writenoi18n(
3182 3194 b'delta size (min/max/avg) : %d / %d / %d\n'
3183 3195 % tuple(deltasize)
3184 3196 )
3185 3197
3186 3198 if numdeltas > 0:
3187 3199 ui.write(b'\n')
3188 3200 fmt = pcfmtstr(numdeltas)
3189 3201 fmt2 = pcfmtstr(numdeltas, 4)
3190 3202 ui.writenoi18n(
3191 3203 b'deltas against prev : ' + fmt % pcfmt(numprev, numdeltas)
3192 3204 )
3193 3205 if numprev > 0:
3194 3206 ui.writenoi18n(
3195 3207 b' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev)
3196 3208 )
3197 3209 ui.writenoi18n(
3198 3210 b' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev)
3199 3211 )
3200 3212 ui.writenoi18n(
3201 3213 b' other : ' + fmt2 % pcfmt(numoprev, numprev)
3202 3214 )
3203 3215 if gdelta:
3204 3216 ui.writenoi18n(
3205 3217 b'deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas)
3206 3218 )
3207 3219 ui.writenoi18n(
3208 3220 b'deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas)
3209 3221 )
3210 3222 ui.writenoi18n(
3211 3223 b'deltas against other : ' + fmt % pcfmt(numother, numdeltas)
3212 3224 )
3213 3225
3214 3226
3215 3227 @command(
3216 3228 b'debugrevlogindex',
3217 3229 cmdutil.debugrevlogopts
3218 3230 + [(b'f', b'format', 0, _(b'revlog format'), _(b'FORMAT'))],
3219 3231 _(b'[-f FORMAT] -c|-m|FILE'),
3220 3232 optionalrepo=True,
3221 3233 )
3222 3234 def debugrevlogindex(ui, repo, file_=None, **opts):
3223 3235 """dump the contents of a revlog index"""
3224 3236 opts = pycompat.byteskwargs(opts)
3225 3237 r = cmdutil.openrevlog(repo, b'debugrevlogindex', file_, opts)
3226 3238 format = opts.get(b'format', 0)
3227 3239 if format not in (0, 1):
3228 3240 raise error.Abort(_(b"unknown format %d") % format)
3229 3241
3230 3242 if ui.debugflag:
3231 3243 shortfn = hex
3232 3244 else:
3233 3245 shortfn = short
3234 3246
3235 3247 # There might not be anything in r, so have a sane default
3236 3248 idlen = 12
3237 3249 for i in r:
3238 3250 idlen = len(shortfn(r.node(i)))
3239 3251 break
3240 3252
3241 3253 if format == 0:
3242 3254 if ui.verbose:
3243 3255 ui.writenoi18n(
3244 3256 b" rev offset length linkrev %s %s p2\n"
3245 3257 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3246 3258 )
3247 3259 else:
3248 3260 ui.writenoi18n(
3249 3261 b" rev linkrev %s %s p2\n"
3250 3262 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3251 3263 )
3252 3264 elif format == 1:
3253 3265 if ui.verbose:
3254 3266 ui.writenoi18n(
3255 3267 (
3256 3268 b" rev flag offset length size link p1"
3257 3269 b" p2 %s\n"
3258 3270 )
3259 3271 % b"nodeid".rjust(idlen)
3260 3272 )
3261 3273 else:
3262 3274 ui.writenoi18n(
3263 3275 b" rev flag size link p1 p2 %s\n"
3264 3276 % b"nodeid".rjust(idlen)
3265 3277 )
3266 3278
3267 3279 for i in r:
3268 3280 node = r.node(i)
3269 3281 if format == 0:
3270 3282 try:
3271 3283 pp = r.parents(node)
3272 3284 except Exception:
3273 3285 pp = [nullid, nullid]
3274 3286 if ui.verbose:
3275 3287 ui.write(
3276 3288 b"% 6d % 9d % 7d % 7d %s %s %s\n"
3277 3289 % (
3278 3290 i,
3279 3291 r.start(i),
3280 3292 r.length(i),
3281 3293 r.linkrev(i),
3282 3294 shortfn(node),
3283 3295 shortfn(pp[0]),
3284 3296 shortfn(pp[1]),
3285 3297 )
3286 3298 )
3287 3299 else:
3288 3300 ui.write(
3289 3301 b"% 6d % 7d %s %s %s\n"
3290 3302 % (
3291 3303 i,
3292 3304 r.linkrev(i),
3293 3305 shortfn(node),
3294 3306 shortfn(pp[0]),
3295 3307 shortfn(pp[1]),
3296 3308 )
3297 3309 )
3298 3310 elif format == 1:
3299 3311 pr = r.parentrevs(i)
3300 3312 if ui.verbose:
3301 3313 ui.write(
3302 3314 b"% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n"
3303 3315 % (
3304 3316 i,
3305 3317 r.flags(i),
3306 3318 r.start(i),
3307 3319 r.length(i),
3308 3320 r.rawsize(i),
3309 3321 r.linkrev(i),
3310 3322 pr[0],
3311 3323 pr[1],
3312 3324 shortfn(node),
3313 3325 )
3314 3326 )
3315 3327 else:
3316 3328 ui.write(
3317 3329 b"% 6d %04x % 8d % 6d % 6d % 6d %s\n"
3318 3330 % (
3319 3331 i,
3320 3332 r.flags(i),
3321 3333 r.rawsize(i),
3322 3334 r.linkrev(i),
3323 3335 pr[0],
3324 3336 pr[1],
3325 3337 shortfn(node),
3326 3338 )
3327 3339 )
3328 3340
3329 3341
3330 3342 @command(
3331 3343 b'debugrevspec',
3332 3344 [
3333 3345 (
3334 3346 b'',
3335 3347 b'optimize',
3336 3348 None,
3337 3349 _(b'print parsed tree after optimizing (DEPRECATED)'),
3338 3350 ),
3339 3351 (
3340 3352 b'',
3341 3353 b'show-revs',
3342 3354 True,
3343 3355 _(b'print list of result revisions (default)'),
3344 3356 ),
3345 3357 (
3346 3358 b's',
3347 3359 b'show-set',
3348 3360 None,
3349 3361 _(b'print internal representation of result set'),
3350 3362 ),
3351 3363 (
3352 3364 b'p',
3353 3365 b'show-stage',
3354 3366 [],
3355 3367 _(b'print parsed tree at the given stage'),
3356 3368 _(b'NAME'),
3357 3369 ),
3358 3370 (b'', b'no-optimized', False, _(b'evaluate tree without optimization')),
3359 3371 (b'', b'verify-optimized', False, _(b'verify optimized result')),
3360 3372 ],
3361 3373 b'REVSPEC',
3362 3374 )
3363 3375 def debugrevspec(ui, repo, expr, **opts):
3364 3376 """parse and apply a revision specification
3365 3377
3366 3378 Use -p/--show-stage option to print the parsed tree at the given stages.
3367 3379 Use -p all to print tree at every stage.
3368 3380
3369 3381 Use --no-show-revs option with -s or -p to print only the set
3370 3382 representation or the parsed tree respectively.
3371 3383
3372 3384 Use --verify-optimized to compare the optimized result with the unoptimized
3373 3385 one. Returns 1 if the optimized result differs.
3374 3386 """
3375 3387 opts = pycompat.byteskwargs(opts)
3376 3388 aliases = ui.configitems(b'revsetalias')
3377 3389 stages = [
3378 3390 (b'parsed', lambda tree: tree),
3379 3391 (
3380 3392 b'expanded',
3381 3393 lambda tree: revsetlang.expandaliases(tree, aliases, ui.warn),
3382 3394 ),
3383 3395 (b'concatenated', revsetlang.foldconcat),
3384 3396 (b'analyzed', revsetlang.analyze),
3385 3397 (b'optimized', revsetlang.optimize),
3386 3398 ]
3387 3399 if opts[b'no_optimized']:
3388 3400 stages = stages[:-1]
3389 3401 if opts[b'verify_optimized'] and opts[b'no_optimized']:
3390 3402 raise error.Abort(
3391 3403 _(b'cannot use --verify-optimized with --no-optimized')
3392 3404 )
3393 3405 stagenames = {n for n, f in stages}
3394 3406
3395 3407 showalways = set()
3396 3408 showchanged = set()
3397 3409 if ui.verbose and not opts[b'show_stage']:
3398 3410 # show parsed tree by --verbose (deprecated)
3399 3411 showalways.add(b'parsed')
3400 3412 showchanged.update([b'expanded', b'concatenated'])
3401 3413 if opts[b'optimize']:
3402 3414 showalways.add(b'optimized')
3403 3415 if opts[b'show_stage'] and opts[b'optimize']:
3404 3416 raise error.Abort(_(b'cannot use --optimize with --show-stage'))
3405 3417 if opts[b'show_stage'] == [b'all']:
3406 3418 showalways.update(stagenames)
3407 3419 else:
3408 3420 for n in opts[b'show_stage']:
3409 3421 if n not in stagenames:
3410 3422 raise error.Abort(_(b'invalid stage name: %s') % n)
3411 3423 showalways.update(opts[b'show_stage'])
3412 3424
3413 3425 treebystage = {}
3414 3426 printedtree = None
3415 3427 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
3416 3428 for n, f in stages:
3417 3429 treebystage[n] = tree = f(tree)
3418 3430 if n in showalways or (n in showchanged and tree != printedtree):
3419 3431 if opts[b'show_stage'] or n != b'parsed':
3420 3432 ui.write(b"* %s:\n" % n)
3421 3433 ui.write(revsetlang.prettyformat(tree), b"\n")
3422 3434 printedtree = tree
3423 3435
3424 3436 if opts[b'verify_optimized']:
3425 3437 arevs = revset.makematcher(treebystage[b'analyzed'])(repo)
3426 3438 brevs = revset.makematcher(treebystage[b'optimized'])(repo)
3427 3439 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3428 3440 ui.writenoi18n(
3429 3441 b"* analyzed set:\n", stringutil.prettyrepr(arevs), b"\n"
3430 3442 )
3431 3443 ui.writenoi18n(
3432 3444 b"* optimized set:\n", stringutil.prettyrepr(brevs), b"\n"
3433 3445 )
3434 3446 arevs = list(arevs)
3435 3447 brevs = list(brevs)
3436 3448 if arevs == brevs:
3437 3449 return 0
3438 3450 ui.writenoi18n(b'--- analyzed\n', label=b'diff.file_a')
3439 3451 ui.writenoi18n(b'+++ optimized\n', label=b'diff.file_b')
3440 3452 sm = difflib.SequenceMatcher(None, arevs, brevs)
3441 3453 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3442 3454 if tag in ('delete', 'replace'):
3443 3455 for c in arevs[alo:ahi]:
3444 3456 ui.write(b'-%d\n' % c, label=b'diff.deleted')
3445 3457 if tag in ('insert', 'replace'):
3446 3458 for c in brevs[blo:bhi]:
3447 3459 ui.write(b'+%d\n' % c, label=b'diff.inserted')
3448 3460 if tag == 'equal':
3449 3461 for c in arevs[alo:ahi]:
3450 3462 ui.write(b' %d\n' % c)
3451 3463 return 1
3452 3464
3453 3465 func = revset.makematcher(tree)
3454 3466 revs = func(repo)
3455 3467 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3456 3468 ui.writenoi18n(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
3457 3469 if not opts[b'show_revs']:
3458 3470 return
3459 3471 for c in revs:
3460 3472 ui.write(b"%d\n" % c)
3461 3473
3462 3474
3463 3475 @command(
3464 3476 b'debugserve',
3465 3477 [
3466 3478 (
3467 3479 b'',
3468 3480 b'sshstdio',
3469 3481 False,
3470 3482 _(b'run an SSH server bound to process handles'),
3471 3483 ),
3472 3484 (b'', b'logiofd', b'', _(b'file descriptor to log server I/O to')),
3473 3485 (b'', b'logiofile', b'', _(b'file to log server I/O to')),
3474 3486 ],
3475 3487 b'',
3476 3488 )
3477 3489 def debugserve(ui, repo, **opts):
3478 3490 """run a server with advanced settings
3479 3491
3480 3492 This command is similar to :hg:`serve`. It exists partially as a
3481 3493 workaround to the fact that ``hg serve --stdio`` must have specific
3482 3494 arguments for security reasons.
3483 3495 """
3484 3496 opts = pycompat.byteskwargs(opts)
3485 3497
3486 3498 if not opts[b'sshstdio']:
3487 3499 raise error.Abort(_(b'only --sshstdio is currently supported'))
3488 3500
3489 3501 logfh = None
3490 3502
3491 3503 if opts[b'logiofd'] and opts[b'logiofile']:
3492 3504 raise error.Abort(_(b'cannot use both --logiofd and --logiofile'))
3493 3505
3494 3506 if opts[b'logiofd']:
3495 3507 # Ideally we would be line buffered. But line buffering in binary
3496 3508 # mode isn't supported and emits a warning in Python 3.8+. Disabling
3497 3509 # buffering could have performance impacts. But since this isn't
3498 3510 # performance critical code, it should be fine.
3499 3511 try:
3500 3512 logfh = os.fdopen(int(opts[b'logiofd']), 'ab', 0)
3501 3513 except OSError as e:
3502 3514 if e.errno != errno.ESPIPE:
3503 3515 raise
3504 3516 # can't seek a pipe, so `ab` mode fails on py3
3505 3517 logfh = os.fdopen(int(opts[b'logiofd']), 'wb', 0)
3506 3518 elif opts[b'logiofile']:
3507 3519 logfh = open(opts[b'logiofile'], b'ab', 0)
3508 3520
3509 3521 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
3510 3522 s.serve_forever()
3511 3523
3512 3524
3513 3525 @command(b'debugsetparents', [], _(b'REV1 [REV2]'))
3514 3526 def debugsetparents(ui, repo, rev1, rev2=None):
3515 3527 """manually set the parents of the current working directory (DANGEROUS)
3516 3528
3517 3529 This command is not what you are looking for and should not be used. Using
3518 3530 this command will most certainly results in slight corruption of the file
3519 3531 level histories withing your repository. DO NOT USE THIS COMMAND.
3520 3532
3521 3533 The command update the p1 and p2 field in the dirstate, and not touching
3522 3534 anything else. This useful for writing repository conversion tools, but
3523 3535 should be used with extreme care. For example, neither the working
3524 3536 directory nor the dirstate is updated, so file status may be incorrect
3525 3537 after running this command. Only used if you are one of the few people that
3526 3538 deeply unstand both conversion tools and file level histories. If you are
3527 3539 reading this help, you are not one of this people (most of them sailed west
3528 3540 from Mithlond anyway.
3529 3541
3530 3542 So one last time DO NOT USE THIS COMMAND.
3531 3543
3532 3544 Returns 0 on success.
3533 3545 """
3534 3546
3535 3547 node1 = scmutil.revsingle(repo, rev1).node()
3536 3548 node2 = scmutil.revsingle(repo, rev2, b'null').node()
3537 3549
3538 3550 with repo.wlock():
3539 3551 repo.setparents(node1, node2)
3540 3552
3541 3553
3542 3554 @command(b'debugsidedata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
3543 3555 def debugsidedata(ui, repo, file_, rev=None, **opts):
3544 3556 """dump the side data for a cl/manifest/file revision
3545 3557
3546 3558 Use --verbose to dump the sidedata content."""
3547 3559 opts = pycompat.byteskwargs(opts)
3548 3560 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
3549 3561 if rev is not None:
3550 3562 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3551 3563 file_, rev = None, file_
3552 3564 elif rev is None:
3553 3565 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3554 3566 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
3555 3567 r = getattr(r, '_revlog', r)
3556 3568 try:
3557 3569 sidedata = r.sidedata(r.lookup(rev))
3558 3570 except KeyError:
3559 3571 raise error.Abort(_(b'invalid revision identifier %s') % rev)
3560 3572 if sidedata:
3561 3573 sidedata = list(sidedata.items())
3562 3574 sidedata.sort()
3563 3575 ui.writenoi18n(b'%d sidedata entries\n' % len(sidedata))
3564 3576 for key, value in sidedata:
3565 3577 ui.writenoi18n(b' entry-%04o size %d\n' % (key, len(value)))
3566 3578 if ui.verbose:
3567 3579 ui.writenoi18n(b' %s\n' % stringutil.pprint(value))
3568 3580
3569 3581
3570 3582 @command(b'debugssl', [], b'[SOURCE]', optionalrepo=True)
3571 3583 def debugssl(ui, repo, source=None, **opts):
3572 3584 """test a secure connection to a server
3573 3585
3574 3586 This builds the certificate chain for the server on Windows, installing the
3575 3587 missing intermediates and trusted root via Windows Update if necessary. It
3576 3588 does nothing on other platforms.
3577 3589
3578 3590 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
3579 3591 that server is used. See :hg:`help urls` for more information.
3580 3592
3581 3593 If the update succeeds, retry the original operation. Otherwise, the cause
3582 3594 of the SSL error is likely another issue.
3583 3595 """
3584 3596 if not pycompat.iswindows:
3585 3597 raise error.Abort(
3586 3598 _(b'certificate chain building is only possible on Windows')
3587 3599 )
3588 3600
3589 3601 if not source:
3590 3602 if not repo:
3591 3603 raise error.Abort(
3592 3604 _(
3593 3605 b"there is no Mercurial repository here, and no "
3594 3606 b"server specified"
3595 3607 )
3596 3608 )
3597 3609 source = b"default"
3598 3610
3599 3611 source, branches = hg.parseurl(ui.expandpath(source))
3600 3612 url = util.url(source)
3601 3613
3602 3614 defaultport = {b'https': 443, b'ssh': 22}
3603 3615 if url.scheme in defaultport:
3604 3616 try:
3605 3617 addr = (url.host, int(url.port or defaultport[url.scheme]))
3606 3618 except ValueError:
3607 3619 raise error.Abort(_(b"malformed port number in URL"))
3608 3620 else:
3609 3621 raise error.Abort(_(b"only https and ssh connections are supported"))
3610 3622
3611 3623 from . import win32
3612 3624
3613 3625 s = ssl.wrap_socket(
3614 3626 socket.socket(),
3615 3627 ssl_version=ssl.PROTOCOL_TLS,
3616 3628 cert_reqs=ssl.CERT_NONE,
3617 3629 ca_certs=None,
3618 3630 )
3619 3631
3620 3632 try:
3621 3633 s.connect(addr)
3622 3634 cert = s.getpeercert(True)
3623 3635
3624 3636 ui.status(_(b'checking the certificate chain for %s\n') % url.host)
3625 3637
3626 3638 complete = win32.checkcertificatechain(cert, build=False)
3627 3639
3628 3640 if not complete:
3629 3641 ui.status(_(b'certificate chain is incomplete, updating... '))
3630 3642
3631 3643 if not win32.checkcertificatechain(cert):
3632 3644 ui.status(_(b'failed.\n'))
3633 3645 else:
3634 3646 ui.status(_(b'done.\n'))
3635 3647 else:
3636 3648 ui.status(_(b'full certificate chain is available\n'))
3637 3649 finally:
3638 3650 s.close()
3639 3651
3640 3652
3641 3653 @command(
3642 3654 b"debugbackupbundle",
3643 3655 [
3644 3656 (
3645 3657 b"",
3646 3658 b"recover",
3647 3659 b"",
3648 3660 b"brings the specified changeset back into the repository",
3649 3661 )
3650 3662 ]
3651 3663 + cmdutil.logopts,
3652 3664 _(b"hg debugbackupbundle [--recover HASH]"),
3653 3665 )
3654 3666 def debugbackupbundle(ui, repo, *pats, **opts):
3655 3667 """lists the changesets available in backup bundles
3656 3668
3657 3669 Without any arguments, this command prints a list of the changesets in each
3658 3670 backup bundle.
3659 3671
3660 3672 --recover takes a changeset hash and unbundles the first bundle that
3661 3673 contains that hash, which puts that changeset back in your repository.
3662 3674
3663 3675 --verbose will print the entire commit message and the bundle path for that
3664 3676 backup.
3665 3677 """
3666 3678 backups = list(
3667 3679 filter(
3668 3680 os.path.isfile, glob.glob(repo.vfs.join(b"strip-backup") + b"/*.hg")
3669 3681 )
3670 3682 )
3671 3683 backups.sort(key=lambda x: os.path.getmtime(x), reverse=True)
3672 3684
3673 3685 opts = pycompat.byteskwargs(opts)
3674 3686 opts[b"bundle"] = b""
3675 3687 opts[b"force"] = None
3676 3688 limit = logcmdutil.getlimit(opts)
3677 3689
3678 3690 def display(other, chlist, displayer):
3679 3691 if opts.get(b"newest_first"):
3680 3692 chlist.reverse()
3681 3693 count = 0
3682 3694 for n in chlist:
3683 3695 if limit is not None and count >= limit:
3684 3696 break
3685 3697 parents = [True for p in other.changelog.parents(n) if p != nullid]
3686 3698 if opts.get(b"no_merges") and len(parents) == 2:
3687 3699 continue
3688 3700 count += 1
3689 3701 displayer.show(other[n])
3690 3702
3691 3703 recovernode = opts.get(b"recover")
3692 3704 if recovernode:
3693 3705 if scmutil.isrevsymbol(repo, recovernode):
3694 3706 ui.warn(_(b"%s already exists in the repo\n") % recovernode)
3695 3707 return
3696 3708 elif backups:
3697 3709 msg = _(
3698 3710 b"Recover changesets using: hg debugbackupbundle --recover "
3699 3711 b"<changeset hash>\n\nAvailable backup changesets:"
3700 3712 )
3701 3713 ui.status(msg, label=b"status.removed")
3702 3714 else:
3703 3715 ui.status(_(b"no backup changesets found\n"))
3704 3716 return
3705 3717
3706 3718 for backup in backups:
3707 3719 # Much of this is copied from the hg incoming logic
3708 3720 source = ui.expandpath(os.path.relpath(backup, encoding.getcwd()))
3709 3721 source, branches = hg.parseurl(source, opts.get(b"branch"))
3710 3722 try:
3711 3723 other = hg.peer(repo, opts, source)
3712 3724 except error.LookupError as ex:
3713 3725 msg = _(b"\nwarning: unable to open bundle %s") % source
3714 3726 hint = _(b"\n(missing parent rev %s)\n") % short(ex.name)
3715 3727 ui.warn(msg, hint=hint)
3716 3728 continue
3717 3729 revs, checkout = hg.addbranchrevs(
3718 3730 repo, other, branches, opts.get(b"rev")
3719 3731 )
3720 3732
3721 3733 if revs:
3722 3734 revs = [other.lookup(rev) for rev in revs]
3723 3735
3724 3736 quiet = ui.quiet
3725 3737 try:
3726 3738 ui.quiet = True
3727 3739 other, chlist, cleanupfn = bundlerepo.getremotechanges(
3728 3740 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
3729 3741 )
3730 3742 except error.LookupError:
3731 3743 continue
3732 3744 finally:
3733 3745 ui.quiet = quiet
3734 3746
3735 3747 try:
3736 3748 if not chlist:
3737 3749 continue
3738 3750 if recovernode:
3739 3751 with repo.lock(), repo.transaction(b"unbundle") as tr:
3740 3752 if scmutil.isrevsymbol(other, recovernode):
3741 3753 ui.status(_(b"Unbundling %s\n") % (recovernode))
3742 3754 f = hg.openpath(ui, source)
3743 3755 gen = exchange.readbundle(ui, f, source)
3744 3756 if isinstance(gen, bundle2.unbundle20):
3745 3757 bundle2.applybundle(
3746 3758 repo,
3747 3759 gen,
3748 3760 tr,
3749 3761 source=b"unbundle",
3750 3762 url=b"bundle:" + source,
3751 3763 )
3752 3764 else:
3753 3765 gen.apply(repo, b"unbundle", b"bundle:" + source)
3754 3766 break
3755 3767 else:
3756 3768 backupdate = encoding.strtolocal(
3757 3769 time.strftime(
3758 3770 "%a %H:%M, %Y-%m-%d",
3759 3771 time.localtime(os.path.getmtime(source)),
3760 3772 )
3761 3773 )
3762 3774 ui.status(b"\n%s\n" % (backupdate.ljust(50)))
3763 3775 if ui.verbose:
3764 3776 ui.status(b"%s%s\n" % (b"bundle:".ljust(13), source))
3765 3777 else:
3766 3778 opts[
3767 3779 b"template"
3768 3780 ] = b"{label('status.modified', node|short)} {desc|firstline}\n"
3769 3781 displayer = logcmdutil.changesetdisplayer(
3770 3782 ui, other, opts, False
3771 3783 )
3772 3784 display(other, chlist, displayer)
3773 3785 displayer.close()
3774 3786 finally:
3775 3787 cleanupfn()
3776 3788
3777 3789
3778 3790 @command(
3779 3791 b'debugsub',
3780 3792 [(b'r', b'rev', b'', _(b'revision to check'), _(b'REV'))],
3781 3793 _(b'[-r REV] [REV]'),
3782 3794 )
3783 3795 def debugsub(ui, repo, rev=None):
3784 3796 ctx = scmutil.revsingle(repo, rev, None)
3785 3797 for k, v in sorted(ctx.substate.items()):
3786 3798 ui.writenoi18n(b'path %s\n' % k)
3787 3799 ui.writenoi18n(b' source %s\n' % v[0])
3788 3800 ui.writenoi18n(b' revision %s\n' % v[1])
3789 3801
3790 3802
3791 3803 @command(b'debugshell', optionalrepo=True)
3792 3804 def debugshell(ui, repo):
3793 3805 """run an interactive Python interpreter
3794 3806
3795 3807 The local namespace is provided with a reference to the ui and
3796 3808 the repo instance (if available).
3797 3809 """
3798 3810 import code
3799 3811
3800 3812 imported_objects = {
3801 3813 'ui': ui,
3802 3814 'repo': repo,
3803 3815 }
3804 3816
3805 3817 code.interact(local=imported_objects)
3806 3818
3807 3819
3808 3820 @command(
3809 3821 b'debugsuccessorssets',
3810 3822 [(b'', b'closest', False, _(b'return closest successors sets only'))],
3811 3823 _(b'[REV]'),
3812 3824 )
3813 3825 def debugsuccessorssets(ui, repo, *revs, **opts):
3814 3826 """show set of successors for revision
3815 3827
3816 3828 A successors set of changeset A is a consistent group of revisions that
3817 3829 succeed A. It contains non-obsolete changesets only unless closests
3818 3830 successors set is set.
3819 3831
3820 3832 In most cases a changeset A has a single successors set containing a single
3821 3833 successor (changeset A replaced by A').
3822 3834
3823 3835 A changeset that is made obsolete with no successors are called "pruned".
3824 3836 Such changesets have no successors sets at all.
3825 3837
3826 3838 A changeset that has been "split" will have a successors set containing
3827 3839 more than one successor.
3828 3840
3829 3841 A changeset that has been rewritten in multiple different ways is called
3830 3842 "divergent". Such changesets have multiple successor sets (each of which
3831 3843 may also be split, i.e. have multiple successors).
3832 3844
3833 3845 Results are displayed as follows::
3834 3846
3835 3847 <rev1>
3836 3848 <successors-1A>
3837 3849 <rev2>
3838 3850 <successors-2A>
3839 3851 <successors-2B1> <successors-2B2> <successors-2B3>
3840 3852
3841 3853 Here rev2 has two possible (i.e. divergent) successors sets. The first
3842 3854 holds one element, whereas the second holds three (i.e. the changeset has
3843 3855 been split).
3844 3856 """
3845 3857 # passed to successorssets caching computation from one call to another
3846 3858 cache = {}
3847 3859 ctx2str = bytes
3848 3860 node2str = short
3849 3861 for rev in scmutil.revrange(repo, revs):
3850 3862 ctx = repo[rev]
3851 3863 ui.write(b'%s\n' % ctx2str(ctx))
3852 3864 for succsset in obsutil.successorssets(
3853 3865 repo, ctx.node(), closest=opts['closest'], cache=cache
3854 3866 ):
3855 3867 if succsset:
3856 3868 ui.write(b' ')
3857 3869 ui.write(node2str(succsset[0]))
3858 3870 for node in succsset[1:]:
3859 3871 ui.write(b' ')
3860 3872 ui.write(node2str(node))
3861 3873 ui.write(b'\n')
3862 3874
3863 3875
3864 3876 @command(b'debugtagscache', [])
3865 3877 def debugtagscache(ui, repo):
3866 3878 """display the contents of .hg/cache/hgtagsfnodes1"""
3867 3879 cache = tagsmod.hgtagsfnodescache(repo.unfiltered())
3868 3880 flog = repo.file(b'.hgtags')
3869 3881 for r in repo:
3870 3882 node = repo[r].node()
3871 3883 tagsnode = cache.getfnode(node, computemissing=False)
3872 3884 if tagsnode:
3873 3885 tagsnodedisplay = hex(tagsnode)
3874 3886 if not flog.hasnode(tagsnode):
3875 3887 tagsnodedisplay += b' (unknown node)'
3876 3888 elif tagsnode is None:
3877 3889 tagsnodedisplay = b'missing'
3878 3890 else:
3879 3891 tagsnodedisplay = b'invalid'
3880 3892
3881 3893 ui.write(b'%d %s %s\n' % (r, hex(node), tagsnodedisplay))
3882 3894
3883 3895
3884 3896 @command(
3885 3897 b'debugtemplate',
3886 3898 [
3887 3899 (b'r', b'rev', [], _(b'apply template on changesets'), _(b'REV')),
3888 3900 (b'D', b'define', [], _(b'define template keyword'), _(b'KEY=VALUE')),
3889 3901 ],
3890 3902 _(b'[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3891 3903 optionalrepo=True,
3892 3904 )
3893 3905 def debugtemplate(ui, repo, tmpl, **opts):
3894 3906 """parse and apply a template
3895 3907
3896 3908 If -r/--rev is given, the template is processed as a log template and
3897 3909 applied to the given changesets. Otherwise, it is processed as a generic
3898 3910 template.
3899 3911
3900 3912 Use --verbose to print the parsed tree.
3901 3913 """
3902 3914 revs = None
3903 3915 if opts['rev']:
3904 3916 if repo is None:
3905 3917 raise error.RepoError(
3906 3918 _(b'there is no Mercurial repository here (.hg not found)')
3907 3919 )
3908 3920 revs = scmutil.revrange(repo, opts['rev'])
3909 3921
3910 3922 props = {}
3911 3923 for d in opts['define']:
3912 3924 try:
3913 3925 k, v = (e.strip() for e in d.split(b'=', 1))
3914 3926 if not k or k == b'ui':
3915 3927 raise ValueError
3916 3928 props[k] = v
3917 3929 except ValueError:
3918 3930 raise error.Abort(_(b'malformed keyword definition: %s') % d)
3919 3931
3920 3932 if ui.verbose:
3921 3933 aliases = ui.configitems(b'templatealias')
3922 3934 tree = templater.parse(tmpl)
3923 3935 ui.note(templater.prettyformat(tree), b'\n')
3924 3936 newtree = templater.expandaliases(tree, aliases)
3925 3937 if newtree != tree:
3926 3938 ui.notenoi18n(
3927 3939 b"* expanded:\n", templater.prettyformat(newtree), b'\n'
3928 3940 )
3929 3941
3930 3942 if revs is None:
3931 3943 tres = formatter.templateresources(ui, repo)
3932 3944 t = formatter.maketemplater(ui, tmpl, resources=tres)
3933 3945 if ui.verbose:
3934 3946 kwds, funcs = t.symbolsuseddefault()
3935 3947 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3936 3948 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3937 3949 ui.write(t.renderdefault(props))
3938 3950 else:
3939 3951 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
3940 3952 if ui.verbose:
3941 3953 kwds, funcs = displayer.t.symbolsuseddefault()
3942 3954 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3943 3955 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3944 3956 for r in revs:
3945 3957 displayer.show(repo[r], **pycompat.strkwargs(props))
3946 3958 displayer.close()
3947 3959
3948 3960
3949 3961 @command(
3950 3962 b'debuguigetpass',
3951 3963 [
3952 3964 (b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),
3953 3965 ],
3954 3966 _(b'[-p TEXT]'),
3955 3967 norepo=True,
3956 3968 )
3957 3969 def debuguigetpass(ui, prompt=b''):
3958 3970 """show prompt to type password"""
3959 3971 r = ui.getpass(prompt)
3960 3972 if r is None:
3961 3973 r = b"<default response>"
3962 3974 ui.writenoi18n(b'response: %s\n' % r)
3963 3975
3964 3976
3965 3977 @command(
3966 3978 b'debuguiprompt',
3967 3979 [
3968 3980 (b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),
3969 3981 ],
3970 3982 _(b'[-p TEXT]'),
3971 3983 norepo=True,
3972 3984 )
3973 3985 def debuguiprompt(ui, prompt=b''):
3974 3986 """show plain prompt"""
3975 3987 r = ui.prompt(prompt)
3976 3988 ui.writenoi18n(b'response: %s\n' % r)
3977 3989
3978 3990
3979 3991 @command(b'debugupdatecaches', [])
3980 3992 def debugupdatecaches(ui, repo, *pats, **opts):
3981 3993 """warm all known caches in the repository"""
3982 3994 with repo.wlock(), repo.lock():
3983 3995 repo.updatecaches(full=True)
3984 3996
3985 3997
3986 3998 @command(
3987 3999 b'debugupgraderepo',
3988 4000 [
3989 4001 (
3990 4002 b'o',
3991 4003 b'optimize',
3992 4004 [],
3993 4005 _(b'extra optimization to perform'),
3994 4006 _(b'NAME'),
3995 4007 ),
3996 4008 (b'', b'run', False, _(b'performs an upgrade')),
3997 4009 (b'', b'backup', True, _(b'keep the old repository content around')),
3998 4010 (b'', b'changelog', None, _(b'select the changelog for upgrade')),
3999 4011 (b'', b'manifest', None, _(b'select the manifest for upgrade')),
4000 4012 (b'', b'filelogs', None, _(b'select all filelogs for upgrade')),
4001 4013 ],
4002 4014 )
4003 4015 def debugupgraderepo(ui, repo, run=False, optimize=None, backup=True, **opts):
4004 4016 """upgrade a repository to use different features
4005 4017
4006 4018 If no arguments are specified, the repository is evaluated for upgrade
4007 4019 and a list of problems and potential optimizations is printed.
4008 4020
4009 4021 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
4010 4022 can be influenced via additional arguments. More details will be provided
4011 4023 by the command output when run without ``--run``.
4012 4024
4013 4025 During the upgrade, the repository will be locked and no writes will be
4014 4026 allowed.
4015 4027
4016 4028 At the end of the upgrade, the repository may not be readable while new
4017 4029 repository data is swapped in. This window will be as long as it takes to
4018 4030 rename some directories inside the ``.hg`` directory. On most machines, this
4019 4031 should complete almost instantaneously and the chances of a consumer being
4020 4032 unable to access the repository should be low.
4021 4033
4022 4034 By default, all revlog will be upgraded. You can restrict this using flag
4023 4035 such as `--manifest`:
4024 4036
4025 4037 * `--manifest`: only optimize the manifest
4026 4038 * `--no-manifest`: optimize all revlog but the manifest
4027 4039 * `--changelog`: optimize the changelog only
4028 4040 * `--no-changelog --no-manifest`: optimize filelogs only
4029 4041 * `--filelogs`: optimize the filelogs only
4030 4042 * `--no-changelog --no-manifest --no-filelogs`: skip all revlog optimizations
4031 4043 """
4032 4044 return upgrade.upgraderepo(
4033 4045 ui, repo, run=run, optimize=set(optimize), backup=backup, **opts
4034 4046 )
4035 4047
4036 4048
4037 4049 @command(
4038 4050 b'debugwalk', cmdutil.walkopts, _(b'[OPTION]... [FILE]...'), inferrepo=True
4039 4051 )
4040 4052 def debugwalk(ui, repo, *pats, **opts):
4041 4053 """show how files match on given patterns"""
4042 4054 opts = pycompat.byteskwargs(opts)
4043 4055 m = scmutil.match(repo[None], pats, opts)
4044 4056 if ui.verbose:
4045 4057 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
4046 4058 items = list(repo[None].walk(m))
4047 4059 if not items:
4048 4060 return
4049 4061 f = lambda fn: fn
4050 4062 if ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/':
4051 4063 f = lambda fn: util.normpath(fn)
4052 4064 fmt = b'f %%-%ds %%-%ds %%s' % (
4053 4065 max([len(abs) for abs in items]),
4054 4066 max([len(repo.pathto(abs)) for abs in items]),
4055 4067 )
4056 4068 for abs in items:
4057 4069 line = fmt % (
4058 4070 abs,
4059 4071 f(repo.pathto(abs)),
4060 4072 m.exact(abs) and b'exact' or b'',
4061 4073 )
4062 4074 ui.write(b"%s\n" % line.rstrip())
4063 4075
4064 4076
4065 4077 @command(b'debugwhyunstable', [], _(b'REV'))
4066 4078 def debugwhyunstable(ui, repo, rev):
4067 4079 """explain instabilities of a changeset"""
4068 4080 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
4069 4081 dnodes = b''
4070 4082 if entry.get(b'divergentnodes'):
4071 4083 dnodes = (
4072 4084 b' '.join(
4073 4085 b'%s (%s)' % (ctx.hex(), ctx.phasestr())
4074 4086 for ctx in entry[b'divergentnodes']
4075 4087 )
4076 4088 + b' '
4077 4089 )
4078 4090 ui.write(
4079 4091 b'%s: %s%s %s\n'
4080 4092 % (entry[b'instability'], dnodes, entry[b'reason'], entry[b'node'])
4081 4093 )
4082 4094
4083 4095
4084 4096 @command(
4085 4097 b'debugwireargs',
4086 4098 [
4087 4099 (b'', b'three', b'', b'three'),
4088 4100 (b'', b'four', b'', b'four'),
4089 4101 (b'', b'five', b'', b'five'),
4090 4102 ]
4091 4103 + cmdutil.remoteopts,
4092 4104 _(b'REPO [OPTIONS]... [ONE [TWO]]'),
4093 4105 norepo=True,
4094 4106 )
4095 4107 def debugwireargs(ui, repopath, *vals, **opts):
4096 4108 opts = pycompat.byteskwargs(opts)
4097 4109 repo = hg.peer(ui, opts, repopath)
4098 for opt in cmdutil.remoteopts:
4099 del opts[opt[1]]
4100 args = {}
4101 for k, v in pycompat.iteritems(opts):
4102 if v:
4103 args[k] = v
4104 args = pycompat.strkwargs(args)
4105 # run twice to check that we don't mess up the stream for the next command
4106 res1 = repo.debugwireargs(*vals, **args)
4107 res2 = repo.debugwireargs(*vals, **args)
4108 ui.write(b"%s\n" % res1)
4109 if res1 != res2:
4110 ui.warn(b"%s\n" % res2)
4110 try:
4111 for opt in cmdutil.remoteopts:
4112 del opts[opt[1]]
4113 args = {}
4114 for k, v in pycompat.iteritems(opts):
4115 if v:
4116 args[k] = v
4117 args = pycompat.strkwargs(args)
4118 # run twice to check that we don't mess up the stream for the next command
4119 res1 = repo.debugwireargs(*vals, **args)
4120 res2 = repo.debugwireargs(*vals, **args)
4121 ui.write(b"%s\n" % res1)
4122 if res1 != res2:
4123 ui.warn(b"%s\n" % res2)
4124 finally:
4125 repo.close()
4111 4126
4112 4127
4113 4128 def _parsewirelangblocks(fh):
4114 4129 activeaction = None
4115 4130 blocklines = []
4116 4131 lastindent = 0
4117 4132
4118 4133 for line in fh:
4119 4134 line = line.rstrip()
4120 4135 if not line:
4121 4136 continue
4122 4137
4123 4138 if line.startswith(b'#'):
4124 4139 continue
4125 4140
4126 4141 if not line.startswith(b' '):
4127 4142 # New block. Flush previous one.
4128 4143 if activeaction:
4129 4144 yield activeaction, blocklines
4130 4145
4131 4146 activeaction = line
4132 4147 blocklines = []
4133 4148 lastindent = 0
4134 4149 continue
4135 4150
4136 4151 # Else we start with an indent.
4137 4152
4138 4153 if not activeaction:
4139 4154 raise error.Abort(_(b'indented line outside of block'))
4140 4155
4141 4156 indent = len(line) - len(line.lstrip())
4142 4157
4143 4158 # If this line is indented more than the last line, concatenate it.
4144 4159 if indent > lastindent and blocklines:
4145 4160 blocklines[-1] += line.lstrip()
4146 4161 else:
4147 4162 blocklines.append(line)
4148 4163 lastindent = indent
4149 4164
4150 4165 # Flush last block.
4151 4166 if activeaction:
4152 4167 yield activeaction, blocklines
4153 4168
4154 4169
4155 4170 @command(
4156 4171 b'debugwireproto',
4157 4172 [
4158 4173 (b'', b'localssh', False, _(b'start an SSH server for this repo')),
4159 4174 (b'', b'peer', b'', _(b'construct a specific version of the peer')),
4160 4175 (
4161 4176 b'',
4162 4177 b'noreadstderr',
4163 4178 False,
4164 4179 _(b'do not read from stderr of the remote'),
4165 4180 ),
4166 4181 (
4167 4182 b'',
4168 4183 b'nologhandshake',
4169 4184 False,
4170 4185 _(b'do not log I/O related to the peer handshake'),
4171 4186 ),
4172 4187 ]
4173 4188 + cmdutil.remoteopts,
4174 4189 _(b'[PATH]'),
4175 4190 optionalrepo=True,
4176 4191 )
4177 4192 def debugwireproto(ui, repo, path=None, **opts):
4178 4193 """send wire protocol commands to a server
4179 4194
4180 4195 This command can be used to issue wire protocol commands to remote
4181 4196 peers and to debug the raw data being exchanged.
4182 4197
4183 4198 ``--localssh`` will start an SSH server against the current repository
4184 4199 and connect to that. By default, the connection will perform a handshake
4185 4200 and establish an appropriate peer instance.
4186 4201
4187 4202 ``--peer`` can be used to bypass the handshake protocol and construct a
4188 4203 peer instance using the specified class type. Valid values are ``raw``,
4189 4204 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
4190 4205 raw data payloads and don't support higher-level command actions.
4191 4206
4192 4207 ``--noreadstderr`` can be used to disable automatic reading from stderr
4193 4208 of the peer (for SSH connections only). Disabling automatic reading of
4194 4209 stderr is useful for making output more deterministic.
4195 4210
4196 4211 Commands are issued via a mini language which is specified via stdin.
4197 4212 The language consists of individual actions to perform. An action is
4198 4213 defined by a block. A block is defined as a line with no leading
4199 4214 space followed by 0 or more lines with leading space. Blocks are
4200 4215 effectively a high-level command with additional metadata.
4201 4216
4202 4217 Lines beginning with ``#`` are ignored.
4203 4218
4204 4219 The following sections denote available actions.
4205 4220
4206 4221 raw
4207 4222 ---
4208 4223
4209 4224 Send raw data to the server.
4210 4225
4211 4226 The block payload contains the raw data to send as one atomic send
4212 4227 operation. The data may not actually be delivered in a single system
4213 4228 call: it depends on the abilities of the transport being used.
4214 4229
4215 4230 Each line in the block is de-indented and concatenated. Then, that
4216 4231 value is evaluated as a Python b'' literal. This allows the use of
4217 4232 backslash escaping, etc.
4218 4233
4219 4234 raw+
4220 4235 ----
4221 4236
4222 4237 Behaves like ``raw`` except flushes output afterwards.
4223 4238
4224 4239 command <X>
4225 4240 -----------
4226 4241
4227 4242 Send a request to run a named command, whose name follows the ``command``
4228 4243 string.
4229 4244
4230 4245 Arguments to the command are defined as lines in this block. The format of
4231 4246 each line is ``<key> <value>``. e.g.::
4232 4247
4233 4248 command listkeys
4234 4249 namespace bookmarks
4235 4250
4236 4251 If the value begins with ``eval:``, it will be interpreted as a Python
4237 4252 literal expression. Otherwise values are interpreted as Python b'' literals.
4238 4253 This allows sending complex types and encoding special byte sequences via
4239 4254 backslash escaping.
4240 4255
4241 4256 The following arguments have special meaning:
4242 4257
4243 4258 ``PUSHFILE``
4244 4259 When defined, the *push* mechanism of the peer will be used instead
4245 4260 of the static request-response mechanism and the content of the
4246 4261 file specified in the value of this argument will be sent as the
4247 4262 command payload.
4248 4263
4249 4264 This can be used to submit a local bundle file to the remote.
4250 4265
4251 4266 batchbegin
4252 4267 ----------
4253 4268
4254 4269 Instruct the peer to begin a batched send.
4255 4270
4256 4271 All ``command`` blocks are queued for execution until the next
4257 4272 ``batchsubmit`` block.
4258 4273
4259 4274 batchsubmit
4260 4275 -----------
4261 4276
4262 4277 Submit previously queued ``command`` blocks as a batch request.
4263 4278
4264 4279 This action MUST be paired with a ``batchbegin`` action.
4265 4280
4266 4281 httprequest <method> <path>
4267 4282 ---------------------------
4268 4283
4269 4284 (HTTP peer only)
4270 4285
4271 4286 Send an HTTP request to the peer.
4272 4287
4273 4288 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
4274 4289
4275 4290 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
4276 4291 headers to add to the request. e.g. ``Accept: foo``.
4277 4292
4278 4293 The following arguments are special:
4279 4294
4280 4295 ``BODYFILE``
4281 4296 The content of the file defined as the value to this argument will be
4282 4297 transferred verbatim as the HTTP request body.
4283 4298
4284 4299 ``frame <type> <flags> <payload>``
4285 4300 Send a unified protocol frame as part of the request body.
4286 4301
4287 4302 All frames will be collected and sent as the body to the HTTP
4288 4303 request.
4289 4304
4290 4305 close
4291 4306 -----
4292 4307
4293 4308 Close the connection to the server.
4294 4309
4295 4310 flush
4296 4311 -----
4297 4312
4298 4313 Flush data written to the server.
4299 4314
4300 4315 readavailable
4301 4316 -------------
4302 4317
4303 4318 Close the write end of the connection and read all available data from
4304 4319 the server.
4305 4320
4306 4321 If the connection to the server encompasses multiple pipes, we poll both
4307 4322 pipes and read available data.
4308 4323
4309 4324 readline
4310 4325 --------
4311 4326
4312 4327 Read a line of output from the server. If there are multiple output
4313 4328 pipes, reads only the main pipe.
4314 4329
4315 4330 ereadline
4316 4331 ---------
4317 4332
4318 4333 Like ``readline``, but read from the stderr pipe, if available.
4319 4334
4320 4335 read <X>
4321 4336 --------
4322 4337
4323 4338 ``read()`` N bytes from the server's main output pipe.
4324 4339
4325 4340 eread <X>
4326 4341 ---------
4327 4342
4328 4343 ``read()`` N bytes from the server's stderr pipe, if available.
4329 4344
4330 4345 Specifying Unified Frame-Based Protocol Frames
4331 4346 ----------------------------------------------
4332 4347
4333 4348 It is possible to emit a *Unified Frame-Based Protocol* by using special
4334 4349 syntax.
4335 4350
4336 4351 A frame is composed as a type, flags, and payload. These can be parsed
4337 4352 from a string of the form:
4338 4353
4339 4354 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
4340 4355
4341 4356 ``request-id`` and ``stream-id`` are integers defining the request and
4342 4357 stream identifiers.
4343 4358
4344 4359 ``type`` can be an integer value for the frame type or the string name
4345 4360 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
4346 4361 ``command-name``.
4347 4362
4348 4363 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
4349 4364 components. Each component (and there can be just one) can be an integer
4350 4365 or a flag name for stream flags or frame flags, respectively. Values are
4351 4366 resolved to integers and then bitwise OR'd together.
4352 4367
4353 4368 ``payload`` represents the raw frame payload. If it begins with
4354 4369 ``cbor:``, the following string is evaluated as Python code and the
4355 4370 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
4356 4371 as a Python byte string literal.
4357 4372 """
4358 4373 opts = pycompat.byteskwargs(opts)
4359 4374
4360 4375 if opts[b'localssh'] and not repo:
4361 4376 raise error.Abort(_(b'--localssh requires a repository'))
4362 4377
4363 4378 if opts[b'peer'] and opts[b'peer'] not in (
4364 4379 b'raw',
4365 4380 b'http2',
4366 4381 b'ssh1',
4367 4382 b'ssh2',
4368 4383 ):
4369 4384 raise error.Abort(
4370 4385 _(b'invalid value for --peer'),
4371 4386 hint=_(b'valid values are "raw", "ssh1", and "ssh2"'),
4372 4387 )
4373 4388
4374 4389 if path and opts[b'localssh']:
4375 4390 raise error.Abort(_(b'cannot specify --localssh with an explicit path'))
4376 4391
4377 4392 if ui.interactive():
4378 4393 ui.write(_(b'(waiting for commands on stdin)\n'))
4379 4394
4380 4395 blocks = list(_parsewirelangblocks(ui.fin))
4381 4396
4382 4397 proc = None
4383 4398 stdin = None
4384 4399 stdout = None
4385 4400 stderr = None
4386 4401 opener = None
4387 4402
4388 4403 if opts[b'localssh']:
4389 4404 # We start the SSH server in its own process so there is process
4390 4405 # separation. This prevents a whole class of potential bugs around
4391 4406 # shared state from interfering with server operation.
4392 4407 args = procutil.hgcmd() + [
4393 4408 b'-R',
4394 4409 repo.root,
4395 4410 b'debugserve',
4396 4411 b'--sshstdio',
4397 4412 ]
4398 4413 proc = subprocess.Popen(
4399 4414 pycompat.rapply(procutil.tonativestr, args),
4400 4415 stdin=subprocess.PIPE,
4401 4416 stdout=subprocess.PIPE,
4402 4417 stderr=subprocess.PIPE,
4403 4418 bufsize=0,
4404 4419 )
4405 4420
4406 4421 stdin = proc.stdin
4407 4422 stdout = proc.stdout
4408 4423 stderr = proc.stderr
4409 4424
4410 4425 # We turn the pipes into observers so we can log I/O.
4411 4426 if ui.verbose or opts[b'peer'] == b'raw':
4412 4427 stdin = util.makeloggingfileobject(
4413 4428 ui, proc.stdin, b'i', logdata=True
4414 4429 )
4415 4430 stdout = util.makeloggingfileobject(
4416 4431 ui, proc.stdout, b'o', logdata=True
4417 4432 )
4418 4433 stderr = util.makeloggingfileobject(
4419 4434 ui, proc.stderr, b'e', logdata=True
4420 4435 )
4421 4436
4422 4437 # --localssh also implies the peer connection settings.
4423 4438
4424 4439 url = b'ssh://localserver'
4425 4440 autoreadstderr = not opts[b'noreadstderr']
4426 4441
4427 4442 if opts[b'peer'] == b'ssh1':
4428 4443 ui.write(_(b'creating ssh peer for wire protocol version 1\n'))
4429 4444 peer = sshpeer.sshv1peer(
4430 4445 ui,
4431 4446 url,
4432 4447 proc,
4433 4448 stdin,
4434 4449 stdout,
4435 4450 stderr,
4436 4451 None,
4437 4452 autoreadstderr=autoreadstderr,
4438 4453 )
4439 4454 elif opts[b'peer'] == b'ssh2':
4440 4455 ui.write(_(b'creating ssh peer for wire protocol version 2\n'))
4441 4456 peer = sshpeer.sshv2peer(
4442 4457 ui,
4443 4458 url,
4444 4459 proc,
4445 4460 stdin,
4446 4461 stdout,
4447 4462 stderr,
4448 4463 None,
4449 4464 autoreadstderr=autoreadstderr,
4450 4465 )
4451 4466 elif opts[b'peer'] == b'raw':
4452 4467 ui.write(_(b'using raw connection to peer\n'))
4453 4468 peer = None
4454 4469 else:
4455 4470 ui.write(_(b'creating ssh peer from handshake results\n'))
4456 4471 peer = sshpeer.makepeer(
4457 4472 ui,
4458 4473 url,
4459 4474 proc,
4460 4475 stdin,
4461 4476 stdout,
4462 4477 stderr,
4463 4478 autoreadstderr=autoreadstderr,
4464 4479 )
4465 4480
4466 4481 elif path:
4467 4482 # We bypass hg.peer() so we can proxy the sockets.
4468 4483 # TODO consider not doing this because we skip
4469 4484 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
4470 4485 u = util.url(path)
4471 4486 if u.scheme != b'http':
4472 4487 raise error.Abort(_(b'only http:// paths are currently supported'))
4473 4488
4474 4489 url, authinfo = u.authinfo()
4475 4490 openerargs = {
4476 4491 'useragent': b'Mercurial debugwireproto',
4477 4492 }
4478 4493
4479 4494 # Turn pipes/sockets into observers so we can log I/O.
4480 4495 if ui.verbose:
4481 4496 openerargs.update(
4482 4497 {
4483 4498 'loggingfh': ui,
4484 4499 'loggingname': b's',
4485 4500 'loggingopts': {
4486 4501 'logdata': True,
4487 4502 'logdataapis': False,
4488 4503 },
4489 4504 }
4490 4505 )
4491 4506
4492 4507 if ui.debugflag:
4493 4508 openerargs['loggingopts']['logdataapis'] = True
4494 4509
4495 4510 # Don't send default headers when in raw mode. This allows us to
4496 4511 # bypass most of the behavior of our URL handling code so we can
4497 4512 # have near complete control over what's sent on the wire.
4498 4513 if opts[b'peer'] == b'raw':
4499 4514 openerargs['sendaccept'] = False
4500 4515
4501 4516 opener = urlmod.opener(ui, authinfo, **openerargs)
4502 4517
4503 4518 if opts[b'peer'] == b'http2':
4504 4519 ui.write(_(b'creating http peer for wire protocol version 2\n'))
4505 4520 # We go through makepeer() because we need an API descriptor for
4506 4521 # the peer instance to be useful.
4507 4522 with ui.configoverride(
4508 4523 {(b'experimental', b'httppeer.advertise-v2'): True}
4509 4524 ):
4510 4525 if opts[b'nologhandshake']:
4511 4526 ui.pushbuffer()
4512 4527
4513 4528 peer = httppeer.makepeer(ui, path, opener=opener)
4514 4529
4515 4530 if opts[b'nologhandshake']:
4516 4531 ui.popbuffer()
4517 4532
4518 4533 if not isinstance(peer, httppeer.httpv2peer):
4519 4534 raise error.Abort(
4520 4535 _(
4521 4536 b'could not instantiate HTTP peer for '
4522 4537 b'wire protocol version 2'
4523 4538 ),
4524 4539 hint=_(
4525 4540 b'the server may not have the feature '
4526 4541 b'enabled or is not allowing this '
4527 4542 b'client version'
4528 4543 ),
4529 4544 )
4530 4545
4531 4546 elif opts[b'peer'] == b'raw':
4532 4547 ui.write(_(b'using raw connection to peer\n'))
4533 4548 peer = None
4534 4549 elif opts[b'peer']:
4535 4550 raise error.Abort(
4536 4551 _(b'--peer %s not supported with HTTP peers') % opts[b'peer']
4537 4552 )
4538 4553 else:
4539 4554 peer = httppeer.makepeer(ui, path, opener=opener)
4540 4555
4541 4556 # We /could/ populate stdin/stdout with sock.makefile()...
4542 4557 else:
4543 4558 raise error.Abort(_(b'unsupported connection configuration'))
4544 4559
4545 4560 batchedcommands = None
4546 4561
4547 4562 # Now perform actions based on the parsed wire language instructions.
4548 4563 for action, lines in blocks:
4549 4564 if action in (b'raw', b'raw+'):
4550 4565 if not stdin:
4551 4566 raise error.Abort(_(b'cannot call raw/raw+ on this peer'))
4552 4567
4553 4568 # Concatenate the data together.
4554 4569 data = b''.join(l.lstrip() for l in lines)
4555 4570 data = stringutil.unescapestr(data)
4556 4571 stdin.write(data)
4557 4572
4558 4573 if action == b'raw+':
4559 4574 stdin.flush()
4560 4575 elif action == b'flush':
4561 4576 if not stdin:
4562 4577 raise error.Abort(_(b'cannot call flush on this peer'))
4563 4578 stdin.flush()
4564 4579 elif action.startswith(b'command'):
4565 4580 if not peer:
4566 4581 raise error.Abort(
4567 4582 _(
4568 4583 b'cannot send commands unless peer instance '
4569 4584 b'is available'
4570 4585 )
4571 4586 )
4572 4587
4573 4588 command = action.split(b' ', 1)[1]
4574 4589
4575 4590 args = {}
4576 4591 for line in lines:
4577 4592 # We need to allow empty values.
4578 4593 fields = line.lstrip().split(b' ', 1)
4579 4594 if len(fields) == 1:
4580 4595 key = fields[0]
4581 4596 value = b''
4582 4597 else:
4583 4598 key, value = fields
4584 4599
4585 4600 if value.startswith(b'eval:'):
4586 4601 value = stringutil.evalpythonliteral(value[5:])
4587 4602 else:
4588 4603 value = stringutil.unescapestr(value)
4589 4604
4590 4605 args[key] = value
4591 4606
4592 4607 if batchedcommands is not None:
4593 4608 batchedcommands.append((command, args))
4594 4609 continue
4595 4610
4596 4611 ui.status(_(b'sending %s command\n') % command)
4597 4612
4598 4613 if b'PUSHFILE' in args:
4599 4614 with open(args[b'PUSHFILE'], 'rb') as fh:
4600 4615 del args[b'PUSHFILE']
4601 4616 res, output = peer._callpush(
4602 4617 command, fh, **pycompat.strkwargs(args)
4603 4618 )
4604 4619 ui.status(_(b'result: %s\n') % stringutil.escapestr(res))
4605 4620 ui.status(
4606 4621 _(b'remote output: %s\n') % stringutil.escapestr(output)
4607 4622 )
4608 4623 else:
4609 4624 with peer.commandexecutor() as e:
4610 4625 res = e.callcommand(command, args).result()
4611 4626
4612 4627 if isinstance(res, wireprotov2peer.commandresponse):
4613 4628 val = res.objects()
4614 4629 ui.status(
4615 4630 _(b'response: %s\n')
4616 4631 % stringutil.pprint(val, bprefix=True, indent=2)
4617 4632 )
4618 4633 else:
4619 4634 ui.status(
4620 4635 _(b'response: %s\n')
4621 4636 % stringutil.pprint(res, bprefix=True, indent=2)
4622 4637 )
4623 4638
4624 4639 elif action == b'batchbegin':
4625 4640 if batchedcommands is not None:
4626 4641 raise error.Abort(_(b'nested batchbegin not allowed'))
4627 4642
4628 4643 batchedcommands = []
4629 4644 elif action == b'batchsubmit':
4630 4645 # There is a batching API we could go through. But it would be
4631 4646 # difficult to normalize requests into function calls. It is easier
4632 4647 # to bypass this layer and normalize to commands + args.
4633 4648 ui.status(
4634 4649 _(b'sending batch with %d sub-commands\n')
4635 4650 % len(batchedcommands)
4636 4651 )
4637 4652 assert peer is not None
4638 4653 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
4639 4654 ui.status(
4640 4655 _(b'response #%d: %s\n') % (i, stringutil.escapestr(chunk))
4641 4656 )
4642 4657
4643 4658 batchedcommands = None
4644 4659
4645 4660 elif action.startswith(b'httprequest '):
4646 4661 if not opener:
4647 4662 raise error.Abort(
4648 4663 _(b'cannot use httprequest without an HTTP peer')
4649 4664 )
4650 4665
4651 4666 request = action.split(b' ', 2)
4652 4667 if len(request) != 3:
4653 4668 raise error.Abort(
4654 4669 _(
4655 4670 b'invalid httprequest: expected format is '
4656 4671 b'"httprequest <method> <path>'
4657 4672 )
4658 4673 )
4659 4674
4660 4675 method, httppath = request[1:]
4661 4676 headers = {}
4662 4677 body = None
4663 4678 frames = []
4664 4679 for line in lines:
4665 4680 line = line.lstrip()
4666 4681 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
4667 4682 if m:
4668 4683 # Headers need to use native strings.
4669 4684 key = pycompat.strurl(m.group(1))
4670 4685 value = pycompat.strurl(m.group(2))
4671 4686 headers[key] = value
4672 4687 continue
4673 4688
4674 4689 if line.startswith(b'BODYFILE '):
4675 4690 with open(line.split(b' ', 1), b'rb') as fh:
4676 4691 body = fh.read()
4677 4692 elif line.startswith(b'frame '):
4678 4693 frame = wireprotoframing.makeframefromhumanstring(
4679 4694 line[len(b'frame ') :]
4680 4695 )
4681 4696
4682 4697 frames.append(frame)
4683 4698 else:
4684 4699 raise error.Abort(
4685 4700 _(b'unknown argument to httprequest: %s') % line
4686 4701 )
4687 4702
4688 4703 url = path + httppath
4689 4704
4690 4705 if frames:
4691 4706 body = b''.join(bytes(f) for f in frames)
4692 4707
4693 4708 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
4694 4709
4695 4710 # urllib.Request insists on using has_data() as a proxy for
4696 4711 # determining the request method. Override that to use our
4697 4712 # explicitly requested method.
4698 4713 req.get_method = lambda: pycompat.sysstr(method)
4699 4714
4700 4715 try:
4701 4716 res = opener.open(req)
4702 4717 body = res.read()
4703 4718 except util.urlerr.urlerror as e:
4704 4719 # read() method must be called, but only exists in Python 2
4705 4720 getattr(e, 'read', lambda: None)()
4706 4721 continue
4707 4722
4708 4723 ct = res.headers.get('Content-Type')
4709 4724 if ct == 'application/mercurial-cbor':
4710 4725 ui.write(
4711 4726 _(b'cbor> %s\n')
4712 4727 % stringutil.pprint(
4713 4728 cborutil.decodeall(body), bprefix=True, indent=2
4714 4729 )
4715 4730 )
4716 4731
4717 4732 elif action == b'close':
4718 4733 assert peer is not None
4719 4734 peer.close()
4720 4735 elif action == b'readavailable':
4721 4736 if not stdout or not stderr:
4722 4737 raise error.Abort(
4723 4738 _(b'readavailable not available on this peer')
4724 4739 )
4725 4740
4726 4741 stdin.close()
4727 4742 stdout.read()
4728 4743 stderr.read()
4729 4744
4730 4745 elif action == b'readline':
4731 4746 if not stdout:
4732 4747 raise error.Abort(_(b'readline not available on this peer'))
4733 4748 stdout.readline()
4734 4749 elif action == b'ereadline':
4735 4750 if not stderr:
4736 4751 raise error.Abort(_(b'ereadline not available on this peer'))
4737 4752 stderr.readline()
4738 4753 elif action.startswith(b'read '):
4739 4754 count = int(action.split(b' ', 1)[1])
4740 4755 if not stdout:
4741 4756 raise error.Abort(_(b'read not available on this peer'))
4742 4757 stdout.read(count)
4743 4758 elif action.startswith(b'eread '):
4744 4759 count = int(action.split(b' ', 1)[1])
4745 4760 if not stderr:
4746 4761 raise error.Abort(_(b'eread not available on this peer'))
4747 4762 stderr.read(count)
4748 4763 else:
4749 4764 raise error.Abort(_(b'unknown action: %s') % action)
4750 4765
4751 4766 if batchedcommands is not None:
4752 4767 raise error.Abort(_(b'unclosed "batchbegin" request'))
4753 4768
4754 4769 if peer:
4755 4770 peer.close()
4756 4771
4757 4772 if proc:
4758 4773 proc.kill()
@@ -1,1514 +1,1533 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import os
13 13 import shutil
14 14 import stat
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullhex,
20 20 nullid,
21 21 short,
22 22 )
23 23 from .pycompat import getattr
24 24
25 25 from . import (
26 26 bookmarks,
27 27 bundlerepo,
28 28 cacheutil,
29 29 cmdutil,
30 30 destutil,
31 31 discovery,
32 32 error,
33 33 exchange,
34 34 extensions,
35 35 httppeer,
36 36 localrepo,
37 37 lock,
38 38 logcmdutil,
39 39 logexchange,
40 40 merge as mergemod,
41 41 mergestate as mergestatemod,
42 42 narrowspec,
43 43 phases,
44 44 pycompat,
45 45 requirements,
46 46 scmutil,
47 47 sshpeer,
48 48 statichttprepo,
49 49 ui as uimod,
50 50 unionrepo,
51 51 url,
52 52 util,
53 53 verify as verifymod,
54 54 vfs as vfsmod,
55 55 )
56 56 from .utils import hashutil
57 57
58 58 release = lock.release
59 59
60 60 # shared features
61 61 sharedbookmarks = b'bookmarks'
62 62
63 63
64 64 def _local(path):
65 65 path = util.expandpath(util.urllocalpath(path))
66 66
67 67 try:
68 68 # we use os.stat() directly here instead of os.path.isfile()
69 69 # because the latter started returning `False` on invalid path
70 70 # exceptions starting in 3.8 and we care about handling
71 71 # invalid paths specially here.
72 72 st = os.stat(path)
73 73 isfile = stat.S_ISREG(st.st_mode)
74 74 # Python 2 raises TypeError, Python 3 ValueError.
75 75 except (TypeError, ValueError) as e:
76 76 raise error.Abort(
77 77 _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
78 78 )
79 79 except OSError:
80 80 isfile = False
81 81
82 82 return isfile and bundlerepo or localrepo
83 83
84 84
85 85 def addbranchrevs(lrepo, other, branches, revs):
86 86 peer = other.peer() # a courtesy to callers using a localrepo for other
87 87 hashbranch, branches = branches
88 88 if not hashbranch and not branches:
89 89 x = revs or None
90 90 if revs:
91 91 y = revs[0]
92 92 else:
93 93 y = None
94 94 return x, y
95 95 if revs:
96 96 revs = list(revs)
97 97 else:
98 98 revs = []
99 99
100 100 if not peer.capable(b'branchmap'):
101 101 if branches:
102 102 raise error.Abort(_(b"remote branch lookup not supported"))
103 103 revs.append(hashbranch)
104 104 return revs, revs[0]
105 105
106 106 with peer.commandexecutor() as e:
107 107 branchmap = e.callcommand(b'branchmap', {}).result()
108 108
109 109 def primary(branch):
110 110 if branch == b'.':
111 111 if not lrepo:
112 112 raise error.Abort(_(b"dirstate branch not accessible"))
113 113 branch = lrepo.dirstate.branch()
114 114 if branch in branchmap:
115 115 revs.extend(hex(r) for r in reversed(branchmap[branch]))
116 116 return True
117 117 else:
118 118 return False
119 119
120 120 for branch in branches:
121 121 if not primary(branch):
122 122 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
123 123 if hashbranch:
124 124 if not primary(hashbranch):
125 125 revs.append(hashbranch)
126 126 return revs, revs[0]
127 127
128 128
129 129 def parseurl(path, branches=None):
130 130 '''parse url#branch, returning (url, (branch, branches))'''
131 131
132 132 u = util.url(path)
133 133 branch = None
134 134 if u.fragment:
135 135 branch = u.fragment
136 136 u.fragment = None
137 137 return bytes(u), (branch, branches or [])
138 138
139 139
140 140 schemes = {
141 141 b'bundle': bundlerepo,
142 142 b'union': unionrepo,
143 143 b'file': _local,
144 144 b'http': httppeer,
145 145 b'https': httppeer,
146 146 b'ssh': sshpeer,
147 147 b'static-http': statichttprepo,
148 148 }
149 149
150 150
151 151 def _peerlookup(path):
152 152 u = util.url(path)
153 153 scheme = u.scheme or b'file'
154 154 thing = schemes.get(scheme) or schemes[b'file']
155 155 try:
156 156 return thing(path)
157 157 except TypeError:
158 158 # we can't test callable(thing) because 'thing' can be an unloaded
159 159 # module that implements __call__
160 160 if not util.safehasattr(thing, b'instance'):
161 161 raise
162 162 return thing
163 163
164 164
165 165 def islocal(repo):
166 166 '''return true if repo (or path pointing to repo) is local'''
167 167 if isinstance(repo, bytes):
168 168 try:
169 169 return _peerlookup(repo).islocal(repo)
170 170 except AttributeError:
171 171 return False
172 172 return repo.local()
173 173
174 174
175 175 def openpath(ui, path, sendaccept=True):
176 176 '''open path with open if local, url.open if remote'''
177 177 pathurl = util.url(path, parsequery=False, parsefragment=False)
178 178 if pathurl.islocal():
179 179 return util.posixfile(pathurl.localpath(), b'rb')
180 180 else:
181 181 return url.open(ui, path, sendaccept=sendaccept)
182 182
183 183
184 184 # a list of (ui, repo) functions called for wire peer initialization
185 185 wirepeersetupfuncs = []
186 186
187 187
188 188 def _peerorrepo(
189 189 ui, path, create=False, presetupfuncs=None, intents=None, createopts=None
190 190 ):
191 191 """return a repository object for the specified path"""
192 192 obj = _peerlookup(path).instance(
193 193 ui, path, create, intents=intents, createopts=createopts
194 194 )
195 195 ui = getattr(obj, "ui", ui)
196 196 for f in presetupfuncs or []:
197 197 f(ui, obj)
198 198 ui.log(b'extension', b'- executing reposetup hooks\n')
199 199 with util.timedcm('all reposetup') as allreposetupstats:
200 200 for name, module in extensions.extensions(ui):
201 201 ui.log(b'extension', b' - running reposetup for %s\n', name)
202 202 hook = getattr(module, 'reposetup', None)
203 203 if hook:
204 204 with util.timedcm('reposetup %r', name) as stats:
205 205 hook(ui, obj)
206 206 ui.log(
207 207 b'extension', b' > reposetup for %s took %s\n', name, stats
208 208 )
209 209 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
210 210 if not obj.local():
211 211 for f in wirepeersetupfuncs:
212 212 f(ui, obj)
213 213 return obj
214 214
215 215
216 216 def repository(
217 217 ui,
218 218 path=b'',
219 219 create=False,
220 220 presetupfuncs=None,
221 221 intents=None,
222 222 createopts=None,
223 223 ):
224 224 """return a repository object for the specified path"""
225 225 peer = _peerorrepo(
226 226 ui,
227 227 path,
228 228 create,
229 229 presetupfuncs=presetupfuncs,
230 230 intents=intents,
231 231 createopts=createopts,
232 232 )
233 233 repo = peer.local()
234 234 if not repo:
235 235 raise error.Abort(
236 236 _(b"repository '%s' is not local") % (path or peer.url())
237 237 )
238 238 return repo.filtered(b'visible')
239 239
240 240
241 241 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
242 242 '''return a repository peer for the specified path'''
243 243 rui = remoteui(uiorrepo, opts)
244 244 return _peerorrepo(
245 245 rui, path, create, intents=intents, createopts=createopts
246 246 ).peer()
247 247
248 248
249 249 def defaultdest(source):
250 250 """return default destination of clone if none is given
251 251
252 252 >>> defaultdest(b'foo')
253 253 'foo'
254 254 >>> defaultdest(b'/foo/bar')
255 255 'bar'
256 256 >>> defaultdest(b'/')
257 257 ''
258 258 >>> defaultdest(b'')
259 259 ''
260 260 >>> defaultdest(b'http://example.org/')
261 261 ''
262 262 >>> defaultdest(b'http://example.org/foo/')
263 263 'foo'
264 264 """
265 265 path = util.url(source).path
266 266 if not path:
267 267 return b''
268 268 return os.path.basename(os.path.normpath(path))
269 269
270 270
271 271 def sharedreposource(repo):
272 272 """Returns repository object for source repository of a shared repo.
273 273
274 274 If repo is not a shared repository, returns None.
275 275 """
276 276 if repo.sharedpath == repo.path:
277 277 return None
278 278
279 279 if util.safehasattr(repo, b'srcrepo') and repo.srcrepo:
280 280 return repo.srcrepo
281 281
282 282 # the sharedpath always ends in the .hg; we want the path to the repo
283 283 source = repo.vfs.split(repo.sharedpath)[0]
284 284 srcurl, branches = parseurl(source)
285 285 srcrepo = repository(repo.ui, srcurl)
286 286 repo.srcrepo = srcrepo
287 287 return srcrepo
288 288
289 289
290 290 def share(
291 291 ui,
292 292 source,
293 293 dest=None,
294 294 update=True,
295 295 bookmarks=True,
296 296 defaultpath=None,
297 297 relative=False,
298 298 ):
299 299 '''create a shared repository'''
300 300
301 301 if not islocal(source):
302 302 raise error.Abort(_(b'can only share local repositories'))
303 303
304 304 if not dest:
305 305 dest = defaultdest(source)
306 306 else:
307 307 dest = ui.expandpath(dest)
308 308
309 309 if isinstance(source, bytes):
310 310 origsource = ui.expandpath(source)
311 311 source, branches = parseurl(origsource)
312 312 srcrepo = repository(ui, source)
313 313 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
314 314 else:
315 315 srcrepo = source.local()
316 316 checkout = None
317 317
318 318 shareditems = set()
319 319 if bookmarks:
320 320 shareditems.add(sharedbookmarks)
321 321
322 322 r = repository(
323 323 ui,
324 324 dest,
325 325 create=True,
326 326 createopts={
327 327 b'sharedrepo': srcrepo,
328 328 b'sharedrelative': relative,
329 329 b'shareditems': shareditems,
330 330 },
331 331 )
332 332
333 333 postshare(srcrepo, r, defaultpath=defaultpath)
334 334 r = repository(ui, dest)
335 335 _postshareupdate(r, update, checkout=checkout)
336 336 return r
337 337
338 338
339 339 def _prependsourcehgrc(repo):
340 340 """copies the source repo config and prepend it in current repo .hg/hgrc
341 341 on unshare. This is only done if the share was perfomed using share safe
342 342 method where we share config of source in shares"""
343 343 srcvfs = vfsmod.vfs(repo.sharedpath)
344 344 dstvfs = vfsmod.vfs(repo.path)
345 345
346 346 if not srcvfs.exists(b'hgrc'):
347 347 return
348 348
349 349 currentconfig = b''
350 350 if dstvfs.exists(b'hgrc'):
351 351 currentconfig = dstvfs.read(b'hgrc')
352 352
353 353 with dstvfs(b'hgrc', b'wb') as fp:
354 354 sourceconfig = srcvfs.read(b'hgrc')
355 355 fp.write(b"# Config copied from shared source\n")
356 356 fp.write(sourceconfig)
357 357 fp.write(b'\n')
358 358 fp.write(currentconfig)
359 359
360 360
361 361 def unshare(ui, repo):
362 362 """convert a shared repository to a normal one
363 363
364 364 Copy the store data to the repo and remove the sharedpath data.
365 365
366 366 Returns a new repository object representing the unshared repository.
367 367
368 368 The passed repository object is not usable after this function is
369 369 called.
370 370 """
371 371
372 372 with repo.lock():
373 373 # we use locks here because if we race with commit, we
374 374 # can end up with extra data in the cloned revlogs that's
375 375 # not pointed to by changesets, thus causing verify to
376 376 # fail
377 377 destlock = copystore(ui, repo, repo.path)
378 378 with destlock or util.nullcontextmanager():
379 379 if requirements.SHARESAFE_REQUIREMENT in repo.requirements:
380 380 # we were sharing .hg/hgrc of the share source with the current
381 381 # repo. We need to copy that while unsharing otherwise it can
382 382 # disable hooks and other checks
383 383 _prependsourcehgrc(repo)
384 384
385 385 sharefile = repo.vfs.join(b'sharedpath')
386 386 util.rename(sharefile, sharefile + b'.old')
387 387
388 388 repo.requirements.discard(requirements.SHARED_REQUIREMENT)
389 389 repo.requirements.discard(requirements.RELATIVE_SHARED_REQUIREMENT)
390 390 scmutil.writereporequirements(repo)
391 391
392 392 # Removing share changes some fundamental properties of the repo instance.
393 393 # So we instantiate a new repo object and operate on it rather than
394 394 # try to keep the existing repo usable.
395 395 newrepo = repository(repo.baseui, repo.root, create=False)
396 396
397 397 # TODO: figure out how to access subrepos that exist, but were previously
398 398 # removed from .hgsub
399 399 c = newrepo[b'.']
400 400 subs = c.substate
401 401 for s in sorted(subs):
402 402 c.sub(s).unshare()
403 403
404 404 localrepo.poisonrepository(repo)
405 405
406 406 return newrepo
407 407
408 408
409 409 def postshare(sourcerepo, destrepo, defaultpath=None):
410 410 """Called after a new shared repo is created.
411 411
412 412 The new repo only has a requirements file and pointer to the source.
413 413 This function configures additional shared data.
414 414
415 415 Extensions can wrap this function and write additional entries to
416 416 destrepo/.hg/shared to indicate additional pieces of data to be shared.
417 417 """
418 418 default = defaultpath or sourcerepo.ui.config(b'paths', b'default')
419 419 if default:
420 420 template = b'[paths]\ndefault = %s\n'
421 421 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % default))
422 422 if requirements.NARROW_REQUIREMENT in sourcerepo.requirements:
423 423 with destrepo.wlock():
424 424 narrowspec.copytoworkingcopy(destrepo)
425 425
426 426
427 427 def _postshareupdate(repo, update, checkout=None):
428 428 """Maybe perform a working directory update after a shared repo is created.
429 429
430 430 ``update`` can be a boolean or a revision to update to.
431 431 """
432 432 if not update:
433 433 return
434 434
435 435 repo.ui.status(_(b"updating working directory\n"))
436 436 if update is not True:
437 437 checkout = update
438 438 for test in (checkout, b'default', b'tip'):
439 439 if test is None:
440 440 continue
441 441 try:
442 442 uprev = repo.lookup(test)
443 443 break
444 444 except error.RepoLookupError:
445 445 continue
446 446 _update(repo, uprev)
447 447
448 448
449 449 def copystore(ui, srcrepo, destpath):
450 450 """copy files from store of srcrepo in destpath
451 451
452 452 returns destlock
453 453 """
454 454 destlock = None
455 455 try:
456 456 hardlink = None
457 457 topic = _(b'linking') if hardlink else _(b'copying')
458 458 with ui.makeprogress(topic, unit=_(b'files')) as progress:
459 459 num = 0
460 460 srcpublishing = srcrepo.publishing()
461 461 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
462 462 dstvfs = vfsmod.vfs(destpath)
463 463 for f in srcrepo.store.copylist():
464 464 if srcpublishing and f.endswith(b'phaseroots'):
465 465 continue
466 466 dstbase = os.path.dirname(f)
467 467 if dstbase and not dstvfs.exists(dstbase):
468 468 dstvfs.mkdir(dstbase)
469 469 if srcvfs.exists(f):
470 470 if f.endswith(b'data'):
471 471 # 'dstbase' may be empty (e.g. revlog format 0)
472 472 lockfile = os.path.join(dstbase, b"lock")
473 473 # lock to avoid premature writing to the target
474 474 destlock = lock.lock(dstvfs, lockfile)
475 475 hardlink, n = util.copyfiles(
476 476 srcvfs.join(f), dstvfs.join(f), hardlink, progress
477 477 )
478 478 num += n
479 479 if hardlink:
480 480 ui.debug(b"linked %d files\n" % num)
481 481 else:
482 482 ui.debug(b"copied %d files\n" % num)
483 483 return destlock
484 484 except: # re-raises
485 485 release(destlock)
486 486 raise
487 487
488 488
489 489 def clonewithshare(
490 490 ui,
491 491 peeropts,
492 492 sharepath,
493 493 source,
494 494 srcpeer,
495 495 dest,
496 496 pull=False,
497 497 rev=None,
498 498 update=True,
499 499 stream=False,
500 500 ):
501 501 """Perform a clone using a shared repo.
502 502
503 503 The store for the repository will be located at <sharepath>/.hg. The
504 504 specified revisions will be cloned or pulled from "source". A shared repo
505 505 will be created at "dest" and a working copy will be created if "update" is
506 506 True.
507 507 """
508 508 revs = None
509 509 if rev:
510 510 if not srcpeer.capable(b'lookup'):
511 511 raise error.Abort(
512 512 _(
513 513 b"src repository does not support "
514 514 b"revision lookup and so doesn't "
515 515 b"support clone by revision"
516 516 )
517 517 )
518 518
519 519 # TODO this is batchable.
520 520 remoterevs = []
521 521 for r in rev:
522 522 with srcpeer.commandexecutor() as e:
523 523 remoterevs.append(
524 524 e.callcommand(
525 525 b'lookup',
526 526 {
527 527 b'key': r,
528 528 },
529 529 ).result()
530 530 )
531 531 revs = remoterevs
532 532
533 533 # Obtain a lock before checking for or cloning the pooled repo otherwise
534 534 # 2 clients may race creating or populating it.
535 535 pooldir = os.path.dirname(sharepath)
536 536 # lock class requires the directory to exist.
537 537 try:
538 538 util.makedir(pooldir, False)
539 539 except OSError as e:
540 540 if e.errno != errno.EEXIST:
541 541 raise
542 542
543 543 poolvfs = vfsmod.vfs(pooldir)
544 544 basename = os.path.basename(sharepath)
545 545
546 546 with lock.lock(poolvfs, b'%s.lock' % basename):
547 547 if os.path.exists(sharepath):
548 548 ui.status(
549 549 _(b'(sharing from existing pooled repository %s)\n') % basename
550 550 )
551 551 else:
552 552 ui.status(
553 553 _(b'(sharing from new pooled repository %s)\n') % basename
554 554 )
555 555 # Always use pull mode because hardlinks in share mode don't work
556 556 # well. Never update because working copies aren't necessary in
557 557 # share mode.
558 558 clone(
559 559 ui,
560 560 peeropts,
561 561 source,
562 562 dest=sharepath,
563 563 pull=True,
564 564 revs=rev,
565 565 update=False,
566 566 stream=stream,
567 567 )
568 568
569 569 # Resolve the value to put in [paths] section for the source.
570 570 if islocal(source):
571 571 defaultpath = os.path.abspath(util.urllocalpath(source))
572 572 else:
573 573 defaultpath = source
574 574
575 575 sharerepo = repository(ui, path=sharepath)
576 576 destrepo = share(
577 577 ui,
578 578 sharerepo,
579 579 dest=dest,
580 580 update=False,
581 581 bookmarks=False,
582 582 defaultpath=defaultpath,
583 583 )
584 584
585 585 # We need to perform a pull against the dest repo to fetch bookmarks
586 586 # and other non-store data that isn't shared by default. In the case of
587 587 # non-existing shared repo, this means we pull from the remote twice. This
588 588 # is a bit weird. But at the time it was implemented, there wasn't an easy
589 589 # way to pull just non-changegroup data.
590 590 exchange.pull(destrepo, srcpeer, heads=revs)
591 591
592 592 _postshareupdate(destrepo, update)
593 593
594 594 return srcpeer, peer(ui, peeropts, dest)
595 595
596 596
597 597 # Recomputing caches is often slow on big repos, so copy them.
598 598 def _copycache(srcrepo, dstcachedir, fname):
599 599 """copy a cache from srcrepo to destcachedir (if it exists)"""
600 600 srcfname = srcrepo.cachevfs.join(fname)
601 601 dstfname = os.path.join(dstcachedir, fname)
602 602 if os.path.exists(srcfname):
603 603 if not os.path.exists(dstcachedir):
604 604 os.mkdir(dstcachedir)
605 605 util.copyfile(srcfname, dstfname)
606 606
607 607
608 608 def clone(
609 609 ui,
610 610 peeropts,
611 611 source,
612 612 dest=None,
613 613 pull=False,
614 614 revs=None,
615 615 update=True,
616 616 stream=False,
617 617 branch=None,
618 618 shareopts=None,
619 619 storeincludepats=None,
620 620 storeexcludepats=None,
621 621 depth=None,
622 622 ):
623 623 """Make a copy of an existing repository.
624 624
625 625 Create a copy of an existing repository in a new directory. The
626 626 source and destination are URLs, as passed to the repository
627 627 function. Returns a pair of repository peers, the source and
628 628 newly created destination.
629 629
630 630 The location of the source is added to the new repository's
631 631 .hg/hgrc file, as the default to be used for future pulls and
632 632 pushes.
633 633
634 634 If an exception is raised, the partly cloned/updated destination
635 635 repository will be deleted.
636 636
637 637 Arguments:
638 638
639 639 source: repository object or URL
640 640
641 641 dest: URL of destination repository to create (defaults to base
642 642 name of source repository)
643 643
644 644 pull: always pull from source repository, even in local case or if the
645 645 server prefers streaming
646 646
647 647 stream: stream raw data uncompressed from repository (fast over
648 648 LAN, slow over WAN)
649 649
650 650 revs: revision to clone up to (implies pull=True)
651 651
652 652 update: update working directory after clone completes, if
653 653 destination is local repository (True means update to default rev,
654 654 anything else is treated as a revision)
655 655
656 656 branch: branches to clone
657 657
658 658 shareopts: dict of options to control auto sharing behavior. The "pool" key
659 659 activates auto sharing mode and defines the directory for stores. The
660 660 "mode" key determines how to construct the directory name of the shared
661 661 repository. "identity" means the name is derived from the node of the first
662 662 changeset in the repository. "remote" means the name is derived from the
663 663 remote's path/URL. Defaults to "identity."
664 664
665 665 storeincludepats and storeexcludepats: sets of file patterns to include and
666 666 exclude in the repository copy, respectively. If not defined, all files
667 667 will be included (a "full" clone). Otherwise a "narrow" clone containing
668 668 only the requested files will be performed. If ``storeincludepats`` is not
669 669 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
670 670 ``path:.``. If both are empty sets, no files will be cloned.
671 671 """
672 672
673 673 if isinstance(source, bytes):
674 674 origsource = ui.expandpath(source)
675 675 source, branches = parseurl(origsource, branch)
676 676 srcpeer = peer(ui, peeropts, source)
677 677 else:
678 678 srcpeer = source.peer() # in case we were called with a localrepo
679 679 branches = (None, branch or [])
680 680 origsource = source = srcpeer.url()
681 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
681 srclock = destlock = cleandir = None
682 destpeer = None
683 try:
684 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
682 685
683 if dest is None:
684 dest = defaultdest(source)
685 if dest:
686 ui.status(_(b"destination directory: %s\n") % dest)
687 else:
688 dest = ui.expandpath(dest)
686 if dest is None:
687 dest = defaultdest(source)
688 if dest:
689 ui.status(_(b"destination directory: %s\n") % dest)
690 else:
691 dest = ui.expandpath(dest)
689 692
690 dest = util.urllocalpath(dest)
691 source = util.urllocalpath(source)
693 dest = util.urllocalpath(dest)
694 source = util.urllocalpath(source)
692 695
693 if not dest:
694 raise error.InputError(_(b"empty destination path is not valid"))
696 if not dest:
697 raise error.InputError(_(b"empty destination path is not valid"))
695 698
696 destvfs = vfsmod.vfs(dest, expandpath=True)
697 if destvfs.lexists():
698 if not destvfs.isdir():
699 raise error.InputError(_(b"destination '%s' already exists") % dest)
700 elif destvfs.listdir():
701 raise error.InputError(_(b"destination '%s' is not empty") % dest)
699 destvfs = vfsmod.vfs(dest, expandpath=True)
700 if destvfs.lexists():
701 if not destvfs.isdir():
702 raise error.InputError(
703 _(b"destination '%s' already exists") % dest
704 )
705 elif destvfs.listdir():
706 raise error.InputError(
707 _(b"destination '%s' is not empty") % dest
708 )
702 709
703 createopts = {}
704 narrow = False
705
706 if storeincludepats is not None:
707 narrowspec.validatepatterns(storeincludepats)
708 narrow = True
710 createopts = {}
711 narrow = False
709 712
710 if storeexcludepats is not None:
711 narrowspec.validatepatterns(storeexcludepats)
712 narrow = True
713 if storeincludepats is not None:
714 narrowspec.validatepatterns(storeincludepats)
715 narrow = True
716
717 if storeexcludepats is not None:
718 narrowspec.validatepatterns(storeexcludepats)
719 narrow = True
713 720
714 if narrow:
715 # Include everything by default if only exclusion patterns defined.
716 if storeexcludepats and not storeincludepats:
717 storeincludepats = {b'path:.'}
721 if narrow:
722 # Include everything by default if only exclusion patterns defined.
723 if storeexcludepats and not storeincludepats:
724 storeincludepats = {b'path:.'}
718 725
719 createopts[b'narrowfiles'] = True
726 createopts[b'narrowfiles'] = True
720 727
721 if depth:
722 createopts[b'shallowfilestore'] = True
728 if depth:
729 createopts[b'shallowfilestore'] = True
723 730
724 if srcpeer.capable(b'lfs-serve'):
725 # Repository creation honors the config if it disabled the extension, so
726 # we can't just announce that lfs will be enabled. This check avoids
727 # saying that lfs will be enabled, and then saying it's an unknown
728 # feature. The lfs creation option is set in either case so that a
729 # requirement is added. If the extension is explicitly disabled but the
730 # requirement is set, the clone aborts early, before transferring any
731 # data.
732 createopts[b'lfs'] = True
731 if srcpeer.capable(b'lfs-serve'):
732 # Repository creation honors the config if it disabled the extension, so
733 # we can't just announce that lfs will be enabled. This check avoids
734 # saying that lfs will be enabled, and then saying it's an unknown
735 # feature. The lfs creation option is set in either case so that a
736 # requirement is added. If the extension is explicitly disabled but the
737 # requirement is set, the clone aborts early, before transferring any
738 # data.
739 createopts[b'lfs'] = True
733 740
734 if extensions.disabled_help(b'lfs'):
735 ui.status(
736 _(
737 b'(remote is using large file support (lfs), but it is '
738 b'explicitly disabled in the local configuration)\n'
741 if extensions.disabled_help(b'lfs'):
742 ui.status(
743 _(
744 b'(remote is using large file support (lfs), but it is '
745 b'explicitly disabled in the local configuration)\n'
746 )
739 747 )
740 )
741 else:
742 ui.status(
743 _(
744 b'(remote is using large file support (lfs); lfs will '
745 b'be enabled for this repository)\n'
748 else:
749 ui.status(
750 _(
751 b'(remote is using large file support (lfs); lfs will '
752 b'be enabled for this repository)\n'
753 )
746 754 )
747 )
748 755
749 shareopts = shareopts or {}
750 sharepool = shareopts.get(b'pool')
751 sharenamemode = shareopts.get(b'mode')
752 if sharepool and islocal(dest):
753 sharepath = None
754 if sharenamemode == b'identity':
755 # Resolve the name from the initial changeset in the remote
756 # repository. This returns nullid when the remote is empty. It
757 # raises RepoLookupError if revision 0 is filtered or otherwise
758 # not available. If we fail to resolve, sharing is not enabled.
759 try:
760 with srcpeer.commandexecutor() as e:
761 rootnode = e.callcommand(
762 b'lookup',
763 {
764 b'key': b'0',
765 },
766 ).result()
756 shareopts = shareopts or {}
757 sharepool = shareopts.get(b'pool')
758 sharenamemode = shareopts.get(b'mode')
759 if sharepool and islocal(dest):
760 sharepath = None
761 if sharenamemode == b'identity':
762 # Resolve the name from the initial changeset in the remote
763 # repository. This returns nullid when the remote is empty. It
764 # raises RepoLookupError if revision 0 is filtered or otherwise
765 # not available. If we fail to resolve, sharing is not enabled.
766 try:
767 with srcpeer.commandexecutor() as e:
768 rootnode = e.callcommand(
769 b'lookup',
770 {
771 b'key': b'0',
772 },
773 ).result()
767 774
768 if rootnode != nullid:
769 sharepath = os.path.join(sharepool, hex(rootnode))
770 else:
775 if rootnode != nullid:
776 sharepath = os.path.join(sharepool, hex(rootnode))
777 else:
778 ui.status(
779 _(
780 b'(not using pooled storage: '
781 b'remote appears to be empty)\n'
782 )
783 )
784 except error.RepoLookupError:
771 785 ui.status(
772 786 _(
773 787 b'(not using pooled storage: '
774 b'remote appears to be empty)\n'
788 b'unable to resolve identity of remote)\n'
775 789 )
776 790 )
777 except error.RepoLookupError:
778 ui.status(
779 _(
780 b'(not using pooled storage: '
781 b'unable to resolve identity of remote)\n'
782 )
791 elif sharenamemode == b'remote':
792 sharepath = os.path.join(
793 sharepool, hex(hashutil.sha1(source).digest())
794 )
795 else:
796 raise error.Abort(
797 _(b'unknown share naming mode: %s') % sharenamemode
783 798 )
784 elif sharenamemode == b'remote':
785 sharepath = os.path.join(
786 sharepool, hex(hashutil.sha1(source).digest())
787 )
788 else:
789 raise error.Abort(
790 _(b'unknown share naming mode: %s') % sharenamemode
791 )
799
800 # TODO this is a somewhat arbitrary restriction.
801 if narrow:
802 ui.status(
803 _(b'(pooled storage not supported for narrow clones)\n')
804 )
805 sharepath = None
792 806
793 # TODO this is a somewhat arbitrary restriction.
794 if narrow:
795 ui.status(_(b'(pooled storage not supported for narrow clones)\n'))
796 sharepath = None
807 if sharepath:
808 return clonewithshare(
809 ui,
810 peeropts,
811 sharepath,
812 source,
813 srcpeer,
814 dest,
815 pull=pull,
816 rev=revs,
817 update=update,
818 stream=stream,
819 )
797 820
798 if sharepath:
799 return clonewithshare(
800 ui,
801 peeropts,
802 sharepath,
803 source,
804 srcpeer,
805 dest,
806 pull=pull,
807 rev=revs,
808 update=update,
809 stream=stream,
810 )
821 srcrepo = srcpeer.local()
811 822
812 srclock = destlock = cleandir = None
813 srcrepo = srcpeer.local()
814 try:
815 823 abspath = origsource
816 824 if islocal(origsource):
817 825 abspath = os.path.abspath(util.urllocalpath(origsource))
818 826
819 827 if islocal(dest):
820 828 cleandir = dest
821 829
822 830 copy = False
823 831 if (
824 832 srcrepo
825 833 and srcrepo.cancopy()
826 834 and islocal(dest)
827 835 and not phases.hassecret(srcrepo)
828 836 ):
829 837 copy = not pull and not revs
830 838
831 839 # TODO this is a somewhat arbitrary restriction.
832 840 if narrow:
833 841 copy = False
834 842
835 843 if copy:
836 844 try:
837 845 # we use a lock here because if we race with commit, we
838 846 # can end up with extra data in the cloned revlogs that's
839 847 # not pointed to by changesets, thus causing verify to
840 848 # fail
841 849 srclock = srcrepo.lock(wait=False)
842 850 except error.LockError:
843 851 copy = False
844 852
845 853 if copy:
846 854 srcrepo.hook(b'preoutgoing', throw=True, source=b'clone')
847 855 hgdir = os.path.realpath(os.path.join(dest, b".hg"))
848 856 if not os.path.exists(dest):
849 857 util.makedirs(dest)
850 858 else:
851 859 # only clean up directories we create ourselves
852 860 cleandir = hgdir
853 861 try:
854 862 destpath = hgdir
855 863 util.makedir(destpath, notindexed=True)
856 864 except OSError as inst:
857 865 if inst.errno == errno.EEXIST:
858 866 cleandir = None
859 867 raise error.Abort(
860 868 _(b"destination '%s' already exists") % dest
861 869 )
862 870 raise
863 871
864 872 destlock = copystore(ui, srcrepo, destpath)
865 873 # copy bookmarks over
866 874 srcbookmarks = srcrepo.vfs.join(b'bookmarks')
867 875 dstbookmarks = os.path.join(destpath, b'bookmarks')
868 876 if os.path.exists(srcbookmarks):
869 877 util.copyfile(srcbookmarks, dstbookmarks)
870 878
871 879 dstcachedir = os.path.join(destpath, b'cache')
872 880 for cache in cacheutil.cachetocopy(srcrepo):
873 881 _copycache(srcrepo, dstcachedir, cache)
874 882
875 883 # we need to re-init the repo after manually copying the data
876 884 # into it
877 885 destpeer = peer(srcrepo, peeropts, dest)
878 886 srcrepo.hook(b'outgoing', source=b'clone', node=nullhex)
879 887 else:
880 888 try:
881 889 # only pass ui when no srcrepo
882 890 destpeer = peer(
883 891 srcrepo or ui,
884 892 peeropts,
885 893 dest,
886 894 create=True,
887 895 createopts=createopts,
888 896 )
889 897 except OSError as inst:
890 898 if inst.errno == errno.EEXIST:
891 899 cleandir = None
892 900 raise error.Abort(
893 901 _(b"destination '%s' already exists") % dest
894 902 )
895 903 raise
896 904
897 905 if revs:
898 906 if not srcpeer.capable(b'lookup'):
899 907 raise error.Abort(
900 908 _(
901 909 b"src repository does not support "
902 910 b"revision lookup and so doesn't "
903 911 b"support clone by revision"
904 912 )
905 913 )
906 914
907 915 # TODO this is batchable.
908 916 remoterevs = []
909 917 for rev in revs:
910 918 with srcpeer.commandexecutor() as e:
911 919 remoterevs.append(
912 920 e.callcommand(
913 921 b'lookup',
914 922 {
915 923 b'key': rev,
916 924 },
917 925 ).result()
918 926 )
919 927 revs = remoterevs
920 928
921 929 checkout = revs[0]
922 930 else:
923 931 revs = None
924 932 local = destpeer.local()
925 933 if local:
926 934 if narrow:
927 935 with local.wlock(), local.lock():
928 936 local.setnarrowpats(storeincludepats, storeexcludepats)
929 937 narrowspec.copytoworkingcopy(local)
930 938
931 939 u = util.url(abspath)
932 940 defaulturl = bytes(u)
933 941 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
934 942 if not stream:
935 943 if pull:
936 944 stream = False
937 945 else:
938 946 stream = None
939 947 # internal config: ui.quietbookmarkmove
940 948 overrides = {(b'ui', b'quietbookmarkmove'): True}
941 949 with local.ui.configoverride(overrides, b'clone'):
942 950 exchange.pull(
943 951 local,
944 952 srcpeer,
945 953 revs,
946 954 streamclonerequested=stream,
947 955 includepats=storeincludepats,
948 956 excludepats=storeexcludepats,
949 957 depth=depth,
950 958 )
951 959 elif srcrepo:
952 960 # TODO lift restriction once exchange.push() accepts narrow
953 961 # push.
954 962 if narrow:
955 963 raise error.Abort(
956 964 _(
957 965 b'narrow clone not available for '
958 966 b'remote destinations'
959 967 )
960 968 )
961 969
962 970 exchange.push(
963 971 srcrepo,
964 972 destpeer,
965 973 revs=revs,
966 974 bookmarks=srcrepo._bookmarks.keys(),
967 975 )
968 976 else:
969 977 raise error.Abort(
970 978 _(b"clone from remote to remote not supported")
971 979 )
972 980
973 981 cleandir = None
974 982
975 983 destrepo = destpeer.local()
976 984 if destrepo:
977 985 template = uimod.samplehgrcs[b'cloned']
978 986 u = util.url(abspath)
979 987 u.passwd = None
980 988 defaulturl = bytes(u)
981 989 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
982 990 destrepo.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
983 991
984 992 if ui.configbool(b'experimental', b'remotenames'):
985 993 logexchange.pullremotenames(destrepo, srcpeer)
986 994
987 995 if update:
988 996 if update is not True:
989 997 with srcpeer.commandexecutor() as e:
990 998 checkout = e.callcommand(
991 999 b'lookup',
992 1000 {
993 1001 b'key': update,
994 1002 },
995 1003 ).result()
996 1004
997 1005 uprev = None
998 1006 status = None
999 1007 if checkout is not None:
1000 1008 # Some extensions (at least hg-git and hg-subversion) have
1001 1009 # a peer.lookup() implementation that returns a name instead
1002 1010 # of a nodeid. We work around it here until we've figured
1003 1011 # out a better solution.
1004 1012 if len(checkout) == 20 and checkout in destrepo:
1005 1013 uprev = checkout
1006 1014 elif scmutil.isrevsymbol(destrepo, checkout):
1007 1015 uprev = scmutil.revsymbol(destrepo, checkout).node()
1008 1016 else:
1009 1017 if update is not True:
1010 1018 try:
1011 1019 uprev = destrepo.lookup(update)
1012 1020 except error.RepoLookupError:
1013 1021 pass
1014 1022 if uprev is None:
1015 1023 try:
1016 1024 if destrepo._activebookmark:
1017 1025 uprev = destrepo.lookup(destrepo._activebookmark)
1018 1026 update = destrepo._activebookmark
1019 1027 else:
1020 1028 uprev = destrepo._bookmarks[b'@']
1021 1029 update = b'@'
1022 1030 bn = destrepo[uprev].branch()
1023 1031 if bn == b'default':
1024 1032 status = _(b"updating to bookmark %s\n" % update)
1025 1033 else:
1026 1034 status = (
1027 1035 _(b"updating to bookmark %s on branch %s\n")
1028 1036 ) % (update, bn)
1029 1037 except KeyError:
1030 1038 try:
1031 1039 uprev = destrepo.branchtip(b'default')
1032 1040 except error.RepoLookupError:
1033 1041 uprev = destrepo.lookup(b'tip')
1034 1042 if not status:
1035 1043 bn = destrepo[uprev].branch()
1036 1044 status = _(b"updating to branch %s\n") % bn
1037 1045 destrepo.ui.status(status)
1038 1046 _update(destrepo, uprev)
1039 1047 if update in destrepo._bookmarks:
1040 1048 bookmarks.activate(destrepo, update)
1041 1049 if destlock is not None:
1042 1050 release(destlock)
1043 1051 # here is a tiny windows were someone could end up writing the
1044 1052 # repository before the cache are sure to be warm. This is "fine"
1045 1053 # as the only "bad" outcome would be some slowness. That potential
1046 1054 # slowness already affect reader.
1047 1055 with destrepo.lock():
1048 1056 destrepo.updatecaches(full=True)
1049 1057 finally:
1050 1058 release(srclock, destlock)
1051 1059 if cleandir is not None:
1052 1060 shutil.rmtree(cleandir, True)
1053 1061 if srcpeer is not None:
1054 1062 srcpeer.close()
1063 if destpeer and destpeer.local() is None:
1064 destpeer.close()
1055 1065 return srcpeer, destpeer
1056 1066
1057 1067
1058 1068 def _showstats(repo, stats, quietempty=False):
1059 1069 if quietempty and stats.isempty():
1060 1070 return
1061 1071 repo.ui.status(
1062 1072 _(
1063 1073 b"%d files updated, %d files merged, "
1064 1074 b"%d files removed, %d files unresolved\n"
1065 1075 )
1066 1076 % (
1067 1077 stats.updatedcount,
1068 1078 stats.mergedcount,
1069 1079 stats.removedcount,
1070 1080 stats.unresolvedcount,
1071 1081 )
1072 1082 )
1073 1083
1074 1084
1075 1085 def updaterepo(repo, node, overwrite, updatecheck=None):
1076 1086 """Update the working directory to node.
1077 1087
1078 1088 When overwrite is set, changes are clobbered, merged else
1079 1089
1080 1090 returns stats (see pydoc mercurial.merge.applyupdates)"""
1081 1091 repo.ui.deprecwarn(
1082 1092 b'prefer merge.update() or merge.clean_update() over hg.updaterepo()',
1083 1093 b'5.7',
1084 1094 )
1085 1095 return mergemod._update(
1086 1096 repo,
1087 1097 node,
1088 1098 branchmerge=False,
1089 1099 force=overwrite,
1090 1100 labels=[b'working copy', b'destination'],
1091 1101 updatecheck=updatecheck,
1092 1102 )
1093 1103
1094 1104
1095 1105 def update(repo, node, quietempty=False, updatecheck=None):
1096 1106 """update the working directory to node"""
1097 1107 stats = mergemod.update(repo[node], updatecheck=updatecheck)
1098 1108 _showstats(repo, stats, quietempty)
1099 1109 if stats.unresolvedcount:
1100 1110 repo.ui.status(_(b"use 'hg resolve' to retry unresolved file merges\n"))
1101 1111 return stats.unresolvedcount > 0
1102 1112
1103 1113
1104 1114 # naming conflict in clone()
1105 1115 _update = update
1106 1116
1107 1117
1108 1118 def clean(repo, node, show_stats=True, quietempty=False):
1109 1119 """forcibly switch the working directory to node, clobbering changes"""
1110 1120 stats = mergemod.clean_update(repo[node])
1111 1121 assert stats.unresolvedcount == 0
1112 1122 if show_stats:
1113 1123 _showstats(repo, stats, quietempty)
1114 1124
1115 1125
1116 1126 # naming conflict in updatetotally()
1117 1127 _clean = clean
1118 1128
1119 1129 _VALID_UPDATECHECKS = {
1120 1130 mergemod.UPDATECHECK_ABORT,
1121 1131 mergemod.UPDATECHECK_NONE,
1122 1132 mergemod.UPDATECHECK_LINEAR,
1123 1133 mergemod.UPDATECHECK_NO_CONFLICT,
1124 1134 }
1125 1135
1126 1136
1127 1137 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
1128 1138 """Update the working directory with extra care for non-file components
1129 1139
1130 1140 This takes care of non-file components below:
1131 1141
1132 1142 :bookmark: might be advanced or (in)activated
1133 1143
1134 1144 This takes arguments below:
1135 1145
1136 1146 :checkout: to which revision the working directory is updated
1137 1147 :brev: a name, which might be a bookmark to be activated after updating
1138 1148 :clean: whether changes in the working directory can be discarded
1139 1149 :updatecheck: how to deal with a dirty working directory
1140 1150
1141 1151 Valid values for updatecheck are the UPDATECHECK_* constants
1142 1152 defined in the merge module. Passing `None` will result in using the
1143 1153 configured default.
1144 1154
1145 1155 * ABORT: abort if the working directory is dirty
1146 1156 * NONE: don't check (merge working directory changes into destination)
1147 1157 * LINEAR: check that update is linear before merging working directory
1148 1158 changes into destination
1149 1159 * NO_CONFLICT: check that the update does not result in file merges
1150 1160
1151 1161 This returns whether conflict is detected at updating or not.
1152 1162 """
1153 1163 if updatecheck is None:
1154 1164 updatecheck = ui.config(b'commands', b'update.check')
1155 1165 if updatecheck not in _VALID_UPDATECHECKS:
1156 1166 # If not configured, or invalid value configured
1157 1167 updatecheck = mergemod.UPDATECHECK_LINEAR
1158 1168 if updatecheck not in _VALID_UPDATECHECKS:
1159 1169 raise ValueError(
1160 1170 r'Invalid updatecheck value %r (can accept %r)'
1161 1171 % (updatecheck, _VALID_UPDATECHECKS)
1162 1172 )
1163 1173 with repo.wlock():
1164 1174 movemarkfrom = None
1165 1175 warndest = False
1166 1176 if checkout is None:
1167 1177 updata = destutil.destupdate(repo, clean=clean)
1168 1178 checkout, movemarkfrom, brev = updata
1169 1179 warndest = True
1170 1180
1171 1181 if clean:
1172 1182 ret = _clean(repo, checkout)
1173 1183 else:
1174 1184 if updatecheck == mergemod.UPDATECHECK_ABORT:
1175 1185 cmdutil.bailifchanged(repo, merge=False)
1176 1186 updatecheck = mergemod.UPDATECHECK_NONE
1177 1187 ret = _update(repo, checkout, updatecheck=updatecheck)
1178 1188
1179 1189 if not ret and movemarkfrom:
1180 1190 if movemarkfrom == repo[b'.'].node():
1181 1191 pass # no-op update
1182 1192 elif bookmarks.update(repo, [movemarkfrom], repo[b'.'].node()):
1183 1193 b = ui.label(repo._activebookmark, b'bookmarks.active')
1184 1194 ui.status(_(b"updating bookmark %s\n") % b)
1185 1195 else:
1186 1196 # this can happen with a non-linear update
1187 1197 b = ui.label(repo._activebookmark, b'bookmarks')
1188 1198 ui.status(_(b"(leaving bookmark %s)\n") % b)
1189 1199 bookmarks.deactivate(repo)
1190 1200 elif brev in repo._bookmarks:
1191 1201 if brev != repo._activebookmark:
1192 1202 b = ui.label(brev, b'bookmarks.active')
1193 1203 ui.status(_(b"(activating bookmark %s)\n") % b)
1194 1204 bookmarks.activate(repo, brev)
1195 1205 elif brev:
1196 1206 if repo._activebookmark:
1197 1207 b = ui.label(repo._activebookmark, b'bookmarks')
1198 1208 ui.status(_(b"(leaving bookmark %s)\n") % b)
1199 1209 bookmarks.deactivate(repo)
1200 1210
1201 1211 if warndest:
1202 1212 destutil.statusotherdests(ui, repo)
1203 1213
1204 1214 return ret
1205 1215
1206 1216
1207 1217 def merge(
1208 1218 ctx,
1209 1219 force=False,
1210 1220 remind=True,
1211 1221 labels=None,
1212 1222 ):
1213 1223 """Branch merge with node, resolving changes. Return true if any
1214 1224 unresolved conflicts."""
1215 1225 repo = ctx.repo()
1216 1226 stats = mergemod.merge(ctx, force=force, labels=labels)
1217 1227 _showstats(repo, stats)
1218 1228 if stats.unresolvedcount:
1219 1229 repo.ui.status(
1220 1230 _(
1221 1231 b"use 'hg resolve' to retry unresolved file merges "
1222 1232 b"or 'hg merge --abort' to abandon\n"
1223 1233 )
1224 1234 )
1225 1235 elif remind:
1226 1236 repo.ui.status(_(b"(branch merge, don't forget to commit)\n"))
1227 1237 return stats.unresolvedcount > 0
1228 1238
1229 1239
1230 1240 def abortmerge(ui, repo):
1231 1241 ms = mergestatemod.mergestate.read(repo)
1232 1242 if ms.active():
1233 1243 # there were conflicts
1234 1244 node = ms.localctx.hex()
1235 1245 else:
1236 1246 # there were no conficts, mergestate was not stored
1237 1247 node = repo[b'.'].hex()
1238 1248
1239 1249 repo.ui.status(_(b"aborting the merge, updating back to %s\n") % node[:12])
1240 1250 stats = mergemod.clean_update(repo[node])
1241 1251 assert stats.unresolvedcount == 0
1242 1252 _showstats(repo, stats)
1243 1253
1244 1254
1245 1255 def _incoming(
1246 1256 displaychlist, subreporecurse, ui, repo, source, opts, buffered=False
1247 1257 ):
1248 1258 """
1249 1259 Helper for incoming / gincoming.
1250 1260 displaychlist gets called with
1251 1261 (remoterepo, incomingchangesetlist, displayer) parameters,
1252 1262 and is supposed to contain only code that can't be unified.
1253 1263 """
1254 1264 source, branches = parseurl(ui.expandpath(source), opts.get(b'branch'))
1255 1265 other = peer(repo, opts, source)
1256 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
1257 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1266 cleanupfn = other.close
1267 try:
1268 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
1269 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1258 1270
1259 if revs:
1260 revs = [other.lookup(rev) for rev in revs]
1261 other, chlist, cleanupfn = bundlerepo.getremotechanges(
1262 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
1263 )
1264 try:
1271 if revs:
1272 revs = [other.lookup(rev) for rev in revs]
1273 other, chlist, cleanupfn = bundlerepo.getremotechanges(
1274 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
1275 )
1276
1265 1277 if not chlist:
1266 1278 ui.status(_(b"no changes found\n"))
1267 1279 return subreporecurse()
1268 1280 ui.pager(b'incoming')
1269 1281 displayer = logcmdutil.changesetdisplayer(
1270 1282 ui, other, opts, buffered=buffered
1271 1283 )
1272 1284 displaychlist(other, chlist, displayer)
1273 1285 displayer.close()
1274 1286 finally:
1275 1287 cleanupfn()
1276 1288 subreporecurse()
1277 1289 return 0 # exit code is zero since we found incoming changes
1278 1290
1279 1291
1280 1292 def incoming(ui, repo, source, opts):
1281 1293 def subreporecurse():
1282 1294 ret = 1
1283 1295 if opts.get(b'subrepos'):
1284 1296 ctx = repo[None]
1285 1297 for subpath in sorted(ctx.substate):
1286 1298 sub = ctx.sub(subpath)
1287 1299 ret = min(ret, sub.incoming(ui, source, opts))
1288 1300 return ret
1289 1301
1290 1302 def display(other, chlist, displayer):
1291 1303 limit = logcmdutil.getlimit(opts)
1292 1304 if opts.get(b'newest_first'):
1293 1305 chlist.reverse()
1294 1306 count = 0
1295 1307 for n in chlist:
1296 1308 if limit is not None and count >= limit:
1297 1309 break
1298 1310 parents = [p for p in other.changelog.parents(n) if p != nullid]
1299 1311 if opts.get(b'no_merges') and len(parents) == 2:
1300 1312 continue
1301 1313 count += 1
1302 1314 displayer.show(other[n])
1303 1315
1304 1316 return _incoming(display, subreporecurse, ui, repo, source, opts)
1305 1317
1306 1318
1307 1319 def _outgoing(ui, repo, dest, opts):
1308 1320 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
1309 1321 if not path:
1310 1322 raise error.Abort(
1311 1323 _(b'default repository not configured!'),
1312 1324 hint=_(b"see 'hg help config.paths'"),
1313 1325 )
1314 1326 dest = path.pushloc or path.loc
1315 1327 branches = path.branch, opts.get(b'branch') or []
1316 1328
1317 1329 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1318 1330 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1319 1331 if revs:
1320 1332 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1321 1333
1322 1334 other = peer(repo, opts, dest)
1323 outgoing = discovery.findcommonoutgoing(
1324 repo, other, revs, force=opts.get(b'force')
1325 )
1326 o = outgoing.missing
1327 if not o:
1328 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1329 return o, other
1335 try:
1336 outgoing = discovery.findcommonoutgoing(
1337 repo, other, revs, force=opts.get(b'force')
1338 )
1339 o = outgoing.missing
1340 if not o:
1341 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1342 return o, other
1343 except: # re-raises
1344 other.close()
1345 raise
1330 1346
1331 1347
1332 1348 def outgoing(ui, repo, dest, opts):
1333 1349 def recurse():
1334 1350 ret = 1
1335 1351 if opts.get(b'subrepos'):
1336 1352 ctx = repo[None]
1337 1353 for subpath in sorted(ctx.substate):
1338 1354 sub = ctx.sub(subpath)
1339 1355 ret = min(ret, sub.outgoing(ui, dest, opts))
1340 1356 return ret
1341 1357
1342 1358 limit = logcmdutil.getlimit(opts)
1343 1359 o, other = _outgoing(ui, repo, dest, opts)
1344 if not o:
1345 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1346 return recurse()
1360 try:
1361 if not o:
1362 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1363 return recurse()
1347 1364
1348 if opts.get(b'newest_first'):
1349 o.reverse()
1350 ui.pager(b'outgoing')
1351 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1352 count = 0
1353 for n in o:
1354 if limit is not None and count >= limit:
1355 break
1356 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1357 if opts.get(b'no_merges') and len(parents) == 2:
1358 continue
1359 count += 1
1360 displayer.show(repo[n])
1361 displayer.close()
1362 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1363 recurse()
1364 return 0 # exit code is zero since we found outgoing changes
1365 if opts.get(b'newest_first'):
1366 o.reverse()
1367 ui.pager(b'outgoing')
1368 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1369 count = 0
1370 for n in o:
1371 if limit is not None and count >= limit:
1372 break
1373 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1374 if opts.get(b'no_merges') and len(parents) == 2:
1375 continue
1376 count += 1
1377 displayer.show(repo[n])
1378 displayer.close()
1379 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1380 recurse()
1381 return 0 # exit code is zero since we found outgoing changes
1382 finally:
1383 other.close()
1365 1384
1366 1385
1367 1386 def verify(repo, level=None):
1368 1387 """verify the consistency of a repository"""
1369 1388 ret = verifymod.verify(repo, level=level)
1370 1389
1371 1390 # Broken subrepo references in hidden csets don't seem worth worrying about,
1372 1391 # since they can't be pushed/pulled, and --hidden can be used if they are a
1373 1392 # concern.
1374 1393
1375 1394 # pathto() is needed for -R case
1376 1395 revs = repo.revs(
1377 1396 b"filelog(%s)", util.pathto(repo.root, repo.getcwd(), b'.hgsubstate')
1378 1397 )
1379 1398
1380 1399 if revs:
1381 1400 repo.ui.status(_(b'checking subrepo links\n'))
1382 1401 for rev in revs:
1383 1402 ctx = repo[rev]
1384 1403 try:
1385 1404 for subpath in ctx.substate:
1386 1405 try:
1387 1406 ret = (
1388 1407 ctx.sub(subpath, allowcreate=False).verify() or ret
1389 1408 )
1390 1409 except error.RepoError as e:
1391 1410 repo.ui.warn(b'%d: %s\n' % (rev, e))
1392 1411 except Exception:
1393 1412 repo.ui.warn(
1394 1413 _(b'.hgsubstate is corrupt in revision %s\n')
1395 1414 % short(ctx.node())
1396 1415 )
1397 1416
1398 1417 return ret
1399 1418
1400 1419
1401 1420 def remoteui(src, opts):
1402 1421 """build a remote ui from ui or repo and opts"""
1403 1422 if util.safehasattr(src, b'baseui'): # looks like a repository
1404 1423 dst = src.baseui.copy() # drop repo-specific config
1405 1424 src = src.ui # copy target options from repo
1406 1425 else: # assume it's a global ui object
1407 1426 dst = src.copy() # keep all global options
1408 1427
1409 1428 # copy ssh-specific options
1410 1429 for o in b'ssh', b'remotecmd':
1411 1430 v = opts.get(o) or src.config(b'ui', o)
1412 1431 if v:
1413 1432 dst.setconfig(b"ui", o, v, b'copied')
1414 1433
1415 1434 # copy bundle-specific options
1416 1435 r = src.config(b'bundle', b'mainreporoot')
1417 1436 if r:
1418 1437 dst.setconfig(b'bundle', b'mainreporoot', r, b'copied')
1419 1438
1420 1439 # copy selected local settings to the remote ui
1421 1440 for sect in (b'auth', b'hostfingerprints', b'hostsecurity', b'http_proxy'):
1422 1441 for key, val in src.configitems(sect):
1423 1442 dst.setconfig(sect, key, val, b'copied')
1424 1443 v = src.config(b'web', b'cacerts')
1425 1444 if v:
1426 1445 dst.setconfig(b'web', b'cacerts', util.expandpath(v), b'copied')
1427 1446
1428 1447 return dst
1429 1448
1430 1449
1431 1450 # Files of interest
1432 1451 # Used to check if the repository has changed looking at mtime and size of
1433 1452 # these files.
1434 1453 foi = [
1435 1454 (b'spath', b'00changelog.i'),
1436 1455 (b'spath', b'phaseroots'), # ! phase can change content at the same size
1437 1456 (b'spath', b'obsstore'),
1438 1457 (b'path', b'bookmarks'), # ! bookmark can change content at the same size
1439 1458 ]
1440 1459
1441 1460
1442 1461 class cachedlocalrepo(object):
1443 1462 """Holds a localrepository that can be cached and reused."""
1444 1463
1445 1464 def __init__(self, repo):
1446 1465 """Create a new cached repo from an existing repo.
1447 1466
1448 1467 We assume the passed in repo was recently created. If the
1449 1468 repo has changed between when it was created and when it was
1450 1469 turned into a cache, it may not refresh properly.
1451 1470 """
1452 1471 assert isinstance(repo, localrepo.localrepository)
1453 1472 self._repo = repo
1454 1473 self._state, self.mtime = self._repostate()
1455 1474 self._filtername = repo.filtername
1456 1475
1457 1476 def fetch(self):
1458 1477 """Refresh (if necessary) and return a repository.
1459 1478
1460 1479 If the cached instance is out of date, it will be recreated
1461 1480 automatically and returned.
1462 1481
1463 1482 Returns a tuple of the repo and a boolean indicating whether a new
1464 1483 repo instance was created.
1465 1484 """
1466 1485 # We compare the mtimes and sizes of some well-known files to
1467 1486 # determine if the repo changed. This is not precise, as mtimes
1468 1487 # are susceptible to clock skew and imprecise filesystems and
1469 1488 # file content can change while maintaining the same size.
1470 1489
1471 1490 state, mtime = self._repostate()
1472 1491 if state == self._state:
1473 1492 return self._repo, False
1474 1493
1475 1494 repo = repository(self._repo.baseui, self._repo.url())
1476 1495 if self._filtername:
1477 1496 self._repo = repo.filtered(self._filtername)
1478 1497 else:
1479 1498 self._repo = repo.unfiltered()
1480 1499 self._state = state
1481 1500 self.mtime = mtime
1482 1501
1483 1502 return self._repo, True
1484 1503
1485 1504 def _repostate(self):
1486 1505 state = []
1487 1506 maxmtime = -1
1488 1507 for attr, fname in foi:
1489 1508 prefix = getattr(self._repo, attr)
1490 1509 p = os.path.join(prefix, fname)
1491 1510 try:
1492 1511 st = os.stat(p)
1493 1512 except OSError:
1494 1513 st = os.stat(prefix)
1495 1514 state.append((st[stat.ST_MTIME], st.st_size))
1496 1515 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1497 1516
1498 1517 return tuple(state), maxmtime
1499 1518
1500 1519 def copy(self):
1501 1520 """Obtain a copy of this class instance.
1502 1521
1503 1522 A new localrepository instance is obtained. The new instance should be
1504 1523 completely independent of the original.
1505 1524 """
1506 1525 repo = repository(self._repo.baseui, self._repo.origroot)
1507 1526 if self._filtername:
1508 1527 repo = repo.filtered(self._filtername)
1509 1528 else:
1510 1529 repo = repo.unfiltered()
1511 1530 c = cachedlocalrepo(repo)
1512 1531 c._state = self._state
1513 1532 c.mtime = self.mtime
1514 1533 return c
@@ -1,2779 +1,2782 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import re
11 11
12 12 from .i18n import _
13 13 from .pycompat import getattr
14 14 from .node import (
15 15 bin,
16 16 nullrev,
17 17 wdirrev,
18 18 )
19 19 from . import (
20 20 dagop,
21 21 destutil,
22 22 diffutil,
23 23 encoding,
24 24 error,
25 25 grep as grepmod,
26 26 hbisect,
27 27 match as matchmod,
28 28 obsolete as obsmod,
29 29 obsutil,
30 30 pathutil,
31 31 phases,
32 32 pycompat,
33 33 registrar,
34 34 repoview,
35 35 revsetlang,
36 36 scmutil,
37 37 smartset,
38 38 stack as stackmod,
39 39 util,
40 40 )
41 41 from .utils import (
42 42 dateutil,
43 43 stringutil,
44 44 )
45 45
46 46 # helpers for processing parsed tree
47 47 getsymbol = revsetlang.getsymbol
48 48 getstring = revsetlang.getstring
49 49 getinteger = revsetlang.getinteger
50 50 getboolean = revsetlang.getboolean
51 51 getlist = revsetlang.getlist
52 52 getintrange = revsetlang.getintrange
53 53 getargs = revsetlang.getargs
54 54 getargsdict = revsetlang.getargsdict
55 55
56 56 baseset = smartset.baseset
57 57 generatorset = smartset.generatorset
58 58 spanset = smartset.spanset
59 59 fullreposet = smartset.fullreposet
60 60
61 61 # revisions not included in all(), but populated if specified
62 62 _virtualrevs = (nullrev, wdirrev)
63 63
64 64 # Constants for ordering requirement, used in getset():
65 65 #
66 66 # If 'define', any nested functions and operations MAY change the ordering of
67 67 # the entries in the set (but if changes the ordering, it MUST ALWAYS change
68 68 # it). If 'follow', any nested functions and operations MUST take the ordering
69 69 # specified by the first operand to the '&' operator.
70 70 #
71 71 # For instance,
72 72 #
73 73 # X & (Y | Z)
74 74 # ^ ^^^^^^^
75 75 # | follow
76 76 # define
77 77 #
78 78 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
79 79 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
80 80 #
81 81 # 'any' means the order doesn't matter. For instance,
82 82 #
83 83 # (X & !Y) | ancestors(Z)
84 84 # ^ ^
85 85 # any any
86 86 #
87 87 # For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
88 88 # order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
89 89 # since 'ancestors' does not care about the order of its argument.
90 90 #
91 91 # Currently, most revsets do not care about the order, so 'define' is
92 92 # equivalent to 'follow' for them, and the resulting order is based on the
93 93 # 'subset' parameter passed down to them:
94 94 #
95 95 # m = revset.match(...)
96 96 # m(repo, subset, order=defineorder)
97 97 # ^^^^^^
98 98 # For most revsets, 'define' means using the order this subset provides
99 99 #
100 100 # There are a few revsets that always redefine the order if 'define' is
101 101 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
102 102 anyorder = b'any' # don't care the order, could be even random-shuffled
103 103 defineorder = b'define' # ALWAYS redefine, or ALWAYS follow the current order
104 104 followorder = b'follow' # MUST follow the current order
105 105
106 106 # helpers
107 107
108 108
109 109 def getset(repo, subset, x, order=defineorder):
110 110 if not x:
111 111 raise error.ParseError(_(b"missing argument"))
112 112 return methods[x[0]](repo, subset, *x[1:], order=order)
113 113
114 114
115 115 def _getrevsource(repo, r):
116 116 extra = repo[r].extra()
117 117 for label in (b'source', b'transplant_source', b'rebase_source'):
118 118 if label in extra:
119 119 try:
120 120 return repo[extra[label]].rev()
121 121 except error.RepoLookupError:
122 122 pass
123 123 return None
124 124
125 125
126 126 def _sortedb(xs):
127 127 return sorted(pycompat.rapply(pycompat.maybebytestr, xs))
128 128
129 129
130 130 # operator methods
131 131
132 132
133 133 def stringset(repo, subset, x, order):
134 134 if not x:
135 135 raise error.ParseError(_(b"empty string is not a valid revision"))
136 136 x = scmutil.intrev(scmutil.revsymbol(repo, x))
137 137 if x in subset or x in _virtualrevs and isinstance(subset, fullreposet):
138 138 return baseset([x])
139 139 return baseset()
140 140
141 141
142 142 def rawsmartset(repo, subset, x, order):
143 143 """argument is already a smartset, use that directly"""
144 144 if order == followorder:
145 145 return subset & x
146 146 else:
147 147 return x & subset
148 148
149 149
150 150 def rangeset(repo, subset, x, y, order):
151 151 m = getset(repo, fullreposet(repo), x)
152 152 n = getset(repo, fullreposet(repo), y)
153 153
154 154 if not m or not n:
155 155 return baseset()
156 156 return _makerangeset(repo, subset, m.first(), n.last(), order)
157 157
158 158
159 159 def rangeall(repo, subset, x, order):
160 160 assert x is None
161 161 return _makerangeset(repo, subset, 0, repo.changelog.tiprev(), order)
162 162
163 163
164 164 def rangepre(repo, subset, y, order):
165 165 # ':y' can't be rewritten to '0:y' since '0' may be hidden
166 166 n = getset(repo, fullreposet(repo), y)
167 167 if not n:
168 168 return baseset()
169 169 return _makerangeset(repo, subset, 0, n.last(), order)
170 170
171 171
172 172 def rangepost(repo, subset, x, order):
173 173 m = getset(repo, fullreposet(repo), x)
174 174 if not m:
175 175 return baseset()
176 176 return _makerangeset(
177 177 repo, subset, m.first(), repo.changelog.tiprev(), order
178 178 )
179 179
180 180
181 181 def _makerangeset(repo, subset, m, n, order):
182 182 if m == n:
183 183 r = baseset([m])
184 184 elif n == wdirrev:
185 185 r = spanset(repo, m, len(repo)) + baseset([n])
186 186 elif m == wdirrev:
187 187 r = baseset([m]) + spanset(repo, repo.changelog.tiprev(), n - 1)
188 188 elif m < n:
189 189 r = spanset(repo, m, n + 1)
190 190 else:
191 191 r = spanset(repo, m, n - 1)
192 192
193 193 if order == defineorder:
194 194 return r & subset
195 195 else:
196 196 # carrying the sorting over when possible would be more efficient
197 197 return subset & r
198 198
199 199
200 200 def dagrange(repo, subset, x, y, order):
201 201 r = fullreposet(repo)
202 202 xs = dagop.reachableroots(
203 203 repo, getset(repo, r, x), getset(repo, r, y), includepath=True
204 204 )
205 205 return subset & xs
206 206
207 207
208 208 def andset(repo, subset, x, y, order):
209 209 if order == anyorder:
210 210 yorder = anyorder
211 211 else:
212 212 yorder = followorder
213 213 return getset(repo, getset(repo, subset, x, order), y, yorder)
214 214
215 215
216 216 def andsmallyset(repo, subset, x, y, order):
217 217 # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
218 218 if order == anyorder:
219 219 yorder = anyorder
220 220 else:
221 221 yorder = followorder
222 222 return getset(repo, getset(repo, subset, y, yorder), x, order)
223 223
224 224
225 225 def differenceset(repo, subset, x, y, order):
226 226 return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
227 227
228 228
229 229 def _orsetlist(repo, subset, xs, order):
230 230 assert xs
231 231 if len(xs) == 1:
232 232 return getset(repo, subset, xs[0], order)
233 233 p = len(xs) // 2
234 234 a = _orsetlist(repo, subset, xs[:p], order)
235 235 b = _orsetlist(repo, subset, xs[p:], order)
236 236 return a + b
237 237
238 238
239 239 def orset(repo, subset, x, order):
240 240 xs = getlist(x)
241 241 if not xs:
242 242 return baseset()
243 243 if order == followorder:
244 244 # slow path to take the subset order
245 245 return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
246 246 else:
247 247 return _orsetlist(repo, subset, xs, order)
248 248
249 249
250 250 def notset(repo, subset, x, order):
251 251 return subset - getset(repo, subset, x, anyorder)
252 252
253 253
254 254 def relationset(repo, subset, x, y, order):
255 255 # this is pretty basic implementation of 'x#y' operator, still
256 256 # experimental so undocumented. see the wiki for further ideas.
257 257 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
258 258 rel = getsymbol(y)
259 259 if rel in relations:
260 260 return relations[rel](repo, subset, x, rel, order)
261 261
262 262 relnames = [r for r in relations.keys() if len(r) > 1]
263 263 raise error.UnknownIdentifier(rel, relnames)
264 264
265 265
266 266 def _splitrange(a, b):
267 267 """Split range with bounds a and b into two ranges at 0 and return two
268 268 tuples of numbers for use as startdepth and stopdepth arguments of
269 269 revancestors and revdescendants.
270 270
271 271 >>> _splitrange(-10, -5) # [-10:-5]
272 272 ((5, 11), (None, None))
273 273 >>> _splitrange(5, 10) # [5:10]
274 274 ((None, None), (5, 11))
275 275 >>> _splitrange(-10, 10) # [-10:10]
276 276 ((0, 11), (0, 11))
277 277 >>> _splitrange(-10, 0) # [-10:0]
278 278 ((0, 11), (None, None))
279 279 >>> _splitrange(0, 10) # [0:10]
280 280 ((None, None), (0, 11))
281 281 >>> _splitrange(0, 0) # [0:0]
282 282 ((0, 1), (None, None))
283 283 >>> _splitrange(1, -1) # [1:-1]
284 284 ((None, None), (None, None))
285 285 """
286 286 ancdepths = (None, None)
287 287 descdepths = (None, None)
288 288 if a == b == 0:
289 289 ancdepths = (0, 1)
290 290 if a < 0:
291 291 ancdepths = (-min(b, 0), -a + 1)
292 292 if b > 0:
293 293 descdepths = (max(a, 0), b + 1)
294 294 return ancdepths, descdepths
295 295
296 296
297 297 def generationsrel(repo, subset, x, rel, order):
298 298 z = (b'rangeall', None)
299 299 return generationssubrel(repo, subset, x, rel, z, order)
300 300
301 301
302 302 def generationssubrel(repo, subset, x, rel, z, order):
303 303 # TODO: rewrite tests, and drop startdepth argument from ancestors() and
304 304 # descendants() predicates
305 305 a, b = getintrange(
306 306 z,
307 307 _(b'relation subscript must be an integer or a range'),
308 308 _(b'relation subscript bounds must be integers'),
309 309 deffirst=-(dagop.maxlogdepth - 1),
310 310 deflast=+(dagop.maxlogdepth - 1),
311 311 )
312 312 (ancstart, ancstop), (descstart, descstop) = _splitrange(a, b)
313 313
314 314 if ancstart is None and descstart is None:
315 315 return baseset()
316 316
317 317 revs = getset(repo, fullreposet(repo), x)
318 318 if not revs:
319 319 return baseset()
320 320
321 321 if ancstart is not None and descstart is not None:
322 322 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
323 323 s += dagop.revdescendants(repo, revs, False, descstart, descstop)
324 324 elif ancstart is not None:
325 325 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
326 326 elif descstart is not None:
327 327 s = dagop.revdescendants(repo, revs, False, descstart, descstop)
328 328
329 329 return subset & s
330 330
331 331
332 332 def relsubscriptset(repo, subset, x, y, z, order):
333 333 # this is pretty basic implementation of 'x#y[z]' operator, still
334 334 # experimental so undocumented. see the wiki for further ideas.
335 335 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
336 336 rel = getsymbol(y)
337 337 if rel in subscriptrelations:
338 338 return subscriptrelations[rel](repo, subset, x, rel, z, order)
339 339
340 340 relnames = [r for r in subscriptrelations.keys() if len(r) > 1]
341 341 raise error.UnknownIdentifier(rel, relnames)
342 342
343 343
344 344 def subscriptset(repo, subset, x, y, order):
345 345 raise error.ParseError(_(b"can't use a subscript in this context"))
346 346
347 347
348 348 def listset(repo, subset, *xs, **opts):
349 349 raise error.ParseError(
350 350 _(b"can't use a list in this context"),
351 351 hint=_(b'see \'hg help "revsets.x or y"\''),
352 352 )
353 353
354 354
355 355 def keyvaluepair(repo, subset, k, v, order):
356 356 raise error.ParseError(_(b"can't use a key-value pair in this context"))
357 357
358 358
359 359 def func(repo, subset, a, b, order):
360 360 f = getsymbol(a)
361 361 if f in symbols:
362 362 func = symbols[f]
363 363 if getattr(func, '_takeorder', False):
364 364 return func(repo, subset, b, order)
365 365 return func(repo, subset, b)
366 366
367 367 keep = lambda fn: getattr(fn, '__doc__', None) is not None
368 368
369 369 syms = [s for (s, fn) in symbols.items() if keep(fn)]
370 370 raise error.UnknownIdentifier(f, syms)
371 371
372 372
373 373 # functions
374 374
375 375 # symbols are callables like:
376 376 # fn(repo, subset, x)
377 377 # with:
378 378 # repo - current repository instance
379 379 # subset - of revisions to be examined
380 380 # x - argument in tree form
381 381 symbols = revsetlang.symbols
382 382
383 383 # symbols which can't be used for a DoS attack for any given input
384 384 # (e.g. those which accept regexes as plain strings shouldn't be included)
385 385 # functions that just return a lot of changesets (like all) don't count here
386 386 safesymbols = set()
387 387
388 388 predicate = registrar.revsetpredicate()
389 389
390 390
391 391 @predicate(b'_destupdate')
392 392 def _destupdate(repo, subset, x):
393 393 # experimental revset for update destination
394 394 args = getargsdict(x, b'limit', b'clean')
395 395 return subset & baseset(
396 396 [destutil.destupdate(repo, **pycompat.strkwargs(args))[0]]
397 397 )
398 398
399 399
400 400 @predicate(b'_destmerge')
401 401 def _destmerge(repo, subset, x):
402 402 # experimental revset for merge destination
403 403 sourceset = None
404 404 if x is not None:
405 405 sourceset = getset(repo, fullreposet(repo), x)
406 406 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
407 407
408 408
409 409 @predicate(b'adds(pattern)', safe=True, weight=30)
410 410 def adds(repo, subset, x):
411 411 """Changesets that add a file matching pattern.
412 412
413 413 The pattern without explicit kind like ``glob:`` is expected to be
414 414 relative to the current directory and match against a file or a
415 415 directory.
416 416 """
417 417 # i18n: "adds" is a keyword
418 418 pat = getstring(x, _(b"adds requires a pattern"))
419 419 return checkstatus(repo, subset, pat, 'added')
420 420
421 421
422 422 @predicate(b'ancestor(*changeset)', safe=True, weight=0.5)
423 423 def ancestor(repo, subset, x):
424 424 """A greatest common ancestor of the changesets.
425 425
426 426 Accepts 0 or more changesets.
427 427 Will return empty list when passed no args.
428 428 Greatest common ancestor of a single changeset is that changeset.
429 429 """
430 430 reviter = iter(orset(repo, fullreposet(repo), x, order=anyorder))
431 431 try:
432 432 anc = repo[next(reviter)]
433 433 except StopIteration:
434 434 return baseset()
435 435 for r in reviter:
436 436 anc = anc.ancestor(repo[r])
437 437
438 438 r = scmutil.intrev(anc)
439 439 if r in subset:
440 440 return baseset([r])
441 441 return baseset()
442 442
443 443
444 444 def _ancestors(
445 445 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
446 446 ):
447 447 heads = getset(repo, fullreposet(repo), x)
448 448 if not heads:
449 449 return baseset()
450 450 s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
451 451 return subset & s
452 452
453 453
454 454 @predicate(b'ancestors(set[, depth])', safe=True)
455 455 def ancestors(repo, subset, x):
456 456 """Changesets that are ancestors of changesets in set, including the
457 457 given changesets themselves.
458 458
459 459 If depth is specified, the result only includes changesets up to
460 460 the specified generation.
461 461 """
462 462 # startdepth is for internal use only until we can decide the UI
463 463 args = getargsdict(x, b'ancestors', b'set depth startdepth')
464 464 if b'set' not in args:
465 465 # i18n: "ancestors" is a keyword
466 466 raise error.ParseError(_(b'ancestors takes at least 1 argument'))
467 467 startdepth = stopdepth = None
468 468 if b'startdepth' in args:
469 469 n = getinteger(
470 470 args[b'startdepth'], b"ancestors expects an integer startdepth"
471 471 )
472 472 if n < 0:
473 473 raise error.ParseError(b"negative startdepth")
474 474 startdepth = n
475 475 if b'depth' in args:
476 476 # i18n: "ancestors" is a keyword
477 477 n = getinteger(args[b'depth'], _(b"ancestors expects an integer depth"))
478 478 if n < 0:
479 479 raise error.ParseError(_(b"negative depth"))
480 480 stopdepth = n + 1
481 481 return _ancestors(
482 482 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
483 483 )
484 484
485 485
486 486 @predicate(b'_firstancestors', safe=True)
487 487 def _firstancestors(repo, subset, x):
488 488 # ``_firstancestors(set)``
489 489 # Like ``ancestors(set)`` but follows only the first parents.
490 490 return _ancestors(repo, subset, x, followfirst=True)
491 491
492 492
493 493 def _childrenspec(repo, subset, x, n, order):
494 494 """Changesets that are the Nth child of a changeset
495 495 in set.
496 496 """
497 497 cs = set()
498 498 for r in getset(repo, fullreposet(repo), x):
499 499 for i in range(n):
500 500 c = repo[r].children()
501 501 if len(c) == 0:
502 502 break
503 503 if len(c) > 1:
504 504 raise error.RepoLookupError(
505 505 _(b"revision in set has more than one child")
506 506 )
507 507 r = c[0].rev()
508 508 else:
509 509 cs.add(r)
510 510 return subset & cs
511 511
512 512
513 513 def ancestorspec(repo, subset, x, n, order):
514 514 """``set~n``
515 515 Changesets that are the Nth ancestor (first parents only) of a changeset
516 516 in set.
517 517 """
518 518 n = getinteger(n, _(b"~ expects a number"))
519 519 if n < 0:
520 520 # children lookup
521 521 return _childrenspec(repo, subset, x, -n, order)
522 522 ps = set()
523 523 cl = repo.changelog
524 524 for r in getset(repo, fullreposet(repo), x):
525 525 for i in range(n):
526 526 try:
527 527 r = cl.parentrevs(r)[0]
528 528 except error.WdirUnsupported:
529 529 r = repo[r].p1().rev()
530 530 ps.add(r)
531 531 return subset & ps
532 532
533 533
534 534 @predicate(b'author(string)', safe=True, weight=10)
535 535 def author(repo, subset, x):
536 536 """Alias for ``user(string)``."""
537 537 # i18n: "author" is a keyword
538 538 n = getstring(x, _(b"author requires a string"))
539 539 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
540 540 return subset.filter(
541 541 lambda x: matcher(repo[x].user()), condrepr=(b'<user %r>', n)
542 542 )
543 543
544 544
545 545 @predicate(b'bisect(string)', safe=True)
546 546 def bisect(repo, subset, x):
547 547 """Changesets marked in the specified bisect status:
548 548
549 549 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
550 550 - ``goods``, ``bads`` : csets topologically good/bad
551 551 - ``range`` : csets taking part in the bisection
552 552 - ``pruned`` : csets that are goods, bads or skipped
553 553 - ``untested`` : csets whose fate is yet unknown
554 554 - ``ignored`` : csets ignored due to DAG topology
555 555 - ``current`` : the cset currently being bisected
556 556 """
557 557 # i18n: "bisect" is a keyword
558 558 status = getstring(x, _(b"bisect requires a string")).lower()
559 559 state = set(hbisect.get(repo, status))
560 560 return subset & state
561 561
562 562
563 563 # Backward-compatibility
564 564 # - no help entry so that we do not advertise it any more
565 565 @predicate(b'bisected', safe=True)
566 566 def bisected(repo, subset, x):
567 567 return bisect(repo, subset, x)
568 568
569 569
570 570 @predicate(b'bookmark([name])', safe=True)
571 571 def bookmark(repo, subset, x):
572 572 """The named bookmark or all bookmarks.
573 573
574 574 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
575 575 """
576 576 # i18n: "bookmark" is a keyword
577 577 args = getargs(x, 0, 1, _(b'bookmark takes one or no arguments'))
578 578 if args:
579 579 bm = getstring(
580 580 args[0],
581 581 # i18n: "bookmark" is a keyword
582 582 _(b'the argument to bookmark must be a string'),
583 583 )
584 584 kind, pattern, matcher = stringutil.stringmatcher(bm)
585 585 bms = set()
586 586 if kind == b'literal':
587 587 if bm == pattern:
588 588 pattern = repo._bookmarks.expandname(pattern)
589 589 bmrev = repo._bookmarks.get(pattern, None)
590 590 if not bmrev:
591 591 raise error.RepoLookupError(
592 592 _(b"bookmark '%s' does not exist") % pattern
593 593 )
594 594 bms.add(repo[bmrev].rev())
595 595 else:
596 596 matchrevs = set()
597 597 for name, bmrev in pycompat.iteritems(repo._bookmarks):
598 598 if matcher(name):
599 599 matchrevs.add(bmrev)
600 600 for bmrev in matchrevs:
601 601 bms.add(repo[bmrev].rev())
602 602 else:
603 603 bms = {repo[r].rev() for r in repo._bookmarks.values()}
604 604 bms -= {nullrev}
605 605 return subset & bms
606 606
607 607
608 608 @predicate(b'branch(string or set)', safe=True, weight=10)
609 609 def branch(repo, subset, x):
610 610 """
611 611 All changesets belonging to the given branch or the branches of the given
612 612 changesets.
613 613
614 614 Pattern matching is supported for `string`. See
615 615 :hg:`help revisions.patterns`.
616 616 """
617 617 getbi = repo.revbranchcache().branchinfo
618 618
619 619 def getbranch(r):
620 620 try:
621 621 return getbi(r)[0]
622 622 except error.WdirUnsupported:
623 623 return repo[r].branch()
624 624
625 625 try:
626 626 b = getstring(x, b'')
627 627 except error.ParseError:
628 628 # not a string, but another revspec, e.g. tip()
629 629 pass
630 630 else:
631 631 kind, pattern, matcher = stringutil.stringmatcher(b)
632 632 if kind == b'literal':
633 633 # note: falls through to the revspec case if no branch with
634 634 # this name exists and pattern kind is not specified explicitly
635 635 if repo.branchmap().hasbranch(pattern):
636 636 return subset.filter(
637 637 lambda r: matcher(getbranch(r)),
638 638 condrepr=(b'<branch %r>', b),
639 639 )
640 640 if b.startswith(b'literal:'):
641 641 raise error.RepoLookupError(
642 642 _(b"branch '%s' does not exist") % pattern
643 643 )
644 644 else:
645 645 return subset.filter(
646 646 lambda r: matcher(getbranch(r)), condrepr=(b'<branch %r>', b)
647 647 )
648 648
649 649 s = getset(repo, fullreposet(repo), x)
650 650 b = set()
651 651 for r in s:
652 652 b.add(getbranch(r))
653 653 c = s.__contains__
654 654 return subset.filter(
655 655 lambda r: c(r) or getbranch(r) in b,
656 656 condrepr=lambda: b'<branch %r>' % _sortedb(b),
657 657 )
658 658
659 659
660 660 @predicate(b'phasedivergent()', safe=True)
661 661 def phasedivergent(repo, subset, x):
662 662 """Mutable changesets marked as successors of public changesets.
663 663
664 664 Only non-public and non-obsolete changesets can be `phasedivergent`.
665 665 (EXPERIMENTAL)
666 666 """
667 667 # i18n: "phasedivergent" is a keyword
668 668 getargs(x, 0, 0, _(b"phasedivergent takes no arguments"))
669 669 phasedivergent = obsmod.getrevs(repo, b'phasedivergent')
670 670 return subset & phasedivergent
671 671
672 672
673 673 @predicate(b'bundle()', safe=True)
674 674 def bundle(repo, subset, x):
675 675 """Changesets in the bundle.
676 676
677 677 Bundle must be specified by the -R option."""
678 678
679 679 try:
680 680 bundlerevs = repo.changelog.bundlerevs
681 681 except AttributeError:
682 682 raise error.Abort(_(b"no bundle provided - specify with -R"))
683 683 return subset & bundlerevs
684 684
685 685
686 686 def checkstatus(repo, subset, pat, field):
687 687 """Helper for status-related revsets (adds, removes, modifies).
688 688 The field parameter says which kind is desired.
689 689 """
690 690 hasset = matchmod.patkind(pat) == b'set'
691 691
692 692 mcache = [None]
693 693
694 694 def matches(x):
695 695 c = repo[x]
696 696 if not mcache[0] or hasset:
697 697 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
698 698 m = mcache[0]
699 699 fname = None
700 700
701 701 assert m is not None # help pytype
702 702 if not m.anypats() and len(m.files()) == 1:
703 703 fname = m.files()[0]
704 704 if fname is not None:
705 705 if fname not in c.files():
706 706 return False
707 707 else:
708 708 if not any(m(f) for f in c.files()):
709 709 return False
710 710 files = getattr(repo.status(c.p1().node(), c.node()), field)
711 711 if fname is not None:
712 712 if fname in files:
713 713 return True
714 714 else:
715 715 if any(m(f) for f in files):
716 716 return True
717 717
718 718 return subset.filter(
719 719 matches, condrepr=(b'<status.%s %r>', pycompat.sysbytes(field), pat)
720 720 )
721 721
722 722
723 723 def _children(repo, subset, parentset):
724 724 if not parentset:
725 725 return baseset()
726 726 cs = set()
727 727 pr = repo.changelog.parentrevs
728 728 minrev = parentset.min()
729 729 for r in subset:
730 730 if r <= minrev:
731 731 continue
732 732 p1, p2 = pr(r)
733 733 if p1 in parentset:
734 734 cs.add(r)
735 735 if p2 != nullrev and p2 in parentset:
736 736 cs.add(r)
737 737 return baseset(cs)
738 738
739 739
740 740 @predicate(b'children(set)', safe=True)
741 741 def children(repo, subset, x):
742 742 """Child changesets of changesets in set."""
743 743 s = getset(repo, fullreposet(repo), x)
744 744 cs = _children(repo, subset, s)
745 745 return subset & cs
746 746
747 747
748 748 @predicate(b'closed()', safe=True, weight=10)
749 749 def closed(repo, subset, x):
750 750 """Changeset is closed."""
751 751 # i18n: "closed" is a keyword
752 752 getargs(x, 0, 0, _(b"closed takes no arguments"))
753 753 return subset.filter(
754 754 lambda r: repo[r].closesbranch(), condrepr=b'<branch closed>'
755 755 )
756 756
757 757
758 758 # for internal use
759 759 @predicate(b'_commonancestorheads(set)', safe=True)
760 760 def _commonancestorheads(repo, subset, x):
761 761 # This is an internal method is for quickly calculating "heads(::x and
762 762 # ::y)"
763 763
764 764 # These greatest common ancestors are the same ones that the consensus bid
765 765 # merge will find.
766 766 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
767 767
768 768 ancs = repo.changelog._commonancestorsheads(*list(startrevs))
769 769 return subset & baseset(ancs)
770 770
771 771
772 772 @predicate(b'commonancestors(set)', safe=True)
773 773 def commonancestors(repo, subset, x):
774 774 """Changesets that are ancestors of every changeset in set."""
775 775 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
776 776 if not startrevs:
777 777 return baseset()
778 778 for r in startrevs:
779 779 subset &= dagop.revancestors(repo, baseset([r]))
780 780 return subset
781 781
782 782
783 783 @predicate(b'conflictlocal()', safe=True)
784 784 def conflictlocal(repo, subset, x):
785 785 """The local side of the merge, if currently in an unresolved merge.
786 786
787 787 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
788 788 """
789 789 getargs(x, 0, 0, _(b"conflictlocal takes no arguments"))
790 790 from . import mergestate as mergestatemod
791 791
792 792 mergestate = mergestatemod.mergestate.read(repo)
793 793 if mergestate.active() and repo.changelog.hasnode(mergestate.local):
794 794 return subset & {repo.changelog.rev(mergestate.local)}
795 795
796 796 return baseset()
797 797
798 798
799 799 @predicate(b'conflictother()', safe=True)
800 800 def conflictother(repo, subset, x):
801 801 """The other side of the merge, if currently in an unresolved merge.
802 802
803 803 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
804 804 """
805 805 getargs(x, 0, 0, _(b"conflictother takes no arguments"))
806 806 from . import mergestate as mergestatemod
807 807
808 808 mergestate = mergestatemod.mergestate.read(repo)
809 809 if mergestate.active() and repo.changelog.hasnode(mergestate.other):
810 810 return subset & {repo.changelog.rev(mergestate.other)}
811 811
812 812 return baseset()
813 813
814 814
815 815 @predicate(b'contains(pattern)', weight=100)
816 816 def contains(repo, subset, x):
817 817 """The revision's manifest contains a file matching pattern (but might not
818 818 modify it). See :hg:`help patterns` for information about file patterns.
819 819
820 820 The pattern without explicit kind like ``glob:`` is expected to be
821 821 relative to the current directory and match against a file exactly
822 822 for efficiency.
823 823 """
824 824 # i18n: "contains" is a keyword
825 825 pat = getstring(x, _(b"contains requires a pattern"))
826 826
827 827 def matches(x):
828 828 if not matchmod.patkind(pat):
829 829 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
830 830 if pats in repo[x]:
831 831 return True
832 832 else:
833 833 c = repo[x]
834 834 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
835 835 for f in c.manifest():
836 836 if m(f):
837 837 return True
838 838 return False
839 839
840 840 return subset.filter(matches, condrepr=(b'<contains %r>', pat))
841 841
842 842
843 843 @predicate(b'converted([id])', safe=True)
844 844 def converted(repo, subset, x):
845 845 """Changesets converted from the given identifier in the old repository if
846 846 present, or all converted changesets if no identifier is specified.
847 847 """
848 848
849 849 # There is exactly no chance of resolving the revision, so do a simple
850 850 # string compare and hope for the best
851 851
852 852 rev = None
853 853 # i18n: "converted" is a keyword
854 854 l = getargs(x, 0, 1, _(b'converted takes one or no arguments'))
855 855 if l:
856 856 # i18n: "converted" is a keyword
857 857 rev = getstring(l[0], _(b'converted requires a revision'))
858 858
859 859 def _matchvalue(r):
860 860 source = repo[r].extra().get(b'convert_revision', None)
861 861 return source is not None and (rev is None or source.startswith(rev))
862 862
863 863 return subset.filter(
864 864 lambda r: _matchvalue(r), condrepr=(b'<converted %r>', rev)
865 865 )
866 866
867 867
868 868 @predicate(b'date(interval)', safe=True, weight=10)
869 869 def date(repo, subset, x):
870 870 """Changesets within the interval, see :hg:`help dates`."""
871 871 # i18n: "date" is a keyword
872 872 ds = getstring(x, _(b"date requires a string"))
873 873 dm = dateutil.matchdate(ds)
874 874 return subset.filter(
875 875 lambda x: dm(repo[x].date()[0]), condrepr=(b'<date %r>', ds)
876 876 )
877 877
878 878
879 879 @predicate(b'desc(string)', safe=True, weight=10)
880 880 def desc(repo, subset, x):
881 881 """Search commit message for string. The match is case-insensitive.
882 882
883 883 Pattern matching is supported for `string`. See
884 884 :hg:`help revisions.patterns`.
885 885 """
886 886 # i18n: "desc" is a keyword
887 887 ds = getstring(x, _(b"desc requires a string"))
888 888
889 889 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
890 890
891 891 return subset.filter(
892 892 lambda r: matcher(repo[r].description()), condrepr=(b'<desc %r>', ds)
893 893 )
894 894
895 895
896 896 def _descendants(
897 897 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
898 898 ):
899 899 roots = getset(repo, fullreposet(repo), x)
900 900 if not roots:
901 901 return baseset()
902 902 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
903 903 return subset & s
904 904
905 905
906 906 @predicate(b'descendants(set[, depth])', safe=True)
907 907 def descendants(repo, subset, x):
908 908 """Changesets which are descendants of changesets in set, including the
909 909 given changesets themselves.
910 910
911 911 If depth is specified, the result only includes changesets up to
912 912 the specified generation.
913 913 """
914 914 # startdepth is for internal use only until we can decide the UI
915 915 args = getargsdict(x, b'descendants', b'set depth startdepth')
916 916 if b'set' not in args:
917 917 # i18n: "descendants" is a keyword
918 918 raise error.ParseError(_(b'descendants takes at least 1 argument'))
919 919 startdepth = stopdepth = None
920 920 if b'startdepth' in args:
921 921 n = getinteger(
922 922 args[b'startdepth'], b"descendants expects an integer startdepth"
923 923 )
924 924 if n < 0:
925 925 raise error.ParseError(b"negative startdepth")
926 926 startdepth = n
927 927 if b'depth' in args:
928 928 # i18n: "descendants" is a keyword
929 929 n = getinteger(
930 930 args[b'depth'], _(b"descendants expects an integer depth")
931 931 )
932 932 if n < 0:
933 933 raise error.ParseError(_(b"negative depth"))
934 934 stopdepth = n + 1
935 935 return _descendants(
936 936 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
937 937 )
938 938
939 939
940 940 @predicate(b'_firstdescendants', safe=True)
941 941 def _firstdescendants(repo, subset, x):
942 942 # ``_firstdescendants(set)``
943 943 # Like ``descendants(set)`` but follows only the first parents.
944 944 return _descendants(repo, subset, x, followfirst=True)
945 945
946 946
947 947 @predicate(b'destination([set])', safe=True, weight=10)
948 948 def destination(repo, subset, x):
949 949 """Changesets that were created by a graft, transplant or rebase operation,
950 950 with the given revisions specified as the source. Omitting the optional set
951 951 is the same as passing all().
952 952 """
953 953 if x is not None:
954 954 sources = getset(repo, fullreposet(repo), x)
955 955 else:
956 956 sources = fullreposet(repo)
957 957
958 958 dests = set()
959 959
960 960 # subset contains all of the possible destinations that can be returned, so
961 961 # iterate over them and see if their source(s) were provided in the arg set.
962 962 # Even if the immediate src of r is not in the arg set, src's source (or
963 963 # further back) may be. Scanning back further than the immediate src allows
964 964 # transitive transplants and rebases to yield the same results as transitive
965 965 # grafts.
966 966 for r in subset:
967 967 src = _getrevsource(repo, r)
968 968 lineage = None
969 969
970 970 while src is not None:
971 971 if lineage is None:
972 972 lineage = list()
973 973
974 974 lineage.append(r)
975 975
976 976 # The visited lineage is a match if the current source is in the arg
977 977 # set. Since every candidate dest is visited by way of iterating
978 978 # subset, any dests further back in the lineage will be tested by a
979 979 # different iteration over subset. Likewise, if the src was already
980 980 # selected, the current lineage can be selected without going back
981 981 # further.
982 982 if src in sources or src in dests:
983 983 dests.update(lineage)
984 984 break
985 985
986 986 r = src
987 987 src = _getrevsource(repo, r)
988 988
989 989 return subset.filter(
990 990 dests.__contains__,
991 991 condrepr=lambda: b'<destination %r>' % _sortedb(dests),
992 992 )
993 993
994 994
995 995 @predicate(b'diffcontains(pattern)', weight=110)
996 996 def diffcontains(repo, subset, x):
997 997 """Search revision differences for when the pattern was added or removed.
998 998
999 999 The pattern may be a substring literal or a regular expression. See
1000 1000 :hg:`help revisions.patterns`.
1001 1001 """
1002 1002 args = getargsdict(x, b'diffcontains', b'pattern')
1003 1003 if b'pattern' not in args:
1004 1004 # i18n: "diffcontains" is a keyword
1005 1005 raise error.ParseError(_(b'diffcontains takes at least 1 argument'))
1006 1006
1007 1007 pattern = getstring(
1008 1008 args[b'pattern'], _(b'diffcontains requires a string pattern')
1009 1009 )
1010 1010 regexp = stringutil.substringregexp(pattern, re.M)
1011 1011
1012 1012 # TODO: add support for file pattern and --follow. For example,
1013 1013 # diffcontains(pattern[, set]) where set may be file(pattern) or
1014 1014 # follow(pattern), and we'll eventually add a support for narrowing
1015 1015 # files by revset?
1016 1016 fmatch = matchmod.always()
1017 1017
1018 1018 def makefilematcher(ctx):
1019 1019 return fmatch
1020 1020
1021 1021 # TODO: search in a windowed way
1022 1022 searcher = grepmod.grepsearcher(repo.ui, repo, regexp, diff=True)
1023 1023
1024 1024 def testdiff(rev):
1025 1025 # consume the generator to discard revfiles/matches cache
1026 1026 found = False
1027 1027 for fn, ctx, pstates, states in searcher.searchfiles(
1028 1028 baseset([rev]), makefilematcher
1029 1029 ):
1030 1030 if next(grepmod.difflinestates(pstates, states), None):
1031 1031 found = True
1032 1032 return found
1033 1033
1034 1034 return subset.filter(testdiff, condrepr=(b'<diffcontains %r>', pattern))
1035 1035
1036 1036
1037 1037 @predicate(b'contentdivergent()', safe=True)
1038 1038 def contentdivergent(repo, subset, x):
1039 1039 """
1040 1040 Final successors of changesets with an alternative set of final
1041 1041 successors. (EXPERIMENTAL)
1042 1042 """
1043 1043 # i18n: "contentdivergent" is a keyword
1044 1044 getargs(x, 0, 0, _(b"contentdivergent takes no arguments"))
1045 1045 contentdivergent = obsmod.getrevs(repo, b'contentdivergent')
1046 1046 return subset & contentdivergent
1047 1047
1048 1048
1049 1049 @predicate(b'expectsize(set[, size])', safe=True, takeorder=True)
1050 1050 def expectsize(repo, subset, x, order):
1051 1051 """Return the given revset if size matches the revset size.
1052 1052 Abort if the revset doesn't expect given size.
1053 1053 size can either be an integer range or an integer.
1054 1054
1055 1055 For example, ``expectsize(0:1, 3:5)`` will abort as revset size is 2 and
1056 1056 2 is not between 3 and 5 inclusive."""
1057 1057
1058 1058 args = getargsdict(x, b'expectsize', b'set size')
1059 1059 minsize = 0
1060 1060 maxsize = len(repo) + 1
1061 1061 err = b''
1062 1062 if b'size' not in args or b'set' not in args:
1063 1063 raise error.ParseError(_(b'invalid set of arguments'))
1064 1064 minsize, maxsize = getintrange(
1065 1065 args[b'size'],
1066 1066 _(b'expectsize requires a size range or a positive integer'),
1067 1067 _(b'size range bounds must be integers'),
1068 1068 minsize,
1069 1069 maxsize,
1070 1070 )
1071 1071 if minsize < 0 or maxsize < 0:
1072 1072 raise error.ParseError(_(b'negative size'))
1073 1073 rev = getset(repo, fullreposet(repo), args[b'set'], order=order)
1074 1074 if minsize != maxsize and (len(rev) < minsize or len(rev) > maxsize):
1075 1075 err = _(b'revset size mismatch. expected between %d and %d, got %d') % (
1076 1076 minsize,
1077 1077 maxsize,
1078 1078 len(rev),
1079 1079 )
1080 1080 elif minsize == maxsize and len(rev) != minsize:
1081 1081 err = _(b'revset size mismatch. expected %d, got %d') % (
1082 1082 minsize,
1083 1083 len(rev),
1084 1084 )
1085 1085 if err:
1086 1086 raise error.RepoLookupError(err)
1087 1087 if order == followorder:
1088 1088 return subset & rev
1089 1089 else:
1090 1090 return rev & subset
1091 1091
1092 1092
1093 1093 @predicate(b'extdata(source)', safe=False, weight=100)
1094 1094 def extdata(repo, subset, x):
1095 1095 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
1096 1096 # i18n: "extdata" is a keyword
1097 1097 args = getargsdict(x, b'extdata', b'source')
1098 1098 source = getstring(
1099 1099 args.get(b'source'),
1100 1100 # i18n: "extdata" is a keyword
1101 1101 _(b'extdata takes at least 1 string argument'),
1102 1102 )
1103 1103 data = scmutil.extdatasource(repo, source)
1104 1104 return subset & baseset(data)
1105 1105
1106 1106
1107 1107 @predicate(b'extinct()', safe=True)
1108 1108 def extinct(repo, subset, x):
1109 1109 """Obsolete changesets with obsolete descendants only. (EXPERIMENTAL)"""
1110 1110 # i18n: "extinct" is a keyword
1111 1111 getargs(x, 0, 0, _(b"extinct takes no arguments"))
1112 1112 extincts = obsmod.getrevs(repo, b'extinct')
1113 1113 return subset & extincts
1114 1114
1115 1115
1116 1116 @predicate(b'extra(label, [value])', safe=True)
1117 1117 def extra(repo, subset, x):
1118 1118 """Changesets with the given label in the extra metadata, with the given
1119 1119 optional value.
1120 1120
1121 1121 Pattern matching is supported for `value`. See
1122 1122 :hg:`help revisions.patterns`.
1123 1123 """
1124 1124 args = getargsdict(x, b'extra', b'label value')
1125 1125 if b'label' not in args:
1126 1126 # i18n: "extra" is a keyword
1127 1127 raise error.ParseError(_(b'extra takes at least 1 argument'))
1128 1128 # i18n: "extra" is a keyword
1129 1129 label = getstring(
1130 1130 args[b'label'], _(b'first argument to extra must be a string')
1131 1131 )
1132 1132 value = None
1133 1133
1134 1134 if b'value' in args:
1135 1135 # i18n: "extra" is a keyword
1136 1136 value = getstring(
1137 1137 args[b'value'], _(b'second argument to extra must be a string')
1138 1138 )
1139 1139 kind, value, matcher = stringutil.stringmatcher(value)
1140 1140
1141 1141 def _matchvalue(r):
1142 1142 extra = repo[r].extra()
1143 1143 return label in extra and (value is None or matcher(extra[label]))
1144 1144
1145 1145 return subset.filter(
1146 1146 lambda r: _matchvalue(r), condrepr=(b'<extra[%r] %r>', label, value)
1147 1147 )
1148 1148
1149 1149
1150 1150 @predicate(b'filelog(pattern)', safe=True)
1151 1151 def filelog(repo, subset, x):
1152 1152 """Changesets connected to the specified filelog.
1153 1153
1154 1154 For performance reasons, visits only revisions mentioned in the file-level
1155 1155 filelog, rather than filtering through all changesets (much faster, but
1156 1156 doesn't include deletes or duplicate changes). For a slower, more accurate
1157 1157 result, use ``file()``.
1158 1158
1159 1159 The pattern without explicit kind like ``glob:`` is expected to be
1160 1160 relative to the current directory and match against a file exactly
1161 1161 for efficiency.
1162 1162 """
1163 1163
1164 1164 # i18n: "filelog" is a keyword
1165 1165 pat = getstring(x, _(b"filelog requires a pattern"))
1166 1166 s = set()
1167 1167 cl = repo.changelog
1168 1168
1169 1169 if not matchmod.patkind(pat):
1170 1170 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1171 1171 files = [f]
1172 1172 else:
1173 1173 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1174 1174 files = (f for f in repo[None] if m(f))
1175 1175
1176 1176 for f in files:
1177 1177 fl = repo.file(f)
1178 1178 known = {}
1179 1179 scanpos = 0
1180 1180 for fr in list(fl):
1181 1181 fn = fl.node(fr)
1182 1182 if fn in known:
1183 1183 s.add(known[fn])
1184 1184 continue
1185 1185
1186 1186 lr = fl.linkrev(fr)
1187 1187 if lr in cl:
1188 1188 s.add(lr)
1189 1189 elif scanpos is not None:
1190 1190 # lowest matching changeset is filtered, scan further
1191 1191 # ahead in changelog
1192 1192 start = max(lr, scanpos) + 1
1193 1193 scanpos = None
1194 1194 for r in cl.revs(start):
1195 1195 # minimize parsing of non-matching entries
1196 1196 if f in cl.revision(r) and f in cl.readfiles(r):
1197 1197 try:
1198 1198 # try to use manifest delta fastpath
1199 1199 n = repo[r].filenode(f)
1200 1200 if n not in known:
1201 1201 if n == fn:
1202 1202 s.add(r)
1203 1203 scanpos = r
1204 1204 break
1205 1205 else:
1206 1206 known[n] = r
1207 1207 except error.ManifestLookupError:
1208 1208 # deletion in changelog
1209 1209 continue
1210 1210
1211 1211 return subset & s
1212 1212
1213 1213
1214 1214 @predicate(b'first(set, [n])', safe=True, takeorder=True, weight=0)
1215 1215 def first(repo, subset, x, order):
1216 1216 """An alias for limit()."""
1217 1217 return limit(repo, subset, x, order)
1218 1218
1219 1219
1220 1220 def _follow(repo, subset, x, name, followfirst=False):
1221 1221 args = getargsdict(x, name, b'file startrev')
1222 1222 revs = None
1223 1223 if b'startrev' in args:
1224 1224 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1225 1225 if b'file' in args:
1226 1226 x = getstring(args[b'file'], _(b"%s expected a pattern") % name)
1227 1227 if revs is None:
1228 1228 revs = [None]
1229 1229 fctxs = []
1230 1230 for r in revs:
1231 1231 ctx = mctx = repo[r]
1232 1232 if r is None:
1233 1233 ctx = repo[b'.']
1234 1234 m = matchmod.match(
1235 1235 repo.root, repo.getcwd(), [x], ctx=mctx, default=b'path'
1236 1236 )
1237 1237 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
1238 1238 s = dagop.filerevancestors(fctxs, followfirst)
1239 1239 else:
1240 1240 if revs is None:
1241 1241 revs = baseset([repo[b'.'].rev()])
1242 1242 s = dagop.revancestors(repo, revs, followfirst)
1243 1243
1244 1244 return subset & s
1245 1245
1246 1246
1247 1247 @predicate(b'follow([file[, startrev]])', safe=True)
1248 1248 def follow(repo, subset, x):
1249 1249 """
1250 1250 An alias for ``::.`` (ancestors of the working directory's first parent).
1251 1251 If file pattern is specified, the histories of files matching given
1252 1252 pattern in the revision given by startrev are followed, including copies.
1253 1253 """
1254 1254 return _follow(repo, subset, x, b'follow')
1255 1255
1256 1256
1257 1257 @predicate(b'_followfirst', safe=True)
1258 1258 def _followfirst(repo, subset, x):
1259 1259 # ``followfirst([file[, startrev]])``
1260 1260 # Like ``follow([file[, startrev]])`` but follows only the first parent
1261 1261 # of every revisions or files revisions.
1262 1262 return _follow(repo, subset, x, b'_followfirst', followfirst=True)
1263 1263
1264 1264
1265 1265 @predicate(
1266 1266 b'followlines(file, fromline:toline[, startrev=., descend=False])',
1267 1267 safe=True,
1268 1268 )
1269 1269 def followlines(repo, subset, x):
1270 1270 """Changesets modifying `file` in line range ('fromline', 'toline').
1271 1271
1272 1272 Line range corresponds to 'file' content at 'startrev' and should hence be
1273 1273 consistent with file size. If startrev is not specified, working directory's
1274 1274 parent is used.
1275 1275
1276 1276 By default, ancestors of 'startrev' are returned. If 'descend' is True,
1277 1277 descendants of 'startrev' are returned though renames are (currently) not
1278 1278 followed in this direction.
1279 1279 """
1280 1280 args = getargsdict(x, b'followlines', b'file *lines startrev descend')
1281 1281 if len(args[b'lines']) != 1:
1282 1282 raise error.ParseError(_(b"followlines requires a line range"))
1283 1283
1284 1284 rev = b'.'
1285 1285 if b'startrev' in args:
1286 1286 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1287 1287 if len(revs) != 1:
1288 1288 raise error.ParseError(
1289 1289 # i18n: "followlines" is a keyword
1290 1290 _(b"followlines expects exactly one revision")
1291 1291 )
1292 1292 rev = revs.last()
1293 1293
1294 1294 pat = getstring(args[b'file'], _(b"followlines requires a pattern"))
1295 1295 # i18n: "followlines" is a keyword
1296 1296 msg = _(b"followlines expects exactly one file")
1297 1297 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
1298 1298 fromline, toline = util.processlinerange(
1299 1299 *getintrange(
1300 1300 args[b'lines'][0],
1301 1301 # i18n: "followlines" is a keyword
1302 1302 _(b"followlines expects a line number or a range"),
1303 1303 _(b"line range bounds must be integers"),
1304 1304 )
1305 1305 )
1306 1306
1307 1307 fctx = repo[rev].filectx(fname)
1308 1308 descend = False
1309 1309 if b'descend' in args:
1310 1310 descend = getboolean(
1311 1311 args[b'descend'],
1312 1312 # i18n: "descend" is a keyword
1313 1313 _(b"descend argument must be a boolean"),
1314 1314 )
1315 1315 if descend:
1316 1316 rs = generatorset(
1317 1317 (
1318 1318 c.rev()
1319 1319 for c, _linerange in dagop.blockdescendants(
1320 1320 fctx, fromline, toline
1321 1321 )
1322 1322 ),
1323 1323 iterasc=True,
1324 1324 )
1325 1325 else:
1326 1326 rs = generatorset(
1327 1327 (
1328 1328 c.rev()
1329 1329 for c, _linerange in dagop.blockancestors(
1330 1330 fctx, fromline, toline
1331 1331 )
1332 1332 ),
1333 1333 iterasc=False,
1334 1334 )
1335 1335 return subset & rs
1336 1336
1337 1337
1338 1338 @predicate(b'all()', safe=True)
1339 1339 def getall(repo, subset, x):
1340 1340 """All changesets, the same as ``0:tip``."""
1341 1341 # i18n: "all" is a keyword
1342 1342 getargs(x, 0, 0, _(b"all takes no arguments"))
1343 1343 return subset & spanset(repo) # drop "null" if any
1344 1344
1345 1345
1346 1346 @predicate(b'grep(regex)', weight=10)
1347 1347 def grep(repo, subset, x):
1348 1348 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1349 1349 to ensure special escape characters are handled correctly. Unlike
1350 1350 ``keyword(string)``, the match is case-sensitive.
1351 1351 """
1352 1352 try:
1353 1353 # i18n: "grep" is a keyword
1354 1354 gr = re.compile(getstring(x, _(b"grep requires a string")))
1355 1355 except re.error as e:
1356 1356 raise error.ParseError(
1357 1357 _(b'invalid match pattern: %s') % stringutil.forcebytestr(e)
1358 1358 )
1359 1359
1360 1360 def matches(x):
1361 1361 c = repo[x]
1362 1362 for e in c.files() + [c.user(), c.description()]:
1363 1363 if gr.search(e):
1364 1364 return True
1365 1365 return False
1366 1366
1367 1367 return subset.filter(matches, condrepr=(b'<grep %r>', gr.pattern))
1368 1368
1369 1369
1370 1370 @predicate(b'_matchfiles', safe=True)
1371 1371 def _matchfiles(repo, subset, x):
1372 1372 # _matchfiles takes a revset list of prefixed arguments:
1373 1373 #
1374 1374 # [p:foo, i:bar, x:baz]
1375 1375 #
1376 1376 # builds a match object from them and filters subset. Allowed
1377 1377 # prefixes are 'p:' for regular patterns, 'i:' for include
1378 1378 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1379 1379 # a revision identifier, or the empty string to reference the
1380 1380 # working directory, from which the match object is
1381 1381 # initialized. Use 'd:' to set the default matching mode, default
1382 1382 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1383 1383
1384 1384 l = getargs(x, 1, -1, b"_matchfiles requires at least one argument")
1385 1385 pats, inc, exc = [], [], []
1386 1386 rev, default = None, None
1387 1387 for arg in l:
1388 1388 s = getstring(arg, b"_matchfiles requires string arguments")
1389 1389 prefix, value = s[:2], s[2:]
1390 1390 if prefix == b'p:':
1391 1391 pats.append(value)
1392 1392 elif prefix == b'i:':
1393 1393 inc.append(value)
1394 1394 elif prefix == b'x:':
1395 1395 exc.append(value)
1396 1396 elif prefix == b'r:':
1397 1397 if rev is not None:
1398 1398 raise error.ParseError(
1399 1399 b'_matchfiles expected at most one revision'
1400 1400 )
1401 1401 if value == b'': # empty means working directory
1402 1402 rev = wdirrev
1403 1403 else:
1404 1404 rev = value
1405 1405 elif prefix == b'd:':
1406 1406 if default is not None:
1407 1407 raise error.ParseError(
1408 1408 b'_matchfiles expected at most one default mode'
1409 1409 )
1410 1410 default = value
1411 1411 else:
1412 1412 raise error.ParseError(b'invalid _matchfiles prefix: %s' % prefix)
1413 1413 if not default:
1414 1414 default = b'glob'
1415 1415 hasset = any(matchmod.patkind(p) == b'set' for p in pats + inc + exc)
1416 1416
1417 1417 mcache = [None]
1418 1418
1419 1419 # This directly read the changelog data as creating changectx for all
1420 1420 # revisions is quite expensive.
1421 1421 getfiles = repo.changelog.readfiles
1422 1422
1423 1423 def matches(x):
1424 1424 if x == wdirrev:
1425 1425 files = repo[x].files()
1426 1426 else:
1427 1427 files = getfiles(x)
1428 1428
1429 1429 if not mcache[0] or (hasset and rev is None):
1430 1430 r = x if rev is None else rev
1431 1431 mcache[0] = matchmod.match(
1432 1432 repo.root,
1433 1433 repo.getcwd(),
1434 1434 pats,
1435 1435 include=inc,
1436 1436 exclude=exc,
1437 1437 ctx=repo[r],
1438 1438 default=default,
1439 1439 )
1440 1440 m = mcache[0]
1441 1441
1442 1442 for f in files:
1443 1443 if m(f):
1444 1444 return True
1445 1445 return False
1446 1446
1447 1447 return subset.filter(
1448 1448 matches,
1449 1449 condrepr=(
1450 1450 b'<matchfiles patterns=%r, include=%r '
1451 1451 b'exclude=%r, default=%r, rev=%r>',
1452 1452 pats,
1453 1453 inc,
1454 1454 exc,
1455 1455 default,
1456 1456 rev,
1457 1457 ),
1458 1458 )
1459 1459
1460 1460
1461 1461 @predicate(b'file(pattern)', safe=True, weight=10)
1462 1462 def hasfile(repo, subset, x):
1463 1463 """Changesets affecting files matched by pattern.
1464 1464
1465 1465 For a faster but less accurate result, consider using ``filelog()``
1466 1466 instead.
1467 1467
1468 1468 This predicate uses ``glob:`` as the default kind of pattern.
1469 1469 """
1470 1470 # i18n: "file" is a keyword
1471 1471 pat = getstring(x, _(b"file requires a pattern"))
1472 1472 return _matchfiles(repo, subset, (b'string', b'p:' + pat))
1473 1473
1474 1474
1475 1475 @predicate(b'head()', safe=True)
1476 1476 def head(repo, subset, x):
1477 1477 """Changeset is a named branch head."""
1478 1478 # i18n: "head" is a keyword
1479 1479 getargs(x, 0, 0, _(b"head takes no arguments"))
1480 1480 hs = set()
1481 1481 cl = repo.changelog
1482 1482 for ls in repo.branchmap().iterheads():
1483 1483 hs.update(cl.rev(h) for h in ls)
1484 1484 return subset & baseset(hs)
1485 1485
1486 1486
1487 1487 @predicate(b'heads(set)', safe=True, takeorder=True)
1488 1488 def heads(repo, subset, x, order):
1489 1489 """Members of set with no children in set."""
1490 1490 # argument set should never define order
1491 1491 if order == defineorder:
1492 1492 order = followorder
1493 1493 inputset = getset(repo, fullreposet(repo), x, order=order)
1494 1494 wdirparents = None
1495 1495 if wdirrev in inputset:
1496 1496 # a bit slower, but not common so good enough for now
1497 1497 wdirparents = [p.rev() for p in repo[None].parents()]
1498 1498 inputset = set(inputset)
1499 1499 inputset.discard(wdirrev)
1500 1500 heads = repo.changelog.headrevs(inputset)
1501 1501 if wdirparents is not None:
1502 1502 heads.difference_update(wdirparents)
1503 1503 heads.add(wdirrev)
1504 1504 heads = baseset(heads)
1505 1505 return subset & heads
1506 1506
1507 1507
1508 1508 @predicate(b'hidden()', safe=True)
1509 1509 def hidden(repo, subset, x):
1510 1510 """Hidden changesets."""
1511 1511 # i18n: "hidden" is a keyword
1512 1512 getargs(x, 0, 0, _(b"hidden takes no arguments"))
1513 1513 hiddenrevs = repoview.filterrevs(repo, b'visible')
1514 1514 return subset & hiddenrevs
1515 1515
1516 1516
1517 1517 @predicate(b'keyword(string)', safe=True, weight=10)
1518 1518 def keyword(repo, subset, x):
1519 1519 """Search commit message, user name, and names of changed files for
1520 1520 string. The match is case-insensitive.
1521 1521
1522 1522 For a regular expression or case sensitive search of these fields, use
1523 1523 ``grep(regex)``.
1524 1524 """
1525 1525 # i18n: "keyword" is a keyword
1526 1526 kw = encoding.lower(getstring(x, _(b"keyword requires a string")))
1527 1527
1528 1528 def matches(r):
1529 1529 c = repo[r]
1530 1530 return any(
1531 1531 kw in encoding.lower(t)
1532 1532 for t in c.files() + [c.user(), c.description()]
1533 1533 )
1534 1534
1535 1535 return subset.filter(matches, condrepr=(b'<keyword %r>', kw))
1536 1536
1537 1537
1538 1538 @predicate(b'limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1539 1539 def limit(repo, subset, x, order):
1540 1540 """First n members of set, defaulting to 1, starting from offset."""
1541 1541 args = getargsdict(x, b'limit', b'set n offset')
1542 1542 if b'set' not in args:
1543 1543 # i18n: "limit" is a keyword
1544 1544 raise error.ParseError(_(b"limit requires one to three arguments"))
1545 1545 # i18n: "limit" is a keyword
1546 1546 lim = getinteger(args.get(b'n'), _(b"limit expects a number"), default=1)
1547 1547 if lim < 0:
1548 1548 raise error.ParseError(_(b"negative number to select"))
1549 1549 # i18n: "limit" is a keyword
1550 1550 ofs = getinteger(
1551 1551 args.get(b'offset'), _(b"limit expects a number"), default=0
1552 1552 )
1553 1553 if ofs < 0:
1554 1554 raise error.ParseError(_(b"negative offset"))
1555 1555 os = getset(repo, fullreposet(repo), args[b'set'])
1556 1556 ls = os.slice(ofs, ofs + lim)
1557 1557 if order == followorder and lim > 1:
1558 1558 return subset & ls
1559 1559 return ls & subset
1560 1560
1561 1561
1562 1562 @predicate(b'last(set, [n])', safe=True, takeorder=True)
1563 1563 def last(repo, subset, x, order):
1564 1564 """Last n members of set, defaulting to 1."""
1565 1565 # i18n: "last" is a keyword
1566 1566 l = getargs(x, 1, 2, _(b"last requires one or two arguments"))
1567 1567 lim = 1
1568 1568 if len(l) == 2:
1569 1569 # i18n: "last" is a keyword
1570 1570 lim = getinteger(l[1], _(b"last expects a number"))
1571 1571 if lim < 0:
1572 1572 raise error.ParseError(_(b"negative number to select"))
1573 1573 os = getset(repo, fullreposet(repo), l[0])
1574 1574 os.reverse()
1575 1575 ls = os.slice(0, lim)
1576 1576 if order == followorder and lim > 1:
1577 1577 return subset & ls
1578 1578 ls.reverse()
1579 1579 return ls & subset
1580 1580
1581 1581
1582 1582 @predicate(b'max(set)', safe=True)
1583 1583 def maxrev(repo, subset, x):
1584 1584 """Changeset with highest revision number in set."""
1585 1585 os = getset(repo, fullreposet(repo), x)
1586 1586 try:
1587 1587 m = os.max()
1588 1588 if m in subset:
1589 1589 return baseset([m], datarepr=(b'<max %r, %r>', subset, os))
1590 1590 except ValueError:
1591 1591 # os.max() throws a ValueError when the collection is empty.
1592 1592 # Same as python's max().
1593 1593 pass
1594 1594 return baseset(datarepr=(b'<max %r, %r>', subset, os))
1595 1595
1596 1596
1597 1597 @predicate(b'merge()', safe=True)
1598 1598 def merge(repo, subset, x):
1599 1599 """Changeset is a merge changeset."""
1600 1600 # i18n: "merge" is a keyword
1601 1601 getargs(x, 0, 0, _(b"merge takes no arguments"))
1602 1602 cl = repo.changelog
1603 1603
1604 1604 def ismerge(r):
1605 1605 try:
1606 1606 return cl.parentrevs(r)[1] != nullrev
1607 1607 except error.WdirUnsupported:
1608 1608 return bool(repo[r].p2())
1609 1609
1610 1610 return subset.filter(ismerge, condrepr=b'<merge>')
1611 1611
1612 1612
1613 1613 @predicate(b'branchpoint()', safe=True)
1614 1614 def branchpoint(repo, subset, x):
1615 1615 """Changesets with more than one child."""
1616 1616 # i18n: "branchpoint" is a keyword
1617 1617 getargs(x, 0, 0, _(b"branchpoint takes no arguments"))
1618 1618 cl = repo.changelog
1619 1619 if not subset:
1620 1620 return baseset()
1621 1621 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1622 1622 # (and if it is not, it should.)
1623 1623 baserev = min(subset)
1624 1624 parentscount = [0] * (len(repo) - baserev)
1625 1625 for r in cl.revs(start=baserev + 1):
1626 1626 for p in cl.parentrevs(r):
1627 1627 if p >= baserev:
1628 1628 parentscount[p - baserev] += 1
1629 1629 return subset.filter(
1630 1630 lambda r: parentscount[r - baserev] > 1, condrepr=b'<branchpoint>'
1631 1631 )
1632 1632
1633 1633
1634 1634 @predicate(b'min(set)', safe=True)
1635 1635 def minrev(repo, subset, x):
1636 1636 """Changeset with lowest revision number in set."""
1637 1637 os = getset(repo, fullreposet(repo), x)
1638 1638 try:
1639 1639 m = os.min()
1640 1640 if m in subset:
1641 1641 return baseset([m], datarepr=(b'<min %r, %r>', subset, os))
1642 1642 except ValueError:
1643 1643 # os.min() throws a ValueError when the collection is empty.
1644 1644 # Same as python's min().
1645 1645 pass
1646 1646 return baseset(datarepr=(b'<min %r, %r>', subset, os))
1647 1647
1648 1648
1649 1649 @predicate(b'modifies(pattern)', safe=True, weight=30)
1650 1650 def modifies(repo, subset, x):
1651 1651 """Changesets modifying files matched by pattern.
1652 1652
1653 1653 The pattern without explicit kind like ``glob:`` is expected to be
1654 1654 relative to the current directory and match against a file or a
1655 1655 directory.
1656 1656 """
1657 1657 # i18n: "modifies" is a keyword
1658 1658 pat = getstring(x, _(b"modifies requires a pattern"))
1659 1659 return checkstatus(repo, subset, pat, 'modified')
1660 1660
1661 1661
1662 1662 @predicate(b'named(namespace)')
1663 1663 def named(repo, subset, x):
1664 1664 """The changesets in a given namespace.
1665 1665
1666 1666 Pattern matching is supported for `namespace`. See
1667 1667 :hg:`help revisions.patterns`.
1668 1668 """
1669 1669 # i18n: "named" is a keyword
1670 1670 args = getargs(x, 1, 1, _(b'named requires a namespace argument'))
1671 1671
1672 1672 ns = getstring(
1673 1673 args[0],
1674 1674 # i18n: "named" is a keyword
1675 1675 _(b'the argument to named must be a string'),
1676 1676 )
1677 1677 kind, pattern, matcher = stringutil.stringmatcher(ns)
1678 1678 namespaces = set()
1679 1679 if kind == b'literal':
1680 1680 if pattern not in repo.names:
1681 1681 raise error.RepoLookupError(
1682 1682 _(b"namespace '%s' does not exist") % ns
1683 1683 )
1684 1684 namespaces.add(repo.names[pattern])
1685 1685 else:
1686 1686 for name, ns in pycompat.iteritems(repo.names):
1687 1687 if matcher(name):
1688 1688 namespaces.add(ns)
1689 1689
1690 1690 names = set()
1691 1691 for ns in namespaces:
1692 1692 for name in ns.listnames(repo):
1693 1693 if name not in ns.deprecated:
1694 1694 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1695 1695
1696 1696 names -= {nullrev}
1697 1697 return subset & names
1698 1698
1699 1699
1700 1700 @predicate(b'id(string)', safe=True)
1701 1701 def node_(repo, subset, x):
1702 1702 """Revision non-ambiguously specified by the given hex string prefix."""
1703 1703 # i18n: "id" is a keyword
1704 1704 l = getargs(x, 1, 1, _(b"id requires one argument"))
1705 1705 # i18n: "id" is a keyword
1706 1706 n = getstring(l[0], _(b"id requires a string"))
1707 1707 if len(n) == 40:
1708 1708 try:
1709 1709 rn = repo.changelog.rev(bin(n))
1710 1710 except error.WdirUnsupported:
1711 1711 rn = wdirrev
1712 1712 except (LookupError, TypeError):
1713 1713 rn = None
1714 1714 else:
1715 1715 rn = None
1716 1716 try:
1717 1717 pm = scmutil.resolvehexnodeidprefix(repo, n)
1718 1718 if pm is not None:
1719 1719 rn = repo.changelog.rev(pm)
1720 1720 except LookupError:
1721 1721 pass
1722 1722 except error.WdirUnsupported:
1723 1723 rn = wdirrev
1724 1724
1725 1725 if rn is None:
1726 1726 return baseset()
1727 1727 result = baseset([rn])
1728 1728 return result & subset
1729 1729
1730 1730
1731 1731 @predicate(b'none()', safe=True)
1732 1732 def none(repo, subset, x):
1733 1733 """No changesets."""
1734 1734 # i18n: "none" is a keyword
1735 1735 getargs(x, 0, 0, _(b"none takes no arguments"))
1736 1736 return baseset()
1737 1737
1738 1738
1739 1739 @predicate(b'obsolete()', safe=True)
1740 1740 def obsolete(repo, subset, x):
1741 1741 """Mutable changeset with a newer version. (EXPERIMENTAL)"""
1742 1742 # i18n: "obsolete" is a keyword
1743 1743 getargs(x, 0, 0, _(b"obsolete takes no arguments"))
1744 1744 obsoletes = obsmod.getrevs(repo, b'obsolete')
1745 1745 return subset & obsoletes
1746 1746
1747 1747
1748 1748 @predicate(b'only(set, [set])', safe=True)
1749 1749 def only(repo, subset, x):
1750 1750 """Changesets that are ancestors of the first set that are not ancestors
1751 1751 of any other head in the repo. If a second set is specified, the result
1752 1752 is ancestors of the first set that are not ancestors of the second set
1753 1753 (i.e. ::<set1> - ::<set2>).
1754 1754 """
1755 1755 cl = repo.changelog
1756 1756 # i18n: "only" is a keyword
1757 1757 args = getargs(x, 1, 2, _(b'only takes one or two arguments'))
1758 1758 include = getset(repo, fullreposet(repo), args[0])
1759 1759 if len(args) == 1:
1760 1760 if not include:
1761 1761 return baseset()
1762 1762
1763 1763 descendants = set(dagop.revdescendants(repo, include, False))
1764 1764 exclude = [
1765 1765 rev
1766 1766 for rev in cl.headrevs()
1767 1767 if not rev in descendants and not rev in include
1768 1768 ]
1769 1769 else:
1770 1770 exclude = getset(repo, fullreposet(repo), args[1])
1771 1771
1772 1772 results = set(cl.findmissingrevs(common=exclude, heads=include))
1773 1773 # XXX we should turn this into a baseset instead of a set, smartset may do
1774 1774 # some optimizations from the fact this is a baseset.
1775 1775 return subset & results
1776 1776
1777 1777
1778 1778 @predicate(b'origin([set])', safe=True)
1779 1779 def origin(repo, subset, x):
1780 1780 """
1781 1781 Changesets that were specified as a source for the grafts, transplants or
1782 1782 rebases that created the given revisions. Omitting the optional set is the
1783 1783 same as passing all(). If a changeset created by these operations is itself
1784 1784 specified as a source for one of these operations, only the source changeset
1785 1785 for the first operation is selected.
1786 1786 """
1787 1787 if x is not None:
1788 1788 dests = getset(repo, fullreposet(repo), x)
1789 1789 else:
1790 1790 dests = fullreposet(repo)
1791 1791
1792 1792 def _firstsrc(rev):
1793 1793 src = _getrevsource(repo, rev)
1794 1794 if src is None:
1795 1795 return None
1796 1796
1797 1797 while True:
1798 1798 prev = _getrevsource(repo, src)
1799 1799
1800 1800 if prev is None:
1801 1801 return src
1802 1802 src = prev
1803 1803
1804 1804 o = {_firstsrc(r) for r in dests}
1805 1805 o -= {None}
1806 1806 # XXX we should turn this into a baseset instead of a set, smartset may do
1807 1807 # some optimizations from the fact this is a baseset.
1808 1808 return subset & o
1809 1809
1810 1810
1811 1811 @predicate(b'outgoing([path])', safe=False, weight=10)
1812 1812 def outgoing(repo, subset, x):
1813 1813 """Changesets not found in the specified destination repository, or the
1814 1814 default push location.
1815 1815 """
1816 1816 # Avoid cycles.
1817 1817 from . import (
1818 1818 discovery,
1819 1819 hg,
1820 1820 )
1821 1821
1822 1822 # i18n: "outgoing" is a keyword
1823 1823 l = getargs(x, 0, 1, _(b"outgoing takes one or no arguments"))
1824 1824 # i18n: "outgoing" is a keyword
1825 1825 dest = (
1826 1826 l and getstring(l[0], _(b"outgoing requires a repository path")) or b''
1827 1827 )
1828 1828 if not dest:
1829 1829 # ui.paths.getpath() explicitly tests for None, not just a boolean
1830 1830 dest = None
1831 1831 path = repo.ui.paths.getpath(dest, default=(b'default-push', b'default'))
1832 1832 if not path:
1833 1833 raise error.Abort(
1834 1834 _(b'default repository not configured!'),
1835 1835 hint=_(b"see 'hg help config.paths'"),
1836 1836 )
1837 1837 dest = path.pushloc or path.loc
1838 1838 branches = path.branch, []
1839 1839
1840 1840 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1841 1841 if revs:
1842 1842 revs = [repo.lookup(rev) for rev in revs]
1843 1843 other = hg.peer(repo, {}, dest)
1844 repo.ui.pushbuffer()
1845 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1846 repo.ui.popbuffer()
1844 try:
1845 repo.ui.pushbuffer()
1846 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1847 repo.ui.popbuffer()
1848 finally:
1849 other.close()
1847 1850 cl = repo.changelog
1848 1851 o = {cl.rev(r) for r in outgoing.missing}
1849 1852 return subset & o
1850 1853
1851 1854
1852 1855 @predicate(b'p1([set])', safe=True)
1853 1856 def p1(repo, subset, x):
1854 1857 """First parent of changesets in set, or the working directory."""
1855 1858 if x is None:
1856 1859 p = repo[x].p1().rev()
1857 1860 if p >= 0:
1858 1861 return subset & baseset([p])
1859 1862 return baseset()
1860 1863
1861 1864 ps = set()
1862 1865 cl = repo.changelog
1863 1866 for r in getset(repo, fullreposet(repo), x):
1864 1867 try:
1865 1868 ps.add(cl.parentrevs(r)[0])
1866 1869 except error.WdirUnsupported:
1867 1870 ps.add(repo[r].p1().rev())
1868 1871 ps -= {nullrev}
1869 1872 # XXX we should turn this into a baseset instead of a set, smartset may do
1870 1873 # some optimizations from the fact this is a baseset.
1871 1874 return subset & ps
1872 1875
1873 1876
1874 1877 @predicate(b'p2([set])', safe=True)
1875 1878 def p2(repo, subset, x):
1876 1879 """Second parent of changesets in set, or the working directory."""
1877 1880 if x is None:
1878 1881 ps = repo[x].parents()
1879 1882 try:
1880 1883 p = ps[1].rev()
1881 1884 if p >= 0:
1882 1885 return subset & baseset([p])
1883 1886 return baseset()
1884 1887 except IndexError:
1885 1888 return baseset()
1886 1889
1887 1890 ps = set()
1888 1891 cl = repo.changelog
1889 1892 for r in getset(repo, fullreposet(repo), x):
1890 1893 try:
1891 1894 ps.add(cl.parentrevs(r)[1])
1892 1895 except error.WdirUnsupported:
1893 1896 parents = repo[r].parents()
1894 1897 if len(parents) == 2:
1895 1898 ps.add(parents[1])
1896 1899 ps -= {nullrev}
1897 1900 # XXX we should turn this into a baseset instead of a set, smartset may do
1898 1901 # some optimizations from the fact this is a baseset.
1899 1902 return subset & ps
1900 1903
1901 1904
1902 1905 def parentpost(repo, subset, x, order):
1903 1906 return p1(repo, subset, x)
1904 1907
1905 1908
1906 1909 @predicate(b'parents([set])', safe=True)
1907 1910 def parents(repo, subset, x):
1908 1911 """
1909 1912 The set of all parents for all changesets in set, or the working directory.
1910 1913 """
1911 1914 if x is None:
1912 1915 ps = {p.rev() for p in repo[x].parents()}
1913 1916 else:
1914 1917 ps = set()
1915 1918 cl = repo.changelog
1916 1919 up = ps.update
1917 1920 parentrevs = cl.parentrevs
1918 1921 for r in getset(repo, fullreposet(repo), x):
1919 1922 try:
1920 1923 up(parentrevs(r))
1921 1924 except error.WdirUnsupported:
1922 1925 up(p.rev() for p in repo[r].parents())
1923 1926 ps -= {nullrev}
1924 1927 return subset & ps
1925 1928
1926 1929
1927 1930 def _phase(repo, subset, *targets):
1928 1931 """helper to select all rev in <targets> phases"""
1929 1932 return repo._phasecache.getrevset(repo, targets, subset)
1930 1933
1931 1934
1932 1935 @predicate(b'_phase(idx)', safe=True)
1933 1936 def phase(repo, subset, x):
1934 1937 l = getargs(x, 1, 1, b"_phase requires one argument")
1935 1938 target = getinteger(l[0], b"_phase expects a number")
1936 1939 return _phase(repo, subset, target)
1937 1940
1938 1941
1939 1942 @predicate(b'draft()', safe=True)
1940 1943 def draft(repo, subset, x):
1941 1944 """Changeset in draft phase."""
1942 1945 # i18n: "draft" is a keyword
1943 1946 getargs(x, 0, 0, _(b"draft takes no arguments"))
1944 1947 target = phases.draft
1945 1948 return _phase(repo, subset, target)
1946 1949
1947 1950
1948 1951 @predicate(b'secret()', safe=True)
1949 1952 def secret(repo, subset, x):
1950 1953 """Changeset in secret phase."""
1951 1954 # i18n: "secret" is a keyword
1952 1955 getargs(x, 0, 0, _(b"secret takes no arguments"))
1953 1956 target = phases.secret
1954 1957 return _phase(repo, subset, target)
1955 1958
1956 1959
1957 1960 @predicate(b'stack([revs])', safe=True)
1958 1961 def stack(repo, subset, x):
1959 1962 """Experimental revset for the stack of changesets or working directory
1960 1963 parent. (EXPERIMENTAL)
1961 1964 """
1962 1965 if x is None:
1963 1966 stacks = stackmod.getstack(repo)
1964 1967 else:
1965 1968 stacks = smartset.baseset([])
1966 1969 for revision in getset(repo, fullreposet(repo), x):
1967 1970 currentstack = stackmod.getstack(repo, revision)
1968 1971 stacks = stacks + currentstack
1969 1972
1970 1973 return subset & stacks
1971 1974
1972 1975
1973 1976 def parentspec(repo, subset, x, n, order):
1974 1977 """``set^0``
1975 1978 The set.
1976 1979 ``set^1`` (or ``set^``), ``set^2``
1977 1980 First or second parent, respectively, of all changesets in set.
1978 1981 """
1979 1982 try:
1980 1983 n = int(n[1])
1981 1984 if n not in (0, 1, 2):
1982 1985 raise ValueError
1983 1986 except (TypeError, ValueError):
1984 1987 raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
1985 1988 ps = set()
1986 1989 cl = repo.changelog
1987 1990 for r in getset(repo, fullreposet(repo), x):
1988 1991 if n == 0:
1989 1992 ps.add(r)
1990 1993 elif n == 1:
1991 1994 try:
1992 1995 ps.add(cl.parentrevs(r)[0])
1993 1996 except error.WdirUnsupported:
1994 1997 ps.add(repo[r].p1().rev())
1995 1998 else:
1996 1999 try:
1997 2000 parents = cl.parentrevs(r)
1998 2001 if parents[1] != nullrev:
1999 2002 ps.add(parents[1])
2000 2003 except error.WdirUnsupported:
2001 2004 parents = repo[r].parents()
2002 2005 if len(parents) == 2:
2003 2006 ps.add(parents[1].rev())
2004 2007 return subset & ps
2005 2008
2006 2009
2007 2010 @predicate(b'present(set)', safe=True, takeorder=True)
2008 2011 def present(repo, subset, x, order):
2009 2012 """An empty set, if any revision in set isn't found; otherwise,
2010 2013 all revisions in set.
2011 2014
2012 2015 If any of specified revisions is not present in the local repository,
2013 2016 the query is normally aborted. But this predicate allows the query
2014 2017 to continue even in such cases.
2015 2018 """
2016 2019 try:
2017 2020 return getset(repo, subset, x, order)
2018 2021 except error.RepoLookupError:
2019 2022 return baseset()
2020 2023
2021 2024
2022 2025 # for internal use
2023 2026 @predicate(b'_notpublic', safe=True)
2024 2027 def _notpublic(repo, subset, x):
2025 2028 getargs(x, 0, 0, b"_notpublic takes no arguments")
2026 2029 return _phase(repo, subset, phases.draft, phases.secret)
2027 2030
2028 2031
2029 2032 # for internal use
2030 2033 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
2031 2034 def _phaseandancestors(repo, subset, x):
2032 2035 # equivalent to (phasename() & ancestors(set)) but more efficient
2033 2036 # phasename could be one of 'draft', 'secret', or '_notpublic'
2034 2037 args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
2035 2038 phasename = getsymbol(args[0])
2036 2039 s = getset(repo, fullreposet(repo), args[1])
2037 2040
2038 2041 draft = phases.draft
2039 2042 secret = phases.secret
2040 2043 phasenamemap = {
2041 2044 b'_notpublic': draft,
2042 2045 b'draft': draft, # follow secret's ancestors
2043 2046 b'secret': secret,
2044 2047 }
2045 2048 if phasename not in phasenamemap:
2046 2049 raise error.ParseError(b'%r is not a valid phasename' % phasename)
2047 2050
2048 2051 minimalphase = phasenamemap[phasename]
2049 2052 getphase = repo._phasecache.phase
2050 2053
2051 2054 def cutfunc(rev):
2052 2055 return getphase(repo, rev) < minimalphase
2053 2056
2054 2057 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
2055 2058
2056 2059 if phasename == b'draft': # need to remove secret changesets
2057 2060 revs = revs.filter(lambda r: getphase(repo, r) == draft)
2058 2061 return subset & revs
2059 2062
2060 2063
2061 2064 @predicate(b'public()', safe=True)
2062 2065 def public(repo, subset, x):
2063 2066 """Changeset in public phase."""
2064 2067 # i18n: "public" is a keyword
2065 2068 getargs(x, 0, 0, _(b"public takes no arguments"))
2066 2069 return _phase(repo, subset, phases.public)
2067 2070
2068 2071
2069 2072 @predicate(b'remote([id [,path]])', safe=False)
2070 2073 def remote(repo, subset, x):
2071 2074 """Local revision that corresponds to the given identifier in a
2072 2075 remote repository, if present. Here, the '.' identifier is a
2073 2076 synonym for the current local branch.
2074 2077 """
2075 2078
2076 2079 from . import hg # avoid start-up nasties
2077 2080
2078 2081 # i18n: "remote" is a keyword
2079 2082 l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
2080 2083
2081 2084 q = b'.'
2082 2085 if len(l) > 0:
2083 2086 # i18n: "remote" is a keyword
2084 2087 q = getstring(l[0], _(b"remote requires a string id"))
2085 2088 if q == b'.':
2086 2089 q = repo[b'.'].branch()
2087 2090
2088 2091 dest = b''
2089 2092 if len(l) > 1:
2090 2093 # i18n: "remote" is a keyword
2091 2094 dest = getstring(l[1], _(b"remote requires a repository path"))
2092 2095 dest = repo.ui.expandpath(dest or b'default')
2093 2096 dest, branches = hg.parseurl(dest)
2094 2097
2095 2098 other = hg.peer(repo, {}, dest)
2096 2099 n = other.lookup(q)
2097 2100 if n in repo:
2098 2101 r = repo[n].rev()
2099 2102 if r in subset:
2100 2103 return baseset([r])
2101 2104 return baseset()
2102 2105
2103 2106
2104 2107 @predicate(b'removes(pattern)', safe=True, weight=30)
2105 2108 def removes(repo, subset, x):
2106 2109 """Changesets which remove files matching pattern.
2107 2110
2108 2111 The pattern without explicit kind like ``glob:`` is expected to be
2109 2112 relative to the current directory and match against a file or a
2110 2113 directory.
2111 2114 """
2112 2115 # i18n: "removes" is a keyword
2113 2116 pat = getstring(x, _(b"removes requires a pattern"))
2114 2117 return checkstatus(repo, subset, pat, 'removed')
2115 2118
2116 2119
2117 2120 @predicate(b'rev(number)', safe=True)
2118 2121 def rev(repo, subset, x):
2119 2122 """Revision with the given numeric identifier."""
2120 2123 try:
2121 2124 return _rev(repo, subset, x)
2122 2125 except error.RepoLookupError:
2123 2126 return baseset()
2124 2127
2125 2128
2126 2129 @predicate(b'_rev(number)', safe=True)
2127 2130 def _rev(repo, subset, x):
2128 2131 # internal version of "rev(x)" that raise error if "x" is invalid
2129 2132 # i18n: "rev" is a keyword
2130 2133 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2131 2134 try:
2132 2135 # i18n: "rev" is a keyword
2133 2136 l = int(getstring(l[0], _(b"rev requires a number")))
2134 2137 except (TypeError, ValueError):
2135 2138 # i18n: "rev" is a keyword
2136 2139 raise error.ParseError(_(b"rev expects a number"))
2137 2140 if l not in _virtualrevs:
2138 2141 try:
2139 2142 repo.changelog.node(l) # check that the rev exists
2140 2143 except IndexError:
2141 2144 raise error.RepoLookupError(_(b"unknown revision '%d'") % l)
2142 2145 return subset & baseset([l])
2143 2146
2144 2147
2145 2148 @predicate(b'revset(set)', safe=True, takeorder=True)
2146 2149 def revsetpredicate(repo, subset, x, order):
2147 2150 """Strictly interpret the content as a revset.
2148 2151
2149 2152 The content of this special predicate will be strictly interpreted as a
2150 2153 revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
2151 2154 without possible ambiguity with a "id(0)" bookmark or tag.
2152 2155 """
2153 2156 return getset(repo, subset, x, order)
2154 2157
2155 2158
2156 2159 @predicate(b'matching(revision [, field])', safe=True)
2157 2160 def matching(repo, subset, x):
2158 2161 """Changesets in which a given set of fields match the set of fields in the
2159 2162 selected revision or set.
2160 2163
2161 2164 To match more than one field pass the list of fields to match separated
2162 2165 by spaces (e.g. ``author description``).
2163 2166
2164 2167 Valid fields are most regular revision fields and some special fields.
2165 2168
2166 2169 Regular revision fields are ``description``, ``author``, ``branch``,
2167 2170 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
2168 2171 and ``diff``.
2169 2172 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
2170 2173 contents of the revision. Two revisions matching their ``diff`` will
2171 2174 also match their ``files``.
2172 2175
2173 2176 Special fields are ``summary`` and ``metadata``:
2174 2177 ``summary`` matches the first line of the description.
2175 2178 ``metadata`` is equivalent to matching ``description user date``
2176 2179 (i.e. it matches the main metadata fields).
2177 2180
2178 2181 ``metadata`` is the default field which is used when no fields are
2179 2182 specified. You can match more than one field at a time.
2180 2183 """
2181 2184 # i18n: "matching" is a keyword
2182 2185 l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
2183 2186
2184 2187 revs = getset(repo, fullreposet(repo), l[0])
2185 2188
2186 2189 fieldlist = [b'metadata']
2187 2190 if len(l) > 1:
2188 2191 fieldlist = getstring(
2189 2192 l[1],
2190 2193 # i18n: "matching" is a keyword
2191 2194 _(b"matching requires a string as its second argument"),
2192 2195 ).split()
2193 2196
2194 2197 # Make sure that there are no repeated fields,
2195 2198 # expand the 'special' 'metadata' field type
2196 2199 # and check the 'files' whenever we check the 'diff'
2197 2200 fields = []
2198 2201 for field in fieldlist:
2199 2202 if field == b'metadata':
2200 2203 fields += [b'user', b'description', b'date']
2201 2204 elif field == b'diff':
2202 2205 # a revision matching the diff must also match the files
2203 2206 # since matching the diff is very costly, make sure to
2204 2207 # also match the files first
2205 2208 fields += [b'files', b'diff']
2206 2209 else:
2207 2210 if field == b'author':
2208 2211 field = b'user'
2209 2212 fields.append(field)
2210 2213 fields = set(fields)
2211 2214 if b'summary' in fields and b'description' in fields:
2212 2215 # If a revision matches its description it also matches its summary
2213 2216 fields.discard(b'summary')
2214 2217
2215 2218 # We may want to match more than one field
2216 2219 # Not all fields take the same amount of time to be matched
2217 2220 # Sort the selected fields in order of increasing matching cost
2218 2221 fieldorder = [
2219 2222 b'phase',
2220 2223 b'parents',
2221 2224 b'user',
2222 2225 b'date',
2223 2226 b'branch',
2224 2227 b'summary',
2225 2228 b'files',
2226 2229 b'description',
2227 2230 b'substate',
2228 2231 b'diff',
2229 2232 ]
2230 2233
2231 2234 def fieldkeyfunc(f):
2232 2235 try:
2233 2236 return fieldorder.index(f)
2234 2237 except ValueError:
2235 2238 # assume an unknown field is very costly
2236 2239 return len(fieldorder)
2237 2240
2238 2241 fields = list(fields)
2239 2242 fields.sort(key=fieldkeyfunc)
2240 2243
2241 2244 # Each field will be matched with its own "getfield" function
2242 2245 # which will be added to the getfieldfuncs array of functions
2243 2246 getfieldfuncs = []
2244 2247 _funcs = {
2245 2248 b'user': lambda r: repo[r].user(),
2246 2249 b'branch': lambda r: repo[r].branch(),
2247 2250 b'date': lambda r: repo[r].date(),
2248 2251 b'description': lambda r: repo[r].description(),
2249 2252 b'files': lambda r: repo[r].files(),
2250 2253 b'parents': lambda r: repo[r].parents(),
2251 2254 b'phase': lambda r: repo[r].phase(),
2252 2255 b'substate': lambda r: repo[r].substate,
2253 2256 b'summary': lambda r: repo[r].description().splitlines()[0],
2254 2257 b'diff': lambda r: list(
2255 2258 repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
2256 2259 ),
2257 2260 }
2258 2261 for info in fields:
2259 2262 getfield = _funcs.get(info, None)
2260 2263 if getfield is None:
2261 2264 raise error.ParseError(
2262 2265 # i18n: "matching" is a keyword
2263 2266 _(b"unexpected field name passed to matching: %s")
2264 2267 % info
2265 2268 )
2266 2269 getfieldfuncs.append(getfield)
2267 2270 # convert the getfield array of functions into a "getinfo" function
2268 2271 # which returns an array of field values (or a single value if there
2269 2272 # is only one field to match)
2270 2273 getinfo = lambda r: [f(r) for f in getfieldfuncs]
2271 2274
2272 2275 def matches(x):
2273 2276 for rev in revs:
2274 2277 target = getinfo(rev)
2275 2278 match = True
2276 2279 for n, f in enumerate(getfieldfuncs):
2277 2280 if target[n] != f(x):
2278 2281 match = False
2279 2282 if match:
2280 2283 return True
2281 2284 return False
2282 2285
2283 2286 return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
2284 2287
2285 2288
2286 2289 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
2287 2290 def reverse(repo, subset, x, order):
2288 2291 """Reverse order of set."""
2289 2292 l = getset(repo, subset, x, order)
2290 2293 if order == defineorder:
2291 2294 l.reverse()
2292 2295 return l
2293 2296
2294 2297
2295 2298 @predicate(b'roots(set)', safe=True)
2296 2299 def roots(repo, subset, x):
2297 2300 """Changesets in set with no parent changeset in set."""
2298 2301 s = getset(repo, fullreposet(repo), x)
2299 2302 parents = repo.changelog.parentrevs
2300 2303
2301 2304 def filter(r):
2302 2305 for p in parents(r):
2303 2306 if 0 <= p and p in s:
2304 2307 return False
2305 2308 return True
2306 2309
2307 2310 return subset & s.filter(filter, condrepr=b'<roots>')
2308 2311
2309 2312
2310 2313 _sortkeyfuncs = {
2311 2314 b'rev': scmutil.intrev,
2312 2315 b'branch': lambda c: c.branch(),
2313 2316 b'desc': lambda c: c.description(),
2314 2317 b'user': lambda c: c.user(),
2315 2318 b'author': lambda c: c.user(),
2316 2319 b'date': lambda c: c.date()[0],
2317 2320 b'node': scmutil.binnode,
2318 2321 }
2319 2322
2320 2323
2321 2324 def _getsortargs(x):
2322 2325 """Parse sort options into (set, [(key, reverse)], opts)"""
2323 2326 args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
2324 2327 if b'set' not in args:
2325 2328 # i18n: "sort" is a keyword
2326 2329 raise error.ParseError(_(b'sort requires one or two arguments'))
2327 2330 keys = b"rev"
2328 2331 if b'keys' in args:
2329 2332 # i18n: "sort" is a keyword
2330 2333 keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
2331 2334
2332 2335 keyflags = []
2333 2336 for k in keys.split():
2334 2337 fk = k
2335 2338 reverse = k.startswith(b'-')
2336 2339 if reverse:
2337 2340 k = k[1:]
2338 2341 if k not in _sortkeyfuncs and k != b'topo':
2339 2342 raise error.ParseError(
2340 2343 _(b"unknown sort key %r") % pycompat.bytestr(fk)
2341 2344 )
2342 2345 keyflags.append((k, reverse))
2343 2346
2344 2347 if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
2345 2348 # i18n: "topo" is a keyword
2346 2349 raise error.ParseError(
2347 2350 _(b'topo sort order cannot be combined with other sort keys')
2348 2351 )
2349 2352
2350 2353 opts = {}
2351 2354 if b'topo.firstbranch' in args:
2352 2355 if any(k == b'topo' for k, reverse in keyflags):
2353 2356 opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
2354 2357 else:
2355 2358 # i18n: "topo" and "topo.firstbranch" are keywords
2356 2359 raise error.ParseError(
2357 2360 _(
2358 2361 b'topo.firstbranch can only be used '
2359 2362 b'when using the topo sort key'
2360 2363 )
2361 2364 )
2362 2365
2363 2366 return args[b'set'], keyflags, opts
2364 2367
2365 2368
2366 2369 @predicate(
2367 2370 b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
2368 2371 )
2369 2372 def sort(repo, subset, x, order):
2370 2373 """Sort set by keys. The default sort order is ascending, specify a key
2371 2374 as ``-key`` to sort in descending order.
2372 2375
2373 2376 The keys can be:
2374 2377
2375 2378 - ``rev`` for the revision number,
2376 2379 - ``branch`` for the branch name,
2377 2380 - ``desc`` for the commit message (description),
2378 2381 - ``user`` for user name (``author`` can be used as an alias),
2379 2382 - ``date`` for the commit date
2380 2383 - ``topo`` for a reverse topographical sort
2381 2384 - ``node`` the nodeid of the revision
2382 2385
2383 2386 The ``topo`` sort order cannot be combined with other sort keys. This sort
2384 2387 takes one optional argument, ``topo.firstbranch``, which takes a revset that
2385 2388 specifies what topographical branches to prioritize in the sort.
2386 2389
2387 2390 """
2388 2391 s, keyflags, opts = _getsortargs(x)
2389 2392 revs = getset(repo, subset, s, order)
2390 2393
2391 2394 if not keyflags or order != defineorder:
2392 2395 return revs
2393 2396 if len(keyflags) == 1 and keyflags[0][0] == b"rev":
2394 2397 revs.sort(reverse=keyflags[0][1])
2395 2398 return revs
2396 2399 elif keyflags[0][0] == b"topo":
2397 2400 firstbranch = ()
2398 2401 if b'topo.firstbranch' in opts:
2399 2402 firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
2400 2403 revs = baseset(
2401 2404 dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
2402 2405 istopo=True,
2403 2406 )
2404 2407 if keyflags[0][1]:
2405 2408 revs.reverse()
2406 2409 return revs
2407 2410
2408 2411 # sort() is guaranteed to be stable
2409 2412 ctxs = [repo[r] for r in revs]
2410 2413 for k, reverse in reversed(keyflags):
2411 2414 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
2412 2415 return baseset([c.rev() for c in ctxs])
2413 2416
2414 2417
2415 2418 @predicate(b'subrepo([pattern])')
2416 2419 def subrepo(repo, subset, x):
2417 2420 """Changesets that add, modify or remove the given subrepo. If no subrepo
2418 2421 pattern is named, any subrepo changes are returned.
2419 2422 """
2420 2423 # i18n: "subrepo" is a keyword
2421 2424 args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
2422 2425 pat = None
2423 2426 if len(args) != 0:
2424 2427 pat = getstring(args[0], _(b"subrepo requires a pattern"))
2425 2428
2426 2429 m = matchmod.exact([b'.hgsubstate'])
2427 2430
2428 2431 def submatches(names):
2429 2432 k, p, m = stringutil.stringmatcher(pat)
2430 2433 for name in names:
2431 2434 if m(name):
2432 2435 yield name
2433 2436
2434 2437 def matches(x):
2435 2438 c = repo[x]
2436 2439 s = repo.status(c.p1().node(), c.node(), match=m)
2437 2440
2438 2441 if pat is None:
2439 2442 return s.added or s.modified or s.removed
2440 2443
2441 2444 if s.added:
2442 2445 return any(submatches(c.substate.keys()))
2443 2446
2444 2447 if s.modified:
2445 2448 subs = set(c.p1().substate.keys())
2446 2449 subs.update(c.substate.keys())
2447 2450
2448 2451 for path in submatches(subs):
2449 2452 if c.p1().substate.get(path) != c.substate.get(path):
2450 2453 return True
2451 2454
2452 2455 if s.removed:
2453 2456 return any(submatches(c.p1().substate.keys()))
2454 2457
2455 2458 return False
2456 2459
2457 2460 return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
2458 2461
2459 2462
2460 2463 def _mapbynodefunc(repo, s, f):
2461 2464 """(repo, smartset, [node] -> [node]) -> smartset
2462 2465
2463 2466 Helper method to map a smartset to another smartset given a function only
2464 2467 talking about nodes. Handles converting between rev numbers and nodes, and
2465 2468 filtering.
2466 2469 """
2467 2470 cl = repo.unfiltered().changelog
2468 2471 torev = cl.index.get_rev
2469 2472 tonode = cl.node
2470 2473 result = {torev(n) for n in f(tonode(r) for r in s)}
2471 2474 result.discard(None)
2472 2475 return smartset.baseset(result - repo.changelog.filteredrevs)
2473 2476
2474 2477
2475 2478 @predicate(b'successors(set)', safe=True)
2476 2479 def successors(repo, subset, x):
2477 2480 """All successors for set, including the given set themselves.
2478 2481 (EXPERIMENTAL)"""
2479 2482 s = getset(repo, fullreposet(repo), x)
2480 2483 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2481 2484 d = _mapbynodefunc(repo, s, f)
2482 2485 return subset & d
2483 2486
2484 2487
2485 2488 def _substringmatcher(pattern, casesensitive=True):
2486 2489 kind, pattern, matcher = stringutil.stringmatcher(
2487 2490 pattern, casesensitive=casesensitive
2488 2491 )
2489 2492 if kind == b'literal':
2490 2493 if not casesensitive:
2491 2494 pattern = encoding.lower(pattern)
2492 2495 matcher = lambda s: pattern in encoding.lower(s)
2493 2496 else:
2494 2497 matcher = lambda s: pattern in s
2495 2498 return kind, pattern, matcher
2496 2499
2497 2500
2498 2501 @predicate(b'tag([name])', safe=True)
2499 2502 def tag(repo, subset, x):
2500 2503 """The specified tag by name, or all tagged revisions if no name is given.
2501 2504
2502 2505 Pattern matching is supported for `name`. See
2503 2506 :hg:`help revisions.patterns`.
2504 2507 """
2505 2508 # i18n: "tag" is a keyword
2506 2509 args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
2507 2510 cl = repo.changelog
2508 2511 if args:
2509 2512 pattern = getstring(
2510 2513 args[0],
2511 2514 # i18n: "tag" is a keyword
2512 2515 _(b'the argument to tag must be a string'),
2513 2516 )
2514 2517 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2515 2518 if kind == b'literal':
2516 2519 # avoid resolving all tags
2517 2520 tn = repo._tagscache.tags.get(pattern, None)
2518 2521 if tn is None:
2519 2522 raise error.RepoLookupError(
2520 2523 _(b"tag '%s' does not exist") % pattern
2521 2524 )
2522 2525 s = {repo[tn].rev()}
2523 2526 else:
2524 2527 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2525 2528 else:
2526 2529 s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
2527 2530 return subset & s
2528 2531
2529 2532
2530 2533 @predicate(b'tagged', safe=True)
2531 2534 def tagged(repo, subset, x):
2532 2535 return tag(repo, subset, x)
2533 2536
2534 2537
2535 2538 @predicate(b'orphan()', safe=True)
2536 2539 def orphan(repo, subset, x):
2537 2540 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)"""
2538 2541 # i18n: "orphan" is a keyword
2539 2542 getargs(x, 0, 0, _(b"orphan takes no arguments"))
2540 2543 orphan = obsmod.getrevs(repo, b'orphan')
2541 2544 return subset & orphan
2542 2545
2543 2546
2544 2547 @predicate(b'unstable()', safe=True)
2545 2548 def unstable(repo, subset, x):
2546 2549 """Changesets with instabilities. (EXPERIMENTAL)"""
2547 2550 # i18n: "unstable" is a keyword
2548 2551 getargs(x, 0, 0, b'unstable takes no arguments')
2549 2552 _unstable = set()
2550 2553 _unstable.update(obsmod.getrevs(repo, b'orphan'))
2551 2554 _unstable.update(obsmod.getrevs(repo, b'phasedivergent'))
2552 2555 _unstable.update(obsmod.getrevs(repo, b'contentdivergent'))
2553 2556 return subset & baseset(_unstable)
2554 2557
2555 2558
2556 2559 @predicate(b'user(string)', safe=True, weight=10)
2557 2560 def user(repo, subset, x):
2558 2561 """User name contains string. The match is case-insensitive.
2559 2562
2560 2563 Pattern matching is supported for `string`. See
2561 2564 :hg:`help revisions.patterns`.
2562 2565 """
2563 2566 return author(repo, subset, x)
2564 2567
2565 2568
2566 2569 @predicate(b'wdir()', safe=True, weight=0)
2567 2570 def wdir(repo, subset, x):
2568 2571 """Working directory. (EXPERIMENTAL)"""
2569 2572 # i18n: "wdir" is a keyword
2570 2573 getargs(x, 0, 0, _(b"wdir takes no arguments"))
2571 2574 if wdirrev in subset or isinstance(subset, fullreposet):
2572 2575 return baseset([wdirrev])
2573 2576 return baseset()
2574 2577
2575 2578
2576 2579 def _orderedlist(repo, subset, x):
2577 2580 s = getstring(x, b"internal error")
2578 2581 if not s:
2579 2582 return baseset()
2580 2583 # remove duplicates here. it's difficult for caller to deduplicate sets
2581 2584 # because different symbols can point to the same rev.
2582 2585 cl = repo.changelog
2583 2586 ls = []
2584 2587 seen = set()
2585 2588 for t in s.split(b'\0'):
2586 2589 try:
2587 2590 # fast path for integer revision
2588 2591 r = int(t)
2589 2592 if (b'%d' % r) != t or r not in cl:
2590 2593 raise ValueError
2591 2594 revs = [r]
2592 2595 except ValueError:
2593 2596 revs = stringset(repo, subset, t, defineorder)
2594 2597
2595 2598 for r in revs:
2596 2599 if r in seen:
2597 2600 continue
2598 2601 if (
2599 2602 r in subset
2600 2603 or r in _virtualrevs
2601 2604 and isinstance(subset, fullreposet)
2602 2605 ):
2603 2606 ls.append(r)
2604 2607 seen.add(r)
2605 2608 return baseset(ls)
2606 2609
2607 2610
2608 2611 # for internal use
2609 2612 @predicate(b'_list', safe=True, takeorder=True)
2610 2613 def _list(repo, subset, x, order):
2611 2614 if order == followorder:
2612 2615 # slow path to take the subset order
2613 2616 return subset & _orderedlist(repo, fullreposet(repo), x)
2614 2617 else:
2615 2618 return _orderedlist(repo, subset, x)
2616 2619
2617 2620
2618 2621 def _orderedintlist(repo, subset, x):
2619 2622 s = getstring(x, b"internal error")
2620 2623 if not s:
2621 2624 return baseset()
2622 2625 ls = [int(r) for r in s.split(b'\0')]
2623 2626 s = subset
2624 2627 return baseset([r for r in ls if r in s])
2625 2628
2626 2629
2627 2630 # for internal use
2628 2631 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
2629 2632 def _intlist(repo, subset, x, order):
2630 2633 if order == followorder:
2631 2634 # slow path to take the subset order
2632 2635 return subset & _orderedintlist(repo, fullreposet(repo), x)
2633 2636 else:
2634 2637 return _orderedintlist(repo, subset, x)
2635 2638
2636 2639
2637 2640 def _orderedhexlist(repo, subset, x):
2638 2641 s = getstring(x, b"internal error")
2639 2642 if not s:
2640 2643 return baseset()
2641 2644 cl = repo.changelog
2642 2645 ls = [cl.rev(bin(r)) for r in s.split(b'\0')]
2643 2646 s = subset
2644 2647 return baseset([r for r in ls if r in s])
2645 2648
2646 2649
2647 2650 # for internal use
2648 2651 @predicate(b'_hexlist', safe=True, takeorder=True)
2649 2652 def _hexlist(repo, subset, x, order):
2650 2653 if order == followorder:
2651 2654 # slow path to take the subset order
2652 2655 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2653 2656 else:
2654 2657 return _orderedhexlist(repo, subset, x)
2655 2658
2656 2659
2657 2660 methods = {
2658 2661 b"range": rangeset,
2659 2662 b"rangeall": rangeall,
2660 2663 b"rangepre": rangepre,
2661 2664 b"rangepost": rangepost,
2662 2665 b"dagrange": dagrange,
2663 2666 b"string": stringset,
2664 2667 b"symbol": stringset,
2665 2668 b"and": andset,
2666 2669 b"andsmally": andsmallyset,
2667 2670 b"or": orset,
2668 2671 b"not": notset,
2669 2672 b"difference": differenceset,
2670 2673 b"relation": relationset,
2671 2674 b"relsubscript": relsubscriptset,
2672 2675 b"subscript": subscriptset,
2673 2676 b"list": listset,
2674 2677 b"keyvalue": keyvaluepair,
2675 2678 b"func": func,
2676 2679 b"ancestor": ancestorspec,
2677 2680 b"parent": parentspec,
2678 2681 b"parentpost": parentpost,
2679 2682 b"smartset": rawsmartset,
2680 2683 }
2681 2684
2682 2685 relations = {
2683 2686 b"g": generationsrel,
2684 2687 b"generations": generationsrel,
2685 2688 }
2686 2689
2687 2690 subscriptrelations = {
2688 2691 b"g": generationssubrel,
2689 2692 b"generations": generationssubrel,
2690 2693 }
2691 2694
2692 2695
2693 2696 def lookupfn(repo):
2694 2697 def fn(symbol):
2695 2698 try:
2696 2699 return scmutil.isrevsymbol(repo, symbol)
2697 2700 except error.AmbiguousPrefixLookupError:
2698 2701 raise error.InputError(
2699 2702 b'ambiguous revision identifier: %s' % symbol
2700 2703 )
2701 2704
2702 2705 return fn
2703 2706
2704 2707
2705 2708 def match(ui, spec, lookup=None):
2706 2709 """Create a matcher for a single revision spec"""
2707 2710 return matchany(ui, [spec], lookup=lookup)
2708 2711
2709 2712
2710 2713 def matchany(ui, specs, lookup=None, localalias=None):
2711 2714 """Create a matcher that will include any revisions matching one of the
2712 2715 given specs
2713 2716
2714 2717 If lookup function is not None, the parser will first attempt to handle
2715 2718 old-style ranges, which may contain operator characters.
2716 2719
2717 2720 If localalias is not None, it is a dict {name: definitionstring}. It takes
2718 2721 precedence over [revsetalias] config section.
2719 2722 """
2720 2723 if not specs:
2721 2724
2722 2725 def mfunc(repo, subset=None):
2723 2726 return baseset()
2724 2727
2725 2728 return mfunc
2726 2729 if not all(specs):
2727 2730 raise error.ParseError(_(b"empty query"))
2728 2731 if len(specs) == 1:
2729 2732 tree = revsetlang.parse(specs[0], lookup)
2730 2733 else:
2731 2734 tree = (
2732 2735 b'or',
2733 2736 (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
2734 2737 )
2735 2738
2736 2739 aliases = []
2737 2740 warn = None
2738 2741 if ui:
2739 2742 aliases.extend(ui.configitems(b'revsetalias'))
2740 2743 warn = ui.warn
2741 2744 if localalias:
2742 2745 aliases.extend(localalias.items())
2743 2746 if aliases:
2744 2747 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2745 2748 tree = revsetlang.foldconcat(tree)
2746 2749 tree = revsetlang.analyze(tree)
2747 2750 tree = revsetlang.optimize(tree)
2748 2751 return makematcher(tree)
2749 2752
2750 2753
2751 2754 def makematcher(tree):
2752 2755 """Create a matcher from an evaluatable tree"""
2753 2756
2754 2757 def mfunc(repo, subset=None, order=None):
2755 2758 if order is None:
2756 2759 if subset is None:
2757 2760 order = defineorder # 'x'
2758 2761 else:
2759 2762 order = followorder # 'subset & x'
2760 2763 if subset is None:
2761 2764 subset = fullreposet(repo)
2762 2765 return getset(repo, subset, tree, order)
2763 2766
2764 2767 return mfunc
2765 2768
2766 2769
2767 2770 def loadpredicate(ui, extname, registrarobj):
2768 2771 """Load revset predicates from specified registrarobj"""
2769 2772 for name, func in pycompat.iteritems(registrarobj._table):
2770 2773 symbols[name] = func
2771 2774 if func._safe:
2772 2775 safesymbols.add(name)
2773 2776
2774 2777
2775 2778 # load built-in predicates explicitly to setup safesymbols
2776 2779 loadpredicate(None, None, predicate)
2777 2780
2778 2781 # tell hggettext to extract docstrings from these functions:
2779 2782 i18nfunctions = symbols.values()
@@ -1,728 +1,725 b''
1 1 # sshpeer.py - ssh repository proxy class for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import re
11 11 import uuid
12 12
13 13 from .i18n import _
14 14 from .pycompat import getattr
15 15 from . import (
16 16 error,
17 17 pycompat,
18 18 util,
19 19 wireprotoserver,
20 20 wireprototypes,
21 21 wireprotov1peer,
22 22 wireprotov1server,
23 23 )
24 24 from .utils import (
25 25 procutil,
26 26 stringutil,
27 27 )
28 28
29 29
30 30 def _serverquote(s):
31 31 """quote a string for the remote shell ... which we assume is sh"""
32 32 if not s:
33 33 return s
34 34 if re.match(b'[a-zA-Z0-9@%_+=:,./-]*$', s):
35 35 return s
36 36 return b"'%s'" % s.replace(b"'", b"'\\''")
37 37
38 38
39 39 def _forwardoutput(ui, pipe, warn=False):
40 40 """display all data currently available on pipe as remote output.
41 41
42 42 This is non blocking."""
43 43 if pipe:
44 44 s = procutil.readpipe(pipe)
45 45 if s:
46 46 display = ui.warn if warn else ui.status
47 47 for l in s.splitlines():
48 48 display(_(b"remote: "), l, b'\n')
49 49
50 50
51 51 class doublepipe(object):
52 52 """Operate a side-channel pipe in addition of a main one
53 53
54 54 The side-channel pipe contains server output to be forwarded to the user
55 55 input. The double pipe will behave as the "main" pipe, but will ensure the
56 56 content of the "side" pipe is properly processed while we wait for blocking
57 57 call on the "main" pipe.
58 58
59 59 If large amounts of data are read from "main", the forward will cease after
60 60 the first bytes start to appear. This simplifies the implementation
61 61 without affecting actual output of sshpeer too much as we rarely issue
62 62 large read for data not yet emitted by the server.
63 63
64 64 The main pipe is expected to be a 'bufferedinputpipe' from the util module
65 65 that handle all the os specific bits. This class lives in this module
66 66 because it focus on behavior specific to the ssh protocol."""
67 67
68 68 def __init__(self, ui, main, side):
69 69 self._ui = ui
70 70 self._main = main
71 71 self._side = side
72 72
73 73 def _wait(self):
74 74 """wait until some data are available on main or side
75 75
76 76 return a pair of boolean (ismainready, issideready)
77 77
78 78 (This will only wait for data if the setup is supported by `util.poll`)
79 79 """
80 80 if (
81 81 isinstance(self._main, util.bufferedinputpipe)
82 82 and self._main.hasbuffer
83 83 ):
84 84 # Main has data. Assume side is worth poking at.
85 85 return True, True
86 86
87 87 fds = [self._main.fileno(), self._side.fileno()]
88 88 try:
89 89 act = util.poll(fds)
90 90 except NotImplementedError:
91 91 # non supported yet case, assume all have data.
92 92 act = fds
93 93 return (self._main.fileno() in act, self._side.fileno() in act)
94 94
95 95 def write(self, data):
96 96 return self._call(b'write', data)
97 97
98 98 def read(self, size):
99 99 r = self._call(b'read', size)
100 100 if size != 0 and not r:
101 101 # We've observed a condition that indicates the
102 102 # stdout closed unexpectedly. Check stderr one
103 103 # more time and snag anything that's there before
104 104 # letting anyone know the main part of the pipe
105 105 # closed prematurely.
106 106 _forwardoutput(self._ui, self._side)
107 107 return r
108 108
109 109 def unbufferedread(self, size):
110 110 r = self._call(b'unbufferedread', size)
111 111 if size != 0 and not r:
112 112 # We've observed a condition that indicates the
113 113 # stdout closed unexpectedly. Check stderr one
114 114 # more time and snag anything that's there before
115 115 # letting anyone know the main part of the pipe
116 116 # closed prematurely.
117 117 _forwardoutput(self._ui, self._side)
118 118 return r
119 119
120 120 def readline(self):
121 121 return self._call(b'readline')
122 122
123 123 def _call(self, methname, data=None):
124 124 """call <methname> on "main", forward output of "side" while blocking"""
125 125 # data can be '' or 0
126 126 if (data is not None and not data) or self._main.closed:
127 127 _forwardoutput(self._ui, self._side)
128 128 return b''
129 129 while True:
130 130 mainready, sideready = self._wait()
131 131 if sideready:
132 132 _forwardoutput(self._ui, self._side)
133 133 if mainready:
134 134 meth = getattr(self._main, methname)
135 135 if data is None:
136 136 return meth()
137 137 else:
138 138 return meth(data)
139 139
140 140 def close(self):
141 141 return self._main.close()
142 142
143 143 @property
144 144 def closed(self):
145 145 return self._main.closed
146 146
147 147 def flush(self):
148 148 return self._main.flush()
149 149
150 150
151 151 def _cleanuppipes(ui, pipei, pipeo, pipee, warn):
152 152 """Clean up pipes used by an SSH connection."""
153 153 didsomething = False
154 154 if pipeo and not pipeo.closed:
155 155 didsomething = True
156 156 pipeo.close()
157 157 if pipei and not pipei.closed:
158 158 didsomething = True
159 159 pipei.close()
160 160
161 161 if pipee and not pipee.closed:
162 162 didsomething = True
163 163 # Try to read from the err descriptor until EOF.
164 164 try:
165 165 for l in pipee:
166 166 ui.status(_(b'remote: '), l)
167 167 except (IOError, ValueError):
168 168 pass
169 169
170 170 pipee.close()
171 171
172 172 if didsomething and warn is not None:
173 173 # Encourage explicit close of sshpeers. Closing via __del__ is
174 174 # not very predictable when exceptions are thrown, which has led
175 175 # to deadlocks due to a peer get gc'ed in a fork
176 176 # We add our own stack trace, because the stacktrace when called
177 177 # from __del__ is useless.
178 if False: # enabled in next commit
179 ui.develwarn(
180 b'missing close on SSH connection created at:\n%s' % warn
181 )
178 ui.develwarn(b'missing close on SSH connection created at:\n%s' % warn)
182 179
183 180
184 181 def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
185 182 """Create an SSH connection to a server.
186 183
187 184 Returns a tuple of (process, stdin, stdout, stderr) for the
188 185 spawned process.
189 186 """
190 187 cmd = b'%s %s %s' % (
191 188 sshcmd,
192 189 args,
193 190 procutil.shellquote(
194 191 b'%s -R %s serve --stdio'
195 192 % (_serverquote(remotecmd), _serverquote(path))
196 193 ),
197 194 )
198 195
199 196 ui.debug(b'running %s\n' % cmd)
200 197
201 198 # no buffer allow the use of 'select'
202 199 # feel free to remove buffering and select usage when we ultimately
203 200 # move to threading.
204 201 stdin, stdout, stderr, proc = procutil.popen4(cmd, bufsize=0, env=sshenv)
205 202
206 203 return proc, stdin, stdout, stderr
207 204
208 205
209 206 def _clientcapabilities():
210 207 """Return list of capabilities of this client.
211 208
212 209 Returns a list of capabilities that are supported by this client.
213 210 """
214 211 protoparams = {b'partial-pull'}
215 212 comps = [
216 213 e.wireprotosupport().name
217 214 for e in util.compengines.supportedwireengines(util.CLIENTROLE)
218 215 ]
219 216 protoparams.add(b'comp=%s' % b','.join(comps))
220 217 return protoparams
221 218
222 219
223 220 def _performhandshake(ui, stdin, stdout, stderr):
224 221 def badresponse():
225 222 # Flush any output on stderr. In general, the stderr contains errors
226 223 # from the remote (ssh errors, some hg errors), and status indications
227 224 # (like "adding changes"), with no current way to tell them apart.
228 225 # Here we failed so early that it's almost certainly only errors, so
229 226 # use warn=True so -q doesn't hide them.
230 227 _forwardoutput(ui, stderr, warn=True)
231 228
232 229 msg = _(b'no suitable response from remote hg')
233 230 hint = ui.config(b'ui', b'ssherrorhint')
234 231 raise error.RepoError(msg, hint=hint)
235 232
236 233 # The handshake consists of sending wire protocol commands in reverse
237 234 # order of protocol implementation and then sniffing for a response
238 235 # to one of them.
239 236 #
240 237 # Those commands (from oldest to newest) are:
241 238 #
242 239 # ``between``
243 240 # Asks for the set of revisions between a pair of revisions. Command
244 241 # present in all Mercurial server implementations.
245 242 #
246 243 # ``hello``
247 244 # Instructs the server to advertise its capabilities. Introduced in
248 245 # Mercurial 0.9.1.
249 246 #
250 247 # ``upgrade``
251 248 # Requests upgrade from default transport protocol version 1 to
252 249 # a newer version. Introduced in Mercurial 4.6 as an experimental
253 250 # feature.
254 251 #
255 252 # The ``between`` command is issued with a request for the null
256 253 # range. If the remote is a Mercurial server, this request will
257 254 # generate a specific response: ``1\n\n``. This represents the
258 255 # wire protocol encoded value for ``\n``. We look for ``1\n\n``
259 256 # in the output stream and know this is the response to ``between``
260 257 # and we're at the end of our handshake reply.
261 258 #
262 259 # The response to the ``hello`` command will be a line with the
263 260 # length of the value returned by that command followed by that
264 261 # value. If the server doesn't support ``hello`` (which should be
265 262 # rare), that line will be ``0\n``. Otherwise, the value will contain
266 263 # RFC 822 like lines. Of these, the ``capabilities:`` line contains
267 264 # the capabilities of the server.
268 265 #
269 266 # The ``upgrade`` command isn't really a command in the traditional
270 267 # sense of version 1 of the transport because it isn't using the
271 268 # proper mechanism for formatting insteads: instead, it just encodes
272 269 # arguments on the line, delimited by spaces.
273 270 #
274 271 # The ``upgrade`` line looks like ``upgrade <token> <capabilities>``.
275 272 # If the server doesn't support protocol upgrades, it will reply to
276 273 # this line with ``0\n``. Otherwise, it emits an
277 274 # ``upgraded <token> <protocol>`` line to both stdout and stderr.
278 275 # Content immediately following this line describes additional
279 276 # protocol and server state.
280 277 #
281 278 # In addition to the responses to our command requests, the server
282 279 # may emit "banner" output on stdout. SSH servers are allowed to
283 280 # print messages to stdout on login. Issuing commands on connection
284 281 # allows us to flush this banner output from the server by scanning
285 282 # for output to our well-known ``between`` command. Of course, if
286 283 # the banner contains ``1\n\n``, this will throw off our detection.
287 284
288 285 requestlog = ui.configbool(b'devel', b'debug.peer-request')
289 286
290 287 # Generate a random token to help identify responses to version 2
291 288 # upgrade request.
292 289 token = pycompat.sysbytes(str(uuid.uuid4()))
293 290 upgradecaps = [
294 291 (b'proto', wireprotoserver.SSHV2),
295 292 ]
296 293 upgradecaps = util.urlreq.urlencode(upgradecaps)
297 294
298 295 try:
299 296 pairsarg = b'%s-%s' % (b'0' * 40, b'0' * 40)
300 297 handshake = [
301 298 b'hello\n',
302 299 b'between\n',
303 300 b'pairs %d\n' % len(pairsarg),
304 301 pairsarg,
305 302 ]
306 303
307 304 # Request upgrade to version 2 if configured.
308 305 if ui.configbool(b'experimental', b'sshpeer.advertise-v2'):
309 306 ui.debug(b'sending upgrade request: %s %s\n' % (token, upgradecaps))
310 307 handshake.insert(0, b'upgrade %s %s\n' % (token, upgradecaps))
311 308
312 309 if requestlog:
313 310 ui.debug(b'devel-peer-request: hello+between\n')
314 311 ui.debug(b'devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
315 312 ui.debug(b'sending hello command\n')
316 313 ui.debug(b'sending between command\n')
317 314
318 315 stdin.write(b''.join(handshake))
319 316 stdin.flush()
320 317 except IOError:
321 318 badresponse()
322 319
323 320 # Assume version 1 of wire protocol by default.
324 321 protoname = wireprototypes.SSHV1
325 322 reupgraded = re.compile(b'^upgraded %s (.*)$' % stringutil.reescape(token))
326 323
327 324 lines = [b'', b'dummy']
328 325 max_noise = 500
329 326 while lines[-1] and max_noise:
330 327 try:
331 328 l = stdout.readline()
332 329 _forwardoutput(ui, stderr, warn=True)
333 330
334 331 # Look for reply to protocol upgrade request. It has a token
335 332 # in it, so there should be no false positives.
336 333 m = reupgraded.match(l)
337 334 if m:
338 335 protoname = m.group(1)
339 336 ui.debug(b'protocol upgraded to %s\n' % protoname)
340 337 # If an upgrade was handled, the ``hello`` and ``between``
341 338 # requests are ignored. The next output belongs to the
342 339 # protocol, so stop scanning lines.
343 340 break
344 341
345 342 # Otherwise it could be a banner, ``0\n`` response if server
346 343 # doesn't support upgrade.
347 344
348 345 if lines[-1] == b'1\n' and l == b'\n':
349 346 break
350 347 if l:
351 348 ui.debug(b'remote: ', l)
352 349 lines.append(l)
353 350 max_noise -= 1
354 351 except IOError:
355 352 badresponse()
356 353 else:
357 354 badresponse()
358 355
359 356 caps = set()
360 357
361 358 # For version 1, we should see a ``capabilities`` line in response to the
362 359 # ``hello`` command.
363 360 if protoname == wireprototypes.SSHV1:
364 361 for l in reversed(lines):
365 362 # Look for response to ``hello`` command. Scan from the back so
366 363 # we don't misinterpret banner output as the command reply.
367 364 if l.startswith(b'capabilities:'):
368 365 caps.update(l[:-1].split(b':')[1].split())
369 366 break
370 367 elif protoname == wireprotoserver.SSHV2:
371 368 # We see a line with number of bytes to follow and then a value
372 369 # looking like ``capabilities: *``.
373 370 line = stdout.readline()
374 371 try:
375 372 valuelen = int(line)
376 373 except ValueError:
377 374 badresponse()
378 375
379 376 capsline = stdout.read(valuelen)
380 377 if not capsline.startswith(b'capabilities: '):
381 378 badresponse()
382 379
383 380 ui.debug(b'remote: %s\n' % capsline)
384 381
385 382 caps.update(capsline.split(b':')[1].split())
386 383 # Trailing newline.
387 384 stdout.read(1)
388 385
389 386 # Error if we couldn't find capabilities, this means:
390 387 #
391 388 # 1. Remote isn't a Mercurial server
392 389 # 2. Remote is a <0.9.1 Mercurial server
393 390 # 3. Remote is a future Mercurial server that dropped ``hello``
394 391 # and other attempted handshake mechanisms.
395 392 if not caps:
396 393 badresponse()
397 394
398 395 # Flush any output on stderr before proceeding.
399 396 _forwardoutput(ui, stderr, warn=True)
400 397
401 398 return protoname, caps
402 399
403 400
404 401 class sshv1peer(wireprotov1peer.wirepeer):
405 402 def __init__(
406 403 self, ui, url, proc, stdin, stdout, stderr, caps, autoreadstderr=True
407 404 ):
408 405 """Create a peer from an existing SSH connection.
409 406
410 407 ``proc`` is a handle on the underlying SSH process.
411 408 ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
412 409 pipes for that process.
413 410 ``caps`` is a set of capabilities supported by the remote.
414 411 ``autoreadstderr`` denotes whether to automatically read from
415 412 stderr and to forward its output.
416 413 """
417 414 self._url = url
418 415 self.ui = ui
419 416 # self._subprocess is unused. Keeping a handle on the process
420 417 # holds a reference and prevents it from being garbage collected.
421 418 self._subprocess = proc
422 419
423 420 # And we hook up our "doublepipe" wrapper to allow querying
424 421 # stderr any time we perform I/O.
425 422 if autoreadstderr:
426 423 stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
427 424 stdin = doublepipe(ui, stdin, stderr)
428 425
429 426 self._pipeo = stdin
430 427 self._pipei = stdout
431 428 self._pipee = stderr
432 429 self._caps = caps
433 430 self._autoreadstderr = autoreadstderr
434 431 self._initstack = b''.join(util.getstackframes(1))
435 432
436 433 # Commands that have a "framed" response where the first line of the
437 434 # response contains the length of that response.
438 435 _FRAMED_COMMANDS = {
439 436 b'batch',
440 437 }
441 438
442 439 # Begin of ipeerconnection interface.
443 440
444 441 def url(self):
445 442 return self._url
446 443
447 444 def local(self):
448 445 return None
449 446
450 447 def peer(self):
451 448 return self
452 449
453 450 def canpush(self):
454 451 return True
455 452
456 453 def close(self):
457 454 self._cleanup()
458 455
459 456 # End of ipeerconnection interface.
460 457
461 458 # Begin of ipeercommands interface.
462 459
463 460 def capabilities(self):
464 461 return self._caps
465 462
466 463 # End of ipeercommands interface.
467 464
468 465 def _readerr(self):
469 466 _forwardoutput(self.ui, self._pipee)
470 467
471 468 def _abort(self, exception):
472 469 self._cleanup()
473 470 raise exception
474 471
475 472 def _cleanup(self, warn=None):
476 473 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee, warn=warn)
477 474
478 475 def __del__(self):
479 476 self._cleanup(warn=self._initstack)
480 477
481 478 def _sendrequest(self, cmd, args, framed=False):
482 479 if self.ui.debugflag and self.ui.configbool(
483 480 b'devel', b'debug.peer-request'
484 481 ):
485 482 dbg = self.ui.debug
486 483 line = b'devel-peer-request: %s\n'
487 484 dbg(line % cmd)
488 485 for key, value in sorted(args.items()):
489 486 if not isinstance(value, dict):
490 487 dbg(line % b' %s: %d bytes' % (key, len(value)))
491 488 else:
492 489 for dk, dv in sorted(value.items()):
493 490 dbg(line % b' %s-%s: %d' % (key, dk, len(dv)))
494 491 self.ui.debug(b"sending %s command\n" % cmd)
495 492 self._pipeo.write(b"%s\n" % cmd)
496 493 _func, names = wireprotov1server.commands[cmd]
497 494 keys = names.split()
498 495 wireargs = {}
499 496 for k in keys:
500 497 if k == b'*':
501 498 wireargs[b'*'] = args
502 499 break
503 500 else:
504 501 wireargs[k] = args[k]
505 502 del args[k]
506 503 for k, v in sorted(pycompat.iteritems(wireargs)):
507 504 self._pipeo.write(b"%s %d\n" % (k, len(v)))
508 505 if isinstance(v, dict):
509 506 for dk, dv in pycompat.iteritems(v):
510 507 self._pipeo.write(b"%s %d\n" % (dk, len(dv)))
511 508 self._pipeo.write(dv)
512 509 else:
513 510 self._pipeo.write(v)
514 511 self._pipeo.flush()
515 512
516 513 # We know exactly how many bytes are in the response. So return a proxy
517 514 # around the raw output stream that allows reading exactly this many
518 515 # bytes. Callers then can read() without fear of overrunning the
519 516 # response.
520 517 if framed:
521 518 amount = self._getamount()
522 519 return util.cappedreader(self._pipei, amount)
523 520
524 521 return self._pipei
525 522
526 523 def _callstream(self, cmd, **args):
527 524 args = pycompat.byteskwargs(args)
528 525 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
529 526
530 527 def _callcompressable(self, cmd, **args):
531 528 args = pycompat.byteskwargs(args)
532 529 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
533 530
534 531 def _call(self, cmd, **args):
535 532 args = pycompat.byteskwargs(args)
536 533 return self._sendrequest(cmd, args, framed=True).read()
537 534
538 535 def _callpush(self, cmd, fp, **args):
539 536 # The server responds with an empty frame if the client should
540 537 # continue submitting the payload.
541 538 r = self._call(cmd, **args)
542 539 if r:
543 540 return b'', r
544 541
545 542 # The payload consists of frames with content followed by an empty
546 543 # frame.
547 544 for d in iter(lambda: fp.read(4096), b''):
548 545 self._writeframed(d)
549 546 self._writeframed(b"", flush=True)
550 547
551 548 # In case of success, there is an empty frame and a frame containing
552 549 # the integer result (as a string).
553 550 # In case of error, there is a non-empty frame containing the error.
554 551 r = self._readframed()
555 552 if r:
556 553 return b'', r
557 554 return self._readframed(), b''
558 555
559 556 def _calltwowaystream(self, cmd, fp, **args):
560 557 # The server responds with an empty frame if the client should
561 558 # continue submitting the payload.
562 559 r = self._call(cmd, **args)
563 560 if r:
564 561 # XXX needs to be made better
565 562 raise error.Abort(_(b'unexpected remote reply: %s') % r)
566 563
567 564 # The payload consists of frames with content followed by an empty
568 565 # frame.
569 566 for d in iter(lambda: fp.read(4096), b''):
570 567 self._writeframed(d)
571 568 self._writeframed(b"", flush=True)
572 569
573 570 return self._pipei
574 571
575 572 def _getamount(self):
576 573 l = self._pipei.readline()
577 574 if l == b'\n':
578 575 if self._autoreadstderr:
579 576 self._readerr()
580 577 msg = _(b'check previous remote output')
581 578 self._abort(error.OutOfBandError(hint=msg))
582 579 if self._autoreadstderr:
583 580 self._readerr()
584 581 try:
585 582 return int(l)
586 583 except ValueError:
587 584 self._abort(error.ResponseError(_(b"unexpected response:"), l))
588 585
589 586 def _readframed(self):
590 587 size = self._getamount()
591 588 if not size:
592 589 return b''
593 590
594 591 return self._pipei.read(size)
595 592
596 593 def _writeframed(self, data, flush=False):
597 594 self._pipeo.write(b"%d\n" % len(data))
598 595 if data:
599 596 self._pipeo.write(data)
600 597 if flush:
601 598 self._pipeo.flush()
602 599 if self._autoreadstderr:
603 600 self._readerr()
604 601
605 602
606 603 class sshv2peer(sshv1peer):
607 604 """A peer that speakers version 2 of the transport protocol."""
608 605
609 606 # Currently version 2 is identical to version 1 post handshake.
610 607 # And handshake is performed before the peer is instantiated. So
611 608 # we need no custom code.
612 609
613 610
614 611 def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True):
615 612 """Make a peer instance from existing pipes.
616 613
617 614 ``path`` and ``proc`` are stored on the eventual peer instance and may
618 615 not be used for anything meaningful.
619 616
620 617 ``stdin``, ``stdout``, and ``stderr`` are the pipes connected to the
621 618 SSH server's stdio handles.
622 619
623 620 This function is factored out to allow creating peers that don't
624 621 actually spawn a new process. It is useful for starting SSH protocol
625 622 servers and clients via non-standard means, which can be useful for
626 623 testing.
627 624 """
628 625 try:
629 626 protoname, caps = _performhandshake(ui, stdin, stdout, stderr)
630 627 except Exception:
631 628 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
632 629 raise
633 630
634 631 if protoname == wireprototypes.SSHV1:
635 632 return sshv1peer(
636 633 ui,
637 634 path,
638 635 proc,
639 636 stdin,
640 637 stdout,
641 638 stderr,
642 639 caps,
643 640 autoreadstderr=autoreadstderr,
644 641 )
645 642 elif protoname == wireprototypes.SSHV2:
646 643 return sshv2peer(
647 644 ui,
648 645 path,
649 646 proc,
650 647 stdin,
651 648 stdout,
652 649 stderr,
653 650 caps,
654 651 autoreadstderr=autoreadstderr,
655 652 )
656 653 else:
657 654 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
658 655 raise error.RepoError(
659 656 _(b'unknown version of SSH protocol: %s') % protoname
660 657 )
661 658
662 659
663 660 def instance(ui, path, create, intents=None, createopts=None):
664 661 """Create an SSH peer.
665 662
666 663 The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
667 664 """
668 665 u = util.url(path, parsequery=False, parsefragment=False)
669 666 if u.scheme != b'ssh' or not u.host or u.path is None:
670 667 raise error.RepoError(_(b"couldn't parse location %s") % path)
671 668
672 669 util.checksafessh(path)
673 670
674 671 if u.passwd is not None:
675 672 raise error.RepoError(_(b'password in URL not supported'))
676 673
677 674 sshcmd = ui.config(b'ui', b'ssh')
678 675 remotecmd = ui.config(b'ui', b'remotecmd')
679 676 sshaddenv = dict(ui.configitems(b'sshenv'))
680 677 sshenv = procutil.shellenviron(sshaddenv)
681 678 remotepath = u.path or b'.'
682 679
683 680 args = procutil.sshargs(sshcmd, u.host, u.user, u.port)
684 681
685 682 if create:
686 683 # We /could/ do this, but only if the remote init command knows how to
687 684 # handle them. We don't yet make any assumptions about that. And without
688 685 # querying the remote, there's no way of knowing if the remote even
689 686 # supports said requested feature.
690 687 if createopts:
691 688 raise error.RepoError(
692 689 _(
693 690 b'cannot create remote SSH repositories '
694 691 b'with extra options'
695 692 )
696 693 )
697 694
698 695 cmd = b'%s %s %s' % (
699 696 sshcmd,
700 697 args,
701 698 procutil.shellquote(
702 699 b'%s init %s'
703 700 % (_serverquote(remotecmd), _serverquote(remotepath))
704 701 ),
705 702 )
706 703 ui.debug(b'running %s\n' % cmd)
707 704 res = ui.system(cmd, blockedtag=b'sshpeer', environ=sshenv)
708 705 if res != 0:
709 706 raise error.RepoError(_(b'could not create remote repo'))
710 707
711 708 proc, stdin, stdout, stderr = _makeconnection(
712 709 ui, sshcmd, args, remotecmd, remotepath, sshenv
713 710 )
714 711
715 712 peer = makepeer(ui, path, proc, stdin, stdout, stderr)
716 713
717 714 # Finally, if supported by the server, notify it about our own
718 715 # capabilities.
719 716 if b'protocaps' in peer.capabilities():
720 717 try:
721 718 peer._call(
722 719 b"protocaps", caps=b' '.join(sorted(_clientcapabilities()))
723 720 )
724 721 except IOError:
725 722 peer._cleanup()
726 723 raise error.RepoError(_(b'capability exchange failed'))
727 724
728 725 return peer
@@ -1,2054 +1,2069 b''
1 1 # subrepo.py - sub-repository classes and factory
2 2 #
3 3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import copy
11 11 import errno
12 12 import os
13 13 import re
14 14 import stat
15 15 import subprocess
16 16 import sys
17 17 import tarfile
18 18 import xml.dom.minidom
19 19
20 20 from .i18n import _
21 21 from .node import (
22 22 bin,
23 23 hex,
24 24 nullid,
25 25 short,
26 26 )
27 27 from . import (
28 28 cmdutil,
29 29 encoding,
30 30 error,
31 31 exchange,
32 32 logcmdutil,
33 33 match as matchmod,
34 34 merge as merge,
35 35 pathutil,
36 36 phases,
37 37 pycompat,
38 38 scmutil,
39 39 subrepoutil,
40 40 util,
41 41 vfs as vfsmod,
42 42 )
43 43 from .utils import (
44 44 dateutil,
45 45 hashutil,
46 46 procutil,
47 47 )
48 48
49 49 hg = None
50 50 reporelpath = subrepoutil.reporelpath
51 51 subrelpath = subrepoutil.subrelpath
52 52 _abssource = subrepoutil._abssource
53 53 propertycache = util.propertycache
54 54
55 55
56 56 def _expandedabspath(path):
57 57 """
58 58 get a path or url and if it is a path expand it and return an absolute path
59 59 """
60 60 expandedpath = util.urllocalpath(util.expandpath(path))
61 61 u = util.url(expandedpath)
62 62 if not u.scheme:
63 63 path = util.normpath(os.path.abspath(u.path))
64 64 return path
65 65
66 66
67 67 def _getstorehashcachename(remotepath):
68 68 '''get a unique filename for the store hash cache of a remote repository'''
69 69 return hex(hashutil.sha1(_expandedabspath(remotepath)).digest())[0:12]
70 70
71 71
72 72 class SubrepoAbort(error.Abort):
73 73 """Exception class used to avoid handling a subrepo error more than once"""
74 74
75 75 def __init__(self, *args, **kw):
76 76 self.subrepo = kw.pop('subrepo', None)
77 77 self.cause = kw.pop('cause', None)
78 78 error.Abort.__init__(self, *args, **kw)
79 79
80 80
81 81 def annotatesubrepoerror(func):
82 82 def decoratedmethod(self, *args, **kargs):
83 83 try:
84 84 res = func(self, *args, **kargs)
85 85 except SubrepoAbort as ex:
86 86 # This exception has already been handled
87 87 raise ex
88 88 except error.Abort as ex:
89 89 subrepo = subrelpath(self)
90 90 errormsg = (
91 91 ex.message + b' ' + _(b'(in subrepository "%s")') % subrepo
92 92 )
93 93 # avoid handling this exception by raising a SubrepoAbort exception
94 94 raise SubrepoAbort(
95 95 errormsg, hint=ex.hint, subrepo=subrepo, cause=sys.exc_info()
96 96 )
97 97 return res
98 98
99 99 return decoratedmethod
100 100
101 101
102 102 def _updateprompt(ui, sub, dirty, local, remote):
103 103 if dirty:
104 104 msg = _(
105 105 b' subrepository sources for %s differ\n'
106 106 b'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
107 107 b'what do you want to do?'
108 108 b'$$ &Local $$ &Remote'
109 109 ) % (subrelpath(sub), local, remote)
110 110 else:
111 111 msg = _(
112 112 b' subrepository sources for %s differ (in checked out '
113 113 b'version)\n'
114 114 b'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
115 115 b'what do you want to do?'
116 116 b'$$ &Local $$ &Remote'
117 117 ) % (subrelpath(sub), local, remote)
118 118 return ui.promptchoice(msg, 0)
119 119
120 120
121 121 def _sanitize(ui, vfs, ignore):
122 122 for dirname, dirs, names in vfs.walk():
123 123 for i, d in enumerate(dirs):
124 124 if d.lower() == ignore:
125 125 del dirs[i]
126 126 break
127 127 if vfs.basename(dirname).lower() != b'.hg':
128 128 continue
129 129 for f in names:
130 130 if f.lower() == b'hgrc':
131 131 ui.warn(
132 132 _(
133 133 b"warning: removing potentially hostile 'hgrc' "
134 134 b"in '%s'\n"
135 135 )
136 136 % vfs.join(dirname)
137 137 )
138 138 vfs.unlink(vfs.reljoin(dirname, f))
139 139
140 140
141 141 def _auditsubrepopath(repo, path):
142 142 # sanity check for potentially unsafe paths such as '~' and '$FOO'
143 143 if path.startswith(b'~') or b'$' in path or util.expandpath(path) != path:
144 144 raise error.Abort(
145 145 _(b'subrepo path contains illegal component: %s') % path
146 146 )
147 147 # auditor doesn't check if the path itself is a symlink
148 148 pathutil.pathauditor(repo.root)(path)
149 149 if repo.wvfs.islink(path):
150 150 raise error.Abort(_(b"subrepo '%s' traverses symbolic link") % path)
151 151
152 152
153 153 SUBREPO_ALLOWED_DEFAULTS = {
154 154 b'hg': True,
155 155 b'git': False,
156 156 b'svn': False,
157 157 }
158 158
159 159
160 160 def _checktype(ui, kind):
161 161 # subrepos.allowed is a master kill switch. If disabled, subrepos are
162 162 # disabled period.
163 163 if not ui.configbool(b'subrepos', b'allowed', True):
164 164 raise error.Abort(
165 165 _(b'subrepos not enabled'),
166 166 hint=_(b"see 'hg help config.subrepos' for details"),
167 167 )
168 168
169 169 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
170 170 if not ui.configbool(b'subrepos', b'%s:allowed' % kind, default):
171 171 raise error.Abort(
172 172 _(b'%s subrepos not allowed') % kind,
173 173 hint=_(b"see 'hg help config.subrepos' for details"),
174 174 )
175 175
176 176 if kind not in types:
177 177 raise error.Abort(_(b'unknown subrepo type %s') % kind)
178 178
179 179
180 180 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
181 181 """return instance of the right subrepo class for subrepo in path"""
182 182 # subrepo inherently violates our import layering rules
183 183 # because it wants to make repo objects from deep inside the stack
184 184 # so we manually delay the circular imports to not break
185 185 # scripts that don't use our demand-loading
186 186 global hg
187 187 from . import hg as h
188 188
189 189 hg = h
190 190
191 191 repo = ctx.repo()
192 192 _auditsubrepopath(repo, path)
193 193 state = ctx.substate[path]
194 194 _checktype(repo.ui, state[2])
195 195 if allowwdir:
196 196 state = (state[0], ctx.subrev(path), state[2])
197 197 return types[state[2]](ctx, path, state[:2], allowcreate)
198 198
199 199
200 200 def nullsubrepo(ctx, path, pctx):
201 201 """return an empty subrepo in pctx for the extant subrepo in ctx"""
202 202 # subrepo inherently violates our import layering rules
203 203 # because it wants to make repo objects from deep inside the stack
204 204 # so we manually delay the circular imports to not break
205 205 # scripts that don't use our demand-loading
206 206 global hg
207 207 from . import hg as h
208 208
209 209 hg = h
210 210
211 211 repo = ctx.repo()
212 212 _auditsubrepopath(repo, path)
213 213 state = ctx.substate[path]
214 214 _checktype(repo.ui, state[2])
215 215 subrev = b''
216 216 if state[2] == b'hg':
217 217 subrev = b"0" * 40
218 218 return types[state[2]](pctx, path, (state[0], subrev), True)
219 219
220 220
221 221 # subrepo classes need to implement the following abstract class:
222 222
223 223
224 224 class abstractsubrepo(object):
225 225 def __init__(self, ctx, path):
226 226 """Initialize abstractsubrepo part
227 227
228 228 ``ctx`` is the context referring this subrepository in the
229 229 parent repository.
230 230
231 231 ``path`` is the path to this subrepository as seen from
232 232 innermost repository.
233 233 """
234 234 self.ui = ctx.repo().ui
235 235 self._ctx = ctx
236 236 self._path = path
237 237
238 238 def addwebdirpath(self, serverpath, webconf):
239 239 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
240 240
241 241 ``serverpath`` is the path component of the URL for this repo.
242 242
243 243 ``webconf`` is the dictionary of hgwebdir entries.
244 244 """
245 245 pass
246 246
247 247 def storeclean(self, path):
248 248 """
249 249 returns true if the repository has not changed since it was last
250 250 cloned from or pushed to a given repository.
251 251 """
252 252 return False
253 253
254 254 def dirty(self, ignoreupdate=False, missing=False):
255 255 """returns true if the dirstate of the subrepo is dirty or does not
256 256 match current stored state. If ignoreupdate is true, only check
257 257 whether the subrepo has uncommitted changes in its dirstate. If missing
258 258 is true, check for deleted files.
259 259 """
260 260 raise NotImplementedError
261 261
262 262 def dirtyreason(self, ignoreupdate=False, missing=False):
263 263 """return reason string if it is ``dirty()``
264 264
265 265 Returned string should have enough information for the message
266 266 of exception.
267 267
268 268 This returns None, otherwise.
269 269 """
270 270 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
271 271 return _(b'uncommitted changes in subrepository "%s"') % subrelpath(
272 272 self
273 273 )
274 274
275 275 def bailifchanged(self, ignoreupdate=False, hint=None):
276 276 """raise Abort if subrepository is ``dirty()``"""
277 277 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate, missing=True)
278 278 if dirtyreason:
279 279 raise error.Abort(dirtyreason, hint=hint)
280 280
281 281 def basestate(self):
282 282 """current working directory base state, disregarding .hgsubstate
283 283 state and working directory modifications"""
284 284 raise NotImplementedError
285 285
286 286 def checknested(self, path):
287 287 """check if path is a subrepository within this repository"""
288 288 return False
289 289
290 290 def commit(self, text, user, date):
291 291 """commit the current changes to the subrepo with the given
292 292 log message. Use given user and date if possible. Return the
293 293 new state of the subrepo.
294 294 """
295 295 raise NotImplementedError
296 296
297 297 def phase(self, state):
298 298 """returns phase of specified state in the subrepository."""
299 299 return phases.public
300 300
301 301 def remove(self):
302 302 """remove the subrepo
303 303
304 304 (should verify the dirstate is not dirty first)
305 305 """
306 306 raise NotImplementedError
307 307
308 308 def get(self, state, overwrite=False):
309 309 """run whatever commands are needed to put the subrepo into
310 310 this state
311 311 """
312 312 raise NotImplementedError
313 313
314 314 def merge(self, state):
315 315 """merge currently-saved state with the new state."""
316 316 raise NotImplementedError
317 317
318 318 def push(self, opts):
319 319 """perform whatever action is analogous to 'hg push'
320 320
321 321 This may be a no-op on some systems.
322 322 """
323 323 raise NotImplementedError
324 324
325 325 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
326 326 return []
327 327
328 328 def addremove(self, matcher, prefix, uipathfn, opts):
329 329 self.ui.warn(b"%s: %s" % (prefix, _(b"addremove is not supported")))
330 330 return 1
331 331
332 332 def cat(self, match, fm, fntemplate, prefix, **opts):
333 333 return 1
334 334
335 335 def status(self, rev2, **opts):
336 336 return scmutil.status([], [], [], [], [], [], [])
337 337
338 338 def diff(self, ui, diffopts, node2, match, prefix, **opts):
339 339 pass
340 340
341 341 def outgoing(self, ui, dest, opts):
342 342 return 1
343 343
344 344 def incoming(self, ui, source, opts):
345 345 return 1
346 346
347 347 def files(self):
348 348 """return filename iterator"""
349 349 raise NotImplementedError
350 350
351 351 def filedata(self, name, decode):
352 352 """return file data, optionally passed through repo decoders"""
353 353 raise NotImplementedError
354 354
355 355 def fileflags(self, name):
356 356 """return file flags"""
357 357 return b''
358 358
359 359 def matchfileset(self, cwd, expr, badfn=None):
360 360 """Resolve the fileset expression for this repo"""
361 361 return matchmod.never(badfn=badfn)
362 362
363 363 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
364 364 """handle the files command for this subrepo"""
365 365 return 1
366 366
367 367 def archive(self, archiver, prefix, match=None, decode=True):
368 368 if match is not None:
369 369 files = [f for f in self.files() if match(f)]
370 370 else:
371 371 files = self.files()
372 372 total = len(files)
373 373 relpath = subrelpath(self)
374 374 progress = self.ui.makeprogress(
375 375 _(b'archiving (%s)') % relpath, unit=_(b'files'), total=total
376 376 )
377 377 progress.update(0)
378 378 for name in files:
379 379 flags = self.fileflags(name)
380 380 mode = b'x' in flags and 0o755 or 0o644
381 381 symlink = b'l' in flags
382 382 archiver.addfile(
383 383 prefix + name, mode, symlink, self.filedata(name, decode)
384 384 )
385 385 progress.increment()
386 386 progress.complete()
387 387 return total
388 388
389 389 def walk(self, match):
390 390 """
391 391 walk recursively through the directory tree, finding all files
392 392 matched by the match function
393 393 """
394 394
395 395 def forget(self, match, prefix, uipathfn, dryrun, interactive):
396 396 return ([], [])
397 397
398 398 def removefiles(
399 399 self,
400 400 matcher,
401 401 prefix,
402 402 uipathfn,
403 403 after,
404 404 force,
405 405 subrepos,
406 406 dryrun,
407 407 warnings,
408 408 ):
409 409 """remove the matched files from the subrepository and the filesystem,
410 410 possibly by force and/or after the file has been removed from the
411 411 filesystem. Return 0 on success, 1 on any warning.
412 412 """
413 413 warnings.append(
414 414 _(b"warning: removefiles not implemented (%s)") % self._path
415 415 )
416 416 return 1
417 417
418 418 def revert(self, substate, *pats, **opts):
419 419 self.ui.warn(
420 420 _(b'%s: reverting %s subrepos is unsupported\n')
421 421 % (substate[0], substate[2])
422 422 )
423 423 return []
424 424
425 425 def shortid(self, revid):
426 426 return revid
427 427
428 428 def unshare(self):
429 429 """
430 430 convert this repository from shared to normal storage.
431 431 """
432 432
433 433 def verify(self, onpush=False):
434 434 """verify the revision of this repository that is held in `_state` is
435 435 present and not hidden. Return 0 on success or warning, 1 on any
436 436 error. In the case of ``onpush``, warnings or errors will raise an
437 437 exception if the result of pushing would be a broken remote repository.
438 438 """
439 439 return 0
440 440
441 441 @propertycache
442 442 def wvfs(self):
443 443 """return vfs to access the working directory of this subrepository"""
444 444 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
445 445
446 446 @propertycache
447 447 def _relpath(self):
448 448 """return path to this subrepository as seen from outermost repository"""
449 449 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
450 450
451 451
452 452 class hgsubrepo(abstractsubrepo):
453 453 def __init__(self, ctx, path, state, allowcreate):
454 454 super(hgsubrepo, self).__init__(ctx, path)
455 455 self._state = state
456 456 r = ctx.repo()
457 457 root = r.wjoin(util.localpath(path))
458 458 create = allowcreate and not r.wvfs.exists(b'%s/.hg' % path)
459 459 # repository constructor does expand variables in path, which is
460 460 # unsafe since subrepo path might come from untrusted source.
461 461 if os.path.realpath(util.expandpath(root)) != root:
462 462 raise error.Abort(
463 463 _(b'subrepo path contains illegal component: %s') % path
464 464 )
465 465 self._repo = hg.repository(r.baseui, root, create=create)
466 466 if self._repo.root != root:
467 467 raise error.ProgrammingError(
468 468 b'failed to reject unsafe subrepo '
469 469 b'path: %s (expanded to %s)' % (root, self._repo.root)
470 470 )
471 471
472 472 # Propagate the parent's --hidden option
473 473 if r is r.unfiltered():
474 474 self._repo = self._repo.unfiltered()
475 475
476 476 self.ui = self._repo.ui
477 477 for s, k in [(b'ui', b'commitsubrepos')]:
478 478 v = r.ui.config(s, k)
479 479 if v:
480 480 self.ui.setconfig(s, k, v, b'subrepo')
481 481 # internal config: ui._usedassubrepo
482 482 self.ui.setconfig(b'ui', b'_usedassubrepo', b'True', b'subrepo')
483 483 self._initrepo(r, state[0], create)
484 484
485 485 @annotatesubrepoerror
486 486 def addwebdirpath(self, serverpath, webconf):
487 487 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
488 488
489 489 def storeclean(self, path):
490 490 with self._repo.lock():
491 491 return self._storeclean(path)
492 492
493 493 def _storeclean(self, path):
494 494 clean = True
495 495 itercache = self._calcstorehash(path)
496 496 for filehash in self._readstorehashcache(path):
497 497 if filehash != next(itercache, None):
498 498 clean = False
499 499 break
500 500 if clean:
501 501 # if not empty:
502 502 # the cached and current pull states have a different size
503 503 clean = next(itercache, None) is None
504 504 return clean
505 505
506 506 def _calcstorehash(self, remotepath):
507 507 """calculate a unique "store hash"
508 508
509 509 This method is used to to detect when there are changes that may
510 510 require a push to a given remote path."""
511 511 # sort the files that will be hashed in increasing (likely) file size
512 512 filelist = (b'bookmarks', b'store/phaseroots', b'store/00changelog.i')
513 513 yield b'# %s\n' % _expandedabspath(remotepath)
514 514 vfs = self._repo.vfs
515 515 for relname in filelist:
516 516 filehash = hex(hashutil.sha1(vfs.tryread(relname)).digest())
517 517 yield b'%s = %s\n' % (relname, filehash)
518 518
519 519 @propertycache
520 520 def _cachestorehashvfs(self):
521 521 return vfsmod.vfs(self._repo.vfs.join(b'cache/storehash'))
522 522
523 523 def _readstorehashcache(self, remotepath):
524 524 '''read the store hash cache for a given remote repository'''
525 525 cachefile = _getstorehashcachename(remotepath)
526 526 return self._cachestorehashvfs.tryreadlines(cachefile, b'r')
527 527
528 528 def _cachestorehash(self, remotepath):
529 529 """cache the current store hash
530 530
531 531 Each remote repo requires its own store hash cache, because a subrepo
532 532 store may be "clean" versus a given remote repo, but not versus another
533 533 """
534 534 cachefile = _getstorehashcachename(remotepath)
535 535 with self._repo.lock():
536 536 storehash = list(self._calcstorehash(remotepath))
537 537 vfs = self._cachestorehashvfs
538 538 vfs.writelines(cachefile, storehash, mode=b'wb', notindexed=True)
539 539
540 540 def _getctx(self):
541 541 """fetch the context for this subrepo revision, possibly a workingctx"""
542 542 if self._ctx.rev() is None:
543 543 return self._repo[None] # workingctx if parent is workingctx
544 544 else:
545 545 rev = self._state[1]
546 546 return self._repo[rev]
547 547
548 548 @annotatesubrepoerror
549 549 def _initrepo(self, parentrepo, source, create):
550 550 self._repo._subparent = parentrepo
551 551 self._repo._subsource = source
552 552
553 553 if create:
554 554 lines = [b'[paths]\n']
555 555
556 556 def addpathconfig(key, value):
557 557 if value:
558 558 lines.append(b'%s = %s\n' % (key, value))
559 559 self.ui.setconfig(b'paths', key, value, b'subrepo')
560 560
561 561 defpath = _abssource(self._repo, abort=False)
562 562 defpushpath = _abssource(self._repo, True, abort=False)
563 563 addpathconfig(b'default', defpath)
564 564 if defpath != defpushpath:
565 565 addpathconfig(b'default-push', defpushpath)
566 566
567 567 self._repo.vfs.write(b'hgrc', util.tonativeeol(b''.join(lines)))
568 568
569 569 @annotatesubrepoerror
570 570 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
571 571 return cmdutil.add(
572 572 ui, self._repo, match, prefix, uipathfn, explicitonly, **opts
573 573 )
574 574
575 575 @annotatesubrepoerror
576 576 def addremove(self, m, prefix, uipathfn, opts):
577 577 # In the same way as sub directories are processed, once in a subrepo,
578 578 # always entry any of its subrepos. Don't corrupt the options that will
579 579 # be used to process sibling subrepos however.
580 580 opts = copy.copy(opts)
581 581 opts[b'subrepos'] = True
582 582 return scmutil.addremove(self._repo, m, prefix, uipathfn, opts)
583 583
584 584 @annotatesubrepoerror
585 585 def cat(self, match, fm, fntemplate, prefix, **opts):
586 586 rev = self._state[1]
587 587 ctx = self._repo[rev]
588 588 return cmdutil.cat(
589 589 self.ui, self._repo, ctx, match, fm, fntemplate, prefix, **opts
590 590 )
591 591
592 592 @annotatesubrepoerror
593 593 def status(self, rev2, **opts):
594 594 try:
595 595 rev1 = self._state[1]
596 596 ctx1 = self._repo[rev1]
597 597 ctx2 = self._repo[rev2]
598 598 return self._repo.status(ctx1, ctx2, **opts)
599 599 except error.RepoLookupError as inst:
600 600 self.ui.warn(
601 601 _(b'warning: error "%s" in subrepository "%s"\n')
602 602 % (inst, subrelpath(self))
603 603 )
604 604 return scmutil.status([], [], [], [], [], [], [])
605 605
606 606 @annotatesubrepoerror
607 607 def diff(self, ui, diffopts, node2, match, prefix, **opts):
608 608 try:
609 609 node1 = bin(self._state[1])
610 610 # We currently expect node2 to come from substate and be
611 611 # in hex format
612 612 if node2 is not None:
613 613 node2 = bin(node2)
614 614 logcmdutil.diffordiffstat(
615 615 ui,
616 616 self._repo,
617 617 diffopts,
618 618 self._repo[node1],
619 619 self._repo[node2],
620 620 match,
621 621 prefix=prefix,
622 622 listsubrepos=True,
623 623 **opts
624 624 )
625 625 except error.RepoLookupError as inst:
626 626 self.ui.warn(
627 627 _(b'warning: error "%s" in subrepository "%s"\n')
628 628 % (inst, subrelpath(self))
629 629 )
630 630
631 631 @annotatesubrepoerror
632 632 def archive(self, archiver, prefix, match=None, decode=True):
633 633 self._get(self._state + (b'hg',))
634 634 files = self.files()
635 635 if match:
636 636 files = [f for f in files if match(f)]
637 637 rev = self._state[1]
638 638 ctx = self._repo[rev]
639 639 scmutil.prefetchfiles(
640 640 self._repo, [(ctx.rev(), scmutil.matchfiles(self._repo, files))]
641 641 )
642 642 total = abstractsubrepo.archive(self, archiver, prefix, match)
643 643 for subpath in ctx.substate:
644 644 s = subrepo(ctx, subpath, True)
645 645 submatch = matchmod.subdirmatcher(subpath, match)
646 646 subprefix = prefix + subpath + b'/'
647 647 total += s.archive(archiver, subprefix, submatch, decode)
648 648 return total
649 649
650 650 @annotatesubrepoerror
651 651 def dirty(self, ignoreupdate=False, missing=False):
652 652 r = self._state[1]
653 653 if r == b'' and not ignoreupdate: # no state recorded
654 654 return True
655 655 w = self._repo[None]
656 656 if r != w.p1().hex() and not ignoreupdate:
657 657 # different version checked out
658 658 return True
659 659 return w.dirty(missing=missing) # working directory changed
660 660
661 661 def basestate(self):
662 662 return self._repo[b'.'].hex()
663 663
664 664 def checknested(self, path):
665 665 return self._repo._checknested(self._repo.wjoin(path))
666 666
667 667 @annotatesubrepoerror
668 668 def commit(self, text, user, date):
669 669 # don't bother committing in the subrepo if it's only been
670 670 # updated
671 671 if not self.dirty(True):
672 672 return self._repo[b'.'].hex()
673 673 self.ui.debug(b"committing subrepo %s\n" % subrelpath(self))
674 674 n = self._repo.commit(text, user, date)
675 675 if not n:
676 676 return self._repo[b'.'].hex() # different version checked out
677 677 return hex(n)
678 678
679 679 @annotatesubrepoerror
680 680 def phase(self, state):
681 681 return self._repo[state or b'.'].phase()
682 682
683 683 @annotatesubrepoerror
684 684 def remove(self):
685 685 # we can't fully delete the repository as it may contain
686 686 # local-only history
687 687 self.ui.note(_(b'removing subrepo %s\n') % subrelpath(self))
688 688 hg.clean(self._repo, nullid, False)
689 689
690 690 def _get(self, state):
691 691 source, revision, kind = state
692 692 parentrepo = self._repo._subparent
693 693
694 694 if revision in self._repo.unfiltered():
695 695 # Allow shared subrepos tracked at null to setup the sharedpath
696 696 if len(self._repo) != 0 or not parentrepo.shared():
697 697 return True
698 698 self._repo._subsource = source
699 699 srcurl = _abssource(self._repo)
700 700
701 701 # Defer creating the peer until after the status message is logged, in
702 702 # case there are network problems.
703 703 getpeer = lambda: hg.peer(self._repo, {}, srcurl)
704 704
705 705 if len(self._repo) == 0:
706 706 # use self._repo.vfs instead of self.wvfs to remove .hg only
707 707 self._repo.vfs.rmtree()
708 708
709 709 # A remote subrepo could be shared if there is a local copy
710 710 # relative to the parent's share source. But clone pooling doesn't
711 711 # assemble the repos in a tree, so that can't be consistently done.
712 712 # A simpler option is for the user to configure clone pooling, and
713 713 # work with that.
714 714 if parentrepo.shared() and hg.islocal(srcurl):
715 715 self.ui.status(
716 716 _(b'sharing subrepo %s from %s\n')
717 717 % (subrelpath(self), srcurl)
718 718 )
719 shared = hg.share(
720 self._repo._subparent.baseui,
721 getpeer(),
722 self._repo.root,
723 update=False,
724 bookmarks=False,
725 )
719 peer = getpeer()
720 try:
721 shared = hg.share(
722 self._repo._subparent.baseui,
723 peer,
724 self._repo.root,
725 update=False,
726 bookmarks=False,
727 )
728 finally:
729 peer.close()
726 730 self._repo = shared.local()
727 731 else:
728 732 # TODO: find a common place for this and this code in the
729 733 # share.py wrap of the clone command.
730 734 if parentrepo.shared():
731 735 pool = self.ui.config(b'share', b'pool')
732 736 if pool:
733 737 pool = util.expandpath(pool)
734 738
735 739 shareopts = {
736 740 b'pool': pool,
737 741 b'mode': self.ui.config(b'share', b'poolnaming'),
738 742 }
739 743 else:
740 744 shareopts = {}
741 745
742 746 self.ui.status(
743 747 _(b'cloning subrepo %s from %s\n')
744 748 % (subrelpath(self), util.hidepassword(srcurl))
745 749 )
746 other, cloned = hg.clone(
747 self._repo._subparent.baseui,
748 {},
749 getpeer(),
750 self._repo.root,
751 update=False,
752 shareopts=shareopts,
753 )
750 peer = getpeer()
751 try:
752 other, cloned = hg.clone(
753 self._repo._subparent.baseui,
754 {},
755 peer,
756 self._repo.root,
757 update=False,
758 shareopts=shareopts,
759 )
760 finally:
761 peer.close()
754 762 self._repo = cloned.local()
755 763 self._initrepo(parentrepo, source, create=True)
756 764 self._cachestorehash(srcurl)
757 765 else:
758 766 self.ui.status(
759 767 _(b'pulling subrepo %s from %s\n')
760 768 % (subrelpath(self), util.hidepassword(srcurl))
761 769 )
762 770 cleansub = self.storeclean(srcurl)
763 exchange.pull(self._repo, getpeer())
771 peer = getpeer()
772 try:
773 exchange.pull(self._repo, peer)
774 finally:
775 peer.close()
764 776 if cleansub:
765 777 # keep the repo clean after pull
766 778 self._cachestorehash(srcurl)
767 779 return False
768 780
769 781 @annotatesubrepoerror
770 782 def get(self, state, overwrite=False):
771 783 inrepo = self._get(state)
772 784 source, revision, kind = state
773 785 repo = self._repo
774 786 repo.ui.debug(b"getting subrepo %s\n" % self._path)
775 787 if inrepo:
776 788 urepo = repo.unfiltered()
777 789 ctx = urepo[revision]
778 790 if ctx.hidden():
779 791 urepo.ui.warn(
780 792 _(b'revision %s in subrepository "%s" is hidden\n')
781 793 % (revision[0:12], self._path)
782 794 )
783 795 repo = urepo
784 796 if overwrite:
785 797 merge.clean_update(repo[revision])
786 798 else:
787 799 merge.update(repo[revision])
788 800
789 801 @annotatesubrepoerror
790 802 def merge(self, state):
791 803 self._get(state)
792 804 cur = self._repo[b'.']
793 805 dst = self._repo[state[1]]
794 806 anc = dst.ancestor(cur)
795 807
796 808 def mergefunc():
797 809 if anc == cur and dst.branch() == cur.branch():
798 810 self.ui.debug(
799 811 b'updating subrepository "%s"\n' % subrelpath(self)
800 812 )
801 813 hg.update(self._repo, state[1])
802 814 elif anc == dst:
803 815 self.ui.debug(
804 816 b'skipping subrepository "%s"\n' % subrelpath(self)
805 817 )
806 818 else:
807 819 self.ui.debug(
808 820 b'merging subrepository "%s"\n' % subrelpath(self)
809 821 )
810 822 hg.merge(dst, remind=False)
811 823
812 824 wctx = self._repo[None]
813 825 if self.dirty():
814 826 if anc != dst:
815 827 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
816 828 mergefunc()
817 829 else:
818 830 mergefunc()
819 831 else:
820 832 mergefunc()
821 833
822 834 @annotatesubrepoerror
823 835 def push(self, opts):
824 836 force = opts.get(b'force')
825 837 newbranch = opts.get(b'new_branch')
826 838 ssh = opts.get(b'ssh')
827 839
828 840 # push subrepos depth-first for coherent ordering
829 841 c = self._repo[b'.']
830 842 subs = c.substate # only repos that are committed
831 843 for s in sorted(subs):
832 844 if c.sub(s).push(opts) == 0:
833 845 return False
834 846
835 847 dsturl = _abssource(self._repo, True)
836 848 if not force:
837 849 if self.storeclean(dsturl):
838 850 self.ui.status(
839 851 _(b'no changes made to subrepo %s since last push to %s\n')
840 852 % (subrelpath(self), util.hidepassword(dsturl))
841 853 )
842 854 return None
843 855 self.ui.status(
844 856 _(b'pushing subrepo %s to %s\n')
845 857 % (subrelpath(self), util.hidepassword(dsturl))
846 858 )
847 859 other = hg.peer(self._repo, {b'ssh': ssh}, dsturl)
848 res = exchange.push(self._repo, other, force, newbranch=newbranch)
860 try:
861 res = exchange.push(self._repo, other, force, newbranch=newbranch)
862 finally:
863 other.close()
849 864
850 865 # the repo is now clean
851 866 self._cachestorehash(dsturl)
852 867 return res.cgresult
853 868
854 869 @annotatesubrepoerror
855 870 def outgoing(self, ui, dest, opts):
856 871 if b'rev' in opts or b'branch' in opts:
857 872 opts = copy.copy(opts)
858 873 opts.pop(b'rev', None)
859 874 opts.pop(b'branch', None)
860 875 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
861 876
862 877 @annotatesubrepoerror
863 878 def incoming(self, ui, source, opts):
864 879 if b'rev' in opts or b'branch' in opts:
865 880 opts = copy.copy(opts)
866 881 opts.pop(b'rev', None)
867 882 opts.pop(b'branch', None)
868 883 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
869 884
870 885 @annotatesubrepoerror
871 886 def files(self):
872 887 rev = self._state[1]
873 888 ctx = self._repo[rev]
874 889 return ctx.manifest().keys()
875 890
876 891 def filedata(self, name, decode):
877 892 rev = self._state[1]
878 893 data = self._repo[rev][name].data()
879 894 if decode:
880 895 data = self._repo.wwritedata(name, data)
881 896 return data
882 897
883 898 def fileflags(self, name):
884 899 rev = self._state[1]
885 900 ctx = self._repo[rev]
886 901 return ctx.flags(name)
887 902
888 903 @annotatesubrepoerror
889 904 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
890 905 # If the parent context is a workingctx, use the workingctx here for
891 906 # consistency.
892 907 if self._ctx.rev() is None:
893 908 ctx = self._repo[None]
894 909 else:
895 910 rev = self._state[1]
896 911 ctx = self._repo[rev]
897 912 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt, subrepos)
898 913
899 914 @annotatesubrepoerror
900 915 def matchfileset(self, cwd, expr, badfn=None):
901 916 if self._ctx.rev() is None:
902 917 ctx = self._repo[None]
903 918 else:
904 919 rev = self._state[1]
905 920 ctx = self._repo[rev]
906 921
907 922 matchers = [ctx.matchfileset(cwd, expr, badfn=badfn)]
908 923
909 924 for subpath in ctx.substate:
910 925 sub = ctx.sub(subpath)
911 926
912 927 try:
913 928 sm = sub.matchfileset(cwd, expr, badfn=badfn)
914 929 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn)
915 930 matchers.append(pm)
916 931 except error.LookupError:
917 932 self.ui.status(
918 933 _(b"skipping missing subrepository: %s\n")
919 934 % self.wvfs.reljoin(reporelpath(self), subpath)
920 935 )
921 936 if len(matchers) == 1:
922 937 return matchers[0]
923 938 return matchmod.unionmatcher(matchers)
924 939
925 940 def walk(self, match):
926 941 ctx = self._repo[None]
927 942 return ctx.walk(match)
928 943
929 944 @annotatesubrepoerror
930 945 def forget(self, match, prefix, uipathfn, dryrun, interactive):
931 946 return cmdutil.forget(
932 947 self.ui,
933 948 self._repo,
934 949 match,
935 950 prefix,
936 951 uipathfn,
937 952 True,
938 953 dryrun=dryrun,
939 954 interactive=interactive,
940 955 )
941 956
942 957 @annotatesubrepoerror
943 958 def removefiles(
944 959 self,
945 960 matcher,
946 961 prefix,
947 962 uipathfn,
948 963 after,
949 964 force,
950 965 subrepos,
951 966 dryrun,
952 967 warnings,
953 968 ):
954 969 return cmdutil.remove(
955 970 self.ui,
956 971 self._repo,
957 972 matcher,
958 973 prefix,
959 974 uipathfn,
960 975 after,
961 976 force,
962 977 subrepos,
963 978 dryrun,
964 979 )
965 980
966 981 @annotatesubrepoerror
967 982 def revert(self, substate, *pats, **opts):
968 983 # reverting a subrepo is a 2 step process:
969 984 # 1. if the no_backup is not set, revert all modified
970 985 # files inside the subrepo
971 986 # 2. update the subrepo to the revision specified in
972 987 # the corresponding substate dictionary
973 988 self.ui.status(_(b'reverting subrepo %s\n') % substate[0])
974 989 if not opts.get('no_backup'):
975 990 # Revert all files on the subrepo, creating backups
976 991 # Note that this will not recursively revert subrepos
977 992 # We could do it if there was a set:subrepos() predicate
978 993 opts = opts.copy()
979 994 opts['date'] = None
980 995 opts['rev'] = substate[1]
981 996
982 997 self.filerevert(*pats, **opts)
983 998
984 999 # Update the repo to the revision specified in the given substate
985 1000 if not opts.get('dry_run'):
986 1001 self.get(substate, overwrite=True)
987 1002
988 1003 def filerevert(self, *pats, **opts):
989 1004 ctx = self._repo[opts['rev']]
990 1005 if opts.get('all'):
991 1006 pats = [b'set:modified()']
992 1007 else:
993 1008 pats = []
994 1009 cmdutil.revert(self.ui, self._repo, ctx, *pats, **opts)
995 1010
996 1011 def shortid(self, revid):
997 1012 return revid[:12]
998 1013
999 1014 @annotatesubrepoerror
1000 1015 def unshare(self):
1001 1016 # subrepo inherently violates our import layering rules
1002 1017 # because it wants to make repo objects from deep inside the stack
1003 1018 # so we manually delay the circular imports to not break
1004 1019 # scripts that don't use our demand-loading
1005 1020 global hg
1006 1021 from . import hg as h
1007 1022
1008 1023 hg = h
1009 1024
1010 1025 # Nothing prevents a user from sharing in a repo, and then making that a
1011 1026 # subrepo. Alternately, the previous unshare attempt may have failed
1012 1027 # part way through. So recurse whether or not this layer is shared.
1013 1028 if self._repo.shared():
1014 1029 self.ui.status(_(b"unsharing subrepo '%s'\n") % self._relpath)
1015 1030
1016 1031 hg.unshare(self.ui, self._repo)
1017 1032
1018 1033 def verify(self, onpush=False):
1019 1034 try:
1020 1035 rev = self._state[1]
1021 1036 ctx = self._repo.unfiltered()[rev]
1022 1037 if ctx.hidden():
1023 1038 # Since hidden revisions aren't pushed/pulled, it seems worth an
1024 1039 # explicit warning.
1025 1040 msg = _(b"subrepo '%s' is hidden in revision %s") % (
1026 1041 self._relpath,
1027 1042 short(self._ctx.node()),
1028 1043 )
1029 1044
1030 1045 if onpush:
1031 1046 raise error.Abort(msg)
1032 1047 else:
1033 1048 self._repo.ui.warn(b'%s\n' % msg)
1034 1049 return 0
1035 1050 except error.RepoLookupError:
1036 1051 # A missing subrepo revision may be a case of needing to pull it, so
1037 1052 # don't treat this as an error for `hg verify`.
1038 1053 msg = _(b"subrepo '%s' not found in revision %s") % (
1039 1054 self._relpath,
1040 1055 short(self._ctx.node()),
1041 1056 )
1042 1057
1043 1058 if onpush:
1044 1059 raise error.Abort(msg)
1045 1060 else:
1046 1061 self._repo.ui.warn(b'%s\n' % msg)
1047 1062 return 0
1048 1063
1049 1064 @propertycache
1050 1065 def wvfs(self):
1051 1066 """return own wvfs for efficiency and consistency"""
1052 1067 return self._repo.wvfs
1053 1068
1054 1069 @propertycache
1055 1070 def _relpath(self):
1056 1071 """return path to this subrepository as seen from outermost repository"""
1057 1072 # Keep consistent dir separators by avoiding vfs.join(self._path)
1058 1073 return reporelpath(self._repo)
1059 1074
1060 1075
1061 1076 class svnsubrepo(abstractsubrepo):
1062 1077 def __init__(self, ctx, path, state, allowcreate):
1063 1078 super(svnsubrepo, self).__init__(ctx, path)
1064 1079 self._state = state
1065 1080 self._exe = procutil.findexe(b'svn')
1066 1081 if not self._exe:
1067 1082 raise error.Abort(
1068 1083 _(b"'svn' executable not found for subrepo '%s'") % self._path
1069 1084 )
1070 1085
1071 1086 def _svncommand(self, commands, filename=b'', failok=False):
1072 1087 cmd = [self._exe]
1073 1088 extrakw = {}
1074 1089 if not self.ui.interactive():
1075 1090 # Making stdin be a pipe should prevent svn from behaving
1076 1091 # interactively even if we can't pass --non-interactive.
1077 1092 extrakw['stdin'] = subprocess.PIPE
1078 1093 # Starting in svn 1.5 --non-interactive is a global flag
1079 1094 # instead of being per-command, but we need to support 1.4 so
1080 1095 # we have to be intelligent about what commands take
1081 1096 # --non-interactive.
1082 1097 if commands[0] in (b'update', b'checkout', b'commit'):
1083 1098 cmd.append(b'--non-interactive')
1084 1099 cmd.extend(commands)
1085 1100 if filename is not None:
1086 1101 path = self.wvfs.reljoin(
1087 1102 self._ctx.repo().origroot, self._path, filename
1088 1103 )
1089 1104 cmd.append(path)
1090 1105 env = dict(encoding.environ)
1091 1106 # Avoid localized output, preserve current locale for everything else.
1092 1107 lc_all = env.get(b'LC_ALL')
1093 1108 if lc_all:
1094 1109 env[b'LANG'] = lc_all
1095 1110 del env[b'LC_ALL']
1096 1111 env[b'LC_MESSAGES'] = b'C'
1097 1112 p = subprocess.Popen(
1098 1113 pycompat.rapply(procutil.tonativestr, cmd),
1099 1114 bufsize=-1,
1100 1115 close_fds=procutil.closefds,
1101 1116 stdout=subprocess.PIPE,
1102 1117 stderr=subprocess.PIPE,
1103 1118 env=procutil.tonativeenv(env),
1104 1119 **extrakw
1105 1120 )
1106 1121 stdout, stderr = map(util.fromnativeeol, p.communicate())
1107 1122 stderr = stderr.strip()
1108 1123 if not failok:
1109 1124 if p.returncode:
1110 1125 raise error.Abort(
1111 1126 stderr or b'exited with code %d' % p.returncode
1112 1127 )
1113 1128 if stderr:
1114 1129 self.ui.warn(stderr + b'\n')
1115 1130 return stdout, stderr
1116 1131
1117 1132 @propertycache
1118 1133 def _svnversion(self):
1119 1134 output, err = self._svncommand(
1120 1135 [b'--version', b'--quiet'], filename=None
1121 1136 )
1122 1137 m = re.search(br'^(\d+)\.(\d+)', output)
1123 1138 if not m:
1124 1139 raise error.Abort(_(b'cannot retrieve svn tool version'))
1125 1140 return (int(m.group(1)), int(m.group(2)))
1126 1141
1127 1142 def _svnmissing(self):
1128 1143 return not self.wvfs.exists(b'.svn')
1129 1144
1130 1145 def _wcrevs(self):
1131 1146 # Get the working directory revision as well as the last
1132 1147 # commit revision so we can compare the subrepo state with
1133 1148 # both. We used to store the working directory one.
1134 1149 output, err = self._svncommand([b'info', b'--xml'])
1135 1150 doc = xml.dom.minidom.parseString(output)
1136 1151 entries = doc.getElementsByTagName('entry')
1137 1152 lastrev, rev = b'0', b'0'
1138 1153 if entries:
1139 1154 rev = pycompat.bytestr(entries[0].getAttribute('revision')) or b'0'
1140 1155 commits = entries[0].getElementsByTagName('commit')
1141 1156 if commits:
1142 1157 lastrev = (
1143 1158 pycompat.bytestr(commits[0].getAttribute('revision'))
1144 1159 or b'0'
1145 1160 )
1146 1161 return (lastrev, rev)
1147 1162
1148 1163 def _wcrev(self):
1149 1164 return self._wcrevs()[0]
1150 1165
1151 1166 def _wcchanged(self):
1152 1167 """Return (changes, extchanges, missing) where changes is True
1153 1168 if the working directory was changed, extchanges is
1154 1169 True if any of these changes concern an external entry and missing
1155 1170 is True if any change is a missing entry.
1156 1171 """
1157 1172 output, err = self._svncommand([b'status', b'--xml'])
1158 1173 externals, changes, missing = [], [], []
1159 1174 doc = xml.dom.minidom.parseString(output)
1160 1175 for e in doc.getElementsByTagName('entry'):
1161 1176 s = e.getElementsByTagName('wc-status')
1162 1177 if not s:
1163 1178 continue
1164 1179 item = s[0].getAttribute('item')
1165 1180 props = s[0].getAttribute('props')
1166 1181 path = e.getAttribute('path').encode('utf8')
1167 1182 if item == 'external':
1168 1183 externals.append(path)
1169 1184 elif item == 'missing':
1170 1185 missing.append(path)
1171 1186 if (
1172 1187 item
1173 1188 not in (
1174 1189 '',
1175 1190 'normal',
1176 1191 'unversioned',
1177 1192 'external',
1178 1193 )
1179 1194 or props not in ('', 'none', 'normal')
1180 1195 ):
1181 1196 changes.append(path)
1182 1197 for path in changes:
1183 1198 for ext in externals:
1184 1199 if path == ext or path.startswith(ext + pycompat.ossep):
1185 1200 return True, True, bool(missing)
1186 1201 return bool(changes), False, bool(missing)
1187 1202
1188 1203 @annotatesubrepoerror
1189 1204 def dirty(self, ignoreupdate=False, missing=False):
1190 1205 if self._svnmissing():
1191 1206 return self._state[1] != b''
1192 1207 wcchanged = self._wcchanged()
1193 1208 changed = wcchanged[0] or (missing and wcchanged[2])
1194 1209 if not changed:
1195 1210 if self._state[1] in self._wcrevs() or ignoreupdate:
1196 1211 return False
1197 1212 return True
1198 1213
1199 1214 def basestate(self):
1200 1215 lastrev, rev = self._wcrevs()
1201 1216 if lastrev != rev:
1202 1217 # Last committed rev is not the same than rev. We would
1203 1218 # like to take lastrev but we do not know if the subrepo
1204 1219 # URL exists at lastrev. Test it and fallback to rev it
1205 1220 # is not there.
1206 1221 try:
1207 1222 self._svncommand(
1208 1223 [b'list', b'%s@%s' % (self._state[0], lastrev)]
1209 1224 )
1210 1225 return lastrev
1211 1226 except error.Abort:
1212 1227 pass
1213 1228 return rev
1214 1229
1215 1230 @annotatesubrepoerror
1216 1231 def commit(self, text, user, date):
1217 1232 # user and date are out of our hands since svn is centralized
1218 1233 changed, extchanged, missing = self._wcchanged()
1219 1234 if not changed:
1220 1235 return self.basestate()
1221 1236 if extchanged:
1222 1237 # Do not try to commit externals
1223 1238 raise error.Abort(_(b'cannot commit svn externals'))
1224 1239 if missing:
1225 1240 # svn can commit with missing entries but aborting like hg
1226 1241 # seems a better approach.
1227 1242 raise error.Abort(_(b'cannot commit missing svn entries'))
1228 1243 commitinfo, err = self._svncommand([b'commit', b'-m', text])
1229 1244 self.ui.status(commitinfo)
1230 1245 newrev = re.search(b'Committed revision ([0-9]+).', commitinfo)
1231 1246 if not newrev:
1232 1247 if not commitinfo.strip():
1233 1248 # Sometimes, our definition of "changed" differs from
1234 1249 # svn one. For instance, svn ignores missing files
1235 1250 # when committing. If there are only missing files, no
1236 1251 # commit is made, no output and no error code.
1237 1252 raise error.Abort(_(b'failed to commit svn changes'))
1238 1253 raise error.Abort(commitinfo.splitlines()[-1])
1239 1254 newrev = newrev.groups()[0]
1240 1255 self.ui.status(self._svncommand([b'update', b'-r', newrev])[0])
1241 1256 return newrev
1242 1257
1243 1258 @annotatesubrepoerror
1244 1259 def remove(self):
1245 1260 if self.dirty():
1246 1261 self.ui.warn(
1247 1262 _(b'not removing repo %s because it has changes.\n')
1248 1263 % self._path
1249 1264 )
1250 1265 return
1251 1266 self.ui.note(_(b'removing subrepo %s\n') % self._path)
1252 1267
1253 1268 self.wvfs.rmtree(forcibly=True)
1254 1269 try:
1255 1270 pwvfs = self._ctx.repo().wvfs
1256 1271 pwvfs.removedirs(pwvfs.dirname(self._path))
1257 1272 except OSError:
1258 1273 pass
1259 1274
1260 1275 @annotatesubrepoerror
1261 1276 def get(self, state, overwrite=False):
1262 1277 if overwrite:
1263 1278 self._svncommand([b'revert', b'--recursive'])
1264 1279 args = [b'checkout']
1265 1280 if self._svnversion >= (1, 5):
1266 1281 args.append(b'--force')
1267 1282 # The revision must be specified at the end of the URL to properly
1268 1283 # update to a directory which has since been deleted and recreated.
1269 1284 args.append(b'%s@%s' % (state[0], state[1]))
1270 1285
1271 1286 # SEC: check that the ssh url is safe
1272 1287 util.checksafessh(state[0])
1273 1288
1274 1289 status, err = self._svncommand(args, failok=True)
1275 1290 _sanitize(self.ui, self.wvfs, b'.svn')
1276 1291 if not re.search(b'Checked out revision [0-9]+.', status):
1277 1292 if b'is already a working copy for a different URL' in err and (
1278 1293 self._wcchanged()[:2] == (False, False)
1279 1294 ):
1280 1295 # obstructed but clean working copy, so just blow it away.
1281 1296 self.remove()
1282 1297 self.get(state, overwrite=False)
1283 1298 return
1284 1299 raise error.Abort((status or err).splitlines()[-1])
1285 1300 self.ui.status(status)
1286 1301
1287 1302 @annotatesubrepoerror
1288 1303 def merge(self, state):
1289 1304 old = self._state[1]
1290 1305 new = state[1]
1291 1306 wcrev = self._wcrev()
1292 1307 if new != wcrev:
1293 1308 dirty = old == wcrev or self._wcchanged()[0]
1294 1309 if _updateprompt(self.ui, self, dirty, wcrev, new):
1295 1310 self.get(state, False)
1296 1311
1297 1312 def push(self, opts):
1298 1313 # push is a no-op for SVN
1299 1314 return True
1300 1315
1301 1316 @annotatesubrepoerror
1302 1317 def files(self):
1303 1318 output = self._svncommand([b'list', b'--recursive', b'--xml'])[0]
1304 1319 doc = xml.dom.minidom.parseString(output)
1305 1320 paths = []
1306 1321 for e in doc.getElementsByTagName('entry'):
1307 1322 kind = pycompat.bytestr(e.getAttribute('kind'))
1308 1323 if kind != b'file':
1309 1324 continue
1310 1325 name = ''.join(
1311 1326 c.data
1312 1327 for c in e.getElementsByTagName('name')[0].childNodes
1313 1328 if c.nodeType == c.TEXT_NODE
1314 1329 )
1315 1330 paths.append(name.encode('utf8'))
1316 1331 return paths
1317 1332
1318 1333 def filedata(self, name, decode):
1319 1334 return self._svncommand([b'cat'], name)[0]
1320 1335
1321 1336
1322 1337 class gitsubrepo(abstractsubrepo):
1323 1338 def __init__(self, ctx, path, state, allowcreate):
1324 1339 super(gitsubrepo, self).__init__(ctx, path)
1325 1340 self._state = state
1326 1341 self._abspath = ctx.repo().wjoin(path)
1327 1342 self._subparent = ctx.repo()
1328 1343 self._ensuregit()
1329 1344
1330 1345 def _ensuregit(self):
1331 1346 try:
1332 1347 self._gitexecutable = b'git'
1333 1348 out, err = self._gitnodir([b'--version'])
1334 1349 except OSError as e:
1335 1350 genericerror = _(b"error executing git for subrepo '%s': %s")
1336 1351 notfoundhint = _(b"check git is installed and in your PATH")
1337 1352 if e.errno != errno.ENOENT:
1338 1353 raise error.Abort(
1339 1354 genericerror % (self._path, encoding.strtolocal(e.strerror))
1340 1355 )
1341 1356 elif pycompat.iswindows:
1342 1357 try:
1343 1358 self._gitexecutable = b'git.cmd'
1344 1359 out, err = self._gitnodir([b'--version'])
1345 1360 except OSError as e2:
1346 1361 if e2.errno == errno.ENOENT:
1347 1362 raise error.Abort(
1348 1363 _(
1349 1364 b"couldn't find 'git' or 'git.cmd'"
1350 1365 b" for subrepo '%s'"
1351 1366 )
1352 1367 % self._path,
1353 1368 hint=notfoundhint,
1354 1369 )
1355 1370 else:
1356 1371 raise error.Abort(
1357 1372 genericerror
1358 1373 % (self._path, encoding.strtolocal(e2.strerror))
1359 1374 )
1360 1375 else:
1361 1376 raise error.Abort(
1362 1377 _(b"couldn't find git for subrepo '%s'") % self._path,
1363 1378 hint=notfoundhint,
1364 1379 )
1365 1380 versionstatus = self._checkversion(out)
1366 1381 if versionstatus == b'unknown':
1367 1382 self.ui.warn(_(b'cannot retrieve git version\n'))
1368 1383 elif versionstatus == b'abort':
1369 1384 raise error.Abort(
1370 1385 _(b'git subrepo requires at least 1.6.0 or later')
1371 1386 )
1372 1387 elif versionstatus == b'warning':
1373 1388 self.ui.warn(_(b'git subrepo requires at least 1.6.0 or later\n'))
1374 1389
1375 1390 @staticmethod
1376 1391 def _gitversion(out):
1377 1392 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1378 1393 if m:
1379 1394 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1380 1395
1381 1396 m = re.search(br'^git version (\d+)\.(\d+)', out)
1382 1397 if m:
1383 1398 return (int(m.group(1)), int(m.group(2)), 0)
1384 1399
1385 1400 return -1
1386 1401
1387 1402 @staticmethod
1388 1403 def _checkversion(out):
1389 1404 """ensure git version is new enough
1390 1405
1391 1406 >>> _checkversion = gitsubrepo._checkversion
1392 1407 >>> _checkversion(b'git version 1.6.0')
1393 1408 'ok'
1394 1409 >>> _checkversion(b'git version 1.8.5')
1395 1410 'ok'
1396 1411 >>> _checkversion(b'git version 1.4.0')
1397 1412 'abort'
1398 1413 >>> _checkversion(b'git version 1.5.0')
1399 1414 'warning'
1400 1415 >>> _checkversion(b'git version 1.9-rc0')
1401 1416 'ok'
1402 1417 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1403 1418 'ok'
1404 1419 >>> _checkversion(b'git version 1.9.0.GIT')
1405 1420 'ok'
1406 1421 >>> _checkversion(b'git version 12345')
1407 1422 'unknown'
1408 1423 >>> _checkversion(b'no')
1409 1424 'unknown'
1410 1425 """
1411 1426 version = gitsubrepo._gitversion(out)
1412 1427 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1413 1428 # despite the docstring comment. For now, error on 1.4.0, warn on
1414 1429 # 1.5.0 but attempt to continue.
1415 1430 if version == -1:
1416 1431 return b'unknown'
1417 1432 if version < (1, 5, 0):
1418 1433 return b'abort'
1419 1434 elif version < (1, 6, 0):
1420 1435 return b'warning'
1421 1436 return b'ok'
1422 1437
1423 1438 def _gitcommand(self, commands, env=None, stream=False):
1424 1439 return self._gitdir(commands, env=env, stream=stream)[0]
1425 1440
1426 1441 def _gitdir(self, commands, env=None, stream=False):
1427 1442 return self._gitnodir(
1428 1443 commands, env=env, stream=stream, cwd=self._abspath
1429 1444 )
1430 1445
1431 1446 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1432 1447 """Calls the git command
1433 1448
1434 1449 The methods tries to call the git command. versions prior to 1.6.0
1435 1450 are not supported and very probably fail.
1436 1451 """
1437 1452 self.ui.debug(b'%s: git %s\n' % (self._relpath, b' '.join(commands)))
1438 1453 if env is None:
1439 1454 env = encoding.environ.copy()
1440 1455 # disable localization for Git output (issue5176)
1441 1456 env[b'LC_ALL'] = b'C'
1442 1457 # fix for Git CVE-2015-7545
1443 1458 if b'GIT_ALLOW_PROTOCOL' not in env:
1444 1459 env[b'GIT_ALLOW_PROTOCOL'] = b'file:git:http:https:ssh'
1445 1460 # unless ui.quiet is set, print git's stderr,
1446 1461 # which is mostly progress and useful info
1447 1462 errpipe = None
1448 1463 if self.ui.quiet:
1449 1464 errpipe = pycompat.open(os.devnull, b'w')
1450 1465 if self.ui._colormode and len(commands) and commands[0] == b"diff":
1451 1466 # insert the argument in the front,
1452 1467 # the end of git diff arguments is used for paths
1453 1468 commands.insert(1, b'--color')
1454 1469 p = subprocess.Popen(
1455 1470 pycompat.rapply(
1456 1471 procutil.tonativestr, [self._gitexecutable] + commands
1457 1472 ),
1458 1473 bufsize=-1,
1459 1474 cwd=pycompat.rapply(procutil.tonativestr, cwd),
1460 1475 env=procutil.tonativeenv(env),
1461 1476 close_fds=procutil.closefds,
1462 1477 stdout=subprocess.PIPE,
1463 1478 stderr=errpipe,
1464 1479 )
1465 1480 if stream:
1466 1481 return p.stdout, None
1467 1482
1468 1483 retdata = p.stdout.read().strip()
1469 1484 # wait for the child to exit to avoid race condition.
1470 1485 p.wait()
1471 1486
1472 1487 if p.returncode != 0 and p.returncode != 1:
1473 1488 # there are certain error codes that are ok
1474 1489 command = commands[0]
1475 1490 if command in (b'cat-file', b'symbolic-ref'):
1476 1491 return retdata, p.returncode
1477 1492 # for all others, abort
1478 1493 raise error.Abort(
1479 1494 _(b'git %s error %d in %s')
1480 1495 % (command, p.returncode, self._relpath)
1481 1496 )
1482 1497
1483 1498 return retdata, p.returncode
1484 1499
1485 1500 def _gitmissing(self):
1486 1501 return not self.wvfs.exists(b'.git')
1487 1502
1488 1503 def _gitstate(self):
1489 1504 return self._gitcommand([b'rev-parse', b'HEAD'])
1490 1505
1491 1506 def _gitcurrentbranch(self):
1492 1507 current, err = self._gitdir([b'symbolic-ref', b'HEAD', b'--quiet'])
1493 1508 if err:
1494 1509 current = None
1495 1510 return current
1496 1511
1497 1512 def _gitremote(self, remote):
1498 1513 out = self._gitcommand([b'remote', b'show', b'-n', remote])
1499 1514 line = out.split(b'\n')[1]
1500 1515 i = line.index(b'URL: ') + len(b'URL: ')
1501 1516 return line[i:]
1502 1517
1503 1518 def _githavelocally(self, revision):
1504 1519 out, code = self._gitdir([b'cat-file', b'-e', revision])
1505 1520 return code == 0
1506 1521
1507 1522 def _gitisancestor(self, r1, r2):
1508 1523 base = self._gitcommand([b'merge-base', r1, r2])
1509 1524 return base == r1
1510 1525
1511 1526 def _gitisbare(self):
1512 1527 return self._gitcommand([b'config', b'--bool', b'core.bare']) == b'true'
1513 1528
1514 1529 def _gitupdatestat(self):
1515 1530 """This must be run before git diff-index.
1516 1531 diff-index only looks at changes to file stat;
1517 1532 this command looks at file contents and updates the stat."""
1518 1533 self._gitcommand([b'update-index', b'-q', b'--refresh'])
1519 1534
1520 1535 def _gitbranchmap(self):
1521 1536 """returns 2 things:
1522 1537 a map from git branch to revision
1523 1538 a map from revision to branches"""
1524 1539 branch2rev = {}
1525 1540 rev2branch = {}
1526 1541
1527 1542 out = self._gitcommand(
1528 1543 [b'for-each-ref', b'--format', b'%(objectname) %(refname)']
1529 1544 )
1530 1545 for line in out.split(b'\n'):
1531 1546 revision, ref = line.split(b' ')
1532 1547 if not ref.startswith(b'refs/heads/') and not ref.startswith(
1533 1548 b'refs/remotes/'
1534 1549 ):
1535 1550 continue
1536 1551 if ref.startswith(b'refs/remotes/') and ref.endswith(b'/HEAD'):
1537 1552 continue # ignore remote/HEAD redirects
1538 1553 branch2rev[ref] = revision
1539 1554 rev2branch.setdefault(revision, []).append(ref)
1540 1555 return branch2rev, rev2branch
1541 1556
1542 1557 def _gittracking(self, branches):
1543 1558 """return map of remote branch to local tracking branch"""
1544 1559 # assumes no more than one local tracking branch for each remote
1545 1560 tracking = {}
1546 1561 for b in branches:
1547 1562 if b.startswith(b'refs/remotes/'):
1548 1563 continue
1549 1564 bname = b.split(b'/', 2)[2]
1550 1565 remote = self._gitcommand([b'config', b'branch.%s.remote' % bname])
1551 1566 if remote:
1552 1567 ref = self._gitcommand([b'config', b'branch.%s.merge' % bname])
1553 1568 tracking[
1554 1569 b'refs/remotes/%s/%s' % (remote, ref.split(b'/', 2)[2])
1555 1570 ] = b
1556 1571 return tracking
1557 1572
1558 1573 def _abssource(self, source):
1559 1574 if b'://' not in source:
1560 1575 # recognize the scp syntax as an absolute source
1561 1576 colon = source.find(b':')
1562 1577 if colon != -1 and b'/' not in source[:colon]:
1563 1578 return source
1564 1579 self._subsource = source
1565 1580 return _abssource(self)
1566 1581
1567 1582 def _fetch(self, source, revision):
1568 1583 if self._gitmissing():
1569 1584 # SEC: check for safe ssh url
1570 1585 util.checksafessh(source)
1571 1586
1572 1587 source = self._abssource(source)
1573 1588 self.ui.status(
1574 1589 _(b'cloning subrepo %s from %s\n') % (self._relpath, source)
1575 1590 )
1576 1591 self._gitnodir([b'clone', source, self._abspath])
1577 1592 if self._githavelocally(revision):
1578 1593 return
1579 1594 self.ui.status(
1580 1595 _(b'pulling subrepo %s from %s\n')
1581 1596 % (self._relpath, self._gitremote(b'origin'))
1582 1597 )
1583 1598 # try only origin: the originally cloned repo
1584 1599 self._gitcommand([b'fetch'])
1585 1600 if not self._githavelocally(revision):
1586 1601 raise error.Abort(
1587 1602 _(b'revision %s does not exist in subrepository "%s"\n')
1588 1603 % (revision, self._relpath)
1589 1604 )
1590 1605
1591 1606 @annotatesubrepoerror
1592 1607 def dirty(self, ignoreupdate=False, missing=False):
1593 1608 if self._gitmissing():
1594 1609 return self._state[1] != b''
1595 1610 if self._gitisbare():
1596 1611 return True
1597 1612 if not ignoreupdate and self._state[1] != self._gitstate():
1598 1613 # different version checked out
1599 1614 return True
1600 1615 # check for staged changes or modified files; ignore untracked files
1601 1616 self._gitupdatestat()
1602 1617 out, code = self._gitdir([b'diff-index', b'--quiet', b'HEAD'])
1603 1618 return code == 1
1604 1619
1605 1620 def basestate(self):
1606 1621 return self._gitstate()
1607 1622
1608 1623 @annotatesubrepoerror
1609 1624 def get(self, state, overwrite=False):
1610 1625 source, revision, kind = state
1611 1626 if not revision:
1612 1627 self.remove()
1613 1628 return
1614 1629 self._fetch(source, revision)
1615 1630 # if the repo was set to be bare, unbare it
1616 1631 if self._gitisbare():
1617 1632 self._gitcommand([b'config', b'core.bare', b'false'])
1618 1633 if self._gitstate() == revision:
1619 1634 self._gitcommand([b'reset', b'--hard', b'HEAD'])
1620 1635 return
1621 1636 elif self._gitstate() == revision:
1622 1637 if overwrite:
1623 1638 # first reset the index to unmark new files for commit, because
1624 1639 # reset --hard will otherwise throw away files added for commit,
1625 1640 # not just unmark them.
1626 1641 self._gitcommand([b'reset', b'HEAD'])
1627 1642 self._gitcommand([b'reset', b'--hard', b'HEAD'])
1628 1643 return
1629 1644 branch2rev, rev2branch = self._gitbranchmap()
1630 1645
1631 1646 def checkout(args):
1632 1647 cmd = [b'checkout']
1633 1648 if overwrite:
1634 1649 # first reset the index to unmark new files for commit, because
1635 1650 # the -f option will otherwise throw away files added for
1636 1651 # commit, not just unmark them.
1637 1652 self._gitcommand([b'reset', b'HEAD'])
1638 1653 cmd.append(b'-f')
1639 1654 self._gitcommand(cmd + args)
1640 1655 _sanitize(self.ui, self.wvfs, b'.git')
1641 1656
1642 1657 def rawcheckout():
1643 1658 # no branch to checkout, check it out with no branch
1644 1659 self.ui.warn(
1645 1660 _(b'checking out detached HEAD in subrepository "%s"\n')
1646 1661 % self._relpath
1647 1662 )
1648 1663 self.ui.warn(
1649 1664 _(b'check out a git branch if you intend to make changes\n')
1650 1665 )
1651 1666 checkout([b'-q', revision])
1652 1667
1653 1668 if revision not in rev2branch:
1654 1669 rawcheckout()
1655 1670 return
1656 1671 branches = rev2branch[revision]
1657 1672 firstlocalbranch = None
1658 1673 for b in branches:
1659 1674 if b == b'refs/heads/master':
1660 1675 # master trumps all other branches
1661 1676 checkout([b'refs/heads/master'])
1662 1677 return
1663 1678 if not firstlocalbranch and not b.startswith(b'refs/remotes/'):
1664 1679 firstlocalbranch = b
1665 1680 if firstlocalbranch:
1666 1681 checkout([firstlocalbranch])
1667 1682 return
1668 1683
1669 1684 tracking = self._gittracking(branch2rev.keys())
1670 1685 # choose a remote branch already tracked if possible
1671 1686 remote = branches[0]
1672 1687 if remote not in tracking:
1673 1688 for b in branches:
1674 1689 if b in tracking:
1675 1690 remote = b
1676 1691 break
1677 1692
1678 1693 if remote not in tracking:
1679 1694 # create a new local tracking branch
1680 1695 local = remote.split(b'/', 3)[3]
1681 1696 checkout([b'-b', local, remote])
1682 1697 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1683 1698 # When updating to a tracked remote branch,
1684 1699 # if the local tracking branch is downstream of it,
1685 1700 # a normal `git pull` would have performed a "fast-forward merge"
1686 1701 # which is equivalent to updating the local branch to the remote.
1687 1702 # Since we are only looking at branching at update, we need to
1688 1703 # detect this situation and perform this action lazily.
1689 1704 if tracking[remote] != self._gitcurrentbranch():
1690 1705 checkout([tracking[remote]])
1691 1706 self._gitcommand([b'merge', b'--ff', remote])
1692 1707 _sanitize(self.ui, self.wvfs, b'.git')
1693 1708 else:
1694 1709 # a real merge would be required, just checkout the revision
1695 1710 rawcheckout()
1696 1711
1697 1712 @annotatesubrepoerror
1698 1713 def commit(self, text, user, date):
1699 1714 if self._gitmissing():
1700 1715 raise error.Abort(_(b"subrepo %s is missing") % self._relpath)
1701 1716 cmd = [b'commit', b'-a', b'-m', text]
1702 1717 env = encoding.environ.copy()
1703 1718 if user:
1704 1719 cmd += [b'--author', user]
1705 1720 if date:
1706 1721 # git's date parser silently ignores when seconds < 1e9
1707 1722 # convert to ISO8601
1708 1723 env[b'GIT_AUTHOR_DATE'] = dateutil.datestr(
1709 1724 date, b'%Y-%m-%dT%H:%M:%S %1%2'
1710 1725 )
1711 1726 self._gitcommand(cmd, env=env)
1712 1727 # make sure commit works otherwise HEAD might not exist under certain
1713 1728 # circumstances
1714 1729 return self._gitstate()
1715 1730
1716 1731 @annotatesubrepoerror
1717 1732 def merge(self, state):
1718 1733 source, revision, kind = state
1719 1734 self._fetch(source, revision)
1720 1735 base = self._gitcommand([b'merge-base', revision, self._state[1]])
1721 1736 self._gitupdatestat()
1722 1737 out, code = self._gitdir([b'diff-index', b'--quiet', b'HEAD'])
1723 1738
1724 1739 def mergefunc():
1725 1740 if base == revision:
1726 1741 self.get(state) # fast forward merge
1727 1742 elif base != self._state[1]:
1728 1743 self._gitcommand([b'merge', b'--no-commit', revision])
1729 1744 _sanitize(self.ui, self.wvfs, b'.git')
1730 1745
1731 1746 if self.dirty():
1732 1747 if self._gitstate() != revision:
1733 1748 dirty = self._gitstate() == self._state[1] or code != 0
1734 1749 if _updateprompt(
1735 1750 self.ui, self, dirty, self._state[1][:7], revision[:7]
1736 1751 ):
1737 1752 mergefunc()
1738 1753 else:
1739 1754 mergefunc()
1740 1755
1741 1756 @annotatesubrepoerror
1742 1757 def push(self, opts):
1743 1758 force = opts.get(b'force')
1744 1759
1745 1760 if not self._state[1]:
1746 1761 return True
1747 1762 if self._gitmissing():
1748 1763 raise error.Abort(_(b"subrepo %s is missing") % self._relpath)
1749 1764 # if a branch in origin contains the revision, nothing to do
1750 1765 branch2rev, rev2branch = self._gitbranchmap()
1751 1766 if self._state[1] in rev2branch:
1752 1767 for b in rev2branch[self._state[1]]:
1753 1768 if b.startswith(b'refs/remotes/origin/'):
1754 1769 return True
1755 1770 for b, revision in pycompat.iteritems(branch2rev):
1756 1771 if b.startswith(b'refs/remotes/origin/'):
1757 1772 if self._gitisancestor(self._state[1], revision):
1758 1773 return True
1759 1774 # otherwise, try to push the currently checked out branch
1760 1775 cmd = [b'push']
1761 1776 if force:
1762 1777 cmd.append(b'--force')
1763 1778
1764 1779 current = self._gitcurrentbranch()
1765 1780 if current:
1766 1781 # determine if the current branch is even useful
1767 1782 if not self._gitisancestor(self._state[1], current):
1768 1783 self.ui.warn(
1769 1784 _(
1770 1785 b'unrelated git branch checked out '
1771 1786 b'in subrepository "%s"\n'
1772 1787 )
1773 1788 % self._relpath
1774 1789 )
1775 1790 return False
1776 1791 self.ui.status(
1777 1792 _(b'pushing branch %s of subrepository "%s"\n')
1778 1793 % (current.split(b'/', 2)[2], self._relpath)
1779 1794 )
1780 1795 ret = self._gitdir(cmd + [b'origin', current])
1781 1796 return ret[1] == 0
1782 1797 else:
1783 1798 self.ui.warn(
1784 1799 _(
1785 1800 b'no branch checked out in subrepository "%s"\n'
1786 1801 b'cannot push revision %s\n'
1787 1802 )
1788 1803 % (self._relpath, self._state[1])
1789 1804 )
1790 1805 return False
1791 1806
1792 1807 @annotatesubrepoerror
1793 1808 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
1794 1809 if self._gitmissing():
1795 1810 return []
1796 1811
1797 1812 s = self.status(None, unknown=True, clean=True)
1798 1813
1799 1814 tracked = set()
1800 1815 # dirstates 'amn' warn, 'r' is added again
1801 1816 for l in (s.modified, s.added, s.deleted, s.clean):
1802 1817 tracked.update(l)
1803 1818
1804 1819 # Unknown files not of interest will be rejected by the matcher
1805 1820 files = s.unknown
1806 1821 files.extend(match.files())
1807 1822
1808 1823 rejected = []
1809 1824
1810 1825 files = [f for f in sorted(set(files)) if match(f)]
1811 1826 for f in files:
1812 1827 exact = match.exact(f)
1813 1828 command = [b"add"]
1814 1829 if exact:
1815 1830 command.append(b"-f") # should be added, even if ignored
1816 1831 if ui.verbose or not exact:
1817 1832 ui.status(_(b'adding %s\n') % uipathfn(f))
1818 1833
1819 1834 if f in tracked: # hg prints 'adding' even if already tracked
1820 1835 if exact:
1821 1836 rejected.append(f)
1822 1837 continue
1823 1838 if not opts.get('dry_run'):
1824 1839 self._gitcommand(command + [f])
1825 1840
1826 1841 for f in rejected:
1827 1842 ui.warn(_(b"%s already tracked!\n") % uipathfn(f))
1828 1843
1829 1844 return rejected
1830 1845
1831 1846 @annotatesubrepoerror
1832 1847 def remove(self):
1833 1848 if self._gitmissing():
1834 1849 return
1835 1850 if self.dirty():
1836 1851 self.ui.warn(
1837 1852 _(b'not removing repo %s because it has changes.\n')
1838 1853 % self._relpath
1839 1854 )
1840 1855 return
1841 1856 # we can't fully delete the repository as it may contain
1842 1857 # local-only history
1843 1858 self.ui.note(_(b'removing subrepo %s\n') % self._relpath)
1844 1859 self._gitcommand([b'config', b'core.bare', b'true'])
1845 1860 for f, kind in self.wvfs.readdir():
1846 1861 if f == b'.git':
1847 1862 continue
1848 1863 if kind == stat.S_IFDIR:
1849 1864 self.wvfs.rmtree(f)
1850 1865 else:
1851 1866 self.wvfs.unlink(f)
1852 1867
1853 1868 def archive(self, archiver, prefix, match=None, decode=True):
1854 1869 total = 0
1855 1870 source, revision = self._state
1856 1871 if not revision:
1857 1872 return total
1858 1873 self._fetch(source, revision)
1859 1874
1860 1875 # Parse git's native archive command.
1861 1876 # This should be much faster than manually traversing the trees
1862 1877 # and objects with many subprocess calls.
1863 1878 tarstream = self._gitcommand([b'archive', revision], stream=True)
1864 1879 tar = tarfile.open(fileobj=tarstream, mode='r|')
1865 1880 relpath = subrelpath(self)
1866 1881 progress = self.ui.makeprogress(
1867 1882 _(b'archiving (%s)') % relpath, unit=_(b'files')
1868 1883 )
1869 1884 progress.update(0)
1870 1885 for info in tar:
1871 1886 if info.isdir():
1872 1887 continue
1873 1888 bname = pycompat.fsencode(info.name)
1874 1889 if match and not match(bname):
1875 1890 continue
1876 1891 if info.issym():
1877 1892 data = info.linkname
1878 1893 else:
1879 1894 data = tar.extractfile(info).read()
1880 1895 archiver.addfile(prefix + bname, info.mode, info.issym(), data)
1881 1896 total += 1
1882 1897 progress.increment()
1883 1898 progress.complete()
1884 1899 return total
1885 1900
1886 1901 @annotatesubrepoerror
1887 1902 def cat(self, match, fm, fntemplate, prefix, **opts):
1888 1903 rev = self._state[1]
1889 1904 if match.anypats():
1890 1905 return 1 # No support for include/exclude yet
1891 1906
1892 1907 if not match.files():
1893 1908 return 1
1894 1909
1895 1910 # TODO: add support for non-plain formatter (see cmdutil.cat())
1896 1911 for f in match.files():
1897 1912 output = self._gitcommand([b"show", b"%s:%s" % (rev, f)])
1898 1913 fp = cmdutil.makefileobj(
1899 1914 self._ctx, fntemplate, pathname=self.wvfs.reljoin(prefix, f)
1900 1915 )
1901 1916 fp.write(output)
1902 1917 fp.close()
1903 1918 return 0
1904 1919
1905 1920 @annotatesubrepoerror
1906 1921 def status(self, rev2, **opts):
1907 1922 rev1 = self._state[1]
1908 1923 if self._gitmissing() or not rev1:
1909 1924 # if the repo is missing, return no results
1910 1925 return scmutil.status([], [], [], [], [], [], [])
1911 1926 modified, added, removed = [], [], []
1912 1927 self._gitupdatestat()
1913 1928 if rev2:
1914 1929 command = [b'diff-tree', b'--no-renames', b'-r', rev1, rev2]
1915 1930 else:
1916 1931 command = [b'diff-index', b'--no-renames', rev1]
1917 1932 out = self._gitcommand(command)
1918 1933 for line in out.split(b'\n'):
1919 1934 tab = line.find(b'\t')
1920 1935 if tab == -1:
1921 1936 continue
1922 1937 status, f = line[tab - 1 : tab], line[tab + 1 :]
1923 1938 if status == b'M':
1924 1939 modified.append(f)
1925 1940 elif status == b'A':
1926 1941 added.append(f)
1927 1942 elif status == b'D':
1928 1943 removed.append(f)
1929 1944
1930 1945 deleted, unknown, ignored, clean = [], [], [], []
1931 1946
1932 1947 command = [b'status', b'--porcelain', b'-z']
1933 1948 if opts.get('unknown'):
1934 1949 command += [b'--untracked-files=all']
1935 1950 if opts.get('ignored'):
1936 1951 command += [b'--ignored']
1937 1952 out = self._gitcommand(command)
1938 1953
1939 1954 changedfiles = set()
1940 1955 changedfiles.update(modified)
1941 1956 changedfiles.update(added)
1942 1957 changedfiles.update(removed)
1943 1958 for line in out.split(b'\0'):
1944 1959 if not line:
1945 1960 continue
1946 1961 st = line[0:2]
1947 1962 # moves and copies show 2 files on one line
1948 1963 if line.find(b'\0') >= 0:
1949 1964 filename1, filename2 = line[3:].split(b'\0')
1950 1965 else:
1951 1966 filename1 = line[3:]
1952 1967 filename2 = None
1953 1968
1954 1969 changedfiles.add(filename1)
1955 1970 if filename2:
1956 1971 changedfiles.add(filename2)
1957 1972
1958 1973 if st == b'??':
1959 1974 unknown.append(filename1)
1960 1975 elif st == b'!!':
1961 1976 ignored.append(filename1)
1962 1977
1963 1978 if opts.get('clean'):
1964 1979 out = self._gitcommand([b'ls-files'])
1965 1980 for f in out.split(b'\n'):
1966 1981 if not f in changedfiles:
1967 1982 clean.append(f)
1968 1983
1969 1984 return scmutil.status(
1970 1985 modified, added, removed, deleted, unknown, ignored, clean
1971 1986 )
1972 1987
1973 1988 @annotatesubrepoerror
1974 1989 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1975 1990 node1 = self._state[1]
1976 1991 cmd = [b'diff', b'--no-renames']
1977 1992 if opts['stat']:
1978 1993 cmd.append(b'--stat')
1979 1994 else:
1980 1995 # for Git, this also implies '-p'
1981 1996 cmd.append(b'-U%d' % diffopts.context)
1982 1997
1983 1998 if diffopts.noprefix:
1984 1999 cmd.extend(
1985 2000 [b'--src-prefix=%s/' % prefix, b'--dst-prefix=%s/' % prefix]
1986 2001 )
1987 2002 else:
1988 2003 cmd.extend(
1989 2004 [b'--src-prefix=a/%s/' % prefix, b'--dst-prefix=b/%s/' % prefix]
1990 2005 )
1991 2006
1992 2007 if diffopts.ignorews:
1993 2008 cmd.append(b'--ignore-all-space')
1994 2009 if diffopts.ignorewsamount:
1995 2010 cmd.append(b'--ignore-space-change')
1996 2011 if (
1997 2012 self._gitversion(self._gitcommand([b'--version'])) >= (1, 8, 4)
1998 2013 and diffopts.ignoreblanklines
1999 2014 ):
2000 2015 cmd.append(b'--ignore-blank-lines')
2001 2016
2002 2017 cmd.append(node1)
2003 2018 if node2:
2004 2019 cmd.append(node2)
2005 2020
2006 2021 output = b""
2007 2022 if match.always():
2008 2023 output += self._gitcommand(cmd) + b'\n'
2009 2024 else:
2010 2025 st = self.status(node2)
2011 2026 files = [
2012 2027 f
2013 2028 for sublist in (st.modified, st.added, st.removed)
2014 2029 for f in sublist
2015 2030 ]
2016 2031 for f in files:
2017 2032 if match(f):
2018 2033 output += self._gitcommand(cmd + [b'--', f]) + b'\n'
2019 2034
2020 2035 if output.strip():
2021 2036 ui.write(output)
2022 2037
2023 2038 @annotatesubrepoerror
2024 2039 def revert(self, substate, *pats, **opts):
2025 2040 self.ui.status(_(b'reverting subrepo %s\n') % substate[0])
2026 2041 if not opts.get('no_backup'):
2027 2042 status = self.status(None)
2028 2043 names = status.modified
2029 2044 for name in names:
2030 2045 # backuppath() expects a path relative to the parent repo (the
2031 2046 # repo that ui.origbackuppath is relative to)
2032 2047 parentname = os.path.join(self._path, name)
2033 2048 bakname = scmutil.backuppath(
2034 2049 self.ui, self._subparent, parentname
2035 2050 )
2036 2051 self.ui.note(
2037 2052 _(b'saving current version of %s as %s\n')
2038 2053 % (name, os.path.relpath(bakname))
2039 2054 )
2040 2055 util.rename(self.wvfs.join(name), bakname)
2041 2056
2042 2057 if not opts.get('dry_run'):
2043 2058 self.get(substate, overwrite=True)
2044 2059 return []
2045 2060
2046 2061 def shortid(self, revid):
2047 2062 return revid[:7]
2048 2063
2049 2064
2050 2065 types = {
2051 2066 b'hg': hgsubrepo,
2052 2067 b'svn': svnsubrepo,
2053 2068 b'git': gitsubrepo,
2054 2069 }
@@ -1,30 +1,33 b''
1 1 from __future__ import absolute_import
2 2
3 3 from mercurial.i18n import _
4 4 from mercurial import (
5 5 hg,
6 6 registrar,
7 7 )
8 8
9 9 cmdtable = {}
10 10 command = registrar.command(cmdtable)
11 11
12 12
13 13 @command(b'getflogheads', [], b'path')
14 14 def getflogheads(ui, repo, path):
15 15 """
16 16 Extension printing a remotefilelog's heads
17 17
18 18 Used for testing purpose
19 19 """
20 20
21 21 dest = repo.ui.expandpath(b'default')
22 22 peer = hg.peer(repo, {}, dest)
23 23
24 flogheads = peer.x_rfl_getflogheads(path)
24 try:
25 flogheads = peer.x_rfl_getflogheads(path)
26 finally:
27 peer.close()
25 28
26 29 if flogheads:
27 30 for head in flogheads:
28 31 ui.write(head + b'\n')
29 32 else:
30 33 ui.write(_(b'EMPTY\n'))
@@ -1,2444 +1,2445 b''
1 1 > do_push()
2 2 > {
3 3 > user=$1
4 4 > shift
5 5 > echo "Pushing as user $user"
6 6 > echo 'hgrc = """'
7 7 > sed -n '/\[[ha]/,$p' b/.hg/hgrc | grep -v fakegroups.py
8 8 > echo '"""'
9 9 > if test -f acl.config; then
10 10 > echo 'acl.config = """'
11 11 > cat acl.config
12 12 > echo '"""'
13 13 > fi
14 14 > # On AIX /etc/profile sets LOGNAME read-only. So
15 15 > # LOGNAME=$user hg --cws a --debug push ../b
16 16 > # fails with "This variable is read only."
17 17 > # Use env to work around this.
18 18 > env LOGNAME=$user hg --cwd a --debug push ../b $*
19 19 > hg --cwd b rollback
20 20 > hg --cwd b --quiet tip
21 21 > echo
22 22 > }
23 23
24 24 > cat > posixgetuser.py <<'EOF'
25 25 > import getpass
26 26 > from mercurial import pycompat
27 27 > from mercurial.utils import procutil
28 28 > def posixgetuser():
29 29 > return pycompat.fsencode(getpass.getuser())
30 30 > if not pycompat.isposix:
31 31 > procutil.getuser = posixgetuser # forcibly trust $LOGNAME
32 32 > EOF
33 33
34 34 > init_config()
35 35 > {
36 36 > cat > fakegroups.py <<EOF
37 37 > from hgext import acl
38 38 > def fakegetusers(ui, group):
39 39 > try:
40 40 > return acl._getusersorig(ui, group)
41 41 > except BaseException:
42 42 > return [b"fred", b"betty"]
43 43 > acl._getusersorig = acl._getusers
44 44 > acl._getusers = fakegetusers
45 45 > EOF
46 46 > rm -f acl.config
47 47 > cat > $config <<EOF
48 48 > [hooks]
49 49 > pretxnchangegroup.acl = python:hgext.acl.hook
50 50 > prepushkey.acl = python:hgext.acl.hook
51 51 > [acl]
52 52 > sources = push
53 53 > [extensions]
54 54 > f=`pwd`/fakegroups.py
55 55 > posixgetuser=$TESTTMP/posixgetuser.py
56 56 > EOF
57 57 > }
58 58
59 59 $ hg init a
60 60 $ cd a
61 61 $ mkdir foo foo/Bar quux
62 62 $ echo 'in foo' > foo/file.txt
63 63 $ echo 'in foo/Bar' > foo/Bar/file.txt
64 64 $ echo 'in quux' > quux/file.py
65 65 $ hg add -q
66 66 $ hg ci -m 'add files' -d '1000000 0'
67 67 $ echo >> foo/file.txt
68 68 $ hg ci -m 'change foo/file' -d '1000001 0'
69 69 $ echo >> foo/Bar/file.txt
70 70 $ hg ci -m 'change foo/Bar/file' -d '1000002 0'
71 71 $ echo >> quux/file.py
72 72 $ hg ci -m 'change quux/file' -d '1000003 0'
73 73 $ hg tip --quiet
74 74 3:911600dab2ae
75 75
76 76 $ cd ..
77 77 $ hg clone -r 0 a b
78 78 adding changesets
79 79 adding manifests
80 80 adding file changes
81 81 added 1 changesets with 3 changes to 3 files
82 82 new changesets 6675d58eff77
83 83 updating to branch default
84 84 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 85
86 86 $ config=b/.hg/hgrc
87 87 $ cat >> "$config" <<EOF
88 88 > [extensions]
89 89 > posixgetuser=$TESTTMP/posixgetuser.py
90 90 > EOF
91 91
92 92 Extension disabled for lack of a hook
93 93
94 94 $ do_push fred
95 95 Pushing as user fred
96 96 hgrc = """
97 97 """
98 98 pushing to ../b
99 99 query 1; heads
100 100 searching for changes
101 101 all remote heads known locally
102 102 listing keys for "phases"
103 103 checking for updated bookmarks
104 104 listing keys for "bookmarks"
105 105 listing keys for "bookmarks"
106 106 3 changesets found
107 107 list of changesets:
108 108 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
109 109 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
110 110 911600dab2ae7a9baff75958b84fe606851ce955
111 111 bundle2-output-bundle: "HG20", 5 parts total
112 112 bundle2-output-part: "replycaps" 207 bytes payload
113 113 bundle2-output-part: "check:phases" 24 bytes payload
114 114 bundle2-output-part: "check:updated-heads" streamed payload
115 115 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
116 116 bundle2-output-part: "phase-heads" 24 bytes payload
117 117 bundle2-input-bundle: with-transaction
118 118 bundle2-input-part: "replycaps" supported
119 119 bundle2-input-part: total payload size 207
120 120 bundle2-input-part: "check:phases" supported
121 121 bundle2-input-part: total payload size 24
122 122 bundle2-input-part: "check:updated-heads" supported
123 123 bundle2-input-part: total payload size 20
124 124 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
125 125 adding changesets
126 126 add changeset ef1ea85a6374
127 127 add changeset f9cafe1212c8
128 128 add changeset 911600dab2ae
129 129 adding manifests
130 130 adding file changes
131 131 adding foo/Bar/file.txt revisions
132 132 adding foo/file.txt revisions
133 133 adding quux/file.py revisions
134 134 bundle2-input-part: total payload size 1553
135 135 bundle2-input-part: "phase-heads" supported
136 136 bundle2-input-part: total payload size 24
137 137 bundle2-input-bundle: 5 parts total
138 138 updating the branch cache
139 139 added 3 changesets with 3 changes to 3 files
140 140 bundle2-output-bundle: "HG20", 1 parts total
141 141 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
142 142 bundle2-input-bundle: no-transaction
143 143 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
144 144 bundle2-input-bundle: 1 parts total
145 145 listing keys for "phases"
146 146 repository tip rolled back to revision 0 (undo push)
147 147 0:6675d58eff77
148 148
149 149
150 150 $ echo '[hooks]' >> $config
151 151 $ echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config
152 152 $ echo 'prepushkey.acl = python:hgext.acl.hook' >> $config
153 153
154 154 Extension disabled for lack of acl.sources
155 155
156 156 $ do_push fred
157 157 Pushing as user fred
158 158 hgrc = """
159 159 [hooks]
160 160 pretxnchangegroup.acl = python:hgext.acl.hook
161 161 prepushkey.acl = python:hgext.acl.hook
162 162 """
163 163 pushing to ../b
164 164 query 1; heads
165 165 searching for changes
166 166 all remote heads known locally
167 167 listing keys for "phases"
168 168 checking for updated bookmarks
169 169 listing keys for "bookmarks"
170 170 invalid branch cache (served): tip differs
171 171 listing keys for "bookmarks"
172 172 3 changesets found
173 173 list of changesets:
174 174 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
175 175 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
176 176 911600dab2ae7a9baff75958b84fe606851ce955
177 177 bundle2-output-bundle: "HG20", 5 parts total
178 178 bundle2-output-part: "replycaps" 207 bytes payload
179 179 bundle2-output-part: "check:phases" 24 bytes payload
180 180 bundle2-output-part: "check:updated-heads" streamed payload
181 181 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
182 182 bundle2-output-part: "phase-heads" 24 bytes payload
183 183 bundle2-input-bundle: with-transaction
184 184 bundle2-input-part: "replycaps" supported
185 185 bundle2-input-part: total payload size 207
186 186 bundle2-input-part: "check:phases" supported
187 187 bundle2-input-part: total payload size 24
188 188 bundle2-input-part: "check:updated-heads" supported
189 189 bundle2-input-part: total payload size 20
190 190 invalid branch cache (served): tip differs
191 191 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
192 192 adding changesets
193 193 add changeset ef1ea85a6374
194 194 add changeset f9cafe1212c8
195 195 add changeset 911600dab2ae
196 196 adding manifests
197 197 adding file changes
198 198 adding foo/Bar/file.txt revisions
199 199 adding foo/file.txt revisions
200 200 adding quux/file.py revisions
201 201 calling hook pretxnchangegroup.acl: hgext.acl.hook
202 202 acl: changes have source "push" - skipping
203 203 bundle2-input-part: total payload size 1553
204 204 bundle2-input-part: "phase-heads" supported
205 205 bundle2-input-part: total payload size 24
206 206 bundle2-input-bundle: 5 parts total
207 207 truncating cache/rbc-revs-v1 to 8
208 208 updating the branch cache
209 209 added 3 changesets with 3 changes to 3 files
210 210 bundle2-output-bundle: "HG20", 1 parts total
211 211 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
212 212 bundle2-input-bundle: no-transaction
213 213 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
214 214 bundle2-input-bundle: 1 parts total
215 215 listing keys for "phases"
216 216 repository tip rolled back to revision 0 (undo push)
217 217 0:6675d58eff77
218 218
219 219
220 220 No [acl.allow]/[acl.deny]
221 221
222 222 $ echo '[acl]' >> $config
223 223 $ echo 'sources = push' >> $config
224 224 $ do_push fred
225 225 Pushing as user fred
226 226 hgrc = """
227 227 [hooks]
228 228 pretxnchangegroup.acl = python:hgext.acl.hook
229 229 prepushkey.acl = python:hgext.acl.hook
230 230 [acl]
231 231 sources = push
232 232 """
233 233 pushing to ../b
234 234 query 1; heads
235 235 searching for changes
236 236 all remote heads known locally
237 237 listing keys for "phases"
238 238 checking for updated bookmarks
239 239 listing keys for "bookmarks"
240 240 invalid branch cache (served): tip differs
241 241 listing keys for "bookmarks"
242 242 3 changesets found
243 243 list of changesets:
244 244 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
245 245 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
246 246 911600dab2ae7a9baff75958b84fe606851ce955
247 247 bundle2-output-bundle: "HG20", 5 parts total
248 248 bundle2-output-part: "replycaps" 207 bytes payload
249 249 bundle2-output-part: "check:phases" 24 bytes payload
250 250 bundle2-output-part: "check:updated-heads" streamed payload
251 251 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
252 252 bundle2-output-part: "phase-heads" 24 bytes payload
253 253 bundle2-input-bundle: with-transaction
254 254 bundle2-input-part: "replycaps" supported
255 255 bundle2-input-part: total payload size 207
256 256 bundle2-input-part: "check:phases" supported
257 257 bundle2-input-part: total payload size 24
258 258 bundle2-input-part: "check:updated-heads" supported
259 259 bundle2-input-part: total payload size 20
260 260 invalid branch cache (served): tip differs
261 261 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
262 262 adding changesets
263 263 add changeset ef1ea85a6374
264 264 add changeset f9cafe1212c8
265 265 add changeset 911600dab2ae
266 266 adding manifests
267 267 adding file changes
268 268 adding foo/Bar/file.txt revisions
269 269 adding foo/file.txt revisions
270 270 adding quux/file.py revisions
271 271 calling hook pretxnchangegroup.acl: hgext.acl.hook
272 272 acl: checking access for user "fred"
273 273 acl: acl.allow.branches not enabled
274 274 acl: acl.deny.branches not enabled
275 275 acl: acl.allow not enabled
276 276 acl: acl.deny not enabled
277 277 acl: branch access granted: "ef1ea85a6374" on branch "default"
278 278 acl: path access granted: "ef1ea85a6374"
279 279 acl: branch access granted: "f9cafe1212c8" on branch "default"
280 280 acl: path access granted: "f9cafe1212c8"
281 281 acl: branch access granted: "911600dab2ae" on branch "default"
282 282 acl: path access granted: "911600dab2ae"
283 283 bundle2-input-part: total payload size 1553
284 284 bundle2-input-part: "phase-heads" supported
285 285 bundle2-input-part: total payload size 24
286 286 bundle2-input-bundle: 5 parts total
287 287 truncating cache/rbc-revs-v1 to 8
288 288 updating the branch cache
289 289 added 3 changesets with 3 changes to 3 files
290 290 bundle2-output-bundle: "HG20", 1 parts total
291 291 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
292 292 bundle2-input-bundle: no-transaction
293 293 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
294 294 bundle2-input-bundle: 1 parts total
295 295 listing keys for "phases"
296 296 repository tip rolled back to revision 0 (undo push)
297 297 0:6675d58eff77
298 298
299 299
300 300 Empty [acl.allow]
301 301
302 302 $ echo '[acl.allow]' >> $config
303 303 $ do_push fred
304 304 Pushing as user fred
305 305 hgrc = """
306 306 [hooks]
307 307 pretxnchangegroup.acl = python:hgext.acl.hook
308 308 prepushkey.acl = python:hgext.acl.hook
309 309 [acl]
310 310 sources = push
311 311 [acl.allow]
312 312 """
313 313 pushing to ../b
314 314 query 1; heads
315 315 searching for changes
316 316 all remote heads known locally
317 317 listing keys for "phases"
318 318 checking for updated bookmarks
319 319 listing keys for "bookmarks"
320 320 invalid branch cache (served): tip differs
321 321 listing keys for "bookmarks"
322 322 3 changesets found
323 323 list of changesets:
324 324 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
325 325 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
326 326 911600dab2ae7a9baff75958b84fe606851ce955
327 327 bundle2-output-bundle: "HG20", 5 parts total
328 328 bundle2-output-part: "replycaps" 207 bytes payload
329 329 bundle2-output-part: "check:phases" 24 bytes payload
330 330 bundle2-output-part: "check:updated-heads" streamed payload
331 331 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
332 332 bundle2-output-part: "phase-heads" 24 bytes payload
333 333 bundle2-input-bundle: with-transaction
334 334 bundle2-input-part: "replycaps" supported
335 335 bundle2-input-part: total payload size 207
336 336 bundle2-input-part: "check:phases" supported
337 337 bundle2-input-part: total payload size 24
338 338 bundle2-input-part: "check:updated-heads" supported
339 339 bundle2-input-part: total payload size 20
340 340 invalid branch cache (served): tip differs
341 341 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
342 342 adding changesets
343 343 add changeset ef1ea85a6374
344 344 add changeset f9cafe1212c8
345 345 add changeset 911600dab2ae
346 346 adding manifests
347 347 adding file changes
348 348 adding foo/Bar/file.txt revisions
349 349 adding foo/file.txt revisions
350 350 adding quux/file.py revisions
351 351 calling hook pretxnchangegroup.acl: hgext.acl.hook
352 352 acl: checking access for user "fred"
353 353 acl: acl.allow.branches not enabled
354 354 acl: acl.deny.branches not enabled
355 355 acl: acl.allow enabled, 0 entries for user fred
356 356 acl: acl.deny not enabled
357 357 acl: branch access granted: "ef1ea85a6374" on branch "default"
358 358 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
359 359 bundle2-input-part: total payload size 1553
360 360 bundle2-input-part: total payload size 24
361 361 bundle2-input-bundle: 5 parts total
362 362 transaction abort!
363 363 rollback completed
364 truncating cache/rbc-revs-v1 to 8
364 365 abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
365 366 no rollback information available
366 367 0:6675d58eff77
367 368
368 369
369 370 fred is allowed inside foo/
370 371
371 372 $ echo 'foo/** = fred' >> $config
372 373 $ do_push fred
373 374 Pushing as user fred
374 375 hgrc = """
375 376 [hooks]
376 377 pretxnchangegroup.acl = python:hgext.acl.hook
377 378 prepushkey.acl = python:hgext.acl.hook
378 379 [acl]
379 380 sources = push
380 381 [acl.allow]
381 382 foo/** = fred
382 383 """
383 384 pushing to ../b
384 385 query 1; heads
385 386 searching for changes
386 387 all remote heads known locally
387 388 listing keys for "phases"
388 389 checking for updated bookmarks
389 390 listing keys for "bookmarks"
390 391 invalid branch cache (served): tip differs
391 392 listing keys for "bookmarks"
392 393 3 changesets found
393 394 list of changesets:
394 395 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
395 396 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
396 397 911600dab2ae7a9baff75958b84fe606851ce955
397 398 bundle2-output-bundle: "HG20", 5 parts total
398 399 bundle2-output-part: "replycaps" 207 bytes payload
399 400 bundle2-output-part: "check:phases" 24 bytes payload
400 401 bundle2-output-part: "check:updated-heads" streamed payload
401 402 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
402 403 bundle2-output-part: "phase-heads" 24 bytes payload
403 404 bundle2-input-bundle: with-transaction
404 405 bundle2-input-part: "replycaps" supported
405 406 bundle2-input-part: total payload size 207
406 407 bundle2-input-part: "check:phases" supported
407 408 bundle2-input-part: total payload size 24
408 409 bundle2-input-part: "check:updated-heads" supported
409 410 bundle2-input-part: total payload size 20
410 411 invalid branch cache (served): tip differs
411 412 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
412 413 adding changesets
413 414 add changeset ef1ea85a6374
414 415 add changeset f9cafe1212c8
415 416 add changeset 911600dab2ae
416 417 adding manifests
417 418 adding file changes
418 419 adding foo/Bar/file.txt revisions
419 420 adding foo/file.txt revisions
420 421 adding quux/file.py revisions
421 422 calling hook pretxnchangegroup.acl: hgext.acl.hook
422 423 acl: checking access for user "fred"
423 424 acl: acl.allow.branches not enabled
424 425 acl: acl.deny.branches not enabled
425 426 acl: acl.allow enabled, 1 entries for user fred
426 427 acl: acl.deny not enabled
427 428 acl: branch access granted: "ef1ea85a6374" on branch "default"
428 429 acl: path access granted: "ef1ea85a6374"
429 430 acl: branch access granted: "f9cafe1212c8" on branch "default"
430 431 acl: path access granted: "f9cafe1212c8"
431 432 acl: branch access granted: "911600dab2ae" on branch "default"
432 433 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
433 434 bundle2-input-part: total payload size 1553
434 435 bundle2-input-part: total payload size 24
435 436 bundle2-input-bundle: 5 parts total
436 437 transaction abort!
437 438 rollback completed
438 439 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
439 440 no rollback information available
440 441 0:6675d58eff77
441 442
442 443
443 444 Empty [acl.deny]
444 445
445 446 $ echo '[acl.deny]' >> $config
446 447 $ do_push barney
447 448 Pushing as user barney
448 449 hgrc = """
449 450 [hooks]
450 451 pretxnchangegroup.acl = python:hgext.acl.hook
451 452 prepushkey.acl = python:hgext.acl.hook
452 453 [acl]
453 454 sources = push
454 455 [acl.allow]
455 456 foo/** = fred
456 457 [acl.deny]
457 458 """
458 459 pushing to ../b
459 460 query 1; heads
460 461 searching for changes
461 462 all remote heads known locally
462 463 listing keys for "phases"
463 464 checking for updated bookmarks
464 465 listing keys for "bookmarks"
465 466 invalid branch cache (served): tip differs
466 467 listing keys for "bookmarks"
467 468 3 changesets found
468 469 list of changesets:
469 470 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
470 471 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
471 472 911600dab2ae7a9baff75958b84fe606851ce955
472 473 bundle2-output-bundle: "HG20", 5 parts total
473 474 bundle2-output-part: "replycaps" 207 bytes payload
474 475 bundle2-output-part: "check:phases" 24 bytes payload
475 476 bundle2-output-part: "check:updated-heads" streamed payload
476 477 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
477 478 bundle2-output-part: "phase-heads" 24 bytes payload
478 479 bundle2-input-bundle: with-transaction
479 480 bundle2-input-part: "replycaps" supported
480 481 bundle2-input-part: total payload size 207
481 482 bundle2-input-part: "check:phases" supported
482 483 bundle2-input-part: total payload size 24
483 484 bundle2-input-part: "check:updated-heads" supported
484 485 bundle2-input-part: total payload size 20
485 486 invalid branch cache (served): tip differs
486 487 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
487 488 adding changesets
488 489 add changeset ef1ea85a6374
489 490 add changeset f9cafe1212c8
490 491 add changeset 911600dab2ae
491 492 adding manifests
492 493 adding file changes
493 494 adding foo/Bar/file.txt revisions
494 495 adding foo/file.txt revisions
495 496 adding quux/file.py revisions
496 497 calling hook pretxnchangegroup.acl: hgext.acl.hook
497 498 acl: checking access for user "barney"
498 499 acl: acl.allow.branches not enabled
499 500 acl: acl.deny.branches not enabled
500 501 acl: acl.allow enabled, 0 entries for user barney
501 502 acl: acl.deny enabled, 0 entries for user barney
502 503 acl: branch access granted: "ef1ea85a6374" on branch "default"
503 504 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
504 505 bundle2-input-part: total payload size 1553
505 506 bundle2-input-part: total payload size 24
506 507 bundle2-input-bundle: 5 parts total
507 508 transaction abort!
508 509 rollback completed
509 510 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
510 511 no rollback information available
511 512 0:6675d58eff77
512 513
513 514
514 515 fred is allowed inside foo/, but not foo/bar/ (case matters)
515 516
516 517 $ echo 'foo/bar/** = fred' >> $config
517 518 $ do_push fred
518 519 Pushing as user fred
519 520 hgrc = """
520 521 [hooks]
521 522 pretxnchangegroup.acl = python:hgext.acl.hook
522 523 prepushkey.acl = python:hgext.acl.hook
523 524 [acl]
524 525 sources = push
525 526 [acl.allow]
526 527 foo/** = fred
527 528 [acl.deny]
528 529 foo/bar/** = fred
529 530 """
530 531 pushing to ../b
531 532 query 1; heads
532 533 searching for changes
533 534 all remote heads known locally
534 535 listing keys for "phases"
535 536 checking for updated bookmarks
536 537 listing keys for "bookmarks"
537 538 invalid branch cache (served): tip differs
538 539 listing keys for "bookmarks"
539 540 3 changesets found
540 541 list of changesets:
541 542 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
542 543 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
543 544 911600dab2ae7a9baff75958b84fe606851ce955
544 545 bundle2-output-bundle: "HG20", 5 parts total
545 546 bundle2-output-part: "replycaps" 207 bytes payload
546 547 bundle2-output-part: "check:phases" 24 bytes payload
547 548 bundle2-output-part: "check:updated-heads" streamed payload
548 549 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
549 550 bundle2-output-part: "phase-heads" 24 bytes payload
550 551 bundle2-input-bundle: with-transaction
551 552 bundle2-input-part: "replycaps" supported
552 553 bundle2-input-part: total payload size 207
553 554 bundle2-input-part: "check:phases" supported
554 555 bundle2-input-part: total payload size 24
555 556 bundle2-input-part: "check:updated-heads" supported
556 557 bundle2-input-part: total payload size 20
557 558 invalid branch cache (served): tip differs
558 559 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
559 560 adding changesets
560 561 add changeset ef1ea85a6374
561 562 add changeset f9cafe1212c8
562 563 add changeset 911600dab2ae
563 564 adding manifests
564 565 adding file changes
565 566 adding foo/Bar/file.txt revisions
566 567 adding foo/file.txt revisions
567 568 adding quux/file.py revisions
568 569 calling hook pretxnchangegroup.acl: hgext.acl.hook
569 570 acl: checking access for user "fred"
570 571 acl: acl.allow.branches not enabled
571 572 acl: acl.deny.branches not enabled
572 573 acl: acl.allow enabled, 1 entries for user fred
573 574 acl: acl.deny enabled, 1 entries for user fred
574 575 acl: branch access granted: "ef1ea85a6374" on branch "default"
575 576 acl: path access granted: "ef1ea85a6374"
576 577 acl: branch access granted: "f9cafe1212c8" on branch "default"
577 578 acl: path access granted: "f9cafe1212c8"
578 579 acl: branch access granted: "911600dab2ae" on branch "default"
579 580 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
580 581 bundle2-input-part: total payload size 1553
581 582 bundle2-input-part: total payload size 24
582 583 bundle2-input-bundle: 5 parts total
583 584 transaction abort!
584 585 rollback completed
585 586 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
586 587 no rollback information available
587 588 0:6675d58eff77
588 589
589 590
590 591 fred is allowed inside foo/, but not foo/Bar/
591 592
592 593 $ echo 'foo/Bar/** = fred' >> $config
593 594 $ do_push fred
594 595 Pushing as user fred
595 596 hgrc = """
596 597 [hooks]
597 598 pretxnchangegroup.acl = python:hgext.acl.hook
598 599 prepushkey.acl = python:hgext.acl.hook
599 600 [acl]
600 601 sources = push
601 602 [acl.allow]
602 603 foo/** = fred
603 604 [acl.deny]
604 605 foo/bar/** = fred
605 606 foo/Bar/** = fred
606 607 """
607 608 pushing to ../b
608 609 query 1; heads
609 610 searching for changes
610 611 all remote heads known locally
611 612 listing keys for "phases"
612 613 checking for updated bookmarks
613 614 listing keys for "bookmarks"
614 615 invalid branch cache (served): tip differs
615 616 listing keys for "bookmarks"
616 617 3 changesets found
617 618 list of changesets:
618 619 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
619 620 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
620 621 911600dab2ae7a9baff75958b84fe606851ce955
621 622 bundle2-output-bundle: "HG20", 5 parts total
622 623 bundle2-output-part: "replycaps" 207 bytes payload
623 624 bundle2-output-part: "check:phases" 24 bytes payload
624 625 bundle2-output-part: "check:updated-heads" streamed payload
625 626 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
626 627 bundle2-output-part: "phase-heads" 24 bytes payload
627 628 bundle2-input-bundle: with-transaction
628 629 bundle2-input-part: "replycaps" supported
629 630 bundle2-input-part: total payload size 207
630 631 bundle2-input-part: "check:phases" supported
631 632 bundle2-input-part: total payload size 24
632 633 bundle2-input-part: "check:updated-heads" supported
633 634 bundle2-input-part: total payload size 20
634 635 invalid branch cache (served): tip differs
635 636 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
636 637 adding changesets
637 638 add changeset ef1ea85a6374
638 639 add changeset f9cafe1212c8
639 640 add changeset 911600dab2ae
640 641 adding manifests
641 642 adding file changes
642 643 adding foo/Bar/file.txt revisions
643 644 adding foo/file.txt revisions
644 645 adding quux/file.py revisions
645 646 calling hook pretxnchangegroup.acl: hgext.acl.hook
646 647 acl: checking access for user "fred"
647 648 acl: acl.allow.branches not enabled
648 649 acl: acl.deny.branches not enabled
649 650 acl: acl.allow enabled, 1 entries for user fred
650 651 acl: acl.deny enabled, 2 entries for user fred
651 652 acl: branch access granted: "ef1ea85a6374" on branch "default"
652 653 acl: path access granted: "ef1ea85a6374"
653 654 acl: branch access granted: "f9cafe1212c8" on branch "default"
654 655 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
655 656 bundle2-input-part: total payload size 1553
656 657 bundle2-input-part: total payload size 24
657 658 bundle2-input-bundle: 5 parts total
658 659 transaction abort!
659 660 rollback completed
660 661 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
661 662 no rollback information available
662 663 0:6675d58eff77
663 664
664 665
665 666 $ echo 'barney is not mentioned => not allowed anywhere'
666 667 barney is not mentioned => not allowed anywhere
667 668 $ do_push barney
668 669 Pushing as user barney
669 670 hgrc = """
670 671 [hooks]
671 672 pretxnchangegroup.acl = python:hgext.acl.hook
672 673 prepushkey.acl = python:hgext.acl.hook
673 674 [acl]
674 675 sources = push
675 676 [acl.allow]
676 677 foo/** = fred
677 678 [acl.deny]
678 679 foo/bar/** = fred
679 680 foo/Bar/** = fred
680 681 """
681 682 pushing to ../b
682 683 query 1; heads
683 684 searching for changes
684 685 all remote heads known locally
685 686 listing keys for "phases"
686 687 checking for updated bookmarks
687 688 listing keys for "bookmarks"
688 689 invalid branch cache (served): tip differs
689 690 listing keys for "bookmarks"
690 691 3 changesets found
691 692 list of changesets:
692 693 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
693 694 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
694 695 911600dab2ae7a9baff75958b84fe606851ce955
695 696 bundle2-output-bundle: "HG20", 5 parts total
696 697 bundle2-output-part: "replycaps" 207 bytes payload
697 698 bundle2-output-part: "check:phases" 24 bytes payload
698 699 bundle2-output-part: "check:updated-heads" streamed payload
699 700 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
700 701 bundle2-output-part: "phase-heads" 24 bytes payload
701 702 bundle2-input-bundle: with-transaction
702 703 bundle2-input-part: "replycaps" supported
703 704 bundle2-input-part: total payload size 207
704 705 bundle2-input-part: "check:phases" supported
705 706 bundle2-input-part: total payload size 24
706 707 bundle2-input-part: "check:updated-heads" supported
707 708 bundle2-input-part: total payload size 20
708 709 invalid branch cache (served): tip differs
709 710 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
710 711 adding changesets
711 712 add changeset ef1ea85a6374
712 713 add changeset f9cafe1212c8
713 714 add changeset 911600dab2ae
714 715 adding manifests
715 716 adding file changes
716 717 adding foo/Bar/file.txt revisions
717 718 adding foo/file.txt revisions
718 719 adding quux/file.py revisions
719 720 calling hook pretxnchangegroup.acl: hgext.acl.hook
720 721 acl: checking access for user "barney"
721 722 acl: acl.allow.branches not enabled
722 723 acl: acl.deny.branches not enabled
723 724 acl: acl.allow enabled, 0 entries for user barney
724 725 acl: acl.deny enabled, 0 entries for user barney
725 726 acl: branch access granted: "ef1ea85a6374" on branch "default"
726 727 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
727 728 bundle2-input-part: total payload size 1553
728 729 bundle2-input-part: total payload size 24
729 730 bundle2-input-bundle: 5 parts total
730 731 transaction abort!
731 732 rollback completed
732 733 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
733 734 no rollback information available
734 735 0:6675d58eff77
735 736
736 737
737 738 fred is not blocked from moving bookmarks
738 739
739 740 $ hg -R a book -q moving-bookmark -r 1
740 741 $ hg -R b book -q moving-bookmark -r 0
741 742 $ cp $config normalconfig
742 743 $ do_push fred -r 1
743 744 Pushing as user fred
744 745 hgrc = """
745 746 [hooks]
746 747 pretxnchangegroup.acl = python:hgext.acl.hook
747 748 prepushkey.acl = python:hgext.acl.hook
748 749 [acl]
749 750 sources = push
750 751 [acl.allow]
751 752 foo/** = fred
752 753 [acl.deny]
753 754 foo/bar/** = fred
754 755 foo/Bar/** = fred
755 756 """
756 757 pushing to ../b
757 758 query 1; heads
758 759 searching for changes
759 760 all remote heads known locally
760 761 listing keys for "phases"
761 762 checking for updated bookmarks
762 763 listing keys for "bookmarks"
763 764 invalid branch cache (served): tip differs
764 765 listing keys for "bookmarks"
765 766 1 changesets found
766 767 list of changesets:
767 768 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
768 769 bundle2-output-bundle: "HG20", 7 parts total
769 770 bundle2-output-part: "replycaps" 207 bytes payload
770 771 bundle2-output-part: "check:bookmarks" 37 bytes payload
771 772 bundle2-output-part: "check:phases" 24 bytes payload
772 773 bundle2-output-part: "check:updated-heads" streamed payload
773 774 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
774 775 bundle2-output-part: "phase-heads" 24 bytes payload
775 776 bundle2-output-part: "bookmarks" 37 bytes payload
776 777 bundle2-input-bundle: with-transaction
777 778 bundle2-input-part: "replycaps" supported
778 779 bundle2-input-part: total payload size 207
779 780 bundle2-input-part: "check:bookmarks" supported
780 781 bundle2-input-part: total payload size 37
781 782 bundle2-input-part: "check:phases" supported
782 783 bundle2-input-part: total payload size 24
783 784 bundle2-input-part: "check:updated-heads" supported
784 785 bundle2-input-part: total payload size 20
785 786 invalid branch cache (served): tip differs
786 787 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
787 788 adding changesets
788 789 add changeset ef1ea85a6374
789 790 adding manifests
790 791 adding file changes
791 792 adding foo/file.txt revisions
792 793 calling hook pretxnchangegroup.acl: hgext.acl.hook
793 794 acl: checking access for user "fred"
794 795 acl: acl.allow.branches not enabled
795 796 acl: acl.deny.branches not enabled
796 797 acl: acl.allow enabled, 1 entries for user fred
797 798 acl: acl.deny enabled, 2 entries for user fred
798 799 acl: branch access granted: "ef1ea85a6374" on branch "default"
799 800 acl: path access granted: "ef1ea85a6374"
800 801 bundle2-input-part: total payload size 520
801 802 bundle2-input-part: "phase-heads" supported
802 803 bundle2-input-part: total payload size 24
803 804 bundle2-input-part: "bookmarks" supported
804 805 bundle2-input-part: total payload size 37
805 806 calling hook prepushkey.acl: hgext.acl.hook
806 807 acl: checking access for user "fred"
807 808 acl: acl.allow.bookmarks not enabled
808 809 acl: acl.deny.bookmarks not enabled
809 810 acl: bookmark access granted: "ef1ea85a6374b77d6da9dcda9541f498f2d17df7" on bookmark "moving-bookmark"
810 811 bundle2-input-bundle: 7 parts total
811 truncating cache/rbc-revs-v1 to 8
812 812 updating the branch cache
813 813 invalid branch cache (served.hidden): tip differs
814 814 added 1 changesets with 1 changes to 1 files
815 815 bundle2-output-bundle: "HG20", 1 parts total
816 816 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
817 817 bundle2-input-bundle: no-transaction
818 818 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
819 819 bundle2-input-bundle: 1 parts total
820 820 updating bookmark moving-bookmark
821 821 listing keys for "phases"
822 822 repository tip rolled back to revision 0 (undo push)
823 823 0:6675d58eff77
824 824
825 825
826 826 fred is not allowed to move bookmarks
827 827
828 828 $ echo '[acl.deny.bookmarks]' >> $config
829 829 $ echo '* = fred' >> $config
830 830 $ do_push fred -r 1
831 831 Pushing as user fred
832 832 hgrc = """
833 833 [hooks]
834 834 pretxnchangegroup.acl = python:hgext.acl.hook
835 835 prepushkey.acl = python:hgext.acl.hook
836 836 [acl]
837 837 sources = push
838 838 [acl.allow]
839 839 foo/** = fred
840 840 [acl.deny]
841 841 foo/bar/** = fred
842 842 foo/Bar/** = fred
843 843 [acl.deny.bookmarks]
844 844 * = fred
845 845 """
846 846 pushing to ../b
847 847 query 1; heads
848 848 searching for changes
849 849 all remote heads known locally
850 850 listing keys for "phases"
851 851 checking for updated bookmarks
852 852 listing keys for "bookmarks"
853 853 invalid branch cache (served): tip differs
854 854 listing keys for "bookmarks"
855 855 1 changesets found
856 856 list of changesets:
857 857 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
858 858 bundle2-output-bundle: "HG20", 7 parts total
859 859 bundle2-output-part: "replycaps" 207 bytes payload
860 860 bundle2-output-part: "check:bookmarks" 37 bytes payload
861 861 bundle2-output-part: "check:phases" 24 bytes payload
862 862 bundle2-output-part: "check:updated-heads" streamed payload
863 863 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
864 864 bundle2-output-part: "phase-heads" 24 bytes payload
865 865 bundle2-output-part: "bookmarks" 37 bytes payload
866 866 bundle2-input-bundle: with-transaction
867 867 bundle2-input-part: "replycaps" supported
868 868 bundle2-input-part: total payload size 207
869 869 bundle2-input-part: "check:bookmarks" supported
870 870 bundle2-input-part: total payload size 37
871 871 bundle2-input-part: "check:phases" supported
872 872 bundle2-input-part: total payload size 24
873 873 bundle2-input-part: "check:updated-heads" supported
874 874 bundle2-input-part: total payload size 20
875 875 invalid branch cache (served): tip differs
876 876 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
877 877 adding changesets
878 878 add changeset ef1ea85a6374
879 879 adding manifests
880 880 adding file changes
881 881 adding foo/file.txt revisions
882 882 calling hook pretxnchangegroup.acl: hgext.acl.hook
883 883 acl: checking access for user "fred"
884 884 acl: acl.allow.branches not enabled
885 885 acl: acl.deny.branches not enabled
886 886 acl: acl.allow enabled, 1 entries for user fred
887 887 acl: acl.deny enabled, 2 entries for user fred
888 888 acl: branch access granted: "ef1ea85a6374" on branch "default"
889 889 acl: path access granted: "ef1ea85a6374"
890 890 bundle2-input-part: total payload size 520
891 891 bundle2-input-part: "phase-heads" supported
892 892 bundle2-input-part: total payload size 24
893 893 bundle2-input-part: "bookmarks" supported
894 894 bundle2-input-part: total payload size 37
895 895 calling hook prepushkey.acl: hgext.acl.hook
896 896 acl: checking access for user "fred"
897 897 acl: acl.allow.bookmarks not enabled
898 898 acl: acl.deny.bookmarks enabled, 1 entries for user fred
899 899 error: prepushkey.acl hook failed: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
900 900 bundle2-input-bundle: 7 parts total
901 901 transaction abort!
902 902 rollback completed
903 truncating cache/rbc-revs-v1 to 8
903 904 abort: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
904 905 no rollback information available
905 906 0:6675d58eff77
906 907
907 908
908 909 cleanup bookmark stuff
909 910
910 911 $ hg book -R a -d moving-bookmark
911 912 $ hg book -R b -d moving-bookmark
912 913 $ cp normalconfig $config
913 914
914 915 barney is allowed everywhere
915 916
916 917 $ echo '[acl.allow]' >> $config
917 918 $ echo '** = barney' >> $config
918 919 $ do_push barney
919 920 Pushing as user barney
920 921 hgrc = """
921 922 [hooks]
922 923 pretxnchangegroup.acl = python:hgext.acl.hook
923 924 prepushkey.acl = python:hgext.acl.hook
924 925 [acl]
925 926 sources = push
926 927 [acl.allow]
927 928 foo/** = fred
928 929 [acl.deny]
929 930 foo/bar/** = fred
930 931 foo/Bar/** = fred
931 932 [acl.allow]
932 933 ** = barney
933 934 """
934 935 pushing to ../b
935 936 query 1; heads
936 937 searching for changes
937 938 all remote heads known locally
938 939 listing keys for "phases"
939 940 checking for updated bookmarks
940 941 listing keys for "bookmarks"
941 942 invalid branch cache (served): tip differs
942 943 listing keys for "bookmarks"
943 944 3 changesets found
944 945 list of changesets:
945 946 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
946 947 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
947 948 911600dab2ae7a9baff75958b84fe606851ce955
948 949 bundle2-output-bundle: "HG20", 5 parts total
949 950 bundle2-output-part: "replycaps" 207 bytes payload
950 951 bundle2-output-part: "check:phases" 24 bytes payload
951 952 bundle2-output-part: "check:updated-heads" streamed payload
952 953 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
953 954 bundle2-output-part: "phase-heads" 24 bytes payload
954 955 bundle2-input-bundle: with-transaction
955 956 bundle2-input-part: "replycaps" supported
956 957 bundle2-input-part: total payload size 207
957 958 bundle2-input-part: "check:phases" supported
958 959 bundle2-input-part: total payload size 24
959 960 bundle2-input-part: "check:updated-heads" supported
960 961 bundle2-input-part: total payload size 20
961 962 invalid branch cache (served): tip differs
962 963 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
963 964 adding changesets
964 965 add changeset ef1ea85a6374
965 966 add changeset f9cafe1212c8
966 967 add changeset 911600dab2ae
967 968 adding manifests
968 969 adding file changes
969 970 adding foo/Bar/file.txt revisions
970 971 adding foo/file.txt revisions
971 972 adding quux/file.py revisions
972 973 calling hook pretxnchangegroup.acl: hgext.acl.hook
973 974 acl: checking access for user "barney"
974 975 acl: acl.allow.branches not enabled
975 976 acl: acl.deny.branches not enabled
976 977 acl: acl.allow enabled, 1 entries for user barney
977 978 acl: acl.deny enabled, 0 entries for user barney
978 979 acl: branch access granted: "ef1ea85a6374" on branch "default"
979 980 acl: path access granted: "ef1ea85a6374"
980 981 acl: branch access granted: "f9cafe1212c8" on branch "default"
981 982 acl: path access granted: "f9cafe1212c8"
982 983 acl: branch access granted: "911600dab2ae" on branch "default"
983 984 acl: path access granted: "911600dab2ae"
984 985 bundle2-input-part: total payload size 1553
985 986 bundle2-input-part: "phase-heads" supported
986 987 bundle2-input-part: total payload size 24
987 988 bundle2-input-bundle: 5 parts total
988 truncating cache/rbc-revs-v1 to 8
989 989 updating the branch cache
990 990 added 3 changesets with 3 changes to 3 files
991 991 bundle2-output-bundle: "HG20", 1 parts total
992 992 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
993 993 bundle2-input-bundle: no-transaction
994 994 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
995 995 bundle2-input-bundle: 1 parts total
996 996 listing keys for "phases"
997 997 repository tip rolled back to revision 0 (undo push)
998 998 0:6675d58eff77
999 999
1000 1000
1001 1001 wilma can change files with a .txt extension
1002 1002
1003 1003 $ echo '**/*.txt = wilma' >> $config
1004 1004 $ do_push wilma
1005 1005 Pushing as user wilma
1006 1006 hgrc = """
1007 1007 [hooks]
1008 1008 pretxnchangegroup.acl = python:hgext.acl.hook
1009 1009 prepushkey.acl = python:hgext.acl.hook
1010 1010 [acl]
1011 1011 sources = push
1012 1012 [acl.allow]
1013 1013 foo/** = fred
1014 1014 [acl.deny]
1015 1015 foo/bar/** = fred
1016 1016 foo/Bar/** = fred
1017 1017 [acl.allow]
1018 1018 ** = barney
1019 1019 **/*.txt = wilma
1020 1020 """
1021 1021 pushing to ../b
1022 1022 query 1; heads
1023 1023 searching for changes
1024 1024 all remote heads known locally
1025 1025 listing keys for "phases"
1026 1026 checking for updated bookmarks
1027 1027 listing keys for "bookmarks"
1028 1028 invalid branch cache (served): tip differs
1029 1029 listing keys for "bookmarks"
1030 1030 3 changesets found
1031 1031 list of changesets:
1032 1032 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1033 1033 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1034 1034 911600dab2ae7a9baff75958b84fe606851ce955
1035 1035 bundle2-output-bundle: "HG20", 5 parts total
1036 1036 bundle2-output-part: "replycaps" 207 bytes payload
1037 1037 bundle2-output-part: "check:phases" 24 bytes payload
1038 1038 bundle2-output-part: "check:updated-heads" streamed payload
1039 1039 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1040 1040 bundle2-output-part: "phase-heads" 24 bytes payload
1041 1041 bundle2-input-bundle: with-transaction
1042 1042 bundle2-input-part: "replycaps" supported
1043 1043 bundle2-input-part: total payload size 207
1044 1044 bundle2-input-part: "check:phases" supported
1045 1045 bundle2-input-part: total payload size 24
1046 1046 bundle2-input-part: "check:updated-heads" supported
1047 1047 bundle2-input-part: total payload size 20
1048 1048 invalid branch cache (served): tip differs
1049 1049 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1050 1050 adding changesets
1051 1051 add changeset ef1ea85a6374
1052 1052 add changeset f9cafe1212c8
1053 1053 add changeset 911600dab2ae
1054 1054 adding manifests
1055 1055 adding file changes
1056 1056 adding foo/Bar/file.txt revisions
1057 1057 adding foo/file.txt revisions
1058 1058 adding quux/file.py revisions
1059 1059 calling hook pretxnchangegroup.acl: hgext.acl.hook
1060 1060 acl: checking access for user "wilma"
1061 1061 acl: acl.allow.branches not enabled
1062 1062 acl: acl.deny.branches not enabled
1063 1063 acl: acl.allow enabled, 1 entries for user wilma
1064 1064 acl: acl.deny enabled, 0 entries for user wilma
1065 1065 acl: branch access granted: "ef1ea85a6374" on branch "default"
1066 1066 acl: path access granted: "ef1ea85a6374"
1067 1067 acl: branch access granted: "f9cafe1212c8" on branch "default"
1068 1068 acl: path access granted: "f9cafe1212c8"
1069 1069 acl: branch access granted: "911600dab2ae" on branch "default"
1070 1070 error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
1071 1071 bundle2-input-part: total payload size 1553
1072 1072 bundle2-input-part: total payload size 24
1073 1073 bundle2-input-bundle: 5 parts total
1074 1074 transaction abort!
1075 1075 rollback completed
1076 truncating cache/rbc-revs-v1 to 8
1076 1077 abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
1077 1078 no rollback information available
1078 1079 0:6675d58eff77
1079 1080
1080 1081
1081 1082 file specified by acl.config does not exist
1082 1083
1083 1084 $ echo '[acl]' >> $config
1084 1085 $ echo 'config = ../acl.config' >> $config
1085 1086 $ do_push barney
1086 1087 Pushing as user barney
1087 1088 hgrc = """
1088 1089 [hooks]
1089 1090 pretxnchangegroup.acl = python:hgext.acl.hook
1090 1091 prepushkey.acl = python:hgext.acl.hook
1091 1092 [acl]
1092 1093 sources = push
1093 1094 [acl.allow]
1094 1095 foo/** = fred
1095 1096 [acl.deny]
1096 1097 foo/bar/** = fred
1097 1098 foo/Bar/** = fred
1098 1099 [acl.allow]
1099 1100 ** = barney
1100 1101 **/*.txt = wilma
1101 1102 [acl]
1102 1103 config = ../acl.config
1103 1104 """
1104 1105 pushing to ../b
1105 1106 query 1; heads
1106 1107 searching for changes
1107 1108 all remote heads known locally
1108 1109 listing keys for "phases"
1109 1110 checking for updated bookmarks
1110 1111 listing keys for "bookmarks"
1111 1112 invalid branch cache (served): tip differs
1112 1113 listing keys for "bookmarks"
1113 1114 3 changesets found
1114 1115 list of changesets:
1115 1116 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1116 1117 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1117 1118 911600dab2ae7a9baff75958b84fe606851ce955
1118 1119 bundle2-output-bundle: "HG20", 5 parts total
1119 1120 bundle2-output-part: "replycaps" 207 bytes payload
1120 1121 bundle2-output-part: "check:phases" 24 bytes payload
1121 1122 bundle2-output-part: "check:updated-heads" streamed payload
1122 1123 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1123 1124 bundle2-output-part: "phase-heads" 24 bytes payload
1124 1125 bundle2-input-bundle: with-transaction
1125 1126 bundle2-input-part: "replycaps" supported
1126 1127 bundle2-input-part: total payload size 207
1127 1128 bundle2-input-part: "check:phases" supported
1128 1129 bundle2-input-part: total payload size 24
1129 1130 bundle2-input-part: "check:updated-heads" supported
1130 1131 bundle2-input-part: total payload size 20
1131 1132 invalid branch cache (served): tip differs
1132 1133 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1133 1134 adding changesets
1134 1135 add changeset ef1ea85a6374
1135 1136 add changeset f9cafe1212c8
1136 1137 add changeset 911600dab2ae
1137 1138 adding manifests
1138 1139 adding file changes
1139 1140 adding foo/Bar/file.txt revisions
1140 1141 adding foo/file.txt revisions
1141 1142 adding quux/file.py revisions
1142 1143 calling hook pretxnchangegroup.acl: hgext.acl.hook
1143 1144 acl: checking access for user "barney"
1144 1145 error: pretxnchangegroup.acl hook raised an exception: [Errno *] * (glob)
1145 1146 bundle2-input-part: total payload size 1553
1146 1147 bundle2-input-part: total payload size 24
1147 1148 bundle2-input-bundle: 5 parts total
1148 1149 transaction abort!
1149 1150 rollback completed
1150 1151 abort: $ENOENT$: '../acl.config'
1151 1152 no rollback information available
1152 1153 0:6675d58eff77
1153 1154
1154 1155
1155 1156 betty is allowed inside foo/ by a acl.config file
1156 1157
1157 1158 $ echo '[acl.allow]' >> acl.config
1158 1159 $ echo 'foo/** = betty' >> acl.config
1159 1160 $ do_push betty
1160 1161 Pushing as user betty
1161 1162 hgrc = """
1162 1163 [hooks]
1163 1164 pretxnchangegroup.acl = python:hgext.acl.hook
1164 1165 prepushkey.acl = python:hgext.acl.hook
1165 1166 [acl]
1166 1167 sources = push
1167 1168 [acl.allow]
1168 1169 foo/** = fred
1169 1170 [acl.deny]
1170 1171 foo/bar/** = fred
1171 1172 foo/Bar/** = fred
1172 1173 [acl.allow]
1173 1174 ** = barney
1174 1175 **/*.txt = wilma
1175 1176 [acl]
1176 1177 config = ../acl.config
1177 1178 """
1178 1179 acl.config = """
1179 1180 [acl.allow]
1180 1181 foo/** = betty
1181 1182 """
1182 1183 pushing to ../b
1183 1184 query 1; heads
1184 1185 searching for changes
1185 1186 all remote heads known locally
1186 1187 listing keys for "phases"
1187 1188 checking for updated bookmarks
1188 1189 listing keys for "bookmarks"
1189 1190 invalid branch cache (served): tip differs
1190 1191 listing keys for "bookmarks"
1191 1192 3 changesets found
1192 1193 list of changesets:
1193 1194 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1194 1195 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1195 1196 911600dab2ae7a9baff75958b84fe606851ce955
1196 1197 bundle2-output-bundle: "HG20", 5 parts total
1197 1198 bundle2-output-part: "replycaps" 207 bytes payload
1198 1199 bundle2-output-part: "check:phases" 24 bytes payload
1199 1200 bundle2-output-part: "check:updated-heads" streamed payload
1200 1201 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1201 1202 bundle2-output-part: "phase-heads" 24 bytes payload
1202 1203 bundle2-input-bundle: with-transaction
1203 1204 bundle2-input-part: "replycaps" supported
1204 1205 bundle2-input-part: total payload size 207
1205 1206 bundle2-input-part: "check:phases" supported
1206 1207 bundle2-input-part: total payload size 24
1207 1208 bundle2-input-part: "check:updated-heads" supported
1208 1209 bundle2-input-part: total payload size 20
1209 1210 invalid branch cache (served): tip differs
1210 1211 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1211 1212 adding changesets
1212 1213 add changeset ef1ea85a6374
1213 1214 add changeset f9cafe1212c8
1214 1215 add changeset 911600dab2ae
1215 1216 adding manifests
1216 1217 adding file changes
1217 1218 adding foo/Bar/file.txt revisions
1218 1219 adding foo/file.txt revisions
1219 1220 adding quux/file.py revisions
1220 1221 calling hook pretxnchangegroup.acl: hgext.acl.hook
1221 1222 acl: checking access for user "betty"
1222 1223 acl: acl.allow.branches not enabled
1223 1224 acl: acl.deny.branches not enabled
1224 1225 acl: acl.allow enabled, 1 entries for user betty
1225 1226 acl: acl.deny enabled, 0 entries for user betty
1226 1227 acl: branch access granted: "ef1ea85a6374" on branch "default"
1227 1228 acl: path access granted: "ef1ea85a6374"
1228 1229 acl: branch access granted: "f9cafe1212c8" on branch "default"
1229 1230 acl: path access granted: "f9cafe1212c8"
1230 1231 acl: branch access granted: "911600dab2ae" on branch "default"
1231 1232 error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
1232 1233 bundle2-input-part: total payload size 1553
1233 1234 bundle2-input-part: total payload size 24
1234 1235 bundle2-input-bundle: 5 parts total
1235 1236 transaction abort!
1236 1237 rollback completed
1237 1238 abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
1238 1239 no rollback information available
1239 1240 0:6675d58eff77
1240 1241
1241 1242
1242 1243 acl.config can set only [acl.allow]/[acl.deny]
1243 1244
1244 1245 $ echo '[hooks]' >> acl.config
1245 1246 $ echo 'changegroup.acl = false' >> acl.config
1246 1247 $ do_push barney
1247 1248 Pushing as user barney
1248 1249 hgrc = """
1249 1250 [hooks]
1250 1251 pretxnchangegroup.acl = python:hgext.acl.hook
1251 1252 prepushkey.acl = python:hgext.acl.hook
1252 1253 [acl]
1253 1254 sources = push
1254 1255 [acl.allow]
1255 1256 foo/** = fred
1256 1257 [acl.deny]
1257 1258 foo/bar/** = fred
1258 1259 foo/Bar/** = fred
1259 1260 [acl.allow]
1260 1261 ** = barney
1261 1262 **/*.txt = wilma
1262 1263 [acl]
1263 1264 config = ../acl.config
1264 1265 """
1265 1266 acl.config = """
1266 1267 [acl.allow]
1267 1268 foo/** = betty
1268 1269 [hooks]
1269 1270 changegroup.acl = false
1270 1271 """
1271 1272 pushing to ../b
1272 1273 query 1; heads
1273 1274 searching for changes
1274 1275 all remote heads known locally
1275 1276 listing keys for "phases"
1276 1277 checking for updated bookmarks
1277 1278 listing keys for "bookmarks"
1278 1279 invalid branch cache (served): tip differs
1279 1280 listing keys for "bookmarks"
1280 1281 3 changesets found
1281 1282 list of changesets:
1282 1283 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1283 1284 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1284 1285 911600dab2ae7a9baff75958b84fe606851ce955
1285 1286 bundle2-output-bundle: "HG20", 5 parts total
1286 1287 bundle2-output-part: "replycaps" 207 bytes payload
1287 1288 bundle2-output-part: "check:phases" 24 bytes payload
1288 1289 bundle2-output-part: "check:updated-heads" streamed payload
1289 1290 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1290 1291 bundle2-output-part: "phase-heads" 24 bytes payload
1291 1292 bundle2-input-bundle: with-transaction
1292 1293 bundle2-input-part: "replycaps" supported
1293 1294 bundle2-input-part: total payload size 207
1294 1295 bundle2-input-part: "check:phases" supported
1295 1296 bundle2-input-part: total payload size 24
1296 1297 bundle2-input-part: "check:updated-heads" supported
1297 1298 bundle2-input-part: total payload size 20
1298 1299 invalid branch cache (served): tip differs
1299 1300 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1300 1301 adding changesets
1301 1302 add changeset ef1ea85a6374
1302 1303 add changeset f9cafe1212c8
1303 1304 add changeset 911600dab2ae
1304 1305 adding manifests
1305 1306 adding file changes
1306 1307 adding foo/Bar/file.txt revisions
1307 1308 adding foo/file.txt revisions
1308 1309 adding quux/file.py revisions
1309 1310 calling hook pretxnchangegroup.acl: hgext.acl.hook
1310 1311 acl: checking access for user "barney"
1311 1312 acl: acl.allow.branches not enabled
1312 1313 acl: acl.deny.branches not enabled
1313 1314 acl: acl.allow enabled, 1 entries for user barney
1314 1315 acl: acl.deny enabled, 0 entries for user barney
1315 1316 acl: branch access granted: "ef1ea85a6374" on branch "default"
1316 1317 acl: path access granted: "ef1ea85a6374"
1317 1318 acl: branch access granted: "f9cafe1212c8" on branch "default"
1318 1319 acl: path access granted: "f9cafe1212c8"
1319 1320 acl: branch access granted: "911600dab2ae" on branch "default"
1320 1321 acl: path access granted: "911600dab2ae"
1321 1322 bundle2-input-part: total payload size 1553
1322 1323 bundle2-input-part: "phase-heads" supported
1323 1324 bundle2-input-part: total payload size 24
1324 1325 bundle2-input-bundle: 5 parts total
1325 truncating cache/rbc-revs-v1 to 8
1326 1326 updating the branch cache
1327 1327 added 3 changesets with 3 changes to 3 files
1328 1328 bundle2-output-bundle: "HG20", 1 parts total
1329 1329 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1330 1330 bundle2-input-bundle: no-transaction
1331 1331 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1332 1332 bundle2-input-bundle: 1 parts total
1333 1333 listing keys for "phases"
1334 1334 repository tip rolled back to revision 0 (undo push)
1335 1335 0:6675d58eff77
1336 1336
1337 1337
1338 1338 asterisk
1339 1339
1340 1340 $ init_config
1341 1341
1342 1342 asterisk test
1343 1343
1344 1344 $ echo '[acl.allow]' >> $config
1345 1345 $ echo "** = fred" >> $config
1346 1346
1347 1347 fred is always allowed
1348 1348
1349 1349 $ do_push fred
1350 1350 Pushing as user fred
1351 1351 hgrc = """
1352 1352 [hooks]
1353 1353 pretxnchangegroup.acl = python:hgext.acl.hook
1354 1354 prepushkey.acl = python:hgext.acl.hook
1355 1355 [acl]
1356 1356 sources = push
1357 1357 [extensions]
1358 1358 posixgetuser=$TESTTMP/posixgetuser.py
1359 1359 [acl.allow]
1360 1360 ** = fred
1361 1361 """
1362 1362 pushing to ../b
1363 1363 query 1; heads
1364 1364 searching for changes
1365 1365 all remote heads known locally
1366 1366 listing keys for "phases"
1367 1367 checking for updated bookmarks
1368 1368 listing keys for "bookmarks"
1369 1369 invalid branch cache (served): tip differs
1370 1370 listing keys for "bookmarks"
1371 1371 3 changesets found
1372 1372 list of changesets:
1373 1373 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1374 1374 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1375 1375 911600dab2ae7a9baff75958b84fe606851ce955
1376 1376 bundle2-output-bundle: "HG20", 5 parts total
1377 1377 bundle2-output-part: "replycaps" 207 bytes payload
1378 1378 bundle2-output-part: "check:phases" 24 bytes payload
1379 1379 bundle2-output-part: "check:updated-heads" streamed payload
1380 1380 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1381 1381 bundle2-output-part: "phase-heads" 24 bytes payload
1382 1382 bundle2-input-bundle: with-transaction
1383 1383 bundle2-input-part: "replycaps" supported
1384 1384 bundle2-input-part: total payload size 207
1385 1385 bundle2-input-part: "check:phases" supported
1386 1386 bundle2-input-part: total payload size 24
1387 1387 bundle2-input-part: "check:updated-heads" supported
1388 1388 bundle2-input-part: total payload size 20
1389 1389 invalid branch cache (served): tip differs
1390 1390 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1391 1391 adding changesets
1392 1392 add changeset ef1ea85a6374
1393 1393 add changeset f9cafe1212c8
1394 1394 add changeset 911600dab2ae
1395 1395 adding manifests
1396 1396 adding file changes
1397 1397 adding foo/Bar/file.txt revisions
1398 1398 adding foo/file.txt revisions
1399 1399 adding quux/file.py revisions
1400 1400 calling hook pretxnchangegroup.acl: hgext.acl.hook
1401 1401 acl: checking access for user "fred"
1402 1402 acl: acl.allow.branches not enabled
1403 1403 acl: acl.deny.branches not enabled
1404 1404 acl: acl.allow enabled, 1 entries for user fred
1405 1405 acl: acl.deny not enabled
1406 1406 acl: branch access granted: "ef1ea85a6374" on branch "default"
1407 1407 acl: path access granted: "ef1ea85a6374"
1408 1408 acl: branch access granted: "f9cafe1212c8" on branch "default"
1409 1409 acl: path access granted: "f9cafe1212c8"
1410 1410 acl: branch access granted: "911600dab2ae" on branch "default"
1411 1411 acl: path access granted: "911600dab2ae"
1412 1412 bundle2-input-part: total payload size 1553
1413 1413 bundle2-input-part: "phase-heads" supported
1414 1414 bundle2-input-part: total payload size 24
1415 1415 bundle2-input-bundle: 5 parts total
1416 1416 truncating cache/rbc-revs-v1 to 8
1417 1417 updating the branch cache
1418 1418 added 3 changesets with 3 changes to 3 files
1419 1419 bundle2-output-bundle: "HG20", 1 parts total
1420 1420 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1421 1421 bundle2-input-bundle: no-transaction
1422 1422 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1423 1423 bundle2-input-bundle: 1 parts total
1424 1424 listing keys for "phases"
1425 1425 repository tip rolled back to revision 0 (undo push)
1426 1426 0:6675d58eff77
1427 1427
1428 1428
1429 1429 $ echo '[acl.deny]' >> $config
1430 1430 $ echo "foo/Bar/** = *" >> $config
1431 1431
1432 1432 no one is allowed inside foo/Bar/
1433 1433
1434 1434 $ do_push fred
1435 1435 Pushing as user fred
1436 1436 hgrc = """
1437 1437 [hooks]
1438 1438 pretxnchangegroup.acl = python:hgext.acl.hook
1439 1439 prepushkey.acl = python:hgext.acl.hook
1440 1440 [acl]
1441 1441 sources = push
1442 1442 [extensions]
1443 1443 posixgetuser=$TESTTMP/posixgetuser.py
1444 1444 [acl.allow]
1445 1445 ** = fred
1446 1446 [acl.deny]
1447 1447 foo/Bar/** = *
1448 1448 """
1449 1449 pushing to ../b
1450 1450 query 1; heads
1451 1451 searching for changes
1452 1452 all remote heads known locally
1453 1453 listing keys for "phases"
1454 1454 checking for updated bookmarks
1455 1455 listing keys for "bookmarks"
1456 1456 invalid branch cache (served): tip differs
1457 1457 listing keys for "bookmarks"
1458 1458 3 changesets found
1459 1459 list of changesets:
1460 1460 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1461 1461 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1462 1462 911600dab2ae7a9baff75958b84fe606851ce955
1463 1463 bundle2-output-bundle: "HG20", 5 parts total
1464 1464 bundle2-output-part: "replycaps" 207 bytes payload
1465 1465 bundle2-output-part: "check:phases" 24 bytes payload
1466 1466 bundle2-output-part: "check:updated-heads" streamed payload
1467 1467 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1468 1468 bundle2-output-part: "phase-heads" 24 bytes payload
1469 1469 bundle2-input-bundle: with-transaction
1470 1470 bundle2-input-part: "replycaps" supported
1471 1471 bundle2-input-part: total payload size 207
1472 1472 bundle2-input-part: "check:phases" supported
1473 1473 bundle2-input-part: total payload size 24
1474 1474 bundle2-input-part: "check:updated-heads" supported
1475 1475 bundle2-input-part: total payload size 20
1476 1476 invalid branch cache (served): tip differs
1477 1477 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1478 1478 adding changesets
1479 1479 add changeset ef1ea85a6374
1480 1480 add changeset f9cafe1212c8
1481 1481 add changeset 911600dab2ae
1482 1482 adding manifests
1483 1483 adding file changes
1484 1484 adding foo/Bar/file.txt revisions
1485 1485 adding foo/file.txt revisions
1486 1486 adding quux/file.py revisions
1487 1487 calling hook pretxnchangegroup.acl: hgext.acl.hook
1488 1488 acl: checking access for user "fred"
1489 1489 acl: acl.allow.branches not enabled
1490 1490 acl: acl.deny.branches not enabled
1491 1491 acl: acl.allow enabled, 1 entries for user fred
1492 1492 acl: acl.deny enabled, 1 entries for user fred
1493 1493 acl: branch access granted: "ef1ea85a6374" on branch "default"
1494 1494 acl: path access granted: "ef1ea85a6374"
1495 1495 acl: branch access granted: "f9cafe1212c8" on branch "default"
1496 1496 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1497 1497 bundle2-input-part: total payload size 1553
1498 1498 bundle2-input-part: total payload size 24
1499 1499 bundle2-input-bundle: 5 parts total
1500 1500 transaction abort!
1501 1501 rollback completed
1502 truncating cache/rbc-revs-v1 to 8
1502 1503 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1503 1504 no rollback information available
1504 1505 0:6675d58eff77
1505 1506
1506 1507
1507 1508 Groups
1508 1509
1509 1510 $ init_config
1510 1511
1511 1512 OS-level groups
1512 1513
1513 1514 $ echo '[acl.allow]' >> $config
1514 1515 $ echo "** = @group1" >> $config
1515 1516
1516 1517 @group1 is always allowed
1517 1518
1518 1519 $ do_push fred
1519 1520 Pushing as user fred
1520 1521 hgrc = """
1521 1522 [hooks]
1522 1523 pretxnchangegroup.acl = python:hgext.acl.hook
1523 1524 prepushkey.acl = python:hgext.acl.hook
1524 1525 [acl]
1525 1526 sources = push
1526 1527 [extensions]
1527 1528 posixgetuser=$TESTTMP/posixgetuser.py
1528 1529 [acl.allow]
1529 1530 ** = @group1
1530 1531 """
1531 1532 pushing to ../b
1532 1533 query 1; heads
1533 1534 searching for changes
1534 1535 all remote heads known locally
1535 1536 listing keys for "phases"
1536 1537 checking for updated bookmarks
1537 1538 listing keys for "bookmarks"
1538 1539 invalid branch cache (served): tip differs
1539 1540 listing keys for "bookmarks"
1540 1541 3 changesets found
1541 1542 list of changesets:
1542 1543 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1543 1544 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1544 1545 911600dab2ae7a9baff75958b84fe606851ce955
1545 1546 bundle2-output-bundle: "HG20", 5 parts total
1546 1547 bundle2-output-part: "replycaps" 207 bytes payload
1547 1548 bundle2-output-part: "check:phases" 24 bytes payload
1548 1549 bundle2-output-part: "check:updated-heads" streamed payload
1549 1550 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1550 1551 bundle2-output-part: "phase-heads" 24 bytes payload
1551 1552 bundle2-input-bundle: with-transaction
1552 1553 bundle2-input-part: "replycaps" supported
1553 1554 bundle2-input-part: total payload size 207
1554 1555 bundle2-input-part: "check:phases" supported
1555 1556 bundle2-input-part: total payload size 24
1556 1557 bundle2-input-part: "check:updated-heads" supported
1557 1558 bundle2-input-part: total payload size 20
1558 1559 invalid branch cache (served): tip differs
1559 1560 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1560 1561 adding changesets
1561 1562 add changeset ef1ea85a6374
1562 1563 add changeset f9cafe1212c8
1563 1564 add changeset 911600dab2ae
1564 1565 adding manifests
1565 1566 adding file changes
1566 1567 adding foo/Bar/file.txt revisions
1567 1568 adding foo/file.txt revisions
1568 1569 adding quux/file.py revisions
1569 1570 calling hook pretxnchangegroup.acl: hgext.acl.hook
1570 1571 acl: checking access for user "fred"
1571 1572 acl: acl.allow.branches not enabled
1572 1573 acl: acl.deny.branches not enabled
1573 1574 acl: "group1" not defined in [acl.groups]
1574 1575 acl: acl.allow enabled, 1 entries for user fred
1575 1576 acl: acl.deny not enabled
1576 1577 acl: branch access granted: "ef1ea85a6374" on branch "default"
1577 1578 acl: path access granted: "ef1ea85a6374"
1578 1579 acl: branch access granted: "f9cafe1212c8" on branch "default"
1579 1580 acl: path access granted: "f9cafe1212c8"
1580 1581 acl: branch access granted: "911600dab2ae" on branch "default"
1581 1582 acl: path access granted: "911600dab2ae"
1582 1583 bundle2-input-part: total payload size 1553
1583 1584 bundle2-input-part: "phase-heads" supported
1584 1585 bundle2-input-part: total payload size 24
1585 1586 bundle2-input-bundle: 5 parts total
1586 truncating cache/rbc-revs-v1 to 8
1587 1587 updating the branch cache
1588 1588 added 3 changesets with 3 changes to 3 files
1589 1589 bundle2-output-bundle: "HG20", 1 parts total
1590 1590 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1591 1591 bundle2-input-bundle: no-transaction
1592 1592 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1593 1593 bundle2-input-bundle: 1 parts total
1594 1594 listing keys for "phases"
1595 1595 repository tip rolled back to revision 0 (undo push)
1596 1596 0:6675d58eff77
1597 1597
1598 1598
1599 1599 $ echo '[acl.deny]' >> $config
1600 1600 $ echo "foo/Bar/** = @group1" >> $config
1601 1601
1602 1602 @group is allowed inside anything but foo/Bar/
1603 1603
1604 1604 $ do_push fred
1605 1605 Pushing as user fred
1606 1606 hgrc = """
1607 1607 [hooks]
1608 1608 pretxnchangegroup.acl = python:hgext.acl.hook
1609 1609 prepushkey.acl = python:hgext.acl.hook
1610 1610 [acl]
1611 1611 sources = push
1612 1612 [extensions]
1613 1613 posixgetuser=$TESTTMP/posixgetuser.py
1614 1614 [acl.allow]
1615 1615 ** = @group1
1616 1616 [acl.deny]
1617 1617 foo/Bar/** = @group1
1618 1618 """
1619 1619 pushing to ../b
1620 1620 query 1; heads
1621 1621 searching for changes
1622 1622 all remote heads known locally
1623 1623 listing keys for "phases"
1624 1624 checking for updated bookmarks
1625 1625 listing keys for "bookmarks"
1626 1626 invalid branch cache (served): tip differs
1627 1627 listing keys for "bookmarks"
1628 1628 3 changesets found
1629 1629 list of changesets:
1630 1630 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1631 1631 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1632 1632 911600dab2ae7a9baff75958b84fe606851ce955
1633 1633 bundle2-output-bundle: "HG20", 5 parts total
1634 1634 bundle2-output-part: "replycaps" 207 bytes payload
1635 1635 bundle2-output-part: "check:phases" 24 bytes payload
1636 1636 bundle2-output-part: "check:updated-heads" streamed payload
1637 1637 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1638 1638 bundle2-output-part: "phase-heads" 24 bytes payload
1639 1639 bundle2-input-bundle: with-transaction
1640 1640 bundle2-input-part: "replycaps" supported
1641 1641 bundle2-input-part: total payload size 207
1642 1642 bundle2-input-part: "check:phases" supported
1643 1643 bundle2-input-part: total payload size 24
1644 1644 bundle2-input-part: "check:updated-heads" supported
1645 1645 bundle2-input-part: total payload size 20
1646 1646 invalid branch cache (served): tip differs
1647 1647 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1648 1648 adding changesets
1649 1649 add changeset ef1ea85a6374
1650 1650 add changeset f9cafe1212c8
1651 1651 add changeset 911600dab2ae
1652 1652 adding manifests
1653 1653 adding file changes
1654 1654 adding foo/Bar/file.txt revisions
1655 1655 adding foo/file.txt revisions
1656 1656 adding quux/file.py revisions
1657 1657 calling hook pretxnchangegroup.acl: hgext.acl.hook
1658 1658 acl: checking access for user "fred"
1659 1659 acl: acl.allow.branches not enabled
1660 1660 acl: acl.deny.branches not enabled
1661 1661 acl: "group1" not defined in [acl.groups]
1662 1662 acl: acl.allow enabled, 1 entries for user fred
1663 1663 acl: "group1" not defined in [acl.groups]
1664 1664 acl: acl.deny enabled, 1 entries for user fred
1665 1665 acl: branch access granted: "ef1ea85a6374" on branch "default"
1666 1666 acl: path access granted: "ef1ea85a6374"
1667 1667 acl: branch access granted: "f9cafe1212c8" on branch "default"
1668 1668 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1669 1669 bundle2-input-part: total payload size 1553
1670 1670 bundle2-input-part: total payload size 24
1671 1671 bundle2-input-bundle: 5 parts total
1672 1672 transaction abort!
1673 1673 rollback completed
1674 truncating cache/rbc-revs-v1 to 8
1674 1675 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1675 1676 no rollback information available
1676 1677 0:6675d58eff77
1677 1678
1678 1679
1679 1680 Invalid group
1680 1681
1681 1682 Disable the fakegroups trick to get real failures
1682 1683
1683 1684 $ grep -v fakegroups $config > config.tmp
1684 1685 $ mv config.tmp $config
1685 1686 $ echo '[acl.allow]' >> $config
1686 1687 $ echo "** = @unlikelytoexist" >> $config
1687 1688 $ do_push fred 2>&1 | grep unlikelytoexist
1688 1689 ** = @unlikelytoexist
1689 1690 acl: "unlikelytoexist" not defined in [acl.groups]
1690 1691 error: pretxnchangegroup.acl hook failed: group 'unlikelytoexist' is undefined
1691 1692 abort: group 'unlikelytoexist' is undefined
1692 1693
1693 1694
1694 1695 Branch acl tests setup
1695 1696
1696 1697 $ init_config
1697 1698 $ cd b
1698 1699 $ hg up
1699 1700 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1700 1701 $ hg branch foobar
1701 1702 marked working directory as branch foobar
1702 1703 (branches are permanent and global, did you want a bookmark?)
1703 1704 $ hg commit -m 'create foobar'
1704 1705 $ echo 'foo contents' > abc.txt
1705 1706 $ hg add abc.txt
1706 1707 $ hg commit -m 'foobar contents'
1707 1708 $ cd ..
1708 1709 $ hg --cwd a pull ../b
1709 1710 pulling from ../b
1710 1711 searching for changes
1711 1712 adding changesets
1712 1713 adding manifests
1713 1714 adding file changes
1714 1715 added 2 changesets with 1 changes to 1 files (+1 heads)
1715 1716 new changesets 81fbf4469322:fb35475503ef
1716 1717 (run 'hg heads' to see heads)
1717 1718
1718 1719 Create additional changeset on foobar branch
1719 1720
1720 1721 $ cd a
1721 1722 $ hg up -C foobar
1722 1723 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1723 1724 $ echo 'foo contents2' > abc.txt
1724 1725 $ hg commit -m 'foobar contents2'
1725 1726 $ cd ..
1726 1727
1727 1728
1728 1729 No branch acls specified
1729 1730
1730 1731 $ do_push astro
1731 1732 Pushing as user astro
1732 1733 hgrc = """
1733 1734 [hooks]
1734 1735 pretxnchangegroup.acl = python:hgext.acl.hook
1735 1736 prepushkey.acl = python:hgext.acl.hook
1736 1737 [acl]
1737 1738 sources = push
1738 1739 [extensions]
1739 1740 posixgetuser=$TESTTMP/posixgetuser.py
1740 1741 """
1741 1742 pushing to ../b
1742 1743 query 1; heads
1743 1744 searching for changes
1744 1745 all remote heads known locally
1745 1746 listing keys for "phases"
1746 1747 checking for updated bookmarks
1747 1748 listing keys for "bookmarks"
1748 1749 listing keys for "bookmarks"
1749 1750 4 changesets found
1750 1751 list of changesets:
1751 1752 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1752 1753 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1753 1754 911600dab2ae7a9baff75958b84fe606851ce955
1754 1755 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1755 1756 bundle2-output-bundle: "HG20", 5 parts total
1756 1757 bundle2-output-part: "replycaps" 207 bytes payload
1757 1758 bundle2-output-part: "check:phases" 48 bytes payload
1758 1759 bundle2-output-part: "check:updated-heads" streamed payload
1759 1760 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1760 1761 bundle2-output-part: "phase-heads" 48 bytes payload
1761 1762 bundle2-input-bundle: with-transaction
1762 1763 bundle2-input-part: "replycaps" supported
1763 1764 bundle2-input-part: total payload size 207
1764 1765 bundle2-input-part: "check:phases" supported
1765 1766 bundle2-input-part: total payload size 48
1766 1767 bundle2-input-part: "check:updated-heads" supported
1767 1768 bundle2-input-part: total payload size 40
1768 1769 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1769 1770 adding changesets
1770 1771 add changeset ef1ea85a6374
1771 1772 add changeset f9cafe1212c8
1772 1773 add changeset 911600dab2ae
1773 1774 add changeset e8fc755d4d82
1774 1775 adding manifests
1775 1776 adding file changes
1776 1777 adding abc.txt revisions
1777 1778 adding foo/Bar/file.txt revisions
1778 1779 adding foo/file.txt revisions
1779 1780 adding quux/file.py revisions
1780 1781 calling hook pretxnchangegroup.acl: hgext.acl.hook
1781 1782 acl: checking access for user "astro"
1782 1783 acl: acl.allow.branches not enabled
1783 1784 acl: acl.deny.branches not enabled
1784 1785 acl: acl.allow not enabled
1785 1786 acl: acl.deny not enabled
1786 1787 acl: branch access granted: "ef1ea85a6374" on branch "default"
1787 1788 acl: path access granted: "ef1ea85a6374"
1788 1789 acl: branch access granted: "f9cafe1212c8" on branch "default"
1789 1790 acl: path access granted: "f9cafe1212c8"
1790 1791 acl: branch access granted: "911600dab2ae" on branch "default"
1791 1792 acl: path access granted: "911600dab2ae"
1792 1793 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1793 1794 acl: path access granted: "e8fc755d4d82"
1794 1795 bundle2-input-part: total payload size 2068
1795 1796 bundle2-input-part: "phase-heads" supported
1796 1797 bundle2-input-part: total payload size 48
1797 1798 bundle2-input-bundle: 5 parts total
1798 1799 updating the branch cache
1799 1800 invalid branch cache (served.hidden): tip differs
1800 1801 added 4 changesets with 4 changes to 4 files (+1 heads)
1801 1802 bundle2-output-bundle: "HG20", 1 parts total
1802 1803 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1803 1804 bundle2-input-bundle: no-transaction
1804 1805 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1805 1806 bundle2-input-bundle: 1 parts total
1806 1807 listing keys for "phases"
1807 1808 repository tip rolled back to revision 2 (undo push)
1808 1809 2:fb35475503ef
1809 1810
1810 1811
1811 1812 Branch acl deny test
1812 1813
1813 1814 $ echo "[acl.deny.branches]" >> $config
1814 1815 $ echo "foobar = *" >> $config
1815 1816 $ do_push astro
1816 1817 Pushing as user astro
1817 1818 hgrc = """
1818 1819 [hooks]
1819 1820 pretxnchangegroup.acl = python:hgext.acl.hook
1820 1821 prepushkey.acl = python:hgext.acl.hook
1821 1822 [acl]
1822 1823 sources = push
1823 1824 [extensions]
1824 1825 posixgetuser=$TESTTMP/posixgetuser.py
1825 1826 [acl.deny.branches]
1826 1827 foobar = *
1827 1828 """
1828 1829 pushing to ../b
1829 1830 query 1; heads
1830 1831 searching for changes
1831 1832 all remote heads known locally
1832 1833 listing keys for "phases"
1833 1834 checking for updated bookmarks
1834 1835 listing keys for "bookmarks"
1835 1836 listing keys for "bookmarks"
1836 1837 4 changesets found
1837 1838 list of changesets:
1838 1839 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1839 1840 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1840 1841 911600dab2ae7a9baff75958b84fe606851ce955
1841 1842 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1842 1843 bundle2-output-bundle: "HG20", 5 parts total
1843 1844 bundle2-output-part: "replycaps" 207 bytes payload
1844 1845 bundle2-output-part: "check:phases" 48 bytes payload
1845 1846 bundle2-output-part: "check:updated-heads" streamed payload
1846 1847 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1847 1848 bundle2-output-part: "phase-heads" 48 bytes payload
1848 1849 bundle2-input-bundle: with-transaction
1849 1850 bundle2-input-part: "replycaps" supported
1850 1851 bundle2-input-part: total payload size 207
1851 1852 bundle2-input-part: "check:phases" supported
1852 1853 bundle2-input-part: total payload size 48
1853 1854 bundle2-input-part: "check:updated-heads" supported
1854 1855 bundle2-input-part: total payload size 40
1855 1856 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1856 1857 adding changesets
1857 1858 add changeset ef1ea85a6374
1858 1859 add changeset f9cafe1212c8
1859 1860 add changeset 911600dab2ae
1860 1861 add changeset e8fc755d4d82
1861 1862 adding manifests
1862 1863 adding file changes
1863 1864 adding abc.txt revisions
1864 1865 adding foo/Bar/file.txt revisions
1865 1866 adding foo/file.txt revisions
1866 1867 adding quux/file.py revisions
1867 1868 calling hook pretxnchangegroup.acl: hgext.acl.hook
1868 1869 acl: checking access for user "astro"
1869 1870 acl: acl.allow.branches not enabled
1870 1871 acl: acl.deny.branches enabled, 1 entries for user astro
1871 1872 acl: acl.allow not enabled
1872 1873 acl: acl.deny not enabled
1873 1874 acl: branch access granted: "ef1ea85a6374" on branch "default"
1874 1875 acl: path access granted: "ef1ea85a6374"
1875 1876 acl: branch access granted: "f9cafe1212c8" on branch "default"
1876 1877 acl: path access granted: "f9cafe1212c8"
1877 1878 acl: branch access granted: "911600dab2ae" on branch "default"
1878 1879 acl: path access granted: "911600dab2ae"
1879 1880 error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1880 1881 bundle2-input-part: total payload size 2068
1881 1882 bundle2-input-part: total payload size 48
1882 1883 bundle2-input-bundle: 5 parts total
1883 1884 transaction abort!
1884 1885 rollback completed
1885 1886 abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1886 1887 no rollback information available
1887 1888 2:fb35475503ef
1888 1889
1889 1890
1890 1891 Branch acl empty allow test
1891 1892
1892 1893 $ init_config
1893 1894 $ echo "[acl.allow.branches]" >> $config
1894 1895 $ do_push astro
1895 1896 Pushing as user astro
1896 1897 hgrc = """
1897 1898 [hooks]
1898 1899 pretxnchangegroup.acl = python:hgext.acl.hook
1899 1900 prepushkey.acl = python:hgext.acl.hook
1900 1901 [acl]
1901 1902 sources = push
1902 1903 [extensions]
1903 1904 posixgetuser=$TESTTMP/posixgetuser.py
1904 1905 [acl.allow.branches]
1905 1906 """
1906 1907 pushing to ../b
1907 1908 query 1; heads
1908 1909 searching for changes
1909 1910 all remote heads known locally
1910 1911 listing keys for "phases"
1911 1912 checking for updated bookmarks
1912 1913 listing keys for "bookmarks"
1913 1914 listing keys for "bookmarks"
1914 1915 4 changesets found
1915 1916 list of changesets:
1916 1917 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1917 1918 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1918 1919 911600dab2ae7a9baff75958b84fe606851ce955
1919 1920 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1920 1921 bundle2-output-bundle: "HG20", 5 parts total
1921 1922 bundle2-output-part: "replycaps" 207 bytes payload
1922 1923 bundle2-output-part: "check:phases" 48 bytes payload
1923 1924 bundle2-output-part: "check:updated-heads" streamed payload
1924 1925 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1925 1926 bundle2-output-part: "phase-heads" 48 bytes payload
1926 1927 bundle2-input-bundle: with-transaction
1927 1928 bundle2-input-part: "replycaps" supported
1928 1929 bundle2-input-part: total payload size 207
1929 1930 bundle2-input-part: "check:phases" supported
1930 1931 bundle2-input-part: total payload size 48
1931 1932 bundle2-input-part: "check:updated-heads" supported
1932 1933 bundle2-input-part: total payload size 40
1933 1934 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1934 1935 adding changesets
1935 1936 add changeset ef1ea85a6374
1936 1937 add changeset f9cafe1212c8
1937 1938 add changeset 911600dab2ae
1938 1939 add changeset e8fc755d4d82
1939 1940 adding manifests
1940 1941 adding file changes
1941 1942 adding abc.txt revisions
1942 1943 adding foo/Bar/file.txt revisions
1943 1944 adding foo/file.txt revisions
1944 1945 adding quux/file.py revisions
1945 1946 calling hook pretxnchangegroup.acl: hgext.acl.hook
1946 1947 acl: checking access for user "astro"
1947 1948 acl: acl.allow.branches enabled, 0 entries for user astro
1948 1949 acl: acl.deny.branches not enabled
1949 1950 acl: acl.allow not enabled
1950 1951 acl: acl.deny not enabled
1951 1952 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1952 1953 bundle2-input-part: total payload size 2068
1953 1954 bundle2-input-part: total payload size 48
1954 1955 bundle2-input-bundle: 5 parts total
1955 1956 transaction abort!
1956 1957 rollback completed
1957 1958 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1958 1959 no rollback information available
1959 1960 2:fb35475503ef
1960 1961
1961 1962
1962 1963 Branch acl allow other
1963 1964
1964 1965 $ init_config
1965 1966 $ echo "[acl.allow.branches]" >> $config
1966 1967 $ echo "* = george" >> $config
1967 1968 $ do_push astro
1968 1969 Pushing as user astro
1969 1970 hgrc = """
1970 1971 [hooks]
1971 1972 pretxnchangegroup.acl = python:hgext.acl.hook
1972 1973 prepushkey.acl = python:hgext.acl.hook
1973 1974 [acl]
1974 1975 sources = push
1975 1976 [extensions]
1976 1977 posixgetuser=$TESTTMP/posixgetuser.py
1977 1978 [acl.allow.branches]
1978 1979 * = george
1979 1980 """
1980 1981 pushing to ../b
1981 1982 query 1; heads
1982 1983 searching for changes
1983 1984 all remote heads known locally
1984 1985 listing keys for "phases"
1985 1986 checking for updated bookmarks
1986 1987 listing keys for "bookmarks"
1987 1988 listing keys for "bookmarks"
1988 1989 4 changesets found
1989 1990 list of changesets:
1990 1991 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1991 1992 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1992 1993 911600dab2ae7a9baff75958b84fe606851ce955
1993 1994 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1994 1995 bundle2-output-bundle: "HG20", 5 parts total
1995 1996 bundle2-output-part: "replycaps" 207 bytes payload
1996 1997 bundle2-output-part: "check:phases" 48 bytes payload
1997 1998 bundle2-output-part: "check:updated-heads" streamed payload
1998 1999 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1999 2000 bundle2-output-part: "phase-heads" 48 bytes payload
2000 2001 bundle2-input-bundle: with-transaction
2001 2002 bundle2-input-part: "replycaps" supported
2002 2003 bundle2-input-part: total payload size 207
2003 2004 bundle2-input-part: "check:phases" supported
2004 2005 bundle2-input-part: total payload size 48
2005 2006 bundle2-input-part: "check:updated-heads" supported
2006 2007 bundle2-input-part: total payload size 40
2007 2008 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2008 2009 adding changesets
2009 2010 add changeset ef1ea85a6374
2010 2011 add changeset f9cafe1212c8
2011 2012 add changeset 911600dab2ae
2012 2013 add changeset e8fc755d4d82
2013 2014 adding manifests
2014 2015 adding file changes
2015 2016 adding abc.txt revisions
2016 2017 adding foo/Bar/file.txt revisions
2017 2018 adding foo/file.txt revisions
2018 2019 adding quux/file.py revisions
2019 2020 calling hook pretxnchangegroup.acl: hgext.acl.hook
2020 2021 acl: checking access for user "astro"
2021 2022 acl: acl.allow.branches enabled, 0 entries for user astro
2022 2023 acl: acl.deny.branches not enabled
2023 2024 acl: acl.allow not enabled
2024 2025 acl: acl.deny not enabled
2025 2026 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
2026 2027 bundle2-input-part: total payload size 2068
2027 2028 bundle2-input-part: total payload size 48
2028 2029 bundle2-input-bundle: 5 parts total
2029 2030 transaction abort!
2030 2031 rollback completed
2031 2032 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
2032 2033 no rollback information available
2033 2034 2:fb35475503ef
2034 2035
2035 2036 $ do_push george
2036 2037 Pushing as user george
2037 2038 hgrc = """
2038 2039 [hooks]
2039 2040 pretxnchangegroup.acl = python:hgext.acl.hook
2040 2041 prepushkey.acl = python:hgext.acl.hook
2041 2042 [acl]
2042 2043 sources = push
2043 2044 [extensions]
2044 2045 posixgetuser=$TESTTMP/posixgetuser.py
2045 2046 [acl.allow.branches]
2046 2047 * = george
2047 2048 """
2048 2049 pushing to ../b
2049 2050 query 1; heads
2050 2051 searching for changes
2051 2052 all remote heads known locally
2052 2053 listing keys for "phases"
2053 2054 checking for updated bookmarks
2054 2055 listing keys for "bookmarks"
2055 2056 listing keys for "bookmarks"
2056 2057 4 changesets found
2057 2058 list of changesets:
2058 2059 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2059 2060 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2060 2061 911600dab2ae7a9baff75958b84fe606851ce955
2061 2062 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2062 2063 bundle2-output-bundle: "HG20", 5 parts total
2063 2064 bundle2-output-part: "replycaps" 207 bytes payload
2064 2065 bundle2-output-part: "check:phases" 48 bytes payload
2065 2066 bundle2-output-part: "check:updated-heads" streamed payload
2066 2067 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2067 2068 bundle2-output-part: "phase-heads" 48 bytes payload
2068 2069 bundle2-input-bundle: with-transaction
2069 2070 bundle2-input-part: "replycaps" supported
2070 2071 bundle2-input-part: total payload size 207
2071 2072 bundle2-input-part: "check:phases" supported
2072 2073 bundle2-input-part: total payload size 48
2073 2074 bundle2-input-part: "check:updated-heads" supported
2074 2075 bundle2-input-part: total payload size 40
2075 2076 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2076 2077 adding changesets
2077 2078 add changeset ef1ea85a6374
2078 2079 add changeset f9cafe1212c8
2079 2080 add changeset 911600dab2ae
2080 2081 add changeset e8fc755d4d82
2081 2082 adding manifests
2082 2083 adding file changes
2083 2084 adding abc.txt revisions
2084 2085 adding foo/Bar/file.txt revisions
2085 2086 adding foo/file.txt revisions
2086 2087 adding quux/file.py revisions
2087 2088 calling hook pretxnchangegroup.acl: hgext.acl.hook
2088 2089 acl: checking access for user "george"
2089 2090 acl: acl.allow.branches enabled, 1 entries for user george
2090 2091 acl: acl.deny.branches not enabled
2091 2092 acl: acl.allow not enabled
2092 2093 acl: acl.deny not enabled
2093 2094 acl: branch access granted: "ef1ea85a6374" on branch "default"
2094 2095 acl: path access granted: "ef1ea85a6374"
2095 2096 acl: branch access granted: "f9cafe1212c8" on branch "default"
2096 2097 acl: path access granted: "f9cafe1212c8"
2097 2098 acl: branch access granted: "911600dab2ae" on branch "default"
2098 2099 acl: path access granted: "911600dab2ae"
2099 2100 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2100 2101 acl: path access granted: "e8fc755d4d82"
2101 2102 bundle2-input-part: total payload size 2068
2102 2103 bundle2-input-part: "phase-heads" supported
2103 2104 bundle2-input-part: total payload size 48
2104 2105 bundle2-input-bundle: 5 parts total
2105 2106 updating the branch cache
2106 2107 invalid branch cache (served.hidden): tip differs
2107 2108 added 4 changesets with 4 changes to 4 files (+1 heads)
2108 2109 bundle2-output-bundle: "HG20", 1 parts total
2109 2110 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2110 2111 bundle2-input-bundle: no-transaction
2111 2112 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2112 2113 bundle2-input-bundle: 1 parts total
2113 2114 listing keys for "phases"
2114 2115 repository tip rolled back to revision 2 (undo push)
2115 2116 2:fb35475503ef
2116 2117
2117 2118
2118 2119 Branch acl conflicting allow
2119 2120 asterisk ends up applying to all branches and allowing george to
2120 2121 push foobar into the remote
2121 2122
2122 2123 $ init_config
2123 2124 $ echo "[acl.allow.branches]" >> $config
2124 2125 $ echo "foobar = astro" >> $config
2125 2126 $ echo "* = george" >> $config
2126 2127 $ do_push george
2127 2128 Pushing as user george
2128 2129 hgrc = """
2129 2130 [hooks]
2130 2131 pretxnchangegroup.acl = python:hgext.acl.hook
2131 2132 prepushkey.acl = python:hgext.acl.hook
2132 2133 [acl]
2133 2134 sources = push
2134 2135 [extensions]
2135 2136 posixgetuser=$TESTTMP/posixgetuser.py
2136 2137 [acl.allow.branches]
2137 2138 foobar = astro
2138 2139 * = george
2139 2140 """
2140 2141 pushing to ../b
2141 2142 query 1; heads
2142 2143 searching for changes
2143 2144 all remote heads known locally
2144 2145 listing keys for "phases"
2145 2146 checking for updated bookmarks
2146 2147 listing keys for "bookmarks"
2147 2148 listing keys for "bookmarks"
2148 2149 4 changesets found
2149 2150 list of changesets:
2150 2151 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2151 2152 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2152 2153 911600dab2ae7a9baff75958b84fe606851ce955
2153 2154 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2154 2155 bundle2-output-bundle: "HG20", 5 parts total
2155 2156 bundle2-output-part: "replycaps" 207 bytes payload
2156 2157 bundle2-output-part: "check:phases" 48 bytes payload
2157 2158 bundle2-output-part: "check:updated-heads" streamed payload
2158 2159 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2159 2160 bundle2-output-part: "phase-heads" 48 bytes payload
2160 2161 bundle2-input-bundle: with-transaction
2161 2162 bundle2-input-part: "replycaps" supported
2162 2163 bundle2-input-part: total payload size 207
2163 2164 bundle2-input-part: "check:phases" supported
2164 2165 bundle2-input-part: total payload size 48
2165 2166 bundle2-input-part: "check:updated-heads" supported
2166 2167 bundle2-input-part: total payload size 40
2167 2168 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2168 2169 adding changesets
2169 2170 add changeset ef1ea85a6374
2170 2171 add changeset f9cafe1212c8
2171 2172 add changeset 911600dab2ae
2172 2173 add changeset e8fc755d4d82
2173 2174 adding manifests
2174 2175 adding file changes
2175 2176 adding abc.txt revisions
2176 2177 adding foo/Bar/file.txt revisions
2177 2178 adding foo/file.txt revisions
2178 2179 adding quux/file.py revisions
2179 2180 calling hook pretxnchangegroup.acl: hgext.acl.hook
2180 2181 acl: checking access for user "george"
2181 2182 acl: acl.allow.branches enabled, 1 entries for user george
2182 2183 acl: acl.deny.branches not enabled
2183 2184 acl: acl.allow not enabled
2184 2185 acl: acl.deny not enabled
2185 2186 acl: branch access granted: "ef1ea85a6374" on branch "default"
2186 2187 acl: path access granted: "ef1ea85a6374"
2187 2188 acl: branch access granted: "f9cafe1212c8" on branch "default"
2188 2189 acl: path access granted: "f9cafe1212c8"
2189 2190 acl: branch access granted: "911600dab2ae" on branch "default"
2190 2191 acl: path access granted: "911600dab2ae"
2191 2192 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2192 2193 acl: path access granted: "e8fc755d4d82"
2193 2194 bundle2-input-part: total payload size 2068
2194 2195 bundle2-input-part: "phase-heads" supported
2195 2196 bundle2-input-part: total payload size 48
2196 2197 bundle2-input-bundle: 5 parts total
2197 2198 updating the branch cache
2198 2199 invalid branch cache (served.hidden): tip differs
2199 2200 added 4 changesets with 4 changes to 4 files (+1 heads)
2200 2201 bundle2-output-bundle: "HG20", 1 parts total
2201 2202 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2202 2203 bundle2-input-bundle: no-transaction
2203 2204 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2204 2205 bundle2-input-bundle: 1 parts total
2205 2206 listing keys for "phases"
2206 2207 repository tip rolled back to revision 2 (undo push)
2207 2208 2:fb35475503ef
2208 2209
2209 2210 Branch acl conflicting deny
2210 2211
2211 2212 $ init_config
2212 2213 $ echo "[acl.deny.branches]" >> $config
2213 2214 $ echo "foobar = astro" >> $config
2214 2215 $ echo "default = astro" >> $config
2215 2216 $ echo "* = george" >> $config
2216 2217 $ do_push george
2217 2218 Pushing as user george
2218 2219 hgrc = """
2219 2220 [hooks]
2220 2221 pretxnchangegroup.acl = python:hgext.acl.hook
2221 2222 prepushkey.acl = python:hgext.acl.hook
2222 2223 [acl]
2223 2224 sources = push
2224 2225 [extensions]
2225 2226 posixgetuser=$TESTTMP/posixgetuser.py
2226 2227 [acl.deny.branches]
2227 2228 foobar = astro
2228 2229 default = astro
2229 2230 * = george
2230 2231 """
2231 2232 pushing to ../b
2232 2233 query 1; heads
2233 2234 searching for changes
2234 2235 all remote heads known locally
2235 2236 listing keys for "phases"
2236 2237 checking for updated bookmarks
2237 2238 listing keys for "bookmarks"
2238 2239 listing keys for "bookmarks"
2239 2240 4 changesets found
2240 2241 list of changesets:
2241 2242 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2242 2243 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2243 2244 911600dab2ae7a9baff75958b84fe606851ce955
2244 2245 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2245 2246 bundle2-output-bundle: "HG20", 5 parts total
2246 2247 bundle2-output-part: "replycaps" 207 bytes payload
2247 2248 bundle2-output-part: "check:phases" 48 bytes payload
2248 2249 bundle2-output-part: "check:updated-heads" streamed payload
2249 2250 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2250 2251 bundle2-output-part: "phase-heads" 48 bytes payload
2251 2252 bundle2-input-bundle: with-transaction
2252 2253 bundle2-input-part: "replycaps" supported
2253 2254 bundle2-input-part: total payload size 207
2254 2255 bundle2-input-part: "check:phases" supported
2255 2256 bundle2-input-part: total payload size 48
2256 2257 bundle2-input-part: "check:updated-heads" supported
2257 2258 bundle2-input-part: total payload size 40
2258 2259 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2259 2260 adding changesets
2260 2261 add changeset ef1ea85a6374
2261 2262 add changeset f9cafe1212c8
2262 2263 add changeset 911600dab2ae
2263 2264 add changeset e8fc755d4d82
2264 2265 adding manifests
2265 2266 adding file changes
2266 2267 adding abc.txt revisions
2267 2268 adding foo/Bar/file.txt revisions
2268 2269 adding foo/file.txt revisions
2269 2270 adding quux/file.py revisions
2270 2271 calling hook pretxnchangegroup.acl: hgext.acl.hook
2271 2272 acl: checking access for user "george"
2272 2273 acl: acl.allow.branches not enabled
2273 2274 acl: acl.deny.branches enabled, 1 entries for user george
2274 2275 acl: acl.allow not enabled
2275 2276 acl: acl.deny not enabled
2276 2277 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2277 2278 bundle2-input-part: total payload size 2068
2278 2279 bundle2-input-part: total payload size 48
2279 2280 bundle2-input-bundle: 5 parts total
2280 2281 transaction abort!
2281 2282 rollback completed
2282 2283 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2283 2284 no rollback information available
2284 2285 2:fb35475503ef
2285 2286
2286 2287 User 'astro' must not be denied
2287 2288
2288 2289 $ init_config
2289 2290 $ echo "[acl.deny.branches]" >> $config
2290 2291 $ echo "default = !astro" >> $config
2291 2292 $ do_push astro
2292 2293 Pushing as user astro
2293 2294 hgrc = """
2294 2295 [hooks]
2295 2296 pretxnchangegroup.acl = python:hgext.acl.hook
2296 2297 prepushkey.acl = python:hgext.acl.hook
2297 2298 [acl]
2298 2299 sources = push
2299 2300 [extensions]
2300 2301 posixgetuser=$TESTTMP/posixgetuser.py
2301 2302 [acl.deny.branches]
2302 2303 default = !astro
2303 2304 """
2304 2305 pushing to ../b
2305 2306 query 1; heads
2306 2307 searching for changes
2307 2308 all remote heads known locally
2308 2309 listing keys for "phases"
2309 2310 checking for updated bookmarks
2310 2311 listing keys for "bookmarks"
2311 2312 listing keys for "bookmarks"
2312 2313 4 changesets found
2313 2314 list of changesets:
2314 2315 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2315 2316 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2316 2317 911600dab2ae7a9baff75958b84fe606851ce955
2317 2318 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2318 2319 bundle2-output-bundle: "HG20", 5 parts total
2319 2320 bundle2-output-part: "replycaps" 207 bytes payload
2320 2321 bundle2-output-part: "check:phases" 48 bytes payload
2321 2322 bundle2-output-part: "check:updated-heads" streamed payload
2322 2323 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2323 2324 bundle2-output-part: "phase-heads" 48 bytes payload
2324 2325 bundle2-input-bundle: with-transaction
2325 2326 bundle2-input-part: "replycaps" supported
2326 2327 bundle2-input-part: total payload size 207
2327 2328 bundle2-input-part: "check:phases" supported
2328 2329 bundle2-input-part: total payload size 48
2329 2330 bundle2-input-part: "check:updated-heads" supported
2330 2331 bundle2-input-part: total payload size 40
2331 2332 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2332 2333 adding changesets
2333 2334 add changeset ef1ea85a6374
2334 2335 add changeset f9cafe1212c8
2335 2336 add changeset 911600dab2ae
2336 2337 add changeset e8fc755d4d82
2337 2338 adding manifests
2338 2339 adding file changes
2339 2340 adding abc.txt revisions
2340 2341 adding foo/Bar/file.txt revisions
2341 2342 adding foo/file.txt revisions
2342 2343 adding quux/file.py revisions
2343 2344 calling hook pretxnchangegroup.acl: hgext.acl.hook
2344 2345 acl: checking access for user "astro"
2345 2346 acl: acl.allow.branches not enabled
2346 2347 acl: acl.deny.branches enabled, 0 entries for user astro
2347 2348 acl: acl.allow not enabled
2348 2349 acl: acl.deny not enabled
2349 2350 acl: branch access granted: "ef1ea85a6374" on branch "default"
2350 2351 acl: path access granted: "ef1ea85a6374"
2351 2352 acl: branch access granted: "f9cafe1212c8" on branch "default"
2352 2353 acl: path access granted: "f9cafe1212c8"
2353 2354 acl: branch access granted: "911600dab2ae" on branch "default"
2354 2355 acl: path access granted: "911600dab2ae"
2355 2356 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2356 2357 acl: path access granted: "e8fc755d4d82"
2357 2358 bundle2-input-part: total payload size 2068
2358 2359 bundle2-input-part: "phase-heads" supported
2359 2360 bundle2-input-part: total payload size 48
2360 2361 bundle2-input-bundle: 5 parts total
2361 2362 updating the branch cache
2362 2363 invalid branch cache (served.hidden): tip differs
2363 2364 added 4 changesets with 4 changes to 4 files (+1 heads)
2364 2365 bundle2-output-bundle: "HG20", 1 parts total
2365 2366 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2366 2367 bundle2-input-bundle: no-transaction
2367 2368 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2368 2369 bundle2-input-bundle: 1 parts total
2369 2370 listing keys for "phases"
2370 2371 repository tip rolled back to revision 2 (undo push)
2371 2372 2:fb35475503ef
2372 2373
2373 2374
2374 2375 Non-astro users must be denied
2375 2376
2376 2377 $ do_push george
2377 2378 Pushing as user george
2378 2379 hgrc = """
2379 2380 [hooks]
2380 2381 pretxnchangegroup.acl = python:hgext.acl.hook
2381 2382 prepushkey.acl = python:hgext.acl.hook
2382 2383 [acl]
2383 2384 sources = push
2384 2385 [extensions]
2385 2386 posixgetuser=$TESTTMP/posixgetuser.py
2386 2387 [acl.deny.branches]
2387 2388 default = !astro
2388 2389 """
2389 2390 pushing to ../b
2390 2391 query 1; heads
2391 2392 searching for changes
2392 2393 all remote heads known locally
2393 2394 listing keys for "phases"
2394 2395 checking for updated bookmarks
2395 2396 listing keys for "bookmarks"
2396 2397 listing keys for "bookmarks"
2397 2398 4 changesets found
2398 2399 list of changesets:
2399 2400 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2400 2401 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2401 2402 911600dab2ae7a9baff75958b84fe606851ce955
2402 2403 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2403 2404 bundle2-output-bundle: "HG20", 5 parts total
2404 2405 bundle2-output-part: "replycaps" 207 bytes payload
2405 2406 bundle2-output-part: "check:phases" 48 bytes payload
2406 2407 bundle2-output-part: "check:updated-heads" streamed payload
2407 2408 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2408 2409 bundle2-output-part: "phase-heads" 48 bytes payload
2409 2410 bundle2-input-bundle: with-transaction
2410 2411 bundle2-input-part: "replycaps" supported
2411 2412 bundle2-input-part: total payload size 207
2412 2413 bundle2-input-part: "check:phases" supported
2413 2414 bundle2-input-part: total payload size 48
2414 2415 bundle2-input-part: "check:updated-heads" supported
2415 2416 bundle2-input-part: total payload size 40
2416 2417 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2417 2418 adding changesets
2418 2419 add changeset ef1ea85a6374
2419 2420 add changeset f9cafe1212c8
2420 2421 add changeset 911600dab2ae
2421 2422 add changeset e8fc755d4d82
2422 2423 adding manifests
2423 2424 adding file changes
2424 2425 adding abc.txt revisions
2425 2426 adding foo/Bar/file.txt revisions
2426 2427 adding foo/file.txt revisions
2427 2428 adding quux/file.py revisions
2428 2429 calling hook pretxnchangegroup.acl: hgext.acl.hook
2429 2430 acl: checking access for user "george"
2430 2431 acl: acl.allow.branches not enabled
2431 2432 acl: acl.deny.branches enabled, 1 entries for user george
2432 2433 acl: acl.allow not enabled
2433 2434 acl: acl.deny not enabled
2434 2435 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2435 2436 bundle2-input-part: total payload size 2068
2436 2437 bundle2-input-part: total payload size 48
2437 2438 bundle2-input-bundle: 5 parts total
2438 2439 transaction abort!
2439 2440 rollback completed
2440 2441 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2441 2442 no rollback information available
2442 2443 2:fb35475503ef
2443 2444
2444 2445
@@ -1,615 +1,616 b''
1 1 #require serve
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo foo>foo
6 6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
7 7 $ echo foo>foo.d/foo
8 8 $ echo bar>foo.d/bAr.hg.d/BaR
9 9 $ echo bar>foo.d/baR.d.hg/bAR
10 10 $ hg commit -A -m 1
11 11 adding foo
12 12 adding foo.d/bAr.hg.d/BaR
13 13 adding foo.d/baR.d.hg/bAR
14 14 adding foo.d/foo
15 15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
16 16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
17 17
18 18 Test server address cannot be reused
19 19
20 20 $ hg serve -p $HGPORT1 2>&1
21 21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
22 22 [255]
23 23
24 24 $ cd ..
25 25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
26 26
27 27 clone via stream
28 28
29 29 #if no-reposimplestore
30 30 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
31 31 streaming all changes
32 32 9 files to transfer, 715 bytes of data
33 33 transferred * bytes in * seconds (*/sec) (glob)
34 34 updating to branch default
35 35 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36 $ hg verify -R copy
37 37 checking changesets
38 38 checking manifests
39 39 crosschecking files in changesets and manifests
40 40 checking files
41 41 checked 1 changesets with 4 changes to 4 files
42 42 #endif
43 43
44 44 try to clone via stream, should use pull instead
45 45
46 46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
47 47 warning: stream clone requested but server has them disabled
48 48 requesting all changes
49 49 adding changesets
50 50 adding manifests
51 51 adding file changes
52 52 added 1 changesets with 4 changes to 4 files
53 53 new changesets 8b6053c928fe
54 54 updating to branch default
55 55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 56
57 57 try to clone via stream but missing requirements, so should use pull instead
58 58
59 59 $ cat > $TESTTMP/removesupportedformat.py << EOF
60 60 > from mercurial import localrepo
61 61 > def extsetup(ui):
62 62 > localrepo.localrepository.supportedformats.remove(b'generaldelta')
63 63 > EOF
64 64
65 65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
66 66 warning: stream clone requested but client is missing requirements: generaldelta
67 67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
68 68 requesting all changes
69 69 adding changesets
70 70 adding manifests
71 71 adding file changes
72 72 added 1 changesets with 4 changes to 4 files
73 73 new changesets 8b6053c928fe
74 74 updating to branch default
75 75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 76
77 77 clone via pull
78 78
79 79 $ hg clone http://localhost:$HGPORT1/ copy-pull
80 80 requesting all changes
81 81 adding changesets
82 82 adding manifests
83 83 adding file changes
84 84 added 1 changesets with 4 changes to 4 files
85 85 new changesets 8b6053c928fe
86 86 updating to branch default
87 87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 88 $ hg verify -R copy-pull
89 89 checking changesets
90 90 checking manifests
91 91 crosschecking files in changesets and manifests
92 92 checking files
93 93 checked 1 changesets with 4 changes to 4 files
94 94 $ cd test
95 95 $ echo bar > bar
96 96 $ hg commit -A -d '1 0' -m 2
97 97 adding bar
98 98 $ cd ..
99 99
100 100 clone over http with --update
101 101
102 102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
103 103 requesting all changes
104 104 adding changesets
105 105 adding manifests
106 106 adding file changes
107 107 added 2 changesets with 5 changes to 5 files
108 108 new changesets 8b6053c928fe:5fed3813f7f5
109 109 updating to branch default
110 110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 111 $ hg log -r . -R updated
112 112 changeset: 0:8b6053c928fe
113 113 user: test
114 114 date: Thu Jan 01 00:00:00 1970 +0000
115 115 summary: 1
116 116
117 117 $ rm -rf updated
118 118
119 119 incoming via HTTP
120 120
121 121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
122 122 adding changesets
123 123 adding manifests
124 124 adding file changes
125 125 added 1 changesets with 4 changes to 4 files
126 126 new changesets 8b6053c928fe
127 127 updating to branch default
128 128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 129 $ cd partial
130 130 $ touch LOCAL
131 131 $ hg ci -qAm LOCAL
132 132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
133 133 comparing with http://localhost:$HGPORT1/
134 134 searching for changes
135 135 2
136 136 $ cd ..
137 137
138 138 pull
139 139
140 140 $ cd copy-pull
141 141 $ cat >> .hg/hgrc <<EOF
142 142 > [hooks]
143 143 > changegroup = sh -c "printenv.py --line changegroup"
144 144 > EOF
145 145 $ hg pull
146 146 pulling from http://localhost:$HGPORT1/
147 147 searching for changes
148 148 adding changesets
149 149 adding manifests
150 150 adding file changes
151 151 added 1 changesets with 1 changes to 1 files
152 152 new changesets 5fed3813f7f5
153 153 changegroup hook: HG_HOOKNAME=changegroup
154 154 HG_HOOKTYPE=changegroup
155 155 HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
156 156 HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
157 157 HG_SOURCE=pull
158 158 HG_TXNID=TXN:$ID$
159 159 HG_TXNNAME=pull
160 160 http://localhost:$HGPORT1/
161 161 HG_URL=http://localhost:$HGPORT1/
162 162
163 163 (run 'hg update' to get a working copy)
164 164 $ cd ..
165 165
166 166 clone from invalid URL
167 167
168 168 $ hg clone http://localhost:$HGPORT/bad
169 169 abort: HTTP Error 404: Not Found
170 170 [100]
171 171
172 172 test http authentication
173 173 + use the same server to test server side streaming preference
174 174
175 175 $ cd test
176 176
177 177 $ hg serve --config extensions.x=$TESTDIR/httpserverauth.py -p $HGPORT2 -d \
178 178 > --pid-file=pid --config server.preferuncompressed=True -E ../errors2.log \
179 179 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
180 180 $ cat pid >> $DAEMON_PIDS
181 181
182 182 $ cat << EOF > get_pass.py
183 183 > import getpass
184 184 > def newgetpass(arg):
185 185 > return "pass"
186 186 > getpass.getpass = newgetpass
187 187 > EOF
188 188
189 189 $ hg id http://localhost:$HGPORT2/
190 190 abort: http authorization required for http://localhost:$HGPORT2/
191 191 [255]
192 192 $ hg id http://localhost:$HGPORT2/
193 193 abort: http authorization required for http://localhost:$HGPORT2/
194 194 [255]
195 195 $ hg id --config ui.interactive=true --debug http://localhost:$HGPORT2/
196 196 using http://localhost:$HGPORT2/
197 197 sending capabilities command
198 198 http authorization required for http://localhost:$HGPORT2/
199 199 realm: mercurial
200 200 user: abort: response expected
201 201 [255]
202 202 $ cat <<'EOF' | hg id --config ui.interactive=true --config ui.nontty=true --debug http://localhost:$HGPORT2/
203 203 >
204 204 > EOF
205 205 using http://localhost:$HGPORT2/
206 206 sending capabilities command
207 207 http authorization required for http://localhost:$HGPORT2/
208 208 realm: mercurial
209 209 user:
210 210 password: abort: response expected
211 211 [255]
212 212 $ cat <<'EOF' | hg id --config ui.interactive=true --config ui.nontty=true --debug http://localhost:$HGPORT2/
213 213 >
214 214 >
215 215 > EOF
216 216 using http://localhost:$HGPORT2/
217 217 sending capabilities command
218 218 http authorization required for http://localhost:$HGPORT2/
219 219 realm: mercurial
220 220 user:
221 221 password: abort: authorization failed
222 222 [255]
223 223 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
224 224 http authorization required for http://localhost:$HGPORT2/
225 225 realm: mercurial
226 226 user: user
227 227 password: 5fed3813f7f5
228 228 $ hg id http://user:pass@localhost:$HGPORT2/
229 229 5fed3813f7f5
230 230 $ echo '[auth]' >> .hg/hgrc
231 231 $ echo 'l.schemes=http' >> .hg/hgrc
232 232 $ echo 'l.prefix=lo' >> .hg/hgrc
233 233 $ echo 'l.username=user' >> .hg/hgrc
234 234 $ echo 'l.password=pass' >> .hg/hgrc
235 235 $ hg id http://localhost:$HGPORT2/
236 236 5fed3813f7f5
237 237 $ hg id http://localhost:$HGPORT2/
238 238 5fed3813f7f5
239 239 $ hg id http://user@localhost:$HGPORT2/
240 240 5fed3813f7f5
241 241
242 242 $ cat > use_digests.py << EOF
243 243 > from mercurial import (
244 244 > exthelper,
245 245 > url,
246 246 > )
247 247 >
248 248 > eh = exthelper.exthelper()
249 249 > uisetup = eh.finaluisetup
250 250 >
251 251 > @eh.wrapfunction(url, 'opener')
252 252 > def urlopener(orig, *args, **kwargs):
253 253 > opener = orig(*args, **kwargs)
254 254 > opener.addheaders.append((r'X-HgTest-AuthType', r'Digest'))
255 255 > return opener
256 256 > EOF
257 257
258 258 $ hg id http://localhost:$HGPORT2/ --config extensions.x=use_digests.py
259 259 5fed3813f7f5
260 260
261 261 #if no-reposimplestore
262 262 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
263 263 streaming all changes
264 264 10 files to transfer, 1.01 KB of data
265 265 transferred * KB in * seconds (*/sec) (glob)
266 266 updating to branch default
267 267 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
268 268 #endif
269 269
270 270 --pull should override server's preferuncompressed
271 271 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
272 272 requesting all changes
273 273 adding changesets
274 274 adding manifests
275 275 adding file changes
276 276 added 2 changesets with 5 changes to 5 files
277 277 new changesets 8b6053c928fe:5fed3813f7f5
278 278 updating to branch default
279 279 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
280 280
281 281 $ hg id http://user2@localhost:$HGPORT2/
282 282 abort: http authorization required for http://localhost:$HGPORT2/
283 283 [255]
284 284 $ hg id http://user:pass2@localhost:$HGPORT2/
285 285 abort: HTTP Error 403: no
286 286 [100]
287 287
288 288 $ hg -R dest-pull tag -r tip top
289 289 $ hg -R dest-pull push http://user:pass@localhost:$HGPORT2/
290 290 pushing to http://user:***@localhost:$HGPORT2/
291 291 searching for changes
292 292 remote: adding changesets
293 293 remote: adding manifests
294 294 remote: adding file changes
295 295 remote: added 1 changesets with 1 changes to 1 files
296 296 $ hg rollback -q
297 297 $ hg -R dest-pull push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
298 298 pushing to http://user:***@localhost:$HGPORT2/
299 299 using http://localhost:$HGPORT2/
300 300 http auth: user user, password ****
301 301 sending capabilities command
302 302 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
303 303 http auth: user user, password ****
304 304 devel-peer-request: finished in *.???? seconds (200) (glob)
305 305 query 1; heads
306 306 devel-peer-request: batched-content
307 307 devel-peer-request: - heads (0 arguments)
308 308 devel-peer-request: - known (1 arguments)
309 309 sending batch command
310 310 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
311 311 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
312 312 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
313 313 devel-peer-request: 68 bytes of commands arguments in headers
314 314 devel-peer-request: finished in *.???? seconds (200) (glob)
315 315 searching for changes
316 316 all remote heads known locally
317 317 preparing listkeys for "phases"
318 318 sending listkeys command
319 319 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
320 320 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
321 321 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
322 322 devel-peer-request: 16 bytes of commands arguments in headers
323 323 devel-peer-request: finished in *.???? seconds (200) (glob)
324 324 received listkey for "phases": 58 bytes
325 325 checking for updated bookmarks
326 326 preparing listkeys for "bookmarks"
327 327 sending listkeys command
328 328 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
329 329 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
330 330 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
331 331 devel-peer-request: 19 bytes of commands arguments in headers
332 332 devel-peer-request: finished in *.???? seconds (200) (glob)
333 333 received listkey for "bookmarks": 0 bytes
334 334 sending branchmap command
335 335 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
336 336 devel-peer-request: Vary X-HgProto-1
337 337 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
338 338 devel-peer-request: finished in *.???? seconds (200) (glob)
339 339 preparing listkeys for "bookmarks"
340 340 sending listkeys command
341 341 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
342 342 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
343 343 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
344 344 devel-peer-request: 19 bytes of commands arguments in headers
345 345 devel-peer-request: finished in *.???? seconds (200) (glob)
346 346 received listkey for "bookmarks": 0 bytes
347 347 1 changesets found
348 348 list of changesets:
349 349 7f4e523d01f2cc3765ac8934da3d14db775ff872
350 350 bundle2-output-bundle: "HG20", 5 parts total
351 351 bundle2-output-part: "replycaps" 207 bytes payload
352 352 bundle2-output-part: "check:phases" 24 bytes payload
353 353 bundle2-output-part: "check:updated-heads" streamed payload
354 354 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
355 355 bundle2-output-part: "phase-heads" 24 bytes payload
356 356 sending unbundle command
357 357 sending 1023 bytes
358 358 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
359 359 devel-peer-request: Content-length 1023
360 360 devel-peer-request: Content-type application/mercurial-0.1
361 361 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
362 362 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
363 363 devel-peer-request: 16 bytes of commands arguments in headers
364 364 devel-peer-request: 1023 bytes of data
365 365 devel-peer-request: finished in *.???? seconds (200) (glob)
366 366 bundle2-input-bundle: no-transaction
367 367 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
368 368 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
369 369 bundle2-input-part: total payload size 55
370 370 remote: adding changesets
371 371 remote: adding manifests
372 372 remote: adding file changes
373 373 bundle2-input-part: "output" (advisory) supported
374 374 bundle2-input-part: total payload size 45
375 375 remote: added 1 changesets with 1 changes to 1 files
376 376 bundle2-input-bundle: 3 parts total
377 377 preparing listkeys for "phases"
378 378 sending listkeys command
379 379 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
380 380 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
381 381 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
382 382 devel-peer-request: 16 bytes of commands arguments in headers
383 383 devel-peer-request: finished in *.???? seconds (200) (glob)
384 384 received listkey for "phases": 15 bytes
385 (sent 9 HTTP requests and 3898 bytes; received 920 bytes in responses)
385 386 $ hg rollback -q
386 387
387 388 $ sed 's/.*] "/"/' < ../access.log
388 389 "GET /?cmd=capabilities HTTP/1.1" 401 -
389 390 "GET /?cmd=capabilities HTTP/1.1" 401 -
390 391 "GET /?cmd=capabilities HTTP/1.1" 401 -
391 392 "GET /?cmd=capabilities HTTP/1.1" 401 -
392 393 "GET /?cmd=capabilities HTTP/1.1" 401 -
393 394 "GET /?cmd=capabilities HTTP/1.1" 401 -
394 395 "GET /?cmd=capabilities HTTP/1.1" 200 -
395 396 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
396 397 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
397 398 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
398 399 "GET /?cmd=capabilities HTTP/1.1" 401 -
399 400 "GET /?cmd=capabilities HTTP/1.1" 200 -
400 401 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
401 402 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
402 403 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
403 404 "GET /?cmd=capabilities HTTP/1.1" 401 -
404 405 "GET /?cmd=capabilities HTTP/1.1" 200 -
405 406 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
406 407 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
407 408 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
408 409 "GET /?cmd=capabilities HTTP/1.1" 401 -
409 410 "GET /?cmd=capabilities HTTP/1.1" 200 -
410 411 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
411 412 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
412 413 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
413 414 "GET /?cmd=capabilities HTTP/1.1" 401 -
414 415 "GET /?cmd=capabilities HTTP/1.1" 200 -
415 416 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
416 417 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
417 418 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
418 419 "GET /?cmd=capabilities HTTP/1.1" 401 - x-hgtest-authtype:Digest
419 420 "GET /?cmd=capabilities HTTP/1.1" 200 - x-hgtest-authtype:Digest
420 421 "GET /?cmd=lookup HTTP/1.1" 401 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
421 422 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
422 423 "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
423 424 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
424 425 "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
425 426 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
426 427 "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
427 428 "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
428 429 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
429 430 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&stream=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
430 431 "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
431 432 "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
432 433 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
433 434 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
434 435 "GET /?cmd=capabilities HTTP/1.1" 401 -
435 436 "GET /?cmd=capabilities HTTP/1.1" 401 -
436 437 "GET /?cmd=capabilities HTTP/1.1" 403 -
437 438 "GET /?cmd=capabilities HTTP/1.1" 401 -
438 439 "GET /?cmd=capabilities HTTP/1.1" 200 -
439 440 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
440 441 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
441 442 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
442 443 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
443 444 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
444 445 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
445 446 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
446 447 "GET /?cmd=capabilities HTTP/1.1" 401 -
447 448 "GET /?cmd=capabilities HTTP/1.1" 200 -
448 449 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
449 450 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
450 451 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
451 452 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
452 453 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
453 454 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
454 455 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
455 456
456 457 $ cd ..
457 458
458 459 clone of serve with repo in root and unserved subrepo (issue2970)
459 460
460 461 $ hg --cwd test init sub
461 462 $ echo empty > test/sub/empty
462 463 $ hg --cwd test/sub add empty
463 464 $ hg --cwd test/sub commit -qm 'add empty'
464 465 $ hg --cwd test/sub tag -r 0 something
465 466 $ echo sub = sub > test/.hgsub
466 467 $ hg --cwd test add .hgsub
467 468 $ hg --cwd test commit -qm 'add subrepo'
468 469 $ hg clone http://localhost:$HGPORT noslash-clone
469 470 requesting all changes
470 471 adding changesets
471 472 adding manifests
472 473 adding file changes
473 474 added 3 changesets with 7 changes to 7 files
474 475 new changesets 8b6053c928fe:56f9bc90cce6
475 476 updating to branch default
476 477 cloning subrepo sub from http://localhost:$HGPORT/sub
477 478 abort: HTTP Error 404: Not Found
478 479 [100]
479 480 $ hg clone http://localhost:$HGPORT/ slash-clone
480 481 requesting all changes
481 482 adding changesets
482 483 adding manifests
483 484 adding file changes
484 485 added 3 changesets with 7 changes to 7 files
485 486 new changesets 8b6053c928fe:56f9bc90cce6
486 487 updating to branch default
487 488 cloning subrepo sub from http://localhost:$HGPORT/sub
488 489 abort: HTTP Error 404: Not Found
489 490 [100]
490 491
491 492 check error log
492 493
493 494 $ cat error.log
494 495
495 496 $ cat errors2.log
496 497
497 498 check abort error reporting while pulling/cloning
498 499
499 500 $ $RUNTESTDIR/killdaemons.py
500 501 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
501 502 $ cat hg3.pid >> $DAEMON_PIDS
502 503 $ hg clone http://localhost:$HGPORT/ abort-clone
503 504 requesting all changes
504 505 remote: abort: this is an exercise
505 506 abort: pull failed on remote
506 507 [255]
507 508 $ cat error.log
508 509
509 510 disable pull-based clones
510 511
511 512 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
512 513 $ cat hg4.pid >> $DAEMON_PIDS
513 514 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
514 515 requesting all changes
515 516 remote: abort: server has pull-based clones disabled
516 517 abort: pull failed on remote
517 518 (remove --pull if specified or upgrade Mercurial)
518 519 [255]
519 520
520 521 #if no-reposimplestore
521 522 ... but keep stream clones working
522 523
523 524 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
524 525 streaming all changes
525 526 * files to transfer, * of data (glob)
526 527 transferred * in * seconds (*/sec) (glob)
527 528 $ cat error.log
528 529 #endif
529 530
530 531 ... and also keep partial clones and pulls working
531 532 $ hg clone http://localhost:$HGPORT1 --rev 0 test/partial/clone
532 533 adding changesets
533 534 adding manifests
534 535 adding file changes
535 536 added 1 changesets with 4 changes to 4 files
536 537 new changesets 8b6053c928fe
537 538 updating to branch default
538 539 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
539 540 $ hg pull -R test/partial/clone
540 541 pulling from http://localhost:$HGPORT1/
541 542 searching for changes
542 543 adding changesets
543 544 adding manifests
544 545 adding file changes
545 546 added 2 changesets with 3 changes to 3 files
546 547 new changesets 5fed3813f7f5:56f9bc90cce6
547 548 (run 'hg update' to get a working copy)
548 549
549 550 $ hg clone -U -r 0 test/partial/clone test/another/clone
550 551 adding changesets
551 552 adding manifests
552 553 adding file changes
553 554 added 1 changesets with 4 changes to 4 files
554 555 new changesets 8b6053c928fe
555 556
556 557 corrupt cookies file should yield a warning
557 558
558 559 $ cat > $TESTTMP/cookies.txt << EOF
559 560 > bad format
560 561 > EOF
561 562
562 563 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
563 564 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
564 565 56f9bc90cce6
565 566
566 567 $ killdaemons.py
567 568
568 569 Create dummy authentication handler that looks for cookies. It doesn't do anything
569 570 useful. It just raises an HTTP 500 with details about the Cookie request header.
570 571 We raise HTTP 500 because its message is printed in the abort message.
571 572
572 573 $ cat > cookieauth.py << EOF
573 574 > from mercurial import util
574 575 > from mercurial.hgweb import common
575 576 > def perform_authentication(hgweb, req, op):
576 577 > cookie = req.headers.get(b'Cookie')
577 578 > if not cookie:
578 579 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, b'no-cookie')
579 580 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, b'Cookie: %s' % cookie)
580 581 > def extsetup(ui):
581 582 > common.permhooks.insert(0, perform_authentication)
582 583 > EOF
583 584
584 585 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
585 586 $ cat pid > $DAEMON_PIDS
586 587
587 588 Request without cookie sent should fail due to lack of cookie
588 589
589 590 $ hg id http://localhost:$HGPORT
590 591 abort: HTTP Error 500: no-cookie
591 592 [100]
592 593
593 594 Populate a cookies file
594 595
595 596 $ cat > cookies.txt << EOF
596 597 > # HTTP Cookie File
597 598 > # Expiration is 2030-01-01 at midnight
598 599 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
599 600 > EOF
600 601
601 602 Should not send a cookie for another domain
602 603
603 604 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
604 605 abort: HTTP Error 500: no-cookie
605 606 [100]
606 607
607 608 Add a cookie entry for our test server and verify it is sent
608 609
609 610 $ cat >> cookies.txt << EOF
610 611 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
611 612 > EOF
612 613
613 614 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
614 615 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
615 616 [100]
@@ -1,693 +1,694 b''
1 1 #testcases lfsremote-on lfsremote-off
2 2 #require serve no-reposimplestore no-chg
3 3
4 4 This test splits `hg serve` with and without using the extension into separate
5 5 tests cases. The tests are broken down as follows, where "LFS"/"No-LFS"
6 6 indicates whether or not there are commits that use an LFS file, and "D"/"E"
7 7 indicates whether or not the extension is loaded. The "X" cases are not tested
8 8 individually, because the lfs requirement causes the process to bail early if
9 9 the extension is disabled.
10 10
11 11 . Server
12 12 .
13 13 . No-LFS LFS
14 14 . +----------------------------+
15 15 . | || D | E | D | E |
16 16 . |---++=======================|
17 17 . C | D || N/A | #1 | X | #4 |
18 18 . l No +---++-----------------------|
19 19 . i LFS | E || #2 | #2 | X | #5 |
20 20 . e +---++-----------------------|
21 21 . n | D || X | X | X | X |
22 22 . t LFS |---++-----------------------|
23 23 . | E || #3 | #3 | X | #6 |
24 24 . |---++-----------------------+
25 25
26 26 make command server magic visible
27 27
28 28 #if windows
29 29 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
30 30 #else
31 31 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
32 32 #endif
33 33 $ export PYTHONPATH
34 34
35 35 $ hg init server
36 36 $ SERVER_REQUIRES="$TESTTMP/server/.hg/requires"
37 37
38 38 $ cat > $TESTTMP/debugprocessors.py <<EOF
39 39 > from mercurial import (
40 40 > cmdutil,
41 41 > commands,
42 42 > pycompat,
43 43 > registrar,
44 44 > )
45 45 > cmdtable = {}
46 46 > command = registrar.command(cmdtable)
47 47 > @command(b'debugprocessors', [], b'FILE')
48 48 > def debugprocessors(ui, repo, file_=None, **opts):
49 49 > opts = pycompat.byteskwargs(opts)
50 50 > opts[b'changelog'] = False
51 51 > opts[b'manifest'] = False
52 52 > opts[b'dir'] = False
53 53 > rl = cmdutil.openrevlog(repo, b'debugprocessors', file_, opts)
54 54 > for flag, proc in rl._flagprocessors.items():
55 55 > ui.status(b"registered processor '%#x'\n" % (flag))
56 56 > EOF
57 57
58 58 Skip the experimental.changegroup3=True config. Failure to agree on this comes
59 59 first, and causes an "abort: no common changegroup version" if the extension is
60 60 only loaded on one side. If that *is* enabled, the subsequent failure is "abort:
61 61 missing processor for flag '0x2000'!" if the extension is only loaded on one side
62 62 (possibly also masked by the Internal Server Error message).
63 63 $ cat >> $HGRCPATH <<EOF
64 64 > [extensions]
65 65 > debugprocessors = $TESTTMP/debugprocessors.py
66 66 > [experimental]
67 67 > lfs.disableusercache = True
68 68 > lfs.worker-enable = False
69 69 > [lfs]
70 70 > threshold=10
71 71 > [web]
72 72 > allow_push=*
73 73 > push_ssl=False
74 74 > EOF
75 75
76 76 $ cp $HGRCPATH $HGRCPATH.orig
77 77
78 78 #if lfsremote-on
79 79 $ hg --config extensions.lfs= -R server \
80 80 > serve -p $HGPORT -d --pid-file=hg.pid --errorlog=$TESTTMP/errors.log
81 81 #else
82 82 $ hg --config extensions.lfs=! -R server \
83 83 > serve -p $HGPORT -d --pid-file=hg.pid --errorlog=$TESTTMP/errors.log
84 84 #endif
85 85
86 86 $ cat hg.pid >> $DAEMON_PIDS
87 87 $ hg clone -q http://localhost:$HGPORT client
88 88 $ grep 'lfs' client/.hg/requires $SERVER_REQUIRES
89 89 [1]
90 90
91 91 This trivial repo will force commandserver to load the extension, but not call
92 92 reposetup() on another repo actually being operated on. This gives coverage
93 93 that wrapper functions are not assuming reposetup() was called.
94 94
95 95 $ hg init $TESTTMP/cmdservelfs
96 96 $ cat >> $TESTTMP/cmdservelfs/.hg/hgrc << EOF
97 97 > [extensions]
98 98 > lfs =
99 99 > EOF
100 100
101 101 --------------------------------------------------------------------------------
102 102 Case #1: client with non-lfs content and the extension disabled; server with
103 103 non-lfs content, and the extension enabled.
104 104
105 105 $ cd client
106 106 $ echo 'non-lfs' > nonlfs.txt
107 107 >>> from __future__ import absolute_import
108 108 >>> from hgclient import check, readchannel, runcommand
109 109 >>> @check
110 110 ... def diff(server):
111 111 ... readchannel(server)
112 112 ... # run an arbitrary command in the repo with the extension loaded
113 113 ... runcommand(server, [b'id', b'-R', b'../cmdservelfs'])
114 114 ... # now run a command in a repo without the extension to ensure that
115 115 ... # files are added safely..
116 116 ... runcommand(server, [b'ci', b'-Aqm', b'non-lfs'])
117 117 ... # .. and that scmutil.prefetchfiles() safely no-ops..
118 118 ... runcommand(server, [b'diff', b'-r', b'.~1'])
119 119 ... # .. and that debugupgraderepo safely no-ops.
120 120 ... runcommand(server, [b'debugupgraderepo', b'-q', b'--run'])
121 121 *** runcommand id -R ../cmdservelfs
122 122 000000000000 tip
123 123 *** runcommand ci -Aqm non-lfs
124 124 *** runcommand diff -r .~1
125 125 diff -r 000000000000 nonlfs.txt
126 126 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
127 127 +++ b/nonlfs.txt Thu Jan 01 00:00:00 1970 +0000
128 128 @@ -0,0 +1,1 @@
129 129 +non-lfs
130 130 *** runcommand debugupgraderepo -q --run
131 131
132 132 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
133 133 [1]
134 134
135 135 #if lfsremote-on
136 136
137 137 $ hg push -q
138 138 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
139 139 [1]
140 140
141 141 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client1_clone
142 142 $ grep 'lfs' $TESTTMP/client1_clone/.hg/requires $SERVER_REQUIRES
143 143 [1]
144 144
145 145 $ hg init $TESTTMP/client1_pull
146 146 $ hg -R $TESTTMP/client1_pull pull -q http://localhost:$HGPORT
147 147 $ grep 'lfs' $TESTTMP/client1_pull/.hg/requires $SERVER_REQUIRES
148 148 [1]
149 149
150 150 $ hg identify http://localhost:$HGPORT
151 151 d437e1d24fbd
152 152
153 153 #endif
154 154
155 155 --------------------------------------------------------------------------------
156 156 Case #2: client with non-lfs content and the extension enabled; server with
157 157 non-lfs content, and the extension state controlled by #testcases.
158 158
159 159 $ cat >> $HGRCPATH <<EOF
160 160 > [extensions]
161 161 > lfs =
162 162 > EOF
163 163 $ echo 'non-lfs' > nonlfs2.txt
164 164 $ hg ci -Aqm 'non-lfs file with lfs client'
165 165
166 166 Since no lfs content has been added yet, the push is allowed, even when the
167 167 extension is not enabled remotely.
168 168
169 169 $ hg push -q
170 170 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
171 171 [1]
172 172
173 173 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client2_clone
174 174 $ grep 'lfs' $TESTTMP/client2_clone/.hg/requires $SERVER_REQUIRES
175 175 [1]
176 176
177 177 $ hg init $TESTTMP/client2_pull
178 178 $ hg -R $TESTTMP/client2_pull pull -q http://localhost:$HGPORT
179 179 $ grep 'lfs' $TESTTMP/client2_pull/.hg/requires $SERVER_REQUIRES
180 180 [1]
181 181
182 182 $ hg identify http://localhost:$HGPORT
183 183 1477875038c6
184 184
185 185 --------------------------------------------------------------------------------
186 186 Case #3: client with lfs content and the extension enabled; server with
187 187 non-lfs content, and the extension state controlled by #testcases. The server
188 188 should have an 'lfs' requirement after it picks up its first commit with a blob.
189 189
190 190 $ echo 'this is a big lfs file' > lfs.bin
191 191 $ hg ci -Aqm 'lfs'
192 192 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
193 193 .hg/requires:lfs
194 194
195 195 #if lfsremote-off
196 196 $ hg push -q
197 197 abort: required features are not supported in the destination: lfs
198 198 (enable the lfs extension on the server)
199 199 [255]
200 200 #else
201 201 $ hg push -q
202 202 #endif
203 203 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
204 204 .hg/requires:lfs
205 205 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
206 206
207 207 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client3_clone
208 208 $ grep 'lfs' $TESTTMP/client3_clone/.hg/requires $SERVER_REQUIRES || true
209 209 $TESTTMP/client3_clone/.hg/requires:lfs (lfsremote-on !)
210 210 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
211 211
212 212 $ hg init $TESTTMP/client3_pull
213 213 $ hg -R $TESTTMP/client3_pull pull -q http://localhost:$HGPORT
214 214 $ grep 'lfs' $TESTTMP/client3_pull/.hg/requires $SERVER_REQUIRES || true
215 215 $TESTTMP/client3_pull/.hg/requires:lfs (lfsremote-on !)
216 216 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
217 217
218 218 Test that the commit/changegroup requirement check hook can be run multiple
219 219 times.
220 220
221 221 $ hg clone -qr 0 http://localhost:$HGPORT $TESTTMP/cmdserve_client3
222 222
223 223 $ cd ../cmdserve_client3
224 224
225 225 >>> from __future__ import absolute_import
226 226 >>> from hgclient import check, readchannel, runcommand
227 227 >>> @check
228 228 ... def addrequirement(server):
229 229 ... readchannel(server)
230 230 ... # change the repo in a way that adds the lfs requirement
231 231 ... runcommand(server, [b'pull', b'-qu'])
232 232 ... # Now cause the requirement adding hook to fire again, without going
233 233 ... # through reposetup() again.
234 234 ... with open('file.txt', 'wb') as fp:
235 235 ... fp.write(b'data')
236 236 ... runcommand(server, [b'ci', b'-Aqm', b'non-lfs'])
237 237 *** runcommand pull -qu
238 238 *** runcommand ci -Aqm non-lfs
239 239
240 240 $ cd ../client
241 241
242 242 The difference here is the push failed above when the extension isn't
243 243 enabled on the server.
244 244 $ hg identify http://localhost:$HGPORT
245 245 8374dc4052cb (lfsremote-on !)
246 246 1477875038c6 (lfsremote-off !)
247 247
248 248 Don't bother testing the lfsremote-off cases- the server won't be able
249 249 to launch if there's lfs content and the extension is disabled.
250 250
251 251 #if lfsremote-on
252 252
253 253 --------------------------------------------------------------------------------
254 254 Case #4: client with non-lfs content and the extension disabled; server with
255 255 lfs content, and the extension enabled.
256 256
257 257 $ cat >> $HGRCPATH <<EOF
258 258 > [extensions]
259 259 > lfs = !
260 260 > EOF
261 261
262 262 $ hg init $TESTTMP/client4
263 263 $ cd $TESTTMP/client4
264 264 $ cat >> .hg/hgrc <<EOF
265 265 > [paths]
266 266 > default = http://localhost:$HGPORT
267 267 > EOF
268 268 $ echo 'non-lfs' > nonlfs2.txt
269 269 $ hg ci -Aqm 'non-lfs'
270 270 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
271 271 $TESTTMP/server/.hg/requires:lfs
272 272
273 273 $ hg push -q --force
274 274 warning: repository is unrelated
275 275 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
276 276 $TESTTMP/server/.hg/requires:lfs
277 277
278 278 $ hg clone http://localhost:$HGPORT $TESTTMP/client4_clone
279 279 (remote is using large file support (lfs), but it is explicitly disabled in the local configuration)
280 280 abort: repository requires features unknown to this Mercurial: lfs
281 281 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
282 282 [255]
283 283 $ grep 'lfs' $TESTTMP/client4_clone/.hg/requires $SERVER_REQUIRES
284 284 grep: $TESTTMP/client4_clone/.hg/requires: $ENOENT$
285 285 $TESTTMP/server/.hg/requires:lfs
286 286 [2]
287 287
288 288 TODO: fail more gracefully.
289 289
290 290 $ hg init $TESTTMP/client4_pull
291 291 $ hg -R $TESTTMP/client4_pull pull http://localhost:$HGPORT
292 292 pulling from http://localhost:$HGPORT/
293 293 requesting all changes
294 294 remote: abort: no common changegroup version
295 295 abort: pull failed on remote
296 296 [255]
297 297 $ grep 'lfs' $TESTTMP/client4_pull/.hg/requires $SERVER_REQUIRES
298 298 $TESTTMP/server/.hg/requires:lfs
299 299
300 300 $ hg identify http://localhost:$HGPORT
301 301 03b080fa9d93
302 302
303 303 --------------------------------------------------------------------------------
304 304 Case #5: client with non-lfs content and the extension enabled; server with
305 305 lfs content, and the extension enabled.
306 306
307 307 $ cat >> $HGRCPATH <<EOF
308 308 > [extensions]
309 309 > lfs =
310 310 > EOF
311 311 $ echo 'non-lfs' > nonlfs3.txt
312 312 $ hg ci -Aqm 'non-lfs file with lfs client'
313 313
314 314 $ hg push -q
315 315 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
316 316 $TESTTMP/server/.hg/requires:lfs
317 317
318 318 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client5_clone
319 319 $ grep 'lfs' $TESTTMP/client5_clone/.hg/requires $SERVER_REQUIRES
320 320 $TESTTMP/client5_clone/.hg/requires:lfs
321 321 $TESTTMP/server/.hg/requires:lfs
322 322
323 323 $ hg init $TESTTMP/client5_pull
324 324 $ hg -R $TESTTMP/client5_pull pull -q http://localhost:$HGPORT
325 325 $ grep 'lfs' $TESTTMP/client5_pull/.hg/requires $SERVER_REQUIRES
326 326 $TESTTMP/client5_pull/.hg/requires:lfs
327 327 $TESTTMP/server/.hg/requires:lfs
328 328
329 329 $ hg identify http://localhost:$HGPORT
330 330 c729025cc5e3
331 331
332 332 $ mv $HGRCPATH $HGRCPATH.tmp
333 333 $ cp $HGRCPATH.orig $HGRCPATH
334 334
335 335 >>> from __future__ import absolute_import
336 336 >>> from hgclient import bprint, check, readchannel, runcommand, stdout
337 337 >>> @check
338 338 ... def checkflags(server):
339 339 ... readchannel(server)
340 340 ... bprint(b'')
341 341 ... bprint(b'# LFS required- both lfs and non-lfs revlogs have 0x2000 flag')
342 342 ... stdout.flush()
343 343 ... runcommand(server, [b'debugprocessors', b'lfs.bin', b'-R',
344 344 ... b'../server'])
345 345 ... runcommand(server, [b'debugprocessors', b'nonlfs2.txt', b'-R',
346 346 ... b'../server'])
347 347 ... runcommand(server, [b'config', b'extensions', b'--cwd',
348 348 ... b'../server'])
349 349 ...
350 350 ... bprint(b"\n# LFS not enabled- revlogs don't have 0x2000 flag")
351 351 ... stdout.flush()
352 352 ... runcommand(server, [b'debugprocessors', b'nonlfs3.txt'])
353 353 ... runcommand(server, [b'config', b'extensions'])
354 354
355 355 # LFS required- both lfs and non-lfs revlogs have 0x2000 flag
356 356 *** runcommand debugprocessors lfs.bin -R ../server
357 357 registered processor '0x8000'
358 358 registered processor '0x800'
359 359 registered processor '0x2000'
360 360 *** runcommand debugprocessors nonlfs2.txt -R ../server
361 361 registered processor '0x8000'
362 362 registered processor '0x800'
363 363 registered processor '0x2000'
364 364 *** runcommand config extensions --cwd ../server
365 365 extensions.debugprocessors=$TESTTMP/debugprocessors.py
366 366 extensions.lfs=
367 367
368 368 # LFS not enabled- revlogs don't have 0x2000 flag
369 369 *** runcommand debugprocessors nonlfs3.txt
370 370 registered processor '0x8000'
371 371 registered processor '0x800'
372 372 *** runcommand config extensions
373 373 extensions.debugprocessors=$TESTTMP/debugprocessors.py
374 374
375 375 $ rm $HGRCPATH
376 376 $ mv $HGRCPATH.tmp $HGRCPATH
377 377
378 378 $ hg clone $TESTTMP/client $TESTTMP/nonlfs -qr 0 --config extensions.lfs=
379 379 $ cat >> $TESTTMP/nonlfs/.hg/hgrc <<EOF
380 380 > [extensions]
381 381 > lfs = !
382 382 > EOF
383 383
384 384 >>> from __future__ import absolute_import, print_function
385 385 >>> from hgclient import bprint, check, readchannel, runcommand, stdout
386 386 >>> @check
387 387 ... def checkflags2(server):
388 388 ... readchannel(server)
389 389 ... bprint(b'')
390 390 ... bprint(b'# LFS enabled- both lfs and non-lfs revlogs have 0x2000 flag')
391 391 ... stdout.flush()
392 392 ... runcommand(server, [b'debugprocessors', b'lfs.bin', b'-R',
393 393 ... b'../server'])
394 394 ... runcommand(server, [b'debugprocessors', b'nonlfs2.txt', b'-R',
395 395 ... b'../server'])
396 396 ... runcommand(server, [b'config', b'extensions', b'--cwd',
397 397 ... b'../server'])
398 398 ...
399 399 ... bprint(b'\n# LFS enabled without requirement- revlogs have 0x2000 flag')
400 400 ... stdout.flush()
401 401 ... runcommand(server, [b'debugprocessors', b'nonlfs3.txt'])
402 402 ... runcommand(server, [b'config', b'extensions'])
403 403 ...
404 404 ... bprint(b"\n# LFS disabled locally- revlogs don't have 0x2000 flag")
405 405 ... stdout.flush()
406 406 ... runcommand(server, [b'debugprocessors', b'nonlfs.txt', b'-R',
407 407 ... b'../nonlfs'])
408 408 ... runcommand(server, [b'config', b'extensions', b'--cwd',
409 409 ... b'../nonlfs'])
410 410
411 411 # LFS enabled- both lfs and non-lfs revlogs have 0x2000 flag
412 412 *** runcommand debugprocessors lfs.bin -R ../server
413 413 registered processor '0x8000'
414 414 registered processor '0x800'
415 415 registered processor '0x2000'
416 416 *** runcommand debugprocessors nonlfs2.txt -R ../server
417 417 registered processor '0x8000'
418 418 registered processor '0x800'
419 419 registered processor '0x2000'
420 420 *** runcommand config extensions --cwd ../server
421 421 extensions.debugprocessors=$TESTTMP/debugprocessors.py
422 422 extensions.lfs=
423 423
424 424 # LFS enabled without requirement- revlogs have 0x2000 flag
425 425 *** runcommand debugprocessors nonlfs3.txt
426 426 registered processor '0x8000'
427 427 registered processor '0x800'
428 428 registered processor '0x2000'
429 429 *** runcommand config extensions
430 430 extensions.debugprocessors=$TESTTMP/debugprocessors.py
431 431 extensions.lfs=
432 432
433 433 # LFS disabled locally- revlogs don't have 0x2000 flag
434 434 *** runcommand debugprocessors nonlfs.txt -R ../nonlfs
435 435 registered processor '0x8000'
436 436 registered processor '0x800'
437 437 *** runcommand config extensions --cwd ../nonlfs
438 438 extensions.debugprocessors=$TESTTMP/debugprocessors.py
439 439 extensions.lfs=!
440 440
441 441 --------------------------------------------------------------------------------
442 442 Case #6: client with lfs content and the extension enabled; server with
443 443 lfs content, and the extension enabled.
444 444
445 445 $ echo 'this is another lfs file' > lfs2.txt
446 446 $ hg ci -Aqm 'lfs file with lfs client'
447 447
448 448 $ hg --config paths.default= push -v http://localhost:$HGPORT
449 449 pushing to http://localhost:$HGPORT/
450 450 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
451 451 searching for changes
452 452 remote has heads on branch 'default' that are not known locally: 8374dc4052cb
453 453 lfs: uploading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
454 454 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
455 455 lfs: uploaded 1 files (25 bytes)
456 456 1 changesets found
457 457 uncompressed size of bundle content:
458 458 206 (changelog)
459 459 172 (manifests)
460 460 275 lfs2.txt
461 461 remote: adding changesets
462 462 remote: adding manifests
463 463 remote: adding file changes
464 464 remote: added 1 changesets with 1 changes to 1 files
465 (sent 8 HTTP requests and 3526 bytes; received 961 bytes in responses) (?)
465 466 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
466 467 .hg/requires:lfs
467 468 $TESTTMP/server/.hg/requires:lfs
468 469
469 470 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client6_clone
470 471 $ grep 'lfs' $TESTTMP/client6_clone/.hg/requires $SERVER_REQUIRES
471 472 $TESTTMP/client6_clone/.hg/requires:lfs
472 473 $TESTTMP/server/.hg/requires:lfs
473 474
474 475 $ hg init $TESTTMP/client6_pull
475 476 $ hg -R $TESTTMP/client6_pull pull -u -v http://localhost:$HGPORT
476 477 pulling from http://localhost:$HGPORT/
477 478 requesting all changes
478 479 adding changesets
479 480 adding manifests
480 481 adding file changes
481 482 calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs
482 483 added 6 changesets with 5 changes to 5 files (+1 heads)
483 484 new changesets d437e1d24fbd:d3b84d50eacb
484 485 resolving manifests
485 486 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
486 487 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
487 488 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
488 489 lfs: downloaded 1 files (25 bytes)
489 490 getting lfs2.txt
490 491 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
491 492 getting nonlfs2.txt
492 493 getting nonlfs3.txt
493 494 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
494 495 updated to "d3b84d50eacb: lfs file with lfs client"
495 496 1 other heads for branch "default"
496 497 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
497 498 $ grep 'lfs' $TESTTMP/client6_pull/.hg/requires $SERVER_REQUIRES
498 499 $TESTTMP/client6_pull/.hg/requires:lfs
499 500 $TESTTMP/server/.hg/requires:lfs
500 501
501 502 $ hg identify http://localhost:$HGPORT
502 503 d3b84d50eacb
503 504
504 505 --------------------------------------------------------------------------------
505 506 Misc: process dies early if a requirement exists and the extension is disabled
506 507
507 508 $ hg --config extensions.lfs=! summary
508 509 abort: repository requires features unknown to this Mercurial: lfs
509 510 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
510 511 [255]
511 512
512 513 $ echo 'this is an lfs file' > $TESTTMP/client6_clone/lfspair1.bin
513 514 $ echo 'this is an lfs file too' > $TESTTMP/client6_clone/lfspair2.bin
514 515 $ hg -R $TESTTMP/client6_clone ci -Aqm 'add lfs pair'
515 516 $ hg -R $TESTTMP/client6_clone push -q
516 517
517 518 $ hg clone -qU http://localhost:$HGPORT $TESTTMP/bulkfetch
518 519
519 520 Cat doesn't prefetch unless data is needed (e.g. '-T {rawdata}' doesn't need it)
520 521
521 522 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T '{rawdata}\n{path}\n'
522 523 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
523 524 version https://git-lfs.github.com/spec/v1
524 525 oid sha256:cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
525 526 size 20
526 527 x-is-binary 0
527 528
528 529 lfspair1.bin
529 530
530 531 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T json
531 532 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
532 533 [lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
533 534 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
534 535 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
535 536 lfs: downloaded 1 files (20 bytes)
536 537 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
537 538
538 539 {
539 540 "data": "this is an lfs file\n",
540 541 "path": "lfspair1.bin",
541 542 "rawdata": "version https://git-lfs.github.com/spec/v1\noid sha256:cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782\nsize 20\nx-is-binary 0\n"
542 543 }
543 544 ]
544 545
545 546 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
546 547
547 548 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T '{data}\n'
548 549 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
549 550 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
550 551 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
551 552 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
552 553 lfs: downloaded 1 files (20 bytes)
553 554 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
554 555 this is an lfs file
555 556
556 557 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair2.bin
557 558 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
558 559 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
559 560 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
560 561 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
561 562 lfs: downloaded 1 files (24 bytes)
562 563 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
563 564 this is an lfs file too
564 565
565 566 Export will prefetch all needed files across all needed revisions
566 567
567 568 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
568 569 $ hg -R $TESTTMP/bulkfetch -v export -r 0:tip -o all.export
569 570 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
570 571 exporting patches:
571 572 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
572 573 lfs: need to transfer 4 objects (92 bytes)
573 574 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
574 575 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
575 576 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
576 577 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
577 578 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
578 579 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
579 580 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
580 581 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
581 582 lfs: downloaded 4 files (92 bytes)
582 583 all.export
583 584 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
584 585 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
585 586 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
586 587 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
587 588
588 589 Export with selected files is used with `extdiff --patch`
589 590
590 591 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
591 592 $ hg --config extensions.extdiff= \
592 593 > -R $TESTTMP/bulkfetch -v extdiff -r 2:tip --patch $TESTTMP/bulkfetch/lfs.bin
593 594 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
594 595 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
595 596 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
596 597 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
597 598 lfs: downloaded 1 files (23 bytes)
598 599 */hg-8374dc4052cb.patch (glob)
599 600 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
600 601 */hg-9640b57e77b1.patch (glob)
601 602 --- */hg-8374dc4052cb.patch * (glob)
602 603 +++ */hg-9640b57e77b1.patch * (glob)
603 604 @@ -2,12 +2,7 @@
604 605 # User test
605 606 # Date 0 0
606 607 # Thu Jan 01 00:00:00 1970 +0000
607 608 -# Node ID 8374dc4052cbd388e79d9dc4ddb29784097aa354
608 609 -# Parent 1477875038c60152e391238920a16381c627b487
609 610 -lfs
610 611 +# Node ID 9640b57e77b14c3a0144fb4478b6cc13e13ea0d1
611 612 +# Parent d3b84d50eacbd56638e11abce6b8616aaba54420
612 613 +add lfs pair
613 614
614 615 -diff -r 1477875038c6 -r 8374dc4052cb lfs.bin
615 616 ---- /dev/null Thu Jan 01 00:00:00 1970 +0000
616 617 -+++ b/lfs.bin Thu Jan 01 00:00:00 1970 +0000
617 618 -@@ -0,0 +1,1 @@
618 619 -+this is a big lfs file
619 620 cleaning up temp directory
620 621 [1]
621 622
622 623 Diff will prefetch files
623 624
624 625 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
625 626 $ hg -R $TESTTMP/bulkfetch -v diff -r 2:tip
626 627 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
627 628 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
628 629 lfs: need to transfer 4 objects (92 bytes)
629 630 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
630 631 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
631 632 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
632 633 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
633 634 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
634 635 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
635 636 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
636 637 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
637 638 lfs: downloaded 4 files (92 bytes)
638 639 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
639 640 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
640 641 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
641 642 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
642 643 diff -r 8374dc4052cb -r 9640b57e77b1 lfs.bin
643 644 --- a/lfs.bin Thu Jan 01 00:00:00 1970 +0000
644 645 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
645 646 @@ -1,1 +0,0 @@
646 647 -this is a big lfs file
647 648 diff -r 8374dc4052cb -r 9640b57e77b1 lfs2.txt
648 649 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
649 650 +++ b/lfs2.txt Thu Jan 01 00:00:00 1970 +0000
650 651 @@ -0,0 +1,1 @@
651 652 +this is another lfs file
652 653 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair1.bin
653 654 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
654 655 +++ b/lfspair1.bin Thu Jan 01 00:00:00 1970 +0000
655 656 @@ -0,0 +1,1 @@
656 657 +this is an lfs file
657 658 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair2.bin
658 659 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
659 660 +++ b/lfspair2.bin Thu Jan 01 00:00:00 1970 +0000
660 661 @@ -0,0 +1,1 @@
661 662 +this is an lfs file too
662 663 diff -r 8374dc4052cb -r 9640b57e77b1 nonlfs.txt
663 664 --- a/nonlfs.txt Thu Jan 01 00:00:00 1970 +0000
664 665 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
665 666 @@ -1,1 +0,0 @@
666 667 -non-lfs
667 668 diff -r 8374dc4052cb -r 9640b57e77b1 nonlfs3.txt
668 669 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
669 670 +++ b/nonlfs3.txt Thu Jan 01 00:00:00 1970 +0000
670 671 @@ -0,0 +1,1 @@
671 672 +non-lfs
672 673
673 674 Only the files required by diff are prefetched
674 675
675 676 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
676 677 $ hg -R $TESTTMP/bulkfetch -v diff -r 2:tip $TESTTMP/bulkfetch/lfspair2.bin
677 678 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
678 679 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
679 680 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
680 681 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
681 682 lfs: downloaded 1 files (24 bytes)
682 683 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
683 684 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair2.bin
684 685 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
685 686 +++ b/lfspair2.bin Thu Jan 01 00:00:00 1970 +0000
686 687 @@ -0,0 +1,1 @@
687 688 +this is an lfs file too
688 689
689 690 #endif
690 691
691 692 $ "$PYTHON" $TESTDIR/killdaemons.py $DAEMON_PIDS
692 693
693 694 $ cat $TESTTMP/errors.log
General Comments 0
You need to be logged in to leave comments. Login now