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