##// END OF EJS Templates
scmutil: proxy revrange() through repo to break import cycles...
Yuya Nishihara -
r31025:6cf28575 default
parent child Browse files
Show More
@@ -1,407 +1,405 b''
1 1 # destutil.py - Mercurial utility function for command destination
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com> and other
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11 from . import (
12 12 bookmarks,
13 13 error,
14 14 obsolete,
15 scmutil,
15 16 )
16 17
17 18 def _destupdateobs(repo, clean):
18 19 """decide of an update destination from obsolescence markers"""
19 20 node = None
20 21 wc = repo[None]
21 22 p1 = wc.p1()
22 23 movemark = None
23 24
24 25 if p1.obsolete() and not p1.children():
25 26 # allow updating to successors
26 27 successors = obsolete.successorssets(repo, p1.node())
27 28
28 29 # behavior of certain cases is as follows,
29 30 #
30 31 # divergent changesets: update to highest rev, similar to what
31 32 # is currently done when there are more than one head
32 33 # (i.e. 'tip')
33 34 #
34 35 # replaced changesets: same as divergent except we know there
35 36 # is no conflict
36 37 #
37 38 # pruned changeset: no update is done; though, we could
38 39 # consider updating to the first non-obsolete parent,
39 40 # similar to what is current done for 'hg prune'
40 41
41 42 if successors:
42 43 # flatten the list here handles both divergent (len > 1)
43 44 # and the usual case (len = 1)
44 45 successors = [n for sub in successors for n in sub]
45 46
46 47 # get the max revision for the given successors set,
47 48 # i.e. the 'tip' of a set
48 49 node = repo.revs('max(%ln)', successors).first()
49 50 if bookmarks.isactivewdirparent(repo):
50 51 movemark = repo['.'].node()
51 52 return node, movemark, None
52 53
53 54 def _destupdatebook(repo, clean):
54 55 """decide on an update destination from active bookmark"""
55 56 # we also move the active bookmark, if any
56 57 activemark = None
57 58 node, movemark = bookmarks.calculateupdate(repo.ui, repo, None)
58 59 if node is not None:
59 60 activemark = node
60 61 return node, movemark, activemark
61 62
62 63 def _destupdatebranch(repo, clean):
63 64 """decide on an update destination from current branch
64 65
65 66 This ignores closed branch heads.
66 67 """
67 68 wc = repo[None]
68 69 movemark = node = None
69 70 currentbranch = wc.branch()
70 71
71 72 if clean:
72 73 currentbranch = repo['.'].branch()
73 74
74 75 if currentbranch in repo.branchmap():
75 76 heads = repo.branchheads(currentbranch)
76 77 if heads:
77 78 node = repo.revs('max(.::(%ln))', heads).first()
78 79 if bookmarks.isactivewdirparent(repo):
79 80 movemark = repo['.'].node()
80 81 elif currentbranch == 'default' and not wc.p1():
81 82 # "null" parent belongs to "default" branch, but it doesn't exist, so
82 83 # update to the tipmost non-closed branch head
83 84 node = repo.revs('max(head() and not closed())').first()
84 85 else:
85 86 node = repo['.'].node()
86 87 return node, movemark, None
87 88
88 89 def _destupdatebranchfallback(repo, clean):
89 90 """decide on an update destination from closed heads in current branch"""
90 91 wc = repo[None]
91 92 currentbranch = wc.branch()
92 93 movemark = None
93 94 if currentbranch in repo.branchmap():
94 95 # here, all descendant branch heads are closed
95 96 heads = repo.branchheads(currentbranch, closed=True)
96 97 assert heads, "any branch has at least one head"
97 98 node = repo.revs('max(.::(%ln))', heads).first()
98 99 assert node is not None, ("any revision has at least "
99 100 "one descendant branch head")
100 101 if bookmarks.isactivewdirparent(repo):
101 102 movemark = repo['.'].node()
102 103 else:
103 104 # here, no "default" branch, and all branches are closed
104 105 node = repo.lookup('tip')
105 106 assert node is not None, "'tip' exists even in empty repository"
106 107 return node, movemark, None
107 108
108 109 # order in which each step should be evaluated
109 110 # steps are run until one finds a destination
110 111 destupdatesteps = ['evolution', 'bookmark', 'branch', 'branchfallback']
111 112 # mapping to ease extension overriding steps.
112 113 destupdatestepmap = {'evolution': _destupdateobs,
113 114 'bookmark': _destupdatebook,
114 115 'branch': _destupdatebranch,
115 116 'branchfallback': _destupdatebranchfallback,
116 117 }
117 118
118 119 def destupdate(repo, clean=False):
119 120 """destination for bare update operation
120 121
121 122 return (rev, movemark, activemark)
122 123
123 124 - rev: the revision to update to,
124 125 - movemark: node to move the active bookmark from
125 126 (cf bookmark.calculate update),
126 127 - activemark: a bookmark to activate at the end of the update.
127 128 """
128 129 node = movemark = activemark = None
129 130
130 131 for step in destupdatesteps:
131 132 node, movemark, activemark = destupdatestepmap[step](repo, clean)
132 133 if node is not None:
133 134 break
134 135 rev = repo[node].rev()
135 136
136 137 return rev, movemark, activemark
137 138
138 139 msgdestmerge = {
139 140 # too many matching divergent bookmark
140 141 'toomanybookmarks':
141 142 {'merge':
142 143 (_("multiple matching bookmarks to merge -"
143 144 " please merge with an explicit rev or bookmark"),
144 145 _("run 'hg heads' to see all heads")),
145 146 'rebase':
146 147 (_("multiple matching bookmarks to rebase -"
147 148 " please rebase to an explicit rev or bookmark"),
148 149 _("run 'hg heads' to see all heads")),
149 150 },
150 151 # no other matching divergent bookmark
151 152 'nootherbookmarks':
152 153 {'merge':
153 154 (_("no matching bookmark to merge - "
154 155 "please merge with an explicit rev or bookmark"),
155 156 _("run 'hg heads' to see all heads")),
156 157 'rebase':
157 158 (_("no matching bookmark to rebase - "
158 159 "please rebase to an explicit rev or bookmark"),
159 160 _("run 'hg heads' to see all heads")),
160 161 },
161 162 # branch have too many unbookmarked heads, no obvious destination
162 163 'toomanyheads':
163 164 {'merge':
164 165 (_("branch '%s' has %d heads - please merge with an explicit rev"),
165 166 _("run 'hg heads .' to see heads")),
166 167 'rebase':
167 168 (_("branch '%s' has %d heads - please rebase to an explicit rev"),
168 169 _("run 'hg heads .' to see heads")),
169 170 },
170 171 # branch have no other unbookmarked heads
171 172 'bookmarkedheads':
172 173 {'merge':
173 174 (_("heads are bookmarked - please merge with an explicit rev"),
174 175 _("run 'hg heads' to see all heads")),
175 176 'rebase':
176 177 (_("heads are bookmarked - please rebase to an explicit rev"),
177 178 _("run 'hg heads' to see all heads")),
178 179 },
179 180 # branch have just a single heads, but there is other branches
180 181 'nootherbranchheads':
181 182 {'merge':
182 183 (_("branch '%s' has one head - please merge with an explicit rev"),
183 184 _("run 'hg heads' to see all heads")),
184 185 'rebase':
185 186 (_("branch '%s' has one head - please rebase to an explicit rev"),
186 187 _("run 'hg heads' to see all heads")),
187 188 },
188 189 # repository have a single head
189 190 'nootherheads':
190 191 {'merge':
191 192 (_('nothing to merge'),
192 193 None),
193 194 'rebase':
194 195 (_('nothing to rebase'),
195 196 None),
196 197 },
197 198 # repository have a single head and we are not on it
198 199 'nootherheadsbehind':
199 200 {'merge':
200 201 (_('nothing to merge'),
201 202 _("use 'hg update' instead")),
202 203 'rebase':
203 204 (_('nothing to rebase'),
204 205 _("use 'hg update' instead")),
205 206 },
206 207 # We are not on a head
207 208 'notatheads':
208 209 {'merge':
209 210 (_('working directory not at a head revision'),
210 211 _("use 'hg update' or merge with an explicit revision")),
211 212 'rebase':
212 213 (_('working directory not at a head revision'),
213 214 _("use 'hg update' or rebase to an explicit revision"))
214 215 },
215 216 'emptysourceset':
216 217 {'merge':
217 218 (_('source set is empty'),
218 219 None),
219 220 'rebase':
220 221 (_('source set is empty'),
221 222 None),
222 223 },
223 224 'multiplebranchessourceset':
224 225 {'merge':
225 226 (_('source set is rooted in multiple branches'),
226 227 None),
227 228 'rebase':
228 229 (_('rebaseset is rooted in multiple named branches'),
229 230 _('specify an explicit destination with --dest')),
230 231 },
231 232 }
232 233
233 234 def _destmergebook(repo, action='merge', sourceset=None, destspace=None):
234 235 """find merge destination in the active bookmark case"""
235 236 node = None
236 237 bmheads = repo.bookmarkheads(repo._activebookmark)
237 238 curhead = repo[repo._activebookmark].node()
238 239 if len(bmheads) == 2:
239 240 if curhead == bmheads[0]:
240 241 node = bmheads[1]
241 242 else:
242 243 node = bmheads[0]
243 244 elif len(bmheads) > 2:
244 245 msg, hint = msgdestmerge['toomanybookmarks'][action]
245 246 raise error.ManyMergeDestAbort(msg, hint=hint)
246 247 elif len(bmheads) <= 1:
247 248 msg, hint = msgdestmerge['nootherbookmarks'][action]
248 249 raise error.NoMergeDestAbort(msg, hint=hint)
249 250 assert node is not None
250 251 return node
251 252
252 253 def _destmergebranch(repo, action='merge', sourceset=None, onheadcheck=True,
253 254 destspace=None):
254 255 """find merge destination based on branch heads"""
255 256 node = None
256 257
257 258 if sourceset is None:
258 259 sourceset = [repo[repo.dirstate.p1()].rev()]
259 260 branch = repo.dirstate.branch()
260 261 elif not sourceset:
261 262 msg, hint = msgdestmerge['emptysourceset'][action]
262 263 raise error.NoMergeDestAbort(msg, hint=hint)
263 264 else:
264 265 branch = None
265 266 for ctx in repo.set('roots(%ld::%ld)', sourceset, sourceset):
266 267 if branch is not None and ctx.branch() != branch:
267 268 msg, hint = msgdestmerge['multiplebranchessourceset'][action]
268 269 raise error.ManyMergeDestAbort(msg, hint=hint)
269 270 branch = ctx.branch()
270 271
271 272 bheads = repo.branchheads(branch)
272 273 onhead = repo.revs('%ld and %ln', sourceset, bheads)
273 274 if onheadcheck and not onhead:
274 275 # Case A: working copy if not on a head. (merge only)
275 276 #
276 277 # This is probably a user mistake We bailout pointing at 'hg update'
277 278 if len(repo.heads()) <= 1:
278 279 msg, hint = msgdestmerge['nootherheadsbehind'][action]
279 280 else:
280 281 msg, hint = msgdestmerge['notatheads'][action]
281 282 raise error.Abort(msg, hint=hint)
282 283 # remove heads descendants of source from the set
283 284 bheads = list(repo.revs('%ln - (%ld::)', bheads, sourceset))
284 285 # filters out bookmarked heads
285 286 nbhs = list(repo.revs('%ld - bookmark()', bheads))
286 287
287 288 if destspace is not None:
288 289 # restrict search space
289 290 # used in the 'hg pull --rebase' case, see issue 5214.
290 291 nbhs = list(repo.revs('%ld and %ld', destspace, nbhs))
291 292
292 293 if len(nbhs) > 1:
293 294 # Case B: There is more than 1 other anonymous heads
294 295 #
295 296 # This means that there will be more than 1 candidate. This is
296 297 # ambiguous. We abort asking the user to pick as explicit destination
297 298 # instead.
298 299 msg, hint = msgdestmerge['toomanyheads'][action]
299 300 msg %= (branch, len(bheads) + 1)
300 301 raise error.ManyMergeDestAbort(msg, hint=hint)
301 302 elif not nbhs:
302 303 # Case B: There is no other anonymous heads
303 304 #
304 305 # This means that there is no natural candidate to merge with.
305 306 # We abort, with various messages for various cases.
306 307 if bheads:
307 308 msg, hint = msgdestmerge['bookmarkedheads'][action]
308 309 elif len(repo.heads()) > 1:
309 310 msg, hint = msgdestmerge['nootherbranchheads'][action]
310 311 msg %= branch
311 312 elif not onhead:
312 313 # if 'onheadcheck == False' (rebase case),
313 314 # this was not caught in Case A.
314 315 msg, hint = msgdestmerge['nootherheadsbehind'][action]
315 316 else:
316 317 msg, hint = msgdestmerge['nootherheads'][action]
317 318 raise error.NoMergeDestAbort(msg, hint=hint)
318 319 else:
319 320 node = nbhs[0]
320 321 assert node is not None
321 322 return node
322 323
323 324 def destmerge(repo, action='merge', sourceset=None, onheadcheck=True,
324 325 destspace=None):
325 326 """return the default destination for a merge
326 327
327 328 (or raise exception about why it can't pick one)
328 329
329 330 :action: the action being performed, controls emitted error message
330 331 """
331 332 # destspace is here to work around issues with `hg pull --rebase` see
332 333 # issue5214 for details
333 334 if repo._activebookmark:
334 335 node = _destmergebook(repo, action=action, sourceset=sourceset,
335 336 destspace=destspace)
336 337 else:
337 338 node = _destmergebranch(repo, action=action, sourceset=sourceset,
338 339 onheadcheck=onheadcheck, destspace=destspace)
339 340 return repo[node].rev()
340 341
341 342 histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())'
342 343
343 344 def desthistedit(ui, repo):
344 345 """Default base revision to edit for `hg histedit`."""
345 # Avoid cycle: scmutil -> revset -> destutil
346 from . import scmutil
347
348 346 default = ui.config('histedit', 'defaultrev', histeditdefaultrevset)
349 347 if default:
350 348 revs = scmutil.revrange(repo, [default])
351 349 if revs:
352 350 # The revset supplied by the user may not be in ascending order nor
353 351 # take the first revision. So do this manually.
354 352 revs.sort()
355 353 return revs.first()
356 354
357 355 return None
358 356
359 357 def _statusotherbook(ui, repo):
360 358 bmheads = repo.bookmarkheads(repo._activebookmark)
361 359 curhead = repo[repo._activebookmark].node()
362 360 if repo.revs('%n and parents()', curhead):
363 361 # we are on the active bookmark
364 362 bmheads = [b for b in bmheads if curhead != b]
365 363 if bmheads:
366 364 msg = _('%i other divergent bookmarks for "%s"\n')
367 365 ui.status(msg % (len(bmheads), repo._activebookmark))
368 366
369 367 def _statusotherbranchheads(ui, repo):
370 368 currentbranch = repo.dirstate.branch()
371 369 allheads = repo.branchheads(currentbranch, closed=True)
372 370 heads = repo.branchheads(currentbranch)
373 371 if repo.revs('%ln and parents()', allheads):
374 372 # we are on a head, even though it might be closed
375 373 #
376 374 # on closed otherheads
377 375 # ========= ==========
378 376 # o 0 all heads for current branch are closed
379 377 # N only descendant branch heads are closed
380 378 # x 0 there is only one non-closed branch head
381 379 # N there are some non-closed branch heads
382 380 # ========= ==========
383 381 otherheads = repo.revs('%ln - parents()', heads)
384 382 if repo['.'].closesbranch():
385 383 ui.warn(_('no open descendant heads on branch "%s", '
386 384 'updating to a closed head\n') %
387 385 (currentbranch))
388 386 if otherheads:
389 387 ui.warn(_("(committing will reopen the head, "
390 388 "use 'hg heads .' to see %i other heads)\n") %
391 389 (len(otherheads)))
392 390 else:
393 391 ui.warn(_('(committing will reopen branch "%s")\n') %
394 392 (currentbranch))
395 393 elif otherheads:
396 394 ui.status(_('%i other heads for branch "%s"\n') %
397 395 (len(otherheads), currentbranch))
398 396
399 397 def statusotherdests(ui, repo):
400 398 """Print message about other head"""
401 399 # XXX we should probably include a hint:
402 400 # - about what to do
403 401 # - how to see such heads
404 402 if repo._activebookmark:
405 403 _statusotherbook(ui, repo)
406 404 else:
407 405 _statusotherbranchheads(ui, repo)
@@ -1,2035 +1,2048 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import hashlib
12 12 import inspect
13 13 import os
14 14 import random
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 hex,
21 21 nullid,
22 22 short,
23 23 wdirrev,
24 24 )
25 25 from . import (
26 26 bookmarks,
27 27 branchmap,
28 28 bundle2,
29 29 changegroup,
30 30 changelog,
31 31 context,
32 32 dirstate,
33 33 dirstateguard,
34 34 encoding,
35 35 error,
36 36 exchange,
37 37 extensions,
38 38 filelog,
39 39 hook,
40 40 lock as lockmod,
41 41 manifest,
42 42 match as matchmod,
43 43 merge as mergemod,
44 44 mergeutil,
45 45 namespaces,
46 46 obsolete,
47 47 pathutil,
48 48 peer,
49 49 phases,
50 50 pushkey,
51 51 repoview,
52 52 revset,
53 53 revsetlang,
54 54 scmutil,
55 55 store,
56 56 subrepo,
57 57 tags as tagsmod,
58 58 transaction,
59 59 util,
60 60 )
61 61
62 62 release = lockmod.release
63 63 urlerr = util.urlerr
64 64 urlreq = util.urlreq
65 65
66 66 class repofilecache(scmutil.filecache):
67 67 """All filecache usage on repo are done for logic that should be unfiltered
68 68 """
69 69
70 70 def __get__(self, repo, type=None):
71 71 if repo is None:
72 72 return self
73 73 return super(repofilecache, self).__get__(repo.unfiltered(), type)
74 74 def __set__(self, repo, value):
75 75 return super(repofilecache, self).__set__(repo.unfiltered(), value)
76 76 def __delete__(self, repo):
77 77 return super(repofilecache, self).__delete__(repo.unfiltered())
78 78
79 79 class storecache(repofilecache):
80 80 """filecache for files in the store"""
81 81 def join(self, obj, fname):
82 82 return obj.sjoin(fname)
83 83
84 84 class unfilteredpropertycache(util.propertycache):
85 85 """propertycache that apply to unfiltered repo only"""
86 86
87 87 def __get__(self, repo, type=None):
88 88 unfi = repo.unfiltered()
89 89 if unfi is repo:
90 90 return super(unfilteredpropertycache, self).__get__(unfi)
91 91 return getattr(unfi, self.name)
92 92
93 93 class filteredpropertycache(util.propertycache):
94 94 """propertycache that must take filtering in account"""
95 95
96 96 def cachevalue(self, obj, value):
97 97 object.__setattr__(obj, self.name, value)
98 98
99 99
100 100 def hasunfilteredcache(repo, name):
101 101 """check if a repo has an unfilteredpropertycache value for <name>"""
102 102 return name in vars(repo.unfiltered())
103 103
104 104 def unfilteredmethod(orig):
105 105 """decorate method that always need to be run on unfiltered version"""
106 106 def wrapper(repo, *args, **kwargs):
107 107 return orig(repo.unfiltered(), *args, **kwargs)
108 108 return wrapper
109 109
110 110 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
111 111 'unbundle'))
112 112 legacycaps = moderncaps.union(set(['changegroupsubset']))
113 113
114 114 class localpeer(peer.peerrepository):
115 115 '''peer for a local repo; reflects only the most recent API'''
116 116
117 117 def __init__(self, repo, caps=moderncaps):
118 118 peer.peerrepository.__init__(self)
119 119 self._repo = repo.filtered('served')
120 120 self.ui = repo.ui
121 121 self._caps = repo._restrictcapabilities(caps)
122 122 self.requirements = repo.requirements
123 123 self.supportedformats = repo.supportedformats
124 124
125 125 def close(self):
126 126 self._repo.close()
127 127
128 128 def _capabilities(self):
129 129 return self._caps
130 130
131 131 def local(self):
132 132 return self._repo
133 133
134 134 def canpush(self):
135 135 return True
136 136
137 137 def url(self):
138 138 return self._repo.url()
139 139
140 140 def lookup(self, key):
141 141 return self._repo.lookup(key)
142 142
143 143 def branchmap(self):
144 144 return self._repo.branchmap()
145 145
146 146 def heads(self):
147 147 return self._repo.heads()
148 148
149 149 def known(self, nodes):
150 150 return self._repo.known(nodes)
151 151
152 152 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
153 153 **kwargs):
154 154 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
155 155 common=common, bundlecaps=bundlecaps,
156 156 **kwargs)
157 157 cb = util.chunkbuffer(chunks)
158 158
159 159 if bundlecaps is not None and 'HG20' in bundlecaps:
160 160 # When requesting a bundle2, getbundle returns a stream to make the
161 161 # wire level function happier. We need to build a proper object
162 162 # from it in local peer.
163 163 return bundle2.getunbundler(self.ui, cb)
164 164 else:
165 165 return changegroup.getunbundler('01', cb, None)
166 166
167 167 # TODO We might want to move the next two calls into legacypeer and add
168 168 # unbundle instead.
169 169
170 170 def unbundle(self, cg, heads, url):
171 171 """apply a bundle on a repo
172 172
173 173 This function handles the repo locking itself."""
174 174 try:
175 175 try:
176 176 cg = exchange.readbundle(self.ui, cg, None)
177 177 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
178 178 if util.safehasattr(ret, 'getchunks'):
179 179 # This is a bundle20 object, turn it into an unbundler.
180 180 # This little dance should be dropped eventually when the
181 181 # API is finally improved.
182 182 stream = util.chunkbuffer(ret.getchunks())
183 183 ret = bundle2.getunbundler(self.ui, stream)
184 184 return ret
185 185 except Exception as exc:
186 186 # If the exception contains output salvaged from a bundle2
187 187 # reply, we need to make sure it is printed before continuing
188 188 # to fail. So we build a bundle2 with such output and consume
189 189 # it directly.
190 190 #
191 191 # This is not very elegant but allows a "simple" solution for
192 192 # issue4594
193 193 output = getattr(exc, '_bundle2salvagedoutput', ())
194 194 if output:
195 195 bundler = bundle2.bundle20(self._repo.ui)
196 196 for out in output:
197 197 bundler.addpart(out)
198 198 stream = util.chunkbuffer(bundler.getchunks())
199 199 b = bundle2.getunbundler(self.ui, stream)
200 200 bundle2.processbundle(self._repo, b)
201 201 raise
202 202 except error.PushRaced as exc:
203 203 raise error.ResponseError(_('push failed:'), str(exc))
204 204
205 205 def lock(self):
206 206 return self._repo.lock()
207 207
208 208 def addchangegroup(self, cg, source, url):
209 209 return cg.apply(self._repo, source, url)
210 210
211 211 def pushkey(self, namespace, key, old, new):
212 212 return self._repo.pushkey(namespace, key, old, new)
213 213
214 214 def listkeys(self, namespace):
215 215 return self._repo.listkeys(namespace)
216 216
217 217 def debugwireargs(self, one, two, three=None, four=None, five=None):
218 218 '''used to test argument passing over the wire'''
219 219 return "%s %s %s %s %s" % (one, two, three, four, five)
220 220
221 221 class locallegacypeer(localpeer):
222 222 '''peer extension which implements legacy methods too; used for tests with
223 223 restricted capabilities'''
224 224
225 225 def __init__(self, repo):
226 226 localpeer.__init__(self, repo, caps=legacycaps)
227 227
228 228 def branches(self, nodes):
229 229 return self._repo.branches(nodes)
230 230
231 231 def between(self, pairs):
232 232 return self._repo.between(pairs)
233 233
234 234 def changegroup(self, basenodes, source):
235 235 return changegroup.changegroup(self._repo, basenodes, source)
236 236
237 237 def changegroupsubset(self, bases, heads, source):
238 238 return changegroup.changegroupsubset(self._repo, bases, heads, source)
239 239
240 240 class localrepository(object):
241 241
242 242 supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest',
243 243 'manifestv2'))
244 244 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
245 245 'dotencode'))
246 246 openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2'))
247 247 filtername = None
248 248
249 249 # a list of (ui, featureset) functions.
250 250 # only functions defined in module of enabled extensions are invoked
251 251 featuresetupfuncs = set()
252 252
253 253 def __init__(self, baseui, path, create=False):
254 254 self.requirements = set()
255 255 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
256 256 self.wopener = self.wvfs
257 257 self.root = self.wvfs.base
258 258 self.path = self.wvfs.join(".hg")
259 259 self.origroot = path
260 260 self.auditor = pathutil.pathauditor(self.root, self._checknested)
261 261 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
262 262 realfs=False)
263 263 self.vfs = scmutil.vfs(self.path)
264 264 self.opener = self.vfs
265 265 self.baseui = baseui
266 266 self.ui = baseui.copy()
267 267 self.ui.copy = baseui.copy # prevent copying repo configuration
268 268 # A list of callback to shape the phase if no data were found.
269 269 # Callback are in the form: func(repo, roots) --> processed root.
270 270 # This list it to be filled by extension during repo setup
271 271 self._phasedefaults = []
272 272 try:
273 273 self.ui.readconfig(self.join("hgrc"), self.root)
274 274 self._loadextensions()
275 275 except IOError:
276 276 pass
277 277
278 278 if self.featuresetupfuncs:
279 279 self.supported = set(self._basesupported) # use private copy
280 280 extmods = set(m.__name__ for n, m
281 281 in extensions.extensions(self.ui))
282 282 for setupfunc in self.featuresetupfuncs:
283 283 if setupfunc.__module__ in extmods:
284 284 setupfunc(self.ui, self.supported)
285 285 else:
286 286 self.supported = self._basesupported
287 287
288 288 # Add compression engines.
289 289 for name in util.compengines:
290 290 engine = util.compengines[name]
291 291 if engine.revlogheader():
292 292 self.supported.add('exp-compression-%s' % name)
293 293
294 294 if not self.vfs.isdir():
295 295 if create:
296 296 self.requirements = newreporequirements(self)
297 297
298 298 if not self.wvfs.exists():
299 299 self.wvfs.makedirs()
300 300 self.vfs.makedir(notindexed=True)
301 301
302 302 if 'store' in self.requirements:
303 303 self.vfs.mkdir("store")
304 304
305 305 # create an invalid changelog
306 306 self.vfs.append(
307 307 "00changelog.i",
308 308 '\0\0\0\2' # represents revlogv2
309 309 ' dummy changelog to prevent using the old repo layout'
310 310 )
311 311 else:
312 312 raise error.RepoError(_("repository %s not found") % path)
313 313 elif create:
314 314 raise error.RepoError(_("repository %s already exists") % path)
315 315 else:
316 316 try:
317 317 self.requirements = scmutil.readrequires(
318 318 self.vfs, self.supported)
319 319 except IOError as inst:
320 320 if inst.errno != errno.ENOENT:
321 321 raise
322 322
323 323 self.sharedpath = self.path
324 324 try:
325 325 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
326 326 realpath=True)
327 327 s = vfs.base
328 328 if not vfs.exists():
329 329 raise error.RepoError(
330 330 _('.hg/sharedpath points to nonexistent directory %s') % s)
331 331 self.sharedpath = s
332 332 except IOError as inst:
333 333 if inst.errno != errno.ENOENT:
334 334 raise
335 335
336 336 self.store = store.store(
337 337 self.requirements, self.sharedpath, scmutil.vfs)
338 338 self.spath = self.store.path
339 339 self.svfs = self.store.vfs
340 340 self.sjoin = self.store.join
341 341 self.vfs.createmode = self.store.createmode
342 342 self._applyopenerreqs()
343 343 if create:
344 344 self._writerequirements()
345 345
346 346 self._dirstatevalidatewarned = False
347 347
348 348 self._branchcaches = {}
349 349 self._revbranchcache = None
350 350 self.filterpats = {}
351 351 self._datafilters = {}
352 352 self._transref = self._lockref = self._wlockref = None
353 353
354 354 # A cache for various files under .hg/ that tracks file changes,
355 355 # (used by the filecache decorator)
356 356 #
357 357 # Maps a property name to its util.filecacheentry
358 358 self._filecache = {}
359 359
360 360 # hold sets of revision to be filtered
361 361 # should be cleared when something might have changed the filter value:
362 362 # - new changesets,
363 363 # - phase change,
364 364 # - new obsolescence marker,
365 365 # - working directory parent change,
366 366 # - bookmark changes
367 367 self.filteredrevcache = {}
368 368
369 369 # generic mapping between names and nodes
370 370 self.names = namespaces.namespaces()
371 371
372 372 def close(self):
373 373 self._writecaches()
374 374
375 375 def _loadextensions(self):
376 376 extensions.loadall(self.ui)
377 377
378 378 def _writecaches(self):
379 379 if self._revbranchcache:
380 380 self._revbranchcache.write()
381 381
382 382 def _restrictcapabilities(self, caps):
383 383 if self.ui.configbool('experimental', 'bundle2-advertise', True):
384 384 caps = set(caps)
385 385 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
386 386 caps.add('bundle2=' + urlreq.quote(capsblob))
387 387 return caps
388 388
389 389 def _applyopenerreqs(self):
390 390 self.svfs.options = dict((r, 1) for r in self.requirements
391 391 if r in self.openerreqs)
392 392 # experimental config: format.chunkcachesize
393 393 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
394 394 if chunkcachesize is not None:
395 395 self.svfs.options['chunkcachesize'] = chunkcachesize
396 396 # experimental config: format.maxchainlen
397 397 maxchainlen = self.ui.configint('format', 'maxchainlen')
398 398 if maxchainlen is not None:
399 399 self.svfs.options['maxchainlen'] = maxchainlen
400 400 # experimental config: format.manifestcachesize
401 401 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
402 402 if manifestcachesize is not None:
403 403 self.svfs.options['manifestcachesize'] = manifestcachesize
404 404 # experimental config: format.aggressivemergedeltas
405 405 aggressivemergedeltas = self.ui.configbool('format',
406 406 'aggressivemergedeltas', False)
407 407 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
408 408 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
409 409
410 410 for r in self.requirements:
411 411 if r.startswith('exp-compression-'):
412 412 self.svfs.options['compengine'] = r[len('exp-compression-'):]
413 413
414 414 def _writerequirements(self):
415 415 scmutil.writerequires(self.vfs, self.requirements)
416 416
417 417 def _checknested(self, path):
418 418 """Determine if path is a legal nested repository."""
419 419 if not path.startswith(self.root):
420 420 return False
421 421 subpath = path[len(self.root) + 1:]
422 422 normsubpath = util.pconvert(subpath)
423 423
424 424 # XXX: Checking against the current working copy is wrong in
425 425 # the sense that it can reject things like
426 426 #
427 427 # $ hg cat -r 10 sub/x.txt
428 428 #
429 429 # if sub/ is no longer a subrepository in the working copy
430 430 # parent revision.
431 431 #
432 432 # However, it can of course also allow things that would have
433 433 # been rejected before, such as the above cat command if sub/
434 434 # is a subrepository now, but was a normal directory before.
435 435 # The old path auditor would have rejected by mistake since it
436 436 # panics when it sees sub/.hg/.
437 437 #
438 438 # All in all, checking against the working copy seems sensible
439 439 # since we want to prevent access to nested repositories on
440 440 # the filesystem *now*.
441 441 ctx = self[None]
442 442 parts = util.splitpath(subpath)
443 443 while parts:
444 444 prefix = '/'.join(parts)
445 445 if prefix in ctx.substate:
446 446 if prefix == normsubpath:
447 447 return True
448 448 else:
449 449 sub = ctx.sub(prefix)
450 450 return sub.checknested(subpath[len(prefix) + 1:])
451 451 else:
452 452 parts.pop()
453 453 return False
454 454
455 455 def peer(self):
456 456 return localpeer(self) # not cached to avoid reference cycle
457 457
458 458 def unfiltered(self):
459 459 """Return unfiltered version of the repository
460 460
461 461 Intended to be overwritten by filtered repo."""
462 462 return self
463 463
464 464 def filtered(self, name):
465 465 """Return a filtered version of a repository"""
466 466 # build a new class with the mixin and the current class
467 467 # (possibly subclass of the repo)
468 468 class proxycls(repoview.repoview, self.unfiltered().__class__):
469 469 pass
470 470 return proxycls(self, name)
471 471
472 472 @repofilecache('bookmarks', 'bookmarks.current')
473 473 def _bookmarks(self):
474 474 return bookmarks.bmstore(self)
475 475
476 476 @property
477 477 def _activebookmark(self):
478 478 return self._bookmarks.active
479 479
480 480 def bookmarkheads(self, bookmark):
481 481 name = bookmark.split('@', 1)[0]
482 482 heads = []
483 483 for mark, n in self._bookmarks.iteritems():
484 484 if mark.split('@', 1)[0] == name:
485 485 heads.append(n)
486 486 return heads
487 487
488 488 # _phaserevs and _phasesets depend on changelog. what we need is to
489 489 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
490 490 # can't be easily expressed in filecache mechanism.
491 491 @storecache('phaseroots', '00changelog.i')
492 492 def _phasecache(self):
493 493 return phases.phasecache(self, self._phasedefaults)
494 494
495 495 @storecache('obsstore')
496 496 def obsstore(self):
497 497 # read default format for new obsstore.
498 498 # developer config: format.obsstore-version
499 499 defaultformat = self.ui.configint('format', 'obsstore-version', None)
500 500 # rely on obsstore class default when possible.
501 501 kwargs = {}
502 502 if defaultformat is not None:
503 503 kwargs['defaultformat'] = defaultformat
504 504 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
505 505 store = obsolete.obsstore(self.svfs, readonly=readonly,
506 506 **kwargs)
507 507 if store and readonly:
508 508 self.ui.warn(
509 509 _('obsolete feature not enabled but %i markers found!\n')
510 510 % len(list(store)))
511 511 return store
512 512
513 513 @storecache('00changelog.i')
514 514 def changelog(self):
515 515 c = changelog.changelog(self.svfs)
516 516 if 'HG_PENDING' in encoding.environ:
517 517 p = encoding.environ['HG_PENDING']
518 518 if p.startswith(self.root):
519 519 c.readpending('00changelog.i.a')
520 520 return c
521 521
522 522 def _constructmanifest(self):
523 523 # This is a temporary function while we migrate from manifest to
524 524 # manifestlog. It allows bundlerepo and unionrepo to intercept the
525 525 # manifest creation.
526 526 return manifest.manifestrevlog(self.svfs)
527 527
528 528 @storecache('00manifest.i')
529 529 def manifestlog(self):
530 530 return manifest.manifestlog(self.svfs, self)
531 531
532 532 @repofilecache('dirstate')
533 533 def dirstate(self):
534 534 return dirstate.dirstate(self.vfs, self.ui, self.root,
535 535 self._dirstatevalidate)
536 536
537 537 def _dirstatevalidate(self, node):
538 538 try:
539 539 self.changelog.rev(node)
540 540 return node
541 541 except error.LookupError:
542 542 if not self._dirstatevalidatewarned:
543 543 self._dirstatevalidatewarned = True
544 544 self.ui.warn(_("warning: ignoring unknown"
545 545 " working parent %s!\n") % short(node))
546 546 return nullid
547 547
548 548 def __getitem__(self, changeid):
549 549 if changeid is None or changeid == wdirrev:
550 550 return context.workingctx(self)
551 551 if isinstance(changeid, slice):
552 552 return [context.changectx(self, i)
553 553 for i in xrange(*changeid.indices(len(self)))
554 554 if i not in self.changelog.filteredrevs]
555 555 return context.changectx(self, changeid)
556 556
557 557 def __contains__(self, changeid):
558 558 try:
559 559 self[changeid]
560 560 return True
561 561 except error.RepoLookupError:
562 562 return False
563 563
564 564 def __nonzero__(self):
565 565 return True
566 566
567 567 def __len__(self):
568 568 return len(self.changelog)
569 569
570 570 def __iter__(self):
571 571 return iter(self.changelog)
572 572
573 573 def revs(self, expr, *args):
574 574 '''Find revisions matching a revset.
575 575
576 576 The revset is specified as a string ``expr`` that may contain
577 577 %-formatting to escape certain types. See ``revsetlang.formatspec``.
578 578
579 579 Revset aliases from the configuration are not expanded. To expand
580 user aliases, consider calling ``scmutil.revrange()``.
580 user aliases, consider calling ``scmutil.revrange()`` or
581 ``repo.anyrevs([expr], user=True)``.
581 582
582 583 Returns a revset.abstractsmartset, which is a list-like interface
583 584 that contains integer revisions.
584 585 '''
585 586 expr = revsetlang.formatspec(expr, *args)
586 587 m = revset.match(None, expr)
587 588 return m(self)
588 589
589 590 def set(self, expr, *args):
590 591 '''Find revisions matching a revset and emit changectx instances.
591 592
592 593 This is a convenience wrapper around ``revs()`` that iterates the
593 594 result and is a generator of changectx instances.
594 595
595 596 Revset aliases from the configuration are not expanded. To expand
596 597 user aliases, consider calling ``scmutil.revrange()``.
597 598 '''
598 599 for r in self.revs(expr, *args):
599 600 yield self[r]
600 601
602 def anyrevs(self, specs, user=False):
603 '''Find revisions matching one of the given revsets.
604
605 Revset aliases from the configuration are not expanded by default. To
606 expand user aliases, specify ``user=True``.
607 '''
608 if user:
609 m = revset.matchany(self.ui, specs, repo=self)
610 else:
611 m = revset.matchany(None, specs)
612 return m(self)
613
601 614 def url(self):
602 615 return 'file:' + self.root
603 616
604 617 def hook(self, name, throw=False, **args):
605 618 """Call a hook, passing this repo instance.
606 619
607 620 This a convenience method to aid invoking hooks. Extensions likely
608 621 won't call this unless they have registered a custom hook or are
609 622 replacing code that is expected to call a hook.
610 623 """
611 624 return hook.hook(self.ui, self, name, throw, **args)
612 625
613 626 @unfilteredmethod
614 627 def _tag(self, names, node, message, local, user, date, extra=None,
615 628 editor=False):
616 629 if isinstance(names, str):
617 630 names = (names,)
618 631
619 632 branches = self.branchmap()
620 633 for name in names:
621 634 self.hook('pretag', throw=True, node=hex(node), tag=name,
622 635 local=local)
623 636 if name in branches:
624 637 self.ui.warn(_("warning: tag %s conflicts with existing"
625 638 " branch name\n") % name)
626 639
627 640 def writetags(fp, names, munge, prevtags):
628 641 fp.seek(0, 2)
629 642 if prevtags and prevtags[-1] != '\n':
630 643 fp.write('\n')
631 644 for name in names:
632 645 if munge:
633 646 m = munge(name)
634 647 else:
635 648 m = name
636 649
637 650 if (self._tagscache.tagtypes and
638 651 name in self._tagscache.tagtypes):
639 652 old = self.tags().get(name, nullid)
640 653 fp.write('%s %s\n' % (hex(old), m))
641 654 fp.write('%s %s\n' % (hex(node), m))
642 655 fp.close()
643 656
644 657 prevtags = ''
645 658 if local:
646 659 try:
647 660 fp = self.vfs('localtags', 'r+')
648 661 except IOError:
649 662 fp = self.vfs('localtags', 'a')
650 663 else:
651 664 prevtags = fp.read()
652 665
653 666 # local tags are stored in the current charset
654 667 writetags(fp, names, None, prevtags)
655 668 for name in names:
656 669 self.hook('tag', node=hex(node), tag=name, local=local)
657 670 return
658 671
659 672 try:
660 673 fp = self.wfile('.hgtags', 'rb+')
661 674 except IOError as e:
662 675 if e.errno != errno.ENOENT:
663 676 raise
664 677 fp = self.wfile('.hgtags', 'ab')
665 678 else:
666 679 prevtags = fp.read()
667 680
668 681 # committed tags are stored in UTF-8
669 682 writetags(fp, names, encoding.fromlocal, prevtags)
670 683
671 684 fp.close()
672 685
673 686 self.invalidatecaches()
674 687
675 688 if '.hgtags' not in self.dirstate:
676 689 self[None].add(['.hgtags'])
677 690
678 691 m = matchmod.exact(self.root, '', ['.hgtags'])
679 692 tagnode = self.commit(message, user, date, extra=extra, match=m,
680 693 editor=editor)
681 694
682 695 for name in names:
683 696 self.hook('tag', node=hex(node), tag=name, local=local)
684 697
685 698 return tagnode
686 699
687 700 def tag(self, names, node, message, local, user, date, editor=False):
688 701 '''tag a revision with one or more symbolic names.
689 702
690 703 names is a list of strings or, when adding a single tag, names may be a
691 704 string.
692 705
693 706 if local is True, the tags are stored in a per-repository file.
694 707 otherwise, they are stored in the .hgtags file, and a new
695 708 changeset is committed with the change.
696 709
697 710 keyword arguments:
698 711
699 712 local: whether to store tags in non-version-controlled file
700 713 (default False)
701 714
702 715 message: commit message to use if committing
703 716
704 717 user: name of user to use if committing
705 718
706 719 date: date tuple to use if committing'''
707 720
708 721 if not local:
709 722 m = matchmod.exact(self.root, '', ['.hgtags'])
710 723 if any(self.status(match=m, unknown=True, ignored=True)):
711 724 raise error.Abort(_('working copy of .hgtags is changed'),
712 725 hint=_('please commit .hgtags manually'))
713 726
714 727 self.tags() # instantiate the cache
715 728 self._tag(names, node, message, local, user, date, editor=editor)
716 729
717 730 @filteredpropertycache
718 731 def _tagscache(self):
719 732 '''Returns a tagscache object that contains various tags related
720 733 caches.'''
721 734
722 735 # This simplifies its cache management by having one decorated
723 736 # function (this one) and the rest simply fetch things from it.
724 737 class tagscache(object):
725 738 def __init__(self):
726 739 # These two define the set of tags for this repository. tags
727 740 # maps tag name to node; tagtypes maps tag name to 'global' or
728 741 # 'local'. (Global tags are defined by .hgtags across all
729 742 # heads, and local tags are defined in .hg/localtags.)
730 743 # They constitute the in-memory cache of tags.
731 744 self.tags = self.tagtypes = None
732 745
733 746 self.nodetagscache = self.tagslist = None
734 747
735 748 cache = tagscache()
736 749 cache.tags, cache.tagtypes = self._findtags()
737 750
738 751 return cache
739 752
740 753 def tags(self):
741 754 '''return a mapping of tag to node'''
742 755 t = {}
743 756 if self.changelog.filteredrevs:
744 757 tags, tt = self._findtags()
745 758 else:
746 759 tags = self._tagscache.tags
747 760 for k, v in tags.iteritems():
748 761 try:
749 762 # ignore tags to unknown nodes
750 763 self.changelog.rev(v)
751 764 t[k] = v
752 765 except (error.LookupError, ValueError):
753 766 pass
754 767 return t
755 768
756 769 def _findtags(self):
757 770 '''Do the hard work of finding tags. Return a pair of dicts
758 771 (tags, tagtypes) where tags maps tag name to node, and tagtypes
759 772 maps tag name to a string like \'global\' or \'local\'.
760 773 Subclasses or extensions are free to add their own tags, but
761 774 should be aware that the returned dicts will be retained for the
762 775 duration of the localrepo object.'''
763 776
764 777 # XXX what tagtype should subclasses/extensions use? Currently
765 778 # mq and bookmarks add tags, but do not set the tagtype at all.
766 779 # Should each extension invent its own tag type? Should there
767 780 # be one tagtype for all such "virtual" tags? Or is the status
768 781 # quo fine?
769 782
770 783 alltags = {} # map tag name to (node, hist)
771 784 tagtypes = {}
772 785
773 786 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
774 787 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
775 788
776 789 # Build the return dicts. Have to re-encode tag names because
777 790 # the tags module always uses UTF-8 (in order not to lose info
778 791 # writing to the cache), but the rest of Mercurial wants them in
779 792 # local encoding.
780 793 tags = {}
781 794 for (name, (node, hist)) in alltags.iteritems():
782 795 if node != nullid:
783 796 tags[encoding.tolocal(name)] = node
784 797 tags['tip'] = self.changelog.tip()
785 798 tagtypes = dict([(encoding.tolocal(name), value)
786 799 for (name, value) in tagtypes.iteritems()])
787 800 return (tags, tagtypes)
788 801
789 802 def tagtype(self, tagname):
790 803 '''
791 804 return the type of the given tag. result can be:
792 805
793 806 'local' : a local tag
794 807 'global' : a global tag
795 808 None : tag does not exist
796 809 '''
797 810
798 811 return self._tagscache.tagtypes.get(tagname)
799 812
800 813 def tagslist(self):
801 814 '''return a list of tags ordered by revision'''
802 815 if not self._tagscache.tagslist:
803 816 l = []
804 817 for t, n in self.tags().iteritems():
805 818 l.append((self.changelog.rev(n), t, n))
806 819 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
807 820
808 821 return self._tagscache.tagslist
809 822
810 823 def nodetags(self, node):
811 824 '''return the tags associated with a node'''
812 825 if not self._tagscache.nodetagscache:
813 826 nodetagscache = {}
814 827 for t, n in self._tagscache.tags.iteritems():
815 828 nodetagscache.setdefault(n, []).append(t)
816 829 for tags in nodetagscache.itervalues():
817 830 tags.sort()
818 831 self._tagscache.nodetagscache = nodetagscache
819 832 return self._tagscache.nodetagscache.get(node, [])
820 833
821 834 def nodebookmarks(self, node):
822 835 """return the list of bookmarks pointing to the specified node"""
823 836 marks = []
824 837 for bookmark, n in self._bookmarks.iteritems():
825 838 if n == node:
826 839 marks.append(bookmark)
827 840 return sorted(marks)
828 841
829 842 def branchmap(self):
830 843 '''returns a dictionary {branch: [branchheads]} with branchheads
831 844 ordered by increasing revision number'''
832 845 branchmap.updatecache(self)
833 846 return self._branchcaches[self.filtername]
834 847
835 848 @unfilteredmethod
836 849 def revbranchcache(self):
837 850 if not self._revbranchcache:
838 851 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
839 852 return self._revbranchcache
840 853
841 854 def branchtip(self, branch, ignoremissing=False):
842 855 '''return the tip node for a given branch
843 856
844 857 If ignoremissing is True, then this method will not raise an error.
845 858 This is helpful for callers that only expect None for a missing branch
846 859 (e.g. namespace).
847 860
848 861 '''
849 862 try:
850 863 return self.branchmap().branchtip(branch)
851 864 except KeyError:
852 865 if not ignoremissing:
853 866 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
854 867 else:
855 868 pass
856 869
857 870 def lookup(self, key):
858 871 return self[key].node()
859 872
860 873 def lookupbranch(self, key, remote=None):
861 874 repo = remote or self
862 875 if key in repo.branchmap():
863 876 return key
864 877
865 878 repo = (remote and remote.local()) and remote or self
866 879 return repo[key].branch()
867 880
868 881 def known(self, nodes):
869 882 cl = self.changelog
870 883 nm = cl.nodemap
871 884 filtered = cl.filteredrevs
872 885 result = []
873 886 for n in nodes:
874 887 r = nm.get(n)
875 888 resp = not (r is None or r in filtered)
876 889 result.append(resp)
877 890 return result
878 891
879 892 def local(self):
880 893 return self
881 894
882 895 def publishing(self):
883 896 # it's safe (and desirable) to trust the publish flag unconditionally
884 897 # so that we don't finalize changes shared between users via ssh or nfs
885 898 return self.ui.configbool('phases', 'publish', True, untrusted=True)
886 899
887 900 def cancopy(self):
888 901 # so statichttprepo's override of local() works
889 902 if not self.local():
890 903 return False
891 904 if not self.publishing():
892 905 return True
893 906 # if publishing we can't copy if there is filtered content
894 907 return not self.filtered('visible').changelog.filteredrevs
895 908
896 909 def shared(self):
897 910 '''the type of shared repository (None if not shared)'''
898 911 if self.sharedpath != self.path:
899 912 return 'store'
900 913 return None
901 914
902 915 def join(self, f, *insidef):
903 916 return self.vfs.join(os.path.join(f, *insidef))
904 917
905 918 def wjoin(self, f, *insidef):
906 919 return self.vfs.reljoin(self.root, f, *insidef)
907 920
908 921 def file(self, f):
909 922 if f[0] == '/':
910 923 f = f[1:]
911 924 return filelog.filelog(self.svfs, f)
912 925
913 926 def changectx(self, changeid):
914 927 return self[changeid]
915 928
916 929 def setparents(self, p1, p2=nullid):
917 930 self.dirstate.beginparentchange()
918 931 copies = self.dirstate.setparents(p1, p2)
919 932 pctx = self[p1]
920 933 if copies:
921 934 # Adjust copy records, the dirstate cannot do it, it
922 935 # requires access to parents manifests. Preserve them
923 936 # only for entries added to first parent.
924 937 for f in copies:
925 938 if f not in pctx and copies[f] in pctx:
926 939 self.dirstate.copy(copies[f], f)
927 940 if p2 == nullid:
928 941 for f, s in sorted(self.dirstate.copies().items()):
929 942 if f not in pctx and s not in pctx:
930 943 self.dirstate.copy(None, f)
931 944 self.dirstate.endparentchange()
932 945
933 946 def filectx(self, path, changeid=None, fileid=None):
934 947 """changeid can be a changeset revision, node, or tag.
935 948 fileid can be a file revision or node."""
936 949 return context.filectx(self, path, changeid, fileid)
937 950
938 951 def getcwd(self):
939 952 return self.dirstate.getcwd()
940 953
941 954 def pathto(self, f, cwd=None):
942 955 return self.dirstate.pathto(f, cwd)
943 956
944 957 def wfile(self, f, mode='r'):
945 958 return self.wvfs(f, mode)
946 959
947 960 def _link(self, f):
948 961 return self.wvfs.islink(f)
949 962
950 963 def _loadfilter(self, filter):
951 964 if filter not in self.filterpats:
952 965 l = []
953 966 for pat, cmd in self.ui.configitems(filter):
954 967 if cmd == '!':
955 968 continue
956 969 mf = matchmod.match(self.root, '', [pat])
957 970 fn = None
958 971 params = cmd
959 972 for name, filterfn in self._datafilters.iteritems():
960 973 if cmd.startswith(name):
961 974 fn = filterfn
962 975 params = cmd[len(name):].lstrip()
963 976 break
964 977 if not fn:
965 978 fn = lambda s, c, **kwargs: util.filter(s, c)
966 979 # Wrap old filters not supporting keyword arguments
967 980 if not inspect.getargspec(fn)[2]:
968 981 oldfn = fn
969 982 fn = lambda s, c, **kwargs: oldfn(s, c)
970 983 l.append((mf, fn, params))
971 984 self.filterpats[filter] = l
972 985 return self.filterpats[filter]
973 986
974 987 def _filter(self, filterpats, filename, data):
975 988 for mf, fn, cmd in filterpats:
976 989 if mf(filename):
977 990 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
978 991 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
979 992 break
980 993
981 994 return data
982 995
983 996 @unfilteredpropertycache
984 997 def _encodefilterpats(self):
985 998 return self._loadfilter('encode')
986 999
987 1000 @unfilteredpropertycache
988 1001 def _decodefilterpats(self):
989 1002 return self._loadfilter('decode')
990 1003
991 1004 def adddatafilter(self, name, filter):
992 1005 self._datafilters[name] = filter
993 1006
994 1007 def wread(self, filename):
995 1008 if self._link(filename):
996 1009 data = self.wvfs.readlink(filename)
997 1010 else:
998 1011 data = self.wvfs.read(filename)
999 1012 return self._filter(self._encodefilterpats, filename, data)
1000 1013
1001 1014 def wwrite(self, filename, data, flags, backgroundclose=False):
1002 1015 """write ``data`` into ``filename`` in the working directory
1003 1016
1004 1017 This returns length of written (maybe decoded) data.
1005 1018 """
1006 1019 data = self._filter(self._decodefilterpats, filename, data)
1007 1020 if 'l' in flags:
1008 1021 self.wvfs.symlink(data, filename)
1009 1022 else:
1010 1023 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
1011 1024 if 'x' in flags:
1012 1025 self.wvfs.setflags(filename, False, True)
1013 1026 return len(data)
1014 1027
1015 1028 def wwritedata(self, filename, data):
1016 1029 return self._filter(self._decodefilterpats, filename, data)
1017 1030
1018 1031 def currenttransaction(self):
1019 1032 """return the current transaction or None if non exists"""
1020 1033 if self._transref:
1021 1034 tr = self._transref()
1022 1035 else:
1023 1036 tr = None
1024 1037
1025 1038 if tr and tr.running():
1026 1039 return tr
1027 1040 return None
1028 1041
1029 1042 def transaction(self, desc, report=None):
1030 1043 if (self.ui.configbool('devel', 'all-warnings')
1031 1044 or self.ui.configbool('devel', 'check-locks')):
1032 1045 if self._currentlock(self._lockref) is None:
1033 1046 raise error.ProgrammingError('transaction requires locking')
1034 1047 tr = self.currenttransaction()
1035 1048 if tr is not None:
1036 1049 return tr.nest()
1037 1050
1038 1051 # abort here if the journal already exists
1039 1052 if self.svfs.exists("journal"):
1040 1053 raise error.RepoError(
1041 1054 _("abandoned transaction found"),
1042 1055 hint=_("run 'hg recover' to clean up transaction"))
1043 1056
1044 1057 idbase = "%.40f#%f" % (random.random(), time.time())
1045 1058 txnid = 'TXN:' + hashlib.sha1(idbase).hexdigest()
1046 1059 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1047 1060
1048 1061 self._writejournal(desc)
1049 1062 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1050 1063 if report:
1051 1064 rp = report
1052 1065 else:
1053 1066 rp = self.ui.warn
1054 1067 vfsmap = {'plain': self.vfs} # root of .hg/
1055 1068 # we must avoid cyclic reference between repo and transaction.
1056 1069 reporef = weakref.ref(self)
1057 1070 def validate(tr):
1058 1071 """will run pre-closing hooks"""
1059 1072 reporef().hook('pretxnclose', throw=True,
1060 1073 txnname=desc, **tr.hookargs)
1061 1074 def releasefn(tr, success):
1062 1075 repo = reporef()
1063 1076 if success:
1064 1077 # this should be explicitly invoked here, because
1065 1078 # in-memory changes aren't written out at closing
1066 1079 # transaction, if tr.addfilegenerator (via
1067 1080 # dirstate.write or so) isn't invoked while
1068 1081 # transaction running
1069 1082 repo.dirstate.write(None)
1070 1083 else:
1071 1084 # discard all changes (including ones already written
1072 1085 # out) in this transaction
1073 1086 repo.dirstate.restorebackup(None, prefix='journal.')
1074 1087
1075 1088 repo.invalidate(clearfilecache=True)
1076 1089
1077 1090 tr = transaction.transaction(rp, self.svfs, vfsmap,
1078 1091 "journal",
1079 1092 "undo",
1080 1093 aftertrans(renames),
1081 1094 self.store.createmode,
1082 1095 validator=validate,
1083 1096 releasefn=releasefn)
1084 1097
1085 1098 tr.hookargs['txnid'] = txnid
1086 1099 # note: writing the fncache only during finalize mean that the file is
1087 1100 # outdated when running hooks. As fncache is used for streaming clone,
1088 1101 # this is not expected to break anything that happen during the hooks.
1089 1102 tr.addfinalize('flush-fncache', self.store.write)
1090 1103 def txnclosehook(tr2):
1091 1104 """To be run if transaction is successful, will schedule a hook run
1092 1105 """
1093 1106 # Don't reference tr2 in hook() so we don't hold a reference.
1094 1107 # This reduces memory consumption when there are multiple
1095 1108 # transactions per lock. This can likely go away if issue5045
1096 1109 # fixes the function accumulation.
1097 1110 hookargs = tr2.hookargs
1098 1111
1099 1112 def hook():
1100 1113 reporef().hook('txnclose', throw=False, txnname=desc,
1101 1114 **hookargs)
1102 1115 reporef()._afterlock(hook)
1103 1116 tr.addfinalize('txnclose-hook', txnclosehook)
1104 1117 def txnaborthook(tr2):
1105 1118 """To be run if transaction is aborted
1106 1119 """
1107 1120 reporef().hook('txnabort', throw=False, txnname=desc,
1108 1121 **tr2.hookargs)
1109 1122 tr.addabort('txnabort-hook', txnaborthook)
1110 1123 # avoid eager cache invalidation. in-memory data should be identical
1111 1124 # to stored data if transaction has no error.
1112 1125 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1113 1126 self._transref = weakref.ref(tr)
1114 1127 return tr
1115 1128
1116 1129 def _journalfiles(self):
1117 1130 return ((self.svfs, 'journal'),
1118 1131 (self.vfs, 'journal.dirstate'),
1119 1132 (self.vfs, 'journal.branch'),
1120 1133 (self.vfs, 'journal.desc'),
1121 1134 (self.vfs, 'journal.bookmarks'),
1122 1135 (self.svfs, 'journal.phaseroots'))
1123 1136
1124 1137 def undofiles(self):
1125 1138 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1126 1139
1127 1140 def _writejournal(self, desc):
1128 1141 self.dirstate.savebackup(None, prefix='journal.')
1129 1142 self.vfs.write("journal.branch",
1130 1143 encoding.fromlocal(self.dirstate.branch()))
1131 1144 self.vfs.write("journal.desc",
1132 1145 "%d\n%s\n" % (len(self), desc))
1133 1146 self.vfs.write("journal.bookmarks",
1134 1147 self.vfs.tryread("bookmarks"))
1135 1148 self.svfs.write("journal.phaseroots",
1136 1149 self.svfs.tryread("phaseroots"))
1137 1150
1138 1151 def recover(self):
1139 1152 with self.lock():
1140 1153 if self.svfs.exists("journal"):
1141 1154 self.ui.status(_("rolling back interrupted transaction\n"))
1142 1155 vfsmap = {'': self.svfs,
1143 1156 'plain': self.vfs,}
1144 1157 transaction.rollback(self.svfs, vfsmap, "journal",
1145 1158 self.ui.warn)
1146 1159 self.invalidate()
1147 1160 return True
1148 1161 else:
1149 1162 self.ui.warn(_("no interrupted transaction available\n"))
1150 1163 return False
1151 1164
1152 1165 def rollback(self, dryrun=False, force=False):
1153 1166 wlock = lock = dsguard = None
1154 1167 try:
1155 1168 wlock = self.wlock()
1156 1169 lock = self.lock()
1157 1170 if self.svfs.exists("undo"):
1158 1171 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1159 1172
1160 1173 return self._rollback(dryrun, force, dsguard)
1161 1174 else:
1162 1175 self.ui.warn(_("no rollback information available\n"))
1163 1176 return 1
1164 1177 finally:
1165 1178 release(dsguard, lock, wlock)
1166 1179
1167 1180 @unfilteredmethod # Until we get smarter cache management
1168 1181 def _rollback(self, dryrun, force, dsguard):
1169 1182 ui = self.ui
1170 1183 try:
1171 1184 args = self.vfs.read('undo.desc').splitlines()
1172 1185 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1173 1186 if len(args) >= 3:
1174 1187 detail = args[2]
1175 1188 oldtip = oldlen - 1
1176 1189
1177 1190 if detail and ui.verbose:
1178 1191 msg = (_('repository tip rolled back to revision %s'
1179 1192 ' (undo %s: %s)\n')
1180 1193 % (oldtip, desc, detail))
1181 1194 else:
1182 1195 msg = (_('repository tip rolled back to revision %s'
1183 1196 ' (undo %s)\n')
1184 1197 % (oldtip, desc))
1185 1198 except IOError:
1186 1199 msg = _('rolling back unknown transaction\n')
1187 1200 desc = None
1188 1201
1189 1202 if not force and self['.'] != self['tip'] and desc == 'commit':
1190 1203 raise error.Abort(
1191 1204 _('rollback of last commit while not checked out '
1192 1205 'may lose data'), hint=_('use -f to force'))
1193 1206
1194 1207 ui.status(msg)
1195 1208 if dryrun:
1196 1209 return 0
1197 1210
1198 1211 parents = self.dirstate.parents()
1199 1212 self.destroying()
1200 1213 vfsmap = {'plain': self.vfs, '': self.svfs}
1201 1214 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1202 1215 if self.vfs.exists('undo.bookmarks'):
1203 1216 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1204 1217 if self.svfs.exists('undo.phaseroots'):
1205 1218 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1206 1219 self.invalidate()
1207 1220
1208 1221 parentgone = (parents[0] not in self.changelog.nodemap or
1209 1222 parents[1] not in self.changelog.nodemap)
1210 1223 if parentgone:
1211 1224 # prevent dirstateguard from overwriting already restored one
1212 1225 dsguard.close()
1213 1226
1214 1227 self.dirstate.restorebackup(None, prefix='undo.')
1215 1228 try:
1216 1229 branch = self.vfs.read('undo.branch')
1217 1230 self.dirstate.setbranch(encoding.tolocal(branch))
1218 1231 except IOError:
1219 1232 ui.warn(_('named branch could not be reset: '
1220 1233 'current branch is still \'%s\'\n')
1221 1234 % self.dirstate.branch())
1222 1235
1223 1236 parents = tuple([p.rev() for p in self[None].parents()])
1224 1237 if len(parents) > 1:
1225 1238 ui.status(_('working directory now based on '
1226 1239 'revisions %d and %d\n') % parents)
1227 1240 else:
1228 1241 ui.status(_('working directory now based on '
1229 1242 'revision %d\n') % parents)
1230 1243 mergemod.mergestate.clean(self, self['.'].node())
1231 1244
1232 1245 # TODO: if we know which new heads may result from this rollback, pass
1233 1246 # them to destroy(), which will prevent the branchhead cache from being
1234 1247 # invalidated.
1235 1248 self.destroyed()
1236 1249 return 0
1237 1250
1238 1251 def invalidatecaches(self):
1239 1252
1240 1253 if '_tagscache' in vars(self):
1241 1254 # can't use delattr on proxy
1242 1255 del self.__dict__['_tagscache']
1243 1256
1244 1257 self.unfiltered()._branchcaches.clear()
1245 1258 self.invalidatevolatilesets()
1246 1259
1247 1260 def invalidatevolatilesets(self):
1248 1261 self.filteredrevcache.clear()
1249 1262 obsolete.clearobscaches(self)
1250 1263
1251 1264 def invalidatedirstate(self):
1252 1265 '''Invalidates the dirstate, causing the next call to dirstate
1253 1266 to check if it was modified since the last time it was read,
1254 1267 rereading it if it has.
1255 1268
1256 1269 This is different to dirstate.invalidate() that it doesn't always
1257 1270 rereads the dirstate. Use dirstate.invalidate() if you want to
1258 1271 explicitly read the dirstate again (i.e. restoring it to a previous
1259 1272 known good state).'''
1260 1273 if hasunfilteredcache(self, 'dirstate'):
1261 1274 for k in self.dirstate._filecache:
1262 1275 try:
1263 1276 delattr(self.dirstate, k)
1264 1277 except AttributeError:
1265 1278 pass
1266 1279 delattr(self.unfiltered(), 'dirstate')
1267 1280
1268 1281 def invalidate(self, clearfilecache=False):
1269 1282 '''Invalidates both store and non-store parts other than dirstate
1270 1283
1271 1284 If a transaction is running, invalidation of store is omitted,
1272 1285 because discarding in-memory changes might cause inconsistency
1273 1286 (e.g. incomplete fncache causes unintentional failure, but
1274 1287 redundant one doesn't).
1275 1288 '''
1276 1289 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1277 1290 for k in self._filecache.keys():
1278 1291 # dirstate is invalidated separately in invalidatedirstate()
1279 1292 if k == 'dirstate':
1280 1293 continue
1281 1294
1282 1295 if clearfilecache:
1283 1296 del self._filecache[k]
1284 1297 try:
1285 1298 delattr(unfiltered, k)
1286 1299 except AttributeError:
1287 1300 pass
1288 1301 self.invalidatecaches()
1289 1302 if not self.currenttransaction():
1290 1303 # TODO: Changing contents of store outside transaction
1291 1304 # causes inconsistency. We should make in-memory store
1292 1305 # changes detectable, and abort if changed.
1293 1306 self.store.invalidatecaches()
1294 1307
1295 1308 def invalidateall(self):
1296 1309 '''Fully invalidates both store and non-store parts, causing the
1297 1310 subsequent operation to reread any outside changes.'''
1298 1311 # extension should hook this to invalidate its caches
1299 1312 self.invalidate()
1300 1313 self.invalidatedirstate()
1301 1314
1302 1315 @unfilteredmethod
1303 1316 def _refreshfilecachestats(self, tr):
1304 1317 """Reload stats of cached files so that they are flagged as valid"""
1305 1318 for k, ce in self._filecache.items():
1306 1319 if k == 'dirstate' or k not in self.__dict__:
1307 1320 continue
1308 1321 ce.refresh()
1309 1322
1310 1323 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1311 1324 inheritchecker=None, parentenvvar=None):
1312 1325 parentlock = None
1313 1326 # the contents of parentenvvar are used by the underlying lock to
1314 1327 # determine whether it can be inherited
1315 1328 if parentenvvar is not None:
1316 1329 parentlock = encoding.environ.get(parentenvvar)
1317 1330 try:
1318 1331 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1319 1332 acquirefn=acquirefn, desc=desc,
1320 1333 inheritchecker=inheritchecker,
1321 1334 parentlock=parentlock)
1322 1335 except error.LockHeld as inst:
1323 1336 if not wait:
1324 1337 raise
1325 1338 # show more details for new-style locks
1326 1339 if ':' in inst.locker:
1327 1340 host, pid = inst.locker.split(":", 1)
1328 1341 self.ui.warn(
1329 1342 _("waiting for lock on %s held by process %r "
1330 1343 "on host %r\n") % (desc, pid, host))
1331 1344 else:
1332 1345 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1333 1346 (desc, inst.locker))
1334 1347 # default to 600 seconds timeout
1335 1348 l = lockmod.lock(vfs, lockname,
1336 1349 int(self.ui.config("ui", "timeout", "600")),
1337 1350 releasefn=releasefn, acquirefn=acquirefn,
1338 1351 desc=desc)
1339 1352 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1340 1353 return l
1341 1354
1342 1355 def _afterlock(self, callback):
1343 1356 """add a callback to be run when the repository is fully unlocked
1344 1357
1345 1358 The callback will be executed when the outermost lock is released
1346 1359 (with wlock being higher level than 'lock')."""
1347 1360 for ref in (self._wlockref, self._lockref):
1348 1361 l = ref and ref()
1349 1362 if l and l.held:
1350 1363 l.postrelease.append(callback)
1351 1364 break
1352 1365 else: # no lock have been found.
1353 1366 callback()
1354 1367
1355 1368 def lock(self, wait=True):
1356 1369 '''Lock the repository store (.hg/store) and return a weak reference
1357 1370 to the lock. Use this before modifying the store (e.g. committing or
1358 1371 stripping). If you are opening a transaction, get a lock as well.)
1359 1372
1360 1373 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1361 1374 'wlock' first to avoid a dead-lock hazard.'''
1362 1375 l = self._currentlock(self._lockref)
1363 1376 if l is not None:
1364 1377 l.lock()
1365 1378 return l
1366 1379
1367 1380 l = self._lock(self.svfs, "lock", wait, None,
1368 1381 self.invalidate, _('repository %s') % self.origroot)
1369 1382 self._lockref = weakref.ref(l)
1370 1383 return l
1371 1384
1372 1385 def _wlockchecktransaction(self):
1373 1386 if self.currenttransaction() is not None:
1374 1387 raise error.LockInheritanceContractViolation(
1375 1388 'wlock cannot be inherited in the middle of a transaction')
1376 1389
1377 1390 def wlock(self, wait=True):
1378 1391 '''Lock the non-store parts of the repository (everything under
1379 1392 .hg except .hg/store) and return a weak reference to the lock.
1380 1393
1381 1394 Use this before modifying files in .hg.
1382 1395
1383 1396 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1384 1397 'wlock' first to avoid a dead-lock hazard.'''
1385 1398 l = self._wlockref and self._wlockref()
1386 1399 if l is not None and l.held:
1387 1400 l.lock()
1388 1401 return l
1389 1402
1390 1403 # We do not need to check for non-waiting lock acquisition. Such
1391 1404 # acquisition would not cause dead-lock as they would just fail.
1392 1405 if wait and (self.ui.configbool('devel', 'all-warnings')
1393 1406 or self.ui.configbool('devel', 'check-locks')):
1394 1407 if self._currentlock(self._lockref) is not None:
1395 1408 self.ui.develwarn('"wlock" acquired after "lock"')
1396 1409
1397 1410 def unlock():
1398 1411 if self.dirstate.pendingparentchange():
1399 1412 self.dirstate.invalidate()
1400 1413 else:
1401 1414 self.dirstate.write(None)
1402 1415
1403 1416 self._filecache['dirstate'].refresh()
1404 1417
1405 1418 l = self._lock(self.vfs, "wlock", wait, unlock,
1406 1419 self.invalidatedirstate, _('working directory of %s') %
1407 1420 self.origroot,
1408 1421 inheritchecker=self._wlockchecktransaction,
1409 1422 parentenvvar='HG_WLOCK_LOCKER')
1410 1423 self._wlockref = weakref.ref(l)
1411 1424 return l
1412 1425
1413 1426 def _currentlock(self, lockref):
1414 1427 """Returns the lock if it's held, or None if it's not."""
1415 1428 if lockref is None:
1416 1429 return None
1417 1430 l = lockref()
1418 1431 if l is None or not l.held:
1419 1432 return None
1420 1433 return l
1421 1434
1422 1435 def currentwlock(self):
1423 1436 """Returns the wlock if it's held, or None if it's not."""
1424 1437 return self._currentlock(self._wlockref)
1425 1438
1426 1439 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1427 1440 """
1428 1441 commit an individual file as part of a larger transaction
1429 1442 """
1430 1443
1431 1444 fname = fctx.path()
1432 1445 fparent1 = manifest1.get(fname, nullid)
1433 1446 fparent2 = manifest2.get(fname, nullid)
1434 1447 if isinstance(fctx, context.filectx):
1435 1448 node = fctx.filenode()
1436 1449 if node in [fparent1, fparent2]:
1437 1450 self.ui.debug('reusing %s filelog entry\n' % fname)
1438 1451 if manifest1.flags(fname) != fctx.flags():
1439 1452 changelist.append(fname)
1440 1453 return node
1441 1454
1442 1455 flog = self.file(fname)
1443 1456 meta = {}
1444 1457 copy = fctx.renamed()
1445 1458 if copy and copy[0] != fname:
1446 1459 # Mark the new revision of this file as a copy of another
1447 1460 # file. This copy data will effectively act as a parent
1448 1461 # of this new revision. If this is a merge, the first
1449 1462 # parent will be the nullid (meaning "look up the copy data")
1450 1463 # and the second one will be the other parent. For example:
1451 1464 #
1452 1465 # 0 --- 1 --- 3 rev1 changes file foo
1453 1466 # \ / rev2 renames foo to bar and changes it
1454 1467 # \- 2 -/ rev3 should have bar with all changes and
1455 1468 # should record that bar descends from
1456 1469 # bar in rev2 and foo in rev1
1457 1470 #
1458 1471 # this allows this merge to succeed:
1459 1472 #
1460 1473 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1461 1474 # \ / merging rev3 and rev4 should use bar@rev2
1462 1475 # \- 2 --- 4 as the merge base
1463 1476 #
1464 1477
1465 1478 cfname = copy[0]
1466 1479 crev = manifest1.get(cfname)
1467 1480 newfparent = fparent2
1468 1481
1469 1482 if manifest2: # branch merge
1470 1483 if fparent2 == nullid or crev is None: # copied on remote side
1471 1484 if cfname in manifest2:
1472 1485 crev = manifest2[cfname]
1473 1486 newfparent = fparent1
1474 1487
1475 1488 # Here, we used to search backwards through history to try to find
1476 1489 # where the file copy came from if the source of a copy was not in
1477 1490 # the parent directory. However, this doesn't actually make sense to
1478 1491 # do (what does a copy from something not in your working copy even
1479 1492 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1480 1493 # the user that copy information was dropped, so if they didn't
1481 1494 # expect this outcome it can be fixed, but this is the correct
1482 1495 # behavior in this circumstance.
1483 1496
1484 1497 if crev:
1485 1498 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1486 1499 meta["copy"] = cfname
1487 1500 meta["copyrev"] = hex(crev)
1488 1501 fparent1, fparent2 = nullid, newfparent
1489 1502 else:
1490 1503 self.ui.warn(_("warning: can't find ancestor for '%s' "
1491 1504 "copied from '%s'!\n") % (fname, cfname))
1492 1505
1493 1506 elif fparent1 == nullid:
1494 1507 fparent1, fparent2 = fparent2, nullid
1495 1508 elif fparent2 != nullid:
1496 1509 # is one parent an ancestor of the other?
1497 1510 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1498 1511 if fparent1 in fparentancestors:
1499 1512 fparent1, fparent2 = fparent2, nullid
1500 1513 elif fparent2 in fparentancestors:
1501 1514 fparent2 = nullid
1502 1515
1503 1516 # is the file changed?
1504 1517 text = fctx.data()
1505 1518 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1506 1519 changelist.append(fname)
1507 1520 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1508 1521 # are just the flags changed during merge?
1509 1522 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1510 1523 changelist.append(fname)
1511 1524
1512 1525 return fparent1
1513 1526
1514 1527 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1515 1528 """check for commit arguments that aren't committable"""
1516 1529 if match.isexact() or match.prefix():
1517 1530 matched = set(status.modified + status.added + status.removed)
1518 1531
1519 1532 for f in match.files():
1520 1533 f = self.dirstate.normalize(f)
1521 1534 if f == '.' or f in matched or f in wctx.substate:
1522 1535 continue
1523 1536 if f in status.deleted:
1524 1537 fail(f, _('file not found!'))
1525 1538 if f in vdirs: # visited directory
1526 1539 d = f + '/'
1527 1540 for mf in matched:
1528 1541 if mf.startswith(d):
1529 1542 break
1530 1543 else:
1531 1544 fail(f, _("no match under directory!"))
1532 1545 elif f not in self.dirstate:
1533 1546 fail(f, _("file not tracked!"))
1534 1547
1535 1548 @unfilteredmethod
1536 1549 def commit(self, text="", user=None, date=None, match=None, force=False,
1537 1550 editor=False, extra=None):
1538 1551 """Add a new revision to current repository.
1539 1552
1540 1553 Revision information is gathered from the working directory,
1541 1554 match can be used to filter the committed files. If editor is
1542 1555 supplied, it is called to get a commit message.
1543 1556 """
1544 1557 if extra is None:
1545 1558 extra = {}
1546 1559
1547 1560 def fail(f, msg):
1548 1561 raise error.Abort('%s: %s' % (f, msg))
1549 1562
1550 1563 if not match:
1551 1564 match = matchmod.always(self.root, '')
1552 1565
1553 1566 if not force:
1554 1567 vdirs = []
1555 1568 match.explicitdir = vdirs.append
1556 1569 match.bad = fail
1557 1570
1558 1571 wlock = lock = tr = None
1559 1572 try:
1560 1573 wlock = self.wlock()
1561 1574 lock = self.lock() # for recent changelog (see issue4368)
1562 1575
1563 1576 wctx = self[None]
1564 1577 merge = len(wctx.parents()) > 1
1565 1578
1566 1579 if not force and merge and match.ispartial():
1567 1580 raise error.Abort(_('cannot partially commit a merge '
1568 1581 '(do not specify files or patterns)'))
1569 1582
1570 1583 status = self.status(match=match, clean=force)
1571 1584 if force:
1572 1585 status.modified.extend(status.clean) # mq may commit clean files
1573 1586
1574 1587 # check subrepos
1575 1588 subs = []
1576 1589 commitsubs = set()
1577 1590 newstate = wctx.substate.copy()
1578 1591 # only manage subrepos and .hgsubstate if .hgsub is present
1579 1592 if '.hgsub' in wctx:
1580 1593 # we'll decide whether to track this ourselves, thanks
1581 1594 for c in status.modified, status.added, status.removed:
1582 1595 if '.hgsubstate' in c:
1583 1596 c.remove('.hgsubstate')
1584 1597
1585 1598 # compare current state to last committed state
1586 1599 # build new substate based on last committed state
1587 1600 oldstate = wctx.p1().substate
1588 1601 for s in sorted(newstate.keys()):
1589 1602 if not match(s):
1590 1603 # ignore working copy, use old state if present
1591 1604 if s in oldstate:
1592 1605 newstate[s] = oldstate[s]
1593 1606 continue
1594 1607 if not force:
1595 1608 raise error.Abort(
1596 1609 _("commit with new subrepo %s excluded") % s)
1597 1610 dirtyreason = wctx.sub(s).dirtyreason(True)
1598 1611 if dirtyreason:
1599 1612 if not self.ui.configbool('ui', 'commitsubrepos'):
1600 1613 raise error.Abort(dirtyreason,
1601 1614 hint=_("use --subrepos for recursive commit"))
1602 1615 subs.append(s)
1603 1616 commitsubs.add(s)
1604 1617 else:
1605 1618 bs = wctx.sub(s).basestate()
1606 1619 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1607 1620 if oldstate.get(s, (None, None, None))[1] != bs:
1608 1621 subs.append(s)
1609 1622
1610 1623 # check for removed subrepos
1611 1624 for p in wctx.parents():
1612 1625 r = [s for s in p.substate if s not in newstate]
1613 1626 subs += [s for s in r if match(s)]
1614 1627 if subs:
1615 1628 if (not match('.hgsub') and
1616 1629 '.hgsub' in (wctx.modified() + wctx.added())):
1617 1630 raise error.Abort(
1618 1631 _("can't commit subrepos without .hgsub"))
1619 1632 status.modified.insert(0, '.hgsubstate')
1620 1633
1621 1634 elif '.hgsub' in status.removed:
1622 1635 # clean up .hgsubstate when .hgsub is removed
1623 1636 if ('.hgsubstate' in wctx and
1624 1637 '.hgsubstate' not in (status.modified + status.added +
1625 1638 status.removed)):
1626 1639 status.removed.insert(0, '.hgsubstate')
1627 1640
1628 1641 # make sure all explicit patterns are matched
1629 1642 if not force:
1630 1643 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1631 1644
1632 1645 cctx = context.workingcommitctx(self, status,
1633 1646 text, user, date, extra)
1634 1647
1635 1648 # internal config: ui.allowemptycommit
1636 1649 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1637 1650 or extra.get('close') or merge or cctx.files()
1638 1651 or self.ui.configbool('ui', 'allowemptycommit'))
1639 1652 if not allowemptycommit:
1640 1653 return None
1641 1654
1642 1655 if merge and cctx.deleted():
1643 1656 raise error.Abort(_("cannot commit merge with missing files"))
1644 1657
1645 1658 ms = mergemod.mergestate.read(self)
1646 1659 mergeutil.checkunresolved(ms)
1647 1660
1648 1661 if editor:
1649 1662 cctx._text = editor(self, cctx, subs)
1650 1663 edited = (text != cctx._text)
1651 1664
1652 1665 # Save commit message in case this transaction gets rolled back
1653 1666 # (e.g. by a pretxncommit hook). Leave the content alone on
1654 1667 # the assumption that the user will use the same editor again.
1655 1668 msgfn = self.savecommitmessage(cctx._text)
1656 1669
1657 1670 # commit subs and write new state
1658 1671 if subs:
1659 1672 for s in sorted(commitsubs):
1660 1673 sub = wctx.sub(s)
1661 1674 self.ui.status(_('committing subrepository %s\n') %
1662 1675 subrepo.subrelpath(sub))
1663 1676 sr = sub.commit(cctx._text, user, date)
1664 1677 newstate[s] = (newstate[s][0], sr)
1665 1678 subrepo.writestate(self, newstate)
1666 1679
1667 1680 p1, p2 = self.dirstate.parents()
1668 1681 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1669 1682 try:
1670 1683 self.hook("precommit", throw=True, parent1=hookp1,
1671 1684 parent2=hookp2)
1672 1685 tr = self.transaction('commit')
1673 1686 ret = self.commitctx(cctx, True)
1674 1687 except: # re-raises
1675 1688 if edited:
1676 1689 self.ui.write(
1677 1690 _('note: commit message saved in %s\n') % msgfn)
1678 1691 raise
1679 1692 # update bookmarks, dirstate and mergestate
1680 1693 bookmarks.update(self, [p1, p2], ret)
1681 1694 cctx.markcommitted(ret)
1682 1695 ms.reset()
1683 1696 tr.close()
1684 1697
1685 1698 finally:
1686 1699 lockmod.release(tr, lock, wlock)
1687 1700
1688 1701 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1689 1702 # hack for command that use a temporary commit (eg: histedit)
1690 1703 # temporary commit got stripped before hook release
1691 1704 if self.changelog.hasnode(ret):
1692 1705 self.hook("commit", node=node, parent1=parent1,
1693 1706 parent2=parent2)
1694 1707 self._afterlock(commithook)
1695 1708 return ret
1696 1709
1697 1710 @unfilteredmethod
1698 1711 def commitctx(self, ctx, error=False):
1699 1712 """Add a new revision to current repository.
1700 1713 Revision information is passed via the context argument.
1701 1714 """
1702 1715
1703 1716 tr = None
1704 1717 p1, p2 = ctx.p1(), ctx.p2()
1705 1718 user = ctx.user()
1706 1719
1707 1720 lock = self.lock()
1708 1721 try:
1709 1722 tr = self.transaction("commit")
1710 1723 trp = weakref.proxy(tr)
1711 1724
1712 1725 if ctx.manifestnode():
1713 1726 # reuse an existing manifest revision
1714 1727 mn = ctx.manifestnode()
1715 1728 files = ctx.files()
1716 1729 elif ctx.files():
1717 1730 m1ctx = p1.manifestctx()
1718 1731 m2ctx = p2.manifestctx()
1719 1732 mctx = m1ctx.copy()
1720 1733
1721 1734 m = mctx.read()
1722 1735 m1 = m1ctx.read()
1723 1736 m2 = m2ctx.read()
1724 1737
1725 1738 # check in files
1726 1739 added = []
1727 1740 changed = []
1728 1741 removed = list(ctx.removed())
1729 1742 linkrev = len(self)
1730 1743 self.ui.note(_("committing files:\n"))
1731 1744 for f in sorted(ctx.modified() + ctx.added()):
1732 1745 self.ui.note(f + "\n")
1733 1746 try:
1734 1747 fctx = ctx[f]
1735 1748 if fctx is None:
1736 1749 removed.append(f)
1737 1750 else:
1738 1751 added.append(f)
1739 1752 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1740 1753 trp, changed)
1741 1754 m.setflag(f, fctx.flags())
1742 1755 except OSError as inst:
1743 1756 self.ui.warn(_("trouble committing %s!\n") % f)
1744 1757 raise
1745 1758 except IOError as inst:
1746 1759 errcode = getattr(inst, 'errno', errno.ENOENT)
1747 1760 if error or errcode and errcode != errno.ENOENT:
1748 1761 self.ui.warn(_("trouble committing %s!\n") % f)
1749 1762 raise
1750 1763
1751 1764 # update manifest
1752 1765 self.ui.note(_("committing manifest\n"))
1753 1766 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1754 1767 drop = [f for f in removed if f in m]
1755 1768 for f in drop:
1756 1769 del m[f]
1757 1770 mn = mctx.write(trp, linkrev,
1758 1771 p1.manifestnode(), p2.manifestnode(),
1759 1772 added, drop)
1760 1773 files = changed + removed
1761 1774 else:
1762 1775 mn = p1.manifestnode()
1763 1776 files = []
1764 1777
1765 1778 # update changelog
1766 1779 self.ui.note(_("committing changelog\n"))
1767 1780 self.changelog.delayupdate(tr)
1768 1781 n = self.changelog.add(mn, files, ctx.description(),
1769 1782 trp, p1.node(), p2.node(),
1770 1783 user, ctx.date(), ctx.extra().copy())
1771 1784 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1772 1785 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1773 1786 parent2=xp2)
1774 1787 # set the new commit is proper phase
1775 1788 targetphase = subrepo.newcommitphase(self.ui, ctx)
1776 1789 if targetphase:
1777 1790 # retract boundary do not alter parent changeset.
1778 1791 # if a parent have higher the resulting phase will
1779 1792 # be compliant anyway
1780 1793 #
1781 1794 # if minimal phase was 0 we don't need to retract anything
1782 1795 phases.retractboundary(self, tr, targetphase, [n])
1783 1796 tr.close()
1784 1797 branchmap.updatecache(self.filtered('served'))
1785 1798 return n
1786 1799 finally:
1787 1800 if tr:
1788 1801 tr.release()
1789 1802 lock.release()
1790 1803
1791 1804 @unfilteredmethod
1792 1805 def destroying(self):
1793 1806 '''Inform the repository that nodes are about to be destroyed.
1794 1807 Intended for use by strip and rollback, so there's a common
1795 1808 place for anything that has to be done before destroying history.
1796 1809
1797 1810 This is mostly useful for saving state that is in memory and waiting
1798 1811 to be flushed when the current lock is released. Because a call to
1799 1812 destroyed is imminent, the repo will be invalidated causing those
1800 1813 changes to stay in memory (waiting for the next unlock), or vanish
1801 1814 completely.
1802 1815 '''
1803 1816 # When using the same lock to commit and strip, the phasecache is left
1804 1817 # dirty after committing. Then when we strip, the repo is invalidated,
1805 1818 # causing those changes to disappear.
1806 1819 if '_phasecache' in vars(self):
1807 1820 self._phasecache.write()
1808 1821
1809 1822 @unfilteredmethod
1810 1823 def destroyed(self):
1811 1824 '''Inform the repository that nodes have been destroyed.
1812 1825 Intended for use by strip and rollback, so there's a common
1813 1826 place for anything that has to be done after destroying history.
1814 1827 '''
1815 1828 # When one tries to:
1816 1829 # 1) destroy nodes thus calling this method (e.g. strip)
1817 1830 # 2) use phasecache somewhere (e.g. commit)
1818 1831 #
1819 1832 # then 2) will fail because the phasecache contains nodes that were
1820 1833 # removed. We can either remove phasecache from the filecache,
1821 1834 # causing it to reload next time it is accessed, or simply filter
1822 1835 # the removed nodes now and write the updated cache.
1823 1836 self._phasecache.filterunknown(self)
1824 1837 self._phasecache.write()
1825 1838
1826 1839 # update the 'served' branch cache to help read only server process
1827 1840 # Thanks to branchcache collaboration this is done from the nearest
1828 1841 # filtered subset and it is expected to be fast.
1829 1842 branchmap.updatecache(self.filtered('served'))
1830 1843
1831 1844 # Ensure the persistent tag cache is updated. Doing it now
1832 1845 # means that the tag cache only has to worry about destroyed
1833 1846 # heads immediately after a strip/rollback. That in turn
1834 1847 # guarantees that "cachetip == currenttip" (comparing both rev
1835 1848 # and node) always means no nodes have been added or destroyed.
1836 1849
1837 1850 # XXX this is suboptimal when qrefresh'ing: we strip the current
1838 1851 # head, refresh the tag cache, then immediately add a new head.
1839 1852 # But I think doing it this way is necessary for the "instant
1840 1853 # tag cache retrieval" case to work.
1841 1854 self.invalidate()
1842 1855
1843 1856 def walk(self, match, node=None):
1844 1857 '''
1845 1858 walk recursively through the directory tree or a given
1846 1859 changeset, finding all files matched by the match
1847 1860 function
1848 1861 '''
1849 1862 return self[node].walk(match)
1850 1863
1851 1864 def status(self, node1='.', node2=None, match=None,
1852 1865 ignored=False, clean=False, unknown=False,
1853 1866 listsubrepos=False):
1854 1867 '''a convenience method that calls node1.status(node2)'''
1855 1868 return self[node1].status(node2, match, ignored, clean, unknown,
1856 1869 listsubrepos)
1857 1870
1858 1871 def heads(self, start=None):
1859 1872 if start is None:
1860 1873 cl = self.changelog
1861 1874 headrevs = reversed(cl.headrevs())
1862 1875 return [cl.node(rev) for rev in headrevs]
1863 1876
1864 1877 heads = self.changelog.heads(start)
1865 1878 # sort the output in rev descending order
1866 1879 return sorted(heads, key=self.changelog.rev, reverse=True)
1867 1880
1868 1881 def branchheads(self, branch=None, start=None, closed=False):
1869 1882 '''return a (possibly filtered) list of heads for the given branch
1870 1883
1871 1884 Heads are returned in topological order, from newest to oldest.
1872 1885 If branch is None, use the dirstate branch.
1873 1886 If start is not None, return only heads reachable from start.
1874 1887 If closed is True, return heads that are marked as closed as well.
1875 1888 '''
1876 1889 if branch is None:
1877 1890 branch = self[None].branch()
1878 1891 branches = self.branchmap()
1879 1892 if branch not in branches:
1880 1893 return []
1881 1894 # the cache returns heads ordered lowest to highest
1882 1895 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1883 1896 if start is not None:
1884 1897 # filter out the heads that cannot be reached from startrev
1885 1898 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1886 1899 bheads = [h for h in bheads if h in fbheads]
1887 1900 return bheads
1888 1901
1889 1902 def branches(self, nodes):
1890 1903 if not nodes:
1891 1904 nodes = [self.changelog.tip()]
1892 1905 b = []
1893 1906 for n in nodes:
1894 1907 t = n
1895 1908 while True:
1896 1909 p = self.changelog.parents(n)
1897 1910 if p[1] != nullid or p[0] == nullid:
1898 1911 b.append((t, n, p[0], p[1]))
1899 1912 break
1900 1913 n = p[0]
1901 1914 return b
1902 1915
1903 1916 def between(self, pairs):
1904 1917 r = []
1905 1918
1906 1919 for top, bottom in pairs:
1907 1920 n, l, i = top, [], 0
1908 1921 f = 1
1909 1922
1910 1923 while n != bottom and n != nullid:
1911 1924 p = self.changelog.parents(n)[0]
1912 1925 if i == f:
1913 1926 l.append(n)
1914 1927 f = f * 2
1915 1928 n = p
1916 1929 i += 1
1917 1930
1918 1931 r.append(l)
1919 1932
1920 1933 return r
1921 1934
1922 1935 def checkpush(self, pushop):
1923 1936 """Extensions can override this function if additional checks have
1924 1937 to be performed before pushing, or call it if they override push
1925 1938 command.
1926 1939 """
1927 1940 pass
1928 1941
1929 1942 @unfilteredpropertycache
1930 1943 def prepushoutgoinghooks(self):
1931 1944 """Return util.hooks consists of a pushop with repo, remote, outgoing
1932 1945 methods, which are called before pushing changesets.
1933 1946 """
1934 1947 return util.hooks()
1935 1948
1936 1949 def pushkey(self, namespace, key, old, new):
1937 1950 try:
1938 1951 tr = self.currenttransaction()
1939 1952 hookargs = {}
1940 1953 if tr is not None:
1941 1954 hookargs.update(tr.hookargs)
1942 1955 hookargs['namespace'] = namespace
1943 1956 hookargs['key'] = key
1944 1957 hookargs['old'] = old
1945 1958 hookargs['new'] = new
1946 1959 self.hook('prepushkey', throw=True, **hookargs)
1947 1960 except error.HookAbort as exc:
1948 1961 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1949 1962 if exc.hint:
1950 1963 self.ui.write_err(_("(%s)\n") % exc.hint)
1951 1964 return False
1952 1965 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1953 1966 ret = pushkey.push(self, namespace, key, old, new)
1954 1967 def runhook():
1955 1968 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1956 1969 ret=ret)
1957 1970 self._afterlock(runhook)
1958 1971 return ret
1959 1972
1960 1973 def listkeys(self, namespace):
1961 1974 self.hook('prelistkeys', throw=True, namespace=namespace)
1962 1975 self.ui.debug('listing keys for "%s"\n' % namespace)
1963 1976 values = pushkey.list(self, namespace)
1964 1977 self.hook('listkeys', namespace=namespace, values=values)
1965 1978 return values
1966 1979
1967 1980 def debugwireargs(self, one, two, three=None, four=None, five=None):
1968 1981 '''used to test argument passing over the wire'''
1969 1982 return "%s %s %s %s %s" % (one, two, three, four, five)
1970 1983
1971 1984 def savecommitmessage(self, text):
1972 1985 fp = self.vfs('last-message.txt', 'wb')
1973 1986 try:
1974 1987 fp.write(text)
1975 1988 finally:
1976 1989 fp.close()
1977 1990 return self.pathto(fp.name[len(self.root) + 1:])
1978 1991
1979 1992 # used to avoid circular references so destructors work
1980 1993 def aftertrans(files):
1981 1994 renamefiles = [tuple(t) for t in files]
1982 1995 def a():
1983 1996 for vfs, src, dest in renamefiles:
1984 1997 try:
1985 1998 vfs.rename(src, dest)
1986 1999 except OSError: # journal file does not yet exist
1987 2000 pass
1988 2001 return a
1989 2002
1990 2003 def undoname(fn):
1991 2004 base, name = os.path.split(fn)
1992 2005 assert name.startswith('journal')
1993 2006 return os.path.join(base, name.replace('journal', 'undo', 1))
1994 2007
1995 2008 def instance(ui, path, create):
1996 2009 return localrepository(ui, util.urllocalpath(path), create)
1997 2010
1998 2011 def islocal(path):
1999 2012 return True
2000 2013
2001 2014 def newreporequirements(repo):
2002 2015 """Determine the set of requirements for a new local repository.
2003 2016
2004 2017 Extensions can wrap this function to specify custom requirements for
2005 2018 new repositories.
2006 2019 """
2007 2020 ui = repo.ui
2008 2021 requirements = set(['revlogv1'])
2009 2022 if ui.configbool('format', 'usestore', True):
2010 2023 requirements.add('store')
2011 2024 if ui.configbool('format', 'usefncache', True):
2012 2025 requirements.add('fncache')
2013 2026 if ui.configbool('format', 'dotencode', True):
2014 2027 requirements.add('dotencode')
2015 2028
2016 2029 compengine = ui.config('experimental', 'format.compression', 'zlib')
2017 2030 if compengine not in util.compengines:
2018 2031 raise error.Abort(_('compression engine %s defined by '
2019 2032 'experimental.format.compression not available') %
2020 2033 compengine,
2021 2034 hint=_('run "hg debuginstall" to list available '
2022 2035 'compression engines'))
2023 2036
2024 2037 # zlib is the historical default and doesn't need an explicit requirement.
2025 2038 if compengine != 'zlib':
2026 2039 requirements.add('exp-compression-%s' % compengine)
2027 2040
2028 2041 if scmutil.gdinitconfig(ui):
2029 2042 requirements.add('generaldelta')
2030 2043 if ui.configbool('experimental', 'treemanifest', False):
2031 2044 requirements.add('treemanifest')
2032 2045 if ui.configbool('experimental', 'manifestv2', False):
2033 2046 requirements.add('manifestv2')
2034 2047
2035 2048 return requirements
@@ -1,1574 +1,1572 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import contextlib
11 11 import errno
12 12 import glob
13 13 import hashlib
14 14 import os
15 15 import re
16 16 import shutil
17 17 import socket
18 18 import stat
19 19 import tempfile
20 20 import threading
21 21
22 22 from .i18n import _
23 23 from .node import wdirrev
24 24 from . import (
25 25 encoding,
26 26 error,
27 27 match as matchmod,
28 28 osutil,
29 29 pathutil,
30 30 phases,
31 31 pycompat,
32 revset,
33 32 revsetlang,
34 33 similar,
35 34 util,
36 35 )
37 36
38 37 if pycompat.osname == 'nt':
39 38 from . import scmwindows as scmplatform
40 39 else:
41 40 from . import scmposix as scmplatform
42 41
43 42 systemrcpath = scmplatform.systemrcpath
44 43 userrcpath = scmplatform.userrcpath
45 44 termsize = scmplatform.termsize
46 45
47 46 class status(tuple):
48 47 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
49 48 and 'ignored' properties are only relevant to the working copy.
50 49 '''
51 50
52 51 __slots__ = ()
53 52
54 53 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
55 54 clean):
56 55 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
57 56 ignored, clean))
58 57
59 58 @property
60 59 def modified(self):
61 60 '''files that have been modified'''
62 61 return self[0]
63 62
64 63 @property
65 64 def added(self):
66 65 '''files that have been added'''
67 66 return self[1]
68 67
69 68 @property
70 69 def removed(self):
71 70 '''files that have been removed'''
72 71 return self[2]
73 72
74 73 @property
75 74 def deleted(self):
76 75 '''files that are in the dirstate, but have been deleted from the
77 76 working copy (aka "missing")
78 77 '''
79 78 return self[3]
80 79
81 80 @property
82 81 def unknown(self):
83 82 '''files not in the dirstate that are not ignored'''
84 83 return self[4]
85 84
86 85 @property
87 86 def ignored(self):
88 87 '''files not in the dirstate that are ignored (by _dirignore())'''
89 88 return self[5]
90 89
91 90 @property
92 91 def clean(self):
93 92 '''files that have not been modified'''
94 93 return self[6]
95 94
96 95 def __repr__(self, *args, **kwargs):
97 96 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
98 97 'unknown=%r, ignored=%r, clean=%r>') % self)
99 98
100 99 def itersubrepos(ctx1, ctx2):
101 100 """find subrepos in ctx1 or ctx2"""
102 101 # Create a (subpath, ctx) mapping where we prefer subpaths from
103 102 # ctx1. The subpaths from ctx2 are important when the .hgsub file
104 103 # has been modified (in ctx2) but not yet committed (in ctx1).
105 104 subpaths = dict.fromkeys(ctx2.substate, ctx2)
106 105 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
107 106
108 107 missing = set()
109 108
110 109 for subpath in ctx2.substate:
111 110 if subpath not in ctx1.substate:
112 111 del subpaths[subpath]
113 112 missing.add(subpath)
114 113
115 114 for subpath, ctx in sorted(subpaths.iteritems()):
116 115 yield subpath, ctx.sub(subpath)
117 116
118 117 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
119 118 # status and diff will have an accurate result when it does
120 119 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
121 120 # against itself.
122 121 for subpath in missing:
123 122 yield subpath, ctx2.nullsub(subpath, ctx1)
124 123
125 124 def nochangesfound(ui, repo, excluded=None):
126 125 '''Report no changes for push/pull, excluded is None or a list of
127 126 nodes excluded from the push/pull.
128 127 '''
129 128 secretlist = []
130 129 if excluded:
131 130 for n in excluded:
132 131 if n not in repo:
133 132 # discovery should not have included the filtered revision,
134 133 # we have to explicitly exclude it until discovery is cleanup.
135 134 continue
136 135 ctx = repo[n]
137 136 if ctx.phase() >= phases.secret and not ctx.extinct():
138 137 secretlist.append(n)
139 138
140 139 if secretlist:
141 140 ui.status(_("no changes found (ignored %d secret changesets)\n")
142 141 % len(secretlist))
143 142 else:
144 143 ui.status(_("no changes found\n"))
145 144
146 145 def callcatch(ui, func):
147 146 """call func() with global exception handling
148 147
149 148 return func() if no exception happens. otherwise do some error handling
150 149 and return an exit code accordingly. does not handle all exceptions.
151 150 """
152 151 try:
153 152 return func()
154 153 # Global exception handling, alphabetically
155 154 # Mercurial-specific first, followed by built-in and library exceptions
156 155 except error.LockHeld as inst:
157 156 if inst.errno == errno.ETIMEDOUT:
158 157 reason = _('timed out waiting for lock held by %s') % inst.locker
159 158 else:
160 159 reason = _('lock held by %s') % inst.locker
161 160 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
162 161 except error.LockUnavailable as inst:
163 162 ui.warn(_("abort: could not lock %s: %s\n") %
164 163 (inst.desc or inst.filename, inst.strerror))
165 164 except error.OutOfBandError as inst:
166 165 if inst.args:
167 166 msg = _("abort: remote error:\n")
168 167 else:
169 168 msg = _("abort: remote error\n")
170 169 ui.warn(msg)
171 170 if inst.args:
172 171 ui.warn(''.join(inst.args))
173 172 if inst.hint:
174 173 ui.warn('(%s)\n' % inst.hint)
175 174 except error.RepoError as inst:
176 175 ui.warn(_("abort: %s!\n") % inst)
177 176 if inst.hint:
178 177 ui.warn(_("(%s)\n") % inst.hint)
179 178 except error.ResponseError as inst:
180 179 ui.warn(_("abort: %s") % inst.args[0])
181 180 if not isinstance(inst.args[1], basestring):
182 181 ui.warn(" %r\n" % (inst.args[1],))
183 182 elif not inst.args[1]:
184 183 ui.warn(_(" empty string\n"))
185 184 else:
186 185 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
187 186 except error.CensoredNodeError as inst:
188 187 ui.warn(_("abort: file censored %s!\n") % inst)
189 188 except error.RevlogError as inst:
190 189 ui.warn(_("abort: %s!\n") % inst)
191 190 except error.SignalInterrupt:
192 191 ui.warn(_("killed!\n"))
193 192 except error.InterventionRequired as inst:
194 193 ui.warn("%s\n" % inst)
195 194 if inst.hint:
196 195 ui.warn(_("(%s)\n") % inst.hint)
197 196 return 1
198 197 except error.Abort as inst:
199 198 ui.warn(_("abort: %s\n") % inst)
200 199 if inst.hint:
201 200 ui.warn(_("(%s)\n") % inst.hint)
202 201 except ImportError as inst:
203 202 ui.warn(_("abort: %s!\n") % inst)
204 203 m = str(inst).split()[-1]
205 204 if m in "mpatch bdiff".split():
206 205 ui.warn(_("(did you forget to compile extensions?)\n"))
207 206 elif m in "zlib".split():
208 207 ui.warn(_("(is your Python install correct?)\n"))
209 208 except IOError as inst:
210 209 if util.safehasattr(inst, "code"):
211 210 ui.warn(_("abort: %s\n") % inst)
212 211 elif util.safehasattr(inst, "reason"):
213 212 try: # usually it is in the form (errno, strerror)
214 213 reason = inst.reason.args[1]
215 214 except (AttributeError, IndexError):
216 215 # it might be anything, for example a string
217 216 reason = inst.reason
218 217 if isinstance(reason, unicode):
219 218 # SSLError of Python 2.7.9 contains a unicode
220 219 reason = reason.encode(encoding.encoding, 'replace')
221 220 ui.warn(_("abort: error: %s\n") % reason)
222 221 elif (util.safehasattr(inst, "args")
223 222 and inst.args and inst.args[0] == errno.EPIPE):
224 223 pass
225 224 elif getattr(inst, "strerror", None):
226 225 if getattr(inst, "filename", None):
227 226 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
228 227 else:
229 228 ui.warn(_("abort: %s\n") % inst.strerror)
230 229 else:
231 230 raise
232 231 except OSError as inst:
233 232 if getattr(inst, "filename", None) is not None:
234 233 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
235 234 else:
236 235 ui.warn(_("abort: %s\n") % inst.strerror)
237 236 except MemoryError:
238 237 ui.warn(_("abort: out of memory\n"))
239 238 except SystemExit as inst:
240 239 # Commands shouldn't sys.exit directly, but give a return code.
241 240 # Just in case catch this and and pass exit code to caller.
242 241 return inst.code
243 242 except socket.error as inst:
244 243 ui.warn(_("abort: %s\n") % inst.args[-1])
245 244
246 245 return -1
247 246
248 247 def checknewlabel(repo, lbl, kind):
249 248 # Do not use the "kind" parameter in ui output.
250 249 # It makes strings difficult to translate.
251 250 if lbl in ['tip', '.', 'null']:
252 251 raise error.Abort(_("the name '%s' is reserved") % lbl)
253 252 for c in (':', '\0', '\n', '\r'):
254 253 if c in lbl:
255 254 raise error.Abort(_("%r cannot be used in a name") % c)
256 255 try:
257 256 int(lbl)
258 257 raise error.Abort(_("cannot use an integer as a name"))
259 258 except ValueError:
260 259 pass
261 260
262 261 def checkfilename(f):
263 262 '''Check that the filename f is an acceptable filename for a tracked file'''
264 263 if '\r' in f or '\n' in f:
265 264 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
266 265
267 266 def checkportable(ui, f):
268 267 '''Check if filename f is portable and warn or abort depending on config'''
269 268 checkfilename(f)
270 269 abort, warn = checkportabilityalert(ui)
271 270 if abort or warn:
272 271 msg = util.checkwinfilename(f)
273 272 if msg:
274 273 msg = "%s: %r" % (msg, f)
275 274 if abort:
276 275 raise error.Abort(msg)
277 276 ui.warn(_("warning: %s\n") % msg)
278 277
279 278 def checkportabilityalert(ui):
280 279 '''check if the user's config requests nothing, a warning, or abort for
281 280 non-portable filenames'''
282 281 val = ui.config('ui', 'portablefilenames', 'warn')
283 282 lval = val.lower()
284 283 bval = util.parsebool(val)
285 284 abort = pycompat.osname == 'nt' or lval == 'abort'
286 285 warn = bval or lval == 'warn'
287 286 if bval is None and not (warn or abort or lval == 'ignore'):
288 287 raise error.ConfigError(
289 288 _("ui.portablefilenames value is invalid ('%s')") % val)
290 289 return abort, warn
291 290
292 291 class casecollisionauditor(object):
293 292 def __init__(self, ui, abort, dirstate):
294 293 self._ui = ui
295 294 self._abort = abort
296 295 allfiles = '\0'.join(dirstate._map)
297 296 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
298 297 self._dirstate = dirstate
299 298 # The purpose of _newfiles is so that we don't complain about
300 299 # case collisions if someone were to call this object with the
301 300 # same filename twice.
302 301 self._newfiles = set()
303 302
304 303 def __call__(self, f):
305 304 if f in self._newfiles:
306 305 return
307 306 fl = encoding.lower(f)
308 307 if fl in self._loweredfiles and f not in self._dirstate:
309 308 msg = _('possible case-folding collision for %s') % f
310 309 if self._abort:
311 310 raise error.Abort(msg)
312 311 self._ui.warn(_("warning: %s\n") % msg)
313 312 self._loweredfiles.add(fl)
314 313 self._newfiles.add(f)
315 314
316 315 def filteredhash(repo, maxrev):
317 316 """build hash of filtered revisions in the current repoview.
318 317
319 318 Multiple caches perform up-to-date validation by checking that the
320 319 tiprev and tipnode stored in the cache file match the current repository.
321 320 However, this is not sufficient for validating repoviews because the set
322 321 of revisions in the view may change without the repository tiprev and
323 322 tipnode changing.
324 323
325 324 This function hashes all the revs filtered from the view and returns
326 325 that SHA-1 digest.
327 326 """
328 327 cl = repo.changelog
329 328 if not cl.filteredrevs:
330 329 return None
331 330 key = None
332 331 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
333 332 if revs:
334 333 s = hashlib.sha1()
335 334 for rev in revs:
336 335 s.update('%s;' % rev)
337 336 key = s.digest()
338 337 return key
339 338
340 339 class abstractvfs(object):
341 340 """Abstract base class; cannot be instantiated"""
342 341
343 342 def __init__(self, *args, **kwargs):
344 343 '''Prevent instantiation; don't call this from subclasses.'''
345 344 raise NotImplementedError('attempted instantiating ' + str(type(self)))
346 345
347 346 def tryread(self, path):
348 347 '''gracefully return an empty string for missing files'''
349 348 try:
350 349 return self.read(path)
351 350 except IOError as inst:
352 351 if inst.errno != errno.ENOENT:
353 352 raise
354 353 return ""
355 354
356 355 def tryreadlines(self, path, mode='rb'):
357 356 '''gracefully return an empty array for missing files'''
358 357 try:
359 358 return self.readlines(path, mode=mode)
360 359 except IOError as inst:
361 360 if inst.errno != errno.ENOENT:
362 361 raise
363 362 return []
364 363
365 364 @util.propertycache
366 365 def open(self):
367 366 '''Open ``path`` file, which is relative to vfs root.
368 367
369 368 Newly created directories are marked as "not to be indexed by
370 369 the content indexing service", if ``notindexed`` is specified
371 370 for "write" mode access.
372 371 '''
373 372 return self.__call__
374 373
375 374 def read(self, path):
376 375 with self(path, 'rb') as fp:
377 376 return fp.read()
378 377
379 378 def readlines(self, path, mode='rb'):
380 379 with self(path, mode=mode) as fp:
381 380 return fp.readlines()
382 381
383 382 def write(self, path, data, backgroundclose=False):
384 383 with self(path, 'wb', backgroundclose=backgroundclose) as fp:
385 384 return fp.write(data)
386 385
387 386 def writelines(self, path, data, mode='wb', notindexed=False):
388 387 with self(path, mode=mode, notindexed=notindexed) as fp:
389 388 return fp.writelines(data)
390 389
391 390 def append(self, path, data):
392 391 with self(path, 'ab') as fp:
393 392 return fp.write(data)
394 393
395 394 def basename(self, path):
396 395 """return base element of a path (as os.path.basename would do)
397 396
398 397 This exists to allow handling of strange encoding if needed."""
399 398 return os.path.basename(path)
400 399
401 400 def chmod(self, path, mode):
402 401 return os.chmod(self.join(path), mode)
403 402
404 403 def dirname(self, path):
405 404 """return dirname element of a path (as os.path.dirname would do)
406 405
407 406 This exists to allow handling of strange encoding if needed."""
408 407 return os.path.dirname(path)
409 408
410 409 def exists(self, path=None):
411 410 return os.path.exists(self.join(path))
412 411
413 412 def fstat(self, fp):
414 413 return util.fstat(fp)
415 414
416 415 def isdir(self, path=None):
417 416 return os.path.isdir(self.join(path))
418 417
419 418 def isfile(self, path=None):
420 419 return os.path.isfile(self.join(path))
421 420
422 421 def islink(self, path=None):
423 422 return os.path.islink(self.join(path))
424 423
425 424 def isfileorlink(self, path=None):
426 425 '''return whether path is a regular file or a symlink
427 426
428 427 Unlike isfile, this doesn't follow symlinks.'''
429 428 try:
430 429 st = self.lstat(path)
431 430 except OSError:
432 431 return False
433 432 mode = st.st_mode
434 433 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
435 434
436 435 def reljoin(self, *paths):
437 436 """join various elements of a path together (as os.path.join would do)
438 437
439 438 The vfs base is not injected so that path stay relative. This exists
440 439 to allow handling of strange encoding if needed."""
441 440 return os.path.join(*paths)
442 441
443 442 def split(self, path):
444 443 """split top-most element of a path (as os.path.split would do)
445 444
446 445 This exists to allow handling of strange encoding if needed."""
447 446 return os.path.split(path)
448 447
449 448 def lexists(self, path=None):
450 449 return os.path.lexists(self.join(path))
451 450
452 451 def lstat(self, path=None):
453 452 return os.lstat(self.join(path))
454 453
455 454 def listdir(self, path=None):
456 455 return os.listdir(self.join(path))
457 456
458 457 def makedir(self, path=None, notindexed=True):
459 458 return util.makedir(self.join(path), notindexed)
460 459
461 460 def makedirs(self, path=None, mode=None):
462 461 return util.makedirs(self.join(path), mode)
463 462
464 463 def makelock(self, info, path):
465 464 return util.makelock(info, self.join(path))
466 465
467 466 def mkdir(self, path=None):
468 467 return os.mkdir(self.join(path))
469 468
470 469 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
471 470 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
472 471 dir=self.join(dir), text=text)
473 472 dname, fname = util.split(name)
474 473 if dir:
475 474 return fd, os.path.join(dir, fname)
476 475 else:
477 476 return fd, fname
478 477
479 478 def readdir(self, path=None, stat=None, skip=None):
480 479 return osutil.listdir(self.join(path), stat, skip)
481 480
482 481 def readlock(self, path):
483 482 return util.readlock(self.join(path))
484 483
485 484 def rename(self, src, dst, checkambig=False):
486 485 """Rename from src to dst
487 486
488 487 checkambig argument is used with util.filestat, and is useful
489 488 only if destination file is guarded by any lock
490 489 (e.g. repo.lock or repo.wlock).
491 490 """
492 491 dstpath = self.join(dst)
493 492 oldstat = checkambig and util.filestat(dstpath)
494 493 if oldstat and oldstat.stat:
495 494 ret = util.rename(self.join(src), dstpath)
496 495 newstat = util.filestat(dstpath)
497 496 if newstat.isambig(oldstat):
498 497 # stat of renamed file is ambiguous to original one
499 498 newstat.avoidambig(dstpath, oldstat)
500 499 return ret
501 500 return util.rename(self.join(src), dstpath)
502 501
503 502 def readlink(self, path):
504 503 return os.readlink(self.join(path))
505 504
506 505 def removedirs(self, path=None):
507 506 """Remove a leaf directory and all empty intermediate ones
508 507 """
509 508 return util.removedirs(self.join(path))
510 509
511 510 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
512 511 """Remove a directory tree recursively
513 512
514 513 If ``forcibly``, this tries to remove READ-ONLY files, too.
515 514 """
516 515 if forcibly:
517 516 def onerror(function, path, excinfo):
518 517 if function is not os.remove:
519 518 raise
520 519 # read-only files cannot be unlinked under Windows
521 520 s = os.stat(path)
522 521 if (s.st_mode & stat.S_IWRITE) != 0:
523 522 raise
524 523 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
525 524 os.remove(path)
526 525 else:
527 526 onerror = None
528 527 return shutil.rmtree(self.join(path),
529 528 ignore_errors=ignore_errors, onerror=onerror)
530 529
531 530 def setflags(self, path, l, x):
532 531 return util.setflags(self.join(path), l, x)
533 532
534 533 def stat(self, path=None):
535 534 return os.stat(self.join(path))
536 535
537 536 def unlink(self, path=None):
538 537 return util.unlink(self.join(path))
539 538
540 539 def unlinkpath(self, path=None, ignoremissing=False):
541 540 return util.unlinkpath(self.join(path), ignoremissing)
542 541
543 542 def utime(self, path=None, t=None):
544 543 return os.utime(self.join(path), t)
545 544
546 545 def walk(self, path=None, onerror=None):
547 546 """Yield (dirpath, dirs, files) tuple for each directories under path
548 547
549 548 ``dirpath`` is relative one from the root of this vfs. This
550 549 uses ``os.sep`` as path separator, even you specify POSIX
551 550 style ``path``.
552 551
553 552 "The root of this vfs" is represented as empty ``dirpath``.
554 553 """
555 554 root = os.path.normpath(self.join(None))
556 555 # when dirpath == root, dirpath[prefixlen:] becomes empty
557 556 # because len(dirpath) < prefixlen.
558 557 prefixlen = len(pathutil.normasprefix(root))
559 558 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
560 559 yield (dirpath[prefixlen:], dirs, files)
561 560
562 561 @contextlib.contextmanager
563 562 def backgroundclosing(self, ui, expectedcount=-1):
564 563 """Allow files to be closed asynchronously.
565 564
566 565 When this context manager is active, ``backgroundclose`` can be passed
567 566 to ``__call__``/``open`` to result in the file possibly being closed
568 567 asynchronously, on a background thread.
569 568 """
570 569 # This is an arbitrary restriction and could be changed if we ever
571 570 # have a use case.
572 571 vfs = getattr(self, 'vfs', self)
573 572 if getattr(vfs, '_backgroundfilecloser', None):
574 573 raise error.Abort(
575 574 _('can only have 1 active background file closer'))
576 575
577 576 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
578 577 try:
579 578 vfs._backgroundfilecloser = bfc
580 579 yield bfc
581 580 finally:
582 581 vfs._backgroundfilecloser = None
583 582
584 583 class vfs(abstractvfs):
585 584 '''Operate files relative to a base directory
586 585
587 586 This class is used to hide the details of COW semantics and
588 587 remote file access from higher level code.
589 588 '''
590 589 def __init__(self, base, audit=True, expandpath=False, realpath=False):
591 590 if expandpath:
592 591 base = util.expandpath(base)
593 592 if realpath:
594 593 base = os.path.realpath(base)
595 594 self.base = base
596 595 self.mustaudit = audit
597 596 self.createmode = None
598 597 self._trustnlink = None
599 598
600 599 @property
601 600 def mustaudit(self):
602 601 return self._audit
603 602
604 603 @mustaudit.setter
605 604 def mustaudit(self, onoff):
606 605 self._audit = onoff
607 606 if onoff:
608 607 self.audit = pathutil.pathauditor(self.base)
609 608 else:
610 609 self.audit = util.always
611 610
612 611 @util.propertycache
613 612 def _cansymlink(self):
614 613 return util.checklink(self.base)
615 614
616 615 @util.propertycache
617 616 def _chmod(self):
618 617 return util.checkexec(self.base)
619 618
620 619 def _fixfilemode(self, name):
621 620 if self.createmode is None or not self._chmod:
622 621 return
623 622 os.chmod(name, self.createmode & 0o666)
624 623
625 624 def __call__(self, path, mode="r", text=False, atomictemp=False,
626 625 notindexed=False, backgroundclose=False, checkambig=False):
627 626 '''Open ``path`` file, which is relative to vfs root.
628 627
629 628 Newly created directories are marked as "not to be indexed by
630 629 the content indexing service", if ``notindexed`` is specified
631 630 for "write" mode access.
632 631
633 632 If ``backgroundclose`` is passed, the file may be closed asynchronously.
634 633 It can only be used if the ``self.backgroundclosing()`` context manager
635 634 is active. This should only be specified if the following criteria hold:
636 635
637 636 1. There is a potential for writing thousands of files. Unless you
638 637 are writing thousands of files, the performance benefits of
639 638 asynchronously closing files is not realized.
640 639 2. Files are opened exactly once for the ``backgroundclosing``
641 640 active duration and are therefore free of race conditions between
642 641 closing a file on a background thread and reopening it. (If the
643 642 file were opened multiple times, there could be unflushed data
644 643 because the original file handle hasn't been flushed/closed yet.)
645 644
646 645 ``checkambig`` argument is passed to atomictemplfile (valid
647 646 only for writing), and is useful only if target file is
648 647 guarded by any lock (e.g. repo.lock or repo.wlock).
649 648 '''
650 649 if self._audit:
651 650 r = util.checkosfilename(path)
652 651 if r:
653 652 raise error.Abort("%s: %r" % (r, path))
654 653 self.audit(path)
655 654 f = self.join(path)
656 655
657 656 if not text and "b" not in mode:
658 657 mode += "b" # for that other OS
659 658
660 659 nlink = -1
661 660 if mode not in ('r', 'rb'):
662 661 dirname, basename = util.split(f)
663 662 # If basename is empty, then the path is malformed because it points
664 663 # to a directory. Let the posixfile() call below raise IOError.
665 664 if basename:
666 665 if atomictemp:
667 666 util.makedirs(dirname, self.createmode, notindexed)
668 667 return util.atomictempfile(f, mode, self.createmode,
669 668 checkambig=checkambig)
670 669 try:
671 670 if 'w' in mode:
672 671 util.unlink(f)
673 672 nlink = 0
674 673 else:
675 674 # nlinks() may behave differently for files on Windows
676 675 # shares if the file is open.
677 676 with util.posixfile(f):
678 677 nlink = util.nlinks(f)
679 678 if nlink < 1:
680 679 nlink = 2 # force mktempcopy (issue1922)
681 680 except (OSError, IOError) as e:
682 681 if e.errno != errno.ENOENT:
683 682 raise
684 683 nlink = 0
685 684 util.makedirs(dirname, self.createmode, notindexed)
686 685 if nlink > 0:
687 686 if self._trustnlink is None:
688 687 self._trustnlink = nlink > 1 or util.checknlink(f)
689 688 if nlink > 1 or not self._trustnlink:
690 689 util.rename(util.mktempcopy(f), f)
691 690 fp = util.posixfile(f, mode)
692 691 if nlink == 0:
693 692 self._fixfilemode(f)
694 693
695 694 if checkambig:
696 695 if mode in ('r', 'rb'):
697 696 raise error.Abort(_('implementation error: mode %s is not'
698 697 ' valid for checkambig=True') % mode)
699 698 fp = checkambigatclosing(fp)
700 699
701 700 if backgroundclose:
702 701 if not self._backgroundfilecloser:
703 702 raise error.Abort(_('backgroundclose can only be used when a '
704 703 'backgroundclosing context manager is active')
705 704 )
706 705
707 706 fp = delayclosedfile(fp, self._backgroundfilecloser)
708 707
709 708 return fp
710 709
711 710 def symlink(self, src, dst):
712 711 self.audit(dst)
713 712 linkname = self.join(dst)
714 713 try:
715 714 os.unlink(linkname)
716 715 except OSError:
717 716 pass
718 717
719 718 util.makedirs(os.path.dirname(linkname), self.createmode)
720 719
721 720 if self._cansymlink:
722 721 try:
723 722 os.symlink(src, linkname)
724 723 except OSError as err:
725 724 raise OSError(err.errno, _('could not symlink to %r: %s') %
726 725 (src, err.strerror), linkname)
727 726 else:
728 727 self.write(dst, src)
729 728
730 729 def join(self, path, *insidef):
731 730 if path:
732 731 return os.path.join(self.base, path, *insidef)
733 732 else:
734 733 return self.base
735 734
736 735 opener = vfs
737 736
738 737 class auditvfs(object):
739 738 def __init__(self, vfs):
740 739 self.vfs = vfs
741 740
742 741 @property
743 742 def mustaudit(self):
744 743 return self.vfs.mustaudit
745 744
746 745 @mustaudit.setter
747 746 def mustaudit(self, onoff):
748 747 self.vfs.mustaudit = onoff
749 748
750 749 @property
751 750 def options(self):
752 751 return self.vfs.options
753 752
754 753 @options.setter
755 754 def options(self, value):
756 755 self.vfs.options = value
757 756
758 757 class filtervfs(abstractvfs, auditvfs):
759 758 '''Wrapper vfs for filtering filenames with a function.'''
760 759
761 760 def __init__(self, vfs, filter):
762 761 auditvfs.__init__(self, vfs)
763 762 self._filter = filter
764 763
765 764 def __call__(self, path, *args, **kwargs):
766 765 return self.vfs(self._filter(path), *args, **kwargs)
767 766
768 767 def join(self, path, *insidef):
769 768 if path:
770 769 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
771 770 else:
772 771 return self.vfs.join(path)
773 772
774 773 filteropener = filtervfs
775 774
776 775 class readonlyvfs(abstractvfs, auditvfs):
777 776 '''Wrapper vfs preventing any writing.'''
778 777
779 778 def __init__(self, vfs):
780 779 auditvfs.__init__(self, vfs)
781 780
782 781 def __call__(self, path, mode='r', *args, **kw):
783 782 if mode not in ('r', 'rb'):
784 783 raise error.Abort(_('this vfs is read only'))
785 784 return self.vfs(path, mode, *args, **kw)
786 785
787 786 def join(self, path, *insidef):
788 787 return self.vfs.join(path, *insidef)
789 788
790 789 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
791 790 '''yield every hg repository under path, always recursively.
792 791 The recurse flag will only control recursion into repo working dirs'''
793 792 def errhandler(err):
794 793 if err.filename == path:
795 794 raise err
796 795 samestat = getattr(os.path, 'samestat', None)
797 796 if followsym and samestat is not None:
798 797 def adddir(dirlst, dirname):
799 798 match = False
800 799 dirstat = os.stat(dirname)
801 800 for lstdirstat in dirlst:
802 801 if samestat(dirstat, lstdirstat):
803 802 match = True
804 803 break
805 804 if not match:
806 805 dirlst.append(dirstat)
807 806 return not match
808 807 else:
809 808 followsym = False
810 809
811 810 if (seen_dirs is None) and followsym:
812 811 seen_dirs = []
813 812 adddir(seen_dirs, path)
814 813 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
815 814 dirs.sort()
816 815 if '.hg' in dirs:
817 816 yield root # found a repository
818 817 qroot = os.path.join(root, '.hg', 'patches')
819 818 if os.path.isdir(os.path.join(qroot, '.hg')):
820 819 yield qroot # we have a patch queue repo here
821 820 if recurse:
822 821 # avoid recursing inside the .hg directory
823 822 dirs.remove('.hg')
824 823 else:
825 824 dirs[:] = [] # don't descend further
826 825 elif followsym:
827 826 newdirs = []
828 827 for d in dirs:
829 828 fname = os.path.join(root, d)
830 829 if adddir(seen_dirs, fname):
831 830 if os.path.islink(fname):
832 831 for hgname in walkrepos(fname, True, seen_dirs):
833 832 yield hgname
834 833 else:
835 834 newdirs.append(d)
836 835 dirs[:] = newdirs
837 836
838 837 def osrcpath():
839 838 '''return default os-specific hgrc search path'''
840 839 path = []
841 840 defaultpath = os.path.join(util.datapath, 'default.d')
842 841 if os.path.isdir(defaultpath):
843 842 for f, kind in osutil.listdir(defaultpath):
844 843 if f.endswith('.rc'):
845 844 path.append(os.path.join(defaultpath, f))
846 845 path.extend(systemrcpath())
847 846 path.extend(userrcpath())
848 847 path = [os.path.normpath(f) for f in path]
849 848 return path
850 849
851 850 _rcpath = None
852 851
853 852 def rcpath():
854 853 '''return hgrc search path. if env var HGRCPATH is set, use it.
855 854 for each item in path, if directory, use files ending in .rc,
856 855 else use item.
857 856 make HGRCPATH empty to only look in .hg/hgrc of current repo.
858 857 if no HGRCPATH, use default os-specific path.'''
859 858 global _rcpath
860 859 if _rcpath is None:
861 860 if 'HGRCPATH' in encoding.environ:
862 861 _rcpath = []
863 862 for p in encoding.environ['HGRCPATH'].split(pycompat.ospathsep):
864 863 if not p:
865 864 continue
866 865 p = util.expandpath(p)
867 866 if os.path.isdir(p):
868 867 for f, kind in osutil.listdir(p):
869 868 if f.endswith('.rc'):
870 869 _rcpath.append(os.path.join(p, f))
871 870 else:
872 871 _rcpath.append(p)
873 872 else:
874 873 _rcpath = osrcpath()
875 874 return _rcpath
876 875
877 876 def intrev(rev):
878 877 """Return integer for a given revision that can be used in comparison or
879 878 arithmetic operation"""
880 879 if rev is None:
881 880 return wdirrev
882 881 return rev
883 882
884 883 def revsingle(repo, revspec, default='.'):
885 884 if not revspec and revspec != 0:
886 885 return repo[default]
887 886
888 887 l = revrange(repo, [revspec])
889 888 if not l:
890 889 raise error.Abort(_('empty revision set'))
891 890 return repo[l.last()]
892 891
893 892 def _pairspec(revspec):
894 893 tree = revsetlang.parse(revspec)
895 894 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
896 895
897 896 def revpair(repo, revs):
898 897 if not revs:
899 898 return repo.dirstate.p1(), None
900 899
901 900 l = revrange(repo, revs)
902 901
903 902 if not l:
904 903 first = second = None
905 904 elif l.isascending():
906 905 first = l.min()
907 906 second = l.max()
908 907 elif l.isdescending():
909 908 first = l.max()
910 909 second = l.min()
911 910 else:
912 911 first = l.first()
913 912 second = l.last()
914 913
915 914 if first is None:
916 915 raise error.Abort(_('empty revision range'))
917 916 if (first == second and len(revs) >= 2
918 917 and not all(revrange(repo, [r]) for r in revs)):
919 918 raise error.Abort(_('empty revision on one side of range'))
920 919
921 920 # if top-level is range expression, the result must always be a pair
922 921 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
923 922 return repo.lookup(first), None
924 923
925 924 return repo.lookup(first), repo.lookup(second)
926 925
927 926 def revrange(repo, specs):
928 927 """Execute 1 to many revsets and return the union.
929 928
930 929 This is the preferred mechanism for executing revsets using user-specified
931 930 config options, such as revset aliases.
932 931
933 932 The revsets specified by ``specs`` will be executed via a chained ``OR``
934 933 expression. If ``specs`` is empty, an empty result is returned.
935 934
936 935 ``specs`` can contain integers, in which case they are assumed to be
937 936 revision numbers.
938 937
939 938 It is assumed the revsets are already formatted. If you have arguments
940 939 that need to be expanded in the revset, call ``revsetlang.formatspec()``
941 940 and pass the result as an element of ``specs``.
942 941
943 942 Specifying a single revset is allowed.
944 943
945 944 Returns a ``revset.abstractsmartset`` which is a list-like interface over
946 945 integer revisions.
947 946 """
948 947 allspecs = []
949 948 for spec in specs:
950 949 if isinstance(spec, int):
951 950 spec = revsetlang.formatspec('rev(%d)', spec)
952 951 allspecs.append(spec)
953 m = revset.matchany(repo.ui, allspecs, repo)
954 return m(repo)
952 return repo.anyrevs(allspecs, user=True)
955 953
956 954 def meaningfulparents(repo, ctx):
957 955 """Return list of meaningful (or all if debug) parentrevs for rev.
958 956
959 957 For merges (two non-nullrev revisions) both parents are meaningful.
960 958 Otherwise the first parent revision is considered meaningful if it
961 959 is not the preceding revision.
962 960 """
963 961 parents = ctx.parents()
964 962 if len(parents) > 1:
965 963 return parents
966 964 if repo.ui.debugflag:
967 965 return [parents[0], repo['null']]
968 966 if parents[0].rev() >= intrev(ctx.rev()) - 1:
969 967 return []
970 968 return parents
971 969
972 970 def expandpats(pats):
973 971 '''Expand bare globs when running on windows.
974 972 On posix we assume it already has already been done by sh.'''
975 973 if not util.expandglobs:
976 974 return list(pats)
977 975 ret = []
978 976 for kindpat in pats:
979 977 kind, pat = matchmod._patsplit(kindpat, None)
980 978 if kind is None:
981 979 try:
982 980 globbed = glob.glob(pat)
983 981 except re.error:
984 982 globbed = [pat]
985 983 if globbed:
986 984 ret.extend(globbed)
987 985 continue
988 986 ret.append(kindpat)
989 987 return ret
990 988
991 989 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
992 990 badfn=None):
993 991 '''Return a matcher and the patterns that were used.
994 992 The matcher will warn about bad matches, unless an alternate badfn callback
995 993 is provided.'''
996 994 if pats == ("",):
997 995 pats = []
998 996 if opts is None:
999 997 opts = {}
1000 998 if not globbed and default == 'relpath':
1001 999 pats = expandpats(pats or [])
1002 1000
1003 1001 def bad(f, msg):
1004 1002 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
1005 1003
1006 1004 if badfn is None:
1007 1005 badfn = bad
1008 1006
1009 1007 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
1010 1008 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
1011 1009
1012 1010 if m.always():
1013 1011 pats = []
1014 1012 return m, pats
1015 1013
1016 1014 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
1017 1015 badfn=None):
1018 1016 '''Return a matcher that will warn about bad matches.'''
1019 1017 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
1020 1018
1021 1019 def matchall(repo):
1022 1020 '''Return a matcher that will efficiently match everything.'''
1023 1021 return matchmod.always(repo.root, repo.getcwd())
1024 1022
1025 1023 def matchfiles(repo, files, badfn=None):
1026 1024 '''Return a matcher that will efficiently match exactly these files.'''
1027 1025 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
1028 1026
1029 1027 def origpath(ui, repo, filepath):
1030 1028 '''customize where .orig files are created
1031 1029
1032 1030 Fetch user defined path from config file: [ui] origbackuppath = <path>
1033 1031 Fall back to default (filepath) if not specified
1034 1032 '''
1035 1033 origbackuppath = ui.config('ui', 'origbackuppath', None)
1036 1034 if origbackuppath is None:
1037 1035 return filepath + ".orig"
1038 1036
1039 1037 filepathfromroot = os.path.relpath(filepath, start=repo.root)
1040 1038 fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
1041 1039
1042 1040 origbackupdir = repo.vfs.dirname(fullorigpath)
1043 1041 if not repo.vfs.exists(origbackupdir):
1044 1042 ui.note(_('creating directory: %s\n') % origbackupdir)
1045 1043 util.makedirs(origbackupdir)
1046 1044
1047 1045 return fullorigpath + ".orig"
1048 1046
1049 1047 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
1050 1048 if opts is None:
1051 1049 opts = {}
1052 1050 m = matcher
1053 1051 if dry_run is None:
1054 1052 dry_run = opts.get('dry_run')
1055 1053 if similarity is None:
1056 1054 similarity = float(opts.get('similarity') or 0)
1057 1055
1058 1056 ret = 0
1059 1057 join = lambda f: os.path.join(prefix, f)
1060 1058
1061 1059 wctx = repo[None]
1062 1060 for subpath in sorted(wctx.substate):
1063 1061 submatch = matchmod.subdirmatcher(subpath, m)
1064 1062 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1065 1063 sub = wctx.sub(subpath)
1066 1064 try:
1067 1065 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
1068 1066 ret = 1
1069 1067 except error.LookupError:
1070 1068 repo.ui.status(_("skipping missing subrepository: %s\n")
1071 1069 % join(subpath))
1072 1070
1073 1071 rejected = []
1074 1072 def badfn(f, msg):
1075 1073 if f in m.files():
1076 1074 m.bad(f, msg)
1077 1075 rejected.append(f)
1078 1076
1079 1077 badmatch = matchmod.badmatch(m, badfn)
1080 1078 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1081 1079 badmatch)
1082 1080
1083 1081 unknownset = set(unknown + forgotten)
1084 1082 toprint = unknownset.copy()
1085 1083 toprint.update(deleted)
1086 1084 for abs in sorted(toprint):
1087 1085 if repo.ui.verbose or not m.exact(abs):
1088 1086 if abs in unknownset:
1089 1087 status = _('adding %s\n') % m.uipath(abs)
1090 1088 else:
1091 1089 status = _('removing %s\n') % m.uipath(abs)
1092 1090 repo.ui.status(status)
1093 1091
1094 1092 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1095 1093 similarity)
1096 1094
1097 1095 if not dry_run:
1098 1096 _markchanges(repo, unknown + forgotten, deleted, renames)
1099 1097
1100 1098 for f in rejected:
1101 1099 if f in m.files():
1102 1100 return 1
1103 1101 return ret
1104 1102
1105 1103 def marktouched(repo, files, similarity=0.0):
1106 1104 '''Assert that files have somehow been operated upon. files are relative to
1107 1105 the repo root.'''
1108 1106 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1109 1107 rejected = []
1110 1108
1111 1109 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1112 1110
1113 1111 if repo.ui.verbose:
1114 1112 unknownset = set(unknown + forgotten)
1115 1113 toprint = unknownset.copy()
1116 1114 toprint.update(deleted)
1117 1115 for abs in sorted(toprint):
1118 1116 if abs in unknownset:
1119 1117 status = _('adding %s\n') % abs
1120 1118 else:
1121 1119 status = _('removing %s\n') % abs
1122 1120 repo.ui.status(status)
1123 1121
1124 1122 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1125 1123 similarity)
1126 1124
1127 1125 _markchanges(repo, unknown + forgotten, deleted, renames)
1128 1126
1129 1127 for f in rejected:
1130 1128 if f in m.files():
1131 1129 return 1
1132 1130 return 0
1133 1131
1134 1132 def _interestingfiles(repo, matcher):
1135 1133 '''Walk dirstate with matcher, looking for files that addremove would care
1136 1134 about.
1137 1135
1138 1136 This is different from dirstate.status because it doesn't care about
1139 1137 whether files are modified or clean.'''
1140 1138 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1141 1139 audit_path = pathutil.pathauditor(repo.root)
1142 1140
1143 1141 ctx = repo[None]
1144 1142 dirstate = repo.dirstate
1145 1143 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
1146 1144 full=False)
1147 1145 for abs, st in walkresults.iteritems():
1148 1146 dstate = dirstate[abs]
1149 1147 if dstate == '?' and audit_path.check(abs):
1150 1148 unknown.append(abs)
1151 1149 elif dstate != 'r' and not st:
1152 1150 deleted.append(abs)
1153 1151 elif dstate == 'r' and st:
1154 1152 forgotten.append(abs)
1155 1153 # for finding renames
1156 1154 elif dstate == 'r' and not st:
1157 1155 removed.append(abs)
1158 1156 elif dstate == 'a':
1159 1157 added.append(abs)
1160 1158
1161 1159 return added, unknown, deleted, removed, forgotten
1162 1160
1163 1161 def _findrenames(repo, matcher, added, removed, similarity):
1164 1162 '''Find renames from removed files to added ones.'''
1165 1163 renames = {}
1166 1164 if similarity > 0:
1167 1165 for old, new, score in similar.findrenames(repo, added, removed,
1168 1166 similarity):
1169 1167 if (repo.ui.verbose or not matcher.exact(old)
1170 1168 or not matcher.exact(new)):
1171 1169 repo.ui.status(_('recording removal of %s as rename to %s '
1172 1170 '(%d%% similar)\n') %
1173 1171 (matcher.rel(old), matcher.rel(new),
1174 1172 score * 100))
1175 1173 renames[new] = old
1176 1174 return renames
1177 1175
1178 1176 def _markchanges(repo, unknown, deleted, renames):
1179 1177 '''Marks the files in unknown as added, the files in deleted as removed,
1180 1178 and the files in renames as copied.'''
1181 1179 wctx = repo[None]
1182 1180 with repo.wlock():
1183 1181 wctx.forget(deleted)
1184 1182 wctx.add(unknown)
1185 1183 for new, old in renames.iteritems():
1186 1184 wctx.copy(old, new)
1187 1185
1188 1186 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1189 1187 """Update the dirstate to reflect the intent of copying src to dst. For
1190 1188 different reasons it might not end with dst being marked as copied from src.
1191 1189 """
1192 1190 origsrc = repo.dirstate.copied(src) or src
1193 1191 if dst == origsrc: # copying back a copy?
1194 1192 if repo.dirstate[dst] not in 'mn' and not dryrun:
1195 1193 repo.dirstate.normallookup(dst)
1196 1194 else:
1197 1195 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1198 1196 if not ui.quiet:
1199 1197 ui.warn(_("%s has not been committed yet, so no copy "
1200 1198 "data will be stored for %s.\n")
1201 1199 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1202 1200 if repo.dirstate[dst] in '?r' and not dryrun:
1203 1201 wctx.add([dst])
1204 1202 elif not dryrun:
1205 1203 wctx.copy(origsrc, dst)
1206 1204
1207 1205 def readrequires(opener, supported):
1208 1206 '''Reads and parses .hg/requires and checks if all entries found
1209 1207 are in the list of supported features.'''
1210 1208 requirements = set(opener.read("requires").splitlines())
1211 1209 missings = []
1212 1210 for r in requirements:
1213 1211 if r not in supported:
1214 1212 if not r or not r[0].isalnum():
1215 1213 raise error.RequirementError(_(".hg/requires file is corrupt"))
1216 1214 missings.append(r)
1217 1215 missings.sort()
1218 1216 if missings:
1219 1217 raise error.RequirementError(
1220 1218 _("repository requires features unknown to this Mercurial: %s")
1221 1219 % " ".join(missings),
1222 1220 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
1223 1221 " for more information"))
1224 1222 return requirements
1225 1223
1226 1224 def writerequires(opener, requirements):
1227 1225 with opener('requires', 'w') as fp:
1228 1226 for r in sorted(requirements):
1229 1227 fp.write("%s\n" % r)
1230 1228
1231 1229 class filecachesubentry(object):
1232 1230 def __init__(self, path, stat):
1233 1231 self.path = path
1234 1232 self.cachestat = None
1235 1233 self._cacheable = None
1236 1234
1237 1235 if stat:
1238 1236 self.cachestat = filecachesubentry.stat(self.path)
1239 1237
1240 1238 if self.cachestat:
1241 1239 self._cacheable = self.cachestat.cacheable()
1242 1240 else:
1243 1241 # None means we don't know yet
1244 1242 self._cacheable = None
1245 1243
1246 1244 def refresh(self):
1247 1245 if self.cacheable():
1248 1246 self.cachestat = filecachesubentry.stat(self.path)
1249 1247
1250 1248 def cacheable(self):
1251 1249 if self._cacheable is not None:
1252 1250 return self._cacheable
1253 1251
1254 1252 # we don't know yet, assume it is for now
1255 1253 return True
1256 1254
1257 1255 def changed(self):
1258 1256 # no point in going further if we can't cache it
1259 1257 if not self.cacheable():
1260 1258 return True
1261 1259
1262 1260 newstat = filecachesubentry.stat(self.path)
1263 1261
1264 1262 # we may not know if it's cacheable yet, check again now
1265 1263 if newstat and self._cacheable is None:
1266 1264 self._cacheable = newstat.cacheable()
1267 1265
1268 1266 # check again
1269 1267 if not self._cacheable:
1270 1268 return True
1271 1269
1272 1270 if self.cachestat != newstat:
1273 1271 self.cachestat = newstat
1274 1272 return True
1275 1273 else:
1276 1274 return False
1277 1275
1278 1276 @staticmethod
1279 1277 def stat(path):
1280 1278 try:
1281 1279 return util.cachestat(path)
1282 1280 except OSError as e:
1283 1281 if e.errno != errno.ENOENT:
1284 1282 raise
1285 1283
1286 1284 class filecacheentry(object):
1287 1285 def __init__(self, paths, stat=True):
1288 1286 self._entries = []
1289 1287 for path in paths:
1290 1288 self._entries.append(filecachesubentry(path, stat))
1291 1289
1292 1290 def changed(self):
1293 1291 '''true if any entry has changed'''
1294 1292 for entry in self._entries:
1295 1293 if entry.changed():
1296 1294 return True
1297 1295 return False
1298 1296
1299 1297 def refresh(self):
1300 1298 for entry in self._entries:
1301 1299 entry.refresh()
1302 1300
1303 1301 class filecache(object):
1304 1302 '''A property like decorator that tracks files under .hg/ for updates.
1305 1303
1306 1304 Records stat info when called in _filecache.
1307 1305
1308 1306 On subsequent calls, compares old stat info with new info, and recreates the
1309 1307 object when any of the files changes, updating the new stat info in
1310 1308 _filecache.
1311 1309
1312 1310 Mercurial either atomic renames or appends for files under .hg,
1313 1311 so to ensure the cache is reliable we need the filesystem to be able
1314 1312 to tell us if a file has been replaced. If it can't, we fallback to
1315 1313 recreating the object on every call (essentially the same behavior as
1316 1314 propertycache).
1317 1315
1318 1316 '''
1319 1317 def __init__(self, *paths):
1320 1318 self.paths = paths
1321 1319
1322 1320 def join(self, obj, fname):
1323 1321 """Used to compute the runtime path of a cached file.
1324 1322
1325 1323 Users should subclass filecache and provide their own version of this
1326 1324 function to call the appropriate join function on 'obj' (an instance
1327 1325 of the class that its member function was decorated).
1328 1326 """
1329 1327 return obj.join(fname)
1330 1328
1331 1329 def __call__(self, func):
1332 1330 self.func = func
1333 1331 self.name = func.__name__
1334 1332 return self
1335 1333
1336 1334 def __get__(self, obj, type=None):
1337 1335 # if accessed on the class, return the descriptor itself.
1338 1336 if obj is None:
1339 1337 return self
1340 1338 # do we need to check if the file changed?
1341 1339 if self.name in obj.__dict__:
1342 1340 assert self.name in obj._filecache, self.name
1343 1341 return obj.__dict__[self.name]
1344 1342
1345 1343 entry = obj._filecache.get(self.name)
1346 1344
1347 1345 if entry:
1348 1346 if entry.changed():
1349 1347 entry.obj = self.func(obj)
1350 1348 else:
1351 1349 paths = [self.join(obj, path) for path in self.paths]
1352 1350
1353 1351 # We stat -before- creating the object so our cache doesn't lie if
1354 1352 # a writer modified between the time we read and stat
1355 1353 entry = filecacheentry(paths, True)
1356 1354 entry.obj = self.func(obj)
1357 1355
1358 1356 obj._filecache[self.name] = entry
1359 1357
1360 1358 obj.__dict__[self.name] = entry.obj
1361 1359 return entry.obj
1362 1360
1363 1361 def __set__(self, obj, value):
1364 1362 if self.name not in obj._filecache:
1365 1363 # we add an entry for the missing value because X in __dict__
1366 1364 # implies X in _filecache
1367 1365 paths = [self.join(obj, path) for path in self.paths]
1368 1366 ce = filecacheentry(paths, False)
1369 1367 obj._filecache[self.name] = ce
1370 1368 else:
1371 1369 ce = obj._filecache[self.name]
1372 1370
1373 1371 ce.obj = value # update cached copy
1374 1372 obj.__dict__[self.name] = value # update copy returned by obj.x
1375 1373
1376 1374 def __delete__(self, obj):
1377 1375 try:
1378 1376 del obj.__dict__[self.name]
1379 1377 except KeyError:
1380 1378 raise AttributeError(self.name)
1381 1379
1382 1380 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1383 1381 if lock is None:
1384 1382 raise error.LockInheritanceContractViolation(
1385 1383 'lock can only be inherited while held')
1386 1384 if environ is None:
1387 1385 environ = {}
1388 1386 with lock.inherit() as locker:
1389 1387 environ[envvar] = locker
1390 1388 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1391 1389
1392 1390 def wlocksub(repo, cmd, *args, **kwargs):
1393 1391 """run cmd as a subprocess that allows inheriting repo's wlock
1394 1392
1395 1393 This can only be called while the wlock is held. This takes all the
1396 1394 arguments that ui.system does, and returns the exit code of the
1397 1395 subprocess."""
1398 1396 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1399 1397 **kwargs)
1400 1398
1401 1399 def gdinitconfig(ui):
1402 1400 """helper function to know if a repo should be created as general delta
1403 1401 """
1404 1402 # experimental config: format.generaldelta
1405 1403 return (ui.configbool('format', 'generaldelta', False)
1406 1404 or ui.configbool('format', 'usegeneraldelta', True))
1407 1405
1408 1406 def gddeltaconfig(ui):
1409 1407 """helper function to know if incoming delta should be optimised
1410 1408 """
1411 1409 # experimental config: format.generaldelta
1412 1410 return ui.configbool('format', 'generaldelta', False)
1413 1411
1414 1412 class closewrapbase(object):
1415 1413 """Base class of wrapper, which hooks closing
1416 1414
1417 1415 Do not instantiate outside of the vfs layer.
1418 1416 """
1419 1417 def __init__(self, fh):
1420 1418 object.__setattr__(self, '_origfh', fh)
1421 1419
1422 1420 def __getattr__(self, attr):
1423 1421 return getattr(self._origfh, attr)
1424 1422
1425 1423 def __setattr__(self, attr, value):
1426 1424 return setattr(self._origfh, attr, value)
1427 1425
1428 1426 def __delattr__(self, attr):
1429 1427 return delattr(self._origfh, attr)
1430 1428
1431 1429 def __enter__(self):
1432 1430 return self._origfh.__enter__()
1433 1431
1434 1432 def __exit__(self, exc_type, exc_value, exc_tb):
1435 1433 raise NotImplementedError('attempted instantiating ' + str(type(self)))
1436 1434
1437 1435 def close(self):
1438 1436 raise NotImplementedError('attempted instantiating ' + str(type(self)))
1439 1437
1440 1438 class delayclosedfile(closewrapbase):
1441 1439 """Proxy for a file object whose close is delayed.
1442 1440
1443 1441 Do not instantiate outside of the vfs layer.
1444 1442 """
1445 1443 def __init__(self, fh, closer):
1446 1444 super(delayclosedfile, self).__init__(fh)
1447 1445 object.__setattr__(self, '_closer', closer)
1448 1446
1449 1447 def __exit__(self, exc_type, exc_value, exc_tb):
1450 1448 self._closer.close(self._origfh)
1451 1449
1452 1450 def close(self):
1453 1451 self._closer.close(self._origfh)
1454 1452
1455 1453 class backgroundfilecloser(object):
1456 1454 """Coordinates background closing of file handles on multiple threads."""
1457 1455 def __init__(self, ui, expectedcount=-1):
1458 1456 self._running = False
1459 1457 self._entered = False
1460 1458 self._threads = []
1461 1459 self._threadexception = None
1462 1460
1463 1461 # Only Windows/NTFS has slow file closing. So only enable by default
1464 1462 # on that platform. But allow to be enabled elsewhere for testing.
1465 1463 defaultenabled = pycompat.osname == 'nt'
1466 1464 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
1467 1465
1468 1466 if not enabled:
1469 1467 return
1470 1468
1471 1469 # There is overhead to starting and stopping the background threads.
1472 1470 # Don't do background processing unless the file count is large enough
1473 1471 # to justify it.
1474 1472 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount',
1475 1473 2048)
1476 1474 # FUTURE dynamically start background threads after minfilecount closes.
1477 1475 # (We don't currently have any callers that don't know their file count)
1478 1476 if expectedcount > 0 and expectedcount < minfilecount:
1479 1477 return
1480 1478
1481 1479 # Windows defaults to a limit of 512 open files. A buffer of 128
1482 1480 # should give us enough headway.
1483 1481 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue', 384)
1484 1482 threadcount = ui.configint('worker', 'backgroundclosethreadcount', 4)
1485 1483
1486 1484 ui.debug('starting %d threads for background file closing\n' %
1487 1485 threadcount)
1488 1486
1489 1487 self._queue = util.queue(maxsize=maxqueue)
1490 1488 self._running = True
1491 1489
1492 1490 for i in range(threadcount):
1493 1491 t = threading.Thread(target=self._worker, name='backgroundcloser')
1494 1492 self._threads.append(t)
1495 1493 t.start()
1496 1494
1497 1495 def __enter__(self):
1498 1496 self._entered = True
1499 1497 return self
1500 1498
1501 1499 def __exit__(self, exc_type, exc_value, exc_tb):
1502 1500 self._running = False
1503 1501
1504 1502 # Wait for threads to finish closing so open files don't linger for
1505 1503 # longer than lifetime of context manager.
1506 1504 for t in self._threads:
1507 1505 t.join()
1508 1506
1509 1507 def _worker(self):
1510 1508 """Main routine for worker thread."""
1511 1509 while True:
1512 1510 try:
1513 1511 fh = self._queue.get(block=True, timeout=0.100)
1514 1512 # Need to catch or the thread will terminate and
1515 1513 # we could orphan file descriptors.
1516 1514 try:
1517 1515 fh.close()
1518 1516 except Exception as e:
1519 1517 # Stash so can re-raise from main thread later.
1520 1518 self._threadexception = e
1521 1519 except util.empty:
1522 1520 if not self._running:
1523 1521 break
1524 1522
1525 1523 def close(self, fh):
1526 1524 """Schedule a file for closing."""
1527 1525 if not self._entered:
1528 1526 raise error.Abort(_('can only call close() when context manager '
1529 1527 'active'))
1530 1528
1531 1529 # If a background thread encountered an exception, raise now so we fail
1532 1530 # fast. Otherwise we may potentially go on for minutes until the error
1533 1531 # is acted on.
1534 1532 if self._threadexception:
1535 1533 e = self._threadexception
1536 1534 self._threadexception = None
1537 1535 raise e
1538 1536
1539 1537 # If we're not actively running, close synchronously.
1540 1538 if not self._running:
1541 1539 fh.close()
1542 1540 return
1543 1541
1544 1542 self._queue.put(fh, block=True, timeout=None)
1545 1543
1546 1544 class checkambigatclosing(closewrapbase):
1547 1545 """Proxy for a file object, to avoid ambiguity of file stat
1548 1546
1549 1547 See also util.filestat for detail about "ambiguity of file stat".
1550 1548
1551 1549 This proxy is useful only if the target file is guarded by any
1552 1550 lock (e.g. repo.lock or repo.wlock)
1553 1551
1554 1552 Do not instantiate outside of the vfs layer.
1555 1553 """
1556 1554 def __init__(self, fh):
1557 1555 super(checkambigatclosing, self).__init__(fh)
1558 1556 object.__setattr__(self, '_oldstat', util.filestat(fh.name))
1559 1557
1560 1558 def _checkambig(self):
1561 1559 oldstat = self._oldstat
1562 1560 if oldstat.stat:
1563 1561 newstat = util.filestat(self._origfh.name)
1564 1562 if newstat.isambig(oldstat):
1565 1563 # stat of changed file is ambiguous to original one
1566 1564 newstat.avoidambig(self._origfh.name, oldstat)
1567 1565
1568 1566 def __exit__(self, exc_type, exc_value, exc_tb):
1569 1567 self._origfh.__exit__(exc_type, exc_value, exc_tb)
1570 1568 self._checkambig()
1571 1569
1572 1570 def close(self):
1573 1571 self._origfh.close()
1574 1572 self._checkambig()
@@ -1,570 +1,569 b''
1 1 # tags.py - read tag info from local repository
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2009 Greg Ward <greg@gerg.ca>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 # Currently this module only deals with reading and caching tags.
10 10 # Eventually, it could take care of updating (adding/removing/moving)
11 11 # tags too.
12 12
13 13 from __future__ import absolute_import
14 14
15 15 import array
16 16 import errno
17 17
18 18 from .node import (
19 19 bin,
20 20 hex,
21 21 nullid,
22 22 short,
23 23 )
24 24 from . import (
25 25 encoding,
26 26 error,
27 scmutil,
27 28 util,
28 29 )
29 30
30 31 array = array.array
31 32
32 33 # Tags computation can be expensive and caches exist to make it fast in
33 34 # the common case.
34 35 #
35 36 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
36 37 # each revision in the repository. The file is effectively an array of
37 38 # fixed length records. Read the docs for "hgtagsfnodescache" for technical
38 39 # details.
39 40 #
40 41 # The .hgtags filenode cache grows in proportion to the length of the
41 42 # changelog. The file is truncated when the # changelog is stripped.
42 43 #
43 44 # The purpose of the filenode cache is to avoid the most expensive part
44 45 # of finding global tags, which is looking up the .hgtags filenode in the
45 46 # manifest for each head. This can take dozens or over 100ms for
46 47 # repositories with very large manifests. Multiplied by dozens or even
47 48 # hundreds of heads and there is a significant performance concern.
48 49 #
49 50 # There also exist a separate cache file for each repository filter.
50 51 # These "tags-*" files store information about the history of tags.
51 52 #
52 53 # The tags cache files consists of a cache validation line followed by
53 54 # a history of tags.
54 55 #
55 56 # The cache validation line has the format:
56 57 #
57 58 # <tiprev> <tipnode> [<filteredhash>]
58 59 #
59 60 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
60 61 # node for that changeset. These redundantly identify the repository
61 62 # tip from the time the cache was written. In addition, <filteredhash>,
62 63 # if present, is a 40 character hex hash of the contents of the filtered
63 64 # revisions for this filter. If the set of filtered revs changes, the
64 65 # hash will change and invalidate the cache.
65 66 #
66 67 # The history part of the tags cache consists of lines of the form:
67 68 #
68 69 # <node> <tag>
69 70 #
70 71 # (This format is identical to that of .hgtags files.)
71 72 #
72 73 # <tag> is the tag name and <node> is the 40 character hex changeset
73 74 # the tag is associated with.
74 75 #
75 76 # Tags are written sorted by tag name.
76 77 #
77 78 # Tags associated with multiple changesets have an entry for each changeset.
78 79 # The most recent changeset (in terms of revlog ordering for the head
79 80 # setting it) for each tag is last.
80 81
81 82 def findglobaltags(ui, repo, alltags, tagtypes):
82 83 '''Find global tags in a repo.
83 84
84 85 "alltags" maps tag name to (node, hist) 2-tuples.
85 86
86 87 "tagtypes" maps tag name to tag type. Global tags always have the
87 88 "global" tag type.
88 89
89 90 The "alltags" and "tagtypes" dicts are updated in place. Empty dicts
90 91 should be passed in.
91 92
92 93 The tags cache is read and updated as a side-effect of calling.
93 94 '''
94 95 # This is so we can be lazy and assume alltags contains only global
95 96 # tags when we pass it to _writetagcache().
96 97 assert len(alltags) == len(tagtypes) == 0, \
97 98 "findglobaltags() should be called first"
98 99
99 100 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
100 101 if cachetags is not None:
101 102 assert not shouldwrite
102 103 # XXX is this really 100% correct? are there oddball special
103 104 # cases where a global tag should outrank a local tag but won't,
104 105 # because cachetags does not contain rank info?
105 106 _updatetags(cachetags, 'global', alltags, tagtypes)
106 107 return
107 108
108 109 seen = set() # set of fnode
109 110 fctx = None
110 111 for head in reversed(heads): # oldest to newest
111 112 assert head in repo.changelog.nodemap, \
112 113 "tag cache returned bogus head %s" % short(head)
113 114
114 115 fnode = tagfnode.get(head)
115 116 if fnode and fnode not in seen:
116 117 seen.add(fnode)
117 118 if not fctx:
118 119 fctx = repo.filectx('.hgtags', fileid=fnode)
119 120 else:
120 121 fctx = fctx.filectx(fnode)
121 122
122 123 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
123 124 _updatetags(filetags, 'global', alltags, tagtypes)
124 125
125 126 # and update the cache (if necessary)
126 127 if shouldwrite:
127 128 _writetagcache(ui, repo, valid, alltags)
128 129
129 130 def readlocaltags(ui, repo, alltags, tagtypes):
130 131 '''Read local tags in repo. Update alltags and tagtypes.'''
131 132 try:
132 133 data = repo.vfs.read("localtags")
133 134 except IOError as inst:
134 135 if inst.errno != errno.ENOENT:
135 136 raise
136 137 return
137 138
138 139 # localtags is in the local encoding; re-encode to UTF-8 on
139 140 # input for consistency with the rest of this module.
140 141 filetags = _readtags(
141 142 ui, repo, data.splitlines(), "localtags",
142 143 recode=encoding.fromlocal)
143 144
144 145 # remove tags pointing to invalid nodes
145 146 cl = repo.changelog
146 147 for t in filetags.keys():
147 148 try:
148 149 cl.rev(filetags[t][0])
149 150 except (LookupError, ValueError):
150 151 del filetags[t]
151 152
152 153 _updatetags(filetags, "local", alltags, tagtypes)
153 154
154 155 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
155 156 '''Read tag definitions from a file (or any source of lines).
156 157
157 158 This function returns two sortdicts with similar information:
158 159
159 160 - the first dict, bintaghist, contains the tag information as expected by
160 161 the _readtags function, i.e. a mapping from tag name to (node, hist):
161 162 - node is the node id from the last line read for that name,
162 163 - hist is the list of node ids previously associated with it (in file
163 164 order). All node ids are binary, not hex.
164 165
165 166 - the second dict, hextaglines, is a mapping from tag name to a list of
166 167 [hexnode, line number] pairs, ordered from the oldest to the newest node.
167 168
168 169 When calcnodelines is False the hextaglines dict is not calculated (an
169 170 empty dict is returned). This is done to improve this function's
170 171 performance in cases where the line numbers are not needed.
171 172 '''
172 173
173 174 bintaghist = util.sortdict()
174 175 hextaglines = util.sortdict()
175 176 count = 0
176 177
177 178 def dbg(msg):
178 179 ui.debug("%s, line %s: %s\n" % (fn, count, msg))
179 180
180 181 for nline, line in enumerate(lines):
181 182 count += 1
182 183 if not line:
183 184 continue
184 185 try:
185 186 (nodehex, name) = line.split(" ", 1)
186 187 except ValueError:
187 188 dbg("cannot parse entry")
188 189 continue
189 190 name = name.strip()
190 191 if recode:
191 192 name = recode(name)
192 193 try:
193 194 nodebin = bin(nodehex)
194 195 except TypeError:
195 196 dbg("node '%s' is not well formed" % nodehex)
196 197 continue
197 198
198 199 # update filetags
199 200 if calcnodelines:
200 201 # map tag name to a list of line numbers
201 202 if name not in hextaglines:
202 203 hextaglines[name] = []
203 204 hextaglines[name].append([nodehex, nline])
204 205 continue
205 206 # map tag name to (node, hist)
206 207 if name not in bintaghist:
207 208 bintaghist[name] = []
208 209 bintaghist[name].append(nodebin)
209 210 return bintaghist, hextaglines
210 211
211 212 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
212 213 '''Read tag definitions from a file (or any source of lines).
213 214
214 215 Returns a mapping from tag name to (node, hist).
215 216
216 217 "node" is the node id from the last line read for that name. "hist"
217 218 is the list of node ids previously associated with it (in file order).
218 219 All node ids are binary, not hex.
219 220 '''
220 221 filetags, nodelines = _readtaghist(ui, repo, lines, fn, recode=recode,
221 222 calcnodelines=calcnodelines)
222 223 # util.sortdict().__setitem__ is much slower at replacing then inserting
223 224 # new entries. The difference can matter if there are thousands of tags.
224 225 # Create a new sortdict to avoid the performance penalty.
225 226 newtags = util.sortdict()
226 227 for tag, taghist in filetags.items():
227 228 newtags[tag] = (taghist[-1], taghist[:-1])
228 229 return newtags
229 230
230 231 def _updatetags(filetags, tagtype, alltags, tagtypes):
231 232 '''Incorporate the tag info read from one file into the two
232 233 dictionaries, alltags and tagtypes, that contain all tag
233 234 info (global across all heads plus local).'''
234 235
235 236 for name, nodehist in filetags.iteritems():
236 237 if name not in alltags:
237 238 alltags[name] = nodehist
238 239 tagtypes[name] = tagtype
239 240 continue
240 241
241 242 # we prefer alltags[name] if:
242 243 # it supersedes us OR
243 244 # mutual supersedes and it has a higher rank
244 245 # otherwise we win because we're tip-most
245 246 anode, ahist = nodehist
246 247 bnode, bhist = alltags[name]
247 248 if (bnode != anode and anode in bhist and
248 249 (bnode not in ahist or len(bhist) > len(ahist))):
249 250 anode = bnode
250 251 else:
251 252 tagtypes[name] = tagtype
252 253 ahist.extend([n for n in bhist if n not in ahist])
253 254 alltags[name] = anode, ahist
254 255
255 256 def _filename(repo):
256 257 """name of a tagcache file for a given repo or repoview"""
257 258 filename = 'cache/tags2'
258 259 if repo.filtername:
259 260 filename = '%s-%s' % (filename, repo.filtername)
260 261 return filename
261 262
262 263 def _readtagcache(ui, repo):
263 264 '''Read the tag cache.
264 265
265 266 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
266 267
267 268 If the cache is completely up-to-date, "cachetags" is a dict of the
268 269 form returned by _readtags() and "heads", "fnodes", and "validinfo" are
269 270 None and "shouldwrite" is False.
270 271
271 272 If the cache is not up to date, "cachetags" is None. "heads" is a list
272 273 of all heads currently in the repository, ordered from tip to oldest.
273 274 "validinfo" is a tuple describing cache validation info. This is used
274 275 when writing the tags cache. "fnodes" is a mapping from head to .hgtags
275 276 filenode. "shouldwrite" is True.
276 277
277 278 If the cache is not up to date, the caller is responsible for reading tag
278 279 info from each returned head. (See findglobaltags().)
279 280 '''
280 from . import scmutil # avoid cycle
281
282 281 try:
283 282 cachefile = repo.vfs(_filename(repo), 'r')
284 283 # force reading the file for static-http
285 284 cachelines = iter(cachefile)
286 285 except IOError:
287 286 cachefile = None
288 287
289 288 cacherev = None
290 289 cachenode = None
291 290 cachehash = None
292 291 if cachefile:
293 292 try:
294 293 validline = next(cachelines)
295 294 validline = validline.split()
296 295 cacherev = int(validline[0])
297 296 cachenode = bin(validline[1])
298 297 if len(validline) > 2:
299 298 cachehash = bin(validline[2])
300 299 except Exception:
301 300 # corruption of the cache, just recompute it.
302 301 pass
303 302
304 303 tipnode = repo.changelog.tip()
305 304 tiprev = len(repo.changelog) - 1
306 305
307 306 # Case 1 (common): tip is the same, so nothing has changed.
308 307 # (Unchanged tip trivially means no changesets have been added.
309 308 # But, thanks to localrepository.destroyed(), it also means none
310 309 # have been destroyed by strip or rollback.)
311 310 if (cacherev == tiprev
312 311 and cachenode == tipnode
313 312 and cachehash == scmutil.filteredhash(repo, tiprev)):
314 313 tags = _readtags(ui, repo, cachelines, cachefile.name)
315 314 cachefile.close()
316 315 return (None, None, None, tags, False)
317 316 if cachefile:
318 317 cachefile.close() # ignore rest of file
319 318
320 319 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
321 320
322 321 repoheads = repo.heads()
323 322 # Case 2 (uncommon): empty repo; get out quickly and don't bother
324 323 # writing an empty cache.
325 324 if repoheads == [nullid]:
326 325 return ([], {}, valid, {}, False)
327 326
328 327 # Case 3 (uncommon): cache file missing or empty.
329 328
330 329 # Case 4 (uncommon): tip rev decreased. This should only happen
331 330 # when we're called from localrepository.destroyed(). Refresh the
332 331 # cache so future invocations will not see disappeared heads in the
333 332 # cache.
334 333
335 334 # Case 5 (common): tip has changed, so we've added/replaced heads.
336 335
337 336 # As it happens, the code to handle cases 3, 4, 5 is the same.
338 337
339 338 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
340 339 # exposed".
341 340 if not len(repo.file('.hgtags')):
342 341 # No tags have ever been committed, so we can avoid a
343 342 # potentially expensive search.
344 343 return ([], {}, valid, None, True)
345 344
346 345 starttime = util.timer()
347 346
348 347 # Now we have to lookup the .hgtags filenode for every new head.
349 348 # This is the most expensive part of finding tags, so performance
350 349 # depends primarily on the size of newheads. Worst case: no cache
351 350 # file, so newheads == repoheads.
352 351 fnodescache = hgtagsfnodescache(repo.unfiltered())
353 352 cachefnode = {}
354 353 for head in reversed(repoheads):
355 354 fnode = fnodescache.getfnode(head)
356 355 if fnode != nullid:
357 356 cachefnode[head] = fnode
358 357
359 358 fnodescache.write()
360 359
361 360 duration = util.timer() - starttime
362 361 ui.log('tagscache',
363 362 '%d/%d cache hits/lookups in %0.4f '
364 363 'seconds\n',
365 364 fnodescache.hitcount, fnodescache.lookupcount, duration)
366 365
367 366 # Caller has to iterate over all heads, but can use the filenodes in
368 367 # cachefnode to get to each .hgtags revision quickly.
369 368 return (repoheads, cachefnode, valid, None, True)
370 369
371 370 def _writetagcache(ui, repo, valid, cachetags):
372 371 filename = _filename(repo)
373 372 try:
374 373 cachefile = repo.vfs(filename, 'w', atomictemp=True)
375 374 except (OSError, IOError):
376 375 return
377 376
378 377 ui.log('tagscache', 'writing .hg/%s with %d tags\n',
379 378 filename, len(cachetags))
380 379
381 380 if valid[2]:
382 381 cachefile.write('%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2])))
383 382 else:
384 383 cachefile.write('%d %s\n' % (valid[0], hex(valid[1])))
385 384
386 385 # Tag names in the cache are in UTF-8 -- which is the whole reason
387 386 # we keep them in UTF-8 throughout this module. If we converted
388 387 # them local encoding on input, we would lose info writing them to
389 388 # the cache.
390 389 for (name, (node, hist)) in sorted(cachetags.iteritems()):
391 390 for n in hist:
392 391 cachefile.write("%s %s\n" % (hex(n), name))
393 392 cachefile.write("%s %s\n" % (hex(node), name))
394 393
395 394 try:
396 395 cachefile.close()
397 396 except (OSError, IOError):
398 397 pass
399 398
400 399 _fnodescachefile = 'cache/hgtagsfnodes1'
401 400 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
402 401 _fnodesmissingrec = '\xff' * 24
403 402
404 403 class hgtagsfnodescache(object):
405 404 """Persistent cache mapping revisions to .hgtags filenodes.
406 405
407 406 The cache is an array of records. Each item in the array corresponds to
408 407 a changelog revision. Values in the array contain the first 4 bytes of
409 408 the node hash and the 20 bytes .hgtags filenode for that revision.
410 409
411 410 The first 4 bytes are present as a form of verification. Repository
412 411 stripping and rewriting may change the node at a numeric revision in the
413 412 changelog. The changeset fragment serves as a verifier to detect
414 413 rewriting. This logic is shared with the rev branch cache (see
415 414 branchmap.py).
416 415
417 416 The instance holds in memory the full cache content but entries are
418 417 only parsed on read.
419 418
420 419 Instances behave like lists. ``c[i]`` works where i is a rev or
421 420 changeset node. Missing indexes are populated automatically on access.
422 421 """
423 422 def __init__(self, repo):
424 423 assert repo.filtername is None
425 424
426 425 self._repo = repo
427 426
428 427 # Only for reporting purposes.
429 428 self.lookupcount = 0
430 429 self.hitcount = 0
431 430
432 431 self._raw = array('c')
433 432
434 433 try:
435 434 data = repo.vfs.read(_fnodescachefile)
436 435 except (OSError, IOError):
437 436 data = ""
438 437 self._raw.fromstring(data)
439 438
440 439 # The end state of self._raw is an array that is of the exact length
441 440 # required to hold a record for every revision in the repository.
442 441 # We truncate or extend the array as necessary. self._dirtyoffset is
443 442 # defined to be the start offset at which we need to write the output
444 443 # file. This offset is also adjusted when new entries are calculated
445 444 # for array members.
446 445 cllen = len(repo.changelog)
447 446 wantedlen = cllen * _fnodesrecsize
448 447 rawlen = len(self._raw)
449 448
450 449 self._dirtyoffset = None
451 450
452 451 if rawlen < wantedlen:
453 452 self._dirtyoffset = rawlen
454 453 self._raw.extend('\xff' * (wantedlen - rawlen))
455 454 elif rawlen > wantedlen:
456 455 # There's no easy way to truncate array instances. This seems
457 456 # slightly less evil than copying a potentially large array slice.
458 457 for i in range(rawlen - wantedlen):
459 458 self._raw.pop()
460 459 self._dirtyoffset = len(self._raw)
461 460
462 461 def getfnode(self, node, computemissing=True):
463 462 """Obtain the filenode of the .hgtags file at a specified revision.
464 463
465 464 If the value is in the cache, the entry will be validated and returned.
466 465 Otherwise, the filenode will be computed and returned unless
467 466 "computemissing" is False, in which case None will be returned without
468 467 any potentially expensive computation being performed.
469 468
470 469 If an .hgtags does not exist at the specified revision, nullid is
471 470 returned.
472 471 """
473 472 ctx = self._repo[node]
474 473 rev = ctx.rev()
475 474
476 475 self.lookupcount += 1
477 476
478 477 offset = rev * _fnodesrecsize
479 478 record = self._raw[offset:offset + _fnodesrecsize].tostring()
480 479 properprefix = node[0:4]
481 480
482 481 # Validate and return existing entry.
483 482 if record != _fnodesmissingrec:
484 483 fileprefix = record[0:4]
485 484
486 485 if fileprefix == properprefix:
487 486 self.hitcount += 1
488 487 return record[4:]
489 488
490 489 # Fall through.
491 490
492 491 # If we get here, the entry is either missing or invalid.
493 492
494 493 if not computemissing:
495 494 return None
496 495
497 496 # Populate missing entry.
498 497 try:
499 498 fnode = ctx.filenode('.hgtags')
500 499 except error.LookupError:
501 500 # No .hgtags file on this revision.
502 501 fnode = nullid
503 502
504 503 self._writeentry(offset, properprefix, fnode)
505 504 return fnode
506 505
507 506 def setfnode(self, node, fnode):
508 507 """Set the .hgtags filenode for a given changeset."""
509 508 assert len(fnode) == 20
510 509 ctx = self._repo[node]
511 510
512 511 # Do a lookup first to avoid writing if nothing has changed.
513 512 if self.getfnode(ctx.node(), computemissing=False) == fnode:
514 513 return
515 514
516 515 self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
517 516
518 517 def _writeentry(self, offset, prefix, fnode):
519 518 # Slices on array instances only accept other array.
520 519 entry = array('c', prefix + fnode)
521 520 self._raw[offset:offset + _fnodesrecsize] = entry
522 521 # self._dirtyoffset could be None.
523 522 self._dirtyoffset = min(self._dirtyoffset, offset) or 0
524 523
525 524 def write(self):
526 525 """Perform all necessary writes to cache file.
527 526
528 527 This may no-op if no writes are needed or if a write lock could
529 528 not be obtained.
530 529 """
531 530 if self._dirtyoffset is None:
532 531 return
533 532
534 533 data = self._raw[self._dirtyoffset:]
535 534 if not data:
536 535 return
537 536
538 537 repo = self._repo
539 538
540 539 try:
541 540 lock = repo.wlock(wait=False)
542 541 except error.LockError:
543 542 repo.ui.log('tagscache',
544 543 'not writing .hg/%s because lock cannot be acquired\n' %
545 544 (_fnodescachefile))
546 545 return
547 546
548 547 try:
549 548 f = repo.vfs.open(_fnodescachefile, 'ab')
550 549 try:
551 550 # if the file has been truncated
552 551 actualoffset = f.tell()
553 552 if actualoffset < self._dirtyoffset:
554 553 self._dirtyoffset = actualoffset
555 554 data = self._raw[self._dirtyoffset:]
556 555 f.seek(self._dirtyoffset)
557 556 f.truncate()
558 557 repo.ui.log('tagscache',
559 558 'writing %d bytes to %s\n' % (
560 559 len(data), _fnodescachefile))
561 560 f.write(data)
562 561 self._dirtyoffset = None
563 562 finally:
564 563 f.close()
565 564 except (IOError, OSError) as inst:
566 565 repo.ui.log('tagscache',
567 566 "couldn't write %s: %s\n" % (
568 567 _fnodescachefile, inst))
569 568 finally:
570 569 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now