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