##// END OF EJS Templates
util: drop the 'unpacker' helper...
Pierre-Yves David -
r25211:22f4ce49 default
parent child Browse files
Show More
@@ -1,1252 +1,1252 b''
1 1 # obsolete.py - obsolete markers handling
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """Obsolete marker handling
10 10
11 11 An obsolete marker maps an old changeset to a list of new
12 12 changesets. If the list of new changesets is empty, the old changeset
13 13 is said to be "killed". Otherwise, the old changeset is being
14 14 "replaced" by the new changesets.
15 15
16 16 Obsolete markers can be used to record and distribute changeset graph
17 17 transformations performed by history rewrite operations, and help
18 18 building new tools to reconcile conflicting rewrite actions. To
19 19 facilitate conflict resolution, markers include various annotations
20 20 besides old and news changeset identifiers, such as creation date or
21 21 author name.
22 22
23 23 The old obsoleted changeset is called a "precursor" and possible
24 24 replacements are called "successors". Markers that used changeset X as
25 25 a precursor are called "successor markers of X" because they hold
26 26 information about the successors of X. Markers that use changeset Y as
27 27 a successors are call "precursor markers of Y" because they hold
28 28 information about the precursors of Y.
29 29
30 30 Examples:
31 31
32 32 - When changeset A is replaced by changeset A', one marker is stored:
33 33
34 34 (A, (A',))
35 35
36 36 - When changesets A and B are folded into a new changeset C, two markers are
37 37 stored:
38 38
39 39 (A, (C,)) and (B, (C,))
40 40
41 41 - When changeset A is simply "pruned" from the graph, a marker is created:
42 42
43 43 (A, ())
44 44
45 45 - When changeset A is split into B and C, a single marker are used:
46 46
47 47 (A, (C, C))
48 48
49 49 We use a single marker to distinguish the "split" case from the "divergence"
50 50 case. If two independent operations rewrite the same changeset A in to A' and
51 51 A'', we have an error case: divergent rewriting. We can detect it because
52 52 two markers will be created independently:
53 53
54 54 (A, (B,)) and (A, (C,))
55 55
56 56 Format
57 57 ------
58 58
59 59 Markers are stored in an append-only file stored in
60 60 '.hg/store/obsstore'.
61 61
62 62 The file starts with a version header:
63 63
64 64 - 1 unsigned byte: version number, starting at zero.
65 65
66 66 The header is followed by the markers. Marker format depend of the version. See
67 67 comment associated with each format for details.
68 68
69 69 """
70 70 import struct
71 71 import util, base85, node, parsers
72 72 import phases
73 73 from i18n import _
74 74
75 75 _pack = struct.pack
76 76 _unpack = struct.unpack
77 77 _calcsize = struct.calcsize
78 78 propertycache = util.propertycache
79 79
80 80 # the obsolete feature is not mature enough to be enabled by default.
81 81 # you have to rely on third party extension extension to enable this.
82 82 _enabled = False
83 83
84 84 # Options for obsolescence
85 85 createmarkersopt = 'createmarkers'
86 86 allowunstableopt = 'allowunstable'
87 87 exchangeopt = 'exchange'
88 88
89 89 ### obsolescence marker flag
90 90
91 91 ## bumpedfix flag
92 92 #
93 93 # When a changeset A' succeed to a changeset A which became public, we call A'
94 94 # "bumped" because it's a successors of a public changesets
95 95 #
96 96 # o A' (bumped)
97 97 # |`:
98 98 # | o A
99 99 # |/
100 100 # o Z
101 101 #
102 102 # The way to solve this situation is to create a new changeset Ad as children
103 103 # of A. This changeset have the same content than A'. So the diff from A to A'
104 104 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
105 105 #
106 106 # o Ad
107 107 # |`:
108 108 # | x A'
109 109 # |'|
110 110 # o | A
111 111 # |/
112 112 # o Z
113 113 #
114 114 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
115 115 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
116 116 # This flag mean that the successors express the changes between the public and
117 117 # bumped version and fix the situation, breaking the transitivity of
118 118 # "bumped" here.
119 119 bumpedfix = 1
120 120 usingsha256 = 2
121 121
122 122 ## Parsing and writing of version "0"
123 123 #
124 124 # The header is followed by the markers. Each marker is made of:
125 125 #
126 126 # - 1 uint8 : number of new changesets "N", can be zero.
127 127 #
128 128 # - 1 uint32: metadata size "M" in bytes.
129 129 #
130 130 # - 1 byte: a bit field. It is reserved for flags used in common
131 131 # obsolete marker operations, to avoid repeated decoding of metadata
132 132 # entries.
133 133 #
134 134 # - 20 bytes: obsoleted changeset identifier.
135 135 #
136 136 # - N*20 bytes: new changesets identifiers.
137 137 #
138 138 # - M bytes: metadata as a sequence of nul-terminated strings. Each
139 139 # string contains a key and a value, separated by a colon ':', without
140 140 # additional encoding. Keys cannot contain '\0' or ':' and values
141 141 # cannot contain '\0'.
142 142 _fm0version = 0
143 143 _fm0fixed = '>BIB20s'
144 144 _fm0node = '20s'
145 145 _fm0fsize = _calcsize(_fm0fixed)
146 146 _fm0fnodesize = _calcsize(_fm0node)
147 147
148 148 def _fm0readmarkers(data, off):
149 149 # Loop on markers
150 150 l = len(data)
151 151 while off + _fm0fsize <= l:
152 152 # read fixed part
153 153 cur = data[off:off + _fm0fsize]
154 154 off += _fm0fsize
155 155 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
156 156 # read replacement
157 157 sucs = ()
158 158 if numsuc:
159 159 s = (_fm0fnodesize * numsuc)
160 160 cur = data[off:off + s]
161 161 sucs = _unpack(_fm0node * numsuc, cur)
162 162 off += s
163 163 # read metadata
164 164 # (metadata will be decoded on demand)
165 165 metadata = data[off:off + mdsize]
166 166 if len(metadata) != mdsize:
167 167 raise util.Abort(_('parsing obsolete marker: metadata is too '
168 168 'short, %d bytes expected, got %d')
169 169 % (mdsize, len(metadata)))
170 170 off += mdsize
171 171 metadata = _fm0decodemeta(metadata)
172 172 try:
173 173 when, offset = metadata.pop('date', '0 0').split(' ')
174 174 date = float(when), int(offset)
175 175 except ValueError:
176 176 date = (0., 0)
177 177 parents = None
178 178 if 'p2' in metadata:
179 179 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
180 180 elif 'p1' in metadata:
181 181 parents = (metadata.pop('p1', None),)
182 182 elif 'p0' in metadata:
183 183 parents = ()
184 184 if parents is not None:
185 185 try:
186 186 parents = tuple(node.bin(p) for p in parents)
187 187 # if parent content is not a nodeid, drop the data
188 188 for p in parents:
189 189 if len(p) != 20:
190 190 parents = None
191 191 break
192 192 except TypeError:
193 193 # if content cannot be translated to nodeid drop the data.
194 194 parents = None
195 195
196 196 metadata = tuple(sorted(metadata.iteritems()))
197 197
198 198 yield (pre, sucs, flags, metadata, date, parents)
199 199
200 200 def _fm0encodeonemarker(marker):
201 201 pre, sucs, flags, metadata, date, parents = marker
202 202 if flags & usingsha256:
203 203 raise util.Abort(_('cannot handle sha256 with old obsstore format'))
204 204 metadata = dict(metadata)
205 205 time, tz = date
206 206 metadata['date'] = '%r %i' % (time, tz)
207 207 if parents is not None:
208 208 if not parents:
209 209 # mark that we explicitly recorded no parents
210 210 metadata['p0'] = ''
211 211 for i, p in enumerate(parents):
212 212 metadata['p%i' % (i + 1)] = node.hex(p)
213 213 metadata = _fm0encodemeta(metadata)
214 214 numsuc = len(sucs)
215 215 format = _fm0fixed + (_fm0node * numsuc)
216 216 data = [numsuc, len(metadata), flags, pre]
217 217 data.extend(sucs)
218 218 return _pack(format, *data) + metadata
219 219
220 220 def _fm0encodemeta(meta):
221 221 """Return encoded metadata string to string mapping.
222 222
223 223 Assume no ':' in key and no '\0' in both key and value."""
224 224 for key, value in meta.iteritems():
225 225 if ':' in key or '\0' in key:
226 226 raise ValueError("':' and '\0' are forbidden in metadata key'")
227 227 if '\0' in value:
228 228 raise ValueError("':' is forbidden in metadata value'")
229 229 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
230 230
231 231 def _fm0decodemeta(data):
232 232 """Return string to string dictionary from encoded version."""
233 233 d = {}
234 234 for l in data.split('\0'):
235 235 if l:
236 236 key, value = l.split(':')
237 237 d[key] = value
238 238 return d
239 239
240 240 ## Parsing and writing of version "1"
241 241 #
242 242 # The header is followed by the markers. Each marker is made of:
243 243 #
244 244 # - uint32: total size of the marker (including this field)
245 245 #
246 246 # - float64: date in seconds since epoch
247 247 #
248 248 # - int16: timezone offset in minutes
249 249 #
250 250 # - uint16: a bit field. It is reserved for flags used in common
251 251 # obsolete marker operations, to avoid repeated decoding of metadata
252 252 # entries.
253 253 #
254 254 # - uint8: number of successors "N", can be zero.
255 255 #
256 256 # - uint8: number of parents "P", can be zero.
257 257 #
258 258 # 0: parents data stored but no parent,
259 259 # 1: one parent stored,
260 260 # 2: two parents stored,
261 261 # 3: no parent data stored
262 262 #
263 263 # - uint8: number of metadata entries M
264 264 #
265 265 # - 20 or 32 bytes: precursor changeset identifier.
266 266 #
267 267 # - N*(20 or 32) bytes: successors changesets identifiers.
268 268 #
269 269 # - P*(20 or 32) bytes: parents of the precursors changesets.
270 270 #
271 271 # - M*(uint8, uint8): size of all metadata entries (key and value)
272 272 #
273 273 # - remaining bytes: the metadata, each (key, value) pair after the other.
274 274 _fm1version = 1
275 275 _fm1fixed = '>IdhHBBB20s'
276 276 _fm1nodesha1 = '20s'
277 277 _fm1nodesha256 = '32s'
278 278 _fm1nodesha1size = _calcsize(_fm1nodesha1)
279 279 _fm1nodesha256size = _calcsize(_fm1nodesha256)
280 280 _fm1fsize = _calcsize(_fm1fixed)
281 281 _fm1parentnone = 3
282 282 _fm1parentshift = 14
283 283 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
284 284 _fm1metapair = 'BB'
285 285 _fm1metapairsize = _calcsize('BB')
286 286
287 287 def _fm1purereadmarkers(data, off):
288 288 # make some global constants local for performance
289 289 noneflag = _fm1parentnone
290 290 sha2flag = usingsha256
291 291 sha1size = _fm1nodesha1size
292 292 sha2size = _fm1nodesha256size
293 293 sha1fmt = _fm1nodesha1
294 294 sha2fmt = _fm1nodesha256
295 295 metasize = _fm1metapairsize
296 296 metafmt = _fm1metapair
297 297 fsize = _fm1fsize
298 298 unpack = _unpack
299 299
300 300 # Loop on markers
301 301 stop = len(data) - _fm1fsize
302 ufixed = util.unpacker(_fm1fixed)
302 ufixed = struct.Struct(_fm1fixed).unpack
303 303
304 304 while off <= stop:
305 305 # read fixed part
306 306 o1 = off + fsize
307 307 t, secs, tz, flags, numsuc, numpar, nummeta, prec = ufixed(data[off:o1])
308 308
309 309 if flags & sha2flag:
310 310 # FIXME: prec was read as a SHA1, needs to be amended
311 311
312 312 # read 0 or more successors
313 313 if numsuc == 1:
314 314 o2 = o1 + sha2size
315 315 sucs = (data[o1:o2],)
316 316 else:
317 317 o2 = o1 + sha2size * numsuc
318 318 sucs = unpack(sha2fmt * numsuc, data[o1:o2])
319 319
320 320 # read parents
321 321 if numpar == noneflag:
322 322 o3 = o2
323 323 parents = None
324 324 elif numpar == 1:
325 325 o3 = o2 + sha2size
326 326 parents = (data[o2:o3],)
327 327 else:
328 328 o3 = o2 + sha2size * numpar
329 329 parents = unpack(sha2fmt * numpar, data[o2:o3])
330 330 else:
331 331 # read 0 or more successors
332 332 if numsuc == 1:
333 333 o2 = o1 + sha1size
334 334 sucs = (data[o1:o2],)
335 335 else:
336 336 o2 = o1 + sha1size * numsuc
337 337 sucs = unpack(sha1fmt * numsuc, data[o1:o2])
338 338
339 339 # read parents
340 340 if numpar == noneflag:
341 341 o3 = o2
342 342 parents = None
343 343 elif numpar == 1:
344 344 o3 = o2 + sha1size
345 345 parents = (data[o2:o3],)
346 346 else:
347 347 o3 = o2 + sha1size * numpar
348 348 parents = unpack(sha1fmt * numpar, data[o2:o3])
349 349
350 350 # read metadata
351 351 off = o3 + metasize * nummeta
352 352 metapairsize = unpack('>' + (metafmt * nummeta), data[o3:off])
353 353 metadata = []
354 354 for idx in xrange(0, len(metapairsize), 2):
355 355 o1 = off + metapairsize[idx]
356 356 o2 = o1 + metapairsize[idx + 1]
357 357 metadata.append((data[off:o1], data[o1:o2]))
358 358 off = o2
359 359
360 360 yield (prec, sucs, flags, tuple(metadata), (secs, tz * 60), parents)
361 361
362 362 def _fm1encodeonemarker(marker):
363 363 pre, sucs, flags, metadata, date, parents = marker
364 364 # determine node size
365 365 _fm1node = _fm1nodesha1
366 366 if flags & usingsha256:
367 367 _fm1node = _fm1nodesha256
368 368 numsuc = len(sucs)
369 369 numextranodes = numsuc
370 370 if parents is None:
371 371 numpar = _fm1parentnone
372 372 else:
373 373 numpar = len(parents)
374 374 numextranodes += numpar
375 375 formatnodes = _fm1node * numextranodes
376 376 formatmeta = _fm1metapair * len(metadata)
377 377 format = _fm1fixed + formatnodes + formatmeta
378 378 # tz is stored in minutes so we divide by 60
379 379 tz = date[1]//60
380 380 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
381 381 data.extend(sucs)
382 382 if parents is not None:
383 383 data.extend(parents)
384 384 totalsize = _calcsize(format)
385 385 for key, value in metadata:
386 386 lk = len(key)
387 387 lv = len(value)
388 388 data.append(lk)
389 389 data.append(lv)
390 390 totalsize += lk + lv
391 391 data[0] = totalsize
392 392 data = [_pack(format, *data)]
393 393 for key, value in metadata:
394 394 data.append(key)
395 395 data.append(value)
396 396 return ''.join(data)
397 397
398 398 def _fm1readmarkers(data, off):
399 399 native = getattr(parsers, 'fm1readmarkers', None)
400 400 if not native:
401 401 return _fm1purereadmarkers(data, off)
402 402 stop = len(data) - _fm1fsize
403 403 return native(data, off, stop)
404 404
405 405 # mapping to read/write various marker formats
406 406 # <version> -> (decoder, encoder)
407 407 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
408 408 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
409 409
410 410 @util.nogc
411 411 def _readmarkers(data):
412 412 """Read and enumerate markers from raw data"""
413 413 off = 0
414 414 diskversion = _unpack('>B', data[off:off + 1])[0]
415 415 off += 1
416 416 if diskversion not in formats:
417 417 raise util.Abort(_('parsing obsolete marker: unknown version %r')
418 418 % diskversion)
419 419 return diskversion, formats[diskversion][0](data, off)
420 420
421 421 def encodemarkers(markers, addheader=False, version=_fm0version):
422 422 # Kept separate from flushmarkers(), it will be reused for
423 423 # markers exchange.
424 424 encodeone = formats[version][1]
425 425 if addheader:
426 426 yield _pack('>B', version)
427 427 for marker in markers:
428 428 yield encodeone(marker)
429 429
430 430
431 431 class marker(object):
432 432 """Wrap obsolete marker raw data"""
433 433
434 434 def __init__(self, repo, data):
435 435 # the repo argument will be used to create changectx in later version
436 436 self._repo = repo
437 437 self._data = data
438 438 self._decodedmeta = None
439 439
440 440 def __hash__(self):
441 441 return hash(self._data)
442 442
443 443 def __eq__(self, other):
444 444 if type(other) != type(self):
445 445 return False
446 446 return self._data == other._data
447 447
448 448 def precnode(self):
449 449 """Precursor changeset node identifier"""
450 450 return self._data[0]
451 451
452 452 def succnodes(self):
453 453 """List of successor changesets node identifiers"""
454 454 return self._data[1]
455 455
456 456 def parentnodes(self):
457 457 """Parents of the precursors (None if not recorded)"""
458 458 return self._data[5]
459 459
460 460 def metadata(self):
461 461 """Decoded metadata dictionary"""
462 462 return dict(self._data[3])
463 463
464 464 def date(self):
465 465 """Creation date as (unixtime, offset)"""
466 466 return self._data[4]
467 467
468 468 def flags(self):
469 469 """The flags field of the marker"""
470 470 return self._data[2]
471 471
472 472 @util.nogc
473 473 def _addsuccessors(successors, markers):
474 474 for mark in markers:
475 475 successors.setdefault(mark[0], set()).add(mark)
476 476
477 477 @util.nogc
478 478 def _addprecursors(precursors, markers):
479 479 for mark in markers:
480 480 for suc in mark[1]:
481 481 precursors.setdefault(suc, set()).add(mark)
482 482
483 483 @util.nogc
484 484 def _addchildren(children, markers):
485 485 for mark in markers:
486 486 parents = mark[5]
487 487 if parents is not None:
488 488 for p in parents:
489 489 children.setdefault(p, set()).add(mark)
490 490
491 491 def _checkinvalidmarkers(markers):
492 492 """search for marker with invalid data and raise error if needed
493 493
494 494 Exist as a separated function to allow the evolve extension for a more
495 495 subtle handling.
496 496 """
497 497 for mark in markers:
498 498 if node.nullid in mark[1]:
499 499 raise util.Abort(_('bad obsolescence marker detected: '
500 500 'invalid successors nullid'))
501 501
502 502 class obsstore(object):
503 503 """Store obsolete markers
504 504
505 505 Markers can be accessed with two mappings:
506 506 - precursors[x] -> set(markers on precursors edges of x)
507 507 - successors[x] -> set(markers on successors edges of x)
508 508 - children[x] -> set(markers on precursors edges of children(x)
509 509 """
510 510
511 511 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
512 512 # prec: nodeid, precursor changesets
513 513 # succs: tuple of nodeid, successor changesets (0-N length)
514 514 # flag: integer, flag field carrying modifier for the markers (see doc)
515 515 # meta: binary blob, encoded metadata dictionary
516 516 # date: (float, int) tuple, date of marker creation
517 517 # parents: (tuple of nodeid) or None, parents of precursors
518 518 # None is used when no data has been recorded
519 519
520 520 def __init__(self, sopener, defaultformat=_fm1version, readonly=False):
521 521 # caches for various obsolescence related cache
522 522 self.caches = {}
523 523 self._all = []
524 524 self.sopener = sopener
525 525 data = sopener.tryread('obsstore')
526 526 self._version = defaultformat
527 527 self._readonly = readonly
528 528 if data:
529 529 self._version, markers = _readmarkers(data)
530 530 self._addmarkers(markers)
531 531
532 532 def __iter__(self):
533 533 return iter(self._all)
534 534
535 535 def __len__(self):
536 536 return len(self._all)
537 537
538 538 def __nonzero__(self):
539 539 return bool(self._all)
540 540
541 541 def create(self, transaction, prec, succs=(), flag=0, parents=None,
542 542 date=None, metadata=None):
543 543 """obsolete: add a new obsolete marker
544 544
545 545 * ensuring it is hashable
546 546 * check mandatory metadata
547 547 * encode metadata
548 548
549 549 If you are a human writing code creating marker you want to use the
550 550 `createmarkers` function in this module instead.
551 551
552 552 return True if a new marker have been added, False if the markers
553 553 already existed (no op).
554 554 """
555 555 if metadata is None:
556 556 metadata = {}
557 557 if date is None:
558 558 if 'date' in metadata:
559 559 # as a courtesy for out-of-tree extensions
560 560 date = util.parsedate(metadata.pop('date'))
561 561 else:
562 562 date = util.makedate()
563 563 if len(prec) != 20:
564 564 raise ValueError(prec)
565 565 for succ in succs:
566 566 if len(succ) != 20:
567 567 raise ValueError(succ)
568 568 if prec in succs:
569 569 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
570 570
571 571 metadata = tuple(sorted(metadata.iteritems()))
572 572
573 573 marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
574 574 return bool(self.add(transaction, [marker]))
575 575
576 576 def add(self, transaction, markers):
577 577 """Add new markers to the store
578 578
579 579 Take care of filtering duplicate.
580 580 Return the number of new marker."""
581 581 if self._readonly:
582 582 raise util.Abort('creating obsolete markers is not enabled on this '
583 583 'repo')
584 584 known = set(self._all)
585 585 new = []
586 586 for m in markers:
587 587 if m not in known:
588 588 known.add(m)
589 589 new.append(m)
590 590 if new:
591 591 f = self.sopener('obsstore', 'ab')
592 592 try:
593 593 offset = f.tell()
594 594 transaction.add('obsstore', offset)
595 595 # offset == 0: new file - add the version header
596 596 for bytes in encodemarkers(new, offset == 0, self._version):
597 597 f.write(bytes)
598 598 finally:
599 599 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
600 600 # call 'filecacheentry.refresh()' here
601 601 f.close()
602 602 self._addmarkers(new)
603 603 # new marker *may* have changed several set. invalidate the cache.
604 604 self.caches.clear()
605 605 # records the number of new markers for the transaction hooks
606 606 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
607 607 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
608 608 return len(new)
609 609
610 610 def mergemarkers(self, transaction, data):
611 611 """merge a binary stream of markers inside the obsstore
612 612
613 613 Returns the number of new markers added."""
614 614 version, markers = _readmarkers(data)
615 615 return self.add(transaction, markers)
616 616
617 617 @propertycache
618 618 def successors(self):
619 619 successors = {}
620 620 _addsuccessors(successors, self._all)
621 621 return successors
622 622
623 623 @propertycache
624 624 def precursors(self):
625 625 precursors = {}
626 626 _addprecursors(precursors, self._all)
627 627 return precursors
628 628
629 629 @propertycache
630 630 def children(self):
631 631 children = {}
632 632 _addchildren(children, self._all)
633 633 return children
634 634
635 635 def _cached(self, attr):
636 636 return attr in self.__dict__
637 637
638 638 def _addmarkers(self, markers):
639 639 markers = list(markers) # to allow repeated iteration
640 640 self._all.extend(markers)
641 641 if self._cached('successors'):
642 642 _addsuccessors(self.successors, markers)
643 643 if self._cached('precursors'):
644 644 _addprecursors(self.precursors, markers)
645 645 if self._cached('children'):
646 646 _addchildren(self.children, markers)
647 647 _checkinvalidmarkers(markers)
648 648
649 649 def relevantmarkers(self, nodes):
650 650 """return a set of all obsolescence markers relevant to a set of nodes.
651 651
652 652 "relevant" to a set of nodes mean:
653 653
654 654 - marker that use this changeset as successor
655 655 - prune marker of direct children on this changeset
656 656 - recursive application of the two rules on precursors of these markers
657 657
658 658 It is a set so you cannot rely on order."""
659 659
660 660 pendingnodes = set(nodes)
661 661 seenmarkers = set()
662 662 seennodes = set(pendingnodes)
663 663 precursorsmarkers = self.precursors
664 664 children = self.children
665 665 while pendingnodes:
666 666 direct = set()
667 667 for current in pendingnodes:
668 668 direct.update(precursorsmarkers.get(current, ()))
669 669 pruned = [m for m in children.get(current, ()) if not m[1]]
670 670 direct.update(pruned)
671 671 direct -= seenmarkers
672 672 pendingnodes = set([m[0] for m in direct])
673 673 seenmarkers |= direct
674 674 pendingnodes -= seennodes
675 675 seennodes |= pendingnodes
676 676 return seenmarkers
677 677
678 678 def commonversion(versions):
679 679 """Return the newest version listed in both versions and our local formats.
680 680
681 681 Returns None if no common version exists.
682 682 """
683 683 versions.sort(reverse=True)
684 684 # search for highest version known on both side
685 685 for v in versions:
686 686 if v in formats:
687 687 return v
688 688 return None
689 689
690 690 # arbitrary picked to fit into 8K limit from HTTP server
691 691 # you have to take in account:
692 692 # - the version header
693 693 # - the base85 encoding
694 694 _maxpayload = 5300
695 695
696 696 def _pushkeyescape(markers):
697 697 """encode markers into a dict suitable for pushkey exchange
698 698
699 699 - binary data is base85 encoded
700 700 - split in chunks smaller than 5300 bytes"""
701 701 keys = {}
702 702 parts = []
703 703 currentlen = _maxpayload * 2 # ensure we create a new part
704 704 for marker in markers:
705 705 nextdata = _fm0encodeonemarker(marker)
706 706 if (len(nextdata) + currentlen > _maxpayload):
707 707 currentpart = []
708 708 currentlen = 0
709 709 parts.append(currentpart)
710 710 currentpart.append(nextdata)
711 711 currentlen += len(nextdata)
712 712 for idx, part in enumerate(reversed(parts)):
713 713 data = ''.join([_pack('>B', _fm0version)] + part)
714 714 keys['dump%i' % idx] = base85.b85encode(data)
715 715 return keys
716 716
717 717 def listmarkers(repo):
718 718 """List markers over pushkey"""
719 719 if not repo.obsstore:
720 720 return {}
721 721 return _pushkeyescape(sorted(repo.obsstore))
722 722
723 723 def pushmarker(repo, key, old, new):
724 724 """Push markers over pushkey"""
725 725 if not key.startswith('dump'):
726 726 repo.ui.warn(_('unknown key: %r') % key)
727 727 return 0
728 728 if old:
729 729 repo.ui.warn(_('unexpected old value for %r') % key)
730 730 return 0
731 731 data = base85.b85decode(new)
732 732 lock = repo.lock()
733 733 try:
734 734 tr = repo.transaction('pushkey: obsolete markers')
735 735 try:
736 736 repo.obsstore.mergemarkers(tr, data)
737 737 tr.close()
738 738 return 1
739 739 finally:
740 740 tr.release()
741 741 finally:
742 742 lock.release()
743 743
744 744 def getmarkers(repo, nodes=None):
745 745 """returns markers known in a repository
746 746
747 747 If <nodes> is specified, only markers "relevant" to those nodes are are
748 748 returned"""
749 749 if nodes is None:
750 750 rawmarkers = repo.obsstore
751 751 else:
752 752 rawmarkers = repo.obsstore.relevantmarkers(nodes)
753 753
754 754 for markerdata in rawmarkers:
755 755 yield marker(repo, markerdata)
756 756
757 757 def relevantmarkers(repo, node):
758 758 """all obsolete markers relevant to some revision"""
759 759 for markerdata in repo.obsstore.relevantmarkers(node):
760 760 yield marker(repo, markerdata)
761 761
762 762
763 763 def precursormarkers(ctx):
764 764 """obsolete marker marking this changeset as a successors"""
765 765 for data in ctx.repo().obsstore.precursors.get(ctx.node(), ()):
766 766 yield marker(ctx.repo(), data)
767 767
768 768 def successormarkers(ctx):
769 769 """obsolete marker making this changeset obsolete"""
770 770 for data in ctx.repo().obsstore.successors.get(ctx.node(), ()):
771 771 yield marker(ctx.repo(), data)
772 772
773 773 def allsuccessors(obsstore, nodes, ignoreflags=0):
774 774 """Yield node for every successor of <nodes>.
775 775
776 776 Some successors may be unknown locally.
777 777
778 778 This is a linear yield unsuited to detecting split changesets. It includes
779 779 initial nodes too."""
780 780 remaining = set(nodes)
781 781 seen = set(remaining)
782 782 while remaining:
783 783 current = remaining.pop()
784 784 yield current
785 785 for mark in obsstore.successors.get(current, ()):
786 786 # ignore marker flagged with specified flag
787 787 if mark[2] & ignoreflags:
788 788 continue
789 789 for suc in mark[1]:
790 790 if suc not in seen:
791 791 seen.add(suc)
792 792 remaining.add(suc)
793 793
794 794 def allprecursors(obsstore, nodes, ignoreflags=0):
795 795 """Yield node for every precursors of <nodes>.
796 796
797 797 Some precursors may be unknown locally.
798 798
799 799 This is a linear yield unsuited to detecting folded changesets. It includes
800 800 initial nodes too."""
801 801
802 802 remaining = set(nodes)
803 803 seen = set(remaining)
804 804 while remaining:
805 805 current = remaining.pop()
806 806 yield current
807 807 for mark in obsstore.precursors.get(current, ()):
808 808 # ignore marker flagged with specified flag
809 809 if mark[2] & ignoreflags:
810 810 continue
811 811 suc = mark[0]
812 812 if suc not in seen:
813 813 seen.add(suc)
814 814 remaining.add(suc)
815 815
816 816 def foreground(repo, nodes):
817 817 """return all nodes in the "foreground" of other node
818 818
819 819 The foreground of a revision is anything reachable using parent -> children
820 820 or precursor -> successor relation. It is very similar to "descendant" but
821 821 augmented with obsolescence information.
822 822
823 823 Beware that possible obsolescence cycle may result if complex situation.
824 824 """
825 825 repo = repo.unfiltered()
826 826 foreground = set(repo.set('%ln::', nodes))
827 827 if repo.obsstore:
828 828 # We only need this complicated logic if there is obsolescence
829 829 # XXX will probably deserve an optimised revset.
830 830 nm = repo.changelog.nodemap
831 831 plen = -1
832 832 # compute the whole set of successors or descendants
833 833 while len(foreground) != plen:
834 834 plen = len(foreground)
835 835 succs = set(c.node() for c in foreground)
836 836 mutable = [c.node() for c in foreground if c.mutable()]
837 837 succs.update(allsuccessors(repo.obsstore, mutable))
838 838 known = (n for n in succs if n in nm)
839 839 foreground = set(repo.set('%ln::', known))
840 840 return set(c.node() for c in foreground)
841 841
842 842
843 843 def successorssets(repo, initialnode, cache=None):
844 844 """Return all set of successors of initial nodes
845 845
846 846 The successors set of a changeset A are a group of revisions that succeed
847 847 A. It succeeds A as a consistent whole, each revision being only a partial
848 848 replacement. The successors set contains non-obsolete changesets only.
849 849
850 850 This function returns the full list of successor sets which is why it
851 851 returns a list of tuples and not just a single tuple. Each tuple is a valid
852 852 successors set. Not that (A,) may be a valid successors set for changeset A
853 853 (see below).
854 854
855 855 In most cases, a changeset A will have a single element (e.g. the changeset
856 856 A is replaced by A') in its successors set. Though, it is also common for a
857 857 changeset A to have no elements in its successor set (e.g. the changeset
858 858 has been pruned). Therefore, the returned list of successors sets will be
859 859 [(A',)] or [], respectively.
860 860
861 861 When a changeset A is split into A' and B', however, it will result in a
862 862 successors set containing more than a single element, i.e. [(A',B')].
863 863 Divergent changesets will result in multiple successors sets, i.e. [(A',),
864 864 (A'')].
865 865
866 866 If a changeset A is not obsolete, then it will conceptually have no
867 867 successors set. To distinguish this from a pruned changeset, the successor
868 868 set will only contain itself, i.e. [(A,)].
869 869
870 870 Finally, successors unknown locally are considered to be pruned (obsoleted
871 871 without any successors).
872 872
873 873 The optional `cache` parameter is a dictionary that may contain precomputed
874 874 successors sets. It is meant to reuse the computation of a previous call to
875 875 `successorssets` when multiple calls are made at the same time. The cache
876 876 dictionary is updated in place. The caller is responsible for its live
877 877 spawn. Code that makes multiple calls to `successorssets` *must* use this
878 878 cache mechanism or suffer terrible performances.
879 879
880 880 """
881 881
882 882 succmarkers = repo.obsstore.successors
883 883
884 884 # Stack of nodes we search successors sets for
885 885 toproceed = [initialnode]
886 886 # set version of above list for fast loop detection
887 887 # element added to "toproceed" must be added here
888 888 stackedset = set(toproceed)
889 889 if cache is None:
890 890 cache = {}
891 891
892 892 # This while loop is the flattened version of a recursive search for
893 893 # successors sets
894 894 #
895 895 # def successorssets(x):
896 896 # successors = directsuccessors(x)
897 897 # ss = [[]]
898 898 # for succ in directsuccessors(x):
899 899 # # product as in itertools cartesian product
900 900 # ss = product(ss, successorssets(succ))
901 901 # return ss
902 902 #
903 903 # But we can not use plain recursive calls here:
904 904 # - that would blow the python call stack
905 905 # - obsolescence markers may have cycles, we need to handle them.
906 906 #
907 907 # The `toproceed` list act as our call stack. Every node we search
908 908 # successors set for are stacked there.
909 909 #
910 910 # The `stackedset` is set version of this stack used to check if a node is
911 911 # already stacked. This check is used to detect cycles and prevent infinite
912 912 # loop.
913 913 #
914 914 # successors set of all nodes are stored in the `cache` dictionary.
915 915 #
916 916 # After this while loop ends we use the cache to return the successors sets
917 917 # for the node requested by the caller.
918 918 while toproceed:
919 919 # Every iteration tries to compute the successors sets of the topmost
920 920 # node of the stack: CURRENT.
921 921 #
922 922 # There are four possible outcomes:
923 923 #
924 924 # 1) We already know the successors sets of CURRENT:
925 925 # -> mission accomplished, pop it from the stack.
926 926 # 2) Node is not obsolete:
927 927 # -> the node is its own successors sets. Add it to the cache.
928 928 # 3) We do not know successors set of direct successors of CURRENT:
929 929 # -> We add those successors to the stack.
930 930 # 4) We know successors sets of all direct successors of CURRENT:
931 931 # -> We can compute CURRENT successors set and add it to the
932 932 # cache.
933 933 #
934 934 current = toproceed[-1]
935 935 if current in cache:
936 936 # case (1): We already know the successors sets
937 937 stackedset.remove(toproceed.pop())
938 938 elif current not in succmarkers:
939 939 # case (2): The node is not obsolete.
940 940 if current in repo:
941 941 # We have a valid last successors.
942 942 cache[current] = [(current,)]
943 943 else:
944 944 # Final obsolete version is unknown locally.
945 945 # Do not count that as a valid successors
946 946 cache[current] = []
947 947 else:
948 948 # cases (3) and (4)
949 949 #
950 950 # We proceed in two phases. Phase 1 aims to distinguish case (3)
951 951 # from case (4):
952 952 #
953 953 # For each direct successors of CURRENT, we check whether its
954 954 # successors sets are known. If they are not, we stack the
955 955 # unknown node and proceed to the next iteration of the while
956 956 # loop. (case 3)
957 957 #
958 958 # During this step, we may detect obsolescence cycles: a node
959 959 # with unknown successors sets but already in the call stack.
960 960 # In such a situation, we arbitrary set the successors sets of
961 961 # the node to nothing (node pruned) to break the cycle.
962 962 #
963 963 # If no break was encountered we proceed to phase 2.
964 964 #
965 965 # Phase 2 computes successors sets of CURRENT (case 4); see details
966 966 # in phase 2 itself.
967 967 #
968 968 # Note the two levels of iteration in each phase.
969 969 # - The first one handles obsolescence markers using CURRENT as
970 970 # precursor (successors markers of CURRENT).
971 971 #
972 972 # Having multiple entry here means divergence.
973 973 #
974 974 # - The second one handles successors defined in each marker.
975 975 #
976 976 # Having none means pruned node, multiple successors means split,
977 977 # single successors are standard replacement.
978 978 #
979 979 for mark in sorted(succmarkers[current]):
980 980 for suc in mark[1]:
981 981 if suc not in cache:
982 982 if suc in stackedset:
983 983 # cycle breaking
984 984 cache[suc] = []
985 985 else:
986 986 # case (3) If we have not computed successors sets
987 987 # of one of those successors we add it to the
988 988 # `toproceed` stack and stop all work for this
989 989 # iteration.
990 990 toproceed.append(suc)
991 991 stackedset.add(suc)
992 992 break
993 993 else:
994 994 continue
995 995 break
996 996 else:
997 997 # case (4): we know all successors sets of all direct
998 998 # successors
999 999 #
1000 1000 # Successors set contributed by each marker depends on the
1001 1001 # successors sets of all its "successors" node.
1002 1002 #
1003 1003 # Each different marker is a divergence in the obsolescence
1004 1004 # history. It contributes successors sets distinct from other
1005 1005 # markers.
1006 1006 #
1007 1007 # Within a marker, a successor may have divergent successors
1008 1008 # sets. In such a case, the marker will contribute multiple
1009 1009 # divergent successors sets. If multiple successors have
1010 1010 # divergent successors sets, a Cartesian product is used.
1011 1011 #
1012 1012 # At the end we post-process successors sets to remove
1013 1013 # duplicated entry and successors set that are strict subset of
1014 1014 # another one.
1015 1015 succssets = []
1016 1016 for mark in sorted(succmarkers[current]):
1017 1017 # successors sets contributed by this marker
1018 1018 markss = [[]]
1019 1019 for suc in mark[1]:
1020 1020 # cardinal product with previous successors
1021 1021 productresult = []
1022 1022 for prefix in markss:
1023 1023 for suffix in cache[suc]:
1024 1024 newss = list(prefix)
1025 1025 for part in suffix:
1026 1026 # do not duplicated entry in successors set
1027 1027 # first entry wins.
1028 1028 if part not in newss:
1029 1029 newss.append(part)
1030 1030 productresult.append(newss)
1031 1031 markss = productresult
1032 1032 succssets.extend(markss)
1033 1033 # remove duplicated and subset
1034 1034 seen = []
1035 1035 final = []
1036 1036 candidate = sorted(((set(s), s) for s in succssets if s),
1037 1037 key=lambda x: len(x[1]), reverse=True)
1038 1038 for setversion, listversion in candidate:
1039 1039 for seenset in seen:
1040 1040 if setversion.issubset(seenset):
1041 1041 break
1042 1042 else:
1043 1043 final.append(listversion)
1044 1044 seen.append(setversion)
1045 1045 final.reverse() # put small successors set first
1046 1046 cache[current] = final
1047 1047 return cache[initialnode]
1048 1048
1049 1049 def _knownrevs(repo, nodes):
1050 1050 """yield revision numbers of known nodes passed in parameters
1051 1051
1052 1052 Unknown revisions are silently ignored."""
1053 1053 torev = repo.changelog.nodemap.get
1054 1054 for n in nodes:
1055 1055 rev = torev(n)
1056 1056 if rev is not None:
1057 1057 yield rev
1058 1058
1059 1059 # mapping of 'set-name' -> <function to compute this set>
1060 1060 cachefuncs = {}
1061 1061 def cachefor(name):
1062 1062 """Decorator to register a function as computing the cache for a set"""
1063 1063 def decorator(func):
1064 1064 assert name not in cachefuncs
1065 1065 cachefuncs[name] = func
1066 1066 return func
1067 1067 return decorator
1068 1068
1069 1069 def getrevs(repo, name):
1070 1070 """Return the set of revision that belong to the <name> set
1071 1071
1072 1072 Such access may compute the set and cache it for future use"""
1073 1073 repo = repo.unfiltered()
1074 1074 if not repo.obsstore:
1075 1075 return frozenset()
1076 1076 if name not in repo.obsstore.caches:
1077 1077 repo.obsstore.caches[name] = cachefuncs[name](repo)
1078 1078 return repo.obsstore.caches[name]
1079 1079
1080 1080 # To be simple we need to invalidate obsolescence cache when:
1081 1081 #
1082 1082 # - new changeset is added:
1083 1083 # - public phase is changed
1084 1084 # - obsolescence marker are added
1085 1085 # - strip is used a repo
1086 1086 def clearobscaches(repo):
1087 1087 """Remove all obsolescence related cache from a repo
1088 1088
1089 1089 This remove all cache in obsstore is the obsstore already exist on the
1090 1090 repo.
1091 1091
1092 1092 (We could be smarter here given the exact event that trigger the cache
1093 1093 clearing)"""
1094 1094 # only clear cache is there is obsstore data in this repo
1095 1095 if 'obsstore' in repo._filecache:
1096 1096 repo.obsstore.caches.clear()
1097 1097
1098 1098 @cachefor('obsolete')
1099 1099 def _computeobsoleteset(repo):
1100 1100 """the set of obsolete revisions"""
1101 1101 obs = set()
1102 1102 getrev = repo.changelog.nodemap.get
1103 1103 getphase = repo._phasecache.phase
1104 1104 for n in repo.obsstore.successors:
1105 1105 rev = getrev(n)
1106 1106 if rev is not None and getphase(repo, rev):
1107 1107 obs.add(rev)
1108 1108 return obs
1109 1109
1110 1110 @cachefor('unstable')
1111 1111 def _computeunstableset(repo):
1112 1112 """the set of non obsolete revisions with obsolete parents"""
1113 1113 revs = [(ctx.rev(), ctx) for ctx in
1114 1114 repo.set('(not public()) and (not obsolete())')]
1115 1115 revs.sort(key=lambda x:x[0])
1116 1116 unstable = set()
1117 1117 for rev, ctx in revs:
1118 1118 # A rev is unstable if one of its parent is obsolete or unstable
1119 1119 # this works since we traverse following growing rev order
1120 1120 if any((x.obsolete() or (x.rev() in unstable))
1121 1121 for x in ctx.parents()):
1122 1122 unstable.add(rev)
1123 1123 return unstable
1124 1124
1125 1125 @cachefor('suspended')
1126 1126 def _computesuspendedset(repo):
1127 1127 """the set of obsolete parents with non obsolete descendants"""
1128 1128 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
1129 1129 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
1130 1130
1131 1131 @cachefor('extinct')
1132 1132 def _computeextinctset(repo):
1133 1133 """the set of obsolete parents without non obsolete descendants"""
1134 1134 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
1135 1135
1136 1136
1137 1137 @cachefor('bumped')
1138 1138 def _computebumpedset(repo):
1139 1139 """the set of revs trying to obsolete public revisions"""
1140 1140 bumped = set()
1141 1141 # util function (avoid attribute lookup in the loop)
1142 1142 phase = repo._phasecache.phase # would be faster to grab the full list
1143 1143 public = phases.public
1144 1144 cl = repo.changelog
1145 1145 torev = cl.nodemap.get
1146 1146 for ctx in repo.set('(not public()) and (not obsolete())'):
1147 1147 rev = ctx.rev()
1148 1148 # We only evaluate mutable, non-obsolete revision
1149 1149 node = ctx.node()
1150 1150 # (future) A cache of precursors may worth if split is very common
1151 1151 for pnode in allprecursors(repo.obsstore, [node],
1152 1152 ignoreflags=bumpedfix):
1153 1153 prev = torev(pnode) # unfiltered! but so is phasecache
1154 1154 if (prev is not None) and (phase(repo, prev) <= public):
1155 1155 # we have a public precursors
1156 1156 bumped.add(rev)
1157 1157 break # Next draft!
1158 1158 return bumped
1159 1159
1160 1160 @cachefor('divergent')
1161 1161 def _computedivergentset(repo):
1162 1162 """the set of rev that compete to be the final successors of some revision.
1163 1163 """
1164 1164 divergent = set()
1165 1165 obsstore = repo.obsstore
1166 1166 newermap = {}
1167 1167 for ctx in repo.set('(not public()) - obsolete()'):
1168 1168 mark = obsstore.precursors.get(ctx.node(), ())
1169 1169 toprocess = set(mark)
1170 1170 seen = set()
1171 1171 while toprocess:
1172 1172 prec = toprocess.pop()[0]
1173 1173 if prec in seen:
1174 1174 continue # emergency cycle hanging prevention
1175 1175 seen.add(prec)
1176 1176 if prec not in newermap:
1177 1177 successorssets(repo, prec, newermap)
1178 1178 newer = [n for n in newermap[prec] if n]
1179 1179 if len(newer) > 1:
1180 1180 divergent.add(ctx.rev())
1181 1181 break
1182 1182 toprocess.update(obsstore.precursors.get(prec, ()))
1183 1183 return divergent
1184 1184
1185 1185
1186 1186 def createmarkers(repo, relations, flag=0, date=None, metadata=None):
1187 1187 """Add obsolete markers between changesets in a repo
1188 1188
1189 1189 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1190 1190 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1191 1191 containing metadata for this marker only. It is merged with the global
1192 1192 metadata specified through the `metadata` argument of this function,
1193 1193
1194 1194 Trying to obsolete a public changeset will raise an exception.
1195 1195
1196 1196 Current user and date are used except if specified otherwise in the
1197 1197 metadata attribute.
1198 1198
1199 1199 This function operates within a transaction of its own, but does
1200 1200 not take any lock on the repo.
1201 1201 """
1202 1202 # prepare metadata
1203 1203 if metadata is None:
1204 1204 metadata = {}
1205 1205 if 'user' not in metadata:
1206 1206 metadata['user'] = repo.ui.username()
1207 1207 tr = repo.transaction('add-obsolescence-marker')
1208 1208 try:
1209 1209 for rel in relations:
1210 1210 prec = rel[0]
1211 1211 sucs = rel[1]
1212 1212 localmetadata = metadata.copy()
1213 1213 if 2 < len(rel):
1214 1214 localmetadata.update(rel[2])
1215 1215
1216 1216 if not prec.mutable():
1217 1217 raise util.Abort("cannot obsolete immutable changeset: %s"
1218 1218 % prec)
1219 1219 nprec = prec.node()
1220 1220 nsucs = tuple(s.node() for s in sucs)
1221 1221 npare = None
1222 1222 if not nsucs:
1223 1223 npare = tuple(p.node() for p in prec.parents())
1224 1224 if nprec in nsucs:
1225 1225 raise util.Abort("changeset %s cannot obsolete itself" % prec)
1226 1226 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1227 1227 date=date, metadata=localmetadata)
1228 1228 repo.filteredrevcache.clear()
1229 1229 tr.close()
1230 1230 finally:
1231 1231 tr.release()
1232 1232
1233 1233 def isenabled(repo, option):
1234 1234 """Returns True if the given repository has the given obsolete option
1235 1235 enabled.
1236 1236 """
1237 1237 result = set(repo.ui.configlist('experimental', 'evolution'))
1238 1238 if 'all' in result:
1239 1239 return True
1240 1240
1241 1241 # For migration purposes, temporarily return true if the config hasn't been
1242 1242 # set but _enabled is true.
1243 1243 if len(result) == 0 and _enabled:
1244 1244 return True
1245 1245
1246 1246 # createmarkers must be enabled if other options are enabled
1247 1247 if ((allowunstableopt in result or exchangeopt in result) and
1248 1248 not createmarkersopt in result):
1249 1249 raise util.Abort(_("'createmarkers' obsolete option must be enabled "
1250 1250 "if other obsolete options are enabled"))
1251 1251
1252 1252 return option in result
@@ -1,2249 +1,2245 b''
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 import i18n
17 17 _ = i18n._
18 18 import error, osutil, encoding, parsers
19 19 import errno, shutil, sys, tempfile, traceback
20 20 import re as remod
21 21 import os, time, datetime, calendar, textwrap, signal, collections
22 import imp, socket, urllib, struct
22 import imp, socket, urllib
23 23 import gc
24 24
25 25 if os.name == 'nt':
26 26 import windows as platform
27 27 else:
28 28 import posix as platform
29 29
30 30 cachestat = platform.cachestat
31 31 checkexec = platform.checkexec
32 32 checklink = platform.checklink
33 33 copymode = platform.copymode
34 34 executablepath = platform.executablepath
35 35 expandglobs = platform.expandglobs
36 36 explainexit = platform.explainexit
37 37 findexe = platform.findexe
38 38 gethgcmd = platform.gethgcmd
39 39 getuser = platform.getuser
40 40 groupmembers = platform.groupmembers
41 41 groupname = platform.groupname
42 42 hidewindow = platform.hidewindow
43 43 isexec = platform.isexec
44 44 isowner = platform.isowner
45 45 localpath = platform.localpath
46 46 lookupreg = platform.lookupreg
47 47 makedir = platform.makedir
48 48 nlinks = platform.nlinks
49 49 normpath = platform.normpath
50 50 normcase = platform.normcase
51 51 normcasespec = platform.normcasespec
52 52 normcasefallback = platform.normcasefallback
53 53 openhardlinks = platform.openhardlinks
54 54 oslink = platform.oslink
55 55 parsepatchoutput = platform.parsepatchoutput
56 56 pconvert = platform.pconvert
57 57 popen = platform.popen
58 58 posixfile = platform.posixfile
59 59 quotecommand = platform.quotecommand
60 60 readpipe = platform.readpipe
61 61 rename = platform.rename
62 62 removedirs = platform.removedirs
63 63 samedevice = platform.samedevice
64 64 samefile = platform.samefile
65 65 samestat = platform.samestat
66 66 setbinary = platform.setbinary
67 67 setflags = platform.setflags
68 68 setsignalhandler = platform.setsignalhandler
69 69 shellquote = platform.shellquote
70 70 spawndetached = platform.spawndetached
71 71 split = platform.split
72 72 sshargs = platform.sshargs
73 73 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
74 74 statisexec = platform.statisexec
75 75 statislink = platform.statislink
76 76 termwidth = platform.termwidth
77 77 testpid = platform.testpid
78 78 umask = platform.umask
79 79 unlink = platform.unlink
80 80 unlinkpath = platform.unlinkpath
81 81 username = platform.username
82 82
83 83 # Python compatibility
84 84
85 85 _notset = object()
86 86
87 87 def safehasattr(thing, attr):
88 88 return getattr(thing, attr, _notset) is not _notset
89 89
90 90 def sha1(s=''):
91 91 '''
92 92 Low-overhead wrapper around Python's SHA support
93 93
94 94 >>> f = _fastsha1
95 95 >>> a = sha1()
96 96 >>> a = f()
97 97 >>> a.hexdigest()
98 98 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
99 99 '''
100 100
101 101 return _fastsha1(s)
102 102
103 103 def _fastsha1(s=''):
104 104 # This function will import sha1 from hashlib or sha (whichever is
105 105 # available) and overwrite itself with it on the first call.
106 106 # Subsequent calls will go directly to the imported function.
107 107 if sys.version_info >= (2, 5):
108 108 from hashlib import sha1 as _sha1
109 109 else:
110 110 from sha import sha as _sha1
111 111 global _fastsha1, sha1
112 112 _fastsha1 = sha1 = _sha1
113 113 return _sha1(s)
114 114
115 115 def md5(s=''):
116 116 try:
117 117 from hashlib import md5 as _md5
118 118 except ImportError:
119 119 from md5 import md5 as _md5
120 120 global md5
121 121 md5 = _md5
122 122 return _md5(s)
123 123
124 124 DIGESTS = {
125 125 'md5': md5,
126 126 'sha1': sha1,
127 127 }
128 128 # List of digest types from strongest to weakest
129 129 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
130 130
131 131 try:
132 132 import hashlib
133 133 DIGESTS.update({
134 134 'sha512': hashlib.sha512,
135 135 })
136 136 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
137 137 except ImportError:
138 138 pass
139 139
140 140 for k in DIGESTS_BY_STRENGTH:
141 141 assert k in DIGESTS
142 142
143 143 class digester(object):
144 144 """helper to compute digests.
145 145
146 146 This helper can be used to compute one or more digests given their name.
147 147
148 148 >>> d = digester(['md5', 'sha1'])
149 149 >>> d.update('foo')
150 150 >>> [k for k in sorted(d)]
151 151 ['md5', 'sha1']
152 152 >>> d['md5']
153 153 'acbd18db4cc2f85cedef654fccc4a4d8'
154 154 >>> d['sha1']
155 155 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
156 156 >>> digester.preferred(['md5', 'sha1'])
157 157 'sha1'
158 158 """
159 159
160 160 def __init__(self, digests, s=''):
161 161 self._hashes = {}
162 162 for k in digests:
163 163 if k not in DIGESTS:
164 164 raise Abort(_('unknown digest type: %s') % k)
165 165 self._hashes[k] = DIGESTS[k]()
166 166 if s:
167 167 self.update(s)
168 168
169 169 def update(self, data):
170 170 for h in self._hashes.values():
171 171 h.update(data)
172 172
173 173 def __getitem__(self, key):
174 174 if key not in DIGESTS:
175 175 raise Abort(_('unknown digest type: %s') % k)
176 176 return self._hashes[key].hexdigest()
177 177
178 178 def __iter__(self):
179 179 return iter(self._hashes)
180 180
181 181 @staticmethod
182 182 def preferred(supported):
183 183 """returns the strongest digest type in both supported and DIGESTS."""
184 184
185 185 for k in DIGESTS_BY_STRENGTH:
186 186 if k in supported:
187 187 return k
188 188 return None
189 189
190 190 class digestchecker(object):
191 191 """file handle wrapper that additionally checks content against a given
192 192 size and digests.
193 193
194 194 d = digestchecker(fh, size, {'md5': '...'})
195 195
196 196 When multiple digests are given, all of them are validated.
197 197 """
198 198
199 199 def __init__(self, fh, size, digests):
200 200 self._fh = fh
201 201 self._size = size
202 202 self._got = 0
203 203 self._digests = dict(digests)
204 204 self._digester = digester(self._digests.keys())
205 205
206 206 def read(self, length=-1):
207 207 content = self._fh.read(length)
208 208 self._digester.update(content)
209 209 self._got += len(content)
210 210 return content
211 211
212 212 def validate(self):
213 213 if self._size != self._got:
214 214 raise Abort(_('size mismatch: expected %d, got %d') %
215 215 (self._size, self._got))
216 216 for k, v in self._digests.items():
217 217 if v != self._digester[k]:
218 218 # i18n: first parameter is a digest name
219 219 raise Abort(_('%s mismatch: expected %s, got %s') %
220 220 (k, v, self._digester[k]))
221 221
222 222 try:
223 223 buffer = buffer
224 224 except NameError:
225 225 if sys.version_info[0] < 3:
226 226 def buffer(sliceable, offset=0):
227 227 return sliceable[offset:]
228 228 else:
229 229 def buffer(sliceable, offset=0):
230 230 return memoryview(sliceable)[offset:]
231 231
232 232 import subprocess
233 233 closefds = os.name == 'posix'
234 234
235 def unpacker(fmt):
236 """create a struct unpacker for the specified format"""
237 return struct.Struct(fmt).unpack
238
239 235 def popen2(cmd, env=None, newlines=False):
240 236 # Setting bufsize to -1 lets the system decide the buffer size.
241 237 # The default for bufsize is 0, meaning unbuffered. This leads to
242 238 # poor performance on Mac OS X: http://bugs.python.org/issue4194
243 239 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
244 240 close_fds=closefds,
245 241 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
246 242 universal_newlines=newlines,
247 243 env=env)
248 244 return p.stdin, p.stdout
249 245
250 246 def popen3(cmd, env=None, newlines=False):
251 247 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
252 248 return stdin, stdout, stderr
253 249
254 250 def popen4(cmd, env=None, newlines=False):
255 251 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
256 252 close_fds=closefds,
257 253 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
258 254 stderr=subprocess.PIPE,
259 255 universal_newlines=newlines,
260 256 env=env)
261 257 return p.stdin, p.stdout, p.stderr, p
262 258
263 259 def version():
264 260 """Return version information if available."""
265 261 try:
266 262 import __version__
267 263 return __version__.version
268 264 except ImportError:
269 265 return 'unknown'
270 266
271 267 # used by parsedate
272 268 defaultdateformats = (
273 269 '%Y-%m-%d %H:%M:%S',
274 270 '%Y-%m-%d %I:%M:%S%p',
275 271 '%Y-%m-%d %H:%M',
276 272 '%Y-%m-%d %I:%M%p',
277 273 '%Y-%m-%d',
278 274 '%m-%d',
279 275 '%m/%d',
280 276 '%m/%d/%y',
281 277 '%m/%d/%Y',
282 278 '%a %b %d %H:%M:%S %Y',
283 279 '%a %b %d %I:%M:%S%p %Y',
284 280 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
285 281 '%b %d %H:%M:%S %Y',
286 282 '%b %d %I:%M:%S%p %Y',
287 283 '%b %d %H:%M:%S',
288 284 '%b %d %I:%M:%S%p',
289 285 '%b %d %H:%M',
290 286 '%b %d %I:%M%p',
291 287 '%b %d %Y',
292 288 '%b %d',
293 289 '%H:%M:%S',
294 290 '%I:%M:%S%p',
295 291 '%H:%M',
296 292 '%I:%M%p',
297 293 )
298 294
299 295 extendeddateformats = defaultdateformats + (
300 296 "%Y",
301 297 "%Y-%m",
302 298 "%b",
303 299 "%b %Y",
304 300 )
305 301
306 302 def cachefunc(func):
307 303 '''cache the result of function calls'''
308 304 # XXX doesn't handle keywords args
309 305 if func.func_code.co_argcount == 0:
310 306 cache = []
311 307 def f():
312 308 if len(cache) == 0:
313 309 cache.append(func())
314 310 return cache[0]
315 311 return f
316 312 cache = {}
317 313 if func.func_code.co_argcount == 1:
318 314 # we gain a small amount of time because
319 315 # we don't need to pack/unpack the list
320 316 def f(arg):
321 317 if arg not in cache:
322 318 cache[arg] = func(arg)
323 319 return cache[arg]
324 320 else:
325 321 def f(*args):
326 322 if args not in cache:
327 323 cache[args] = func(*args)
328 324 return cache[args]
329 325
330 326 return f
331 327
332 328 class sortdict(dict):
333 329 '''a simple sorted dictionary'''
334 330 def __init__(self, data=None):
335 331 self._list = []
336 332 if data:
337 333 self.update(data)
338 334 def copy(self):
339 335 return sortdict(self)
340 336 def __setitem__(self, key, val):
341 337 if key in self:
342 338 self._list.remove(key)
343 339 self._list.append(key)
344 340 dict.__setitem__(self, key, val)
345 341 def __iter__(self):
346 342 return self._list.__iter__()
347 343 def update(self, src):
348 344 if isinstance(src, dict):
349 345 src = src.iteritems()
350 346 for k, v in src:
351 347 self[k] = v
352 348 def clear(self):
353 349 dict.clear(self)
354 350 self._list = []
355 351 def items(self):
356 352 return [(k, self[k]) for k in self._list]
357 353 def __delitem__(self, key):
358 354 dict.__delitem__(self, key)
359 355 self._list.remove(key)
360 356 def pop(self, key, *args, **kwargs):
361 357 dict.pop(self, key, *args, **kwargs)
362 358 try:
363 359 self._list.remove(key)
364 360 except ValueError:
365 361 pass
366 362 def keys(self):
367 363 return self._list
368 364 def iterkeys(self):
369 365 return self._list.__iter__()
370 366 def iteritems(self):
371 367 for k in self._list:
372 368 yield k, self[k]
373 369 def insert(self, index, key, val):
374 370 self._list.insert(index, key)
375 371 dict.__setitem__(self, key, val)
376 372
377 373 class lrucachedict(object):
378 374 '''cache most recent gets from or sets to this dictionary'''
379 375 def __init__(self, maxsize):
380 376 self._cache = {}
381 377 self._maxsize = maxsize
382 378 self._order = collections.deque()
383 379
384 380 def __getitem__(self, key):
385 381 value = self._cache[key]
386 382 self._order.remove(key)
387 383 self._order.append(key)
388 384 return value
389 385
390 386 def __setitem__(self, key, value):
391 387 if key not in self._cache:
392 388 if len(self._cache) >= self._maxsize:
393 389 del self._cache[self._order.popleft()]
394 390 else:
395 391 self._order.remove(key)
396 392 self._cache[key] = value
397 393 self._order.append(key)
398 394
399 395 def __contains__(self, key):
400 396 return key in self._cache
401 397
402 398 def clear(self):
403 399 self._cache.clear()
404 400 self._order = collections.deque()
405 401
406 402 def lrucachefunc(func):
407 403 '''cache most recent results of function calls'''
408 404 cache = {}
409 405 order = collections.deque()
410 406 if func.func_code.co_argcount == 1:
411 407 def f(arg):
412 408 if arg not in cache:
413 409 if len(cache) > 20:
414 410 del cache[order.popleft()]
415 411 cache[arg] = func(arg)
416 412 else:
417 413 order.remove(arg)
418 414 order.append(arg)
419 415 return cache[arg]
420 416 else:
421 417 def f(*args):
422 418 if args not in cache:
423 419 if len(cache) > 20:
424 420 del cache[order.popleft()]
425 421 cache[args] = func(*args)
426 422 else:
427 423 order.remove(args)
428 424 order.append(args)
429 425 return cache[args]
430 426
431 427 return f
432 428
433 429 class propertycache(object):
434 430 def __init__(self, func):
435 431 self.func = func
436 432 self.name = func.__name__
437 433 def __get__(self, obj, type=None):
438 434 result = self.func(obj)
439 435 self.cachevalue(obj, result)
440 436 return result
441 437
442 438 def cachevalue(self, obj, value):
443 439 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
444 440 obj.__dict__[self.name] = value
445 441
446 442 def pipefilter(s, cmd):
447 443 '''filter string S through command CMD, returning its output'''
448 444 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
449 445 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
450 446 pout, perr = p.communicate(s)
451 447 return pout
452 448
453 449 def tempfilter(s, cmd):
454 450 '''filter string S through a pair of temporary files with CMD.
455 451 CMD is used as a template to create the real command to be run,
456 452 with the strings INFILE and OUTFILE replaced by the real names of
457 453 the temporary files generated.'''
458 454 inname, outname = None, None
459 455 try:
460 456 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
461 457 fp = os.fdopen(infd, 'wb')
462 458 fp.write(s)
463 459 fp.close()
464 460 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
465 461 os.close(outfd)
466 462 cmd = cmd.replace('INFILE', inname)
467 463 cmd = cmd.replace('OUTFILE', outname)
468 464 code = os.system(cmd)
469 465 if sys.platform == 'OpenVMS' and code & 1:
470 466 code = 0
471 467 if code:
472 468 raise Abort(_("command '%s' failed: %s") %
473 469 (cmd, explainexit(code)))
474 470 fp = open(outname, 'rb')
475 471 r = fp.read()
476 472 fp.close()
477 473 return r
478 474 finally:
479 475 try:
480 476 if inname:
481 477 os.unlink(inname)
482 478 except OSError:
483 479 pass
484 480 try:
485 481 if outname:
486 482 os.unlink(outname)
487 483 except OSError:
488 484 pass
489 485
490 486 filtertable = {
491 487 'tempfile:': tempfilter,
492 488 'pipe:': pipefilter,
493 489 }
494 490
495 491 def filter(s, cmd):
496 492 "filter a string through a command that transforms its input to its output"
497 493 for name, fn in filtertable.iteritems():
498 494 if cmd.startswith(name):
499 495 return fn(s, cmd[len(name):].lstrip())
500 496 return pipefilter(s, cmd)
501 497
502 498 def binary(s):
503 499 """return true if a string is binary data"""
504 500 return bool(s and '\0' in s)
505 501
506 502 def increasingchunks(source, min=1024, max=65536):
507 503 '''return no less than min bytes per chunk while data remains,
508 504 doubling min after each chunk until it reaches max'''
509 505 def log2(x):
510 506 if not x:
511 507 return 0
512 508 i = 0
513 509 while x:
514 510 x >>= 1
515 511 i += 1
516 512 return i - 1
517 513
518 514 buf = []
519 515 blen = 0
520 516 for chunk in source:
521 517 buf.append(chunk)
522 518 blen += len(chunk)
523 519 if blen >= min:
524 520 if min < max:
525 521 min = min << 1
526 522 nmin = 1 << log2(blen)
527 523 if nmin > min:
528 524 min = nmin
529 525 if min > max:
530 526 min = max
531 527 yield ''.join(buf)
532 528 blen = 0
533 529 buf = []
534 530 if buf:
535 531 yield ''.join(buf)
536 532
537 533 Abort = error.Abort
538 534
539 535 def always(fn):
540 536 return True
541 537
542 538 def never(fn):
543 539 return False
544 540
545 541 def nogc(func):
546 542 """disable garbage collector
547 543
548 544 Python's garbage collector triggers a GC each time a certain number of
549 545 container objects (the number being defined by gc.get_threshold()) are
550 546 allocated even when marked not to be tracked by the collector. Tracking has
551 547 no effect on when GCs are triggered, only on what objects the GC looks
552 548 into. As a workaround, disable GC while building complex (huge)
553 549 containers.
554 550
555 551 This garbage collector issue have been fixed in 2.7.
556 552 """
557 553 def wrapper(*args, **kwargs):
558 554 gcenabled = gc.isenabled()
559 555 gc.disable()
560 556 try:
561 557 return func(*args, **kwargs)
562 558 finally:
563 559 if gcenabled:
564 560 gc.enable()
565 561 return wrapper
566 562
567 563 def pathto(root, n1, n2):
568 564 '''return the relative path from one place to another.
569 565 root should use os.sep to separate directories
570 566 n1 should use os.sep to separate directories
571 567 n2 should use "/" to separate directories
572 568 returns an os.sep-separated path.
573 569
574 570 If n1 is a relative path, it's assumed it's
575 571 relative to root.
576 572 n2 should always be relative to root.
577 573 '''
578 574 if not n1:
579 575 return localpath(n2)
580 576 if os.path.isabs(n1):
581 577 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
582 578 return os.path.join(root, localpath(n2))
583 579 n2 = '/'.join((pconvert(root), n2))
584 580 a, b = splitpath(n1), n2.split('/')
585 581 a.reverse()
586 582 b.reverse()
587 583 while a and b and a[-1] == b[-1]:
588 584 a.pop()
589 585 b.pop()
590 586 b.reverse()
591 587 return os.sep.join((['..'] * len(a)) + b) or '.'
592 588
593 589 def mainfrozen():
594 590 """return True if we are a frozen executable.
595 591
596 592 The code supports py2exe (most common, Windows only) and tools/freeze
597 593 (portable, not much used).
598 594 """
599 595 return (safehasattr(sys, "frozen") or # new py2exe
600 596 safehasattr(sys, "importers") or # old py2exe
601 597 imp.is_frozen("__main__")) # tools/freeze
602 598
603 599 # the location of data files matching the source code
604 600 if mainfrozen():
605 601 # executable version (py2exe) doesn't support __file__
606 602 datapath = os.path.dirname(sys.executable)
607 603 else:
608 604 datapath = os.path.dirname(__file__)
609 605
610 606 i18n.setdatapath(datapath)
611 607
612 608 _hgexecutable = None
613 609
614 610 def hgexecutable():
615 611 """return location of the 'hg' executable.
616 612
617 613 Defaults to $HG or 'hg' in the search path.
618 614 """
619 615 if _hgexecutable is None:
620 616 hg = os.environ.get('HG')
621 617 mainmod = sys.modules['__main__']
622 618 if hg:
623 619 _sethgexecutable(hg)
624 620 elif mainfrozen():
625 621 _sethgexecutable(sys.executable)
626 622 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
627 623 _sethgexecutable(mainmod.__file__)
628 624 else:
629 625 exe = findexe('hg') or os.path.basename(sys.argv[0])
630 626 _sethgexecutable(exe)
631 627 return _hgexecutable
632 628
633 629 def _sethgexecutable(path):
634 630 """set location of the 'hg' executable"""
635 631 global _hgexecutable
636 632 _hgexecutable = path
637 633
638 634 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
639 635 '''enhanced shell command execution.
640 636 run with environment maybe modified, maybe in different dir.
641 637
642 638 if command fails and onerr is None, return status, else raise onerr
643 639 object as exception.
644 640
645 641 if out is specified, it is assumed to be a file-like object that has a
646 642 write() method. stdout and stderr will be redirected to out.'''
647 643 try:
648 644 sys.stdout.flush()
649 645 except Exception:
650 646 pass
651 647 def py2shell(val):
652 648 'convert python object into string that is useful to shell'
653 649 if val is None or val is False:
654 650 return '0'
655 651 if val is True:
656 652 return '1'
657 653 return str(val)
658 654 origcmd = cmd
659 655 cmd = quotecommand(cmd)
660 656 if sys.platform == 'plan9' and (sys.version_info[0] == 2
661 657 and sys.version_info[1] < 7):
662 658 # subprocess kludge to work around issues in half-baked Python
663 659 # ports, notably bichued/python:
664 660 if not cwd is None:
665 661 os.chdir(cwd)
666 662 rc = os.system(cmd)
667 663 else:
668 664 env = dict(os.environ)
669 665 env.update((k, py2shell(v)) for k, v in environ.iteritems())
670 666 env['HG'] = hgexecutable()
671 667 if out is None or out == sys.__stdout__:
672 668 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
673 669 env=env, cwd=cwd)
674 670 else:
675 671 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
676 672 env=env, cwd=cwd, stdout=subprocess.PIPE,
677 673 stderr=subprocess.STDOUT)
678 674 while True:
679 675 line = proc.stdout.readline()
680 676 if not line:
681 677 break
682 678 out.write(line)
683 679 proc.wait()
684 680 rc = proc.returncode
685 681 if sys.platform == 'OpenVMS' and rc & 1:
686 682 rc = 0
687 683 if rc and onerr:
688 684 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
689 685 explainexit(rc)[0])
690 686 if errprefix:
691 687 errmsg = '%s: %s' % (errprefix, errmsg)
692 688 raise onerr(errmsg)
693 689 return rc
694 690
695 691 def checksignature(func):
696 692 '''wrap a function with code to check for calling errors'''
697 693 def check(*args, **kwargs):
698 694 try:
699 695 return func(*args, **kwargs)
700 696 except TypeError:
701 697 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
702 698 raise error.SignatureError
703 699 raise
704 700
705 701 return check
706 702
707 703 def copyfile(src, dest, hardlink=False):
708 704 "copy a file, preserving mode and atime/mtime"
709 705 if os.path.lexists(dest):
710 706 unlink(dest)
711 707 # hardlinks are problematic on CIFS, quietly ignore this flag
712 708 # until we find a way to work around it cleanly (issue4546)
713 709 if False and hardlink:
714 710 try:
715 711 oslink(src, dest)
716 712 return
717 713 except (IOError, OSError):
718 714 pass # fall back to normal copy
719 715 if os.path.islink(src):
720 716 os.symlink(os.readlink(src), dest)
721 717 else:
722 718 try:
723 719 shutil.copyfile(src, dest)
724 720 shutil.copymode(src, dest)
725 721 except shutil.Error, inst:
726 722 raise Abort(str(inst))
727 723
728 724 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
729 725 """Copy a directory tree using hardlinks if possible."""
730 726 num = 0
731 727
732 728 if hardlink is None:
733 729 hardlink = (os.stat(src).st_dev ==
734 730 os.stat(os.path.dirname(dst)).st_dev)
735 731 if hardlink:
736 732 topic = _('linking')
737 733 else:
738 734 topic = _('copying')
739 735
740 736 if os.path.isdir(src):
741 737 os.mkdir(dst)
742 738 for name, kind in osutil.listdir(src):
743 739 srcname = os.path.join(src, name)
744 740 dstname = os.path.join(dst, name)
745 741 def nprog(t, pos):
746 742 if pos is not None:
747 743 return progress(t, pos + num)
748 744 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
749 745 num += n
750 746 else:
751 747 if hardlink:
752 748 try:
753 749 oslink(src, dst)
754 750 except (IOError, OSError):
755 751 hardlink = False
756 752 shutil.copy(src, dst)
757 753 else:
758 754 shutil.copy(src, dst)
759 755 num += 1
760 756 progress(topic, num)
761 757 progress(topic, None)
762 758
763 759 return hardlink, num
764 760
765 761 _winreservednames = '''con prn aux nul
766 762 com1 com2 com3 com4 com5 com6 com7 com8 com9
767 763 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
768 764 _winreservedchars = ':*?"<>|'
769 765 def checkwinfilename(path):
770 766 r'''Check that the base-relative path is a valid filename on Windows.
771 767 Returns None if the path is ok, or a UI string describing the problem.
772 768
773 769 >>> checkwinfilename("just/a/normal/path")
774 770 >>> checkwinfilename("foo/bar/con.xml")
775 771 "filename contains 'con', which is reserved on Windows"
776 772 >>> checkwinfilename("foo/con.xml/bar")
777 773 "filename contains 'con', which is reserved on Windows"
778 774 >>> checkwinfilename("foo/bar/xml.con")
779 775 >>> checkwinfilename("foo/bar/AUX/bla.txt")
780 776 "filename contains 'AUX', which is reserved on Windows"
781 777 >>> checkwinfilename("foo/bar/bla:.txt")
782 778 "filename contains ':', which is reserved on Windows"
783 779 >>> checkwinfilename("foo/bar/b\07la.txt")
784 780 "filename contains '\\x07', which is invalid on Windows"
785 781 >>> checkwinfilename("foo/bar/bla ")
786 782 "filename ends with ' ', which is not allowed on Windows"
787 783 >>> checkwinfilename("../bar")
788 784 >>> checkwinfilename("foo\\")
789 785 "filename ends with '\\', which is invalid on Windows"
790 786 >>> checkwinfilename("foo\\/bar")
791 787 "directory name ends with '\\', which is invalid on Windows"
792 788 '''
793 789 if path.endswith('\\'):
794 790 return _("filename ends with '\\', which is invalid on Windows")
795 791 if '\\/' in path:
796 792 return _("directory name ends with '\\', which is invalid on Windows")
797 793 for n in path.replace('\\', '/').split('/'):
798 794 if not n:
799 795 continue
800 796 for c in n:
801 797 if c in _winreservedchars:
802 798 return _("filename contains '%s', which is reserved "
803 799 "on Windows") % c
804 800 if ord(c) <= 31:
805 801 return _("filename contains %r, which is invalid "
806 802 "on Windows") % c
807 803 base = n.split('.')[0]
808 804 if base and base.lower() in _winreservednames:
809 805 return _("filename contains '%s', which is reserved "
810 806 "on Windows") % base
811 807 t = n[-1]
812 808 if t in '. ' and n not in '..':
813 809 return _("filename ends with '%s', which is not allowed "
814 810 "on Windows") % t
815 811
816 812 if os.name == 'nt':
817 813 checkosfilename = checkwinfilename
818 814 else:
819 815 checkosfilename = platform.checkosfilename
820 816
821 817 def makelock(info, pathname):
822 818 try:
823 819 return os.symlink(info, pathname)
824 820 except OSError, why:
825 821 if why.errno == errno.EEXIST:
826 822 raise
827 823 except AttributeError: # no symlink in os
828 824 pass
829 825
830 826 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
831 827 os.write(ld, info)
832 828 os.close(ld)
833 829
834 830 def readlock(pathname):
835 831 try:
836 832 return os.readlink(pathname)
837 833 except OSError, why:
838 834 if why.errno not in (errno.EINVAL, errno.ENOSYS):
839 835 raise
840 836 except AttributeError: # no symlink in os
841 837 pass
842 838 fp = posixfile(pathname)
843 839 r = fp.read()
844 840 fp.close()
845 841 return r
846 842
847 843 def fstat(fp):
848 844 '''stat file object that may not have fileno method.'''
849 845 try:
850 846 return os.fstat(fp.fileno())
851 847 except AttributeError:
852 848 return os.stat(fp.name)
853 849
854 850 # File system features
855 851
856 852 def checkcase(path):
857 853 """
858 854 Return true if the given path is on a case-sensitive filesystem
859 855
860 856 Requires a path (like /foo/.hg) ending with a foldable final
861 857 directory component.
862 858 """
863 859 s1 = os.lstat(path)
864 860 d, b = os.path.split(path)
865 861 b2 = b.upper()
866 862 if b == b2:
867 863 b2 = b.lower()
868 864 if b == b2:
869 865 return True # no evidence against case sensitivity
870 866 p2 = os.path.join(d, b2)
871 867 try:
872 868 s2 = os.lstat(p2)
873 869 if s2 == s1:
874 870 return False
875 871 return True
876 872 except OSError:
877 873 return True
878 874
879 875 try:
880 876 import re2
881 877 _re2 = None
882 878 except ImportError:
883 879 _re2 = False
884 880
885 881 class _re(object):
886 882 def _checkre2(self):
887 883 global _re2
888 884 try:
889 885 # check if match works, see issue3964
890 886 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
891 887 except ImportError:
892 888 _re2 = False
893 889
894 890 def compile(self, pat, flags=0):
895 891 '''Compile a regular expression, using re2 if possible
896 892
897 893 For best performance, use only re2-compatible regexp features. The
898 894 only flags from the re module that are re2-compatible are
899 895 IGNORECASE and MULTILINE.'''
900 896 if _re2 is None:
901 897 self._checkre2()
902 898 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
903 899 if flags & remod.IGNORECASE:
904 900 pat = '(?i)' + pat
905 901 if flags & remod.MULTILINE:
906 902 pat = '(?m)' + pat
907 903 try:
908 904 return re2.compile(pat)
909 905 except re2.error:
910 906 pass
911 907 return remod.compile(pat, flags)
912 908
913 909 @propertycache
914 910 def escape(self):
915 911 '''Return the version of escape corresponding to self.compile.
916 912
917 913 This is imperfect because whether re2 or re is used for a particular
918 914 function depends on the flags, etc, but it's the best we can do.
919 915 '''
920 916 global _re2
921 917 if _re2 is None:
922 918 self._checkre2()
923 919 if _re2:
924 920 return re2.escape
925 921 else:
926 922 return remod.escape
927 923
928 924 re = _re()
929 925
930 926 _fspathcache = {}
931 927 def fspath(name, root):
932 928 '''Get name in the case stored in the filesystem
933 929
934 930 The name should be relative to root, and be normcase-ed for efficiency.
935 931
936 932 Note that this function is unnecessary, and should not be
937 933 called, for case-sensitive filesystems (simply because it's expensive).
938 934
939 935 The root should be normcase-ed, too.
940 936 '''
941 937 def _makefspathcacheentry(dir):
942 938 return dict((normcase(n), n) for n in os.listdir(dir))
943 939
944 940 seps = os.sep
945 941 if os.altsep:
946 942 seps = seps + os.altsep
947 943 # Protect backslashes. This gets silly very quickly.
948 944 seps.replace('\\','\\\\')
949 945 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
950 946 dir = os.path.normpath(root)
951 947 result = []
952 948 for part, sep in pattern.findall(name):
953 949 if sep:
954 950 result.append(sep)
955 951 continue
956 952
957 953 if dir not in _fspathcache:
958 954 _fspathcache[dir] = _makefspathcacheentry(dir)
959 955 contents = _fspathcache[dir]
960 956
961 957 found = contents.get(part)
962 958 if not found:
963 959 # retry "once per directory" per "dirstate.walk" which
964 960 # may take place for each patches of "hg qpush", for example
965 961 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
966 962 found = contents.get(part)
967 963
968 964 result.append(found or part)
969 965 dir = os.path.join(dir, part)
970 966
971 967 return ''.join(result)
972 968
973 969 def checknlink(testfile):
974 970 '''check whether hardlink count reporting works properly'''
975 971
976 972 # testfile may be open, so we need a separate file for checking to
977 973 # work around issue2543 (or testfile may get lost on Samba shares)
978 974 f1 = testfile + ".hgtmp1"
979 975 if os.path.lexists(f1):
980 976 return False
981 977 try:
982 978 posixfile(f1, 'w').close()
983 979 except IOError:
984 980 return False
985 981
986 982 f2 = testfile + ".hgtmp2"
987 983 fd = None
988 984 try:
989 985 oslink(f1, f2)
990 986 # nlinks() may behave differently for files on Windows shares if
991 987 # the file is open.
992 988 fd = posixfile(f2)
993 989 return nlinks(f2) > 1
994 990 except OSError:
995 991 return False
996 992 finally:
997 993 if fd is not None:
998 994 fd.close()
999 995 for f in (f1, f2):
1000 996 try:
1001 997 os.unlink(f)
1002 998 except OSError:
1003 999 pass
1004 1000
1005 1001 def endswithsep(path):
1006 1002 '''Check path ends with os.sep or os.altsep.'''
1007 1003 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1008 1004
1009 1005 def splitpath(path):
1010 1006 '''Split path by os.sep.
1011 1007 Note that this function does not use os.altsep because this is
1012 1008 an alternative of simple "xxx.split(os.sep)".
1013 1009 It is recommended to use os.path.normpath() before using this
1014 1010 function if need.'''
1015 1011 return path.split(os.sep)
1016 1012
1017 1013 def gui():
1018 1014 '''Are we running in a GUI?'''
1019 1015 if sys.platform == 'darwin':
1020 1016 if 'SSH_CONNECTION' in os.environ:
1021 1017 # handle SSH access to a box where the user is logged in
1022 1018 return False
1023 1019 elif getattr(osutil, 'isgui', None):
1024 1020 # check if a CoreGraphics session is available
1025 1021 return osutil.isgui()
1026 1022 else:
1027 1023 # pure build; use a safe default
1028 1024 return True
1029 1025 else:
1030 1026 return os.name == "nt" or os.environ.get("DISPLAY")
1031 1027
1032 1028 def mktempcopy(name, emptyok=False, createmode=None):
1033 1029 """Create a temporary file with the same contents from name
1034 1030
1035 1031 The permission bits are copied from the original file.
1036 1032
1037 1033 If the temporary file is going to be truncated immediately, you
1038 1034 can use emptyok=True as an optimization.
1039 1035
1040 1036 Returns the name of the temporary file.
1041 1037 """
1042 1038 d, fn = os.path.split(name)
1043 1039 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1044 1040 os.close(fd)
1045 1041 # Temporary files are created with mode 0600, which is usually not
1046 1042 # what we want. If the original file already exists, just copy
1047 1043 # its mode. Otherwise, manually obey umask.
1048 1044 copymode(name, temp, createmode)
1049 1045 if emptyok:
1050 1046 return temp
1051 1047 try:
1052 1048 try:
1053 1049 ifp = posixfile(name, "rb")
1054 1050 except IOError, inst:
1055 1051 if inst.errno == errno.ENOENT:
1056 1052 return temp
1057 1053 if not getattr(inst, 'filename', None):
1058 1054 inst.filename = name
1059 1055 raise
1060 1056 ofp = posixfile(temp, "wb")
1061 1057 for chunk in filechunkiter(ifp):
1062 1058 ofp.write(chunk)
1063 1059 ifp.close()
1064 1060 ofp.close()
1065 1061 except: # re-raises
1066 1062 try: os.unlink(temp)
1067 1063 except OSError: pass
1068 1064 raise
1069 1065 return temp
1070 1066
1071 1067 class atomictempfile(object):
1072 1068 '''writable file object that atomically updates a file
1073 1069
1074 1070 All writes will go to a temporary copy of the original file. Call
1075 1071 close() when you are done writing, and atomictempfile will rename
1076 1072 the temporary copy to the original name, making the changes
1077 1073 visible. If the object is destroyed without being closed, all your
1078 1074 writes are discarded.
1079 1075 '''
1080 1076 def __init__(self, name, mode='w+b', createmode=None):
1081 1077 self.__name = name # permanent name
1082 1078 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1083 1079 createmode=createmode)
1084 1080 self._fp = posixfile(self._tempname, mode)
1085 1081
1086 1082 # delegated methods
1087 1083 self.write = self._fp.write
1088 1084 self.seek = self._fp.seek
1089 1085 self.tell = self._fp.tell
1090 1086 self.fileno = self._fp.fileno
1091 1087
1092 1088 def close(self):
1093 1089 if not self._fp.closed:
1094 1090 self._fp.close()
1095 1091 rename(self._tempname, localpath(self.__name))
1096 1092
1097 1093 def discard(self):
1098 1094 if not self._fp.closed:
1099 1095 try:
1100 1096 os.unlink(self._tempname)
1101 1097 except OSError:
1102 1098 pass
1103 1099 self._fp.close()
1104 1100
1105 1101 def __del__(self):
1106 1102 if safehasattr(self, '_fp'): # constructor actually did something
1107 1103 self.discard()
1108 1104
1109 1105 def makedirs(name, mode=None, notindexed=False):
1110 1106 """recursive directory creation with parent mode inheritance"""
1111 1107 try:
1112 1108 makedir(name, notindexed)
1113 1109 except OSError, err:
1114 1110 if err.errno == errno.EEXIST:
1115 1111 return
1116 1112 if err.errno != errno.ENOENT or not name:
1117 1113 raise
1118 1114 parent = os.path.dirname(os.path.abspath(name))
1119 1115 if parent == name:
1120 1116 raise
1121 1117 makedirs(parent, mode, notindexed)
1122 1118 makedir(name, notindexed)
1123 1119 if mode is not None:
1124 1120 os.chmod(name, mode)
1125 1121
1126 1122 def ensuredirs(name, mode=None, notindexed=False):
1127 1123 """race-safe recursive directory creation
1128 1124
1129 1125 Newly created directories are marked as "not to be indexed by
1130 1126 the content indexing service", if ``notindexed`` is specified
1131 1127 for "write" mode access.
1132 1128 """
1133 1129 if os.path.isdir(name):
1134 1130 return
1135 1131 parent = os.path.dirname(os.path.abspath(name))
1136 1132 if parent != name:
1137 1133 ensuredirs(parent, mode, notindexed)
1138 1134 try:
1139 1135 makedir(name, notindexed)
1140 1136 except OSError, err:
1141 1137 if err.errno == errno.EEXIST and os.path.isdir(name):
1142 1138 # someone else seems to have won a directory creation race
1143 1139 return
1144 1140 raise
1145 1141 if mode is not None:
1146 1142 os.chmod(name, mode)
1147 1143
1148 1144 def readfile(path):
1149 1145 fp = open(path, 'rb')
1150 1146 try:
1151 1147 return fp.read()
1152 1148 finally:
1153 1149 fp.close()
1154 1150
1155 1151 def writefile(path, text):
1156 1152 fp = open(path, 'wb')
1157 1153 try:
1158 1154 fp.write(text)
1159 1155 finally:
1160 1156 fp.close()
1161 1157
1162 1158 def appendfile(path, text):
1163 1159 fp = open(path, 'ab')
1164 1160 try:
1165 1161 fp.write(text)
1166 1162 finally:
1167 1163 fp.close()
1168 1164
1169 1165 class chunkbuffer(object):
1170 1166 """Allow arbitrary sized chunks of data to be efficiently read from an
1171 1167 iterator over chunks of arbitrary size."""
1172 1168
1173 1169 def __init__(self, in_iter):
1174 1170 """in_iter is the iterator that's iterating over the input chunks.
1175 1171 targetsize is how big a buffer to try to maintain."""
1176 1172 def splitbig(chunks):
1177 1173 for chunk in chunks:
1178 1174 if len(chunk) > 2**20:
1179 1175 pos = 0
1180 1176 while pos < len(chunk):
1181 1177 end = pos + 2 ** 18
1182 1178 yield chunk[pos:end]
1183 1179 pos = end
1184 1180 else:
1185 1181 yield chunk
1186 1182 self.iter = splitbig(in_iter)
1187 1183 self._queue = collections.deque()
1188 1184
1189 1185 def read(self, l=None):
1190 1186 """Read L bytes of data from the iterator of chunks of data.
1191 1187 Returns less than L bytes if the iterator runs dry.
1192 1188
1193 1189 If size parameter is omitted, read everything"""
1194 1190 left = l
1195 1191 buf = []
1196 1192 queue = self._queue
1197 1193 while left is None or left > 0:
1198 1194 # refill the queue
1199 1195 if not queue:
1200 1196 target = 2**18
1201 1197 for chunk in self.iter:
1202 1198 queue.append(chunk)
1203 1199 target -= len(chunk)
1204 1200 if target <= 0:
1205 1201 break
1206 1202 if not queue:
1207 1203 break
1208 1204
1209 1205 chunk = queue.popleft()
1210 1206 if left is not None:
1211 1207 left -= len(chunk)
1212 1208 if left is not None and left < 0:
1213 1209 queue.appendleft(chunk[left:])
1214 1210 buf.append(chunk[:left])
1215 1211 else:
1216 1212 buf.append(chunk)
1217 1213
1218 1214 return ''.join(buf)
1219 1215
1220 1216 def filechunkiter(f, size=65536, limit=None):
1221 1217 """Create a generator that produces the data in the file size
1222 1218 (default 65536) bytes at a time, up to optional limit (default is
1223 1219 to read all data). Chunks may be less than size bytes if the
1224 1220 chunk is the last chunk in the file, or the file is a socket or
1225 1221 some other type of file that sometimes reads less data than is
1226 1222 requested."""
1227 1223 assert size >= 0
1228 1224 assert limit is None or limit >= 0
1229 1225 while True:
1230 1226 if limit is None:
1231 1227 nbytes = size
1232 1228 else:
1233 1229 nbytes = min(limit, size)
1234 1230 s = nbytes and f.read(nbytes)
1235 1231 if not s:
1236 1232 break
1237 1233 if limit:
1238 1234 limit -= len(s)
1239 1235 yield s
1240 1236
1241 1237 def makedate(timestamp=None):
1242 1238 '''Return a unix timestamp (or the current time) as a (unixtime,
1243 1239 offset) tuple based off the local timezone.'''
1244 1240 if timestamp is None:
1245 1241 timestamp = time.time()
1246 1242 if timestamp < 0:
1247 1243 hint = _("check your clock")
1248 1244 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1249 1245 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1250 1246 datetime.datetime.fromtimestamp(timestamp))
1251 1247 tz = delta.days * 86400 + delta.seconds
1252 1248 return timestamp, tz
1253 1249
1254 1250 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1255 1251 """represent a (unixtime, offset) tuple as a localized time.
1256 1252 unixtime is seconds since the epoch, and offset is the time zone's
1257 1253 number of seconds away from UTC. if timezone is false, do not
1258 1254 append time zone to string."""
1259 1255 t, tz = date or makedate()
1260 1256 if t < 0:
1261 1257 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1262 1258 tz = 0
1263 1259 if "%1" in format or "%2" in format or "%z" in format:
1264 1260 sign = (tz > 0) and "-" or "+"
1265 1261 minutes = abs(tz) // 60
1266 1262 format = format.replace("%z", "%1%2")
1267 1263 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1268 1264 format = format.replace("%2", "%02d" % (minutes % 60))
1269 1265 try:
1270 1266 t = time.gmtime(float(t) - tz)
1271 1267 except ValueError:
1272 1268 # time was out of range
1273 1269 t = time.gmtime(sys.maxint)
1274 1270 s = time.strftime(format, t)
1275 1271 return s
1276 1272
1277 1273 def shortdate(date=None):
1278 1274 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1279 1275 return datestr(date, format='%Y-%m-%d')
1280 1276
1281 1277 def strdate(string, format, defaults=[]):
1282 1278 """parse a localized time string and return a (unixtime, offset) tuple.
1283 1279 if the string cannot be parsed, ValueError is raised."""
1284 1280 def timezone(string):
1285 1281 tz = string.split()[-1]
1286 1282 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1287 1283 sign = (tz[0] == "+") and 1 or -1
1288 1284 hours = int(tz[1:3])
1289 1285 minutes = int(tz[3:5])
1290 1286 return -sign * (hours * 60 + minutes) * 60
1291 1287 if tz == "GMT" or tz == "UTC":
1292 1288 return 0
1293 1289 return None
1294 1290
1295 1291 # NOTE: unixtime = localunixtime + offset
1296 1292 offset, date = timezone(string), string
1297 1293 if offset is not None:
1298 1294 date = " ".join(string.split()[:-1])
1299 1295
1300 1296 # add missing elements from defaults
1301 1297 usenow = False # default to using biased defaults
1302 1298 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1303 1299 found = [True for p in part if ("%"+p) in format]
1304 1300 if not found:
1305 1301 date += "@" + defaults[part][usenow]
1306 1302 format += "@%" + part[0]
1307 1303 else:
1308 1304 # We've found a specific time element, less specific time
1309 1305 # elements are relative to today
1310 1306 usenow = True
1311 1307
1312 1308 timetuple = time.strptime(date, format)
1313 1309 localunixtime = int(calendar.timegm(timetuple))
1314 1310 if offset is None:
1315 1311 # local timezone
1316 1312 unixtime = int(time.mktime(timetuple))
1317 1313 offset = unixtime - localunixtime
1318 1314 else:
1319 1315 unixtime = localunixtime + offset
1320 1316 return unixtime, offset
1321 1317
1322 1318 def parsedate(date, formats=None, bias={}):
1323 1319 """parse a localized date/time and return a (unixtime, offset) tuple.
1324 1320
1325 1321 The date may be a "unixtime offset" string or in one of the specified
1326 1322 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1327 1323
1328 1324 >>> parsedate(' today ') == parsedate(\
1329 1325 datetime.date.today().strftime('%b %d'))
1330 1326 True
1331 1327 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1332 1328 datetime.timedelta(days=1)\
1333 1329 ).strftime('%b %d'))
1334 1330 True
1335 1331 >>> now, tz = makedate()
1336 1332 >>> strnow, strtz = parsedate('now')
1337 1333 >>> (strnow - now) < 1
1338 1334 True
1339 1335 >>> tz == strtz
1340 1336 True
1341 1337 """
1342 1338 if not date:
1343 1339 return 0, 0
1344 1340 if isinstance(date, tuple) and len(date) == 2:
1345 1341 return date
1346 1342 if not formats:
1347 1343 formats = defaultdateformats
1348 1344 date = date.strip()
1349 1345
1350 1346 if date == 'now' or date == _('now'):
1351 1347 return makedate()
1352 1348 if date == 'today' or date == _('today'):
1353 1349 date = datetime.date.today().strftime('%b %d')
1354 1350 elif date == 'yesterday' or date == _('yesterday'):
1355 1351 date = (datetime.date.today() -
1356 1352 datetime.timedelta(days=1)).strftime('%b %d')
1357 1353
1358 1354 try:
1359 1355 when, offset = map(int, date.split(' '))
1360 1356 except ValueError:
1361 1357 # fill out defaults
1362 1358 now = makedate()
1363 1359 defaults = {}
1364 1360 for part in ("d", "mb", "yY", "HI", "M", "S"):
1365 1361 # this piece is for rounding the specific end of unknowns
1366 1362 b = bias.get(part)
1367 1363 if b is None:
1368 1364 if part[0] in "HMS":
1369 1365 b = "00"
1370 1366 else:
1371 1367 b = "0"
1372 1368
1373 1369 # this piece is for matching the generic end to today's date
1374 1370 n = datestr(now, "%" + part[0])
1375 1371
1376 1372 defaults[part] = (b, n)
1377 1373
1378 1374 for format in formats:
1379 1375 try:
1380 1376 when, offset = strdate(date, format, defaults)
1381 1377 except (ValueError, OverflowError):
1382 1378 pass
1383 1379 else:
1384 1380 break
1385 1381 else:
1386 1382 raise Abort(_('invalid date: %r') % date)
1387 1383 # validate explicit (probably user-specified) date and
1388 1384 # time zone offset. values must fit in signed 32 bits for
1389 1385 # current 32-bit linux runtimes. timezones go from UTC-12
1390 1386 # to UTC+14
1391 1387 if abs(when) > 0x7fffffff:
1392 1388 raise Abort(_('date exceeds 32 bits: %d') % when)
1393 1389 if when < 0:
1394 1390 raise Abort(_('negative date value: %d') % when)
1395 1391 if offset < -50400 or offset > 43200:
1396 1392 raise Abort(_('impossible time zone offset: %d') % offset)
1397 1393 return when, offset
1398 1394
1399 1395 def matchdate(date):
1400 1396 """Return a function that matches a given date match specifier
1401 1397
1402 1398 Formats include:
1403 1399
1404 1400 '{date}' match a given date to the accuracy provided
1405 1401
1406 1402 '<{date}' on or before a given date
1407 1403
1408 1404 '>{date}' on or after a given date
1409 1405
1410 1406 >>> p1 = parsedate("10:29:59")
1411 1407 >>> p2 = parsedate("10:30:00")
1412 1408 >>> p3 = parsedate("10:30:59")
1413 1409 >>> p4 = parsedate("10:31:00")
1414 1410 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1415 1411 >>> f = matchdate("10:30")
1416 1412 >>> f(p1[0])
1417 1413 False
1418 1414 >>> f(p2[0])
1419 1415 True
1420 1416 >>> f(p3[0])
1421 1417 True
1422 1418 >>> f(p4[0])
1423 1419 False
1424 1420 >>> f(p5[0])
1425 1421 False
1426 1422 """
1427 1423
1428 1424 def lower(date):
1429 1425 d = {'mb': "1", 'd': "1"}
1430 1426 return parsedate(date, extendeddateformats, d)[0]
1431 1427
1432 1428 def upper(date):
1433 1429 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1434 1430 for days in ("31", "30", "29"):
1435 1431 try:
1436 1432 d["d"] = days
1437 1433 return parsedate(date, extendeddateformats, d)[0]
1438 1434 except Abort:
1439 1435 pass
1440 1436 d["d"] = "28"
1441 1437 return parsedate(date, extendeddateformats, d)[0]
1442 1438
1443 1439 date = date.strip()
1444 1440
1445 1441 if not date:
1446 1442 raise Abort(_("dates cannot consist entirely of whitespace"))
1447 1443 elif date[0] == "<":
1448 1444 if not date[1:]:
1449 1445 raise Abort(_("invalid day spec, use '<DATE'"))
1450 1446 when = upper(date[1:])
1451 1447 return lambda x: x <= when
1452 1448 elif date[0] == ">":
1453 1449 if not date[1:]:
1454 1450 raise Abort(_("invalid day spec, use '>DATE'"))
1455 1451 when = lower(date[1:])
1456 1452 return lambda x: x >= when
1457 1453 elif date[0] == "-":
1458 1454 try:
1459 1455 days = int(date[1:])
1460 1456 except ValueError:
1461 1457 raise Abort(_("invalid day spec: %s") % date[1:])
1462 1458 if days < 0:
1463 1459 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1464 1460 % date[1:])
1465 1461 when = makedate()[0] - days * 3600 * 24
1466 1462 return lambda x: x >= when
1467 1463 elif " to " in date:
1468 1464 a, b = date.split(" to ")
1469 1465 start, stop = lower(a), upper(b)
1470 1466 return lambda x: x >= start and x <= stop
1471 1467 else:
1472 1468 start, stop = lower(date), upper(date)
1473 1469 return lambda x: x >= start and x <= stop
1474 1470
1475 1471 def shortuser(user):
1476 1472 """Return a short representation of a user name or email address."""
1477 1473 f = user.find('@')
1478 1474 if f >= 0:
1479 1475 user = user[:f]
1480 1476 f = user.find('<')
1481 1477 if f >= 0:
1482 1478 user = user[f + 1:]
1483 1479 f = user.find(' ')
1484 1480 if f >= 0:
1485 1481 user = user[:f]
1486 1482 f = user.find('.')
1487 1483 if f >= 0:
1488 1484 user = user[:f]
1489 1485 return user
1490 1486
1491 1487 def emailuser(user):
1492 1488 """Return the user portion of an email address."""
1493 1489 f = user.find('@')
1494 1490 if f >= 0:
1495 1491 user = user[:f]
1496 1492 f = user.find('<')
1497 1493 if f >= 0:
1498 1494 user = user[f + 1:]
1499 1495 return user
1500 1496
1501 1497 def email(author):
1502 1498 '''get email of author.'''
1503 1499 r = author.find('>')
1504 1500 if r == -1:
1505 1501 r = None
1506 1502 return author[author.find('<') + 1:r]
1507 1503
1508 1504 def ellipsis(text, maxlength=400):
1509 1505 """Trim string to at most maxlength (default: 400) columns in display."""
1510 1506 return encoding.trim(text, maxlength, ellipsis='...')
1511 1507
1512 1508 def unitcountfn(*unittable):
1513 1509 '''return a function that renders a readable count of some quantity'''
1514 1510
1515 1511 def go(count):
1516 1512 for multiplier, divisor, format in unittable:
1517 1513 if count >= divisor * multiplier:
1518 1514 return format % (count / float(divisor))
1519 1515 return unittable[-1][2] % count
1520 1516
1521 1517 return go
1522 1518
1523 1519 bytecount = unitcountfn(
1524 1520 (100, 1 << 30, _('%.0f GB')),
1525 1521 (10, 1 << 30, _('%.1f GB')),
1526 1522 (1, 1 << 30, _('%.2f GB')),
1527 1523 (100, 1 << 20, _('%.0f MB')),
1528 1524 (10, 1 << 20, _('%.1f MB')),
1529 1525 (1, 1 << 20, _('%.2f MB')),
1530 1526 (100, 1 << 10, _('%.0f KB')),
1531 1527 (10, 1 << 10, _('%.1f KB')),
1532 1528 (1, 1 << 10, _('%.2f KB')),
1533 1529 (1, 1, _('%.0f bytes')),
1534 1530 )
1535 1531
1536 1532 def uirepr(s):
1537 1533 # Avoid double backslash in Windows path repr()
1538 1534 return repr(s).replace('\\\\', '\\')
1539 1535
1540 1536 # delay import of textwrap
1541 1537 def MBTextWrapper(**kwargs):
1542 1538 class tw(textwrap.TextWrapper):
1543 1539 """
1544 1540 Extend TextWrapper for width-awareness.
1545 1541
1546 1542 Neither number of 'bytes' in any encoding nor 'characters' is
1547 1543 appropriate to calculate terminal columns for specified string.
1548 1544
1549 1545 Original TextWrapper implementation uses built-in 'len()' directly,
1550 1546 so overriding is needed to use width information of each characters.
1551 1547
1552 1548 In addition, characters classified into 'ambiguous' width are
1553 1549 treated as wide in East Asian area, but as narrow in other.
1554 1550
1555 1551 This requires use decision to determine width of such characters.
1556 1552 """
1557 1553 def _cutdown(self, ucstr, space_left):
1558 1554 l = 0
1559 1555 colwidth = encoding.ucolwidth
1560 1556 for i in xrange(len(ucstr)):
1561 1557 l += colwidth(ucstr[i])
1562 1558 if space_left < l:
1563 1559 return (ucstr[:i], ucstr[i:])
1564 1560 return ucstr, ''
1565 1561
1566 1562 # overriding of base class
1567 1563 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1568 1564 space_left = max(width - cur_len, 1)
1569 1565
1570 1566 if self.break_long_words:
1571 1567 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1572 1568 cur_line.append(cut)
1573 1569 reversed_chunks[-1] = res
1574 1570 elif not cur_line:
1575 1571 cur_line.append(reversed_chunks.pop())
1576 1572
1577 1573 # this overriding code is imported from TextWrapper of python 2.6
1578 1574 # to calculate columns of string by 'encoding.ucolwidth()'
1579 1575 def _wrap_chunks(self, chunks):
1580 1576 colwidth = encoding.ucolwidth
1581 1577
1582 1578 lines = []
1583 1579 if self.width <= 0:
1584 1580 raise ValueError("invalid width %r (must be > 0)" % self.width)
1585 1581
1586 1582 # Arrange in reverse order so items can be efficiently popped
1587 1583 # from a stack of chucks.
1588 1584 chunks.reverse()
1589 1585
1590 1586 while chunks:
1591 1587
1592 1588 # Start the list of chunks that will make up the current line.
1593 1589 # cur_len is just the length of all the chunks in cur_line.
1594 1590 cur_line = []
1595 1591 cur_len = 0
1596 1592
1597 1593 # Figure out which static string will prefix this line.
1598 1594 if lines:
1599 1595 indent = self.subsequent_indent
1600 1596 else:
1601 1597 indent = self.initial_indent
1602 1598
1603 1599 # Maximum width for this line.
1604 1600 width = self.width - len(indent)
1605 1601
1606 1602 # First chunk on line is whitespace -- drop it, unless this
1607 1603 # is the very beginning of the text (i.e. no lines started yet).
1608 1604 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1609 1605 del chunks[-1]
1610 1606
1611 1607 while chunks:
1612 1608 l = colwidth(chunks[-1])
1613 1609
1614 1610 # Can at least squeeze this chunk onto the current line.
1615 1611 if cur_len + l <= width:
1616 1612 cur_line.append(chunks.pop())
1617 1613 cur_len += l
1618 1614
1619 1615 # Nope, this line is full.
1620 1616 else:
1621 1617 break
1622 1618
1623 1619 # The current line is full, and the next chunk is too big to
1624 1620 # fit on *any* line (not just this one).
1625 1621 if chunks and colwidth(chunks[-1]) > width:
1626 1622 self._handle_long_word(chunks, cur_line, cur_len, width)
1627 1623
1628 1624 # If the last chunk on this line is all whitespace, drop it.
1629 1625 if (self.drop_whitespace and
1630 1626 cur_line and cur_line[-1].strip() == ''):
1631 1627 del cur_line[-1]
1632 1628
1633 1629 # Convert current line back to a string and store it in list
1634 1630 # of all lines (return value).
1635 1631 if cur_line:
1636 1632 lines.append(indent + ''.join(cur_line))
1637 1633
1638 1634 return lines
1639 1635
1640 1636 global MBTextWrapper
1641 1637 MBTextWrapper = tw
1642 1638 return tw(**kwargs)
1643 1639
1644 1640 def wrap(line, width, initindent='', hangindent=''):
1645 1641 maxindent = max(len(hangindent), len(initindent))
1646 1642 if width <= maxindent:
1647 1643 # adjust for weird terminal size
1648 1644 width = max(78, maxindent + 1)
1649 1645 line = line.decode(encoding.encoding, encoding.encodingmode)
1650 1646 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1651 1647 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1652 1648 wrapper = MBTextWrapper(width=width,
1653 1649 initial_indent=initindent,
1654 1650 subsequent_indent=hangindent)
1655 1651 return wrapper.fill(line).encode(encoding.encoding)
1656 1652
1657 1653 def iterlines(iterator):
1658 1654 for chunk in iterator:
1659 1655 for line in chunk.splitlines():
1660 1656 yield line
1661 1657
1662 1658 def expandpath(path):
1663 1659 return os.path.expanduser(os.path.expandvars(path))
1664 1660
1665 1661 def hgcmd():
1666 1662 """Return the command used to execute current hg
1667 1663
1668 1664 This is different from hgexecutable() because on Windows we want
1669 1665 to avoid things opening new shell windows like batch files, so we
1670 1666 get either the python call or current executable.
1671 1667 """
1672 1668 if mainfrozen():
1673 1669 return [sys.executable]
1674 1670 return gethgcmd()
1675 1671
1676 1672 def rundetached(args, condfn):
1677 1673 """Execute the argument list in a detached process.
1678 1674
1679 1675 condfn is a callable which is called repeatedly and should return
1680 1676 True once the child process is known to have started successfully.
1681 1677 At this point, the child process PID is returned. If the child
1682 1678 process fails to start or finishes before condfn() evaluates to
1683 1679 True, return -1.
1684 1680 """
1685 1681 # Windows case is easier because the child process is either
1686 1682 # successfully starting and validating the condition or exiting
1687 1683 # on failure. We just poll on its PID. On Unix, if the child
1688 1684 # process fails to start, it will be left in a zombie state until
1689 1685 # the parent wait on it, which we cannot do since we expect a long
1690 1686 # running process on success. Instead we listen for SIGCHLD telling
1691 1687 # us our child process terminated.
1692 1688 terminated = set()
1693 1689 def handler(signum, frame):
1694 1690 terminated.add(os.wait())
1695 1691 prevhandler = None
1696 1692 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1697 1693 if SIGCHLD is not None:
1698 1694 prevhandler = signal.signal(SIGCHLD, handler)
1699 1695 try:
1700 1696 pid = spawndetached(args)
1701 1697 while not condfn():
1702 1698 if ((pid in terminated or not testpid(pid))
1703 1699 and not condfn()):
1704 1700 return -1
1705 1701 time.sleep(0.1)
1706 1702 return pid
1707 1703 finally:
1708 1704 if prevhandler is not None:
1709 1705 signal.signal(signal.SIGCHLD, prevhandler)
1710 1706
1711 1707 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1712 1708 """Return the result of interpolating items in the mapping into string s.
1713 1709
1714 1710 prefix is a single character string, or a two character string with
1715 1711 a backslash as the first character if the prefix needs to be escaped in
1716 1712 a regular expression.
1717 1713
1718 1714 fn is an optional function that will be applied to the replacement text
1719 1715 just before replacement.
1720 1716
1721 1717 escape_prefix is an optional flag that allows using doubled prefix for
1722 1718 its escaping.
1723 1719 """
1724 1720 fn = fn or (lambda s: s)
1725 1721 patterns = '|'.join(mapping.keys())
1726 1722 if escape_prefix:
1727 1723 patterns += '|' + prefix
1728 1724 if len(prefix) > 1:
1729 1725 prefix_char = prefix[1:]
1730 1726 else:
1731 1727 prefix_char = prefix
1732 1728 mapping[prefix_char] = prefix_char
1733 1729 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1734 1730 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1735 1731
1736 1732 def getport(port):
1737 1733 """Return the port for a given network service.
1738 1734
1739 1735 If port is an integer, it's returned as is. If it's a string, it's
1740 1736 looked up using socket.getservbyname(). If there's no matching
1741 1737 service, util.Abort is raised.
1742 1738 """
1743 1739 try:
1744 1740 return int(port)
1745 1741 except ValueError:
1746 1742 pass
1747 1743
1748 1744 try:
1749 1745 return socket.getservbyname(port)
1750 1746 except socket.error:
1751 1747 raise Abort(_("no port number associated with service '%s'") % port)
1752 1748
1753 1749 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1754 1750 '0': False, 'no': False, 'false': False, 'off': False,
1755 1751 'never': False}
1756 1752
1757 1753 def parsebool(s):
1758 1754 """Parse s into a boolean.
1759 1755
1760 1756 If s is not a valid boolean, returns None.
1761 1757 """
1762 1758 return _booleans.get(s.lower(), None)
1763 1759
1764 1760 _hexdig = '0123456789ABCDEFabcdef'
1765 1761 _hextochr = dict((a + b, chr(int(a + b, 16)))
1766 1762 for a in _hexdig for b in _hexdig)
1767 1763
1768 1764 def _urlunquote(s):
1769 1765 """Decode HTTP/HTML % encoding.
1770 1766
1771 1767 >>> _urlunquote('abc%20def')
1772 1768 'abc def'
1773 1769 """
1774 1770 res = s.split('%')
1775 1771 # fastpath
1776 1772 if len(res) == 1:
1777 1773 return s
1778 1774 s = res[0]
1779 1775 for item in res[1:]:
1780 1776 try:
1781 1777 s += _hextochr[item[:2]] + item[2:]
1782 1778 except KeyError:
1783 1779 s += '%' + item
1784 1780 except UnicodeDecodeError:
1785 1781 s += unichr(int(item[:2], 16)) + item[2:]
1786 1782 return s
1787 1783
1788 1784 class url(object):
1789 1785 r"""Reliable URL parser.
1790 1786
1791 1787 This parses URLs and provides attributes for the following
1792 1788 components:
1793 1789
1794 1790 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1795 1791
1796 1792 Missing components are set to None. The only exception is
1797 1793 fragment, which is set to '' if present but empty.
1798 1794
1799 1795 If parsefragment is False, fragment is included in query. If
1800 1796 parsequery is False, query is included in path. If both are
1801 1797 False, both fragment and query are included in path.
1802 1798
1803 1799 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1804 1800
1805 1801 Note that for backward compatibility reasons, bundle URLs do not
1806 1802 take host names. That means 'bundle://../' has a path of '../'.
1807 1803
1808 1804 Examples:
1809 1805
1810 1806 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1811 1807 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1812 1808 >>> url('ssh://[::1]:2200//home/joe/repo')
1813 1809 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1814 1810 >>> url('file:///home/joe/repo')
1815 1811 <url scheme: 'file', path: '/home/joe/repo'>
1816 1812 >>> url('file:///c:/temp/foo/')
1817 1813 <url scheme: 'file', path: 'c:/temp/foo/'>
1818 1814 >>> url('bundle:foo')
1819 1815 <url scheme: 'bundle', path: 'foo'>
1820 1816 >>> url('bundle://../foo')
1821 1817 <url scheme: 'bundle', path: '../foo'>
1822 1818 >>> url(r'c:\foo\bar')
1823 1819 <url path: 'c:\\foo\\bar'>
1824 1820 >>> url(r'\\blah\blah\blah')
1825 1821 <url path: '\\\\blah\\blah\\blah'>
1826 1822 >>> url(r'\\blah\blah\blah#baz')
1827 1823 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1828 1824 >>> url(r'file:///C:\users\me')
1829 1825 <url scheme: 'file', path: 'C:\\users\\me'>
1830 1826
1831 1827 Authentication credentials:
1832 1828
1833 1829 >>> url('ssh://joe:xyz@x/repo')
1834 1830 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1835 1831 >>> url('ssh://joe@x/repo')
1836 1832 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1837 1833
1838 1834 Query strings and fragments:
1839 1835
1840 1836 >>> url('http://host/a?b#c')
1841 1837 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1842 1838 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1843 1839 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1844 1840 """
1845 1841
1846 1842 _safechars = "!~*'()+"
1847 1843 _safepchars = "/!~*'()+:\\"
1848 1844 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
1849 1845
1850 1846 def __init__(self, path, parsequery=True, parsefragment=True):
1851 1847 # We slowly chomp away at path until we have only the path left
1852 1848 self.scheme = self.user = self.passwd = self.host = None
1853 1849 self.port = self.path = self.query = self.fragment = None
1854 1850 self._localpath = True
1855 1851 self._hostport = ''
1856 1852 self._origpath = path
1857 1853
1858 1854 if parsefragment and '#' in path:
1859 1855 path, self.fragment = path.split('#', 1)
1860 1856 if not path:
1861 1857 path = None
1862 1858
1863 1859 # special case for Windows drive letters and UNC paths
1864 1860 if hasdriveletter(path) or path.startswith(r'\\'):
1865 1861 self.path = path
1866 1862 return
1867 1863
1868 1864 # For compatibility reasons, we can't handle bundle paths as
1869 1865 # normal URLS
1870 1866 if path.startswith('bundle:'):
1871 1867 self.scheme = 'bundle'
1872 1868 path = path[7:]
1873 1869 if path.startswith('//'):
1874 1870 path = path[2:]
1875 1871 self.path = path
1876 1872 return
1877 1873
1878 1874 if self._matchscheme(path):
1879 1875 parts = path.split(':', 1)
1880 1876 if parts[0]:
1881 1877 self.scheme, path = parts
1882 1878 self._localpath = False
1883 1879
1884 1880 if not path:
1885 1881 path = None
1886 1882 if self._localpath:
1887 1883 self.path = ''
1888 1884 return
1889 1885 else:
1890 1886 if self._localpath:
1891 1887 self.path = path
1892 1888 return
1893 1889
1894 1890 if parsequery and '?' in path:
1895 1891 path, self.query = path.split('?', 1)
1896 1892 if not path:
1897 1893 path = None
1898 1894 if not self.query:
1899 1895 self.query = None
1900 1896
1901 1897 # // is required to specify a host/authority
1902 1898 if path and path.startswith('//'):
1903 1899 parts = path[2:].split('/', 1)
1904 1900 if len(parts) > 1:
1905 1901 self.host, path = parts
1906 1902 else:
1907 1903 self.host = parts[0]
1908 1904 path = None
1909 1905 if not self.host:
1910 1906 self.host = None
1911 1907 # path of file:///d is /d
1912 1908 # path of file:///d:/ is d:/, not /d:/
1913 1909 if path and not hasdriveletter(path):
1914 1910 path = '/' + path
1915 1911
1916 1912 if self.host and '@' in self.host:
1917 1913 self.user, self.host = self.host.rsplit('@', 1)
1918 1914 if ':' in self.user:
1919 1915 self.user, self.passwd = self.user.split(':', 1)
1920 1916 if not self.host:
1921 1917 self.host = None
1922 1918
1923 1919 # Don't split on colons in IPv6 addresses without ports
1924 1920 if (self.host and ':' in self.host and
1925 1921 not (self.host.startswith('[') and self.host.endswith(']'))):
1926 1922 self._hostport = self.host
1927 1923 self.host, self.port = self.host.rsplit(':', 1)
1928 1924 if not self.host:
1929 1925 self.host = None
1930 1926
1931 1927 if (self.host and self.scheme == 'file' and
1932 1928 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1933 1929 raise Abort(_('file:// URLs can only refer to localhost'))
1934 1930
1935 1931 self.path = path
1936 1932
1937 1933 # leave the query string escaped
1938 1934 for a in ('user', 'passwd', 'host', 'port',
1939 1935 'path', 'fragment'):
1940 1936 v = getattr(self, a)
1941 1937 if v is not None:
1942 1938 setattr(self, a, _urlunquote(v))
1943 1939
1944 1940 def __repr__(self):
1945 1941 attrs = []
1946 1942 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1947 1943 'query', 'fragment'):
1948 1944 v = getattr(self, a)
1949 1945 if v is not None:
1950 1946 attrs.append('%s: %r' % (a, v))
1951 1947 return '<url %s>' % ', '.join(attrs)
1952 1948
1953 1949 def __str__(self):
1954 1950 r"""Join the URL's components back into a URL string.
1955 1951
1956 1952 Examples:
1957 1953
1958 1954 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1959 1955 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1960 1956 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1961 1957 'http://user:pw@host:80/?foo=bar&baz=42'
1962 1958 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1963 1959 'http://user:pw@host:80/?foo=bar%3dbaz'
1964 1960 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1965 1961 'ssh://user:pw@[::1]:2200//home/joe#'
1966 1962 >>> str(url('http://localhost:80//'))
1967 1963 'http://localhost:80//'
1968 1964 >>> str(url('http://localhost:80/'))
1969 1965 'http://localhost:80/'
1970 1966 >>> str(url('http://localhost:80'))
1971 1967 'http://localhost:80/'
1972 1968 >>> str(url('bundle:foo'))
1973 1969 'bundle:foo'
1974 1970 >>> str(url('bundle://../foo'))
1975 1971 'bundle:../foo'
1976 1972 >>> str(url('path'))
1977 1973 'path'
1978 1974 >>> str(url('file:///tmp/foo/bar'))
1979 1975 'file:///tmp/foo/bar'
1980 1976 >>> str(url('file:///c:/tmp/foo/bar'))
1981 1977 'file:///c:/tmp/foo/bar'
1982 1978 >>> print url(r'bundle:foo\bar')
1983 1979 bundle:foo\bar
1984 1980 >>> print url(r'file:///D:\data\hg')
1985 1981 file:///D:\data\hg
1986 1982 """
1987 1983 if self._localpath:
1988 1984 s = self.path
1989 1985 if self.scheme == 'bundle':
1990 1986 s = 'bundle:' + s
1991 1987 if self.fragment:
1992 1988 s += '#' + self.fragment
1993 1989 return s
1994 1990
1995 1991 s = self.scheme + ':'
1996 1992 if self.user or self.passwd or self.host:
1997 1993 s += '//'
1998 1994 elif self.scheme and (not self.path or self.path.startswith('/')
1999 1995 or hasdriveletter(self.path)):
2000 1996 s += '//'
2001 1997 if hasdriveletter(self.path):
2002 1998 s += '/'
2003 1999 if self.user:
2004 2000 s += urllib.quote(self.user, safe=self._safechars)
2005 2001 if self.passwd:
2006 2002 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2007 2003 if self.user or self.passwd:
2008 2004 s += '@'
2009 2005 if self.host:
2010 2006 if not (self.host.startswith('[') and self.host.endswith(']')):
2011 2007 s += urllib.quote(self.host)
2012 2008 else:
2013 2009 s += self.host
2014 2010 if self.port:
2015 2011 s += ':' + urllib.quote(self.port)
2016 2012 if self.host:
2017 2013 s += '/'
2018 2014 if self.path:
2019 2015 # TODO: similar to the query string, we should not unescape the
2020 2016 # path when we store it, the path might contain '%2f' = '/',
2021 2017 # which we should *not* escape.
2022 2018 s += urllib.quote(self.path, safe=self._safepchars)
2023 2019 if self.query:
2024 2020 # we store the query in escaped form.
2025 2021 s += '?' + self.query
2026 2022 if self.fragment is not None:
2027 2023 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2028 2024 return s
2029 2025
2030 2026 def authinfo(self):
2031 2027 user, passwd = self.user, self.passwd
2032 2028 try:
2033 2029 self.user, self.passwd = None, None
2034 2030 s = str(self)
2035 2031 finally:
2036 2032 self.user, self.passwd = user, passwd
2037 2033 if not self.user:
2038 2034 return (s, None)
2039 2035 # authinfo[1] is passed to urllib2 password manager, and its
2040 2036 # URIs must not contain credentials. The host is passed in the
2041 2037 # URIs list because Python < 2.4.3 uses only that to search for
2042 2038 # a password.
2043 2039 return (s, (None, (s, self.host),
2044 2040 self.user, self.passwd or ''))
2045 2041
2046 2042 def isabs(self):
2047 2043 if self.scheme and self.scheme != 'file':
2048 2044 return True # remote URL
2049 2045 if hasdriveletter(self.path):
2050 2046 return True # absolute for our purposes - can't be joined()
2051 2047 if self.path.startswith(r'\\'):
2052 2048 return True # Windows UNC path
2053 2049 if self.path.startswith('/'):
2054 2050 return True # POSIX-style
2055 2051 return False
2056 2052
2057 2053 def localpath(self):
2058 2054 if self.scheme == 'file' or self.scheme == 'bundle':
2059 2055 path = self.path or '/'
2060 2056 # For Windows, we need to promote hosts containing drive
2061 2057 # letters to paths with drive letters.
2062 2058 if hasdriveletter(self._hostport):
2063 2059 path = self._hostport + '/' + self.path
2064 2060 elif (self.host is not None and self.path
2065 2061 and not hasdriveletter(path)):
2066 2062 path = '/' + path
2067 2063 return path
2068 2064 return self._origpath
2069 2065
2070 2066 def islocal(self):
2071 2067 '''whether localpath will return something that posixfile can open'''
2072 2068 return (not self.scheme or self.scheme == 'file'
2073 2069 or self.scheme == 'bundle')
2074 2070
2075 2071 def hasscheme(path):
2076 2072 return bool(url(path).scheme)
2077 2073
2078 2074 def hasdriveletter(path):
2079 2075 return path and path[1:2] == ':' and path[0:1].isalpha()
2080 2076
2081 2077 def urllocalpath(path):
2082 2078 return url(path, parsequery=False, parsefragment=False).localpath()
2083 2079
2084 2080 def hidepassword(u):
2085 2081 '''hide user credential in a url string'''
2086 2082 u = url(u)
2087 2083 if u.passwd:
2088 2084 u.passwd = '***'
2089 2085 return str(u)
2090 2086
2091 2087 def removeauth(u):
2092 2088 '''remove all authentication information from a url string'''
2093 2089 u = url(u)
2094 2090 u.user = u.passwd = None
2095 2091 return str(u)
2096 2092
2097 2093 def isatty(fd):
2098 2094 try:
2099 2095 return fd.isatty()
2100 2096 except AttributeError:
2101 2097 return False
2102 2098
2103 2099 timecount = unitcountfn(
2104 2100 (1, 1e3, _('%.0f s')),
2105 2101 (100, 1, _('%.1f s')),
2106 2102 (10, 1, _('%.2f s')),
2107 2103 (1, 1, _('%.3f s')),
2108 2104 (100, 0.001, _('%.1f ms')),
2109 2105 (10, 0.001, _('%.2f ms')),
2110 2106 (1, 0.001, _('%.3f ms')),
2111 2107 (100, 0.000001, _('%.1f us')),
2112 2108 (10, 0.000001, _('%.2f us')),
2113 2109 (1, 0.000001, _('%.3f us')),
2114 2110 (100, 0.000000001, _('%.1f ns')),
2115 2111 (10, 0.000000001, _('%.2f ns')),
2116 2112 (1, 0.000000001, _('%.3f ns')),
2117 2113 )
2118 2114
2119 2115 _timenesting = [0]
2120 2116
2121 2117 def timed(func):
2122 2118 '''Report the execution time of a function call to stderr.
2123 2119
2124 2120 During development, use as a decorator when you need to measure
2125 2121 the cost of a function, e.g. as follows:
2126 2122
2127 2123 @util.timed
2128 2124 def foo(a, b, c):
2129 2125 pass
2130 2126 '''
2131 2127
2132 2128 def wrapper(*args, **kwargs):
2133 2129 start = time.time()
2134 2130 indent = 2
2135 2131 _timenesting[0] += indent
2136 2132 try:
2137 2133 return func(*args, **kwargs)
2138 2134 finally:
2139 2135 elapsed = time.time() - start
2140 2136 _timenesting[0] -= indent
2141 2137 sys.stderr.write('%s%s: %s\n' %
2142 2138 (' ' * _timenesting[0], func.__name__,
2143 2139 timecount(elapsed)))
2144 2140 return wrapper
2145 2141
2146 2142 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2147 2143 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2148 2144
2149 2145 def sizetoint(s):
2150 2146 '''Convert a space specifier to a byte count.
2151 2147
2152 2148 >>> sizetoint('30')
2153 2149 30
2154 2150 >>> sizetoint('2.2kb')
2155 2151 2252
2156 2152 >>> sizetoint('6M')
2157 2153 6291456
2158 2154 '''
2159 2155 t = s.strip().lower()
2160 2156 try:
2161 2157 for k, u in _sizeunits:
2162 2158 if t.endswith(k):
2163 2159 return int(float(t[:-len(k)]) * u)
2164 2160 return int(t)
2165 2161 except ValueError:
2166 2162 raise error.ParseError(_("couldn't parse size: %s") % s)
2167 2163
2168 2164 class hooks(object):
2169 2165 '''A collection of hook functions that can be used to extend a
2170 2166 function's behaviour. Hooks are called in lexicographic order,
2171 2167 based on the names of their sources.'''
2172 2168
2173 2169 def __init__(self):
2174 2170 self._hooks = []
2175 2171
2176 2172 def add(self, source, hook):
2177 2173 self._hooks.append((source, hook))
2178 2174
2179 2175 def __call__(self, *args):
2180 2176 self._hooks.sort(key=lambda x: x[0])
2181 2177 results = []
2182 2178 for source, hook in self._hooks:
2183 2179 results.append(hook(*args))
2184 2180 return results
2185 2181
2186 2182 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2187 2183 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2188 2184 Skips the 'skip' last entries. By default it will flush stdout first.
2189 2185 It can be used everywhere and do intentionally not require an ui object.
2190 2186 Not be used in production code but very convenient while developing.
2191 2187 '''
2192 2188 if otherf:
2193 2189 otherf.flush()
2194 2190 f.write('%s at:\n' % msg)
2195 2191 entries = [('%s:%s' % (fn, ln), func)
2196 2192 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2197 2193 if entries:
2198 2194 fnmax = max(len(entry[0]) for entry in entries)
2199 2195 for fnln, func in entries:
2200 2196 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2201 2197 f.flush()
2202 2198
2203 2199 class dirs(object):
2204 2200 '''a multiset of directory names from a dirstate or manifest'''
2205 2201
2206 2202 def __init__(self, map, skip=None):
2207 2203 self._dirs = {}
2208 2204 addpath = self.addpath
2209 2205 if safehasattr(map, 'iteritems') and skip is not None:
2210 2206 for f, s in map.iteritems():
2211 2207 if s[0] != skip:
2212 2208 addpath(f)
2213 2209 else:
2214 2210 for f in map:
2215 2211 addpath(f)
2216 2212
2217 2213 def addpath(self, path):
2218 2214 dirs = self._dirs
2219 2215 for base in finddirs(path):
2220 2216 if base in dirs:
2221 2217 dirs[base] += 1
2222 2218 return
2223 2219 dirs[base] = 1
2224 2220
2225 2221 def delpath(self, path):
2226 2222 dirs = self._dirs
2227 2223 for base in finddirs(path):
2228 2224 if dirs[base] > 1:
2229 2225 dirs[base] -= 1
2230 2226 return
2231 2227 del dirs[base]
2232 2228
2233 2229 def __iter__(self):
2234 2230 return self._dirs.iterkeys()
2235 2231
2236 2232 def __contains__(self, d):
2237 2233 return d in self._dirs
2238 2234
2239 2235 if safehasattr(parsers, 'dirs'):
2240 2236 dirs = parsers.dirs
2241 2237
2242 2238 def finddirs(path):
2243 2239 pos = path.rfind('/')
2244 2240 while pos != -1:
2245 2241 yield path[:pos]
2246 2242 pos = path.rfind('/', 0, pos)
2247 2243
2248 2244 # convenient shortcut
2249 2245 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now