##// END OF EJS Templates
dirstate: expose a sparse matcher on dirstate (API)...
Gregory Szorc -
r33373:fb320398 default
parent child Browse files
Show More
@@ -1,670 +1,672 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''largefiles utility code: must not import other modules in this package.'''
9 '''largefiles utility code: must not import other modules in this package.'''
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import copy
12 import copy
13 import hashlib
13 import hashlib
14 import os
14 import os
15 import platform
15 import platform
16 import stat
16 import stat
17
17
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19
19
20 from mercurial import (
20 from mercurial import (
21 dirstate,
21 dirstate,
22 encoding,
22 encoding,
23 error,
23 error,
24 httpconnection,
24 httpconnection,
25 match as matchmod,
25 match as matchmod,
26 node,
26 node,
27 pycompat,
27 pycompat,
28 scmutil,
28 scmutil,
29 sparse,
29 util,
30 util,
30 vfs as vfsmod,
31 vfs as vfsmod,
31 )
32 )
32
33
33 shortname = '.hglf'
34 shortname = '.hglf'
34 shortnameslash = shortname + '/'
35 shortnameslash = shortname + '/'
35 longname = 'largefiles'
36 longname = 'largefiles'
36
37
37 # -- Private worker functions ------------------------------------------
38 # -- Private worker functions ------------------------------------------
38
39
39 def getminsize(ui, assumelfiles, opt, default=10):
40 def getminsize(ui, assumelfiles, opt, default=10):
40 lfsize = opt
41 lfsize = opt
41 if not lfsize and assumelfiles:
42 if not lfsize and assumelfiles:
42 lfsize = ui.config(longname, 'minsize', default=default)
43 lfsize = ui.config(longname, 'minsize', default=default)
43 if lfsize:
44 if lfsize:
44 try:
45 try:
45 lfsize = float(lfsize)
46 lfsize = float(lfsize)
46 except ValueError:
47 except ValueError:
47 raise error.Abort(_('largefiles: size must be number (not %s)\n')
48 raise error.Abort(_('largefiles: size must be number (not %s)\n')
48 % lfsize)
49 % lfsize)
49 if lfsize is None:
50 if lfsize is None:
50 raise error.Abort(_('minimum size for largefiles must be specified'))
51 raise error.Abort(_('minimum size for largefiles must be specified'))
51 return lfsize
52 return lfsize
52
53
53 def link(src, dest):
54 def link(src, dest):
54 """Try to create hardlink - if that fails, efficiently make a copy."""
55 """Try to create hardlink - if that fails, efficiently make a copy."""
55 util.makedirs(os.path.dirname(dest))
56 util.makedirs(os.path.dirname(dest))
56 try:
57 try:
57 util.oslink(src, dest)
58 util.oslink(src, dest)
58 except OSError:
59 except OSError:
59 # if hardlinks fail, fallback on atomic copy
60 # if hardlinks fail, fallback on atomic copy
60 with open(src, 'rb') as srcf:
61 with open(src, 'rb') as srcf:
61 with util.atomictempfile(dest) as dstf:
62 with util.atomictempfile(dest) as dstf:
62 for chunk in util.filechunkiter(srcf):
63 for chunk in util.filechunkiter(srcf):
63 dstf.write(chunk)
64 dstf.write(chunk)
64 os.chmod(dest, os.stat(src).st_mode)
65 os.chmod(dest, os.stat(src).st_mode)
65
66
66 def usercachepath(ui, hash):
67 def usercachepath(ui, hash):
67 '''Return the correct location in the "global" largefiles cache for a file
68 '''Return the correct location in the "global" largefiles cache for a file
68 with the given hash.
69 with the given hash.
69 This cache is used for sharing of largefiles across repositories - both
70 This cache is used for sharing of largefiles across repositories - both
70 to preserve download bandwidth and storage space.'''
71 to preserve download bandwidth and storage space.'''
71 return os.path.join(_usercachedir(ui), hash)
72 return os.path.join(_usercachedir(ui), hash)
72
73
73 def _usercachedir(ui):
74 def _usercachedir(ui):
74 '''Return the location of the "global" largefiles cache.'''
75 '''Return the location of the "global" largefiles cache.'''
75 path = ui.configpath(longname, 'usercache', None)
76 path = ui.configpath(longname, 'usercache', None)
76 if path:
77 if path:
77 return path
78 return path
78 if pycompat.osname == 'nt':
79 if pycompat.osname == 'nt':
79 appdata = encoding.environ.get('LOCALAPPDATA',\
80 appdata = encoding.environ.get('LOCALAPPDATA',\
80 encoding.environ.get('APPDATA'))
81 encoding.environ.get('APPDATA'))
81 if appdata:
82 if appdata:
82 return os.path.join(appdata, longname)
83 return os.path.join(appdata, longname)
83 elif platform.system() == 'Darwin':
84 elif platform.system() == 'Darwin':
84 home = encoding.environ.get('HOME')
85 home = encoding.environ.get('HOME')
85 if home:
86 if home:
86 return os.path.join(home, 'Library', 'Caches', longname)
87 return os.path.join(home, 'Library', 'Caches', longname)
87 elif pycompat.osname == 'posix':
88 elif pycompat.osname == 'posix':
88 path = encoding.environ.get('XDG_CACHE_HOME')
89 path = encoding.environ.get('XDG_CACHE_HOME')
89 if path:
90 if path:
90 return os.path.join(path, longname)
91 return os.path.join(path, longname)
91 home = encoding.environ.get('HOME')
92 home = encoding.environ.get('HOME')
92 if home:
93 if home:
93 return os.path.join(home, '.cache', longname)
94 return os.path.join(home, '.cache', longname)
94 else:
95 else:
95 raise error.Abort(_('unknown operating system: %s\n')
96 raise error.Abort(_('unknown operating system: %s\n')
96 % pycompat.osname)
97 % pycompat.osname)
97 raise error.Abort(_('unknown %s usercache location') % longname)
98 raise error.Abort(_('unknown %s usercache location') % longname)
98
99
99 def inusercache(ui, hash):
100 def inusercache(ui, hash):
100 path = usercachepath(ui, hash)
101 path = usercachepath(ui, hash)
101 return os.path.exists(path)
102 return os.path.exists(path)
102
103
103 def findfile(repo, hash):
104 def findfile(repo, hash):
104 '''Return store path of the largefile with the specified hash.
105 '''Return store path of the largefile with the specified hash.
105 As a side effect, the file might be linked from user cache.
106 As a side effect, the file might be linked from user cache.
106 Return None if the file can't be found locally.'''
107 Return None if the file can't be found locally.'''
107 path, exists = findstorepath(repo, hash)
108 path, exists = findstorepath(repo, hash)
108 if exists:
109 if exists:
109 repo.ui.note(_('found %s in store\n') % hash)
110 repo.ui.note(_('found %s in store\n') % hash)
110 return path
111 return path
111 elif inusercache(repo.ui, hash):
112 elif inusercache(repo.ui, hash):
112 repo.ui.note(_('found %s in system cache\n') % hash)
113 repo.ui.note(_('found %s in system cache\n') % hash)
113 path = storepath(repo, hash)
114 path = storepath(repo, hash)
114 link(usercachepath(repo.ui, hash), path)
115 link(usercachepath(repo.ui, hash), path)
115 return path
116 return path
116 return None
117 return None
117
118
118 class largefilesdirstate(dirstate.dirstate):
119 class largefilesdirstate(dirstate.dirstate):
119 def __getitem__(self, key):
120 def __getitem__(self, key):
120 return super(largefilesdirstate, self).__getitem__(unixpath(key))
121 return super(largefilesdirstate, self).__getitem__(unixpath(key))
121 def normal(self, f):
122 def normal(self, f):
122 return super(largefilesdirstate, self).normal(unixpath(f))
123 return super(largefilesdirstate, self).normal(unixpath(f))
123 def remove(self, f):
124 def remove(self, f):
124 return super(largefilesdirstate, self).remove(unixpath(f))
125 return super(largefilesdirstate, self).remove(unixpath(f))
125 def add(self, f):
126 def add(self, f):
126 return super(largefilesdirstate, self).add(unixpath(f))
127 return super(largefilesdirstate, self).add(unixpath(f))
127 def drop(self, f):
128 def drop(self, f):
128 return super(largefilesdirstate, self).drop(unixpath(f))
129 return super(largefilesdirstate, self).drop(unixpath(f))
129 def forget(self, f):
130 def forget(self, f):
130 return super(largefilesdirstate, self).forget(unixpath(f))
131 return super(largefilesdirstate, self).forget(unixpath(f))
131 def normallookup(self, f):
132 def normallookup(self, f):
132 return super(largefilesdirstate, self).normallookup(unixpath(f))
133 return super(largefilesdirstate, self).normallookup(unixpath(f))
133 def _ignore(self, f):
134 def _ignore(self, f):
134 return False
135 return False
135 def write(self, tr=False):
136 def write(self, tr=False):
136 # (1) disable PENDING mode always
137 # (1) disable PENDING mode always
137 # (lfdirstate isn't yet managed as a part of the transaction)
138 # (lfdirstate isn't yet managed as a part of the transaction)
138 # (2) avoid develwarn 'use dirstate.write with ....'
139 # (2) avoid develwarn 'use dirstate.write with ....'
139 super(largefilesdirstate, self).write(None)
140 super(largefilesdirstate, self).write(None)
140
141
141 def openlfdirstate(ui, repo, create=True):
142 def openlfdirstate(ui, repo, create=True):
142 '''
143 '''
143 Return a dirstate object that tracks largefiles: i.e. its root is
144 Return a dirstate object that tracks largefiles: i.e. its root is
144 the repo root, but it is saved in .hg/largefiles/dirstate.
145 the repo root, but it is saved in .hg/largefiles/dirstate.
145 '''
146 '''
146 vfs = repo.vfs
147 vfs = repo.vfs
147 lfstoredir = longname
148 lfstoredir = longname
148 opener = vfsmod.vfs(vfs.join(lfstoredir))
149 opener = vfsmod.vfs(vfs.join(lfstoredir))
149 lfdirstate = largefilesdirstate(opener, ui, repo.root,
150 lfdirstate = largefilesdirstate(opener, ui, repo.root,
150 repo.dirstate._validate)
151 repo.dirstate._validate,
152 lambda: sparse.matcher(repo))
151
153
152 # If the largefiles dirstate does not exist, populate and create
154 # If the largefiles dirstate does not exist, populate and create
153 # it. This ensures that we create it on the first meaningful
155 # it. This ensures that we create it on the first meaningful
154 # largefiles operation in a new clone.
156 # largefiles operation in a new clone.
155 if create and not vfs.exists(vfs.join(lfstoredir, 'dirstate')):
157 if create and not vfs.exists(vfs.join(lfstoredir, 'dirstate')):
156 matcher = getstandinmatcher(repo)
158 matcher = getstandinmatcher(repo)
157 standins = repo.dirstate.walk(matcher, [], False, False)
159 standins = repo.dirstate.walk(matcher, [], False, False)
158
160
159 if len(standins) > 0:
161 if len(standins) > 0:
160 vfs.makedirs(lfstoredir)
162 vfs.makedirs(lfstoredir)
161
163
162 for standin in standins:
164 for standin in standins:
163 lfile = splitstandin(standin)
165 lfile = splitstandin(standin)
164 lfdirstate.normallookup(lfile)
166 lfdirstate.normallookup(lfile)
165 return lfdirstate
167 return lfdirstate
166
168
167 def lfdirstatestatus(lfdirstate, repo):
169 def lfdirstatestatus(lfdirstate, repo):
168 pctx = repo['.']
170 pctx = repo['.']
169 match = matchmod.always(repo.root, repo.getcwd())
171 match = matchmod.always(repo.root, repo.getcwd())
170 unsure, s = lfdirstate.status(match, [], False, False, False)
172 unsure, s = lfdirstate.status(match, [], False, False, False)
171 modified, clean = s.modified, s.clean
173 modified, clean = s.modified, s.clean
172 for lfile in unsure:
174 for lfile in unsure:
173 try:
175 try:
174 fctx = pctx[standin(lfile)]
176 fctx = pctx[standin(lfile)]
175 except LookupError:
177 except LookupError:
176 fctx = None
178 fctx = None
177 if not fctx or readasstandin(fctx) != hashfile(repo.wjoin(lfile)):
179 if not fctx or readasstandin(fctx) != hashfile(repo.wjoin(lfile)):
178 modified.append(lfile)
180 modified.append(lfile)
179 else:
181 else:
180 clean.append(lfile)
182 clean.append(lfile)
181 lfdirstate.normal(lfile)
183 lfdirstate.normal(lfile)
182 return s
184 return s
183
185
184 def listlfiles(repo, rev=None, matcher=None):
186 def listlfiles(repo, rev=None, matcher=None):
185 '''return a list of largefiles in the working copy or the
187 '''return a list of largefiles in the working copy or the
186 specified changeset'''
188 specified changeset'''
187
189
188 if matcher is None:
190 if matcher is None:
189 matcher = getstandinmatcher(repo)
191 matcher = getstandinmatcher(repo)
190
192
191 # ignore unknown files in working directory
193 # ignore unknown files in working directory
192 return [splitstandin(f)
194 return [splitstandin(f)
193 for f in repo[rev].walk(matcher)
195 for f in repo[rev].walk(matcher)
194 if rev is not None or repo.dirstate[f] != '?']
196 if rev is not None or repo.dirstate[f] != '?']
195
197
196 def instore(repo, hash, forcelocal=False):
198 def instore(repo, hash, forcelocal=False):
197 '''Return true if a largefile with the given hash exists in the store'''
199 '''Return true if a largefile with the given hash exists in the store'''
198 return os.path.exists(storepath(repo, hash, forcelocal))
200 return os.path.exists(storepath(repo, hash, forcelocal))
199
201
200 def storepath(repo, hash, forcelocal=False):
202 def storepath(repo, hash, forcelocal=False):
201 '''Return the correct location in the repository largefiles store for a
203 '''Return the correct location in the repository largefiles store for a
202 file with the given hash.'''
204 file with the given hash.'''
203 if not forcelocal and repo.shared():
205 if not forcelocal and repo.shared():
204 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
206 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
205 return repo.vfs.join(longname, hash)
207 return repo.vfs.join(longname, hash)
206
208
207 def findstorepath(repo, hash):
209 def findstorepath(repo, hash):
208 '''Search through the local store path(s) to find the file for the given
210 '''Search through the local store path(s) to find the file for the given
209 hash. If the file is not found, its path in the primary store is returned.
211 hash. If the file is not found, its path in the primary store is returned.
210 The return value is a tuple of (path, exists(path)).
212 The return value is a tuple of (path, exists(path)).
211 '''
213 '''
212 # For shared repos, the primary store is in the share source. But for
214 # For shared repos, the primary store is in the share source. But for
213 # backward compatibility, force a lookup in the local store if it wasn't
215 # backward compatibility, force a lookup in the local store if it wasn't
214 # found in the share source.
216 # found in the share source.
215 path = storepath(repo, hash, False)
217 path = storepath(repo, hash, False)
216
218
217 if instore(repo, hash):
219 if instore(repo, hash):
218 return (path, True)
220 return (path, True)
219 elif repo.shared() and instore(repo, hash, True):
221 elif repo.shared() and instore(repo, hash, True):
220 return storepath(repo, hash, True), True
222 return storepath(repo, hash, True), True
221
223
222 return (path, False)
224 return (path, False)
223
225
224 def copyfromcache(repo, hash, filename):
226 def copyfromcache(repo, hash, filename):
225 '''Copy the specified largefile from the repo or system cache to
227 '''Copy the specified largefile from the repo or system cache to
226 filename in the repository. Return true on success or false if the
228 filename in the repository. Return true on success or false if the
227 file was not found in either cache (which should not happened:
229 file was not found in either cache (which should not happened:
228 this is meant to be called only after ensuring that the needed
230 this is meant to be called only after ensuring that the needed
229 largefile exists in the cache).'''
231 largefile exists in the cache).'''
230 wvfs = repo.wvfs
232 wvfs = repo.wvfs
231 path = findfile(repo, hash)
233 path = findfile(repo, hash)
232 if path is None:
234 if path is None:
233 return False
235 return False
234 wvfs.makedirs(wvfs.dirname(wvfs.join(filename)))
236 wvfs.makedirs(wvfs.dirname(wvfs.join(filename)))
235 # The write may fail before the file is fully written, but we
237 # The write may fail before the file is fully written, but we
236 # don't use atomic writes in the working copy.
238 # don't use atomic writes in the working copy.
237 with open(path, 'rb') as srcfd:
239 with open(path, 'rb') as srcfd:
238 with wvfs(filename, 'wb') as destfd:
240 with wvfs(filename, 'wb') as destfd:
239 gothash = copyandhash(
241 gothash = copyandhash(
240 util.filechunkiter(srcfd), destfd)
242 util.filechunkiter(srcfd), destfd)
241 if gothash != hash:
243 if gothash != hash:
242 repo.ui.warn(_('%s: data corruption in %s with hash %s\n')
244 repo.ui.warn(_('%s: data corruption in %s with hash %s\n')
243 % (filename, path, gothash))
245 % (filename, path, gothash))
244 wvfs.unlink(filename)
246 wvfs.unlink(filename)
245 return False
247 return False
246 return True
248 return True
247
249
248 def copytostore(repo, ctx, file, fstandin):
250 def copytostore(repo, ctx, file, fstandin):
249 wvfs = repo.wvfs
251 wvfs = repo.wvfs
250 hash = readasstandin(ctx[fstandin])
252 hash = readasstandin(ctx[fstandin])
251 if instore(repo, hash):
253 if instore(repo, hash):
252 return
254 return
253 if wvfs.exists(file):
255 if wvfs.exists(file):
254 copytostoreabsolute(repo, wvfs.join(file), hash)
256 copytostoreabsolute(repo, wvfs.join(file), hash)
255 else:
257 else:
256 repo.ui.warn(_("%s: largefile %s not available from local store\n") %
258 repo.ui.warn(_("%s: largefile %s not available from local store\n") %
257 (file, hash))
259 (file, hash))
258
260
259 def copyalltostore(repo, node):
261 def copyalltostore(repo, node):
260 '''Copy all largefiles in a given revision to the store'''
262 '''Copy all largefiles in a given revision to the store'''
261
263
262 ctx = repo[node]
264 ctx = repo[node]
263 for filename in ctx.files():
265 for filename in ctx.files():
264 realfile = splitstandin(filename)
266 realfile = splitstandin(filename)
265 if realfile is not None and filename in ctx.manifest():
267 if realfile is not None and filename in ctx.manifest():
266 copytostore(repo, ctx, realfile, filename)
268 copytostore(repo, ctx, realfile, filename)
267
269
268 def copytostoreabsolute(repo, file, hash):
270 def copytostoreabsolute(repo, file, hash):
269 if inusercache(repo.ui, hash):
271 if inusercache(repo.ui, hash):
270 link(usercachepath(repo.ui, hash), storepath(repo, hash))
272 link(usercachepath(repo.ui, hash), storepath(repo, hash))
271 else:
273 else:
272 util.makedirs(os.path.dirname(storepath(repo, hash)))
274 util.makedirs(os.path.dirname(storepath(repo, hash)))
273 with open(file, 'rb') as srcf:
275 with open(file, 'rb') as srcf:
274 with util.atomictempfile(storepath(repo, hash),
276 with util.atomictempfile(storepath(repo, hash),
275 createmode=repo.store.createmode) as dstf:
277 createmode=repo.store.createmode) as dstf:
276 for chunk in util.filechunkiter(srcf):
278 for chunk in util.filechunkiter(srcf):
277 dstf.write(chunk)
279 dstf.write(chunk)
278 linktousercache(repo, hash)
280 linktousercache(repo, hash)
279
281
280 def linktousercache(repo, hash):
282 def linktousercache(repo, hash):
281 '''Link / copy the largefile with the specified hash from the store
283 '''Link / copy the largefile with the specified hash from the store
282 to the cache.'''
284 to the cache.'''
283 path = usercachepath(repo.ui, hash)
285 path = usercachepath(repo.ui, hash)
284 link(storepath(repo, hash), path)
286 link(storepath(repo, hash), path)
285
287
286 def getstandinmatcher(repo, rmatcher=None):
288 def getstandinmatcher(repo, rmatcher=None):
287 '''Return a match object that applies rmatcher to the standin directory'''
289 '''Return a match object that applies rmatcher to the standin directory'''
288 wvfs = repo.wvfs
290 wvfs = repo.wvfs
289 standindir = shortname
291 standindir = shortname
290
292
291 # no warnings about missing files or directories
293 # no warnings about missing files or directories
292 badfn = lambda f, msg: None
294 badfn = lambda f, msg: None
293
295
294 if rmatcher and not rmatcher.always():
296 if rmatcher and not rmatcher.always():
295 pats = [wvfs.join(standindir, pat) for pat in rmatcher.files()]
297 pats = [wvfs.join(standindir, pat) for pat in rmatcher.files()]
296 if not pats:
298 if not pats:
297 pats = [wvfs.join(standindir)]
299 pats = [wvfs.join(standindir)]
298 match = scmutil.match(repo[None], pats, badfn=badfn)
300 match = scmutil.match(repo[None], pats, badfn=badfn)
299 else:
301 else:
300 # no patterns: relative to repo root
302 # no patterns: relative to repo root
301 match = scmutil.match(repo[None], [wvfs.join(standindir)], badfn=badfn)
303 match = scmutil.match(repo[None], [wvfs.join(standindir)], badfn=badfn)
302 return match
304 return match
303
305
304 def composestandinmatcher(repo, rmatcher):
306 def composestandinmatcher(repo, rmatcher):
305 '''Return a matcher that accepts standins corresponding to the
307 '''Return a matcher that accepts standins corresponding to the
306 files accepted by rmatcher. Pass the list of files in the matcher
308 files accepted by rmatcher. Pass the list of files in the matcher
307 as the paths specified by the user.'''
309 as the paths specified by the user.'''
308 smatcher = getstandinmatcher(repo, rmatcher)
310 smatcher = getstandinmatcher(repo, rmatcher)
309 isstandin = smatcher.matchfn
311 isstandin = smatcher.matchfn
310 def composedmatchfn(f):
312 def composedmatchfn(f):
311 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
313 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
312 smatcher.matchfn = composedmatchfn
314 smatcher.matchfn = composedmatchfn
313
315
314 return smatcher
316 return smatcher
315
317
316 def standin(filename):
318 def standin(filename):
317 '''Return the repo-relative path to the standin for the specified big
319 '''Return the repo-relative path to the standin for the specified big
318 file.'''
320 file.'''
319 # Notes:
321 # Notes:
320 # 1) Some callers want an absolute path, but for instance addlargefiles
322 # 1) Some callers want an absolute path, but for instance addlargefiles
321 # needs it repo-relative so it can be passed to repo[None].add(). So
323 # needs it repo-relative so it can be passed to repo[None].add(). So
322 # leave it up to the caller to use repo.wjoin() to get an absolute path.
324 # leave it up to the caller to use repo.wjoin() to get an absolute path.
323 # 2) Join with '/' because that's what dirstate always uses, even on
325 # 2) Join with '/' because that's what dirstate always uses, even on
324 # Windows. Change existing separator to '/' first in case we are
326 # Windows. Change existing separator to '/' first in case we are
325 # passed filenames from an external source (like the command line).
327 # passed filenames from an external source (like the command line).
326 return shortnameslash + util.pconvert(filename)
328 return shortnameslash + util.pconvert(filename)
327
329
328 def isstandin(filename):
330 def isstandin(filename):
329 '''Return true if filename is a big file standin. filename must be
331 '''Return true if filename is a big file standin. filename must be
330 in Mercurial's internal form (slash-separated).'''
332 in Mercurial's internal form (slash-separated).'''
331 return filename.startswith(shortnameslash)
333 return filename.startswith(shortnameslash)
332
334
333 def splitstandin(filename):
335 def splitstandin(filename):
334 # Split on / because that's what dirstate always uses, even on Windows.
336 # Split on / because that's what dirstate always uses, even on Windows.
335 # Change local separator to / first just in case we are passed filenames
337 # Change local separator to / first just in case we are passed filenames
336 # from an external source (like the command line).
338 # from an external source (like the command line).
337 bits = util.pconvert(filename).split('/', 1)
339 bits = util.pconvert(filename).split('/', 1)
338 if len(bits) == 2 and bits[0] == shortname:
340 if len(bits) == 2 and bits[0] == shortname:
339 return bits[1]
341 return bits[1]
340 else:
342 else:
341 return None
343 return None
342
344
343 def updatestandin(repo, lfile, standin):
345 def updatestandin(repo, lfile, standin):
344 """Re-calculate hash value of lfile and write it into standin
346 """Re-calculate hash value of lfile and write it into standin
345
347
346 This assumes that "lfutil.standin(lfile) == standin", for efficiency.
348 This assumes that "lfutil.standin(lfile) == standin", for efficiency.
347 """
349 """
348 file = repo.wjoin(lfile)
350 file = repo.wjoin(lfile)
349 if repo.wvfs.exists(lfile):
351 if repo.wvfs.exists(lfile):
350 hash = hashfile(file)
352 hash = hashfile(file)
351 executable = getexecutable(file)
353 executable = getexecutable(file)
352 writestandin(repo, standin, hash, executable)
354 writestandin(repo, standin, hash, executable)
353 else:
355 else:
354 raise error.Abort(_('%s: file not found!') % lfile)
356 raise error.Abort(_('%s: file not found!') % lfile)
355
357
356 def readasstandin(fctx):
358 def readasstandin(fctx):
357 '''read hex hash from given filectx of standin file
359 '''read hex hash from given filectx of standin file
358
360
359 This encapsulates how "standin" data is stored into storage layer.'''
361 This encapsulates how "standin" data is stored into storage layer.'''
360 return fctx.data().strip()
362 return fctx.data().strip()
361
363
362 def writestandin(repo, standin, hash, executable):
364 def writestandin(repo, standin, hash, executable):
363 '''write hash to <repo.root>/<standin>'''
365 '''write hash to <repo.root>/<standin>'''
364 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
366 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
365
367
366 def copyandhash(instream, outfile):
368 def copyandhash(instream, outfile):
367 '''Read bytes from instream (iterable) and write them to outfile,
369 '''Read bytes from instream (iterable) and write them to outfile,
368 computing the SHA-1 hash of the data along the way. Return the hash.'''
370 computing the SHA-1 hash of the data along the way. Return the hash.'''
369 hasher = hashlib.sha1('')
371 hasher = hashlib.sha1('')
370 for data in instream:
372 for data in instream:
371 hasher.update(data)
373 hasher.update(data)
372 outfile.write(data)
374 outfile.write(data)
373 return hasher.hexdigest()
375 return hasher.hexdigest()
374
376
375 def hashfile(file):
377 def hashfile(file):
376 if not os.path.exists(file):
378 if not os.path.exists(file):
377 return ''
379 return ''
378 with open(file, 'rb') as fd:
380 with open(file, 'rb') as fd:
379 return hexsha1(fd)
381 return hexsha1(fd)
380
382
381 def getexecutable(filename):
383 def getexecutable(filename):
382 mode = os.stat(filename).st_mode
384 mode = os.stat(filename).st_mode
383 return ((mode & stat.S_IXUSR) and
385 return ((mode & stat.S_IXUSR) and
384 (mode & stat.S_IXGRP) and
386 (mode & stat.S_IXGRP) and
385 (mode & stat.S_IXOTH))
387 (mode & stat.S_IXOTH))
386
388
387 def urljoin(first, second, *arg):
389 def urljoin(first, second, *arg):
388 def join(left, right):
390 def join(left, right):
389 if not left.endswith('/'):
391 if not left.endswith('/'):
390 left += '/'
392 left += '/'
391 if right.startswith('/'):
393 if right.startswith('/'):
392 right = right[1:]
394 right = right[1:]
393 return left + right
395 return left + right
394
396
395 url = join(first, second)
397 url = join(first, second)
396 for a in arg:
398 for a in arg:
397 url = join(url, a)
399 url = join(url, a)
398 return url
400 return url
399
401
400 def hexsha1(fileobj):
402 def hexsha1(fileobj):
401 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
403 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
402 object data"""
404 object data"""
403 h = hashlib.sha1()
405 h = hashlib.sha1()
404 for chunk in util.filechunkiter(fileobj):
406 for chunk in util.filechunkiter(fileobj):
405 h.update(chunk)
407 h.update(chunk)
406 return h.hexdigest()
408 return h.hexdigest()
407
409
408 def httpsendfile(ui, filename):
410 def httpsendfile(ui, filename):
409 return httpconnection.httpsendfile(ui, filename, 'rb')
411 return httpconnection.httpsendfile(ui, filename, 'rb')
410
412
411 def unixpath(path):
413 def unixpath(path):
412 '''Return a version of path normalized for use with the lfdirstate.'''
414 '''Return a version of path normalized for use with the lfdirstate.'''
413 return util.pconvert(os.path.normpath(path))
415 return util.pconvert(os.path.normpath(path))
414
416
415 def islfilesrepo(repo):
417 def islfilesrepo(repo):
416 '''Return true if the repo is a largefile repo.'''
418 '''Return true if the repo is a largefile repo.'''
417 if ('largefiles' in repo.requirements and
419 if ('largefiles' in repo.requirements and
418 any(shortnameslash in f[0] for f in repo.store.datafiles())):
420 any(shortnameslash in f[0] for f in repo.store.datafiles())):
419 return True
421 return True
420
422
421 return any(openlfdirstate(repo.ui, repo, False))
423 return any(openlfdirstate(repo.ui, repo, False))
422
424
423 class storeprotonotcapable(Exception):
425 class storeprotonotcapable(Exception):
424 def __init__(self, storetypes):
426 def __init__(self, storetypes):
425 self.storetypes = storetypes
427 self.storetypes = storetypes
426
428
427 def getstandinsstate(repo):
429 def getstandinsstate(repo):
428 standins = []
430 standins = []
429 matcher = getstandinmatcher(repo)
431 matcher = getstandinmatcher(repo)
430 wctx = repo[None]
432 wctx = repo[None]
431 for standin in repo.dirstate.walk(matcher, [], False, False):
433 for standin in repo.dirstate.walk(matcher, [], False, False):
432 lfile = splitstandin(standin)
434 lfile = splitstandin(standin)
433 try:
435 try:
434 hash = readasstandin(wctx[standin])
436 hash = readasstandin(wctx[standin])
435 except IOError:
437 except IOError:
436 hash = None
438 hash = None
437 standins.append((lfile, hash))
439 standins.append((lfile, hash))
438 return standins
440 return standins
439
441
440 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
442 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
441 lfstandin = standin(lfile)
443 lfstandin = standin(lfile)
442 if lfstandin in repo.dirstate:
444 if lfstandin in repo.dirstate:
443 stat = repo.dirstate._map[lfstandin]
445 stat = repo.dirstate._map[lfstandin]
444 state, mtime = stat[0], stat[3]
446 state, mtime = stat[0], stat[3]
445 else:
447 else:
446 state, mtime = '?', -1
448 state, mtime = '?', -1
447 if state == 'n':
449 if state == 'n':
448 if (normallookup or mtime < 0 or
450 if (normallookup or mtime < 0 or
449 not repo.wvfs.exists(lfile)):
451 not repo.wvfs.exists(lfile)):
450 # state 'n' doesn't ensure 'clean' in this case
452 # state 'n' doesn't ensure 'clean' in this case
451 lfdirstate.normallookup(lfile)
453 lfdirstate.normallookup(lfile)
452 else:
454 else:
453 lfdirstate.normal(lfile)
455 lfdirstate.normal(lfile)
454 elif state == 'm':
456 elif state == 'm':
455 lfdirstate.normallookup(lfile)
457 lfdirstate.normallookup(lfile)
456 elif state == 'r':
458 elif state == 'r':
457 lfdirstate.remove(lfile)
459 lfdirstate.remove(lfile)
458 elif state == 'a':
460 elif state == 'a':
459 lfdirstate.add(lfile)
461 lfdirstate.add(lfile)
460 elif state == '?':
462 elif state == '?':
461 lfdirstate.drop(lfile)
463 lfdirstate.drop(lfile)
462
464
463 def markcommitted(orig, ctx, node):
465 def markcommitted(orig, ctx, node):
464 repo = ctx.repo()
466 repo = ctx.repo()
465
467
466 orig(node)
468 orig(node)
467
469
468 # ATTENTION: "ctx.files()" may differ from "repo[node].files()"
470 # ATTENTION: "ctx.files()" may differ from "repo[node].files()"
469 # because files coming from the 2nd parent are omitted in the latter.
471 # because files coming from the 2nd parent are omitted in the latter.
470 #
472 #
471 # The former should be used to get targets of "synclfdirstate",
473 # The former should be used to get targets of "synclfdirstate",
472 # because such files:
474 # because such files:
473 # - are marked as "a" by "patch.patch()" (e.g. via transplant), and
475 # - are marked as "a" by "patch.patch()" (e.g. via transplant), and
474 # - have to be marked as "n" after commit, but
476 # - have to be marked as "n" after commit, but
475 # - aren't listed in "repo[node].files()"
477 # - aren't listed in "repo[node].files()"
476
478
477 lfdirstate = openlfdirstate(repo.ui, repo)
479 lfdirstate = openlfdirstate(repo.ui, repo)
478 for f in ctx.files():
480 for f in ctx.files():
479 lfile = splitstandin(f)
481 lfile = splitstandin(f)
480 if lfile is not None:
482 if lfile is not None:
481 synclfdirstate(repo, lfdirstate, lfile, False)
483 synclfdirstate(repo, lfdirstate, lfile, False)
482 lfdirstate.write()
484 lfdirstate.write()
483
485
484 # As part of committing, copy all of the largefiles into the cache.
486 # As part of committing, copy all of the largefiles into the cache.
485 #
487 #
486 # Using "node" instead of "ctx" implies additional "repo[node]"
488 # Using "node" instead of "ctx" implies additional "repo[node]"
487 # lookup while copyalltostore(), but can omit redundant check for
489 # lookup while copyalltostore(), but can omit redundant check for
488 # files comming from the 2nd parent, which should exist in store
490 # files comming from the 2nd parent, which should exist in store
489 # at merging.
491 # at merging.
490 copyalltostore(repo, node)
492 copyalltostore(repo, node)
491
493
492 def getlfilestoupdate(oldstandins, newstandins):
494 def getlfilestoupdate(oldstandins, newstandins):
493 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
495 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
494 filelist = []
496 filelist = []
495 for f in changedstandins:
497 for f in changedstandins:
496 if f[0] not in filelist:
498 if f[0] not in filelist:
497 filelist.append(f[0])
499 filelist.append(f[0])
498 return filelist
500 return filelist
499
501
500 def getlfilestoupload(repo, missing, addfunc):
502 def getlfilestoupload(repo, missing, addfunc):
501 for i, n in enumerate(missing):
503 for i, n in enumerate(missing):
502 repo.ui.progress(_('finding outgoing largefiles'), i,
504 repo.ui.progress(_('finding outgoing largefiles'), i,
503 unit=_('revisions'), total=len(missing))
505 unit=_('revisions'), total=len(missing))
504 parents = [p for p in repo[n].parents() if p != node.nullid]
506 parents = [p for p in repo[n].parents() if p != node.nullid]
505
507
506 oldlfstatus = repo.lfstatus
508 oldlfstatus = repo.lfstatus
507 repo.lfstatus = False
509 repo.lfstatus = False
508 try:
510 try:
509 ctx = repo[n]
511 ctx = repo[n]
510 finally:
512 finally:
511 repo.lfstatus = oldlfstatus
513 repo.lfstatus = oldlfstatus
512
514
513 files = set(ctx.files())
515 files = set(ctx.files())
514 if len(parents) == 2:
516 if len(parents) == 2:
515 mc = ctx.manifest()
517 mc = ctx.manifest()
516 mp1 = ctx.parents()[0].manifest()
518 mp1 = ctx.parents()[0].manifest()
517 mp2 = ctx.parents()[1].manifest()
519 mp2 = ctx.parents()[1].manifest()
518 for f in mp1:
520 for f in mp1:
519 if f not in mc:
521 if f not in mc:
520 files.add(f)
522 files.add(f)
521 for f in mp2:
523 for f in mp2:
522 if f not in mc:
524 if f not in mc:
523 files.add(f)
525 files.add(f)
524 for f in mc:
526 for f in mc:
525 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
527 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
526 files.add(f)
528 files.add(f)
527 for fn in files:
529 for fn in files:
528 if isstandin(fn) and fn in ctx:
530 if isstandin(fn) and fn in ctx:
529 addfunc(fn, readasstandin(ctx[fn]))
531 addfunc(fn, readasstandin(ctx[fn]))
530 repo.ui.progress(_('finding outgoing largefiles'), None)
532 repo.ui.progress(_('finding outgoing largefiles'), None)
531
533
532 def updatestandinsbymatch(repo, match):
534 def updatestandinsbymatch(repo, match):
533 '''Update standins in the working directory according to specified match
535 '''Update standins in the working directory according to specified match
534
536
535 This returns (possibly modified) ``match`` object to be used for
537 This returns (possibly modified) ``match`` object to be used for
536 subsequent commit process.
538 subsequent commit process.
537 '''
539 '''
538
540
539 ui = repo.ui
541 ui = repo.ui
540
542
541 # Case 1: user calls commit with no specific files or
543 # Case 1: user calls commit with no specific files or
542 # include/exclude patterns: refresh and commit all files that
544 # include/exclude patterns: refresh and commit all files that
543 # are "dirty".
545 # are "dirty".
544 if match is None or match.always():
546 if match is None or match.always():
545 # Spend a bit of time here to get a list of files we know
547 # Spend a bit of time here to get a list of files we know
546 # are modified so we can compare only against those.
548 # are modified so we can compare only against those.
547 # It can cost a lot of time (several seconds)
549 # It can cost a lot of time (several seconds)
548 # otherwise to update all standins if the largefiles are
550 # otherwise to update all standins if the largefiles are
549 # large.
551 # large.
550 lfdirstate = openlfdirstate(ui, repo)
552 lfdirstate = openlfdirstate(ui, repo)
551 dirtymatch = matchmod.always(repo.root, repo.getcwd())
553 dirtymatch = matchmod.always(repo.root, repo.getcwd())
552 unsure, s = lfdirstate.status(dirtymatch, [], False, False,
554 unsure, s = lfdirstate.status(dirtymatch, [], False, False,
553 False)
555 False)
554 modifiedfiles = unsure + s.modified + s.added + s.removed
556 modifiedfiles = unsure + s.modified + s.added + s.removed
555 lfiles = listlfiles(repo)
557 lfiles = listlfiles(repo)
556 # this only loops through largefiles that exist (not
558 # this only loops through largefiles that exist (not
557 # removed/renamed)
559 # removed/renamed)
558 for lfile in lfiles:
560 for lfile in lfiles:
559 if lfile in modifiedfiles:
561 if lfile in modifiedfiles:
560 fstandin = standin(lfile)
562 fstandin = standin(lfile)
561 if repo.wvfs.exists(fstandin):
563 if repo.wvfs.exists(fstandin):
562 # this handles the case where a rebase is being
564 # this handles the case where a rebase is being
563 # performed and the working copy is not updated
565 # performed and the working copy is not updated
564 # yet.
566 # yet.
565 if repo.wvfs.exists(lfile):
567 if repo.wvfs.exists(lfile):
566 updatestandin(repo, lfile, fstandin)
568 updatestandin(repo, lfile, fstandin)
567
569
568 return match
570 return match
569
571
570 lfiles = listlfiles(repo)
572 lfiles = listlfiles(repo)
571 match._files = repo._subdirlfs(match.files(), lfiles)
573 match._files = repo._subdirlfs(match.files(), lfiles)
572
574
573 # Case 2: user calls commit with specified patterns: refresh
575 # Case 2: user calls commit with specified patterns: refresh
574 # any matching big files.
576 # any matching big files.
575 smatcher = composestandinmatcher(repo, match)
577 smatcher = composestandinmatcher(repo, match)
576 standins = repo.dirstate.walk(smatcher, [], False, False)
578 standins = repo.dirstate.walk(smatcher, [], False, False)
577
579
578 # No matching big files: get out of the way and pass control to
580 # No matching big files: get out of the way and pass control to
579 # the usual commit() method.
581 # the usual commit() method.
580 if not standins:
582 if not standins:
581 return match
583 return match
582
584
583 # Refresh all matching big files. It's possible that the
585 # Refresh all matching big files. It's possible that the
584 # commit will end up failing, in which case the big files will
586 # commit will end up failing, in which case the big files will
585 # stay refreshed. No harm done: the user modified them and
587 # stay refreshed. No harm done: the user modified them and
586 # asked to commit them, so sooner or later we're going to
588 # asked to commit them, so sooner or later we're going to
587 # refresh the standins. Might as well leave them refreshed.
589 # refresh the standins. Might as well leave them refreshed.
588 lfdirstate = openlfdirstate(ui, repo)
590 lfdirstate = openlfdirstate(ui, repo)
589 for fstandin in standins:
591 for fstandin in standins:
590 lfile = splitstandin(fstandin)
592 lfile = splitstandin(fstandin)
591 if lfdirstate[lfile] != 'r':
593 if lfdirstate[lfile] != 'r':
592 updatestandin(repo, lfile, fstandin)
594 updatestandin(repo, lfile, fstandin)
593
595
594 # Cook up a new matcher that only matches regular files or
596 # Cook up a new matcher that only matches regular files or
595 # standins corresponding to the big files requested by the
597 # standins corresponding to the big files requested by the
596 # user. Have to modify _files to prevent commit() from
598 # user. Have to modify _files to prevent commit() from
597 # complaining "not tracked" for big files.
599 # complaining "not tracked" for big files.
598 match = copy.copy(match)
600 match = copy.copy(match)
599 origmatchfn = match.matchfn
601 origmatchfn = match.matchfn
600
602
601 # Check both the list of largefiles and the list of
603 # Check both the list of largefiles and the list of
602 # standins because if a largefile was removed, it
604 # standins because if a largefile was removed, it
603 # won't be in the list of largefiles at this point
605 # won't be in the list of largefiles at this point
604 match._files += sorted(standins)
606 match._files += sorted(standins)
605
607
606 actualfiles = []
608 actualfiles = []
607 for f in match._files:
609 for f in match._files:
608 fstandin = standin(f)
610 fstandin = standin(f)
609
611
610 # For largefiles, only one of the normal and standin should be
612 # For largefiles, only one of the normal and standin should be
611 # committed (except if one of them is a remove). In the case of a
613 # committed (except if one of them is a remove). In the case of a
612 # standin removal, drop the normal file if it is unknown to dirstate.
614 # standin removal, drop the normal file if it is unknown to dirstate.
613 # Thus, skip plain largefile names but keep the standin.
615 # Thus, skip plain largefile names but keep the standin.
614 if f in lfiles or fstandin in standins:
616 if f in lfiles or fstandin in standins:
615 if repo.dirstate[fstandin] != 'r':
617 if repo.dirstate[fstandin] != 'r':
616 if repo.dirstate[f] != 'r':
618 if repo.dirstate[f] != 'r':
617 continue
619 continue
618 elif repo.dirstate[f] == '?':
620 elif repo.dirstate[f] == '?':
619 continue
621 continue
620
622
621 actualfiles.append(f)
623 actualfiles.append(f)
622 match._files = actualfiles
624 match._files = actualfiles
623
625
624 def matchfn(f):
626 def matchfn(f):
625 if origmatchfn(f):
627 if origmatchfn(f):
626 return f not in lfiles
628 return f not in lfiles
627 else:
629 else:
628 return f in standins
630 return f in standins
629
631
630 match.matchfn = matchfn
632 match.matchfn = matchfn
631
633
632 return match
634 return match
633
635
634 class automatedcommithook(object):
636 class automatedcommithook(object):
635 '''Stateful hook to update standins at the 1st commit of resuming
637 '''Stateful hook to update standins at the 1st commit of resuming
636
638
637 For efficiency, updating standins in the working directory should
639 For efficiency, updating standins in the working directory should
638 be avoided while automated committing (like rebase, transplant and
640 be avoided while automated committing (like rebase, transplant and
639 so on), because they should be updated before committing.
641 so on), because they should be updated before committing.
640
642
641 But the 1st commit of resuming automated committing (e.g. ``rebase
643 But the 1st commit of resuming automated committing (e.g. ``rebase
642 --continue``) should update them, because largefiles may be
644 --continue``) should update them, because largefiles may be
643 modified manually.
645 modified manually.
644 '''
646 '''
645 def __init__(self, resuming):
647 def __init__(self, resuming):
646 self.resuming = resuming
648 self.resuming = resuming
647
649
648 def __call__(self, repo, match):
650 def __call__(self, repo, match):
649 if self.resuming:
651 if self.resuming:
650 self.resuming = False # avoids updating at subsequent commits
652 self.resuming = False # avoids updating at subsequent commits
651 return updatestandinsbymatch(repo, match)
653 return updatestandinsbymatch(repo, match)
652 else:
654 else:
653 return match
655 return match
654
656
655 def getstatuswriter(ui, repo, forcibly=None):
657 def getstatuswriter(ui, repo, forcibly=None):
656 '''Return the function to write largefiles specific status out
658 '''Return the function to write largefiles specific status out
657
659
658 If ``forcibly`` is ``None``, this returns the last element of
660 If ``forcibly`` is ``None``, this returns the last element of
659 ``repo._lfstatuswriters`` as "default" writer function.
661 ``repo._lfstatuswriters`` as "default" writer function.
660
662
661 Otherwise, this returns the function to always write out (or
663 Otherwise, this returns the function to always write out (or
662 ignore if ``not forcibly``) status.
664 ignore if ``not forcibly``) status.
663 '''
665 '''
664 if forcibly is None and util.safehasattr(repo, '_largefilesenabled'):
666 if forcibly is None and util.safehasattr(repo, '_largefilesenabled'):
665 return repo._lfstatuswriters[-1]
667 return repo._lfstatuswriters[-1]
666 else:
668 else:
667 if forcibly:
669 if forcibly:
668 return ui.status # forcibly WRITE OUT
670 return ui.status # forcibly WRITE OUT
669 else:
671 else:
670 return lambda *msg, **opts: None # forcibly IGNORE
672 return lambda *msg, **opts: None # forcibly IGNORE
@@ -1,445 +1,428 b''
1 # sparse.py - allow sparse checkouts of the working directory
1 # sparse.py - allow sparse checkouts of the working directory
2 #
2 #
3 # Copyright 2014 Facebook, Inc.
3 # Copyright 2014 Facebook, Inc.
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 """allow sparse checkouts of the working directory (EXPERIMENTAL)
8 """allow sparse checkouts of the working directory (EXPERIMENTAL)
9
9
10 (This extension is not yet protected by backwards compatibility
10 (This extension is not yet protected by backwards compatibility
11 guarantees. Any aspect may break in future releases until this
11 guarantees. Any aspect may break in future releases until this
12 notice is removed.)
12 notice is removed.)
13
13
14 This extension allows the working directory to only consist of a
14 This extension allows the working directory to only consist of a
15 subset of files for the revision. This allows specific files or
15 subset of files for the revision. This allows specific files or
16 directories to be explicitly included or excluded. Many repository
16 directories to be explicitly included or excluded. Many repository
17 operations have performance proportional to the number of files in
17 operations have performance proportional to the number of files in
18 the working directory. So only realizing a subset of files in the
18 the working directory. So only realizing a subset of files in the
19 working directory can improve performance.
19 working directory can improve performance.
20
20
21 Sparse Config Files
21 Sparse Config Files
22 -------------------
22 -------------------
23
23
24 The set of files that are part of a sparse checkout are defined by
24 The set of files that are part of a sparse checkout are defined by
25 a sparse config file. The file defines 3 things: includes (files to
25 a sparse config file. The file defines 3 things: includes (files to
26 include in the sparse checkout), excludes (files to exclude from the
26 include in the sparse checkout), excludes (files to exclude from the
27 sparse checkout), and profiles (links to other config files).
27 sparse checkout), and profiles (links to other config files).
28
28
29 The file format is newline delimited. Empty lines and lines beginning
29 The file format is newline delimited. Empty lines and lines beginning
30 with ``#`` are ignored.
30 with ``#`` are ignored.
31
31
32 Lines beginning with ``%include `` denote another sparse config file
32 Lines beginning with ``%include `` denote another sparse config file
33 to include. e.g. ``%include tests.sparse``. The filename is relative
33 to include. e.g. ``%include tests.sparse``. The filename is relative
34 to the repository root.
34 to the repository root.
35
35
36 The special lines ``[include]`` and ``[exclude]`` denote the section
36 The special lines ``[include]`` and ``[exclude]`` denote the section
37 for includes and excludes that follow, respectively. It is illegal to
37 for includes and excludes that follow, respectively. It is illegal to
38 have ``[include]`` after ``[exclude]``. If no sections are defined,
38 have ``[include]`` after ``[exclude]``. If no sections are defined,
39 entries are assumed to be in the ``[include]`` section.
39 entries are assumed to be in the ``[include]`` section.
40
40
41 Non-special lines resemble file patterns to be added to either includes
41 Non-special lines resemble file patterns to be added to either includes
42 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
42 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
43 Patterns are interpreted as ``glob:`` by default and match against the
43 Patterns are interpreted as ``glob:`` by default and match against the
44 root of the repository.
44 root of the repository.
45
45
46 Exclusion patterns take precedence over inclusion patterns. So even
46 Exclusion patterns take precedence over inclusion patterns. So even
47 if a file is explicitly included, an ``[exclude]`` entry can remove it.
47 if a file is explicitly included, an ``[exclude]`` entry can remove it.
48
48
49 For example, say you have a repository with 3 directories, ``frontend/``,
49 For example, say you have a repository with 3 directories, ``frontend/``,
50 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
50 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
51 to different projects and it is uncommon for someone working on one
51 to different projects and it is uncommon for someone working on one
52 to need the files for the other. But ``tools/`` contains files shared
52 to need the files for the other. But ``tools/`` contains files shared
53 between both projects. Your sparse config files may resemble::
53 between both projects. Your sparse config files may resemble::
54
54
55 # frontend.sparse
55 # frontend.sparse
56 frontend/**
56 frontend/**
57 tools/**
57 tools/**
58
58
59 # backend.sparse
59 # backend.sparse
60 backend/**
60 backend/**
61 tools/**
61 tools/**
62
62
63 Say the backend grows in size. Or there's a directory with thousands
63 Say the backend grows in size. Or there's a directory with thousands
64 of files you wish to exclude. You can modify the profile to exclude
64 of files you wish to exclude. You can modify the profile to exclude
65 certain files::
65 certain files::
66
66
67 [include]
67 [include]
68 backend/**
68 backend/**
69 tools/**
69 tools/**
70
70
71 [exclude]
71 [exclude]
72 tools/tests/**
72 tools/tests/**
73 """
73 """
74
74
75 from __future__ import absolute_import
75 from __future__ import absolute_import
76
76
77 from mercurial.i18n import _
77 from mercurial.i18n import _
78 from mercurial import (
78 from mercurial import (
79 cmdutil,
79 cmdutil,
80 commands,
80 commands,
81 dirstate,
81 dirstate,
82 error,
82 error,
83 extensions,
83 extensions,
84 hg,
84 hg,
85 localrepo,
86 match as matchmod,
85 match as matchmod,
87 registrar,
86 registrar,
88 sparse,
87 sparse,
89 util,
88 util,
90 )
89 )
91
90
92 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
91 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
93 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
92 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
94 # be specifying the version(s) of Mercurial they are tested with, or
93 # be specifying the version(s) of Mercurial they are tested with, or
95 # leave the attribute unspecified.
94 # leave the attribute unspecified.
96 testedwith = 'ships-with-hg-core'
95 testedwith = 'ships-with-hg-core'
97
96
98 cmdtable = {}
97 cmdtable = {}
99 command = registrar.command(cmdtable)
98 command = registrar.command(cmdtable)
100
99
101 def extsetup(ui):
100 def extsetup(ui):
102 sparse.enabled = True
101 sparse.enabled = True
103
102
104 _setupclone(ui)
103 _setupclone(ui)
105 _setuplog(ui)
104 _setuplog(ui)
106 _setupadd(ui)
105 _setupadd(ui)
107 _setupdirstate(ui)
106 _setupdirstate(ui)
108
107
109 def reposetup(ui, repo):
110 if not util.safehasattr(repo, 'dirstate'):
111 return
112
113 if 'dirstate' in repo._filecache:
114 repo.dirstate.repo = repo
115
116 def replacefilecache(cls, propname, replacement):
108 def replacefilecache(cls, propname, replacement):
117 """Replace a filecache property with a new class. This allows changing the
109 """Replace a filecache property with a new class. This allows changing the
118 cache invalidation condition."""
110 cache invalidation condition."""
119 origcls = cls
111 origcls = cls
120 assert callable(replacement)
112 assert callable(replacement)
121 while cls is not object:
113 while cls is not object:
122 if propname in cls.__dict__:
114 if propname in cls.__dict__:
123 orig = cls.__dict__[propname]
115 orig = cls.__dict__[propname]
124 setattr(cls, propname, replacement(orig))
116 setattr(cls, propname, replacement(orig))
125 break
117 break
126 cls = cls.__bases__[0]
118 cls = cls.__bases__[0]
127
119
128 if cls is object:
120 if cls is object:
129 raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
121 raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
130 propname))
122 propname))
131
123
132 def _setuplog(ui):
124 def _setuplog(ui):
133 entry = commands.table['^log|history']
125 entry = commands.table['^log|history']
134 entry[1].append(('', 'sparse', None,
126 entry[1].append(('', 'sparse', None,
135 "limit to changesets affecting the sparse checkout"))
127 "limit to changesets affecting the sparse checkout"))
136
128
137 def _logrevs(orig, repo, opts):
129 def _logrevs(orig, repo, opts):
138 revs = orig(repo, opts)
130 revs = orig(repo, opts)
139 if opts.get('sparse'):
131 if opts.get('sparse'):
140 sparsematch = sparse.matcher(repo)
132 sparsematch = sparse.matcher(repo)
141 def ctxmatch(rev):
133 def ctxmatch(rev):
142 ctx = repo[rev]
134 ctx = repo[rev]
143 return any(f for f in ctx.files() if sparsematch(f))
135 return any(f for f in ctx.files() if sparsematch(f))
144 revs = revs.filter(ctxmatch)
136 revs = revs.filter(ctxmatch)
145 return revs
137 return revs
146 extensions.wrapfunction(cmdutil, '_logrevs', _logrevs)
138 extensions.wrapfunction(cmdutil, '_logrevs', _logrevs)
147
139
148 def _clonesparsecmd(orig, ui, repo, *args, **opts):
140 def _clonesparsecmd(orig, ui, repo, *args, **opts):
149 include_pat = opts.get('include')
141 include_pat = opts.get('include')
150 exclude_pat = opts.get('exclude')
142 exclude_pat = opts.get('exclude')
151 enableprofile_pat = opts.get('enable_profile')
143 enableprofile_pat = opts.get('enable_profile')
152 include = exclude = enableprofile = False
144 include = exclude = enableprofile = False
153 if include_pat:
145 if include_pat:
154 pat = include_pat
146 pat = include_pat
155 include = True
147 include = True
156 if exclude_pat:
148 if exclude_pat:
157 pat = exclude_pat
149 pat = exclude_pat
158 exclude = True
150 exclude = True
159 if enableprofile_pat:
151 if enableprofile_pat:
160 pat = enableprofile_pat
152 pat = enableprofile_pat
161 enableprofile = True
153 enableprofile = True
162 if sum([include, exclude, enableprofile]) > 1:
154 if sum([include, exclude, enableprofile]) > 1:
163 raise error.Abort(_("too many flags specified."))
155 raise error.Abort(_("too many flags specified."))
164 if include or exclude or enableprofile:
156 if include or exclude or enableprofile:
165 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
157 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
166 _config(self.ui, self.unfiltered(), pat, {}, include=include,
158 _config(self.ui, self.unfiltered(), pat, {}, include=include,
167 exclude=exclude, enableprofile=enableprofile)
159 exclude=exclude, enableprofile=enableprofile)
168 return orig(self, node, overwrite, *args, **kwargs)
160 return orig(self, node, overwrite, *args, **kwargs)
169 extensions.wrapfunction(hg, 'updaterepo', clonesparse)
161 extensions.wrapfunction(hg, 'updaterepo', clonesparse)
170 return orig(ui, repo, *args, **opts)
162 return orig(ui, repo, *args, **opts)
171
163
172 def _setupclone(ui):
164 def _setupclone(ui):
173 entry = commands.table['^clone']
165 entry = commands.table['^clone']
174 entry[1].append(('', 'enable-profile', [],
166 entry[1].append(('', 'enable-profile', [],
175 'enable a sparse profile'))
167 'enable a sparse profile'))
176 entry[1].append(('', 'include', [],
168 entry[1].append(('', 'include', [],
177 'include sparse pattern'))
169 'include sparse pattern'))
178 entry[1].append(('', 'exclude', [],
170 entry[1].append(('', 'exclude', [],
179 'exclude sparse pattern'))
171 'exclude sparse pattern'))
180 extensions.wrapcommand(commands.table, 'clone', _clonesparsecmd)
172 extensions.wrapcommand(commands.table, 'clone', _clonesparsecmd)
181
173
182 def _setupadd(ui):
174 def _setupadd(ui):
183 entry = commands.table['^add']
175 entry = commands.table['^add']
184 entry[1].append(('s', 'sparse', None,
176 entry[1].append(('s', 'sparse', None,
185 'also include directories of added files in sparse config'))
177 'also include directories of added files in sparse config'))
186
178
187 def _add(orig, ui, repo, *pats, **opts):
179 def _add(orig, ui, repo, *pats, **opts):
188 if opts.get('sparse'):
180 if opts.get('sparse'):
189 dirs = set()
181 dirs = set()
190 for pat in pats:
182 for pat in pats:
191 dirname, basename = util.split(pat)
183 dirname, basename = util.split(pat)
192 dirs.add(dirname)
184 dirs.add(dirname)
193 _config(ui, repo, list(dirs), opts, include=True)
185 _config(ui, repo, list(dirs), opts, include=True)
194 return orig(ui, repo, *pats, **opts)
186 return orig(ui, repo, *pats, **opts)
195
187
196 extensions.wrapcommand(commands.table, 'add', _add)
188 extensions.wrapcommand(commands.table, 'add', _add)
197
189
198 def _setupdirstate(ui):
190 def _setupdirstate(ui):
199 """Modify the dirstate to prevent stat'ing excluded files,
191 """Modify the dirstate to prevent stat'ing excluded files,
200 and to prevent modifications to files outside the checkout.
192 and to prevent modifications to files outside the checkout.
201 """
193 """
202
194
203 def _dirstate(orig, repo):
204 dirstate = orig(repo)
205 dirstate.repo = repo
206 return dirstate
207 extensions.wrapfunction(
208 localrepo.localrepository.dirstate, 'func', _dirstate)
209
210 # The atrocity below is needed to wrap dirstate._ignore. It is a cached
195 # The atrocity below is needed to wrap dirstate._ignore. It is a cached
211 # property, which means normal function wrapping doesn't work.
196 # property, which means normal function wrapping doesn't work.
212 class ignorewrapper(object):
197 class ignorewrapper(object):
213 def __init__(self, orig):
198 def __init__(self, orig):
214 self.orig = orig
199 self.orig = orig
215 self.origignore = None
200 self.origignore = None
216 self.func = None
201 self.func = None
217 self.sparsematch = None
202 self.sparsematch = None
218
203
219 def __get__(self, obj, type=None):
204 def __get__(self, obj, type=None):
220 repo = obj.repo
221 origignore = self.orig.__get__(obj)
205 origignore = self.orig.__get__(obj)
222
206
223 sparsematch = sparse.matcher(repo)
207 sparsematch = obj._sparsematcher
224 if sparsematch.always():
208 if sparsematch.always():
225 return origignore
209 return origignore
226
210
227 if self.sparsematch != sparsematch or self.origignore != origignore:
211 if self.sparsematch != sparsematch or self.origignore != origignore:
228 self.func = matchmod.unionmatcher([
212 self.func = matchmod.unionmatcher([
229 origignore, matchmod.negatematcher(sparsematch)])
213 origignore, matchmod.negatematcher(sparsematch)])
230 self.sparsematch = sparsematch
214 self.sparsematch = sparsematch
231 self.origignore = origignore
215 self.origignore = origignore
232 return self.func
216 return self.func
233
217
234 def __set__(self, obj, value):
218 def __set__(self, obj, value):
235 return self.orig.__set__(obj, value)
219 return self.orig.__set__(obj, value)
236
220
237 def __delete__(self, obj):
221 def __delete__(self, obj):
238 return self.orig.__delete__(obj)
222 return self.orig.__delete__(obj)
239
223
240 replacefilecache(dirstate.dirstate, '_ignore', ignorewrapper)
224 replacefilecache(dirstate.dirstate, '_ignore', ignorewrapper)
241
225
242 # dirstate.rebuild should not add non-matching files
226 # dirstate.rebuild should not add non-matching files
243 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
227 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
244 matcher = sparse.matcher(self.repo)
228 matcher = self._sparsematcher
245 if not matcher.always():
229 if not matcher.always():
246 allfiles = allfiles.matches(matcher)
230 allfiles = allfiles.matches(matcher)
247 if changedfiles:
231 if changedfiles:
248 changedfiles = [f for f in changedfiles if matcher(f)]
232 changedfiles = [f for f in changedfiles if matcher(f)]
249
233
250 if changedfiles is not None:
234 if changedfiles is not None:
251 # In _rebuild, these files will be deleted from the dirstate
235 # In _rebuild, these files will be deleted from the dirstate
252 # when they are not found to be in allfiles
236 # when they are not found to be in allfiles
253 dirstatefilestoremove = set(f for f in self if not matcher(f))
237 dirstatefilestoremove = set(f for f in self if not matcher(f))
254 changedfiles = dirstatefilestoremove.union(changedfiles)
238 changedfiles = dirstatefilestoremove.union(changedfiles)
255
239
256 return orig(self, parent, allfiles, changedfiles)
240 return orig(self, parent, allfiles, changedfiles)
257 extensions.wrapfunction(dirstate.dirstate, 'rebuild', _rebuild)
241 extensions.wrapfunction(dirstate.dirstate, 'rebuild', _rebuild)
258
242
259 # Prevent adding files that are outside the sparse checkout
243 # Prevent adding files that are outside the sparse checkout
260 editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
244 editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
261 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
245 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
262 '`hg add -s <file>` to include file directory while adding')
246 '`hg add -s <file>` to include file directory while adding')
263 for func in editfuncs:
247 for func in editfuncs:
264 def _wrapper(orig, self, *args):
248 def _wrapper(orig, self, *args):
265 repo = self.repo
249 sparsematch = self._sparsematcher
266 sparsematch = sparse.matcher(repo)
267 if not sparsematch.always():
250 if not sparsematch.always():
268 for f in args:
251 for f in args:
269 if (f is not None and not sparsematch(f) and
252 if (f is not None and not sparsematch(f) and
270 f not in self):
253 f not in self):
271 raise error.Abort(_("cannot add '%s' - it is outside "
254 raise error.Abort(_("cannot add '%s' - it is outside "
272 "the sparse checkout") % f,
255 "the sparse checkout") % f,
273 hint=hint)
256 hint=hint)
274 return orig(self, *args)
257 return orig(self, *args)
275 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
258 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
276
259
277 @command('^debugsparse', [
260 @command('^debugsparse', [
278 ('I', 'include', False, _('include files in the sparse checkout')),
261 ('I', 'include', False, _('include files in the sparse checkout')),
279 ('X', 'exclude', False, _('exclude files in the sparse checkout')),
262 ('X', 'exclude', False, _('exclude files in the sparse checkout')),
280 ('d', 'delete', False, _('delete an include/exclude rule')),
263 ('d', 'delete', False, _('delete an include/exclude rule')),
281 ('f', 'force', False, _('allow changing rules even with pending changes')),
264 ('f', 'force', False, _('allow changing rules even with pending changes')),
282 ('', 'enable-profile', False, _('enables the specified profile')),
265 ('', 'enable-profile', False, _('enables the specified profile')),
283 ('', 'disable-profile', False, _('disables the specified profile')),
266 ('', 'disable-profile', False, _('disables the specified profile')),
284 ('', 'import-rules', False, _('imports rules from a file')),
267 ('', 'import-rules', False, _('imports rules from a file')),
285 ('', 'clear-rules', False, _('clears local include/exclude rules')),
268 ('', 'clear-rules', False, _('clears local include/exclude rules')),
286 ('', 'refresh', False, _('updates the working after sparseness changes')),
269 ('', 'refresh', False, _('updates the working after sparseness changes')),
287 ('', 'reset', False, _('makes the repo full again')),
270 ('', 'reset', False, _('makes the repo full again')),
288 ] + commands.templateopts,
271 ] + commands.templateopts,
289 _('[--OPTION] PATTERN...'))
272 _('[--OPTION] PATTERN...'))
290 def debugsparse(ui, repo, *pats, **opts):
273 def debugsparse(ui, repo, *pats, **opts):
291 """make the current checkout sparse, or edit the existing checkout
274 """make the current checkout sparse, or edit the existing checkout
292
275
293 The sparse command is used to make the current checkout sparse.
276 The sparse command is used to make the current checkout sparse.
294 This means files that don't meet the sparse condition will not be
277 This means files that don't meet the sparse condition will not be
295 written to disk, or show up in any working copy operations. It does
278 written to disk, or show up in any working copy operations. It does
296 not affect files in history in any way.
279 not affect files in history in any way.
297
280
298 Passing no arguments prints the currently applied sparse rules.
281 Passing no arguments prints the currently applied sparse rules.
299
282
300 --include and --exclude are used to add and remove files from the sparse
283 --include and --exclude are used to add and remove files from the sparse
301 checkout. The effects of adding an include or exclude rule are applied
284 checkout. The effects of adding an include or exclude rule are applied
302 immediately. If applying the new rule would cause a file with pending
285 immediately. If applying the new rule would cause a file with pending
303 changes to be added or removed, the command will fail. Pass --force to
286 changes to be added or removed, the command will fail. Pass --force to
304 force a rule change even with pending changes (the changes on disk will
287 force a rule change even with pending changes (the changes on disk will
305 be preserved).
288 be preserved).
306
289
307 --delete removes an existing include/exclude rule. The effects are
290 --delete removes an existing include/exclude rule. The effects are
308 immediate.
291 immediate.
309
292
310 --refresh refreshes the files on disk based on the sparse rules. This is
293 --refresh refreshes the files on disk based on the sparse rules. This is
311 only necessary if .hg/sparse was changed by hand.
294 only necessary if .hg/sparse was changed by hand.
312
295
313 --enable-profile and --disable-profile accept a path to a .hgsparse file.
296 --enable-profile and --disable-profile accept a path to a .hgsparse file.
314 This allows defining sparse checkouts and tracking them inside the
297 This allows defining sparse checkouts and tracking them inside the
315 repository. This is useful for defining commonly used sparse checkouts for
298 repository. This is useful for defining commonly used sparse checkouts for
316 many people to use. As the profile definition changes over time, the sparse
299 many people to use. As the profile definition changes over time, the sparse
317 checkout will automatically be updated appropriately, depending on which
300 checkout will automatically be updated appropriately, depending on which
318 changeset is checked out. Changes to .hgsparse are not applied until they
301 changeset is checked out. Changes to .hgsparse are not applied until they
319 have been committed.
302 have been committed.
320
303
321 --import-rules accepts a path to a file containing rules in the .hgsparse
304 --import-rules accepts a path to a file containing rules in the .hgsparse
322 format, allowing you to add --include, --exclude and --enable-profile rules
305 format, allowing you to add --include, --exclude and --enable-profile rules
323 in bulk. Like the --include, --exclude and --enable-profile switches, the
306 in bulk. Like the --include, --exclude and --enable-profile switches, the
324 changes are applied immediately.
307 changes are applied immediately.
325
308
326 --clear-rules removes all local include and exclude rules, while leaving
309 --clear-rules removes all local include and exclude rules, while leaving
327 any enabled profiles in place.
310 any enabled profiles in place.
328
311
329 Returns 0 if editing the sparse checkout succeeds.
312 Returns 0 if editing the sparse checkout succeeds.
330 """
313 """
331 include = opts.get('include')
314 include = opts.get('include')
332 exclude = opts.get('exclude')
315 exclude = opts.get('exclude')
333 force = opts.get('force')
316 force = opts.get('force')
334 enableprofile = opts.get('enable_profile')
317 enableprofile = opts.get('enable_profile')
335 disableprofile = opts.get('disable_profile')
318 disableprofile = opts.get('disable_profile')
336 importrules = opts.get('import_rules')
319 importrules = opts.get('import_rules')
337 clearrules = opts.get('clear_rules')
320 clearrules = opts.get('clear_rules')
338 delete = opts.get('delete')
321 delete = opts.get('delete')
339 refresh = opts.get('refresh')
322 refresh = opts.get('refresh')
340 reset = opts.get('reset')
323 reset = opts.get('reset')
341 count = sum([include, exclude, enableprofile, disableprofile, delete,
324 count = sum([include, exclude, enableprofile, disableprofile, delete,
342 importrules, refresh, clearrules, reset])
325 importrules, refresh, clearrules, reset])
343 if count > 1:
326 if count > 1:
344 raise error.Abort(_("too many flags specified"))
327 raise error.Abort(_("too many flags specified"))
345
328
346 if count == 0:
329 if count == 0:
347 if repo.vfs.exists('sparse'):
330 if repo.vfs.exists('sparse'):
348 ui.status(repo.vfs.read("sparse") + "\n")
331 ui.status(repo.vfs.read("sparse") + "\n")
349 temporaryincludes = sparse.readtemporaryincludes(repo)
332 temporaryincludes = sparse.readtemporaryincludes(repo)
350 if temporaryincludes:
333 if temporaryincludes:
351 ui.status(_("Temporarily Included Files (for merge/rebase):\n"))
334 ui.status(_("Temporarily Included Files (for merge/rebase):\n"))
352 ui.status(("\n".join(temporaryincludes) + "\n"))
335 ui.status(("\n".join(temporaryincludes) + "\n"))
353 else:
336 else:
354 ui.status(_('repo is not sparse\n'))
337 ui.status(_('repo is not sparse\n'))
355 return
338 return
356
339
357 if include or exclude or delete or reset or enableprofile or disableprofile:
340 if include or exclude or delete or reset or enableprofile or disableprofile:
358 _config(ui, repo, pats, opts, include=include, exclude=exclude,
341 _config(ui, repo, pats, opts, include=include, exclude=exclude,
359 reset=reset, delete=delete, enableprofile=enableprofile,
342 reset=reset, delete=delete, enableprofile=enableprofile,
360 disableprofile=disableprofile, force=force)
343 disableprofile=disableprofile, force=force)
361
344
362 if importrules:
345 if importrules:
363 sparse.importfromfiles(repo, opts, pats, force=force)
346 sparse.importfromfiles(repo, opts, pats, force=force)
364
347
365 if clearrules:
348 if clearrules:
366 sparse.clearrules(repo, force=force)
349 sparse.clearrules(repo, force=force)
367
350
368 if refresh:
351 if refresh:
369 try:
352 try:
370 wlock = repo.wlock()
353 wlock = repo.wlock()
371 fcounts = map(
354 fcounts = map(
372 len,
355 len,
373 sparse.refreshwdir(repo, repo.status(), sparse.matcher(repo),
356 sparse.refreshwdir(repo, repo.status(), sparse.matcher(repo),
374 force=force))
357 force=force))
375 sparse.printchanges(ui, opts, added=fcounts[0], dropped=fcounts[1],
358 sparse.printchanges(ui, opts, added=fcounts[0], dropped=fcounts[1],
376 conflicting=fcounts[2])
359 conflicting=fcounts[2])
377 finally:
360 finally:
378 wlock.release()
361 wlock.release()
379
362
380 def _config(ui, repo, pats, opts, include=False, exclude=False, reset=False,
363 def _config(ui, repo, pats, opts, include=False, exclude=False, reset=False,
381 delete=False, enableprofile=False, disableprofile=False,
364 delete=False, enableprofile=False, disableprofile=False,
382 force=False):
365 force=False):
383 """
366 """
384 Perform a sparse config update. Only one of the kwargs may be specified.
367 Perform a sparse config update. Only one of the kwargs may be specified.
385 """
368 """
386 wlock = repo.wlock()
369 wlock = repo.wlock()
387 try:
370 try:
388 oldsparsematch = sparse.matcher(repo)
371 oldsparsematch = sparse.matcher(repo)
389
372
390 raw = repo.vfs.tryread('sparse')
373 raw = repo.vfs.tryread('sparse')
391 if raw:
374 if raw:
392 oldinclude, oldexclude, oldprofiles = map(
375 oldinclude, oldexclude, oldprofiles = map(
393 set, sparse.parseconfig(ui, raw))
376 set, sparse.parseconfig(ui, raw))
394 else:
377 else:
395 oldinclude = set()
378 oldinclude = set()
396 oldexclude = set()
379 oldexclude = set()
397 oldprofiles = set()
380 oldprofiles = set()
398
381
399 try:
382 try:
400 if reset:
383 if reset:
401 newinclude = set()
384 newinclude = set()
402 newexclude = set()
385 newexclude = set()
403 newprofiles = set()
386 newprofiles = set()
404 else:
387 else:
405 newinclude = set(oldinclude)
388 newinclude = set(oldinclude)
406 newexclude = set(oldexclude)
389 newexclude = set(oldexclude)
407 newprofiles = set(oldprofiles)
390 newprofiles = set(oldprofiles)
408
391
409 oldstatus = repo.status()
392 oldstatus = repo.status()
410
393
411 if any(pat.startswith('/') for pat in pats):
394 if any(pat.startswith('/') for pat in pats):
412 ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
395 ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
413 % ([pat for pat in pats if pat.startswith('/')]))
396 % ([pat for pat in pats if pat.startswith('/')]))
414 elif include:
397 elif include:
415 newinclude.update(pats)
398 newinclude.update(pats)
416 elif exclude:
399 elif exclude:
417 newexclude.update(pats)
400 newexclude.update(pats)
418 elif enableprofile:
401 elif enableprofile:
419 newprofiles.update(pats)
402 newprofiles.update(pats)
420 elif disableprofile:
403 elif disableprofile:
421 newprofiles.difference_update(pats)
404 newprofiles.difference_update(pats)
422 elif delete:
405 elif delete:
423 newinclude.difference_update(pats)
406 newinclude.difference_update(pats)
424 newexclude.difference_update(pats)
407 newexclude.difference_update(pats)
425
408
426 sparse.writeconfig(repo, newinclude, newexclude, newprofiles)
409 sparse.writeconfig(repo, newinclude, newexclude, newprofiles)
427
410
428 fcounts = map(
411 fcounts = map(
429 len,
412 len,
430 sparse.refreshwdir(repo, oldstatus, oldsparsematch,
413 sparse.refreshwdir(repo, oldstatus, oldsparsematch,
431 force=force))
414 force=force))
432
415
433 profilecount = (len(newprofiles - oldprofiles) -
416 profilecount = (len(newprofiles - oldprofiles) -
434 len(oldprofiles - newprofiles))
417 len(oldprofiles - newprofiles))
435 includecount = (len(newinclude - oldinclude) -
418 includecount = (len(newinclude - oldinclude) -
436 len(oldinclude - newinclude))
419 len(oldinclude - newinclude))
437 excludecount = (len(newexclude - oldexclude) -
420 excludecount = (len(newexclude - oldexclude) -
438 len(oldexclude - newexclude))
421 len(oldexclude - newexclude))
439 sparse.printchanges(ui, opts, profilecount, includecount,
422 sparse.printchanges(ui, opts, profilecount, includecount,
440 excludecount, *fcounts)
423 excludecount, *fcounts)
441 except Exception:
424 except Exception:
442 sparse.writeconfig(repo, oldinclude, oldexclude, oldprofiles)
425 sparse.writeconfig(repo, oldinclude, oldexclude, oldprofiles)
443 raise
426 raise
444 finally:
427 finally:
445 wlock.release()
428 wlock.release()
@@ -1,1336 +1,1350 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import os
13 import os
14 import stat
14 import stat
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 match as matchmod,
21 match as matchmod,
22 pathutil,
22 pathutil,
23 policy,
23 policy,
24 pycompat,
24 pycompat,
25 scmutil,
25 scmutil,
26 txnutil,
26 txnutil,
27 util,
27 util,
28 )
28 )
29
29
30 parsers = policy.importmod(r'parsers')
30 parsers = policy.importmod(r'parsers')
31
31
32 propertycache = util.propertycache
32 propertycache = util.propertycache
33 filecache = scmutil.filecache
33 filecache = scmutil.filecache
34 _rangemask = 0x7fffffff
34 _rangemask = 0x7fffffff
35
35
36 dirstatetuple = parsers.dirstatetuple
36 dirstatetuple = parsers.dirstatetuple
37
37
38 class repocache(filecache):
38 class repocache(filecache):
39 """filecache for files in .hg/"""
39 """filecache for files in .hg/"""
40 def join(self, obj, fname):
40 def join(self, obj, fname):
41 return obj._opener.join(fname)
41 return obj._opener.join(fname)
42
42
43 class rootcache(filecache):
43 class rootcache(filecache):
44 """filecache for files in the repository root"""
44 """filecache for files in the repository root"""
45 def join(self, obj, fname):
45 def join(self, obj, fname):
46 return obj._join(fname)
46 return obj._join(fname)
47
47
48 def _getfsnow(vfs):
48 def _getfsnow(vfs):
49 '''Get "now" timestamp on filesystem'''
49 '''Get "now" timestamp on filesystem'''
50 tmpfd, tmpname = vfs.mkstemp()
50 tmpfd, tmpname = vfs.mkstemp()
51 try:
51 try:
52 return os.fstat(tmpfd).st_mtime
52 return os.fstat(tmpfd).st_mtime
53 finally:
53 finally:
54 os.close(tmpfd)
54 os.close(tmpfd)
55 vfs.unlink(tmpname)
55 vfs.unlink(tmpname)
56
56
57 def nonnormalentries(dmap):
57 def nonnormalentries(dmap):
58 '''Compute the nonnormal dirstate entries from the dmap'''
58 '''Compute the nonnormal dirstate entries from the dmap'''
59 try:
59 try:
60 return parsers.nonnormalotherparententries(dmap)
60 return parsers.nonnormalotherparententries(dmap)
61 except AttributeError:
61 except AttributeError:
62 nonnorm = set()
62 nonnorm = set()
63 otherparent = set()
63 otherparent = set()
64 for fname, e in dmap.iteritems():
64 for fname, e in dmap.iteritems():
65 if e[0] != 'n' or e[3] == -1:
65 if e[0] != 'n' or e[3] == -1:
66 nonnorm.add(fname)
66 nonnorm.add(fname)
67 if e[0] == 'n' and e[2] == -2:
67 if e[0] == 'n' and e[2] == -2:
68 otherparent.add(fname)
68 otherparent.add(fname)
69 return nonnorm, otherparent
69 return nonnorm, otherparent
70
70
71 class dirstate(object):
71 class dirstate(object):
72
72
73 def __init__(self, opener, ui, root, validate):
73 def __init__(self, opener, ui, root, validate, sparsematchfn):
74 '''Create a new dirstate object.
74 '''Create a new dirstate object.
75
75
76 opener is an open()-like callable that can be used to open the
76 opener is an open()-like callable that can be used to open the
77 dirstate file; root is the root of the directory tracked by
77 dirstate file; root is the root of the directory tracked by
78 the dirstate.
78 the dirstate.
79 '''
79 '''
80 self._opener = opener
80 self._opener = opener
81 self._validate = validate
81 self._validate = validate
82 self._root = root
82 self._root = root
83 self._sparsematchfn = sparsematchfn
83 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
84 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
84 # UNC path pointing to root share (issue4557)
85 # UNC path pointing to root share (issue4557)
85 self._rootdir = pathutil.normasprefix(root)
86 self._rootdir = pathutil.normasprefix(root)
86 self._dirty = False
87 self._dirty = False
87 self._dirtypl = False
88 self._dirtypl = False
88 self._lastnormaltime = 0
89 self._lastnormaltime = 0
89 self._ui = ui
90 self._ui = ui
90 self._filecache = {}
91 self._filecache = {}
91 self._parentwriters = 0
92 self._parentwriters = 0
92 self._filename = 'dirstate'
93 self._filename = 'dirstate'
93 self._pendingfilename = '%s.pending' % self._filename
94 self._pendingfilename = '%s.pending' % self._filename
94 self._plchangecallbacks = {}
95 self._plchangecallbacks = {}
95 self._origpl = None
96 self._origpl = None
96 self._updatedfiles = set()
97 self._updatedfiles = set()
97
98
98 # for consistent view between _pl() and _read() invocations
99 # for consistent view between _pl() and _read() invocations
99 self._pendingmode = None
100 self._pendingmode = None
100
101
101 @contextlib.contextmanager
102 @contextlib.contextmanager
102 def parentchange(self):
103 def parentchange(self):
103 '''Context manager for handling dirstate parents.
104 '''Context manager for handling dirstate parents.
104
105
105 If an exception occurs in the scope of the context manager,
106 If an exception occurs in the scope of the context manager,
106 the incoherent dirstate won't be written when wlock is
107 the incoherent dirstate won't be written when wlock is
107 released.
108 released.
108 '''
109 '''
109 self._parentwriters += 1
110 self._parentwriters += 1
110 yield
111 yield
111 # Typically we want the "undo" step of a context manager in a
112 # Typically we want the "undo" step of a context manager in a
112 # finally block so it happens even when an exception
113 # finally block so it happens even when an exception
113 # occurs. In this case, however, we only want to decrement
114 # occurs. In this case, however, we only want to decrement
114 # parentwriters if the code in the with statement exits
115 # parentwriters if the code in the with statement exits
115 # normally, so we don't have a try/finally here on purpose.
116 # normally, so we don't have a try/finally here on purpose.
116 self._parentwriters -= 1
117 self._parentwriters -= 1
117
118
118 def beginparentchange(self):
119 def beginparentchange(self):
119 '''Marks the beginning of a set of changes that involve changing
120 '''Marks the beginning of a set of changes that involve changing
120 the dirstate parents. If there is an exception during this time,
121 the dirstate parents. If there is an exception during this time,
121 the dirstate will not be written when the wlock is released. This
122 the dirstate will not be written when the wlock is released. This
122 prevents writing an incoherent dirstate where the parent doesn't
123 prevents writing an incoherent dirstate where the parent doesn't
123 match the contents.
124 match the contents.
124 '''
125 '''
125 self._ui.deprecwarn('beginparentchange is obsoleted by the '
126 self._ui.deprecwarn('beginparentchange is obsoleted by the '
126 'parentchange context manager.', '4.3')
127 'parentchange context manager.', '4.3')
127 self._parentwriters += 1
128 self._parentwriters += 1
128
129
129 def endparentchange(self):
130 def endparentchange(self):
130 '''Marks the end of a set of changes that involve changing the
131 '''Marks the end of a set of changes that involve changing the
131 dirstate parents. Once all parent changes have been marked done,
132 dirstate parents. Once all parent changes have been marked done,
132 the wlock will be free to write the dirstate on release.
133 the wlock will be free to write the dirstate on release.
133 '''
134 '''
134 self._ui.deprecwarn('endparentchange is obsoleted by the '
135 self._ui.deprecwarn('endparentchange is obsoleted by the '
135 'parentchange context manager.', '4.3')
136 'parentchange context manager.', '4.3')
136 if self._parentwriters > 0:
137 if self._parentwriters > 0:
137 self._parentwriters -= 1
138 self._parentwriters -= 1
138
139
139 def pendingparentchange(self):
140 def pendingparentchange(self):
140 '''Returns true if the dirstate is in the middle of a set of changes
141 '''Returns true if the dirstate is in the middle of a set of changes
141 that modify the dirstate parent.
142 that modify the dirstate parent.
142 '''
143 '''
143 return self._parentwriters > 0
144 return self._parentwriters > 0
144
145
145 @propertycache
146 @propertycache
146 def _map(self):
147 def _map(self):
147 '''Return the dirstate contents as a map from filename to
148 '''Return the dirstate contents as a map from filename to
148 (state, mode, size, time).'''
149 (state, mode, size, time).'''
149 self._read()
150 self._read()
150 return self._map
151 return self._map
151
152
152 @propertycache
153 @propertycache
153 def _copymap(self):
154 def _copymap(self):
154 self._read()
155 self._read()
155 return self._copymap
156 return self._copymap
156
157
157 @propertycache
158 @propertycache
158 def _identity(self):
159 def _identity(self):
159 self._read()
160 self._read()
160 return self._identity
161 return self._identity
161
162
162 @propertycache
163 @propertycache
163 def _nonnormalset(self):
164 def _nonnormalset(self):
164 nonnorm, otherparents = nonnormalentries(self._map)
165 nonnorm, otherparents = nonnormalentries(self._map)
165 self._otherparentset = otherparents
166 self._otherparentset = otherparents
166 return nonnorm
167 return nonnorm
167
168
168 @propertycache
169 @propertycache
169 def _otherparentset(self):
170 def _otherparentset(self):
170 nonnorm, otherparents = nonnormalentries(self._map)
171 nonnorm, otherparents = nonnormalentries(self._map)
171 self._nonnormalset = nonnorm
172 self._nonnormalset = nonnorm
172 return otherparents
173 return otherparents
173
174
174 @propertycache
175 @propertycache
175 def _filefoldmap(self):
176 def _filefoldmap(self):
176 try:
177 try:
177 makefilefoldmap = parsers.make_file_foldmap
178 makefilefoldmap = parsers.make_file_foldmap
178 except AttributeError:
179 except AttributeError:
179 pass
180 pass
180 else:
181 else:
181 return makefilefoldmap(self._map, util.normcasespec,
182 return makefilefoldmap(self._map, util.normcasespec,
182 util.normcasefallback)
183 util.normcasefallback)
183
184
184 f = {}
185 f = {}
185 normcase = util.normcase
186 normcase = util.normcase
186 for name, s in self._map.iteritems():
187 for name, s in self._map.iteritems():
187 if s[0] != 'r':
188 if s[0] != 'r':
188 f[normcase(name)] = name
189 f[normcase(name)] = name
189 f['.'] = '.' # prevents useless util.fspath() invocation
190 f['.'] = '.' # prevents useless util.fspath() invocation
190 return f
191 return f
191
192
192 @propertycache
193 @propertycache
193 def _dirfoldmap(self):
194 def _dirfoldmap(self):
194 f = {}
195 f = {}
195 normcase = util.normcase
196 normcase = util.normcase
196 for name in self._dirs:
197 for name in self._dirs:
197 f[normcase(name)] = name
198 f[normcase(name)] = name
198 return f
199 return f
199
200
201 @property
202 def _sparsematcher(self):
203 """The matcher for the sparse checkout.
204
205 The working directory may not include every file from a manifest. The
206 matcher obtained by this property will match a path if it is to be
207 included in the working directory.
208 """
209 # TODO there is potential to cache this property. For now, the matcher
210 # is resolved on every access. (But the called function does use a
211 # cache to keep the lookup fast.)
212 return self._sparsematchfn()
213
200 @repocache('branch')
214 @repocache('branch')
201 def _branch(self):
215 def _branch(self):
202 try:
216 try:
203 return self._opener.read("branch").strip() or "default"
217 return self._opener.read("branch").strip() or "default"
204 except IOError as inst:
218 except IOError as inst:
205 if inst.errno != errno.ENOENT:
219 if inst.errno != errno.ENOENT:
206 raise
220 raise
207 return "default"
221 return "default"
208
222
209 @propertycache
223 @propertycache
210 def _pl(self):
224 def _pl(self):
211 try:
225 try:
212 fp = self._opendirstatefile()
226 fp = self._opendirstatefile()
213 st = fp.read(40)
227 st = fp.read(40)
214 fp.close()
228 fp.close()
215 l = len(st)
229 l = len(st)
216 if l == 40:
230 if l == 40:
217 return st[:20], st[20:40]
231 return st[:20], st[20:40]
218 elif l > 0 and l < 40:
232 elif l > 0 and l < 40:
219 raise error.Abort(_('working directory state appears damaged!'))
233 raise error.Abort(_('working directory state appears damaged!'))
220 except IOError as err:
234 except IOError as err:
221 if err.errno != errno.ENOENT:
235 if err.errno != errno.ENOENT:
222 raise
236 raise
223 return [nullid, nullid]
237 return [nullid, nullid]
224
238
225 @propertycache
239 @propertycache
226 def _dirs(self):
240 def _dirs(self):
227 return util.dirs(self._map, 'r')
241 return util.dirs(self._map, 'r')
228
242
229 def dirs(self):
243 def dirs(self):
230 return self._dirs
244 return self._dirs
231
245
232 @rootcache('.hgignore')
246 @rootcache('.hgignore')
233 def _ignore(self):
247 def _ignore(self):
234 files = self._ignorefiles()
248 files = self._ignorefiles()
235 if not files:
249 if not files:
236 return matchmod.never(self._root, '')
250 return matchmod.never(self._root, '')
237
251
238 pats = ['include:%s' % f for f in files]
252 pats = ['include:%s' % f for f in files]
239 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
253 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
240
254
241 @propertycache
255 @propertycache
242 def _slash(self):
256 def _slash(self):
243 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
257 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
244
258
245 @propertycache
259 @propertycache
246 def _checklink(self):
260 def _checklink(self):
247 return util.checklink(self._root)
261 return util.checklink(self._root)
248
262
249 @propertycache
263 @propertycache
250 def _checkexec(self):
264 def _checkexec(self):
251 return util.checkexec(self._root)
265 return util.checkexec(self._root)
252
266
253 @propertycache
267 @propertycache
254 def _checkcase(self):
268 def _checkcase(self):
255 return not util.fscasesensitive(self._join('.hg'))
269 return not util.fscasesensitive(self._join('.hg'))
256
270
257 def _join(self, f):
271 def _join(self, f):
258 # much faster than os.path.join()
272 # much faster than os.path.join()
259 # it's safe because f is always a relative path
273 # it's safe because f is always a relative path
260 return self._rootdir + f
274 return self._rootdir + f
261
275
262 def flagfunc(self, buildfallback):
276 def flagfunc(self, buildfallback):
263 if self._checklink and self._checkexec:
277 if self._checklink and self._checkexec:
264 def f(x):
278 def f(x):
265 try:
279 try:
266 st = os.lstat(self._join(x))
280 st = os.lstat(self._join(x))
267 if util.statislink(st):
281 if util.statislink(st):
268 return 'l'
282 return 'l'
269 if util.statisexec(st):
283 if util.statisexec(st):
270 return 'x'
284 return 'x'
271 except OSError:
285 except OSError:
272 pass
286 pass
273 return ''
287 return ''
274 return f
288 return f
275
289
276 fallback = buildfallback()
290 fallback = buildfallback()
277 if self._checklink:
291 if self._checklink:
278 def f(x):
292 def f(x):
279 if os.path.islink(self._join(x)):
293 if os.path.islink(self._join(x)):
280 return 'l'
294 return 'l'
281 if 'x' in fallback(x):
295 if 'x' in fallback(x):
282 return 'x'
296 return 'x'
283 return ''
297 return ''
284 return f
298 return f
285 if self._checkexec:
299 if self._checkexec:
286 def f(x):
300 def f(x):
287 if 'l' in fallback(x):
301 if 'l' in fallback(x):
288 return 'l'
302 return 'l'
289 if util.isexec(self._join(x)):
303 if util.isexec(self._join(x)):
290 return 'x'
304 return 'x'
291 return ''
305 return ''
292 return f
306 return f
293 else:
307 else:
294 return fallback
308 return fallback
295
309
296 @propertycache
310 @propertycache
297 def _cwd(self):
311 def _cwd(self):
298 # internal config: ui.forcecwd
312 # internal config: ui.forcecwd
299 forcecwd = self._ui.config('ui', 'forcecwd')
313 forcecwd = self._ui.config('ui', 'forcecwd')
300 if forcecwd:
314 if forcecwd:
301 return forcecwd
315 return forcecwd
302 return pycompat.getcwd()
316 return pycompat.getcwd()
303
317
304 def getcwd(self):
318 def getcwd(self):
305 '''Return the path from which a canonical path is calculated.
319 '''Return the path from which a canonical path is calculated.
306
320
307 This path should be used to resolve file patterns or to convert
321 This path should be used to resolve file patterns or to convert
308 canonical paths back to file paths for display. It shouldn't be
322 canonical paths back to file paths for display. It shouldn't be
309 used to get real file paths. Use vfs functions instead.
323 used to get real file paths. Use vfs functions instead.
310 '''
324 '''
311 cwd = self._cwd
325 cwd = self._cwd
312 if cwd == self._root:
326 if cwd == self._root:
313 return ''
327 return ''
314 # self._root ends with a path separator if self._root is '/' or 'C:\'
328 # self._root ends with a path separator if self._root is '/' or 'C:\'
315 rootsep = self._root
329 rootsep = self._root
316 if not util.endswithsep(rootsep):
330 if not util.endswithsep(rootsep):
317 rootsep += pycompat.ossep
331 rootsep += pycompat.ossep
318 if cwd.startswith(rootsep):
332 if cwd.startswith(rootsep):
319 return cwd[len(rootsep):]
333 return cwd[len(rootsep):]
320 else:
334 else:
321 # we're outside the repo. return an absolute path.
335 # we're outside the repo. return an absolute path.
322 return cwd
336 return cwd
323
337
324 def pathto(self, f, cwd=None):
338 def pathto(self, f, cwd=None):
325 if cwd is None:
339 if cwd is None:
326 cwd = self.getcwd()
340 cwd = self.getcwd()
327 path = util.pathto(self._root, cwd, f)
341 path = util.pathto(self._root, cwd, f)
328 if self._slash:
342 if self._slash:
329 return util.pconvert(path)
343 return util.pconvert(path)
330 return path
344 return path
331
345
332 def __getitem__(self, key):
346 def __getitem__(self, key):
333 '''Return the current state of key (a filename) in the dirstate.
347 '''Return the current state of key (a filename) in the dirstate.
334
348
335 States are:
349 States are:
336 n normal
350 n normal
337 m needs merging
351 m needs merging
338 r marked for removal
352 r marked for removal
339 a marked for addition
353 a marked for addition
340 ? not tracked
354 ? not tracked
341 '''
355 '''
342 return self._map.get(key, ("?",))[0]
356 return self._map.get(key, ("?",))[0]
343
357
344 def __contains__(self, key):
358 def __contains__(self, key):
345 return key in self._map
359 return key in self._map
346
360
347 def __iter__(self):
361 def __iter__(self):
348 for x in sorted(self._map):
362 for x in sorted(self._map):
349 yield x
363 yield x
350
364
351 def items(self):
365 def items(self):
352 return self._map.iteritems()
366 return self._map.iteritems()
353
367
354 iteritems = items
368 iteritems = items
355
369
356 def parents(self):
370 def parents(self):
357 return [self._validate(p) for p in self._pl]
371 return [self._validate(p) for p in self._pl]
358
372
359 def p1(self):
373 def p1(self):
360 return self._validate(self._pl[0])
374 return self._validate(self._pl[0])
361
375
362 def p2(self):
376 def p2(self):
363 return self._validate(self._pl[1])
377 return self._validate(self._pl[1])
364
378
365 def branch(self):
379 def branch(self):
366 return encoding.tolocal(self._branch)
380 return encoding.tolocal(self._branch)
367
381
368 def setparents(self, p1, p2=nullid):
382 def setparents(self, p1, p2=nullid):
369 """Set dirstate parents to p1 and p2.
383 """Set dirstate parents to p1 and p2.
370
384
371 When moving from two parents to one, 'm' merged entries a
385 When moving from two parents to one, 'm' merged entries a
372 adjusted to normal and previous copy records discarded and
386 adjusted to normal and previous copy records discarded and
373 returned by the call.
387 returned by the call.
374
388
375 See localrepo.setparents()
389 See localrepo.setparents()
376 """
390 """
377 if self._parentwriters == 0:
391 if self._parentwriters == 0:
378 raise ValueError("cannot set dirstate parent without "
392 raise ValueError("cannot set dirstate parent without "
379 "calling dirstate.beginparentchange")
393 "calling dirstate.beginparentchange")
380
394
381 self._dirty = self._dirtypl = True
395 self._dirty = self._dirtypl = True
382 oldp2 = self._pl[1]
396 oldp2 = self._pl[1]
383 if self._origpl is None:
397 if self._origpl is None:
384 self._origpl = self._pl
398 self._origpl = self._pl
385 self._pl = p1, p2
399 self._pl = p1, p2
386 copies = {}
400 copies = {}
387 if oldp2 != nullid and p2 == nullid:
401 if oldp2 != nullid and p2 == nullid:
388 candidatefiles = self._nonnormalset.union(self._otherparentset)
402 candidatefiles = self._nonnormalset.union(self._otherparentset)
389 for f in candidatefiles:
403 for f in candidatefiles:
390 s = self._map.get(f)
404 s = self._map.get(f)
391 if s is None:
405 if s is None:
392 continue
406 continue
393
407
394 # Discard 'm' markers when moving away from a merge state
408 # Discard 'm' markers when moving away from a merge state
395 if s[0] == 'm':
409 if s[0] == 'm':
396 if f in self._copymap:
410 if f in self._copymap:
397 copies[f] = self._copymap[f]
411 copies[f] = self._copymap[f]
398 self.normallookup(f)
412 self.normallookup(f)
399 # Also fix up otherparent markers
413 # Also fix up otherparent markers
400 elif s[0] == 'n' and s[2] == -2:
414 elif s[0] == 'n' and s[2] == -2:
401 if f in self._copymap:
415 if f in self._copymap:
402 copies[f] = self._copymap[f]
416 copies[f] = self._copymap[f]
403 self.add(f)
417 self.add(f)
404 return copies
418 return copies
405
419
406 def setbranch(self, branch):
420 def setbranch(self, branch):
407 self._branch = encoding.fromlocal(branch)
421 self._branch = encoding.fromlocal(branch)
408 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
422 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
409 try:
423 try:
410 f.write(self._branch + '\n')
424 f.write(self._branch + '\n')
411 f.close()
425 f.close()
412
426
413 # make sure filecache has the correct stat info for _branch after
427 # make sure filecache has the correct stat info for _branch after
414 # replacing the underlying file
428 # replacing the underlying file
415 ce = self._filecache['_branch']
429 ce = self._filecache['_branch']
416 if ce:
430 if ce:
417 ce.refresh()
431 ce.refresh()
418 except: # re-raises
432 except: # re-raises
419 f.discard()
433 f.discard()
420 raise
434 raise
421
435
422 def _opendirstatefile(self):
436 def _opendirstatefile(self):
423 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
437 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
424 if self._pendingmode is not None and self._pendingmode != mode:
438 if self._pendingmode is not None and self._pendingmode != mode:
425 fp.close()
439 fp.close()
426 raise error.Abort(_('working directory state may be '
440 raise error.Abort(_('working directory state may be '
427 'changed parallelly'))
441 'changed parallelly'))
428 self._pendingmode = mode
442 self._pendingmode = mode
429 return fp
443 return fp
430
444
431 def _read(self):
445 def _read(self):
432 self._map = {}
446 self._map = {}
433 self._copymap = {}
447 self._copymap = {}
434 # ignore HG_PENDING because identity is used only for writing
448 # ignore HG_PENDING because identity is used only for writing
435 self._identity = util.filestat.frompath(
449 self._identity = util.filestat.frompath(
436 self._opener.join(self._filename))
450 self._opener.join(self._filename))
437 try:
451 try:
438 fp = self._opendirstatefile()
452 fp = self._opendirstatefile()
439 try:
453 try:
440 st = fp.read()
454 st = fp.read()
441 finally:
455 finally:
442 fp.close()
456 fp.close()
443 except IOError as err:
457 except IOError as err:
444 if err.errno != errno.ENOENT:
458 if err.errno != errno.ENOENT:
445 raise
459 raise
446 return
460 return
447 if not st:
461 if not st:
448 return
462 return
449
463
450 if util.safehasattr(parsers, 'dict_new_presized'):
464 if util.safehasattr(parsers, 'dict_new_presized'):
451 # Make an estimate of the number of files in the dirstate based on
465 # Make an estimate of the number of files in the dirstate based on
452 # its size. From a linear regression on a set of real-world repos,
466 # its size. From a linear regression on a set of real-world repos,
453 # all over 10,000 files, the size of a dirstate entry is 85
467 # all over 10,000 files, the size of a dirstate entry is 85
454 # bytes. The cost of resizing is significantly higher than the cost
468 # bytes. The cost of resizing is significantly higher than the cost
455 # of filling in a larger presized dict, so subtract 20% from the
469 # of filling in a larger presized dict, so subtract 20% from the
456 # size.
470 # size.
457 #
471 #
458 # This heuristic is imperfect in many ways, so in a future dirstate
472 # This heuristic is imperfect in many ways, so in a future dirstate
459 # format update it makes sense to just record the number of entries
473 # format update it makes sense to just record the number of entries
460 # on write.
474 # on write.
461 self._map = parsers.dict_new_presized(len(st) / 71)
475 self._map = parsers.dict_new_presized(len(st) / 71)
462
476
463 # Python's garbage collector triggers a GC each time a certain number
477 # Python's garbage collector triggers a GC each time a certain number
464 # of container objects (the number being defined by
478 # of container objects (the number being defined by
465 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
479 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
466 # for each file in the dirstate. The C version then immediately marks
480 # for each file in the dirstate. The C version then immediately marks
467 # them as not to be tracked by the collector. However, this has no
481 # them as not to be tracked by the collector. However, this has no
468 # effect on when GCs are triggered, only on what objects the GC looks
482 # effect on when GCs are triggered, only on what objects the GC looks
469 # into. This means that O(number of files) GCs are unavoidable.
483 # into. This means that O(number of files) GCs are unavoidable.
470 # Depending on when in the process's lifetime the dirstate is parsed,
484 # Depending on when in the process's lifetime the dirstate is parsed,
471 # this can get very expensive. As a workaround, disable GC while
485 # this can get very expensive. As a workaround, disable GC while
472 # parsing the dirstate.
486 # parsing the dirstate.
473 #
487 #
474 # (we cannot decorate the function directly since it is in a C module)
488 # (we cannot decorate the function directly since it is in a C module)
475 parse_dirstate = util.nogc(parsers.parse_dirstate)
489 parse_dirstate = util.nogc(parsers.parse_dirstate)
476 p = parse_dirstate(self._map, self._copymap, st)
490 p = parse_dirstate(self._map, self._copymap, st)
477 if not self._dirtypl:
491 if not self._dirtypl:
478 self._pl = p
492 self._pl = p
479
493
480 def invalidate(self):
494 def invalidate(self):
481 '''Causes the next access to reread the dirstate.
495 '''Causes the next access to reread the dirstate.
482
496
483 This is different from localrepo.invalidatedirstate() because it always
497 This is different from localrepo.invalidatedirstate() because it always
484 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
498 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
485 check whether the dirstate has changed before rereading it.'''
499 check whether the dirstate has changed before rereading it.'''
486
500
487 for a in ("_map", "_copymap", "_identity",
501 for a in ("_map", "_copymap", "_identity",
488 "_filefoldmap", "_dirfoldmap", "_branch",
502 "_filefoldmap", "_dirfoldmap", "_branch",
489 "_pl", "_dirs", "_ignore", "_nonnormalset",
503 "_pl", "_dirs", "_ignore", "_nonnormalset",
490 "_otherparentset"):
504 "_otherparentset"):
491 if a in self.__dict__:
505 if a in self.__dict__:
492 delattr(self, a)
506 delattr(self, a)
493 self._lastnormaltime = 0
507 self._lastnormaltime = 0
494 self._dirty = False
508 self._dirty = False
495 self._updatedfiles.clear()
509 self._updatedfiles.clear()
496 self._parentwriters = 0
510 self._parentwriters = 0
497 self._origpl = None
511 self._origpl = None
498
512
499 def copy(self, source, dest):
513 def copy(self, source, dest):
500 """Mark dest as a copy of source. Unmark dest if source is None."""
514 """Mark dest as a copy of source. Unmark dest if source is None."""
501 if source == dest:
515 if source == dest:
502 return
516 return
503 self._dirty = True
517 self._dirty = True
504 if source is not None:
518 if source is not None:
505 self._copymap[dest] = source
519 self._copymap[dest] = source
506 self._updatedfiles.add(source)
520 self._updatedfiles.add(source)
507 self._updatedfiles.add(dest)
521 self._updatedfiles.add(dest)
508 elif dest in self._copymap:
522 elif dest in self._copymap:
509 del self._copymap[dest]
523 del self._copymap[dest]
510 self._updatedfiles.add(dest)
524 self._updatedfiles.add(dest)
511
525
512 def copied(self, file):
526 def copied(self, file):
513 return self._copymap.get(file, None)
527 return self._copymap.get(file, None)
514
528
515 def copies(self):
529 def copies(self):
516 return self._copymap
530 return self._copymap
517
531
518 def _droppath(self, f):
532 def _droppath(self, f):
519 if self[f] not in "?r" and "_dirs" in self.__dict__:
533 if self[f] not in "?r" and "_dirs" in self.__dict__:
520 self._dirs.delpath(f)
534 self._dirs.delpath(f)
521
535
522 if "_filefoldmap" in self.__dict__:
536 if "_filefoldmap" in self.__dict__:
523 normed = util.normcase(f)
537 normed = util.normcase(f)
524 if normed in self._filefoldmap:
538 if normed in self._filefoldmap:
525 del self._filefoldmap[normed]
539 del self._filefoldmap[normed]
526
540
527 self._updatedfiles.add(f)
541 self._updatedfiles.add(f)
528
542
529 def _addpath(self, f, state, mode, size, mtime):
543 def _addpath(self, f, state, mode, size, mtime):
530 oldstate = self[f]
544 oldstate = self[f]
531 if state == 'a' or oldstate == 'r':
545 if state == 'a' or oldstate == 'r':
532 scmutil.checkfilename(f)
546 scmutil.checkfilename(f)
533 if f in self._dirs:
547 if f in self._dirs:
534 raise error.Abort(_('directory %r already in dirstate') % f)
548 raise error.Abort(_('directory %r already in dirstate') % f)
535 # shadows
549 # shadows
536 for d in util.finddirs(f):
550 for d in util.finddirs(f):
537 if d in self._dirs:
551 if d in self._dirs:
538 break
552 break
539 if d in self._map and self[d] != 'r':
553 if d in self._map and self[d] != 'r':
540 raise error.Abort(
554 raise error.Abort(
541 _('file %r in dirstate clashes with %r') % (d, f))
555 _('file %r in dirstate clashes with %r') % (d, f))
542 if oldstate in "?r" and "_dirs" in self.__dict__:
556 if oldstate in "?r" and "_dirs" in self.__dict__:
543 self._dirs.addpath(f)
557 self._dirs.addpath(f)
544 self._dirty = True
558 self._dirty = True
545 self._updatedfiles.add(f)
559 self._updatedfiles.add(f)
546 self._map[f] = dirstatetuple(state, mode, size, mtime)
560 self._map[f] = dirstatetuple(state, mode, size, mtime)
547 if state != 'n' or mtime == -1:
561 if state != 'n' or mtime == -1:
548 self._nonnormalset.add(f)
562 self._nonnormalset.add(f)
549 if size == -2:
563 if size == -2:
550 self._otherparentset.add(f)
564 self._otherparentset.add(f)
551
565
552 def normal(self, f):
566 def normal(self, f):
553 '''Mark a file normal and clean.'''
567 '''Mark a file normal and clean.'''
554 s = os.lstat(self._join(f))
568 s = os.lstat(self._join(f))
555 mtime = s.st_mtime
569 mtime = s.st_mtime
556 self._addpath(f, 'n', s.st_mode,
570 self._addpath(f, 'n', s.st_mode,
557 s.st_size & _rangemask, mtime & _rangemask)
571 s.st_size & _rangemask, mtime & _rangemask)
558 if f in self._copymap:
572 if f in self._copymap:
559 del self._copymap[f]
573 del self._copymap[f]
560 if f in self._nonnormalset:
574 if f in self._nonnormalset:
561 self._nonnormalset.remove(f)
575 self._nonnormalset.remove(f)
562 if mtime > self._lastnormaltime:
576 if mtime > self._lastnormaltime:
563 # Remember the most recent modification timeslot for status(),
577 # Remember the most recent modification timeslot for status(),
564 # to make sure we won't miss future size-preserving file content
578 # to make sure we won't miss future size-preserving file content
565 # modifications that happen within the same timeslot.
579 # modifications that happen within the same timeslot.
566 self._lastnormaltime = mtime
580 self._lastnormaltime = mtime
567
581
568 def normallookup(self, f):
582 def normallookup(self, f):
569 '''Mark a file normal, but possibly dirty.'''
583 '''Mark a file normal, but possibly dirty.'''
570 if self._pl[1] != nullid and f in self._map:
584 if self._pl[1] != nullid and f in self._map:
571 # if there is a merge going on and the file was either
585 # if there is a merge going on and the file was either
572 # in state 'm' (-1) or coming from other parent (-2) before
586 # in state 'm' (-1) or coming from other parent (-2) before
573 # being removed, restore that state.
587 # being removed, restore that state.
574 entry = self._map[f]
588 entry = self._map[f]
575 if entry[0] == 'r' and entry[2] in (-1, -2):
589 if entry[0] == 'r' and entry[2] in (-1, -2):
576 source = self._copymap.get(f)
590 source = self._copymap.get(f)
577 if entry[2] == -1:
591 if entry[2] == -1:
578 self.merge(f)
592 self.merge(f)
579 elif entry[2] == -2:
593 elif entry[2] == -2:
580 self.otherparent(f)
594 self.otherparent(f)
581 if source:
595 if source:
582 self.copy(source, f)
596 self.copy(source, f)
583 return
597 return
584 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
598 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
585 return
599 return
586 self._addpath(f, 'n', 0, -1, -1)
600 self._addpath(f, 'n', 0, -1, -1)
587 if f in self._copymap:
601 if f in self._copymap:
588 del self._copymap[f]
602 del self._copymap[f]
589 if f in self._nonnormalset:
603 if f in self._nonnormalset:
590 self._nonnormalset.remove(f)
604 self._nonnormalset.remove(f)
591
605
592 def otherparent(self, f):
606 def otherparent(self, f):
593 '''Mark as coming from the other parent, always dirty.'''
607 '''Mark as coming from the other parent, always dirty.'''
594 if self._pl[1] == nullid:
608 if self._pl[1] == nullid:
595 raise error.Abort(_("setting %r to other parent "
609 raise error.Abort(_("setting %r to other parent "
596 "only allowed in merges") % f)
610 "only allowed in merges") % f)
597 if f in self and self[f] == 'n':
611 if f in self and self[f] == 'n':
598 # merge-like
612 # merge-like
599 self._addpath(f, 'm', 0, -2, -1)
613 self._addpath(f, 'm', 0, -2, -1)
600 else:
614 else:
601 # add-like
615 # add-like
602 self._addpath(f, 'n', 0, -2, -1)
616 self._addpath(f, 'n', 0, -2, -1)
603
617
604 if f in self._copymap:
618 if f in self._copymap:
605 del self._copymap[f]
619 del self._copymap[f]
606
620
607 def add(self, f):
621 def add(self, f):
608 '''Mark a file added.'''
622 '''Mark a file added.'''
609 self._addpath(f, 'a', 0, -1, -1)
623 self._addpath(f, 'a', 0, -1, -1)
610 if f in self._copymap:
624 if f in self._copymap:
611 del self._copymap[f]
625 del self._copymap[f]
612
626
613 def remove(self, f):
627 def remove(self, f):
614 '''Mark a file removed.'''
628 '''Mark a file removed.'''
615 self._dirty = True
629 self._dirty = True
616 self._droppath(f)
630 self._droppath(f)
617 size = 0
631 size = 0
618 if self._pl[1] != nullid and f in self._map:
632 if self._pl[1] != nullid and f in self._map:
619 # backup the previous state
633 # backup the previous state
620 entry = self._map[f]
634 entry = self._map[f]
621 if entry[0] == 'm': # merge
635 if entry[0] == 'm': # merge
622 size = -1
636 size = -1
623 elif entry[0] == 'n' and entry[2] == -2: # other parent
637 elif entry[0] == 'n' and entry[2] == -2: # other parent
624 size = -2
638 size = -2
625 self._otherparentset.add(f)
639 self._otherparentset.add(f)
626 self._map[f] = dirstatetuple('r', 0, size, 0)
640 self._map[f] = dirstatetuple('r', 0, size, 0)
627 self._nonnormalset.add(f)
641 self._nonnormalset.add(f)
628 if size == 0 and f in self._copymap:
642 if size == 0 and f in self._copymap:
629 del self._copymap[f]
643 del self._copymap[f]
630
644
631 def merge(self, f):
645 def merge(self, f):
632 '''Mark a file merged.'''
646 '''Mark a file merged.'''
633 if self._pl[1] == nullid:
647 if self._pl[1] == nullid:
634 return self.normallookup(f)
648 return self.normallookup(f)
635 return self.otherparent(f)
649 return self.otherparent(f)
636
650
637 def drop(self, f):
651 def drop(self, f):
638 '''Drop a file from the dirstate'''
652 '''Drop a file from the dirstate'''
639 if f in self._map:
653 if f in self._map:
640 self._dirty = True
654 self._dirty = True
641 self._droppath(f)
655 self._droppath(f)
642 del self._map[f]
656 del self._map[f]
643 if f in self._nonnormalset:
657 if f in self._nonnormalset:
644 self._nonnormalset.remove(f)
658 self._nonnormalset.remove(f)
645 if f in self._copymap:
659 if f in self._copymap:
646 del self._copymap[f]
660 del self._copymap[f]
647
661
648 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
662 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
649 if exists is None:
663 if exists is None:
650 exists = os.path.lexists(os.path.join(self._root, path))
664 exists = os.path.lexists(os.path.join(self._root, path))
651 if not exists:
665 if not exists:
652 # Maybe a path component exists
666 # Maybe a path component exists
653 if not ignoremissing and '/' in path:
667 if not ignoremissing and '/' in path:
654 d, f = path.rsplit('/', 1)
668 d, f = path.rsplit('/', 1)
655 d = self._normalize(d, False, ignoremissing, None)
669 d = self._normalize(d, False, ignoremissing, None)
656 folded = d + "/" + f
670 folded = d + "/" + f
657 else:
671 else:
658 # No path components, preserve original case
672 # No path components, preserve original case
659 folded = path
673 folded = path
660 else:
674 else:
661 # recursively normalize leading directory components
675 # recursively normalize leading directory components
662 # against dirstate
676 # against dirstate
663 if '/' in normed:
677 if '/' in normed:
664 d, f = normed.rsplit('/', 1)
678 d, f = normed.rsplit('/', 1)
665 d = self._normalize(d, False, ignoremissing, True)
679 d = self._normalize(d, False, ignoremissing, True)
666 r = self._root + "/" + d
680 r = self._root + "/" + d
667 folded = d + "/" + util.fspath(f, r)
681 folded = d + "/" + util.fspath(f, r)
668 else:
682 else:
669 folded = util.fspath(normed, self._root)
683 folded = util.fspath(normed, self._root)
670 storemap[normed] = folded
684 storemap[normed] = folded
671
685
672 return folded
686 return folded
673
687
674 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
688 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
675 normed = util.normcase(path)
689 normed = util.normcase(path)
676 folded = self._filefoldmap.get(normed, None)
690 folded = self._filefoldmap.get(normed, None)
677 if folded is None:
691 if folded is None:
678 if isknown:
692 if isknown:
679 folded = path
693 folded = path
680 else:
694 else:
681 folded = self._discoverpath(path, normed, ignoremissing, exists,
695 folded = self._discoverpath(path, normed, ignoremissing, exists,
682 self._filefoldmap)
696 self._filefoldmap)
683 return folded
697 return folded
684
698
685 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
699 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
686 normed = util.normcase(path)
700 normed = util.normcase(path)
687 folded = self._filefoldmap.get(normed, None)
701 folded = self._filefoldmap.get(normed, None)
688 if folded is None:
702 if folded is None:
689 folded = self._dirfoldmap.get(normed, None)
703 folded = self._dirfoldmap.get(normed, None)
690 if folded is None:
704 if folded is None:
691 if isknown:
705 if isknown:
692 folded = path
706 folded = path
693 else:
707 else:
694 # store discovered result in dirfoldmap so that future
708 # store discovered result in dirfoldmap so that future
695 # normalizefile calls don't start matching directories
709 # normalizefile calls don't start matching directories
696 folded = self._discoverpath(path, normed, ignoremissing, exists,
710 folded = self._discoverpath(path, normed, ignoremissing, exists,
697 self._dirfoldmap)
711 self._dirfoldmap)
698 return folded
712 return folded
699
713
700 def normalize(self, path, isknown=False, ignoremissing=False):
714 def normalize(self, path, isknown=False, ignoremissing=False):
701 '''
715 '''
702 normalize the case of a pathname when on a casefolding filesystem
716 normalize the case of a pathname when on a casefolding filesystem
703
717
704 isknown specifies whether the filename came from walking the
718 isknown specifies whether the filename came from walking the
705 disk, to avoid extra filesystem access.
719 disk, to avoid extra filesystem access.
706
720
707 If ignoremissing is True, missing path are returned
721 If ignoremissing is True, missing path are returned
708 unchanged. Otherwise, we try harder to normalize possibly
722 unchanged. Otherwise, we try harder to normalize possibly
709 existing path components.
723 existing path components.
710
724
711 The normalized case is determined based on the following precedence:
725 The normalized case is determined based on the following precedence:
712
726
713 - version of name already stored in the dirstate
727 - version of name already stored in the dirstate
714 - version of name stored on disk
728 - version of name stored on disk
715 - version provided via command arguments
729 - version provided via command arguments
716 '''
730 '''
717
731
718 if self._checkcase:
732 if self._checkcase:
719 return self._normalize(path, isknown, ignoremissing)
733 return self._normalize(path, isknown, ignoremissing)
720 return path
734 return path
721
735
722 def clear(self):
736 def clear(self):
723 self._map = {}
737 self._map = {}
724 self._nonnormalset = set()
738 self._nonnormalset = set()
725 self._otherparentset = set()
739 self._otherparentset = set()
726 if "_dirs" in self.__dict__:
740 if "_dirs" in self.__dict__:
727 delattr(self, "_dirs")
741 delattr(self, "_dirs")
728 self._copymap = {}
742 self._copymap = {}
729 self._pl = [nullid, nullid]
743 self._pl = [nullid, nullid]
730 self._lastnormaltime = 0
744 self._lastnormaltime = 0
731 self._updatedfiles.clear()
745 self._updatedfiles.clear()
732 self._dirty = True
746 self._dirty = True
733
747
734 def rebuild(self, parent, allfiles, changedfiles=None):
748 def rebuild(self, parent, allfiles, changedfiles=None):
735 if changedfiles is None:
749 if changedfiles is None:
736 # Rebuild entire dirstate
750 # Rebuild entire dirstate
737 changedfiles = allfiles
751 changedfiles = allfiles
738 lastnormaltime = self._lastnormaltime
752 lastnormaltime = self._lastnormaltime
739 self.clear()
753 self.clear()
740 self._lastnormaltime = lastnormaltime
754 self._lastnormaltime = lastnormaltime
741
755
742 if self._origpl is None:
756 if self._origpl is None:
743 self._origpl = self._pl
757 self._origpl = self._pl
744 self._pl = (parent, nullid)
758 self._pl = (parent, nullid)
745 for f in changedfiles:
759 for f in changedfiles:
746 if f in allfiles:
760 if f in allfiles:
747 self.normallookup(f)
761 self.normallookup(f)
748 else:
762 else:
749 self.drop(f)
763 self.drop(f)
750
764
751 self._dirty = True
765 self._dirty = True
752
766
753 def identity(self):
767 def identity(self):
754 '''Return identity of dirstate itself to detect changing in storage
768 '''Return identity of dirstate itself to detect changing in storage
755
769
756 If identity of previous dirstate is equal to this, writing
770 If identity of previous dirstate is equal to this, writing
757 changes based on the former dirstate out can keep consistency.
771 changes based on the former dirstate out can keep consistency.
758 '''
772 '''
759 return self._identity
773 return self._identity
760
774
761 def write(self, tr):
775 def write(self, tr):
762 if not self._dirty:
776 if not self._dirty:
763 return
777 return
764
778
765 filename = self._filename
779 filename = self._filename
766 if tr:
780 if tr:
767 # 'dirstate.write()' is not only for writing in-memory
781 # 'dirstate.write()' is not only for writing in-memory
768 # changes out, but also for dropping ambiguous timestamp.
782 # changes out, but also for dropping ambiguous timestamp.
769 # delayed writing re-raise "ambiguous timestamp issue".
783 # delayed writing re-raise "ambiguous timestamp issue".
770 # See also the wiki page below for detail:
784 # See also the wiki page below for detail:
771 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
785 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
772
786
773 # emulate dropping timestamp in 'parsers.pack_dirstate'
787 # emulate dropping timestamp in 'parsers.pack_dirstate'
774 now = _getfsnow(self._opener)
788 now = _getfsnow(self._opener)
775 dmap = self._map
789 dmap = self._map
776 for f in self._updatedfiles:
790 for f in self._updatedfiles:
777 e = dmap.get(f)
791 e = dmap.get(f)
778 if e is not None and e[0] == 'n' and e[3] == now:
792 if e is not None and e[0] == 'n' and e[3] == now:
779 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
793 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
780 self._nonnormalset.add(f)
794 self._nonnormalset.add(f)
781
795
782 # emulate that all 'dirstate.normal' results are written out
796 # emulate that all 'dirstate.normal' results are written out
783 self._lastnormaltime = 0
797 self._lastnormaltime = 0
784 self._updatedfiles.clear()
798 self._updatedfiles.clear()
785
799
786 # delay writing in-memory changes out
800 # delay writing in-memory changes out
787 tr.addfilegenerator('dirstate', (self._filename,),
801 tr.addfilegenerator('dirstate', (self._filename,),
788 self._writedirstate, location='plain')
802 self._writedirstate, location='plain')
789 return
803 return
790
804
791 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
805 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
792 self._writedirstate(st)
806 self._writedirstate(st)
793
807
794 def addparentchangecallback(self, category, callback):
808 def addparentchangecallback(self, category, callback):
795 """add a callback to be called when the wd parents are changed
809 """add a callback to be called when the wd parents are changed
796
810
797 Callback will be called with the following arguments:
811 Callback will be called with the following arguments:
798 dirstate, (oldp1, oldp2), (newp1, newp2)
812 dirstate, (oldp1, oldp2), (newp1, newp2)
799
813
800 Category is a unique identifier to allow overwriting an old callback
814 Category is a unique identifier to allow overwriting an old callback
801 with a newer callback.
815 with a newer callback.
802 """
816 """
803 self._plchangecallbacks[category] = callback
817 self._plchangecallbacks[category] = callback
804
818
805 def _writedirstate(self, st):
819 def _writedirstate(self, st):
806 # notify callbacks about parents change
820 # notify callbacks about parents change
807 if self._origpl is not None and self._origpl != self._pl:
821 if self._origpl is not None and self._origpl != self._pl:
808 for c, callback in sorted(self._plchangecallbacks.iteritems()):
822 for c, callback in sorted(self._plchangecallbacks.iteritems()):
809 callback(self, self._origpl, self._pl)
823 callback(self, self._origpl, self._pl)
810 self._origpl = None
824 self._origpl = None
811 # use the modification time of the newly created temporary file as the
825 # use the modification time of the newly created temporary file as the
812 # filesystem's notion of 'now'
826 # filesystem's notion of 'now'
813 now = util.fstat(st).st_mtime & _rangemask
827 now = util.fstat(st).st_mtime & _rangemask
814
828
815 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
829 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
816 # timestamp of each entries in dirstate, because of 'now > mtime'
830 # timestamp of each entries in dirstate, because of 'now > mtime'
817 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
831 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
818 if delaywrite > 0:
832 if delaywrite > 0:
819 # do we have any files to delay for?
833 # do we have any files to delay for?
820 for f, e in self._map.iteritems():
834 for f, e in self._map.iteritems():
821 if e[0] == 'n' and e[3] == now:
835 if e[0] == 'n' and e[3] == now:
822 import time # to avoid useless import
836 import time # to avoid useless import
823 # rather than sleep n seconds, sleep until the next
837 # rather than sleep n seconds, sleep until the next
824 # multiple of n seconds
838 # multiple of n seconds
825 clock = time.time()
839 clock = time.time()
826 start = int(clock) - (int(clock) % delaywrite)
840 start = int(clock) - (int(clock) % delaywrite)
827 end = start + delaywrite
841 end = start + delaywrite
828 time.sleep(end - clock)
842 time.sleep(end - clock)
829 now = end # trust our estimate that the end is near now
843 now = end # trust our estimate that the end is near now
830 break
844 break
831
845
832 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
846 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
833 self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
847 self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
834 st.close()
848 st.close()
835 self._lastnormaltime = 0
849 self._lastnormaltime = 0
836 self._dirty = self._dirtypl = False
850 self._dirty = self._dirtypl = False
837
851
838 def _dirignore(self, f):
852 def _dirignore(self, f):
839 if f == '.':
853 if f == '.':
840 return False
854 return False
841 if self._ignore(f):
855 if self._ignore(f):
842 return True
856 return True
843 for p in util.finddirs(f):
857 for p in util.finddirs(f):
844 if self._ignore(p):
858 if self._ignore(p):
845 return True
859 return True
846 return False
860 return False
847
861
848 def _ignorefiles(self):
862 def _ignorefiles(self):
849 files = []
863 files = []
850 if os.path.exists(self._join('.hgignore')):
864 if os.path.exists(self._join('.hgignore')):
851 files.append(self._join('.hgignore'))
865 files.append(self._join('.hgignore'))
852 for name, path in self._ui.configitems("ui"):
866 for name, path in self._ui.configitems("ui"):
853 if name == 'ignore' or name.startswith('ignore.'):
867 if name == 'ignore' or name.startswith('ignore.'):
854 # we need to use os.path.join here rather than self._join
868 # we need to use os.path.join here rather than self._join
855 # because path is arbitrary and user-specified
869 # because path is arbitrary and user-specified
856 files.append(os.path.join(self._rootdir, util.expandpath(path)))
870 files.append(os.path.join(self._rootdir, util.expandpath(path)))
857 return files
871 return files
858
872
859 def _ignorefileandline(self, f):
873 def _ignorefileandline(self, f):
860 files = collections.deque(self._ignorefiles())
874 files = collections.deque(self._ignorefiles())
861 visited = set()
875 visited = set()
862 while files:
876 while files:
863 i = files.popleft()
877 i = files.popleft()
864 patterns = matchmod.readpatternfile(i, self._ui.warn,
878 patterns = matchmod.readpatternfile(i, self._ui.warn,
865 sourceinfo=True)
879 sourceinfo=True)
866 for pattern, lineno, line in patterns:
880 for pattern, lineno, line in patterns:
867 kind, p = matchmod._patsplit(pattern, 'glob')
881 kind, p = matchmod._patsplit(pattern, 'glob')
868 if kind == "subinclude":
882 if kind == "subinclude":
869 if p not in visited:
883 if p not in visited:
870 files.append(p)
884 files.append(p)
871 continue
885 continue
872 m = matchmod.match(self._root, '', [], [pattern],
886 m = matchmod.match(self._root, '', [], [pattern],
873 warn=self._ui.warn)
887 warn=self._ui.warn)
874 if m(f):
888 if m(f):
875 return (i, lineno, line)
889 return (i, lineno, line)
876 visited.add(i)
890 visited.add(i)
877 return (None, -1, "")
891 return (None, -1, "")
878
892
879 def _walkexplicit(self, match, subrepos):
893 def _walkexplicit(self, match, subrepos):
880 '''Get stat data about the files explicitly specified by match.
894 '''Get stat data about the files explicitly specified by match.
881
895
882 Return a triple (results, dirsfound, dirsnotfound).
896 Return a triple (results, dirsfound, dirsnotfound).
883 - results is a mapping from filename to stat result. It also contains
897 - results is a mapping from filename to stat result. It also contains
884 listings mapping subrepos and .hg to None.
898 listings mapping subrepos and .hg to None.
885 - dirsfound is a list of files found to be directories.
899 - dirsfound is a list of files found to be directories.
886 - dirsnotfound is a list of files that the dirstate thinks are
900 - dirsnotfound is a list of files that the dirstate thinks are
887 directories and that were not found.'''
901 directories and that were not found.'''
888
902
889 def badtype(mode):
903 def badtype(mode):
890 kind = _('unknown')
904 kind = _('unknown')
891 if stat.S_ISCHR(mode):
905 if stat.S_ISCHR(mode):
892 kind = _('character device')
906 kind = _('character device')
893 elif stat.S_ISBLK(mode):
907 elif stat.S_ISBLK(mode):
894 kind = _('block device')
908 kind = _('block device')
895 elif stat.S_ISFIFO(mode):
909 elif stat.S_ISFIFO(mode):
896 kind = _('fifo')
910 kind = _('fifo')
897 elif stat.S_ISSOCK(mode):
911 elif stat.S_ISSOCK(mode):
898 kind = _('socket')
912 kind = _('socket')
899 elif stat.S_ISDIR(mode):
913 elif stat.S_ISDIR(mode):
900 kind = _('directory')
914 kind = _('directory')
901 return _('unsupported file type (type is %s)') % kind
915 return _('unsupported file type (type is %s)') % kind
902
916
903 matchedir = match.explicitdir
917 matchedir = match.explicitdir
904 badfn = match.bad
918 badfn = match.bad
905 dmap = self._map
919 dmap = self._map
906 lstat = os.lstat
920 lstat = os.lstat
907 getkind = stat.S_IFMT
921 getkind = stat.S_IFMT
908 dirkind = stat.S_IFDIR
922 dirkind = stat.S_IFDIR
909 regkind = stat.S_IFREG
923 regkind = stat.S_IFREG
910 lnkkind = stat.S_IFLNK
924 lnkkind = stat.S_IFLNK
911 join = self._join
925 join = self._join
912 dirsfound = []
926 dirsfound = []
913 foundadd = dirsfound.append
927 foundadd = dirsfound.append
914 dirsnotfound = []
928 dirsnotfound = []
915 notfoundadd = dirsnotfound.append
929 notfoundadd = dirsnotfound.append
916
930
917 if not match.isexact() and self._checkcase:
931 if not match.isexact() and self._checkcase:
918 normalize = self._normalize
932 normalize = self._normalize
919 else:
933 else:
920 normalize = None
934 normalize = None
921
935
922 files = sorted(match.files())
936 files = sorted(match.files())
923 subrepos.sort()
937 subrepos.sort()
924 i, j = 0, 0
938 i, j = 0, 0
925 while i < len(files) and j < len(subrepos):
939 while i < len(files) and j < len(subrepos):
926 subpath = subrepos[j] + "/"
940 subpath = subrepos[j] + "/"
927 if files[i] < subpath:
941 if files[i] < subpath:
928 i += 1
942 i += 1
929 continue
943 continue
930 while i < len(files) and files[i].startswith(subpath):
944 while i < len(files) and files[i].startswith(subpath):
931 del files[i]
945 del files[i]
932 j += 1
946 j += 1
933
947
934 if not files or '.' in files:
948 if not files or '.' in files:
935 files = ['.']
949 files = ['.']
936 results = dict.fromkeys(subrepos)
950 results = dict.fromkeys(subrepos)
937 results['.hg'] = None
951 results['.hg'] = None
938
952
939 alldirs = None
953 alldirs = None
940 for ff in files:
954 for ff in files:
941 # constructing the foldmap is expensive, so don't do it for the
955 # constructing the foldmap is expensive, so don't do it for the
942 # common case where files is ['.']
956 # common case where files is ['.']
943 if normalize and ff != '.':
957 if normalize and ff != '.':
944 nf = normalize(ff, False, True)
958 nf = normalize(ff, False, True)
945 else:
959 else:
946 nf = ff
960 nf = ff
947 if nf in results:
961 if nf in results:
948 continue
962 continue
949
963
950 try:
964 try:
951 st = lstat(join(nf))
965 st = lstat(join(nf))
952 kind = getkind(st.st_mode)
966 kind = getkind(st.st_mode)
953 if kind == dirkind:
967 if kind == dirkind:
954 if nf in dmap:
968 if nf in dmap:
955 # file replaced by dir on disk but still in dirstate
969 # file replaced by dir on disk but still in dirstate
956 results[nf] = None
970 results[nf] = None
957 if matchedir:
971 if matchedir:
958 matchedir(nf)
972 matchedir(nf)
959 foundadd((nf, ff))
973 foundadd((nf, ff))
960 elif kind == regkind or kind == lnkkind:
974 elif kind == regkind or kind == lnkkind:
961 results[nf] = st
975 results[nf] = st
962 else:
976 else:
963 badfn(ff, badtype(kind))
977 badfn(ff, badtype(kind))
964 if nf in dmap:
978 if nf in dmap:
965 results[nf] = None
979 results[nf] = None
966 except OSError as inst: # nf not found on disk - it is dirstate only
980 except OSError as inst: # nf not found on disk - it is dirstate only
967 if nf in dmap: # does it exactly match a missing file?
981 if nf in dmap: # does it exactly match a missing file?
968 results[nf] = None
982 results[nf] = None
969 else: # does it match a missing directory?
983 else: # does it match a missing directory?
970 if alldirs is None:
984 if alldirs is None:
971 alldirs = util.dirs(dmap)
985 alldirs = util.dirs(dmap)
972 if nf in alldirs:
986 if nf in alldirs:
973 if matchedir:
987 if matchedir:
974 matchedir(nf)
988 matchedir(nf)
975 notfoundadd(nf)
989 notfoundadd(nf)
976 else:
990 else:
977 badfn(ff, inst.strerror)
991 badfn(ff, inst.strerror)
978
992
979 # Case insensitive filesystems cannot rely on lstat() failing to detect
993 # Case insensitive filesystems cannot rely on lstat() failing to detect
980 # a case-only rename. Prune the stat object for any file that does not
994 # a case-only rename. Prune the stat object for any file that does not
981 # match the case in the filesystem, if there are multiple files that
995 # match the case in the filesystem, if there are multiple files that
982 # normalize to the same path.
996 # normalize to the same path.
983 if match.isexact() and self._checkcase:
997 if match.isexact() and self._checkcase:
984 normed = {}
998 normed = {}
985
999
986 for f, st in results.iteritems():
1000 for f, st in results.iteritems():
987 if st is None:
1001 if st is None:
988 continue
1002 continue
989
1003
990 nc = util.normcase(f)
1004 nc = util.normcase(f)
991 paths = normed.get(nc)
1005 paths = normed.get(nc)
992
1006
993 if paths is None:
1007 if paths is None:
994 paths = set()
1008 paths = set()
995 normed[nc] = paths
1009 normed[nc] = paths
996
1010
997 paths.add(f)
1011 paths.add(f)
998
1012
999 for norm, paths in normed.iteritems():
1013 for norm, paths in normed.iteritems():
1000 if len(paths) > 1:
1014 if len(paths) > 1:
1001 for path in paths:
1015 for path in paths:
1002 folded = self._discoverpath(path, norm, True, None,
1016 folded = self._discoverpath(path, norm, True, None,
1003 self._dirfoldmap)
1017 self._dirfoldmap)
1004 if path != folded:
1018 if path != folded:
1005 results[path] = None
1019 results[path] = None
1006
1020
1007 return results, dirsfound, dirsnotfound
1021 return results, dirsfound, dirsnotfound
1008
1022
1009 def walk(self, match, subrepos, unknown, ignored, full=True):
1023 def walk(self, match, subrepos, unknown, ignored, full=True):
1010 '''
1024 '''
1011 Walk recursively through the directory tree, finding all files
1025 Walk recursively through the directory tree, finding all files
1012 matched by match.
1026 matched by match.
1013
1027
1014 If full is False, maybe skip some known-clean files.
1028 If full is False, maybe skip some known-clean files.
1015
1029
1016 Return a dict mapping filename to stat-like object (either
1030 Return a dict mapping filename to stat-like object (either
1017 mercurial.osutil.stat instance or return value of os.stat()).
1031 mercurial.osutil.stat instance or return value of os.stat()).
1018
1032
1019 '''
1033 '''
1020 # full is a flag that extensions that hook into walk can use -- this
1034 # full is a flag that extensions that hook into walk can use -- this
1021 # implementation doesn't use it at all. This satisfies the contract
1035 # implementation doesn't use it at all. This satisfies the contract
1022 # because we only guarantee a "maybe".
1036 # because we only guarantee a "maybe".
1023
1037
1024 if ignored:
1038 if ignored:
1025 ignore = util.never
1039 ignore = util.never
1026 dirignore = util.never
1040 dirignore = util.never
1027 elif unknown:
1041 elif unknown:
1028 ignore = self._ignore
1042 ignore = self._ignore
1029 dirignore = self._dirignore
1043 dirignore = self._dirignore
1030 else:
1044 else:
1031 # if not unknown and not ignored, drop dir recursion and step 2
1045 # if not unknown and not ignored, drop dir recursion and step 2
1032 ignore = util.always
1046 ignore = util.always
1033 dirignore = util.always
1047 dirignore = util.always
1034
1048
1035 matchfn = match.matchfn
1049 matchfn = match.matchfn
1036 matchalways = match.always()
1050 matchalways = match.always()
1037 matchtdir = match.traversedir
1051 matchtdir = match.traversedir
1038 dmap = self._map
1052 dmap = self._map
1039 listdir = util.listdir
1053 listdir = util.listdir
1040 lstat = os.lstat
1054 lstat = os.lstat
1041 dirkind = stat.S_IFDIR
1055 dirkind = stat.S_IFDIR
1042 regkind = stat.S_IFREG
1056 regkind = stat.S_IFREG
1043 lnkkind = stat.S_IFLNK
1057 lnkkind = stat.S_IFLNK
1044 join = self._join
1058 join = self._join
1045
1059
1046 exact = skipstep3 = False
1060 exact = skipstep3 = False
1047 if match.isexact(): # match.exact
1061 if match.isexact(): # match.exact
1048 exact = True
1062 exact = True
1049 dirignore = util.always # skip step 2
1063 dirignore = util.always # skip step 2
1050 elif match.prefix(): # match.match, no patterns
1064 elif match.prefix(): # match.match, no patterns
1051 skipstep3 = True
1065 skipstep3 = True
1052
1066
1053 if not exact and self._checkcase:
1067 if not exact and self._checkcase:
1054 normalize = self._normalize
1068 normalize = self._normalize
1055 normalizefile = self._normalizefile
1069 normalizefile = self._normalizefile
1056 skipstep3 = False
1070 skipstep3 = False
1057 else:
1071 else:
1058 normalize = self._normalize
1072 normalize = self._normalize
1059 normalizefile = None
1073 normalizefile = None
1060
1074
1061 # step 1: find all explicit files
1075 # step 1: find all explicit files
1062 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1076 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1063
1077
1064 skipstep3 = skipstep3 and not (work or dirsnotfound)
1078 skipstep3 = skipstep3 and not (work or dirsnotfound)
1065 work = [d for d in work if not dirignore(d[0])]
1079 work = [d for d in work if not dirignore(d[0])]
1066
1080
1067 # step 2: visit subdirectories
1081 # step 2: visit subdirectories
1068 def traverse(work, alreadynormed):
1082 def traverse(work, alreadynormed):
1069 wadd = work.append
1083 wadd = work.append
1070 while work:
1084 while work:
1071 nd = work.pop()
1085 nd = work.pop()
1072 if not match.visitdir(nd):
1086 if not match.visitdir(nd):
1073 continue
1087 continue
1074 skip = None
1088 skip = None
1075 if nd == '.':
1089 if nd == '.':
1076 nd = ''
1090 nd = ''
1077 else:
1091 else:
1078 skip = '.hg'
1092 skip = '.hg'
1079 try:
1093 try:
1080 entries = listdir(join(nd), stat=True, skip=skip)
1094 entries = listdir(join(nd), stat=True, skip=skip)
1081 except OSError as inst:
1095 except OSError as inst:
1082 if inst.errno in (errno.EACCES, errno.ENOENT):
1096 if inst.errno in (errno.EACCES, errno.ENOENT):
1083 match.bad(self.pathto(nd), inst.strerror)
1097 match.bad(self.pathto(nd), inst.strerror)
1084 continue
1098 continue
1085 raise
1099 raise
1086 for f, kind, st in entries:
1100 for f, kind, st in entries:
1087 if normalizefile:
1101 if normalizefile:
1088 # even though f might be a directory, we're only
1102 # even though f might be a directory, we're only
1089 # interested in comparing it to files currently in the
1103 # interested in comparing it to files currently in the
1090 # dmap -- therefore normalizefile is enough
1104 # dmap -- therefore normalizefile is enough
1091 nf = normalizefile(nd and (nd + "/" + f) or f, True,
1105 nf = normalizefile(nd and (nd + "/" + f) or f, True,
1092 True)
1106 True)
1093 else:
1107 else:
1094 nf = nd and (nd + "/" + f) or f
1108 nf = nd and (nd + "/" + f) or f
1095 if nf not in results:
1109 if nf not in results:
1096 if kind == dirkind:
1110 if kind == dirkind:
1097 if not ignore(nf):
1111 if not ignore(nf):
1098 if matchtdir:
1112 if matchtdir:
1099 matchtdir(nf)
1113 matchtdir(nf)
1100 wadd(nf)
1114 wadd(nf)
1101 if nf in dmap and (matchalways or matchfn(nf)):
1115 if nf in dmap and (matchalways or matchfn(nf)):
1102 results[nf] = None
1116 results[nf] = None
1103 elif kind == regkind or kind == lnkkind:
1117 elif kind == regkind or kind == lnkkind:
1104 if nf in dmap:
1118 if nf in dmap:
1105 if matchalways or matchfn(nf):
1119 if matchalways or matchfn(nf):
1106 results[nf] = st
1120 results[nf] = st
1107 elif ((matchalways or matchfn(nf))
1121 elif ((matchalways or matchfn(nf))
1108 and not ignore(nf)):
1122 and not ignore(nf)):
1109 # unknown file -- normalize if necessary
1123 # unknown file -- normalize if necessary
1110 if not alreadynormed:
1124 if not alreadynormed:
1111 nf = normalize(nf, False, True)
1125 nf = normalize(nf, False, True)
1112 results[nf] = st
1126 results[nf] = st
1113 elif nf in dmap and (matchalways or matchfn(nf)):
1127 elif nf in dmap and (matchalways or matchfn(nf)):
1114 results[nf] = None
1128 results[nf] = None
1115
1129
1116 for nd, d in work:
1130 for nd, d in work:
1117 # alreadynormed means that processwork doesn't have to do any
1131 # alreadynormed means that processwork doesn't have to do any
1118 # expensive directory normalization
1132 # expensive directory normalization
1119 alreadynormed = not normalize or nd == d
1133 alreadynormed = not normalize or nd == d
1120 traverse([d], alreadynormed)
1134 traverse([d], alreadynormed)
1121
1135
1122 for s in subrepos:
1136 for s in subrepos:
1123 del results[s]
1137 del results[s]
1124 del results['.hg']
1138 del results['.hg']
1125
1139
1126 # step 3: visit remaining files from dmap
1140 # step 3: visit remaining files from dmap
1127 if not skipstep3 and not exact:
1141 if not skipstep3 and not exact:
1128 # If a dmap file is not in results yet, it was either
1142 # If a dmap file is not in results yet, it was either
1129 # a) not matching matchfn b) ignored, c) missing, or d) under a
1143 # a) not matching matchfn b) ignored, c) missing, or d) under a
1130 # symlink directory.
1144 # symlink directory.
1131 if not results and matchalways:
1145 if not results and matchalways:
1132 visit = [f for f in dmap]
1146 visit = [f for f in dmap]
1133 else:
1147 else:
1134 visit = [f for f in dmap if f not in results and matchfn(f)]
1148 visit = [f for f in dmap if f not in results and matchfn(f)]
1135 visit.sort()
1149 visit.sort()
1136
1150
1137 if unknown:
1151 if unknown:
1138 # unknown == True means we walked all dirs under the roots
1152 # unknown == True means we walked all dirs under the roots
1139 # that wasn't ignored, and everything that matched was stat'ed
1153 # that wasn't ignored, and everything that matched was stat'ed
1140 # and is already in results.
1154 # and is already in results.
1141 # The rest must thus be ignored or under a symlink.
1155 # The rest must thus be ignored or under a symlink.
1142 audit_path = pathutil.pathauditor(self._root)
1156 audit_path = pathutil.pathauditor(self._root)
1143
1157
1144 for nf in iter(visit):
1158 for nf in iter(visit):
1145 # If a stat for the same file was already added with a
1159 # If a stat for the same file was already added with a
1146 # different case, don't add one for this, since that would
1160 # different case, don't add one for this, since that would
1147 # make it appear as if the file exists under both names
1161 # make it appear as if the file exists under both names
1148 # on disk.
1162 # on disk.
1149 if (normalizefile and
1163 if (normalizefile and
1150 normalizefile(nf, True, True) in results):
1164 normalizefile(nf, True, True) in results):
1151 results[nf] = None
1165 results[nf] = None
1152 # Report ignored items in the dmap as long as they are not
1166 # Report ignored items in the dmap as long as they are not
1153 # under a symlink directory.
1167 # under a symlink directory.
1154 elif audit_path.check(nf):
1168 elif audit_path.check(nf):
1155 try:
1169 try:
1156 results[nf] = lstat(join(nf))
1170 results[nf] = lstat(join(nf))
1157 # file was just ignored, no links, and exists
1171 # file was just ignored, no links, and exists
1158 except OSError:
1172 except OSError:
1159 # file doesn't exist
1173 # file doesn't exist
1160 results[nf] = None
1174 results[nf] = None
1161 else:
1175 else:
1162 # It's either missing or under a symlink directory
1176 # It's either missing or under a symlink directory
1163 # which we in this case report as missing
1177 # which we in this case report as missing
1164 results[nf] = None
1178 results[nf] = None
1165 else:
1179 else:
1166 # We may not have walked the full directory tree above,
1180 # We may not have walked the full directory tree above,
1167 # so stat and check everything we missed.
1181 # so stat and check everything we missed.
1168 iv = iter(visit)
1182 iv = iter(visit)
1169 for st in util.statfiles([join(i) for i in visit]):
1183 for st in util.statfiles([join(i) for i in visit]):
1170 results[next(iv)] = st
1184 results[next(iv)] = st
1171 return results
1185 return results
1172
1186
1173 def status(self, match, subrepos, ignored, clean, unknown):
1187 def status(self, match, subrepos, ignored, clean, unknown):
1174 '''Determine the status of the working copy relative to the
1188 '''Determine the status of the working copy relative to the
1175 dirstate and return a pair of (unsure, status), where status is of type
1189 dirstate and return a pair of (unsure, status), where status is of type
1176 scmutil.status and:
1190 scmutil.status and:
1177
1191
1178 unsure:
1192 unsure:
1179 files that might have been modified since the dirstate was
1193 files that might have been modified since the dirstate was
1180 written, but need to be read to be sure (size is the same
1194 written, but need to be read to be sure (size is the same
1181 but mtime differs)
1195 but mtime differs)
1182 status.modified:
1196 status.modified:
1183 files that have definitely been modified since the dirstate
1197 files that have definitely been modified since the dirstate
1184 was written (different size or mode)
1198 was written (different size or mode)
1185 status.clean:
1199 status.clean:
1186 files that have definitely not been modified since the
1200 files that have definitely not been modified since the
1187 dirstate was written
1201 dirstate was written
1188 '''
1202 '''
1189 listignored, listclean, listunknown = ignored, clean, unknown
1203 listignored, listclean, listunknown = ignored, clean, unknown
1190 lookup, modified, added, unknown, ignored = [], [], [], [], []
1204 lookup, modified, added, unknown, ignored = [], [], [], [], []
1191 removed, deleted, clean = [], [], []
1205 removed, deleted, clean = [], [], []
1192
1206
1193 dmap = self._map
1207 dmap = self._map
1194 ladd = lookup.append # aka "unsure"
1208 ladd = lookup.append # aka "unsure"
1195 madd = modified.append
1209 madd = modified.append
1196 aadd = added.append
1210 aadd = added.append
1197 uadd = unknown.append
1211 uadd = unknown.append
1198 iadd = ignored.append
1212 iadd = ignored.append
1199 radd = removed.append
1213 radd = removed.append
1200 dadd = deleted.append
1214 dadd = deleted.append
1201 cadd = clean.append
1215 cadd = clean.append
1202 mexact = match.exact
1216 mexact = match.exact
1203 dirignore = self._dirignore
1217 dirignore = self._dirignore
1204 checkexec = self._checkexec
1218 checkexec = self._checkexec
1205 copymap = self._copymap
1219 copymap = self._copymap
1206 lastnormaltime = self._lastnormaltime
1220 lastnormaltime = self._lastnormaltime
1207
1221
1208 # We need to do full walks when either
1222 # We need to do full walks when either
1209 # - we're listing all clean files, or
1223 # - we're listing all clean files, or
1210 # - match.traversedir does something, because match.traversedir should
1224 # - match.traversedir does something, because match.traversedir should
1211 # be called for every dir in the working dir
1225 # be called for every dir in the working dir
1212 full = listclean or match.traversedir is not None
1226 full = listclean or match.traversedir is not None
1213 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1227 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1214 full=full).iteritems():
1228 full=full).iteritems():
1215 if fn not in dmap:
1229 if fn not in dmap:
1216 if (listignored or mexact(fn)) and dirignore(fn):
1230 if (listignored or mexact(fn)) and dirignore(fn):
1217 if listignored:
1231 if listignored:
1218 iadd(fn)
1232 iadd(fn)
1219 else:
1233 else:
1220 uadd(fn)
1234 uadd(fn)
1221 continue
1235 continue
1222
1236
1223 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1237 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1224 # written like that for performance reasons. dmap[fn] is not a
1238 # written like that for performance reasons. dmap[fn] is not a
1225 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1239 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1226 # opcode has fast paths when the value to be unpacked is a tuple or
1240 # opcode has fast paths when the value to be unpacked is a tuple or
1227 # a list, but falls back to creating a full-fledged iterator in
1241 # a list, but falls back to creating a full-fledged iterator in
1228 # general. That is much slower than simply accessing and storing the
1242 # general. That is much slower than simply accessing and storing the
1229 # tuple members one by one.
1243 # tuple members one by one.
1230 t = dmap[fn]
1244 t = dmap[fn]
1231 state = t[0]
1245 state = t[0]
1232 mode = t[1]
1246 mode = t[1]
1233 size = t[2]
1247 size = t[2]
1234 time = t[3]
1248 time = t[3]
1235
1249
1236 if not st and state in "nma":
1250 if not st and state in "nma":
1237 dadd(fn)
1251 dadd(fn)
1238 elif state == 'n':
1252 elif state == 'n':
1239 if (size >= 0 and
1253 if (size >= 0 and
1240 ((size != st.st_size and size != st.st_size & _rangemask)
1254 ((size != st.st_size and size != st.st_size & _rangemask)
1241 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1255 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1242 or size == -2 # other parent
1256 or size == -2 # other parent
1243 or fn in copymap):
1257 or fn in copymap):
1244 madd(fn)
1258 madd(fn)
1245 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1259 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1246 ladd(fn)
1260 ladd(fn)
1247 elif st.st_mtime == lastnormaltime:
1261 elif st.st_mtime == lastnormaltime:
1248 # fn may have just been marked as normal and it may have
1262 # fn may have just been marked as normal and it may have
1249 # changed in the same second without changing its size.
1263 # changed in the same second without changing its size.
1250 # This can happen if we quickly do multiple commits.
1264 # This can happen if we quickly do multiple commits.
1251 # Force lookup, so we don't miss such a racy file change.
1265 # Force lookup, so we don't miss such a racy file change.
1252 ladd(fn)
1266 ladd(fn)
1253 elif listclean:
1267 elif listclean:
1254 cadd(fn)
1268 cadd(fn)
1255 elif state == 'm':
1269 elif state == 'm':
1256 madd(fn)
1270 madd(fn)
1257 elif state == 'a':
1271 elif state == 'a':
1258 aadd(fn)
1272 aadd(fn)
1259 elif state == 'r':
1273 elif state == 'r':
1260 radd(fn)
1274 radd(fn)
1261
1275
1262 return (lookup, scmutil.status(modified, added, removed, deleted,
1276 return (lookup, scmutil.status(modified, added, removed, deleted,
1263 unknown, ignored, clean))
1277 unknown, ignored, clean))
1264
1278
1265 def matches(self, match):
1279 def matches(self, match):
1266 '''
1280 '''
1267 return files in the dirstate (in whatever state) filtered by match
1281 return files in the dirstate (in whatever state) filtered by match
1268 '''
1282 '''
1269 dmap = self._map
1283 dmap = self._map
1270 if match.always():
1284 if match.always():
1271 return dmap.keys()
1285 return dmap.keys()
1272 files = match.files()
1286 files = match.files()
1273 if match.isexact():
1287 if match.isexact():
1274 # fast path -- filter the other way around, since typically files is
1288 # fast path -- filter the other way around, since typically files is
1275 # much smaller than dmap
1289 # much smaller than dmap
1276 return [f for f in files if f in dmap]
1290 return [f for f in files if f in dmap]
1277 if match.prefix() and all(fn in dmap for fn in files):
1291 if match.prefix() and all(fn in dmap for fn in files):
1278 # fast path -- all the values are known to be files, so just return
1292 # fast path -- all the values are known to be files, so just return
1279 # that
1293 # that
1280 return list(files)
1294 return list(files)
1281 return [f for f in dmap if match(f)]
1295 return [f for f in dmap if match(f)]
1282
1296
1283 def _actualfilename(self, tr):
1297 def _actualfilename(self, tr):
1284 if tr:
1298 if tr:
1285 return self._pendingfilename
1299 return self._pendingfilename
1286 else:
1300 else:
1287 return self._filename
1301 return self._filename
1288
1302
1289 def savebackup(self, tr, suffix='', prefix=''):
1303 def savebackup(self, tr, suffix='', prefix=''):
1290 '''Save current dirstate into backup file with suffix'''
1304 '''Save current dirstate into backup file with suffix'''
1291 assert len(suffix) > 0 or len(prefix) > 0
1305 assert len(suffix) > 0 or len(prefix) > 0
1292 filename = self._actualfilename(tr)
1306 filename = self._actualfilename(tr)
1293
1307
1294 # use '_writedirstate' instead of 'write' to write changes certainly,
1308 # use '_writedirstate' instead of 'write' to write changes certainly,
1295 # because the latter omits writing out if transaction is running.
1309 # because the latter omits writing out if transaction is running.
1296 # output file will be used to create backup of dirstate at this point.
1310 # output file will be used to create backup of dirstate at this point.
1297 if self._dirty or not self._opener.exists(filename):
1311 if self._dirty or not self._opener.exists(filename):
1298 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1312 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1299 checkambig=True))
1313 checkambig=True))
1300
1314
1301 if tr:
1315 if tr:
1302 # ensure that subsequent tr.writepending returns True for
1316 # ensure that subsequent tr.writepending returns True for
1303 # changes written out above, even if dirstate is never
1317 # changes written out above, even if dirstate is never
1304 # changed after this
1318 # changed after this
1305 tr.addfilegenerator('dirstate', (self._filename,),
1319 tr.addfilegenerator('dirstate', (self._filename,),
1306 self._writedirstate, location='plain')
1320 self._writedirstate, location='plain')
1307
1321
1308 # ensure that pending file written above is unlinked at
1322 # ensure that pending file written above is unlinked at
1309 # failure, even if tr.writepending isn't invoked until the
1323 # failure, even if tr.writepending isn't invoked until the
1310 # end of this transaction
1324 # end of this transaction
1311 tr.registertmp(filename, location='plain')
1325 tr.registertmp(filename, location='plain')
1312
1326
1313 backupname = prefix + self._filename + suffix
1327 backupname = prefix + self._filename + suffix
1314 assert backupname != filename
1328 assert backupname != filename
1315 self._opener.tryunlink(backupname)
1329 self._opener.tryunlink(backupname)
1316 # hardlink backup is okay because _writedirstate is always called
1330 # hardlink backup is okay because _writedirstate is always called
1317 # with an "atomictemp=True" file.
1331 # with an "atomictemp=True" file.
1318 util.copyfile(self._opener.join(filename),
1332 util.copyfile(self._opener.join(filename),
1319 self._opener.join(backupname), hardlink=True)
1333 self._opener.join(backupname), hardlink=True)
1320
1334
1321 def restorebackup(self, tr, suffix='', prefix=''):
1335 def restorebackup(self, tr, suffix='', prefix=''):
1322 '''Restore dirstate by backup file with suffix'''
1336 '''Restore dirstate by backup file with suffix'''
1323 assert len(suffix) > 0 or len(prefix) > 0
1337 assert len(suffix) > 0 or len(prefix) > 0
1324 # this "invalidate()" prevents "wlock.release()" from writing
1338 # this "invalidate()" prevents "wlock.release()" from writing
1325 # changes of dirstate out after restoring from backup file
1339 # changes of dirstate out after restoring from backup file
1326 self.invalidate()
1340 self.invalidate()
1327 filename = self._actualfilename(tr)
1341 filename = self._actualfilename(tr)
1328 # using self._filename to avoid having "pending" in the backup filename
1342 # using self._filename to avoid having "pending" in the backup filename
1329 self._opener.rename(prefix + self._filename + suffix, filename,
1343 self._opener.rename(prefix + self._filename + suffix, filename,
1330 checkambig=True)
1344 checkambig=True)
1331
1345
1332 def clearbackup(self, tr, suffix='', prefix=''):
1346 def clearbackup(self, tr, suffix='', prefix=''):
1333 '''Clear backup file with suffix'''
1347 '''Clear backup file with suffix'''
1334 assert len(suffix) > 0 or len(prefix) > 0
1348 assert len(suffix) > 0 or len(prefix) > 0
1335 # using self._filename to avoid having "pending" in the backup filename
1349 # using self._filename to avoid having "pending" in the backup filename
1336 self._opener.unlink(prefix + self._filename + suffix)
1350 self._opener.unlink(prefix + self._filename + suffix)
@@ -1,2140 +1,2143 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import inspect
12 import inspect
13 import os
13 import os
14 import random
14 import random
15 import time
15 import time
16 import weakref
16 import weakref
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import (
19 from .node import (
20 hex,
20 hex,
21 nullid,
21 nullid,
22 short,
22 short,
23 )
23 )
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 branchmap,
26 branchmap,
27 bundle2,
27 bundle2,
28 changegroup,
28 changegroup,
29 changelog,
29 changelog,
30 color,
30 color,
31 context,
31 context,
32 dirstate,
32 dirstate,
33 dirstateguard,
33 dirstateguard,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 filelog,
38 filelog,
39 hook,
39 hook,
40 lock as lockmod,
40 lock as lockmod,
41 manifest,
41 manifest,
42 match as matchmod,
42 match as matchmod,
43 merge as mergemod,
43 merge as mergemod,
44 mergeutil,
44 mergeutil,
45 namespaces,
45 namespaces,
46 obsolete,
46 obsolete,
47 pathutil,
47 pathutil,
48 peer,
48 peer,
49 phases,
49 phases,
50 pushkey,
50 pushkey,
51 pycompat,
51 pycompat,
52 repoview,
52 repoview,
53 revset,
53 revset,
54 revsetlang,
54 revsetlang,
55 scmutil,
55 scmutil,
56 sparse,
56 store,
57 store,
57 subrepo,
58 subrepo,
58 tags as tagsmod,
59 tags as tagsmod,
59 transaction,
60 transaction,
60 txnutil,
61 txnutil,
61 util,
62 util,
62 vfs as vfsmod,
63 vfs as vfsmod,
63 )
64 )
64
65
65 release = lockmod.release
66 release = lockmod.release
66 urlerr = util.urlerr
67 urlerr = util.urlerr
67 urlreq = util.urlreq
68 urlreq = util.urlreq
68
69
69 # set of (path, vfs-location) tuples. vfs-location is:
70 # set of (path, vfs-location) tuples. vfs-location is:
70 # - 'plain for vfs relative paths
71 # - 'plain for vfs relative paths
71 # - '' for svfs relative paths
72 # - '' for svfs relative paths
72 _cachedfiles = set()
73 _cachedfiles = set()
73
74
74 class _basefilecache(scmutil.filecache):
75 class _basefilecache(scmutil.filecache):
75 """All filecache usage on repo are done for logic that should be unfiltered
76 """All filecache usage on repo are done for logic that should be unfiltered
76 """
77 """
77 def __get__(self, repo, type=None):
78 def __get__(self, repo, type=None):
78 if repo is None:
79 if repo is None:
79 return self
80 return self
80 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
81 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
81 def __set__(self, repo, value):
82 def __set__(self, repo, value):
82 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
83 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
83 def __delete__(self, repo):
84 def __delete__(self, repo):
84 return super(_basefilecache, self).__delete__(repo.unfiltered())
85 return super(_basefilecache, self).__delete__(repo.unfiltered())
85
86
86 class repofilecache(_basefilecache):
87 class repofilecache(_basefilecache):
87 """filecache for files in .hg but outside of .hg/store"""
88 """filecache for files in .hg but outside of .hg/store"""
88 def __init__(self, *paths):
89 def __init__(self, *paths):
89 super(repofilecache, self).__init__(*paths)
90 super(repofilecache, self).__init__(*paths)
90 for path in paths:
91 for path in paths:
91 _cachedfiles.add((path, 'plain'))
92 _cachedfiles.add((path, 'plain'))
92
93
93 def join(self, obj, fname):
94 def join(self, obj, fname):
94 return obj.vfs.join(fname)
95 return obj.vfs.join(fname)
95
96
96 class storecache(_basefilecache):
97 class storecache(_basefilecache):
97 """filecache for files in the store"""
98 """filecache for files in the store"""
98 def __init__(self, *paths):
99 def __init__(self, *paths):
99 super(storecache, self).__init__(*paths)
100 super(storecache, self).__init__(*paths)
100 for path in paths:
101 for path in paths:
101 _cachedfiles.add((path, ''))
102 _cachedfiles.add((path, ''))
102
103
103 def join(self, obj, fname):
104 def join(self, obj, fname):
104 return obj.sjoin(fname)
105 return obj.sjoin(fname)
105
106
106 class unfilteredpropertycache(util.propertycache):
107 class unfilteredpropertycache(util.propertycache):
107 """propertycache that apply to unfiltered repo only"""
108 """propertycache that apply to unfiltered repo only"""
108
109
109 def __get__(self, repo, type=None):
110 def __get__(self, repo, type=None):
110 unfi = repo.unfiltered()
111 unfi = repo.unfiltered()
111 if unfi is repo:
112 if unfi is repo:
112 return super(unfilteredpropertycache, self).__get__(unfi)
113 return super(unfilteredpropertycache, self).__get__(unfi)
113 return getattr(unfi, self.name)
114 return getattr(unfi, self.name)
114
115
115 class filteredpropertycache(util.propertycache):
116 class filteredpropertycache(util.propertycache):
116 """propertycache that must take filtering in account"""
117 """propertycache that must take filtering in account"""
117
118
118 def cachevalue(self, obj, value):
119 def cachevalue(self, obj, value):
119 object.__setattr__(obj, self.name, value)
120 object.__setattr__(obj, self.name, value)
120
121
121
122
122 def hasunfilteredcache(repo, name):
123 def hasunfilteredcache(repo, name):
123 """check if a repo has an unfilteredpropertycache value for <name>"""
124 """check if a repo has an unfilteredpropertycache value for <name>"""
124 return name in vars(repo.unfiltered())
125 return name in vars(repo.unfiltered())
125
126
126 def unfilteredmethod(orig):
127 def unfilteredmethod(orig):
127 """decorate method that always need to be run on unfiltered version"""
128 """decorate method that always need to be run on unfiltered version"""
128 def wrapper(repo, *args, **kwargs):
129 def wrapper(repo, *args, **kwargs):
129 return orig(repo.unfiltered(), *args, **kwargs)
130 return orig(repo.unfiltered(), *args, **kwargs)
130 return wrapper
131 return wrapper
131
132
132 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
133 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
133 'unbundle'}
134 'unbundle'}
134 legacycaps = moderncaps.union({'changegroupsubset'})
135 legacycaps = moderncaps.union({'changegroupsubset'})
135
136
136 class localpeer(peer.peerrepository):
137 class localpeer(peer.peerrepository):
137 '''peer for a local repo; reflects only the most recent API'''
138 '''peer for a local repo; reflects only the most recent API'''
138
139
139 def __init__(self, repo, caps=None):
140 def __init__(self, repo, caps=None):
140 if caps is None:
141 if caps is None:
141 caps = moderncaps.copy()
142 caps = moderncaps.copy()
142 peer.peerrepository.__init__(self)
143 peer.peerrepository.__init__(self)
143 self._repo = repo.filtered('served')
144 self._repo = repo.filtered('served')
144 self.ui = repo.ui
145 self.ui = repo.ui
145 self._caps = repo._restrictcapabilities(caps)
146 self._caps = repo._restrictcapabilities(caps)
146 self.requirements = repo.requirements
147 self.requirements = repo.requirements
147 self.supportedformats = repo.supportedformats
148 self.supportedformats = repo.supportedformats
148
149
149 def close(self):
150 def close(self):
150 self._repo.close()
151 self._repo.close()
151
152
152 def _capabilities(self):
153 def _capabilities(self):
153 return self._caps
154 return self._caps
154
155
155 def local(self):
156 def local(self):
156 return self._repo
157 return self._repo
157
158
158 def canpush(self):
159 def canpush(self):
159 return True
160 return True
160
161
161 def url(self):
162 def url(self):
162 return self._repo.url()
163 return self._repo.url()
163
164
164 def lookup(self, key):
165 def lookup(self, key):
165 return self._repo.lookup(key)
166 return self._repo.lookup(key)
166
167
167 def branchmap(self):
168 def branchmap(self):
168 return self._repo.branchmap()
169 return self._repo.branchmap()
169
170
170 def heads(self):
171 def heads(self):
171 return self._repo.heads()
172 return self._repo.heads()
172
173
173 def known(self, nodes):
174 def known(self, nodes):
174 return self._repo.known(nodes)
175 return self._repo.known(nodes)
175
176
176 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
177 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
177 **kwargs):
178 **kwargs):
178 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
179 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
179 common=common, bundlecaps=bundlecaps,
180 common=common, bundlecaps=bundlecaps,
180 **kwargs)
181 **kwargs)
181 cb = util.chunkbuffer(chunks)
182 cb = util.chunkbuffer(chunks)
182
183
183 if exchange.bundle2requested(bundlecaps):
184 if exchange.bundle2requested(bundlecaps):
184 # When requesting a bundle2, getbundle returns a stream to make the
185 # When requesting a bundle2, getbundle returns a stream to make the
185 # wire level function happier. We need to build a proper object
186 # wire level function happier. We need to build a proper object
186 # from it in local peer.
187 # from it in local peer.
187 return bundle2.getunbundler(self.ui, cb)
188 return bundle2.getunbundler(self.ui, cb)
188 else:
189 else:
189 return changegroup.getunbundler('01', cb, None)
190 return changegroup.getunbundler('01', cb, None)
190
191
191 # TODO We might want to move the next two calls into legacypeer and add
192 # TODO We might want to move the next two calls into legacypeer and add
192 # unbundle instead.
193 # unbundle instead.
193
194
194 def unbundle(self, cg, heads, url):
195 def unbundle(self, cg, heads, url):
195 """apply a bundle on a repo
196 """apply a bundle on a repo
196
197
197 This function handles the repo locking itself."""
198 This function handles the repo locking itself."""
198 try:
199 try:
199 try:
200 try:
200 cg = exchange.readbundle(self.ui, cg, None)
201 cg = exchange.readbundle(self.ui, cg, None)
201 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
202 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
202 if util.safehasattr(ret, 'getchunks'):
203 if util.safehasattr(ret, 'getchunks'):
203 # This is a bundle20 object, turn it into an unbundler.
204 # This is a bundle20 object, turn it into an unbundler.
204 # This little dance should be dropped eventually when the
205 # This little dance should be dropped eventually when the
205 # API is finally improved.
206 # API is finally improved.
206 stream = util.chunkbuffer(ret.getchunks())
207 stream = util.chunkbuffer(ret.getchunks())
207 ret = bundle2.getunbundler(self.ui, stream)
208 ret = bundle2.getunbundler(self.ui, stream)
208 return ret
209 return ret
209 except Exception as exc:
210 except Exception as exc:
210 # If the exception contains output salvaged from a bundle2
211 # If the exception contains output salvaged from a bundle2
211 # reply, we need to make sure it is printed before continuing
212 # reply, we need to make sure it is printed before continuing
212 # to fail. So we build a bundle2 with such output and consume
213 # to fail. So we build a bundle2 with such output and consume
213 # it directly.
214 # it directly.
214 #
215 #
215 # This is not very elegant but allows a "simple" solution for
216 # This is not very elegant but allows a "simple" solution for
216 # issue4594
217 # issue4594
217 output = getattr(exc, '_bundle2salvagedoutput', ())
218 output = getattr(exc, '_bundle2salvagedoutput', ())
218 if output:
219 if output:
219 bundler = bundle2.bundle20(self._repo.ui)
220 bundler = bundle2.bundle20(self._repo.ui)
220 for out in output:
221 for out in output:
221 bundler.addpart(out)
222 bundler.addpart(out)
222 stream = util.chunkbuffer(bundler.getchunks())
223 stream = util.chunkbuffer(bundler.getchunks())
223 b = bundle2.getunbundler(self.ui, stream)
224 b = bundle2.getunbundler(self.ui, stream)
224 bundle2.processbundle(self._repo, b)
225 bundle2.processbundle(self._repo, b)
225 raise
226 raise
226 except error.PushRaced as exc:
227 except error.PushRaced as exc:
227 raise error.ResponseError(_('push failed:'), str(exc))
228 raise error.ResponseError(_('push failed:'), str(exc))
228
229
229 def lock(self):
230 def lock(self):
230 return self._repo.lock()
231 return self._repo.lock()
231
232
232 def pushkey(self, namespace, key, old, new):
233 def pushkey(self, namespace, key, old, new):
233 return self._repo.pushkey(namespace, key, old, new)
234 return self._repo.pushkey(namespace, key, old, new)
234
235
235 def listkeys(self, namespace):
236 def listkeys(self, namespace):
236 return self._repo.listkeys(namespace)
237 return self._repo.listkeys(namespace)
237
238
238 def debugwireargs(self, one, two, three=None, four=None, five=None):
239 def debugwireargs(self, one, two, three=None, four=None, five=None):
239 '''used to test argument passing over the wire'''
240 '''used to test argument passing over the wire'''
240 return "%s %s %s %s %s" % (one, two, three, four, five)
241 return "%s %s %s %s %s" % (one, two, three, four, five)
241
242
242 class locallegacypeer(localpeer):
243 class locallegacypeer(localpeer):
243 '''peer extension which implements legacy methods too; used for tests with
244 '''peer extension which implements legacy methods too; used for tests with
244 restricted capabilities'''
245 restricted capabilities'''
245
246
246 def __init__(self, repo):
247 def __init__(self, repo):
247 localpeer.__init__(self, repo, caps=legacycaps)
248 localpeer.__init__(self, repo, caps=legacycaps)
248
249
249 def branches(self, nodes):
250 def branches(self, nodes):
250 return self._repo.branches(nodes)
251 return self._repo.branches(nodes)
251
252
252 def between(self, pairs):
253 def between(self, pairs):
253 return self._repo.between(pairs)
254 return self._repo.between(pairs)
254
255
255 def changegroup(self, basenodes, source):
256 def changegroup(self, basenodes, source):
256 return changegroup.changegroup(self._repo, basenodes, source)
257 return changegroup.changegroup(self._repo, basenodes, source)
257
258
258 def changegroupsubset(self, bases, heads, source):
259 def changegroupsubset(self, bases, heads, source):
259 return changegroup.changegroupsubset(self._repo, bases, heads, source)
260 return changegroup.changegroupsubset(self._repo, bases, heads, source)
260
261
261 # Increment the sub-version when the revlog v2 format changes to lock out old
262 # Increment the sub-version when the revlog v2 format changes to lock out old
262 # clients.
263 # clients.
263 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
264 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
264
265
265 class localrepository(object):
266 class localrepository(object):
266
267
267 supportedformats = {
268 supportedformats = {
268 'revlogv1',
269 'revlogv1',
269 'generaldelta',
270 'generaldelta',
270 'treemanifest',
271 'treemanifest',
271 'manifestv2',
272 'manifestv2',
272 REVLOGV2_REQUIREMENT,
273 REVLOGV2_REQUIREMENT,
273 }
274 }
274 _basesupported = supportedformats | {
275 _basesupported = supportedformats | {
275 'store',
276 'store',
276 'fncache',
277 'fncache',
277 'shared',
278 'shared',
278 'relshared',
279 'relshared',
279 'dotencode',
280 'dotencode',
280 }
281 }
281 openerreqs = {
282 openerreqs = {
282 'revlogv1',
283 'revlogv1',
283 'generaldelta',
284 'generaldelta',
284 'treemanifest',
285 'treemanifest',
285 'manifestv2',
286 'manifestv2',
286 }
287 }
287
288
288 # a list of (ui, featureset) functions.
289 # a list of (ui, featureset) functions.
289 # only functions defined in module of enabled extensions are invoked
290 # only functions defined in module of enabled extensions are invoked
290 featuresetupfuncs = set()
291 featuresetupfuncs = set()
291
292
292 def __init__(self, baseui, path, create=False):
293 def __init__(self, baseui, path, create=False):
293 self.requirements = set()
294 self.requirements = set()
294 self.filtername = None
295 self.filtername = None
295 # wvfs: rooted at the repository root, used to access the working copy
296 # wvfs: rooted at the repository root, used to access the working copy
296 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
297 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
297 # vfs: rooted at .hg, used to access repo files outside of .hg/store
298 # vfs: rooted at .hg, used to access repo files outside of .hg/store
298 self.vfs = None
299 self.vfs = None
299 # svfs: usually rooted at .hg/store, used to access repository history
300 # svfs: usually rooted at .hg/store, used to access repository history
300 # If this is a shared repository, this vfs may point to another
301 # If this is a shared repository, this vfs may point to another
301 # repository's .hg/store directory.
302 # repository's .hg/store directory.
302 self.svfs = None
303 self.svfs = None
303 self.root = self.wvfs.base
304 self.root = self.wvfs.base
304 self.path = self.wvfs.join(".hg")
305 self.path = self.wvfs.join(".hg")
305 self.origroot = path
306 self.origroot = path
306 # These auditor are not used by the vfs,
307 # These auditor are not used by the vfs,
307 # only used when writing this comment: basectx.match
308 # only used when writing this comment: basectx.match
308 self.auditor = pathutil.pathauditor(self.root, self._checknested)
309 self.auditor = pathutil.pathauditor(self.root, self._checknested)
309 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
310 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
310 realfs=False)
311 realfs=False)
311 self.vfs = vfsmod.vfs(self.path)
312 self.vfs = vfsmod.vfs(self.path)
312 self.baseui = baseui
313 self.baseui = baseui
313 self.ui = baseui.copy()
314 self.ui = baseui.copy()
314 self.ui.copy = baseui.copy # prevent copying repo configuration
315 self.ui.copy = baseui.copy # prevent copying repo configuration
315 # A list of callback to shape the phase if no data were found.
316 # A list of callback to shape the phase if no data were found.
316 # Callback are in the form: func(repo, roots) --> processed root.
317 # Callback are in the form: func(repo, roots) --> processed root.
317 # This list it to be filled by extension during repo setup
318 # This list it to be filled by extension during repo setup
318 self._phasedefaults = []
319 self._phasedefaults = []
319 try:
320 try:
320 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
321 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
321 self._loadextensions()
322 self._loadextensions()
322 except IOError:
323 except IOError:
323 pass
324 pass
324
325
325 if self.featuresetupfuncs:
326 if self.featuresetupfuncs:
326 self.supported = set(self._basesupported) # use private copy
327 self.supported = set(self._basesupported) # use private copy
327 extmods = set(m.__name__ for n, m
328 extmods = set(m.__name__ for n, m
328 in extensions.extensions(self.ui))
329 in extensions.extensions(self.ui))
329 for setupfunc in self.featuresetupfuncs:
330 for setupfunc in self.featuresetupfuncs:
330 if setupfunc.__module__ in extmods:
331 if setupfunc.__module__ in extmods:
331 setupfunc(self.ui, self.supported)
332 setupfunc(self.ui, self.supported)
332 else:
333 else:
333 self.supported = self._basesupported
334 self.supported = self._basesupported
334 color.setup(self.ui)
335 color.setup(self.ui)
335
336
336 # Add compression engines.
337 # Add compression engines.
337 for name in util.compengines:
338 for name in util.compengines:
338 engine = util.compengines[name]
339 engine = util.compengines[name]
339 if engine.revlogheader():
340 if engine.revlogheader():
340 self.supported.add('exp-compression-%s' % name)
341 self.supported.add('exp-compression-%s' % name)
341
342
342 if not self.vfs.isdir():
343 if not self.vfs.isdir():
343 if create:
344 if create:
344 self.requirements = newreporequirements(self)
345 self.requirements = newreporequirements(self)
345
346
346 if not self.wvfs.exists():
347 if not self.wvfs.exists():
347 self.wvfs.makedirs()
348 self.wvfs.makedirs()
348 self.vfs.makedir(notindexed=True)
349 self.vfs.makedir(notindexed=True)
349
350
350 if 'store' in self.requirements:
351 if 'store' in self.requirements:
351 self.vfs.mkdir("store")
352 self.vfs.mkdir("store")
352
353
353 # create an invalid changelog
354 # create an invalid changelog
354 self.vfs.append(
355 self.vfs.append(
355 "00changelog.i",
356 "00changelog.i",
356 '\0\0\0\2' # represents revlogv2
357 '\0\0\0\2' # represents revlogv2
357 ' dummy changelog to prevent using the old repo layout'
358 ' dummy changelog to prevent using the old repo layout'
358 )
359 )
359 else:
360 else:
360 raise error.RepoError(_("repository %s not found") % path)
361 raise error.RepoError(_("repository %s not found") % path)
361 elif create:
362 elif create:
362 raise error.RepoError(_("repository %s already exists") % path)
363 raise error.RepoError(_("repository %s already exists") % path)
363 else:
364 else:
364 try:
365 try:
365 self.requirements = scmutil.readrequires(
366 self.requirements = scmutil.readrequires(
366 self.vfs, self.supported)
367 self.vfs, self.supported)
367 except IOError as inst:
368 except IOError as inst:
368 if inst.errno != errno.ENOENT:
369 if inst.errno != errno.ENOENT:
369 raise
370 raise
370
371
371 self.sharedpath = self.path
372 self.sharedpath = self.path
372 try:
373 try:
373 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
374 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
374 if 'relshared' in self.requirements:
375 if 'relshared' in self.requirements:
375 sharedpath = self.vfs.join(sharedpath)
376 sharedpath = self.vfs.join(sharedpath)
376 vfs = vfsmod.vfs(sharedpath, realpath=True)
377 vfs = vfsmod.vfs(sharedpath, realpath=True)
377 s = vfs.base
378 s = vfs.base
378 if not vfs.exists():
379 if not vfs.exists():
379 raise error.RepoError(
380 raise error.RepoError(
380 _('.hg/sharedpath points to nonexistent directory %s') % s)
381 _('.hg/sharedpath points to nonexistent directory %s') % s)
381 self.sharedpath = s
382 self.sharedpath = s
382 except IOError as inst:
383 except IOError as inst:
383 if inst.errno != errno.ENOENT:
384 if inst.errno != errno.ENOENT:
384 raise
385 raise
385
386
386 self.store = store.store(
387 self.store = store.store(
387 self.requirements, self.sharedpath, vfsmod.vfs)
388 self.requirements, self.sharedpath, vfsmod.vfs)
388 self.spath = self.store.path
389 self.spath = self.store.path
389 self.svfs = self.store.vfs
390 self.svfs = self.store.vfs
390 self.sjoin = self.store.join
391 self.sjoin = self.store.join
391 self.vfs.createmode = self.store.createmode
392 self.vfs.createmode = self.store.createmode
392 self._applyopenerreqs()
393 self._applyopenerreqs()
393 if create:
394 if create:
394 self._writerequirements()
395 self._writerequirements()
395
396
396 self._dirstatevalidatewarned = False
397 self._dirstatevalidatewarned = False
397
398
398 self._branchcaches = {}
399 self._branchcaches = {}
399 self._revbranchcache = None
400 self._revbranchcache = None
400 self.filterpats = {}
401 self.filterpats = {}
401 self._datafilters = {}
402 self._datafilters = {}
402 self._transref = self._lockref = self._wlockref = None
403 self._transref = self._lockref = self._wlockref = None
403
404
404 # A cache for various files under .hg/ that tracks file changes,
405 # A cache for various files under .hg/ that tracks file changes,
405 # (used by the filecache decorator)
406 # (used by the filecache decorator)
406 #
407 #
407 # Maps a property name to its util.filecacheentry
408 # Maps a property name to its util.filecacheentry
408 self._filecache = {}
409 self._filecache = {}
409
410
410 # hold sets of revision to be filtered
411 # hold sets of revision to be filtered
411 # should be cleared when something might have changed the filter value:
412 # should be cleared when something might have changed the filter value:
412 # - new changesets,
413 # - new changesets,
413 # - phase change,
414 # - phase change,
414 # - new obsolescence marker,
415 # - new obsolescence marker,
415 # - working directory parent change,
416 # - working directory parent change,
416 # - bookmark changes
417 # - bookmark changes
417 self.filteredrevcache = {}
418 self.filteredrevcache = {}
418
419
419 # post-dirstate-status hooks
420 # post-dirstate-status hooks
420 self._postdsstatus = []
421 self._postdsstatus = []
421
422
422 # generic mapping between names and nodes
423 # generic mapping between names and nodes
423 self.names = namespaces.namespaces()
424 self.names = namespaces.namespaces()
424
425
425 # Key to signature value.
426 # Key to signature value.
426 self._sparsesignaturecache = {}
427 self._sparsesignaturecache = {}
427 # Signature to cached matcher instance.
428 # Signature to cached matcher instance.
428 self._sparsematchercache = {}
429 self._sparsematchercache = {}
429
430
430 def close(self):
431 def close(self):
431 self._writecaches()
432 self._writecaches()
432
433
433 def _loadextensions(self):
434 def _loadextensions(self):
434 extensions.loadall(self.ui)
435 extensions.loadall(self.ui)
435
436
436 def _writecaches(self):
437 def _writecaches(self):
437 if self._revbranchcache:
438 if self._revbranchcache:
438 self._revbranchcache.write()
439 self._revbranchcache.write()
439
440
440 def _restrictcapabilities(self, caps):
441 def _restrictcapabilities(self, caps):
441 if self.ui.configbool('experimental', 'bundle2-advertise', True):
442 if self.ui.configbool('experimental', 'bundle2-advertise', True):
442 caps = set(caps)
443 caps = set(caps)
443 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
444 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
444 caps.add('bundle2=' + urlreq.quote(capsblob))
445 caps.add('bundle2=' + urlreq.quote(capsblob))
445 return caps
446 return caps
446
447
447 def _applyopenerreqs(self):
448 def _applyopenerreqs(self):
448 self.svfs.options = dict((r, 1) for r in self.requirements
449 self.svfs.options = dict((r, 1) for r in self.requirements
449 if r in self.openerreqs)
450 if r in self.openerreqs)
450 # experimental config: format.chunkcachesize
451 # experimental config: format.chunkcachesize
451 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
452 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
452 if chunkcachesize is not None:
453 if chunkcachesize is not None:
453 self.svfs.options['chunkcachesize'] = chunkcachesize
454 self.svfs.options['chunkcachesize'] = chunkcachesize
454 # experimental config: format.maxchainlen
455 # experimental config: format.maxchainlen
455 maxchainlen = self.ui.configint('format', 'maxchainlen')
456 maxchainlen = self.ui.configint('format', 'maxchainlen')
456 if maxchainlen is not None:
457 if maxchainlen is not None:
457 self.svfs.options['maxchainlen'] = maxchainlen
458 self.svfs.options['maxchainlen'] = maxchainlen
458 # experimental config: format.manifestcachesize
459 # experimental config: format.manifestcachesize
459 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
460 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
460 if manifestcachesize is not None:
461 if manifestcachesize is not None:
461 self.svfs.options['manifestcachesize'] = manifestcachesize
462 self.svfs.options['manifestcachesize'] = manifestcachesize
462 # experimental config: format.aggressivemergedeltas
463 # experimental config: format.aggressivemergedeltas
463 aggressivemergedeltas = self.ui.configbool('format',
464 aggressivemergedeltas = self.ui.configbool('format',
464 'aggressivemergedeltas')
465 'aggressivemergedeltas')
465 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
466 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
466 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
467 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
467 chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
468 chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
468 if 0 <= chainspan:
469 if 0 <= chainspan:
469 self.svfs.options['maxdeltachainspan'] = chainspan
470 self.svfs.options['maxdeltachainspan'] = chainspan
470
471
471 for r in self.requirements:
472 for r in self.requirements:
472 if r.startswith('exp-compression-'):
473 if r.startswith('exp-compression-'):
473 self.svfs.options['compengine'] = r[len('exp-compression-'):]
474 self.svfs.options['compengine'] = r[len('exp-compression-'):]
474
475
475 # TODO move "revlogv2" to openerreqs once finalized.
476 # TODO move "revlogv2" to openerreqs once finalized.
476 if REVLOGV2_REQUIREMENT in self.requirements:
477 if REVLOGV2_REQUIREMENT in self.requirements:
477 self.svfs.options['revlogv2'] = True
478 self.svfs.options['revlogv2'] = True
478
479
479 def _writerequirements(self):
480 def _writerequirements(self):
480 scmutil.writerequires(self.vfs, self.requirements)
481 scmutil.writerequires(self.vfs, self.requirements)
481
482
482 def _checknested(self, path):
483 def _checknested(self, path):
483 """Determine if path is a legal nested repository."""
484 """Determine if path is a legal nested repository."""
484 if not path.startswith(self.root):
485 if not path.startswith(self.root):
485 return False
486 return False
486 subpath = path[len(self.root) + 1:]
487 subpath = path[len(self.root) + 1:]
487 normsubpath = util.pconvert(subpath)
488 normsubpath = util.pconvert(subpath)
488
489
489 # XXX: Checking against the current working copy is wrong in
490 # XXX: Checking against the current working copy is wrong in
490 # the sense that it can reject things like
491 # the sense that it can reject things like
491 #
492 #
492 # $ hg cat -r 10 sub/x.txt
493 # $ hg cat -r 10 sub/x.txt
493 #
494 #
494 # if sub/ is no longer a subrepository in the working copy
495 # if sub/ is no longer a subrepository in the working copy
495 # parent revision.
496 # parent revision.
496 #
497 #
497 # However, it can of course also allow things that would have
498 # However, it can of course also allow things that would have
498 # been rejected before, such as the above cat command if sub/
499 # been rejected before, such as the above cat command if sub/
499 # is a subrepository now, but was a normal directory before.
500 # is a subrepository now, but was a normal directory before.
500 # The old path auditor would have rejected by mistake since it
501 # The old path auditor would have rejected by mistake since it
501 # panics when it sees sub/.hg/.
502 # panics when it sees sub/.hg/.
502 #
503 #
503 # All in all, checking against the working copy seems sensible
504 # All in all, checking against the working copy seems sensible
504 # since we want to prevent access to nested repositories on
505 # since we want to prevent access to nested repositories on
505 # the filesystem *now*.
506 # the filesystem *now*.
506 ctx = self[None]
507 ctx = self[None]
507 parts = util.splitpath(subpath)
508 parts = util.splitpath(subpath)
508 while parts:
509 while parts:
509 prefix = '/'.join(parts)
510 prefix = '/'.join(parts)
510 if prefix in ctx.substate:
511 if prefix in ctx.substate:
511 if prefix == normsubpath:
512 if prefix == normsubpath:
512 return True
513 return True
513 else:
514 else:
514 sub = ctx.sub(prefix)
515 sub = ctx.sub(prefix)
515 return sub.checknested(subpath[len(prefix) + 1:])
516 return sub.checknested(subpath[len(prefix) + 1:])
516 else:
517 else:
517 parts.pop()
518 parts.pop()
518 return False
519 return False
519
520
520 def peer(self):
521 def peer(self):
521 return localpeer(self) # not cached to avoid reference cycle
522 return localpeer(self) # not cached to avoid reference cycle
522
523
523 def unfiltered(self):
524 def unfiltered(self):
524 """Return unfiltered version of the repository
525 """Return unfiltered version of the repository
525
526
526 Intended to be overwritten by filtered repo."""
527 Intended to be overwritten by filtered repo."""
527 return self
528 return self
528
529
529 def filtered(self, name):
530 def filtered(self, name):
530 """Return a filtered version of a repository"""
531 """Return a filtered version of a repository"""
531 # build a new class with the mixin and the current class
532 # build a new class with the mixin and the current class
532 # (possibly subclass of the repo)
533 # (possibly subclass of the repo)
533 class filteredrepo(repoview.repoview, self.unfiltered().__class__):
534 class filteredrepo(repoview.repoview, self.unfiltered().__class__):
534 pass
535 pass
535 return filteredrepo(self, name)
536 return filteredrepo(self, name)
536
537
537 @repofilecache('bookmarks', 'bookmarks.current')
538 @repofilecache('bookmarks', 'bookmarks.current')
538 def _bookmarks(self):
539 def _bookmarks(self):
539 return bookmarks.bmstore(self)
540 return bookmarks.bmstore(self)
540
541
541 @property
542 @property
542 def _activebookmark(self):
543 def _activebookmark(self):
543 return self._bookmarks.active
544 return self._bookmarks.active
544
545
545 # _phaserevs and _phasesets depend on changelog. what we need is to
546 # _phaserevs and _phasesets depend on changelog. what we need is to
546 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
547 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
547 # can't be easily expressed in filecache mechanism.
548 # can't be easily expressed in filecache mechanism.
548 @storecache('phaseroots', '00changelog.i')
549 @storecache('phaseroots', '00changelog.i')
549 def _phasecache(self):
550 def _phasecache(self):
550 return phases.phasecache(self, self._phasedefaults)
551 return phases.phasecache(self, self._phasedefaults)
551
552
552 @storecache('obsstore')
553 @storecache('obsstore')
553 def obsstore(self):
554 def obsstore(self):
554 return obsolete.makestore(self.ui, self)
555 return obsolete.makestore(self.ui, self)
555
556
556 @storecache('00changelog.i')
557 @storecache('00changelog.i')
557 def changelog(self):
558 def changelog(self):
558 return changelog.changelog(self.svfs,
559 return changelog.changelog(self.svfs,
559 trypending=txnutil.mayhavepending(self.root))
560 trypending=txnutil.mayhavepending(self.root))
560
561
561 def _constructmanifest(self):
562 def _constructmanifest(self):
562 # This is a temporary function while we migrate from manifest to
563 # This is a temporary function while we migrate from manifest to
563 # manifestlog. It allows bundlerepo and unionrepo to intercept the
564 # manifestlog. It allows bundlerepo and unionrepo to intercept the
564 # manifest creation.
565 # manifest creation.
565 return manifest.manifestrevlog(self.svfs)
566 return manifest.manifestrevlog(self.svfs)
566
567
567 @storecache('00manifest.i')
568 @storecache('00manifest.i')
568 def manifestlog(self):
569 def manifestlog(self):
569 return manifest.manifestlog(self.svfs, self)
570 return manifest.manifestlog(self.svfs, self)
570
571
571 @repofilecache('dirstate')
572 @repofilecache('dirstate')
572 def dirstate(self):
573 def dirstate(self):
574 sparsematchfn = lambda: sparse.matcher(self)
575
573 return dirstate.dirstate(self.vfs, self.ui, self.root,
576 return dirstate.dirstate(self.vfs, self.ui, self.root,
574 self._dirstatevalidate)
577 self._dirstatevalidate, sparsematchfn)
575
578
576 def _dirstatevalidate(self, node):
579 def _dirstatevalidate(self, node):
577 try:
580 try:
578 self.changelog.rev(node)
581 self.changelog.rev(node)
579 return node
582 return node
580 except error.LookupError:
583 except error.LookupError:
581 if not self._dirstatevalidatewarned:
584 if not self._dirstatevalidatewarned:
582 self._dirstatevalidatewarned = True
585 self._dirstatevalidatewarned = True
583 self.ui.warn(_("warning: ignoring unknown"
586 self.ui.warn(_("warning: ignoring unknown"
584 " working parent %s!\n") % short(node))
587 " working parent %s!\n") % short(node))
585 return nullid
588 return nullid
586
589
587 def __getitem__(self, changeid):
590 def __getitem__(self, changeid):
588 if changeid is None:
591 if changeid is None:
589 return context.workingctx(self)
592 return context.workingctx(self)
590 if isinstance(changeid, slice):
593 if isinstance(changeid, slice):
591 # wdirrev isn't contiguous so the slice shouldn't include it
594 # wdirrev isn't contiguous so the slice shouldn't include it
592 return [context.changectx(self, i)
595 return [context.changectx(self, i)
593 for i in xrange(*changeid.indices(len(self)))
596 for i in xrange(*changeid.indices(len(self)))
594 if i not in self.changelog.filteredrevs]
597 if i not in self.changelog.filteredrevs]
595 try:
598 try:
596 return context.changectx(self, changeid)
599 return context.changectx(self, changeid)
597 except error.WdirUnsupported:
600 except error.WdirUnsupported:
598 return context.workingctx(self)
601 return context.workingctx(self)
599
602
600 def __contains__(self, changeid):
603 def __contains__(self, changeid):
601 """True if the given changeid exists
604 """True if the given changeid exists
602
605
603 error.LookupError is raised if an ambiguous node specified.
606 error.LookupError is raised if an ambiguous node specified.
604 """
607 """
605 try:
608 try:
606 self[changeid]
609 self[changeid]
607 return True
610 return True
608 except error.RepoLookupError:
611 except error.RepoLookupError:
609 return False
612 return False
610
613
611 def __nonzero__(self):
614 def __nonzero__(self):
612 return True
615 return True
613
616
614 __bool__ = __nonzero__
617 __bool__ = __nonzero__
615
618
616 def __len__(self):
619 def __len__(self):
617 return len(self.changelog)
620 return len(self.changelog)
618
621
619 def __iter__(self):
622 def __iter__(self):
620 return iter(self.changelog)
623 return iter(self.changelog)
621
624
622 def revs(self, expr, *args):
625 def revs(self, expr, *args):
623 '''Find revisions matching a revset.
626 '''Find revisions matching a revset.
624
627
625 The revset is specified as a string ``expr`` that may contain
628 The revset is specified as a string ``expr`` that may contain
626 %-formatting to escape certain types. See ``revsetlang.formatspec``.
629 %-formatting to escape certain types. See ``revsetlang.formatspec``.
627
630
628 Revset aliases from the configuration are not expanded. To expand
631 Revset aliases from the configuration are not expanded. To expand
629 user aliases, consider calling ``scmutil.revrange()`` or
632 user aliases, consider calling ``scmutil.revrange()`` or
630 ``repo.anyrevs([expr], user=True)``.
633 ``repo.anyrevs([expr], user=True)``.
631
634
632 Returns a revset.abstractsmartset, which is a list-like interface
635 Returns a revset.abstractsmartset, which is a list-like interface
633 that contains integer revisions.
636 that contains integer revisions.
634 '''
637 '''
635 expr = revsetlang.formatspec(expr, *args)
638 expr = revsetlang.formatspec(expr, *args)
636 m = revset.match(None, expr)
639 m = revset.match(None, expr)
637 return m(self)
640 return m(self)
638
641
639 def set(self, expr, *args):
642 def set(self, expr, *args):
640 '''Find revisions matching a revset and emit changectx instances.
643 '''Find revisions matching a revset and emit changectx instances.
641
644
642 This is a convenience wrapper around ``revs()`` that iterates the
645 This is a convenience wrapper around ``revs()`` that iterates the
643 result and is a generator of changectx instances.
646 result and is a generator of changectx instances.
644
647
645 Revset aliases from the configuration are not expanded. To expand
648 Revset aliases from the configuration are not expanded. To expand
646 user aliases, consider calling ``scmutil.revrange()``.
649 user aliases, consider calling ``scmutil.revrange()``.
647 '''
650 '''
648 for r in self.revs(expr, *args):
651 for r in self.revs(expr, *args):
649 yield self[r]
652 yield self[r]
650
653
651 def anyrevs(self, specs, user=False, localalias=None):
654 def anyrevs(self, specs, user=False, localalias=None):
652 '''Find revisions matching one of the given revsets.
655 '''Find revisions matching one of the given revsets.
653
656
654 Revset aliases from the configuration are not expanded by default. To
657 Revset aliases from the configuration are not expanded by default. To
655 expand user aliases, specify ``user=True``. To provide some local
658 expand user aliases, specify ``user=True``. To provide some local
656 definitions overriding user aliases, set ``localalias`` to
659 definitions overriding user aliases, set ``localalias`` to
657 ``{name: definitionstring}``.
660 ``{name: definitionstring}``.
658 '''
661 '''
659 if user:
662 if user:
660 m = revset.matchany(self.ui, specs, repo=self,
663 m = revset.matchany(self.ui, specs, repo=self,
661 localalias=localalias)
664 localalias=localalias)
662 else:
665 else:
663 m = revset.matchany(None, specs, localalias=localalias)
666 m = revset.matchany(None, specs, localalias=localalias)
664 return m(self)
667 return m(self)
665
668
666 def url(self):
669 def url(self):
667 return 'file:' + self.root
670 return 'file:' + self.root
668
671
669 def hook(self, name, throw=False, **args):
672 def hook(self, name, throw=False, **args):
670 """Call a hook, passing this repo instance.
673 """Call a hook, passing this repo instance.
671
674
672 This a convenience method to aid invoking hooks. Extensions likely
675 This a convenience method to aid invoking hooks. Extensions likely
673 won't call this unless they have registered a custom hook or are
676 won't call this unless they have registered a custom hook or are
674 replacing code that is expected to call a hook.
677 replacing code that is expected to call a hook.
675 """
678 """
676 return hook.hook(self.ui, self, name, throw, **args)
679 return hook.hook(self.ui, self, name, throw, **args)
677
680
678 @filteredpropertycache
681 @filteredpropertycache
679 def _tagscache(self):
682 def _tagscache(self):
680 '''Returns a tagscache object that contains various tags related
683 '''Returns a tagscache object that contains various tags related
681 caches.'''
684 caches.'''
682
685
683 # This simplifies its cache management by having one decorated
686 # This simplifies its cache management by having one decorated
684 # function (this one) and the rest simply fetch things from it.
687 # function (this one) and the rest simply fetch things from it.
685 class tagscache(object):
688 class tagscache(object):
686 def __init__(self):
689 def __init__(self):
687 # These two define the set of tags for this repository. tags
690 # These two define the set of tags for this repository. tags
688 # maps tag name to node; tagtypes maps tag name to 'global' or
691 # maps tag name to node; tagtypes maps tag name to 'global' or
689 # 'local'. (Global tags are defined by .hgtags across all
692 # 'local'. (Global tags are defined by .hgtags across all
690 # heads, and local tags are defined in .hg/localtags.)
693 # heads, and local tags are defined in .hg/localtags.)
691 # They constitute the in-memory cache of tags.
694 # They constitute the in-memory cache of tags.
692 self.tags = self.tagtypes = None
695 self.tags = self.tagtypes = None
693
696
694 self.nodetagscache = self.tagslist = None
697 self.nodetagscache = self.tagslist = None
695
698
696 cache = tagscache()
699 cache = tagscache()
697 cache.tags, cache.tagtypes = self._findtags()
700 cache.tags, cache.tagtypes = self._findtags()
698
701
699 return cache
702 return cache
700
703
701 def tags(self):
704 def tags(self):
702 '''return a mapping of tag to node'''
705 '''return a mapping of tag to node'''
703 t = {}
706 t = {}
704 if self.changelog.filteredrevs:
707 if self.changelog.filteredrevs:
705 tags, tt = self._findtags()
708 tags, tt = self._findtags()
706 else:
709 else:
707 tags = self._tagscache.tags
710 tags = self._tagscache.tags
708 for k, v in tags.iteritems():
711 for k, v in tags.iteritems():
709 try:
712 try:
710 # ignore tags to unknown nodes
713 # ignore tags to unknown nodes
711 self.changelog.rev(v)
714 self.changelog.rev(v)
712 t[k] = v
715 t[k] = v
713 except (error.LookupError, ValueError):
716 except (error.LookupError, ValueError):
714 pass
717 pass
715 return t
718 return t
716
719
717 def _findtags(self):
720 def _findtags(self):
718 '''Do the hard work of finding tags. Return a pair of dicts
721 '''Do the hard work of finding tags. Return a pair of dicts
719 (tags, tagtypes) where tags maps tag name to node, and tagtypes
722 (tags, tagtypes) where tags maps tag name to node, and tagtypes
720 maps tag name to a string like \'global\' or \'local\'.
723 maps tag name to a string like \'global\' or \'local\'.
721 Subclasses or extensions are free to add their own tags, but
724 Subclasses or extensions are free to add their own tags, but
722 should be aware that the returned dicts will be retained for the
725 should be aware that the returned dicts will be retained for the
723 duration of the localrepo object.'''
726 duration of the localrepo object.'''
724
727
725 # XXX what tagtype should subclasses/extensions use? Currently
728 # XXX what tagtype should subclasses/extensions use? Currently
726 # mq and bookmarks add tags, but do not set the tagtype at all.
729 # mq and bookmarks add tags, but do not set the tagtype at all.
727 # Should each extension invent its own tag type? Should there
730 # Should each extension invent its own tag type? Should there
728 # be one tagtype for all such "virtual" tags? Or is the status
731 # be one tagtype for all such "virtual" tags? Or is the status
729 # quo fine?
732 # quo fine?
730
733
731
734
732 # map tag name to (node, hist)
735 # map tag name to (node, hist)
733 alltags = tagsmod.findglobaltags(self.ui, self)
736 alltags = tagsmod.findglobaltags(self.ui, self)
734 # map tag name to tag type
737 # map tag name to tag type
735 tagtypes = dict((tag, 'global') for tag in alltags)
738 tagtypes = dict((tag, 'global') for tag in alltags)
736
739
737 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
740 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
738
741
739 # Build the return dicts. Have to re-encode tag names because
742 # Build the return dicts. Have to re-encode tag names because
740 # the tags module always uses UTF-8 (in order not to lose info
743 # the tags module always uses UTF-8 (in order not to lose info
741 # writing to the cache), but the rest of Mercurial wants them in
744 # writing to the cache), but the rest of Mercurial wants them in
742 # local encoding.
745 # local encoding.
743 tags = {}
746 tags = {}
744 for (name, (node, hist)) in alltags.iteritems():
747 for (name, (node, hist)) in alltags.iteritems():
745 if node != nullid:
748 if node != nullid:
746 tags[encoding.tolocal(name)] = node
749 tags[encoding.tolocal(name)] = node
747 tags['tip'] = self.changelog.tip()
750 tags['tip'] = self.changelog.tip()
748 tagtypes = dict([(encoding.tolocal(name), value)
751 tagtypes = dict([(encoding.tolocal(name), value)
749 for (name, value) in tagtypes.iteritems()])
752 for (name, value) in tagtypes.iteritems()])
750 return (tags, tagtypes)
753 return (tags, tagtypes)
751
754
752 def tagtype(self, tagname):
755 def tagtype(self, tagname):
753 '''
756 '''
754 return the type of the given tag. result can be:
757 return the type of the given tag. result can be:
755
758
756 'local' : a local tag
759 'local' : a local tag
757 'global' : a global tag
760 'global' : a global tag
758 None : tag does not exist
761 None : tag does not exist
759 '''
762 '''
760
763
761 return self._tagscache.tagtypes.get(tagname)
764 return self._tagscache.tagtypes.get(tagname)
762
765
763 def tagslist(self):
766 def tagslist(self):
764 '''return a list of tags ordered by revision'''
767 '''return a list of tags ordered by revision'''
765 if not self._tagscache.tagslist:
768 if not self._tagscache.tagslist:
766 l = []
769 l = []
767 for t, n in self.tags().iteritems():
770 for t, n in self.tags().iteritems():
768 l.append((self.changelog.rev(n), t, n))
771 l.append((self.changelog.rev(n), t, n))
769 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
772 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
770
773
771 return self._tagscache.tagslist
774 return self._tagscache.tagslist
772
775
773 def nodetags(self, node):
776 def nodetags(self, node):
774 '''return the tags associated with a node'''
777 '''return the tags associated with a node'''
775 if not self._tagscache.nodetagscache:
778 if not self._tagscache.nodetagscache:
776 nodetagscache = {}
779 nodetagscache = {}
777 for t, n in self._tagscache.tags.iteritems():
780 for t, n in self._tagscache.tags.iteritems():
778 nodetagscache.setdefault(n, []).append(t)
781 nodetagscache.setdefault(n, []).append(t)
779 for tags in nodetagscache.itervalues():
782 for tags in nodetagscache.itervalues():
780 tags.sort()
783 tags.sort()
781 self._tagscache.nodetagscache = nodetagscache
784 self._tagscache.nodetagscache = nodetagscache
782 return self._tagscache.nodetagscache.get(node, [])
785 return self._tagscache.nodetagscache.get(node, [])
783
786
784 def nodebookmarks(self, node):
787 def nodebookmarks(self, node):
785 """return the list of bookmarks pointing to the specified node"""
788 """return the list of bookmarks pointing to the specified node"""
786 marks = []
789 marks = []
787 for bookmark, n in self._bookmarks.iteritems():
790 for bookmark, n in self._bookmarks.iteritems():
788 if n == node:
791 if n == node:
789 marks.append(bookmark)
792 marks.append(bookmark)
790 return sorted(marks)
793 return sorted(marks)
791
794
792 def branchmap(self):
795 def branchmap(self):
793 '''returns a dictionary {branch: [branchheads]} with branchheads
796 '''returns a dictionary {branch: [branchheads]} with branchheads
794 ordered by increasing revision number'''
797 ordered by increasing revision number'''
795 branchmap.updatecache(self)
798 branchmap.updatecache(self)
796 return self._branchcaches[self.filtername]
799 return self._branchcaches[self.filtername]
797
800
798 @unfilteredmethod
801 @unfilteredmethod
799 def revbranchcache(self):
802 def revbranchcache(self):
800 if not self._revbranchcache:
803 if not self._revbranchcache:
801 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
804 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
802 return self._revbranchcache
805 return self._revbranchcache
803
806
804 def branchtip(self, branch, ignoremissing=False):
807 def branchtip(self, branch, ignoremissing=False):
805 '''return the tip node for a given branch
808 '''return the tip node for a given branch
806
809
807 If ignoremissing is True, then this method will not raise an error.
810 If ignoremissing is True, then this method will not raise an error.
808 This is helpful for callers that only expect None for a missing branch
811 This is helpful for callers that only expect None for a missing branch
809 (e.g. namespace).
812 (e.g. namespace).
810
813
811 '''
814 '''
812 try:
815 try:
813 return self.branchmap().branchtip(branch)
816 return self.branchmap().branchtip(branch)
814 except KeyError:
817 except KeyError:
815 if not ignoremissing:
818 if not ignoremissing:
816 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
819 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
817 else:
820 else:
818 pass
821 pass
819
822
820 def lookup(self, key):
823 def lookup(self, key):
821 return self[key].node()
824 return self[key].node()
822
825
823 def lookupbranch(self, key, remote=None):
826 def lookupbranch(self, key, remote=None):
824 repo = remote or self
827 repo = remote or self
825 if key in repo.branchmap():
828 if key in repo.branchmap():
826 return key
829 return key
827
830
828 repo = (remote and remote.local()) and remote or self
831 repo = (remote and remote.local()) and remote or self
829 return repo[key].branch()
832 return repo[key].branch()
830
833
831 def known(self, nodes):
834 def known(self, nodes):
832 cl = self.changelog
835 cl = self.changelog
833 nm = cl.nodemap
836 nm = cl.nodemap
834 filtered = cl.filteredrevs
837 filtered = cl.filteredrevs
835 result = []
838 result = []
836 for n in nodes:
839 for n in nodes:
837 r = nm.get(n)
840 r = nm.get(n)
838 resp = not (r is None or r in filtered)
841 resp = not (r is None or r in filtered)
839 result.append(resp)
842 result.append(resp)
840 return result
843 return result
841
844
842 def local(self):
845 def local(self):
843 return self
846 return self
844
847
845 def publishing(self):
848 def publishing(self):
846 # it's safe (and desirable) to trust the publish flag unconditionally
849 # it's safe (and desirable) to trust the publish flag unconditionally
847 # so that we don't finalize changes shared between users via ssh or nfs
850 # so that we don't finalize changes shared between users via ssh or nfs
848 return self.ui.configbool('phases', 'publish', True, untrusted=True)
851 return self.ui.configbool('phases', 'publish', True, untrusted=True)
849
852
850 def cancopy(self):
853 def cancopy(self):
851 # so statichttprepo's override of local() works
854 # so statichttprepo's override of local() works
852 if not self.local():
855 if not self.local():
853 return False
856 return False
854 if not self.publishing():
857 if not self.publishing():
855 return True
858 return True
856 # if publishing we can't copy if there is filtered content
859 # if publishing we can't copy if there is filtered content
857 return not self.filtered('visible').changelog.filteredrevs
860 return not self.filtered('visible').changelog.filteredrevs
858
861
859 def shared(self):
862 def shared(self):
860 '''the type of shared repository (None if not shared)'''
863 '''the type of shared repository (None if not shared)'''
861 if self.sharedpath != self.path:
864 if self.sharedpath != self.path:
862 return 'store'
865 return 'store'
863 return None
866 return None
864
867
865 def wjoin(self, f, *insidef):
868 def wjoin(self, f, *insidef):
866 return self.vfs.reljoin(self.root, f, *insidef)
869 return self.vfs.reljoin(self.root, f, *insidef)
867
870
868 def file(self, f):
871 def file(self, f):
869 if f[0] == '/':
872 if f[0] == '/':
870 f = f[1:]
873 f = f[1:]
871 return filelog.filelog(self.svfs, f)
874 return filelog.filelog(self.svfs, f)
872
875
873 def changectx(self, changeid):
876 def changectx(self, changeid):
874 return self[changeid]
877 return self[changeid]
875
878
876 def setparents(self, p1, p2=nullid):
879 def setparents(self, p1, p2=nullid):
877 with self.dirstate.parentchange():
880 with self.dirstate.parentchange():
878 copies = self.dirstate.setparents(p1, p2)
881 copies = self.dirstate.setparents(p1, p2)
879 pctx = self[p1]
882 pctx = self[p1]
880 if copies:
883 if copies:
881 # Adjust copy records, the dirstate cannot do it, it
884 # Adjust copy records, the dirstate cannot do it, it
882 # requires access to parents manifests. Preserve them
885 # requires access to parents manifests. Preserve them
883 # only for entries added to first parent.
886 # only for entries added to first parent.
884 for f in copies:
887 for f in copies:
885 if f not in pctx and copies[f] in pctx:
888 if f not in pctx and copies[f] in pctx:
886 self.dirstate.copy(copies[f], f)
889 self.dirstate.copy(copies[f], f)
887 if p2 == nullid:
890 if p2 == nullid:
888 for f, s in sorted(self.dirstate.copies().items()):
891 for f, s in sorted(self.dirstate.copies().items()):
889 if f not in pctx and s not in pctx:
892 if f not in pctx and s not in pctx:
890 self.dirstate.copy(None, f)
893 self.dirstate.copy(None, f)
891
894
892 def filectx(self, path, changeid=None, fileid=None):
895 def filectx(self, path, changeid=None, fileid=None):
893 """changeid can be a changeset revision, node, or tag.
896 """changeid can be a changeset revision, node, or tag.
894 fileid can be a file revision or node."""
897 fileid can be a file revision or node."""
895 return context.filectx(self, path, changeid, fileid)
898 return context.filectx(self, path, changeid, fileid)
896
899
897 def getcwd(self):
900 def getcwd(self):
898 return self.dirstate.getcwd()
901 return self.dirstate.getcwd()
899
902
900 def pathto(self, f, cwd=None):
903 def pathto(self, f, cwd=None):
901 return self.dirstate.pathto(f, cwd)
904 return self.dirstate.pathto(f, cwd)
902
905
903 def _loadfilter(self, filter):
906 def _loadfilter(self, filter):
904 if filter not in self.filterpats:
907 if filter not in self.filterpats:
905 l = []
908 l = []
906 for pat, cmd in self.ui.configitems(filter):
909 for pat, cmd in self.ui.configitems(filter):
907 if cmd == '!':
910 if cmd == '!':
908 continue
911 continue
909 mf = matchmod.match(self.root, '', [pat])
912 mf = matchmod.match(self.root, '', [pat])
910 fn = None
913 fn = None
911 params = cmd
914 params = cmd
912 for name, filterfn in self._datafilters.iteritems():
915 for name, filterfn in self._datafilters.iteritems():
913 if cmd.startswith(name):
916 if cmd.startswith(name):
914 fn = filterfn
917 fn = filterfn
915 params = cmd[len(name):].lstrip()
918 params = cmd[len(name):].lstrip()
916 break
919 break
917 if not fn:
920 if not fn:
918 fn = lambda s, c, **kwargs: util.filter(s, c)
921 fn = lambda s, c, **kwargs: util.filter(s, c)
919 # Wrap old filters not supporting keyword arguments
922 # Wrap old filters not supporting keyword arguments
920 if not inspect.getargspec(fn)[2]:
923 if not inspect.getargspec(fn)[2]:
921 oldfn = fn
924 oldfn = fn
922 fn = lambda s, c, **kwargs: oldfn(s, c)
925 fn = lambda s, c, **kwargs: oldfn(s, c)
923 l.append((mf, fn, params))
926 l.append((mf, fn, params))
924 self.filterpats[filter] = l
927 self.filterpats[filter] = l
925 return self.filterpats[filter]
928 return self.filterpats[filter]
926
929
927 def _filter(self, filterpats, filename, data):
930 def _filter(self, filterpats, filename, data):
928 for mf, fn, cmd in filterpats:
931 for mf, fn, cmd in filterpats:
929 if mf(filename):
932 if mf(filename):
930 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
933 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
931 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
934 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
932 break
935 break
933
936
934 return data
937 return data
935
938
936 @unfilteredpropertycache
939 @unfilteredpropertycache
937 def _encodefilterpats(self):
940 def _encodefilterpats(self):
938 return self._loadfilter('encode')
941 return self._loadfilter('encode')
939
942
940 @unfilteredpropertycache
943 @unfilteredpropertycache
941 def _decodefilterpats(self):
944 def _decodefilterpats(self):
942 return self._loadfilter('decode')
945 return self._loadfilter('decode')
943
946
944 def adddatafilter(self, name, filter):
947 def adddatafilter(self, name, filter):
945 self._datafilters[name] = filter
948 self._datafilters[name] = filter
946
949
947 def wread(self, filename):
950 def wread(self, filename):
948 if self.wvfs.islink(filename):
951 if self.wvfs.islink(filename):
949 data = self.wvfs.readlink(filename)
952 data = self.wvfs.readlink(filename)
950 else:
953 else:
951 data = self.wvfs.read(filename)
954 data = self.wvfs.read(filename)
952 return self._filter(self._encodefilterpats, filename, data)
955 return self._filter(self._encodefilterpats, filename, data)
953
956
954 def wwrite(self, filename, data, flags, backgroundclose=False):
957 def wwrite(self, filename, data, flags, backgroundclose=False):
955 """write ``data`` into ``filename`` in the working directory
958 """write ``data`` into ``filename`` in the working directory
956
959
957 This returns length of written (maybe decoded) data.
960 This returns length of written (maybe decoded) data.
958 """
961 """
959 data = self._filter(self._decodefilterpats, filename, data)
962 data = self._filter(self._decodefilterpats, filename, data)
960 if 'l' in flags:
963 if 'l' in flags:
961 self.wvfs.symlink(data, filename)
964 self.wvfs.symlink(data, filename)
962 else:
965 else:
963 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
966 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
964 if 'x' in flags:
967 if 'x' in flags:
965 self.wvfs.setflags(filename, False, True)
968 self.wvfs.setflags(filename, False, True)
966 return len(data)
969 return len(data)
967
970
968 def wwritedata(self, filename, data):
971 def wwritedata(self, filename, data):
969 return self._filter(self._decodefilterpats, filename, data)
972 return self._filter(self._decodefilterpats, filename, data)
970
973
971 def currenttransaction(self):
974 def currenttransaction(self):
972 """return the current transaction or None if non exists"""
975 """return the current transaction or None if non exists"""
973 if self._transref:
976 if self._transref:
974 tr = self._transref()
977 tr = self._transref()
975 else:
978 else:
976 tr = None
979 tr = None
977
980
978 if tr and tr.running():
981 if tr and tr.running():
979 return tr
982 return tr
980 return None
983 return None
981
984
982 def transaction(self, desc, report=None):
985 def transaction(self, desc, report=None):
983 if (self.ui.configbool('devel', 'all-warnings')
986 if (self.ui.configbool('devel', 'all-warnings')
984 or self.ui.configbool('devel', 'check-locks')):
987 or self.ui.configbool('devel', 'check-locks')):
985 if self._currentlock(self._lockref) is None:
988 if self._currentlock(self._lockref) is None:
986 raise error.ProgrammingError('transaction requires locking')
989 raise error.ProgrammingError('transaction requires locking')
987 tr = self.currenttransaction()
990 tr = self.currenttransaction()
988 if tr is not None:
991 if tr is not None:
989 return tr.nest()
992 return tr.nest()
990
993
991 # abort here if the journal already exists
994 # abort here if the journal already exists
992 if self.svfs.exists("journal"):
995 if self.svfs.exists("journal"):
993 raise error.RepoError(
996 raise error.RepoError(
994 _("abandoned transaction found"),
997 _("abandoned transaction found"),
995 hint=_("run 'hg recover' to clean up transaction"))
998 hint=_("run 'hg recover' to clean up transaction"))
996
999
997 idbase = "%.40f#%f" % (random.random(), time.time())
1000 idbase = "%.40f#%f" % (random.random(), time.time())
998 ha = hex(hashlib.sha1(idbase).digest())
1001 ha = hex(hashlib.sha1(idbase).digest())
999 txnid = 'TXN:' + ha
1002 txnid = 'TXN:' + ha
1000 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1003 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1001
1004
1002 self._writejournal(desc)
1005 self._writejournal(desc)
1003 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1006 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1004 if report:
1007 if report:
1005 rp = report
1008 rp = report
1006 else:
1009 else:
1007 rp = self.ui.warn
1010 rp = self.ui.warn
1008 vfsmap = {'plain': self.vfs} # root of .hg/
1011 vfsmap = {'plain': self.vfs} # root of .hg/
1009 # we must avoid cyclic reference between repo and transaction.
1012 # we must avoid cyclic reference between repo and transaction.
1010 reporef = weakref.ref(self)
1013 reporef = weakref.ref(self)
1011 # Code to track tag movement
1014 # Code to track tag movement
1012 #
1015 #
1013 # Since tags are all handled as file content, it is actually quite hard
1016 # Since tags are all handled as file content, it is actually quite hard
1014 # to track these movement from a code perspective. So we fallback to a
1017 # to track these movement from a code perspective. So we fallback to a
1015 # tracking at the repository level. One could envision to track changes
1018 # tracking at the repository level. One could envision to track changes
1016 # to the '.hgtags' file through changegroup apply but that fails to
1019 # to the '.hgtags' file through changegroup apply but that fails to
1017 # cope with case where transaction expose new heads without changegroup
1020 # cope with case where transaction expose new heads without changegroup
1018 # being involved (eg: phase movement).
1021 # being involved (eg: phase movement).
1019 #
1022 #
1020 # For now, We gate the feature behind a flag since this likely comes
1023 # For now, We gate the feature behind a flag since this likely comes
1021 # with performance impacts. The current code run more often than needed
1024 # with performance impacts. The current code run more often than needed
1022 # and do not use caches as much as it could. The current focus is on
1025 # and do not use caches as much as it could. The current focus is on
1023 # the behavior of the feature so we disable it by default. The flag
1026 # the behavior of the feature so we disable it by default. The flag
1024 # will be removed when we are happy with the performance impact.
1027 # will be removed when we are happy with the performance impact.
1025 #
1028 #
1026 # Once this feature is no longer experimental move the following
1029 # Once this feature is no longer experimental move the following
1027 # documentation to the appropriate help section:
1030 # documentation to the appropriate help section:
1028 #
1031 #
1029 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1032 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1030 # tags (new or changed or deleted tags). In addition the details of
1033 # tags (new or changed or deleted tags). In addition the details of
1031 # these changes are made available in a file at:
1034 # these changes are made available in a file at:
1032 # ``REPOROOT/.hg/changes/tags.changes``.
1035 # ``REPOROOT/.hg/changes/tags.changes``.
1033 # Make sure you check for HG_TAG_MOVED before reading that file as it
1036 # Make sure you check for HG_TAG_MOVED before reading that file as it
1034 # might exist from a previous transaction even if no tag were touched
1037 # might exist from a previous transaction even if no tag were touched
1035 # in this one. Changes are recorded in a line base format::
1038 # in this one. Changes are recorded in a line base format::
1036 #
1039 #
1037 # <action> <hex-node> <tag-name>\n
1040 # <action> <hex-node> <tag-name>\n
1038 #
1041 #
1039 # Actions are defined as follow:
1042 # Actions are defined as follow:
1040 # "-R": tag is removed,
1043 # "-R": tag is removed,
1041 # "+A": tag is added,
1044 # "+A": tag is added,
1042 # "-M": tag is moved (old value),
1045 # "-M": tag is moved (old value),
1043 # "+M": tag is moved (new value),
1046 # "+M": tag is moved (new value),
1044 tracktags = lambda x: None
1047 tracktags = lambda x: None
1045 # experimental config: experimental.hook-track-tags
1048 # experimental config: experimental.hook-track-tags
1046 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags',
1049 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags',
1047 False)
1050 False)
1048 if desc != 'strip' and shouldtracktags:
1051 if desc != 'strip' and shouldtracktags:
1049 oldheads = self.changelog.headrevs()
1052 oldheads = self.changelog.headrevs()
1050 def tracktags(tr2):
1053 def tracktags(tr2):
1051 repo = reporef()
1054 repo = reporef()
1052 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1055 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1053 newheads = repo.changelog.headrevs()
1056 newheads = repo.changelog.headrevs()
1054 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1057 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1055 # notes: we compare lists here.
1058 # notes: we compare lists here.
1056 # As we do it only once buiding set would not be cheaper
1059 # As we do it only once buiding set would not be cheaper
1057 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1060 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1058 if changes:
1061 if changes:
1059 tr2.hookargs['tag_moved'] = '1'
1062 tr2.hookargs['tag_moved'] = '1'
1060 with repo.vfs('changes/tags.changes', 'w',
1063 with repo.vfs('changes/tags.changes', 'w',
1061 atomictemp=True) as changesfile:
1064 atomictemp=True) as changesfile:
1062 # note: we do not register the file to the transaction
1065 # note: we do not register the file to the transaction
1063 # because we needs it to still exist on the transaction
1066 # because we needs it to still exist on the transaction
1064 # is close (for txnclose hooks)
1067 # is close (for txnclose hooks)
1065 tagsmod.writediff(changesfile, changes)
1068 tagsmod.writediff(changesfile, changes)
1066 def validate(tr2):
1069 def validate(tr2):
1067 """will run pre-closing hooks"""
1070 """will run pre-closing hooks"""
1068 # XXX the transaction API is a bit lacking here so we take a hacky
1071 # XXX the transaction API is a bit lacking here so we take a hacky
1069 # path for now
1072 # path for now
1070 #
1073 #
1071 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1074 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1072 # dict is copied before these run. In addition we needs the data
1075 # dict is copied before these run. In addition we needs the data
1073 # available to in memory hooks too.
1076 # available to in memory hooks too.
1074 #
1077 #
1075 # Moreover, we also need to make sure this runs before txnclose
1078 # Moreover, we also need to make sure this runs before txnclose
1076 # hooks and there is no "pending" mechanism that would execute
1079 # hooks and there is no "pending" mechanism that would execute
1077 # logic only if hooks are about to run.
1080 # logic only if hooks are about to run.
1078 #
1081 #
1079 # Fixing this limitation of the transaction is also needed to track
1082 # Fixing this limitation of the transaction is also needed to track
1080 # other families of changes (bookmarks, phases, obsolescence).
1083 # other families of changes (bookmarks, phases, obsolescence).
1081 #
1084 #
1082 # This will have to be fixed before we remove the experimental
1085 # This will have to be fixed before we remove the experimental
1083 # gating.
1086 # gating.
1084 tracktags(tr2)
1087 tracktags(tr2)
1085 reporef().hook('pretxnclose', throw=True,
1088 reporef().hook('pretxnclose', throw=True,
1086 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1089 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1087 def releasefn(tr, success):
1090 def releasefn(tr, success):
1088 repo = reporef()
1091 repo = reporef()
1089 if success:
1092 if success:
1090 # this should be explicitly invoked here, because
1093 # this should be explicitly invoked here, because
1091 # in-memory changes aren't written out at closing
1094 # in-memory changes aren't written out at closing
1092 # transaction, if tr.addfilegenerator (via
1095 # transaction, if tr.addfilegenerator (via
1093 # dirstate.write or so) isn't invoked while
1096 # dirstate.write or so) isn't invoked while
1094 # transaction running
1097 # transaction running
1095 repo.dirstate.write(None)
1098 repo.dirstate.write(None)
1096 else:
1099 else:
1097 # discard all changes (including ones already written
1100 # discard all changes (including ones already written
1098 # out) in this transaction
1101 # out) in this transaction
1099 repo.dirstate.restorebackup(None, prefix='journal.')
1102 repo.dirstate.restorebackup(None, prefix='journal.')
1100
1103
1101 repo.invalidate(clearfilecache=True)
1104 repo.invalidate(clearfilecache=True)
1102
1105
1103 tr = transaction.transaction(rp, self.svfs, vfsmap,
1106 tr = transaction.transaction(rp, self.svfs, vfsmap,
1104 "journal",
1107 "journal",
1105 "undo",
1108 "undo",
1106 aftertrans(renames),
1109 aftertrans(renames),
1107 self.store.createmode,
1110 self.store.createmode,
1108 validator=validate,
1111 validator=validate,
1109 releasefn=releasefn,
1112 releasefn=releasefn,
1110 checkambigfiles=_cachedfiles)
1113 checkambigfiles=_cachedfiles)
1111 tr.changes['revs'] = set()
1114 tr.changes['revs'] = set()
1112 tr.changes['obsmarkers'] = set()
1115 tr.changes['obsmarkers'] = set()
1113
1116
1114 tr.hookargs['txnid'] = txnid
1117 tr.hookargs['txnid'] = txnid
1115 # note: writing the fncache only during finalize mean that the file is
1118 # note: writing the fncache only during finalize mean that the file is
1116 # outdated when running hooks. As fncache is used for streaming clone,
1119 # outdated when running hooks. As fncache is used for streaming clone,
1117 # this is not expected to break anything that happen during the hooks.
1120 # this is not expected to break anything that happen during the hooks.
1118 tr.addfinalize('flush-fncache', self.store.write)
1121 tr.addfinalize('flush-fncache', self.store.write)
1119 def txnclosehook(tr2):
1122 def txnclosehook(tr2):
1120 """To be run if transaction is successful, will schedule a hook run
1123 """To be run if transaction is successful, will schedule a hook run
1121 """
1124 """
1122 # Don't reference tr2 in hook() so we don't hold a reference.
1125 # Don't reference tr2 in hook() so we don't hold a reference.
1123 # This reduces memory consumption when there are multiple
1126 # This reduces memory consumption when there are multiple
1124 # transactions per lock. This can likely go away if issue5045
1127 # transactions per lock. This can likely go away if issue5045
1125 # fixes the function accumulation.
1128 # fixes the function accumulation.
1126 hookargs = tr2.hookargs
1129 hookargs = tr2.hookargs
1127
1130
1128 def hook():
1131 def hook():
1129 reporef().hook('txnclose', throw=False, txnname=desc,
1132 reporef().hook('txnclose', throw=False, txnname=desc,
1130 **pycompat.strkwargs(hookargs))
1133 **pycompat.strkwargs(hookargs))
1131 reporef()._afterlock(hook)
1134 reporef()._afterlock(hook)
1132 tr.addfinalize('txnclose-hook', txnclosehook)
1135 tr.addfinalize('txnclose-hook', txnclosehook)
1133 tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
1136 tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
1134 def txnaborthook(tr2):
1137 def txnaborthook(tr2):
1135 """To be run if transaction is aborted
1138 """To be run if transaction is aborted
1136 """
1139 """
1137 reporef().hook('txnabort', throw=False, txnname=desc,
1140 reporef().hook('txnabort', throw=False, txnname=desc,
1138 **tr2.hookargs)
1141 **tr2.hookargs)
1139 tr.addabort('txnabort-hook', txnaborthook)
1142 tr.addabort('txnabort-hook', txnaborthook)
1140 # avoid eager cache invalidation. in-memory data should be identical
1143 # avoid eager cache invalidation. in-memory data should be identical
1141 # to stored data if transaction has no error.
1144 # to stored data if transaction has no error.
1142 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1145 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1143 self._transref = weakref.ref(tr)
1146 self._transref = weakref.ref(tr)
1144 return tr
1147 return tr
1145
1148
1146 def _journalfiles(self):
1149 def _journalfiles(self):
1147 return ((self.svfs, 'journal'),
1150 return ((self.svfs, 'journal'),
1148 (self.vfs, 'journal.dirstate'),
1151 (self.vfs, 'journal.dirstate'),
1149 (self.vfs, 'journal.branch'),
1152 (self.vfs, 'journal.branch'),
1150 (self.vfs, 'journal.desc'),
1153 (self.vfs, 'journal.desc'),
1151 (self.vfs, 'journal.bookmarks'),
1154 (self.vfs, 'journal.bookmarks'),
1152 (self.svfs, 'journal.phaseroots'))
1155 (self.svfs, 'journal.phaseroots'))
1153
1156
1154 def undofiles(self):
1157 def undofiles(self):
1155 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1158 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1156
1159
1157 @unfilteredmethod
1160 @unfilteredmethod
1158 def _writejournal(self, desc):
1161 def _writejournal(self, desc):
1159 self.dirstate.savebackup(None, prefix='journal.')
1162 self.dirstate.savebackup(None, prefix='journal.')
1160 self.vfs.write("journal.branch",
1163 self.vfs.write("journal.branch",
1161 encoding.fromlocal(self.dirstate.branch()))
1164 encoding.fromlocal(self.dirstate.branch()))
1162 self.vfs.write("journal.desc",
1165 self.vfs.write("journal.desc",
1163 "%d\n%s\n" % (len(self), desc))
1166 "%d\n%s\n" % (len(self), desc))
1164 self.vfs.write("journal.bookmarks",
1167 self.vfs.write("journal.bookmarks",
1165 self.vfs.tryread("bookmarks"))
1168 self.vfs.tryread("bookmarks"))
1166 self.svfs.write("journal.phaseroots",
1169 self.svfs.write("journal.phaseroots",
1167 self.svfs.tryread("phaseroots"))
1170 self.svfs.tryread("phaseroots"))
1168
1171
1169 def recover(self):
1172 def recover(self):
1170 with self.lock():
1173 with self.lock():
1171 if self.svfs.exists("journal"):
1174 if self.svfs.exists("journal"):
1172 self.ui.status(_("rolling back interrupted transaction\n"))
1175 self.ui.status(_("rolling back interrupted transaction\n"))
1173 vfsmap = {'': self.svfs,
1176 vfsmap = {'': self.svfs,
1174 'plain': self.vfs,}
1177 'plain': self.vfs,}
1175 transaction.rollback(self.svfs, vfsmap, "journal",
1178 transaction.rollback(self.svfs, vfsmap, "journal",
1176 self.ui.warn,
1179 self.ui.warn,
1177 checkambigfiles=_cachedfiles)
1180 checkambigfiles=_cachedfiles)
1178 self.invalidate()
1181 self.invalidate()
1179 return True
1182 return True
1180 else:
1183 else:
1181 self.ui.warn(_("no interrupted transaction available\n"))
1184 self.ui.warn(_("no interrupted transaction available\n"))
1182 return False
1185 return False
1183
1186
1184 def rollback(self, dryrun=False, force=False):
1187 def rollback(self, dryrun=False, force=False):
1185 wlock = lock = dsguard = None
1188 wlock = lock = dsguard = None
1186 try:
1189 try:
1187 wlock = self.wlock()
1190 wlock = self.wlock()
1188 lock = self.lock()
1191 lock = self.lock()
1189 if self.svfs.exists("undo"):
1192 if self.svfs.exists("undo"):
1190 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1193 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1191
1194
1192 return self._rollback(dryrun, force, dsguard)
1195 return self._rollback(dryrun, force, dsguard)
1193 else:
1196 else:
1194 self.ui.warn(_("no rollback information available\n"))
1197 self.ui.warn(_("no rollback information available\n"))
1195 return 1
1198 return 1
1196 finally:
1199 finally:
1197 release(dsguard, lock, wlock)
1200 release(dsguard, lock, wlock)
1198
1201
1199 @unfilteredmethod # Until we get smarter cache management
1202 @unfilteredmethod # Until we get smarter cache management
1200 def _rollback(self, dryrun, force, dsguard):
1203 def _rollback(self, dryrun, force, dsguard):
1201 ui = self.ui
1204 ui = self.ui
1202 try:
1205 try:
1203 args = self.vfs.read('undo.desc').splitlines()
1206 args = self.vfs.read('undo.desc').splitlines()
1204 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1207 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1205 if len(args) >= 3:
1208 if len(args) >= 3:
1206 detail = args[2]
1209 detail = args[2]
1207 oldtip = oldlen - 1
1210 oldtip = oldlen - 1
1208
1211
1209 if detail and ui.verbose:
1212 if detail and ui.verbose:
1210 msg = (_('repository tip rolled back to revision %d'
1213 msg = (_('repository tip rolled back to revision %d'
1211 ' (undo %s: %s)\n')
1214 ' (undo %s: %s)\n')
1212 % (oldtip, desc, detail))
1215 % (oldtip, desc, detail))
1213 else:
1216 else:
1214 msg = (_('repository tip rolled back to revision %d'
1217 msg = (_('repository tip rolled back to revision %d'
1215 ' (undo %s)\n')
1218 ' (undo %s)\n')
1216 % (oldtip, desc))
1219 % (oldtip, desc))
1217 except IOError:
1220 except IOError:
1218 msg = _('rolling back unknown transaction\n')
1221 msg = _('rolling back unknown transaction\n')
1219 desc = None
1222 desc = None
1220
1223
1221 if not force and self['.'] != self['tip'] and desc == 'commit':
1224 if not force and self['.'] != self['tip'] and desc == 'commit':
1222 raise error.Abort(
1225 raise error.Abort(
1223 _('rollback of last commit while not checked out '
1226 _('rollback of last commit while not checked out '
1224 'may lose data'), hint=_('use -f to force'))
1227 'may lose data'), hint=_('use -f to force'))
1225
1228
1226 ui.status(msg)
1229 ui.status(msg)
1227 if dryrun:
1230 if dryrun:
1228 return 0
1231 return 0
1229
1232
1230 parents = self.dirstate.parents()
1233 parents = self.dirstate.parents()
1231 self.destroying()
1234 self.destroying()
1232 vfsmap = {'plain': self.vfs, '': self.svfs}
1235 vfsmap = {'plain': self.vfs, '': self.svfs}
1233 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1236 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1234 checkambigfiles=_cachedfiles)
1237 checkambigfiles=_cachedfiles)
1235 if self.vfs.exists('undo.bookmarks'):
1238 if self.vfs.exists('undo.bookmarks'):
1236 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1239 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1237 if self.svfs.exists('undo.phaseroots'):
1240 if self.svfs.exists('undo.phaseroots'):
1238 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1241 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1239 self.invalidate()
1242 self.invalidate()
1240
1243
1241 parentgone = (parents[0] not in self.changelog.nodemap or
1244 parentgone = (parents[0] not in self.changelog.nodemap or
1242 parents[1] not in self.changelog.nodemap)
1245 parents[1] not in self.changelog.nodemap)
1243 if parentgone:
1246 if parentgone:
1244 # prevent dirstateguard from overwriting already restored one
1247 # prevent dirstateguard from overwriting already restored one
1245 dsguard.close()
1248 dsguard.close()
1246
1249
1247 self.dirstate.restorebackup(None, prefix='undo.')
1250 self.dirstate.restorebackup(None, prefix='undo.')
1248 try:
1251 try:
1249 branch = self.vfs.read('undo.branch')
1252 branch = self.vfs.read('undo.branch')
1250 self.dirstate.setbranch(encoding.tolocal(branch))
1253 self.dirstate.setbranch(encoding.tolocal(branch))
1251 except IOError:
1254 except IOError:
1252 ui.warn(_('named branch could not be reset: '
1255 ui.warn(_('named branch could not be reset: '
1253 'current branch is still \'%s\'\n')
1256 'current branch is still \'%s\'\n')
1254 % self.dirstate.branch())
1257 % self.dirstate.branch())
1255
1258
1256 parents = tuple([p.rev() for p in self[None].parents()])
1259 parents = tuple([p.rev() for p in self[None].parents()])
1257 if len(parents) > 1:
1260 if len(parents) > 1:
1258 ui.status(_('working directory now based on '
1261 ui.status(_('working directory now based on '
1259 'revisions %d and %d\n') % parents)
1262 'revisions %d and %d\n') % parents)
1260 else:
1263 else:
1261 ui.status(_('working directory now based on '
1264 ui.status(_('working directory now based on '
1262 'revision %d\n') % parents)
1265 'revision %d\n') % parents)
1263 mergemod.mergestate.clean(self, self['.'].node())
1266 mergemod.mergestate.clean(self, self['.'].node())
1264
1267
1265 # TODO: if we know which new heads may result from this rollback, pass
1268 # TODO: if we know which new heads may result from this rollback, pass
1266 # them to destroy(), which will prevent the branchhead cache from being
1269 # them to destroy(), which will prevent the branchhead cache from being
1267 # invalidated.
1270 # invalidated.
1268 self.destroyed()
1271 self.destroyed()
1269 return 0
1272 return 0
1270
1273
1271 def _buildcacheupdater(self, newtransaction):
1274 def _buildcacheupdater(self, newtransaction):
1272 """called during transaction to build the callback updating cache
1275 """called during transaction to build the callback updating cache
1273
1276
1274 Lives on the repository to help extension who might want to augment
1277 Lives on the repository to help extension who might want to augment
1275 this logic. For this purpose, the created transaction is passed to the
1278 this logic. For this purpose, the created transaction is passed to the
1276 method.
1279 method.
1277 """
1280 """
1278 # we must avoid cyclic reference between repo and transaction.
1281 # we must avoid cyclic reference between repo and transaction.
1279 reporef = weakref.ref(self)
1282 reporef = weakref.ref(self)
1280 def updater(tr):
1283 def updater(tr):
1281 repo = reporef()
1284 repo = reporef()
1282 repo.updatecaches(tr)
1285 repo.updatecaches(tr)
1283 return updater
1286 return updater
1284
1287
1285 @unfilteredmethod
1288 @unfilteredmethod
1286 def updatecaches(self, tr=None):
1289 def updatecaches(self, tr=None):
1287 """warm appropriate caches
1290 """warm appropriate caches
1288
1291
1289 If this function is called after a transaction closed. The transaction
1292 If this function is called after a transaction closed. The transaction
1290 will be available in the 'tr' argument. This can be used to selectively
1293 will be available in the 'tr' argument. This can be used to selectively
1291 update caches relevant to the changes in that transaction.
1294 update caches relevant to the changes in that transaction.
1292 """
1295 """
1293 if tr is not None and tr.hookargs.get('source') == 'strip':
1296 if tr is not None and tr.hookargs.get('source') == 'strip':
1294 # During strip, many caches are invalid but
1297 # During strip, many caches are invalid but
1295 # later call to `destroyed` will refresh them.
1298 # later call to `destroyed` will refresh them.
1296 return
1299 return
1297
1300
1298 if tr is None or tr.changes['revs']:
1301 if tr is None or tr.changes['revs']:
1299 # updating the unfiltered branchmap should refresh all the others,
1302 # updating the unfiltered branchmap should refresh all the others,
1300 self.ui.debug('updating the branch cache\n')
1303 self.ui.debug('updating the branch cache\n')
1301 branchmap.updatecache(self.filtered('served'))
1304 branchmap.updatecache(self.filtered('served'))
1302
1305
1303 def invalidatecaches(self):
1306 def invalidatecaches(self):
1304
1307
1305 if '_tagscache' in vars(self):
1308 if '_tagscache' in vars(self):
1306 # can't use delattr on proxy
1309 # can't use delattr on proxy
1307 del self.__dict__['_tagscache']
1310 del self.__dict__['_tagscache']
1308
1311
1309 self.unfiltered()._branchcaches.clear()
1312 self.unfiltered()._branchcaches.clear()
1310 self.invalidatevolatilesets()
1313 self.invalidatevolatilesets()
1311 self._sparsesignaturecache.clear()
1314 self._sparsesignaturecache.clear()
1312
1315
1313 def invalidatevolatilesets(self):
1316 def invalidatevolatilesets(self):
1314 self.filteredrevcache.clear()
1317 self.filteredrevcache.clear()
1315 obsolete.clearobscaches(self)
1318 obsolete.clearobscaches(self)
1316
1319
1317 def invalidatedirstate(self):
1320 def invalidatedirstate(self):
1318 '''Invalidates the dirstate, causing the next call to dirstate
1321 '''Invalidates the dirstate, causing the next call to dirstate
1319 to check if it was modified since the last time it was read,
1322 to check if it was modified since the last time it was read,
1320 rereading it if it has.
1323 rereading it if it has.
1321
1324
1322 This is different to dirstate.invalidate() that it doesn't always
1325 This is different to dirstate.invalidate() that it doesn't always
1323 rereads the dirstate. Use dirstate.invalidate() if you want to
1326 rereads the dirstate. Use dirstate.invalidate() if you want to
1324 explicitly read the dirstate again (i.e. restoring it to a previous
1327 explicitly read the dirstate again (i.e. restoring it to a previous
1325 known good state).'''
1328 known good state).'''
1326 if hasunfilteredcache(self, 'dirstate'):
1329 if hasunfilteredcache(self, 'dirstate'):
1327 for k in self.dirstate._filecache:
1330 for k in self.dirstate._filecache:
1328 try:
1331 try:
1329 delattr(self.dirstate, k)
1332 delattr(self.dirstate, k)
1330 except AttributeError:
1333 except AttributeError:
1331 pass
1334 pass
1332 delattr(self.unfiltered(), 'dirstate')
1335 delattr(self.unfiltered(), 'dirstate')
1333
1336
1334 def invalidate(self, clearfilecache=False):
1337 def invalidate(self, clearfilecache=False):
1335 '''Invalidates both store and non-store parts other than dirstate
1338 '''Invalidates both store and non-store parts other than dirstate
1336
1339
1337 If a transaction is running, invalidation of store is omitted,
1340 If a transaction is running, invalidation of store is omitted,
1338 because discarding in-memory changes might cause inconsistency
1341 because discarding in-memory changes might cause inconsistency
1339 (e.g. incomplete fncache causes unintentional failure, but
1342 (e.g. incomplete fncache causes unintentional failure, but
1340 redundant one doesn't).
1343 redundant one doesn't).
1341 '''
1344 '''
1342 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1345 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1343 for k in list(self._filecache.keys()):
1346 for k in list(self._filecache.keys()):
1344 # dirstate is invalidated separately in invalidatedirstate()
1347 # dirstate is invalidated separately in invalidatedirstate()
1345 if k == 'dirstate':
1348 if k == 'dirstate':
1346 continue
1349 continue
1347
1350
1348 if clearfilecache:
1351 if clearfilecache:
1349 del self._filecache[k]
1352 del self._filecache[k]
1350 try:
1353 try:
1351 delattr(unfiltered, k)
1354 delattr(unfiltered, k)
1352 except AttributeError:
1355 except AttributeError:
1353 pass
1356 pass
1354 self.invalidatecaches()
1357 self.invalidatecaches()
1355 if not self.currenttransaction():
1358 if not self.currenttransaction():
1356 # TODO: Changing contents of store outside transaction
1359 # TODO: Changing contents of store outside transaction
1357 # causes inconsistency. We should make in-memory store
1360 # causes inconsistency. We should make in-memory store
1358 # changes detectable, and abort if changed.
1361 # changes detectable, and abort if changed.
1359 self.store.invalidatecaches()
1362 self.store.invalidatecaches()
1360
1363
1361 def invalidateall(self):
1364 def invalidateall(self):
1362 '''Fully invalidates both store and non-store parts, causing the
1365 '''Fully invalidates both store and non-store parts, causing the
1363 subsequent operation to reread any outside changes.'''
1366 subsequent operation to reread any outside changes.'''
1364 # extension should hook this to invalidate its caches
1367 # extension should hook this to invalidate its caches
1365 self.invalidate()
1368 self.invalidate()
1366 self.invalidatedirstate()
1369 self.invalidatedirstate()
1367
1370
1368 @unfilteredmethod
1371 @unfilteredmethod
1369 def _refreshfilecachestats(self, tr):
1372 def _refreshfilecachestats(self, tr):
1370 """Reload stats of cached files so that they are flagged as valid"""
1373 """Reload stats of cached files so that they are flagged as valid"""
1371 for k, ce in self._filecache.items():
1374 for k, ce in self._filecache.items():
1372 if k == 'dirstate' or k not in self.__dict__:
1375 if k == 'dirstate' or k not in self.__dict__:
1373 continue
1376 continue
1374 ce.refresh()
1377 ce.refresh()
1375
1378
1376 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1379 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1377 inheritchecker=None, parentenvvar=None):
1380 inheritchecker=None, parentenvvar=None):
1378 parentlock = None
1381 parentlock = None
1379 # the contents of parentenvvar are used by the underlying lock to
1382 # the contents of parentenvvar are used by the underlying lock to
1380 # determine whether it can be inherited
1383 # determine whether it can be inherited
1381 if parentenvvar is not None:
1384 if parentenvvar is not None:
1382 parentlock = encoding.environ.get(parentenvvar)
1385 parentlock = encoding.environ.get(parentenvvar)
1383 try:
1386 try:
1384 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1387 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1385 acquirefn=acquirefn, desc=desc,
1388 acquirefn=acquirefn, desc=desc,
1386 inheritchecker=inheritchecker,
1389 inheritchecker=inheritchecker,
1387 parentlock=parentlock)
1390 parentlock=parentlock)
1388 except error.LockHeld as inst:
1391 except error.LockHeld as inst:
1389 if not wait:
1392 if not wait:
1390 raise
1393 raise
1391 # show more details for new-style locks
1394 # show more details for new-style locks
1392 if ':' in inst.locker:
1395 if ':' in inst.locker:
1393 host, pid = inst.locker.split(":", 1)
1396 host, pid = inst.locker.split(":", 1)
1394 self.ui.warn(
1397 self.ui.warn(
1395 _("waiting for lock on %s held by process %r "
1398 _("waiting for lock on %s held by process %r "
1396 "on host %r\n") % (desc, pid, host))
1399 "on host %r\n") % (desc, pid, host))
1397 else:
1400 else:
1398 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1401 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1399 (desc, inst.locker))
1402 (desc, inst.locker))
1400 # default to 600 seconds timeout
1403 # default to 600 seconds timeout
1401 l = lockmod.lock(vfs, lockname,
1404 l = lockmod.lock(vfs, lockname,
1402 int(self.ui.config("ui", "timeout", "600")),
1405 int(self.ui.config("ui", "timeout", "600")),
1403 releasefn=releasefn, acquirefn=acquirefn,
1406 releasefn=releasefn, acquirefn=acquirefn,
1404 desc=desc)
1407 desc=desc)
1405 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1408 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1406 return l
1409 return l
1407
1410
1408 def _afterlock(self, callback):
1411 def _afterlock(self, callback):
1409 """add a callback to be run when the repository is fully unlocked
1412 """add a callback to be run when the repository is fully unlocked
1410
1413
1411 The callback will be executed when the outermost lock is released
1414 The callback will be executed when the outermost lock is released
1412 (with wlock being higher level than 'lock')."""
1415 (with wlock being higher level than 'lock')."""
1413 for ref in (self._wlockref, self._lockref):
1416 for ref in (self._wlockref, self._lockref):
1414 l = ref and ref()
1417 l = ref and ref()
1415 if l and l.held:
1418 if l and l.held:
1416 l.postrelease.append(callback)
1419 l.postrelease.append(callback)
1417 break
1420 break
1418 else: # no lock have been found.
1421 else: # no lock have been found.
1419 callback()
1422 callback()
1420
1423
1421 def lock(self, wait=True):
1424 def lock(self, wait=True):
1422 '''Lock the repository store (.hg/store) and return a weak reference
1425 '''Lock the repository store (.hg/store) and return a weak reference
1423 to the lock. Use this before modifying the store (e.g. committing or
1426 to the lock. Use this before modifying the store (e.g. committing or
1424 stripping). If you are opening a transaction, get a lock as well.)
1427 stripping). If you are opening a transaction, get a lock as well.)
1425
1428
1426 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1429 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1427 'wlock' first to avoid a dead-lock hazard.'''
1430 'wlock' first to avoid a dead-lock hazard.'''
1428 l = self._currentlock(self._lockref)
1431 l = self._currentlock(self._lockref)
1429 if l is not None:
1432 if l is not None:
1430 l.lock()
1433 l.lock()
1431 return l
1434 return l
1432
1435
1433 l = self._lock(self.svfs, "lock", wait, None,
1436 l = self._lock(self.svfs, "lock", wait, None,
1434 self.invalidate, _('repository %s') % self.origroot)
1437 self.invalidate, _('repository %s') % self.origroot)
1435 self._lockref = weakref.ref(l)
1438 self._lockref = weakref.ref(l)
1436 return l
1439 return l
1437
1440
1438 def _wlockchecktransaction(self):
1441 def _wlockchecktransaction(self):
1439 if self.currenttransaction() is not None:
1442 if self.currenttransaction() is not None:
1440 raise error.LockInheritanceContractViolation(
1443 raise error.LockInheritanceContractViolation(
1441 'wlock cannot be inherited in the middle of a transaction')
1444 'wlock cannot be inherited in the middle of a transaction')
1442
1445
1443 def wlock(self, wait=True):
1446 def wlock(self, wait=True):
1444 '''Lock the non-store parts of the repository (everything under
1447 '''Lock the non-store parts of the repository (everything under
1445 .hg except .hg/store) and return a weak reference to the lock.
1448 .hg except .hg/store) and return a weak reference to the lock.
1446
1449
1447 Use this before modifying files in .hg.
1450 Use this before modifying files in .hg.
1448
1451
1449 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1452 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1450 'wlock' first to avoid a dead-lock hazard.'''
1453 'wlock' first to avoid a dead-lock hazard.'''
1451 l = self._wlockref and self._wlockref()
1454 l = self._wlockref and self._wlockref()
1452 if l is not None and l.held:
1455 if l is not None and l.held:
1453 l.lock()
1456 l.lock()
1454 return l
1457 return l
1455
1458
1456 # We do not need to check for non-waiting lock acquisition. Such
1459 # We do not need to check for non-waiting lock acquisition. Such
1457 # acquisition would not cause dead-lock as they would just fail.
1460 # acquisition would not cause dead-lock as they would just fail.
1458 if wait and (self.ui.configbool('devel', 'all-warnings')
1461 if wait and (self.ui.configbool('devel', 'all-warnings')
1459 or self.ui.configbool('devel', 'check-locks')):
1462 or self.ui.configbool('devel', 'check-locks')):
1460 if self._currentlock(self._lockref) is not None:
1463 if self._currentlock(self._lockref) is not None:
1461 self.ui.develwarn('"wlock" acquired after "lock"')
1464 self.ui.develwarn('"wlock" acquired after "lock"')
1462
1465
1463 def unlock():
1466 def unlock():
1464 if self.dirstate.pendingparentchange():
1467 if self.dirstate.pendingparentchange():
1465 self.dirstate.invalidate()
1468 self.dirstate.invalidate()
1466 else:
1469 else:
1467 self.dirstate.write(None)
1470 self.dirstate.write(None)
1468
1471
1469 self._filecache['dirstate'].refresh()
1472 self._filecache['dirstate'].refresh()
1470
1473
1471 l = self._lock(self.vfs, "wlock", wait, unlock,
1474 l = self._lock(self.vfs, "wlock", wait, unlock,
1472 self.invalidatedirstate, _('working directory of %s') %
1475 self.invalidatedirstate, _('working directory of %s') %
1473 self.origroot,
1476 self.origroot,
1474 inheritchecker=self._wlockchecktransaction,
1477 inheritchecker=self._wlockchecktransaction,
1475 parentenvvar='HG_WLOCK_LOCKER')
1478 parentenvvar='HG_WLOCK_LOCKER')
1476 self._wlockref = weakref.ref(l)
1479 self._wlockref = weakref.ref(l)
1477 return l
1480 return l
1478
1481
1479 def _currentlock(self, lockref):
1482 def _currentlock(self, lockref):
1480 """Returns the lock if it's held, or None if it's not."""
1483 """Returns the lock if it's held, or None if it's not."""
1481 if lockref is None:
1484 if lockref is None:
1482 return None
1485 return None
1483 l = lockref()
1486 l = lockref()
1484 if l is None or not l.held:
1487 if l is None or not l.held:
1485 return None
1488 return None
1486 return l
1489 return l
1487
1490
1488 def currentwlock(self):
1491 def currentwlock(self):
1489 """Returns the wlock if it's held, or None if it's not."""
1492 """Returns the wlock if it's held, or None if it's not."""
1490 return self._currentlock(self._wlockref)
1493 return self._currentlock(self._wlockref)
1491
1494
1492 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1495 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1493 """
1496 """
1494 commit an individual file as part of a larger transaction
1497 commit an individual file as part of a larger transaction
1495 """
1498 """
1496
1499
1497 fname = fctx.path()
1500 fname = fctx.path()
1498 fparent1 = manifest1.get(fname, nullid)
1501 fparent1 = manifest1.get(fname, nullid)
1499 fparent2 = manifest2.get(fname, nullid)
1502 fparent2 = manifest2.get(fname, nullid)
1500 if isinstance(fctx, context.filectx):
1503 if isinstance(fctx, context.filectx):
1501 node = fctx.filenode()
1504 node = fctx.filenode()
1502 if node in [fparent1, fparent2]:
1505 if node in [fparent1, fparent2]:
1503 self.ui.debug('reusing %s filelog entry\n' % fname)
1506 self.ui.debug('reusing %s filelog entry\n' % fname)
1504 if manifest1.flags(fname) != fctx.flags():
1507 if manifest1.flags(fname) != fctx.flags():
1505 changelist.append(fname)
1508 changelist.append(fname)
1506 return node
1509 return node
1507
1510
1508 flog = self.file(fname)
1511 flog = self.file(fname)
1509 meta = {}
1512 meta = {}
1510 copy = fctx.renamed()
1513 copy = fctx.renamed()
1511 if copy and copy[0] != fname:
1514 if copy and copy[0] != fname:
1512 # Mark the new revision of this file as a copy of another
1515 # Mark the new revision of this file as a copy of another
1513 # file. This copy data will effectively act as a parent
1516 # file. This copy data will effectively act as a parent
1514 # of this new revision. If this is a merge, the first
1517 # of this new revision. If this is a merge, the first
1515 # parent will be the nullid (meaning "look up the copy data")
1518 # parent will be the nullid (meaning "look up the copy data")
1516 # and the second one will be the other parent. For example:
1519 # and the second one will be the other parent. For example:
1517 #
1520 #
1518 # 0 --- 1 --- 3 rev1 changes file foo
1521 # 0 --- 1 --- 3 rev1 changes file foo
1519 # \ / rev2 renames foo to bar and changes it
1522 # \ / rev2 renames foo to bar and changes it
1520 # \- 2 -/ rev3 should have bar with all changes and
1523 # \- 2 -/ rev3 should have bar with all changes and
1521 # should record that bar descends from
1524 # should record that bar descends from
1522 # bar in rev2 and foo in rev1
1525 # bar in rev2 and foo in rev1
1523 #
1526 #
1524 # this allows this merge to succeed:
1527 # this allows this merge to succeed:
1525 #
1528 #
1526 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1529 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1527 # \ / merging rev3 and rev4 should use bar@rev2
1530 # \ / merging rev3 and rev4 should use bar@rev2
1528 # \- 2 --- 4 as the merge base
1531 # \- 2 --- 4 as the merge base
1529 #
1532 #
1530
1533
1531 cfname = copy[0]
1534 cfname = copy[0]
1532 crev = manifest1.get(cfname)
1535 crev = manifest1.get(cfname)
1533 newfparent = fparent2
1536 newfparent = fparent2
1534
1537
1535 if manifest2: # branch merge
1538 if manifest2: # branch merge
1536 if fparent2 == nullid or crev is None: # copied on remote side
1539 if fparent2 == nullid or crev is None: # copied on remote side
1537 if cfname in manifest2:
1540 if cfname in manifest2:
1538 crev = manifest2[cfname]
1541 crev = manifest2[cfname]
1539 newfparent = fparent1
1542 newfparent = fparent1
1540
1543
1541 # Here, we used to search backwards through history to try to find
1544 # Here, we used to search backwards through history to try to find
1542 # where the file copy came from if the source of a copy was not in
1545 # where the file copy came from if the source of a copy was not in
1543 # the parent directory. However, this doesn't actually make sense to
1546 # the parent directory. However, this doesn't actually make sense to
1544 # do (what does a copy from something not in your working copy even
1547 # do (what does a copy from something not in your working copy even
1545 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1548 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1546 # the user that copy information was dropped, so if they didn't
1549 # the user that copy information was dropped, so if they didn't
1547 # expect this outcome it can be fixed, but this is the correct
1550 # expect this outcome it can be fixed, but this is the correct
1548 # behavior in this circumstance.
1551 # behavior in this circumstance.
1549
1552
1550 if crev:
1553 if crev:
1551 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1554 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1552 meta["copy"] = cfname
1555 meta["copy"] = cfname
1553 meta["copyrev"] = hex(crev)
1556 meta["copyrev"] = hex(crev)
1554 fparent1, fparent2 = nullid, newfparent
1557 fparent1, fparent2 = nullid, newfparent
1555 else:
1558 else:
1556 self.ui.warn(_("warning: can't find ancestor for '%s' "
1559 self.ui.warn(_("warning: can't find ancestor for '%s' "
1557 "copied from '%s'!\n") % (fname, cfname))
1560 "copied from '%s'!\n") % (fname, cfname))
1558
1561
1559 elif fparent1 == nullid:
1562 elif fparent1 == nullid:
1560 fparent1, fparent2 = fparent2, nullid
1563 fparent1, fparent2 = fparent2, nullid
1561 elif fparent2 != nullid:
1564 elif fparent2 != nullid:
1562 # is one parent an ancestor of the other?
1565 # is one parent an ancestor of the other?
1563 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1566 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1564 if fparent1 in fparentancestors:
1567 if fparent1 in fparentancestors:
1565 fparent1, fparent2 = fparent2, nullid
1568 fparent1, fparent2 = fparent2, nullid
1566 elif fparent2 in fparentancestors:
1569 elif fparent2 in fparentancestors:
1567 fparent2 = nullid
1570 fparent2 = nullid
1568
1571
1569 # is the file changed?
1572 # is the file changed?
1570 text = fctx.data()
1573 text = fctx.data()
1571 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1574 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1572 changelist.append(fname)
1575 changelist.append(fname)
1573 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1576 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1574 # are just the flags changed during merge?
1577 # are just the flags changed during merge?
1575 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1578 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1576 changelist.append(fname)
1579 changelist.append(fname)
1577
1580
1578 return fparent1
1581 return fparent1
1579
1582
1580 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1583 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1581 """check for commit arguments that aren't committable"""
1584 """check for commit arguments that aren't committable"""
1582 if match.isexact() or match.prefix():
1585 if match.isexact() or match.prefix():
1583 matched = set(status.modified + status.added + status.removed)
1586 matched = set(status.modified + status.added + status.removed)
1584
1587
1585 for f in match.files():
1588 for f in match.files():
1586 f = self.dirstate.normalize(f)
1589 f = self.dirstate.normalize(f)
1587 if f == '.' or f in matched or f in wctx.substate:
1590 if f == '.' or f in matched or f in wctx.substate:
1588 continue
1591 continue
1589 if f in status.deleted:
1592 if f in status.deleted:
1590 fail(f, _('file not found!'))
1593 fail(f, _('file not found!'))
1591 if f in vdirs: # visited directory
1594 if f in vdirs: # visited directory
1592 d = f + '/'
1595 d = f + '/'
1593 for mf in matched:
1596 for mf in matched:
1594 if mf.startswith(d):
1597 if mf.startswith(d):
1595 break
1598 break
1596 else:
1599 else:
1597 fail(f, _("no match under directory!"))
1600 fail(f, _("no match under directory!"))
1598 elif f not in self.dirstate:
1601 elif f not in self.dirstate:
1599 fail(f, _("file not tracked!"))
1602 fail(f, _("file not tracked!"))
1600
1603
1601 @unfilteredmethod
1604 @unfilteredmethod
1602 def commit(self, text="", user=None, date=None, match=None, force=False,
1605 def commit(self, text="", user=None, date=None, match=None, force=False,
1603 editor=False, extra=None):
1606 editor=False, extra=None):
1604 """Add a new revision to current repository.
1607 """Add a new revision to current repository.
1605
1608
1606 Revision information is gathered from the working directory,
1609 Revision information is gathered from the working directory,
1607 match can be used to filter the committed files. If editor is
1610 match can be used to filter the committed files. If editor is
1608 supplied, it is called to get a commit message.
1611 supplied, it is called to get a commit message.
1609 """
1612 """
1610 if extra is None:
1613 if extra is None:
1611 extra = {}
1614 extra = {}
1612
1615
1613 def fail(f, msg):
1616 def fail(f, msg):
1614 raise error.Abort('%s: %s' % (f, msg))
1617 raise error.Abort('%s: %s' % (f, msg))
1615
1618
1616 if not match:
1619 if not match:
1617 match = matchmod.always(self.root, '')
1620 match = matchmod.always(self.root, '')
1618
1621
1619 if not force:
1622 if not force:
1620 vdirs = []
1623 vdirs = []
1621 match.explicitdir = vdirs.append
1624 match.explicitdir = vdirs.append
1622 match.bad = fail
1625 match.bad = fail
1623
1626
1624 wlock = lock = tr = None
1627 wlock = lock = tr = None
1625 try:
1628 try:
1626 wlock = self.wlock()
1629 wlock = self.wlock()
1627 lock = self.lock() # for recent changelog (see issue4368)
1630 lock = self.lock() # for recent changelog (see issue4368)
1628
1631
1629 wctx = self[None]
1632 wctx = self[None]
1630 merge = len(wctx.parents()) > 1
1633 merge = len(wctx.parents()) > 1
1631
1634
1632 if not force and merge and not match.always():
1635 if not force and merge and not match.always():
1633 raise error.Abort(_('cannot partially commit a merge '
1636 raise error.Abort(_('cannot partially commit a merge '
1634 '(do not specify files or patterns)'))
1637 '(do not specify files or patterns)'))
1635
1638
1636 status = self.status(match=match, clean=force)
1639 status = self.status(match=match, clean=force)
1637 if force:
1640 if force:
1638 status.modified.extend(status.clean) # mq may commit clean files
1641 status.modified.extend(status.clean) # mq may commit clean files
1639
1642
1640 # check subrepos
1643 # check subrepos
1641 subs = []
1644 subs = []
1642 commitsubs = set()
1645 commitsubs = set()
1643 newstate = wctx.substate.copy()
1646 newstate = wctx.substate.copy()
1644 # only manage subrepos and .hgsubstate if .hgsub is present
1647 # only manage subrepos and .hgsubstate if .hgsub is present
1645 if '.hgsub' in wctx:
1648 if '.hgsub' in wctx:
1646 # we'll decide whether to track this ourselves, thanks
1649 # we'll decide whether to track this ourselves, thanks
1647 for c in status.modified, status.added, status.removed:
1650 for c in status.modified, status.added, status.removed:
1648 if '.hgsubstate' in c:
1651 if '.hgsubstate' in c:
1649 c.remove('.hgsubstate')
1652 c.remove('.hgsubstate')
1650
1653
1651 # compare current state to last committed state
1654 # compare current state to last committed state
1652 # build new substate based on last committed state
1655 # build new substate based on last committed state
1653 oldstate = wctx.p1().substate
1656 oldstate = wctx.p1().substate
1654 for s in sorted(newstate.keys()):
1657 for s in sorted(newstate.keys()):
1655 if not match(s):
1658 if not match(s):
1656 # ignore working copy, use old state if present
1659 # ignore working copy, use old state if present
1657 if s in oldstate:
1660 if s in oldstate:
1658 newstate[s] = oldstate[s]
1661 newstate[s] = oldstate[s]
1659 continue
1662 continue
1660 if not force:
1663 if not force:
1661 raise error.Abort(
1664 raise error.Abort(
1662 _("commit with new subrepo %s excluded") % s)
1665 _("commit with new subrepo %s excluded") % s)
1663 dirtyreason = wctx.sub(s).dirtyreason(True)
1666 dirtyreason = wctx.sub(s).dirtyreason(True)
1664 if dirtyreason:
1667 if dirtyreason:
1665 if not self.ui.configbool('ui', 'commitsubrepos'):
1668 if not self.ui.configbool('ui', 'commitsubrepos'):
1666 raise error.Abort(dirtyreason,
1669 raise error.Abort(dirtyreason,
1667 hint=_("use --subrepos for recursive commit"))
1670 hint=_("use --subrepos for recursive commit"))
1668 subs.append(s)
1671 subs.append(s)
1669 commitsubs.add(s)
1672 commitsubs.add(s)
1670 else:
1673 else:
1671 bs = wctx.sub(s).basestate()
1674 bs = wctx.sub(s).basestate()
1672 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1675 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1673 if oldstate.get(s, (None, None, None))[1] != bs:
1676 if oldstate.get(s, (None, None, None))[1] != bs:
1674 subs.append(s)
1677 subs.append(s)
1675
1678
1676 # check for removed subrepos
1679 # check for removed subrepos
1677 for p in wctx.parents():
1680 for p in wctx.parents():
1678 r = [s for s in p.substate if s not in newstate]
1681 r = [s for s in p.substate if s not in newstate]
1679 subs += [s for s in r if match(s)]
1682 subs += [s for s in r if match(s)]
1680 if subs:
1683 if subs:
1681 if (not match('.hgsub') and
1684 if (not match('.hgsub') and
1682 '.hgsub' in (wctx.modified() + wctx.added())):
1685 '.hgsub' in (wctx.modified() + wctx.added())):
1683 raise error.Abort(
1686 raise error.Abort(
1684 _("can't commit subrepos without .hgsub"))
1687 _("can't commit subrepos without .hgsub"))
1685 status.modified.insert(0, '.hgsubstate')
1688 status.modified.insert(0, '.hgsubstate')
1686
1689
1687 elif '.hgsub' in status.removed:
1690 elif '.hgsub' in status.removed:
1688 # clean up .hgsubstate when .hgsub is removed
1691 # clean up .hgsubstate when .hgsub is removed
1689 if ('.hgsubstate' in wctx and
1692 if ('.hgsubstate' in wctx and
1690 '.hgsubstate' not in (status.modified + status.added +
1693 '.hgsubstate' not in (status.modified + status.added +
1691 status.removed)):
1694 status.removed)):
1692 status.removed.insert(0, '.hgsubstate')
1695 status.removed.insert(0, '.hgsubstate')
1693
1696
1694 # make sure all explicit patterns are matched
1697 # make sure all explicit patterns are matched
1695 if not force:
1698 if not force:
1696 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1699 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1697
1700
1698 cctx = context.workingcommitctx(self, status,
1701 cctx = context.workingcommitctx(self, status,
1699 text, user, date, extra)
1702 text, user, date, extra)
1700
1703
1701 # internal config: ui.allowemptycommit
1704 # internal config: ui.allowemptycommit
1702 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1705 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1703 or extra.get('close') or merge or cctx.files()
1706 or extra.get('close') or merge or cctx.files()
1704 or self.ui.configbool('ui', 'allowemptycommit'))
1707 or self.ui.configbool('ui', 'allowemptycommit'))
1705 if not allowemptycommit:
1708 if not allowemptycommit:
1706 return None
1709 return None
1707
1710
1708 if merge and cctx.deleted():
1711 if merge and cctx.deleted():
1709 raise error.Abort(_("cannot commit merge with missing files"))
1712 raise error.Abort(_("cannot commit merge with missing files"))
1710
1713
1711 ms = mergemod.mergestate.read(self)
1714 ms = mergemod.mergestate.read(self)
1712 mergeutil.checkunresolved(ms)
1715 mergeutil.checkunresolved(ms)
1713
1716
1714 if editor:
1717 if editor:
1715 cctx._text = editor(self, cctx, subs)
1718 cctx._text = editor(self, cctx, subs)
1716 edited = (text != cctx._text)
1719 edited = (text != cctx._text)
1717
1720
1718 # Save commit message in case this transaction gets rolled back
1721 # Save commit message in case this transaction gets rolled back
1719 # (e.g. by a pretxncommit hook). Leave the content alone on
1722 # (e.g. by a pretxncommit hook). Leave the content alone on
1720 # the assumption that the user will use the same editor again.
1723 # the assumption that the user will use the same editor again.
1721 msgfn = self.savecommitmessage(cctx._text)
1724 msgfn = self.savecommitmessage(cctx._text)
1722
1725
1723 # commit subs and write new state
1726 # commit subs and write new state
1724 if subs:
1727 if subs:
1725 for s in sorted(commitsubs):
1728 for s in sorted(commitsubs):
1726 sub = wctx.sub(s)
1729 sub = wctx.sub(s)
1727 self.ui.status(_('committing subrepository %s\n') %
1730 self.ui.status(_('committing subrepository %s\n') %
1728 subrepo.subrelpath(sub))
1731 subrepo.subrelpath(sub))
1729 sr = sub.commit(cctx._text, user, date)
1732 sr = sub.commit(cctx._text, user, date)
1730 newstate[s] = (newstate[s][0], sr)
1733 newstate[s] = (newstate[s][0], sr)
1731 subrepo.writestate(self, newstate)
1734 subrepo.writestate(self, newstate)
1732
1735
1733 p1, p2 = self.dirstate.parents()
1736 p1, p2 = self.dirstate.parents()
1734 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1737 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1735 try:
1738 try:
1736 self.hook("precommit", throw=True, parent1=hookp1,
1739 self.hook("precommit", throw=True, parent1=hookp1,
1737 parent2=hookp2)
1740 parent2=hookp2)
1738 tr = self.transaction('commit')
1741 tr = self.transaction('commit')
1739 ret = self.commitctx(cctx, True)
1742 ret = self.commitctx(cctx, True)
1740 except: # re-raises
1743 except: # re-raises
1741 if edited:
1744 if edited:
1742 self.ui.write(
1745 self.ui.write(
1743 _('note: commit message saved in %s\n') % msgfn)
1746 _('note: commit message saved in %s\n') % msgfn)
1744 raise
1747 raise
1745 # update bookmarks, dirstate and mergestate
1748 # update bookmarks, dirstate and mergestate
1746 bookmarks.update(self, [p1, p2], ret)
1749 bookmarks.update(self, [p1, p2], ret)
1747 cctx.markcommitted(ret)
1750 cctx.markcommitted(ret)
1748 ms.reset()
1751 ms.reset()
1749 tr.close()
1752 tr.close()
1750
1753
1751 finally:
1754 finally:
1752 lockmod.release(tr, lock, wlock)
1755 lockmod.release(tr, lock, wlock)
1753
1756
1754 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1757 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1755 # hack for command that use a temporary commit (eg: histedit)
1758 # hack for command that use a temporary commit (eg: histedit)
1756 # temporary commit got stripped before hook release
1759 # temporary commit got stripped before hook release
1757 if self.changelog.hasnode(ret):
1760 if self.changelog.hasnode(ret):
1758 self.hook("commit", node=node, parent1=parent1,
1761 self.hook("commit", node=node, parent1=parent1,
1759 parent2=parent2)
1762 parent2=parent2)
1760 self._afterlock(commithook)
1763 self._afterlock(commithook)
1761 return ret
1764 return ret
1762
1765
1763 @unfilteredmethod
1766 @unfilteredmethod
1764 def commitctx(self, ctx, error=False):
1767 def commitctx(self, ctx, error=False):
1765 """Add a new revision to current repository.
1768 """Add a new revision to current repository.
1766 Revision information is passed via the context argument.
1769 Revision information is passed via the context argument.
1767 """
1770 """
1768
1771
1769 tr = None
1772 tr = None
1770 p1, p2 = ctx.p1(), ctx.p2()
1773 p1, p2 = ctx.p1(), ctx.p2()
1771 user = ctx.user()
1774 user = ctx.user()
1772
1775
1773 lock = self.lock()
1776 lock = self.lock()
1774 try:
1777 try:
1775 tr = self.transaction("commit")
1778 tr = self.transaction("commit")
1776 trp = weakref.proxy(tr)
1779 trp = weakref.proxy(tr)
1777
1780
1778 if ctx.manifestnode():
1781 if ctx.manifestnode():
1779 # reuse an existing manifest revision
1782 # reuse an existing manifest revision
1780 mn = ctx.manifestnode()
1783 mn = ctx.manifestnode()
1781 files = ctx.files()
1784 files = ctx.files()
1782 elif ctx.files():
1785 elif ctx.files():
1783 m1ctx = p1.manifestctx()
1786 m1ctx = p1.manifestctx()
1784 m2ctx = p2.manifestctx()
1787 m2ctx = p2.manifestctx()
1785 mctx = m1ctx.copy()
1788 mctx = m1ctx.copy()
1786
1789
1787 m = mctx.read()
1790 m = mctx.read()
1788 m1 = m1ctx.read()
1791 m1 = m1ctx.read()
1789 m2 = m2ctx.read()
1792 m2 = m2ctx.read()
1790
1793
1791 # check in files
1794 # check in files
1792 added = []
1795 added = []
1793 changed = []
1796 changed = []
1794 removed = list(ctx.removed())
1797 removed = list(ctx.removed())
1795 linkrev = len(self)
1798 linkrev = len(self)
1796 self.ui.note(_("committing files:\n"))
1799 self.ui.note(_("committing files:\n"))
1797 for f in sorted(ctx.modified() + ctx.added()):
1800 for f in sorted(ctx.modified() + ctx.added()):
1798 self.ui.note(f + "\n")
1801 self.ui.note(f + "\n")
1799 try:
1802 try:
1800 fctx = ctx[f]
1803 fctx = ctx[f]
1801 if fctx is None:
1804 if fctx is None:
1802 removed.append(f)
1805 removed.append(f)
1803 else:
1806 else:
1804 added.append(f)
1807 added.append(f)
1805 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1808 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1806 trp, changed)
1809 trp, changed)
1807 m.setflag(f, fctx.flags())
1810 m.setflag(f, fctx.flags())
1808 except OSError as inst:
1811 except OSError as inst:
1809 self.ui.warn(_("trouble committing %s!\n") % f)
1812 self.ui.warn(_("trouble committing %s!\n") % f)
1810 raise
1813 raise
1811 except IOError as inst:
1814 except IOError as inst:
1812 errcode = getattr(inst, 'errno', errno.ENOENT)
1815 errcode = getattr(inst, 'errno', errno.ENOENT)
1813 if error or errcode and errcode != errno.ENOENT:
1816 if error or errcode and errcode != errno.ENOENT:
1814 self.ui.warn(_("trouble committing %s!\n") % f)
1817 self.ui.warn(_("trouble committing %s!\n") % f)
1815 raise
1818 raise
1816
1819
1817 # update manifest
1820 # update manifest
1818 self.ui.note(_("committing manifest\n"))
1821 self.ui.note(_("committing manifest\n"))
1819 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1822 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1820 drop = [f for f in removed if f in m]
1823 drop = [f for f in removed if f in m]
1821 for f in drop:
1824 for f in drop:
1822 del m[f]
1825 del m[f]
1823 mn = mctx.write(trp, linkrev,
1826 mn = mctx.write(trp, linkrev,
1824 p1.manifestnode(), p2.manifestnode(),
1827 p1.manifestnode(), p2.manifestnode(),
1825 added, drop)
1828 added, drop)
1826 files = changed + removed
1829 files = changed + removed
1827 else:
1830 else:
1828 mn = p1.manifestnode()
1831 mn = p1.manifestnode()
1829 files = []
1832 files = []
1830
1833
1831 # update changelog
1834 # update changelog
1832 self.ui.note(_("committing changelog\n"))
1835 self.ui.note(_("committing changelog\n"))
1833 self.changelog.delayupdate(tr)
1836 self.changelog.delayupdate(tr)
1834 n = self.changelog.add(mn, files, ctx.description(),
1837 n = self.changelog.add(mn, files, ctx.description(),
1835 trp, p1.node(), p2.node(),
1838 trp, p1.node(), p2.node(),
1836 user, ctx.date(), ctx.extra().copy())
1839 user, ctx.date(), ctx.extra().copy())
1837 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1840 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1838 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1841 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1839 parent2=xp2)
1842 parent2=xp2)
1840 # set the new commit is proper phase
1843 # set the new commit is proper phase
1841 targetphase = subrepo.newcommitphase(self.ui, ctx)
1844 targetphase = subrepo.newcommitphase(self.ui, ctx)
1842 if targetphase:
1845 if targetphase:
1843 # retract boundary do not alter parent changeset.
1846 # retract boundary do not alter parent changeset.
1844 # if a parent have higher the resulting phase will
1847 # if a parent have higher the resulting phase will
1845 # be compliant anyway
1848 # be compliant anyway
1846 #
1849 #
1847 # if minimal phase was 0 we don't need to retract anything
1850 # if minimal phase was 0 we don't need to retract anything
1848 phases.retractboundary(self, tr, targetphase, [n])
1851 phases.retractboundary(self, tr, targetphase, [n])
1849 tr.close()
1852 tr.close()
1850 return n
1853 return n
1851 finally:
1854 finally:
1852 if tr:
1855 if tr:
1853 tr.release()
1856 tr.release()
1854 lock.release()
1857 lock.release()
1855
1858
1856 @unfilteredmethod
1859 @unfilteredmethod
1857 def destroying(self):
1860 def destroying(self):
1858 '''Inform the repository that nodes are about to be destroyed.
1861 '''Inform the repository that nodes are about to be destroyed.
1859 Intended for use by strip and rollback, so there's a common
1862 Intended for use by strip and rollback, so there's a common
1860 place for anything that has to be done before destroying history.
1863 place for anything that has to be done before destroying history.
1861
1864
1862 This is mostly useful for saving state that is in memory and waiting
1865 This is mostly useful for saving state that is in memory and waiting
1863 to be flushed when the current lock is released. Because a call to
1866 to be flushed when the current lock is released. Because a call to
1864 destroyed is imminent, the repo will be invalidated causing those
1867 destroyed is imminent, the repo will be invalidated causing those
1865 changes to stay in memory (waiting for the next unlock), or vanish
1868 changes to stay in memory (waiting for the next unlock), or vanish
1866 completely.
1869 completely.
1867 '''
1870 '''
1868 # When using the same lock to commit and strip, the phasecache is left
1871 # When using the same lock to commit and strip, the phasecache is left
1869 # dirty after committing. Then when we strip, the repo is invalidated,
1872 # dirty after committing. Then when we strip, the repo is invalidated,
1870 # causing those changes to disappear.
1873 # causing those changes to disappear.
1871 if '_phasecache' in vars(self):
1874 if '_phasecache' in vars(self):
1872 self._phasecache.write()
1875 self._phasecache.write()
1873
1876
1874 @unfilteredmethod
1877 @unfilteredmethod
1875 def destroyed(self):
1878 def destroyed(self):
1876 '''Inform the repository that nodes have been destroyed.
1879 '''Inform the repository that nodes have been destroyed.
1877 Intended for use by strip and rollback, so there's a common
1880 Intended for use by strip and rollback, so there's a common
1878 place for anything that has to be done after destroying history.
1881 place for anything that has to be done after destroying history.
1879 '''
1882 '''
1880 # When one tries to:
1883 # When one tries to:
1881 # 1) destroy nodes thus calling this method (e.g. strip)
1884 # 1) destroy nodes thus calling this method (e.g. strip)
1882 # 2) use phasecache somewhere (e.g. commit)
1885 # 2) use phasecache somewhere (e.g. commit)
1883 #
1886 #
1884 # then 2) will fail because the phasecache contains nodes that were
1887 # then 2) will fail because the phasecache contains nodes that were
1885 # removed. We can either remove phasecache from the filecache,
1888 # removed. We can either remove phasecache from the filecache,
1886 # causing it to reload next time it is accessed, or simply filter
1889 # causing it to reload next time it is accessed, or simply filter
1887 # the removed nodes now and write the updated cache.
1890 # the removed nodes now and write the updated cache.
1888 self._phasecache.filterunknown(self)
1891 self._phasecache.filterunknown(self)
1889 self._phasecache.write()
1892 self._phasecache.write()
1890
1893
1891 # refresh all repository caches
1894 # refresh all repository caches
1892 self.updatecaches()
1895 self.updatecaches()
1893
1896
1894 # Ensure the persistent tag cache is updated. Doing it now
1897 # Ensure the persistent tag cache is updated. Doing it now
1895 # means that the tag cache only has to worry about destroyed
1898 # means that the tag cache only has to worry about destroyed
1896 # heads immediately after a strip/rollback. That in turn
1899 # heads immediately after a strip/rollback. That in turn
1897 # guarantees that "cachetip == currenttip" (comparing both rev
1900 # guarantees that "cachetip == currenttip" (comparing both rev
1898 # and node) always means no nodes have been added or destroyed.
1901 # and node) always means no nodes have been added or destroyed.
1899
1902
1900 # XXX this is suboptimal when qrefresh'ing: we strip the current
1903 # XXX this is suboptimal when qrefresh'ing: we strip the current
1901 # head, refresh the tag cache, then immediately add a new head.
1904 # head, refresh the tag cache, then immediately add a new head.
1902 # But I think doing it this way is necessary for the "instant
1905 # But I think doing it this way is necessary for the "instant
1903 # tag cache retrieval" case to work.
1906 # tag cache retrieval" case to work.
1904 self.invalidate()
1907 self.invalidate()
1905
1908
1906 def walk(self, match, node=None):
1909 def walk(self, match, node=None):
1907 '''
1910 '''
1908 walk recursively through the directory tree or a given
1911 walk recursively through the directory tree or a given
1909 changeset, finding all files matched by the match
1912 changeset, finding all files matched by the match
1910 function
1913 function
1911 '''
1914 '''
1912 self.ui.deprecwarn('use repo[node].walk instead of repo.walk', '4.3')
1915 self.ui.deprecwarn('use repo[node].walk instead of repo.walk', '4.3')
1913 return self[node].walk(match)
1916 return self[node].walk(match)
1914
1917
1915 def status(self, node1='.', node2=None, match=None,
1918 def status(self, node1='.', node2=None, match=None,
1916 ignored=False, clean=False, unknown=False,
1919 ignored=False, clean=False, unknown=False,
1917 listsubrepos=False):
1920 listsubrepos=False):
1918 '''a convenience method that calls node1.status(node2)'''
1921 '''a convenience method that calls node1.status(node2)'''
1919 return self[node1].status(node2, match, ignored, clean, unknown,
1922 return self[node1].status(node2, match, ignored, clean, unknown,
1920 listsubrepos)
1923 listsubrepos)
1921
1924
1922 def addpostdsstatus(self, ps):
1925 def addpostdsstatus(self, ps):
1923 """Add a callback to run within the wlock, at the point at which status
1926 """Add a callback to run within the wlock, at the point at which status
1924 fixups happen.
1927 fixups happen.
1925
1928
1926 On status completion, callback(wctx, status) will be called with the
1929 On status completion, callback(wctx, status) will be called with the
1927 wlock held, unless the dirstate has changed from underneath or the wlock
1930 wlock held, unless the dirstate has changed from underneath or the wlock
1928 couldn't be grabbed.
1931 couldn't be grabbed.
1929
1932
1930 Callbacks should not capture and use a cached copy of the dirstate --
1933 Callbacks should not capture and use a cached copy of the dirstate --
1931 it might change in the meanwhile. Instead, they should access the
1934 it might change in the meanwhile. Instead, they should access the
1932 dirstate via wctx.repo().dirstate.
1935 dirstate via wctx.repo().dirstate.
1933
1936
1934 This list is emptied out after each status run -- extensions should
1937 This list is emptied out after each status run -- extensions should
1935 make sure it adds to this list each time dirstate.status is called.
1938 make sure it adds to this list each time dirstate.status is called.
1936 Extensions should also make sure they don't call this for statuses
1939 Extensions should also make sure they don't call this for statuses
1937 that don't involve the dirstate.
1940 that don't involve the dirstate.
1938 """
1941 """
1939
1942
1940 # The list is located here for uniqueness reasons -- it is actually
1943 # The list is located here for uniqueness reasons -- it is actually
1941 # managed by the workingctx, but that isn't unique per-repo.
1944 # managed by the workingctx, but that isn't unique per-repo.
1942 self._postdsstatus.append(ps)
1945 self._postdsstatus.append(ps)
1943
1946
1944 def postdsstatus(self):
1947 def postdsstatus(self):
1945 """Used by workingctx to get the list of post-dirstate-status hooks."""
1948 """Used by workingctx to get the list of post-dirstate-status hooks."""
1946 return self._postdsstatus
1949 return self._postdsstatus
1947
1950
1948 def clearpostdsstatus(self):
1951 def clearpostdsstatus(self):
1949 """Used by workingctx to clear post-dirstate-status hooks."""
1952 """Used by workingctx to clear post-dirstate-status hooks."""
1950 del self._postdsstatus[:]
1953 del self._postdsstatus[:]
1951
1954
1952 def heads(self, start=None):
1955 def heads(self, start=None):
1953 if start is None:
1956 if start is None:
1954 cl = self.changelog
1957 cl = self.changelog
1955 headrevs = reversed(cl.headrevs())
1958 headrevs = reversed(cl.headrevs())
1956 return [cl.node(rev) for rev in headrevs]
1959 return [cl.node(rev) for rev in headrevs]
1957
1960
1958 heads = self.changelog.heads(start)
1961 heads = self.changelog.heads(start)
1959 # sort the output in rev descending order
1962 # sort the output in rev descending order
1960 return sorted(heads, key=self.changelog.rev, reverse=True)
1963 return sorted(heads, key=self.changelog.rev, reverse=True)
1961
1964
1962 def branchheads(self, branch=None, start=None, closed=False):
1965 def branchheads(self, branch=None, start=None, closed=False):
1963 '''return a (possibly filtered) list of heads for the given branch
1966 '''return a (possibly filtered) list of heads for the given branch
1964
1967
1965 Heads are returned in topological order, from newest to oldest.
1968 Heads are returned in topological order, from newest to oldest.
1966 If branch is None, use the dirstate branch.
1969 If branch is None, use the dirstate branch.
1967 If start is not None, return only heads reachable from start.
1970 If start is not None, return only heads reachable from start.
1968 If closed is True, return heads that are marked as closed as well.
1971 If closed is True, return heads that are marked as closed as well.
1969 '''
1972 '''
1970 if branch is None:
1973 if branch is None:
1971 branch = self[None].branch()
1974 branch = self[None].branch()
1972 branches = self.branchmap()
1975 branches = self.branchmap()
1973 if branch not in branches:
1976 if branch not in branches:
1974 return []
1977 return []
1975 # the cache returns heads ordered lowest to highest
1978 # the cache returns heads ordered lowest to highest
1976 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1979 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1977 if start is not None:
1980 if start is not None:
1978 # filter out the heads that cannot be reached from startrev
1981 # filter out the heads that cannot be reached from startrev
1979 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1982 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1980 bheads = [h for h in bheads if h in fbheads]
1983 bheads = [h for h in bheads if h in fbheads]
1981 return bheads
1984 return bheads
1982
1985
1983 def branches(self, nodes):
1986 def branches(self, nodes):
1984 if not nodes:
1987 if not nodes:
1985 nodes = [self.changelog.tip()]
1988 nodes = [self.changelog.tip()]
1986 b = []
1989 b = []
1987 for n in nodes:
1990 for n in nodes:
1988 t = n
1991 t = n
1989 while True:
1992 while True:
1990 p = self.changelog.parents(n)
1993 p = self.changelog.parents(n)
1991 if p[1] != nullid or p[0] == nullid:
1994 if p[1] != nullid or p[0] == nullid:
1992 b.append((t, n, p[0], p[1]))
1995 b.append((t, n, p[0], p[1]))
1993 break
1996 break
1994 n = p[0]
1997 n = p[0]
1995 return b
1998 return b
1996
1999
1997 def between(self, pairs):
2000 def between(self, pairs):
1998 r = []
2001 r = []
1999
2002
2000 for top, bottom in pairs:
2003 for top, bottom in pairs:
2001 n, l, i = top, [], 0
2004 n, l, i = top, [], 0
2002 f = 1
2005 f = 1
2003
2006
2004 while n != bottom and n != nullid:
2007 while n != bottom and n != nullid:
2005 p = self.changelog.parents(n)[0]
2008 p = self.changelog.parents(n)[0]
2006 if i == f:
2009 if i == f:
2007 l.append(n)
2010 l.append(n)
2008 f = f * 2
2011 f = f * 2
2009 n = p
2012 n = p
2010 i += 1
2013 i += 1
2011
2014
2012 r.append(l)
2015 r.append(l)
2013
2016
2014 return r
2017 return r
2015
2018
2016 def checkpush(self, pushop):
2019 def checkpush(self, pushop):
2017 """Extensions can override this function if additional checks have
2020 """Extensions can override this function if additional checks have
2018 to be performed before pushing, or call it if they override push
2021 to be performed before pushing, or call it if they override push
2019 command.
2022 command.
2020 """
2023 """
2021 pass
2024 pass
2022
2025
2023 @unfilteredpropertycache
2026 @unfilteredpropertycache
2024 def prepushoutgoinghooks(self):
2027 def prepushoutgoinghooks(self):
2025 """Return util.hooks consists of a pushop with repo, remote, outgoing
2028 """Return util.hooks consists of a pushop with repo, remote, outgoing
2026 methods, which are called before pushing changesets.
2029 methods, which are called before pushing changesets.
2027 """
2030 """
2028 return util.hooks()
2031 return util.hooks()
2029
2032
2030 def pushkey(self, namespace, key, old, new):
2033 def pushkey(self, namespace, key, old, new):
2031 try:
2034 try:
2032 tr = self.currenttransaction()
2035 tr = self.currenttransaction()
2033 hookargs = {}
2036 hookargs = {}
2034 if tr is not None:
2037 if tr is not None:
2035 hookargs.update(tr.hookargs)
2038 hookargs.update(tr.hookargs)
2036 hookargs['namespace'] = namespace
2039 hookargs['namespace'] = namespace
2037 hookargs['key'] = key
2040 hookargs['key'] = key
2038 hookargs['old'] = old
2041 hookargs['old'] = old
2039 hookargs['new'] = new
2042 hookargs['new'] = new
2040 self.hook('prepushkey', throw=True, **hookargs)
2043 self.hook('prepushkey', throw=True, **hookargs)
2041 except error.HookAbort as exc:
2044 except error.HookAbort as exc:
2042 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2045 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2043 if exc.hint:
2046 if exc.hint:
2044 self.ui.write_err(_("(%s)\n") % exc.hint)
2047 self.ui.write_err(_("(%s)\n") % exc.hint)
2045 return False
2048 return False
2046 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2049 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2047 ret = pushkey.push(self, namespace, key, old, new)
2050 ret = pushkey.push(self, namespace, key, old, new)
2048 def runhook():
2051 def runhook():
2049 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2052 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2050 ret=ret)
2053 ret=ret)
2051 self._afterlock(runhook)
2054 self._afterlock(runhook)
2052 return ret
2055 return ret
2053
2056
2054 def listkeys(self, namespace):
2057 def listkeys(self, namespace):
2055 self.hook('prelistkeys', throw=True, namespace=namespace)
2058 self.hook('prelistkeys', throw=True, namespace=namespace)
2056 self.ui.debug('listing keys for "%s"\n' % namespace)
2059 self.ui.debug('listing keys for "%s"\n' % namespace)
2057 values = pushkey.list(self, namespace)
2060 values = pushkey.list(self, namespace)
2058 self.hook('listkeys', namespace=namespace, values=values)
2061 self.hook('listkeys', namespace=namespace, values=values)
2059 return values
2062 return values
2060
2063
2061 def debugwireargs(self, one, two, three=None, four=None, five=None):
2064 def debugwireargs(self, one, two, three=None, four=None, five=None):
2062 '''used to test argument passing over the wire'''
2065 '''used to test argument passing over the wire'''
2063 return "%s %s %s %s %s" % (one, two, three, four, five)
2066 return "%s %s %s %s %s" % (one, two, three, four, five)
2064
2067
2065 def savecommitmessage(self, text):
2068 def savecommitmessage(self, text):
2066 fp = self.vfs('last-message.txt', 'wb')
2069 fp = self.vfs('last-message.txt', 'wb')
2067 try:
2070 try:
2068 fp.write(text)
2071 fp.write(text)
2069 finally:
2072 finally:
2070 fp.close()
2073 fp.close()
2071 return self.pathto(fp.name[len(self.root) + 1:])
2074 return self.pathto(fp.name[len(self.root) + 1:])
2072
2075
2073 # used to avoid circular references so destructors work
2076 # used to avoid circular references so destructors work
2074 def aftertrans(files):
2077 def aftertrans(files):
2075 renamefiles = [tuple(t) for t in files]
2078 renamefiles = [tuple(t) for t in files]
2076 def a():
2079 def a():
2077 for vfs, src, dest in renamefiles:
2080 for vfs, src, dest in renamefiles:
2078 # if src and dest refer to a same file, vfs.rename is a no-op,
2081 # if src and dest refer to a same file, vfs.rename is a no-op,
2079 # leaving both src and dest on disk. delete dest to make sure
2082 # leaving both src and dest on disk. delete dest to make sure
2080 # the rename couldn't be such a no-op.
2083 # the rename couldn't be such a no-op.
2081 vfs.tryunlink(dest)
2084 vfs.tryunlink(dest)
2082 try:
2085 try:
2083 vfs.rename(src, dest)
2086 vfs.rename(src, dest)
2084 except OSError: # journal file does not yet exist
2087 except OSError: # journal file does not yet exist
2085 pass
2088 pass
2086 return a
2089 return a
2087
2090
2088 def undoname(fn):
2091 def undoname(fn):
2089 base, name = os.path.split(fn)
2092 base, name = os.path.split(fn)
2090 assert name.startswith('journal')
2093 assert name.startswith('journal')
2091 return os.path.join(base, name.replace('journal', 'undo', 1))
2094 return os.path.join(base, name.replace('journal', 'undo', 1))
2092
2095
2093 def instance(ui, path, create):
2096 def instance(ui, path, create):
2094 return localrepository(ui, util.urllocalpath(path), create)
2097 return localrepository(ui, util.urllocalpath(path), create)
2095
2098
2096 def islocal(path):
2099 def islocal(path):
2097 return True
2100 return True
2098
2101
2099 def newreporequirements(repo):
2102 def newreporequirements(repo):
2100 """Determine the set of requirements for a new local repository.
2103 """Determine the set of requirements for a new local repository.
2101
2104
2102 Extensions can wrap this function to specify custom requirements for
2105 Extensions can wrap this function to specify custom requirements for
2103 new repositories.
2106 new repositories.
2104 """
2107 """
2105 ui = repo.ui
2108 ui = repo.ui
2106 requirements = {'revlogv1'}
2109 requirements = {'revlogv1'}
2107 if ui.configbool('format', 'usestore'):
2110 if ui.configbool('format', 'usestore'):
2108 requirements.add('store')
2111 requirements.add('store')
2109 if ui.configbool('format', 'usefncache'):
2112 if ui.configbool('format', 'usefncache'):
2110 requirements.add('fncache')
2113 requirements.add('fncache')
2111 if ui.configbool('format', 'dotencode'):
2114 if ui.configbool('format', 'dotencode'):
2112 requirements.add('dotencode')
2115 requirements.add('dotencode')
2113
2116
2114 compengine = ui.config('experimental', 'format.compression', 'zlib')
2117 compengine = ui.config('experimental', 'format.compression', 'zlib')
2115 if compengine not in util.compengines:
2118 if compengine not in util.compengines:
2116 raise error.Abort(_('compression engine %s defined by '
2119 raise error.Abort(_('compression engine %s defined by '
2117 'experimental.format.compression not available') %
2120 'experimental.format.compression not available') %
2118 compengine,
2121 compengine,
2119 hint=_('run "hg debuginstall" to list available '
2122 hint=_('run "hg debuginstall" to list available '
2120 'compression engines'))
2123 'compression engines'))
2121
2124
2122 # zlib is the historical default and doesn't need an explicit requirement.
2125 # zlib is the historical default and doesn't need an explicit requirement.
2123 if compengine != 'zlib':
2126 if compengine != 'zlib':
2124 requirements.add('exp-compression-%s' % compengine)
2127 requirements.add('exp-compression-%s' % compengine)
2125
2128
2126 if scmutil.gdinitconfig(ui):
2129 if scmutil.gdinitconfig(ui):
2127 requirements.add('generaldelta')
2130 requirements.add('generaldelta')
2128 if ui.configbool('experimental', 'treemanifest', False):
2131 if ui.configbool('experimental', 'treemanifest', False):
2129 requirements.add('treemanifest')
2132 requirements.add('treemanifest')
2130 if ui.configbool('experimental', 'manifestv2', False):
2133 if ui.configbool('experimental', 'manifestv2', False):
2131 requirements.add('manifestv2')
2134 requirements.add('manifestv2')
2132
2135
2133 revlogv2 = ui.config('experimental', 'revlogv2')
2136 revlogv2 = ui.config('experimental', 'revlogv2')
2134 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2137 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2135 requirements.remove('revlogv1')
2138 requirements.remove('revlogv1')
2136 # generaldelta is implied by revlogv2.
2139 # generaldelta is implied by revlogv2.
2137 requirements.discard('generaldelta')
2140 requirements.discard('generaldelta')
2138 requirements.add(REVLOGV2_REQUIREMENT)
2141 requirements.add(REVLOGV2_REQUIREMENT)
2139
2142
2140 return requirements
2143 return requirements
General Comments 0
You need to be logged in to leave comments. Login now