##// END OF EJS Templates
validate the resulting date in parsedate
Benoit Boissinot -
r2523:4ab59a3a default
parent child Browse files
Show More
@@ -1,57 +1,48 b''
1 1 # changelog.py - changelog class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from revlog import *
9 9 from i18n import gettext as _
10 10 from demandload import demandload
11 11 demandload(globals(), "os time util")
12 12
13 13 class changelog(revlog):
14 14 def __init__(self, opener, defversion=REVLOGV0):
15 15 revlog.__init__(self, opener, "00changelog.i", "00changelog.d",
16 16 defversion)
17 17
18 18 def extract(self, text):
19 19 if not text:
20 20 return (nullid, "", (0, 0), [], "")
21 21 last = text.index("\n\n")
22 22 desc = text[last + 2:]
23 23 l = text[:last].splitlines()
24 24 manifest = bin(l[0])
25 25 user = l[1]
26 26 date = l[2].split(' ')
27 27 time = float(date.pop(0))
28 28 try:
29 29 # various tools did silly things with the time zone field.
30 30 timezone = int(date[0])
31 31 except:
32 32 timezone = 0
33 33 files = l[3:]
34 34 return (manifest, user, (time, timezone), files, desc)
35 35
36 36 def read(self, node):
37 37 return self.extract(self.revision(node))
38 38
39 39 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
40 40 user=None, date=None):
41 41 if date:
42 # validate explicit (probably user-specified) date and
43 # time zone offset. values must fit in signed 32 bits for
44 # current 32-bit linux runtimes. timezones go from UTC-12
45 # to UTC+14
46 when, offset = util.parsedate(date)
47 if abs(when) > 0x7fffffff:
48 raise ValueError(_('date exceeds 32 bits: %d') % when)
49 if offset < -50400 or offset > 43200:
50 raise ValueError(_('impossible time zone offset: %d') % offset)
51 parseddate = "%d %d" % (when, offset)
42 parseddate = "%d %d" % util.parsedate(date)
52 43 else:
53 44 parseddate = "%d %d" % util.makedate()
54 45 list.sort()
55 46 l = [hex(manifest), user, parseddate] + list + ["", desc]
56 47 text = "\n".join(l)
57 48 return self.addrevision(text, transaction, self.count(), p1, p2)
@@ -1,938 +1,950 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 from i18n import gettext as _
14 14 from demandload import *
15 15 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
16 16 demandload(globals(), "os threading time")
17 17
18 18 class SignalInterrupt(Exception):
19 19 """Exception raised on SIGTERM and SIGHUP."""
20 20
21 21 def pipefilter(s, cmd):
22 22 '''filter string S through command CMD, returning its output'''
23 23 (pout, pin) = popen2.popen2(cmd, -1, 'b')
24 24 def writer():
25 25 try:
26 26 pin.write(s)
27 27 pin.close()
28 28 except IOError, inst:
29 29 if inst.errno != errno.EPIPE:
30 30 raise
31 31
32 32 # we should use select instead on UNIX, but this will work on most
33 33 # systems, including Windows
34 34 w = threading.Thread(target=writer)
35 35 w.start()
36 36 f = pout.read()
37 37 pout.close()
38 38 w.join()
39 39 return f
40 40
41 41 def tempfilter(s, cmd):
42 42 '''filter string S through a pair of temporary files with CMD.
43 43 CMD is used as a template to create the real command to be run,
44 44 with the strings INFILE and OUTFILE replaced by the real names of
45 45 the temporary files generated.'''
46 46 inname, outname = None, None
47 47 try:
48 48 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
49 49 fp = os.fdopen(infd, 'wb')
50 50 fp.write(s)
51 51 fp.close()
52 52 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
53 53 os.close(outfd)
54 54 cmd = cmd.replace('INFILE', inname)
55 55 cmd = cmd.replace('OUTFILE', outname)
56 56 code = os.system(cmd)
57 57 if code: raise Abort(_("command '%s' failed: %s") %
58 58 (cmd, explain_exit(code)))
59 59 return open(outname, 'rb').read()
60 60 finally:
61 61 try:
62 62 if inname: os.unlink(inname)
63 63 except: pass
64 64 try:
65 65 if outname: os.unlink(outname)
66 66 except: pass
67 67
68 68 filtertable = {
69 69 'tempfile:': tempfilter,
70 70 'pipe:': pipefilter,
71 71 }
72 72
73 73 def filter(s, cmd):
74 74 "filter a string through a command that transforms its input to its output"
75 75 for name, fn in filtertable.iteritems():
76 76 if cmd.startswith(name):
77 77 return fn(s, cmd[len(name):].lstrip())
78 78 return pipefilter(s, cmd)
79 79
80 80 def find_in_path(name, path, default=None):
81 81 '''find name in search path. path can be string (will be split
82 82 with os.pathsep), or iterable thing that returns strings. if name
83 83 found, return path to name. else return default.'''
84 84 if isinstance(path, str):
85 85 path = path.split(os.pathsep)
86 86 for p in path:
87 87 p_name = os.path.join(p, name)
88 88 if os.path.exists(p_name):
89 89 return p_name
90 90 return default
91 91
92 92 def patch(strip, patchname, ui):
93 93 """apply the patch <patchname> to the working directory.
94 94 a list of patched files is returned"""
95 95 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
96 96 fp = os.popen('%s -p%d < "%s"' % (patcher, strip, patchname))
97 97 files = {}
98 98 for line in fp:
99 99 line = line.rstrip()
100 100 ui.status("%s\n" % line)
101 101 if line.startswith('patching file '):
102 102 pf = parse_patch_output(line)
103 103 files.setdefault(pf, 1)
104 104 code = fp.close()
105 105 if code:
106 106 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
107 107 return files.keys()
108 108
109 109 def binary(s):
110 110 """return true if a string is binary data using diff's heuristic"""
111 111 if s and '\0' in s[:4096]:
112 112 return True
113 113 return False
114 114
115 115 def unique(g):
116 116 """return the uniq elements of iterable g"""
117 117 seen = {}
118 118 for f in g:
119 119 if f not in seen:
120 120 seen[f] = 1
121 121 yield f
122 122
123 123 class Abort(Exception):
124 124 """Raised if a command needs to print an error and exit."""
125 125
126 126 def always(fn): return True
127 127 def never(fn): return False
128 128
129 129 def patkind(name, dflt_pat='glob'):
130 130 """Split a string into an optional pattern kind prefix and the
131 131 actual pattern."""
132 132 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
133 133 if name.startswith(prefix + ':'): return name.split(':', 1)
134 134 return dflt_pat, name
135 135
136 136 def globre(pat, head='^', tail='$'):
137 137 "convert a glob pattern into a regexp"
138 138 i, n = 0, len(pat)
139 139 res = ''
140 140 group = False
141 141 def peek(): return i < n and pat[i]
142 142 while i < n:
143 143 c = pat[i]
144 144 i = i+1
145 145 if c == '*':
146 146 if peek() == '*':
147 147 i += 1
148 148 res += '.*'
149 149 else:
150 150 res += '[^/]*'
151 151 elif c == '?':
152 152 res += '.'
153 153 elif c == '[':
154 154 j = i
155 155 if j < n and pat[j] in '!]':
156 156 j += 1
157 157 while j < n and pat[j] != ']':
158 158 j += 1
159 159 if j >= n:
160 160 res += '\\['
161 161 else:
162 162 stuff = pat[i:j].replace('\\','\\\\')
163 163 i = j + 1
164 164 if stuff[0] == '!':
165 165 stuff = '^' + stuff[1:]
166 166 elif stuff[0] == '^':
167 167 stuff = '\\' + stuff
168 168 res = '%s[%s]' % (res, stuff)
169 169 elif c == '{':
170 170 group = True
171 171 res += '(?:'
172 172 elif c == '}' and group:
173 173 res += ')'
174 174 group = False
175 175 elif c == ',' and group:
176 176 res += '|'
177 177 elif c == '\\':
178 178 p = peek()
179 179 if p:
180 180 i += 1
181 181 res += re.escape(p)
182 182 else:
183 183 res += re.escape(c)
184 184 else:
185 185 res += re.escape(c)
186 186 return head + res + tail
187 187
188 188 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
189 189
190 190 def pathto(n1, n2):
191 191 '''return the relative path from one place to another.
192 192 this returns a path in the form used by the local filesystem, not hg.'''
193 193 if not n1: return localpath(n2)
194 194 a, b = n1.split('/'), n2.split('/')
195 195 a.reverse()
196 196 b.reverse()
197 197 while a and b and a[-1] == b[-1]:
198 198 a.pop()
199 199 b.pop()
200 200 b.reverse()
201 201 return os.sep.join((['..'] * len(a)) + b)
202 202
203 203 def canonpath(root, cwd, myname):
204 204 """return the canonical path of myname, given cwd and root"""
205 205 if root == os.sep:
206 206 rootsep = os.sep
207 207 elif root.endswith(os.sep):
208 208 rootsep = root
209 209 else:
210 210 rootsep = root + os.sep
211 211 name = myname
212 212 if not os.path.isabs(name):
213 213 name = os.path.join(root, cwd, name)
214 214 name = os.path.normpath(name)
215 215 if name != rootsep and name.startswith(rootsep):
216 216 name = name[len(rootsep):]
217 217 audit_path(name)
218 218 return pconvert(name)
219 219 elif name == root:
220 220 return ''
221 221 else:
222 222 # Determine whether `name' is in the hierarchy at or beneath `root',
223 223 # by iterating name=dirname(name) until that causes no change (can't
224 224 # check name == '/', because that doesn't work on windows). For each
225 225 # `name', compare dev/inode numbers. If they match, the list `rel'
226 226 # holds the reversed list of components making up the relative file
227 227 # name we want.
228 228 root_st = os.stat(root)
229 229 rel = []
230 230 while True:
231 231 try:
232 232 name_st = os.stat(name)
233 233 except OSError:
234 234 break
235 235 if samestat(name_st, root_st):
236 236 rel.reverse()
237 237 name = os.path.join(*rel)
238 238 audit_path(name)
239 239 return pconvert(name)
240 240 dirname, basename = os.path.split(name)
241 241 rel.append(basename)
242 242 if dirname == name:
243 243 break
244 244 name = dirname
245 245
246 246 raise Abort('%s not under root' % myname)
247 247
248 248 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
249 249 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
250 250
251 251 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
252 252 if os.name == 'nt':
253 253 dflt_pat = 'glob'
254 254 else:
255 255 dflt_pat = 'relpath'
256 256 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
257 257
258 258 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
259 259 """build a function to match a set of file patterns
260 260
261 261 arguments:
262 262 canonroot - the canonical root of the tree you're matching against
263 263 cwd - the current working directory, if relevant
264 264 names - patterns to find
265 265 inc - patterns to include
266 266 exc - patterns to exclude
267 267 head - a regex to prepend to patterns to control whether a match is rooted
268 268
269 269 a pattern is one of:
270 270 'glob:<rooted glob>'
271 271 're:<rooted regexp>'
272 272 'path:<rooted path>'
273 273 'relglob:<relative glob>'
274 274 'relpath:<relative path>'
275 275 'relre:<relative regexp>'
276 276 '<rooted path or regexp>'
277 277
278 278 returns:
279 279 a 3-tuple containing
280 280 - list of explicit non-pattern names passed in
281 281 - a bool match(filename) function
282 282 - a bool indicating if any patterns were passed in
283 283
284 284 todo:
285 285 make head regex a rooted bool
286 286 """
287 287
288 288 def contains_glob(name):
289 289 for c in name:
290 290 if c in _globchars: return True
291 291 return False
292 292
293 293 def regex(kind, name, tail):
294 294 '''convert a pattern into a regular expression'''
295 295 if kind == 're':
296 296 return name
297 297 elif kind == 'path':
298 298 return '^' + re.escape(name) + '(?:/|$)'
299 299 elif kind == 'relglob':
300 300 return head + globre(name, '(?:|.*/)', tail)
301 301 elif kind == 'relpath':
302 302 return head + re.escape(name) + tail
303 303 elif kind == 'relre':
304 304 if name.startswith('^'):
305 305 return name
306 306 return '.*' + name
307 307 return head + globre(name, '', tail)
308 308
309 309 def matchfn(pats, tail):
310 310 """build a matching function from a set of patterns"""
311 311 if not pats:
312 312 return
313 313 matches = []
314 314 for k, p in pats:
315 315 try:
316 316 pat = '(?:%s)' % regex(k, p, tail)
317 317 matches.append(re.compile(pat).match)
318 318 except re.error:
319 319 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
320 320 else: raise Abort("invalid pattern (%s): %s" % (k, p))
321 321
322 322 def buildfn(text):
323 323 for m in matches:
324 324 r = m(text)
325 325 if r:
326 326 return r
327 327
328 328 return buildfn
329 329
330 330 def globprefix(pat):
331 331 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
332 332 root = []
333 333 for p in pat.split(os.sep):
334 334 if contains_glob(p): break
335 335 root.append(p)
336 336 return '/'.join(root)
337 337
338 338 pats = []
339 339 files = []
340 340 roots = []
341 341 for kind, name in [patkind(p, dflt_pat) for p in names]:
342 342 if kind in ('glob', 'relpath'):
343 343 name = canonpath(canonroot, cwd, name)
344 344 if name == '':
345 345 kind, name = 'glob', '**'
346 346 if kind in ('glob', 'path', 're'):
347 347 pats.append((kind, name))
348 348 if kind == 'glob':
349 349 root = globprefix(name)
350 350 if root: roots.append(root)
351 351 elif kind == 'relpath':
352 352 files.append((kind, name))
353 353 roots.append(name)
354 354
355 355 patmatch = matchfn(pats, '$') or always
356 356 filematch = matchfn(files, '(?:/|$)') or always
357 357 incmatch = always
358 358 if inc:
359 359 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
360 360 incmatch = matchfn(inckinds, '(?:/|$)')
361 361 excmatch = lambda fn: False
362 362 if exc:
363 363 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
364 364 excmatch = matchfn(exckinds, '(?:/|$)')
365 365
366 366 return (roots,
367 367 lambda fn: (incmatch(fn) and not excmatch(fn) and
368 368 (fn.endswith('/') or
369 369 (not pats and not files) or
370 370 (pats and patmatch(fn)) or
371 371 (files and filematch(fn)))),
372 372 (inc or exc or (pats and pats != [('glob', '**')])) and True)
373 373
374 374 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
375 375 '''enhanced shell command execution.
376 376 run with environment maybe modified, maybe in different dir.
377 377
378 378 if command fails and onerr is None, return status. if ui object,
379 379 print error message and return status, else raise onerr object as
380 380 exception.'''
381 381 oldenv = {}
382 382 for k in environ:
383 383 oldenv[k] = os.environ.get(k)
384 384 if cwd is not None:
385 385 oldcwd = os.getcwd()
386 386 try:
387 387 for k, v in environ.iteritems():
388 388 os.environ[k] = str(v)
389 389 if cwd is not None and oldcwd != cwd:
390 390 os.chdir(cwd)
391 391 rc = os.system(cmd)
392 392 if rc and onerr:
393 393 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
394 394 explain_exit(rc)[0])
395 395 if errprefix:
396 396 errmsg = '%s: %s' % (errprefix, errmsg)
397 397 try:
398 398 onerr.warn(errmsg + '\n')
399 399 except AttributeError:
400 400 raise onerr(errmsg)
401 401 return rc
402 402 finally:
403 403 for k, v in oldenv.iteritems():
404 404 if v is None:
405 405 del os.environ[k]
406 406 else:
407 407 os.environ[k] = v
408 408 if cwd is not None and oldcwd != cwd:
409 409 os.chdir(oldcwd)
410 410
411 411 def rename(src, dst):
412 412 """forcibly rename a file"""
413 413 try:
414 414 os.rename(src, dst)
415 415 except OSError, err:
416 416 # on windows, rename to existing file is not allowed, so we
417 417 # must delete destination first. but if file is open, unlink
418 418 # schedules it for delete but does not delete it. rename
419 419 # happens immediately even for open files, so we create
420 420 # temporary file, delete it, rename destination to that name,
421 421 # then delete that. then rename is safe to do.
422 422 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
423 423 os.close(fd)
424 424 os.unlink(temp)
425 425 os.rename(dst, temp)
426 426 os.unlink(temp)
427 427 os.rename(src, dst)
428 428
429 429 def unlink(f):
430 430 """unlink and remove the directory if it is empty"""
431 431 os.unlink(f)
432 432 # try removing directories that might now be empty
433 433 try:
434 434 os.removedirs(os.path.dirname(f))
435 435 except OSError:
436 436 pass
437 437
438 438 def copyfiles(src, dst, hardlink=None):
439 439 """Copy a directory tree using hardlinks if possible"""
440 440
441 441 if hardlink is None:
442 442 hardlink = (os.stat(src).st_dev ==
443 443 os.stat(os.path.dirname(dst)).st_dev)
444 444
445 445 if os.path.isdir(src):
446 446 os.mkdir(dst)
447 447 for name in os.listdir(src):
448 448 srcname = os.path.join(src, name)
449 449 dstname = os.path.join(dst, name)
450 450 copyfiles(srcname, dstname, hardlink)
451 451 else:
452 452 if hardlink:
453 453 try:
454 454 os_link(src, dst)
455 455 except (IOError, OSError):
456 456 hardlink = False
457 457 shutil.copy(src, dst)
458 458 else:
459 459 shutil.copy(src, dst)
460 460
461 461 def audit_path(path):
462 462 """Abort if path contains dangerous components"""
463 463 parts = os.path.normcase(path).split(os.sep)
464 464 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
465 465 or os.pardir in parts):
466 466 raise Abort(_("path contains illegal component: %s\n") % path)
467 467
468 468 def _makelock_file(info, pathname):
469 469 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
470 470 os.write(ld, info)
471 471 os.close(ld)
472 472
473 473 def _readlock_file(pathname):
474 474 return posixfile(pathname).read()
475 475
476 476 def nlinks(pathname):
477 477 """Return number of hardlinks for the given file."""
478 478 return os.lstat(pathname).st_nlink
479 479
480 480 if hasattr(os, 'link'):
481 481 os_link = os.link
482 482 else:
483 483 def os_link(src, dst):
484 484 raise OSError(0, _("Hardlinks not supported"))
485 485
486 486 def fstat(fp):
487 487 '''stat file object that may not have fileno method.'''
488 488 try:
489 489 return os.fstat(fp.fileno())
490 490 except AttributeError:
491 491 return os.stat(fp.name)
492 492
493 493 posixfile = file
494 494
495 495 def is_win_9x():
496 496 '''return true if run on windows 95, 98 or me.'''
497 497 try:
498 498 return sys.getwindowsversion()[3] == 1
499 499 except AttributeError:
500 500 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
501 501
502 502 # Platform specific variants
503 503 if os.name == 'nt':
504 504 demandload(globals(), "msvcrt")
505 505 nulldev = 'NUL:'
506 506
507 507 class winstdout:
508 508 '''stdout on windows misbehaves if sent through a pipe'''
509 509
510 510 def __init__(self, fp):
511 511 self.fp = fp
512 512
513 513 def __getattr__(self, key):
514 514 return getattr(self.fp, key)
515 515
516 516 def close(self):
517 517 try:
518 518 self.fp.close()
519 519 except: pass
520 520
521 521 def write(self, s):
522 522 try:
523 523 return self.fp.write(s)
524 524 except IOError, inst:
525 525 if inst.errno != 0: raise
526 526 self.close()
527 527 raise IOError(errno.EPIPE, 'Broken pipe')
528 528
529 529 sys.stdout = winstdout(sys.stdout)
530 530
531 531 def system_rcpath():
532 532 try:
533 533 return system_rcpath_win32()
534 534 except:
535 535 return [r'c:\mercurial\mercurial.ini']
536 536
537 537 def os_rcpath():
538 538 '''return default os-specific hgrc search path'''
539 539 path = system_rcpath()
540 540 path.append(user_rcpath())
541 541 userprofile = os.environ.get('USERPROFILE')
542 542 if userprofile:
543 543 path.append(os.path.join(userprofile, 'mercurial.ini'))
544 544 return path
545 545
546 546 def user_rcpath():
547 547 '''return os-specific hgrc search path to the user dir'''
548 548 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
549 549
550 550 def parse_patch_output(output_line):
551 551 """parses the output produced by patch and returns the file name"""
552 552 pf = output_line[14:]
553 553 if pf[0] == '`':
554 554 pf = pf[1:-1] # Remove the quotes
555 555 return pf
556 556
557 557 def testpid(pid):
558 558 '''return False if pid dead, True if running or not known'''
559 559 return True
560 560
561 561 def is_exec(f, last):
562 562 return last
563 563
564 564 def set_exec(f, mode):
565 565 pass
566 566
567 567 def set_binary(fd):
568 568 msvcrt.setmode(fd.fileno(), os.O_BINARY)
569 569
570 570 def pconvert(path):
571 571 return path.replace("\\", "/")
572 572
573 573 def localpath(path):
574 574 return path.replace('/', '\\')
575 575
576 576 def normpath(path):
577 577 return pconvert(os.path.normpath(path))
578 578
579 579 makelock = _makelock_file
580 580 readlock = _readlock_file
581 581
582 582 def samestat(s1, s2):
583 583 return False
584 584
585 585 def explain_exit(code):
586 586 return _("exited with status %d") % code, code
587 587
588 588 try:
589 589 # override functions with win32 versions if possible
590 590 from util_win32 import *
591 591 if not is_win_9x():
592 592 posixfile = posixfile_nt
593 593 except ImportError:
594 594 pass
595 595
596 596 else:
597 597 nulldev = '/dev/null'
598 598
599 599 def rcfiles(path):
600 600 rcs = [os.path.join(path, 'hgrc')]
601 601 rcdir = os.path.join(path, 'hgrc.d')
602 602 try:
603 603 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
604 604 if f.endswith(".rc")])
605 605 except OSError, inst: pass
606 606 return rcs
607 607
608 608 def os_rcpath():
609 609 '''return default os-specific hgrc search path'''
610 610 path = []
611 611 # old mod_python does not set sys.argv
612 612 if len(getattr(sys, 'argv', [])) > 0:
613 613 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
614 614 '/../etc/mercurial'))
615 615 path.extend(rcfiles('/etc/mercurial'))
616 616 path.append(os.path.expanduser('~/.hgrc'))
617 617 path = [os.path.normpath(f) for f in path]
618 618 return path
619 619
620 620 def parse_patch_output(output_line):
621 621 """parses the output produced by patch and returns the file name"""
622 622 pf = output_line[14:]
623 623 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
624 624 pf = pf[1:-1] # Remove the quotes
625 625 return pf
626 626
627 627 def is_exec(f, last):
628 628 """check whether a file is executable"""
629 629 return (os.lstat(f).st_mode & 0100 != 0)
630 630
631 631 def set_exec(f, mode):
632 632 s = os.lstat(f).st_mode
633 633 if (s & 0100 != 0) == mode:
634 634 return
635 635 if mode:
636 636 # Turn on +x for every +r bit when making a file executable
637 637 # and obey umask.
638 638 umask = os.umask(0)
639 639 os.umask(umask)
640 640 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
641 641 else:
642 642 os.chmod(f, s & 0666)
643 643
644 644 def set_binary(fd):
645 645 pass
646 646
647 647 def pconvert(path):
648 648 return path
649 649
650 650 def localpath(path):
651 651 return path
652 652
653 653 normpath = os.path.normpath
654 654 samestat = os.path.samestat
655 655
656 656 def makelock(info, pathname):
657 657 try:
658 658 os.symlink(info, pathname)
659 659 except OSError, why:
660 660 if why.errno == errno.EEXIST:
661 661 raise
662 662 else:
663 663 _makelock_file(info, pathname)
664 664
665 665 def readlock(pathname):
666 666 try:
667 667 return os.readlink(pathname)
668 668 except OSError, why:
669 669 if why.errno == errno.EINVAL:
670 670 return _readlock_file(pathname)
671 671 else:
672 672 raise
673 673
674 674 def testpid(pid):
675 675 '''return False if pid dead, True if running or not sure'''
676 676 try:
677 677 os.kill(pid, 0)
678 678 return True
679 679 except OSError, inst:
680 680 return inst.errno != errno.ESRCH
681 681
682 682 def explain_exit(code):
683 683 """return a 2-tuple (desc, code) describing a process's status"""
684 684 if os.WIFEXITED(code):
685 685 val = os.WEXITSTATUS(code)
686 686 return _("exited with status %d") % val, val
687 687 elif os.WIFSIGNALED(code):
688 688 val = os.WTERMSIG(code)
689 689 return _("killed by signal %d") % val, val
690 690 elif os.WIFSTOPPED(code):
691 691 val = os.WSTOPSIG(code)
692 692 return _("stopped by signal %d") % val, val
693 693 raise ValueError(_("invalid exit code"))
694 694
695 695 def opener(base, audit=True):
696 696 """
697 697 return a function that opens files relative to base
698 698
699 699 this function is used to hide the details of COW semantics and
700 700 remote file access from higher level code.
701 701 """
702 702 p = base
703 703 audit_p = audit
704 704
705 705 def mktempcopy(name):
706 706 d, fn = os.path.split(name)
707 707 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
708 708 os.close(fd)
709 709 ofp = posixfile(temp, "wb")
710 710 try:
711 711 try:
712 712 ifp = posixfile(name, "rb")
713 713 except IOError, inst:
714 714 if not getattr(inst, 'filename', None):
715 715 inst.filename = name
716 716 raise
717 717 for chunk in filechunkiter(ifp):
718 718 ofp.write(chunk)
719 719 ifp.close()
720 720 ofp.close()
721 721 except:
722 722 try: os.unlink(temp)
723 723 except: pass
724 724 raise
725 725 st = os.lstat(name)
726 726 os.chmod(temp, st.st_mode)
727 727 return temp
728 728
729 729 class atomictempfile(posixfile):
730 730 """the file will only be copied when rename is called"""
731 731 def __init__(self, name, mode):
732 732 self.__name = name
733 733 self.temp = mktempcopy(name)
734 734 posixfile.__init__(self, self.temp, mode)
735 735 def rename(self):
736 736 if not self.closed:
737 737 posixfile.close(self)
738 738 rename(self.temp, localpath(self.__name))
739 739 def __del__(self):
740 740 if not self.closed:
741 741 try:
742 742 os.unlink(self.temp)
743 743 except: pass
744 744 posixfile.close(self)
745 745
746 746 class atomicfile(atomictempfile):
747 747 """the file will only be copied on close"""
748 748 def __init__(self, name, mode):
749 749 atomictempfile.__init__(self, name, mode)
750 750 def close(self):
751 751 self.rename()
752 752 def __del__(self):
753 753 self.rename()
754 754
755 755 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
756 756 if audit_p:
757 757 audit_path(path)
758 758 f = os.path.join(p, path)
759 759
760 760 if not text:
761 761 mode += "b" # for that other OS
762 762
763 763 if mode[0] != "r":
764 764 try:
765 765 nlink = nlinks(f)
766 766 except OSError:
767 767 d = os.path.dirname(f)
768 768 if not os.path.isdir(d):
769 769 os.makedirs(d)
770 770 else:
771 771 if atomic:
772 772 return atomicfile(f, mode)
773 773 elif atomictemp:
774 774 return atomictempfile(f, mode)
775 775 if nlink > 1:
776 776 rename(mktempcopy(f), f)
777 777 return posixfile(f, mode)
778 778
779 779 return o
780 780
781 781 class chunkbuffer(object):
782 782 """Allow arbitrary sized chunks of data to be efficiently read from an
783 783 iterator over chunks of arbitrary size."""
784 784
785 785 def __init__(self, in_iter, targetsize = 2**16):
786 786 """in_iter is the iterator that's iterating over the input chunks.
787 787 targetsize is how big a buffer to try to maintain."""
788 788 self.in_iter = iter(in_iter)
789 789 self.buf = ''
790 790 self.targetsize = int(targetsize)
791 791 if self.targetsize <= 0:
792 792 raise ValueError(_("targetsize must be greater than 0, was %d") %
793 793 targetsize)
794 794 self.iterempty = False
795 795
796 796 def fillbuf(self):
797 797 """Ignore target size; read every chunk from iterator until empty."""
798 798 if not self.iterempty:
799 799 collector = cStringIO.StringIO()
800 800 collector.write(self.buf)
801 801 for ch in self.in_iter:
802 802 collector.write(ch)
803 803 self.buf = collector.getvalue()
804 804 self.iterempty = True
805 805
806 806 def read(self, l):
807 807 """Read L bytes of data from the iterator of chunks of data.
808 808 Returns less than L bytes if the iterator runs dry."""
809 809 if l > len(self.buf) and not self.iterempty:
810 810 # Clamp to a multiple of self.targetsize
811 811 targetsize = self.targetsize * ((l // self.targetsize) + 1)
812 812 collector = cStringIO.StringIO()
813 813 collector.write(self.buf)
814 814 collected = len(self.buf)
815 815 for chunk in self.in_iter:
816 816 collector.write(chunk)
817 817 collected += len(chunk)
818 818 if collected >= targetsize:
819 819 break
820 820 if collected < targetsize:
821 821 self.iterempty = True
822 822 self.buf = collector.getvalue()
823 823 s, self.buf = self.buf[:l], buffer(self.buf, l)
824 824 return s
825 825
826 826 def filechunkiter(f, size=65536, limit=None):
827 827 """Create a generator that produces the data in the file size
828 828 (default 65536) bytes at a time, up to optional limit (default is
829 829 to read all data). Chunks may be less than size bytes if the
830 830 chunk is the last chunk in the file, or the file is a socket or
831 831 some other type of file that sometimes reads less data than is
832 832 requested."""
833 833 assert size >= 0
834 834 assert limit is None or limit >= 0
835 835 while True:
836 836 if limit is None: nbytes = size
837 837 else: nbytes = min(limit, size)
838 838 s = nbytes and f.read(nbytes)
839 839 if not s: break
840 840 if limit: limit -= len(s)
841 841 yield s
842 842
843 843 def makedate():
844 844 lt = time.localtime()
845 845 if lt[8] == 1 and time.daylight:
846 846 tz = time.altzone
847 847 else:
848 848 tz = time.timezone
849 849 return time.mktime(lt), tz
850 850
851 851 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
852 852 """represent a (unixtime, offset) tuple as a localized time.
853 853 unixtime is seconds since the epoch, and offset is the time zone's
854 854 number of seconds away from UTC. if timezone is false, do not
855 855 append time zone to string."""
856 856 t, tz = date or makedate()
857 857 s = time.strftime(format, time.gmtime(float(t) - tz))
858 858 if timezone:
859 859 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
860 860 return s
861 861
862 862 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
863 863 """parse a localized time string and return a (unixtime, offset) tuple.
864 864 if the string cannot be parsed, ValueError is raised."""
865 865 def hastimezone(string):
866 866 return (string[-4:].isdigit() and
867 867 (string[-5] == '+' or string[-5] == '-') and
868 868 string[-6].isspace())
869 869
870 870 if hastimezone(string):
871 871 date, tz = string.rsplit(None, 1)
872 872 tz = int(tz)
873 873 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
874 874 else:
875 875 date, offset = string, 0
876 876 when = int(time.mktime(time.strptime(date, format))) + offset
877 877 return when, offset
878 878
879 879 def parsedate(string, formats=('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M')):
880 880 """parse a localized time string and return a (unixtime, offset) tuple.
881 881 The date may be a "unixtime offset" string or in one of the specified
882 882 formats."""
883 883 try:
884 884 when, offset = map(int, string.split(' '))
885 return when, offset
886 except ValueError: pass
885 except ValueError:
887 886 for format in formats:
888 887 try:
889 return strdate(string, format)
890 except ValueError: pass
888 when, offset = strdate(string, format)
889 except ValueError:
890 pass
891 else:
892 break
893 else:
891 894 raise ValueError(_('invalid date: %r') % string)
895 # validate explicit (probably user-specified) date and
896 # time zone offset. values must fit in signed 32 bits for
897 # current 32-bit linux runtimes. timezones go from UTC-12
898 # to UTC+14
899 if abs(when) > 0x7fffffff:
900 raise ValueError(_('date exceeds 32 bits: %d') % when)
901 if offset < -50400 or offset > 43200:
902 raise ValueError(_('impossible time zone offset: %d') % offset)
903 return when, offset
892 904
893 905 def shortuser(user):
894 906 """Return a short representation of a user name or email address."""
895 907 f = user.find('@')
896 908 if f >= 0:
897 909 user = user[:f]
898 910 f = user.find('<')
899 911 if f >= 0:
900 912 user = user[f+1:]
901 913 return user
902 914
903 915 def walkrepos(path):
904 916 '''yield every hg repository under path, recursively.'''
905 917 def errhandler(err):
906 918 if err.filename == path:
907 919 raise err
908 920
909 921 for root, dirs, files in os.walk(path, onerror=errhandler):
910 922 for d in dirs:
911 923 if d == '.hg':
912 924 yield root
913 925 dirs[:] = []
914 926 break
915 927
916 928 _rcpath = None
917 929
918 930 def rcpath():
919 931 '''return hgrc search path. if env var HGRCPATH is set, use it.
920 932 for each item in path, if directory, use files ending in .rc,
921 933 else use item.
922 934 make HGRCPATH empty to only look in .hg/hgrc of current repo.
923 935 if no HGRCPATH, use default os-specific path.'''
924 936 global _rcpath
925 937 if _rcpath is None:
926 938 if 'HGRCPATH' in os.environ:
927 939 _rcpath = []
928 940 for p in os.environ['HGRCPATH'].split(os.pathsep):
929 941 if not p: continue
930 942 if os.path.isdir(p):
931 943 for f in os.listdir(p):
932 944 if f.endswith('.rc'):
933 945 _rcpath.append(os.path.join(p, f))
934 946 else:
935 947 _rcpath.append(p)
936 948 else:
937 949 _rcpath = os_rcpath()
938 950 return _rcpath
General Comments 0
You need to be logged in to leave comments. Login now