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