##// END OF EJS Templates
bundle2: feed a binary stream to `peer.unbundle`...
Pierre-Yves David -
r21070:408877d4 default
parent child Browse files
Show More
@@ -1,708 +1,707 b''
1 1 # exchange.py - utility to exchange data between repos.
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
8 8 from i18n import _
9 9 from node import hex, nullid
10 10 import errno
11 11 import util, scmutil, changegroup, base85
12 12 import discovery, phases, obsolete, bookmarks, bundle2
13 13
14 14 def readbundle(ui, fh, fname, vfs=None):
15 15 header = changegroup.readexactly(fh, 4)
16 16
17 17 alg = None
18 18 if not fname:
19 19 fname = "stream"
20 20 if not header.startswith('HG') and header.startswith('\0'):
21 21 fh = changegroup.headerlessfixup(fh, header)
22 22 header = "HG10"
23 23 alg = 'UN'
24 24 elif vfs:
25 25 fname = vfs.join(fname)
26 26
27 27 magic, version = header[0:2], header[2:4]
28 28
29 29 if magic != 'HG':
30 30 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
31 31 if version == '10':
32 32 if alg is None:
33 33 alg = changegroup.readexactly(fh, 2)
34 34 return changegroup.unbundle10(fh, alg)
35 35 elif version == '20':
36 36 return bundle2.unbundle20(ui, fh, header=magic + version)
37 37 else:
38 38 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
39 39
40 40
41 41 class pushoperation(object):
42 42 """A object that represent a single push operation
43 43
44 44 It purpose is to carry push related state and very common operation.
45 45
46 46 A new should be created at the beginning of each push and discarded
47 47 afterward.
48 48 """
49 49
50 50 def __init__(self, repo, remote, force=False, revs=None, newbranch=False):
51 51 # repo we push from
52 52 self.repo = repo
53 53 self.ui = repo.ui
54 54 # repo we push to
55 55 self.remote = remote
56 56 # force option provided
57 57 self.force = force
58 58 # revs to be pushed (None is "all")
59 59 self.revs = revs
60 60 # allow push of new branch
61 61 self.newbranch = newbranch
62 62 # did a local lock get acquired?
63 63 self.locallocked = None
64 64 # Integer version of the push result
65 65 # - None means nothing to push
66 66 # - 0 means HTTP error
67 67 # - 1 means we pushed and remote head count is unchanged *or*
68 68 # we have outgoing changesets but refused to push
69 69 # - other values as described by addchangegroup()
70 70 self.ret = None
71 71 # discover.outgoing object (contains common and outgoing data)
72 72 self.outgoing = None
73 73 # all remote heads before the push
74 74 self.remoteheads = None
75 75 # testable as a boolean indicating if any nodes are missing locally.
76 76 self.incoming = None
77 77 # set of all heads common after changeset bundle push
78 78 self.commonheads = None
79 79
80 80 def push(repo, remote, force=False, revs=None, newbranch=False):
81 81 '''Push outgoing changesets (limited by revs) from a local
82 82 repository to remote. Return an integer:
83 83 - None means nothing to push
84 84 - 0 means HTTP error
85 85 - 1 means we pushed and remote head count is unchanged *or*
86 86 we have outgoing changesets but refused to push
87 87 - other values as described by addchangegroup()
88 88 '''
89 89 pushop = pushoperation(repo, remote, force, revs, newbranch)
90 90 if pushop.remote.local():
91 91 missing = (set(pushop.repo.requirements)
92 92 - pushop.remote.local().supported)
93 93 if missing:
94 94 msg = _("required features are not"
95 95 " supported in the destination:"
96 96 " %s") % (', '.join(sorted(missing)))
97 97 raise util.Abort(msg)
98 98
99 99 # there are two ways to push to remote repo:
100 100 #
101 101 # addchangegroup assumes local user can lock remote
102 102 # repo (local filesystem, old ssh servers).
103 103 #
104 104 # unbundle assumes local user cannot lock remote repo (new ssh
105 105 # servers, http servers).
106 106
107 107 if not pushop.remote.canpush():
108 108 raise util.Abort(_("destination does not support push"))
109 109 # get local lock as we might write phase data
110 110 locallock = None
111 111 try:
112 112 locallock = pushop.repo.lock()
113 113 pushop.locallocked = True
114 114 except IOError, err:
115 115 pushop.locallocked = False
116 116 if err.errno != errno.EACCES:
117 117 raise
118 118 # source repo cannot be locked.
119 119 # We do not abort the push, but just disable the local phase
120 120 # synchronisation.
121 121 msg = 'cannot lock source repository: %s\n' % err
122 122 pushop.ui.debug(msg)
123 123 try:
124 124 pushop.repo.checkpush(pushop)
125 125 lock = None
126 126 unbundle = pushop.remote.capable('unbundle')
127 127 if not unbundle:
128 128 lock = pushop.remote.lock()
129 129 try:
130 130 _pushdiscovery(pushop)
131 131 if _pushcheckoutgoing(pushop):
132 132 pushop.repo.prepushoutgoinghooks(pushop.repo,
133 133 pushop.remote,
134 134 pushop.outgoing)
135 135 if pushop.remote.capable('bundle2'):
136 136 _pushbundle2(pushop)
137 137 else:
138 138 _pushchangeset(pushop)
139 139 _pushcomputecommonheads(pushop)
140 140 _pushsyncphase(pushop)
141 141 _pushobsolete(pushop)
142 142 finally:
143 143 if lock is not None:
144 144 lock.release()
145 145 finally:
146 146 if locallock is not None:
147 147 locallock.release()
148 148
149 149 _pushbookmark(pushop)
150 150 return pushop.ret
151 151
152 152 def _pushdiscovery(pushop):
153 153 # discovery
154 154 unfi = pushop.repo.unfiltered()
155 155 fci = discovery.findcommonincoming
156 156 commoninc = fci(unfi, pushop.remote, force=pushop.force)
157 157 common, inc, remoteheads = commoninc
158 158 fco = discovery.findcommonoutgoing
159 159 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
160 160 commoninc=commoninc, force=pushop.force)
161 161 pushop.outgoing = outgoing
162 162 pushop.remoteheads = remoteheads
163 163 pushop.incoming = inc
164 164
165 165 def _pushcheckoutgoing(pushop):
166 166 outgoing = pushop.outgoing
167 167 unfi = pushop.repo.unfiltered()
168 168 if not outgoing.missing:
169 169 # nothing to push
170 170 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
171 171 return False
172 172 # something to push
173 173 if not pushop.force:
174 174 # if repo.obsstore == False --> no obsolete
175 175 # then, save the iteration
176 176 if unfi.obsstore:
177 177 # this message are here for 80 char limit reason
178 178 mso = _("push includes obsolete changeset: %s!")
179 179 mst = "push includes %s changeset: %s!"
180 180 # plain versions for i18n tool to detect them
181 181 _("push includes unstable changeset: %s!")
182 182 _("push includes bumped changeset: %s!")
183 183 _("push includes divergent changeset: %s!")
184 184 # If we are to push if there is at least one
185 185 # obsolete or unstable changeset in missing, at
186 186 # least one of the missinghead will be obsolete or
187 187 # unstable. So checking heads only is ok
188 188 for node in outgoing.missingheads:
189 189 ctx = unfi[node]
190 190 if ctx.obsolete():
191 191 raise util.Abort(mso % ctx)
192 192 elif ctx.troubled():
193 193 raise util.Abort(_(mst)
194 194 % (ctx.troubles()[0],
195 195 ctx))
196 196 newbm = pushop.ui.configlist('bookmarks', 'pushing')
197 197 discovery.checkheads(unfi, pushop.remote, outgoing,
198 198 pushop.remoteheads,
199 199 pushop.newbranch,
200 200 bool(pushop.incoming),
201 201 newbm)
202 202 return True
203 203
204 204 def _pushbundle2(pushop):
205 205 """push data to the remote using bundle2
206 206
207 207 The only currently supported type of data is changegroup but this will
208 208 evolve in the future."""
209 209 # Send known head to the server for race detection.
210 210 bundler = bundle2.bundle20(pushop.ui)
211 211 if not pushop.force:
212 212 part = bundle2.bundlepart('CHECK:HEADS', data=iter(pushop.remoteheads))
213 213 bundler.addpart(part)
214 214 # add the changegroup bundle
215 215 cg = changegroup.getlocalbundle(pushop.repo, 'push', pushop.outgoing)
216 216 cgpart = bundle2.bundlepart('CHANGEGROUP', data=cg.getchunks())
217 217 bundler.addpart(cgpart)
218 218 stream = util.chunkbuffer(bundler.getchunks())
219 sent = bundle2.unbundle20(pushop.repo.ui, stream)
220 reply = pushop.remote.unbundle(sent, ['force'], 'push')
219 reply = pushop.remote.unbundle(stream, ['force'], 'push')
221 220 try:
222 221 op = bundle2.processbundle(pushop.repo, reply)
223 222 except KeyError, exc:
224 223 raise util.Abort('missing support for %s' % exc)
225 224 cgreplies = op.records.getreplies(cgpart.id)
226 225 assert len(cgreplies['changegroup']) == 1
227 226 pushop.ret = cgreplies['changegroup'][0]['return']
228 227
229 228 def _pushchangeset(pushop):
230 229 """Make the actual push of changeset bundle to remote repo"""
231 230 outgoing = pushop.outgoing
232 231 unbundle = pushop.remote.capable('unbundle')
233 232 # TODO: get bundlecaps from remote
234 233 bundlecaps = None
235 234 # create a changegroup from local
236 235 if pushop.revs is None and not (outgoing.excluded
237 236 or pushop.repo.changelog.filteredrevs):
238 237 # push everything,
239 238 # use the fast path, no race possible on push
240 239 bundler = changegroup.bundle10(pushop.repo, bundlecaps)
241 240 cg = changegroup.getsubset(pushop.repo,
242 241 outgoing,
243 242 bundler,
244 243 'push',
245 244 fastpath=True)
246 245 else:
247 246 cg = changegroup.getlocalbundle(pushop.repo, 'push', outgoing,
248 247 bundlecaps)
249 248
250 249 # apply changegroup to remote
251 250 if unbundle:
252 251 # local repo finds heads on server, finds out what
253 252 # revs it must push. once revs transferred, if server
254 253 # finds it has different heads (someone else won
255 254 # commit/push race), server aborts.
256 255 if pushop.force:
257 256 remoteheads = ['force']
258 257 else:
259 258 remoteheads = pushop.remoteheads
260 259 # ssh: return remote's addchangegroup()
261 260 # http: return remote's addchangegroup() or 0 for error
262 261 pushop.ret = pushop.remote.unbundle(cg, remoteheads,
263 262 'push')
264 263 else:
265 264 # we return an integer indicating remote head count
266 265 # change
267 266 pushop.ret = pushop.remote.addchangegroup(cg, 'push', pushop.repo.url())
268 267
269 268 def _pushcomputecommonheads(pushop):
270 269 unfi = pushop.repo.unfiltered()
271 270 if pushop.ret:
272 271 # push succeed, synchronize target of the push
273 272 cheads = pushop.outgoing.missingheads
274 273 elif pushop.revs is None:
275 274 # All out push fails. synchronize all common
276 275 cheads = pushop.outgoing.commonheads
277 276 else:
278 277 # I want cheads = heads(::missingheads and ::commonheads)
279 278 # (missingheads is revs with secret changeset filtered out)
280 279 #
281 280 # This can be expressed as:
282 281 # cheads = ( (missingheads and ::commonheads)
283 282 # + (commonheads and ::missingheads))"
284 283 # )
285 284 #
286 285 # while trying to push we already computed the following:
287 286 # common = (::commonheads)
288 287 # missing = ((commonheads::missingheads) - commonheads)
289 288 #
290 289 # We can pick:
291 290 # * missingheads part of common (::commonheads)
292 291 common = set(pushop.outgoing.common)
293 292 nm = pushop.repo.changelog.nodemap
294 293 cheads = [node for node in pushop.revs if nm[node] in common]
295 294 # and
296 295 # * commonheads parents on missing
297 296 revset = unfi.set('%ln and parents(roots(%ln))',
298 297 pushop.outgoing.commonheads,
299 298 pushop.outgoing.missing)
300 299 cheads.extend(c.node() for c in revset)
301 300 pushop.commonheads = cheads
302 301
303 302 def _pushsyncphase(pushop):
304 303 """synchronise phase information locally and remotely"""
305 304 unfi = pushop.repo.unfiltered()
306 305 cheads = pushop.commonheads
307 306 if pushop.ret:
308 307 # push succeed, synchronize target of the push
309 308 cheads = pushop.outgoing.missingheads
310 309 elif pushop.revs is None:
311 310 # All out push fails. synchronize all common
312 311 cheads = pushop.outgoing.commonheads
313 312 else:
314 313 # I want cheads = heads(::missingheads and ::commonheads)
315 314 # (missingheads is revs with secret changeset filtered out)
316 315 #
317 316 # This can be expressed as:
318 317 # cheads = ( (missingheads and ::commonheads)
319 318 # + (commonheads and ::missingheads))"
320 319 # )
321 320 #
322 321 # while trying to push we already computed the following:
323 322 # common = (::commonheads)
324 323 # missing = ((commonheads::missingheads) - commonheads)
325 324 #
326 325 # We can pick:
327 326 # * missingheads part of common (::commonheads)
328 327 common = set(pushop.outgoing.common)
329 328 nm = pushop.repo.changelog.nodemap
330 329 cheads = [node for node in pushop.revs if nm[node] in common]
331 330 # and
332 331 # * commonheads parents on missing
333 332 revset = unfi.set('%ln and parents(roots(%ln))',
334 333 pushop.outgoing.commonheads,
335 334 pushop.outgoing.missing)
336 335 cheads.extend(c.node() for c in revset)
337 336 pushop.commonheads = cheads
338 337 # even when we don't push, exchanging phase data is useful
339 338 remotephases = pushop.remote.listkeys('phases')
340 339 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
341 340 and remotephases # server supports phases
342 341 and pushop.ret is None # nothing was pushed
343 342 and remotephases.get('publishing', False)):
344 343 # When:
345 344 # - this is a subrepo push
346 345 # - and remote support phase
347 346 # - and no changeset was pushed
348 347 # - and remote is publishing
349 348 # We may be in issue 3871 case!
350 349 # We drop the possible phase synchronisation done by
351 350 # courtesy to publish changesets possibly locally draft
352 351 # on the remote.
353 352 remotephases = {'publishing': 'True'}
354 353 if not remotephases: # old server or public only reply from non-publishing
355 354 _localphasemove(pushop, cheads)
356 355 # don't push any phase data as there is nothing to push
357 356 else:
358 357 ana = phases.analyzeremotephases(pushop.repo, cheads,
359 358 remotephases)
360 359 pheads, droots = ana
361 360 ### Apply remote phase on local
362 361 if remotephases.get('publishing', False):
363 362 _localphasemove(pushop, cheads)
364 363 else: # publish = False
365 364 _localphasemove(pushop, pheads)
366 365 _localphasemove(pushop, cheads, phases.draft)
367 366 ### Apply local phase on remote
368 367
369 368 # Get the list of all revs draft on remote by public here.
370 369 # XXX Beware that revset break if droots is not strictly
371 370 # XXX root we may want to ensure it is but it is costly
372 371 outdated = unfi.set('heads((%ln::%ln) and public())',
373 372 droots, cheads)
374 373 for newremotehead in outdated:
375 374 r = pushop.remote.pushkey('phases',
376 375 newremotehead.hex(),
377 376 str(phases.draft),
378 377 str(phases.public))
379 378 if not r:
380 379 pushop.ui.warn(_('updating %s to public failed!\n')
381 380 % newremotehead)
382 381
383 382 def _localphasemove(pushop, nodes, phase=phases.public):
384 383 """move <nodes> to <phase> in the local source repo"""
385 384 if pushop.locallocked:
386 385 phases.advanceboundary(pushop.repo, phase, nodes)
387 386 else:
388 387 # repo is not locked, do not change any phases!
389 388 # Informs the user that phases should have been moved when
390 389 # applicable.
391 390 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
392 391 phasestr = phases.phasenames[phase]
393 392 if actualmoves:
394 393 pushop.ui.status(_('cannot lock source repo, skipping '
395 394 'local %s phase update\n') % phasestr)
396 395
397 396 def _pushobsolete(pushop):
398 397 """utility function to push obsolete markers to a remote"""
399 398 pushop.ui.debug('try to push obsolete markers to remote\n')
400 399 repo = pushop.repo
401 400 remote = pushop.remote
402 401 if (obsolete._enabled and repo.obsstore and
403 402 'obsolete' in remote.listkeys('namespaces')):
404 403 rslts = []
405 404 remotedata = repo.listkeys('obsolete')
406 405 for key in sorted(remotedata, reverse=True):
407 406 # reverse sort to ensure we end with dump0
408 407 data = remotedata[key]
409 408 rslts.append(remote.pushkey('obsolete', key, '', data))
410 409 if [r for r in rslts if not r]:
411 410 msg = _('failed to push some obsolete markers!\n')
412 411 repo.ui.warn(msg)
413 412
414 413 def _pushbookmark(pushop):
415 414 """Update bookmark position on remote"""
416 415 ui = pushop.ui
417 416 repo = pushop.repo.unfiltered()
418 417 remote = pushop.remote
419 418 ui.debug("checking for updated bookmarks\n")
420 419 revnums = map(repo.changelog.rev, pushop.revs or [])
421 420 ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
422 421 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
423 422 ) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
424 423 srchex=hex)
425 424
426 425 for b, scid, dcid in advsrc:
427 426 if ancestors and repo[scid].rev() not in ancestors:
428 427 continue
429 428 if remote.pushkey('bookmarks', b, dcid, scid):
430 429 ui.status(_("updating bookmark %s\n") % b)
431 430 else:
432 431 ui.warn(_('updating bookmark %s failed!\n') % b)
433 432
434 433 class pulloperation(object):
435 434 """A object that represent a single pull operation
436 435
437 436 It purpose is to carry push related state and very common operation.
438 437
439 438 A new should be created at the beginning of each pull and discarded
440 439 afterward.
441 440 """
442 441
443 442 def __init__(self, repo, remote, heads=None, force=False):
444 443 # repo we pull into
445 444 self.repo = repo
446 445 # repo we pull from
447 446 self.remote = remote
448 447 # revision we try to pull (None is "all")
449 448 self.heads = heads
450 449 # do we force pull?
451 450 self.force = force
452 451 # the name the pull transaction
453 452 self._trname = 'pull\n' + util.hidepassword(remote.url())
454 453 # hold the transaction once created
455 454 self._tr = None
456 455 # set of common changeset between local and remote before pull
457 456 self.common = None
458 457 # set of pulled head
459 458 self.rheads = None
460 459 # list of missing changeset to fetch remotely
461 460 self.fetch = None
462 461 # result of changegroup pulling (used as return code by pull)
463 462 self.cgresult = None
464 463 # list of step remaining todo (related to future bundle2 usage)
465 464 self.todosteps = set(['changegroup', 'phases', 'obsmarkers'])
466 465
467 466 @util.propertycache
468 467 def pulledsubset(self):
469 468 """heads of the set of changeset target by the pull"""
470 469 # compute target subset
471 470 if self.heads is None:
472 471 # We pulled every thing possible
473 472 # sync on everything common
474 473 c = set(self.common)
475 474 ret = list(self.common)
476 475 for n in self.rheads:
477 476 if n not in c:
478 477 ret.append(n)
479 478 return ret
480 479 else:
481 480 # We pulled a specific subset
482 481 # sync on this subset
483 482 return self.heads
484 483
485 484 def gettransaction(self):
486 485 """get appropriate pull transaction, creating it if needed"""
487 486 if self._tr is None:
488 487 self._tr = self.repo.transaction(self._trname)
489 488 return self._tr
490 489
491 490 def closetransaction(self):
492 491 """close transaction if created"""
493 492 if self._tr is not None:
494 493 self._tr.close()
495 494
496 495 def releasetransaction(self):
497 496 """release transaction if created"""
498 497 if self._tr is not None:
499 498 self._tr.release()
500 499
501 500 def pull(repo, remote, heads=None, force=False):
502 501 pullop = pulloperation(repo, remote, heads, force)
503 502 if pullop.remote.local():
504 503 missing = set(pullop.remote.requirements) - pullop.repo.supported
505 504 if missing:
506 505 msg = _("required features are not"
507 506 " supported in the destination:"
508 507 " %s") % (', '.join(sorted(missing)))
509 508 raise util.Abort(msg)
510 509
511 510 lock = pullop.repo.lock()
512 511 try:
513 512 _pulldiscovery(pullop)
514 513 if pullop.remote.capable('bundle2'):
515 514 _pullbundle2(pullop)
516 515 if 'changegroup' in pullop.todosteps:
517 516 _pullchangeset(pullop)
518 517 if 'phases' in pullop.todosteps:
519 518 _pullphase(pullop)
520 519 if 'obsmarkers' in pullop.todosteps:
521 520 _pullobsolete(pullop)
522 521 pullop.closetransaction()
523 522 finally:
524 523 pullop.releasetransaction()
525 524 lock.release()
526 525
527 526 return pullop.cgresult
528 527
529 528 def _pulldiscovery(pullop):
530 529 """discovery phase for the pull
531 530
532 531 Current handle changeset discovery only, will change handle all discovery
533 532 at some point."""
534 533 tmp = discovery.findcommonincoming(pullop.repo.unfiltered(),
535 534 pullop.remote,
536 535 heads=pullop.heads,
537 536 force=pullop.force)
538 537 pullop.common, pullop.fetch, pullop.rheads = tmp
539 538
540 539 def _pullbundle2(pullop):
541 540 """pull data using bundle2
542 541
543 542 For now, the only supported data are changegroup."""
544 543 kwargs = {'bundlecaps': set(['HG20'])}
545 544 # pulling changegroup
546 545 pullop.todosteps.remove('changegroup')
547 546 if not pullop.fetch:
548 547 pullop.repo.ui.status(_("no changes found\n"))
549 548 pullop.cgresult = 0
550 549 else:
551 550 kwargs['common'] = pullop.common
552 551 kwargs['heads'] = pullop.heads or pullop.rheads
553 552 if pullop.heads is None and list(pullop.common) == [nullid]:
554 553 pullop.repo.ui.status(_("requesting all changes\n"))
555 554 if kwargs.keys() == ['format']:
556 555 return # nothing to pull
557 556 bundle = pullop.remote.getbundle('pull', **kwargs)
558 557 try:
559 558 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
560 559 except KeyError, exc:
561 560 raise util.Abort('missing support for %s' % exc)
562 561 assert len(op.records['changegroup']) == 1
563 562 pullop.cgresult = op.records['changegroup'][0]['return']
564 563
565 564 def _pullchangeset(pullop):
566 565 """pull changeset from unbundle into the local repo"""
567 566 # We delay the open of the transaction as late as possible so we
568 567 # don't open transaction for nothing or you break future useful
569 568 # rollback call
570 569 pullop.todosteps.remove('changegroup')
571 570 if not pullop.fetch:
572 571 pullop.repo.ui.status(_("no changes found\n"))
573 572 pullop.cgresult = 0
574 573 return
575 574 pullop.gettransaction()
576 575 if pullop.heads is None and list(pullop.common) == [nullid]:
577 576 pullop.repo.ui.status(_("requesting all changes\n"))
578 577 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
579 578 # issue1320, avoid a race if remote changed after discovery
580 579 pullop.heads = pullop.rheads
581 580
582 581 if pullop.remote.capable('getbundle'):
583 582 # TODO: get bundlecaps from remote
584 583 cg = pullop.remote.getbundle('pull', common=pullop.common,
585 584 heads=pullop.heads or pullop.rheads)
586 585 elif pullop.heads is None:
587 586 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
588 587 elif not pullop.remote.capable('changegroupsubset'):
589 588 raise util.Abort(_("partial pull cannot be done because "
590 589 "other repository doesn't support "
591 590 "changegroupsubset."))
592 591 else:
593 592 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
594 593 pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull',
595 594 pullop.remote.url())
596 595
597 596 def _pullphase(pullop):
598 597 # Get remote phases data from remote
599 598 pullop.todosteps.remove('phases')
600 599 remotephases = pullop.remote.listkeys('phases')
601 600 publishing = bool(remotephases.get('publishing', False))
602 601 if remotephases and not publishing:
603 602 # remote is new and unpublishing
604 603 pheads, _dr = phases.analyzeremotephases(pullop.repo,
605 604 pullop.pulledsubset,
606 605 remotephases)
607 606 phases.advanceboundary(pullop.repo, phases.public, pheads)
608 607 phases.advanceboundary(pullop.repo, phases.draft,
609 608 pullop.pulledsubset)
610 609 else:
611 610 # Remote is old or publishing all common changesets
612 611 # should be seen as public
613 612 phases.advanceboundary(pullop.repo, phases.public,
614 613 pullop.pulledsubset)
615 614
616 615 def _pullobsolete(pullop):
617 616 """utility function to pull obsolete markers from a remote
618 617
619 618 The `gettransaction` is function that return the pull transaction, creating
620 619 one if necessary. We return the transaction to inform the calling code that
621 620 a new transaction have been created (when applicable).
622 621
623 622 Exists mostly to allow overriding for experimentation purpose"""
624 623 pullop.todosteps.remove('obsmarkers')
625 624 tr = None
626 625 if obsolete._enabled:
627 626 pullop.repo.ui.debug('fetching remote obsolete markers\n')
628 627 remoteobs = pullop.remote.listkeys('obsolete')
629 628 if 'dump0' in remoteobs:
630 629 tr = pullop.gettransaction()
631 630 for key in sorted(remoteobs, reverse=True):
632 631 if key.startswith('dump'):
633 632 data = base85.b85decode(remoteobs[key])
634 633 pullop.repo.obsstore.mergemarkers(tr, data)
635 634 pullop.repo.invalidatevolatilesets()
636 635 return tr
637 636
638 637 def getbundle(repo, source, heads=None, common=None, bundlecaps=None):
639 638 """return a full bundle (with potentially multiple kind of parts)
640 639
641 640 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
642 641 passed. For now, the bundle can contain only changegroup, but this will
643 642 changes when more part type will be available for bundle2.
644 643
645 644 This is different from changegroup.getbundle that only returns an HG10
646 645 changegroup bundle. They may eventually get reunited in the future when we
647 646 have a clearer idea of the API we what to query different data.
648 647
649 648 The implementation is at a very early stage and will get massive rework
650 649 when the API of bundle is refined.
651 650 """
652 651 # build bundle here.
653 652 cg = changegroup.getbundle(repo, source, heads=heads,
654 653 common=common, bundlecaps=bundlecaps)
655 654 if bundlecaps is None or 'HG20' not in bundlecaps:
656 655 return cg
657 656 # very crude first implementation,
658 657 # the bundle API will change and the generation will be done lazily.
659 658 bundler = bundle2.bundle20(repo.ui)
660 659 part = bundle2.bundlepart('changegroup', data=cg.getchunks())
661 660 bundler.addpart(part)
662 661 return util.chunkbuffer(bundler.getchunks())
663 662
664 663 class PushRaced(RuntimeError):
665 664 """An exception raised during unbundling that indicate a push race"""
666 665
667 666 def check_heads(repo, their_heads, context):
668 667 """check if the heads of a repo have been modified
669 668
670 669 Used by peer for unbundling.
671 670 """
672 671 heads = repo.heads()
673 672 heads_hash = util.sha1(''.join(sorted(heads))).digest()
674 673 if not (their_heads == ['force'] or their_heads == heads or
675 674 their_heads == ['hashed', heads_hash]):
676 675 # someone else committed/pushed/unbundled while we
677 676 # were transferring data
678 677 raise PushRaced('repository changed while %s - '
679 678 'please try again' % context)
680 679
681 680 def unbundle(repo, cg, heads, source, url):
682 681 """Apply a bundle to a repo.
683 682
684 683 this function makes sure the repo is locked during the application and have
685 684 mechanism to check that no push race occurred between the creation of the
686 685 bundle and its application.
687 686
688 687 If the push was raced as PushRaced exception is raised."""
689 688 r = 0
690 689 # need a transaction when processing a bundle2 stream
691 690 tr = None
692 691 lock = repo.lock()
693 692 try:
694 693 check_heads(repo, heads, 'uploading changes')
695 694 # push can proceed
696 695 if util.safehasattr(cg, 'params'):
697 696 tr = repo.transaction('unbundle')
698 697 ret = bundle2.processbundle(repo, cg, lambda: tr)
699 698 tr.close()
700 699 stream = util.chunkbuffer(ret.reply.getchunks())
701 700 r = bundle2.unbundle20(repo.ui, stream)
702 701 else:
703 702 r = changegroup.addchangegroup(repo, cg, source, url)
704 703 finally:
705 704 if tr is not None:
706 705 tr.release()
707 706 lock.release()
708 707 return r
@@ -1,1898 +1,1899 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, pushkey, obsolete, repoview
10 10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
11 11 import lock as lockmod
12 12 import transaction, store, encoding, exchange, bundle2
13 13 import scmutil, util, extensions, hook, error, revset
14 14 import match as matchmod
15 15 import merge as mergemod
16 16 import tags as tagsmod
17 17 from lock import release
18 18 import weakref, errno, os, time, inspect
19 19 import branchmap, pathutil
20 20 propertycache = util.propertycache
21 21 filecache = scmutil.filecache
22 22
23 23 class repofilecache(filecache):
24 24 """All filecache usage on repo are done for logic that should be unfiltered
25 25 """
26 26
27 27 def __get__(self, repo, type=None):
28 28 return super(repofilecache, self).__get__(repo.unfiltered(), type)
29 29 def __set__(self, repo, value):
30 30 return super(repofilecache, self).__set__(repo.unfiltered(), value)
31 31 def __delete__(self, repo):
32 32 return super(repofilecache, self).__delete__(repo.unfiltered())
33 33
34 34 class storecache(repofilecache):
35 35 """filecache for files in the store"""
36 36 def join(self, obj, fname):
37 37 return obj.sjoin(fname)
38 38
39 39 class unfilteredpropertycache(propertycache):
40 40 """propertycache that apply to unfiltered repo only"""
41 41
42 42 def __get__(self, repo, type=None):
43 43 unfi = repo.unfiltered()
44 44 if unfi is repo:
45 45 return super(unfilteredpropertycache, self).__get__(unfi)
46 46 return getattr(unfi, self.name)
47 47
48 48 class filteredpropertycache(propertycache):
49 49 """propertycache that must take filtering in account"""
50 50
51 51 def cachevalue(self, obj, value):
52 52 object.__setattr__(obj, self.name, value)
53 53
54 54
55 55 def hasunfilteredcache(repo, name):
56 56 """check if a repo has an unfilteredpropertycache value for <name>"""
57 57 return name in vars(repo.unfiltered())
58 58
59 59 def unfilteredmethod(orig):
60 60 """decorate method that always need to be run on unfiltered version"""
61 61 def wrapper(repo, *args, **kwargs):
62 62 return orig(repo.unfiltered(), *args, **kwargs)
63 63 return wrapper
64 64
65 65 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
66 66 'bundle2', 'unbundle'))
67 67 legacycaps = moderncaps.union(set(['changegroupsubset']))
68 68
69 69 class localpeer(peer.peerrepository):
70 70 '''peer for a local repo; reflects only the most recent API'''
71 71
72 72 def __init__(self, repo, caps=moderncaps):
73 73 peer.peerrepository.__init__(self)
74 74 self._repo = repo.filtered('served')
75 75 self.ui = repo.ui
76 76 self._caps = repo._restrictcapabilities(caps)
77 77 self.requirements = repo.requirements
78 78 self.supportedformats = repo.supportedformats
79 79
80 80 def close(self):
81 81 self._repo.close()
82 82
83 83 def _capabilities(self):
84 84 return self._caps
85 85
86 86 def local(self):
87 87 return self._repo
88 88
89 89 def canpush(self):
90 90 return True
91 91
92 92 def url(self):
93 93 return self._repo.url()
94 94
95 95 def lookup(self, key):
96 96 return self._repo.lookup(key)
97 97
98 98 def branchmap(self):
99 99 return self._repo.branchmap()
100 100
101 101 def heads(self):
102 102 return self._repo.heads()
103 103
104 104 def known(self, nodes):
105 105 return self._repo.known(nodes)
106 106
107 107 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
108 108 format='HG10'):
109 109 cg = exchange.getbundle(self._repo, source, heads=heads,
110 110 common=common, bundlecaps=bundlecaps)
111 111 if bundlecaps is not None and 'HG20' in bundlecaps:
112 112 # When requesting a bundle2, getbundle returns a stream to make the
113 113 # wire level function happier. We need to build a proper object
114 114 # from it in local peer.
115 115 cg = bundle2.unbundle20(self.ui, cg)
116 116 return cg
117 117
118 118 # TODO We might want to move the next two calls into legacypeer and add
119 119 # unbundle instead.
120 120
121 121 def unbundle(self, cg, heads, url):
122 122 """apply a bundle on a repo
123 123
124 124 This function handles the repo locking itself."""
125 125 try:
126 cg = exchange.readbundle(self.ui, cg, None)
126 127 return exchange.unbundle(self._repo, cg, heads, 'push', url)
127 128 except exchange.PushRaced, exc:
128 129 raise error.ResponseError(_('push failed:'), exc.message)
129 130
130 131 def lock(self):
131 132 return self._repo.lock()
132 133
133 134 def addchangegroup(self, cg, source, url):
134 135 return changegroup.addchangegroup(self._repo, cg, source, url)
135 136
136 137 def pushkey(self, namespace, key, old, new):
137 138 return self._repo.pushkey(namespace, key, old, new)
138 139
139 140 def listkeys(self, namespace):
140 141 return self._repo.listkeys(namespace)
141 142
142 143 def debugwireargs(self, one, two, three=None, four=None, five=None):
143 144 '''used to test argument passing over the wire'''
144 145 return "%s %s %s %s %s" % (one, two, three, four, five)
145 146
146 147 class locallegacypeer(localpeer):
147 148 '''peer extension which implements legacy methods too; used for tests with
148 149 restricted capabilities'''
149 150
150 151 def __init__(self, repo):
151 152 localpeer.__init__(self, repo, caps=legacycaps)
152 153
153 154 def branches(self, nodes):
154 155 return self._repo.branches(nodes)
155 156
156 157 def between(self, pairs):
157 158 return self._repo.between(pairs)
158 159
159 160 def changegroup(self, basenodes, source):
160 161 return changegroup.changegroup(self._repo, basenodes, source)
161 162
162 163 def changegroupsubset(self, bases, heads, source):
163 164 return changegroup.changegroupsubset(self._repo, bases, heads, source)
164 165
165 166 class localrepository(object):
166 167
167 168 supportedformats = set(('revlogv1', 'generaldelta'))
168 169 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
169 170 'dotencode'))
170 171 openerreqs = set(('revlogv1', 'generaldelta'))
171 172 requirements = ['revlogv1']
172 173 filtername = None
173 174
174 175 # a list of (ui, featureset) functions.
175 176 # only functions defined in module of enabled extensions are invoked
176 177 featuresetupfuncs = set()
177 178
178 179 def _baserequirements(self, create):
179 180 return self.requirements[:]
180 181
181 182 def __init__(self, baseui, path=None, create=False):
182 183 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
183 184 self.wopener = self.wvfs
184 185 self.root = self.wvfs.base
185 186 self.path = self.wvfs.join(".hg")
186 187 self.origroot = path
187 188 self.auditor = pathutil.pathauditor(self.root, self._checknested)
188 189 self.vfs = scmutil.vfs(self.path)
189 190 self.opener = self.vfs
190 191 self.baseui = baseui
191 192 self.ui = baseui.copy()
192 193 self.ui.copy = baseui.copy # prevent copying repo configuration
193 194 # A list of callback to shape the phase if no data were found.
194 195 # Callback are in the form: func(repo, roots) --> processed root.
195 196 # This list it to be filled by extension during repo setup
196 197 self._phasedefaults = []
197 198 try:
198 199 self.ui.readconfig(self.join("hgrc"), self.root)
199 200 extensions.loadall(self.ui)
200 201 except IOError:
201 202 pass
202 203
203 204 if self.featuresetupfuncs:
204 205 self.supported = set(self._basesupported) # use private copy
205 206 extmods = set(m.__name__ for n, m
206 207 in extensions.extensions(self.ui))
207 208 for setupfunc in self.featuresetupfuncs:
208 209 if setupfunc.__module__ in extmods:
209 210 setupfunc(self.ui, self.supported)
210 211 else:
211 212 self.supported = self._basesupported
212 213
213 214 if not self.vfs.isdir():
214 215 if create:
215 216 if not self.wvfs.exists():
216 217 self.wvfs.makedirs()
217 218 self.vfs.makedir(notindexed=True)
218 219 requirements = self._baserequirements(create)
219 220 if self.ui.configbool('format', 'usestore', True):
220 221 self.vfs.mkdir("store")
221 222 requirements.append("store")
222 223 if self.ui.configbool('format', 'usefncache', True):
223 224 requirements.append("fncache")
224 225 if self.ui.configbool('format', 'dotencode', True):
225 226 requirements.append('dotencode')
226 227 # create an invalid changelog
227 228 self.vfs.append(
228 229 "00changelog.i",
229 230 '\0\0\0\2' # represents revlogv2
230 231 ' dummy changelog to prevent using the old repo layout'
231 232 )
232 233 if self.ui.configbool('format', 'generaldelta', False):
233 234 requirements.append("generaldelta")
234 235 requirements = set(requirements)
235 236 else:
236 237 raise error.RepoError(_("repository %s not found") % path)
237 238 elif create:
238 239 raise error.RepoError(_("repository %s already exists") % path)
239 240 else:
240 241 try:
241 242 requirements = scmutil.readrequires(self.vfs, self.supported)
242 243 except IOError, inst:
243 244 if inst.errno != errno.ENOENT:
244 245 raise
245 246 requirements = set()
246 247
247 248 self.sharedpath = self.path
248 249 try:
249 250 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
250 251 realpath=True)
251 252 s = vfs.base
252 253 if not vfs.exists():
253 254 raise error.RepoError(
254 255 _('.hg/sharedpath points to nonexistent directory %s') % s)
255 256 self.sharedpath = s
256 257 except IOError, inst:
257 258 if inst.errno != errno.ENOENT:
258 259 raise
259 260
260 261 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
261 262 self.spath = self.store.path
262 263 self.svfs = self.store.vfs
263 264 self.sopener = self.svfs
264 265 self.sjoin = self.store.join
265 266 self.vfs.createmode = self.store.createmode
266 267 self._applyrequirements(requirements)
267 268 if create:
268 269 self._writerequirements()
269 270
270 271
271 272 self._branchcaches = {}
272 273 self.filterpats = {}
273 274 self._datafilters = {}
274 275 self._transref = self._lockref = self._wlockref = None
275 276
276 277 # A cache for various files under .hg/ that tracks file changes,
277 278 # (used by the filecache decorator)
278 279 #
279 280 # Maps a property name to its util.filecacheentry
280 281 self._filecache = {}
281 282
282 283 # hold sets of revision to be filtered
283 284 # should be cleared when something might have changed the filter value:
284 285 # - new changesets,
285 286 # - phase change,
286 287 # - new obsolescence marker,
287 288 # - working directory parent change,
288 289 # - bookmark changes
289 290 self.filteredrevcache = {}
290 291
291 292 def close(self):
292 293 pass
293 294
294 295 def _restrictcapabilities(self, caps):
295 296 # bundle2 is not ready for prime time, drop it unless explicitly
296 297 # required by the tests (or some brave tester)
297 298 if not self.ui.configbool('server', 'bundle2', False):
298 299 caps = set(caps)
299 300 caps.discard('bundle2')
300 301 return caps
301 302
302 303 def _applyrequirements(self, requirements):
303 304 self.requirements = requirements
304 305 self.sopener.options = dict((r, 1) for r in requirements
305 306 if r in self.openerreqs)
306 307 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
307 308 if chunkcachesize is not None:
308 309 self.sopener.options['chunkcachesize'] = chunkcachesize
309 310
310 311 def _writerequirements(self):
311 312 reqfile = self.opener("requires", "w")
312 313 for r in sorted(self.requirements):
313 314 reqfile.write("%s\n" % r)
314 315 reqfile.close()
315 316
316 317 def _checknested(self, path):
317 318 """Determine if path is a legal nested repository."""
318 319 if not path.startswith(self.root):
319 320 return False
320 321 subpath = path[len(self.root) + 1:]
321 322 normsubpath = util.pconvert(subpath)
322 323
323 324 # XXX: Checking against the current working copy is wrong in
324 325 # the sense that it can reject things like
325 326 #
326 327 # $ hg cat -r 10 sub/x.txt
327 328 #
328 329 # if sub/ is no longer a subrepository in the working copy
329 330 # parent revision.
330 331 #
331 332 # However, it can of course also allow things that would have
332 333 # been rejected before, such as the above cat command if sub/
333 334 # is a subrepository now, but was a normal directory before.
334 335 # The old path auditor would have rejected by mistake since it
335 336 # panics when it sees sub/.hg/.
336 337 #
337 338 # All in all, checking against the working copy seems sensible
338 339 # since we want to prevent access to nested repositories on
339 340 # the filesystem *now*.
340 341 ctx = self[None]
341 342 parts = util.splitpath(subpath)
342 343 while parts:
343 344 prefix = '/'.join(parts)
344 345 if prefix in ctx.substate:
345 346 if prefix == normsubpath:
346 347 return True
347 348 else:
348 349 sub = ctx.sub(prefix)
349 350 return sub.checknested(subpath[len(prefix) + 1:])
350 351 else:
351 352 parts.pop()
352 353 return False
353 354
354 355 def peer(self):
355 356 return localpeer(self) # not cached to avoid reference cycle
356 357
357 358 def unfiltered(self):
358 359 """Return unfiltered version of the repository
359 360
360 361 Intended to be overwritten by filtered repo."""
361 362 return self
362 363
363 364 def filtered(self, name):
364 365 """Return a filtered version of a repository"""
365 366 # build a new class with the mixin and the current class
366 367 # (possibly subclass of the repo)
367 368 class proxycls(repoview.repoview, self.unfiltered().__class__):
368 369 pass
369 370 return proxycls(self, name)
370 371
371 372 @repofilecache('bookmarks')
372 373 def _bookmarks(self):
373 374 return bookmarks.bmstore(self)
374 375
375 376 @repofilecache('bookmarks.current')
376 377 def _bookmarkcurrent(self):
377 378 return bookmarks.readcurrent(self)
378 379
379 380 def bookmarkheads(self, bookmark):
380 381 name = bookmark.split('@', 1)[0]
381 382 heads = []
382 383 for mark, n in self._bookmarks.iteritems():
383 384 if mark.split('@', 1)[0] == name:
384 385 heads.append(n)
385 386 return heads
386 387
387 388 @storecache('phaseroots')
388 389 def _phasecache(self):
389 390 return phases.phasecache(self, self._phasedefaults)
390 391
391 392 @storecache('obsstore')
392 393 def obsstore(self):
393 394 store = obsolete.obsstore(self.sopener)
394 395 if store and not obsolete._enabled:
395 396 # message is rare enough to not be translated
396 397 msg = 'obsolete feature not enabled but %i markers found!\n'
397 398 self.ui.warn(msg % len(list(store)))
398 399 return store
399 400
400 401 @storecache('00changelog.i')
401 402 def changelog(self):
402 403 c = changelog.changelog(self.sopener)
403 404 if 'HG_PENDING' in os.environ:
404 405 p = os.environ['HG_PENDING']
405 406 if p.startswith(self.root):
406 407 c.readpending('00changelog.i.a')
407 408 return c
408 409
409 410 @storecache('00manifest.i')
410 411 def manifest(self):
411 412 return manifest.manifest(self.sopener)
412 413
413 414 @repofilecache('dirstate')
414 415 def dirstate(self):
415 416 warned = [0]
416 417 def validate(node):
417 418 try:
418 419 self.changelog.rev(node)
419 420 return node
420 421 except error.LookupError:
421 422 if not warned[0]:
422 423 warned[0] = True
423 424 self.ui.warn(_("warning: ignoring unknown"
424 425 " working parent %s!\n") % short(node))
425 426 return nullid
426 427
427 428 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
428 429
429 430 def __getitem__(self, changeid):
430 431 if changeid is None:
431 432 return context.workingctx(self)
432 433 return context.changectx(self, changeid)
433 434
434 435 def __contains__(self, changeid):
435 436 try:
436 437 return bool(self.lookup(changeid))
437 438 except error.RepoLookupError:
438 439 return False
439 440
440 441 def __nonzero__(self):
441 442 return True
442 443
443 444 def __len__(self):
444 445 return len(self.changelog)
445 446
446 447 def __iter__(self):
447 448 return iter(self.changelog)
448 449
449 450 def revs(self, expr, *args):
450 451 '''Return a list of revisions matching the given revset'''
451 452 expr = revset.formatspec(expr, *args)
452 453 m = revset.match(None, expr)
453 454 return m(self, revset.spanset(self))
454 455
455 456 def set(self, expr, *args):
456 457 '''
457 458 Yield a context for each matching revision, after doing arg
458 459 replacement via revset.formatspec
459 460 '''
460 461 for r in self.revs(expr, *args):
461 462 yield self[r]
462 463
463 464 def url(self):
464 465 return 'file:' + self.root
465 466
466 467 def hook(self, name, throw=False, **args):
467 468 return hook.hook(self.ui, self, name, throw, **args)
468 469
469 470 @unfilteredmethod
470 471 def _tag(self, names, node, message, local, user, date, extra={}):
471 472 if isinstance(names, str):
472 473 names = (names,)
473 474
474 475 branches = self.branchmap()
475 476 for name in names:
476 477 self.hook('pretag', throw=True, node=hex(node), tag=name,
477 478 local=local)
478 479 if name in branches:
479 480 self.ui.warn(_("warning: tag %s conflicts with existing"
480 481 " branch name\n") % name)
481 482
482 483 def writetags(fp, names, munge, prevtags):
483 484 fp.seek(0, 2)
484 485 if prevtags and prevtags[-1] != '\n':
485 486 fp.write('\n')
486 487 for name in names:
487 488 m = munge and munge(name) or name
488 489 if (self._tagscache.tagtypes and
489 490 name in self._tagscache.tagtypes):
490 491 old = self.tags().get(name, nullid)
491 492 fp.write('%s %s\n' % (hex(old), m))
492 493 fp.write('%s %s\n' % (hex(node), m))
493 494 fp.close()
494 495
495 496 prevtags = ''
496 497 if local:
497 498 try:
498 499 fp = self.opener('localtags', 'r+')
499 500 except IOError:
500 501 fp = self.opener('localtags', 'a')
501 502 else:
502 503 prevtags = fp.read()
503 504
504 505 # local tags are stored in the current charset
505 506 writetags(fp, names, None, prevtags)
506 507 for name in names:
507 508 self.hook('tag', node=hex(node), tag=name, local=local)
508 509 return
509 510
510 511 try:
511 512 fp = self.wfile('.hgtags', 'rb+')
512 513 except IOError, e:
513 514 if e.errno != errno.ENOENT:
514 515 raise
515 516 fp = self.wfile('.hgtags', 'ab')
516 517 else:
517 518 prevtags = fp.read()
518 519
519 520 # committed tags are stored in UTF-8
520 521 writetags(fp, names, encoding.fromlocal, prevtags)
521 522
522 523 fp.close()
523 524
524 525 self.invalidatecaches()
525 526
526 527 if '.hgtags' not in self.dirstate:
527 528 self[None].add(['.hgtags'])
528 529
529 530 m = matchmod.exact(self.root, '', ['.hgtags'])
530 531 tagnode = self.commit(message, user, date, extra=extra, match=m)
531 532
532 533 for name in names:
533 534 self.hook('tag', node=hex(node), tag=name, local=local)
534 535
535 536 return tagnode
536 537
537 538 def tag(self, names, node, message, local, user, date):
538 539 '''tag a revision with one or more symbolic names.
539 540
540 541 names is a list of strings or, when adding a single tag, names may be a
541 542 string.
542 543
543 544 if local is True, the tags are stored in a per-repository file.
544 545 otherwise, they are stored in the .hgtags file, and a new
545 546 changeset is committed with the change.
546 547
547 548 keyword arguments:
548 549
549 550 local: whether to store tags in non-version-controlled file
550 551 (default False)
551 552
552 553 message: commit message to use if committing
553 554
554 555 user: name of user to use if committing
555 556
556 557 date: date tuple to use if committing'''
557 558
558 559 if not local:
559 560 for x in self.status()[:5]:
560 561 if '.hgtags' in x:
561 562 raise util.Abort(_('working copy of .hgtags is changed '
562 563 '(please commit .hgtags manually)'))
563 564
564 565 self.tags() # instantiate the cache
565 566 self._tag(names, node, message, local, user, date)
566 567
567 568 @filteredpropertycache
568 569 def _tagscache(self):
569 570 '''Returns a tagscache object that contains various tags related
570 571 caches.'''
571 572
572 573 # This simplifies its cache management by having one decorated
573 574 # function (this one) and the rest simply fetch things from it.
574 575 class tagscache(object):
575 576 def __init__(self):
576 577 # These two define the set of tags for this repository. tags
577 578 # maps tag name to node; tagtypes maps tag name to 'global' or
578 579 # 'local'. (Global tags are defined by .hgtags across all
579 580 # heads, and local tags are defined in .hg/localtags.)
580 581 # They constitute the in-memory cache of tags.
581 582 self.tags = self.tagtypes = None
582 583
583 584 self.nodetagscache = self.tagslist = None
584 585
585 586 cache = tagscache()
586 587 cache.tags, cache.tagtypes = self._findtags()
587 588
588 589 return cache
589 590
590 591 def tags(self):
591 592 '''return a mapping of tag to node'''
592 593 t = {}
593 594 if self.changelog.filteredrevs:
594 595 tags, tt = self._findtags()
595 596 else:
596 597 tags = self._tagscache.tags
597 598 for k, v in tags.iteritems():
598 599 try:
599 600 # ignore tags to unknown nodes
600 601 self.changelog.rev(v)
601 602 t[k] = v
602 603 except (error.LookupError, ValueError):
603 604 pass
604 605 return t
605 606
606 607 def _findtags(self):
607 608 '''Do the hard work of finding tags. Return a pair of dicts
608 609 (tags, tagtypes) where tags maps tag name to node, and tagtypes
609 610 maps tag name to a string like \'global\' or \'local\'.
610 611 Subclasses or extensions are free to add their own tags, but
611 612 should be aware that the returned dicts will be retained for the
612 613 duration of the localrepo object.'''
613 614
614 615 # XXX what tagtype should subclasses/extensions use? Currently
615 616 # mq and bookmarks add tags, but do not set the tagtype at all.
616 617 # Should each extension invent its own tag type? Should there
617 618 # be one tagtype for all such "virtual" tags? Or is the status
618 619 # quo fine?
619 620
620 621 alltags = {} # map tag name to (node, hist)
621 622 tagtypes = {}
622 623
623 624 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
624 625 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
625 626
626 627 # Build the return dicts. Have to re-encode tag names because
627 628 # the tags module always uses UTF-8 (in order not to lose info
628 629 # writing to the cache), but the rest of Mercurial wants them in
629 630 # local encoding.
630 631 tags = {}
631 632 for (name, (node, hist)) in alltags.iteritems():
632 633 if node != nullid:
633 634 tags[encoding.tolocal(name)] = node
634 635 tags['tip'] = self.changelog.tip()
635 636 tagtypes = dict([(encoding.tolocal(name), value)
636 637 for (name, value) in tagtypes.iteritems()])
637 638 return (tags, tagtypes)
638 639
639 640 def tagtype(self, tagname):
640 641 '''
641 642 return the type of the given tag. result can be:
642 643
643 644 'local' : a local tag
644 645 'global' : a global tag
645 646 None : tag does not exist
646 647 '''
647 648
648 649 return self._tagscache.tagtypes.get(tagname)
649 650
650 651 def tagslist(self):
651 652 '''return a list of tags ordered by revision'''
652 653 if not self._tagscache.tagslist:
653 654 l = []
654 655 for t, n in self.tags().iteritems():
655 656 r = self.changelog.rev(n)
656 657 l.append((r, t, n))
657 658 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
658 659
659 660 return self._tagscache.tagslist
660 661
661 662 def nodetags(self, node):
662 663 '''return the tags associated with a node'''
663 664 if not self._tagscache.nodetagscache:
664 665 nodetagscache = {}
665 666 for t, n in self._tagscache.tags.iteritems():
666 667 nodetagscache.setdefault(n, []).append(t)
667 668 for tags in nodetagscache.itervalues():
668 669 tags.sort()
669 670 self._tagscache.nodetagscache = nodetagscache
670 671 return self._tagscache.nodetagscache.get(node, [])
671 672
672 673 def nodebookmarks(self, node):
673 674 marks = []
674 675 for bookmark, n in self._bookmarks.iteritems():
675 676 if n == node:
676 677 marks.append(bookmark)
677 678 return sorted(marks)
678 679
679 680 def branchmap(self):
680 681 '''returns a dictionary {branch: [branchheads]} with branchheads
681 682 ordered by increasing revision number'''
682 683 branchmap.updatecache(self)
683 684 return self._branchcaches[self.filtername]
684 685
685 686 def branchtip(self, branch):
686 687 '''return the tip node for a given branch'''
687 688 try:
688 689 return self.branchmap().branchtip(branch)
689 690 except KeyError:
690 691 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
691 692
692 693 def lookup(self, key):
693 694 return self[key].node()
694 695
695 696 def lookupbranch(self, key, remote=None):
696 697 repo = remote or self
697 698 if key in repo.branchmap():
698 699 return key
699 700
700 701 repo = (remote and remote.local()) and remote or self
701 702 return repo[key].branch()
702 703
703 704 def known(self, nodes):
704 705 nm = self.changelog.nodemap
705 706 pc = self._phasecache
706 707 result = []
707 708 for n in nodes:
708 709 r = nm.get(n)
709 710 resp = not (r is None or pc.phase(self, r) >= phases.secret)
710 711 result.append(resp)
711 712 return result
712 713
713 714 def local(self):
714 715 return self
715 716
716 717 def cancopy(self):
717 718 # so statichttprepo's override of local() works
718 719 if not self.local():
719 720 return False
720 721 if not self.ui.configbool('phases', 'publish', True):
721 722 return True
722 723 # if publishing we can't copy if there is filtered content
723 724 return not self.filtered('visible').changelog.filteredrevs
724 725
725 726 def join(self, f):
726 727 return os.path.join(self.path, f)
727 728
728 729 def wjoin(self, f):
729 730 return os.path.join(self.root, f)
730 731
731 732 def file(self, f):
732 733 if f[0] == '/':
733 734 f = f[1:]
734 735 return filelog.filelog(self.sopener, f)
735 736
736 737 def changectx(self, changeid):
737 738 return self[changeid]
738 739
739 740 def parents(self, changeid=None):
740 741 '''get list of changectxs for parents of changeid'''
741 742 return self[changeid].parents()
742 743
743 744 def setparents(self, p1, p2=nullid):
744 745 copies = self.dirstate.setparents(p1, p2)
745 746 pctx = self[p1]
746 747 if copies:
747 748 # Adjust copy records, the dirstate cannot do it, it
748 749 # requires access to parents manifests. Preserve them
749 750 # only for entries added to first parent.
750 751 for f in copies:
751 752 if f not in pctx and copies[f] in pctx:
752 753 self.dirstate.copy(copies[f], f)
753 754 if p2 == nullid:
754 755 for f, s in sorted(self.dirstate.copies().items()):
755 756 if f not in pctx and s not in pctx:
756 757 self.dirstate.copy(None, f)
757 758
758 759 def filectx(self, path, changeid=None, fileid=None):
759 760 """changeid can be a changeset revision, node, or tag.
760 761 fileid can be a file revision or node."""
761 762 return context.filectx(self, path, changeid, fileid)
762 763
763 764 def getcwd(self):
764 765 return self.dirstate.getcwd()
765 766
766 767 def pathto(self, f, cwd=None):
767 768 return self.dirstate.pathto(f, cwd)
768 769
769 770 def wfile(self, f, mode='r'):
770 771 return self.wopener(f, mode)
771 772
772 773 def _link(self, f):
773 774 return self.wvfs.islink(f)
774 775
775 776 def _loadfilter(self, filter):
776 777 if filter not in self.filterpats:
777 778 l = []
778 779 for pat, cmd in self.ui.configitems(filter):
779 780 if cmd == '!':
780 781 continue
781 782 mf = matchmod.match(self.root, '', [pat])
782 783 fn = None
783 784 params = cmd
784 785 for name, filterfn in self._datafilters.iteritems():
785 786 if cmd.startswith(name):
786 787 fn = filterfn
787 788 params = cmd[len(name):].lstrip()
788 789 break
789 790 if not fn:
790 791 fn = lambda s, c, **kwargs: util.filter(s, c)
791 792 # Wrap old filters not supporting keyword arguments
792 793 if not inspect.getargspec(fn)[2]:
793 794 oldfn = fn
794 795 fn = lambda s, c, **kwargs: oldfn(s, c)
795 796 l.append((mf, fn, params))
796 797 self.filterpats[filter] = l
797 798 return self.filterpats[filter]
798 799
799 800 def _filter(self, filterpats, filename, data):
800 801 for mf, fn, cmd in filterpats:
801 802 if mf(filename):
802 803 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
803 804 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
804 805 break
805 806
806 807 return data
807 808
808 809 @unfilteredpropertycache
809 810 def _encodefilterpats(self):
810 811 return self._loadfilter('encode')
811 812
812 813 @unfilteredpropertycache
813 814 def _decodefilterpats(self):
814 815 return self._loadfilter('decode')
815 816
816 817 def adddatafilter(self, name, filter):
817 818 self._datafilters[name] = filter
818 819
819 820 def wread(self, filename):
820 821 if self._link(filename):
821 822 data = self.wvfs.readlink(filename)
822 823 else:
823 824 data = self.wopener.read(filename)
824 825 return self._filter(self._encodefilterpats, filename, data)
825 826
826 827 def wwrite(self, filename, data, flags):
827 828 data = self._filter(self._decodefilterpats, filename, data)
828 829 if 'l' in flags:
829 830 self.wopener.symlink(data, filename)
830 831 else:
831 832 self.wopener.write(filename, data)
832 833 if 'x' in flags:
833 834 self.wvfs.setflags(filename, False, True)
834 835
835 836 def wwritedata(self, filename, data):
836 837 return self._filter(self._decodefilterpats, filename, data)
837 838
838 839 def transaction(self, desc, report=None):
839 840 tr = self._transref and self._transref() or None
840 841 if tr and tr.running():
841 842 return tr.nest()
842 843
843 844 # abort here if the journal already exists
844 845 if self.svfs.exists("journal"):
845 846 raise error.RepoError(
846 847 _("abandoned transaction found - run hg recover"))
847 848
848 849 def onclose():
849 850 self.store.write(tr)
850 851
851 852 self._writejournal(desc)
852 853 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
853 854 rp = report and report or self.ui.warn
854 855 tr = transaction.transaction(rp, self.sopener,
855 856 "journal",
856 857 aftertrans(renames),
857 858 self.store.createmode,
858 859 onclose)
859 860 self._transref = weakref.ref(tr)
860 861 return tr
861 862
862 863 def _journalfiles(self):
863 864 return ((self.svfs, 'journal'),
864 865 (self.vfs, 'journal.dirstate'),
865 866 (self.vfs, 'journal.branch'),
866 867 (self.vfs, 'journal.desc'),
867 868 (self.vfs, 'journal.bookmarks'),
868 869 (self.svfs, 'journal.phaseroots'))
869 870
870 871 def undofiles(self):
871 872 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
872 873
873 874 def _writejournal(self, desc):
874 875 self.opener.write("journal.dirstate",
875 876 self.opener.tryread("dirstate"))
876 877 self.opener.write("journal.branch",
877 878 encoding.fromlocal(self.dirstate.branch()))
878 879 self.opener.write("journal.desc",
879 880 "%d\n%s\n" % (len(self), desc))
880 881 self.opener.write("journal.bookmarks",
881 882 self.opener.tryread("bookmarks"))
882 883 self.sopener.write("journal.phaseroots",
883 884 self.sopener.tryread("phaseroots"))
884 885
885 886 def recover(self):
886 887 lock = self.lock()
887 888 try:
888 889 if self.svfs.exists("journal"):
889 890 self.ui.status(_("rolling back interrupted transaction\n"))
890 891 transaction.rollback(self.sopener, "journal",
891 892 self.ui.warn)
892 893 self.invalidate()
893 894 return True
894 895 else:
895 896 self.ui.warn(_("no interrupted transaction available\n"))
896 897 return False
897 898 finally:
898 899 lock.release()
899 900
900 901 def rollback(self, dryrun=False, force=False):
901 902 wlock = lock = None
902 903 try:
903 904 wlock = self.wlock()
904 905 lock = self.lock()
905 906 if self.svfs.exists("undo"):
906 907 return self._rollback(dryrun, force)
907 908 else:
908 909 self.ui.warn(_("no rollback information available\n"))
909 910 return 1
910 911 finally:
911 912 release(lock, wlock)
912 913
913 914 @unfilteredmethod # Until we get smarter cache management
914 915 def _rollback(self, dryrun, force):
915 916 ui = self.ui
916 917 try:
917 918 args = self.opener.read('undo.desc').splitlines()
918 919 (oldlen, desc, detail) = (int(args[0]), args[1], None)
919 920 if len(args) >= 3:
920 921 detail = args[2]
921 922 oldtip = oldlen - 1
922 923
923 924 if detail and ui.verbose:
924 925 msg = (_('repository tip rolled back to revision %s'
925 926 ' (undo %s: %s)\n')
926 927 % (oldtip, desc, detail))
927 928 else:
928 929 msg = (_('repository tip rolled back to revision %s'
929 930 ' (undo %s)\n')
930 931 % (oldtip, desc))
931 932 except IOError:
932 933 msg = _('rolling back unknown transaction\n')
933 934 desc = None
934 935
935 936 if not force and self['.'] != self['tip'] and desc == 'commit':
936 937 raise util.Abort(
937 938 _('rollback of last commit while not checked out '
938 939 'may lose data'), hint=_('use -f to force'))
939 940
940 941 ui.status(msg)
941 942 if dryrun:
942 943 return 0
943 944
944 945 parents = self.dirstate.parents()
945 946 self.destroying()
946 947 transaction.rollback(self.sopener, 'undo', ui.warn)
947 948 if self.vfs.exists('undo.bookmarks'):
948 949 self.vfs.rename('undo.bookmarks', 'bookmarks')
949 950 if self.svfs.exists('undo.phaseroots'):
950 951 self.svfs.rename('undo.phaseroots', 'phaseroots')
951 952 self.invalidate()
952 953
953 954 parentgone = (parents[0] not in self.changelog.nodemap or
954 955 parents[1] not in self.changelog.nodemap)
955 956 if parentgone:
956 957 self.vfs.rename('undo.dirstate', 'dirstate')
957 958 try:
958 959 branch = self.opener.read('undo.branch')
959 960 self.dirstate.setbranch(encoding.tolocal(branch))
960 961 except IOError:
961 962 ui.warn(_('named branch could not be reset: '
962 963 'current branch is still \'%s\'\n')
963 964 % self.dirstate.branch())
964 965
965 966 self.dirstate.invalidate()
966 967 parents = tuple([p.rev() for p in self.parents()])
967 968 if len(parents) > 1:
968 969 ui.status(_('working directory now based on '
969 970 'revisions %d and %d\n') % parents)
970 971 else:
971 972 ui.status(_('working directory now based on '
972 973 'revision %d\n') % parents)
973 974 # TODO: if we know which new heads may result from this rollback, pass
974 975 # them to destroy(), which will prevent the branchhead cache from being
975 976 # invalidated.
976 977 self.destroyed()
977 978 return 0
978 979
979 980 def invalidatecaches(self):
980 981
981 982 if '_tagscache' in vars(self):
982 983 # can't use delattr on proxy
983 984 del self.__dict__['_tagscache']
984 985
985 986 self.unfiltered()._branchcaches.clear()
986 987 self.invalidatevolatilesets()
987 988
988 989 def invalidatevolatilesets(self):
989 990 self.filteredrevcache.clear()
990 991 obsolete.clearobscaches(self)
991 992
992 993 def invalidatedirstate(self):
993 994 '''Invalidates the dirstate, causing the next call to dirstate
994 995 to check if it was modified since the last time it was read,
995 996 rereading it if it has.
996 997
997 998 This is different to dirstate.invalidate() that it doesn't always
998 999 rereads the dirstate. Use dirstate.invalidate() if you want to
999 1000 explicitly read the dirstate again (i.e. restoring it to a previous
1000 1001 known good state).'''
1001 1002 if hasunfilteredcache(self, 'dirstate'):
1002 1003 for k in self.dirstate._filecache:
1003 1004 try:
1004 1005 delattr(self.dirstate, k)
1005 1006 except AttributeError:
1006 1007 pass
1007 1008 delattr(self.unfiltered(), 'dirstate')
1008 1009
1009 1010 def invalidate(self):
1010 1011 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1011 1012 for k in self._filecache:
1012 1013 # dirstate is invalidated separately in invalidatedirstate()
1013 1014 if k == 'dirstate':
1014 1015 continue
1015 1016
1016 1017 try:
1017 1018 delattr(unfiltered, k)
1018 1019 except AttributeError:
1019 1020 pass
1020 1021 self.invalidatecaches()
1021 1022 self.store.invalidatecaches()
1022 1023
1023 1024 def invalidateall(self):
1024 1025 '''Fully invalidates both store and non-store parts, causing the
1025 1026 subsequent operation to reread any outside changes.'''
1026 1027 # extension should hook this to invalidate its caches
1027 1028 self.invalidate()
1028 1029 self.invalidatedirstate()
1029 1030
1030 1031 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc):
1031 1032 try:
1032 1033 l = lockmod.lock(vfs, lockname, 0, releasefn, desc=desc)
1033 1034 except error.LockHeld, inst:
1034 1035 if not wait:
1035 1036 raise
1036 1037 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1037 1038 (desc, inst.locker))
1038 1039 # default to 600 seconds timeout
1039 1040 l = lockmod.lock(vfs, lockname,
1040 1041 int(self.ui.config("ui", "timeout", "600")),
1041 1042 releasefn, desc=desc)
1042 1043 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1043 1044 if acquirefn:
1044 1045 acquirefn()
1045 1046 return l
1046 1047
1047 1048 def _afterlock(self, callback):
1048 1049 """add a callback to the current repository lock.
1049 1050
1050 1051 The callback will be executed on lock release."""
1051 1052 l = self._lockref and self._lockref()
1052 1053 if l:
1053 1054 l.postrelease.append(callback)
1054 1055 else:
1055 1056 callback()
1056 1057
1057 1058 def lock(self, wait=True):
1058 1059 '''Lock the repository store (.hg/store) and return a weak reference
1059 1060 to the lock. Use this before modifying the store (e.g. committing or
1060 1061 stripping). If you are opening a transaction, get a lock as well.)'''
1061 1062 l = self._lockref and self._lockref()
1062 1063 if l is not None and l.held:
1063 1064 l.lock()
1064 1065 return l
1065 1066
1066 1067 def unlock():
1067 1068 if hasunfilteredcache(self, '_phasecache'):
1068 1069 self._phasecache.write()
1069 1070 for k, ce in self._filecache.items():
1070 1071 if k == 'dirstate' or k not in self.__dict__:
1071 1072 continue
1072 1073 ce.refresh()
1073 1074
1074 1075 l = self._lock(self.svfs, "lock", wait, unlock,
1075 1076 self.invalidate, _('repository %s') % self.origroot)
1076 1077 self._lockref = weakref.ref(l)
1077 1078 return l
1078 1079
1079 1080 def wlock(self, wait=True):
1080 1081 '''Lock the non-store parts of the repository (everything under
1081 1082 .hg except .hg/store) and return a weak reference to the lock.
1082 1083 Use this before modifying files in .hg.'''
1083 1084 l = self._wlockref and self._wlockref()
1084 1085 if l is not None and l.held:
1085 1086 l.lock()
1086 1087 return l
1087 1088
1088 1089 def unlock():
1089 1090 self.dirstate.write()
1090 1091 self._filecache['dirstate'].refresh()
1091 1092
1092 1093 l = self._lock(self.vfs, "wlock", wait, unlock,
1093 1094 self.invalidatedirstate, _('working directory of %s') %
1094 1095 self.origroot)
1095 1096 self._wlockref = weakref.ref(l)
1096 1097 return l
1097 1098
1098 1099 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1099 1100 """
1100 1101 commit an individual file as part of a larger transaction
1101 1102 """
1102 1103
1103 1104 fname = fctx.path()
1104 1105 text = fctx.data()
1105 1106 flog = self.file(fname)
1106 1107 fparent1 = manifest1.get(fname, nullid)
1107 1108 fparent2 = fparent2o = manifest2.get(fname, nullid)
1108 1109
1109 1110 meta = {}
1110 1111 copy = fctx.renamed()
1111 1112 if copy and copy[0] != fname:
1112 1113 # Mark the new revision of this file as a copy of another
1113 1114 # file. This copy data will effectively act as a parent
1114 1115 # of this new revision. If this is a merge, the first
1115 1116 # parent will be the nullid (meaning "look up the copy data")
1116 1117 # and the second one will be the other parent. For example:
1117 1118 #
1118 1119 # 0 --- 1 --- 3 rev1 changes file foo
1119 1120 # \ / rev2 renames foo to bar and changes it
1120 1121 # \- 2 -/ rev3 should have bar with all changes and
1121 1122 # should record that bar descends from
1122 1123 # bar in rev2 and foo in rev1
1123 1124 #
1124 1125 # this allows this merge to succeed:
1125 1126 #
1126 1127 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1127 1128 # \ / merging rev3 and rev4 should use bar@rev2
1128 1129 # \- 2 --- 4 as the merge base
1129 1130 #
1130 1131
1131 1132 cfname = copy[0]
1132 1133 crev = manifest1.get(cfname)
1133 1134 newfparent = fparent2
1134 1135
1135 1136 if manifest2: # branch merge
1136 1137 if fparent2 == nullid or crev is None: # copied on remote side
1137 1138 if cfname in manifest2:
1138 1139 crev = manifest2[cfname]
1139 1140 newfparent = fparent1
1140 1141
1141 1142 # find source in nearest ancestor if we've lost track
1142 1143 if not crev:
1143 1144 self.ui.debug(" %s: searching for copy revision for %s\n" %
1144 1145 (fname, cfname))
1145 1146 for ancestor in self[None].ancestors():
1146 1147 if cfname in ancestor:
1147 1148 crev = ancestor[cfname].filenode()
1148 1149 break
1149 1150
1150 1151 if crev:
1151 1152 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1152 1153 meta["copy"] = cfname
1153 1154 meta["copyrev"] = hex(crev)
1154 1155 fparent1, fparent2 = nullid, newfparent
1155 1156 else:
1156 1157 self.ui.warn(_("warning: can't find ancestor for '%s' "
1157 1158 "copied from '%s'!\n") % (fname, cfname))
1158 1159
1159 1160 elif fparent1 == nullid:
1160 1161 fparent1, fparent2 = fparent2, nullid
1161 1162 elif fparent2 != nullid:
1162 1163 # is one parent an ancestor of the other?
1163 1164 fparentancestors = flog.commonancestors(fparent1, fparent2)
1164 1165 if fparent1 in fparentancestors:
1165 1166 fparent1, fparent2 = fparent2, nullid
1166 1167 elif fparent2 in fparentancestors:
1167 1168 fparent2 = nullid
1168 1169
1169 1170 # is the file changed?
1170 1171 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1171 1172 changelist.append(fname)
1172 1173 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1173 1174
1174 1175 # are just the flags changed during merge?
1175 1176 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1176 1177 changelist.append(fname)
1177 1178
1178 1179 return fparent1
1179 1180
1180 1181 @unfilteredmethod
1181 1182 def commit(self, text="", user=None, date=None, match=None, force=False,
1182 1183 editor=False, extra={}):
1183 1184 """Add a new revision to current repository.
1184 1185
1185 1186 Revision information is gathered from the working directory,
1186 1187 match can be used to filter the committed files. If editor is
1187 1188 supplied, it is called to get a commit message.
1188 1189 """
1189 1190
1190 1191 def fail(f, msg):
1191 1192 raise util.Abort('%s: %s' % (f, msg))
1192 1193
1193 1194 if not match:
1194 1195 match = matchmod.always(self.root, '')
1195 1196
1196 1197 if not force:
1197 1198 vdirs = []
1198 1199 match.explicitdir = vdirs.append
1199 1200 match.bad = fail
1200 1201
1201 1202 wlock = self.wlock()
1202 1203 try:
1203 1204 wctx = self[None]
1204 1205 merge = len(wctx.parents()) > 1
1205 1206
1206 1207 if (not force and merge and match and
1207 1208 (match.files() or match.anypats())):
1208 1209 raise util.Abort(_('cannot partially commit a merge '
1209 1210 '(do not specify files or patterns)'))
1210 1211
1211 1212 changes = self.status(match=match, clean=force)
1212 1213 if force:
1213 1214 changes[0].extend(changes[6]) # mq may commit unchanged files
1214 1215
1215 1216 # check subrepos
1216 1217 subs = []
1217 1218 commitsubs = set()
1218 1219 newstate = wctx.substate.copy()
1219 1220 # only manage subrepos and .hgsubstate if .hgsub is present
1220 1221 if '.hgsub' in wctx:
1221 1222 # we'll decide whether to track this ourselves, thanks
1222 1223 for c in changes[:3]:
1223 1224 if '.hgsubstate' in c:
1224 1225 c.remove('.hgsubstate')
1225 1226
1226 1227 # compare current state to last committed state
1227 1228 # build new substate based on last committed state
1228 1229 oldstate = wctx.p1().substate
1229 1230 for s in sorted(newstate.keys()):
1230 1231 if not match(s):
1231 1232 # ignore working copy, use old state if present
1232 1233 if s in oldstate:
1233 1234 newstate[s] = oldstate[s]
1234 1235 continue
1235 1236 if not force:
1236 1237 raise util.Abort(
1237 1238 _("commit with new subrepo %s excluded") % s)
1238 1239 if wctx.sub(s).dirty(True):
1239 1240 if not self.ui.configbool('ui', 'commitsubrepos'):
1240 1241 raise util.Abort(
1241 1242 _("uncommitted changes in subrepo %s") % s,
1242 1243 hint=_("use --subrepos for recursive commit"))
1243 1244 subs.append(s)
1244 1245 commitsubs.add(s)
1245 1246 else:
1246 1247 bs = wctx.sub(s).basestate()
1247 1248 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1248 1249 if oldstate.get(s, (None, None, None))[1] != bs:
1249 1250 subs.append(s)
1250 1251
1251 1252 # check for removed subrepos
1252 1253 for p in wctx.parents():
1253 1254 r = [s for s in p.substate if s not in newstate]
1254 1255 subs += [s for s in r if match(s)]
1255 1256 if subs:
1256 1257 if (not match('.hgsub') and
1257 1258 '.hgsub' in (wctx.modified() + wctx.added())):
1258 1259 raise util.Abort(
1259 1260 _("can't commit subrepos without .hgsub"))
1260 1261 changes[0].insert(0, '.hgsubstate')
1261 1262
1262 1263 elif '.hgsub' in changes[2]:
1263 1264 # clean up .hgsubstate when .hgsub is removed
1264 1265 if ('.hgsubstate' in wctx and
1265 1266 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1266 1267 changes[2].insert(0, '.hgsubstate')
1267 1268
1268 1269 # make sure all explicit patterns are matched
1269 1270 if not force and match.files():
1270 1271 matched = set(changes[0] + changes[1] + changes[2])
1271 1272
1272 1273 for f in match.files():
1273 1274 f = self.dirstate.normalize(f)
1274 1275 if f == '.' or f in matched or f in wctx.substate:
1275 1276 continue
1276 1277 if f in changes[3]: # missing
1277 1278 fail(f, _('file not found!'))
1278 1279 if f in vdirs: # visited directory
1279 1280 d = f + '/'
1280 1281 for mf in matched:
1281 1282 if mf.startswith(d):
1282 1283 break
1283 1284 else:
1284 1285 fail(f, _("no match under directory!"))
1285 1286 elif f not in self.dirstate:
1286 1287 fail(f, _("file not tracked!"))
1287 1288
1288 1289 cctx = context.workingctx(self, text, user, date, extra, changes)
1289 1290
1290 1291 if (not force and not extra.get("close") and not merge
1291 1292 and not cctx.files()
1292 1293 and wctx.branch() == wctx.p1().branch()):
1293 1294 return None
1294 1295
1295 1296 if merge and cctx.deleted():
1296 1297 raise util.Abort(_("cannot commit merge with missing files"))
1297 1298
1298 1299 ms = mergemod.mergestate(self)
1299 1300 for f in changes[0]:
1300 1301 if f in ms and ms[f] == 'u':
1301 1302 raise util.Abort(_("unresolved merge conflicts "
1302 1303 "(see hg help resolve)"))
1303 1304
1304 1305 if editor:
1305 1306 cctx._text = editor(self, cctx, subs)
1306 1307 edited = (text != cctx._text)
1307 1308
1308 1309 # Save commit message in case this transaction gets rolled back
1309 1310 # (e.g. by a pretxncommit hook). Leave the content alone on
1310 1311 # the assumption that the user will use the same editor again.
1311 1312 msgfn = self.savecommitmessage(cctx._text)
1312 1313
1313 1314 # commit subs and write new state
1314 1315 if subs:
1315 1316 for s in sorted(commitsubs):
1316 1317 sub = wctx.sub(s)
1317 1318 self.ui.status(_('committing subrepository %s\n') %
1318 1319 subrepo.subrelpath(sub))
1319 1320 sr = sub.commit(cctx._text, user, date)
1320 1321 newstate[s] = (newstate[s][0], sr)
1321 1322 subrepo.writestate(self, newstate)
1322 1323
1323 1324 p1, p2 = self.dirstate.parents()
1324 1325 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1325 1326 try:
1326 1327 self.hook("precommit", throw=True, parent1=hookp1,
1327 1328 parent2=hookp2)
1328 1329 ret = self.commitctx(cctx, True)
1329 1330 except: # re-raises
1330 1331 if edited:
1331 1332 self.ui.write(
1332 1333 _('note: commit message saved in %s\n') % msgfn)
1333 1334 raise
1334 1335
1335 1336 # update bookmarks, dirstate and mergestate
1336 1337 bookmarks.update(self, [p1, p2], ret)
1337 1338 cctx.markcommitted(ret)
1338 1339 ms.reset()
1339 1340 finally:
1340 1341 wlock.release()
1341 1342
1342 1343 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1343 1344 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1344 1345 self._afterlock(commithook)
1345 1346 return ret
1346 1347
1347 1348 @unfilteredmethod
1348 1349 def commitctx(self, ctx, error=False):
1349 1350 """Add a new revision to current repository.
1350 1351 Revision information is passed via the context argument.
1351 1352 """
1352 1353
1353 1354 tr = lock = None
1354 1355 removed = list(ctx.removed())
1355 1356 p1, p2 = ctx.p1(), ctx.p2()
1356 1357 user = ctx.user()
1357 1358
1358 1359 lock = self.lock()
1359 1360 try:
1360 1361 tr = self.transaction("commit")
1361 1362 trp = weakref.proxy(tr)
1362 1363
1363 1364 if ctx.files():
1364 1365 m1 = p1.manifest().copy()
1365 1366 m2 = p2.manifest()
1366 1367
1367 1368 # check in files
1368 1369 new = {}
1369 1370 changed = []
1370 1371 linkrev = len(self)
1371 1372 for f in sorted(ctx.modified() + ctx.added()):
1372 1373 self.ui.note(f + "\n")
1373 1374 try:
1374 1375 fctx = ctx[f]
1375 1376 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1376 1377 changed)
1377 1378 m1.set(f, fctx.flags())
1378 1379 except OSError, inst:
1379 1380 self.ui.warn(_("trouble committing %s!\n") % f)
1380 1381 raise
1381 1382 except IOError, inst:
1382 1383 errcode = getattr(inst, 'errno', errno.ENOENT)
1383 1384 if error or errcode and errcode != errno.ENOENT:
1384 1385 self.ui.warn(_("trouble committing %s!\n") % f)
1385 1386 raise
1386 1387 else:
1387 1388 removed.append(f)
1388 1389
1389 1390 # update manifest
1390 1391 m1.update(new)
1391 1392 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1392 1393 drop = [f for f in removed if f in m1]
1393 1394 for f in drop:
1394 1395 del m1[f]
1395 1396 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1396 1397 p2.manifestnode(), (new, drop))
1397 1398 files = changed + removed
1398 1399 else:
1399 1400 mn = p1.manifestnode()
1400 1401 files = []
1401 1402
1402 1403 # update changelog
1403 1404 self.changelog.delayupdate()
1404 1405 n = self.changelog.add(mn, files, ctx.description(),
1405 1406 trp, p1.node(), p2.node(),
1406 1407 user, ctx.date(), ctx.extra().copy())
1407 1408 p = lambda: self.changelog.writepending() and self.root or ""
1408 1409 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1409 1410 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1410 1411 parent2=xp2, pending=p)
1411 1412 self.changelog.finalize(trp)
1412 1413 # set the new commit is proper phase
1413 1414 targetphase = subrepo.newcommitphase(self.ui, ctx)
1414 1415 if targetphase:
1415 1416 # retract boundary do not alter parent changeset.
1416 1417 # if a parent have higher the resulting phase will
1417 1418 # be compliant anyway
1418 1419 #
1419 1420 # if minimal phase was 0 we don't need to retract anything
1420 1421 phases.retractboundary(self, targetphase, [n])
1421 1422 tr.close()
1422 1423 branchmap.updatecache(self.filtered('served'))
1423 1424 return n
1424 1425 finally:
1425 1426 if tr:
1426 1427 tr.release()
1427 1428 lock.release()
1428 1429
1429 1430 @unfilteredmethod
1430 1431 def destroying(self):
1431 1432 '''Inform the repository that nodes are about to be destroyed.
1432 1433 Intended for use by strip and rollback, so there's a common
1433 1434 place for anything that has to be done before destroying history.
1434 1435
1435 1436 This is mostly useful for saving state that is in memory and waiting
1436 1437 to be flushed when the current lock is released. Because a call to
1437 1438 destroyed is imminent, the repo will be invalidated causing those
1438 1439 changes to stay in memory (waiting for the next unlock), or vanish
1439 1440 completely.
1440 1441 '''
1441 1442 # When using the same lock to commit and strip, the phasecache is left
1442 1443 # dirty after committing. Then when we strip, the repo is invalidated,
1443 1444 # causing those changes to disappear.
1444 1445 if '_phasecache' in vars(self):
1445 1446 self._phasecache.write()
1446 1447
1447 1448 @unfilteredmethod
1448 1449 def destroyed(self):
1449 1450 '''Inform the repository that nodes have been destroyed.
1450 1451 Intended for use by strip and rollback, so there's a common
1451 1452 place for anything that has to be done after destroying history.
1452 1453 '''
1453 1454 # When one tries to:
1454 1455 # 1) destroy nodes thus calling this method (e.g. strip)
1455 1456 # 2) use phasecache somewhere (e.g. commit)
1456 1457 #
1457 1458 # then 2) will fail because the phasecache contains nodes that were
1458 1459 # removed. We can either remove phasecache from the filecache,
1459 1460 # causing it to reload next time it is accessed, or simply filter
1460 1461 # the removed nodes now and write the updated cache.
1461 1462 self._phasecache.filterunknown(self)
1462 1463 self._phasecache.write()
1463 1464
1464 1465 # update the 'served' branch cache to help read only server process
1465 1466 # Thanks to branchcache collaboration this is done from the nearest
1466 1467 # filtered subset and it is expected to be fast.
1467 1468 branchmap.updatecache(self.filtered('served'))
1468 1469
1469 1470 # Ensure the persistent tag cache is updated. Doing it now
1470 1471 # means that the tag cache only has to worry about destroyed
1471 1472 # heads immediately after a strip/rollback. That in turn
1472 1473 # guarantees that "cachetip == currenttip" (comparing both rev
1473 1474 # and node) always means no nodes have been added or destroyed.
1474 1475
1475 1476 # XXX this is suboptimal when qrefresh'ing: we strip the current
1476 1477 # head, refresh the tag cache, then immediately add a new head.
1477 1478 # But I think doing it this way is necessary for the "instant
1478 1479 # tag cache retrieval" case to work.
1479 1480 self.invalidate()
1480 1481
1481 1482 def walk(self, match, node=None):
1482 1483 '''
1483 1484 walk recursively through the directory tree or a given
1484 1485 changeset, finding all files matched by the match
1485 1486 function
1486 1487 '''
1487 1488 return self[node].walk(match)
1488 1489
1489 1490 def status(self, node1='.', node2=None, match=None,
1490 1491 ignored=False, clean=False, unknown=False,
1491 1492 listsubrepos=False):
1492 1493 """return status of files between two nodes or node and working
1493 1494 directory.
1494 1495
1495 1496 If node1 is None, use the first dirstate parent instead.
1496 1497 If node2 is None, compare node1 with working directory.
1497 1498 """
1498 1499
1499 1500 def mfmatches(ctx):
1500 1501 mf = ctx.manifest().copy()
1501 1502 if match.always():
1502 1503 return mf
1503 1504 for fn in mf.keys():
1504 1505 if not match(fn):
1505 1506 del mf[fn]
1506 1507 return mf
1507 1508
1508 1509 ctx1 = self[node1]
1509 1510 ctx2 = self[node2]
1510 1511
1511 1512 working = ctx2.rev() is None
1512 1513 parentworking = working and ctx1 == self['.']
1513 1514 match = match or matchmod.always(self.root, self.getcwd())
1514 1515 listignored, listclean, listunknown = ignored, clean, unknown
1515 1516
1516 1517 # load earliest manifest first for caching reasons
1517 1518 if not working and ctx2.rev() < ctx1.rev():
1518 1519 ctx2.manifest()
1519 1520
1520 1521 if not parentworking:
1521 1522 def bad(f, msg):
1522 1523 # 'f' may be a directory pattern from 'match.files()',
1523 1524 # so 'f not in ctx1' is not enough
1524 1525 if f not in ctx1 and f not in ctx1.dirs():
1525 1526 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1526 1527 match.bad = bad
1527 1528
1528 1529 if working: # we need to scan the working dir
1529 1530 subrepos = []
1530 1531 if '.hgsub' in self.dirstate:
1531 1532 subrepos = sorted(ctx2.substate)
1532 1533 s = self.dirstate.status(match, subrepos, listignored,
1533 1534 listclean, listunknown)
1534 1535 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1535 1536
1536 1537 # check for any possibly clean files
1537 1538 if parentworking and cmp:
1538 1539 fixup = []
1539 1540 # do a full compare of any files that might have changed
1540 1541 for f in sorted(cmp):
1541 1542 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1542 1543 or ctx1[f].cmp(ctx2[f])):
1543 1544 modified.append(f)
1544 1545 else:
1545 1546 fixup.append(f)
1546 1547
1547 1548 # update dirstate for files that are actually clean
1548 1549 if fixup:
1549 1550 if listclean:
1550 1551 clean += fixup
1551 1552
1552 1553 try:
1553 1554 # updating the dirstate is optional
1554 1555 # so we don't wait on the lock
1555 1556 wlock = self.wlock(False)
1556 1557 try:
1557 1558 for f in fixup:
1558 1559 self.dirstate.normal(f)
1559 1560 finally:
1560 1561 wlock.release()
1561 1562 except error.LockError:
1562 1563 pass
1563 1564
1564 1565 if not parentworking:
1565 1566 mf1 = mfmatches(ctx1)
1566 1567 if working:
1567 1568 # we are comparing working dir against non-parent
1568 1569 # generate a pseudo-manifest for the working dir
1569 1570 mf2 = mfmatches(self['.'])
1570 1571 for f in cmp + modified + added:
1571 1572 mf2[f] = None
1572 1573 mf2.set(f, ctx2.flags(f))
1573 1574 for f in removed:
1574 1575 if f in mf2:
1575 1576 del mf2[f]
1576 1577 else:
1577 1578 # we are comparing two revisions
1578 1579 deleted, unknown, ignored = [], [], []
1579 1580 mf2 = mfmatches(ctx2)
1580 1581
1581 1582 modified, added, clean = [], [], []
1582 1583 withflags = mf1.withflags() | mf2.withflags()
1583 1584 for fn, mf2node in mf2.iteritems():
1584 1585 if fn in mf1:
1585 1586 if (fn not in deleted and
1586 1587 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1587 1588 (mf1[fn] != mf2node and
1588 1589 (mf2node or ctx1[fn].cmp(ctx2[fn]))))):
1589 1590 modified.append(fn)
1590 1591 elif listclean:
1591 1592 clean.append(fn)
1592 1593 del mf1[fn]
1593 1594 elif fn not in deleted:
1594 1595 added.append(fn)
1595 1596 removed = mf1.keys()
1596 1597
1597 1598 if working and modified and not self.dirstate._checklink:
1598 1599 # Symlink placeholders may get non-symlink-like contents
1599 1600 # via user error or dereferencing by NFS or Samba servers,
1600 1601 # so we filter out any placeholders that don't look like a
1601 1602 # symlink
1602 1603 sane = []
1603 1604 for f in modified:
1604 1605 if ctx2.flags(f) == 'l':
1605 1606 d = ctx2[f].data()
1606 1607 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1607 1608 self.ui.debug('ignoring suspect symlink placeholder'
1608 1609 ' "%s"\n' % f)
1609 1610 continue
1610 1611 sane.append(f)
1611 1612 modified = sane
1612 1613
1613 1614 r = modified, added, removed, deleted, unknown, ignored, clean
1614 1615
1615 1616 if listsubrepos:
1616 1617 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1617 1618 if working:
1618 1619 rev2 = None
1619 1620 else:
1620 1621 rev2 = ctx2.substate[subpath][1]
1621 1622 try:
1622 1623 submatch = matchmod.narrowmatcher(subpath, match)
1623 1624 s = sub.status(rev2, match=submatch, ignored=listignored,
1624 1625 clean=listclean, unknown=listunknown,
1625 1626 listsubrepos=True)
1626 1627 for rfiles, sfiles in zip(r, s):
1627 1628 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1628 1629 except error.LookupError:
1629 1630 self.ui.status(_("skipping missing subrepository: %s\n")
1630 1631 % subpath)
1631 1632
1632 1633 for l in r:
1633 1634 l.sort()
1634 1635 return r
1635 1636
1636 1637 def heads(self, start=None):
1637 1638 heads = self.changelog.heads(start)
1638 1639 # sort the output in rev descending order
1639 1640 return sorted(heads, key=self.changelog.rev, reverse=True)
1640 1641
1641 1642 def branchheads(self, branch=None, start=None, closed=False):
1642 1643 '''return a (possibly filtered) list of heads for the given branch
1643 1644
1644 1645 Heads are returned in topological order, from newest to oldest.
1645 1646 If branch is None, use the dirstate branch.
1646 1647 If start is not None, return only heads reachable from start.
1647 1648 If closed is True, return heads that are marked as closed as well.
1648 1649 '''
1649 1650 if branch is None:
1650 1651 branch = self[None].branch()
1651 1652 branches = self.branchmap()
1652 1653 if branch not in branches:
1653 1654 return []
1654 1655 # the cache returns heads ordered lowest to highest
1655 1656 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1656 1657 if start is not None:
1657 1658 # filter out the heads that cannot be reached from startrev
1658 1659 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1659 1660 bheads = [h for h in bheads if h in fbheads]
1660 1661 return bheads
1661 1662
1662 1663 def branches(self, nodes):
1663 1664 if not nodes:
1664 1665 nodes = [self.changelog.tip()]
1665 1666 b = []
1666 1667 for n in nodes:
1667 1668 t = n
1668 1669 while True:
1669 1670 p = self.changelog.parents(n)
1670 1671 if p[1] != nullid or p[0] == nullid:
1671 1672 b.append((t, n, p[0], p[1]))
1672 1673 break
1673 1674 n = p[0]
1674 1675 return b
1675 1676
1676 1677 def between(self, pairs):
1677 1678 r = []
1678 1679
1679 1680 for top, bottom in pairs:
1680 1681 n, l, i = top, [], 0
1681 1682 f = 1
1682 1683
1683 1684 while n != bottom and n != nullid:
1684 1685 p = self.changelog.parents(n)[0]
1685 1686 if i == f:
1686 1687 l.append(n)
1687 1688 f = f * 2
1688 1689 n = p
1689 1690 i += 1
1690 1691
1691 1692 r.append(l)
1692 1693
1693 1694 return r
1694 1695
1695 1696 def pull(self, remote, heads=None, force=False):
1696 1697 return exchange.pull (self, remote, heads, force)
1697 1698
1698 1699 def checkpush(self, pushop):
1699 1700 """Extensions can override this function if additional checks have
1700 1701 to be performed before pushing, or call it if they override push
1701 1702 command.
1702 1703 """
1703 1704 pass
1704 1705
1705 1706 @unfilteredpropertycache
1706 1707 def prepushoutgoinghooks(self):
1707 1708 """Return util.hooks consists of "(repo, remote, outgoing)"
1708 1709 functions, which are called before pushing changesets.
1709 1710 """
1710 1711 return util.hooks()
1711 1712
1712 1713 def push(self, remote, force=False, revs=None, newbranch=False):
1713 1714 return exchange.push(self, remote, force, revs, newbranch)
1714 1715
1715 1716 def stream_in(self, remote, requirements):
1716 1717 lock = self.lock()
1717 1718 try:
1718 1719 # Save remote branchmap. We will use it later
1719 1720 # to speed up branchcache creation
1720 1721 rbranchmap = None
1721 1722 if remote.capable("branchmap"):
1722 1723 rbranchmap = remote.branchmap()
1723 1724
1724 1725 fp = remote.stream_out()
1725 1726 l = fp.readline()
1726 1727 try:
1727 1728 resp = int(l)
1728 1729 except ValueError:
1729 1730 raise error.ResponseError(
1730 1731 _('unexpected response from remote server:'), l)
1731 1732 if resp == 1:
1732 1733 raise util.Abort(_('operation forbidden by server'))
1733 1734 elif resp == 2:
1734 1735 raise util.Abort(_('locking the remote repository failed'))
1735 1736 elif resp != 0:
1736 1737 raise util.Abort(_('the server sent an unknown error code'))
1737 1738 self.ui.status(_('streaming all changes\n'))
1738 1739 l = fp.readline()
1739 1740 try:
1740 1741 total_files, total_bytes = map(int, l.split(' ', 1))
1741 1742 except (ValueError, TypeError):
1742 1743 raise error.ResponseError(
1743 1744 _('unexpected response from remote server:'), l)
1744 1745 self.ui.status(_('%d files to transfer, %s of data\n') %
1745 1746 (total_files, util.bytecount(total_bytes)))
1746 1747 handled_bytes = 0
1747 1748 self.ui.progress(_('clone'), 0, total=total_bytes)
1748 1749 start = time.time()
1749 1750
1750 1751 tr = self.transaction(_('clone'))
1751 1752 try:
1752 1753 for i in xrange(total_files):
1753 1754 # XXX doesn't support '\n' or '\r' in filenames
1754 1755 l = fp.readline()
1755 1756 try:
1756 1757 name, size = l.split('\0', 1)
1757 1758 size = int(size)
1758 1759 except (ValueError, TypeError):
1759 1760 raise error.ResponseError(
1760 1761 _('unexpected response from remote server:'), l)
1761 1762 if self.ui.debugflag:
1762 1763 self.ui.debug('adding %s (%s)\n' %
1763 1764 (name, util.bytecount(size)))
1764 1765 # for backwards compat, name was partially encoded
1765 1766 ofp = self.sopener(store.decodedir(name), 'w')
1766 1767 for chunk in util.filechunkiter(fp, limit=size):
1767 1768 handled_bytes += len(chunk)
1768 1769 self.ui.progress(_('clone'), handled_bytes,
1769 1770 total=total_bytes)
1770 1771 ofp.write(chunk)
1771 1772 ofp.close()
1772 1773 tr.close()
1773 1774 finally:
1774 1775 tr.release()
1775 1776
1776 1777 # Writing straight to files circumvented the inmemory caches
1777 1778 self.invalidate()
1778 1779
1779 1780 elapsed = time.time() - start
1780 1781 if elapsed <= 0:
1781 1782 elapsed = 0.001
1782 1783 self.ui.progress(_('clone'), None)
1783 1784 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1784 1785 (util.bytecount(total_bytes), elapsed,
1785 1786 util.bytecount(total_bytes / elapsed)))
1786 1787
1787 1788 # new requirements = old non-format requirements +
1788 1789 # new format-related
1789 1790 # requirements from the streamed-in repository
1790 1791 requirements.update(set(self.requirements) - self.supportedformats)
1791 1792 self._applyrequirements(requirements)
1792 1793 self._writerequirements()
1793 1794
1794 1795 if rbranchmap:
1795 1796 rbheads = []
1796 1797 for bheads in rbranchmap.itervalues():
1797 1798 rbheads.extend(bheads)
1798 1799
1799 1800 if rbheads:
1800 1801 rtiprev = max((int(self.changelog.rev(node))
1801 1802 for node in rbheads))
1802 1803 cache = branchmap.branchcache(rbranchmap,
1803 1804 self[rtiprev].node(),
1804 1805 rtiprev)
1805 1806 # Try to stick it as low as possible
1806 1807 # filter above served are unlikely to be fetch from a clone
1807 1808 for candidate in ('base', 'immutable', 'served'):
1808 1809 rview = self.filtered(candidate)
1809 1810 if cache.validfor(rview):
1810 1811 self._branchcaches[candidate] = cache
1811 1812 cache.write(rview)
1812 1813 break
1813 1814 self.invalidate()
1814 1815 return len(self.heads()) + 1
1815 1816 finally:
1816 1817 lock.release()
1817 1818
1818 1819 def clone(self, remote, heads=[], stream=False):
1819 1820 '''clone remote repository.
1820 1821
1821 1822 keyword arguments:
1822 1823 heads: list of revs to clone (forces use of pull)
1823 1824 stream: use streaming clone if possible'''
1824 1825
1825 1826 # now, all clients that can request uncompressed clones can
1826 1827 # read repo formats supported by all servers that can serve
1827 1828 # them.
1828 1829
1829 1830 # if revlog format changes, client will have to check version
1830 1831 # and format flags on "stream" capability, and use
1831 1832 # uncompressed only if compatible.
1832 1833
1833 1834 if not stream:
1834 1835 # if the server explicitly prefers to stream (for fast LANs)
1835 1836 stream = remote.capable('stream-preferred')
1836 1837
1837 1838 if stream and not heads:
1838 1839 # 'stream' means remote revlog format is revlogv1 only
1839 1840 if remote.capable('stream'):
1840 1841 return self.stream_in(remote, set(('revlogv1',)))
1841 1842 # otherwise, 'streamreqs' contains the remote revlog format
1842 1843 streamreqs = remote.capable('streamreqs')
1843 1844 if streamreqs:
1844 1845 streamreqs = set(streamreqs.split(','))
1845 1846 # if we support it, stream in and adjust our requirements
1846 1847 if not streamreqs - self.supportedformats:
1847 1848 return self.stream_in(remote, streamreqs)
1848 1849 return self.pull(remote, heads)
1849 1850
1850 1851 def pushkey(self, namespace, key, old, new):
1851 1852 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
1852 1853 old=old, new=new)
1853 1854 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1854 1855 ret = pushkey.push(self, namespace, key, old, new)
1855 1856 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1856 1857 ret=ret)
1857 1858 return ret
1858 1859
1859 1860 def listkeys(self, namespace):
1860 1861 self.hook('prelistkeys', throw=True, namespace=namespace)
1861 1862 self.ui.debug('listing keys for "%s"\n' % namespace)
1862 1863 values = pushkey.list(self, namespace)
1863 1864 self.hook('listkeys', namespace=namespace, values=values)
1864 1865 return values
1865 1866
1866 1867 def debugwireargs(self, one, two, three=None, four=None, five=None):
1867 1868 '''used to test argument passing over the wire'''
1868 1869 return "%s %s %s %s %s" % (one, two, three, four, five)
1869 1870
1870 1871 def savecommitmessage(self, text):
1871 1872 fp = self.opener('last-message.txt', 'wb')
1872 1873 try:
1873 1874 fp.write(text)
1874 1875 finally:
1875 1876 fp.close()
1876 1877 return self.pathto(fp.name[len(self.root) + 1:])
1877 1878
1878 1879 # used to avoid circular references so destructors work
1879 1880 def aftertrans(files):
1880 1881 renamefiles = [tuple(t) for t in files]
1881 1882 def a():
1882 1883 for vfs, src, dest in renamefiles:
1883 1884 try:
1884 1885 vfs.rename(src, dest)
1885 1886 except OSError: # journal file does not yet exist
1886 1887 pass
1887 1888 return a
1888 1889
1889 1890 def undoname(fn):
1890 1891 base, name = os.path.split(fn)
1891 1892 assert name.startswith('journal')
1892 1893 return os.path.join(base, name.replace('journal', 'undo', 1))
1893 1894
1894 1895 def instance(ui, path, create):
1895 1896 return localrepository(ui, util.urllocalpath(path), create)
1896 1897
1897 1898 def islocal(path):
1898 1899 return True
@@ -1,2139 +1,2139 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 adding changesets
92 91 bundling: 1/3 changesets (33.33%)
93 92 bundling: 2/3 changesets (66.67%)
94 93 bundling: 3/3 changesets (100.00%)
95 94 bundling: 1/3 manifests (33.33%)
96 95 bundling: 2/3 manifests (66.67%)
97 96 bundling: 3/3 manifests (100.00%)
98 97 bundling: foo/Bar/file.txt 1/3 files (33.33%)
99 98 bundling: foo/file.txt 2/3 files (66.67%)
100 99 bundling: quux/file.py 3/3 files (100.00%)
100 adding changesets
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 updating the branch cache
120 120 listing keys for "phases"
121 121 try to push obsolete markers to remote
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 143 invalid branchheads cache (served): tip differs
144 144 listing keys for "bookmarks"
145 145 3 changesets found
146 146 list of changesets:
147 147 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
148 148 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
149 149 911600dab2ae7a9baff75958b84fe606851ce955
150 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%)
159 adding changesets
160 160 changesets: 1 chunks
161 161 add changeset ef1ea85a6374
162 162 changesets: 2 chunks
163 163 add changeset f9cafe1212c8
164 164 changesets: 3 chunks
165 165 add changeset 911600dab2ae
166 166 adding manifests
167 167 manifests: 1/3 chunks (33.33%)
168 168 manifests: 2/3 chunks (66.67%)
169 169 manifests: 3/3 chunks (100.00%)
170 170 adding file changes
171 171 adding foo/Bar/file.txt revisions
172 172 files: 1/3 chunks (33.33%)
173 173 adding foo/file.txt revisions
174 174 files: 2/3 chunks (66.67%)
175 175 adding quux/file.py revisions
176 176 files: 3/3 chunks (100.00%)
177 177 added 3 changesets with 3 changes to 3 files
178 178 calling hook pretxnchangegroup.acl: hgext.acl.hook
179 179 acl: changes have source "push" - skipping
180 180 updating the branch cache
181 181 listing keys for "phases"
182 182 try to push obsolete markers to remote
183 183 checking for updated bookmarks
184 184 listing keys for "bookmarks"
185 185 repository tip rolled back to revision 0 (undo push)
186 186 0:6675d58eff77
187 187
188 188
189 189 No [acl.allow]/[acl.deny]
190 190
191 191 $ echo '[acl]' >> $config
192 192 $ echo 'sources = push' >> $config
193 193 $ do_push fred
194 194 Pushing as user fred
195 195 hgrc = """
196 196 [hooks]
197 197 pretxnchangegroup.acl = python:hgext.acl.hook
198 198 [acl]
199 199 sources = push
200 200 """
201 201 pushing to ../b
202 202 query 1; heads
203 203 searching for changes
204 204 all remote heads known locally
205 205 invalid branchheads cache (served): tip differs
206 206 listing keys for "bookmarks"
207 207 3 changesets found
208 208 list of changesets:
209 209 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
210 210 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
211 211 911600dab2ae7a9baff75958b84fe606851ce955
212 adding changesets
213 212 bundling: 1/3 changesets (33.33%)
214 213 bundling: 2/3 changesets (66.67%)
215 214 bundling: 3/3 changesets (100.00%)
216 215 bundling: 1/3 manifests (33.33%)
217 216 bundling: 2/3 manifests (66.67%)
218 217 bundling: 3/3 manifests (100.00%)
219 218 bundling: foo/Bar/file.txt 1/3 files (33.33%)
220 219 bundling: foo/file.txt 2/3 files (66.67%)
221 220 bundling: quux/file.py 3/3 files (100.00%)
221 adding changesets
222 222 changesets: 1 chunks
223 223 add changeset ef1ea85a6374
224 224 changesets: 2 chunks
225 225 add changeset f9cafe1212c8
226 226 changesets: 3 chunks
227 227 add changeset 911600dab2ae
228 228 adding manifests
229 229 manifests: 1/3 chunks (33.33%)
230 230 manifests: 2/3 chunks (66.67%)
231 231 manifests: 3/3 chunks (100.00%)
232 232 adding file changes
233 233 adding foo/Bar/file.txt revisions
234 234 files: 1/3 chunks (33.33%)
235 235 adding foo/file.txt revisions
236 236 files: 2/3 chunks (66.67%)
237 237 adding quux/file.py revisions
238 238 files: 3/3 chunks (100.00%)
239 239 added 3 changesets with 3 changes to 3 files
240 240 calling hook pretxnchangegroup.acl: hgext.acl.hook
241 241 acl: checking access for user "fred"
242 242 acl: acl.allow.branches not enabled
243 243 acl: acl.deny.branches not enabled
244 244 acl: acl.allow not enabled
245 245 acl: acl.deny not enabled
246 246 acl: branch access granted: "ef1ea85a6374" on branch "default"
247 247 acl: path access granted: "ef1ea85a6374"
248 248 acl: branch access granted: "f9cafe1212c8" on branch "default"
249 249 acl: path access granted: "f9cafe1212c8"
250 250 acl: branch access granted: "911600dab2ae" on branch "default"
251 251 acl: path access granted: "911600dab2ae"
252 252 updating the branch cache
253 253 listing keys for "phases"
254 254 try to push obsolete markers to remote
255 255 checking for updated bookmarks
256 256 listing keys for "bookmarks"
257 257 repository tip rolled back to revision 0 (undo push)
258 258 0:6675d58eff77
259 259
260 260
261 261 Empty [acl.allow]
262 262
263 263 $ echo '[acl.allow]' >> $config
264 264 $ do_push fred
265 265 Pushing as user fred
266 266 hgrc = """
267 267 [hooks]
268 268 pretxnchangegroup.acl = python:hgext.acl.hook
269 269 [acl]
270 270 sources = push
271 271 [acl.allow]
272 272 """
273 273 pushing to ../b
274 274 query 1; heads
275 275 searching for changes
276 276 all remote heads known locally
277 277 invalid branchheads cache (served): tip differs
278 278 listing keys for "bookmarks"
279 279 3 changesets found
280 280 list of changesets:
281 281 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
282 282 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
283 283 911600dab2ae7a9baff75958b84fe606851ce955
284 adding changesets
285 284 bundling: 1/3 changesets (33.33%)
286 285 bundling: 2/3 changesets (66.67%)
287 286 bundling: 3/3 changesets (100.00%)
288 287 bundling: 1/3 manifests (33.33%)
289 288 bundling: 2/3 manifests (66.67%)
290 289 bundling: 3/3 manifests (100.00%)
291 290 bundling: foo/Bar/file.txt 1/3 files (33.33%)
292 291 bundling: foo/file.txt 2/3 files (66.67%)
293 292 bundling: quux/file.py 3/3 files (100.00%)
293 adding changesets
294 294 changesets: 1 chunks
295 295 add changeset ef1ea85a6374
296 296 changesets: 2 chunks
297 297 add changeset f9cafe1212c8
298 298 changesets: 3 chunks
299 299 add changeset 911600dab2ae
300 300 adding manifests
301 301 manifests: 1/3 chunks (33.33%)
302 302 manifests: 2/3 chunks (66.67%)
303 303 manifests: 3/3 chunks (100.00%)
304 304 adding file changes
305 305 adding foo/Bar/file.txt revisions
306 306 files: 1/3 chunks (33.33%)
307 307 adding foo/file.txt revisions
308 308 files: 2/3 chunks (66.67%)
309 309 adding quux/file.py revisions
310 310 files: 3/3 chunks (100.00%)
311 311 added 3 changesets with 3 changes to 3 files
312 312 calling hook pretxnchangegroup.acl: hgext.acl.hook
313 313 acl: checking access for user "fred"
314 314 acl: acl.allow.branches not enabled
315 315 acl: acl.deny.branches not enabled
316 316 acl: acl.allow enabled, 0 entries for user fred
317 317 acl: acl.deny not enabled
318 318 acl: branch access granted: "ef1ea85a6374" on branch "default"
319 319 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
320 320 transaction abort!
321 321 rollback completed
322 322 abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
323 323 no rollback information available
324 324 0:6675d58eff77
325 325
326 326
327 327 fred is allowed inside foo/
328 328
329 329 $ echo 'foo/** = fred' >> $config
330 330 $ do_push fred
331 331 Pushing as user fred
332 332 hgrc = """
333 333 [hooks]
334 334 pretxnchangegroup.acl = python:hgext.acl.hook
335 335 [acl]
336 336 sources = push
337 337 [acl.allow]
338 338 foo/** = fred
339 339 """
340 340 pushing to ../b
341 341 query 1; heads
342 342 searching for changes
343 343 all remote heads known locally
344 344 invalid branchheads cache (served): tip differs
345 345 listing keys for "bookmarks"
346 346 3 changesets found
347 347 list of changesets:
348 348 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
349 349 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
350 350 911600dab2ae7a9baff75958b84fe606851ce955
351 adding changesets
352 351 bundling: 1/3 changesets (33.33%)
353 352 bundling: 2/3 changesets (66.67%)
354 353 bundling: 3/3 changesets (100.00%)
355 354 bundling: 1/3 manifests (33.33%)
356 355 bundling: 2/3 manifests (66.67%)
357 356 bundling: 3/3 manifests (100.00%)
358 357 bundling: foo/Bar/file.txt 1/3 files (33.33%)
359 358 bundling: foo/file.txt 2/3 files (66.67%)
360 359 bundling: quux/file.py 3/3 files (100.00%)
360 adding changesets
361 361 changesets: 1 chunks
362 362 add changeset ef1ea85a6374
363 363 changesets: 2 chunks
364 364 add changeset f9cafe1212c8
365 365 changesets: 3 chunks
366 366 add changeset 911600dab2ae
367 367 adding manifests
368 368 manifests: 1/3 chunks (33.33%)
369 369 manifests: 2/3 chunks (66.67%)
370 370 manifests: 3/3 chunks (100.00%)
371 371 adding file changes
372 372 adding foo/Bar/file.txt revisions
373 373 files: 1/3 chunks (33.33%)
374 374 adding foo/file.txt revisions
375 375 files: 2/3 chunks (66.67%)
376 376 adding quux/file.py revisions
377 377 files: 3/3 chunks (100.00%)
378 378 added 3 changesets with 3 changes to 3 files
379 379 calling hook pretxnchangegroup.acl: hgext.acl.hook
380 380 acl: checking access for user "fred"
381 381 acl: acl.allow.branches not enabled
382 382 acl: acl.deny.branches not enabled
383 383 acl: acl.allow enabled, 1 entries for user fred
384 384 acl: acl.deny not enabled
385 385 acl: branch access granted: "ef1ea85a6374" on branch "default"
386 386 acl: path access granted: "ef1ea85a6374"
387 387 acl: branch access granted: "f9cafe1212c8" on branch "default"
388 388 acl: path access granted: "f9cafe1212c8"
389 389 acl: branch access granted: "911600dab2ae" on branch "default"
390 390 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
391 391 transaction abort!
392 392 rollback completed
393 393 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
394 394 no rollback information available
395 395 0:6675d58eff77
396 396
397 397
398 398 Empty [acl.deny]
399 399
400 400 $ echo '[acl.deny]' >> $config
401 401 $ do_push barney
402 402 Pushing as user barney
403 403 hgrc = """
404 404 [hooks]
405 405 pretxnchangegroup.acl = python:hgext.acl.hook
406 406 [acl]
407 407 sources = push
408 408 [acl.allow]
409 409 foo/** = fred
410 410 [acl.deny]
411 411 """
412 412 pushing to ../b
413 413 query 1; heads
414 414 searching for changes
415 415 all remote heads known locally
416 416 invalid branchheads cache (served): tip differs
417 417 listing keys for "bookmarks"
418 418 3 changesets found
419 419 list of changesets:
420 420 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
421 421 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
422 422 911600dab2ae7a9baff75958b84fe606851ce955
423 adding changesets
424 423 bundling: 1/3 changesets (33.33%)
425 424 bundling: 2/3 changesets (66.67%)
426 425 bundling: 3/3 changesets (100.00%)
427 426 bundling: 1/3 manifests (33.33%)
428 427 bundling: 2/3 manifests (66.67%)
429 428 bundling: 3/3 manifests (100.00%)
430 429 bundling: foo/Bar/file.txt 1/3 files (33.33%)
431 430 bundling: foo/file.txt 2/3 files (66.67%)
432 431 bundling: quux/file.py 3/3 files (100.00%)
432 adding changesets
433 433 changesets: 1 chunks
434 434 add changeset ef1ea85a6374
435 435 changesets: 2 chunks
436 436 add changeset f9cafe1212c8
437 437 changesets: 3 chunks
438 438 add changeset 911600dab2ae
439 439 adding manifests
440 440 manifests: 1/3 chunks (33.33%)
441 441 manifests: 2/3 chunks (66.67%)
442 442 manifests: 3/3 chunks (100.00%)
443 443 adding file changes
444 444 adding foo/Bar/file.txt revisions
445 445 files: 1/3 chunks (33.33%)
446 446 adding foo/file.txt revisions
447 447 files: 2/3 chunks (66.67%)
448 448 adding quux/file.py revisions
449 449 files: 3/3 chunks (100.00%)
450 450 added 3 changesets with 3 changes to 3 files
451 451 calling hook pretxnchangegroup.acl: hgext.acl.hook
452 452 acl: checking access for user "barney"
453 453 acl: acl.allow.branches not enabled
454 454 acl: acl.deny.branches not enabled
455 455 acl: acl.allow enabled, 0 entries for user barney
456 456 acl: acl.deny enabled, 0 entries for user barney
457 457 acl: branch access granted: "ef1ea85a6374" on branch "default"
458 458 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
459 459 transaction abort!
460 460 rollback completed
461 461 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
462 462 no rollback information available
463 463 0:6675d58eff77
464 464
465 465
466 466 fred is allowed inside foo/, but not foo/bar/ (case matters)
467 467
468 468 $ echo 'foo/bar/** = fred' >> $config
469 469 $ do_push fred
470 470 Pushing as user fred
471 471 hgrc = """
472 472 [hooks]
473 473 pretxnchangegroup.acl = python:hgext.acl.hook
474 474 [acl]
475 475 sources = push
476 476 [acl.allow]
477 477 foo/** = fred
478 478 [acl.deny]
479 479 foo/bar/** = fred
480 480 """
481 481 pushing to ../b
482 482 query 1; heads
483 483 searching for changes
484 484 all remote heads known locally
485 485 invalid branchheads cache (served): tip differs
486 486 listing keys for "bookmarks"
487 487 3 changesets found
488 488 list of changesets:
489 489 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
490 490 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
491 491 911600dab2ae7a9baff75958b84fe606851ce955
492 adding changesets
493 492 bundling: 1/3 changesets (33.33%)
494 493 bundling: 2/3 changesets (66.67%)
495 494 bundling: 3/3 changesets (100.00%)
496 495 bundling: 1/3 manifests (33.33%)
497 496 bundling: 2/3 manifests (66.67%)
498 497 bundling: 3/3 manifests (100.00%)
499 498 bundling: foo/Bar/file.txt 1/3 files (33.33%)
500 499 bundling: foo/file.txt 2/3 files (66.67%)
501 500 bundling: quux/file.py 3/3 files (100.00%)
501 adding changesets
502 502 changesets: 1 chunks
503 503 add changeset ef1ea85a6374
504 504 changesets: 2 chunks
505 505 add changeset f9cafe1212c8
506 506 changesets: 3 chunks
507 507 add changeset 911600dab2ae
508 508 adding manifests
509 509 manifests: 1/3 chunks (33.33%)
510 510 manifests: 2/3 chunks (66.67%)
511 511 manifests: 3/3 chunks (100.00%)
512 512 adding file changes
513 513 adding foo/Bar/file.txt revisions
514 514 files: 1/3 chunks (33.33%)
515 515 adding foo/file.txt revisions
516 516 files: 2/3 chunks (66.67%)
517 517 adding quux/file.py revisions
518 518 files: 3/3 chunks (100.00%)
519 519 added 3 changesets with 3 changes to 3 files
520 520 calling hook pretxnchangegroup.acl: hgext.acl.hook
521 521 acl: checking access for user "fred"
522 522 acl: acl.allow.branches not enabled
523 523 acl: acl.deny.branches not enabled
524 524 acl: acl.allow enabled, 1 entries for user fred
525 525 acl: acl.deny enabled, 1 entries for user fred
526 526 acl: branch access granted: "ef1ea85a6374" on branch "default"
527 527 acl: path access granted: "ef1ea85a6374"
528 528 acl: branch access granted: "f9cafe1212c8" on branch "default"
529 529 acl: path access granted: "f9cafe1212c8"
530 530 acl: branch access granted: "911600dab2ae" on branch "default"
531 531 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
532 532 transaction abort!
533 533 rollback completed
534 534 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
535 535 no rollback information available
536 536 0:6675d58eff77
537 537
538 538
539 539 fred is allowed inside foo/, but not foo/Bar/
540 540
541 541 $ echo 'foo/Bar/** = fred' >> $config
542 542 $ do_push fred
543 543 Pushing as user fred
544 544 hgrc = """
545 545 [hooks]
546 546 pretxnchangegroup.acl = python:hgext.acl.hook
547 547 [acl]
548 548 sources = push
549 549 [acl.allow]
550 550 foo/** = fred
551 551 [acl.deny]
552 552 foo/bar/** = fred
553 553 foo/Bar/** = fred
554 554 """
555 555 pushing to ../b
556 556 query 1; heads
557 557 searching for changes
558 558 all remote heads known locally
559 559 invalid branchheads cache (served): tip differs
560 560 listing keys for "bookmarks"
561 561 3 changesets found
562 562 list of changesets:
563 563 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
564 564 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
565 565 911600dab2ae7a9baff75958b84fe606851ce955
566 adding changesets
567 566 bundling: 1/3 changesets (33.33%)
568 567 bundling: 2/3 changesets (66.67%)
569 568 bundling: 3/3 changesets (100.00%)
570 569 bundling: 1/3 manifests (33.33%)
571 570 bundling: 2/3 manifests (66.67%)
572 571 bundling: 3/3 manifests (100.00%)
573 572 bundling: foo/Bar/file.txt 1/3 files (33.33%)
574 573 bundling: foo/file.txt 2/3 files (66.67%)
575 574 bundling: quux/file.py 3/3 files (100.00%)
575 adding changesets
576 576 changesets: 1 chunks
577 577 add changeset ef1ea85a6374
578 578 changesets: 2 chunks
579 579 add changeset f9cafe1212c8
580 580 changesets: 3 chunks
581 581 add changeset 911600dab2ae
582 582 adding manifests
583 583 manifests: 1/3 chunks (33.33%)
584 584 manifests: 2/3 chunks (66.67%)
585 585 manifests: 3/3 chunks (100.00%)
586 586 adding file changes
587 587 adding foo/Bar/file.txt revisions
588 588 files: 1/3 chunks (33.33%)
589 589 adding foo/file.txt revisions
590 590 files: 2/3 chunks (66.67%)
591 591 adding quux/file.py revisions
592 592 files: 3/3 chunks (100.00%)
593 593 added 3 changesets with 3 changes to 3 files
594 594 calling hook pretxnchangegroup.acl: hgext.acl.hook
595 595 acl: checking access for user "fred"
596 596 acl: acl.allow.branches not enabled
597 597 acl: acl.deny.branches not enabled
598 598 acl: acl.allow enabled, 1 entries for user fred
599 599 acl: acl.deny enabled, 2 entries for user fred
600 600 acl: branch access granted: "ef1ea85a6374" on branch "default"
601 601 acl: path access granted: "ef1ea85a6374"
602 602 acl: branch access granted: "f9cafe1212c8" on branch "default"
603 603 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
604 604 transaction abort!
605 605 rollback completed
606 606 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
607 607 no rollback information available
608 608 0:6675d58eff77
609 609
610 610
611 611 $ echo 'barney is not mentioned => not allowed anywhere'
612 612 barney is not mentioned => not allowed anywhere
613 613 $ do_push barney
614 614 Pushing as user barney
615 615 hgrc = """
616 616 [hooks]
617 617 pretxnchangegroup.acl = python:hgext.acl.hook
618 618 [acl]
619 619 sources = push
620 620 [acl.allow]
621 621 foo/** = fred
622 622 [acl.deny]
623 623 foo/bar/** = fred
624 624 foo/Bar/** = fred
625 625 """
626 626 pushing to ../b
627 627 query 1; heads
628 628 searching for changes
629 629 all remote heads known locally
630 630 invalid branchheads cache (served): tip differs
631 631 listing keys for "bookmarks"
632 632 3 changesets found
633 633 list of changesets:
634 634 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
635 635 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
636 636 911600dab2ae7a9baff75958b84fe606851ce955
637 adding changesets
638 637 bundling: 1/3 changesets (33.33%)
639 638 bundling: 2/3 changesets (66.67%)
640 639 bundling: 3/3 changesets (100.00%)
641 640 bundling: 1/3 manifests (33.33%)
642 641 bundling: 2/3 manifests (66.67%)
643 642 bundling: 3/3 manifests (100.00%)
644 643 bundling: foo/Bar/file.txt 1/3 files (33.33%)
645 644 bundling: foo/file.txt 2/3 files (66.67%)
646 645 bundling: quux/file.py 3/3 files (100.00%)
646 adding changesets
647 647 changesets: 1 chunks
648 648 add changeset ef1ea85a6374
649 649 changesets: 2 chunks
650 650 add changeset f9cafe1212c8
651 651 changesets: 3 chunks
652 652 add changeset 911600dab2ae
653 653 adding manifests
654 654 manifests: 1/3 chunks (33.33%)
655 655 manifests: 2/3 chunks (66.67%)
656 656 manifests: 3/3 chunks (100.00%)
657 657 adding file changes
658 658 adding foo/Bar/file.txt revisions
659 659 files: 1/3 chunks (33.33%)
660 660 adding foo/file.txt revisions
661 661 files: 2/3 chunks (66.67%)
662 662 adding quux/file.py revisions
663 663 files: 3/3 chunks (100.00%)
664 664 added 3 changesets with 3 changes to 3 files
665 665 calling hook pretxnchangegroup.acl: hgext.acl.hook
666 666 acl: checking access for user "barney"
667 667 acl: acl.allow.branches not enabled
668 668 acl: acl.deny.branches not enabled
669 669 acl: acl.allow enabled, 0 entries for user barney
670 670 acl: acl.deny enabled, 0 entries for user barney
671 671 acl: branch access granted: "ef1ea85a6374" on branch "default"
672 672 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
673 673 transaction abort!
674 674 rollback completed
675 675 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
676 676 no rollback information available
677 677 0:6675d58eff77
678 678
679 679
680 680 barney is allowed everywhere
681 681
682 682 $ echo '[acl.allow]' >> $config
683 683 $ echo '** = barney' >> $config
684 684 $ do_push barney
685 685 Pushing as user barney
686 686 hgrc = """
687 687 [hooks]
688 688 pretxnchangegroup.acl = python:hgext.acl.hook
689 689 [acl]
690 690 sources = push
691 691 [acl.allow]
692 692 foo/** = fred
693 693 [acl.deny]
694 694 foo/bar/** = fred
695 695 foo/Bar/** = fred
696 696 [acl.allow]
697 697 ** = barney
698 698 """
699 699 pushing to ../b
700 700 query 1; heads
701 701 searching for changes
702 702 all remote heads known locally
703 703 invalid branchheads cache (served): tip differs
704 704 listing keys for "bookmarks"
705 705 3 changesets found
706 706 list of changesets:
707 707 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
708 708 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
709 709 911600dab2ae7a9baff75958b84fe606851ce955
710 adding changesets
711 710 bundling: 1/3 changesets (33.33%)
712 711 bundling: 2/3 changesets (66.67%)
713 712 bundling: 3/3 changesets (100.00%)
714 713 bundling: 1/3 manifests (33.33%)
715 714 bundling: 2/3 manifests (66.67%)
716 715 bundling: 3/3 manifests (100.00%)
717 716 bundling: foo/Bar/file.txt 1/3 files (33.33%)
718 717 bundling: foo/file.txt 2/3 files (66.67%)
719 718 bundling: quux/file.py 3/3 files (100.00%)
719 adding changesets
720 720 changesets: 1 chunks
721 721 add changeset ef1ea85a6374
722 722 changesets: 2 chunks
723 723 add changeset f9cafe1212c8
724 724 changesets: 3 chunks
725 725 add changeset 911600dab2ae
726 726 adding manifests
727 727 manifests: 1/3 chunks (33.33%)
728 728 manifests: 2/3 chunks (66.67%)
729 729 manifests: 3/3 chunks (100.00%)
730 730 adding file changes
731 731 adding foo/Bar/file.txt revisions
732 732 files: 1/3 chunks (33.33%)
733 733 adding foo/file.txt revisions
734 734 files: 2/3 chunks (66.67%)
735 735 adding quux/file.py revisions
736 736 files: 3/3 chunks (100.00%)
737 737 added 3 changesets with 3 changes to 3 files
738 738 calling hook pretxnchangegroup.acl: hgext.acl.hook
739 739 acl: checking access for user "barney"
740 740 acl: acl.allow.branches not enabled
741 741 acl: acl.deny.branches not enabled
742 742 acl: acl.allow enabled, 1 entries for user barney
743 743 acl: acl.deny enabled, 0 entries for user barney
744 744 acl: branch access granted: "ef1ea85a6374" on branch "default"
745 745 acl: path access granted: "ef1ea85a6374"
746 746 acl: branch access granted: "f9cafe1212c8" on branch "default"
747 747 acl: path access granted: "f9cafe1212c8"
748 748 acl: branch access granted: "911600dab2ae" on branch "default"
749 749 acl: path access granted: "911600dab2ae"
750 750 updating the branch cache
751 751 listing keys for "phases"
752 752 try to push obsolete markers to remote
753 753 checking for updated bookmarks
754 754 listing keys for "bookmarks"
755 755 repository tip rolled back to revision 0 (undo push)
756 756 0:6675d58eff77
757 757
758 758
759 759 wilma can change files with a .txt extension
760 760
761 761 $ echo '**/*.txt = wilma' >> $config
762 762 $ do_push wilma
763 763 Pushing as user wilma
764 764 hgrc = """
765 765 [hooks]
766 766 pretxnchangegroup.acl = python:hgext.acl.hook
767 767 [acl]
768 768 sources = push
769 769 [acl.allow]
770 770 foo/** = fred
771 771 [acl.deny]
772 772 foo/bar/** = fred
773 773 foo/Bar/** = fred
774 774 [acl.allow]
775 775 ** = barney
776 776 **/*.txt = wilma
777 777 """
778 778 pushing to ../b
779 779 query 1; heads
780 780 searching for changes
781 781 all remote heads known locally
782 782 invalid branchheads cache (served): tip differs
783 783 listing keys for "bookmarks"
784 784 3 changesets found
785 785 list of changesets:
786 786 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
787 787 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
788 788 911600dab2ae7a9baff75958b84fe606851ce955
789 adding changesets
790 789 bundling: 1/3 changesets (33.33%)
791 790 bundling: 2/3 changesets (66.67%)
792 791 bundling: 3/3 changesets (100.00%)
793 792 bundling: 1/3 manifests (33.33%)
794 793 bundling: 2/3 manifests (66.67%)
795 794 bundling: 3/3 manifests (100.00%)
796 795 bundling: foo/Bar/file.txt 1/3 files (33.33%)
797 796 bundling: foo/file.txt 2/3 files (66.67%)
798 797 bundling: quux/file.py 3/3 files (100.00%)
798 adding changesets
799 799 changesets: 1 chunks
800 800 add changeset ef1ea85a6374
801 801 changesets: 2 chunks
802 802 add changeset f9cafe1212c8
803 803 changesets: 3 chunks
804 804 add changeset 911600dab2ae
805 805 adding manifests
806 806 manifests: 1/3 chunks (33.33%)
807 807 manifests: 2/3 chunks (66.67%)
808 808 manifests: 3/3 chunks (100.00%)
809 809 adding file changes
810 810 adding foo/Bar/file.txt revisions
811 811 files: 1/3 chunks (33.33%)
812 812 adding foo/file.txt revisions
813 813 files: 2/3 chunks (66.67%)
814 814 adding quux/file.py revisions
815 815 files: 3/3 chunks (100.00%)
816 816 added 3 changesets with 3 changes to 3 files
817 817 calling hook pretxnchangegroup.acl: hgext.acl.hook
818 818 acl: checking access for user "wilma"
819 819 acl: acl.allow.branches not enabled
820 820 acl: acl.deny.branches not enabled
821 821 acl: acl.allow enabled, 1 entries for user wilma
822 822 acl: acl.deny enabled, 0 entries for user wilma
823 823 acl: branch access granted: "ef1ea85a6374" on branch "default"
824 824 acl: path access granted: "ef1ea85a6374"
825 825 acl: branch access granted: "f9cafe1212c8" on branch "default"
826 826 acl: path access granted: "f9cafe1212c8"
827 827 acl: branch access granted: "911600dab2ae" on branch "default"
828 828 error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
829 829 transaction abort!
830 830 rollback completed
831 831 abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
832 832 no rollback information available
833 833 0:6675d58eff77
834 834
835 835
836 836 file specified by acl.config does not exist
837 837
838 838 $ echo '[acl]' >> $config
839 839 $ echo 'config = ../acl.config' >> $config
840 840 $ do_push barney
841 841 Pushing as user barney
842 842 hgrc = """
843 843 [hooks]
844 844 pretxnchangegroup.acl = python:hgext.acl.hook
845 845 [acl]
846 846 sources = push
847 847 [acl.allow]
848 848 foo/** = fred
849 849 [acl.deny]
850 850 foo/bar/** = fred
851 851 foo/Bar/** = fred
852 852 [acl.allow]
853 853 ** = barney
854 854 **/*.txt = wilma
855 855 [acl]
856 856 config = ../acl.config
857 857 """
858 858 pushing to ../b
859 859 query 1; heads
860 860 searching for changes
861 861 all remote heads known locally
862 862 invalid branchheads cache (served): tip differs
863 863 listing keys for "bookmarks"
864 864 3 changesets found
865 865 list of changesets:
866 866 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
867 867 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
868 868 911600dab2ae7a9baff75958b84fe606851ce955
869 adding changesets
870 869 bundling: 1/3 changesets (33.33%)
871 870 bundling: 2/3 changesets (66.67%)
872 871 bundling: 3/3 changesets (100.00%)
873 872 bundling: 1/3 manifests (33.33%)
874 873 bundling: 2/3 manifests (66.67%)
875 874 bundling: 3/3 manifests (100.00%)
876 875 bundling: foo/Bar/file.txt 1/3 files (33.33%)
877 876 bundling: foo/file.txt 2/3 files (66.67%)
878 877 bundling: quux/file.py 3/3 files (100.00%)
878 adding changesets
879 879 changesets: 1 chunks
880 880 add changeset ef1ea85a6374
881 881 changesets: 2 chunks
882 882 add changeset f9cafe1212c8
883 883 changesets: 3 chunks
884 884 add changeset 911600dab2ae
885 885 adding manifests
886 886 manifests: 1/3 chunks (33.33%)
887 887 manifests: 2/3 chunks (66.67%)
888 888 manifests: 3/3 chunks (100.00%)
889 889 adding file changes
890 890 adding foo/Bar/file.txt revisions
891 891 files: 1/3 chunks (33.33%)
892 892 adding foo/file.txt revisions
893 893 files: 2/3 chunks (66.67%)
894 894 adding quux/file.py revisions
895 895 files: 3/3 chunks (100.00%)
896 896 added 3 changesets with 3 changes to 3 files
897 897 calling hook pretxnchangegroup.acl: hgext.acl.hook
898 898 acl: checking access for user "barney"
899 899 error: pretxnchangegroup.acl hook raised an exception: [Errno *] *: '../acl.config' (glob)
900 900 transaction abort!
901 901 rollback completed
902 902 abort: *: ../acl.config (glob)
903 903 no rollback information available
904 904 0:6675d58eff77
905 905
906 906
907 907 betty is allowed inside foo/ by a acl.config file
908 908
909 909 $ echo '[acl.allow]' >> acl.config
910 910 $ echo 'foo/** = betty' >> acl.config
911 911 $ do_push betty
912 912 Pushing as user betty
913 913 hgrc = """
914 914 [hooks]
915 915 pretxnchangegroup.acl = python:hgext.acl.hook
916 916 [acl]
917 917 sources = push
918 918 [acl.allow]
919 919 foo/** = fred
920 920 [acl.deny]
921 921 foo/bar/** = fred
922 922 foo/Bar/** = fred
923 923 [acl.allow]
924 924 ** = barney
925 925 **/*.txt = wilma
926 926 [acl]
927 927 config = ../acl.config
928 928 """
929 929 acl.config = """
930 930 [acl.allow]
931 931 foo/** = betty
932 932 """
933 933 pushing to ../b
934 934 query 1; heads
935 935 searching for changes
936 936 all remote heads known locally
937 937 invalid branchheads cache (served): tip differs
938 938 listing keys for "bookmarks"
939 939 3 changesets found
940 940 list of changesets:
941 941 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
942 942 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
943 943 911600dab2ae7a9baff75958b84fe606851ce955
944 adding changesets
945 944 bundling: 1/3 changesets (33.33%)
946 945 bundling: 2/3 changesets (66.67%)
947 946 bundling: 3/3 changesets (100.00%)
948 947 bundling: 1/3 manifests (33.33%)
949 948 bundling: 2/3 manifests (66.67%)
950 949 bundling: 3/3 manifests (100.00%)
951 950 bundling: foo/Bar/file.txt 1/3 files (33.33%)
952 951 bundling: foo/file.txt 2/3 files (66.67%)
953 952 bundling: quux/file.py 3/3 files (100.00%)
953 adding changesets
954 954 changesets: 1 chunks
955 955 add changeset ef1ea85a6374
956 956 changesets: 2 chunks
957 957 add changeset f9cafe1212c8
958 958 changesets: 3 chunks
959 959 add changeset 911600dab2ae
960 960 adding manifests
961 961 manifests: 1/3 chunks (33.33%)
962 962 manifests: 2/3 chunks (66.67%)
963 963 manifests: 3/3 chunks (100.00%)
964 964 adding file changes
965 965 adding foo/Bar/file.txt revisions
966 966 files: 1/3 chunks (33.33%)
967 967 adding foo/file.txt revisions
968 968 files: 2/3 chunks (66.67%)
969 969 adding quux/file.py revisions
970 970 files: 3/3 chunks (100.00%)
971 971 added 3 changesets with 3 changes to 3 files
972 972 calling hook pretxnchangegroup.acl: hgext.acl.hook
973 973 acl: checking access for user "betty"
974 974 acl: acl.allow.branches not enabled
975 975 acl: acl.deny.branches not enabled
976 976 acl: acl.allow enabled, 1 entries for user betty
977 977 acl: acl.deny enabled, 0 entries for user betty
978 978 acl: branch access granted: "ef1ea85a6374" on branch "default"
979 979 acl: path access granted: "ef1ea85a6374"
980 980 acl: branch access granted: "f9cafe1212c8" on branch "default"
981 981 acl: path access granted: "f9cafe1212c8"
982 982 acl: branch access granted: "911600dab2ae" on branch "default"
983 983 error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
984 984 transaction abort!
985 985 rollback completed
986 986 abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
987 987 no rollback information available
988 988 0:6675d58eff77
989 989
990 990
991 991 acl.config can set only [acl.allow]/[acl.deny]
992 992
993 993 $ echo '[hooks]' >> acl.config
994 994 $ echo 'changegroup.acl = false' >> acl.config
995 995 $ do_push barney
996 996 Pushing as user barney
997 997 hgrc = """
998 998 [hooks]
999 999 pretxnchangegroup.acl = python:hgext.acl.hook
1000 1000 [acl]
1001 1001 sources = push
1002 1002 [acl.allow]
1003 1003 foo/** = fred
1004 1004 [acl.deny]
1005 1005 foo/bar/** = fred
1006 1006 foo/Bar/** = fred
1007 1007 [acl.allow]
1008 1008 ** = barney
1009 1009 **/*.txt = wilma
1010 1010 [acl]
1011 1011 config = ../acl.config
1012 1012 """
1013 1013 acl.config = """
1014 1014 [acl.allow]
1015 1015 foo/** = betty
1016 1016 [hooks]
1017 1017 changegroup.acl = false
1018 1018 """
1019 1019 pushing to ../b
1020 1020 query 1; heads
1021 1021 searching for changes
1022 1022 all remote heads known locally
1023 1023 invalid branchheads cache (served): tip differs
1024 1024 listing keys for "bookmarks"
1025 1025 3 changesets found
1026 1026 list of changesets:
1027 1027 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1028 1028 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1029 1029 911600dab2ae7a9baff75958b84fe606851ce955
1030 adding changesets
1031 1030 bundling: 1/3 changesets (33.33%)
1032 1031 bundling: 2/3 changesets (66.67%)
1033 1032 bundling: 3/3 changesets (100.00%)
1034 1033 bundling: 1/3 manifests (33.33%)
1035 1034 bundling: 2/3 manifests (66.67%)
1036 1035 bundling: 3/3 manifests (100.00%)
1037 1036 bundling: foo/Bar/file.txt 1/3 files (33.33%)
1038 1037 bundling: foo/file.txt 2/3 files (66.67%)
1039 1038 bundling: quux/file.py 3/3 files (100.00%)
1039 adding changesets
1040 1040 changesets: 1 chunks
1041 1041 add changeset ef1ea85a6374
1042 1042 changesets: 2 chunks
1043 1043 add changeset f9cafe1212c8
1044 1044 changesets: 3 chunks
1045 1045 add changeset 911600dab2ae
1046 1046 adding manifests
1047 1047 manifests: 1/3 chunks (33.33%)
1048 1048 manifests: 2/3 chunks (66.67%)
1049 1049 manifests: 3/3 chunks (100.00%)
1050 1050 adding file changes
1051 1051 adding foo/Bar/file.txt revisions
1052 1052 files: 1/3 chunks (33.33%)
1053 1053 adding foo/file.txt revisions
1054 1054 files: 2/3 chunks (66.67%)
1055 1055 adding quux/file.py revisions
1056 1056 files: 3/3 chunks (100.00%)
1057 1057 added 3 changesets with 3 changes to 3 files
1058 1058 calling hook pretxnchangegroup.acl: hgext.acl.hook
1059 1059 acl: checking access for user "barney"
1060 1060 acl: acl.allow.branches not enabled
1061 1061 acl: acl.deny.branches not enabled
1062 1062 acl: acl.allow enabled, 1 entries for user barney
1063 1063 acl: acl.deny enabled, 0 entries for user barney
1064 1064 acl: branch access granted: "ef1ea85a6374" on branch "default"
1065 1065 acl: path access granted: "ef1ea85a6374"
1066 1066 acl: branch access granted: "f9cafe1212c8" on branch "default"
1067 1067 acl: path access granted: "f9cafe1212c8"
1068 1068 acl: branch access granted: "911600dab2ae" on branch "default"
1069 1069 acl: path access granted: "911600dab2ae"
1070 1070 updating the branch cache
1071 1071 listing keys for "phases"
1072 1072 try to push obsolete markers to remote
1073 1073 checking for updated bookmarks
1074 1074 listing keys for "bookmarks"
1075 1075 repository tip rolled back to revision 0 (undo push)
1076 1076 0:6675d58eff77
1077 1077
1078 1078
1079 1079 asterisk
1080 1080
1081 1081 $ init_config
1082 1082
1083 1083 asterisk test
1084 1084
1085 1085 $ echo '[acl.allow]' >> $config
1086 1086 $ echo "** = fred" >> $config
1087 1087
1088 1088 fred is always allowed
1089 1089
1090 1090 $ do_push fred
1091 1091 Pushing as user fred
1092 1092 hgrc = """
1093 1093 [acl]
1094 1094 sources = push
1095 1095 [extensions]
1096 1096 [acl.allow]
1097 1097 ** = fred
1098 1098 """
1099 1099 pushing to ../b
1100 1100 query 1; heads
1101 1101 searching for changes
1102 1102 all remote heads known locally
1103 1103 invalid branchheads cache (served): tip differs
1104 1104 listing keys for "bookmarks"
1105 1105 3 changesets found
1106 1106 list of changesets:
1107 1107 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1108 1108 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1109 1109 911600dab2ae7a9baff75958b84fe606851ce955
1110 adding changesets
1111 1110 bundling: 1/3 changesets (33.33%)
1112 1111 bundling: 2/3 changesets (66.67%)
1113 1112 bundling: 3/3 changesets (100.00%)
1114 1113 bundling: 1/3 manifests (33.33%)
1115 1114 bundling: 2/3 manifests (66.67%)
1116 1115 bundling: 3/3 manifests (100.00%)
1117 1116 bundling: foo/Bar/file.txt 1/3 files (33.33%)
1118 1117 bundling: foo/file.txt 2/3 files (66.67%)
1119 1118 bundling: quux/file.py 3/3 files (100.00%)
1119 adding changesets
1120 1120 changesets: 1 chunks
1121 1121 add changeset ef1ea85a6374
1122 1122 changesets: 2 chunks
1123 1123 add changeset f9cafe1212c8
1124 1124 changesets: 3 chunks
1125 1125 add changeset 911600dab2ae
1126 1126 adding manifests
1127 1127 manifests: 1/3 chunks (33.33%)
1128 1128 manifests: 2/3 chunks (66.67%)
1129 1129 manifests: 3/3 chunks (100.00%)
1130 1130 adding file changes
1131 1131 adding foo/Bar/file.txt revisions
1132 1132 files: 1/3 chunks (33.33%)
1133 1133 adding foo/file.txt revisions
1134 1134 files: 2/3 chunks (66.67%)
1135 1135 adding quux/file.py revisions
1136 1136 files: 3/3 chunks (100.00%)
1137 1137 added 3 changesets with 3 changes to 3 files
1138 1138 calling hook pretxnchangegroup.acl: hgext.acl.hook
1139 1139 acl: checking access for user "fred"
1140 1140 acl: acl.allow.branches not enabled
1141 1141 acl: acl.deny.branches not enabled
1142 1142 acl: acl.allow enabled, 1 entries for user fred
1143 1143 acl: acl.deny not enabled
1144 1144 acl: branch access granted: "ef1ea85a6374" on branch "default"
1145 1145 acl: path access granted: "ef1ea85a6374"
1146 1146 acl: branch access granted: "f9cafe1212c8" on branch "default"
1147 1147 acl: path access granted: "f9cafe1212c8"
1148 1148 acl: branch access granted: "911600dab2ae" on branch "default"
1149 1149 acl: path access granted: "911600dab2ae"
1150 1150 updating the branch cache
1151 1151 listing keys for "phases"
1152 1152 try to push obsolete markers to remote
1153 1153 checking for updated bookmarks
1154 1154 listing keys for "bookmarks"
1155 1155 repository tip rolled back to revision 0 (undo push)
1156 1156 0:6675d58eff77
1157 1157
1158 1158
1159 1159 $ echo '[acl.deny]' >> $config
1160 1160 $ echo "foo/Bar/** = *" >> $config
1161 1161
1162 1162 no one is allowed inside foo/Bar/
1163 1163
1164 1164 $ do_push fred
1165 1165 Pushing as user fred
1166 1166 hgrc = """
1167 1167 [acl]
1168 1168 sources = push
1169 1169 [extensions]
1170 1170 [acl.allow]
1171 1171 ** = fred
1172 1172 [acl.deny]
1173 1173 foo/Bar/** = *
1174 1174 """
1175 1175 pushing to ../b
1176 1176 query 1; heads
1177 1177 searching for changes
1178 1178 all remote heads known locally
1179 1179 invalid branchheads cache (served): tip differs
1180 1180 listing keys for "bookmarks"
1181 1181 3 changesets found
1182 1182 list of changesets:
1183 1183 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1184 1184 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1185 1185 911600dab2ae7a9baff75958b84fe606851ce955
1186 adding changesets
1187 1186 bundling: 1/3 changesets (33.33%)
1188 1187 bundling: 2/3 changesets (66.67%)
1189 1188 bundling: 3/3 changesets (100.00%)
1190 1189 bundling: 1/3 manifests (33.33%)
1191 1190 bundling: 2/3 manifests (66.67%)
1192 1191 bundling: 3/3 manifests (100.00%)
1193 1192 bundling: foo/Bar/file.txt 1/3 files (33.33%)
1194 1193 bundling: foo/file.txt 2/3 files (66.67%)
1195 1194 bundling: quux/file.py 3/3 files (100.00%)
1195 adding changesets
1196 1196 changesets: 1 chunks
1197 1197 add changeset ef1ea85a6374
1198 1198 changesets: 2 chunks
1199 1199 add changeset f9cafe1212c8
1200 1200 changesets: 3 chunks
1201 1201 add changeset 911600dab2ae
1202 1202 adding manifests
1203 1203 manifests: 1/3 chunks (33.33%)
1204 1204 manifests: 2/3 chunks (66.67%)
1205 1205 manifests: 3/3 chunks (100.00%)
1206 1206 adding file changes
1207 1207 adding foo/Bar/file.txt revisions
1208 1208 files: 1/3 chunks (33.33%)
1209 1209 adding foo/file.txt revisions
1210 1210 files: 2/3 chunks (66.67%)
1211 1211 adding quux/file.py revisions
1212 1212 files: 3/3 chunks (100.00%)
1213 1213 added 3 changesets with 3 changes to 3 files
1214 1214 calling hook pretxnchangegroup.acl: hgext.acl.hook
1215 1215 acl: checking access for user "fred"
1216 1216 acl: acl.allow.branches not enabled
1217 1217 acl: acl.deny.branches not enabled
1218 1218 acl: acl.allow enabled, 1 entries for user fred
1219 1219 acl: acl.deny enabled, 1 entries for user fred
1220 1220 acl: branch access granted: "ef1ea85a6374" on branch "default"
1221 1221 acl: path access granted: "ef1ea85a6374"
1222 1222 acl: branch access granted: "f9cafe1212c8" on branch "default"
1223 1223 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1224 1224 transaction abort!
1225 1225 rollback completed
1226 1226 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1227 1227 no rollback information available
1228 1228 0:6675d58eff77
1229 1229
1230 1230
1231 1231 Groups
1232 1232
1233 1233 $ init_config
1234 1234
1235 1235 OS-level groups
1236 1236
1237 1237 $ echo '[acl.allow]' >> $config
1238 1238 $ echo "** = @group1" >> $config
1239 1239
1240 1240 @group1 is always allowed
1241 1241
1242 1242 $ do_push fred
1243 1243 Pushing as user fred
1244 1244 hgrc = """
1245 1245 [acl]
1246 1246 sources = push
1247 1247 [extensions]
1248 1248 [acl.allow]
1249 1249 ** = @group1
1250 1250 """
1251 1251 pushing to ../b
1252 1252 query 1; heads
1253 1253 searching for changes
1254 1254 all remote heads known locally
1255 1255 invalid branchheads cache (served): tip differs
1256 1256 listing keys for "bookmarks"
1257 1257 3 changesets found
1258 1258 list of changesets:
1259 1259 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1260 1260 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1261 1261 911600dab2ae7a9baff75958b84fe606851ce955
1262 adding changesets
1263 1262 bundling: 1/3 changesets (33.33%)
1264 1263 bundling: 2/3 changesets (66.67%)
1265 1264 bundling: 3/3 changesets (100.00%)
1266 1265 bundling: 1/3 manifests (33.33%)
1267 1266 bundling: 2/3 manifests (66.67%)
1268 1267 bundling: 3/3 manifests (100.00%)
1269 1268 bundling: foo/Bar/file.txt 1/3 files (33.33%)
1270 1269 bundling: foo/file.txt 2/3 files (66.67%)
1271 1270 bundling: quux/file.py 3/3 files (100.00%)
1271 adding changesets
1272 1272 changesets: 1 chunks
1273 1273 add changeset ef1ea85a6374
1274 1274 changesets: 2 chunks
1275 1275 add changeset f9cafe1212c8
1276 1276 changesets: 3 chunks
1277 1277 add changeset 911600dab2ae
1278 1278 adding manifests
1279 1279 manifests: 1/3 chunks (33.33%)
1280 1280 manifests: 2/3 chunks (66.67%)
1281 1281 manifests: 3/3 chunks (100.00%)
1282 1282 adding file changes
1283 1283 adding foo/Bar/file.txt revisions
1284 1284 files: 1/3 chunks (33.33%)
1285 1285 adding foo/file.txt revisions
1286 1286 files: 2/3 chunks (66.67%)
1287 1287 adding quux/file.py revisions
1288 1288 files: 3/3 chunks (100.00%)
1289 1289 added 3 changesets with 3 changes to 3 files
1290 1290 calling hook pretxnchangegroup.acl: hgext.acl.hook
1291 1291 acl: checking access for user "fred"
1292 1292 acl: acl.allow.branches not enabled
1293 1293 acl: acl.deny.branches not enabled
1294 1294 acl: "group1" not defined in [acl.groups]
1295 1295 acl: acl.allow enabled, 1 entries for user fred
1296 1296 acl: acl.deny not enabled
1297 1297 acl: branch access granted: "ef1ea85a6374" on branch "default"
1298 1298 acl: path access granted: "ef1ea85a6374"
1299 1299 acl: branch access granted: "f9cafe1212c8" on branch "default"
1300 1300 acl: path access granted: "f9cafe1212c8"
1301 1301 acl: branch access granted: "911600dab2ae" on branch "default"
1302 1302 acl: path access granted: "911600dab2ae"
1303 1303 updating the branch cache
1304 1304 listing keys for "phases"
1305 1305 try to push obsolete markers to remote
1306 1306 checking for updated bookmarks
1307 1307 listing keys for "bookmarks"
1308 1308 repository tip rolled back to revision 0 (undo push)
1309 1309 0:6675d58eff77
1310 1310
1311 1311
1312 1312 $ echo '[acl.deny]' >> $config
1313 1313 $ echo "foo/Bar/** = @group1" >> $config
1314 1314
1315 1315 @group is allowed inside anything but foo/Bar/
1316 1316
1317 1317 $ do_push fred
1318 1318 Pushing as user fred
1319 1319 hgrc = """
1320 1320 [acl]
1321 1321 sources = push
1322 1322 [extensions]
1323 1323 [acl.allow]
1324 1324 ** = @group1
1325 1325 [acl.deny]
1326 1326 foo/Bar/** = @group1
1327 1327 """
1328 1328 pushing to ../b
1329 1329 query 1; heads
1330 1330 searching for changes
1331 1331 all remote heads known locally
1332 1332 invalid branchheads cache (served): tip differs
1333 1333 listing keys for "bookmarks"
1334 1334 3 changesets found
1335 1335 list of changesets:
1336 1336 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1337 1337 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1338 1338 911600dab2ae7a9baff75958b84fe606851ce955
1339 adding changesets
1340 1339 bundling: 1/3 changesets (33.33%)
1341 1340 bundling: 2/3 changesets (66.67%)
1342 1341 bundling: 3/3 changesets (100.00%)
1343 1342 bundling: 1/3 manifests (33.33%)
1344 1343 bundling: 2/3 manifests (66.67%)
1345 1344 bundling: 3/3 manifests (100.00%)
1346 1345 bundling: foo/Bar/file.txt 1/3 files (33.33%)
1347 1346 bundling: foo/file.txt 2/3 files (66.67%)
1348 1347 bundling: quux/file.py 3/3 files (100.00%)
1348 adding changesets
1349 1349 changesets: 1 chunks
1350 1350 add changeset ef1ea85a6374
1351 1351 changesets: 2 chunks
1352 1352 add changeset f9cafe1212c8
1353 1353 changesets: 3 chunks
1354 1354 add changeset 911600dab2ae
1355 1355 adding manifests
1356 1356 manifests: 1/3 chunks (33.33%)
1357 1357 manifests: 2/3 chunks (66.67%)
1358 1358 manifests: 3/3 chunks (100.00%)
1359 1359 adding file changes
1360 1360 adding foo/Bar/file.txt revisions
1361 1361 files: 1/3 chunks (33.33%)
1362 1362 adding foo/file.txt revisions
1363 1363 files: 2/3 chunks (66.67%)
1364 1364 adding quux/file.py revisions
1365 1365 files: 3/3 chunks (100.00%)
1366 1366 added 3 changesets with 3 changes to 3 files
1367 1367 calling hook pretxnchangegroup.acl: hgext.acl.hook
1368 1368 acl: checking access for user "fred"
1369 1369 acl: acl.allow.branches not enabled
1370 1370 acl: acl.deny.branches not enabled
1371 1371 acl: "group1" not defined in [acl.groups]
1372 1372 acl: acl.allow enabled, 1 entries for user fred
1373 1373 acl: "group1" not defined in [acl.groups]
1374 1374 acl: acl.deny enabled, 1 entries for user fred
1375 1375 acl: branch access granted: "ef1ea85a6374" on branch "default"
1376 1376 acl: path access granted: "ef1ea85a6374"
1377 1377 acl: branch access granted: "f9cafe1212c8" on branch "default"
1378 1378 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1379 1379 transaction abort!
1380 1380 rollback completed
1381 1381 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1382 1382 no rollback information available
1383 1383 0:6675d58eff77
1384 1384
1385 1385
1386 1386 Invalid group
1387 1387
1388 1388 Disable the fakegroups trick to get real failures
1389 1389
1390 1390 $ grep -v fakegroups $config > config.tmp
1391 1391 $ mv config.tmp $config
1392 1392 $ echo '[acl.allow]' >> $config
1393 1393 $ echo "** = @unlikelytoexist" >> $config
1394 1394 $ do_push fred 2>&1 | grep unlikelytoexist
1395 1395 ** = @unlikelytoexist
1396 1396 acl: "unlikelytoexist" not defined in [acl.groups]
1397 1397 error: pretxnchangegroup.acl hook failed: group 'unlikelytoexist' is undefined
1398 1398 abort: group 'unlikelytoexist' is undefined
1399 1399
1400 1400
1401 1401 Branch acl tests setup
1402 1402
1403 1403 $ init_config
1404 1404 $ cd b
1405 1405 $ hg up
1406 1406 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1407 1407 $ hg branch foobar
1408 1408 marked working directory as branch foobar
1409 1409 (branches are permanent and global, did you want a bookmark?)
1410 1410 $ hg commit -m 'create foobar'
1411 1411 $ echo 'foo contents' > abc.txt
1412 1412 $ hg add abc.txt
1413 1413 $ hg commit -m 'foobar contents'
1414 1414 $ cd ..
1415 1415 $ hg --cwd a pull ../b
1416 1416 pulling from ../b
1417 1417 searching for changes
1418 1418 adding changesets
1419 1419 adding manifests
1420 1420 adding file changes
1421 1421 added 2 changesets with 1 changes to 1 files (+1 heads)
1422 1422 (run 'hg heads' to see heads)
1423 1423
1424 1424 Create additional changeset on foobar branch
1425 1425
1426 1426 $ cd a
1427 1427 $ hg up -C foobar
1428 1428 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1429 1429 $ echo 'foo contents2' > abc.txt
1430 1430 $ hg commit -m 'foobar contents2'
1431 1431 $ cd ..
1432 1432
1433 1433
1434 1434 No branch acls specified
1435 1435
1436 1436 $ do_push astro
1437 1437 Pushing as user astro
1438 1438 hgrc = """
1439 1439 [acl]
1440 1440 sources = push
1441 1441 [extensions]
1442 1442 """
1443 1443 pushing to ../b
1444 1444 query 1; heads
1445 1445 searching for changes
1446 1446 all remote heads known locally
1447 1447 listing keys for "bookmarks"
1448 1448 4 changesets found
1449 1449 list of changesets:
1450 1450 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1451 1451 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1452 1452 911600dab2ae7a9baff75958b84fe606851ce955
1453 1453 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1454 adding changesets
1455 1454 bundling: 1/4 changesets (25.00%)
1456 1455 bundling: 2/4 changesets (50.00%)
1457 1456 bundling: 3/4 changesets (75.00%)
1458 1457 bundling: 4/4 changesets (100.00%)
1459 1458 bundling: 1/4 manifests (25.00%)
1460 1459 bundling: 2/4 manifests (50.00%)
1461 1460 bundling: 3/4 manifests (75.00%)
1462 1461 bundling: 4/4 manifests (100.00%)
1463 1462 bundling: abc.txt 1/4 files (25.00%)
1464 1463 bundling: foo/Bar/file.txt 2/4 files (50.00%)
1465 1464 bundling: foo/file.txt 3/4 files (75.00%)
1466 1465 bundling: quux/file.py 4/4 files (100.00%)
1466 adding changesets
1467 1467 changesets: 1 chunks
1468 1468 add changeset ef1ea85a6374
1469 1469 changesets: 2 chunks
1470 1470 add changeset f9cafe1212c8
1471 1471 changesets: 3 chunks
1472 1472 add changeset 911600dab2ae
1473 1473 changesets: 4 chunks
1474 1474 add changeset e8fc755d4d82
1475 1475 adding manifests
1476 1476 manifests: 1/4 chunks (25.00%)
1477 1477 manifests: 2/4 chunks (50.00%)
1478 1478 manifests: 3/4 chunks (75.00%)
1479 1479 manifests: 4/4 chunks (100.00%)
1480 1480 adding file changes
1481 1481 adding abc.txt revisions
1482 1482 files: 1/4 chunks (25.00%)
1483 1483 adding foo/Bar/file.txt revisions
1484 1484 files: 2/4 chunks (50.00%)
1485 1485 adding foo/file.txt revisions
1486 1486 files: 3/4 chunks (75.00%)
1487 1487 adding quux/file.py revisions
1488 1488 files: 4/4 chunks (100.00%)
1489 1489 added 4 changesets with 4 changes to 4 files (+1 heads)
1490 1490 calling hook pretxnchangegroup.acl: hgext.acl.hook
1491 1491 acl: checking access for user "astro"
1492 1492 acl: acl.allow.branches not enabled
1493 1493 acl: acl.deny.branches not enabled
1494 1494 acl: acl.allow not enabled
1495 1495 acl: acl.deny not enabled
1496 1496 acl: branch access granted: "ef1ea85a6374" on branch "default"
1497 1497 acl: path access granted: "ef1ea85a6374"
1498 1498 acl: branch access granted: "f9cafe1212c8" on branch "default"
1499 1499 acl: path access granted: "f9cafe1212c8"
1500 1500 acl: branch access granted: "911600dab2ae" on branch "default"
1501 1501 acl: path access granted: "911600dab2ae"
1502 1502 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1503 1503 acl: path access granted: "e8fc755d4d82"
1504 1504 updating the branch cache
1505 1505 listing keys for "phases"
1506 1506 try to push obsolete markers to remote
1507 1507 checking for updated bookmarks
1508 1508 listing keys for "bookmarks"
1509 1509 repository tip rolled back to revision 2 (undo push)
1510 1510 2:fb35475503ef
1511 1511
1512 1512
1513 1513 Branch acl deny test
1514 1514
1515 1515 $ echo "[acl.deny.branches]" >> $config
1516 1516 $ echo "foobar = *" >> $config
1517 1517 $ do_push astro
1518 1518 Pushing as user astro
1519 1519 hgrc = """
1520 1520 [acl]
1521 1521 sources = push
1522 1522 [extensions]
1523 1523 [acl.deny.branches]
1524 1524 foobar = *
1525 1525 """
1526 1526 pushing to ../b
1527 1527 query 1; heads
1528 1528 searching for changes
1529 1529 all remote heads known locally
1530 1530 listing keys for "bookmarks"
1531 1531 4 changesets found
1532 1532 list of changesets:
1533 1533 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1534 1534 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1535 1535 911600dab2ae7a9baff75958b84fe606851ce955
1536 1536 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1537 adding changesets
1538 1537 bundling: 1/4 changesets (25.00%)
1539 1538 bundling: 2/4 changesets (50.00%)
1540 1539 bundling: 3/4 changesets (75.00%)
1541 1540 bundling: 4/4 changesets (100.00%)
1542 1541 bundling: 1/4 manifests (25.00%)
1543 1542 bundling: 2/4 manifests (50.00%)
1544 1543 bundling: 3/4 manifests (75.00%)
1545 1544 bundling: 4/4 manifests (100.00%)
1546 1545 bundling: abc.txt 1/4 files (25.00%)
1547 1546 bundling: foo/Bar/file.txt 2/4 files (50.00%)
1548 1547 bundling: foo/file.txt 3/4 files (75.00%)
1549 1548 bundling: quux/file.py 4/4 files (100.00%)
1549 adding changesets
1550 1550 changesets: 1 chunks
1551 1551 add changeset ef1ea85a6374
1552 1552 changesets: 2 chunks
1553 1553 add changeset f9cafe1212c8
1554 1554 changesets: 3 chunks
1555 1555 add changeset 911600dab2ae
1556 1556 changesets: 4 chunks
1557 1557 add changeset e8fc755d4d82
1558 1558 adding manifests
1559 1559 manifests: 1/4 chunks (25.00%)
1560 1560 manifests: 2/4 chunks (50.00%)
1561 1561 manifests: 3/4 chunks (75.00%)
1562 1562 manifests: 4/4 chunks (100.00%)
1563 1563 adding file changes
1564 1564 adding abc.txt revisions
1565 1565 files: 1/4 chunks (25.00%)
1566 1566 adding foo/Bar/file.txt revisions
1567 1567 files: 2/4 chunks (50.00%)
1568 1568 adding foo/file.txt revisions
1569 1569 files: 3/4 chunks (75.00%)
1570 1570 adding quux/file.py revisions
1571 1571 files: 4/4 chunks (100.00%)
1572 1572 added 4 changesets with 4 changes to 4 files (+1 heads)
1573 1573 calling hook pretxnchangegroup.acl: hgext.acl.hook
1574 1574 acl: checking access for user "astro"
1575 1575 acl: acl.allow.branches not enabled
1576 1576 acl: acl.deny.branches enabled, 1 entries for user astro
1577 1577 acl: acl.allow not enabled
1578 1578 acl: acl.deny not enabled
1579 1579 acl: branch access granted: "ef1ea85a6374" on branch "default"
1580 1580 acl: path access granted: "ef1ea85a6374"
1581 1581 acl: branch access granted: "f9cafe1212c8" on branch "default"
1582 1582 acl: path access granted: "f9cafe1212c8"
1583 1583 acl: branch access granted: "911600dab2ae" on branch "default"
1584 1584 acl: path access granted: "911600dab2ae"
1585 1585 error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1586 1586 transaction abort!
1587 1587 rollback completed
1588 1588 abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1589 1589 no rollback information available
1590 1590 2:fb35475503ef
1591 1591
1592 1592
1593 1593 Branch acl empty allow test
1594 1594
1595 1595 $ init_config
1596 1596 $ echo "[acl.allow.branches]" >> $config
1597 1597 $ do_push astro
1598 1598 Pushing as user astro
1599 1599 hgrc = """
1600 1600 [acl]
1601 1601 sources = push
1602 1602 [extensions]
1603 1603 [acl.allow.branches]
1604 1604 """
1605 1605 pushing to ../b
1606 1606 query 1; heads
1607 1607 searching for changes
1608 1608 all remote heads known locally
1609 1609 listing keys for "bookmarks"
1610 1610 4 changesets found
1611 1611 list of changesets:
1612 1612 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1613 1613 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1614 1614 911600dab2ae7a9baff75958b84fe606851ce955
1615 1615 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1616 adding changesets
1617 1616 bundling: 1/4 changesets (25.00%)
1618 1617 bundling: 2/4 changesets (50.00%)
1619 1618 bundling: 3/4 changesets (75.00%)
1620 1619 bundling: 4/4 changesets (100.00%)
1621 1620 bundling: 1/4 manifests (25.00%)
1622 1621 bundling: 2/4 manifests (50.00%)
1623 1622 bundling: 3/4 manifests (75.00%)
1624 1623 bundling: 4/4 manifests (100.00%)
1625 1624 bundling: abc.txt 1/4 files (25.00%)
1626 1625 bundling: foo/Bar/file.txt 2/4 files (50.00%)
1627 1626 bundling: foo/file.txt 3/4 files (75.00%)
1628 1627 bundling: quux/file.py 4/4 files (100.00%)
1628 adding changesets
1629 1629 changesets: 1 chunks
1630 1630 add changeset ef1ea85a6374
1631 1631 changesets: 2 chunks
1632 1632 add changeset f9cafe1212c8
1633 1633 changesets: 3 chunks
1634 1634 add changeset 911600dab2ae
1635 1635 changesets: 4 chunks
1636 1636 add changeset e8fc755d4d82
1637 1637 adding manifests
1638 1638 manifests: 1/4 chunks (25.00%)
1639 1639 manifests: 2/4 chunks (50.00%)
1640 1640 manifests: 3/4 chunks (75.00%)
1641 1641 manifests: 4/4 chunks (100.00%)
1642 1642 adding file changes
1643 1643 adding abc.txt revisions
1644 1644 files: 1/4 chunks (25.00%)
1645 1645 adding foo/Bar/file.txt revisions
1646 1646 files: 2/4 chunks (50.00%)
1647 1647 adding foo/file.txt revisions
1648 1648 files: 3/4 chunks (75.00%)
1649 1649 adding quux/file.py revisions
1650 1650 files: 4/4 chunks (100.00%)
1651 1651 added 4 changesets with 4 changes to 4 files (+1 heads)
1652 1652 calling hook pretxnchangegroup.acl: hgext.acl.hook
1653 1653 acl: checking access for user "astro"
1654 1654 acl: acl.allow.branches enabled, 0 entries for user astro
1655 1655 acl: acl.deny.branches not enabled
1656 1656 acl: acl.allow not enabled
1657 1657 acl: acl.deny not enabled
1658 1658 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1659 1659 transaction abort!
1660 1660 rollback completed
1661 1661 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1662 1662 no rollback information available
1663 1663 2:fb35475503ef
1664 1664
1665 1665
1666 1666 Branch acl allow other
1667 1667
1668 1668 $ init_config
1669 1669 $ echo "[acl.allow.branches]" >> $config
1670 1670 $ echo "* = george" >> $config
1671 1671 $ do_push astro
1672 1672 Pushing as user astro
1673 1673 hgrc = """
1674 1674 [acl]
1675 1675 sources = push
1676 1676 [extensions]
1677 1677 [acl.allow.branches]
1678 1678 * = george
1679 1679 """
1680 1680 pushing to ../b
1681 1681 query 1; heads
1682 1682 searching for changes
1683 1683 all remote heads known locally
1684 1684 listing keys for "bookmarks"
1685 1685 4 changesets found
1686 1686 list of changesets:
1687 1687 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1688 1688 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1689 1689 911600dab2ae7a9baff75958b84fe606851ce955
1690 1690 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1691 adding changesets
1692 1691 bundling: 1/4 changesets (25.00%)
1693 1692 bundling: 2/4 changesets (50.00%)
1694 1693 bundling: 3/4 changesets (75.00%)
1695 1694 bundling: 4/4 changesets (100.00%)
1696 1695 bundling: 1/4 manifests (25.00%)
1697 1696 bundling: 2/4 manifests (50.00%)
1698 1697 bundling: 3/4 manifests (75.00%)
1699 1698 bundling: 4/4 manifests (100.00%)
1700 1699 bundling: abc.txt 1/4 files (25.00%)
1701 1700 bundling: foo/Bar/file.txt 2/4 files (50.00%)
1702 1701 bundling: foo/file.txt 3/4 files (75.00%)
1703 1702 bundling: quux/file.py 4/4 files (100.00%)
1703 adding changesets
1704 1704 changesets: 1 chunks
1705 1705 add changeset ef1ea85a6374
1706 1706 changesets: 2 chunks
1707 1707 add changeset f9cafe1212c8
1708 1708 changesets: 3 chunks
1709 1709 add changeset 911600dab2ae
1710 1710 changesets: 4 chunks
1711 1711 add changeset e8fc755d4d82
1712 1712 adding manifests
1713 1713 manifests: 1/4 chunks (25.00%)
1714 1714 manifests: 2/4 chunks (50.00%)
1715 1715 manifests: 3/4 chunks (75.00%)
1716 1716 manifests: 4/4 chunks (100.00%)
1717 1717 adding file changes
1718 1718 adding abc.txt revisions
1719 1719 files: 1/4 chunks (25.00%)
1720 1720 adding foo/Bar/file.txt revisions
1721 1721 files: 2/4 chunks (50.00%)
1722 1722 adding foo/file.txt revisions
1723 1723 files: 3/4 chunks (75.00%)
1724 1724 adding quux/file.py revisions
1725 1725 files: 4/4 chunks (100.00%)
1726 1726 added 4 changesets with 4 changes to 4 files (+1 heads)
1727 1727 calling hook pretxnchangegroup.acl: hgext.acl.hook
1728 1728 acl: checking access for user "astro"
1729 1729 acl: acl.allow.branches enabled, 0 entries for user astro
1730 1730 acl: acl.deny.branches not enabled
1731 1731 acl: acl.allow not enabled
1732 1732 acl: acl.deny not enabled
1733 1733 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1734 1734 transaction abort!
1735 1735 rollback completed
1736 1736 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1737 1737 no rollback information available
1738 1738 2:fb35475503ef
1739 1739
1740 1740 $ do_push george
1741 1741 Pushing as user george
1742 1742 hgrc = """
1743 1743 [acl]
1744 1744 sources = push
1745 1745 [extensions]
1746 1746 [acl.allow.branches]
1747 1747 * = george
1748 1748 """
1749 1749 pushing to ../b
1750 1750 query 1; heads
1751 1751 searching for changes
1752 1752 all remote heads known locally
1753 1753 listing keys for "bookmarks"
1754 1754 4 changesets found
1755 1755 list of changesets:
1756 1756 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1757 1757 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1758 1758 911600dab2ae7a9baff75958b84fe606851ce955
1759 1759 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1760 adding changesets
1761 1760 bundling: 1/4 changesets (25.00%)
1762 1761 bundling: 2/4 changesets (50.00%)
1763 1762 bundling: 3/4 changesets (75.00%)
1764 1763 bundling: 4/4 changesets (100.00%)
1765 1764 bundling: 1/4 manifests (25.00%)
1766 1765 bundling: 2/4 manifests (50.00%)
1767 1766 bundling: 3/4 manifests (75.00%)
1768 1767 bundling: 4/4 manifests (100.00%)
1769 1768 bundling: abc.txt 1/4 files (25.00%)
1770 1769 bundling: foo/Bar/file.txt 2/4 files (50.00%)
1771 1770 bundling: foo/file.txt 3/4 files (75.00%)
1772 1771 bundling: quux/file.py 4/4 files (100.00%)
1772 adding changesets
1773 1773 changesets: 1 chunks
1774 1774 add changeset ef1ea85a6374
1775 1775 changesets: 2 chunks
1776 1776 add changeset f9cafe1212c8
1777 1777 changesets: 3 chunks
1778 1778 add changeset 911600dab2ae
1779 1779 changesets: 4 chunks
1780 1780 add changeset e8fc755d4d82
1781 1781 adding manifests
1782 1782 manifests: 1/4 chunks (25.00%)
1783 1783 manifests: 2/4 chunks (50.00%)
1784 1784 manifests: 3/4 chunks (75.00%)
1785 1785 manifests: 4/4 chunks (100.00%)
1786 1786 adding file changes
1787 1787 adding abc.txt revisions
1788 1788 files: 1/4 chunks (25.00%)
1789 1789 adding foo/Bar/file.txt revisions
1790 1790 files: 2/4 chunks (50.00%)
1791 1791 adding foo/file.txt revisions
1792 1792 files: 3/4 chunks (75.00%)
1793 1793 adding quux/file.py revisions
1794 1794 files: 4/4 chunks (100.00%)
1795 1795 added 4 changesets with 4 changes to 4 files (+1 heads)
1796 1796 calling hook pretxnchangegroup.acl: hgext.acl.hook
1797 1797 acl: checking access for user "george"
1798 1798 acl: acl.allow.branches enabled, 1 entries for user george
1799 1799 acl: acl.deny.branches not enabled
1800 1800 acl: acl.allow not enabled
1801 1801 acl: acl.deny not enabled
1802 1802 acl: branch access granted: "ef1ea85a6374" on branch "default"
1803 1803 acl: path access granted: "ef1ea85a6374"
1804 1804 acl: branch access granted: "f9cafe1212c8" on branch "default"
1805 1805 acl: path access granted: "f9cafe1212c8"
1806 1806 acl: branch access granted: "911600dab2ae" on branch "default"
1807 1807 acl: path access granted: "911600dab2ae"
1808 1808 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1809 1809 acl: path access granted: "e8fc755d4d82"
1810 1810 updating the branch cache
1811 1811 listing keys for "phases"
1812 1812 try to push obsolete markers to remote
1813 1813 checking for updated bookmarks
1814 1814 listing keys for "bookmarks"
1815 1815 repository tip rolled back to revision 2 (undo push)
1816 1816 2:fb35475503ef
1817 1817
1818 1818
1819 1819 Branch acl conflicting allow
1820 1820 asterisk ends up applying to all branches and allowing george to
1821 1821 push foobar into the remote
1822 1822
1823 1823 $ init_config
1824 1824 $ echo "[acl.allow.branches]" >> $config
1825 1825 $ echo "foobar = astro" >> $config
1826 1826 $ echo "* = george" >> $config
1827 1827 $ do_push george
1828 1828 Pushing as user george
1829 1829 hgrc = """
1830 1830 [acl]
1831 1831 sources = push
1832 1832 [extensions]
1833 1833 [acl.allow.branches]
1834 1834 foobar = astro
1835 1835 * = george
1836 1836 """
1837 1837 pushing to ../b
1838 1838 query 1; heads
1839 1839 searching for changes
1840 1840 all remote heads known locally
1841 1841 listing keys for "bookmarks"
1842 1842 4 changesets found
1843 1843 list of changesets:
1844 1844 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1845 1845 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1846 1846 911600dab2ae7a9baff75958b84fe606851ce955
1847 1847 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1848 adding changesets
1849 1848 bundling: 1/4 changesets (25.00%)
1850 1849 bundling: 2/4 changesets (50.00%)
1851 1850 bundling: 3/4 changesets (75.00%)
1852 1851 bundling: 4/4 changesets (100.00%)
1853 1852 bundling: 1/4 manifests (25.00%)
1854 1853 bundling: 2/4 manifests (50.00%)
1855 1854 bundling: 3/4 manifests (75.00%)
1856 1855 bundling: 4/4 manifests (100.00%)
1857 1856 bundling: abc.txt 1/4 files (25.00%)
1858 1857 bundling: foo/Bar/file.txt 2/4 files (50.00%)
1859 1858 bundling: foo/file.txt 3/4 files (75.00%)
1860 1859 bundling: quux/file.py 4/4 files (100.00%)
1860 adding changesets
1861 1861 changesets: 1 chunks
1862 1862 add changeset ef1ea85a6374
1863 1863 changesets: 2 chunks
1864 1864 add changeset f9cafe1212c8
1865 1865 changesets: 3 chunks
1866 1866 add changeset 911600dab2ae
1867 1867 changesets: 4 chunks
1868 1868 add changeset e8fc755d4d82
1869 1869 adding manifests
1870 1870 manifests: 1/4 chunks (25.00%)
1871 1871 manifests: 2/4 chunks (50.00%)
1872 1872 manifests: 3/4 chunks (75.00%)
1873 1873 manifests: 4/4 chunks (100.00%)
1874 1874 adding file changes
1875 1875 adding abc.txt revisions
1876 1876 files: 1/4 chunks (25.00%)
1877 1877 adding foo/Bar/file.txt revisions
1878 1878 files: 2/4 chunks (50.00%)
1879 1879 adding foo/file.txt revisions
1880 1880 files: 3/4 chunks (75.00%)
1881 1881 adding quux/file.py revisions
1882 1882 files: 4/4 chunks (100.00%)
1883 1883 added 4 changesets with 4 changes to 4 files (+1 heads)
1884 1884 calling hook pretxnchangegroup.acl: hgext.acl.hook
1885 1885 acl: checking access for user "george"
1886 1886 acl: acl.allow.branches enabled, 1 entries for user george
1887 1887 acl: acl.deny.branches not enabled
1888 1888 acl: acl.allow not enabled
1889 1889 acl: acl.deny not enabled
1890 1890 acl: branch access granted: "ef1ea85a6374" on branch "default"
1891 1891 acl: path access granted: "ef1ea85a6374"
1892 1892 acl: branch access granted: "f9cafe1212c8" on branch "default"
1893 1893 acl: path access granted: "f9cafe1212c8"
1894 1894 acl: branch access granted: "911600dab2ae" on branch "default"
1895 1895 acl: path access granted: "911600dab2ae"
1896 1896 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1897 1897 acl: path access granted: "e8fc755d4d82"
1898 1898 updating the branch cache
1899 1899 listing keys for "phases"
1900 1900 try to push obsolete markers to remote
1901 1901 checking for updated bookmarks
1902 1902 listing keys for "bookmarks"
1903 1903 repository tip rolled back to revision 2 (undo push)
1904 1904 2:fb35475503ef
1905 1905
1906 1906 Branch acl conflicting deny
1907 1907
1908 1908 $ init_config
1909 1909 $ echo "[acl.deny.branches]" >> $config
1910 1910 $ echo "foobar = astro" >> $config
1911 1911 $ echo "default = astro" >> $config
1912 1912 $ echo "* = george" >> $config
1913 1913 $ do_push george
1914 1914 Pushing as user george
1915 1915 hgrc = """
1916 1916 [acl]
1917 1917 sources = push
1918 1918 [extensions]
1919 1919 [acl.deny.branches]
1920 1920 foobar = astro
1921 1921 default = astro
1922 1922 * = george
1923 1923 """
1924 1924 pushing to ../b
1925 1925 query 1; heads
1926 1926 searching for changes
1927 1927 all remote heads known locally
1928 1928 listing keys for "bookmarks"
1929 1929 4 changesets found
1930 1930 list of changesets:
1931 1931 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1932 1932 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1933 1933 911600dab2ae7a9baff75958b84fe606851ce955
1934 1934 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1935 adding changesets
1936 1935 bundling: 1/4 changesets (25.00%)
1937 1936 bundling: 2/4 changesets (50.00%)
1938 1937 bundling: 3/4 changesets (75.00%)
1939 1938 bundling: 4/4 changesets (100.00%)
1940 1939 bundling: 1/4 manifests (25.00%)
1941 1940 bundling: 2/4 manifests (50.00%)
1942 1941 bundling: 3/4 manifests (75.00%)
1943 1942 bundling: 4/4 manifests (100.00%)
1944 1943 bundling: abc.txt 1/4 files (25.00%)
1945 1944 bundling: foo/Bar/file.txt 2/4 files (50.00%)
1946 1945 bundling: foo/file.txt 3/4 files (75.00%)
1947 1946 bundling: quux/file.py 4/4 files (100.00%)
1947 adding changesets
1948 1948 changesets: 1 chunks
1949 1949 add changeset ef1ea85a6374
1950 1950 changesets: 2 chunks
1951 1951 add changeset f9cafe1212c8
1952 1952 changesets: 3 chunks
1953 1953 add changeset 911600dab2ae
1954 1954 changesets: 4 chunks
1955 1955 add changeset e8fc755d4d82
1956 1956 adding manifests
1957 1957 manifests: 1/4 chunks (25.00%)
1958 1958 manifests: 2/4 chunks (50.00%)
1959 1959 manifests: 3/4 chunks (75.00%)
1960 1960 manifests: 4/4 chunks (100.00%)
1961 1961 adding file changes
1962 1962 adding abc.txt revisions
1963 1963 files: 1/4 chunks (25.00%)
1964 1964 adding foo/Bar/file.txt revisions
1965 1965 files: 2/4 chunks (50.00%)
1966 1966 adding foo/file.txt revisions
1967 1967 files: 3/4 chunks (75.00%)
1968 1968 adding quux/file.py revisions
1969 1969 files: 4/4 chunks (100.00%)
1970 1970 added 4 changesets with 4 changes to 4 files (+1 heads)
1971 1971 calling hook pretxnchangegroup.acl: hgext.acl.hook
1972 1972 acl: checking access for user "george"
1973 1973 acl: acl.allow.branches not enabled
1974 1974 acl: acl.deny.branches enabled, 1 entries for user george
1975 1975 acl: acl.allow not enabled
1976 1976 acl: acl.deny not enabled
1977 1977 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
1978 1978 transaction abort!
1979 1979 rollback completed
1980 1980 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
1981 1981 no rollback information available
1982 1982 2:fb35475503ef
1983 1983
1984 1984 User 'astro' must not be denied
1985 1985
1986 1986 $ init_config
1987 1987 $ echo "[acl.deny.branches]" >> $config
1988 1988 $ echo "default = !astro" >> $config
1989 1989 $ do_push astro
1990 1990 Pushing as user astro
1991 1991 hgrc = """
1992 1992 [acl]
1993 1993 sources = push
1994 1994 [extensions]
1995 1995 [acl.deny.branches]
1996 1996 default = !astro
1997 1997 """
1998 1998 pushing to ../b
1999 1999 query 1; heads
2000 2000 searching for changes
2001 2001 all remote heads known locally
2002 2002 listing keys for "bookmarks"
2003 2003 4 changesets found
2004 2004 list of changesets:
2005 2005 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2006 2006 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2007 2007 911600dab2ae7a9baff75958b84fe606851ce955
2008 2008 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2009 adding changesets
2010 2009 bundling: 1/4 changesets (25.00%)
2011 2010 bundling: 2/4 changesets (50.00%)
2012 2011 bundling: 3/4 changesets (75.00%)
2013 2012 bundling: 4/4 changesets (100.00%)
2014 2013 bundling: 1/4 manifests (25.00%)
2015 2014 bundling: 2/4 manifests (50.00%)
2016 2015 bundling: 3/4 manifests (75.00%)
2017 2016 bundling: 4/4 manifests (100.00%)
2018 2017 bundling: abc.txt 1/4 files (25.00%)
2019 2018 bundling: foo/Bar/file.txt 2/4 files (50.00%)
2020 2019 bundling: foo/file.txt 3/4 files (75.00%)
2021 2020 bundling: quux/file.py 4/4 files (100.00%)
2021 adding changesets
2022 2022 changesets: 1 chunks
2023 2023 add changeset ef1ea85a6374
2024 2024 changesets: 2 chunks
2025 2025 add changeset f9cafe1212c8
2026 2026 changesets: 3 chunks
2027 2027 add changeset 911600dab2ae
2028 2028 changesets: 4 chunks
2029 2029 add changeset e8fc755d4d82
2030 2030 adding manifests
2031 2031 manifests: 1/4 chunks (25.00%)
2032 2032 manifests: 2/4 chunks (50.00%)
2033 2033 manifests: 3/4 chunks (75.00%)
2034 2034 manifests: 4/4 chunks (100.00%)
2035 2035 adding file changes
2036 2036 adding abc.txt revisions
2037 2037 files: 1/4 chunks (25.00%)
2038 2038 adding foo/Bar/file.txt revisions
2039 2039 files: 2/4 chunks (50.00%)
2040 2040 adding foo/file.txt revisions
2041 2041 files: 3/4 chunks (75.00%)
2042 2042 adding quux/file.py revisions
2043 2043 files: 4/4 chunks (100.00%)
2044 2044 added 4 changesets with 4 changes to 4 files (+1 heads)
2045 2045 calling hook pretxnchangegroup.acl: hgext.acl.hook
2046 2046 acl: checking access for user "astro"
2047 2047 acl: acl.allow.branches not enabled
2048 2048 acl: acl.deny.branches enabled, 0 entries for user astro
2049 2049 acl: acl.allow not enabled
2050 2050 acl: acl.deny not enabled
2051 2051 acl: branch access granted: "ef1ea85a6374" on branch "default"
2052 2052 acl: path access granted: "ef1ea85a6374"
2053 2053 acl: branch access granted: "f9cafe1212c8" on branch "default"
2054 2054 acl: path access granted: "f9cafe1212c8"
2055 2055 acl: branch access granted: "911600dab2ae" on branch "default"
2056 2056 acl: path access granted: "911600dab2ae"
2057 2057 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2058 2058 acl: path access granted: "e8fc755d4d82"
2059 2059 updating the branch cache
2060 2060 listing keys for "phases"
2061 2061 try to push obsolete markers to remote
2062 2062 checking for updated bookmarks
2063 2063 listing keys for "bookmarks"
2064 2064 repository tip rolled back to revision 2 (undo push)
2065 2065 2:fb35475503ef
2066 2066
2067 2067
2068 2068 Non-astro users must be denied
2069 2069
2070 2070 $ do_push george
2071 2071 Pushing as user george
2072 2072 hgrc = """
2073 2073 [acl]
2074 2074 sources = push
2075 2075 [extensions]
2076 2076 [acl.deny.branches]
2077 2077 default = !astro
2078 2078 """
2079 2079 pushing to ../b
2080 2080 query 1; heads
2081 2081 searching for changes
2082 2082 all remote heads known locally
2083 2083 listing keys for "bookmarks"
2084 2084 4 changesets found
2085 2085 list of changesets:
2086 2086 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2087 2087 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2088 2088 911600dab2ae7a9baff75958b84fe606851ce955
2089 2089 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2090 adding changesets
2091 2090 bundling: 1/4 changesets (25.00%)
2092 2091 bundling: 2/4 changesets (50.00%)
2093 2092 bundling: 3/4 changesets (75.00%)
2094 2093 bundling: 4/4 changesets (100.00%)
2095 2094 bundling: 1/4 manifests (25.00%)
2096 2095 bundling: 2/4 manifests (50.00%)
2097 2096 bundling: 3/4 manifests (75.00%)
2098 2097 bundling: 4/4 manifests (100.00%)
2099 2098 bundling: abc.txt 1/4 files (25.00%)
2100 2099 bundling: foo/Bar/file.txt 2/4 files (50.00%)
2101 2100 bundling: foo/file.txt 3/4 files (75.00%)
2102 2101 bundling: quux/file.py 4/4 files (100.00%)
2102 adding changesets
2103 2103 changesets: 1 chunks
2104 2104 add changeset ef1ea85a6374
2105 2105 changesets: 2 chunks
2106 2106 add changeset f9cafe1212c8
2107 2107 changesets: 3 chunks
2108 2108 add changeset 911600dab2ae
2109 2109 changesets: 4 chunks
2110 2110 add changeset e8fc755d4d82
2111 2111 adding manifests
2112 2112 manifests: 1/4 chunks (25.00%)
2113 2113 manifests: 2/4 chunks (50.00%)
2114 2114 manifests: 3/4 chunks (75.00%)
2115 2115 manifests: 4/4 chunks (100.00%)
2116 2116 adding file changes
2117 2117 adding abc.txt revisions
2118 2118 files: 1/4 chunks (25.00%)
2119 2119 adding foo/Bar/file.txt revisions
2120 2120 files: 2/4 chunks (50.00%)
2121 2121 adding foo/file.txt revisions
2122 2122 files: 3/4 chunks (75.00%)
2123 2123 adding quux/file.py revisions
2124 2124 files: 4/4 chunks (100.00%)
2125 2125 added 4 changesets with 4 changes to 4 files (+1 heads)
2126 2126 calling hook pretxnchangegroup.acl: hgext.acl.hook
2127 2127 acl: checking access for user "george"
2128 2128 acl: acl.allow.branches not enabled
2129 2129 acl: acl.deny.branches enabled, 1 entries for user george
2130 2130 acl: acl.allow not enabled
2131 2131 acl: acl.deny not enabled
2132 2132 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2133 2133 transaction abort!
2134 2134 rollback completed
2135 2135 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2136 2136 no rollback information available
2137 2137 2:fb35475503ef
2138 2138
2139 2139
General Comments 0
You need to be logged in to leave comments. Login now