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