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