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