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