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