##// END OF EJS Templates
update the branch cache at the end of addchangegroup...
Alexis S. L. Carvalho -
r5988:ee317dbf default
parent child Browse files
Show More
@@ -1,1995 +1,1999 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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import repo, changegroup
11 11 import changelog, dirstate, filelog, manifest, context, weakref
12 12 import re, lock, transaction, tempfile, stat, errno, ui
13 13 import os, revlog, time, util, extensions, hook
14 14
15 15 class localrepository(repo.repository):
16 16 capabilities = util.set(('lookup', 'changegroupsubset'))
17 17 supported = ('revlogv1', 'store')
18 18
19 19 def __init__(self, parentui, path=None, create=0):
20 20 repo.repository.__init__(self)
21 21 self.root = os.path.realpath(path)
22 22 self.path = os.path.join(self.root, ".hg")
23 23 self.origroot = path
24 24 self.opener = util.opener(self.path)
25 25 self.wopener = util.opener(self.root)
26 26
27 27 if not os.path.isdir(self.path):
28 28 if create:
29 29 if not os.path.exists(path):
30 30 os.mkdir(path)
31 31 os.mkdir(self.path)
32 32 requirements = ["revlogv1"]
33 33 if parentui.configbool('format', 'usestore', True):
34 34 os.mkdir(os.path.join(self.path, "store"))
35 35 requirements.append("store")
36 36 # create an invalid changelog
37 37 self.opener("00changelog.i", "a").write(
38 38 '\0\0\0\2' # represents revlogv2
39 39 ' dummy changelog to prevent using the old repo layout'
40 40 )
41 41 reqfile = self.opener("requires", "w")
42 42 for r in requirements:
43 43 reqfile.write("%s\n" % r)
44 44 reqfile.close()
45 45 else:
46 46 raise repo.RepoError(_("repository %s not found") % path)
47 47 elif create:
48 48 raise repo.RepoError(_("repository %s already exists") % path)
49 49 else:
50 50 # find requirements
51 51 try:
52 52 requirements = self.opener("requires").read().splitlines()
53 53 except IOError, inst:
54 54 if inst.errno != errno.ENOENT:
55 55 raise
56 56 requirements = []
57 57 # check them
58 58 for r in requirements:
59 59 if r not in self.supported:
60 60 raise repo.RepoError(_("requirement '%s' not supported") % r)
61 61
62 62 # setup store
63 63 if "store" in requirements:
64 64 self.encodefn = util.encodefilename
65 65 self.decodefn = util.decodefilename
66 66 self.spath = os.path.join(self.path, "store")
67 67 else:
68 68 self.encodefn = lambda x: x
69 69 self.decodefn = lambda x: x
70 70 self.spath = self.path
71 71 self.sopener = util.encodedopener(util.opener(self.spath),
72 72 self.encodefn)
73 73
74 74 self.ui = ui.ui(parentui=parentui)
75 75 try:
76 76 self.ui.readconfig(self.join("hgrc"), self.root)
77 77 extensions.loadall(self.ui)
78 78 except IOError:
79 79 pass
80 80
81 81 self.tagscache = None
82 82 self.branchcache = None
83 83 self.nodetagscache = None
84 84 self.filterpats = {}
85 85 self._transref = self._lockref = self._wlockref = None
86 86
87 87 def __getattr__(self, name):
88 88 if name == 'changelog':
89 89 self.changelog = changelog.changelog(self.sopener)
90 90 self.sopener.defversion = self.changelog.version
91 91 return self.changelog
92 92 if name == 'manifest':
93 93 self.changelog
94 94 self.manifest = manifest.manifest(self.sopener)
95 95 return self.manifest
96 96 if name == 'dirstate':
97 97 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
98 98 return self.dirstate
99 99 else:
100 100 raise AttributeError, name
101 101
102 102 def url(self):
103 103 return 'file:' + self.root
104 104
105 105 def hook(self, name, throw=False, **args):
106 106 return hook.hook(self.ui, self, name, throw, **args)
107 107
108 108 tag_disallowed = ':\r\n'
109 109
110 110 def _tag(self, name, node, message, local, user, date, parent=None,
111 111 extra={}):
112 112 use_dirstate = parent is None
113 113
114 114 for c in self.tag_disallowed:
115 115 if c in name:
116 116 raise util.Abort(_('%r cannot be used in a tag name') % c)
117 117
118 118 self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
119 119
120 120 def writetag(fp, name, munge, prevtags):
121 121 fp.seek(0, 2)
122 122 if prevtags and prevtags[-1] != '\n':
123 123 fp.write('\n')
124 124 fp.write('%s %s\n' % (hex(node), munge and munge(name) or name))
125 125 fp.close()
126 126
127 127 prevtags = ''
128 128 if local:
129 129 try:
130 130 fp = self.opener('localtags', 'r+')
131 131 except IOError, err:
132 132 fp = self.opener('localtags', 'a')
133 133 else:
134 134 prevtags = fp.read()
135 135
136 136 # local tags are stored in the current charset
137 137 writetag(fp, name, None, prevtags)
138 138 self.hook('tag', node=hex(node), tag=name, local=local)
139 139 return
140 140
141 141 if use_dirstate:
142 142 try:
143 143 fp = self.wfile('.hgtags', 'rb+')
144 144 except IOError, err:
145 145 fp = self.wfile('.hgtags', 'ab')
146 146 else:
147 147 prevtags = fp.read()
148 148 else:
149 149 try:
150 150 prevtags = self.filectx('.hgtags', parent).data()
151 151 except revlog.LookupError:
152 152 pass
153 153 fp = self.wfile('.hgtags', 'wb')
154 154 if prevtags:
155 155 fp.write(prevtags)
156 156
157 157 # committed tags are stored in UTF-8
158 158 writetag(fp, name, util.fromlocal, prevtags)
159 159
160 160 if use_dirstate and '.hgtags' not in self.dirstate:
161 161 self.add(['.hgtags'])
162 162
163 163 tagnode = self.commit(['.hgtags'], message, user, date, p1=parent,
164 164 extra=extra)
165 165
166 166 self.hook('tag', node=hex(node), tag=name, local=local)
167 167
168 168 return tagnode
169 169
170 170 def tag(self, name, node, message, local, user, date):
171 171 '''tag a revision with a symbolic name.
172 172
173 173 if local is True, the tag is stored in a per-repository file.
174 174 otherwise, it is stored in the .hgtags file, and a new
175 175 changeset is committed with the change.
176 176
177 177 keyword arguments:
178 178
179 179 local: whether to store tag in non-version-controlled file
180 180 (default False)
181 181
182 182 message: commit message to use if committing
183 183
184 184 user: name of user to use if committing
185 185
186 186 date: date tuple to use if committing'''
187 187
188 188 for x in self.status()[:5]:
189 189 if '.hgtags' in x:
190 190 raise util.Abort(_('working copy of .hgtags is changed '
191 191 '(please commit .hgtags manually)'))
192 192
193 193
194 194 self._tag(name, node, message, local, user, date)
195 195
196 196 def tags(self):
197 197 '''return a mapping of tag to node'''
198 198 if self.tagscache:
199 199 return self.tagscache
200 200
201 201 globaltags = {}
202 202
203 203 def readtags(lines, fn):
204 204 filetags = {}
205 205 count = 0
206 206
207 207 def warn(msg):
208 208 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
209 209
210 210 for l in lines:
211 211 count += 1
212 212 if not l:
213 213 continue
214 214 s = l.split(" ", 1)
215 215 if len(s) != 2:
216 216 warn(_("cannot parse entry"))
217 217 continue
218 218 node, key = s
219 219 key = util.tolocal(key.strip()) # stored in UTF-8
220 220 try:
221 221 bin_n = bin(node)
222 222 except TypeError:
223 223 warn(_("node '%s' is not well formed") % node)
224 224 continue
225 225 if bin_n not in self.changelog.nodemap:
226 226 warn(_("tag '%s' refers to unknown node") % key)
227 227 continue
228 228
229 229 h = []
230 230 if key in filetags:
231 231 n, h = filetags[key]
232 232 h.append(n)
233 233 filetags[key] = (bin_n, h)
234 234
235 235 for k, nh in filetags.items():
236 236 if k not in globaltags:
237 237 globaltags[k] = nh
238 238 continue
239 239 # we prefer the global tag if:
240 240 # it supercedes us OR
241 241 # mutual supercedes and it has a higher rank
242 242 # otherwise we win because we're tip-most
243 243 an, ah = nh
244 244 bn, bh = globaltags[k]
245 245 if (bn != an and an in bh and
246 246 (bn not in ah or len(bh) > len(ah))):
247 247 an = bn
248 248 ah.extend([n for n in bh if n not in ah])
249 249 globaltags[k] = an, ah
250 250
251 251 # read the tags file from each head, ending with the tip
252 252 f = None
253 253 for rev, node, fnode in self._hgtagsnodes():
254 254 f = (f and f.filectx(fnode) or
255 255 self.filectx('.hgtags', fileid=fnode))
256 256 readtags(f.data().splitlines(), f)
257 257
258 258 try:
259 259 data = util.fromlocal(self.opener("localtags").read())
260 260 # localtags are stored in the local character set
261 261 # while the internal tag table is stored in UTF-8
262 262 readtags(data.splitlines(), "localtags")
263 263 except IOError:
264 264 pass
265 265
266 266 self.tagscache = {}
267 267 for k,nh in globaltags.items():
268 268 n = nh[0]
269 269 if n != nullid:
270 270 self.tagscache[k] = n
271 271 self.tagscache['tip'] = self.changelog.tip()
272 272
273 273 return self.tagscache
274 274
275 275 def _hgtagsnodes(self):
276 276 heads = self.heads()
277 277 heads.reverse()
278 278 last = {}
279 279 ret = []
280 280 for node in heads:
281 281 c = self.changectx(node)
282 282 rev = c.rev()
283 283 try:
284 284 fnode = c.filenode('.hgtags')
285 285 except revlog.LookupError:
286 286 continue
287 287 ret.append((rev, node, fnode))
288 288 if fnode in last:
289 289 ret[last[fnode]] = None
290 290 last[fnode] = len(ret) - 1
291 291 return [item for item in ret if item]
292 292
293 293 def tagslist(self):
294 294 '''return a list of tags ordered by revision'''
295 295 l = []
296 296 for t, n in self.tags().items():
297 297 try:
298 298 r = self.changelog.rev(n)
299 299 except:
300 300 r = -2 # sort to the beginning of the list if unknown
301 301 l.append((r, t, n))
302 302 l.sort()
303 303 return [(t, n) for r, t, n in l]
304 304
305 305 def nodetags(self, node):
306 306 '''return the tags associated with a node'''
307 307 if not self.nodetagscache:
308 308 self.nodetagscache = {}
309 309 for t, n in self.tags().items():
310 310 self.nodetagscache.setdefault(n, []).append(t)
311 311 return self.nodetagscache.get(node, [])
312 312
313 313 def _branchtags(self):
314 314 partial, last, lrev = self._readbranchcache()
315 315
316 316 tiprev = self.changelog.count() - 1
317 317 if lrev != tiprev:
318 318 self._updatebranchcache(partial, lrev+1, tiprev+1)
319 319 self._writebranchcache(partial, self.changelog.tip(), tiprev)
320 320
321 321 return partial
322 322
323 323 def branchtags(self):
324 324 if self.branchcache is not None:
325 325 return self.branchcache
326 326
327 327 self.branchcache = {} # avoid recursion in changectx
328 328 partial = self._branchtags()
329 329
330 330 # the branch cache is stored on disk as UTF-8, but in the local
331 331 # charset internally
332 332 for k, v in partial.items():
333 333 self.branchcache[util.tolocal(k)] = v
334 334 return self.branchcache
335 335
336 336 def _readbranchcache(self):
337 337 partial = {}
338 338 try:
339 339 f = self.opener("branch.cache")
340 340 lines = f.read().split('\n')
341 341 f.close()
342 342 except (IOError, OSError):
343 343 return {}, nullid, nullrev
344 344
345 345 try:
346 346 last, lrev = lines.pop(0).split(" ", 1)
347 347 last, lrev = bin(last), int(lrev)
348 348 if not (lrev < self.changelog.count() and
349 349 self.changelog.node(lrev) == last): # sanity check
350 350 # invalidate the cache
351 351 raise ValueError('Invalid branch cache: unknown tip')
352 352 for l in lines:
353 353 if not l: continue
354 354 node, label = l.split(" ", 1)
355 355 partial[label.strip()] = bin(node)
356 356 except (KeyboardInterrupt, util.SignalInterrupt):
357 357 raise
358 358 except Exception, inst:
359 359 if self.ui.debugflag:
360 360 self.ui.warn(str(inst), '\n')
361 361 partial, last, lrev = {}, nullid, nullrev
362 362 return partial, last, lrev
363 363
364 364 def _writebranchcache(self, branches, tip, tiprev):
365 365 try:
366 366 f = self.opener("branch.cache", "w", atomictemp=True)
367 367 f.write("%s %s\n" % (hex(tip), tiprev))
368 368 for label, node in branches.iteritems():
369 369 f.write("%s %s\n" % (hex(node), label))
370 370 f.rename()
371 371 except (IOError, OSError):
372 372 pass
373 373
374 374 def _updatebranchcache(self, partial, start, end):
375 375 for r in xrange(start, end):
376 376 c = self.changectx(r)
377 377 b = c.branch()
378 378 partial[b] = c.node()
379 379
380 380 def lookup(self, key):
381 381 if key == '.':
382 382 key, second = self.dirstate.parents()
383 383 if key == nullid:
384 384 raise repo.RepoError(_("no revision checked out"))
385 385 if second != nullid:
386 386 self.ui.warn(_("warning: working directory has two parents, "
387 387 "tag '.' uses the first\n"))
388 388 elif key == 'null':
389 389 return nullid
390 390 n = self.changelog._match(key)
391 391 if n:
392 392 return n
393 393 if key in self.tags():
394 394 return self.tags()[key]
395 395 if key in self.branchtags():
396 396 return self.branchtags()[key]
397 397 n = self.changelog._partialmatch(key)
398 398 if n:
399 399 return n
400 400 try:
401 401 if len(key) == 20:
402 402 key = hex(key)
403 403 except:
404 404 pass
405 405 raise repo.RepoError(_("unknown revision '%s'") % key)
406 406
407 407 def dev(self):
408 408 return os.lstat(self.path).st_dev
409 409
410 410 def local(self):
411 411 return True
412 412
413 413 def join(self, f):
414 414 return os.path.join(self.path, f)
415 415
416 416 def sjoin(self, f):
417 417 f = self.encodefn(f)
418 418 return os.path.join(self.spath, f)
419 419
420 420 def wjoin(self, f):
421 421 return os.path.join(self.root, f)
422 422
423 423 def file(self, f):
424 424 if f[0] == '/':
425 425 f = f[1:]
426 426 return filelog.filelog(self.sopener, f)
427 427
428 428 def changectx(self, changeid=None):
429 429 return context.changectx(self, changeid)
430 430
431 431 def workingctx(self):
432 432 return context.workingctx(self)
433 433
434 434 def parents(self, changeid=None):
435 435 '''
436 436 get list of changectxs for parents of changeid or working directory
437 437 '''
438 438 if changeid is None:
439 439 pl = self.dirstate.parents()
440 440 else:
441 441 n = self.changelog.lookup(changeid)
442 442 pl = self.changelog.parents(n)
443 443 if pl[1] == nullid:
444 444 return [self.changectx(pl[0])]
445 445 return [self.changectx(pl[0]), self.changectx(pl[1])]
446 446
447 447 def filectx(self, path, changeid=None, fileid=None):
448 448 """changeid can be a changeset revision, node, or tag.
449 449 fileid can be a file revision or node."""
450 450 return context.filectx(self, path, changeid, fileid)
451 451
452 452 def getcwd(self):
453 453 return self.dirstate.getcwd()
454 454
455 455 def pathto(self, f, cwd=None):
456 456 return self.dirstate.pathto(f, cwd)
457 457
458 458 def wfile(self, f, mode='r'):
459 459 return self.wopener(f, mode)
460 460
461 461 def _link(self, f):
462 462 return os.path.islink(self.wjoin(f))
463 463
464 464 def _filter(self, filter, filename, data):
465 465 if filter not in self.filterpats:
466 466 l = []
467 467 for pat, cmd in self.ui.configitems(filter):
468 468 mf = util.matcher(self.root, "", [pat], [], [])[1]
469 469 l.append((mf, cmd))
470 470 self.filterpats[filter] = l
471 471
472 472 for mf, cmd in self.filterpats[filter]:
473 473 if mf(filename):
474 474 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
475 475 data = util.filter(data, cmd)
476 476 break
477 477
478 478 return data
479 479
480 480 def wread(self, filename):
481 481 if self._link(filename):
482 482 data = os.readlink(self.wjoin(filename))
483 483 else:
484 484 data = self.wopener(filename, 'r').read()
485 485 return self._filter("encode", filename, data)
486 486
487 487 def wwrite(self, filename, data, flags):
488 488 data = self._filter("decode", filename, data)
489 489 if "l" in flags:
490 490 self.wopener.symlink(data, filename)
491 491 else:
492 492 try:
493 493 if self._link(filename):
494 494 os.unlink(self.wjoin(filename))
495 495 except OSError:
496 496 pass
497 497 self.wopener(filename, 'w').write(data)
498 498 util.set_exec(self.wjoin(filename), "x" in flags)
499 499
500 500 def wwritedata(self, filename, data):
501 501 return self._filter("decode", filename, data)
502 502
503 503 def transaction(self):
504 504 if self._transref and self._transref():
505 505 return self._transref().nest()
506 506
507 507 # save dirstate for rollback
508 508 try:
509 509 ds = self.opener("dirstate").read()
510 510 except IOError:
511 511 ds = ""
512 512 self.opener("journal.dirstate", "w").write(ds)
513 513
514 514 renames = [(self.sjoin("journal"), self.sjoin("undo")),
515 515 (self.join("journal.dirstate"), self.join("undo.dirstate"))]
516 516 tr = transaction.transaction(self.ui.warn, self.sopener,
517 517 self.sjoin("journal"),
518 518 aftertrans(renames))
519 519 self._transref = weakref.ref(tr)
520 520 return tr
521 521
522 522 def recover(self):
523 523 l = self.lock()
524 524 try:
525 525 if os.path.exists(self.sjoin("journal")):
526 526 self.ui.status(_("rolling back interrupted transaction\n"))
527 527 transaction.rollback(self.sopener, self.sjoin("journal"))
528 528 self.invalidate()
529 529 return True
530 530 else:
531 531 self.ui.warn(_("no interrupted transaction available\n"))
532 532 return False
533 533 finally:
534 534 del l
535 535
536 536 def rollback(self):
537 537 wlock = lock = None
538 538 try:
539 539 wlock = self.wlock()
540 540 lock = self.lock()
541 541 if os.path.exists(self.sjoin("undo")):
542 542 self.ui.status(_("rolling back last transaction\n"))
543 543 transaction.rollback(self.sopener, self.sjoin("undo"))
544 544 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
545 545 self.invalidate()
546 546 self.dirstate.invalidate()
547 547 else:
548 548 self.ui.warn(_("no rollback information available\n"))
549 549 finally:
550 550 del lock, wlock
551 551
552 552 def invalidate(self):
553 553 for a in "changelog manifest".split():
554 554 if hasattr(self, a):
555 555 self.__delattr__(a)
556 556 self.tagscache = None
557 557 self.nodetagscache = None
558 558
559 559 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
560 560 try:
561 561 l = lock.lock(lockname, 0, releasefn, desc=desc)
562 562 except lock.LockHeld, inst:
563 563 if not wait:
564 564 raise
565 565 self.ui.warn(_("waiting for lock on %s held by %r\n") %
566 566 (desc, inst.locker))
567 567 # default to 600 seconds timeout
568 568 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
569 569 releasefn, desc=desc)
570 570 if acquirefn:
571 571 acquirefn()
572 572 return l
573 573
574 574 def lock(self, wait=True):
575 575 if self._lockref and self._lockref():
576 576 return self._lockref()
577 577
578 578 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
579 579 _('repository %s') % self.origroot)
580 580 self._lockref = weakref.ref(l)
581 581 return l
582 582
583 583 def wlock(self, wait=True):
584 584 if self._wlockref and self._wlockref():
585 585 return self._wlockref()
586 586
587 587 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
588 588 self.dirstate.invalidate, _('working directory of %s') %
589 589 self.origroot)
590 590 self._wlockref = weakref.ref(l)
591 591 return l
592 592
593 593 def filecommit(self, fn, manifest1, manifest2, linkrev, tr, changelist):
594 594 """
595 595 commit an individual file as part of a larger transaction
596 596 """
597 597
598 598 t = self.wread(fn)
599 599 fl = self.file(fn)
600 600 fp1 = manifest1.get(fn, nullid)
601 601 fp2 = manifest2.get(fn, nullid)
602 602
603 603 meta = {}
604 604 cp = self.dirstate.copied(fn)
605 605 if cp:
606 606 # Mark the new revision of this file as a copy of another
607 607 # file. This copy data will effectively act as a parent
608 608 # of this new revision. If this is a merge, the first
609 609 # parent will be the nullid (meaning "look up the copy data")
610 610 # and the second one will be the other parent. For example:
611 611 #
612 612 # 0 --- 1 --- 3 rev1 changes file foo
613 613 # \ / rev2 renames foo to bar and changes it
614 614 # \- 2 -/ rev3 should have bar with all changes and
615 615 # should record that bar descends from
616 616 # bar in rev2 and foo in rev1
617 617 #
618 618 # this allows this merge to succeed:
619 619 #
620 620 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
621 621 # \ / merging rev3 and rev4 should use bar@rev2
622 622 # \- 2 --- 4 as the merge base
623 623 #
624 624 meta["copy"] = cp
625 625 if not manifest2: # not a branch merge
626 626 meta["copyrev"] = hex(manifest1.get(cp, nullid))
627 627 fp2 = nullid
628 628 elif fp2 != nullid: # copied on remote side
629 629 meta["copyrev"] = hex(manifest1.get(cp, nullid))
630 630 elif fp1 != nullid: # copied on local side, reversed
631 631 meta["copyrev"] = hex(manifest2.get(cp))
632 632 fp2 = fp1
633 633 elif cp in manifest2: # directory rename on local side
634 634 meta["copyrev"] = hex(manifest2[cp])
635 635 else: # directory rename on remote side
636 636 meta["copyrev"] = hex(manifest1.get(cp, nullid))
637 637 self.ui.debug(_(" %s: copy %s:%s\n") %
638 638 (fn, cp, meta["copyrev"]))
639 639 fp1 = nullid
640 640 elif fp2 != nullid:
641 641 # is one parent an ancestor of the other?
642 642 fpa = fl.ancestor(fp1, fp2)
643 643 if fpa == fp1:
644 644 fp1, fp2 = fp2, nullid
645 645 elif fpa == fp2:
646 646 fp2 = nullid
647 647
648 648 # is the file unmodified from the parent? report existing entry
649 649 if fp2 == nullid and not fl.cmp(fp1, t) and not meta:
650 650 return fp1
651 651
652 652 changelist.append(fn)
653 653 return fl.add(t, meta, tr, linkrev, fp1, fp2)
654 654
655 655 def rawcommit(self, files, text, user, date, p1=None, p2=None, extra={}):
656 656 if p1 is None:
657 657 p1, p2 = self.dirstate.parents()
658 658 return self.commit(files=files, text=text, user=user, date=date,
659 659 p1=p1, p2=p2, extra=extra, empty_ok=True)
660 660
661 661 def commit(self, files=None, text="", user=None, date=None,
662 662 match=util.always, force=False, force_editor=False,
663 663 p1=None, p2=None, extra={}, empty_ok=False):
664 664 wlock = lock = tr = None
665 665 if files:
666 666 files = util.unique(files)
667 667 try:
668 668 commit = []
669 669 remove = []
670 670 changed = []
671 671 use_dirstate = (p1 is None) # not rawcommit
672 672 extra = extra.copy()
673 673
674 674 if use_dirstate:
675 675 if files:
676 676 for f in files:
677 677 s = self.dirstate[f]
678 678 if s in 'nma':
679 679 commit.append(f)
680 680 elif s == 'r':
681 681 remove.append(f)
682 682 else:
683 683 self.ui.warn(_("%s not tracked!\n") % f)
684 684 else:
685 685 changes = self.status(match=match)[:5]
686 686 modified, added, removed, deleted, unknown = changes
687 687 commit = modified + added
688 688 remove = removed
689 689 else:
690 690 commit = files
691 691
692 692 if use_dirstate:
693 693 p1, p2 = self.dirstate.parents()
694 694 update_dirstate = True
695 695 else:
696 696 p1, p2 = p1, p2 or nullid
697 697 update_dirstate = (self.dirstate.parents()[0] == p1)
698 698
699 699 c1 = self.changelog.read(p1)
700 700 c2 = self.changelog.read(p2)
701 701 m1 = self.manifest.read(c1[0]).copy()
702 702 m2 = self.manifest.read(c2[0])
703 703
704 704 if use_dirstate:
705 705 branchname = self.workingctx().branch()
706 706 try:
707 707 branchname = branchname.decode('UTF-8').encode('UTF-8')
708 708 except UnicodeDecodeError:
709 709 raise util.Abort(_('branch name not in UTF-8!'))
710 710 else:
711 711 branchname = ""
712 712
713 713 if use_dirstate:
714 714 oldname = c1[5].get("branch") # stored in UTF-8
715 715 if (not commit and not remove and not force and p2 == nullid
716 716 and branchname == oldname):
717 717 self.ui.status(_("nothing changed\n"))
718 718 return None
719 719
720 720 xp1 = hex(p1)
721 721 if p2 == nullid: xp2 = ''
722 722 else: xp2 = hex(p2)
723 723
724 724 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
725 725
726 726 wlock = self.wlock()
727 727 lock = self.lock()
728 728 tr = self.transaction()
729 729 trp = weakref.proxy(tr)
730 730
731 731 # check in files
732 732 new = {}
733 733 linkrev = self.changelog.count()
734 734 commit.sort()
735 735 is_exec = util.execfunc(self.root, m1.execf)
736 736 is_link = util.linkfunc(self.root, m1.linkf)
737 737 for f in commit:
738 738 self.ui.note(f + "\n")
739 739 try:
740 740 new[f] = self.filecommit(f, m1, m2, linkrev, trp, changed)
741 741 new_exec = is_exec(f)
742 742 new_link = is_link(f)
743 743 if ((not changed or changed[-1] != f) and
744 744 m2.get(f) != new[f]):
745 745 # mention the file in the changelog if some
746 746 # flag changed, even if there was no content
747 747 # change.
748 748 old_exec = m1.execf(f)
749 749 old_link = m1.linkf(f)
750 750 if old_exec != new_exec or old_link != new_link:
751 751 changed.append(f)
752 752 m1.set(f, new_exec, new_link)
753 753 except (OSError, IOError):
754 754 if use_dirstate:
755 755 self.ui.warn(_("trouble committing %s!\n") % f)
756 756 raise
757 757 else:
758 758 remove.append(f)
759 759
760 760 # update manifest
761 761 m1.update(new)
762 762 remove.sort()
763 763 removed = []
764 764
765 765 for f in remove:
766 766 if f in m1:
767 767 del m1[f]
768 768 removed.append(f)
769 769 elif f in m2:
770 770 removed.append(f)
771 771 mn = self.manifest.add(m1, trp, linkrev, c1[0], c2[0],
772 772 (new, removed))
773 773
774 774 # add changeset
775 775 new = new.keys()
776 776 new.sort()
777 777
778 778 user = user or self.ui.username()
779 779 if (not empty_ok and not text) or force_editor:
780 780 edittext = []
781 781 if text:
782 782 edittext.append(text)
783 783 edittext.append("")
784 784 edittext.append("HG: user: %s" % user)
785 785 if p2 != nullid:
786 786 edittext.append("HG: branch merge")
787 787 if branchname:
788 788 edittext.append("HG: branch %s" % util.tolocal(branchname))
789 789 edittext.extend(["HG: changed %s" % f for f in changed])
790 790 edittext.extend(["HG: removed %s" % f for f in removed])
791 791 if not changed and not remove:
792 792 edittext.append("HG: no files changed")
793 793 edittext.append("")
794 794 # run editor in the repository root
795 795 olddir = os.getcwd()
796 796 os.chdir(self.root)
797 797 text = self.ui.edit("\n".join(edittext), user)
798 798 os.chdir(olddir)
799 799
800 800 if branchname:
801 801 extra["branch"] = branchname
802 802
803 803 if use_dirstate:
804 804 lines = [line.rstrip() for line in text.rstrip().splitlines()]
805 805 while lines and not lines[0]:
806 806 del lines[0]
807 807 if not lines:
808 808 return None
809 809 text = '\n'.join(lines)
810 810
811 811 n = self.changelog.add(mn, changed + removed, text, trp, p1, p2,
812 812 user, date, extra)
813 813 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
814 814 parent2=xp2)
815 815 tr.close()
816 816
817 817 if self.branchcache and "branch" in extra:
818 818 self.branchcache[util.tolocal(extra["branch"])] = n
819 819
820 820 if use_dirstate or update_dirstate:
821 821 self.dirstate.setparents(n)
822 822 if use_dirstate:
823 823 for f in new:
824 824 self.dirstate.normal(f)
825 825 for f in removed:
826 826 self.dirstate.forget(f)
827 827
828 828 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
829 829 return n
830 830 finally:
831 831 del tr, lock, wlock
832 832
833 833 def walk(self, node=None, files=[], match=util.always, badmatch=None):
834 834 '''
835 835 walk recursively through the directory tree or a given
836 836 changeset, finding all files matched by the match
837 837 function
838 838
839 839 results are yielded in a tuple (src, filename), where src
840 840 is one of:
841 841 'f' the file was found in the directory tree
842 842 'm' the file was only in the dirstate and not in the tree
843 843 'b' file was not found and matched badmatch
844 844 '''
845 845
846 846 if node:
847 847 fdict = dict.fromkeys(files)
848 848 # for dirstate.walk, files=['.'] means "walk the whole tree".
849 849 # follow that here, too
850 850 fdict.pop('.', None)
851 851 mdict = self.manifest.read(self.changelog.read(node)[0])
852 852 mfiles = mdict.keys()
853 853 mfiles.sort()
854 854 for fn in mfiles:
855 855 for ffn in fdict:
856 856 # match if the file is the exact name or a directory
857 857 if ffn == fn or fn.startswith("%s/" % ffn):
858 858 del fdict[ffn]
859 859 break
860 860 if match(fn):
861 861 yield 'm', fn
862 862 ffiles = fdict.keys()
863 863 ffiles.sort()
864 864 for fn in ffiles:
865 865 if badmatch and badmatch(fn):
866 866 if match(fn):
867 867 yield 'b', fn
868 868 else:
869 869 self.ui.warn(_('%s: No such file in rev %s\n')
870 870 % (self.pathto(fn), short(node)))
871 871 else:
872 872 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
873 873 yield src, fn
874 874
875 875 def status(self, node1=None, node2=None, files=[], match=util.always,
876 876 list_ignored=False, list_clean=False):
877 877 """return status of files between two nodes or node and working directory
878 878
879 879 If node1 is None, use the first dirstate parent instead.
880 880 If node2 is None, compare node1 with working directory.
881 881 """
882 882
883 883 def fcmp(fn, getnode):
884 884 t1 = self.wread(fn)
885 885 return self.file(fn).cmp(getnode(fn), t1)
886 886
887 887 def mfmatches(node):
888 888 change = self.changelog.read(node)
889 889 mf = self.manifest.read(change[0]).copy()
890 890 for fn in mf.keys():
891 891 if not match(fn):
892 892 del mf[fn]
893 893 return mf
894 894
895 895 modified, added, removed, deleted, unknown = [], [], [], [], []
896 896 ignored, clean = [], []
897 897
898 898 compareworking = False
899 899 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
900 900 compareworking = True
901 901
902 902 if not compareworking:
903 903 # read the manifest from node1 before the manifest from node2,
904 904 # so that we'll hit the manifest cache if we're going through
905 905 # all the revisions in parent->child order.
906 906 mf1 = mfmatches(node1)
907 907
908 908 # are we comparing the working directory?
909 909 if not node2:
910 910 (lookup, modified, added, removed, deleted, unknown,
911 911 ignored, clean) = self.dirstate.status(files, match,
912 912 list_ignored, list_clean)
913 913
914 914 # are we comparing working dir against its parent?
915 915 if compareworking:
916 916 if lookup:
917 917 fixup = []
918 918 # do a full compare of any files that might have changed
919 919 ctx = self.changectx()
920 920 for f in lookup:
921 921 if f not in ctx or ctx[f].cmp(self.wread(f)):
922 922 modified.append(f)
923 923 else:
924 924 fixup.append(f)
925 925 if list_clean:
926 926 clean.append(f)
927 927
928 928 # update dirstate for files that are actually clean
929 929 if fixup:
930 930 wlock = None
931 931 try:
932 932 try:
933 933 wlock = self.wlock(False)
934 934 except lock.LockException:
935 935 pass
936 936 if wlock:
937 937 for f in fixup:
938 938 self.dirstate.normal(f)
939 939 finally:
940 940 del wlock
941 941 else:
942 942 # we are comparing working dir against non-parent
943 943 # generate a pseudo-manifest for the working dir
944 944 # XXX: create it in dirstate.py ?
945 945 mf2 = mfmatches(self.dirstate.parents()[0])
946 946 is_exec = util.execfunc(self.root, mf2.execf)
947 947 is_link = util.linkfunc(self.root, mf2.linkf)
948 948 for f in lookup + modified + added:
949 949 mf2[f] = ""
950 950 mf2.set(f, is_exec(f), is_link(f))
951 951 for f in removed:
952 952 if f in mf2:
953 953 del mf2[f]
954 954
955 955 else:
956 956 # we are comparing two revisions
957 957 mf2 = mfmatches(node2)
958 958
959 959 if not compareworking:
960 960 # flush lists from dirstate before comparing manifests
961 961 modified, added, clean = [], [], []
962 962
963 963 # make sure to sort the files so we talk to the disk in a
964 964 # reasonable order
965 965 mf2keys = mf2.keys()
966 966 mf2keys.sort()
967 967 getnode = lambda fn: mf1.get(fn, nullid)
968 968 for fn in mf2keys:
969 969 if mf1.has_key(fn):
970 970 if (mf1.flags(fn) != mf2.flags(fn) or
971 971 (mf1[fn] != mf2[fn] and
972 972 (mf2[fn] != "" or fcmp(fn, getnode)))):
973 973 modified.append(fn)
974 974 elif list_clean:
975 975 clean.append(fn)
976 976 del mf1[fn]
977 977 else:
978 978 added.append(fn)
979 979
980 980 removed = mf1.keys()
981 981
982 982 # sort and return results:
983 983 for l in modified, added, removed, deleted, unknown, ignored, clean:
984 984 l.sort()
985 985 return (modified, added, removed, deleted, unknown, ignored, clean)
986 986
987 987 def add(self, list):
988 988 wlock = self.wlock()
989 989 try:
990 990 for f in list:
991 991 p = self.wjoin(f)
992 992 try:
993 993 st = os.lstat(p)
994 994 except:
995 995 self.ui.warn(_("%s does not exist!\n") % f)
996 996 continue
997 997 if st.st_size > 10000000:
998 998 self.ui.warn(_("%s: files over 10MB may cause memory and"
999 999 " performance problems\n"
1000 1000 "(use 'hg revert %s' to unadd the file)\n")
1001 1001 % (f, f))
1002 1002 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1003 1003 self.ui.warn(_("%s not added: only files and symlinks "
1004 1004 "supported currently\n") % f)
1005 1005 elif self.dirstate[f] in 'amn':
1006 1006 self.ui.warn(_("%s already tracked!\n") % f)
1007 1007 elif self.dirstate[f] == 'r':
1008 1008 self.dirstate.normallookup(f)
1009 1009 else:
1010 1010 self.dirstate.add(f)
1011 1011 finally:
1012 1012 del wlock
1013 1013
1014 1014 def forget(self, list):
1015 1015 wlock = self.wlock()
1016 1016 try:
1017 1017 for f in list:
1018 1018 if self.dirstate[f] != 'a':
1019 1019 self.ui.warn(_("%s not added!\n") % f)
1020 1020 else:
1021 1021 self.dirstate.forget(f)
1022 1022 finally:
1023 1023 del wlock
1024 1024
1025 1025 def remove(self, list, unlink=False):
1026 1026 wlock = None
1027 1027 try:
1028 1028 if unlink:
1029 1029 for f in list:
1030 1030 try:
1031 1031 util.unlink(self.wjoin(f))
1032 1032 except OSError, inst:
1033 1033 if inst.errno != errno.ENOENT:
1034 1034 raise
1035 1035 wlock = self.wlock()
1036 1036 for f in list:
1037 1037 if unlink and os.path.exists(self.wjoin(f)):
1038 1038 self.ui.warn(_("%s still exists!\n") % f)
1039 1039 elif self.dirstate[f] == 'a':
1040 1040 self.dirstate.forget(f)
1041 1041 elif f not in self.dirstate:
1042 1042 self.ui.warn(_("%s not tracked!\n") % f)
1043 1043 else:
1044 1044 self.dirstate.remove(f)
1045 1045 finally:
1046 1046 del wlock
1047 1047
1048 1048 def undelete(self, list):
1049 1049 wlock = None
1050 1050 try:
1051 1051 manifests = [self.manifest.read(self.changelog.read(p)[0])
1052 1052 for p in self.dirstate.parents() if p != nullid]
1053 1053 wlock = self.wlock()
1054 1054 for f in list:
1055 1055 if self.dirstate[f] != 'r':
1056 1056 self.ui.warn("%s not removed!\n" % f)
1057 1057 else:
1058 1058 m = f in manifests[0] and manifests[0] or manifests[1]
1059 1059 t = self.file(f).read(m[f])
1060 1060 self.wwrite(f, t, m.flags(f))
1061 1061 self.dirstate.normal(f)
1062 1062 finally:
1063 1063 del wlock
1064 1064
1065 1065 def copy(self, source, dest):
1066 1066 wlock = None
1067 1067 try:
1068 1068 p = self.wjoin(dest)
1069 1069 if not (os.path.exists(p) or os.path.islink(p)):
1070 1070 self.ui.warn(_("%s does not exist!\n") % dest)
1071 1071 elif not (os.path.isfile(p) or os.path.islink(p)):
1072 1072 self.ui.warn(_("copy failed: %s is not a file or a "
1073 1073 "symbolic link\n") % dest)
1074 1074 else:
1075 1075 wlock = self.wlock()
1076 1076 if dest not in self.dirstate:
1077 1077 self.dirstate.add(dest)
1078 1078 self.dirstate.copy(source, dest)
1079 1079 finally:
1080 1080 del wlock
1081 1081
1082 1082 def heads(self, start=None):
1083 1083 heads = self.changelog.heads(start)
1084 1084 # sort the output in rev descending order
1085 1085 heads = [(-self.changelog.rev(h), h) for h in heads]
1086 1086 heads.sort()
1087 1087 return [n for (r, n) in heads]
1088 1088
1089 1089 def branchheads(self, branch, start=None):
1090 1090 branches = self.branchtags()
1091 1091 if branch not in branches:
1092 1092 return []
1093 1093 # The basic algorithm is this:
1094 1094 #
1095 1095 # Start from the branch tip since there are no later revisions that can
1096 1096 # possibly be in this branch, and the tip is a guaranteed head.
1097 1097 #
1098 1098 # Remember the tip's parents as the first ancestors, since these by
1099 1099 # definition are not heads.
1100 1100 #
1101 1101 # Step backwards from the brach tip through all the revisions. We are
1102 1102 # guaranteed by the rules of Mercurial that we will now be visiting the
1103 1103 # nodes in reverse topological order (children before parents).
1104 1104 #
1105 1105 # If a revision is one of the ancestors of a head then we can toss it
1106 1106 # out of the ancestors set (we've already found it and won't be
1107 1107 # visiting it again) and put its parents in the ancestors set.
1108 1108 #
1109 1109 # Otherwise, if a revision is in the branch it's another head, since it
1110 1110 # wasn't in the ancestor list of an existing head. So add it to the
1111 1111 # head list, and add its parents to the ancestor list.
1112 1112 #
1113 1113 # If it is not in the branch ignore it.
1114 1114 #
1115 1115 # Once we have a list of heads, use nodesbetween to filter out all the
1116 1116 # heads that cannot be reached from startrev. There may be a more
1117 1117 # efficient way to do this as part of the previous algorithm.
1118 1118
1119 1119 set = util.set
1120 1120 heads = [self.changelog.rev(branches[branch])]
1121 1121 # Don't care if ancestors contains nullrev or not.
1122 1122 ancestors = set(self.changelog.parentrevs(heads[0]))
1123 1123 for rev in xrange(heads[0] - 1, nullrev, -1):
1124 1124 if rev in ancestors:
1125 1125 ancestors.update(self.changelog.parentrevs(rev))
1126 1126 ancestors.remove(rev)
1127 1127 elif self.changectx(rev).branch() == branch:
1128 1128 heads.append(rev)
1129 1129 ancestors.update(self.changelog.parentrevs(rev))
1130 1130 heads = [self.changelog.node(rev) for rev in heads]
1131 1131 if start is not None:
1132 1132 heads = self.changelog.nodesbetween([start], heads)[2]
1133 1133 return heads
1134 1134
1135 1135 def branches(self, nodes):
1136 1136 if not nodes:
1137 1137 nodes = [self.changelog.tip()]
1138 1138 b = []
1139 1139 for n in nodes:
1140 1140 t = n
1141 1141 while 1:
1142 1142 p = self.changelog.parents(n)
1143 1143 if p[1] != nullid or p[0] == nullid:
1144 1144 b.append((t, n, p[0], p[1]))
1145 1145 break
1146 1146 n = p[0]
1147 1147 return b
1148 1148
1149 1149 def between(self, pairs):
1150 1150 r = []
1151 1151
1152 1152 for top, bottom in pairs:
1153 1153 n, l, i = top, [], 0
1154 1154 f = 1
1155 1155
1156 1156 while n != bottom:
1157 1157 p = self.changelog.parents(n)[0]
1158 1158 if i == f:
1159 1159 l.append(n)
1160 1160 f = f * 2
1161 1161 n = p
1162 1162 i += 1
1163 1163
1164 1164 r.append(l)
1165 1165
1166 1166 return r
1167 1167
1168 1168 def findincoming(self, remote, base=None, heads=None, force=False):
1169 1169 """Return list of roots of the subsets of missing nodes from remote
1170 1170
1171 1171 If base dict is specified, assume that these nodes and their parents
1172 1172 exist on the remote side and that no child of a node of base exists
1173 1173 in both remote and self.
1174 1174 Furthermore base will be updated to include the nodes that exists
1175 1175 in self and remote but no children exists in self and remote.
1176 1176 If a list of heads is specified, return only nodes which are heads
1177 1177 or ancestors of these heads.
1178 1178
1179 1179 All the ancestors of base are in self and in remote.
1180 1180 All the descendants of the list returned are missing in self.
1181 1181 (and so we know that the rest of the nodes are missing in remote, see
1182 1182 outgoing)
1183 1183 """
1184 1184 m = self.changelog.nodemap
1185 1185 search = []
1186 1186 fetch = {}
1187 1187 seen = {}
1188 1188 seenbranch = {}
1189 1189 if base == None:
1190 1190 base = {}
1191 1191
1192 1192 if not heads:
1193 1193 heads = remote.heads()
1194 1194
1195 1195 if self.changelog.tip() == nullid:
1196 1196 base[nullid] = 1
1197 1197 if heads != [nullid]:
1198 1198 return [nullid]
1199 1199 return []
1200 1200
1201 1201 # assume we're closer to the tip than the root
1202 1202 # and start by examining the heads
1203 1203 self.ui.status(_("searching for changes\n"))
1204 1204
1205 1205 unknown = []
1206 1206 for h in heads:
1207 1207 if h not in m:
1208 1208 unknown.append(h)
1209 1209 else:
1210 1210 base[h] = 1
1211 1211
1212 1212 if not unknown:
1213 1213 return []
1214 1214
1215 1215 req = dict.fromkeys(unknown)
1216 1216 reqcnt = 0
1217 1217
1218 1218 # search through remote branches
1219 1219 # a 'branch' here is a linear segment of history, with four parts:
1220 1220 # head, root, first parent, second parent
1221 1221 # (a branch always has two parents (or none) by definition)
1222 1222 unknown = remote.branches(unknown)
1223 1223 while unknown:
1224 1224 r = []
1225 1225 while unknown:
1226 1226 n = unknown.pop(0)
1227 1227 if n[0] in seen:
1228 1228 continue
1229 1229
1230 1230 self.ui.debug(_("examining %s:%s\n")
1231 1231 % (short(n[0]), short(n[1])))
1232 1232 if n[0] == nullid: # found the end of the branch
1233 1233 pass
1234 1234 elif n in seenbranch:
1235 1235 self.ui.debug(_("branch already found\n"))
1236 1236 continue
1237 1237 elif n[1] and n[1] in m: # do we know the base?
1238 1238 self.ui.debug(_("found incomplete branch %s:%s\n")
1239 1239 % (short(n[0]), short(n[1])))
1240 1240 search.append(n) # schedule branch range for scanning
1241 1241 seenbranch[n] = 1
1242 1242 else:
1243 1243 if n[1] not in seen and n[1] not in fetch:
1244 1244 if n[2] in m and n[3] in m:
1245 1245 self.ui.debug(_("found new changeset %s\n") %
1246 1246 short(n[1]))
1247 1247 fetch[n[1]] = 1 # earliest unknown
1248 1248 for p in n[2:4]:
1249 1249 if p in m:
1250 1250 base[p] = 1 # latest known
1251 1251
1252 1252 for p in n[2:4]:
1253 1253 if p not in req and p not in m:
1254 1254 r.append(p)
1255 1255 req[p] = 1
1256 1256 seen[n[0]] = 1
1257 1257
1258 1258 if r:
1259 1259 reqcnt += 1
1260 1260 self.ui.debug(_("request %d: %s\n") %
1261 1261 (reqcnt, " ".join(map(short, r))))
1262 1262 for p in xrange(0, len(r), 10):
1263 1263 for b in remote.branches(r[p:p+10]):
1264 1264 self.ui.debug(_("received %s:%s\n") %
1265 1265 (short(b[0]), short(b[1])))
1266 1266 unknown.append(b)
1267 1267
1268 1268 # do binary search on the branches we found
1269 1269 while search:
1270 1270 n = search.pop(0)
1271 1271 reqcnt += 1
1272 1272 l = remote.between([(n[0], n[1])])[0]
1273 1273 l.append(n[1])
1274 1274 p = n[0]
1275 1275 f = 1
1276 1276 for i in l:
1277 1277 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1278 1278 if i in m:
1279 1279 if f <= 2:
1280 1280 self.ui.debug(_("found new branch changeset %s\n") %
1281 1281 short(p))
1282 1282 fetch[p] = 1
1283 1283 base[i] = 1
1284 1284 else:
1285 1285 self.ui.debug(_("narrowed branch search to %s:%s\n")
1286 1286 % (short(p), short(i)))
1287 1287 search.append((p, i))
1288 1288 break
1289 1289 p, f = i, f * 2
1290 1290
1291 1291 # sanity check our fetch list
1292 1292 for f in fetch.keys():
1293 1293 if f in m:
1294 1294 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1295 1295
1296 1296 if base.keys() == [nullid]:
1297 1297 if force:
1298 1298 self.ui.warn(_("warning: repository is unrelated\n"))
1299 1299 else:
1300 1300 raise util.Abort(_("repository is unrelated"))
1301 1301
1302 1302 self.ui.debug(_("found new changesets starting at ") +
1303 1303 " ".join([short(f) for f in fetch]) + "\n")
1304 1304
1305 1305 self.ui.debug(_("%d total queries\n") % reqcnt)
1306 1306
1307 1307 return fetch.keys()
1308 1308
1309 1309 def findoutgoing(self, remote, base=None, heads=None, force=False):
1310 1310 """Return list of nodes that are roots of subsets not in remote
1311 1311
1312 1312 If base dict is specified, assume that these nodes and their parents
1313 1313 exist on the remote side.
1314 1314 If a list of heads is specified, return only nodes which are heads
1315 1315 or ancestors of these heads, and return a second element which
1316 1316 contains all remote heads which get new children.
1317 1317 """
1318 1318 if base == None:
1319 1319 base = {}
1320 1320 self.findincoming(remote, base, heads, force=force)
1321 1321
1322 1322 self.ui.debug(_("common changesets up to ")
1323 1323 + " ".join(map(short, base.keys())) + "\n")
1324 1324
1325 1325 remain = dict.fromkeys(self.changelog.nodemap)
1326 1326
1327 1327 # prune everything remote has from the tree
1328 1328 del remain[nullid]
1329 1329 remove = base.keys()
1330 1330 while remove:
1331 1331 n = remove.pop(0)
1332 1332 if n in remain:
1333 1333 del remain[n]
1334 1334 for p in self.changelog.parents(n):
1335 1335 remove.append(p)
1336 1336
1337 1337 # find every node whose parents have been pruned
1338 1338 subset = []
1339 1339 # find every remote head that will get new children
1340 1340 updated_heads = {}
1341 1341 for n in remain:
1342 1342 p1, p2 = self.changelog.parents(n)
1343 1343 if p1 not in remain and p2 not in remain:
1344 1344 subset.append(n)
1345 1345 if heads:
1346 1346 if p1 in heads:
1347 1347 updated_heads[p1] = True
1348 1348 if p2 in heads:
1349 1349 updated_heads[p2] = True
1350 1350
1351 1351 # this is the set of all roots we have to push
1352 1352 if heads:
1353 1353 return subset, updated_heads.keys()
1354 1354 else:
1355 1355 return subset
1356 1356
1357 1357 def pull(self, remote, heads=None, force=False):
1358 1358 lock = self.lock()
1359 1359 try:
1360 1360 fetch = self.findincoming(remote, heads=heads, force=force)
1361 1361 if fetch == [nullid]:
1362 1362 self.ui.status(_("requesting all changes\n"))
1363 1363
1364 1364 if not fetch:
1365 1365 self.ui.status(_("no changes found\n"))
1366 1366 return 0
1367 1367
1368 1368 if heads is None:
1369 1369 cg = remote.changegroup(fetch, 'pull')
1370 1370 else:
1371 1371 if 'changegroupsubset' not in remote.capabilities:
1372 1372 raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset."))
1373 1373 cg = remote.changegroupsubset(fetch, heads, 'pull')
1374 1374 return self.addchangegroup(cg, 'pull', remote.url())
1375 1375 finally:
1376 1376 del lock
1377 1377
1378 1378 def push(self, remote, force=False, revs=None):
1379 1379 # there are two ways to push to remote repo:
1380 1380 #
1381 1381 # addchangegroup assumes local user can lock remote
1382 1382 # repo (local filesystem, old ssh servers).
1383 1383 #
1384 1384 # unbundle assumes local user cannot lock remote repo (new ssh
1385 1385 # servers, http servers).
1386 1386
1387 1387 if remote.capable('unbundle'):
1388 1388 return self.push_unbundle(remote, force, revs)
1389 1389 return self.push_addchangegroup(remote, force, revs)
1390 1390
1391 1391 def prepush(self, remote, force, revs):
1392 1392 base = {}
1393 1393 remote_heads = remote.heads()
1394 1394 inc = self.findincoming(remote, base, remote_heads, force=force)
1395 1395
1396 1396 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1397 1397 if revs is not None:
1398 1398 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1399 1399 else:
1400 1400 bases, heads = update, self.changelog.heads()
1401 1401
1402 1402 if not bases:
1403 1403 self.ui.status(_("no changes found\n"))
1404 1404 return None, 1
1405 1405 elif not force:
1406 1406 # check if we're creating new remote heads
1407 1407 # to be a remote head after push, node must be either
1408 1408 # - unknown locally
1409 1409 # - a local outgoing head descended from update
1410 1410 # - a remote head that's known locally and not
1411 1411 # ancestral to an outgoing head
1412 1412
1413 1413 warn = 0
1414 1414
1415 1415 if remote_heads == [nullid]:
1416 1416 warn = 0
1417 1417 elif not revs and len(heads) > len(remote_heads):
1418 1418 warn = 1
1419 1419 else:
1420 1420 newheads = list(heads)
1421 1421 for r in remote_heads:
1422 1422 if r in self.changelog.nodemap:
1423 1423 desc = self.changelog.heads(r, heads)
1424 1424 l = [h for h in heads if h in desc]
1425 1425 if not l:
1426 1426 newheads.append(r)
1427 1427 else:
1428 1428 newheads.append(r)
1429 1429 if len(newheads) > len(remote_heads):
1430 1430 warn = 1
1431 1431
1432 1432 if warn:
1433 1433 self.ui.warn(_("abort: push creates new remote branches!\n"))
1434 1434 self.ui.status(_("(did you forget to merge?"
1435 1435 " use push -f to force)\n"))
1436 1436 return None, 1
1437 1437 elif inc:
1438 1438 self.ui.warn(_("note: unsynced remote changes!\n"))
1439 1439
1440 1440
1441 1441 if revs is None:
1442 1442 cg = self.changegroup(update, 'push')
1443 1443 else:
1444 1444 cg = self.changegroupsubset(update, revs, 'push')
1445 1445 return cg, remote_heads
1446 1446
1447 1447 def push_addchangegroup(self, remote, force, revs):
1448 1448 lock = remote.lock()
1449 1449 try:
1450 1450 ret = self.prepush(remote, force, revs)
1451 1451 if ret[0] is not None:
1452 1452 cg, remote_heads = ret
1453 1453 return remote.addchangegroup(cg, 'push', self.url())
1454 1454 return ret[1]
1455 1455 finally:
1456 1456 del lock
1457 1457
1458 1458 def push_unbundle(self, remote, force, revs):
1459 1459 # local repo finds heads on server, finds out what revs it
1460 1460 # must push. once revs transferred, if server finds it has
1461 1461 # different heads (someone else won commit/push race), server
1462 1462 # aborts.
1463 1463
1464 1464 ret = self.prepush(remote, force, revs)
1465 1465 if ret[0] is not None:
1466 1466 cg, remote_heads = ret
1467 1467 if force: remote_heads = ['force']
1468 1468 return remote.unbundle(cg, remote_heads, 'push')
1469 1469 return ret[1]
1470 1470
1471 1471 def changegroupinfo(self, nodes):
1472 1472 self.ui.note(_("%d changesets found\n") % len(nodes))
1473 1473 if self.ui.debugflag:
1474 1474 self.ui.debug(_("List of changesets:\n"))
1475 1475 for node in nodes:
1476 1476 self.ui.debug("%s\n" % hex(node))
1477 1477
1478 1478 def changegroupsubset(self, bases, heads, source):
1479 1479 """This function generates a changegroup consisting of all the nodes
1480 1480 that are descendents of any of the bases, and ancestors of any of
1481 1481 the heads.
1482 1482
1483 1483 It is fairly complex as determining which filenodes and which
1484 1484 manifest nodes need to be included for the changeset to be complete
1485 1485 is non-trivial.
1486 1486
1487 1487 Another wrinkle is doing the reverse, figuring out which changeset in
1488 1488 the changegroup a particular filenode or manifestnode belongs to."""
1489 1489
1490 1490 self.hook('preoutgoing', throw=True, source=source)
1491 1491
1492 1492 # Set up some initial variables
1493 1493 # Make it easy to refer to self.changelog
1494 1494 cl = self.changelog
1495 1495 # msng is short for missing - compute the list of changesets in this
1496 1496 # changegroup.
1497 1497 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1498 1498 self.changegroupinfo(msng_cl_lst)
1499 1499 # Some bases may turn out to be superfluous, and some heads may be
1500 1500 # too. nodesbetween will return the minimal set of bases and heads
1501 1501 # necessary to re-create the changegroup.
1502 1502
1503 1503 # Known heads are the list of heads that it is assumed the recipient
1504 1504 # of this changegroup will know about.
1505 1505 knownheads = {}
1506 1506 # We assume that all parents of bases are known heads.
1507 1507 for n in bases:
1508 1508 for p in cl.parents(n):
1509 1509 if p != nullid:
1510 1510 knownheads[p] = 1
1511 1511 knownheads = knownheads.keys()
1512 1512 if knownheads:
1513 1513 # Now that we know what heads are known, we can compute which
1514 1514 # changesets are known. The recipient must know about all
1515 1515 # changesets required to reach the known heads from the null
1516 1516 # changeset.
1517 1517 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1518 1518 junk = None
1519 1519 # Transform the list into an ersatz set.
1520 1520 has_cl_set = dict.fromkeys(has_cl_set)
1521 1521 else:
1522 1522 # If there were no known heads, the recipient cannot be assumed to
1523 1523 # know about any changesets.
1524 1524 has_cl_set = {}
1525 1525
1526 1526 # Make it easy to refer to self.manifest
1527 1527 mnfst = self.manifest
1528 1528 # We don't know which manifests are missing yet
1529 1529 msng_mnfst_set = {}
1530 1530 # Nor do we know which filenodes are missing.
1531 1531 msng_filenode_set = {}
1532 1532
1533 1533 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1534 1534 junk = None
1535 1535
1536 1536 # A changeset always belongs to itself, so the changenode lookup
1537 1537 # function for a changenode is identity.
1538 1538 def identity(x):
1539 1539 return x
1540 1540
1541 1541 # A function generating function. Sets up an environment for the
1542 1542 # inner function.
1543 1543 def cmp_by_rev_func(revlog):
1544 1544 # Compare two nodes by their revision number in the environment's
1545 1545 # revision history. Since the revision number both represents the
1546 1546 # most efficient order to read the nodes in, and represents a
1547 1547 # topological sorting of the nodes, this function is often useful.
1548 1548 def cmp_by_rev(a, b):
1549 1549 return cmp(revlog.rev(a), revlog.rev(b))
1550 1550 return cmp_by_rev
1551 1551
1552 1552 # If we determine that a particular file or manifest node must be a
1553 1553 # node that the recipient of the changegroup will already have, we can
1554 1554 # also assume the recipient will have all the parents. This function
1555 1555 # prunes them from the set of missing nodes.
1556 1556 def prune_parents(revlog, hasset, msngset):
1557 1557 haslst = hasset.keys()
1558 1558 haslst.sort(cmp_by_rev_func(revlog))
1559 1559 for node in haslst:
1560 1560 parentlst = [p for p in revlog.parents(node) if p != nullid]
1561 1561 while parentlst:
1562 1562 n = parentlst.pop()
1563 1563 if n not in hasset:
1564 1564 hasset[n] = 1
1565 1565 p = [p for p in revlog.parents(n) if p != nullid]
1566 1566 parentlst.extend(p)
1567 1567 for n in hasset:
1568 1568 msngset.pop(n, None)
1569 1569
1570 1570 # This is a function generating function used to set up an environment
1571 1571 # for the inner function to execute in.
1572 1572 def manifest_and_file_collector(changedfileset):
1573 1573 # This is an information gathering function that gathers
1574 1574 # information from each changeset node that goes out as part of
1575 1575 # the changegroup. The information gathered is a list of which
1576 1576 # manifest nodes are potentially required (the recipient may
1577 1577 # already have them) and total list of all files which were
1578 1578 # changed in any changeset in the changegroup.
1579 1579 #
1580 1580 # We also remember the first changenode we saw any manifest
1581 1581 # referenced by so we can later determine which changenode 'owns'
1582 1582 # the manifest.
1583 1583 def collect_manifests_and_files(clnode):
1584 1584 c = cl.read(clnode)
1585 1585 for f in c[3]:
1586 1586 # This is to make sure we only have one instance of each
1587 1587 # filename string for each filename.
1588 1588 changedfileset.setdefault(f, f)
1589 1589 msng_mnfst_set.setdefault(c[0], clnode)
1590 1590 return collect_manifests_and_files
1591 1591
1592 1592 # Figure out which manifest nodes (of the ones we think might be part
1593 1593 # of the changegroup) the recipient must know about and remove them
1594 1594 # from the changegroup.
1595 1595 def prune_manifests():
1596 1596 has_mnfst_set = {}
1597 1597 for n in msng_mnfst_set:
1598 1598 # If a 'missing' manifest thinks it belongs to a changenode
1599 1599 # the recipient is assumed to have, obviously the recipient
1600 1600 # must have that manifest.
1601 1601 linknode = cl.node(mnfst.linkrev(n))
1602 1602 if linknode in has_cl_set:
1603 1603 has_mnfst_set[n] = 1
1604 1604 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1605 1605
1606 1606 # Use the information collected in collect_manifests_and_files to say
1607 1607 # which changenode any manifestnode belongs to.
1608 1608 def lookup_manifest_link(mnfstnode):
1609 1609 return msng_mnfst_set[mnfstnode]
1610 1610
1611 1611 # A function generating function that sets up the initial environment
1612 1612 # the inner function.
1613 1613 def filenode_collector(changedfiles):
1614 1614 next_rev = [0]
1615 1615 # This gathers information from each manifestnode included in the
1616 1616 # changegroup about which filenodes the manifest node references
1617 1617 # so we can include those in the changegroup too.
1618 1618 #
1619 1619 # It also remembers which changenode each filenode belongs to. It
1620 1620 # does this by assuming the a filenode belongs to the changenode
1621 1621 # the first manifest that references it belongs to.
1622 1622 def collect_msng_filenodes(mnfstnode):
1623 1623 r = mnfst.rev(mnfstnode)
1624 1624 if r == next_rev[0]:
1625 1625 # If the last rev we looked at was the one just previous,
1626 1626 # we only need to see a diff.
1627 1627 deltamf = mnfst.readdelta(mnfstnode)
1628 1628 # For each line in the delta
1629 1629 for f, fnode in deltamf.items():
1630 1630 f = changedfiles.get(f, None)
1631 1631 # And if the file is in the list of files we care
1632 1632 # about.
1633 1633 if f is not None:
1634 1634 # Get the changenode this manifest belongs to
1635 1635 clnode = msng_mnfst_set[mnfstnode]
1636 1636 # Create the set of filenodes for the file if
1637 1637 # there isn't one already.
1638 1638 ndset = msng_filenode_set.setdefault(f, {})
1639 1639 # And set the filenode's changelog node to the
1640 1640 # manifest's if it hasn't been set already.
1641 1641 ndset.setdefault(fnode, clnode)
1642 1642 else:
1643 1643 # Otherwise we need a full manifest.
1644 1644 m = mnfst.read(mnfstnode)
1645 1645 # For every file in we care about.
1646 1646 for f in changedfiles:
1647 1647 fnode = m.get(f, None)
1648 1648 # If it's in the manifest
1649 1649 if fnode is not None:
1650 1650 # See comments above.
1651 1651 clnode = msng_mnfst_set[mnfstnode]
1652 1652 ndset = msng_filenode_set.setdefault(f, {})
1653 1653 ndset.setdefault(fnode, clnode)
1654 1654 # Remember the revision we hope to see next.
1655 1655 next_rev[0] = r + 1
1656 1656 return collect_msng_filenodes
1657 1657
1658 1658 # We have a list of filenodes we think we need for a file, lets remove
1659 1659 # all those we now the recipient must have.
1660 1660 def prune_filenodes(f, filerevlog):
1661 1661 msngset = msng_filenode_set[f]
1662 1662 hasset = {}
1663 1663 # If a 'missing' filenode thinks it belongs to a changenode we
1664 1664 # assume the recipient must have, then the recipient must have
1665 1665 # that filenode.
1666 1666 for n in msngset:
1667 1667 clnode = cl.node(filerevlog.linkrev(n))
1668 1668 if clnode in has_cl_set:
1669 1669 hasset[n] = 1
1670 1670 prune_parents(filerevlog, hasset, msngset)
1671 1671
1672 1672 # A function generator function that sets up the a context for the
1673 1673 # inner function.
1674 1674 def lookup_filenode_link_func(fname):
1675 1675 msngset = msng_filenode_set[fname]
1676 1676 # Lookup the changenode the filenode belongs to.
1677 1677 def lookup_filenode_link(fnode):
1678 1678 return msngset[fnode]
1679 1679 return lookup_filenode_link
1680 1680
1681 1681 # Now that we have all theses utility functions to help out and
1682 1682 # logically divide up the task, generate the group.
1683 1683 def gengroup():
1684 1684 # The set of changed files starts empty.
1685 1685 changedfiles = {}
1686 1686 # Create a changenode group generator that will call our functions
1687 1687 # back to lookup the owning changenode and collect information.
1688 1688 group = cl.group(msng_cl_lst, identity,
1689 1689 manifest_and_file_collector(changedfiles))
1690 1690 for chnk in group:
1691 1691 yield chnk
1692 1692
1693 1693 # The list of manifests has been collected by the generator
1694 1694 # calling our functions back.
1695 1695 prune_manifests()
1696 1696 msng_mnfst_lst = msng_mnfst_set.keys()
1697 1697 # Sort the manifestnodes by revision number.
1698 1698 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1699 1699 # Create a generator for the manifestnodes that calls our lookup
1700 1700 # and data collection functions back.
1701 1701 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1702 1702 filenode_collector(changedfiles))
1703 1703 for chnk in group:
1704 1704 yield chnk
1705 1705
1706 1706 # These are no longer needed, dereference and toss the memory for
1707 1707 # them.
1708 1708 msng_mnfst_lst = None
1709 1709 msng_mnfst_set.clear()
1710 1710
1711 1711 changedfiles = changedfiles.keys()
1712 1712 changedfiles.sort()
1713 1713 # Go through all our files in order sorted by name.
1714 1714 for fname in changedfiles:
1715 1715 filerevlog = self.file(fname)
1716 1716 # Toss out the filenodes that the recipient isn't really
1717 1717 # missing.
1718 1718 if msng_filenode_set.has_key(fname):
1719 1719 prune_filenodes(fname, filerevlog)
1720 1720 msng_filenode_lst = msng_filenode_set[fname].keys()
1721 1721 else:
1722 1722 msng_filenode_lst = []
1723 1723 # If any filenodes are left, generate the group for them,
1724 1724 # otherwise don't bother.
1725 1725 if len(msng_filenode_lst) > 0:
1726 1726 yield changegroup.chunkheader(len(fname))
1727 1727 yield fname
1728 1728 # Sort the filenodes by their revision #
1729 1729 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1730 1730 # Create a group generator and only pass in a changenode
1731 1731 # lookup function as we need to collect no information
1732 1732 # from filenodes.
1733 1733 group = filerevlog.group(msng_filenode_lst,
1734 1734 lookup_filenode_link_func(fname))
1735 1735 for chnk in group:
1736 1736 yield chnk
1737 1737 if msng_filenode_set.has_key(fname):
1738 1738 # Don't need this anymore, toss it to free memory.
1739 1739 del msng_filenode_set[fname]
1740 1740 # Signal that no more groups are left.
1741 1741 yield changegroup.closechunk()
1742 1742
1743 1743 if msng_cl_lst:
1744 1744 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1745 1745
1746 1746 return util.chunkbuffer(gengroup())
1747 1747
1748 1748 def changegroup(self, basenodes, source):
1749 1749 """Generate a changegroup of all nodes that we have that a recipient
1750 1750 doesn't.
1751 1751
1752 1752 This is much easier than the previous function as we can assume that
1753 1753 the recipient has any changenode we aren't sending them."""
1754 1754
1755 1755 self.hook('preoutgoing', throw=True, source=source)
1756 1756
1757 1757 cl = self.changelog
1758 1758 nodes = cl.nodesbetween(basenodes, None)[0]
1759 1759 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1760 1760 self.changegroupinfo(nodes)
1761 1761
1762 1762 def identity(x):
1763 1763 return x
1764 1764
1765 1765 def gennodelst(revlog):
1766 1766 for r in xrange(0, revlog.count()):
1767 1767 n = revlog.node(r)
1768 1768 if revlog.linkrev(n) in revset:
1769 1769 yield n
1770 1770
1771 1771 def changed_file_collector(changedfileset):
1772 1772 def collect_changed_files(clnode):
1773 1773 c = cl.read(clnode)
1774 1774 for fname in c[3]:
1775 1775 changedfileset[fname] = 1
1776 1776 return collect_changed_files
1777 1777
1778 1778 def lookuprevlink_func(revlog):
1779 1779 def lookuprevlink(n):
1780 1780 return cl.node(revlog.linkrev(n))
1781 1781 return lookuprevlink
1782 1782
1783 1783 def gengroup():
1784 1784 # construct a list of all changed files
1785 1785 changedfiles = {}
1786 1786
1787 1787 for chnk in cl.group(nodes, identity,
1788 1788 changed_file_collector(changedfiles)):
1789 1789 yield chnk
1790 1790 changedfiles = changedfiles.keys()
1791 1791 changedfiles.sort()
1792 1792
1793 1793 mnfst = self.manifest
1794 1794 nodeiter = gennodelst(mnfst)
1795 1795 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1796 1796 yield chnk
1797 1797
1798 1798 for fname in changedfiles:
1799 1799 filerevlog = self.file(fname)
1800 1800 nodeiter = gennodelst(filerevlog)
1801 1801 nodeiter = list(nodeiter)
1802 1802 if nodeiter:
1803 1803 yield changegroup.chunkheader(len(fname))
1804 1804 yield fname
1805 1805 lookup = lookuprevlink_func(filerevlog)
1806 1806 for chnk in filerevlog.group(nodeiter, lookup):
1807 1807 yield chnk
1808 1808
1809 1809 yield changegroup.closechunk()
1810 1810
1811 1811 if nodes:
1812 1812 self.hook('outgoing', node=hex(nodes[0]), source=source)
1813 1813
1814 1814 return util.chunkbuffer(gengroup())
1815 1815
1816 1816 def addchangegroup(self, source, srctype, url):
1817 1817 """add changegroup to repo.
1818 1818
1819 1819 return values:
1820 1820 - nothing changed or no source: 0
1821 1821 - more heads than before: 1+added heads (2..n)
1822 1822 - less heads than before: -1-removed heads (-2..-n)
1823 1823 - number of heads stays the same: 1
1824 1824 """
1825 1825 def csmap(x):
1826 1826 self.ui.debug(_("add changeset %s\n") % short(x))
1827 1827 return cl.count()
1828 1828
1829 1829 def revmap(x):
1830 1830 return cl.rev(x)
1831 1831
1832 1832 if not source:
1833 1833 return 0
1834 1834
1835 1835 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1836 1836
1837 1837 changesets = files = revisions = 0
1838 1838
1839 1839 # write changelog data to temp files so concurrent readers will not see
1840 1840 # inconsistent view
1841 1841 cl = self.changelog
1842 1842 cl.delayupdate()
1843 1843 oldheads = len(cl.heads())
1844 1844
1845 1845 tr = self.transaction()
1846 1846 try:
1847 1847 trp = weakref.proxy(tr)
1848 1848 # pull off the changeset group
1849 1849 self.ui.status(_("adding changesets\n"))
1850 1850 cor = cl.count() - 1
1851 1851 chunkiter = changegroup.chunkiter(source)
1852 1852 if cl.addgroup(chunkiter, csmap, trp, 1) is None:
1853 1853 raise util.Abort(_("received changelog group is empty"))
1854 1854 cnr = cl.count() - 1
1855 1855 changesets = cnr - cor
1856 1856
1857 1857 # pull off the manifest group
1858 1858 self.ui.status(_("adding manifests\n"))
1859 1859 chunkiter = changegroup.chunkiter(source)
1860 1860 # no need to check for empty manifest group here:
1861 1861 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1862 1862 # no new manifest will be created and the manifest group will
1863 1863 # be empty during the pull
1864 1864 self.manifest.addgroup(chunkiter, revmap, trp)
1865 1865
1866 1866 # process the files
1867 1867 self.ui.status(_("adding file changes\n"))
1868 1868 while 1:
1869 1869 f = changegroup.getchunk(source)
1870 1870 if not f:
1871 1871 break
1872 1872 self.ui.debug(_("adding %s revisions\n") % f)
1873 1873 fl = self.file(f)
1874 1874 o = fl.count()
1875 1875 chunkiter = changegroup.chunkiter(source)
1876 1876 if fl.addgroup(chunkiter, revmap, trp) is None:
1877 1877 raise util.Abort(_("received file revlog group is empty"))
1878 1878 revisions += fl.count() - o
1879 1879 files += 1
1880 1880
1881 1881 # make changelog see real files again
1882 1882 cl.finalize(trp)
1883 1883
1884 1884 newheads = len(self.changelog.heads())
1885 1885 heads = ""
1886 1886 if oldheads and newheads != oldheads:
1887 1887 heads = _(" (%+d heads)") % (newheads - oldheads)
1888 1888
1889 1889 self.ui.status(_("added %d changesets"
1890 1890 " with %d changes to %d files%s\n")
1891 1891 % (changesets, revisions, files, heads))
1892 1892
1893 1893 if changesets > 0:
1894 1894 self.hook('pretxnchangegroup', throw=True,
1895 1895 node=hex(self.changelog.node(cor+1)), source=srctype,
1896 1896 url=url)
1897 1897
1898 1898 tr.close()
1899 1899 finally:
1900 1900 del tr
1901 1901
1902 1902 if changesets > 0:
1903 # forcefully update the on-disk branch cache
1904 self.ui.debug(_("updating the branch cache\n"))
1905 self.branchcache = None
1906 self.branchtags()
1903 1907 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1904 1908 source=srctype, url=url)
1905 1909
1906 1910 for i in xrange(cor + 1, cnr + 1):
1907 1911 self.hook("incoming", node=hex(self.changelog.node(i)),
1908 1912 source=srctype, url=url)
1909 1913
1910 1914 # never return 0 here:
1911 1915 if newheads < oldheads:
1912 1916 return newheads - oldheads - 1
1913 1917 else:
1914 1918 return newheads - oldheads + 1
1915 1919
1916 1920
1917 1921 def stream_in(self, remote):
1918 1922 fp = remote.stream_out()
1919 1923 l = fp.readline()
1920 1924 try:
1921 1925 resp = int(l)
1922 1926 except ValueError:
1923 1927 raise util.UnexpectedOutput(
1924 1928 _('Unexpected response from remote server:'), l)
1925 1929 if resp == 1:
1926 1930 raise util.Abort(_('operation forbidden by server'))
1927 1931 elif resp == 2:
1928 1932 raise util.Abort(_('locking the remote repository failed'))
1929 1933 elif resp != 0:
1930 1934 raise util.Abort(_('the server sent an unknown error code'))
1931 1935 self.ui.status(_('streaming all changes\n'))
1932 1936 l = fp.readline()
1933 1937 try:
1934 1938 total_files, total_bytes = map(int, l.split(' ', 1))
1935 1939 except ValueError, TypeError:
1936 1940 raise util.UnexpectedOutput(
1937 1941 _('Unexpected response from remote server:'), l)
1938 1942 self.ui.status(_('%d files to transfer, %s of data\n') %
1939 1943 (total_files, util.bytecount(total_bytes)))
1940 1944 start = time.time()
1941 1945 for i in xrange(total_files):
1942 1946 # XXX doesn't support '\n' or '\r' in filenames
1943 1947 l = fp.readline()
1944 1948 try:
1945 1949 name, size = l.split('\0', 1)
1946 1950 size = int(size)
1947 1951 except ValueError, TypeError:
1948 1952 raise util.UnexpectedOutput(
1949 1953 _('Unexpected response from remote server:'), l)
1950 1954 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1951 1955 ofp = self.sopener(name, 'w')
1952 1956 for chunk in util.filechunkiter(fp, limit=size):
1953 1957 ofp.write(chunk)
1954 1958 ofp.close()
1955 1959 elapsed = time.time() - start
1956 1960 if elapsed <= 0:
1957 1961 elapsed = 0.001
1958 1962 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1959 1963 (util.bytecount(total_bytes), elapsed,
1960 1964 util.bytecount(total_bytes / elapsed)))
1961 1965 self.invalidate()
1962 1966 return len(self.heads()) + 1
1963 1967
1964 1968 def clone(self, remote, heads=[], stream=False):
1965 1969 '''clone remote repository.
1966 1970
1967 1971 keyword arguments:
1968 1972 heads: list of revs to clone (forces use of pull)
1969 1973 stream: use streaming clone if possible'''
1970 1974
1971 1975 # now, all clients that can request uncompressed clones can
1972 1976 # read repo formats supported by all servers that can serve
1973 1977 # them.
1974 1978
1975 1979 # if revlog format changes, client will have to check version
1976 1980 # and format flags on "stream" capability, and use
1977 1981 # uncompressed only if compatible.
1978 1982
1979 1983 if stream and not heads and remote.capable('stream'):
1980 1984 return self.stream_in(remote)
1981 1985 return self.pull(remote, heads)
1982 1986
1983 1987 # used to avoid circular references so destructors work
1984 1988 def aftertrans(files):
1985 1989 renamefiles = [tuple(t) for t in files]
1986 1990 def a():
1987 1991 for src, dest in renamefiles:
1988 1992 util.rename(src, dest)
1989 1993 return a
1990 1994
1991 1995 def instance(ui, path, create):
1992 1996 return localrepository(ui, util.drop_scheme('file', path), create)
1993 1997
1994 1998 def islocal(path):
1995 1999 return True
@@ -1,583 +1,588 b''
1 1 3:911600dab2ae
2 2 requesting all changes
3 3 adding changesets
4 4 adding manifests
5 5 adding file changes
6 6 added 1 changesets with 3 changes to 3 files
7 7 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 8
9 9 Extension disabled for lack of a hook
10 10 Pushing as user fred
11 11 hgrc = """
12 12 """
13 13 pushing to ../b
14 14 searching for changes
15 15 common changesets up to 6675d58eff77
16 16 3 changesets found
17 17 List of changesets:
18 18 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
19 19 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
20 20 911600dab2ae7a9baff75958b84fe606851ce955
21 21 adding changesets
22 22 add changeset ef1ea85a6374
23 23 add changeset f9cafe1212c8
24 24 add changeset 911600dab2ae
25 25 adding manifests
26 26 adding file changes
27 27 adding foo/Bar/file.txt revisions
28 28 adding foo/file.txt revisions
29 29 adding quux/file.py revisions
30 30 added 3 changesets with 3 changes to 3 files
31 updating the branch cache
31 32 rolling back last transaction
32 33 0:6675d58eff77
33 34
34 35 Extension disabled for lack of acl.sources
35 36 Pushing as user fred
36 37 hgrc = """
37 38 [hooks]
38 39 pretxnchangegroup.acl = python:hgext.acl.hook
39 40 """
40 41 pushing to ../b
41 42 searching for changes
42 43 common changesets up to 6675d58eff77
43 44 3 changesets found
44 45 List of changesets:
45 46 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
46 47 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
47 48 911600dab2ae7a9baff75958b84fe606851ce955
48 49 adding changesets
49 50 add changeset ef1ea85a6374
50 51 add changeset f9cafe1212c8
51 52 add changeset 911600dab2ae
52 53 adding manifests
53 54 adding file changes
54 55 adding foo/Bar/file.txt revisions
55 56 adding foo/file.txt revisions
56 57 adding quux/file.py revisions
57 58 added 3 changesets with 3 changes to 3 files
58 59 calling hook pretxnchangegroup.acl: hgext.acl.hook
59 60 acl: acl.allow not enabled
60 61 acl: acl.deny not enabled
61 62 acl: changes have source "push" - skipping
63 updating the branch cache
62 64 rolling back last transaction
63 65 0:6675d58eff77
64 66
65 67 No [acl.allow]/[acl.deny]
66 68 Pushing as user fred
67 69 hgrc = """
68 70 [hooks]
69 71 pretxnchangegroup.acl = python:hgext.acl.hook
70 72 [acl]
71 73 sources = push
72 74 """
73 75 pushing to ../b
74 76 searching for changes
75 77 common changesets up to 6675d58eff77
76 78 3 changesets found
77 79 List of changesets:
78 80 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
79 81 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
80 82 911600dab2ae7a9baff75958b84fe606851ce955
81 83 adding changesets
82 84 add changeset ef1ea85a6374
83 85 add changeset f9cafe1212c8
84 86 add changeset 911600dab2ae
85 87 adding manifests
86 88 adding file changes
87 89 adding foo/Bar/file.txt revisions
88 90 adding foo/file.txt revisions
89 91 adding quux/file.py revisions
90 92 added 3 changesets with 3 changes to 3 files
91 93 calling hook pretxnchangegroup.acl: hgext.acl.hook
92 94 acl: acl.allow not enabled
93 95 acl: acl.deny not enabled
94 96 acl: allowing changeset ef1ea85a6374
95 97 acl: allowing changeset f9cafe1212c8
96 98 acl: allowing changeset 911600dab2ae
99 updating the branch cache
97 100 rolling back last transaction
98 101 0:6675d58eff77
99 102
100 103 Empty [acl.allow]
101 104 Pushing as user fred
102 105 hgrc = """
103 106 [hooks]
104 107 pretxnchangegroup.acl = python:hgext.acl.hook
105 108 [acl]
106 109 sources = push
107 110 [acl.allow]
108 111 """
109 112 pushing to ../b
110 113 searching for changes
111 114 common changesets up to 6675d58eff77
112 115 3 changesets found
113 116 List of changesets:
114 117 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
115 118 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
116 119 911600dab2ae7a9baff75958b84fe606851ce955
117 120 adding changesets
118 121 add changeset ef1ea85a6374
119 122 add changeset f9cafe1212c8
120 123 add changeset 911600dab2ae
121 124 adding manifests
122 125 adding file changes
123 126 adding foo/Bar/file.txt revisions
124 127 adding foo/file.txt revisions
125 128 adding quux/file.py revisions
126 129 added 3 changesets with 3 changes to 3 files
127 130 calling hook pretxnchangegroup.acl: hgext.acl.hook
128 131 acl: acl.allow enabled, 0 entries for user fred
129 132 acl: acl.deny not enabled
130 133 acl: user fred not allowed on foo/file.txt
131 134 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
132 135 transaction abort!
133 136 rollback completed
134 137 abort: acl: access denied for changeset ef1ea85a6374
135 138 no rollback information available
136 139 0:6675d58eff77
137 140
138 141 fred is allowed inside foo/
139 142 Pushing as user fred
140 143 hgrc = """
141 144 [hooks]
142 145 pretxnchangegroup.acl = python:hgext.acl.hook
143 146 [acl]
144 147 sources = push
145 148 [acl.allow]
146 149 foo/** = fred
147 150 """
148 151 pushing to ../b
149 152 searching for changes
150 153 common changesets up to 6675d58eff77
151 154 3 changesets found
152 155 List of changesets:
153 156 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
154 157 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
155 158 911600dab2ae7a9baff75958b84fe606851ce955
156 159 adding changesets
157 160 add changeset ef1ea85a6374
158 161 add changeset f9cafe1212c8
159 162 add changeset 911600dab2ae
160 163 adding manifests
161 164 adding file changes
162 165 adding foo/Bar/file.txt revisions
163 166 adding foo/file.txt revisions
164 167 adding quux/file.py revisions
165 168 added 3 changesets with 3 changes to 3 files
166 169 calling hook pretxnchangegroup.acl: hgext.acl.hook
167 170 acl: acl.allow enabled, 1 entries for user fred
168 171 acl: acl.deny not enabled
169 172 acl: allowing changeset ef1ea85a6374
170 173 acl: allowing changeset f9cafe1212c8
171 174 acl: user fred not allowed on quux/file.py
172 175 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
173 176 transaction abort!
174 177 rollback completed
175 178 abort: acl: access denied for changeset 911600dab2ae
176 179 no rollback information available
177 180 0:6675d58eff77
178 181
179 182 Empty [acl.deny]
180 183 Pushing as user barney
181 184 hgrc = """
182 185 [hooks]
183 186 pretxnchangegroup.acl = python:hgext.acl.hook
184 187 [acl]
185 188 sources = push
186 189 [acl.allow]
187 190 foo/** = fred
188 191 [acl.deny]
189 192 """
190 193 pushing to ../b
191 194 searching for changes
192 195 common changesets up to 6675d58eff77
193 196 3 changesets found
194 197 List of changesets:
195 198 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
196 199 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
197 200 911600dab2ae7a9baff75958b84fe606851ce955
198 201 adding changesets
199 202 add changeset ef1ea85a6374
200 203 add changeset f9cafe1212c8
201 204 add changeset 911600dab2ae
202 205 adding manifests
203 206 adding file changes
204 207 adding foo/Bar/file.txt revisions
205 208 adding foo/file.txt revisions
206 209 adding quux/file.py revisions
207 210 added 3 changesets with 3 changes to 3 files
208 211 calling hook pretxnchangegroup.acl: hgext.acl.hook
209 212 acl: acl.allow enabled, 0 entries for user barney
210 213 acl: acl.deny enabled, 0 entries for user barney
211 214 acl: user barney not allowed on foo/file.txt
212 215 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
213 216 transaction abort!
214 217 rollback completed
215 218 abort: acl: access denied for changeset ef1ea85a6374
216 219 no rollback information available
217 220 0:6675d58eff77
218 221
219 222 fred is allowed inside foo/, but not foo/bar/ (case matters)
220 223 Pushing as user fred
221 224 hgrc = """
222 225 [hooks]
223 226 pretxnchangegroup.acl = python:hgext.acl.hook
224 227 [acl]
225 228 sources = push
226 229 [acl.allow]
227 230 foo/** = fred
228 231 [acl.deny]
229 232 foo/bar/** = fred
230 233 """
231 234 pushing to ../b
232 235 searching for changes
233 236 common changesets up to 6675d58eff77
234 237 3 changesets found
235 238 List of changesets:
236 239 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
237 240 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
238 241 911600dab2ae7a9baff75958b84fe606851ce955
239 242 adding changesets
240 243 add changeset ef1ea85a6374
241 244 add changeset f9cafe1212c8
242 245 add changeset 911600dab2ae
243 246 adding manifests
244 247 adding file changes
245 248 adding foo/Bar/file.txt revisions
246 249 adding foo/file.txt revisions
247 250 adding quux/file.py revisions
248 251 added 3 changesets with 3 changes to 3 files
249 252 calling hook pretxnchangegroup.acl: hgext.acl.hook
250 253 acl: acl.allow enabled, 1 entries for user fred
251 254 acl: acl.deny enabled, 1 entries for user fred
252 255 acl: allowing changeset ef1ea85a6374
253 256 acl: allowing changeset f9cafe1212c8
254 257 acl: user fred not allowed on quux/file.py
255 258 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
256 259 transaction abort!
257 260 rollback completed
258 261 abort: acl: access denied for changeset 911600dab2ae
259 262 no rollback information available
260 263 0:6675d58eff77
261 264
262 265 fred is allowed inside foo/, but not foo/Bar/
263 266 Pushing as user fred
264 267 hgrc = """
265 268 [hooks]
266 269 pretxnchangegroup.acl = python:hgext.acl.hook
267 270 [acl]
268 271 sources = push
269 272 [acl.allow]
270 273 foo/** = fred
271 274 [acl.deny]
272 275 foo/bar/** = fred
273 276 foo/Bar/** = fred
274 277 """
275 278 pushing to ../b
276 279 searching for changes
277 280 common changesets up to 6675d58eff77
278 281 3 changesets found
279 282 List of changesets:
280 283 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
281 284 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
282 285 911600dab2ae7a9baff75958b84fe606851ce955
283 286 adding changesets
284 287 add changeset ef1ea85a6374
285 288 add changeset f9cafe1212c8
286 289 add changeset 911600dab2ae
287 290 adding manifests
288 291 adding file changes
289 292 adding foo/Bar/file.txt revisions
290 293 adding foo/file.txt revisions
291 294 adding quux/file.py revisions
292 295 added 3 changesets with 3 changes to 3 files
293 296 calling hook pretxnchangegroup.acl: hgext.acl.hook
294 297 acl: acl.allow enabled, 1 entries for user fred
295 298 acl: acl.deny enabled, 2 entries for user fred
296 299 acl: allowing changeset ef1ea85a6374
297 300 acl: user fred denied on foo/Bar/file.txt
298 301 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8
299 302 transaction abort!
300 303 rollback completed
301 304 abort: acl: access denied for changeset f9cafe1212c8
302 305 no rollback information available
303 306 0:6675d58eff77
304 307
305 308 barney is not mentioned => not allowed anywhere
306 309 Pushing as user barney
307 310 hgrc = """
308 311 [hooks]
309 312 pretxnchangegroup.acl = python:hgext.acl.hook
310 313 [acl]
311 314 sources = push
312 315 [acl.allow]
313 316 foo/** = fred
314 317 [acl.deny]
315 318 foo/bar/** = fred
316 319 foo/Bar/** = fred
317 320 """
318 321 pushing to ../b
319 322 searching for changes
320 323 common changesets up to 6675d58eff77
321 324 3 changesets found
322 325 List of changesets:
323 326 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
324 327 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
325 328 911600dab2ae7a9baff75958b84fe606851ce955
326 329 adding changesets
327 330 add changeset ef1ea85a6374
328 331 add changeset f9cafe1212c8
329 332 add changeset 911600dab2ae
330 333 adding manifests
331 334 adding file changes
332 335 adding foo/Bar/file.txt revisions
333 336 adding foo/file.txt revisions
334 337 adding quux/file.py revisions
335 338 added 3 changesets with 3 changes to 3 files
336 339 calling hook pretxnchangegroup.acl: hgext.acl.hook
337 340 acl: acl.allow enabled, 0 entries for user barney
338 341 acl: acl.deny enabled, 0 entries for user barney
339 342 acl: user barney not allowed on foo/file.txt
340 343 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
341 344 transaction abort!
342 345 rollback completed
343 346 abort: acl: access denied for changeset ef1ea85a6374
344 347 no rollback information available
345 348 0:6675d58eff77
346 349
347 350 barney is allowed everywhere
348 351 Pushing as user barney
349 352 hgrc = """
350 353 [hooks]
351 354 pretxnchangegroup.acl = python:hgext.acl.hook
352 355 [acl]
353 356 sources = push
354 357 [acl.allow]
355 358 foo/** = fred
356 359 [acl.deny]
357 360 foo/bar/** = fred
358 361 foo/Bar/** = fred
359 362 [acl.allow]
360 363 ** = barney
361 364 """
362 365 pushing to ../b
363 366 searching for changes
364 367 common changesets up to 6675d58eff77
365 368 3 changesets found
366 369 List of changesets:
367 370 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
368 371 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
369 372 911600dab2ae7a9baff75958b84fe606851ce955
370 373 adding changesets
371 374 add changeset ef1ea85a6374
372 375 add changeset f9cafe1212c8
373 376 add changeset 911600dab2ae
374 377 adding manifests
375 378 adding file changes
376 379 adding foo/Bar/file.txt revisions
377 380 adding foo/file.txt revisions
378 381 adding quux/file.py revisions
379 382 added 3 changesets with 3 changes to 3 files
380 383 calling hook pretxnchangegroup.acl: hgext.acl.hook
381 384 acl: acl.allow enabled, 1 entries for user barney
382 385 acl: acl.deny enabled, 0 entries for user barney
383 386 acl: allowing changeset ef1ea85a6374
384 387 acl: allowing changeset f9cafe1212c8
385 388 acl: allowing changeset 911600dab2ae
389 updating the branch cache
386 390 rolling back last transaction
387 391 0:6675d58eff77
388 392
389 393 wilma can change files with a .txt extension
390 394 Pushing as user wilma
391 395 hgrc = """
392 396 [hooks]
393 397 pretxnchangegroup.acl = python:hgext.acl.hook
394 398 [acl]
395 399 sources = push
396 400 [acl.allow]
397 401 foo/** = fred
398 402 [acl.deny]
399 403 foo/bar/** = fred
400 404 foo/Bar/** = fred
401 405 [acl.allow]
402 406 ** = barney
403 407 **/*.txt = wilma
404 408 """
405 409 pushing to ../b
406 410 searching for changes
407 411 common changesets up to 6675d58eff77
408 412 3 changesets found
409 413 List of changesets:
410 414 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
411 415 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
412 416 911600dab2ae7a9baff75958b84fe606851ce955
413 417 adding changesets
414 418 add changeset ef1ea85a6374
415 419 add changeset f9cafe1212c8
416 420 add changeset 911600dab2ae
417 421 adding manifests
418 422 adding file changes
419 423 adding foo/Bar/file.txt revisions
420 424 adding foo/file.txt revisions
421 425 adding quux/file.py revisions
422 426 added 3 changesets with 3 changes to 3 files
423 427 calling hook pretxnchangegroup.acl: hgext.acl.hook
424 428 acl: acl.allow enabled, 1 entries for user wilma
425 429 acl: acl.deny enabled, 0 entries for user wilma
426 430 acl: allowing changeset ef1ea85a6374
427 431 acl: allowing changeset f9cafe1212c8
428 432 acl: user wilma not allowed on quux/file.py
429 433 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
430 434 transaction abort!
431 435 rollback completed
432 436 abort: acl: access denied for changeset 911600dab2ae
433 437 no rollback information available
434 438 0:6675d58eff77
435 439
436 440 file specified by acl.config does not exist
437 441 Pushing as user barney
438 442 hgrc = """
439 443 [hooks]
440 444 pretxnchangegroup.acl = python:hgext.acl.hook
441 445 [acl]
442 446 sources = push
443 447 [acl.allow]
444 448 foo/** = fred
445 449 [acl.deny]
446 450 foo/bar/** = fred
447 451 foo/Bar/** = fred
448 452 [acl.allow]
449 453 ** = barney
450 454 **/*.txt = wilma
451 455 [acl]
452 456 config = ../acl.config
453 457 """
454 458 pushing to ../b
455 459 searching for changes
456 460 common changesets up to 6675d58eff77
457 461 3 changesets found
458 462 List of changesets:
459 463 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
460 464 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
461 465 911600dab2ae7a9baff75958b84fe606851ce955
462 466 adding changesets
463 467 add changeset ef1ea85a6374
464 468 add changeset f9cafe1212c8
465 469 add changeset 911600dab2ae
466 470 adding manifests
467 471 adding file changes
468 472 adding foo/Bar/file.txt revisions
469 473 adding foo/file.txt revisions
470 474 adding quux/file.py revisions
471 475 added 3 changesets with 3 changes to 3 files
472 476 calling hook pretxnchangegroup.acl: hgext.acl.hook
473 477 error: pretxnchangegroup.acl hook failed: unable to open ../acl.config: No such file or directory
474 478 transaction abort!
475 479 rollback completed
476 480 abort: unable to open ../acl.config: No such file or directory
477 481 no rollback information available
478 482 0:6675d58eff77
479 483
480 484 betty is allowed inside foo/ by a acl.config file
481 485 Pushing as user betty
482 486 hgrc = """
483 487 [hooks]
484 488 pretxnchangegroup.acl = python:hgext.acl.hook
485 489 [acl]
486 490 sources = push
487 491 [acl.allow]
488 492 foo/** = fred
489 493 [acl.deny]
490 494 foo/bar/** = fred
491 495 foo/Bar/** = fred
492 496 [acl.allow]
493 497 ** = barney
494 498 **/*.txt = wilma
495 499 [acl]
496 500 config = ../acl.config
497 501 """
498 502 acl.config = """
499 503 [acl.allow]
500 504 foo/** = betty
501 505 """
502 506 pushing to ../b
503 507 searching for changes
504 508 common changesets up to 6675d58eff77
505 509 3 changesets found
506 510 List of changesets:
507 511 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
508 512 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
509 513 911600dab2ae7a9baff75958b84fe606851ce955
510 514 adding changesets
511 515 add changeset ef1ea85a6374
512 516 add changeset f9cafe1212c8
513 517 add changeset 911600dab2ae
514 518 adding manifests
515 519 adding file changes
516 520 adding foo/Bar/file.txt revisions
517 521 adding foo/file.txt revisions
518 522 adding quux/file.py revisions
519 523 added 3 changesets with 3 changes to 3 files
520 524 calling hook pretxnchangegroup.acl: hgext.acl.hook
521 525 acl: acl.allow enabled, 1 entries for user betty
522 526 acl: acl.deny enabled, 0 entries for user betty
523 527 acl: allowing changeset ef1ea85a6374
524 528 acl: allowing changeset f9cafe1212c8
525 529 acl: user betty not allowed on quux/file.py
526 530 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
527 531 transaction abort!
528 532 rollback completed
529 533 abort: acl: access denied for changeset 911600dab2ae
530 534 no rollback information available
531 535 0:6675d58eff77
532 536
533 537 acl.config can set only [acl.allow]/[acl.deny]
534 538 Pushing as user barney
535 539 hgrc = """
536 540 [hooks]
537 541 pretxnchangegroup.acl = python:hgext.acl.hook
538 542 [acl]
539 543 sources = push
540 544 [acl.allow]
541 545 foo/** = fred
542 546 [acl.deny]
543 547 foo/bar/** = fred
544 548 foo/Bar/** = fred
545 549 [acl.allow]
546 550 ** = barney
547 551 **/*.txt = wilma
548 552 [acl]
549 553 config = ../acl.config
550 554 """
551 555 acl.config = """
552 556 [acl.allow]
553 557 foo/** = betty
554 558 [hooks]
555 559 changegroup.acl = false
556 560 """
557 561 pushing to ../b
558 562 searching for changes
559 563 common changesets up to 6675d58eff77
560 564 3 changesets found
561 565 List of changesets:
562 566 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
563 567 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
564 568 911600dab2ae7a9baff75958b84fe606851ce955
565 569 adding changesets
566 570 add changeset ef1ea85a6374
567 571 add changeset f9cafe1212c8
568 572 add changeset 911600dab2ae
569 573 adding manifests
570 574 adding file changes
571 575 adding foo/Bar/file.txt revisions
572 576 adding foo/file.txt revisions
573 577 adding quux/file.py revisions
574 578 added 3 changesets with 3 changes to 3 files
575 579 calling hook pretxnchangegroup.acl: hgext.acl.hook
576 580 acl: acl.allow enabled, 1 entries for user barney
577 581 acl: acl.deny enabled, 0 entries for user barney
578 582 acl: allowing changeset ef1ea85a6374
579 583 acl: allowing changeset f9cafe1212c8
580 584 acl: allowing changeset 911600dab2ae
585 updating the branch cache
581 586 rolling back last transaction
582 587 0:6675d58eff77
583 588
@@ -1,65 +1,74 b''
1 1 #!/bin/sh
2 2
3 3 hg init t
4 4 cd t
5 5 hg branches
6 6
7 7 echo foo > a
8 8 hg add a
9 9 hg ci -m "initial" -d "1000000 0"
10 10 hg branch foo
11 11 hg branch
12 12 hg ci -m "add branch name" -d "1000000 0"
13 13 hg branch bar
14 14 hg ci -m "change branch name" -d "1000000 0"
15 15 echo % branch shadowing
16 16 hg branch default
17 17 hg branch -f default
18 18 hg ci -m "clear branch name" -d "1000000 0"
19 19
20 20 hg co foo
21 21 hg branch
22 22 echo bleah > a
23 23 hg ci -m "modify a branch" -d "1000000 0"
24 24
25 25 hg merge
26 26 hg branch
27 27 hg ci -m "merge" -d "1000000 0"
28 28 hg log
29 29
30 30 hg branches
31 31 hg branches -q
32 32
33 33 echo % test for invalid branch cache
34 34 hg rollback
35 35 cp .hg/branch.cache .hg/bc-invalid
36 36 hg log -r foo
37 37 cp .hg/bc-invalid .hg/branch.cache
38 38 hg --debug log -r foo
39 39 rm .hg/branch.cache
40 40 echo corrupted > .hg/branch.cache
41 41 hg log -qr foo
42 42 cat .hg/branch.cache
43 43
44 echo % push should update the branch cache
45 hg init ../target
46 echo % pushing just rev 0
47 hg push -qr 0 ../target
48 cat ../target/.hg/branch.cache
49 echo % pushing everything
50 hg push -qf ../target
51 cat ../target/.hg/branch.cache
52
44 53 echo % update with no arguments: tipmost revision of the current branch
45 54 hg up -q -C 0
46 55 hg up -q
47 56 hg id
48 57 hg up -q 1
49 58 hg up -q
50 59 hg id
51 60 hg branch foobar
52 61 hg up
53 62
54 63 echo % fastforward merge
55 64 hg branch ff
56 65 echo ff > ff
57 66 hg ci -Am'fast forward' -d '1000000 0'
58 67 hg up foo
59 68 hg merge ff
60 69 hg branch
61 70 hg commit -m'Merge ff into foo' -d '1000000 0'
62 71 hg parents
63 72 hg manifest
64 73
65 74 exit 0
@@ -1,108 +1,117 b''
1 1 marked working directory as branch foo
2 2 foo
3 3 marked working directory as branch bar
4 4 % branch shadowing
5 5 abort: a branch of the same name already exists (use --force to override)
6 6 marked working directory as branch default
7 7 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 8 foo
9 9 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
10 10 (branch merge, don't forget to commit)
11 11 foo
12 12 changeset: 5:5f8fb06e083e
13 13 branch: foo
14 14 tag: tip
15 15 parent: 4:4909a3732169
16 16 parent: 3:bf1bc2f45e83
17 17 user: test
18 18 date: Mon Jan 12 13:46:40 1970 +0000
19 19 summary: merge
20 20
21 21 changeset: 4:4909a3732169
22 22 branch: foo
23 23 parent: 1:b699b1cec9c2
24 24 user: test
25 25 date: Mon Jan 12 13:46:40 1970 +0000
26 26 summary: modify a branch
27 27
28 28 changeset: 3:bf1bc2f45e83
29 29 user: test
30 30 date: Mon Jan 12 13:46:40 1970 +0000
31 31 summary: clear branch name
32 32
33 33 changeset: 2:67ec16bde7f1
34 34 branch: bar
35 35 user: test
36 36 date: Mon Jan 12 13:46:40 1970 +0000
37 37 summary: change branch name
38 38
39 39 changeset: 1:b699b1cec9c2
40 40 branch: foo
41 41 user: test
42 42 date: Mon Jan 12 13:46:40 1970 +0000
43 43 summary: add branch name
44 44
45 45 changeset: 0:be8523e69bf8
46 46 user: test
47 47 date: Mon Jan 12 13:46:40 1970 +0000
48 48 summary: initial
49 49
50 50 foo 5:5f8fb06e083e
51 51 default 3:bf1bc2f45e83 (inactive)
52 52 bar 2:67ec16bde7f1 (inactive)
53 53 foo
54 54 default
55 55 bar
56 56 % test for invalid branch cache
57 57 rolling back last transaction
58 58 changeset: 4:4909a3732169
59 59 branch: foo
60 60 tag: tip
61 61 parent: 1:b699b1cec9c2
62 62 user: test
63 63 date: Mon Jan 12 13:46:40 1970 +0000
64 64 summary: modify a branch
65 65
66 66 Invalid branch cache: unknown tip
67 67 changeset: 4:4909a3732169c0c20011c4f4b8fdff4e3d89b23f
68 68 branch: foo
69 69 tag: tip
70 70 parent: 1:b699b1cec9c2966b3700de4fef0dc123cd754c31
71 71 parent: -1:0000000000000000000000000000000000000000
72 72 manifest: 4:d01b250baaa05909152f7ae07d7a649deea0df9a
73 73 user: test
74 74 date: Mon Jan 12 13:46:40 1970 +0000
75 75 files: a
76 76 extra: branch=foo
77 77 description:
78 78 modify a branch
79 79
80 80
81 81 4:4909a3732169
82 82 4909a3732169c0c20011c4f4b8fdff4e3d89b23f 4
83 83 bf1bc2f45e834c75404d0ddab57d53beab56e2f8 default
84 84 4909a3732169c0c20011c4f4b8fdff4e3d89b23f foo
85 85 67ec16bde7f1575d523313b9bca000f6a6f12dca bar
86 % push should update the branch cache
87 % pushing just rev 0
88 be8523e69bf892e25817fc97187516b3c0804ae4 0
89 be8523e69bf892e25817fc97187516b3c0804ae4 default
90 % pushing everything
91 4909a3732169c0c20011c4f4b8fdff4e3d89b23f 4
92 bf1bc2f45e834c75404d0ddab57d53beab56e2f8 default
93 4909a3732169c0c20011c4f4b8fdff4e3d89b23f foo
94 67ec16bde7f1575d523313b9bca000f6a6f12dca bar
86 95 % update with no arguments: tipmost revision of the current branch
87 96 bf1bc2f45e83
88 97 4909a3732169 (foo) tip
89 98 marked working directory as branch foobar
90 99 abort: branch foobar not found
91 100 % fastforward merge
92 101 marked working directory as branch ff
93 102 adding ff
94 103 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
95 104 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 105 (branch merge, don't forget to commit)
97 106 foo
98 107 changeset: 6:f0c74f92a385
99 108 branch: foo
100 109 tag: tip
101 110 parent: 4:4909a3732169
102 111 parent: 5:c420d2121b71
103 112 user: test
104 113 date: Mon Jan 12 13:46:40 1970 +0000
105 114 summary: Merge ff into foo
106 115
107 116 a
108 117 ff
General Comments 0
You need to be logged in to leave comments. Login now