##// END OF EJS Templates
dirstate: add p1/p2 convenience methods
Matt Mackall -
r13876:10c7d92a default
parent child Browse files
Show More
@@ -1,720 +1,726 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import util, ignore, osutil, parsers, encoding
10 import util, ignore, osutil, parsers, encoding
11 import struct, os, stat, errno
11 import struct, os, stat, errno
12 import cStringIO
12 import cStringIO
13
13
14 _format = ">cllll"
14 _format = ">cllll"
15 propertycache = util.propertycache
15 propertycache = util.propertycache
16
16
17 def _finddirs(path):
17 def _finddirs(path):
18 pos = path.rfind('/')
18 pos = path.rfind('/')
19 while pos != -1:
19 while pos != -1:
20 yield path[:pos]
20 yield path[:pos]
21 pos = path.rfind('/', 0, pos)
21 pos = path.rfind('/', 0, pos)
22
22
23 def _incdirs(dirs, path):
23 def _incdirs(dirs, path):
24 for base in _finddirs(path):
24 for base in _finddirs(path):
25 if base in dirs:
25 if base in dirs:
26 dirs[base] += 1
26 dirs[base] += 1
27 return
27 return
28 dirs[base] = 1
28 dirs[base] = 1
29
29
30 def _decdirs(dirs, path):
30 def _decdirs(dirs, path):
31 for base in _finddirs(path):
31 for base in _finddirs(path):
32 if dirs[base] > 1:
32 if dirs[base] > 1:
33 dirs[base] -= 1
33 dirs[base] -= 1
34 return
34 return
35 del dirs[base]
35 del dirs[base]
36
36
37 class dirstate(object):
37 class dirstate(object):
38
38
39 def __init__(self, opener, ui, root, validate):
39 def __init__(self, opener, ui, root, validate):
40 '''Create a new dirstate object.
40 '''Create a new dirstate object.
41
41
42 opener is an open()-like callable that can be used to open the
42 opener is an open()-like callable that can be used to open the
43 dirstate file; root is the root of the directory tracked by
43 dirstate file; root is the root of the directory tracked by
44 the dirstate.
44 the dirstate.
45 '''
45 '''
46 self._opener = opener
46 self._opener = opener
47 self._validate = validate
47 self._validate = validate
48 self._root = root
48 self._root = root
49 self._rootdir = os.path.join(root, '')
49 self._rootdir = os.path.join(root, '')
50 self._dirty = False
50 self._dirty = False
51 self._dirtypl = False
51 self._dirtypl = False
52 self._lastnormaltime = None
52 self._lastnormaltime = None
53 self._ui = ui
53 self._ui = ui
54
54
55 @propertycache
55 @propertycache
56 def _map(self):
56 def _map(self):
57 '''Return the dirstate contents as a map from filename to
57 '''Return the dirstate contents as a map from filename to
58 (state, mode, size, time).'''
58 (state, mode, size, time).'''
59 self._read()
59 self._read()
60 return self._map
60 return self._map
61
61
62 @propertycache
62 @propertycache
63 def _copymap(self):
63 def _copymap(self):
64 self._read()
64 self._read()
65 return self._copymap
65 return self._copymap
66
66
67 @propertycache
67 @propertycache
68 def _foldmap(self):
68 def _foldmap(self):
69 f = {}
69 f = {}
70 for name in self._map:
70 for name in self._map:
71 f[os.path.normcase(name)] = name
71 f[os.path.normcase(name)] = name
72 return f
72 return f
73
73
74 @propertycache
74 @propertycache
75 def _branch(self):
75 def _branch(self):
76 try:
76 try:
77 return self._opener("branch").read().strip() or "default"
77 return self._opener("branch").read().strip() or "default"
78 except IOError:
78 except IOError:
79 return "default"
79 return "default"
80
80
81 @propertycache
81 @propertycache
82 def _pl(self):
82 def _pl(self):
83 try:
83 try:
84 fp = self._opener("dirstate")
84 fp = self._opener("dirstate")
85 st = fp.read(40)
85 st = fp.read(40)
86 fp.close()
86 fp.close()
87 l = len(st)
87 l = len(st)
88 if l == 40:
88 if l == 40:
89 return st[:20], st[20:40]
89 return st[:20], st[20:40]
90 elif l > 0 and l < 40:
90 elif l > 0 and l < 40:
91 raise util.Abort(_('working directory state appears damaged!'))
91 raise util.Abort(_('working directory state appears damaged!'))
92 except IOError, err:
92 except IOError, err:
93 if err.errno != errno.ENOENT:
93 if err.errno != errno.ENOENT:
94 raise
94 raise
95 return [nullid, nullid]
95 return [nullid, nullid]
96
96
97 @propertycache
97 @propertycache
98 def _dirs(self):
98 def _dirs(self):
99 dirs = {}
99 dirs = {}
100 for f, s in self._map.iteritems():
100 for f, s in self._map.iteritems():
101 if s[0] != 'r':
101 if s[0] != 'r':
102 _incdirs(dirs, f)
102 _incdirs(dirs, f)
103 return dirs
103 return dirs
104
104
105 @propertycache
105 @propertycache
106 def _ignore(self):
106 def _ignore(self):
107 files = [self._join('.hgignore')]
107 files = [self._join('.hgignore')]
108 for name, path in self._ui.configitems("ui"):
108 for name, path in self._ui.configitems("ui"):
109 if name == 'ignore' or name.startswith('ignore.'):
109 if name == 'ignore' or name.startswith('ignore.'):
110 files.append(util.expandpath(path))
110 files.append(util.expandpath(path))
111 return ignore.ignore(self._root, files, self._ui.warn)
111 return ignore.ignore(self._root, files, self._ui.warn)
112
112
113 @propertycache
113 @propertycache
114 def _slash(self):
114 def _slash(self):
115 return self._ui.configbool('ui', 'slash') and os.sep != '/'
115 return self._ui.configbool('ui', 'slash') and os.sep != '/'
116
116
117 @propertycache
117 @propertycache
118 def _checklink(self):
118 def _checklink(self):
119 return util.checklink(self._root)
119 return util.checklink(self._root)
120
120
121 @propertycache
121 @propertycache
122 def _checkexec(self):
122 def _checkexec(self):
123 return util.checkexec(self._root)
123 return util.checkexec(self._root)
124
124
125 @propertycache
125 @propertycache
126 def _checkcase(self):
126 def _checkcase(self):
127 return not util.checkcase(self._join('.hg'))
127 return not util.checkcase(self._join('.hg'))
128
128
129 def _join(self, f):
129 def _join(self, f):
130 # much faster than os.path.join()
130 # much faster than os.path.join()
131 # it's safe because f is always a relative path
131 # it's safe because f is always a relative path
132 return self._rootdir + f
132 return self._rootdir + f
133
133
134 def flagfunc(self, fallback):
134 def flagfunc(self, fallback):
135 if self._checklink:
135 if self._checklink:
136 if self._checkexec:
136 if self._checkexec:
137 def f(x):
137 def f(x):
138 p = self._join(x)
138 p = self._join(x)
139 if os.path.islink(p):
139 if os.path.islink(p):
140 return 'l'
140 return 'l'
141 if util.is_exec(p):
141 if util.is_exec(p):
142 return 'x'
142 return 'x'
143 return ''
143 return ''
144 return f
144 return f
145 def f(x):
145 def f(x):
146 if os.path.islink(self._join(x)):
146 if os.path.islink(self._join(x)):
147 return 'l'
147 return 'l'
148 if 'x' in fallback(x):
148 if 'x' in fallback(x):
149 return 'x'
149 return 'x'
150 return ''
150 return ''
151 return f
151 return f
152 if self._checkexec:
152 if self._checkexec:
153 def f(x):
153 def f(x):
154 if 'l' in fallback(x):
154 if 'l' in fallback(x):
155 return 'l'
155 return 'l'
156 if util.is_exec(self._join(x)):
156 if util.is_exec(self._join(x)):
157 return 'x'
157 return 'x'
158 return ''
158 return ''
159 return f
159 return f
160 return fallback
160 return fallback
161
161
162 def getcwd(self):
162 def getcwd(self):
163 cwd = os.getcwd()
163 cwd = os.getcwd()
164 if cwd == self._root:
164 if cwd == self._root:
165 return ''
165 return ''
166 # self._root ends with a path separator if self._root is '/' or 'C:\'
166 # self._root ends with a path separator if self._root is '/' or 'C:\'
167 rootsep = self._root
167 rootsep = self._root
168 if not util.endswithsep(rootsep):
168 if not util.endswithsep(rootsep):
169 rootsep += os.sep
169 rootsep += os.sep
170 if cwd.startswith(rootsep):
170 if cwd.startswith(rootsep):
171 return cwd[len(rootsep):]
171 return cwd[len(rootsep):]
172 else:
172 else:
173 # we're outside the repo. return an absolute path.
173 # we're outside the repo. return an absolute path.
174 return cwd
174 return cwd
175
175
176 def pathto(self, f, cwd=None):
176 def pathto(self, f, cwd=None):
177 if cwd is None:
177 if cwd is None:
178 cwd = self.getcwd()
178 cwd = self.getcwd()
179 path = util.pathto(self._root, cwd, f)
179 path = util.pathto(self._root, cwd, f)
180 if self._slash:
180 if self._slash:
181 return util.normpath(path)
181 return util.normpath(path)
182 return path
182 return path
183
183
184 def __getitem__(self, key):
184 def __getitem__(self, key):
185 '''Return the current state of key (a filename) in the dirstate.
185 '''Return the current state of key (a filename) in the dirstate.
186
186
187 States are:
187 States are:
188 n normal
188 n normal
189 m needs merging
189 m needs merging
190 r marked for removal
190 r marked for removal
191 a marked for addition
191 a marked for addition
192 ? not tracked
192 ? not tracked
193 '''
193 '''
194 return self._map.get(key, ("?",))[0]
194 return self._map.get(key, ("?",))[0]
195
195
196 def __contains__(self, key):
196 def __contains__(self, key):
197 return key in self._map
197 return key in self._map
198
198
199 def __iter__(self):
199 def __iter__(self):
200 for x in sorted(self._map):
200 for x in sorted(self._map):
201 yield x
201 yield x
202
202
203 def parents(self):
203 def parents(self):
204 return [self._validate(p) for p in self._pl]
204 return [self._validate(p) for p in self._pl]
205
205
206 def p1(self):
207 return self._validate(self._pl[0])
208
209 def p2(self):
210 return self._validate(self._pl[1])
211
206 def branch(self):
212 def branch(self):
207 return encoding.tolocal(self._branch)
213 return encoding.tolocal(self._branch)
208
214
209 def setparents(self, p1, p2=nullid):
215 def setparents(self, p1, p2=nullid):
210 self._dirty = self._dirtypl = True
216 self._dirty = self._dirtypl = True
211 self._pl = p1, p2
217 self._pl = p1, p2
212
218
213 def setbranch(self, branch):
219 def setbranch(self, branch):
214 if branch in ['tip', '.', 'null']:
220 if branch in ['tip', '.', 'null']:
215 raise util.Abort(_('the name \'%s\' is reserved') % branch)
221 raise util.Abort(_('the name \'%s\' is reserved') % branch)
216 self._branch = encoding.fromlocal(branch)
222 self._branch = encoding.fromlocal(branch)
217 self._opener("branch", "w").write(self._branch + '\n')
223 self._opener("branch", "w").write(self._branch + '\n')
218
224
219 def _read(self):
225 def _read(self):
220 self._map = {}
226 self._map = {}
221 self._copymap = {}
227 self._copymap = {}
222 try:
228 try:
223 st = self._opener("dirstate").read()
229 st = self._opener("dirstate").read()
224 except IOError, err:
230 except IOError, err:
225 if err.errno != errno.ENOENT:
231 if err.errno != errno.ENOENT:
226 raise
232 raise
227 return
233 return
228 if not st:
234 if not st:
229 return
235 return
230
236
231 p = parsers.parse_dirstate(self._map, self._copymap, st)
237 p = parsers.parse_dirstate(self._map, self._copymap, st)
232 if not self._dirtypl:
238 if not self._dirtypl:
233 self._pl = p
239 self._pl = p
234
240
235 def invalidate(self):
241 def invalidate(self):
236 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
242 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
237 "_ignore"):
243 "_ignore"):
238 if a in self.__dict__:
244 if a in self.__dict__:
239 delattr(self, a)
245 delattr(self, a)
240 self._lastnormaltime = None
246 self._lastnormaltime = None
241 self._dirty = False
247 self._dirty = False
242
248
243 def copy(self, source, dest):
249 def copy(self, source, dest):
244 """Mark dest as a copy of source. Unmark dest if source is None."""
250 """Mark dest as a copy of source. Unmark dest if source is None."""
245 if source == dest:
251 if source == dest:
246 return
252 return
247 self._dirty = True
253 self._dirty = True
248 if source is not None:
254 if source is not None:
249 self._copymap[dest] = source
255 self._copymap[dest] = source
250 elif dest in self._copymap:
256 elif dest in self._copymap:
251 del self._copymap[dest]
257 del self._copymap[dest]
252
258
253 def copied(self, file):
259 def copied(self, file):
254 return self._copymap.get(file, None)
260 return self._copymap.get(file, None)
255
261
256 def copies(self):
262 def copies(self):
257 return self._copymap
263 return self._copymap
258
264
259 def _droppath(self, f):
265 def _droppath(self, f):
260 if self[f] not in "?r" and "_dirs" in self.__dict__:
266 if self[f] not in "?r" and "_dirs" in self.__dict__:
261 _decdirs(self._dirs, f)
267 _decdirs(self._dirs, f)
262
268
263 def _addpath(self, f, check=False):
269 def _addpath(self, f, check=False):
264 oldstate = self[f]
270 oldstate = self[f]
265 if check or oldstate == "r":
271 if check or oldstate == "r":
266 if '\r' in f or '\n' in f:
272 if '\r' in f or '\n' in f:
267 raise util.Abort(
273 raise util.Abort(
268 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
274 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
269 if f in self._dirs:
275 if f in self._dirs:
270 raise util.Abort(_('directory %r already in dirstate') % f)
276 raise util.Abort(_('directory %r already in dirstate') % f)
271 # shadows
277 # shadows
272 for d in _finddirs(f):
278 for d in _finddirs(f):
273 if d in self._dirs:
279 if d in self._dirs:
274 break
280 break
275 if d in self._map and self[d] != 'r':
281 if d in self._map and self[d] != 'r':
276 raise util.Abort(
282 raise util.Abort(
277 _('file %r in dirstate clashes with %r') % (d, f))
283 _('file %r in dirstate clashes with %r') % (d, f))
278 if oldstate in "?r" and "_dirs" in self.__dict__:
284 if oldstate in "?r" and "_dirs" in self.__dict__:
279 _incdirs(self._dirs, f)
285 _incdirs(self._dirs, f)
280
286
281 def normal(self, f):
287 def normal(self, f):
282 '''Mark a file normal and clean.'''
288 '''Mark a file normal and clean.'''
283 self._dirty = True
289 self._dirty = True
284 self._addpath(f)
290 self._addpath(f)
285 s = os.lstat(self._join(f))
291 s = os.lstat(self._join(f))
286 mtime = int(s.st_mtime)
292 mtime = int(s.st_mtime)
287 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
293 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
288 if f in self._copymap:
294 if f in self._copymap:
289 del self._copymap[f]
295 del self._copymap[f]
290 if mtime > self._lastnormaltime:
296 if mtime > self._lastnormaltime:
291 # Remember the most recent modification timeslot for status(),
297 # Remember the most recent modification timeslot for status(),
292 # to make sure we won't miss future size-preserving file content
298 # to make sure we won't miss future size-preserving file content
293 # modifications that happen within the same timeslot.
299 # modifications that happen within the same timeslot.
294 self._lastnormaltime = mtime
300 self._lastnormaltime = mtime
295
301
296 def normallookup(self, f):
302 def normallookup(self, f):
297 '''Mark a file normal, but possibly dirty.'''
303 '''Mark a file normal, but possibly dirty.'''
298 if self._pl[1] != nullid and f in self._map:
304 if self._pl[1] != nullid and f in self._map:
299 # if there is a merge going on and the file was either
305 # if there is a merge going on and the file was either
300 # in state 'm' (-1) or coming from other parent (-2) before
306 # in state 'm' (-1) or coming from other parent (-2) before
301 # being removed, restore that state.
307 # being removed, restore that state.
302 entry = self._map[f]
308 entry = self._map[f]
303 if entry[0] == 'r' and entry[2] in (-1, -2):
309 if entry[0] == 'r' and entry[2] in (-1, -2):
304 source = self._copymap.get(f)
310 source = self._copymap.get(f)
305 if entry[2] == -1:
311 if entry[2] == -1:
306 self.merge(f)
312 self.merge(f)
307 elif entry[2] == -2:
313 elif entry[2] == -2:
308 self.otherparent(f)
314 self.otherparent(f)
309 if source:
315 if source:
310 self.copy(source, f)
316 self.copy(source, f)
311 return
317 return
312 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
318 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
313 return
319 return
314 self._dirty = True
320 self._dirty = True
315 self._addpath(f)
321 self._addpath(f)
316 self._map[f] = ('n', 0, -1, -1)
322 self._map[f] = ('n', 0, -1, -1)
317 if f in self._copymap:
323 if f in self._copymap:
318 del self._copymap[f]
324 del self._copymap[f]
319
325
320 def otherparent(self, f):
326 def otherparent(self, f):
321 '''Mark as coming from the other parent, always dirty.'''
327 '''Mark as coming from the other parent, always dirty.'''
322 if self._pl[1] == nullid:
328 if self._pl[1] == nullid:
323 raise util.Abort(_("setting %r to other parent "
329 raise util.Abort(_("setting %r to other parent "
324 "only allowed in merges") % f)
330 "only allowed in merges") % f)
325 self._dirty = True
331 self._dirty = True
326 self._addpath(f)
332 self._addpath(f)
327 self._map[f] = ('n', 0, -2, -1)
333 self._map[f] = ('n', 0, -2, -1)
328 if f in self._copymap:
334 if f in self._copymap:
329 del self._copymap[f]
335 del self._copymap[f]
330
336
331 def add(self, f):
337 def add(self, f):
332 '''Mark a file added.'''
338 '''Mark a file added.'''
333 self._dirty = True
339 self._dirty = True
334 self._addpath(f, True)
340 self._addpath(f, True)
335 self._map[f] = ('a', 0, -1, -1)
341 self._map[f] = ('a', 0, -1, -1)
336 if f in self._copymap:
342 if f in self._copymap:
337 del self._copymap[f]
343 del self._copymap[f]
338
344
339 def remove(self, f):
345 def remove(self, f):
340 '''Mark a file removed.'''
346 '''Mark a file removed.'''
341 self._dirty = True
347 self._dirty = True
342 self._droppath(f)
348 self._droppath(f)
343 size = 0
349 size = 0
344 if self._pl[1] != nullid and f in self._map:
350 if self._pl[1] != nullid and f in self._map:
345 # backup the previous state
351 # backup the previous state
346 entry = self._map[f]
352 entry = self._map[f]
347 if entry[0] == 'm': # merge
353 if entry[0] == 'm': # merge
348 size = -1
354 size = -1
349 elif entry[0] == 'n' and entry[2] == -2: # other parent
355 elif entry[0] == 'n' and entry[2] == -2: # other parent
350 size = -2
356 size = -2
351 self._map[f] = ('r', 0, size, 0)
357 self._map[f] = ('r', 0, size, 0)
352 if size == 0 and f in self._copymap:
358 if size == 0 and f in self._copymap:
353 del self._copymap[f]
359 del self._copymap[f]
354
360
355 def merge(self, f):
361 def merge(self, f):
356 '''Mark a file merged.'''
362 '''Mark a file merged.'''
357 self._dirty = True
363 self._dirty = True
358 s = os.lstat(self._join(f))
364 s = os.lstat(self._join(f))
359 self._addpath(f)
365 self._addpath(f)
360 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
366 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
361 if f in self._copymap:
367 if f in self._copymap:
362 del self._copymap[f]
368 del self._copymap[f]
363
369
364 def forget(self, f):
370 def forget(self, f):
365 '''Forget a file.'''
371 '''Forget a file.'''
366 self._dirty = True
372 self._dirty = True
367 try:
373 try:
368 self._droppath(f)
374 self._droppath(f)
369 del self._map[f]
375 del self._map[f]
370 except KeyError:
376 except KeyError:
371 self._ui.warn(_("not in dirstate: %s\n") % f)
377 self._ui.warn(_("not in dirstate: %s\n") % f)
372
378
373 def _normalize(self, path, isknown):
379 def _normalize(self, path, isknown):
374 normed = os.path.normcase(path)
380 normed = os.path.normcase(path)
375 folded = self._foldmap.get(normed, None)
381 folded = self._foldmap.get(normed, None)
376 if folded is None:
382 if folded is None:
377 if isknown or not os.path.lexists(os.path.join(self._root, path)):
383 if isknown or not os.path.lexists(os.path.join(self._root, path)):
378 folded = path
384 folded = path
379 else:
385 else:
380 folded = self._foldmap.setdefault(normed,
386 folded = self._foldmap.setdefault(normed,
381 util.fspath(path, self._root))
387 util.fspath(path, self._root))
382 return folded
388 return folded
383
389
384 def normalize(self, path, isknown=False):
390 def normalize(self, path, isknown=False):
385 '''
391 '''
386 normalize the case of a pathname when on a casefolding filesystem
392 normalize the case of a pathname when on a casefolding filesystem
387
393
388 isknown specifies whether the filename came from walking the
394 isknown specifies whether the filename came from walking the
389 disk, to avoid extra filesystem access
395 disk, to avoid extra filesystem access
390
396
391 The normalized case is determined based on the following precedence:
397 The normalized case is determined based on the following precedence:
392
398
393 - version of name already stored in the dirstate
399 - version of name already stored in the dirstate
394 - version of name stored on disk
400 - version of name stored on disk
395 - version provided via command arguments
401 - version provided via command arguments
396 '''
402 '''
397
403
398 if self._checkcase:
404 if self._checkcase:
399 return self._normalize(path, isknown)
405 return self._normalize(path, isknown)
400 return path
406 return path
401
407
402 def clear(self):
408 def clear(self):
403 self._map = {}
409 self._map = {}
404 if "_dirs" in self.__dict__:
410 if "_dirs" in self.__dict__:
405 delattr(self, "_dirs")
411 delattr(self, "_dirs")
406 self._copymap = {}
412 self._copymap = {}
407 self._pl = [nullid, nullid]
413 self._pl = [nullid, nullid]
408 self._lastnormaltime = None
414 self._lastnormaltime = None
409 self._dirty = True
415 self._dirty = True
410
416
411 def rebuild(self, parent, files):
417 def rebuild(self, parent, files):
412 self.clear()
418 self.clear()
413 for f in files:
419 for f in files:
414 if 'x' in files.flags(f):
420 if 'x' in files.flags(f):
415 self._map[f] = ('n', 0777, -1, 0)
421 self._map[f] = ('n', 0777, -1, 0)
416 else:
422 else:
417 self._map[f] = ('n', 0666, -1, 0)
423 self._map[f] = ('n', 0666, -1, 0)
418 self._pl = (parent, nullid)
424 self._pl = (parent, nullid)
419 self._dirty = True
425 self._dirty = True
420
426
421 def write(self):
427 def write(self):
422 if not self._dirty:
428 if not self._dirty:
423 return
429 return
424 st = self._opener("dirstate", "w", atomictemp=True)
430 st = self._opener("dirstate", "w", atomictemp=True)
425
431
426 # use the modification time of the newly created temporary file as the
432 # use the modification time of the newly created temporary file as the
427 # filesystem's notion of 'now'
433 # filesystem's notion of 'now'
428 now = int(util.fstat(st).st_mtime)
434 now = int(util.fstat(st).st_mtime)
429
435
430 cs = cStringIO.StringIO()
436 cs = cStringIO.StringIO()
431 copymap = self._copymap
437 copymap = self._copymap
432 pack = struct.pack
438 pack = struct.pack
433 write = cs.write
439 write = cs.write
434 write("".join(self._pl))
440 write("".join(self._pl))
435 for f, e in self._map.iteritems():
441 for f, e in self._map.iteritems():
436 if e[0] == 'n' and e[3] == now:
442 if e[0] == 'n' and e[3] == now:
437 # The file was last modified "simultaneously" with the current
443 # The file was last modified "simultaneously" with the current
438 # write to dirstate (i.e. within the same second for file-
444 # write to dirstate (i.e. within the same second for file-
439 # systems with a granularity of 1 sec). This commonly happens
445 # systems with a granularity of 1 sec). This commonly happens
440 # for at least a couple of files on 'update'.
446 # for at least a couple of files on 'update'.
441 # The user could change the file without changing its size
447 # The user could change the file without changing its size
442 # within the same second. Invalidate the file's stat data in
448 # within the same second. Invalidate the file's stat data in
443 # dirstate, forcing future 'status' calls to compare the
449 # dirstate, forcing future 'status' calls to compare the
444 # contents of the file. This prevents mistakenly treating such
450 # contents of the file. This prevents mistakenly treating such
445 # files as clean.
451 # files as clean.
446 e = (e[0], 0, -1, -1) # mark entry as 'unset'
452 e = (e[0], 0, -1, -1) # mark entry as 'unset'
447 self._map[f] = e
453 self._map[f] = e
448
454
449 if f in copymap:
455 if f in copymap:
450 f = "%s\0%s" % (f, copymap[f])
456 f = "%s\0%s" % (f, copymap[f])
451 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
457 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
452 write(e)
458 write(e)
453 write(f)
459 write(f)
454 st.write(cs.getvalue())
460 st.write(cs.getvalue())
455 st.rename()
461 st.rename()
456 self._lastnormaltime = None
462 self._lastnormaltime = None
457 self._dirty = self._dirtypl = False
463 self._dirty = self._dirtypl = False
458
464
459 def _dirignore(self, f):
465 def _dirignore(self, f):
460 if f == '.':
466 if f == '.':
461 return False
467 return False
462 if self._ignore(f):
468 if self._ignore(f):
463 return True
469 return True
464 for p in _finddirs(f):
470 for p in _finddirs(f):
465 if self._ignore(p):
471 if self._ignore(p):
466 return True
472 return True
467 return False
473 return False
468
474
469 def walk(self, match, subrepos, unknown, ignored):
475 def walk(self, match, subrepos, unknown, ignored):
470 '''
476 '''
471 Walk recursively through the directory tree, finding all files
477 Walk recursively through the directory tree, finding all files
472 matched by match.
478 matched by match.
473
479
474 Return a dict mapping filename to stat-like object (either
480 Return a dict mapping filename to stat-like object (either
475 mercurial.osutil.stat instance or return value of os.stat()).
481 mercurial.osutil.stat instance or return value of os.stat()).
476 '''
482 '''
477
483
478 def fwarn(f, msg):
484 def fwarn(f, msg):
479 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
485 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
480 return False
486 return False
481
487
482 def badtype(mode):
488 def badtype(mode):
483 kind = _('unknown')
489 kind = _('unknown')
484 if stat.S_ISCHR(mode):
490 if stat.S_ISCHR(mode):
485 kind = _('character device')
491 kind = _('character device')
486 elif stat.S_ISBLK(mode):
492 elif stat.S_ISBLK(mode):
487 kind = _('block device')
493 kind = _('block device')
488 elif stat.S_ISFIFO(mode):
494 elif stat.S_ISFIFO(mode):
489 kind = _('fifo')
495 kind = _('fifo')
490 elif stat.S_ISSOCK(mode):
496 elif stat.S_ISSOCK(mode):
491 kind = _('socket')
497 kind = _('socket')
492 elif stat.S_ISDIR(mode):
498 elif stat.S_ISDIR(mode):
493 kind = _('directory')
499 kind = _('directory')
494 return _('unsupported file type (type is %s)') % kind
500 return _('unsupported file type (type is %s)') % kind
495
501
496 ignore = self._ignore
502 ignore = self._ignore
497 dirignore = self._dirignore
503 dirignore = self._dirignore
498 if ignored:
504 if ignored:
499 ignore = util.never
505 ignore = util.never
500 dirignore = util.never
506 dirignore = util.never
501 elif not unknown:
507 elif not unknown:
502 # if unknown and ignored are False, skip step 2
508 # if unknown and ignored are False, skip step 2
503 ignore = util.always
509 ignore = util.always
504 dirignore = util.always
510 dirignore = util.always
505
511
506 matchfn = match.matchfn
512 matchfn = match.matchfn
507 badfn = match.bad
513 badfn = match.bad
508 dmap = self._map
514 dmap = self._map
509 normpath = util.normpath
515 normpath = util.normpath
510 listdir = osutil.listdir
516 listdir = osutil.listdir
511 lstat = os.lstat
517 lstat = os.lstat
512 getkind = stat.S_IFMT
518 getkind = stat.S_IFMT
513 dirkind = stat.S_IFDIR
519 dirkind = stat.S_IFDIR
514 regkind = stat.S_IFREG
520 regkind = stat.S_IFREG
515 lnkkind = stat.S_IFLNK
521 lnkkind = stat.S_IFLNK
516 join = self._join
522 join = self._join
517 work = []
523 work = []
518 wadd = work.append
524 wadd = work.append
519
525
520 exact = skipstep3 = False
526 exact = skipstep3 = False
521 if matchfn == match.exact: # match.exact
527 if matchfn == match.exact: # match.exact
522 exact = True
528 exact = True
523 dirignore = util.always # skip step 2
529 dirignore = util.always # skip step 2
524 elif match.files() and not match.anypats(): # match.match, no patterns
530 elif match.files() and not match.anypats(): # match.match, no patterns
525 skipstep3 = True
531 skipstep3 = True
526
532
527 if self._checkcase:
533 if self._checkcase:
528 normalize = self._normalize
534 normalize = self._normalize
529 skipstep3 = False
535 skipstep3 = False
530 else:
536 else:
531 normalize = lambda x, y: x
537 normalize = lambda x, y: x
532
538
533 files = sorted(match.files())
539 files = sorted(match.files())
534 subrepos.sort()
540 subrepos.sort()
535 i, j = 0, 0
541 i, j = 0, 0
536 while i < len(files) and j < len(subrepos):
542 while i < len(files) and j < len(subrepos):
537 subpath = subrepos[j] + "/"
543 subpath = subrepos[j] + "/"
538 if files[i] < subpath:
544 if files[i] < subpath:
539 i += 1
545 i += 1
540 continue
546 continue
541 while i < len(files) and files[i].startswith(subpath):
547 while i < len(files) and files[i].startswith(subpath):
542 del files[i]
548 del files[i]
543 j += 1
549 j += 1
544
550
545 if not files or '.' in files:
551 if not files or '.' in files:
546 files = ['']
552 files = ['']
547 results = dict.fromkeys(subrepos)
553 results = dict.fromkeys(subrepos)
548 results['.hg'] = None
554 results['.hg'] = None
549
555
550 # step 1: find all explicit files
556 # step 1: find all explicit files
551 for ff in files:
557 for ff in files:
552 nf = normalize(normpath(ff), False)
558 nf = normalize(normpath(ff), False)
553 if nf in results:
559 if nf in results:
554 continue
560 continue
555
561
556 try:
562 try:
557 st = lstat(join(nf))
563 st = lstat(join(nf))
558 kind = getkind(st.st_mode)
564 kind = getkind(st.st_mode)
559 if kind == dirkind:
565 if kind == dirkind:
560 skipstep3 = False
566 skipstep3 = False
561 if nf in dmap:
567 if nf in dmap:
562 #file deleted on disk but still in dirstate
568 #file deleted on disk but still in dirstate
563 results[nf] = None
569 results[nf] = None
564 match.dir(nf)
570 match.dir(nf)
565 if not dirignore(nf):
571 if not dirignore(nf):
566 wadd(nf)
572 wadd(nf)
567 elif kind == regkind or kind == lnkkind:
573 elif kind == regkind or kind == lnkkind:
568 results[nf] = st
574 results[nf] = st
569 else:
575 else:
570 badfn(ff, badtype(kind))
576 badfn(ff, badtype(kind))
571 if nf in dmap:
577 if nf in dmap:
572 results[nf] = None
578 results[nf] = None
573 except OSError, inst:
579 except OSError, inst:
574 if nf in dmap: # does it exactly match a file?
580 if nf in dmap: # does it exactly match a file?
575 results[nf] = None
581 results[nf] = None
576 else: # does it match a directory?
582 else: # does it match a directory?
577 prefix = nf + "/"
583 prefix = nf + "/"
578 for fn in dmap:
584 for fn in dmap:
579 if fn.startswith(prefix):
585 if fn.startswith(prefix):
580 match.dir(nf)
586 match.dir(nf)
581 skipstep3 = False
587 skipstep3 = False
582 break
588 break
583 else:
589 else:
584 badfn(ff, inst.strerror)
590 badfn(ff, inst.strerror)
585
591
586 # step 2: visit subdirectories
592 # step 2: visit subdirectories
587 while work:
593 while work:
588 nd = work.pop()
594 nd = work.pop()
589 skip = None
595 skip = None
590 if nd == '.':
596 if nd == '.':
591 nd = ''
597 nd = ''
592 else:
598 else:
593 skip = '.hg'
599 skip = '.hg'
594 try:
600 try:
595 entries = listdir(join(nd), stat=True, skip=skip)
601 entries = listdir(join(nd), stat=True, skip=skip)
596 except OSError, inst:
602 except OSError, inst:
597 if inst.errno == errno.EACCES:
603 if inst.errno == errno.EACCES:
598 fwarn(nd, inst.strerror)
604 fwarn(nd, inst.strerror)
599 continue
605 continue
600 raise
606 raise
601 for f, kind, st in entries:
607 for f, kind, st in entries:
602 nf = normalize(nd and (nd + "/" + f) or f, True)
608 nf = normalize(nd and (nd + "/" + f) or f, True)
603 if nf not in results:
609 if nf not in results:
604 if kind == dirkind:
610 if kind == dirkind:
605 if not ignore(nf):
611 if not ignore(nf):
606 match.dir(nf)
612 match.dir(nf)
607 wadd(nf)
613 wadd(nf)
608 if nf in dmap and matchfn(nf):
614 if nf in dmap and matchfn(nf):
609 results[nf] = None
615 results[nf] = None
610 elif kind == regkind or kind == lnkkind:
616 elif kind == regkind or kind == lnkkind:
611 if nf in dmap:
617 if nf in dmap:
612 if matchfn(nf):
618 if matchfn(nf):
613 results[nf] = st
619 results[nf] = st
614 elif matchfn(nf) and not ignore(nf):
620 elif matchfn(nf) and not ignore(nf):
615 results[nf] = st
621 results[nf] = st
616 elif nf in dmap and matchfn(nf):
622 elif nf in dmap and matchfn(nf):
617 results[nf] = None
623 results[nf] = None
618
624
619 # step 3: report unseen items in the dmap hash
625 # step 3: report unseen items in the dmap hash
620 if not skipstep3 and not exact:
626 if not skipstep3 and not exact:
621 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
627 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
622 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
628 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
623 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
629 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
624 st = None
630 st = None
625 results[nf] = st
631 results[nf] = st
626 for s in subrepos:
632 for s in subrepos:
627 del results[s]
633 del results[s]
628 del results['.hg']
634 del results['.hg']
629 return results
635 return results
630
636
631 def status(self, match, subrepos, ignored, clean, unknown):
637 def status(self, match, subrepos, ignored, clean, unknown):
632 '''Determine the status of the working copy relative to the
638 '''Determine the status of the working copy relative to the
633 dirstate and return a tuple of lists (unsure, modified, added,
639 dirstate and return a tuple of lists (unsure, modified, added,
634 removed, deleted, unknown, ignored, clean), where:
640 removed, deleted, unknown, ignored, clean), where:
635
641
636 unsure:
642 unsure:
637 files that might have been modified since the dirstate was
643 files that might have been modified since the dirstate was
638 written, but need to be read to be sure (size is the same
644 written, but need to be read to be sure (size is the same
639 but mtime differs)
645 but mtime differs)
640 modified:
646 modified:
641 files that have definitely been modified since the dirstate
647 files that have definitely been modified since the dirstate
642 was written (different size or mode)
648 was written (different size or mode)
643 added:
649 added:
644 files that have been explicitly added with hg add
650 files that have been explicitly added with hg add
645 removed:
651 removed:
646 files that have been explicitly removed with hg remove
652 files that have been explicitly removed with hg remove
647 deleted:
653 deleted:
648 files that have been deleted through other means ("missing")
654 files that have been deleted through other means ("missing")
649 unknown:
655 unknown:
650 files not in the dirstate that are not ignored
656 files not in the dirstate that are not ignored
651 ignored:
657 ignored:
652 files not in the dirstate that are ignored
658 files not in the dirstate that are ignored
653 (by _dirignore())
659 (by _dirignore())
654 clean:
660 clean:
655 files that have definitely not been modified since the
661 files that have definitely not been modified since the
656 dirstate was written
662 dirstate was written
657 '''
663 '''
658 listignored, listclean, listunknown = ignored, clean, unknown
664 listignored, listclean, listunknown = ignored, clean, unknown
659 lookup, modified, added, unknown, ignored = [], [], [], [], []
665 lookup, modified, added, unknown, ignored = [], [], [], [], []
660 removed, deleted, clean = [], [], []
666 removed, deleted, clean = [], [], []
661
667
662 dmap = self._map
668 dmap = self._map
663 ladd = lookup.append # aka "unsure"
669 ladd = lookup.append # aka "unsure"
664 madd = modified.append
670 madd = modified.append
665 aadd = added.append
671 aadd = added.append
666 uadd = unknown.append
672 uadd = unknown.append
667 iadd = ignored.append
673 iadd = ignored.append
668 radd = removed.append
674 radd = removed.append
669 dadd = deleted.append
675 dadd = deleted.append
670 cadd = clean.append
676 cadd = clean.append
671
677
672 lnkkind = stat.S_IFLNK
678 lnkkind = stat.S_IFLNK
673
679
674 for fn, st in self.walk(match, subrepos, listunknown,
680 for fn, st in self.walk(match, subrepos, listunknown,
675 listignored).iteritems():
681 listignored).iteritems():
676 if fn not in dmap:
682 if fn not in dmap:
677 if (listignored or match.exact(fn)) and self._dirignore(fn):
683 if (listignored or match.exact(fn)) and self._dirignore(fn):
678 if listignored:
684 if listignored:
679 iadd(fn)
685 iadd(fn)
680 elif listunknown:
686 elif listunknown:
681 uadd(fn)
687 uadd(fn)
682 continue
688 continue
683
689
684 state, mode, size, time = dmap[fn]
690 state, mode, size, time = dmap[fn]
685
691
686 if not st and state in "nma":
692 if not st and state in "nma":
687 dadd(fn)
693 dadd(fn)
688 elif state == 'n':
694 elif state == 'n':
689 # The "mode & lnkkind != lnkkind or self._checklink"
695 # The "mode & lnkkind != lnkkind or self._checklink"
690 # lines are an expansion of "islink => checklink"
696 # lines are an expansion of "islink => checklink"
691 # where islink means "is this a link?" and checklink
697 # where islink means "is this a link?" and checklink
692 # means "can we check links?".
698 # means "can we check links?".
693 mtime = int(st.st_mtime)
699 mtime = int(st.st_mtime)
694 if (size >= 0 and
700 if (size >= 0 and
695 (size != st.st_size
701 (size != st.st_size
696 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
702 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
697 and (mode & lnkkind != lnkkind or self._checklink)
703 and (mode & lnkkind != lnkkind or self._checklink)
698 or size == -2 # other parent
704 or size == -2 # other parent
699 or fn in self._copymap):
705 or fn in self._copymap):
700 madd(fn)
706 madd(fn)
701 elif (mtime != time
707 elif (mtime != time
702 and (mode & lnkkind != lnkkind or self._checklink)):
708 and (mode & lnkkind != lnkkind or self._checklink)):
703 ladd(fn)
709 ladd(fn)
704 elif mtime == self._lastnormaltime:
710 elif mtime == self._lastnormaltime:
705 # fn may have been changed in the same timeslot without
711 # fn may have been changed in the same timeslot without
706 # changing its size. This can happen if we quickly do
712 # changing its size. This can happen if we quickly do
707 # multiple commits in a single transaction.
713 # multiple commits in a single transaction.
708 # Force lookup, so we don't miss such a racy file change.
714 # Force lookup, so we don't miss such a racy file change.
709 ladd(fn)
715 ladd(fn)
710 elif listclean:
716 elif listclean:
711 cadd(fn)
717 cadd(fn)
712 elif state == 'm':
718 elif state == 'm':
713 madd(fn)
719 madd(fn)
714 elif state == 'a':
720 elif state == 'a':
715 aadd(fn)
721 aadd(fn)
716 elif state == 'r':
722 elif state == 'r':
717 radd(fn)
723 radd(fn)
718
724
719 return (lookup, modified, added, removed, deleted, unknown, ignored,
725 return (lookup, modified, added, removed, deleted, unknown, ignored,
720 clean)
726 clean)
General Comments 0
You need to be logged in to leave comments. Login now