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