##// END OF EJS Templates
localrepo: use "vfs.rename()" instead of "util.rename()"
FUJIWARA Katsunori -
r18948:2f05fa16 default
parent child Browse files
Show More
@@ -1,2596 +1,2594 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 from node import hex, nullid, short
8 8 from i18n import _
9 9 import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
10 10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
11 11 import lock, transaction, store, encoding, base85
12 12 import scmutil, util, extensions, hook, error, revset
13 13 import match as matchmod
14 14 import merge as mergemod
15 15 import tags as tagsmod
16 16 from lock import release
17 17 import weakref, errno, os, time, inspect
18 18 import branchmap
19 19 propertycache = util.propertycache
20 20 filecache = scmutil.filecache
21 21
22 22 class repofilecache(filecache):
23 23 """All filecache usage on repo are done for logic that should be unfiltered
24 24 """
25 25
26 26 def __get__(self, repo, type=None):
27 27 return super(repofilecache, self).__get__(repo.unfiltered(), type)
28 28 def __set__(self, repo, value):
29 29 return super(repofilecache, self).__set__(repo.unfiltered(), value)
30 30 def __delete__(self, repo):
31 31 return super(repofilecache, self).__delete__(repo.unfiltered())
32 32
33 33 class storecache(repofilecache):
34 34 """filecache for files in the store"""
35 35 def join(self, obj, fname):
36 36 return obj.sjoin(fname)
37 37
38 38 class unfilteredpropertycache(propertycache):
39 39 """propertycache that apply to unfiltered repo only"""
40 40
41 41 def __get__(self, repo, type=None):
42 42 return super(unfilteredpropertycache, self).__get__(repo.unfiltered())
43 43
44 44 class filteredpropertycache(propertycache):
45 45 """propertycache that must take filtering in account"""
46 46
47 47 def cachevalue(self, obj, value):
48 48 object.__setattr__(obj, self.name, value)
49 49
50 50
51 51 def hasunfilteredcache(repo, name):
52 52 """check if a repo has an unfilteredpropertycache value for <name>"""
53 53 return name in vars(repo.unfiltered())
54 54
55 55 def unfilteredmethod(orig):
56 56 """decorate method that always need to be run on unfiltered version"""
57 57 def wrapper(repo, *args, **kwargs):
58 58 return orig(repo.unfiltered(), *args, **kwargs)
59 59 return wrapper
60 60
61 61 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
62 62 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
63 63
64 64 class localpeer(peer.peerrepository):
65 65 '''peer for a local repo; reflects only the most recent API'''
66 66
67 67 def __init__(self, repo, caps=MODERNCAPS):
68 68 peer.peerrepository.__init__(self)
69 69 self._repo = repo.filtered('served')
70 70 self.ui = repo.ui
71 71 self._caps = repo._restrictcapabilities(caps)
72 72 self.requirements = repo.requirements
73 73 self.supportedformats = repo.supportedformats
74 74
75 75 def close(self):
76 76 self._repo.close()
77 77
78 78 def _capabilities(self):
79 79 return self._caps
80 80
81 81 def local(self):
82 82 return self._repo
83 83
84 84 def canpush(self):
85 85 return True
86 86
87 87 def url(self):
88 88 return self._repo.url()
89 89
90 90 def lookup(self, key):
91 91 return self._repo.lookup(key)
92 92
93 93 def branchmap(self):
94 94 return self._repo.branchmap()
95 95
96 96 def heads(self):
97 97 return self._repo.heads()
98 98
99 99 def known(self, nodes):
100 100 return self._repo.known(nodes)
101 101
102 102 def getbundle(self, source, heads=None, common=None):
103 103 return self._repo.getbundle(source, heads=heads, common=common)
104 104
105 105 # TODO We might want to move the next two calls into legacypeer and add
106 106 # unbundle instead.
107 107
108 108 def lock(self):
109 109 return self._repo.lock()
110 110
111 111 def addchangegroup(self, cg, source, url):
112 112 return self._repo.addchangegroup(cg, source, url)
113 113
114 114 def pushkey(self, namespace, key, old, new):
115 115 return self._repo.pushkey(namespace, key, old, new)
116 116
117 117 def listkeys(self, namespace):
118 118 return self._repo.listkeys(namespace)
119 119
120 120 def debugwireargs(self, one, two, three=None, four=None, five=None):
121 121 '''used to test argument passing over the wire'''
122 122 return "%s %s %s %s %s" % (one, two, three, four, five)
123 123
124 124 class locallegacypeer(localpeer):
125 125 '''peer extension which implements legacy methods too; used for tests with
126 126 restricted capabilities'''
127 127
128 128 def __init__(self, repo):
129 129 localpeer.__init__(self, repo, caps=LEGACYCAPS)
130 130
131 131 def branches(self, nodes):
132 132 return self._repo.branches(nodes)
133 133
134 134 def between(self, pairs):
135 135 return self._repo.between(pairs)
136 136
137 137 def changegroup(self, basenodes, source):
138 138 return self._repo.changegroup(basenodes, source)
139 139
140 140 def changegroupsubset(self, bases, heads, source):
141 141 return self._repo.changegroupsubset(bases, heads, source)
142 142
143 143 class localrepository(object):
144 144
145 145 supportedformats = set(('revlogv1', 'generaldelta'))
146 146 supported = supportedformats | set(('store', 'fncache', 'shared',
147 147 'dotencode'))
148 148 openerreqs = set(('revlogv1', 'generaldelta'))
149 149 requirements = ['revlogv1']
150 150 filtername = None
151 151
152 152 def _baserequirements(self, create):
153 153 return self.requirements[:]
154 154
155 155 def __init__(self, baseui, path=None, create=False):
156 156 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
157 157 self.wopener = self.wvfs
158 158 self.root = self.wvfs.base
159 159 self.path = self.wvfs.join(".hg")
160 160 self.origroot = path
161 161 self.auditor = scmutil.pathauditor(self.root, self._checknested)
162 162 self.vfs = scmutil.vfs(self.path)
163 163 self.opener = self.vfs
164 164 self.baseui = baseui
165 165 self.ui = baseui.copy()
166 166 # A list of callback to shape the phase if no data were found.
167 167 # Callback are in the form: func(repo, roots) --> processed root.
168 168 # This list it to be filled by extension during repo setup
169 169 self._phasedefaults = []
170 170 try:
171 171 self.ui.readconfig(self.join("hgrc"), self.root)
172 172 extensions.loadall(self.ui)
173 173 except IOError:
174 174 pass
175 175
176 176 if not self.vfs.isdir():
177 177 if create:
178 178 if not self.wvfs.exists():
179 179 self.wvfs.makedirs()
180 180 self.vfs.makedir(notindexed=True)
181 181 requirements = self._baserequirements(create)
182 182 if self.ui.configbool('format', 'usestore', True):
183 183 self.vfs.mkdir("store")
184 184 requirements.append("store")
185 185 if self.ui.configbool('format', 'usefncache', True):
186 186 requirements.append("fncache")
187 187 if self.ui.configbool('format', 'dotencode', True):
188 188 requirements.append('dotencode')
189 189 # create an invalid changelog
190 190 self.vfs.append(
191 191 "00changelog.i",
192 192 '\0\0\0\2' # represents revlogv2
193 193 ' dummy changelog to prevent using the old repo layout'
194 194 )
195 195 if self.ui.configbool('format', 'generaldelta', False):
196 196 requirements.append("generaldelta")
197 197 requirements = set(requirements)
198 198 else:
199 199 raise error.RepoError(_("repository %s not found") % path)
200 200 elif create:
201 201 raise error.RepoError(_("repository %s already exists") % path)
202 202 else:
203 203 try:
204 204 requirements = scmutil.readrequires(self.vfs, self.supported)
205 205 except IOError, inst:
206 206 if inst.errno != errno.ENOENT:
207 207 raise
208 208 requirements = set()
209 209
210 210 self.sharedpath = self.path
211 211 try:
212 212 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
213 213 realpath=True)
214 214 s = vfs.base
215 215 if not vfs.exists():
216 216 raise error.RepoError(
217 217 _('.hg/sharedpath points to nonexistent directory %s') % s)
218 218 self.sharedpath = s
219 219 except IOError, inst:
220 220 if inst.errno != errno.ENOENT:
221 221 raise
222 222
223 223 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
224 224 self.spath = self.store.path
225 225 self.svfs = self.store.vfs
226 226 self.sopener = self.svfs
227 227 self.sjoin = self.store.join
228 228 self.vfs.createmode = self.store.createmode
229 229 self._applyrequirements(requirements)
230 230 if create:
231 231 self._writerequirements()
232 232
233 233
234 234 self._branchcaches = {}
235 235 self.filterpats = {}
236 236 self._datafilters = {}
237 237 self._transref = self._lockref = self._wlockref = None
238 238
239 239 # A cache for various files under .hg/ that tracks file changes,
240 240 # (used by the filecache decorator)
241 241 #
242 242 # Maps a property name to its util.filecacheentry
243 243 self._filecache = {}
244 244
245 245 # hold sets of revision to be filtered
246 246 # should be cleared when something might have changed the filter value:
247 247 # - new changesets,
248 248 # - phase change,
249 249 # - new obsolescence marker,
250 250 # - working directory parent change,
251 251 # - bookmark changes
252 252 self.filteredrevcache = {}
253 253
254 254 def close(self):
255 255 pass
256 256
257 257 def _restrictcapabilities(self, caps):
258 258 return caps
259 259
260 260 def _applyrequirements(self, requirements):
261 261 self.requirements = requirements
262 262 self.sopener.options = dict((r, 1) for r in requirements
263 263 if r in self.openerreqs)
264 264
265 265 def _writerequirements(self):
266 266 reqfile = self.opener("requires", "w")
267 267 for r in sorted(self.requirements):
268 268 reqfile.write("%s\n" % r)
269 269 reqfile.close()
270 270
271 271 def _checknested(self, path):
272 272 """Determine if path is a legal nested repository."""
273 273 if not path.startswith(self.root):
274 274 return False
275 275 subpath = path[len(self.root) + 1:]
276 276 normsubpath = util.pconvert(subpath)
277 277
278 278 # XXX: Checking against the current working copy is wrong in
279 279 # the sense that it can reject things like
280 280 #
281 281 # $ hg cat -r 10 sub/x.txt
282 282 #
283 283 # if sub/ is no longer a subrepository in the working copy
284 284 # parent revision.
285 285 #
286 286 # However, it can of course also allow things that would have
287 287 # been rejected before, such as the above cat command if sub/
288 288 # is a subrepository now, but was a normal directory before.
289 289 # The old path auditor would have rejected by mistake since it
290 290 # panics when it sees sub/.hg/.
291 291 #
292 292 # All in all, checking against the working copy seems sensible
293 293 # since we want to prevent access to nested repositories on
294 294 # the filesystem *now*.
295 295 ctx = self[None]
296 296 parts = util.splitpath(subpath)
297 297 while parts:
298 298 prefix = '/'.join(parts)
299 299 if prefix in ctx.substate:
300 300 if prefix == normsubpath:
301 301 return True
302 302 else:
303 303 sub = ctx.sub(prefix)
304 304 return sub.checknested(subpath[len(prefix) + 1:])
305 305 else:
306 306 parts.pop()
307 307 return False
308 308
309 309 def peer(self):
310 310 return localpeer(self) # not cached to avoid reference cycle
311 311
312 312 def unfiltered(self):
313 313 """Return unfiltered version of the repository
314 314
315 315 Intended to be overwritten by filtered repo."""
316 316 return self
317 317
318 318 def filtered(self, name):
319 319 """Return a filtered version of a repository"""
320 320 # build a new class with the mixin and the current class
321 321 # (possibly subclass of the repo)
322 322 class proxycls(repoview.repoview, self.unfiltered().__class__):
323 323 pass
324 324 return proxycls(self, name)
325 325
326 326 @repofilecache('bookmarks')
327 327 def _bookmarks(self):
328 328 return bookmarks.bmstore(self)
329 329
330 330 @repofilecache('bookmarks.current')
331 331 def _bookmarkcurrent(self):
332 332 return bookmarks.readcurrent(self)
333 333
334 334 def bookmarkheads(self, bookmark):
335 335 name = bookmark.split('@', 1)[0]
336 336 heads = []
337 337 for mark, n in self._bookmarks.iteritems():
338 338 if mark.split('@', 1)[0] == name:
339 339 heads.append(n)
340 340 return heads
341 341
342 342 @storecache('phaseroots')
343 343 def _phasecache(self):
344 344 return phases.phasecache(self, self._phasedefaults)
345 345
346 346 @storecache('obsstore')
347 347 def obsstore(self):
348 348 store = obsolete.obsstore(self.sopener)
349 349 if store and not obsolete._enabled:
350 350 # message is rare enough to not be translated
351 351 msg = 'obsolete feature not enabled but %i markers found!\n'
352 352 self.ui.warn(msg % len(list(store)))
353 353 return store
354 354
355 355 @storecache('00changelog.i')
356 356 def changelog(self):
357 357 c = changelog.changelog(self.sopener)
358 358 if 'HG_PENDING' in os.environ:
359 359 p = os.environ['HG_PENDING']
360 360 if p.startswith(self.root):
361 361 c.readpending('00changelog.i.a')
362 362 return c
363 363
364 364 @storecache('00manifest.i')
365 365 def manifest(self):
366 366 return manifest.manifest(self.sopener)
367 367
368 368 @repofilecache('dirstate')
369 369 def dirstate(self):
370 370 warned = [0]
371 371 def validate(node):
372 372 try:
373 373 self.changelog.rev(node)
374 374 return node
375 375 except error.LookupError:
376 376 if not warned[0]:
377 377 warned[0] = True
378 378 self.ui.warn(_("warning: ignoring unknown"
379 379 " working parent %s!\n") % short(node))
380 380 return nullid
381 381
382 382 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
383 383
384 384 def __getitem__(self, changeid):
385 385 if changeid is None:
386 386 return context.workingctx(self)
387 387 return context.changectx(self, changeid)
388 388
389 389 def __contains__(self, changeid):
390 390 try:
391 391 return bool(self.lookup(changeid))
392 392 except error.RepoLookupError:
393 393 return False
394 394
395 395 def __nonzero__(self):
396 396 return True
397 397
398 398 def __len__(self):
399 399 return len(self.changelog)
400 400
401 401 def __iter__(self):
402 402 return iter(self.changelog)
403 403
404 404 def revs(self, expr, *args):
405 405 '''Return a list of revisions matching the given revset'''
406 406 expr = revset.formatspec(expr, *args)
407 407 m = revset.match(None, expr)
408 408 return [r for r in m(self, list(self))]
409 409
410 410 def set(self, expr, *args):
411 411 '''
412 412 Yield a context for each matching revision, after doing arg
413 413 replacement via revset.formatspec
414 414 '''
415 415 for r in self.revs(expr, *args):
416 416 yield self[r]
417 417
418 418 def url(self):
419 419 return 'file:' + self.root
420 420
421 421 def hook(self, name, throw=False, **args):
422 422 return hook.hook(self.ui, self, name, throw, **args)
423 423
424 424 @unfilteredmethod
425 425 def _tag(self, names, node, message, local, user, date, extra={}):
426 426 if isinstance(names, str):
427 427 names = (names,)
428 428
429 429 branches = self.branchmap()
430 430 for name in names:
431 431 self.hook('pretag', throw=True, node=hex(node), tag=name,
432 432 local=local)
433 433 if name in branches:
434 434 self.ui.warn(_("warning: tag %s conflicts with existing"
435 435 " branch name\n") % name)
436 436
437 437 def writetags(fp, names, munge, prevtags):
438 438 fp.seek(0, 2)
439 439 if prevtags and prevtags[-1] != '\n':
440 440 fp.write('\n')
441 441 for name in names:
442 442 m = munge and munge(name) or name
443 443 if (self._tagscache.tagtypes and
444 444 name in self._tagscache.tagtypes):
445 445 old = self.tags().get(name, nullid)
446 446 fp.write('%s %s\n' % (hex(old), m))
447 447 fp.write('%s %s\n' % (hex(node), m))
448 448 fp.close()
449 449
450 450 prevtags = ''
451 451 if local:
452 452 try:
453 453 fp = self.opener('localtags', 'r+')
454 454 except IOError:
455 455 fp = self.opener('localtags', 'a')
456 456 else:
457 457 prevtags = fp.read()
458 458
459 459 # local tags are stored in the current charset
460 460 writetags(fp, names, None, prevtags)
461 461 for name in names:
462 462 self.hook('tag', node=hex(node), tag=name, local=local)
463 463 return
464 464
465 465 try:
466 466 fp = self.wfile('.hgtags', 'rb+')
467 467 except IOError, e:
468 468 if e.errno != errno.ENOENT:
469 469 raise
470 470 fp = self.wfile('.hgtags', 'ab')
471 471 else:
472 472 prevtags = fp.read()
473 473
474 474 # committed tags are stored in UTF-8
475 475 writetags(fp, names, encoding.fromlocal, prevtags)
476 476
477 477 fp.close()
478 478
479 479 self.invalidatecaches()
480 480
481 481 if '.hgtags' not in self.dirstate:
482 482 self[None].add(['.hgtags'])
483 483
484 484 m = matchmod.exact(self.root, '', ['.hgtags'])
485 485 tagnode = self.commit(message, user, date, extra=extra, match=m)
486 486
487 487 for name in names:
488 488 self.hook('tag', node=hex(node), tag=name, local=local)
489 489
490 490 return tagnode
491 491
492 492 def tag(self, names, node, message, local, user, date):
493 493 '''tag a revision with one or more symbolic names.
494 494
495 495 names is a list of strings or, when adding a single tag, names may be a
496 496 string.
497 497
498 498 if local is True, the tags are stored in a per-repository file.
499 499 otherwise, they are stored in the .hgtags file, and a new
500 500 changeset is committed with the change.
501 501
502 502 keyword arguments:
503 503
504 504 local: whether to store tags in non-version-controlled file
505 505 (default False)
506 506
507 507 message: commit message to use if committing
508 508
509 509 user: name of user to use if committing
510 510
511 511 date: date tuple to use if committing'''
512 512
513 513 if not local:
514 514 for x in self.status()[:5]:
515 515 if '.hgtags' in x:
516 516 raise util.Abort(_('working copy of .hgtags is changed '
517 517 '(please commit .hgtags manually)'))
518 518
519 519 self.tags() # instantiate the cache
520 520 self._tag(names, node, message, local, user, date)
521 521
522 522 @filteredpropertycache
523 523 def _tagscache(self):
524 524 '''Returns a tagscache object that contains various tags related
525 525 caches.'''
526 526
527 527 # This simplifies its cache management by having one decorated
528 528 # function (this one) and the rest simply fetch things from it.
529 529 class tagscache(object):
530 530 def __init__(self):
531 531 # These two define the set of tags for this repository. tags
532 532 # maps tag name to node; tagtypes maps tag name to 'global' or
533 533 # 'local'. (Global tags are defined by .hgtags across all
534 534 # heads, and local tags are defined in .hg/localtags.)
535 535 # They constitute the in-memory cache of tags.
536 536 self.tags = self.tagtypes = None
537 537
538 538 self.nodetagscache = self.tagslist = None
539 539
540 540 cache = tagscache()
541 541 cache.tags, cache.tagtypes = self._findtags()
542 542
543 543 return cache
544 544
545 545 def tags(self):
546 546 '''return a mapping of tag to node'''
547 547 t = {}
548 548 if self.changelog.filteredrevs:
549 549 tags, tt = self._findtags()
550 550 else:
551 551 tags = self._tagscache.tags
552 552 for k, v in tags.iteritems():
553 553 try:
554 554 # ignore tags to unknown nodes
555 555 self.changelog.rev(v)
556 556 t[k] = v
557 557 except (error.LookupError, ValueError):
558 558 pass
559 559 return t
560 560
561 561 def _findtags(self):
562 562 '''Do the hard work of finding tags. Return a pair of dicts
563 563 (tags, tagtypes) where tags maps tag name to node, and tagtypes
564 564 maps tag name to a string like \'global\' or \'local\'.
565 565 Subclasses or extensions are free to add their own tags, but
566 566 should be aware that the returned dicts will be retained for the
567 567 duration of the localrepo object.'''
568 568
569 569 # XXX what tagtype should subclasses/extensions use? Currently
570 570 # mq and bookmarks add tags, but do not set the tagtype at all.
571 571 # Should each extension invent its own tag type? Should there
572 572 # be one tagtype for all such "virtual" tags? Or is the status
573 573 # quo fine?
574 574
575 575 alltags = {} # map tag name to (node, hist)
576 576 tagtypes = {}
577 577
578 578 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
579 579 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
580 580
581 581 # Build the return dicts. Have to re-encode tag names because
582 582 # the tags module always uses UTF-8 (in order not to lose info
583 583 # writing to the cache), but the rest of Mercurial wants them in
584 584 # local encoding.
585 585 tags = {}
586 586 for (name, (node, hist)) in alltags.iteritems():
587 587 if node != nullid:
588 588 tags[encoding.tolocal(name)] = node
589 589 tags['tip'] = self.changelog.tip()
590 590 tagtypes = dict([(encoding.tolocal(name), value)
591 591 for (name, value) in tagtypes.iteritems()])
592 592 return (tags, tagtypes)
593 593
594 594 def tagtype(self, tagname):
595 595 '''
596 596 return the type of the given tag. result can be:
597 597
598 598 'local' : a local tag
599 599 'global' : a global tag
600 600 None : tag does not exist
601 601 '''
602 602
603 603 return self._tagscache.tagtypes.get(tagname)
604 604
605 605 def tagslist(self):
606 606 '''return a list of tags ordered by revision'''
607 607 if not self._tagscache.tagslist:
608 608 l = []
609 609 for t, n in self.tags().iteritems():
610 610 r = self.changelog.rev(n)
611 611 l.append((r, t, n))
612 612 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
613 613
614 614 return self._tagscache.tagslist
615 615
616 616 def nodetags(self, node):
617 617 '''return the tags associated with a node'''
618 618 if not self._tagscache.nodetagscache:
619 619 nodetagscache = {}
620 620 for t, n in self._tagscache.tags.iteritems():
621 621 nodetagscache.setdefault(n, []).append(t)
622 622 for tags in nodetagscache.itervalues():
623 623 tags.sort()
624 624 self._tagscache.nodetagscache = nodetagscache
625 625 return self._tagscache.nodetagscache.get(node, [])
626 626
627 627 def nodebookmarks(self, node):
628 628 marks = []
629 629 for bookmark, n in self._bookmarks.iteritems():
630 630 if n == node:
631 631 marks.append(bookmark)
632 632 return sorted(marks)
633 633
634 634 def branchmap(self):
635 635 '''returns a dictionary {branch: [branchheads]}'''
636 636 branchmap.updatecache(self)
637 637 return self._branchcaches[self.filtername]
638 638
639 639
640 640 def _branchtip(self, heads):
641 641 '''return the tipmost branch head in heads'''
642 642 tip = heads[-1]
643 643 for h in reversed(heads):
644 644 if not self[h].closesbranch():
645 645 tip = h
646 646 break
647 647 return tip
648 648
649 649 def branchtip(self, branch):
650 650 '''return the tip node for a given branch'''
651 651 if branch not in self.branchmap():
652 652 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
653 653 return self._branchtip(self.branchmap()[branch])
654 654
655 655 def branchtags(self):
656 656 '''return a dict where branch names map to the tipmost head of
657 657 the branch, open heads come before closed'''
658 658 bt = {}
659 659 for bn, heads in self.branchmap().iteritems():
660 660 bt[bn] = self._branchtip(heads)
661 661 return bt
662 662
663 663 def lookup(self, key):
664 664 return self[key].node()
665 665
666 666 def lookupbranch(self, key, remote=None):
667 667 repo = remote or self
668 668 if key in repo.branchmap():
669 669 return key
670 670
671 671 repo = (remote and remote.local()) and remote or self
672 672 return repo[key].branch()
673 673
674 674 def known(self, nodes):
675 675 nm = self.changelog.nodemap
676 676 pc = self._phasecache
677 677 result = []
678 678 for n in nodes:
679 679 r = nm.get(n)
680 680 resp = not (r is None or pc.phase(self, r) >= phases.secret)
681 681 result.append(resp)
682 682 return result
683 683
684 684 def local(self):
685 685 return self
686 686
687 687 def cancopy(self):
688 688 return self.local() # so statichttprepo's override of local() works
689 689
690 690 def join(self, f):
691 691 return os.path.join(self.path, f)
692 692
693 693 def wjoin(self, f):
694 694 return os.path.join(self.root, f)
695 695
696 696 def file(self, f):
697 697 if f[0] == '/':
698 698 f = f[1:]
699 699 return filelog.filelog(self.sopener, f)
700 700
701 701 def changectx(self, changeid):
702 702 return self[changeid]
703 703
704 704 def parents(self, changeid=None):
705 705 '''get list of changectxs for parents of changeid'''
706 706 return self[changeid].parents()
707 707
708 708 def setparents(self, p1, p2=nullid):
709 709 copies = self.dirstate.setparents(p1, p2)
710 710 pctx = self[p1]
711 711 if copies:
712 712 # Adjust copy records, the dirstate cannot do it, it
713 713 # requires access to parents manifests. Preserve them
714 714 # only for entries added to first parent.
715 715 for f in copies:
716 716 if f not in pctx and copies[f] in pctx:
717 717 self.dirstate.copy(copies[f], f)
718 718 if p2 == nullid:
719 719 for f, s in sorted(self.dirstate.copies().items()):
720 720 if f not in pctx and s not in pctx:
721 721 self.dirstate.copy(None, f)
722 722
723 723 def filectx(self, path, changeid=None, fileid=None):
724 724 """changeid can be a changeset revision, node, or tag.
725 725 fileid can be a file revision or node."""
726 726 return context.filectx(self, path, changeid, fileid)
727 727
728 728 def getcwd(self):
729 729 return self.dirstate.getcwd()
730 730
731 731 def pathto(self, f, cwd=None):
732 732 return self.dirstate.pathto(f, cwd)
733 733
734 734 def wfile(self, f, mode='r'):
735 735 return self.wopener(f, mode)
736 736
737 737 def _link(self, f):
738 738 return os.path.islink(self.wjoin(f))
739 739
740 740 def _loadfilter(self, filter):
741 741 if filter not in self.filterpats:
742 742 l = []
743 743 for pat, cmd in self.ui.configitems(filter):
744 744 if cmd == '!':
745 745 continue
746 746 mf = matchmod.match(self.root, '', [pat])
747 747 fn = None
748 748 params = cmd
749 749 for name, filterfn in self._datafilters.iteritems():
750 750 if cmd.startswith(name):
751 751 fn = filterfn
752 752 params = cmd[len(name):].lstrip()
753 753 break
754 754 if not fn:
755 755 fn = lambda s, c, **kwargs: util.filter(s, c)
756 756 # Wrap old filters not supporting keyword arguments
757 757 if not inspect.getargspec(fn)[2]:
758 758 oldfn = fn
759 759 fn = lambda s, c, **kwargs: oldfn(s, c)
760 760 l.append((mf, fn, params))
761 761 self.filterpats[filter] = l
762 762 return self.filterpats[filter]
763 763
764 764 def _filter(self, filterpats, filename, data):
765 765 for mf, fn, cmd in filterpats:
766 766 if mf(filename):
767 767 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
768 768 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
769 769 break
770 770
771 771 return data
772 772
773 773 @unfilteredpropertycache
774 774 def _encodefilterpats(self):
775 775 return self._loadfilter('encode')
776 776
777 777 @unfilteredpropertycache
778 778 def _decodefilterpats(self):
779 779 return self._loadfilter('decode')
780 780
781 781 def adddatafilter(self, name, filter):
782 782 self._datafilters[name] = filter
783 783
784 784 def wread(self, filename):
785 785 if self._link(filename):
786 786 data = os.readlink(self.wjoin(filename))
787 787 else:
788 788 data = self.wopener.read(filename)
789 789 return self._filter(self._encodefilterpats, filename, data)
790 790
791 791 def wwrite(self, filename, data, flags):
792 792 data = self._filter(self._decodefilterpats, filename, data)
793 793 if 'l' in flags:
794 794 self.wopener.symlink(data, filename)
795 795 else:
796 796 self.wopener.write(filename, data)
797 797 if 'x' in flags:
798 798 util.setflags(self.wjoin(filename), False, True)
799 799
800 800 def wwritedata(self, filename, data):
801 801 return self._filter(self._decodefilterpats, filename, data)
802 802
803 803 def transaction(self, desc):
804 804 tr = self._transref and self._transref() or None
805 805 if tr and tr.running():
806 806 return tr.nest()
807 807
808 808 # abort here if the journal already exists
809 809 if self.svfs.exists("journal"):
810 810 raise error.RepoError(
811 811 _("abandoned transaction found - run hg recover"))
812 812
813 813 self._writejournal(desc)
814 814 renames = [(x, undoname(x)) for x in self._journalfiles()]
815 815
816 816 tr = transaction.transaction(self.ui.warn, self.sopener,
817 817 self.sjoin("journal"),
818 818 aftertrans(renames),
819 819 self.store.createmode)
820 820 self._transref = weakref.ref(tr)
821 821 return tr
822 822
823 823 def _journalfiles(self):
824 824 return (self.sjoin('journal'), self.join('journal.dirstate'),
825 825 self.join('journal.branch'), self.join('journal.desc'),
826 826 self.join('journal.bookmarks'),
827 827 self.sjoin('journal.phaseroots'))
828 828
829 829 def undofiles(self):
830 830 return [undoname(x) for x in self._journalfiles()]
831 831
832 832 def _writejournal(self, desc):
833 833 self.opener.write("journal.dirstate",
834 834 self.opener.tryread("dirstate"))
835 835 self.opener.write("journal.branch",
836 836 encoding.fromlocal(self.dirstate.branch()))
837 837 self.opener.write("journal.desc",
838 838 "%d\n%s\n" % (len(self), desc))
839 839 self.opener.write("journal.bookmarks",
840 840 self.opener.tryread("bookmarks"))
841 841 self.sopener.write("journal.phaseroots",
842 842 self.sopener.tryread("phaseroots"))
843 843
844 844 def recover(self):
845 845 lock = self.lock()
846 846 try:
847 847 if self.svfs.exists("journal"):
848 848 self.ui.status(_("rolling back interrupted transaction\n"))
849 849 transaction.rollback(self.sopener, self.sjoin("journal"),
850 850 self.ui.warn)
851 851 self.invalidate()
852 852 return True
853 853 else:
854 854 self.ui.warn(_("no interrupted transaction available\n"))
855 855 return False
856 856 finally:
857 857 lock.release()
858 858
859 859 def rollback(self, dryrun=False, force=False):
860 860 wlock = lock = None
861 861 try:
862 862 wlock = self.wlock()
863 863 lock = self.lock()
864 864 if self.svfs.exists("undo"):
865 865 return self._rollback(dryrun, force)
866 866 else:
867 867 self.ui.warn(_("no rollback information available\n"))
868 868 return 1
869 869 finally:
870 870 release(lock, wlock)
871 871
872 872 @unfilteredmethod # Until we get smarter cache management
873 873 def _rollback(self, dryrun, force):
874 874 ui = self.ui
875 875 try:
876 876 args = self.opener.read('undo.desc').splitlines()
877 877 (oldlen, desc, detail) = (int(args[0]), args[1], None)
878 878 if len(args) >= 3:
879 879 detail = args[2]
880 880 oldtip = oldlen - 1
881 881
882 882 if detail and ui.verbose:
883 883 msg = (_('repository tip rolled back to revision %s'
884 884 ' (undo %s: %s)\n')
885 885 % (oldtip, desc, detail))
886 886 else:
887 887 msg = (_('repository tip rolled back to revision %s'
888 888 ' (undo %s)\n')
889 889 % (oldtip, desc))
890 890 except IOError:
891 891 msg = _('rolling back unknown transaction\n')
892 892 desc = None
893 893
894 894 if not force and self['.'] != self['tip'] and desc == 'commit':
895 895 raise util.Abort(
896 896 _('rollback of last commit while not checked out '
897 897 'may lose data'), hint=_('use -f to force'))
898 898
899 899 ui.status(msg)
900 900 if dryrun:
901 901 return 0
902 902
903 903 parents = self.dirstate.parents()
904 904 self.destroying()
905 905 transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
906 906 if self.vfs.exists('undo.bookmarks'):
907 util.rename(self.join('undo.bookmarks'),
908 self.join('bookmarks'))
907 self.vfs.rename('undo.bookmarks', 'bookmarks')
909 908 if self.svfs.exists('undo.phaseroots'):
910 util.rename(self.sjoin('undo.phaseroots'),
911 self.sjoin('phaseroots'))
909 self.svfs.rename('undo.phaseroots', 'phaseroots')
912 910 self.invalidate()
913 911
914 912 parentgone = (parents[0] not in self.changelog.nodemap or
915 913 parents[1] not in self.changelog.nodemap)
916 914 if parentgone:
917 util.rename(self.join('undo.dirstate'), self.join('dirstate'))
915 self.vfs.rename('undo.dirstate', 'dirstate')
918 916 try:
919 917 branch = self.opener.read('undo.branch')
920 918 self.dirstate.setbranch(encoding.tolocal(branch))
921 919 except IOError:
922 920 ui.warn(_('named branch could not be reset: '
923 921 'current branch is still \'%s\'\n')
924 922 % self.dirstate.branch())
925 923
926 924 self.dirstate.invalidate()
927 925 parents = tuple([p.rev() for p in self.parents()])
928 926 if len(parents) > 1:
929 927 ui.status(_('working directory now based on '
930 928 'revisions %d and %d\n') % parents)
931 929 else:
932 930 ui.status(_('working directory now based on '
933 931 'revision %d\n') % parents)
934 932 # TODO: if we know which new heads may result from this rollback, pass
935 933 # them to destroy(), which will prevent the branchhead cache from being
936 934 # invalidated.
937 935 self.destroyed()
938 936 return 0
939 937
940 938 def invalidatecaches(self):
941 939
942 940 if '_tagscache' in vars(self):
943 941 # can't use delattr on proxy
944 942 del self.__dict__['_tagscache']
945 943
946 944 self.unfiltered()._branchcaches.clear()
947 945 self.invalidatevolatilesets()
948 946
949 947 def invalidatevolatilesets(self):
950 948 self.filteredrevcache.clear()
951 949 obsolete.clearobscaches(self)
952 950
953 951 def invalidatedirstate(self):
954 952 '''Invalidates the dirstate, causing the next call to dirstate
955 953 to check if it was modified since the last time it was read,
956 954 rereading it if it has.
957 955
958 956 This is different to dirstate.invalidate() that it doesn't always
959 957 rereads the dirstate. Use dirstate.invalidate() if you want to
960 958 explicitly read the dirstate again (i.e. restoring it to a previous
961 959 known good state).'''
962 960 if hasunfilteredcache(self, 'dirstate'):
963 961 for k in self.dirstate._filecache:
964 962 try:
965 963 delattr(self.dirstate, k)
966 964 except AttributeError:
967 965 pass
968 966 delattr(self.unfiltered(), 'dirstate')
969 967
970 968 def invalidate(self):
971 969 unfiltered = self.unfiltered() # all file caches are stored unfiltered
972 970 for k in self._filecache:
973 971 # dirstate is invalidated separately in invalidatedirstate()
974 972 if k == 'dirstate':
975 973 continue
976 974
977 975 try:
978 976 delattr(unfiltered, k)
979 977 except AttributeError:
980 978 pass
981 979 self.invalidatecaches()
982 980
983 981 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
984 982 try:
985 983 l = lock.lock(lockname, 0, releasefn, desc=desc)
986 984 except error.LockHeld, inst:
987 985 if not wait:
988 986 raise
989 987 self.ui.warn(_("waiting for lock on %s held by %r\n") %
990 988 (desc, inst.locker))
991 989 # default to 600 seconds timeout
992 990 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
993 991 releasefn, desc=desc)
994 992 if acquirefn:
995 993 acquirefn()
996 994 return l
997 995
998 996 def _afterlock(self, callback):
999 997 """add a callback to the current repository lock.
1000 998
1001 999 The callback will be executed on lock release."""
1002 1000 l = self._lockref and self._lockref()
1003 1001 if l:
1004 1002 l.postrelease.append(callback)
1005 1003 else:
1006 1004 callback()
1007 1005
1008 1006 def lock(self, wait=True):
1009 1007 '''Lock the repository store (.hg/store) and return a weak reference
1010 1008 to the lock. Use this before modifying the store (e.g. committing or
1011 1009 stripping). If you are opening a transaction, get a lock as well.)'''
1012 1010 l = self._lockref and self._lockref()
1013 1011 if l is not None and l.held:
1014 1012 l.lock()
1015 1013 return l
1016 1014
1017 1015 def unlock():
1018 1016 self.store.write()
1019 1017 if hasunfilteredcache(self, '_phasecache'):
1020 1018 self._phasecache.write()
1021 1019 for k, ce in self._filecache.items():
1022 1020 if k == 'dirstate' or k not in self.__dict__:
1023 1021 continue
1024 1022 ce.refresh()
1025 1023
1026 1024 l = self._lock(self.sjoin("lock"), wait, unlock,
1027 1025 self.invalidate, _('repository %s') % self.origroot)
1028 1026 self._lockref = weakref.ref(l)
1029 1027 return l
1030 1028
1031 1029 def wlock(self, wait=True):
1032 1030 '''Lock the non-store parts of the repository (everything under
1033 1031 .hg except .hg/store) and return a weak reference to the lock.
1034 1032 Use this before modifying files in .hg.'''
1035 1033 l = self._wlockref and self._wlockref()
1036 1034 if l is not None and l.held:
1037 1035 l.lock()
1038 1036 return l
1039 1037
1040 1038 def unlock():
1041 1039 self.dirstate.write()
1042 1040 self._filecache['dirstate'].refresh()
1043 1041
1044 1042 l = self._lock(self.join("wlock"), wait, unlock,
1045 1043 self.invalidatedirstate, _('working directory of %s') %
1046 1044 self.origroot)
1047 1045 self._wlockref = weakref.ref(l)
1048 1046 return l
1049 1047
1050 1048 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1051 1049 """
1052 1050 commit an individual file as part of a larger transaction
1053 1051 """
1054 1052
1055 1053 fname = fctx.path()
1056 1054 text = fctx.data()
1057 1055 flog = self.file(fname)
1058 1056 fparent1 = manifest1.get(fname, nullid)
1059 1057 fparent2 = fparent2o = manifest2.get(fname, nullid)
1060 1058
1061 1059 meta = {}
1062 1060 copy = fctx.renamed()
1063 1061 if copy and copy[0] != fname:
1064 1062 # Mark the new revision of this file as a copy of another
1065 1063 # file. This copy data will effectively act as a parent
1066 1064 # of this new revision. If this is a merge, the first
1067 1065 # parent will be the nullid (meaning "look up the copy data")
1068 1066 # and the second one will be the other parent. For example:
1069 1067 #
1070 1068 # 0 --- 1 --- 3 rev1 changes file foo
1071 1069 # \ / rev2 renames foo to bar and changes it
1072 1070 # \- 2 -/ rev3 should have bar with all changes and
1073 1071 # should record that bar descends from
1074 1072 # bar in rev2 and foo in rev1
1075 1073 #
1076 1074 # this allows this merge to succeed:
1077 1075 #
1078 1076 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1079 1077 # \ / merging rev3 and rev4 should use bar@rev2
1080 1078 # \- 2 --- 4 as the merge base
1081 1079 #
1082 1080
1083 1081 cfname = copy[0]
1084 1082 crev = manifest1.get(cfname)
1085 1083 newfparent = fparent2
1086 1084
1087 1085 if manifest2: # branch merge
1088 1086 if fparent2 == nullid or crev is None: # copied on remote side
1089 1087 if cfname in manifest2:
1090 1088 crev = manifest2[cfname]
1091 1089 newfparent = fparent1
1092 1090
1093 1091 # find source in nearest ancestor if we've lost track
1094 1092 if not crev:
1095 1093 self.ui.debug(" %s: searching for copy revision for %s\n" %
1096 1094 (fname, cfname))
1097 1095 for ancestor in self[None].ancestors():
1098 1096 if cfname in ancestor:
1099 1097 crev = ancestor[cfname].filenode()
1100 1098 break
1101 1099
1102 1100 if crev:
1103 1101 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1104 1102 meta["copy"] = cfname
1105 1103 meta["copyrev"] = hex(crev)
1106 1104 fparent1, fparent2 = nullid, newfparent
1107 1105 else:
1108 1106 self.ui.warn(_("warning: can't find ancestor for '%s' "
1109 1107 "copied from '%s'!\n") % (fname, cfname))
1110 1108
1111 1109 elif fparent2 != nullid:
1112 1110 # is one parent an ancestor of the other?
1113 1111 fparentancestor = flog.ancestor(fparent1, fparent2)
1114 1112 if fparentancestor == fparent1:
1115 1113 fparent1, fparent2 = fparent2, nullid
1116 1114 elif fparentancestor == fparent2:
1117 1115 fparent2 = nullid
1118 1116
1119 1117 # is the file changed?
1120 1118 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1121 1119 changelist.append(fname)
1122 1120 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1123 1121
1124 1122 # are just the flags changed during merge?
1125 1123 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1126 1124 changelist.append(fname)
1127 1125
1128 1126 return fparent1
1129 1127
1130 1128 @unfilteredmethod
1131 1129 def commit(self, text="", user=None, date=None, match=None, force=False,
1132 1130 editor=False, extra={}):
1133 1131 """Add a new revision to current repository.
1134 1132
1135 1133 Revision information is gathered from the working directory,
1136 1134 match can be used to filter the committed files. If editor is
1137 1135 supplied, it is called to get a commit message.
1138 1136 """
1139 1137
1140 1138 def fail(f, msg):
1141 1139 raise util.Abort('%s: %s' % (f, msg))
1142 1140
1143 1141 if not match:
1144 1142 match = matchmod.always(self.root, '')
1145 1143
1146 1144 if not force:
1147 1145 vdirs = []
1148 1146 match.dir = vdirs.append
1149 1147 match.bad = fail
1150 1148
1151 1149 wlock = self.wlock()
1152 1150 try:
1153 1151 wctx = self[None]
1154 1152 merge = len(wctx.parents()) > 1
1155 1153
1156 1154 if (not force and merge and match and
1157 1155 (match.files() or match.anypats())):
1158 1156 raise util.Abort(_('cannot partially commit a merge '
1159 1157 '(do not specify files or patterns)'))
1160 1158
1161 1159 changes = self.status(match=match, clean=force)
1162 1160 if force:
1163 1161 changes[0].extend(changes[6]) # mq may commit unchanged files
1164 1162
1165 1163 # check subrepos
1166 1164 subs = []
1167 1165 commitsubs = set()
1168 1166 newstate = wctx.substate.copy()
1169 1167 # only manage subrepos and .hgsubstate if .hgsub is present
1170 1168 if '.hgsub' in wctx:
1171 1169 # we'll decide whether to track this ourselves, thanks
1172 1170 if '.hgsubstate' in changes[0]:
1173 1171 changes[0].remove('.hgsubstate')
1174 1172 if '.hgsubstate' in changes[2]:
1175 1173 changes[2].remove('.hgsubstate')
1176 1174
1177 1175 # compare current state to last committed state
1178 1176 # build new substate based on last committed state
1179 1177 oldstate = wctx.p1().substate
1180 1178 for s in sorted(newstate.keys()):
1181 1179 if not match(s):
1182 1180 # ignore working copy, use old state if present
1183 1181 if s in oldstate:
1184 1182 newstate[s] = oldstate[s]
1185 1183 continue
1186 1184 if not force:
1187 1185 raise util.Abort(
1188 1186 _("commit with new subrepo %s excluded") % s)
1189 1187 if wctx.sub(s).dirty(True):
1190 1188 if not self.ui.configbool('ui', 'commitsubrepos'):
1191 1189 raise util.Abort(
1192 1190 _("uncommitted changes in subrepo %s") % s,
1193 1191 hint=_("use --subrepos for recursive commit"))
1194 1192 subs.append(s)
1195 1193 commitsubs.add(s)
1196 1194 else:
1197 1195 bs = wctx.sub(s).basestate()
1198 1196 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1199 1197 if oldstate.get(s, (None, None, None))[1] != bs:
1200 1198 subs.append(s)
1201 1199
1202 1200 # check for removed subrepos
1203 1201 for p in wctx.parents():
1204 1202 r = [s for s in p.substate if s not in newstate]
1205 1203 subs += [s for s in r if match(s)]
1206 1204 if subs:
1207 1205 if (not match('.hgsub') and
1208 1206 '.hgsub' in (wctx.modified() + wctx.added())):
1209 1207 raise util.Abort(
1210 1208 _("can't commit subrepos without .hgsub"))
1211 1209 changes[0].insert(0, '.hgsubstate')
1212 1210
1213 1211 elif '.hgsub' in changes[2]:
1214 1212 # clean up .hgsubstate when .hgsub is removed
1215 1213 if ('.hgsubstate' in wctx and
1216 1214 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1217 1215 changes[2].insert(0, '.hgsubstate')
1218 1216
1219 1217 # make sure all explicit patterns are matched
1220 1218 if not force and match.files():
1221 1219 matched = set(changes[0] + changes[1] + changes[2])
1222 1220
1223 1221 for f in match.files():
1224 1222 f = self.dirstate.normalize(f)
1225 1223 if f == '.' or f in matched or f in wctx.substate:
1226 1224 continue
1227 1225 if f in changes[3]: # missing
1228 1226 fail(f, _('file not found!'))
1229 1227 if f in vdirs: # visited directory
1230 1228 d = f + '/'
1231 1229 for mf in matched:
1232 1230 if mf.startswith(d):
1233 1231 break
1234 1232 else:
1235 1233 fail(f, _("no match under directory!"))
1236 1234 elif f not in self.dirstate:
1237 1235 fail(f, _("file not tracked!"))
1238 1236
1239 1237 cctx = context.workingctx(self, text, user, date, extra, changes)
1240 1238
1241 1239 if (not force and not extra.get("close") and not merge
1242 1240 and not cctx.files()
1243 1241 and wctx.branch() == wctx.p1().branch()):
1244 1242 return None
1245 1243
1246 1244 if merge and cctx.deleted():
1247 1245 raise util.Abort(_("cannot commit merge with missing files"))
1248 1246
1249 1247 ms = mergemod.mergestate(self)
1250 1248 for f in changes[0]:
1251 1249 if f in ms and ms[f] == 'u':
1252 1250 raise util.Abort(_("unresolved merge conflicts "
1253 1251 "(see hg help resolve)"))
1254 1252
1255 1253 if editor:
1256 1254 cctx._text = editor(self, cctx, subs)
1257 1255 edited = (text != cctx._text)
1258 1256
1259 1257 # commit subs and write new state
1260 1258 if subs:
1261 1259 for s in sorted(commitsubs):
1262 1260 sub = wctx.sub(s)
1263 1261 self.ui.status(_('committing subrepository %s\n') %
1264 1262 subrepo.subrelpath(sub))
1265 1263 sr = sub.commit(cctx._text, user, date)
1266 1264 newstate[s] = (newstate[s][0], sr)
1267 1265 subrepo.writestate(self, newstate)
1268 1266
1269 1267 # Save commit message in case this transaction gets rolled back
1270 1268 # (e.g. by a pretxncommit hook). Leave the content alone on
1271 1269 # the assumption that the user will use the same editor again.
1272 1270 msgfn = self.savecommitmessage(cctx._text)
1273 1271
1274 1272 p1, p2 = self.dirstate.parents()
1275 1273 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1276 1274 try:
1277 1275 self.hook("precommit", throw=True, parent1=hookp1,
1278 1276 parent2=hookp2)
1279 1277 ret = self.commitctx(cctx, True)
1280 1278 except: # re-raises
1281 1279 if edited:
1282 1280 self.ui.write(
1283 1281 _('note: commit message saved in %s\n') % msgfn)
1284 1282 raise
1285 1283
1286 1284 # update bookmarks, dirstate and mergestate
1287 1285 bookmarks.update(self, [p1, p2], ret)
1288 1286 cctx.markcommitted(ret)
1289 1287 ms.reset()
1290 1288 finally:
1291 1289 wlock.release()
1292 1290
1293 1291 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1294 1292 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1295 1293 self._afterlock(commithook)
1296 1294 return ret
1297 1295
1298 1296 @unfilteredmethod
1299 1297 def commitctx(self, ctx, error=False):
1300 1298 """Add a new revision to current repository.
1301 1299 Revision information is passed via the context argument.
1302 1300 """
1303 1301
1304 1302 tr = lock = None
1305 1303 removed = list(ctx.removed())
1306 1304 p1, p2 = ctx.p1(), ctx.p2()
1307 1305 user = ctx.user()
1308 1306
1309 1307 lock = self.lock()
1310 1308 try:
1311 1309 tr = self.transaction("commit")
1312 1310 trp = weakref.proxy(tr)
1313 1311
1314 1312 if ctx.files():
1315 1313 m1 = p1.manifest().copy()
1316 1314 m2 = p2.manifest()
1317 1315
1318 1316 # check in files
1319 1317 new = {}
1320 1318 changed = []
1321 1319 linkrev = len(self)
1322 1320 for f in sorted(ctx.modified() + ctx.added()):
1323 1321 self.ui.note(f + "\n")
1324 1322 try:
1325 1323 fctx = ctx[f]
1326 1324 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1327 1325 changed)
1328 1326 m1.set(f, fctx.flags())
1329 1327 except OSError, inst:
1330 1328 self.ui.warn(_("trouble committing %s!\n") % f)
1331 1329 raise
1332 1330 except IOError, inst:
1333 1331 errcode = getattr(inst, 'errno', errno.ENOENT)
1334 1332 if error or errcode and errcode != errno.ENOENT:
1335 1333 self.ui.warn(_("trouble committing %s!\n") % f)
1336 1334 raise
1337 1335 else:
1338 1336 removed.append(f)
1339 1337
1340 1338 # update manifest
1341 1339 m1.update(new)
1342 1340 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1343 1341 drop = [f for f in removed if f in m1]
1344 1342 for f in drop:
1345 1343 del m1[f]
1346 1344 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1347 1345 p2.manifestnode(), (new, drop))
1348 1346 files = changed + removed
1349 1347 else:
1350 1348 mn = p1.manifestnode()
1351 1349 files = []
1352 1350
1353 1351 # update changelog
1354 1352 self.changelog.delayupdate()
1355 1353 n = self.changelog.add(mn, files, ctx.description(),
1356 1354 trp, p1.node(), p2.node(),
1357 1355 user, ctx.date(), ctx.extra().copy())
1358 1356 p = lambda: self.changelog.writepending() and self.root or ""
1359 1357 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1360 1358 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1361 1359 parent2=xp2, pending=p)
1362 1360 self.changelog.finalize(trp)
1363 1361 # set the new commit is proper phase
1364 1362 targetphase = phases.newcommitphase(self.ui)
1365 1363 if targetphase:
1366 1364 # retract boundary do not alter parent changeset.
1367 1365 # if a parent have higher the resulting phase will
1368 1366 # be compliant anyway
1369 1367 #
1370 1368 # if minimal phase was 0 we don't need to retract anything
1371 1369 phases.retractboundary(self, targetphase, [n])
1372 1370 tr.close()
1373 1371 branchmap.updatecache(self.filtered('served'))
1374 1372 return n
1375 1373 finally:
1376 1374 if tr:
1377 1375 tr.release()
1378 1376 lock.release()
1379 1377
1380 1378 @unfilteredmethod
1381 1379 def destroying(self):
1382 1380 '''Inform the repository that nodes are about to be destroyed.
1383 1381 Intended for use by strip and rollback, so there's a common
1384 1382 place for anything that has to be done before destroying history.
1385 1383
1386 1384 This is mostly useful for saving state that is in memory and waiting
1387 1385 to be flushed when the current lock is released. Because a call to
1388 1386 destroyed is imminent, the repo will be invalidated causing those
1389 1387 changes to stay in memory (waiting for the next unlock), or vanish
1390 1388 completely.
1391 1389 '''
1392 1390 # When using the same lock to commit and strip, the phasecache is left
1393 1391 # dirty after committing. Then when we strip, the repo is invalidated,
1394 1392 # causing those changes to disappear.
1395 1393 if '_phasecache' in vars(self):
1396 1394 self._phasecache.write()
1397 1395
1398 1396 @unfilteredmethod
1399 1397 def destroyed(self):
1400 1398 '''Inform the repository that nodes have been destroyed.
1401 1399 Intended for use by strip and rollback, so there's a common
1402 1400 place for anything that has to be done after destroying history.
1403 1401 '''
1404 1402 # When one tries to:
1405 1403 # 1) destroy nodes thus calling this method (e.g. strip)
1406 1404 # 2) use phasecache somewhere (e.g. commit)
1407 1405 #
1408 1406 # then 2) will fail because the phasecache contains nodes that were
1409 1407 # removed. We can either remove phasecache from the filecache,
1410 1408 # causing it to reload next time it is accessed, or simply filter
1411 1409 # the removed nodes now and write the updated cache.
1412 1410 self._phasecache.filterunknown(self)
1413 1411 self._phasecache.write()
1414 1412
1415 1413 # update the 'served' branch cache to help read only server process
1416 1414 # Thanks to branchcache collaboration this is done from the nearest
1417 1415 # filtered subset and it is expected to be fast.
1418 1416 branchmap.updatecache(self.filtered('served'))
1419 1417
1420 1418 # Ensure the persistent tag cache is updated. Doing it now
1421 1419 # means that the tag cache only has to worry about destroyed
1422 1420 # heads immediately after a strip/rollback. That in turn
1423 1421 # guarantees that "cachetip == currenttip" (comparing both rev
1424 1422 # and node) always means no nodes have been added or destroyed.
1425 1423
1426 1424 # XXX this is suboptimal when qrefresh'ing: we strip the current
1427 1425 # head, refresh the tag cache, then immediately add a new head.
1428 1426 # But I think doing it this way is necessary for the "instant
1429 1427 # tag cache retrieval" case to work.
1430 1428 self.invalidate()
1431 1429
1432 1430 def walk(self, match, node=None):
1433 1431 '''
1434 1432 walk recursively through the directory tree or a given
1435 1433 changeset, finding all files matched by the match
1436 1434 function
1437 1435 '''
1438 1436 return self[node].walk(match)
1439 1437
1440 1438 def status(self, node1='.', node2=None, match=None,
1441 1439 ignored=False, clean=False, unknown=False,
1442 1440 listsubrepos=False):
1443 1441 """return status of files between two nodes or node and working
1444 1442 directory.
1445 1443
1446 1444 If node1 is None, use the first dirstate parent instead.
1447 1445 If node2 is None, compare node1 with working directory.
1448 1446 """
1449 1447
1450 1448 def mfmatches(ctx):
1451 1449 mf = ctx.manifest().copy()
1452 1450 if match.always():
1453 1451 return mf
1454 1452 for fn in mf.keys():
1455 1453 if not match(fn):
1456 1454 del mf[fn]
1457 1455 return mf
1458 1456
1459 1457 if isinstance(node1, context.changectx):
1460 1458 ctx1 = node1
1461 1459 else:
1462 1460 ctx1 = self[node1]
1463 1461 if isinstance(node2, context.changectx):
1464 1462 ctx2 = node2
1465 1463 else:
1466 1464 ctx2 = self[node2]
1467 1465
1468 1466 working = ctx2.rev() is None
1469 1467 parentworking = working and ctx1 == self['.']
1470 1468 match = match or matchmod.always(self.root, self.getcwd())
1471 1469 listignored, listclean, listunknown = ignored, clean, unknown
1472 1470
1473 1471 # load earliest manifest first for caching reasons
1474 1472 if not working and ctx2.rev() < ctx1.rev():
1475 1473 ctx2.manifest()
1476 1474
1477 1475 if not parentworking:
1478 1476 def bad(f, msg):
1479 1477 # 'f' may be a directory pattern from 'match.files()',
1480 1478 # so 'f not in ctx1' is not enough
1481 1479 if f not in ctx1 and f not in ctx1.dirs():
1482 1480 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1483 1481 match.bad = bad
1484 1482
1485 1483 if working: # we need to scan the working dir
1486 1484 subrepos = []
1487 1485 if '.hgsub' in self.dirstate:
1488 1486 subrepos = sorted(ctx2.substate)
1489 1487 s = self.dirstate.status(match, subrepos, listignored,
1490 1488 listclean, listunknown)
1491 1489 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1492 1490
1493 1491 # check for any possibly clean files
1494 1492 if parentworking and cmp:
1495 1493 fixup = []
1496 1494 # do a full compare of any files that might have changed
1497 1495 for f in sorted(cmp):
1498 1496 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1499 1497 or ctx1[f].cmp(ctx2[f])):
1500 1498 modified.append(f)
1501 1499 else:
1502 1500 fixup.append(f)
1503 1501
1504 1502 # update dirstate for files that are actually clean
1505 1503 if fixup:
1506 1504 if listclean:
1507 1505 clean += fixup
1508 1506
1509 1507 try:
1510 1508 # updating the dirstate is optional
1511 1509 # so we don't wait on the lock
1512 1510 wlock = self.wlock(False)
1513 1511 try:
1514 1512 for f in fixup:
1515 1513 self.dirstate.normal(f)
1516 1514 finally:
1517 1515 wlock.release()
1518 1516 except error.LockError:
1519 1517 pass
1520 1518
1521 1519 if not parentworking:
1522 1520 mf1 = mfmatches(ctx1)
1523 1521 if working:
1524 1522 # we are comparing working dir against non-parent
1525 1523 # generate a pseudo-manifest for the working dir
1526 1524 mf2 = mfmatches(self['.'])
1527 1525 for f in cmp + modified + added:
1528 1526 mf2[f] = None
1529 1527 mf2.set(f, ctx2.flags(f))
1530 1528 for f in removed:
1531 1529 if f in mf2:
1532 1530 del mf2[f]
1533 1531 else:
1534 1532 # we are comparing two revisions
1535 1533 deleted, unknown, ignored = [], [], []
1536 1534 mf2 = mfmatches(ctx2)
1537 1535
1538 1536 modified, added, clean = [], [], []
1539 1537 withflags = mf1.withflags() | mf2.withflags()
1540 1538 for fn, mf2node in mf2.iteritems():
1541 1539 if fn in mf1:
1542 1540 if (fn not in deleted and
1543 1541 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1544 1542 (mf1[fn] != mf2node and
1545 1543 (mf2node or ctx1[fn].cmp(ctx2[fn]))))):
1546 1544 modified.append(fn)
1547 1545 elif listclean:
1548 1546 clean.append(fn)
1549 1547 del mf1[fn]
1550 1548 elif fn not in deleted:
1551 1549 added.append(fn)
1552 1550 removed = mf1.keys()
1553 1551
1554 1552 if working and modified and not self.dirstate._checklink:
1555 1553 # Symlink placeholders may get non-symlink-like contents
1556 1554 # via user error or dereferencing by NFS or Samba servers,
1557 1555 # so we filter out any placeholders that don't look like a
1558 1556 # symlink
1559 1557 sane = []
1560 1558 for f in modified:
1561 1559 if ctx2.flags(f) == 'l':
1562 1560 d = ctx2[f].data()
1563 1561 if len(d) >= 1024 or '\n' in d or util.binary(d):
1564 1562 self.ui.debug('ignoring suspect symlink placeholder'
1565 1563 ' "%s"\n' % f)
1566 1564 continue
1567 1565 sane.append(f)
1568 1566 modified = sane
1569 1567
1570 1568 r = modified, added, removed, deleted, unknown, ignored, clean
1571 1569
1572 1570 if listsubrepos:
1573 1571 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1574 1572 if working:
1575 1573 rev2 = None
1576 1574 else:
1577 1575 rev2 = ctx2.substate[subpath][1]
1578 1576 try:
1579 1577 submatch = matchmod.narrowmatcher(subpath, match)
1580 1578 s = sub.status(rev2, match=submatch, ignored=listignored,
1581 1579 clean=listclean, unknown=listunknown,
1582 1580 listsubrepos=True)
1583 1581 for rfiles, sfiles in zip(r, s):
1584 1582 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1585 1583 except error.LookupError:
1586 1584 self.ui.status(_("skipping missing subrepository: %s\n")
1587 1585 % subpath)
1588 1586
1589 1587 for l in r:
1590 1588 l.sort()
1591 1589 return r
1592 1590
1593 1591 def heads(self, start=None):
1594 1592 heads = self.changelog.heads(start)
1595 1593 # sort the output in rev descending order
1596 1594 return sorted(heads, key=self.changelog.rev, reverse=True)
1597 1595
1598 1596 def branchheads(self, branch=None, start=None, closed=False):
1599 1597 '''return a (possibly filtered) list of heads for the given branch
1600 1598
1601 1599 Heads are returned in topological order, from newest to oldest.
1602 1600 If branch is None, use the dirstate branch.
1603 1601 If start is not None, return only heads reachable from start.
1604 1602 If closed is True, return heads that are marked as closed as well.
1605 1603 '''
1606 1604 if branch is None:
1607 1605 branch = self[None].branch()
1608 1606 branches = self.branchmap()
1609 1607 if branch not in branches:
1610 1608 return []
1611 1609 # the cache returns heads ordered lowest to highest
1612 1610 bheads = list(reversed(branches[branch]))
1613 1611 if start is not None:
1614 1612 # filter out the heads that cannot be reached from startrev
1615 1613 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1616 1614 bheads = [h for h in bheads if h in fbheads]
1617 1615 if not closed:
1618 1616 bheads = [h for h in bheads if not self[h].closesbranch()]
1619 1617 return bheads
1620 1618
1621 1619 def branches(self, nodes):
1622 1620 if not nodes:
1623 1621 nodes = [self.changelog.tip()]
1624 1622 b = []
1625 1623 for n in nodes:
1626 1624 t = n
1627 1625 while True:
1628 1626 p = self.changelog.parents(n)
1629 1627 if p[1] != nullid or p[0] == nullid:
1630 1628 b.append((t, n, p[0], p[1]))
1631 1629 break
1632 1630 n = p[0]
1633 1631 return b
1634 1632
1635 1633 def between(self, pairs):
1636 1634 r = []
1637 1635
1638 1636 for top, bottom in pairs:
1639 1637 n, l, i = top, [], 0
1640 1638 f = 1
1641 1639
1642 1640 while n != bottom and n != nullid:
1643 1641 p = self.changelog.parents(n)[0]
1644 1642 if i == f:
1645 1643 l.append(n)
1646 1644 f = f * 2
1647 1645 n = p
1648 1646 i += 1
1649 1647
1650 1648 r.append(l)
1651 1649
1652 1650 return r
1653 1651
1654 1652 def pull(self, remote, heads=None, force=False):
1655 1653 # don't open transaction for nothing or you break future useful
1656 1654 # rollback call
1657 1655 tr = None
1658 1656 trname = 'pull\n' + util.hidepassword(remote.url())
1659 1657 lock = self.lock()
1660 1658 try:
1661 1659 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1662 1660 force=force)
1663 1661 common, fetch, rheads = tmp
1664 1662 if not fetch:
1665 1663 self.ui.status(_("no changes found\n"))
1666 1664 added = []
1667 1665 result = 0
1668 1666 else:
1669 1667 tr = self.transaction(trname)
1670 1668 if heads is None and list(common) == [nullid]:
1671 1669 self.ui.status(_("requesting all changes\n"))
1672 1670 elif heads is None and remote.capable('changegroupsubset'):
1673 1671 # issue1320, avoid a race if remote changed after discovery
1674 1672 heads = rheads
1675 1673
1676 1674 if remote.capable('getbundle'):
1677 1675 cg = remote.getbundle('pull', common=common,
1678 1676 heads=heads or rheads)
1679 1677 elif heads is None:
1680 1678 cg = remote.changegroup(fetch, 'pull')
1681 1679 elif not remote.capable('changegroupsubset'):
1682 1680 raise util.Abort(_("partial pull cannot be done because "
1683 1681 "other repository doesn't support "
1684 1682 "changegroupsubset."))
1685 1683 else:
1686 1684 cg = remote.changegroupsubset(fetch, heads, 'pull')
1687 1685 # we use unfiltered changelog here because hidden revision must
1688 1686 # be taken in account for phase synchronization. They may
1689 1687 # becomes public and becomes visible again.
1690 1688 cl = self.unfiltered().changelog
1691 1689 clstart = len(cl)
1692 1690 result = self.addchangegroup(cg, 'pull', remote.url())
1693 1691 clend = len(cl)
1694 1692 added = [cl.node(r) for r in xrange(clstart, clend)]
1695 1693
1696 1694 # compute target subset
1697 1695 if heads is None:
1698 1696 # We pulled every thing possible
1699 1697 # sync on everything common
1700 1698 subset = common + added
1701 1699 else:
1702 1700 # We pulled a specific subset
1703 1701 # sync on this subset
1704 1702 subset = heads
1705 1703
1706 1704 # Get remote phases data from remote
1707 1705 remotephases = remote.listkeys('phases')
1708 1706 publishing = bool(remotephases.get('publishing', False))
1709 1707 if remotephases and not publishing:
1710 1708 # remote is new and unpublishing
1711 1709 pheads, _dr = phases.analyzeremotephases(self, subset,
1712 1710 remotephases)
1713 1711 phases.advanceboundary(self, phases.public, pheads)
1714 1712 phases.advanceboundary(self, phases.draft, subset)
1715 1713 else:
1716 1714 # Remote is old or publishing all common changesets
1717 1715 # should be seen as public
1718 1716 phases.advanceboundary(self, phases.public, subset)
1719 1717
1720 1718 if obsolete._enabled:
1721 1719 self.ui.debug('fetching remote obsolete markers\n')
1722 1720 remoteobs = remote.listkeys('obsolete')
1723 1721 if 'dump0' in remoteobs:
1724 1722 if tr is None:
1725 1723 tr = self.transaction(trname)
1726 1724 for key in sorted(remoteobs, reverse=True):
1727 1725 if key.startswith('dump'):
1728 1726 data = base85.b85decode(remoteobs[key])
1729 1727 self.obsstore.mergemarkers(tr, data)
1730 1728 self.invalidatevolatilesets()
1731 1729 if tr is not None:
1732 1730 tr.close()
1733 1731 finally:
1734 1732 if tr is not None:
1735 1733 tr.release()
1736 1734 lock.release()
1737 1735
1738 1736 return result
1739 1737
1740 1738 def checkpush(self, force, revs):
1741 1739 """Extensions can override this function if additional checks have
1742 1740 to be performed before pushing, or call it if they override push
1743 1741 command.
1744 1742 """
1745 1743 pass
1746 1744
1747 1745 def push(self, remote, force=False, revs=None, newbranch=False):
1748 1746 '''Push outgoing changesets (limited by revs) from the current
1749 1747 repository to remote. Return an integer:
1750 1748 - None means nothing to push
1751 1749 - 0 means HTTP error
1752 1750 - 1 means we pushed and remote head count is unchanged *or*
1753 1751 we have outgoing changesets but refused to push
1754 1752 - other values as described by addchangegroup()
1755 1753 '''
1756 1754 # there are two ways to push to remote repo:
1757 1755 #
1758 1756 # addchangegroup assumes local user can lock remote
1759 1757 # repo (local filesystem, old ssh servers).
1760 1758 #
1761 1759 # unbundle assumes local user cannot lock remote repo (new ssh
1762 1760 # servers, http servers).
1763 1761
1764 1762 if not remote.canpush():
1765 1763 raise util.Abort(_("destination does not support push"))
1766 1764 unfi = self.unfiltered()
1767 1765 # get local lock as we might write phase data
1768 1766 locallock = self.lock()
1769 1767 try:
1770 1768 self.checkpush(force, revs)
1771 1769 lock = None
1772 1770 unbundle = remote.capable('unbundle')
1773 1771 if not unbundle:
1774 1772 lock = remote.lock()
1775 1773 try:
1776 1774 # discovery
1777 1775 fci = discovery.findcommonincoming
1778 1776 commoninc = fci(unfi, remote, force=force)
1779 1777 common, inc, remoteheads = commoninc
1780 1778 fco = discovery.findcommonoutgoing
1781 1779 outgoing = fco(unfi, remote, onlyheads=revs,
1782 1780 commoninc=commoninc, force=force)
1783 1781
1784 1782
1785 1783 if not outgoing.missing:
1786 1784 # nothing to push
1787 1785 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
1788 1786 ret = None
1789 1787 else:
1790 1788 # something to push
1791 1789 if not force:
1792 1790 # if self.obsstore == False --> no obsolete
1793 1791 # then, save the iteration
1794 1792 if unfi.obsstore:
1795 1793 # this message are here for 80 char limit reason
1796 1794 mso = _("push includes obsolete changeset: %s!")
1797 1795 mst = "push includes %s changeset: %s!"
1798 1796 # plain versions for i18n tool to detect them
1799 1797 _("push includes unstable changeset: %s!")
1800 1798 _("push includes bumped changeset: %s!")
1801 1799 _("push includes divergent changeset: %s!")
1802 1800 # If we are to push if there is at least one
1803 1801 # obsolete or unstable changeset in missing, at
1804 1802 # least one of the missinghead will be obsolete or
1805 1803 # unstable. So checking heads only is ok
1806 1804 for node in outgoing.missingheads:
1807 1805 ctx = unfi[node]
1808 1806 if ctx.obsolete():
1809 1807 raise util.Abort(mso % ctx)
1810 1808 elif ctx.troubled():
1811 1809 raise util.Abort(_(mst)
1812 1810 % (ctx.troubles()[0],
1813 1811 ctx))
1814 1812 discovery.checkheads(unfi, remote, outgoing,
1815 1813 remoteheads, newbranch,
1816 1814 bool(inc))
1817 1815
1818 1816 # create a changegroup from local
1819 1817 if revs is None and not outgoing.excluded:
1820 1818 # push everything,
1821 1819 # use the fast path, no race possible on push
1822 1820 cg = self._changegroup(outgoing.missing, 'push')
1823 1821 else:
1824 1822 cg = self.getlocalbundle('push', outgoing)
1825 1823
1826 1824 # apply changegroup to remote
1827 1825 if unbundle:
1828 1826 # local repo finds heads on server, finds out what
1829 1827 # revs it must push. once revs transferred, if server
1830 1828 # finds it has different heads (someone else won
1831 1829 # commit/push race), server aborts.
1832 1830 if force:
1833 1831 remoteheads = ['force']
1834 1832 # ssh: return remote's addchangegroup()
1835 1833 # http: return remote's addchangegroup() or 0 for error
1836 1834 ret = remote.unbundle(cg, remoteheads, 'push')
1837 1835 else:
1838 1836 # we return an integer indicating remote head count
1839 1837 # change
1840 1838 ret = remote.addchangegroup(cg, 'push', self.url())
1841 1839
1842 1840 if ret:
1843 1841 # push succeed, synchronize target of the push
1844 1842 cheads = outgoing.missingheads
1845 1843 elif revs is None:
1846 1844 # All out push fails. synchronize all common
1847 1845 cheads = outgoing.commonheads
1848 1846 else:
1849 1847 # I want cheads = heads(::missingheads and ::commonheads)
1850 1848 # (missingheads is revs with secret changeset filtered out)
1851 1849 #
1852 1850 # This can be expressed as:
1853 1851 # cheads = ( (missingheads and ::commonheads)
1854 1852 # + (commonheads and ::missingheads))"
1855 1853 # )
1856 1854 #
1857 1855 # while trying to push we already computed the following:
1858 1856 # common = (::commonheads)
1859 1857 # missing = ((commonheads::missingheads) - commonheads)
1860 1858 #
1861 1859 # We can pick:
1862 1860 # * missingheads part of common (::commonheads)
1863 1861 common = set(outgoing.common)
1864 1862 cheads = [node for node in revs if node in common]
1865 1863 # and
1866 1864 # * commonheads parents on missing
1867 1865 revset = unfi.set('%ln and parents(roots(%ln))',
1868 1866 outgoing.commonheads,
1869 1867 outgoing.missing)
1870 1868 cheads.extend(c.node() for c in revset)
1871 1869 # even when we don't push, exchanging phase data is useful
1872 1870 remotephases = remote.listkeys('phases')
1873 1871 if (self.ui.configbool('ui', '_usedassubrepo', False)
1874 1872 and remotephases # server supports phases
1875 1873 and ret is None # nothing was pushed
1876 1874 and remotephases.get('publishing', False)):
1877 1875 # When:
1878 1876 # - this is a subrepo push
1879 1877 # - and remote support phase
1880 1878 # - and no changeset was pushed
1881 1879 # - and remote is publishing
1882 1880 # We may be in issue 3871 case!
1883 1881 # We drop the possible phase synchronisation done by
1884 1882 # courtesy to publish changesets possibly locally draft
1885 1883 # on the remote.
1886 1884 remotephases = {'publishing': 'True'}
1887 1885 if not remotephases: # old server or public only repo
1888 1886 phases.advanceboundary(self, phases.public, cheads)
1889 1887 # don't push any phase data as there is nothing to push
1890 1888 else:
1891 1889 ana = phases.analyzeremotephases(self, cheads, remotephases)
1892 1890 pheads, droots = ana
1893 1891 ### Apply remote phase on local
1894 1892 if remotephases.get('publishing', False):
1895 1893 phases.advanceboundary(self, phases.public, cheads)
1896 1894 else: # publish = False
1897 1895 phases.advanceboundary(self, phases.public, pheads)
1898 1896 phases.advanceboundary(self, phases.draft, cheads)
1899 1897 ### Apply local phase on remote
1900 1898
1901 1899 # Get the list of all revs draft on remote by public here.
1902 1900 # XXX Beware that revset break if droots is not strictly
1903 1901 # XXX root we may want to ensure it is but it is costly
1904 1902 outdated = unfi.set('heads((%ln::%ln) and public())',
1905 1903 droots, cheads)
1906 1904 for newremotehead in outdated:
1907 1905 r = remote.pushkey('phases',
1908 1906 newremotehead.hex(),
1909 1907 str(phases.draft),
1910 1908 str(phases.public))
1911 1909 if not r:
1912 1910 self.ui.warn(_('updating %s to public failed!\n')
1913 1911 % newremotehead)
1914 1912 self.ui.debug('try to push obsolete markers to remote\n')
1915 1913 if (obsolete._enabled and self.obsstore and
1916 1914 'obsolete' in remote.listkeys('namespaces')):
1917 1915 rslts = []
1918 1916 remotedata = self.listkeys('obsolete')
1919 1917 for key in sorted(remotedata, reverse=True):
1920 1918 # reverse sort to ensure we end with dump0
1921 1919 data = remotedata[key]
1922 1920 rslts.append(remote.pushkey('obsolete', key, '', data))
1923 1921 if [r for r in rslts if not r]:
1924 1922 msg = _('failed to push some obsolete markers!\n')
1925 1923 self.ui.warn(msg)
1926 1924 finally:
1927 1925 if lock is not None:
1928 1926 lock.release()
1929 1927 finally:
1930 1928 locallock.release()
1931 1929
1932 1930 self.ui.debug("checking for updated bookmarks\n")
1933 1931 rb = remote.listkeys('bookmarks')
1934 1932 for k in rb.keys():
1935 1933 if k in unfi._bookmarks:
1936 1934 nr, nl = rb[k], hex(self._bookmarks[k])
1937 1935 if nr in unfi:
1938 1936 cr = unfi[nr]
1939 1937 cl = unfi[nl]
1940 1938 if bookmarks.validdest(unfi, cr, cl):
1941 1939 r = remote.pushkey('bookmarks', k, nr, nl)
1942 1940 if r:
1943 1941 self.ui.status(_("updating bookmark %s\n") % k)
1944 1942 else:
1945 1943 self.ui.warn(_('updating bookmark %s'
1946 1944 ' failed!\n') % k)
1947 1945
1948 1946 return ret
1949 1947
1950 1948 def changegroupinfo(self, nodes, source):
1951 1949 if self.ui.verbose or source == 'bundle':
1952 1950 self.ui.status(_("%d changesets found\n") % len(nodes))
1953 1951 if self.ui.debugflag:
1954 1952 self.ui.debug("list of changesets:\n")
1955 1953 for node in nodes:
1956 1954 self.ui.debug("%s\n" % hex(node))
1957 1955
1958 1956 def changegroupsubset(self, bases, heads, source):
1959 1957 """Compute a changegroup consisting of all the nodes that are
1960 1958 descendants of any of the bases and ancestors of any of the heads.
1961 1959 Return a chunkbuffer object whose read() method will return
1962 1960 successive changegroup chunks.
1963 1961
1964 1962 It is fairly complex as determining which filenodes and which
1965 1963 manifest nodes need to be included for the changeset to be complete
1966 1964 is non-trivial.
1967 1965
1968 1966 Another wrinkle is doing the reverse, figuring out which changeset in
1969 1967 the changegroup a particular filenode or manifestnode belongs to.
1970 1968 """
1971 1969 cl = self.changelog
1972 1970 if not bases:
1973 1971 bases = [nullid]
1974 1972 csets, bases, heads = cl.nodesbetween(bases, heads)
1975 1973 # We assume that all ancestors of bases are known
1976 1974 common = cl.ancestors([cl.rev(n) for n in bases])
1977 1975 return self._changegroupsubset(common, csets, heads, source)
1978 1976
1979 1977 def getlocalbundle(self, source, outgoing):
1980 1978 """Like getbundle, but taking a discovery.outgoing as an argument.
1981 1979
1982 1980 This is only implemented for local repos and reuses potentially
1983 1981 precomputed sets in outgoing."""
1984 1982 if not outgoing.missing:
1985 1983 return None
1986 1984 return self._changegroupsubset(outgoing.common,
1987 1985 outgoing.missing,
1988 1986 outgoing.missingheads,
1989 1987 source)
1990 1988
1991 1989 def getbundle(self, source, heads=None, common=None):
1992 1990 """Like changegroupsubset, but returns the set difference between the
1993 1991 ancestors of heads and the ancestors common.
1994 1992
1995 1993 If heads is None, use the local heads. If common is None, use [nullid].
1996 1994
1997 1995 The nodes in common might not all be known locally due to the way the
1998 1996 current discovery protocol works.
1999 1997 """
2000 1998 cl = self.changelog
2001 1999 if common:
2002 2000 hasnode = cl.hasnode
2003 2001 common = [n for n in common if hasnode(n)]
2004 2002 else:
2005 2003 common = [nullid]
2006 2004 if not heads:
2007 2005 heads = cl.heads()
2008 2006 return self.getlocalbundle(source,
2009 2007 discovery.outgoing(cl, common, heads))
2010 2008
2011 2009 @unfilteredmethod
2012 2010 def _changegroupsubset(self, commonrevs, csets, heads, source):
2013 2011
2014 2012 cl = self.changelog
2015 2013 mf = self.manifest
2016 2014 mfs = {} # needed manifests
2017 2015 fnodes = {} # needed file nodes
2018 2016 changedfiles = set()
2019 2017 fstate = ['', {}]
2020 2018 count = [0, 0]
2021 2019
2022 2020 # can we go through the fast path ?
2023 2021 heads.sort()
2024 2022 if heads == sorted(self.heads()):
2025 2023 return self._changegroup(csets, source)
2026 2024
2027 2025 # slow path
2028 2026 self.hook('preoutgoing', throw=True, source=source)
2029 2027 self.changegroupinfo(csets, source)
2030 2028
2031 2029 # filter any nodes that claim to be part of the known set
2032 2030 def prune(revlog, missing):
2033 2031 rr, rl = revlog.rev, revlog.linkrev
2034 2032 return [n for n in missing
2035 2033 if rl(rr(n)) not in commonrevs]
2036 2034
2037 2035 progress = self.ui.progress
2038 2036 _bundling = _('bundling')
2039 2037 _changesets = _('changesets')
2040 2038 _manifests = _('manifests')
2041 2039 _files = _('files')
2042 2040
2043 2041 def lookup(revlog, x):
2044 2042 if revlog == cl:
2045 2043 c = cl.read(x)
2046 2044 changedfiles.update(c[3])
2047 2045 mfs.setdefault(c[0], x)
2048 2046 count[0] += 1
2049 2047 progress(_bundling, count[0],
2050 2048 unit=_changesets, total=count[1])
2051 2049 return x
2052 2050 elif revlog == mf:
2053 2051 clnode = mfs[x]
2054 2052 mdata = mf.readfast(x)
2055 2053 for f, n in mdata.iteritems():
2056 2054 if f in changedfiles:
2057 2055 fnodes[f].setdefault(n, clnode)
2058 2056 count[0] += 1
2059 2057 progress(_bundling, count[0],
2060 2058 unit=_manifests, total=count[1])
2061 2059 return clnode
2062 2060 else:
2063 2061 progress(_bundling, count[0], item=fstate[0],
2064 2062 unit=_files, total=count[1])
2065 2063 return fstate[1][x]
2066 2064
2067 2065 bundler = changegroup.bundle10(lookup)
2068 2066 reorder = self.ui.config('bundle', 'reorder', 'auto')
2069 2067 if reorder == 'auto':
2070 2068 reorder = None
2071 2069 else:
2072 2070 reorder = util.parsebool(reorder)
2073 2071
2074 2072 def gengroup():
2075 2073 # Create a changenode group generator that will call our functions
2076 2074 # back to lookup the owning changenode and collect information.
2077 2075 count[:] = [0, len(csets)]
2078 2076 for chunk in cl.group(csets, bundler, reorder=reorder):
2079 2077 yield chunk
2080 2078 progress(_bundling, None)
2081 2079
2082 2080 # Create a generator for the manifestnodes that calls our lookup
2083 2081 # and data collection functions back.
2084 2082 for f in changedfiles:
2085 2083 fnodes[f] = {}
2086 2084 count[:] = [0, len(mfs)]
2087 2085 for chunk in mf.group(prune(mf, mfs), bundler, reorder=reorder):
2088 2086 yield chunk
2089 2087 progress(_bundling, None)
2090 2088
2091 2089 mfs.clear()
2092 2090
2093 2091 # Go through all our files in order sorted by name.
2094 2092 count[:] = [0, len(changedfiles)]
2095 2093 for fname in sorted(changedfiles):
2096 2094 filerevlog = self.file(fname)
2097 2095 if not len(filerevlog):
2098 2096 raise util.Abort(_("empty or missing revlog for %s")
2099 2097 % fname)
2100 2098 fstate[0] = fname
2101 2099 fstate[1] = fnodes.pop(fname, {})
2102 2100
2103 2101 nodelist = prune(filerevlog, fstate[1])
2104 2102 if nodelist:
2105 2103 count[0] += 1
2106 2104 yield bundler.fileheader(fname)
2107 2105 for chunk in filerevlog.group(nodelist, bundler, reorder):
2108 2106 yield chunk
2109 2107
2110 2108 # Signal that no more groups are left.
2111 2109 yield bundler.close()
2112 2110 progress(_bundling, None)
2113 2111
2114 2112 if csets:
2115 2113 self.hook('outgoing', node=hex(csets[0]), source=source)
2116 2114
2117 2115 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2118 2116
2119 2117 def changegroup(self, basenodes, source):
2120 2118 # to avoid a race we use changegroupsubset() (issue1320)
2121 2119 return self.changegroupsubset(basenodes, self.heads(), source)
2122 2120
2123 2121 @unfilteredmethod
2124 2122 def _changegroup(self, nodes, source):
2125 2123 """Compute the changegroup of all nodes that we have that a recipient
2126 2124 doesn't. Return a chunkbuffer object whose read() method will return
2127 2125 successive changegroup chunks.
2128 2126
2129 2127 This is much easier than the previous function as we can assume that
2130 2128 the recipient has any changenode we aren't sending them.
2131 2129
2132 2130 nodes is the set of nodes to send"""
2133 2131
2134 2132 cl = self.changelog
2135 2133 mf = self.manifest
2136 2134 mfs = {}
2137 2135 changedfiles = set()
2138 2136 fstate = ['']
2139 2137 count = [0, 0]
2140 2138
2141 2139 self.hook('preoutgoing', throw=True, source=source)
2142 2140 self.changegroupinfo(nodes, source)
2143 2141
2144 2142 revset = set([cl.rev(n) for n in nodes])
2145 2143
2146 2144 def gennodelst(log):
2147 2145 ln, llr = log.node, log.linkrev
2148 2146 return [ln(r) for r in log if llr(r) in revset]
2149 2147
2150 2148 progress = self.ui.progress
2151 2149 _bundling = _('bundling')
2152 2150 _changesets = _('changesets')
2153 2151 _manifests = _('manifests')
2154 2152 _files = _('files')
2155 2153
2156 2154 def lookup(revlog, x):
2157 2155 if revlog == cl:
2158 2156 c = cl.read(x)
2159 2157 changedfiles.update(c[3])
2160 2158 mfs.setdefault(c[0], x)
2161 2159 count[0] += 1
2162 2160 progress(_bundling, count[0],
2163 2161 unit=_changesets, total=count[1])
2164 2162 return x
2165 2163 elif revlog == mf:
2166 2164 count[0] += 1
2167 2165 progress(_bundling, count[0],
2168 2166 unit=_manifests, total=count[1])
2169 2167 return cl.node(revlog.linkrev(revlog.rev(x)))
2170 2168 else:
2171 2169 progress(_bundling, count[0], item=fstate[0],
2172 2170 total=count[1], unit=_files)
2173 2171 return cl.node(revlog.linkrev(revlog.rev(x)))
2174 2172
2175 2173 bundler = changegroup.bundle10(lookup)
2176 2174 reorder = self.ui.config('bundle', 'reorder', 'auto')
2177 2175 if reorder == 'auto':
2178 2176 reorder = None
2179 2177 else:
2180 2178 reorder = util.parsebool(reorder)
2181 2179
2182 2180 def gengroup():
2183 2181 '''yield a sequence of changegroup chunks (strings)'''
2184 2182 # construct a list of all changed files
2185 2183
2186 2184 count[:] = [0, len(nodes)]
2187 2185 for chunk in cl.group(nodes, bundler, reorder=reorder):
2188 2186 yield chunk
2189 2187 progress(_bundling, None)
2190 2188
2191 2189 count[:] = [0, len(mfs)]
2192 2190 for chunk in mf.group(gennodelst(mf), bundler, reorder=reorder):
2193 2191 yield chunk
2194 2192 progress(_bundling, None)
2195 2193
2196 2194 count[:] = [0, len(changedfiles)]
2197 2195 for fname in sorted(changedfiles):
2198 2196 filerevlog = self.file(fname)
2199 2197 if not len(filerevlog):
2200 2198 raise util.Abort(_("empty or missing revlog for %s")
2201 2199 % fname)
2202 2200 fstate[0] = fname
2203 2201 nodelist = gennodelst(filerevlog)
2204 2202 if nodelist:
2205 2203 count[0] += 1
2206 2204 yield bundler.fileheader(fname)
2207 2205 for chunk in filerevlog.group(nodelist, bundler, reorder):
2208 2206 yield chunk
2209 2207 yield bundler.close()
2210 2208 progress(_bundling, None)
2211 2209
2212 2210 if nodes:
2213 2211 self.hook('outgoing', node=hex(nodes[0]), source=source)
2214 2212
2215 2213 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2216 2214
2217 2215 @unfilteredmethod
2218 2216 def addchangegroup(self, source, srctype, url, emptyok=False):
2219 2217 """Add the changegroup returned by source.read() to this repo.
2220 2218 srctype is a string like 'push', 'pull', or 'unbundle'. url is
2221 2219 the URL of the repo where this changegroup is coming from.
2222 2220
2223 2221 Return an integer summarizing the change to this repo:
2224 2222 - nothing changed or no source: 0
2225 2223 - more heads than before: 1+added heads (2..n)
2226 2224 - fewer heads than before: -1-removed heads (-2..-n)
2227 2225 - number of heads stays the same: 1
2228 2226 """
2229 2227 def csmap(x):
2230 2228 self.ui.debug("add changeset %s\n" % short(x))
2231 2229 return len(cl)
2232 2230
2233 2231 def revmap(x):
2234 2232 return cl.rev(x)
2235 2233
2236 2234 if not source:
2237 2235 return 0
2238 2236
2239 2237 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2240 2238
2241 2239 changesets = files = revisions = 0
2242 2240 efiles = set()
2243 2241
2244 2242 # write changelog data to temp files so concurrent readers will not see
2245 2243 # inconsistent view
2246 2244 cl = self.changelog
2247 2245 cl.delayupdate()
2248 2246 oldheads = cl.heads()
2249 2247
2250 2248 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
2251 2249 try:
2252 2250 trp = weakref.proxy(tr)
2253 2251 # pull off the changeset group
2254 2252 self.ui.status(_("adding changesets\n"))
2255 2253 clstart = len(cl)
2256 2254 class prog(object):
2257 2255 step = _('changesets')
2258 2256 count = 1
2259 2257 ui = self.ui
2260 2258 total = None
2261 2259 def __call__(self):
2262 2260 self.ui.progress(self.step, self.count, unit=_('chunks'),
2263 2261 total=self.total)
2264 2262 self.count += 1
2265 2263 pr = prog()
2266 2264 source.callback = pr
2267 2265
2268 2266 source.changelogheader()
2269 2267 srccontent = cl.addgroup(source, csmap, trp)
2270 2268 if not (srccontent or emptyok):
2271 2269 raise util.Abort(_("received changelog group is empty"))
2272 2270 clend = len(cl)
2273 2271 changesets = clend - clstart
2274 2272 for c in xrange(clstart, clend):
2275 2273 efiles.update(self[c].files())
2276 2274 efiles = len(efiles)
2277 2275 self.ui.progress(_('changesets'), None)
2278 2276
2279 2277 # pull off the manifest group
2280 2278 self.ui.status(_("adding manifests\n"))
2281 2279 pr.step = _('manifests')
2282 2280 pr.count = 1
2283 2281 pr.total = changesets # manifests <= changesets
2284 2282 # no need to check for empty manifest group here:
2285 2283 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2286 2284 # no new manifest will be created and the manifest group will
2287 2285 # be empty during the pull
2288 2286 source.manifestheader()
2289 2287 self.manifest.addgroup(source, revmap, trp)
2290 2288 self.ui.progress(_('manifests'), None)
2291 2289
2292 2290 needfiles = {}
2293 2291 if self.ui.configbool('server', 'validate', default=False):
2294 2292 # validate incoming csets have their manifests
2295 2293 for cset in xrange(clstart, clend):
2296 2294 mfest = self.changelog.read(self.changelog.node(cset))[0]
2297 2295 mfest = self.manifest.readdelta(mfest)
2298 2296 # store file nodes we must see
2299 2297 for f, n in mfest.iteritems():
2300 2298 needfiles.setdefault(f, set()).add(n)
2301 2299
2302 2300 # process the files
2303 2301 self.ui.status(_("adding file changes\n"))
2304 2302 pr.step = _('files')
2305 2303 pr.count = 1
2306 2304 pr.total = efiles
2307 2305 source.callback = None
2308 2306
2309 2307 while True:
2310 2308 chunkdata = source.filelogheader()
2311 2309 if not chunkdata:
2312 2310 break
2313 2311 f = chunkdata["filename"]
2314 2312 self.ui.debug("adding %s revisions\n" % f)
2315 2313 pr()
2316 2314 fl = self.file(f)
2317 2315 o = len(fl)
2318 2316 if not fl.addgroup(source, revmap, trp):
2319 2317 raise util.Abort(_("received file revlog group is empty"))
2320 2318 revisions += len(fl) - o
2321 2319 files += 1
2322 2320 if f in needfiles:
2323 2321 needs = needfiles[f]
2324 2322 for new in xrange(o, len(fl)):
2325 2323 n = fl.node(new)
2326 2324 if n in needs:
2327 2325 needs.remove(n)
2328 2326 else:
2329 2327 raise util.Abort(
2330 2328 _("received spurious file revlog entry"))
2331 2329 if not needs:
2332 2330 del needfiles[f]
2333 2331 self.ui.progress(_('files'), None)
2334 2332
2335 2333 for f, needs in needfiles.iteritems():
2336 2334 fl = self.file(f)
2337 2335 for n in needs:
2338 2336 try:
2339 2337 fl.rev(n)
2340 2338 except error.LookupError:
2341 2339 raise util.Abort(
2342 2340 _('missing file data for %s:%s - run hg verify') %
2343 2341 (f, hex(n)))
2344 2342
2345 2343 dh = 0
2346 2344 if oldheads:
2347 2345 heads = cl.heads()
2348 2346 dh = len(heads) - len(oldheads)
2349 2347 for h in heads:
2350 2348 if h not in oldheads and self[h].closesbranch():
2351 2349 dh -= 1
2352 2350 htext = ""
2353 2351 if dh:
2354 2352 htext = _(" (%+d heads)") % dh
2355 2353
2356 2354 self.ui.status(_("added %d changesets"
2357 2355 " with %d changes to %d files%s\n")
2358 2356 % (changesets, revisions, files, htext))
2359 2357 self.invalidatevolatilesets()
2360 2358
2361 2359 if changesets > 0:
2362 2360 p = lambda: cl.writepending() and self.root or ""
2363 2361 self.hook('pretxnchangegroup', throw=True,
2364 2362 node=hex(cl.node(clstart)), source=srctype,
2365 2363 url=url, pending=p)
2366 2364
2367 2365 added = [cl.node(r) for r in xrange(clstart, clend)]
2368 2366 publishing = self.ui.configbool('phases', 'publish', True)
2369 2367 if srctype == 'push':
2370 2368 # Old server can not push the boundary themself.
2371 2369 # New server won't push the boundary if changeset already
2372 2370 # existed locally as secrete
2373 2371 #
2374 2372 # We should not use added here but the list of all change in
2375 2373 # the bundle
2376 2374 if publishing:
2377 2375 phases.advanceboundary(self, phases.public, srccontent)
2378 2376 else:
2379 2377 phases.advanceboundary(self, phases.draft, srccontent)
2380 2378 phases.retractboundary(self, phases.draft, added)
2381 2379 elif srctype != 'strip':
2382 2380 # publishing only alter behavior during push
2383 2381 #
2384 2382 # strip should not touch boundary at all
2385 2383 phases.retractboundary(self, phases.draft, added)
2386 2384
2387 2385 # make changelog see real files again
2388 2386 cl.finalize(trp)
2389 2387
2390 2388 tr.close()
2391 2389
2392 2390 if changesets > 0:
2393 2391 if srctype != 'strip':
2394 2392 # During strip, branchcache is invalid but coming call to
2395 2393 # `destroyed` will repair it.
2396 2394 # In other case we can safely update cache on disk.
2397 2395 branchmap.updatecache(self.filtered('served'))
2398 2396 def runhooks():
2399 2397 # forcefully update the on-disk branch cache
2400 2398 self.ui.debug("updating the branch cache\n")
2401 2399 self.hook("changegroup", node=hex(cl.node(clstart)),
2402 2400 source=srctype, url=url)
2403 2401
2404 2402 for n in added:
2405 2403 self.hook("incoming", node=hex(n), source=srctype,
2406 2404 url=url)
2407 2405
2408 2406 newheads = [h for h in self.heads() if h not in oldheads]
2409 2407 self.ui.log("incoming",
2410 2408 "%s incoming changes - new heads: %s\n",
2411 2409 len(added),
2412 2410 ', '.join([hex(c[:6]) for c in newheads]))
2413 2411 self._afterlock(runhooks)
2414 2412
2415 2413 finally:
2416 2414 tr.release()
2417 2415 # never return 0 here:
2418 2416 if dh < 0:
2419 2417 return dh - 1
2420 2418 else:
2421 2419 return dh + 1
2422 2420
2423 2421 def stream_in(self, remote, requirements):
2424 2422 lock = self.lock()
2425 2423 try:
2426 2424 # Save remote branchmap. We will use it later
2427 2425 # to speed up branchcache creation
2428 2426 rbranchmap = None
2429 2427 if remote.capable("branchmap"):
2430 2428 rbranchmap = remote.branchmap()
2431 2429
2432 2430 fp = remote.stream_out()
2433 2431 l = fp.readline()
2434 2432 try:
2435 2433 resp = int(l)
2436 2434 except ValueError:
2437 2435 raise error.ResponseError(
2438 2436 _('unexpected response from remote server:'), l)
2439 2437 if resp == 1:
2440 2438 raise util.Abort(_('operation forbidden by server'))
2441 2439 elif resp == 2:
2442 2440 raise util.Abort(_('locking the remote repository failed'))
2443 2441 elif resp != 0:
2444 2442 raise util.Abort(_('the server sent an unknown error code'))
2445 2443 self.ui.status(_('streaming all changes\n'))
2446 2444 l = fp.readline()
2447 2445 try:
2448 2446 total_files, total_bytes = map(int, l.split(' ', 1))
2449 2447 except (ValueError, TypeError):
2450 2448 raise error.ResponseError(
2451 2449 _('unexpected response from remote server:'), l)
2452 2450 self.ui.status(_('%d files to transfer, %s of data\n') %
2453 2451 (total_files, util.bytecount(total_bytes)))
2454 2452 handled_bytes = 0
2455 2453 self.ui.progress(_('clone'), 0, total=total_bytes)
2456 2454 start = time.time()
2457 2455 for i in xrange(total_files):
2458 2456 # XXX doesn't support '\n' or '\r' in filenames
2459 2457 l = fp.readline()
2460 2458 try:
2461 2459 name, size = l.split('\0', 1)
2462 2460 size = int(size)
2463 2461 except (ValueError, TypeError):
2464 2462 raise error.ResponseError(
2465 2463 _('unexpected response from remote server:'), l)
2466 2464 if self.ui.debugflag:
2467 2465 self.ui.debug('adding %s (%s)\n' %
2468 2466 (name, util.bytecount(size)))
2469 2467 # for backwards compat, name was partially encoded
2470 2468 ofp = self.sopener(store.decodedir(name), 'w')
2471 2469 for chunk in util.filechunkiter(fp, limit=size):
2472 2470 handled_bytes += len(chunk)
2473 2471 self.ui.progress(_('clone'), handled_bytes,
2474 2472 total=total_bytes)
2475 2473 ofp.write(chunk)
2476 2474 ofp.close()
2477 2475 elapsed = time.time() - start
2478 2476 if elapsed <= 0:
2479 2477 elapsed = 0.001
2480 2478 self.ui.progress(_('clone'), None)
2481 2479 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2482 2480 (util.bytecount(total_bytes), elapsed,
2483 2481 util.bytecount(total_bytes / elapsed)))
2484 2482
2485 2483 # new requirements = old non-format requirements +
2486 2484 # new format-related
2487 2485 # requirements from the streamed-in repository
2488 2486 requirements.update(set(self.requirements) - self.supportedformats)
2489 2487 self._applyrequirements(requirements)
2490 2488 self._writerequirements()
2491 2489
2492 2490 if rbranchmap:
2493 2491 rbheads = []
2494 2492 for bheads in rbranchmap.itervalues():
2495 2493 rbheads.extend(bheads)
2496 2494
2497 2495 if rbheads:
2498 2496 rtiprev = max((int(self.changelog.rev(node))
2499 2497 for node in rbheads))
2500 2498 cache = branchmap.branchcache(rbranchmap,
2501 2499 self[rtiprev].node(),
2502 2500 rtiprev)
2503 2501 # Try to stick it as low as possible
2504 2502 # filter above served are unlikely to be fetch from a clone
2505 2503 for candidate in ('base', 'immutable', 'served'):
2506 2504 rview = self.filtered(candidate)
2507 2505 if cache.validfor(rview):
2508 2506 self._branchcaches[candidate] = cache
2509 2507 cache.write(rview)
2510 2508 break
2511 2509 self.invalidate()
2512 2510 return len(self.heads()) + 1
2513 2511 finally:
2514 2512 lock.release()
2515 2513
2516 2514 def clone(self, remote, heads=[], stream=False):
2517 2515 '''clone remote repository.
2518 2516
2519 2517 keyword arguments:
2520 2518 heads: list of revs to clone (forces use of pull)
2521 2519 stream: use streaming clone if possible'''
2522 2520
2523 2521 # now, all clients that can request uncompressed clones can
2524 2522 # read repo formats supported by all servers that can serve
2525 2523 # them.
2526 2524
2527 2525 # if revlog format changes, client will have to check version
2528 2526 # and format flags on "stream" capability, and use
2529 2527 # uncompressed only if compatible.
2530 2528
2531 2529 if not stream:
2532 2530 # if the server explicitly prefers to stream (for fast LANs)
2533 2531 stream = remote.capable('stream-preferred')
2534 2532
2535 2533 if stream and not heads:
2536 2534 # 'stream' means remote revlog format is revlogv1 only
2537 2535 if remote.capable('stream'):
2538 2536 return self.stream_in(remote, set(('revlogv1',)))
2539 2537 # otherwise, 'streamreqs' contains the remote revlog format
2540 2538 streamreqs = remote.capable('streamreqs')
2541 2539 if streamreqs:
2542 2540 streamreqs = set(streamreqs.split(','))
2543 2541 # if we support it, stream in and adjust our requirements
2544 2542 if not streamreqs - self.supportedformats:
2545 2543 return self.stream_in(remote, streamreqs)
2546 2544 return self.pull(remote, heads)
2547 2545
2548 2546 def pushkey(self, namespace, key, old, new):
2549 2547 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
2550 2548 old=old, new=new)
2551 2549 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2552 2550 ret = pushkey.push(self, namespace, key, old, new)
2553 2551 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2554 2552 ret=ret)
2555 2553 return ret
2556 2554
2557 2555 def listkeys(self, namespace):
2558 2556 self.hook('prelistkeys', throw=True, namespace=namespace)
2559 2557 self.ui.debug('listing keys for "%s"\n' % namespace)
2560 2558 values = pushkey.list(self, namespace)
2561 2559 self.hook('listkeys', namespace=namespace, values=values)
2562 2560 return values
2563 2561
2564 2562 def debugwireargs(self, one, two, three=None, four=None, five=None):
2565 2563 '''used to test argument passing over the wire'''
2566 2564 return "%s %s %s %s %s" % (one, two, three, four, five)
2567 2565
2568 2566 def savecommitmessage(self, text):
2569 2567 fp = self.opener('last-message.txt', 'wb')
2570 2568 try:
2571 2569 fp.write(text)
2572 2570 finally:
2573 2571 fp.close()
2574 2572 return self.pathto(fp.name[len(self.root) + 1:])
2575 2573
2576 2574 # used to avoid circular references so destructors work
2577 2575 def aftertrans(files):
2578 2576 renamefiles = [tuple(t) for t in files]
2579 2577 def a():
2580 2578 for src, dest in renamefiles:
2581 2579 try:
2582 2580 util.rename(src, dest)
2583 2581 except OSError: # journal file does not yet exist
2584 2582 pass
2585 2583 return a
2586 2584
2587 2585 def undoname(fn):
2588 2586 base, name = os.path.split(fn)
2589 2587 assert name.startswith('journal')
2590 2588 return os.path.join(base, name.replace('journal', 'undo', 1))
2591 2589
2592 2590 def instance(ui, path, create):
2593 2591 return localrepository(ui, util.urllocalpath(path), create)
2594 2592
2595 2593 def islocal(path):
2596 2594 return True
@@ -1,939 +1,942 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 from mercurial.node import nullrev
10 10 import util, error, osutil, revset, similar, encoding, phases, parsers
11 11 import match as matchmod
12 12 import os, errno, re, stat, glob
13 13
14 14 if os.name == 'nt':
15 15 import scmwindows as scmplatform
16 16 else:
17 17 import scmposix as scmplatform
18 18
19 19 systemrcpath = scmplatform.systemrcpath
20 20 userrcpath = scmplatform.userrcpath
21 21
22 22 def nochangesfound(ui, repo, excluded=None):
23 23 '''Report no changes for push/pull, excluded is None or a list of
24 24 nodes excluded from the push/pull.
25 25 '''
26 26 secretlist = []
27 27 if excluded:
28 28 for n in excluded:
29 29 if n not in repo:
30 30 # discovery should not have included the filtered revision,
31 31 # we have to explicitly exclude it until discovery is cleanup.
32 32 continue
33 33 ctx = repo[n]
34 34 if ctx.phase() >= phases.secret and not ctx.extinct():
35 35 secretlist.append(n)
36 36
37 37 if secretlist:
38 38 ui.status(_("no changes found (ignored %d secret changesets)\n")
39 39 % len(secretlist))
40 40 else:
41 41 ui.status(_("no changes found\n"))
42 42
43 43 def checknewlabel(repo, lbl, kind):
44 44 if lbl in ['tip', '.', 'null']:
45 45 raise util.Abort(_("the name '%s' is reserved") % lbl)
46 46 for c in (':', '\0', '\n', '\r'):
47 47 if c in lbl:
48 48 raise util.Abort(_("%r cannot be used in a name") % c)
49 49 try:
50 50 int(lbl)
51 51 raise util.Abort(_("a %s cannot have an integer as its name") % kind)
52 52 except ValueError:
53 53 pass
54 54
55 55 def checkfilename(f):
56 56 '''Check that the filename f is an acceptable filename for a tracked file'''
57 57 if '\r' in f or '\n' in f:
58 58 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
59 59
60 60 def checkportable(ui, f):
61 61 '''Check if filename f is portable and warn or abort depending on config'''
62 62 checkfilename(f)
63 63 abort, warn = checkportabilityalert(ui)
64 64 if abort or warn:
65 65 msg = util.checkwinfilename(f)
66 66 if msg:
67 67 msg = "%s: %r" % (msg, f)
68 68 if abort:
69 69 raise util.Abort(msg)
70 70 ui.warn(_("warning: %s\n") % msg)
71 71
72 72 def checkportabilityalert(ui):
73 73 '''check if the user's config requests nothing, a warning, or abort for
74 74 non-portable filenames'''
75 75 val = ui.config('ui', 'portablefilenames', 'warn')
76 76 lval = val.lower()
77 77 bval = util.parsebool(val)
78 78 abort = os.name == 'nt' or lval == 'abort'
79 79 warn = bval or lval == 'warn'
80 80 if bval is None and not (warn or abort or lval == 'ignore'):
81 81 raise error.ConfigError(
82 82 _("ui.portablefilenames value is invalid ('%s')") % val)
83 83 return abort, warn
84 84
85 85 class casecollisionauditor(object):
86 86 def __init__(self, ui, abort, dirstate):
87 87 self._ui = ui
88 88 self._abort = abort
89 89 allfiles = '\0'.join(dirstate._map)
90 90 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
91 91 self._dirstate = dirstate
92 92 # The purpose of _newfiles is so that we don't complain about
93 93 # case collisions if someone were to call this object with the
94 94 # same filename twice.
95 95 self._newfiles = set()
96 96
97 97 def __call__(self, f):
98 98 fl = encoding.lower(f)
99 99 if (fl in self._loweredfiles and f not in self._dirstate and
100 100 f not in self._newfiles):
101 101 msg = _('possible case-folding collision for %s') % f
102 102 if self._abort:
103 103 raise util.Abort(msg)
104 104 self._ui.warn(_("warning: %s\n") % msg)
105 105 self._loweredfiles.add(fl)
106 106 self._newfiles.add(f)
107 107
108 108 class pathauditor(object):
109 109 '''ensure that a filesystem path contains no banned components.
110 110 the following properties of a path are checked:
111 111
112 112 - ends with a directory separator
113 113 - under top-level .hg
114 114 - starts at the root of a windows drive
115 115 - contains ".."
116 116 - traverses a symlink (e.g. a/symlink_here/b)
117 117 - inside a nested repository (a callback can be used to approve
118 118 some nested repositories, e.g., subrepositories)
119 119 '''
120 120
121 121 def __init__(self, root, callback=None):
122 122 self.audited = set()
123 123 self.auditeddir = set()
124 124 self.root = root
125 125 self.callback = callback
126 126 if os.path.lexists(root) and not util.checkcase(root):
127 127 self.normcase = util.normcase
128 128 else:
129 129 self.normcase = lambda x: x
130 130
131 131 def __call__(self, path):
132 132 '''Check the relative path.
133 133 path may contain a pattern (e.g. foodir/**.txt)'''
134 134
135 135 path = util.localpath(path)
136 136 normpath = self.normcase(path)
137 137 if normpath in self.audited:
138 138 return
139 139 # AIX ignores "/" at end of path, others raise EISDIR.
140 140 if util.endswithsep(path):
141 141 raise util.Abort(_("path ends in directory separator: %s") % path)
142 142 parts = util.splitpath(path)
143 143 if (os.path.splitdrive(path)[0]
144 144 or parts[0].lower() in ('.hg', '.hg.', '')
145 145 or os.pardir in parts):
146 146 raise util.Abort(_("path contains illegal component: %s") % path)
147 147 if '.hg' in path.lower():
148 148 lparts = [p.lower() for p in parts]
149 149 for p in '.hg', '.hg.':
150 150 if p in lparts[1:]:
151 151 pos = lparts.index(p)
152 152 base = os.path.join(*parts[:pos])
153 153 raise util.Abort(_("path '%s' is inside nested repo %r")
154 154 % (path, base))
155 155
156 156 normparts = util.splitpath(normpath)
157 157 assert len(parts) == len(normparts)
158 158
159 159 parts.pop()
160 160 normparts.pop()
161 161 prefixes = []
162 162 while parts:
163 163 prefix = os.sep.join(parts)
164 164 normprefix = os.sep.join(normparts)
165 165 if normprefix in self.auditeddir:
166 166 break
167 167 curpath = os.path.join(self.root, prefix)
168 168 try:
169 169 st = os.lstat(curpath)
170 170 except OSError, err:
171 171 # EINVAL can be raised as invalid path syntax under win32.
172 172 # They must be ignored for patterns can be checked too.
173 173 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
174 174 raise
175 175 else:
176 176 if stat.S_ISLNK(st.st_mode):
177 177 raise util.Abort(
178 178 _('path %r traverses symbolic link %r')
179 179 % (path, prefix))
180 180 elif (stat.S_ISDIR(st.st_mode) and
181 181 os.path.isdir(os.path.join(curpath, '.hg'))):
182 182 if not self.callback or not self.callback(curpath):
183 183 raise util.Abort(_("path '%s' is inside nested "
184 184 "repo %r")
185 185 % (path, prefix))
186 186 prefixes.append(normprefix)
187 187 parts.pop()
188 188 normparts.pop()
189 189
190 190 self.audited.add(normpath)
191 191 # only add prefixes to the cache after checking everything: we don't
192 192 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
193 193 self.auditeddir.update(prefixes)
194 194
195 195 def check(self, path):
196 196 try:
197 197 self(path)
198 198 return True
199 199 except (OSError, util.Abort):
200 200 return False
201 201
202 202 class abstractvfs(object):
203 203 """Abstract base class; cannot be instantiated"""
204 204
205 205 def __init__(self, *args, **kwargs):
206 206 '''Prevent instantiation; don't call this from subclasses.'''
207 207 raise NotImplementedError('attempted instantiating ' + str(type(self)))
208 208
209 209 def tryread(self, path):
210 210 '''gracefully return an empty string for missing files'''
211 211 try:
212 212 return self.read(path)
213 213 except IOError, inst:
214 214 if inst.errno != errno.ENOENT:
215 215 raise
216 216 return ""
217 217
218 218 def read(self, path):
219 219 fp = self(path, 'rb')
220 220 try:
221 221 return fp.read()
222 222 finally:
223 223 fp.close()
224 224
225 225 def write(self, path, data):
226 226 fp = self(path, 'wb')
227 227 try:
228 228 return fp.write(data)
229 229 finally:
230 230 fp.close()
231 231
232 232 def append(self, path, data):
233 233 fp = self(path, 'ab')
234 234 try:
235 235 return fp.write(data)
236 236 finally:
237 237 fp.close()
238 238
239 239 def exists(self, path=None):
240 240 return os.path.exists(self.join(path))
241 241
242 242 def isdir(self, path=None):
243 243 return os.path.isdir(self.join(path))
244 244
245 245 def makedir(self, path=None, notindexed=True):
246 246 return util.makedir(self.join(path), notindexed)
247 247
248 248 def makedirs(self, path=None, mode=None):
249 249 return util.makedirs(self.join(path), mode)
250 250
251 251 def mkdir(self, path=None):
252 252 return os.mkdir(self.join(path))
253 253
254 254 def readdir(self, path=None, stat=None, skip=None):
255 255 return osutil.listdir(self.join(path), stat, skip)
256 256
257 def rename(self, src, dst):
258 return util.rename(self.join(src), self.join(dst))
259
257 260 def stat(self, path=None):
258 261 return os.stat(self.join(path))
259 262
260 263 class vfs(abstractvfs):
261 264 '''Operate files relative to a base directory
262 265
263 266 This class is used to hide the details of COW semantics and
264 267 remote file access from higher level code.
265 268 '''
266 269 def __init__(self, base, audit=True, expandpath=False, realpath=False):
267 270 if expandpath:
268 271 base = util.expandpath(base)
269 272 if realpath:
270 273 base = os.path.realpath(base)
271 274 self.base = base
272 275 self._setmustaudit(audit)
273 276 self.createmode = None
274 277 self._trustnlink = None
275 278
276 279 def _getmustaudit(self):
277 280 return self._audit
278 281
279 282 def _setmustaudit(self, onoff):
280 283 self._audit = onoff
281 284 if onoff:
282 285 self.audit = pathauditor(self.base)
283 286 else:
284 287 self.audit = util.always
285 288
286 289 mustaudit = property(_getmustaudit, _setmustaudit)
287 290
288 291 @util.propertycache
289 292 def _cansymlink(self):
290 293 return util.checklink(self.base)
291 294
292 295 @util.propertycache
293 296 def _chmod(self):
294 297 return util.checkexec(self.base)
295 298
296 299 def _fixfilemode(self, name):
297 300 if self.createmode is None or not self._chmod:
298 301 return
299 302 os.chmod(name, self.createmode & 0666)
300 303
301 304 def __call__(self, path, mode="r", text=False, atomictemp=False):
302 305 if self._audit:
303 306 r = util.checkosfilename(path)
304 307 if r:
305 308 raise util.Abort("%s: %r" % (r, path))
306 309 self.audit(path)
307 310 f = self.join(path)
308 311
309 312 if not text and "b" not in mode:
310 313 mode += "b" # for that other OS
311 314
312 315 nlink = -1
313 316 if mode not in ('r', 'rb'):
314 317 dirname, basename = util.split(f)
315 318 # If basename is empty, then the path is malformed because it points
316 319 # to a directory. Let the posixfile() call below raise IOError.
317 320 if basename:
318 321 if atomictemp:
319 322 util.ensuredirs(dirname, self.createmode)
320 323 return util.atomictempfile(f, mode, self.createmode)
321 324 try:
322 325 if 'w' in mode:
323 326 util.unlink(f)
324 327 nlink = 0
325 328 else:
326 329 # nlinks() may behave differently for files on Windows
327 330 # shares if the file is open.
328 331 fd = util.posixfile(f)
329 332 nlink = util.nlinks(f)
330 333 if nlink < 1:
331 334 nlink = 2 # force mktempcopy (issue1922)
332 335 fd.close()
333 336 except (OSError, IOError), e:
334 337 if e.errno != errno.ENOENT:
335 338 raise
336 339 nlink = 0
337 340 util.ensuredirs(dirname, self.createmode)
338 341 if nlink > 0:
339 342 if self._trustnlink is None:
340 343 self._trustnlink = nlink > 1 or util.checknlink(f)
341 344 if nlink > 1 or not self._trustnlink:
342 345 util.rename(util.mktempcopy(f), f)
343 346 fp = util.posixfile(f, mode)
344 347 if nlink == 0:
345 348 self._fixfilemode(f)
346 349 return fp
347 350
348 351 def symlink(self, src, dst):
349 352 self.audit(dst)
350 353 linkname = self.join(dst)
351 354 try:
352 355 os.unlink(linkname)
353 356 except OSError:
354 357 pass
355 358
356 359 util.ensuredirs(os.path.dirname(linkname), self.createmode)
357 360
358 361 if self._cansymlink:
359 362 try:
360 363 os.symlink(src, linkname)
361 364 except OSError, err:
362 365 raise OSError(err.errno, _('could not symlink to %r: %s') %
363 366 (src, err.strerror), linkname)
364 367 else:
365 368 self.write(dst, src)
366 369
367 370 def join(self, path):
368 371 if path:
369 372 return os.path.join(self.base, path)
370 373 else:
371 374 return self.base
372 375
373 376 opener = vfs
374 377
375 378 class auditvfs(object):
376 379 def __init__(self, vfs):
377 380 self.vfs = vfs
378 381
379 382 def _getmustaudit(self):
380 383 return self.vfs.mustaudit
381 384
382 385 def _setmustaudit(self, onoff):
383 386 self.vfs.mustaudit = onoff
384 387
385 388 mustaudit = property(_getmustaudit, _setmustaudit)
386 389
387 390 class filtervfs(abstractvfs, auditvfs):
388 391 '''Wrapper vfs for filtering filenames with a function.'''
389 392
390 393 def __init__(self, vfs, filter):
391 394 auditvfs.__init__(self, vfs)
392 395 self._filter = filter
393 396
394 397 def __call__(self, path, *args, **kwargs):
395 398 return self.vfs(self._filter(path), *args, **kwargs)
396 399
397 400 def join(self, path):
398 401 if path:
399 402 return self.vfs.join(self._filter(path))
400 403 else:
401 404 return self.vfs.join(path)
402 405
403 406 filteropener = filtervfs
404 407
405 408 class readonlyvfs(abstractvfs, auditvfs):
406 409 '''Wrapper vfs preventing any writing.'''
407 410
408 411 def __init__(self, vfs):
409 412 auditvfs.__init__(self, vfs)
410 413
411 414 def __call__(self, path, mode='r', *args, **kw):
412 415 if mode not in ('r', 'rb'):
413 416 raise util.Abort('this vfs is read only')
414 417 return self.vfs(path, mode, *args, **kw)
415 418
416 419
417 420 def canonpath(root, cwd, myname, auditor=None):
418 421 '''return the canonical path of myname, given cwd and root'''
419 422 if util.endswithsep(root):
420 423 rootsep = root
421 424 else:
422 425 rootsep = root + os.sep
423 426 name = myname
424 427 if not os.path.isabs(name):
425 428 name = os.path.join(root, cwd, name)
426 429 name = os.path.normpath(name)
427 430 if auditor is None:
428 431 auditor = pathauditor(root)
429 432 if name != rootsep and name.startswith(rootsep):
430 433 name = name[len(rootsep):]
431 434 auditor(name)
432 435 return util.pconvert(name)
433 436 elif name == root:
434 437 return ''
435 438 else:
436 439 # Determine whether `name' is in the hierarchy at or beneath `root',
437 440 # by iterating name=dirname(name) until that causes no change (can't
438 441 # check name == '/', because that doesn't work on windows). The list
439 442 # `rel' holds the reversed list of components making up the relative
440 443 # file name we want.
441 444 rel = []
442 445 while True:
443 446 try:
444 447 s = util.samefile(name, root)
445 448 except OSError:
446 449 s = False
447 450 if s:
448 451 if not rel:
449 452 # name was actually the same as root (maybe a symlink)
450 453 return ''
451 454 rel.reverse()
452 455 name = os.path.join(*rel)
453 456 auditor(name)
454 457 return util.pconvert(name)
455 458 dirname, basename = util.split(name)
456 459 rel.append(basename)
457 460 if dirname == name:
458 461 break
459 462 name = dirname
460 463
461 464 raise util.Abort(_("%s not under root '%s'") % (myname, root))
462 465
463 466 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
464 467 '''yield every hg repository under path, always recursively.
465 468 The recurse flag will only control recursion into repo working dirs'''
466 469 def errhandler(err):
467 470 if err.filename == path:
468 471 raise err
469 472 samestat = getattr(os.path, 'samestat', None)
470 473 if followsym and samestat is not None:
471 474 def adddir(dirlst, dirname):
472 475 match = False
473 476 dirstat = os.stat(dirname)
474 477 for lstdirstat in dirlst:
475 478 if samestat(dirstat, lstdirstat):
476 479 match = True
477 480 break
478 481 if not match:
479 482 dirlst.append(dirstat)
480 483 return not match
481 484 else:
482 485 followsym = False
483 486
484 487 if (seen_dirs is None) and followsym:
485 488 seen_dirs = []
486 489 adddir(seen_dirs, path)
487 490 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
488 491 dirs.sort()
489 492 if '.hg' in dirs:
490 493 yield root # found a repository
491 494 qroot = os.path.join(root, '.hg', 'patches')
492 495 if os.path.isdir(os.path.join(qroot, '.hg')):
493 496 yield qroot # we have a patch queue repo here
494 497 if recurse:
495 498 # avoid recursing inside the .hg directory
496 499 dirs.remove('.hg')
497 500 else:
498 501 dirs[:] = [] # don't descend further
499 502 elif followsym:
500 503 newdirs = []
501 504 for d in dirs:
502 505 fname = os.path.join(root, d)
503 506 if adddir(seen_dirs, fname):
504 507 if os.path.islink(fname):
505 508 for hgname in walkrepos(fname, True, seen_dirs):
506 509 yield hgname
507 510 else:
508 511 newdirs.append(d)
509 512 dirs[:] = newdirs
510 513
511 514 def osrcpath():
512 515 '''return default os-specific hgrc search path'''
513 516 path = systemrcpath()
514 517 path.extend(userrcpath())
515 518 path = [os.path.normpath(f) for f in path]
516 519 return path
517 520
518 521 _rcpath = None
519 522
520 523 def rcpath():
521 524 '''return hgrc search path. if env var HGRCPATH is set, use it.
522 525 for each item in path, if directory, use files ending in .rc,
523 526 else use item.
524 527 make HGRCPATH empty to only look in .hg/hgrc of current repo.
525 528 if no HGRCPATH, use default os-specific path.'''
526 529 global _rcpath
527 530 if _rcpath is None:
528 531 if 'HGRCPATH' in os.environ:
529 532 _rcpath = []
530 533 for p in os.environ['HGRCPATH'].split(os.pathsep):
531 534 if not p:
532 535 continue
533 536 p = util.expandpath(p)
534 537 if os.path.isdir(p):
535 538 for f, kind in osutil.listdir(p):
536 539 if f.endswith('.rc'):
537 540 _rcpath.append(os.path.join(p, f))
538 541 else:
539 542 _rcpath.append(p)
540 543 else:
541 544 _rcpath = osrcpath()
542 545 return _rcpath
543 546
544 547 def revsingle(repo, revspec, default='.'):
545 548 if not revspec:
546 549 return repo[default]
547 550
548 551 l = revrange(repo, [revspec])
549 552 if len(l) < 1:
550 553 raise util.Abort(_('empty revision set'))
551 554 return repo[l[-1]]
552 555
553 556 def revpair(repo, revs):
554 557 if not revs:
555 558 return repo.dirstate.p1(), None
556 559
557 560 l = revrange(repo, revs)
558 561
559 562 if len(l) == 0:
560 563 if revs:
561 564 raise util.Abort(_('empty revision range'))
562 565 return repo.dirstate.p1(), None
563 566
564 567 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
565 568 return repo.lookup(l[0]), None
566 569
567 570 return repo.lookup(l[0]), repo.lookup(l[-1])
568 571
569 572 _revrangesep = ':'
570 573
571 574 def revrange(repo, revs):
572 575 """Yield revision as strings from a list of revision specifications."""
573 576
574 577 def revfix(repo, val, defval):
575 578 if not val and val != 0 and defval is not None:
576 579 return defval
577 580 return repo[val].rev()
578 581
579 582 seen, l = set(), []
580 583 for spec in revs:
581 584 if l and not seen:
582 585 seen = set(l)
583 586 # attempt to parse old-style ranges first to deal with
584 587 # things like old-tag which contain query metacharacters
585 588 try:
586 589 if isinstance(spec, int):
587 590 seen.add(spec)
588 591 l.append(spec)
589 592 continue
590 593
591 594 if _revrangesep in spec:
592 595 start, end = spec.split(_revrangesep, 1)
593 596 start = revfix(repo, start, 0)
594 597 end = revfix(repo, end, len(repo) - 1)
595 598 if end == nullrev and start <= 0:
596 599 start = nullrev
597 600 rangeiter = repo.changelog.revs(start, end)
598 601 if not seen and not l:
599 602 # by far the most common case: revs = ["-1:0"]
600 603 l = list(rangeiter)
601 604 # defer syncing seen until next iteration
602 605 continue
603 606 newrevs = set(rangeiter)
604 607 if seen:
605 608 newrevs.difference_update(seen)
606 609 seen.update(newrevs)
607 610 else:
608 611 seen = newrevs
609 612 l.extend(sorted(newrevs, reverse=start > end))
610 613 continue
611 614 elif spec and spec in repo: # single unquoted rev
612 615 rev = revfix(repo, spec, None)
613 616 if rev in seen:
614 617 continue
615 618 seen.add(rev)
616 619 l.append(rev)
617 620 continue
618 621 except error.RepoLookupError:
619 622 pass
620 623
621 624 # fall through to new-style queries if old-style fails
622 625 m = revset.match(repo.ui, spec)
623 626 dl = [r for r in m(repo, list(repo)) if r not in seen]
624 627 l.extend(dl)
625 628 seen.update(dl)
626 629
627 630 return l
628 631
629 632 def expandpats(pats):
630 633 if not util.expandglobs:
631 634 return list(pats)
632 635 ret = []
633 636 for p in pats:
634 637 kind, name = matchmod._patsplit(p, None)
635 638 if kind is None:
636 639 try:
637 640 globbed = glob.glob(name)
638 641 except re.error:
639 642 globbed = [name]
640 643 if globbed:
641 644 ret.extend(globbed)
642 645 continue
643 646 ret.append(p)
644 647 return ret
645 648
646 649 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
647 650 if pats == ("",):
648 651 pats = []
649 652 if not globbed and default == 'relpath':
650 653 pats = expandpats(pats or [])
651 654
652 655 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
653 656 default)
654 657 def badfn(f, msg):
655 658 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
656 659 m.bad = badfn
657 660 return m, pats
658 661
659 662 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
660 663 return matchandpats(ctx, pats, opts, globbed, default)[0]
661 664
662 665 def matchall(repo):
663 666 return matchmod.always(repo.root, repo.getcwd())
664 667
665 668 def matchfiles(repo, files):
666 669 return matchmod.exact(repo.root, repo.getcwd(), files)
667 670
668 671 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
669 672 if dry_run is None:
670 673 dry_run = opts.get('dry_run')
671 674 if similarity is None:
672 675 similarity = float(opts.get('similarity') or 0)
673 676 # we'd use status here, except handling of symlinks and ignore is tricky
674 677 added, unknown, deleted, removed = [], [], [], []
675 678 audit_path = pathauditor(repo.root)
676 679 m = match(repo[None], pats, opts)
677 680 rejected = []
678 681 m.bad = lambda x, y: rejected.append(x)
679 682
680 683 ctx = repo[None]
681 684 dirstate = repo.dirstate
682 685 walkresults = dirstate.walk(m, sorted(ctx.substate), True, False)
683 686 for abs, st in walkresults.iteritems():
684 687 dstate = dirstate[abs]
685 688 if dstate == '?' and audit_path.check(abs):
686 689 unknown.append(abs)
687 690 elif dstate != 'r' and not st:
688 691 deleted.append(abs)
689 692 # for finding renames
690 693 elif dstate == 'r':
691 694 removed.append(abs)
692 695 elif dstate == 'a':
693 696 added.append(abs)
694 697
695 698 unknownset = set(unknown)
696 699 toprint = unknownset.copy()
697 700 toprint.update(deleted)
698 701 for abs in sorted(toprint):
699 702 if repo.ui.verbose or not m.exact(abs):
700 703 rel = m.rel(abs)
701 704 if abs in unknownset:
702 705 status = _('adding %s\n') % ((pats and rel) or abs)
703 706 else:
704 707 status = _('removing %s\n') % ((pats and rel) or abs)
705 708 repo.ui.status(status)
706 709
707 710 copies = {}
708 711 if similarity > 0:
709 712 for old, new, score in similar.findrenames(repo,
710 713 added + unknown, removed + deleted, similarity):
711 714 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
712 715 repo.ui.status(_('recording removal of %s as rename to %s '
713 716 '(%d%% similar)\n') %
714 717 (m.rel(old), m.rel(new), score * 100))
715 718 copies[new] = old
716 719
717 720 if not dry_run:
718 721 wctx = repo[None]
719 722 wlock = repo.wlock()
720 723 try:
721 724 wctx.forget(deleted)
722 725 wctx.add(unknown)
723 726 for new, old in copies.iteritems():
724 727 wctx.copy(old, new)
725 728 finally:
726 729 wlock.release()
727 730
728 731 for f in rejected:
729 732 if f in m.files():
730 733 return 1
731 734 return 0
732 735
733 736 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
734 737 """Update the dirstate to reflect the intent of copying src to dst. For
735 738 different reasons it might not end with dst being marked as copied from src.
736 739 """
737 740 origsrc = repo.dirstate.copied(src) or src
738 741 if dst == origsrc: # copying back a copy?
739 742 if repo.dirstate[dst] not in 'mn' and not dryrun:
740 743 repo.dirstate.normallookup(dst)
741 744 else:
742 745 if repo.dirstate[origsrc] == 'a' and origsrc == src:
743 746 if not ui.quiet:
744 747 ui.warn(_("%s has not been committed yet, so no copy "
745 748 "data will be stored for %s.\n")
746 749 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
747 750 if repo.dirstate[dst] in '?r' and not dryrun:
748 751 wctx.add([dst])
749 752 elif not dryrun:
750 753 wctx.copy(origsrc, dst)
751 754
752 755 def readrequires(opener, supported):
753 756 '''Reads and parses .hg/requires and checks if all entries found
754 757 are in the list of supported features.'''
755 758 requirements = set(opener.read("requires").splitlines())
756 759 missings = []
757 760 for r in requirements:
758 761 if r not in supported:
759 762 if not r or not r[0].isalnum():
760 763 raise error.RequirementError(_(".hg/requires file is corrupt"))
761 764 missings.append(r)
762 765 missings.sort()
763 766 if missings:
764 767 raise error.RequirementError(
765 768 _("unknown repository format: requires features '%s' (upgrade "
766 769 "Mercurial)") % "', '".join(missings))
767 770 return requirements
768 771
769 772 class filecacheentry(object):
770 773 def __init__(self, path, stat=True):
771 774 self.path = path
772 775 self.cachestat = None
773 776 self._cacheable = None
774 777
775 778 if stat:
776 779 self.cachestat = filecacheentry.stat(self.path)
777 780
778 781 if self.cachestat:
779 782 self._cacheable = self.cachestat.cacheable()
780 783 else:
781 784 # None means we don't know yet
782 785 self._cacheable = None
783 786
784 787 def refresh(self):
785 788 if self.cacheable():
786 789 self.cachestat = filecacheentry.stat(self.path)
787 790
788 791 def cacheable(self):
789 792 if self._cacheable is not None:
790 793 return self._cacheable
791 794
792 795 # we don't know yet, assume it is for now
793 796 return True
794 797
795 798 def changed(self):
796 799 # no point in going further if we can't cache it
797 800 if not self.cacheable():
798 801 return True
799 802
800 803 newstat = filecacheentry.stat(self.path)
801 804
802 805 # we may not know if it's cacheable yet, check again now
803 806 if newstat and self._cacheable is None:
804 807 self._cacheable = newstat.cacheable()
805 808
806 809 # check again
807 810 if not self._cacheable:
808 811 return True
809 812
810 813 if self.cachestat != newstat:
811 814 self.cachestat = newstat
812 815 return True
813 816 else:
814 817 return False
815 818
816 819 @staticmethod
817 820 def stat(path):
818 821 try:
819 822 return util.cachestat(path)
820 823 except OSError, e:
821 824 if e.errno != errno.ENOENT:
822 825 raise
823 826
824 827 class filecache(object):
825 828 '''A property like decorator that tracks a file under .hg/ for updates.
826 829
827 830 Records stat info when called in _filecache.
828 831
829 832 On subsequent calls, compares old stat info with new info, and recreates
830 833 the object when needed, updating the new stat info in _filecache.
831 834
832 835 Mercurial either atomic renames or appends for files under .hg,
833 836 so to ensure the cache is reliable we need the filesystem to be able
834 837 to tell us if a file has been replaced. If it can't, we fallback to
835 838 recreating the object on every call (essentially the same behaviour as
836 839 propertycache).'''
837 840 def __init__(self, path):
838 841 self.path = path
839 842
840 843 def join(self, obj, fname):
841 844 """Used to compute the runtime path of the cached file.
842 845
843 846 Users should subclass filecache and provide their own version of this
844 847 function to call the appropriate join function on 'obj' (an instance
845 848 of the class that its member function was decorated).
846 849 """
847 850 return obj.join(fname)
848 851
849 852 def __call__(self, func):
850 853 self.func = func
851 854 self.name = func.__name__
852 855 return self
853 856
854 857 def __get__(self, obj, type=None):
855 858 # do we need to check if the file changed?
856 859 if self.name in obj.__dict__:
857 860 assert self.name in obj._filecache, self.name
858 861 return obj.__dict__[self.name]
859 862
860 863 entry = obj._filecache.get(self.name)
861 864
862 865 if entry:
863 866 if entry.changed():
864 867 entry.obj = self.func(obj)
865 868 else:
866 869 path = self.join(obj, self.path)
867 870
868 871 # We stat -before- creating the object so our cache doesn't lie if
869 872 # a writer modified between the time we read and stat
870 873 entry = filecacheentry(path)
871 874 entry.obj = self.func(obj)
872 875
873 876 obj._filecache[self.name] = entry
874 877
875 878 obj.__dict__[self.name] = entry.obj
876 879 return entry.obj
877 880
878 881 def __set__(self, obj, value):
879 882 if self.name not in obj._filecache:
880 883 # we add an entry for the missing value because X in __dict__
881 884 # implies X in _filecache
882 885 ce = filecacheentry(self.join(obj, self.path), False)
883 886 obj._filecache[self.name] = ce
884 887 else:
885 888 ce = obj._filecache[self.name]
886 889
887 890 ce.obj = value # update cached copy
888 891 obj.__dict__[self.name] = value # update copy returned by obj.x
889 892
890 893 def __delete__(self, obj):
891 894 try:
892 895 del obj.__dict__[self.name]
893 896 except KeyError:
894 897 raise AttributeError(self.name)
895 898
896 899 class dirs(object):
897 900 '''a multiset of directory names from a dirstate or manifest'''
898 901
899 902 def __init__(self, map, skip=None):
900 903 self._dirs = {}
901 904 addpath = self.addpath
902 905 if util.safehasattr(map, 'iteritems') and skip is not None:
903 906 for f, s in map.iteritems():
904 907 if s[0] != skip:
905 908 addpath(f)
906 909 else:
907 910 for f in map:
908 911 addpath(f)
909 912
910 913 def addpath(self, path):
911 914 dirs = self._dirs
912 915 for base in finddirs(path):
913 916 if base in dirs:
914 917 dirs[base] += 1
915 918 return
916 919 dirs[base] = 1
917 920
918 921 def delpath(self, path):
919 922 dirs = self._dirs
920 923 for base in finddirs(path):
921 924 if dirs[base] > 1:
922 925 dirs[base] -= 1
923 926 return
924 927 del dirs[base]
925 928
926 929 def __iter__(self):
927 930 return self._dirs.iterkeys()
928 931
929 932 def __contains__(self, d):
930 933 return d in self._dirs
931 934
932 935 if util.safehasattr(parsers, 'dirs'):
933 936 dirs = parsers.dirs
934 937
935 938 def finddirs(path):
936 939 pos = path.rfind('/')
937 940 while pos != -1:
938 941 yield path[:pos]
939 942 pos = path.rfind('/', 0, pos)
General Comments 0
You need to be logged in to leave comments. Login now