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