##// END OF EJS Templates
commit: drop unused "vdirs" argument from repo.checkcommitpatterns()...
Martin von Zweigbergk -
r44111:f965b102 default
parent child Browse files
Show More
@@ -1,3984 +1,3982 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in 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 copy as copymod
10 import copy as copymod
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22 from .pycompat import (
22 from .pycompat import (
23 getattr,
23 getattr,
24 open,
24 open,
25 setattr,
25 setattr,
26 )
26 )
27
27
28 from . import (
28 from . import (
29 bookmarks,
29 bookmarks,
30 changelog,
30 changelog,
31 copies,
31 copies,
32 crecord as crecordmod,
32 crecord as crecordmod,
33 dirstateguard,
33 dirstateguard,
34 encoding,
34 encoding,
35 error,
35 error,
36 formatter,
36 formatter,
37 logcmdutil,
37 logcmdutil,
38 match as matchmod,
38 match as matchmod,
39 merge as mergemod,
39 merge as mergemod,
40 mergeutil,
40 mergeutil,
41 obsolete,
41 obsolete,
42 patch,
42 patch,
43 pathutil,
43 pathutil,
44 phases,
44 phases,
45 pycompat,
45 pycompat,
46 repair,
46 repair,
47 revlog,
47 revlog,
48 rewriteutil,
48 rewriteutil,
49 scmutil,
49 scmutil,
50 smartset,
50 smartset,
51 state as statemod,
51 state as statemod,
52 subrepoutil,
52 subrepoutil,
53 templatekw,
53 templatekw,
54 templater,
54 templater,
55 util,
55 util,
56 vfs as vfsmod,
56 vfs as vfsmod,
57 )
57 )
58
58
59 from .utils import (
59 from .utils import (
60 dateutil,
60 dateutil,
61 stringutil,
61 stringutil,
62 )
62 )
63
63
64 if not globals():
64 if not globals():
65 from typing import (
65 from typing import (
66 Any,
66 Any,
67 Dict,
67 Dict,
68 )
68 )
69
69
70 for t in (Any, Dict):
70 for t in (Any, Dict):
71 assert t
71 assert t
72
72
73 stringio = util.stringio
73 stringio = util.stringio
74
74
75 # templates of common command options
75 # templates of common command options
76
76
77 dryrunopts = [
77 dryrunopts = [
78 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
78 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
79 ]
79 ]
80
80
81 confirmopts = [
81 confirmopts = [
82 (b'', b'confirm', None, _(b'ask before applying actions')),
82 (b'', b'confirm', None, _(b'ask before applying actions')),
83 ]
83 ]
84
84
85 remoteopts = [
85 remoteopts = [
86 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
86 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
87 (
87 (
88 b'',
88 b'',
89 b'remotecmd',
89 b'remotecmd',
90 b'',
90 b'',
91 _(b'specify hg command to run on the remote side'),
91 _(b'specify hg command to run on the remote side'),
92 _(b'CMD'),
92 _(b'CMD'),
93 ),
93 ),
94 (
94 (
95 b'',
95 b'',
96 b'insecure',
96 b'insecure',
97 None,
97 None,
98 _(b'do not verify server certificate (ignoring web.cacerts config)'),
98 _(b'do not verify server certificate (ignoring web.cacerts config)'),
99 ),
99 ),
100 ]
100 ]
101
101
102 walkopts = [
102 walkopts = [
103 (
103 (
104 b'I',
104 b'I',
105 b'include',
105 b'include',
106 [],
106 [],
107 _(b'include names matching the given patterns'),
107 _(b'include names matching the given patterns'),
108 _(b'PATTERN'),
108 _(b'PATTERN'),
109 ),
109 ),
110 (
110 (
111 b'X',
111 b'X',
112 b'exclude',
112 b'exclude',
113 [],
113 [],
114 _(b'exclude names matching the given patterns'),
114 _(b'exclude names matching the given patterns'),
115 _(b'PATTERN'),
115 _(b'PATTERN'),
116 ),
116 ),
117 ]
117 ]
118
118
119 commitopts = [
119 commitopts = [
120 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
120 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
121 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
121 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
122 ]
122 ]
123
123
124 commitopts2 = [
124 commitopts2 = [
125 (
125 (
126 b'd',
126 b'd',
127 b'date',
127 b'date',
128 b'',
128 b'',
129 _(b'record the specified date as commit date'),
129 _(b'record the specified date as commit date'),
130 _(b'DATE'),
130 _(b'DATE'),
131 ),
131 ),
132 (
132 (
133 b'u',
133 b'u',
134 b'user',
134 b'user',
135 b'',
135 b'',
136 _(b'record the specified user as committer'),
136 _(b'record the specified user as committer'),
137 _(b'USER'),
137 _(b'USER'),
138 ),
138 ),
139 ]
139 ]
140
140
141 commitopts3 = [
141 commitopts3 = [
142 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
142 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
143 (b'U', b'currentuser', None, _(b'record the current user as committer')),
143 (b'U', b'currentuser', None, _(b'record the current user as committer')),
144 ]
144 ]
145
145
146 formatteropts = [
146 formatteropts = [
147 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
147 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
148 ]
148 ]
149
149
150 templateopts = [
150 templateopts = [
151 (
151 (
152 b'',
152 b'',
153 b'style',
153 b'style',
154 b'',
154 b'',
155 _(b'display using template map file (DEPRECATED)'),
155 _(b'display using template map file (DEPRECATED)'),
156 _(b'STYLE'),
156 _(b'STYLE'),
157 ),
157 ),
158 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
158 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
159 ]
159 ]
160
160
161 logopts = [
161 logopts = [
162 (b'p', b'patch', None, _(b'show patch')),
162 (b'p', b'patch', None, _(b'show patch')),
163 (b'g', b'git', None, _(b'use git extended diff format')),
163 (b'g', b'git', None, _(b'use git extended diff format')),
164 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
164 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
165 (b'M', b'no-merges', None, _(b'do not show merges')),
165 (b'M', b'no-merges', None, _(b'do not show merges')),
166 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
166 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
167 (b'G', b'graph', None, _(b"show the revision DAG")),
167 (b'G', b'graph', None, _(b"show the revision DAG")),
168 ] + templateopts
168 ] + templateopts
169
169
170 diffopts = [
170 diffopts = [
171 (b'a', b'text', None, _(b'treat all files as text')),
171 (b'a', b'text', None, _(b'treat all files as text')),
172 (b'g', b'git', None, _(b'use git extended diff format')),
172 (b'g', b'git', None, _(b'use git extended diff format')),
173 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
173 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
174 (b'', b'nodates', None, _(b'omit dates from diff headers')),
174 (b'', b'nodates', None, _(b'omit dates from diff headers')),
175 ]
175 ]
176
176
177 diffwsopts = [
177 diffwsopts = [
178 (
178 (
179 b'w',
179 b'w',
180 b'ignore-all-space',
180 b'ignore-all-space',
181 None,
181 None,
182 _(b'ignore white space when comparing lines'),
182 _(b'ignore white space when comparing lines'),
183 ),
183 ),
184 (
184 (
185 b'b',
185 b'b',
186 b'ignore-space-change',
186 b'ignore-space-change',
187 None,
187 None,
188 _(b'ignore changes in the amount of white space'),
188 _(b'ignore changes in the amount of white space'),
189 ),
189 ),
190 (
190 (
191 b'B',
191 b'B',
192 b'ignore-blank-lines',
192 b'ignore-blank-lines',
193 None,
193 None,
194 _(b'ignore changes whose lines are all blank'),
194 _(b'ignore changes whose lines are all blank'),
195 ),
195 ),
196 (
196 (
197 b'Z',
197 b'Z',
198 b'ignore-space-at-eol',
198 b'ignore-space-at-eol',
199 None,
199 None,
200 _(b'ignore changes in whitespace at EOL'),
200 _(b'ignore changes in whitespace at EOL'),
201 ),
201 ),
202 ]
202 ]
203
203
204 diffopts2 = (
204 diffopts2 = (
205 [
205 [
206 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
206 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
207 (
207 (
208 b'p',
208 b'p',
209 b'show-function',
209 b'show-function',
210 None,
210 None,
211 _(b'show which function each change is in'),
211 _(b'show which function each change is in'),
212 ),
212 ),
213 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
213 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
214 ]
214 ]
215 + diffwsopts
215 + diffwsopts
216 + [
216 + [
217 (
217 (
218 b'U',
218 b'U',
219 b'unified',
219 b'unified',
220 b'',
220 b'',
221 _(b'number of lines of context to show'),
221 _(b'number of lines of context to show'),
222 _(b'NUM'),
222 _(b'NUM'),
223 ),
223 ),
224 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
224 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
225 (
225 (
226 b'',
226 b'',
227 b'root',
227 b'root',
228 b'',
228 b'',
229 _(b'produce diffs relative to subdirectory'),
229 _(b'produce diffs relative to subdirectory'),
230 _(b'DIR'),
230 _(b'DIR'),
231 ),
231 ),
232 ]
232 ]
233 )
233 )
234
234
235 mergetoolopts = [
235 mergetoolopts = [
236 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
236 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
237 ]
237 ]
238
238
239 similarityopts = [
239 similarityopts = [
240 (
240 (
241 b's',
241 b's',
242 b'similarity',
242 b'similarity',
243 b'',
243 b'',
244 _(b'guess renamed files by similarity (0<=s<=100)'),
244 _(b'guess renamed files by similarity (0<=s<=100)'),
245 _(b'SIMILARITY'),
245 _(b'SIMILARITY'),
246 )
246 )
247 ]
247 ]
248
248
249 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
249 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
250
250
251 debugrevlogopts = [
251 debugrevlogopts = [
252 (b'c', b'changelog', False, _(b'open changelog')),
252 (b'c', b'changelog', False, _(b'open changelog')),
253 (b'm', b'manifest', False, _(b'open manifest')),
253 (b'm', b'manifest', False, _(b'open manifest')),
254 (b'', b'dir', b'', _(b'open directory manifest')),
254 (b'', b'dir', b'', _(b'open directory manifest')),
255 ]
255 ]
256
256
257 # special string such that everything below this line will be ingored in the
257 # special string such that everything below this line will be ingored in the
258 # editor text
258 # editor text
259 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
259 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
260
260
261
261
262 def resolvecommitoptions(ui, opts):
262 def resolvecommitoptions(ui, opts):
263 """modify commit options dict to handle related options
263 """modify commit options dict to handle related options
264
264
265 The return value indicates that ``rewrite.update-timestamp`` is the reason
265 The return value indicates that ``rewrite.update-timestamp`` is the reason
266 the ``date`` option is set.
266 the ``date`` option is set.
267 """
267 """
268 if opts.get(b'date') and opts.get(b'currentdate'):
268 if opts.get(b'date') and opts.get(b'currentdate'):
269 raise error.Abort(_(b'--date and --currentdate are mutually exclusive'))
269 raise error.Abort(_(b'--date and --currentdate are mutually exclusive'))
270 if opts.get(b'user') and opts.get(b'currentuser'):
270 if opts.get(b'user') and opts.get(b'currentuser'):
271 raise error.Abort(_(b'--user and --currentuser are mutually exclusive'))
271 raise error.Abort(_(b'--user and --currentuser are mutually exclusive'))
272
272
273 datemaydiffer = False # date-only change should be ignored?
273 datemaydiffer = False # date-only change should be ignored?
274
274
275 if opts.get(b'currentdate'):
275 if opts.get(b'currentdate'):
276 opts[b'date'] = b'%d %d' % dateutil.makedate()
276 opts[b'date'] = b'%d %d' % dateutil.makedate()
277 elif (
277 elif (
278 not opts.get(b'date')
278 not opts.get(b'date')
279 and ui.configbool(b'rewrite', b'update-timestamp')
279 and ui.configbool(b'rewrite', b'update-timestamp')
280 and opts.get(b'currentdate') is None
280 and opts.get(b'currentdate') is None
281 ):
281 ):
282 opts[b'date'] = b'%d %d' % dateutil.makedate()
282 opts[b'date'] = b'%d %d' % dateutil.makedate()
283 datemaydiffer = True
283 datemaydiffer = True
284
284
285 if opts.get(b'currentuser'):
285 if opts.get(b'currentuser'):
286 opts[b'user'] = ui.username()
286 opts[b'user'] = ui.username()
287
287
288 return datemaydiffer
288 return datemaydiffer
289
289
290
290
291 def checknotesize(ui, opts):
291 def checknotesize(ui, opts):
292 """ make sure note is of valid format """
292 """ make sure note is of valid format """
293
293
294 note = opts.get(b'note')
294 note = opts.get(b'note')
295 if not note:
295 if not note:
296 return
296 return
297
297
298 if len(note) > 255:
298 if len(note) > 255:
299 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
299 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
300 if b'\n' in note:
300 if b'\n' in note:
301 raise error.Abort(_(b"note cannot contain a newline"))
301 raise error.Abort(_(b"note cannot contain a newline"))
302
302
303
303
304 def ishunk(x):
304 def ishunk(x):
305 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
305 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
306 return isinstance(x, hunkclasses)
306 return isinstance(x, hunkclasses)
307
307
308
308
309 def newandmodified(chunks, originalchunks):
309 def newandmodified(chunks, originalchunks):
310 newlyaddedandmodifiedfiles = set()
310 newlyaddedandmodifiedfiles = set()
311 alsorestore = set()
311 alsorestore = set()
312 for chunk in chunks:
312 for chunk in chunks:
313 if (
313 if (
314 ishunk(chunk)
314 ishunk(chunk)
315 and chunk.header.isnewfile()
315 and chunk.header.isnewfile()
316 and chunk not in originalchunks
316 and chunk not in originalchunks
317 ):
317 ):
318 newlyaddedandmodifiedfiles.add(chunk.header.filename())
318 newlyaddedandmodifiedfiles.add(chunk.header.filename())
319 alsorestore.update(
319 alsorestore.update(
320 set(chunk.header.files()) - {chunk.header.filename()}
320 set(chunk.header.files()) - {chunk.header.filename()}
321 )
321 )
322 return newlyaddedandmodifiedfiles, alsorestore
322 return newlyaddedandmodifiedfiles, alsorestore
323
323
324
324
325 def parsealiases(cmd):
325 def parsealiases(cmd):
326 return cmd.split(b"|")
326 return cmd.split(b"|")
327
327
328
328
329 def setupwrapcolorwrite(ui):
329 def setupwrapcolorwrite(ui):
330 # wrap ui.write so diff output can be labeled/colorized
330 # wrap ui.write so diff output can be labeled/colorized
331 def wrapwrite(orig, *args, **kw):
331 def wrapwrite(orig, *args, **kw):
332 label = kw.pop('label', b'')
332 label = kw.pop('label', b'')
333 for chunk, l in patch.difflabel(lambda: args):
333 for chunk, l in patch.difflabel(lambda: args):
334 orig(chunk, label=label + l)
334 orig(chunk, label=label + l)
335
335
336 oldwrite = ui.write
336 oldwrite = ui.write
337
337
338 def wrap(*args, **kwargs):
338 def wrap(*args, **kwargs):
339 return wrapwrite(oldwrite, *args, **kwargs)
339 return wrapwrite(oldwrite, *args, **kwargs)
340
340
341 setattr(ui, 'write', wrap)
341 setattr(ui, 'write', wrap)
342 return oldwrite
342 return oldwrite
343
343
344
344
345 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
345 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
346 try:
346 try:
347 if usecurses:
347 if usecurses:
348 if testfile:
348 if testfile:
349 recordfn = crecordmod.testdecorator(
349 recordfn = crecordmod.testdecorator(
350 testfile, crecordmod.testchunkselector
350 testfile, crecordmod.testchunkselector
351 )
351 )
352 else:
352 else:
353 recordfn = crecordmod.chunkselector
353 recordfn = crecordmod.chunkselector
354
354
355 return crecordmod.filterpatch(
355 return crecordmod.filterpatch(
356 ui, originalhunks, recordfn, operation
356 ui, originalhunks, recordfn, operation
357 )
357 )
358 except crecordmod.fallbackerror as e:
358 except crecordmod.fallbackerror as e:
359 ui.warn(b'%s\n' % e.message) # pytype: disable=attribute-error
359 ui.warn(b'%s\n' % e.message) # pytype: disable=attribute-error
360 ui.warn(_(b'falling back to text mode\n'))
360 ui.warn(_(b'falling back to text mode\n'))
361
361
362 return patch.filterpatch(ui, originalhunks, match, operation)
362 return patch.filterpatch(ui, originalhunks, match, operation)
363
363
364
364
365 def recordfilter(ui, originalhunks, match, operation=None):
365 def recordfilter(ui, originalhunks, match, operation=None):
366 """ Prompts the user to filter the originalhunks and return a list of
366 """ Prompts the user to filter the originalhunks and return a list of
367 selected hunks.
367 selected hunks.
368 *operation* is used for to build ui messages to indicate the user what
368 *operation* is used for to build ui messages to indicate the user what
369 kind of filtering they are doing: reverting, committing, shelving, etc.
369 kind of filtering they are doing: reverting, committing, shelving, etc.
370 (see patch.filterpatch).
370 (see patch.filterpatch).
371 """
371 """
372 usecurses = crecordmod.checkcurses(ui)
372 usecurses = crecordmod.checkcurses(ui)
373 testfile = ui.config(b'experimental', b'crecordtest')
373 testfile = ui.config(b'experimental', b'crecordtest')
374 oldwrite = setupwrapcolorwrite(ui)
374 oldwrite = setupwrapcolorwrite(ui)
375 try:
375 try:
376 newchunks, newopts = filterchunks(
376 newchunks, newopts = filterchunks(
377 ui, originalhunks, usecurses, testfile, match, operation
377 ui, originalhunks, usecurses, testfile, match, operation
378 )
378 )
379 finally:
379 finally:
380 ui.write = oldwrite
380 ui.write = oldwrite
381 return newchunks, newopts
381 return newchunks, newopts
382
382
383
383
384 def dorecord(
384 def dorecord(
385 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
385 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
386 ):
386 ):
387 opts = pycompat.byteskwargs(opts)
387 opts = pycompat.byteskwargs(opts)
388 if not ui.interactive():
388 if not ui.interactive():
389 if cmdsuggest:
389 if cmdsuggest:
390 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
390 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
391 else:
391 else:
392 msg = _(b'running non-interactively')
392 msg = _(b'running non-interactively')
393 raise error.Abort(msg)
393 raise error.Abort(msg)
394
394
395 # make sure username is set before going interactive
395 # make sure username is set before going interactive
396 if not opts.get(b'user'):
396 if not opts.get(b'user'):
397 ui.username() # raise exception, username not provided
397 ui.username() # raise exception, username not provided
398
398
399 def recordfunc(ui, repo, message, match, opts):
399 def recordfunc(ui, repo, message, match, opts):
400 """This is generic record driver.
400 """This is generic record driver.
401
401
402 Its job is to interactively filter local changes, and
402 Its job is to interactively filter local changes, and
403 accordingly prepare working directory into a state in which the
403 accordingly prepare working directory into a state in which the
404 job can be delegated to a non-interactive commit command such as
404 job can be delegated to a non-interactive commit command such as
405 'commit' or 'qrefresh'.
405 'commit' or 'qrefresh'.
406
406
407 After the actual job is done by non-interactive command, the
407 After the actual job is done by non-interactive command, the
408 working directory is restored to its original state.
408 working directory is restored to its original state.
409
409
410 In the end we'll record interesting changes, and everything else
410 In the end we'll record interesting changes, and everything else
411 will be left in place, so the user can continue working.
411 will be left in place, so the user can continue working.
412 """
412 """
413 if not opts.get(b'interactive-unshelve'):
413 if not opts.get(b'interactive-unshelve'):
414 checkunfinished(repo, commit=True)
414 checkunfinished(repo, commit=True)
415 wctx = repo[None]
415 wctx = repo[None]
416 merge = len(wctx.parents()) > 1
416 merge = len(wctx.parents()) > 1
417 if merge:
417 if merge:
418 raise error.Abort(
418 raise error.Abort(
419 _(
419 _(
420 b'cannot partially commit a merge '
420 b'cannot partially commit a merge '
421 b'(use "hg commit" instead)'
421 b'(use "hg commit" instead)'
422 )
422 )
423 )
423 )
424
424
425 def fail(f, msg):
425 def fail(f, msg):
426 raise error.Abort(b'%s: %s' % (f, msg))
426 raise error.Abort(b'%s: %s' % (f, msg))
427
427
428 force = opts.get(b'force')
428 force = opts.get(b'force')
429 if not force:
429 if not force:
430 vdirs = []
431 match = matchmod.badmatch(match, fail)
430 match = matchmod.badmatch(match, fail)
432 match.explicitdir = vdirs.append
433
431
434 status = repo.status(match=match)
432 status = repo.status(match=match)
435
433
436 overrides = {(b'ui', b'commitsubrepos'): True}
434 overrides = {(b'ui', b'commitsubrepos'): True}
437
435
438 with repo.ui.configoverride(overrides, b'record'):
436 with repo.ui.configoverride(overrides, b'record'):
439 # subrepoutil.precommit() modifies the status
437 # subrepoutil.precommit() modifies the status
440 tmpstatus = scmutil.status(
438 tmpstatus = scmutil.status(
441 copymod.copy(status.modified),
439 copymod.copy(status.modified),
442 copymod.copy(status.added),
440 copymod.copy(status.added),
443 copymod.copy(status.removed),
441 copymod.copy(status.removed),
444 copymod.copy(status.deleted),
442 copymod.copy(status.deleted),
445 copymod.copy(status.unknown),
443 copymod.copy(status.unknown),
446 copymod.copy(status.ignored),
444 copymod.copy(status.ignored),
447 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
445 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
448 )
446 )
449
447
450 # Force allows -X subrepo to skip the subrepo.
448 # Force allows -X subrepo to skip the subrepo.
451 subs, commitsubs, newstate = subrepoutil.precommit(
449 subs, commitsubs, newstate = subrepoutil.precommit(
452 repo.ui, wctx, tmpstatus, match, force=True
450 repo.ui, wctx, tmpstatus, match, force=True
453 )
451 )
454 for s in subs:
452 for s in subs:
455 if s in commitsubs:
453 if s in commitsubs:
456 dirtyreason = wctx.sub(s).dirtyreason(True)
454 dirtyreason = wctx.sub(s).dirtyreason(True)
457 raise error.Abort(dirtyreason)
455 raise error.Abort(dirtyreason)
458
456
459 if not force:
457 if not force:
460 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
458 repo.checkcommitpatterns(wctx, match, status, fail)
461 diffopts = patch.difffeatureopts(
459 diffopts = patch.difffeatureopts(
462 ui,
460 ui,
463 opts=opts,
461 opts=opts,
464 whitespace=True,
462 whitespace=True,
465 section=b'commands',
463 section=b'commands',
466 configprefix=b'commit.interactive.',
464 configprefix=b'commit.interactive.',
467 )
465 )
468 diffopts.nodates = True
466 diffopts.nodates = True
469 diffopts.git = True
467 diffopts.git = True
470 diffopts.showfunc = True
468 diffopts.showfunc = True
471 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
469 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
472 originalchunks = patch.parsepatch(originaldiff)
470 originalchunks = patch.parsepatch(originaldiff)
473 match = scmutil.match(repo[None], pats)
471 match = scmutil.match(repo[None], pats)
474
472
475 # 1. filter patch, since we are intending to apply subset of it
473 # 1. filter patch, since we are intending to apply subset of it
476 try:
474 try:
477 chunks, newopts = filterfn(ui, originalchunks, match)
475 chunks, newopts = filterfn(ui, originalchunks, match)
478 except error.PatchError as err:
476 except error.PatchError as err:
479 raise error.Abort(_(b'error parsing patch: %s') % err)
477 raise error.Abort(_(b'error parsing patch: %s') % err)
480 opts.update(newopts)
478 opts.update(newopts)
481
479
482 # We need to keep a backup of files that have been newly added and
480 # We need to keep a backup of files that have been newly added and
483 # modified during the recording process because there is a previous
481 # modified during the recording process because there is a previous
484 # version without the edit in the workdir. We also will need to restore
482 # version without the edit in the workdir. We also will need to restore
485 # files that were the sources of renames so that the patch application
483 # files that were the sources of renames so that the patch application
486 # works.
484 # works.
487 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
485 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
488 chunks, originalchunks
486 chunks, originalchunks
489 )
487 )
490 contenders = set()
488 contenders = set()
491 for h in chunks:
489 for h in chunks:
492 try:
490 try:
493 contenders.update(set(h.files()))
491 contenders.update(set(h.files()))
494 except AttributeError:
492 except AttributeError:
495 pass
493 pass
496
494
497 changed = status.modified + status.added + status.removed
495 changed = status.modified + status.added + status.removed
498 newfiles = [f for f in changed if f in contenders]
496 newfiles = [f for f in changed if f in contenders]
499 if not newfiles:
497 if not newfiles:
500 ui.status(_(b'no changes to record\n'))
498 ui.status(_(b'no changes to record\n'))
501 return 0
499 return 0
502
500
503 modified = set(status.modified)
501 modified = set(status.modified)
504
502
505 # 2. backup changed files, so we can restore them in the end
503 # 2. backup changed files, so we can restore them in the end
506
504
507 if backupall:
505 if backupall:
508 tobackup = changed
506 tobackup = changed
509 else:
507 else:
510 tobackup = [
508 tobackup = [
511 f
509 f
512 for f in newfiles
510 for f in newfiles
513 if f in modified or f in newlyaddedandmodifiedfiles
511 if f in modified or f in newlyaddedandmodifiedfiles
514 ]
512 ]
515 backups = {}
513 backups = {}
516 if tobackup:
514 if tobackup:
517 backupdir = repo.vfs.join(b'record-backups')
515 backupdir = repo.vfs.join(b'record-backups')
518 try:
516 try:
519 os.mkdir(backupdir)
517 os.mkdir(backupdir)
520 except OSError as err:
518 except OSError as err:
521 if err.errno != errno.EEXIST:
519 if err.errno != errno.EEXIST:
522 raise
520 raise
523 try:
521 try:
524 # backup continues
522 # backup continues
525 for f in tobackup:
523 for f in tobackup:
526 fd, tmpname = pycompat.mkstemp(
524 fd, tmpname = pycompat.mkstemp(
527 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
525 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
528 )
526 )
529 os.close(fd)
527 os.close(fd)
530 ui.debug(b'backup %r as %r\n' % (f, tmpname))
528 ui.debug(b'backup %r as %r\n' % (f, tmpname))
531 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
529 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
532 backups[f] = tmpname
530 backups[f] = tmpname
533
531
534 fp = stringio()
532 fp = stringio()
535 for c in chunks:
533 for c in chunks:
536 fname = c.filename()
534 fname = c.filename()
537 if fname in backups:
535 if fname in backups:
538 c.write(fp)
536 c.write(fp)
539 dopatch = fp.tell()
537 dopatch = fp.tell()
540 fp.seek(0)
538 fp.seek(0)
541
539
542 # 2.5 optionally review / modify patch in text editor
540 # 2.5 optionally review / modify patch in text editor
543 if opts.get(b'review', False):
541 if opts.get(b'review', False):
544 patchtext = (
542 patchtext = (
545 crecordmod.diffhelptext
543 crecordmod.diffhelptext
546 + crecordmod.patchhelptext
544 + crecordmod.patchhelptext
547 + fp.read()
545 + fp.read()
548 )
546 )
549 reviewedpatch = ui.edit(
547 reviewedpatch = ui.edit(
550 patchtext, b"", action=b"diff", repopath=repo.path
548 patchtext, b"", action=b"diff", repopath=repo.path
551 )
549 )
552 fp.truncate(0)
550 fp.truncate(0)
553 fp.write(reviewedpatch)
551 fp.write(reviewedpatch)
554 fp.seek(0)
552 fp.seek(0)
555
553
556 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
554 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
557 # 3a. apply filtered patch to clean repo (clean)
555 # 3a. apply filtered patch to clean repo (clean)
558 if backups:
556 if backups:
559 # Equivalent to hg.revert
557 # Equivalent to hg.revert
560 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
558 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
561 mergemod.update(
559 mergemod.update(
562 repo,
560 repo,
563 repo.dirstate.p1(),
561 repo.dirstate.p1(),
564 branchmerge=False,
562 branchmerge=False,
565 force=True,
563 force=True,
566 matcher=m,
564 matcher=m,
567 )
565 )
568
566
569 # 3b. (apply)
567 # 3b. (apply)
570 if dopatch:
568 if dopatch:
571 try:
569 try:
572 ui.debug(b'applying patch\n')
570 ui.debug(b'applying patch\n')
573 ui.debug(fp.getvalue())
571 ui.debug(fp.getvalue())
574 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
572 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
575 except error.PatchError as err:
573 except error.PatchError as err:
576 raise error.Abort(pycompat.bytestr(err))
574 raise error.Abort(pycompat.bytestr(err))
577 del fp
575 del fp
578
576
579 # 4. We prepared working directory according to filtered
577 # 4. We prepared working directory according to filtered
580 # patch. Now is the time to delegate the job to
578 # patch. Now is the time to delegate the job to
581 # commit/qrefresh or the like!
579 # commit/qrefresh or the like!
582
580
583 # Make all of the pathnames absolute.
581 # Make all of the pathnames absolute.
584 newfiles = [repo.wjoin(nf) for nf in newfiles]
582 newfiles = [repo.wjoin(nf) for nf in newfiles]
585 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
583 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
586 finally:
584 finally:
587 # 5. finally restore backed-up files
585 # 5. finally restore backed-up files
588 try:
586 try:
589 dirstate = repo.dirstate
587 dirstate = repo.dirstate
590 for realname, tmpname in pycompat.iteritems(backups):
588 for realname, tmpname in pycompat.iteritems(backups):
591 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
589 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
592
590
593 if dirstate[realname] == b'n':
591 if dirstate[realname] == b'n':
594 # without normallookup, restoring timestamp
592 # without normallookup, restoring timestamp
595 # may cause partially committed files
593 # may cause partially committed files
596 # to be treated as unmodified
594 # to be treated as unmodified
597 dirstate.normallookup(realname)
595 dirstate.normallookup(realname)
598
596
599 # copystat=True here and above are a hack to trick any
597 # copystat=True here and above are a hack to trick any
600 # editors that have f open that we haven't modified them.
598 # editors that have f open that we haven't modified them.
601 #
599 #
602 # Also note that this racy as an editor could notice the
600 # Also note that this racy as an editor could notice the
603 # file's mtime before we've finished writing it.
601 # file's mtime before we've finished writing it.
604 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
602 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
605 os.unlink(tmpname)
603 os.unlink(tmpname)
606 if tobackup:
604 if tobackup:
607 os.rmdir(backupdir)
605 os.rmdir(backupdir)
608 except OSError:
606 except OSError:
609 pass
607 pass
610
608
611 def recordinwlock(ui, repo, message, match, opts):
609 def recordinwlock(ui, repo, message, match, opts):
612 with repo.wlock():
610 with repo.wlock():
613 return recordfunc(ui, repo, message, match, opts)
611 return recordfunc(ui, repo, message, match, opts)
614
612
615 return commit(ui, repo, recordinwlock, pats, opts)
613 return commit(ui, repo, recordinwlock, pats, opts)
616
614
617
615
618 class dirnode(object):
616 class dirnode(object):
619 """
617 """
620 Represent a directory in user working copy with information required for
618 Represent a directory in user working copy with information required for
621 the purpose of tersing its status.
619 the purpose of tersing its status.
622
620
623 path is the path to the directory, without a trailing '/'
621 path is the path to the directory, without a trailing '/'
624
622
625 statuses is a set of statuses of all files in this directory (this includes
623 statuses is a set of statuses of all files in this directory (this includes
626 all the files in all the subdirectories too)
624 all the files in all the subdirectories too)
627
625
628 files is a list of files which are direct child of this directory
626 files is a list of files which are direct child of this directory
629
627
630 subdirs is a dictionary of sub-directory name as the key and it's own
628 subdirs is a dictionary of sub-directory name as the key and it's own
631 dirnode object as the value
629 dirnode object as the value
632 """
630 """
633
631
634 def __init__(self, dirpath):
632 def __init__(self, dirpath):
635 self.path = dirpath
633 self.path = dirpath
636 self.statuses = set()
634 self.statuses = set()
637 self.files = []
635 self.files = []
638 self.subdirs = {}
636 self.subdirs = {}
639
637
640 def _addfileindir(self, filename, status):
638 def _addfileindir(self, filename, status):
641 """Add a file in this directory as a direct child."""
639 """Add a file in this directory as a direct child."""
642 self.files.append((filename, status))
640 self.files.append((filename, status))
643
641
644 def addfile(self, filename, status):
642 def addfile(self, filename, status):
645 """
643 """
646 Add a file to this directory or to its direct parent directory.
644 Add a file to this directory or to its direct parent directory.
647
645
648 If the file is not direct child of this directory, we traverse to the
646 If the file is not direct child of this directory, we traverse to the
649 directory of which this file is a direct child of and add the file
647 directory of which this file is a direct child of and add the file
650 there.
648 there.
651 """
649 """
652
650
653 # the filename contains a path separator, it means it's not the direct
651 # the filename contains a path separator, it means it's not the direct
654 # child of this directory
652 # child of this directory
655 if b'/' in filename:
653 if b'/' in filename:
656 subdir, filep = filename.split(b'/', 1)
654 subdir, filep = filename.split(b'/', 1)
657
655
658 # does the dirnode object for subdir exists
656 # does the dirnode object for subdir exists
659 if subdir not in self.subdirs:
657 if subdir not in self.subdirs:
660 subdirpath = pathutil.join(self.path, subdir)
658 subdirpath = pathutil.join(self.path, subdir)
661 self.subdirs[subdir] = dirnode(subdirpath)
659 self.subdirs[subdir] = dirnode(subdirpath)
662
660
663 # try adding the file in subdir
661 # try adding the file in subdir
664 self.subdirs[subdir].addfile(filep, status)
662 self.subdirs[subdir].addfile(filep, status)
665
663
666 else:
664 else:
667 self._addfileindir(filename, status)
665 self._addfileindir(filename, status)
668
666
669 if status not in self.statuses:
667 if status not in self.statuses:
670 self.statuses.add(status)
668 self.statuses.add(status)
671
669
672 def iterfilepaths(self):
670 def iterfilepaths(self):
673 """Yield (status, path) for files directly under this directory."""
671 """Yield (status, path) for files directly under this directory."""
674 for f, st in self.files:
672 for f, st in self.files:
675 yield st, pathutil.join(self.path, f)
673 yield st, pathutil.join(self.path, f)
676
674
677 def tersewalk(self, terseargs):
675 def tersewalk(self, terseargs):
678 """
676 """
679 Yield (status, path) obtained by processing the status of this
677 Yield (status, path) obtained by processing the status of this
680 dirnode.
678 dirnode.
681
679
682 terseargs is the string of arguments passed by the user with `--terse`
680 terseargs is the string of arguments passed by the user with `--terse`
683 flag.
681 flag.
684
682
685 Following are the cases which can happen:
683 Following are the cases which can happen:
686
684
687 1) All the files in the directory (including all the files in its
685 1) All the files in the directory (including all the files in its
688 subdirectories) share the same status and the user has asked us to terse
686 subdirectories) share the same status and the user has asked us to terse
689 that status. -> yield (status, dirpath). dirpath will end in '/'.
687 that status. -> yield (status, dirpath). dirpath will end in '/'.
690
688
691 2) Otherwise, we do following:
689 2) Otherwise, we do following:
692
690
693 a) Yield (status, filepath) for all the files which are in this
691 a) Yield (status, filepath) for all the files which are in this
694 directory (only the ones in this directory, not the subdirs)
692 directory (only the ones in this directory, not the subdirs)
695
693
696 b) Recurse the function on all the subdirectories of this
694 b) Recurse the function on all the subdirectories of this
697 directory
695 directory
698 """
696 """
699
697
700 if len(self.statuses) == 1:
698 if len(self.statuses) == 1:
701 onlyst = self.statuses.pop()
699 onlyst = self.statuses.pop()
702
700
703 # Making sure we terse only when the status abbreviation is
701 # Making sure we terse only when the status abbreviation is
704 # passed as terse argument
702 # passed as terse argument
705 if onlyst in terseargs:
703 if onlyst in terseargs:
706 yield onlyst, self.path + b'/'
704 yield onlyst, self.path + b'/'
707 return
705 return
708
706
709 # add the files to status list
707 # add the files to status list
710 for st, fpath in self.iterfilepaths():
708 for st, fpath in self.iterfilepaths():
711 yield st, fpath
709 yield st, fpath
712
710
713 # recurse on the subdirs
711 # recurse on the subdirs
714 for dirobj in self.subdirs.values():
712 for dirobj in self.subdirs.values():
715 for st, fpath in dirobj.tersewalk(terseargs):
713 for st, fpath in dirobj.tersewalk(terseargs):
716 yield st, fpath
714 yield st, fpath
717
715
718
716
719 def tersedir(statuslist, terseargs):
717 def tersedir(statuslist, terseargs):
720 """
718 """
721 Terse the status if all the files in a directory shares the same status.
719 Terse the status if all the files in a directory shares the same status.
722
720
723 statuslist is scmutil.status() object which contains a list of files for
721 statuslist is scmutil.status() object which contains a list of files for
724 each status.
722 each status.
725 terseargs is string which is passed by the user as the argument to `--terse`
723 terseargs is string which is passed by the user as the argument to `--terse`
726 flag.
724 flag.
727
725
728 The function makes a tree of objects of dirnode class, and at each node it
726 The function makes a tree of objects of dirnode class, and at each node it
729 stores the information required to know whether we can terse a certain
727 stores the information required to know whether we can terse a certain
730 directory or not.
728 directory or not.
731 """
729 """
732 # the order matters here as that is used to produce final list
730 # the order matters here as that is used to produce final list
733 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
731 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
734
732
735 # checking the argument validity
733 # checking the argument validity
736 for s in pycompat.bytestr(terseargs):
734 for s in pycompat.bytestr(terseargs):
737 if s not in allst:
735 if s not in allst:
738 raise error.Abort(_(b"'%s' not recognized") % s)
736 raise error.Abort(_(b"'%s' not recognized") % s)
739
737
740 # creating a dirnode object for the root of the repo
738 # creating a dirnode object for the root of the repo
741 rootobj = dirnode(b'')
739 rootobj = dirnode(b'')
742 pstatus = (
740 pstatus = (
743 b'modified',
741 b'modified',
744 b'added',
742 b'added',
745 b'deleted',
743 b'deleted',
746 b'clean',
744 b'clean',
747 b'unknown',
745 b'unknown',
748 b'ignored',
746 b'ignored',
749 b'removed',
747 b'removed',
750 )
748 )
751
749
752 tersedict = {}
750 tersedict = {}
753 for attrname in pstatus:
751 for attrname in pstatus:
754 statuschar = attrname[0:1]
752 statuschar = attrname[0:1]
755 for f in getattr(statuslist, attrname):
753 for f in getattr(statuslist, attrname):
756 rootobj.addfile(f, statuschar)
754 rootobj.addfile(f, statuschar)
757 tersedict[statuschar] = []
755 tersedict[statuschar] = []
758
756
759 # we won't be tersing the root dir, so add files in it
757 # we won't be tersing the root dir, so add files in it
760 for st, fpath in rootobj.iterfilepaths():
758 for st, fpath in rootobj.iterfilepaths():
761 tersedict[st].append(fpath)
759 tersedict[st].append(fpath)
762
760
763 # process each sub-directory and build tersedict
761 # process each sub-directory and build tersedict
764 for subdir in rootobj.subdirs.values():
762 for subdir in rootobj.subdirs.values():
765 for st, f in subdir.tersewalk(terseargs):
763 for st, f in subdir.tersewalk(terseargs):
766 tersedict[st].append(f)
764 tersedict[st].append(f)
767
765
768 tersedlist = []
766 tersedlist = []
769 for st in allst:
767 for st in allst:
770 tersedict[st].sort()
768 tersedict[st].sort()
771 tersedlist.append(tersedict[st])
769 tersedlist.append(tersedict[st])
772
770
773 return scmutil.status(*tersedlist)
771 return scmutil.status(*tersedlist)
774
772
775
773
776 def _commentlines(raw):
774 def _commentlines(raw):
777 '''Surround lineswith a comment char and a new line'''
775 '''Surround lineswith a comment char and a new line'''
778 lines = raw.splitlines()
776 lines = raw.splitlines()
779 commentedlines = [b'# %s' % line for line in lines]
777 commentedlines = [b'# %s' % line for line in lines]
780 return b'\n'.join(commentedlines) + b'\n'
778 return b'\n'.join(commentedlines) + b'\n'
781
779
782
780
783 def _conflictsmsg(repo):
781 def _conflictsmsg(repo):
784 mergestate = mergemod.mergestate.read(repo)
782 mergestate = mergemod.mergestate.read(repo)
785 if not mergestate.active():
783 if not mergestate.active():
786 return
784 return
787
785
788 m = scmutil.match(repo[None])
786 m = scmutil.match(repo[None])
789 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
787 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
790 if unresolvedlist:
788 if unresolvedlist:
791 mergeliststr = b'\n'.join(
789 mergeliststr = b'\n'.join(
792 [
790 [
793 b' %s' % util.pathto(repo.root, encoding.getcwd(), path)
791 b' %s' % util.pathto(repo.root, encoding.getcwd(), path)
794 for path in sorted(unresolvedlist)
792 for path in sorted(unresolvedlist)
795 ]
793 ]
796 )
794 )
797 msg = (
795 msg = (
798 _(
796 _(
799 '''Unresolved merge conflicts:
797 '''Unresolved merge conflicts:
800
798
801 %s
799 %s
802
800
803 To mark files as resolved: hg resolve --mark FILE'''
801 To mark files as resolved: hg resolve --mark FILE'''
804 )
802 )
805 % mergeliststr
803 % mergeliststr
806 )
804 )
807 else:
805 else:
808 msg = _(b'No unresolved merge conflicts.')
806 msg = _(b'No unresolved merge conflicts.')
809
807
810 return _commentlines(msg)
808 return _commentlines(msg)
811
809
812
810
813 def morestatus(repo, fm):
811 def morestatus(repo, fm):
814 statetuple = statemod.getrepostate(repo)
812 statetuple = statemod.getrepostate(repo)
815 label = b'status.morestatus'
813 label = b'status.morestatus'
816 if statetuple:
814 if statetuple:
817 state, helpfulmsg = statetuple
815 state, helpfulmsg = statetuple
818 statemsg = _(b'The repository is in an unfinished *%s* state.') % state
816 statemsg = _(b'The repository is in an unfinished *%s* state.') % state
819 fm.plain(b'%s\n' % _commentlines(statemsg), label=label)
817 fm.plain(b'%s\n' % _commentlines(statemsg), label=label)
820 conmsg = _conflictsmsg(repo)
818 conmsg = _conflictsmsg(repo)
821 if conmsg:
819 if conmsg:
822 fm.plain(b'%s\n' % conmsg, label=label)
820 fm.plain(b'%s\n' % conmsg, label=label)
823 if helpfulmsg:
821 if helpfulmsg:
824 fm.plain(b'%s\n' % _commentlines(helpfulmsg), label=label)
822 fm.plain(b'%s\n' % _commentlines(helpfulmsg), label=label)
825
823
826
824
827 def findpossible(cmd, table, strict=False):
825 def findpossible(cmd, table, strict=False):
828 """
826 """
829 Return cmd -> (aliases, command table entry)
827 Return cmd -> (aliases, command table entry)
830 for each matching command.
828 for each matching command.
831 Return debug commands (or their aliases) only if no normal command matches.
829 Return debug commands (or their aliases) only if no normal command matches.
832 """
830 """
833 choice = {}
831 choice = {}
834 debugchoice = {}
832 debugchoice = {}
835
833
836 if cmd in table:
834 if cmd in table:
837 # short-circuit exact matches, "log" alias beats "log|history"
835 # short-circuit exact matches, "log" alias beats "log|history"
838 keys = [cmd]
836 keys = [cmd]
839 else:
837 else:
840 keys = table.keys()
838 keys = table.keys()
841
839
842 allcmds = []
840 allcmds = []
843 for e in keys:
841 for e in keys:
844 aliases = parsealiases(e)
842 aliases = parsealiases(e)
845 allcmds.extend(aliases)
843 allcmds.extend(aliases)
846 found = None
844 found = None
847 if cmd in aliases:
845 if cmd in aliases:
848 found = cmd
846 found = cmd
849 elif not strict:
847 elif not strict:
850 for a in aliases:
848 for a in aliases:
851 if a.startswith(cmd):
849 if a.startswith(cmd):
852 found = a
850 found = a
853 break
851 break
854 if found is not None:
852 if found is not None:
855 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
853 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
856 debugchoice[found] = (aliases, table[e])
854 debugchoice[found] = (aliases, table[e])
857 else:
855 else:
858 choice[found] = (aliases, table[e])
856 choice[found] = (aliases, table[e])
859
857
860 if not choice and debugchoice:
858 if not choice and debugchoice:
861 choice = debugchoice
859 choice = debugchoice
862
860
863 return choice, allcmds
861 return choice, allcmds
864
862
865
863
866 def findcmd(cmd, table, strict=True):
864 def findcmd(cmd, table, strict=True):
867 """Return (aliases, command table entry) for command string."""
865 """Return (aliases, command table entry) for command string."""
868 choice, allcmds = findpossible(cmd, table, strict)
866 choice, allcmds = findpossible(cmd, table, strict)
869
867
870 if cmd in choice:
868 if cmd in choice:
871 return choice[cmd]
869 return choice[cmd]
872
870
873 if len(choice) > 1:
871 if len(choice) > 1:
874 clist = sorted(choice)
872 clist = sorted(choice)
875 raise error.AmbiguousCommand(cmd, clist)
873 raise error.AmbiguousCommand(cmd, clist)
876
874
877 if choice:
875 if choice:
878 return list(choice.values())[0]
876 return list(choice.values())[0]
879
877
880 raise error.UnknownCommand(cmd, allcmds)
878 raise error.UnknownCommand(cmd, allcmds)
881
879
882
880
883 def changebranch(ui, repo, revs, label):
881 def changebranch(ui, repo, revs, label):
884 """ Change the branch name of given revs to label """
882 """ Change the branch name of given revs to label """
885
883
886 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
884 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
887 # abort in case of uncommitted merge or dirty wdir
885 # abort in case of uncommitted merge or dirty wdir
888 bailifchanged(repo)
886 bailifchanged(repo)
889 revs = scmutil.revrange(repo, revs)
887 revs = scmutil.revrange(repo, revs)
890 if not revs:
888 if not revs:
891 raise error.Abort(b"empty revision set")
889 raise error.Abort(b"empty revision set")
892 roots = repo.revs(b'roots(%ld)', revs)
890 roots = repo.revs(b'roots(%ld)', revs)
893 if len(roots) > 1:
891 if len(roots) > 1:
894 raise error.Abort(
892 raise error.Abort(
895 _(b"cannot change branch of non-linear revisions")
893 _(b"cannot change branch of non-linear revisions")
896 )
894 )
897 rewriteutil.precheck(repo, revs, b'change branch of')
895 rewriteutil.precheck(repo, revs, b'change branch of')
898
896
899 root = repo[roots.first()]
897 root = repo[roots.first()]
900 rpb = {parent.branch() for parent in root.parents()}
898 rpb = {parent.branch() for parent in root.parents()}
901 if label not in rpb and label in repo.branchmap():
899 if label not in rpb and label in repo.branchmap():
902 raise error.Abort(_(b"a branch of the same name already exists"))
900 raise error.Abort(_(b"a branch of the same name already exists"))
903
901
904 if repo.revs(b'obsolete() and %ld', revs):
902 if repo.revs(b'obsolete() and %ld', revs):
905 raise error.Abort(
903 raise error.Abort(
906 _(b"cannot change branch of a obsolete changeset")
904 _(b"cannot change branch of a obsolete changeset")
907 )
905 )
908
906
909 # make sure only topological heads
907 # make sure only topological heads
910 if repo.revs(b'heads(%ld) - head()', revs):
908 if repo.revs(b'heads(%ld) - head()', revs):
911 raise error.Abort(_(b"cannot change branch in middle of a stack"))
909 raise error.Abort(_(b"cannot change branch in middle of a stack"))
912
910
913 replacements = {}
911 replacements = {}
914 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
912 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
915 # mercurial.subrepo -> mercurial.cmdutil
913 # mercurial.subrepo -> mercurial.cmdutil
916 from . import context
914 from . import context
917
915
918 for rev in revs:
916 for rev in revs:
919 ctx = repo[rev]
917 ctx = repo[rev]
920 oldbranch = ctx.branch()
918 oldbranch = ctx.branch()
921 # check if ctx has same branch
919 # check if ctx has same branch
922 if oldbranch == label:
920 if oldbranch == label:
923 continue
921 continue
924
922
925 def filectxfn(repo, newctx, path):
923 def filectxfn(repo, newctx, path):
926 try:
924 try:
927 return ctx[path]
925 return ctx[path]
928 except error.ManifestLookupError:
926 except error.ManifestLookupError:
929 return None
927 return None
930
928
931 ui.debug(
929 ui.debug(
932 b"changing branch of '%s' from '%s' to '%s'\n"
930 b"changing branch of '%s' from '%s' to '%s'\n"
933 % (hex(ctx.node()), oldbranch, label)
931 % (hex(ctx.node()), oldbranch, label)
934 )
932 )
935 extra = ctx.extra()
933 extra = ctx.extra()
936 extra[b'branch_change'] = hex(ctx.node())
934 extra[b'branch_change'] = hex(ctx.node())
937 # While changing branch of set of linear commits, make sure that
935 # While changing branch of set of linear commits, make sure that
938 # we base our commits on new parent rather than old parent which
936 # we base our commits on new parent rather than old parent which
939 # was obsoleted while changing the branch
937 # was obsoleted while changing the branch
940 p1 = ctx.p1().node()
938 p1 = ctx.p1().node()
941 p2 = ctx.p2().node()
939 p2 = ctx.p2().node()
942 if p1 in replacements:
940 if p1 in replacements:
943 p1 = replacements[p1][0]
941 p1 = replacements[p1][0]
944 if p2 in replacements:
942 if p2 in replacements:
945 p2 = replacements[p2][0]
943 p2 = replacements[p2][0]
946
944
947 mc = context.memctx(
945 mc = context.memctx(
948 repo,
946 repo,
949 (p1, p2),
947 (p1, p2),
950 ctx.description(),
948 ctx.description(),
951 ctx.files(),
949 ctx.files(),
952 filectxfn,
950 filectxfn,
953 user=ctx.user(),
951 user=ctx.user(),
954 date=ctx.date(),
952 date=ctx.date(),
955 extra=extra,
953 extra=extra,
956 branch=label,
954 branch=label,
957 )
955 )
958
956
959 newnode = repo.commitctx(mc)
957 newnode = repo.commitctx(mc)
960 replacements[ctx.node()] = (newnode,)
958 replacements[ctx.node()] = (newnode,)
961 ui.debug(b'new node id is %s\n' % hex(newnode))
959 ui.debug(b'new node id is %s\n' % hex(newnode))
962
960
963 # create obsmarkers and move bookmarks
961 # create obsmarkers and move bookmarks
964 scmutil.cleanupnodes(
962 scmutil.cleanupnodes(
965 repo, replacements, b'branch-change', fixphase=True
963 repo, replacements, b'branch-change', fixphase=True
966 )
964 )
967
965
968 # move the working copy too
966 # move the working copy too
969 wctx = repo[None]
967 wctx = repo[None]
970 # in-progress merge is a bit too complex for now.
968 # in-progress merge is a bit too complex for now.
971 if len(wctx.parents()) == 1:
969 if len(wctx.parents()) == 1:
972 newid = replacements.get(wctx.p1().node())
970 newid = replacements.get(wctx.p1().node())
973 if newid is not None:
971 if newid is not None:
974 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
972 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
975 # mercurial.cmdutil
973 # mercurial.cmdutil
976 from . import hg
974 from . import hg
977
975
978 hg.update(repo, newid[0], quietempty=True)
976 hg.update(repo, newid[0], quietempty=True)
979
977
980 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
978 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
981
979
982
980
983 def findrepo(p):
981 def findrepo(p):
984 while not os.path.isdir(os.path.join(p, b".hg")):
982 while not os.path.isdir(os.path.join(p, b".hg")):
985 oldp, p = p, os.path.dirname(p)
983 oldp, p = p, os.path.dirname(p)
986 if p == oldp:
984 if p == oldp:
987 return None
985 return None
988
986
989 return p
987 return p
990
988
991
989
992 def bailifchanged(repo, merge=True, hint=None):
990 def bailifchanged(repo, merge=True, hint=None):
993 """ enforce the precondition that working directory must be clean.
991 """ enforce the precondition that working directory must be clean.
994
992
995 'merge' can be set to false if a pending uncommitted merge should be
993 'merge' can be set to false if a pending uncommitted merge should be
996 ignored (such as when 'update --check' runs).
994 ignored (such as when 'update --check' runs).
997
995
998 'hint' is the usual hint given to Abort exception.
996 'hint' is the usual hint given to Abort exception.
999 """
997 """
1000
998
1001 if merge and repo.dirstate.p2() != nullid:
999 if merge and repo.dirstate.p2() != nullid:
1002 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1000 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1003 st = repo.status()
1001 st = repo.status()
1004 if st.modified or st.added or st.removed or st.deleted:
1002 if st.modified or st.added or st.removed or st.deleted:
1005 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1003 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1006 ctx = repo[None]
1004 ctx = repo[None]
1007 for s in sorted(ctx.substate):
1005 for s in sorted(ctx.substate):
1008 ctx.sub(s).bailifchanged(hint=hint)
1006 ctx.sub(s).bailifchanged(hint=hint)
1009
1007
1010
1008
1011 def logmessage(ui, opts):
1009 def logmessage(ui, opts):
1012 """ get the log message according to -m and -l option """
1010 """ get the log message according to -m and -l option """
1013 message = opts.get(b'message')
1011 message = opts.get(b'message')
1014 logfile = opts.get(b'logfile')
1012 logfile = opts.get(b'logfile')
1015
1013
1016 if message and logfile:
1014 if message and logfile:
1017 raise error.Abort(
1015 raise error.Abort(
1018 _(b'options --message and --logfile are mutually exclusive')
1016 _(b'options --message and --logfile are mutually exclusive')
1019 )
1017 )
1020 if not message and logfile:
1018 if not message and logfile:
1021 try:
1019 try:
1022 if isstdiofilename(logfile):
1020 if isstdiofilename(logfile):
1023 message = ui.fin.read()
1021 message = ui.fin.read()
1024 else:
1022 else:
1025 message = b'\n'.join(util.readfile(logfile).splitlines())
1023 message = b'\n'.join(util.readfile(logfile).splitlines())
1026 except IOError as inst:
1024 except IOError as inst:
1027 raise error.Abort(
1025 raise error.Abort(
1028 _(b"can't read commit message '%s': %s")
1026 _(b"can't read commit message '%s': %s")
1029 % (logfile, encoding.strtolocal(inst.strerror))
1027 % (logfile, encoding.strtolocal(inst.strerror))
1030 )
1028 )
1031 return message
1029 return message
1032
1030
1033
1031
1034 def mergeeditform(ctxorbool, baseformname):
1032 def mergeeditform(ctxorbool, baseformname):
1035 """return appropriate editform name (referencing a committemplate)
1033 """return appropriate editform name (referencing a committemplate)
1036
1034
1037 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1035 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1038 merging is committed.
1036 merging is committed.
1039
1037
1040 This returns baseformname with '.merge' appended if it is a merge,
1038 This returns baseformname with '.merge' appended if it is a merge,
1041 otherwise '.normal' is appended.
1039 otherwise '.normal' is appended.
1042 """
1040 """
1043 if isinstance(ctxorbool, bool):
1041 if isinstance(ctxorbool, bool):
1044 if ctxorbool:
1042 if ctxorbool:
1045 return baseformname + b".merge"
1043 return baseformname + b".merge"
1046 elif len(ctxorbool.parents()) > 1:
1044 elif len(ctxorbool.parents()) > 1:
1047 return baseformname + b".merge"
1045 return baseformname + b".merge"
1048
1046
1049 return baseformname + b".normal"
1047 return baseformname + b".normal"
1050
1048
1051
1049
1052 def getcommiteditor(
1050 def getcommiteditor(
1053 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1051 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1054 ):
1052 ):
1055 """get appropriate commit message editor according to '--edit' option
1053 """get appropriate commit message editor according to '--edit' option
1056
1054
1057 'finishdesc' is a function to be called with edited commit message
1055 'finishdesc' is a function to be called with edited commit message
1058 (= 'description' of the new changeset) just after editing, but
1056 (= 'description' of the new changeset) just after editing, but
1059 before checking empty-ness. It should return actual text to be
1057 before checking empty-ness. It should return actual text to be
1060 stored into history. This allows to change description before
1058 stored into history. This allows to change description before
1061 storing.
1059 storing.
1062
1060
1063 'extramsg' is a extra message to be shown in the editor instead of
1061 'extramsg' is a extra message to be shown in the editor instead of
1064 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1062 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1065 is automatically added.
1063 is automatically added.
1066
1064
1067 'editform' is a dot-separated list of names, to distinguish
1065 'editform' is a dot-separated list of names, to distinguish
1068 the purpose of commit text editing.
1066 the purpose of commit text editing.
1069
1067
1070 'getcommiteditor' returns 'commitforceeditor' regardless of
1068 'getcommiteditor' returns 'commitforceeditor' regardless of
1071 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1069 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1072 they are specific for usage in MQ.
1070 they are specific for usage in MQ.
1073 """
1071 """
1074 if edit or finishdesc or extramsg:
1072 if edit or finishdesc or extramsg:
1075 return lambda r, c, s: commitforceeditor(
1073 return lambda r, c, s: commitforceeditor(
1076 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1074 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1077 )
1075 )
1078 elif editform:
1076 elif editform:
1079 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1077 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1080 else:
1078 else:
1081 return commiteditor
1079 return commiteditor
1082
1080
1083
1081
1084 def _escapecommandtemplate(tmpl):
1082 def _escapecommandtemplate(tmpl):
1085 parts = []
1083 parts = []
1086 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1084 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1087 if typ == b'string':
1085 if typ == b'string':
1088 parts.append(stringutil.escapestr(tmpl[start:end]))
1086 parts.append(stringutil.escapestr(tmpl[start:end]))
1089 else:
1087 else:
1090 parts.append(tmpl[start:end])
1088 parts.append(tmpl[start:end])
1091 return b''.join(parts)
1089 return b''.join(parts)
1092
1090
1093
1091
1094 def rendercommandtemplate(ui, tmpl, props):
1092 def rendercommandtemplate(ui, tmpl, props):
1095 r"""Expand a literal template 'tmpl' in a way suitable for command line
1093 r"""Expand a literal template 'tmpl' in a way suitable for command line
1096
1094
1097 '\' in outermost string is not taken as an escape character because it
1095 '\' in outermost string is not taken as an escape character because it
1098 is a directory separator on Windows.
1096 is a directory separator on Windows.
1099
1097
1100 >>> from . import ui as uimod
1098 >>> from . import ui as uimod
1101 >>> ui = uimod.ui()
1099 >>> ui = uimod.ui()
1102 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1100 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1103 'c:\\foo'
1101 'c:\\foo'
1104 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1102 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1105 'c:{path}'
1103 'c:{path}'
1106 """
1104 """
1107 if not tmpl:
1105 if not tmpl:
1108 return tmpl
1106 return tmpl
1109 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1107 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1110 return t.renderdefault(props)
1108 return t.renderdefault(props)
1111
1109
1112
1110
1113 def rendertemplate(ctx, tmpl, props=None):
1111 def rendertemplate(ctx, tmpl, props=None):
1114 """Expand a literal template 'tmpl' byte-string against one changeset
1112 """Expand a literal template 'tmpl' byte-string against one changeset
1115
1113
1116 Each props item must be a stringify-able value or a callable returning
1114 Each props item must be a stringify-able value or a callable returning
1117 such value, i.e. no bare list nor dict should be passed.
1115 such value, i.e. no bare list nor dict should be passed.
1118 """
1116 """
1119 repo = ctx.repo()
1117 repo = ctx.repo()
1120 tres = formatter.templateresources(repo.ui, repo)
1118 tres = formatter.templateresources(repo.ui, repo)
1121 t = formatter.maketemplater(
1119 t = formatter.maketemplater(
1122 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1120 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1123 )
1121 )
1124 mapping = {b'ctx': ctx}
1122 mapping = {b'ctx': ctx}
1125 if props:
1123 if props:
1126 mapping.update(props)
1124 mapping.update(props)
1127 return t.renderdefault(mapping)
1125 return t.renderdefault(mapping)
1128
1126
1129
1127
1130 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1128 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1131 r"""Convert old-style filename format string to template string
1129 r"""Convert old-style filename format string to template string
1132
1130
1133 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1131 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1134 'foo-{reporoot|basename}-{seqno}.patch'
1132 'foo-{reporoot|basename}-{seqno}.patch'
1135 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1133 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1136 '{rev}{tags % "{tag}"}{node}'
1134 '{rev}{tags % "{tag}"}{node}'
1137
1135
1138 '\' in outermost strings has to be escaped because it is a directory
1136 '\' in outermost strings has to be escaped because it is a directory
1139 separator on Windows:
1137 separator on Windows:
1140
1138
1141 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1139 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1142 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1140 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1143 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1141 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1144 '\\\\\\\\foo\\\\bar.patch'
1142 '\\\\\\\\foo\\\\bar.patch'
1145 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1143 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1146 '\\\\{tags % "{tag}"}'
1144 '\\\\{tags % "{tag}"}'
1147
1145
1148 but inner strings follow the template rules (i.e. '\' is taken as an
1146 but inner strings follow the template rules (i.e. '\' is taken as an
1149 escape character):
1147 escape character):
1150
1148
1151 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1149 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1152 '{"c:\\tmp"}'
1150 '{"c:\\tmp"}'
1153 """
1151 """
1154 expander = {
1152 expander = {
1155 b'H': b'{node}',
1153 b'H': b'{node}',
1156 b'R': b'{rev}',
1154 b'R': b'{rev}',
1157 b'h': b'{node|short}',
1155 b'h': b'{node|short}',
1158 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1156 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1159 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1157 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1160 b'%': b'%',
1158 b'%': b'%',
1161 b'b': b'{reporoot|basename}',
1159 b'b': b'{reporoot|basename}',
1162 }
1160 }
1163 if total is not None:
1161 if total is not None:
1164 expander[b'N'] = b'{total}'
1162 expander[b'N'] = b'{total}'
1165 if seqno is not None:
1163 if seqno is not None:
1166 expander[b'n'] = b'{seqno}'
1164 expander[b'n'] = b'{seqno}'
1167 if total is not None and seqno is not None:
1165 if total is not None and seqno is not None:
1168 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1166 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1169 if pathname is not None:
1167 if pathname is not None:
1170 expander[b's'] = b'{pathname|basename}'
1168 expander[b's'] = b'{pathname|basename}'
1171 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1169 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1172 expander[b'p'] = b'{pathname}'
1170 expander[b'p'] = b'{pathname}'
1173
1171
1174 newname = []
1172 newname = []
1175 for typ, start, end in templater.scantemplate(pat, raw=True):
1173 for typ, start, end in templater.scantemplate(pat, raw=True):
1176 if typ != b'string':
1174 if typ != b'string':
1177 newname.append(pat[start:end])
1175 newname.append(pat[start:end])
1178 continue
1176 continue
1179 i = start
1177 i = start
1180 while i < end:
1178 while i < end:
1181 n = pat.find(b'%', i, end)
1179 n = pat.find(b'%', i, end)
1182 if n < 0:
1180 if n < 0:
1183 newname.append(stringutil.escapestr(pat[i:end]))
1181 newname.append(stringutil.escapestr(pat[i:end]))
1184 break
1182 break
1185 newname.append(stringutil.escapestr(pat[i:n]))
1183 newname.append(stringutil.escapestr(pat[i:n]))
1186 if n + 2 > end:
1184 if n + 2 > end:
1187 raise error.Abort(
1185 raise error.Abort(
1188 _(b"incomplete format spec in output filename")
1186 _(b"incomplete format spec in output filename")
1189 )
1187 )
1190 c = pat[n + 1 : n + 2]
1188 c = pat[n + 1 : n + 2]
1191 i = n + 2
1189 i = n + 2
1192 try:
1190 try:
1193 newname.append(expander[c])
1191 newname.append(expander[c])
1194 except KeyError:
1192 except KeyError:
1195 raise error.Abort(
1193 raise error.Abort(
1196 _(b"invalid format spec '%%%s' in output filename") % c
1194 _(b"invalid format spec '%%%s' in output filename") % c
1197 )
1195 )
1198 return b''.join(newname)
1196 return b''.join(newname)
1199
1197
1200
1198
1201 def makefilename(ctx, pat, **props):
1199 def makefilename(ctx, pat, **props):
1202 if not pat:
1200 if not pat:
1203 return pat
1201 return pat
1204 tmpl = _buildfntemplate(pat, **props)
1202 tmpl = _buildfntemplate(pat, **props)
1205 # BUG: alias expansion shouldn't be made against template fragments
1203 # BUG: alias expansion shouldn't be made against template fragments
1206 # rewritten from %-format strings, but we have no easy way to partially
1204 # rewritten from %-format strings, but we have no easy way to partially
1207 # disable the expansion.
1205 # disable the expansion.
1208 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1206 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1209
1207
1210
1208
1211 def isstdiofilename(pat):
1209 def isstdiofilename(pat):
1212 """True if the given pat looks like a filename denoting stdin/stdout"""
1210 """True if the given pat looks like a filename denoting stdin/stdout"""
1213 return not pat or pat == b'-'
1211 return not pat or pat == b'-'
1214
1212
1215
1213
1216 class _unclosablefile(object):
1214 class _unclosablefile(object):
1217 def __init__(self, fp):
1215 def __init__(self, fp):
1218 self._fp = fp
1216 self._fp = fp
1219
1217
1220 def close(self):
1218 def close(self):
1221 pass
1219 pass
1222
1220
1223 def __iter__(self):
1221 def __iter__(self):
1224 return iter(self._fp)
1222 return iter(self._fp)
1225
1223
1226 def __getattr__(self, attr):
1224 def __getattr__(self, attr):
1227 return getattr(self._fp, attr)
1225 return getattr(self._fp, attr)
1228
1226
1229 def __enter__(self):
1227 def __enter__(self):
1230 return self
1228 return self
1231
1229
1232 def __exit__(self, exc_type, exc_value, exc_tb):
1230 def __exit__(self, exc_type, exc_value, exc_tb):
1233 pass
1231 pass
1234
1232
1235
1233
1236 def makefileobj(ctx, pat, mode=b'wb', **props):
1234 def makefileobj(ctx, pat, mode=b'wb', **props):
1237 writable = mode not in (b'r', b'rb')
1235 writable = mode not in (b'r', b'rb')
1238
1236
1239 if isstdiofilename(pat):
1237 if isstdiofilename(pat):
1240 repo = ctx.repo()
1238 repo = ctx.repo()
1241 if writable:
1239 if writable:
1242 fp = repo.ui.fout
1240 fp = repo.ui.fout
1243 else:
1241 else:
1244 fp = repo.ui.fin
1242 fp = repo.ui.fin
1245 return _unclosablefile(fp)
1243 return _unclosablefile(fp)
1246 fn = makefilename(ctx, pat, **props)
1244 fn = makefilename(ctx, pat, **props)
1247 return open(fn, mode)
1245 return open(fn, mode)
1248
1246
1249
1247
1250 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1248 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1251 """opens the changelog, manifest, a filelog or a given revlog"""
1249 """opens the changelog, manifest, a filelog or a given revlog"""
1252 cl = opts[b'changelog']
1250 cl = opts[b'changelog']
1253 mf = opts[b'manifest']
1251 mf = opts[b'manifest']
1254 dir = opts[b'dir']
1252 dir = opts[b'dir']
1255 msg = None
1253 msg = None
1256 if cl and mf:
1254 if cl and mf:
1257 msg = _(b'cannot specify --changelog and --manifest at the same time')
1255 msg = _(b'cannot specify --changelog and --manifest at the same time')
1258 elif cl and dir:
1256 elif cl and dir:
1259 msg = _(b'cannot specify --changelog and --dir at the same time')
1257 msg = _(b'cannot specify --changelog and --dir at the same time')
1260 elif cl or mf or dir:
1258 elif cl or mf or dir:
1261 if file_:
1259 if file_:
1262 msg = _(b'cannot specify filename with --changelog or --manifest')
1260 msg = _(b'cannot specify filename with --changelog or --manifest')
1263 elif not repo:
1261 elif not repo:
1264 msg = _(
1262 msg = _(
1265 b'cannot specify --changelog or --manifest or --dir '
1263 b'cannot specify --changelog or --manifest or --dir '
1266 b'without a repository'
1264 b'without a repository'
1267 )
1265 )
1268 if msg:
1266 if msg:
1269 raise error.Abort(msg)
1267 raise error.Abort(msg)
1270
1268
1271 r = None
1269 r = None
1272 if repo:
1270 if repo:
1273 if cl:
1271 if cl:
1274 r = repo.unfiltered().changelog
1272 r = repo.unfiltered().changelog
1275 elif dir:
1273 elif dir:
1276 if b'treemanifest' not in repo.requirements:
1274 if b'treemanifest' not in repo.requirements:
1277 raise error.Abort(
1275 raise error.Abort(
1278 _(
1276 _(
1279 b"--dir can only be used on repos with "
1277 b"--dir can only be used on repos with "
1280 b"treemanifest enabled"
1278 b"treemanifest enabled"
1281 )
1279 )
1282 )
1280 )
1283 if not dir.endswith(b'/'):
1281 if not dir.endswith(b'/'):
1284 dir = dir + b'/'
1282 dir = dir + b'/'
1285 dirlog = repo.manifestlog.getstorage(dir)
1283 dirlog = repo.manifestlog.getstorage(dir)
1286 if len(dirlog):
1284 if len(dirlog):
1287 r = dirlog
1285 r = dirlog
1288 elif mf:
1286 elif mf:
1289 r = repo.manifestlog.getstorage(b'')
1287 r = repo.manifestlog.getstorage(b'')
1290 elif file_:
1288 elif file_:
1291 filelog = repo.file(file_)
1289 filelog = repo.file(file_)
1292 if len(filelog):
1290 if len(filelog):
1293 r = filelog
1291 r = filelog
1294
1292
1295 # Not all storage may be revlogs. If requested, try to return an actual
1293 # Not all storage may be revlogs. If requested, try to return an actual
1296 # revlog instance.
1294 # revlog instance.
1297 if returnrevlog:
1295 if returnrevlog:
1298 if isinstance(r, revlog.revlog):
1296 if isinstance(r, revlog.revlog):
1299 pass
1297 pass
1300 elif util.safehasattr(r, b'_revlog'):
1298 elif util.safehasattr(r, b'_revlog'):
1301 r = r._revlog # pytype: disable=attribute-error
1299 r = r._revlog # pytype: disable=attribute-error
1302 elif r is not None:
1300 elif r is not None:
1303 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1301 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1304
1302
1305 if not r:
1303 if not r:
1306 if not returnrevlog:
1304 if not returnrevlog:
1307 raise error.Abort(_(b'cannot give path to non-revlog'))
1305 raise error.Abort(_(b'cannot give path to non-revlog'))
1308
1306
1309 if not file_:
1307 if not file_:
1310 raise error.CommandError(cmd, _(b'invalid arguments'))
1308 raise error.CommandError(cmd, _(b'invalid arguments'))
1311 if not os.path.isfile(file_):
1309 if not os.path.isfile(file_):
1312 raise error.Abort(_(b"revlog '%s' not found") % file_)
1310 raise error.Abort(_(b"revlog '%s' not found") % file_)
1313 r = revlog.revlog(
1311 r = revlog.revlog(
1314 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1312 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1315 )
1313 )
1316 return r
1314 return r
1317
1315
1318
1316
1319 def openrevlog(repo, cmd, file_, opts):
1317 def openrevlog(repo, cmd, file_, opts):
1320 """Obtain a revlog backing storage of an item.
1318 """Obtain a revlog backing storage of an item.
1321
1319
1322 This is similar to ``openstorage()`` except it always returns a revlog.
1320 This is similar to ``openstorage()`` except it always returns a revlog.
1323
1321
1324 In most cases, a caller cares about the main storage object - not the
1322 In most cases, a caller cares about the main storage object - not the
1325 revlog backing it. Therefore, this function should only be used by code
1323 revlog backing it. Therefore, this function should only be used by code
1326 that needs to examine low-level revlog implementation details. e.g. debug
1324 that needs to examine low-level revlog implementation details. e.g. debug
1327 commands.
1325 commands.
1328 """
1326 """
1329 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1327 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1330
1328
1331
1329
1332 def copy(ui, repo, pats, opts, rename=False):
1330 def copy(ui, repo, pats, opts, rename=False):
1333 # called with the repo lock held
1331 # called with the repo lock held
1334 #
1332 #
1335 # hgsep => pathname that uses "/" to separate directories
1333 # hgsep => pathname that uses "/" to separate directories
1336 # ossep => pathname that uses os.sep to separate directories
1334 # ossep => pathname that uses os.sep to separate directories
1337 cwd = repo.getcwd()
1335 cwd = repo.getcwd()
1338 targets = {}
1336 targets = {}
1339 after = opts.get(b"after")
1337 after = opts.get(b"after")
1340 dryrun = opts.get(b"dry_run")
1338 dryrun = opts.get(b"dry_run")
1341 wctx = repo[None]
1339 wctx = repo[None]
1342
1340
1343 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1341 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1344
1342
1345 def walkpat(pat):
1343 def walkpat(pat):
1346 srcs = []
1344 srcs = []
1347 if after:
1345 if after:
1348 badstates = b'?'
1346 badstates = b'?'
1349 else:
1347 else:
1350 badstates = b'?r'
1348 badstates = b'?r'
1351 m = scmutil.match(wctx, [pat], opts, globbed=True)
1349 m = scmutil.match(wctx, [pat], opts, globbed=True)
1352 for abs in wctx.walk(m):
1350 for abs in wctx.walk(m):
1353 state = repo.dirstate[abs]
1351 state = repo.dirstate[abs]
1354 rel = uipathfn(abs)
1352 rel = uipathfn(abs)
1355 exact = m.exact(abs)
1353 exact = m.exact(abs)
1356 if state in badstates:
1354 if state in badstates:
1357 if exact and state == b'?':
1355 if exact and state == b'?':
1358 ui.warn(_(b'%s: not copying - file is not managed\n') % rel)
1356 ui.warn(_(b'%s: not copying - file is not managed\n') % rel)
1359 if exact and state == b'r':
1357 if exact and state == b'r':
1360 ui.warn(
1358 ui.warn(
1361 _(
1359 _(
1362 b'%s: not copying - file has been marked for'
1360 b'%s: not copying - file has been marked for'
1363 b' remove\n'
1361 b' remove\n'
1364 )
1362 )
1365 % rel
1363 % rel
1366 )
1364 )
1367 continue
1365 continue
1368 # abs: hgsep
1366 # abs: hgsep
1369 # rel: ossep
1367 # rel: ossep
1370 srcs.append((abs, rel, exact))
1368 srcs.append((abs, rel, exact))
1371 return srcs
1369 return srcs
1372
1370
1373 # abssrc: hgsep
1371 # abssrc: hgsep
1374 # relsrc: ossep
1372 # relsrc: ossep
1375 # otarget: ossep
1373 # otarget: ossep
1376 def copyfile(abssrc, relsrc, otarget, exact):
1374 def copyfile(abssrc, relsrc, otarget, exact):
1377 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1375 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1378 if b'/' in abstarget:
1376 if b'/' in abstarget:
1379 # We cannot normalize abstarget itself, this would prevent
1377 # We cannot normalize abstarget itself, this would prevent
1380 # case only renames, like a => A.
1378 # case only renames, like a => A.
1381 abspath, absname = abstarget.rsplit(b'/', 1)
1379 abspath, absname = abstarget.rsplit(b'/', 1)
1382 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1380 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1383 reltarget = repo.pathto(abstarget, cwd)
1381 reltarget = repo.pathto(abstarget, cwd)
1384 target = repo.wjoin(abstarget)
1382 target = repo.wjoin(abstarget)
1385 src = repo.wjoin(abssrc)
1383 src = repo.wjoin(abssrc)
1386 state = repo.dirstate[abstarget]
1384 state = repo.dirstate[abstarget]
1387
1385
1388 scmutil.checkportable(ui, abstarget)
1386 scmutil.checkportable(ui, abstarget)
1389
1387
1390 # check for collisions
1388 # check for collisions
1391 prevsrc = targets.get(abstarget)
1389 prevsrc = targets.get(abstarget)
1392 if prevsrc is not None:
1390 if prevsrc is not None:
1393 ui.warn(
1391 ui.warn(
1394 _(b'%s: not overwriting - %s collides with %s\n')
1392 _(b'%s: not overwriting - %s collides with %s\n')
1395 % (
1393 % (
1396 reltarget,
1394 reltarget,
1397 repo.pathto(abssrc, cwd),
1395 repo.pathto(abssrc, cwd),
1398 repo.pathto(prevsrc, cwd),
1396 repo.pathto(prevsrc, cwd),
1399 )
1397 )
1400 )
1398 )
1401 return True # report a failure
1399 return True # report a failure
1402
1400
1403 # check for overwrites
1401 # check for overwrites
1404 exists = os.path.lexists(target)
1402 exists = os.path.lexists(target)
1405 samefile = False
1403 samefile = False
1406 if exists and abssrc != abstarget:
1404 if exists and abssrc != abstarget:
1407 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1405 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1408 abstarget
1406 abstarget
1409 ):
1407 ):
1410 if not rename:
1408 if not rename:
1411 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1409 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1412 return True # report a failure
1410 return True # report a failure
1413 exists = False
1411 exists = False
1414 samefile = True
1412 samefile = True
1415
1413
1416 if not after and exists or after and state in b'mn':
1414 if not after and exists or after and state in b'mn':
1417 if not opts[b'force']:
1415 if not opts[b'force']:
1418 if state in b'mn':
1416 if state in b'mn':
1419 msg = _(b'%s: not overwriting - file already committed\n')
1417 msg = _(b'%s: not overwriting - file already committed\n')
1420 if after:
1418 if after:
1421 flags = b'--after --force'
1419 flags = b'--after --force'
1422 else:
1420 else:
1423 flags = b'--force'
1421 flags = b'--force'
1424 if rename:
1422 if rename:
1425 hint = (
1423 hint = (
1426 _(
1424 _(
1427 b"('hg rename %s' to replace the file by "
1425 b"('hg rename %s' to replace the file by "
1428 b'recording a rename)\n'
1426 b'recording a rename)\n'
1429 )
1427 )
1430 % flags
1428 % flags
1431 )
1429 )
1432 else:
1430 else:
1433 hint = (
1431 hint = (
1434 _(
1432 _(
1435 b"('hg copy %s' to replace the file by "
1433 b"('hg copy %s' to replace the file by "
1436 b'recording a copy)\n'
1434 b'recording a copy)\n'
1437 )
1435 )
1438 % flags
1436 % flags
1439 )
1437 )
1440 else:
1438 else:
1441 msg = _(b'%s: not overwriting - file exists\n')
1439 msg = _(b'%s: not overwriting - file exists\n')
1442 if rename:
1440 if rename:
1443 hint = _(
1441 hint = _(
1444 b"('hg rename --after' to record the rename)\n"
1442 b"('hg rename --after' to record the rename)\n"
1445 )
1443 )
1446 else:
1444 else:
1447 hint = _(b"('hg copy --after' to record the copy)\n")
1445 hint = _(b"('hg copy --after' to record the copy)\n")
1448 ui.warn(msg % reltarget)
1446 ui.warn(msg % reltarget)
1449 ui.warn(hint)
1447 ui.warn(hint)
1450 return True # report a failure
1448 return True # report a failure
1451
1449
1452 if after:
1450 if after:
1453 if not exists:
1451 if not exists:
1454 if rename:
1452 if rename:
1455 ui.warn(
1453 ui.warn(
1456 _(b'%s: not recording move - %s does not exist\n')
1454 _(b'%s: not recording move - %s does not exist\n')
1457 % (relsrc, reltarget)
1455 % (relsrc, reltarget)
1458 )
1456 )
1459 else:
1457 else:
1460 ui.warn(
1458 ui.warn(
1461 _(b'%s: not recording copy - %s does not exist\n')
1459 _(b'%s: not recording copy - %s does not exist\n')
1462 % (relsrc, reltarget)
1460 % (relsrc, reltarget)
1463 )
1461 )
1464 return True # report a failure
1462 return True # report a failure
1465 elif not dryrun:
1463 elif not dryrun:
1466 try:
1464 try:
1467 if exists:
1465 if exists:
1468 os.unlink(target)
1466 os.unlink(target)
1469 targetdir = os.path.dirname(target) or b'.'
1467 targetdir = os.path.dirname(target) or b'.'
1470 if not os.path.isdir(targetdir):
1468 if not os.path.isdir(targetdir):
1471 os.makedirs(targetdir)
1469 os.makedirs(targetdir)
1472 if samefile:
1470 if samefile:
1473 tmp = target + b"~hgrename"
1471 tmp = target + b"~hgrename"
1474 os.rename(src, tmp)
1472 os.rename(src, tmp)
1475 os.rename(tmp, target)
1473 os.rename(tmp, target)
1476 else:
1474 else:
1477 # Preserve stat info on renames, not on copies; this matches
1475 # Preserve stat info on renames, not on copies; this matches
1478 # Linux CLI behavior.
1476 # Linux CLI behavior.
1479 util.copyfile(src, target, copystat=rename)
1477 util.copyfile(src, target, copystat=rename)
1480 srcexists = True
1478 srcexists = True
1481 except IOError as inst:
1479 except IOError as inst:
1482 if inst.errno == errno.ENOENT:
1480 if inst.errno == errno.ENOENT:
1483 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1481 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1484 srcexists = False
1482 srcexists = False
1485 else:
1483 else:
1486 ui.warn(
1484 ui.warn(
1487 _(b'%s: cannot copy - %s\n')
1485 _(b'%s: cannot copy - %s\n')
1488 % (relsrc, encoding.strtolocal(inst.strerror))
1486 % (relsrc, encoding.strtolocal(inst.strerror))
1489 )
1487 )
1490 return True # report a failure
1488 return True # report a failure
1491
1489
1492 if ui.verbose or not exact:
1490 if ui.verbose or not exact:
1493 if rename:
1491 if rename:
1494 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1492 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1495 else:
1493 else:
1496 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1494 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1497
1495
1498 targets[abstarget] = abssrc
1496 targets[abstarget] = abssrc
1499
1497
1500 # fix up dirstate
1498 # fix up dirstate
1501 scmutil.dirstatecopy(
1499 scmutil.dirstatecopy(
1502 ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1500 ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1503 )
1501 )
1504 if rename and not dryrun:
1502 if rename and not dryrun:
1505 if not after and srcexists and not samefile:
1503 if not after and srcexists and not samefile:
1506 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1504 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1507 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1505 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1508 wctx.forget([abssrc])
1506 wctx.forget([abssrc])
1509
1507
1510 # pat: ossep
1508 # pat: ossep
1511 # dest ossep
1509 # dest ossep
1512 # srcs: list of (hgsep, hgsep, ossep, bool)
1510 # srcs: list of (hgsep, hgsep, ossep, bool)
1513 # return: function that takes hgsep and returns ossep
1511 # return: function that takes hgsep and returns ossep
1514 def targetpathfn(pat, dest, srcs):
1512 def targetpathfn(pat, dest, srcs):
1515 if os.path.isdir(pat):
1513 if os.path.isdir(pat):
1516 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1514 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1517 abspfx = util.localpath(abspfx)
1515 abspfx = util.localpath(abspfx)
1518 if destdirexists:
1516 if destdirexists:
1519 striplen = len(os.path.split(abspfx)[0])
1517 striplen = len(os.path.split(abspfx)[0])
1520 else:
1518 else:
1521 striplen = len(abspfx)
1519 striplen = len(abspfx)
1522 if striplen:
1520 if striplen:
1523 striplen += len(pycompat.ossep)
1521 striplen += len(pycompat.ossep)
1524 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1522 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1525 elif destdirexists:
1523 elif destdirexists:
1526 res = lambda p: os.path.join(
1524 res = lambda p: os.path.join(
1527 dest, os.path.basename(util.localpath(p))
1525 dest, os.path.basename(util.localpath(p))
1528 )
1526 )
1529 else:
1527 else:
1530 res = lambda p: dest
1528 res = lambda p: dest
1531 return res
1529 return res
1532
1530
1533 # pat: ossep
1531 # pat: ossep
1534 # dest ossep
1532 # dest ossep
1535 # srcs: list of (hgsep, hgsep, ossep, bool)
1533 # srcs: list of (hgsep, hgsep, ossep, bool)
1536 # return: function that takes hgsep and returns ossep
1534 # return: function that takes hgsep and returns ossep
1537 def targetpathafterfn(pat, dest, srcs):
1535 def targetpathafterfn(pat, dest, srcs):
1538 if matchmod.patkind(pat):
1536 if matchmod.patkind(pat):
1539 # a mercurial pattern
1537 # a mercurial pattern
1540 res = lambda p: os.path.join(
1538 res = lambda p: os.path.join(
1541 dest, os.path.basename(util.localpath(p))
1539 dest, os.path.basename(util.localpath(p))
1542 )
1540 )
1543 else:
1541 else:
1544 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1542 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1545 if len(abspfx) < len(srcs[0][0]):
1543 if len(abspfx) < len(srcs[0][0]):
1546 # A directory. Either the target path contains the last
1544 # A directory. Either the target path contains the last
1547 # component of the source path or it does not.
1545 # component of the source path or it does not.
1548 def evalpath(striplen):
1546 def evalpath(striplen):
1549 score = 0
1547 score = 0
1550 for s in srcs:
1548 for s in srcs:
1551 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1549 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1552 if os.path.lexists(t):
1550 if os.path.lexists(t):
1553 score += 1
1551 score += 1
1554 return score
1552 return score
1555
1553
1556 abspfx = util.localpath(abspfx)
1554 abspfx = util.localpath(abspfx)
1557 striplen = len(abspfx)
1555 striplen = len(abspfx)
1558 if striplen:
1556 if striplen:
1559 striplen += len(pycompat.ossep)
1557 striplen += len(pycompat.ossep)
1560 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1558 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1561 score = evalpath(striplen)
1559 score = evalpath(striplen)
1562 striplen1 = len(os.path.split(abspfx)[0])
1560 striplen1 = len(os.path.split(abspfx)[0])
1563 if striplen1:
1561 if striplen1:
1564 striplen1 += len(pycompat.ossep)
1562 striplen1 += len(pycompat.ossep)
1565 if evalpath(striplen1) > score:
1563 if evalpath(striplen1) > score:
1566 striplen = striplen1
1564 striplen = striplen1
1567 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1565 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1568 else:
1566 else:
1569 # a file
1567 # a file
1570 if destdirexists:
1568 if destdirexists:
1571 res = lambda p: os.path.join(
1569 res = lambda p: os.path.join(
1572 dest, os.path.basename(util.localpath(p))
1570 dest, os.path.basename(util.localpath(p))
1573 )
1571 )
1574 else:
1572 else:
1575 res = lambda p: dest
1573 res = lambda p: dest
1576 return res
1574 return res
1577
1575
1578 pats = scmutil.expandpats(pats)
1576 pats = scmutil.expandpats(pats)
1579 if not pats:
1577 if not pats:
1580 raise error.Abort(_(b'no source or destination specified'))
1578 raise error.Abort(_(b'no source or destination specified'))
1581 if len(pats) == 1:
1579 if len(pats) == 1:
1582 raise error.Abort(_(b'no destination specified'))
1580 raise error.Abort(_(b'no destination specified'))
1583 dest = pats.pop()
1581 dest = pats.pop()
1584 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1582 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1585 if not destdirexists:
1583 if not destdirexists:
1586 if len(pats) > 1 or matchmod.patkind(pats[0]):
1584 if len(pats) > 1 or matchmod.patkind(pats[0]):
1587 raise error.Abort(
1585 raise error.Abort(
1588 _(
1586 _(
1589 b'with multiple sources, destination must be an '
1587 b'with multiple sources, destination must be an '
1590 b'existing directory'
1588 b'existing directory'
1591 )
1589 )
1592 )
1590 )
1593 if util.endswithsep(dest):
1591 if util.endswithsep(dest):
1594 raise error.Abort(_(b'destination %s is not a directory') % dest)
1592 raise error.Abort(_(b'destination %s is not a directory') % dest)
1595
1593
1596 tfn = targetpathfn
1594 tfn = targetpathfn
1597 if after:
1595 if after:
1598 tfn = targetpathafterfn
1596 tfn = targetpathafterfn
1599 copylist = []
1597 copylist = []
1600 for pat in pats:
1598 for pat in pats:
1601 srcs = walkpat(pat)
1599 srcs = walkpat(pat)
1602 if not srcs:
1600 if not srcs:
1603 continue
1601 continue
1604 copylist.append((tfn(pat, dest, srcs), srcs))
1602 copylist.append((tfn(pat, dest, srcs), srcs))
1605 if not copylist:
1603 if not copylist:
1606 raise error.Abort(_(b'no files to copy'))
1604 raise error.Abort(_(b'no files to copy'))
1607
1605
1608 errors = 0
1606 errors = 0
1609 for targetpath, srcs in copylist:
1607 for targetpath, srcs in copylist:
1610 for abssrc, relsrc, exact in srcs:
1608 for abssrc, relsrc, exact in srcs:
1611 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1609 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1612 errors += 1
1610 errors += 1
1613
1611
1614 return errors != 0
1612 return errors != 0
1615
1613
1616
1614
1617 ## facility to let extension process additional data into an import patch
1615 ## facility to let extension process additional data into an import patch
1618 # list of identifier to be executed in order
1616 # list of identifier to be executed in order
1619 extrapreimport = [] # run before commit
1617 extrapreimport = [] # run before commit
1620 extrapostimport = [] # run after commit
1618 extrapostimport = [] # run after commit
1621 # mapping from identifier to actual import function
1619 # mapping from identifier to actual import function
1622 #
1620 #
1623 # 'preimport' are run before the commit is made and are provided the following
1621 # 'preimport' are run before the commit is made and are provided the following
1624 # arguments:
1622 # arguments:
1625 # - repo: the localrepository instance,
1623 # - repo: the localrepository instance,
1626 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1624 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1627 # - extra: the future extra dictionary of the changeset, please mutate it,
1625 # - extra: the future extra dictionary of the changeset, please mutate it,
1628 # - opts: the import options.
1626 # - opts: the import options.
1629 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1627 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1630 # mutation of in memory commit and more. Feel free to rework the code to get
1628 # mutation of in memory commit and more. Feel free to rework the code to get
1631 # there.
1629 # there.
1632 extrapreimportmap = {}
1630 extrapreimportmap = {}
1633 # 'postimport' are run after the commit is made and are provided the following
1631 # 'postimport' are run after the commit is made and are provided the following
1634 # argument:
1632 # argument:
1635 # - ctx: the changectx created by import.
1633 # - ctx: the changectx created by import.
1636 extrapostimportmap = {}
1634 extrapostimportmap = {}
1637
1635
1638
1636
1639 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1637 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1640 """Utility function used by commands.import to import a single patch
1638 """Utility function used by commands.import to import a single patch
1641
1639
1642 This function is explicitly defined here to help the evolve extension to
1640 This function is explicitly defined here to help the evolve extension to
1643 wrap this part of the import logic.
1641 wrap this part of the import logic.
1644
1642
1645 The API is currently a bit ugly because it a simple code translation from
1643 The API is currently a bit ugly because it a simple code translation from
1646 the import command. Feel free to make it better.
1644 the import command. Feel free to make it better.
1647
1645
1648 :patchdata: a dictionary containing parsed patch data (such as from
1646 :patchdata: a dictionary containing parsed patch data (such as from
1649 ``patch.extract()``)
1647 ``patch.extract()``)
1650 :parents: nodes that will be parent of the created commit
1648 :parents: nodes that will be parent of the created commit
1651 :opts: the full dict of option passed to the import command
1649 :opts: the full dict of option passed to the import command
1652 :msgs: list to save commit message to.
1650 :msgs: list to save commit message to.
1653 (used in case we need to save it when failing)
1651 (used in case we need to save it when failing)
1654 :updatefunc: a function that update a repo to a given node
1652 :updatefunc: a function that update a repo to a given node
1655 updatefunc(<repo>, <node>)
1653 updatefunc(<repo>, <node>)
1656 """
1654 """
1657 # avoid cycle context -> subrepo -> cmdutil
1655 # avoid cycle context -> subrepo -> cmdutil
1658 from . import context
1656 from . import context
1659
1657
1660 tmpname = patchdata.get(b'filename')
1658 tmpname = patchdata.get(b'filename')
1661 message = patchdata.get(b'message')
1659 message = patchdata.get(b'message')
1662 user = opts.get(b'user') or patchdata.get(b'user')
1660 user = opts.get(b'user') or patchdata.get(b'user')
1663 date = opts.get(b'date') or patchdata.get(b'date')
1661 date = opts.get(b'date') or patchdata.get(b'date')
1664 branch = patchdata.get(b'branch')
1662 branch = patchdata.get(b'branch')
1665 nodeid = patchdata.get(b'nodeid')
1663 nodeid = patchdata.get(b'nodeid')
1666 p1 = patchdata.get(b'p1')
1664 p1 = patchdata.get(b'p1')
1667 p2 = patchdata.get(b'p2')
1665 p2 = patchdata.get(b'p2')
1668
1666
1669 nocommit = opts.get(b'no_commit')
1667 nocommit = opts.get(b'no_commit')
1670 importbranch = opts.get(b'import_branch')
1668 importbranch = opts.get(b'import_branch')
1671 update = not opts.get(b'bypass')
1669 update = not opts.get(b'bypass')
1672 strip = opts[b"strip"]
1670 strip = opts[b"strip"]
1673 prefix = opts[b"prefix"]
1671 prefix = opts[b"prefix"]
1674 sim = float(opts.get(b'similarity') or 0)
1672 sim = float(opts.get(b'similarity') or 0)
1675
1673
1676 if not tmpname:
1674 if not tmpname:
1677 return None, None, False
1675 return None, None, False
1678
1676
1679 rejects = False
1677 rejects = False
1680
1678
1681 cmdline_message = logmessage(ui, opts)
1679 cmdline_message = logmessage(ui, opts)
1682 if cmdline_message:
1680 if cmdline_message:
1683 # pickup the cmdline msg
1681 # pickup the cmdline msg
1684 message = cmdline_message
1682 message = cmdline_message
1685 elif message:
1683 elif message:
1686 # pickup the patch msg
1684 # pickup the patch msg
1687 message = message.strip()
1685 message = message.strip()
1688 else:
1686 else:
1689 # launch the editor
1687 # launch the editor
1690 message = None
1688 message = None
1691 ui.debug(b'message:\n%s\n' % (message or b''))
1689 ui.debug(b'message:\n%s\n' % (message or b''))
1692
1690
1693 if len(parents) == 1:
1691 if len(parents) == 1:
1694 parents.append(repo[nullid])
1692 parents.append(repo[nullid])
1695 if opts.get(b'exact'):
1693 if opts.get(b'exact'):
1696 if not nodeid or not p1:
1694 if not nodeid or not p1:
1697 raise error.Abort(_(b'not a Mercurial patch'))
1695 raise error.Abort(_(b'not a Mercurial patch'))
1698 p1 = repo[p1]
1696 p1 = repo[p1]
1699 p2 = repo[p2 or nullid]
1697 p2 = repo[p2 or nullid]
1700 elif p2:
1698 elif p2:
1701 try:
1699 try:
1702 p1 = repo[p1]
1700 p1 = repo[p1]
1703 p2 = repo[p2]
1701 p2 = repo[p2]
1704 # Without any options, consider p2 only if the
1702 # Without any options, consider p2 only if the
1705 # patch is being applied on top of the recorded
1703 # patch is being applied on top of the recorded
1706 # first parent.
1704 # first parent.
1707 if p1 != parents[0]:
1705 if p1 != parents[0]:
1708 p1 = parents[0]
1706 p1 = parents[0]
1709 p2 = repo[nullid]
1707 p2 = repo[nullid]
1710 except error.RepoError:
1708 except error.RepoError:
1711 p1, p2 = parents
1709 p1, p2 = parents
1712 if p2.node() == nullid:
1710 if p2.node() == nullid:
1713 ui.warn(
1711 ui.warn(
1714 _(
1712 _(
1715 b"warning: import the patch as a normal revision\n"
1713 b"warning: import the patch as a normal revision\n"
1716 b"(use --exact to import the patch as a merge)\n"
1714 b"(use --exact to import the patch as a merge)\n"
1717 )
1715 )
1718 )
1716 )
1719 else:
1717 else:
1720 p1, p2 = parents
1718 p1, p2 = parents
1721
1719
1722 n = None
1720 n = None
1723 if update:
1721 if update:
1724 if p1 != parents[0]:
1722 if p1 != parents[0]:
1725 updatefunc(repo, p1.node())
1723 updatefunc(repo, p1.node())
1726 if p2 != parents[1]:
1724 if p2 != parents[1]:
1727 repo.setparents(p1.node(), p2.node())
1725 repo.setparents(p1.node(), p2.node())
1728
1726
1729 if opts.get(b'exact') or importbranch:
1727 if opts.get(b'exact') or importbranch:
1730 repo.dirstate.setbranch(branch or b'default')
1728 repo.dirstate.setbranch(branch or b'default')
1731
1729
1732 partial = opts.get(b'partial', False)
1730 partial = opts.get(b'partial', False)
1733 files = set()
1731 files = set()
1734 try:
1732 try:
1735 patch.patch(
1733 patch.patch(
1736 ui,
1734 ui,
1737 repo,
1735 repo,
1738 tmpname,
1736 tmpname,
1739 strip=strip,
1737 strip=strip,
1740 prefix=prefix,
1738 prefix=prefix,
1741 files=files,
1739 files=files,
1742 eolmode=None,
1740 eolmode=None,
1743 similarity=sim / 100.0,
1741 similarity=sim / 100.0,
1744 )
1742 )
1745 except error.PatchError as e:
1743 except error.PatchError as e:
1746 if not partial:
1744 if not partial:
1747 raise error.Abort(pycompat.bytestr(e))
1745 raise error.Abort(pycompat.bytestr(e))
1748 if partial:
1746 if partial:
1749 rejects = True
1747 rejects = True
1750
1748
1751 files = list(files)
1749 files = list(files)
1752 if nocommit:
1750 if nocommit:
1753 if message:
1751 if message:
1754 msgs.append(message)
1752 msgs.append(message)
1755 else:
1753 else:
1756 if opts.get(b'exact') or p2:
1754 if opts.get(b'exact') or p2:
1757 # If you got here, you either use --force and know what
1755 # If you got here, you either use --force and know what
1758 # you are doing or used --exact or a merge patch while
1756 # you are doing or used --exact or a merge patch while
1759 # being updated to its first parent.
1757 # being updated to its first parent.
1760 m = None
1758 m = None
1761 else:
1759 else:
1762 m = scmutil.matchfiles(repo, files or [])
1760 m = scmutil.matchfiles(repo, files or [])
1763 editform = mergeeditform(repo[None], b'import.normal')
1761 editform = mergeeditform(repo[None], b'import.normal')
1764 if opts.get(b'exact'):
1762 if opts.get(b'exact'):
1765 editor = None
1763 editor = None
1766 else:
1764 else:
1767 editor = getcommiteditor(
1765 editor = getcommiteditor(
1768 editform=editform, **pycompat.strkwargs(opts)
1766 editform=editform, **pycompat.strkwargs(opts)
1769 )
1767 )
1770 extra = {}
1768 extra = {}
1771 for idfunc in extrapreimport:
1769 for idfunc in extrapreimport:
1772 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1770 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1773 overrides = {}
1771 overrides = {}
1774 if partial:
1772 if partial:
1775 overrides[(b'ui', b'allowemptycommit')] = True
1773 overrides[(b'ui', b'allowemptycommit')] = True
1776 with repo.ui.configoverride(overrides, b'import'):
1774 with repo.ui.configoverride(overrides, b'import'):
1777 n = repo.commit(
1775 n = repo.commit(
1778 message, user, date, match=m, editor=editor, extra=extra
1776 message, user, date, match=m, editor=editor, extra=extra
1779 )
1777 )
1780 for idfunc in extrapostimport:
1778 for idfunc in extrapostimport:
1781 extrapostimportmap[idfunc](repo[n])
1779 extrapostimportmap[idfunc](repo[n])
1782 else:
1780 else:
1783 if opts.get(b'exact') or importbranch:
1781 if opts.get(b'exact') or importbranch:
1784 branch = branch or b'default'
1782 branch = branch or b'default'
1785 else:
1783 else:
1786 branch = p1.branch()
1784 branch = p1.branch()
1787 store = patch.filestore()
1785 store = patch.filestore()
1788 try:
1786 try:
1789 files = set()
1787 files = set()
1790 try:
1788 try:
1791 patch.patchrepo(
1789 patch.patchrepo(
1792 ui,
1790 ui,
1793 repo,
1791 repo,
1794 p1,
1792 p1,
1795 store,
1793 store,
1796 tmpname,
1794 tmpname,
1797 strip,
1795 strip,
1798 prefix,
1796 prefix,
1799 files,
1797 files,
1800 eolmode=None,
1798 eolmode=None,
1801 )
1799 )
1802 except error.PatchError as e:
1800 except error.PatchError as e:
1803 raise error.Abort(stringutil.forcebytestr(e))
1801 raise error.Abort(stringutil.forcebytestr(e))
1804 if opts.get(b'exact'):
1802 if opts.get(b'exact'):
1805 editor = None
1803 editor = None
1806 else:
1804 else:
1807 editor = getcommiteditor(editform=b'import.bypass')
1805 editor = getcommiteditor(editform=b'import.bypass')
1808 memctx = context.memctx(
1806 memctx = context.memctx(
1809 repo,
1807 repo,
1810 (p1.node(), p2.node()),
1808 (p1.node(), p2.node()),
1811 message,
1809 message,
1812 files=files,
1810 files=files,
1813 filectxfn=store,
1811 filectxfn=store,
1814 user=user,
1812 user=user,
1815 date=date,
1813 date=date,
1816 branch=branch,
1814 branch=branch,
1817 editor=editor,
1815 editor=editor,
1818 )
1816 )
1819 n = memctx.commit()
1817 n = memctx.commit()
1820 finally:
1818 finally:
1821 store.close()
1819 store.close()
1822 if opts.get(b'exact') and nocommit:
1820 if opts.get(b'exact') and nocommit:
1823 # --exact with --no-commit is still useful in that it does merge
1821 # --exact with --no-commit is still useful in that it does merge
1824 # and branch bits
1822 # and branch bits
1825 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
1823 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
1826 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
1824 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
1827 raise error.Abort(_(b'patch is damaged or loses information'))
1825 raise error.Abort(_(b'patch is damaged or loses information'))
1828 msg = _(b'applied to working directory')
1826 msg = _(b'applied to working directory')
1829 if n:
1827 if n:
1830 # i18n: refers to a short changeset id
1828 # i18n: refers to a short changeset id
1831 msg = _(b'created %s') % short(n)
1829 msg = _(b'created %s') % short(n)
1832 return msg, n, rejects
1830 return msg, n, rejects
1833
1831
1834
1832
1835 # facility to let extensions include additional data in an exported patch
1833 # facility to let extensions include additional data in an exported patch
1836 # list of identifiers to be executed in order
1834 # list of identifiers to be executed in order
1837 extraexport = []
1835 extraexport = []
1838 # mapping from identifier to actual export function
1836 # mapping from identifier to actual export function
1839 # function as to return a string to be added to the header or None
1837 # function as to return a string to be added to the header or None
1840 # it is given two arguments (sequencenumber, changectx)
1838 # it is given two arguments (sequencenumber, changectx)
1841 extraexportmap = {}
1839 extraexportmap = {}
1842
1840
1843
1841
1844 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1842 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1845 node = scmutil.binnode(ctx)
1843 node = scmutil.binnode(ctx)
1846 parents = [p.node() for p in ctx.parents() if p]
1844 parents = [p.node() for p in ctx.parents() if p]
1847 branch = ctx.branch()
1845 branch = ctx.branch()
1848 if switch_parent:
1846 if switch_parent:
1849 parents.reverse()
1847 parents.reverse()
1850
1848
1851 if parents:
1849 if parents:
1852 prev = parents[0]
1850 prev = parents[0]
1853 else:
1851 else:
1854 prev = nullid
1852 prev = nullid
1855
1853
1856 fm.context(ctx=ctx)
1854 fm.context(ctx=ctx)
1857 fm.plain(b'# HG changeset patch\n')
1855 fm.plain(b'# HG changeset patch\n')
1858 fm.write(b'user', b'# User %s\n', ctx.user())
1856 fm.write(b'user', b'# User %s\n', ctx.user())
1859 fm.plain(b'# Date %d %d\n' % ctx.date())
1857 fm.plain(b'# Date %d %d\n' % ctx.date())
1860 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
1858 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
1861 fm.condwrite(
1859 fm.condwrite(
1862 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
1860 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
1863 )
1861 )
1864 fm.write(b'node', b'# Node ID %s\n', hex(node))
1862 fm.write(b'node', b'# Node ID %s\n', hex(node))
1865 fm.plain(b'# Parent %s\n' % hex(prev))
1863 fm.plain(b'# Parent %s\n' % hex(prev))
1866 if len(parents) > 1:
1864 if len(parents) > 1:
1867 fm.plain(b'# Parent %s\n' % hex(parents[1]))
1865 fm.plain(b'# Parent %s\n' % hex(parents[1]))
1868 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
1866 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
1869
1867
1870 # TODO: redesign extraexportmap function to support formatter
1868 # TODO: redesign extraexportmap function to support formatter
1871 for headerid in extraexport:
1869 for headerid in extraexport:
1872 header = extraexportmap[headerid](seqno, ctx)
1870 header = extraexportmap[headerid](seqno, ctx)
1873 if header is not None:
1871 if header is not None:
1874 fm.plain(b'# %s\n' % header)
1872 fm.plain(b'# %s\n' % header)
1875
1873
1876 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
1874 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
1877 fm.plain(b'\n')
1875 fm.plain(b'\n')
1878
1876
1879 if fm.isplain():
1877 if fm.isplain():
1880 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1878 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1881 for chunk, label in chunkiter:
1879 for chunk, label in chunkiter:
1882 fm.plain(chunk, label=label)
1880 fm.plain(chunk, label=label)
1883 else:
1881 else:
1884 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1882 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1885 # TODO: make it structured?
1883 # TODO: make it structured?
1886 fm.data(diff=b''.join(chunkiter))
1884 fm.data(diff=b''.join(chunkiter))
1887
1885
1888
1886
1889 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1887 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1890 """Export changesets to stdout or a single file"""
1888 """Export changesets to stdout or a single file"""
1891 for seqno, rev in enumerate(revs, 1):
1889 for seqno, rev in enumerate(revs, 1):
1892 ctx = repo[rev]
1890 ctx = repo[rev]
1893 if not dest.startswith(b'<'):
1891 if not dest.startswith(b'<'):
1894 repo.ui.note(b"%s\n" % dest)
1892 repo.ui.note(b"%s\n" % dest)
1895 fm.startitem()
1893 fm.startitem()
1896 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1894 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1897
1895
1898
1896
1899 def _exportfntemplate(
1897 def _exportfntemplate(
1900 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
1898 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
1901 ):
1899 ):
1902 """Export changesets to possibly multiple files"""
1900 """Export changesets to possibly multiple files"""
1903 total = len(revs)
1901 total = len(revs)
1904 revwidth = max(len(str(rev)) for rev in revs)
1902 revwidth = max(len(str(rev)) for rev in revs)
1905 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1903 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1906
1904
1907 for seqno, rev in enumerate(revs, 1):
1905 for seqno, rev in enumerate(revs, 1):
1908 ctx = repo[rev]
1906 ctx = repo[rev]
1909 dest = makefilename(
1907 dest = makefilename(
1910 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
1908 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
1911 )
1909 )
1912 filemap.setdefault(dest, []).append((seqno, rev))
1910 filemap.setdefault(dest, []).append((seqno, rev))
1913
1911
1914 for dest in filemap:
1912 for dest in filemap:
1915 with formatter.maybereopen(basefm, dest) as fm:
1913 with formatter.maybereopen(basefm, dest) as fm:
1916 repo.ui.note(b"%s\n" % dest)
1914 repo.ui.note(b"%s\n" % dest)
1917 for seqno, rev in filemap[dest]:
1915 for seqno, rev in filemap[dest]:
1918 fm.startitem()
1916 fm.startitem()
1919 ctx = repo[rev]
1917 ctx = repo[rev]
1920 _exportsingle(
1918 _exportsingle(
1921 repo, ctx, fm, match, switch_parent, seqno, diffopts
1919 repo, ctx, fm, match, switch_parent, seqno, diffopts
1922 )
1920 )
1923
1921
1924
1922
1925 def _prefetchchangedfiles(repo, revs, match):
1923 def _prefetchchangedfiles(repo, revs, match):
1926 allfiles = set()
1924 allfiles = set()
1927 for rev in revs:
1925 for rev in revs:
1928 for file in repo[rev].files():
1926 for file in repo[rev].files():
1929 if not match or match(file):
1927 if not match or match(file):
1930 allfiles.add(file)
1928 allfiles.add(file)
1931 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1929 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1932
1930
1933
1931
1934 def export(
1932 def export(
1935 repo,
1933 repo,
1936 revs,
1934 revs,
1937 basefm,
1935 basefm,
1938 fntemplate=b'hg-%h.patch',
1936 fntemplate=b'hg-%h.patch',
1939 switch_parent=False,
1937 switch_parent=False,
1940 opts=None,
1938 opts=None,
1941 match=None,
1939 match=None,
1942 ):
1940 ):
1943 '''export changesets as hg patches
1941 '''export changesets as hg patches
1944
1942
1945 Args:
1943 Args:
1946 repo: The repository from which we're exporting revisions.
1944 repo: The repository from which we're exporting revisions.
1947 revs: A list of revisions to export as revision numbers.
1945 revs: A list of revisions to export as revision numbers.
1948 basefm: A formatter to which patches should be written.
1946 basefm: A formatter to which patches should be written.
1949 fntemplate: An optional string to use for generating patch file names.
1947 fntemplate: An optional string to use for generating patch file names.
1950 switch_parent: If True, show diffs against second parent when not nullid.
1948 switch_parent: If True, show diffs against second parent when not nullid.
1951 Default is false, which always shows diff against p1.
1949 Default is false, which always shows diff against p1.
1952 opts: diff options to use for generating the patch.
1950 opts: diff options to use for generating the patch.
1953 match: If specified, only export changes to files matching this matcher.
1951 match: If specified, only export changes to files matching this matcher.
1954
1952
1955 Returns:
1953 Returns:
1956 Nothing.
1954 Nothing.
1957
1955
1958 Side Effect:
1956 Side Effect:
1959 "HG Changeset Patch" data is emitted to one of the following
1957 "HG Changeset Patch" data is emitted to one of the following
1960 destinations:
1958 destinations:
1961 fntemplate specified: Each rev is written to a unique file named using
1959 fntemplate specified: Each rev is written to a unique file named using
1962 the given template.
1960 the given template.
1963 Otherwise: All revs will be written to basefm.
1961 Otherwise: All revs will be written to basefm.
1964 '''
1962 '''
1965 _prefetchchangedfiles(repo, revs, match)
1963 _prefetchchangedfiles(repo, revs, match)
1966
1964
1967 if not fntemplate:
1965 if not fntemplate:
1968 _exportfile(
1966 _exportfile(
1969 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
1967 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
1970 )
1968 )
1971 else:
1969 else:
1972 _exportfntemplate(
1970 _exportfntemplate(
1973 repo, revs, basefm, fntemplate, switch_parent, opts, match
1971 repo, revs, basefm, fntemplate, switch_parent, opts, match
1974 )
1972 )
1975
1973
1976
1974
1977 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1975 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1978 """Export changesets to the given file stream"""
1976 """Export changesets to the given file stream"""
1979 _prefetchchangedfiles(repo, revs, match)
1977 _prefetchchangedfiles(repo, revs, match)
1980
1978
1981 dest = getattr(fp, 'name', b'<unnamed>')
1979 dest = getattr(fp, 'name', b'<unnamed>')
1982 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
1980 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
1983 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1981 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1984
1982
1985
1983
1986 def showmarker(fm, marker, index=None):
1984 def showmarker(fm, marker, index=None):
1987 """utility function to display obsolescence marker in a readable way
1985 """utility function to display obsolescence marker in a readable way
1988
1986
1989 To be used by debug function."""
1987 To be used by debug function."""
1990 if index is not None:
1988 if index is not None:
1991 fm.write(b'index', b'%i ', index)
1989 fm.write(b'index', b'%i ', index)
1992 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
1990 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
1993 succs = marker.succnodes()
1991 succs = marker.succnodes()
1994 fm.condwrite(
1992 fm.condwrite(
1995 succs,
1993 succs,
1996 b'succnodes',
1994 b'succnodes',
1997 b'%s ',
1995 b'%s ',
1998 fm.formatlist(map(hex, succs), name=b'node'),
1996 fm.formatlist(map(hex, succs), name=b'node'),
1999 )
1997 )
2000 fm.write(b'flag', b'%X ', marker.flags())
1998 fm.write(b'flag', b'%X ', marker.flags())
2001 parents = marker.parentnodes()
1999 parents = marker.parentnodes()
2002 if parents is not None:
2000 if parents is not None:
2003 fm.write(
2001 fm.write(
2004 b'parentnodes',
2002 b'parentnodes',
2005 b'{%s} ',
2003 b'{%s} ',
2006 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2004 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2007 )
2005 )
2008 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2006 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2009 meta = marker.metadata().copy()
2007 meta = marker.metadata().copy()
2010 meta.pop(b'date', None)
2008 meta.pop(b'date', None)
2011 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2009 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2012 fm.write(
2010 fm.write(
2013 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2011 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2014 )
2012 )
2015 fm.plain(b'\n')
2013 fm.plain(b'\n')
2016
2014
2017
2015
2018 def finddate(ui, repo, date):
2016 def finddate(ui, repo, date):
2019 """Find the tipmost changeset that matches the given date spec"""
2017 """Find the tipmost changeset that matches the given date spec"""
2020
2018
2021 df = dateutil.matchdate(date)
2019 df = dateutil.matchdate(date)
2022 m = scmutil.matchall(repo)
2020 m = scmutil.matchall(repo)
2023 results = {}
2021 results = {}
2024
2022
2025 def prep(ctx, fns):
2023 def prep(ctx, fns):
2026 d = ctx.date()
2024 d = ctx.date()
2027 if df(d[0]):
2025 if df(d[0]):
2028 results[ctx.rev()] = d
2026 results[ctx.rev()] = d
2029
2027
2030 for ctx in walkchangerevs(repo, m, {b'rev': None}, prep):
2028 for ctx in walkchangerevs(repo, m, {b'rev': None}, prep):
2031 rev = ctx.rev()
2029 rev = ctx.rev()
2032 if rev in results:
2030 if rev in results:
2033 ui.status(
2031 ui.status(
2034 _(b"found revision %d from %s\n")
2032 _(b"found revision %d from %s\n")
2035 % (rev, dateutil.datestr(results[rev]))
2033 % (rev, dateutil.datestr(results[rev]))
2036 )
2034 )
2037 return b'%d' % rev
2035 return b'%d' % rev
2038
2036
2039 raise error.Abort(_(b"revision matching date not found"))
2037 raise error.Abort(_(b"revision matching date not found"))
2040
2038
2041
2039
2042 def increasingwindows(windowsize=8, sizelimit=512):
2040 def increasingwindows(windowsize=8, sizelimit=512):
2043 while True:
2041 while True:
2044 yield windowsize
2042 yield windowsize
2045 if windowsize < sizelimit:
2043 if windowsize < sizelimit:
2046 windowsize *= 2
2044 windowsize *= 2
2047
2045
2048
2046
2049 def _walkrevs(repo, opts):
2047 def _walkrevs(repo, opts):
2050 # Default --rev value depends on --follow but --follow behavior
2048 # Default --rev value depends on --follow but --follow behavior
2051 # depends on revisions resolved from --rev...
2049 # depends on revisions resolved from --rev...
2052 follow = opts.get(b'follow') or opts.get(b'follow_first')
2050 follow = opts.get(b'follow') or opts.get(b'follow_first')
2053 if opts.get(b'rev'):
2051 if opts.get(b'rev'):
2054 revs = scmutil.revrange(repo, opts[b'rev'])
2052 revs = scmutil.revrange(repo, opts[b'rev'])
2055 elif follow and repo.dirstate.p1() == nullid:
2053 elif follow and repo.dirstate.p1() == nullid:
2056 revs = smartset.baseset()
2054 revs = smartset.baseset()
2057 elif follow:
2055 elif follow:
2058 revs = repo.revs(b'reverse(:.)')
2056 revs = repo.revs(b'reverse(:.)')
2059 else:
2057 else:
2060 revs = smartset.spanset(repo)
2058 revs = smartset.spanset(repo)
2061 revs.reverse()
2059 revs.reverse()
2062 return revs
2060 return revs
2063
2061
2064
2062
2065 class FileWalkError(Exception):
2063 class FileWalkError(Exception):
2066 pass
2064 pass
2067
2065
2068
2066
2069 def walkfilerevs(repo, match, follow, revs, fncache):
2067 def walkfilerevs(repo, match, follow, revs, fncache):
2070 '''Walks the file history for the matched files.
2068 '''Walks the file history for the matched files.
2071
2069
2072 Returns the changeset revs that are involved in the file history.
2070 Returns the changeset revs that are involved in the file history.
2073
2071
2074 Throws FileWalkError if the file history can't be walked using
2072 Throws FileWalkError if the file history can't be walked using
2075 filelogs alone.
2073 filelogs alone.
2076 '''
2074 '''
2077 wanted = set()
2075 wanted = set()
2078 copies = []
2076 copies = []
2079 minrev, maxrev = min(revs), max(revs)
2077 minrev, maxrev = min(revs), max(revs)
2080
2078
2081 def filerevs(filelog, last):
2079 def filerevs(filelog, last):
2082 """
2080 """
2083 Only files, no patterns. Check the history of each file.
2081 Only files, no patterns. Check the history of each file.
2084
2082
2085 Examines filelog entries within minrev, maxrev linkrev range
2083 Examines filelog entries within minrev, maxrev linkrev range
2086 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2084 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2087 tuples in backwards order
2085 tuples in backwards order
2088 """
2086 """
2089 cl_count = len(repo)
2087 cl_count = len(repo)
2090 revs = []
2088 revs = []
2091 for j in pycompat.xrange(0, last + 1):
2089 for j in pycompat.xrange(0, last + 1):
2092 linkrev = filelog.linkrev(j)
2090 linkrev = filelog.linkrev(j)
2093 if linkrev < minrev:
2091 if linkrev < minrev:
2094 continue
2092 continue
2095 # only yield rev for which we have the changelog, it can
2093 # only yield rev for which we have the changelog, it can
2096 # happen while doing "hg log" during a pull or commit
2094 # happen while doing "hg log" during a pull or commit
2097 if linkrev >= cl_count:
2095 if linkrev >= cl_count:
2098 break
2096 break
2099
2097
2100 parentlinkrevs = []
2098 parentlinkrevs = []
2101 for p in filelog.parentrevs(j):
2099 for p in filelog.parentrevs(j):
2102 if p != nullrev:
2100 if p != nullrev:
2103 parentlinkrevs.append(filelog.linkrev(p))
2101 parentlinkrevs.append(filelog.linkrev(p))
2104 n = filelog.node(j)
2102 n = filelog.node(j)
2105 revs.append(
2103 revs.append(
2106 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2104 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2107 )
2105 )
2108
2106
2109 return reversed(revs)
2107 return reversed(revs)
2110
2108
2111 def iterfiles():
2109 def iterfiles():
2112 pctx = repo[b'.']
2110 pctx = repo[b'.']
2113 for filename in match.files():
2111 for filename in match.files():
2114 if follow:
2112 if follow:
2115 if filename not in pctx:
2113 if filename not in pctx:
2116 raise error.Abort(
2114 raise error.Abort(
2117 _(
2115 _(
2118 b'cannot follow file not in parent '
2116 b'cannot follow file not in parent '
2119 b'revision: "%s"'
2117 b'revision: "%s"'
2120 )
2118 )
2121 % filename
2119 % filename
2122 )
2120 )
2123 yield filename, pctx[filename].filenode()
2121 yield filename, pctx[filename].filenode()
2124 else:
2122 else:
2125 yield filename, None
2123 yield filename, None
2126 for filename_node in copies:
2124 for filename_node in copies:
2127 yield filename_node
2125 yield filename_node
2128
2126
2129 for file_, node in iterfiles():
2127 for file_, node in iterfiles():
2130 filelog = repo.file(file_)
2128 filelog = repo.file(file_)
2131 if not len(filelog):
2129 if not len(filelog):
2132 if node is None:
2130 if node is None:
2133 # A zero count may be a directory or deleted file, so
2131 # A zero count may be a directory or deleted file, so
2134 # try to find matching entries on the slow path.
2132 # try to find matching entries on the slow path.
2135 if follow:
2133 if follow:
2136 raise error.Abort(
2134 raise error.Abort(
2137 _(b'cannot follow nonexistent file: "%s"') % file_
2135 _(b'cannot follow nonexistent file: "%s"') % file_
2138 )
2136 )
2139 raise FileWalkError(b"Cannot walk via filelog")
2137 raise FileWalkError(b"Cannot walk via filelog")
2140 else:
2138 else:
2141 continue
2139 continue
2142
2140
2143 if node is None:
2141 if node is None:
2144 last = len(filelog) - 1
2142 last = len(filelog) - 1
2145 else:
2143 else:
2146 last = filelog.rev(node)
2144 last = filelog.rev(node)
2147
2145
2148 # keep track of all ancestors of the file
2146 # keep track of all ancestors of the file
2149 ancestors = {filelog.linkrev(last)}
2147 ancestors = {filelog.linkrev(last)}
2150
2148
2151 # iterate from latest to oldest revision
2149 # iterate from latest to oldest revision
2152 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2150 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2153 if not follow:
2151 if not follow:
2154 if rev > maxrev:
2152 if rev > maxrev:
2155 continue
2153 continue
2156 else:
2154 else:
2157 # Note that last might not be the first interesting
2155 # Note that last might not be the first interesting
2158 # rev to us:
2156 # rev to us:
2159 # if the file has been changed after maxrev, we'll
2157 # if the file has been changed after maxrev, we'll
2160 # have linkrev(last) > maxrev, and we still need
2158 # have linkrev(last) > maxrev, and we still need
2161 # to explore the file graph
2159 # to explore the file graph
2162 if rev not in ancestors:
2160 if rev not in ancestors:
2163 continue
2161 continue
2164 # XXX insert 1327 fix here
2162 # XXX insert 1327 fix here
2165 if flparentlinkrevs:
2163 if flparentlinkrevs:
2166 ancestors.update(flparentlinkrevs)
2164 ancestors.update(flparentlinkrevs)
2167
2165
2168 fncache.setdefault(rev, []).append(file_)
2166 fncache.setdefault(rev, []).append(file_)
2169 wanted.add(rev)
2167 wanted.add(rev)
2170 if copied:
2168 if copied:
2171 copies.append(copied)
2169 copies.append(copied)
2172
2170
2173 return wanted
2171 return wanted
2174
2172
2175
2173
2176 class _followfilter(object):
2174 class _followfilter(object):
2177 def __init__(self, repo, onlyfirst=False):
2175 def __init__(self, repo, onlyfirst=False):
2178 self.repo = repo
2176 self.repo = repo
2179 self.startrev = nullrev
2177 self.startrev = nullrev
2180 self.roots = set()
2178 self.roots = set()
2181 self.onlyfirst = onlyfirst
2179 self.onlyfirst = onlyfirst
2182
2180
2183 def match(self, rev):
2181 def match(self, rev):
2184 def realparents(rev):
2182 def realparents(rev):
2185 if self.onlyfirst:
2183 if self.onlyfirst:
2186 return self.repo.changelog.parentrevs(rev)[0:1]
2184 return self.repo.changelog.parentrevs(rev)[0:1]
2187 else:
2185 else:
2188 return filter(
2186 return filter(
2189 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2187 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2190 )
2188 )
2191
2189
2192 if self.startrev == nullrev:
2190 if self.startrev == nullrev:
2193 self.startrev = rev
2191 self.startrev = rev
2194 return True
2192 return True
2195
2193
2196 if rev > self.startrev:
2194 if rev > self.startrev:
2197 # forward: all descendants
2195 # forward: all descendants
2198 if not self.roots:
2196 if not self.roots:
2199 self.roots.add(self.startrev)
2197 self.roots.add(self.startrev)
2200 for parent in realparents(rev):
2198 for parent in realparents(rev):
2201 if parent in self.roots:
2199 if parent in self.roots:
2202 self.roots.add(rev)
2200 self.roots.add(rev)
2203 return True
2201 return True
2204 else:
2202 else:
2205 # backwards: all parents
2203 # backwards: all parents
2206 if not self.roots:
2204 if not self.roots:
2207 self.roots.update(realparents(self.startrev))
2205 self.roots.update(realparents(self.startrev))
2208 if rev in self.roots:
2206 if rev in self.roots:
2209 self.roots.remove(rev)
2207 self.roots.remove(rev)
2210 self.roots.update(realparents(rev))
2208 self.roots.update(realparents(rev))
2211 return True
2209 return True
2212
2210
2213 return False
2211 return False
2214
2212
2215
2213
2216 def walkchangerevs(repo, match, opts, prepare):
2214 def walkchangerevs(repo, match, opts, prepare):
2217 '''Iterate over files and the revs in which they changed.
2215 '''Iterate over files and the revs in which they changed.
2218
2216
2219 Callers most commonly need to iterate backwards over the history
2217 Callers most commonly need to iterate backwards over the history
2220 in which they are interested. Doing so has awful (quadratic-looking)
2218 in which they are interested. Doing so has awful (quadratic-looking)
2221 performance, so we use iterators in a "windowed" way.
2219 performance, so we use iterators in a "windowed" way.
2222
2220
2223 We walk a window of revisions in the desired order. Within the
2221 We walk a window of revisions in the desired order. Within the
2224 window, we first walk forwards to gather data, then in the desired
2222 window, we first walk forwards to gather data, then in the desired
2225 order (usually backwards) to display it.
2223 order (usually backwards) to display it.
2226
2224
2227 This function returns an iterator yielding contexts. Before
2225 This function returns an iterator yielding contexts. Before
2228 yielding each context, the iterator will first call the prepare
2226 yielding each context, the iterator will first call the prepare
2229 function on each context in the window in forward order.'''
2227 function on each context in the window in forward order.'''
2230
2228
2231 allfiles = opts.get(b'all_files')
2229 allfiles = opts.get(b'all_files')
2232 follow = opts.get(b'follow') or opts.get(b'follow_first')
2230 follow = opts.get(b'follow') or opts.get(b'follow_first')
2233 revs = _walkrevs(repo, opts)
2231 revs = _walkrevs(repo, opts)
2234 if not revs:
2232 if not revs:
2235 return []
2233 return []
2236 wanted = set()
2234 wanted = set()
2237 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2235 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2238 fncache = {}
2236 fncache = {}
2239 change = repo.__getitem__
2237 change = repo.__getitem__
2240
2238
2241 # First step is to fill wanted, the set of revisions that we want to yield.
2239 # First step is to fill wanted, the set of revisions that we want to yield.
2242 # When it does not induce extra cost, we also fill fncache for revisions in
2240 # When it does not induce extra cost, we also fill fncache for revisions in
2243 # wanted: a cache of filenames that were changed (ctx.files()) and that
2241 # wanted: a cache of filenames that were changed (ctx.files()) and that
2244 # match the file filtering conditions.
2242 # match the file filtering conditions.
2245
2243
2246 if match.always() or allfiles:
2244 if match.always() or allfiles:
2247 # No files, no patterns. Display all revs.
2245 # No files, no patterns. Display all revs.
2248 wanted = revs
2246 wanted = revs
2249 elif not slowpath:
2247 elif not slowpath:
2250 # We only have to read through the filelog to find wanted revisions
2248 # We only have to read through the filelog to find wanted revisions
2251
2249
2252 try:
2250 try:
2253 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2251 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2254 except FileWalkError:
2252 except FileWalkError:
2255 slowpath = True
2253 slowpath = True
2256
2254
2257 # We decided to fall back to the slowpath because at least one
2255 # We decided to fall back to the slowpath because at least one
2258 # of the paths was not a file. Check to see if at least one of them
2256 # of the paths was not a file. Check to see if at least one of them
2259 # existed in history, otherwise simply return
2257 # existed in history, otherwise simply return
2260 for path in match.files():
2258 for path in match.files():
2261 if path == b'.' or path in repo.store:
2259 if path == b'.' or path in repo.store:
2262 break
2260 break
2263 else:
2261 else:
2264 return []
2262 return []
2265
2263
2266 if slowpath:
2264 if slowpath:
2267 # We have to read the changelog to match filenames against
2265 # We have to read the changelog to match filenames against
2268 # changed files
2266 # changed files
2269
2267
2270 if follow:
2268 if follow:
2271 raise error.Abort(
2269 raise error.Abort(
2272 _(b'can only follow copies/renames for explicit filenames')
2270 _(b'can only follow copies/renames for explicit filenames')
2273 )
2271 )
2274
2272
2275 # The slow path checks files modified in every changeset.
2273 # The slow path checks files modified in every changeset.
2276 # This is really slow on large repos, so compute the set lazily.
2274 # This is really slow on large repos, so compute the set lazily.
2277 class lazywantedset(object):
2275 class lazywantedset(object):
2278 def __init__(self):
2276 def __init__(self):
2279 self.set = set()
2277 self.set = set()
2280 self.revs = set(revs)
2278 self.revs = set(revs)
2281
2279
2282 # No need to worry about locality here because it will be accessed
2280 # No need to worry about locality here because it will be accessed
2283 # in the same order as the increasing window below.
2281 # in the same order as the increasing window below.
2284 def __contains__(self, value):
2282 def __contains__(self, value):
2285 if value in self.set:
2283 if value in self.set:
2286 return True
2284 return True
2287 elif not value in self.revs:
2285 elif not value in self.revs:
2288 return False
2286 return False
2289 else:
2287 else:
2290 self.revs.discard(value)
2288 self.revs.discard(value)
2291 ctx = change(value)
2289 ctx = change(value)
2292 if allfiles:
2290 if allfiles:
2293 matches = list(ctx.manifest().walk(match))
2291 matches = list(ctx.manifest().walk(match))
2294 else:
2292 else:
2295 matches = [f for f in ctx.files() if match(f)]
2293 matches = [f for f in ctx.files() if match(f)]
2296 if matches:
2294 if matches:
2297 fncache[value] = matches
2295 fncache[value] = matches
2298 self.set.add(value)
2296 self.set.add(value)
2299 return True
2297 return True
2300 return False
2298 return False
2301
2299
2302 def discard(self, value):
2300 def discard(self, value):
2303 self.revs.discard(value)
2301 self.revs.discard(value)
2304 self.set.discard(value)
2302 self.set.discard(value)
2305
2303
2306 wanted = lazywantedset()
2304 wanted = lazywantedset()
2307
2305
2308 # it might be worthwhile to do this in the iterator if the rev range
2306 # it might be worthwhile to do this in the iterator if the rev range
2309 # is descending and the prune args are all within that range
2307 # is descending and the prune args are all within that range
2310 for rev in opts.get(b'prune', ()):
2308 for rev in opts.get(b'prune', ()):
2311 rev = repo[rev].rev()
2309 rev = repo[rev].rev()
2312 ff = _followfilter(repo)
2310 ff = _followfilter(repo)
2313 stop = min(revs[0], revs[-1])
2311 stop = min(revs[0], revs[-1])
2314 for x in pycompat.xrange(rev, stop - 1, -1):
2312 for x in pycompat.xrange(rev, stop - 1, -1):
2315 if ff.match(x):
2313 if ff.match(x):
2316 wanted = wanted - [x]
2314 wanted = wanted - [x]
2317
2315
2318 # Now that wanted is correctly initialized, we can iterate over the
2316 # Now that wanted is correctly initialized, we can iterate over the
2319 # revision range, yielding only revisions in wanted.
2317 # revision range, yielding only revisions in wanted.
2320 def iterate():
2318 def iterate():
2321 if follow and match.always():
2319 if follow and match.always():
2322 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2320 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2323
2321
2324 def want(rev):
2322 def want(rev):
2325 return ff.match(rev) and rev in wanted
2323 return ff.match(rev) and rev in wanted
2326
2324
2327 else:
2325 else:
2328
2326
2329 def want(rev):
2327 def want(rev):
2330 return rev in wanted
2328 return rev in wanted
2331
2329
2332 it = iter(revs)
2330 it = iter(revs)
2333 stopiteration = False
2331 stopiteration = False
2334 for windowsize in increasingwindows():
2332 for windowsize in increasingwindows():
2335 nrevs = []
2333 nrevs = []
2336 for i in pycompat.xrange(windowsize):
2334 for i in pycompat.xrange(windowsize):
2337 rev = next(it, None)
2335 rev = next(it, None)
2338 if rev is None:
2336 if rev is None:
2339 stopiteration = True
2337 stopiteration = True
2340 break
2338 break
2341 elif want(rev):
2339 elif want(rev):
2342 nrevs.append(rev)
2340 nrevs.append(rev)
2343 for rev in sorted(nrevs):
2341 for rev in sorted(nrevs):
2344 fns = fncache.get(rev)
2342 fns = fncache.get(rev)
2345 ctx = change(rev)
2343 ctx = change(rev)
2346 if not fns:
2344 if not fns:
2347
2345
2348 def fns_generator():
2346 def fns_generator():
2349 if allfiles:
2347 if allfiles:
2350 fiter = iter(ctx)
2348 fiter = iter(ctx)
2351 else:
2349 else:
2352 fiter = ctx.files()
2350 fiter = ctx.files()
2353 for f in fiter:
2351 for f in fiter:
2354 if match(f):
2352 if match(f):
2355 yield f
2353 yield f
2356
2354
2357 fns = fns_generator()
2355 fns = fns_generator()
2358 prepare(ctx, fns)
2356 prepare(ctx, fns)
2359 for rev in nrevs:
2357 for rev in nrevs:
2360 yield change(rev)
2358 yield change(rev)
2361
2359
2362 if stopiteration:
2360 if stopiteration:
2363 break
2361 break
2364
2362
2365 return iterate()
2363 return iterate()
2366
2364
2367
2365
2368 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2366 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2369 bad = []
2367 bad = []
2370
2368
2371 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2369 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2372 names = []
2370 names = []
2373 wctx = repo[None]
2371 wctx = repo[None]
2374 cca = None
2372 cca = None
2375 abort, warn = scmutil.checkportabilityalert(ui)
2373 abort, warn = scmutil.checkportabilityalert(ui)
2376 if abort or warn:
2374 if abort or warn:
2377 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2375 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2378
2376
2379 match = repo.narrowmatch(match, includeexact=True)
2377 match = repo.narrowmatch(match, includeexact=True)
2380 badmatch = matchmod.badmatch(match, badfn)
2378 badmatch = matchmod.badmatch(match, badfn)
2381 dirstate = repo.dirstate
2379 dirstate = repo.dirstate
2382 # We don't want to just call wctx.walk here, since it would return a lot of
2380 # We don't want to just call wctx.walk here, since it would return a lot of
2383 # clean files, which we aren't interested in and takes time.
2381 # clean files, which we aren't interested in and takes time.
2384 for f in sorted(
2382 for f in sorted(
2385 dirstate.walk(
2383 dirstate.walk(
2386 badmatch,
2384 badmatch,
2387 subrepos=sorted(wctx.substate),
2385 subrepos=sorted(wctx.substate),
2388 unknown=True,
2386 unknown=True,
2389 ignored=False,
2387 ignored=False,
2390 full=False,
2388 full=False,
2391 )
2389 )
2392 ):
2390 ):
2393 exact = match.exact(f)
2391 exact = match.exact(f)
2394 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2392 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2395 if cca:
2393 if cca:
2396 cca(f)
2394 cca(f)
2397 names.append(f)
2395 names.append(f)
2398 if ui.verbose or not exact:
2396 if ui.verbose or not exact:
2399 ui.status(
2397 ui.status(
2400 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2398 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2401 )
2399 )
2402
2400
2403 for subpath in sorted(wctx.substate):
2401 for subpath in sorted(wctx.substate):
2404 sub = wctx.sub(subpath)
2402 sub = wctx.sub(subpath)
2405 try:
2403 try:
2406 submatch = matchmod.subdirmatcher(subpath, match)
2404 submatch = matchmod.subdirmatcher(subpath, match)
2407 subprefix = repo.wvfs.reljoin(prefix, subpath)
2405 subprefix = repo.wvfs.reljoin(prefix, subpath)
2408 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2406 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2409 if opts.get('subrepos'):
2407 if opts.get('subrepos'):
2410 bad.extend(
2408 bad.extend(
2411 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2409 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2412 )
2410 )
2413 else:
2411 else:
2414 bad.extend(
2412 bad.extend(
2415 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2413 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2416 )
2414 )
2417 except error.LookupError:
2415 except error.LookupError:
2418 ui.status(
2416 ui.status(
2419 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2417 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2420 )
2418 )
2421
2419
2422 if not opts.get('dry_run'):
2420 if not opts.get('dry_run'):
2423 rejected = wctx.add(names, prefix)
2421 rejected = wctx.add(names, prefix)
2424 bad.extend(f for f in rejected if f in match.files())
2422 bad.extend(f for f in rejected if f in match.files())
2425 return bad
2423 return bad
2426
2424
2427
2425
2428 def addwebdirpath(repo, serverpath, webconf):
2426 def addwebdirpath(repo, serverpath, webconf):
2429 webconf[serverpath] = repo.root
2427 webconf[serverpath] = repo.root
2430 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2428 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2431
2429
2432 for r in repo.revs(b'filelog("path:.hgsub")'):
2430 for r in repo.revs(b'filelog("path:.hgsub")'):
2433 ctx = repo[r]
2431 ctx = repo[r]
2434 for subpath in ctx.substate:
2432 for subpath in ctx.substate:
2435 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2433 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2436
2434
2437
2435
2438 def forget(
2436 def forget(
2439 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2437 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2440 ):
2438 ):
2441 if dryrun and interactive:
2439 if dryrun and interactive:
2442 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2440 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2443 bad = []
2441 bad = []
2444 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2442 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2445 wctx = repo[None]
2443 wctx = repo[None]
2446 forgot = []
2444 forgot = []
2447
2445
2448 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2446 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2449 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2447 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2450 if explicitonly:
2448 if explicitonly:
2451 forget = [f for f in forget if match.exact(f)]
2449 forget = [f for f in forget if match.exact(f)]
2452
2450
2453 for subpath in sorted(wctx.substate):
2451 for subpath in sorted(wctx.substate):
2454 sub = wctx.sub(subpath)
2452 sub = wctx.sub(subpath)
2455 submatch = matchmod.subdirmatcher(subpath, match)
2453 submatch = matchmod.subdirmatcher(subpath, match)
2456 subprefix = repo.wvfs.reljoin(prefix, subpath)
2454 subprefix = repo.wvfs.reljoin(prefix, subpath)
2457 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2455 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2458 try:
2456 try:
2459 subbad, subforgot = sub.forget(
2457 subbad, subforgot = sub.forget(
2460 submatch,
2458 submatch,
2461 subprefix,
2459 subprefix,
2462 subuipathfn,
2460 subuipathfn,
2463 dryrun=dryrun,
2461 dryrun=dryrun,
2464 interactive=interactive,
2462 interactive=interactive,
2465 )
2463 )
2466 bad.extend([subpath + b'/' + f for f in subbad])
2464 bad.extend([subpath + b'/' + f for f in subbad])
2467 forgot.extend([subpath + b'/' + f for f in subforgot])
2465 forgot.extend([subpath + b'/' + f for f in subforgot])
2468 except error.LookupError:
2466 except error.LookupError:
2469 ui.status(
2467 ui.status(
2470 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2468 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2471 )
2469 )
2472
2470
2473 if not explicitonly:
2471 if not explicitonly:
2474 for f in match.files():
2472 for f in match.files():
2475 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2473 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2476 if f not in forgot:
2474 if f not in forgot:
2477 if repo.wvfs.exists(f):
2475 if repo.wvfs.exists(f):
2478 # Don't complain if the exact case match wasn't given.
2476 # Don't complain if the exact case match wasn't given.
2479 # But don't do this until after checking 'forgot', so
2477 # But don't do this until after checking 'forgot', so
2480 # that subrepo files aren't normalized, and this op is
2478 # that subrepo files aren't normalized, and this op is
2481 # purely from data cached by the status walk above.
2479 # purely from data cached by the status walk above.
2482 if repo.dirstate.normalize(f) in repo.dirstate:
2480 if repo.dirstate.normalize(f) in repo.dirstate:
2483 continue
2481 continue
2484 ui.warn(
2482 ui.warn(
2485 _(
2483 _(
2486 b'not removing %s: '
2484 b'not removing %s: '
2487 b'file is already untracked\n'
2485 b'file is already untracked\n'
2488 )
2486 )
2489 % uipathfn(f)
2487 % uipathfn(f)
2490 )
2488 )
2491 bad.append(f)
2489 bad.append(f)
2492
2490
2493 if interactive:
2491 if interactive:
2494 responses = _(
2492 responses = _(
2495 b'[Ynsa?]'
2493 b'[Ynsa?]'
2496 b'$$ &Yes, forget this file'
2494 b'$$ &Yes, forget this file'
2497 b'$$ &No, skip this file'
2495 b'$$ &No, skip this file'
2498 b'$$ &Skip remaining files'
2496 b'$$ &Skip remaining files'
2499 b'$$ Include &all remaining files'
2497 b'$$ Include &all remaining files'
2500 b'$$ &? (display help)'
2498 b'$$ &? (display help)'
2501 )
2499 )
2502 for filename in forget[:]:
2500 for filename in forget[:]:
2503 r = ui.promptchoice(
2501 r = ui.promptchoice(
2504 _(b'forget %s %s') % (uipathfn(filename), responses)
2502 _(b'forget %s %s') % (uipathfn(filename), responses)
2505 )
2503 )
2506 if r == 4: # ?
2504 if r == 4: # ?
2507 while r == 4:
2505 while r == 4:
2508 for c, t in ui.extractchoices(responses)[1]:
2506 for c, t in ui.extractchoices(responses)[1]:
2509 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2507 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2510 r = ui.promptchoice(
2508 r = ui.promptchoice(
2511 _(b'forget %s %s') % (uipathfn(filename), responses)
2509 _(b'forget %s %s') % (uipathfn(filename), responses)
2512 )
2510 )
2513 if r == 0: # yes
2511 if r == 0: # yes
2514 continue
2512 continue
2515 elif r == 1: # no
2513 elif r == 1: # no
2516 forget.remove(filename)
2514 forget.remove(filename)
2517 elif r == 2: # Skip
2515 elif r == 2: # Skip
2518 fnindex = forget.index(filename)
2516 fnindex = forget.index(filename)
2519 del forget[fnindex:]
2517 del forget[fnindex:]
2520 break
2518 break
2521 elif r == 3: # All
2519 elif r == 3: # All
2522 break
2520 break
2523
2521
2524 for f in forget:
2522 for f in forget:
2525 if ui.verbose or not match.exact(f) or interactive:
2523 if ui.verbose or not match.exact(f) or interactive:
2526 ui.status(
2524 ui.status(
2527 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2525 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2528 )
2526 )
2529
2527
2530 if not dryrun:
2528 if not dryrun:
2531 rejected = wctx.forget(forget, prefix)
2529 rejected = wctx.forget(forget, prefix)
2532 bad.extend(f for f in rejected if f in match.files())
2530 bad.extend(f for f in rejected if f in match.files())
2533 forgot.extend(f for f in forget if f not in rejected)
2531 forgot.extend(f for f in forget if f not in rejected)
2534 return bad, forgot
2532 return bad, forgot
2535
2533
2536
2534
2537 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2535 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2538 ret = 1
2536 ret = 1
2539
2537
2540 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2538 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2541 for f in ctx.matches(m):
2539 for f in ctx.matches(m):
2542 fm.startitem()
2540 fm.startitem()
2543 fm.context(ctx=ctx)
2541 fm.context(ctx=ctx)
2544 if needsfctx:
2542 if needsfctx:
2545 fc = ctx[f]
2543 fc = ctx[f]
2546 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2544 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2547 fm.data(path=f)
2545 fm.data(path=f)
2548 fm.plain(fmt % uipathfn(f))
2546 fm.plain(fmt % uipathfn(f))
2549 ret = 0
2547 ret = 0
2550
2548
2551 for subpath in sorted(ctx.substate):
2549 for subpath in sorted(ctx.substate):
2552 submatch = matchmod.subdirmatcher(subpath, m)
2550 submatch = matchmod.subdirmatcher(subpath, m)
2553 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2551 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2554 if subrepos or m.exact(subpath) or any(submatch.files()):
2552 if subrepos or m.exact(subpath) or any(submatch.files()):
2555 sub = ctx.sub(subpath)
2553 sub = ctx.sub(subpath)
2556 try:
2554 try:
2557 recurse = m.exact(subpath) or subrepos
2555 recurse = m.exact(subpath) or subrepos
2558 if (
2556 if (
2559 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2557 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2560 == 0
2558 == 0
2561 ):
2559 ):
2562 ret = 0
2560 ret = 0
2563 except error.LookupError:
2561 except error.LookupError:
2564 ui.status(
2562 ui.status(
2565 _(b"skipping missing subrepository: %s\n")
2563 _(b"skipping missing subrepository: %s\n")
2566 % uipathfn(subpath)
2564 % uipathfn(subpath)
2567 )
2565 )
2568
2566
2569 return ret
2567 return ret
2570
2568
2571
2569
2572 def remove(
2570 def remove(
2573 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2571 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2574 ):
2572 ):
2575 ret = 0
2573 ret = 0
2576 s = repo.status(match=m, clean=True)
2574 s = repo.status(match=m, clean=True)
2577 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2575 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2578
2576
2579 wctx = repo[None]
2577 wctx = repo[None]
2580
2578
2581 if warnings is None:
2579 if warnings is None:
2582 warnings = []
2580 warnings = []
2583 warn = True
2581 warn = True
2584 else:
2582 else:
2585 warn = False
2583 warn = False
2586
2584
2587 subs = sorted(wctx.substate)
2585 subs = sorted(wctx.substate)
2588 progress = ui.makeprogress(
2586 progress = ui.makeprogress(
2589 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2587 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2590 )
2588 )
2591 for subpath in subs:
2589 for subpath in subs:
2592 submatch = matchmod.subdirmatcher(subpath, m)
2590 submatch = matchmod.subdirmatcher(subpath, m)
2593 subprefix = repo.wvfs.reljoin(prefix, subpath)
2591 subprefix = repo.wvfs.reljoin(prefix, subpath)
2594 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2592 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2595 if subrepos or m.exact(subpath) or any(submatch.files()):
2593 if subrepos or m.exact(subpath) or any(submatch.files()):
2596 progress.increment()
2594 progress.increment()
2597 sub = wctx.sub(subpath)
2595 sub = wctx.sub(subpath)
2598 try:
2596 try:
2599 if sub.removefiles(
2597 if sub.removefiles(
2600 submatch,
2598 submatch,
2601 subprefix,
2599 subprefix,
2602 subuipathfn,
2600 subuipathfn,
2603 after,
2601 after,
2604 force,
2602 force,
2605 subrepos,
2603 subrepos,
2606 dryrun,
2604 dryrun,
2607 warnings,
2605 warnings,
2608 ):
2606 ):
2609 ret = 1
2607 ret = 1
2610 except error.LookupError:
2608 except error.LookupError:
2611 warnings.append(
2609 warnings.append(
2612 _(b"skipping missing subrepository: %s\n")
2610 _(b"skipping missing subrepository: %s\n")
2613 % uipathfn(subpath)
2611 % uipathfn(subpath)
2614 )
2612 )
2615 progress.complete()
2613 progress.complete()
2616
2614
2617 # warn about failure to delete explicit files/dirs
2615 # warn about failure to delete explicit files/dirs
2618 deleteddirs = pathutil.dirs(deleted)
2616 deleteddirs = pathutil.dirs(deleted)
2619 files = m.files()
2617 files = m.files()
2620 progress = ui.makeprogress(
2618 progress = ui.makeprogress(
2621 _(b'deleting'), total=len(files), unit=_(b'files')
2619 _(b'deleting'), total=len(files), unit=_(b'files')
2622 )
2620 )
2623 for f in files:
2621 for f in files:
2624
2622
2625 def insubrepo():
2623 def insubrepo():
2626 for subpath in wctx.substate:
2624 for subpath in wctx.substate:
2627 if f.startswith(subpath + b'/'):
2625 if f.startswith(subpath + b'/'):
2628 return True
2626 return True
2629 return False
2627 return False
2630
2628
2631 progress.increment()
2629 progress.increment()
2632 isdir = f in deleteddirs or wctx.hasdir(f)
2630 isdir = f in deleteddirs or wctx.hasdir(f)
2633 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2631 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2634 continue
2632 continue
2635
2633
2636 if repo.wvfs.exists(f):
2634 if repo.wvfs.exists(f):
2637 if repo.wvfs.isdir(f):
2635 if repo.wvfs.isdir(f):
2638 warnings.append(
2636 warnings.append(
2639 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2637 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2640 )
2638 )
2641 else:
2639 else:
2642 warnings.append(
2640 warnings.append(
2643 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2641 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2644 )
2642 )
2645 # missing files will generate a warning elsewhere
2643 # missing files will generate a warning elsewhere
2646 ret = 1
2644 ret = 1
2647 progress.complete()
2645 progress.complete()
2648
2646
2649 if force:
2647 if force:
2650 list = modified + deleted + clean + added
2648 list = modified + deleted + clean + added
2651 elif after:
2649 elif after:
2652 list = deleted
2650 list = deleted
2653 remaining = modified + added + clean
2651 remaining = modified + added + clean
2654 progress = ui.makeprogress(
2652 progress = ui.makeprogress(
2655 _(b'skipping'), total=len(remaining), unit=_(b'files')
2653 _(b'skipping'), total=len(remaining), unit=_(b'files')
2656 )
2654 )
2657 for f in remaining:
2655 for f in remaining:
2658 progress.increment()
2656 progress.increment()
2659 if ui.verbose or (f in files):
2657 if ui.verbose or (f in files):
2660 warnings.append(
2658 warnings.append(
2661 _(b'not removing %s: file still exists\n') % uipathfn(f)
2659 _(b'not removing %s: file still exists\n') % uipathfn(f)
2662 )
2660 )
2663 ret = 1
2661 ret = 1
2664 progress.complete()
2662 progress.complete()
2665 else:
2663 else:
2666 list = deleted + clean
2664 list = deleted + clean
2667 progress = ui.makeprogress(
2665 progress = ui.makeprogress(
2668 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2666 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2669 )
2667 )
2670 for f in modified:
2668 for f in modified:
2671 progress.increment()
2669 progress.increment()
2672 warnings.append(
2670 warnings.append(
2673 _(
2671 _(
2674 b'not removing %s: file is modified (use -f'
2672 b'not removing %s: file is modified (use -f'
2675 b' to force removal)\n'
2673 b' to force removal)\n'
2676 )
2674 )
2677 % uipathfn(f)
2675 % uipathfn(f)
2678 )
2676 )
2679 ret = 1
2677 ret = 1
2680 for f in added:
2678 for f in added:
2681 progress.increment()
2679 progress.increment()
2682 warnings.append(
2680 warnings.append(
2683 _(
2681 _(
2684 b"not removing %s: file has been marked for add"
2682 b"not removing %s: file has been marked for add"
2685 b" (use 'hg forget' to undo add)\n"
2683 b" (use 'hg forget' to undo add)\n"
2686 )
2684 )
2687 % uipathfn(f)
2685 % uipathfn(f)
2688 )
2686 )
2689 ret = 1
2687 ret = 1
2690 progress.complete()
2688 progress.complete()
2691
2689
2692 list = sorted(list)
2690 list = sorted(list)
2693 progress = ui.makeprogress(
2691 progress = ui.makeprogress(
2694 _(b'deleting'), total=len(list), unit=_(b'files')
2692 _(b'deleting'), total=len(list), unit=_(b'files')
2695 )
2693 )
2696 for f in list:
2694 for f in list:
2697 if ui.verbose or not m.exact(f):
2695 if ui.verbose or not m.exact(f):
2698 progress.increment()
2696 progress.increment()
2699 ui.status(
2697 ui.status(
2700 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2698 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2701 )
2699 )
2702 progress.complete()
2700 progress.complete()
2703
2701
2704 if not dryrun:
2702 if not dryrun:
2705 with repo.wlock():
2703 with repo.wlock():
2706 if not after:
2704 if not after:
2707 for f in list:
2705 for f in list:
2708 if f in added:
2706 if f in added:
2709 continue # we never unlink added files on remove
2707 continue # we never unlink added files on remove
2710 rmdir = repo.ui.configbool(
2708 rmdir = repo.ui.configbool(
2711 b'experimental', b'removeemptydirs'
2709 b'experimental', b'removeemptydirs'
2712 )
2710 )
2713 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2711 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2714 repo[None].forget(list)
2712 repo[None].forget(list)
2715
2713
2716 if warn:
2714 if warn:
2717 for warning in warnings:
2715 for warning in warnings:
2718 ui.warn(warning)
2716 ui.warn(warning)
2719
2717
2720 return ret
2718 return ret
2721
2719
2722
2720
2723 def _catfmtneedsdata(fm):
2721 def _catfmtneedsdata(fm):
2724 return not fm.datahint() or b'data' in fm.datahint()
2722 return not fm.datahint() or b'data' in fm.datahint()
2725
2723
2726
2724
2727 def _updatecatformatter(fm, ctx, matcher, path, decode):
2725 def _updatecatformatter(fm, ctx, matcher, path, decode):
2728 """Hook for adding data to the formatter used by ``hg cat``.
2726 """Hook for adding data to the formatter used by ``hg cat``.
2729
2727
2730 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2728 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2731 this method first."""
2729 this method first."""
2732
2730
2733 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2731 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2734 # wasn't requested.
2732 # wasn't requested.
2735 data = b''
2733 data = b''
2736 if _catfmtneedsdata(fm):
2734 if _catfmtneedsdata(fm):
2737 data = ctx[path].data()
2735 data = ctx[path].data()
2738 if decode:
2736 if decode:
2739 data = ctx.repo().wwritedata(path, data)
2737 data = ctx.repo().wwritedata(path, data)
2740 fm.startitem()
2738 fm.startitem()
2741 fm.context(ctx=ctx)
2739 fm.context(ctx=ctx)
2742 fm.write(b'data', b'%s', data)
2740 fm.write(b'data', b'%s', data)
2743 fm.data(path=path)
2741 fm.data(path=path)
2744
2742
2745
2743
2746 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2744 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2747 err = 1
2745 err = 1
2748 opts = pycompat.byteskwargs(opts)
2746 opts = pycompat.byteskwargs(opts)
2749
2747
2750 def write(path):
2748 def write(path):
2751 filename = None
2749 filename = None
2752 if fntemplate:
2750 if fntemplate:
2753 filename = makefilename(
2751 filename = makefilename(
2754 ctx, fntemplate, pathname=os.path.join(prefix, path)
2752 ctx, fntemplate, pathname=os.path.join(prefix, path)
2755 )
2753 )
2756 # attempt to create the directory if it does not already exist
2754 # attempt to create the directory if it does not already exist
2757 try:
2755 try:
2758 os.makedirs(os.path.dirname(filename))
2756 os.makedirs(os.path.dirname(filename))
2759 except OSError:
2757 except OSError:
2760 pass
2758 pass
2761 with formatter.maybereopen(basefm, filename) as fm:
2759 with formatter.maybereopen(basefm, filename) as fm:
2762 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2760 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2763
2761
2764 # Automation often uses hg cat on single files, so special case it
2762 # Automation often uses hg cat on single files, so special case it
2765 # for performance to avoid the cost of parsing the manifest.
2763 # for performance to avoid the cost of parsing the manifest.
2766 if len(matcher.files()) == 1 and not matcher.anypats():
2764 if len(matcher.files()) == 1 and not matcher.anypats():
2767 file = matcher.files()[0]
2765 file = matcher.files()[0]
2768 mfl = repo.manifestlog
2766 mfl = repo.manifestlog
2769 mfnode = ctx.manifestnode()
2767 mfnode = ctx.manifestnode()
2770 try:
2768 try:
2771 if mfnode and mfl[mfnode].find(file)[0]:
2769 if mfnode and mfl[mfnode].find(file)[0]:
2772 if _catfmtneedsdata(basefm):
2770 if _catfmtneedsdata(basefm):
2773 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2771 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2774 write(file)
2772 write(file)
2775 return 0
2773 return 0
2776 except KeyError:
2774 except KeyError:
2777 pass
2775 pass
2778
2776
2779 if _catfmtneedsdata(basefm):
2777 if _catfmtneedsdata(basefm):
2780 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2778 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2781
2779
2782 for abs in ctx.walk(matcher):
2780 for abs in ctx.walk(matcher):
2783 write(abs)
2781 write(abs)
2784 err = 0
2782 err = 0
2785
2783
2786 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2784 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2787 for subpath in sorted(ctx.substate):
2785 for subpath in sorted(ctx.substate):
2788 sub = ctx.sub(subpath)
2786 sub = ctx.sub(subpath)
2789 try:
2787 try:
2790 submatch = matchmod.subdirmatcher(subpath, matcher)
2788 submatch = matchmod.subdirmatcher(subpath, matcher)
2791 subprefix = os.path.join(prefix, subpath)
2789 subprefix = os.path.join(prefix, subpath)
2792 if not sub.cat(
2790 if not sub.cat(
2793 submatch,
2791 submatch,
2794 basefm,
2792 basefm,
2795 fntemplate,
2793 fntemplate,
2796 subprefix,
2794 subprefix,
2797 **pycompat.strkwargs(opts)
2795 **pycompat.strkwargs(opts)
2798 ):
2796 ):
2799 err = 0
2797 err = 0
2800 except error.RepoLookupError:
2798 except error.RepoLookupError:
2801 ui.status(
2799 ui.status(
2802 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2800 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2803 )
2801 )
2804
2802
2805 return err
2803 return err
2806
2804
2807
2805
2808 def commit(ui, repo, commitfunc, pats, opts):
2806 def commit(ui, repo, commitfunc, pats, opts):
2809 '''commit the specified files or all outstanding changes'''
2807 '''commit the specified files or all outstanding changes'''
2810 date = opts.get(b'date')
2808 date = opts.get(b'date')
2811 if date:
2809 if date:
2812 opts[b'date'] = dateutil.parsedate(date)
2810 opts[b'date'] = dateutil.parsedate(date)
2813 message = logmessage(ui, opts)
2811 message = logmessage(ui, opts)
2814 matcher = scmutil.match(repo[None], pats, opts)
2812 matcher = scmutil.match(repo[None], pats, opts)
2815
2813
2816 dsguard = None
2814 dsguard = None
2817 # extract addremove carefully -- this function can be called from a command
2815 # extract addremove carefully -- this function can be called from a command
2818 # that doesn't support addremove
2816 # that doesn't support addremove
2819 if opts.get(b'addremove'):
2817 if opts.get(b'addremove'):
2820 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2818 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2821 with dsguard or util.nullcontextmanager():
2819 with dsguard or util.nullcontextmanager():
2822 if dsguard:
2820 if dsguard:
2823 relative = scmutil.anypats(pats, opts)
2821 relative = scmutil.anypats(pats, opts)
2824 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2822 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2825 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2823 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2826 raise error.Abort(
2824 raise error.Abort(
2827 _(b"failed to mark all new/missing files as added/removed")
2825 _(b"failed to mark all new/missing files as added/removed")
2828 )
2826 )
2829
2827
2830 return commitfunc(ui, repo, message, matcher, opts)
2828 return commitfunc(ui, repo, message, matcher, opts)
2831
2829
2832
2830
2833 def samefile(f, ctx1, ctx2):
2831 def samefile(f, ctx1, ctx2):
2834 if f in ctx1.manifest():
2832 if f in ctx1.manifest():
2835 a = ctx1.filectx(f)
2833 a = ctx1.filectx(f)
2836 if f in ctx2.manifest():
2834 if f in ctx2.manifest():
2837 b = ctx2.filectx(f)
2835 b = ctx2.filectx(f)
2838 return not a.cmp(b) and a.flags() == b.flags()
2836 return not a.cmp(b) and a.flags() == b.flags()
2839 else:
2837 else:
2840 return False
2838 return False
2841 else:
2839 else:
2842 return f not in ctx2.manifest()
2840 return f not in ctx2.manifest()
2843
2841
2844
2842
2845 def amend(ui, repo, old, extra, pats, opts):
2843 def amend(ui, repo, old, extra, pats, opts):
2846 # avoid cycle context -> subrepo -> cmdutil
2844 # avoid cycle context -> subrepo -> cmdutil
2847 from . import context
2845 from . import context
2848
2846
2849 # amend will reuse the existing user if not specified, but the obsolete
2847 # amend will reuse the existing user if not specified, but the obsolete
2850 # marker creation requires that the current user's name is specified.
2848 # marker creation requires that the current user's name is specified.
2851 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2849 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2852 ui.username() # raise exception if username not set
2850 ui.username() # raise exception if username not set
2853
2851
2854 ui.note(_(b'amending changeset %s\n') % old)
2852 ui.note(_(b'amending changeset %s\n') % old)
2855 base = old.p1()
2853 base = old.p1()
2856
2854
2857 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2855 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2858 # Participating changesets:
2856 # Participating changesets:
2859 #
2857 #
2860 # wctx o - workingctx that contains changes from working copy
2858 # wctx o - workingctx that contains changes from working copy
2861 # | to go into amending commit
2859 # | to go into amending commit
2862 # |
2860 # |
2863 # old o - changeset to amend
2861 # old o - changeset to amend
2864 # |
2862 # |
2865 # base o - first parent of the changeset to amend
2863 # base o - first parent of the changeset to amend
2866 wctx = repo[None]
2864 wctx = repo[None]
2867
2865
2868 # Copy to avoid mutating input
2866 # Copy to avoid mutating input
2869 extra = extra.copy()
2867 extra = extra.copy()
2870 # Update extra dict from amended commit (e.g. to preserve graft
2868 # Update extra dict from amended commit (e.g. to preserve graft
2871 # source)
2869 # source)
2872 extra.update(old.extra())
2870 extra.update(old.extra())
2873
2871
2874 # Also update it from the from the wctx
2872 # Also update it from the from the wctx
2875 extra.update(wctx.extra())
2873 extra.update(wctx.extra())
2876
2874
2877 # date-only change should be ignored?
2875 # date-only change should be ignored?
2878 datemaydiffer = resolvecommitoptions(ui, opts)
2876 datemaydiffer = resolvecommitoptions(ui, opts)
2879
2877
2880 date = old.date()
2878 date = old.date()
2881 if opts.get(b'date'):
2879 if opts.get(b'date'):
2882 date = dateutil.parsedate(opts.get(b'date'))
2880 date = dateutil.parsedate(opts.get(b'date'))
2883 user = opts.get(b'user') or old.user()
2881 user = opts.get(b'user') or old.user()
2884
2882
2885 if len(old.parents()) > 1:
2883 if len(old.parents()) > 1:
2886 # ctx.files() isn't reliable for merges, so fall back to the
2884 # ctx.files() isn't reliable for merges, so fall back to the
2887 # slower repo.status() method
2885 # slower repo.status() method
2888 st = base.status(old)
2886 st = base.status(old)
2889 files = set(st.modified) | set(st.added) | set(st.removed)
2887 files = set(st.modified) | set(st.added) | set(st.removed)
2890 else:
2888 else:
2891 files = set(old.files())
2889 files = set(old.files())
2892
2890
2893 # add/remove the files to the working copy if the "addremove" option
2891 # add/remove the files to the working copy if the "addremove" option
2894 # was specified.
2892 # was specified.
2895 matcher = scmutil.match(wctx, pats, opts)
2893 matcher = scmutil.match(wctx, pats, opts)
2896 relative = scmutil.anypats(pats, opts)
2894 relative = scmutil.anypats(pats, opts)
2897 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2895 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2898 if opts.get(b'addremove') and scmutil.addremove(
2896 if opts.get(b'addremove') and scmutil.addremove(
2899 repo, matcher, b"", uipathfn, opts
2897 repo, matcher, b"", uipathfn, opts
2900 ):
2898 ):
2901 raise error.Abort(
2899 raise error.Abort(
2902 _(b"failed to mark all new/missing files as added/removed")
2900 _(b"failed to mark all new/missing files as added/removed")
2903 )
2901 )
2904
2902
2905 # Check subrepos. This depends on in-place wctx._status update in
2903 # Check subrepos. This depends on in-place wctx._status update in
2906 # subrepo.precommit(). To minimize the risk of this hack, we do
2904 # subrepo.precommit(). To minimize the risk of this hack, we do
2907 # nothing if .hgsub does not exist.
2905 # nothing if .hgsub does not exist.
2908 if b'.hgsub' in wctx or b'.hgsub' in old:
2906 if b'.hgsub' in wctx or b'.hgsub' in old:
2909 subs, commitsubs, newsubstate = subrepoutil.precommit(
2907 subs, commitsubs, newsubstate = subrepoutil.precommit(
2910 ui, wctx, wctx._status, matcher
2908 ui, wctx, wctx._status, matcher
2911 )
2909 )
2912 # amend should abort if commitsubrepos is enabled
2910 # amend should abort if commitsubrepos is enabled
2913 assert not commitsubs
2911 assert not commitsubs
2914 if subs:
2912 if subs:
2915 subrepoutil.writestate(repo, newsubstate)
2913 subrepoutil.writestate(repo, newsubstate)
2916
2914
2917 ms = mergemod.mergestate.read(repo)
2915 ms = mergemod.mergestate.read(repo)
2918 mergeutil.checkunresolved(ms)
2916 mergeutil.checkunresolved(ms)
2919
2917
2920 filestoamend = set(f for f in wctx.files() if matcher(f))
2918 filestoamend = set(f for f in wctx.files() if matcher(f))
2921
2919
2922 changes = len(filestoamend) > 0
2920 changes = len(filestoamend) > 0
2923 if changes:
2921 if changes:
2924 # Recompute copies (avoid recording a -> b -> a)
2922 # Recompute copies (avoid recording a -> b -> a)
2925 copied = copies.pathcopies(base, wctx, matcher)
2923 copied = copies.pathcopies(base, wctx, matcher)
2926 if old.p2:
2924 if old.p2:
2927 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2925 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2928
2926
2929 # Prune files which were reverted by the updates: if old
2927 # Prune files which were reverted by the updates: if old
2930 # introduced file X and the file was renamed in the working
2928 # introduced file X and the file was renamed in the working
2931 # copy, then those two files are the same and
2929 # copy, then those two files are the same and
2932 # we can discard X from our list of files. Likewise if X
2930 # we can discard X from our list of files. Likewise if X
2933 # was removed, it's no longer relevant. If X is missing (aka
2931 # was removed, it's no longer relevant. If X is missing (aka
2934 # deleted), old X must be preserved.
2932 # deleted), old X must be preserved.
2935 files.update(filestoamend)
2933 files.update(filestoamend)
2936 files = [
2934 files = [
2937 f
2935 f
2938 for f in files
2936 for f in files
2939 if (f not in filestoamend or not samefile(f, wctx, base))
2937 if (f not in filestoamend or not samefile(f, wctx, base))
2940 ]
2938 ]
2941
2939
2942 def filectxfn(repo, ctx_, path):
2940 def filectxfn(repo, ctx_, path):
2943 try:
2941 try:
2944 # If the file being considered is not amongst the files
2942 # If the file being considered is not amongst the files
2945 # to be amended, we should return the file context from the
2943 # to be amended, we should return the file context from the
2946 # old changeset. This avoids issues when only some files in
2944 # old changeset. This avoids issues when only some files in
2947 # the working copy are being amended but there are also
2945 # the working copy are being amended but there are also
2948 # changes to other files from the old changeset.
2946 # changes to other files from the old changeset.
2949 if path not in filestoamend:
2947 if path not in filestoamend:
2950 return old.filectx(path)
2948 return old.filectx(path)
2951
2949
2952 # Return None for removed files.
2950 # Return None for removed files.
2953 if path in wctx.removed():
2951 if path in wctx.removed():
2954 return None
2952 return None
2955
2953
2956 fctx = wctx[path]
2954 fctx = wctx[path]
2957 flags = fctx.flags()
2955 flags = fctx.flags()
2958 mctx = context.memfilectx(
2956 mctx = context.memfilectx(
2959 repo,
2957 repo,
2960 ctx_,
2958 ctx_,
2961 fctx.path(),
2959 fctx.path(),
2962 fctx.data(),
2960 fctx.data(),
2963 islink=b'l' in flags,
2961 islink=b'l' in flags,
2964 isexec=b'x' in flags,
2962 isexec=b'x' in flags,
2965 copysource=copied.get(path),
2963 copysource=copied.get(path),
2966 )
2964 )
2967 return mctx
2965 return mctx
2968 except KeyError:
2966 except KeyError:
2969 return None
2967 return None
2970
2968
2971 else:
2969 else:
2972 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2970 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2973
2971
2974 # Use version of files as in the old cset
2972 # Use version of files as in the old cset
2975 def filectxfn(repo, ctx_, path):
2973 def filectxfn(repo, ctx_, path):
2976 try:
2974 try:
2977 return old.filectx(path)
2975 return old.filectx(path)
2978 except KeyError:
2976 except KeyError:
2979 return None
2977 return None
2980
2978
2981 # See if we got a message from -m or -l, if not, open the editor with
2979 # See if we got a message from -m or -l, if not, open the editor with
2982 # the message of the changeset to amend.
2980 # the message of the changeset to amend.
2983 message = logmessage(ui, opts)
2981 message = logmessage(ui, opts)
2984
2982
2985 editform = mergeeditform(old, b'commit.amend')
2983 editform = mergeeditform(old, b'commit.amend')
2986
2984
2987 if not message:
2985 if not message:
2988 message = old.description()
2986 message = old.description()
2989 # Default if message isn't provided and --edit is not passed is to
2987 # Default if message isn't provided and --edit is not passed is to
2990 # invoke editor, but allow --no-edit. If somehow we don't have any
2988 # invoke editor, but allow --no-edit. If somehow we don't have any
2991 # description, let's always start the editor.
2989 # description, let's always start the editor.
2992 doedit = not message or opts.get(b'edit') in [True, None]
2990 doedit = not message or opts.get(b'edit') in [True, None]
2993 else:
2991 else:
2994 # Default if message is provided is to not invoke editor, but allow
2992 # Default if message is provided is to not invoke editor, but allow
2995 # --edit.
2993 # --edit.
2996 doedit = opts.get(b'edit') is True
2994 doedit = opts.get(b'edit') is True
2997 editor = getcommiteditor(edit=doedit, editform=editform)
2995 editor = getcommiteditor(edit=doedit, editform=editform)
2998
2996
2999 pureextra = extra.copy()
2997 pureextra = extra.copy()
3000 extra[b'amend_source'] = old.hex()
2998 extra[b'amend_source'] = old.hex()
3001
2999
3002 new = context.memctx(
3000 new = context.memctx(
3003 repo,
3001 repo,
3004 parents=[base.node(), old.p2().node()],
3002 parents=[base.node(), old.p2().node()],
3005 text=message,
3003 text=message,
3006 files=files,
3004 files=files,
3007 filectxfn=filectxfn,
3005 filectxfn=filectxfn,
3008 user=user,
3006 user=user,
3009 date=date,
3007 date=date,
3010 extra=extra,
3008 extra=extra,
3011 editor=editor,
3009 editor=editor,
3012 )
3010 )
3013
3011
3014 newdesc = changelog.stripdesc(new.description())
3012 newdesc = changelog.stripdesc(new.description())
3015 if (
3013 if (
3016 (not changes)
3014 (not changes)
3017 and newdesc == old.description()
3015 and newdesc == old.description()
3018 and user == old.user()
3016 and user == old.user()
3019 and (date == old.date() or datemaydiffer)
3017 and (date == old.date() or datemaydiffer)
3020 and pureextra == old.extra()
3018 and pureextra == old.extra()
3021 ):
3019 ):
3022 # nothing changed. continuing here would create a new node
3020 # nothing changed. continuing here would create a new node
3023 # anyway because of the amend_source noise.
3021 # anyway because of the amend_source noise.
3024 #
3022 #
3025 # This not what we expect from amend.
3023 # This not what we expect from amend.
3026 return old.node()
3024 return old.node()
3027
3025
3028 commitphase = None
3026 commitphase = None
3029 if opts.get(b'secret'):
3027 if opts.get(b'secret'):
3030 commitphase = phases.secret
3028 commitphase = phases.secret
3031 newid = repo.commitctx(new)
3029 newid = repo.commitctx(new)
3032
3030
3033 # Reroute the working copy parent to the new changeset
3031 # Reroute the working copy parent to the new changeset
3034 repo.setparents(newid, nullid)
3032 repo.setparents(newid, nullid)
3035 mapping = {old.node(): (newid,)}
3033 mapping = {old.node(): (newid,)}
3036 obsmetadata = None
3034 obsmetadata = None
3037 if opts.get(b'note'):
3035 if opts.get(b'note'):
3038 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3036 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3039 backup = ui.configbool(b'rewrite', b'backup-bundle')
3037 backup = ui.configbool(b'rewrite', b'backup-bundle')
3040 scmutil.cleanupnodes(
3038 scmutil.cleanupnodes(
3041 repo,
3039 repo,
3042 mapping,
3040 mapping,
3043 b'amend',
3041 b'amend',
3044 metadata=obsmetadata,
3042 metadata=obsmetadata,
3045 fixphase=True,
3043 fixphase=True,
3046 targetphase=commitphase,
3044 targetphase=commitphase,
3047 backup=backup,
3045 backup=backup,
3048 )
3046 )
3049
3047
3050 # Fixing the dirstate because localrepo.commitctx does not update
3048 # Fixing the dirstate because localrepo.commitctx does not update
3051 # it. This is rather convenient because we did not need to update
3049 # it. This is rather convenient because we did not need to update
3052 # the dirstate for all the files in the new commit which commitctx
3050 # the dirstate for all the files in the new commit which commitctx
3053 # could have done if it updated the dirstate. Now, we can
3051 # could have done if it updated the dirstate. Now, we can
3054 # selectively update the dirstate only for the amended files.
3052 # selectively update the dirstate only for the amended files.
3055 dirstate = repo.dirstate
3053 dirstate = repo.dirstate
3056
3054
3057 # Update the state of the files which were added and
3055 # Update the state of the files which were added and
3058 # and modified in the amend to "normal" in the dirstate.
3056 # and modified in the amend to "normal" in the dirstate.
3059 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3057 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3060 for f in normalfiles:
3058 for f in normalfiles:
3061 dirstate.normal(f)
3059 dirstate.normal(f)
3062
3060
3063 # Update the state of files which were removed in the amend
3061 # Update the state of files which were removed in the amend
3064 # to "removed" in the dirstate.
3062 # to "removed" in the dirstate.
3065 removedfiles = set(wctx.removed()) & filestoamend
3063 removedfiles = set(wctx.removed()) & filestoamend
3066 for f in removedfiles:
3064 for f in removedfiles:
3067 dirstate.drop(f)
3065 dirstate.drop(f)
3068
3066
3069 return newid
3067 return newid
3070
3068
3071
3069
3072 def commiteditor(repo, ctx, subs, editform=b''):
3070 def commiteditor(repo, ctx, subs, editform=b''):
3073 if ctx.description():
3071 if ctx.description():
3074 return ctx.description()
3072 return ctx.description()
3075 return commitforceeditor(
3073 return commitforceeditor(
3076 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3074 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3077 )
3075 )
3078
3076
3079
3077
3080 def commitforceeditor(
3078 def commitforceeditor(
3081 repo,
3079 repo,
3082 ctx,
3080 ctx,
3083 subs,
3081 subs,
3084 finishdesc=None,
3082 finishdesc=None,
3085 extramsg=None,
3083 extramsg=None,
3086 editform=b'',
3084 editform=b'',
3087 unchangedmessagedetection=False,
3085 unchangedmessagedetection=False,
3088 ):
3086 ):
3089 if not extramsg:
3087 if not extramsg:
3090 extramsg = _(b"Leave message empty to abort commit.")
3088 extramsg = _(b"Leave message empty to abort commit.")
3091
3089
3092 forms = [e for e in editform.split(b'.') if e]
3090 forms = [e for e in editform.split(b'.') if e]
3093 forms.insert(0, b'changeset')
3091 forms.insert(0, b'changeset')
3094 templatetext = None
3092 templatetext = None
3095 while forms:
3093 while forms:
3096 ref = b'.'.join(forms)
3094 ref = b'.'.join(forms)
3097 if repo.ui.config(b'committemplate', ref):
3095 if repo.ui.config(b'committemplate', ref):
3098 templatetext = committext = buildcommittemplate(
3096 templatetext = committext = buildcommittemplate(
3099 repo, ctx, subs, extramsg, ref
3097 repo, ctx, subs, extramsg, ref
3100 )
3098 )
3101 break
3099 break
3102 forms.pop()
3100 forms.pop()
3103 else:
3101 else:
3104 committext = buildcommittext(repo, ctx, subs, extramsg)
3102 committext = buildcommittext(repo, ctx, subs, extramsg)
3105
3103
3106 # run editor in the repository root
3104 # run editor in the repository root
3107 olddir = encoding.getcwd()
3105 olddir = encoding.getcwd()
3108 os.chdir(repo.root)
3106 os.chdir(repo.root)
3109
3107
3110 # make in-memory changes visible to external process
3108 # make in-memory changes visible to external process
3111 tr = repo.currenttransaction()
3109 tr = repo.currenttransaction()
3112 repo.dirstate.write(tr)
3110 repo.dirstate.write(tr)
3113 pending = tr and tr.writepending() and repo.root
3111 pending = tr and tr.writepending() and repo.root
3114
3112
3115 editortext = repo.ui.edit(
3113 editortext = repo.ui.edit(
3116 committext,
3114 committext,
3117 ctx.user(),
3115 ctx.user(),
3118 ctx.extra(),
3116 ctx.extra(),
3119 editform=editform,
3117 editform=editform,
3120 pending=pending,
3118 pending=pending,
3121 repopath=repo.path,
3119 repopath=repo.path,
3122 action=b'commit',
3120 action=b'commit',
3123 )
3121 )
3124 text = editortext
3122 text = editortext
3125
3123
3126 # strip away anything below this special string (used for editors that want
3124 # strip away anything below this special string (used for editors that want
3127 # to display the diff)
3125 # to display the diff)
3128 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3126 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3129 if stripbelow:
3127 if stripbelow:
3130 text = text[: stripbelow.start()]
3128 text = text[: stripbelow.start()]
3131
3129
3132 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3130 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3133 os.chdir(olddir)
3131 os.chdir(olddir)
3134
3132
3135 if finishdesc:
3133 if finishdesc:
3136 text = finishdesc(text)
3134 text = finishdesc(text)
3137 if not text.strip():
3135 if not text.strip():
3138 raise error.Abort(_(b"empty commit message"))
3136 raise error.Abort(_(b"empty commit message"))
3139 if unchangedmessagedetection and editortext == templatetext:
3137 if unchangedmessagedetection and editortext == templatetext:
3140 raise error.Abort(_(b"commit message unchanged"))
3138 raise error.Abort(_(b"commit message unchanged"))
3141
3139
3142 return text
3140 return text
3143
3141
3144
3142
3145 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3143 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3146 ui = repo.ui
3144 ui = repo.ui
3147 spec = formatter.templatespec(ref, None, None)
3145 spec = formatter.templatespec(ref, None, None)
3148 t = logcmdutil.changesettemplater(ui, repo, spec)
3146 t = logcmdutil.changesettemplater(ui, repo, spec)
3149 t.t.cache.update(
3147 t.t.cache.update(
3150 (k, templater.unquotestring(v))
3148 (k, templater.unquotestring(v))
3151 for k, v in repo.ui.configitems(b'committemplate')
3149 for k, v in repo.ui.configitems(b'committemplate')
3152 )
3150 )
3153
3151
3154 if not extramsg:
3152 if not extramsg:
3155 extramsg = b'' # ensure that extramsg is string
3153 extramsg = b'' # ensure that extramsg is string
3156
3154
3157 ui.pushbuffer()
3155 ui.pushbuffer()
3158 t.show(ctx, extramsg=extramsg)
3156 t.show(ctx, extramsg=extramsg)
3159 return ui.popbuffer()
3157 return ui.popbuffer()
3160
3158
3161
3159
3162 def hgprefix(msg):
3160 def hgprefix(msg):
3163 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3161 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3164
3162
3165
3163
3166 def buildcommittext(repo, ctx, subs, extramsg):
3164 def buildcommittext(repo, ctx, subs, extramsg):
3167 edittext = []
3165 edittext = []
3168 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3166 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3169 if ctx.description():
3167 if ctx.description():
3170 edittext.append(ctx.description())
3168 edittext.append(ctx.description())
3171 edittext.append(b"")
3169 edittext.append(b"")
3172 edittext.append(b"") # Empty line between message and comments.
3170 edittext.append(b"") # Empty line between message and comments.
3173 edittext.append(
3171 edittext.append(
3174 hgprefix(
3172 hgprefix(
3175 _(
3173 _(
3176 b"Enter commit message."
3174 b"Enter commit message."
3177 b" Lines beginning with 'HG:' are removed."
3175 b" Lines beginning with 'HG:' are removed."
3178 )
3176 )
3179 )
3177 )
3180 )
3178 )
3181 edittext.append(hgprefix(extramsg))
3179 edittext.append(hgprefix(extramsg))
3182 edittext.append(b"HG: --")
3180 edittext.append(b"HG: --")
3183 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3181 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3184 if ctx.p2():
3182 if ctx.p2():
3185 edittext.append(hgprefix(_(b"branch merge")))
3183 edittext.append(hgprefix(_(b"branch merge")))
3186 if ctx.branch():
3184 if ctx.branch():
3187 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3185 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3188 if bookmarks.isactivewdirparent(repo):
3186 if bookmarks.isactivewdirparent(repo):
3189 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3187 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3190 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3188 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3191 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3189 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3192 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3190 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3193 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3191 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3194 if not added and not modified and not removed:
3192 if not added and not modified and not removed:
3195 edittext.append(hgprefix(_(b"no files changed")))
3193 edittext.append(hgprefix(_(b"no files changed")))
3196 edittext.append(b"")
3194 edittext.append(b"")
3197
3195
3198 return b"\n".join(edittext)
3196 return b"\n".join(edittext)
3199
3197
3200
3198
3201 def commitstatus(repo, node, branch, bheads=None, opts=None):
3199 def commitstatus(repo, node, branch, bheads=None, opts=None):
3202 if opts is None:
3200 if opts is None:
3203 opts = {}
3201 opts = {}
3204 ctx = repo[node]
3202 ctx = repo[node]
3205 parents = ctx.parents()
3203 parents = ctx.parents()
3206
3204
3207 if (
3205 if (
3208 not opts.get(b'amend')
3206 not opts.get(b'amend')
3209 and bheads
3207 and bheads
3210 and node not in bheads
3208 and node not in bheads
3211 and not [
3209 and not [
3212 x for x in parents if x.node() in bheads and x.branch() == branch
3210 x for x in parents if x.node() in bheads and x.branch() == branch
3213 ]
3211 ]
3214 ):
3212 ):
3215 repo.ui.status(_(b'created new head\n'))
3213 repo.ui.status(_(b'created new head\n'))
3216 # The message is not printed for initial roots. For the other
3214 # The message is not printed for initial roots. For the other
3217 # changesets, it is printed in the following situations:
3215 # changesets, it is printed in the following situations:
3218 #
3216 #
3219 # Par column: for the 2 parents with ...
3217 # Par column: for the 2 parents with ...
3220 # N: null or no parent
3218 # N: null or no parent
3221 # B: parent is on another named branch
3219 # B: parent is on another named branch
3222 # C: parent is a regular non head changeset
3220 # C: parent is a regular non head changeset
3223 # H: parent was a branch head of the current branch
3221 # H: parent was a branch head of the current branch
3224 # Msg column: whether we print "created new head" message
3222 # Msg column: whether we print "created new head" message
3225 # In the following, it is assumed that there already exists some
3223 # In the following, it is assumed that there already exists some
3226 # initial branch heads of the current branch, otherwise nothing is
3224 # initial branch heads of the current branch, otherwise nothing is
3227 # printed anyway.
3225 # printed anyway.
3228 #
3226 #
3229 # Par Msg Comment
3227 # Par Msg Comment
3230 # N N y additional topo root
3228 # N N y additional topo root
3231 #
3229 #
3232 # B N y additional branch root
3230 # B N y additional branch root
3233 # C N y additional topo head
3231 # C N y additional topo head
3234 # H N n usual case
3232 # H N n usual case
3235 #
3233 #
3236 # B B y weird additional branch root
3234 # B B y weird additional branch root
3237 # C B y branch merge
3235 # C B y branch merge
3238 # H B n merge with named branch
3236 # H B n merge with named branch
3239 #
3237 #
3240 # C C y additional head from merge
3238 # C C y additional head from merge
3241 # C H n merge with a head
3239 # C H n merge with a head
3242 #
3240 #
3243 # H H n head merge: head count decreases
3241 # H H n head merge: head count decreases
3244
3242
3245 if not opts.get(b'close_branch'):
3243 if not opts.get(b'close_branch'):
3246 for r in parents:
3244 for r in parents:
3247 if r.closesbranch() and r.branch() == branch:
3245 if r.closesbranch() and r.branch() == branch:
3248 repo.ui.status(
3246 repo.ui.status(
3249 _(b'reopening closed branch head %d\n') % r.rev()
3247 _(b'reopening closed branch head %d\n') % r.rev()
3250 )
3248 )
3251
3249
3252 if repo.ui.debugflag:
3250 if repo.ui.debugflag:
3253 repo.ui.write(
3251 repo.ui.write(
3254 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3252 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3255 )
3253 )
3256 elif repo.ui.verbose:
3254 elif repo.ui.verbose:
3257 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3255 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3258
3256
3259
3257
3260 def postcommitstatus(repo, pats, opts):
3258 def postcommitstatus(repo, pats, opts):
3261 return repo.status(match=scmutil.match(repo[None], pats, opts))
3259 return repo.status(match=scmutil.match(repo[None], pats, opts))
3262
3260
3263
3261
3264 def revert(ui, repo, ctx, parents, *pats, **opts):
3262 def revert(ui, repo, ctx, parents, *pats, **opts):
3265 opts = pycompat.byteskwargs(opts)
3263 opts = pycompat.byteskwargs(opts)
3266 parent, p2 = parents
3264 parent, p2 = parents
3267 node = ctx.node()
3265 node = ctx.node()
3268
3266
3269 mf = ctx.manifest()
3267 mf = ctx.manifest()
3270 if node == p2:
3268 if node == p2:
3271 parent = p2
3269 parent = p2
3272
3270
3273 # need all matching names in dirstate and manifest of target rev,
3271 # need all matching names in dirstate and manifest of target rev,
3274 # so have to walk both. do not print errors if files exist in one
3272 # so have to walk both. do not print errors if files exist in one
3275 # but not other. in both cases, filesets should be evaluated against
3273 # but not other. in both cases, filesets should be evaluated against
3276 # workingctx to get consistent result (issue4497). this means 'set:**'
3274 # workingctx to get consistent result (issue4497). this means 'set:**'
3277 # cannot be used to select missing files from target rev.
3275 # cannot be used to select missing files from target rev.
3278
3276
3279 # `names` is a mapping for all elements in working copy and target revision
3277 # `names` is a mapping for all elements in working copy and target revision
3280 # The mapping is in the form:
3278 # The mapping is in the form:
3281 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3279 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3282 names = {}
3280 names = {}
3283 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3281 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3284
3282
3285 with repo.wlock():
3283 with repo.wlock():
3286 ## filling of the `names` mapping
3284 ## filling of the `names` mapping
3287 # walk dirstate to fill `names`
3285 # walk dirstate to fill `names`
3288
3286
3289 interactive = opts.get(b'interactive', False)
3287 interactive = opts.get(b'interactive', False)
3290 wctx = repo[None]
3288 wctx = repo[None]
3291 m = scmutil.match(wctx, pats, opts)
3289 m = scmutil.match(wctx, pats, opts)
3292
3290
3293 # we'll need this later
3291 # we'll need this later
3294 targetsubs = sorted(s for s in wctx.substate if m(s))
3292 targetsubs = sorted(s for s in wctx.substate if m(s))
3295
3293
3296 if not m.always():
3294 if not m.always():
3297 matcher = matchmod.badmatch(m, lambda x, y: False)
3295 matcher = matchmod.badmatch(m, lambda x, y: False)
3298 for abs in wctx.walk(matcher):
3296 for abs in wctx.walk(matcher):
3299 names[abs] = m.exact(abs)
3297 names[abs] = m.exact(abs)
3300
3298
3301 # walk target manifest to fill `names`
3299 # walk target manifest to fill `names`
3302
3300
3303 def badfn(path, msg):
3301 def badfn(path, msg):
3304 if path in names:
3302 if path in names:
3305 return
3303 return
3306 if path in ctx.substate:
3304 if path in ctx.substate:
3307 return
3305 return
3308 path_ = path + b'/'
3306 path_ = path + b'/'
3309 for f in names:
3307 for f in names:
3310 if f.startswith(path_):
3308 if f.startswith(path_):
3311 return
3309 return
3312 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3310 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3313
3311
3314 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3312 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3315 if abs not in names:
3313 if abs not in names:
3316 names[abs] = m.exact(abs)
3314 names[abs] = m.exact(abs)
3317
3315
3318 # Find status of all file in `names`.
3316 # Find status of all file in `names`.
3319 m = scmutil.matchfiles(repo, names)
3317 m = scmutil.matchfiles(repo, names)
3320
3318
3321 changes = repo.status(
3319 changes = repo.status(
3322 node1=node, match=m, unknown=True, ignored=True, clean=True
3320 node1=node, match=m, unknown=True, ignored=True, clean=True
3323 )
3321 )
3324 else:
3322 else:
3325 changes = repo.status(node1=node, match=m)
3323 changes = repo.status(node1=node, match=m)
3326 for kind in changes:
3324 for kind in changes:
3327 for abs in kind:
3325 for abs in kind:
3328 names[abs] = m.exact(abs)
3326 names[abs] = m.exact(abs)
3329
3327
3330 m = scmutil.matchfiles(repo, names)
3328 m = scmutil.matchfiles(repo, names)
3331
3329
3332 modified = set(changes.modified)
3330 modified = set(changes.modified)
3333 added = set(changes.added)
3331 added = set(changes.added)
3334 removed = set(changes.removed)
3332 removed = set(changes.removed)
3335 _deleted = set(changes.deleted)
3333 _deleted = set(changes.deleted)
3336 unknown = set(changes.unknown)
3334 unknown = set(changes.unknown)
3337 unknown.update(changes.ignored)
3335 unknown.update(changes.ignored)
3338 clean = set(changes.clean)
3336 clean = set(changes.clean)
3339 modadded = set()
3337 modadded = set()
3340
3338
3341 # We need to account for the state of the file in the dirstate,
3339 # We need to account for the state of the file in the dirstate,
3342 # even when we revert against something else than parent. This will
3340 # even when we revert against something else than parent. This will
3343 # slightly alter the behavior of revert (doing back up or not, delete
3341 # slightly alter the behavior of revert (doing back up or not, delete
3344 # or just forget etc).
3342 # or just forget etc).
3345 if parent == node:
3343 if parent == node:
3346 dsmodified = modified
3344 dsmodified = modified
3347 dsadded = added
3345 dsadded = added
3348 dsremoved = removed
3346 dsremoved = removed
3349 # store all local modifications, useful later for rename detection
3347 # store all local modifications, useful later for rename detection
3350 localchanges = dsmodified | dsadded
3348 localchanges = dsmodified | dsadded
3351 modified, added, removed = set(), set(), set()
3349 modified, added, removed = set(), set(), set()
3352 else:
3350 else:
3353 changes = repo.status(node1=parent, match=m)
3351 changes = repo.status(node1=parent, match=m)
3354 dsmodified = set(changes.modified)
3352 dsmodified = set(changes.modified)
3355 dsadded = set(changes.added)
3353 dsadded = set(changes.added)
3356 dsremoved = set(changes.removed)
3354 dsremoved = set(changes.removed)
3357 # store all local modifications, useful later for rename detection
3355 # store all local modifications, useful later for rename detection
3358 localchanges = dsmodified | dsadded
3356 localchanges = dsmodified | dsadded
3359
3357
3360 # only take into account for removes between wc and target
3358 # only take into account for removes between wc and target
3361 clean |= dsremoved - removed
3359 clean |= dsremoved - removed
3362 dsremoved &= removed
3360 dsremoved &= removed
3363 # distinct between dirstate remove and other
3361 # distinct between dirstate remove and other
3364 removed -= dsremoved
3362 removed -= dsremoved
3365
3363
3366 modadded = added & dsmodified
3364 modadded = added & dsmodified
3367 added -= modadded
3365 added -= modadded
3368
3366
3369 # tell newly modified apart.
3367 # tell newly modified apart.
3370 dsmodified &= modified
3368 dsmodified &= modified
3371 dsmodified |= modified & dsadded # dirstate added may need backup
3369 dsmodified |= modified & dsadded # dirstate added may need backup
3372 modified -= dsmodified
3370 modified -= dsmodified
3373
3371
3374 # We need to wait for some post-processing to update this set
3372 # We need to wait for some post-processing to update this set
3375 # before making the distinction. The dirstate will be used for
3373 # before making the distinction. The dirstate will be used for
3376 # that purpose.
3374 # that purpose.
3377 dsadded = added
3375 dsadded = added
3378
3376
3379 # in case of merge, files that are actually added can be reported as
3377 # in case of merge, files that are actually added can be reported as
3380 # modified, we need to post process the result
3378 # modified, we need to post process the result
3381 if p2 != nullid:
3379 if p2 != nullid:
3382 mergeadd = set(dsmodified)
3380 mergeadd = set(dsmodified)
3383 for path in dsmodified:
3381 for path in dsmodified:
3384 if path in mf:
3382 if path in mf:
3385 mergeadd.remove(path)
3383 mergeadd.remove(path)
3386 dsadded |= mergeadd
3384 dsadded |= mergeadd
3387 dsmodified -= mergeadd
3385 dsmodified -= mergeadd
3388
3386
3389 # if f is a rename, update `names` to also revert the source
3387 # if f is a rename, update `names` to also revert the source
3390 for f in localchanges:
3388 for f in localchanges:
3391 src = repo.dirstate.copied(f)
3389 src = repo.dirstate.copied(f)
3392 # XXX should we check for rename down to target node?
3390 # XXX should we check for rename down to target node?
3393 if src and src not in names and repo.dirstate[src] == b'r':
3391 if src and src not in names and repo.dirstate[src] == b'r':
3394 dsremoved.add(src)
3392 dsremoved.add(src)
3395 names[src] = True
3393 names[src] = True
3396
3394
3397 # determine the exact nature of the deleted changesets
3395 # determine the exact nature of the deleted changesets
3398 deladded = set(_deleted)
3396 deladded = set(_deleted)
3399 for path in _deleted:
3397 for path in _deleted:
3400 if path in mf:
3398 if path in mf:
3401 deladded.remove(path)
3399 deladded.remove(path)
3402 deleted = _deleted - deladded
3400 deleted = _deleted - deladded
3403
3401
3404 # distinguish between file to forget and the other
3402 # distinguish between file to forget and the other
3405 added = set()
3403 added = set()
3406 for abs in dsadded:
3404 for abs in dsadded:
3407 if repo.dirstate[abs] != b'a':
3405 if repo.dirstate[abs] != b'a':
3408 added.add(abs)
3406 added.add(abs)
3409 dsadded -= added
3407 dsadded -= added
3410
3408
3411 for abs in deladded:
3409 for abs in deladded:
3412 if repo.dirstate[abs] == b'a':
3410 if repo.dirstate[abs] == b'a':
3413 dsadded.add(abs)
3411 dsadded.add(abs)
3414 deladded -= dsadded
3412 deladded -= dsadded
3415
3413
3416 # For files marked as removed, we check if an unknown file is present at
3414 # For files marked as removed, we check if an unknown file is present at
3417 # the same path. If a such file exists it may need to be backed up.
3415 # the same path. If a such file exists it may need to be backed up.
3418 # Making the distinction at this stage helps have simpler backup
3416 # Making the distinction at this stage helps have simpler backup
3419 # logic.
3417 # logic.
3420 removunk = set()
3418 removunk = set()
3421 for abs in removed:
3419 for abs in removed:
3422 target = repo.wjoin(abs)
3420 target = repo.wjoin(abs)
3423 if os.path.lexists(target):
3421 if os.path.lexists(target):
3424 removunk.add(abs)
3422 removunk.add(abs)
3425 removed -= removunk
3423 removed -= removunk
3426
3424
3427 dsremovunk = set()
3425 dsremovunk = set()
3428 for abs in dsremoved:
3426 for abs in dsremoved:
3429 target = repo.wjoin(abs)
3427 target = repo.wjoin(abs)
3430 if os.path.lexists(target):
3428 if os.path.lexists(target):
3431 dsremovunk.add(abs)
3429 dsremovunk.add(abs)
3432 dsremoved -= dsremovunk
3430 dsremoved -= dsremovunk
3433
3431
3434 # action to be actually performed by revert
3432 # action to be actually performed by revert
3435 # (<list of file>, message>) tuple
3433 # (<list of file>, message>) tuple
3436 actions = {
3434 actions = {
3437 b'revert': ([], _(b'reverting %s\n')),
3435 b'revert': ([], _(b'reverting %s\n')),
3438 b'add': ([], _(b'adding %s\n')),
3436 b'add': ([], _(b'adding %s\n')),
3439 b'remove': ([], _(b'removing %s\n')),
3437 b'remove': ([], _(b'removing %s\n')),
3440 b'drop': ([], _(b'removing %s\n')),
3438 b'drop': ([], _(b'removing %s\n')),
3441 b'forget': ([], _(b'forgetting %s\n')),
3439 b'forget': ([], _(b'forgetting %s\n')),
3442 b'undelete': ([], _(b'undeleting %s\n')),
3440 b'undelete': ([], _(b'undeleting %s\n')),
3443 b'noop': (None, _(b'no changes needed to %s\n')),
3441 b'noop': (None, _(b'no changes needed to %s\n')),
3444 b'unknown': (None, _(b'file not managed: %s\n')),
3442 b'unknown': (None, _(b'file not managed: %s\n')),
3445 }
3443 }
3446
3444
3447 # "constant" that convey the backup strategy.
3445 # "constant" that convey the backup strategy.
3448 # All set to `discard` if `no-backup` is set do avoid checking
3446 # All set to `discard` if `no-backup` is set do avoid checking
3449 # no_backup lower in the code.
3447 # no_backup lower in the code.
3450 # These values are ordered for comparison purposes
3448 # These values are ordered for comparison purposes
3451 backupinteractive = 3 # do backup if interactively modified
3449 backupinteractive = 3 # do backup if interactively modified
3452 backup = 2 # unconditionally do backup
3450 backup = 2 # unconditionally do backup
3453 check = 1 # check if the existing file differs from target
3451 check = 1 # check if the existing file differs from target
3454 discard = 0 # never do backup
3452 discard = 0 # never do backup
3455 if opts.get(b'no_backup'):
3453 if opts.get(b'no_backup'):
3456 backupinteractive = backup = check = discard
3454 backupinteractive = backup = check = discard
3457 if interactive:
3455 if interactive:
3458 dsmodifiedbackup = backupinteractive
3456 dsmodifiedbackup = backupinteractive
3459 else:
3457 else:
3460 dsmodifiedbackup = backup
3458 dsmodifiedbackup = backup
3461 tobackup = set()
3459 tobackup = set()
3462
3460
3463 backupanddel = actions[b'remove']
3461 backupanddel = actions[b'remove']
3464 if not opts.get(b'no_backup'):
3462 if not opts.get(b'no_backup'):
3465 backupanddel = actions[b'drop']
3463 backupanddel = actions[b'drop']
3466
3464
3467 disptable = (
3465 disptable = (
3468 # dispatch table:
3466 # dispatch table:
3469 # file state
3467 # file state
3470 # action
3468 # action
3471 # make backup
3469 # make backup
3472 ## Sets that results that will change file on disk
3470 ## Sets that results that will change file on disk
3473 # Modified compared to target, no local change
3471 # Modified compared to target, no local change
3474 (modified, actions[b'revert'], discard),
3472 (modified, actions[b'revert'], discard),
3475 # Modified compared to target, but local file is deleted
3473 # Modified compared to target, but local file is deleted
3476 (deleted, actions[b'revert'], discard),
3474 (deleted, actions[b'revert'], discard),
3477 # Modified compared to target, local change
3475 # Modified compared to target, local change
3478 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3476 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3479 # Added since target
3477 # Added since target
3480 (added, actions[b'remove'], discard),
3478 (added, actions[b'remove'], discard),
3481 # Added in working directory
3479 # Added in working directory
3482 (dsadded, actions[b'forget'], discard),
3480 (dsadded, actions[b'forget'], discard),
3483 # Added since target, have local modification
3481 # Added since target, have local modification
3484 (modadded, backupanddel, backup),
3482 (modadded, backupanddel, backup),
3485 # Added since target but file is missing in working directory
3483 # Added since target but file is missing in working directory
3486 (deladded, actions[b'drop'], discard),
3484 (deladded, actions[b'drop'], discard),
3487 # Removed since target, before working copy parent
3485 # Removed since target, before working copy parent
3488 (removed, actions[b'add'], discard),
3486 (removed, actions[b'add'], discard),
3489 # Same as `removed` but an unknown file exists at the same path
3487 # Same as `removed` but an unknown file exists at the same path
3490 (removunk, actions[b'add'], check),
3488 (removunk, actions[b'add'], check),
3491 # Removed since targe, marked as such in working copy parent
3489 # Removed since targe, marked as such in working copy parent
3492 (dsremoved, actions[b'undelete'], discard),
3490 (dsremoved, actions[b'undelete'], discard),
3493 # Same as `dsremoved` but an unknown file exists at the same path
3491 # Same as `dsremoved` but an unknown file exists at the same path
3494 (dsremovunk, actions[b'undelete'], check),
3492 (dsremovunk, actions[b'undelete'], check),
3495 ## the following sets does not result in any file changes
3493 ## the following sets does not result in any file changes
3496 # File with no modification
3494 # File with no modification
3497 (clean, actions[b'noop'], discard),
3495 (clean, actions[b'noop'], discard),
3498 # Existing file, not tracked anywhere
3496 # Existing file, not tracked anywhere
3499 (unknown, actions[b'unknown'], discard),
3497 (unknown, actions[b'unknown'], discard),
3500 )
3498 )
3501
3499
3502 for abs, exact in sorted(names.items()):
3500 for abs, exact in sorted(names.items()):
3503 # target file to be touch on disk (relative to cwd)
3501 # target file to be touch on disk (relative to cwd)
3504 target = repo.wjoin(abs)
3502 target = repo.wjoin(abs)
3505 # search the entry in the dispatch table.
3503 # search the entry in the dispatch table.
3506 # if the file is in any of these sets, it was touched in the working
3504 # if the file is in any of these sets, it was touched in the working
3507 # directory parent and we are sure it needs to be reverted.
3505 # directory parent and we are sure it needs to be reverted.
3508 for table, (xlist, msg), dobackup in disptable:
3506 for table, (xlist, msg), dobackup in disptable:
3509 if abs not in table:
3507 if abs not in table:
3510 continue
3508 continue
3511 if xlist is not None:
3509 if xlist is not None:
3512 xlist.append(abs)
3510 xlist.append(abs)
3513 if dobackup:
3511 if dobackup:
3514 # If in interactive mode, don't automatically create
3512 # If in interactive mode, don't automatically create
3515 # .orig files (issue4793)
3513 # .orig files (issue4793)
3516 if dobackup == backupinteractive:
3514 if dobackup == backupinteractive:
3517 tobackup.add(abs)
3515 tobackup.add(abs)
3518 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3516 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3519 absbakname = scmutil.backuppath(ui, repo, abs)
3517 absbakname = scmutil.backuppath(ui, repo, abs)
3520 bakname = os.path.relpath(
3518 bakname = os.path.relpath(
3521 absbakname, start=repo.root
3519 absbakname, start=repo.root
3522 )
3520 )
3523 ui.note(
3521 ui.note(
3524 _(b'saving current version of %s as %s\n')
3522 _(b'saving current version of %s as %s\n')
3525 % (uipathfn(abs), uipathfn(bakname))
3523 % (uipathfn(abs), uipathfn(bakname))
3526 )
3524 )
3527 if not opts.get(b'dry_run'):
3525 if not opts.get(b'dry_run'):
3528 if interactive:
3526 if interactive:
3529 util.copyfile(target, absbakname)
3527 util.copyfile(target, absbakname)
3530 else:
3528 else:
3531 util.rename(target, absbakname)
3529 util.rename(target, absbakname)
3532 if opts.get(b'dry_run'):
3530 if opts.get(b'dry_run'):
3533 if ui.verbose or not exact:
3531 if ui.verbose or not exact:
3534 ui.status(msg % uipathfn(abs))
3532 ui.status(msg % uipathfn(abs))
3535 elif exact:
3533 elif exact:
3536 ui.warn(msg % uipathfn(abs))
3534 ui.warn(msg % uipathfn(abs))
3537 break
3535 break
3538
3536
3539 if not opts.get(b'dry_run'):
3537 if not opts.get(b'dry_run'):
3540 needdata = (b'revert', b'add', b'undelete')
3538 needdata = (b'revert', b'add', b'undelete')
3541 oplist = [actions[name][0] for name in needdata]
3539 oplist = [actions[name][0] for name in needdata]
3542 prefetch = scmutil.prefetchfiles
3540 prefetch = scmutil.prefetchfiles
3543 matchfiles = scmutil.matchfiles
3541 matchfiles = scmutil.matchfiles
3544 prefetch(
3542 prefetch(
3545 repo,
3543 repo,
3546 [ctx.rev()],
3544 [ctx.rev()],
3547 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3545 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3548 )
3546 )
3549 match = scmutil.match(repo[None], pats)
3547 match = scmutil.match(repo[None], pats)
3550 _performrevert(
3548 _performrevert(
3551 repo,
3549 repo,
3552 parents,
3550 parents,
3553 ctx,
3551 ctx,
3554 names,
3552 names,
3555 uipathfn,
3553 uipathfn,
3556 actions,
3554 actions,
3557 match,
3555 match,
3558 interactive,
3556 interactive,
3559 tobackup,
3557 tobackup,
3560 )
3558 )
3561
3559
3562 if targetsubs:
3560 if targetsubs:
3563 # Revert the subrepos on the revert list
3561 # Revert the subrepos on the revert list
3564 for sub in targetsubs:
3562 for sub in targetsubs:
3565 try:
3563 try:
3566 wctx.sub(sub).revert(
3564 wctx.sub(sub).revert(
3567 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3565 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3568 )
3566 )
3569 except KeyError:
3567 except KeyError:
3570 raise error.Abort(
3568 raise error.Abort(
3571 b"subrepository '%s' does not exist in %s!"
3569 b"subrepository '%s' does not exist in %s!"
3572 % (sub, short(ctx.node()))
3570 % (sub, short(ctx.node()))
3573 )
3571 )
3574
3572
3575
3573
3576 def _performrevert(
3574 def _performrevert(
3577 repo,
3575 repo,
3578 parents,
3576 parents,
3579 ctx,
3577 ctx,
3580 names,
3578 names,
3581 uipathfn,
3579 uipathfn,
3582 actions,
3580 actions,
3583 match,
3581 match,
3584 interactive=False,
3582 interactive=False,
3585 tobackup=None,
3583 tobackup=None,
3586 ):
3584 ):
3587 """function that actually perform all the actions computed for revert
3585 """function that actually perform all the actions computed for revert
3588
3586
3589 This is an independent function to let extension to plug in and react to
3587 This is an independent function to let extension to plug in and react to
3590 the imminent revert.
3588 the imminent revert.
3591
3589
3592 Make sure you have the working directory locked when calling this function.
3590 Make sure you have the working directory locked when calling this function.
3593 """
3591 """
3594 parent, p2 = parents
3592 parent, p2 = parents
3595 node = ctx.node()
3593 node = ctx.node()
3596 excluded_files = []
3594 excluded_files = []
3597
3595
3598 def checkout(f):
3596 def checkout(f):
3599 fc = ctx[f]
3597 fc = ctx[f]
3600 repo.wwrite(f, fc.data(), fc.flags())
3598 repo.wwrite(f, fc.data(), fc.flags())
3601
3599
3602 def doremove(f):
3600 def doremove(f):
3603 try:
3601 try:
3604 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3602 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3605 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3603 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3606 except OSError:
3604 except OSError:
3607 pass
3605 pass
3608 repo.dirstate.remove(f)
3606 repo.dirstate.remove(f)
3609
3607
3610 def prntstatusmsg(action, f):
3608 def prntstatusmsg(action, f):
3611 exact = names[f]
3609 exact = names[f]
3612 if repo.ui.verbose or not exact:
3610 if repo.ui.verbose or not exact:
3613 repo.ui.status(actions[action][1] % uipathfn(f))
3611 repo.ui.status(actions[action][1] % uipathfn(f))
3614
3612
3615 audit_path = pathutil.pathauditor(repo.root, cached=True)
3613 audit_path = pathutil.pathauditor(repo.root, cached=True)
3616 for f in actions[b'forget'][0]:
3614 for f in actions[b'forget'][0]:
3617 if interactive:
3615 if interactive:
3618 choice = repo.ui.promptchoice(
3616 choice = repo.ui.promptchoice(
3619 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3617 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3620 )
3618 )
3621 if choice == 0:
3619 if choice == 0:
3622 prntstatusmsg(b'forget', f)
3620 prntstatusmsg(b'forget', f)
3623 repo.dirstate.drop(f)
3621 repo.dirstate.drop(f)
3624 else:
3622 else:
3625 excluded_files.append(f)
3623 excluded_files.append(f)
3626 else:
3624 else:
3627 prntstatusmsg(b'forget', f)
3625 prntstatusmsg(b'forget', f)
3628 repo.dirstate.drop(f)
3626 repo.dirstate.drop(f)
3629 for f in actions[b'remove'][0]:
3627 for f in actions[b'remove'][0]:
3630 audit_path(f)
3628 audit_path(f)
3631 if interactive:
3629 if interactive:
3632 choice = repo.ui.promptchoice(
3630 choice = repo.ui.promptchoice(
3633 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3631 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3634 )
3632 )
3635 if choice == 0:
3633 if choice == 0:
3636 prntstatusmsg(b'remove', f)
3634 prntstatusmsg(b'remove', f)
3637 doremove(f)
3635 doremove(f)
3638 else:
3636 else:
3639 excluded_files.append(f)
3637 excluded_files.append(f)
3640 else:
3638 else:
3641 prntstatusmsg(b'remove', f)
3639 prntstatusmsg(b'remove', f)
3642 doremove(f)
3640 doremove(f)
3643 for f in actions[b'drop'][0]:
3641 for f in actions[b'drop'][0]:
3644 audit_path(f)
3642 audit_path(f)
3645 prntstatusmsg(b'drop', f)
3643 prntstatusmsg(b'drop', f)
3646 repo.dirstate.remove(f)
3644 repo.dirstate.remove(f)
3647
3645
3648 normal = None
3646 normal = None
3649 if node == parent:
3647 if node == parent:
3650 # We're reverting to our parent. If possible, we'd like status
3648 # We're reverting to our parent. If possible, we'd like status
3651 # to report the file as clean. We have to use normallookup for
3649 # to report the file as clean. We have to use normallookup for
3652 # merges to avoid losing information about merged/dirty files.
3650 # merges to avoid losing information about merged/dirty files.
3653 if p2 != nullid:
3651 if p2 != nullid:
3654 normal = repo.dirstate.normallookup
3652 normal = repo.dirstate.normallookup
3655 else:
3653 else:
3656 normal = repo.dirstate.normal
3654 normal = repo.dirstate.normal
3657
3655
3658 newlyaddedandmodifiedfiles = set()
3656 newlyaddedandmodifiedfiles = set()
3659 if interactive:
3657 if interactive:
3660 # Prompt the user for changes to revert
3658 # Prompt the user for changes to revert
3661 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3659 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3662 m = scmutil.matchfiles(repo, torevert)
3660 m = scmutil.matchfiles(repo, torevert)
3663 diffopts = patch.difffeatureopts(
3661 diffopts = patch.difffeatureopts(
3664 repo.ui,
3662 repo.ui,
3665 whitespace=True,
3663 whitespace=True,
3666 section=b'commands',
3664 section=b'commands',
3667 configprefix=b'revert.interactive.',
3665 configprefix=b'revert.interactive.',
3668 )
3666 )
3669 diffopts.nodates = True
3667 diffopts.nodates = True
3670 diffopts.git = True
3668 diffopts.git = True
3671 operation = b'apply'
3669 operation = b'apply'
3672 if node == parent:
3670 if node == parent:
3673 if repo.ui.configbool(
3671 if repo.ui.configbool(
3674 b'experimental', b'revert.interactive.select-to-keep'
3672 b'experimental', b'revert.interactive.select-to-keep'
3675 ):
3673 ):
3676 operation = b'keep'
3674 operation = b'keep'
3677 else:
3675 else:
3678 operation = b'discard'
3676 operation = b'discard'
3679
3677
3680 if operation == b'apply':
3678 if operation == b'apply':
3681 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3679 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3682 else:
3680 else:
3683 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3681 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3684 originalchunks = patch.parsepatch(diff)
3682 originalchunks = patch.parsepatch(diff)
3685
3683
3686 try:
3684 try:
3687
3685
3688 chunks, opts = recordfilter(
3686 chunks, opts = recordfilter(
3689 repo.ui, originalchunks, match, operation=operation
3687 repo.ui, originalchunks, match, operation=operation
3690 )
3688 )
3691 if operation == b'discard':
3689 if operation == b'discard':
3692 chunks = patch.reversehunks(chunks)
3690 chunks = patch.reversehunks(chunks)
3693
3691
3694 except error.PatchError as err:
3692 except error.PatchError as err:
3695 raise error.Abort(_(b'error parsing patch: %s') % err)
3693 raise error.Abort(_(b'error parsing patch: %s') % err)
3696
3694
3697 # FIXME: when doing an interactive revert of a copy, there's no way of
3695 # FIXME: when doing an interactive revert of a copy, there's no way of
3698 # performing a partial revert of the added file, the only option is
3696 # performing a partial revert of the added file, the only option is
3699 # "remove added file <name> (Yn)?", so we don't need to worry about the
3697 # "remove added file <name> (Yn)?", so we don't need to worry about the
3700 # alsorestore value. Ideally we'd be able to partially revert
3698 # alsorestore value. Ideally we'd be able to partially revert
3701 # copied/renamed files.
3699 # copied/renamed files.
3702 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3700 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3703 chunks, originalchunks
3701 chunks, originalchunks
3704 )
3702 )
3705 if tobackup is None:
3703 if tobackup is None:
3706 tobackup = set()
3704 tobackup = set()
3707 # Apply changes
3705 # Apply changes
3708 fp = stringio()
3706 fp = stringio()
3709 # chunks are serialized per file, but files aren't sorted
3707 # chunks are serialized per file, but files aren't sorted
3710 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3708 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3711 prntstatusmsg(b'revert', f)
3709 prntstatusmsg(b'revert', f)
3712 files = set()
3710 files = set()
3713 for c in chunks:
3711 for c in chunks:
3714 if ishunk(c):
3712 if ishunk(c):
3715 abs = c.header.filename()
3713 abs = c.header.filename()
3716 # Create a backup file only if this hunk should be backed up
3714 # Create a backup file only if this hunk should be backed up
3717 if c.header.filename() in tobackup:
3715 if c.header.filename() in tobackup:
3718 target = repo.wjoin(abs)
3716 target = repo.wjoin(abs)
3719 bakname = scmutil.backuppath(repo.ui, repo, abs)
3717 bakname = scmutil.backuppath(repo.ui, repo, abs)
3720 util.copyfile(target, bakname)
3718 util.copyfile(target, bakname)
3721 tobackup.remove(abs)
3719 tobackup.remove(abs)
3722 if abs not in files:
3720 if abs not in files:
3723 files.add(abs)
3721 files.add(abs)
3724 if operation == b'keep':
3722 if operation == b'keep':
3725 checkout(abs)
3723 checkout(abs)
3726 c.write(fp)
3724 c.write(fp)
3727 dopatch = fp.tell()
3725 dopatch = fp.tell()
3728 fp.seek(0)
3726 fp.seek(0)
3729 if dopatch:
3727 if dopatch:
3730 try:
3728 try:
3731 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3729 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3732 except error.PatchError as err:
3730 except error.PatchError as err:
3733 raise error.Abort(pycompat.bytestr(err))
3731 raise error.Abort(pycompat.bytestr(err))
3734 del fp
3732 del fp
3735 else:
3733 else:
3736 for f in actions[b'revert'][0]:
3734 for f in actions[b'revert'][0]:
3737 prntstatusmsg(b'revert', f)
3735 prntstatusmsg(b'revert', f)
3738 checkout(f)
3736 checkout(f)
3739 if normal:
3737 if normal:
3740 normal(f)
3738 normal(f)
3741
3739
3742 for f in actions[b'add'][0]:
3740 for f in actions[b'add'][0]:
3743 # Don't checkout modified files, they are already created by the diff
3741 # Don't checkout modified files, they are already created by the diff
3744 if f not in newlyaddedandmodifiedfiles:
3742 if f not in newlyaddedandmodifiedfiles:
3745 prntstatusmsg(b'add', f)
3743 prntstatusmsg(b'add', f)
3746 checkout(f)
3744 checkout(f)
3747 repo.dirstate.add(f)
3745 repo.dirstate.add(f)
3748
3746
3749 normal = repo.dirstate.normallookup
3747 normal = repo.dirstate.normallookup
3750 if node == parent and p2 == nullid:
3748 if node == parent and p2 == nullid:
3751 normal = repo.dirstate.normal
3749 normal = repo.dirstate.normal
3752 for f in actions[b'undelete'][0]:
3750 for f in actions[b'undelete'][0]:
3753 if interactive:
3751 if interactive:
3754 choice = repo.ui.promptchoice(
3752 choice = repo.ui.promptchoice(
3755 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3753 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3756 )
3754 )
3757 if choice == 0:
3755 if choice == 0:
3758 prntstatusmsg(b'undelete', f)
3756 prntstatusmsg(b'undelete', f)
3759 checkout(f)
3757 checkout(f)
3760 normal(f)
3758 normal(f)
3761 else:
3759 else:
3762 excluded_files.append(f)
3760 excluded_files.append(f)
3763 else:
3761 else:
3764 prntstatusmsg(b'undelete', f)
3762 prntstatusmsg(b'undelete', f)
3765 checkout(f)
3763 checkout(f)
3766 normal(f)
3764 normal(f)
3767
3765
3768 copied = copies.pathcopies(repo[parent], ctx)
3766 copied = copies.pathcopies(repo[parent], ctx)
3769
3767
3770 for f in (
3768 for f in (
3771 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3769 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3772 ):
3770 ):
3773 if f in copied:
3771 if f in copied:
3774 repo.dirstate.copy(copied[f], f)
3772 repo.dirstate.copy(copied[f], f)
3775
3773
3776
3774
3777 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3775 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3778 # commands.outgoing. "missing" is "missing" of the result of
3776 # commands.outgoing. "missing" is "missing" of the result of
3779 # "findcommonoutgoing()"
3777 # "findcommonoutgoing()"
3780 outgoinghooks = util.hooks()
3778 outgoinghooks = util.hooks()
3781
3779
3782 # a list of (ui, repo) functions called by commands.summary
3780 # a list of (ui, repo) functions called by commands.summary
3783 summaryhooks = util.hooks()
3781 summaryhooks = util.hooks()
3784
3782
3785 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3783 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3786 #
3784 #
3787 # functions should return tuple of booleans below, if 'changes' is None:
3785 # functions should return tuple of booleans below, if 'changes' is None:
3788 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3786 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3789 #
3787 #
3790 # otherwise, 'changes' is a tuple of tuples below:
3788 # otherwise, 'changes' is a tuple of tuples below:
3791 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3789 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3792 # - (desturl, destbranch, destpeer, outgoing)
3790 # - (desturl, destbranch, destpeer, outgoing)
3793 summaryremotehooks = util.hooks()
3791 summaryremotehooks = util.hooks()
3794
3792
3795
3793
3796 def checkunfinished(repo, commit=False, skipmerge=False):
3794 def checkunfinished(repo, commit=False, skipmerge=False):
3797 '''Look for an unfinished multistep operation, like graft, and abort
3795 '''Look for an unfinished multistep operation, like graft, and abort
3798 if found. It's probably good to check this right before
3796 if found. It's probably good to check this right before
3799 bailifchanged().
3797 bailifchanged().
3800 '''
3798 '''
3801 # Check for non-clearable states first, so things like rebase will take
3799 # Check for non-clearable states first, so things like rebase will take
3802 # precedence over update.
3800 # precedence over update.
3803 for state in statemod._unfinishedstates:
3801 for state in statemod._unfinishedstates:
3804 if (
3802 if (
3805 state._clearable
3803 state._clearable
3806 or (commit and state._allowcommit)
3804 or (commit and state._allowcommit)
3807 or state._reportonly
3805 or state._reportonly
3808 ):
3806 ):
3809 continue
3807 continue
3810 if state.isunfinished(repo):
3808 if state.isunfinished(repo):
3811 raise error.Abort(state.msg(), hint=state.hint())
3809 raise error.Abort(state.msg(), hint=state.hint())
3812
3810
3813 for s in statemod._unfinishedstates:
3811 for s in statemod._unfinishedstates:
3814 if (
3812 if (
3815 not s._clearable
3813 not s._clearable
3816 or (commit and s._allowcommit)
3814 or (commit and s._allowcommit)
3817 or (s._opname == b'merge' and skipmerge)
3815 or (s._opname == b'merge' and skipmerge)
3818 or s._reportonly
3816 or s._reportonly
3819 ):
3817 ):
3820 continue
3818 continue
3821 if s.isunfinished(repo):
3819 if s.isunfinished(repo):
3822 raise error.Abort(s.msg(), hint=s.hint())
3820 raise error.Abort(s.msg(), hint=s.hint())
3823
3821
3824
3822
3825 def clearunfinished(repo):
3823 def clearunfinished(repo):
3826 '''Check for unfinished operations (as above), and clear the ones
3824 '''Check for unfinished operations (as above), and clear the ones
3827 that are clearable.
3825 that are clearable.
3828 '''
3826 '''
3829 for state in statemod._unfinishedstates:
3827 for state in statemod._unfinishedstates:
3830 if state._reportonly:
3828 if state._reportonly:
3831 continue
3829 continue
3832 if not state._clearable and state.isunfinished(repo):
3830 if not state._clearable and state.isunfinished(repo):
3833 raise error.Abort(state.msg(), hint=state.hint())
3831 raise error.Abort(state.msg(), hint=state.hint())
3834
3832
3835 for s in statemod._unfinishedstates:
3833 for s in statemod._unfinishedstates:
3836 if s._opname == b'merge' or state._reportonly:
3834 if s._opname == b'merge' or state._reportonly:
3837 continue
3835 continue
3838 if s._clearable and s.isunfinished(repo):
3836 if s._clearable and s.isunfinished(repo):
3839 util.unlink(repo.vfs.join(s._fname))
3837 util.unlink(repo.vfs.join(s._fname))
3840
3838
3841
3839
3842 def getunfinishedstate(repo):
3840 def getunfinishedstate(repo):
3843 ''' Checks for unfinished operations and returns statecheck object
3841 ''' Checks for unfinished operations and returns statecheck object
3844 for it'''
3842 for it'''
3845 for state in statemod._unfinishedstates:
3843 for state in statemod._unfinishedstates:
3846 if state.isunfinished(repo):
3844 if state.isunfinished(repo):
3847 return state
3845 return state
3848 return None
3846 return None
3849
3847
3850
3848
3851 def howtocontinue(repo):
3849 def howtocontinue(repo):
3852 '''Check for an unfinished operation and return the command to finish
3850 '''Check for an unfinished operation and return the command to finish
3853 it.
3851 it.
3854
3852
3855 statemod._unfinishedstates list is checked for an unfinished operation
3853 statemod._unfinishedstates list is checked for an unfinished operation
3856 and the corresponding message to finish it is generated if a method to
3854 and the corresponding message to finish it is generated if a method to
3857 continue is supported by the operation.
3855 continue is supported by the operation.
3858
3856
3859 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3857 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3860 a boolean.
3858 a boolean.
3861 '''
3859 '''
3862 contmsg = _(b"continue: %s")
3860 contmsg = _(b"continue: %s")
3863 for state in statemod._unfinishedstates:
3861 for state in statemod._unfinishedstates:
3864 if not state._continueflag:
3862 if not state._continueflag:
3865 continue
3863 continue
3866 if state.isunfinished(repo):
3864 if state.isunfinished(repo):
3867 return contmsg % state.continuemsg(), True
3865 return contmsg % state.continuemsg(), True
3868 if repo[None].dirty(missing=True, merge=False, branch=False):
3866 if repo[None].dirty(missing=True, merge=False, branch=False):
3869 return contmsg % _(b"hg commit"), False
3867 return contmsg % _(b"hg commit"), False
3870 return None, None
3868 return None, None
3871
3869
3872
3870
3873 def checkafterresolved(repo):
3871 def checkafterresolved(repo):
3874 '''Inform the user about the next action after completing hg resolve
3872 '''Inform the user about the next action after completing hg resolve
3875
3873
3876 If there's a an unfinished operation that supports continue flag,
3874 If there's a an unfinished operation that supports continue flag,
3877 howtocontinue will yield repo.ui.warn as the reporter.
3875 howtocontinue will yield repo.ui.warn as the reporter.
3878
3876
3879 Otherwise, it will yield repo.ui.note.
3877 Otherwise, it will yield repo.ui.note.
3880 '''
3878 '''
3881 msg, warning = howtocontinue(repo)
3879 msg, warning = howtocontinue(repo)
3882 if msg is not None:
3880 if msg is not None:
3883 if warning:
3881 if warning:
3884 repo.ui.warn(b"%s\n" % msg)
3882 repo.ui.warn(b"%s\n" % msg)
3885 else:
3883 else:
3886 repo.ui.note(b"%s\n" % msg)
3884 repo.ui.note(b"%s\n" % msg)
3887
3885
3888
3886
3889 def wrongtooltocontinue(repo, task):
3887 def wrongtooltocontinue(repo, task):
3890 '''Raise an abort suggesting how to properly continue if there is an
3888 '''Raise an abort suggesting how to properly continue if there is an
3891 active task.
3889 active task.
3892
3890
3893 Uses howtocontinue() to find the active task.
3891 Uses howtocontinue() to find the active task.
3894
3892
3895 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3893 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3896 a hint.
3894 a hint.
3897 '''
3895 '''
3898 after = howtocontinue(repo)
3896 after = howtocontinue(repo)
3899 hint = None
3897 hint = None
3900 if after[1]:
3898 if after[1]:
3901 hint = after[0]
3899 hint = after[0]
3902 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
3900 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
3903
3901
3904
3902
3905 def abortgraft(ui, repo, graftstate):
3903 def abortgraft(ui, repo, graftstate):
3906 """abort the interrupted graft and rollbacks to the state before interrupted
3904 """abort the interrupted graft and rollbacks to the state before interrupted
3907 graft"""
3905 graft"""
3908 if not graftstate.exists():
3906 if not graftstate.exists():
3909 raise error.Abort(_(b"no interrupted graft to abort"))
3907 raise error.Abort(_(b"no interrupted graft to abort"))
3910 statedata = readgraftstate(repo, graftstate)
3908 statedata = readgraftstate(repo, graftstate)
3911 newnodes = statedata.get(b'newnodes')
3909 newnodes = statedata.get(b'newnodes')
3912 if newnodes is None:
3910 if newnodes is None:
3913 # and old graft state which does not have all the data required to abort
3911 # and old graft state which does not have all the data required to abort
3914 # the graft
3912 # the graft
3915 raise error.Abort(_(b"cannot abort using an old graftstate"))
3913 raise error.Abort(_(b"cannot abort using an old graftstate"))
3916
3914
3917 # changeset from which graft operation was started
3915 # changeset from which graft operation was started
3918 if len(newnodes) > 0:
3916 if len(newnodes) > 0:
3919 startctx = repo[newnodes[0]].p1()
3917 startctx = repo[newnodes[0]].p1()
3920 else:
3918 else:
3921 startctx = repo[b'.']
3919 startctx = repo[b'.']
3922 # whether to strip or not
3920 # whether to strip or not
3923 cleanup = False
3921 cleanup = False
3924 from . import hg
3922 from . import hg
3925
3923
3926 if newnodes:
3924 if newnodes:
3927 newnodes = [repo[r].rev() for r in newnodes]
3925 newnodes = [repo[r].rev() for r in newnodes]
3928 cleanup = True
3926 cleanup = True
3929 # checking that none of the newnodes turned public or is public
3927 # checking that none of the newnodes turned public or is public
3930 immutable = [c for c in newnodes if not repo[c].mutable()]
3928 immutable = [c for c in newnodes if not repo[c].mutable()]
3931 if immutable:
3929 if immutable:
3932 repo.ui.warn(
3930 repo.ui.warn(
3933 _(b"cannot clean up public changesets %s\n")
3931 _(b"cannot clean up public changesets %s\n")
3934 % b', '.join(bytes(repo[r]) for r in immutable),
3932 % b', '.join(bytes(repo[r]) for r in immutable),
3935 hint=_(b"see 'hg help phases' for details"),
3933 hint=_(b"see 'hg help phases' for details"),
3936 )
3934 )
3937 cleanup = False
3935 cleanup = False
3938
3936
3939 # checking that no new nodes are created on top of grafted revs
3937 # checking that no new nodes are created on top of grafted revs
3940 desc = set(repo.changelog.descendants(newnodes))
3938 desc = set(repo.changelog.descendants(newnodes))
3941 if desc - set(newnodes):
3939 if desc - set(newnodes):
3942 repo.ui.warn(
3940 repo.ui.warn(
3943 _(
3941 _(
3944 b"new changesets detected on destination "
3942 b"new changesets detected on destination "
3945 b"branch, can't strip\n"
3943 b"branch, can't strip\n"
3946 )
3944 )
3947 )
3945 )
3948 cleanup = False
3946 cleanup = False
3949
3947
3950 if cleanup:
3948 if cleanup:
3951 with repo.wlock(), repo.lock():
3949 with repo.wlock(), repo.lock():
3952 hg.updaterepo(repo, startctx.node(), overwrite=True)
3950 hg.updaterepo(repo, startctx.node(), overwrite=True)
3953 # stripping the new nodes created
3951 # stripping the new nodes created
3954 strippoints = [
3952 strippoints = [
3955 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3953 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3956 ]
3954 ]
3957 repair.strip(repo.ui, repo, strippoints, backup=False)
3955 repair.strip(repo.ui, repo, strippoints, backup=False)
3958
3956
3959 if not cleanup:
3957 if not cleanup:
3960 # we don't update to the startnode if we can't strip
3958 # we don't update to the startnode if we can't strip
3961 startctx = repo[b'.']
3959 startctx = repo[b'.']
3962 hg.updaterepo(repo, startctx.node(), overwrite=True)
3960 hg.updaterepo(repo, startctx.node(), overwrite=True)
3963
3961
3964 ui.status(_(b"graft aborted\n"))
3962 ui.status(_(b"graft aborted\n"))
3965 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3963 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3966 graftstate.delete()
3964 graftstate.delete()
3967 return 0
3965 return 0
3968
3966
3969
3967
3970 def readgraftstate(repo, graftstate):
3968 def readgraftstate(repo, graftstate):
3971 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3969 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3972 """read the graft state file and return a dict of the data stored in it"""
3970 """read the graft state file and return a dict of the data stored in it"""
3973 try:
3971 try:
3974 return graftstate.read()
3972 return graftstate.read()
3975 except error.CorruptedState:
3973 except error.CorruptedState:
3976 nodes = repo.vfs.read(b'graftstate').splitlines()
3974 nodes = repo.vfs.read(b'graftstate').splitlines()
3977 return {b'nodes': nodes}
3975 return {b'nodes': nodes}
3978
3976
3979
3977
3980 def hgabortgraft(ui, repo):
3978 def hgabortgraft(ui, repo):
3981 """ abort logic for aborting graft using 'hg abort'"""
3979 """ abort logic for aborting graft using 'hg abort'"""
3982 with repo.wlock():
3980 with repo.wlock():
3983 graftstate = statemod.cmdstate(repo, b'graftstate')
3981 graftstate = statemod.cmdstate(repo, b'graftstate')
3984 return abortgraft(ui, repo, graftstate)
3982 return abortgraft(ui, repo, graftstate)
@@ -1,1984 +1,1984 b''
1 # repository.py - Interfaces and base classes for repositories and peers.
1 # repository.py - Interfaces and base classes for repositories and peers.
2 #
2 #
3 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.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 from ..i18n import _
10 from ..i18n import _
11 from .. import error
11 from .. import error
12 from . import util as interfaceutil
12 from . import util as interfaceutil
13
13
14 # When narrowing is finalized and no longer subject to format changes,
14 # When narrowing is finalized and no longer subject to format changes,
15 # we should move this to just "narrow" or similar.
15 # we should move this to just "narrow" or similar.
16 NARROW_REQUIREMENT = b'narrowhg-experimental'
16 NARROW_REQUIREMENT = b'narrowhg-experimental'
17
17
18 # Local repository feature string.
18 # Local repository feature string.
19
19
20 # Revlogs are being used for file storage.
20 # Revlogs are being used for file storage.
21 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
21 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
22 # The storage part of the repository is shared from an external source.
22 # The storage part of the repository is shared from an external source.
23 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
23 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
24 # LFS supported for backing file storage.
24 # LFS supported for backing file storage.
25 REPO_FEATURE_LFS = b'lfs'
25 REPO_FEATURE_LFS = b'lfs'
26 # Repository supports being stream cloned.
26 # Repository supports being stream cloned.
27 REPO_FEATURE_STREAM_CLONE = b'streamclone'
27 REPO_FEATURE_STREAM_CLONE = b'streamclone'
28 # Files storage may lack data for all ancestors.
28 # Files storage may lack data for all ancestors.
29 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
29 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
30
30
31 REVISION_FLAG_CENSORED = 1 << 15
31 REVISION_FLAG_CENSORED = 1 << 15
32 REVISION_FLAG_ELLIPSIS = 1 << 14
32 REVISION_FLAG_ELLIPSIS = 1 << 14
33 REVISION_FLAG_EXTSTORED = 1 << 13
33 REVISION_FLAG_EXTSTORED = 1 << 13
34 REVISION_FLAG_SIDEDATA = 1 << 12
34 REVISION_FLAG_SIDEDATA = 1 << 12
35
35
36 REVISION_FLAGS_KNOWN = (
36 REVISION_FLAGS_KNOWN = (
37 REVISION_FLAG_CENSORED
37 REVISION_FLAG_CENSORED
38 | REVISION_FLAG_ELLIPSIS
38 | REVISION_FLAG_ELLIPSIS
39 | REVISION_FLAG_EXTSTORED
39 | REVISION_FLAG_EXTSTORED
40 | REVISION_FLAG_SIDEDATA
40 | REVISION_FLAG_SIDEDATA
41 )
41 )
42
42
43 CG_DELTAMODE_STD = b'default'
43 CG_DELTAMODE_STD = b'default'
44 CG_DELTAMODE_PREV = b'previous'
44 CG_DELTAMODE_PREV = b'previous'
45 CG_DELTAMODE_FULL = b'fulltext'
45 CG_DELTAMODE_FULL = b'fulltext'
46 CG_DELTAMODE_P1 = b'p1'
46 CG_DELTAMODE_P1 = b'p1'
47
47
48
48
49 class ipeerconnection(interfaceutil.Interface):
49 class ipeerconnection(interfaceutil.Interface):
50 """Represents a "connection" to a repository.
50 """Represents a "connection" to a repository.
51
51
52 This is the base interface for representing a connection to a repository.
52 This is the base interface for representing a connection to a repository.
53 It holds basic properties and methods applicable to all peer types.
53 It holds basic properties and methods applicable to all peer types.
54
54
55 This is not a complete interface definition and should not be used
55 This is not a complete interface definition and should not be used
56 outside of this module.
56 outside of this module.
57 """
57 """
58
58
59 ui = interfaceutil.Attribute("""ui.ui instance""")
59 ui = interfaceutil.Attribute("""ui.ui instance""")
60
60
61 def url():
61 def url():
62 """Returns a URL string representing this peer.
62 """Returns a URL string representing this peer.
63
63
64 Currently, implementations expose the raw URL used to construct the
64 Currently, implementations expose the raw URL used to construct the
65 instance. It may contain credentials as part of the URL. The
65 instance. It may contain credentials as part of the URL. The
66 expectations of the value aren't well-defined and this could lead to
66 expectations of the value aren't well-defined and this could lead to
67 data leakage.
67 data leakage.
68
68
69 TODO audit/clean consumers and more clearly define the contents of this
69 TODO audit/clean consumers and more clearly define the contents of this
70 value.
70 value.
71 """
71 """
72
72
73 def local():
73 def local():
74 """Returns a local repository instance.
74 """Returns a local repository instance.
75
75
76 If the peer represents a local repository, returns an object that
76 If the peer represents a local repository, returns an object that
77 can be used to interface with it. Otherwise returns ``None``.
77 can be used to interface with it. Otherwise returns ``None``.
78 """
78 """
79
79
80 def peer():
80 def peer():
81 """Returns an object conforming to this interface.
81 """Returns an object conforming to this interface.
82
82
83 Most implementations will ``return self``.
83 Most implementations will ``return self``.
84 """
84 """
85
85
86 def canpush():
86 def canpush():
87 """Returns a boolean indicating if this peer can be pushed to."""
87 """Returns a boolean indicating if this peer can be pushed to."""
88
88
89 def close():
89 def close():
90 """Close the connection to this peer.
90 """Close the connection to this peer.
91
91
92 This is called when the peer will no longer be used. Resources
92 This is called when the peer will no longer be used. Resources
93 associated with the peer should be cleaned up.
93 associated with the peer should be cleaned up.
94 """
94 """
95
95
96
96
97 class ipeercapabilities(interfaceutil.Interface):
97 class ipeercapabilities(interfaceutil.Interface):
98 """Peer sub-interface related to capabilities."""
98 """Peer sub-interface related to capabilities."""
99
99
100 def capable(name):
100 def capable(name):
101 """Determine support for a named capability.
101 """Determine support for a named capability.
102
102
103 Returns ``False`` if capability not supported.
103 Returns ``False`` if capability not supported.
104
104
105 Returns ``True`` if boolean capability is supported. Returns a string
105 Returns ``True`` if boolean capability is supported. Returns a string
106 if capability support is non-boolean.
106 if capability support is non-boolean.
107
107
108 Capability strings may or may not map to wire protocol capabilities.
108 Capability strings may or may not map to wire protocol capabilities.
109 """
109 """
110
110
111 def requirecap(name, purpose):
111 def requirecap(name, purpose):
112 """Require a capability to be present.
112 """Require a capability to be present.
113
113
114 Raises a ``CapabilityError`` if the capability isn't present.
114 Raises a ``CapabilityError`` if the capability isn't present.
115 """
115 """
116
116
117
117
118 class ipeercommands(interfaceutil.Interface):
118 class ipeercommands(interfaceutil.Interface):
119 """Client-side interface for communicating over the wire protocol.
119 """Client-side interface for communicating over the wire protocol.
120
120
121 This interface is used as a gateway to the Mercurial wire protocol.
121 This interface is used as a gateway to the Mercurial wire protocol.
122 methods commonly call wire protocol commands of the same name.
122 methods commonly call wire protocol commands of the same name.
123 """
123 """
124
124
125 def branchmap():
125 def branchmap():
126 """Obtain heads in named branches.
126 """Obtain heads in named branches.
127
127
128 Returns a dict mapping branch name to an iterable of nodes that are
128 Returns a dict mapping branch name to an iterable of nodes that are
129 heads on that branch.
129 heads on that branch.
130 """
130 """
131
131
132 def capabilities():
132 def capabilities():
133 """Obtain capabilities of the peer.
133 """Obtain capabilities of the peer.
134
134
135 Returns a set of string capabilities.
135 Returns a set of string capabilities.
136 """
136 """
137
137
138 def clonebundles():
138 def clonebundles():
139 """Obtains the clone bundles manifest for the repo.
139 """Obtains the clone bundles manifest for the repo.
140
140
141 Returns the manifest as unparsed bytes.
141 Returns the manifest as unparsed bytes.
142 """
142 """
143
143
144 def debugwireargs(one, two, three=None, four=None, five=None):
144 def debugwireargs(one, two, three=None, four=None, five=None):
145 """Used to facilitate debugging of arguments passed over the wire."""
145 """Used to facilitate debugging of arguments passed over the wire."""
146
146
147 def getbundle(source, **kwargs):
147 def getbundle(source, **kwargs):
148 """Obtain remote repository data as a bundle.
148 """Obtain remote repository data as a bundle.
149
149
150 This command is how the bulk of repository data is transferred from
150 This command is how the bulk of repository data is transferred from
151 the peer to the local repository
151 the peer to the local repository
152
152
153 Returns a generator of bundle data.
153 Returns a generator of bundle data.
154 """
154 """
155
155
156 def heads():
156 def heads():
157 """Determine all known head revisions in the peer.
157 """Determine all known head revisions in the peer.
158
158
159 Returns an iterable of binary nodes.
159 Returns an iterable of binary nodes.
160 """
160 """
161
161
162 def known(nodes):
162 def known(nodes):
163 """Determine whether multiple nodes are known.
163 """Determine whether multiple nodes are known.
164
164
165 Accepts an iterable of nodes whose presence to check for.
165 Accepts an iterable of nodes whose presence to check for.
166
166
167 Returns an iterable of booleans indicating of the corresponding node
167 Returns an iterable of booleans indicating of the corresponding node
168 at that index is known to the peer.
168 at that index is known to the peer.
169 """
169 """
170
170
171 def listkeys(namespace):
171 def listkeys(namespace):
172 """Obtain all keys in a pushkey namespace.
172 """Obtain all keys in a pushkey namespace.
173
173
174 Returns an iterable of key names.
174 Returns an iterable of key names.
175 """
175 """
176
176
177 def lookup(key):
177 def lookup(key):
178 """Resolve a value to a known revision.
178 """Resolve a value to a known revision.
179
179
180 Returns a binary node of the resolved revision on success.
180 Returns a binary node of the resolved revision on success.
181 """
181 """
182
182
183 def pushkey(namespace, key, old, new):
183 def pushkey(namespace, key, old, new):
184 """Set a value using the ``pushkey`` protocol.
184 """Set a value using the ``pushkey`` protocol.
185
185
186 Arguments correspond to the pushkey namespace and key to operate on and
186 Arguments correspond to the pushkey namespace and key to operate on and
187 the old and new values for that key.
187 the old and new values for that key.
188
188
189 Returns a string with the peer result. The value inside varies by the
189 Returns a string with the peer result. The value inside varies by the
190 namespace.
190 namespace.
191 """
191 """
192
192
193 def stream_out():
193 def stream_out():
194 """Obtain streaming clone data.
194 """Obtain streaming clone data.
195
195
196 Successful result should be a generator of data chunks.
196 Successful result should be a generator of data chunks.
197 """
197 """
198
198
199 def unbundle(bundle, heads, url):
199 def unbundle(bundle, heads, url):
200 """Transfer repository data to the peer.
200 """Transfer repository data to the peer.
201
201
202 This is how the bulk of data during a push is transferred.
202 This is how the bulk of data during a push is transferred.
203
203
204 Returns the integer number of heads added to the peer.
204 Returns the integer number of heads added to the peer.
205 """
205 """
206
206
207
207
208 class ipeerlegacycommands(interfaceutil.Interface):
208 class ipeerlegacycommands(interfaceutil.Interface):
209 """Interface for implementing support for legacy wire protocol commands.
209 """Interface for implementing support for legacy wire protocol commands.
210
210
211 Wire protocol commands transition to legacy status when they are no longer
211 Wire protocol commands transition to legacy status when they are no longer
212 used by modern clients. To facilitate identifying which commands are
212 used by modern clients. To facilitate identifying which commands are
213 legacy, the interfaces are split.
213 legacy, the interfaces are split.
214 """
214 """
215
215
216 def between(pairs):
216 def between(pairs):
217 """Obtain nodes between pairs of nodes.
217 """Obtain nodes between pairs of nodes.
218
218
219 ``pairs`` is an iterable of node pairs.
219 ``pairs`` is an iterable of node pairs.
220
220
221 Returns an iterable of iterables of nodes corresponding to each
221 Returns an iterable of iterables of nodes corresponding to each
222 requested pair.
222 requested pair.
223 """
223 """
224
224
225 def branches(nodes):
225 def branches(nodes):
226 """Obtain ancestor changesets of specific nodes back to a branch point.
226 """Obtain ancestor changesets of specific nodes back to a branch point.
227
227
228 For each requested node, the peer finds the first ancestor node that is
228 For each requested node, the peer finds the first ancestor node that is
229 a DAG root or is a merge.
229 a DAG root or is a merge.
230
230
231 Returns an iterable of iterables with the resolved values for each node.
231 Returns an iterable of iterables with the resolved values for each node.
232 """
232 """
233
233
234 def changegroup(nodes, source):
234 def changegroup(nodes, source):
235 """Obtain a changegroup with data for descendants of specified nodes."""
235 """Obtain a changegroup with data for descendants of specified nodes."""
236
236
237 def changegroupsubset(bases, heads, source):
237 def changegroupsubset(bases, heads, source):
238 pass
238 pass
239
239
240
240
241 class ipeercommandexecutor(interfaceutil.Interface):
241 class ipeercommandexecutor(interfaceutil.Interface):
242 """Represents a mechanism to execute remote commands.
242 """Represents a mechanism to execute remote commands.
243
243
244 This is the primary interface for requesting that wire protocol commands
244 This is the primary interface for requesting that wire protocol commands
245 be executed. Instances of this interface are active in a context manager
245 be executed. Instances of this interface are active in a context manager
246 and have a well-defined lifetime. When the context manager exits, all
246 and have a well-defined lifetime. When the context manager exits, all
247 outstanding requests are waited on.
247 outstanding requests are waited on.
248 """
248 """
249
249
250 def callcommand(name, args):
250 def callcommand(name, args):
251 """Request that a named command be executed.
251 """Request that a named command be executed.
252
252
253 Receives the command name and a dictionary of command arguments.
253 Receives the command name and a dictionary of command arguments.
254
254
255 Returns a ``concurrent.futures.Future`` that will resolve to the
255 Returns a ``concurrent.futures.Future`` that will resolve to the
256 result of that command request. That exact value is left up to
256 result of that command request. That exact value is left up to
257 the implementation and possibly varies by command.
257 the implementation and possibly varies by command.
258
258
259 Not all commands can coexist with other commands in an executor
259 Not all commands can coexist with other commands in an executor
260 instance: it depends on the underlying wire protocol transport being
260 instance: it depends on the underlying wire protocol transport being
261 used and the command itself.
261 used and the command itself.
262
262
263 Implementations MAY call ``sendcommands()`` automatically if the
263 Implementations MAY call ``sendcommands()`` automatically if the
264 requested command can not coexist with other commands in this executor.
264 requested command can not coexist with other commands in this executor.
265
265
266 Implementations MAY call ``sendcommands()`` automatically when the
266 Implementations MAY call ``sendcommands()`` automatically when the
267 future's ``result()`` is called. So, consumers using multiple
267 future's ``result()`` is called. So, consumers using multiple
268 commands with an executor MUST ensure that ``result()`` is not called
268 commands with an executor MUST ensure that ``result()`` is not called
269 until all command requests have been issued.
269 until all command requests have been issued.
270 """
270 """
271
271
272 def sendcommands():
272 def sendcommands():
273 """Trigger submission of queued command requests.
273 """Trigger submission of queued command requests.
274
274
275 Not all transports submit commands as soon as they are requested to
275 Not all transports submit commands as soon as they are requested to
276 run. When called, this method forces queued command requests to be
276 run. When called, this method forces queued command requests to be
277 issued. It will no-op if all commands have already been sent.
277 issued. It will no-op if all commands have already been sent.
278
278
279 When called, no more new commands may be issued with this executor.
279 When called, no more new commands may be issued with this executor.
280 """
280 """
281
281
282 def close():
282 def close():
283 """Signal that this command request is finished.
283 """Signal that this command request is finished.
284
284
285 When called, no more new commands may be issued. All outstanding
285 When called, no more new commands may be issued. All outstanding
286 commands that have previously been issued are waited on before
286 commands that have previously been issued are waited on before
287 returning. This not only includes waiting for the futures to resolve,
287 returning. This not only includes waiting for the futures to resolve,
288 but also waiting for all response data to arrive. In other words,
288 but also waiting for all response data to arrive. In other words,
289 calling this waits for all on-wire state for issued command requests
289 calling this waits for all on-wire state for issued command requests
290 to finish.
290 to finish.
291
291
292 When used as a context manager, this method is called when exiting the
292 When used as a context manager, this method is called when exiting the
293 context manager.
293 context manager.
294
294
295 This method may call ``sendcommands()`` if there are buffered commands.
295 This method may call ``sendcommands()`` if there are buffered commands.
296 """
296 """
297
297
298
298
299 class ipeerrequests(interfaceutil.Interface):
299 class ipeerrequests(interfaceutil.Interface):
300 """Interface for executing commands on a peer."""
300 """Interface for executing commands on a peer."""
301
301
302 limitedarguments = interfaceutil.Attribute(
302 limitedarguments = interfaceutil.Attribute(
303 """True if the peer cannot receive large argument value for commands."""
303 """True if the peer cannot receive large argument value for commands."""
304 )
304 )
305
305
306 def commandexecutor():
306 def commandexecutor():
307 """A context manager that resolves to an ipeercommandexecutor.
307 """A context manager that resolves to an ipeercommandexecutor.
308
308
309 The object this resolves to can be used to issue command requests
309 The object this resolves to can be used to issue command requests
310 to the peer.
310 to the peer.
311
311
312 Callers should call its ``callcommand`` method to issue command
312 Callers should call its ``callcommand`` method to issue command
313 requests.
313 requests.
314
314
315 A new executor should be obtained for each distinct set of commands
315 A new executor should be obtained for each distinct set of commands
316 (possibly just a single command) that the consumer wants to execute
316 (possibly just a single command) that the consumer wants to execute
317 as part of a single operation or round trip. This is because some
317 as part of a single operation or round trip. This is because some
318 peers are half-duplex and/or don't support persistent connections.
318 peers are half-duplex and/or don't support persistent connections.
319 e.g. in the case of HTTP peers, commands sent to an executor represent
319 e.g. in the case of HTTP peers, commands sent to an executor represent
320 a single HTTP request. While some peers may support multiple command
320 a single HTTP request. While some peers may support multiple command
321 sends over the wire per executor, consumers need to code to the least
321 sends over the wire per executor, consumers need to code to the least
322 capable peer. So it should be assumed that command executors buffer
322 capable peer. So it should be assumed that command executors buffer
323 called commands until they are told to send them and that each
323 called commands until they are told to send them and that each
324 command executor could result in a new connection or wire-level request
324 command executor could result in a new connection or wire-level request
325 being issued.
325 being issued.
326 """
326 """
327
327
328
328
329 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
329 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
330 """Unified interface for peer repositories.
330 """Unified interface for peer repositories.
331
331
332 All peer instances must conform to this interface.
332 All peer instances must conform to this interface.
333 """
333 """
334
334
335
335
336 class ipeerv2(ipeerconnection, ipeercapabilities, ipeerrequests):
336 class ipeerv2(ipeerconnection, ipeercapabilities, ipeerrequests):
337 """Unified peer interface for wire protocol version 2 peers."""
337 """Unified peer interface for wire protocol version 2 peers."""
338
338
339 apidescriptor = interfaceutil.Attribute(
339 apidescriptor = interfaceutil.Attribute(
340 """Data structure holding description of server API."""
340 """Data structure holding description of server API."""
341 )
341 )
342
342
343
343
344 @interfaceutil.implementer(ipeerbase)
344 @interfaceutil.implementer(ipeerbase)
345 class peer(object):
345 class peer(object):
346 """Base class for peer repositories."""
346 """Base class for peer repositories."""
347
347
348 limitedarguments = False
348 limitedarguments = False
349
349
350 def capable(self, name):
350 def capable(self, name):
351 caps = self.capabilities()
351 caps = self.capabilities()
352 if name in caps:
352 if name in caps:
353 return True
353 return True
354
354
355 name = b'%s=' % name
355 name = b'%s=' % name
356 for cap in caps:
356 for cap in caps:
357 if cap.startswith(name):
357 if cap.startswith(name):
358 return cap[len(name) :]
358 return cap[len(name) :]
359
359
360 return False
360 return False
361
361
362 def requirecap(self, name, purpose):
362 def requirecap(self, name, purpose):
363 if self.capable(name):
363 if self.capable(name):
364 return
364 return
365
365
366 raise error.CapabilityError(
366 raise error.CapabilityError(
367 _(
367 _(
368 b'cannot %s; remote repository does not support the '
368 b'cannot %s; remote repository does not support the '
369 b'\'%s\' capability'
369 b'\'%s\' capability'
370 )
370 )
371 % (purpose, name)
371 % (purpose, name)
372 )
372 )
373
373
374
374
375 class iverifyproblem(interfaceutil.Interface):
375 class iverifyproblem(interfaceutil.Interface):
376 """Represents a problem with the integrity of the repository.
376 """Represents a problem with the integrity of the repository.
377
377
378 Instances of this interface are emitted to describe an integrity issue
378 Instances of this interface are emitted to describe an integrity issue
379 with a repository (e.g. corrupt storage, missing data, etc).
379 with a repository (e.g. corrupt storage, missing data, etc).
380
380
381 Instances are essentially messages associated with severity.
381 Instances are essentially messages associated with severity.
382 """
382 """
383
383
384 warning = interfaceutil.Attribute(
384 warning = interfaceutil.Attribute(
385 """Message indicating a non-fatal problem."""
385 """Message indicating a non-fatal problem."""
386 )
386 )
387
387
388 error = interfaceutil.Attribute("""Message indicating a fatal problem.""")
388 error = interfaceutil.Attribute("""Message indicating a fatal problem.""")
389
389
390 node = interfaceutil.Attribute(
390 node = interfaceutil.Attribute(
391 """Revision encountering the problem.
391 """Revision encountering the problem.
392
392
393 ``None`` means the problem doesn't apply to a single revision.
393 ``None`` means the problem doesn't apply to a single revision.
394 """
394 """
395 )
395 )
396
396
397
397
398 class irevisiondelta(interfaceutil.Interface):
398 class irevisiondelta(interfaceutil.Interface):
399 """Represents a delta between one revision and another.
399 """Represents a delta between one revision and another.
400
400
401 Instances convey enough information to allow a revision to be exchanged
401 Instances convey enough information to allow a revision to be exchanged
402 with another repository.
402 with another repository.
403
403
404 Instances represent the fulltext revision data or a delta against
404 Instances represent the fulltext revision data or a delta against
405 another revision. Therefore the ``revision`` and ``delta`` attributes
405 another revision. Therefore the ``revision`` and ``delta`` attributes
406 are mutually exclusive.
406 are mutually exclusive.
407
407
408 Typically used for changegroup generation.
408 Typically used for changegroup generation.
409 """
409 """
410
410
411 node = interfaceutil.Attribute("""20 byte node of this revision.""")
411 node = interfaceutil.Attribute("""20 byte node of this revision.""")
412
412
413 p1node = interfaceutil.Attribute(
413 p1node = interfaceutil.Attribute(
414 """20 byte node of 1st parent of this revision."""
414 """20 byte node of 1st parent of this revision."""
415 )
415 )
416
416
417 p2node = interfaceutil.Attribute(
417 p2node = interfaceutil.Attribute(
418 """20 byte node of 2nd parent of this revision."""
418 """20 byte node of 2nd parent of this revision."""
419 )
419 )
420
420
421 linknode = interfaceutil.Attribute(
421 linknode = interfaceutil.Attribute(
422 """20 byte node of the changelog revision this node is linked to."""
422 """20 byte node of the changelog revision this node is linked to."""
423 )
423 )
424
424
425 flags = interfaceutil.Attribute(
425 flags = interfaceutil.Attribute(
426 """2 bytes of integer flags that apply to this revision.
426 """2 bytes of integer flags that apply to this revision.
427
427
428 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
428 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
429 """
429 """
430 )
430 )
431
431
432 basenode = interfaceutil.Attribute(
432 basenode = interfaceutil.Attribute(
433 """20 byte node of the revision this data is a delta against.
433 """20 byte node of the revision this data is a delta against.
434
434
435 ``nullid`` indicates that the revision is a full revision and not
435 ``nullid`` indicates that the revision is a full revision and not
436 a delta.
436 a delta.
437 """
437 """
438 )
438 )
439
439
440 baserevisionsize = interfaceutil.Attribute(
440 baserevisionsize = interfaceutil.Attribute(
441 """Size of base revision this delta is against.
441 """Size of base revision this delta is against.
442
442
443 May be ``None`` if ``basenode`` is ``nullid``.
443 May be ``None`` if ``basenode`` is ``nullid``.
444 """
444 """
445 )
445 )
446
446
447 revision = interfaceutil.Attribute(
447 revision = interfaceutil.Attribute(
448 """Raw fulltext of revision data for this node."""
448 """Raw fulltext of revision data for this node."""
449 )
449 )
450
450
451 delta = interfaceutil.Attribute(
451 delta = interfaceutil.Attribute(
452 """Delta between ``basenode`` and ``node``.
452 """Delta between ``basenode`` and ``node``.
453
453
454 Stored in the bdiff delta format.
454 Stored in the bdiff delta format.
455 """
455 """
456 )
456 )
457
457
458
458
459 class ifilerevisionssequence(interfaceutil.Interface):
459 class ifilerevisionssequence(interfaceutil.Interface):
460 """Contains index data for all revisions of a file.
460 """Contains index data for all revisions of a file.
461
461
462 Types implementing this behave like lists of tuples. The index
462 Types implementing this behave like lists of tuples. The index
463 in the list corresponds to the revision number. The values contain
463 in the list corresponds to the revision number. The values contain
464 index metadata.
464 index metadata.
465
465
466 The *null* revision (revision number -1) is always the last item
466 The *null* revision (revision number -1) is always the last item
467 in the index.
467 in the index.
468 """
468 """
469
469
470 def __len__():
470 def __len__():
471 """The total number of revisions."""
471 """The total number of revisions."""
472
472
473 def __getitem__(rev):
473 def __getitem__(rev):
474 """Returns the object having a specific revision number.
474 """Returns the object having a specific revision number.
475
475
476 Returns an 8-tuple with the following fields:
476 Returns an 8-tuple with the following fields:
477
477
478 offset+flags
478 offset+flags
479 Contains the offset and flags for the revision. 64-bit unsigned
479 Contains the offset and flags for the revision. 64-bit unsigned
480 integer where first 6 bytes are the offset and the next 2 bytes
480 integer where first 6 bytes are the offset and the next 2 bytes
481 are flags. The offset can be 0 if it is not used by the store.
481 are flags. The offset can be 0 if it is not used by the store.
482 compressed size
482 compressed size
483 Size of the revision data in the store. It can be 0 if it isn't
483 Size of the revision data in the store. It can be 0 if it isn't
484 needed by the store.
484 needed by the store.
485 uncompressed size
485 uncompressed size
486 Fulltext size. It can be 0 if it isn't needed by the store.
486 Fulltext size. It can be 0 if it isn't needed by the store.
487 base revision
487 base revision
488 Revision number of revision the delta for storage is encoded
488 Revision number of revision the delta for storage is encoded
489 against. -1 indicates not encoded against a base revision.
489 against. -1 indicates not encoded against a base revision.
490 link revision
490 link revision
491 Revision number of changelog revision this entry is related to.
491 Revision number of changelog revision this entry is related to.
492 p1 revision
492 p1 revision
493 Revision number of 1st parent. -1 if no 1st parent.
493 Revision number of 1st parent. -1 if no 1st parent.
494 p2 revision
494 p2 revision
495 Revision number of 2nd parent. -1 if no 1st parent.
495 Revision number of 2nd parent. -1 if no 1st parent.
496 node
496 node
497 Binary node value for this revision number.
497 Binary node value for this revision number.
498
498
499 Negative values should index off the end of the sequence. ``-1``
499 Negative values should index off the end of the sequence. ``-1``
500 should return the null revision. ``-2`` should return the most
500 should return the null revision. ``-2`` should return the most
501 recent revision.
501 recent revision.
502 """
502 """
503
503
504 def __contains__(rev):
504 def __contains__(rev):
505 """Whether a revision number exists."""
505 """Whether a revision number exists."""
506
506
507 def insert(self, i, entry):
507 def insert(self, i, entry):
508 """Add an item to the index at specific revision."""
508 """Add an item to the index at specific revision."""
509
509
510
510
511 class ifileindex(interfaceutil.Interface):
511 class ifileindex(interfaceutil.Interface):
512 """Storage interface for index data of a single file.
512 """Storage interface for index data of a single file.
513
513
514 File storage data is divided into index metadata and data storage.
514 File storage data is divided into index metadata and data storage.
515 This interface defines the index portion of the interface.
515 This interface defines the index portion of the interface.
516
516
517 The index logically consists of:
517 The index logically consists of:
518
518
519 * A mapping between revision numbers and nodes.
519 * A mapping between revision numbers and nodes.
520 * DAG data (storing and querying the relationship between nodes).
520 * DAG data (storing and querying the relationship between nodes).
521 * Metadata to facilitate storage.
521 * Metadata to facilitate storage.
522 """
522 """
523
523
524 def __len__():
524 def __len__():
525 """Obtain the number of revisions stored for this file."""
525 """Obtain the number of revisions stored for this file."""
526
526
527 def __iter__():
527 def __iter__():
528 """Iterate over revision numbers for this file."""
528 """Iterate over revision numbers for this file."""
529
529
530 def hasnode(node):
530 def hasnode(node):
531 """Returns a bool indicating if a node is known to this store.
531 """Returns a bool indicating if a node is known to this store.
532
532
533 Implementations must only return True for full, binary node values:
533 Implementations must only return True for full, binary node values:
534 hex nodes, revision numbers, and partial node matches must be
534 hex nodes, revision numbers, and partial node matches must be
535 rejected.
535 rejected.
536
536
537 The null node is never present.
537 The null node is never present.
538 """
538 """
539
539
540 def revs(start=0, stop=None):
540 def revs(start=0, stop=None):
541 """Iterate over revision numbers for this file, with control."""
541 """Iterate over revision numbers for this file, with control."""
542
542
543 def parents(node):
543 def parents(node):
544 """Returns a 2-tuple of parent nodes for a revision.
544 """Returns a 2-tuple of parent nodes for a revision.
545
545
546 Values will be ``nullid`` if the parent is empty.
546 Values will be ``nullid`` if the parent is empty.
547 """
547 """
548
548
549 def parentrevs(rev):
549 def parentrevs(rev):
550 """Like parents() but operates on revision numbers."""
550 """Like parents() but operates on revision numbers."""
551
551
552 def rev(node):
552 def rev(node):
553 """Obtain the revision number given a node.
553 """Obtain the revision number given a node.
554
554
555 Raises ``error.LookupError`` if the node is not known.
555 Raises ``error.LookupError`` if the node is not known.
556 """
556 """
557
557
558 def node(rev):
558 def node(rev):
559 """Obtain the node value given a revision number.
559 """Obtain the node value given a revision number.
560
560
561 Raises ``IndexError`` if the node is not known.
561 Raises ``IndexError`` if the node is not known.
562 """
562 """
563
563
564 def lookup(node):
564 def lookup(node):
565 """Attempt to resolve a value to a node.
565 """Attempt to resolve a value to a node.
566
566
567 Value can be a binary node, hex node, revision number, or a string
567 Value can be a binary node, hex node, revision number, or a string
568 that can be converted to an integer.
568 that can be converted to an integer.
569
569
570 Raises ``error.LookupError`` if a node could not be resolved.
570 Raises ``error.LookupError`` if a node could not be resolved.
571 """
571 """
572
572
573 def linkrev(rev):
573 def linkrev(rev):
574 """Obtain the changeset revision number a revision is linked to."""
574 """Obtain the changeset revision number a revision is linked to."""
575
575
576 def iscensored(rev):
576 def iscensored(rev):
577 """Return whether a revision's content has been censored."""
577 """Return whether a revision's content has been censored."""
578
578
579 def commonancestorsheads(node1, node2):
579 def commonancestorsheads(node1, node2):
580 """Obtain an iterable of nodes containing heads of common ancestors.
580 """Obtain an iterable of nodes containing heads of common ancestors.
581
581
582 See ``ancestor.commonancestorsheads()``.
582 See ``ancestor.commonancestorsheads()``.
583 """
583 """
584
584
585 def descendants(revs):
585 def descendants(revs):
586 """Obtain descendant revision numbers for a set of revision numbers.
586 """Obtain descendant revision numbers for a set of revision numbers.
587
587
588 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
588 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
589 """
589 """
590
590
591 def heads(start=None, stop=None):
591 def heads(start=None, stop=None):
592 """Obtain a list of nodes that are DAG heads, with control.
592 """Obtain a list of nodes that are DAG heads, with control.
593
593
594 The set of revisions examined can be limited by specifying
594 The set of revisions examined can be limited by specifying
595 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
595 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
596 iterable of nodes. DAG traversal starts at earlier revision
596 iterable of nodes. DAG traversal starts at earlier revision
597 ``start`` and iterates forward until any node in ``stop`` is
597 ``start`` and iterates forward until any node in ``stop`` is
598 encountered.
598 encountered.
599 """
599 """
600
600
601 def children(node):
601 def children(node):
602 """Obtain nodes that are children of a node.
602 """Obtain nodes that are children of a node.
603
603
604 Returns a list of nodes.
604 Returns a list of nodes.
605 """
605 """
606
606
607
607
608 class ifiledata(interfaceutil.Interface):
608 class ifiledata(interfaceutil.Interface):
609 """Storage interface for data storage of a specific file.
609 """Storage interface for data storage of a specific file.
610
610
611 This complements ``ifileindex`` and provides an interface for accessing
611 This complements ``ifileindex`` and provides an interface for accessing
612 data for a tracked file.
612 data for a tracked file.
613 """
613 """
614
614
615 def size(rev):
615 def size(rev):
616 """Obtain the fulltext size of file data.
616 """Obtain the fulltext size of file data.
617
617
618 Any metadata is excluded from size measurements.
618 Any metadata is excluded from size measurements.
619 """
619 """
620
620
621 def revision(node, raw=False):
621 def revision(node, raw=False):
622 """"Obtain fulltext data for a node.
622 """"Obtain fulltext data for a node.
623
623
624 By default, any storage transformations are applied before the data
624 By default, any storage transformations are applied before the data
625 is returned. If ``raw`` is True, non-raw storage transformations
625 is returned. If ``raw`` is True, non-raw storage transformations
626 are not applied.
626 are not applied.
627
627
628 The fulltext data may contain a header containing metadata. Most
628 The fulltext data may contain a header containing metadata. Most
629 consumers should use ``read()`` to obtain the actual file data.
629 consumers should use ``read()`` to obtain the actual file data.
630 """
630 """
631
631
632 def rawdata(node):
632 def rawdata(node):
633 """Obtain raw data for a node.
633 """Obtain raw data for a node.
634 """
634 """
635
635
636 def read(node):
636 def read(node):
637 """Resolve file fulltext data.
637 """Resolve file fulltext data.
638
638
639 This is similar to ``revision()`` except any metadata in the data
639 This is similar to ``revision()`` except any metadata in the data
640 headers is stripped.
640 headers is stripped.
641 """
641 """
642
642
643 def renamed(node):
643 def renamed(node):
644 """Obtain copy metadata for a node.
644 """Obtain copy metadata for a node.
645
645
646 Returns ``False`` if no copy metadata is stored or a 2-tuple of
646 Returns ``False`` if no copy metadata is stored or a 2-tuple of
647 (path, node) from which this revision was copied.
647 (path, node) from which this revision was copied.
648 """
648 """
649
649
650 def cmp(node, fulltext):
650 def cmp(node, fulltext):
651 """Compare fulltext to another revision.
651 """Compare fulltext to another revision.
652
652
653 Returns True if the fulltext is different from what is stored.
653 Returns True if the fulltext is different from what is stored.
654
654
655 This takes copy metadata into account.
655 This takes copy metadata into account.
656
656
657 TODO better document the copy metadata and censoring logic.
657 TODO better document the copy metadata and censoring logic.
658 """
658 """
659
659
660 def emitrevisions(
660 def emitrevisions(
661 nodes,
661 nodes,
662 nodesorder=None,
662 nodesorder=None,
663 revisiondata=False,
663 revisiondata=False,
664 assumehaveparentrevisions=False,
664 assumehaveparentrevisions=False,
665 deltamode=CG_DELTAMODE_STD,
665 deltamode=CG_DELTAMODE_STD,
666 ):
666 ):
667 """Produce ``irevisiondelta`` for revisions.
667 """Produce ``irevisiondelta`` for revisions.
668
668
669 Given an iterable of nodes, emits objects conforming to the
669 Given an iterable of nodes, emits objects conforming to the
670 ``irevisiondelta`` interface that describe revisions in storage.
670 ``irevisiondelta`` interface that describe revisions in storage.
671
671
672 This method is a generator.
672 This method is a generator.
673
673
674 The input nodes may be unordered. Implementations must ensure that a
674 The input nodes may be unordered. Implementations must ensure that a
675 node's parents are emitted before the node itself. Transitively, this
675 node's parents are emitted before the node itself. Transitively, this
676 means that a node may only be emitted once all its ancestors in
676 means that a node may only be emitted once all its ancestors in
677 ``nodes`` have also been emitted.
677 ``nodes`` have also been emitted.
678
678
679 By default, emits "index" data (the ``node``, ``p1node``, and
679 By default, emits "index" data (the ``node``, ``p1node``, and
680 ``p2node`` attributes). If ``revisiondata`` is set, revision data
680 ``p2node`` attributes). If ``revisiondata`` is set, revision data
681 will also be present on the emitted objects.
681 will also be present on the emitted objects.
682
682
683 With default argument values, implementations can choose to emit
683 With default argument values, implementations can choose to emit
684 either fulltext revision data or a delta. When emitting deltas,
684 either fulltext revision data or a delta. When emitting deltas,
685 implementations must consider whether the delta's base revision
685 implementations must consider whether the delta's base revision
686 fulltext is available to the receiver.
686 fulltext is available to the receiver.
687
687
688 The base revision fulltext is guaranteed to be available if any of
688 The base revision fulltext is guaranteed to be available if any of
689 the following are met:
689 the following are met:
690
690
691 * Its fulltext revision was emitted by this method call.
691 * Its fulltext revision was emitted by this method call.
692 * A delta for that revision was emitted by this method call.
692 * A delta for that revision was emitted by this method call.
693 * ``assumehaveparentrevisions`` is True and the base revision is a
693 * ``assumehaveparentrevisions`` is True and the base revision is a
694 parent of the node.
694 parent of the node.
695
695
696 ``nodesorder`` can be used to control the order that revisions are
696 ``nodesorder`` can be used to control the order that revisions are
697 emitted. By default, revisions can be reordered as long as they are
697 emitted. By default, revisions can be reordered as long as they are
698 in DAG topological order (see above). If the value is ``nodes``,
698 in DAG topological order (see above). If the value is ``nodes``,
699 the iteration order from ``nodes`` should be used. If the value is
699 the iteration order from ``nodes`` should be used. If the value is
700 ``storage``, then the native order from the backing storage layer
700 ``storage``, then the native order from the backing storage layer
701 is used. (Not all storage layers will have strong ordering and behavior
701 is used. (Not all storage layers will have strong ordering and behavior
702 of this mode is storage-dependent.) ``nodes`` ordering can force
702 of this mode is storage-dependent.) ``nodes`` ordering can force
703 revisions to be emitted before their ancestors, so consumers should
703 revisions to be emitted before their ancestors, so consumers should
704 use it with care.
704 use it with care.
705
705
706 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
706 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
707 be set and it is the caller's responsibility to resolve it, if needed.
707 be set and it is the caller's responsibility to resolve it, if needed.
708
708
709 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
709 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
710 all revision data should be emitted as deltas against the revision
710 all revision data should be emitted as deltas against the revision
711 emitted just prior. The initial revision should be a delta against its
711 emitted just prior. The initial revision should be a delta against its
712 1st parent.
712 1st parent.
713 """
713 """
714
714
715
715
716 class ifilemutation(interfaceutil.Interface):
716 class ifilemutation(interfaceutil.Interface):
717 """Storage interface for mutation events of a tracked file."""
717 """Storage interface for mutation events of a tracked file."""
718
718
719 def add(filedata, meta, transaction, linkrev, p1, p2):
719 def add(filedata, meta, transaction, linkrev, p1, p2):
720 """Add a new revision to the store.
720 """Add a new revision to the store.
721
721
722 Takes file data, dictionary of metadata, a transaction, linkrev,
722 Takes file data, dictionary of metadata, a transaction, linkrev,
723 and parent nodes.
723 and parent nodes.
724
724
725 Returns the node that was added.
725 Returns the node that was added.
726
726
727 May no-op if a revision matching the supplied data is already stored.
727 May no-op if a revision matching the supplied data is already stored.
728 """
728 """
729
729
730 def addrevision(
730 def addrevision(
731 revisiondata,
731 revisiondata,
732 transaction,
732 transaction,
733 linkrev,
733 linkrev,
734 p1,
734 p1,
735 p2,
735 p2,
736 node=None,
736 node=None,
737 flags=0,
737 flags=0,
738 cachedelta=None,
738 cachedelta=None,
739 ):
739 ):
740 """Add a new revision to the store.
740 """Add a new revision to the store.
741
741
742 This is similar to ``add()`` except it operates at a lower level.
742 This is similar to ``add()`` except it operates at a lower level.
743
743
744 The data passed in already contains a metadata header, if any.
744 The data passed in already contains a metadata header, if any.
745
745
746 ``node`` and ``flags`` can be used to define the expected node and
746 ``node`` and ``flags`` can be used to define the expected node and
747 the flags to use with storage. ``flags`` is a bitwise value composed
747 the flags to use with storage. ``flags`` is a bitwise value composed
748 of the various ``REVISION_FLAG_*`` constants.
748 of the various ``REVISION_FLAG_*`` constants.
749
749
750 ``add()`` is usually called when adding files from e.g. the working
750 ``add()`` is usually called when adding files from e.g. the working
751 directory. ``addrevision()`` is often called by ``add()`` and for
751 directory. ``addrevision()`` is often called by ``add()`` and for
752 scenarios where revision data has already been computed, such as when
752 scenarios where revision data has already been computed, such as when
753 applying raw data from a peer repo.
753 applying raw data from a peer repo.
754 """
754 """
755
755
756 def addgroup(
756 def addgroup(
757 deltas,
757 deltas,
758 linkmapper,
758 linkmapper,
759 transaction,
759 transaction,
760 addrevisioncb=None,
760 addrevisioncb=None,
761 maybemissingparents=False,
761 maybemissingparents=False,
762 ):
762 ):
763 """Process a series of deltas for storage.
763 """Process a series of deltas for storage.
764
764
765 ``deltas`` is an iterable of 7-tuples of
765 ``deltas`` is an iterable of 7-tuples of
766 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
766 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
767 to add.
767 to add.
768
768
769 The ``delta`` field contains ``mpatch`` data to apply to a base
769 The ``delta`` field contains ``mpatch`` data to apply to a base
770 revision, identified by ``deltabase``. The base node can be
770 revision, identified by ``deltabase``. The base node can be
771 ``nullid``, in which case the header from the delta can be ignored
771 ``nullid``, in which case the header from the delta can be ignored
772 and the delta used as the fulltext.
772 and the delta used as the fulltext.
773
773
774 ``addrevisioncb`` should be called for each node as it is committed.
774 ``addrevisioncb`` should be called for each node as it is committed.
775
775
776 ``maybemissingparents`` is a bool indicating whether the incoming
776 ``maybemissingparents`` is a bool indicating whether the incoming
777 data may reference parents/ancestor revisions that aren't present.
777 data may reference parents/ancestor revisions that aren't present.
778 This flag is set when receiving data into a "shallow" store that
778 This flag is set when receiving data into a "shallow" store that
779 doesn't hold all history.
779 doesn't hold all history.
780
780
781 Returns a list of nodes that were processed. A node will be in the list
781 Returns a list of nodes that were processed. A node will be in the list
782 even if it existed in the store previously.
782 even if it existed in the store previously.
783 """
783 """
784
784
785 def censorrevision(tr, node, tombstone=b''):
785 def censorrevision(tr, node, tombstone=b''):
786 """Remove the content of a single revision.
786 """Remove the content of a single revision.
787
787
788 The specified ``node`` will have its content purged from storage.
788 The specified ``node`` will have its content purged from storage.
789 Future attempts to access the revision data for this node will
789 Future attempts to access the revision data for this node will
790 result in failure.
790 result in failure.
791
791
792 A ``tombstone`` message can optionally be stored. This message may be
792 A ``tombstone`` message can optionally be stored. This message may be
793 displayed to users when they attempt to access the missing revision
793 displayed to users when they attempt to access the missing revision
794 data.
794 data.
795
795
796 Storage backends may have stored deltas against the previous content
796 Storage backends may have stored deltas against the previous content
797 in this revision. As part of censoring a revision, these storage
797 in this revision. As part of censoring a revision, these storage
798 backends are expected to rewrite any internally stored deltas such
798 backends are expected to rewrite any internally stored deltas such
799 that they no longer reference the deleted content.
799 that they no longer reference the deleted content.
800 """
800 """
801
801
802 def getstrippoint(minlink):
802 def getstrippoint(minlink):
803 """Find the minimum revision that must be stripped to strip a linkrev.
803 """Find the minimum revision that must be stripped to strip a linkrev.
804
804
805 Returns a 2-tuple containing the minimum revision number and a set
805 Returns a 2-tuple containing the minimum revision number and a set
806 of all revisions numbers that would be broken by this strip.
806 of all revisions numbers that would be broken by this strip.
807
807
808 TODO this is highly revlog centric and should be abstracted into
808 TODO this is highly revlog centric and should be abstracted into
809 a higher-level deletion API. ``repair.strip()`` relies on this.
809 a higher-level deletion API. ``repair.strip()`` relies on this.
810 """
810 """
811
811
812 def strip(minlink, transaction):
812 def strip(minlink, transaction):
813 """Remove storage of items starting at a linkrev.
813 """Remove storage of items starting at a linkrev.
814
814
815 This uses ``getstrippoint()`` to determine the first node to remove.
815 This uses ``getstrippoint()`` to determine the first node to remove.
816 Then it effectively truncates storage for all revisions after that.
816 Then it effectively truncates storage for all revisions after that.
817
817
818 TODO this is highly revlog centric and should be abstracted into a
818 TODO this is highly revlog centric and should be abstracted into a
819 higher-level deletion API.
819 higher-level deletion API.
820 """
820 """
821
821
822
822
823 class ifilestorage(ifileindex, ifiledata, ifilemutation):
823 class ifilestorage(ifileindex, ifiledata, ifilemutation):
824 """Complete storage interface for a single tracked file."""
824 """Complete storage interface for a single tracked file."""
825
825
826 def files():
826 def files():
827 """Obtain paths that are backing storage for this file.
827 """Obtain paths that are backing storage for this file.
828
828
829 TODO this is used heavily by verify code and there should probably
829 TODO this is used heavily by verify code and there should probably
830 be a better API for that.
830 be a better API for that.
831 """
831 """
832
832
833 def storageinfo(
833 def storageinfo(
834 exclusivefiles=False,
834 exclusivefiles=False,
835 sharedfiles=False,
835 sharedfiles=False,
836 revisionscount=False,
836 revisionscount=False,
837 trackedsize=False,
837 trackedsize=False,
838 storedsize=False,
838 storedsize=False,
839 ):
839 ):
840 """Obtain information about storage for this file's data.
840 """Obtain information about storage for this file's data.
841
841
842 Returns a dict describing storage for this tracked path. The keys
842 Returns a dict describing storage for this tracked path. The keys
843 in the dict map to arguments of the same. The arguments are bools
843 in the dict map to arguments of the same. The arguments are bools
844 indicating whether to calculate and obtain that data.
844 indicating whether to calculate and obtain that data.
845
845
846 exclusivefiles
846 exclusivefiles
847 Iterable of (vfs, path) describing files that are exclusively
847 Iterable of (vfs, path) describing files that are exclusively
848 used to back storage for this tracked path.
848 used to back storage for this tracked path.
849
849
850 sharedfiles
850 sharedfiles
851 Iterable of (vfs, path) describing files that are used to back
851 Iterable of (vfs, path) describing files that are used to back
852 storage for this tracked path. Those files may also provide storage
852 storage for this tracked path. Those files may also provide storage
853 for other stored entities.
853 for other stored entities.
854
854
855 revisionscount
855 revisionscount
856 Number of revisions available for retrieval.
856 Number of revisions available for retrieval.
857
857
858 trackedsize
858 trackedsize
859 Total size in bytes of all tracked revisions. This is a sum of the
859 Total size in bytes of all tracked revisions. This is a sum of the
860 length of the fulltext of all revisions.
860 length of the fulltext of all revisions.
861
861
862 storedsize
862 storedsize
863 Total size in bytes used to store data for all tracked revisions.
863 Total size in bytes used to store data for all tracked revisions.
864 This is commonly less than ``trackedsize`` due to internal usage
864 This is commonly less than ``trackedsize`` due to internal usage
865 of deltas rather than fulltext revisions.
865 of deltas rather than fulltext revisions.
866
866
867 Not all storage backends may support all queries are have a reasonable
867 Not all storage backends may support all queries are have a reasonable
868 value to use. In that case, the value should be set to ``None`` and
868 value to use. In that case, the value should be set to ``None`` and
869 callers are expected to handle this special value.
869 callers are expected to handle this special value.
870 """
870 """
871
871
872 def verifyintegrity(state):
872 def verifyintegrity(state):
873 """Verifies the integrity of file storage.
873 """Verifies the integrity of file storage.
874
874
875 ``state`` is a dict holding state of the verifier process. It can be
875 ``state`` is a dict holding state of the verifier process. It can be
876 used to communicate data between invocations of multiple storage
876 used to communicate data between invocations of multiple storage
877 primitives.
877 primitives.
878
878
879 If individual revisions cannot have their revision content resolved,
879 If individual revisions cannot have their revision content resolved,
880 the method is expected to set the ``skipread`` key to a set of nodes
880 the method is expected to set the ``skipread`` key to a set of nodes
881 that encountered problems.
881 that encountered problems.
882
882
883 The method yields objects conforming to the ``iverifyproblem``
883 The method yields objects conforming to the ``iverifyproblem``
884 interface.
884 interface.
885 """
885 """
886
886
887
887
888 class idirs(interfaceutil.Interface):
888 class idirs(interfaceutil.Interface):
889 """Interface representing a collection of directories from paths.
889 """Interface representing a collection of directories from paths.
890
890
891 This interface is essentially a derived data structure representing
891 This interface is essentially a derived data structure representing
892 directories from a collection of paths.
892 directories from a collection of paths.
893 """
893 """
894
894
895 def addpath(path):
895 def addpath(path):
896 """Add a path to the collection.
896 """Add a path to the collection.
897
897
898 All directories in the path will be added to the collection.
898 All directories in the path will be added to the collection.
899 """
899 """
900
900
901 def delpath(path):
901 def delpath(path):
902 """Remove a path from the collection.
902 """Remove a path from the collection.
903
903
904 If the removal was the last path in a particular directory, the
904 If the removal was the last path in a particular directory, the
905 directory is removed from the collection.
905 directory is removed from the collection.
906 """
906 """
907
907
908 def __iter__():
908 def __iter__():
909 """Iterate over the directories in this collection of paths."""
909 """Iterate over the directories in this collection of paths."""
910
910
911 def __contains__(path):
911 def __contains__(path):
912 """Whether a specific directory is in this collection."""
912 """Whether a specific directory is in this collection."""
913
913
914
914
915 class imanifestdict(interfaceutil.Interface):
915 class imanifestdict(interfaceutil.Interface):
916 """Interface representing a manifest data structure.
916 """Interface representing a manifest data structure.
917
917
918 A manifest is effectively a dict mapping paths to entries. Each entry
918 A manifest is effectively a dict mapping paths to entries. Each entry
919 consists of a binary node and extra flags affecting that entry.
919 consists of a binary node and extra flags affecting that entry.
920 """
920 """
921
921
922 def __getitem__(path):
922 def __getitem__(path):
923 """Returns the binary node value for a path in the manifest.
923 """Returns the binary node value for a path in the manifest.
924
924
925 Raises ``KeyError`` if the path does not exist in the manifest.
925 Raises ``KeyError`` if the path does not exist in the manifest.
926
926
927 Equivalent to ``self.find(path)[0]``.
927 Equivalent to ``self.find(path)[0]``.
928 """
928 """
929
929
930 def find(path):
930 def find(path):
931 """Returns the entry for a path in the manifest.
931 """Returns the entry for a path in the manifest.
932
932
933 Returns a 2-tuple of (node, flags).
933 Returns a 2-tuple of (node, flags).
934
934
935 Raises ``KeyError`` if the path does not exist in the manifest.
935 Raises ``KeyError`` if the path does not exist in the manifest.
936 """
936 """
937
937
938 def __len__():
938 def __len__():
939 """Return the number of entries in the manifest."""
939 """Return the number of entries in the manifest."""
940
940
941 def __nonzero__():
941 def __nonzero__():
942 """Returns True if the manifest has entries, False otherwise."""
942 """Returns True if the manifest has entries, False otherwise."""
943
943
944 __bool__ = __nonzero__
944 __bool__ = __nonzero__
945
945
946 def __setitem__(path, node):
946 def __setitem__(path, node):
947 """Define the node value for a path in the manifest.
947 """Define the node value for a path in the manifest.
948
948
949 If the path is already in the manifest, its flags will be copied to
949 If the path is already in the manifest, its flags will be copied to
950 the new entry.
950 the new entry.
951 """
951 """
952
952
953 def __contains__(path):
953 def __contains__(path):
954 """Whether a path exists in the manifest."""
954 """Whether a path exists in the manifest."""
955
955
956 def __delitem__(path):
956 def __delitem__(path):
957 """Remove a path from the manifest.
957 """Remove a path from the manifest.
958
958
959 Raises ``KeyError`` if the path is not in the manifest.
959 Raises ``KeyError`` if the path is not in the manifest.
960 """
960 """
961
961
962 def __iter__():
962 def __iter__():
963 """Iterate over paths in the manifest."""
963 """Iterate over paths in the manifest."""
964
964
965 def iterkeys():
965 def iterkeys():
966 """Iterate over paths in the manifest."""
966 """Iterate over paths in the manifest."""
967
967
968 def keys():
968 def keys():
969 """Obtain a list of paths in the manifest."""
969 """Obtain a list of paths in the manifest."""
970
970
971 def filesnotin(other, match=None):
971 def filesnotin(other, match=None):
972 """Obtain the set of paths in this manifest but not in another.
972 """Obtain the set of paths in this manifest but not in another.
973
973
974 ``match`` is an optional matcher function to be applied to both
974 ``match`` is an optional matcher function to be applied to both
975 manifests.
975 manifests.
976
976
977 Returns a set of paths.
977 Returns a set of paths.
978 """
978 """
979
979
980 def dirs():
980 def dirs():
981 """Returns an object implementing the ``idirs`` interface."""
981 """Returns an object implementing the ``idirs`` interface."""
982
982
983 def hasdir(dir):
983 def hasdir(dir):
984 """Returns a bool indicating if a directory is in this manifest."""
984 """Returns a bool indicating if a directory is in this manifest."""
985
985
986 def matches(match):
986 def matches(match):
987 """Generate a new manifest filtered through a matcher.
987 """Generate a new manifest filtered through a matcher.
988
988
989 Returns an object conforming to the ``imanifestdict`` interface.
989 Returns an object conforming to the ``imanifestdict`` interface.
990 """
990 """
991
991
992 def walk(match):
992 def walk(match):
993 """Generator of paths in manifest satisfying a matcher.
993 """Generator of paths in manifest satisfying a matcher.
994
994
995 This is equivalent to ``self.matches(match).iterkeys()`` except a new
995 This is equivalent to ``self.matches(match).iterkeys()`` except a new
996 manifest object is not created.
996 manifest object is not created.
997
997
998 If the matcher has explicit files listed and they don't exist in
998 If the matcher has explicit files listed and they don't exist in
999 the manifest, ``match.bad()`` is called for each missing file.
999 the manifest, ``match.bad()`` is called for each missing file.
1000 """
1000 """
1001
1001
1002 def diff(other, match=None, clean=False):
1002 def diff(other, match=None, clean=False):
1003 """Find differences between this manifest and another.
1003 """Find differences between this manifest and another.
1004
1004
1005 This manifest is compared to ``other``.
1005 This manifest is compared to ``other``.
1006
1006
1007 If ``match`` is provided, the two manifests are filtered against this
1007 If ``match`` is provided, the two manifests are filtered against this
1008 matcher and only entries satisfying the matcher are compared.
1008 matcher and only entries satisfying the matcher are compared.
1009
1009
1010 If ``clean`` is True, unchanged files are included in the returned
1010 If ``clean`` is True, unchanged files are included in the returned
1011 object.
1011 object.
1012
1012
1013 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1013 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1014 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1014 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1015 represents the node and flags for this manifest and ``(node2, flag2)``
1015 represents the node and flags for this manifest and ``(node2, flag2)``
1016 are the same for the other manifest.
1016 are the same for the other manifest.
1017 """
1017 """
1018
1018
1019 def setflag(path, flag):
1019 def setflag(path, flag):
1020 """Set the flag value for a given path.
1020 """Set the flag value for a given path.
1021
1021
1022 Raises ``KeyError`` if the path is not already in the manifest.
1022 Raises ``KeyError`` if the path is not already in the manifest.
1023 """
1023 """
1024
1024
1025 def get(path, default=None):
1025 def get(path, default=None):
1026 """Obtain the node value for a path or a default value if missing."""
1026 """Obtain the node value for a path or a default value if missing."""
1027
1027
1028 def flags(path, default=b''):
1028 def flags(path, default=b''):
1029 """Return the flags value for a path or a default value if missing."""
1029 """Return the flags value for a path or a default value if missing."""
1030
1030
1031 def copy():
1031 def copy():
1032 """Return a copy of this manifest."""
1032 """Return a copy of this manifest."""
1033
1033
1034 def items():
1034 def items():
1035 """Returns an iterable of (path, node) for items in this manifest."""
1035 """Returns an iterable of (path, node) for items in this manifest."""
1036
1036
1037 def iteritems():
1037 def iteritems():
1038 """Identical to items()."""
1038 """Identical to items()."""
1039
1039
1040 def iterentries():
1040 def iterentries():
1041 """Returns an iterable of (path, node, flags) for this manifest.
1041 """Returns an iterable of (path, node, flags) for this manifest.
1042
1042
1043 Similar to ``iteritems()`` except items are a 3-tuple and include
1043 Similar to ``iteritems()`` except items are a 3-tuple and include
1044 flags.
1044 flags.
1045 """
1045 """
1046
1046
1047 def text():
1047 def text():
1048 """Obtain the raw data representation for this manifest.
1048 """Obtain the raw data representation for this manifest.
1049
1049
1050 Result is used to create a manifest revision.
1050 Result is used to create a manifest revision.
1051 """
1051 """
1052
1052
1053 def fastdelta(base, changes):
1053 def fastdelta(base, changes):
1054 """Obtain a delta between this manifest and another given changes.
1054 """Obtain a delta between this manifest and another given changes.
1055
1055
1056 ``base`` in the raw data representation for another manifest.
1056 ``base`` in the raw data representation for another manifest.
1057
1057
1058 ``changes`` is an iterable of ``(path, to_delete)``.
1058 ``changes`` is an iterable of ``(path, to_delete)``.
1059
1059
1060 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1060 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1061 delta between ``base`` and this manifest.
1061 delta between ``base`` and this manifest.
1062 """
1062 """
1063
1063
1064
1064
1065 class imanifestrevisionbase(interfaceutil.Interface):
1065 class imanifestrevisionbase(interfaceutil.Interface):
1066 """Base interface representing a single revision of a manifest.
1066 """Base interface representing a single revision of a manifest.
1067
1067
1068 Should not be used as a primary interface: should always be inherited
1068 Should not be used as a primary interface: should always be inherited
1069 as part of a larger interface.
1069 as part of a larger interface.
1070 """
1070 """
1071
1071
1072 def new():
1072 def new():
1073 """Obtain a new manifest instance.
1073 """Obtain a new manifest instance.
1074
1074
1075 Returns an object conforming to the ``imanifestrevisionwritable``
1075 Returns an object conforming to the ``imanifestrevisionwritable``
1076 interface. The instance will be associated with the same
1076 interface. The instance will be associated with the same
1077 ``imanifestlog`` collection as this instance.
1077 ``imanifestlog`` collection as this instance.
1078 """
1078 """
1079
1079
1080 def copy():
1080 def copy():
1081 """Obtain a copy of this manifest instance.
1081 """Obtain a copy of this manifest instance.
1082
1082
1083 Returns an object conforming to the ``imanifestrevisionwritable``
1083 Returns an object conforming to the ``imanifestrevisionwritable``
1084 interface. The instance will be associated with the same
1084 interface. The instance will be associated with the same
1085 ``imanifestlog`` collection as this instance.
1085 ``imanifestlog`` collection as this instance.
1086 """
1086 """
1087
1087
1088 def read():
1088 def read():
1089 """Obtain the parsed manifest data structure.
1089 """Obtain the parsed manifest data structure.
1090
1090
1091 The returned object conforms to the ``imanifestdict`` interface.
1091 The returned object conforms to the ``imanifestdict`` interface.
1092 """
1092 """
1093
1093
1094
1094
1095 class imanifestrevisionstored(imanifestrevisionbase):
1095 class imanifestrevisionstored(imanifestrevisionbase):
1096 """Interface representing a manifest revision committed to storage."""
1096 """Interface representing a manifest revision committed to storage."""
1097
1097
1098 def node():
1098 def node():
1099 """The binary node for this manifest."""
1099 """The binary node for this manifest."""
1100
1100
1101 parents = interfaceutil.Attribute(
1101 parents = interfaceutil.Attribute(
1102 """List of binary nodes that are parents for this manifest revision."""
1102 """List of binary nodes that are parents for this manifest revision."""
1103 )
1103 )
1104
1104
1105 def readdelta(shallow=False):
1105 def readdelta(shallow=False):
1106 """Obtain the manifest data structure representing changes from parent.
1106 """Obtain the manifest data structure representing changes from parent.
1107
1107
1108 This manifest is compared to its 1st parent. A new manifest representing
1108 This manifest is compared to its 1st parent. A new manifest representing
1109 those differences is constructed.
1109 those differences is constructed.
1110
1110
1111 The returned object conforms to the ``imanifestdict`` interface.
1111 The returned object conforms to the ``imanifestdict`` interface.
1112 """
1112 """
1113
1113
1114 def readfast(shallow=False):
1114 def readfast(shallow=False):
1115 """Calls either ``read()`` or ``readdelta()``.
1115 """Calls either ``read()`` or ``readdelta()``.
1116
1116
1117 The faster of the two options is called.
1117 The faster of the two options is called.
1118 """
1118 """
1119
1119
1120 def find(key):
1120 def find(key):
1121 """Calls self.read().find(key)``.
1121 """Calls self.read().find(key)``.
1122
1122
1123 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1123 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1124 """
1124 """
1125
1125
1126
1126
1127 class imanifestrevisionwritable(imanifestrevisionbase):
1127 class imanifestrevisionwritable(imanifestrevisionbase):
1128 """Interface representing a manifest revision that can be committed."""
1128 """Interface representing a manifest revision that can be committed."""
1129
1129
1130 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1130 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1131 """Add this revision to storage.
1131 """Add this revision to storage.
1132
1132
1133 Takes a transaction object, the changeset revision number it will
1133 Takes a transaction object, the changeset revision number it will
1134 be associated with, its parent nodes, and lists of added and
1134 be associated with, its parent nodes, and lists of added and
1135 removed paths.
1135 removed paths.
1136
1136
1137 If match is provided, storage can choose not to inspect or write out
1137 If match is provided, storage can choose not to inspect or write out
1138 items that do not match. Storage is still required to be able to provide
1138 items that do not match. Storage is still required to be able to provide
1139 the full manifest in the future for any directories written (these
1139 the full manifest in the future for any directories written (these
1140 manifests should not be "narrowed on disk").
1140 manifests should not be "narrowed on disk").
1141
1141
1142 Returns the binary node of the created revision.
1142 Returns the binary node of the created revision.
1143 """
1143 """
1144
1144
1145
1145
1146 class imanifeststorage(interfaceutil.Interface):
1146 class imanifeststorage(interfaceutil.Interface):
1147 """Storage interface for manifest data."""
1147 """Storage interface for manifest data."""
1148
1148
1149 tree = interfaceutil.Attribute(
1149 tree = interfaceutil.Attribute(
1150 """The path to the directory this manifest tracks.
1150 """The path to the directory this manifest tracks.
1151
1151
1152 The empty bytestring represents the root manifest.
1152 The empty bytestring represents the root manifest.
1153 """
1153 """
1154 )
1154 )
1155
1155
1156 index = interfaceutil.Attribute(
1156 index = interfaceutil.Attribute(
1157 """An ``ifilerevisionssequence`` instance."""
1157 """An ``ifilerevisionssequence`` instance."""
1158 )
1158 )
1159
1159
1160 indexfile = interfaceutil.Attribute(
1160 indexfile = interfaceutil.Attribute(
1161 """Path of revlog index file.
1161 """Path of revlog index file.
1162
1162
1163 TODO this is revlog specific and should not be exposed.
1163 TODO this is revlog specific and should not be exposed.
1164 """
1164 """
1165 )
1165 )
1166
1166
1167 opener = interfaceutil.Attribute(
1167 opener = interfaceutil.Attribute(
1168 """VFS opener to use to access underlying files used for storage.
1168 """VFS opener to use to access underlying files used for storage.
1169
1169
1170 TODO this is revlog specific and should not be exposed.
1170 TODO this is revlog specific and should not be exposed.
1171 """
1171 """
1172 )
1172 )
1173
1173
1174 version = interfaceutil.Attribute(
1174 version = interfaceutil.Attribute(
1175 """Revlog version number.
1175 """Revlog version number.
1176
1176
1177 TODO this is revlog specific and should not be exposed.
1177 TODO this is revlog specific and should not be exposed.
1178 """
1178 """
1179 )
1179 )
1180
1180
1181 _generaldelta = interfaceutil.Attribute(
1181 _generaldelta = interfaceutil.Attribute(
1182 """Whether generaldelta storage is being used.
1182 """Whether generaldelta storage is being used.
1183
1183
1184 TODO this is revlog specific and should not be exposed.
1184 TODO this is revlog specific and should not be exposed.
1185 """
1185 """
1186 )
1186 )
1187
1187
1188 fulltextcache = interfaceutil.Attribute(
1188 fulltextcache = interfaceutil.Attribute(
1189 """Dict with cache of fulltexts.
1189 """Dict with cache of fulltexts.
1190
1190
1191 TODO this doesn't feel appropriate for the storage interface.
1191 TODO this doesn't feel appropriate for the storage interface.
1192 """
1192 """
1193 )
1193 )
1194
1194
1195 def __len__():
1195 def __len__():
1196 """Obtain the number of revisions stored for this manifest."""
1196 """Obtain the number of revisions stored for this manifest."""
1197
1197
1198 def __iter__():
1198 def __iter__():
1199 """Iterate over revision numbers for this manifest."""
1199 """Iterate over revision numbers for this manifest."""
1200
1200
1201 def rev(node):
1201 def rev(node):
1202 """Obtain the revision number given a binary node.
1202 """Obtain the revision number given a binary node.
1203
1203
1204 Raises ``error.LookupError`` if the node is not known.
1204 Raises ``error.LookupError`` if the node is not known.
1205 """
1205 """
1206
1206
1207 def node(rev):
1207 def node(rev):
1208 """Obtain the node value given a revision number.
1208 """Obtain the node value given a revision number.
1209
1209
1210 Raises ``error.LookupError`` if the revision is not known.
1210 Raises ``error.LookupError`` if the revision is not known.
1211 """
1211 """
1212
1212
1213 def lookup(value):
1213 def lookup(value):
1214 """Attempt to resolve a value to a node.
1214 """Attempt to resolve a value to a node.
1215
1215
1216 Value can be a binary node, hex node, revision number, or a bytes
1216 Value can be a binary node, hex node, revision number, or a bytes
1217 that can be converted to an integer.
1217 that can be converted to an integer.
1218
1218
1219 Raises ``error.LookupError`` if a ndoe could not be resolved.
1219 Raises ``error.LookupError`` if a ndoe could not be resolved.
1220 """
1220 """
1221
1221
1222 def parents(node):
1222 def parents(node):
1223 """Returns a 2-tuple of parent nodes for a node.
1223 """Returns a 2-tuple of parent nodes for a node.
1224
1224
1225 Values will be ``nullid`` if the parent is empty.
1225 Values will be ``nullid`` if the parent is empty.
1226 """
1226 """
1227
1227
1228 def parentrevs(rev):
1228 def parentrevs(rev):
1229 """Like parents() but operates on revision numbers."""
1229 """Like parents() but operates on revision numbers."""
1230
1230
1231 def linkrev(rev):
1231 def linkrev(rev):
1232 """Obtain the changeset revision number a revision is linked to."""
1232 """Obtain the changeset revision number a revision is linked to."""
1233
1233
1234 def revision(node, _df=None, raw=False):
1234 def revision(node, _df=None, raw=False):
1235 """Obtain fulltext data for a node."""
1235 """Obtain fulltext data for a node."""
1236
1236
1237 def rawdata(node, _df=None):
1237 def rawdata(node, _df=None):
1238 """Obtain raw data for a node."""
1238 """Obtain raw data for a node."""
1239
1239
1240 def revdiff(rev1, rev2):
1240 def revdiff(rev1, rev2):
1241 """Obtain a delta between two revision numbers.
1241 """Obtain a delta between two revision numbers.
1242
1242
1243 The returned data is the result of ``bdiff.bdiff()`` on the raw
1243 The returned data is the result of ``bdiff.bdiff()`` on the raw
1244 revision data.
1244 revision data.
1245 """
1245 """
1246
1246
1247 def cmp(node, fulltext):
1247 def cmp(node, fulltext):
1248 """Compare fulltext to another revision.
1248 """Compare fulltext to another revision.
1249
1249
1250 Returns True if the fulltext is different from what is stored.
1250 Returns True if the fulltext is different from what is stored.
1251 """
1251 """
1252
1252
1253 def emitrevisions(
1253 def emitrevisions(
1254 nodes,
1254 nodes,
1255 nodesorder=None,
1255 nodesorder=None,
1256 revisiondata=False,
1256 revisiondata=False,
1257 assumehaveparentrevisions=False,
1257 assumehaveparentrevisions=False,
1258 ):
1258 ):
1259 """Produce ``irevisiondelta`` describing revisions.
1259 """Produce ``irevisiondelta`` describing revisions.
1260
1260
1261 See the documentation for ``ifiledata`` for more.
1261 See the documentation for ``ifiledata`` for more.
1262 """
1262 """
1263
1263
1264 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
1264 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
1265 """Process a series of deltas for storage.
1265 """Process a series of deltas for storage.
1266
1266
1267 See the documentation in ``ifilemutation`` for more.
1267 See the documentation in ``ifilemutation`` for more.
1268 """
1268 """
1269
1269
1270 def rawsize(rev):
1270 def rawsize(rev):
1271 """Obtain the size of tracked data.
1271 """Obtain the size of tracked data.
1272
1272
1273 Is equivalent to ``len(m.rawdata(node))``.
1273 Is equivalent to ``len(m.rawdata(node))``.
1274
1274
1275 TODO this method is only used by upgrade code and may be removed.
1275 TODO this method is only used by upgrade code and may be removed.
1276 """
1276 """
1277
1277
1278 def getstrippoint(minlink):
1278 def getstrippoint(minlink):
1279 """Find minimum revision that must be stripped to strip a linkrev.
1279 """Find minimum revision that must be stripped to strip a linkrev.
1280
1280
1281 See the documentation in ``ifilemutation`` for more.
1281 See the documentation in ``ifilemutation`` for more.
1282 """
1282 """
1283
1283
1284 def strip(minlink, transaction):
1284 def strip(minlink, transaction):
1285 """Remove storage of items starting at a linkrev.
1285 """Remove storage of items starting at a linkrev.
1286
1286
1287 See the documentation in ``ifilemutation`` for more.
1287 See the documentation in ``ifilemutation`` for more.
1288 """
1288 """
1289
1289
1290 def checksize():
1290 def checksize():
1291 """Obtain the expected sizes of backing files.
1291 """Obtain the expected sizes of backing files.
1292
1292
1293 TODO this is used by verify and it should not be part of the interface.
1293 TODO this is used by verify and it should not be part of the interface.
1294 """
1294 """
1295
1295
1296 def files():
1296 def files():
1297 """Obtain paths that are backing storage for this manifest.
1297 """Obtain paths that are backing storage for this manifest.
1298
1298
1299 TODO this is used by verify and there should probably be a better API
1299 TODO this is used by verify and there should probably be a better API
1300 for this functionality.
1300 for this functionality.
1301 """
1301 """
1302
1302
1303 def deltaparent(rev):
1303 def deltaparent(rev):
1304 """Obtain the revision that a revision is delta'd against.
1304 """Obtain the revision that a revision is delta'd against.
1305
1305
1306 TODO delta encoding is an implementation detail of storage and should
1306 TODO delta encoding is an implementation detail of storage and should
1307 not be exposed to the storage interface.
1307 not be exposed to the storage interface.
1308 """
1308 """
1309
1309
1310 def clone(tr, dest, **kwargs):
1310 def clone(tr, dest, **kwargs):
1311 """Clone this instance to another."""
1311 """Clone this instance to another."""
1312
1312
1313 def clearcaches(clear_persisted_data=False):
1313 def clearcaches(clear_persisted_data=False):
1314 """Clear any caches associated with this instance."""
1314 """Clear any caches associated with this instance."""
1315
1315
1316 def dirlog(d):
1316 def dirlog(d):
1317 """Obtain a manifest storage instance for a tree."""
1317 """Obtain a manifest storage instance for a tree."""
1318
1318
1319 def add(
1319 def add(
1320 m, transaction, link, p1, p2, added, removed, readtree=None, match=None
1320 m, transaction, link, p1, p2, added, removed, readtree=None, match=None
1321 ):
1321 ):
1322 """Add a revision to storage.
1322 """Add a revision to storage.
1323
1323
1324 ``m`` is an object conforming to ``imanifestdict``.
1324 ``m`` is an object conforming to ``imanifestdict``.
1325
1325
1326 ``link`` is the linkrev revision number.
1326 ``link`` is the linkrev revision number.
1327
1327
1328 ``p1`` and ``p2`` are the parent revision numbers.
1328 ``p1`` and ``p2`` are the parent revision numbers.
1329
1329
1330 ``added`` and ``removed`` are iterables of added and removed paths,
1330 ``added`` and ``removed`` are iterables of added and removed paths,
1331 respectively.
1331 respectively.
1332
1332
1333 ``readtree`` is a function that can be used to read the child tree(s)
1333 ``readtree`` is a function that can be used to read the child tree(s)
1334 when recursively writing the full tree structure when using
1334 when recursively writing the full tree structure when using
1335 treemanifets.
1335 treemanifets.
1336
1336
1337 ``match`` is a matcher that can be used to hint to storage that not all
1337 ``match`` is a matcher that can be used to hint to storage that not all
1338 paths must be inspected; this is an optimization and can be safely
1338 paths must be inspected; this is an optimization and can be safely
1339 ignored. Note that the storage must still be able to reproduce a full
1339 ignored. Note that the storage must still be able to reproduce a full
1340 manifest including files that did not match.
1340 manifest including files that did not match.
1341 """
1341 """
1342
1342
1343 def storageinfo(
1343 def storageinfo(
1344 exclusivefiles=False,
1344 exclusivefiles=False,
1345 sharedfiles=False,
1345 sharedfiles=False,
1346 revisionscount=False,
1346 revisionscount=False,
1347 trackedsize=False,
1347 trackedsize=False,
1348 storedsize=False,
1348 storedsize=False,
1349 ):
1349 ):
1350 """Obtain information about storage for this manifest's data.
1350 """Obtain information about storage for this manifest's data.
1351
1351
1352 See ``ifilestorage.storageinfo()`` for a description of this method.
1352 See ``ifilestorage.storageinfo()`` for a description of this method.
1353 This one behaves the same way, except for manifest data.
1353 This one behaves the same way, except for manifest data.
1354 """
1354 """
1355
1355
1356
1356
1357 class imanifestlog(interfaceutil.Interface):
1357 class imanifestlog(interfaceutil.Interface):
1358 """Interface representing a collection of manifest snapshots.
1358 """Interface representing a collection of manifest snapshots.
1359
1359
1360 Represents the root manifest in a repository.
1360 Represents the root manifest in a repository.
1361
1361
1362 Also serves as a means to access nested tree manifests and to cache
1362 Also serves as a means to access nested tree manifests and to cache
1363 tree manifests.
1363 tree manifests.
1364 """
1364 """
1365
1365
1366 def __getitem__(node):
1366 def __getitem__(node):
1367 """Obtain a manifest instance for a given binary node.
1367 """Obtain a manifest instance for a given binary node.
1368
1368
1369 Equivalent to calling ``self.get('', node)``.
1369 Equivalent to calling ``self.get('', node)``.
1370
1370
1371 The returned object conforms to the ``imanifestrevisionstored``
1371 The returned object conforms to the ``imanifestrevisionstored``
1372 interface.
1372 interface.
1373 """
1373 """
1374
1374
1375 def get(tree, node, verify=True):
1375 def get(tree, node, verify=True):
1376 """Retrieve the manifest instance for a given directory and binary node.
1376 """Retrieve the manifest instance for a given directory and binary node.
1377
1377
1378 ``node`` always refers to the node of the root manifest (which will be
1378 ``node`` always refers to the node of the root manifest (which will be
1379 the only manifest if flat manifests are being used).
1379 the only manifest if flat manifests are being used).
1380
1380
1381 If ``tree`` is the empty string, the root manifest is returned.
1381 If ``tree`` is the empty string, the root manifest is returned.
1382 Otherwise the manifest for the specified directory will be returned
1382 Otherwise the manifest for the specified directory will be returned
1383 (requires tree manifests).
1383 (requires tree manifests).
1384
1384
1385 If ``verify`` is True, ``LookupError`` is raised if the node is not
1385 If ``verify`` is True, ``LookupError`` is raised if the node is not
1386 known.
1386 known.
1387
1387
1388 The returned object conforms to the ``imanifestrevisionstored``
1388 The returned object conforms to the ``imanifestrevisionstored``
1389 interface.
1389 interface.
1390 """
1390 """
1391
1391
1392 def getstorage(tree):
1392 def getstorage(tree):
1393 """Retrieve an interface to storage for a particular tree.
1393 """Retrieve an interface to storage for a particular tree.
1394
1394
1395 If ``tree`` is the empty bytestring, storage for the root manifest will
1395 If ``tree`` is the empty bytestring, storage for the root manifest will
1396 be returned. Otherwise storage for a tree manifest is returned.
1396 be returned. Otherwise storage for a tree manifest is returned.
1397
1397
1398 TODO formalize interface for returned object.
1398 TODO formalize interface for returned object.
1399 """
1399 """
1400
1400
1401 def clearcaches():
1401 def clearcaches():
1402 """Clear caches associated with this collection."""
1402 """Clear caches associated with this collection."""
1403
1403
1404 def rev(node):
1404 def rev(node):
1405 """Obtain the revision number for a binary node.
1405 """Obtain the revision number for a binary node.
1406
1406
1407 Raises ``error.LookupError`` if the node is not known.
1407 Raises ``error.LookupError`` if the node is not known.
1408 """
1408 """
1409
1409
1410
1410
1411 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1411 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1412 """Local repository sub-interface providing access to tracked file storage.
1412 """Local repository sub-interface providing access to tracked file storage.
1413
1413
1414 This interface defines how a repository accesses storage for a single
1414 This interface defines how a repository accesses storage for a single
1415 tracked file path.
1415 tracked file path.
1416 """
1416 """
1417
1417
1418 def file(f):
1418 def file(f):
1419 """Obtain a filelog for a tracked path.
1419 """Obtain a filelog for a tracked path.
1420
1420
1421 The returned type conforms to the ``ifilestorage`` interface.
1421 The returned type conforms to the ``ifilestorage`` interface.
1422 """
1422 """
1423
1423
1424
1424
1425 class ilocalrepositorymain(interfaceutil.Interface):
1425 class ilocalrepositorymain(interfaceutil.Interface):
1426 """Main interface for local repositories.
1426 """Main interface for local repositories.
1427
1427
1428 This currently captures the reality of things - not how things should be.
1428 This currently captures the reality of things - not how things should be.
1429 """
1429 """
1430
1430
1431 supportedformats = interfaceutil.Attribute(
1431 supportedformats = interfaceutil.Attribute(
1432 """Set of requirements that apply to stream clone.
1432 """Set of requirements that apply to stream clone.
1433
1433
1434 This is actually a class attribute and is shared among all instances.
1434 This is actually a class attribute and is shared among all instances.
1435 """
1435 """
1436 )
1436 )
1437
1437
1438 supported = interfaceutil.Attribute(
1438 supported = interfaceutil.Attribute(
1439 """Set of requirements that this repo is capable of opening."""
1439 """Set of requirements that this repo is capable of opening."""
1440 )
1440 )
1441
1441
1442 requirements = interfaceutil.Attribute(
1442 requirements = interfaceutil.Attribute(
1443 """Set of requirements this repo uses."""
1443 """Set of requirements this repo uses."""
1444 )
1444 )
1445
1445
1446 features = interfaceutil.Attribute(
1446 features = interfaceutil.Attribute(
1447 """Set of "features" this repository supports.
1447 """Set of "features" this repository supports.
1448
1448
1449 A "feature" is a loosely-defined term. It can refer to a feature
1449 A "feature" is a loosely-defined term. It can refer to a feature
1450 in the classical sense or can describe an implementation detail
1450 in the classical sense or can describe an implementation detail
1451 of the repository. For example, a ``readonly`` feature may denote
1451 of the repository. For example, a ``readonly`` feature may denote
1452 the repository as read-only. Or a ``revlogfilestore`` feature may
1452 the repository as read-only. Or a ``revlogfilestore`` feature may
1453 denote that the repository is using revlogs for file storage.
1453 denote that the repository is using revlogs for file storage.
1454
1454
1455 The intent of features is to provide a machine-queryable mechanism
1455 The intent of features is to provide a machine-queryable mechanism
1456 for repo consumers to test for various repository characteristics.
1456 for repo consumers to test for various repository characteristics.
1457
1457
1458 Features are similar to ``requirements``. The main difference is that
1458 Features are similar to ``requirements``. The main difference is that
1459 requirements are stored on-disk and represent requirements to open the
1459 requirements are stored on-disk and represent requirements to open the
1460 repository. Features are more run-time capabilities of the repository
1460 repository. Features are more run-time capabilities of the repository
1461 and more granular capabilities (which may be derived from requirements).
1461 and more granular capabilities (which may be derived from requirements).
1462 """
1462 """
1463 )
1463 )
1464
1464
1465 filtername = interfaceutil.Attribute(
1465 filtername = interfaceutil.Attribute(
1466 """Name of the repoview that is active on this repo."""
1466 """Name of the repoview that is active on this repo."""
1467 )
1467 )
1468
1468
1469 wvfs = interfaceutil.Attribute(
1469 wvfs = interfaceutil.Attribute(
1470 """VFS used to access the working directory."""
1470 """VFS used to access the working directory."""
1471 )
1471 )
1472
1472
1473 vfs = interfaceutil.Attribute(
1473 vfs = interfaceutil.Attribute(
1474 """VFS rooted at the .hg directory.
1474 """VFS rooted at the .hg directory.
1475
1475
1476 Used to access repository data not in the store.
1476 Used to access repository data not in the store.
1477 """
1477 """
1478 )
1478 )
1479
1479
1480 svfs = interfaceutil.Attribute(
1480 svfs = interfaceutil.Attribute(
1481 """VFS rooted at the store.
1481 """VFS rooted at the store.
1482
1482
1483 Used to access repository data in the store. Typically .hg/store.
1483 Used to access repository data in the store. Typically .hg/store.
1484 But can point elsewhere if the store is shared.
1484 But can point elsewhere if the store is shared.
1485 """
1485 """
1486 )
1486 )
1487
1487
1488 root = interfaceutil.Attribute(
1488 root = interfaceutil.Attribute(
1489 """Path to the root of the working directory."""
1489 """Path to the root of the working directory."""
1490 )
1490 )
1491
1491
1492 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1492 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1493
1493
1494 origroot = interfaceutil.Attribute(
1494 origroot = interfaceutil.Attribute(
1495 """The filesystem path that was used to construct the repo."""
1495 """The filesystem path that was used to construct the repo."""
1496 )
1496 )
1497
1497
1498 auditor = interfaceutil.Attribute(
1498 auditor = interfaceutil.Attribute(
1499 """A pathauditor for the working directory.
1499 """A pathauditor for the working directory.
1500
1500
1501 This checks if a path refers to a nested repository.
1501 This checks if a path refers to a nested repository.
1502
1502
1503 Operates on the filesystem.
1503 Operates on the filesystem.
1504 """
1504 """
1505 )
1505 )
1506
1506
1507 nofsauditor = interfaceutil.Attribute(
1507 nofsauditor = interfaceutil.Attribute(
1508 """A pathauditor for the working directory.
1508 """A pathauditor for the working directory.
1509
1509
1510 This is like ``auditor`` except it doesn't do filesystem checks.
1510 This is like ``auditor`` except it doesn't do filesystem checks.
1511 """
1511 """
1512 )
1512 )
1513
1513
1514 baseui = interfaceutil.Attribute(
1514 baseui = interfaceutil.Attribute(
1515 """Original ui instance passed into constructor."""
1515 """Original ui instance passed into constructor."""
1516 )
1516 )
1517
1517
1518 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1518 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1519
1519
1520 sharedpath = interfaceutil.Attribute(
1520 sharedpath = interfaceutil.Attribute(
1521 """Path to the .hg directory of the repo this repo was shared from."""
1521 """Path to the .hg directory of the repo this repo was shared from."""
1522 )
1522 )
1523
1523
1524 store = interfaceutil.Attribute("""A store instance.""")
1524 store = interfaceutil.Attribute("""A store instance.""")
1525
1525
1526 spath = interfaceutil.Attribute("""Path to the store.""")
1526 spath = interfaceutil.Attribute("""Path to the store.""")
1527
1527
1528 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1528 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1529
1529
1530 cachevfs = interfaceutil.Attribute(
1530 cachevfs = interfaceutil.Attribute(
1531 """A VFS used to access the cache directory.
1531 """A VFS used to access the cache directory.
1532
1532
1533 Typically .hg/cache.
1533 Typically .hg/cache.
1534 """
1534 """
1535 )
1535 )
1536
1536
1537 wcachevfs = interfaceutil.Attribute(
1537 wcachevfs = interfaceutil.Attribute(
1538 """A VFS used to access the cache directory dedicated to working copy
1538 """A VFS used to access the cache directory dedicated to working copy
1539
1539
1540 Typically .hg/wcache.
1540 Typically .hg/wcache.
1541 """
1541 """
1542 )
1542 )
1543
1543
1544 filteredrevcache = interfaceutil.Attribute(
1544 filteredrevcache = interfaceutil.Attribute(
1545 """Holds sets of revisions to be filtered."""
1545 """Holds sets of revisions to be filtered."""
1546 )
1546 )
1547
1547
1548 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1548 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1549
1549
1550 filecopiesmode = interfaceutil.Attribute(
1550 filecopiesmode = interfaceutil.Attribute(
1551 """The way files copies should be dealt with in this repo."""
1551 """The way files copies should be dealt with in this repo."""
1552 )
1552 )
1553
1553
1554 def close():
1554 def close():
1555 """Close the handle on this repository."""
1555 """Close the handle on this repository."""
1556
1556
1557 def peer():
1557 def peer():
1558 """Obtain an object conforming to the ``peer`` interface."""
1558 """Obtain an object conforming to the ``peer`` interface."""
1559
1559
1560 def unfiltered():
1560 def unfiltered():
1561 """Obtain an unfiltered/raw view of this repo."""
1561 """Obtain an unfiltered/raw view of this repo."""
1562
1562
1563 def filtered(name, visibilityexceptions=None):
1563 def filtered(name, visibilityexceptions=None):
1564 """Obtain a named view of this repository."""
1564 """Obtain a named view of this repository."""
1565
1565
1566 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1566 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1567
1567
1568 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1568 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1569
1569
1570 manifestlog = interfaceutil.Attribute(
1570 manifestlog = interfaceutil.Attribute(
1571 """An instance conforming to the ``imanifestlog`` interface.
1571 """An instance conforming to the ``imanifestlog`` interface.
1572
1572
1573 Provides access to manifests for the repository.
1573 Provides access to manifests for the repository.
1574 """
1574 """
1575 )
1575 )
1576
1576
1577 dirstate = interfaceutil.Attribute("""Working directory state.""")
1577 dirstate = interfaceutil.Attribute("""Working directory state.""")
1578
1578
1579 narrowpats = interfaceutil.Attribute(
1579 narrowpats = interfaceutil.Attribute(
1580 """Matcher patterns for this repository's narrowspec."""
1580 """Matcher patterns for this repository's narrowspec."""
1581 )
1581 )
1582
1582
1583 def narrowmatch(match=None, includeexact=False):
1583 def narrowmatch(match=None, includeexact=False):
1584 """Obtain a matcher for the narrowspec."""
1584 """Obtain a matcher for the narrowspec."""
1585
1585
1586 def setnarrowpats(newincludes, newexcludes):
1586 def setnarrowpats(newincludes, newexcludes):
1587 """Define the narrowspec for this repository."""
1587 """Define the narrowspec for this repository."""
1588
1588
1589 def __getitem__(changeid):
1589 def __getitem__(changeid):
1590 """Try to resolve a changectx."""
1590 """Try to resolve a changectx."""
1591
1591
1592 def __contains__(changeid):
1592 def __contains__(changeid):
1593 """Whether a changeset exists."""
1593 """Whether a changeset exists."""
1594
1594
1595 def __nonzero__():
1595 def __nonzero__():
1596 """Always returns True."""
1596 """Always returns True."""
1597 return True
1597 return True
1598
1598
1599 __bool__ = __nonzero__
1599 __bool__ = __nonzero__
1600
1600
1601 def __len__():
1601 def __len__():
1602 """Returns the number of changesets in the repo."""
1602 """Returns the number of changesets in the repo."""
1603
1603
1604 def __iter__():
1604 def __iter__():
1605 """Iterate over revisions in the changelog."""
1605 """Iterate over revisions in the changelog."""
1606
1606
1607 def revs(expr, *args):
1607 def revs(expr, *args):
1608 """Evaluate a revset.
1608 """Evaluate a revset.
1609
1609
1610 Emits revisions.
1610 Emits revisions.
1611 """
1611 """
1612
1612
1613 def set(expr, *args):
1613 def set(expr, *args):
1614 """Evaluate a revset.
1614 """Evaluate a revset.
1615
1615
1616 Emits changectx instances.
1616 Emits changectx instances.
1617 """
1617 """
1618
1618
1619 def anyrevs(specs, user=False, localalias=None):
1619 def anyrevs(specs, user=False, localalias=None):
1620 """Find revisions matching one of the given revsets."""
1620 """Find revisions matching one of the given revsets."""
1621
1621
1622 def url():
1622 def url():
1623 """Returns a string representing the location of this repo."""
1623 """Returns a string representing the location of this repo."""
1624
1624
1625 def hook(name, throw=False, **args):
1625 def hook(name, throw=False, **args):
1626 """Call a hook."""
1626 """Call a hook."""
1627
1627
1628 def tags():
1628 def tags():
1629 """Return a mapping of tag to node."""
1629 """Return a mapping of tag to node."""
1630
1630
1631 def tagtype(tagname):
1631 def tagtype(tagname):
1632 """Return the type of a given tag."""
1632 """Return the type of a given tag."""
1633
1633
1634 def tagslist():
1634 def tagslist():
1635 """Return a list of tags ordered by revision."""
1635 """Return a list of tags ordered by revision."""
1636
1636
1637 def nodetags(node):
1637 def nodetags(node):
1638 """Return the tags associated with a node."""
1638 """Return the tags associated with a node."""
1639
1639
1640 def nodebookmarks(node):
1640 def nodebookmarks(node):
1641 """Return the list of bookmarks pointing to the specified node."""
1641 """Return the list of bookmarks pointing to the specified node."""
1642
1642
1643 def branchmap():
1643 def branchmap():
1644 """Return a mapping of branch to heads in that branch."""
1644 """Return a mapping of branch to heads in that branch."""
1645
1645
1646 def revbranchcache():
1646 def revbranchcache():
1647 pass
1647 pass
1648
1648
1649 def branchtip(branchtip, ignoremissing=False):
1649 def branchtip(branchtip, ignoremissing=False):
1650 """Return the tip node for a given branch."""
1650 """Return the tip node for a given branch."""
1651
1651
1652 def lookup(key):
1652 def lookup(key):
1653 """Resolve the node for a revision."""
1653 """Resolve the node for a revision."""
1654
1654
1655 def lookupbranch(key):
1655 def lookupbranch(key):
1656 """Look up the branch name of the given revision or branch name."""
1656 """Look up the branch name of the given revision or branch name."""
1657
1657
1658 def known(nodes):
1658 def known(nodes):
1659 """Determine whether a series of nodes is known.
1659 """Determine whether a series of nodes is known.
1660
1660
1661 Returns a list of bools.
1661 Returns a list of bools.
1662 """
1662 """
1663
1663
1664 def local():
1664 def local():
1665 """Whether the repository is local."""
1665 """Whether the repository is local."""
1666 return True
1666 return True
1667
1667
1668 def publishing():
1668 def publishing():
1669 """Whether the repository is a publishing repository."""
1669 """Whether the repository is a publishing repository."""
1670
1670
1671 def cancopy():
1671 def cancopy():
1672 pass
1672 pass
1673
1673
1674 def shared():
1674 def shared():
1675 """The type of shared repository or None."""
1675 """The type of shared repository or None."""
1676
1676
1677 def wjoin(f, *insidef):
1677 def wjoin(f, *insidef):
1678 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1678 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1679
1679
1680 def setparents(p1, p2):
1680 def setparents(p1, p2):
1681 """Set the parent nodes of the working directory."""
1681 """Set the parent nodes of the working directory."""
1682
1682
1683 def filectx(path, changeid=None, fileid=None):
1683 def filectx(path, changeid=None, fileid=None):
1684 """Obtain a filectx for the given file revision."""
1684 """Obtain a filectx for the given file revision."""
1685
1685
1686 def getcwd():
1686 def getcwd():
1687 """Obtain the current working directory from the dirstate."""
1687 """Obtain the current working directory from the dirstate."""
1688
1688
1689 def pathto(f, cwd=None):
1689 def pathto(f, cwd=None):
1690 """Obtain the relative path to a file."""
1690 """Obtain the relative path to a file."""
1691
1691
1692 def adddatafilter(name, fltr):
1692 def adddatafilter(name, fltr):
1693 pass
1693 pass
1694
1694
1695 def wread(filename):
1695 def wread(filename):
1696 """Read a file from wvfs, using data filters."""
1696 """Read a file from wvfs, using data filters."""
1697
1697
1698 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1698 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1699 """Write data to a file in the wvfs, using data filters."""
1699 """Write data to a file in the wvfs, using data filters."""
1700
1700
1701 def wwritedata(filename, data):
1701 def wwritedata(filename, data):
1702 """Resolve data for writing to the wvfs, using data filters."""
1702 """Resolve data for writing to the wvfs, using data filters."""
1703
1703
1704 def currenttransaction():
1704 def currenttransaction():
1705 """Obtain the current transaction instance or None."""
1705 """Obtain the current transaction instance or None."""
1706
1706
1707 def transaction(desc, report=None):
1707 def transaction(desc, report=None):
1708 """Open a new transaction to write to the repository."""
1708 """Open a new transaction to write to the repository."""
1709
1709
1710 def undofiles():
1710 def undofiles():
1711 """Returns a list of (vfs, path) for files to undo transactions."""
1711 """Returns a list of (vfs, path) for files to undo transactions."""
1712
1712
1713 def recover():
1713 def recover():
1714 """Roll back an interrupted transaction."""
1714 """Roll back an interrupted transaction."""
1715
1715
1716 def rollback(dryrun=False, force=False):
1716 def rollback(dryrun=False, force=False):
1717 """Undo the last transaction.
1717 """Undo the last transaction.
1718
1718
1719 DANGEROUS.
1719 DANGEROUS.
1720 """
1720 """
1721
1721
1722 def updatecaches(tr=None, full=False):
1722 def updatecaches(tr=None, full=False):
1723 """Warm repo caches."""
1723 """Warm repo caches."""
1724
1724
1725 def invalidatecaches():
1725 def invalidatecaches():
1726 """Invalidate cached data due to the repository mutating."""
1726 """Invalidate cached data due to the repository mutating."""
1727
1727
1728 def invalidatevolatilesets():
1728 def invalidatevolatilesets():
1729 pass
1729 pass
1730
1730
1731 def invalidatedirstate():
1731 def invalidatedirstate():
1732 """Invalidate the dirstate."""
1732 """Invalidate the dirstate."""
1733
1733
1734 def invalidate(clearfilecache=False):
1734 def invalidate(clearfilecache=False):
1735 pass
1735 pass
1736
1736
1737 def invalidateall():
1737 def invalidateall():
1738 pass
1738 pass
1739
1739
1740 def lock(wait=True):
1740 def lock(wait=True):
1741 """Lock the repository store and return a lock instance."""
1741 """Lock the repository store and return a lock instance."""
1742
1742
1743 def wlock(wait=True):
1743 def wlock(wait=True):
1744 """Lock the non-store parts of the repository."""
1744 """Lock the non-store parts of the repository."""
1745
1745
1746 def currentwlock():
1746 def currentwlock():
1747 """Return the wlock if it's held or None."""
1747 """Return the wlock if it's held or None."""
1748
1748
1749 def checkcommitpatterns(wctx, vdirs, match, status, fail):
1749 def checkcommitpatterns(wctx, match, status, fail):
1750 pass
1750 pass
1751
1751
1752 def commit(
1752 def commit(
1753 text=b'',
1753 text=b'',
1754 user=None,
1754 user=None,
1755 date=None,
1755 date=None,
1756 match=None,
1756 match=None,
1757 force=False,
1757 force=False,
1758 editor=False,
1758 editor=False,
1759 extra=None,
1759 extra=None,
1760 ):
1760 ):
1761 """Add a new revision to the repository."""
1761 """Add a new revision to the repository."""
1762
1762
1763 def commitctx(ctx, error=False, origctx=None):
1763 def commitctx(ctx, error=False, origctx=None):
1764 """Commit a commitctx instance to the repository."""
1764 """Commit a commitctx instance to the repository."""
1765
1765
1766 def destroying():
1766 def destroying():
1767 """Inform the repository that nodes are about to be destroyed."""
1767 """Inform the repository that nodes are about to be destroyed."""
1768
1768
1769 def destroyed():
1769 def destroyed():
1770 """Inform the repository that nodes have been destroyed."""
1770 """Inform the repository that nodes have been destroyed."""
1771
1771
1772 def status(
1772 def status(
1773 node1=b'.',
1773 node1=b'.',
1774 node2=None,
1774 node2=None,
1775 match=None,
1775 match=None,
1776 ignored=False,
1776 ignored=False,
1777 clean=False,
1777 clean=False,
1778 unknown=False,
1778 unknown=False,
1779 listsubrepos=False,
1779 listsubrepos=False,
1780 ):
1780 ):
1781 """Convenience method to call repo[x].status()."""
1781 """Convenience method to call repo[x].status()."""
1782
1782
1783 def addpostdsstatus(ps):
1783 def addpostdsstatus(ps):
1784 pass
1784 pass
1785
1785
1786 def postdsstatus():
1786 def postdsstatus():
1787 pass
1787 pass
1788
1788
1789 def clearpostdsstatus():
1789 def clearpostdsstatus():
1790 pass
1790 pass
1791
1791
1792 def heads(start=None):
1792 def heads(start=None):
1793 """Obtain list of nodes that are DAG heads."""
1793 """Obtain list of nodes that are DAG heads."""
1794
1794
1795 def branchheads(branch=None, start=None, closed=False):
1795 def branchheads(branch=None, start=None, closed=False):
1796 pass
1796 pass
1797
1797
1798 def branches(nodes):
1798 def branches(nodes):
1799 pass
1799 pass
1800
1800
1801 def between(pairs):
1801 def between(pairs):
1802 pass
1802 pass
1803
1803
1804 def checkpush(pushop):
1804 def checkpush(pushop):
1805 pass
1805 pass
1806
1806
1807 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1807 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1808
1808
1809 def pushkey(namespace, key, old, new):
1809 def pushkey(namespace, key, old, new):
1810 pass
1810 pass
1811
1811
1812 def listkeys(namespace):
1812 def listkeys(namespace):
1813 pass
1813 pass
1814
1814
1815 def debugwireargs(one, two, three=None, four=None, five=None):
1815 def debugwireargs(one, two, three=None, four=None, five=None):
1816 pass
1816 pass
1817
1817
1818 def savecommitmessage(text):
1818 def savecommitmessage(text):
1819 pass
1819 pass
1820
1820
1821
1821
1822 class completelocalrepository(
1822 class completelocalrepository(
1823 ilocalrepositorymain, ilocalrepositoryfilestorage
1823 ilocalrepositorymain, ilocalrepositoryfilestorage
1824 ):
1824 ):
1825 """Complete interface for a local repository."""
1825 """Complete interface for a local repository."""
1826
1826
1827
1827
1828 class iwireprotocolcommandcacher(interfaceutil.Interface):
1828 class iwireprotocolcommandcacher(interfaceutil.Interface):
1829 """Represents a caching backend for wire protocol commands.
1829 """Represents a caching backend for wire protocol commands.
1830
1830
1831 Wire protocol version 2 supports transparent caching of many commands.
1831 Wire protocol version 2 supports transparent caching of many commands.
1832 To leverage this caching, servers can activate objects that cache
1832 To leverage this caching, servers can activate objects that cache
1833 command responses. Objects handle both cache writing and reading.
1833 command responses. Objects handle both cache writing and reading.
1834 This interface defines how that response caching mechanism works.
1834 This interface defines how that response caching mechanism works.
1835
1835
1836 Wire protocol version 2 commands emit a series of objects that are
1836 Wire protocol version 2 commands emit a series of objects that are
1837 serialized and sent to the client. The caching layer exists between
1837 serialized and sent to the client. The caching layer exists between
1838 the invocation of the command function and the sending of its output
1838 the invocation of the command function and the sending of its output
1839 objects to an output layer.
1839 objects to an output layer.
1840
1840
1841 Instances of this interface represent a binding to a cache that
1841 Instances of this interface represent a binding to a cache that
1842 can serve a response (in place of calling a command function) and/or
1842 can serve a response (in place of calling a command function) and/or
1843 write responses to a cache for subsequent use.
1843 write responses to a cache for subsequent use.
1844
1844
1845 When a command request arrives, the following happens with regards
1845 When a command request arrives, the following happens with regards
1846 to this interface:
1846 to this interface:
1847
1847
1848 1. The server determines whether the command request is cacheable.
1848 1. The server determines whether the command request is cacheable.
1849 2. If it is, an instance of this interface is spawned.
1849 2. If it is, an instance of this interface is spawned.
1850 3. The cacher is activated in a context manager (``__enter__`` is called).
1850 3. The cacher is activated in a context manager (``__enter__`` is called).
1851 4. A cache *key* for that request is derived. This will call the
1851 4. A cache *key* for that request is derived. This will call the
1852 instance's ``adjustcachekeystate()`` method so the derivation
1852 instance's ``adjustcachekeystate()`` method so the derivation
1853 can be influenced.
1853 can be influenced.
1854 5. The cacher is informed of the derived cache key via a call to
1854 5. The cacher is informed of the derived cache key via a call to
1855 ``setcachekey()``.
1855 ``setcachekey()``.
1856 6. The cacher's ``lookup()`` method is called to test for presence of
1856 6. The cacher's ``lookup()`` method is called to test for presence of
1857 the derived key in the cache.
1857 the derived key in the cache.
1858 7. If ``lookup()`` returns a hit, that cached result is used in place
1858 7. If ``lookup()`` returns a hit, that cached result is used in place
1859 of invoking the command function. ``__exit__`` is called and the instance
1859 of invoking the command function. ``__exit__`` is called and the instance
1860 is discarded.
1860 is discarded.
1861 8. The command function is invoked.
1861 8. The command function is invoked.
1862 9. ``onobject()`` is called for each object emitted by the command
1862 9. ``onobject()`` is called for each object emitted by the command
1863 function.
1863 function.
1864 10. After the final object is seen, ``onfinished()`` is called.
1864 10. After the final object is seen, ``onfinished()`` is called.
1865 11. ``__exit__`` is called to signal the end of use of the instance.
1865 11. ``__exit__`` is called to signal the end of use of the instance.
1866
1866
1867 Cache *key* derivation can be influenced by the instance.
1867 Cache *key* derivation can be influenced by the instance.
1868
1868
1869 Cache keys are initially derived by a deterministic representation of
1869 Cache keys are initially derived by a deterministic representation of
1870 the command request. This includes the command name, arguments, protocol
1870 the command request. This includes the command name, arguments, protocol
1871 version, etc. This initial key derivation is performed by CBOR-encoding a
1871 version, etc. This initial key derivation is performed by CBOR-encoding a
1872 data structure and feeding that output into a hasher.
1872 data structure and feeding that output into a hasher.
1873
1873
1874 Instances of this interface can influence this initial key derivation
1874 Instances of this interface can influence this initial key derivation
1875 via ``adjustcachekeystate()``.
1875 via ``adjustcachekeystate()``.
1876
1876
1877 The instance is informed of the derived cache key via a call to
1877 The instance is informed of the derived cache key via a call to
1878 ``setcachekey()``. The instance must store the key locally so it can
1878 ``setcachekey()``. The instance must store the key locally so it can
1879 be consulted on subsequent operations that may require it.
1879 be consulted on subsequent operations that may require it.
1880
1880
1881 When constructed, the instance has access to a callable that can be used
1881 When constructed, the instance has access to a callable that can be used
1882 for encoding response objects. This callable receives as its single
1882 for encoding response objects. This callable receives as its single
1883 argument an object emitted by a command function. It returns an iterable
1883 argument an object emitted by a command function. It returns an iterable
1884 of bytes chunks representing the encoded object. Unless the cacher is
1884 of bytes chunks representing the encoded object. Unless the cacher is
1885 caching native Python objects in memory or has a way of reconstructing
1885 caching native Python objects in memory or has a way of reconstructing
1886 the original Python objects, implementations typically call this function
1886 the original Python objects, implementations typically call this function
1887 to produce bytes from the output objects and then store those bytes in
1887 to produce bytes from the output objects and then store those bytes in
1888 the cache. When it comes time to re-emit those bytes, they are wrapped
1888 the cache. When it comes time to re-emit those bytes, they are wrapped
1889 in a ``wireprototypes.encodedresponse`` instance to tell the output
1889 in a ``wireprototypes.encodedresponse`` instance to tell the output
1890 layer that they are pre-encoded.
1890 layer that they are pre-encoded.
1891
1891
1892 When receiving the objects emitted by the command function, instances
1892 When receiving the objects emitted by the command function, instances
1893 can choose what to do with those objects. The simplest thing to do is
1893 can choose what to do with those objects. The simplest thing to do is
1894 re-emit the original objects. They will be forwarded to the output
1894 re-emit the original objects. They will be forwarded to the output
1895 layer and will be processed as if the cacher did not exist.
1895 layer and will be processed as if the cacher did not exist.
1896
1896
1897 Implementations could also choose to not emit objects - instead locally
1897 Implementations could also choose to not emit objects - instead locally
1898 buffering objects or their encoded representation. They could then emit
1898 buffering objects or their encoded representation. They could then emit
1899 a single "coalesced" object when ``onfinished()`` is called. In
1899 a single "coalesced" object when ``onfinished()`` is called. In
1900 this way, the implementation would function as a filtering layer of
1900 this way, the implementation would function as a filtering layer of
1901 sorts.
1901 sorts.
1902
1902
1903 When caching objects, typically the encoded form of the object will
1903 When caching objects, typically the encoded form of the object will
1904 be stored. Keep in mind that if the original object is forwarded to
1904 be stored. Keep in mind that if the original object is forwarded to
1905 the output layer, it will need to be encoded there as well. For large
1905 the output layer, it will need to be encoded there as well. For large
1906 output, this redundant encoding could add overhead. Implementations
1906 output, this redundant encoding could add overhead. Implementations
1907 could wrap the encoded object data in ``wireprototypes.encodedresponse``
1907 could wrap the encoded object data in ``wireprototypes.encodedresponse``
1908 instances to avoid this overhead.
1908 instances to avoid this overhead.
1909 """
1909 """
1910
1910
1911 def __enter__():
1911 def __enter__():
1912 """Marks the instance as active.
1912 """Marks the instance as active.
1913
1913
1914 Should return self.
1914 Should return self.
1915 """
1915 """
1916
1916
1917 def __exit__(exctype, excvalue, exctb):
1917 def __exit__(exctype, excvalue, exctb):
1918 """Called when cacher is no longer used.
1918 """Called when cacher is no longer used.
1919
1919
1920 This can be used by implementations to perform cleanup actions (e.g.
1920 This can be used by implementations to perform cleanup actions (e.g.
1921 disconnecting network sockets, aborting a partially cached response.
1921 disconnecting network sockets, aborting a partially cached response.
1922 """
1922 """
1923
1923
1924 def adjustcachekeystate(state):
1924 def adjustcachekeystate(state):
1925 """Influences cache key derivation by adjusting state to derive key.
1925 """Influences cache key derivation by adjusting state to derive key.
1926
1926
1927 A dict defining the state used to derive the cache key is passed.
1927 A dict defining the state used to derive the cache key is passed.
1928
1928
1929 Implementations can modify this dict to record additional state that
1929 Implementations can modify this dict to record additional state that
1930 is wanted to influence key derivation.
1930 is wanted to influence key derivation.
1931
1931
1932 Implementations are *highly* encouraged to not modify or delete
1932 Implementations are *highly* encouraged to not modify or delete
1933 existing keys.
1933 existing keys.
1934 """
1934 """
1935
1935
1936 def setcachekey(key):
1936 def setcachekey(key):
1937 """Record the derived cache key for this request.
1937 """Record the derived cache key for this request.
1938
1938
1939 Instances may mutate the key for internal usage, as desired. e.g.
1939 Instances may mutate the key for internal usage, as desired. e.g.
1940 instances may wish to prepend the repo name, introduce path
1940 instances may wish to prepend the repo name, introduce path
1941 components for filesystem or URL addressing, etc. Behavior is up to
1941 components for filesystem or URL addressing, etc. Behavior is up to
1942 the cache.
1942 the cache.
1943
1943
1944 Returns a bool indicating if the request is cacheable by this
1944 Returns a bool indicating if the request is cacheable by this
1945 instance.
1945 instance.
1946 """
1946 """
1947
1947
1948 def lookup():
1948 def lookup():
1949 """Attempt to resolve an entry in the cache.
1949 """Attempt to resolve an entry in the cache.
1950
1950
1951 The instance is instructed to look for the cache key that it was
1951 The instance is instructed to look for the cache key that it was
1952 informed about via the call to ``setcachekey()``.
1952 informed about via the call to ``setcachekey()``.
1953
1953
1954 If there's no cache hit or the cacher doesn't wish to use the cached
1954 If there's no cache hit or the cacher doesn't wish to use the cached
1955 entry, ``None`` should be returned.
1955 entry, ``None`` should be returned.
1956
1956
1957 Else, a dict defining the cached result should be returned. The
1957 Else, a dict defining the cached result should be returned. The
1958 dict may have the following keys:
1958 dict may have the following keys:
1959
1959
1960 objs
1960 objs
1961 An iterable of objects that should be sent to the client. That
1961 An iterable of objects that should be sent to the client. That
1962 iterable of objects is expected to be what the command function
1962 iterable of objects is expected to be what the command function
1963 would return if invoked or an equivalent representation thereof.
1963 would return if invoked or an equivalent representation thereof.
1964 """
1964 """
1965
1965
1966 def onobject(obj):
1966 def onobject(obj):
1967 """Called when a new object is emitted from the command function.
1967 """Called when a new object is emitted from the command function.
1968
1968
1969 Receives as its argument the object that was emitted from the
1969 Receives as its argument the object that was emitted from the
1970 command function.
1970 command function.
1971
1971
1972 This method returns an iterator of objects to forward to the output
1972 This method returns an iterator of objects to forward to the output
1973 layer. The easiest implementation is a generator that just
1973 layer. The easiest implementation is a generator that just
1974 ``yield obj``.
1974 ``yield obj``.
1975 """
1975 """
1976
1976
1977 def onfinished():
1977 def onfinished():
1978 """Called after all objects have been emitted from the command function.
1978 """Called after all objects have been emitted from the command function.
1979
1979
1980 Implementations should return an iterator of objects to forward to
1980 Implementations should return an iterator of objects to forward to
1981 the output layer.
1981 the output layer.
1982
1982
1983 This method can be a generator.
1983 This method can be a generator.
1984 """
1984 """
@@ -1,3723 +1,3721 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 os
12 import os
13 import random
13 import random
14 import sys
14 import sys
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 bin,
20 bin,
21 hex,
21 hex,
22 nullid,
22 nullid,
23 nullrev,
23 nullrev,
24 short,
24 short,
25 )
25 )
26 from .pycompat import (
26 from .pycompat import (
27 delattr,
27 delattr,
28 getattr,
28 getattr,
29 )
29 )
30 from . import (
30 from . import (
31 bookmarks,
31 bookmarks,
32 branchmap,
32 branchmap,
33 bundle2,
33 bundle2,
34 changegroup,
34 changegroup,
35 color,
35 color,
36 context,
36 context,
37 dirstate,
37 dirstate,
38 dirstateguard,
38 dirstateguard,
39 discovery,
39 discovery,
40 encoding,
40 encoding,
41 error,
41 error,
42 exchange,
42 exchange,
43 extensions,
43 extensions,
44 filelog,
44 filelog,
45 hook,
45 hook,
46 lock as lockmod,
46 lock as lockmod,
47 match as matchmod,
47 match as matchmod,
48 merge as mergemod,
48 merge as mergemod,
49 mergeutil,
49 mergeutil,
50 namespaces,
50 namespaces,
51 narrowspec,
51 narrowspec,
52 obsolete,
52 obsolete,
53 pathutil,
53 pathutil,
54 phases,
54 phases,
55 pushkey,
55 pushkey,
56 pycompat,
56 pycompat,
57 repoview,
57 repoview,
58 revset,
58 revset,
59 revsetlang,
59 revsetlang,
60 scmutil,
60 scmutil,
61 sparse,
61 sparse,
62 store as storemod,
62 store as storemod,
63 subrepoutil,
63 subrepoutil,
64 tags as tagsmod,
64 tags as tagsmod,
65 transaction,
65 transaction,
66 txnutil,
66 txnutil,
67 util,
67 util,
68 vfs as vfsmod,
68 vfs as vfsmod,
69 )
69 )
70
70
71 from .interfaces import (
71 from .interfaces import (
72 repository,
72 repository,
73 util as interfaceutil,
73 util as interfaceutil,
74 )
74 )
75
75
76 from .utils import (
76 from .utils import (
77 procutil,
77 procutil,
78 stringutil,
78 stringutil,
79 )
79 )
80
80
81 from .revlogutils import constants as revlogconst
81 from .revlogutils import constants as revlogconst
82
82
83 release = lockmod.release
83 release = lockmod.release
84 urlerr = util.urlerr
84 urlerr = util.urlerr
85 urlreq = util.urlreq
85 urlreq = util.urlreq
86
86
87 # set of (path, vfs-location) tuples. vfs-location is:
87 # set of (path, vfs-location) tuples. vfs-location is:
88 # - 'plain for vfs relative paths
88 # - 'plain for vfs relative paths
89 # - '' for svfs relative paths
89 # - '' for svfs relative paths
90 _cachedfiles = set()
90 _cachedfiles = set()
91
91
92
92
93 class _basefilecache(scmutil.filecache):
93 class _basefilecache(scmutil.filecache):
94 """All filecache usage on repo are done for logic that should be unfiltered
94 """All filecache usage on repo are done for logic that should be unfiltered
95 """
95 """
96
96
97 def __get__(self, repo, type=None):
97 def __get__(self, repo, type=None):
98 if repo is None:
98 if repo is None:
99 return self
99 return self
100 # proxy to unfiltered __dict__ since filtered repo has no entry
100 # proxy to unfiltered __dict__ since filtered repo has no entry
101 unfi = repo.unfiltered()
101 unfi = repo.unfiltered()
102 try:
102 try:
103 return unfi.__dict__[self.sname]
103 return unfi.__dict__[self.sname]
104 except KeyError:
104 except KeyError:
105 pass
105 pass
106 return super(_basefilecache, self).__get__(unfi, type)
106 return super(_basefilecache, self).__get__(unfi, type)
107
107
108 def set(self, repo, value):
108 def set(self, repo, value):
109 return super(_basefilecache, self).set(repo.unfiltered(), value)
109 return super(_basefilecache, self).set(repo.unfiltered(), value)
110
110
111
111
112 class repofilecache(_basefilecache):
112 class repofilecache(_basefilecache):
113 """filecache for files in .hg but outside of .hg/store"""
113 """filecache for files in .hg but outside of .hg/store"""
114
114
115 def __init__(self, *paths):
115 def __init__(self, *paths):
116 super(repofilecache, self).__init__(*paths)
116 super(repofilecache, self).__init__(*paths)
117 for path in paths:
117 for path in paths:
118 _cachedfiles.add((path, b'plain'))
118 _cachedfiles.add((path, b'plain'))
119
119
120 def join(self, obj, fname):
120 def join(self, obj, fname):
121 return obj.vfs.join(fname)
121 return obj.vfs.join(fname)
122
122
123
123
124 class storecache(_basefilecache):
124 class storecache(_basefilecache):
125 """filecache for files in the store"""
125 """filecache for files in the store"""
126
126
127 def __init__(self, *paths):
127 def __init__(self, *paths):
128 super(storecache, self).__init__(*paths)
128 super(storecache, self).__init__(*paths)
129 for path in paths:
129 for path in paths:
130 _cachedfiles.add((path, b''))
130 _cachedfiles.add((path, b''))
131
131
132 def join(self, obj, fname):
132 def join(self, obj, fname):
133 return obj.sjoin(fname)
133 return obj.sjoin(fname)
134
134
135
135
136 class mixedrepostorecache(_basefilecache):
136 class mixedrepostorecache(_basefilecache):
137 """filecache for a mix files in .hg/store and outside"""
137 """filecache for a mix files in .hg/store and outside"""
138
138
139 def __init__(self, *pathsandlocations):
139 def __init__(self, *pathsandlocations):
140 # scmutil.filecache only uses the path for passing back into our
140 # scmutil.filecache only uses the path for passing back into our
141 # join(), so we can safely pass a list of paths and locations
141 # join(), so we can safely pass a list of paths and locations
142 super(mixedrepostorecache, self).__init__(*pathsandlocations)
142 super(mixedrepostorecache, self).__init__(*pathsandlocations)
143 _cachedfiles.update(pathsandlocations)
143 _cachedfiles.update(pathsandlocations)
144
144
145 def join(self, obj, fnameandlocation):
145 def join(self, obj, fnameandlocation):
146 fname, location = fnameandlocation
146 fname, location = fnameandlocation
147 if location == b'plain':
147 if location == b'plain':
148 return obj.vfs.join(fname)
148 return obj.vfs.join(fname)
149 else:
149 else:
150 if location != b'':
150 if location != b'':
151 raise error.ProgrammingError(
151 raise error.ProgrammingError(
152 b'unexpected location: %s' % location
152 b'unexpected location: %s' % location
153 )
153 )
154 return obj.sjoin(fname)
154 return obj.sjoin(fname)
155
155
156
156
157 def isfilecached(repo, name):
157 def isfilecached(repo, name):
158 """check if a repo has already cached "name" filecache-ed property
158 """check if a repo has already cached "name" filecache-ed property
159
159
160 This returns (cachedobj-or-None, iscached) tuple.
160 This returns (cachedobj-or-None, iscached) tuple.
161 """
161 """
162 cacheentry = repo.unfiltered()._filecache.get(name, None)
162 cacheentry = repo.unfiltered()._filecache.get(name, None)
163 if not cacheentry:
163 if not cacheentry:
164 return None, False
164 return None, False
165 return cacheentry.obj, True
165 return cacheentry.obj, True
166
166
167
167
168 class unfilteredpropertycache(util.propertycache):
168 class unfilteredpropertycache(util.propertycache):
169 """propertycache that apply to unfiltered repo only"""
169 """propertycache that apply to unfiltered repo only"""
170
170
171 def __get__(self, repo, type=None):
171 def __get__(self, repo, type=None):
172 unfi = repo.unfiltered()
172 unfi = repo.unfiltered()
173 if unfi is repo:
173 if unfi is repo:
174 return super(unfilteredpropertycache, self).__get__(unfi)
174 return super(unfilteredpropertycache, self).__get__(unfi)
175 return getattr(unfi, self.name)
175 return getattr(unfi, self.name)
176
176
177
177
178 class filteredpropertycache(util.propertycache):
178 class filteredpropertycache(util.propertycache):
179 """propertycache that must take filtering in account"""
179 """propertycache that must take filtering in account"""
180
180
181 def cachevalue(self, obj, value):
181 def cachevalue(self, obj, value):
182 object.__setattr__(obj, self.name, value)
182 object.__setattr__(obj, self.name, value)
183
183
184
184
185 def hasunfilteredcache(repo, name):
185 def hasunfilteredcache(repo, name):
186 """check if a repo has an unfilteredpropertycache value for <name>"""
186 """check if a repo has an unfilteredpropertycache value for <name>"""
187 return name in vars(repo.unfiltered())
187 return name in vars(repo.unfiltered())
188
188
189
189
190 def unfilteredmethod(orig):
190 def unfilteredmethod(orig):
191 """decorate method that always need to be run on unfiltered version"""
191 """decorate method that always need to be run on unfiltered version"""
192
192
193 def wrapper(repo, *args, **kwargs):
193 def wrapper(repo, *args, **kwargs):
194 return orig(repo.unfiltered(), *args, **kwargs)
194 return orig(repo.unfiltered(), *args, **kwargs)
195
195
196 return wrapper
196 return wrapper
197
197
198
198
199 moderncaps = {
199 moderncaps = {
200 b'lookup',
200 b'lookup',
201 b'branchmap',
201 b'branchmap',
202 b'pushkey',
202 b'pushkey',
203 b'known',
203 b'known',
204 b'getbundle',
204 b'getbundle',
205 b'unbundle',
205 b'unbundle',
206 }
206 }
207 legacycaps = moderncaps.union({b'changegroupsubset'})
207 legacycaps = moderncaps.union({b'changegroupsubset'})
208
208
209
209
210 @interfaceutil.implementer(repository.ipeercommandexecutor)
210 @interfaceutil.implementer(repository.ipeercommandexecutor)
211 class localcommandexecutor(object):
211 class localcommandexecutor(object):
212 def __init__(self, peer):
212 def __init__(self, peer):
213 self._peer = peer
213 self._peer = peer
214 self._sent = False
214 self._sent = False
215 self._closed = False
215 self._closed = False
216
216
217 def __enter__(self):
217 def __enter__(self):
218 return self
218 return self
219
219
220 def __exit__(self, exctype, excvalue, exctb):
220 def __exit__(self, exctype, excvalue, exctb):
221 self.close()
221 self.close()
222
222
223 def callcommand(self, command, args):
223 def callcommand(self, command, args):
224 if self._sent:
224 if self._sent:
225 raise error.ProgrammingError(
225 raise error.ProgrammingError(
226 b'callcommand() cannot be used after sendcommands()'
226 b'callcommand() cannot be used after sendcommands()'
227 )
227 )
228
228
229 if self._closed:
229 if self._closed:
230 raise error.ProgrammingError(
230 raise error.ProgrammingError(
231 b'callcommand() cannot be used after close()'
231 b'callcommand() cannot be used after close()'
232 )
232 )
233
233
234 # We don't need to support anything fancy. Just call the named
234 # We don't need to support anything fancy. Just call the named
235 # method on the peer and return a resolved future.
235 # method on the peer and return a resolved future.
236 fn = getattr(self._peer, pycompat.sysstr(command))
236 fn = getattr(self._peer, pycompat.sysstr(command))
237
237
238 f = pycompat.futures.Future()
238 f = pycompat.futures.Future()
239
239
240 try:
240 try:
241 result = fn(**pycompat.strkwargs(args))
241 result = fn(**pycompat.strkwargs(args))
242 except Exception:
242 except Exception:
243 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
243 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
244 else:
244 else:
245 f.set_result(result)
245 f.set_result(result)
246
246
247 return f
247 return f
248
248
249 def sendcommands(self):
249 def sendcommands(self):
250 self._sent = True
250 self._sent = True
251
251
252 def close(self):
252 def close(self):
253 self._closed = True
253 self._closed = True
254
254
255
255
256 @interfaceutil.implementer(repository.ipeercommands)
256 @interfaceutil.implementer(repository.ipeercommands)
257 class localpeer(repository.peer):
257 class localpeer(repository.peer):
258 '''peer for a local repo; reflects only the most recent API'''
258 '''peer for a local repo; reflects only the most recent API'''
259
259
260 def __init__(self, repo, caps=None):
260 def __init__(self, repo, caps=None):
261 super(localpeer, self).__init__()
261 super(localpeer, self).__init__()
262
262
263 if caps is None:
263 if caps is None:
264 caps = moderncaps.copy()
264 caps = moderncaps.copy()
265 self._repo = repo.filtered(b'served')
265 self._repo = repo.filtered(b'served')
266 self.ui = repo.ui
266 self.ui = repo.ui
267 self._caps = repo._restrictcapabilities(caps)
267 self._caps = repo._restrictcapabilities(caps)
268
268
269 # Begin of _basepeer interface.
269 # Begin of _basepeer interface.
270
270
271 def url(self):
271 def url(self):
272 return self._repo.url()
272 return self._repo.url()
273
273
274 def local(self):
274 def local(self):
275 return self._repo
275 return self._repo
276
276
277 def peer(self):
277 def peer(self):
278 return self
278 return self
279
279
280 def canpush(self):
280 def canpush(self):
281 return True
281 return True
282
282
283 def close(self):
283 def close(self):
284 self._repo.close()
284 self._repo.close()
285
285
286 # End of _basepeer interface.
286 # End of _basepeer interface.
287
287
288 # Begin of _basewirecommands interface.
288 # Begin of _basewirecommands interface.
289
289
290 def branchmap(self):
290 def branchmap(self):
291 return self._repo.branchmap()
291 return self._repo.branchmap()
292
292
293 def capabilities(self):
293 def capabilities(self):
294 return self._caps
294 return self._caps
295
295
296 def clonebundles(self):
296 def clonebundles(self):
297 return self._repo.tryread(b'clonebundles.manifest')
297 return self._repo.tryread(b'clonebundles.manifest')
298
298
299 def debugwireargs(self, one, two, three=None, four=None, five=None):
299 def debugwireargs(self, one, two, three=None, four=None, five=None):
300 """Used to test argument passing over the wire"""
300 """Used to test argument passing over the wire"""
301 return b"%s %s %s %s %s" % (
301 return b"%s %s %s %s %s" % (
302 one,
302 one,
303 two,
303 two,
304 pycompat.bytestr(three),
304 pycompat.bytestr(three),
305 pycompat.bytestr(four),
305 pycompat.bytestr(four),
306 pycompat.bytestr(five),
306 pycompat.bytestr(five),
307 )
307 )
308
308
309 def getbundle(
309 def getbundle(
310 self, source, heads=None, common=None, bundlecaps=None, **kwargs
310 self, source, heads=None, common=None, bundlecaps=None, **kwargs
311 ):
311 ):
312 chunks = exchange.getbundlechunks(
312 chunks = exchange.getbundlechunks(
313 self._repo,
313 self._repo,
314 source,
314 source,
315 heads=heads,
315 heads=heads,
316 common=common,
316 common=common,
317 bundlecaps=bundlecaps,
317 bundlecaps=bundlecaps,
318 **kwargs
318 **kwargs
319 )[1]
319 )[1]
320 cb = util.chunkbuffer(chunks)
320 cb = util.chunkbuffer(chunks)
321
321
322 if exchange.bundle2requested(bundlecaps):
322 if exchange.bundle2requested(bundlecaps):
323 # When requesting a bundle2, getbundle returns a stream to make the
323 # When requesting a bundle2, getbundle returns a stream to make the
324 # wire level function happier. We need to build a proper object
324 # wire level function happier. We need to build a proper object
325 # from it in local peer.
325 # from it in local peer.
326 return bundle2.getunbundler(self.ui, cb)
326 return bundle2.getunbundler(self.ui, cb)
327 else:
327 else:
328 return changegroup.getunbundler(b'01', cb, None)
328 return changegroup.getunbundler(b'01', cb, None)
329
329
330 def heads(self):
330 def heads(self):
331 return self._repo.heads()
331 return self._repo.heads()
332
332
333 def known(self, nodes):
333 def known(self, nodes):
334 return self._repo.known(nodes)
334 return self._repo.known(nodes)
335
335
336 def listkeys(self, namespace):
336 def listkeys(self, namespace):
337 return self._repo.listkeys(namespace)
337 return self._repo.listkeys(namespace)
338
338
339 def lookup(self, key):
339 def lookup(self, key):
340 return self._repo.lookup(key)
340 return self._repo.lookup(key)
341
341
342 def pushkey(self, namespace, key, old, new):
342 def pushkey(self, namespace, key, old, new):
343 return self._repo.pushkey(namespace, key, old, new)
343 return self._repo.pushkey(namespace, key, old, new)
344
344
345 def stream_out(self):
345 def stream_out(self):
346 raise error.Abort(_(b'cannot perform stream clone against local peer'))
346 raise error.Abort(_(b'cannot perform stream clone against local peer'))
347
347
348 def unbundle(self, bundle, heads, url):
348 def unbundle(self, bundle, heads, url):
349 """apply a bundle on a repo
349 """apply a bundle on a repo
350
350
351 This function handles the repo locking itself."""
351 This function handles the repo locking itself."""
352 try:
352 try:
353 try:
353 try:
354 bundle = exchange.readbundle(self.ui, bundle, None)
354 bundle = exchange.readbundle(self.ui, bundle, None)
355 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
355 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
356 if util.safehasattr(ret, b'getchunks'):
356 if util.safehasattr(ret, b'getchunks'):
357 # This is a bundle20 object, turn it into an unbundler.
357 # This is a bundle20 object, turn it into an unbundler.
358 # This little dance should be dropped eventually when the
358 # This little dance should be dropped eventually when the
359 # API is finally improved.
359 # API is finally improved.
360 stream = util.chunkbuffer(ret.getchunks())
360 stream = util.chunkbuffer(ret.getchunks())
361 ret = bundle2.getunbundler(self.ui, stream)
361 ret = bundle2.getunbundler(self.ui, stream)
362 return ret
362 return ret
363 except Exception as exc:
363 except Exception as exc:
364 # If the exception contains output salvaged from a bundle2
364 # If the exception contains output salvaged from a bundle2
365 # reply, we need to make sure it is printed before continuing
365 # reply, we need to make sure it is printed before continuing
366 # to fail. So we build a bundle2 with such output and consume
366 # to fail. So we build a bundle2 with such output and consume
367 # it directly.
367 # it directly.
368 #
368 #
369 # This is not very elegant but allows a "simple" solution for
369 # This is not very elegant but allows a "simple" solution for
370 # issue4594
370 # issue4594
371 output = getattr(exc, '_bundle2salvagedoutput', ())
371 output = getattr(exc, '_bundle2salvagedoutput', ())
372 if output:
372 if output:
373 bundler = bundle2.bundle20(self._repo.ui)
373 bundler = bundle2.bundle20(self._repo.ui)
374 for out in output:
374 for out in output:
375 bundler.addpart(out)
375 bundler.addpart(out)
376 stream = util.chunkbuffer(bundler.getchunks())
376 stream = util.chunkbuffer(bundler.getchunks())
377 b = bundle2.getunbundler(self.ui, stream)
377 b = bundle2.getunbundler(self.ui, stream)
378 bundle2.processbundle(self._repo, b)
378 bundle2.processbundle(self._repo, b)
379 raise
379 raise
380 except error.PushRaced as exc:
380 except error.PushRaced as exc:
381 raise error.ResponseError(
381 raise error.ResponseError(
382 _(b'push failed:'), stringutil.forcebytestr(exc)
382 _(b'push failed:'), stringutil.forcebytestr(exc)
383 )
383 )
384
384
385 # End of _basewirecommands interface.
385 # End of _basewirecommands interface.
386
386
387 # Begin of peer interface.
387 # Begin of peer interface.
388
388
389 def commandexecutor(self):
389 def commandexecutor(self):
390 return localcommandexecutor(self)
390 return localcommandexecutor(self)
391
391
392 # End of peer interface.
392 # End of peer interface.
393
393
394
394
395 @interfaceutil.implementer(repository.ipeerlegacycommands)
395 @interfaceutil.implementer(repository.ipeerlegacycommands)
396 class locallegacypeer(localpeer):
396 class locallegacypeer(localpeer):
397 '''peer extension which implements legacy methods too; used for tests with
397 '''peer extension which implements legacy methods too; used for tests with
398 restricted capabilities'''
398 restricted capabilities'''
399
399
400 def __init__(self, repo):
400 def __init__(self, repo):
401 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
401 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
402
402
403 # Begin of baselegacywirecommands interface.
403 # Begin of baselegacywirecommands interface.
404
404
405 def between(self, pairs):
405 def between(self, pairs):
406 return self._repo.between(pairs)
406 return self._repo.between(pairs)
407
407
408 def branches(self, nodes):
408 def branches(self, nodes):
409 return self._repo.branches(nodes)
409 return self._repo.branches(nodes)
410
410
411 def changegroup(self, nodes, source):
411 def changegroup(self, nodes, source):
412 outgoing = discovery.outgoing(
412 outgoing = discovery.outgoing(
413 self._repo, missingroots=nodes, missingheads=self._repo.heads()
413 self._repo, missingroots=nodes, missingheads=self._repo.heads()
414 )
414 )
415 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
415 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
416
416
417 def changegroupsubset(self, bases, heads, source):
417 def changegroupsubset(self, bases, heads, source):
418 outgoing = discovery.outgoing(
418 outgoing = discovery.outgoing(
419 self._repo, missingroots=bases, missingheads=heads
419 self._repo, missingroots=bases, missingheads=heads
420 )
420 )
421 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
421 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
422
422
423 # End of baselegacywirecommands interface.
423 # End of baselegacywirecommands interface.
424
424
425
425
426 # Increment the sub-version when the revlog v2 format changes to lock out old
426 # Increment the sub-version when the revlog v2 format changes to lock out old
427 # clients.
427 # clients.
428 REVLOGV2_REQUIREMENT = b'exp-revlogv2.1'
428 REVLOGV2_REQUIREMENT = b'exp-revlogv2.1'
429
429
430 # A repository with the sparserevlog feature will have delta chains that
430 # A repository with the sparserevlog feature will have delta chains that
431 # can spread over a larger span. Sparse reading cuts these large spans into
431 # can spread over a larger span. Sparse reading cuts these large spans into
432 # pieces, so that each piece isn't too big.
432 # pieces, so that each piece isn't too big.
433 # Without the sparserevlog capability, reading from the repository could use
433 # Without the sparserevlog capability, reading from the repository could use
434 # huge amounts of memory, because the whole span would be read at once,
434 # huge amounts of memory, because the whole span would be read at once,
435 # including all the intermediate revisions that aren't pertinent for the chain.
435 # including all the intermediate revisions that aren't pertinent for the chain.
436 # This is why once a repository has enabled sparse-read, it becomes required.
436 # This is why once a repository has enabled sparse-read, it becomes required.
437 SPARSEREVLOG_REQUIREMENT = b'sparserevlog'
437 SPARSEREVLOG_REQUIREMENT = b'sparserevlog'
438
438
439 # A repository with the sidedataflag requirement will allow to store extra
439 # A repository with the sidedataflag requirement will allow to store extra
440 # information for revision without altering their original hashes.
440 # information for revision without altering their original hashes.
441 SIDEDATA_REQUIREMENT = b'exp-sidedata-flag'
441 SIDEDATA_REQUIREMENT = b'exp-sidedata-flag'
442
442
443 # A repository with the the copies-sidedata-changeset requirement will store
443 # A repository with the the copies-sidedata-changeset requirement will store
444 # copies related information in changeset's sidedata.
444 # copies related information in changeset's sidedata.
445 COPIESSDC_REQUIREMENT = b'exp-copies-sidedata-changeset'
445 COPIESSDC_REQUIREMENT = b'exp-copies-sidedata-changeset'
446
446
447 # Functions receiving (ui, features) that extensions can register to impact
447 # Functions receiving (ui, features) that extensions can register to impact
448 # the ability to load repositories with custom requirements. Only
448 # the ability to load repositories with custom requirements. Only
449 # functions defined in loaded extensions are called.
449 # functions defined in loaded extensions are called.
450 #
450 #
451 # The function receives a set of requirement strings that the repository
451 # The function receives a set of requirement strings that the repository
452 # is capable of opening. Functions will typically add elements to the
452 # is capable of opening. Functions will typically add elements to the
453 # set to reflect that the extension knows how to handle that requirements.
453 # set to reflect that the extension knows how to handle that requirements.
454 featuresetupfuncs = set()
454 featuresetupfuncs = set()
455
455
456
456
457 def makelocalrepository(baseui, path, intents=None):
457 def makelocalrepository(baseui, path, intents=None):
458 """Create a local repository object.
458 """Create a local repository object.
459
459
460 Given arguments needed to construct a local repository, this function
460 Given arguments needed to construct a local repository, this function
461 performs various early repository loading functionality (such as
461 performs various early repository loading functionality (such as
462 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
462 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
463 the repository can be opened, derives a type suitable for representing
463 the repository can be opened, derives a type suitable for representing
464 that repository, and returns an instance of it.
464 that repository, and returns an instance of it.
465
465
466 The returned object conforms to the ``repository.completelocalrepository``
466 The returned object conforms to the ``repository.completelocalrepository``
467 interface.
467 interface.
468
468
469 The repository type is derived by calling a series of factory functions
469 The repository type is derived by calling a series of factory functions
470 for each aspect/interface of the final repository. These are defined by
470 for each aspect/interface of the final repository. These are defined by
471 ``REPO_INTERFACES``.
471 ``REPO_INTERFACES``.
472
472
473 Each factory function is called to produce a type implementing a specific
473 Each factory function is called to produce a type implementing a specific
474 interface. The cumulative list of returned types will be combined into a
474 interface. The cumulative list of returned types will be combined into a
475 new type and that type will be instantiated to represent the local
475 new type and that type will be instantiated to represent the local
476 repository.
476 repository.
477
477
478 The factory functions each receive various state that may be consulted
478 The factory functions each receive various state that may be consulted
479 as part of deriving a type.
479 as part of deriving a type.
480
480
481 Extensions should wrap these factory functions to customize repository type
481 Extensions should wrap these factory functions to customize repository type
482 creation. Note that an extension's wrapped function may be called even if
482 creation. Note that an extension's wrapped function may be called even if
483 that extension is not loaded for the repo being constructed. Extensions
483 that extension is not loaded for the repo being constructed. Extensions
484 should check if their ``__name__`` appears in the
484 should check if their ``__name__`` appears in the
485 ``extensionmodulenames`` set passed to the factory function and no-op if
485 ``extensionmodulenames`` set passed to the factory function and no-op if
486 not.
486 not.
487 """
487 """
488 ui = baseui.copy()
488 ui = baseui.copy()
489 # Prevent copying repo configuration.
489 # Prevent copying repo configuration.
490 ui.copy = baseui.copy
490 ui.copy = baseui.copy
491
491
492 # Working directory VFS rooted at repository root.
492 # Working directory VFS rooted at repository root.
493 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
493 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
494
494
495 # Main VFS for .hg/ directory.
495 # Main VFS for .hg/ directory.
496 hgpath = wdirvfs.join(b'.hg')
496 hgpath = wdirvfs.join(b'.hg')
497 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
497 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
498
498
499 # The .hg/ path should exist and should be a directory. All other
499 # The .hg/ path should exist and should be a directory. All other
500 # cases are errors.
500 # cases are errors.
501 if not hgvfs.isdir():
501 if not hgvfs.isdir():
502 try:
502 try:
503 hgvfs.stat()
503 hgvfs.stat()
504 except OSError as e:
504 except OSError as e:
505 if e.errno != errno.ENOENT:
505 if e.errno != errno.ENOENT:
506 raise
506 raise
507
507
508 raise error.RepoError(_(b'repository %s not found') % path)
508 raise error.RepoError(_(b'repository %s not found') % path)
509
509
510 # .hg/requires file contains a newline-delimited list of
510 # .hg/requires file contains a newline-delimited list of
511 # features/capabilities the opener (us) must have in order to use
511 # features/capabilities the opener (us) must have in order to use
512 # the repository. This file was introduced in Mercurial 0.9.2,
512 # the repository. This file was introduced in Mercurial 0.9.2,
513 # which means very old repositories may not have one. We assume
513 # which means very old repositories may not have one. We assume
514 # a missing file translates to no requirements.
514 # a missing file translates to no requirements.
515 try:
515 try:
516 requirements = set(hgvfs.read(b'requires').splitlines())
516 requirements = set(hgvfs.read(b'requires').splitlines())
517 except IOError as e:
517 except IOError as e:
518 if e.errno != errno.ENOENT:
518 if e.errno != errno.ENOENT:
519 raise
519 raise
520 requirements = set()
520 requirements = set()
521
521
522 # The .hg/hgrc file may load extensions or contain config options
522 # The .hg/hgrc file may load extensions or contain config options
523 # that influence repository construction. Attempt to load it and
523 # that influence repository construction. Attempt to load it and
524 # process any new extensions that it may have pulled in.
524 # process any new extensions that it may have pulled in.
525 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
525 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
526 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
526 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
527 extensions.loadall(ui)
527 extensions.loadall(ui)
528 extensions.populateui(ui)
528 extensions.populateui(ui)
529
529
530 # Set of module names of extensions loaded for this repository.
530 # Set of module names of extensions loaded for this repository.
531 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
531 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
532
532
533 supportedrequirements = gathersupportedrequirements(ui)
533 supportedrequirements = gathersupportedrequirements(ui)
534
534
535 # We first validate the requirements are known.
535 # We first validate the requirements are known.
536 ensurerequirementsrecognized(requirements, supportedrequirements)
536 ensurerequirementsrecognized(requirements, supportedrequirements)
537
537
538 # Then we validate that the known set is reasonable to use together.
538 # Then we validate that the known set is reasonable to use together.
539 ensurerequirementscompatible(ui, requirements)
539 ensurerequirementscompatible(ui, requirements)
540
540
541 # TODO there are unhandled edge cases related to opening repositories with
541 # TODO there are unhandled edge cases related to opening repositories with
542 # shared storage. If storage is shared, we should also test for requirements
542 # shared storage. If storage is shared, we should also test for requirements
543 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
543 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
544 # that repo, as that repo may load extensions needed to open it. This is a
544 # that repo, as that repo may load extensions needed to open it. This is a
545 # bit complicated because we don't want the other hgrc to overwrite settings
545 # bit complicated because we don't want the other hgrc to overwrite settings
546 # in this hgrc.
546 # in this hgrc.
547 #
547 #
548 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
548 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
549 # file when sharing repos. But if a requirement is added after the share is
549 # file when sharing repos. But if a requirement is added after the share is
550 # performed, thereby introducing a new requirement for the opener, we may
550 # performed, thereby introducing a new requirement for the opener, we may
551 # will not see that and could encounter a run-time error interacting with
551 # will not see that and could encounter a run-time error interacting with
552 # that shared store since it has an unknown-to-us requirement.
552 # that shared store since it has an unknown-to-us requirement.
553
553
554 # At this point, we know we should be capable of opening the repository.
554 # At this point, we know we should be capable of opening the repository.
555 # Now get on with doing that.
555 # Now get on with doing that.
556
556
557 features = set()
557 features = set()
558
558
559 # The "store" part of the repository holds versioned data. How it is
559 # The "store" part of the repository holds versioned data. How it is
560 # accessed is determined by various requirements. The ``shared`` or
560 # accessed is determined by various requirements. The ``shared`` or
561 # ``relshared`` requirements indicate the store lives in the path contained
561 # ``relshared`` requirements indicate the store lives in the path contained
562 # in the ``.hg/sharedpath`` file. This is an absolute path for
562 # in the ``.hg/sharedpath`` file. This is an absolute path for
563 # ``shared`` and relative to ``.hg/`` for ``relshared``.
563 # ``shared`` and relative to ``.hg/`` for ``relshared``.
564 if b'shared' in requirements or b'relshared' in requirements:
564 if b'shared' in requirements or b'relshared' in requirements:
565 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
565 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
566 if b'relshared' in requirements:
566 if b'relshared' in requirements:
567 sharedpath = hgvfs.join(sharedpath)
567 sharedpath = hgvfs.join(sharedpath)
568
568
569 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
569 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
570
570
571 if not sharedvfs.exists():
571 if not sharedvfs.exists():
572 raise error.RepoError(
572 raise error.RepoError(
573 _(b'.hg/sharedpath points to nonexistent directory %s')
573 _(b'.hg/sharedpath points to nonexistent directory %s')
574 % sharedvfs.base
574 % sharedvfs.base
575 )
575 )
576
576
577 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
577 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
578
578
579 storebasepath = sharedvfs.base
579 storebasepath = sharedvfs.base
580 cachepath = sharedvfs.join(b'cache')
580 cachepath = sharedvfs.join(b'cache')
581 else:
581 else:
582 storebasepath = hgvfs.base
582 storebasepath = hgvfs.base
583 cachepath = hgvfs.join(b'cache')
583 cachepath = hgvfs.join(b'cache')
584 wcachepath = hgvfs.join(b'wcache')
584 wcachepath = hgvfs.join(b'wcache')
585
585
586 # The store has changed over time and the exact layout is dictated by
586 # The store has changed over time and the exact layout is dictated by
587 # requirements. The store interface abstracts differences across all
587 # requirements. The store interface abstracts differences across all
588 # of them.
588 # of them.
589 store = makestore(
589 store = makestore(
590 requirements,
590 requirements,
591 storebasepath,
591 storebasepath,
592 lambda base: vfsmod.vfs(base, cacheaudited=True),
592 lambda base: vfsmod.vfs(base, cacheaudited=True),
593 )
593 )
594 hgvfs.createmode = store.createmode
594 hgvfs.createmode = store.createmode
595
595
596 storevfs = store.vfs
596 storevfs = store.vfs
597 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
597 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
598
598
599 # The cache vfs is used to manage cache files.
599 # The cache vfs is used to manage cache files.
600 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
600 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
601 cachevfs.createmode = store.createmode
601 cachevfs.createmode = store.createmode
602 # The cache vfs is used to manage cache files related to the working copy
602 # The cache vfs is used to manage cache files related to the working copy
603 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
603 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
604 wcachevfs.createmode = store.createmode
604 wcachevfs.createmode = store.createmode
605
605
606 # Now resolve the type for the repository object. We do this by repeatedly
606 # Now resolve the type for the repository object. We do this by repeatedly
607 # calling a factory function to produces types for specific aspects of the
607 # calling a factory function to produces types for specific aspects of the
608 # repo's operation. The aggregate returned types are used as base classes
608 # repo's operation. The aggregate returned types are used as base classes
609 # for a dynamically-derived type, which will represent our new repository.
609 # for a dynamically-derived type, which will represent our new repository.
610
610
611 bases = []
611 bases = []
612 extrastate = {}
612 extrastate = {}
613
613
614 for iface, fn in REPO_INTERFACES:
614 for iface, fn in REPO_INTERFACES:
615 # We pass all potentially useful state to give extensions tons of
615 # We pass all potentially useful state to give extensions tons of
616 # flexibility.
616 # flexibility.
617 typ = fn()(
617 typ = fn()(
618 ui=ui,
618 ui=ui,
619 intents=intents,
619 intents=intents,
620 requirements=requirements,
620 requirements=requirements,
621 features=features,
621 features=features,
622 wdirvfs=wdirvfs,
622 wdirvfs=wdirvfs,
623 hgvfs=hgvfs,
623 hgvfs=hgvfs,
624 store=store,
624 store=store,
625 storevfs=storevfs,
625 storevfs=storevfs,
626 storeoptions=storevfs.options,
626 storeoptions=storevfs.options,
627 cachevfs=cachevfs,
627 cachevfs=cachevfs,
628 wcachevfs=wcachevfs,
628 wcachevfs=wcachevfs,
629 extensionmodulenames=extensionmodulenames,
629 extensionmodulenames=extensionmodulenames,
630 extrastate=extrastate,
630 extrastate=extrastate,
631 baseclasses=bases,
631 baseclasses=bases,
632 )
632 )
633
633
634 if not isinstance(typ, type):
634 if not isinstance(typ, type):
635 raise error.ProgrammingError(
635 raise error.ProgrammingError(
636 b'unable to construct type for %s' % iface
636 b'unable to construct type for %s' % iface
637 )
637 )
638
638
639 bases.append(typ)
639 bases.append(typ)
640
640
641 # type() allows you to use characters in type names that wouldn't be
641 # type() allows you to use characters in type names that wouldn't be
642 # recognized as Python symbols in source code. We abuse that to add
642 # recognized as Python symbols in source code. We abuse that to add
643 # rich information about our constructed repo.
643 # rich information about our constructed repo.
644 name = pycompat.sysstr(
644 name = pycompat.sysstr(
645 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
645 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
646 )
646 )
647
647
648 cls = type(name, tuple(bases), {})
648 cls = type(name, tuple(bases), {})
649
649
650 return cls(
650 return cls(
651 baseui=baseui,
651 baseui=baseui,
652 ui=ui,
652 ui=ui,
653 origroot=path,
653 origroot=path,
654 wdirvfs=wdirvfs,
654 wdirvfs=wdirvfs,
655 hgvfs=hgvfs,
655 hgvfs=hgvfs,
656 requirements=requirements,
656 requirements=requirements,
657 supportedrequirements=supportedrequirements,
657 supportedrequirements=supportedrequirements,
658 sharedpath=storebasepath,
658 sharedpath=storebasepath,
659 store=store,
659 store=store,
660 cachevfs=cachevfs,
660 cachevfs=cachevfs,
661 wcachevfs=wcachevfs,
661 wcachevfs=wcachevfs,
662 features=features,
662 features=features,
663 intents=intents,
663 intents=intents,
664 )
664 )
665
665
666
666
667 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
667 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
668 """Load hgrc files/content into a ui instance.
668 """Load hgrc files/content into a ui instance.
669
669
670 This is called during repository opening to load any additional
670 This is called during repository opening to load any additional
671 config files or settings relevant to the current repository.
671 config files or settings relevant to the current repository.
672
672
673 Returns a bool indicating whether any additional configs were loaded.
673 Returns a bool indicating whether any additional configs were loaded.
674
674
675 Extensions should monkeypatch this function to modify how per-repo
675 Extensions should monkeypatch this function to modify how per-repo
676 configs are loaded. For example, an extension may wish to pull in
676 configs are loaded. For example, an extension may wish to pull in
677 configs from alternate files or sources.
677 configs from alternate files or sources.
678 """
678 """
679 try:
679 try:
680 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
680 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
681 return True
681 return True
682 except IOError:
682 except IOError:
683 return False
683 return False
684
684
685
685
686 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
686 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
687 """Perform additional actions after .hg/hgrc is loaded.
687 """Perform additional actions after .hg/hgrc is loaded.
688
688
689 This function is called during repository loading immediately after
689 This function is called during repository loading immediately after
690 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
690 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
691
691
692 The function can be used to validate configs, automatically add
692 The function can be used to validate configs, automatically add
693 options (including extensions) based on requirements, etc.
693 options (including extensions) based on requirements, etc.
694 """
694 """
695
695
696 # Map of requirements to list of extensions to load automatically when
696 # Map of requirements to list of extensions to load automatically when
697 # requirement is present.
697 # requirement is present.
698 autoextensions = {
698 autoextensions = {
699 b'largefiles': [b'largefiles'],
699 b'largefiles': [b'largefiles'],
700 b'lfs': [b'lfs'],
700 b'lfs': [b'lfs'],
701 }
701 }
702
702
703 for requirement, names in sorted(autoextensions.items()):
703 for requirement, names in sorted(autoextensions.items()):
704 if requirement not in requirements:
704 if requirement not in requirements:
705 continue
705 continue
706
706
707 for name in names:
707 for name in names:
708 if not ui.hasconfig(b'extensions', name):
708 if not ui.hasconfig(b'extensions', name):
709 ui.setconfig(b'extensions', name, b'', source=b'autoload')
709 ui.setconfig(b'extensions', name, b'', source=b'autoload')
710
710
711
711
712 def gathersupportedrequirements(ui):
712 def gathersupportedrequirements(ui):
713 """Determine the complete set of recognized requirements."""
713 """Determine the complete set of recognized requirements."""
714 # Start with all requirements supported by this file.
714 # Start with all requirements supported by this file.
715 supported = set(localrepository._basesupported)
715 supported = set(localrepository._basesupported)
716
716
717 # Execute ``featuresetupfuncs`` entries if they belong to an extension
717 # Execute ``featuresetupfuncs`` entries if they belong to an extension
718 # relevant to this ui instance.
718 # relevant to this ui instance.
719 modules = {m.__name__ for n, m in extensions.extensions(ui)}
719 modules = {m.__name__ for n, m in extensions.extensions(ui)}
720
720
721 for fn in featuresetupfuncs:
721 for fn in featuresetupfuncs:
722 if fn.__module__ in modules:
722 if fn.__module__ in modules:
723 fn(ui, supported)
723 fn(ui, supported)
724
724
725 # Add derived requirements from registered compression engines.
725 # Add derived requirements from registered compression engines.
726 for name in util.compengines:
726 for name in util.compengines:
727 engine = util.compengines[name]
727 engine = util.compengines[name]
728 if engine.available() and engine.revlogheader():
728 if engine.available() and engine.revlogheader():
729 supported.add(b'exp-compression-%s' % name)
729 supported.add(b'exp-compression-%s' % name)
730 if engine.name() == b'zstd':
730 if engine.name() == b'zstd':
731 supported.add(b'revlog-compression-zstd')
731 supported.add(b'revlog-compression-zstd')
732
732
733 return supported
733 return supported
734
734
735
735
736 def ensurerequirementsrecognized(requirements, supported):
736 def ensurerequirementsrecognized(requirements, supported):
737 """Validate that a set of local requirements is recognized.
737 """Validate that a set of local requirements is recognized.
738
738
739 Receives a set of requirements. Raises an ``error.RepoError`` if there
739 Receives a set of requirements. Raises an ``error.RepoError`` if there
740 exists any requirement in that set that currently loaded code doesn't
740 exists any requirement in that set that currently loaded code doesn't
741 recognize.
741 recognize.
742
742
743 Returns a set of supported requirements.
743 Returns a set of supported requirements.
744 """
744 """
745 missing = set()
745 missing = set()
746
746
747 for requirement in requirements:
747 for requirement in requirements:
748 if requirement in supported:
748 if requirement in supported:
749 continue
749 continue
750
750
751 if not requirement or not requirement[0:1].isalnum():
751 if not requirement or not requirement[0:1].isalnum():
752 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
752 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
753
753
754 missing.add(requirement)
754 missing.add(requirement)
755
755
756 if missing:
756 if missing:
757 raise error.RequirementError(
757 raise error.RequirementError(
758 _(b'repository requires features unknown to this Mercurial: %s')
758 _(b'repository requires features unknown to this Mercurial: %s')
759 % b' '.join(sorted(missing)),
759 % b' '.join(sorted(missing)),
760 hint=_(
760 hint=_(
761 b'see https://mercurial-scm.org/wiki/MissingRequirement '
761 b'see https://mercurial-scm.org/wiki/MissingRequirement '
762 b'for more information'
762 b'for more information'
763 ),
763 ),
764 )
764 )
765
765
766
766
767 def ensurerequirementscompatible(ui, requirements):
767 def ensurerequirementscompatible(ui, requirements):
768 """Validates that a set of recognized requirements is mutually compatible.
768 """Validates that a set of recognized requirements is mutually compatible.
769
769
770 Some requirements may not be compatible with others or require
770 Some requirements may not be compatible with others or require
771 config options that aren't enabled. This function is called during
771 config options that aren't enabled. This function is called during
772 repository opening to ensure that the set of requirements needed
772 repository opening to ensure that the set of requirements needed
773 to open a repository is sane and compatible with config options.
773 to open a repository is sane and compatible with config options.
774
774
775 Extensions can monkeypatch this function to perform additional
775 Extensions can monkeypatch this function to perform additional
776 checking.
776 checking.
777
777
778 ``error.RepoError`` should be raised on failure.
778 ``error.RepoError`` should be raised on failure.
779 """
779 """
780 if b'exp-sparse' in requirements and not sparse.enabled:
780 if b'exp-sparse' in requirements and not sparse.enabled:
781 raise error.RepoError(
781 raise error.RepoError(
782 _(
782 _(
783 b'repository is using sparse feature but '
783 b'repository is using sparse feature but '
784 b'sparse is not enabled; enable the '
784 b'sparse is not enabled; enable the '
785 b'"sparse" extensions to access'
785 b'"sparse" extensions to access'
786 )
786 )
787 )
787 )
788
788
789
789
790 def makestore(requirements, path, vfstype):
790 def makestore(requirements, path, vfstype):
791 """Construct a storage object for a repository."""
791 """Construct a storage object for a repository."""
792 if b'store' in requirements:
792 if b'store' in requirements:
793 if b'fncache' in requirements:
793 if b'fncache' in requirements:
794 return storemod.fncachestore(
794 return storemod.fncachestore(
795 path, vfstype, b'dotencode' in requirements
795 path, vfstype, b'dotencode' in requirements
796 )
796 )
797
797
798 return storemod.encodedstore(path, vfstype)
798 return storemod.encodedstore(path, vfstype)
799
799
800 return storemod.basicstore(path, vfstype)
800 return storemod.basicstore(path, vfstype)
801
801
802
802
803 def resolvestorevfsoptions(ui, requirements, features):
803 def resolvestorevfsoptions(ui, requirements, features):
804 """Resolve the options to pass to the store vfs opener.
804 """Resolve the options to pass to the store vfs opener.
805
805
806 The returned dict is used to influence behavior of the storage layer.
806 The returned dict is used to influence behavior of the storage layer.
807 """
807 """
808 options = {}
808 options = {}
809
809
810 if b'treemanifest' in requirements:
810 if b'treemanifest' in requirements:
811 options[b'treemanifest'] = True
811 options[b'treemanifest'] = True
812
812
813 # experimental config: format.manifestcachesize
813 # experimental config: format.manifestcachesize
814 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
814 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
815 if manifestcachesize is not None:
815 if manifestcachesize is not None:
816 options[b'manifestcachesize'] = manifestcachesize
816 options[b'manifestcachesize'] = manifestcachesize
817
817
818 # In the absence of another requirement superseding a revlog-related
818 # In the absence of another requirement superseding a revlog-related
819 # requirement, we have to assume the repo is using revlog version 0.
819 # requirement, we have to assume the repo is using revlog version 0.
820 # This revlog format is super old and we don't bother trying to parse
820 # This revlog format is super old and we don't bother trying to parse
821 # opener options for it because those options wouldn't do anything
821 # opener options for it because those options wouldn't do anything
822 # meaningful on such old repos.
822 # meaningful on such old repos.
823 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
823 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
824 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
824 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
825 else: # explicitly mark repo as using revlogv0
825 else: # explicitly mark repo as using revlogv0
826 options[b'revlogv0'] = True
826 options[b'revlogv0'] = True
827
827
828 if COPIESSDC_REQUIREMENT in requirements:
828 if COPIESSDC_REQUIREMENT in requirements:
829 options[b'copies-storage'] = b'changeset-sidedata'
829 options[b'copies-storage'] = b'changeset-sidedata'
830 else:
830 else:
831 writecopiesto = ui.config(b'experimental', b'copies.write-to')
831 writecopiesto = ui.config(b'experimental', b'copies.write-to')
832 copiesextramode = (b'changeset-only', b'compatibility')
832 copiesextramode = (b'changeset-only', b'compatibility')
833 if writecopiesto in copiesextramode:
833 if writecopiesto in copiesextramode:
834 options[b'copies-storage'] = b'extra'
834 options[b'copies-storage'] = b'extra'
835
835
836 return options
836 return options
837
837
838
838
839 def resolverevlogstorevfsoptions(ui, requirements, features):
839 def resolverevlogstorevfsoptions(ui, requirements, features):
840 """Resolve opener options specific to revlogs."""
840 """Resolve opener options specific to revlogs."""
841
841
842 options = {}
842 options = {}
843 options[b'flagprocessors'] = {}
843 options[b'flagprocessors'] = {}
844
844
845 if b'revlogv1' in requirements:
845 if b'revlogv1' in requirements:
846 options[b'revlogv1'] = True
846 options[b'revlogv1'] = True
847 if REVLOGV2_REQUIREMENT in requirements:
847 if REVLOGV2_REQUIREMENT in requirements:
848 options[b'revlogv2'] = True
848 options[b'revlogv2'] = True
849
849
850 if b'generaldelta' in requirements:
850 if b'generaldelta' in requirements:
851 options[b'generaldelta'] = True
851 options[b'generaldelta'] = True
852
852
853 # experimental config: format.chunkcachesize
853 # experimental config: format.chunkcachesize
854 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
854 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
855 if chunkcachesize is not None:
855 if chunkcachesize is not None:
856 options[b'chunkcachesize'] = chunkcachesize
856 options[b'chunkcachesize'] = chunkcachesize
857
857
858 deltabothparents = ui.configbool(
858 deltabothparents = ui.configbool(
859 b'storage', b'revlog.optimize-delta-parent-choice'
859 b'storage', b'revlog.optimize-delta-parent-choice'
860 )
860 )
861 options[b'deltabothparents'] = deltabothparents
861 options[b'deltabothparents'] = deltabothparents
862
862
863 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
863 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
864 lazydeltabase = False
864 lazydeltabase = False
865 if lazydelta:
865 if lazydelta:
866 lazydeltabase = ui.configbool(
866 lazydeltabase = ui.configbool(
867 b'storage', b'revlog.reuse-external-delta-parent'
867 b'storage', b'revlog.reuse-external-delta-parent'
868 )
868 )
869 if lazydeltabase is None:
869 if lazydeltabase is None:
870 lazydeltabase = not scmutil.gddeltaconfig(ui)
870 lazydeltabase = not scmutil.gddeltaconfig(ui)
871 options[b'lazydelta'] = lazydelta
871 options[b'lazydelta'] = lazydelta
872 options[b'lazydeltabase'] = lazydeltabase
872 options[b'lazydeltabase'] = lazydeltabase
873
873
874 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
874 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
875 if 0 <= chainspan:
875 if 0 <= chainspan:
876 options[b'maxdeltachainspan'] = chainspan
876 options[b'maxdeltachainspan'] = chainspan
877
877
878 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
878 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
879 if mmapindexthreshold is not None:
879 if mmapindexthreshold is not None:
880 options[b'mmapindexthreshold'] = mmapindexthreshold
880 options[b'mmapindexthreshold'] = mmapindexthreshold
881
881
882 withsparseread = ui.configbool(b'experimental', b'sparse-read')
882 withsparseread = ui.configbool(b'experimental', b'sparse-read')
883 srdensitythres = float(
883 srdensitythres = float(
884 ui.config(b'experimental', b'sparse-read.density-threshold')
884 ui.config(b'experimental', b'sparse-read.density-threshold')
885 )
885 )
886 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
886 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
887 options[b'with-sparse-read'] = withsparseread
887 options[b'with-sparse-read'] = withsparseread
888 options[b'sparse-read-density-threshold'] = srdensitythres
888 options[b'sparse-read-density-threshold'] = srdensitythres
889 options[b'sparse-read-min-gap-size'] = srmingapsize
889 options[b'sparse-read-min-gap-size'] = srmingapsize
890
890
891 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
891 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
892 options[b'sparse-revlog'] = sparserevlog
892 options[b'sparse-revlog'] = sparserevlog
893 if sparserevlog:
893 if sparserevlog:
894 options[b'generaldelta'] = True
894 options[b'generaldelta'] = True
895
895
896 sidedata = SIDEDATA_REQUIREMENT in requirements
896 sidedata = SIDEDATA_REQUIREMENT in requirements
897 options[b'side-data'] = sidedata
897 options[b'side-data'] = sidedata
898
898
899 maxchainlen = None
899 maxchainlen = None
900 if sparserevlog:
900 if sparserevlog:
901 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
901 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
902 # experimental config: format.maxchainlen
902 # experimental config: format.maxchainlen
903 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
903 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
904 if maxchainlen is not None:
904 if maxchainlen is not None:
905 options[b'maxchainlen'] = maxchainlen
905 options[b'maxchainlen'] = maxchainlen
906
906
907 for r in requirements:
907 for r in requirements:
908 # we allow multiple compression engine requirement to co-exist because
908 # we allow multiple compression engine requirement to co-exist because
909 # strickly speaking, revlog seems to support mixed compression style.
909 # strickly speaking, revlog seems to support mixed compression style.
910 #
910 #
911 # The compression used for new entries will be "the last one"
911 # The compression used for new entries will be "the last one"
912 prefix = r.startswith
912 prefix = r.startswith
913 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
913 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
914 options[b'compengine'] = r.split(b'-', 2)[2]
914 options[b'compengine'] = r.split(b'-', 2)[2]
915
915
916 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
916 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
917 if options[b'zlib.level'] is not None:
917 if options[b'zlib.level'] is not None:
918 if not (0 <= options[b'zlib.level'] <= 9):
918 if not (0 <= options[b'zlib.level'] <= 9):
919 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
919 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
920 raise error.Abort(msg % options[b'zlib.level'])
920 raise error.Abort(msg % options[b'zlib.level'])
921 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
921 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
922 if options[b'zstd.level'] is not None:
922 if options[b'zstd.level'] is not None:
923 if not (0 <= options[b'zstd.level'] <= 22):
923 if not (0 <= options[b'zstd.level'] <= 22):
924 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
924 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
925 raise error.Abort(msg % options[b'zstd.level'])
925 raise error.Abort(msg % options[b'zstd.level'])
926
926
927 if repository.NARROW_REQUIREMENT in requirements:
927 if repository.NARROW_REQUIREMENT in requirements:
928 options[b'enableellipsis'] = True
928 options[b'enableellipsis'] = True
929
929
930 return options
930 return options
931
931
932
932
933 def makemain(**kwargs):
933 def makemain(**kwargs):
934 """Produce a type conforming to ``ilocalrepositorymain``."""
934 """Produce a type conforming to ``ilocalrepositorymain``."""
935 return localrepository
935 return localrepository
936
936
937
937
938 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
938 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
939 class revlogfilestorage(object):
939 class revlogfilestorage(object):
940 """File storage when using revlogs."""
940 """File storage when using revlogs."""
941
941
942 def file(self, path):
942 def file(self, path):
943 if path[0] == b'/':
943 if path[0] == b'/':
944 path = path[1:]
944 path = path[1:]
945
945
946 return filelog.filelog(self.svfs, path)
946 return filelog.filelog(self.svfs, path)
947
947
948
948
949 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
949 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
950 class revlognarrowfilestorage(object):
950 class revlognarrowfilestorage(object):
951 """File storage when using revlogs and narrow files."""
951 """File storage when using revlogs and narrow files."""
952
952
953 def file(self, path):
953 def file(self, path):
954 if path[0] == b'/':
954 if path[0] == b'/':
955 path = path[1:]
955 path = path[1:]
956
956
957 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
957 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
958
958
959
959
960 def makefilestorage(requirements, features, **kwargs):
960 def makefilestorage(requirements, features, **kwargs):
961 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
961 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
962 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
962 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
963 features.add(repository.REPO_FEATURE_STREAM_CLONE)
963 features.add(repository.REPO_FEATURE_STREAM_CLONE)
964
964
965 if repository.NARROW_REQUIREMENT in requirements:
965 if repository.NARROW_REQUIREMENT in requirements:
966 return revlognarrowfilestorage
966 return revlognarrowfilestorage
967 else:
967 else:
968 return revlogfilestorage
968 return revlogfilestorage
969
969
970
970
971 # List of repository interfaces and factory functions for them. Each
971 # List of repository interfaces and factory functions for them. Each
972 # will be called in order during ``makelocalrepository()`` to iteratively
972 # will be called in order during ``makelocalrepository()`` to iteratively
973 # derive the final type for a local repository instance. We capture the
973 # derive the final type for a local repository instance. We capture the
974 # function as a lambda so we don't hold a reference and the module-level
974 # function as a lambda so we don't hold a reference and the module-level
975 # functions can be wrapped.
975 # functions can be wrapped.
976 REPO_INTERFACES = [
976 REPO_INTERFACES = [
977 (repository.ilocalrepositorymain, lambda: makemain),
977 (repository.ilocalrepositorymain, lambda: makemain),
978 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
978 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
979 ]
979 ]
980
980
981
981
982 @interfaceutil.implementer(repository.ilocalrepositorymain)
982 @interfaceutil.implementer(repository.ilocalrepositorymain)
983 class localrepository(object):
983 class localrepository(object):
984 """Main class for representing local repositories.
984 """Main class for representing local repositories.
985
985
986 All local repositories are instances of this class.
986 All local repositories are instances of this class.
987
987
988 Constructed on its own, instances of this class are not usable as
988 Constructed on its own, instances of this class are not usable as
989 repository objects. To obtain a usable repository object, call
989 repository objects. To obtain a usable repository object, call
990 ``hg.repository()``, ``localrepo.instance()``, or
990 ``hg.repository()``, ``localrepo.instance()``, or
991 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
991 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
992 ``instance()`` adds support for creating new repositories.
992 ``instance()`` adds support for creating new repositories.
993 ``hg.repository()`` adds more extension integration, including calling
993 ``hg.repository()`` adds more extension integration, including calling
994 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
994 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
995 used.
995 used.
996 """
996 """
997
997
998 # obsolete experimental requirements:
998 # obsolete experimental requirements:
999 # - manifestv2: An experimental new manifest format that allowed
999 # - manifestv2: An experimental new manifest format that allowed
1000 # for stem compression of long paths. Experiment ended up not
1000 # for stem compression of long paths. Experiment ended up not
1001 # being successful (repository sizes went up due to worse delta
1001 # being successful (repository sizes went up due to worse delta
1002 # chains), and the code was deleted in 4.6.
1002 # chains), and the code was deleted in 4.6.
1003 supportedformats = {
1003 supportedformats = {
1004 b'revlogv1',
1004 b'revlogv1',
1005 b'generaldelta',
1005 b'generaldelta',
1006 b'treemanifest',
1006 b'treemanifest',
1007 COPIESSDC_REQUIREMENT,
1007 COPIESSDC_REQUIREMENT,
1008 REVLOGV2_REQUIREMENT,
1008 REVLOGV2_REQUIREMENT,
1009 SIDEDATA_REQUIREMENT,
1009 SIDEDATA_REQUIREMENT,
1010 SPARSEREVLOG_REQUIREMENT,
1010 SPARSEREVLOG_REQUIREMENT,
1011 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1011 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1012 }
1012 }
1013 _basesupported = supportedformats | {
1013 _basesupported = supportedformats | {
1014 b'store',
1014 b'store',
1015 b'fncache',
1015 b'fncache',
1016 b'shared',
1016 b'shared',
1017 b'relshared',
1017 b'relshared',
1018 b'dotencode',
1018 b'dotencode',
1019 b'exp-sparse',
1019 b'exp-sparse',
1020 b'internal-phase',
1020 b'internal-phase',
1021 }
1021 }
1022
1022
1023 # list of prefix for file which can be written without 'wlock'
1023 # list of prefix for file which can be written without 'wlock'
1024 # Extensions should extend this list when needed
1024 # Extensions should extend this list when needed
1025 _wlockfreeprefix = {
1025 _wlockfreeprefix = {
1026 # We migh consider requiring 'wlock' for the next
1026 # We migh consider requiring 'wlock' for the next
1027 # two, but pretty much all the existing code assume
1027 # two, but pretty much all the existing code assume
1028 # wlock is not needed so we keep them excluded for
1028 # wlock is not needed so we keep them excluded for
1029 # now.
1029 # now.
1030 b'hgrc',
1030 b'hgrc',
1031 b'requires',
1031 b'requires',
1032 # XXX cache is a complicatged business someone
1032 # XXX cache is a complicatged business someone
1033 # should investigate this in depth at some point
1033 # should investigate this in depth at some point
1034 b'cache/',
1034 b'cache/',
1035 # XXX shouldn't be dirstate covered by the wlock?
1035 # XXX shouldn't be dirstate covered by the wlock?
1036 b'dirstate',
1036 b'dirstate',
1037 # XXX bisect was still a bit too messy at the time
1037 # XXX bisect was still a bit too messy at the time
1038 # this changeset was introduced. Someone should fix
1038 # this changeset was introduced. Someone should fix
1039 # the remainig bit and drop this line
1039 # the remainig bit and drop this line
1040 b'bisect.state',
1040 b'bisect.state',
1041 }
1041 }
1042
1042
1043 def __init__(
1043 def __init__(
1044 self,
1044 self,
1045 baseui,
1045 baseui,
1046 ui,
1046 ui,
1047 origroot,
1047 origroot,
1048 wdirvfs,
1048 wdirvfs,
1049 hgvfs,
1049 hgvfs,
1050 requirements,
1050 requirements,
1051 supportedrequirements,
1051 supportedrequirements,
1052 sharedpath,
1052 sharedpath,
1053 store,
1053 store,
1054 cachevfs,
1054 cachevfs,
1055 wcachevfs,
1055 wcachevfs,
1056 features,
1056 features,
1057 intents=None,
1057 intents=None,
1058 ):
1058 ):
1059 """Create a new local repository instance.
1059 """Create a new local repository instance.
1060
1060
1061 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1061 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1062 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1062 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1063 object.
1063 object.
1064
1064
1065 Arguments:
1065 Arguments:
1066
1066
1067 baseui
1067 baseui
1068 ``ui.ui`` instance that ``ui`` argument was based off of.
1068 ``ui.ui`` instance that ``ui`` argument was based off of.
1069
1069
1070 ui
1070 ui
1071 ``ui.ui`` instance for use by the repository.
1071 ``ui.ui`` instance for use by the repository.
1072
1072
1073 origroot
1073 origroot
1074 ``bytes`` path to working directory root of this repository.
1074 ``bytes`` path to working directory root of this repository.
1075
1075
1076 wdirvfs
1076 wdirvfs
1077 ``vfs.vfs`` rooted at the working directory.
1077 ``vfs.vfs`` rooted at the working directory.
1078
1078
1079 hgvfs
1079 hgvfs
1080 ``vfs.vfs`` rooted at .hg/
1080 ``vfs.vfs`` rooted at .hg/
1081
1081
1082 requirements
1082 requirements
1083 ``set`` of bytestrings representing repository opening requirements.
1083 ``set`` of bytestrings representing repository opening requirements.
1084
1084
1085 supportedrequirements
1085 supportedrequirements
1086 ``set`` of bytestrings representing repository requirements that we
1086 ``set`` of bytestrings representing repository requirements that we
1087 know how to open. May be a supetset of ``requirements``.
1087 know how to open. May be a supetset of ``requirements``.
1088
1088
1089 sharedpath
1089 sharedpath
1090 ``bytes`` Defining path to storage base directory. Points to a
1090 ``bytes`` Defining path to storage base directory. Points to a
1091 ``.hg/`` directory somewhere.
1091 ``.hg/`` directory somewhere.
1092
1092
1093 store
1093 store
1094 ``store.basicstore`` (or derived) instance providing access to
1094 ``store.basicstore`` (or derived) instance providing access to
1095 versioned storage.
1095 versioned storage.
1096
1096
1097 cachevfs
1097 cachevfs
1098 ``vfs.vfs`` used for cache files.
1098 ``vfs.vfs`` used for cache files.
1099
1099
1100 wcachevfs
1100 wcachevfs
1101 ``vfs.vfs`` used for cache files related to the working copy.
1101 ``vfs.vfs`` used for cache files related to the working copy.
1102
1102
1103 features
1103 features
1104 ``set`` of bytestrings defining features/capabilities of this
1104 ``set`` of bytestrings defining features/capabilities of this
1105 instance.
1105 instance.
1106
1106
1107 intents
1107 intents
1108 ``set`` of system strings indicating what this repo will be used
1108 ``set`` of system strings indicating what this repo will be used
1109 for.
1109 for.
1110 """
1110 """
1111 self.baseui = baseui
1111 self.baseui = baseui
1112 self.ui = ui
1112 self.ui = ui
1113 self.origroot = origroot
1113 self.origroot = origroot
1114 # vfs rooted at working directory.
1114 # vfs rooted at working directory.
1115 self.wvfs = wdirvfs
1115 self.wvfs = wdirvfs
1116 self.root = wdirvfs.base
1116 self.root = wdirvfs.base
1117 # vfs rooted at .hg/. Used to access most non-store paths.
1117 # vfs rooted at .hg/. Used to access most non-store paths.
1118 self.vfs = hgvfs
1118 self.vfs = hgvfs
1119 self.path = hgvfs.base
1119 self.path = hgvfs.base
1120 self.requirements = requirements
1120 self.requirements = requirements
1121 self.supported = supportedrequirements
1121 self.supported = supportedrequirements
1122 self.sharedpath = sharedpath
1122 self.sharedpath = sharedpath
1123 self.store = store
1123 self.store = store
1124 self.cachevfs = cachevfs
1124 self.cachevfs = cachevfs
1125 self.wcachevfs = wcachevfs
1125 self.wcachevfs = wcachevfs
1126 self.features = features
1126 self.features = features
1127
1127
1128 self.filtername = None
1128 self.filtername = None
1129
1129
1130 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1130 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1131 b'devel', b'check-locks'
1131 b'devel', b'check-locks'
1132 ):
1132 ):
1133 self.vfs.audit = self._getvfsward(self.vfs.audit)
1133 self.vfs.audit = self._getvfsward(self.vfs.audit)
1134 # A list of callback to shape the phase if no data were found.
1134 # A list of callback to shape the phase if no data were found.
1135 # Callback are in the form: func(repo, roots) --> processed root.
1135 # Callback are in the form: func(repo, roots) --> processed root.
1136 # This list it to be filled by extension during repo setup
1136 # This list it to be filled by extension during repo setup
1137 self._phasedefaults = []
1137 self._phasedefaults = []
1138
1138
1139 color.setup(self.ui)
1139 color.setup(self.ui)
1140
1140
1141 self.spath = self.store.path
1141 self.spath = self.store.path
1142 self.svfs = self.store.vfs
1142 self.svfs = self.store.vfs
1143 self.sjoin = self.store.join
1143 self.sjoin = self.store.join
1144 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1144 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1145 b'devel', b'check-locks'
1145 b'devel', b'check-locks'
1146 ):
1146 ):
1147 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1147 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1148 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1148 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1149 else: # standard vfs
1149 else: # standard vfs
1150 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1150 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1151
1151
1152 self._dirstatevalidatewarned = False
1152 self._dirstatevalidatewarned = False
1153
1153
1154 self._branchcaches = branchmap.BranchMapCache()
1154 self._branchcaches = branchmap.BranchMapCache()
1155 self._revbranchcache = None
1155 self._revbranchcache = None
1156 self._filterpats = {}
1156 self._filterpats = {}
1157 self._datafilters = {}
1157 self._datafilters = {}
1158 self._transref = self._lockref = self._wlockref = None
1158 self._transref = self._lockref = self._wlockref = None
1159
1159
1160 # A cache for various files under .hg/ that tracks file changes,
1160 # A cache for various files under .hg/ that tracks file changes,
1161 # (used by the filecache decorator)
1161 # (used by the filecache decorator)
1162 #
1162 #
1163 # Maps a property name to its util.filecacheentry
1163 # Maps a property name to its util.filecacheentry
1164 self._filecache = {}
1164 self._filecache = {}
1165
1165
1166 # hold sets of revision to be filtered
1166 # hold sets of revision to be filtered
1167 # should be cleared when something might have changed the filter value:
1167 # should be cleared when something might have changed the filter value:
1168 # - new changesets,
1168 # - new changesets,
1169 # - phase change,
1169 # - phase change,
1170 # - new obsolescence marker,
1170 # - new obsolescence marker,
1171 # - working directory parent change,
1171 # - working directory parent change,
1172 # - bookmark changes
1172 # - bookmark changes
1173 self.filteredrevcache = {}
1173 self.filteredrevcache = {}
1174
1174
1175 # post-dirstate-status hooks
1175 # post-dirstate-status hooks
1176 self._postdsstatus = []
1176 self._postdsstatus = []
1177
1177
1178 # generic mapping between names and nodes
1178 # generic mapping between names and nodes
1179 self.names = namespaces.namespaces()
1179 self.names = namespaces.namespaces()
1180
1180
1181 # Key to signature value.
1181 # Key to signature value.
1182 self._sparsesignaturecache = {}
1182 self._sparsesignaturecache = {}
1183 # Signature to cached matcher instance.
1183 # Signature to cached matcher instance.
1184 self._sparsematchercache = {}
1184 self._sparsematchercache = {}
1185
1185
1186 self._extrafilterid = repoview.extrafilter(ui)
1186 self._extrafilterid = repoview.extrafilter(ui)
1187
1187
1188 self.filecopiesmode = None
1188 self.filecopiesmode = None
1189 if COPIESSDC_REQUIREMENT in self.requirements:
1189 if COPIESSDC_REQUIREMENT in self.requirements:
1190 self.filecopiesmode = b'changeset-sidedata'
1190 self.filecopiesmode = b'changeset-sidedata'
1191
1191
1192 def _getvfsward(self, origfunc):
1192 def _getvfsward(self, origfunc):
1193 """build a ward for self.vfs"""
1193 """build a ward for self.vfs"""
1194 rref = weakref.ref(self)
1194 rref = weakref.ref(self)
1195
1195
1196 def checkvfs(path, mode=None):
1196 def checkvfs(path, mode=None):
1197 ret = origfunc(path, mode=mode)
1197 ret = origfunc(path, mode=mode)
1198 repo = rref()
1198 repo = rref()
1199 if (
1199 if (
1200 repo is None
1200 repo is None
1201 or not util.safehasattr(repo, b'_wlockref')
1201 or not util.safehasattr(repo, b'_wlockref')
1202 or not util.safehasattr(repo, b'_lockref')
1202 or not util.safehasattr(repo, b'_lockref')
1203 ):
1203 ):
1204 return
1204 return
1205 if mode in (None, b'r', b'rb'):
1205 if mode in (None, b'r', b'rb'):
1206 return
1206 return
1207 if path.startswith(repo.path):
1207 if path.startswith(repo.path):
1208 # truncate name relative to the repository (.hg)
1208 # truncate name relative to the repository (.hg)
1209 path = path[len(repo.path) + 1 :]
1209 path = path[len(repo.path) + 1 :]
1210 if path.startswith(b'cache/'):
1210 if path.startswith(b'cache/'):
1211 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1211 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1212 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1212 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1213 if path.startswith(b'journal.') or path.startswith(b'undo.'):
1213 if path.startswith(b'journal.') or path.startswith(b'undo.'):
1214 # journal is covered by 'lock'
1214 # journal is covered by 'lock'
1215 if repo._currentlock(repo._lockref) is None:
1215 if repo._currentlock(repo._lockref) is None:
1216 repo.ui.develwarn(
1216 repo.ui.develwarn(
1217 b'write with no lock: "%s"' % path,
1217 b'write with no lock: "%s"' % path,
1218 stacklevel=3,
1218 stacklevel=3,
1219 config=b'check-locks',
1219 config=b'check-locks',
1220 )
1220 )
1221 elif repo._currentlock(repo._wlockref) is None:
1221 elif repo._currentlock(repo._wlockref) is None:
1222 # rest of vfs files are covered by 'wlock'
1222 # rest of vfs files are covered by 'wlock'
1223 #
1223 #
1224 # exclude special files
1224 # exclude special files
1225 for prefix in self._wlockfreeprefix:
1225 for prefix in self._wlockfreeprefix:
1226 if path.startswith(prefix):
1226 if path.startswith(prefix):
1227 return
1227 return
1228 repo.ui.develwarn(
1228 repo.ui.develwarn(
1229 b'write with no wlock: "%s"' % path,
1229 b'write with no wlock: "%s"' % path,
1230 stacklevel=3,
1230 stacklevel=3,
1231 config=b'check-locks',
1231 config=b'check-locks',
1232 )
1232 )
1233 return ret
1233 return ret
1234
1234
1235 return checkvfs
1235 return checkvfs
1236
1236
1237 def _getsvfsward(self, origfunc):
1237 def _getsvfsward(self, origfunc):
1238 """build a ward for self.svfs"""
1238 """build a ward for self.svfs"""
1239 rref = weakref.ref(self)
1239 rref = weakref.ref(self)
1240
1240
1241 def checksvfs(path, mode=None):
1241 def checksvfs(path, mode=None):
1242 ret = origfunc(path, mode=mode)
1242 ret = origfunc(path, mode=mode)
1243 repo = rref()
1243 repo = rref()
1244 if repo is None or not util.safehasattr(repo, b'_lockref'):
1244 if repo is None or not util.safehasattr(repo, b'_lockref'):
1245 return
1245 return
1246 if mode in (None, b'r', b'rb'):
1246 if mode in (None, b'r', b'rb'):
1247 return
1247 return
1248 if path.startswith(repo.sharedpath):
1248 if path.startswith(repo.sharedpath):
1249 # truncate name relative to the repository (.hg)
1249 # truncate name relative to the repository (.hg)
1250 path = path[len(repo.sharedpath) + 1 :]
1250 path = path[len(repo.sharedpath) + 1 :]
1251 if repo._currentlock(repo._lockref) is None:
1251 if repo._currentlock(repo._lockref) is None:
1252 repo.ui.develwarn(
1252 repo.ui.develwarn(
1253 b'write with no lock: "%s"' % path, stacklevel=4
1253 b'write with no lock: "%s"' % path, stacklevel=4
1254 )
1254 )
1255 return ret
1255 return ret
1256
1256
1257 return checksvfs
1257 return checksvfs
1258
1258
1259 def close(self):
1259 def close(self):
1260 self._writecaches()
1260 self._writecaches()
1261
1261
1262 def _writecaches(self):
1262 def _writecaches(self):
1263 if self._revbranchcache:
1263 if self._revbranchcache:
1264 self._revbranchcache.write()
1264 self._revbranchcache.write()
1265
1265
1266 def _restrictcapabilities(self, caps):
1266 def _restrictcapabilities(self, caps):
1267 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1267 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1268 caps = set(caps)
1268 caps = set(caps)
1269 capsblob = bundle2.encodecaps(
1269 capsblob = bundle2.encodecaps(
1270 bundle2.getrepocaps(self, role=b'client')
1270 bundle2.getrepocaps(self, role=b'client')
1271 )
1271 )
1272 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1272 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1273 return caps
1273 return caps
1274
1274
1275 def _writerequirements(self):
1275 def _writerequirements(self):
1276 scmutil.writerequires(self.vfs, self.requirements)
1276 scmutil.writerequires(self.vfs, self.requirements)
1277
1277
1278 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1278 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1279 # self -> auditor -> self._checknested -> self
1279 # self -> auditor -> self._checknested -> self
1280
1280
1281 @property
1281 @property
1282 def auditor(self):
1282 def auditor(self):
1283 # This is only used by context.workingctx.match in order to
1283 # This is only used by context.workingctx.match in order to
1284 # detect files in subrepos.
1284 # detect files in subrepos.
1285 return pathutil.pathauditor(self.root, callback=self._checknested)
1285 return pathutil.pathauditor(self.root, callback=self._checknested)
1286
1286
1287 @property
1287 @property
1288 def nofsauditor(self):
1288 def nofsauditor(self):
1289 # This is only used by context.basectx.match in order to detect
1289 # This is only used by context.basectx.match in order to detect
1290 # files in subrepos.
1290 # files in subrepos.
1291 return pathutil.pathauditor(
1291 return pathutil.pathauditor(
1292 self.root, callback=self._checknested, realfs=False, cached=True
1292 self.root, callback=self._checknested, realfs=False, cached=True
1293 )
1293 )
1294
1294
1295 def _checknested(self, path):
1295 def _checknested(self, path):
1296 """Determine if path is a legal nested repository."""
1296 """Determine if path is a legal nested repository."""
1297 if not path.startswith(self.root):
1297 if not path.startswith(self.root):
1298 return False
1298 return False
1299 subpath = path[len(self.root) + 1 :]
1299 subpath = path[len(self.root) + 1 :]
1300 normsubpath = util.pconvert(subpath)
1300 normsubpath = util.pconvert(subpath)
1301
1301
1302 # XXX: Checking against the current working copy is wrong in
1302 # XXX: Checking against the current working copy is wrong in
1303 # the sense that it can reject things like
1303 # the sense that it can reject things like
1304 #
1304 #
1305 # $ hg cat -r 10 sub/x.txt
1305 # $ hg cat -r 10 sub/x.txt
1306 #
1306 #
1307 # if sub/ is no longer a subrepository in the working copy
1307 # if sub/ is no longer a subrepository in the working copy
1308 # parent revision.
1308 # parent revision.
1309 #
1309 #
1310 # However, it can of course also allow things that would have
1310 # However, it can of course also allow things that would have
1311 # been rejected before, such as the above cat command if sub/
1311 # been rejected before, such as the above cat command if sub/
1312 # is a subrepository now, but was a normal directory before.
1312 # is a subrepository now, but was a normal directory before.
1313 # The old path auditor would have rejected by mistake since it
1313 # The old path auditor would have rejected by mistake since it
1314 # panics when it sees sub/.hg/.
1314 # panics when it sees sub/.hg/.
1315 #
1315 #
1316 # All in all, checking against the working copy seems sensible
1316 # All in all, checking against the working copy seems sensible
1317 # since we want to prevent access to nested repositories on
1317 # since we want to prevent access to nested repositories on
1318 # the filesystem *now*.
1318 # the filesystem *now*.
1319 ctx = self[None]
1319 ctx = self[None]
1320 parts = util.splitpath(subpath)
1320 parts = util.splitpath(subpath)
1321 while parts:
1321 while parts:
1322 prefix = b'/'.join(parts)
1322 prefix = b'/'.join(parts)
1323 if prefix in ctx.substate:
1323 if prefix in ctx.substate:
1324 if prefix == normsubpath:
1324 if prefix == normsubpath:
1325 return True
1325 return True
1326 else:
1326 else:
1327 sub = ctx.sub(prefix)
1327 sub = ctx.sub(prefix)
1328 return sub.checknested(subpath[len(prefix) + 1 :])
1328 return sub.checknested(subpath[len(prefix) + 1 :])
1329 else:
1329 else:
1330 parts.pop()
1330 parts.pop()
1331 return False
1331 return False
1332
1332
1333 def peer(self):
1333 def peer(self):
1334 return localpeer(self) # not cached to avoid reference cycle
1334 return localpeer(self) # not cached to avoid reference cycle
1335
1335
1336 def unfiltered(self):
1336 def unfiltered(self):
1337 """Return unfiltered version of the repository
1337 """Return unfiltered version of the repository
1338
1338
1339 Intended to be overwritten by filtered repo."""
1339 Intended to be overwritten by filtered repo."""
1340 return self
1340 return self
1341
1341
1342 def filtered(self, name, visibilityexceptions=None):
1342 def filtered(self, name, visibilityexceptions=None):
1343 """Return a filtered version of a repository
1343 """Return a filtered version of a repository
1344
1344
1345 The `name` parameter is the identifier of the requested view. This
1345 The `name` parameter is the identifier of the requested view. This
1346 will return a repoview object set "exactly" to the specified view.
1346 will return a repoview object set "exactly" to the specified view.
1347
1347
1348 This function does not apply recursive filtering to a repository. For
1348 This function does not apply recursive filtering to a repository. For
1349 example calling `repo.filtered("served")` will return a repoview using
1349 example calling `repo.filtered("served")` will return a repoview using
1350 the "served" view, regardless of the initial view used by `repo`.
1350 the "served" view, regardless of the initial view used by `repo`.
1351
1351
1352 In other word, there is always only one level of `repoview` "filtering".
1352 In other word, there is always only one level of `repoview` "filtering".
1353 """
1353 """
1354 if self._extrafilterid is not None and b'%' not in name:
1354 if self._extrafilterid is not None and b'%' not in name:
1355 name = name + b'%' + self._extrafilterid
1355 name = name + b'%' + self._extrafilterid
1356
1356
1357 cls = repoview.newtype(self.unfiltered().__class__)
1357 cls = repoview.newtype(self.unfiltered().__class__)
1358 return cls(self, name, visibilityexceptions)
1358 return cls(self, name, visibilityexceptions)
1359
1359
1360 @mixedrepostorecache(
1360 @mixedrepostorecache(
1361 (b'bookmarks', b'plain'),
1361 (b'bookmarks', b'plain'),
1362 (b'bookmarks.current', b'plain'),
1362 (b'bookmarks.current', b'plain'),
1363 (b'bookmarks', b''),
1363 (b'bookmarks', b''),
1364 (b'00changelog.i', b''),
1364 (b'00changelog.i', b''),
1365 )
1365 )
1366 def _bookmarks(self):
1366 def _bookmarks(self):
1367 # Since the multiple files involved in the transaction cannot be
1367 # Since the multiple files involved in the transaction cannot be
1368 # written atomically (with current repository format), there is a race
1368 # written atomically (with current repository format), there is a race
1369 # condition here.
1369 # condition here.
1370 #
1370 #
1371 # 1) changelog content A is read
1371 # 1) changelog content A is read
1372 # 2) outside transaction update changelog to content B
1372 # 2) outside transaction update changelog to content B
1373 # 3) outside transaction update bookmark file referring to content B
1373 # 3) outside transaction update bookmark file referring to content B
1374 # 4) bookmarks file content is read and filtered against changelog-A
1374 # 4) bookmarks file content is read and filtered against changelog-A
1375 #
1375 #
1376 # When this happens, bookmarks against nodes missing from A are dropped.
1376 # When this happens, bookmarks against nodes missing from A are dropped.
1377 #
1377 #
1378 # Having this happening during read is not great, but it become worse
1378 # Having this happening during read is not great, but it become worse
1379 # when this happen during write because the bookmarks to the "unknown"
1379 # when this happen during write because the bookmarks to the "unknown"
1380 # nodes will be dropped for good. However, writes happen within locks.
1380 # nodes will be dropped for good. However, writes happen within locks.
1381 # This locking makes it possible to have a race free consistent read.
1381 # This locking makes it possible to have a race free consistent read.
1382 # For this purpose data read from disc before locking are
1382 # For this purpose data read from disc before locking are
1383 # "invalidated" right after the locks are taken. This invalidations are
1383 # "invalidated" right after the locks are taken. This invalidations are
1384 # "light", the `filecache` mechanism keep the data in memory and will
1384 # "light", the `filecache` mechanism keep the data in memory and will
1385 # reuse them if the underlying files did not changed. Not parsing the
1385 # reuse them if the underlying files did not changed. Not parsing the
1386 # same data multiple times helps performances.
1386 # same data multiple times helps performances.
1387 #
1387 #
1388 # Unfortunately in the case describe above, the files tracked by the
1388 # Unfortunately in the case describe above, the files tracked by the
1389 # bookmarks file cache might not have changed, but the in-memory
1389 # bookmarks file cache might not have changed, but the in-memory
1390 # content is still "wrong" because we used an older changelog content
1390 # content is still "wrong" because we used an older changelog content
1391 # to process the on-disk data. So after locking, the changelog would be
1391 # to process the on-disk data. So after locking, the changelog would be
1392 # refreshed but `_bookmarks` would be preserved.
1392 # refreshed but `_bookmarks` would be preserved.
1393 # Adding `00changelog.i` to the list of tracked file is not
1393 # Adding `00changelog.i` to the list of tracked file is not
1394 # enough, because at the time we build the content for `_bookmarks` in
1394 # enough, because at the time we build the content for `_bookmarks` in
1395 # (4), the changelog file has already diverged from the content used
1395 # (4), the changelog file has already diverged from the content used
1396 # for loading `changelog` in (1)
1396 # for loading `changelog` in (1)
1397 #
1397 #
1398 # To prevent the issue, we force the changelog to be explicitly
1398 # To prevent the issue, we force the changelog to be explicitly
1399 # reloaded while computing `_bookmarks`. The data race can still happen
1399 # reloaded while computing `_bookmarks`. The data race can still happen
1400 # without the lock (with a narrower window), but it would no longer go
1400 # without the lock (with a narrower window), but it would no longer go
1401 # undetected during the lock time refresh.
1401 # undetected during the lock time refresh.
1402 #
1402 #
1403 # The new schedule is as follow
1403 # The new schedule is as follow
1404 #
1404 #
1405 # 1) filecache logic detect that `_bookmarks` needs to be computed
1405 # 1) filecache logic detect that `_bookmarks` needs to be computed
1406 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1406 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1407 # 3) We force `changelog` filecache to be tested
1407 # 3) We force `changelog` filecache to be tested
1408 # 4) cachestat for `changelog` are captured (for changelog)
1408 # 4) cachestat for `changelog` are captured (for changelog)
1409 # 5) `_bookmarks` is computed and cached
1409 # 5) `_bookmarks` is computed and cached
1410 #
1410 #
1411 # The step in (3) ensure we have a changelog at least as recent as the
1411 # The step in (3) ensure we have a changelog at least as recent as the
1412 # cache stat computed in (1). As a result at locking time:
1412 # cache stat computed in (1). As a result at locking time:
1413 # * if the changelog did not changed since (1) -> we can reuse the data
1413 # * if the changelog did not changed since (1) -> we can reuse the data
1414 # * otherwise -> the bookmarks get refreshed.
1414 # * otherwise -> the bookmarks get refreshed.
1415 self._refreshchangelog()
1415 self._refreshchangelog()
1416 return bookmarks.bmstore(self)
1416 return bookmarks.bmstore(self)
1417
1417
1418 def _refreshchangelog(self):
1418 def _refreshchangelog(self):
1419 """make sure the in memory changelog match the on-disk one"""
1419 """make sure the in memory changelog match the on-disk one"""
1420 if 'changelog' in vars(self) and self.currenttransaction() is None:
1420 if 'changelog' in vars(self) and self.currenttransaction() is None:
1421 del self.changelog
1421 del self.changelog
1422
1422
1423 @property
1423 @property
1424 def _activebookmark(self):
1424 def _activebookmark(self):
1425 return self._bookmarks.active
1425 return self._bookmarks.active
1426
1426
1427 # _phasesets depend on changelog. what we need is to call
1427 # _phasesets depend on changelog. what we need is to call
1428 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1428 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1429 # can't be easily expressed in filecache mechanism.
1429 # can't be easily expressed in filecache mechanism.
1430 @storecache(b'phaseroots', b'00changelog.i')
1430 @storecache(b'phaseroots', b'00changelog.i')
1431 def _phasecache(self):
1431 def _phasecache(self):
1432 return phases.phasecache(self, self._phasedefaults)
1432 return phases.phasecache(self, self._phasedefaults)
1433
1433
1434 @storecache(b'obsstore')
1434 @storecache(b'obsstore')
1435 def obsstore(self):
1435 def obsstore(self):
1436 return obsolete.makestore(self.ui, self)
1436 return obsolete.makestore(self.ui, self)
1437
1437
1438 @storecache(b'00changelog.i')
1438 @storecache(b'00changelog.i')
1439 def changelog(self):
1439 def changelog(self):
1440 return self.store.changelog(txnutil.mayhavepending(self.root))
1440 return self.store.changelog(txnutil.mayhavepending(self.root))
1441
1441
1442 @storecache(b'00manifest.i')
1442 @storecache(b'00manifest.i')
1443 def manifestlog(self):
1443 def manifestlog(self):
1444 return self.store.manifestlog(self, self._storenarrowmatch)
1444 return self.store.manifestlog(self, self._storenarrowmatch)
1445
1445
1446 @repofilecache(b'dirstate')
1446 @repofilecache(b'dirstate')
1447 def dirstate(self):
1447 def dirstate(self):
1448 return self._makedirstate()
1448 return self._makedirstate()
1449
1449
1450 def _makedirstate(self):
1450 def _makedirstate(self):
1451 """Extension point for wrapping the dirstate per-repo."""
1451 """Extension point for wrapping the dirstate per-repo."""
1452 sparsematchfn = lambda: sparse.matcher(self)
1452 sparsematchfn = lambda: sparse.matcher(self)
1453
1453
1454 return dirstate.dirstate(
1454 return dirstate.dirstate(
1455 self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn
1455 self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn
1456 )
1456 )
1457
1457
1458 def _dirstatevalidate(self, node):
1458 def _dirstatevalidate(self, node):
1459 try:
1459 try:
1460 self.changelog.rev(node)
1460 self.changelog.rev(node)
1461 return node
1461 return node
1462 except error.LookupError:
1462 except error.LookupError:
1463 if not self._dirstatevalidatewarned:
1463 if not self._dirstatevalidatewarned:
1464 self._dirstatevalidatewarned = True
1464 self._dirstatevalidatewarned = True
1465 self.ui.warn(
1465 self.ui.warn(
1466 _(b"warning: ignoring unknown working parent %s!\n")
1466 _(b"warning: ignoring unknown working parent %s!\n")
1467 % short(node)
1467 % short(node)
1468 )
1468 )
1469 return nullid
1469 return nullid
1470
1470
1471 @storecache(narrowspec.FILENAME)
1471 @storecache(narrowspec.FILENAME)
1472 def narrowpats(self):
1472 def narrowpats(self):
1473 """matcher patterns for this repository's narrowspec
1473 """matcher patterns for this repository's narrowspec
1474
1474
1475 A tuple of (includes, excludes).
1475 A tuple of (includes, excludes).
1476 """
1476 """
1477 return narrowspec.load(self)
1477 return narrowspec.load(self)
1478
1478
1479 @storecache(narrowspec.FILENAME)
1479 @storecache(narrowspec.FILENAME)
1480 def _storenarrowmatch(self):
1480 def _storenarrowmatch(self):
1481 if repository.NARROW_REQUIREMENT not in self.requirements:
1481 if repository.NARROW_REQUIREMENT not in self.requirements:
1482 return matchmod.always()
1482 return matchmod.always()
1483 include, exclude = self.narrowpats
1483 include, exclude = self.narrowpats
1484 return narrowspec.match(self.root, include=include, exclude=exclude)
1484 return narrowspec.match(self.root, include=include, exclude=exclude)
1485
1485
1486 @storecache(narrowspec.FILENAME)
1486 @storecache(narrowspec.FILENAME)
1487 def _narrowmatch(self):
1487 def _narrowmatch(self):
1488 if repository.NARROW_REQUIREMENT not in self.requirements:
1488 if repository.NARROW_REQUIREMENT not in self.requirements:
1489 return matchmod.always()
1489 return matchmod.always()
1490 narrowspec.checkworkingcopynarrowspec(self)
1490 narrowspec.checkworkingcopynarrowspec(self)
1491 include, exclude = self.narrowpats
1491 include, exclude = self.narrowpats
1492 return narrowspec.match(self.root, include=include, exclude=exclude)
1492 return narrowspec.match(self.root, include=include, exclude=exclude)
1493
1493
1494 def narrowmatch(self, match=None, includeexact=False):
1494 def narrowmatch(self, match=None, includeexact=False):
1495 """matcher corresponding the the repo's narrowspec
1495 """matcher corresponding the the repo's narrowspec
1496
1496
1497 If `match` is given, then that will be intersected with the narrow
1497 If `match` is given, then that will be intersected with the narrow
1498 matcher.
1498 matcher.
1499
1499
1500 If `includeexact` is True, then any exact matches from `match` will
1500 If `includeexact` is True, then any exact matches from `match` will
1501 be included even if they're outside the narrowspec.
1501 be included even if they're outside the narrowspec.
1502 """
1502 """
1503 if match:
1503 if match:
1504 if includeexact and not self._narrowmatch.always():
1504 if includeexact and not self._narrowmatch.always():
1505 # do not exclude explicitly-specified paths so that they can
1505 # do not exclude explicitly-specified paths so that they can
1506 # be warned later on
1506 # be warned later on
1507 em = matchmod.exact(match.files())
1507 em = matchmod.exact(match.files())
1508 nm = matchmod.unionmatcher([self._narrowmatch, em])
1508 nm = matchmod.unionmatcher([self._narrowmatch, em])
1509 return matchmod.intersectmatchers(match, nm)
1509 return matchmod.intersectmatchers(match, nm)
1510 return matchmod.intersectmatchers(match, self._narrowmatch)
1510 return matchmod.intersectmatchers(match, self._narrowmatch)
1511 return self._narrowmatch
1511 return self._narrowmatch
1512
1512
1513 def setnarrowpats(self, newincludes, newexcludes):
1513 def setnarrowpats(self, newincludes, newexcludes):
1514 narrowspec.save(self, newincludes, newexcludes)
1514 narrowspec.save(self, newincludes, newexcludes)
1515 self.invalidate(clearfilecache=True)
1515 self.invalidate(clearfilecache=True)
1516
1516
1517 def __getitem__(self, changeid):
1517 def __getitem__(self, changeid):
1518 if changeid is None:
1518 if changeid is None:
1519 return context.workingctx(self)
1519 return context.workingctx(self)
1520 if isinstance(changeid, context.basectx):
1520 if isinstance(changeid, context.basectx):
1521 return changeid
1521 return changeid
1522 if isinstance(changeid, slice):
1522 if isinstance(changeid, slice):
1523 # wdirrev isn't contiguous so the slice shouldn't include it
1523 # wdirrev isn't contiguous so the slice shouldn't include it
1524 return [
1524 return [
1525 self[i]
1525 self[i]
1526 for i in pycompat.xrange(*changeid.indices(len(self)))
1526 for i in pycompat.xrange(*changeid.indices(len(self)))
1527 if i not in self.changelog.filteredrevs
1527 if i not in self.changelog.filteredrevs
1528 ]
1528 ]
1529 try:
1529 try:
1530 if isinstance(changeid, int):
1530 if isinstance(changeid, int):
1531 node = self.changelog.node(changeid)
1531 node = self.changelog.node(changeid)
1532 rev = changeid
1532 rev = changeid
1533 elif changeid == b'null':
1533 elif changeid == b'null':
1534 node = nullid
1534 node = nullid
1535 rev = nullrev
1535 rev = nullrev
1536 elif changeid == b'tip':
1536 elif changeid == b'tip':
1537 node = self.changelog.tip()
1537 node = self.changelog.tip()
1538 rev = self.changelog.rev(node)
1538 rev = self.changelog.rev(node)
1539 elif changeid == b'.':
1539 elif changeid == b'.':
1540 # this is a hack to delay/avoid loading obsmarkers
1540 # this is a hack to delay/avoid loading obsmarkers
1541 # when we know that '.' won't be hidden
1541 # when we know that '.' won't be hidden
1542 node = self.dirstate.p1()
1542 node = self.dirstate.p1()
1543 rev = self.unfiltered().changelog.rev(node)
1543 rev = self.unfiltered().changelog.rev(node)
1544 elif len(changeid) == 20:
1544 elif len(changeid) == 20:
1545 try:
1545 try:
1546 node = changeid
1546 node = changeid
1547 rev = self.changelog.rev(changeid)
1547 rev = self.changelog.rev(changeid)
1548 except error.FilteredLookupError:
1548 except error.FilteredLookupError:
1549 changeid = hex(changeid) # for the error message
1549 changeid = hex(changeid) # for the error message
1550 raise
1550 raise
1551 except LookupError:
1551 except LookupError:
1552 # check if it might have come from damaged dirstate
1552 # check if it might have come from damaged dirstate
1553 #
1553 #
1554 # XXX we could avoid the unfiltered if we had a recognizable
1554 # XXX we could avoid the unfiltered if we had a recognizable
1555 # exception for filtered changeset access
1555 # exception for filtered changeset access
1556 if (
1556 if (
1557 self.local()
1557 self.local()
1558 and changeid in self.unfiltered().dirstate.parents()
1558 and changeid in self.unfiltered().dirstate.parents()
1559 ):
1559 ):
1560 msg = _(b"working directory has unknown parent '%s'!")
1560 msg = _(b"working directory has unknown parent '%s'!")
1561 raise error.Abort(msg % short(changeid))
1561 raise error.Abort(msg % short(changeid))
1562 changeid = hex(changeid) # for the error message
1562 changeid = hex(changeid) # for the error message
1563 raise
1563 raise
1564
1564
1565 elif len(changeid) == 40:
1565 elif len(changeid) == 40:
1566 node = bin(changeid)
1566 node = bin(changeid)
1567 rev = self.changelog.rev(node)
1567 rev = self.changelog.rev(node)
1568 else:
1568 else:
1569 raise error.ProgrammingError(
1569 raise error.ProgrammingError(
1570 b"unsupported changeid '%s' of type %s"
1570 b"unsupported changeid '%s' of type %s"
1571 % (changeid, pycompat.sysstr(type(changeid)))
1571 % (changeid, pycompat.sysstr(type(changeid)))
1572 )
1572 )
1573
1573
1574 return context.changectx(self, rev, node)
1574 return context.changectx(self, rev, node)
1575
1575
1576 except (error.FilteredIndexError, error.FilteredLookupError):
1576 except (error.FilteredIndexError, error.FilteredLookupError):
1577 raise error.FilteredRepoLookupError(
1577 raise error.FilteredRepoLookupError(
1578 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1578 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1579 )
1579 )
1580 except (IndexError, LookupError):
1580 except (IndexError, LookupError):
1581 raise error.RepoLookupError(
1581 raise error.RepoLookupError(
1582 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1582 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1583 )
1583 )
1584 except error.WdirUnsupported:
1584 except error.WdirUnsupported:
1585 return context.workingctx(self)
1585 return context.workingctx(self)
1586
1586
1587 def __contains__(self, changeid):
1587 def __contains__(self, changeid):
1588 """True if the given changeid exists
1588 """True if the given changeid exists
1589
1589
1590 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1590 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1591 specified.
1591 specified.
1592 """
1592 """
1593 try:
1593 try:
1594 self[changeid]
1594 self[changeid]
1595 return True
1595 return True
1596 except error.RepoLookupError:
1596 except error.RepoLookupError:
1597 return False
1597 return False
1598
1598
1599 def __nonzero__(self):
1599 def __nonzero__(self):
1600 return True
1600 return True
1601
1601
1602 __bool__ = __nonzero__
1602 __bool__ = __nonzero__
1603
1603
1604 def __len__(self):
1604 def __len__(self):
1605 # no need to pay the cost of repoview.changelog
1605 # no need to pay the cost of repoview.changelog
1606 unfi = self.unfiltered()
1606 unfi = self.unfiltered()
1607 return len(unfi.changelog)
1607 return len(unfi.changelog)
1608
1608
1609 def __iter__(self):
1609 def __iter__(self):
1610 return iter(self.changelog)
1610 return iter(self.changelog)
1611
1611
1612 def revs(self, expr, *args):
1612 def revs(self, expr, *args):
1613 '''Find revisions matching a revset.
1613 '''Find revisions matching a revset.
1614
1614
1615 The revset is specified as a string ``expr`` that may contain
1615 The revset is specified as a string ``expr`` that may contain
1616 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1616 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1617
1617
1618 Revset aliases from the configuration are not expanded. To expand
1618 Revset aliases from the configuration are not expanded. To expand
1619 user aliases, consider calling ``scmutil.revrange()`` or
1619 user aliases, consider calling ``scmutil.revrange()`` or
1620 ``repo.anyrevs([expr], user=True)``.
1620 ``repo.anyrevs([expr], user=True)``.
1621
1621
1622 Returns a revset.abstractsmartset, which is a list-like interface
1622 Returns a revset.abstractsmartset, which is a list-like interface
1623 that contains integer revisions.
1623 that contains integer revisions.
1624 '''
1624 '''
1625 tree = revsetlang.spectree(expr, *args)
1625 tree = revsetlang.spectree(expr, *args)
1626 return revset.makematcher(tree)(self)
1626 return revset.makematcher(tree)(self)
1627
1627
1628 def set(self, expr, *args):
1628 def set(self, expr, *args):
1629 '''Find revisions matching a revset and emit changectx instances.
1629 '''Find revisions matching a revset and emit changectx instances.
1630
1630
1631 This is a convenience wrapper around ``revs()`` that iterates the
1631 This is a convenience wrapper around ``revs()`` that iterates the
1632 result and is a generator of changectx instances.
1632 result and is a generator of changectx instances.
1633
1633
1634 Revset aliases from the configuration are not expanded. To expand
1634 Revset aliases from the configuration are not expanded. To expand
1635 user aliases, consider calling ``scmutil.revrange()``.
1635 user aliases, consider calling ``scmutil.revrange()``.
1636 '''
1636 '''
1637 for r in self.revs(expr, *args):
1637 for r in self.revs(expr, *args):
1638 yield self[r]
1638 yield self[r]
1639
1639
1640 def anyrevs(self, specs, user=False, localalias=None):
1640 def anyrevs(self, specs, user=False, localalias=None):
1641 '''Find revisions matching one of the given revsets.
1641 '''Find revisions matching one of the given revsets.
1642
1642
1643 Revset aliases from the configuration are not expanded by default. To
1643 Revset aliases from the configuration are not expanded by default. To
1644 expand user aliases, specify ``user=True``. To provide some local
1644 expand user aliases, specify ``user=True``. To provide some local
1645 definitions overriding user aliases, set ``localalias`` to
1645 definitions overriding user aliases, set ``localalias`` to
1646 ``{name: definitionstring}``.
1646 ``{name: definitionstring}``.
1647 '''
1647 '''
1648 if user:
1648 if user:
1649 m = revset.matchany(
1649 m = revset.matchany(
1650 self.ui,
1650 self.ui,
1651 specs,
1651 specs,
1652 lookup=revset.lookupfn(self),
1652 lookup=revset.lookupfn(self),
1653 localalias=localalias,
1653 localalias=localalias,
1654 )
1654 )
1655 else:
1655 else:
1656 m = revset.matchany(None, specs, localalias=localalias)
1656 m = revset.matchany(None, specs, localalias=localalias)
1657 return m(self)
1657 return m(self)
1658
1658
1659 def url(self):
1659 def url(self):
1660 return b'file:' + self.root
1660 return b'file:' + self.root
1661
1661
1662 def hook(self, name, throw=False, **args):
1662 def hook(self, name, throw=False, **args):
1663 """Call a hook, passing this repo instance.
1663 """Call a hook, passing this repo instance.
1664
1664
1665 This a convenience method to aid invoking hooks. Extensions likely
1665 This a convenience method to aid invoking hooks. Extensions likely
1666 won't call this unless they have registered a custom hook or are
1666 won't call this unless they have registered a custom hook or are
1667 replacing code that is expected to call a hook.
1667 replacing code that is expected to call a hook.
1668 """
1668 """
1669 return hook.hook(self.ui, self, name, throw, **args)
1669 return hook.hook(self.ui, self, name, throw, **args)
1670
1670
1671 @filteredpropertycache
1671 @filteredpropertycache
1672 def _tagscache(self):
1672 def _tagscache(self):
1673 '''Returns a tagscache object that contains various tags related
1673 '''Returns a tagscache object that contains various tags related
1674 caches.'''
1674 caches.'''
1675
1675
1676 # This simplifies its cache management by having one decorated
1676 # This simplifies its cache management by having one decorated
1677 # function (this one) and the rest simply fetch things from it.
1677 # function (this one) and the rest simply fetch things from it.
1678 class tagscache(object):
1678 class tagscache(object):
1679 def __init__(self):
1679 def __init__(self):
1680 # These two define the set of tags for this repository. tags
1680 # These two define the set of tags for this repository. tags
1681 # maps tag name to node; tagtypes maps tag name to 'global' or
1681 # maps tag name to node; tagtypes maps tag name to 'global' or
1682 # 'local'. (Global tags are defined by .hgtags across all
1682 # 'local'. (Global tags are defined by .hgtags across all
1683 # heads, and local tags are defined in .hg/localtags.)
1683 # heads, and local tags are defined in .hg/localtags.)
1684 # They constitute the in-memory cache of tags.
1684 # They constitute the in-memory cache of tags.
1685 self.tags = self.tagtypes = None
1685 self.tags = self.tagtypes = None
1686
1686
1687 self.nodetagscache = self.tagslist = None
1687 self.nodetagscache = self.tagslist = None
1688
1688
1689 cache = tagscache()
1689 cache = tagscache()
1690 cache.tags, cache.tagtypes = self._findtags()
1690 cache.tags, cache.tagtypes = self._findtags()
1691
1691
1692 return cache
1692 return cache
1693
1693
1694 def tags(self):
1694 def tags(self):
1695 '''return a mapping of tag to node'''
1695 '''return a mapping of tag to node'''
1696 t = {}
1696 t = {}
1697 if self.changelog.filteredrevs:
1697 if self.changelog.filteredrevs:
1698 tags, tt = self._findtags()
1698 tags, tt = self._findtags()
1699 else:
1699 else:
1700 tags = self._tagscache.tags
1700 tags = self._tagscache.tags
1701 rev = self.changelog.rev
1701 rev = self.changelog.rev
1702 for k, v in pycompat.iteritems(tags):
1702 for k, v in pycompat.iteritems(tags):
1703 try:
1703 try:
1704 # ignore tags to unknown nodes
1704 # ignore tags to unknown nodes
1705 rev(v)
1705 rev(v)
1706 t[k] = v
1706 t[k] = v
1707 except (error.LookupError, ValueError):
1707 except (error.LookupError, ValueError):
1708 pass
1708 pass
1709 return t
1709 return t
1710
1710
1711 def _findtags(self):
1711 def _findtags(self):
1712 '''Do the hard work of finding tags. Return a pair of dicts
1712 '''Do the hard work of finding tags. Return a pair of dicts
1713 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1713 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1714 maps tag name to a string like \'global\' or \'local\'.
1714 maps tag name to a string like \'global\' or \'local\'.
1715 Subclasses or extensions are free to add their own tags, but
1715 Subclasses or extensions are free to add their own tags, but
1716 should be aware that the returned dicts will be retained for the
1716 should be aware that the returned dicts will be retained for the
1717 duration of the localrepo object.'''
1717 duration of the localrepo object.'''
1718
1718
1719 # XXX what tagtype should subclasses/extensions use? Currently
1719 # XXX what tagtype should subclasses/extensions use? Currently
1720 # mq and bookmarks add tags, but do not set the tagtype at all.
1720 # mq and bookmarks add tags, but do not set the tagtype at all.
1721 # Should each extension invent its own tag type? Should there
1721 # Should each extension invent its own tag type? Should there
1722 # be one tagtype for all such "virtual" tags? Or is the status
1722 # be one tagtype for all such "virtual" tags? Or is the status
1723 # quo fine?
1723 # quo fine?
1724
1724
1725 # map tag name to (node, hist)
1725 # map tag name to (node, hist)
1726 alltags = tagsmod.findglobaltags(self.ui, self)
1726 alltags = tagsmod.findglobaltags(self.ui, self)
1727 # map tag name to tag type
1727 # map tag name to tag type
1728 tagtypes = dict((tag, b'global') for tag in alltags)
1728 tagtypes = dict((tag, b'global') for tag in alltags)
1729
1729
1730 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1730 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1731
1731
1732 # Build the return dicts. Have to re-encode tag names because
1732 # Build the return dicts. Have to re-encode tag names because
1733 # the tags module always uses UTF-8 (in order not to lose info
1733 # the tags module always uses UTF-8 (in order not to lose info
1734 # writing to the cache), but the rest of Mercurial wants them in
1734 # writing to the cache), but the rest of Mercurial wants them in
1735 # local encoding.
1735 # local encoding.
1736 tags = {}
1736 tags = {}
1737 for (name, (node, hist)) in pycompat.iteritems(alltags):
1737 for (name, (node, hist)) in pycompat.iteritems(alltags):
1738 if node != nullid:
1738 if node != nullid:
1739 tags[encoding.tolocal(name)] = node
1739 tags[encoding.tolocal(name)] = node
1740 tags[b'tip'] = self.changelog.tip()
1740 tags[b'tip'] = self.changelog.tip()
1741 tagtypes = dict(
1741 tagtypes = dict(
1742 [
1742 [
1743 (encoding.tolocal(name), value)
1743 (encoding.tolocal(name), value)
1744 for (name, value) in pycompat.iteritems(tagtypes)
1744 for (name, value) in pycompat.iteritems(tagtypes)
1745 ]
1745 ]
1746 )
1746 )
1747 return (tags, tagtypes)
1747 return (tags, tagtypes)
1748
1748
1749 def tagtype(self, tagname):
1749 def tagtype(self, tagname):
1750 '''
1750 '''
1751 return the type of the given tag. result can be:
1751 return the type of the given tag. result can be:
1752
1752
1753 'local' : a local tag
1753 'local' : a local tag
1754 'global' : a global tag
1754 'global' : a global tag
1755 None : tag does not exist
1755 None : tag does not exist
1756 '''
1756 '''
1757
1757
1758 return self._tagscache.tagtypes.get(tagname)
1758 return self._tagscache.tagtypes.get(tagname)
1759
1759
1760 def tagslist(self):
1760 def tagslist(self):
1761 '''return a list of tags ordered by revision'''
1761 '''return a list of tags ordered by revision'''
1762 if not self._tagscache.tagslist:
1762 if not self._tagscache.tagslist:
1763 l = []
1763 l = []
1764 for t, n in pycompat.iteritems(self.tags()):
1764 for t, n in pycompat.iteritems(self.tags()):
1765 l.append((self.changelog.rev(n), t, n))
1765 l.append((self.changelog.rev(n), t, n))
1766 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1766 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1767
1767
1768 return self._tagscache.tagslist
1768 return self._tagscache.tagslist
1769
1769
1770 def nodetags(self, node):
1770 def nodetags(self, node):
1771 '''return the tags associated with a node'''
1771 '''return the tags associated with a node'''
1772 if not self._tagscache.nodetagscache:
1772 if not self._tagscache.nodetagscache:
1773 nodetagscache = {}
1773 nodetagscache = {}
1774 for t, n in pycompat.iteritems(self._tagscache.tags):
1774 for t, n in pycompat.iteritems(self._tagscache.tags):
1775 nodetagscache.setdefault(n, []).append(t)
1775 nodetagscache.setdefault(n, []).append(t)
1776 for tags in pycompat.itervalues(nodetagscache):
1776 for tags in pycompat.itervalues(nodetagscache):
1777 tags.sort()
1777 tags.sort()
1778 self._tagscache.nodetagscache = nodetagscache
1778 self._tagscache.nodetagscache = nodetagscache
1779 return self._tagscache.nodetagscache.get(node, [])
1779 return self._tagscache.nodetagscache.get(node, [])
1780
1780
1781 def nodebookmarks(self, node):
1781 def nodebookmarks(self, node):
1782 """return the list of bookmarks pointing to the specified node"""
1782 """return the list of bookmarks pointing to the specified node"""
1783 return self._bookmarks.names(node)
1783 return self._bookmarks.names(node)
1784
1784
1785 def branchmap(self):
1785 def branchmap(self):
1786 '''returns a dictionary {branch: [branchheads]} with branchheads
1786 '''returns a dictionary {branch: [branchheads]} with branchheads
1787 ordered by increasing revision number'''
1787 ordered by increasing revision number'''
1788 return self._branchcaches[self]
1788 return self._branchcaches[self]
1789
1789
1790 @unfilteredmethod
1790 @unfilteredmethod
1791 def revbranchcache(self):
1791 def revbranchcache(self):
1792 if not self._revbranchcache:
1792 if not self._revbranchcache:
1793 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1793 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1794 return self._revbranchcache
1794 return self._revbranchcache
1795
1795
1796 def branchtip(self, branch, ignoremissing=False):
1796 def branchtip(self, branch, ignoremissing=False):
1797 '''return the tip node for a given branch
1797 '''return the tip node for a given branch
1798
1798
1799 If ignoremissing is True, then this method will not raise an error.
1799 If ignoremissing is True, then this method will not raise an error.
1800 This is helpful for callers that only expect None for a missing branch
1800 This is helpful for callers that only expect None for a missing branch
1801 (e.g. namespace).
1801 (e.g. namespace).
1802
1802
1803 '''
1803 '''
1804 try:
1804 try:
1805 return self.branchmap().branchtip(branch)
1805 return self.branchmap().branchtip(branch)
1806 except KeyError:
1806 except KeyError:
1807 if not ignoremissing:
1807 if not ignoremissing:
1808 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
1808 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
1809 else:
1809 else:
1810 pass
1810 pass
1811
1811
1812 def lookup(self, key):
1812 def lookup(self, key):
1813 node = scmutil.revsymbol(self, key).node()
1813 node = scmutil.revsymbol(self, key).node()
1814 if node is None:
1814 if node is None:
1815 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
1815 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
1816 return node
1816 return node
1817
1817
1818 def lookupbranch(self, key):
1818 def lookupbranch(self, key):
1819 if self.branchmap().hasbranch(key):
1819 if self.branchmap().hasbranch(key):
1820 return key
1820 return key
1821
1821
1822 return scmutil.revsymbol(self, key).branch()
1822 return scmutil.revsymbol(self, key).branch()
1823
1823
1824 def known(self, nodes):
1824 def known(self, nodes):
1825 cl = self.changelog
1825 cl = self.changelog
1826 get_rev = cl.index.get_rev
1826 get_rev = cl.index.get_rev
1827 filtered = cl.filteredrevs
1827 filtered = cl.filteredrevs
1828 result = []
1828 result = []
1829 for n in nodes:
1829 for n in nodes:
1830 r = get_rev(n)
1830 r = get_rev(n)
1831 resp = not (r is None or r in filtered)
1831 resp = not (r is None or r in filtered)
1832 result.append(resp)
1832 result.append(resp)
1833 return result
1833 return result
1834
1834
1835 def local(self):
1835 def local(self):
1836 return self
1836 return self
1837
1837
1838 def publishing(self):
1838 def publishing(self):
1839 # it's safe (and desirable) to trust the publish flag unconditionally
1839 # it's safe (and desirable) to trust the publish flag unconditionally
1840 # so that we don't finalize changes shared between users via ssh or nfs
1840 # so that we don't finalize changes shared between users via ssh or nfs
1841 return self.ui.configbool(b'phases', b'publish', untrusted=True)
1841 return self.ui.configbool(b'phases', b'publish', untrusted=True)
1842
1842
1843 def cancopy(self):
1843 def cancopy(self):
1844 # so statichttprepo's override of local() works
1844 # so statichttprepo's override of local() works
1845 if not self.local():
1845 if not self.local():
1846 return False
1846 return False
1847 if not self.publishing():
1847 if not self.publishing():
1848 return True
1848 return True
1849 # if publishing we can't copy if there is filtered content
1849 # if publishing we can't copy if there is filtered content
1850 return not self.filtered(b'visible').changelog.filteredrevs
1850 return not self.filtered(b'visible').changelog.filteredrevs
1851
1851
1852 def shared(self):
1852 def shared(self):
1853 '''the type of shared repository (None if not shared)'''
1853 '''the type of shared repository (None if not shared)'''
1854 if self.sharedpath != self.path:
1854 if self.sharedpath != self.path:
1855 return b'store'
1855 return b'store'
1856 return None
1856 return None
1857
1857
1858 def wjoin(self, f, *insidef):
1858 def wjoin(self, f, *insidef):
1859 return self.vfs.reljoin(self.root, f, *insidef)
1859 return self.vfs.reljoin(self.root, f, *insidef)
1860
1860
1861 def setparents(self, p1, p2=nullid):
1861 def setparents(self, p1, p2=nullid):
1862 with self.dirstate.parentchange():
1862 with self.dirstate.parentchange():
1863 copies = self.dirstate.setparents(p1, p2)
1863 copies = self.dirstate.setparents(p1, p2)
1864 pctx = self[p1]
1864 pctx = self[p1]
1865 if copies:
1865 if copies:
1866 # Adjust copy records, the dirstate cannot do it, it
1866 # Adjust copy records, the dirstate cannot do it, it
1867 # requires access to parents manifests. Preserve them
1867 # requires access to parents manifests. Preserve them
1868 # only for entries added to first parent.
1868 # only for entries added to first parent.
1869 for f in copies:
1869 for f in copies:
1870 if f not in pctx and copies[f] in pctx:
1870 if f not in pctx and copies[f] in pctx:
1871 self.dirstate.copy(copies[f], f)
1871 self.dirstate.copy(copies[f], f)
1872 if p2 == nullid:
1872 if p2 == nullid:
1873 for f, s in sorted(self.dirstate.copies().items()):
1873 for f, s in sorted(self.dirstate.copies().items()):
1874 if f not in pctx and s not in pctx:
1874 if f not in pctx and s not in pctx:
1875 self.dirstate.copy(None, f)
1875 self.dirstate.copy(None, f)
1876
1876
1877 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1877 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1878 """changeid must be a changeset revision, if specified.
1878 """changeid must be a changeset revision, if specified.
1879 fileid can be a file revision or node."""
1879 fileid can be a file revision or node."""
1880 return context.filectx(
1880 return context.filectx(
1881 self, path, changeid, fileid, changectx=changectx
1881 self, path, changeid, fileid, changectx=changectx
1882 )
1882 )
1883
1883
1884 def getcwd(self):
1884 def getcwd(self):
1885 return self.dirstate.getcwd()
1885 return self.dirstate.getcwd()
1886
1886
1887 def pathto(self, f, cwd=None):
1887 def pathto(self, f, cwd=None):
1888 return self.dirstate.pathto(f, cwd)
1888 return self.dirstate.pathto(f, cwd)
1889
1889
1890 def _loadfilter(self, filter):
1890 def _loadfilter(self, filter):
1891 if filter not in self._filterpats:
1891 if filter not in self._filterpats:
1892 l = []
1892 l = []
1893 for pat, cmd in self.ui.configitems(filter):
1893 for pat, cmd in self.ui.configitems(filter):
1894 if cmd == b'!':
1894 if cmd == b'!':
1895 continue
1895 continue
1896 mf = matchmod.match(self.root, b'', [pat])
1896 mf = matchmod.match(self.root, b'', [pat])
1897 fn = None
1897 fn = None
1898 params = cmd
1898 params = cmd
1899 for name, filterfn in pycompat.iteritems(self._datafilters):
1899 for name, filterfn in pycompat.iteritems(self._datafilters):
1900 if cmd.startswith(name):
1900 if cmd.startswith(name):
1901 fn = filterfn
1901 fn = filterfn
1902 params = cmd[len(name) :].lstrip()
1902 params = cmd[len(name) :].lstrip()
1903 break
1903 break
1904 if not fn:
1904 if not fn:
1905 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1905 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1906 fn.__name__ = 'commandfilter'
1906 fn.__name__ = 'commandfilter'
1907 # Wrap old filters not supporting keyword arguments
1907 # Wrap old filters not supporting keyword arguments
1908 if not pycompat.getargspec(fn)[2]:
1908 if not pycompat.getargspec(fn)[2]:
1909 oldfn = fn
1909 oldfn = fn
1910 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
1910 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
1911 fn.__name__ = 'compat-' + oldfn.__name__
1911 fn.__name__ = 'compat-' + oldfn.__name__
1912 l.append((mf, fn, params))
1912 l.append((mf, fn, params))
1913 self._filterpats[filter] = l
1913 self._filterpats[filter] = l
1914 return self._filterpats[filter]
1914 return self._filterpats[filter]
1915
1915
1916 def _filter(self, filterpats, filename, data):
1916 def _filter(self, filterpats, filename, data):
1917 for mf, fn, cmd in filterpats:
1917 for mf, fn, cmd in filterpats:
1918 if mf(filename):
1918 if mf(filename):
1919 self.ui.debug(
1919 self.ui.debug(
1920 b"filtering %s through %s\n"
1920 b"filtering %s through %s\n"
1921 % (filename, cmd or pycompat.sysbytes(fn.__name__))
1921 % (filename, cmd or pycompat.sysbytes(fn.__name__))
1922 )
1922 )
1923 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1923 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1924 break
1924 break
1925
1925
1926 return data
1926 return data
1927
1927
1928 @unfilteredpropertycache
1928 @unfilteredpropertycache
1929 def _encodefilterpats(self):
1929 def _encodefilterpats(self):
1930 return self._loadfilter(b'encode')
1930 return self._loadfilter(b'encode')
1931
1931
1932 @unfilteredpropertycache
1932 @unfilteredpropertycache
1933 def _decodefilterpats(self):
1933 def _decodefilterpats(self):
1934 return self._loadfilter(b'decode')
1934 return self._loadfilter(b'decode')
1935
1935
1936 def adddatafilter(self, name, filter):
1936 def adddatafilter(self, name, filter):
1937 self._datafilters[name] = filter
1937 self._datafilters[name] = filter
1938
1938
1939 def wread(self, filename):
1939 def wread(self, filename):
1940 if self.wvfs.islink(filename):
1940 if self.wvfs.islink(filename):
1941 data = self.wvfs.readlink(filename)
1941 data = self.wvfs.readlink(filename)
1942 else:
1942 else:
1943 data = self.wvfs.read(filename)
1943 data = self.wvfs.read(filename)
1944 return self._filter(self._encodefilterpats, filename, data)
1944 return self._filter(self._encodefilterpats, filename, data)
1945
1945
1946 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1946 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1947 """write ``data`` into ``filename`` in the working directory
1947 """write ``data`` into ``filename`` in the working directory
1948
1948
1949 This returns length of written (maybe decoded) data.
1949 This returns length of written (maybe decoded) data.
1950 """
1950 """
1951 data = self._filter(self._decodefilterpats, filename, data)
1951 data = self._filter(self._decodefilterpats, filename, data)
1952 if b'l' in flags:
1952 if b'l' in flags:
1953 self.wvfs.symlink(data, filename)
1953 self.wvfs.symlink(data, filename)
1954 else:
1954 else:
1955 self.wvfs.write(
1955 self.wvfs.write(
1956 filename, data, backgroundclose=backgroundclose, **kwargs
1956 filename, data, backgroundclose=backgroundclose, **kwargs
1957 )
1957 )
1958 if b'x' in flags:
1958 if b'x' in flags:
1959 self.wvfs.setflags(filename, False, True)
1959 self.wvfs.setflags(filename, False, True)
1960 else:
1960 else:
1961 self.wvfs.setflags(filename, False, False)
1961 self.wvfs.setflags(filename, False, False)
1962 return len(data)
1962 return len(data)
1963
1963
1964 def wwritedata(self, filename, data):
1964 def wwritedata(self, filename, data):
1965 return self._filter(self._decodefilterpats, filename, data)
1965 return self._filter(self._decodefilterpats, filename, data)
1966
1966
1967 def currenttransaction(self):
1967 def currenttransaction(self):
1968 """return the current transaction or None if non exists"""
1968 """return the current transaction or None if non exists"""
1969 if self._transref:
1969 if self._transref:
1970 tr = self._transref()
1970 tr = self._transref()
1971 else:
1971 else:
1972 tr = None
1972 tr = None
1973
1973
1974 if tr and tr.running():
1974 if tr and tr.running():
1975 return tr
1975 return tr
1976 return None
1976 return None
1977
1977
1978 def transaction(self, desc, report=None):
1978 def transaction(self, desc, report=None):
1979 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1979 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1980 b'devel', b'check-locks'
1980 b'devel', b'check-locks'
1981 ):
1981 ):
1982 if self._currentlock(self._lockref) is None:
1982 if self._currentlock(self._lockref) is None:
1983 raise error.ProgrammingError(b'transaction requires locking')
1983 raise error.ProgrammingError(b'transaction requires locking')
1984 tr = self.currenttransaction()
1984 tr = self.currenttransaction()
1985 if tr is not None:
1985 if tr is not None:
1986 return tr.nest(name=desc)
1986 return tr.nest(name=desc)
1987
1987
1988 # abort here if the journal already exists
1988 # abort here if the journal already exists
1989 if self.svfs.exists(b"journal"):
1989 if self.svfs.exists(b"journal"):
1990 raise error.RepoError(
1990 raise error.RepoError(
1991 _(b"abandoned transaction found"),
1991 _(b"abandoned transaction found"),
1992 hint=_(b"run 'hg recover' to clean up transaction"),
1992 hint=_(b"run 'hg recover' to clean up transaction"),
1993 )
1993 )
1994
1994
1995 idbase = b"%.40f#%f" % (random.random(), time.time())
1995 idbase = b"%.40f#%f" % (random.random(), time.time())
1996 ha = hex(hashlib.sha1(idbase).digest())
1996 ha = hex(hashlib.sha1(idbase).digest())
1997 txnid = b'TXN:' + ha
1997 txnid = b'TXN:' + ha
1998 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
1998 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
1999
1999
2000 self._writejournal(desc)
2000 self._writejournal(desc)
2001 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2001 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2002 if report:
2002 if report:
2003 rp = report
2003 rp = report
2004 else:
2004 else:
2005 rp = self.ui.warn
2005 rp = self.ui.warn
2006 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2006 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2007 # we must avoid cyclic reference between repo and transaction.
2007 # we must avoid cyclic reference between repo and transaction.
2008 reporef = weakref.ref(self)
2008 reporef = weakref.ref(self)
2009 # Code to track tag movement
2009 # Code to track tag movement
2010 #
2010 #
2011 # Since tags are all handled as file content, it is actually quite hard
2011 # Since tags are all handled as file content, it is actually quite hard
2012 # to track these movement from a code perspective. So we fallback to a
2012 # to track these movement from a code perspective. So we fallback to a
2013 # tracking at the repository level. One could envision to track changes
2013 # tracking at the repository level. One could envision to track changes
2014 # to the '.hgtags' file through changegroup apply but that fails to
2014 # to the '.hgtags' file through changegroup apply but that fails to
2015 # cope with case where transaction expose new heads without changegroup
2015 # cope with case where transaction expose new heads without changegroup
2016 # being involved (eg: phase movement).
2016 # being involved (eg: phase movement).
2017 #
2017 #
2018 # For now, We gate the feature behind a flag since this likely comes
2018 # For now, We gate the feature behind a flag since this likely comes
2019 # with performance impacts. The current code run more often than needed
2019 # with performance impacts. The current code run more often than needed
2020 # and do not use caches as much as it could. The current focus is on
2020 # and do not use caches as much as it could. The current focus is on
2021 # the behavior of the feature so we disable it by default. The flag
2021 # the behavior of the feature so we disable it by default. The flag
2022 # will be removed when we are happy with the performance impact.
2022 # will be removed when we are happy with the performance impact.
2023 #
2023 #
2024 # Once this feature is no longer experimental move the following
2024 # Once this feature is no longer experimental move the following
2025 # documentation to the appropriate help section:
2025 # documentation to the appropriate help section:
2026 #
2026 #
2027 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2027 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2028 # tags (new or changed or deleted tags). In addition the details of
2028 # tags (new or changed or deleted tags). In addition the details of
2029 # these changes are made available in a file at:
2029 # these changes are made available in a file at:
2030 # ``REPOROOT/.hg/changes/tags.changes``.
2030 # ``REPOROOT/.hg/changes/tags.changes``.
2031 # Make sure you check for HG_TAG_MOVED before reading that file as it
2031 # Make sure you check for HG_TAG_MOVED before reading that file as it
2032 # might exist from a previous transaction even if no tag were touched
2032 # might exist from a previous transaction even if no tag were touched
2033 # in this one. Changes are recorded in a line base format::
2033 # in this one. Changes are recorded in a line base format::
2034 #
2034 #
2035 # <action> <hex-node> <tag-name>\n
2035 # <action> <hex-node> <tag-name>\n
2036 #
2036 #
2037 # Actions are defined as follow:
2037 # Actions are defined as follow:
2038 # "-R": tag is removed,
2038 # "-R": tag is removed,
2039 # "+A": tag is added,
2039 # "+A": tag is added,
2040 # "-M": tag is moved (old value),
2040 # "-M": tag is moved (old value),
2041 # "+M": tag is moved (new value),
2041 # "+M": tag is moved (new value),
2042 tracktags = lambda x: None
2042 tracktags = lambda x: None
2043 # experimental config: experimental.hook-track-tags
2043 # experimental config: experimental.hook-track-tags
2044 shouldtracktags = self.ui.configbool(
2044 shouldtracktags = self.ui.configbool(
2045 b'experimental', b'hook-track-tags'
2045 b'experimental', b'hook-track-tags'
2046 )
2046 )
2047 if desc != b'strip' and shouldtracktags:
2047 if desc != b'strip' and shouldtracktags:
2048 oldheads = self.changelog.headrevs()
2048 oldheads = self.changelog.headrevs()
2049
2049
2050 def tracktags(tr2):
2050 def tracktags(tr2):
2051 repo = reporef()
2051 repo = reporef()
2052 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2052 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2053 newheads = repo.changelog.headrevs()
2053 newheads = repo.changelog.headrevs()
2054 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2054 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2055 # notes: we compare lists here.
2055 # notes: we compare lists here.
2056 # As we do it only once buiding set would not be cheaper
2056 # As we do it only once buiding set would not be cheaper
2057 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2057 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2058 if changes:
2058 if changes:
2059 tr2.hookargs[b'tag_moved'] = b'1'
2059 tr2.hookargs[b'tag_moved'] = b'1'
2060 with repo.vfs(
2060 with repo.vfs(
2061 b'changes/tags.changes', b'w', atomictemp=True
2061 b'changes/tags.changes', b'w', atomictemp=True
2062 ) as changesfile:
2062 ) as changesfile:
2063 # note: we do not register the file to the transaction
2063 # note: we do not register the file to the transaction
2064 # because we needs it to still exist on the transaction
2064 # because we needs it to still exist on the transaction
2065 # is close (for txnclose hooks)
2065 # is close (for txnclose hooks)
2066 tagsmod.writediff(changesfile, changes)
2066 tagsmod.writediff(changesfile, changes)
2067
2067
2068 def validate(tr2):
2068 def validate(tr2):
2069 """will run pre-closing hooks"""
2069 """will run pre-closing hooks"""
2070 # XXX the transaction API is a bit lacking here so we take a hacky
2070 # XXX the transaction API is a bit lacking here so we take a hacky
2071 # path for now
2071 # path for now
2072 #
2072 #
2073 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2073 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2074 # dict is copied before these run. In addition we needs the data
2074 # dict is copied before these run. In addition we needs the data
2075 # available to in memory hooks too.
2075 # available to in memory hooks too.
2076 #
2076 #
2077 # Moreover, we also need to make sure this runs before txnclose
2077 # Moreover, we also need to make sure this runs before txnclose
2078 # hooks and there is no "pending" mechanism that would execute
2078 # hooks and there is no "pending" mechanism that would execute
2079 # logic only if hooks are about to run.
2079 # logic only if hooks are about to run.
2080 #
2080 #
2081 # Fixing this limitation of the transaction is also needed to track
2081 # Fixing this limitation of the transaction is also needed to track
2082 # other families of changes (bookmarks, phases, obsolescence).
2082 # other families of changes (bookmarks, phases, obsolescence).
2083 #
2083 #
2084 # This will have to be fixed before we remove the experimental
2084 # This will have to be fixed before we remove the experimental
2085 # gating.
2085 # gating.
2086 tracktags(tr2)
2086 tracktags(tr2)
2087 repo = reporef()
2087 repo = reporef()
2088
2088
2089 r = repo.ui.configsuboptions(
2089 r = repo.ui.configsuboptions(
2090 b'experimental', b'single-head-per-branch'
2090 b'experimental', b'single-head-per-branch'
2091 )
2091 )
2092 singlehead, singleheadsub = r
2092 singlehead, singleheadsub = r
2093 if singlehead:
2093 if singlehead:
2094 accountclosed = singleheadsub.get(
2094 accountclosed = singleheadsub.get(
2095 b"account-closed-heads", False
2095 b"account-closed-heads", False
2096 )
2096 )
2097 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
2097 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
2098 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2098 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2099 for name, (old, new) in sorted(
2099 for name, (old, new) in sorted(
2100 tr.changes[b'bookmarks'].items()
2100 tr.changes[b'bookmarks'].items()
2101 ):
2101 ):
2102 args = tr.hookargs.copy()
2102 args = tr.hookargs.copy()
2103 args.update(bookmarks.preparehookargs(name, old, new))
2103 args.update(bookmarks.preparehookargs(name, old, new))
2104 repo.hook(
2104 repo.hook(
2105 b'pretxnclose-bookmark',
2105 b'pretxnclose-bookmark',
2106 throw=True,
2106 throw=True,
2107 **pycompat.strkwargs(args)
2107 **pycompat.strkwargs(args)
2108 )
2108 )
2109 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2109 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2110 cl = repo.unfiltered().changelog
2110 cl = repo.unfiltered().changelog
2111 for rev, (old, new) in tr.changes[b'phases'].items():
2111 for rev, (old, new) in tr.changes[b'phases'].items():
2112 args = tr.hookargs.copy()
2112 args = tr.hookargs.copy()
2113 node = hex(cl.node(rev))
2113 node = hex(cl.node(rev))
2114 args.update(phases.preparehookargs(node, old, new))
2114 args.update(phases.preparehookargs(node, old, new))
2115 repo.hook(
2115 repo.hook(
2116 b'pretxnclose-phase',
2116 b'pretxnclose-phase',
2117 throw=True,
2117 throw=True,
2118 **pycompat.strkwargs(args)
2118 **pycompat.strkwargs(args)
2119 )
2119 )
2120
2120
2121 repo.hook(
2121 repo.hook(
2122 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2122 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2123 )
2123 )
2124
2124
2125 def releasefn(tr, success):
2125 def releasefn(tr, success):
2126 repo = reporef()
2126 repo = reporef()
2127 if repo is None:
2127 if repo is None:
2128 # If the repo has been GC'd (and this release function is being
2128 # If the repo has been GC'd (and this release function is being
2129 # called from transaction.__del__), there's not much we can do,
2129 # called from transaction.__del__), there's not much we can do,
2130 # so just leave the unfinished transaction there and let the
2130 # so just leave the unfinished transaction there and let the
2131 # user run `hg recover`.
2131 # user run `hg recover`.
2132 return
2132 return
2133 if success:
2133 if success:
2134 # this should be explicitly invoked here, because
2134 # this should be explicitly invoked here, because
2135 # in-memory changes aren't written out at closing
2135 # in-memory changes aren't written out at closing
2136 # transaction, if tr.addfilegenerator (via
2136 # transaction, if tr.addfilegenerator (via
2137 # dirstate.write or so) isn't invoked while
2137 # dirstate.write or so) isn't invoked while
2138 # transaction running
2138 # transaction running
2139 repo.dirstate.write(None)
2139 repo.dirstate.write(None)
2140 else:
2140 else:
2141 # discard all changes (including ones already written
2141 # discard all changes (including ones already written
2142 # out) in this transaction
2142 # out) in this transaction
2143 narrowspec.restorebackup(self, b'journal.narrowspec')
2143 narrowspec.restorebackup(self, b'journal.narrowspec')
2144 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2144 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2145 repo.dirstate.restorebackup(None, b'journal.dirstate')
2145 repo.dirstate.restorebackup(None, b'journal.dirstate')
2146
2146
2147 repo.invalidate(clearfilecache=True)
2147 repo.invalidate(clearfilecache=True)
2148
2148
2149 tr = transaction.transaction(
2149 tr = transaction.transaction(
2150 rp,
2150 rp,
2151 self.svfs,
2151 self.svfs,
2152 vfsmap,
2152 vfsmap,
2153 b"journal",
2153 b"journal",
2154 b"undo",
2154 b"undo",
2155 aftertrans(renames),
2155 aftertrans(renames),
2156 self.store.createmode,
2156 self.store.createmode,
2157 validator=validate,
2157 validator=validate,
2158 releasefn=releasefn,
2158 releasefn=releasefn,
2159 checkambigfiles=_cachedfiles,
2159 checkambigfiles=_cachedfiles,
2160 name=desc,
2160 name=desc,
2161 )
2161 )
2162 tr.changes[b'origrepolen'] = len(self)
2162 tr.changes[b'origrepolen'] = len(self)
2163 tr.changes[b'obsmarkers'] = set()
2163 tr.changes[b'obsmarkers'] = set()
2164 tr.changes[b'phases'] = {}
2164 tr.changes[b'phases'] = {}
2165 tr.changes[b'bookmarks'] = {}
2165 tr.changes[b'bookmarks'] = {}
2166
2166
2167 tr.hookargs[b'txnid'] = txnid
2167 tr.hookargs[b'txnid'] = txnid
2168 tr.hookargs[b'txnname'] = desc
2168 tr.hookargs[b'txnname'] = desc
2169 # note: writing the fncache only during finalize mean that the file is
2169 # note: writing the fncache only during finalize mean that the file is
2170 # outdated when running hooks. As fncache is used for streaming clone,
2170 # outdated when running hooks. As fncache is used for streaming clone,
2171 # this is not expected to break anything that happen during the hooks.
2171 # this is not expected to break anything that happen during the hooks.
2172 tr.addfinalize(b'flush-fncache', self.store.write)
2172 tr.addfinalize(b'flush-fncache', self.store.write)
2173
2173
2174 def txnclosehook(tr2):
2174 def txnclosehook(tr2):
2175 """To be run if transaction is successful, will schedule a hook run
2175 """To be run if transaction is successful, will schedule a hook run
2176 """
2176 """
2177 # Don't reference tr2 in hook() so we don't hold a reference.
2177 # Don't reference tr2 in hook() so we don't hold a reference.
2178 # This reduces memory consumption when there are multiple
2178 # This reduces memory consumption when there are multiple
2179 # transactions per lock. This can likely go away if issue5045
2179 # transactions per lock. This can likely go away if issue5045
2180 # fixes the function accumulation.
2180 # fixes the function accumulation.
2181 hookargs = tr2.hookargs
2181 hookargs = tr2.hookargs
2182
2182
2183 def hookfunc():
2183 def hookfunc():
2184 repo = reporef()
2184 repo = reporef()
2185 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2185 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2186 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2186 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2187 for name, (old, new) in bmchanges:
2187 for name, (old, new) in bmchanges:
2188 args = tr.hookargs.copy()
2188 args = tr.hookargs.copy()
2189 args.update(bookmarks.preparehookargs(name, old, new))
2189 args.update(bookmarks.preparehookargs(name, old, new))
2190 repo.hook(
2190 repo.hook(
2191 b'txnclose-bookmark',
2191 b'txnclose-bookmark',
2192 throw=False,
2192 throw=False,
2193 **pycompat.strkwargs(args)
2193 **pycompat.strkwargs(args)
2194 )
2194 )
2195
2195
2196 if hook.hashook(repo.ui, b'txnclose-phase'):
2196 if hook.hashook(repo.ui, b'txnclose-phase'):
2197 cl = repo.unfiltered().changelog
2197 cl = repo.unfiltered().changelog
2198 phasemv = sorted(tr.changes[b'phases'].items())
2198 phasemv = sorted(tr.changes[b'phases'].items())
2199 for rev, (old, new) in phasemv:
2199 for rev, (old, new) in phasemv:
2200 args = tr.hookargs.copy()
2200 args = tr.hookargs.copy()
2201 node = hex(cl.node(rev))
2201 node = hex(cl.node(rev))
2202 args.update(phases.preparehookargs(node, old, new))
2202 args.update(phases.preparehookargs(node, old, new))
2203 repo.hook(
2203 repo.hook(
2204 b'txnclose-phase',
2204 b'txnclose-phase',
2205 throw=False,
2205 throw=False,
2206 **pycompat.strkwargs(args)
2206 **pycompat.strkwargs(args)
2207 )
2207 )
2208
2208
2209 repo.hook(
2209 repo.hook(
2210 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2210 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2211 )
2211 )
2212
2212
2213 reporef()._afterlock(hookfunc)
2213 reporef()._afterlock(hookfunc)
2214
2214
2215 tr.addfinalize(b'txnclose-hook', txnclosehook)
2215 tr.addfinalize(b'txnclose-hook', txnclosehook)
2216 # Include a leading "-" to make it happen before the transaction summary
2216 # Include a leading "-" to make it happen before the transaction summary
2217 # reports registered via scmutil.registersummarycallback() whose names
2217 # reports registered via scmutil.registersummarycallback() whose names
2218 # are 00-txnreport etc. That way, the caches will be warm when the
2218 # are 00-txnreport etc. That way, the caches will be warm when the
2219 # callbacks run.
2219 # callbacks run.
2220 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2220 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2221
2221
2222 def txnaborthook(tr2):
2222 def txnaborthook(tr2):
2223 """To be run if transaction is aborted
2223 """To be run if transaction is aborted
2224 """
2224 """
2225 reporef().hook(
2225 reporef().hook(
2226 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2226 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2227 )
2227 )
2228
2228
2229 tr.addabort(b'txnabort-hook', txnaborthook)
2229 tr.addabort(b'txnabort-hook', txnaborthook)
2230 # avoid eager cache invalidation. in-memory data should be identical
2230 # avoid eager cache invalidation. in-memory data should be identical
2231 # to stored data if transaction has no error.
2231 # to stored data if transaction has no error.
2232 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2232 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2233 self._transref = weakref.ref(tr)
2233 self._transref = weakref.ref(tr)
2234 scmutil.registersummarycallback(self, tr, desc)
2234 scmutil.registersummarycallback(self, tr, desc)
2235 return tr
2235 return tr
2236
2236
2237 def _journalfiles(self):
2237 def _journalfiles(self):
2238 return (
2238 return (
2239 (self.svfs, b'journal'),
2239 (self.svfs, b'journal'),
2240 (self.svfs, b'journal.narrowspec'),
2240 (self.svfs, b'journal.narrowspec'),
2241 (self.vfs, b'journal.narrowspec.dirstate'),
2241 (self.vfs, b'journal.narrowspec.dirstate'),
2242 (self.vfs, b'journal.dirstate'),
2242 (self.vfs, b'journal.dirstate'),
2243 (self.vfs, b'journal.branch'),
2243 (self.vfs, b'journal.branch'),
2244 (self.vfs, b'journal.desc'),
2244 (self.vfs, b'journal.desc'),
2245 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2245 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2246 (self.svfs, b'journal.phaseroots'),
2246 (self.svfs, b'journal.phaseroots'),
2247 )
2247 )
2248
2248
2249 def undofiles(self):
2249 def undofiles(self):
2250 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2250 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2251
2251
2252 @unfilteredmethod
2252 @unfilteredmethod
2253 def _writejournal(self, desc):
2253 def _writejournal(self, desc):
2254 self.dirstate.savebackup(None, b'journal.dirstate')
2254 self.dirstate.savebackup(None, b'journal.dirstate')
2255 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2255 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2256 narrowspec.savebackup(self, b'journal.narrowspec')
2256 narrowspec.savebackup(self, b'journal.narrowspec')
2257 self.vfs.write(
2257 self.vfs.write(
2258 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2258 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2259 )
2259 )
2260 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2260 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2261 bookmarksvfs = bookmarks.bookmarksvfs(self)
2261 bookmarksvfs = bookmarks.bookmarksvfs(self)
2262 bookmarksvfs.write(
2262 bookmarksvfs.write(
2263 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2263 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2264 )
2264 )
2265 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2265 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2266
2266
2267 def recover(self):
2267 def recover(self):
2268 with self.lock():
2268 with self.lock():
2269 if self.svfs.exists(b"journal"):
2269 if self.svfs.exists(b"journal"):
2270 self.ui.status(_(b"rolling back interrupted transaction\n"))
2270 self.ui.status(_(b"rolling back interrupted transaction\n"))
2271 vfsmap = {
2271 vfsmap = {
2272 b'': self.svfs,
2272 b'': self.svfs,
2273 b'plain': self.vfs,
2273 b'plain': self.vfs,
2274 }
2274 }
2275 transaction.rollback(
2275 transaction.rollback(
2276 self.svfs,
2276 self.svfs,
2277 vfsmap,
2277 vfsmap,
2278 b"journal",
2278 b"journal",
2279 self.ui.warn,
2279 self.ui.warn,
2280 checkambigfiles=_cachedfiles,
2280 checkambigfiles=_cachedfiles,
2281 )
2281 )
2282 self.invalidate()
2282 self.invalidate()
2283 return True
2283 return True
2284 else:
2284 else:
2285 self.ui.warn(_(b"no interrupted transaction available\n"))
2285 self.ui.warn(_(b"no interrupted transaction available\n"))
2286 return False
2286 return False
2287
2287
2288 def rollback(self, dryrun=False, force=False):
2288 def rollback(self, dryrun=False, force=False):
2289 wlock = lock = dsguard = None
2289 wlock = lock = dsguard = None
2290 try:
2290 try:
2291 wlock = self.wlock()
2291 wlock = self.wlock()
2292 lock = self.lock()
2292 lock = self.lock()
2293 if self.svfs.exists(b"undo"):
2293 if self.svfs.exists(b"undo"):
2294 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2294 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2295
2295
2296 return self._rollback(dryrun, force, dsguard)
2296 return self._rollback(dryrun, force, dsguard)
2297 else:
2297 else:
2298 self.ui.warn(_(b"no rollback information available\n"))
2298 self.ui.warn(_(b"no rollback information available\n"))
2299 return 1
2299 return 1
2300 finally:
2300 finally:
2301 release(dsguard, lock, wlock)
2301 release(dsguard, lock, wlock)
2302
2302
2303 @unfilteredmethod # Until we get smarter cache management
2303 @unfilteredmethod # Until we get smarter cache management
2304 def _rollback(self, dryrun, force, dsguard):
2304 def _rollback(self, dryrun, force, dsguard):
2305 ui = self.ui
2305 ui = self.ui
2306 try:
2306 try:
2307 args = self.vfs.read(b'undo.desc').splitlines()
2307 args = self.vfs.read(b'undo.desc').splitlines()
2308 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2308 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2309 if len(args) >= 3:
2309 if len(args) >= 3:
2310 detail = args[2]
2310 detail = args[2]
2311 oldtip = oldlen - 1
2311 oldtip = oldlen - 1
2312
2312
2313 if detail and ui.verbose:
2313 if detail and ui.verbose:
2314 msg = _(
2314 msg = _(
2315 b'repository tip rolled back to revision %d'
2315 b'repository tip rolled back to revision %d'
2316 b' (undo %s: %s)\n'
2316 b' (undo %s: %s)\n'
2317 ) % (oldtip, desc, detail)
2317 ) % (oldtip, desc, detail)
2318 else:
2318 else:
2319 msg = _(
2319 msg = _(
2320 b'repository tip rolled back to revision %d (undo %s)\n'
2320 b'repository tip rolled back to revision %d (undo %s)\n'
2321 ) % (oldtip, desc)
2321 ) % (oldtip, desc)
2322 except IOError:
2322 except IOError:
2323 msg = _(b'rolling back unknown transaction\n')
2323 msg = _(b'rolling back unknown transaction\n')
2324 desc = None
2324 desc = None
2325
2325
2326 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2326 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2327 raise error.Abort(
2327 raise error.Abort(
2328 _(
2328 _(
2329 b'rollback of last commit while not checked out '
2329 b'rollback of last commit while not checked out '
2330 b'may lose data'
2330 b'may lose data'
2331 ),
2331 ),
2332 hint=_(b'use -f to force'),
2332 hint=_(b'use -f to force'),
2333 )
2333 )
2334
2334
2335 ui.status(msg)
2335 ui.status(msg)
2336 if dryrun:
2336 if dryrun:
2337 return 0
2337 return 0
2338
2338
2339 parents = self.dirstate.parents()
2339 parents = self.dirstate.parents()
2340 self.destroying()
2340 self.destroying()
2341 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2341 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2342 transaction.rollback(
2342 transaction.rollback(
2343 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2343 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2344 )
2344 )
2345 bookmarksvfs = bookmarks.bookmarksvfs(self)
2345 bookmarksvfs = bookmarks.bookmarksvfs(self)
2346 if bookmarksvfs.exists(b'undo.bookmarks'):
2346 if bookmarksvfs.exists(b'undo.bookmarks'):
2347 bookmarksvfs.rename(
2347 bookmarksvfs.rename(
2348 b'undo.bookmarks', b'bookmarks', checkambig=True
2348 b'undo.bookmarks', b'bookmarks', checkambig=True
2349 )
2349 )
2350 if self.svfs.exists(b'undo.phaseroots'):
2350 if self.svfs.exists(b'undo.phaseroots'):
2351 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2351 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2352 self.invalidate()
2352 self.invalidate()
2353
2353
2354 has_node = self.changelog.index.has_node
2354 has_node = self.changelog.index.has_node
2355 parentgone = any(not has_node(p) for p in parents)
2355 parentgone = any(not has_node(p) for p in parents)
2356 if parentgone:
2356 if parentgone:
2357 # prevent dirstateguard from overwriting already restored one
2357 # prevent dirstateguard from overwriting already restored one
2358 dsguard.close()
2358 dsguard.close()
2359
2359
2360 narrowspec.restorebackup(self, b'undo.narrowspec')
2360 narrowspec.restorebackup(self, b'undo.narrowspec')
2361 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2361 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2362 self.dirstate.restorebackup(None, b'undo.dirstate')
2362 self.dirstate.restorebackup(None, b'undo.dirstate')
2363 try:
2363 try:
2364 branch = self.vfs.read(b'undo.branch')
2364 branch = self.vfs.read(b'undo.branch')
2365 self.dirstate.setbranch(encoding.tolocal(branch))
2365 self.dirstate.setbranch(encoding.tolocal(branch))
2366 except IOError:
2366 except IOError:
2367 ui.warn(
2367 ui.warn(
2368 _(
2368 _(
2369 b'named branch could not be reset: '
2369 b'named branch could not be reset: '
2370 b'current branch is still \'%s\'\n'
2370 b'current branch is still \'%s\'\n'
2371 )
2371 )
2372 % self.dirstate.branch()
2372 % self.dirstate.branch()
2373 )
2373 )
2374
2374
2375 parents = tuple([p.rev() for p in self[None].parents()])
2375 parents = tuple([p.rev() for p in self[None].parents()])
2376 if len(parents) > 1:
2376 if len(parents) > 1:
2377 ui.status(
2377 ui.status(
2378 _(
2378 _(
2379 b'working directory now based on '
2379 b'working directory now based on '
2380 b'revisions %d and %d\n'
2380 b'revisions %d and %d\n'
2381 )
2381 )
2382 % parents
2382 % parents
2383 )
2383 )
2384 else:
2384 else:
2385 ui.status(
2385 ui.status(
2386 _(b'working directory now based on revision %d\n') % parents
2386 _(b'working directory now based on revision %d\n') % parents
2387 )
2387 )
2388 mergemod.mergestate.clean(self, self[b'.'].node())
2388 mergemod.mergestate.clean(self, self[b'.'].node())
2389
2389
2390 # TODO: if we know which new heads may result from this rollback, pass
2390 # TODO: if we know which new heads may result from this rollback, pass
2391 # them to destroy(), which will prevent the branchhead cache from being
2391 # them to destroy(), which will prevent the branchhead cache from being
2392 # invalidated.
2392 # invalidated.
2393 self.destroyed()
2393 self.destroyed()
2394 return 0
2394 return 0
2395
2395
2396 def _buildcacheupdater(self, newtransaction):
2396 def _buildcacheupdater(self, newtransaction):
2397 """called during transaction to build the callback updating cache
2397 """called during transaction to build the callback updating cache
2398
2398
2399 Lives on the repository to help extension who might want to augment
2399 Lives on the repository to help extension who might want to augment
2400 this logic. For this purpose, the created transaction is passed to the
2400 this logic. For this purpose, the created transaction is passed to the
2401 method.
2401 method.
2402 """
2402 """
2403 # we must avoid cyclic reference between repo and transaction.
2403 # we must avoid cyclic reference between repo and transaction.
2404 reporef = weakref.ref(self)
2404 reporef = weakref.ref(self)
2405
2405
2406 def updater(tr):
2406 def updater(tr):
2407 repo = reporef()
2407 repo = reporef()
2408 repo.updatecaches(tr)
2408 repo.updatecaches(tr)
2409
2409
2410 return updater
2410 return updater
2411
2411
2412 @unfilteredmethod
2412 @unfilteredmethod
2413 def updatecaches(self, tr=None, full=False):
2413 def updatecaches(self, tr=None, full=False):
2414 """warm appropriate caches
2414 """warm appropriate caches
2415
2415
2416 If this function is called after a transaction closed. The transaction
2416 If this function is called after a transaction closed. The transaction
2417 will be available in the 'tr' argument. This can be used to selectively
2417 will be available in the 'tr' argument. This can be used to selectively
2418 update caches relevant to the changes in that transaction.
2418 update caches relevant to the changes in that transaction.
2419
2419
2420 If 'full' is set, make sure all caches the function knows about have
2420 If 'full' is set, make sure all caches the function knows about have
2421 up-to-date data. Even the ones usually loaded more lazily.
2421 up-to-date data. Even the ones usually loaded more lazily.
2422 """
2422 """
2423 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2423 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2424 # During strip, many caches are invalid but
2424 # During strip, many caches are invalid but
2425 # later call to `destroyed` will refresh them.
2425 # later call to `destroyed` will refresh them.
2426 return
2426 return
2427
2427
2428 if tr is None or tr.changes[b'origrepolen'] < len(self):
2428 if tr is None or tr.changes[b'origrepolen'] < len(self):
2429 # accessing the 'ser ved' branchmap should refresh all the others,
2429 # accessing the 'ser ved' branchmap should refresh all the others,
2430 self.ui.debug(b'updating the branch cache\n')
2430 self.ui.debug(b'updating the branch cache\n')
2431 self.filtered(b'served').branchmap()
2431 self.filtered(b'served').branchmap()
2432 self.filtered(b'served.hidden').branchmap()
2432 self.filtered(b'served.hidden').branchmap()
2433
2433
2434 if full:
2434 if full:
2435 unfi = self.unfiltered()
2435 unfi = self.unfiltered()
2436 rbc = unfi.revbranchcache()
2436 rbc = unfi.revbranchcache()
2437 for r in unfi.changelog:
2437 for r in unfi.changelog:
2438 rbc.branchinfo(r)
2438 rbc.branchinfo(r)
2439 rbc.write()
2439 rbc.write()
2440
2440
2441 # ensure the working copy parents are in the manifestfulltextcache
2441 # ensure the working copy parents are in the manifestfulltextcache
2442 for ctx in self[b'.'].parents():
2442 for ctx in self[b'.'].parents():
2443 ctx.manifest() # accessing the manifest is enough
2443 ctx.manifest() # accessing the manifest is enough
2444
2444
2445 # accessing fnode cache warms the cache
2445 # accessing fnode cache warms the cache
2446 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2446 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2447 # accessing tags warm the cache
2447 # accessing tags warm the cache
2448 self.tags()
2448 self.tags()
2449 self.filtered(b'served').tags()
2449 self.filtered(b'served').tags()
2450
2450
2451 # The `full` arg is documented as updating even the lazily-loaded
2451 # The `full` arg is documented as updating even the lazily-loaded
2452 # caches immediately, so we're forcing a write to cause these caches
2452 # caches immediately, so we're forcing a write to cause these caches
2453 # to be warmed up even if they haven't explicitly been requested
2453 # to be warmed up even if they haven't explicitly been requested
2454 # yet (if they've never been used by hg, they won't ever have been
2454 # yet (if they've never been used by hg, they won't ever have been
2455 # written, even if they're a subset of another kind of cache that
2455 # written, even if they're a subset of another kind of cache that
2456 # *has* been used).
2456 # *has* been used).
2457 for filt in repoview.filtertable.keys():
2457 for filt in repoview.filtertable.keys():
2458 filtered = self.filtered(filt)
2458 filtered = self.filtered(filt)
2459 filtered.branchmap().write(filtered)
2459 filtered.branchmap().write(filtered)
2460
2460
2461 def invalidatecaches(self):
2461 def invalidatecaches(self):
2462
2462
2463 if '_tagscache' in vars(self):
2463 if '_tagscache' in vars(self):
2464 # can't use delattr on proxy
2464 # can't use delattr on proxy
2465 del self.__dict__['_tagscache']
2465 del self.__dict__['_tagscache']
2466
2466
2467 self._branchcaches.clear()
2467 self._branchcaches.clear()
2468 self.invalidatevolatilesets()
2468 self.invalidatevolatilesets()
2469 self._sparsesignaturecache.clear()
2469 self._sparsesignaturecache.clear()
2470
2470
2471 def invalidatevolatilesets(self):
2471 def invalidatevolatilesets(self):
2472 self.filteredrevcache.clear()
2472 self.filteredrevcache.clear()
2473 obsolete.clearobscaches(self)
2473 obsolete.clearobscaches(self)
2474
2474
2475 def invalidatedirstate(self):
2475 def invalidatedirstate(self):
2476 '''Invalidates the dirstate, causing the next call to dirstate
2476 '''Invalidates the dirstate, causing the next call to dirstate
2477 to check if it was modified since the last time it was read,
2477 to check if it was modified since the last time it was read,
2478 rereading it if it has.
2478 rereading it if it has.
2479
2479
2480 This is different to dirstate.invalidate() that it doesn't always
2480 This is different to dirstate.invalidate() that it doesn't always
2481 rereads the dirstate. Use dirstate.invalidate() if you want to
2481 rereads the dirstate. Use dirstate.invalidate() if you want to
2482 explicitly read the dirstate again (i.e. restoring it to a previous
2482 explicitly read the dirstate again (i.e. restoring it to a previous
2483 known good state).'''
2483 known good state).'''
2484 if hasunfilteredcache(self, 'dirstate'):
2484 if hasunfilteredcache(self, 'dirstate'):
2485 for k in self.dirstate._filecache:
2485 for k in self.dirstate._filecache:
2486 try:
2486 try:
2487 delattr(self.dirstate, k)
2487 delattr(self.dirstate, k)
2488 except AttributeError:
2488 except AttributeError:
2489 pass
2489 pass
2490 delattr(self.unfiltered(), 'dirstate')
2490 delattr(self.unfiltered(), 'dirstate')
2491
2491
2492 def invalidate(self, clearfilecache=False):
2492 def invalidate(self, clearfilecache=False):
2493 '''Invalidates both store and non-store parts other than dirstate
2493 '''Invalidates both store and non-store parts other than dirstate
2494
2494
2495 If a transaction is running, invalidation of store is omitted,
2495 If a transaction is running, invalidation of store is omitted,
2496 because discarding in-memory changes might cause inconsistency
2496 because discarding in-memory changes might cause inconsistency
2497 (e.g. incomplete fncache causes unintentional failure, but
2497 (e.g. incomplete fncache causes unintentional failure, but
2498 redundant one doesn't).
2498 redundant one doesn't).
2499 '''
2499 '''
2500 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2500 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2501 for k in list(self._filecache.keys()):
2501 for k in list(self._filecache.keys()):
2502 # dirstate is invalidated separately in invalidatedirstate()
2502 # dirstate is invalidated separately in invalidatedirstate()
2503 if k == b'dirstate':
2503 if k == b'dirstate':
2504 continue
2504 continue
2505 if (
2505 if (
2506 k == b'changelog'
2506 k == b'changelog'
2507 and self.currenttransaction()
2507 and self.currenttransaction()
2508 and self.changelog._delayed
2508 and self.changelog._delayed
2509 ):
2509 ):
2510 # The changelog object may store unwritten revisions. We don't
2510 # The changelog object may store unwritten revisions. We don't
2511 # want to lose them.
2511 # want to lose them.
2512 # TODO: Solve the problem instead of working around it.
2512 # TODO: Solve the problem instead of working around it.
2513 continue
2513 continue
2514
2514
2515 if clearfilecache:
2515 if clearfilecache:
2516 del self._filecache[k]
2516 del self._filecache[k]
2517 try:
2517 try:
2518 delattr(unfiltered, k)
2518 delattr(unfiltered, k)
2519 except AttributeError:
2519 except AttributeError:
2520 pass
2520 pass
2521 self.invalidatecaches()
2521 self.invalidatecaches()
2522 if not self.currenttransaction():
2522 if not self.currenttransaction():
2523 # TODO: Changing contents of store outside transaction
2523 # TODO: Changing contents of store outside transaction
2524 # causes inconsistency. We should make in-memory store
2524 # causes inconsistency. We should make in-memory store
2525 # changes detectable, and abort if changed.
2525 # changes detectable, and abort if changed.
2526 self.store.invalidatecaches()
2526 self.store.invalidatecaches()
2527
2527
2528 def invalidateall(self):
2528 def invalidateall(self):
2529 '''Fully invalidates both store and non-store parts, causing the
2529 '''Fully invalidates both store and non-store parts, causing the
2530 subsequent operation to reread any outside changes.'''
2530 subsequent operation to reread any outside changes.'''
2531 # extension should hook this to invalidate its caches
2531 # extension should hook this to invalidate its caches
2532 self.invalidate()
2532 self.invalidate()
2533 self.invalidatedirstate()
2533 self.invalidatedirstate()
2534
2534
2535 @unfilteredmethod
2535 @unfilteredmethod
2536 def _refreshfilecachestats(self, tr):
2536 def _refreshfilecachestats(self, tr):
2537 """Reload stats of cached files so that they are flagged as valid"""
2537 """Reload stats of cached files so that they are flagged as valid"""
2538 for k, ce in self._filecache.items():
2538 for k, ce in self._filecache.items():
2539 k = pycompat.sysstr(k)
2539 k = pycompat.sysstr(k)
2540 if k == 'dirstate' or k not in self.__dict__:
2540 if k == 'dirstate' or k not in self.__dict__:
2541 continue
2541 continue
2542 ce.refresh()
2542 ce.refresh()
2543
2543
2544 def _lock(
2544 def _lock(
2545 self,
2545 self,
2546 vfs,
2546 vfs,
2547 lockname,
2547 lockname,
2548 wait,
2548 wait,
2549 releasefn,
2549 releasefn,
2550 acquirefn,
2550 acquirefn,
2551 desc,
2551 desc,
2552 inheritchecker=None,
2552 inheritchecker=None,
2553 parentenvvar=None,
2553 parentenvvar=None,
2554 ):
2554 ):
2555 parentlock = None
2555 parentlock = None
2556 # the contents of parentenvvar are used by the underlying lock to
2556 # the contents of parentenvvar are used by the underlying lock to
2557 # determine whether it can be inherited
2557 # determine whether it can be inherited
2558 if parentenvvar is not None:
2558 if parentenvvar is not None:
2559 parentlock = encoding.environ.get(parentenvvar)
2559 parentlock = encoding.environ.get(parentenvvar)
2560
2560
2561 timeout = 0
2561 timeout = 0
2562 warntimeout = 0
2562 warntimeout = 0
2563 if wait:
2563 if wait:
2564 timeout = self.ui.configint(b"ui", b"timeout")
2564 timeout = self.ui.configint(b"ui", b"timeout")
2565 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2565 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2566 # internal config: ui.signal-safe-lock
2566 # internal config: ui.signal-safe-lock
2567 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2567 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2568
2568
2569 l = lockmod.trylock(
2569 l = lockmod.trylock(
2570 self.ui,
2570 self.ui,
2571 vfs,
2571 vfs,
2572 lockname,
2572 lockname,
2573 timeout,
2573 timeout,
2574 warntimeout,
2574 warntimeout,
2575 releasefn=releasefn,
2575 releasefn=releasefn,
2576 acquirefn=acquirefn,
2576 acquirefn=acquirefn,
2577 desc=desc,
2577 desc=desc,
2578 inheritchecker=inheritchecker,
2578 inheritchecker=inheritchecker,
2579 parentlock=parentlock,
2579 parentlock=parentlock,
2580 signalsafe=signalsafe,
2580 signalsafe=signalsafe,
2581 )
2581 )
2582 return l
2582 return l
2583
2583
2584 def _afterlock(self, callback):
2584 def _afterlock(self, callback):
2585 """add a callback to be run when the repository is fully unlocked
2585 """add a callback to be run when the repository is fully unlocked
2586
2586
2587 The callback will be executed when the outermost lock is released
2587 The callback will be executed when the outermost lock is released
2588 (with wlock being higher level than 'lock')."""
2588 (with wlock being higher level than 'lock')."""
2589 for ref in (self._wlockref, self._lockref):
2589 for ref in (self._wlockref, self._lockref):
2590 l = ref and ref()
2590 l = ref and ref()
2591 if l and l.held:
2591 if l and l.held:
2592 l.postrelease.append(callback)
2592 l.postrelease.append(callback)
2593 break
2593 break
2594 else: # no lock have been found.
2594 else: # no lock have been found.
2595 callback()
2595 callback()
2596
2596
2597 def lock(self, wait=True):
2597 def lock(self, wait=True):
2598 '''Lock the repository store (.hg/store) and return a weak reference
2598 '''Lock the repository store (.hg/store) and return a weak reference
2599 to the lock. Use this before modifying the store (e.g. committing or
2599 to the lock. Use this before modifying the store (e.g. committing or
2600 stripping). If you are opening a transaction, get a lock as well.)
2600 stripping). If you are opening a transaction, get a lock as well.)
2601
2601
2602 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2602 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2603 'wlock' first to avoid a dead-lock hazard.'''
2603 'wlock' first to avoid a dead-lock hazard.'''
2604 l = self._currentlock(self._lockref)
2604 l = self._currentlock(self._lockref)
2605 if l is not None:
2605 if l is not None:
2606 l.lock()
2606 l.lock()
2607 return l
2607 return l
2608
2608
2609 l = self._lock(
2609 l = self._lock(
2610 vfs=self.svfs,
2610 vfs=self.svfs,
2611 lockname=b"lock",
2611 lockname=b"lock",
2612 wait=wait,
2612 wait=wait,
2613 releasefn=None,
2613 releasefn=None,
2614 acquirefn=self.invalidate,
2614 acquirefn=self.invalidate,
2615 desc=_(b'repository %s') % self.origroot,
2615 desc=_(b'repository %s') % self.origroot,
2616 )
2616 )
2617 self._lockref = weakref.ref(l)
2617 self._lockref = weakref.ref(l)
2618 return l
2618 return l
2619
2619
2620 def _wlockchecktransaction(self):
2620 def _wlockchecktransaction(self):
2621 if self.currenttransaction() is not None:
2621 if self.currenttransaction() is not None:
2622 raise error.LockInheritanceContractViolation(
2622 raise error.LockInheritanceContractViolation(
2623 b'wlock cannot be inherited in the middle of a transaction'
2623 b'wlock cannot be inherited in the middle of a transaction'
2624 )
2624 )
2625
2625
2626 def wlock(self, wait=True):
2626 def wlock(self, wait=True):
2627 '''Lock the non-store parts of the repository (everything under
2627 '''Lock the non-store parts of the repository (everything under
2628 .hg except .hg/store) and return a weak reference to the lock.
2628 .hg except .hg/store) and return a weak reference to the lock.
2629
2629
2630 Use this before modifying files in .hg.
2630 Use this before modifying files in .hg.
2631
2631
2632 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2632 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2633 'wlock' first to avoid a dead-lock hazard.'''
2633 'wlock' first to avoid a dead-lock hazard.'''
2634 l = self._wlockref and self._wlockref()
2634 l = self._wlockref and self._wlockref()
2635 if l is not None and l.held:
2635 if l is not None and l.held:
2636 l.lock()
2636 l.lock()
2637 return l
2637 return l
2638
2638
2639 # We do not need to check for non-waiting lock acquisition. Such
2639 # We do not need to check for non-waiting lock acquisition. Such
2640 # acquisition would not cause dead-lock as they would just fail.
2640 # acquisition would not cause dead-lock as they would just fail.
2641 if wait and (
2641 if wait and (
2642 self.ui.configbool(b'devel', b'all-warnings')
2642 self.ui.configbool(b'devel', b'all-warnings')
2643 or self.ui.configbool(b'devel', b'check-locks')
2643 or self.ui.configbool(b'devel', b'check-locks')
2644 ):
2644 ):
2645 if self._currentlock(self._lockref) is not None:
2645 if self._currentlock(self._lockref) is not None:
2646 self.ui.develwarn(b'"wlock" acquired after "lock"')
2646 self.ui.develwarn(b'"wlock" acquired after "lock"')
2647
2647
2648 def unlock():
2648 def unlock():
2649 if self.dirstate.pendingparentchange():
2649 if self.dirstate.pendingparentchange():
2650 self.dirstate.invalidate()
2650 self.dirstate.invalidate()
2651 else:
2651 else:
2652 self.dirstate.write(None)
2652 self.dirstate.write(None)
2653
2653
2654 self._filecache[b'dirstate'].refresh()
2654 self._filecache[b'dirstate'].refresh()
2655
2655
2656 l = self._lock(
2656 l = self._lock(
2657 self.vfs,
2657 self.vfs,
2658 b"wlock",
2658 b"wlock",
2659 wait,
2659 wait,
2660 unlock,
2660 unlock,
2661 self.invalidatedirstate,
2661 self.invalidatedirstate,
2662 _(b'working directory of %s') % self.origroot,
2662 _(b'working directory of %s') % self.origroot,
2663 inheritchecker=self._wlockchecktransaction,
2663 inheritchecker=self._wlockchecktransaction,
2664 parentenvvar=b'HG_WLOCK_LOCKER',
2664 parentenvvar=b'HG_WLOCK_LOCKER',
2665 )
2665 )
2666 self._wlockref = weakref.ref(l)
2666 self._wlockref = weakref.ref(l)
2667 return l
2667 return l
2668
2668
2669 def _currentlock(self, lockref):
2669 def _currentlock(self, lockref):
2670 """Returns the lock if it's held, or None if it's not."""
2670 """Returns the lock if it's held, or None if it's not."""
2671 if lockref is None:
2671 if lockref is None:
2672 return None
2672 return None
2673 l = lockref()
2673 l = lockref()
2674 if l is None or not l.held:
2674 if l is None or not l.held:
2675 return None
2675 return None
2676 return l
2676 return l
2677
2677
2678 def currentwlock(self):
2678 def currentwlock(self):
2679 """Returns the wlock if it's held, or None if it's not."""
2679 """Returns the wlock if it's held, or None if it's not."""
2680 return self._currentlock(self._wlockref)
2680 return self._currentlock(self._wlockref)
2681
2681
2682 def _filecommit(
2682 def _filecommit(
2683 self,
2683 self,
2684 fctx,
2684 fctx,
2685 manifest1,
2685 manifest1,
2686 manifest2,
2686 manifest2,
2687 linkrev,
2687 linkrev,
2688 tr,
2688 tr,
2689 changelist,
2689 changelist,
2690 includecopymeta,
2690 includecopymeta,
2691 ):
2691 ):
2692 """
2692 """
2693 commit an individual file as part of a larger transaction
2693 commit an individual file as part of a larger transaction
2694 """
2694 """
2695
2695
2696 fname = fctx.path()
2696 fname = fctx.path()
2697 fparent1 = manifest1.get(fname, nullid)
2697 fparent1 = manifest1.get(fname, nullid)
2698 fparent2 = manifest2.get(fname, nullid)
2698 fparent2 = manifest2.get(fname, nullid)
2699 if isinstance(fctx, context.filectx):
2699 if isinstance(fctx, context.filectx):
2700 node = fctx.filenode()
2700 node = fctx.filenode()
2701 if node in [fparent1, fparent2]:
2701 if node in [fparent1, fparent2]:
2702 self.ui.debug(b'reusing %s filelog entry\n' % fname)
2702 self.ui.debug(b'reusing %s filelog entry\n' % fname)
2703 if (
2703 if (
2704 fparent1 != nullid
2704 fparent1 != nullid
2705 and manifest1.flags(fname) != fctx.flags()
2705 and manifest1.flags(fname) != fctx.flags()
2706 ) or (
2706 ) or (
2707 fparent2 != nullid
2707 fparent2 != nullid
2708 and manifest2.flags(fname) != fctx.flags()
2708 and manifest2.flags(fname) != fctx.flags()
2709 ):
2709 ):
2710 changelist.append(fname)
2710 changelist.append(fname)
2711 return node
2711 return node
2712
2712
2713 flog = self.file(fname)
2713 flog = self.file(fname)
2714 meta = {}
2714 meta = {}
2715 cfname = fctx.copysource()
2715 cfname = fctx.copysource()
2716 if cfname and cfname != fname:
2716 if cfname and cfname != fname:
2717 # Mark the new revision of this file as a copy of another
2717 # Mark the new revision of this file as a copy of another
2718 # file. This copy data will effectively act as a parent
2718 # file. This copy data will effectively act as a parent
2719 # of this new revision. If this is a merge, the first
2719 # of this new revision. If this is a merge, the first
2720 # parent will be the nullid (meaning "look up the copy data")
2720 # parent will be the nullid (meaning "look up the copy data")
2721 # and the second one will be the other parent. For example:
2721 # and the second one will be the other parent. For example:
2722 #
2722 #
2723 # 0 --- 1 --- 3 rev1 changes file foo
2723 # 0 --- 1 --- 3 rev1 changes file foo
2724 # \ / rev2 renames foo to bar and changes it
2724 # \ / rev2 renames foo to bar and changes it
2725 # \- 2 -/ rev3 should have bar with all changes and
2725 # \- 2 -/ rev3 should have bar with all changes and
2726 # should record that bar descends from
2726 # should record that bar descends from
2727 # bar in rev2 and foo in rev1
2727 # bar in rev2 and foo in rev1
2728 #
2728 #
2729 # this allows this merge to succeed:
2729 # this allows this merge to succeed:
2730 #
2730 #
2731 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2731 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2732 # \ / merging rev3 and rev4 should use bar@rev2
2732 # \ / merging rev3 and rev4 should use bar@rev2
2733 # \- 2 --- 4 as the merge base
2733 # \- 2 --- 4 as the merge base
2734 #
2734 #
2735
2735
2736 cnode = manifest1.get(cfname)
2736 cnode = manifest1.get(cfname)
2737 newfparent = fparent2
2737 newfparent = fparent2
2738
2738
2739 if manifest2: # branch merge
2739 if manifest2: # branch merge
2740 if fparent2 == nullid or cnode is None: # copied on remote side
2740 if fparent2 == nullid or cnode is None: # copied on remote side
2741 if cfname in manifest2:
2741 if cfname in manifest2:
2742 cnode = manifest2[cfname]
2742 cnode = manifest2[cfname]
2743 newfparent = fparent1
2743 newfparent = fparent1
2744
2744
2745 # Here, we used to search backwards through history to try to find
2745 # Here, we used to search backwards through history to try to find
2746 # where the file copy came from if the source of a copy was not in
2746 # where the file copy came from if the source of a copy was not in
2747 # the parent directory. However, this doesn't actually make sense to
2747 # the parent directory. However, this doesn't actually make sense to
2748 # do (what does a copy from something not in your working copy even
2748 # do (what does a copy from something not in your working copy even
2749 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2749 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2750 # the user that copy information was dropped, so if they didn't
2750 # the user that copy information was dropped, so if they didn't
2751 # expect this outcome it can be fixed, but this is the correct
2751 # expect this outcome it can be fixed, but this is the correct
2752 # behavior in this circumstance.
2752 # behavior in this circumstance.
2753
2753
2754 if cnode:
2754 if cnode:
2755 self.ui.debug(
2755 self.ui.debug(
2756 b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode))
2756 b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode))
2757 )
2757 )
2758 if includecopymeta:
2758 if includecopymeta:
2759 meta[b"copy"] = cfname
2759 meta[b"copy"] = cfname
2760 meta[b"copyrev"] = hex(cnode)
2760 meta[b"copyrev"] = hex(cnode)
2761 fparent1, fparent2 = nullid, newfparent
2761 fparent1, fparent2 = nullid, newfparent
2762 else:
2762 else:
2763 self.ui.warn(
2763 self.ui.warn(
2764 _(
2764 _(
2765 b"warning: can't find ancestor for '%s' "
2765 b"warning: can't find ancestor for '%s' "
2766 b"copied from '%s'!\n"
2766 b"copied from '%s'!\n"
2767 )
2767 )
2768 % (fname, cfname)
2768 % (fname, cfname)
2769 )
2769 )
2770
2770
2771 elif fparent1 == nullid:
2771 elif fparent1 == nullid:
2772 fparent1, fparent2 = fparent2, nullid
2772 fparent1, fparent2 = fparent2, nullid
2773 elif fparent2 != nullid:
2773 elif fparent2 != nullid:
2774 # is one parent an ancestor of the other?
2774 # is one parent an ancestor of the other?
2775 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2775 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2776 if fparent1 in fparentancestors:
2776 if fparent1 in fparentancestors:
2777 fparent1, fparent2 = fparent2, nullid
2777 fparent1, fparent2 = fparent2, nullid
2778 elif fparent2 in fparentancestors:
2778 elif fparent2 in fparentancestors:
2779 fparent2 = nullid
2779 fparent2 = nullid
2780
2780
2781 # is the file changed?
2781 # is the file changed?
2782 text = fctx.data()
2782 text = fctx.data()
2783 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2783 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2784 changelist.append(fname)
2784 changelist.append(fname)
2785 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2785 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2786 # are just the flags changed during merge?
2786 # are just the flags changed during merge?
2787 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2787 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2788 changelist.append(fname)
2788 changelist.append(fname)
2789
2789
2790 return fparent1
2790 return fparent1
2791
2791
2792 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2792 def checkcommitpatterns(self, wctx, match, status, fail):
2793 """check for commit arguments that aren't committable"""
2793 """check for commit arguments that aren't committable"""
2794 if match.isexact() or match.prefix():
2794 if match.isexact() or match.prefix():
2795 matched = set(status.modified + status.added + status.removed)
2795 matched = set(status.modified + status.added + status.removed)
2796
2796
2797 for f in match.files():
2797 for f in match.files():
2798 f = self.dirstate.normalize(f)
2798 f = self.dirstate.normalize(f)
2799 if f == b'.' or f in matched or f in wctx.substate:
2799 if f == b'.' or f in matched or f in wctx.substate:
2800 continue
2800 continue
2801 if f in status.deleted:
2801 if f in status.deleted:
2802 fail(f, _(b'file not found!'))
2802 fail(f, _(b'file not found!'))
2803 # Is it a directory that exists or used to exist?
2803 # Is it a directory that exists or used to exist?
2804 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2804 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2805 d = f + b'/'
2805 d = f + b'/'
2806 for mf in matched:
2806 for mf in matched:
2807 if mf.startswith(d):
2807 if mf.startswith(d):
2808 break
2808 break
2809 else:
2809 else:
2810 fail(f, _(b"no match under directory!"))
2810 fail(f, _(b"no match under directory!"))
2811 elif f not in self.dirstate:
2811 elif f not in self.dirstate:
2812 fail(f, _(b"file not tracked!"))
2812 fail(f, _(b"file not tracked!"))
2813
2813
2814 @unfilteredmethod
2814 @unfilteredmethod
2815 def commit(
2815 def commit(
2816 self,
2816 self,
2817 text=b"",
2817 text=b"",
2818 user=None,
2818 user=None,
2819 date=None,
2819 date=None,
2820 match=None,
2820 match=None,
2821 force=False,
2821 force=False,
2822 editor=False,
2822 editor=False,
2823 extra=None,
2823 extra=None,
2824 ):
2824 ):
2825 """Add a new revision to current repository.
2825 """Add a new revision to current repository.
2826
2826
2827 Revision information is gathered from the working directory,
2827 Revision information is gathered from the working directory,
2828 match can be used to filter the committed files. If editor is
2828 match can be used to filter the committed files. If editor is
2829 supplied, it is called to get a commit message.
2829 supplied, it is called to get a commit message.
2830 """
2830 """
2831 if extra is None:
2831 if extra is None:
2832 extra = {}
2832 extra = {}
2833
2833
2834 def fail(f, msg):
2834 def fail(f, msg):
2835 raise error.Abort(b'%s: %s' % (f, msg))
2835 raise error.Abort(b'%s: %s' % (f, msg))
2836
2836
2837 if not match:
2837 if not match:
2838 match = matchmod.always()
2838 match = matchmod.always()
2839
2839
2840 if not force:
2840 if not force:
2841 vdirs = []
2842 match.explicitdir = vdirs.append
2843 match.bad = fail
2841 match.bad = fail
2844
2842
2845 # lock() for recent changelog (see issue4368)
2843 # lock() for recent changelog (see issue4368)
2846 with self.wlock(), self.lock():
2844 with self.wlock(), self.lock():
2847 wctx = self[None]
2845 wctx = self[None]
2848 merge = len(wctx.parents()) > 1
2846 merge = len(wctx.parents()) > 1
2849
2847
2850 if not force and merge and not match.always():
2848 if not force and merge and not match.always():
2851 raise error.Abort(
2849 raise error.Abort(
2852 _(
2850 _(
2853 b'cannot partially commit a merge '
2851 b'cannot partially commit a merge '
2854 b'(do not specify files or patterns)'
2852 b'(do not specify files or patterns)'
2855 )
2853 )
2856 )
2854 )
2857
2855
2858 status = self.status(match=match, clean=force)
2856 status = self.status(match=match, clean=force)
2859 if force:
2857 if force:
2860 status.modified.extend(
2858 status.modified.extend(
2861 status.clean
2859 status.clean
2862 ) # mq may commit clean files
2860 ) # mq may commit clean files
2863
2861
2864 # check subrepos
2862 # check subrepos
2865 subs, commitsubs, newstate = subrepoutil.precommit(
2863 subs, commitsubs, newstate = subrepoutil.precommit(
2866 self.ui, wctx, status, match, force=force
2864 self.ui, wctx, status, match, force=force
2867 )
2865 )
2868
2866
2869 # make sure all explicit patterns are matched
2867 # make sure all explicit patterns are matched
2870 if not force:
2868 if not force:
2871 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2869 self.checkcommitpatterns(wctx, match, status, fail)
2872
2870
2873 cctx = context.workingcommitctx(
2871 cctx = context.workingcommitctx(
2874 self, status, text, user, date, extra
2872 self, status, text, user, date, extra
2875 )
2873 )
2876
2874
2877 # internal config: ui.allowemptycommit
2875 # internal config: ui.allowemptycommit
2878 allowemptycommit = (
2876 allowemptycommit = (
2879 wctx.branch() != wctx.p1().branch()
2877 wctx.branch() != wctx.p1().branch()
2880 or extra.get(b'close')
2878 or extra.get(b'close')
2881 or merge
2879 or merge
2882 or cctx.files()
2880 or cctx.files()
2883 or self.ui.configbool(b'ui', b'allowemptycommit')
2881 or self.ui.configbool(b'ui', b'allowemptycommit')
2884 )
2882 )
2885 if not allowemptycommit:
2883 if not allowemptycommit:
2886 return None
2884 return None
2887
2885
2888 if merge and cctx.deleted():
2886 if merge and cctx.deleted():
2889 raise error.Abort(_(b"cannot commit merge with missing files"))
2887 raise error.Abort(_(b"cannot commit merge with missing files"))
2890
2888
2891 ms = mergemod.mergestate.read(self)
2889 ms = mergemod.mergestate.read(self)
2892 mergeutil.checkunresolved(ms)
2890 mergeutil.checkunresolved(ms)
2893
2891
2894 if editor:
2892 if editor:
2895 cctx._text = editor(self, cctx, subs)
2893 cctx._text = editor(self, cctx, subs)
2896 edited = text != cctx._text
2894 edited = text != cctx._text
2897
2895
2898 # Save commit message in case this transaction gets rolled back
2896 # Save commit message in case this transaction gets rolled back
2899 # (e.g. by a pretxncommit hook). Leave the content alone on
2897 # (e.g. by a pretxncommit hook). Leave the content alone on
2900 # the assumption that the user will use the same editor again.
2898 # the assumption that the user will use the same editor again.
2901 msgfn = self.savecommitmessage(cctx._text)
2899 msgfn = self.savecommitmessage(cctx._text)
2902
2900
2903 # commit subs and write new state
2901 # commit subs and write new state
2904 if subs:
2902 if subs:
2905 uipathfn = scmutil.getuipathfn(self)
2903 uipathfn = scmutil.getuipathfn(self)
2906 for s in sorted(commitsubs):
2904 for s in sorted(commitsubs):
2907 sub = wctx.sub(s)
2905 sub = wctx.sub(s)
2908 self.ui.status(
2906 self.ui.status(
2909 _(b'committing subrepository %s\n')
2907 _(b'committing subrepository %s\n')
2910 % uipathfn(subrepoutil.subrelpath(sub))
2908 % uipathfn(subrepoutil.subrelpath(sub))
2911 )
2909 )
2912 sr = sub.commit(cctx._text, user, date)
2910 sr = sub.commit(cctx._text, user, date)
2913 newstate[s] = (newstate[s][0], sr)
2911 newstate[s] = (newstate[s][0], sr)
2914 subrepoutil.writestate(self, newstate)
2912 subrepoutil.writestate(self, newstate)
2915
2913
2916 p1, p2 = self.dirstate.parents()
2914 p1, p2 = self.dirstate.parents()
2917 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or b'')
2915 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or b'')
2918 try:
2916 try:
2919 self.hook(
2917 self.hook(
2920 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
2918 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
2921 )
2919 )
2922 with self.transaction(b'commit'):
2920 with self.transaction(b'commit'):
2923 ret = self.commitctx(cctx, True)
2921 ret = self.commitctx(cctx, True)
2924 # update bookmarks, dirstate and mergestate
2922 # update bookmarks, dirstate and mergestate
2925 bookmarks.update(self, [p1, p2], ret)
2923 bookmarks.update(self, [p1, p2], ret)
2926 cctx.markcommitted(ret)
2924 cctx.markcommitted(ret)
2927 ms.reset()
2925 ms.reset()
2928 except: # re-raises
2926 except: # re-raises
2929 if edited:
2927 if edited:
2930 self.ui.write(
2928 self.ui.write(
2931 _(b'note: commit message saved in %s\n') % msgfn
2929 _(b'note: commit message saved in %s\n') % msgfn
2932 )
2930 )
2933 raise
2931 raise
2934
2932
2935 def commithook():
2933 def commithook():
2936 # hack for command that use a temporary commit (eg: histedit)
2934 # hack for command that use a temporary commit (eg: histedit)
2937 # temporary commit got stripped before hook release
2935 # temporary commit got stripped before hook release
2938 if self.changelog.hasnode(ret):
2936 if self.changelog.hasnode(ret):
2939 self.hook(
2937 self.hook(
2940 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
2938 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
2941 )
2939 )
2942
2940
2943 self._afterlock(commithook)
2941 self._afterlock(commithook)
2944 return ret
2942 return ret
2945
2943
2946 @unfilteredmethod
2944 @unfilteredmethod
2947 def commitctx(self, ctx, error=False, origctx=None):
2945 def commitctx(self, ctx, error=False, origctx=None):
2948 """Add a new revision to current repository.
2946 """Add a new revision to current repository.
2949 Revision information is passed via the context argument.
2947 Revision information is passed via the context argument.
2950
2948
2951 ctx.files() should list all files involved in this commit, i.e.
2949 ctx.files() should list all files involved in this commit, i.e.
2952 modified/added/removed files. On merge, it may be wider than the
2950 modified/added/removed files. On merge, it may be wider than the
2953 ctx.files() to be committed, since any file nodes derived directly
2951 ctx.files() to be committed, since any file nodes derived directly
2954 from p1 or p2 are excluded from the committed ctx.files().
2952 from p1 or p2 are excluded from the committed ctx.files().
2955
2953
2956 origctx is for convert to work around the problem that bug
2954 origctx is for convert to work around the problem that bug
2957 fixes to the files list in changesets change hashes. For
2955 fixes to the files list in changesets change hashes. For
2958 convert to be the identity, it can pass an origctx and this
2956 convert to be the identity, it can pass an origctx and this
2959 function will use the same files list when it makes sense to
2957 function will use the same files list when it makes sense to
2960 do so.
2958 do so.
2961 """
2959 """
2962
2960
2963 p1, p2 = ctx.p1(), ctx.p2()
2961 p1, p2 = ctx.p1(), ctx.p2()
2964 user = ctx.user()
2962 user = ctx.user()
2965
2963
2966 if self.filecopiesmode == b'changeset-sidedata':
2964 if self.filecopiesmode == b'changeset-sidedata':
2967 writechangesetcopy = True
2965 writechangesetcopy = True
2968 writefilecopymeta = True
2966 writefilecopymeta = True
2969 writecopiesto = None
2967 writecopiesto = None
2970 else:
2968 else:
2971 writecopiesto = self.ui.config(b'experimental', b'copies.write-to')
2969 writecopiesto = self.ui.config(b'experimental', b'copies.write-to')
2972 writefilecopymeta = writecopiesto != b'changeset-only'
2970 writefilecopymeta = writecopiesto != b'changeset-only'
2973 writechangesetcopy = writecopiesto in (
2971 writechangesetcopy = writecopiesto in (
2974 b'changeset-only',
2972 b'changeset-only',
2975 b'compatibility',
2973 b'compatibility',
2976 )
2974 )
2977 p1copies, p2copies = None, None
2975 p1copies, p2copies = None, None
2978 if writechangesetcopy:
2976 if writechangesetcopy:
2979 p1copies = ctx.p1copies()
2977 p1copies = ctx.p1copies()
2980 p2copies = ctx.p2copies()
2978 p2copies = ctx.p2copies()
2981 filesadded, filesremoved = None, None
2979 filesadded, filesremoved = None, None
2982 with self.lock(), self.transaction(b"commit") as tr:
2980 with self.lock(), self.transaction(b"commit") as tr:
2983 trp = weakref.proxy(tr)
2981 trp = weakref.proxy(tr)
2984
2982
2985 if ctx.manifestnode():
2983 if ctx.manifestnode():
2986 # reuse an existing manifest revision
2984 # reuse an existing manifest revision
2987 self.ui.debug(b'reusing known manifest\n')
2985 self.ui.debug(b'reusing known manifest\n')
2988 mn = ctx.manifestnode()
2986 mn = ctx.manifestnode()
2989 files = ctx.files()
2987 files = ctx.files()
2990 if writechangesetcopy:
2988 if writechangesetcopy:
2991 filesadded = ctx.filesadded()
2989 filesadded = ctx.filesadded()
2992 filesremoved = ctx.filesremoved()
2990 filesremoved = ctx.filesremoved()
2993 elif ctx.files():
2991 elif ctx.files():
2994 m1ctx = p1.manifestctx()
2992 m1ctx = p1.manifestctx()
2995 m2ctx = p2.manifestctx()
2993 m2ctx = p2.manifestctx()
2996 mctx = m1ctx.copy()
2994 mctx = m1ctx.copy()
2997
2995
2998 m = mctx.read()
2996 m = mctx.read()
2999 m1 = m1ctx.read()
2997 m1 = m1ctx.read()
3000 m2 = m2ctx.read()
2998 m2 = m2ctx.read()
3001
2999
3002 # check in files
3000 # check in files
3003 added = []
3001 added = []
3004 changed = []
3002 changed = []
3005 removed = list(ctx.removed())
3003 removed = list(ctx.removed())
3006 linkrev = len(self)
3004 linkrev = len(self)
3007 self.ui.note(_(b"committing files:\n"))
3005 self.ui.note(_(b"committing files:\n"))
3008 uipathfn = scmutil.getuipathfn(self)
3006 uipathfn = scmutil.getuipathfn(self)
3009 for f in sorted(ctx.modified() + ctx.added()):
3007 for f in sorted(ctx.modified() + ctx.added()):
3010 self.ui.note(uipathfn(f) + b"\n")
3008 self.ui.note(uipathfn(f) + b"\n")
3011 try:
3009 try:
3012 fctx = ctx[f]
3010 fctx = ctx[f]
3013 if fctx is None:
3011 if fctx is None:
3014 removed.append(f)
3012 removed.append(f)
3015 else:
3013 else:
3016 added.append(f)
3014 added.append(f)
3017 m[f] = self._filecommit(
3015 m[f] = self._filecommit(
3018 fctx,
3016 fctx,
3019 m1,
3017 m1,
3020 m2,
3018 m2,
3021 linkrev,
3019 linkrev,
3022 trp,
3020 trp,
3023 changed,
3021 changed,
3024 writefilecopymeta,
3022 writefilecopymeta,
3025 )
3023 )
3026 m.setflag(f, fctx.flags())
3024 m.setflag(f, fctx.flags())
3027 except OSError:
3025 except OSError:
3028 self.ui.warn(
3026 self.ui.warn(
3029 _(b"trouble committing %s!\n") % uipathfn(f)
3027 _(b"trouble committing %s!\n") % uipathfn(f)
3030 )
3028 )
3031 raise
3029 raise
3032 except IOError as inst:
3030 except IOError as inst:
3033 errcode = getattr(inst, 'errno', errno.ENOENT)
3031 errcode = getattr(inst, 'errno', errno.ENOENT)
3034 if error or errcode and errcode != errno.ENOENT:
3032 if error or errcode and errcode != errno.ENOENT:
3035 self.ui.warn(
3033 self.ui.warn(
3036 _(b"trouble committing %s!\n") % uipathfn(f)
3034 _(b"trouble committing %s!\n") % uipathfn(f)
3037 )
3035 )
3038 raise
3036 raise
3039
3037
3040 # update manifest
3038 # update manifest
3041 removed = [f for f in removed if f in m1 or f in m2]
3039 removed = [f for f in removed if f in m1 or f in m2]
3042 drop = sorted([f for f in removed if f in m])
3040 drop = sorted([f for f in removed if f in m])
3043 for f in drop:
3041 for f in drop:
3044 del m[f]
3042 del m[f]
3045 if p2.rev() != nullrev:
3043 if p2.rev() != nullrev:
3046
3044
3047 @util.cachefunc
3045 @util.cachefunc
3048 def mas():
3046 def mas():
3049 p1n = p1.node()
3047 p1n = p1.node()
3050 p2n = p2.node()
3048 p2n = p2.node()
3051 cahs = self.changelog.commonancestorsheads(p1n, p2n)
3049 cahs = self.changelog.commonancestorsheads(p1n, p2n)
3052 if not cahs:
3050 if not cahs:
3053 cahs = [nullrev]
3051 cahs = [nullrev]
3054 return [self[r].manifest() for r in cahs]
3052 return [self[r].manifest() for r in cahs]
3055
3053
3056 def deletionfromparent(f):
3054 def deletionfromparent(f):
3057 # When a file is removed relative to p1 in a merge, this
3055 # When a file is removed relative to p1 in a merge, this
3058 # function determines whether the absence is due to a
3056 # function determines whether the absence is due to a
3059 # deletion from a parent, or whether the merge commit
3057 # deletion from a parent, or whether the merge commit
3060 # itself deletes the file. We decide this by doing a
3058 # itself deletes the file. We decide this by doing a
3061 # simplified three way merge of the manifest entry for
3059 # simplified three way merge of the manifest entry for
3062 # the file. There are two ways we decide the merge
3060 # the file. There are two ways we decide the merge
3063 # itself didn't delete a file:
3061 # itself didn't delete a file:
3064 # - neither parent (nor the merge) contain the file
3062 # - neither parent (nor the merge) contain the file
3065 # - exactly one parent contains the file, and that
3063 # - exactly one parent contains the file, and that
3066 # parent has the same filelog entry as the merge
3064 # parent has the same filelog entry as the merge
3067 # ancestor (or all of them if there two). In other
3065 # ancestor (or all of them if there two). In other
3068 # words, that parent left the file unchanged while the
3066 # words, that parent left the file unchanged while the
3069 # other one deleted it.
3067 # other one deleted it.
3070 # One way to think about this is that deleting a file is
3068 # One way to think about this is that deleting a file is
3071 # similar to emptying it, so the list of changed files
3069 # similar to emptying it, so the list of changed files
3072 # should be similar either way. The computation
3070 # should be similar either way. The computation
3073 # described above is not done directly in _filecommit
3071 # described above is not done directly in _filecommit
3074 # when creating the list of changed files, however
3072 # when creating the list of changed files, however
3075 # it does something very similar by comparing filelog
3073 # it does something very similar by comparing filelog
3076 # nodes.
3074 # nodes.
3077 if f in m1:
3075 if f in m1:
3078 return f not in m2 and all(
3076 return f not in m2 and all(
3079 f in ma and ma.find(f) == m1.find(f)
3077 f in ma and ma.find(f) == m1.find(f)
3080 for ma in mas()
3078 for ma in mas()
3081 )
3079 )
3082 elif f in m2:
3080 elif f in m2:
3083 return all(
3081 return all(
3084 f in ma and ma.find(f) == m2.find(f)
3082 f in ma and ma.find(f) == m2.find(f)
3085 for ma in mas()
3083 for ma in mas()
3086 )
3084 )
3087 else:
3085 else:
3088 return True
3086 return True
3089
3087
3090 removed = [f for f in removed if not deletionfromparent(f)]
3088 removed = [f for f in removed if not deletionfromparent(f)]
3091
3089
3092 files = changed + removed
3090 files = changed + removed
3093 md = None
3091 md = None
3094 if not files:
3092 if not files:
3095 # if no "files" actually changed in terms of the changelog,
3093 # if no "files" actually changed in terms of the changelog,
3096 # try hard to detect unmodified manifest entry so that the
3094 # try hard to detect unmodified manifest entry so that the
3097 # exact same commit can be reproduced later on convert.
3095 # exact same commit can be reproduced later on convert.
3098 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
3096 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
3099 if not files and md:
3097 if not files and md:
3100 self.ui.debug(
3098 self.ui.debug(
3101 b'not reusing manifest (no file change in '
3099 b'not reusing manifest (no file change in '
3102 b'changelog, but manifest differs)\n'
3100 b'changelog, but manifest differs)\n'
3103 )
3101 )
3104 if files or md:
3102 if files or md:
3105 self.ui.note(_(b"committing manifest\n"))
3103 self.ui.note(_(b"committing manifest\n"))
3106 # we're using narrowmatch here since it's already applied at
3104 # we're using narrowmatch here since it's already applied at
3107 # other stages (such as dirstate.walk), so we're already
3105 # other stages (such as dirstate.walk), so we're already
3108 # ignoring things outside of narrowspec in most cases. The
3106 # ignoring things outside of narrowspec in most cases. The
3109 # one case where we might have files outside the narrowspec
3107 # one case where we might have files outside the narrowspec
3110 # at this point is merges, and we already error out in the
3108 # at this point is merges, and we already error out in the
3111 # case where the merge has files outside of the narrowspec,
3109 # case where the merge has files outside of the narrowspec,
3112 # so this is safe.
3110 # so this is safe.
3113 mn = mctx.write(
3111 mn = mctx.write(
3114 trp,
3112 trp,
3115 linkrev,
3113 linkrev,
3116 p1.manifestnode(),
3114 p1.manifestnode(),
3117 p2.manifestnode(),
3115 p2.manifestnode(),
3118 added,
3116 added,
3119 drop,
3117 drop,
3120 match=self.narrowmatch(),
3118 match=self.narrowmatch(),
3121 )
3119 )
3122
3120
3123 if writechangesetcopy:
3121 if writechangesetcopy:
3124 filesadded = [
3122 filesadded = [
3125 f for f in changed if not (f in m1 or f in m2)
3123 f for f in changed if not (f in m1 or f in m2)
3126 ]
3124 ]
3127 filesremoved = removed
3125 filesremoved = removed
3128 else:
3126 else:
3129 self.ui.debug(
3127 self.ui.debug(
3130 b'reusing manifest from p1 (listed files '
3128 b'reusing manifest from p1 (listed files '
3131 b'actually unchanged)\n'
3129 b'actually unchanged)\n'
3132 )
3130 )
3133 mn = p1.manifestnode()
3131 mn = p1.manifestnode()
3134 else:
3132 else:
3135 self.ui.debug(b'reusing manifest from p1 (no file change)\n')
3133 self.ui.debug(b'reusing manifest from p1 (no file change)\n')
3136 mn = p1.manifestnode()
3134 mn = p1.manifestnode()
3137 files = []
3135 files = []
3138
3136
3139 if writecopiesto == b'changeset-only':
3137 if writecopiesto == b'changeset-only':
3140 # If writing only to changeset extras, use None to indicate that
3138 # If writing only to changeset extras, use None to indicate that
3141 # no entry should be written. If writing to both, write an empty
3139 # no entry should be written. If writing to both, write an empty
3142 # entry to prevent the reader from falling back to reading
3140 # entry to prevent the reader from falling back to reading
3143 # filelogs.
3141 # filelogs.
3144 p1copies = p1copies or None
3142 p1copies = p1copies or None
3145 p2copies = p2copies or None
3143 p2copies = p2copies or None
3146 filesadded = filesadded or None
3144 filesadded = filesadded or None
3147 filesremoved = filesremoved or None
3145 filesremoved = filesremoved or None
3148
3146
3149 if origctx and origctx.manifestnode() == mn:
3147 if origctx and origctx.manifestnode() == mn:
3150 files = origctx.files()
3148 files = origctx.files()
3151
3149
3152 # update changelog
3150 # update changelog
3153 self.ui.note(_(b"committing changelog\n"))
3151 self.ui.note(_(b"committing changelog\n"))
3154 self.changelog.delayupdate(tr)
3152 self.changelog.delayupdate(tr)
3155 n = self.changelog.add(
3153 n = self.changelog.add(
3156 mn,
3154 mn,
3157 files,
3155 files,
3158 ctx.description(),
3156 ctx.description(),
3159 trp,
3157 trp,
3160 p1.node(),
3158 p1.node(),
3161 p2.node(),
3159 p2.node(),
3162 user,
3160 user,
3163 ctx.date(),
3161 ctx.date(),
3164 ctx.extra().copy(),
3162 ctx.extra().copy(),
3165 p1copies,
3163 p1copies,
3166 p2copies,
3164 p2copies,
3167 filesadded,
3165 filesadded,
3168 filesremoved,
3166 filesremoved,
3169 )
3167 )
3170 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
3168 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
3171 self.hook(
3169 self.hook(
3172 b'pretxncommit',
3170 b'pretxncommit',
3173 throw=True,
3171 throw=True,
3174 node=hex(n),
3172 node=hex(n),
3175 parent1=xp1,
3173 parent1=xp1,
3176 parent2=xp2,
3174 parent2=xp2,
3177 )
3175 )
3178 # set the new commit is proper phase
3176 # set the new commit is proper phase
3179 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
3177 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
3180 if targetphase:
3178 if targetphase:
3181 # retract boundary do not alter parent changeset.
3179 # retract boundary do not alter parent changeset.
3182 # if a parent have higher the resulting phase will
3180 # if a parent have higher the resulting phase will
3183 # be compliant anyway
3181 # be compliant anyway
3184 #
3182 #
3185 # if minimal phase was 0 we don't need to retract anything
3183 # if minimal phase was 0 we don't need to retract anything
3186 phases.registernew(self, tr, targetphase, [n])
3184 phases.registernew(self, tr, targetphase, [n])
3187 return n
3185 return n
3188
3186
3189 @unfilteredmethod
3187 @unfilteredmethod
3190 def destroying(self):
3188 def destroying(self):
3191 '''Inform the repository that nodes are about to be destroyed.
3189 '''Inform the repository that nodes are about to be destroyed.
3192 Intended for use by strip and rollback, so there's a common
3190 Intended for use by strip and rollback, so there's a common
3193 place for anything that has to be done before destroying history.
3191 place for anything that has to be done before destroying history.
3194
3192
3195 This is mostly useful for saving state that is in memory and waiting
3193 This is mostly useful for saving state that is in memory and waiting
3196 to be flushed when the current lock is released. Because a call to
3194 to be flushed when the current lock is released. Because a call to
3197 destroyed is imminent, the repo will be invalidated causing those
3195 destroyed is imminent, the repo will be invalidated causing those
3198 changes to stay in memory (waiting for the next unlock), or vanish
3196 changes to stay in memory (waiting for the next unlock), or vanish
3199 completely.
3197 completely.
3200 '''
3198 '''
3201 # When using the same lock to commit and strip, the phasecache is left
3199 # When using the same lock to commit and strip, the phasecache is left
3202 # dirty after committing. Then when we strip, the repo is invalidated,
3200 # dirty after committing. Then when we strip, the repo is invalidated,
3203 # causing those changes to disappear.
3201 # causing those changes to disappear.
3204 if '_phasecache' in vars(self):
3202 if '_phasecache' in vars(self):
3205 self._phasecache.write()
3203 self._phasecache.write()
3206
3204
3207 @unfilteredmethod
3205 @unfilteredmethod
3208 def destroyed(self):
3206 def destroyed(self):
3209 '''Inform the repository that nodes have been destroyed.
3207 '''Inform the repository that nodes have been destroyed.
3210 Intended for use by strip and rollback, so there's a common
3208 Intended for use by strip and rollback, so there's a common
3211 place for anything that has to be done after destroying history.
3209 place for anything that has to be done after destroying history.
3212 '''
3210 '''
3213 # When one tries to:
3211 # When one tries to:
3214 # 1) destroy nodes thus calling this method (e.g. strip)
3212 # 1) destroy nodes thus calling this method (e.g. strip)
3215 # 2) use phasecache somewhere (e.g. commit)
3213 # 2) use phasecache somewhere (e.g. commit)
3216 #
3214 #
3217 # then 2) will fail because the phasecache contains nodes that were
3215 # then 2) will fail because the phasecache contains nodes that were
3218 # removed. We can either remove phasecache from the filecache,
3216 # removed. We can either remove phasecache from the filecache,
3219 # causing it to reload next time it is accessed, or simply filter
3217 # causing it to reload next time it is accessed, or simply filter
3220 # the removed nodes now and write the updated cache.
3218 # the removed nodes now and write the updated cache.
3221 self._phasecache.filterunknown(self)
3219 self._phasecache.filterunknown(self)
3222 self._phasecache.write()
3220 self._phasecache.write()
3223
3221
3224 # refresh all repository caches
3222 # refresh all repository caches
3225 self.updatecaches()
3223 self.updatecaches()
3226
3224
3227 # Ensure the persistent tag cache is updated. Doing it now
3225 # Ensure the persistent tag cache is updated. Doing it now
3228 # means that the tag cache only has to worry about destroyed
3226 # means that the tag cache only has to worry about destroyed
3229 # heads immediately after a strip/rollback. That in turn
3227 # heads immediately after a strip/rollback. That in turn
3230 # guarantees that "cachetip == currenttip" (comparing both rev
3228 # guarantees that "cachetip == currenttip" (comparing both rev
3231 # and node) always means no nodes have been added or destroyed.
3229 # and node) always means no nodes have been added or destroyed.
3232
3230
3233 # XXX this is suboptimal when qrefresh'ing: we strip the current
3231 # XXX this is suboptimal when qrefresh'ing: we strip the current
3234 # head, refresh the tag cache, then immediately add a new head.
3232 # head, refresh the tag cache, then immediately add a new head.
3235 # But I think doing it this way is necessary for the "instant
3233 # But I think doing it this way is necessary for the "instant
3236 # tag cache retrieval" case to work.
3234 # tag cache retrieval" case to work.
3237 self.invalidate()
3235 self.invalidate()
3238
3236
3239 def status(
3237 def status(
3240 self,
3238 self,
3241 node1=b'.',
3239 node1=b'.',
3242 node2=None,
3240 node2=None,
3243 match=None,
3241 match=None,
3244 ignored=False,
3242 ignored=False,
3245 clean=False,
3243 clean=False,
3246 unknown=False,
3244 unknown=False,
3247 listsubrepos=False,
3245 listsubrepos=False,
3248 ):
3246 ):
3249 '''a convenience method that calls node1.status(node2)'''
3247 '''a convenience method that calls node1.status(node2)'''
3250 return self[node1].status(
3248 return self[node1].status(
3251 node2, match, ignored, clean, unknown, listsubrepos
3249 node2, match, ignored, clean, unknown, listsubrepos
3252 )
3250 )
3253
3251
3254 def addpostdsstatus(self, ps):
3252 def addpostdsstatus(self, ps):
3255 """Add a callback to run within the wlock, at the point at which status
3253 """Add a callback to run within the wlock, at the point at which status
3256 fixups happen.
3254 fixups happen.
3257
3255
3258 On status completion, callback(wctx, status) will be called with the
3256 On status completion, callback(wctx, status) will be called with the
3259 wlock held, unless the dirstate has changed from underneath or the wlock
3257 wlock held, unless the dirstate has changed from underneath or the wlock
3260 couldn't be grabbed.
3258 couldn't be grabbed.
3261
3259
3262 Callbacks should not capture and use a cached copy of the dirstate --
3260 Callbacks should not capture and use a cached copy of the dirstate --
3263 it might change in the meanwhile. Instead, they should access the
3261 it might change in the meanwhile. Instead, they should access the
3264 dirstate via wctx.repo().dirstate.
3262 dirstate via wctx.repo().dirstate.
3265
3263
3266 This list is emptied out after each status run -- extensions should
3264 This list is emptied out after each status run -- extensions should
3267 make sure it adds to this list each time dirstate.status is called.
3265 make sure it adds to this list each time dirstate.status is called.
3268 Extensions should also make sure they don't call this for statuses
3266 Extensions should also make sure they don't call this for statuses
3269 that don't involve the dirstate.
3267 that don't involve the dirstate.
3270 """
3268 """
3271
3269
3272 # The list is located here for uniqueness reasons -- it is actually
3270 # The list is located here for uniqueness reasons -- it is actually
3273 # managed by the workingctx, but that isn't unique per-repo.
3271 # managed by the workingctx, but that isn't unique per-repo.
3274 self._postdsstatus.append(ps)
3272 self._postdsstatus.append(ps)
3275
3273
3276 def postdsstatus(self):
3274 def postdsstatus(self):
3277 """Used by workingctx to get the list of post-dirstate-status hooks."""
3275 """Used by workingctx to get the list of post-dirstate-status hooks."""
3278 return self._postdsstatus
3276 return self._postdsstatus
3279
3277
3280 def clearpostdsstatus(self):
3278 def clearpostdsstatus(self):
3281 """Used by workingctx to clear post-dirstate-status hooks."""
3279 """Used by workingctx to clear post-dirstate-status hooks."""
3282 del self._postdsstatus[:]
3280 del self._postdsstatus[:]
3283
3281
3284 def heads(self, start=None):
3282 def heads(self, start=None):
3285 if start is None:
3283 if start is None:
3286 cl = self.changelog
3284 cl = self.changelog
3287 headrevs = reversed(cl.headrevs())
3285 headrevs = reversed(cl.headrevs())
3288 return [cl.node(rev) for rev in headrevs]
3286 return [cl.node(rev) for rev in headrevs]
3289
3287
3290 heads = self.changelog.heads(start)
3288 heads = self.changelog.heads(start)
3291 # sort the output in rev descending order
3289 # sort the output in rev descending order
3292 return sorted(heads, key=self.changelog.rev, reverse=True)
3290 return sorted(heads, key=self.changelog.rev, reverse=True)
3293
3291
3294 def branchheads(self, branch=None, start=None, closed=False):
3292 def branchheads(self, branch=None, start=None, closed=False):
3295 '''return a (possibly filtered) list of heads for the given branch
3293 '''return a (possibly filtered) list of heads for the given branch
3296
3294
3297 Heads are returned in topological order, from newest to oldest.
3295 Heads are returned in topological order, from newest to oldest.
3298 If branch is None, use the dirstate branch.
3296 If branch is None, use the dirstate branch.
3299 If start is not None, return only heads reachable from start.
3297 If start is not None, return only heads reachable from start.
3300 If closed is True, return heads that are marked as closed as well.
3298 If closed is True, return heads that are marked as closed as well.
3301 '''
3299 '''
3302 if branch is None:
3300 if branch is None:
3303 branch = self[None].branch()
3301 branch = self[None].branch()
3304 branches = self.branchmap()
3302 branches = self.branchmap()
3305 if not branches.hasbranch(branch):
3303 if not branches.hasbranch(branch):
3306 return []
3304 return []
3307 # the cache returns heads ordered lowest to highest
3305 # the cache returns heads ordered lowest to highest
3308 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3306 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3309 if start is not None:
3307 if start is not None:
3310 # filter out the heads that cannot be reached from startrev
3308 # filter out the heads that cannot be reached from startrev
3311 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3309 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3312 bheads = [h for h in bheads if h in fbheads]
3310 bheads = [h for h in bheads if h in fbheads]
3313 return bheads
3311 return bheads
3314
3312
3315 def branches(self, nodes):
3313 def branches(self, nodes):
3316 if not nodes:
3314 if not nodes:
3317 nodes = [self.changelog.tip()]
3315 nodes = [self.changelog.tip()]
3318 b = []
3316 b = []
3319 for n in nodes:
3317 for n in nodes:
3320 t = n
3318 t = n
3321 while True:
3319 while True:
3322 p = self.changelog.parents(n)
3320 p = self.changelog.parents(n)
3323 if p[1] != nullid or p[0] == nullid:
3321 if p[1] != nullid or p[0] == nullid:
3324 b.append((t, n, p[0], p[1]))
3322 b.append((t, n, p[0], p[1]))
3325 break
3323 break
3326 n = p[0]
3324 n = p[0]
3327 return b
3325 return b
3328
3326
3329 def between(self, pairs):
3327 def between(self, pairs):
3330 r = []
3328 r = []
3331
3329
3332 for top, bottom in pairs:
3330 for top, bottom in pairs:
3333 n, l, i = top, [], 0
3331 n, l, i = top, [], 0
3334 f = 1
3332 f = 1
3335
3333
3336 while n != bottom and n != nullid:
3334 while n != bottom and n != nullid:
3337 p = self.changelog.parents(n)[0]
3335 p = self.changelog.parents(n)[0]
3338 if i == f:
3336 if i == f:
3339 l.append(n)
3337 l.append(n)
3340 f = f * 2
3338 f = f * 2
3341 n = p
3339 n = p
3342 i += 1
3340 i += 1
3343
3341
3344 r.append(l)
3342 r.append(l)
3345
3343
3346 return r
3344 return r
3347
3345
3348 def checkpush(self, pushop):
3346 def checkpush(self, pushop):
3349 """Extensions can override this function if additional checks have
3347 """Extensions can override this function if additional checks have
3350 to be performed before pushing, or call it if they override push
3348 to be performed before pushing, or call it if they override push
3351 command.
3349 command.
3352 """
3350 """
3353
3351
3354 @unfilteredpropertycache
3352 @unfilteredpropertycache
3355 def prepushoutgoinghooks(self):
3353 def prepushoutgoinghooks(self):
3356 """Return util.hooks consists of a pushop with repo, remote, outgoing
3354 """Return util.hooks consists of a pushop with repo, remote, outgoing
3357 methods, which are called before pushing changesets.
3355 methods, which are called before pushing changesets.
3358 """
3356 """
3359 return util.hooks()
3357 return util.hooks()
3360
3358
3361 def pushkey(self, namespace, key, old, new):
3359 def pushkey(self, namespace, key, old, new):
3362 try:
3360 try:
3363 tr = self.currenttransaction()
3361 tr = self.currenttransaction()
3364 hookargs = {}
3362 hookargs = {}
3365 if tr is not None:
3363 if tr is not None:
3366 hookargs.update(tr.hookargs)
3364 hookargs.update(tr.hookargs)
3367 hookargs = pycompat.strkwargs(hookargs)
3365 hookargs = pycompat.strkwargs(hookargs)
3368 hookargs['namespace'] = namespace
3366 hookargs['namespace'] = namespace
3369 hookargs['key'] = key
3367 hookargs['key'] = key
3370 hookargs['old'] = old
3368 hookargs['old'] = old
3371 hookargs['new'] = new
3369 hookargs['new'] = new
3372 self.hook(b'prepushkey', throw=True, **hookargs)
3370 self.hook(b'prepushkey', throw=True, **hookargs)
3373 except error.HookAbort as exc:
3371 except error.HookAbort as exc:
3374 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3372 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3375 if exc.hint:
3373 if exc.hint:
3376 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3374 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3377 return False
3375 return False
3378 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3376 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3379 ret = pushkey.push(self, namespace, key, old, new)
3377 ret = pushkey.push(self, namespace, key, old, new)
3380
3378
3381 def runhook():
3379 def runhook():
3382 self.hook(
3380 self.hook(
3383 b'pushkey',
3381 b'pushkey',
3384 namespace=namespace,
3382 namespace=namespace,
3385 key=key,
3383 key=key,
3386 old=old,
3384 old=old,
3387 new=new,
3385 new=new,
3388 ret=ret,
3386 ret=ret,
3389 )
3387 )
3390
3388
3391 self._afterlock(runhook)
3389 self._afterlock(runhook)
3392 return ret
3390 return ret
3393
3391
3394 def listkeys(self, namespace):
3392 def listkeys(self, namespace):
3395 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3393 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3396 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3394 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3397 values = pushkey.list(self, namespace)
3395 values = pushkey.list(self, namespace)
3398 self.hook(b'listkeys', namespace=namespace, values=values)
3396 self.hook(b'listkeys', namespace=namespace, values=values)
3399 return values
3397 return values
3400
3398
3401 def debugwireargs(self, one, two, three=None, four=None, five=None):
3399 def debugwireargs(self, one, two, three=None, four=None, five=None):
3402 '''used to test argument passing over the wire'''
3400 '''used to test argument passing over the wire'''
3403 return b"%s %s %s %s %s" % (
3401 return b"%s %s %s %s %s" % (
3404 one,
3402 one,
3405 two,
3403 two,
3406 pycompat.bytestr(three),
3404 pycompat.bytestr(three),
3407 pycompat.bytestr(four),
3405 pycompat.bytestr(four),
3408 pycompat.bytestr(five),
3406 pycompat.bytestr(five),
3409 )
3407 )
3410
3408
3411 def savecommitmessage(self, text):
3409 def savecommitmessage(self, text):
3412 fp = self.vfs(b'last-message.txt', b'wb')
3410 fp = self.vfs(b'last-message.txt', b'wb')
3413 try:
3411 try:
3414 fp.write(text)
3412 fp.write(text)
3415 finally:
3413 finally:
3416 fp.close()
3414 fp.close()
3417 return self.pathto(fp.name[len(self.root) + 1 :])
3415 return self.pathto(fp.name[len(self.root) + 1 :])
3418
3416
3419
3417
3420 # used to avoid circular references so destructors work
3418 # used to avoid circular references so destructors work
3421 def aftertrans(files):
3419 def aftertrans(files):
3422 renamefiles = [tuple(t) for t in files]
3420 renamefiles = [tuple(t) for t in files]
3423
3421
3424 def a():
3422 def a():
3425 for vfs, src, dest in renamefiles:
3423 for vfs, src, dest in renamefiles:
3426 # if src and dest refer to a same file, vfs.rename is a no-op,
3424 # if src and dest refer to a same file, vfs.rename is a no-op,
3427 # leaving both src and dest on disk. delete dest to make sure
3425 # leaving both src and dest on disk. delete dest to make sure
3428 # the rename couldn't be such a no-op.
3426 # the rename couldn't be such a no-op.
3429 vfs.tryunlink(dest)
3427 vfs.tryunlink(dest)
3430 try:
3428 try:
3431 vfs.rename(src, dest)
3429 vfs.rename(src, dest)
3432 except OSError: # journal file does not yet exist
3430 except OSError: # journal file does not yet exist
3433 pass
3431 pass
3434
3432
3435 return a
3433 return a
3436
3434
3437
3435
3438 def undoname(fn):
3436 def undoname(fn):
3439 base, name = os.path.split(fn)
3437 base, name = os.path.split(fn)
3440 assert name.startswith(b'journal')
3438 assert name.startswith(b'journal')
3441 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3439 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3442
3440
3443
3441
3444 def instance(ui, path, create, intents=None, createopts=None):
3442 def instance(ui, path, create, intents=None, createopts=None):
3445 localpath = util.urllocalpath(path)
3443 localpath = util.urllocalpath(path)
3446 if create:
3444 if create:
3447 createrepository(ui, localpath, createopts=createopts)
3445 createrepository(ui, localpath, createopts=createopts)
3448
3446
3449 return makelocalrepository(ui, localpath, intents=intents)
3447 return makelocalrepository(ui, localpath, intents=intents)
3450
3448
3451
3449
3452 def islocal(path):
3450 def islocal(path):
3453 return True
3451 return True
3454
3452
3455
3453
3456 def defaultcreateopts(ui, createopts=None):
3454 def defaultcreateopts(ui, createopts=None):
3457 """Populate the default creation options for a repository.
3455 """Populate the default creation options for a repository.
3458
3456
3459 A dictionary of explicitly requested creation options can be passed
3457 A dictionary of explicitly requested creation options can be passed
3460 in. Missing keys will be populated.
3458 in. Missing keys will be populated.
3461 """
3459 """
3462 createopts = dict(createopts or {})
3460 createopts = dict(createopts or {})
3463
3461
3464 if b'backend' not in createopts:
3462 if b'backend' not in createopts:
3465 # experimental config: storage.new-repo-backend
3463 # experimental config: storage.new-repo-backend
3466 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3464 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3467
3465
3468 return createopts
3466 return createopts
3469
3467
3470
3468
3471 def newreporequirements(ui, createopts):
3469 def newreporequirements(ui, createopts):
3472 """Determine the set of requirements for a new local repository.
3470 """Determine the set of requirements for a new local repository.
3473
3471
3474 Extensions can wrap this function to specify custom requirements for
3472 Extensions can wrap this function to specify custom requirements for
3475 new repositories.
3473 new repositories.
3476 """
3474 """
3477 # If the repo is being created from a shared repository, we copy
3475 # If the repo is being created from a shared repository, we copy
3478 # its requirements.
3476 # its requirements.
3479 if b'sharedrepo' in createopts:
3477 if b'sharedrepo' in createopts:
3480 requirements = set(createopts[b'sharedrepo'].requirements)
3478 requirements = set(createopts[b'sharedrepo'].requirements)
3481 if createopts.get(b'sharedrelative'):
3479 if createopts.get(b'sharedrelative'):
3482 requirements.add(b'relshared')
3480 requirements.add(b'relshared')
3483 else:
3481 else:
3484 requirements.add(b'shared')
3482 requirements.add(b'shared')
3485
3483
3486 return requirements
3484 return requirements
3487
3485
3488 if b'backend' not in createopts:
3486 if b'backend' not in createopts:
3489 raise error.ProgrammingError(
3487 raise error.ProgrammingError(
3490 b'backend key not present in createopts; '
3488 b'backend key not present in createopts; '
3491 b'was defaultcreateopts() called?'
3489 b'was defaultcreateopts() called?'
3492 )
3490 )
3493
3491
3494 if createopts[b'backend'] != b'revlogv1':
3492 if createopts[b'backend'] != b'revlogv1':
3495 raise error.Abort(
3493 raise error.Abort(
3496 _(
3494 _(
3497 b'unable to determine repository requirements for '
3495 b'unable to determine repository requirements for '
3498 b'storage backend: %s'
3496 b'storage backend: %s'
3499 )
3497 )
3500 % createopts[b'backend']
3498 % createopts[b'backend']
3501 )
3499 )
3502
3500
3503 requirements = {b'revlogv1'}
3501 requirements = {b'revlogv1'}
3504 if ui.configbool(b'format', b'usestore'):
3502 if ui.configbool(b'format', b'usestore'):
3505 requirements.add(b'store')
3503 requirements.add(b'store')
3506 if ui.configbool(b'format', b'usefncache'):
3504 if ui.configbool(b'format', b'usefncache'):
3507 requirements.add(b'fncache')
3505 requirements.add(b'fncache')
3508 if ui.configbool(b'format', b'dotencode'):
3506 if ui.configbool(b'format', b'dotencode'):
3509 requirements.add(b'dotencode')
3507 requirements.add(b'dotencode')
3510
3508
3511 compengine = ui.config(b'format', b'revlog-compression')
3509 compengine = ui.config(b'format', b'revlog-compression')
3512 if compengine not in util.compengines:
3510 if compengine not in util.compengines:
3513 raise error.Abort(
3511 raise error.Abort(
3514 _(
3512 _(
3515 b'compression engine %s defined by '
3513 b'compression engine %s defined by '
3516 b'format.revlog-compression not available'
3514 b'format.revlog-compression not available'
3517 )
3515 )
3518 % compengine,
3516 % compengine,
3519 hint=_(
3517 hint=_(
3520 b'run "hg debuginstall" to list available '
3518 b'run "hg debuginstall" to list available '
3521 b'compression engines'
3519 b'compression engines'
3522 ),
3520 ),
3523 )
3521 )
3524
3522
3525 # zlib is the historical default and doesn't need an explicit requirement.
3523 # zlib is the historical default and doesn't need an explicit requirement.
3526 elif compengine == b'zstd':
3524 elif compengine == b'zstd':
3527 requirements.add(b'revlog-compression-zstd')
3525 requirements.add(b'revlog-compression-zstd')
3528 elif compengine != b'zlib':
3526 elif compengine != b'zlib':
3529 requirements.add(b'exp-compression-%s' % compengine)
3527 requirements.add(b'exp-compression-%s' % compengine)
3530
3528
3531 if scmutil.gdinitconfig(ui):
3529 if scmutil.gdinitconfig(ui):
3532 requirements.add(b'generaldelta')
3530 requirements.add(b'generaldelta')
3533 if ui.configbool(b'format', b'sparse-revlog'):
3531 if ui.configbool(b'format', b'sparse-revlog'):
3534 requirements.add(SPARSEREVLOG_REQUIREMENT)
3532 requirements.add(SPARSEREVLOG_REQUIREMENT)
3535
3533
3536 # experimental config: format.exp-use-side-data
3534 # experimental config: format.exp-use-side-data
3537 if ui.configbool(b'format', b'exp-use-side-data'):
3535 if ui.configbool(b'format', b'exp-use-side-data'):
3538 requirements.add(SIDEDATA_REQUIREMENT)
3536 requirements.add(SIDEDATA_REQUIREMENT)
3539 # experimental config: format.exp-use-copies-side-data-changeset
3537 # experimental config: format.exp-use-copies-side-data-changeset
3540 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3538 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3541 requirements.add(SIDEDATA_REQUIREMENT)
3539 requirements.add(SIDEDATA_REQUIREMENT)
3542 requirements.add(COPIESSDC_REQUIREMENT)
3540 requirements.add(COPIESSDC_REQUIREMENT)
3543 if ui.configbool(b'experimental', b'treemanifest'):
3541 if ui.configbool(b'experimental', b'treemanifest'):
3544 requirements.add(b'treemanifest')
3542 requirements.add(b'treemanifest')
3545
3543
3546 revlogv2 = ui.config(b'experimental', b'revlogv2')
3544 revlogv2 = ui.config(b'experimental', b'revlogv2')
3547 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3545 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3548 requirements.remove(b'revlogv1')
3546 requirements.remove(b'revlogv1')
3549 # generaldelta is implied by revlogv2.
3547 # generaldelta is implied by revlogv2.
3550 requirements.discard(b'generaldelta')
3548 requirements.discard(b'generaldelta')
3551 requirements.add(REVLOGV2_REQUIREMENT)
3549 requirements.add(REVLOGV2_REQUIREMENT)
3552 # experimental config: format.internal-phase
3550 # experimental config: format.internal-phase
3553 if ui.configbool(b'format', b'internal-phase'):
3551 if ui.configbool(b'format', b'internal-phase'):
3554 requirements.add(b'internal-phase')
3552 requirements.add(b'internal-phase')
3555
3553
3556 if createopts.get(b'narrowfiles'):
3554 if createopts.get(b'narrowfiles'):
3557 requirements.add(repository.NARROW_REQUIREMENT)
3555 requirements.add(repository.NARROW_REQUIREMENT)
3558
3556
3559 if createopts.get(b'lfs'):
3557 if createopts.get(b'lfs'):
3560 requirements.add(b'lfs')
3558 requirements.add(b'lfs')
3561
3559
3562 if ui.configbool(b'format', b'bookmarks-in-store'):
3560 if ui.configbool(b'format', b'bookmarks-in-store'):
3563 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3561 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3564
3562
3565 return requirements
3563 return requirements
3566
3564
3567
3565
3568 def filterknowncreateopts(ui, createopts):
3566 def filterknowncreateopts(ui, createopts):
3569 """Filters a dict of repo creation options against options that are known.
3567 """Filters a dict of repo creation options against options that are known.
3570
3568
3571 Receives a dict of repo creation options and returns a dict of those
3569 Receives a dict of repo creation options and returns a dict of those
3572 options that we don't know how to handle.
3570 options that we don't know how to handle.
3573
3571
3574 This function is called as part of repository creation. If the
3572 This function is called as part of repository creation. If the
3575 returned dict contains any items, repository creation will not
3573 returned dict contains any items, repository creation will not
3576 be allowed, as it means there was a request to create a repository
3574 be allowed, as it means there was a request to create a repository
3577 with options not recognized by loaded code.
3575 with options not recognized by loaded code.
3578
3576
3579 Extensions can wrap this function to filter out creation options
3577 Extensions can wrap this function to filter out creation options
3580 they know how to handle.
3578 they know how to handle.
3581 """
3579 """
3582 known = {
3580 known = {
3583 b'backend',
3581 b'backend',
3584 b'lfs',
3582 b'lfs',
3585 b'narrowfiles',
3583 b'narrowfiles',
3586 b'sharedrepo',
3584 b'sharedrepo',
3587 b'sharedrelative',
3585 b'sharedrelative',
3588 b'shareditems',
3586 b'shareditems',
3589 b'shallowfilestore',
3587 b'shallowfilestore',
3590 }
3588 }
3591
3589
3592 return {k: v for k, v in createopts.items() if k not in known}
3590 return {k: v for k, v in createopts.items() if k not in known}
3593
3591
3594
3592
3595 def createrepository(ui, path, createopts=None):
3593 def createrepository(ui, path, createopts=None):
3596 """Create a new repository in a vfs.
3594 """Create a new repository in a vfs.
3597
3595
3598 ``path`` path to the new repo's working directory.
3596 ``path`` path to the new repo's working directory.
3599 ``createopts`` options for the new repository.
3597 ``createopts`` options for the new repository.
3600
3598
3601 The following keys for ``createopts`` are recognized:
3599 The following keys for ``createopts`` are recognized:
3602
3600
3603 backend
3601 backend
3604 The storage backend to use.
3602 The storage backend to use.
3605 lfs
3603 lfs
3606 Repository will be created with ``lfs`` requirement. The lfs extension
3604 Repository will be created with ``lfs`` requirement. The lfs extension
3607 will automatically be loaded when the repository is accessed.
3605 will automatically be loaded when the repository is accessed.
3608 narrowfiles
3606 narrowfiles
3609 Set up repository to support narrow file storage.
3607 Set up repository to support narrow file storage.
3610 sharedrepo
3608 sharedrepo
3611 Repository object from which storage should be shared.
3609 Repository object from which storage should be shared.
3612 sharedrelative
3610 sharedrelative
3613 Boolean indicating if the path to the shared repo should be
3611 Boolean indicating if the path to the shared repo should be
3614 stored as relative. By default, the pointer to the "parent" repo
3612 stored as relative. By default, the pointer to the "parent" repo
3615 is stored as an absolute path.
3613 is stored as an absolute path.
3616 shareditems
3614 shareditems
3617 Set of items to share to the new repository (in addition to storage).
3615 Set of items to share to the new repository (in addition to storage).
3618 shallowfilestore
3616 shallowfilestore
3619 Indicates that storage for files should be shallow (not all ancestor
3617 Indicates that storage for files should be shallow (not all ancestor
3620 revisions are known).
3618 revisions are known).
3621 """
3619 """
3622 createopts = defaultcreateopts(ui, createopts=createopts)
3620 createopts = defaultcreateopts(ui, createopts=createopts)
3623
3621
3624 unknownopts = filterknowncreateopts(ui, createopts)
3622 unknownopts = filterknowncreateopts(ui, createopts)
3625
3623
3626 if not isinstance(unknownopts, dict):
3624 if not isinstance(unknownopts, dict):
3627 raise error.ProgrammingError(
3625 raise error.ProgrammingError(
3628 b'filterknowncreateopts() did not return a dict'
3626 b'filterknowncreateopts() did not return a dict'
3629 )
3627 )
3630
3628
3631 if unknownopts:
3629 if unknownopts:
3632 raise error.Abort(
3630 raise error.Abort(
3633 _(
3631 _(
3634 b'unable to create repository because of unknown '
3632 b'unable to create repository because of unknown '
3635 b'creation option: %s'
3633 b'creation option: %s'
3636 )
3634 )
3637 % b', '.join(sorted(unknownopts)),
3635 % b', '.join(sorted(unknownopts)),
3638 hint=_(b'is a required extension not loaded?'),
3636 hint=_(b'is a required extension not loaded?'),
3639 )
3637 )
3640
3638
3641 requirements = newreporequirements(ui, createopts=createopts)
3639 requirements = newreporequirements(ui, createopts=createopts)
3642
3640
3643 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3641 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3644
3642
3645 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3643 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3646 if hgvfs.exists():
3644 if hgvfs.exists():
3647 raise error.RepoError(_(b'repository %s already exists') % path)
3645 raise error.RepoError(_(b'repository %s already exists') % path)
3648
3646
3649 if b'sharedrepo' in createopts:
3647 if b'sharedrepo' in createopts:
3650 sharedpath = createopts[b'sharedrepo'].sharedpath
3648 sharedpath = createopts[b'sharedrepo'].sharedpath
3651
3649
3652 if createopts.get(b'sharedrelative'):
3650 if createopts.get(b'sharedrelative'):
3653 try:
3651 try:
3654 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3652 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3655 except (IOError, ValueError) as e:
3653 except (IOError, ValueError) as e:
3656 # ValueError is raised on Windows if the drive letters differ
3654 # ValueError is raised on Windows if the drive letters differ
3657 # on each path.
3655 # on each path.
3658 raise error.Abort(
3656 raise error.Abort(
3659 _(b'cannot calculate relative path'),
3657 _(b'cannot calculate relative path'),
3660 hint=stringutil.forcebytestr(e),
3658 hint=stringutil.forcebytestr(e),
3661 )
3659 )
3662
3660
3663 if not wdirvfs.exists():
3661 if not wdirvfs.exists():
3664 wdirvfs.makedirs()
3662 wdirvfs.makedirs()
3665
3663
3666 hgvfs.makedir(notindexed=True)
3664 hgvfs.makedir(notindexed=True)
3667 if b'sharedrepo' not in createopts:
3665 if b'sharedrepo' not in createopts:
3668 hgvfs.mkdir(b'cache')
3666 hgvfs.mkdir(b'cache')
3669 hgvfs.mkdir(b'wcache')
3667 hgvfs.mkdir(b'wcache')
3670
3668
3671 if b'store' in requirements and b'sharedrepo' not in createopts:
3669 if b'store' in requirements and b'sharedrepo' not in createopts:
3672 hgvfs.mkdir(b'store')
3670 hgvfs.mkdir(b'store')
3673
3671
3674 # We create an invalid changelog outside the store so very old
3672 # We create an invalid changelog outside the store so very old
3675 # Mercurial versions (which didn't know about the requirements
3673 # Mercurial versions (which didn't know about the requirements
3676 # file) encounter an error on reading the changelog. This
3674 # file) encounter an error on reading the changelog. This
3677 # effectively locks out old clients and prevents them from
3675 # effectively locks out old clients and prevents them from
3678 # mucking with a repo in an unknown format.
3676 # mucking with a repo in an unknown format.
3679 #
3677 #
3680 # The revlog header has version 2, which won't be recognized by
3678 # The revlog header has version 2, which won't be recognized by
3681 # such old clients.
3679 # such old clients.
3682 hgvfs.append(
3680 hgvfs.append(
3683 b'00changelog.i',
3681 b'00changelog.i',
3684 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3682 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3685 b'layout',
3683 b'layout',
3686 )
3684 )
3687
3685
3688 scmutil.writerequires(hgvfs, requirements)
3686 scmutil.writerequires(hgvfs, requirements)
3689
3687
3690 # Write out file telling readers where to find the shared store.
3688 # Write out file telling readers where to find the shared store.
3691 if b'sharedrepo' in createopts:
3689 if b'sharedrepo' in createopts:
3692 hgvfs.write(b'sharedpath', sharedpath)
3690 hgvfs.write(b'sharedpath', sharedpath)
3693
3691
3694 if createopts.get(b'shareditems'):
3692 if createopts.get(b'shareditems'):
3695 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3693 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3696 hgvfs.write(b'shared', shared)
3694 hgvfs.write(b'shared', shared)
3697
3695
3698
3696
3699 def poisonrepository(repo):
3697 def poisonrepository(repo):
3700 """Poison a repository instance so it can no longer be used."""
3698 """Poison a repository instance so it can no longer be used."""
3701 # Perform any cleanup on the instance.
3699 # Perform any cleanup on the instance.
3702 repo.close()
3700 repo.close()
3703
3701
3704 # Our strategy is to replace the type of the object with one that
3702 # Our strategy is to replace the type of the object with one that
3705 # has all attribute lookups result in error.
3703 # has all attribute lookups result in error.
3706 #
3704 #
3707 # But we have to allow the close() method because some constructors
3705 # But we have to allow the close() method because some constructors
3708 # of repos call close() on repo references.
3706 # of repos call close() on repo references.
3709 class poisonedrepository(object):
3707 class poisonedrepository(object):
3710 def __getattribute__(self, item):
3708 def __getattribute__(self, item):
3711 if item == 'close':
3709 if item == 'close':
3712 return object.__getattribute__(self, item)
3710 return object.__getattribute__(self, item)
3713
3711
3714 raise error.ProgrammingError(
3712 raise error.ProgrammingError(
3715 b'repo instances should not be used after unshare'
3713 b'repo instances should not be used after unshare'
3716 )
3714 )
3717
3715
3718 def close(self):
3716 def close(self):
3719 pass
3717 pass
3720
3718
3721 # We may have a repoview, which intercepts __setattr__. So be sure
3719 # We may have a repoview, which intercepts __setattr__. So be sure
3722 # we operate at the lowest level possible.
3720 # we operate at the lowest level possible.
3723 object.__setattr__(repo, '__class__', poisonedrepository)
3721 object.__setattr__(repo, '__class__', poisonedrepository)
General Comments 0
You need to be logged in to leave comments. Login now