##// END OF EJS Templates
fix a bug where hg could remove file ending with .tmp...
Benoit Boissinot -
r1479:1a3c6689 default
parent child Browse files
Show More
@@ -1,617 +1,626
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 file(f + ".tmp", "wb").write(file(f, "rb").read())
381 rename(f+".tmp", f)
380 d, fn = os.path.split(f)
381 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
382 fp = os.fdopen(fd, "wb")
383 try:
384 fp.write(file(f, "rb").read())
385 except:
386 try: os.unlink(temp)
387 except: pass
388 raise
389 fp.close()
390 rename(temp, f)
382 391
383 392 return file(f, mode)
384 393
385 394 return o
386 395
387 396 def _makelock_file(info, pathname):
388 397 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
389 398 os.write(ld, info)
390 399 os.close(ld)
391 400
392 401 def _readlock_file(pathname):
393 402 return file(pathname).read()
394 403
395 404 def nlinks(pathname):
396 405 """Return number of hardlinks for the given file."""
397 406 return os.stat(pathname).st_nlink
398 407
399 408 if hasattr(os, 'link'):
400 409 os_link = os.link
401 410 else:
402 411 def os_link(src, dst):
403 412 raise OSError(0, _("Hardlinks not supported"))
404 413
405 414 # Platform specific variants
406 415 if os.name == 'nt':
407 416 demandload(globals(), "msvcrt")
408 417 nulldev = 'NUL:'
409 418
410 419 try:
411 420 import win32api, win32process
412 421 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
413 422 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
414 423
415 424 except ImportError:
416 425 systemrc = r'c:\mercurial\mercurial.ini'
417 426 pass
418 427
419 428 rcpath = (systemrc,
420 429 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
421 430
422 431 def parse_patch_output(output_line):
423 432 """parses the output produced by patch and returns the file name"""
424 433 pf = output_line[14:]
425 434 if pf[0] == '`':
426 435 pf = pf[1:-1] # Remove the quotes
427 436 return pf
428 437
429 438 try: # ActivePython can create hard links using win32file module
430 439 import win32file
431 440
432 441 def os_link(src, dst): # NB will only succeed on NTFS
433 442 win32file.CreateHardLink(dst, src)
434 443
435 444 def nlinks(pathname):
436 445 """Return number of hardlinks for the given file."""
437 446 try:
438 447 fh = win32file.CreateFile(pathname,
439 448 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
440 449 None, win32file.OPEN_EXISTING, 0, None)
441 450 res = win32file.GetFileInformationByHandle(fh)
442 451 fh.Close()
443 452 return res[7]
444 453 except:
445 454 return os.stat(pathname).st_nlink
446 455
447 456 except ImportError:
448 457 pass
449 458
450 459 def is_exec(f, last):
451 460 return last
452 461
453 462 def set_exec(f, mode):
454 463 pass
455 464
456 465 def set_binary(fd):
457 466 msvcrt.setmode(fd.fileno(), os.O_BINARY)
458 467
459 468 def pconvert(path):
460 469 return path.replace("\\", "/")
461 470
462 471 def localpath(path):
463 472 return path.replace('/', '\\')
464 473
465 474 def normpath(path):
466 475 return pconvert(os.path.normpath(path))
467 476
468 477 makelock = _makelock_file
469 478 readlock = _readlock_file
470 479
471 480 def explain_exit(code):
472 481 return _("exited with status %d") % code, code
473 482
474 483 else:
475 484 nulldev = '/dev/null'
476 485
477 486 hgrcd = '/etc/mercurial/hgrc.d'
478 487 hgrcs = []
479 488 if os.path.isdir(hgrcd):
480 489 hgrcs = [f for f in os.listdir(hgrcd) if f.endswith(".rc")]
481 490 rcpath = map(os.path.normpath, hgrcs +
482 491 ['/etc/mercurial/hgrc', os.path.expanduser('~/.hgrc')])
483 492
484 493 def parse_patch_output(output_line):
485 494 """parses the output produced by patch and returns the file name"""
486 495 return output_line[14:]
487 496
488 497 def is_exec(f, last):
489 498 """check whether a file is executable"""
490 499 return (os.stat(f).st_mode & 0100 != 0)
491 500
492 501 def set_exec(f, mode):
493 502 s = os.stat(f).st_mode
494 503 if (s & 0100 != 0) == mode:
495 504 return
496 505 if mode:
497 506 # Turn on +x for every +r bit when making a file executable
498 507 # and obey umask.
499 508 umask = os.umask(0)
500 509 os.umask(umask)
501 510 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
502 511 else:
503 512 os.chmod(f, s & 0666)
504 513
505 514 def set_binary(fd):
506 515 pass
507 516
508 517 def pconvert(path):
509 518 return path
510 519
511 520 def localpath(path):
512 521 return path
513 522
514 523 normpath = os.path.normpath
515 524
516 525 def makelock(info, pathname):
517 526 try:
518 527 os.symlink(info, pathname)
519 528 except OSError, why:
520 529 if why.errno == errno.EEXIST:
521 530 raise
522 531 else:
523 532 _makelock_file(info, pathname)
524 533
525 534 def readlock(pathname):
526 535 try:
527 536 return os.readlink(pathname)
528 537 except OSError, why:
529 538 if why.errno == errno.EINVAL:
530 539 return _readlock_file(pathname)
531 540 else:
532 541 raise
533 542
534 543 def explain_exit(code):
535 544 """return a 2-tuple (desc, code) describing a process's status"""
536 545 if os.WIFEXITED(code):
537 546 val = os.WEXITSTATUS(code)
538 547 return _("exited with status %d") % val, val
539 548 elif os.WIFSIGNALED(code):
540 549 val = os.WTERMSIG(code)
541 550 return _("killed by signal %d") % val, val
542 551 elif os.WIFSTOPPED(code):
543 552 val = os.WSTOPSIG(code)
544 553 return _("stopped by signal %d") % val, val
545 554 raise ValueError(_("invalid exit code"))
546 555
547 556 class chunkbuffer(object):
548 557 """Allow arbitrary sized chunks of data to be efficiently read from an
549 558 iterator over chunks of arbitrary size."""
550 559
551 560 def __init__(self, in_iter, targetsize = 2**16):
552 561 """in_iter is the iterator that's iterating over the input chunks.
553 562 targetsize is how big a buffer to try to maintain."""
554 563 self.in_iter = iter(in_iter)
555 564 self.buf = ''
556 565 self.targetsize = int(targetsize)
557 566 if self.targetsize <= 0:
558 567 raise ValueError(_("targetsize must be greater than 0, was %d") %
559 568 targetsize)
560 569 self.iterempty = False
561 570
562 571 def fillbuf(self):
563 572 """Ignore target size; read every chunk from iterator until empty."""
564 573 if not self.iterempty:
565 574 collector = cStringIO.StringIO()
566 575 collector.write(self.buf)
567 576 for ch in self.in_iter:
568 577 collector.write(ch)
569 578 self.buf = collector.getvalue()
570 579 self.iterempty = True
571 580
572 581 def read(self, l):
573 582 """Read L bytes of data from the iterator of chunks of data.
574 583 Returns less than L bytes if the iterator runs dry."""
575 584 if l > len(self.buf) and not self.iterempty:
576 585 # Clamp to a multiple of self.targetsize
577 586 targetsize = self.targetsize * ((l // self.targetsize) + 1)
578 587 collector = cStringIO.StringIO()
579 588 collector.write(self.buf)
580 589 collected = len(self.buf)
581 590 for chunk in self.in_iter:
582 591 collector.write(chunk)
583 592 collected += len(chunk)
584 593 if collected >= targetsize:
585 594 break
586 595 if collected < targetsize:
587 596 self.iterempty = True
588 597 self.buf = collector.getvalue()
589 598 s, self.buf = self.buf[:l], buffer(self.buf, l)
590 599 return s
591 600
592 601 def filechunkiter(f, size = 65536):
593 602 """Create a generator that produces all the data in the file size
594 603 (default 65536) bytes at a time. Chunks may be less than size
595 604 bytes if the chunk is the last chunk in the file, or the file is a
596 605 socket or some other type of file that sometimes reads less data
597 606 than is requested."""
598 607 s = f.read(size)
599 608 while len(s) > 0:
600 609 yield s
601 610 s = f.read(size)
602 611
603 612 def makedate():
604 613 t = time.time()
605 614 if time.daylight: tz = time.altzone
606 615 else: tz = time.timezone
607 616 return t, tz
608 617
609 618 def datestr(date=None, format='%c'):
610 619 """represent a (unixtime, offset) tuple as a localized time.
611 620 unixtime is seconds since the epoch, and offset is the time zone's
612 621 number of seconds away from UTC."""
613 622 t, tz = date or makedate()
614 623 return ("%s %+03d%02d" %
615 624 (time.strftime(format, time.gmtime(float(t) - tz)),
616 625 -tz / 3600,
617 626 ((-tz % 3600) / 60)))
General Comments 0
You need to be logged in to leave comments. Login now