##// END OF EJS Templates
clone: check update rev for being True...
Sean Farley -
r26354:c1fb2cab default
parent child Browse files
Show More
@@ -1,899 +1,900
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 util.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 util.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 util.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 util.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 util.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
239 239 default = srcrepo.ui.config('paths', 'default')
240 240 if default:
241 241 fp = r.vfs("hgrc", "w", text=True)
242 242 fp.write("[paths]\n")
243 243 fp.write("default = %s\n" % default)
244 244 fp.close()
245 245
246 246 if update:
247 247 r.ui.status(_("updating working directory\n"))
248 248 if update is not True:
249 249 checkout = update
250 250 for test in (checkout, 'default', 'tip'):
251 251 if test is None:
252 252 continue
253 253 try:
254 254 uprev = r.lookup(test)
255 255 break
256 256 except error.RepoLookupError:
257 257 continue
258 258 _update(r, uprev)
259 259
260 260 if bookmarks:
261 261 fp = r.vfs('shared', 'w')
262 262 fp.write('bookmarks\n')
263 263 fp.close()
264 264
265 265 def copystore(ui, srcrepo, destpath):
266 266 '''copy files from store of srcrepo in destpath
267 267
268 268 returns destlock
269 269 '''
270 270 destlock = None
271 271 try:
272 272 hardlink = None
273 273 num = 0
274 274 closetopic = [None]
275 275 def prog(topic, pos):
276 276 if pos is None:
277 277 closetopic[0] = topic
278 278 else:
279 279 ui.progress(topic, pos + num)
280 280 srcpublishing = srcrepo.publishing()
281 281 srcvfs = scmutil.vfs(srcrepo.sharedpath)
282 282 dstvfs = scmutil.vfs(destpath)
283 283 for f in srcrepo.store.copylist():
284 284 if srcpublishing and f.endswith('phaseroots'):
285 285 continue
286 286 dstbase = os.path.dirname(f)
287 287 if dstbase and not dstvfs.exists(dstbase):
288 288 dstvfs.mkdir(dstbase)
289 289 if srcvfs.exists(f):
290 290 if f.endswith('data'):
291 291 # 'dstbase' may be empty (e.g. revlog format 0)
292 292 lockfile = os.path.join(dstbase, "lock")
293 293 # lock to avoid premature writing to the target
294 294 destlock = lock.lock(dstvfs, lockfile)
295 295 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
296 296 hardlink, progress=prog)
297 297 num += n
298 298 if hardlink:
299 299 ui.debug("linked %d files\n" % num)
300 300 if closetopic[0]:
301 301 ui.progress(closetopic[0], None)
302 302 else:
303 303 ui.debug("copied %d files\n" % num)
304 304 if closetopic[0]:
305 305 ui.progress(closetopic[0], None)
306 306 return destlock
307 307 except: # re-raises
308 308 release(destlock)
309 309 raise
310 310
311 311 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
312 312 rev=None, update=True, stream=False):
313 313 """Perform a clone using a shared repo.
314 314
315 315 The store for the repository will be located at <sharepath>/.hg. The
316 316 specified revisions will be cloned or pulled from "source". A shared repo
317 317 will be created at "dest" and a working copy will be created if "update" is
318 318 True.
319 319 """
320 320 revs = None
321 321 if rev:
322 322 if not srcpeer.capable('lookup'):
323 323 raise util.Abort(_("src repository does not support "
324 324 "revision lookup and so doesn't "
325 325 "support clone by revision"))
326 326 revs = [srcpeer.lookup(r) for r in rev]
327 327
328 328 basename = os.path.basename(sharepath)
329 329
330 330 if os.path.exists(sharepath):
331 331 ui.status(_('(sharing from existing pooled repository %s)\n') %
332 332 basename)
333 333 else:
334 334 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
335 335 # Always use pull mode because hardlinks in share mode don't work well.
336 336 # Never update because working copies aren't necessary in share mode.
337 337 clone(ui, peeropts, source, dest=sharepath, pull=True,
338 338 rev=rev, update=False, stream=stream)
339 339
340 340 sharerepo = repository(ui, path=sharepath)
341 341 share(ui, sharerepo, dest=dest, update=update, bookmarks=False)
342 342
343 343 # We need to perform a pull against the dest repo to fetch bookmarks
344 344 # and other non-store data that isn't shared by default. In the case of
345 345 # non-existing shared repo, this means we pull from the remote twice. This
346 346 # is a bit weird. But at the time it was implemented, there wasn't an easy
347 347 # way to pull just non-changegroup data.
348 348 destrepo = repository(ui, path=dest)
349 349 exchange.pull(destrepo, srcpeer, heads=revs)
350 350
351 351 return srcpeer, peer(ui, peeropts, dest)
352 352
353 353 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
354 354 update=True, stream=False, branch=None, shareopts=None):
355 355 """Make a copy of an existing repository.
356 356
357 357 Create a copy of an existing repository in a new directory. The
358 358 source and destination are URLs, as passed to the repository
359 359 function. Returns a pair of repository peers, the source and
360 360 newly created destination.
361 361
362 362 The location of the source is added to the new repository's
363 363 .hg/hgrc file, as the default to be used for future pulls and
364 364 pushes.
365 365
366 366 If an exception is raised, the partly cloned/updated destination
367 367 repository will be deleted.
368 368
369 369 Arguments:
370 370
371 371 source: repository object or URL
372 372
373 373 dest: URL of destination repository to create (defaults to base
374 374 name of source repository)
375 375
376 376 pull: always pull from source repository, even in local case or if the
377 377 server prefers streaming
378 378
379 379 stream: stream raw data uncompressed from repository (fast over
380 380 LAN, slow over WAN)
381 381
382 382 rev: revision to clone up to (implies pull=True)
383 383
384 384 update: update working directory after clone completes, if
385 385 destination is local repository (True means update to default rev,
386 386 anything else is treated as a revision)
387 387
388 388 branch: branches to clone
389 389
390 390 shareopts: dict of options to control auto sharing behavior. The "pool" key
391 391 activates auto sharing mode and defines the directory for stores. The
392 392 "mode" key determines how to construct the directory name of the shared
393 393 repository. "identity" means the name is derived from the node of the first
394 394 changeset in the repository. "remote" means the name is derived from the
395 395 remote's path/URL. Defaults to "identity."
396 396 """
397 397
398 398 if isinstance(source, str):
399 399 origsource = ui.expandpath(source)
400 400 source, branch = parseurl(origsource, branch)
401 401 srcpeer = peer(ui, peeropts, source)
402 402 else:
403 403 srcpeer = source.peer() # in case we were called with a localrepo
404 404 branch = (None, branch or [])
405 405 origsource = source = srcpeer.url()
406 406 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
407 407
408 408 if dest is None:
409 409 dest = defaultdest(source)
410 410 if dest:
411 411 ui.status(_("destination directory: %s\n") % dest)
412 412 else:
413 413 dest = ui.expandpath(dest)
414 414
415 415 dest = util.urllocalpath(dest)
416 416 source = util.urllocalpath(source)
417 417
418 418 if not dest:
419 419 raise util.Abort(_("empty destination path is not valid"))
420 420
421 421 destvfs = scmutil.vfs(dest, expandpath=True)
422 422 if destvfs.lexists():
423 423 if not destvfs.isdir():
424 424 raise util.Abort(_("destination '%s' already exists") % dest)
425 425 elif destvfs.listdir():
426 426 raise util.Abort(_("destination '%s' is not empty") % dest)
427 427
428 428 shareopts = shareopts or {}
429 429 sharepool = shareopts.get('pool')
430 430 sharenamemode = shareopts.get('mode')
431 431 if sharepool and islocal(dest):
432 432 sharepath = None
433 433 if sharenamemode == 'identity':
434 434 # Resolve the name from the initial changeset in the remote
435 435 # repository. This returns nullid when the remote is empty. It
436 436 # raises RepoLookupError if revision 0 is filtered or otherwise
437 437 # not available. If we fail to resolve, sharing is not enabled.
438 438 try:
439 439 rootnode = srcpeer.lookup('0')
440 440 if rootnode != node.nullid:
441 441 sharepath = os.path.join(sharepool, node.hex(rootnode))
442 442 else:
443 443 ui.status(_('(not using pooled storage: '
444 444 'remote appears to be empty)\n'))
445 445 except error.RepoLookupError:
446 446 ui.status(_('(not using pooled storage: '
447 447 'unable to resolve identity of remote)\n'))
448 448 elif sharenamemode == 'remote':
449 449 sharepath = os.path.join(sharepool, util.sha1(source).hexdigest())
450 450 else:
451 451 raise util.Abort('unknown share naming mode: %s' % sharenamemode)
452 452
453 453 if sharepath:
454 454 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
455 455 dest, pull=pull, rev=rev, update=update,
456 456 stream=stream)
457 457
458 458 srclock = destlock = cleandir = None
459 459 srcrepo = srcpeer.local()
460 460 try:
461 461 abspath = origsource
462 462 if islocal(origsource):
463 463 abspath = os.path.abspath(util.urllocalpath(origsource))
464 464
465 465 if islocal(dest):
466 466 cleandir = dest
467 467
468 468 copy = False
469 469 if (srcrepo and srcrepo.cancopy() and islocal(dest)
470 470 and not phases.hassecret(srcrepo)):
471 471 copy = not pull and not rev
472 472
473 473 if copy:
474 474 try:
475 475 # we use a lock here because if we race with commit, we
476 476 # can end up with extra data in the cloned revlogs that's
477 477 # not pointed to by changesets, thus causing verify to
478 478 # fail
479 479 srclock = srcrepo.lock(wait=False)
480 480 except error.LockError:
481 481 copy = False
482 482
483 483 if copy:
484 484 srcrepo.hook('preoutgoing', throw=True, source='clone')
485 485 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
486 486 if not os.path.exists(dest):
487 487 os.mkdir(dest)
488 488 else:
489 489 # only clean up directories we create ourselves
490 490 cleandir = hgdir
491 491 try:
492 492 destpath = hgdir
493 493 util.makedir(destpath, notindexed=True)
494 494 except OSError as inst:
495 495 if inst.errno == errno.EEXIST:
496 496 cleandir = None
497 497 raise util.Abort(_("destination '%s' already exists")
498 498 % dest)
499 499 raise
500 500
501 501 destlock = copystore(ui, srcrepo, destpath)
502 502 # copy bookmarks over
503 503 srcbookmarks = srcrepo.join('bookmarks')
504 504 dstbookmarks = os.path.join(destpath, 'bookmarks')
505 505 if os.path.exists(srcbookmarks):
506 506 util.copyfile(srcbookmarks, dstbookmarks)
507 507
508 508 # Recomputing branch cache might be slow on big repos,
509 509 # so just copy it
510 510 def copybranchcache(fname):
511 511 srcbranchcache = srcrepo.join('cache/%s' % fname)
512 512 dstbranchcache = os.path.join(dstcachedir, fname)
513 513 if os.path.exists(srcbranchcache):
514 514 if not os.path.exists(dstcachedir):
515 515 os.mkdir(dstcachedir)
516 516 util.copyfile(srcbranchcache, dstbranchcache)
517 517
518 518 dstcachedir = os.path.join(destpath, 'cache')
519 519 # In local clones we're copying all nodes, not just served
520 520 # ones. Therefore copy all branch caches over.
521 521 copybranchcache('branch2')
522 522 for cachename in repoview.filtertable:
523 523 copybranchcache('branch2-%s' % cachename)
524 524
525 525 # we need to re-init the repo after manually copying the data
526 526 # into it
527 527 destpeer = peer(srcrepo, peeropts, dest)
528 528 srcrepo.hook('outgoing', source='clone',
529 529 node=node.hex(node.nullid))
530 530 else:
531 531 try:
532 532 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
533 533 # only pass ui when no srcrepo
534 534 except OSError as inst:
535 535 if inst.errno == errno.EEXIST:
536 536 cleandir = None
537 537 raise util.Abort(_("destination '%s' already exists")
538 538 % dest)
539 539 raise
540 540
541 541 revs = None
542 542 if rev:
543 543 if not srcpeer.capable('lookup'):
544 544 raise util.Abort(_("src repository does not support "
545 545 "revision lookup and so doesn't "
546 546 "support clone by revision"))
547 547 revs = [srcpeer.lookup(r) for r in rev]
548 548 checkout = revs[0]
549 549 if destpeer.local():
550 550 if not stream:
551 551 if pull:
552 552 stream = False
553 553 else:
554 554 stream = None
555 555 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
556 556 elif srcrepo:
557 557 exchange.push(srcrepo, destpeer, revs=revs,
558 558 bookmarks=srcrepo._bookmarks.keys())
559 559 else:
560 560 raise util.Abort(_("clone from remote to remote not supported"))
561 561
562 562 cleandir = None
563 563
564 564 destrepo = destpeer.local()
565 565 if destrepo:
566 566 template = uimod.samplehgrcs['cloned']
567 567 fp = destrepo.vfs("hgrc", "w", text=True)
568 568 u = util.url(abspath)
569 569 u.passwd = None
570 570 defaulturl = str(u)
571 571 fp.write(template % defaulturl)
572 572 fp.close()
573 573
574 574 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
575 575
576 576 if update:
577 577 if update is not True:
578 578 checkout = srcpeer.lookup(update)
579 579 uprev = None
580 580 status = None
581 581 if checkout is not None:
582 582 try:
583 583 uprev = destrepo.lookup(checkout)
584 584 except error.RepoLookupError:
585 try:
586 uprev = destrepo.lookup(update)
587 except error.RepoLookupError:
588 pass
585 if update is not True:
586 try:
587 uprev = destrepo.lookup(update)
588 except error.RepoLookupError:
589 pass
589 590 if uprev is None:
590 591 try:
591 592 uprev = destrepo._bookmarks['@']
592 593 update = '@'
593 594 bn = destrepo[uprev].branch()
594 595 if bn == 'default':
595 596 status = _("updating to bookmark @\n")
596 597 else:
597 598 status = (_("updating to bookmark @ on branch %s\n")
598 599 % bn)
599 600 except KeyError:
600 601 try:
601 602 uprev = destrepo.branchtip('default')
602 603 except error.RepoLookupError:
603 604 uprev = destrepo.lookup('tip')
604 605 if not status:
605 606 bn = destrepo[uprev].branch()
606 607 status = _("updating to branch %s\n") % bn
607 608 destrepo.ui.status(status)
608 609 _update(destrepo, uprev)
609 610 if update in destrepo._bookmarks:
610 611 bookmarks.activate(destrepo, update)
611 612 finally:
612 613 release(srclock, destlock)
613 614 if cleandir is not None:
614 615 shutil.rmtree(cleandir, True)
615 616 if srcpeer is not None:
616 617 srcpeer.close()
617 618 return srcpeer, destpeer
618 619
619 620 def _showstats(repo, stats):
620 621 repo.ui.status(_("%d files updated, %d files merged, "
621 622 "%d files removed, %d files unresolved\n") % stats)
622 623
623 624 def updaterepo(repo, node, overwrite):
624 625 """Update the working directory to node.
625 626
626 627 When overwrite is set, changes are clobbered, merged else
627 628
628 629 returns stats (see pydoc mercurial.merge.applyupdates)"""
629 630 return mergemod.update(repo, node, False, overwrite, None,
630 631 labels=['working copy', 'destination'])
631 632
632 633 def update(repo, node):
633 634 """update the working directory to node, merging linear changes"""
634 635 stats = updaterepo(repo, node, False)
635 636 _showstats(repo, stats)
636 637 if stats[3]:
637 638 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
638 639 return stats[3] > 0
639 640
640 641 # naming conflict in clone()
641 642 _update = update
642 643
643 644 def clean(repo, node, show_stats=True):
644 645 """forcibly switch the working directory to node, clobbering changes"""
645 646 stats = updaterepo(repo, node, True)
646 647 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
647 648 if show_stats:
648 649 _showstats(repo, stats)
649 650 return stats[3] > 0
650 651
651 652 def merge(repo, node, force=None, remind=True):
652 653 """Branch merge with node, resolving changes. Return true if any
653 654 unresolved conflicts."""
654 655 stats = mergemod.update(repo, node, True, force, False)
655 656 _showstats(repo, stats)
656 657 if stats[3]:
657 658 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
658 659 "or 'hg update -C .' to abandon\n"))
659 660 elif remind:
660 661 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
661 662 return stats[3] > 0
662 663
663 664 def _incoming(displaychlist, subreporecurse, ui, repo, source,
664 665 opts, buffered=False):
665 666 """
666 667 Helper for incoming / gincoming.
667 668 displaychlist gets called with
668 669 (remoterepo, incomingchangesetlist, displayer) parameters,
669 670 and is supposed to contain only code that can't be unified.
670 671 """
671 672 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
672 673 other = peer(repo, opts, source)
673 674 ui.status(_('comparing with %s\n') % util.hidepassword(source))
674 675 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
675 676
676 677 if revs:
677 678 revs = [other.lookup(rev) for rev in revs]
678 679 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
679 680 revs, opts["bundle"], opts["force"])
680 681 try:
681 682 if not chlist:
682 683 ui.status(_("no changes found\n"))
683 684 return subreporecurse()
684 685
685 686 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
686 687 displaychlist(other, chlist, displayer)
687 688 displayer.close()
688 689 finally:
689 690 cleanupfn()
690 691 subreporecurse()
691 692 return 0 # exit code is zero since we found incoming changes
692 693
693 694 def incoming(ui, repo, source, opts):
694 695 def subreporecurse():
695 696 ret = 1
696 697 if opts.get('subrepos'):
697 698 ctx = repo[None]
698 699 for subpath in sorted(ctx.substate):
699 700 sub = ctx.sub(subpath)
700 701 ret = min(ret, sub.incoming(ui, source, opts))
701 702 return ret
702 703
703 704 def display(other, chlist, displayer):
704 705 limit = cmdutil.loglimit(opts)
705 706 if opts.get('newest_first'):
706 707 chlist.reverse()
707 708 count = 0
708 709 for n in chlist:
709 710 if limit is not None and count >= limit:
710 711 break
711 712 parents = [p for p in other.changelog.parents(n) if p != nullid]
712 713 if opts.get('no_merges') and len(parents) == 2:
713 714 continue
714 715 count += 1
715 716 displayer.show(other[n])
716 717 return _incoming(display, subreporecurse, ui, repo, source, opts)
717 718
718 719 def _outgoing(ui, repo, dest, opts):
719 720 dest = ui.expandpath(dest or 'default-push', dest or 'default')
720 721 dest, branches = parseurl(dest, opts.get('branch'))
721 722 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
722 723 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
723 724 if revs:
724 725 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
725 726
726 727 other = peer(repo, opts, dest)
727 728 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
728 729 force=opts.get('force'))
729 730 o = outgoing.missing
730 731 if not o:
731 732 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
732 733 return o, other
733 734
734 735 def outgoing(ui, repo, dest, opts):
735 736 def recurse():
736 737 ret = 1
737 738 if opts.get('subrepos'):
738 739 ctx = repo[None]
739 740 for subpath in sorted(ctx.substate):
740 741 sub = ctx.sub(subpath)
741 742 ret = min(ret, sub.outgoing(ui, dest, opts))
742 743 return ret
743 744
744 745 limit = cmdutil.loglimit(opts)
745 746 o, other = _outgoing(ui, repo, dest, opts)
746 747 if not o:
747 748 cmdutil.outgoinghooks(ui, repo, other, opts, o)
748 749 return recurse()
749 750
750 751 if opts.get('newest_first'):
751 752 o.reverse()
752 753 displayer = cmdutil.show_changeset(ui, repo, opts)
753 754 count = 0
754 755 for n in o:
755 756 if limit is not None and count >= limit:
756 757 break
757 758 parents = [p for p in repo.changelog.parents(n) if p != nullid]
758 759 if opts.get('no_merges') and len(parents) == 2:
759 760 continue
760 761 count += 1
761 762 displayer.show(repo[n])
762 763 displayer.close()
763 764 cmdutil.outgoinghooks(ui, repo, other, opts, o)
764 765 recurse()
765 766 return 0 # exit code is zero since we found outgoing changes
766 767
767 768 def revert(repo, node, choose):
768 769 """revert changes to revision in node without updating dirstate"""
769 770 return mergemod.update(repo, node, False, True, choose)[3] > 0
770 771
771 772 def verify(repo):
772 773 """verify the consistency of a repository"""
773 774 ret = verifymod.verify(repo)
774 775
775 776 # Broken subrepo references in hidden csets don't seem worth worrying about,
776 777 # since they can't be pushed/pulled, and --hidden can be used if they are a
777 778 # concern.
778 779
779 780 # pathto() is needed for -R case
780 781 revs = repo.revs("filelog(%s)",
781 782 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
782 783
783 784 if revs:
784 785 repo.ui.status(_('checking subrepo links\n'))
785 786 for rev in revs:
786 787 ctx = repo[rev]
787 788 try:
788 789 for subpath in ctx.substate:
789 790 ret = ctx.sub(subpath).verify() or ret
790 791 except Exception:
791 792 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
792 793 node.short(ctx.node()))
793 794
794 795 return ret
795 796
796 797 def remoteui(src, opts):
797 798 'build a remote ui from ui or repo and opts'
798 799 if util.safehasattr(src, 'baseui'): # looks like a repository
799 800 dst = src.baseui.copy() # drop repo-specific config
800 801 src = src.ui # copy target options from repo
801 802 else: # assume it's a global ui object
802 803 dst = src.copy() # keep all global options
803 804
804 805 # copy ssh-specific options
805 806 for o in 'ssh', 'remotecmd':
806 807 v = opts.get(o) or src.config('ui', o)
807 808 if v:
808 809 dst.setconfig("ui", o, v, 'copied')
809 810
810 811 # copy bundle-specific options
811 812 r = src.config('bundle', 'mainreporoot')
812 813 if r:
813 814 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
814 815
815 816 # copy selected local settings to the remote ui
816 817 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
817 818 for key, val in src.configitems(sect):
818 819 dst.setconfig(sect, key, val, 'copied')
819 820 v = src.config('web', 'cacerts')
820 821 if v == '!':
821 822 dst.setconfig('web', 'cacerts', v, 'copied')
822 823 elif v:
823 824 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
824 825
825 826 return dst
826 827
827 828 # Files of interest
828 829 # Used to check if the repository has changed looking at mtime and size of
829 830 # theses files.
830 831 foi = [('spath', '00changelog.i'),
831 832 ('spath', 'phaseroots'), # ! phase can change content at the same size
832 833 ('spath', 'obsstore'),
833 834 ('path', 'bookmarks'), # ! bookmark can change content at the same size
834 835 ]
835 836
836 837 class cachedlocalrepo(object):
837 838 """Holds a localrepository that can be cached and reused."""
838 839
839 840 def __init__(self, repo):
840 841 """Create a new cached repo from an existing repo.
841 842
842 843 We assume the passed in repo was recently created. If the
843 844 repo has changed between when it was created and when it was
844 845 turned into a cache, it may not refresh properly.
845 846 """
846 847 assert isinstance(repo, localrepo.localrepository)
847 848 self._repo = repo
848 849 self._state, self.mtime = self._repostate()
849 850
850 851 def fetch(self):
851 852 """Refresh (if necessary) and return a repository.
852 853
853 854 If the cached instance is out of date, it will be recreated
854 855 automatically and returned.
855 856
856 857 Returns a tuple of the repo and a boolean indicating whether a new
857 858 repo instance was created.
858 859 """
859 860 # We compare the mtimes and sizes of some well-known files to
860 861 # determine if the repo changed. This is not precise, as mtimes
861 862 # are susceptible to clock skew and imprecise filesystems and
862 863 # file content can change while maintaining the same size.
863 864
864 865 state, mtime = self._repostate()
865 866 if state == self._state:
866 867 return self._repo, False
867 868
868 869 self._repo = repository(self._repo.baseui, self._repo.url())
869 870 self._state = state
870 871 self.mtime = mtime
871 872
872 873 return self._repo, True
873 874
874 875 def _repostate(self):
875 876 state = []
876 877 maxmtime = -1
877 878 for attr, fname in foi:
878 879 prefix = getattr(self._repo, attr)
879 880 p = os.path.join(prefix, fname)
880 881 try:
881 882 st = os.stat(p)
882 883 except OSError:
883 884 st = os.stat(prefix)
884 885 state.append((st.st_mtime, st.st_size))
885 886 maxmtime = max(maxmtime, st.st_mtime)
886 887
887 888 return tuple(state), maxmtime
888 889
889 890 def copy(self):
890 891 """Obtain a copy of this class instance.
891 892
892 893 A new localrepository instance is obtained. The new instance should be
893 894 completely independent of the original.
894 895 """
895 896 repo = repository(self._repo.baseui, self._repo.origroot)
896 897 c = cachedlocalrepo(repo)
897 898 c._state = self._state
898 899 c.mtime = self.mtime
899 900 return c
General Comments 0
You need to be logged in to leave comments. Login now