##// END OF EJS Templates
fix handling of daylight saving time
Benoit Boissinot -
r1482:4d38b85e default
parent child Browse files
Show More
@@ -1,626 +1,628 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5
6 6 This software may be used and distributed according to the terms
7 7 of the GNU General Public License, incorporated herein by reference.
8 8
9 9 This contains helper routines that are independent of the SCM core and hide
10 10 platform-specific details from the core.
11 11 """
12 12
13 13 import os, errno
14 14 from i18n import gettext as _
15 15 from demandload import *
16 16 demandload(globals(), "re cStringIO shutil popen2 tempfile threading time")
17 17
18 18 def pipefilter(s, cmd):
19 19 '''filter string S through command CMD, returning its output'''
20 20 (pout, pin) = popen2.popen2(cmd, -1, 'b')
21 21 def writer():
22 22 pin.write(s)
23 23 pin.close()
24 24
25 25 # we should use select instead on UNIX, but this will work on most
26 26 # systems, including Windows
27 27 w = threading.Thread(target=writer)
28 28 w.start()
29 29 f = pout.read()
30 30 pout.close()
31 31 w.join()
32 32 return f
33 33
34 34 def tempfilter(s, cmd):
35 35 '''filter string S through a pair of temporary files with CMD.
36 36 CMD is used as a template to create the real command to be run,
37 37 with the strings INFILE and OUTFILE replaced by the real names of
38 38 the temporary files generated.'''
39 39 inname, outname = None, None
40 40 try:
41 41 infd, inname = tempfile.mkstemp(prefix='hgfin')
42 42 fp = os.fdopen(infd, 'wb')
43 43 fp.write(s)
44 44 fp.close()
45 45 outfd, outname = tempfile.mkstemp(prefix='hgfout')
46 46 os.close(outfd)
47 47 cmd = cmd.replace('INFILE', inname)
48 48 cmd = cmd.replace('OUTFILE', outname)
49 49 code = os.system(cmd)
50 50 if code: raise Abort(_("command '%s' failed: %s") %
51 51 (cmd, explain_exit(code)))
52 52 return open(outname, 'rb').read()
53 53 finally:
54 54 try:
55 55 if inname: os.unlink(inname)
56 56 except: pass
57 57 try:
58 58 if outname: os.unlink(outname)
59 59 except: pass
60 60
61 61 filtertable = {
62 62 'tempfile:': tempfilter,
63 63 'pipe:': pipefilter,
64 64 }
65 65
66 66 def filter(s, cmd):
67 67 "filter a string through a command that transforms its input to its output"
68 68 for name, fn in filtertable.iteritems():
69 69 if cmd.startswith(name):
70 70 return fn(s, cmd[len(name):].lstrip())
71 71 return pipefilter(s, cmd)
72 72
73 73 def patch(strip, patchname, ui):
74 74 """apply the patch <patchname> to the working directory.
75 75 a list of patched files is returned"""
76 76 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
77 77 files = {}
78 78 for line in fp:
79 79 line = line.rstrip()
80 80 ui.status("%s\n" % line)
81 81 if line.startswith('patching file '):
82 82 pf = parse_patch_output(line)
83 83 files.setdefault(pf, 1)
84 84 code = fp.close()
85 85 if code:
86 86 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
87 87 return files.keys()
88 88
89 89 def binary(s):
90 90 """return true if a string is binary data using diff's heuristic"""
91 91 if s and '\0' in s[:4096]:
92 92 return True
93 93 return False
94 94
95 95 def unique(g):
96 96 """return the uniq elements of iterable g"""
97 97 seen = {}
98 98 for f in g:
99 99 if f not in seen:
100 100 seen[f] = 1
101 101 yield f
102 102
103 103 class Abort(Exception):
104 104 """Raised if a command needs to print an error and exit."""
105 105
106 106 def always(fn): return True
107 107 def never(fn): return False
108 108
109 109 def globre(pat, head='^', tail='$'):
110 110 "convert a glob pattern into a regexp"
111 111 i, n = 0, len(pat)
112 112 res = ''
113 113 group = False
114 114 def peek(): return i < n and pat[i]
115 115 while i < n:
116 116 c = pat[i]
117 117 i = i+1
118 118 if c == '*':
119 119 if peek() == '*':
120 120 i += 1
121 121 res += '.*'
122 122 else:
123 123 res += '[^/]*'
124 124 elif c == '?':
125 125 res += '.'
126 126 elif c == '[':
127 127 j = i
128 128 if j < n and pat[j] in '!]':
129 129 j += 1
130 130 while j < n and pat[j] != ']':
131 131 j += 1
132 132 if j >= n:
133 133 res += '\\['
134 134 else:
135 135 stuff = pat[i:j].replace('\\','\\\\')
136 136 i = j + 1
137 137 if stuff[0] == '!':
138 138 stuff = '^' + stuff[1:]
139 139 elif stuff[0] == '^':
140 140 stuff = '\\' + stuff
141 141 res = '%s[%s]' % (res, stuff)
142 142 elif c == '{':
143 143 group = True
144 144 res += '(?:'
145 145 elif c == '}' and group:
146 146 res += ')'
147 147 group = False
148 148 elif c == ',' and group:
149 149 res += '|'
150 150 else:
151 151 res += re.escape(c)
152 152 return head + res + tail
153 153
154 154 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
155 155
156 156 def pathto(n1, n2):
157 157 '''return the relative path from one place to another.
158 158 this returns a path in the form used by the local filesystem, not hg.'''
159 159 if not n1: return localpath(n2)
160 160 a, b = n1.split('/'), n2.split('/')
161 161 a.reverse(), b.reverse()
162 162 while a and b and a[-1] == b[-1]:
163 163 a.pop(), b.pop()
164 164 b.reverse()
165 165 return os.sep.join((['..'] * len(a)) + b)
166 166
167 167 def canonpath(root, cwd, myname):
168 168 """return the canonical path of myname, given cwd and root"""
169 169 rootsep = root + os.sep
170 170 name = myname
171 171 if not name.startswith(os.sep):
172 172 name = os.path.join(root, cwd, name)
173 173 name = os.path.normpath(name)
174 174 if name.startswith(rootsep):
175 175 return pconvert(name[len(rootsep):])
176 176 elif name == root:
177 177 return ''
178 178 else:
179 179 raise Abort('%s not under root' % myname)
180 180
181 181 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
182 182 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob')
183 183
184 184 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
185 185 if os.name == 'nt':
186 186 dflt_pat = 'glob'
187 187 else:
188 188 dflt_pat = 'relpath'
189 189 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat)
190 190
191 191 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat):
192 192 """build a function to match a set of file patterns
193 193
194 194 arguments:
195 195 canonroot - the canonical root of the tree you're matching against
196 196 cwd - the current working directory, if relevant
197 197 names - patterns to find
198 198 inc - patterns to include
199 199 exc - patterns to exclude
200 200 head - a regex to prepend to patterns to control whether a match is rooted
201 201
202 202 a pattern is one of:
203 203 'glob:<rooted glob>'
204 204 're:<rooted regexp>'
205 205 'path:<rooted path>'
206 206 'relglob:<relative glob>'
207 207 'relpath:<relative path>'
208 208 'relre:<relative regexp>'
209 209 '<rooted path or regexp>'
210 210
211 211 returns:
212 212 a 3-tuple containing
213 213 - list of explicit non-pattern names passed in
214 214 - a bool match(filename) function
215 215 - a bool indicating if any patterns were passed in
216 216
217 217 todo:
218 218 make head regex a rooted bool
219 219 """
220 220
221 221 def patkind(name, dflt_pat='glob'):
222 222 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
223 223 if name.startswith(prefix + ':'): return name.split(':', 1)
224 224 return dflt_pat, name
225 225
226 226 def contains_glob(name):
227 227 for c in name:
228 228 if c in _globchars: return True
229 229 return False
230 230
231 231 def regex(kind, name, tail):
232 232 '''convert a pattern into a regular expression'''
233 233 if kind == 're':
234 234 return name
235 235 elif kind == 'path':
236 236 return '^' + re.escape(name) + '(?:/|$)'
237 237 elif kind == 'relglob':
238 238 return head + globre(name, '(?:|.*/)', tail)
239 239 elif kind == 'relpath':
240 240 return head + re.escape(name) + tail
241 241 elif kind == 'relre':
242 242 if name.startswith('^'):
243 243 return name
244 244 return '.*' + name
245 245 return head + globre(name, '', tail)
246 246
247 247 def matchfn(pats, tail):
248 248 """build a matching function from a set of patterns"""
249 249 if not pats:
250 250 return
251 251 matches = []
252 252 for k, p in pats:
253 253 try:
254 254 pat = '(?:%s)' % regex(k, p, tail)
255 255 matches.append(re.compile(pat).match)
256 256 except re.error, inst:
257 257 raise Abort("invalid pattern: %s:%s" % (k, p))
258 258
259 259 def buildfn(text):
260 260 for m in matches:
261 261 r = m(text)
262 262 if r:
263 263 return r
264 264
265 265 return buildfn
266 266
267 267 def globprefix(pat):
268 268 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
269 269 root = []
270 270 for p in pat.split(os.sep):
271 271 if contains_glob(p): break
272 272 root.append(p)
273 273 return '/'.join(root)
274 274
275 275 pats = []
276 276 files = []
277 277 roots = []
278 278 for kind, name in [patkind(p, dflt_pat) for p in names]:
279 279 if kind in ('glob', 'relpath'):
280 280 name = canonpath(canonroot, cwd, name)
281 281 if name == '':
282 282 kind, name = 'glob', '**'
283 283 if kind in ('glob', 'path', 're'):
284 284 pats.append((kind, name))
285 285 if kind == 'glob':
286 286 root = globprefix(name)
287 287 if root: roots.append(root)
288 288 elif kind == 'relpath':
289 289 files.append((kind, name))
290 290 roots.append(name)
291 291
292 292 patmatch = matchfn(pats, '$') or always
293 293 filematch = matchfn(files, '(?:/|$)') or always
294 294 incmatch = always
295 295 if inc:
296 296 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
297 297 excmatch = lambda fn: False
298 298 if exc:
299 299 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
300 300
301 301 return (roots,
302 302 lambda fn: (incmatch(fn) and not excmatch(fn) and
303 303 (fn.endswith('/') or
304 304 (not pats and not files) or
305 305 (pats and patmatch(fn)) or
306 306 (files and filematch(fn)))),
307 307 (inc or exc or (pats and pats != [('glob', '**')])) and True)
308 308
309 309 def system(cmd, errprefix=None):
310 310 """execute a shell command that must succeed"""
311 311 rc = os.system(cmd)
312 312 if rc:
313 313 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
314 314 explain_exit(rc)[0])
315 315 if errprefix:
316 316 errmsg = "%s: %s" % (errprefix, errmsg)
317 317 raise Abort(errmsg)
318 318
319 319 def rename(src, dst):
320 320 """forcibly rename a file"""
321 321 try:
322 322 os.rename(src, dst)
323 323 except:
324 324 os.unlink(dst)
325 325 os.rename(src, dst)
326 326
327 327 def unlink(f):
328 328 """unlink and remove the directory if it is empty"""
329 329 os.unlink(f)
330 330 # try removing directories that might now be empty
331 331 try: os.removedirs(os.path.dirname(f))
332 332 except: pass
333 333
334 334 def copyfiles(src, dst, hardlink=None):
335 335 """Copy a directory tree using hardlinks if possible"""
336 336
337 337 if hardlink is None:
338 338 hardlink = (os.stat(src).st_dev ==
339 339 os.stat(os.path.dirname(dst)).st_dev)
340 340
341 341 if os.path.isdir(src):
342 342 os.mkdir(dst)
343 343 for name in os.listdir(src):
344 344 srcname = os.path.join(src, name)
345 345 dstname = os.path.join(dst, name)
346 346 copyfiles(srcname, dstname, hardlink)
347 347 else:
348 348 if hardlink:
349 349 try:
350 350 os_link(src, dst)
351 351 except:
352 352 hardlink = False
353 353 shutil.copy2(src, dst)
354 354 else:
355 355 shutil.copy2(src, dst)
356 356
357 357 def opener(base):
358 358 """
359 359 return a function that opens files relative to base
360 360
361 361 this function is used to hide the details of COW semantics and
362 362 remote file access from higher level code.
363 363 """
364 364 p = base
365 365 def o(path, mode="r", text=False):
366 366 f = os.path.join(p, path)
367 367
368 368 if not text:
369 369 mode += "b" # for that other OS
370 370
371 371 if mode[0] != "r":
372 372 try:
373 373 nlink = nlinks(f)
374 374 except OSError:
375 375 d = os.path.dirname(f)
376 376 if not os.path.isdir(d):
377 377 os.makedirs(d)
378 378 else:
379 379 if nlink > 1:
380 380 d, fn = os.path.split(f)
381 381 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
382 382 fp = os.fdopen(fd, "wb")
383 383 try:
384 384 fp.write(file(f, "rb").read())
385 385 except:
386 386 try: os.unlink(temp)
387 387 except: pass
388 388 raise
389 389 fp.close()
390 390 rename(temp, f)
391 391
392 392 return file(f, mode)
393 393
394 394 return o
395 395
396 396 def _makelock_file(info, pathname):
397 397 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
398 398 os.write(ld, info)
399 399 os.close(ld)
400 400
401 401 def _readlock_file(pathname):
402 402 return file(pathname).read()
403 403
404 404 def nlinks(pathname):
405 405 """Return number of hardlinks for the given file."""
406 406 return os.stat(pathname).st_nlink
407 407
408 408 if hasattr(os, 'link'):
409 409 os_link = os.link
410 410 else:
411 411 def os_link(src, dst):
412 412 raise OSError(0, _("Hardlinks not supported"))
413 413
414 414 # Platform specific variants
415 415 if os.name == 'nt':
416 416 demandload(globals(), "msvcrt")
417 417 nulldev = 'NUL:'
418 418
419 419 try:
420 420 import win32api, win32process
421 421 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
422 422 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
423 423
424 424 except ImportError:
425 425 systemrc = r'c:\mercurial\mercurial.ini'
426 426 pass
427 427
428 428 rcpath = (systemrc,
429 429 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
430 430
431 431 def parse_patch_output(output_line):
432 432 """parses the output produced by patch and returns the file name"""
433 433 pf = output_line[14:]
434 434 if pf[0] == '`':
435 435 pf = pf[1:-1] # Remove the quotes
436 436 return pf
437 437
438 438 try: # ActivePython can create hard links using win32file module
439 439 import win32file
440 440
441 441 def os_link(src, dst): # NB will only succeed on NTFS
442 442 win32file.CreateHardLink(dst, src)
443 443
444 444 def nlinks(pathname):
445 445 """Return number of hardlinks for the given file."""
446 446 try:
447 447 fh = win32file.CreateFile(pathname,
448 448 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
449 449 None, win32file.OPEN_EXISTING, 0, None)
450 450 res = win32file.GetFileInformationByHandle(fh)
451 451 fh.Close()
452 452 return res[7]
453 453 except:
454 454 return os.stat(pathname).st_nlink
455 455
456 456 except ImportError:
457 457 pass
458 458
459 459 def is_exec(f, last):
460 460 return last
461 461
462 462 def set_exec(f, mode):
463 463 pass
464 464
465 465 def set_binary(fd):
466 466 msvcrt.setmode(fd.fileno(), os.O_BINARY)
467 467
468 468 def pconvert(path):
469 469 return path.replace("\\", "/")
470 470
471 471 def localpath(path):
472 472 return path.replace('/', '\\')
473 473
474 474 def normpath(path):
475 475 return pconvert(os.path.normpath(path))
476 476
477 477 makelock = _makelock_file
478 478 readlock = _readlock_file
479 479
480 480 def explain_exit(code):
481 481 return _("exited with status %d") % code, code
482 482
483 483 else:
484 484 nulldev = '/dev/null'
485 485
486 486 hgrcd = '/etc/mercurial/hgrc.d'
487 487 hgrcs = []
488 488 if os.path.isdir(hgrcd):
489 489 hgrcs = [f for f in os.listdir(hgrcd) if f.endswith(".rc")]
490 490 rcpath = map(os.path.normpath, hgrcs +
491 491 ['/etc/mercurial/hgrc', os.path.expanduser('~/.hgrc')])
492 492
493 493 def parse_patch_output(output_line):
494 494 """parses the output produced by patch and returns the file name"""
495 495 return output_line[14:]
496 496
497 497 def is_exec(f, last):
498 498 """check whether a file is executable"""
499 499 return (os.stat(f).st_mode & 0100 != 0)
500 500
501 501 def set_exec(f, mode):
502 502 s = os.stat(f).st_mode
503 503 if (s & 0100 != 0) == mode:
504 504 return
505 505 if mode:
506 506 # Turn on +x for every +r bit when making a file executable
507 507 # and obey umask.
508 508 umask = os.umask(0)
509 509 os.umask(umask)
510 510 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
511 511 else:
512 512 os.chmod(f, s & 0666)
513 513
514 514 def set_binary(fd):
515 515 pass
516 516
517 517 def pconvert(path):
518 518 return path
519 519
520 520 def localpath(path):
521 521 return path
522 522
523 523 normpath = os.path.normpath
524 524
525 525 def makelock(info, pathname):
526 526 try:
527 527 os.symlink(info, pathname)
528 528 except OSError, why:
529 529 if why.errno == errno.EEXIST:
530 530 raise
531 531 else:
532 532 _makelock_file(info, pathname)
533 533
534 534 def readlock(pathname):
535 535 try:
536 536 return os.readlink(pathname)
537 537 except OSError, why:
538 538 if why.errno == errno.EINVAL:
539 539 return _readlock_file(pathname)
540 540 else:
541 541 raise
542 542
543 543 def explain_exit(code):
544 544 """return a 2-tuple (desc, code) describing a process's status"""
545 545 if os.WIFEXITED(code):
546 546 val = os.WEXITSTATUS(code)
547 547 return _("exited with status %d") % val, val
548 548 elif os.WIFSIGNALED(code):
549 549 val = os.WTERMSIG(code)
550 550 return _("killed by signal %d") % val, val
551 551 elif os.WIFSTOPPED(code):
552 552 val = os.WSTOPSIG(code)
553 553 return _("stopped by signal %d") % val, val
554 554 raise ValueError(_("invalid exit code"))
555 555
556 556 class chunkbuffer(object):
557 557 """Allow arbitrary sized chunks of data to be efficiently read from an
558 558 iterator over chunks of arbitrary size."""
559 559
560 560 def __init__(self, in_iter, targetsize = 2**16):
561 561 """in_iter is the iterator that's iterating over the input chunks.
562 562 targetsize is how big a buffer to try to maintain."""
563 563 self.in_iter = iter(in_iter)
564 564 self.buf = ''
565 565 self.targetsize = int(targetsize)
566 566 if self.targetsize <= 0:
567 567 raise ValueError(_("targetsize must be greater than 0, was %d") %
568 568 targetsize)
569 569 self.iterempty = False
570 570
571 571 def fillbuf(self):
572 572 """Ignore target size; read every chunk from iterator until empty."""
573 573 if not self.iterempty:
574 574 collector = cStringIO.StringIO()
575 575 collector.write(self.buf)
576 576 for ch in self.in_iter:
577 577 collector.write(ch)
578 578 self.buf = collector.getvalue()
579 579 self.iterempty = True
580 580
581 581 def read(self, l):
582 582 """Read L bytes of data from the iterator of chunks of data.
583 583 Returns less than L bytes if the iterator runs dry."""
584 584 if l > len(self.buf) and not self.iterempty:
585 585 # Clamp to a multiple of self.targetsize
586 586 targetsize = self.targetsize * ((l // self.targetsize) + 1)
587 587 collector = cStringIO.StringIO()
588 588 collector.write(self.buf)
589 589 collected = len(self.buf)
590 590 for chunk in self.in_iter:
591 591 collector.write(chunk)
592 592 collected += len(chunk)
593 593 if collected >= targetsize:
594 594 break
595 595 if collected < targetsize:
596 596 self.iterempty = True
597 597 self.buf = collector.getvalue()
598 598 s, self.buf = self.buf[:l], buffer(self.buf, l)
599 599 return s
600 600
601 601 def filechunkiter(f, size = 65536):
602 602 """Create a generator that produces all the data in the file size
603 603 (default 65536) bytes at a time. Chunks may be less than size
604 604 bytes if the chunk is the last chunk in the file, or the file is a
605 605 socket or some other type of file that sometimes reads less data
606 606 than is requested."""
607 607 s = f.read(size)
608 608 while len(s) > 0:
609 609 yield s
610 610 s = f.read(size)
611 611
612 612 def makedate():
613 t = time.time()
614 if time.daylight: tz = time.altzone
615 else: tz = time.timezone
616 return t, tz
613 lt = time.localtime()
614 if lt[8] == 1 and time.daylight:
615 tz = time.altzone
616 else:
617 tz = time.timezone
618 return time.mktime(lt), tz
617 619
618 620 def datestr(date=None, format='%c'):
619 621 """represent a (unixtime, offset) tuple as a localized time.
620 622 unixtime is seconds since the epoch, and offset is the time zone's
621 623 number of seconds away from UTC."""
622 624 t, tz = date or makedate()
623 625 return ("%s %+03d%02d" %
624 626 (time.strftime(format, time.gmtime(float(t) - tz)),
625 627 -tz / 3600,
626 628 ((-tz % 3600) / 60)))
General Comments 0
You need to be logged in to leave comments. Login now