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