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