##// END OF EJS Templates
Handle hg under /
Arun Sharma -
r1566:8befbb4e default
parent child Browse files
Show More
@@ -1,652 +1,655 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 patkind(name, dflt_pat='glob'):
110 110 """Split a string into an optional pattern kind prefix and the
111 111 actual pattern."""
112 112 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
113 113 if name.startswith(prefix + ':'): return name.split(':', 1)
114 114 return dflt_pat, name
115 115
116 116 def globre(pat, head='^', tail='$'):
117 117 "convert a glob pattern into a regexp"
118 118 i, n = 0, len(pat)
119 119 res = ''
120 120 group = False
121 121 def peek(): return i < n and pat[i]
122 122 while i < n:
123 123 c = pat[i]
124 124 i = i+1
125 125 if c == '*':
126 126 if peek() == '*':
127 127 i += 1
128 128 res += '.*'
129 129 else:
130 130 res += '[^/]*'
131 131 elif c == '?':
132 132 res += '.'
133 133 elif c == '[':
134 134 j = i
135 135 if j < n and pat[j] in '!]':
136 136 j += 1
137 137 while j < n and pat[j] != ']':
138 138 j += 1
139 139 if j >= n:
140 140 res += '\\['
141 141 else:
142 142 stuff = pat[i:j].replace('\\','\\\\')
143 143 i = j + 1
144 144 if stuff[0] == '!':
145 145 stuff = '^' + stuff[1:]
146 146 elif stuff[0] == '^':
147 147 stuff = '\\' + stuff
148 148 res = '%s[%s]' % (res, stuff)
149 149 elif c == '{':
150 150 group = True
151 151 res += '(?:'
152 152 elif c == '}' and group:
153 153 res += ')'
154 154 group = False
155 155 elif c == ',' and group:
156 156 res += '|'
157 157 else:
158 158 res += re.escape(c)
159 159 return head + res + tail
160 160
161 161 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
162 162
163 163 def pathto(n1, n2):
164 164 '''return the relative path from one place to another.
165 165 this returns a path in the form used by the local filesystem, not hg.'''
166 166 if not n1: return localpath(n2)
167 167 a, b = n1.split('/'), n2.split('/')
168 168 a.reverse()
169 169 b.reverse()
170 170 while a and b and a[-1] == b[-1]:
171 171 a.pop()
172 172 b.pop()
173 173 b.reverse()
174 174 return os.sep.join((['..'] * len(a)) + b)
175 175
176 176 def canonpath(root, cwd, myname):
177 177 """return the canonical path of myname, given cwd and root"""
178 if root == os.sep:
179 rootsep = os.sep
180 else:
178 181 rootsep = root + os.sep
179 182 name = myname
180 183 if not name.startswith(os.sep):
181 184 name = os.path.join(root, cwd, name)
182 185 name = os.path.normpath(name)
183 186 if name.startswith(rootsep):
184 187 return pconvert(name[len(rootsep):])
185 188 elif name == root:
186 189 return ''
187 190 else:
188 191 raise Abort('%s not under root' % myname)
189 192
190 193 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
191 194 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob')
192 195
193 196 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
194 197 if os.name == 'nt':
195 198 dflt_pat = 'glob'
196 199 else:
197 200 dflt_pat = 'relpath'
198 201 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat)
199 202
200 203 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat):
201 204 """build a function to match a set of file patterns
202 205
203 206 arguments:
204 207 canonroot - the canonical root of the tree you're matching against
205 208 cwd - the current working directory, if relevant
206 209 names - patterns to find
207 210 inc - patterns to include
208 211 exc - patterns to exclude
209 212 head - a regex to prepend to patterns to control whether a match is rooted
210 213
211 214 a pattern is one of:
212 215 'glob:<rooted glob>'
213 216 're:<rooted regexp>'
214 217 'path:<rooted path>'
215 218 'relglob:<relative glob>'
216 219 'relpath:<relative path>'
217 220 'relre:<relative regexp>'
218 221 '<rooted path or regexp>'
219 222
220 223 returns:
221 224 a 3-tuple containing
222 225 - list of explicit non-pattern names passed in
223 226 - a bool match(filename) function
224 227 - a bool indicating if any patterns were passed in
225 228
226 229 todo:
227 230 make head regex a rooted bool
228 231 """
229 232
230 233 def contains_glob(name):
231 234 for c in name:
232 235 if c in _globchars: return True
233 236 return False
234 237
235 238 def regex(kind, name, tail):
236 239 '''convert a pattern into a regular expression'''
237 240 if kind == 're':
238 241 return name
239 242 elif kind == 'path':
240 243 return '^' + re.escape(name) + '(?:/|$)'
241 244 elif kind == 'relglob':
242 245 return head + globre(name, '(?:|.*/)', tail)
243 246 elif kind == 'relpath':
244 247 return head + re.escape(name) + tail
245 248 elif kind == 'relre':
246 249 if name.startswith('^'):
247 250 return name
248 251 return '.*' + name
249 252 return head + globre(name, '', tail)
250 253
251 254 def matchfn(pats, tail):
252 255 """build a matching function from a set of patterns"""
253 256 if not pats:
254 257 return
255 258 matches = []
256 259 for k, p in pats:
257 260 try:
258 261 pat = '(?:%s)' % regex(k, p, tail)
259 262 matches.append(re.compile(pat).match)
260 263 except re.error:
261 264 raise Abort("invalid pattern: %s:%s" % (k, p))
262 265
263 266 def buildfn(text):
264 267 for m in matches:
265 268 r = m(text)
266 269 if r:
267 270 return r
268 271
269 272 return buildfn
270 273
271 274 def globprefix(pat):
272 275 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
273 276 root = []
274 277 for p in pat.split(os.sep):
275 278 if contains_glob(p): break
276 279 root.append(p)
277 280 return '/'.join(root)
278 281
279 282 pats = []
280 283 files = []
281 284 roots = []
282 285 for kind, name in [patkind(p, dflt_pat) for p in names]:
283 286 if kind in ('glob', 'relpath'):
284 287 name = canonpath(canonroot, cwd, name)
285 288 if name == '':
286 289 kind, name = 'glob', '**'
287 290 if kind in ('glob', 'path', 're'):
288 291 pats.append((kind, name))
289 292 if kind == 'glob':
290 293 root = globprefix(name)
291 294 if root: roots.append(root)
292 295 elif kind == 'relpath':
293 296 files.append((kind, name))
294 297 roots.append(name)
295 298
296 299 patmatch = matchfn(pats, '$') or always
297 300 filematch = matchfn(files, '(?:/|$)') or always
298 301 incmatch = always
299 302 if inc:
300 303 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
301 304 excmatch = lambda fn: False
302 305 if exc:
303 306 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
304 307
305 308 return (roots,
306 309 lambda fn: (incmatch(fn) and not excmatch(fn) and
307 310 (fn.endswith('/') or
308 311 (not pats and not files) or
309 312 (pats and patmatch(fn)) or
310 313 (files and filematch(fn)))),
311 314 (inc or exc or (pats and pats != [('glob', '**')])) and True)
312 315
313 316 def system(cmd, errprefix=None):
314 317 """execute a shell command that must succeed"""
315 318 rc = os.system(cmd)
316 319 if rc:
317 320 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
318 321 explain_exit(rc)[0])
319 322 if errprefix:
320 323 errmsg = "%s: %s" % (errprefix, errmsg)
321 324 raise Abort(errmsg)
322 325
323 326 def rename(src, dst):
324 327 """forcibly rename a file"""
325 328 try:
326 329 os.rename(src, dst)
327 330 except:
328 331 os.unlink(dst)
329 332 os.rename(src, dst)
330 333
331 334 def unlink(f):
332 335 """unlink and remove the directory if it is empty"""
333 336 os.unlink(f)
334 337 # try removing directories that might now be empty
335 338 try: os.removedirs(os.path.dirname(f))
336 339 except: pass
337 340
338 341 def copyfiles(src, dst, hardlink=None):
339 342 """Copy a directory tree using hardlinks if possible"""
340 343
341 344 if hardlink is None:
342 345 hardlink = (os.stat(src).st_dev ==
343 346 os.stat(os.path.dirname(dst)).st_dev)
344 347
345 348 if os.path.isdir(src):
346 349 os.mkdir(dst)
347 350 for name in os.listdir(src):
348 351 srcname = os.path.join(src, name)
349 352 dstname = os.path.join(dst, name)
350 353 copyfiles(srcname, dstname, hardlink)
351 354 else:
352 355 if hardlink:
353 356 try:
354 357 os_link(src, dst)
355 358 except:
356 359 hardlink = False
357 360 shutil.copy2(src, dst)
358 361 else:
359 362 shutil.copy2(src, dst)
360 363
361 364 def opener(base):
362 365 """
363 366 return a function that opens files relative to base
364 367
365 368 this function is used to hide the details of COW semantics and
366 369 remote file access from higher level code.
367 370 """
368 371 p = base
369 372
370 373 def mktempcopy(name):
371 374 d, fn = os.path.split(name)
372 375 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
373 376 fp = os.fdopen(fd, "wb")
374 377 try:
375 378 fp.write(file(name, "rb").read())
376 379 except:
377 380 try: os.unlink(temp)
378 381 except: pass
379 382 raise
380 383 fp.close()
381 384 st = os.lstat(name)
382 385 os.chmod(temp, st.st_mode)
383 386 return temp
384 387
385 388 class atomicfile(file):
386 389 """the file will only be copied on close"""
387 390 def __init__(self, name, mode, atomic=False):
388 391 self.__name = name
389 392 self.temp = mktempcopy(name)
390 393 file.__init__(self, self.temp, mode)
391 394 def close(self):
392 395 if not self.closed:
393 396 file.close(self)
394 397 rename(self.temp, self.__name)
395 398 def __del__(self):
396 399 self.close()
397 400
398 401 def o(path, mode="r", text=False, atomic=False):
399 402 f = os.path.join(p, path)
400 403
401 404 if not text:
402 405 mode += "b" # for that other OS
403 406
404 407 if mode[0] != "r":
405 408 try:
406 409 nlink = nlinks(f)
407 410 except OSError:
408 411 d = os.path.dirname(f)
409 412 if not os.path.isdir(d):
410 413 os.makedirs(d)
411 414 else:
412 415 if atomic:
413 416 return atomicfile(f, mode)
414 417 if nlink > 1:
415 418 rename(mktempcopy(f), f)
416 419 return file(f, mode)
417 420
418 421 return o
419 422
420 423 def _makelock_file(info, pathname):
421 424 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
422 425 os.write(ld, info)
423 426 os.close(ld)
424 427
425 428 def _readlock_file(pathname):
426 429 return file(pathname).read()
427 430
428 431 def nlinks(pathname):
429 432 """Return number of hardlinks for the given file."""
430 433 return os.stat(pathname).st_nlink
431 434
432 435 if hasattr(os, 'link'):
433 436 os_link = os.link
434 437 else:
435 438 def os_link(src, dst):
436 439 raise OSError(0, _("Hardlinks not supported"))
437 440
438 441 # Platform specific variants
439 442 if os.name == 'nt':
440 443 demandload(globals(), "msvcrt")
441 444 nulldev = 'NUL:'
442 445
443 446 try:
444 447 import win32api, win32process
445 448 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
446 449 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
447 450
448 451 except ImportError:
449 452 systemrc = r'c:\mercurial\mercurial.ini'
450 453 pass
451 454
452 455 rcpath = (systemrc,
453 456 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
454 457
455 458 def parse_patch_output(output_line):
456 459 """parses the output produced by patch and returns the file name"""
457 460 pf = output_line[14:]
458 461 if pf[0] == '`':
459 462 pf = pf[1:-1] # Remove the quotes
460 463 return pf
461 464
462 465 try: # ActivePython can create hard links using win32file module
463 466 import win32file
464 467
465 468 def os_link(src, dst): # NB will only succeed on NTFS
466 469 win32file.CreateHardLink(dst, src)
467 470
468 471 def nlinks(pathname):
469 472 """Return number of hardlinks for the given file."""
470 473 try:
471 474 fh = win32file.CreateFile(pathname,
472 475 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
473 476 None, win32file.OPEN_EXISTING, 0, None)
474 477 res = win32file.GetFileInformationByHandle(fh)
475 478 fh.Close()
476 479 return res[7]
477 480 except:
478 481 return os.stat(pathname).st_nlink
479 482
480 483 except ImportError:
481 484 pass
482 485
483 486 def is_exec(f, last):
484 487 return last
485 488
486 489 def set_exec(f, mode):
487 490 pass
488 491
489 492 def set_binary(fd):
490 493 msvcrt.setmode(fd.fileno(), os.O_BINARY)
491 494
492 495 def pconvert(path):
493 496 return path.replace("\\", "/")
494 497
495 498 def localpath(path):
496 499 return path.replace('/', '\\')
497 500
498 501 def normpath(path):
499 502 return pconvert(os.path.normpath(path))
500 503
501 504 makelock = _makelock_file
502 505 readlock = _readlock_file
503 506
504 507 def explain_exit(code):
505 508 return _("exited with status %d") % code, code
506 509
507 510 else:
508 511 nulldev = '/dev/null'
509 512
510 513 hgrcd = '/etc/mercurial/hgrc.d'
511 514 hgrcs = []
512 515 if os.path.isdir(hgrcd):
513 516 hgrcs = [f for f in os.listdir(hgrcd) if f.endswith(".rc")]
514 517 rcpath = map(os.path.normpath, hgrcs +
515 518 ['/etc/mercurial/hgrc', os.path.expanduser('~/.hgrc')])
516 519
517 520 def parse_patch_output(output_line):
518 521 """parses the output produced by patch and returns the file name"""
519 522 return output_line[14:]
520 523
521 524 def is_exec(f, last):
522 525 """check whether a file is executable"""
523 526 return (os.stat(f).st_mode & 0100 != 0)
524 527
525 528 def set_exec(f, mode):
526 529 s = os.stat(f).st_mode
527 530 if (s & 0100 != 0) == mode:
528 531 return
529 532 if mode:
530 533 # Turn on +x for every +r bit when making a file executable
531 534 # and obey umask.
532 535 umask = os.umask(0)
533 536 os.umask(umask)
534 537 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
535 538 else:
536 539 os.chmod(f, s & 0666)
537 540
538 541 def set_binary(fd):
539 542 pass
540 543
541 544 def pconvert(path):
542 545 return path
543 546
544 547 def localpath(path):
545 548 return path
546 549
547 550 normpath = os.path.normpath
548 551
549 552 def makelock(info, pathname):
550 553 try:
551 554 os.symlink(info, pathname)
552 555 except OSError, why:
553 556 if why.errno == errno.EEXIST:
554 557 raise
555 558 else:
556 559 _makelock_file(info, pathname)
557 560
558 561 def readlock(pathname):
559 562 try:
560 563 return os.readlink(pathname)
561 564 except OSError, why:
562 565 if why.errno == errno.EINVAL:
563 566 return _readlock_file(pathname)
564 567 else:
565 568 raise
566 569
567 570 def explain_exit(code):
568 571 """return a 2-tuple (desc, code) describing a process's status"""
569 572 if os.WIFEXITED(code):
570 573 val = os.WEXITSTATUS(code)
571 574 return _("exited with status %d") % val, val
572 575 elif os.WIFSIGNALED(code):
573 576 val = os.WTERMSIG(code)
574 577 return _("killed by signal %d") % val, val
575 578 elif os.WIFSTOPPED(code):
576 579 val = os.WSTOPSIG(code)
577 580 return _("stopped by signal %d") % val, val
578 581 raise ValueError(_("invalid exit code"))
579 582
580 583 class chunkbuffer(object):
581 584 """Allow arbitrary sized chunks of data to be efficiently read from an
582 585 iterator over chunks of arbitrary size."""
583 586
584 587 def __init__(self, in_iter, targetsize = 2**16):
585 588 """in_iter is the iterator that's iterating over the input chunks.
586 589 targetsize is how big a buffer to try to maintain."""
587 590 self.in_iter = iter(in_iter)
588 591 self.buf = ''
589 592 self.targetsize = int(targetsize)
590 593 if self.targetsize <= 0:
591 594 raise ValueError(_("targetsize must be greater than 0, was %d") %
592 595 targetsize)
593 596 self.iterempty = False
594 597
595 598 def fillbuf(self):
596 599 """Ignore target size; read every chunk from iterator until empty."""
597 600 if not self.iterempty:
598 601 collector = cStringIO.StringIO()
599 602 collector.write(self.buf)
600 603 for ch in self.in_iter:
601 604 collector.write(ch)
602 605 self.buf = collector.getvalue()
603 606 self.iterempty = True
604 607
605 608 def read(self, l):
606 609 """Read L bytes of data from the iterator of chunks of data.
607 610 Returns less than L bytes if the iterator runs dry."""
608 611 if l > len(self.buf) and not self.iterempty:
609 612 # Clamp to a multiple of self.targetsize
610 613 targetsize = self.targetsize * ((l // self.targetsize) + 1)
611 614 collector = cStringIO.StringIO()
612 615 collector.write(self.buf)
613 616 collected = len(self.buf)
614 617 for chunk in self.in_iter:
615 618 collector.write(chunk)
616 619 collected += len(chunk)
617 620 if collected >= targetsize:
618 621 break
619 622 if collected < targetsize:
620 623 self.iterempty = True
621 624 self.buf = collector.getvalue()
622 625 s, self.buf = self.buf[:l], buffer(self.buf, l)
623 626 return s
624 627
625 628 def filechunkiter(f, size = 65536):
626 629 """Create a generator that produces all the data in the file size
627 630 (default 65536) bytes at a time. Chunks may be less than size
628 631 bytes if the chunk is the last chunk in the file, or the file is a
629 632 socket or some other type of file that sometimes reads less data
630 633 than is requested."""
631 634 s = f.read(size)
632 635 while len(s) > 0:
633 636 yield s
634 637 s = f.read(size)
635 638
636 639 def makedate():
637 640 lt = time.localtime()
638 641 if lt[8] == 1 and time.daylight:
639 642 tz = time.altzone
640 643 else:
641 644 tz = time.timezone
642 645 return time.mktime(lt), tz
643 646
644 647 def datestr(date=None, format='%c'):
645 648 """represent a (unixtime, offset) tuple as a localized time.
646 649 unixtime is seconds since the epoch, and offset is the time zone's
647 650 number of seconds away from UTC."""
648 651 t, tz = date or makedate()
649 652 return ("%s %+03d%02d" %
650 653 (time.strftime(format, time.gmtime(float(t) - tz)),
651 654 -tz / 3600,
652 655 ((-tz % 3600) / 60)))
General Comments 0
You need to be logged in to leave comments. Login now