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