##// END OF EJS Templates
share: handle --relative shares to a different drive letter gracefully...
Matt Harbison -
r34980:b64ea7fb stable
parent child Browse files
Show More
@@ -1,1101 +1,1103
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import hashlib
13 13 import os
14 14 import shutil
15 15
16 16 from .i18n import _
17 17 from .node import nullid
18 18
19 19 from . import (
20 20 bookmarks,
21 21 bundlerepo,
22 22 cmdutil,
23 23 destutil,
24 24 discovery,
25 25 error,
26 26 exchange,
27 27 extensions,
28 28 httppeer,
29 29 localrepo,
30 30 lock,
31 31 merge as mergemod,
32 32 node,
33 33 phases,
34 34 repoview,
35 35 scmutil,
36 36 sshpeer,
37 37 statichttprepo,
38 38 ui as uimod,
39 39 unionrepo,
40 40 url,
41 41 util,
42 42 verify as verifymod,
43 43 vfs as vfsmod,
44 44 )
45 45
46 46 release = lock.release
47 47
48 48 # shared features
49 49 sharedbookmarks = 'bookmarks'
50 50
51 51 def _local(path):
52 52 path = util.expandpath(util.urllocalpath(path))
53 53 return (os.path.isfile(path) and bundlerepo or localrepo)
54 54
55 55 def addbranchrevs(lrepo, other, branches, revs):
56 56 peer = other.peer() # a courtesy to callers using a localrepo for other
57 57 hashbranch, branches = branches
58 58 if not hashbranch and not branches:
59 59 x = revs or None
60 60 if util.safehasattr(revs, 'first'):
61 61 y = revs.first()
62 62 elif revs:
63 63 y = revs[0]
64 64 else:
65 65 y = None
66 66 return x, y
67 67 if revs:
68 68 revs = list(revs)
69 69 else:
70 70 revs = []
71 71
72 72 if not peer.capable('branchmap'):
73 73 if branches:
74 74 raise error.Abort(_("remote branch lookup not supported"))
75 75 revs.append(hashbranch)
76 76 return revs, revs[0]
77 77 branchmap = peer.branchmap()
78 78
79 79 def primary(branch):
80 80 if branch == '.':
81 81 if not lrepo:
82 82 raise error.Abort(_("dirstate branch not accessible"))
83 83 branch = lrepo.dirstate.branch()
84 84 if branch in branchmap:
85 85 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
86 86 return True
87 87 else:
88 88 return False
89 89
90 90 for branch in branches:
91 91 if not primary(branch):
92 92 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
93 93 if hashbranch:
94 94 if not primary(hashbranch):
95 95 revs.append(hashbranch)
96 96 return revs, revs[0]
97 97
98 98 def parseurl(path, branches=None):
99 99 '''parse url#branch, returning (url, (branch, branches))'''
100 100
101 101 u = util.url(path)
102 102 branch = None
103 103 if u.fragment:
104 104 branch = u.fragment
105 105 u.fragment = None
106 106 return bytes(u), (branch, branches or [])
107 107
108 108 schemes = {
109 109 'bundle': bundlerepo,
110 110 'union': unionrepo,
111 111 'file': _local,
112 112 'http': httppeer,
113 113 'https': httppeer,
114 114 'ssh': sshpeer,
115 115 'static-http': statichttprepo,
116 116 }
117 117
118 118 def _peerlookup(path):
119 119 u = util.url(path)
120 120 scheme = u.scheme or 'file'
121 121 thing = schemes.get(scheme) or schemes['file']
122 122 try:
123 123 return thing(path)
124 124 except TypeError:
125 125 # we can't test callable(thing) because 'thing' can be an unloaded
126 126 # module that implements __call__
127 127 if not util.safehasattr(thing, 'instance'):
128 128 raise
129 129 return thing
130 130
131 131 def islocal(repo):
132 132 '''return true if repo (or path pointing to repo) is local'''
133 133 if isinstance(repo, bytes):
134 134 try:
135 135 return _peerlookup(repo).islocal(repo)
136 136 except AttributeError:
137 137 return False
138 138 return repo.local()
139 139
140 140 def openpath(ui, path):
141 141 '''open path with open if local, url.open if remote'''
142 142 pathurl = util.url(path, parsequery=False, parsefragment=False)
143 143 if pathurl.islocal():
144 144 return util.posixfile(pathurl.localpath(), 'rb')
145 145 else:
146 146 return url.open(ui, path)
147 147
148 148 # a list of (ui, repo) functions called for wire peer initialization
149 149 wirepeersetupfuncs = []
150 150
151 151 def _peerorrepo(ui, path, create=False, presetupfuncs=None):
152 152 """return a repository object for the specified path"""
153 153 obj = _peerlookup(path).instance(ui, path, create)
154 154 ui = getattr(obj, "ui", ui)
155 155 for f in presetupfuncs or []:
156 156 f(ui, obj)
157 157 for name, module in extensions.extensions(ui):
158 158 hook = getattr(module, 'reposetup', None)
159 159 if hook:
160 160 hook(ui, obj)
161 161 if not obj.local():
162 162 for f in wirepeersetupfuncs:
163 163 f(ui, obj)
164 164 return obj
165 165
166 166 def repository(ui, path='', create=False, presetupfuncs=None):
167 167 """return a repository object for the specified path"""
168 168 peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs)
169 169 repo = peer.local()
170 170 if not repo:
171 171 raise error.Abort(_("repository '%s' is not local") %
172 172 (path or peer.url()))
173 173 return repo.filtered('visible')
174 174
175 175 def peer(uiorrepo, opts, path, create=False):
176 176 '''return a repository peer for the specified path'''
177 177 rui = remoteui(uiorrepo, opts)
178 178 return _peerorrepo(rui, path, create).peer()
179 179
180 180 def defaultdest(source):
181 181 '''return default destination of clone if none is given
182 182
183 183 >>> defaultdest(b'foo')
184 184 'foo'
185 185 >>> defaultdest(b'/foo/bar')
186 186 'bar'
187 187 >>> defaultdest(b'/')
188 188 ''
189 189 >>> defaultdest(b'')
190 190 ''
191 191 >>> defaultdest(b'http://example.org/')
192 192 ''
193 193 >>> defaultdest(b'http://example.org/foo/')
194 194 'foo'
195 195 '''
196 196 path = util.url(source).path
197 197 if not path:
198 198 return ''
199 199 return os.path.basename(os.path.normpath(path))
200 200
201 201 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
202 202 relative=False):
203 203 '''create a shared repository'''
204 204
205 205 if not islocal(source):
206 206 raise error.Abort(_('can only share local repositories'))
207 207
208 208 if not dest:
209 209 dest = defaultdest(source)
210 210 else:
211 211 dest = ui.expandpath(dest)
212 212
213 213 if isinstance(source, str):
214 214 origsource = ui.expandpath(source)
215 215 source, branches = parseurl(origsource)
216 216 srcrepo = repository(ui, source)
217 217 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
218 218 else:
219 219 srcrepo = source.local()
220 220 origsource = source = srcrepo.url()
221 221 checkout = None
222 222
223 223 sharedpath = srcrepo.sharedpath # if our source is already sharing
224 224
225 225 destwvfs = vfsmod.vfs(dest, realpath=True)
226 226 destvfs = vfsmod.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
227 227
228 228 if destvfs.lexists():
229 229 raise error.Abort(_('destination already exists'))
230 230
231 231 if not destwvfs.isdir():
232 232 destwvfs.mkdir()
233 233 destvfs.makedir()
234 234
235 235 requirements = ''
236 236 try:
237 237 requirements = srcrepo.vfs.read('requires')
238 238 except IOError as inst:
239 239 if inst.errno != errno.ENOENT:
240 240 raise
241 241
242 242 if relative:
243 243 try:
244 244 sharedpath = os.path.relpath(sharedpath, destvfs.base)
245 245 requirements += 'relshared\n'
246 except IOError as e:
246 except (IOError, ValueError) as e:
247 # ValueError is raised on Windows if the drive letters differ on
248 # each path
247 249 raise error.Abort(_('cannot calculate relative path'),
248 250 hint=str(e))
249 251 else:
250 252 requirements += 'shared\n'
251 253
252 254 destvfs.write('requires', requirements)
253 255 destvfs.write('sharedpath', sharedpath)
254 256
255 257 r = repository(ui, destwvfs.base)
256 258 postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
257 259 _postshareupdate(r, update, checkout=checkout)
258 260 return r
259 261
260 262 def unshare(ui, repo):
261 263 """convert a shared repository to a normal one
262 264
263 265 Copy the store data to the repo and remove the sharedpath data.
264 266 """
265 267
266 268 destlock = lock = None
267 269 lock = repo.lock()
268 270 try:
269 271 # we use locks here because if we race with commit, we
270 272 # can end up with extra data in the cloned revlogs that's
271 273 # not pointed to by changesets, thus causing verify to
272 274 # fail
273 275
274 276 destlock = copystore(ui, repo, repo.path)
275 277
276 278 sharefile = repo.vfs.join('sharedpath')
277 279 util.rename(sharefile, sharefile + '.old')
278 280
279 281 repo.requirements.discard('shared')
280 282 repo.requirements.discard('relshared')
281 283 repo._writerequirements()
282 284 finally:
283 285 destlock and destlock.release()
284 286 lock and lock.release()
285 287
286 288 # update store, spath, svfs and sjoin of repo
287 289 repo.unfiltered().__init__(repo.baseui, repo.root)
288 290
289 291 # TODO: figure out how to access subrepos that exist, but were previously
290 292 # removed from .hgsub
291 293 c = repo['.']
292 294 subs = c.substate
293 295 for s in sorted(subs):
294 296 c.sub(s).unshare()
295 297
296 298 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
297 299 """Called after a new shared repo is created.
298 300
299 301 The new repo only has a requirements file and pointer to the source.
300 302 This function configures additional shared data.
301 303
302 304 Extensions can wrap this function and write additional entries to
303 305 destrepo/.hg/shared to indicate additional pieces of data to be shared.
304 306 """
305 307 default = defaultpath or sourcerepo.ui.config('paths', 'default')
306 308 if default:
307 309 fp = destrepo.vfs("hgrc", "w", text=True)
308 310 fp.write("[paths]\n")
309 311 fp.write("default = %s\n" % default)
310 312 fp.close()
311 313
312 314 with destrepo.wlock():
313 315 if bookmarks:
314 316 fp = destrepo.vfs('shared', 'w')
315 317 fp.write(sharedbookmarks + '\n')
316 318 fp.close()
317 319
318 320 def _postshareupdate(repo, update, checkout=None):
319 321 """Maybe perform a working directory update after a shared repo is created.
320 322
321 323 ``update`` can be a boolean or a revision to update to.
322 324 """
323 325 if not update:
324 326 return
325 327
326 328 repo.ui.status(_("updating working directory\n"))
327 329 if update is not True:
328 330 checkout = update
329 331 for test in (checkout, 'default', 'tip'):
330 332 if test is None:
331 333 continue
332 334 try:
333 335 uprev = repo.lookup(test)
334 336 break
335 337 except error.RepoLookupError:
336 338 continue
337 339 _update(repo, uprev)
338 340
339 341 def copystore(ui, srcrepo, destpath):
340 342 '''copy files from store of srcrepo in destpath
341 343
342 344 returns destlock
343 345 '''
344 346 destlock = None
345 347 try:
346 348 hardlink = None
347 349 num = 0
348 350 closetopic = [None]
349 351 def prog(topic, pos):
350 352 if pos is None:
351 353 closetopic[0] = topic
352 354 else:
353 355 ui.progress(topic, pos + num)
354 356 srcpublishing = srcrepo.publishing()
355 357 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
356 358 dstvfs = vfsmod.vfs(destpath)
357 359 for f in srcrepo.store.copylist():
358 360 if srcpublishing and f.endswith('phaseroots'):
359 361 continue
360 362 dstbase = os.path.dirname(f)
361 363 if dstbase and not dstvfs.exists(dstbase):
362 364 dstvfs.mkdir(dstbase)
363 365 if srcvfs.exists(f):
364 366 if f.endswith('data'):
365 367 # 'dstbase' may be empty (e.g. revlog format 0)
366 368 lockfile = os.path.join(dstbase, "lock")
367 369 # lock to avoid premature writing to the target
368 370 destlock = lock.lock(dstvfs, lockfile)
369 371 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
370 372 hardlink, progress=prog)
371 373 num += n
372 374 if hardlink:
373 375 ui.debug("linked %d files\n" % num)
374 376 if closetopic[0]:
375 377 ui.progress(closetopic[0], None)
376 378 else:
377 379 ui.debug("copied %d files\n" % num)
378 380 if closetopic[0]:
379 381 ui.progress(closetopic[0], None)
380 382 return destlock
381 383 except: # re-raises
382 384 release(destlock)
383 385 raise
384 386
385 387 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
386 388 rev=None, update=True, stream=False):
387 389 """Perform a clone using a shared repo.
388 390
389 391 The store for the repository will be located at <sharepath>/.hg. The
390 392 specified revisions will be cloned or pulled from "source". A shared repo
391 393 will be created at "dest" and a working copy will be created if "update" is
392 394 True.
393 395 """
394 396 revs = None
395 397 if rev:
396 398 if not srcpeer.capable('lookup'):
397 399 raise error.Abort(_("src repository does not support "
398 400 "revision lookup and so doesn't "
399 401 "support clone by revision"))
400 402 revs = [srcpeer.lookup(r) for r in rev]
401 403
402 404 # Obtain a lock before checking for or cloning the pooled repo otherwise
403 405 # 2 clients may race creating or populating it.
404 406 pooldir = os.path.dirname(sharepath)
405 407 # lock class requires the directory to exist.
406 408 try:
407 409 util.makedir(pooldir, False)
408 410 except OSError as e:
409 411 if e.errno != errno.EEXIST:
410 412 raise
411 413
412 414 poolvfs = vfsmod.vfs(pooldir)
413 415 basename = os.path.basename(sharepath)
414 416
415 417 with lock.lock(poolvfs, '%s.lock' % basename):
416 418 if os.path.exists(sharepath):
417 419 ui.status(_('(sharing from existing pooled repository %s)\n') %
418 420 basename)
419 421 else:
420 422 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
421 423 # Always use pull mode because hardlinks in share mode don't work
422 424 # well. Never update because working copies aren't necessary in
423 425 # share mode.
424 426 clone(ui, peeropts, source, dest=sharepath, pull=True,
425 427 rev=rev, update=False, stream=stream)
426 428
427 429 # Resolve the value to put in [paths] section for the source.
428 430 if islocal(source):
429 431 defaultpath = os.path.abspath(util.urllocalpath(source))
430 432 else:
431 433 defaultpath = source
432 434
433 435 sharerepo = repository(ui, path=sharepath)
434 436 share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
435 437 defaultpath=defaultpath)
436 438
437 439 # We need to perform a pull against the dest repo to fetch bookmarks
438 440 # and other non-store data that isn't shared by default. In the case of
439 441 # non-existing shared repo, this means we pull from the remote twice. This
440 442 # is a bit weird. But at the time it was implemented, there wasn't an easy
441 443 # way to pull just non-changegroup data.
442 444 destrepo = repository(ui, path=dest)
443 445 exchange.pull(destrepo, srcpeer, heads=revs)
444 446
445 447 _postshareupdate(destrepo, update)
446 448
447 449 return srcpeer, peer(ui, peeropts, dest)
448 450
449 451 # Recomputing branch cache might be slow on big repos,
450 452 # so just copy it
451 453 def _copycache(srcrepo, dstcachedir, fname):
452 454 """copy a cache from srcrepo to destcachedir (if it exists)"""
453 455 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
454 456 dstbranchcache = os.path.join(dstcachedir, fname)
455 457 if os.path.exists(srcbranchcache):
456 458 if not os.path.exists(dstcachedir):
457 459 os.mkdir(dstcachedir)
458 460 util.copyfile(srcbranchcache, dstbranchcache)
459 461
460 462 def _cachetocopy(srcrepo):
461 463 """return the list of cache file valuable to copy during a clone"""
462 464 # In local clones we're copying all nodes, not just served
463 465 # ones. Therefore copy all branch caches over.
464 466 cachefiles = ['branch2']
465 467 cachefiles += ['branch2-%s' % f for f in repoview.filtertable]
466 468 cachefiles += ['rbc-names-v1', 'rbc-revs-v1']
467 469 cachefiles += ['tags2']
468 470 cachefiles += ['tags2-%s' % f for f in repoview.filtertable]
469 471 cachefiles += ['hgtagsfnodes1']
470 472 return cachefiles
471 473
472 474 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
473 475 update=True, stream=False, branch=None, shareopts=None):
474 476 """Make a copy of an existing repository.
475 477
476 478 Create a copy of an existing repository in a new directory. The
477 479 source and destination are URLs, as passed to the repository
478 480 function. Returns a pair of repository peers, the source and
479 481 newly created destination.
480 482
481 483 The location of the source is added to the new repository's
482 484 .hg/hgrc file, as the default to be used for future pulls and
483 485 pushes.
484 486
485 487 If an exception is raised, the partly cloned/updated destination
486 488 repository will be deleted.
487 489
488 490 Arguments:
489 491
490 492 source: repository object or URL
491 493
492 494 dest: URL of destination repository to create (defaults to base
493 495 name of source repository)
494 496
495 497 pull: always pull from source repository, even in local case or if the
496 498 server prefers streaming
497 499
498 500 stream: stream raw data uncompressed from repository (fast over
499 501 LAN, slow over WAN)
500 502
501 503 rev: revision to clone up to (implies pull=True)
502 504
503 505 update: update working directory after clone completes, if
504 506 destination is local repository (True means update to default rev,
505 507 anything else is treated as a revision)
506 508
507 509 branch: branches to clone
508 510
509 511 shareopts: dict of options to control auto sharing behavior. The "pool" key
510 512 activates auto sharing mode and defines the directory for stores. The
511 513 "mode" key determines how to construct the directory name of the shared
512 514 repository. "identity" means the name is derived from the node of the first
513 515 changeset in the repository. "remote" means the name is derived from the
514 516 remote's path/URL. Defaults to "identity."
515 517 """
516 518
517 519 if isinstance(source, bytes):
518 520 origsource = ui.expandpath(source)
519 521 source, branch = parseurl(origsource, branch)
520 522 srcpeer = peer(ui, peeropts, source)
521 523 else:
522 524 srcpeer = source.peer() # in case we were called with a localrepo
523 525 branch = (None, branch or [])
524 526 origsource = source = srcpeer.url()
525 527 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
526 528
527 529 if dest is None:
528 530 dest = defaultdest(source)
529 531 if dest:
530 532 ui.status(_("destination directory: %s\n") % dest)
531 533 else:
532 534 dest = ui.expandpath(dest)
533 535
534 536 dest = util.urllocalpath(dest)
535 537 source = util.urllocalpath(source)
536 538
537 539 if not dest:
538 540 raise error.Abort(_("empty destination path is not valid"))
539 541
540 542 destvfs = vfsmod.vfs(dest, expandpath=True)
541 543 if destvfs.lexists():
542 544 if not destvfs.isdir():
543 545 raise error.Abort(_("destination '%s' already exists") % dest)
544 546 elif destvfs.listdir():
545 547 raise error.Abort(_("destination '%s' is not empty") % dest)
546 548
547 549 shareopts = shareopts or {}
548 550 sharepool = shareopts.get('pool')
549 551 sharenamemode = shareopts.get('mode')
550 552 if sharepool and islocal(dest):
551 553 sharepath = None
552 554 if sharenamemode == 'identity':
553 555 # Resolve the name from the initial changeset in the remote
554 556 # repository. This returns nullid when the remote is empty. It
555 557 # raises RepoLookupError if revision 0 is filtered or otherwise
556 558 # not available. If we fail to resolve, sharing is not enabled.
557 559 try:
558 560 rootnode = srcpeer.lookup('0')
559 561 if rootnode != node.nullid:
560 562 sharepath = os.path.join(sharepool, node.hex(rootnode))
561 563 else:
562 564 ui.status(_('(not using pooled storage: '
563 565 'remote appears to be empty)\n'))
564 566 except error.RepoLookupError:
565 567 ui.status(_('(not using pooled storage: '
566 568 'unable to resolve identity of remote)\n'))
567 569 elif sharenamemode == 'remote':
568 570 sharepath = os.path.join(
569 571 sharepool, hashlib.sha1(source).hexdigest())
570 572 else:
571 573 raise error.Abort(_('unknown share naming mode: %s') %
572 574 sharenamemode)
573 575
574 576 if sharepath:
575 577 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
576 578 dest, pull=pull, rev=rev, update=update,
577 579 stream=stream)
578 580
579 581 srclock = destlock = cleandir = None
580 582 srcrepo = srcpeer.local()
581 583 try:
582 584 abspath = origsource
583 585 if islocal(origsource):
584 586 abspath = os.path.abspath(util.urllocalpath(origsource))
585 587
586 588 if islocal(dest):
587 589 cleandir = dest
588 590
589 591 copy = False
590 592 if (srcrepo and srcrepo.cancopy() and islocal(dest)
591 593 and not phases.hassecret(srcrepo)):
592 594 copy = not pull and not rev
593 595
594 596 if copy:
595 597 try:
596 598 # we use a lock here because if we race with commit, we
597 599 # can end up with extra data in the cloned revlogs that's
598 600 # not pointed to by changesets, thus causing verify to
599 601 # fail
600 602 srclock = srcrepo.lock(wait=False)
601 603 except error.LockError:
602 604 copy = False
603 605
604 606 if copy:
605 607 srcrepo.hook('preoutgoing', throw=True, source='clone')
606 608 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
607 609 if not os.path.exists(dest):
608 610 os.mkdir(dest)
609 611 else:
610 612 # only clean up directories we create ourselves
611 613 cleandir = hgdir
612 614 try:
613 615 destpath = hgdir
614 616 util.makedir(destpath, notindexed=True)
615 617 except OSError as inst:
616 618 if inst.errno == errno.EEXIST:
617 619 cleandir = None
618 620 raise error.Abort(_("destination '%s' already exists")
619 621 % dest)
620 622 raise
621 623
622 624 destlock = copystore(ui, srcrepo, destpath)
623 625 # copy bookmarks over
624 626 srcbookmarks = srcrepo.vfs.join('bookmarks')
625 627 dstbookmarks = os.path.join(destpath, 'bookmarks')
626 628 if os.path.exists(srcbookmarks):
627 629 util.copyfile(srcbookmarks, dstbookmarks)
628 630
629 631 dstcachedir = os.path.join(destpath, 'cache')
630 632 for cache in _cachetocopy(srcrepo):
631 633 _copycache(srcrepo, dstcachedir, cache)
632 634
633 635 # we need to re-init the repo after manually copying the data
634 636 # into it
635 637 destpeer = peer(srcrepo, peeropts, dest)
636 638 srcrepo.hook('outgoing', source='clone',
637 639 node=node.hex(node.nullid))
638 640 else:
639 641 try:
640 642 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
641 643 # only pass ui when no srcrepo
642 644 except OSError as inst:
643 645 if inst.errno == errno.EEXIST:
644 646 cleandir = None
645 647 raise error.Abort(_("destination '%s' already exists")
646 648 % dest)
647 649 raise
648 650
649 651 revs = None
650 652 if rev:
651 653 if not srcpeer.capable('lookup'):
652 654 raise error.Abort(_("src repository does not support "
653 655 "revision lookup and so doesn't "
654 656 "support clone by revision"))
655 657 revs = [srcpeer.lookup(r) for r in rev]
656 658 checkout = revs[0]
657 659 local = destpeer.local()
658 660 if local:
659 661 if not stream:
660 662 if pull:
661 663 stream = False
662 664 else:
663 665 stream = None
664 666 # internal config: ui.quietbookmarkmove
665 667 overrides = {('ui', 'quietbookmarkmove'): True}
666 668 with local.ui.configoverride(overrides, 'clone'):
667 669 exchange.pull(local, srcpeer, revs,
668 670 streamclonerequested=stream)
669 671 elif srcrepo:
670 672 exchange.push(srcrepo, destpeer, revs=revs,
671 673 bookmarks=srcrepo._bookmarks.keys())
672 674 else:
673 675 raise error.Abort(_("clone from remote to remote not supported")
674 676 )
675 677
676 678 cleandir = None
677 679
678 680 destrepo = destpeer.local()
679 681 if destrepo:
680 682 template = uimod.samplehgrcs['cloned']
681 683 fp = destrepo.vfs("hgrc", "wb")
682 684 u = util.url(abspath)
683 685 u.passwd = None
684 686 defaulturl = bytes(u)
685 687 fp.write(util.tonativeeol(template % defaulturl))
686 688 fp.close()
687 689
688 690 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
689 691
690 692 if update:
691 693 if update is not True:
692 694 checkout = srcpeer.lookup(update)
693 695 uprev = None
694 696 status = None
695 697 if checkout is not None:
696 698 try:
697 699 uprev = destrepo.lookup(checkout)
698 700 except error.RepoLookupError:
699 701 if update is not True:
700 702 try:
701 703 uprev = destrepo.lookup(update)
702 704 except error.RepoLookupError:
703 705 pass
704 706 if uprev is None:
705 707 try:
706 708 uprev = destrepo._bookmarks['@']
707 709 update = '@'
708 710 bn = destrepo[uprev].branch()
709 711 if bn == 'default':
710 712 status = _("updating to bookmark @\n")
711 713 else:
712 714 status = (_("updating to bookmark @ on branch %s\n")
713 715 % bn)
714 716 except KeyError:
715 717 try:
716 718 uprev = destrepo.branchtip('default')
717 719 except error.RepoLookupError:
718 720 uprev = destrepo.lookup('tip')
719 721 if not status:
720 722 bn = destrepo[uprev].branch()
721 723 status = _("updating to branch %s\n") % bn
722 724 destrepo.ui.status(status)
723 725 _update(destrepo, uprev)
724 726 if update in destrepo._bookmarks:
725 727 bookmarks.activate(destrepo, update)
726 728 finally:
727 729 release(srclock, destlock)
728 730 if cleandir is not None:
729 731 shutil.rmtree(cleandir, True)
730 732 if srcpeer is not None:
731 733 srcpeer.close()
732 734 return srcpeer, destpeer
733 735
734 736 def _showstats(repo, stats, quietempty=False):
735 737 if quietempty and not any(stats):
736 738 return
737 739 repo.ui.status(_("%d files updated, %d files merged, "
738 740 "%d files removed, %d files unresolved\n") % stats)
739 741
740 742 def updaterepo(repo, node, overwrite, updatecheck=None):
741 743 """Update the working directory to node.
742 744
743 745 When overwrite is set, changes are clobbered, merged else
744 746
745 747 returns stats (see pydoc mercurial.merge.applyupdates)"""
746 748 return mergemod.update(repo, node, False, overwrite,
747 749 labels=['working copy', 'destination'],
748 750 updatecheck=updatecheck)
749 751
750 752 def update(repo, node, quietempty=False, updatecheck=None):
751 753 """update the working directory to node"""
752 754 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
753 755 _showstats(repo, stats, quietempty)
754 756 if stats[3]:
755 757 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
756 758 return stats[3] > 0
757 759
758 760 # naming conflict in clone()
759 761 _update = update
760 762
761 763 def clean(repo, node, show_stats=True, quietempty=False):
762 764 """forcibly switch the working directory to node, clobbering changes"""
763 765 stats = updaterepo(repo, node, True)
764 766 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
765 767 if show_stats:
766 768 _showstats(repo, stats, quietempty)
767 769 return stats[3] > 0
768 770
769 771 # naming conflict in updatetotally()
770 772 _clean = clean
771 773
772 774 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
773 775 """Update the working directory with extra care for non-file components
774 776
775 777 This takes care of non-file components below:
776 778
777 779 :bookmark: might be advanced or (in)activated
778 780
779 781 This takes arguments below:
780 782
781 783 :checkout: to which revision the working directory is updated
782 784 :brev: a name, which might be a bookmark to be activated after updating
783 785 :clean: whether changes in the working directory can be discarded
784 786 :updatecheck: how to deal with a dirty working directory
785 787
786 788 Valid values for updatecheck are (None => linear):
787 789
788 790 * abort: abort if the working directory is dirty
789 791 * none: don't check (merge working directory changes into destination)
790 792 * linear: check that update is linear before merging working directory
791 793 changes into destination
792 794 * noconflict: check that the update does not result in file merges
793 795
794 796 This returns whether conflict is detected at updating or not.
795 797 """
796 798 if updatecheck is None:
797 799 updatecheck = ui.config('commands', 'update.check')
798 800 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
799 801 # If not configured, or invalid value configured
800 802 updatecheck = 'linear'
801 803 with repo.wlock():
802 804 movemarkfrom = None
803 805 warndest = False
804 806 if checkout is None:
805 807 updata = destutil.destupdate(repo, clean=clean)
806 808 checkout, movemarkfrom, brev = updata
807 809 warndest = True
808 810
809 811 if clean:
810 812 ret = _clean(repo, checkout)
811 813 else:
812 814 if updatecheck == 'abort':
813 815 cmdutil.bailifchanged(repo, merge=False)
814 816 updatecheck = 'none'
815 817 ret = _update(repo, checkout, updatecheck=updatecheck)
816 818
817 819 if not ret and movemarkfrom:
818 820 if movemarkfrom == repo['.'].node():
819 821 pass # no-op update
820 822 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
821 823 b = ui.label(repo._activebookmark, 'bookmarks.active')
822 824 ui.status(_("updating bookmark %s\n") % b)
823 825 else:
824 826 # this can happen with a non-linear update
825 827 b = ui.label(repo._activebookmark, 'bookmarks')
826 828 ui.status(_("(leaving bookmark %s)\n") % b)
827 829 bookmarks.deactivate(repo)
828 830 elif brev in repo._bookmarks:
829 831 if brev != repo._activebookmark:
830 832 b = ui.label(brev, 'bookmarks.active')
831 833 ui.status(_("(activating bookmark %s)\n") % b)
832 834 bookmarks.activate(repo, brev)
833 835 elif brev:
834 836 if repo._activebookmark:
835 837 b = ui.label(repo._activebookmark, 'bookmarks')
836 838 ui.status(_("(leaving bookmark %s)\n") % b)
837 839 bookmarks.deactivate(repo)
838 840
839 841 if warndest:
840 842 destutil.statusotherdests(ui, repo)
841 843
842 844 return ret
843 845
844 846 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None):
845 847 """Branch merge with node, resolving changes. Return true if any
846 848 unresolved conflicts."""
847 849 stats = mergemod.update(repo, node, True, force, mergeforce=mergeforce,
848 850 labels=labels)
849 851 _showstats(repo, stats)
850 852 if stats[3]:
851 853 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
852 854 "or 'hg update -C .' to abandon\n"))
853 855 elif remind:
854 856 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
855 857 return stats[3] > 0
856 858
857 859 def _incoming(displaychlist, subreporecurse, ui, repo, source,
858 860 opts, buffered=False):
859 861 """
860 862 Helper for incoming / gincoming.
861 863 displaychlist gets called with
862 864 (remoterepo, incomingchangesetlist, displayer) parameters,
863 865 and is supposed to contain only code that can't be unified.
864 866 """
865 867 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
866 868 other = peer(repo, opts, source)
867 869 ui.status(_('comparing with %s\n') % util.hidepassword(source))
868 870 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
869 871
870 872 if revs:
871 873 revs = [other.lookup(rev) for rev in revs]
872 874 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
873 875 revs, opts["bundle"], opts["force"])
874 876 try:
875 877 if not chlist:
876 878 ui.status(_("no changes found\n"))
877 879 return subreporecurse()
878 880 ui.pager('incoming')
879 881 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
880 882 displaychlist(other, chlist, displayer)
881 883 displayer.close()
882 884 finally:
883 885 cleanupfn()
884 886 subreporecurse()
885 887 return 0 # exit code is zero since we found incoming changes
886 888
887 889 def incoming(ui, repo, source, opts):
888 890 def subreporecurse():
889 891 ret = 1
890 892 if opts.get('subrepos'):
891 893 ctx = repo[None]
892 894 for subpath in sorted(ctx.substate):
893 895 sub = ctx.sub(subpath)
894 896 ret = min(ret, sub.incoming(ui, source, opts))
895 897 return ret
896 898
897 899 def display(other, chlist, displayer):
898 900 limit = cmdutil.loglimit(opts)
899 901 if opts.get('newest_first'):
900 902 chlist.reverse()
901 903 count = 0
902 904 for n in chlist:
903 905 if limit is not None and count >= limit:
904 906 break
905 907 parents = [p for p in other.changelog.parents(n) if p != nullid]
906 908 if opts.get('no_merges') and len(parents) == 2:
907 909 continue
908 910 count += 1
909 911 displayer.show(other[n])
910 912 return _incoming(display, subreporecurse, ui, repo, source, opts)
911 913
912 914 def _outgoing(ui, repo, dest, opts):
913 915 dest = ui.expandpath(dest or 'default-push', dest or 'default')
914 916 dest, branches = parseurl(dest, opts.get('branch'))
915 917 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
916 918 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
917 919 if revs:
918 920 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
919 921
920 922 other = peer(repo, opts, dest)
921 923 outgoing = discovery.findcommonoutgoing(repo, other, revs,
922 924 force=opts.get('force'))
923 925 o = outgoing.missing
924 926 if not o:
925 927 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
926 928 return o, other
927 929
928 930 def outgoing(ui, repo, dest, opts):
929 931 def recurse():
930 932 ret = 1
931 933 if opts.get('subrepos'):
932 934 ctx = repo[None]
933 935 for subpath in sorted(ctx.substate):
934 936 sub = ctx.sub(subpath)
935 937 ret = min(ret, sub.outgoing(ui, dest, opts))
936 938 return ret
937 939
938 940 limit = cmdutil.loglimit(opts)
939 941 o, other = _outgoing(ui, repo, dest, opts)
940 942 if not o:
941 943 cmdutil.outgoinghooks(ui, repo, other, opts, o)
942 944 return recurse()
943 945
944 946 if opts.get('newest_first'):
945 947 o.reverse()
946 948 ui.pager('outgoing')
947 949 displayer = cmdutil.show_changeset(ui, repo, opts)
948 950 count = 0
949 951 for n in o:
950 952 if limit is not None and count >= limit:
951 953 break
952 954 parents = [p for p in repo.changelog.parents(n) if p != nullid]
953 955 if opts.get('no_merges') and len(parents) == 2:
954 956 continue
955 957 count += 1
956 958 displayer.show(repo[n])
957 959 displayer.close()
958 960 cmdutil.outgoinghooks(ui, repo, other, opts, o)
959 961 recurse()
960 962 return 0 # exit code is zero since we found outgoing changes
961 963
962 964 def verify(repo):
963 965 """verify the consistency of a repository"""
964 966 ret = verifymod.verify(repo)
965 967
966 968 # Broken subrepo references in hidden csets don't seem worth worrying about,
967 969 # since they can't be pushed/pulled, and --hidden can be used if they are a
968 970 # concern.
969 971
970 972 # pathto() is needed for -R case
971 973 revs = repo.revs("filelog(%s)",
972 974 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
973 975
974 976 if revs:
975 977 repo.ui.status(_('checking subrepo links\n'))
976 978 for rev in revs:
977 979 ctx = repo[rev]
978 980 try:
979 981 for subpath in ctx.substate:
980 982 try:
981 983 ret = (ctx.sub(subpath, allowcreate=False).verify()
982 984 or ret)
983 985 except error.RepoError as e:
984 986 repo.ui.warn(('%s: %s\n') % (rev, e))
985 987 except Exception:
986 988 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
987 989 node.short(ctx.node()))
988 990
989 991 return ret
990 992
991 993 def remoteui(src, opts):
992 994 'build a remote ui from ui or repo and opts'
993 995 if util.safehasattr(src, 'baseui'): # looks like a repository
994 996 dst = src.baseui.copy() # drop repo-specific config
995 997 src = src.ui # copy target options from repo
996 998 else: # assume it's a global ui object
997 999 dst = src.copy() # keep all global options
998 1000
999 1001 # copy ssh-specific options
1000 1002 for o in 'ssh', 'remotecmd':
1001 1003 v = opts.get(o) or src.config('ui', o)
1002 1004 if v:
1003 1005 dst.setconfig("ui", o, v, 'copied')
1004 1006
1005 1007 # copy bundle-specific options
1006 1008 r = src.config('bundle', 'mainreporoot')
1007 1009 if r:
1008 1010 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
1009 1011
1010 1012 # copy selected local settings to the remote ui
1011 1013 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
1012 1014 for key, val in src.configitems(sect):
1013 1015 dst.setconfig(sect, key, val, 'copied')
1014 1016 v = src.config('web', 'cacerts')
1015 1017 if v:
1016 1018 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
1017 1019
1018 1020 return dst
1019 1021
1020 1022 # Files of interest
1021 1023 # Used to check if the repository has changed looking at mtime and size of
1022 1024 # these files.
1023 1025 foi = [('spath', '00changelog.i'),
1024 1026 ('spath', 'phaseroots'), # ! phase can change content at the same size
1025 1027 ('spath', 'obsstore'),
1026 1028 ('path', 'bookmarks'), # ! bookmark can change content at the same size
1027 1029 ]
1028 1030
1029 1031 class cachedlocalrepo(object):
1030 1032 """Holds a localrepository that can be cached and reused."""
1031 1033
1032 1034 def __init__(self, repo):
1033 1035 """Create a new cached repo from an existing repo.
1034 1036
1035 1037 We assume the passed in repo was recently created. If the
1036 1038 repo has changed between when it was created and when it was
1037 1039 turned into a cache, it may not refresh properly.
1038 1040 """
1039 1041 assert isinstance(repo, localrepo.localrepository)
1040 1042 self._repo = repo
1041 1043 self._state, self.mtime = self._repostate()
1042 1044 self._filtername = repo.filtername
1043 1045
1044 1046 def fetch(self):
1045 1047 """Refresh (if necessary) and return a repository.
1046 1048
1047 1049 If the cached instance is out of date, it will be recreated
1048 1050 automatically and returned.
1049 1051
1050 1052 Returns a tuple of the repo and a boolean indicating whether a new
1051 1053 repo instance was created.
1052 1054 """
1053 1055 # We compare the mtimes and sizes of some well-known files to
1054 1056 # determine if the repo changed. This is not precise, as mtimes
1055 1057 # are susceptible to clock skew and imprecise filesystems and
1056 1058 # file content can change while maintaining the same size.
1057 1059
1058 1060 state, mtime = self._repostate()
1059 1061 if state == self._state:
1060 1062 return self._repo, False
1061 1063
1062 1064 repo = repository(self._repo.baseui, self._repo.url())
1063 1065 if self._filtername:
1064 1066 self._repo = repo.filtered(self._filtername)
1065 1067 else:
1066 1068 self._repo = repo.unfiltered()
1067 1069 self._state = state
1068 1070 self.mtime = mtime
1069 1071
1070 1072 return self._repo, True
1071 1073
1072 1074 def _repostate(self):
1073 1075 state = []
1074 1076 maxmtime = -1
1075 1077 for attr, fname in foi:
1076 1078 prefix = getattr(self._repo, attr)
1077 1079 p = os.path.join(prefix, fname)
1078 1080 try:
1079 1081 st = os.stat(p)
1080 1082 except OSError:
1081 1083 st = os.stat(prefix)
1082 1084 state.append((st.st_mtime, st.st_size))
1083 1085 maxmtime = max(maxmtime, st.st_mtime)
1084 1086
1085 1087 return tuple(state), maxmtime
1086 1088
1087 1089 def copy(self):
1088 1090 """Obtain a copy of this class instance.
1089 1091
1090 1092 A new localrepository instance is obtained. The new instance should be
1091 1093 completely independent of the original.
1092 1094 """
1093 1095 repo = repository(self._repo.baseui, self._repo.origroot)
1094 1096 if self._filtername:
1095 1097 repo = repo.filtered(self._filtername)
1096 1098 else:
1097 1099 repo = repo.unfiltered()
1098 1100 c = cachedlocalrepo(repo)
1099 1101 c._state = self._state
1100 1102 c.mtime = self.mtime
1101 1103 return c
General Comments 0
You need to be logged in to leave comments. Login now