##// END OF EJS Templates
subrepo: fix pruning of subrepo filenames in dirstate (issue2619)
trbs -
r13339:22167be0 stable
parent child Browse files
Show More
@@ -1,681 +1,681 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
7
8 from node import nullid
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import util, ignore, osutil, parsers
10 import util, ignore, osutil, parsers
11 import struct, os, stat, errno
11 import struct, os, stat, errno
12 import cStringIO
12 import cStringIO
13
13
14 _format = ">cllll"
14 _format = ">cllll"
15 propertycache = util.propertycache
15 propertycache = util.propertycache
16
16
17 def _finddirs(path):
17 def _finddirs(path):
18 pos = path.rfind('/')
18 pos = path.rfind('/')
19 while pos != -1:
19 while pos != -1:
20 yield path[:pos]
20 yield path[:pos]
21 pos = path.rfind('/', 0, pos)
21 pos = path.rfind('/', 0, pos)
22
22
23 def _incdirs(dirs, path):
23 def _incdirs(dirs, path):
24 for base in _finddirs(path):
24 for base in _finddirs(path):
25 if base in dirs:
25 if base in dirs:
26 dirs[base] += 1
26 dirs[base] += 1
27 return
27 return
28 dirs[base] = 1
28 dirs[base] = 1
29
29
30 def _decdirs(dirs, path):
30 def _decdirs(dirs, path):
31 for base in _finddirs(path):
31 for base in _finddirs(path):
32 if dirs[base] > 1:
32 if dirs[base] > 1:
33 dirs[base] -= 1
33 dirs[base] -= 1
34 return
34 return
35 del dirs[base]
35 del dirs[base]
36
36
37 class dirstate(object):
37 class dirstate(object):
38
38
39 def __init__(self, opener, ui, root):
39 def __init__(self, opener, ui, root):
40 '''Create a new dirstate object.
40 '''Create a new dirstate object.
41
41
42 opener is an open()-like callable that can be used to open the
42 opener is an open()-like callable that can be used to open the
43 dirstate file; root is the root of the directory tracked by
43 dirstate file; root is the root of the directory tracked by
44 the dirstate.
44 the dirstate.
45 '''
45 '''
46 self._opener = opener
46 self._opener = opener
47 self._root = root
47 self._root = root
48 self._rootdir = os.path.join(root, '')
48 self._rootdir = os.path.join(root, '')
49 self._dirty = False
49 self._dirty = False
50 self._dirtypl = False
50 self._dirtypl = False
51 self._ui = ui
51 self._ui = ui
52
52
53 @propertycache
53 @propertycache
54 def _map(self):
54 def _map(self):
55 '''Return the dirstate contents as a map from filename to
55 '''Return the dirstate contents as a map from filename to
56 (state, mode, size, time).'''
56 (state, mode, size, time).'''
57 self._read()
57 self._read()
58 return self._map
58 return self._map
59
59
60 @propertycache
60 @propertycache
61 def _copymap(self):
61 def _copymap(self):
62 self._read()
62 self._read()
63 return self._copymap
63 return self._copymap
64
64
65 @propertycache
65 @propertycache
66 def _foldmap(self):
66 def _foldmap(self):
67 f = {}
67 f = {}
68 for name in self._map:
68 for name in self._map:
69 f[os.path.normcase(name)] = name
69 f[os.path.normcase(name)] = name
70 return f
70 return f
71
71
72 @propertycache
72 @propertycache
73 def _branch(self):
73 def _branch(self):
74 try:
74 try:
75 return self._opener("branch").read().strip() or "default"
75 return self._opener("branch").read().strip() or "default"
76 except IOError:
76 except IOError:
77 return "default"
77 return "default"
78
78
79 @propertycache
79 @propertycache
80 def _pl(self):
80 def _pl(self):
81 try:
81 try:
82 st = self._opener("dirstate").read(40)
82 st = self._opener("dirstate").read(40)
83 l = len(st)
83 l = len(st)
84 if l == 40:
84 if l == 40:
85 return st[:20], st[20:40]
85 return st[:20], st[20:40]
86 elif l > 0 and l < 40:
86 elif l > 0 and l < 40:
87 raise util.Abort(_('working directory state appears damaged!'))
87 raise util.Abort(_('working directory state appears damaged!'))
88 except IOError, err:
88 except IOError, err:
89 if err.errno != errno.ENOENT:
89 if err.errno != errno.ENOENT:
90 raise
90 raise
91 return [nullid, nullid]
91 return [nullid, nullid]
92
92
93 @propertycache
93 @propertycache
94 def _dirs(self):
94 def _dirs(self):
95 dirs = {}
95 dirs = {}
96 for f, s in self._map.iteritems():
96 for f, s in self._map.iteritems():
97 if s[0] != 'r':
97 if s[0] != 'r':
98 _incdirs(dirs, f)
98 _incdirs(dirs, f)
99 return dirs
99 return dirs
100
100
101 @propertycache
101 @propertycache
102 def _ignore(self):
102 def _ignore(self):
103 files = [self._join('.hgignore')]
103 files = [self._join('.hgignore')]
104 for name, path in self._ui.configitems("ui"):
104 for name, path in self._ui.configitems("ui"):
105 if name == 'ignore' or name.startswith('ignore.'):
105 if name == 'ignore' or name.startswith('ignore.'):
106 files.append(util.expandpath(path))
106 files.append(util.expandpath(path))
107 return ignore.ignore(self._root, files, self._ui.warn)
107 return ignore.ignore(self._root, files, self._ui.warn)
108
108
109 @propertycache
109 @propertycache
110 def _slash(self):
110 def _slash(self):
111 return self._ui.configbool('ui', 'slash') and os.sep != '/'
111 return self._ui.configbool('ui', 'slash') and os.sep != '/'
112
112
113 @propertycache
113 @propertycache
114 def _checklink(self):
114 def _checklink(self):
115 return util.checklink(self._root)
115 return util.checklink(self._root)
116
116
117 @propertycache
117 @propertycache
118 def _checkexec(self):
118 def _checkexec(self):
119 return util.checkexec(self._root)
119 return util.checkexec(self._root)
120
120
121 @propertycache
121 @propertycache
122 def _checkcase(self):
122 def _checkcase(self):
123 return not util.checkcase(self._join('.hg'))
123 return not util.checkcase(self._join('.hg'))
124
124
125 def _join(self, f):
125 def _join(self, f):
126 # much faster than os.path.join()
126 # much faster than os.path.join()
127 # it's safe because f is always a relative path
127 # it's safe because f is always a relative path
128 return self._rootdir + f
128 return self._rootdir + f
129
129
130 def flagfunc(self, fallback):
130 def flagfunc(self, fallback):
131 if self._checklink:
131 if self._checklink:
132 if self._checkexec:
132 if self._checkexec:
133 def f(x):
133 def f(x):
134 p = self._join(x)
134 p = self._join(x)
135 if os.path.islink(p):
135 if os.path.islink(p):
136 return 'l'
136 return 'l'
137 if util.is_exec(p):
137 if util.is_exec(p):
138 return 'x'
138 return 'x'
139 return ''
139 return ''
140 return f
140 return f
141 def f(x):
141 def f(x):
142 if os.path.islink(self._join(x)):
142 if os.path.islink(self._join(x)):
143 return 'l'
143 return 'l'
144 if 'x' in fallback(x):
144 if 'x' in fallback(x):
145 return 'x'
145 return 'x'
146 return ''
146 return ''
147 return f
147 return f
148 if self._checkexec:
148 if self._checkexec:
149 def f(x):
149 def f(x):
150 if 'l' in fallback(x):
150 if 'l' in fallback(x):
151 return 'l'
151 return 'l'
152 if util.is_exec(self._join(x)):
152 if util.is_exec(self._join(x)):
153 return 'x'
153 return 'x'
154 return ''
154 return ''
155 return f
155 return f
156 return fallback
156 return fallback
157
157
158 def getcwd(self):
158 def getcwd(self):
159 cwd = os.getcwd()
159 cwd = os.getcwd()
160 if cwd == self._root:
160 if cwd == self._root:
161 return ''
161 return ''
162 # self._root ends with a path separator if self._root is '/' or 'C:\'
162 # self._root ends with a path separator if self._root is '/' or 'C:\'
163 rootsep = self._root
163 rootsep = self._root
164 if not util.endswithsep(rootsep):
164 if not util.endswithsep(rootsep):
165 rootsep += os.sep
165 rootsep += os.sep
166 if cwd.startswith(rootsep):
166 if cwd.startswith(rootsep):
167 return cwd[len(rootsep):]
167 return cwd[len(rootsep):]
168 else:
168 else:
169 # we're outside the repo. return an absolute path.
169 # we're outside the repo. return an absolute path.
170 return cwd
170 return cwd
171
171
172 def pathto(self, f, cwd=None):
172 def pathto(self, f, cwd=None):
173 if cwd is None:
173 if cwd is None:
174 cwd = self.getcwd()
174 cwd = self.getcwd()
175 path = util.pathto(self._root, cwd, f)
175 path = util.pathto(self._root, cwd, f)
176 if self._slash:
176 if self._slash:
177 return util.normpath(path)
177 return util.normpath(path)
178 return path
178 return path
179
179
180 def __getitem__(self, key):
180 def __getitem__(self, key):
181 '''Return the current state of key (a filename) in the dirstate.
181 '''Return the current state of key (a filename) in the dirstate.
182
182
183 States are:
183 States are:
184 n normal
184 n normal
185 m needs merging
185 m needs merging
186 r marked for removal
186 r marked for removal
187 a marked for addition
187 a marked for addition
188 ? not tracked
188 ? not tracked
189 '''
189 '''
190 return self._map.get(key, ("?",))[0]
190 return self._map.get(key, ("?",))[0]
191
191
192 def __contains__(self, key):
192 def __contains__(self, key):
193 return key in self._map
193 return key in self._map
194
194
195 def __iter__(self):
195 def __iter__(self):
196 for x in sorted(self._map):
196 for x in sorted(self._map):
197 yield x
197 yield x
198
198
199 def parents(self):
199 def parents(self):
200 return self._pl
200 return self._pl
201
201
202 def branch(self):
202 def branch(self):
203 return self._branch
203 return self._branch
204
204
205 def setparents(self, p1, p2=nullid):
205 def setparents(self, p1, p2=nullid):
206 self._dirty = self._dirtypl = True
206 self._dirty = self._dirtypl = True
207 self._pl = p1, p2
207 self._pl = p1, p2
208
208
209 def setbranch(self, branch):
209 def setbranch(self, branch):
210 if branch in ['tip', '.', 'null']:
210 if branch in ['tip', '.', 'null']:
211 raise util.Abort(_('the name \'%s\' is reserved') % branch)
211 raise util.Abort(_('the name \'%s\' is reserved') % branch)
212 self._branch = branch
212 self._branch = branch
213 self._opener("branch", "w").write(branch + '\n')
213 self._opener("branch", "w").write(branch + '\n')
214
214
215 def _read(self):
215 def _read(self):
216 self._map = {}
216 self._map = {}
217 self._copymap = {}
217 self._copymap = {}
218 try:
218 try:
219 st = self._opener("dirstate").read()
219 st = self._opener("dirstate").read()
220 except IOError, err:
220 except IOError, err:
221 if err.errno != errno.ENOENT:
221 if err.errno != errno.ENOENT:
222 raise
222 raise
223 return
223 return
224 if not st:
224 if not st:
225 return
225 return
226
226
227 p = parsers.parse_dirstate(self._map, self._copymap, st)
227 p = parsers.parse_dirstate(self._map, self._copymap, st)
228 if not self._dirtypl:
228 if not self._dirtypl:
229 self._pl = p
229 self._pl = p
230
230
231 def invalidate(self):
231 def invalidate(self):
232 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
232 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
233 if a in self.__dict__:
233 if a in self.__dict__:
234 delattr(self, a)
234 delattr(self, a)
235 self._dirty = False
235 self._dirty = False
236
236
237 def copy(self, source, dest):
237 def copy(self, source, dest):
238 """Mark dest as a copy of source. Unmark dest if source is None."""
238 """Mark dest as a copy of source. Unmark dest if source is None."""
239 if source == dest:
239 if source == dest:
240 return
240 return
241 self._dirty = True
241 self._dirty = True
242 if source is not None:
242 if source is not None:
243 self._copymap[dest] = source
243 self._copymap[dest] = source
244 elif dest in self._copymap:
244 elif dest in self._copymap:
245 del self._copymap[dest]
245 del self._copymap[dest]
246
246
247 def copied(self, file):
247 def copied(self, file):
248 return self._copymap.get(file, None)
248 return self._copymap.get(file, None)
249
249
250 def copies(self):
250 def copies(self):
251 return self._copymap
251 return self._copymap
252
252
253 def _droppath(self, f):
253 def _droppath(self, f):
254 if self[f] not in "?r" and "_dirs" in self.__dict__:
254 if self[f] not in "?r" and "_dirs" in self.__dict__:
255 _decdirs(self._dirs, f)
255 _decdirs(self._dirs, f)
256
256
257 def _addpath(self, f, check=False):
257 def _addpath(self, f, check=False):
258 oldstate = self[f]
258 oldstate = self[f]
259 if check or oldstate == "r":
259 if check or oldstate == "r":
260 if '\r' in f or '\n' in f:
260 if '\r' in f or '\n' in f:
261 raise util.Abort(
261 raise util.Abort(
262 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
262 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
263 if f in self._dirs:
263 if f in self._dirs:
264 raise util.Abort(_('directory %r already in dirstate') % f)
264 raise util.Abort(_('directory %r already in dirstate') % f)
265 # shadows
265 # shadows
266 for d in _finddirs(f):
266 for d in _finddirs(f):
267 if d in self._dirs:
267 if d in self._dirs:
268 break
268 break
269 if d in self._map and self[d] != 'r':
269 if d in self._map and self[d] != 'r':
270 raise util.Abort(
270 raise util.Abort(
271 _('file %r in dirstate clashes with %r') % (d, f))
271 _('file %r in dirstate clashes with %r') % (d, f))
272 if oldstate in "?r" and "_dirs" in self.__dict__:
272 if oldstate in "?r" and "_dirs" in self.__dict__:
273 _incdirs(self._dirs, f)
273 _incdirs(self._dirs, f)
274
274
275 def normal(self, f):
275 def normal(self, f):
276 '''Mark a file normal and clean.'''
276 '''Mark a file normal and clean.'''
277 self._dirty = True
277 self._dirty = True
278 self._addpath(f)
278 self._addpath(f)
279 s = os.lstat(self._join(f))
279 s = os.lstat(self._join(f))
280 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
280 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
281 if f in self._copymap:
281 if f in self._copymap:
282 del self._copymap[f]
282 del self._copymap[f]
283
283
284 def normallookup(self, f):
284 def normallookup(self, f):
285 '''Mark a file normal, but possibly dirty.'''
285 '''Mark a file normal, but possibly dirty.'''
286 if self._pl[1] != nullid and f in self._map:
286 if self._pl[1] != nullid and f in self._map:
287 # if there is a merge going on and the file was either
287 # if there is a merge going on and the file was either
288 # in state 'm' (-1) or coming from other parent (-2) before
288 # in state 'm' (-1) or coming from other parent (-2) before
289 # being removed, restore that state.
289 # being removed, restore that state.
290 entry = self._map[f]
290 entry = self._map[f]
291 if entry[0] == 'r' and entry[2] in (-1, -2):
291 if entry[0] == 'r' and entry[2] in (-1, -2):
292 source = self._copymap.get(f)
292 source = self._copymap.get(f)
293 if entry[2] == -1:
293 if entry[2] == -1:
294 self.merge(f)
294 self.merge(f)
295 elif entry[2] == -2:
295 elif entry[2] == -2:
296 self.otherparent(f)
296 self.otherparent(f)
297 if source:
297 if source:
298 self.copy(source, f)
298 self.copy(source, f)
299 return
299 return
300 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
300 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
301 return
301 return
302 self._dirty = True
302 self._dirty = True
303 self._addpath(f)
303 self._addpath(f)
304 self._map[f] = ('n', 0, -1, -1)
304 self._map[f] = ('n', 0, -1, -1)
305 if f in self._copymap:
305 if f in self._copymap:
306 del self._copymap[f]
306 del self._copymap[f]
307
307
308 def otherparent(self, f):
308 def otherparent(self, f):
309 '''Mark as coming from the other parent, always dirty.'''
309 '''Mark as coming from the other parent, always dirty.'''
310 if self._pl[1] == nullid:
310 if self._pl[1] == nullid:
311 raise util.Abort(_("setting %r to other parent "
311 raise util.Abort(_("setting %r to other parent "
312 "only allowed in merges") % f)
312 "only allowed in merges") % f)
313 self._dirty = True
313 self._dirty = True
314 self._addpath(f)
314 self._addpath(f)
315 self._map[f] = ('n', 0, -2, -1)
315 self._map[f] = ('n', 0, -2, -1)
316 if f in self._copymap:
316 if f in self._copymap:
317 del self._copymap[f]
317 del self._copymap[f]
318
318
319 def add(self, f):
319 def add(self, f):
320 '''Mark a file added.'''
320 '''Mark a file added.'''
321 self._dirty = True
321 self._dirty = True
322 self._addpath(f, True)
322 self._addpath(f, True)
323 self._map[f] = ('a', 0, -1, -1)
323 self._map[f] = ('a', 0, -1, -1)
324 if f in self._copymap:
324 if f in self._copymap:
325 del self._copymap[f]
325 del self._copymap[f]
326
326
327 def remove(self, f):
327 def remove(self, f):
328 '''Mark a file removed.'''
328 '''Mark a file removed.'''
329 self._dirty = True
329 self._dirty = True
330 self._droppath(f)
330 self._droppath(f)
331 size = 0
331 size = 0
332 if self._pl[1] != nullid and f in self._map:
332 if self._pl[1] != nullid and f in self._map:
333 # backup the previous state
333 # backup the previous state
334 entry = self._map[f]
334 entry = self._map[f]
335 if entry[0] == 'm': # merge
335 if entry[0] == 'm': # merge
336 size = -1
336 size = -1
337 elif entry[0] == 'n' and entry[2] == -2: # other parent
337 elif entry[0] == 'n' and entry[2] == -2: # other parent
338 size = -2
338 size = -2
339 self._map[f] = ('r', 0, size, 0)
339 self._map[f] = ('r', 0, size, 0)
340 if size == 0 and f in self._copymap:
340 if size == 0 and f in self._copymap:
341 del self._copymap[f]
341 del self._copymap[f]
342
342
343 def merge(self, f):
343 def merge(self, f):
344 '''Mark a file merged.'''
344 '''Mark a file merged.'''
345 self._dirty = True
345 self._dirty = True
346 s = os.lstat(self._join(f))
346 s = os.lstat(self._join(f))
347 self._addpath(f)
347 self._addpath(f)
348 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
348 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
349 if f in self._copymap:
349 if f in self._copymap:
350 del self._copymap[f]
350 del self._copymap[f]
351
351
352 def forget(self, f):
352 def forget(self, f):
353 '''Forget a file.'''
353 '''Forget a file.'''
354 self._dirty = True
354 self._dirty = True
355 try:
355 try:
356 self._droppath(f)
356 self._droppath(f)
357 del self._map[f]
357 del self._map[f]
358 except KeyError:
358 except KeyError:
359 self._ui.warn(_("not in dirstate: %s\n") % f)
359 self._ui.warn(_("not in dirstate: %s\n") % f)
360
360
361 def _normalize(self, path, knownpath):
361 def _normalize(self, path, knownpath):
362 norm_path = os.path.normcase(path)
362 norm_path = os.path.normcase(path)
363 fold_path = self._foldmap.get(norm_path, None)
363 fold_path = self._foldmap.get(norm_path, None)
364 if fold_path is None:
364 if fold_path is None:
365 if knownpath or not os.path.lexists(os.path.join(self._root, path)):
365 if knownpath or not os.path.lexists(os.path.join(self._root, path)):
366 fold_path = path
366 fold_path = path
367 else:
367 else:
368 fold_path = self._foldmap.setdefault(norm_path,
368 fold_path = self._foldmap.setdefault(norm_path,
369 util.fspath(path, self._root))
369 util.fspath(path, self._root))
370 return fold_path
370 return fold_path
371
371
372 def clear(self):
372 def clear(self):
373 self._map = {}
373 self._map = {}
374 if "_dirs" in self.__dict__:
374 if "_dirs" in self.__dict__:
375 delattr(self, "_dirs")
375 delattr(self, "_dirs")
376 self._copymap = {}
376 self._copymap = {}
377 self._pl = [nullid, nullid]
377 self._pl = [nullid, nullid]
378 self._dirty = True
378 self._dirty = True
379
379
380 def rebuild(self, parent, files):
380 def rebuild(self, parent, files):
381 self.clear()
381 self.clear()
382 for f in files:
382 for f in files:
383 if 'x' in files.flags(f):
383 if 'x' in files.flags(f):
384 self._map[f] = ('n', 0777, -1, 0)
384 self._map[f] = ('n', 0777, -1, 0)
385 else:
385 else:
386 self._map[f] = ('n', 0666, -1, 0)
386 self._map[f] = ('n', 0666, -1, 0)
387 self._pl = (parent, nullid)
387 self._pl = (parent, nullid)
388 self._dirty = True
388 self._dirty = True
389
389
390 def write(self):
390 def write(self):
391 if not self._dirty:
391 if not self._dirty:
392 return
392 return
393 st = self._opener("dirstate", "w", atomictemp=True)
393 st = self._opener("dirstate", "w", atomictemp=True)
394
394
395 # use the modification time of the newly created temporary file as the
395 # use the modification time of the newly created temporary file as the
396 # filesystem's notion of 'now'
396 # filesystem's notion of 'now'
397 now = int(util.fstat(st).st_mtime)
397 now = int(util.fstat(st).st_mtime)
398
398
399 cs = cStringIO.StringIO()
399 cs = cStringIO.StringIO()
400 copymap = self._copymap
400 copymap = self._copymap
401 pack = struct.pack
401 pack = struct.pack
402 write = cs.write
402 write = cs.write
403 write("".join(self._pl))
403 write("".join(self._pl))
404 for f, e in self._map.iteritems():
404 for f, e in self._map.iteritems():
405 if e[0] == 'n' and e[3] == now:
405 if e[0] == 'n' and e[3] == now:
406 # The file was last modified "simultaneously" with the current
406 # The file was last modified "simultaneously" with the current
407 # write to dirstate (i.e. within the same second for file-
407 # write to dirstate (i.e. within the same second for file-
408 # systems with a granularity of 1 sec). This commonly happens
408 # systems with a granularity of 1 sec). This commonly happens
409 # for at least a couple of files on 'update'.
409 # for at least a couple of files on 'update'.
410 # The user could change the file without changing its size
410 # The user could change the file without changing its size
411 # within the same second. Invalidate the file's stat data in
411 # within the same second. Invalidate the file's stat data in
412 # dirstate, forcing future 'status' calls to compare the
412 # dirstate, forcing future 'status' calls to compare the
413 # contents of the file. This prevents mistakenly treating such
413 # contents of the file. This prevents mistakenly treating such
414 # files as clean.
414 # files as clean.
415 e = (e[0], 0, -1, -1) # mark entry as 'unset'
415 e = (e[0], 0, -1, -1) # mark entry as 'unset'
416 self._map[f] = e
416 self._map[f] = e
417
417
418 if f in copymap:
418 if f in copymap:
419 f = "%s\0%s" % (f, copymap[f])
419 f = "%s\0%s" % (f, copymap[f])
420 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
420 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
421 write(e)
421 write(e)
422 write(f)
422 write(f)
423 st.write(cs.getvalue())
423 st.write(cs.getvalue())
424 st.rename()
424 st.rename()
425 self._dirty = self._dirtypl = False
425 self._dirty = self._dirtypl = False
426
426
427 def _dirignore(self, f):
427 def _dirignore(self, f):
428 if f == '.':
428 if f == '.':
429 return False
429 return False
430 if self._ignore(f):
430 if self._ignore(f):
431 return True
431 return True
432 for p in _finddirs(f):
432 for p in _finddirs(f):
433 if self._ignore(p):
433 if self._ignore(p):
434 return True
434 return True
435 return False
435 return False
436
436
437 def walk(self, match, subrepos, unknown, ignored):
437 def walk(self, match, subrepos, unknown, ignored):
438 '''
438 '''
439 Walk recursively through the directory tree, finding all files
439 Walk recursively through the directory tree, finding all files
440 matched by match.
440 matched by match.
441
441
442 Return a dict mapping filename to stat-like object (either
442 Return a dict mapping filename to stat-like object (either
443 mercurial.osutil.stat instance or return value of os.stat()).
443 mercurial.osutil.stat instance or return value of os.stat()).
444 '''
444 '''
445
445
446 def fwarn(f, msg):
446 def fwarn(f, msg):
447 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
447 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
448 return False
448 return False
449
449
450 def badtype(mode):
450 def badtype(mode):
451 kind = _('unknown')
451 kind = _('unknown')
452 if stat.S_ISCHR(mode):
452 if stat.S_ISCHR(mode):
453 kind = _('character device')
453 kind = _('character device')
454 elif stat.S_ISBLK(mode):
454 elif stat.S_ISBLK(mode):
455 kind = _('block device')
455 kind = _('block device')
456 elif stat.S_ISFIFO(mode):
456 elif stat.S_ISFIFO(mode):
457 kind = _('fifo')
457 kind = _('fifo')
458 elif stat.S_ISSOCK(mode):
458 elif stat.S_ISSOCK(mode):
459 kind = _('socket')
459 kind = _('socket')
460 elif stat.S_ISDIR(mode):
460 elif stat.S_ISDIR(mode):
461 kind = _('directory')
461 kind = _('directory')
462 return _('unsupported file type (type is %s)') % kind
462 return _('unsupported file type (type is %s)') % kind
463
463
464 ignore = self._ignore
464 ignore = self._ignore
465 dirignore = self._dirignore
465 dirignore = self._dirignore
466 if ignored:
466 if ignored:
467 ignore = util.never
467 ignore = util.never
468 dirignore = util.never
468 dirignore = util.never
469 elif not unknown:
469 elif not unknown:
470 # if unknown and ignored are False, skip step 2
470 # if unknown and ignored are False, skip step 2
471 ignore = util.always
471 ignore = util.always
472 dirignore = util.always
472 dirignore = util.always
473
473
474 matchfn = match.matchfn
474 matchfn = match.matchfn
475 badfn = match.bad
475 badfn = match.bad
476 dmap = self._map
476 dmap = self._map
477 normpath = util.normpath
477 normpath = util.normpath
478 listdir = osutil.listdir
478 listdir = osutil.listdir
479 lstat = os.lstat
479 lstat = os.lstat
480 getkind = stat.S_IFMT
480 getkind = stat.S_IFMT
481 dirkind = stat.S_IFDIR
481 dirkind = stat.S_IFDIR
482 regkind = stat.S_IFREG
482 regkind = stat.S_IFREG
483 lnkkind = stat.S_IFLNK
483 lnkkind = stat.S_IFLNK
484 join = self._join
484 join = self._join
485 work = []
485 work = []
486 wadd = work.append
486 wadd = work.append
487
487
488 exact = skipstep3 = False
488 exact = skipstep3 = False
489 if matchfn == match.exact: # match.exact
489 if matchfn == match.exact: # match.exact
490 exact = True
490 exact = True
491 dirignore = util.always # skip step 2
491 dirignore = util.always # skip step 2
492 elif match.files() and not match.anypats(): # match.match, no patterns
492 elif match.files() and not match.anypats(): # match.match, no patterns
493 skipstep3 = True
493 skipstep3 = True
494
494
495 if self._checkcase:
495 if self._checkcase:
496 normalize = self._normalize
496 normalize = self._normalize
497 skipstep3 = False
497 skipstep3 = False
498 else:
498 else:
499 normalize = lambda x, y: x
499 normalize = lambda x, y: x
500
500
501 files = sorted(match.files())
501 files = sorted(match.files())
502 subrepos.sort()
502 subrepos.sort()
503 i, j = 0, 0
503 i, j = 0, 0
504 while i < len(files) and j < len(subrepos):
504 while i < len(files) and j < len(subrepos):
505 subpath = subrepos[j] + "/"
505 subpath = subrepos[j] + "/"
506 if files[i] < subpath:
506 if files[i] < subpath:
507 i += 1
507 i += 1
508 continue
508 continue
509 while files and files[i].startswith(subpath):
509 while i < len(files) and files[i].startswith(subpath):
510 del files[i]
510 del files[i]
511 j += 1
511 j += 1
512
512
513 if not files or '.' in files:
513 if not files or '.' in files:
514 files = ['']
514 files = ['']
515 results = dict.fromkeys(subrepos)
515 results = dict.fromkeys(subrepos)
516 results['.hg'] = None
516 results['.hg'] = None
517
517
518 # step 1: find all explicit files
518 # step 1: find all explicit files
519 for ff in files:
519 for ff in files:
520 nf = normalize(normpath(ff), False)
520 nf = normalize(normpath(ff), False)
521 if nf in results:
521 if nf in results:
522 continue
522 continue
523
523
524 try:
524 try:
525 st = lstat(join(nf))
525 st = lstat(join(nf))
526 kind = getkind(st.st_mode)
526 kind = getkind(st.st_mode)
527 if kind == dirkind:
527 if kind == dirkind:
528 skipstep3 = False
528 skipstep3 = False
529 if nf in dmap:
529 if nf in dmap:
530 #file deleted on disk but still in dirstate
530 #file deleted on disk but still in dirstate
531 results[nf] = None
531 results[nf] = None
532 match.dir(nf)
532 match.dir(nf)
533 if not dirignore(nf):
533 if not dirignore(nf):
534 wadd(nf)
534 wadd(nf)
535 elif kind == regkind or kind == lnkkind:
535 elif kind == regkind or kind == lnkkind:
536 results[nf] = st
536 results[nf] = st
537 else:
537 else:
538 badfn(ff, badtype(kind))
538 badfn(ff, badtype(kind))
539 if nf in dmap:
539 if nf in dmap:
540 results[nf] = None
540 results[nf] = None
541 except OSError, inst:
541 except OSError, inst:
542 if nf in dmap: # does it exactly match a file?
542 if nf in dmap: # does it exactly match a file?
543 results[nf] = None
543 results[nf] = None
544 else: # does it match a directory?
544 else: # does it match a directory?
545 prefix = nf + "/"
545 prefix = nf + "/"
546 for fn in dmap:
546 for fn in dmap:
547 if fn.startswith(prefix):
547 if fn.startswith(prefix):
548 match.dir(nf)
548 match.dir(nf)
549 skipstep3 = False
549 skipstep3 = False
550 break
550 break
551 else:
551 else:
552 badfn(ff, inst.strerror)
552 badfn(ff, inst.strerror)
553
553
554 # step 2: visit subdirectories
554 # step 2: visit subdirectories
555 while work:
555 while work:
556 nd = work.pop()
556 nd = work.pop()
557 skip = None
557 skip = None
558 if nd == '.':
558 if nd == '.':
559 nd = ''
559 nd = ''
560 else:
560 else:
561 skip = '.hg'
561 skip = '.hg'
562 try:
562 try:
563 entries = listdir(join(nd), stat=True, skip=skip)
563 entries = listdir(join(nd), stat=True, skip=skip)
564 except OSError, inst:
564 except OSError, inst:
565 if inst.errno == errno.EACCES:
565 if inst.errno == errno.EACCES:
566 fwarn(nd, inst.strerror)
566 fwarn(nd, inst.strerror)
567 continue
567 continue
568 raise
568 raise
569 for f, kind, st in entries:
569 for f, kind, st in entries:
570 nf = normalize(nd and (nd + "/" + f) or f, True)
570 nf = normalize(nd and (nd + "/" + f) or f, True)
571 if nf not in results:
571 if nf not in results:
572 if kind == dirkind:
572 if kind == dirkind:
573 if not ignore(nf):
573 if not ignore(nf):
574 match.dir(nf)
574 match.dir(nf)
575 wadd(nf)
575 wadd(nf)
576 if nf in dmap and matchfn(nf):
576 if nf in dmap and matchfn(nf):
577 results[nf] = None
577 results[nf] = None
578 elif kind == regkind or kind == lnkkind:
578 elif kind == regkind or kind == lnkkind:
579 if nf in dmap:
579 if nf in dmap:
580 if matchfn(nf):
580 if matchfn(nf):
581 results[nf] = st
581 results[nf] = st
582 elif matchfn(nf) and not ignore(nf):
582 elif matchfn(nf) and not ignore(nf):
583 results[nf] = st
583 results[nf] = st
584 elif nf in dmap and matchfn(nf):
584 elif nf in dmap and matchfn(nf):
585 results[nf] = None
585 results[nf] = None
586
586
587 # step 3: report unseen items in the dmap hash
587 # step 3: report unseen items in the dmap hash
588 if not skipstep3 and not exact:
588 if not skipstep3 and not exact:
589 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
589 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
590 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
590 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
591 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
591 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
592 st = None
592 st = None
593 results[nf] = st
593 results[nf] = st
594 for s in subrepos:
594 for s in subrepos:
595 del results[s]
595 del results[s]
596 del results['.hg']
596 del results['.hg']
597 return results
597 return results
598
598
599 def status(self, match, subrepos, ignored, clean, unknown):
599 def status(self, match, subrepos, ignored, clean, unknown):
600 '''Determine the status of the working copy relative to the
600 '''Determine the status of the working copy relative to the
601 dirstate and return a tuple of lists (unsure, modified, added,
601 dirstate and return a tuple of lists (unsure, modified, added,
602 removed, deleted, unknown, ignored, clean), where:
602 removed, deleted, unknown, ignored, clean), where:
603
603
604 unsure:
604 unsure:
605 files that might have been modified since the dirstate was
605 files that might have been modified since the dirstate was
606 written, but need to be read to be sure (size is the same
606 written, but need to be read to be sure (size is the same
607 but mtime differs)
607 but mtime differs)
608 modified:
608 modified:
609 files that have definitely been modified since the dirstate
609 files that have definitely been modified since the dirstate
610 was written (different size or mode)
610 was written (different size or mode)
611 added:
611 added:
612 files that have been explicitly added with hg add
612 files that have been explicitly added with hg add
613 removed:
613 removed:
614 files that have been explicitly removed with hg remove
614 files that have been explicitly removed with hg remove
615 deleted:
615 deleted:
616 files that have been deleted through other means ("missing")
616 files that have been deleted through other means ("missing")
617 unknown:
617 unknown:
618 files not in the dirstate that are not ignored
618 files not in the dirstate that are not ignored
619 ignored:
619 ignored:
620 files not in the dirstate that are ignored
620 files not in the dirstate that are ignored
621 (by _dirignore())
621 (by _dirignore())
622 clean:
622 clean:
623 files that have definitely not been modified since the
623 files that have definitely not been modified since the
624 dirstate was written
624 dirstate was written
625 '''
625 '''
626 listignored, listclean, listunknown = ignored, clean, unknown
626 listignored, listclean, listunknown = ignored, clean, unknown
627 lookup, modified, added, unknown, ignored = [], [], [], [], []
627 lookup, modified, added, unknown, ignored = [], [], [], [], []
628 removed, deleted, clean = [], [], []
628 removed, deleted, clean = [], [], []
629
629
630 dmap = self._map
630 dmap = self._map
631 ladd = lookup.append # aka "unsure"
631 ladd = lookup.append # aka "unsure"
632 madd = modified.append
632 madd = modified.append
633 aadd = added.append
633 aadd = added.append
634 uadd = unknown.append
634 uadd = unknown.append
635 iadd = ignored.append
635 iadd = ignored.append
636 radd = removed.append
636 radd = removed.append
637 dadd = deleted.append
637 dadd = deleted.append
638 cadd = clean.append
638 cadd = clean.append
639
639
640 lnkkind = stat.S_IFLNK
640 lnkkind = stat.S_IFLNK
641
641
642 for fn, st in self.walk(match, subrepos, listunknown,
642 for fn, st in self.walk(match, subrepos, listunknown,
643 listignored).iteritems():
643 listignored).iteritems():
644 if fn not in dmap:
644 if fn not in dmap:
645 if (listignored or match.exact(fn)) and self._dirignore(fn):
645 if (listignored or match.exact(fn)) and self._dirignore(fn):
646 if listignored:
646 if listignored:
647 iadd(fn)
647 iadd(fn)
648 elif listunknown:
648 elif listunknown:
649 uadd(fn)
649 uadd(fn)
650 continue
650 continue
651
651
652 state, mode, size, time = dmap[fn]
652 state, mode, size, time = dmap[fn]
653
653
654 if not st and state in "nma":
654 if not st and state in "nma":
655 dadd(fn)
655 dadd(fn)
656 elif state == 'n':
656 elif state == 'n':
657 # The "mode & lnkkind != lnkkind or self._checklink"
657 # The "mode & lnkkind != lnkkind or self._checklink"
658 # lines are an expansion of "islink => checklink"
658 # lines are an expansion of "islink => checklink"
659 # where islink means "is this a link?" and checklink
659 # where islink means "is this a link?" and checklink
660 # means "can we check links?".
660 # means "can we check links?".
661 if (size >= 0 and
661 if (size >= 0 and
662 (size != st.st_size
662 (size != st.st_size
663 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
663 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
664 and (mode & lnkkind != lnkkind or self._checklink)
664 and (mode & lnkkind != lnkkind or self._checklink)
665 or size == -2 # other parent
665 or size == -2 # other parent
666 or fn in self._copymap):
666 or fn in self._copymap):
667 madd(fn)
667 madd(fn)
668 elif (time != int(st.st_mtime)
668 elif (time != int(st.st_mtime)
669 and (mode & lnkkind != lnkkind or self._checklink)):
669 and (mode & lnkkind != lnkkind or self._checklink)):
670 ladd(fn)
670 ladd(fn)
671 elif listclean:
671 elif listclean:
672 cadd(fn)
672 cadd(fn)
673 elif state == 'm':
673 elif state == 'm':
674 madd(fn)
674 madd(fn)
675 elif state == 'a':
675 elif state == 'a':
676 aadd(fn)
676 aadd(fn)
677 elif state == 'r':
677 elif state == 'r':
678 radd(fn)
678 radd(fn)
679
679
680 return (lookup, modified, added, removed, deleted, unknown, ignored,
680 return (lookup, modified, added, removed, deleted, unknown, ignored,
681 clean)
681 clean)
@@ -1,348 +1,360 b''
1 Create test repository:
1 Create test repository:
2
2
3 $ hg init repo
3 $ hg init repo
4 $ cd repo
4 $ cd repo
5 $ echo x1 > x.txt
5 $ echo x1 > x.txt
6
6
7 $ hg init foo
7 $ hg init foo
8 $ cd foo
8 $ cd foo
9 $ echo y1 > y.txt
9 $ echo y1 > y.txt
10
10
11 $ hg init bar
11 $ hg init bar
12 $ cd bar
12 $ cd bar
13 $ echo z1 > z.txt
13 $ echo z1 > z.txt
14
14
15 $ cd ..
15 $ cd ..
16 $ echo 'bar = bar' > .hgsub
16 $ echo 'bar = bar' > .hgsub
17
17
18 $ cd ..
18 $ cd ..
19 $ echo 'foo = foo' > .hgsub
19 $ echo 'foo = foo' > .hgsub
20
20
21 Add files --- .hgsub files must go first to trigger subrepos:
21 Add files --- .hgsub files must go first to trigger subrepos:
22
22
23 $ hg add -S .hgsub
23 $ hg add -S .hgsub
24 $ hg add -S foo/.hgsub
24 $ hg add -S foo/.hgsub
25 $ hg add -S foo/bar
25 $ hg add -S foo/bar
26 adding foo/bar/z.txt
26 adding foo/bar/z.txt
27 $ hg add -S
27 $ hg add -S
28 adding x.txt
28 adding x.txt
29 adding foo/y.txt
29 adding foo/y.txt
30
30
31 Test recursive status without committing anything:
31 Test recursive status without committing anything:
32
32
33 $ hg status -S
33 $ hg status -S
34 A .hgsub
34 A .hgsub
35 A foo/.hgsub
35 A foo/.hgsub
36 A foo/bar/z.txt
36 A foo/bar/z.txt
37 A foo/y.txt
37 A foo/y.txt
38 A x.txt
38 A x.txt
39
39
40 Test recursive diff without committing anything:
40 Test recursive diff without committing anything:
41
41
42 $ hg diff --nodates -S foo
42 $ hg diff --nodates -S foo
43 diff -r 000000000000 foo/.hgsub
43 diff -r 000000000000 foo/.hgsub
44 --- /dev/null
44 --- /dev/null
45 +++ b/foo/.hgsub
45 +++ b/foo/.hgsub
46 @@ -0,0 +1,1 @@
46 @@ -0,0 +1,1 @@
47 +bar = bar
47 +bar = bar
48 diff -r 000000000000 foo/y.txt
48 diff -r 000000000000 foo/y.txt
49 --- /dev/null
49 --- /dev/null
50 +++ b/foo/y.txt
50 +++ b/foo/y.txt
51 @@ -0,0 +1,1 @@
51 @@ -0,0 +1,1 @@
52 +y1
52 +y1
53 diff -r 000000000000 foo/bar/z.txt
53 diff -r 000000000000 foo/bar/z.txt
54 --- /dev/null
54 --- /dev/null
55 +++ b/foo/bar/z.txt
55 +++ b/foo/bar/z.txt
56 @@ -0,0 +1,1 @@
56 @@ -0,0 +1,1 @@
57 +z1
57 +z1
58
58
59 Commits:
59 Commits:
60
60
61 $ hg commit -m 0-0-0
61 $ hg commit -m 0-0-0
62 committing subrepository foo
62 committing subrepository foo
63 committing subrepository foo/bar
63 committing subrepository foo/bar
64
64
65 $ cd foo
65 $ cd foo
66 $ echo y2 >> y.txt
66 $ echo y2 >> y.txt
67 $ hg commit -m 0-1-0
67 $ hg commit -m 0-1-0
68
68
69 $ cd bar
69 $ cd bar
70 $ echo z2 >> z.txt
70 $ echo z2 >> z.txt
71 $ hg commit -m 0-1-1
71 $ hg commit -m 0-1-1
72
72
73 $ cd ..
73 $ cd ..
74 $ hg commit -m 0-2-1
74 $ hg commit -m 0-2-1
75 committing subrepository bar
75 committing subrepository bar
76
76
77 $ cd ..
77 $ cd ..
78 $ hg commit -m 1-2-1
78 $ hg commit -m 1-2-1
79 committing subrepository foo
79 committing subrepository foo
80
80
81 Change working directory:
81 Change working directory:
82
82
83 $ echo y3 >> foo/y.txt
83 $ echo y3 >> foo/y.txt
84 $ echo z3 >> foo/bar/z.txt
84 $ echo z3 >> foo/bar/z.txt
85 $ hg status -S
85 $ hg status -S
86 M foo/bar/z.txt
86 M foo/bar/z.txt
87 M foo/y.txt
87 M foo/y.txt
88 $ hg diff --nodates -S
88 $ hg diff --nodates -S
89 diff -r d254738c5f5e foo/y.txt
89 diff -r d254738c5f5e foo/y.txt
90 --- a/foo/y.txt
90 --- a/foo/y.txt
91 +++ b/foo/y.txt
91 +++ b/foo/y.txt
92 @@ -1,2 +1,3 @@
92 @@ -1,2 +1,3 @@
93 y1
93 y1
94 y2
94 y2
95 +y3
95 +y3
96 diff -r 9647f22de499 foo/bar/z.txt
96 diff -r 9647f22de499 foo/bar/z.txt
97 --- a/foo/bar/z.txt
97 --- a/foo/bar/z.txt
98 +++ b/foo/bar/z.txt
98 +++ b/foo/bar/z.txt
99 @@ -1,2 +1,3 @@
99 @@ -1,2 +1,3 @@
100 z1
100 z1
101 z2
101 z2
102 +z3
102 +z3
103
103
104 Status call crossing repository boundaries:
104 Status call crossing repository boundaries:
105
105
106 $ hg status -S foo/bar/z.txt
106 $ hg status -S foo/bar/z.txt
107 M foo/bar/z.txt
107 M foo/bar/z.txt
108 $ hg status -S -I 'foo/?.txt'
108 $ hg status -S -I 'foo/?.txt'
109 M foo/y.txt
109 M foo/y.txt
110 $ hg status -S -I '**/?.txt'
110 $ hg status -S -I '**/?.txt'
111 M foo/bar/z.txt
111 M foo/bar/z.txt
112 M foo/y.txt
112 M foo/y.txt
113 $ hg diff --nodates -S -I '**/?.txt'
113 $ hg diff --nodates -S -I '**/?.txt'
114 diff -r d254738c5f5e foo/y.txt
114 diff -r d254738c5f5e foo/y.txt
115 --- a/foo/y.txt
115 --- a/foo/y.txt
116 +++ b/foo/y.txt
116 +++ b/foo/y.txt
117 @@ -1,2 +1,3 @@
117 @@ -1,2 +1,3 @@
118 y1
118 y1
119 y2
119 y2
120 +y3
120 +y3
121 diff -r 9647f22de499 foo/bar/z.txt
121 diff -r 9647f22de499 foo/bar/z.txt
122 --- a/foo/bar/z.txt
122 --- a/foo/bar/z.txt
123 +++ b/foo/bar/z.txt
123 +++ b/foo/bar/z.txt
124 @@ -1,2 +1,3 @@
124 @@ -1,2 +1,3 @@
125 z1
125 z1
126 z2
126 z2
127 +z3
127 +z3
128
128
129 Status from within a subdirectory:
129 Status from within a subdirectory:
130
130
131 $ mkdir dir
131 $ mkdir dir
132 $ cd dir
132 $ cd dir
133 $ echo a1 > a.txt
133 $ echo a1 > a.txt
134 $ hg status -S
134 $ hg status -S
135 M foo/bar/z.txt
135 M foo/bar/z.txt
136 M foo/y.txt
136 M foo/y.txt
137 ? dir/a.txt
137 ? dir/a.txt
138 $ hg diff --nodates -S
138 $ hg diff --nodates -S
139 diff -r d254738c5f5e foo/y.txt
139 diff -r d254738c5f5e foo/y.txt
140 --- a/foo/y.txt
140 --- a/foo/y.txt
141 +++ b/foo/y.txt
141 +++ b/foo/y.txt
142 @@ -1,2 +1,3 @@
142 @@ -1,2 +1,3 @@
143 y1
143 y1
144 y2
144 y2
145 +y3
145 +y3
146 diff -r 9647f22de499 foo/bar/z.txt
146 diff -r 9647f22de499 foo/bar/z.txt
147 --- a/foo/bar/z.txt
147 --- a/foo/bar/z.txt
148 +++ b/foo/bar/z.txt
148 +++ b/foo/bar/z.txt
149 @@ -1,2 +1,3 @@
149 @@ -1,2 +1,3 @@
150 z1
150 z1
151 z2
151 z2
152 +z3
152 +z3
153
153
154 Status with relative path:
154 Status with relative path:
155
155
156 $ hg status -S ..
156 $ hg status -S ..
157 M ../foo/bar/z.txt
157 M ../foo/bar/z.txt
158 M ../foo/y.txt
158 M ../foo/y.txt
159 ? a.txt
159 ? a.txt
160 $ hg diff --nodates -S ..
160 $ hg diff --nodates -S ..
161 diff -r d254738c5f5e foo/y.txt
161 diff -r d254738c5f5e foo/y.txt
162 --- a/foo/y.txt
162 --- a/foo/y.txt
163 +++ b/foo/y.txt
163 +++ b/foo/y.txt
164 @@ -1,2 +1,3 @@
164 @@ -1,2 +1,3 @@
165 y1
165 y1
166 y2
166 y2
167 +y3
167 +y3
168 diff -r 9647f22de499 foo/bar/z.txt
168 diff -r 9647f22de499 foo/bar/z.txt
169 --- a/foo/bar/z.txt
169 --- a/foo/bar/z.txt
170 +++ b/foo/bar/z.txt
170 +++ b/foo/bar/z.txt
171 @@ -1,2 +1,3 @@
171 @@ -1,2 +1,3 @@
172 z1
172 z1
173 z2
173 z2
174 +z3
174 +z3
175 $ cd ..
175 $ cd ..
176
176
177 Cleanup and final commit:
177 Cleanup and final commit:
178
178
179 $ rm -r dir
179 $ rm -r dir
180 $ hg commit -m 2-3-2
180 $ hg commit -m 2-3-2
181 committing subrepository foo
181 committing subrepository foo
182 committing subrepository foo/bar
182 committing subrepository foo/bar
183
183
184 Log with the relationships between repo and its subrepo:
184 Log with the relationships between repo and its subrepo:
185
185
186 $ hg log --template '{rev}:{node|short} {desc}\n'
186 $ hg log --template '{rev}:{node|short} {desc}\n'
187 2:1326fa26d0c0 2-3-2
187 2:1326fa26d0c0 2-3-2
188 1:4b3c9ff4f66b 1-2-1
188 1:4b3c9ff4f66b 1-2-1
189 0:23376cbba0d8 0-0-0
189 0:23376cbba0d8 0-0-0
190
190
191 $ hg -R foo log --template '{rev}:{node|short} {desc}\n'
191 $ hg -R foo log --template '{rev}:{node|short} {desc}\n'
192 3:65903cebad86 2-3-2
192 3:65903cebad86 2-3-2
193 2:d254738c5f5e 0-2-1
193 2:d254738c5f5e 0-2-1
194 1:8629ce7dcc39 0-1-0
194 1:8629ce7dcc39 0-1-0
195 0:af048e97ade2 0-0-0
195 0:af048e97ade2 0-0-0
196
196
197 $ hg -R foo/bar log --template '{rev}:{node|short} {desc}\n'
197 $ hg -R foo/bar log --template '{rev}:{node|short} {desc}\n'
198 2:31ecbdafd357 2-3-2
198 2:31ecbdafd357 2-3-2
199 1:9647f22de499 0-1-1
199 1:9647f22de499 0-1-1
200 0:4904098473f9 0-0-0
200 0:4904098473f9 0-0-0
201
201
202 Status between revisions:
202 Status between revisions:
203
203
204 $ hg status -S
204 $ hg status -S
205 $ hg status -S --rev 0:1
205 $ hg status -S --rev 0:1
206 M .hgsubstate
206 M .hgsubstate
207 M foo/.hgsubstate
207 M foo/.hgsubstate
208 M foo/bar/z.txt
208 M foo/bar/z.txt
209 M foo/y.txt
209 M foo/y.txt
210 $ hg diff --nodates -S -I '**/?.txt' --rev 0:1
210 $ hg diff --nodates -S -I '**/?.txt' --rev 0:1
211 diff -r af048e97ade2 -r d254738c5f5e foo/y.txt
211 diff -r af048e97ade2 -r d254738c5f5e foo/y.txt
212 --- a/foo/y.txt
212 --- a/foo/y.txt
213 +++ b/foo/y.txt
213 +++ b/foo/y.txt
214 @@ -1,1 +1,2 @@
214 @@ -1,1 +1,2 @@
215 y1
215 y1
216 +y2
216 +y2
217 diff -r 4904098473f9 -r 9647f22de499 foo/bar/z.txt
217 diff -r 4904098473f9 -r 9647f22de499 foo/bar/z.txt
218 --- a/foo/bar/z.txt
218 --- a/foo/bar/z.txt
219 +++ b/foo/bar/z.txt
219 +++ b/foo/bar/z.txt
220 @@ -1,1 +1,2 @@
220 @@ -1,1 +1,2 @@
221 z1
221 z1
222 +z2
222 +z2
223
223
224 Test archiving to a directory tree:
224 Test archiving to a directory tree:
225
225
226 $ hg archive --subrepos ../archive
226 $ hg archive --subrepos ../archive
227 $ find ../archive | sort
227 $ find ../archive | sort
228 ../archive
228 ../archive
229 ../archive/.hg_archival.txt
229 ../archive/.hg_archival.txt
230 ../archive/.hgsub
230 ../archive/.hgsub
231 ../archive/.hgsubstate
231 ../archive/.hgsubstate
232 ../archive/foo
232 ../archive/foo
233 ../archive/foo/.hgsub
233 ../archive/foo/.hgsub
234 ../archive/foo/.hgsubstate
234 ../archive/foo/.hgsubstate
235 ../archive/foo/bar
235 ../archive/foo/bar
236 ../archive/foo/bar/z.txt
236 ../archive/foo/bar/z.txt
237 ../archive/foo/y.txt
237 ../archive/foo/y.txt
238 ../archive/x.txt
238 ../archive/x.txt
239
239
240 Test archiving to zip file (unzip output is unstable):
240 Test archiving to zip file (unzip output is unstable):
241
241
242 $ hg archive --subrepos ../archive.zip
242 $ hg archive --subrepos ../archive.zip
243
243
244 Clone and test outgoing:
244 Clone and test outgoing:
245
245
246 $ cd ..
246 $ cd ..
247 $ hg clone repo repo2
247 $ hg clone repo repo2
248 updating to branch default
248 updating to branch default
249 pulling subrepo foo from $TESTTMP/repo/foo
249 pulling subrepo foo from $TESTTMP/repo/foo
250 requesting all changes
250 requesting all changes
251 adding changesets
251 adding changesets
252 adding manifests
252 adding manifests
253 adding file changes
253 adding file changes
254 added 4 changesets with 7 changes to 3 files
254 added 4 changesets with 7 changes to 3 files
255 pulling subrepo foo/bar from $TESTTMP/repo/foo/bar
255 pulling subrepo foo/bar from $TESTTMP/repo/foo/bar
256 requesting all changes
256 requesting all changes
257 adding changesets
257 adding changesets
258 adding manifests
258 adding manifests
259 adding file changes
259 adding file changes
260 added 3 changesets with 3 changes to 1 files
260 added 3 changesets with 3 changes to 1 files
261 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
261 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
262 $ cd repo2
262 $ cd repo2
263 $ hg outgoing -S
263 $ hg outgoing -S
264 comparing with $TESTTMP/repo
264 comparing with $TESTTMP/repo
265 searching for changes
265 searching for changes
266 no changes found
266 no changes found
267 comparing with $TESTTMP/repo/foo
267 comparing with $TESTTMP/repo/foo
268 searching for changes
268 searching for changes
269 no changes found
269 no changes found
270 comparing with $TESTTMP/repo/foo/bar
270 comparing with $TESTTMP/repo/foo/bar
271 searching for changes
271 searching for changes
272 no changes found
272 no changes found
273 [1]
273 [1]
274
274
275 Make nested change:
275 Make nested change:
276
276
277 $ echo y4 >> foo/y.txt
277 $ echo y4 >> foo/y.txt
278 $ hg diff --nodates -S
278 $ hg diff --nodates -S
279 diff -r 65903cebad86 foo/y.txt
279 diff -r 65903cebad86 foo/y.txt
280 --- a/foo/y.txt
280 --- a/foo/y.txt
281 +++ b/foo/y.txt
281 +++ b/foo/y.txt
282 @@ -1,3 +1,4 @@
282 @@ -1,3 +1,4 @@
283 y1
283 y1
284 y2
284 y2
285 y3
285 y3
286 +y4
286 +y4
287 $ hg commit -m 3-4-2
287 $ hg commit -m 3-4-2
288 committing subrepository foo
288 committing subrepository foo
289 $ hg outgoing -S
289 $ hg outgoing -S
290 comparing with $TESTTMP/repo
290 comparing with $TESTTMP/repo
291 searching for changes
291 searching for changes
292 changeset: 3:2655b8ecc4ee
292 changeset: 3:2655b8ecc4ee
293 tag: tip
293 tag: tip
294 user: test
294 user: test
295 date: Thu Jan 01 00:00:00 1970 +0000
295 date: Thu Jan 01 00:00:00 1970 +0000
296 summary: 3-4-2
296 summary: 3-4-2
297
297
298 comparing with $TESTTMP/repo/foo
298 comparing with $TESTTMP/repo/foo
299 searching for changes
299 searching for changes
300 changeset: 4:e96193d6cb36
300 changeset: 4:e96193d6cb36
301 tag: tip
301 tag: tip
302 user: test
302 user: test
303 date: Thu Jan 01 00:00:00 1970 +0000
303 date: Thu Jan 01 00:00:00 1970 +0000
304 summary: 3-4-2
304 summary: 3-4-2
305
305
306 comparing with $TESTTMP/repo/foo/bar
306 comparing with $TESTTMP/repo/foo/bar
307 searching for changes
307 searching for changes
308 no changes found
308 no changes found
309
309
310
310
311 Switch to original repo and setup default path:
311 Switch to original repo and setup default path:
312
312
313 $ cd ../repo
313 $ cd ../repo
314 $ echo '[paths]' >> .hg/hgrc
314 $ echo '[paths]' >> .hg/hgrc
315 $ echo 'default = ../repo2' >> .hg/hgrc
315 $ echo 'default = ../repo2' >> .hg/hgrc
316
316
317 Test incoming:
317 Test incoming:
318
318
319 $ hg incoming -S
319 $ hg incoming -S
320 comparing with $TESTTMP/repo2
320 comparing with $TESTTMP/repo2
321 searching for changes
321 searching for changes
322 changeset: 3:2655b8ecc4ee
322 changeset: 3:2655b8ecc4ee
323 tag: tip
323 tag: tip
324 user: test
324 user: test
325 date: Thu Jan 01 00:00:00 1970 +0000
325 date: Thu Jan 01 00:00:00 1970 +0000
326 summary: 3-4-2
326 summary: 3-4-2
327
327
328 comparing with $TESTTMP/repo2/foo
328 comparing with $TESTTMP/repo2/foo
329 searching for changes
329 searching for changes
330 changeset: 4:e96193d6cb36
330 changeset: 4:e96193d6cb36
331 tag: tip
331 tag: tip
332 user: test
332 user: test
333 date: Thu Jan 01 00:00:00 1970 +0000
333 date: Thu Jan 01 00:00:00 1970 +0000
334 summary: 3-4-2
334 summary: 3-4-2
335
335
336 comparing with $TESTTMP/repo2/foo/bar
336 comparing with $TESTTMP/repo2/foo/bar
337 searching for changes
337 searching for changes
338 no changes found
338 no changes found
339
339
340 $ hg incoming -S --bundle incoming.hg
340 $ hg incoming -S --bundle incoming.hg
341 abort: cannot combine --bundle and --subrepos
341 abort: cannot combine --bundle and --subrepos
342 [255]
342 [255]
343
343
344 Test missing subrepo:
344 Test missing subrepo:
345
345
346 $ rm -r foo
346 $ rm -r foo
347 $ hg status -S
347 $ hg status -S
348 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
348 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
349
350 Issue2619: IndexError: list index out of range on hg add with subrepos
351 The subrepo must sorts after the explicit filename.
352
353 $ cd ..
354 $ hg init test
355 $ cd test
356 $ hg init x
357 $ echo "x = x" >> .hgsub
358 $ hg add .hgsub
359 $ touch a x/a
360 $ hg add a x/a
General Comments 0
You need to be logged in to leave comments. Login now