##// END OF EJS Templates
merge with stable
Matt Mackall -
r16326:b95b006e merge default
parent child Browse files
Show More
@@ -1,754 +1,755 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 import errno
7 import errno
8
8
9 from node import nullid
9 from node import nullid
10 from i18n import _
10 from i18n import _
11 import scmutil, util, ignore, osutil, parsers, encoding
11 import scmutil, util, ignore, osutil, parsers, encoding
12 import struct, os, stat, errno
12 import struct, os, stat, errno
13 import cStringIO
13 import cStringIO
14
14
15 _format = ">cllll"
15 _format = ">cllll"
16 propertycache = util.propertycache
16 propertycache = util.propertycache
17 filecache = scmutil.filecache
17 filecache = scmutil.filecache
18
18
19 class repocache(filecache):
19 class repocache(filecache):
20 """filecache for files in .hg/"""
20 """filecache for files in .hg/"""
21 def join(self, obj, fname):
21 def join(self, obj, fname):
22 return obj._opener.join(fname)
22 return obj._opener.join(fname)
23
23
24 class rootcache(filecache):
24 class rootcache(filecache):
25 """filecache for files in the repository root"""
25 """filecache for files in the repository root"""
26 def join(self, obj, fname):
26 def join(self, obj, fname):
27 return obj._join(fname)
27 return obj._join(fname)
28
28
29 def _finddirs(path):
29 def _finddirs(path):
30 pos = path.rfind('/')
30 pos = path.rfind('/')
31 while pos != -1:
31 while pos != -1:
32 yield path[:pos]
32 yield path[:pos]
33 pos = path.rfind('/', 0, pos)
33 pos = path.rfind('/', 0, pos)
34
34
35 def _incdirs(dirs, path):
35 def _incdirs(dirs, path):
36 for base in _finddirs(path):
36 for base in _finddirs(path):
37 if base in dirs:
37 if base in dirs:
38 dirs[base] += 1
38 dirs[base] += 1
39 return
39 return
40 dirs[base] = 1
40 dirs[base] = 1
41
41
42 def _decdirs(dirs, path):
42 def _decdirs(dirs, path):
43 for base in _finddirs(path):
43 for base in _finddirs(path):
44 if dirs[base] > 1:
44 if dirs[base] > 1:
45 dirs[base] -= 1
45 dirs[base] -= 1
46 return
46 return
47 del dirs[base]
47 del dirs[base]
48
48
49 class dirstate(object):
49 class dirstate(object):
50
50
51 def __init__(self, opener, ui, root, validate):
51 def __init__(self, opener, ui, root, validate):
52 '''Create a new dirstate object.
52 '''Create a new dirstate object.
53
53
54 opener is an open()-like callable that can be used to open the
54 opener is an open()-like callable that can be used to open the
55 dirstate file; root is the root of the directory tracked by
55 dirstate file; root is the root of the directory tracked by
56 the dirstate.
56 the dirstate.
57 '''
57 '''
58 self._opener = opener
58 self._opener = opener
59 self._validate = validate
59 self._validate = validate
60 self._root = root
60 self._root = root
61 self._rootdir = os.path.join(root, '')
61 self._rootdir = os.path.join(root, '')
62 self._dirty = False
62 self._dirty = False
63 self._dirtypl = False
63 self._dirtypl = False
64 self._lastnormaltime = 0
64 self._lastnormaltime = 0
65 self._ui = ui
65 self._ui = ui
66 self._filecache = {}
66 self._filecache = {}
67
67
68 @propertycache
68 @propertycache
69 def _map(self):
69 def _map(self):
70 '''Return the dirstate contents as a map from filename to
70 '''Return the dirstate contents as a map from filename to
71 (state, mode, size, time).'''
71 (state, mode, size, time).'''
72 self._read()
72 self._read()
73 return self._map
73 return self._map
74
74
75 @propertycache
75 @propertycache
76 def _copymap(self):
76 def _copymap(self):
77 self._read()
77 self._read()
78 return self._copymap
78 return self._copymap
79
79
80 @propertycache
80 @propertycache
81 def _foldmap(self):
81 def _foldmap(self):
82 f = {}
82 f = {}
83 for name in self._map:
83 for name in self._map:
84 f[util.normcase(name)] = name
84 f[util.normcase(name)] = name
85 for name in self._dirs:
85 for name in self._dirs:
86 f[util.normcase(name)] = name
86 f[util.normcase(name)] = name
87 f['.'] = '.' # prevents useless util.fspath() invocation
87 f['.'] = '.' # prevents useless util.fspath() invocation
88 return f
88 return f
89
89
90 @repocache('branch')
90 @repocache('branch')
91 def _branch(self):
91 def _branch(self):
92 try:
92 try:
93 return self._opener.read("branch").strip() or "default"
93 return self._opener.read("branch").strip() or "default"
94 except IOError, inst:
94 except IOError, inst:
95 if inst.errno != errno.ENOENT:
95 if inst.errno != errno.ENOENT:
96 raise
96 raise
97 return "default"
97 return "default"
98
98
99 @propertycache
99 @propertycache
100 def _pl(self):
100 def _pl(self):
101 try:
101 try:
102 fp = self._opener("dirstate")
102 fp = self._opener("dirstate")
103 st = fp.read(40)
103 st = fp.read(40)
104 fp.close()
104 fp.close()
105 l = len(st)
105 l = len(st)
106 if l == 40:
106 if l == 40:
107 return st[:20], st[20:40]
107 return st[:20], st[20:40]
108 elif l > 0 and l < 40:
108 elif l > 0 and l < 40:
109 raise util.Abort(_('working directory state appears damaged!'))
109 raise util.Abort(_('working directory state appears damaged!'))
110 except IOError, err:
110 except IOError, err:
111 if err.errno != errno.ENOENT:
111 if err.errno != errno.ENOENT:
112 raise
112 raise
113 return [nullid, nullid]
113 return [nullid, nullid]
114
114
115 @propertycache
115 @propertycache
116 def _dirs(self):
116 def _dirs(self):
117 dirs = {}
117 dirs = {}
118 for f, s in self._map.iteritems():
118 for f, s in self._map.iteritems():
119 if s[0] != 'r':
119 if s[0] != 'r':
120 _incdirs(dirs, f)
120 _incdirs(dirs, f)
121 return dirs
121 return dirs
122
122
123 def dirs(self):
123 def dirs(self):
124 return self._dirs
124 return self._dirs
125
125
126 @rootcache('.hgignore')
126 @rootcache('.hgignore')
127 def _ignore(self):
127 def _ignore(self):
128 files = [self._join('.hgignore')]
128 files = [self._join('.hgignore')]
129 for name, path in self._ui.configitems("ui"):
129 for name, path in self._ui.configitems("ui"):
130 if name == 'ignore' or name.startswith('ignore.'):
130 if name == 'ignore' or name.startswith('ignore.'):
131 files.append(util.expandpath(path))
131 files.append(util.expandpath(path))
132 return ignore.ignore(self._root, files, self._ui.warn)
132 return ignore.ignore(self._root, files, self._ui.warn)
133
133
134 @propertycache
134 @propertycache
135 def _slash(self):
135 def _slash(self):
136 return self._ui.configbool('ui', 'slash') and os.sep != '/'
136 return self._ui.configbool('ui', 'slash') and os.sep != '/'
137
137
138 @propertycache
138 @propertycache
139 def _checklink(self):
139 def _checklink(self):
140 return util.checklink(self._root)
140 return util.checklink(self._root)
141
141
142 @propertycache
142 @propertycache
143 def _checkexec(self):
143 def _checkexec(self):
144 return util.checkexec(self._root)
144 return util.checkexec(self._root)
145
145
146 @propertycache
146 @propertycache
147 def _checkcase(self):
147 def _checkcase(self):
148 return not util.checkcase(self._join('.hg'))
148 return not util.checkcase(self._join('.hg'))
149
149
150 def _join(self, f):
150 def _join(self, f):
151 # much faster than os.path.join()
151 # much faster than os.path.join()
152 # it's safe because f is always a relative path
152 # it's safe because f is always a relative path
153 return self._rootdir + f
153 return self._rootdir + f
154
154
155 def flagfunc(self, buildfallback):
155 def flagfunc(self, buildfallback):
156 if self._checklink and self._checkexec:
156 if self._checklink and self._checkexec:
157 def f(x):
157 def f(x):
158 p = self._join(x)
158 p = self._join(x)
159 if os.path.islink(p):
159 if os.path.islink(p):
160 return 'l'
160 return 'l'
161 if util.isexec(p):
161 if util.isexec(p):
162 return 'x'
162 return 'x'
163 return ''
163 return ''
164 return f
164 return f
165
165
166 fallback = buildfallback()
166 fallback = buildfallback()
167 if self._checklink:
167 if self._checklink:
168 def f(x):
168 def f(x):
169 if os.path.islink(self._join(x)):
169 if os.path.islink(self._join(x)):
170 return 'l'
170 return 'l'
171 if 'x' in fallback(x):
171 if 'x' in fallback(x):
172 return 'x'
172 return 'x'
173 return ''
173 return ''
174 return f
174 return f
175 if self._checkexec:
175 if self._checkexec:
176 def f(x):
176 def f(x):
177 if 'l' in fallback(x):
177 if 'l' in fallback(x):
178 return 'l'
178 return 'l'
179 if util.isexec(self._join(x)):
179 if util.isexec(self._join(x)):
180 return 'x'
180 return 'x'
181 return ''
181 return ''
182 return f
182 return f
183 else:
183 else:
184 return fallback
184 return fallback
185
185
186 def getcwd(self):
186 def getcwd(self):
187 cwd = os.getcwd()
187 cwd = os.getcwd()
188 if cwd == self._root:
188 if cwd == self._root:
189 return ''
189 return ''
190 # self._root ends with a path separator if self._root is '/' or 'C:\'
190 # self._root ends with a path separator if self._root is '/' or 'C:\'
191 rootsep = self._root
191 rootsep = self._root
192 if not util.endswithsep(rootsep):
192 if not util.endswithsep(rootsep):
193 rootsep += os.sep
193 rootsep += os.sep
194 if cwd.startswith(rootsep):
194 if cwd.startswith(rootsep):
195 return cwd[len(rootsep):]
195 return cwd[len(rootsep):]
196 else:
196 else:
197 # we're outside the repo. return an absolute path.
197 # we're outside the repo. return an absolute path.
198 return cwd
198 return cwd
199
199
200 def pathto(self, f, cwd=None):
200 def pathto(self, f, cwd=None):
201 if cwd is None:
201 if cwd is None:
202 cwd = self.getcwd()
202 cwd = self.getcwd()
203 path = util.pathto(self._root, cwd, f)
203 path = util.pathto(self._root, cwd, f)
204 if self._slash:
204 if self._slash:
205 return util.normpath(path)
205 return util.normpath(path)
206 return path
206 return path
207
207
208 def __getitem__(self, key):
208 def __getitem__(self, key):
209 '''Return the current state of key (a filename) in the dirstate.
209 '''Return the current state of key (a filename) in the dirstate.
210
210
211 States are:
211 States are:
212 n normal
212 n normal
213 m needs merging
213 m needs merging
214 r marked for removal
214 r marked for removal
215 a marked for addition
215 a marked for addition
216 ? not tracked
216 ? not tracked
217 '''
217 '''
218 return self._map.get(key, ("?",))[0]
218 return self._map.get(key, ("?",))[0]
219
219
220 def __contains__(self, key):
220 def __contains__(self, key):
221 return key in self._map
221 return key in self._map
222
222
223 def __iter__(self):
223 def __iter__(self):
224 for x in sorted(self._map):
224 for x in sorted(self._map):
225 yield x
225 yield x
226
226
227 def parents(self):
227 def parents(self):
228 return [self._validate(p) for p in self._pl]
228 return [self._validate(p) for p in self._pl]
229
229
230 def p1(self):
230 def p1(self):
231 return self._validate(self._pl[0])
231 return self._validate(self._pl[0])
232
232
233 def p2(self):
233 def p2(self):
234 return self._validate(self._pl[1])
234 return self._validate(self._pl[1])
235
235
236 def branch(self):
236 def branch(self):
237 return encoding.tolocal(self._branch)
237 return encoding.tolocal(self._branch)
238
238
239 def setparents(self, p1, p2=nullid):
239 def setparents(self, p1, p2=nullid):
240 self._dirty = self._dirtypl = True
240 self._dirty = self._dirtypl = True
241 self._pl = p1, p2
241 self._pl = p1, p2
242
242
243 def setbranch(self, branch):
243 def setbranch(self, branch):
244 if branch in ['tip', '.', 'null']:
244 if branch in ['tip', '.', 'null']:
245 raise util.Abort(_('the name \'%s\' is reserved') % branch)
245 raise util.Abort(_('the name \'%s\' is reserved') % branch)
246 self._branch = encoding.fromlocal(branch)
246 self._branch = encoding.fromlocal(branch)
247 self._opener.write("branch", self._branch + '\n')
247 self._opener.write("branch", self._branch + '\n')
248
248
249 def _read(self):
249 def _read(self):
250 self._map = {}
250 self._map = {}
251 self._copymap = {}
251 self._copymap = {}
252 try:
252 try:
253 st = self._opener.read("dirstate")
253 st = self._opener.read("dirstate")
254 except IOError, err:
254 except IOError, err:
255 if err.errno != errno.ENOENT:
255 if err.errno != errno.ENOENT:
256 raise
256 raise
257 return
257 return
258 if not st:
258 if not st:
259 return
259 return
260
260
261 p = parsers.parse_dirstate(self._map, self._copymap, st)
261 p = parsers.parse_dirstate(self._map, self._copymap, st)
262 if not self._dirtypl:
262 if not self._dirtypl:
263 self._pl = p
263 self._pl = p
264
264
265 def invalidate(self):
265 def invalidate(self):
266 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
266 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
267 "_ignore"):
267 "_ignore"):
268 if a in self.__dict__:
268 if a in self.__dict__:
269 delattr(self, a)
269 delattr(self, a)
270 self._lastnormaltime = 0
270 self._lastnormaltime = 0
271 self._dirty = False
271 self._dirty = False
272
272
273 def copy(self, source, dest):
273 def copy(self, source, dest):
274 """Mark dest as a copy of source. Unmark dest if source is None."""
274 """Mark dest as a copy of source. Unmark dest if source is None."""
275 if source == dest:
275 if source == dest:
276 return
276 return
277 self._dirty = True
277 self._dirty = True
278 if source is not None:
278 if source is not None:
279 self._copymap[dest] = source
279 self._copymap[dest] = source
280 elif dest in self._copymap:
280 elif dest in self._copymap:
281 del self._copymap[dest]
281 del self._copymap[dest]
282
282
283 def copied(self, file):
283 def copied(self, file):
284 return self._copymap.get(file, None)
284 return self._copymap.get(file, None)
285
285
286 def copies(self):
286 def copies(self):
287 return self._copymap
287 return self._copymap
288
288
289 def _droppath(self, f):
289 def _droppath(self, f):
290 if self[f] not in "?r" and "_dirs" in self.__dict__:
290 if self[f] not in "?r" and "_dirs" in self.__dict__:
291 _decdirs(self._dirs, f)
291 _decdirs(self._dirs, f)
292
292
293 def _addpath(self, f, check=False):
293 def _addpath(self, f, check=False):
294 oldstate = self[f]
294 oldstate = self[f]
295 if check or oldstate == "r":
295 if check or oldstate == "r":
296 scmutil.checkfilename(f)
296 scmutil.checkfilename(f)
297 if f in self._dirs:
297 if f in self._dirs:
298 raise util.Abort(_('directory %r already in dirstate') % f)
298 raise util.Abort(_('directory %r already in dirstate') % f)
299 # shadows
299 # shadows
300 for d in _finddirs(f):
300 for d in _finddirs(f):
301 if d in self._dirs:
301 if d in self._dirs:
302 break
302 break
303 if d in self._map and self[d] != 'r':
303 if d in self._map and self[d] != 'r':
304 raise util.Abort(
304 raise util.Abort(
305 _('file %r in dirstate clashes with %r') % (d, f))
305 _('file %r in dirstate clashes with %r') % (d, f))
306 if oldstate in "?r" and "_dirs" in self.__dict__:
306 if oldstate in "?r" and "_dirs" in self.__dict__:
307 _incdirs(self._dirs, f)
307 _incdirs(self._dirs, f)
308
308
309 def normal(self, f):
309 def normal(self, f):
310 '''Mark a file normal and clean.'''
310 '''Mark a file normal and clean.'''
311 self._dirty = True
311 self._dirty = True
312 self._addpath(f)
312 self._addpath(f)
313 s = os.lstat(self._join(f))
313 s = os.lstat(self._join(f))
314 mtime = int(s.st_mtime)
314 mtime = int(s.st_mtime)
315 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
315 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
316 if f in self._copymap:
316 if f in self._copymap:
317 del self._copymap[f]
317 del self._copymap[f]
318 if mtime > self._lastnormaltime:
318 if mtime > self._lastnormaltime:
319 # Remember the most recent modification timeslot for status(),
319 # Remember the most recent modification timeslot for status(),
320 # to make sure we won't miss future size-preserving file content
320 # to make sure we won't miss future size-preserving file content
321 # modifications that happen within the same timeslot.
321 # modifications that happen within the same timeslot.
322 self._lastnormaltime = mtime
322 self._lastnormaltime = mtime
323
323
324 def normallookup(self, f):
324 def normallookup(self, f):
325 '''Mark a file normal, but possibly dirty.'''
325 '''Mark a file normal, but possibly dirty.'''
326 if self._pl[1] != nullid and f in self._map:
326 if self._pl[1] != nullid and f in self._map:
327 # if there is a merge going on and the file was either
327 # if there is a merge going on and the file was either
328 # in state 'm' (-1) or coming from other parent (-2) before
328 # in state 'm' (-1) or coming from other parent (-2) before
329 # being removed, restore that state.
329 # being removed, restore that state.
330 entry = self._map[f]
330 entry = self._map[f]
331 if entry[0] == 'r' and entry[2] in (-1, -2):
331 if entry[0] == 'r' and entry[2] in (-1, -2):
332 source = self._copymap.get(f)
332 source = self._copymap.get(f)
333 if entry[2] == -1:
333 if entry[2] == -1:
334 self.merge(f)
334 self.merge(f)
335 elif entry[2] == -2:
335 elif entry[2] == -2:
336 self.otherparent(f)
336 self.otherparent(f)
337 if source:
337 if source:
338 self.copy(source, f)
338 self.copy(source, f)
339 return
339 return
340 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
340 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
341 return
341 return
342 self._dirty = True
342 self._dirty = True
343 self._addpath(f)
343 self._addpath(f)
344 self._map[f] = ('n', 0, -1, -1)
344 self._map[f] = ('n', 0, -1, -1)
345 if f in self._copymap:
345 if f in self._copymap:
346 del self._copymap[f]
346 del self._copymap[f]
347
347
348 def otherparent(self, f):
348 def otherparent(self, f):
349 '''Mark as coming from the other parent, always dirty.'''
349 '''Mark as coming from the other parent, always dirty.'''
350 if self._pl[1] == nullid:
350 if self._pl[1] == nullid:
351 raise util.Abort(_("setting %r to other parent "
351 raise util.Abort(_("setting %r to other parent "
352 "only allowed in merges") % f)
352 "only allowed in merges") % f)
353 self._dirty = True
353 self._dirty = True
354 self._addpath(f)
354 self._addpath(f)
355 self._map[f] = ('n', 0, -2, -1)
355 self._map[f] = ('n', 0, -2, -1)
356 if f in self._copymap:
356 if f in self._copymap:
357 del self._copymap[f]
357 del self._copymap[f]
358
358
359 def add(self, f):
359 def add(self, f):
360 '''Mark a file added.'''
360 '''Mark a file added.'''
361 self._dirty = True
361 self._dirty = True
362 self._addpath(f, True)
362 self._addpath(f, True)
363 self._map[f] = ('a', 0, -1, -1)
363 self._map[f] = ('a', 0, -1, -1)
364 if f in self._copymap:
364 if f in self._copymap:
365 del self._copymap[f]
365 del self._copymap[f]
366
366
367 def remove(self, f):
367 def remove(self, f):
368 '''Mark a file removed.'''
368 '''Mark a file removed.'''
369 self._dirty = True
369 self._dirty = True
370 self._droppath(f)
370 self._droppath(f)
371 size = 0
371 size = 0
372 if self._pl[1] != nullid and f in self._map:
372 if self._pl[1] != nullid and f in self._map:
373 # backup the previous state
373 # backup the previous state
374 entry = self._map[f]
374 entry = self._map[f]
375 if entry[0] == 'm': # merge
375 if entry[0] == 'm': # merge
376 size = -1
376 size = -1
377 elif entry[0] == 'n' and entry[2] == -2: # other parent
377 elif entry[0] == 'n' and entry[2] == -2: # other parent
378 size = -2
378 size = -2
379 self._map[f] = ('r', 0, size, 0)
379 self._map[f] = ('r', 0, size, 0)
380 if size == 0 and f in self._copymap:
380 if size == 0 and f in self._copymap:
381 del self._copymap[f]
381 del self._copymap[f]
382
382
383 def merge(self, f):
383 def merge(self, f):
384 '''Mark a file merged.'''
384 '''Mark a file merged.'''
385 self._dirty = True
385 self._dirty = True
386 s = os.lstat(self._join(f))
386 s = os.lstat(self._join(f))
387 self._addpath(f)
387 self._addpath(f)
388 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
388 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
389 if f in self._copymap:
389 if f in self._copymap:
390 del self._copymap[f]
390 del self._copymap[f]
391
391
392 def drop(self, f):
392 def drop(self, f):
393 '''Drop a file from the dirstate'''
393 '''Drop a file from the dirstate'''
394 if f in self._map:
394 if f in self._map:
395 self._dirty = True
395 self._dirty = True
396 self._droppath(f)
396 self._droppath(f)
397 del self._map[f]
397 del self._map[f]
398
398
399 def _normalize(self, path, isknown):
399 def _normalize(self, path, isknown):
400 normed = util.normcase(path)
400 normed = util.normcase(path)
401 folded = self._foldmap.get(normed, None)
401 folded = self._foldmap.get(normed, None)
402 if folded is None:
402 if folded is None:
403 if isknown or not os.path.lexists(os.path.join(self._root, path)):
403 if isknown or not os.path.lexists(os.path.join(self._root, path)):
404 folded = path
404 folded = path
405 else:
405 else:
406 # recursively normalize leading directory components
406 # recursively normalize leading directory components
407 # against dirstate
407 # against dirstate
408 if '/' in normed:
408 if '/' in normed:
409 d, f = normed.rsplit('/')
409 d, f = normed.rsplit('/', 1)
410 d = self._root + "/" + self._normalize(d, isknown)
410 d = self._normalize(d, isknown)
411 folded = d + "/" + util.fspath(f, d)
411 r = self._root + "/" + d
412 folded = d + "/" + util.fspath(f, r)
412 else:
413 else:
413 folded = util.fspath(normed, self._root)
414 folded = util.fspath(normed, self._root)
414 self._foldmap[normed] = folded
415 self._foldmap[normed] = folded
415
416
416 return folded
417 return folded
417
418
418 def normalize(self, path, isknown=False):
419 def normalize(self, path, isknown=False):
419 '''
420 '''
420 normalize the case of a pathname when on a casefolding filesystem
421 normalize the case of a pathname when on a casefolding filesystem
421
422
422 isknown specifies whether the filename came from walking the
423 isknown specifies whether the filename came from walking the
423 disk, to avoid extra filesystem access
424 disk, to avoid extra filesystem access
424
425
425 The normalized case is determined based on the following precedence:
426 The normalized case is determined based on the following precedence:
426
427
427 - version of name already stored in the dirstate
428 - version of name already stored in the dirstate
428 - version of name stored on disk
429 - version of name stored on disk
429 - version provided via command arguments
430 - version provided via command arguments
430 '''
431 '''
431
432
432 if self._checkcase:
433 if self._checkcase:
433 return self._normalize(path, isknown)
434 return self._normalize(path, isknown)
434 return path
435 return path
435
436
436 def clear(self):
437 def clear(self):
437 self._map = {}
438 self._map = {}
438 if "_dirs" in self.__dict__:
439 if "_dirs" in self.__dict__:
439 delattr(self, "_dirs")
440 delattr(self, "_dirs")
440 self._copymap = {}
441 self._copymap = {}
441 self._pl = [nullid, nullid]
442 self._pl = [nullid, nullid]
442 self._lastnormaltime = 0
443 self._lastnormaltime = 0
443 self._dirty = True
444 self._dirty = True
444
445
445 def rebuild(self, parent, files):
446 def rebuild(self, parent, files):
446 self.clear()
447 self.clear()
447 for f in files:
448 for f in files:
448 if 'x' in files.flags(f):
449 if 'x' in files.flags(f):
449 self._map[f] = ('n', 0777, -1, 0)
450 self._map[f] = ('n', 0777, -1, 0)
450 else:
451 else:
451 self._map[f] = ('n', 0666, -1, 0)
452 self._map[f] = ('n', 0666, -1, 0)
452 self._pl = (parent, nullid)
453 self._pl = (parent, nullid)
453 self._dirty = True
454 self._dirty = True
454
455
455 def write(self):
456 def write(self):
456 if not self._dirty:
457 if not self._dirty:
457 return
458 return
458 st = self._opener("dirstate", "w", atomictemp=True)
459 st = self._opener("dirstate", "w", atomictemp=True)
459
460
460 # use the modification time of the newly created temporary file as the
461 # use the modification time of the newly created temporary file as the
461 # filesystem's notion of 'now'
462 # filesystem's notion of 'now'
462 now = int(util.fstat(st).st_mtime)
463 now = int(util.fstat(st).st_mtime)
463
464
464 cs = cStringIO.StringIO()
465 cs = cStringIO.StringIO()
465 copymap = self._copymap
466 copymap = self._copymap
466 pack = struct.pack
467 pack = struct.pack
467 write = cs.write
468 write = cs.write
468 write("".join(self._pl))
469 write("".join(self._pl))
469 for f, e in self._map.iteritems():
470 for f, e in self._map.iteritems():
470 if e[0] == 'n' and e[3] == now:
471 if e[0] == 'n' and e[3] == now:
471 # The file was last modified "simultaneously" with the current
472 # The file was last modified "simultaneously" with the current
472 # write to dirstate (i.e. within the same second for file-
473 # write to dirstate (i.e. within the same second for file-
473 # systems with a granularity of 1 sec). This commonly happens
474 # systems with a granularity of 1 sec). This commonly happens
474 # for at least a couple of files on 'update'.
475 # for at least a couple of files on 'update'.
475 # The user could change the file without changing its size
476 # The user could change the file without changing its size
476 # within the same second. Invalidate the file's stat data in
477 # within the same second. Invalidate the file's stat data in
477 # dirstate, forcing future 'status' calls to compare the
478 # dirstate, forcing future 'status' calls to compare the
478 # contents of the file. This prevents mistakenly treating such
479 # contents of the file. This prevents mistakenly treating such
479 # files as clean.
480 # files as clean.
480 e = (e[0], 0, -1, -1) # mark entry as 'unset'
481 e = (e[0], 0, -1, -1) # mark entry as 'unset'
481 self._map[f] = e
482 self._map[f] = e
482
483
483 if f in copymap:
484 if f in copymap:
484 f = "%s\0%s" % (f, copymap[f])
485 f = "%s\0%s" % (f, copymap[f])
485 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
486 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
486 write(e)
487 write(e)
487 write(f)
488 write(f)
488 st.write(cs.getvalue())
489 st.write(cs.getvalue())
489 st.close()
490 st.close()
490 self._lastnormaltime = 0
491 self._lastnormaltime = 0
491 self._dirty = self._dirtypl = False
492 self._dirty = self._dirtypl = False
492
493
493 def _dirignore(self, f):
494 def _dirignore(self, f):
494 if f == '.':
495 if f == '.':
495 return False
496 return False
496 if self._ignore(f):
497 if self._ignore(f):
497 return True
498 return True
498 for p in _finddirs(f):
499 for p in _finddirs(f):
499 if self._ignore(p):
500 if self._ignore(p):
500 return True
501 return True
501 return False
502 return False
502
503
503 def walk(self, match, subrepos, unknown, ignored):
504 def walk(self, match, subrepos, unknown, ignored):
504 '''
505 '''
505 Walk recursively through the directory tree, finding all files
506 Walk recursively through the directory tree, finding all files
506 matched by match.
507 matched by match.
507
508
508 Return a dict mapping filename to stat-like object (either
509 Return a dict mapping filename to stat-like object (either
509 mercurial.osutil.stat instance or return value of os.stat()).
510 mercurial.osutil.stat instance or return value of os.stat()).
510 '''
511 '''
511
512
512 def fwarn(f, msg):
513 def fwarn(f, msg):
513 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
514 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
514 return False
515 return False
515
516
516 def badtype(mode):
517 def badtype(mode):
517 kind = _('unknown')
518 kind = _('unknown')
518 if stat.S_ISCHR(mode):
519 if stat.S_ISCHR(mode):
519 kind = _('character device')
520 kind = _('character device')
520 elif stat.S_ISBLK(mode):
521 elif stat.S_ISBLK(mode):
521 kind = _('block device')
522 kind = _('block device')
522 elif stat.S_ISFIFO(mode):
523 elif stat.S_ISFIFO(mode):
523 kind = _('fifo')
524 kind = _('fifo')
524 elif stat.S_ISSOCK(mode):
525 elif stat.S_ISSOCK(mode):
525 kind = _('socket')
526 kind = _('socket')
526 elif stat.S_ISDIR(mode):
527 elif stat.S_ISDIR(mode):
527 kind = _('directory')
528 kind = _('directory')
528 return _('unsupported file type (type is %s)') % kind
529 return _('unsupported file type (type is %s)') % kind
529
530
530 ignore = self._ignore
531 ignore = self._ignore
531 dirignore = self._dirignore
532 dirignore = self._dirignore
532 if ignored:
533 if ignored:
533 ignore = util.never
534 ignore = util.never
534 dirignore = util.never
535 dirignore = util.never
535 elif not unknown:
536 elif not unknown:
536 # if unknown and ignored are False, skip step 2
537 # if unknown and ignored are False, skip step 2
537 ignore = util.always
538 ignore = util.always
538 dirignore = util.always
539 dirignore = util.always
539
540
540 matchfn = match.matchfn
541 matchfn = match.matchfn
541 badfn = match.bad
542 badfn = match.bad
542 dmap = self._map
543 dmap = self._map
543 normpath = util.normpath
544 normpath = util.normpath
544 listdir = osutil.listdir
545 listdir = osutil.listdir
545 lstat = os.lstat
546 lstat = os.lstat
546 getkind = stat.S_IFMT
547 getkind = stat.S_IFMT
547 dirkind = stat.S_IFDIR
548 dirkind = stat.S_IFDIR
548 regkind = stat.S_IFREG
549 regkind = stat.S_IFREG
549 lnkkind = stat.S_IFLNK
550 lnkkind = stat.S_IFLNK
550 join = self._join
551 join = self._join
551 work = []
552 work = []
552 wadd = work.append
553 wadd = work.append
553
554
554 exact = skipstep3 = False
555 exact = skipstep3 = False
555 if matchfn == match.exact: # match.exact
556 if matchfn == match.exact: # match.exact
556 exact = True
557 exact = True
557 dirignore = util.always # skip step 2
558 dirignore = util.always # skip step 2
558 elif match.files() and not match.anypats(): # match.match, no patterns
559 elif match.files() and not match.anypats(): # match.match, no patterns
559 skipstep3 = True
560 skipstep3 = True
560
561
561 if not exact and self._checkcase:
562 if not exact and self._checkcase:
562 normalize = self._normalize
563 normalize = self._normalize
563 skipstep3 = False
564 skipstep3 = False
564 else:
565 else:
565 normalize = lambda x, y: x
566 normalize = lambda x, y: x
566
567
567 files = sorted(match.files())
568 files = sorted(match.files())
568 subrepos.sort()
569 subrepos.sort()
569 i, j = 0, 0
570 i, j = 0, 0
570 while i < len(files) and j < len(subrepos):
571 while i < len(files) and j < len(subrepos):
571 subpath = subrepos[j] + "/"
572 subpath = subrepos[j] + "/"
572 if files[i] < subpath:
573 if files[i] < subpath:
573 i += 1
574 i += 1
574 continue
575 continue
575 while i < len(files) and files[i].startswith(subpath):
576 while i < len(files) and files[i].startswith(subpath):
576 del files[i]
577 del files[i]
577 j += 1
578 j += 1
578
579
579 if not files or '.' in files:
580 if not files or '.' in files:
580 files = ['']
581 files = ['']
581 results = dict.fromkeys(subrepos)
582 results = dict.fromkeys(subrepos)
582 results['.hg'] = None
583 results['.hg'] = None
583
584
584 # step 1: find all explicit files
585 # step 1: find all explicit files
585 for ff in files:
586 for ff in files:
586 nf = normalize(normpath(ff), False)
587 nf = normalize(normpath(ff), False)
587 if nf in results:
588 if nf in results:
588 continue
589 continue
589
590
590 try:
591 try:
591 st = lstat(join(nf))
592 st = lstat(join(nf))
592 kind = getkind(st.st_mode)
593 kind = getkind(st.st_mode)
593 if kind == dirkind:
594 if kind == dirkind:
594 skipstep3 = False
595 skipstep3 = False
595 if nf in dmap:
596 if nf in dmap:
596 #file deleted on disk but still in dirstate
597 #file deleted on disk but still in dirstate
597 results[nf] = None
598 results[nf] = None
598 match.dir(nf)
599 match.dir(nf)
599 if not dirignore(nf):
600 if not dirignore(nf):
600 wadd(nf)
601 wadd(nf)
601 elif kind == regkind or kind == lnkkind:
602 elif kind == regkind or kind == lnkkind:
602 results[nf] = st
603 results[nf] = st
603 else:
604 else:
604 badfn(ff, badtype(kind))
605 badfn(ff, badtype(kind))
605 if nf in dmap:
606 if nf in dmap:
606 results[nf] = None
607 results[nf] = None
607 except OSError, inst:
608 except OSError, inst:
608 if nf in dmap: # does it exactly match a file?
609 if nf in dmap: # does it exactly match a file?
609 results[nf] = None
610 results[nf] = None
610 else: # does it match a directory?
611 else: # does it match a directory?
611 prefix = nf + "/"
612 prefix = nf + "/"
612 for fn in dmap:
613 for fn in dmap:
613 if fn.startswith(prefix):
614 if fn.startswith(prefix):
614 match.dir(nf)
615 match.dir(nf)
615 skipstep3 = False
616 skipstep3 = False
616 break
617 break
617 else:
618 else:
618 badfn(ff, inst.strerror)
619 badfn(ff, inst.strerror)
619
620
620 # step 2: visit subdirectories
621 # step 2: visit subdirectories
621 while work:
622 while work:
622 nd = work.pop()
623 nd = work.pop()
623 skip = None
624 skip = None
624 if nd == '.':
625 if nd == '.':
625 nd = ''
626 nd = ''
626 else:
627 else:
627 skip = '.hg'
628 skip = '.hg'
628 try:
629 try:
629 entries = listdir(join(nd), stat=True, skip=skip)
630 entries = listdir(join(nd), stat=True, skip=skip)
630 except OSError, inst:
631 except OSError, inst:
631 if inst.errno == errno.EACCES:
632 if inst.errno == errno.EACCES:
632 fwarn(nd, inst.strerror)
633 fwarn(nd, inst.strerror)
633 continue
634 continue
634 raise
635 raise
635 for f, kind, st in entries:
636 for f, kind, st in entries:
636 nf = normalize(nd and (nd + "/" + f) or f, True)
637 nf = normalize(nd and (nd + "/" + f) or f, True)
637 if nf not in results:
638 if nf not in results:
638 if kind == dirkind:
639 if kind == dirkind:
639 if not ignore(nf):
640 if not ignore(nf):
640 match.dir(nf)
641 match.dir(nf)
641 wadd(nf)
642 wadd(nf)
642 if nf in dmap and matchfn(nf):
643 if nf in dmap and matchfn(nf):
643 results[nf] = None
644 results[nf] = None
644 elif kind == regkind or kind == lnkkind:
645 elif kind == regkind or kind == lnkkind:
645 if nf in dmap:
646 if nf in dmap:
646 if matchfn(nf):
647 if matchfn(nf):
647 results[nf] = st
648 results[nf] = st
648 elif matchfn(nf) and not ignore(nf):
649 elif matchfn(nf) and not ignore(nf):
649 results[nf] = st
650 results[nf] = st
650 elif nf in dmap and matchfn(nf):
651 elif nf in dmap and matchfn(nf):
651 results[nf] = None
652 results[nf] = None
652
653
653 # step 3: report unseen items in the dmap hash
654 # step 3: report unseen items in the dmap hash
654 if not skipstep3 and not exact:
655 if not skipstep3 and not exact:
655 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
656 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
656 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
657 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
657 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
658 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
658 st = None
659 st = None
659 results[nf] = st
660 results[nf] = st
660 for s in subrepos:
661 for s in subrepos:
661 del results[s]
662 del results[s]
662 del results['.hg']
663 del results['.hg']
663 return results
664 return results
664
665
665 def status(self, match, subrepos, ignored, clean, unknown):
666 def status(self, match, subrepos, ignored, clean, unknown):
666 '''Determine the status of the working copy relative to the
667 '''Determine the status of the working copy relative to the
667 dirstate and return a tuple of lists (unsure, modified, added,
668 dirstate and return a tuple of lists (unsure, modified, added,
668 removed, deleted, unknown, ignored, clean), where:
669 removed, deleted, unknown, ignored, clean), where:
669
670
670 unsure:
671 unsure:
671 files that might have been modified since the dirstate was
672 files that might have been modified since the dirstate was
672 written, but need to be read to be sure (size is the same
673 written, but need to be read to be sure (size is the same
673 but mtime differs)
674 but mtime differs)
674 modified:
675 modified:
675 files that have definitely been modified since the dirstate
676 files that have definitely been modified since the dirstate
676 was written (different size or mode)
677 was written (different size or mode)
677 added:
678 added:
678 files that have been explicitly added with hg add
679 files that have been explicitly added with hg add
679 removed:
680 removed:
680 files that have been explicitly removed with hg remove
681 files that have been explicitly removed with hg remove
681 deleted:
682 deleted:
682 files that have been deleted through other means ("missing")
683 files that have been deleted through other means ("missing")
683 unknown:
684 unknown:
684 files not in the dirstate that are not ignored
685 files not in the dirstate that are not ignored
685 ignored:
686 ignored:
686 files not in the dirstate that are ignored
687 files not in the dirstate that are ignored
687 (by _dirignore())
688 (by _dirignore())
688 clean:
689 clean:
689 files that have definitely not been modified since the
690 files that have definitely not been modified since the
690 dirstate was written
691 dirstate was written
691 '''
692 '''
692 listignored, listclean, listunknown = ignored, clean, unknown
693 listignored, listclean, listunknown = ignored, clean, unknown
693 lookup, modified, added, unknown, ignored = [], [], [], [], []
694 lookup, modified, added, unknown, ignored = [], [], [], [], []
694 removed, deleted, clean = [], [], []
695 removed, deleted, clean = [], [], []
695
696
696 dmap = self._map
697 dmap = self._map
697 ladd = lookup.append # aka "unsure"
698 ladd = lookup.append # aka "unsure"
698 madd = modified.append
699 madd = modified.append
699 aadd = added.append
700 aadd = added.append
700 uadd = unknown.append
701 uadd = unknown.append
701 iadd = ignored.append
702 iadd = ignored.append
702 radd = removed.append
703 radd = removed.append
703 dadd = deleted.append
704 dadd = deleted.append
704 cadd = clean.append
705 cadd = clean.append
705
706
706 lnkkind = stat.S_IFLNK
707 lnkkind = stat.S_IFLNK
707
708
708 for fn, st in self.walk(match, subrepos, listunknown,
709 for fn, st in self.walk(match, subrepos, listunknown,
709 listignored).iteritems():
710 listignored).iteritems():
710 if fn not in dmap:
711 if fn not in dmap:
711 if (listignored or match.exact(fn)) and self._dirignore(fn):
712 if (listignored or match.exact(fn)) and self._dirignore(fn):
712 if listignored:
713 if listignored:
713 iadd(fn)
714 iadd(fn)
714 elif listunknown:
715 elif listunknown:
715 uadd(fn)
716 uadd(fn)
716 continue
717 continue
717
718
718 state, mode, size, time = dmap[fn]
719 state, mode, size, time = dmap[fn]
719
720
720 if not st and state in "nma":
721 if not st and state in "nma":
721 dadd(fn)
722 dadd(fn)
722 elif state == 'n':
723 elif state == 'n':
723 # The "mode & lnkkind != lnkkind or self._checklink"
724 # The "mode & lnkkind != lnkkind or self._checklink"
724 # lines are an expansion of "islink => checklink"
725 # lines are an expansion of "islink => checklink"
725 # where islink means "is this a link?" and checklink
726 # where islink means "is this a link?" and checklink
726 # means "can we check links?".
727 # means "can we check links?".
727 mtime = int(st.st_mtime)
728 mtime = int(st.st_mtime)
728 if (size >= 0 and
729 if (size >= 0 and
729 (size != st.st_size
730 (size != st.st_size
730 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
731 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
731 and (mode & lnkkind != lnkkind or self._checklink)
732 and (mode & lnkkind != lnkkind or self._checklink)
732 or size == -2 # other parent
733 or size == -2 # other parent
733 or fn in self._copymap):
734 or fn in self._copymap):
734 madd(fn)
735 madd(fn)
735 elif (mtime != time
736 elif (mtime != time
736 and (mode & lnkkind != lnkkind or self._checklink)):
737 and (mode & lnkkind != lnkkind or self._checklink)):
737 ladd(fn)
738 ladd(fn)
738 elif mtime == self._lastnormaltime:
739 elif mtime == self._lastnormaltime:
739 # fn may have been changed in the same timeslot without
740 # fn may have been changed in the same timeslot without
740 # changing its size. This can happen if we quickly do
741 # changing its size. This can happen if we quickly do
741 # multiple commits in a single transaction.
742 # multiple commits in a single transaction.
742 # Force lookup, so we don't miss such a racy file change.
743 # Force lookup, so we don't miss such a racy file change.
743 ladd(fn)
744 ladd(fn)
744 elif listclean:
745 elif listclean:
745 cadd(fn)
746 cadd(fn)
746 elif state == 'm':
747 elif state == 'm':
747 madd(fn)
748 madd(fn)
748 elif state == 'a':
749 elif state == 'a':
749 aadd(fn)
750 aadd(fn)
750 elif state == 'r':
751 elif state == 'r':
751 radd(fn)
752 radd(fn)
752
753
753 return (lookup, modified, added, removed, deleted, unknown, ignored,
754 return (lookup, modified, added, removed, deleted, unknown, ignored,
754 clean)
755 clean)
@@ -1,329 +1,345 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Test the running system for features availability. Exit with zero
2 """Test the running system for features availability. Exit with zero
3 if all features are there, non-zero otherwise. If a feature name is
3 if all features are there, non-zero otherwise. If a feature name is
4 prefixed with "no-", the absence of feature is tested.
4 prefixed with "no-", the absence of feature is tested.
5 """
5 """
6 import optparse
6 import optparse
7 import os
7 import os, stat
8 import re
8 import re
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11
11
12 tempprefix = 'hg-hghave-'
12 tempprefix = 'hg-hghave-'
13
13
14 def matchoutput(cmd, regexp, ignorestatus=False):
14 def matchoutput(cmd, regexp, ignorestatus=False):
15 """Return True if cmd executes successfully and its output
15 """Return True if cmd executes successfully and its output
16 is matched by the supplied regular expression.
16 is matched by the supplied regular expression.
17 """
17 """
18 r = re.compile(regexp)
18 r = re.compile(regexp)
19 fh = os.popen(cmd)
19 fh = os.popen(cmd)
20 s = fh.read()
20 s = fh.read()
21 try:
21 try:
22 ret = fh.close()
22 ret = fh.close()
23 except IOError:
23 except IOError:
24 # Happen in Windows test environment
24 # Happen in Windows test environment
25 ret = 1
25 ret = 1
26 return (ignorestatus or ret is None) and r.search(s)
26 return (ignorestatus or ret is None) and r.search(s)
27
27
28 def has_baz():
28 def has_baz():
29 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
29 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
30
30
31 def has_bzr():
31 def has_bzr():
32 try:
32 try:
33 import bzrlib
33 import bzrlib
34 return bzrlib.__doc__ != None
34 return bzrlib.__doc__ != None
35 except ImportError:
35 except ImportError:
36 return False
36 return False
37
37
38 def has_bzr114():
38 def has_bzr114():
39 try:
39 try:
40 import bzrlib
40 import bzrlib
41 return (bzrlib.__doc__ != None
41 return (bzrlib.__doc__ != None
42 and bzrlib.version_info[:2] >= (1, 14))
42 and bzrlib.version_info[:2] >= (1, 14))
43 except ImportError:
43 except ImportError:
44 return False
44 return False
45
45
46 def has_cvs():
46 def has_cvs():
47 re = r'Concurrent Versions System.*?server'
47 re = r'Concurrent Versions System.*?server'
48 return matchoutput('cvs --version 2>&1', re) and not has_msys()
48 return matchoutput('cvs --version 2>&1', re) and not has_msys()
49
49
50 def has_darcs():
50 def has_darcs():
51 return matchoutput('darcs --version', r'2\.[2-9]', True)
51 return matchoutput('darcs --version', r'2\.[2-9]', True)
52
52
53 def has_mtn():
53 def has_mtn():
54 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
54 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
55 'mtn --version', r'monotone 0\.', True)
55 'mtn --version', r'monotone 0\.', True)
56
56
57 def has_eol_in_paths():
57 def has_eol_in_paths():
58 try:
58 try:
59 fd, path = tempfile.mkstemp(prefix=tempprefix, suffix='\n\r')
59 fd, path = tempfile.mkstemp(prefix=tempprefix, suffix='\n\r')
60 os.close(fd)
60 os.close(fd)
61 os.remove(path)
61 os.remove(path)
62 return True
62 return True
63 except:
63 except:
64 return False
64 return False
65
65
66 def has_executablebit():
66 def has_executablebit():
67 fd, path = tempfile.mkstemp(prefix=tempprefix)
68 os.close(fd)
69 try:
67 try:
70 s = os.lstat(path).st_mode
68 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
71 os.chmod(path, s | 0100)
69 fh, fn = tempfile.mkstemp(dir=".", prefix='hg-checkexec-')
72 return (os.lstat(path).st_mode & 0100 != 0)
70 try:
73 finally:
71 os.close(fh)
74 os.remove(path)
72 m = os.stat(fn).st_mode & 0777
73 new_file_has_exec = m & EXECFLAGS
74 os.chmod(fn, m ^ EXECFLAGS)
75 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
76 finally:
77 os.unlink(fn)
78 except (IOError, OSError):
79 # we don't care, the user probably won't be able to commit anyway
80 return False
81 return not (new_file_has_exec or exec_flags_cannot_flip)
75
82
76 def has_icasefs():
83 def has_icasefs():
77 # Stolen from mercurial.util
84 # Stolen from mercurial.util
78 fd, path = tempfile.mkstemp(prefix=tempprefix, dir='.')
85 fd, path = tempfile.mkstemp(prefix=tempprefix, dir='.')
79 os.close(fd)
86 os.close(fd)
80 try:
87 try:
81 s1 = os.stat(path)
88 s1 = os.stat(path)
82 d, b = os.path.split(path)
89 d, b = os.path.split(path)
83 p2 = os.path.join(d, b.upper())
90 p2 = os.path.join(d, b.upper())
84 if path == p2:
91 if path == p2:
85 p2 = os.path.join(d, b.lower())
92 p2 = os.path.join(d, b.lower())
86 try:
93 try:
87 s2 = os.stat(p2)
94 s2 = os.stat(p2)
88 return s2 == s1
95 return s2 == s1
89 except:
96 except:
90 return False
97 return False
91 finally:
98 finally:
92 os.remove(path)
99 os.remove(path)
93
100
94 def has_inotify():
101 def has_inotify():
95 try:
102 try:
96 import hgext.inotify.linux.watcher
103 import hgext.inotify.linux.watcher
97 return True
104 return True
98 except ImportError:
105 except ImportError:
99 return False
106 return False
100
107
101 def has_fifo():
108 def has_fifo():
102 return hasattr(os, "mkfifo")
109 return hasattr(os, "mkfifo")
103
110
104 def has_cacheable_fs():
111 def has_cacheable_fs():
105 from mercurial import util
112 from mercurial import util
106
113
107 fd, path = tempfile.mkstemp(prefix=tempprefix)
114 fd, path = tempfile.mkstemp(prefix=tempprefix)
108 os.close(fd)
115 os.close(fd)
109 try:
116 try:
110 return util.cachestat(path).cacheable()
117 return util.cachestat(path).cacheable()
111 finally:
118 finally:
112 os.remove(path)
119 os.remove(path)
113
120
114 def has_lsprof():
121 def has_lsprof():
115 try:
122 try:
116 import _lsprof
123 import _lsprof
117 return True
124 return True
118 except ImportError:
125 except ImportError:
119 return False
126 return False
120
127
121 def has_gettext():
128 def has_gettext():
122 return matchoutput('msgfmt --version', 'GNU gettext-tools')
129 return matchoutput('msgfmt --version', 'GNU gettext-tools')
123
130
124 def has_git():
131 def has_git():
125 return matchoutput('git --version 2>&1', r'^git version')
132 return matchoutput('git --version 2>&1', r'^git version')
126
133
127 def has_docutils():
134 def has_docutils():
128 try:
135 try:
129 from docutils.core import publish_cmdline
136 from docutils.core import publish_cmdline
130 return True
137 return True
131 except ImportError:
138 except ImportError:
132 return False
139 return False
133
140
134 def getsvnversion():
141 def getsvnversion():
135 m = matchoutput('svn --version 2>&1', r'^svn,\s+version\s+(\d+)\.(\d+)')
142 m = matchoutput('svn --version 2>&1', r'^svn,\s+version\s+(\d+)\.(\d+)')
136 if not m:
143 if not m:
137 return (0, 0)
144 return (0, 0)
138 return (int(m.group(1)), int(m.group(2)))
145 return (int(m.group(1)), int(m.group(2)))
139
146
140 def has_svn15():
147 def has_svn15():
141 return getsvnversion() >= (1, 5)
148 return getsvnversion() >= (1, 5)
142
149
143 def has_svn13():
150 def has_svn13():
144 return getsvnversion() >= (1, 3)
151 return getsvnversion() >= (1, 3)
145
152
146 def has_svn():
153 def has_svn():
147 return matchoutput('svn --version 2>&1', r'^svn, version') and \
154 return matchoutput('svn --version 2>&1', r'^svn, version') and \
148 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
155 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
149
156
150 def has_svn_bindings():
157 def has_svn_bindings():
151 try:
158 try:
152 import svn.core
159 import svn.core
153 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
160 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
154 if version < (1, 4):
161 if version < (1, 4):
155 return False
162 return False
156 return True
163 return True
157 except ImportError:
164 except ImportError:
158 return False
165 return False
159
166
160 def has_p4():
167 def has_p4():
161 return matchoutput('p4 -V', r'Rev\. P4/') and matchoutput('p4d -V', r'Rev\. P4D/')
168 return matchoutput('p4 -V', r'Rev\. P4/') and matchoutput('p4d -V', r'Rev\. P4D/')
162
169
163 def has_symlink():
170 def has_symlink():
171 if not hasattr(os, "symlink"):
172 return False
173 name = tempfile.mktemp(dir=".", prefix='hg-checklink-')
174 try:
175 os.symlink(".", name)
176 os.unlink(name)
177 return True
178 except (OSError, AttributeError):
179 return False
164 return hasattr(os, "symlink") # FIXME: should also check file system and os
180 return hasattr(os, "symlink") # FIXME: should also check file system and os
165
181
166 def has_tla():
182 def has_tla():
167 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
183 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
168
184
169 def has_gpg():
185 def has_gpg():
170 return matchoutput('gpg --version 2>&1', r'GnuPG')
186 return matchoutput('gpg --version 2>&1', r'GnuPG')
171
187
172 def has_unix_permissions():
188 def has_unix_permissions():
173 d = tempfile.mkdtemp(prefix=tempprefix, dir=".")
189 d = tempfile.mkdtemp(prefix=tempprefix, dir=".")
174 try:
190 try:
175 fname = os.path.join(d, 'foo')
191 fname = os.path.join(d, 'foo')
176 for umask in (077, 007, 022):
192 for umask in (077, 007, 022):
177 os.umask(umask)
193 os.umask(umask)
178 f = open(fname, 'w')
194 f = open(fname, 'w')
179 f.close()
195 f.close()
180 mode = os.stat(fname).st_mode
196 mode = os.stat(fname).st_mode
181 os.unlink(fname)
197 os.unlink(fname)
182 if mode & 0777 != ~umask & 0666:
198 if mode & 0777 != ~umask & 0666:
183 return False
199 return False
184 return True
200 return True
185 finally:
201 finally:
186 os.rmdir(d)
202 os.rmdir(d)
187
203
188 def has_pyflakes():
204 def has_pyflakes():
189 return matchoutput('echo "import re" 2>&1 | pyflakes',
205 return matchoutput('echo "import re" 2>&1 | pyflakes',
190 r"<stdin>:1: 're' imported but unused",
206 r"<stdin>:1: 're' imported but unused",
191 True)
207 True)
192
208
193 def has_pygments():
209 def has_pygments():
194 try:
210 try:
195 import pygments
211 import pygments
196 return True
212 return True
197 except ImportError:
213 except ImportError:
198 return False
214 return False
199
215
200 def has_outer_repo():
216 def has_outer_repo():
201 return matchoutput('hg root 2>&1', r'')
217 return matchoutput('hg root 2>&1', r'')
202
218
203 def has_ssl():
219 def has_ssl():
204 try:
220 try:
205 import ssl
221 import ssl
206 import OpenSSL
222 import OpenSSL
207 OpenSSL.SSL.Context
223 OpenSSL.SSL.Context
208 return True
224 return True
209 except ImportError:
225 except ImportError:
210 return False
226 return False
211
227
212 def has_windows():
228 def has_windows():
213 return os.name == 'nt'
229 return os.name == 'nt'
214
230
215 def has_system_sh():
231 def has_system_sh():
216 return os.name != 'nt'
232 return os.name != 'nt'
217
233
218 def has_serve():
234 def has_serve():
219 return os.name != 'nt' # gross approximation
235 return os.name != 'nt' # gross approximation
220
236
221 def has_tic():
237 def has_tic():
222 return matchoutput('test -x "`which tic`"', '')
238 return matchoutput('test -x "`which tic`"', '')
223
239
224 def has_msys():
240 def has_msys():
225 return os.getenv('MSYSTEM')
241 return os.getenv('MSYSTEM')
226
242
227 checks = {
243 checks = {
228 "baz": (has_baz, "GNU Arch baz client"),
244 "baz": (has_baz, "GNU Arch baz client"),
229 "bzr": (has_bzr, "Canonical's Bazaar client"),
245 "bzr": (has_bzr, "Canonical's Bazaar client"),
230 "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"),
246 "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"),
231 "cacheable": (has_cacheable_fs, "cacheable filesystem"),
247 "cacheable": (has_cacheable_fs, "cacheable filesystem"),
232 "cvs": (has_cvs, "cvs client/server"),
248 "cvs": (has_cvs, "cvs client/server"),
233 "darcs": (has_darcs, "darcs client"),
249 "darcs": (has_darcs, "darcs client"),
234 "docutils": (has_docutils, "Docutils text processing library"),
250 "docutils": (has_docutils, "Docutils text processing library"),
235 "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
251 "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
236 "execbit": (has_executablebit, "executable bit"),
252 "execbit": (has_executablebit, "executable bit"),
237 "fifo": (has_fifo, "named pipes"),
253 "fifo": (has_fifo, "named pipes"),
238 "gettext": (has_gettext, "GNU Gettext (msgfmt)"),
254 "gettext": (has_gettext, "GNU Gettext (msgfmt)"),
239 "git": (has_git, "git command line client"),
255 "git": (has_git, "git command line client"),
240 "gpg": (has_gpg, "gpg client"),
256 "gpg": (has_gpg, "gpg client"),
241 "icasefs": (has_icasefs, "case insensitive file system"),
257 "icasefs": (has_icasefs, "case insensitive file system"),
242 "inotify": (has_inotify, "inotify extension support"),
258 "inotify": (has_inotify, "inotify extension support"),
243 "lsprof": (has_lsprof, "python lsprof module"),
259 "lsprof": (has_lsprof, "python lsprof module"),
244 "mtn": (has_mtn, "monotone client (>= 1.0)"),
260 "mtn": (has_mtn, "monotone client (>= 1.0)"),
245 "outer-repo": (has_outer_repo, "outer repo"),
261 "outer-repo": (has_outer_repo, "outer repo"),
246 "p4": (has_p4, "Perforce server and client"),
262 "p4": (has_p4, "Perforce server and client"),
247 "pyflakes": (has_pyflakes, "Pyflakes python linter"),
263 "pyflakes": (has_pyflakes, "Pyflakes python linter"),
248 "pygments": (has_pygments, "Pygments source highlighting library"),
264 "pygments": (has_pygments, "Pygments source highlighting library"),
249 "serve": (has_serve, "platform and python can manage 'hg serve -d'"),
265 "serve": (has_serve, "platform and python can manage 'hg serve -d'"),
250 "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"),
266 "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"),
251 "svn": (has_svn, "subversion client and admin tools"),
267 "svn": (has_svn, "subversion client and admin tools"),
252 "svn13": (has_svn13, "subversion client and admin tools >= 1.3"),
268 "svn13": (has_svn13, "subversion client and admin tools >= 1.3"),
253 "svn15": (has_svn15, "subversion client and admin tools >= 1.5"),
269 "svn15": (has_svn15, "subversion client and admin tools >= 1.5"),
254 "svn-bindings": (has_svn_bindings, "subversion python bindings"),
270 "svn-bindings": (has_svn_bindings, "subversion python bindings"),
255 "symlink": (has_symlink, "symbolic links"),
271 "symlink": (has_symlink, "symbolic links"),
256 "system-sh": (has_system_sh, "system() uses sh"),
272 "system-sh": (has_system_sh, "system() uses sh"),
257 "tic": (has_tic, "terminfo compiler"),
273 "tic": (has_tic, "terminfo compiler"),
258 "tla": (has_tla, "GNU Arch tla client"),
274 "tla": (has_tla, "GNU Arch tla client"),
259 "unix-permissions": (has_unix_permissions, "unix-style permissions"),
275 "unix-permissions": (has_unix_permissions, "unix-style permissions"),
260 "windows": (has_windows, "Windows"),
276 "windows": (has_windows, "Windows"),
261 "msys": (has_msys, "Windows with MSYS"),
277 "msys": (has_msys, "Windows with MSYS"),
262 }
278 }
263
279
264 def list_features():
280 def list_features():
265 for name, feature in checks.iteritems():
281 for name, feature in checks.iteritems():
266 desc = feature[1]
282 desc = feature[1]
267 print name + ':', desc
283 print name + ':', desc
268
284
269 def test_features():
285 def test_features():
270 failed = 0
286 failed = 0
271 for name, feature in checks.iteritems():
287 for name, feature in checks.iteritems():
272 check, _ = feature
288 check, _ = feature
273 try:
289 try:
274 check()
290 check()
275 except Exception, e:
291 except Exception, e:
276 print "feature %s failed: %s" % (name, e)
292 print "feature %s failed: %s" % (name, e)
277 failed += 1
293 failed += 1
278 return failed
294 return failed
279
295
280 parser = optparse.OptionParser("%prog [options] [features]")
296 parser = optparse.OptionParser("%prog [options] [features]")
281 parser.add_option("--test-features", action="store_true",
297 parser.add_option("--test-features", action="store_true",
282 help="test available features")
298 help="test available features")
283 parser.add_option("--list-features", action="store_true",
299 parser.add_option("--list-features", action="store_true",
284 help="list available features")
300 help="list available features")
285 parser.add_option("-q", "--quiet", action="store_true",
301 parser.add_option("-q", "--quiet", action="store_true",
286 help="check features silently")
302 help="check features silently")
287
303
288 if __name__ == '__main__':
304 if __name__ == '__main__':
289 options, args = parser.parse_args()
305 options, args = parser.parse_args()
290 if options.list_features:
306 if options.list_features:
291 list_features()
307 list_features()
292 sys.exit(0)
308 sys.exit(0)
293
309
294 if options.test_features:
310 if options.test_features:
295 sys.exit(test_features())
311 sys.exit(test_features())
296
312
297 quiet = options.quiet
313 quiet = options.quiet
298
314
299 failures = 0
315 failures = 0
300
316
301 def error(msg):
317 def error(msg):
302 global failures
318 global failures
303 if not quiet:
319 if not quiet:
304 sys.stderr.write(msg + '\n')
320 sys.stderr.write(msg + '\n')
305 failures += 1
321 failures += 1
306
322
307 for feature in args:
323 for feature in args:
308 negate = feature.startswith('no-')
324 negate = feature.startswith('no-')
309 if negate:
325 if negate:
310 feature = feature[3:]
326 feature = feature[3:]
311
327
312 if feature not in checks:
328 if feature not in checks:
313 error('skipped: unknown feature: ' + feature)
329 error('skipped: unknown feature: ' + feature)
314 continue
330 continue
315
331
316 check, desc = checks[feature]
332 check, desc = checks[feature]
317 try:
333 try:
318 available = check()
334 available = check()
319 except Exception, e:
335 except Exception, e:
320 error('hghave check failed: ' + feature)
336 error('hghave check failed: ' + feature)
321 continue
337 continue
322
338
323 if not negate and not available:
339 if not negate and not available:
324 error('skipped: missing feature: ' + desc)
340 error('skipped: missing feature: ' + desc)
325 elif negate and available:
341 elif negate and available:
326 error('skipped: system supports %s' % desc)
342 error('skipped: system supports %s' % desc)
327
343
328 if failures != 0:
344 if failures != 0:
329 sys.exit(1)
345 sys.exit(1)
@@ -1,116 +1,127 b''
1 $ "$TESTDIR/hghave" icasefs || exit 80
1 $ "$TESTDIR/hghave" icasefs || exit 80
2
2
3 $ hg debugfs | grep 'case-sensitive:'
3 $ hg debugfs | grep 'case-sensitive:'
4 case-sensitive: no
4 case-sensitive: no
5
5
6 test file addition with bad case
6 test file addition with bad case
7
7
8 $ hg init repo1
8 $ hg init repo1
9 $ cd repo1
9 $ cd repo1
10 $ echo a > a
10 $ echo a > a
11 $ hg add A
11 $ hg add A
12 adding a
12 adding a
13 $ hg st
13 $ hg st
14 A a
14 A a
15 $ hg ci -m adda
15 $ hg ci -m adda
16 $ hg manifest
16 $ hg manifest
17 a
17 a
18 $ cd ..
18 $ cd ..
19
19
20 test case collision on rename (issue750)
20 test case collision on rename (issue750)
21
21
22 $ hg init repo2
22 $ hg init repo2
23 $ cd repo2
23 $ cd repo2
24 $ echo a > a
24 $ echo a > a
25 $ hg --debug ci -Am adda
25 $ hg --debug ci -Am adda
26 adding a
26 adding a
27 a
27 a
28 committed changeset 0:07f4944404050f47db2e5c5071e0e84e7a27bba9
28 committed changeset 0:07f4944404050f47db2e5c5071e0e84e7a27bba9
29
29
30 Case-changing renames should work:
30 Case-changing renames should work:
31
31
32 $ hg mv a A
32 $ hg mv a A
33 $ hg mv A a
33 $ hg mv A a
34 $ hg st
34 $ hg st
35 $ cd ..
35 $ cd ..
36
36
37 test case collision between revisions (issue912)
37 test case collision between revisions (issue912)
38
38
39 $ hg init repo3
39 $ hg init repo3
40 $ cd repo3
40 $ cd repo3
41 $ echo a > a
41 $ echo a > a
42 $ hg ci -Am adda
42 $ hg ci -Am adda
43 adding a
43 adding a
44 $ hg rm a
44 $ hg rm a
45 $ hg ci -Am removea
45 $ hg ci -Am removea
46 $ echo A > A
46 $ echo A > A
47
47
48 on linux hfs keeps the old case stored, force it
48 on linux hfs keeps the old case stored, force it
49
49
50 $ mv a aa
50 $ mv a aa
51 $ mv aa A
51 $ mv aa A
52 $ hg ci -Am addA
52 $ hg ci -Am addA
53 adding A
53 adding A
54
54
55 used to fail under case insensitive fs
55 used to fail under case insensitive fs
56
56
57 $ hg up -C 0
57 $ hg up -C 0
58 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
58 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
59 $ hg up -C
59 $ hg up -C
60 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
60 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
61
61
62 no clobbering of untracked files with wrong casing
62 no clobbering of untracked files with wrong casing
63
63
64 $ hg up -r null
64 $ hg up -r null
65 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
65 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
66 $ echo gold > a
66 $ echo gold > a
67 $ hg up
67 $ hg up
68 A: untracked file differs
68 A: untracked file differs
69 abort: untracked files in working directory differ from files in requested revision
69 abort: untracked files in working directory differ from files in requested revision
70 [255]
70 [255]
71 $ cat a
71 $ cat a
72 gold
72 gold
73
73
74 $ cd ..
74 $ cd ..
75
75
76 issue 3342: file in nested directory causes unexpected abort
77
78 $ hg init issue3342
79 $ cd issue3342
80
81 $ mkdir -p a/B/c/D
82 $ echo e > a/B/c/D/e
83 $ hg add a/B/c/D/e
84
85 $ cd ..
86
76 issue 3340: mq does not handle case changes correctly
87 issue 3340: mq does not handle case changes correctly
77
88
78 in addition to reported case, 'hg qrefresh' is also tested against
89 in addition to reported case, 'hg qrefresh' is also tested against
79 case changes.
90 case changes.
80
91
81 $ echo "[extensions]" >> $HGRCPATH
92 $ echo "[extensions]" >> $HGRCPATH
82 $ echo "mq=" >> $HGRCPATH
93 $ echo "mq=" >> $HGRCPATH
83
94
84 $ hg init issue3340
95 $ hg init issue3340
85 $ cd issue3340
96 $ cd issue3340
86
97
87 $ echo a > mIxEdCaSe
98 $ echo a > mIxEdCaSe
88 $ hg add mIxEdCaSe
99 $ hg add mIxEdCaSe
89 $ hg commit -m '#0'
100 $ hg commit -m '#0'
90 $ hg rename mIxEdCaSe tmp
101 $ hg rename mIxEdCaSe tmp
91 $ hg rename tmp MiXeDcAsE
102 $ hg rename tmp MiXeDcAsE
92 $ hg status -A
103 $ hg status -A
93 A MiXeDcAsE
104 A MiXeDcAsE
94 mIxEdCaSe
105 mIxEdCaSe
95 R mIxEdCaSe
106 R mIxEdCaSe
96 $ hg qnew changecase
107 $ hg qnew changecase
97 $ hg status -A
108 $ hg status -A
98 C MiXeDcAsE
109 C MiXeDcAsE
99
110
100 $ hg qpop -a
111 $ hg qpop -a
101 popping changecase
112 popping changecase
102 patch queue now empty
113 patch queue now empty
103 $ hg qnew refresh-casechange
114 $ hg qnew refresh-casechange
104 $ hg status -A
115 $ hg status -A
105 C mIxEdCaSe
116 C mIxEdCaSe
106 $ hg rename mIxEdCaSe tmp
117 $ hg rename mIxEdCaSe tmp
107 $ hg rename tmp MiXeDcAsE
118 $ hg rename tmp MiXeDcAsE
108 $ hg status -A
119 $ hg status -A
109 A MiXeDcAsE
120 A MiXeDcAsE
110 mIxEdCaSe
121 mIxEdCaSe
111 R mIxEdCaSe
122 R mIxEdCaSe
112 $ hg qrefresh
123 $ hg qrefresh
113 $ hg status -A
124 $ hg status -A
114 C MiXeDcAsE
125 C MiXeDcAsE
115
126
116 $ cd ..
127 $ cd ..
@@ -1,52 +1,53 b''
1 import os
1 import os
2 from mercurial import hg, ui
2 from mercurial import hg, ui
3 from mercurial.scmutil import walkrepos
3 from mercurial.scmutil import walkrepos
4 from mercurial.util import checklink
4 from os import mkdir, chdir
5 from os import mkdir, chdir
5 from os.path import join as pjoin
6 from os.path import join as pjoin
6
7
7 u = ui.ui()
8 u = ui.ui()
8 sym = getattr(os, 'symlink', False) and getattr(os.path, 'samestat', False)
9 sym = checklink('.')
9
10
10 hg.repository(u, 'top1', create=1)
11 hg.repository(u, 'top1', create=1)
11 mkdir('subdir')
12 mkdir('subdir')
12 chdir('subdir')
13 chdir('subdir')
13 hg.repository(u, 'sub1', create=1)
14 hg.repository(u, 'sub1', create=1)
14 mkdir('subsubdir')
15 mkdir('subsubdir')
15 chdir('subsubdir')
16 chdir('subsubdir')
16 hg.repository(u, 'subsub1', create=1)
17 hg.repository(u, 'subsub1', create=1)
17 chdir(os.path.pardir)
18 chdir(os.path.pardir)
18 if sym:
19 if sym:
19 os.symlink(os.path.pardir, 'circle')
20 os.symlink(os.path.pardir, 'circle')
20 os.symlink(pjoin('subsubdir', 'subsub1'), 'subsub1')
21 os.symlink(pjoin('subsubdir', 'subsub1'), 'subsub1')
21
22
22 def runtest():
23 def runtest():
23 reposet = frozenset(walkrepos('.', followsym=True))
24 reposet = frozenset(walkrepos('.', followsym=True))
24 if sym and (len(reposet) != 3):
25 if sym and (len(reposet) != 3):
25 print "reposet = %r" % (reposet,)
26 print "reposet = %r" % (reposet,)
26 print "Found %d repositories when I should have found 3" % (len(reposet),)
27 print "Found %d repositories when I should have found 3" % (len(reposet),)
27 if (not sym) and (len(reposet) != 2):
28 if (not sym) and (len(reposet) != 2):
28 print "reposet = %r" % (reposet,)
29 print "reposet = %r" % (reposet,)
29 print "Found %d repositories when I should have found 2" % (len(reposet),)
30 print "Found %d repositories when I should have found 2" % (len(reposet),)
30 sub1set = frozenset((pjoin('.', 'sub1'),
31 sub1set = frozenset((pjoin('.', 'sub1'),
31 pjoin('.', 'circle', 'subdir', 'sub1')))
32 pjoin('.', 'circle', 'subdir', 'sub1')))
32 if len(sub1set & reposet) != 1:
33 if len(sub1set & reposet) != 1:
33 print "sub1set = %r" % (sub1set,)
34 print "sub1set = %r" % (sub1set,)
34 print "reposet = %r" % (reposet,)
35 print "reposet = %r" % (reposet,)
35 print "sub1set and reposet should have exactly one path in common."
36 print "sub1set and reposet should have exactly one path in common."
36 sub2set = frozenset((pjoin('.', 'subsub1'),
37 sub2set = frozenset((pjoin('.', 'subsub1'),
37 pjoin('.', 'subsubdir', 'subsub1')))
38 pjoin('.', 'subsubdir', 'subsub1')))
38 if len(sub2set & reposet) != 1:
39 if len(sub2set & reposet) != 1:
39 print "sub2set = %r" % (sub2set,)
40 print "sub2set = %r" % (sub2set,)
40 print "reposet = %r" % (reposet,)
41 print "reposet = %r" % (reposet,)
41 print "sub1set and reposet should have exactly one path in common."
42 print "sub1set and reposet should have exactly one path in common."
42 sub3 = pjoin('.', 'circle', 'top1')
43 sub3 = pjoin('.', 'circle', 'top1')
43 if sym and not (sub3 in reposet):
44 if sym and not (sub3 in reposet):
44 print "reposet = %r" % (reposet,)
45 print "reposet = %r" % (reposet,)
45 print "Symbolic links are supported and %s is not in reposet" % (sub3,)
46 print "Symbolic links are supported and %s is not in reposet" % (sub3,)
46
47
47 runtest()
48 runtest()
48 if sym:
49 if sym:
49 # Simulate not having symlinks.
50 # Simulate not having symlinks.
50 del os.path.samestat
51 del os.path.samestat
51 sym = False
52 sym = False
52 runtest()
53 runtest()
General Comments 0
You need to be logged in to leave comments. Login now