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