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