##// END OF EJS Templates
ssh: quote parameters using shellquote (SEC)...
Jun Wu -
r33715:00a75672 stable
parent child Browse files
Show More
@@ -1,653 +1,656
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import fcntl
12 12 import getpass
13 13 import grp
14 14 import os
15 15 import pwd
16 16 import re
17 17 import select
18 18 import stat
19 19 import sys
20 20 import tempfile
21 21 import unicodedata
22 22
23 23 from .i18n import _
24 24 from . import (
25 25 encoding,
26 26 error,
27 27 pycompat,
28 28 )
29 29
30 30 posixfile = open
31 31 normpath = os.path.normpath
32 32 samestat = os.path.samestat
33 33 try:
34 34 oslink = os.link
35 35 except AttributeError:
36 36 # Some platforms build Python without os.link on systems that are
37 37 # vaguely unix-like but don't have hardlink support. For those
38 38 # poor souls, just say we tried and that it failed so we fall back
39 39 # to copies.
40 40 def oslink(src, dst):
41 41 raise OSError(errno.EINVAL,
42 42 'hardlinks not supported: %s to %s' % (src, dst))
43 43 unlink = os.unlink
44 44 rename = os.rename
45 45 removedirs = os.removedirs
46 46 expandglobs = False
47 47
48 48 umask = os.umask(0)
49 49 os.umask(umask)
50 50
51 51 def split(p):
52 52 '''Same as posixpath.split, but faster
53 53
54 54 >>> import posixpath
55 55 >>> for f in ['/absolute/path/to/file',
56 56 ... 'relative/path/to/file',
57 57 ... 'file_alone',
58 58 ... 'path/to/directory/',
59 59 ... '/multiple/path//separators',
60 60 ... '/file_at_root',
61 61 ... '///multiple_leading_separators_at_root',
62 62 ... '']:
63 63 ... assert split(f) == posixpath.split(f), f
64 64 '''
65 65 ht = p.rsplit('/', 1)
66 66 if len(ht) == 1:
67 67 return '', p
68 68 nh = ht[0].rstrip('/')
69 69 if nh:
70 70 return nh, ht[1]
71 71 return ht[0] + '/', ht[1]
72 72
73 73 def openhardlinks():
74 74 '''return true if it is safe to hold open file handles to hardlinks'''
75 75 return True
76 76
77 77 def nlinks(name):
78 78 '''return number of hardlinks for the given file'''
79 79 return os.lstat(name).st_nlink
80 80
81 81 def parsepatchoutput(output_line):
82 82 """parses the output produced by patch and returns the filename"""
83 83 pf = output_line[14:]
84 84 if pycompat.sysplatform == 'OpenVMS':
85 85 if pf[0] == '`':
86 86 pf = pf[1:-1] # Remove the quotes
87 87 else:
88 88 if pf.startswith("'") and pf.endswith("'") and " " in pf:
89 89 pf = pf[1:-1] # Remove the quotes
90 90 return pf
91 91
92 92 def sshargs(sshcmd, host, user, port):
93 93 '''Build argument list for ssh'''
94 94 args = user and ("%s@%s" % (user, host)) or host
95 if '-' in args[:2]:
95 if '-' in args[:1]:
96 96 raise error.Abort(
97 97 _('illegal ssh hostname or username starting with -: %s') % args)
98 return port and ("%s -p %s" % (args, port)) or args
98 args = shellquote(args)
99 if port:
100 args = '-p %s %s' % (shellquote(port), args)
101 return args
99 102
100 103 def isexec(f):
101 104 """check whether a file is executable"""
102 105 return (os.lstat(f).st_mode & 0o100 != 0)
103 106
104 107 def setflags(f, l, x):
105 108 s = os.lstat(f).st_mode
106 109 if l:
107 110 if not stat.S_ISLNK(s):
108 111 # switch file to link
109 112 fp = open(f)
110 113 data = fp.read()
111 114 fp.close()
112 115 unlink(f)
113 116 try:
114 117 os.symlink(data, f)
115 118 except OSError:
116 119 # failed to make a link, rewrite file
117 120 fp = open(f, "w")
118 121 fp.write(data)
119 122 fp.close()
120 123 # no chmod needed at this point
121 124 return
122 125 if stat.S_ISLNK(s):
123 126 # switch link to file
124 127 data = os.readlink(f)
125 128 unlink(f)
126 129 fp = open(f, "w")
127 130 fp.write(data)
128 131 fp.close()
129 132 s = 0o666 & ~umask # avoid restatting for chmod
130 133
131 134 sx = s & 0o100
132 135 if x and not sx:
133 136 # Turn on +x for every +r bit when making a file executable
134 137 # and obey umask.
135 138 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
136 139 elif not x and sx:
137 140 # Turn off all +x bits
138 141 os.chmod(f, s & 0o666)
139 142
140 143 def copymode(src, dst, mode=None):
141 144 '''Copy the file mode from the file at path src to dst.
142 145 If src doesn't exist, we're using mode instead. If mode is None, we're
143 146 using umask.'''
144 147 try:
145 148 st_mode = os.lstat(src).st_mode & 0o777
146 149 except OSError as inst:
147 150 if inst.errno != errno.ENOENT:
148 151 raise
149 152 st_mode = mode
150 153 if st_mode is None:
151 154 st_mode = ~umask
152 155 st_mode &= 0o666
153 156 os.chmod(dst, st_mode)
154 157
155 158 def checkexec(path):
156 159 """
157 160 Check whether the given path is on a filesystem with UNIX-like exec flags
158 161
159 162 Requires a directory (like /foo/.hg)
160 163 """
161 164
162 165 # VFAT on some Linux versions can flip mode but it doesn't persist
163 166 # a FS remount. Frequently we can detect it if files are created
164 167 # with exec bit on.
165 168
166 169 try:
167 170 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
168 171 cachedir = os.path.join(path, '.hg', 'cache')
169 172 if os.path.isdir(cachedir):
170 173 checkisexec = os.path.join(cachedir, 'checkisexec')
171 174 checknoexec = os.path.join(cachedir, 'checknoexec')
172 175
173 176 try:
174 177 m = os.stat(checkisexec).st_mode
175 178 except OSError as e:
176 179 if e.errno != errno.ENOENT:
177 180 raise
178 181 # checkisexec does not exist - fall through ...
179 182 else:
180 183 # checkisexec exists, check if it actually is exec
181 184 if m & EXECFLAGS != 0:
182 185 # ensure checkisexec exists, check it isn't exec
183 186 try:
184 187 m = os.stat(checknoexec).st_mode
185 188 except OSError as e:
186 189 if e.errno != errno.ENOENT:
187 190 raise
188 191 open(checknoexec, 'w').close() # might fail
189 192 m = os.stat(checknoexec).st_mode
190 193 if m & EXECFLAGS == 0:
191 194 # check-exec is exec and check-no-exec is not exec
192 195 return True
193 196 # checknoexec exists but is exec - delete it
194 197 unlink(checknoexec)
195 198 # checkisexec exists but is not exec - delete it
196 199 unlink(checkisexec)
197 200
198 201 # check using one file, leave it as checkisexec
199 202 checkdir = cachedir
200 203 else:
201 204 # check directly in path and don't leave checkisexec behind
202 205 checkdir = path
203 206 checkisexec = None
204 207 fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-')
205 208 try:
206 209 os.close(fh)
207 210 m = os.stat(fn).st_mode
208 211 if m & EXECFLAGS == 0:
209 212 os.chmod(fn, m & 0o777 | EXECFLAGS)
210 213 if os.stat(fn).st_mode & EXECFLAGS != 0:
211 214 if checkisexec is not None:
212 215 os.rename(fn, checkisexec)
213 216 fn = None
214 217 return True
215 218 finally:
216 219 if fn is not None:
217 220 unlink(fn)
218 221 except (IOError, OSError):
219 222 # we don't care, the user probably won't be able to commit anyway
220 223 return False
221 224
222 225 def checklink(path):
223 226 """check whether the given path is on a symlink-capable filesystem"""
224 227 # mktemp is not racy because symlink creation will fail if the
225 228 # file already exists
226 229 while True:
227 230 cachedir = os.path.join(path, '.hg', 'cache')
228 231 checklink = os.path.join(cachedir, 'checklink')
229 232 # try fast path, read only
230 233 if os.path.islink(checklink):
231 234 return True
232 235 if os.path.isdir(cachedir):
233 236 checkdir = cachedir
234 237 else:
235 238 checkdir = path
236 239 cachedir = None
237 240 fscheckdir = pycompat.fsdecode(checkdir)
238 241 name = tempfile.mktemp(dir=fscheckdir,
239 242 prefix=r'checklink-')
240 243 name = pycompat.fsencode(name)
241 244 try:
242 245 fd = None
243 246 if cachedir is None:
244 247 fd = tempfile.NamedTemporaryFile(dir=fscheckdir,
245 248 prefix=r'hg-checklink-')
246 249 target = pycompat.fsencode(os.path.basename(fd.name))
247 250 else:
248 251 # create a fixed file to link to; doesn't matter if it
249 252 # already exists.
250 253 target = 'checklink-target'
251 254 open(os.path.join(cachedir, target), 'w').close()
252 255 try:
253 256 os.symlink(target, name)
254 257 if cachedir is None:
255 258 unlink(name)
256 259 else:
257 260 try:
258 261 os.rename(name, checklink)
259 262 except OSError:
260 263 unlink(name)
261 264 return True
262 265 except OSError as inst:
263 266 # link creation might race, try again
264 267 if inst[0] == errno.EEXIST:
265 268 continue
266 269 raise
267 270 finally:
268 271 if fd is not None:
269 272 fd.close()
270 273 except AttributeError:
271 274 return False
272 275 except OSError as inst:
273 276 # sshfs might report failure while successfully creating the link
274 277 if inst[0] == errno.EIO and os.path.exists(name):
275 278 unlink(name)
276 279 return False
277 280
278 281 def checkosfilename(path):
279 282 '''Check that the base-relative path is a valid filename on this platform.
280 283 Returns None if the path is ok, or a UI string describing the problem.'''
281 284 pass # on posix platforms, every path is ok
282 285
283 286 def setbinary(fd):
284 287 pass
285 288
286 289 def pconvert(path):
287 290 return path
288 291
289 292 def localpath(path):
290 293 return path
291 294
292 295 def samefile(fpath1, fpath2):
293 296 """Returns whether path1 and path2 refer to the same file. This is only
294 297 guaranteed to work for files, not directories."""
295 298 return os.path.samefile(fpath1, fpath2)
296 299
297 300 def samedevice(fpath1, fpath2):
298 301 """Returns whether fpath1 and fpath2 are on the same device. This is only
299 302 guaranteed to work for files, not directories."""
300 303 st1 = os.lstat(fpath1)
301 304 st2 = os.lstat(fpath2)
302 305 return st1.st_dev == st2.st_dev
303 306
304 307 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
305 308 def normcase(path):
306 309 return path.lower()
307 310
308 311 # what normcase does to ASCII strings
309 312 normcasespec = encoding.normcasespecs.lower
310 313 # fallback normcase function for non-ASCII strings
311 314 normcasefallback = normcase
312 315
313 316 if pycompat.sysplatform == 'darwin':
314 317
315 318 def normcase(path):
316 319 '''
317 320 Normalize a filename for OS X-compatible comparison:
318 321 - escape-encode invalid characters
319 322 - decompose to NFD
320 323 - lowercase
321 324 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
322 325
323 326 >>> normcase('UPPER')
324 327 'upper'
325 328 >>> normcase('Caf\xc3\xa9')
326 329 'cafe\\xcc\\x81'
327 330 >>> normcase('\xc3\x89')
328 331 'e\\xcc\\x81'
329 332 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
330 333 '%b8%ca%c3\\xca\\xbe%c8.jpg'
331 334 '''
332 335
333 336 try:
334 337 return encoding.asciilower(path) # exception for non-ASCII
335 338 except UnicodeDecodeError:
336 339 return normcasefallback(path)
337 340
338 341 normcasespec = encoding.normcasespecs.lower
339 342
340 343 def normcasefallback(path):
341 344 try:
342 345 u = path.decode('utf-8')
343 346 except UnicodeDecodeError:
344 347 # OS X percent-encodes any bytes that aren't valid utf-8
345 348 s = ''
346 349 pos = 0
347 350 l = len(path)
348 351 while pos < l:
349 352 try:
350 353 c = encoding.getutf8char(path, pos)
351 354 pos += len(c)
352 355 except ValueError:
353 356 c = '%%%02X' % ord(path[pos])
354 357 pos += 1
355 358 s += c
356 359
357 360 u = s.decode('utf-8')
358 361
359 362 # Decompose then lowercase (HFS+ technote specifies lower)
360 363 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
361 364 # drop HFS+ ignored characters
362 365 return encoding.hfsignoreclean(enc)
363 366
364 367 if pycompat.sysplatform == 'cygwin':
365 368 # workaround for cygwin, in which mount point part of path is
366 369 # treated as case sensitive, even though underlying NTFS is case
367 370 # insensitive.
368 371
369 372 # default mount points
370 373 cygwinmountpoints = sorted([
371 374 "/usr/bin",
372 375 "/usr/lib",
373 376 "/cygdrive",
374 377 ], reverse=True)
375 378
376 379 # use upper-ing as normcase as same as NTFS workaround
377 380 def normcase(path):
378 381 pathlen = len(path)
379 382 if (pathlen == 0) or (path[0] != pycompat.ossep):
380 383 # treat as relative
381 384 return encoding.upper(path)
382 385
383 386 # to preserve case of mountpoint part
384 387 for mp in cygwinmountpoints:
385 388 if not path.startswith(mp):
386 389 continue
387 390
388 391 mplen = len(mp)
389 392 if mplen == pathlen: # mount point itself
390 393 return mp
391 394 if path[mplen] == pycompat.ossep:
392 395 return mp + encoding.upper(path[mplen:])
393 396
394 397 return encoding.upper(path)
395 398
396 399 normcasespec = encoding.normcasespecs.other
397 400 normcasefallback = normcase
398 401
399 402 # Cygwin translates native ACLs to POSIX permissions,
400 403 # but these translations are not supported by native
401 404 # tools, so the exec bit tends to be set erroneously.
402 405 # Therefore, disable executable bit access on Cygwin.
403 406 def checkexec(path):
404 407 return False
405 408
406 409 # Similarly, Cygwin's symlink emulation is likely to create
407 410 # problems when Mercurial is used from both Cygwin and native
408 411 # Windows, with other native tools, or on shared volumes
409 412 def checklink(path):
410 413 return False
411 414
412 415 _needsshellquote = None
413 416 def shellquote(s):
414 417 if pycompat.sysplatform == 'OpenVMS':
415 418 return '"%s"' % s
416 419 global _needsshellquote
417 420 if _needsshellquote is None:
418 421 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
419 422 if s and not _needsshellquote(s):
420 423 # "s" shouldn't have to be quoted
421 424 return s
422 425 else:
423 426 return "'%s'" % s.replace("'", "'\\''")
424 427
425 428 def quotecommand(cmd):
426 429 return cmd
427 430
428 431 def popen(command, mode='r'):
429 432 return os.popen(command, mode)
430 433
431 434 def testpid(pid):
432 435 '''return False if pid dead, True if running or not sure'''
433 436 if pycompat.sysplatform == 'OpenVMS':
434 437 return True
435 438 try:
436 439 os.kill(pid, 0)
437 440 return True
438 441 except OSError as inst:
439 442 return inst.errno != errno.ESRCH
440 443
441 444 def explainexit(code):
442 445 """return a 2-tuple (desc, code) describing a subprocess status
443 446 (codes from kill are negative - not os.system/wait encoding)"""
444 447 if code >= 0:
445 448 return _("exited with status %d") % code, code
446 449 return _("killed by signal %d") % -code, -code
447 450
448 451 def isowner(st):
449 452 """Return True if the stat object st is from the current user."""
450 453 return st.st_uid == os.getuid()
451 454
452 455 def findexe(command):
453 456 '''Find executable for command searching like which does.
454 457 If command is a basename then PATH is searched for command.
455 458 PATH isn't searched if command is an absolute or relative path.
456 459 If command isn't found None is returned.'''
457 460 if pycompat.sysplatform == 'OpenVMS':
458 461 return command
459 462
460 463 def findexisting(executable):
461 464 'Will return executable if existing file'
462 465 if os.path.isfile(executable) and os.access(executable, os.X_OK):
463 466 return executable
464 467 return None
465 468
466 469 if pycompat.ossep in command:
467 470 return findexisting(command)
468 471
469 472 if pycompat.sysplatform == 'plan9':
470 473 return findexisting(os.path.join('/bin', command))
471 474
472 475 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
473 476 executable = findexisting(os.path.join(path, command))
474 477 if executable is not None:
475 478 return executable
476 479 return None
477 480
478 481 def setsignalhandler():
479 482 pass
480 483
481 484 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
482 485
483 486 def statfiles(files):
484 487 '''Stat each file in files. Yield each stat, or None if a file does not
485 488 exist or has a type we don't care about.'''
486 489 lstat = os.lstat
487 490 getkind = stat.S_IFMT
488 491 for nf in files:
489 492 try:
490 493 st = lstat(nf)
491 494 if getkind(st.st_mode) not in _wantedkinds:
492 495 st = None
493 496 except OSError as err:
494 497 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
495 498 raise
496 499 st = None
497 500 yield st
498 501
499 502 def getuser():
500 503 '''return name of current user'''
501 504 return getpass.getuser()
502 505
503 506 def username(uid=None):
504 507 """Return the name of the user with the given uid.
505 508
506 509 If uid is None, return the name of the current user."""
507 510
508 511 if uid is None:
509 512 uid = os.getuid()
510 513 try:
511 514 return pwd.getpwuid(uid)[0]
512 515 except KeyError:
513 516 return str(uid)
514 517
515 518 def groupname(gid=None):
516 519 """Return the name of the group with the given gid.
517 520
518 521 If gid is None, return the name of the current group."""
519 522
520 523 if gid is None:
521 524 gid = os.getgid()
522 525 try:
523 526 return grp.getgrgid(gid)[0]
524 527 except KeyError:
525 528 return str(gid)
526 529
527 530 def groupmembers(name):
528 531 """Return the list of members of the group with the given
529 532 name, KeyError if the group does not exist.
530 533 """
531 534 return list(grp.getgrnam(name).gr_mem)
532 535
533 536 def spawndetached(args):
534 537 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
535 538 args[0], args)
536 539
537 540 def gethgcmd():
538 541 return sys.argv[:1]
539 542
540 543 def makedir(path, notindexed):
541 544 os.mkdir(path)
542 545
543 546 def lookupreg(key, name=None, scope=None):
544 547 return None
545 548
546 549 def hidewindow():
547 550 """Hide current shell window.
548 551
549 552 Used to hide the window opened when starting asynchronous
550 553 child process under Windows, unneeded on other systems.
551 554 """
552 555 pass
553 556
554 557 class cachestat(object):
555 558 def __init__(self, path):
556 559 self.stat = os.stat(path)
557 560
558 561 def cacheable(self):
559 562 return bool(self.stat.st_ino)
560 563
561 564 __hash__ = object.__hash__
562 565
563 566 def __eq__(self, other):
564 567 try:
565 568 # Only dev, ino, size, mtime and atime are likely to change. Out
566 569 # of these, we shouldn't compare atime but should compare the
567 570 # rest. However, one of the other fields changing indicates
568 571 # something fishy going on, so return False if anything but atime
569 572 # changes.
570 573 return (self.stat.st_mode == other.stat.st_mode and
571 574 self.stat.st_ino == other.stat.st_ino and
572 575 self.stat.st_dev == other.stat.st_dev and
573 576 self.stat.st_nlink == other.stat.st_nlink and
574 577 self.stat.st_uid == other.stat.st_uid and
575 578 self.stat.st_gid == other.stat.st_gid and
576 579 self.stat.st_size == other.stat.st_size and
577 580 self.stat.st_mtime == other.stat.st_mtime and
578 581 self.stat.st_ctime == other.stat.st_ctime)
579 582 except AttributeError:
580 583 return False
581 584
582 585 def __ne__(self, other):
583 586 return not self == other
584 587
585 588 def executablepath():
586 589 return None # available on Windows only
587 590
588 591 def statislink(st):
589 592 '''check whether a stat result is a symlink'''
590 593 return st and stat.S_ISLNK(st.st_mode)
591 594
592 595 def statisexec(st):
593 596 '''check whether a stat result is an executable file'''
594 597 return st and (st.st_mode & 0o100 != 0)
595 598
596 599 def poll(fds):
597 600 """block until something happens on any file descriptor
598 601
599 602 This is a generic helper that will check for any activity
600 603 (read, write. exception) and return the list of touched files.
601 604
602 605 In unsupported cases, it will raise a NotImplementedError"""
603 606 try:
604 607 while True:
605 608 try:
606 609 res = select.select(fds, fds, fds)
607 610 break
608 611 except select.error as inst:
609 612 if inst.args[0] == errno.EINTR:
610 613 continue
611 614 raise
612 615 except ValueError: # out of range file descriptor
613 616 raise NotImplementedError()
614 617 return sorted(list(set(sum(res, []))))
615 618
616 619 def readpipe(pipe):
617 620 """Read all available data from a pipe."""
618 621 # We can't fstat() a pipe because Linux will always report 0.
619 622 # So, we set the pipe to non-blocking mode and read everything
620 623 # that's available.
621 624 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
622 625 flags |= os.O_NONBLOCK
623 626 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
624 627
625 628 try:
626 629 chunks = []
627 630 while True:
628 631 try:
629 632 s = pipe.read()
630 633 if not s:
631 634 break
632 635 chunks.append(s)
633 636 except IOError:
634 637 break
635 638
636 639 return ''.join(chunks)
637 640 finally:
638 641 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
639 642
640 643 def bindunixsocket(sock, path):
641 644 """Bind the UNIX domain socket to the specified path"""
642 645 # use relative path instead of full path at bind() if possible, since
643 646 # AF_UNIX path has very small length limit (107 chars) on common
644 647 # platforms (see sys/un.h)
645 648 dirname, basename = os.path.split(path)
646 649 bakwdfd = None
647 650 if dirname:
648 651 bakwdfd = os.open('.', os.O_DIRECTORY)
649 652 os.chdir(dirname)
650 653 sock.bind(basename)
651 654 if bakwdfd:
652 655 os.fchdir(bakwdfd)
653 656 os.close(bakwdfd)
@@ -1,369 +1,366
1 1 # sshpeer.py - ssh repository proxy class for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import re
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 error,
15 15 util,
16 16 wireproto,
17 17 )
18 18
19 19 class remotelock(object):
20 20 def __init__(self, repo):
21 21 self.repo = repo
22 22 def release(self):
23 23 self.repo.unlock()
24 24 self.repo = None
25 25 def __enter__(self):
26 26 return self
27 27 def __exit__(self, exc_type, exc_val, exc_tb):
28 28 if self.repo:
29 29 self.release()
30 30 def __del__(self):
31 31 if self.repo:
32 32 self.release()
33 33
34 34 def _serverquote(s):
35 35 if not s:
36 36 return s
37 37 '''quote a string for the remote shell ... which we assume is sh'''
38 38 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
39 39 return s
40 40 return "'%s'" % s.replace("'", "'\\''")
41 41
42 42 def _forwardoutput(ui, pipe):
43 43 """display all data currently available on pipe as remote output.
44 44
45 45 This is non blocking."""
46 46 s = util.readpipe(pipe)
47 47 if s:
48 48 for l in s.splitlines():
49 49 ui.status(_("remote: "), l, '\n')
50 50
51 51 class doublepipe(object):
52 52 """Operate a side-channel pipe in addition of a main one
53 53
54 54 The side-channel pipe contains server output to be forwarded to the user
55 55 input. The double pipe will behave as the "main" pipe, but will ensure the
56 56 content of the "side" pipe is properly processed while we wait for blocking
57 57 call on the "main" pipe.
58 58
59 59 If large amounts of data are read from "main", the forward will cease after
60 60 the first bytes start to appear. This simplifies the implementation
61 61 without affecting actual output of sshpeer too much as we rarely issue
62 62 large read for data not yet emitted by the server.
63 63
64 64 The main pipe is expected to be a 'bufferedinputpipe' from the util module
65 65 that handle all the os specific bits. This class lives in this module
66 66 because it focus on behavior specific to the ssh protocol."""
67 67
68 68 def __init__(self, ui, main, side):
69 69 self._ui = ui
70 70 self._main = main
71 71 self._side = side
72 72
73 73 def _wait(self):
74 74 """wait until some data are available on main or side
75 75
76 76 return a pair of boolean (ismainready, issideready)
77 77
78 78 (This will only wait for data if the setup is supported by `util.poll`)
79 79 """
80 80 if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe
81 81 return (True, True) # main has data, assume side is worth poking at.
82 82 fds = [self._main.fileno(), self._side.fileno()]
83 83 try:
84 84 act = util.poll(fds)
85 85 except NotImplementedError:
86 86 # non supported yet case, assume all have data.
87 87 act = fds
88 88 return (self._main.fileno() in act, self._side.fileno() in act)
89 89
90 90 def write(self, data):
91 91 return self._call('write', data)
92 92
93 93 def read(self, size):
94 94 r = self._call('read', size)
95 95 if size != 0 and not r:
96 96 # We've observed a condition that indicates the
97 97 # stdout closed unexpectedly. Check stderr one
98 98 # more time and snag anything that's there before
99 99 # letting anyone know the main part of the pipe
100 100 # closed prematurely.
101 101 _forwardoutput(self._ui, self._side)
102 102 return r
103 103
104 104 def readline(self):
105 105 return self._call('readline')
106 106
107 107 def _call(self, methname, data=None):
108 108 """call <methname> on "main", forward output of "side" while blocking
109 109 """
110 110 # data can be '' or 0
111 111 if (data is not None and not data) or self._main.closed:
112 112 _forwardoutput(self._ui, self._side)
113 113 return ''
114 114 while True:
115 115 mainready, sideready = self._wait()
116 116 if sideready:
117 117 _forwardoutput(self._ui, self._side)
118 118 if mainready:
119 119 meth = getattr(self._main, methname)
120 120 if data is None:
121 121 return meth()
122 122 else:
123 123 return meth(data)
124 124
125 125 def close(self):
126 126 return self._main.close()
127 127
128 128 def flush(self):
129 129 return self._main.flush()
130 130
131 131 class sshpeer(wireproto.wirepeer):
132 132 def __init__(self, ui, path, create=False):
133 133 self._url = path
134 134 self.ui = ui
135 135 self.pipeo = self.pipei = self.pipee = None
136 136
137 137 u = util.url(path, parsequery=False, parsefragment=False)
138 138 if u.scheme != 'ssh' or not u.host or u.path is None:
139 139 self._abort(error.RepoError(_("couldn't parse location %s") % path))
140 140
141 141 util.checksafessh(path)
142 142
143 143 self.user = u.user
144 144 if u.passwd is not None:
145 145 self._abort(error.RepoError(_("password in URL not supported")))
146 146 self.host = u.host
147 147 self.port = u.port
148 148 self.path = u.path or "."
149 149
150 150 sshcmd = self.ui.config("ui", "ssh", "ssh")
151 151 remotecmd = self.ui.config("ui", "remotecmd", "hg")
152 152
153 args = util.sshargs(sshcmd,
154 _serverquote(self.host),
155 _serverquote(self.user),
156 _serverquote(self.port))
153 args = util.sshargs(sshcmd, self.host, self.user, self.port)
157 154
158 155 if create:
159 156 cmd = '%s %s %s' % (sshcmd, args,
160 157 util.shellquote("%s init %s" %
161 158 (_serverquote(remotecmd), _serverquote(self.path))))
162 159 ui.debug('running %s\n' % cmd)
163 160 res = ui.system(cmd, blockedtag='sshpeer')
164 161 if res != 0:
165 162 self._abort(error.RepoError(_("could not create remote repo")))
166 163
167 164 self._validaterepo(sshcmd, args, remotecmd)
168 165
169 166 def url(self):
170 167 return self._url
171 168
172 169 def _validaterepo(self, sshcmd, args, remotecmd):
173 170 # cleanup up previous run
174 171 self.cleanup()
175 172
176 173 cmd = '%s %s %s' % (sshcmd, args,
177 174 util.shellquote("%s -R %s serve --stdio" %
178 175 (_serverquote(remotecmd), _serverquote(self.path))))
179 176 self.ui.debug('running %s\n' % cmd)
180 177 cmd = util.quotecommand(cmd)
181 178
182 179 # while self.subprocess isn't used, having it allows the subprocess to
183 180 # to clean up correctly later
184 181 #
185 182 # no buffer allow the use of 'select'
186 183 # feel free to remove buffering and select usage when we ultimately
187 184 # move to threading.
188 185 sub = util.popen4(cmd, bufsize=0)
189 186 self.pipeo, self.pipei, self.pipee, self.subprocess = sub
190 187
191 188 self.pipei = util.bufferedinputpipe(self.pipei)
192 189 self.pipei = doublepipe(self.ui, self.pipei, self.pipee)
193 190 self.pipeo = doublepipe(self.ui, self.pipeo, self.pipee)
194 191
195 192 # skip any noise generated by remote shell
196 193 self._callstream("hello")
197 194 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
198 195 lines = ["", "dummy"]
199 196 max_noise = 500
200 197 while lines[-1] and max_noise:
201 198 l = r.readline()
202 199 self.readerr()
203 200 if lines[-1] == "1\n" and l == "\n":
204 201 break
205 202 if l:
206 203 self.ui.debug("remote: ", l)
207 204 lines.append(l)
208 205 max_noise -= 1
209 206 else:
210 207 self._abort(error.RepoError(_('no suitable response from '
211 208 'remote hg')))
212 209
213 210 self._caps = set()
214 211 for l in reversed(lines):
215 212 if l.startswith("capabilities:"):
216 213 self._caps.update(l[:-1].split(":")[1].split())
217 214 break
218 215
219 216 def _capabilities(self):
220 217 return self._caps
221 218
222 219 def readerr(self):
223 220 _forwardoutput(self.ui, self.pipee)
224 221
225 222 def _abort(self, exception):
226 223 self.cleanup()
227 224 raise exception
228 225
229 226 def cleanup(self):
230 227 if self.pipeo is None:
231 228 return
232 229 self.pipeo.close()
233 230 self.pipei.close()
234 231 try:
235 232 # read the error descriptor until EOF
236 233 for l in self.pipee:
237 234 self.ui.status(_("remote: "), l)
238 235 except (IOError, ValueError):
239 236 pass
240 237 self.pipee.close()
241 238
242 239 __del__ = cleanup
243 240
244 241 def _submitbatch(self, req):
245 242 rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req))
246 243 available = self._getamount()
247 244 # TODO this response parsing is probably suboptimal for large
248 245 # batches with large responses.
249 246 toread = min(available, 1024)
250 247 work = rsp.read(toread)
251 248 available -= toread
252 249 chunk = work
253 250 while chunk:
254 251 while ';' in work:
255 252 one, work = work.split(';', 1)
256 253 yield wireproto.unescapearg(one)
257 254 toread = min(available, 1024)
258 255 chunk = rsp.read(toread)
259 256 available -= toread
260 257 work += chunk
261 258 yield wireproto.unescapearg(work)
262 259
263 260 def _callstream(self, cmd, **args):
264 261 self.ui.debug("sending %s command\n" % cmd)
265 262 self.pipeo.write("%s\n" % cmd)
266 263 _func, names = wireproto.commands[cmd]
267 264 keys = names.split()
268 265 wireargs = {}
269 266 for k in keys:
270 267 if k == '*':
271 268 wireargs['*'] = args
272 269 break
273 270 else:
274 271 wireargs[k] = args[k]
275 272 del args[k]
276 273 for k, v in sorted(wireargs.iteritems()):
277 274 self.pipeo.write("%s %d\n" % (k, len(v)))
278 275 if isinstance(v, dict):
279 276 for dk, dv in v.iteritems():
280 277 self.pipeo.write("%s %d\n" % (dk, len(dv)))
281 278 self.pipeo.write(dv)
282 279 else:
283 280 self.pipeo.write(v)
284 281 self.pipeo.flush()
285 282
286 283 return self.pipei
287 284
288 285 def _callcompressable(self, cmd, **args):
289 286 return self._callstream(cmd, **args)
290 287
291 288 def _call(self, cmd, **args):
292 289 self._callstream(cmd, **args)
293 290 return self._recv()
294 291
295 292 def _callpush(self, cmd, fp, **args):
296 293 r = self._call(cmd, **args)
297 294 if r:
298 295 return '', r
299 296 for d in iter(lambda: fp.read(4096), ''):
300 297 self._send(d)
301 298 self._send("", flush=True)
302 299 r = self._recv()
303 300 if r:
304 301 return '', r
305 302 return self._recv(), ''
306 303
307 304 def _calltwowaystream(self, cmd, fp, **args):
308 305 r = self._call(cmd, **args)
309 306 if r:
310 307 # XXX needs to be made better
311 308 raise error.Abort(_('unexpected remote reply: %s') % r)
312 309 for d in iter(lambda: fp.read(4096), ''):
313 310 self._send(d)
314 311 self._send("", flush=True)
315 312 return self.pipei
316 313
317 314 def _getamount(self):
318 315 l = self.pipei.readline()
319 316 if l == '\n':
320 317 self.readerr()
321 318 msg = _('check previous remote output')
322 319 self._abort(error.OutOfBandError(hint=msg))
323 320 self.readerr()
324 321 try:
325 322 return int(l)
326 323 except ValueError:
327 324 self._abort(error.ResponseError(_("unexpected response:"), l))
328 325
329 326 def _recv(self):
330 327 return self.pipei.read(self._getamount())
331 328
332 329 def _send(self, data, flush=False):
333 330 self.pipeo.write("%d\n" % len(data))
334 331 if data:
335 332 self.pipeo.write(data)
336 333 if flush:
337 334 self.pipeo.flush()
338 335 self.readerr()
339 336
340 337 def lock(self):
341 338 self._call("lock")
342 339 return remotelock(self)
343 340
344 341 def unlock(self):
345 342 self._call("unlock")
346 343
347 344 def addchangegroup(self, cg, source, url, lock=None):
348 345 '''Send a changegroup to the remote server. Return an integer
349 346 similar to unbundle(). DEPRECATED, since it requires locking the
350 347 remote.'''
351 348 d = self._call("addchangegroup")
352 349 if d:
353 350 self._abort(error.RepoError(_("push refused: %s") % d))
354 351 for d in iter(lambda: cg.read(4096), ''):
355 352 self.pipeo.write(d)
356 353 self.readerr()
357 354
358 355 self.pipeo.flush()
359 356
360 357 self.readerr()
361 358 r = self._recv()
362 359 if not r:
363 360 return 1
364 361 try:
365 362 return int(r)
366 363 except ValueError:
367 364 self._abort(error.ResponseError(_("unexpected response:"), r))
368 365
369 366 instance = sshpeer
@@ -1,477 +1,480
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import msvcrt
12 12 import os
13 13 import re
14 14 import stat
15 15 import sys
16 16
17 17 from .i18n import _
18 18 from . import (
19 19 encoding,
20 20 error,
21 21 osutil,
22 22 pycompat,
23 23 win32,
24 24 )
25 25
26 26 try:
27 27 import _winreg as winreg
28 28 winreg.CloseKey
29 29 except ImportError:
30 30 import winreg
31 31
32 32 executablepath = win32.executablepath
33 33 getuser = win32.getuser
34 34 hidewindow = win32.hidewindow
35 35 makedir = win32.makedir
36 36 nlinks = win32.nlinks
37 37 oslink = win32.oslink
38 38 samedevice = win32.samedevice
39 39 samefile = win32.samefile
40 40 setsignalhandler = win32.setsignalhandler
41 41 spawndetached = win32.spawndetached
42 42 split = os.path.split
43 43 testpid = win32.testpid
44 44 unlink = win32.unlink
45 45
46 46 umask = 0o022
47 47
48 48 class mixedfilemodewrapper(object):
49 49 """Wraps a file handle when it is opened in read/write mode.
50 50
51 51 fopen() and fdopen() on Windows have a specific-to-Windows requirement
52 52 that files opened with mode r+, w+, or a+ make a call to a file positioning
53 53 function when switching between reads and writes. Without this extra call,
54 54 Python will raise a not very intuitive "IOError: [Errno 0] Error."
55 55
56 56 This class wraps posixfile instances when the file is opened in read/write
57 57 mode and automatically adds checks or inserts appropriate file positioning
58 58 calls when necessary.
59 59 """
60 60 OPNONE = 0
61 61 OPREAD = 1
62 62 OPWRITE = 2
63 63
64 64 def __init__(self, fp):
65 65 object.__setattr__(self, r'_fp', fp)
66 66 object.__setattr__(self, r'_lastop', 0)
67 67
68 68 def __enter__(self):
69 69 return self._fp.__enter__()
70 70
71 71 def __exit__(self, exc_type, exc_val, exc_tb):
72 72 self._fp.__exit__(exc_type, exc_val, exc_tb)
73 73
74 74 def __getattr__(self, name):
75 75 return getattr(self._fp, name)
76 76
77 77 def __setattr__(self, name, value):
78 78 return self._fp.__setattr__(name, value)
79 79
80 80 def _noopseek(self):
81 81 self._fp.seek(0, os.SEEK_CUR)
82 82
83 83 def seek(self, *args, **kwargs):
84 84 object.__setattr__(self, r'_lastop', self.OPNONE)
85 85 return self._fp.seek(*args, **kwargs)
86 86
87 87 def write(self, d):
88 88 if self._lastop == self.OPREAD:
89 89 self._noopseek()
90 90
91 91 object.__setattr__(self, r'_lastop', self.OPWRITE)
92 92 return self._fp.write(d)
93 93
94 94 def writelines(self, *args, **kwargs):
95 95 if self._lastop == self.OPREAD:
96 96 self._noopeseek()
97 97
98 98 object.__setattr__(self, r'_lastop', self.OPWRITE)
99 99 return self._fp.writelines(*args, **kwargs)
100 100
101 101 def read(self, *args, **kwargs):
102 102 if self._lastop == self.OPWRITE:
103 103 self._noopseek()
104 104
105 105 object.__setattr__(self, r'_lastop', self.OPREAD)
106 106 return self._fp.read(*args, **kwargs)
107 107
108 108 def readline(self, *args, **kwargs):
109 109 if self._lastop == self.OPWRITE:
110 110 self._noopseek()
111 111
112 112 object.__setattr__(self, r'_lastop', self.OPREAD)
113 113 return self._fp.readline(*args, **kwargs)
114 114
115 115 def readlines(self, *args, **kwargs):
116 116 if self._lastop == self.OPWRITE:
117 117 self._noopseek()
118 118
119 119 object.__setattr__(self, r'_lastop', self.OPREAD)
120 120 return self._fp.readlines(*args, **kwargs)
121 121
122 122 def posixfile(name, mode='r', buffering=-1):
123 123 '''Open a file with even more POSIX-like semantics'''
124 124 try:
125 125 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
126 126
127 127 # The position when opening in append mode is implementation defined, so
128 128 # make it consistent with other platforms, which position at EOF.
129 129 if 'a' in mode:
130 130 fp.seek(0, os.SEEK_END)
131 131
132 132 if '+' in mode:
133 133 return mixedfilemodewrapper(fp)
134 134
135 135 return fp
136 136 except WindowsError as err:
137 137 # convert to a friendlier exception
138 138 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
139 139
140 140 class winstdout(object):
141 141 '''stdout on windows misbehaves if sent through a pipe'''
142 142
143 143 def __init__(self, fp):
144 144 self.fp = fp
145 145
146 146 def __getattr__(self, key):
147 147 return getattr(self.fp, key)
148 148
149 149 def close(self):
150 150 try:
151 151 self.fp.close()
152 152 except IOError:
153 153 pass
154 154
155 155 def write(self, s):
156 156 try:
157 157 # This is workaround for "Not enough space" error on
158 158 # writing large size of data to console.
159 159 limit = 16000
160 160 l = len(s)
161 161 start = 0
162 162 self.softspace = 0
163 163 while start < l:
164 164 end = start + limit
165 165 self.fp.write(s[start:end])
166 166 start = end
167 167 except IOError as inst:
168 168 if inst.errno != 0:
169 169 raise
170 170 self.close()
171 171 raise IOError(errno.EPIPE, 'Broken pipe')
172 172
173 173 def flush(self):
174 174 try:
175 175 return self.fp.flush()
176 176 except IOError as inst:
177 177 if inst.errno != errno.EINVAL:
178 178 raise
179 179 self.close()
180 180 raise IOError(errno.EPIPE, 'Broken pipe')
181 181
182 182 def _is_win_9x():
183 183 '''return true if run on windows 95, 98 or me.'''
184 184 try:
185 185 return sys.getwindowsversion()[3] == 1
186 186 except AttributeError:
187 187 return 'command' in encoding.environ.get('comspec', '')
188 188
189 189 def openhardlinks():
190 190 return not _is_win_9x()
191 191
192 192 def parsepatchoutput(output_line):
193 193 """parses the output produced by patch and returns the filename"""
194 194 pf = output_line[14:]
195 195 if pf[0] == '`':
196 196 pf = pf[1:-1] # Remove the quotes
197 197 return pf
198 198
199 199 def sshargs(sshcmd, host, user, port):
200 200 '''Build argument list for ssh or Plink'''
201 201 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
202 202 args = user and ("%s@%s" % (user, host)) or host
203 203 if args.startswith('-') or args.startswith('/'):
204 204 raise error.Abort(
205 205 _('illegal ssh hostname or username starting with - or /: %s') %
206 206 args)
207 return port and ("%s %s %s" % (args, pflag, port)) or args
207 args = shellquote(args)
208 if port:
209 args = '%s %s %s' % (pflag, shellquote(port), args)
210 return args
208 211
209 212 def setflags(f, l, x):
210 213 pass
211 214
212 215 def copymode(src, dst, mode=None):
213 216 pass
214 217
215 218 def checkexec(path):
216 219 return False
217 220
218 221 def checklink(path):
219 222 return False
220 223
221 224 def setbinary(fd):
222 225 # When run without console, pipes may expose invalid
223 226 # fileno(), usually set to -1.
224 227 fno = getattr(fd, 'fileno', None)
225 228 if fno is not None and fno() >= 0:
226 229 msvcrt.setmode(fno(), os.O_BINARY)
227 230
228 231 def pconvert(path):
229 232 return path.replace(pycompat.ossep, '/')
230 233
231 234 def localpath(path):
232 235 return path.replace('/', '\\')
233 236
234 237 def normpath(path):
235 238 return pconvert(os.path.normpath(path))
236 239
237 240 def normcase(path):
238 241 return encoding.upper(path) # NTFS compares via upper()
239 242
240 243 # see posix.py for definitions
241 244 normcasespec = encoding.normcasespecs.upper
242 245 normcasefallback = encoding.upperfallback
243 246
244 247 def samestat(s1, s2):
245 248 return False
246 249
247 250 # A sequence of backslashes is special iff it precedes a double quote:
248 251 # - if there's an even number of backslashes, the double quote is not
249 252 # quoted (i.e. it ends the quoted region)
250 253 # - if there's an odd number of backslashes, the double quote is quoted
251 254 # - in both cases, every pair of backslashes is unquoted into a single
252 255 # backslash
253 256 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
254 257 # So, to quote a string, we must surround it in double quotes, double
255 258 # the number of backslashes that precede double quotes and add another
256 259 # backslash before every double quote (being careful with the double
257 260 # quote we've appended to the end)
258 261 _quotere = None
259 262 _needsshellquote = None
260 263 def shellquote(s):
261 264 r"""
262 265 >>> shellquote(r'C:\Users\xyz')
263 266 '"C:\\Users\\xyz"'
264 267 >>> shellquote(r'C:\Users\xyz/mixed')
265 268 '"C:\\Users\\xyz/mixed"'
266 269 >>> # Would be safe not to quote too, since it is all double backslashes
267 270 >>> shellquote(r'C:\\Users\\xyz')
268 271 '"C:\\\\Users\\\\xyz"'
269 272 >>> # But this must be quoted
270 273 >>> shellquote(r'C:\\Users\\xyz/abc')
271 274 '"C:\\\\Users\\\\xyz/abc"'
272 275 """
273 276 global _quotere
274 277 if _quotere is None:
275 278 _quotere = re.compile(r'(\\*)("|\\$)')
276 279 global _needsshellquote
277 280 if _needsshellquote is None:
278 281 # ":" is also treated as "safe character", because it is used as a part
279 282 # of path name on Windows. "\" is also part of a path name, but isn't
280 283 # safe because shlex.split() (kind of) treats it as an escape char and
281 284 # drops it. It will leave the next character, even if it is another
282 285 # "\".
283 286 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
284 287 if s and not _needsshellquote(s) and not _quotere.search(s):
285 288 # "s" shouldn't have to be quoted
286 289 return s
287 290 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
288 291
289 292 def quotecommand(cmd):
290 293 """Build a command string suitable for os.popen* calls."""
291 294 if sys.version_info < (2, 7, 1):
292 295 # Python versions since 2.7.1 do this extra quoting themselves
293 296 return '"' + cmd + '"'
294 297 return cmd
295 298
296 299 def popen(command, mode='r'):
297 300 # Work around "popen spawned process may not write to stdout
298 301 # under windows"
299 302 # http://bugs.python.org/issue1366
300 303 command += " 2> %s" % os.devnull
301 304 return os.popen(quotecommand(command), mode)
302 305
303 306 def explainexit(code):
304 307 return _("exited with status %d") % code, code
305 308
306 309 # if you change this stub into a real check, please try to implement the
307 310 # username and groupname functions above, too.
308 311 def isowner(st):
309 312 return True
310 313
311 314 def findexe(command):
312 315 '''Find executable for command searching like cmd.exe does.
313 316 If command is a basename then PATH is searched for command.
314 317 PATH isn't searched if command is an absolute or relative path.
315 318 An extension from PATHEXT is found and added if not present.
316 319 If command isn't found None is returned.'''
317 320 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
318 321 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
319 322 if os.path.splitext(command)[1].lower() in pathexts:
320 323 pathexts = ['']
321 324
322 325 def findexisting(pathcommand):
323 326 'Will append extension (if needed) and return existing file'
324 327 for ext in pathexts:
325 328 executable = pathcommand + ext
326 329 if os.path.exists(executable):
327 330 return executable
328 331 return None
329 332
330 333 if pycompat.ossep in command:
331 334 return findexisting(command)
332 335
333 336 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
334 337 executable = findexisting(os.path.join(path, command))
335 338 if executable is not None:
336 339 return executable
337 340 return findexisting(os.path.expanduser(os.path.expandvars(command)))
338 341
339 342 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
340 343
341 344 def statfiles(files):
342 345 '''Stat each file in files. Yield each stat, or None if a file
343 346 does not exist or has a type we don't care about.
344 347
345 348 Cluster and cache stat per directory to minimize number of OS stat calls.'''
346 349 dircache = {} # dirname -> filename -> status | None if file does not exist
347 350 getkind = stat.S_IFMT
348 351 for nf in files:
349 352 nf = normcase(nf)
350 353 dir, base = os.path.split(nf)
351 354 if not dir:
352 355 dir = '.'
353 356 cache = dircache.get(dir, None)
354 357 if cache is None:
355 358 try:
356 359 dmap = dict([(normcase(n), s)
357 360 for n, k, s in osutil.listdir(dir, True)
358 361 if getkind(s.st_mode) in _wantedkinds])
359 362 except OSError as err:
360 363 # Python >= 2.5 returns ENOENT and adds winerror field
361 364 # EINVAL is raised if dir is not a directory.
362 365 if err.errno not in (errno.ENOENT, errno.EINVAL,
363 366 errno.ENOTDIR):
364 367 raise
365 368 dmap = {}
366 369 cache = dircache.setdefault(dir, dmap)
367 370 yield cache.get(base, None)
368 371
369 372 def username(uid=None):
370 373 """Return the name of the user with the given uid.
371 374
372 375 If uid is None, return the name of the current user."""
373 376 return None
374 377
375 378 def groupname(gid=None):
376 379 """Return the name of the group with the given gid.
377 380
378 381 If gid is None, return the name of the current group."""
379 382 return None
380 383
381 384 def removedirs(name):
382 385 """special version of os.removedirs that does not remove symlinked
383 386 directories or junction points if they actually contain files"""
384 387 if osutil.listdir(name):
385 388 return
386 389 os.rmdir(name)
387 390 head, tail = os.path.split(name)
388 391 if not tail:
389 392 head, tail = os.path.split(head)
390 393 while head and tail:
391 394 try:
392 395 if osutil.listdir(head):
393 396 return
394 397 os.rmdir(head)
395 398 except (ValueError, OSError):
396 399 break
397 400 head, tail = os.path.split(head)
398 401
399 402 def rename(src, dst):
400 403 '''atomically rename file src to dst, replacing dst if it exists'''
401 404 try:
402 405 os.rename(src, dst)
403 406 except OSError as e:
404 407 if e.errno != errno.EEXIST:
405 408 raise
406 409 unlink(dst)
407 410 os.rename(src, dst)
408 411
409 412 def gethgcmd():
410 413 return [sys.executable] + sys.argv[:1]
411 414
412 415 def groupmembers(name):
413 416 # Don't support groups on Windows for now
414 417 raise KeyError
415 418
416 419 def isexec(f):
417 420 return False
418 421
419 422 class cachestat(object):
420 423 def __init__(self, path):
421 424 pass
422 425
423 426 def cacheable(self):
424 427 return False
425 428
426 429 def lookupreg(key, valname=None, scope=None):
427 430 ''' Look up a key/value name in the Windows registry.
428 431
429 432 valname: value name. If unspecified, the default value for the key
430 433 is used.
431 434 scope: optionally specify scope for registry lookup, this can be
432 435 a sequence of scopes to look up in order. Default (CURRENT_USER,
433 436 LOCAL_MACHINE).
434 437 '''
435 438 if scope is None:
436 439 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
437 440 elif not isinstance(scope, (list, tuple)):
438 441 scope = (scope,)
439 442 for s in scope:
440 443 try:
441 444 val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
442 445 # never let a Unicode string escape into the wild
443 446 return encoding.unitolocal(val)
444 447 except EnvironmentError:
445 448 pass
446 449
447 450 expandglobs = True
448 451
449 452 def statislink(st):
450 453 '''check whether a stat result is a symlink'''
451 454 return False
452 455
453 456 def statisexec(st):
454 457 '''check whether a stat result is an executable file'''
455 458 return False
456 459
457 460 def poll(fds):
458 461 # see posix.py for description
459 462 raise NotImplementedError()
460 463
461 464 def readpipe(pipe):
462 465 """Read all available data from a pipe."""
463 466 chunks = []
464 467 while True:
465 468 size = win32.peekpipe(pipe)
466 469 if not size:
467 470 break
468 471
469 472 s = pipe.read(size)
470 473 if not s:
471 474 break
472 475 chunks.append(s)
473 476
474 477 return ''.join(chunks)
475 478
476 479 def bindunixsocket(sock, path):
477 480 raise NotImplementedError('unsupported platform')
@@ -1,1116 +1,1157
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
56 56 $ cat a
57 57 a
58 58 $ hg verify
59 59 checking changesets
60 60 checking manifests
61 61 crosschecking files in changesets and manifests
62 62 checking files
63 63 2 files, 11 changesets, 11 total revisions
64 64
65 65 Invalid dest '' must abort:
66 66
67 67 $ hg clone . ''
68 68 abort: empty destination path is not valid
69 69 [255]
70 70
71 71 No update, with debug option:
72 72
73 73 #if hardlink
74 74 $ hg --debug clone -U . ../c --config progress.debug=true
75 75 linking: 1
76 76 linking: 2
77 77 linking: 3
78 78 linking: 4
79 79 linking: 5
80 80 linking: 6
81 81 linking: 7
82 82 linking: 8
83 83 linked 8 files
84 84 #else
85 85 $ hg --debug clone -U . ../c --config progress.debug=true
86 86 linking: 1
87 87 copying: 2
88 88 copying: 3
89 89 copying: 4
90 90 copying: 5
91 91 copying: 6
92 92 copying: 7
93 93 copying: 8
94 94 copied 8 files
95 95 #endif
96 96 $ cd ../c
97 97
98 98 Ensure branchcache got copied over:
99 99
100 100 $ ls .hg/cache
101 101 branch2-served
102 102
103 103 $ cat a 2>/dev/null || echo "a not present"
104 104 a not present
105 105 $ hg verify
106 106 checking changesets
107 107 checking manifests
108 108 crosschecking files in changesets and manifests
109 109 checking files
110 110 2 files, 11 changesets, 11 total revisions
111 111
112 112 Default destination:
113 113
114 114 $ mkdir ../d
115 115 $ cd ../d
116 116 $ hg clone ../a
117 117 destination directory: a
118 118 updating to branch default
119 119 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 120 $ cd a
121 121 $ hg cat a
122 122 a
123 123 $ cd ../..
124 124
125 125 Check that we drop the 'file:' from the path before writing the .hgrc:
126 126
127 127 $ hg clone file:a e
128 128 updating to branch default
129 129 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
130 130 $ grep 'file:' e/.hg/hgrc
131 131 [1]
132 132
133 133 Check that path aliases are expanded:
134 134
135 135 $ hg clone -q -U --config 'paths.foobar=a#0' foobar f
136 136 $ hg -R f showconfig paths.default
137 137 $TESTTMP/a#0 (glob)
138 138
139 139 Use --pull:
140 140
141 141 $ hg clone --pull a g
142 142 requesting all changes
143 143 adding changesets
144 144 adding manifests
145 145 adding file changes
146 146 added 11 changesets with 11 changes to 2 files
147 147 updating to branch default
148 148 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 149 $ hg -R g verify
150 150 checking changesets
151 151 checking manifests
152 152 crosschecking files in changesets and manifests
153 153 checking files
154 154 2 files, 11 changesets, 11 total revisions
155 155
156 156 Invalid dest '' with --pull must abort (issue2528):
157 157
158 158 $ hg clone --pull a ''
159 159 abort: empty destination path is not valid
160 160 [255]
161 161
162 162 Clone to '.':
163 163
164 164 $ mkdir h
165 165 $ cd h
166 166 $ hg clone ../a .
167 167 updating to branch default
168 168 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
169 169 $ cd ..
170 170
171 171
172 172 *** Tests for option -u ***
173 173
174 174 Adding some more history to repo a:
175 175
176 176 $ cd a
177 177 $ hg tag ref1
178 178 $ echo the quick brown fox >a
179 179 $ hg ci -m "hacked default"
180 180 $ hg up ref1
181 181 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
182 182 $ hg branch stable
183 183 marked working directory as branch stable
184 184 (branches are permanent and global, did you want a bookmark?)
185 185 $ echo some text >a
186 186 $ hg ci -m "starting branch stable"
187 187 $ hg tag ref2
188 188 $ echo some more text >a
189 189 $ hg ci -m "another change for branch stable"
190 190 $ hg up ref2
191 191 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
192 192 $ hg parents
193 193 changeset: 13:e8ece76546a6
194 194 branch: stable
195 195 tag: ref2
196 196 parent: 10:a7949464abda
197 197 user: test
198 198 date: Thu Jan 01 00:00:00 1970 +0000
199 199 summary: starting branch stable
200 200
201 201
202 202 Repo a has two heads:
203 203
204 204 $ hg heads
205 205 changeset: 15:0aae7cf88f0d
206 206 branch: stable
207 207 tag: tip
208 208 user: test
209 209 date: Thu Jan 01 00:00:00 1970 +0000
210 210 summary: another change for branch stable
211 211
212 212 changeset: 12:f21241060d6a
213 213 user: test
214 214 date: Thu Jan 01 00:00:00 1970 +0000
215 215 summary: hacked default
216 216
217 217
218 218 $ cd ..
219 219
220 220
221 221 Testing --noupdate with --updaterev (must abort):
222 222
223 223 $ hg clone --noupdate --updaterev 1 a ua
224 224 abort: cannot specify both --noupdate and --updaterev
225 225 [255]
226 226
227 227
228 228 Testing clone -u:
229 229
230 230 $ hg clone -u . a ua
231 231 updating to branch stable
232 232 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
233 233
234 234 Repo ua has both heads:
235 235
236 236 $ hg -R ua heads
237 237 changeset: 15:0aae7cf88f0d
238 238 branch: stable
239 239 tag: tip
240 240 user: test
241 241 date: Thu Jan 01 00:00:00 1970 +0000
242 242 summary: another change for branch stable
243 243
244 244 changeset: 12:f21241060d6a
245 245 user: test
246 246 date: Thu Jan 01 00:00:00 1970 +0000
247 247 summary: hacked default
248 248
249 249
250 250 Same revision checked out in repo a and ua:
251 251
252 252 $ hg -R a parents --template "{node|short}\n"
253 253 e8ece76546a6
254 254 $ hg -R ua parents --template "{node|short}\n"
255 255 e8ece76546a6
256 256
257 257 $ rm -r ua
258 258
259 259
260 260 Testing clone --pull -u:
261 261
262 262 $ hg clone --pull -u . a ua
263 263 requesting all changes
264 264 adding changesets
265 265 adding manifests
266 266 adding file changes
267 267 added 16 changesets with 16 changes to 3 files (+1 heads)
268 268 updating to branch stable
269 269 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 270
271 271 Repo ua has both heads:
272 272
273 273 $ hg -R ua heads
274 274 changeset: 15:0aae7cf88f0d
275 275 branch: stable
276 276 tag: tip
277 277 user: test
278 278 date: Thu Jan 01 00:00:00 1970 +0000
279 279 summary: another change for branch stable
280 280
281 281 changeset: 12:f21241060d6a
282 282 user: test
283 283 date: Thu Jan 01 00:00:00 1970 +0000
284 284 summary: hacked default
285 285
286 286
287 287 Same revision checked out in repo a and ua:
288 288
289 289 $ hg -R a parents --template "{node|short}\n"
290 290 e8ece76546a6
291 291 $ hg -R ua parents --template "{node|short}\n"
292 292 e8ece76546a6
293 293
294 294 $ rm -r ua
295 295
296 296
297 297 Testing clone -u <branch>:
298 298
299 299 $ hg clone -u stable a ua
300 300 updating to branch stable
301 301 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
302 302
303 303 Repo ua has both heads:
304 304
305 305 $ hg -R ua heads
306 306 changeset: 15:0aae7cf88f0d
307 307 branch: stable
308 308 tag: tip
309 309 user: test
310 310 date: Thu Jan 01 00:00:00 1970 +0000
311 311 summary: another change for branch stable
312 312
313 313 changeset: 12:f21241060d6a
314 314 user: test
315 315 date: Thu Jan 01 00:00:00 1970 +0000
316 316 summary: hacked default
317 317
318 318
319 319 Branch 'stable' is checked out:
320 320
321 321 $ hg -R ua parents
322 322 changeset: 15:0aae7cf88f0d
323 323 branch: stable
324 324 tag: tip
325 325 user: test
326 326 date: Thu Jan 01 00:00:00 1970 +0000
327 327 summary: another change for branch stable
328 328
329 329
330 330 $ rm -r ua
331 331
332 332
333 333 Testing default checkout:
334 334
335 335 $ hg clone a ua
336 336 updating to branch default
337 337 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
338 338
339 339 Repo ua has both heads:
340 340
341 341 $ hg -R ua heads
342 342 changeset: 15:0aae7cf88f0d
343 343 branch: stable
344 344 tag: tip
345 345 user: test
346 346 date: Thu Jan 01 00:00:00 1970 +0000
347 347 summary: another change for branch stable
348 348
349 349 changeset: 12:f21241060d6a
350 350 user: test
351 351 date: Thu Jan 01 00:00:00 1970 +0000
352 352 summary: hacked default
353 353
354 354
355 355 Branch 'default' is checked out:
356 356
357 357 $ hg -R ua parents
358 358 changeset: 12:f21241060d6a
359 359 user: test
360 360 date: Thu Jan 01 00:00:00 1970 +0000
361 361 summary: hacked default
362 362
363 363 Test clone with a branch named "@" (issue3677)
364 364
365 365 $ hg -R ua branch @
366 366 marked working directory as branch @
367 367 $ hg -R ua commit -m 'created branch @'
368 368 $ hg clone ua atbranch
369 369 updating to branch default
370 370 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 371 $ hg -R atbranch heads
372 372 changeset: 16:798b6d97153e
373 373 branch: @
374 374 tag: tip
375 375 parent: 12:f21241060d6a
376 376 user: test
377 377 date: Thu Jan 01 00:00:00 1970 +0000
378 378 summary: created branch @
379 379
380 380 changeset: 15:0aae7cf88f0d
381 381 branch: stable
382 382 user: test
383 383 date: Thu Jan 01 00:00:00 1970 +0000
384 384 summary: another change for branch stable
385 385
386 386 changeset: 12:f21241060d6a
387 387 user: test
388 388 date: Thu Jan 01 00:00:00 1970 +0000
389 389 summary: hacked default
390 390
391 391 $ hg -R atbranch parents
392 392 changeset: 12:f21241060d6a
393 393 user: test
394 394 date: Thu Jan 01 00:00:00 1970 +0000
395 395 summary: hacked default
396 396
397 397
398 398 $ rm -r ua atbranch
399 399
400 400
401 401 Testing #<branch>:
402 402
403 403 $ hg clone -u . a#stable ua
404 404 adding changesets
405 405 adding manifests
406 406 adding file changes
407 407 added 14 changesets with 14 changes to 3 files
408 408 updating to branch stable
409 409 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
410 410
411 411 Repo ua has branch 'stable' and 'default' (was changed in fd511e9eeea6):
412 412
413 413 $ hg -R ua heads
414 414 changeset: 13:0aae7cf88f0d
415 415 branch: stable
416 416 tag: tip
417 417 user: test
418 418 date: Thu Jan 01 00:00:00 1970 +0000
419 419 summary: another change for branch stable
420 420
421 421 changeset: 10:a7949464abda
422 422 user: test
423 423 date: Thu Jan 01 00:00:00 1970 +0000
424 424 summary: test
425 425
426 426
427 427 Same revision checked out in repo a and ua:
428 428
429 429 $ hg -R a parents --template "{node|short}\n"
430 430 e8ece76546a6
431 431 $ hg -R ua parents --template "{node|short}\n"
432 432 e8ece76546a6
433 433
434 434 $ rm -r ua
435 435
436 436
437 437 Testing -u -r <branch>:
438 438
439 439 $ hg clone -u . -r stable a ua
440 440 adding changesets
441 441 adding manifests
442 442 adding file changes
443 443 added 14 changesets with 14 changes to 3 files
444 444 updating to branch stable
445 445 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
446 446
447 447 Repo ua has branch 'stable' and 'default' (was changed in fd511e9eeea6):
448 448
449 449 $ hg -R ua heads
450 450 changeset: 13:0aae7cf88f0d
451 451 branch: stable
452 452 tag: tip
453 453 user: test
454 454 date: Thu Jan 01 00:00:00 1970 +0000
455 455 summary: another change for branch stable
456 456
457 457 changeset: 10:a7949464abda
458 458 user: test
459 459 date: Thu Jan 01 00:00:00 1970 +0000
460 460 summary: test
461 461
462 462
463 463 Same revision checked out in repo a and ua:
464 464
465 465 $ hg -R a parents --template "{node|short}\n"
466 466 e8ece76546a6
467 467 $ hg -R ua parents --template "{node|short}\n"
468 468 e8ece76546a6
469 469
470 470 $ rm -r ua
471 471
472 472
473 473 Testing -r <branch>:
474 474
475 475 $ hg clone -r stable a ua
476 476 adding changesets
477 477 adding manifests
478 478 adding file changes
479 479 added 14 changesets with 14 changes to 3 files
480 480 updating to branch stable
481 481 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 482
483 483 Repo ua has branch 'stable' and 'default' (was changed in fd511e9eeea6):
484 484
485 485 $ hg -R ua heads
486 486 changeset: 13:0aae7cf88f0d
487 487 branch: stable
488 488 tag: tip
489 489 user: test
490 490 date: Thu Jan 01 00:00:00 1970 +0000
491 491 summary: another change for branch stable
492 492
493 493 changeset: 10:a7949464abda
494 494 user: test
495 495 date: Thu Jan 01 00:00:00 1970 +0000
496 496 summary: test
497 497
498 498
499 499 Branch 'stable' is checked out:
500 500
501 501 $ hg -R ua parents
502 502 changeset: 13:0aae7cf88f0d
503 503 branch: stable
504 504 tag: tip
505 505 user: test
506 506 date: Thu Jan 01 00:00:00 1970 +0000
507 507 summary: another change for branch stable
508 508
509 509
510 510 $ rm -r ua
511 511
512 512
513 513 Issue2267: Error in 1.6 hg.py: TypeError: 'NoneType' object is not
514 514 iterable in addbranchrevs()
515 515
516 516 $ cat <<EOF > simpleclone.py
517 517 > from mercurial import ui, hg
518 518 > myui = ui.ui.load()
519 519 > repo = hg.repository(myui, 'a')
520 520 > hg.clone(myui, {}, repo, dest="ua")
521 521 > EOF
522 522
523 523 $ python simpleclone.py
524 524 updating to branch default
525 525 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
526 526
527 527 $ rm -r ua
528 528
529 529 $ cat <<EOF > branchclone.py
530 530 > from mercurial import ui, hg, extensions
531 531 > myui = ui.ui.load()
532 532 > extensions.loadall(myui)
533 533 > repo = hg.repository(myui, 'a')
534 534 > hg.clone(myui, {}, repo, dest="ua", branch=["stable",])
535 535 > EOF
536 536
537 537 $ python branchclone.py
538 538 adding changesets
539 539 adding manifests
540 540 adding file changes
541 541 added 14 changesets with 14 changes to 3 files
542 542 updating to branch stable
543 543 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
544 544 $ rm -r ua
545 545
546 546
547 547 Test clone with special '@' bookmark:
548 548 $ cd a
549 549 $ hg bookmark -r a7949464abda @ # branch point of stable from default
550 550 $ hg clone . ../i
551 551 updating to bookmark @
552 552 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
553 553 $ hg id -i ../i
554 554 a7949464abda
555 555 $ rm -r ../i
556 556
557 557 $ hg bookmark -f -r stable @
558 558 $ hg bookmarks
559 559 @ 15:0aae7cf88f0d
560 560 $ hg clone . ../i
561 561 updating to bookmark @ on branch stable
562 562 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
563 563 $ hg id -i ../i
564 564 0aae7cf88f0d
565 565 $ cd "$TESTTMP"
566 566
567 567
568 568 Testing failures:
569 569
570 570 $ mkdir fail
571 571 $ cd fail
572 572
573 573 No local source
574 574
575 575 $ hg clone a b
576 576 abort: repository a not found!
577 577 [255]
578 578
579 579 No remote source
580 580
581 581 #if windows
582 582 $ hg clone http://$LOCALIP:3121/a b
583 583 abort: error: * (glob)
584 584 [255]
585 585 #else
586 586 $ hg clone http://$LOCALIP:3121/a b
587 587 abort: error: *refused* (glob)
588 588 [255]
589 589 #endif
590 590 $ rm -rf b # work around bug with http clone
591 591
592 592
593 593 #if unix-permissions no-root
594 594
595 595 Inaccessible source
596 596
597 597 $ mkdir a
598 598 $ chmod 000 a
599 599 $ hg clone a b
600 600 abort: repository a not found!
601 601 [255]
602 602
603 603 Inaccessible destination
604 604
605 605 $ hg init b
606 606 $ cd b
607 607 $ hg clone . ../a
608 608 abort: Permission denied: '../a'
609 609 [255]
610 610 $ cd ..
611 611 $ chmod 700 a
612 612 $ rm -r a b
613 613
614 614 #endif
615 615
616 616
617 617 #if fifo
618 618
619 619 Source of wrong type
620 620
621 621 $ mkfifo a
622 622 $ hg clone a b
623 623 abort: repository a not found!
624 624 [255]
625 625 $ rm a
626 626
627 627 #endif
628 628
629 629 Default destination, same directory
630 630
631 631 $ hg init q
632 632 $ hg clone q
633 633 destination directory: q
634 634 abort: destination 'q' is not empty
635 635 [255]
636 636
637 637 destination directory not empty
638 638
639 639 $ mkdir a
640 640 $ echo stuff > a/a
641 641 $ hg clone q a
642 642 abort: destination 'a' is not empty
643 643 [255]
644 644
645 645
646 646 #if unix-permissions no-root
647 647
648 648 leave existing directory in place after clone failure
649 649
650 650 $ hg init c
651 651 $ cd c
652 652 $ echo c > c
653 653 $ hg commit -A -m test
654 654 adding c
655 655 $ chmod -rx .hg/store/data
656 656 $ cd ..
657 657 $ mkdir d
658 658 $ hg clone c d 2> err
659 659 [255]
660 660 $ test -d d
661 661 $ test -d d/.hg
662 662 [1]
663 663
664 664 re-enable perm to allow deletion
665 665
666 666 $ chmod +rx c/.hg/store/data
667 667
668 668 #endif
669 669
670 670 $ cd ..
671 671
672 672 Test clone from the repository in (emulated) revlog format 0 (issue4203):
673 673
674 674 $ mkdir issue4203
675 675 $ mkdir -p src/.hg
676 676 $ echo foo > src/foo
677 677 $ hg -R src add src/foo
678 678 $ hg -R src commit -m '#0'
679 679 $ hg -R src log -q
680 680 0:e1bab28bca43
681 681 $ hg clone -U -q src dst
682 682 $ hg -R dst log -q
683 683 0:e1bab28bca43
684 684
685 685 Create repositories to test auto sharing functionality
686 686
687 687 $ cat >> $HGRCPATH << EOF
688 688 > [extensions]
689 689 > share=
690 690 > EOF
691 691
692 692 $ hg init empty
693 693 $ hg init source1a
694 694 $ cd source1a
695 695 $ echo initial1 > foo
696 696 $ hg -q commit -A -m initial
697 697 $ echo second > foo
698 698 $ hg commit -m second
699 699 $ cd ..
700 700
701 701 $ hg init filteredrev0
702 702 $ cd filteredrev0
703 703 $ cat >> .hg/hgrc << EOF
704 704 > [experimental]
705 705 > evolution=createmarkers
706 706 > EOF
707 707 $ echo initial1 > foo
708 708 $ hg -q commit -A -m initial0
709 709 $ hg -q up -r null
710 710 $ echo initial2 > foo
711 711 $ hg -q commit -A -m initial1
712 712 $ hg debugobsolete c05d5c47a5cf81401869999f3d05f7d699d2b29a e082c1832e09a7d1e78b7fd49a592d372de854c8
713 713 $ cd ..
714 714
715 715 $ hg -q clone --pull source1a source1b
716 716 $ cd source1a
717 717 $ hg bookmark bookA
718 718 $ echo 1a > foo
719 719 $ hg commit -m 1a
720 720 $ cd ../source1b
721 721 $ hg -q up -r 0
722 722 $ echo head1 > foo
723 723 $ hg commit -m head1
724 724 created new head
725 725 $ hg bookmark head1
726 726 $ hg -q up -r 0
727 727 $ echo head2 > foo
728 728 $ hg commit -m head2
729 729 created new head
730 730 $ hg bookmark head2
731 731 $ hg -q up -r 0
732 732 $ hg branch branch1
733 733 marked working directory as branch branch1
734 734 (branches are permanent and global, did you want a bookmark?)
735 735 $ echo branch1 > foo
736 736 $ hg commit -m branch1
737 737 $ hg -q up -r 0
738 738 $ hg branch branch2
739 739 marked working directory as branch branch2
740 740 $ echo branch2 > foo
741 741 $ hg commit -m branch2
742 742 $ cd ..
743 743 $ hg init source2
744 744 $ cd source2
745 745 $ echo initial2 > foo
746 746 $ hg -q commit -A -m initial2
747 747 $ echo second > foo
748 748 $ hg commit -m second
749 749 $ cd ..
750 750
751 751 Clone with auto share from an empty repo should not result in share
752 752
753 753 $ mkdir share
754 754 $ hg --config share.pool=share clone empty share-empty
755 755 (not using pooled storage: remote appears to be empty)
756 756 updating to branch default
757 757 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
758 758 $ ls share
759 759 $ test -d share-empty/.hg/store
760 760 $ test -f share-empty/.hg/sharedpath
761 761 [1]
762 762
763 763 Clone with auto share from a repo with filtered revision 0 should not result in share
764 764
765 765 $ hg --config share.pool=share clone filteredrev0 share-filtered
766 766 (not using pooled storage: unable to resolve identity of remote)
767 767 requesting all changes
768 768 adding changesets
769 769 adding manifests
770 770 adding file changes
771 771 added 1 changesets with 1 changes to 1 files
772 772 updating to branch default
773 773 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
774 774
775 775 Clone from repo with content should result in shared store being created
776 776
777 777 $ hg --config share.pool=share clone source1a share-dest1a
778 778 (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
779 779 requesting all changes
780 780 adding changesets
781 781 adding manifests
782 782 adding file changes
783 783 added 3 changesets with 3 changes to 1 files
784 784 searching for changes
785 785 no changes found
786 786 adding remote bookmark bookA
787 787 updating working directory
788 788 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
789 789
790 790 The shared repo should have been created
791 791
792 792 $ ls share
793 793 b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1
794 794
795 795 The destination should point to it
796 796
797 797 $ cat share-dest1a/.hg/sharedpath; echo
798 798 $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg (glob)
799 799
800 800 The destination should have bookmarks
801 801
802 802 $ hg -R share-dest1a bookmarks
803 803 bookA 2:e5bfe23c0b47
804 804
805 805 The default path should be the remote, not the share
806 806
807 807 $ hg -R share-dest1a config paths.default
808 808 $TESTTMP/source1a (glob)
809 809
810 810 Clone with existing share dir should result in pull + share
811 811
812 812 $ hg --config share.pool=share clone source1b share-dest1b
813 813 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
814 814 searching for changes
815 815 adding changesets
816 816 adding manifests
817 817 adding file changes
818 818 added 4 changesets with 4 changes to 1 files (+4 heads)
819 819 adding remote bookmark head1
820 820 adding remote bookmark head2
821 821 updating working directory
822 822 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
823 823
824 824 $ ls share
825 825 b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1
826 826
827 827 $ cat share-dest1b/.hg/sharedpath; echo
828 828 $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg (glob)
829 829
830 830 We only get bookmarks from the remote, not everything in the share
831 831
832 832 $ hg -R share-dest1b bookmarks
833 833 head1 3:4a8dc1ab4c13
834 834 head2 4:99f71071f117
835 835
836 836 Default path should be source, not share.
837 837
838 838 $ hg -R share-dest1b config paths.default
839 839 $TESTTMP/source1b (glob)
840 840
841 841 Checked out revision should be head of default branch
842 842
843 843 $ hg -R share-dest1b log -r .
844 844 changeset: 4:99f71071f117
845 845 bookmark: head2
846 846 parent: 0:b5f04eac9d8f
847 847 user: test
848 848 date: Thu Jan 01 00:00:00 1970 +0000
849 849 summary: head2
850 850
851 851
852 852 Clone from unrelated repo should result in new share
853 853
854 854 $ hg --config share.pool=share clone source2 share-dest2
855 855 (sharing from new pooled repository 22aeff664783fd44c6d9b435618173c118c3448e)
856 856 requesting all changes
857 857 adding changesets
858 858 adding manifests
859 859 adding file changes
860 860 added 2 changesets with 2 changes to 1 files
861 861 searching for changes
862 862 no changes found
863 863 updating working directory
864 864 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
865 865
866 866 $ ls share
867 867 22aeff664783fd44c6d9b435618173c118c3448e
868 868 b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1
869 869
870 870 remote naming mode works as advertised
871 871
872 872 $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1a share-remote1a
873 873 (sharing from new pooled repository 195bb1fcdb595c14a6c13e0269129ed78f6debde)
874 874 requesting all changes
875 875 adding changesets
876 876 adding manifests
877 877 adding file changes
878 878 added 3 changesets with 3 changes to 1 files
879 879 searching for changes
880 880 no changes found
881 881 adding remote bookmark bookA
882 882 updating working directory
883 883 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
884 884
885 885 $ ls shareremote
886 886 195bb1fcdb595c14a6c13e0269129ed78f6debde
887 887
888 888 $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1b share-remote1b
889 889 (sharing from new pooled repository c0d4f83847ca2a873741feb7048a45085fd47c46)
890 890 requesting all changes
891 891 adding changesets
892 892 adding manifests
893 893 adding file changes
894 894 added 6 changesets with 6 changes to 1 files (+4 heads)
895 895 searching for changes
896 896 no changes found
897 897 adding remote bookmark head1
898 898 adding remote bookmark head2
899 899 updating working directory
900 900 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
901 901
902 902 $ ls shareremote
903 903 195bb1fcdb595c14a6c13e0269129ed78f6debde
904 904 c0d4f83847ca2a873741feb7048a45085fd47c46
905 905
906 906 request to clone a single revision is respected in sharing mode
907 907
908 908 $ hg --config share.pool=sharerevs clone -r 4a8dc1ab4c13 source1b share-1arev
909 909 (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
910 910 adding changesets
911 911 adding manifests
912 912 adding file changes
913 913 added 2 changesets with 2 changes to 1 files
914 914 no changes found
915 915 adding remote bookmark head1
916 916 updating working directory
917 917 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
918 918
919 919 $ hg -R share-1arev log -G
920 920 @ changeset: 1:4a8dc1ab4c13
921 921 | bookmark: head1
922 922 | tag: tip
923 923 | user: test
924 924 | date: Thu Jan 01 00:00:00 1970 +0000
925 925 | summary: head1
926 926 |
927 927 o changeset: 0:b5f04eac9d8f
928 928 user: test
929 929 date: Thu Jan 01 00:00:00 1970 +0000
930 930 summary: initial
931 931
932 932
933 933 making another clone should only pull down requested rev
934 934
935 935 $ hg --config share.pool=sharerevs clone -r 99f71071f117 source1b share-1brev
936 936 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
937 937 searching for changes
938 938 adding changesets
939 939 adding manifests
940 940 adding file changes
941 941 added 1 changesets with 1 changes to 1 files (+1 heads)
942 942 adding remote bookmark head1
943 943 adding remote bookmark head2
944 944 updating working directory
945 945 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
946 946
947 947 $ hg -R share-1brev log -G
948 948 @ changeset: 2:99f71071f117
949 949 | bookmark: head2
950 950 | tag: tip
951 951 | parent: 0:b5f04eac9d8f
952 952 | user: test
953 953 | date: Thu Jan 01 00:00:00 1970 +0000
954 954 | summary: head2
955 955 |
956 956 | o changeset: 1:4a8dc1ab4c13
957 957 |/ bookmark: head1
958 958 | user: test
959 959 | date: Thu Jan 01 00:00:00 1970 +0000
960 960 | summary: head1
961 961 |
962 962 o changeset: 0:b5f04eac9d8f
963 963 user: test
964 964 date: Thu Jan 01 00:00:00 1970 +0000
965 965 summary: initial
966 966
967 967
968 968 Request to clone a single branch is respected in sharing mode
969 969
970 970 $ hg --config share.pool=sharebranch clone -b branch1 source1b share-1bbranch1
971 971 (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
972 972 adding changesets
973 973 adding manifests
974 974 adding file changes
975 975 added 2 changesets with 2 changes to 1 files
976 976 no changes found
977 977 updating working directory
978 978 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
979 979
980 980 $ hg -R share-1bbranch1 log -G
981 981 o changeset: 1:5f92a6c1a1b1
982 982 | branch: branch1
983 983 | tag: tip
984 984 | user: test
985 985 | date: Thu Jan 01 00:00:00 1970 +0000
986 986 | summary: branch1
987 987 |
988 988 @ changeset: 0:b5f04eac9d8f
989 989 user: test
990 990 date: Thu Jan 01 00:00:00 1970 +0000
991 991 summary: initial
992 992
993 993
994 994 $ hg --config share.pool=sharebranch clone -b branch2 source1b share-1bbranch2
995 995 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
996 996 searching for changes
997 997 adding changesets
998 998 adding manifests
999 999 adding file changes
1000 1000 added 1 changesets with 1 changes to 1 files (+1 heads)
1001 1001 updating working directory
1002 1002 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1003 1003
1004 1004 $ hg -R share-1bbranch2 log -G
1005 1005 o changeset: 2:6bacf4683960
1006 1006 | branch: branch2
1007 1007 | tag: tip
1008 1008 | parent: 0:b5f04eac9d8f
1009 1009 | user: test
1010 1010 | date: Thu Jan 01 00:00:00 1970 +0000
1011 1011 | summary: branch2
1012 1012 |
1013 1013 | o changeset: 1:5f92a6c1a1b1
1014 1014 |/ branch: branch1
1015 1015 | user: test
1016 1016 | date: Thu Jan 01 00:00:00 1970 +0000
1017 1017 | summary: branch1
1018 1018 |
1019 1019 @ changeset: 0:b5f04eac9d8f
1020 1020 user: test
1021 1021 date: Thu Jan 01 00:00:00 1970 +0000
1022 1022 summary: initial
1023 1023
1024 1024
1025 1025 -U is respected in share clone mode
1026 1026
1027 1027 $ hg --config share.pool=share clone -U source1a share-1anowc
1028 1028 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
1029 1029 searching for changes
1030 1030 no changes found
1031 1031 adding remote bookmark bookA
1032 1032
1033 1033 $ ls share-1anowc
1034 1034
1035 1035 Test that auto sharing doesn't cause failure of "hg clone local remote"
1036 1036
1037 1037 $ cd $TESTTMP
1038 1038 $ hg -R a id -r 0
1039 1039 acb14030fe0a
1040 1040 $ hg id -R remote -r 0
1041 1041 abort: repository remote not found!
1042 1042 [255]
1043 1043 $ hg --config share.pool=share -q clone -e "python \"$TESTDIR/dummyssh\"" a ssh://user@dummy/remote
1044 1044 $ hg -R remote id -r 0
1045 1045 acb14030fe0a
1046 1046
1047 1047 Cloning into pooled storage doesn't race (issue5104)
1048 1048
1049 1049 $ HGPOSTLOCKDELAY=2.0 hg --config share.pool=racepool --config extensions.lockdelay=$TESTDIR/lockdelay.py clone source1a share-destrace1 > race1.log 2>&1 &
1050 1050 $ HGPRELOCKDELAY=1.0 hg --config share.pool=racepool --config extensions.lockdelay=$TESTDIR/lockdelay.py clone source1a share-destrace2 > race2.log 2>&1
1051 1051 $ wait
1052 1052
1053 1053 $ hg -R share-destrace1 log -r tip
1054 1054 changeset: 2:e5bfe23c0b47
1055 1055 bookmark: bookA
1056 1056 tag: tip
1057 1057 user: test
1058 1058 date: Thu Jan 01 00:00:00 1970 +0000
1059 1059 summary: 1a
1060 1060
1061 1061
1062 1062 $ hg -R share-destrace2 log -r tip
1063 1063 changeset: 2:e5bfe23c0b47
1064 1064 bookmark: bookA
1065 1065 tag: tip
1066 1066 user: test
1067 1067 date: Thu Jan 01 00:00:00 1970 +0000
1068 1068 summary: 1a
1069 1069
1070 1070 One repo should be new, the other should be shared from the pool. We
1071 1071 don't care which is which, so we just make sure we always print the
1072 1072 one containing "new pooled" first, then one one containing "existing
1073 1073 pooled".
1074 1074
1075 1075 $ (grep 'new pooled' race1.log > /dev/null && cat race1.log || cat race2.log) | grep -v lock
1076 1076 (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
1077 1077 requesting all changes
1078 1078 adding changesets
1079 1079 adding manifests
1080 1080 adding file changes
1081 1081 added 3 changesets with 3 changes to 1 files
1082 1082 searching for changes
1083 1083 no changes found
1084 1084 adding remote bookmark bookA
1085 1085 updating working directory
1086 1086 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1087 1087
1088 1088 $ (grep 'existing pooled' race1.log > /dev/null && cat race1.log || cat race2.log) | grep -v lock
1089 1089 (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
1090 1090 searching for changes
1091 1091 no changes found
1092 1092 adding remote bookmark bookA
1093 1093 updating working directory
1094 1094 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1095 1095
1096 1096 SEC: check for unsafe ssh url
1097 1097
1098 $ cat >> $HGRCPATH << EOF
1099 > [ui]
1100 > ssh = sh -c "read l; read l; read l"
1101 > EOF
1102
1098 1103 $ hg clone 'ssh://-oProxyCommand=touch${IFS}owned/path'
1099 1104 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
1100 1105 [255]
1101 1106 $ hg clone 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
1102 1107 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
1103 1108 [255]
1104 1109 $ hg clone 'ssh://fakehost|shellcommand/path'
1105 1110 abort: potentially unsafe url: 'ssh://fakehost|shellcommand/path'
1106 1111 [255]
1107 1112 $ hg clone 'ssh://fakehost%7Cshellcommand/path'
1108 1113 abort: potentially unsafe url: 'ssh://fakehost|shellcommand/path'
1109 1114 [255]
1110 1115
1111 1116 $ hg clone 'ssh://-oProxyCommand=touch owned%20foo@example.com/nonexistent/path'
1112 1117 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned foo@example.com/nonexistent/path'
1113 1118 [255]
1119
1120 #if windows
1121 $ hg clone "ssh://%26touch%20owned%20/" --debug
1122 running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio"
1123 sending hello command
1124 sending between command
1125 abort: no suitable response from remote hg!
1126 [255]
1127 $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug
1128 running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio"
1129 sending hello command
1130 sending between command
1131 abort: no suitable response from remote hg!
1132 [255]
1133 #else
1134 $ hg clone "ssh://%3btouch%20owned%20/" --debug
1135 running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio'
1136 sending hello command
1137 sending between command
1138 abort: no suitable response from remote hg!
1139 [255]
1140 $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug
1141 running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio'
1142 sending hello command
1143 sending between command
1144 abort: no suitable response from remote hg!
1145 [255]
1146 #endif
1147
1148 $ hg clone "ssh://v-alid.example.com/" --debug
1149 running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re)
1150 sending hello command
1151 sending between command
1152 abort: no suitable response from remote hg!
1153 [255]
1154
1114 1155 We should not have created a file named owned - if it exists, the
1115 1156 attack succeeded.
1116 1157 $ if test -f owned; then echo 'you got owned'; fi
@@ -1,562 +1,562
1 1 This test is a duplicate of 'test-http.t' feel free to factor out
2 2 parts that are not bundle1/bundle2 specific.
3 3
4 4 $ cat << EOF >> $HGRCPATH
5 5 > [devel]
6 6 > # This test is dedicated to interaction through old bundle
7 7 > legacy.exchange = bundle1
8 8 > [format] # temporary settings
9 9 > usegeneraldelta=yes
10 10 > EOF
11 11
12 12
13 13 This test tries to exercise the ssh functionality with a dummy script
14 14
15 15 creating 'remote' repo
16 16
17 17 $ hg init remote
18 18 $ cd remote
19 19 $ echo this > foo
20 20 $ echo this > fooO
21 21 $ hg ci -A -m "init" foo fooO
22 22
23 23 insert a closed branch (issue4428)
24 24
25 25 $ hg up null
26 26 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
27 27 $ hg branch closed
28 28 marked working directory as branch closed
29 29 (branches are permanent and global, did you want a bookmark?)
30 30 $ hg ci -mc0
31 31 $ hg ci --close-branch -mc1
32 32 $ hg up -q default
33 33
34 34 configure for serving
35 35
36 36 $ cat <<EOF > .hg/hgrc
37 37 > [server]
38 38 > uncompressed = True
39 39 >
40 40 > [hooks]
41 41 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
42 42 > EOF
43 43 $ cd ..
44 44
45 45 repo not found error
46 46
47 47 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
48 48 remote: abort: repository nonexistent not found!
49 49 abort: no suitable response from remote hg!
50 50 [255]
51 51
52 52 non-existent absolute path
53 53
54 54 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local
55 55 remote: abort: repository /$TESTTMP/nonexistent not found!
56 56 abort: no suitable response from remote hg!
57 57 [255]
58 58
59 59 clone remote via stream
60 60
61 61 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
62 62 streaming all changes
63 63 4 files to transfer, 602 bytes of data
64 64 transferred 602 bytes in * seconds (*) (glob)
65 65 searching for changes
66 66 no changes found
67 67 updating to branch default
68 68 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 69 $ cd local-stream
70 70 $ hg verify
71 71 checking changesets
72 72 checking manifests
73 73 crosschecking files in changesets and manifests
74 74 checking files
75 75 2 files, 3 changesets, 2 total revisions
76 76 $ hg branches
77 77 default 0:1160648e36ce
78 78 $ cd ..
79 79
80 80 clone bookmarks via stream
81 81
82 82 $ hg -R local-stream book mybook
83 83 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
84 84 streaming all changes
85 85 4 files to transfer, 602 bytes of data
86 86 transferred 602 bytes in * seconds (*) (glob)
87 87 searching for changes
88 88 no changes found
89 89 updating to branch default
90 90 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 91 $ cd stream2
92 92 $ hg book
93 93 mybook 0:1160648e36ce
94 94 $ cd ..
95 95 $ rm -rf local-stream stream2
96 96
97 97 clone remote via pull
98 98
99 99 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
100 100 requesting all changes
101 101 adding changesets
102 102 adding manifests
103 103 adding file changes
104 104 added 3 changesets with 2 changes to 2 files
105 105 updating to branch default
106 106 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 107
108 108 verify
109 109
110 110 $ cd local
111 111 $ hg verify
112 112 checking changesets
113 113 checking manifests
114 114 crosschecking files in changesets and manifests
115 115 checking files
116 116 2 files, 3 changesets, 2 total revisions
117 117 $ cat >> .hg/hgrc <<EOF
118 118 > [hooks]
119 119 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
120 120 > EOF
121 121
122 122 empty default pull
123 123
124 124 $ hg paths
125 125 default = ssh://user@dummy/remote
126 126 $ hg pull -e "python \"$TESTDIR/dummyssh\""
127 127 pulling from ssh://user@dummy/remote
128 128 searching for changes
129 129 no changes found
130 130
131 131 pull from wrong ssh URL
132 132
133 133 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
134 134 pulling from ssh://user@dummy/doesnotexist
135 135 remote: abort: repository doesnotexist not found!
136 136 abort: no suitable response from remote hg!
137 137 [255]
138 138
139 139 local change
140 140
141 141 $ echo bleah > foo
142 142 $ hg ci -m "add"
143 143
144 144 updating rc
145 145
146 146 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
147 147 $ echo "[ui]" >> .hg/hgrc
148 148 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
149 149
150 150 find outgoing
151 151
152 152 $ hg out ssh://user@dummy/remote
153 153 comparing with ssh://user@dummy/remote
154 154 searching for changes
155 155 changeset: 3:a28a9d1a809c
156 156 tag: tip
157 157 parent: 0:1160648e36ce
158 158 user: test
159 159 date: Thu Jan 01 00:00:00 1970 +0000
160 160 summary: add
161 161
162 162
163 163 find incoming on the remote side
164 164
165 165 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
166 166 comparing with ssh://user@dummy/local
167 167 searching for changes
168 168 changeset: 3:a28a9d1a809c
169 169 tag: tip
170 170 parent: 0:1160648e36ce
171 171 user: test
172 172 date: Thu Jan 01 00:00:00 1970 +0000
173 173 summary: add
174 174
175 175
176 176 find incoming on the remote side (using absolute path)
177 177
178 178 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
179 179 comparing with ssh://user@dummy/$TESTTMP/local
180 180 searching for changes
181 181 changeset: 3:a28a9d1a809c
182 182 tag: tip
183 183 parent: 0:1160648e36ce
184 184 user: test
185 185 date: Thu Jan 01 00:00:00 1970 +0000
186 186 summary: add
187 187
188 188
189 189 push
190 190
191 191 $ hg push
192 192 pushing to ssh://user@dummy/remote
193 193 searching for changes
194 194 remote: adding changesets
195 195 remote: adding manifests
196 196 remote: adding file changes
197 197 remote: added 1 changesets with 1 changes to 1 files
198 198 $ cd ../remote
199 199
200 200 check remote tip
201 201
202 202 $ hg tip
203 203 changeset: 3:a28a9d1a809c
204 204 tag: tip
205 205 parent: 0:1160648e36ce
206 206 user: test
207 207 date: Thu Jan 01 00:00:00 1970 +0000
208 208 summary: add
209 209
210 210 $ hg verify
211 211 checking changesets
212 212 checking manifests
213 213 crosschecking files in changesets and manifests
214 214 checking files
215 215 2 files, 4 changesets, 3 total revisions
216 216 $ hg cat -r tip foo
217 217 bleah
218 218 $ echo z > z
219 219 $ hg ci -A -m z z
220 220 created new head
221 221
222 222 test pushkeys and bookmarks
223 223
224 224 $ cd ../local
225 225 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
226 226 bookmarks
227 227 namespaces
228 228 phases
229 229 $ hg book foo -r 0
230 230 $ hg out -B
231 231 comparing with ssh://user@dummy/remote
232 232 searching for changed bookmarks
233 233 foo 1160648e36ce
234 234 $ hg push -B foo
235 235 pushing to ssh://user@dummy/remote
236 236 searching for changes
237 237 no changes found
238 238 exporting bookmark foo
239 239 [1]
240 240 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
241 241 foo 1160648e36cec0054048a7edc4110c6f84fde594
242 242 $ hg book -f foo
243 243 $ hg push --traceback
244 244 pushing to ssh://user@dummy/remote
245 245 searching for changes
246 246 no changes found
247 247 updating bookmark foo
248 248 [1]
249 249 $ hg book -d foo
250 250 $ hg in -B
251 251 comparing with ssh://user@dummy/remote
252 252 searching for changed bookmarks
253 253 foo a28a9d1a809c
254 254 $ hg book -f -r 0 foo
255 255 $ hg pull -B foo
256 256 pulling from ssh://user@dummy/remote
257 257 no changes found
258 258 updating bookmark foo
259 259 $ hg book -d foo
260 260 $ hg push -B foo
261 261 pushing to ssh://user@dummy/remote
262 262 searching for changes
263 263 no changes found
264 264 deleting remote bookmark foo
265 265 [1]
266 266
267 267 a bad, evil hook that prints to stdout
268 268
269 269 $ cat <<EOF > $TESTTMP/badhook
270 270 > import sys
271 271 > sys.stdout.write("KABOOM\n")
272 272 > EOF
273 273
274 274 $ echo '[hooks]' >> ../remote/.hg/hgrc
275 275 $ echo "changegroup.stdout = python $TESTTMP/badhook" >> ../remote/.hg/hgrc
276 276 $ echo r > r
277 277 $ hg ci -A -m z r
278 278
279 279 push should succeed even though it has an unexpected response
280 280
281 281 $ hg push
282 282 pushing to ssh://user@dummy/remote
283 283 searching for changes
284 284 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
285 285 remote: adding changesets
286 286 remote: adding manifests
287 287 remote: adding file changes
288 288 remote: added 1 changesets with 1 changes to 1 files
289 289 remote: KABOOM
290 290 $ hg -R ../remote heads
291 291 changeset: 5:1383141674ec
292 292 tag: tip
293 293 parent: 3:a28a9d1a809c
294 294 user: test
295 295 date: Thu Jan 01 00:00:00 1970 +0000
296 296 summary: z
297 297
298 298 changeset: 4:6c0482d977a3
299 299 parent: 0:1160648e36ce
300 300 user: test
301 301 date: Thu Jan 01 00:00:00 1970 +0000
302 302 summary: z
303 303
304 304
305 305 clone bookmarks
306 306
307 307 $ hg -R ../remote bookmark test
308 308 $ hg -R ../remote bookmarks
309 309 * test 4:6c0482d977a3
310 310 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
311 311 requesting all changes
312 312 adding changesets
313 313 adding manifests
314 314 adding file changes
315 315 added 6 changesets with 5 changes to 4 files (+1 heads)
316 316 updating to branch default
317 317 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
318 318 $ hg -R local-bookmarks bookmarks
319 319 test 4:6c0482d977a3
320 320
321 321 passwords in ssh urls are not supported
322 322 (we use a glob here because different Python versions give different
323 323 results here)
324 324
325 325 $ hg push ssh://user:erroneouspwd@dummy/remote
326 326 pushing to ssh://user:*@dummy/remote (glob)
327 327 abort: password in URL not supported!
328 328 [255]
329 329
330 330 $ cd ..
331 331
332 332 hide outer repo
333 333 $ hg init
334 334
335 335 Test remote paths with spaces (issue2983):
336 336
337 337 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
338 338 $ touch "$TESTTMP/a repo/test"
339 339 $ hg -R 'a repo' commit -A -m "test"
340 340 adding test
341 341 $ hg -R 'a repo' tag tag
342 342 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
343 343 73649e48688a
344 344
345 345 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
346 346 abort: unknown revision 'noNoNO'!
347 347 [255]
348 348
349 349 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
350 350
351 351 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
352 352 destination directory: a repo
353 353 abort: destination 'a repo' is not empty
354 354 [255]
355 355
356 356 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
357 357 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
358 358 parameters:
359 359
360 360 $ cat > ssh.sh << EOF
361 361 > userhost="\$1"
362 362 > SSH_ORIGINAL_COMMAND="\$2"
363 363 > export SSH_ORIGINAL_COMMAND
364 364 > PYTHONPATH="$PYTHONPATH"
365 365 > export PYTHONPATH
366 366 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
367 367 > EOF
368 368
369 369 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
370 370 73649e48688a
371 371
372 372 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
373 373 remote: Illegal repository "$TESTTMP/a'repo" (glob)
374 374 abort: no suitable response from remote hg!
375 375 [255]
376 376
377 377 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
378 378 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
379 379 abort: no suitable response from remote hg!
380 380 [255]
381 381
382 382 $ SSH_ORIGINAL_COMMAND="'hg' serve -R 'a'repo' --stdio" python "$TESTDIR/../contrib/hg-ssh"
383 383 Illegal command "'hg' serve -R 'a'repo' --stdio": No closing quotation
384 384 [255]
385 385
386 386 Test hg-ssh in read-only mode:
387 387
388 388 $ cat > ssh.sh << EOF
389 389 > userhost="\$1"
390 390 > SSH_ORIGINAL_COMMAND="\$2"
391 391 > export SSH_ORIGINAL_COMMAND
392 392 > PYTHONPATH="$PYTHONPATH"
393 393 > export PYTHONPATH
394 394 > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
395 395 > EOF
396 396
397 397 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
398 398 requesting all changes
399 399 adding changesets
400 400 adding manifests
401 401 adding file changes
402 402 added 6 changesets with 5 changes to 4 files (+1 heads)
403 403 updating to branch default
404 404 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
405 405
406 406 $ cd read-only-local
407 407 $ echo "baz" > bar
408 408 $ hg ci -A -m "unpushable commit" bar
409 409 $ hg push --ssh "sh ../ssh.sh"
410 410 pushing to ssh://user@dummy/*/remote (glob)
411 411 searching for changes
412 412 remote: Permission denied
413 413 remote: abort: pretxnopen.hg-ssh hook failed
414 414 remote: Permission denied
415 415 remote: pushkey-abort: prepushkey.hg-ssh hook failed
416 416 updating 6c0482d977a3 to public failed!
417 417 [1]
418 418
419 419 $ cd ..
420 420
421 421 stderr from remote commands should be printed before stdout from local code (issue4336)
422 422
423 423 $ hg clone remote stderr-ordering
424 424 updating to branch default
425 425 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 426 $ cd stderr-ordering
427 427 $ cat >> localwrite.py << EOF
428 428 > from mercurial import exchange, extensions
429 429 >
430 430 > def wrappedpush(orig, repo, *args, **kwargs):
431 431 > res = orig(repo, *args, **kwargs)
432 432 > repo.ui.write('local stdout\n')
433 433 > return res
434 434 >
435 435 > def extsetup(ui):
436 436 > extensions.wrapfunction(exchange, 'push', wrappedpush)
437 437 > EOF
438 438
439 439 $ cat >> .hg/hgrc << EOF
440 440 > [paths]
441 441 > default-push = ssh://user@dummy/remote
442 442 > [ui]
443 443 > ssh = python "$TESTDIR/dummyssh"
444 444 > [extensions]
445 445 > localwrite = localwrite.py
446 446 > EOF
447 447
448 448 $ echo localwrite > foo
449 449 $ hg commit -m 'testing localwrite'
450 450 $ hg push
451 451 pushing to ssh://user@dummy/remote
452 452 searching for changes
453 453 remote: adding changesets
454 454 remote: adding manifests
455 455 remote: adding file changes
456 456 remote: added 1 changesets with 1 changes to 1 files
457 457 remote: KABOOM
458 458 local stdout
459 459
460 460 debug output
461 461
462 462 $ hg pull --debug ssh://user@dummy/remote
463 463 pulling from ssh://user@dummy/remote
464 running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
464 running python ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
465 465 sending hello command
466 466 sending between command
467 467 remote: 355
468 468 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
469 469 remote: 1
470 470 preparing listkeys for "bookmarks"
471 471 sending listkeys command
472 472 received listkey for "bookmarks": 45 bytes
473 473 query 1; heads
474 474 sending batch command
475 475 searching for changes
476 476 all remote heads known locally
477 477 no changes found
478 478 preparing listkeys for "phases"
479 479 sending listkeys command
480 480 received listkey for "phases": 15 bytes
481 481 checking for updated bookmarks
482 482
483 483 $ cd ..
484 484
485 485 $ cat dummylog
486 486 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
487 487 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
488 488 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
489 489 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
490 490 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
491 491 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
492 492 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
493 493 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
494 494 Got arguments 1:user@dummy 2:hg -R local serve --stdio
495 495 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
496 496 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
497 497 changegroup-in-remote hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
498 498 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
499 499 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
500 500 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
501 501 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
502 502 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
503 503 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
504 504 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
505 505 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
506 506 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
507 507 changegroup-in-remote hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
508 508 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
509 509 Got arguments 1:user@dummy 2:hg init 'a repo'
510 510 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
511 511 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
512 512 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
513 513 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
514 514 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
515 515 changegroup-in-remote hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
516 516 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
517 517
518 518 remote hook failure is attributed to remote
519 519
520 520 $ cat > $TESTTMP/failhook << EOF
521 521 > def hook(ui, repo, **kwargs):
522 522 > ui.write('hook failure!\n')
523 523 > ui.flush()
524 524 > return 1
525 525 > EOF
526 526
527 527 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
528 528
529 529 $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
530 530 $ cd hookout
531 531 $ touch hookfailure
532 532 $ hg -q commit -A -m 'remote hook failure'
533 533 $ hg --config ui.ssh="python $TESTDIR/dummyssh" push
534 534 pushing to ssh://user@dummy/remote
535 535 searching for changes
536 536 remote: adding changesets
537 537 remote: adding manifests
538 538 remote: adding file changes
539 539 remote: added 1 changesets with 1 changes to 1 files
540 540 remote: hook failure!
541 541 remote: transaction abort!
542 542 remote: rollback completed
543 543 remote: abort: pretxnchangegroup.fail hook failed
544 544 [1]
545 545
546 546 abort during pull is properly reported as such
547 547
548 548 $ echo morefoo >> ../remote/foo
549 549 $ hg -R ../remote commit --message "more foo to be pulled"
550 550 $ cat >> ../remote/.hg/hgrc << EOF
551 551 > [extensions]
552 552 > crash = ${TESTDIR}/crashgetbundler.py
553 553 > EOF
554 554 $ hg --config ui.ssh="python $TESTDIR/dummyssh" pull
555 555 pulling from ssh://user@dummy/remote
556 556 searching for changes
557 557 adding changesets
558 558 remote: abort: this is an exercise
559 559 transaction abort!
560 560 rollback completed
561 561 abort: stream ended unexpectedly (got 0 bytes, expected 4)
562 562 [255]
@@ -1,577 +1,577
1 1
2 2 This test tries to exercise the ssh functionality with a dummy script
3 3
4 4 $ cat <<EOF >> $HGRCPATH
5 5 > [format]
6 6 > usegeneraldelta=yes
7 7 > EOF
8 8
9 9 creating 'remote' repo
10 10
11 11 $ hg init remote
12 12 $ cd remote
13 13 $ echo this > foo
14 14 $ echo this > fooO
15 15 $ hg ci -A -m "init" foo fooO
16 16
17 17 insert a closed branch (issue4428)
18 18
19 19 $ hg up null
20 20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
21 21 $ hg branch closed
22 22 marked working directory as branch closed
23 23 (branches are permanent and global, did you want a bookmark?)
24 24 $ hg ci -mc0
25 25 $ hg ci --close-branch -mc1
26 26 $ hg up -q default
27 27
28 28 configure for serving
29 29
30 30 $ cat <<EOF > .hg/hgrc
31 31 > [server]
32 32 > uncompressed = True
33 33 >
34 34 > [hooks]
35 35 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
36 36 > EOF
37 37 $ cd ..
38 38
39 39 repo not found error
40 40
41 41 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
42 42 remote: abort: repository nonexistent not found!
43 43 abort: no suitable response from remote hg!
44 44 [255]
45 45
46 46 non-existent absolute path
47 47
48 48 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
49 49 remote: abort: repository $TESTTMP/nonexistent not found!
50 50 abort: no suitable response from remote hg!
51 51 [255]
52 52
53 53 clone remote via stream
54 54
55 55 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
56 56 streaming all changes
57 57 4 files to transfer, 602 bytes of data
58 58 transferred 602 bytes in * seconds (*) (glob)
59 59 searching for changes
60 60 no changes found
61 61 updating to branch default
62 62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 63 $ cd local-stream
64 64 $ hg verify
65 65 checking changesets
66 66 checking manifests
67 67 crosschecking files in changesets and manifests
68 68 checking files
69 69 2 files, 3 changesets, 2 total revisions
70 70 $ hg branches
71 71 default 0:1160648e36ce
72 72 $ cd ..
73 73
74 74 clone bookmarks via stream
75 75
76 76 $ hg -R local-stream book mybook
77 77 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
78 78 streaming all changes
79 79 4 files to transfer, 602 bytes of data
80 80 transferred 602 bytes in * seconds (*) (glob)
81 81 searching for changes
82 82 no changes found
83 83 updating to branch default
84 84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 85 $ cd stream2
86 86 $ hg book
87 87 mybook 0:1160648e36ce
88 88 $ cd ..
89 89 $ rm -rf local-stream stream2
90 90
91 91 clone remote via pull
92 92
93 93 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
94 94 requesting all changes
95 95 adding changesets
96 96 adding manifests
97 97 adding file changes
98 98 added 3 changesets with 2 changes to 2 files
99 99 updating to branch default
100 100 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 101
102 102 verify
103 103
104 104 $ cd local
105 105 $ hg verify
106 106 checking changesets
107 107 checking manifests
108 108 crosschecking files in changesets and manifests
109 109 checking files
110 110 2 files, 3 changesets, 2 total revisions
111 111 $ cat >> .hg/hgrc <<EOF
112 112 > [hooks]
113 113 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
114 114 > EOF
115 115
116 116 empty default pull
117 117
118 118 $ hg paths
119 119 default = ssh://user@dummy/remote
120 120 $ hg pull -e "python \"$TESTDIR/dummyssh\""
121 121 pulling from ssh://user@dummy/remote
122 122 searching for changes
123 123 no changes found
124 124
125 125 pull from wrong ssh URL
126 126
127 127 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
128 128 pulling from ssh://user@dummy/doesnotexist
129 129 remote: abort: repository doesnotexist not found!
130 130 abort: no suitable response from remote hg!
131 131 [255]
132 132
133 133 local change
134 134
135 135 $ echo bleah > foo
136 136 $ hg ci -m "add"
137 137
138 138 updating rc
139 139
140 140 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
141 141 $ echo "[ui]" >> .hg/hgrc
142 142 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
143 143
144 144 find outgoing
145 145
146 146 $ hg out ssh://user@dummy/remote
147 147 comparing with ssh://user@dummy/remote
148 148 searching for changes
149 149 changeset: 3:a28a9d1a809c
150 150 tag: tip
151 151 parent: 0:1160648e36ce
152 152 user: test
153 153 date: Thu Jan 01 00:00:00 1970 +0000
154 154 summary: add
155 155
156 156
157 157 find incoming on the remote side
158 158
159 159 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
160 160 comparing with ssh://user@dummy/local
161 161 searching for changes
162 162 changeset: 3:a28a9d1a809c
163 163 tag: tip
164 164 parent: 0:1160648e36ce
165 165 user: test
166 166 date: Thu Jan 01 00:00:00 1970 +0000
167 167 summary: add
168 168
169 169
170 170 find incoming on the remote side (using absolute path)
171 171
172 172 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
173 173 comparing with ssh://user@dummy/$TESTTMP/local
174 174 searching for changes
175 175 changeset: 3:a28a9d1a809c
176 176 tag: tip
177 177 parent: 0:1160648e36ce
178 178 user: test
179 179 date: Thu Jan 01 00:00:00 1970 +0000
180 180 summary: add
181 181
182 182
183 183 push
184 184
185 185 $ hg push
186 186 pushing to ssh://user@dummy/remote
187 187 searching for changes
188 188 remote: adding changesets
189 189 remote: adding manifests
190 190 remote: adding file changes
191 191 remote: added 1 changesets with 1 changes to 1 files
192 192 $ cd ../remote
193 193
194 194 check remote tip
195 195
196 196 $ hg tip
197 197 changeset: 3:a28a9d1a809c
198 198 tag: tip
199 199 parent: 0:1160648e36ce
200 200 user: test
201 201 date: Thu Jan 01 00:00:00 1970 +0000
202 202 summary: add
203 203
204 204 $ hg verify
205 205 checking changesets
206 206 checking manifests
207 207 crosschecking files in changesets and manifests
208 208 checking files
209 209 2 files, 4 changesets, 3 total revisions
210 210 $ hg cat -r tip foo
211 211 bleah
212 212 $ echo z > z
213 213 $ hg ci -A -m z z
214 214 created new head
215 215
216 216 test pushkeys and bookmarks
217 217
218 218 $ cd ../local
219 219 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
220 220 bookmarks
221 221 namespaces
222 222 phases
223 223 $ hg book foo -r 0
224 224 $ hg out -B
225 225 comparing with ssh://user@dummy/remote
226 226 searching for changed bookmarks
227 227 foo 1160648e36ce
228 228 $ hg push -B foo
229 229 pushing to ssh://user@dummy/remote
230 230 searching for changes
231 231 no changes found
232 232 exporting bookmark foo
233 233 [1]
234 234 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
235 235 foo 1160648e36cec0054048a7edc4110c6f84fde594
236 236 $ hg book -f foo
237 237 $ hg push --traceback
238 238 pushing to ssh://user@dummy/remote
239 239 searching for changes
240 240 no changes found
241 241 updating bookmark foo
242 242 [1]
243 243 $ hg book -d foo
244 244 $ hg in -B
245 245 comparing with ssh://user@dummy/remote
246 246 searching for changed bookmarks
247 247 foo a28a9d1a809c
248 248 $ hg book -f -r 0 foo
249 249 $ hg pull -B foo
250 250 pulling from ssh://user@dummy/remote
251 251 no changes found
252 252 updating bookmark foo
253 253 $ hg book -d foo
254 254 $ hg push -B foo
255 255 pushing to ssh://user@dummy/remote
256 256 searching for changes
257 257 no changes found
258 258 deleting remote bookmark foo
259 259 [1]
260 260
261 261 a bad, evil hook that prints to stdout
262 262
263 263 $ cat <<EOF > $TESTTMP/badhook
264 264 > import sys
265 265 > sys.stdout.write("KABOOM\n")
266 266 > EOF
267 267
268 268 $ cat <<EOF > $TESTTMP/badpyhook.py
269 269 > import sys
270 270 > def hook(ui, repo, hooktype, **kwargs):
271 271 > sys.stdout.write("KABOOM IN PROCESS\n")
272 272 > EOF
273 273
274 274 $ cat <<EOF >> ../remote/.hg/hgrc
275 275 > [hooks]
276 276 > changegroup.stdout = python $TESTTMP/badhook
277 277 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
278 278 > EOF
279 279 $ echo r > r
280 280 $ hg ci -A -m z r
281 281
282 282 push should succeed even though it has an unexpected response
283 283
284 284 $ hg push
285 285 pushing to ssh://user@dummy/remote
286 286 searching for changes
287 287 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
288 288 remote: adding changesets
289 289 remote: adding manifests
290 290 remote: adding file changes
291 291 remote: added 1 changesets with 1 changes to 1 files
292 292 remote: KABOOM
293 293 remote: KABOOM IN PROCESS
294 294 $ hg -R ../remote heads
295 295 changeset: 5:1383141674ec
296 296 tag: tip
297 297 parent: 3:a28a9d1a809c
298 298 user: test
299 299 date: Thu Jan 01 00:00:00 1970 +0000
300 300 summary: z
301 301
302 302 changeset: 4:6c0482d977a3
303 303 parent: 0:1160648e36ce
304 304 user: test
305 305 date: Thu Jan 01 00:00:00 1970 +0000
306 306 summary: z
307 307
308 308
309 309 clone bookmarks
310 310
311 311 $ hg -R ../remote bookmark test
312 312 $ hg -R ../remote bookmarks
313 313 * test 4:6c0482d977a3
314 314 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
315 315 requesting all changes
316 316 adding changesets
317 317 adding manifests
318 318 adding file changes
319 319 added 6 changesets with 5 changes to 4 files (+1 heads)
320 320 updating to branch default
321 321 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
322 322 $ hg -R local-bookmarks bookmarks
323 323 test 4:6c0482d977a3
324 324
325 325 passwords in ssh urls are not supported
326 326 (we use a glob here because different Python versions give different
327 327 results here)
328 328
329 329 $ hg push ssh://user:erroneouspwd@dummy/remote
330 330 pushing to ssh://user:*@dummy/remote (glob)
331 331 abort: password in URL not supported!
332 332 [255]
333 333
334 334 $ cd ..
335 335
336 336 hide outer repo
337 337 $ hg init
338 338
339 339 Test remote paths with spaces (issue2983):
340 340
341 341 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
342 342 $ touch "$TESTTMP/a repo/test"
343 343 $ hg -R 'a repo' commit -A -m "test"
344 344 adding test
345 345 $ hg -R 'a repo' tag tag
346 346 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
347 347 73649e48688a
348 348
349 349 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
350 350 abort: unknown revision 'noNoNO'!
351 351 [255]
352 352
353 353 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
354 354
355 355 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
356 356 destination directory: a repo
357 357 abort: destination 'a repo' is not empty
358 358 [255]
359 359
360 360 Make sure hg is really paranoid in serve --stdio mode. It used to be
361 361 possible to get a debugger REPL by specifying a repo named --debugger.
362 362 $ hg -R --debugger serve --stdio
363 363 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
364 364 [255]
365 365 $ hg -R --config=ui.debugger=yes serve --stdio
366 366 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
367 367 [255]
368 368 Abbreviations of 'serve' also don't work, to avoid shenanigans.
369 369 $ hg -R narf serv --stdio
370 370 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
371 371 [255]
372 372
373 373 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
374 374 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
375 375 parameters:
376 376
377 377 $ cat > ssh.sh << EOF
378 378 > userhost="\$1"
379 379 > SSH_ORIGINAL_COMMAND="\$2"
380 380 > export SSH_ORIGINAL_COMMAND
381 381 > PYTHONPATH="$PYTHONPATH"
382 382 > export PYTHONPATH
383 383 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
384 384 > EOF
385 385
386 386 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
387 387 73649e48688a
388 388
389 389 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
390 390 remote: Illegal repository "$TESTTMP/a'repo" (glob)
391 391 abort: no suitable response from remote hg!
392 392 [255]
393 393
394 394 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
395 395 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
396 396 abort: no suitable response from remote hg!
397 397 [255]
398 398
399 399 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" python "$TESTDIR/../contrib/hg-ssh"
400 400 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
401 401 [255]
402 402
403 403 Test hg-ssh in read-only mode:
404 404
405 405 $ cat > ssh.sh << EOF
406 406 > userhost="\$1"
407 407 > SSH_ORIGINAL_COMMAND="\$2"
408 408 > export SSH_ORIGINAL_COMMAND
409 409 > PYTHONPATH="$PYTHONPATH"
410 410 > export PYTHONPATH
411 411 > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
412 412 > EOF
413 413
414 414 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
415 415 requesting all changes
416 416 adding changesets
417 417 adding manifests
418 418 adding file changes
419 419 added 6 changesets with 5 changes to 4 files (+1 heads)
420 420 updating to branch default
421 421 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
422 422
423 423 $ cd read-only-local
424 424 $ echo "baz" > bar
425 425 $ hg ci -A -m "unpushable commit" bar
426 426 $ hg push --ssh "sh ../ssh.sh"
427 427 pushing to ssh://user@dummy/*/remote (glob)
428 428 searching for changes
429 429 remote: Permission denied
430 430 remote: pretxnopen.hg-ssh hook failed
431 431 abort: push failed on remote
432 432 [255]
433 433
434 434 $ cd ..
435 435
436 436 stderr from remote commands should be printed before stdout from local code (issue4336)
437 437
438 438 $ hg clone remote stderr-ordering
439 439 updating to branch default
440 440 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
441 441 $ cd stderr-ordering
442 442 $ cat >> localwrite.py << EOF
443 443 > from mercurial import exchange, extensions
444 444 >
445 445 > def wrappedpush(orig, repo, *args, **kwargs):
446 446 > res = orig(repo, *args, **kwargs)
447 447 > repo.ui.write('local stdout\n')
448 448 > return res
449 449 >
450 450 > def extsetup(ui):
451 451 > extensions.wrapfunction(exchange, 'push', wrappedpush)
452 452 > EOF
453 453
454 454 $ cat >> .hg/hgrc << EOF
455 455 > [paths]
456 456 > default-push = ssh://user@dummy/remote
457 457 > [ui]
458 458 > ssh = python "$TESTDIR/dummyssh"
459 459 > [extensions]
460 460 > localwrite = localwrite.py
461 461 > EOF
462 462
463 463 $ echo localwrite > foo
464 464 $ hg commit -m 'testing localwrite'
465 465 $ hg push
466 466 pushing to ssh://user@dummy/remote
467 467 searching for changes
468 468 remote: adding changesets
469 469 remote: adding manifests
470 470 remote: adding file changes
471 471 remote: added 1 changesets with 1 changes to 1 files
472 472 remote: KABOOM
473 473 remote: KABOOM IN PROCESS
474 474 local stdout
475 475
476 476 debug output
477 477
478 478 $ hg pull --debug ssh://user@dummy/remote
479 479 pulling from ssh://user@dummy/remote
480 running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
480 running python ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
481 481 sending hello command
482 482 sending between command
483 483 remote: 355
484 484 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
485 485 remote: 1
486 486 query 1; heads
487 487 sending batch command
488 488 searching for changes
489 489 all remote heads known locally
490 490 no changes found
491 491 sending getbundle command
492 492 bundle2-input-bundle: with-transaction
493 493 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
494 494 bundle2-input-part: total payload size 15
495 495 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
496 496 bundle2-input-part: total payload size 45
497 497 bundle2-input-bundle: 1 parts total
498 498 checking for updated bookmarks
499 499
500 500 $ cd ..
501 501
502 502 $ cat dummylog
503 503 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
504 504 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
505 505 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
506 506 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
507 507 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
508 508 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
509 509 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
510 510 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
511 511 Got arguments 1:user@dummy 2:hg -R local serve --stdio
512 512 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
513 513 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
514 514 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
515 515 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
516 516 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
517 517 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
518 518 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
519 519 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
520 520 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
521 521 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
522 522 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
523 523 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
524 524 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
525 525 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
526 526 Got arguments 1:user@dummy 2:hg init 'a repo'
527 527 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
528 528 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
529 529 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
530 530 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
531 531 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
532 532 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
533 533 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
534 534
535 535 remote hook failure is attributed to remote
536 536
537 537 $ cat > $TESTTMP/failhook << EOF
538 538 > def hook(ui, repo, **kwargs):
539 539 > ui.write('hook failure!\n')
540 540 > ui.flush()
541 541 > return 1
542 542 > EOF
543 543
544 544 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
545 545
546 546 $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
547 547 $ cd hookout
548 548 $ touch hookfailure
549 549 $ hg -q commit -A -m 'remote hook failure'
550 550 $ hg --config ui.ssh="python $TESTDIR/dummyssh" push
551 551 pushing to ssh://user@dummy/remote
552 552 searching for changes
553 553 remote: adding changesets
554 554 remote: adding manifests
555 555 remote: adding file changes
556 556 remote: added 1 changesets with 1 changes to 1 files
557 557 remote: hook failure!
558 558 remote: transaction abort!
559 559 remote: rollback completed
560 560 remote: pretxnchangegroup.fail hook failed
561 561 abort: push failed on remote
562 562 [255]
563 563
564 564 abort during pull is properly reported as such
565 565
566 566 $ echo morefoo >> ../remote/foo
567 567 $ hg -R ../remote commit --message "more foo to be pulled"
568 568 $ cat >> ../remote/.hg/hgrc << EOF
569 569 > [extensions]
570 570 > crash = ${TESTDIR}/crashgetbundler.py
571 571 > EOF
572 572 $ hg --config ui.ssh="python $TESTDIR/dummyssh" pull
573 573 pulling from ssh://user@dummy/remote
574 574 searching for changes
575 575 remote: abort: this is an exercise
576 576 abort: pull failed on remote
577 577 [255]
General Comments 0
You need to be logged in to leave comments. Login now