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