##// END OF EJS Templates
issue352: disallow '\n' and '\r' in filenames (dirstate and manifest)
Benoit Boissinot -
r3607:f4c9bb4a default
parent child Browse files
Show More
@@ -0,0 +1,21 b''
1 #!/bin/bash
2 # http://www.selenic.com/mercurial/bts/issue352
3
4 hg init foo
5 cd foo
6
7 A=`echo -e -n 'he\rllo'`
8
9 echo foo > "hell
10 o"
11 echo foo > "$A"
12 hg add
13 hg ci -A -m m
14 rm "$A"
15 ls
16 hg add
17 # BUG ? we don't walk on filenames with '\n' (regexp related) ?
18 hg debugwalk
19 hg ci -A -m m
20
21 exit 0
@@ -0,0 +1,7 b''
1 adding he llo
2 abort: '\n' and '\r' disallowed in filenames
3 adding he llo
4 abort: '\n' and '\r' disallowed in filenames
5 hell
6 o
7 nothing changed
@@ -1,525 +1,529 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 strutil 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.dirs = None
26 self.copymap = {}
26 self.copymap = {}
27 self.ignorefunc = None
27 self.ignorefunc = None
28
28
29 def wjoin(self, f):
29 def wjoin(self, f):
30 return os.path.join(self.root, f)
30 return os.path.join(self.root, f)
31
31
32 def getcwd(self):
32 def getcwd(self):
33 cwd = os.getcwd()
33 cwd = os.getcwd()
34 if cwd == self.root: return ''
34 if cwd == self.root: return ''
35 return cwd[len(self.root) + 1:]
35 return cwd[len(self.root) + 1:]
36
36
37 def hgignore(self):
37 def hgignore(self):
38 '''return the contents of .hgignore files as a list of patterns.
38 '''return the contents of .hgignore files as a list of patterns.
39
39
40 the files parsed for patterns include:
40 the files parsed for patterns include:
41 .hgignore in the repository root
41 .hgignore in the repository root
42 any additional files specified in the [ui] section of ~/.hgrc
42 any additional files specified in the [ui] section of ~/.hgrc
43
43
44 trailing white space is dropped.
44 trailing white space is dropped.
45 the escape character is backslash.
45 the escape character is backslash.
46 comments start with #.
46 comments start with #.
47 empty lines are skipped.
47 empty lines are skipped.
48
48
49 lines can be of the following formats:
49 lines can be of the following formats:
50
50
51 syntax: regexp # defaults following lines to non-rooted regexps
51 syntax: regexp # defaults following lines to non-rooted regexps
52 syntax: glob # defaults following lines to non-rooted globs
52 syntax: glob # defaults following lines to non-rooted globs
53 re:pattern # non-rooted regular expression
53 re:pattern # non-rooted regular expression
54 glob:pattern # non-rooted glob
54 glob:pattern # non-rooted glob
55 pattern # pattern of the current default type'''
55 pattern # pattern of the current default type'''
56 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
56 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
57 def parselines(fp):
57 def parselines(fp):
58 for line in fp:
58 for line in fp:
59 escape = False
59 escape = False
60 for i in xrange(len(line)):
60 for i in xrange(len(line)):
61 if escape: escape = False
61 if escape: escape = False
62 elif line[i] == '\\': escape = True
62 elif line[i] == '\\': escape = True
63 elif line[i] == '#': break
63 elif line[i] == '#': break
64 line = line[:i].rstrip()
64 line = line[:i].rstrip()
65 if line: yield line
65 if line: yield line
66 repoignore = self.wjoin('.hgignore')
66 repoignore = self.wjoin('.hgignore')
67 files = [repoignore]
67 files = [repoignore]
68 files.extend(self.ui.hgignorefiles())
68 files.extend(self.ui.hgignorefiles())
69 pats = {}
69 pats = {}
70 for f in files:
70 for f in files:
71 try:
71 try:
72 pats[f] = []
72 pats[f] = []
73 fp = open(f)
73 fp = open(f)
74 syntax = 'relre:'
74 syntax = 'relre:'
75 for line in parselines(fp):
75 for line in parselines(fp):
76 if line.startswith('syntax:'):
76 if line.startswith('syntax:'):
77 s = line[7:].strip()
77 s = line[7:].strip()
78 try:
78 try:
79 syntax = syntaxes[s]
79 syntax = syntaxes[s]
80 except KeyError:
80 except KeyError:
81 self.ui.warn(_("%s: ignoring invalid "
81 self.ui.warn(_("%s: ignoring invalid "
82 "syntax '%s'\n") % (f, s))
82 "syntax '%s'\n") % (f, s))
83 continue
83 continue
84 pat = syntax + line
84 pat = syntax + line
85 for s in syntaxes.values():
85 for s in syntaxes.values():
86 if line.startswith(s):
86 if line.startswith(s):
87 pat = line
87 pat = line
88 break
88 break
89 pats[f].append(pat)
89 pats[f].append(pat)
90 except IOError, inst:
90 except IOError, inst:
91 if f != repoignore:
91 if f != repoignore:
92 self.ui.warn(_("skipping unreadable ignore file"
92 self.ui.warn(_("skipping unreadable ignore file"
93 " '%s': %s\n") % (f, inst.strerror))
93 " '%s': %s\n") % (f, inst.strerror))
94 return pats
94 return pats
95
95
96 def ignore(self, fn):
96 def ignore(self, fn):
97 '''default match function used by dirstate and
97 '''default match function used by dirstate and
98 localrepository. this honours the repository .hgignore file
98 localrepository. this honours the repository .hgignore file
99 and any other files specified in the [ui] section of .hgrc.'''
99 and any other files specified in the [ui] section of .hgrc.'''
100 if not self.ignorefunc:
100 if not self.ignorefunc:
101 ignore = self.hgignore()
101 ignore = self.hgignore()
102 allpats = []
102 allpats = []
103 [allpats.extend(patlist) for patlist in ignore.values()]
103 [allpats.extend(patlist) for patlist in ignore.values()]
104 if allpats:
104 if allpats:
105 try:
105 try:
106 files, self.ignorefunc, anypats = (
106 files, self.ignorefunc, anypats = (
107 util.matcher(self.root, inc=allpats, src='.hgignore'))
107 util.matcher(self.root, inc=allpats, src='.hgignore'))
108 except util.Abort:
108 except util.Abort:
109 # Re-raise an exception where the src is the right file
109 # Re-raise an exception where the src is the right file
110 for f, patlist in ignore.items():
110 for f, patlist in ignore.items():
111 files, self.ignorefunc, anypats = (
111 files, self.ignorefunc, anypats = (
112 util.matcher(self.root, inc=patlist, src=f))
112 util.matcher(self.root, inc=patlist, src=f))
113 else:
113 else:
114 self.ignorefunc = util.never
114 self.ignorefunc = util.never
115 return self.ignorefunc(fn)
115 return self.ignorefunc(fn)
116
116
117 def __del__(self):
117 def __del__(self):
118 if self.dirty:
118 if self.dirty:
119 self.write()
119 self.write()
120
120
121 def __getitem__(self, key):
121 def __getitem__(self, key):
122 try:
122 try:
123 return self.map[key]
123 return self.map[key]
124 except TypeError:
124 except TypeError:
125 self.lazyread()
125 self.lazyread()
126 return self[key]
126 return self[key]
127
127
128 def __contains__(self, key):
128 def __contains__(self, key):
129 self.lazyread()
129 self.lazyread()
130 return key in self.map
130 return key in self.map
131
131
132 def parents(self):
132 def parents(self):
133 self.lazyread()
133 self.lazyread()
134 return self.pl
134 return self.pl
135
135
136 def markdirty(self):
136 def markdirty(self):
137 if not self.dirty:
137 if not self.dirty:
138 self.dirty = 1
138 self.dirty = 1
139
139
140 def setparents(self, p1, p2=nullid):
140 def setparents(self, p1, p2=nullid):
141 self.lazyread()
141 self.lazyread()
142 self.markdirty()
142 self.markdirty()
143 self.pl = p1, p2
143 self.pl = p1, p2
144
144
145 def state(self, key):
145 def state(self, key):
146 try:
146 try:
147 return self[key][0]
147 return self[key][0]
148 except KeyError:
148 except KeyError:
149 return "?"
149 return "?"
150
150
151 def lazyread(self):
151 def lazyread(self):
152 if self.map is None:
152 if self.map is None:
153 self.read()
153 self.read()
154
154
155 def parse(self, st):
155 def parse(self, st):
156 self.pl = [st[:20], st[20: 40]]
156 self.pl = [st[:20], st[20: 40]]
157
157
158 # deref fields so they will be local in loop
158 # deref fields so they will be local in loop
159 map = self.map
159 map = self.map
160 copymap = self.copymap
160 copymap = self.copymap
161 format = self.format
161 format = self.format
162 unpack = struct.unpack
162 unpack = struct.unpack
163
163
164 pos = 40
164 pos = 40
165 e_size = struct.calcsize(format)
165 e_size = struct.calcsize(format)
166
166
167 while pos < len(st):
167 while pos < len(st):
168 newpos = pos + e_size
168 newpos = pos + e_size
169 e = unpack(format, st[pos:newpos])
169 e = unpack(format, st[pos:newpos])
170 l = e[4]
170 l = e[4]
171 pos = newpos
171 pos = newpos
172 newpos = pos + l
172 newpos = pos + l
173 f = st[pos:newpos]
173 f = st[pos:newpos]
174 if '\0' in f:
174 if '\0' in f:
175 f, c = f.split('\0')
175 f, c = f.split('\0')
176 copymap[f] = c
176 copymap[f] = c
177 map[f] = e[:4]
177 map[f] = e[:4]
178 pos = newpos
178 pos = newpos
179
179
180 def read(self):
180 def read(self):
181 self.map = {}
181 self.map = {}
182 self.pl = [nullid, nullid]
182 self.pl = [nullid, nullid]
183 try:
183 try:
184 st = self.opener("dirstate").read()
184 st = self.opener("dirstate").read()
185 if st:
185 if st:
186 self.parse(st)
186 self.parse(st)
187 except IOError, err:
187 except IOError, err:
188 if err.errno != errno.ENOENT: raise
188 if err.errno != errno.ENOENT: raise
189
189
190 def copy(self, source, dest):
190 def copy(self, source, dest):
191 self.lazyread()
191 self.lazyread()
192 self.markdirty()
192 self.markdirty()
193 self.copymap[dest] = source
193 self.copymap[dest] = source
194
194
195 def copied(self, file):
195 def copied(self, file):
196 return self.copymap.get(file, None)
196 return self.copymap.get(file, None)
197
197
198 def copies(self):
198 def copies(self):
199 return self.copymap
199 return self.copymap
200
200
201 def initdirs(self):
201 def initdirs(self):
202 if self.dirs is None:
202 if self.dirs is None:
203 self.dirs = {}
203 self.dirs = {}
204 for f in self.map:
204 for f in self.map:
205 self.updatedirs(f, 1)
205 self.updatedirs(f, 1)
206
206
207 def updatedirs(self, path, delta):
207 def updatedirs(self, path, delta):
208 if self.dirs is not None:
208 if self.dirs is not None:
209 for c in strutil.findall(path, '/'):
209 for c in strutil.findall(path, '/'):
210 pc = path[:c]
210 pc = path[:c]
211 self.dirs.setdefault(pc, 0)
211 self.dirs.setdefault(pc, 0)
212 self.dirs[pc] += delta
212 self.dirs[pc] += delta
213
213
214 def checkshadows(self, files):
214 def checkinterfering(self, files):
215 def prefixes(f):
215 def prefixes(f):
216 for c in strutil.rfindall(f, '/'):
216 for c in strutil.rfindall(f, '/'):
217 yield f[:c]
217 yield f[:c]
218 self.lazyread()
218 self.lazyread()
219 self.initdirs()
219 self.initdirs()
220 seendirs = {}
220 seendirs = {}
221 for f in files:
221 for f in files:
222 # shadows
222 if self.dirs.get(f):
223 if self.dirs.get(f):
223 raise util.Abort(_('directory named %r already in dirstate') %
224 raise util.Abort(_('directory named %r already in dirstate') %
224 f)
225 f)
225 for d in prefixes(f):
226 for d in prefixes(f):
226 if d in seendirs:
227 if d in seendirs:
227 break
228 break
228 if d in self.map:
229 if d in self.map:
229 raise util.Abort(_('file named %r already in dirstate') %
230 raise util.Abort(_('file named %r already in dirstate') %
230 d)
231 d)
231 seendirs[d] = True
232 seendirs[d] = True
233 # disallowed
234 if '\r' in f or '\n' in f:
235 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
232
236
233 def update(self, files, state, **kw):
237 def update(self, files, state, **kw):
234 ''' current states:
238 ''' current states:
235 n normal
239 n normal
236 m needs merging
240 m needs merging
237 r marked for removal
241 r marked for removal
238 a marked for addition'''
242 a marked for addition'''
239
243
240 if not files: return
244 if not files: return
241 self.lazyread()
245 self.lazyread()
242 self.markdirty()
246 self.markdirty()
243 if state == "a":
247 if state == "a":
244 self.initdirs()
248 self.initdirs()
245 self.checkshadows(files)
249 self.checkinterfering(files)
246 for f in files:
250 for f in files:
247 if state == "r":
251 if state == "r":
248 self.map[f] = ('r', 0, 0, 0)
252 self.map[f] = ('r', 0, 0, 0)
249 self.updatedirs(f, -1)
253 self.updatedirs(f, -1)
250 else:
254 else:
251 if state == "a":
255 if state == "a":
252 self.updatedirs(f, 1)
256 self.updatedirs(f, 1)
253 s = os.lstat(self.wjoin(f))
257 s = os.lstat(self.wjoin(f))
254 st_size = kw.get('st_size', s.st_size)
258 st_size = kw.get('st_size', s.st_size)
255 st_mtime = kw.get('st_mtime', s.st_mtime)
259 st_mtime = kw.get('st_mtime', s.st_mtime)
256 self.map[f] = (state, s.st_mode, st_size, st_mtime)
260 self.map[f] = (state, s.st_mode, st_size, st_mtime)
257 if self.copymap.has_key(f):
261 if self.copymap.has_key(f):
258 del self.copymap[f]
262 del self.copymap[f]
259
263
260 def forget(self, files):
264 def forget(self, files):
261 if not files: return
265 if not files: return
262 self.lazyread()
266 self.lazyread()
263 self.markdirty()
267 self.markdirty()
264 self.initdirs()
268 self.initdirs()
265 for f in files:
269 for f in files:
266 try:
270 try:
267 del self.map[f]
271 del self.map[f]
268 self.updatedirs(f, -1)
272 self.updatedirs(f, -1)
269 except KeyError:
273 except KeyError:
270 self.ui.warn(_("not in dirstate: %s!\n") % f)
274 self.ui.warn(_("not in dirstate: %s!\n") % f)
271 pass
275 pass
272
276
273 def clear(self):
277 def clear(self):
274 self.map = {}
278 self.map = {}
275 self.copymap = {}
279 self.copymap = {}
276 self.dirs = None
280 self.dirs = None
277 self.markdirty()
281 self.markdirty()
278
282
279 def rebuild(self, parent, files):
283 def rebuild(self, parent, files):
280 self.clear()
284 self.clear()
281 umask = os.umask(0)
285 umask = os.umask(0)
282 os.umask(umask)
286 os.umask(umask)
283 for f in files:
287 for f in files:
284 if files.execf(f):
288 if files.execf(f):
285 self.map[f] = ('n', ~umask, -1, 0)
289 self.map[f] = ('n', ~umask, -1, 0)
286 else:
290 else:
287 self.map[f] = ('n', ~umask & 0666, -1, 0)
291 self.map[f] = ('n', ~umask & 0666, -1, 0)
288 self.pl = (parent, nullid)
292 self.pl = (parent, nullid)
289 self.markdirty()
293 self.markdirty()
290
294
291 def write(self):
295 def write(self):
292 if not self.dirty:
296 if not self.dirty:
293 return
297 return
294 st = self.opener("dirstate", "w", atomic=True)
298 st = self.opener("dirstate", "w", atomic=True)
295 st.write("".join(self.pl))
299 st.write("".join(self.pl))
296 for f, e in self.map.items():
300 for f, e in self.map.items():
297 c = self.copied(f)
301 c = self.copied(f)
298 if c:
302 if c:
299 f = f + "\0" + c
303 f = f + "\0" + c
300 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
304 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
301 st.write(e + f)
305 st.write(e + f)
302 self.dirty = 0
306 self.dirty = 0
303
307
304 def filterfiles(self, files):
308 def filterfiles(self, files):
305 ret = {}
309 ret = {}
306 unknown = []
310 unknown = []
307
311
308 for x in files:
312 for x in files:
309 if x == '.':
313 if x == '.':
310 return self.map.copy()
314 return self.map.copy()
311 if x not in self.map:
315 if x not in self.map:
312 unknown.append(x)
316 unknown.append(x)
313 else:
317 else:
314 ret[x] = self.map[x]
318 ret[x] = self.map[x]
315
319
316 if not unknown:
320 if not unknown:
317 return ret
321 return ret
318
322
319 b = self.map.keys()
323 b = self.map.keys()
320 b.sort()
324 b.sort()
321 blen = len(b)
325 blen = len(b)
322
326
323 for x in unknown:
327 for x in unknown:
324 bs = bisect.bisect(b, "%s%s" % (x, '/'))
328 bs = bisect.bisect(b, "%s%s" % (x, '/'))
325 while bs < blen:
329 while bs < blen:
326 s = b[bs]
330 s = b[bs]
327 if len(s) > len(x) and s.startswith(x):
331 if len(s) > len(x) and s.startswith(x):
328 ret[s] = self.map[s]
332 ret[s] = self.map[s]
329 else:
333 else:
330 break
334 break
331 bs += 1
335 bs += 1
332 return ret
336 return ret
333
337
334 def supported_type(self, f, st, verbose=False):
338 def supported_type(self, f, st, verbose=False):
335 if stat.S_ISREG(st.st_mode):
339 if stat.S_ISREG(st.st_mode):
336 return True
340 return True
337 if verbose:
341 if verbose:
338 kind = 'unknown'
342 kind = 'unknown'
339 if stat.S_ISCHR(st.st_mode): kind = _('character device')
343 if stat.S_ISCHR(st.st_mode): kind = _('character device')
340 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
344 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
341 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
345 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
342 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
346 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
343 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
347 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
344 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
348 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
345 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
349 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
346 util.pathto(self.getcwd(), f),
350 util.pathto(self.getcwd(), f),
347 kind))
351 kind))
348 return False
352 return False
349
353
350 def walk(self, files=None, match=util.always, badmatch=None):
354 def walk(self, files=None, match=util.always, badmatch=None):
351 # filter out the stat
355 # filter out the stat
352 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
356 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
353 yield src, f
357 yield src, f
354
358
355 def statwalk(self, files=None, match=util.always, ignored=False,
359 def statwalk(self, files=None, match=util.always, ignored=False,
356 badmatch=None):
360 badmatch=None):
357 '''
361 '''
358 walk recursively through the directory tree, finding all files
362 walk recursively through the directory tree, finding all files
359 matched by the match function
363 matched by the match function
360
364
361 results are yielded in a tuple (src, filename, st), where src
365 results are yielded in a tuple (src, filename, st), where src
362 is one of:
366 is one of:
363 'f' the file was found in the directory tree
367 'f' the file was found in the directory tree
364 'm' the file was only in the dirstate and not in the tree
368 'm' the file was only in the dirstate and not in the tree
365 'b' file was not found and matched badmatch
369 'b' file was not found and matched badmatch
366
370
367 and st is the stat result if the file was found in the directory.
371 and st is the stat result if the file was found in the directory.
368 '''
372 '''
369 self.lazyread()
373 self.lazyread()
370
374
371 # walk all files by default
375 # walk all files by default
372 if not files:
376 if not files:
373 files = [self.root]
377 files = [self.root]
374 dc = self.map.copy()
378 dc = self.map.copy()
375 else:
379 else:
376 files = util.unique(files)
380 files = util.unique(files)
377 dc = self.filterfiles(files)
381 dc = self.filterfiles(files)
378
382
379 def imatch(file_):
383 def imatch(file_):
380 if file_ not in dc and self.ignore(file_):
384 if file_ not in dc and self.ignore(file_):
381 return False
385 return False
382 return match(file_)
386 return match(file_)
383
387
384 if ignored: imatch = match
388 if ignored: imatch = match
385
389
386 # self.root may end with a path separator when self.root == '/'
390 # self.root may end with a path separator when self.root == '/'
387 common_prefix_len = len(self.root)
391 common_prefix_len = len(self.root)
388 if not self.root.endswith('/'):
392 if not self.root.endswith('/'):
389 common_prefix_len += 1
393 common_prefix_len += 1
390 # recursion free walker, faster than os.walk.
394 # recursion free walker, faster than os.walk.
391 def findfiles(s):
395 def findfiles(s):
392 work = [s]
396 work = [s]
393 while work:
397 while work:
394 top = work.pop()
398 top = work.pop()
395 names = os.listdir(top)
399 names = os.listdir(top)
396 names.sort()
400 names.sort()
397 # nd is the top of the repository dir tree
401 # nd is the top of the repository dir tree
398 nd = util.normpath(top[common_prefix_len:])
402 nd = util.normpath(top[common_prefix_len:])
399 if nd == '.':
403 if nd == '.':
400 nd = ''
404 nd = ''
401 else:
405 else:
402 # do not recurse into a repo contained in this
406 # do not recurse into a repo contained in this
403 # one. use bisect to find .hg directory so speed
407 # one. use bisect to find .hg directory so speed
404 # is good on big directory.
408 # is good on big directory.
405 hg = bisect.bisect_left(names, '.hg')
409 hg = bisect.bisect_left(names, '.hg')
406 if hg < len(names) and names[hg] == '.hg':
410 if hg < len(names) and names[hg] == '.hg':
407 if os.path.isdir(os.path.join(top, '.hg')):
411 if os.path.isdir(os.path.join(top, '.hg')):
408 continue
412 continue
409 for f in names:
413 for f in names:
410 np = util.pconvert(os.path.join(nd, f))
414 np = util.pconvert(os.path.join(nd, f))
411 if seen(np):
415 if seen(np):
412 continue
416 continue
413 p = os.path.join(top, f)
417 p = os.path.join(top, f)
414 # don't trip over symlinks
418 # don't trip over symlinks
415 st = os.lstat(p)
419 st = os.lstat(p)
416 if stat.S_ISDIR(st.st_mode):
420 if stat.S_ISDIR(st.st_mode):
417 ds = util.pconvert(os.path.join(nd, f +'/'))
421 ds = util.pconvert(os.path.join(nd, f +'/'))
418 if imatch(ds):
422 if imatch(ds):
419 work.append(p)
423 work.append(p)
420 if imatch(np) and np in dc:
424 if imatch(np) and np in dc:
421 yield 'm', np, st
425 yield 'm', np, st
422 elif imatch(np):
426 elif imatch(np):
423 if self.supported_type(np, st):
427 if self.supported_type(np, st):
424 yield 'f', np, st
428 yield 'f', np, st
425 elif np in dc:
429 elif np in dc:
426 yield 'm', np, st
430 yield 'm', np, st
427
431
428 known = {'.hg': 1}
432 known = {'.hg': 1}
429 def seen(fn):
433 def seen(fn):
430 if fn in known: return True
434 if fn in known: return True
431 known[fn] = 1
435 known[fn] = 1
432
436
433 # step one, find all files that match our criteria
437 # step one, find all files that match our criteria
434 files.sort()
438 files.sort()
435 for ff in files:
439 for ff in files:
436 nf = util.normpath(ff)
440 nf = util.normpath(ff)
437 f = self.wjoin(ff)
441 f = self.wjoin(ff)
438 try:
442 try:
439 st = os.lstat(f)
443 st = os.lstat(f)
440 except OSError, inst:
444 except OSError, inst:
441 found = False
445 found = False
442 for fn in dc:
446 for fn in dc:
443 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
447 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
444 found = True
448 found = True
445 break
449 break
446 if not found:
450 if not found:
447 if inst.errno != errno.ENOENT or not badmatch:
451 if inst.errno != errno.ENOENT or not badmatch:
448 self.ui.warn('%s: %s\n' % (
452 self.ui.warn('%s: %s\n' % (
449 util.pathto(self.getcwd(), ff),
453 util.pathto(self.getcwd(), ff),
450 inst.strerror))
454 inst.strerror))
451 elif badmatch and badmatch(ff) and imatch(nf):
455 elif badmatch and badmatch(ff) and imatch(nf):
452 yield 'b', ff, None
456 yield 'b', ff, None
453 continue
457 continue
454 if stat.S_ISDIR(st.st_mode):
458 if stat.S_ISDIR(st.st_mode):
455 cmp1 = (lambda x, y: cmp(x[1], y[1]))
459 cmp1 = (lambda x, y: cmp(x[1], y[1]))
456 sorted_ = [ x for x in findfiles(f) ]
460 sorted_ = [ x for x in findfiles(f) ]
457 sorted_.sort(cmp1)
461 sorted_.sort(cmp1)
458 for e in sorted_:
462 for e in sorted_:
459 yield e
463 yield e
460 else:
464 else:
461 if not seen(nf) and match(nf):
465 if not seen(nf) and match(nf):
462 if self.supported_type(ff, st, verbose=True):
466 if self.supported_type(ff, st, verbose=True):
463 yield 'f', nf, st
467 yield 'f', nf, st
464 elif ff in dc:
468 elif ff in dc:
465 yield 'm', nf, st
469 yield 'm', nf, st
466
470
467 # step two run through anything left in the dc hash and yield
471 # step two run through anything left in the dc hash and yield
468 # if we haven't already seen it
472 # if we haven't already seen it
469 ks = dc.keys()
473 ks = dc.keys()
470 ks.sort()
474 ks.sort()
471 for k in ks:
475 for k in ks:
472 if not seen(k) and imatch(k):
476 if not seen(k) and imatch(k):
473 yield 'm', k, None
477 yield 'm', k, None
474
478
475 def status(self, files=None, match=util.always, list_ignored=False,
479 def status(self, files=None, match=util.always, list_ignored=False,
476 list_clean=False):
480 list_clean=False):
477 lookup, modified, added, unknown, ignored = [], [], [], [], []
481 lookup, modified, added, unknown, ignored = [], [], [], [], []
478 removed, deleted, clean = [], [], []
482 removed, deleted, clean = [], [], []
479
483
480 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
484 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
481 try:
485 try:
482 type_, mode, size, time = self[fn]
486 type_, mode, size, time = self[fn]
483 except KeyError:
487 except KeyError:
484 if list_ignored and self.ignore(fn):
488 if list_ignored and self.ignore(fn):
485 ignored.append(fn)
489 ignored.append(fn)
486 else:
490 else:
487 unknown.append(fn)
491 unknown.append(fn)
488 continue
492 continue
489 if src == 'm':
493 if src == 'm':
490 nonexistent = True
494 nonexistent = True
491 if not st:
495 if not st:
492 try:
496 try:
493 st = os.lstat(self.wjoin(fn))
497 st = os.lstat(self.wjoin(fn))
494 except OSError, inst:
498 except OSError, inst:
495 if inst.errno != errno.ENOENT:
499 if inst.errno != errno.ENOENT:
496 raise
500 raise
497 st = None
501 st = None
498 # We need to re-check that it is a valid file
502 # We need to re-check that it is a valid file
499 if st and self.supported_type(fn, st):
503 if st and self.supported_type(fn, st):
500 nonexistent = False
504 nonexistent = False
501 # XXX: what to do with file no longer present in the fs
505 # XXX: what to do with file no longer present in the fs
502 # who are not removed in the dirstate ?
506 # who are not removed in the dirstate ?
503 if nonexistent and type_ in "nm":
507 if nonexistent and type_ in "nm":
504 deleted.append(fn)
508 deleted.append(fn)
505 continue
509 continue
506 # check the common case first
510 # check the common case first
507 if type_ == 'n':
511 if type_ == 'n':
508 if not st:
512 if not st:
509 st = os.lstat(self.wjoin(fn))
513 st = os.lstat(self.wjoin(fn))
510 if size >= 0 and (size != st.st_size
514 if size >= 0 and (size != st.st_size
511 or (mode ^ st.st_mode) & 0100):
515 or (mode ^ st.st_mode) & 0100):
512 modified.append(fn)
516 modified.append(fn)
513 elif time != int(st.st_mtime):
517 elif time != int(st.st_mtime):
514 lookup.append(fn)
518 lookup.append(fn)
515 elif list_clean:
519 elif list_clean:
516 clean.append(fn)
520 clean.append(fn)
517 elif type_ == 'm':
521 elif type_ == 'm':
518 modified.append(fn)
522 modified.append(fn)
519 elif type_ == 'a':
523 elif type_ == 'a':
520 added.append(fn)
524 added.append(fn)
521 elif type_ == 'r':
525 elif type_ == 'r':
522 removed.append(fn)
526 removed.append(fn)
523
527
524 return (lookup, modified, added, removed, deleted, unknown, ignored,
528 return (lookup, modified, added, removed, deleted, unknown, ignored,
525 clean)
529 clean)
@@ -1,209 +1,218 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from revlog import *
8 from revlog import *
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "array bisect struct")
11 demandload(globals(), "array bisect struct")
12 demandload(globals(), "mdiff")
12 demandload(globals(), "mdiff")
13
13
14 class manifestdict(dict):
14 class manifestdict(dict):
15 def __init__(self, mapping=None, flags=None):
15 def __init__(self, mapping=None, flags=None):
16 if mapping is None: mapping = {}
16 if mapping is None: mapping = {}
17 if flags is None: flags = {}
17 if flags is None: flags = {}
18 dict.__init__(self, mapping)
18 dict.__init__(self, mapping)
19 self._flags = flags
19 self._flags = flags
20 def flags(self, f):
20 def flags(self, f):
21 return self._flags.get(f, "")
21 return self._flags.get(f, "")
22 def execf(self, f):
22 def execf(self, f):
23 "test for executable in manifest flags"
23 "test for executable in manifest flags"
24 return "x" in self.flags(f)
24 return "x" in self.flags(f)
25 def linkf(self, f):
25 def linkf(self, f):
26 "test for symlink in manifest flags"
26 "test for symlink in manifest flags"
27 return "l" in self.flags(f)
27 return "l" in self.flags(f)
28 def rawset(self, f, entry):
28 def rawset(self, f, entry):
29 self[f] = bin(entry[:40])
29 self[f] = bin(entry[:40])
30 fl = entry[40:-1]
30 fl = entry[40:-1]
31 if fl: self._flags[f] = fl
31 if fl: self._flags[f] = fl
32 def set(self, f, execf=False, linkf=False):
32 def set(self, f, execf=False, linkf=False):
33 if linkf: self._flags[f] = "l"
33 if linkf: self._flags[f] = "l"
34 elif execf: self._flags[f] = "x"
34 elif execf: self._flags[f] = "x"
35 else: self._flags[f] = ""
35 else: self._flags[f] = ""
36 def copy(self):
36 def copy(self):
37 return manifestdict(dict.copy(self), dict.copy(self._flags))
37 return manifestdict(dict.copy(self), dict.copy(self._flags))
38
38
39 class manifest(revlog):
39 class manifest(revlog):
40 def __init__(self, opener, defversion=REVLOGV0):
40 def __init__(self, opener, defversion=REVLOGV0):
41 self.mapcache = None
41 self.mapcache = None
42 self.listcache = None
42 self.listcache = None
43 revlog.__init__(self, opener, "00manifest.i", "00manifest.d",
43 revlog.__init__(self, opener, "00manifest.i", "00manifest.d",
44 defversion)
44 defversion)
45
45
46 def parselines(self, lines):
46 def parselines(self, lines):
47 for l in lines.splitlines(1):
47 for l in lines.splitlines(1):
48 yield l.split('\0')
48 yield l.split('\0')
49
49
50 def readdelta(self, node):
50 def readdelta(self, node):
51 delta = mdiff.patchtext(self.delta(node))
51 delta = mdiff.patchtext(self.delta(node))
52 deltamap = manifestdict()
52 deltamap = manifestdict()
53 for f, n in self.parselines(delta):
53 for f, n in self.parselines(delta):
54 deltamap.rawset(f, n)
54 deltamap.rawset(f, n)
55 return deltamap
55 return deltamap
56
56
57 def read(self, node):
57 def read(self, node):
58 if node == nullid: return manifestdict() # don't upset local cache
58 if node == nullid: return manifestdict() # don't upset local cache
59 if self.mapcache and self.mapcache[0] == node:
59 if self.mapcache and self.mapcache[0] == node:
60 return self.mapcache[1]
60 return self.mapcache[1]
61 text = self.revision(node)
61 text = self.revision(node)
62 self.listcache = array.array('c', text)
62 self.listcache = array.array('c', text)
63 mapping = manifestdict()
63 mapping = manifestdict()
64 for f, n in self.parselines(text):
64 for f, n in self.parselines(text):
65 mapping.rawset(f, n)
65 mapping.rawset(f, n)
66 self.mapcache = (node, mapping)
66 self.mapcache = (node, mapping)
67 return mapping
67 return mapping
68
68
69 def _search(self, m, s, lo=0, hi=None):
69 def _search(self, m, s, lo=0, hi=None):
70 '''return a tuple (start, end) that says where to find s within m.
70 '''return a tuple (start, end) that says where to find s within m.
71
71
72 If the string is found m[start:end] are the line containing
72 If the string is found m[start:end] are the line containing
73 that string. If start == end the string was not found and
73 that string. If start == end the string was not found and
74 they indicate the proper sorted insertion point. This was
74 they indicate the proper sorted insertion point. This was
75 taken from bisect_left, and modified to find line start/end as
75 taken from bisect_left, and modified to find line start/end as
76 it goes along.
76 it goes along.
77
77
78 m should be a buffer or a string
78 m should be a buffer or a string
79 s is a string'''
79 s is a string'''
80 def advance(i, c):
80 def advance(i, c):
81 while i < lenm and m[i] != c:
81 while i < lenm and m[i] != c:
82 i += 1
82 i += 1
83 return i
83 return i
84 lenm = len(m)
84 lenm = len(m)
85 if not hi:
85 if not hi:
86 hi = lenm
86 hi = lenm
87 while lo < hi:
87 while lo < hi:
88 mid = (lo + hi) // 2
88 mid = (lo + hi) // 2
89 start = mid
89 start = mid
90 while start > 0 and m[start-1] != '\n':
90 while start > 0 and m[start-1] != '\n':
91 start -= 1
91 start -= 1
92 end = advance(start, '\0')
92 end = advance(start, '\0')
93 if m[start:end] < s:
93 if m[start:end] < s:
94 # we know that after the null there are 40 bytes of sha1
94 # we know that after the null there are 40 bytes of sha1
95 # this translates to the bisect lo = mid + 1
95 # this translates to the bisect lo = mid + 1
96 lo = advance(end + 40, '\n') + 1
96 lo = advance(end + 40, '\n') + 1
97 else:
97 else:
98 # this translates to the bisect hi = mid
98 # this translates to the bisect hi = mid
99 hi = start
99 hi = start
100 end = advance(lo, '\0')
100 end = advance(lo, '\0')
101 found = m[lo:end]
101 found = m[lo:end]
102 if cmp(s, found) == 0:
102 if cmp(s, found) == 0:
103 # we know that after the null there are 40 bytes of sha1
103 # we know that after the null there are 40 bytes of sha1
104 end = advance(end + 40, '\n')
104 end = advance(end + 40, '\n')
105 return (lo, end+1)
105 return (lo, end+1)
106 else:
106 else:
107 return (lo, lo)
107 return (lo, lo)
108
108
109 def find(self, node, f):
109 def find(self, node, f):
110 '''look up entry for a single file efficiently.
110 '''look up entry for a single file efficiently.
111 return (node, flag) pair if found, (None, None) if not.'''
111 return (node, flag) pair if found, (None, None) if not.'''
112 if self.mapcache and node == self.mapcache[0]:
112 if self.mapcache and node == self.mapcache[0]:
113 return self.mapcache[1].get(f), self.mapcache[1].flags(f)
113 return self.mapcache[1].get(f), self.mapcache[1].flags(f)
114 text = self.revision(node)
114 text = self.revision(node)
115 start, end = self._search(text, f)
115 start, end = self._search(text, f)
116 if start == end:
116 if start == end:
117 return None, None
117 return None, None
118 l = text[start:end]
118 l = text[start:end]
119 f, n = l.split('\0')
119 f, n = l.split('\0')
120 return bin(n[:40]), n[40:-1] == 'x'
120 return bin(n[:40]), n[40:-1] == 'x'
121
121
122 def add(self, map, transaction, link, p1=None, p2=None,
122 def add(self, map, transaction, link, p1=None, p2=None,
123 changed=None):
123 changed=None):
124 # apply the changes collected during the bisect loop to our addlist
124 # apply the changes collected during the bisect loop to our addlist
125 # return a delta suitable for addrevision
125 # return a delta suitable for addrevision
126 def addlistdelta(addlist, x):
126 def addlistdelta(addlist, x):
127 # start from the bottom up
127 # start from the bottom up
128 # so changes to the offsets don't mess things up.
128 # so changes to the offsets don't mess things up.
129 i = len(x)
129 i = len(x)
130 while i > 0:
130 while i > 0:
131 i -= 1
131 i -= 1
132 start = x[i][0]
132 start = x[i][0]
133 end = x[i][1]
133 end = x[i][1]
134 if x[i][2]:
134 if x[i][2]:
135 addlist[start:end] = array.array('c', x[i][2])
135 addlist[start:end] = array.array('c', x[i][2])
136 else:
136 else:
137 del addlist[start:end]
137 del addlist[start:end]
138 return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \
138 return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \
139 for d in x ])
139 for d in x ])
140
140
141 def checkforbidden(f):
142 if '\n' in f or '\r' in f:
143 raise RevlogError(_("'\\n' and '\\r' disallowed in filenames"))
144
141 # if we're using the listcache, make sure it is valid and
145 # if we're using the listcache, make sure it is valid and
142 # parented by the same node we're diffing against
146 # parented by the same node we're diffing against
143 if not changed or not self.listcache or not p1 or \
147 if not changed or not self.listcache or not p1 or \
144 self.mapcache[0] != p1:
148 self.mapcache[0] != p1:
145 files = map.keys()
149 files = map.keys()
146 files.sort()
150 files.sort()
147
151
152 for f in files:
153 checkforbidden(f)
154
148 # if this is changed to support newlines in filenames,
155 # if this is changed to support newlines in filenames,
149 # be sure to check the templates/ dir again (especially *-raw.tmpl)
156 # be sure to check the templates/ dir again (especially *-raw.tmpl)
150 text = ["%s\000%s%s\n" % (f, hex(map[f]), map.flags(f)) for f in files]
157 text = ["%s\000%s%s\n" % (f, hex(map[f]), map.flags(f)) for f in files]
151 self.listcache = array.array('c', "".join(text))
158 self.listcache = array.array('c', "".join(text))
152 cachedelta = None
159 cachedelta = None
153 else:
160 else:
154 addlist = self.listcache
161 addlist = self.listcache
155
162
163 for f in changed[0]:
164 checkforbidden(f)
156 # combine the changed lists into one list for sorting
165 # combine the changed lists into one list for sorting
157 work = [[x, 0] for x in changed[0]]
166 work = [[x, 0] for x in changed[0]]
158 work[len(work):] = [[x, 1] for x in changed[1]]
167 work[len(work):] = [[x, 1] for x in changed[1]]
159 work.sort()
168 work.sort()
160
169
161 delta = []
170 delta = []
162 dstart = None
171 dstart = None
163 dend = None
172 dend = None
164 dline = [""]
173 dline = [""]
165 start = 0
174 start = 0
166 # zero copy representation of addlist as a buffer
175 # zero copy representation of addlist as a buffer
167 addbuf = buffer(addlist)
176 addbuf = buffer(addlist)
168
177
169 # start with a readonly loop that finds the offset of
178 # start with a readonly loop that finds the offset of
170 # each line and creates the deltas
179 # each line and creates the deltas
171 for w in work:
180 for w in work:
172 f = w[0]
181 f = w[0]
173 # bs will either be the index of the item or the insert point
182 # bs will either be the index of the item or the insert point
174 start, end = self._search(addbuf, f, start)
183 start, end = self._search(addbuf, f, start)
175 if w[1] == 0:
184 if w[1] == 0:
176 l = "%s\000%s%s\n" % (f, hex(map[f]), map.flags(f))
185 l = "%s\000%s%s\n" % (f, hex(map[f]), map.flags(f))
177 else:
186 else:
178 l = ""
187 l = ""
179 if start == end and w[1] == 1:
188 if start == end and w[1] == 1:
180 # item we want to delete was not found, error out
189 # item we want to delete was not found, error out
181 raise AssertionError(
190 raise AssertionError(
182 _("failed to remove %s from manifest") % f)
191 _("failed to remove %s from manifest") % f)
183 if dstart != None and dstart <= start and dend >= start:
192 if dstart != None and dstart <= start and dend >= start:
184 if dend < end:
193 if dend < end:
185 dend = end
194 dend = end
186 if l:
195 if l:
187 dline.append(l)
196 dline.append(l)
188 else:
197 else:
189 if dstart != None:
198 if dstart != None:
190 delta.append([dstart, dend, "".join(dline)])
199 delta.append([dstart, dend, "".join(dline)])
191 dstart = start
200 dstart = start
192 dend = end
201 dend = end
193 dline = [l]
202 dline = [l]
194
203
195 if dstart != None:
204 if dstart != None:
196 delta.append([dstart, dend, "".join(dline)])
205 delta.append([dstart, dend, "".join(dline)])
197 # apply the delta to the addlist, and get a delta for addrevision
206 # apply the delta to the addlist, and get a delta for addrevision
198 cachedelta = addlistdelta(addlist, delta)
207 cachedelta = addlistdelta(addlist, delta)
199
208
200 # the delta is only valid if we've been processing the tip revision
209 # the delta is only valid if we've been processing the tip revision
201 if self.mapcache[0] != self.tip():
210 if self.mapcache[0] != self.tip():
202 cachedelta = None
211 cachedelta = None
203 self.listcache = addlist
212 self.listcache = addlist
204
213
205 n = self.addrevision(buffer(self.listcache), transaction, link, p1, \
214 n = self.addrevision(buffer(self.listcache), transaction, link, p1, \
206 p2, cachedelta)
215 p2, cachedelta)
207 self.mapcache = (n, map)
216 self.mapcache = (n, map)
208
217
209 return n
218 return n
General Comments 0
You need to be logged in to leave comments. Login now